rebrand: Klassenportal, domain info1.ifb.lol, server status

- Replace INFO1 brand with Klassenportal everywhere (titles, nav,
  emails, TOTP issuer, recovery codes)
- Update domain from info1.simon0x.xyz to info1.ifb.lol
- Remove E2EE claims (e2ee.js was deleted, claims were false)
- Add GET /api/health endpoint (DB check + uptime)
- Add live server status section to landing page
- Fix README: domain, title, layout table
This commit is contained in:
Simon
2026-04-22 21:30:40 +02:00
parent 55cfbcebdc
commit 578dd4eab9
12 changed files with 173 additions and 397 deletions
+113 -24
View File
@@ -3,8 +3,8 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>INFO1 · Das Klassen-Cockpit</title>
<meta name="description" content="INFO1 ist ein privates Klassenportal für die INFO1-Klasse. Stundenplan, Noten, Hausaufgaben, Klassen-Chat und mehr.">
<title>Klassenportal · Das Klassen-Cockpit</title>
<meta name="description" content="Privates Klassenportal für Schüler und Lehrer. Stundenplan, Noten, Hausaufgaben, Klassen-Chat und mehr.">
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
@@ -538,6 +538,35 @@ footer {
flex-wrap: wrap; gap: 12px;
}
/* ── STATUS ───────────────────── */
.status-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
max-width: 760px;
margin: 0 auto;
}
.status-card {
display: flex;
align-items: center;
gap: 14px;
padding: 20px 24px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
}
.status-dot {
width: 10px; height: 10px;
border-radius: 50%;
flex-shrink: 0;
background: var(--subtle);
}
.status-dot.ok { background: var(--green); box-shadow: 0 0 0 3px rgba(22,163,74,.15); }
.status-dot.err { background: var(--red); box-shadow: 0 0 0 3px rgba(220,38,38,.15); }
.status-label { font-size: 14px; font-weight: 600; color: var(--text); }
.status-sub { font-size: 12px; color: var(--muted); margin-top: 1px; }
@media (max-width: 600px) { .status-grid { grid-template-columns: 1fr; } }
/* ── REDACT ──────────────────── */
.redact {
display: inline-block;
@@ -581,10 +610,10 @@ footer {
<nav class="top">
<div class="nav-inner">
<a class="brand" href="/">
<div class="brand-mark">i1</div>
<div class="brand-mark">KP</div>
<div class="brand-text">
<span class="brand-sub">Klassenportal</span>
<span class="brand-name">INFO1</span>
<span class="brand-name">Klassenportal</span>
</div>
</a>
<div class="nav-links">
@@ -592,6 +621,7 @@ footer {
<a href="#ueber">Für wen</a>
<a href="#sicherheit">Sicherheit</a>
<a href="#faq">FAQ</a>
<a href="#status">Status</a>
</div>
<div class="nav-right">
<button class="icon-btn" onclick="toggleDark()" title="Dark Mode" aria-label="Dark Mode"><i data-lucide="moon"></i></button>
@@ -605,7 +635,7 @@ footer {
<section class="hero">
<div class="hero-inner">
<div>
<div class="eyebrow"><i data-lucide="sparkles"></i> Gemacht für die INFO1-Klasse</div>
<div class="eyebrow"><i data-lucide="sparkles"></i> Gemacht für Schüler und Lehrer</div>
<h1 class="hero-title">
Dein Schulalltag, <span class="accent">an einem Ort.</span>
</h1>
@@ -624,7 +654,7 @@ footer {
<div class="hero-trust">
<span><i data-lucide="check"></i> Nur für IFB-Schüler</span>
<span><i data-lucide="check"></i> 2-Faktor-Schutz</span>
<span><i data-lucide="check"></i> Hetzner · Nürnberg</span>
<span><i data-lucide="check"></i> Server in Deutschland</span>
</div>
</div>
@@ -715,7 +745,7 @@ footer {
<div class="feat">
<div class="feat-icon"><i data-lucide="message-square"></i></div>
<h3>Klassen-Chat</h3>
<p>Ende-zu-Ende verschlüsselter Austausch mit der Klasse direkt im Dashboard.</p>
<p>Gemeinsamer Echtzeit-Chat mit der Klasse direkt im Dashboard, kein WhatsApp nötig.</p>
</div>
<div class="feat">
<div class="feat-icon"><i data-lucide="folder"></i></div>
@@ -753,7 +783,7 @@ footer {
<div class="sec-eyebrow">Für wen</div>
<h2 class="sec-title">Zwei Perspektiven, ein System</h2>
<p class="sec-lead">
Ob du im Klassenzimmer sitzt oder davor stehst INFO1 passt sich an deine Rolle an.
Ob du im Klassenzimmer sitzt oder davor stehst das Portal passt sich an deine Rolle an.
</p>
</div>
@@ -766,7 +796,7 @@ footer {
<li><i data-lucide="check"></i> Persönliches Dashboard mit deinem Stundenplan</li>
<li><i data-lucide="check"></i> Noten-Übersicht mit automatischem Schnitt</li>
<li><i data-lucide="check"></i> Hausaufgaben, To-Dos und Countdowns an einem Ort</li>
<li><i data-lucide="check"></i> Verschlüsselter Klassen-Chat + Dateispeicher</li>
<li><i data-lucide="check"></i> Klassen-Chat + persönlicher Dateispeicher</li>
<li><i data-lucide="check"></i> Shared Kalender für Klausuren &amp; Events</li>
</ul>
</div>
@@ -801,8 +831,8 @@ footer {
<div class="sec-grid">
<div class="sec-card">
<i data-lucide="shield-check"></i>
<h4>DSGVO &amp; Hetzner DE</h4>
<p>Server ausschließlich in Nürnberg. Keine Datenübertragung in Drittländer.</p>
<h4>DSGVO &amp; Deutschland</h4>
<p>Server ausschließlich in Deutschland. Keine Datenübertragung in Drittländer.</p>
</div>
<div class="sec-card">
<i data-lucide="key-round"></i>
@@ -810,9 +840,9 @@ footer {
<p>Optionaler TOTP-Schutz per Authenticator-App für deinen Account.</p>
</div>
<div class="sec-card">
<i data-lucide="lock"></i>
<h4>Ende-zu-Ende-Chat</h4>
<p>Chat-Nachrichten werden im Browser verschlüsselt der Server liest nicht mit.</p>
<i data-lucide="zap-off"></i>
<h4>Kein Tracking</h4>
<p>Keine Werbung, keine Analytics, keine Weitergabe. Nur die Daten, die der Betrieb braucht.</p>
</div>
<div class="sec-card">
<i data-lucide="mail-check"></i>
@@ -835,7 +865,7 @@ footer {
<details class="faq-item">
<summary>Was kostet die Nutzung?</summary>
<div class="faq-body">
Nichts. INFO1 ist ein privates Projekt für die Klasse und kostet die Nutzer keinen Cent.
Nichts. Das Klassenportal ist ein privates Projekt und kostet die Nutzer keinen Cent.
</div>
</details>
<details class="faq-item">
@@ -848,15 +878,14 @@ footer {
<details class="faq-item">
<summary>Wo liegen meine Daten?</summary>
<div class="faq-body">
Auf einem Hetzner-Server in Nürnberg, Deutschland. Es findet keine Übertragung in Drittländer statt.
Auf einem Server in Deutschland. Es findet keine Übertragung in Drittländer statt.
Details in der <a href="/datenschutz" style="color:var(--blue);">Datenschutzerklärung</a>.
</div>
</details>
<details class="faq-item">
<summary>Kann der Admin meinen Chat lesen?</summary>
<div class="faq-body">
Nein. Chat-Nachrichten werden im Browser mit einem Klassen-Schlüssel verschlüsselt, bevor sie den Server erreichen.
Auf dem Server liegen nur Chiffre-Texte.
Ja. Chat-Nachrichten sind für den Admin einsehbar.
</div>
</details>
<details class="faq-item">
@@ -867,7 +896,7 @@ footer {
</div>
</details>
<details class="faq-item">
<summary>Läuft INFO1 auch auf dem Handy?</summary>
<summary>Läuft das Klassenportal auch auf dem Handy?</summary>
<div class="faq-body">
Ja. Das Dashboard ist voll responsive und funktioniert auf Smartphone, Tablet und Desktop gleich gut.
</div>
@@ -876,6 +905,39 @@ footer {
</div>
</section>
<!-- STATUS -->
<section id="status">
<div class="section-inner">
<div class="sec-head">
<div class="sec-eyebrow">Systemstatus</div>
<h2 class="sec-title">Alles im grünen Bereich</h2>
</div>
<div class="status-grid">
<div class="status-card">
<div class="status-dot" id="st-api"></div>
<div>
<div class="status-label">API</div>
<div class="status-sub" id="st-api-sub">Wird geprüft…</div>
</div>
</div>
<div class="status-card">
<div class="status-dot" id="st-db"></div>
<div>
<div class="status-label">Datenbank</div>
<div class="status-sub" id="st-db-sub">Wird geprüft…</div>
</div>
</div>
<div class="status-card">
<div class="status-dot" id="st-up"></div>
<div>
<div class="status-label">Uptime</div>
<div class="status-sub" id="st-up-sub">Wird geprüft…</div>
</div>
</div>
</div>
</div>
</section>
<!-- CTA -->
<section>
<div class="cta-band">
@@ -893,13 +955,13 @@ footer {
<div class="foot-inner">
<div class="foot-about">
<a class="brand" href="/">
<div class="brand-mark">i1</div>
<div class="brand-mark">KP</div>
<div class="brand-text">
<span class="brand-sub">Klassenportal</span>
<span class="brand-name">INFO1</span>
<span class="brand-name">Klassenportal</span>
</div>
</a>
<p>Privates Klassenportal für die INFO1-Klasse. Kein offizielles Angebot einer Schule oder eines Trägers. Daten gehostet in Deutschland.</p>
<p>Privates Klassenportal für Schüler und Lehrer. Kein offizielles Angebot einer Schule oder eines Trägers. Daten gehostet in Deutschland.</p>
</div>
<div class="foot-col">
<h5>Produkt</h5>
@@ -927,8 +989,8 @@ footer {
</div>
</div>
<div class="foot-bottom">
<span>© <span id="y"></span> INFO1 · Privates Klassenportal (inoffiziell)</span>
<span>Daten auf Hetzner, Nürnberg · EU-DSGVO konform</span>
<span>© <span id="y"></span> Klassenportal · Privat &amp; inoffiziell</span>
<span>Daten in Deutschland · EU-DSGVO konform</span>
</div>
</footer>
@@ -942,6 +1004,33 @@ footer {
try { if (localStorage.getItem('dark') === '1') document.body.classList.add('dark'); } catch {}
lucide.createIcons();
(async function checkStatus() {
function fmt(s) {
if (s < 60) return s + 's';
if (s < 3600) return Math.floor(s / 60) + 'm';
if (s < 86400) return Math.floor(s / 3600) + 'h';
return Math.floor(s / 86400) + 'd';
}
try {
const r = await fetch('/api/health');
const d = await r.json();
const ok = d.ok === true;
document.getElementById('st-api').className = 'status-dot ' + (ok ? 'ok' : 'err');
document.getElementById('st-api-sub').textContent = ok ? 'Online' : 'Fehler';
document.getElementById('st-db').className = 'status-dot ' + (ok ? 'ok' : 'err');
document.getElementById('st-db-sub').textContent = ok ? 'Online' : 'Nicht erreichbar';
document.getElementById('st-up').className = 'status-dot ok';
document.getElementById('st-up-sub').textContent = ok ? fmt(d.uptime) : '';
} catch {
['st-api','st-db','st-up'].forEach(id => {
document.getElementById(id).className = 'status-dot err';
});
document.getElementById('st-api-sub').textContent = 'Nicht erreichbar';
document.getElementById('st-db-sub').textContent = '';
document.getElementById('st-up-sub').textContent = '';
}
})();
</script>
</body>
</html>