/* global React */ const { useState, useEffect, useRef } = React; // ─── idle "ghost in the wire" sequences (original, site-voice) ──── // triggered after 3 min of inactivity while terminal is open. // two sequences rotate randomly. ~5s pause between messages. // any keystroke / click cancels and resets the idle timer. const IDLE_MS = 8000; const IDLE_STEP = 5000; const step = (n) => n * IDLE_STEP; const IDLE_SEQUENCES = [ // ── A: "you're still here" (slow, paranoid) [ { delay: 0, kind:'sys', text: '// 180s idle. session goes quiet.' }, { delay: step(1), kind:'acid', text: '[unknown]: ...' }, { delay: step(2), kind:'acid', text: "[unknown]: you're still here." }, { delay: step(3), kind:'acid', text: '[unknown]: i watched you scroll the works. twice.' }, { delay: step(4), kind:'acid', text: '[unknown]: nothing is loading. nothing was supposed to.' }, { delay: step(5), kind:'acid', text: "[unknown]: this terminal isn't in the html. you found it anyway." }, { delay: step(6), kind:'acid', text: "[unknown]: there is a sketch with your name on it. you don't know it yet." }, { delay: step(7), kind:'acid', text: '[unknown]: type /book or close this. either way, the choice is logged.' }, { delay: step(8), kind:'sys', text: '// transmission ends. any keystroke disconnects.' }, ], // ── B: "signal incoming" (eerie, unsigned) [ { delay: 0, kind:'sys', text: '// signal incoming. unknown peer.' }, { delay: step(1), kind:'acid', text: '[unknown]: ...' }, { delay: step(2), kind:'acid', text: '[unknown]: Wake up Neo...' }, { delay: step(3), kind:'acid', text: "[unknown]: The Matrix has you..." }, { delay: step(4), kind:'acid', text: '[unknown]: Follow the white rabbit...' }, { delay: step(5), kind:'acid', text: "[unknown]: Knock, knock, Neo..." }, { delay: step(8), kind:'sys', text: '// signal lost.' }, ], ]; function Terminal({ go }){ const [open, setOpen] = useState(false); const [input, setInput] = useState(''); const [history, setHistory] = useState([]); const [hIdx, setHIdx] = useState(-1); const [output, setOutput] = useState([]); const [busy, setBusy] = useState(false); const inputRef = useRef(null); const outputRef = useRef(null); const idleTimerRef = useRef(null); const sequenceTimersRef = useRef([]); const sequenceActiveRef = useRef(false); const lineCounterRef = useRef(0); const print = (text, kind) => { setOutput(prev => [...prev, { kind: kind || 'out', text: text == null ? '' : String(text) }]); }; // Append a line, then reveal it character by character. // Used by the idle sequence to feel like a live caller typing into the terminal. const typewrite = (text, kind, baseCharMs = 55) => { const lineId = ++lineCounterRef.current; setOutput(prev => [...prev, { id: lineId, kind: kind || 'out', text: '' }]); let acc = 0; for (let i = 1; i <= text.length; i++){ acc += baseCharMs + (Math.random() * 50 - 25); // ±25ms jitter const tid = setTimeout(() => { setOutput(prev => prev.map(line => line.id === lineId ? { ...line, text: text.slice(0, i) } : line )); }, acc); sequenceTimersRef.current.push(tid); } }; const cancelIdleSequence = () => { sequenceTimersRef.current.forEach(clearTimeout); sequenceTimersRef.current = []; sequenceActiveRef.current = false; }; const armIdleTimer = () => { if (idleTimerRef.current) clearTimeout(idleTimerRef.current); cancelIdleSequence(); if (!open) return; idleTimerRef.current = setTimeout(() => { sequenceActiveRef.current = true; // ── Phase 1: /hack effect (animated breach progress, ~2.7s) const tHack = setTimeout(() => COMMANDS.hack(true), 0); sequenceTimersRef.current.push(tHack); // ── Phase 2: /matrix noise (12 lines of katakana + final, instant) const matrixStart = 3500; const tMatrix = setTimeout(() => COMMANDS.matrix(), matrixStart); sequenceTimersRef.current.push(tMatrix); // ── Phase 3: dialogue (sequence B only — A is disabled for now) const dialogStart = matrixStart + 2000; const lines = IDLE_SEQUENCES[1]; let lastDialogDelay = 0; lines.forEach(line => { const id = setTimeout(() => typewrite(line.text, line.kind), dialogStart + line.delay); sequenceTimersRef.current.push(id); if (line.delay > lastDialogDelay) lastDialogDelay = line.delay; }); // ── Phase 4: rearm for next cycle (hack → matrix → dialog → repeat) const totalDuration = dialogStart + lastDialogDelay + 5000; const rearmId = setTimeout(() => { sequenceActiveRef.current = false; armIdleTimer(); }, totalDuration); sequenceTimersRef.current.push(rearmId); }, IDLE_MS); }; // bump idle timer on any user activity inside the terminal const bumpIdle = () => { if (open) armIdleTimer(); }; // ─── command table ──────────────────────────────────────── const goAndClose = (name, params) => { go(name, params || {}); print('→ ' + name, 'sys'); setOpen(false); }; const COMMANDS = { help: () => { print('available commands:', 'sys'); print(''); print(' /help this help'); print(' /home go to home'); print(' /book /order open booking flow'); print(' /works [filter] portfolio (blackwork|fineline|gothic|video)'); print(' /sketches /flash flash sketches'); print(' /services /price price list'); print(' /faq [n] faq, optionally jump to question N'); print(' /master /about about the artist'); print(' /contact telegram / instagram / mail'); print(''); print(' /whoami session identity', 'sys'); print(' /ls list site sections'); print(' /cat manifest dump the manifesto'); print(' /clear /cls wipe the screen'); print(' /date /time /uptime clocks'); print(' /echo repeat text'); print(' /sudo ☠'); print(' /hack ☠☠☠'); print(' /matrix ☠☠'); print(' /glitch shake the logo'); print(' /coffee ☕'); print(' /exit /quit close terminal (or ESC)'); }, home: () => goAndClose('home'), book: () => goAndClose('book'), order: () => goAndClose('book'), works: (arg) => { goAndClose('portfolio'); if (arg) print('filter applied: ' + arg + ' (visual filter inside portfolio)', 'sys'); }, portfolio: (arg) => COMMANDS.works(arg), sketches: () => goAndClose('sketch'), flash: () => goAndClose('sketch'), services: () => goAndClose('services'), price: () => goAndClose('services'), faq: () => goAndClose('faq'), master: () => goAndClose('about'), about: () => goAndClose('about'), contact: () => { print('// channels', 'sys'); print(' telegram @nokk717'); print(' instagram @nokk.717'); print(' e-mail nokk717@mail'); print(' studio astana, kz · by appointment only', 'sys'); }, whoami: () => { const sid = 'NK-' + Math.random().toString(16).slice(2, 8).toUpperCase(); print('nokk_visitor@dystopia // session ' + sid, 'sys'); print('uid=1000 gid=1000 groups=guest, no_copies, no_public_faces'); }, ls: () => { print('home portfolio sketches services faq about book', 'sys'); }, cat: (arg) => { if (!arg || /manifest/i.test(arg)) { print('// manifest.txt', 'sys'); print('tattoo as a ritual.'); print('ink under skin = signal that survives the server crash.'); print('research and development in dystopia.'); print(''); print('stop list: racist / nazi / homophobic motifs,', 'err'); print('copies without permission, faces of living public figures.', 'err'); } else { print('cat: ' + arg + ': no such file', 'err'); } }, clear: () => setOutput([]), cls: () => setOutput([]), date: () => print(new Date().toString(), 'sys'), time: () => print(new Date().toLocaleTimeString(), 'sys'), uptime: () => { const m = Math.floor(performance.now() / 60000); const s = Math.floor(performance.now() / 1000) % 60; print('up ' + m + 'm ' + s + 's, 1 ghost user, load avg: 0.717 0.420 0.069', 'sys'); }, echo: (...args) => print(args.join(' ')), sudo: () => { print('[sudo] permission denied: you are not nokk', 'err'); print(' this incident has been reported to angel.engine', 'err'); }, hack: (autoRun) => { if (!autoRun && busy) return; setBusy(true); print('▓▓▓ initiating breach ░░░░░░░░░░░░░░░░░░ 0%', 'acid'); let pct = 0; const bar = (p) => { const filled = Math.round(p / 5); return '▓'.repeat(filled) + '░'.repeat(20 - filled); }; const id = setInterval(() => { pct = Math.min(100, pct + 7 + Math.random() * 14); setOutput(prev => { const next = prev.slice(); next[next.length - 1] = { kind: 'acid', text: '▓▓▓ ' + bar(pct) + ' ' + Math.round(pct) + '%' }; return next; }); if (pct >= 100) { clearInterval(id); setTimeout(() => { print('[ ACCESS DENIED ]', 'err'); print('lol nice try. you wanted /matrix.', 'sys'); setBusy(false); }, 220); } }, 180); }, matrix: () => { const ch = '01アァカサタナハマヤラワガザダバパヲァィゥェォャュョッー░▒▓'; const cols = window.innerWidth >= 1024 ? 95 : 38; const rows = 12; const tickMs = 90; const durationMs = 3000; const rand = () => { let s = ''; for (let j = 0; j < cols; j++) s += ch[Math.floor(Math.random() * ch.length)]; return s; }; // Pre-allocate stable IDs so the interval can update only these lines const lineIds = []; for (let i = 0; i < rows; i++) lineIds.push(++lineCounterRef.current); setOutput(prev => { const next = prev.slice(); for (let i = 0; i < rows; i++){ next.push({ id: lineIds[i], kind: 'matrix', text: rand() }); } return next; }); // Animate: every tick, swap each line for a new random snapshot const totalTicks = Math.floor(durationMs / tickMs); let tick = 0; const intervalId = setInterval(() => { tick++; setOutput(prev => prev.map(line => lineIds.includes(line.id) ? { ...line, text: rand() } : line )); if (tick >= totalTicks){ clearInterval(intervalId); // Resolution line after the storm settles print('// signal in the noise. trace ends here.', 'sys'); } }, tickMs); // Register so any user activity (cancelIdleSequence) can stop the storm sequenceTimersRef.current.push(intervalId); }, glitch: () => { print('* shaking the logo *', 'sys'); const root = document.documentElement; const orig = root.style.getPropertyValue('--acid'); let i = 0; const palette = ['#ff3232', '#ffb000', '#7ad7ff', '#c8ff2e']; const id = setInterval(() => { root.style.setProperty('--acid', palette[i % palette.length]); i++; if (i > 12) { clearInterval(id); root.style.setProperty('--acid', orig || '#c8ff2e'); } }, 90); }, coffee: () => { print(' ) ) )', 'sys'); print(' ( ( (', 'sys'); print(' _________'); print(' [_________]'); print(' \\_______/'); print('// HTCPCP/1.0 418 — i\'m a teapot', 'err'); }, exit: () => setOpen(false), quit: () => setOpen(false), }; const ALIASES = ['/help', '/home', '/book', '/order', '/works', '/portfolio', '/sketches', '/flash', '/services', '/price', '/faq', '/master', '/about', '/contact', '/whoami', '/ls', '/cat', '/clear', '/cls', '/date', '/time', '/uptime', '/echo', '/sudo', '/hack', '/matrix', '/glitch', '/coffee', '/exit', '/quit']; const exec = (raw) => { cancelIdleSequence(); armIdleTimer(); const trimmed = raw.trim(); if (!trimmed) return; print('nokk@dystopia ~ % ' + trimmed, 'prompt'); const parts = trimmed.replace(/^\//, '').split(/\s+/); const cmd = parts[0].toLowerCase(); const args = parts.slice(1); if (COMMANDS[cmd]){ try { COMMANDS[cmd](...args); } catch(e){ print('runtime: ' + e.message, 'err'); } } else { print('zsh: command not found: ' + cmd + '. try /help', 'err'); } setHistory(prev => [...prev, trimmed]); setHIdx(-1); }; const onKey = (e) => { bumpIdle(); if (e.key === 'Enter'){ e.preventDefault(); exec(input); setInput(''); } else if (e.key === 'ArrowUp'){ e.preventDefault(); if (history.length === 0) return; const idx = hIdx === -1 ? history.length - 1 : Math.max(0, hIdx - 1); setHIdx(idx); setInput(history[idx]); } else if (e.key === 'ArrowDown'){ e.preventDefault(); if (hIdx === -1) return; const idx = hIdx + 1; if (idx >= history.length){ setHIdx(-1); setInput(''); } else { setHIdx(idx); setInput(history[idx]); } } else if (e.key === 'Tab'){ e.preventDefault(); const stub = input.startsWith('/') ? input : '/' + input; const matches = ALIASES.filter(a => a.startsWith(stub)); if (matches.length === 1) setInput(matches[0] + ' '); else if (matches.length > 1) print(matches.join(' '), 'sys'); } else if (e.key === 'Escape'){ setOpen(false); } }; // welcome banner once + idle-timer lifecycle useEffect(() => { if (open && output.length === 0){ print('nokk.os v0.7.17 — angel.engine secure shell', 'sys'); print('type /help for commands · ESC to close', 'sys'); print(''); } if (open && inputRef.current) inputRef.current.focus(); if (open) { armIdleTimer(); } else { if (idleTimerRef.current) clearTimeout(idleTimerRef.current); cancelIdleSequence(); } return () => { if (idleTimerRef.current) clearTimeout(idleTimerRef.current); cancelIdleSequence(); }; }, [open]); useEffect(() => { if (outputRef.current) outputRef.current.scrollTop = outputRef.current.scrollHeight; }, [output]); return (
{!open && ( )} {open && (
nokk@dystopia // ssh.717
{output.map((line, i) => (
{line.text || ' '}
))}
{ e.preventDefault(); exec(input); setInput(''); }}> nokk@dystopia ~ % setInput(e.target.value)} onKeyDown={onKey} autoComplete="off" autoCapitalize="off" autoCorrect="off" spellCheck="false" placeholder="/help" />
)}
); } window.Terminal = Terminal;