feat(web): initial bilingual landing page and client cockpit

This commit is contained in:
benzj 2026-09-12 21:23:32 +02:00
commit 35aacf8315
9 changed files with 1335 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
benzcloud-client
benzcloud-client.exe
android/
data/

1
.nojekyll Normal file
View File

@ -0,0 +1 @@

17
404.html Normal file
View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404 - Seite nicht gefunden · BenzCloud Client</title>
<link rel="stylesheet" href="style.css">
</head>
<body style="display: flex; justify-content: center; align-items: center; text-align: center; height: 100vh;">
<div style="max-width: 500px; padding: 2rem;">
<h1 style="font-size: 4rem; color: var(--accent-primary); margin-bottom: 1rem;">404</h1>
<h2 style="margin-bottom: 1rem;">Seite nicht gefunden / Page Not Found</h2>
<p style="color: var(--text-secondary); margin-bottom: 2rem;">Die angeforderte Ressource existiert nicht auf diesem Client.</p>
<a href="index.html" class="btn-primary" style="display: inline-block;">Zur Startseite</a>
</div>
</body>
</html>

160
app/app.js Normal file
View File

@ -0,0 +1,160 @@
// BenzCloud Client App Controller
let currentLang = localStorage.getItem("benzcloud_client_lang") || "de";
let clientProfile = null;
const i18n = {
de: {
pair_badge: "🔗 Client-Ersteinrichtung",
pair_title: "Mit BenzCloud-Server verbinden",
pair_desc: "Gib die lokale IP-Adresse deines BenzCloud-Servers und deine Anmeldedaten ein. Das Mesh-VPN (Nebula) und der DNS-Server konfigurieren sich automatisch.",
lbl_server_url: "Server LAN-Adresse:",
lbl_user: "Benutzername:",
lbl_pass: "Passwort:",
btn_pair: "🚀 Gerät sicher koppeln & verbinden",
status_connected: "Verbunden mit BenzCloud",
services_heading: "Deine Enterprise-Dienste (Direktzugriff)",
card_drive_title: "BenzCloud Drive",
card_drive_desc: "Dateien hochladen, teilen und synchronisieren.",
card_mail_title: "BenzCloud Mail",
card_mail_desc: "Internes Webmail & Thunderbird-kompatibles Postfach.",
card_chat_title: "BenzCloud Chat",
card_chat_desc: "Echtzeit-Teamkommunikation & Direktnachrichten.",
card_web_title: "Webseiten & Portal",
card_web_desc: "Gehostete Firmen- und Team-Webseiten aufrufen.",
btn_unpair: "🔌 Entkoppeln / Gerät trennen",
pairing_in_progress: "⚙️ Kopplung läuft...",
confirm_unpair: "Möchtest du dieses Gerät wirklich vom BenzCloud-Server trennen?"
},
en: {
pair_badge: "🔗 Initial Client Setup",
pair_title: "Connect to BenzCloud Server",
pair_desc: "Enter your local BenzCloud server IP and your credentials. Mesh-VPN (Nebula) and DNS server will configure automatically.",
lbl_server_url: "Server LAN Address:",
lbl_user: "Username:",
lbl_pass: "Password:",
btn_pair: "🚀 Securely Pair & Connect Device",
status_connected: "Connected to BenzCloud",
services_heading: "Your Enterprise Services (Direct Access)",
card_drive_title: "BenzCloud Drive",
card_drive_desc: "Upload, share, and synchronize cloud files.",
card_mail_title: "BenzCloud Mail",
card_mail_desc: "Internal webmail & Thunderbird-compatible mailbox.",
card_chat_title: "BenzCloud Chat",
card_chat_desc: "Real-time team communication & direct messages.",
card_web_title: "Websites & Portal",
card_web_desc: "Browse hosted team and company websites.",
btn_unpair: "🔌 Disconnect / Unpair Device",
pairing_in_progress: "⚙️ Pairing in progress...",
confirm_unpair: "Are you sure you want to disconnect this device from BenzCloud?"
}
};
document.addEventListener("DOMContentLoaded", () => {
applyLanguage(currentLang);
fetchClientProfile();
});
function setLanguage(lang) {
currentLang = lang;
localStorage.setItem("benzcloud_client_lang", lang);
applyLanguage(lang);
}
function applyLanguage(lang) {
document.documentElement.lang = lang;
document.querySelectorAll("[data-i18n]").forEach(el => {
const key = el.getAttribute("data-i18n");
if (i18n[lang] && i18n[lang][key]) {
el.textContent = i18n[lang][key];
}
});
document.getElementById("langDE").classList.toggle("active", lang === "de");
document.getElementById("langEN").classList.toggle("active", lang === "en");
}
async function fetchClientProfile() {
try {
const res = await fetch("/api/profile");
if (!res.ok) {
showPairingView();
return;
}
const data = await res.json();
if (!data.paired) {
showPairingView();
return;
}
clientProfile = data;
showConnectedView(data);
} catch (err) {
showPairingView();
}
}
function showPairingView() {
document.getElementById("pairingView").style.display = "block";
document.getElementById("connectedView").style.display = "none";
}
function showConnectedView(data) {
document.getElementById("pairingView").style.display = "none";
document.getElementById("connectedView").style.display = "block";
document.getElementById("dispDomain").textContent = data.base_domain || "intern";
document.getElementById("dispOverlayIP").textContent = data.overlay_ip || "10.42.0.2";
document.getElementById("dispLighthouse").textContent = data.server_vpn_ip || "10.42.0.1";
const domain = data.base_domain;
document.getElementById("subDrive").textContent = `http://drive.${domain}`;
document.getElementById("cardDrive").href = `http://drive.${domain}`;
document.getElementById("subMail").textContent = `http://mail.${domain}`;
document.getElementById("cardMail").href = `http://mail.${domain}`;
document.getElementById("subChat").textContent = `http://chat.${domain}`;
document.getElementById("cardChat").href = `http://chat.${domain}`;
document.getElementById("subWeb").textContent = `http://${domain}`;
document.getElementById("cardWeb").href = `http://${domain}`;
}
async function submitPairing(e) {
e.preventDefault();
const serverUrl = document.getElementById("serverUrl").value.trim();
const username = document.getElementById("pairUser").value.trim();
const password = document.getElementById("pairPass").value;
const btn = document.getElementById("btnPair");
btn.disabled = true;
btn.textContent = i18n[currentLang].pairing_in_progress;
try {
const res = await fetch("/api/pair", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ server_url: serverUrl, username, password })
});
const data = await res.json();
if (!res.ok) {
alert("Kopplung fehlgeschlagen: " + (data.error || "Serverfehler"));
btn.disabled = false;
btn.textContent = i18n[currentLang].btn_pair;
return;
}
fetchClientProfile();
} catch (err) {
alert("Netzwerkfehler: " + err);
btn.disabled = false;
btn.textContent = i18n[currentLang].btn_pair;
}
}
async function unpairClient() {
if (!confirm(i18n[currentLang].confirm_unpair)) return;
try {
await fetch("/api/unpair", { method: "POST" });
fetchClientProfile();
} catch (err) {
alert("Fehler beim Entkoppeln");
}
}

