Auto-backup: 2026-09-04 00:33

This commit is contained in:
superschnups 2026-09-04 00:33:04 +02:00
parent f14c82dc42
commit d93aca923b
14 changed files with 656 additions and 52 deletions

354
admin-projects.js Normal file
View 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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
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

Binary file not shown.

View file

@ -1,8 +1,8 @@
---
title: '> top'
title: '> topha'
date: 2026-03-18
status_class: 'stable'
status_label: 'PROTOKOLL #002'
status_label: 'PROTOKOLL #0099'
thumb: '/img/hp_muti_steampunk_01.png'
---

View file

@ -7,8 +7,6 @@
data-ascii-shimmer
data-text="KILABOR"
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>
<header>
@ -73,13 +71,13 @@
<!-- SCHWARZES TERMINAL / SYSTEM LOG -->
<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 style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; border-bottom: 1px solid #1a1a1a; padding-bottom: 10px;">
<span style="color: #333; 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>
<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: #4dd0ff; font-weight: bold; font-size: 0.65rem; letter-spacing: 3px;">// SYSTEM_LOG_FEED</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 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 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>
<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>

BIN
public/.DS_Store vendored

Binary file not shown.

View file

@ -26,6 +26,15 @@ body {
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 {
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; }
}
/* 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 --- */
/* 1. KI-KERNEL (Matrix Style) */
@ -583,6 +608,21 @@ body.mode-ghost .prompt {
.log-entry.ok { color: #0f0; }
.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 {
display: flex;
border-top: 1px solid #111;

View file

@ -28,8 +28,6 @@
data-ascii-shimmer
data-text="KILABOR"
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>
<header>
@ -242,13 +240,13 @@
<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 style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; border-bottom: 1px solid #1a1a1a; padding-bottom: 10px;">
<span style="color: #333; 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>
<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: #4dd0ff; font-weight: bold; font-size: 0.65rem; letter-spacing: 3px;">// SYSTEM_LOG_FEED</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 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 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>
<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>
@ -278,13 +276,13 @@
<a href="/posts/protocol-top/" class="portfolio-link project-card" data-category="stable">
<div class="portfolio-item">
<span class="status-tag stable">
PROTOKOLL #002
PROTOKOLL #0099
</span>
<div class="project-header-wrap">
<img src="/img/hp_muti_steampunk_01.png" class="project-thumb" alt="Preview">
<h3>&gt; top</h3>
<h3>&gt; topha</h3>
</div>
<div class="item-excerpt">
<h3 id="status-prozess-überwachung">STATUS: PROZESS-ÜBERWACHUNG</h3>

View file

@ -5,9 +5,12 @@
function initAsciiShimmer(container) {
var text = container.dataset.text || 'KILABOR';
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);
// 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 edgeChars = ['+', 'x', '-', '=', ':', '.', 'o'];
@ -49,8 +52,9 @@
mctx.fillText(text, width / 2, height / 2);
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 colsPerLetter = cols / text.length;
mask = [];
for (var ry = 0; ry < rows; ry++) {
@ -60,7 +64,8 @@
var idx = (py * width + px) * 4;
var alpha = imgData[idx + 3] / 255;
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.textAlign = 'center';
frame++;
var hueBase = (frame * hueSpeed) % 360;
var hueStep = 360 / text.length;
for (var i = 0; i < mask.length; i++) {
var c = mask[i];
var isDense = c.alpha > 0.7;
var set = isDense ? denseChars : edgeChars;
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);
}
}

View file

@ -155,12 +155,19 @@
});
}
function randHex() {
return Math.random().toString(16).substring(2, 15);
}
function log(msg, cls = '') {
if (!logOutput) return;
const entry = document.createElement('div');
entry.className = 'log-entry ' + cls;
entry.innerHTML = msg;
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) {
@ -168,6 +175,16 @@
const cmd = parts[0].toLowerCase();
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>');
switch(cmd) {
@ -199,15 +216,57 @@
case 'trace': document.querySelector('[data-mode="trace"]')?.click(); break;
case 'flux': document.querySelector('[data-mode="overdrive"]')?.click(); break;
case 'hack':
log('>> INITIATING NEURAL HACK...', 'warn');
let hIdx = 0;
const hInt = setInterval(() => {
log(Math.random().toString(16).substring(2, 15), 'ghost');
if (hIdx++ > 15) {
clearInterval(hInt);
log('>> ACCESS GRANTED.', 'ok');
const hackTargets = ['NASA.GOV', 'PENTAGON.MIL', 'AREA51.GOV', 'CIA.GOV', 'FBI.GOV'];
const hackTarget = hackTargets[Math.floor(Math.random() * hackTargets.length)];
const hackIp = Array.from({ length: 4 }, () => Math.floor(Math.random() * 256)).join('.');
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);
const hackScript = [
{ t: '>> INITIATING NEURAL HACK...', c: 'warn', d: 0 },
{ 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;
case 'deep-scan':
log('>> INITIALIZING DEEP SYSTEM SCAN...', 'info');
@ -223,6 +282,22 @@
log('SYSTEM ONLINE SEIT: 01.01.1974', 'info');
log('LAUFZEIT: ' + years + ' JAHRE (STABILITÄT: FRAGLICH)', 'ok');
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;
default:
log('FEHLER: BEFEHL "' + cmd + '" NICHT GEFUNDEN.', 'error');

View file

@ -3,7 +3,7 @@
<head><script src="/livereload.js?mindelay=10&amp;v=2&amp;port=1414&amp;path=livereload" data-no-instant defer></script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>&gt; top | DIGITALE ANARCHIE | KI-LABOR</title>
<title>&gt; topha | DIGITALE ANARCHIE | KI-LABOR</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body class=" ">
@ -189,7 +189,7 @@
<p class="person-pre-label" style="margin:12px 0 4px;">// ZENTRAL-ARCHIV · EINHEIT IDENTIFIZIERT //</p>
<div class="person-title-wrap">
<span class="person-title-deco">⟪</span>
<h1 class="person-glitch-title" data-text="&gt; TOP">&gt; TOP</h1>
<h1 class="person-glitch-title" data-text="&gt; TOPHA">&gt; TOPHA</h1>
<span class="person-title-deco">⟫</span>
</div>
<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>
</div>
<div class="akte-label-divider"></div>
<div class="akte-label-title">AKTE: &gt; TOP</div>
<div class="akte-label-title">AKTE: &gt; TOPHA</div>
<div class="akte-label-divider"></div>
<div class="akte-label-bottom">
<span>STATUS: KLASSIFIZIERT</span>
@ -365,7 +365,7 @@
<div style="flex: 1;">
<div style="font-size: 0.5rem !important; color: #5d4037; letter-spacing: 2px; margin-bottom: 2px;">PROTOKOLL NR. 1974-Ø</div>
<h1 style="font-weight: bold;">&gt; TOP</h1>
<h1 style="font-weight: bold;">&gt; TOPHA</h1>
<p style="color: #5d4037; font-size: 0.7rem !important; margin: 5px 0 10px;">ALIAS: </p>
<div style="display: flex; gap: 10px;">
@ -382,7 +382,7 @@
<div class="data-row" style="display: flex; align-items: center;">
<div class="label" style="width: 160px; letter-spacing: 1px;">DESIGNATION:</div>
<div style="flex: 1; font-weight: normal;">
&gt; top
&gt; topha
</div>
</div>
@ -723,7 +723,7 @@
<script>
(function() {
const key = 'decl_top';
const key = 'decl_topha';
const overlay = document.getElementById('declassified-overlay');
if (!overlay) return;
if (sessionStorage.getItem(key)) {

BIN
static/.DS_Store vendored

Binary file not shown.

View file

@ -26,6 +26,15 @@ body {
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 {
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; }
}
/* 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 --- */
/* 1. KI-KERNEL (Matrix Style) */
@ -583,6 +608,21 @@ body.mode-ghost .prompt {
.log-entry.ok { color: #0f0; }
.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 {
display: flex;
border-top: 1px solid #111;

View file

@ -5,9 +5,12 @@
function initAsciiShimmer(container) {
var text = container.dataset.text || 'KILABOR';
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);
// 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 edgeChars = ['+', 'x', '-', '=', ':', '.', 'o'];
@ -49,8 +52,9 @@
mctx.fillText(text, width / 2, height / 2);
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 colsPerLetter = cols / text.length;
mask = [];
for (var ry = 0; ry < rows; ry++) {
@ -60,7 +64,8 @@
var idx = (py * width + px) * 4;
var alpha = imgData[idx + 3] / 255;
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.textAlign = 'center';
frame++;
var hueBase = (frame * hueSpeed) % 360;
var hueStep = 360 / text.length;
for (var i = 0; i < mask.length; i++) {
var c = mask[i];
var isDense = c.alpha > 0.7;
var set = isDense ? denseChars : edgeChars;
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);
}
}

View file

@ -155,12 +155,19 @@
});
}
function randHex() {
return Math.random().toString(16).substring(2, 15);
}
function log(msg, cls = '') {
if (!logOutput) return;
const entry = document.createElement('div');
entry.className = 'log-entry ' + cls;
entry.innerHTML = msg;
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) {
@ -168,6 +175,16 @@
const cmd = parts[0].toLowerCase();
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>');
switch(cmd) {
@ -199,15 +216,57 @@
case 'trace': document.querySelector('[data-mode="trace"]')?.click(); break;
case 'flux': document.querySelector('[data-mode="overdrive"]')?.click(); break;
case 'hack':
log('>> INITIATING NEURAL HACK...', 'warn');
let hIdx = 0;
const hInt = setInterval(() => {
log(Math.random().toString(16).substring(2, 15), 'ghost');
if (hIdx++ > 15) {
clearInterval(hInt);
log('>> ACCESS GRANTED.', 'ok');
const hackTargets = ['NASA.GOV', 'PENTAGON.MIL', 'AREA51.GOV', 'CIA.GOV', 'FBI.GOV'];
const hackTarget = hackTargets[Math.floor(Math.random() * hackTargets.length)];
const hackIp = Array.from({ length: 4 }, () => Math.floor(Math.random() * 256)).join('.');
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);
const hackScript = [
{ t: '>> INITIATING NEURAL HACK...', c: 'warn', d: 0 },
{ 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;
case 'deep-scan':
log('>> INITIALIZING DEEP SYSTEM SCAN...', 'info');
@ -223,6 +282,22 @@
log('SYSTEM ONLINE SEIT: 01.01.1974', 'info');
log('LAUFZEIT: ' + years + ' JAHRE (STABILITÄT: FRAGLICH)', 'ok');
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;
default:
log('FEHLER: BEFEHL "' + cmd + '" NICHT GEFUNDEN.', 'error');