feat(web): initial bilingual landing page and client cockpit
This commit is contained in:
commit
35aacf8315
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
benzcloud-client
|
||||
benzcloud-client.exe
|
||||
android/
|
||||
data/
|
||||
17
404.html
Normal file
17
404.html
Normal 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
160
app/app.js
Normal 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
124
app/index.html
Normal 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 & 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 & 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 & 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 & 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
300
app/style.css
Normal 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
4
favicon.svg
Normal 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
336
index.html
Normal 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 & 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 & Funktionsumfang</span>
|
||||
<span class="lang-en" style="display:none;">Highlights & 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
389
style.css
Normal 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; }
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user