124
app/index.html Normal file
View File

@ -0,0 +1,124 @@
<!DOCTYPE html>
<html lang="de" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BenzCloud Client Desktop & Mobile</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header class="app-header">
<div class="header-left">
<div class="logo-icon">☁️</div>
<div class="logo-text">
<span class="brand-title">BenzCloud Client</span>
<span class="brand-badge">v1.0</span>
</div>
</div>
<div class="header-right">
<div class="lang-switcher">
<button id="langDE" class="btn-lang active" onclick="setLanguage('de')">DE</button>
<button id="langEN" class="btn-lang" onclick="setLanguage('en')">EN</button>
</div>
</div>
</header>
<main class="main-content">
<!-- PAIRING VIEW -->
<div id="pairingView" class="view-panel" style="display:none;">
<div class="card-box">
<div class="badge-pill" data-i18n="pair_badge">🔗 Client-Ersteinrichtung</div>
<h1 class="title" data-i18n="pair_title">Mit BenzCloud-Server verbinden</h1>
<p class="desc" data-i18n="pair_desc">Gib die lokale IP-Adresse deines BenzCloud-Servers und deine Anmeldedaten ein. Das Mesh-VPN (Nebula) und der DNS-Server konfigurieren sich automatisch.</p>
<form id="pairForm" onsubmit="submitPairing(event)">
<div class="form-group">
<label for="serverUrl" data-i18n="lbl_server_url">Server LAN-Adresse:</label>
<input type="text" id="serverUrl" required placeholder="z. B. http://192.168.0.5:8080" value="http://127.0.0.1:8080">
</div>
<div class="form-row">
<div class="form-group">
<label for="pairUser" data-i18n="lbl_user">Benutzername:</label>
<input type="text" id="pairUser" required placeholder="admin" value="admin">
</div>
<div class="form-group">
<label for="pairPass" data-i18n="lbl_pass">Passwort:</label>
<input type="password" id="pairPass" required placeholder="••••••••">
</div>
</div>
<button type="submit" class="btn-primary btn-block" id="btnPair" data-i18n="btn_pair">🚀 Gerät sicher koppeln &amp; verbinden</button>
</form>
</div>
</div>
<!-- CONNECTED COCKPIT -->
<div id="connectedView" class="view-panel" style="display:none;">
<div class="status-banner">
<div class="s-left">
<span class="pulse-dot"></span>
<div>
<h2 data-i18n="status_connected">Verbunden mit BenzCloud</h2>
<p class="s-domain" id="dispDomain">benzjeremy.de</p>
</div>
</div>
<div class="s-right">
<div class="tag-row">
<span class="badge-tag">Overlay IP: <strong id="dispOverlayIP">10.42.0.2</strong></span>
<span class="badge-tag">Lighthouse: <strong id="dispLighthouse">10.42.0.1</strong></span>
</div>
</div>
</div>
<h3 class="section-title" data-i18n="services_heading">Deine Enterprise-Dienste (Direktzugriff)</h3>
<div class="grid-services">
<a class="service-card" id="cardDrive" href="#" target="_blank">
<div class="s-icon">📁</div>
<div class="s-details">
<h4 data-i18n="card_drive_title">BenzCloud Drive</h4>
<p class="s-sub" id="subDrive">http://drive.domain</p>
<p class="s-desc" data-i18n="card_drive_desc">Dateien hochladen, teilen und synchronisieren.</p>
</div>
</a>
<a class="service-card" id="cardMail" href="#" target="_blank">
<div class="s-icon">📧</div>
<div class="s-details">
<h4 data-i18n="card_mail_title">BenzCloud Mail</h4>
<p class="s-sub" id="subMail">http://mail.domain</p>
<p class="s-desc" data-i18n="card_mail_desc">Internes Webmail &amp; Thunderbird-kompatibles Postfach.</p>
</div>
</a>
<a class="service-card" id="cardChat" href="#" target="_blank">
<div class="s-icon">💬</div>
<div class="s-details">
<h4 data-i18n="card_chat_title">BenzCloud Chat</h4>
<p class="s-sub" id="subChat">http://chat.domain</p>
<p class="s-desc" data-i18n="card_chat_desc">Echtzeit-Teamkommunikation &amp; Direktnachrichten.</p>
</div>
</a>
<a class="service-card" id="cardWeb" href="#" target="_blank">
<div class="s-icon">🌐</div>
<div class="s-details">
<h4 data-i18n="card_web_title">Webseiten &amp; Portal</h4>
<p class="s-sub" id="subWeb">http://domain</p>
<p class="s-desc" data-i18n="card_web_desc">Gehostete Firmen- und Team-Webseiten aufrufen.</p>
</div>
</a>
</div>
<div class="client-footer-actions">
<button class="btn-outline btn-sm" onclick="unpairClient()" data-i18n="btn_unpair">🔌 Entkoppeln / Gerät trennen</button>
</div>
</div>
</main>
<footer class="app-footer">
<span>BenzCloud Client © 2026 Jeremy Benz • GNU GPLv3 Lizenz • Nebula (MIT)</span>
<span class="pill-pre">Pre-Release / In aktiver Entwicklung</span>
</footer>
<script src="app.js"></script>
</body>
</html>

300
app/style.css Normal file
View File

@ -0,0 +1,300 @@
:root {
--bg-page: #0a0e17;
--bg-surface: #121826;
--bg-card: #182032;
--bg-hover: #1e293f;
--border-subtle: #243048;
--border-focus: #38bdf8;
--text-main: #f1f5f9;
--text-muted: #94a3b8;
--text-subtle: #64748b;
--accent-blue: #38bdf8;
--accent-blue-hover: #0284c7;
--accent-green: #34d399;
--accent-amber: #fbbf24;
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
--font-mono: "JetBrains Mono", monospace;
--radius-sm: 6px;
--radius-md: 12px;
--radius-lg: 16px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background-color: var(--bg-page);
color: var(--text-main);
font-family: var(--font-sans);
min-height: 100vh;
display: flex;
flex-direction: column;
}
.app-header {
background-color: var(--bg-surface);
border-bottom: 1px solid var(--border-subtle);
padding: 1rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.header-left {
display: flex;
align-items: center;
gap: 0.75rem;
}
.logo-icon { font-size: 1.5rem; }
.brand-title { font-weight: 700; font-size: 1.2rem; }
.brand-badge {
background-color: rgba(56, 189, 248, 0.15);
color: var(--accent-blue);
border: 1px solid rgba(56, 189, 248, 0.3);
font-size: 0.75rem;
padding: 0.15rem 0.45rem;
border-radius: var(--radius-sm);
font-weight: 600;
}
.lang-switcher {
display: flex;
background-color: var(--bg-card);
border-radius: var(--radius-sm);
overflow: hidden;
border: 1px solid var(--border-subtle);
}
.btn-lang {
background: transparent;
border: none;
color: var(--text-muted);
padding: 0.3rem 0.6rem;
font-size: 0.8rem;
font-weight: 600;
cursor: pointer;
}
.btn-lang.active {
background-color: var(--accent-blue);
color: #000;
}
.main-content {
flex: 1;
max-width: 900px;
width: 100%;
margin: 0 auto;
padding: 2rem 1.5rem;
}
.card-box {
background-color: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
padding: 2.5rem;
margin: 2rem auto;
max-width: 600px;
}
.badge-pill {
display: inline-block;
background-color: rgba(56, 189, 248, 0.15);
color: var(--accent-blue);
border: 1px solid rgba(56, 189, 248, 0.3);
font-size: 0.8rem;
font-weight: 600;
padding: 0.2rem 0.6rem;
border-radius: 9999px;
margin-bottom: 1rem;
}
.title {
font-size: 1.8rem;
font-weight: 800;
margin-bottom: 0.5rem;
}
.desc {
color: var(--text-muted);
margin-bottom: 2rem;
font-size: 0.95rem;
}
.form-group {
margin-bottom: 1.25rem;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
label {
display: block;
font-size: 0.85rem;
font-weight: 600;
margin-bottom: 0.35rem;
}
input[type="text"], input[type="password"] {
width: 100%;
background-color: var(--bg-card);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-sm);
color: var(--text-main);
padding: 0.75rem 1rem;
font-size: 0.95rem;
}
input[type="text"]:focus, input[type="password"]:focus {
outline: none;
border-color: var(--border-focus);
}
.btn-primary {
background-color: var(--accent-blue);
color: #04101d;
font-weight: 700;
border: none;
border-radius: var(--radius-sm);
padding: 0.85rem 1.5rem;
cursor: pointer;
}
.btn-primary:hover { background-color: var(--accent-blue-hover); }
.btn-block { width: 100%; }
.btn-sm { padding: 0.4rem 0.8rem; font-size: 0.8rem; }
.btn-outline {
background: transparent;
border: 1px solid var(--border-subtle);
color: var(--text-muted);
cursor: pointer;
border-radius: var(--radius-sm);
}
/* Connected View */
.status-banner {
background-color: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-md);
padding: 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.s-left {
display: flex;
align-items: center;
gap: 1rem;
}
.pulse-dot {
width: 12px;
height: 12px;
border-radius: 50%;
background-color: var(--accent-green);
box-shadow: 0 0 10px var(--accent-green);
}
.s-domain {
font-family: var(--font-mono);
color: var(--accent-blue);
font-size: 0.9rem;
}
.tag-row {
display: flex;
gap: 0.5rem;
}
.badge-tag {
background-color: var(--bg-card);
border: 1px solid var(--border-subtle);
padding: 0.35rem 0.7rem;
border-radius: var(--radius-sm);
font-size: 0.8rem;
font-family: var(--font-mono);
}
.section-title {
font-size: 1.25rem;
margin-bottom: 1rem;
font-weight: 700;
}
.grid-services {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.service-card {
background-color: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-md);
padding: 1.25rem;
display: flex;
gap: 1rem;
text-decoration: none;
color: var(--text-main);
transition: all 0.2s;
}
.service-card:hover {
border-color: var(--border-focus);
background-color: var(--bg-hover);
transform: translateY(-2px);
}
.s-icon { font-size: 2rem; }
.s-details h4 {
font-size: 1rem;
font-weight: 700;
margin-bottom: 0.2rem;
}
.s-sub {
font-family: var(--font-mono);
color: var(--accent-blue);
font-size: 0.75rem;
margin-bottom: 0.35rem;
}
.s-desc {
font-size: 0.8rem;
color: var(--text-muted);
}
.client-footer-actions {
display: flex;
justify-content: flex-end;
}
.app-footer {
border-top: 1px solid var(--border-subtle);
padding: 1rem 2rem;
font-size: 0.8rem;
color: var(--text-subtle);
display: flex;
justify-content: space-between;
align-items: center;
margin-top: auto;
}
.pill-pre {
background-color: rgba(251, 191, 36, 0.12);
color: var(--accent-amber);
border: 1px solid rgba(251, 191, 36, 0.3);
padding: 0.15rem 0.5rem;
border-radius: 9999px;
font-weight: 600;
font-size: 0.75rem;
}

4
favicon.svg Normal file
View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
<rect width="32" height="32" rx="8" fill="#0e121b"/>
<path d="M4 6h24v14H4V6zm0 16h24v2H4v-2zm8 3h8v1h-8v-1z" fill="#38bdf8"/>
</svg>

After

Width:  |  Height:  |  Size: 211 B

336
index.html Normal file
View File

@ -0,0 +1,336 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BenzCloud Client · Desktop &amp; Mobile Mesh-Cockpit</title>
<link rel="icon" type="image/svg+xml" href="favicon.svg">
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- Navigation Header -->
<header class="site-header">
<div class="nav-container">
<div class="nav-left">
<a href="https://benzjeremy.github.io/" class="back-root-btn">
<span>← benzjeremy.github.io</span>
</a>
<a href="index.html" class="brand-link">
<span class="brand-logo">💻</span>
<span class="brand-name">BenzCloud Client</span>
<span class="brand-ver">v1.0</span>
</a>
</div>
<nav class="main-nav">
<a href="app/">
<span class="lang-de">Cockpit</span>
<span class="lang-en" style="display:none;">Cockpit</span>
</a>
<a href="#features">
<span class="lang-de">Features</span>
<span class="lang-en" style="display:none;">Features</span>
</a>
<a href="#pairing">
<span class="lang-de">Pairing-Ablauf</span>
<span class="lang-en" style="display:none;">Pairing Flow</span>
</a>
<a href="#downloads">
<span class="lang-de">Downloads</span>
<span class="lang-en" style="display:none;">Downloads</span>
</a>
<a href="#about">
<span class="lang-de">Über das Projekt</span>
<span class="lang-en" style="display:none;">About</span>
</a>
<div class="lang-switch" role="group" aria-label="Sprachauswahl">
<button type="button" class="lang-btn active" id="btn-lang-de" onclick="setLang('de')">DE</button>
<button type="button" class="lang-btn" id="btn-lang-en" onclick="setLang('en')">EN</button>
</div>
<a href="https://github.com/benzjeremy/benzcloud-client" target="_blank" rel="noopener noreferrer" class="btn-github">
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.53 1.032 1.53 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/></svg>
<span>GitHub</span>
</a>
</nav>
</div>
</header>
<!-- Hero Section -->
<section class="hero-section">
<div class="badge-pill">
<span></span>
<span class="lang-de">Zero-Config Mesh-Client · 100% Datenschutz</span>
<span class="lang-en" style="display:none;">Zero-Config Mesh Client · 100% Privacy</span>
</div>
<h1 class="hero-title">
<span class="lang-de">Ihr persönlicher Zugang zur <span>BenzCloud</span></span>
<span class="lang-en" style="display:none;">Your Personal Gateway to <span>BenzCloud</span></span>
</h1>
<p class="hero-subtitle">
<span class="lang-de">Verbinden Sie PCs und Android-Smartphones vollautomatisch mit Ihrer privaten Cloud. Ein QR-Code oder Pairing-Token genügt Mesh-VPN, Host-Auflösung und Zugriffsrechte richten sich ohne technische Vorkenntnisse ein.</span>
<span class="lang-en" style="display:none;">Seamlessly connect PCs and Android devices to your private cloud suite. Scan a QR code or paste a pairing token — mesh VPN routing, host resolution, and services configure instantly.</span>
</p>
<div class="hero-cta">
<a href="app/" class="btn-primary">
<span class="lang-de">🚀 Client-Cockpit testen</span>
<span class="lang-en" style="display:none;">🚀 Test Client Cockpit</span>
</a>
<a href="#downloads" class="btn-secondary">
<span class="lang-de">📥 Downloads (PC & Android)</span>
<span class="lang-en" style="display:none;">📥 Downloads (PC & Android)</span>
</a>
</div>
</section>
<!-- Features Section -->
<section class="section" id="features">
<div class="section-header">
<h2 class="section-title">
<span class="lang-de">Highlights &amp; Funktionsumfang</span>
<span class="lang-en" style="display:none;">Highlights &amp; Capabilities</span>
</h2>
<p class="section-desc">
<span class="lang-de">Höchste Ergonomie und kompromissloser Schutz Ihrer Privatsphäre.</span>
<span class="lang-en" style="display:none;">Superior interaction ergonomics combined with uncompromising privacy.</span>
</p>
</div>
<div class="grid-3">
<div class="card">
<div class="card-icon">🔗</div>
<h3 class="card-title">
<span class="lang-de">One-Click QR-Pairing</span>
<span class="lang-en" style="display:none;">One-Click QR Pairing</span>
</h3>
<p class="card-desc">
<span class="lang-de">Kein manuelles Eintippen kryptischer IP-Adressen oder Zertifikate: Scannen Sie den QR-Code vom Server-Dashboard oder fügen Sie den Einmal-Token ein.</span>
<span class="lang-en" style="display:none;">Zero manual configuration of complex IP addresses or keys: Simply scan the QR code from the server dashboard or paste your pairing token.</span>
</p>
</div>
<div class="card">
<div class="card-icon">🚀</div>
<h3 class="card-title">
<span class="lang-de">Zentraler Service-Launcher</span>
<span class="lang-en" style="display:none;">Unified Service Launcher</span>
</h3>
<p class="card-desc">
<span class="lang-de">Direkter Schnellzugriff auf alle aktivierten Clouddienste: Dateitresor (Drive), Webmail, Mesh-Chat und gehostete Webprojekte aus einer Hand.</span>
<span class="lang-en" style="display:none;">Instant access to all enabled cloud services: Encrypted Drive, Webmail, Mesh Chat, and hosted Web projects with single-click launching.</span>
</p>
</div>
<div class="card">
<div class="card-icon">🛡️</div>
<h3 class="card-title">
<span class="lang-de">Unentziehbare Mesh-Rechte</span>
<span class="lang-en" style="display:none;">Immutable Mesh Privileges</span>
</h3>
<p class="card-desc">
<span class="lang-de">Garantierte Konnektivität: VPN- und DNS-Privilegien sind architektonisch unentziehbar verankert. Kein Account kann ausgesperrt werden.</span>
<span class="lang-en" style="display:none;">Guaranteed connectivity: VPN and DNS privileges are architecturally immutable, ensuring no user can be locked out from the network overlay.</span>
</p>
</div>
</div>
</section>
<!-- Pairing Section -->
<section class="section" id="pairing">
<div class="section-header">
<h2 class="section-title">
<span class="lang-de">In 3 Schritten verbunden</span>
<span class="lang-en" style="display:none;">Connected in 3 Easy Steps</span>
</h2>
<p class="section-desc">
<span class="lang-de">Dau-optimierter Verbindungsprozess ohne Router-Eingriffe oder Fachwissen.</span>
<span class="lang-en" style="display:none;">Frictionless pairing flow without router modifications or networking expertise.</span>
</p>
</div>
<div class="pairing-card">
<div>
<div class="pairing-step">
<div class="step-num">1</div>
<div class="step-content">
<h4>
<span class="lang-de">Server starten & Domain wählen</span>
<span class="lang-en" style="display:none;">Start Server & Choose Domain</span>
</h4>
<p>
<span class="lang-de">Im Server-Cockpit die gewünschte Wunschdomain (z. B. <code>meinefirma.local</code>) festlegen.</span>
<span class="lang-en" style="display:none;">Set your preferred domain (e.g. <code>mycompany.local</code>) in the server wizard.</span>
</p>
</div>
</div>
<div class="pairing-step">
<div class="step-num">2</div>
<div class="step-content">
<h4>
<span class="lang-de">Pairing-Code abrufen</span>
<span class="lang-en" style="display:none;">Retrieve Pairing Code</span>
</h4>
<p>
<span class="lang-de">Auf „Gerät koppeln“ klicken, um den QR-Code oder den sicheren 32-Byte-Token anzuzeigen.</span>
<span class="lang-en" style="display:none;">Click "Pair Device" to display the QR code or cryptographic 32-byte pairing token.</span>
</p>
</div>
</div>
<div class="pairing-step">
<div class="step-num">3</div>
<div class="step-content">
<h4>
<span class="lang-de">Client verbinden</span>
<span class="lang-en" style="display:none;">Connect Client</span>
</h4>
<p>
<span class="lang-de">Im Client einfügen oder scannen fertig! Mesh-VPN und DNS-Pfade konfigurieren sich selbst.</span>
<span class="lang-en" style="display:none;">Paste or scan in the client app — done! Mesh VPN and DNS pathways configure automatically.</span>
</p>
</div>
</div>
</div>
<div style="text-align: center; background: var(--bg-card); padding: 2rem; border-radius: var(--radius-md); border: 1px solid var(--border-subtle);">
<div style="font-size: 3.5rem; margin-bottom: 0.5rem;">📲</div>
<h4 style="color: var(--accent-primary); margin-bottom: 0.5rem;">One-Click Mesh Handshake</h4>
<p style="font-size: 0.85rem; color: var(--text-secondary);">End-to-End Encrypted via Noise Protocol</p>
</div>
</div>
</section>
<!-- Downloads Section -->
<section class="section" id="downloads">
<div class="section-header">
<h2 class="section-title">
<span class="lang-de">Pakete & Downloads</span>
<span class="lang-en" style="display:none;">Packages & Downloads</span>
</h2>
<p class="section-desc">
<span class="lang-de">Alle offiziellen Builds für Linux, Windows und Android.</span>
<span class="lang-en" style="display:none;">Official verified binaries for Linux, Windows, and Android.</span>
</p>
</div>
<div class="download-grid">
<div class="download-card">
<div class="download-icon">🐧</div>
<div class="download-title">Linux x86_64</div>
<div class="download-meta">Single Binary · ~10.6 MB</div>
<a href="https://raw.githubusercontent.com/benzjeremy/benzstore/content/linux/benzcloud-client/benzcloud-client_1.0" class="btn-download">
<span class="lang-de">Binary herunterladen</span>
<span class="lang-en" style="display:none;">Download Binary</span>
</a>
</div>
<div class="download-card">
<div class="download-icon">🪟</div>
<div class="download-title">Windows x86_64</div>
<div class="download-meta">Native Executable · ~11.3 MB</div>
<a href="https://raw.githubusercontent.com/benzjeremy/benzstore/content/windows/benzcloud-client/benzcloud-client_1.0.exe" class="btn-download">
<span class="lang-de">.exe herunterladen</span>
<span class="lang-en" style="display:none;">Download .exe</span>
</a>
</div>
<div class="download-card">
<div class="download-icon">🤖</div>
<div class="download-title">Android APK</div>
<div class="download-meta">Signed APK · ~17 KB</div>
<a href="https://raw.githubusercontent.com/benzjeremy/benzstore/content/android/benzcloud-client/benzcloud-client_1.0.apk" class="btn-download">
<span class="lang-de">APK herunterladen</span>
<span class="lang-en" style="display:none;">Download APK</span>
</a>
</div>
<div class="download-card">
<div class="download-icon">🏪</div>
<div class="download-title">BenzStore</div>
<div class="download-meta">One-Click Install & Updates</div>
<a href="https://benzjeremy.github.io/benzstore/" class="btn-download" style="background-color: var(--accent-primary); color: #000;">
<span class="lang-de">Im BenzStore öffnen</span>
<span class="lang-en" style="display:none;">Open in BenzStore</span>
</a>
</div>
</div>
</section>
<!-- About Section -->
<section class="section" id="about">
<div class="about-box">
<div class="about-header">
<img src="https://github.com/benzjeremy.png" alt="Jeremy Benz" class="author-avatar">
<div class="author-info">
<h3>Jeremy Benz (@benzjeremy)</h3>
<p>
<span class="lang-de">Open-Source-Entwickler & Architekt souveräner Software-Ökosysteme</span>
<span class="lang-en" style="display:none;">Open-Source Developer & Architect of Sovereign Software Ecosystems</span>
</p>
</div>
</div>
<p style="color: var(--text-secondary); font-size: 0.95rem; line-height: 1.6;">
<span class="lang-de">Der BenzCloud Client verbindet Endgeräte ohne Zwischenstationen direkt über verschlüsselte Peer-to-Peer-Tunnel mit privaten BenzCloud-Instanzen. Lizenziert unter der freien GNU General Public License v3.0 (GPL-3.0).</span>
<span class="lang-en" style="display:none;">The BenzCloud Client bridges personal devices directly to self-hosted instances over encrypted peer-to-peer tunnels. Licensed under the GNU General Public License v3.0 (GPL-3.0).</span>
</p>
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
<a href="https://github.com/benzjeremy/benzcloud-client/blob/main/LICENSE" target="_blank" rel="noopener noreferrer" style="color: var(--accent-primary); text-decoration: none; font-weight: 600; font-size: 0.9rem;">
⚖️ GNU GPLv3 Lizenz
</a>
<a href="https://github.com/benzjeremy/benzcloud-client" target="_blank" rel="noopener noreferrer" style="color: var(--accent-primary); text-decoration: none; font-weight: 600; font-size: 0.9rem;">
📦 GitHub Repository
</a>
</div>
</div>
</section>
<!-- Footer -->
<footer class="site-footer">
<div class="footer-container">
<div>
© 2026 Jeremy Benz · BenzCloud Ecosystem · GNU GPLv3
</div>
<div class="footer-links">
<a href="https://benzjeremy.github.io/impressum.html">
<span class="lang-de">Impressum</span>
<span class="lang-en" style="display:none;">Legal Notice</span>
</a>
<a href="https://benzjeremy.github.io/datenschutz.html">
<span class="lang-de">Datenschutz</span>
<span class="lang-en" style="display:none;">Privacy Policy</span>
</a>
<a href="https://benzjeremy.github.io/">
<span class="lang-de">Hauptseite</span>
<span class="lang-en" style="display:none;">Main Hub</span>
</a>
</div>
</div>
</footer>
<script>
function setLang(lang) {
localStorage.setItem('benzcloud_lang', lang);
document.documentElement.lang = lang;
const isDe = lang === 'de';
document.querySelectorAll('.lang-de').forEach(el => el.style.display = isDe ? '' : 'none');
document.querySelectorAll('.lang-en').forEach(el => el.style.display = isDe ? 'none' : '');
const btnDe = document.getElementById('btn-lang-de');
const btnEn = document.getElementById('btn-lang-en');
if (btnDe && btnEn) {
btnDe.classList.toggle('active', isDe);
btnEn.classList.toggle('active', !isDe);
}
}
document.addEventListener('DOMContentLoaded', () => {
const savedLang = localStorage.getItem('benzcloud_lang') || 'de';
setLang(savedLang);
});
</script>
</body>
</html>

389
style.css Normal file
View File

@ -0,0 +1,389 @@
:root {
--bg-page: #080a0f;
--bg-surface: #0e121b;
--bg-card: #141a26;
--bg-card-hover: #1a2232;
--border-subtle: rgba(255, 255, 255, 0.08);
--border-strong: rgba(255, 255, 255, 0.16);
--border-accent: rgba(56, 189, 248, 0.35);
--accent-primary: #38bdf8;
--accent-primary-hover: #7dd3fc;
--accent-glow: rgba(56, 189, 248, 0.25);
--accent-green: #10b981;
--accent-purple: #a855f7;
--text-primary: #f8fafc;
--text-secondary: #94a3b8;
--text-muted: #64748b;
--font-main: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
--font-mono: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
--radius-sm: 8px;
--radius-md: 14px;
--radius-lg: 20px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
font-family: var(--font-main);
background-color: var(--bg-page);
color: var(--text-primary);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* Header */
.site-header {
position: sticky;
top: 0;
z-index: 100;
background: rgba(8, 10, 15, 0.9);
backdrop-filter: blur(16px);
border-bottom: 1px solid var(--border-subtle);
}
.nav-container {
max-width: 1200px;
margin: 0 auto;
padding: 0.85rem 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.nav-left { display: flex; align-items: center; gap: 1.25rem; }
.back-root-btn {
color: var(--text-muted);
text-decoration: none;
font-size: 0.85rem;
transition: color 0.15s;
}
.back-root-btn:hover { color: var(--text-primary); }
.brand-link {
display: flex;
align-items: center;
gap: 0.6rem;
text-decoration: none;
color: var(--text-primary);
}
.brand-logo { font-size: 1.5rem; }
.brand-name { font-weight: 700; font-size: 1.15rem; }
.brand-ver {
font-size: 0.72rem;
background: rgba(56, 189, 248, 0.15);
color: var(--accent-primary);
padding: 0.15rem 0.45rem;
border-radius: var(--radius-sm);
font-family: var(--font-mono);
}
.main-nav { display: flex; align-items: center; gap: 1.25rem; }
.main-nav a {
color: var(--text-secondary);
text-decoration: none;
font-size: 0.9rem;
font-weight: 500;
transition: color 0.15s;
}
.main-nav a:hover { color: var(--accent-primary); }
.lang-switch {
display: flex;
background-color: var(--bg-card);
border-radius: var(--radius-sm);
border: 1px solid var(--border-subtle);
overflow: hidden;
}
.lang-btn {
background: transparent;
border: none;
color: var(--text-muted);
padding: 0.25rem 0.6rem;
font-size: 0.78rem;
font-weight: 600;
cursor: pointer;
}
.lang-btn.active {
background-color: var(--accent-primary);
color: #000;
}
.btn-github {
background-color: var(--bg-card);
border: 1px solid var(--border-subtle);
color: var(--text-primary) !important;
padding: 0.4rem 0.8rem;
border-radius: var(--radius-sm);
display: flex;
align-items: center;
gap: 0.4rem;
}
.btn-github:hover {
background-color: var(--bg-card-hover);
border-color: var(--border-accent);
}
/* Hero */
.hero-section {
padding: 5rem 1.5rem 4rem;
text-align: center;
max-width: 900px;
margin: 0 auto;
}
.badge-pill {
display: inline-flex;
align-items: center;
gap: 0.5rem;
background: rgba(56, 189, 248, 0.1);
border: 1px solid rgba(56, 189, 248, 0.25);
color: var(--accent-primary);
font-size: 0.82rem;
font-weight: 600;
padding: 0.35rem 0.9rem;
border-radius: 9999px;
margin-bottom: 1.5rem;
}
.hero-title {
font-size: 3rem;
font-weight: 800;
letter-spacing: -0.03em;
line-height: 1.15;
margin-bottom: 1.25rem;
}
.hero-title span {
color: var(--accent-primary);
text-shadow: 0 0 24px var(--accent-glow);
}
.hero-subtitle {
font-size: 1.15rem;
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: 2.25rem;
max-width: 720px;
margin-left: auto;
margin-right: auto;
}
.hero-cta {
display: flex;
justify-content: center;
gap: 1rem;
flex-wrap: wrap;
}
.btn-primary {
background-color: var(--accent-primary);
color: #000;
font-weight: 700;
font-size: 0.95rem;
padding: 0.8rem 1.6rem;
border-radius: var(--radius-sm);
text-decoration: none;
transition: all 0.2s;
box-shadow: 0 4px 14px var(--accent-glow);
}
.btn-primary:hover {
background-color: var(--accent-primary-hover);
transform: translateY(-2px);
}
.btn-secondary {
background-color: var(--bg-card);
border: 1px solid var(--border-subtle);
color: var(--text-primary);
font-weight: 600;
font-size: 0.95rem;
padding: 0.8rem 1.6rem;
border-radius: var(--radius-sm);
text-decoration: none;
transition: all 0.2s;
}
.btn-secondary:hover {
background-color: var(--bg-card-hover);
border-color: var(--border-accent);
transform: translateY(-2px);
}
/* Sections */
.section {
padding: 4.5rem 1.5rem;
max-width: 1200px;
margin: 0 auto;
width: 100%;
}
.section-header {
text-align: center;
max-width: 650px;
margin: 0 auto 3rem;
}
.section-title {
font-size: 2rem;
font-weight: 700;
margin-bottom: 0.75rem;
}
.section-desc {
color: var(--text-secondary);
font-size: 1rem;
}
/* Feature Grid */
.grid-3 {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 1.5rem;
}
.card {
background-color: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-md);
padding: 1.75rem;
transition: all 0.2s;
}
.card:hover {
background-color: var(--bg-card);
border-color: var(--border-accent);
transform: translateY(-3px);
}
.card-icon {
font-size: 2rem;
margin-bottom: 1rem;
display: inline-block;
}
.card-title {
font-size: 1.2rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
.card-desc {
color: var(--text-secondary);
font-size: 0.92rem;
line-height: 1.5;
}
/* Pairing Showcase */
.pairing-card {
background-color: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
padding: 2.5rem;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 2rem;
align-items: center;
}
.pairing-step {
display: flex;
gap: 1rem;
align-items: flex-start;
margin-bottom: 1.25rem;
}
.step-num {
background: rgba(56, 189, 248, 0.15);
color: var(--accent-primary);
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 0.9rem;
flex-shrink: 0;
}
.step-content h4 { font-size: 1rem; margin-bottom: 0.25rem; }
.step-content p { color: var(--text-secondary); font-size: 0.85rem; }
/* Downloads section */
.download-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
}
.download-card {
background-color: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-md);
padding: 2rem;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
.download-icon { font-size: 2.5rem; }
.download-title { font-size: 1.2rem; font-weight: 700; }
.download-meta {
font-size: 0.78rem;
color: var(--text-muted);
font-family: var(--font-mono);
}
.btn-download {
width: 100%;
background-color: var(--bg-card);
border: 1px solid var(--border-strong);
color: var(--text-primary);
padding: 0.65rem 1rem;
border-radius: var(--radius-sm);
text-decoration: none;
font-weight: 600;
font-size: 0.88rem;
transition: all 0.15s;
}
.btn-download:hover {
background-color: var(--accent-primary);
color: #000;
border-color: var(--accent-primary);
}
/* About / Open Source section */
.about-box {
background-color: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
padding: 2.5rem;
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.about-header { display: flex; align-items: center; gap: 1rem; }
.author-avatar {
width: 54px;
height: 54px;
border-radius: 50%;
border: 2px solid var(--accent-primary);
}
.author-info h3 { font-size: 1.25rem; font-weight: 700; }
.author-info p { color: var(--text-muted); font-size: 0.85rem; }
/* Footer */
.site-footer {
margin-top: auto;
border-top: 1px solid var(--border-subtle);
background-color: var(--bg-surface);
padding: 2rem 1.5rem;
}
.footer-container {
max-width: 1200px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 1rem;
font-size: 0.85rem;
color: var(--text-muted);
}
.footer-links { display: flex; gap: 1.25rem; }
.footer-links a {
color: var(--text-muted);
text-decoration: none;
transition: color 0.15s;
}
.footer-links a:hover { color: var(--accent-primary); }
@media (max-width: 768px) {
.hero-title { font-size: 2.2rem; }
.main-nav { display: none; }
.footer-container { flex-direction: column; text-align: center; }
}