feat: brutalist placeholder site for 0012.de root

Replace ops beacon with sarcastic standalone landing page, centered
layout, no leaked data; update SITE.md that monitoring lives elsewhere.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Frank Schwenk
2026-06-11 23:39:23 +02:00
parent 01de7ed4fb
commit 3558739e07
2 changed files with 225 additions and 96 deletions
+13 -43
View File
@@ -1,53 +1,23 @@
# 0012.de — Landing Page (Konzept) # 0012.de — Website (Konzept)
Die Domain soll nicht leer bleiben, aber zum Projekt passen: **externer Beobachter**, nicht Marketing-Seite. **0012.de ist eine eigene Website** — Inhalt noch offen. Kein Monitoring-Dashboard auf der Root-Domain.
## Idee: „Status Beacon“ Monitoring, Status-Beacons, `ingest.php` usw. laufen später in **Unterverzeichnissen oder Subdomains** (z. B. `status.0012.de`, `/ops/` — noch festzulegen).
Eine minimalistische **Mission-Control-Oberfläche** — dunkel, monospace, ohne Tracking-Bloat. ## Aktuell (Platzhalter)
### Tagline Brutalistische Startseite: scharf, dunkel, sarkastisch. Keine Secrets, keine Ops-Daten.
> **0012** · external observer node ## Später: Status / Monitoring (nicht Root)
> Watching from outside.
„0012“ als Name des Knotens (wie ein Kanal/Rufzeichen), nicht als Marketing-Marke. Siehe [`MONITORING.md`](MONITORING.md) und [`todo/06-site-0012-landing.md`](todo/06-site-0012-landing.md) — Zielpfad anpassen, wenn Subdomain/Pfad steht.
### Inhalt (above the fold) ## Was 0012.de werden könnte
1. **Live-Status-Raster** — 12 Kacheln für die wichtigsten überwachten Endpoints (aus `MONITORING.md` Mindestliste): Noch unklar. Die Domain bleibt frei für etwas Eigenes — Portfolio, Experiment, Kunstprojekt, Katalog, whatever.
- schwenk.online, gitea, pics, medis, portainer, vfb.red, …
- Farbe: grün / gelb (SSL &lt; 14 Tage, Latenz hoch) / rot (down)
- Letzter Check-Zeitstempel
2. **boka-Heartbeat** — „Last push from boka: 3 min ago“ oder „⚠ no signal &gt; 15 min“ ## Was bewusst nicht auf 0012.de /
3. **Meta-Zeile** — „This node runs on wint.global · independent of boka (89.163.231.180)“ - Öffentliche Monitoring-Kacheln (→ Subdomain/Pfad)
- IPs, Tokens, `.env`-Inhalte
### Datenquelle - Comic Sans (never again)
- Gleiche JSON-Datei oder SQLite, die `monitor.php` beim Cron-Lauf schreibt (`data/last-checks.json`)
- Landing Page liest nur diese Datei — **kein** Live-curl im Browser (Performance, Timeout)
### Design
- Hintergrund: sehr dunkles Grau (`#0d1117`)
- Akzent: Amber für Warnung, Grün `#3fb950`, Rot `#f85149` (GitHub-Dark-ähnlich)
- Schrift: system-ui oder `JetBrains Mono` via Google Fonts (ein Request)
- Kein JS-Framework — plain PHP + minimal CSS, eine Datei
### Optional (später)
- `/status.json` — öffentliche API für Uptime (read-only)
- RSS-Feed „incidents“ bei längeren Ausfällen
- Kleine „About“-Sektion: Link zu GitHub-Repo (wenn public)
### Was bewusst **nicht**
- Kein Blog, kein Portfolio — das ist Ops-Infrastruktur
- Keine Login-Fläche auf der Startseite
- Keine Details zu internen IPs/Ports (nur boka-Heartbeat als Text)
## Umsetzung
Siehe [`todo/06-site-0012-landing.md`](todo/06-site-0012-landing.md).
+211 -52
View File
@@ -2,79 +2,238 @@
declare(strict_types=1); declare(strict_types=1);
require_once dirname(__DIR__) . '/private/config/bootstrap.php';
$domain = env('DOMAIN', '0012.de');
$bokaIp = env('BOKA_IP', '89.163.231.180');
$checksFile = privateDataDir() . '/last-checks.json';
$checks = is_readable($checksFile)
? json_decode((string) file_get_contents($checksFile), true)
: null;
$lastRun = is_array($checks) && isset($checks['updated_at'])
? $checks['updated_at']
: null;
header('Content-Type: text/html; charset=utf-8'); header('Content-Type: text/html; charset=utf-8');
header('X-Robots-Tag: noindex'); header('X-Robots-Tag: noindex');
$jahr = (int) date('Y');
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>
<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><?= htmlspecialchars($domain, ENT_QUOTES, 'UTF-8') ?> · external observer</title> <title>0012</title>
<style> <style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root { :root {
--bg: #0d1117; --bg: #0a0a0a;
--text: #c9d1d9; --fg: #e8e8e8;
--muted: #8b949e; --dim: #666;
--accent: #58a6ff; --line: #333;
--warn: #d29922; --acid: #c8ff00;
--ok: #3fb950; --blood: #ff2a2a;
} }
* { box-sizing: border-box; } html { font-size: 16px; }
body { body {
margin: 0;
min-height: 100vh; min-height: 100vh;
background: var(--bg); background: var(--bg);
color: var(--text); color: var(--fg);
font-family: ui-monospace, "Cascadia Code", "JetBrains Mono", monospace; font-family: "IBM Plex Mono", "Courier New", monospace;
line-height: 1.5; line-height: 1.45;
padding: 2rem 1.25rem 3rem; padding: 0 clamp(1rem, 4vw, 3rem);
} }
main { max-width: 42rem; margin: 0 auto; } .wrap {
h1 { font-size: 1.25rem; font-weight: 600; margin: 0 0 0.25rem; letter-spacing: 0.04em; } width: 100%;
.tagline { color: var(--muted); margin: 0 0 2rem; font-size: 0.9rem; } max-width: 42rem;
.panel { margin: 0 auto;
border: 1px solid #30363d; min-height: 100vh;
border-radius: 8px; border-left: 4px solid var(--acid);
padding: 1.25rem 1.5rem; border-right: 1px solid var(--line);
background: #161b22; }
header {
border-bottom: 1px solid var(--line);
padding: 2rem 1.5rem 1.5rem;
}
.logo {
font-size: clamp(4rem, 18vw, 9rem);
font-weight: 700;
letter-spacing: -0.06em;
line-height: 0.85;
color: var(--fg);
}
.logo span { color: var(--acid); }
.claim {
margin-top: 1rem;
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.2em;
color: var(--dim);
}
section {
border-bottom: 1px solid var(--line);
padding: 1.5rem;
}
h2 {
font-size: 0.7rem;
font-weight: 400;
text-transform: uppercase;
letter-spacing: 0.25em;
color: var(--dim);
margin-bottom: 1rem;
}
p { margin-bottom: 0.75rem; }
p:last-child { margin-bottom: 0; }
.blink {
animation: blink 1.2s step-end infinite;
color: var(--blood);
}
@keyframes blink { 50% { opacity: 0; } }
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0;
border: 1px solid var(--line);
}
@media (max-width: 520px) {
.grid { grid-template-columns: 1fr; }
.cell { border-right: none; }
}
.cell {
padding: 1rem 1.25rem;
border-right: 1px solid var(--line);
border-bottom: 1px solid var(--line);
font-size: 0.8rem;
}
.cell:nth-child(2n) { border-right: none; }
.cell strong {
display: block;
font-size: 0.65rem;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--dim);
margin-bottom: 0.4rem;
}
.cell em { color: var(--acid); font-style: normal; }
.cell s { color: var(--dim); }
blockquote {
border-left: 3px solid var(--blood);
padding: 0 0 0 1rem;
color: var(--dim);
font-size: 0.9rem;
}
.meter {
display: flex;
align-items: stretch;
height: 12px;
border: 1px solid var(--line);
margin-top: 0.5rem;
}
.meter-fill {
width: 3%;
background: var(--acid);
}
.meter-rest {
flex: 1;
background: repeating-linear-gradient(
90deg,
var(--line) 0,
var(--line) 2px,
transparent 2px,
transparent 6px
);
}
footer {
padding: 1.5rem;
font-size: 0.7rem;
color: var(--dim);
letter-spacing: 0.05em;
}
footer a { color: var(--dim); }
.cursor {
display: inline-block;
width: 0.55em;
height: 1em;
background: var(--fg);
vertical-align: text-bottom;
margin-left: 2px;
animation: blink 0.9s step-end infinite;
}
.warn {
background: var(--blood);
color: var(--bg);
padding: 0.35rem 0.6rem;
font-size: 0.65rem;
text-transform: uppercase;
letter-spacing: 0.12em;
display: inline-block;
margin-bottom: 1rem;
} }
.status { color: var(--warn); margin: 0 0 0.75rem; }
.status.ok { color: var(--ok); }
.meta { color: var(--muted); font-size: 0.85rem; margin: 1.5rem 0 0; }
code { color: var(--accent); }
</style> </style>
</head> </head>
<body> <body>
<main> <div class="wrap">
<h1>0012</h1> <header>
<p class="tagline">external observer node · watching from outside</p> <div class="logo">00<span>12</span></div>
<p class="claim">Eine Website · kein Inhalt · keine Entschuldigung</p>
</header>
<div class="panel"> <section>
<?php if ($lastRun): ?> <div class="warn">Status: unbelegt</div>
<p class="status ok">Monitoring active — last check <?= htmlspecialchars($lastRun, ENT_QUOTES, 'UTF-8') ?></p> <p>
<?php else: ?> Willkommen auf 0012.de. Hier entsteht irgendwann etwas.
<p class="status">Monitoring not initialized — awaiting first cron run</p> Was genau, ist noch nicht entschieden — und das ist halb Strategie, halb Bequemlichkeit.
<?php endif; ?>
<p class="meta">
Node <code><?= htmlspecialchars($domain, ENT_QUOTES, 'UTF-8') ?></code>
· independent of boka (<code><?= htmlspecialchars($bokaIp, ENT_QUOTES, 'UTF-8') ?></code>)
</p> </p>
</section>
<section>
<h2>Was wir anbieten</h2>
<div class="grid">
<div class="cell">
<strong>Produkt</strong>
<s>Alles</s> Noch nichts.<br>
Aber mit Überzeugung.
</div> </div>
</main> <div class="cell">
<strong>Support</strong>
Schreiben Sie an die Leere.<br>
Antwortzeit: <em>∞</em>
</div>
<div class="cell">
<strong>Preise</strong>
Kostenlos.<br>
Der Preis ist Ihre Zeit, die Sie gerade verschwenden.
</div>
<div class="cell">
<strong>Datenschutz</strong>
Wir sammeln nichts.<br>
Nicht aus Tugend — aus Inkompetenz.
</div>
</div>
</section>
<section>
<h2>Roadmap <?= $jahr ?>–<?= $jahr + 3 ?></h2>
<p>Q1 — Idee haben<br>Q2 — Idee vergessen<br>Q3 — Diese Seite<br>Q4 — Sie lesen das</p>
<p>Fortschritt zur „finalen Vision“:</p>
<div class="meter"><div class="meter-fill"></div><div class="meter-rest"></div></div>
<p style="margin-top:0.5rem;font-size:0.75rem;color:var(--dim);">3 % · gerundet von 0 %</p>
</section>
<section>
<h2>Pressestimmen (intern)</h2>
<blockquote>
„Sieht aus wie eine Website.“ — Entwickler, bezahlt<br><br>
„0012 — eine Zahl vor dem Fehler.“ — Logfile, 03:14 Uhr<br><br>
„Wenigstens keine Comic Sans.“ — Besucher mit Standards
</blockquote>
</section>
<section>
<h2>Live</h2>
<p>
<span class="blink">■</span>
System operational · Inhalt pending · Bedeutung not found
<span class="cursor"></span>
</p>
<p style="font-size:0.8rem;color:var(--dim);margin-top:1rem;">
Keine IPs. Keine Tokens. Nichts auf dem Server, der Ihnen zulächelt.
Nur diese Seite — und Ihr schlechtes Gewissen, noch nicht weiterzuklicken.
</p>
</section>
<footer>
© <?= $jahr ?> 0012.de · Impressum folgt, wenn jemand klagt
</footer>
</div>
</body> </body>
</html> </html>