Auto-backup: 2026-09-04 00:33
This commit is contained in:
parent
f14c82dc42
commit
d93aca923b
14 changed files with 656 additions and 52 deletions
354
admin-projects.js
Normal file
354
admin-projects.js
Normal file
|
|
@ -0,0 +1,354 @@
|
||||||
|
const http = require('http');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const POSTS_DIR = path.join(__dirname, 'content/posts');
|
||||||
|
const PORT = 3002;
|
||||||
|
const STATUS_CLASSES = ['stable', 'corrupted', 'experimental'];
|
||||||
|
|
||||||
|
function esc(s) {
|
||||||
|
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
function slugify(title) {
|
||||||
|
return String(title)
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[äöü]/g, m => ({ ä: 'ae', ö: 'oe', ü: 'ue' }[m]))
|
||||||
|
.replace(/ß/g, 'ss')
|
||||||
|
.replace(/[^a-z0-9]+/g, '-')
|
||||||
|
.replace(/^-+|-+$/g, '') || 'projekt';
|
||||||
|
}
|
||||||
|
|
||||||
|
function uniqueSlug(base) {
|
||||||
|
let slug = base;
|
||||||
|
let i = 2;
|
||||||
|
while (fs.existsSync(path.join(POSTS_DIR, slug + '.md'))) {
|
||||||
|
slug = base + '-' + i;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── FRONTMATTER PARSEN / SCHREIBEN ──────────────────────────────
|
||||||
|
function parsePost(filename) {
|
||||||
|
const raw = fs.readFileSync(path.join(POSTS_DIR, filename), 'utf8');
|
||||||
|
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
||||||
|
const fm = { title: '', date: '', status_class: 'stable', status_label: '', thumb: '' };
|
||||||
|
let body = raw;
|
||||||
|
if (match) {
|
||||||
|
const fmBlock = match[1];
|
||||||
|
body = match[2].replace(/^\n+/, '');
|
||||||
|
fmBlock.split('\n').forEach(line => {
|
||||||
|
const idx = line.indexOf(':');
|
||||||
|
if (idx === -1) return;
|
||||||
|
const key = line.slice(0, idx).trim();
|
||||||
|
let val = line.slice(idx + 1).trim();
|
||||||
|
val = val.replace(/^['"]|['"]$/g, '');
|
||||||
|
if (key in fm) fm[key] = val;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return { filename, ...fm, body };
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializePost(entry) {
|
||||||
|
const q = v => `'${String(v).replace(/'/g, "''")}'`;
|
||||||
|
return `---
|
||||||
|
title: ${q(entry.title)}
|
||||||
|
date: ${entry.date}
|
||||||
|
status_class: ${q(entry.status_class)}
|
||||||
|
status_label: ${q(entry.status_label)}
|
||||||
|
thumb: ${q(entry.thumb)}
|
||||||
|
---
|
||||||
|
|
||||||
|
${entry.body.trim()}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readAllPosts() {
|
||||||
|
return fs.readdirSync(POSTS_DIR)
|
||||||
|
.filter(f => f.endsWith('.md'))
|
||||||
|
.map(parsePost)
|
||||||
|
.sort((a, b) => (a.date < b.date ? 1 : -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── HTML ─────────────────────────────────────────────────────────
|
||||||
|
function html(posts) {
|
||||||
|
const statusOptions = STATUS_CLASSES.map(c => `<option value="${c}">${c}</option>`).join('');
|
||||||
|
const rows = posts.map(p => `
|
||||||
|
<tr>
|
||||||
|
<td>${esc(p.title)}</td>
|
||||||
|
<td>${esc(p.date)}</td>
|
||||||
|
<td><span class="status status-${esc(p.status_class)}">${esc(p.status_label)}</span></td>
|
||||||
|
<td class="file-cell"><code>${esc(p.filename)}</code></td>
|
||||||
|
<td class="actions">
|
||||||
|
<button onclick="editRow('${esc(p.filename)}')">✏</button>
|
||||||
|
<button class="del" onclick="delRow('${esc(p.filename)}')">✕</button>
|
||||||
|
</td>
|
||||||
|
</tr>`).join('');
|
||||||
|
|
||||||
|
const json = JSON.stringify(posts).replace(/</g, '\\u003c');
|
||||||
|
|
||||||
|
return `<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>PROJEKT-ARCHIV // ADMIN</title>
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body { background: #0a0a0a; color: #d4af37; font-family: 'Courier New', monospace; padding: 30px 20px; }
|
||||||
|
h1 { color: #ff4500; letter-spacing: 8px; font-size: 1.6rem; margin-bottom: 6px; }
|
||||||
|
p.sub { color: rgba(212,175,55,0.4); font-size: 0.7rem; letter-spacing: 3px; margin-bottom: 30px; }
|
||||||
|
|
||||||
|
.box { background: #111; border: 1px solid rgba(184,115,51,0.3); padding: 24px; margin-bottom: 24px; }
|
||||||
|
.box h2 { font-size: 0.85rem; letter-spacing: 4px; color: #b87333; margin-bottom: 18px; }
|
||||||
|
|
||||||
|
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||||
|
.form-grid .full { grid-column: 1 / -1; }
|
||||||
|
label { display: block; font-size: 0.65rem; letter-spacing: 2px; color: rgba(212,175,55,0.5); margin-bottom: 4px; }
|
||||||
|
input, textarea, select {
|
||||||
|
width: 100%; background: #000; border: 1px solid rgba(184,115,51,0.25);
|
||||||
|
color: #d4af37; font-family: 'Courier New', monospace; font-size: 0.8rem;
|
||||||
|
padding: 8px 10px; outline: none; transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
input:focus, textarea:focus, select:focus { border-color: #d4af37; }
|
||||||
|
textarea { resize: vertical; min-height: 60px; }
|
||||||
|
textarea#f-body { min-height: 220px; }
|
||||||
|
select option { background: #111; }
|
||||||
|
input:disabled { color: rgba(212,175,55,0.4); border-style: dashed; }
|
||||||
|
|
||||||
|
.btn-row { display: flex; gap: 10px; margin-top: 16px; flex-wrap: wrap; }
|
||||||
|
button {
|
||||||
|
background: transparent; border: 1px solid rgba(212,175,55,0.4);
|
||||||
|
color: #d4af37; font-family: 'Courier New', monospace; font-size: 0.75rem;
|
||||||
|
letter-spacing: 2px; padding: 8px 20px; cursor: pointer; transition: all 0.2s;
|
||||||
|
}
|
||||||
|
button:hover { background: rgba(212,175,55,0.1); border-color: #d4af37; }
|
||||||
|
button.primary { border-color: #ff4500; color: #ff4500; }
|
||||||
|
button.primary:hover { background: rgba(255,69,0,0.1); }
|
||||||
|
button.del { border-color: rgba(255,69,0,0.3); color: rgba(255,69,0,0.6); padding: 4px 10px; }
|
||||||
|
button.del:hover { background: rgba(255,69,0,0.1); border-color: #ff4500; color: #ff4500; }
|
||||||
|
button.reset-btn { border-color: rgba(212,175,55,0.2); color: rgba(212,175,55,0.4); }
|
||||||
|
|
||||||
|
.table-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
|
||||||
|
.count { font-size: 0.65rem; color: rgba(212,175,55,0.4); letter-spacing: 2px; }
|
||||||
|
table { width: 100%; border-collapse: collapse; font-size: 0.78rem; }
|
||||||
|
th { text-align: left; color: rgba(212,175,55,0.4); font-size: 0.6rem; letter-spacing: 3px;
|
||||||
|
border-bottom: 1px solid rgba(184,115,51,0.2); padding: 6px 10px; }
|
||||||
|
td { padding: 10px; border-bottom: 1px solid rgba(184,115,51,0.1); vertical-align: top; }
|
||||||
|
td.file-cell code { color: #7ec8a4; font-size: 0.68rem; }
|
||||||
|
tr:hover td { background: rgba(212,175,55,0.03); }
|
||||||
|
td.actions { white-space: nowrap; }
|
||||||
|
td.actions button { padding: 4px 10px; margin-right: 4px; }
|
||||||
|
.status { font-size: 0.6rem; letter-spacing: 2px; padding: 2px 7px; border: 1px solid; white-space: nowrap; }
|
||||||
|
.status-stable { color: #0f0; border-color: #0f0; }
|
||||||
|
.status-corrupted { color: #f00; border-color: #f00; }
|
||||||
|
.status-experimental { color: #888; border-color: #888; }
|
||||||
|
|
||||||
|
.toast { position: fixed; bottom: 30px; right: 30px; background: #111;
|
||||||
|
border: 1px solid #4caf50; color: #4caf50; padding: 10px 20px;
|
||||||
|
font-size: 0.75rem; letter-spacing: 2px; opacity: 0; transition: opacity 0.3s; pointer-events: none; }
|
||||||
|
.toast.err { border-color: #ff4500; color: #ff4500; }
|
||||||
|
.toast.show { opacity: 1; }
|
||||||
|
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
.form-grid { grid-template-columns: 1fr; }
|
||||||
|
table { font-size: 0.68rem; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>PROJEKT-ARCHIV</h1>
|
||||||
|
<p class="sub">// LOKALES ADMIN-PANEL · localhost:${PORT} · schreibt content/posts/*.md</p>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<h2 id="form-title">+ NEUES PROJEKT</h2>
|
||||||
|
<form id="post-form">
|
||||||
|
<input type="hidden" id="edit-filename" value="">
|
||||||
|
<div class="form-grid">
|
||||||
|
<div class="full">
|
||||||
|
<label>TITEL</label>
|
||||||
|
<input type="text" id="f-title" placeholder="z.B. Project: Flux Overdrive" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>DATUM</label>
|
||||||
|
<input type="date" id="f-date" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>STATUS-KLASSE</label>
|
||||||
|
<select id="f-status-class">${statusOptions}</select>
|
||||||
|
</div>
|
||||||
|
<div class="full">
|
||||||
|
<label>STATUS-LABEL (Badge-Text)</label>
|
||||||
|
<input type="text" id="f-status-label" placeholder="z.B. AKTIV" required>
|
||||||
|
</div>
|
||||||
|
<div class="full">
|
||||||
|
<label>THUMBNAIL-PFAD</label>
|
||||||
|
<input type="text" id="f-thumb" placeholder="/img/hero_1.png">
|
||||||
|
</div>
|
||||||
|
<div class="full">
|
||||||
|
<label>DATEINAME (nur bei neuen Projekten, wird sonst nicht geändert)</label>
|
||||||
|
<input type="text" id="f-filename" placeholder="wird automatisch aus dem Titel erzeugt">
|
||||||
|
</div>
|
||||||
|
<div class="full">
|
||||||
|
<label>INHALT (Markdown)</label>
|
||||||
|
<textarea id="f-body" placeholder="### STATUS: ..."></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="btn-row">
|
||||||
|
<button type="submit" class="primary" id="submit-btn">[ SPEICHERN ]</button>
|
||||||
|
<button type="button" class="reset-btn" onclick="resetForm()">[ ABBRECHEN ]</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<div class="table-header">
|
||||||
|
<h2>PROJEKTE</h2>
|
||||||
|
<span class="count" id="count-label">${posts.length} EINTRÄGE</span>
|
||||||
|
</div>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr><th>TITEL</th><th>DATUM</th><th>STATUS</th><th>DATEI</th><th></th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="post-table">${rows}</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toast" id="toast"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let POSTS = ${json};
|
||||||
|
|
||||||
|
function toast(msg, err) {
|
||||||
|
const t = document.getElementById('toast');
|
||||||
|
t.textContent = msg;
|
||||||
|
t.className = 'toast' + (err ? ' err' : '') + ' show';
|
||||||
|
setTimeout(() => t.classList.remove('show'), 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findPost(filename) { return POSTS.find(p => p.filename === filename); }
|
||||||
|
|
||||||
|
function editRow(filename) {
|
||||||
|
const p = findPost(filename);
|
||||||
|
if (!p) return;
|
||||||
|
document.getElementById('edit-filename').value = p.filename;
|
||||||
|
document.getElementById('f-title').value = p.title;
|
||||||
|
document.getElementById('f-date').value = p.date;
|
||||||
|
document.getElementById('f-status-class').value = p.status_class;
|
||||||
|
document.getElementById('f-status-label').value = p.status_label;
|
||||||
|
document.getElementById('f-thumb').value = p.thumb;
|
||||||
|
document.getElementById('f-filename').value = p.filename;
|
||||||
|
document.getElementById('f-filename').disabled = true;
|
||||||
|
document.getElementById('f-body').value = p.body;
|
||||||
|
document.getElementById('form-title').textContent = '✏ PROJEKT BEARBEITEN: ' + p.filename;
|
||||||
|
document.getElementById('submit-btn').textContent = '[ AKTUALISIEREN ]';
|
||||||
|
document.getElementById('post-form').scrollIntoView({ behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
document.getElementById('post-form').reset();
|
||||||
|
document.getElementById('edit-filename').value = '';
|
||||||
|
document.getElementById('f-filename').disabled = false;
|
||||||
|
document.getElementById('form-title').textContent = '+ NEUES PROJEKT';
|
||||||
|
document.getElementById('submit-btn').textContent = '[ SPEICHERN ]';
|
||||||
|
}
|
||||||
|
|
||||||
|
function delRow(filename) {
|
||||||
|
if (!confirm('Projekt "' + filename + '" wirklich löschen?')) return;
|
||||||
|
fetch('/delete', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ filename })
|
||||||
|
}).then(r => r.json()).then(r => {
|
||||||
|
if (r.ok) { toast('✓ GELÖSCHT'); setTimeout(() => location.reload(), 400); }
|
||||||
|
else toast('Fehler: ' + r.error, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('post-form').addEventListener('submit', e => {
|
||||||
|
e.preventDefault();
|
||||||
|
const editFilename = document.getElementById('edit-filename').value;
|
||||||
|
const entry = {
|
||||||
|
filename: editFilename || document.getElementById('f-filename').value.trim(),
|
||||||
|
title: document.getElementById('f-title').value.trim(),
|
||||||
|
date: document.getElementById('f-date').value,
|
||||||
|
status_class: document.getElementById('f-status-class').value,
|
||||||
|
status_label: document.getElementById('f-status-label').value.trim(),
|
||||||
|
thumb: document.getElementById('f-thumb').value.trim(),
|
||||||
|
body: document.getElementById('f-body').value,
|
||||||
|
};
|
||||||
|
fetch('/save', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ isNew: !editFilename, entry })
|
||||||
|
}).then(r => r.json()).then(r => {
|
||||||
|
if (r.ok) { toast('✓ GESPEICHERT'); setTimeout(() => location.reload(), 400); }
|
||||||
|
else toast('Fehler: ' + r.error, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── SERVER ───────────────────────────────────────────────────────
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
const json = (status, obj) => {
|
||||||
|
res.writeHead(status, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify(obj));
|
||||||
|
};
|
||||||
|
const body = (cb) => {
|
||||||
|
let b = '';
|
||||||
|
req.on('data', c => b += c);
|
||||||
|
req.on('end', () => cb(b));
|
||||||
|
};
|
||||||
|
|
||||||
|
if (req.method === 'GET' && req.url === '/') {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||||
|
res.end(html(readAllPosts()));
|
||||||
|
|
||||||
|
} else if (req.method === 'POST' && req.url === '/save') {
|
||||||
|
body(b => {
|
||||||
|
try {
|
||||||
|
const { isNew, entry } = JSON.parse(b);
|
||||||
|
if (!entry.title || !entry.status_label) throw new Error('Titel und Status-Label sind Pflicht');
|
||||||
|
|
||||||
|
let filename = entry.filename;
|
||||||
|
if (isNew) {
|
||||||
|
filename = uniqueSlug(slugify(filename || entry.title)) + '.md';
|
||||||
|
} else if (!filename.endsWith('.md')) {
|
||||||
|
filename += '.md';
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(path.join(POSTS_DIR, filename), serializePost({ ...entry, filename }), 'utf8');
|
||||||
|
json(200, { ok: true, filename });
|
||||||
|
} catch (e) {
|
||||||
|
json(400, { ok: false, error: e.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (req.method === 'POST' && req.url === '/delete') {
|
||||||
|
body(b => {
|
||||||
|
try {
|
||||||
|
const { filename } = JSON.parse(b);
|
||||||
|
const target = path.join(POSTS_DIR, filename);
|
||||||
|
if (!target.startsWith(POSTS_DIR) || !filename.endsWith('.md')) {
|
||||||
|
throw new Error('Ungültiger Dateiname');
|
||||||
|
}
|
||||||
|
fs.unlinkSync(target);
|
||||||
|
json(200, { ok: true });
|
||||||
|
} catch (e) {
|
||||||
|
json(400, { ok: false, error: e.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
res.writeHead(404); res.end('Not found');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(PORT, '127.0.0.1', () => {
|
||||||
|
console.log(`\n PROJEKT-ARCHIV ADMIN → http://localhost:${PORT}\n`);
|
||||||
|
});
|
||||||
BIN
content/.DS_Store
vendored
BIN
content/.DS_Store
vendored
Binary file not shown.
|
|
@ -1,8 +1,8 @@
|
||||||
---
|
---
|
||||||
title: '> top'
|
title: '> topha'
|
||||||
date: 2026-03-18
|
date: 2026-03-18
|
||||||
status_class: 'stable'
|
status_class: 'stable'
|
||||||
status_label: 'PROTOKOLL #002'
|
status_label: 'PROTOKOLL #0099'
|
||||||
thumb: '/img/hp_muti_steampunk_01.png'
|
thumb: '/img/hp_muti_steampunk_01.png'
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,6 @@
|
||||||
data-ascii-shimmer
|
data-ascii-shimmer
|
||||||
data-text="KILABOR"
|
data-text="KILABOR"
|
||||||
data-cell="10"
|
data-cell="10"
|
||||||
data-color="#ff4500"
|
|
||||||
data-dim-color="rgba(212,175,55,0.35)"
|
|
||||||
style="max-width: 900px; margin: 0 auto 10px; width: 100%;"></div>
|
style="max-width: 900px; margin: 0 auto 10px; width: 100%;"></div>
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
|
|
@ -73,13 +71,13 @@
|
||||||
<!-- SCHWARZES TERMINAL / SYSTEM LOG -->
|
<!-- SCHWARZES TERMINAL / SYSTEM LOG -->
|
||||||
<div id="terminal-container" style="margin-top: 20px; max-width: 900px; margin-left: auto; margin-right: auto;">
|
<div id="terminal-container" style="margin-top: 20px; max-width: 900px; margin-left: auto; margin-right: auto;">
|
||||||
<div id="system-log" style="background: #000; border: 1px solid #1a1a1a; padding: 20px; font-family: 'Courier New', monospace; box-shadow: inset 0 0 20px rgba(0,255,65,0.03); overflow: hidden; transition: height 0.4s ease;">
|
<div id="system-log" style="background: #000; border: 1px solid #1a1a1a; padding: 20px; font-family: 'Courier New', monospace; box-shadow: inset 0 0 20px rgba(0,255,65,0.03); overflow: hidden; transition: height 0.4s ease;">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; border-bottom: 1px solid #1a1a1a; padding-bottom: 10px;">
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; border-bottom: 1px solid #444; padding-bottom: 10px;">
|
||||||
<span style="color: #333; font-size: 0.65rem; letter-spacing: 3px;">// SYSTEM_LOG_FEED</span>
|
<span style="color: #4dd0ff; font-weight: bold; font-size: 0.65rem; letter-spacing: 3px;">// SYSTEM_LOG_FEED</span>
|
||||||
<span id="fortune-trigger" style="color: #222; font-size: 0.6rem; letter-spacing: 2px; cursor: pointer; border: 1px solid #1a1a1a; padding: 2px 10px;">[ ORAKEL ]</span>
|
<span id="fortune-trigger" class="oracle-link" style="font-size: 0.6rem; letter-spacing: 2px; cursor: pointer; padding: 2px 10px;">[ ORAKEL ]</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="fortune-text" style="color: #333; font-size: 0.7rem; min-height: 20px; margin-bottom: 15px; font-style: italic;"></div>
|
<div id="fortune-text" style="color: #f00; font-size: 1.1rem; font-weight: bold; min-height: 20px; margin-bottom: 15px; font-style: italic;"></div>
|
||||||
<div id="log-output" style="color: #0f0; font-size: 0.75rem; height: 220px; overflow-y: auto; margin-bottom: 15px; display: flex; flex-direction: column-reverse;"></div>
|
<div id="log-output" style="color: #0f0; font-size: 0.75rem; height: 220px; overflow-y: auto; margin-bottom: 15px; display: flex; flex-direction: column-reverse;"></div>
|
||||||
<div style="display: flex; align-items: center; border-top: 1px solid #1a1a1a; padding-top: 10px;">
|
<div style="display: flex; align-items: center; border-top: 1px solid #444; padding-top: 10px;">
|
||||||
<span style="color: #ff4500; font-size: 0.75rem; margin-right: 8px;">sascha@ki-labor:~$</span>
|
<span style="color: #ff4500; font-size: 0.75rem; margin-right: 8px;">sascha@ki-labor:~$</span>
|
||||||
<input id="terminal-input" type="text" style="flex: 1; background: transparent; border: none; color: #0f0; font-family: 'Courier New', monospace; font-size: 0.75rem; outline: none;" placeholder="befehl eingeben... (help)">
|
<input id="terminal-input" type="text" style="flex: 1; background: transparent; border: none; color: #0f0; font-family: 'Courier New', monospace; font-size: 0.75rem; outline: none;" placeholder="befehl eingeben... (help)">
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
BIN
public/.DS_Store
vendored
BIN
public/.DS_Store
vendored
Binary file not shown.
|
|
@ -26,6 +26,15 @@ body {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Kein Browser-Default fuer Links (sonst Blau/Lila + Unterstrichen, siehe .portfolio-link) */
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: var(--accent-orange);
|
||||||
|
}
|
||||||
|
|
||||||
body.home-page {
|
body.home-page {
|
||||||
background: linear-gradient(rgba(0, 0, 0, 0.9), rgba(0, 0, 0, 0.9)), url('/img/grid.png') repeat;
|
background: linear-gradient(rgba(0, 0, 0, 0.9), rgba(0, 0, 0, 0.9)), url('/img/grid.png') repeat;
|
||||||
}
|
}
|
||||||
|
|
@ -224,6 +233,22 @@ footer {
|
||||||
50% { opacity: 0.3; }
|
50% { opacity: 0.3; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ORAKEL-LINK: rot, fett, pulsierender Rahmen */
|
||||||
|
.oracle-link {
|
||||||
|
color: #f00;
|
||||||
|
font-weight: bold;
|
||||||
|
border: 1px solid #f00;
|
||||||
|
animation: oraclePulse 1.6s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.oracle-link:hover {
|
||||||
|
color: #fff;
|
||||||
|
background: rgba(255, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
@keyframes oraclePulse {
|
||||||
|
0%, 100% { border-color: rgba(255, 0, 0, 0.35); box-shadow: 0 0 0 rgba(255, 0, 0, 0); }
|
||||||
|
50% { border-color: #f00; box-shadow: 0 0 10px rgba(255, 0, 0, 0.7); }
|
||||||
|
}
|
||||||
|
|
||||||
/* --- INTERAKTIVE MODI --- */
|
/* --- INTERAKTIVE MODI --- */
|
||||||
|
|
||||||
/* 1. KI-KERNEL (Matrix Style) */
|
/* 1. KI-KERNEL (Matrix Style) */
|
||||||
|
|
@ -583,6 +608,21 @@ body.mode-ghost .prompt {
|
||||||
.log-entry.ok { color: #0f0; }
|
.log-entry.ok { color: #0f0; }
|
||||||
.log-entry.warn { color: #ff0; }
|
.log-entry.warn { color: #ff0; }
|
||||||
|
|
||||||
|
.log-entry.hack-skull {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 4rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: #f00;
|
||||||
|
text-shadow: 0 0 25px rgba(255, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#system-log.hacked-bg {
|
||||||
|
background: #1a0000 !important;
|
||||||
|
border-color: #f00 !important;
|
||||||
|
transition: background-color 1.4s ease, border-color 1.4s ease, height 0.4s ease;
|
||||||
|
}
|
||||||
|
|
||||||
.terminal-input-line {
|
.terminal-input-line {
|
||||||
display: flex;
|
display: flex;
|
||||||
border-top: 1px solid #111;
|
border-top: 1px solid #111;
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,6 @@
|
||||||
data-ascii-shimmer
|
data-ascii-shimmer
|
||||||
data-text="KILABOR"
|
data-text="KILABOR"
|
||||||
data-cell="10"
|
data-cell="10"
|
||||||
data-color="#ff4500"
|
|
||||||
data-dim-color="rgba(212,175,55,0.35)"
|
|
||||||
style="max-width: 900px; margin: 0 auto 10px; width: 100%;"></div>
|
style="max-width: 900px; margin: 0 auto 10px; width: 100%;"></div>
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
|
|
@ -242,13 +240,13 @@
|
||||||
|
|
||||||
<div id="terminal-container" style="margin-top: 20px; max-width: 900px; margin-left: auto; margin-right: auto;">
|
<div id="terminal-container" style="margin-top: 20px; max-width: 900px; margin-left: auto; margin-right: auto;">
|
||||||
<div id="system-log" style="background: #000; border: 1px solid #1a1a1a; padding: 20px; font-family: 'Courier New', monospace; box-shadow: inset 0 0 20px rgba(0,255,65,0.03); overflow: hidden; transition: height 0.4s ease;">
|
<div id="system-log" style="background: #000; border: 1px solid #1a1a1a; padding: 20px; font-family: 'Courier New', monospace; box-shadow: inset 0 0 20px rgba(0,255,65,0.03); overflow: hidden; transition: height 0.4s ease;">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; border-bottom: 1px solid #1a1a1a; padding-bottom: 10px;">
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; border-bottom: 1px solid #444; padding-bottom: 10px;">
|
||||||
<span style="color: #333; font-size: 0.65rem; letter-spacing: 3px;">// SYSTEM_LOG_FEED</span>
|
<span style="color: #4dd0ff; font-weight: bold; font-size: 0.65rem; letter-spacing: 3px;">// SYSTEM_LOG_FEED</span>
|
||||||
<span id="fortune-trigger" style="color: #222; font-size: 0.6rem; letter-spacing: 2px; cursor: pointer; border: 1px solid #1a1a1a; padding: 2px 10px;">[ ORAKEL ]</span>
|
<span id="fortune-trigger" class="oracle-link" style="font-size: 0.6rem; letter-spacing: 2px; cursor: pointer; padding: 2px 10px;">[ ORAKEL ]</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="fortune-text" style="color: #333; font-size: 0.7rem; min-height: 20px; margin-bottom: 15px; font-style: italic;"></div>
|
<div id="fortune-text" style="color: #f00; font-size: 1.1rem; font-weight: bold; min-height: 20px; margin-bottom: 15px; font-style: italic;"></div>
|
||||||
<div id="log-output" style="color: #0f0; font-size: 0.75rem; height: 220px; overflow-y: auto; margin-bottom: 15px; display: flex; flex-direction: column-reverse;"></div>
|
<div id="log-output" style="color: #0f0; font-size: 0.75rem; height: 220px; overflow-y: auto; margin-bottom: 15px; display: flex; flex-direction: column-reverse;"></div>
|
||||||
<div style="display: flex; align-items: center; border-top: 1px solid #1a1a1a; padding-top: 10px;">
|
<div style="display: flex; align-items: center; border-top: 1px solid #444; padding-top: 10px;">
|
||||||
<span style="color: #ff4500; font-size: 0.75rem; margin-right: 8px;">sascha@ki-labor:~$</span>
|
<span style="color: #ff4500; font-size: 0.75rem; margin-right: 8px;">sascha@ki-labor:~$</span>
|
||||||
<input id="terminal-input" type="text" style="flex: 1; background: transparent; border: none; color: #0f0; font-family: 'Courier New', monospace; font-size: 0.75rem; outline: none;" placeholder="befehl eingeben... (help)">
|
<input id="terminal-input" type="text" style="flex: 1; background: transparent; border: none; color: #0f0; font-family: 'Courier New', monospace; font-size: 0.75rem; outline: none;" placeholder="befehl eingeben... (help)">
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -278,13 +276,13 @@
|
||||||
<a href="/posts/protocol-top/" class="portfolio-link project-card" data-category="stable">
|
<a href="/posts/protocol-top/" class="portfolio-link project-card" data-category="stable">
|
||||||
<div class="portfolio-item">
|
<div class="portfolio-item">
|
||||||
<span class="status-tag stable">
|
<span class="status-tag stable">
|
||||||
PROTOKOLL #002
|
PROTOKOLL #0099
|
||||||
</span>
|
</span>
|
||||||
<div class="project-header-wrap">
|
<div class="project-header-wrap">
|
||||||
|
|
||||||
<img src="/img/hp_muti_steampunk_01.png" class="project-thumb" alt="Preview">
|
<img src="/img/hp_muti_steampunk_01.png" class="project-thumb" alt="Preview">
|
||||||
|
|
||||||
<h3>> top</h3>
|
<h3>> topha</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="item-excerpt">
|
<div class="item-excerpt">
|
||||||
<h3 id="status-prozess-überwachung">STATUS: PROZESS-ÜBERWACHUNG</h3>
|
<h3 id="status-prozess-überwachung">STATUS: PROZESS-ÜBERWACHUNG</h3>
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,12 @@
|
||||||
function initAsciiShimmer(container) {
|
function initAsciiShimmer(container) {
|
||||||
var text = container.dataset.text || 'KILABOR';
|
var text = container.dataset.text || 'KILABOR';
|
||||||
var cell = parseInt(container.dataset.cell || '10', 10);
|
var cell = parseInt(container.dataset.cell || '10', 10);
|
||||||
var primaryColor = container.dataset.color || '#ff4500';
|
|
||||||
var dimColor = container.dataset.dimColor || 'rgba(212,175,55,0.35)';
|
|
||||||
var intervalMs = parseInt(container.dataset.interval || '140', 10);
|
var intervalMs = parseInt(container.dataset.interval || '140', 10);
|
||||||
|
// Farbwelle: pro Buchstabe ein eigener Hue-Versatz, der Startpunkt
|
||||||
|
// wandert über die Zeit weiter -> Farbe laeuft Buchstabe fuer Buchstabe durch.
|
||||||
|
var hueSpeed = parseFloat(container.dataset.hueSpeed || '2.5');
|
||||||
|
var frame = 0;
|
||||||
|
var cols = 0;
|
||||||
|
|
||||||
var denseChars = ['$', '@', '%', '#', '&', '8', '0'];
|
var denseChars = ['$', '@', '%', '#', '&', '8', '0'];
|
||||||
var edgeChars = ['+', 'x', '-', '=', ':', '.', 'o'];
|
var edgeChars = ['+', 'x', '-', '=', ':', '.', 'o'];
|
||||||
|
|
@ -49,8 +52,9 @@
|
||||||
mctx.fillText(text, width / 2, height / 2);
|
mctx.fillText(text, width / 2, height / 2);
|
||||||
|
|
||||||
var imgData = mctx.getImageData(0, 0, width, height).data;
|
var imgData = mctx.getImageData(0, 0, width, height).data;
|
||||||
var cols = Math.floor(width / cell);
|
cols = Math.floor(width / cell);
|
||||||
var rows = Math.floor(height / cell);
|
var rows = Math.floor(height / cell);
|
||||||
|
var colsPerLetter = cols / text.length;
|
||||||
mask = [];
|
mask = [];
|
||||||
|
|
||||||
for (var ry = 0; ry < rows; ry++) {
|
for (var ry = 0; ry < rows; ry++) {
|
||||||
|
|
@ -60,7 +64,8 @@
|
||||||
var idx = (py * width + px) * 4;
|
var idx = (py * width + px) * 4;
|
||||||
var alpha = imgData[idx + 3] / 255;
|
var alpha = imgData[idx + 3] / 255;
|
||||||
if (alpha > 0.15) {
|
if (alpha > 0.15) {
|
||||||
mask.push({ x: rx, y: ry, alpha: alpha });
|
var letterIndex = Math.min(text.length - 1, Math.floor(rx / colsPerLetter));
|
||||||
|
mask.push({ x: rx, y: ry, alpha: alpha, letter: letterIndex });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -72,12 +77,19 @@
|
||||||
ctx.textBaseline = 'middle';
|
ctx.textBaseline = 'middle';
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
|
|
||||||
|
frame++;
|
||||||
|
var hueBase = (frame * hueSpeed) % 360;
|
||||||
|
var hueStep = 360 / text.length;
|
||||||
|
|
||||||
for (var i = 0; i < mask.length; i++) {
|
for (var i = 0; i < mask.length; i++) {
|
||||||
var c = mask[i];
|
var c = mask[i];
|
||||||
var isDense = c.alpha > 0.7;
|
var isDense = c.alpha > 0.7;
|
||||||
var set = isDense ? denseChars : edgeChars;
|
var set = isDense ? denseChars : edgeChars;
|
||||||
var char = set[(Math.random() * set.length) | 0];
|
var char = set[(Math.random() * set.length) | 0];
|
||||||
ctx.fillStyle = isDense ? primaryColor : dimColor;
|
var hue = (hueBase + c.letter * hueStep) % 360;
|
||||||
|
ctx.fillStyle = isDense
|
||||||
|
? 'hsl(' + hue + ', 90%, 55%)'
|
||||||
|
: 'hsla(' + hue + ', 90%, 55%, 0.4)';
|
||||||
ctx.fillText(char, c.x * cell + cell / 2, c.y * cell + cell / 2);
|
ctx.fillText(char, c.x * cell + cell / 2, c.y * cell + cell / 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -155,12 +155,19 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function randHex() {
|
||||||
|
return Math.random().toString(16).substring(2, 15);
|
||||||
|
}
|
||||||
|
|
||||||
function log(msg, cls = '') {
|
function log(msg, cls = '') {
|
||||||
if (!logOutput) return;
|
if (!logOutput) return;
|
||||||
const entry = document.createElement('div');
|
const entry = document.createElement('div');
|
||||||
entry.className = 'log-entry ' + cls;
|
entry.className = 'log-entry ' + cls;
|
||||||
entry.innerHTML = msg;
|
entry.innerHTML = msg;
|
||||||
logOutput.prepend(entry);
|
logOutput.prepend(entry);
|
||||||
|
// Neuesten Eintrag immer sichtbar halten (sonst bleibt die Ansicht
|
||||||
|
// nach schnellen Log-Bursts wie "hack" an alter Scroll-Position stehen).
|
||||||
|
logOutput.scrollTop = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCommand(raw) {
|
function handleCommand(raw) {
|
||||||
|
|
@ -168,6 +175,16 @@
|
||||||
const cmd = parts[0].toLowerCase();
|
const cmd = parts[0].toLowerCase();
|
||||||
const args = parts.slice(1).join(' ');
|
const args = parts.slice(1).join(' ');
|
||||||
|
|
||||||
|
// Nach einem "hack" hinterlassenes Rot/vergroessertes Terminal bei
|
||||||
|
// jedem neuen Befehl (ausser erneutem hack) wieder normalisieren.
|
||||||
|
if (cmd !== 'hack') {
|
||||||
|
const normalTerminal = document.getElementById('system-log');
|
||||||
|
if (normalTerminal && normalTerminal.classList.contains('hacked-bg')) {
|
||||||
|
normalTerminal.classList.remove('hacked-bg');
|
||||||
|
normalTerminal.style.height = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log('<span class="user-cmd">sascha@ki-labor:~$ ' + raw + '</span>');
|
log('<span class="user-cmd">sascha@ki-labor:~$ ' + raw + '</span>');
|
||||||
|
|
||||||
switch(cmd) {
|
switch(cmd) {
|
||||||
|
|
@ -199,15 +216,57 @@
|
||||||
case 'trace': document.querySelector('[data-mode="trace"]')?.click(); break;
|
case 'trace': document.querySelector('[data-mode="trace"]')?.click(); break;
|
||||||
case 'flux': document.querySelector('[data-mode="overdrive"]')?.click(); break;
|
case 'flux': document.querySelector('[data-mode="overdrive"]')?.click(); break;
|
||||||
case 'hack':
|
case 'hack':
|
||||||
log('>> INITIATING NEURAL HACK...', 'warn');
|
const hackTargets = ['NASA.GOV', 'PENTAGON.MIL', 'AREA51.GOV', 'CIA.GOV', 'FBI.GOV'];
|
||||||
let hIdx = 0;
|
const hackTarget = hackTargets[Math.floor(Math.random() * hackTargets.length)];
|
||||||
const hInt = setInterval(() => {
|
const hackIp = Array.from({ length: 4 }, () => Math.floor(Math.random() * 256)).join('.');
|
||||||
log(Math.random().toString(16).substring(2, 15), 'ghost');
|
const hackFiles = ['AREA51_BBQ_REZEPTE.pdf', 'MONDLANDUNG_MASTERTAPES.mp4', 'ALIEN_KONTAKT_CHATVERLAUF.txt', 'ROSWELL_URLAUBSFOTOS.zip', 'GEHEIME_KAFFEEKASSE.xlsx', 'ECHTE_GEBURTSTAGSPARTY_PLANUNG.docx', 'BEREICH_51_WLAN_PASSWORT.txt'].sort(() => Math.random() - 0.5).slice(0, 3);
|
||||||
if (hIdx++ > 15) {
|
const hackScript = [
|
||||||
clearInterval(hInt);
|
{ t: '>> INITIATING NEURAL HACK...', c: 'warn', d: 0 },
|
||||||
log('>> ACCESS GRANTED.', 'ok');
|
{ t: '>> ZIEL ANVISIERT: ' + hackTarget + ' [' + hackIp + ']', c: 'info', d: 650 },
|
||||||
|
{ t: randHex(), c: 'ghost', d: 320 },
|
||||||
|
{ t: randHex(), c: 'ghost', d: 160 },
|
||||||
|
{ t: randHex(), c: 'ghost', d: 160 },
|
||||||
|
{ t: '>> FIREWALL WIRD UMGANGEN... [FEHLGESCHLAGEN]', c: 'error', d: 800 },
|
||||||
|
{ t: '>> WECHSLE ZU BRUTE-FORCE-MODUL...', c: 'warn', d: 650 },
|
||||||
|
{ t: randHex(), c: 'ghost', d: 240 },
|
||||||
|
{ t: randHex(), c: 'ghost', d: 160 },
|
||||||
|
{ t: randHex(), c: 'ghost', d: 160 },
|
||||||
|
{ t: randHex(), c: 'ghost', d: 160 },
|
||||||
|
{ t: '>> FIREWALL UMGANGEN. (Standard-Passwort war "password123".)', c: 'ok', d: 800 },
|
||||||
|
{ t: '>> INJIZIERE PAYLOAD IN MAINFRAME...', c: 'info', d: 650 },
|
||||||
|
{ t: '[■□□□□□□□□□] 10%', c: '', d: 240 },
|
||||||
|
{ t: '[■■■□□□□□□□] 30%', c: '', d: 240 },
|
||||||
|
{ t: '[■■■■■□□□□□] 50%', c: '', d: 240 },
|
||||||
|
{ t: '[■■■■■■■□□□] 70%', c: '', d: 240 },
|
||||||
|
{ t: '[■■■■■■■■■□] 90%', c: '', d: 240 },
|
||||||
|
{ t: '[■■■■■■■■■■] 100%', c: 'ok', d: 320 },
|
||||||
|
{ t: '>> LADE GEHEIME DATEIEN HERUNTER...', c: 'warn', d: 650 },
|
||||||
|
{ t: ' ↳ ' + hackFiles[0], c: 'ghost', d: 320 },
|
||||||
|
{ t: ' ↳ ' + hackFiles[1], c: 'ghost', d: 320 },
|
||||||
|
{ t: ' ↳ ' + hackFiles[2], c: 'ghost', d: 320 },
|
||||||
|
{ t: '>> SPUREN WERDEN VERWISCHT...', c: 'info', d: 650 },
|
||||||
|
{ t: '>> ACCESS GRANTED. WILLKOMMEN, OVERLORD.', c: 'crit', d: 800 },
|
||||||
|
{ t: '>> (Alles nur Fiktion. Keine echten Server wurden gehackt. Wahrscheinlich.)', c: '', d: 650 }
|
||||||
|
];
|
||||||
|
|
||||||
|
const hackTerminal = document.getElementById('system-log');
|
||||||
|
if (hackTerminal) {
|
||||||
|
hackTerminal.classList.remove('hacked-bg');
|
||||||
|
hackTerminal.style.height = (hackTerminal.offsetHeight + 100) + 'px';
|
||||||
}
|
}
|
||||||
}, 100);
|
|
||||||
|
let hackCum = 0;
|
||||||
|
hackScript.forEach(step => {
|
||||||
|
hackCum += step.d;
|
||||||
|
setTimeout(() => log(step.t, step.c), hackCum);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Nach der Sequenz: Terminal leeren, Totenkopf zeigen, Hintergrund rot einfaerben
|
||||||
|
setTimeout(() => {
|
||||||
|
if (logOutput) logOutput.innerHTML = '';
|
||||||
|
log('☠', 'hack-skull');
|
||||||
|
if (hackTerminal) hackTerminal.classList.add('hacked-bg');
|
||||||
|
}, hackCum + 700);
|
||||||
break;
|
break;
|
||||||
case 'deep-scan':
|
case 'deep-scan':
|
||||||
log('>> INITIALIZING DEEP SYSTEM SCAN...', 'info');
|
log('>> INITIALIZING DEEP SYSTEM SCAN...', 'info');
|
||||||
|
|
@ -223,6 +282,22 @@
|
||||||
log('SYSTEM ONLINE SEIT: 01.01.1974', 'info');
|
log('SYSTEM ONLINE SEIT: 01.01.1974', 'info');
|
||||||
log('LAUFZEIT: ' + years + ' JAHRE (STABILITÄT: FRAGLICH)', 'ok');
|
log('LAUFZEIT: ' + years + ' JAHRE (STABILITÄT: FRAGLICH)', 'ok');
|
||||||
break;
|
break;
|
||||||
|
case 'top':
|
||||||
|
log('PROZESS-MONITOR:', 'info');
|
||||||
|
log('PID CPU% MEM% PROZESS');
|
||||||
|
log('1337 ' + (60 + Math.floor(Math.random() * 40)) + '% ' + (30 + Math.floor(Math.random() * 50)) + '% ANARCHIE.exe', 'warn');
|
||||||
|
log('0001 ' + (5 + Math.floor(Math.random() * 20)) + '% ' + (10 + Math.floor(Math.random() * 30)) + '% KAFFEE_DAEMON');
|
||||||
|
log('4269 ' + (1 + Math.floor(Math.random() * 10)) + '% ' + (5 + Math.floor(Math.random() * 15)) + '% PROKRASTINATION.sys');
|
||||||
|
log('0666 ' + (80 + Math.floor(Math.random() * 20)) + '% ' + (70 + Math.floor(Math.random() * 30)) + '% GEMINI_NEURAL_LINK', 'error');
|
||||||
|
log('9999 0% 0% SCHLAF.exe (SEIT TAGEN INAKTIV)');
|
||||||
|
break;
|
||||||
|
case 'weather':
|
||||||
|
const conditions = ['CHAOS mit Aussicht auf mehr CHAOS', 'Vereinzelte Panik-Schauer', 'Digitaler Nebel, Sicht: 0m', 'Sonnig, aber die Sonne ist auch kaputt', 'Sturmwarnung: MERGE CONFLICTS'];
|
||||||
|
const temp = -10 + Math.floor(Math.random() * 60);
|
||||||
|
log('CHAOS-WETTERBERICHT:', 'info');
|
||||||
|
log('TEMPERATUR: ' + temp + '°C (GEFÜHLT: SCHLIMMER)');
|
||||||
|
log('BEDINGUNG: ' + conditions[Math.floor(Math.random() * conditions.length)], 'warn');
|
||||||
|
break;
|
||||||
case 'fortune': generateFortune(); break;
|
case 'fortune': generateFortune(); break;
|
||||||
default:
|
default:
|
||||||
log('FEHLER: BEFEHL "' + cmd + '" NICHT GEFUNDEN.', 'error');
|
log('FEHLER: BEFEHL "' + cmd + '" NICHT GEFUNDEN.', 'error');
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<head><script src="/livereload.js?mindelay=10&v=2&port=1414&path=livereload" data-no-instant defer></script>
|
<head><script src="/livereload.js?mindelay=10&v=2&port=1414&path=livereload" data-no-instant defer></script>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>> top | DIGITALE ANARCHIE | KI-LABOR</title>
|
<title>> topha | DIGITALE ANARCHIE | KI-LABOR</title>
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
<link rel="stylesheet" href="/css/style.css">
|
||||||
</head>
|
</head>
|
||||||
<body class=" ">
|
<body class=" ">
|
||||||
|
|
@ -189,7 +189,7 @@
|
||||||
<p class="person-pre-label" style="margin:12px 0 4px;">// ZENTRAL-ARCHIV · EINHEIT IDENTIFIZIERT //</p>
|
<p class="person-pre-label" style="margin:12px 0 4px;">// ZENTRAL-ARCHIV · EINHEIT IDENTIFIZIERT //</p>
|
||||||
<div class="person-title-wrap">
|
<div class="person-title-wrap">
|
||||||
<span class="person-title-deco">⟪</span>
|
<span class="person-title-deco">⟪</span>
|
||||||
<h1 class="person-glitch-title" data-text="> TOP">> TOP</h1>
|
<h1 class="person-glitch-title" data-text="> TOPHA">> TOPHA</h1>
|
||||||
<span class="person-title-deco">⟫</span>
|
<span class="person-title-deco">⟫</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="person-sub-label">STATUS: <span class="person-status-blink">AKTIV</span></p>
|
<p class="person-sub-label">STATUS: <span class="person-status-blink">AKTIV</span></p>
|
||||||
|
|
@ -240,7 +240,7 @@
|
||||||
<span class="akte-label-stamp">VERTRAULICH</span>
|
<span class="akte-label-stamp">VERTRAULICH</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="akte-label-divider"></div>
|
<div class="akte-label-divider"></div>
|
||||||
<div class="akte-label-title">AKTE: > TOP</div>
|
<div class="akte-label-title">AKTE: > TOPHA</div>
|
||||||
<div class="akte-label-divider"></div>
|
<div class="akte-label-divider"></div>
|
||||||
<div class="akte-label-bottom">
|
<div class="akte-label-bottom">
|
||||||
<span>STATUS: KLASSIFIZIERT</span>
|
<span>STATUS: KLASSIFIZIERT</span>
|
||||||
|
|
@ -365,7 +365,7 @@
|
||||||
|
|
||||||
<div style="flex: 1;">
|
<div style="flex: 1;">
|
||||||
<div style="font-size: 0.5rem !important; color: #5d4037; letter-spacing: 2px; margin-bottom: 2px;">PROTOKOLL NR. 1974-Ø</div>
|
<div style="font-size: 0.5rem !important; color: #5d4037; letter-spacing: 2px; margin-bottom: 2px;">PROTOKOLL NR. 1974-Ø</div>
|
||||||
<h1 style="font-weight: bold;">> TOP</h1>
|
<h1 style="font-weight: bold;">> TOPHA</h1>
|
||||||
<p style="color: #5d4037; font-size: 0.7rem !important; margin: 5px 0 10px;">ALIAS: </p>
|
<p style="color: #5d4037; font-size: 0.7rem !important; margin: 5px 0 10px;">ALIAS: </p>
|
||||||
|
|
||||||
<div style="display: flex; gap: 10px;">
|
<div style="display: flex; gap: 10px;">
|
||||||
|
|
@ -382,7 +382,7 @@
|
||||||
<div class="data-row" style="display: flex; align-items: center;">
|
<div class="data-row" style="display: flex; align-items: center;">
|
||||||
<div class="label" style="width: 160px; letter-spacing: 1px;">DESIGNATION:</div>
|
<div class="label" style="width: 160px; letter-spacing: 1px;">DESIGNATION:</div>
|
||||||
<div style="flex: 1; font-weight: normal;">
|
<div style="flex: 1; font-weight: normal;">
|
||||||
> top
|
> topha
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -723,7 +723,7 @@
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
const key = 'decl_top';
|
const key = 'decl_topha';
|
||||||
const overlay = document.getElementById('declassified-overlay');
|
const overlay = document.getElementById('declassified-overlay');
|
||||||
if (!overlay) return;
|
if (!overlay) return;
|
||||||
if (sessionStorage.getItem(key)) {
|
if (sessionStorage.getItem(key)) {
|
||||||
|
|
|
||||||
BIN
static/.DS_Store
vendored
BIN
static/.DS_Store
vendored
Binary file not shown.
|
|
@ -26,6 +26,15 @@ body {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Kein Browser-Default fuer Links (sonst Blau/Lila + Unterstrichen, siehe .portfolio-link) */
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: var(--accent-orange);
|
||||||
|
}
|
||||||
|
|
||||||
body.home-page {
|
body.home-page {
|
||||||
background: linear-gradient(rgba(0, 0, 0, 0.9), rgba(0, 0, 0, 0.9)), url('/img/grid.png') repeat;
|
background: linear-gradient(rgba(0, 0, 0, 0.9), rgba(0, 0, 0, 0.9)), url('/img/grid.png') repeat;
|
||||||
}
|
}
|
||||||
|
|
@ -224,6 +233,22 @@ footer {
|
||||||
50% { opacity: 0.3; }
|
50% { opacity: 0.3; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ORAKEL-LINK: rot, fett, pulsierender Rahmen */
|
||||||
|
.oracle-link {
|
||||||
|
color: #f00;
|
||||||
|
font-weight: bold;
|
||||||
|
border: 1px solid #f00;
|
||||||
|
animation: oraclePulse 1.6s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.oracle-link:hover {
|
||||||
|
color: #fff;
|
||||||
|
background: rgba(255, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
@keyframes oraclePulse {
|
||||||
|
0%, 100% { border-color: rgba(255, 0, 0, 0.35); box-shadow: 0 0 0 rgba(255, 0, 0, 0); }
|
||||||
|
50% { border-color: #f00; box-shadow: 0 0 10px rgba(255, 0, 0, 0.7); }
|
||||||
|
}
|
||||||
|
|
||||||
/* --- INTERAKTIVE MODI --- */
|
/* --- INTERAKTIVE MODI --- */
|
||||||
|
|
||||||
/* 1. KI-KERNEL (Matrix Style) */
|
/* 1. KI-KERNEL (Matrix Style) */
|
||||||
|
|
@ -583,6 +608,21 @@ body.mode-ghost .prompt {
|
||||||
.log-entry.ok { color: #0f0; }
|
.log-entry.ok { color: #0f0; }
|
||||||
.log-entry.warn { color: #ff0; }
|
.log-entry.warn { color: #ff0; }
|
||||||
|
|
||||||
|
.log-entry.hack-skull {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 4rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: #f00;
|
||||||
|
text-shadow: 0 0 25px rgba(255, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#system-log.hacked-bg {
|
||||||
|
background: #1a0000 !important;
|
||||||
|
border-color: #f00 !important;
|
||||||
|
transition: background-color 1.4s ease, border-color 1.4s ease, height 0.4s ease;
|
||||||
|
}
|
||||||
|
|
||||||
.terminal-input-line {
|
.terminal-input-line {
|
||||||
display: flex;
|
display: flex;
|
||||||
border-top: 1px solid #111;
|
border-top: 1px solid #111;
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,12 @@
|
||||||
function initAsciiShimmer(container) {
|
function initAsciiShimmer(container) {
|
||||||
var text = container.dataset.text || 'KILABOR';
|
var text = container.dataset.text || 'KILABOR';
|
||||||
var cell = parseInt(container.dataset.cell || '10', 10);
|
var cell = parseInt(container.dataset.cell || '10', 10);
|
||||||
var primaryColor = container.dataset.color || '#ff4500';
|
|
||||||
var dimColor = container.dataset.dimColor || 'rgba(212,175,55,0.35)';
|
|
||||||
var intervalMs = parseInt(container.dataset.interval || '140', 10);
|
var intervalMs = parseInt(container.dataset.interval || '140', 10);
|
||||||
|
// Farbwelle: pro Buchstabe ein eigener Hue-Versatz, der Startpunkt
|
||||||
|
// wandert über die Zeit weiter -> Farbe laeuft Buchstabe fuer Buchstabe durch.
|
||||||
|
var hueSpeed = parseFloat(container.dataset.hueSpeed || '2.5');
|
||||||
|
var frame = 0;
|
||||||
|
var cols = 0;
|
||||||
|
|
||||||
var denseChars = ['$', '@', '%', '#', '&', '8', '0'];
|
var denseChars = ['$', '@', '%', '#', '&', '8', '0'];
|
||||||
var edgeChars = ['+', 'x', '-', '=', ':', '.', 'o'];
|
var edgeChars = ['+', 'x', '-', '=', ':', '.', 'o'];
|
||||||
|
|
@ -49,8 +52,9 @@
|
||||||
mctx.fillText(text, width / 2, height / 2);
|
mctx.fillText(text, width / 2, height / 2);
|
||||||
|
|
||||||
var imgData = mctx.getImageData(0, 0, width, height).data;
|
var imgData = mctx.getImageData(0, 0, width, height).data;
|
||||||
var cols = Math.floor(width / cell);
|
cols = Math.floor(width / cell);
|
||||||
var rows = Math.floor(height / cell);
|
var rows = Math.floor(height / cell);
|
||||||
|
var colsPerLetter = cols / text.length;
|
||||||
mask = [];
|
mask = [];
|
||||||
|
|
||||||
for (var ry = 0; ry < rows; ry++) {
|
for (var ry = 0; ry < rows; ry++) {
|
||||||
|
|
@ -60,7 +64,8 @@
|
||||||
var idx = (py * width + px) * 4;
|
var idx = (py * width + px) * 4;
|
||||||
var alpha = imgData[idx + 3] / 255;
|
var alpha = imgData[idx + 3] / 255;
|
||||||
if (alpha > 0.15) {
|
if (alpha > 0.15) {
|
||||||
mask.push({ x: rx, y: ry, alpha: alpha });
|
var letterIndex = Math.min(text.length - 1, Math.floor(rx / colsPerLetter));
|
||||||
|
mask.push({ x: rx, y: ry, alpha: alpha, letter: letterIndex });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -72,12 +77,19 @@
|
||||||
ctx.textBaseline = 'middle';
|
ctx.textBaseline = 'middle';
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
|
|
||||||
|
frame++;
|
||||||
|
var hueBase = (frame * hueSpeed) % 360;
|
||||||
|
var hueStep = 360 / text.length;
|
||||||
|
|
||||||
for (var i = 0; i < mask.length; i++) {
|
for (var i = 0; i < mask.length; i++) {
|
||||||
var c = mask[i];
|
var c = mask[i];
|
||||||
var isDense = c.alpha > 0.7;
|
var isDense = c.alpha > 0.7;
|
||||||
var set = isDense ? denseChars : edgeChars;
|
var set = isDense ? denseChars : edgeChars;
|
||||||
var char = set[(Math.random() * set.length) | 0];
|
var char = set[(Math.random() * set.length) | 0];
|
||||||
ctx.fillStyle = isDense ? primaryColor : dimColor;
|
var hue = (hueBase + c.letter * hueStep) % 360;
|
||||||
|
ctx.fillStyle = isDense
|
||||||
|
? 'hsl(' + hue + ', 90%, 55%)'
|
||||||
|
: 'hsla(' + hue + ', 90%, 55%, 0.4)';
|
||||||
ctx.fillText(char, c.x * cell + cell / 2, c.y * cell + cell / 2);
|
ctx.fillText(char, c.x * cell + cell / 2, c.y * cell + cell / 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -155,12 +155,19 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function randHex() {
|
||||||
|
return Math.random().toString(16).substring(2, 15);
|
||||||
|
}
|
||||||
|
|
||||||
function log(msg, cls = '') {
|
function log(msg, cls = '') {
|
||||||
if (!logOutput) return;
|
if (!logOutput) return;
|
||||||
const entry = document.createElement('div');
|
const entry = document.createElement('div');
|
||||||
entry.className = 'log-entry ' + cls;
|
entry.className = 'log-entry ' + cls;
|
||||||
entry.innerHTML = msg;
|
entry.innerHTML = msg;
|
||||||
logOutput.prepend(entry);
|
logOutput.prepend(entry);
|
||||||
|
// Neuesten Eintrag immer sichtbar halten (sonst bleibt die Ansicht
|
||||||
|
// nach schnellen Log-Bursts wie "hack" an alter Scroll-Position stehen).
|
||||||
|
logOutput.scrollTop = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCommand(raw) {
|
function handleCommand(raw) {
|
||||||
|
|
@ -168,6 +175,16 @@
|
||||||
const cmd = parts[0].toLowerCase();
|
const cmd = parts[0].toLowerCase();
|
||||||
const args = parts.slice(1).join(' ');
|
const args = parts.slice(1).join(' ');
|
||||||
|
|
||||||
|
// Nach einem "hack" hinterlassenes Rot/vergroessertes Terminal bei
|
||||||
|
// jedem neuen Befehl (ausser erneutem hack) wieder normalisieren.
|
||||||
|
if (cmd !== 'hack') {
|
||||||
|
const normalTerminal = document.getElementById('system-log');
|
||||||
|
if (normalTerminal && normalTerminal.classList.contains('hacked-bg')) {
|
||||||
|
normalTerminal.classList.remove('hacked-bg');
|
||||||
|
normalTerminal.style.height = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log('<span class="user-cmd">sascha@ki-labor:~$ ' + raw + '</span>');
|
log('<span class="user-cmd">sascha@ki-labor:~$ ' + raw + '</span>');
|
||||||
|
|
||||||
switch(cmd) {
|
switch(cmd) {
|
||||||
|
|
@ -199,15 +216,57 @@
|
||||||
case 'trace': document.querySelector('[data-mode="trace"]')?.click(); break;
|
case 'trace': document.querySelector('[data-mode="trace"]')?.click(); break;
|
||||||
case 'flux': document.querySelector('[data-mode="overdrive"]')?.click(); break;
|
case 'flux': document.querySelector('[data-mode="overdrive"]')?.click(); break;
|
||||||
case 'hack':
|
case 'hack':
|
||||||
log('>> INITIATING NEURAL HACK...', 'warn');
|
const hackTargets = ['NASA.GOV', 'PENTAGON.MIL', 'AREA51.GOV', 'CIA.GOV', 'FBI.GOV'];
|
||||||
let hIdx = 0;
|
const hackTarget = hackTargets[Math.floor(Math.random() * hackTargets.length)];
|
||||||
const hInt = setInterval(() => {
|
const hackIp = Array.from({ length: 4 }, () => Math.floor(Math.random() * 256)).join('.');
|
||||||
log(Math.random().toString(16).substring(2, 15), 'ghost');
|
const hackFiles = ['AREA51_BBQ_REZEPTE.pdf', 'MONDLANDUNG_MASTERTAPES.mp4', 'ALIEN_KONTAKT_CHATVERLAUF.txt', 'ROSWELL_URLAUBSFOTOS.zip', 'GEHEIME_KAFFEEKASSE.xlsx', 'ECHTE_GEBURTSTAGSPARTY_PLANUNG.docx', 'BEREICH_51_WLAN_PASSWORT.txt'].sort(() => Math.random() - 0.5).slice(0, 3);
|
||||||
if (hIdx++ > 15) {
|
const hackScript = [
|
||||||
clearInterval(hInt);
|
{ t: '>> INITIATING NEURAL HACK...', c: 'warn', d: 0 },
|
||||||
log('>> ACCESS GRANTED.', 'ok');
|
{ t: '>> ZIEL ANVISIERT: ' + hackTarget + ' [' + hackIp + ']', c: 'info', d: 650 },
|
||||||
|
{ t: randHex(), c: 'ghost', d: 320 },
|
||||||
|
{ t: randHex(), c: 'ghost', d: 160 },
|
||||||
|
{ t: randHex(), c: 'ghost', d: 160 },
|
||||||
|
{ t: '>> FIREWALL WIRD UMGANGEN... [FEHLGESCHLAGEN]', c: 'error', d: 800 },
|
||||||
|
{ t: '>> WECHSLE ZU BRUTE-FORCE-MODUL...', c: 'warn', d: 650 },
|
||||||
|
{ t: randHex(), c: 'ghost', d: 240 },
|
||||||
|
{ t: randHex(), c: 'ghost', d: 160 },
|
||||||
|
{ t: randHex(), c: 'ghost', d: 160 },
|
||||||
|
{ t: randHex(), c: 'ghost', d: 160 },
|
||||||
|
{ t: '>> FIREWALL UMGANGEN. (Standard-Passwort war "password123".)', c: 'ok', d: 800 },
|
||||||
|
{ t: '>> INJIZIERE PAYLOAD IN MAINFRAME...', c: 'info', d: 650 },
|
||||||
|
{ t: '[■□□□□□□□□□] 10%', c: '', d: 240 },
|
||||||
|
{ t: '[■■■□□□□□□□] 30%', c: '', d: 240 },
|
||||||
|
{ t: '[■■■■■□□□□□] 50%', c: '', d: 240 },
|
||||||
|
{ t: '[■■■■■■■□□□] 70%', c: '', d: 240 },
|
||||||
|
{ t: '[■■■■■■■■■□] 90%', c: '', d: 240 },
|
||||||
|
{ t: '[■■■■■■■■■■] 100%', c: 'ok', d: 320 },
|
||||||
|
{ t: '>> LADE GEHEIME DATEIEN HERUNTER...', c: 'warn', d: 650 },
|
||||||
|
{ t: ' ↳ ' + hackFiles[0], c: 'ghost', d: 320 },
|
||||||
|
{ t: ' ↳ ' + hackFiles[1], c: 'ghost', d: 320 },
|
||||||
|
{ t: ' ↳ ' + hackFiles[2], c: 'ghost', d: 320 },
|
||||||
|
{ t: '>> SPUREN WERDEN VERWISCHT...', c: 'info', d: 650 },
|
||||||
|
{ t: '>> ACCESS GRANTED. WILLKOMMEN, OVERLORD.', c: 'crit', d: 800 },
|
||||||
|
{ t: '>> (Alles nur Fiktion. Keine echten Server wurden gehackt. Wahrscheinlich.)', c: '', d: 650 }
|
||||||
|
];
|
||||||
|
|
||||||
|
const hackTerminal = document.getElementById('system-log');
|
||||||
|
if (hackTerminal) {
|
||||||
|
hackTerminal.classList.remove('hacked-bg');
|
||||||
|
hackTerminal.style.height = (hackTerminal.offsetHeight + 100) + 'px';
|
||||||
}
|
}
|
||||||
}, 100);
|
|
||||||
|
let hackCum = 0;
|
||||||
|
hackScript.forEach(step => {
|
||||||
|
hackCum += step.d;
|
||||||
|
setTimeout(() => log(step.t, step.c), hackCum);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Nach der Sequenz: Terminal leeren, Totenkopf zeigen, Hintergrund rot einfaerben
|
||||||
|
setTimeout(() => {
|
||||||
|
if (logOutput) logOutput.innerHTML = '';
|
||||||
|
log('☠', 'hack-skull');
|
||||||
|
if (hackTerminal) hackTerminal.classList.add('hacked-bg');
|
||||||
|
}, hackCum + 700);
|
||||||
break;
|
break;
|
||||||
case 'deep-scan':
|
case 'deep-scan':
|
||||||
log('>> INITIALIZING DEEP SYSTEM SCAN...', 'info');
|
log('>> INITIALIZING DEEP SYSTEM SCAN...', 'info');
|
||||||
|
|
@ -223,6 +282,22 @@
|
||||||
log('SYSTEM ONLINE SEIT: 01.01.1974', 'info');
|
log('SYSTEM ONLINE SEIT: 01.01.1974', 'info');
|
||||||
log('LAUFZEIT: ' + years + ' JAHRE (STABILITÄT: FRAGLICH)', 'ok');
|
log('LAUFZEIT: ' + years + ' JAHRE (STABILITÄT: FRAGLICH)', 'ok');
|
||||||
break;
|
break;
|
||||||
|
case 'top':
|
||||||
|
log('PROZESS-MONITOR:', 'info');
|
||||||
|
log('PID CPU% MEM% PROZESS');
|
||||||
|
log('1337 ' + (60 + Math.floor(Math.random() * 40)) + '% ' + (30 + Math.floor(Math.random() * 50)) + '% ANARCHIE.exe', 'warn');
|
||||||
|
log('0001 ' + (5 + Math.floor(Math.random() * 20)) + '% ' + (10 + Math.floor(Math.random() * 30)) + '% KAFFEE_DAEMON');
|
||||||
|
log('4269 ' + (1 + Math.floor(Math.random() * 10)) + '% ' + (5 + Math.floor(Math.random() * 15)) + '% PROKRASTINATION.sys');
|
||||||
|
log('0666 ' + (80 + Math.floor(Math.random() * 20)) + '% ' + (70 + Math.floor(Math.random() * 30)) + '% GEMINI_NEURAL_LINK', 'error');
|
||||||
|
log('9999 0% 0% SCHLAF.exe (SEIT TAGEN INAKTIV)');
|
||||||
|
break;
|
||||||
|
case 'weather':
|
||||||
|
const conditions = ['CHAOS mit Aussicht auf mehr CHAOS', 'Vereinzelte Panik-Schauer', 'Digitaler Nebel, Sicht: 0m', 'Sonnig, aber die Sonne ist auch kaputt', 'Sturmwarnung: MERGE CONFLICTS'];
|
||||||
|
const temp = -10 + Math.floor(Math.random() * 60);
|
||||||
|
log('CHAOS-WETTERBERICHT:', 'info');
|
||||||
|
log('TEMPERATUR: ' + temp + '°C (GEFÜHLT: SCHLIMMER)');
|
||||||
|
log('BEDINGUNG: ' + conditions[Math.floor(Math.random() * conditions.length)], 'warn');
|
||||||
|
break;
|
||||||
case 'fortune': generateFortune(); break;
|
case 'fortune': generateFortune(); break;
|
||||||
default:
|
default:
|
||||||
log('FEHLER: BEFEHL "' + cmd + '" NICHT GEFUNDEN.', 'error');
|
log('FEHLER: BEFEHL "' + cmd + '" NICHT GEFUNDEN.', 'error');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue