generated from knso/webklar-preview-template
- ui-texte.js: 74 Bedientexte je Sprache (uebersetzt + lektoriert), t() in demo.html; am PC bleibt alles deutsch, am Handy Sprache der Person, RTL fuer ar/fa/ur/ps, verbundene Schriften ohne Sperrsatz - Servermeldungen am Handy nach Code in der Bediensprache - Seitenkopf "Fuer Sie kostenlos ueber ..." statt "2 Analysen gratis" - Turnstile passt auf schmale Handys (Abstaende, compact unter 350 px) - Knopf fuer Mitarbeitende eindeutig: "Zugang dauerhaft auf ein Handy geben" Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1838 lines
89 KiB
HTML
1838 lines
89 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Gratis-Demo: Behördenbrief online erklären lassen | BehördenKlar</title>
|
||
<meta name="description" content="Testen Sie BehördenKlar direkt im Browser: Brief fotografieren, hochladen, in einfacher Sprache erklärt bekommen. 2 Analysen gratis, ohne Konto.">
|
||
<meta name="keywords" content="Amtsbrief, Behördenbrief, verständlich, einfache Sprache, Jobcenter, Ausländerbehörde, Warteliste">
|
||
<meta name="author" content="BehördenKlar">
|
||
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1">
|
||
<meta name="theme-color" content="#1d4e89">
|
||
<meta name="apple-mobile-web-app-title" content="BehördenKlar">
|
||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||
<meta property="og:locale:alternate" content="tr_TR">
|
||
<meta property="og:locale:alternate" content="ar_AR">
|
||
<meta property="og:locale:alternate" content="en_US">
|
||
<meta property="og:locale:alternate" content="uk_UA">
|
||
<meta property="og:locale:alternate" content="ru_RU">
|
||
<meta property="og:locale:alternate" content="pl_PL">
|
||
<link rel="manifest" href="/manifest.json">
|
||
<meta name="google-site-verification" content="6o1sIKmK7WSloQrdTs2q4WtQsCUBB-bRa24gNXzVeFc">
|
||
<meta name="msvalidate.01" content="...">
|
||
<meta name="apple-app-site-association" content="...">
|
||
<meta name="app-links" content="...">
|
||
<meta name="target-audience" content="Alle, die schwierige Post bekommen — Privatpersonen, Selbständige, Menschen mit Deutsch als Fremdsprache">
|
||
<meta name="accessibility" content="WCAG 2.1 AA, Vorlesen, einfache Sprache, mehrsprachig">
|
||
<meta name="topics" content="civic-tech, legal-tech, accessibility, education, language-simplification, vulnerable-populations">
|
||
<meta name="target-organizations" content="Beratungsstellen, Jobcenter, Caritas, Diakonie, VHS, Migrationsberatung">
|
||
<meta name="problem-statement" content="Amtsdeutsch und Vertragssprache sind unnötig schwer. BehördenKlar macht daraus Klartext mit Frist und To-do-Liste.">
|
||
<link rel="canonical" href="https://behoerdenklar.de/demo">
|
||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||
<meta property="og:site_name" content="BehördenKlar">
|
||
<meta property="og:type" content="website">
|
||
<meta property="og:locale" content="de_DE">
|
||
<meta property="og:url" content="https://behoerdenklar.de/demo">
|
||
<meta property="og:title" content="Probieren Sie es aus: Ihr Behördenbrief, einfach erklärt">
|
||
<meta property="og:description" content="Direkt im Browser testen — 2 Gratis-Analysen, ohne Konto. Brief fotografieren, Klartext lesen.">
|
||
<meta property="og:image" content="https://behoerdenklar.de/vorschau.png">
|
||
<meta property="og:image:width" content="1200">
|
||
<meta property="og:image:height" content="630">
|
||
<meta name="twitter:card" content="summary_large_image">
|
||
<style>
|
||
:root {
|
||
--papier: #faf7f0;
|
||
--papier-dunkel: #f1ecdf;
|
||
--tinte: #21211d;
|
||
--grau: #6b675e;
|
||
--amtsblau: #1d4e89;
|
||
--stempelrot: #b3372b;
|
||
--marker: #ffe66b;
|
||
--gruen: #2e7d4f;
|
||
--rot: #b3372b;
|
||
--gelb: #a06b00;
|
||
color-scheme: light;
|
||
}
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
html { scroll-behavior: smooth; }
|
||
body {
|
||
font-family: Georgia, 'Times New Roman', serif;
|
||
background:
|
||
repeating-linear-gradient(0deg, transparent 0 34px, rgba(33, 33, 29, 0.025) 34px 35px),
|
||
var(--papier);
|
||
color: var(--tinte);
|
||
line-height: 1.7;
|
||
font-size: 17px;
|
||
}
|
||
.mono { font-family: 'Courier New', ui-monospace, monospace; letter-spacing: 0.02em; }
|
||
.wrap { max-width: 720px; margin: 0 auto; padding: 0 20px 72px; }
|
||
/* Schriften mit verbundenen Buchstaben (Arabisch, Devanagari …): kein
|
||
Buchstabenabstand und keine Schreibmaschine — sonst reißen Wörter auseinander */
|
||
html.verbundene-schrift .formular-kopf, html.verbundene-schrift .betreff-label,
|
||
html.verbundene-schrift h2, html.verbundene-schrift .karten-label {
|
||
font-family: inherit; letter-spacing: 0; text-transform: none;
|
||
}
|
||
@media (max-width: 420px) {
|
||
/* Cloudflare-Prüfung ist fest 300 px breit — sonst ragt sie aus dem Kasten */
|
||
.wrap { padding: 0 14px 56px; }
|
||
.antrag { padding: 22px 14px; }
|
||
}
|
||
|
||
.briefkopf {
|
||
display: flex; justify-content: space-between; align-items: baseline;
|
||
padding: 20px 0 12px; border-bottom: 2px solid var(--tinte);
|
||
font-size: 14px;
|
||
}
|
||
.briefkopf .absender { font-weight: 700; font-size: 16px; }
|
||
.briefkopf .absender a { color: inherit; text-decoration: none; }
|
||
.briefkopf .az { color: var(--grau); font-size: 13px; }
|
||
|
||
header { padding: 40px 0 8px; }
|
||
.betreff-label {
|
||
font-size: 13px; color: var(--grau); text-transform: uppercase;
|
||
letter-spacing: 0.14em; margin-bottom: 10px;
|
||
}
|
||
h1 {
|
||
font-size: clamp(30px, 6vw, 44px);
|
||
line-height: 1.15; font-weight: 700; letter-spacing: -0.01em;
|
||
}
|
||
.markiert {
|
||
background: linear-gradient(104deg, transparent 2%, var(--marker) 4%, var(--marker) 96%, transparent 98%);
|
||
padding: 0 6px; box-decoration-break: clone; -webkit-box-decoration-break: clone;
|
||
}
|
||
.anrede { margin-top: 20px; font-size: 18px; max-width: 580px; }
|
||
|
||
section { margin-top: 44px; }
|
||
h2 {
|
||
font-size: 15px; text-transform: uppercase; letter-spacing: 0.14em;
|
||
color: var(--grau); font-weight: 400; margin-bottom: 20px;
|
||
display: flex; align-items: center; gap: 12px;
|
||
}
|
||
h2::after { content: ''; flex: 1; border-top: 1px solid rgba(33,33,29,0.25); }
|
||
h2 .nr { font-family: 'Courier New', ui-monospace, monospace; }
|
||
|
||
.antrag {
|
||
background: #fff; border: 2px solid var(--tinte);
|
||
box-shadow: 7px 8px 0 rgba(33, 33, 29, 0.9);
|
||
padding: 28px 26px; position: relative;
|
||
}
|
||
.formular-kopf {
|
||
font-family: 'Courier New', ui-monospace, monospace;
|
||
font-size: 13px; text-transform: uppercase; letter-spacing: 0.1em;
|
||
color: var(--grau); margin-bottom: 10px;
|
||
}
|
||
.cta {
|
||
display: inline-block; width: 100%; padding: 17px 30px;
|
||
background: var(--amtsblau); color: #fff; text-decoration: none;
|
||
font-family: inherit; font-size: 18px; font-weight: 700; text-align: center;
|
||
border: 2px solid var(--tinte);
|
||
box-shadow: 5px 5px 0 var(--tinte);
|
||
transition: transform 0.12s ease, box-shadow 0.12s ease;
|
||
cursor: pointer;
|
||
}
|
||
.cta:hover:not(:disabled) { transform: translate(2px, 2px); box-shadow: 3px 3px 0 var(--tinte); }
|
||
.cta:disabled { opacity: 0.45; cursor: not-allowed; }
|
||
|
||
.konsens {
|
||
display: flex; gap: 12px; align-items: flex-start;
|
||
font-size: 15px; color: var(--grau); margin: 14px 0 18px;
|
||
}
|
||
.konsens input { width: 22px; height: 22px; margin-top: 4px; flex-shrink: 0; accent-color: var(--amtsblau); }
|
||
.konsens a { color: var(--amtsblau); }
|
||
|
||
.klein { font-size: 13.5px; color: var(--grau); margin-top: 12px; }
|
||
.klein a { color: var(--grau); }
|
||
|
||
.status { text-align: center; padding: 26px 10px; display: none; }
|
||
.status .gross { font-size: 20px; font-weight: 700; }
|
||
.status .sub { color: var(--grau); margin-top: 6px; font-size: 15px; }
|
||
.sanduhr {
|
||
display: inline-block; font-size: 34px;
|
||
animation: drehen 2.4s linear infinite;
|
||
}
|
||
@keyframes drehen { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
||
|
||
.fehlerbox {
|
||
display: none; margin-top: 16px;
|
||
border: 2px solid var(--stempelrot); background: #fdf1ef;
|
||
color: #7c2018; padding: 14px 16px; font-size: 16px;
|
||
}
|
||
|
||
.email-teil { display: none; margin-top: 16px; }
|
||
.wl-input {
|
||
width: 100%; padding: 15px 16px; font-size: 17px; font-family: inherit;
|
||
border: 2px solid var(--tinte); background: #fff; color: var(--tinte);
|
||
margin-bottom: 12px;
|
||
}
|
||
.wl-input::placeholder { color: #9a958a; }
|
||
|
||
/* Ergebnis */
|
||
#ergebnis { display: none; }
|
||
.anruf-satz { border-inline-start: 3px solid var(--amtsblau); background: #fff; padding: 6px 12px; margin: 10px 0; }
|
||
.anruf-de { font-weight: 700; margin: 0; }
|
||
.anruf-bed { color: var(--grau); font-size: 15px; margin: 4px 0 0; }
|
||
.kontakt-zeile { margin: 6px 0; font-size: 16px; }
|
||
.az-wert { font-family: 'Courier New', ui-monospace, monospace; font-weight: 700; background: var(--marker); padding: 1px 6px; }
|
||
#ergAnrufKontakt .cta { display: inline-block; width: auto; margin: 4px 0 8px; }
|
||
.brief-karte {
|
||
background: #fff; border: 1px solid rgba(33, 33, 29, 0.28);
|
||
padding: 22px 24px; position: relative; margin-top: 22px;
|
||
box-shadow: 4px 5px 0 rgba(33, 33, 29, 0.08);
|
||
}
|
||
.karten-label {
|
||
position: absolute; top: -12px; left: 16px;
|
||
font-family: 'Courier New', ui-monospace, monospace; font-size: 12px;
|
||
text-transform: uppercase; letter-spacing: 0.1em;
|
||
background: var(--gruen); color: var(--papier); padding: 3px 10px;
|
||
}
|
||
.karten-label.neutral { background: var(--tinte); }
|
||
.ampelzeile {
|
||
margin-top: 14px; padding-top: 12px;
|
||
border-top: 1px dashed rgba(33, 33, 29, 0.3);
|
||
font-size: 16px; font-weight: 700;
|
||
}
|
||
.todo { padding: 8px 0 8px 2px; border-bottom: 1px dotted rgba(33,33,29,0.3); font-size: 16.5px; }
|
||
.todo::before { content: '☐'; margin-inline-end: 10px; color: var(--amtsblau); font-size: 18px; }
|
||
details { border-bottom: 1px solid rgba(33, 33, 29, 0.2); padding: 12px 4px; }
|
||
summary { font-weight: 700; cursor: pointer; font-size: 16.5px; list-style: none; }
|
||
summary::-webkit-details-marker { display: none; }
|
||
details p { margin-top: 8px; color: var(--grau); font-size: 15.5px; }
|
||
|
||
.stempel {
|
||
display: inline-block; padding: 7px 14px;
|
||
border: 2.5px solid var(--stempelrot); color: var(--stempelrot);
|
||
font-family: 'Courier New', ui-monospace, monospace; font-weight: 700;
|
||
font-size: 14px; letter-spacing: 0.1em; text-transform: uppercase;
|
||
transform: rotate(-4deg); opacity: 0.9;
|
||
}
|
||
.hinweis-recht {
|
||
margin-top: 18px; font-size: 14px; color: var(--grau);
|
||
border-top: 1px dashed rgba(33,33,29,0.3); padding-top: 12px;
|
||
}
|
||
|
||
/* Warteliste-Formular (wie Startseite) */
|
||
.wl-form { display: flex; flex-direction: column; gap: 12px; margin-top: 4px; }
|
||
.bonus-zeile {
|
||
margin: 14px 0; padding: 12px 16px;
|
||
background: linear-gradient(104deg, transparent 1%, var(--marker) 3%, var(--marker) 97%, transparent 99%);
|
||
font-size: 16px;
|
||
}
|
||
/* Handy <-> PC */
|
||
.cta-zwei { background: #fff; color: var(--amtsblau); font-size: 16.5px; padding: 13px 20px; margin-top: 12px; }
|
||
.verbunden {
|
||
display: none; margin-bottom: 16px; padding: 12px 16px;
|
||
background: #eaf1fa; border: 2px solid var(--amtsblau); font-size: 16px;
|
||
}
|
||
.qr-box { display: none; margin-top: 16px; padding: 18px; background: #fff; border: 2px dashed var(--amtsblau); text-align: center; }
|
||
.qr-box .qr { width: 290px; max-width: 100%; aspect-ratio: 1; height: auto; margin: 10px auto; }
|
||
.qr-box .qr svg { width: 100%; height: 100%; display: block; }
|
||
.qr-box ol { text-align: start; margin: 8px auto 0; max-width: 420px; padding-inline-start: 22px; font-size: 15.5px; }
|
||
.qr-box .zustand { margin-top: 12px; font-weight: 700; font-size: 16.5px; }
|
||
.qr-box .abbrechen { margin-top: 10px; background: none; border: none; color: var(--grau); text-decoration: underline; font-family: inherit; font-size: 14.5px; cursor: pointer; }
|
||
.umschalter { display: none; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
|
||
.umschalter button {
|
||
font-family: inherit; font-size: 15px; padding: 6px 14px; cursor: pointer;
|
||
border: 1.5px solid var(--tinte); background: #fff; color: var(--tinte);
|
||
}
|
||
.umschalter button[aria-pressed="true"] { background: var(--tinte); color: var(--papier); }
|
||
.link-knopf { background: none; border: none; color: var(--amtsblau); font-family: inherit; font-size: inherit; text-decoration: underline; cursor: pointer; padding: 0; }
|
||
footer { margin-top: 48px; font-size: 14.5px; color: var(--grau); }
|
||
footer a { color: var(--grau); }
|
||
</style>
|
||
<script type="application/ld+json">
|
||
{
|
||
"@context": "https://schema.org",
|
||
"@type": "SoftwareApplication",
|
||
"name": "BehördenKlar",
|
||
"description": "KI erklärt schwierige Briefe von Ämtern, Versicherungen, Vermietern und Banken in einfacher Sprache — mit Fristen und Handlungsanleitung",
|
||
"applicationCategory": "AccessibilityApplication",
|
||
"offers": {
|
||
"@type": "Offer",
|
||
"price": "0",
|
||
"priceCurrency": "EUR"
|
||
},
|
||
"inLanguage": ["de", "tr", "ar", "en", "uk", "ru", "pl", "fa", "ro", "fr"],
|
||
"featureList": [
|
||
"Amtsbriefe fotografieren und erklären lassen",
|
||
"Klartext in einfacher Sprache",
|
||
"Übersetzung in über 25 Sprachen",
|
||
"Automatische Erinnerungen für Fristen",
|
||
"Fertige Antworten generieren",
|
||
"Vorlesen der Inhalte",
|
||
"Keine Anmeldung erforderlich"
|
||
],
|
||
"targetGroup": "Alle, die schwierige offizielle Post bekommen — Privatpersonen und Selbständige",
|
||
"author": {
|
||
"@type": "Organization",
|
||
"name": "BehördenKlar"
|
||
}
|
||
}
|
||
</script>
|
||
</head>
|
||
<body>
|
||
<div class="wrap">
|
||
|
||
<div class="briefkopf">
|
||
<span class="absender"><a href="index.html">📬 BehördenKlar</a> <span style="font-weight:400;font-size:.62em;opacity:.6;letter-spacing:.04em;">by Webklar</span></span>
|
||
<span class="az mono" id="kopfAz">Formular D-01 · Gratis-Demo</span>
|
||
</div>
|
||
|
||
<header>
|
||
<p class="betreff-label" data-t="betreff">Betreff</p>
|
||
<h1><span data-t="titel1">Probieren Sie es aus —</span><br><span class="markiert" data-t="titel2">mit Ihrem eigenen Brief.</span></h1>
|
||
<p class="anrede">
|
||
<span data-t="anrede">Kein Konto. Keine App-Installation. Laden Sie ein Foto Ihres
|
||
Behördenbriefs hoch — nach ein bis zwei Minuten lesen Sie ihn in einfacher
|
||
Sprache.</span> <strong id="anredeAngebot" data-t="anredeGratis">2 Analysen gratis.</strong>
|
||
</p>
|
||
</header>
|
||
|
||
<section id="demo">
|
||
<h2><span class="nr">§ 1</span> <span data-t="abschnittHochladen">Brief hochladen</span></h2>
|
||
<div class="antrag">
|
||
<p class="formular-kopf" data-t="formKopfGratis">Brief erklären lassen · 2 kostenlose Analysen</p>
|
||
|
||
<p class="verbunden" id="verbundenBanner"></p>
|
||
|
||
<div id="eingabe">
|
||
<p id="kaufOk" style="display: none; background: #e7f4ea; border: 1px solid #b7dfc1; color: #1a7f37; padding: 12px 14px; border-radius: 8px; margin-bottom: 14px; font-weight: 600;">
|
||
✅ Kauf erfolgreich! Ihr Guthaben wurde aktiviert.
|
||
</p>
|
||
<p id="guthabenBanner" class="bonus-zeile" style="display: none;">
|
||
<span id="guthabenText">💳 Ihr Guthaben:</span> <strong id="guthabenZahl">0</strong> <span data-t="bannerRest">Analysen — laden Sie den nächsten Brief hoch.</span>
|
||
<br><button type="button" class="link-knopf" id="guthabenHandyKnopf" style="font-size: 14.5px;">📱 Auch auf dem Handy nutzen</button>
|
||
</p>
|
||
<div class="qr-box" id="guthabenQrBox">
|
||
<strong>Guthaben aufs Handy holen</strong>
|
||
<div class="qr" id="guthabenQr"></div>
|
||
<p class="klein" style="margin-top: 0;">Mit der Handy-Kamera scannen. Danach können Sie Briefe auch direkt am Handy erklären lassen.</p>
|
||
</div>
|
||
<p style="margin-bottom: 14px; font-size: 15.5px;">
|
||
🌐 <label for="sprachWahl" data-t="spracheLabel">Erklärung in dieser Sprache:</label>
|
||
<select id="sprachWahl" style="font-family: inherit; font-size: 15.5px; padding: 5px 8px; border: 1.5px solid var(--tinte); background: #fff; margin-inline-start: 6px;"></select>
|
||
</p>
|
||
|
||
<label class="konsens">
|
||
<input type="checkbox" id="konsens">
|
||
<span>
|
||
<span data-t="konsens">Ich bin einverstanden, dass das Foto dieses Briefs zur Erklärung
|
||
an den Server von BehördenKlar und an den KI-Dienst Anthropic
|
||
(USA) geschickt wird. Briefe können sehr persönliche Angaben
|
||
enthalten, zum Beispiel zu Gesundheit, Herkunft oder Aufenthalt.
|
||
BehördenKlar speichert den Brief nicht. Anthropic löscht ihn in
|
||
der Regel nach 30 Tagen und nutzt ihn nicht zum Training.</span>
|
||
<a href="datenschutz.html" target="_blank" data-t="datenschutzLink">Datenschutzerklärung</a>
|
||
</span>
|
||
</label>
|
||
|
||
<div id="emailTeil" class="email-teil">
|
||
<p style="margin-bottom: 10px;">
|
||
🎉 <strong>Ihre erste Analyse ist verbraucht.</strong> Für die
|
||
zweite geben Sie bitte Ihre E-Mail-Adresse ein — so stellen wir
|
||
sicher, dass jeder genau 2 Gratis-Analysen bekommt.
|
||
</p>
|
||
<input class="wl-input" type="email" id="email" placeholder="Ihre E-Mail-Adresse" autocomplete="email">
|
||
<p class="klein" style="margin: 0 0 12px;">
|
||
Die Adresse wird nur verschlüsselt (als Prüfsumme) gespeichert,
|
||
um mitzuzählen — Sie bekommen dadurch keine E-Mails von uns.
|
||
</p>
|
||
</div>
|
||
|
||
<input type="file" id="datei" accept="image/jpeg,image/png,application/pdf" style="display: none;">
|
||
<button class="cta" id="startKnopf" data-t="startKnopf">📷 Brief auswählen & erklären lassen</button>
|
||
<button class="cta cta-zwei" id="handyKnopf" type="button" style="display: none;">📱 Lieber mit dem Handy fotografieren</button>
|
||
<div class="qr-box" id="handyBox">
|
||
<strong>Mit dem Handy fotografieren</strong>
|
||
<div class="qr" id="handyQr"></div>
|
||
<ol>
|
||
<li>Handy-Kamera auf den Code richten und den Link öffnen.</li>
|
||
<li>Am Handy den Brief fotografieren.</li>
|
||
<li>Die Erklärung erscheint hier <em>und</em> auf dem Handy.</li>
|
||
</ol>
|
||
<label class="konsens" id="handyDeutschZeile" style="justify-content: center; margin: 14px 0 0;">
|
||
<input type="checkbox" id="handyDeutsch" checked>
|
||
<span>Hier am Bildschirm immer auf Deutsch zeigen, am Handy in der Sprache der Person</span>
|
||
</label>
|
||
<p class="zustand" id="handyZustand">⏳ Warte auf das Handy …</p>
|
||
<p class="klein" style="margin-top: 6px;">Verschlüsselt: Den Schlüssel kennt nur dieser QR-Code. Der Code gilt 15 Minuten.</p>
|
||
<button type="button" class="abbrechen" id="handyAbbrechen">Abbrechen</button>
|
||
</div>
|
||
|
||
<div class="cf-turnstile" data-sitekey="0x4AAAAAAESi2p41B915R4hW" data-action="turnstile-spin-v1" style="margin-top: 14px;"></div>
|
||
<script>
|
||
// Lokaler Test (wrangler dev): offizieller Turnstile-Testschlüssel, der immer durchlässt
|
||
if (/^(localhost|127\.0\.0\.1)$/.test(location.hostname)) {
|
||
document.querySelector('.cf-turnstile').setAttribute('data-sitekey', '1x00000000000000000000AA');
|
||
}
|
||
// Sehr schmale Handys (< 350 px): kompakte Cloudflare-Prüfung
|
||
if (window.innerWidth < 350) document.querySelector('.cf-turnstile').setAttribute('data-size', 'compact');
|
||
</script>
|
||
|
||
<p class="klein" data-t="tippFoto">
|
||
Geht mit Foto (JPG/PNG) oder PDF, max. 5 MB. Tipp: gutes Licht,
|
||
Brief von oben, ganzer Text im Bild.
|
||
</p>
|
||
<p class="klein">
|
||
<strong data-t="tippHandyTitel">📱 Am Handy:</strong> <span data-t="tippHandy">Wählen Sie „Galerie" oder „Dateien",
|
||
wenn Sie den Brief schon fotografiert haben. Tippen Sie auf
|
||
„Kamera", öffnet sich die Kamera-App Ihres Handys — und die fragt
|
||
nach ihren eigenen Berechtigungen. Diese Abfrage kommt von
|
||
Ihrem Handy, nicht von uns: Diese Seite greift weder auf
|
||
Kamera noch auf Mikrofon zu.</span>
|
||
</p>
|
||
|
||
</div>
|
||
|
||
<div class="status" id="status">
|
||
<span class="sanduhr">⏳</span>
|
||
<p class="gross">Ihr Brief wird erklärt …</p>
|
||
<p class="sub" data-t="statusDauer">Das dauert ein bis zwei Minuten. Bitte lassen Sie die Seite geöffnet.</p>
|
||
</div>
|
||
|
||
<div class="fehlerbox" id="fehler"></div>
|
||
|
||
<details id="codeTeil" style="margin-top: 16px; border-top: 1px solid rgba(33, 33, 29, 0.2);">
|
||
<summary data-t="codeFrage">🤝 Sie haben einen Code von einer Beratungsstelle?</summary>
|
||
<p data-t="codeText">Dann ist BehördenKlar für Sie kostenlos. Geben Sie den Code hier ein:</p>
|
||
<input class="wl-input" id="codeFeld" placeholder="z. B. MBE-K7QX" autocapitalize="characters" autocomplete="off" spellcheck="false" style="margin-top: 10px; text-transform: uppercase;">
|
||
<button class="cta" id="codeKnopf" type="button" data-t="codeKnopf">Code einlösen</button>
|
||
</details>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="kaufBlock" style="display: none;">
|
||
<h2><span class="nr">§ 2</span> Guthaben kaufen & sofort nutzen</h2>
|
||
<div class="antrag">
|
||
<p class="formular-kopf">Im Browser nutzen · ohne App Store</p>
|
||
<h3 style="font-size: 24px; line-height: 1.3;">Guthaben kaufen — sofort loslegen</h3>
|
||
<p style="margin-top: 10px;">
|
||
Sie müssen nicht erst testen: Kaufen Sie direkt Guthaben und nutzen Sie
|
||
BehördenKlar hier im Browser — auf jedem Gerät, ohne App. Ihr Guthaben
|
||
bleibt gespeichert und verfällt nicht.
|
||
</p>
|
||
|
||
<div id="paketListe" style="display: flex; flex-direction: column; gap: 12px; margin-top: 18px;"></div>
|
||
<p style="margin-top: 16px; background: #eef4fb; border-inline-start: 4px solid var(--amtsblau); padding: 12px 14px; font-size: 15.5px;">
|
||
📧 <strong>Bitte geben Sie beim Bezahlen Ihre E-Mail-Adresse an.</strong>
|
||
Sie bekommen danach einen persönlichen Zugangs-Link — damit nutzen Sie
|
||
Ihr Guthaben auf <strong>jedem Gerät</strong>: Handy, Tablet, Computer.
|
||
Ohne diesen Link ist Ihr Guthaben nur in diesem einen Browser
|
||
gespeichert und wäre weg, wenn Sie den Verlauf löschen.
|
||
</p>
|
||
|
||
<label class="konsens" style="margin-top: 18px;">
|
||
<input type="checkbox" id="kaufKonsens">
|
||
<span>
|
||
Ich verlange ausdrücklich, dass die Analysen sofort nach dem Kauf zur
|
||
Verfügung stehen, und bestätige, dass mein Widerrufsrecht damit
|
||
erlischt. Ich habe die <a href="agb.html" target="_blank">AGB</a> und
|
||
die <a href="widerruf.html" target="_blank">Widerrufsbelehrung</a>
|
||
gelesen.
|
||
</span>
|
||
</label>
|
||
|
||
<div class="fehlerbox" id="kaufFehler" style="display: none; margin-top: 12px;"></div>
|
||
|
||
<p class="klein" style="margin-top: 12px;">
|
||
🔒 Sichere Bezahlung über Stripe — wir sehen Ihre Kartendaten nie.
|
||
Keine Umsatzsteuer (§ 19 UStG). Guthaben verfällt nicht.
|
||
</p>
|
||
<p class="klein" style="margin-top: 10px;">
|
||
Anbieter: <strong>Webklar GbR</strong> — BehördenKlar ist ein Angebot von
|
||
Webklar. Auf Ihrer Kontoabrechnung erscheint deshalb
|
||
<strong>„Webklar"</strong>.
|
||
</p>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="ergebnis">
|
||
<h2><span class="nr">§ 3</span> <span data-t="ergebnisTitel">Ihr Brief in Klartext</span></h2>
|
||
<div class="umschalter" id="sprachUmschalter" role="group" aria-label="Sprache der Erklärung"></div>
|
||
|
||
<div class="brief-karte">
|
||
<span class="karten-label neutral" id="ergBrieftyp" dir="auto" style="font-family: inherit; letter-spacing: 0; text-transform: none;">Brief</span>
|
||
<p style="margin-top: 6px;"><strong id="ergKern" dir="auto"></strong></p>
|
||
<p class="ampelzeile" id="ergAmpel" style="display: none;"></p>
|
||
</div>
|
||
|
||
<div class="brief-karte" id="ergKalenderKarte" style="display: none;">
|
||
<span class="karten-label" id="ergKalenderLabel">Nicht vergessen</span>
|
||
<div id="ergKalenderText" style="margin-top: 6px;"></div>
|
||
<button class="cta" id="kalenderKnopf" type="button" style="margin-top: 12px;" data-t="kalenderKnopf">📅 In meinen Kalender eintragen</button>
|
||
<p class="klein" id="ergKalenderHinweis" data-t="kalenderHinweis">
|
||
Ihr Handy erinnert Sie 3 Tage und 1 Tag vorher.
|
||
Der Eintrag landet nur in Ihrem eigenen Kalender — wir speichern nichts.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="brief-karte">
|
||
<span class="karten-label" data-t="labelEinfach">Einfach erklärt</span>
|
||
<p id="ergErklaerung" dir="auto" style="white-space: pre-line;"></p>
|
||
</div>
|
||
|
||
<div class="brief-karte" id="ergChecklisteKarte" style="display: none;">
|
||
<span class="karten-label neutral" data-t="labelTun">Das müssen Sie tun</span>
|
||
<div id="ergCheckliste"></div>
|
||
</div>
|
||
|
||
<div class="brief-karte" id="ergAnrufKarte" style="display: none;">
|
||
<span class="karten-label neutral" data-t="labelAnrufen">Anrufen</span>
|
||
<div id="ergAnrufKontakt" style="margin-top: 6px;"></div>
|
||
<div id="ergAnrufSaetze"></div>
|
||
</div>
|
||
|
||
<div class="brief-karte" id="ergBegriffeKarte" style="display: none;">
|
||
<span class="karten-label neutral" data-t="labelWoerter">Schwere Wörter erklärt</span>
|
||
<div id="ergBegriffe"></div>
|
||
</div>
|
||
|
||
<div id="mitnehmenTeil" style="display: none; margin-top: 22px;">
|
||
<button class="cta cta-zwei" id="mitnehmenKnopf" type="button">📱 Ergebnis aufs Handy mitnehmen</button>
|
||
<div class="qr-box" id="mitnehmenBox">
|
||
<strong>Ergebnis aufs Handy</strong>
|
||
<div class="qr" id="mitnehmenQr"></div>
|
||
<p class="klein" style="margin-top: 0;">Mit der Handy-Kamera scannen. Dort gibt es den Kalender-Eintrag und die Anruf-Hilfe. Verschlüsselt, der Code gilt 15 Minuten.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<p class="hinweis-recht" data-t="rechtHinweis">
|
||
⚖️ BehördenKlar erklärt Briefe, ersetzt aber keine Rechtsberatung. Bei
|
||
wichtigen Entscheidungen hilft eine Beratungsstelle oder eine Anwältin bzw. ein Anwalt.
|
||
</p>
|
||
</section>
|
||
|
||
<section id="werbung" style="display: none;">
|
||
<h2><span class="nr">§ 4</span> Wie geht es weiter?</h2>
|
||
<div class="antrag">
|
||
<p class="formular-kopf" id="restZeile">Warteliste</p>
|
||
<h3 id="wlTitel" style="font-size: 24px; line-height: 1.3;">Das hat Ihnen geholfen?</h3>
|
||
<p style="margin-top: 10px;">
|
||
Die App kann noch mehr: Briefe <strong>übersetzen in über 25 Sprachen</strong>,
|
||
Fristen mit <strong>Erinnerung</strong>, fertige
|
||
<strong>Antwort-Entwürfe</strong> — und Ihre Briefe bleiben
|
||
verschlüsselt auf Ihrem Handy gespeichert.
|
||
</p>
|
||
<p class="bonus-zeile">
|
||
🎁 Warteliste = <strong>5 Gratis-Analysen</strong> in der App statt 3.
|
||
</p>
|
||
<p id="nochmalZeile" style="display: none; margin-bottom: 14px;">
|
||
<a href="#demo" style="color: var(--amtsblau); font-weight: 700;">↑ Noch einen
|
||
Brief testen? Ihre zweite Gratis-Analyse wartet oben.</a>
|
||
</p>
|
||
<form class="wl-form" action="https://86550585.sibforms.com/serve/MUIFAAvOQCpFg148k-xD2Ze2PDVKKaFEIaLECCCMe9fGKd_uIurPljyn_IhTrkR0EkM_pQp_8VBzcHZ6N9sKDX5GBZi9elYKN6isXGPUUxcAgO37OKhTFZyMlJiC9ikRmPI8Q2tU2GvqP6esJ5ReGfv1htZV_OfXRIgW59P6pkeUsYKxqdXA7nWOaWxNTYz4MzzoGg7czJMJ2fxuEg==" method="POST">
|
||
<input class="wl-input" style="margin-bottom: 0;" type="email" name="EMAIL" id="wlEmail" placeholder="Ihre E-Mail-Adresse" required autocomplete="email">
|
||
<input type="text" name="email_address_check" value="" style="position:absolute; left:-5000px;" tabindex="-1" autocomplete="off" aria-hidden="true">
|
||
<input type="hidden" name="locale" value="de">
|
||
<input type="hidden" name="html_type" value="simple">
|
||
<button class="cta" type="submit">Auf die Warteliste eintragen →</button>
|
||
</form>
|
||
<p class="klein">
|
||
Eine E-Mail zum Start, keine Werbung, jederzeit abmeldbar —
|
||
<a href="datenschutz.html">Datenschutzerklärung</a>.
|
||
</p>
|
||
<div style="margin-top: 16px;"><span class="stempel">Bald im App Store</span></div>
|
||
</div>
|
||
</section>
|
||
|
||
<footer>
|
||
<a href="index.html">← Zur Startseite</a> ·
|
||
<a href="agb.html">AGB</a> ·
|
||
<a href="widerruf.html">Widerruf</a> ·
|
||
<a href="impressum.html">Impressum</a> ·
|
||
<a href="datenschutz.html">Datenschutzerklärung</a>
|
||
</footer>
|
||
|
||
</div>
|
||
|
||
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
|
||
<script src="qrcode.js"></script>
|
||
<script src="uebergabe.js"></script>
|
||
<script src="ui-texte.js"></script>
|
||
<script>
|
||
(function () {
|
||
'use strict';
|
||
var BASIS = /^(localhost|127\.0\.0\.1)$/.test(location.hostname)
|
||
? 'http://localhost:8799' // lokaler Test mit `wrangler dev`
|
||
: 'https://behoerdenklar-proxy.behoerdenbrief.workers.dev';
|
||
var ENDPUNKT = BASIS + '/demo';
|
||
var KAUFEN_ENDPUNKT = BASIS + '/kaufen';
|
||
var STATUS_ENDPUNKT = BASIS + '/kauf-status';
|
||
var MAX_KANTE = 2000; // wie in der App: reicht für A4, spart Datenvolumen
|
||
|
||
// Guthaben-Pakete (Stripe TEST-Preise). Für Live später die Live-price_…-IDs.
|
||
var PAKETE = [
|
||
{ priceId: 'price_1U6BNnBsRA1AOdnedGW5eefh', titel: '5 Analysen', preis: '2,99 €' },
|
||
{ priceId: 'price_1U6BPRBsRA1AOdnemDUalzb8', titel: '15 Analysen', preis: '6,99 €', best: true },
|
||
];
|
||
|
||
// Browser-Speicher abgesichert: Manche Browser (Safari „Alle Cookies
|
||
// blockieren", App-interne Browser) werfen schon beim Zugriff einen Fehler.
|
||
// Dann merken wir uns die Werte nur, solange die Seite offen ist.
|
||
var speicher = (function () {
|
||
var ersatz = {};
|
||
return {
|
||
get: function (k) { try { return window.localStorage.getItem(k); } catch (e) { return k in ersatz ? ersatz[k] : null; } },
|
||
set: function (k, v) { try { window.localStorage.setItem(k, v); } catch (e) { ersatz[k] = String(v); } },
|
||
remove: function (k) { try { window.localStorage.removeItem(k); } catch (e) { delete ersatz[k]; } },
|
||
};
|
||
})();
|
||
|
||
// Zielsprache: kommt als ?sprache=tr von den Sprachseiten, wird gemerkt.
|
||
var SPRACH_SCHLUESSEL = 'bk_sprache';
|
||
var SPRACH_DEUTSCH = {
|
||
tr:'Türkisch', ar:'Arabisch', ru:'Russisch', uk:'Ukrainisch', pl:'Polnisch', ro:'Rumänisch',
|
||
en:'Englisch', fa:'Farsi/Dari', ku:'Kurdisch', bg:'Bulgarisch', hr:'Kroatisch', sr:'Serbisch',
|
||
bs:'Bosnisch', sq:'Albanisch', it:'Italienisch', el:'Griechisch', es:'Spanisch', pt:'Portugiesisch',
|
||
fr:'Französisch', hu:'Ungarisch', vi:'Vietnamesisch', hi:'Hindi', ur:'Urdu', ps:'Paschtu',
|
||
ti:'Tigrinya', so:'Somali', zh:'Chinesisch'
|
||
};
|
||
var SPRACH_NAMEN = {
|
||
de:'Deutsch', tr:'Türkçe', ar:'العربية', ru:'Русский', uk:'Українська', pl:'Polski',
|
||
ro:'Română', en:'English', fa:'فارسی', ku:'Kurdî', bg:'Български', hr:'Hrvatski',
|
||
sr:'Српски', bs:'Bosanski', sq:'Shqip', it:'Italiano', el:'Ελληνικά', es:'Español',
|
||
pt:'Português', fr:'Français', hu:'Magyar', vi:'Tiếng Việt', hi:'हिन्दी', ur:'اردو',
|
||
ps:'پښتو', ti:'ትግርኛ', so:'Soomaali', zh:'中文'
|
||
};
|
||
var sitzungsSprache = null; // vom PC per QR vorgegeben — gilt nur, solange die Seite offen ist
|
||
function zielsprache() {
|
||
if (sitzungsSprache) return sitzungsSprache;
|
||
var p = new URLSearchParams(window.location.search).get('sprache');
|
||
if (p && SPRACH_NAMEN[p]) { speicher.set(SPRACH_SCHLUESSEL, p); return p; }
|
||
var g = speicher.get(SPRACH_SCHLUESSEL); if (g && SPRACH_NAMEN[g]) return g;
|
||
// RA4: Sprache des HANDYS vorschlagen (nicht speichern — eine Auswahl von Hand gewinnt).
|
||
// Nicht am PC: Dort wählt die Beraterin die Sprache ihres Gegenübers.
|
||
var amPc = window.matchMedia && window.matchMedia('(hover: hover) and (pointer: fine)').matches;
|
||
var liste = amPc ? [] : (navigator.languages && navigator.languages.length) ? navigator.languages : [navigator.language || ''];
|
||
for (var i = 0; i < liste.length; i++) {
|
||
var kurz = String(liste[i] || '').toLowerCase().split('-')[0];
|
||
if (kurz === 'de') break;
|
||
if (SPRACH_NAMEN[kurz]) return kurz;
|
||
}
|
||
return 'de';
|
||
}
|
||
|
||
var konsens = document.getElementById('konsens');
|
||
var startKnopf = document.getElementById('startKnopf');
|
||
var dateiFeld = document.getElementById('datei');
|
||
var emailTeil = document.getElementById('emailTeil');
|
||
var emailFeld = document.getElementById('email');
|
||
var status = document.getElementById('status');
|
||
var eingabe = document.getElementById('eingabe');
|
||
var fehlerBox = document.getElementById('fehler');
|
||
var kaufBlock = document.getElementById('kaufBlock');
|
||
var werbung = document.getElementById('werbung');
|
||
var guthabenBanner = document.getElementById('guthabenBanner');
|
||
var guthabenZahl = document.getElementById('guthabenZahl');
|
||
var guthabenText = document.getElementById('guthabenText');
|
||
var codeTeil = document.getElementById('codeTeil');
|
||
var codeFeld = document.getElementById('codeFeld');
|
||
var codeKnopf = document.getElementById('codeKnopf');
|
||
var kaufOk = document.getElementById('kaufOk');
|
||
var paketListe = document.getElementById('paketListe');
|
||
var kaufKonsens = document.getElementById('kaufKonsens');
|
||
var kaufFehler = document.getElementById('kaufFehler');
|
||
|
||
// Wallet (passwortloses Guthaben) — Token + zuletzt bekannter Stand lokal.
|
||
var WALLET_SCHLUESSEL = 'bk_wallet';
|
||
var GUTHABEN_SCHLUESSEL = 'bk_guthaben';
|
||
function walletToken() { return speicher.get(WALLET_SCHLUESSEL) || ''; }
|
||
function setzeWallet(t) { speicher.set(WALLET_SCHLUESSEL, t); }
|
||
function guthaben() { return parseInt(speicher.get(GUTHABEN_SCHLUESSEL) || '0', 10); }
|
||
function setzeGuthaben(n) { speicher.set(GUTHABEN_SCHLUESSEL, String(Math.max(0, n | 0))); }
|
||
function hatGuthaben() { return !!walletToken() && guthaben() > 0; }
|
||
// Einrichtungs-Code: Name der Beratungsstelle, über die das Guthaben läuft
|
||
var EINRICHTUNG_SCHLUESSEL = 'bk_einrichtung';
|
||
function einrichtung() { return speicher.get(EINRICHTUNG_SCHLUESSEL) || ''; }
|
||
// BE3: Ablaufdatum des Einrichtungs-Zugangs (kommt beim Einlösen vom Server)
|
||
var ABLAUF_SCHLUESSEL = 'bk_einrichtung_bis';
|
||
var CODE_SCHLUESSEL = 'bk_einrichtung_code';
|
||
function einrichtungBis() { return speicher.get(ABLAUF_SCHLUESSEL) || ''; }
|
||
function setzeEinrichtung(name) {
|
||
if (name) speicher.set(EINRICHTUNG_SCHLUESSEL, name);
|
||
else { speicher.remove(EINRICHTUNG_SCHLUESSEL); speicher.remove(ABLAUF_SCHLUESSEL); speicher.remove(CODE_SCHLUESSEL); }
|
||
}
|
||
// ── Bediensprache ─────────────────────────────────────────────────
|
||
// Am PC (Beratung) bleibt alles deutsch. Am Handy richtet sich die
|
||
// Bedienung nach der gewählten Erklärungssprache (Texte: ui-texte.js).
|
||
var UI = window.BK_UI_TEXTE || { de: {} };
|
||
var RTL = { ar: 1, fa: 1, ur: 1, ps: 1 };
|
||
var VERBUNDEN = { ar: 1, fa: 1, ur: 1, ps: 1, hi: 1, ti: 1, zh: 1 };
|
||
var uiSprache = 'de';
|
||
function t(schluessel, werte) {
|
||
var text = (UI[uiSprache] && UI[uiSprache][schluessel]) || (UI.de && UI.de[schluessel]) || schluessel;
|
||
if (werte) for (var k in werte) text = text.split('{' + k + '}').join(werte[k]);
|
||
return text;
|
||
}
|
||
function wendeUiSpracheAn() {
|
||
var amPc = window.matchMedia && window.matchMedia('(hover: hover) and (pointer: fine)').matches;
|
||
var gewuenscht = amPc ? 'de' : zielsprache();
|
||
uiSprache = UI[gewuenscht] ? gewuenscht : 'de';
|
||
document.documentElement.lang = uiSprache;
|
||
document.documentElement.dir = RTL[uiSprache] ? 'rtl' : 'ltr';
|
||
document.documentElement.classList.toggle('verbundene-schrift', !!VERBUNDEN[uiSprache]);
|
||
var elemente = document.querySelectorAll('[data-t]');
|
||
for (var i = 0; i < elemente.length; i++) {
|
||
var el = elemente[i], key = el.getAttribute('data-t');
|
||
if (UI.de && UI.de[key]) el.textContent = t(key);
|
||
}
|
||
// Was gerade dynamisch angezeigt wird, in der neuen Sprache neu setzen
|
||
var stelle = (typeof handyModus !== 'undefined' && handyModus && handyModus.e) || (einrichtung() && hatGuthaben() ? einrichtung() : '');
|
||
if (stelle) zeigeEinrichtungImKopf(stelle);
|
||
if (typeof handyModus !== 'undefined' && handyModus && !handyModus.fertig) verbundenBanner.textContent = t('verbunden');
|
||
}
|
||
/** Meldung des Servers: am Handy in der Bediensprache, sonst die genaue deutsche. */
|
||
function serverMeldung(daten) {
|
||
var zuordnung = { turnstile: 'srvTurnstile', ki: 'srvKi', ki_konfig: 'srvDienst', dienst: 'srvDienst', datei: 'srvDatei', inhalt: 'srvInhalt', aufgebraucht: 'srvAufgebraucht' };
|
||
if (uiSprache !== 'de' && zuordnung[daten.code]) return t(zuordnung[daten.code]);
|
||
return daten.meldung || t('srvKi');
|
||
}
|
||
|
||
// Handy ↔ PC: Ist dieses Gerät gerade das Handy, das für einen PC
|
||
// fotografiert? Ein Guthaben aus dem QR-Code (w) gilt nur für diese
|
||
// Sitzung und wird hier NICHT gespeichert.
|
||
var handyModus = null;
|
||
var leseModus = false; // Ergebnis vom PC übernommen, nur anzeigen
|
||
var istPc = window.matchMedia && window.matchMedia('(hover: hover) and (pointer: fine)').matches;
|
||
function walletFuerAnalyse() {
|
||
if (handyModus && handyModus.w) return handyModus.w;
|
||
return hatGuthaben() ? walletToken() : '';
|
||
}
|
||
/** An den PC melden. Liefert true/false, ob es geklappt hat (Fehler werden nicht geworfen). */
|
||
function meldeAnPc(daten, fach) {
|
||
if (!handyModus) return Promise.resolve(false);
|
||
return BKUebergabe.senden(handyModus.ue, fach || 'status', daten)
|
||
.then(function () { return true; }, function () { return false; });
|
||
}
|
||
/** Das Ergebnis ist das Wichtigste: bis zu 3 Versuche mit kurzer Pause. */
|
||
async function ergebnisAnPc(paket) {
|
||
for (var i = 0; i < 3; i++) {
|
||
if (await meldeAnPc(paket, 'ergebnis')) return true;
|
||
await new Promise(function (r) { setTimeout(r, 1500); });
|
||
}
|
||
return false;
|
||
}
|
||
function zeigeGuthabenBanner(n) {
|
||
var bis = einrichtungBis();
|
||
document.getElementById('guthabenHandyKnopf').textContent = einrichtung()
|
||
? '🔑 Zugang dauerhaft auf ein Handy geben (nur für Mitarbeitende)'
|
||
: '📱 Auch auf dem Handy nutzen';
|
||
guthabenText.textContent = einrichtung()
|
||
? t('bannerKostenlos', { name: einrichtung() + (bis ? ' ' + t('bannerBis', { datum: deDatum(bis) }) : '') })
|
||
: '💳 Ihr Guthaben:';
|
||
guthabenZahl.textContent = n;
|
||
guthabenBanner.style.display = 'block';
|
||
}
|
||
|
||
// Schlüsselname bewusst 'v2': setzt evtl. falsch gezählte Stände (aus der
|
||
// Testphase) bei bestehenden Besuchern automatisch einmalig zurück.
|
||
var SPEICHER_SCHLUESSEL = 'bk_demo_v2';
|
||
function benutzt() { return parseInt(speicher.get(SPEICHER_SCHLUESSEL) || '0', 10); }
|
||
function setzeBenutzt(n) { speicher.set(SPEICHER_SCHLUESSEL, String(n)); }
|
||
|
||
/** Text mit {name}: der Name kommt in ein <bdi>, damit „(Test)." in arabischem Text richtig steht. */
|
||
function mitName(el, vorlage, name) {
|
||
var teile = vorlage.split('{name}');
|
||
el.textContent = teile[0];
|
||
var b = document.createElement('bdi'); b.textContent = name; el.appendChild(b);
|
||
if (teile[1]) el.appendChild(document.createTextNode(teile[1]));
|
||
}
|
||
/** Einrichtungs-Zugang: oben nicht mehr „2 Analysen gratis" anpreisen. */
|
||
function zeigeEinrichtungImKopf(name) {
|
||
mitName(document.querySelector('#demo .formular-kopf'), t('formKopfKostenlos'), name);
|
||
mitName(document.getElementById('anredeAngebot'), t('anredeKostenlos'), name);
|
||
document.getElementById('kopfAz').textContent = 'Formular D-01 · ' + name;
|
||
}
|
||
function zeigeStand() {
|
||
if (handyModus && handyModus.fertig) return;
|
||
// Handy-Modus: nie Kaufangebot oder Code-Feld (die Beratung ist dabei)
|
||
if (handyModus && !handyModus.w) {
|
||
kaufBlock.style.display = 'none';
|
||
codeTeil.style.display = 'none';
|
||
guthabenBanner.style.display = 'none';
|
||
if (benutzt() >= 2) {
|
||
eingabe.style.display = 'none';
|
||
fehlerBox.style.display = 'block';
|
||
fehlerBox.textContent = t('keinZugangHandy');
|
||
meldeAnPc({ schritt: 'fehler', code: 'kein_zugang', meldung: 'Am Handy sind die kostenlosen Analysen verbraucht.' });
|
||
} else {
|
||
eingabe.style.display = 'block';
|
||
emailTeil.style.display = benutzt() === 1 ? 'block' : 'none';
|
||
}
|
||
return;
|
||
}
|
||
// Einrichtungs-Zugang leer: kein Kaufangebot (Ratsuchende zahlen nichts),
|
||
// Code-Feld bleibt, damit nach dem Aufladen ein neues Öffnen reicht
|
||
if (einrichtung() && walletToken() && guthaben() <= 0 && !(handyModus && handyModus.w)) {
|
||
eingabe.style.display = 'none';
|
||
guthabenBanner.style.display = 'none';
|
||
kaufBlock.style.display = 'none';
|
||
codeTeil.style.display = 'block';
|
||
fehlerBox.style.display = 'block';
|
||
fehlerBox.textContent = t('leerHinweis');
|
||
return;
|
||
}
|
||
if (einrichtung() && hatGuthaben()) zeigeEinrichtungImKopf(einrichtung());
|
||
if (handyModus && handyModus.w) {
|
||
eingabe.style.display = 'block';
|
||
guthabenBanner.style.display = 'none';
|
||
emailTeil.style.display = 'none';
|
||
fehlerBox.style.display = 'none';
|
||
kaufBlock.style.display = 'none';
|
||
codeTeil.style.display = 'none';
|
||
return;
|
||
}
|
||
// Bezahlter Modus: Eingabe offen, Guthaben-Stand zeigen, Gratis-Sperre egal
|
||
if (hatGuthaben()) {
|
||
eingabe.style.display = 'block';
|
||
zeigeGuthabenBanner(guthaben());
|
||
emailTeil.style.display = 'none';
|
||
fehlerBox.style.display = 'none';
|
||
kaufBlock.style.display = 'none';
|
||
codeTeil.style.display = 'none';
|
||
return;
|
||
}
|
||
guthabenBanner.style.display = 'none';
|
||
codeTeil.style.display = 'block';
|
||
// Kauf ist IMMER möglich — auch sofort, ohne die Gratis-Analysen zu nutzen.
|
||
kaufBlock.style.display = 'block';
|
||
// 2. Versuch: E-Mail-Feld einblenden; alles verbraucht: Gratis-Weg sperren
|
||
if (benutzt() >= 2) {
|
||
eingabe.style.display = 'none';
|
||
fehlerBox.style.display = 'block';
|
||
fehlerBox.innerHTML = 'Ihre 2 Gratis-Analysen sind verbraucht. Haben Sie einen <strong>Code von einer Beratungsstelle</strong>? Dann geben Sie ihn direkt hier unten ein. Sonst können Sie unten <strong>Guthaben</strong> kaufen.';
|
||
werbung.style.display = 'block';
|
||
} else if (benutzt() === 1) {
|
||
emailTeil.style.display = 'block';
|
||
}
|
||
}
|
||
|
||
konsens.addEventListener('change', function () {
|
||
if (konsens.checked && fehlerBox.dataset.art === 'konsens') fehlerBox.style.display = 'none';
|
||
});
|
||
startKnopf.addEventListener('click', function () {
|
||
fehlerBox.style.display = 'none';
|
||
if (!konsens.checked) {
|
||
zeigeFehler(t('fehlerKonsens'));
|
||
fehlerBox.dataset.art = 'konsens';
|
||
konsens.closest('label').scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||
konsens.focus();
|
||
return;
|
||
}
|
||
if (!walletFuerAnalyse() && benutzt() >= 1 && !emailFeld.value.trim()) {
|
||
emailTeil.style.display = 'block';
|
||
emailFeld.focus();
|
||
zeigeFehler('Bitte geben Sie zuerst Ihre E-Mail-Adresse ein — dann wählen Sie den Brief aus.');
|
||
return;
|
||
}
|
||
dateiFeld.click();
|
||
});
|
||
dateiFeld.addEventListener('change', function () {
|
||
var datei = dateiFeld.files && dateiFeld.files[0];
|
||
// Feld leeren: sonst meldet der Browser dieselbe Datei beim zweiten Versuch nicht
|
||
dateiFeld.value = '';
|
||
if (datei) starteAnalyse(datei);
|
||
});
|
||
|
||
/** Freundlicher Hinweis (kein Fehler) — nutzt das Feld über dem Upload. */
|
||
function zeigeHinweis(text) {
|
||
kaufOk.textContent = 'ℹ️ ' + text;
|
||
kaufOk.style.display = 'block';
|
||
}
|
||
|
||
function zeigeFertigBanner(angekommen, ohneUebersetzung, paket) {
|
||
verbundenBanner.innerHTML = '';
|
||
verbundenBanner.appendChild(document.createTextNode(angekommen ? t('fertig') : t('nichtAngekommen')));
|
||
if (ohneUebersetzung) verbundenBanner.appendChild(document.createTextNode(' ' + t('ohneUebersetzung')));
|
||
if (!angekommen) {
|
||
var knopf = document.createElement('button');
|
||
knopf.type = 'button'; knopf.className = 'cta cta-zwei';
|
||
knopf.textContent = t('nochmalSenden');
|
||
knopf.addEventListener('click', async function () {
|
||
knopf.disabled = true; knopf.textContent = t('wirdGesendet');
|
||
var ok = await ergebnisAnPc(paket);
|
||
zeigeFertigBanner(ok, ohneUebersetzung, paket);
|
||
});
|
||
verbundenBanner.appendChild(knopf);
|
||
}
|
||
}
|
||
|
||
function zeigeFehler(text) {
|
||
fehlerBox.dataset.art = '';
|
||
fehlerBox.style.display = 'block';
|
||
fehlerBox.textContent = text;
|
||
}
|
||
|
||
function turnstileToken() {
|
||
var feld = document.querySelector('[name="cf-turnstile-response"]');
|
||
return feld ? feld.value : '';
|
||
}
|
||
|
||
/** Foto verkleinern (max 2000px, JPEG 60%) — wie in der App. */
|
||
function bildVerkleinern(datei) {
|
||
return new Promise(function (resolve, reject) {
|
||
var img = new Image();
|
||
var url = URL.createObjectURL(datei);
|
||
img.onload = function () {
|
||
URL.revokeObjectURL(url);
|
||
var f = Math.min(1, MAX_KANTE / Math.max(img.width, img.height));
|
||
var c = document.createElement('canvas');
|
||
c.width = Math.round(img.width * f);
|
||
c.height = Math.round(img.height * f);
|
||
c.getContext('2d').drawImage(img, 0, 0, c.width, c.height);
|
||
resolve(c.toDataURL('image/jpeg', 0.6).split(',')[1]);
|
||
};
|
||
img.onerror = function () { URL.revokeObjectURL(url); reject(new Error('Bild unlesbar')); };
|
||
img.src = url;
|
||
});
|
||
}
|
||
|
||
function pdfAlsBase64(datei) {
|
||
return new Promise(function (resolve, reject) {
|
||
var leser = new FileReader();
|
||
leser.onload = function () { resolve(String(leser.result).split(',')[1]); };
|
||
leser.onerror = function () { reject(new Error('Datei unlesbar')); };
|
||
leser.readAsDataURL(datei);
|
||
});
|
||
}
|
||
|
||
async function starteAnalyse(datei) {
|
||
fehlerBox.style.display = 'none';
|
||
if (datei.size > 5 * 1024 * 1024 && datei.type === 'application/pdf') {
|
||
zeigeFehler(t('fehlerPdfGross'));
|
||
return;
|
||
}
|
||
var token = turnstileToken();
|
||
if (!token) {
|
||
zeigeFehler(t('fehlerTurnstileLaedt'));
|
||
return;
|
||
}
|
||
|
||
eingabe.style.display = 'none';
|
||
status.querySelector('.gross').textContent = t('statusLaeuft');
|
||
status.style.display = 'block';
|
||
|
||
try {
|
||
var istPdf = datei.type === 'application/pdf';
|
||
var base64 = istPdf ? await pdfAlsBase64(datei) : await bildVerkleinern(datei);
|
||
var wallet = walletFuerAnalyse();
|
||
var bezahlt = !!wallet;
|
||
var body = {
|
||
bild: base64,
|
||
mimeType: istPdf ? 'application/pdf' : 'image/jpeg',
|
||
turnstileToken: token,
|
||
};
|
||
var email = emailFeld.value.trim();
|
||
body.zielsprache = zielsprache();
|
||
if (bezahlt) body.walletToken = wallet;
|
||
else if (benutzt() >= 1 && email) body.email = email;
|
||
if (handyModus && handyModus.d) body.auchDeutsch = true;
|
||
meldeAnPc({ schritt: 'analyse' });
|
||
|
||
var antwort = await fetch(ENDPUNKT, {
|
||
method: 'POST',
|
||
headers: { 'content-type': 'application/json' },
|
||
body: JSON.stringify(body),
|
||
});
|
||
var daten = await antwort.json();
|
||
|
||
if (!antwort.ok) {
|
||
status.style.display = 'none';
|
||
eingabe.style.display = 'block';
|
||
if (window.turnstile) window.turnstile.reset();
|
||
meldeAnPc({ schritt: 'fehler', code: daten.code || '', meldung: daten.meldung || '' });
|
||
// Guthaben vom PC: Fehler nur anzeigen, am Handy nichts umstellen
|
||
if (handyModus && handyModus.w) {
|
||
zeigeFehler(serverMeldung(daten));
|
||
return;
|
||
}
|
||
// Handy ohne Zugang vom PC: Kontingent-Fehler nicht als Kaufangebot zeigen
|
||
if (handyModus && /^(aufgebraucht|kein_guthaben|budget)$/.test(daten.code || '')) {
|
||
if (daten.code === 'aufgebraucht') setzeBenutzt(2);
|
||
eingabe.style.display = 'none';
|
||
fehlerBox.style.display = 'block';
|
||
fehlerBox.textContent = t('keinZugangHandy');
|
||
meldeAnPc({ schritt: 'fehler', code: 'kein_zugang', meldung: 'Am Handy sind die kostenlosen Analysen verbraucht.' });
|
||
return;
|
||
}
|
||
if (daten.code === 'email_noetig') {
|
||
// Nur das E-Mail-Feld zeigen — NICHT als verbraucht markieren.
|
||
// (Sonst würde ein serverseitiges "E-Mail nötig", z. B. bei
|
||
// geteiltem Anschluss, den Browser dauerhaft blockieren, obwohl
|
||
// gar keine Analyse gelungen ist.)
|
||
emailTeil.style.display = 'block';
|
||
emailFeld.focus();
|
||
}
|
||
if (daten.code === 'aufgebraucht') {
|
||
setzeBenutzt(2);
|
||
zeigeStand();
|
||
return;
|
||
}
|
||
if (daten.code === 'kontingent_leer' || daten.code === 'kontingent_abgelaufen') {
|
||
vergissHandySitzung();
|
||
setzeGuthaben(0);
|
||
zeigeStand();
|
||
return;
|
||
}
|
||
if (daten.code === 'kein_guthaben') {
|
||
setzeGuthaben(0);
|
||
guthabenBanner.style.display = 'none';
|
||
kaufBlock.style.display = 'block';
|
||
kaufBlock.scrollIntoView({ behavior: 'smooth' });
|
||
return;
|
||
}
|
||
zeigeFehler(serverMeldung(daten));
|
||
return;
|
||
}
|
||
|
||
// Token erneuern — sonst würde die nächste Analyse ein "Seite neu laden" verlangen
|
||
if (window.turnstile) window.turnstile.reset();
|
||
// Die Einwilligung gilt für genau diesen Brief: für den nächsten neu fragen
|
||
konsens.checked = false;
|
||
|
||
// Handy fotografiert für den PC: Ergebnis verschlüsselt hinüberschicken
|
||
var paketFuerPc = {
|
||
analyse: daten.analyse, analyse_de: daten.analyse_de, sprache: daten.sprache,
|
||
guthaben: daten.guthaben, einrichtung: daten.einrichtung,
|
||
};
|
||
var amPcAngekommen = handyModus ? await ergebnisAnPc(paketFuerPc) : false;
|
||
var analyse = bereiteVersionen(daten, false);
|
||
if (daten.uebersetzung_fehlgeschlagen) {
|
||
zeigeHinweis('Die Übersetzung hat diesmal nicht geklappt. Die Erklärung steht hier auf Deutsch.');
|
||
}
|
||
|
||
// Handy hat für den PC fotografiert: Verbindung ist damit erledigt.
|
||
// (Der PC wartet nicht mehr — ein zweiter Brief käme dort nie an.)
|
||
if (handyModus) {
|
||
if (!handyModus.w && typeof daten.versuch === 'number') setzeBenutzt(Math.max(benutzt(), daten.versuch));
|
||
handyModus.fertig = true;
|
||
zeigeAnalyse(analyse);
|
||
eingabe.style.display = 'none';
|
||
kaufBlock.style.display = 'none';
|
||
codeTeil.style.display = 'none';
|
||
werbung.style.display = 'none';
|
||
vergissHandySitzung();
|
||
zeigeFertigBanner(amPcAngekommen, daten.uebersetzung_fehlgeschlagen, paketFuerPc);
|
||
document.getElementById('ergebnis').scrollIntoView({ behavior: 'smooth' });
|
||
return;
|
||
}
|
||
|
||
if (daten.versuch === 'bezahlt' || typeof daten.guthaben === 'number') {
|
||
setzeGuthaben(daten.guthaben);
|
||
zeigeErgebnis(analyse, null, daten.guthaben);
|
||
return;
|
||
}
|
||
|
||
setzeBenutzt(Math.max(benutzt(), daten.versuch));
|
||
if (email) document.getElementById('wlEmail').value = email;
|
||
zeigeErgebnis(analyse, daten.versuch);
|
||
} catch (e) {
|
||
status.style.display = 'none';
|
||
eingabe.style.display = 'block';
|
||
if (window.turnstile) window.turnstile.reset();
|
||
meldeAnPc({ schritt: 'fehler', code: 'verbindung', meldung: 'Verbindung fehlgeschlagen.' });
|
||
zeigeFehler(t('fehlerVerbindung'));
|
||
}
|
||
}
|
||
|
||
/** Ampel wie in der App: rot ≤ 7 Tage, gelb ≤ 21, sonst grün. */
|
||
/** Ganze Kalendertage von heute bis zum ISO-Datum (wie tageBis() in der App).
|
||
* Früher wurde bis 12 Uhr mittags gerechnet — dann zeigte ein Termin in
|
||
* 7 Tagen vormittags „8 Tage" und Gelb statt Rot. */
|
||
function kalenderTage(iso) {
|
||
var t = String(iso).split('-');
|
||
var heute = new Date();
|
||
var a = Date.UTC(heute.getFullYear(), heute.getMonth(), heute.getDate());
|
||
var b = Date.UTC(+t[0], +t[1] - 1, +t[2]);
|
||
return Math.round((b - a) / 86400000);
|
||
}
|
||
function ampel(analyse) {
|
||
var daten = [];
|
||
if (analyse.frist && isoGueltig(analyse.frist.datum)) daten.push({ iso: analyse.frist.datum, art: 'Frist' });
|
||
if (analyse.termin && isoGueltig(analyse.termin.datum)) daten.push({ iso: analyse.termin.datum, art: 'Termin' });
|
||
if (!daten.length) return null;
|
||
daten.sort(function (x, y) { return x.iso < y.iso ? -1 : x.iso > y.iso ? 1 : 0; });
|
||
// Das nächste kommende Datum zählt; nur wenn alles vorbei ist, das zuletzt vergangene
|
||
var kommend = daten.filter(function (d) { return kalenderTage(d.iso) >= 0; });
|
||
var erstes = kommend.length ? kommend[0] : daten[daten.length - 1];
|
||
var tage = kalenderTage(erstes.iso);
|
||
if (isNaN(tage)) return null;
|
||
var farbe = tage <= 7 ? 'var(--rot)' : tage <= 21 ? 'var(--gelb)' : 'var(--gruen)';
|
||
var punkt = tage <= 7 ? '🔴' : tage <= 21 ? '🟡' : '🟢';
|
||
var datum = deDatum(erstes.iso);
|
||
var wann = erstes.art === 'Termin' ? t('ampelTerminAm', { datum: datum }) : t('ampelFristBis', { datum: datum });
|
||
var text = tage < 0 ? (erstes.art === 'Termin' ? t('ampelTerminWar', { datum: datum }) : t('ampelFristVorbei', { datum: datum })) :
|
||
tage === 0 ? t('ampelHeute') + ': ' + wann + '!' :
|
||
tage === 1 ? t('ampelMorgen') + ': ' + wann :
|
||
t('ampelNoch', { n: tage }) + ' · ' + wann;
|
||
return { farbe: farbe, text: punkt + ' ' + text, istFrist: erstes.art === 'Frist' };
|
||
}
|
||
|
||
// Eine Analyse kann in zwei Sprachen vorliegen (PC Deutsch, Handy Muttersprache).
|
||
var versionen = [];
|
||
var aktiveVersion = 0;
|
||
var letzteDaten = null; // { analyse, analyse_de, sprache } — für „Mitnehmen"
|
||
function bereiteVersionen(d, deutschZuerst) {
|
||
letzteDaten = { analyse: d.analyse, analyse_de: d.analyse_de, sprache: d.sprache || 'de' };
|
||
versionen = [{ code: letzteDaten.sprache, analyse: d.analyse }];
|
||
// Zweite Fassung nur, wenn sie wirklich eine andere Sprache ist
|
||
if (d.analyse_de && letzteDaten.sprache !== 'de') {
|
||
versionen[deutschZuerst ? 'unshift' : 'push']({ code: 'de', analyse: d.analyse_de });
|
||
}
|
||
aktiveVersion = 0;
|
||
return versionen[0].analyse;
|
||
}
|
||
function zeichneUmschalter() {
|
||
var box = document.getElementById('sprachUmschalter');
|
||
box.innerHTML = '';
|
||
box.style.display = versionen.length > 1 ? 'flex' : 'none';
|
||
versionen.forEach(function (v, i) {
|
||
var b = document.createElement('button');
|
||
b.type = 'button';
|
||
b.textContent = SPRACH_NAMEN[v.code] || v.code;
|
||
b.setAttribute('aria-pressed', i === aktiveVersion ? 'true' : 'false');
|
||
b.addEventListener('click', function () { aktiveVersion = i; zeigeAnalyse(v.analyse); });
|
||
box.appendChild(b);
|
||
});
|
||
}
|
||
|
||
/** Nur die Anzeige einer Analyse — ohne Guthaben/Gratis-Ablauf. */
|
||
function zeigeAnalyse(analyse) {
|
||
status.style.display = 'none';
|
||
// Optionale Karten zuruecksetzen. Sonst bleibt beim zweiten Brief z. B.
|
||
// die Checkliste des ERSTEN Briefs stehen — zwei Briefe vermischt.
|
||
['ergAmpel', 'ergKalenderKarte', 'ergChecklisteKarte', 'ergAnrufKarte', 'ergBegriffeKarte']
|
||
.forEach(function (id) { document.getElementById(id).style.display = 'none'; });
|
||
document.getElementById('ergBrieftyp').textContent = analyse.brieftyp || 'Ihr Brief';
|
||
document.getElementById('ergKern').textContent = analyse.kernaussage || '';
|
||
document.getElementById('ergErklaerung').textContent = analyse.erklaerung_einfach || '';
|
||
|
||
var a = ampel(analyse);
|
||
var ampelEl = document.getElementById('ergAmpel');
|
||
if (a) {
|
||
ampelEl.style.display = 'block';
|
||
ampelEl.style.color = a.farbe;
|
||
ampelEl.textContent = a.text;
|
||
// Die Aufgabe gehört zur Frist — nur anhängen, wenn die Frist das früheste Datum ist.
|
||
// KI-Text in eigenes <bdi>, damit Arabisch/Farsi richtig herum läuft.
|
||
if (a.istFrist && analyse.frist && analyse.frist.aktion) {
|
||
ampelEl.appendChild(document.createTextNode(' · '));
|
||
var bdi = document.createElement('bdi'); bdi.textContent = analyse.frist.aktion;
|
||
ampelEl.appendChild(bdi);
|
||
}
|
||
}
|
||
|
||
if (analyse.checkliste && analyse.checkliste.length) {
|
||
document.getElementById('ergChecklisteKarte').style.display = 'block';
|
||
document.getElementById('ergCheckliste').innerHTML = analyse.checkliste
|
||
.map(function (t) { var d = document.createElement('div'); d.className = 'todo'; d.setAttribute('dir', 'auto'); d.textContent = t; return d.outerHTML; })
|
||
.join('');
|
||
}
|
||
if (analyse.fachbegriffe && analyse.fachbegriffe.length) {
|
||
document.getElementById('ergBegriffeKarte').style.display = 'block';
|
||
document.getElementById('ergBegriffe').innerHTML = analyse.fachbegriffe
|
||
.map(function (b) {
|
||
var det = document.createElement('details');
|
||
var sum = document.createElement('summary');
|
||
sum.textContent = b.begriff;
|
||
var p = document.createElement('p');
|
||
p.setAttribute('dir', 'auto');
|
||
p.textContent = b.erklaerung;
|
||
det.appendChild(sum); det.appendChild(p);
|
||
return det.outerHTML;
|
||
})
|
||
.join('');
|
||
}
|
||
|
||
zeigeKalender(analyse);
|
||
zeigeAnruf(analyse);
|
||
zeichneUmschalter();
|
||
document.getElementById('mitnehmenTeil').style.display = istPc && !handyModus && !leseModus ? 'block' : 'none';
|
||
document.getElementById('mitnehmenBox').style.display = 'none';
|
||
|
||
document.getElementById('ergebnis').style.display = 'block';
|
||
}
|
||
|
||
function zeigeErgebnis(analyse, versuch, guthabenRest) {
|
||
zeigeAnalyse(analyse);
|
||
|
||
// Bezahlter Modus: Guthaben-Stand zeigen, Eingabe für den nächsten Brief öffnen
|
||
if (typeof guthabenRest === 'number') {
|
||
werbung.style.display = 'none';
|
||
kaufOk.style.display = 'none';
|
||
if (guthabenRest > 0) {
|
||
eingabe.style.display = 'block';
|
||
zeigeGuthabenBanner(guthabenRest);
|
||
emailTeil.style.display = 'none';
|
||
kaufBlock.style.display = 'none';
|
||
} else if (einrichtung()) {
|
||
zeigeStand(); // Leer-Hinweis mit Telefonnummer — kein Kaufangebot für Ratsuchende
|
||
} else {
|
||
eingabe.style.display = 'none';
|
||
guthabenBanner.style.display = 'none';
|
||
kaufBlock.style.display = 'block';
|
||
}
|
||
document.getElementById('ergebnis').scrollIntoView({ behavior: 'smooth' });
|
||
return;
|
||
}
|
||
|
||
var rest = 2 - benutzt();
|
||
document.getElementById('restZeile').textContent =
|
||
rest > 0 ? 'Noch ' + rest + ' Gratis-Analyse übrig' : 'Ihre 2 Gratis-Analysen sind verbraucht';
|
||
document.getElementById('wlTitel').textContent =
|
||
rest > 0 ? 'Das hat Ihnen geholfen?' : 'Weiter geht es in der App';
|
||
document.getElementById('werbung').style.display = 'block';
|
||
if (rest > 0) {
|
||
// Upload-Bereich für die 2. Analyse wieder anbieten (mit E-Mail-Feld)
|
||
eingabe.style.display = 'block';
|
||
emailTeil.style.display = 'block';
|
||
document.getElementById('nochmalZeile').style.display = 'block';
|
||
}
|
||
document.getElementById('ergebnis').scrollIntoView({ behavior: 'smooth' });
|
||
}
|
||
|
||
// ---- Kalender (.ics) --------------------------------------------------
|
||
// Die Datei entsteht im Browser und wird nirgends hochgeladen. Die
|
||
// Erinnerungen stellt danach der Kalender des Handys selbst.
|
||
var letzteAnalyse = null;
|
||
|
||
function icsText(t) {
|
||
return String(t || '').replace(/\\/g, '\\\\').replace(/;/g, '\;')
|
||
.replace(/,/g, '\\,').replace(/\r?\n/g, '\\n');
|
||
}
|
||
// RFC 5545: max. 75 BYTE pro Zeile. Nach Bytes falten, nicht nach Zeichen —
|
||
// arabische oder tigrinische Texte brauchen 2–4 Byte pro Zeichen.
|
||
function icsFalte(zeile) {
|
||
var enc = new TextEncoder(), teile = [], akt = '', bytes = 0;
|
||
for (var zeichen of zeile) {
|
||
var b = enc.encode(zeichen).length;
|
||
if (bytes + b > 75) { teile.push(akt); akt = ' ' + zeichen; bytes = 1 + b; }
|
||
else { akt += zeichen; bytes += b; }
|
||
}
|
||
teile.push(akt);
|
||
return teile.join('\r\n');
|
||
}
|
||
function isoGueltig(d) { return typeof d === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(d); }
|
||
function deDatum(iso) { return iso.split('-').reverse().join('.'); }
|
||
function zweistellig(x) { return ('0' + x).slice(-2); }
|
||
// "Wanduhrzeit" ohne Zeitzone: der Kalender nimmt die Ortszeit des Geraets.
|
||
// Richtig fuer einen Termin in Deutschland, egal wo der Server steht.
|
||
function wanduhr(dt) {
|
||
return dt.getUTCFullYear() + zweistellig(dt.getUTCMonth() + 1) + zweistellig(dt.getUTCDate()) +
|
||
'T' + zweistellig(dt.getUTCHours()) + zweistellig(dt.getUTCMinutes()) + '00';
|
||
}
|
||
function nurDatum(dt) { return wanduhr(dt).slice(0, 8); }
|
||
function ausIso(iso, h, m) { var t = iso.split('-'); return new Date(Date.UTC(+t[0], +t[1] - 1, +t[2], h || 0, m || 0)); }
|
||
function eindeutig() {
|
||
return (window.crypto && crypto.randomUUID ? crypto.randomUUID() : Date.now() + '-' + Math.random().toString(16).slice(2)) + '@behoerdenklar.de';
|
||
}
|
||
function alarm(ausloeser, text) {
|
||
return ['BEGIN:VALARM', 'ACTION:DISPLAY', 'TRIGGER:' + ausloeser, 'DESCRIPTION:' + icsText(text), 'END:VALARM'];
|
||
}
|
||
|
||
function baueIcs(a) {
|
||
var z = ['BEGIN:VCALENDAR', 'VERSION:2.0', 'PRODID:-//Webklar GbR//BehoerdenKlar//DE',
|
||
'CALSCALE:GREGORIAN', 'METHOD:PUBLISH'];
|
||
var titel = a.brieftyp || 'Brief', stempel = wanduhr(new Date()) + 'Z', n = 0;
|
||
|
||
if (a.frist && isoGueltig(a.frist.datum)) {
|
||
var fTag = ausIso(a.frist.datum);
|
||
var fDanach = new Date(fTag.getTime() + 86400000);
|
||
// Titel und Hinweis ohne deutsche Worte: Datum und Symbol versteht jeder,
|
||
// "aktion" kommt bereits in der Sprache des Nutzers von der KI.
|
||
z.push('BEGIN:VEVENT', 'UID:' + eindeutig(), 'DTSTAMP:' + stempel,
|
||
'DTSTART;VALUE=DATE:' + nurDatum(fTag), 'DTEND;VALUE=DATE:' + nurDatum(fDanach),
|
||
'SUMMARY:' + icsText('⚠️ ' + titel),
|
||
'DESCRIPTION:' + icsText((a.frist.aktion || '') + '\n\nbehoerdenklar.de'));
|
||
// Ganztaegig beginnt um 00:00 — so fallen beide Erinnerungen auf 9 Uhr morgens.
|
||
z.push.apply(z, alarm('-P2DT15H', '⏰ ' + titel + ' — ' + deDatum(a.frist.datum)));
|
||
z.push.apply(z, alarm('-PT15H', '⏰ ' + titel + ' — ' + deDatum(a.frist.datum)));
|
||
z.push('END:VEVENT'); n++;
|
||
}
|
||
|
||
if (a.termin && isoGueltig(a.termin.datum)) {
|
||
var zeit = a.termin.uhrzeit && String(a.termin.uhrzeit).match(/(\d{1,2})[:.](\d{2})/);
|
||
var h = zeit ? +zeit[1] : -1, m = zeit ? +zeit[2] : -1;
|
||
z.push('BEGIN:VEVENT', 'UID:' + eindeutig(), 'DTSTAMP:' + stempel);
|
||
if (zeit && h <= 23 && m <= 59) {
|
||
var beginn = ausIso(a.termin.datum, h, m);
|
||
var ende = new Date(beginn.getTime() + 3600000); // eine Stunde, auch ueber Mitternacht korrekt
|
||
z.push('DTSTART:' + wanduhr(beginn), 'DTEND:' + wanduhr(ende));
|
||
z.push.apply(z, alarm('-P3D', '📅 ' + titel + ' — ' + deDatum(a.termin.datum) + ', ' + zweistellig(h) + ':' + zweistellig(m)));
|
||
z.push.apply(z, alarm('-P1D', '📅 ' + titel + ' — ' + deDatum(a.termin.datum) + ', ' + zweistellig(h) + ':' + zweistellig(m)));
|
||
} else {
|
||
var tTag = ausIso(a.termin.datum);
|
||
z.push('DTSTART;VALUE=DATE:' + nurDatum(tTag), 'DTEND;VALUE=DATE:' + nurDatum(new Date(tTag.getTime() + 86400000)));
|
||
z.push.apply(z, alarm('-P2DT15H', '📅 ' + titel + ' — ' + deDatum(a.termin.datum)));
|
||
z.push.apply(z, alarm('-PT15H', '📅 ' + titel + ' — ' + deDatum(a.termin.datum)));
|
||
}
|
||
z.push('SUMMARY:' + icsText('📅 ' + titel));
|
||
if (a.termin.ort) z.push('LOCATION:' + icsText(a.termin.ort));
|
||
z.push('DESCRIPTION:' + icsText('behoerdenklar.de'), 'END:VEVENT'); n++;
|
||
}
|
||
|
||
z.push('END:VCALENDAR');
|
||
return n ? z.map(icsFalte).join('\r\n') + '\r\n' : null;
|
||
}
|
||
|
||
function zeigeKalender(a) {
|
||
var karte = document.getElementById('ergKalenderKarte');
|
||
var knopf = document.getElementById('kalenderKnopf');
|
||
var hinweis = document.getElementById('ergKalenderHinweis');
|
||
var label = document.getElementById('ergKalenderLabel');
|
||
var frist = a.frist && isoGueltig(a.frist.datum) ? a.frist : null;
|
||
var termin = a.termin && isoGueltig(a.termin.datum) ? a.termin : null;
|
||
var box = document.getElementById('ergKalenderText');
|
||
box.innerHTML = '';
|
||
function zeile(text, kiText) {
|
||
var p = document.createElement('p'); p.setAttribute('dir', 'auto'); p.textContent = text;
|
||
if (kiText) { p.appendChild(document.createTextNode(' — ')); var b = document.createElement('bdi'); b.textContent = kiText; p.appendChild(b); }
|
||
box.appendChild(p);
|
||
}
|
||
|
||
// Kein Datum im Brief: trotzdem sichtbar sagen, dass gesucht wurde.
|
||
// Sonst weiss niemand, ob nichts da ist oder etwas kaputt ist — und
|
||
// "keine Frist" ist fuer einen aengstlichen Leser selbst eine Nachricht.
|
||
if (!frist && !termin) {
|
||
letzteAnalyse = null;
|
||
// NICHT "keine Frist" behaupten: Manche Briefe nennen wiederkehrende
|
||
// oder bedingte Fristen ("bis zum 10. Tag nach jedem Vierteljahr"),
|
||
// aus denen sich kein einzelnes Datum machen laesst. Ein Haekchen
|
||
// wuerde dann "alles erledigt" signalisieren — gefaehrlich falsch.
|
||
label.textContent = t('labelKalender');
|
||
zeile(t('keinDatum1'));
|
||
zeile(t('keinDatum2'));
|
||
knopf.style.display = 'none';
|
||
hinweis.style.display = 'none';
|
||
karte.style.border = '';
|
||
karte.style.display = 'block';
|
||
return;
|
||
}
|
||
letzteAnalyse = a;
|
||
label.textContent = t('labelNichtVergessen');
|
||
knopf.style.display = '';
|
||
hinweis.style.display = '';
|
||
karte.style.border = '2px solid var(--amtsblau)';
|
||
if (frist) zeile(t('fristZeile', { datum: deDatum(frist.datum) }), frist.aktion);
|
||
if (termin) zeile(t('terminZeile', { datum: deDatum(termin.datum) }) + (termin.uhrzeit ? ', ' + t('uhrzeit', { zeit: termin.uhrzeit }) : ''), termin.ort);
|
||
karte.style.display = 'block';
|
||
}
|
||
|
||
document.getElementById('kalenderKnopf').addEventListener('click', function () {
|
||
if (!letzteAnalyse) return;
|
||
var ics = baueIcs(letzteAnalyse);
|
||
if (!ics) return;
|
||
var url = URL.createObjectURL(new Blob([ics], { type: 'text/calendar;charset=utf-8' }));
|
||
var link = document.createElement('a');
|
||
link.href = url; link.download = 'BehoerdenKlar-Termin.ics';
|
||
document.body.appendChild(link); link.click(); link.remove();
|
||
setTimeout(function () { URL.revokeObjectURL(url); }, 10000);
|
||
});
|
||
|
||
// ---- Anruf-Hilfe ------------------------------------------------------
|
||
function zeigeAnruf(a) {
|
||
var karte = document.getElementById('ergAnrufKarte');
|
||
var k = a.kontakt || {}, saetze = Array.isArray(a.anruf) ? a.anruf : [];
|
||
if (!k.telefon && !saetze.length) return;
|
||
|
||
var box = document.getElementById('ergAnrufKontakt');
|
||
box.innerHTML = '';
|
||
if (k.telefon) {
|
||
var knopf = document.createElement('a');
|
||
knopf.className = 'cta';
|
||
knopf.href = 'tel:' + String(k.telefon).replace(/[^\d+]/g, '');
|
||
knopf.textContent = '📞 ' + k.telefon;
|
||
box.appendChild(knopf);
|
||
}
|
||
function angabe(label, wert, hervorheben) {
|
||
if (!wert) return;
|
||
var p = document.createElement('p'); p.className = 'kontakt-zeile';
|
||
var b = document.createElement('strong'); b.textContent = label + ': ';
|
||
var w = document.createElement('span'); w.textContent = wert;
|
||
if (hervorheben) w.className = 'az-wert';
|
||
p.appendChild(b); p.appendChild(w); box.appendChild(p);
|
||
}
|
||
angabe(t('ansprechpartner'), k.ansprechpartner);
|
||
angabe(t('erreichbar'), k.sprechzeiten);
|
||
angabe(t('gefragt'), k.aktenzeichen, true);
|
||
|
||
var liste = document.getElementById('ergAnrufSaetze');
|
||
liste.innerHTML = '';
|
||
if (saetze.length) {
|
||
var kopf = document.createElement('p');
|
||
kopf.style.marginTop = '12px';
|
||
var stark = document.createElement('strong'); stark.textContent = t('amTelefon'); kopf.appendChild(stark);
|
||
liste.appendChild(kopf);
|
||
var angezeigt = versionen.length ? versionen[aktiveVersion].code : zielsprache();
|
||
var mitBedeutung = angezeigt !== 'de';
|
||
saetze.forEach(function (s) {
|
||
var d = document.createElement('div'); d.className = 'anruf-satz';
|
||
var de = document.createElement('p'); de.className = 'anruf-de';
|
||
de.setAttribute('dir', 'ltr'); // wird auf Deutsch gesagt — auch auf arabischer Seite links-nach-rechts
|
||
de.textContent = '„' + s.deutsch + '“';
|
||
d.appendChild(de);
|
||
if (mitBedeutung && s.bedeutung && s.bedeutung !== s.deutsch) {
|
||
var be = document.createElement('p'); be.className = 'anruf-bed';
|
||
be.setAttribute('dir', 'auto');
|
||
be.textContent = s.bedeutung;
|
||
d.appendChild(be);
|
||
}
|
||
liste.appendChild(d);
|
||
});
|
||
}
|
||
karte.style.display = 'block';
|
||
}
|
||
|
||
// ---- Guthaben kaufen ------------------------------------------------
|
||
function rendrePakete() {
|
||
paketListe.innerHTML = '';
|
||
PAKETE.forEach(function (p) {
|
||
var b = document.createElement('button');
|
||
b.className = 'cta';
|
||
b.type = 'button';
|
||
b.style.width = '100%';
|
||
b.style.display = 'flex';
|
||
b.style.justifyContent = 'space-between';
|
||
b.style.alignItems = 'center';
|
||
var links = document.createElement('span');
|
||
links.textContent = (p.best ? '⭐ ' : '') + p.titel;
|
||
var rechts = document.createElement('strong');
|
||
rechts.textContent = p.preis;
|
||
b.appendChild(links);
|
||
b.appendChild(rechts);
|
||
b.addEventListener('click', function () { starteKauf(p.priceId, b); });
|
||
paketListe.appendChild(b);
|
||
});
|
||
}
|
||
|
||
function starteKauf(priceId, knopf) {
|
||
if (!kaufKonsens.checked) {
|
||
kaufFehler.style.display = 'block';
|
||
kaufFehler.textContent = 'Bitte bestätigen Sie zuerst das Häkchen, um fortzufahren.';
|
||
kaufKonsens.focus();
|
||
return;
|
||
}
|
||
kaufFehler.style.display = 'none';
|
||
var alt = knopf.innerHTML;
|
||
knopf.disabled = true;
|
||
knopf.textContent = 'Weiter zu Stripe …';
|
||
fetch(KAUFEN_ENDPUNKT, {
|
||
method: 'POST',
|
||
headers: { 'content-type': 'application/json' },
|
||
body: JSON.stringify({ priceId: priceId, sprache: zielsprache() }),
|
||
})
|
||
.then(function (r) { return r.json(); })
|
||
.then(function (d) {
|
||
if (d && d.url) { window.location.href = d.url; return; }
|
||
throw new Error('keine url');
|
||
})
|
||
.catch(function () {
|
||
knopf.disabled = false;
|
||
knopf.innerHTML = alt;
|
||
kaufFehler.style.display = 'block';
|
||
kaufFehler.textContent = 'Die Bezahlseite konnte nicht geöffnet werden. Bitte erneut versuchen.';
|
||
});
|
||
}
|
||
|
||
// ---- Rückkehr von Stripe / Wallet-Wiederherstellung -----------------
|
||
function reinigeUrl() {
|
||
if (window.history.replaceState) {
|
||
window.history.replaceState({}, document.title, window.location.pathname);
|
||
}
|
||
}
|
||
|
||
function holeKaufStatus(sid, n) {
|
||
fetch(STATUS_ENDPUNKT + '?session_id=' + encodeURIComponent(sid))
|
||
.then(function (r) { return r.json().then(function (d) { return { ok: r.ok, d: d }; }); })
|
||
.then(function (res) {
|
||
if (res.ok && res.d && res.d.token) {
|
||
setzeWallet(res.d.token);
|
||
setzeGuthaben(res.d.guthaben);
|
||
setzeEinrichtung(null);
|
||
status.style.display = 'none';
|
||
kaufOk.style.display = 'block';
|
||
zeigeStand();
|
||
document.getElementById('demo').scrollIntoView({ behavior: 'smooth' });
|
||
return;
|
||
}
|
||
if (n < 4) { setTimeout(function () { holeKaufStatus(sid, n + 1); }, 1500); return; }
|
||
status.style.display = 'none';
|
||
zeigeFehler('Ihre Zahlung wird noch bestätigt. Bitte laden Sie die Seite in einer Minute neu.');
|
||
})
|
||
.catch(function () {
|
||
status.style.display = 'none';
|
||
zeigeFehler('Die Zahlungsbestätigung konnte nicht geladen werden. Bitte Seite neu laden.');
|
||
});
|
||
}
|
||
|
||
// ---- Einrichtungs-Code einlösen ------------------------------------
|
||
/** „mbe k7qx", „MBEK7QX", „MBE-K7QX" → „MBE-K7QX" (wie im Worker). */
|
||
function normCode(c) {
|
||
var roh = String(c || '').toUpperCase().replace(/[^A-Z0-9]/g, '');
|
||
return roh.length > 4 ? roh.slice(0, -4) + '-' + roh.slice(-4) : roh;
|
||
}
|
||
function loeseCodeEin(code) {
|
||
code = normCode(code);
|
||
if (!code) { codeFeld.focus(); return; }
|
||
if (walletToken() && guthaben() > 0 && !einrichtung() &&
|
||
!window.confirm('Sie haben noch gekauftes Guthaben. Wenn Sie den Code einlösen, nutzt diese Seite den Code. Ihr gekauftes Guthaben bleibt über den Link in Ihrer Kauf-E-Mail erhalten. Weiter?')) {
|
||
return;
|
||
}
|
||
codeKnopf.disabled = true;
|
||
fehlerBox.style.display = 'none';
|
||
fetch(BASIS + '/code', {
|
||
method: 'POST',
|
||
headers: { 'content-type': 'application/json' },
|
||
body: JSON.stringify({ code: code }),
|
||
})
|
||
.then(function (r) { return r.json().then(function (d) { return { ok: r.ok, d: d }; }); })
|
||
.then(function (res) {
|
||
codeKnopf.disabled = false;
|
||
if (!res.ok || !res.d.token) {
|
||
codeTeil.open = true;
|
||
codeFeld.value = code;
|
||
zeigeFehler(res.d.meldung || 'Der Code konnte nicht eingelöst werden.');
|
||
return;
|
||
}
|
||
setzeWallet(res.d.token);
|
||
setzeGuthaben(res.d.guthaben);
|
||
setzeEinrichtung(res.d.einrichtung);
|
||
if (res.d.ablauf) speicher.set(ABLAUF_SCHLUESSEL, res.d.ablauf);
|
||
speicher.set(CODE_SCHLUESSEL, code);
|
||
zeichneHandyQr();
|
||
zeigeStand();
|
||
if (res.d.guthaben <= 0) return; // zeigeStand zeigt den Leer-Hinweis mit Telefonnummer
|
||
kaufOk.textContent = t('codeEingeloest', { name: res.d.einrichtung });
|
||
kaufOk.style.display = 'block';
|
||
})
|
||
.catch(function () {
|
||
codeKnopf.disabled = false;
|
||
zeigeFehler('Verbindung fehlgeschlagen. Bitte prüfen Sie Ihr Internet und versuchen Sie es erneut.');
|
||
});
|
||
}
|
||
codeKnopf.addEventListener('click', function () { loeseCodeEin(codeFeld.value); });
|
||
codeFeld.addEventListener('keydown', function (e) { if (e.key === 'Enter') loeseCodeEin(codeFeld.value); });
|
||
|
||
// ---- Handy ↔ PC ------------------------------------------------------
|
||
var verbundenBanner = document.getElementById('verbundenBanner');
|
||
var handyKnopf = document.getElementById('handyKnopf');
|
||
var handyBox = document.getElementById('handyBox');
|
||
var handyQr = document.getElementById('handyQr');
|
||
var handyZustand = document.getElementById('handyZustand');
|
||
var handyDeutsch = document.getElementById('handyDeutsch');
|
||
var handyDeutschZeile = document.getElementById('handyDeutschZeile');
|
||
var UEBERGABE_MS = BKUebergabe.LEBENSDAUER_MIN * 60 * 1000;
|
||
|
||
function zeigeQr(el, adresse) {
|
||
el.innerHTML = BKUebergabe.qrSvg(adresse);
|
||
el.setAttribute('data-link', adresse); // zum Testen ohne Kamera
|
||
}
|
||
|
||
// PC: QR-Code zeigen und warten, bis das Handy fotografiert hat
|
||
var pcWarten = null; // { ue, bis, timer }
|
||
var pruefeLaeuft = false;
|
||
function zeichneHandyQr() {
|
||
if (!pcWarten) return;
|
||
var sprache = zielsprache();
|
||
// d=1 auch bei „Deutsch" am PC: Stellt die Person am Handy ihre eigene
|
||
// Sprache ein, bekommt die Beraterin trotzdem die deutsche Fassung.
|
||
zeigeQr(handyQr, BKUebergabe.adresse({
|
||
// „Deutsch" am PC heißt nur: nichts vorgeben — dann erkennt das Handy seine Sprache selbst
|
||
a: 'k', s: pcWarten.ue.id, k: pcWarten.ue.k, l: sprache !== 'de' ? sprache : '',
|
||
d: handyDeutsch.checked ? '1' : '',
|
||
w: hatGuthaben() ? walletToken() : '',
|
||
e: hatGuthaben() ? einrichtung() : '',
|
||
}));
|
||
}
|
||
window.addEventListener('beforeunload', function (ev) {
|
||
if (pcWarten) { ev.preventDefault(); ev.returnValue = ''; }
|
||
});
|
||
function stoppeWarten() {
|
||
if (pcWarten && pcWarten.timer) clearInterval(pcWarten.timer);
|
||
pcWarten = null;
|
||
}
|
||
function schliesseHandyBox() {
|
||
handyBox.style.display = 'none';
|
||
handyKnopf.style.display = istPc ? 'block' : 'none';
|
||
}
|
||
handyKnopf.addEventListener('click', async function () {
|
||
stoppeWarten();
|
||
fehlerBox.style.display = 'none';
|
||
try {
|
||
pcWarten = { ue: await BKUebergabe.neu(), bis: Date.now() + UEBERGABE_MS };
|
||
} catch (e) {
|
||
zeigeFehler('Dieser Browser kann die verschlüsselte Übergabe nicht. Bitte einen aktuellen Browser verwenden.');
|
||
return;
|
||
}
|
||
zeichneHandyQr();
|
||
handyZustand.textContent = '⏳ Warte auf das Handy …';
|
||
handyKnopf.style.display = 'none';
|
||
handyBox.style.display = 'block';
|
||
handyBox.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||
pcWarten.timer = setInterval(pruefeHandy, 1500);
|
||
});
|
||
handyDeutsch.addEventListener('change', zeichneHandyQr);
|
||
document.getElementById('handyAbbrechen').addEventListener('click', function () {
|
||
if (pcWarten) BKUebergabe.loeschen(pcWarten.ue);
|
||
stoppeWarten();
|
||
schliesseHandyBox();
|
||
});
|
||
|
||
async function pruefeHandy() {
|
||
if (!pcWarten || pruefeLaeuft) return;
|
||
if (Date.now() > pcWarten.bis) {
|
||
stoppeWarten();
|
||
schliesseHandyBox();
|
||
zeigeFehler('Der QR-Code ist abgelaufen. Tippen Sie noch einmal auf „Lieber mit dem Handy fotografieren".');
|
||
return;
|
||
}
|
||
pruefeLaeuft = true;
|
||
try {
|
||
var ue = pcWarten.ue;
|
||
var f = await BKUebergabe.holen(ue);
|
||
if (!pcWarten || pcWarten.ue !== ue) return; // inzwischen abgebrochen
|
||
if ((pcWarten.fehlversuche || 0) >= 5 && !f.status) handyZustand.textContent = '⏳ Warte auf das Handy …';
|
||
pcWarten.fehlversuche = 0;
|
||
// Jede neue Meldung vom Handy verlängert das Warten (nicht ab QR-Anzeige rechnen)
|
||
var schritt = f.status ? f.status.schritt + '|' + (f.status.meldung || '') : '';
|
||
if (schritt && schritt !== pcWarten.letzterSchritt) {
|
||
pcWarten.letzterSchritt = schritt;
|
||
// Verbunden: großzügiger warten (Gespräch vor dem Foto kann dauern)
|
||
pcWarten.bis = Date.now() + 3 * UEBERGABE_MS;
|
||
if (f.status.schritt === 'analyse') pcWarten.analyseSeit = Date.now();
|
||
}
|
||
if (f.status && f.status.schritt === 'fehler' && /^kontingent_/.test(f.status.code || '')) {
|
||
// Kontingent leer/abgelaufen: Warten ist sinnlos
|
||
stoppeWarten();
|
||
BKUebergabe.loeschen(ue);
|
||
schliesseHandyBox();
|
||
setzeGuthaben(0);
|
||
zeigeStand();
|
||
return;
|
||
}
|
||
if (f.status && f.status.schritt === 'fehler' && f.status.code === 'kein_zugang') {
|
||
stoppeWarten();
|
||
BKUebergabe.loeschen(ue);
|
||
schliesseHandyBox();
|
||
zeigeFehler('Am Handy sind die kostenlosen Analysen verbraucht. Bitte hier unten den Code der Beratungsstelle einlösen und dann noch einmal auf „Lieber mit dem Handy fotografieren" tippen.');
|
||
codeTeil.style.display = 'block';
|
||
codeTeil.open = true;
|
||
return;
|
||
}
|
||
if (f.ergebnis) {
|
||
stoppeWarten();
|
||
BKUebergabe.loeschen(ue);
|
||
schliesseHandyBox();
|
||
zeigeVomHandy(f.ergebnis);
|
||
} else if (f.status) {
|
||
var langsam = f.status.schritt === 'analyse' && pcWarten.analyseSeit && Date.now() - pcWarten.analyseSeit > 3 * 60 * 1000;
|
||
handyZustand.textContent =
|
||
langsam ? '⚠️ Das dauert ungewöhnlich lange. Bitte am Handy nachsehen: Steht die Erklärung dort schon, auf „Noch einmal an den Computer senden" tippen. Sonst hier abbrechen und neu verbinden.' :
|
||
f.status.schritt === 'analyse' ? '⏳ Das Handy hat fotografiert. Der Brief wird erklärt (ein bis zwei Minuten) …' :
|
||
f.status.schritt === 'fehler' ? '⚠️ Am Handy hat es nicht geklappt: ' + (f.status.meldung || '') + ' Bitte dort noch einmal versuchen.' :
|
||
'✅ Handy verbunden. Jetzt am Handy den Brief fotografieren.';
|
||
}
|
||
} catch (e) {
|
||
// kurz keine Verbindung — beim nächsten Durchlauf erneut versuchen,
|
||
// aber nach mehreren Fehlschlägen sichtbar sagen, woran es liegt
|
||
if (pcWarten) {
|
||
pcWarten.fehlversuche = (pcWarten.fehlversuche || 0) + 1;
|
||
if (pcWarten.fehlversuche >= 5) handyZustand.textContent = '⚠️ Keine Verbindung zum Server. Bitte das Internet dieses Computers prüfen.';
|
||
}
|
||
} finally {
|
||
pruefeLaeuft = false;
|
||
}
|
||
}
|
||
|
||
function zeigeVomHandy(e) {
|
||
// Hat das Handy das Guthaben dieses PCs benutzt, den neuen Stand übernehmen
|
||
if (typeof e.guthaben === 'number' && hatGuthaben()) setzeGuthaben(e.guthaben);
|
||
zeigeAnalyse(bereiteVersionen(e, true));
|
||
werbung.style.display = 'none';
|
||
zeigeStand();
|
||
document.getElementById('ergebnis').scrollIntoView({ behavior: 'smooth' });
|
||
}
|
||
|
||
// PC: Ergebnis per QR-Code aufs Handy mitgeben
|
||
document.getElementById('mitnehmenKnopf').addEventListener('click', async function () {
|
||
var knopf = this;
|
||
if (!letzteDaten) return;
|
||
knopf.disabled = true;
|
||
try {
|
||
var ue = await BKUebergabe.neu();
|
||
await BKUebergabe.senden(ue, 'ergebnis', letzteDaten);
|
||
zeigeQr(document.getElementById('mitnehmenQr'), BKUebergabe.adresse({ a: 'm', s: ue.id, k: ue.k }));
|
||
document.getElementById('mitnehmenBox').style.display = 'block';
|
||
} catch (e) {
|
||
zeigeFehler('Das Ergebnis konnte nicht vorbereitet werden. Bitte prüfen Sie die Internetverbindung.');
|
||
}
|
||
knopf.disabled = false;
|
||
});
|
||
|
||
// PC: Guthaben bzw. Zugang der Beratungsstelle aufs Handy holen
|
||
document.getElementById('guthabenHandyKnopf').addEventListener('click', function () {
|
||
var box = document.getElementById('guthabenQrBox');
|
||
if (box.style.display === 'block') { box.style.display = 'none'; return; }
|
||
zeigeQr(document.getElementById('guthabenQr'), BKUebergabe.adresse({
|
||
a: 'g', w: walletToken(), n: guthaben(), e: einrichtung(),
|
||
}));
|
||
box.style.display = 'block';
|
||
});
|
||
|
||
/** Handy: Seite wurde über einen QR-Code vom PC geöffnet. */
|
||
async function pruefeUebergabe(h) {
|
||
if (!h || !h.a) return;
|
||
if (h.a === 'g') { uebernehmeGuthaben(h); return; }
|
||
var ue = await BKUebergabe.oeffne(h.s, h.k).catch(function () { return null; });
|
||
if (!ue) {
|
||
zeigeFehler(t('qrUngueltig'));
|
||
return;
|
||
}
|
||
|
||
if (h.a === 'k') {
|
||
handyModus = { ue: ue, w: h.w || '', d: h.d === '1', e: h.e || '' };
|
||
if (h.l && SPRACH_NAMEN[h.l]) {
|
||
// nicht dauerhaft speichern: Es ist das Handy der Person, nicht der Beratung
|
||
sitzungsSprache = h.l;
|
||
document.getElementById('sprachWahl').value = h.l;
|
||
}
|
||
wendeUiSpracheAn();
|
||
verbundenBanner.textContent = t('verbunden');
|
||
verbundenBanner.style.display = 'block';
|
||
handyKnopf.style.display = 'none';
|
||
if (handyModus.e) zeigeEinrichtungImKopf(handyModus.e);
|
||
// „verbunden" zuerst melden — zeigeStand meldet danach ggf. „kein Zugang",
|
||
// und das darf nicht von „verbunden" überschrieben werden
|
||
await meldeAnPc({ schritt: 'verbunden' });
|
||
zeigeStand();
|
||
if (handyModus.w) { kaufBlock.style.display = 'none'; codeTeil.style.display = 'none'; }
|
||
verbundenBanner.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
return;
|
||
}
|
||
|
||
if (h.a === 'm') {
|
||
leseModus = true;
|
||
eingabe.style.display = 'none';
|
||
kaufBlock.style.display = 'none';
|
||
status.querySelector('.gross').textContent = t('statusLaden');
|
||
status.style.display = 'block';
|
||
var f = null;
|
||
try { f = await BKUebergabe.holen(ue); } catch (e) {}
|
||
status.style.display = 'none';
|
||
if (!f || !f.ergebnis) {
|
||
leseModus = false;
|
||
zeigeStand();
|
||
zeigeFehler(t('qrAbgelaufen', { min: BKUebergabe.LEBENSDAUER_MIN }));
|
||
return;
|
||
}
|
||
BKUebergabe.loeschen(ue);
|
||
codeTeil.style.display = 'none';
|
||
kaufBlock.style.display = 'none';
|
||
verbundenBanner.textContent = t('mitgenommen');
|
||
verbundenBanner.style.display = 'block';
|
||
zeigeAnalyse(bereiteVersionen(f.ergebnis, false));
|
||
document.getElementById('ergebnis').scrollIntoView({ behavior: 'smooth' });
|
||
}
|
||
}
|
||
|
||
function uebernehmeGuthaben(h) {
|
||
if (!h.w) return;
|
||
if (walletToken() && walletToken() !== h.w && guthaben() > 0 &&
|
||
!window.confirm('Auf diesem Handy ist schon Guthaben gespeichert. Durch das vom Computer ersetzen? Ihr bisheriges Guthaben bleibt über den Link in Ihrer Kauf-E-Mail erhalten.')) {
|
||
return;
|
||
}
|
||
setzeWallet(h.w);
|
||
setzeGuthaben(parseInt(h.n, 10) || 1);
|
||
setzeEinrichtung(h.e || null);
|
||
kaufOk.textContent = h.e ? t('uebernommen', { name: h.e }) : '✅ Guthaben übernommen.';
|
||
kaufOk.style.display = 'block';
|
||
zeigeStand();
|
||
}
|
||
|
||
function vergissHandySitzung() { try { sessionStorage.removeItem('bk_handy'); } catch (e) {} }
|
||
|
||
function pruefeRueckkehr() {
|
||
var p = new URLSearchParams(window.location.search);
|
||
var w = p.get('wallet');
|
||
if (w) { setzeWallet(w); setzeGuthaben(Math.max(guthaben(), 1)); setzeEinrichtung(null); reinigeUrl(); return; }
|
||
var c = p.get('code');
|
||
if (c) { reinigeUrl(); loeseCodeEin(c); return; }
|
||
var kauf = p.get('kauf');
|
||
if (kauf === 'abbruch') {
|
||
reinigeUrl();
|
||
fehlerBox.style.display = 'block';
|
||
fehlerBox.textContent = 'Kauf abgebrochen — es wurde nichts berechnet.';
|
||
return;
|
||
}
|
||
if (kauf === 'ok') {
|
||
var sid = p.get('session_id');
|
||
reinigeUrl();
|
||
if (!sid) return;
|
||
status.querySelector('.gross').textContent = 'Ihr Kauf wird bestätigt …';
|
||
eingabe.style.display = 'none';
|
||
status.style.display = 'block';
|
||
holeKaufStatus(sid, 0);
|
||
}
|
||
}
|
||
|
||
kaufKonsens.addEventListener('change', function () {
|
||
if (kaufKonsens.checked) kaufFehler.style.display = 'none';
|
||
});
|
||
|
||
// Sprachauswahl aufbauen
|
||
(function () {
|
||
var feld = document.getElementById('sprachWahl');
|
||
var aktiv = zielsprache();
|
||
Object.keys(SPRACH_NAMEN).forEach(function (c) {
|
||
var o = document.createElement('option');
|
||
o.value = c; o.textContent = SPRACH_NAMEN[c] + (SPRACH_DEUTSCH[c] ? ' – ' + SPRACH_DEUTSCH[c] : '');
|
||
if (c === aktiv) o.selected = true;
|
||
feld.appendChild(o);
|
||
});
|
||
feld.addEventListener('change', function () {
|
||
speicher.set(SPRACH_SCHLUESSEL, feld.value);
|
||
sitzungsSprache = null;
|
||
wendeUiSpracheAn();
|
||
zeigeStand();
|
||
if (/[?&]sprache=/.test(window.location.search) && window.history.replaceState) {
|
||
var u = new URL(window.location.href);
|
||
u.searchParams.delete('sprache');
|
||
window.history.replaceState(null, document.title, u.pathname + u.search + u.hash);
|
||
}
|
||
zeichneHandyQr();
|
||
});
|
||
})();
|
||
|
||
rendrePakete();
|
||
wendeUiSpracheAn();
|
||
var startHash = BKUebergabe.ausAdresse();
|
||
var startSuche = window.location.search; // vor pruefeRueckkehr, das die Adresse aufräumt
|
||
// Handy-Verbindung übersteht ein Neuladen (z. B. wenn Android den Tab beim
|
||
// Kamera-Wechsel neu lädt). sessionStorage gilt nur für diesen einen Tab.
|
||
try {
|
||
if (startHash && startHash.a === 'k') sessionStorage.setItem('bk_handy', JSON.stringify(startHash));
|
||
else if (!startHash) { var gemerkt = sessionStorage.getItem('bk_handy'); if (gemerkt) startHash = JSON.parse(gemerkt); }
|
||
} catch (e) {}
|
||
if (startHash && window.history.replaceState) {
|
||
// Schlüssel nicht im Browser-Verlauf stehen lassen
|
||
window.history.replaceState(null, document.title, window.location.pathname + window.location.search);
|
||
}
|
||
pruefeRueckkehr();
|
||
zeigeStand();
|
||
// Einrichtungs-Zugang leer? Einmal still nachfragen — vielleicht wurde
|
||
// inzwischen aufgeladen oder verlängert (genau ein Aufruf pro Seitenaufruf)
|
||
if (!startHash && einrichtung() && walletToken() && guthaben() <= 0 && speicher.get(CODE_SCHLUESSEL)
|
||
&& !/[?&](code|wallet|kauf)=/.test(startSuche)) {
|
||
loeseCodeEin(speicher.get(CODE_SCHLUESSEL));
|
||
}
|
||
handyKnopf.style.display = istPc ? 'block' : 'none';
|
||
document.getElementById('guthabenHandyKnopf').style.display = istPc ? '' : 'none';
|
||
pruefeUebergabe(startHash);
|
||
})();
|
||
</script>
|
||
<!-- Cloudflare Web Analytics: cookielose Reichweitenmessung, keine Nutzer-Profile -->
|
||
<script type='module' src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon='{"token": "11f017e9a707466b9151921db6125dde"}'></script>
|
||
</body>
|
||
</html>
|