Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 78f1659db4 | |||
| 935c263648 | |||
| 29ac96f892 | |||
| 4d3b7210b3 | |||
| 369bca2ef1 | |||
| 2fcc741f5e | |||
| 27722186d1 | |||
| 5710c74706 | |||
| cd27dfa27d | |||
| c4c7d42de4 | |||
| 71025b3d61 | |||
| f790a6adcc |
@@ -13,6 +13,7 @@
|
||||
"generate:flyer:png": "node ../scripts/generate-beta-flyer.mjs --png",
|
||||
"generate:flyer:all": "node ../scripts/generate-beta-flyer.mjs --all",
|
||||
"generate:flyer:setup": "playwright install chromium",
|
||||
"generate:sharepic": "node ../scripts/generate-sharepic.mjs",
|
||||
"translate:locales": "node ../scripts/translate-locales.mjs",
|
||||
"translate:flyer": "node ../scripts/translate-flyer.mjs",
|
||||
"validate:i18n": "node ../scripts/validate-i18n-keys.mjs"
|
||||
|
||||
@@ -5642,6 +5642,48 @@ html.theme-cupertino .events-scroll-container {
|
||||
border-color: rgba(255, 94, 91, 0.32);
|
||||
}
|
||||
|
||||
.mail-footer-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #94a3b8;
|
||||
text-decoration: none;
|
||||
background: rgba(56, 189, 248, 0.08);
|
||||
border: 1px solid rgba(56, 189, 248, 0.18);
|
||||
transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
|
||||
}
|
||||
|
||||
.mail-footer-badge:hover {
|
||||
color: #bae6fd;
|
||||
background: rgba(56, 189, 248, 0.14);
|
||||
border-color: rgba(56, 189, 248, 0.32);
|
||||
}
|
||||
|
||||
.knorrlabs-footer-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #94a3b8;
|
||||
text-decoration: none;
|
||||
background: rgba(139, 92, 246, 0.08);
|
||||
border: 1px solid rgba(139, 92, 246, 0.18);
|
||||
transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
|
||||
}
|
||||
|
||||
.knorrlabs-footer-badge:hover {
|
||||
color: #ddd6fe;
|
||||
background: rgba(139, 92, 246, 0.14);
|
||||
border-color: rgba(139, 92, 246, 0.32);
|
||||
}
|
||||
|
||||
.demo-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Coffee } from 'lucide-react'
|
||||
import { Coffee, Mail, Compass } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { PlausibleEvents, trackPlausibleEvent } from '../services/analytics.js'
|
||||
|
||||
@@ -15,17 +15,35 @@ export default function AppFooter() {
|
||||
·
|
||||
</span>
|
||||
<span className="app-version-footer__copyright">
|
||||
© 2026 KnorrLabs/
|
||||
<a
|
||||
href="mailto:moin@kapteins-daagbok.eu"
|
||||
onClick={() => trackPlausibleEvent(PlausibleEvents.FOOTER_LINK_CLICKED)}
|
||||
>
|
||||
Markus F.J. Busche
|
||||
</a>
|
||||
© 2026
|
||||
</span>
|
||||
<span className="app-version-footer__sep" aria-hidden="true">
|
||||
·
|
||||
</span>
|
||||
<a
|
||||
className="knorrlabs-footer-badge"
|
||||
href="https://dashy.elpatron.me/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => trackPlausibleEvent(PlausibleEvents.FOOTER_LINK_CLICKED)}
|
||||
>
|
||||
<Compass size={14} aria-hidden="true" />
|
||||
<span>KnorrLabs</span>
|
||||
</a>
|
||||
<span className="app-version-footer__sep" aria-hidden="true">
|
||||
·
|
||||
</span>
|
||||
<a
|
||||
className="mail-footer-badge"
|
||||
href="mailto:moin@kapteins-daagbok.eu"
|
||||
onClick={() => trackPlausibleEvent(PlausibleEvents.FOOTER_LINK_CLICKED)}
|
||||
>
|
||||
<Mail size={14} aria-hidden="true" />
|
||||
<span>moin@kapteins-daagbok.eu</span>
|
||||
</a>
|
||||
<span className="app-version-footer__sep" aria-hidden="true">
|
||||
·
|
||||
</span>
|
||||
<a
|
||||
className="kofi-footer-badge"
|
||||
href={KOFI_URL}
|
||||
|
||||
BIN
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 2.8 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 621 KiB |
@@ -0,0 +1,318 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Kapteins Daagbok — Sharepic (Portrait)</title>
|
||||
<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=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
width: 1080px;
|
||||
height: 1920px;
|
||||
font-family: 'Plus Jakarta Sans', sans-serif;
|
||||
color: #e2e8f0;
|
||||
background: #0f172a;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 100px 80px;
|
||||
background:
|
||||
radial-gradient(circle at 50% 10%, rgba(56, 189, 248, 0.18) 0%, transparent 45%),
|
||||
radial-gradient(circle at 50% 90%, rgba(134, 59, 255, 0.22) 0%, transparent 45%),
|
||||
linear-gradient(180deg, #090d16 0%, #111827 50%, #090d16 100%);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Subtle background grid pattern */
|
||||
body::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: linear-gradient(rgba(148, 163, 184, 0.03) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(148, 163, 184, 0.03) 1px, transparent 1px);
|
||||
background-size: 40px 40px;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* Outer border */
|
||||
.outer-border {
|
||||
position: absolute;
|
||||
inset: 40px;
|
||||
border: 1px solid rgba(148, 163, 184, 0.1);
|
||||
border-radius: 30px;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 30px;
|
||||
z-index: 2;
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 8px 24px rgba(56, 189, 248, 0.3));
|
||||
}
|
||||
|
||||
.title-group h1 {
|
||||
font-size: 64px;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.03em;
|
||||
color: #ffffff;
|
||||
line-height: 1.1;
|
||||
background: linear-gradient(135deg, #ffffff 60%, #94a3b8 100%);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.title-group p {
|
||||
font-size: 26px;
|
||||
color: #38bdf8;
|
||||
font-weight: 600;
|
||||
margin-top: 8px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 50px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 26px;
|
||||
line-height: 1.6;
|
||||
color: #cbd5e1;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.intro-text strong {
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.features-card {
|
||||
background: rgba(30, 41, 59, 0.45);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 24px;
|
||||
padding: 50px 60px;
|
||||
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.4);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.features-card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 24px;
|
||||
padding: 1px;
|
||||
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.02));
|
||||
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: #94a3b8;
|
||||
margin-bottom: 35px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.card-title::after {
|
||||
content: "";
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: rgba(148, 163, 184, 0.15);
|
||||
}
|
||||
|
||||
.badge-premium {
|
||||
background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
|
||||
color: #1e293b;
|
||||
font-size: 16px;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
padding: 6px 14px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.features-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 20px;
|
||||
font-size: 24px;
|
||||
line-height: 1.4;
|
||||
color: #cbd5e1;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
color: #38bdf8;
|
||||
font-size: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 3px;
|
||||
text-shadow: 0 0 10px rgba(56, 189, 248, 0.6);
|
||||
}
|
||||
|
||||
.bottom-section {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 50px;
|
||||
z-index: 2;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.cta-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.cta-badge {
|
||||
background: linear-gradient(135deg, #38bdf8 0%, #0284c7 100%);
|
||||
color: #0f172a;
|
||||
font-size: 32px;
|
||||
font-weight: 800;
|
||||
padding: 20px 45px;
|
||||
border-radius: 16px;
|
||||
letter-spacing: -0.02em;
|
||||
box-shadow: 0 10px 30px rgba(56, 189, 248, 0.25);
|
||||
}
|
||||
|
||||
.qr-code {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
background: #ffffff;
|
||||
padding: 10px;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.qr-code img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
footer {
|
||||
font-size: 18px;
|
||||
color: #64748b;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
footer strong {
|
||||
color: #94a3b8;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="outer-border"></div>
|
||||
|
||||
<div class="brand">
|
||||
<img class="logo" src="../../client/public/logo.png" alt="Kapteins Daagbok" />
|
||||
<div class="title-group">
|
||||
<h1>Kapteins Daagbok</h1>
|
||||
<p>Digitales Yacht-Logbuch</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<p class="intro-text">
|
||||
Führe dein Bordlogbuch modern & digital: Reisetage, GPS-Tracks, Crew- und Schiffsdaten —
|
||||
<strong>Ende-zu-Ende-verschlüsselt</strong>, als App installierbar und <strong>auch offline</strong> auf See nutzbar.
|
||||
</p>
|
||||
|
||||
<div class="features-card">
|
||||
<div class="card-title">Top Features <span class="badge-premium">Kostenlos & Werbefrei</span></div>
|
||||
<ul class="features-list">
|
||||
<li class="feature-item">
|
||||
<span class="feature-icon">✦</span>
|
||||
<span>Nautisches Logbuch-Format & Streckenstatistik</span>
|
||||
</li>
|
||||
<li class="feature-item">
|
||||
<span class="feature-icon">✦</span>
|
||||
<span>Offline-first PWA — läuft auf allen Smartphones & Tablets</span>
|
||||
</li>
|
||||
<li class="feature-item">
|
||||
<span class="feature-icon">✦</span>
|
||||
<span>Ende-zu-Ende Verschlüsselung (Zero-Knowledge)</span>
|
||||
</li>
|
||||
<li class="feature-item">
|
||||
<span class="feature-icon">✦</span>
|
||||
<span>Einfache passwortlose Passkey-Anmeldung</span>
|
||||
</li>
|
||||
<li class="feature-item">
|
||||
<span class="feature-icon">✦</span>
|
||||
<span>GPS-Track-Upload & automatische NMEA-Erfassung</span>
|
||||
</li>
|
||||
<li class="feature-item">
|
||||
<span class="feature-icon">✦</span>
|
||||
<span>Crew-Einladung zur gemeinsamen Logbuch-Arbeit</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bottom-section">
|
||||
<div class="cta-container">
|
||||
<div class="cta-badge">
|
||||
kapteins-daagbok.eu
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
<img src="assets/qr-kapteins-daagbok.eu.png" alt="QR Code" />
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<strong>Kapteins Daagbok</strong> ist ein werbefreies, privates Hobbyprojekt.
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,320 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Kapteins Daagbok — Sharepic</title>
|
||||
<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=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
width: 1200px;
|
||||
height: 630px;
|
||||
font-family: 'Plus Jakarta Sans', sans-serif;
|
||||
color: #e2e8f0;
|
||||
background: #0f172a;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 60px 80px;
|
||||
background:
|
||||
radial-gradient(circle at 90% 10%, rgba(56, 189, 248, 0.15) 0%, transparent 45%),
|
||||
radial-gradient(circle at 10% 90%, rgba(134, 59, 255, 0.18) 0%, transparent 45%),
|
||||
linear-gradient(165deg, #090d16 0%, #111827 50%, #090d16 100%);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Subtle background grid pattern */
|
||||
body::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: linear-gradient(rgba(148, 163, 184, 0.03) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(148, 163, 184, 0.03) 1px, transparent 1px);
|
||||
background-size: 40px 40px;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* Outer border */
|
||||
.outer-border {
|
||||
position: absolute;
|
||||
inset: 30px;
|
||||
border: 1px solid rgba(148, 163, 184, 0.1);
|
||||
border-radius: 20px;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
gap: 50px;
|
||||
}
|
||||
|
||||
.left-col {
|
||||
flex: 1.1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 4px 12px rgba(56, 189, 248, 0.3));
|
||||
}
|
||||
|
||||
.title-group h1 {
|
||||
font-size: 44px;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.03em;
|
||||
color: #ffffff;
|
||||
line-height: 1.1;
|
||||
background: linear-gradient(135deg, #ffffff 60%, #94a3b8 100%);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.title-group p {
|
||||
font-size: 18px;
|
||||
color: #38bdf8;
|
||||
font-weight: 600;
|
||||
margin-top: 4px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 20px;
|
||||
line-height: 1.6;
|
||||
color: #cbd5e1;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.intro-text strong {
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.cta-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.cta-badge {
|
||||
background: linear-gradient(135deg, #38bdf8 0%, #0284c7 100%);
|
||||
color: #0f172a;
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
padding: 14px 28px;
|
||||
border-radius: 12px;
|
||||
letter-spacing: -0.02em;
|
||||
box-shadow: 0 4px 20px rgba(56, 189, 248, 0.25);
|
||||
}
|
||||
|
||||
.qr-code {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: #ffffff;
|
||||
padding: 4px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.qr-code img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.right-col {
|
||||
flex: 0.9;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.features-card {
|
||||
background: rgba(30, 41, 59, 0.45);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 20px;
|
||||
padding: 35px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.features-card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 20px;
|
||||
padding: 1px;
|
||||
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.02));
|
||||
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.features-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 14px;
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
color: #cbd5e1;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
color: #38bdf8;
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 2px;
|
||||
text-shadow: 0 0 8px rgba(56, 189, 248, 0.6);
|
||||
}
|
||||
|
||||
.badge-premium {
|
||||
background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
|
||||
color: #1e293b;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
padding: 4px 10px;
|
||||
border-radius: 6px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: #94a3b8;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.card-title::after {
|
||||
content: "";
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: rgba(148, 163, 184, 0.15);
|
||||
}
|
||||
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 45px;
|
||||
left: 80px;
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
footer strong {
|
||||
color: #94a3b8;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="outer-border"></div>
|
||||
<div class="content-wrapper">
|
||||
<div class="left-col">
|
||||
<div class="brand">
|
||||
<img class="logo" src="../../client/public/logo.png" alt="Kapteins Daagbok" />
|
||||
<div class="title-group">
|
||||
<h1>Kapteins Daagbok</h1>
|
||||
<p>Digitales Yacht-Logbuch</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="intro-text">
|
||||
Führe dein Bordlogbuch modern & digital: Reisetage, GPS-Tracks, Crew- und Schiffsdaten —
|
||||
<strong>Ende-zu-Ende-verschlüsselt</strong>, als App installierbar und <strong>auch offline</strong> auf See nutzbar.
|
||||
</p>
|
||||
<div class="cta-container">
|
||||
<div class="cta-badge">
|
||||
kapteins-daagbok.eu
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
<img src="assets/qr-kapteins-daagbok.eu.png" alt="QR Code" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-col">
|
||||
<div class="features-card">
|
||||
<div class="card-title">Top Features <span class="badge-premium">Kostenlos & Werbefrei</span></div>
|
||||
<ul class="features-list">
|
||||
<li class="feature-item">
|
||||
<span class="feature-icon">✦</span>
|
||||
<span>Nautisches Logbuch-Format & Streckenstatistik</span>
|
||||
</li>
|
||||
<li class="feature-item">
|
||||
<span class="feature-icon">✦</span>
|
||||
<span>Offline-first PWA — läuft auf allen Smartphones & Tablets</span>
|
||||
</li>
|
||||
<li class="feature-item">
|
||||
<span class="feature-icon">✦</span>
|
||||
<span>Ende-zu-Ende Verschlüsselung (Zero-Knowledge)</span>
|
||||
</li>
|
||||
<li class="feature-item">
|
||||
<span class="feature-icon">✦</span>
|
||||
<span>Einfache passwortlose Passkey-Anmeldung</span>
|
||||
</li>
|
||||
<li class="feature-item">
|
||||
<span class="feature-icon">✦</span>
|
||||
<span>GPS-Track-Upload & automatische NMEA-Erfassung</span>
|
||||
</li>
|
||||
<li class="feature-item">
|
||||
<span class="feature-icon">✦</span>
|
||||
<span>Crew-Einladung zur gemeinsamen Logbuch-Arbeit</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<strong>Kapteins Daagbok</strong> ist ein werbefreies, privates Hobbyprojekt.
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
Executable
+95
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Generates the sharepic PNGs (landscape & portrait) from HTML files
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/generate-sharepic.mjs
|
||||
*/
|
||||
|
||||
import { execSync } from 'node:child_process'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url'
|
||||
import { createRequire } from 'node:module'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const repoRoot = resolve(__dirname, '..')
|
||||
const clientDir = resolve(repoRoot, 'client')
|
||||
const marketingDir = resolve(repoRoot, 'docs/marketing')
|
||||
|
||||
const require = createRequire(resolve(clientDir, 'package.json'))
|
||||
|
||||
function isMissingBrowserError(err) {
|
||||
const msg = err instanceof Error ? err.message : String(err)
|
||||
return msg.includes("Executable doesn't exist") || msg.includes('browserType.launch')
|
||||
}
|
||||
|
||||
async function ensurePlaywrightChromium(playwright) {
|
||||
try {
|
||||
const browser = await playwright.chromium.launch({ headless: true })
|
||||
await browser.close()
|
||||
return
|
||||
} catch (err) {
|
||||
if (!isMissingBrowserError(err)) throw err
|
||||
}
|
||||
|
||||
console.log('Playwright Chromium fehlt — installiere Browser (einmalig)…')
|
||||
execSync('npx playwright install chromium', {
|
||||
cwd: clientDir,
|
||||
stdio: 'inherit'
|
||||
})
|
||||
}
|
||||
|
||||
function loadPlaywright() {
|
||||
try {
|
||||
return require('playwright')
|
||||
} catch {
|
||||
console.error('Fehlende Abhängigkeit: "npm install -D playwright" in client/ ausführen.')
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
async function renderSharepic(browser, htmlName, pngName, width, height) {
|
||||
const htmlPath = resolve(marketingDir, htmlName)
|
||||
const pngPath = resolve(marketingDir, pngName)
|
||||
|
||||
console.log(`Generating sharepic (${width}x${height}) from ${htmlName}...`)
|
||||
|
||||
const context = await browser.newContext({
|
||||
viewport: { width, height },
|
||||
deviceScaleFactor: 2 // High-DPI for crisp text
|
||||
})
|
||||
const page = await context.newPage()
|
||||
|
||||
try {
|
||||
await page.goto(pathToFileURL(htmlPath).href, { waitUntil: 'networkidle' })
|
||||
await page.screenshot({
|
||||
path: pngPath,
|
||||
type: 'png'
|
||||
})
|
||||
console.log('Successfully wrote:', pngPath)
|
||||
} finally {
|
||||
await page.close()
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const playwright = loadPlaywright()
|
||||
await ensurePlaywrightChromium(playwright)
|
||||
|
||||
const browser = await playwright.chromium.launch({ headless: true })
|
||||
|
||||
try {
|
||||
// Landscape 1200x630
|
||||
await renderSharepic(browser, 'sharepic.html', 'kapteins-daagbok-sharepic.png', 1200, 630)
|
||||
|
||||
// Portrait 1080x1920
|
||||
await renderSharepic(browser, 'sharepic-portrait.html', 'kapteins-daagbok-sharepic-portrait.png', 1080, 1920)
|
||||
} finally {
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error('Error generating sharepics:', err)
|
||||
process.exit(1)
|
||||
})
|
||||
Reference in New Issue
Block a user