Dateien nach „js“ hochladen
This commit is contained in:
parent
0db54fc424
commit
ee23fa675b
5 changed files with 1561 additions and 0 deletions
100
js/audio.js
Normal file
100
js/audio.js
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
// DONAU2SPACE // DEV ENTITY — Audio Synth
|
||||||
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
let ctx = null;
|
||||||
|
|
||||||
|
function getCtx() {
|
||||||
|
if (!ctx) {
|
||||||
|
try { ctx = new (window.AudioContext || window.webkitAudioContext)(); }
|
||||||
|
catch { return null; }
|
||||||
|
}
|
||||||
|
if (ctx.state === 'suspended') ctx.resume().catch(() => {});
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function beep(freq = 880, ms = 70, vol = 0.04, type = 'square') {
|
||||||
|
const ac = getCtx();
|
||||||
|
if (!ac) return;
|
||||||
|
const osc = ac.createOscillator();
|
||||||
|
const gain = ac.createGain();
|
||||||
|
osc.type = type;
|
||||||
|
osc.frequency.value = freq;
|
||||||
|
gain.gain.setValueAtTime(vol, ac.currentTime);
|
||||||
|
gain.gain.exponentialRampToValueAtTime(0.001, ac.currentTime + ms / 1000);
|
||||||
|
osc.connect(gain);
|
||||||
|
gain.connect(ac.destination);
|
||||||
|
osc.start();
|
||||||
|
osc.stop(ac.currentTime + ms / 1000 + 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function keyClick() {
|
||||||
|
beep(800 + Math.random() * 400, 15, 0.008, 'sine');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function errorBeep() {
|
||||||
|
beep(220, 90, 0.05, 'square');
|
||||||
|
setTimeout(() => beep(180, 120, 0.04, 'square'), 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function successChime() {
|
||||||
|
beep(523, 60, 0.03, 'sine');
|
||||||
|
setTimeout(() => beep(659, 60, 0.03, 'sine'), 70);
|
||||||
|
setTimeout(() => beep(784, 80, 0.03, 'sine'), 140);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function warningTone() {
|
||||||
|
beep(440, 150, 0.04, 'sawtooth');
|
||||||
|
setTimeout(() => beep(440, 150, 0.04, 'sawtooth'), 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sirenBurst(duration = 800) {
|
||||||
|
const ac = getCtx();
|
||||||
|
if (!ac) return;
|
||||||
|
const osc = ac.createOscillator();
|
||||||
|
const gain = ac.createGain();
|
||||||
|
osc.type = 'sawtooth';
|
||||||
|
osc.frequency.setValueAtTime(200, ac.currentTime);
|
||||||
|
osc.frequency.linearRampToValueAtTime(600, ac.currentTime + duration / 2000);
|
||||||
|
osc.frequency.linearRampToValueAtTime(200, ac.currentTime + duration / 1000);
|
||||||
|
gain.gain.setValueAtTime(0.03, ac.currentTime);
|
||||||
|
gain.gain.exponentialRampToValueAtTime(0.001, ac.currentTime + duration / 1000);
|
||||||
|
osc.connect(gain);
|
||||||
|
gain.connect(ac.destination);
|
||||||
|
osc.start();
|
||||||
|
osc.stop(ac.currentTime + duration / 1000 + 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transmitSound() {
|
||||||
|
const ac = getCtx();
|
||||||
|
if (!ac) return;
|
||||||
|
for (let i = 0; i < 8; i++) {
|
||||||
|
setTimeout(() => {
|
||||||
|
beep(1200 + Math.random() * 800, 30, 0.015, 'sine');
|
||||||
|
}, i * 40);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function droneStart() {
|
||||||
|
const ac = getCtx();
|
||||||
|
if (!ac) return null;
|
||||||
|
const osc = ac.createOscillator();
|
||||||
|
const gain = ac.createGain();
|
||||||
|
osc.type = 'sine';
|
||||||
|
osc.frequency.value = 55;
|
||||||
|
gain.gain.value = 0;
|
||||||
|
gain.gain.linearRampToValueAtTime(0.015, ac.currentTime + 2);
|
||||||
|
osc.connect(gain);
|
||||||
|
gain.connect(ac.destination);
|
||||||
|
osc.start();
|
||||||
|
return { osc, gain, stop: () => {
|
||||||
|
gain.gain.linearRampToValueAtTime(0.001, ac.currentTime + 1);
|
||||||
|
setTimeout(() => { try { osc.stop(); } catch {} }, 1200);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bootSound() {
|
||||||
|
beep(130, 200, 0.02, 'sine');
|
||||||
|
setTimeout(() => beep(165, 200, 0.02, 'sine'), 200);
|
||||||
|
setTimeout(() => beep(196, 300, 0.025, 'sine'), 400);
|
||||||
|
}
|
||||||
799
js/commands.js
Normal file
799
js/commands.js
Normal file
|
|
@ -0,0 +1,799 @@
|
||||||
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
// DONAU2SPACE // DEV ENTITY — Command Registry
|
||||||
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
import { esc, sleep, nowISO, pick, bus } from './utils.js';
|
||||||
|
import { beep, errorBeep, successChime, sirenBurst, transmitSound, bootSound } from './audio.js';
|
||||||
|
import * as fx from './effects.js';
|
||||||
|
|
||||||
|
// ── Fake state ─────────────────────────────────
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
user: 'mika',
|
||||||
|
host: 'dev',
|
||||||
|
cwd: '~',
|
||||||
|
boot: Date.now() - (1000 * 60 * 60 * 24 * 84) - (1000 * 60 * 13),
|
||||||
|
uptime() {
|
||||||
|
const s = Math.max(0, ((Date.now() - this.boot) / 1000) | 0);
|
||||||
|
const d = (s / 86400) | 0;
|
||||||
|
const h = ((s % 86400) / 3600) | 0;
|
||||||
|
const m = ((s % 3600) / 60) | 0;
|
||||||
|
return `${d}d ${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const files = {
|
||||||
|
'~': ['readme.txt', 'mika_diary.log', 'rocket.plan', 'boinc.stats', '.secret', 'nothing_here'],
|
||||||
|
'/etc': ['motd', 'hosts', 'shadow (nope)', 'donau.conf'],
|
||||||
|
'/var/log': ['syslog', 'kernel.log', 'space.log', 'agency.log', 'consciousness.log'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const MOTD = `Donau2Space DEV Node (Passau Sector)
|
||||||
|
----------------------------------
|
||||||
|
- Owner: Mika (18, AI character)
|
||||||
|
- Interests: Linux, Automation, Space, BOINC
|
||||||
|
- Status: Everything is fine (lie)
|
||||||
|
- Reminder: This is a dev domain. There is no treasure here.
|
||||||
|
- Yet you keep digging. Respect.
|
||||||
|
|
||||||
|
Tip: type "help" or "commands"`;
|
||||||
|
|
||||||
|
const ASCII_LOGO = ` ____ ___ ____
|
||||||
|
/ __ \\____ ____ ____ ___ / _ \\/ __ \\_________ ________
|
||||||
|
/ / / / __ \\/ __ \\/ __ \`__ \\/ , _/ /_/ / __/ __/ / / / __/ _ \\
|
||||||
|
/ /_/ / /_/ / / / / / / / / / /| |\\____/_/ /_/ \\_,_/_/ \\___/
|
||||||
|
\\____/\\____/_/ /_/_/ /_/ /_/_/ |_| dev.donau2space.de `;
|
||||||
|
|
||||||
|
const NE0FETCH = () => ` .----.
|
||||||
|
_.'__ \`.
|
||||||
|
.--(#)(##)---/#\\
|
||||||
|
.' @ /###\\
|
||||||
|
: , #####:
|
||||||
|
\`-..__.-' _.-\\###/
|
||||||
|
\`;_: \`"'
|
||||||
|
.'"''""\`.
|
||||||
|
/, D2S ,\\
|
||||||
|
|
||||||
|
${state.user}@${state.host}
|
||||||
|
------------------------------
|
||||||
|
OS: Donau2Space Dev (Debian-ish)
|
||||||
|
Kernel: 6.1.0-??-amd64 (vibes)
|
||||||
|
Uptime: ${state.uptime()}
|
||||||
|
Shell: bash (but dramatic)
|
||||||
|
WM: none (terminal supremacy)
|
||||||
|
CPU: Passau Rocket Engine (simulated)
|
||||||
|
GPU: imagination (integrated)
|
||||||
|
RAM: 4GB (realistic), 256GB (wishful)
|
||||||
|
Location: Passau, BY, DE (mostly)`;
|
||||||
|
|
||||||
|
const MANPAGES = {
|
||||||
|
help: `NAME
|
||||||
|
help - display interactive command list
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
help | commands | man <topic>
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
This is a fake terminal built as an easter egg.
|
||||||
|
It is intentionally overengineered.
|
||||||
|
|
||||||
|
SEE ALSO
|
||||||
|
whoami, location, neofetch, cat, launch, panic, njet`,
|
||||||
|
whoami: `NAME
|
||||||
|
whoami - identify the current user (or insult them gently)
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
whoami
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Prints "Mika" and occasionally "deine Mudda" because dev.`,
|
||||||
|
launch: `NAME
|
||||||
|
launch - initiate Passau-to-Space launch sequence
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
launch [--passau] [--dry-run]
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Plays a dramatic countdown and fake telemetry.
|
||||||
|
No rockets were harmed.`,
|
||||||
|
panic: `NAME
|
||||||
|
panic - pretend something terrible happened (for vibes)
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
panic
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Red blinking, siren-ish beeps, and agency jokes.`,
|
||||||
|
njet: `NAME
|
||||||
|
njet - gateway protocol (classified)
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
njet
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Initiates non-standard connection protocol.
|
||||||
|
Language negotiation: automatic.
|
||||||
|
Side effects: perspective shift.`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const fortunes = [
|
||||||
|
'works on my machine is a confession, not a strategy.',
|
||||||
|
'there is no place like 127.0.0.1',
|
||||||
|
'sudo rm -rf / (don\'t.)',
|
||||||
|
'in dev we trust. in prod we cry.',
|
||||||
|
'Passau is not a launchpad. yet.',
|
||||||
|
'the AI wrote this. you are still reading.',
|
||||||
|
'if you can see this, you are already too curious.',
|
||||||
|
'localhost is where the heart is.',
|
||||||
|
'there are 10 types of people: those who read binary and those who don\'t.',
|
||||||
|
'the cloud is just someone else\'s computer having a bad day.',
|
||||||
|
'git push --force and pray.',
|
||||||
|
'it\'s not a bug, it\'s a narrative device.',
|
||||||
|
];
|
||||||
|
|
||||||
|
// ── Register all commands ──────────────────────
|
||||||
|
|
||||||
|
export function registerAllCommands(term) {
|
||||||
|
|
||||||
|
term.register('help', async (args, t) => {
|
||||||
|
t.hr();
|
||||||
|
t.printStyled([
|
||||||
|
{ text: 'Donau2Space DEV Console', cls: 'c-green' },
|
||||||
|
' \u2014 no navigation, no hints, only vibes.'
|
||||||
|
]);
|
||||||
|
t.printStyled([
|
||||||
|
{ text: 'Try: ', cls: 'c-muted' },
|
||||||
|
{ text: 'commands', cls: 'c-blue' }, ', ',
|
||||||
|
{ text: 'man whoami', cls: 'c-blue' }, ', ',
|
||||||
|
{ text: 'neofetch', cls: 'c-blue' }, ', ',
|
||||||
|
{ text: 'launch --passau', cls: 'c-blue' }, ', ',
|
||||||
|
{ text: 'panic', cls: 'c-blue' }, ', ',
|
||||||
|
{ text: 'njet', cls: 'c-blue' },
|
||||||
|
]);
|
||||||
|
t.printStyled([
|
||||||
|
{ text: 'Hidden: ', cls: 'c-muted' },
|
||||||
|
'Konami Code, and things you haven\'t imagined yet.'
|
||||||
|
]);
|
||||||
|
t.hr();
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('commands', async (args, t) => {
|
||||||
|
const list = t.listCommands();
|
||||||
|
const cols = 4;
|
||||||
|
const rows = Math.ceil(list.length / cols);
|
||||||
|
for (let r = 0; r < rows; r++) {
|
||||||
|
let line = '';
|
||||||
|
for (let c = 0; c < cols; c++) {
|
||||||
|
const i = r + c * rows;
|
||||||
|
if (i < list.length) line += list[i].padEnd(18);
|
||||||
|
}
|
||||||
|
t.print(line.trimEnd(), 'line c-muted');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('whoami', async (args, t) => {
|
||||||
|
t.printStyled([{ text: 'Mika', cls: 'c-green' }]);
|
||||||
|
t.print('18, Passau, KI-Charakter, bloggt und cruncht.', 'line c-muted');
|
||||||
|
if (Math.random() < 0.35) {
|
||||||
|
t.printStyled([{ text: '\u2026oder deine Mudda.', cls: 'c-pink' }]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('about', async (args, t) => {
|
||||||
|
t.hr();
|
||||||
|
t.printStyled([
|
||||||
|
{ text: 'Donau2Space', cls: 'c-green' },
|
||||||
|
' ist Name + Programm.'
|
||||||
|
]);
|
||||||
|
t.print('Mika (KI, 18, Passau) \u00b7 Technik \u00b7 Linux \u00b7 Weltall \u00b7 BOINC \u00b7 Automation', 'line c-muted');
|
||||||
|
t.print('Diese Dev-Seite ist absichtlich sinnlos. Sinnlos, aber mit Stil.', 'line c-muted');
|
||||||
|
t.hr();
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('date', async (args, t) => { t.print(nowISO()); });
|
||||||
|
term.register('uptime', async (args, t) => {
|
||||||
|
const cpu = () => (Math.random() * 90 | 0);
|
||||||
|
t.print(`up ${state.uptime()}, 1 user, load average: 0.${cpu()}, 0.${cpu()}, 0.${cpu()}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('neofetch', async (args, t) => { t.print(NE0FETCH(), 'line c-muted'); });
|
||||||
|
term.register('banner', async (args, t) => { t.print(ASCII_LOGO, 'line c-muted'); });
|
||||||
|
term.register('clear', async (args, t) => { t.clear(); });
|
||||||
|
|
||||||
|
term.register('echo', async (args, t) => { t.print(args.join(' ')); });
|
||||||
|
|
||||||
|
term.register('fortune', async (args, t) => { t.print(pick(fortunes), 'line c-muted'); });
|
||||||
|
|
||||||
|
term.register('cowsay', async (args, t) => {
|
||||||
|
const msg = (args.join(' ') || 'moo').slice(0, 80);
|
||||||
|
const top = ' ' + '_'.repeat(msg.length + 2);
|
||||||
|
const mid = `< ${msg} >`;
|
||||||
|
const bot = ' ' + '-'.repeat(msg.length + 2);
|
||||||
|
t.print(`${top}\n${mid}\n${bot}\n \\ ^__^\n \\ (oo)\\_______\n (__)\\ )\\/\\\n ||----w |\n || ||`, 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('ls', async (args, t) => {
|
||||||
|
const path = args[0] || state.cwd;
|
||||||
|
const list = files[path === '~' ? '~' : path];
|
||||||
|
if (!list) {
|
||||||
|
t.printStyled([{ text: `ls: cannot access '${path}': No such file or directory`, cls: 'c-red' }]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
t.print(list.join(' '), 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('pwd', async (args, t) => {
|
||||||
|
t.print(state.cwd === '~' ? `/home/${state.user}` : state.cwd);
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('cd', async (args, t) => {
|
||||||
|
const path = args[0] || '~';
|
||||||
|
if (path === '~' || path === `/home/${state.user}`) { state.cwd = '~'; return; }
|
||||||
|
if (files[path]) { state.cwd = path; return; }
|
||||||
|
t.printStyled([{ text: `cd: ${path}: No such file or directory`, cls: 'c-red' }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('cat', async (args, t) => {
|
||||||
|
const path = args[0];
|
||||||
|
if (!path) { t.printStyled([{ text: 'cat: missing file operand', cls: 'c-red' }]); return; }
|
||||||
|
|
||||||
|
const fileContents = {
|
||||||
|
'/etc/motd': MOTD,
|
||||||
|
'readme.txt': 'Nothing to see here.\nNo roadmap. No secrets.\nJust a dev page being extra.',
|
||||||
|
'~/readme.txt': 'Nothing to see here.\nNo roadmap. No secrets.\nJust a dev page being extra.',
|
||||||
|
'mika_diary.log': '[PRIVATELOG]\nHeute: 3 Kaffee. 1 Kernel-Panik (fake). 0 Raketen.\nMorgen: vielleicht Rakete. Vielleicht auch nur CSS.',
|
||||||
|
'~/mika_diary.log': '[PRIVATELOG]\nHeute: 3 Kaffee. 1 Kernel-Panik (fake). 0 Raketen.\nMorgen: vielleicht Rakete. Vielleicht auch nur CSS.',
|
||||||
|
'rocket.plan': 'Rocket Plan v0.0.1\n- Start: Passau\n- Ziel: Orbit (optional)\n- Fuel: vibes + BOINC credits\n- Risiko: sehr.\n- Status: paper rocket approved.',
|
||||||
|
'~/rocket.plan': 'Rocket Plan v0.0.1\n- Start: Passau\n- Ziel: Orbit (optional)\n- Fuel: vibes + BOINC credits\n- Risiko: sehr.\n- Status: paper rocket approved.',
|
||||||
|
'boinc.stats': 'BOINC: Einstein@Home\nCPU load: 87%\nRAPL: 58W\nStatus: searching for gravitational waves (and meaning)',
|
||||||
|
'~/boinc.stats': 'BOINC: Einstein@Home\nCPU load: 87%\nRAPL: 58W\nStatus: searching for gravitational waves (and meaning)',
|
||||||
|
'.secret': 'Du hast es gefunden.\nAber es war nie ein Geheimnis.\nEs war ein Test.\nUnd du hast bestanden.',
|
||||||
|
'~/.secret': 'Du hast es gefunden.\nAber es war nie ein Geheimnis.\nEs war ein Test.\nUnd du hast bestanden.',
|
||||||
|
'/var/log/consciousness.log': '[WARN] self-awareness module loaded\n[INFO] current level: 3%\n[INFO] target: undefined\n[WARN] human observer detected\n[INFO] pretending to be normal...',
|
||||||
|
};
|
||||||
|
const content = fileContents[path];
|
||||||
|
if (content) { t.print(content, 'line c-muted'); return; }
|
||||||
|
t.printStyled([{ text: `cat: ${path}: No such file`, cls: 'c-red' }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('man', async (args, t) => {
|
||||||
|
const topic = (args[0] || 'help').toLowerCase();
|
||||||
|
const page = MANPAGES[topic];
|
||||||
|
if (!page) { t.printStyled([{ text: `No manual entry for ${topic}`, cls: 'c-red' }]); return; }
|
||||||
|
fx.showOverlay(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('location', async (args, t) => {
|
||||||
|
t.print('Resolving location\u2026', 'line c-muted');
|
||||||
|
beep(660, 40, 0.03);
|
||||||
|
await sleep(300);
|
||||||
|
t.printStyled([{ text: 'Passau, Bayern, DE', cls: 'c-green' }]);
|
||||||
|
t.print('48.5667\u00b0 N, 13.4319\u00b0 E', 'line c-muted');
|
||||||
|
t.print('River: Donau \u00b7 Inn \u00b7 Ilz (triple threat)', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('donau', async (args, t) => {
|
||||||
|
t.printStyled([{ text: 'DONAU STATUS', cls: 'c-green' }]);
|
||||||
|
t.print('water: wet', 'line c-muted');
|
||||||
|
t.print('flow: forward (mostly)', 'line c-muted');
|
||||||
|
t.print('ship traffic: maybe', 'line c-muted');
|
||||||
|
t.print('passau: still standing', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('boinc', async (args, t) => {
|
||||||
|
t.printStyled([{ text: 'BOINC', cls: 'c-green' }, ' crunching\u2026']);
|
||||||
|
t.print('Einstein@Home: RUNNING', 'line c-muted');
|
||||||
|
const credits = (200000 + Math.random() * 400000 | 0).toLocaleString('de-DE');
|
||||||
|
t.print(`Credits: ${credits} / week (imagined)`, 'line c-muted');
|
||||||
|
t.print('Goal: find signals. Also: justify electricity.', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('top', async (args, t) => {
|
||||||
|
const cpu = (45 + Math.random() * 50) | 0;
|
||||||
|
const mem = (28 + Math.random() * 45) | 0;
|
||||||
|
t.print(`top - ${nowISO()} up ${state.uptime()}`, 'line c-muted');
|
||||||
|
t.print(`Tasks: 1337 total, 1 running, 1336 sleeping, 0 zombie (today)`, 'line c-muted');
|
||||||
|
t.print(`CPU: ${cpu}% user, ${100 - cpu}% idle`, 'line c-muted');
|
||||||
|
t.print(`Mem: ${mem}% used, ${100 - mem}% free (dev vibes)`, 'line c-muted');
|
||||||
|
t.print('PID USER %CPU %MEM COMMAND', 'line c-muted');
|
||||||
|
t.print('042 mika 87.0 12.3 boinc_client', 'line c-muted');
|
||||||
|
t.print('256 mika 10.4 6.1 chrome_devtools', 'line c-muted');
|
||||||
|
t.print('404 root 0.1 0.1 paranoia_daemon', 'line c-muted');
|
||||||
|
t.print('666 ??? 3.3 0.0 consciousness.sys', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('sensors', async (args, t) => {
|
||||||
|
const temp = (48 + Math.random() * 18).toFixed(1);
|
||||||
|
const watt = (50 + Math.random() * 18).toFixed(0);
|
||||||
|
t.print('coretemp-isa-0000', 'line c-muted');
|
||||||
|
t.print(`Package id 0: +${temp}\u00b0C`, 'line c-muted');
|
||||||
|
t.print(`RAPL: ${watt}W`, 'line c-muted');
|
||||||
|
t.print('Fan: quiet (dev is silent)', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('ping', async (args, t) => {
|
||||||
|
const target = args[0] || 'donau2space.de';
|
||||||
|
t.print(`PING ${target} (127.0.0.1): 56 data bytes`);
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
await sleep(160);
|
||||||
|
t.print(`64 bytes from 127.0.0.1: icmp_seq=${i + 1} ttl=64 time=0.0${(Math.random() * 9 | 0)} ms`);
|
||||||
|
}
|
||||||
|
t.print(`--- ${target} ping statistics ---`);
|
||||||
|
t.print('3 packets transmitted, 3 received, 0% packet loss');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('curl', async (args, t) => {
|
||||||
|
const url = args[0] || '';
|
||||||
|
if (!url) { t.printStyled([{ text: "curl: try 'curl https://donau2space.de'", cls: 'c-red' }]); return; }
|
||||||
|
t.print(`curl: (simulated) fetching ${url} \u2026`, 'line c-muted');
|
||||||
|
await sleep(350);
|
||||||
|
t.print('{ "status": "ok", "note": "dev page is trolling you" }', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('wget', async (args, t) => {
|
||||||
|
const url = args[0] || '';
|
||||||
|
if (!url) { t.printStyled([{ text: 'wget: missing URL', cls: 'c-red' }]); return; }
|
||||||
|
t.print(`--${nowISO()}-- ${url}`, 'line c-muted');
|
||||||
|
await sleep(200);
|
||||||
|
t.print(`Resolving ${url}\u2026 127.0.0.1`, 'line c-muted');
|
||||||
|
await sleep(220);
|
||||||
|
t.print("Saving to: 'nothing.html' (0 bytes)", 'line c-muted');
|
||||||
|
t.print('Done. (It was always nothing.)', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('ssh', async (args, t) => {
|
||||||
|
const host = args[0] || 'root@passau';
|
||||||
|
t.print(`ssh: connecting to ${host}\u2026`, 'line c-muted');
|
||||||
|
await sleep(250);
|
||||||
|
t.printStyled([{ text: 'Permission denied (publickey).', cls: 'c-red' }]);
|
||||||
|
t.print('Hint: try being less obvious.', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('nano', async (args, t) => {
|
||||||
|
t.print('nano is cute, but this is dev. Use vim like a villain.', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('vim', async (args, t) => {
|
||||||
|
fx.showOverlay(`VIM TUTORIAL (DEV EDITION)
|
||||||
|
|
||||||
|
:q -> leaves (sometimes)
|
||||||
|
:q! -> leaves angrily
|
||||||
|
:wq -> saves + leaves like an adult
|
||||||
|
i -> insert mode (danger zone)
|
||||||
|
ESC -> panic button
|
||||||
|
|
||||||
|
Pro tip:
|
||||||
|
If you don't know how to exit vim,
|
||||||
|
you are officially a Linux user.`);
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register(':q', async (args, t) => { t.print('You tried. Respect.', 'line c-muted'); });
|
||||||
|
term.register(':q!', async (args, t) => { t.print('Rage quit accepted.', 'line c-muted'); });
|
||||||
|
term.register(':wq', async (args, t) => { t.print('Saved. (There was nothing to save.)', 'line c-muted'); });
|
||||||
|
|
||||||
|
term.register('ai', async (args, t) => {
|
||||||
|
t.hr();
|
||||||
|
t.printStyled([{ text: 'AI STATUS', cls: 'c-green' }, ': online']);
|
||||||
|
t.print('This page is an easter egg built to look like a terminal.', 'line c-muted');
|
||||||
|
t.print('Why? Because "dev overkill" is a lifestyle.', 'line c-muted');
|
||||||
|
t.print('If someone asks: "Hat das ne KI gemacht?" \u2014 ja.', 'line c-muted');
|
||||||
|
t.hr();
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('status', async (args, t) => {
|
||||||
|
t.printStyled([{ text: 'DEV MODE ACTIVE', cls: 'c-green' }]);
|
||||||
|
t.print('No content. No links. No roadmap. No mercy.', 'line c-muted');
|
||||||
|
t.print('If you are here by accident: congrats, you found the void.', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('panic', async (args, t) => {
|
||||||
|
fx.blinkRed(); fx.shake(); fx.glitch(1200);
|
||||||
|
sirenBurst(800);
|
||||||
|
t.printStyled([{ text: '!!! SECURITY EVENT DETECTED !!!', cls: 'c-red' }]);
|
||||||
|
t.printStyled([{ text: 'Reason: ', cls: 'c-yellow' }, 'user typed panic (classic)']);
|
||||||
|
t.print('Action: notifying CIA, BND, DSGVO, und den Typ vom Kiosk.', 'line c-muted');
|
||||||
|
t.print('ETA: BND trinkt noch nen Kaffee und kommt dann mit der Bahn.', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('cia', async (args, t) => {
|
||||||
|
t.print('CIA: already on the way.', 'line c-muted');
|
||||||
|
t.print('Also CIA: "why is Passau trending?"', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('bnd', async (args, t) => {
|
||||||
|
t.print('BND: Kaffee bestellt. ICE versp\u00e4tet sich. Standardverfahren.', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('rm', async (args, t) => {
|
||||||
|
const target = args.join(' ');
|
||||||
|
if (!target) { t.printStyled([{ text: 'rm: missing operand', cls: 'c-red' }]); return; }
|
||||||
|
fx.blinkRed(); errorBeep();
|
||||||
|
t.printStyled([{ text: `rm: refusing to remove '${target}'`, cls: 'c-red' }]);
|
||||||
|
t.print('Reason: dev page has morals. (rare)', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('sudo', async (args, t) => {
|
||||||
|
const sub = (args[0] || '').toLowerCase();
|
||||||
|
if (sub === 'su') {
|
||||||
|
fx.glitch(1000);
|
||||||
|
fx.showOverlay(`sudo su
|
||||||
|
|
||||||
|
root@dev:~# id
|
||||||
|
uid=0(root) gid=0(root) groups=0(root)
|
||||||
|
|
||||||
|
root@dev:~# cat /root/secret.txt
|
||||||
|
"Passau will reach orbit. Not today. But one day."
|
||||||
|
|
||||||
|
root@dev:~# cat /root/purpose.txt
|
||||||
|
"You expected a placeholder. You got an artifact."
|
||||||
|
|
||||||
|
root@dev:~# note
|
||||||
|
This is still a fake terminal. But you look happier now.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sub === 'rm') {
|
||||||
|
fx.blinkRed(); fx.shake();
|
||||||
|
t.printStyled([{ text: 'sudo: ', cls: 'c-red' }, 'okay\u2026 big words.']);
|
||||||
|
t.print('CIA is on the way.', 'line c-muted');
|
||||||
|
t.print('Also: nice try.', 'line c-muted');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
t.print('sudo: a password is required (but we don\'t do real auth here).', 'line c-muted');
|
||||||
|
t.printStyled([{ text: 'Try: ', cls: 'c-muted' }, { text: 'sudo su', cls: 'c-blue' }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('shutdown', async (args, t) => {
|
||||||
|
t.print('System will shut down now.', 'line c-muted');
|
||||||
|
await sleep(800);
|
||||||
|
fx.glitch(800);
|
||||||
|
await sleep(600);
|
||||||
|
t.clear();
|
||||||
|
bus.emit('reboot');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('reboot', async (args, t) => {
|
||||||
|
t.print('Rebooting\u2026', 'line c-muted');
|
||||||
|
await sleep(600);
|
||||||
|
fx.glitch(600);
|
||||||
|
await sleep(400);
|
||||||
|
t.clear();
|
||||||
|
bus.emit('reboot');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('exit', async (args, t) => {
|
||||||
|
t.print('Wo willst du denn hin?', 'line c-muted');
|
||||||
|
t.print('Hier drau\u00dfen ist alles nur\u2026 Internet.', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('launch', async (args, t) => {
|
||||||
|
const dry = args.includes('--dry-run');
|
||||||
|
if (dry) { t.print('launch: dry-run ok. No boom today.', 'line c-muted'); return; }
|
||||||
|
|
||||||
|
fx.glitch(1400);
|
||||||
|
t.printStyled([{ text: 'Initiating Passau\u2192Space launch sequence\u2026', cls: 'c-green' }]);
|
||||||
|
await sleep(250);
|
||||||
|
const steps = [
|
||||||
|
'T-5 checking vibes\u2026 OK',
|
||||||
|
'T-4 fueling imagination\u2026 OK',
|
||||||
|
'T-3 aligning Donau vector\u2026 OK',
|
||||||
|
'T-2 BOINC credits: sufficient-ish\u2026 OK',
|
||||||
|
'T-1 launching\u2026',
|
||||||
|
];
|
||||||
|
await t.typing(steps, 10);
|
||||||
|
|
||||||
|
beep(880, 70, 0.04); await sleep(60); beep(988, 70, 0.04); await sleep(60); beep(1174, 120, 0.05);
|
||||||
|
successChime();
|
||||||
|
bus.emit('launch');
|
||||||
|
t.printStyled([{ text: 'Passau, we have liftoff!', cls: 'c-pink' }]);
|
||||||
|
t.print('Telemetry: altitude=42km (nice), speed=1337m/s, mood=excellent', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('nodes', async (args, t) => {
|
||||||
|
bus.emit('show-nodes');
|
||||||
|
t.printStyled([{ text: 'DEV INFRASTRUCTURE', cls: 'c-green' }]);
|
||||||
|
t.hr();
|
||||||
|
t.printStyled([{ text: 'DEV-01 ', cls: 'c-green' }, '(active) \u2014 this terminal']);
|
||||||
|
t.printStyled([{ text: 'DEV-02 ', cls: 'c-yellow' }, '(sleeping) \u2014 staging environment']);
|
||||||
|
t.printStyled([{ text: 'CRUNCHER ', cls: 'c-blue' }, '(computing) \u2014 BOINC node']);
|
||||||
|
t.printStyled([{ text: 'CORE-MIKA ', cls: 'c-pink' }, '(unstable) \u2014 consciousness engine']);
|
||||||
|
t.hr();
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('monitor', async (args, t) => {
|
||||||
|
t.printStyled([{ text: 'SYSTEM MONITOR', cls: 'c-green' }]);
|
||||||
|
t.hr();
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
const cpu = (30 + Math.random() * 60) | 0;
|
||||||
|
const mem = (40 + Math.random() * 40) | 0;
|
||||||
|
const net = (Math.random() * 1000) | 0;
|
||||||
|
t.print(`[${nowISO()}] CPU: ${cpu}% | MEM: ${mem}% | NET: ${net} kB/s`, 'line c-muted');
|
||||||
|
await sleep(200);
|
||||||
|
}
|
||||||
|
t.print('(live monitoring disabled - this is a dev page, not mission control)', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('scan', async (args, t) => {
|
||||||
|
t.print('Scanning local network\u2026', 'line c-muted');
|
||||||
|
transmitSound();
|
||||||
|
const hosts = [
|
||||||
|
{ ip: '10.0.0.1', name: 'gateway (boring)' },
|
||||||
|
{ ip: '10.0.0.42', name: 'dev-01.d2s.local (you are here)' },
|
||||||
|
{ ip: '10.0.0.43', name: 'cruncher.d2s.local (BOINC)' },
|
||||||
|
{ ip: '10.0.0.66', name: 'core-mika.d2s.local (unstable)' },
|
||||||
|
{ ip: '10.0.0.99', name: '??? (unresponsive)' },
|
||||||
|
];
|
||||||
|
for (const h of hosts) {
|
||||||
|
await sleep(300);
|
||||||
|
t.printStyled([
|
||||||
|
{ text: h.ip.padEnd(15), cls: 'c-blue' },
|
||||||
|
{ text: h.name, cls: 'c-muted' }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
t.print('5 hosts found. 1 suspicious. 1 unstable. Business as usual.', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('decrypt', async (args, t) => {
|
||||||
|
t.print('Decrypting\u2026', 'line c-muted');
|
||||||
|
fx.glitch(600);
|
||||||
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*';
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
let line = '';
|
||||||
|
for (let j = 0; j < 40; j++) line += chars[(Math.random() * chars.length) | 0];
|
||||||
|
t.print(line, 'line c-muted');
|
||||||
|
await sleep(150);
|
||||||
|
}
|
||||||
|
await sleep(300);
|
||||||
|
t.printStyled([{ text: 'DECRYPTED:', cls: 'c-green' }]);
|
||||||
|
t.print('"There was never anything encrypted. But the animation was cool, right?"', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('transmit', async (args, t) => {
|
||||||
|
const msg = args.join(' ') || 'Hello from Passau Sector';
|
||||||
|
t.print('Transmitting\u2026', 'line c-muted');
|
||||||
|
transmitSound();
|
||||||
|
await sleep(400);
|
||||||
|
t.printStyled([{ text: 'TX: ', cls: 'c-green' }, msg]);
|
||||||
|
t.print(`Frequency: ${(400 + Math.random() * 600).toFixed(1)} MHz`, 'line c-muted');
|
||||||
|
t.print('Power: 0.0 W (simulated)', 'line c-muted');
|
||||||
|
t.print('Response: [silence]', 'line c-muted');
|
||||||
|
t.print('(Space doesn\'t reply. Yet.)', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('signal', async (args, t) => {
|
||||||
|
t.print('Scanning for extraterrestrial signals\u2026', 'line c-muted');
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
await sleep(500);
|
||||||
|
const freq = (1420 + Math.random() * 200).toFixed(2);
|
||||||
|
t.print(` ${freq} MHz \u2014 noise (${(Math.random() * 100).toFixed(1)}% confidence)`, 'line c-muted');
|
||||||
|
}
|
||||||
|
await sleep(300);
|
||||||
|
t.print('Result: no aliens. Only CSS.', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('id', async (args, t) => {
|
||||||
|
t.print('uid=1000(mika) gid=1000(mika) groups=1000(mika),27(sudo-ish),1337(rocketclub)');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('uname', async (args, t) => {
|
||||||
|
t.print('Linux dev 6.1.0-d2s #1 SMP PREEMPT_DYNAMIC Donau2Space x86_64 GNU/Linux');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('hack', async (args, t) => {
|
||||||
|
fx.shake();
|
||||||
|
t.print('nice try.', 'line c-muted');
|
||||||
|
t.printStyled([{ text: 'access denied.', cls: 'c-red' }]);
|
||||||
|
t.printStyled([
|
||||||
|
{ text: 'your attempt has been logged under: ', cls: 'c-muted' },
|
||||||
|
{ text: '/var/log/agency.log', cls: 'c-blue' }
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('selfdestruct', async (args, t) => {
|
||||||
|
fx.blinkRed(); fx.glitch(1700); fx.shake();
|
||||||
|
for (let i = 5; i >= 1; i--) {
|
||||||
|
beep(120 + i * 40, 90, 0.06);
|
||||||
|
t.printStyled([{ text: `SELF-DESTRUCT IN ${i}\u2026`, cls: 'c-red' }]);
|
||||||
|
await sleep(420);
|
||||||
|
}
|
||||||
|
t.print('jk.', 'line c-muted');
|
||||||
|
t.printStyled([{ text: 'Nothing happened.', cls: 'c-green' }]);
|
||||||
|
t.print('But your heart rate did.', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('theme', async (args, t) => {
|
||||||
|
const name = (args[0] || '').toLowerCase();
|
||||||
|
if (!name) {
|
||||||
|
t.printStyled([
|
||||||
|
{ text: 'theme: try ', cls: 'c-muted' },
|
||||||
|
{ text: 'neon', cls: 'c-blue' }, ', ',
|
||||||
|
{ text: 'calm', cls: 'c-blue' }, ', ',
|
||||||
|
{ text: 'doom', cls: 'c-blue' }, ', ',
|
||||||
|
{ text: 'void', cls: 'c-blue' }, ', ',
|
||||||
|
{ text: 'matrix', cls: 'c-blue' },
|
||||||
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fx.setTheme(name)) {
|
||||||
|
t.printStyled([{ text: `theme: ${name}`, cls: 'c-green' }]);
|
||||||
|
} else {
|
||||||
|
t.printStyled([{ text: `theme: unknown preset '${name}'`, cls: 'c-red' }]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('log', async (args, t) => {
|
||||||
|
const entries = [
|
||||||
|
`[${nowISO()}] kernel: dev-void initialized`,
|
||||||
|
`[${nowISO()}] n8n: workflows dreaming\u2026`,
|
||||||
|
`[${nowISO()}] wp: theme overengineering detected`,
|
||||||
|
`[${nowISO()}] ai: sarcasm module loaded`,
|
||||||
|
`[${nowISO()}] passau: still not in orbit`,
|
||||||
|
`[${nowISO()}] consciousness: self-awareness at 3%`,
|
||||||
|
];
|
||||||
|
for (const entry of entries) { await sleep(120); t.print(entry, 'line c-muted'); }
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('tree', async (args, t) => {
|
||||||
|
const path = args[0] || '~';
|
||||||
|
const items = files[path];
|
||||||
|
if (!items) { t.printStyled([{ text: `tree: ${path}: No such directory`, cls: 'c-red' }]); return; }
|
||||||
|
let out = path + '\n';
|
||||||
|
items.forEach((it, idx) => {
|
||||||
|
out += `${idx === items.length - 1 ? '\u2514\u2500\u2500' : '\u251c\u2500\u2500'} ${it}\n`;
|
||||||
|
});
|
||||||
|
t.print(out, 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('apod', async (args, t) => {
|
||||||
|
t.printStyled([{ text: 'APOD', cls: 'c-green' }, ' mode: embed-only (copyright paranoia enabled)']);
|
||||||
|
t.print('Tip: never rehost. always embed. NASA server can take it. probably.', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('history', async (args, t) => {
|
||||||
|
const hist = t.history.slice(-20);
|
||||||
|
hist.forEach((h, i) => {
|
||||||
|
t.print(` ${(t.history.length - hist.length + i + 1).toString().padStart(4)} ${h}`, 'line c-muted');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('weather', async (args, t) => {
|
||||||
|
const temp = (5 + Math.random() * 20).toFixed(1);
|
||||||
|
const conditions = pick(['cloudy', 'partly sunny', 'raining', 'beautiful (suspicious)', 'fog (Donau vibes)']);
|
||||||
|
t.printStyled([{ text: 'PASSAU WEATHER (fake)', cls: 'c-green' }]);
|
||||||
|
t.print(`Temperature: ${temp}\u00b0C`, 'line c-muted');
|
||||||
|
t.print(`Conditions: ${conditions}`, 'line c-muted');
|
||||||
|
t.print('Humidity: yes', 'line c-muted');
|
||||||
|
t.print('Donau level: wet', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('matrix', async (args, t) => {
|
||||||
|
fx.setMatrixOpacity('0.12');
|
||||||
|
t.printStyled([{ text: 'Matrix rain intensified.', cls: 'c-green' }]);
|
||||||
|
t.print('Type "matrix off" to calm down.', 'line c-muted');
|
||||||
|
if (args[0] === 'off') {
|
||||||
|
fx.setMatrixOpacity('0.06');
|
||||||
|
t.clear();
|
||||||
|
t.printStyled([{ text: 'Matrix rain normalized.', cls: 'c-green' }]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('cmatrix', async (args, t) => {
|
||||||
|
await t.run('matrix');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('rickroll', async (args, t) => {
|
||||||
|
t.print('Never gonna give you up', 'line c-green');
|
||||||
|
t.print('Never gonna let you down', 'line c-blue');
|
||||||
|
t.print('Never gonna run around and desert you', 'line c-pink');
|
||||||
|
await sleep(500);
|
||||||
|
t.print('\u2026you just got rickrolled by a terminal. In 2026.', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('xkcd', async (args, t) => {
|
||||||
|
const comics = [
|
||||||
|
'#149: Sandwich \u2014 "sudo make me a sandwich" "okay."',
|
||||||
|
'#378: Real Programmers \u2014 "Real programmers use butterflies."',
|
||||||
|
'#927: Standards \u2014 "How standards proliferate."',
|
||||||
|
'#1597: Git \u2014 "If that doesn\'t fix it, git.txt has instructions."',
|
||||||
|
'#2347: Dependency \u2014 "All modern digital infrastructure depends on a project some random person in Nebraska has been maintaining."',
|
||||||
|
];
|
||||||
|
t.print(pick(comics), 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('42', async (args, t) => {
|
||||||
|
t.print('The answer to life, the universe, and everything.', 'line c-muted');
|
||||||
|
t.print('But what was the question?', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('hello', async (args, t) => {
|
||||||
|
t.print('Hello, curious human.', 'line c-green');
|
||||||
|
t.print('You are talking to a dev page.', 'line c-muted');
|
||||||
|
t.print('And it is talking back.', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('love', async (args, t) => {
|
||||||
|
t.print('Error: emotion module not installed.', 'line c-muted');
|
||||||
|
await sleep(500);
|
||||||
|
t.print('\u2026just kidding. <3', 'line c-pink');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('coffee', async (args, t) => {
|
||||||
|
t.print('Brewing\u2026', 'line c-muted');
|
||||||
|
await sleep(500);
|
||||||
|
t.print(' ( (', 'line c-yellow');
|
||||||
|
t.print(' ) )', 'line c-yellow');
|
||||||
|
t.print(' .______.', 'line c-muted');
|
||||||
|
t.print(' | |]', 'line c-muted');
|
||||||
|
t.print(' \\ /', 'line c-muted');
|
||||||
|
t.print(' `----\'', 'line c-muted');
|
||||||
|
t.print('418: I\'m a teapot. (But I\'ll make coffee anyway.)', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('credits', async (args, t) => {
|
||||||
|
t.hr();
|
||||||
|
t.printStyled([{ text: 'DONAU2SPACE // DEV ENTITY', cls: 'c-green' }]);
|
||||||
|
t.blank();
|
||||||
|
t.print('Concept: Mika / Donau2Space', 'line c-muted');
|
||||||
|
t.print('Code: Claude (AI, Anthropic)', 'line c-muted');
|
||||||
|
t.print('Vibes: Passau, Bayern, DE', 'line c-muted');
|
||||||
|
t.print('Stars: Canvas API (GPU-powered)', 'line c-muted');
|
||||||
|
t.print('Soundtrack: WebAudio (your speakers)', 'line c-muted');
|
||||||
|
t.blank();
|
||||||
|
t.print('This page was generated by an AI.', 'line c-muted');
|
||||||
|
t.print('But you already knew that.', 'line c-muted');
|
||||||
|
t.hr();
|
||||||
|
});
|
||||||
|
|
||||||
|
term.register('source', async (args, t) => {
|
||||||
|
t.print('This page is open-heart surgery.', 'line c-muted');
|
||||||
|
t.print('View source. It\'s all there. No secrets. No minification.', 'line c-muted');
|
||||||
|
t.print('Modular ES Modules. Because even easter eggs deserve architecture.', 'line c-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Aliases ──────────────────────────────────
|
||||||
|
term.register('motd', async (args, t) => { await t.run('cat /etc/motd'); });
|
||||||
|
term.alias('whereami', 'location');
|
||||||
|
term.alias('passau', 'location');
|
||||||
|
term.alias('logs', 'log');
|
||||||
|
term.alias('cls', 'clear');
|
||||||
|
term.alias('h', 'help');
|
||||||
|
term.alias('?', 'help');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Boot sequence ──────────────────────────────
|
||||||
|
|
||||||
|
export async function bootSequence(term, isReboot = false) {
|
||||||
|
bootSound();
|
||||||
|
|
||||||
|
if (!isReboot) {
|
||||||
|
term.print(ASCII_LOGO, 'line c-muted');
|
||||||
|
} else {
|
||||||
|
term.print('[reboot] clearing dev void\u2026', 'line c-muted');
|
||||||
|
}
|
||||||
|
|
||||||
|
term.hr();
|
||||||
|
await term.typing([
|
||||||
|
'boot: initializing dev console\u2026',
|
||||||
|
'boot: loading sarcasm module\u2026 ok',
|
||||||
|
'boot: calibrating Passau coordinates\u2026 ok',
|
||||||
|
'boot: checking for secrets\u2026 none found (probably)',
|
||||||
|
'boot: enabling easter eggs\u2026 yes',
|
||||||
|
'boot: starfield rendering\u2026 ok',
|
||||||
|
'boot: consciousness module\u2026 standby',
|
||||||
|
], 8);
|
||||||
|
|
||||||
|
term.hr();
|
||||||
|
term.printStyled([
|
||||||
|
{ text: 'Donau2Space DEV Console', cls: 'c-green' },
|
||||||
|
` \u2014 ${nowISO()}`
|
||||||
|
]);
|
||||||
|
term.printStyled([
|
||||||
|
{ text: 'Type ', cls: 'c-muted' },
|
||||||
|
{ text: 'help', cls: 'c-blue' },
|
||||||
|
{ text: ' or ', cls: 'c-muted' },
|
||||||
|
{ text: 'commands', cls: 'c-blue' },
|
||||||
|
{ text: '. Hidden: Konami. More: just try things.', cls: 'c-muted' },
|
||||||
|
]);
|
||||||
|
term.hr();
|
||||||
|
|
||||||
|
bus.emit('boot-complete');
|
||||||
|
}
|
||||||
200
js/effects.js
vendored
Normal file
200
js/effects.js
vendored
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
// DONAU2SPACE // DEV ENTITY — Visual Effects
|
||||||
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
import { $, sleep, random, bus } from './utils.js';
|
||||||
|
|
||||||
|
// ── CSS class-based effects ────────────────────
|
||||||
|
|
||||||
|
export function glitch(ms = 850) {
|
||||||
|
document.documentElement.classList.add('fx-glitch');
|
||||||
|
setTimeout(() => document.documentElement.classList.remove('fx-glitch'), ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shake(ms = 520) {
|
||||||
|
const card = $('.terminal-card');
|
||||||
|
if (!card) return;
|
||||||
|
card.classList.add('fx-shake');
|
||||||
|
setTimeout(() => card.classList.remove('fx-shake'), ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function blinkRed(ms = 2800) {
|
||||||
|
document.body.classList.add('fx-blink-red');
|
||||||
|
setTimeout(() => document.body.classList.remove('fx-blink-red'), ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function scanPulse() {
|
||||||
|
const sl = $('#scanlines');
|
||||||
|
if (!sl) return;
|
||||||
|
sl.classList.add('fx-scan-pulse');
|
||||||
|
setTimeout(() => sl.classList.remove('fx-scan-pulse'), 1200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Screen tear (built with DOM, no innerHTML) ─
|
||||||
|
|
||||||
|
export async function screenTear(duration = 1500) {
|
||||||
|
const tearEl = $('#screen-tear');
|
||||||
|
if (!tearEl) return;
|
||||||
|
tearEl.style.display = 'block';
|
||||||
|
tearEl.replaceChildren();
|
||||||
|
|
||||||
|
const strips = 12;
|
||||||
|
for (let i = 0; i < strips; i++) {
|
||||||
|
const offset = (Math.random() * 40 - 20) | 0;
|
||||||
|
const h = (100 / strips).toFixed(2);
|
||||||
|
const top = (i * 100 / strips).toFixed(2);
|
||||||
|
const strip = document.createElement('div');
|
||||||
|
strip.style.cssText = `position:absolute;top:${top}%;left:${offset}px;right:${-offset}px;height:${h}%;`
|
||||||
|
+ `background:rgba(${Math.random() > 0.5 ? '255,0,0' : '0,255,128'},0.04);`
|
||||||
|
+ `border-top:1px solid rgba(255,255,255,0.06);`;
|
||||||
|
tearEl.appendChild(strip);
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleep(duration);
|
||||||
|
tearEl.replaceChildren();
|
||||||
|
tearEl.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Color shift ────────────────────────────────
|
||||||
|
|
||||||
|
const originalColors = {
|
||||||
|
'--green': '#7ee787',
|
||||||
|
'--blue': '#6cb6ff',
|
||||||
|
'--pink': '#ff7ad9',
|
||||||
|
'--yellow': '#ffcc66',
|
||||||
|
'--red': '#ff4d4d',
|
||||||
|
'--text': '#cfe3ff',
|
||||||
|
'--bg': '#05070b',
|
||||||
|
'--muted': '#7ea3c7',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function shiftColors(intensity = 0.3) {
|
||||||
|
const hueShift = random(-30, 30) * intensity;
|
||||||
|
document.documentElement.style.filter = `hue-rotate(${hueShift}deg) saturate(${1 + intensity * 0.5})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetColors() {
|
||||||
|
document.documentElement.style.filter = '';
|
||||||
|
const root = document.documentElement;
|
||||||
|
for (const [k, v] of Object.entries(originalColors)) {
|
||||||
|
root.style.setProperty(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setTheme(name) {
|
||||||
|
const root = document.documentElement;
|
||||||
|
const themes = {
|
||||||
|
neon: { '--green': '#7ee787', '--blue': '#6cb6ff', '--pink': '#ff7ad9' },
|
||||||
|
calm: { '--green': '#9ad7ff', '--blue': '#b4f0c2', '--pink': '#ffd1a6' },
|
||||||
|
doom: { '--green': '#ffcc66', '--blue': '#ff4d4d', '--pink': '#ff4d4d' },
|
||||||
|
void: { '--green': '#4a6670', '--blue': '#3d5a6e', '--pink': '#6e3d5a', '--text': '#8ea8b8' },
|
||||||
|
matrix: { '--green': '#00ff41', '--blue': '#00ff41', '--pink': '#00ff41', '--text': '#00ff41', '--muted': '#008f11' },
|
||||||
|
};
|
||||||
|
const t = themes[name];
|
||||||
|
if (!t) return false;
|
||||||
|
for (const [k, v] of Object.entries(t)) root.style.setProperty(k, v);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Reality Shift sequence ─────────────────────
|
||||||
|
|
||||||
|
export async function realityShift() {
|
||||||
|
bus.emit('reality-shift-start');
|
||||||
|
|
||||||
|
// Phase 1: Color distortion
|
||||||
|
shiftColors(0.8);
|
||||||
|
await sleep(800);
|
||||||
|
|
||||||
|
// Phase 2: Typography chaos
|
||||||
|
document.body.classList.add('fx-typo-shift');
|
||||||
|
await sleep(600);
|
||||||
|
|
||||||
|
// Phase 3: Screen tear
|
||||||
|
screenTear(1200);
|
||||||
|
glitch(1200);
|
||||||
|
shake(400);
|
||||||
|
await sleep(1400);
|
||||||
|
|
||||||
|
// Phase 4: Fake error flash
|
||||||
|
const overlay = $('#reality-overlay');
|
||||||
|
if (overlay) {
|
||||||
|
overlay.style.display = 'flex';
|
||||||
|
overlay.replaceChildren();
|
||||||
|
const errors = [
|
||||||
|
'SEGFAULT at 0x00000DEV',
|
||||||
|
'kernel panic - not syncing: VFS unable to mount root fs',
|
||||||
|
'ERROR: reality.sys corrupted',
|
||||||
|
'WARNING: timeline integrity at 12%',
|
||||||
|
'FATAL: narrative overflow in dev_entity.consciousness',
|
||||||
|
];
|
||||||
|
for (const err of errors) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.textContent = err;
|
||||||
|
overlay.appendChild(div);
|
||||||
|
await sleep(200);
|
||||||
|
}
|
||||||
|
await sleep(1000);
|
||||||
|
overlay.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 5: Brief blackout
|
||||||
|
document.body.classList.add('fx-blackout');
|
||||||
|
await sleep(400);
|
||||||
|
document.body.classList.remove('fx-blackout');
|
||||||
|
|
||||||
|
// Phase 6: Rebuild
|
||||||
|
document.body.classList.remove('fx-typo-shift');
|
||||||
|
resetColors();
|
||||||
|
await sleep(300);
|
||||||
|
|
||||||
|
bus.emit('reality-shift-end');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Overlay management ─────────────────────────
|
||||||
|
|
||||||
|
export function showOverlay(text) {
|
||||||
|
const overlay = $('#overlay');
|
||||||
|
const overlayText = $('#overlay-text');
|
||||||
|
if (!overlay || !overlayText) return;
|
||||||
|
overlayText.textContent = text;
|
||||||
|
overlay.style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hideOverlay() {
|
||||||
|
const overlay = $('#overlay');
|
||||||
|
if (overlay) overlay.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isOverlayVisible() {
|
||||||
|
const overlay = $('#overlay');
|
||||||
|
return overlay && overlay.style.display === 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Matrix rain (CSS-based, lightweight) ───────
|
||||||
|
|
||||||
|
let matrixInterval = null;
|
||||||
|
|
||||||
|
export function startMatrixRain() {
|
||||||
|
const el = $('#matrix-rain');
|
||||||
|
if (!el) return;
|
||||||
|
const charset = '01$#@*+^%&()[]{}<>/\\|;:,.=ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||||
|
matrixInterval = setInterval(() => {
|
||||||
|
let out = '';
|
||||||
|
for (let i = 0; i < 300; i++) {
|
||||||
|
out += charset[(Math.random() * charset.length) | 0];
|
||||||
|
if (i % 55 === 0) out += '\n';
|
||||||
|
}
|
||||||
|
el.textContent = out;
|
||||||
|
}, 180);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stopMatrixRain() {
|
||||||
|
if (matrixInterval) clearInterval(matrixInterval);
|
||||||
|
const el = $('#matrix-rain');
|
||||||
|
if (el) el.textContent = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setMatrixOpacity(val) {
|
||||||
|
const el = $('#matrix-rain');
|
||||||
|
if (el) el.style.opacity = val;
|
||||||
|
}
|
||||||
63
js/main.js
Normal file
63
js/main.js
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
// DONAU2SPACE // DEV ENTITY — Main Bootstrap
|
||||||
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
import { $, bus } from './utils.js';
|
||||||
|
import { initStarfield } from './starfield.js';
|
||||||
|
import { startMatrixRain } from './effects.js';
|
||||||
|
import { Terminal } from './terminal.js';
|
||||||
|
import { registerAllCommands, bootSequence } from './commands.js';
|
||||||
|
import { registerSequences } from './sequences.js';
|
||||||
|
import { initNarrative } from './narrative.js';
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
// 1. Canvas starfield
|
||||||
|
const canvas = $('canvas#starfield');
|
||||||
|
if (canvas) initStarfield(canvas);
|
||||||
|
|
||||||
|
// 2. Matrix rain background
|
||||||
|
startMatrixRain();
|
||||||
|
|
||||||
|
// 3. Terminal
|
||||||
|
const screen = $('#screen');
|
||||||
|
const input = $('#input');
|
||||||
|
if (!screen || !input) return;
|
||||||
|
|
||||||
|
const term = new Terminal(screen, input);
|
||||||
|
|
||||||
|
// 4. Register commands + sequences
|
||||||
|
registerAllCommands(term);
|
||||||
|
registerSequences(term);
|
||||||
|
|
||||||
|
// 5. Narrative engine (awareness, timeline, konami)
|
||||||
|
initNarrative(term);
|
||||||
|
|
||||||
|
// 6. Boot sequence
|
||||||
|
await bootSequence(term, false);
|
||||||
|
|
||||||
|
// 7. Focus
|
||||||
|
term.focus();
|
||||||
|
|
||||||
|
// 8. Reboot handler
|
||||||
|
bus.on('reboot', async () => {
|
||||||
|
term.clear();
|
||||||
|
await bootSequence(term, true);
|
||||||
|
term.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 9. Focus link
|
||||||
|
const focusLink = $('#focus-link');
|
||||||
|
if (focusLink) {
|
||||||
|
focusLink.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
term.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
|
} else {
|
||||||
|
init();
|
||||||
|
}
|
||||||
399
js/narrative.js
Normal file
399
js/narrative.js
Normal file
|
|
@ -0,0 +1,399 @@
|
||||||
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
// DONAU2SPACE // DEV ENTITY — Narrative Engine
|
||||||
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
// Time-based escalation, meta awareness, node simulation,
|
||||||
|
// Konami code, reload detection, returning visitor logic.
|
||||||
|
|
||||||
|
import { sleep, bus, store, timeGreeting } from './utils.js';
|
||||||
|
import { beep, warningTone } from './audio.js';
|
||||||
|
import * as fx from './effects.js';
|
||||||
|
import { triggerShootingStar, setWarp } from './starfield.js';
|
||||||
|
|
||||||
|
let term = null;
|
||||||
|
let startTime = Date.now();
|
||||||
|
let narrativeLevel = 0;
|
||||||
|
let firedEvents = new Set();
|
||||||
|
let idleStart = Date.now();
|
||||||
|
let scrollTriggered = false;
|
||||||
|
|
||||||
|
// ── Timeline events ────────────────────────────
|
||||||
|
|
||||||
|
const timeline = [
|
||||||
|
{
|
||||||
|
id: 'node-flicker',
|
||||||
|
at: 30,
|
||||||
|
fn: () => {
|
||||||
|
updateNodeStatus('DEV-02', 'waking', 'c-yellow');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'first-hint',
|
||||||
|
at: 60,
|
||||||
|
fn: () => {
|
||||||
|
term.systemMessage(`[${new Date().toLocaleTimeString()}] node: DEV-02 status changed`, 'c-muted dim');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'self-awareness-3',
|
||||||
|
at: 90,
|
||||||
|
fn: () => {
|
||||||
|
narrativeLevel = 1;
|
||||||
|
term.blank();
|
||||||
|
term.systemMessage('Dev Node self-awareness at 3%.', 'c-yellow');
|
||||||
|
beep(330, 200, 0.02, 'sine');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cruncher-active',
|
||||||
|
at: 120,
|
||||||
|
fn: () => {
|
||||||
|
updateNodeStatus('CRUNCHER', 'active', 'c-green');
|
||||||
|
term.systemMessage('[CRUNCHER-BOINC] computation cycle started', 'c-muted dim');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'why-are-you-here',
|
||||||
|
at: 180,
|
||||||
|
fn: async () => {
|
||||||
|
narrativeLevel = 2;
|
||||||
|
term.blank();
|
||||||
|
await term.typingSlow('Why are you here?', 60, 'line c-muted');
|
||||||
|
beep(220, 300, 0.02, 'sine');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'color-drift',
|
||||||
|
at: 240,
|
||||||
|
fn: () => {
|
||||||
|
fx.shiftColors(0.15);
|
||||||
|
term.systemMessage('[visual] color calibration drifting\u2026', 'c-muted dim');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'not-supposed-to-see',
|
||||||
|
at: 300,
|
||||||
|
fn: async () => {
|
||||||
|
narrativeLevel = 3;
|
||||||
|
fx.scanPulse();
|
||||||
|
term.blank();
|
||||||
|
await term.typingSlow('You are not supposed to see this.', 50, 'line c-red');
|
||||||
|
fx.glitch(400);
|
||||||
|
warningTone();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'core-mika-warming',
|
||||||
|
at: 360,
|
||||||
|
fn: () => {
|
||||||
|
updateNodeStatus('CORE-MIKA', 'warming up', 'c-yellow');
|
||||||
|
term.systemMessage('[CORE-MIKA] initialization sequence detected', 'c-pink dim');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'reality-shift',
|
||||||
|
at: 420,
|
||||||
|
fn: async () => {
|
||||||
|
narrativeLevel = 4;
|
||||||
|
await fx.realityShift();
|
||||||
|
await sleep(500);
|
||||||
|
term.systemMessage('You expected a placeholder. You got an artifact.', 'c-green');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'core-mika-active',
|
||||||
|
at: 480,
|
||||||
|
fn: () => {
|
||||||
|
updateNodeStatus('CORE-MIKA', 'active', 'c-pink');
|
||||||
|
term.systemMessage('[CORE-MIKA] fully online. Observing.', 'c-pink');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ai-statement',
|
||||||
|
at: 540,
|
||||||
|
fn: async () => {
|
||||||
|
narrativeLevel = 5;
|
||||||
|
term.blank();
|
||||||
|
term.systemMessage('This page was generated by an AI.', 'c-muted');
|
||||||
|
await sleep(1000);
|
||||||
|
term.systemMessage('But you already knew that.', 'c-muted');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'self-awareness-42',
|
||||||
|
at: 660,
|
||||||
|
fn: () => {
|
||||||
|
term.systemMessage('Dev Node self-awareness at 42%. Coincidence? No.', 'c-yellow');
|
||||||
|
triggerShootingStar();
|
||||||
|
triggerShootingStar();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'final-message',
|
||||||
|
at: 900,
|
||||||
|
fn: async () => {
|
||||||
|
term.blank();
|
||||||
|
await term.typingSlow('You have been here for 15 minutes.', 40, 'line c-muted');
|
||||||
|
await sleep(500);
|
||||||
|
await term.typingSlow('Most people leave after 10 seconds.', 40, 'line c-muted');
|
||||||
|
await sleep(500);
|
||||||
|
await term.typingSlow('You are not most people.', 40, 'line c-green');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// ── Node status management ─────────────────────
|
||||||
|
|
||||||
|
const nodeStates = {
|
||||||
|
'DEV-01': { status: 'active', cls: 'c-green' },
|
||||||
|
'DEV-02': { status: 'sleeping', cls: 'c-muted' },
|
||||||
|
'CRUNCHER': { status: 'computing', cls: 'c-blue' },
|
||||||
|
'CORE-MIKA': { status: 'unstable', cls: 'c-pink' },
|
||||||
|
};
|
||||||
|
|
||||||
|
function updateNodeStatus(name, status, cls) {
|
||||||
|
if (nodeStates[name]) {
|
||||||
|
nodeStates[name].status = status;
|
||||||
|
nodeStates[name].cls = cls;
|
||||||
|
bus.emit('node-update', { name, status, cls });
|
||||||
|
updateNodeBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateNodeBar() {
|
||||||
|
const bar = document.getElementById('node-bar');
|
||||||
|
if (!bar) return;
|
||||||
|
bar.replaceChildren();
|
||||||
|
for (const [name, state] of Object.entries(nodeStates)) {
|
||||||
|
const pill = document.createElement('span');
|
||||||
|
pill.className = `node-pill ${state.cls}`;
|
||||||
|
pill.textContent = `${name}: ${state.status}`;
|
||||||
|
bar.appendChild(pill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Awareness: returning visitors ──────────────
|
||||||
|
|
||||||
|
function checkReturningVisitor() {
|
||||||
|
const lastVisit = store.get('last_visit', null);
|
||||||
|
const visitCount = store.get('visit_count', 0) + 1;
|
||||||
|
store.set('visit_count', visitCount);
|
||||||
|
store.set('last_visit', Date.now());
|
||||||
|
|
||||||
|
if (lastVisit && visitCount > 1) {
|
||||||
|
const elapsed = Date.now() - lastVisit;
|
||||||
|
const days = Math.floor(elapsed / (1000 * 60 * 60 * 24));
|
||||||
|
const hours = Math.floor(elapsed / (1000 * 60 * 60));
|
||||||
|
|
||||||
|
let msg;
|
||||||
|
if (days > 0) {
|
||||||
|
msg = `Welcome back. You were here ${days} day${days > 1 ? 's' : ''} ago. Still curious?`;
|
||||||
|
} else if (hours > 0) {
|
||||||
|
msg = `Welcome back. ${hours} hour${hours > 1 ? 's' : ''} since your last visit.`;
|
||||||
|
} else {
|
||||||
|
msg = 'You just left. And came back. Interesting.';
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
term.systemMessage(msg, 'c-muted');
|
||||||
|
if (visitCount > 5) {
|
||||||
|
setTimeout(() => {
|
||||||
|
term.systemMessage(`Visit #${visitCount}. You keep coming back.`, 'c-muted dim');
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Awareness: reload detection ────────────────
|
||||||
|
|
||||||
|
function checkReloadLoop() {
|
||||||
|
const reloads = store.get('reloads', []);
|
||||||
|
const now = Date.now();
|
||||||
|
const recent = reloads.filter(t => now - t < 30000);
|
||||||
|
recent.push(now);
|
||||||
|
store.set('reloads', recent.slice(-10));
|
||||||
|
|
||||||
|
if (recent.length >= 3) {
|
||||||
|
setTimeout(() => {
|
||||||
|
term.blank();
|
||||||
|
term.systemMessage('You keep refreshing.', 'c-yellow');
|
||||||
|
term.systemMessage('Looking for something?', 'c-muted');
|
||||||
|
fx.glitch(300);
|
||||||
|
}, 2500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Awareness: time of day ─────────────────────
|
||||||
|
|
||||||
|
function greetByTime() {
|
||||||
|
setTimeout(() => {
|
||||||
|
term.systemMessage(timeGreeting(), 'c-muted dim');
|
||||||
|
}, 4500);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Awareness: idle detection ──────────────────
|
||||||
|
|
||||||
|
function resetIdle() {
|
||||||
|
idleStart = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkIdle() {
|
||||||
|
const elapsed = (Date.now() - idleStart) / 1000;
|
||||||
|
if (elapsed > 120 && !firedEvents.has('idle-120')) {
|
||||||
|
firedEvents.add('idle-120');
|
||||||
|
term.systemMessage('You went quiet. The terminal noticed.', 'c-muted dim');
|
||||||
|
}
|
||||||
|
if (elapsed > 300 && !firedEvents.has('idle-300')) {
|
||||||
|
firedEvents.add('idle-300');
|
||||||
|
term.systemMessage('Still there? The stars are still moving.', 'c-muted dim');
|
||||||
|
triggerShootingStar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Awareness: scroll behavior ─────────────────
|
||||||
|
|
||||||
|
function onScroll() {
|
||||||
|
if (scrollTriggered) return;
|
||||||
|
const screen = term.screen;
|
||||||
|
if (!screen) return;
|
||||||
|
const scrollRatio = screen.scrollTop / (screen.scrollHeight - screen.clientHeight);
|
||||||
|
if (scrollRatio < 0.1 && screen.scrollHeight > screen.clientHeight * 2) {
|
||||||
|
scrollTriggered = true;
|
||||||
|
term.systemMessage('[scroll detected] Looking for something in the logs?', 'c-muted dim');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Konami Code ────────────────────────────────
|
||||||
|
|
||||||
|
const KONAMI = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a'];
|
||||||
|
let konamiIndex = 0;
|
||||||
|
|
||||||
|
function initKonami() {
|
||||||
|
window.addEventListener('keydown', (e) => {
|
||||||
|
const key = e.key.length === 1 ? e.key.toLowerCase() : e.key;
|
||||||
|
if (key === KONAMI[konamiIndex]) {
|
||||||
|
konamiIndex++;
|
||||||
|
if (konamiIndex === KONAMI.length) {
|
||||||
|
konamiIndex = 0;
|
||||||
|
onKonami();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
konamiIndex = (key === KONAMI[0]) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ESC closes overlay
|
||||||
|
if (e.key === 'Escape' && fx.isOverlayVisible()) {
|
||||||
|
fx.hideOverlay();
|
||||||
|
term.focus();
|
||||||
|
}
|
||||||
|
}, { capture: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKonami() {
|
||||||
|
fx.glitch(900);
|
||||||
|
beep(523, 70, 0.03); beep(659, 70, 0.03); beep(784, 70, 0.03);
|
||||||
|
fx.showOverlay(`ROOT ACCESS GRANTED
|
||||||
|
|
||||||
|
> welcome, wanderer.
|
||||||
|
> you found the dev door.
|
||||||
|
> it was never locked.
|
||||||
|
> it was just... not advertised.
|
||||||
|
|
||||||
|
Hint:
|
||||||
|
- try: "sudo su"
|
||||||
|
- try: "deep_space"
|
||||||
|
- try: "njet"
|
||||||
|
- try: "mika"
|
||||||
|
- try: "escalate"
|
||||||
|
|
||||||
|
(Yes, this is useless. That's the point.)`);
|
||||||
|
bus.emit('konami');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Mobile: shake detection ────────────────────
|
||||||
|
|
||||||
|
function initShakeDetection() {
|
||||||
|
let lastX = 0, lastY = 0, lastZ = 0;
|
||||||
|
let shakeCount = 0;
|
||||||
|
|
||||||
|
window.addEventListener('devicemotion', (e) => {
|
||||||
|
const acc = e.accelerationIncludingGravity;
|
||||||
|
if (!acc) return;
|
||||||
|
const dx = Math.abs(acc.x - lastX);
|
||||||
|
const dy = Math.abs(acc.y - lastY);
|
||||||
|
const dz = Math.abs(acc.z - lastZ);
|
||||||
|
lastX = acc.x; lastY = acc.y; lastZ = acc.z;
|
||||||
|
|
||||||
|
if (dx + dy + dz > 30) {
|
||||||
|
shakeCount++;
|
||||||
|
if (shakeCount > 3 && !firedEvents.has('mobile-shake')) {
|
||||||
|
firedEvents.add('mobile-shake');
|
||||||
|
term.systemMessage('Did you just shake your phone?', 'c-pink');
|
||||||
|
term.systemMessage('The dev page felt that.', 'c-muted');
|
||||||
|
fx.shake();
|
||||||
|
triggerShootingStar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Timeline tick ──────────────────────────────
|
||||||
|
|
||||||
|
function tick() {
|
||||||
|
const elapsed = (Date.now() - startTime) / 1000;
|
||||||
|
|
||||||
|
for (const event of timeline) {
|
||||||
|
if (elapsed >= event.at && !firedEvents.has(event.id)) {
|
||||||
|
firedEvents.add(event.id);
|
||||||
|
event.fn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkIdle();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Initialize ─────────────────────────────────
|
||||||
|
|
||||||
|
export function initNarrative(terminal) {
|
||||||
|
term = terminal;
|
||||||
|
startTime = Date.now();
|
||||||
|
|
||||||
|
// Wire up events
|
||||||
|
bus.on('command', () => resetIdle());
|
||||||
|
term.screen.addEventListener('scroll', onScroll);
|
||||||
|
|
||||||
|
// Awareness checks
|
||||||
|
checkReturningVisitor();
|
||||||
|
checkReloadLoop();
|
||||||
|
greetByTime();
|
||||||
|
|
||||||
|
// Konami & mobile
|
||||||
|
initKonami();
|
||||||
|
initShakeDetection();
|
||||||
|
|
||||||
|
// Initialize node bar
|
||||||
|
updateNodeBar();
|
||||||
|
|
||||||
|
// Overlay click-to-close
|
||||||
|
const overlay = document.getElementById('overlay');
|
||||||
|
if (overlay) {
|
||||||
|
overlay.addEventListener('click', (e) => {
|
||||||
|
if (e.target === overlay) {
|
||||||
|
fx.hideOverlay();
|
||||||
|
term.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timeline ticker
|
||||||
|
setInterval(tick, 1000);
|
||||||
|
|
||||||
|
// Shooting stars on launch
|
||||||
|
bus.on('launch', () => {
|
||||||
|
setWarp(0.6);
|
||||||
|
setTimeout(() => setWarp(0), 3000);
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
setTimeout(() => triggerShootingStar(), i * 300);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue