mirror of
https://github.com/litruv/lit.ruv.wtf.git
synced 2026-07-26 19:56:03 +10:00
Compare commits
7 Commits
bf283e8425
...
9b9ab8593f
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b9ab8593f | |||
| 6529eb2e6b | |||
| 7d9a182b4d | |||
| 864b1e3ecc | |||
| 628a0cb948 | |||
| 3d987dced1 | |||
| 288b48ddab |
@@ -22,12 +22,65 @@ const BIOS_LINES = [
|
|||||||
{ text: 'Starting LIT.RUV.WTF Terminal...', class: 'highlight', delay: 400 }
|
{ text: 'Starting LIT.RUV.WTF Terminal...', class: 'highlight', delay: 400 }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const BOOT_STARTUP_SOUND_PATH = window.location.hostname.endsWith('neocities.org')
|
||||||
|
? 'https://lit.ruv.wtf/sounds/551405__nakkivene66__old-pc-startup-idle-shutdown.wav'
|
||||||
|
: 'sounds/551405__nakkivene66__old-pc-startup-idle-shutdown.wav';
|
||||||
|
|
||||||
|
let bootStartupSoundPlayed = false;
|
||||||
|
let bootStartupSoundUnlockBound = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play the boot startup sound as early as possible.
|
||||||
|
* Falls back to first user interaction when autoplay is blocked.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async function playBootStartupSound() {
|
||||||
|
if (bootStartupSoundPlayed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const startupSound = new Audio(BOOT_STARTUP_SOUND_PATH);
|
||||||
|
startupSound.preload = 'auto';
|
||||||
|
startupSound.volume = 0.55;
|
||||||
|
await startupSound.play();
|
||||||
|
bootStartupSoundPlayed = true;
|
||||||
|
bootStartupSoundUnlockBound = false;
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
if (bootStartupSoundUnlockBound) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bootStartupSoundUnlockBound = true;
|
||||||
|
const unlockAndPlay = async () => {
|
||||||
|
if (bootStartupSoundPlayed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const deferredSound = new Audio(BOOT_STARTUP_SOUND_PATH);
|
||||||
|
deferredSound.preload = 'auto';
|
||||||
|
deferredSound.volume = 0.55;
|
||||||
|
await deferredSound.play();
|
||||||
|
bootStartupSoundPlayed = true;
|
||||||
|
} catch (retryError) {
|
||||||
|
// Keep boot sequence running even if sound cannot play.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('pointerdown', unlockAndPlay, { once: true });
|
||||||
|
document.addEventListener('keydown', unlockAndPlay, { once: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the BIOS boot animation
|
* Run the BIOS boot animation
|
||||||
* @returns {Promise} Resolves when animation is complete
|
* @returns {Promise} Resolves when animation is complete
|
||||||
*/
|
*/
|
||||||
function runBootAnimation() {
|
function runBootAnimation() {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
|
void playBootStartupSound();
|
||||||
const bootScreen = document.getElementById('bootScreen');
|
const bootScreen = document.getElementById('bootScreen');
|
||||||
const biosText = document.getElementById('biosText');
|
const biosText = document.getElementById('biosText');
|
||||||
const pageFade = document.getElementById('pageFade');
|
const pageFade = document.getElementById('pageFade');
|
||||||
|
|||||||
75
website/index-neocities.html
Normal file
75
website/index-neocities.html
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
|
<title>user@litruv.neocities.org - Terminal</title>
|
||||||
|
<meta name="description" content="Interactive terminal interface for Lit.ruv.wtf - A retro-styled command line experience with chat, documentation, and more.">
|
||||||
|
|
||||||
|
<!-- Favicons -->
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="logos/16px.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="logos/32px.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="48x48" href="logos/48px.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="64x64" href="logos/64px.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="128x128" href="logos/128px.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="256x256" href="logos/256px.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="logos/256px.png">
|
||||||
|
|
||||||
|
<!-- Open Graph / Social Media -->
|
||||||
|
<meta property="og:type" content="website">
|
||||||
|
<meta property="og:url" content="https://litruv.neocities.org/">
|
||||||
|
<meta property="og:title" content="user@litruv.neocities.org - Terminal">
|
||||||
|
<meta property="og:description" content="Interactive terminal interface with chat, docs, and retro vibes">
|
||||||
|
<meta property="og:image" content="https://lit.ruv.wtf/banner.webp">
|
||||||
|
|
||||||
|
<!-- Twitter Card -->
|
||||||
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
|
<meta name="twitter:url" content="https://litruv.neocities.org/">
|
||||||
|
<meta name="twitter:title" content="user@litruv.neocities.org - Terminal">
|
||||||
|
<meta name="twitter:description" content="Interactive terminal interface with chat, docs, and retro vibes">
|
||||||
|
<meta name="twitter:image" content="https://lit.ruv.wtf/banner.webp">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.min.css" />
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="page-fade-overlay" id="pageFade"></div>
|
||||||
|
<div class="boot-screen" id="bootScreen">
|
||||||
|
<pre id="biosText"></pre>
|
||||||
|
</div>
|
||||||
|
<div class="crt-border-overlay"></div>
|
||||||
|
<div class="container">
|
||||||
|
<div class="terminal-header">
|
||||||
|
<div class="header-left">
|
||||||
|
<span class="terminal-title">LITRUV.NEOCITIES.ORG TERMINAL</span>
|
||||||
|
<span class="header-links">
|
||||||
|
<a href="https://lit.ruv.wtf/docs/" target="_blank" class="header-link">Docs</a>
|
||||||
|
<a href="https://github.com/litruv" target="_blank" class="header-link">GitHub</a>
|
||||||
|
<a href="https://bsky.app/profile/lit.mates.dev" target="_blank" class="header-link">Bluesky</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<span class="system-time" id="systemTime"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="terminal"></div>
|
||||||
|
<div class="status-bar">
|
||||||
|
<span class="status-left">Ready</span>
|
||||||
|
<span class="status-right">
|
||||||
|
<span class="quick-commands">
|
||||||
|
<button class="quick-cmd" onclick="runQuickCommand('help')" title="Show all commands">help</button>
|
||||||
|
<button class="quick-cmd" onclick="runQuickCommand('about')" title="About this terminal">about</button>
|
||||||
|
<button class="quick-cmd" onclick="runQuickCommand('chat')" title="Join chat room">chat</button>
|
||||||
|
<button class="quick-cmd" onclick="runQuickCommand('clear')" title="Clear screen">clear</button>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-web-links@0.9.0/lib/xterm-addon-web-links.min.js"></script>
|
||||||
|
<script src="boot.js"></script>
|
||||||
|
<script type="module" src="terminal.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -7,13 +7,13 @@
|
|||||||
<meta name="description" content="Interactive terminal interface for Lit.ruv.wtf - A retro-styled command line experience with chat, documentation, and more.">
|
<meta name="description" content="Interactive terminal interface for Lit.ruv.wtf - A retro-styled command line experience with chat, documentation, and more.">
|
||||||
|
|
||||||
<!-- Favicons -->
|
<!-- Favicons -->
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/logos/16px.png">
|
<link rel="icon" type="image/png" sizes="16x16" href="logos/16px.png">
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/logos/32px.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="logos/32px.png">
|
||||||
<link rel="icon" type="image/png" sizes="48x48" href="/logos/48px.png">
|
<link rel="icon" type="image/png" sizes="48x48" href="logos/48px.png">
|
||||||
<link rel="icon" type="image/png" sizes="64x64" href="/logos/64px.png">
|
<link rel="icon" type="image/png" sizes="64x64" href="logos/64px.png">
|
||||||
<link rel="icon" type="image/png" sizes="128x128" href="/logos/128px.png">
|
<link rel="icon" type="image/png" sizes="128x128" href="logos/128px.png">
|
||||||
<link rel="icon" type="image/png" sizes="256x256" href="/logos/256px.png">
|
<link rel="icon" type="image/png" sizes="256x256" href="logos/256px.png">
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/logos/256px.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="logos/256px.png">
|
||||||
|
|
||||||
<!-- Open Graph / Social Media -->
|
<!-- Open Graph / Social Media -->
|
||||||
<meta property="og:type" content="website">
|
<meta property="og:type" content="website">
|
||||||
@@ -70,6 +70,6 @@
|
|||||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-web-links@0.9.0/lib/xterm-addon-web-links.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-web-links@0.9.0/lib/xterm-addon-web-links.min.js"></script>
|
||||||
<script src="boot.js"></script>
|
<script src="boot.js"></script>
|
||||||
<script src="terminal.js"></script>
|
<script type="module" src="terminal.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
56
website/logos/LogoFull.svg
Normal file
56
website/logos/LogoFull.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 77 KiB |
13
website/matrix-bridge.html
Normal file
13
website/matrix-bridge.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Matrix Chat Bridge</title>
|
||||||
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- This page acts as a bridge for Matrix API calls from CSP-restricted hosts -->
|
||||||
|
<script src="matrix-bridge.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
234
website/matrix-bridge.js
Normal file
234
website/matrix-bridge.js
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
/**
|
||||||
|
* Matrix Chat Bridge - Iframe communication layer for CSP-restricted hosts
|
||||||
|
* Handles Matrix API calls via postMessage from parent window
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Allow requests from these origins
|
||||||
|
const ALLOWED_ORIGINS = [
|
||||||
|
'https://litruv.neocities.org',
|
||||||
|
'https://lit.ruv.wtf',
|
||||||
|
'http://localhost:3000',
|
||||||
|
'http://localhost:8080',
|
||||||
|
'http://127.0.0.1:3000',
|
||||||
|
'http://127.0.0.1:8080'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matrix API configuration
|
||||||
|
*/
|
||||||
|
const MATRIX_CONFIG = {
|
||||||
|
homeserver: 'https://chat.ruv.wtf',
|
||||||
|
publicReadToken: 'syt_Z2VuZXJhbGNoYXQtcmVhZG9ubHk_sikLltUtfbHlztnanEVm_2icJ1o'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a Matrix API call
|
||||||
|
* @param {string} endpoint - API endpoint path
|
||||||
|
* @param {object} options - Fetch options
|
||||||
|
* @param {string|null} accessToken - Optional access token
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
async function matrixApiFetch(endpoint, options = {}, accessToken = null) {
|
||||||
|
const url = `${MATRIX_CONFIG.homeserver}${endpoint}`;
|
||||||
|
const headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...options.headers
|
||||||
|
};
|
||||||
|
|
||||||
|
if (accessToken) {
|
||||||
|
headers['Authorization'] = `Bearer ${accessToken}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
...options,
|
||||||
|
headers
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return { error: true, ...data };
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
errcode: 'M_NETWORK_ERROR',
|
||||||
|
error: error.message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle Matrix API requests from parent window
|
||||||
|
*/
|
||||||
|
window.addEventListener('message', async (event) => {
|
||||||
|
// Verify origin
|
||||||
|
if (!ALLOWED_ORIGINS.includes(event.origin)) {
|
||||||
|
console.warn('[Matrix Bridge] Rejected message from unauthorized origin:', event.origin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { type, requestId, payload } = event.data;
|
||||||
|
|
||||||
|
if (!type || !requestId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result;
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (type) {
|
||||||
|
case 'matrix:api':
|
||||||
|
result = await handleApiRequest(payload);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'matrix:auth':
|
||||||
|
result = await handleAuth(payload);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'matrix:sendMessage':
|
||||||
|
result = await handleSendMessage(payload);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'matrix:fetchPresence':
|
||||||
|
result = await handleFetchPresence(payload);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'matrix:fetchLastMessage':
|
||||||
|
result = await handleFetchLastMessage(payload);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'matrix:sync':
|
||||||
|
result = await handleSync(payload);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'matrix:resolveAlias':
|
||||||
|
result = await handleResolveAlias(payload);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
result = {
|
||||||
|
error: true,
|
||||||
|
errcode: 'M_UNKNOWN_REQUEST',
|
||||||
|
error: `Unknown request type: ${type}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
result = {
|
||||||
|
error: true,
|
||||||
|
errcode: 'M_BRIDGE_ERROR',
|
||||||
|
error: error.message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send response back to parent
|
||||||
|
event.source.postMessage({
|
||||||
|
type: `${type}:response`,
|
||||||
|
requestId,
|
||||||
|
payload: result
|
||||||
|
}, event.origin);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle generic Matrix API request
|
||||||
|
*/
|
||||||
|
async function handleApiRequest({ endpoint, method = 'GET', body = null, accessToken = null }) {
|
||||||
|
return await matrixApiFetch(
|
||||||
|
`/_matrix/client/r0${endpoint}`,
|
||||||
|
{
|
||||||
|
method,
|
||||||
|
body: body ? JSON.stringify(body) : undefined
|
||||||
|
},
|
||||||
|
accessToken
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle authentication request
|
||||||
|
*/
|
||||||
|
async function handleAuth({ username, password }) {
|
||||||
|
return await matrixApiFetch('/_matrix/client/r0/login', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
type: 'm.login.password',
|
||||||
|
identifier: {
|
||||||
|
type: 'm.id.user',
|
||||||
|
user: username
|
||||||
|
},
|
||||||
|
password: password
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle send message request
|
||||||
|
*/
|
||||||
|
async function handleSendMessage({ roomId, content, accessToken, txnId }) {
|
||||||
|
const msgtype = content.msgtype || 'm.text';
|
||||||
|
const endpoint = `/_matrix/client/r0/rooms/${encodeURIComponent(roomId)}/send/m.room.message/${encodeURIComponent(txnId)}`;
|
||||||
|
|
||||||
|
return await matrixApiFetch(endpoint, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(content)
|
||||||
|
}, accessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle fetch presence request
|
||||||
|
*/
|
||||||
|
async function handleFetchPresence({ userId }) {
|
||||||
|
return await matrixApiFetch(
|
||||||
|
`/_matrix/client/r0/presence/${encodeURIComponent(userId)}/status`,
|
||||||
|
{ method: 'GET' },
|
||||||
|
MATRIX_CONFIG.publicReadToken
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle fetch last message request
|
||||||
|
*/
|
||||||
|
async function handleFetchLastMessage({ roomId }) {
|
||||||
|
return await matrixApiFetch(
|
||||||
|
`/_matrix/client/r0/rooms/${encodeURIComponent(roomId)}/messages?dir=b&limit=1`,
|
||||||
|
{ method: 'GET' },
|
||||||
|
MATRIX_CONFIG.publicReadToken
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle sync request
|
||||||
|
*/
|
||||||
|
async function handleSync({ accessToken, since, timeout = 0 }) {
|
||||||
|
let endpoint = `/_matrix/client/r0/sync?timeout=${timeout}`;
|
||||||
|
if (since) {
|
||||||
|
endpoint += `&since=${encodeURIComponent(since)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await matrixApiFetch(endpoint, {
|
||||||
|
method: 'GET'
|
||||||
|
}, accessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle room alias resolution
|
||||||
|
*/
|
||||||
|
async function handleResolveAlias({ roomAlias }) {
|
||||||
|
return await matrixApiFetch(
|
||||||
|
`/_matrix/client/r0/directory/room/${encodeURIComponent(roomAlias)}`,
|
||||||
|
{ method: 'GET' },
|
||||||
|
MATRIX_CONFIG.publicReadToken
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify parent that bridge is ready
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
if (window.parent !== window) {
|
||||||
|
window.parent.postMessage({
|
||||||
|
type: 'matrix:bridge:ready'
|
||||||
|
}, '*');
|
||||||
|
}
|
||||||
|
console.log('[Matrix Bridge] Ready and listening for requests');
|
||||||
|
});
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 329 KiB After Width: | Height: | Size: 268 KiB |
25
website/scripts/commands/about.js
Normal file
25
website/scripts/commands/about.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* About command - Information about this terminal
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'About this terminal',
|
||||||
|
execute: (term, writeClickable, VERSION) => {
|
||||||
|
return [
|
||||||
|
'',
|
||||||
|
'╔════════════════════════════════════════════════════════════╗',
|
||||||
|
'║ LIT.RUV.WTF TERMINAL ║',
|
||||||
|
'╚════════════════════════════════════════════════════════════╝',
|
||||||
|
'',
|
||||||
|
' A classic terminal interface built with xterm.js',
|
||||||
|
' Features: Keyboard navigation, Mouse support, CRT effects',
|
||||||
|
' Version: ' + VERSION,
|
||||||
|
' Built: ' + new Date().getFullYear(),
|
||||||
|
'',
|
||||||
|
' Technologies:',
|
||||||
|
' • xterm.js - Terminal emulator',
|
||||||
|
' • JavaScript - Terminal logic',
|
||||||
|
' • CSS3 - Classic CRT styling',
|
||||||
|
''
|
||||||
|
].join('\r\n');
|
||||||
|
}
|
||||||
|
};
|
||||||
24
website/scripts/commands/banner.js
Normal file
24
website/scripts/commands/banner.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Banner command - Display welcome banner
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'Display welcome banner',
|
||||||
|
execute: (term, writeClickable, VERSION, args, commandHistory, welcomeBannerFull, welcomeBannerCompact, welcomeBannerMinimal) => {
|
||||||
|
const cols = term.cols;
|
||||||
|
if (cols >= 78) {
|
||||||
|
term.writeln(welcomeBannerFull.split('\r\n').slice(0, -3).join('\r\n'));
|
||||||
|
writeClickable(' Type [command=help] for available commands.');
|
||||||
|
term.writeln(' Use ↑/↓ arrows to navigate command history.');
|
||||||
|
term.writeln('');
|
||||||
|
} else if (cols >= 40) {
|
||||||
|
term.writeln(welcomeBannerCompact.split('\r\n').slice(0, -2).join('\r\n'));
|
||||||
|
writeClickable(' Welcome! Type [command=help] for commands.');
|
||||||
|
term.writeln('');
|
||||||
|
} else {
|
||||||
|
term.writeln(welcomeBannerMinimal.split('\r\n').slice(0, -2).join('\r\n'));
|
||||||
|
writeClickable(' Type [command=help]');
|
||||||
|
term.writeln('');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
79
website/scripts/commands/bluesky.js
Normal file
79
website/scripts/commands/bluesky.js
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* Bluesky command - Fetch recent posts from Bluesky
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'Fetch recent posts from Bluesky',
|
||||||
|
execute: async (term, writeClickable, VERSION, args) => {
|
||||||
|
const actor = 'lit.mates.dev';
|
||||||
|
const limit = args[0] ? parseInt(args[0]) : 5;
|
||||||
|
|
||||||
|
try {
|
||||||
|
term.writeln('\r\n Fetching posts from Bluesky...\r\n');
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed?actor=${actor}&limit=${limit}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return ' Error: Unable to fetch Bluesky posts';
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!data.feed || data.feed.length === 0) {
|
||||||
|
return ' No posts found.';
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = [' ╔════════════════════════════════════════════════════╗'];
|
||||||
|
output.push(' ║ BLUESKY POSTS - @lit.mates.dev ║');
|
||||||
|
output.push(' ╚════════════════════════════════════════════════════╝');
|
||||||
|
output.push('');
|
||||||
|
|
||||||
|
// Reverse to show oldest first, latest at bottom
|
||||||
|
const reversedFeed = [...data.feed].reverse();
|
||||||
|
|
||||||
|
reversedFeed.forEach((item, idx) => {
|
||||||
|
const post = item.post;
|
||||||
|
const text = post.record.text;
|
||||||
|
const createdAt = new Date(post.record.createdAt);
|
||||||
|
const date = createdAt.toLocaleDateString();
|
||||||
|
const time = createdAt.toLocaleTimeString('en-US', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Extract post ID from URI (at://did:plc:xxx/app.bsky.feed.post/{postId})
|
||||||
|
const postId = post.uri.split('/').pop();
|
||||||
|
const postUrl = `https://bsky.app/profile/${actor}/post/${postId}`;
|
||||||
|
|
||||||
|
output.push(` [${idx + 1}] ${date} ${time}`);
|
||||||
|
output.push(` ${postUrl}`);
|
||||||
|
output.push(' ────────────────────────────────────────');
|
||||||
|
|
||||||
|
// Wrap text to max 50 chars
|
||||||
|
const words = text.split(' ');
|
||||||
|
let line = ' ';
|
||||||
|
words.forEach(word => {
|
||||||
|
if (line.length + word.length + 1 > 52) {
|
||||||
|
output.push(line);
|
||||||
|
line = ' ' + word;
|
||||||
|
} else {
|
||||||
|
line += (line.length > 2 ? ' ' : '') + word;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (line.length > 2) output.push(line);
|
||||||
|
|
||||||
|
output.push('');
|
||||||
|
output.push(` ♡ ${post.likeCount || 0} ↻ ${post.repostCount || 0} 💬 ${post.replyCount || 0}`);
|
||||||
|
output.push('');
|
||||||
|
});
|
||||||
|
|
||||||
|
output.push(` Usage: bluesky [count] (default: 5, max: 20)`);
|
||||||
|
output.push('');
|
||||||
|
|
||||||
|
return output.join('\r\n');
|
||||||
|
} catch (error) {
|
||||||
|
return ' Error: Failed to connect to Bluesky API';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
10
website/scripts/commands/clear.js
Normal file
10
website/scripts/commands/clear.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Clear command - Clear terminal screen
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'Clear terminal screen',
|
||||||
|
execute: (term) => {
|
||||||
|
term.clear();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
38
website/scripts/commands/color.js
Normal file
38
website/scripts/commands/color.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Color command - Change color scheme
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'Change color scheme',
|
||||||
|
execute: (term, writeClickable, VERSION, args) => {
|
||||||
|
const scheme = args[0] || '';
|
||||||
|
const schemes = {
|
||||||
|
green: { bg: '#001800', fg: '#00ff00', border: '#0f0' },
|
||||||
|
amber: { bg: '#1a0f00', fg: '#ffb000', border: '#ffb000' },
|
||||||
|
blue: { bg: '#000818', fg: '#00a0ff', border: '#00a0ff' },
|
||||||
|
white: { bg: '#0a0a0a', fg: '#e0e0e0', border: '#999' }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!scheme || !schemes[scheme]) {
|
||||||
|
return [
|
||||||
|
'',
|
||||||
|
' Available color schemes:',
|
||||||
|
' • green - Classic green terminal',
|
||||||
|
' • amber - Amber monochrome',
|
||||||
|
' • blue - IBM blue',
|
||||||
|
' • white - White phosphor',
|
||||||
|
'',
|
||||||
|
' Usage: color [scheme]',
|
||||||
|
''
|
||||||
|
].join('\r\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
const colors = schemes[scheme];
|
||||||
|
term.options.theme.background = colors.bg;
|
||||||
|
term.options.theme.foreground = colors.fg;
|
||||||
|
document.querySelector('.container').style.borderColor = colors.border;
|
||||||
|
document.querySelector('.container').style.background = colors.bg;
|
||||||
|
document.body.style.color = colors.fg;
|
||||||
|
|
||||||
|
return `\r\n Color scheme changed to: ${scheme}\r\n`;
|
||||||
|
}
|
||||||
|
};
|
||||||
16
website/scripts/commands/contact.js
Normal file
16
website/scripts/commands/contact.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Contact command - Contact information
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'Contact information',
|
||||||
|
execute: () => {
|
||||||
|
return [
|
||||||
|
'',
|
||||||
|
' Contact Information:',
|
||||||
|
' ────────────────────',
|
||||||
|
' Email: contact@lit.ruv.wtf',
|
||||||
|
' Web: https://lit.ruv.wtf',
|
||||||
|
''
|
||||||
|
].join('\r\n');
|
||||||
|
}
|
||||||
|
};
|
||||||
20
website/scripts/commands/date.js
Normal file
20
website/scripts/commands/date.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Date command - Display current date and time
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'Display current date and time',
|
||||||
|
execute: () => {
|
||||||
|
const now = new Date();
|
||||||
|
return [
|
||||||
|
'',
|
||||||
|
' ' + now.toLocaleDateString('en-US', {
|
||||||
|
weekday: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
}),
|
||||||
|
' ' + now.toLocaleTimeString('en-US'),
|
||||||
|
''
|
||||||
|
].join('\r\n');
|
||||||
|
}
|
||||||
|
};
|
||||||
9
website/scripts/commands/echo.js
Normal file
9
website/scripts/commands/echo.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Echo command - Echo back message
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'Echo back message',
|
||||||
|
execute: (term, writeClickable, VERSION, args) => {
|
||||||
|
return args.join(' ') || '';
|
||||||
|
}
|
||||||
|
};
|
||||||
9
website/scripts/commands/github.js
Normal file
9
website/scripts/commands/github.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* GitHub command - Open GitHub repository
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'Open GitHub repository',
|
||||||
|
execute: () => {
|
||||||
|
return '\r\n Opening GitHub...\r\n (This would open your repository URL)\r\n';
|
||||||
|
}
|
||||||
|
};
|
||||||
32
website/scripts/commands/help.js
Normal file
32
website/scripts/commands/help.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Help command - Display available commands
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'Display available commands',
|
||||||
|
execute: (term, writeClickable) => {
|
||||||
|
term.writeln('');
|
||||||
|
term.writeln('╔════════════════════════════════════════════════════════════╗');
|
||||||
|
term.writeln('║ AVAILABLE COMMANDS ║');
|
||||||
|
term.writeln('╚════════════════════════════════════════════════════════════╝');
|
||||||
|
term.writeln('');
|
||||||
|
writeClickable(' [command=help] - Display this help message');
|
||||||
|
writeClickable(' [command=about] - Information about this terminal');
|
||||||
|
writeClickable(' [command=clear] - Clear the terminal screen');
|
||||||
|
term.writeln(' echo - Echo back your message (usage: echo [message])');
|
||||||
|
writeClickable(' [command=date] - Display current date and time');
|
||||||
|
writeClickable(' [command=whoami] - Display current user information');
|
||||||
|
writeClickable(' [command=history] - Show command history');
|
||||||
|
writeClickable(' [command=color] - Change terminal color scheme');
|
||||||
|
writeClickable(' [command=banner] - Display welcome banner');
|
||||||
|
writeClickable(' [command=bluesky] - Fetch recent posts from Bluesky');
|
||||||
|
writeClickable(' [command=chat] - Enter interactive chat (type /quit to exit)');
|
||||||
|
writeClickable(' [command=github] - Visit GitHub repository');
|
||||||
|
writeClickable(' [command=contact] - Display contact information');
|
||||||
|
writeClickable(' [command=privacy] - Display privacy policy');
|
||||||
|
term.writeln('');
|
||||||
|
term.writeln('Navigate: Use ↑/↓ arrows for command history');
|
||||||
|
term.writeln('Mouse: Click commands to run them');
|
||||||
|
term.writeln('');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
17
website/scripts/commands/history.js
Normal file
17
website/scripts/commands/history.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* History command - Show command history
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'Show command history',
|
||||||
|
execute: (term, writeClickable, VERSION, args, commandHistory) => {
|
||||||
|
if (commandHistory.length === 0) {
|
||||||
|
return '\r\n No command history yet.\r\n';
|
||||||
|
}
|
||||||
|
let output = ['\r\n Command History:', ' ───────────────'];
|
||||||
|
commandHistory.forEach((cmd, idx) => {
|
||||||
|
output.push(` ${(idx + 1).toString().padStart(3, ' ')} ${cmd}`);
|
||||||
|
});
|
||||||
|
output.push('');
|
||||||
|
return output.join('\r\n');
|
||||||
|
}
|
||||||
|
};
|
||||||
36
website/scripts/commands/privacy.js
Normal file
36
website/scripts/commands/privacy.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Privacy command - Privacy policy
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'Privacy policy',
|
||||||
|
execute: () => {
|
||||||
|
return [
|
||||||
|
'',
|
||||||
|
'╔════════════════════════════════════════════════════════════╗',
|
||||||
|
'║ PRIVACY POLICY ║',
|
||||||
|
'╚════════════════════════════════════════════════════════════╝',
|
||||||
|
'',
|
||||||
|
' Data Collection:',
|
||||||
|
' ────────────────',
|
||||||
|
' • This terminal uses localStorage to save your chat session',
|
||||||
|
' • Chat messages are stored on our Matrix homeserver',
|
||||||
|
' • No cookies or tracking scripts are used',
|
||||||
|
' • No analytics or third-party tracking',
|
||||||
|
'',
|
||||||
|
' Matrix Chat:',
|
||||||
|
' ────────────',
|
||||||
|
' • Chat credentials stored locally in your browser',
|
||||||
|
' • Messages sent through Matrix protocol (b.ruv.wtf)',
|
||||||
|
' • Use "chat disconnect" to clear stored credentials',
|
||||||
|
'',
|
||||||
|
' Your Rights:',
|
||||||
|
' ────────────',
|
||||||
|
' • Clear localStorage anytime via browser settings',
|
||||||
|
' • Request data deletion: contact@lit.ruv.wtf',
|
||||||
|
' • All code is open source and auditable',
|
||||||
|
'',
|
||||||
|
' Updates: Privacy policy last updated March 2026',
|
||||||
|
''
|
||||||
|
].join('\r\n');
|
||||||
|
}
|
||||||
|
};
|
||||||
15
website/scripts/commands/whoami.js
Normal file
15
website/scripts/commands/whoami.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Whoami command - Display user information
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
description: 'Display user information',
|
||||||
|
execute: () => {
|
||||||
|
return [
|
||||||
|
'',
|
||||||
|
' User: visitor@lit.ruv.wtf',
|
||||||
|
' Session: ' + Date.now(),
|
||||||
|
' Terminal: xterm.js',
|
||||||
|
''
|
||||||
|
].join('\r\n');
|
||||||
|
}
|
||||||
|
};
|
||||||
Binary file not shown.
BIN
website/sounds/floppyreadshort.wav
Normal file
BIN
website/sounds/floppyreadshort.wav
Normal file
Binary file not shown.
BIN
website/sounds/poweroff.mp3
Normal file
BIN
website/sounds/poweroff.mp3
Normal file
Binary file not shown.
BIN
website/sounds/poweron.mp3
Normal file
BIN
website/sounds/poweron.mp3
Normal file
Binary file not shown.
BIN
website/sounds/ui_hacking_charenter_01.wav
Normal file
BIN
website/sounds/ui_hacking_charenter_01.wav
Normal file
Binary file not shown.
BIN
website/sounds/ui_hacking_charenter_02.wav
Normal file
BIN
website/sounds/ui_hacking_charenter_02.wav
Normal file
Binary file not shown.
BIN
website/sounds/ui_hacking_charenter_03.wav
Normal file
BIN
website/sounds/ui_hacking_charenter_03.wav
Normal file
Binary file not shown.
BIN
website/sounds/ui_hacking_charscroll.wav
Normal file
BIN
website/sounds/ui_hacking_charscroll.wav
Normal file
Binary file not shown.
BIN
website/sounds/ui_hacking_charscroll_lp.wav
Normal file
BIN
website/sounds/ui_hacking_charscroll_lp.wav
Normal file
Binary file not shown.
BIN
website/sounds/ui_hacking_charsingle_01.wav
Normal file
BIN
website/sounds/ui_hacking_charsingle_01.wav
Normal file
Binary file not shown.
BIN
website/sounds/ui_hacking_charsingle_02.wav
Normal file
BIN
website/sounds/ui_hacking_charsingle_02.wav
Normal file
Binary file not shown.
BIN
website/sounds/ui_hacking_charsingle_03.wav
Normal file
BIN
website/sounds/ui_hacking_charsingle_03.wav
Normal file
Binary file not shown.
BIN
website/sounds/ui_hacking_charsingle_04.wav
Normal file
BIN
website/sounds/ui_hacking_charsingle_04.wav
Normal file
Binary file not shown.
BIN
website/sounds/ui_hacking_charsingle_05.wav
Normal file
BIN
website/sounds/ui_hacking_charsingle_05.wav
Normal file
Binary file not shown.
BIN
website/sounds/ui_hacking_charsingle_06.wav
Normal file
BIN
website/sounds/ui_hacking_charsingle_06.wav
Normal file
Binary file not shown.
@@ -214,6 +214,32 @@ body {
|
|||||||
color: rgba(0, 255, 0, 0.3);
|
color: rgba(0, 255, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mention-suggestions {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 11;
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
max-width: 85%;
|
||||||
|
padding: 1px 4px;
|
||||||
|
border: 1px solid rgba(0, 255, 0, 0.45);
|
||||||
|
background: rgba(0, 24, 0, 0.92);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mention-suggestion {
|
||||||
|
color: #66ff66;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mention-suggestion.selected {
|
||||||
|
color: #001800;
|
||||||
|
background: #66ff66;
|
||||||
|
padding: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.status-bar {
|
.status-bar {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border-top: 2px solid #0f0;
|
border-top: 2px solid #0f0;
|
||||||
@@ -489,6 +515,29 @@ body.keyboard-open .status-bar {
|
|||||||
padding: 3px 8px;
|
padding: 3px 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Small viewport (under 1280x720) - detected via JS */
|
||||||
|
body.small-viewport .crt-border-overlay {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.small-viewport .container,
|
||||||
|
body.small-viewport .boot-screen {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.small-viewport .terminal-header {
|
||||||
|
border-bottom: 1px solid #0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.small-viewport .status-bar {
|
||||||
|
border-top: 1px solid #0f0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Scrollbar styling */
|
/* Scrollbar styling */
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
|
|||||||
1499
website/terminal.js
1499
website/terminal.js
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user