SendSecret: Erstimport und Umstellung auf Tag-Deployment
Wrapper-Frontend vor cryptgeon: der Browser verschluesselt lokal, per Mail geht nur der Link raus. Fuer das Deployment nach den Konventionen aus DEPLOY.md hergerichtet: - docker-compose.yml mit festem Projekt- und Container-Namen, kein ports-Mapping, Healthcheck als Deploy-Gate. cryptgeon und redis liegen im internen Netz, nur app haengt im Web-Netz. - cryptgeon von latest auf 2.9.3 gepinnt. Das ist derselbe Stand, den latest bisher geliefert hat; 2.6.2 existiert nicht. - /healthz in server.js, vor dem Catch-all-Proxy registriert. - Dockerfile auf npm ci mit Lockfile und non-root umgestellt. - .gitea/workflows/deploy.yml: Build und Syntaxpruefung vor dem Deploy, .env aus dem Repo-Secret DOTENV. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
171
app/public/index.html
Normal file
171
app/public/index.html
Normal file
@@ -0,0 +1,171 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Passwort sicher übermitteln – Lucas Orth</title>
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<link rel="icon" href="https://lucas-orth.de/wp-content/uploads/2023/06/favicon-scaled.webp">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Manrope:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
|
||||
<!-- ── Header ─────────────────────────────────── -->
|
||||
<header class="header">
|
||||
<nav class="nav container">
|
||||
<a href="https://lucas-orth.de" target="_blank" rel="noopener">
|
||||
<img
|
||||
src="https://lucas-orth.de/wp-content/uploads/2023/06/Lucas-Orth-black-high-res_ergebnis-scaled-e1687249204543.webp"
|
||||
alt="Lucas Orth"
|
||||
class="logo"
|
||||
>
|
||||
</a>
|
||||
<a href="https://lucas-orth.de/kontakt" target="_blank" rel="noopener" class="btn btn--primary">
|
||||
Kontakt
|
||||
</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main style="display:contents">
|
||||
|
||||
<!-- ── Hero ───────────────────────────────────── -->
|
||||
<section class="hero">
|
||||
<div class="container">
|
||||
<div class="hero__inner">
|
||||
|
||||
<!-- Form State -->
|
||||
<div id="state-form">
|
||||
<p class="eyebrow">Sicher & verschlüsselt</p>
|
||||
<h1 class="hero-title">
|
||||
Passwort sicher<br>
|
||||
<span class="accent">übermitteln.</span>
|
||||
</h1>
|
||||
<p class="lead">
|
||||
Geben Sie Ihr Passwort oder Ihre Zugangsdaten ein.
|
||||
Der Link wird Ende-zu-Ende-verschlüsselt direkt an
|
||||
Lucas Orth gesendet – Sie müssen nichts weiter tun.
|
||||
</p>
|
||||
|
||||
<form id="form" class="hero-form" novalidate>
|
||||
<div class="field">
|
||||
<label for="sender-name" class="label">Ihr Name <span style="color:var(--color-error)">*</span></label>
|
||||
<input
|
||||
id="sender-name"
|
||||
type="text"
|
||||
class="input"
|
||||
placeholder="z. B. Max Mustermann"
|
||||
required
|
||||
autocomplete="name"
|
||||
spellcheck="false"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="text" class="label">Ihr Passwort / Zugangsdaten <span style="color:var(--color-error)">*</span></label>
|
||||
<textarea
|
||||
id="text"
|
||||
class="textarea"
|
||||
placeholder="z. B. Benutzername: max@beispiel.de Passwort: mein-passwort-123"
|
||||
rows="6"
|
||||
required
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div id="state-error" hidden>
|
||||
<div class="alert alert--error" role="alert">
|
||||
<span id="error-msg"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="submit" type="submit" class="btn btn--primary">
|
||||
<span id="submit-label">Jetzt sicher senden</span>
|
||||
</button>
|
||||
|
||||
<p class="hint">Ende-zu-Ende verschlüsselt · Einmalig abrufbar · Kein Speichern</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Success State -->
|
||||
<div id="state-success" hidden>
|
||||
<div class="success-icon" aria-hidden="true">✓</div>
|
||||
<p class="eyebrow">Erfolgreich übermittelt</p>
|
||||
<h2 class="hero-title">
|
||||
Passwort wurde<br>
|
||||
<span class="accent">übermittelt.</span>
|
||||
</h2>
|
||||
<p class="lead">
|
||||
Ihr Passwort wurde sicher verschlüsselt und direkt an Lucas Orth
|
||||
weitergeleitet. Sie müssen nichts weiter tun.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── Steps Section ──────────────────────────── -->
|
||||
<section class="section-steps">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<p class="eyebrow">So funktioniert es</p>
|
||||
<h2 class="section-title">In drei Schritten übermittelt.</h2>
|
||||
<p class="lead">
|
||||
Kein Account nötig, keine versteckten Schritte –
|
||||
einfach eingeben und absenden.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="steps-grid">
|
||||
<div class="step-card">
|
||||
<span class="step-num">01</span>
|
||||
<h3 class="step-title">Text eingeben</h3>
|
||||
<p class="step-text">
|
||||
Tragen Sie Ihr Passwort oder Ihre Zugangsdaten in das Textfeld ein.
|
||||
</p>
|
||||
</div>
|
||||
<div class="step-card">
|
||||
<span class="step-num">02</span>
|
||||
<h3 class="step-title">Sicher senden</h3>
|
||||
<p class="step-text">
|
||||
Mit einem Klick wird der Text verschlüsselt und der Link
|
||||
automatisch an Lucas Orth gesendet.
|
||||
</p>
|
||||
</div>
|
||||
<div class="step-card">
|
||||
<span class="step-num">03</span>
|
||||
<h3 class="step-title">Fertig</h3>
|
||||
<p class="step-text">
|
||||
Der Link ist einmalig abrufbar und löscht sich nach dem
|
||||
ersten Öffnen automatisch.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<!-- ── Footer ─────────────────────────────────── -->
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer__inner">
|
||||
<span>© 2026 Lucas Orth – Webdesign & IT-Betreuung</span>
|
||||
<div class="footer__links">
|
||||
<a href="https://lucas-orth.de/datenschutz" target="_blank" rel="noopener">Datenschutz</a>
|
||||
<a href="https://lucas-orth.de/impressum" target="_blank" rel="noopener">Impressum</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
<script src="/customer.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
174
app/public/link.html
Normal file
174
app/public/link.html
Normal file
@@ -0,0 +1,174 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Link generieren – Lucas Orth</title>
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<link rel="icon" href="https://lucas-orth.de/wp-content/uploads/2023/06/favicon-scaled.webp">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Manrope:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
|
||||
<!-- ── Header ─────────────────────────────────── -->
|
||||
<header class="header">
|
||||
<nav class="nav container">
|
||||
<a href="https://lucas-orth.de" target="_blank" rel="noopener">
|
||||
<img
|
||||
src="https://lucas-orth.de/wp-content/uploads/2023/06/Lucas-Orth-black-high-res_ergebnis-scaled-e1687249204543.webp"
|
||||
alt="Lucas Orth"
|
||||
class="logo"
|
||||
>
|
||||
</a>
|
||||
<a href="https://lucas-orth.de/kontakt" target="_blank" rel="noopener" class="btn btn--primary">
|
||||
Kontakt
|
||||
</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main style="display:contents">
|
||||
|
||||
<!-- ── Hero ───────────────────────────────────── -->
|
||||
<section class="hero">
|
||||
<div class="container">
|
||||
<div class="hero__inner">
|
||||
|
||||
<!-- Form State -->
|
||||
<div id="state-form">
|
||||
<p class="eyebrow">Für interne Nutzung</p>
|
||||
<h1 class="hero-title">
|
||||
Sicheren Link<br>
|
||||
<span class="accent">erstellen.</span>
|
||||
</h1>
|
||||
<p class="lead">
|
||||
Text eingeben und einen einmalig abrufbaren, verschlüsselten
|
||||
Link generieren. Der Link wird nicht automatisch versendet.
|
||||
</p>
|
||||
|
||||
<form id="form" class="hero-form" novalidate>
|
||||
<div class="field">
|
||||
<label for="text" class="label">Geheimtext</label>
|
||||
<textarea
|
||||
id="text"
|
||||
class="textarea"
|
||||
placeholder="Geheimtext eingeben …"
|
||||
rows="6"
|
||||
required
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div id="state-error" hidden>
|
||||
<div class="alert alert--error" role="alert">
|
||||
<span id="error-msg"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="submit" type="submit" class="btn btn--primary">
|
||||
<span id="submit-label">Link erstellen</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Result State -->
|
||||
<div id="state-result" hidden>
|
||||
<p class="eyebrow">Link erstellt</p>
|
||||
<h2 class="hero-title">
|
||||
Bereit zum<br>
|
||||
<span class="accent">Versenden.</span>
|
||||
</h2>
|
||||
<p class="lead">
|
||||
Einmalig abrufbar – nach dem ersten Öffnen wird der Link
|
||||
automatisch gelöscht.
|
||||
</p>
|
||||
|
||||
<div class="hero-form">
|
||||
<div class="field">
|
||||
<label for="link-output" class="label">Ihr sicherer Link</label>
|
||||
<div class="link-row">
|
||||
<input
|
||||
id="link-output"
|
||||
type="text"
|
||||
class="input input--mono"
|
||||
readonly
|
||||
aria-label="Generierter Link"
|
||||
>
|
||||
<button id="copy-btn" type="button" class="btn btn--secondary">
|
||||
<span id="copy-label">Kopieren</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn--ghost" onclick="location.reload()">
|
||||
Neuen Link erstellen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── Steps Section ──────────────────────────── -->
|
||||
<section class="section-steps">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<p class="eyebrow">So funktioniert es</p>
|
||||
<h2 class="section-title">Link erstellen & teilen.</h2>
|
||||
<p class="lead">
|
||||
Ende-zu-Ende verschlüsselt – der Server sieht niemals den Klartext.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="steps-grid">
|
||||
<div class="step-card">
|
||||
<span class="step-num">01</span>
|
||||
<h3 class="step-title">Text eingeben</h3>
|
||||
<p class="step-text">
|
||||
Passwort oder Geheimtext ins Formular eingeben.
|
||||
</p>
|
||||
</div>
|
||||
<div class="step-card">
|
||||
<span class="step-num">02</span>
|
||||
<h3 class="step-title">Link kopieren</h3>
|
||||
<p class="step-text">
|
||||
Den generierten Link mit dem Kopieren-Button in die
|
||||
Zwischenablage übernehmen.
|
||||
</p>
|
||||
</div>
|
||||
<div class="step-card">
|
||||
<span class="step-num">03</span>
|
||||
<h3 class="step-title">Einmalig abrufbar</h3>
|
||||
<p class="step-text">
|
||||
Nach dem ersten Öffnen löscht sich der Inhalt automatisch
|
||||
und unwiderruflich.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<!-- ── Footer ─────────────────────────────────── -->
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer__inner">
|
||||
<span>© 2026 Lucas Orth – Webdesign & IT-Betreuung</span>
|
||||
<div class="footer__links">
|
||||
<a href="https://lucas-orth.de/datenschutz" target="_blank" rel="noopener">Datenschutz</a>
|
||||
<a href="https://lucas-orth.de/impressum" target="_blank" rel="noopener">Impressum</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
<script src="/link.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
400
app/public/style.css
Normal file
400
app/public/style.css
Normal file
@@ -0,0 +1,400 @@
|
||||
/* ── Reset ───────────────────────────────────────────────── */
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
/* ── Design Tokens (1:1 domaincheck.lucas-orth.de) ───────── */
|
||||
:root {
|
||||
--color-accent: #1E40AF;
|
||||
--color-accent-hover: #1E378A;
|
||||
--color-ink: #111827;
|
||||
--color-ink-muted: #6B7280;
|
||||
--color-ink-light: #9CA3AF;
|
||||
--color-surface: #FFFFFF;
|
||||
--color-surface-offset: #FAFAFA; /* footer bg */
|
||||
--color-surface-section: #F3F4F6; /* hero + steps section bg */
|
||||
--color-border: #E5E7EB;
|
||||
--color-border-strong: #D1D5DB; /* step numbers */
|
||||
--color-success: #16A34A;
|
||||
--color-success-bg: #F0FDF4;
|
||||
--color-success-border: #BBF7D0;
|
||||
--color-error: #DC2626;
|
||||
--color-error-bg: #FEF2F2;
|
||||
--color-error-border: #FECACA;
|
||||
|
||||
--font-heading: 'Space Grotesk', sans-serif;
|
||||
--font-body: 'Manrope', sans-serif;
|
||||
|
||||
--radius: 4px;
|
||||
--shadow-card: 0 1px 4px 0 rgba(0,0,0,0.07), 0 1px 2px -1px rgba(0,0,0,0.05);
|
||||
--transition: 180ms;
|
||||
}
|
||||
|
||||
/* ── Base ────────────────────────────────────────────────── */
|
||||
html {
|
||||
font-family: var(--font-body);
|
||||
font-size: 16px;
|
||||
color: var(--color-ink);
|
||||
background: var(--color-surface-section);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a { text-decoration: none; color: inherit; }
|
||||
|
||||
/* ── Page ────────────────────────────────────────────────── */
|
||||
.page {
|
||||
min-height: 100dvh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* ── Container (max-w-5xl = 1024px, px-6 = 24px) ────────── */
|
||||
.container {
|
||||
max-width: 1024px;
|
||||
margin: 0 auto;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
/* ── Header ──────────────────────────────────────────────── */
|
||||
.header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 40;
|
||||
background: var(--color-surface);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
height: 64px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* Logo: h-[45px] wie im Domainchecker */
|
||||
.logo {
|
||||
height: 45px;
|
||||
width: auto;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ── Hero Section ────────────────────────────────────────── */
|
||||
/* bg-surface-section border-b border-border, py-20 md:py-28 */
|
||||
.hero {
|
||||
background: var(--color-surface-section);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
padding: 80px 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.hero { padding: 112px 0; }
|
||||
}
|
||||
|
||||
.hero__inner { max-width: 672px; } /* max-w-xl ≈ max-w-2xl für form */
|
||||
|
||||
/* ── Eyebrow ─────────────────────────────────────────────── */
|
||||
/* text-sm font-semibold tracking-widest uppercase text-accent mb-4 */
|
||||
.eyebrow {
|
||||
font-family: var(--font-body);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-accent);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* ── Hero Title ──────────────────────────────────────────── */
|
||||
/* text-4xl md:text-6xl leading-tight mb-4 */
|
||||
.hero-title {
|
||||
font-family: var(--font-heading);
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
color: var(--color-ink);
|
||||
line-height: 1.15;
|
||||
margin-bottom: 16px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.hero-title .accent { color: var(--color-accent); }
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.hero-title { font-size: 60px; line-height: 1.1; }
|
||||
}
|
||||
|
||||
/* ── Lead Text ───────────────────────────────────────────── */
|
||||
/* text-lg mb-10 max-w-xl */
|
||||
.lead {
|
||||
font-size: 18px;
|
||||
color: var(--color-ink-muted);
|
||||
line-height: 1.7;
|
||||
margin-bottom: 40px;
|
||||
max-width: 512px;
|
||||
}
|
||||
|
||||
/* ── Hero Form ───────────────────────────────────────────── */
|
||||
/* max-w-2xl = 672px */
|
||||
.hero-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
max-width: 672px;
|
||||
}
|
||||
|
||||
.field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
/* Textarea: h-14 equivalent stacked, border-btn focus */
|
||||
.textarea,
|
||||
.input {
|
||||
width: 100%;
|
||||
padding: 12px 20px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius);
|
||||
font-family: var(--font-body);
|
||||
font-size: 16px;
|
||||
color: var(--color-ink);
|
||||
background: var(--color-surface);
|
||||
transition: border-color var(--transition), box-shadow var(--transition);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
resize: vertical;
|
||||
min-height: 130px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.textarea::placeholder,
|
||||
.input::placeholder {
|
||||
color: var(--color-ink-light);
|
||||
}
|
||||
|
||||
.textarea:focus,
|
||||
.input:focus {
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: 0 0 0 2px rgba(30, 64, 175, 0.15);
|
||||
}
|
||||
|
||||
.input--mono {
|
||||
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
color: var(--color-ink-muted);
|
||||
background: #F9FAFB;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* ── Hint ────────────────────────────────────────────────── */
|
||||
/* text-xs text-ink-light mt-4 */
|
||||
.hint {
|
||||
font-size: 12px;
|
||||
color: var(--color-ink-light);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* ── Buttons ─────────────────────────────────────────────── */
|
||||
/* px-4 py-3 font-semibold text-base rounded-btn transition */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 12px 16px;
|
||||
border-radius: var(--radius);
|
||||
font-family: var(--font-body);
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
text-decoration: none;
|
||||
transition: background-color var(--transition), opacity var(--transition);
|
||||
white-space: nowrap;
|
||||
line-height: 1;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
|
||||
.btn--primary {
|
||||
background: var(--color-accent);
|
||||
color: #FAFAFA;
|
||||
}
|
||||
.btn--primary:hover:not(:disabled) {
|
||||
background: var(--color-accent-hover);
|
||||
}
|
||||
|
||||
.btn--secondary {
|
||||
background: var(--color-surface);
|
||||
color: var(--color-ink);
|
||||
border: 1px solid var(--color-border-strong);
|
||||
}
|
||||
.btn--secondary:hover:not(:disabled) { background: #F9FAFB; }
|
||||
|
||||
.btn--ghost {
|
||||
background: transparent;
|
||||
color: var(--color-ink-muted);
|
||||
font-weight: 500;
|
||||
}
|
||||
.btn--ghost:hover:not(:disabled) {
|
||||
background: var(--color-border);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.btn--copied {
|
||||
background: var(--color-success-bg) !important;
|
||||
color: var(--color-success) !important;
|
||||
border-color: var(--color-success-border) !important;
|
||||
}
|
||||
|
||||
/* ── Alert ───────────────────────────────────────────────── */
|
||||
.alert {
|
||||
padding: 11px 14px;
|
||||
border-radius: var(--radius);
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.alert--error {
|
||||
background: var(--color-error-bg);
|
||||
color: var(--color-error);
|
||||
border: 1px solid var(--color-error-border);
|
||||
}
|
||||
|
||||
/* ── Link Row ────────────────────────────────────────────── */
|
||||
.link-row { display: flex; gap: 8px; align-items: stretch; }
|
||||
.link-row .input { flex: 1; min-width: 0; }
|
||||
|
||||
/* ── Success State ───────────────────────────────────────── */
|
||||
.success-icon {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
background: var(--color-success-bg);
|
||||
color: var(--color-success);
|
||||
border: 1px solid var(--color-success-border);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* ── Steps Section ───────────────────────────────────────── */
|
||||
/* bg-surface-section py-20 – gleicher Hintergrund wie Hero, weiße Karten */
|
||||
.section-steps {
|
||||
background: var(--color-surface-section);
|
||||
padding: 80px 0;
|
||||
}
|
||||
|
||||
.section-steps .lead { margin-bottom: 0; }
|
||||
|
||||
/* Section heading: text-3xl md:text-4xl */
|
||||
.section-title {
|
||||
font-family: var(--font-heading);
|
||||
font-size: 30px;
|
||||
font-weight: 700;
|
||||
color: var(--color-ink);
|
||||
line-height: 1.2;
|
||||
margin-bottom: 12px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.section-title { font-size: 36px; }
|
||||
}
|
||||
|
||||
/* mb-12 */
|
||||
.section-header { margin-bottom: 48px; }
|
||||
|
||||
/* grid grid-cols-1 md:grid-cols-3 gap-6 */
|
||||
.steps-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.steps-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
/* bg-white rounded-card border border-border p-7 shadow-card */
|
||||
.step-card {
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow-card);
|
||||
padding: 28px;
|
||||
}
|
||||
|
||||
/* text-4xl font-semibold text-border-strong leading-none block mb-4 */
|
||||
.step-num {
|
||||
display: block;
|
||||
font-family: var(--font-heading);
|
||||
font-size: 36px;
|
||||
font-weight: 600;
|
||||
color: var(--color-border-strong);
|
||||
line-height: 1;
|
||||
margin-bottom: 16px;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
/* font-heading text-lg text-ink mb-2 */
|
||||
.step-title {
|
||||
font-family: var(--font-heading);
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--color-ink);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* text-sm text-ink-muted leading-relaxed */
|
||||
.step-text {
|
||||
font-size: 14px;
|
||||
color: var(--color-ink-muted);
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
/* ── Footer ──────────────────────────────────────────────── */
|
||||
/* border-t border-border bg-surface-offset py-8 */
|
||||
.footer {
|
||||
background: var(--color-surface-offset);
|
||||
border-top: 1px solid var(--color-border);
|
||||
padding: 32px 0;
|
||||
flex-shrink: 0;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.footer__inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 12px;
|
||||
color: var(--color-ink-light);
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.footer__inner { flex-direction: column; text-align: center; }
|
||||
}
|
||||
|
||||
.footer__links { display: flex; gap: 20px; }
|
||||
|
||||
.footer a {
|
||||
color: var(--color-ink-light);
|
||||
text-decoration: none;
|
||||
transition: color var(--transition);
|
||||
}
|
||||
.footer a:hover { color: var(--color-accent); }
|
||||
Reference in New Issue
Block a user