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:
Lucas Orth
2026-08-25 07:26:15 +02:00
commit 681fc1649a
19 changed files with 3096 additions and 0 deletions

400
app/public/style.css Normal file
View 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); }