PageSourceSearch

https://nlp.de/booking/?ticket=basic

html nlp.de collected 2026-10-02 13:38:11 UTC 66,708 bytes, 1,079 lines download raw bytes

1<!DOCTYPE html>
2<html lang="de">
3<head>
4<meta charset="UTF-8">
5<meta name="viewport" content="width=device-width, initial-scale=1.0">
6<meta name="robots" content="noindex, nofollow">
7<title>NHR® 2026 — Buchung</title>
8<link rel="icon" href="../assets/favicon/favicon.ico" sizes="any">
9<link rel="icon" type="image/png" sizes="32x32" href="../assets/favicon/favicon-32.png">
10<link rel="icon" type="image/png" sizes="16x16" href="../assets/favicon/favicon-16.png">
11<link rel="icon" type="image/png" sizes="192x192" href="../assets/favicon/favicon-192.png">
12<link rel="apple-touch-icon" href="../assets/favicon/apple-touch-icon.png">
13<!-- Tracking (GA4 + Meta Pixel) · cockpit-MASKIERT (type="text/plain") → laden erst NACH Einwilligung.
14     SINGLE SOURCE — build-public.sh injiziert das vor </head> in jede Seite. Änderungen NUR hier. -->
15<!-- Google Analytics 4 · Consent-Kategorie "Google Analytics" -->
vendor: 115 bytes, lines 15-16
15
16<script type="text/plain" data-cookiecategory="Google Analytics" src="https://www.googletagmanager.com/gtag/js?id=
16G-EKNB5J0CGQ
vendor: 12 bytes, line 16
16"></script>
17<script type="text/plain" data-cookiecategory="Google Analytics">
18  
vendor: 135 bytes, lines 18-21
18window.dataLayer = window.dataLayer || [];
19  function gtag(){ dataLayer.push(arguments); }
20  gtag('js', new Date());
21  gtag('config', '
21G-EKNB5J0CGQ
vendor: 4 bytes, line 21
21');
22</script>
22
23<!-- Meta Pixel · Consent-Kategorie "Facebook" (kein noscript-Fallback → nichts ohne Consent) -->
24<script type="text/plain" data-cookiecategory="Facebook">
vendor: 400 bytes, lines 24-28
24
25  !function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?n.callMethod.apply(n,arguments):n.queue.push(arguments)};
26  if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
27  t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,document,'script','https://connect.facebook.net/en_US/fbevents.js');
28  fbq('init','
282206061182948811
vendor: 31 bytes, lines 28-29
28');
29  fbq('track','PageView');
30</script>
30
31</head>
32<body>
33<!-- ============================================================
34     NHR 2026 Buchungsformular · nlp.de — STANDALONE
35     Eine Datei: HTML + CSS + JS, kein Framework, kein Webflow.
36     Deployment: diese Datei auf das Hosting laden (z. B. Domain Factory)
37     und direkt verlinken. Logo-Datei "nlp-logo.jpg" daneben ablegen.
38     Webhook-URL + Token unten im 
38<script> bei CONFIG eintragen.
39     ============================================================ -->
40<style>
41  /* ---------- Archia (wie Landingpage); Fallback system-ui ----------
42     Font-Pfade zeigen (wie in der Landing) auf die echten Schriftdateien im Projekt,
43     damit die Schrift auch beim direkten Öffnen der Master-Datei lädt.
44     build-public.sh schreibt sie beim Deploy automatisch auf ../assets/fonts/ um. */
45  @font-face { font-family:'Archia'; src:url('../assets/fonts/archia-regular-webfont.woff2') format('woff2'), url('../assets/fonts/archia-regular-webfont.woff') format('woff'); font-weight:400; font-style:normal; font-display:swap; }
46  @font-face { font-family:'Archia'; src:url('../assets/fonts/archia-medium-webfont.woff2') format('woff2'), url('../assets/fonts/archia-medium-webfont.woff') format('woff'); font-weight:500; font-style:normal; font-display:swap; }
47  @font-face { font-family:'Archia'; src:url('../assets/fonts/archia-semibold-webfont.woff2') format('woff2'), url('../assets/fonts/archia-semibold-webfont.woff') format('woff'); font-weight:600; font-style:normal; font-display:swap; }
48  @font-face { font-family:'Archia'; src:url('../assets/fonts/archia-bold-webfont.woff2') format('woff2'), url('../assets/fonts/archia-bold-webfont.woff') format('woff'); font-weight:700; font-style:normal; font-display:swap; }
49
50  /* ---------- Basis & Variablen (gescoped auf #nhr-booking) ---------- */
51  #nhr-booking{
52    --ink:#1A1A22; --lime:#E4FF00; --bg:#FFFFFF; --bg-soft:#F2F1F7;
53    --line:#E7E6EF; --soft:#F2F1F7; --lav:#C6C4D9; --violet:#5B2FE4;
54    --navy:#5B2FE4; --navy-d:#3A1C9E;
55    --muted:#5b5b66; --ph:#8A8898; --err:#C8102E;
56    font-family:'Archia', system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
57    color:var(--ink); background:var(--bg);
58    -webkit-font-smoothing:antialiased; line-height:1.55;
59    max-width:1160px; margin:0 auto; padding:24px 16px 110px; box-sizing:border-box;
60  }
61  #nhr-booking *,#nhr-booking *::before,#nhr-booking *::after{box-sizing:border-box}
62  #nhr-booking img{max-width:100%}
63
64  /* ---------- Kopf ---------- */
65  .nhr-top{display:flex; align-items:center; justify-content:space-between; gap:16px; margin-bottom:28px}
66  .nhr-logo{height:44px; width:auto; display:block}
67  .nhr-logo-link{display:inline-flex; line-height:0; border-radius:6px; transition:opacity .15s}
68  .nhr-logo-link:hover{opacity:.72}
69  .nhr-logo-link:focus-visible{outline:2px solid var(--ink); outline-offset:4px}
70  @media (min-width:960px){ .nhr-logo{height:50px} }
71  .nhr-alumni{font-size:12px; font-weight:700; letter-spacing:.05em; text-transform:uppercase; color:var(--ink); background:var(--lime); border-radius:999px; padding:8px 14px; text-align:right; line-height:1.3}
72  /* Eyebrow-Label (wie Landing .label) */
73  .nhr-eyebrow{display:block; font-size:15px; font-weight:700; letter-spacing:.12em; text-transform:uppercase; color:var(--violet); margin:0 0 16px}
74  .nhr-h1{font-size:30px; line-height:1.12; font-weight:700; letter-spacing:-.02em; margin:0 0 10px}
75  .nhr-sub{font-size:16px; font-weight:600; color:var(--muted); margin:0 0 32px}
76  .nhr-sub .nhr-pipe{color:var(--line); margin:0 8px}
77
78  /* ---------- Layout: mobile gestapelt, Desktop 2 Spalten ---------- */
79  .nhr-grid{display:grid; grid-template-columns:1fr; gap:32px; align-items:start}
80  .nhr-grid>*{min-width:0} /* verhindert horizontales Aufdrücken durch nowrap-Inhalte */
81  @media (min-width:960px){
82    #nhr-booking{padding:32px 24px 60px}
83    .nhr-h1{font-size:40px}
84    .nhr-grid{grid-template-columns:minmax(0,1fr) 400px; gap:48px}
85    .nhr-aside{position:sticky; top:24px}
86  }
87
88  /* ---------- Ticketbereich ---------- */
89  .nhr-ticket{padding:0}
90  .nhr-ticket-head{display:flex; flex-wrap:wrap; align-items:baseline; justify-content:space-between; gap:8px}
91  .nhr-ticket-name{font-size:20px; font-weight:700; margin:0}
92  .nhr-badge{display:inline-block; background:var(--lime); color:var(--ink); font-size:13px; font-weight:700; padding:4px 10px; border-radius:999px; margin-left:10px; vertical-align:2px; white-space:nowrap}
93  .nhr-ticket-price{font-size:26px; font-weight:700; white-space:nowrap}
94  .nhr-strike{color:var(--ph); text-decoration:line-through; font-weight:600; font-size:16px; margin-right:8px}
95  .nhr-ticket-meta{font-size:14px; color:var(--muted); margin:6px 0 0}
96
97  /* Zwei Ticket-Karten (Basic / Pro) — Look wie Landing-Pricing-Karten */
98  .nhr-tickets{display:grid; grid-template-columns:1fr; gap:32px}
99  .nhr-tk{position:relative; display:block; border:1px solid var(--line); border-radius:20px; padding:30px 28px 28px; cursor:pointer; background:var(--bg); box-shadow:0 8px 24px rgba(58,28,158,.05); transition:transform .15s, border-color .15s, box-shadow .15s}
100  .nhr-tk:hover{transform:translateY(-2px); box-shadow:0 16px 36px rgba(91,47,228,.16)}
101  /* Auswahl (inkl. vorausgewähltem Pro) = helles Lavendel, das lange gehalten wird und erst
102     nach unten in Weiß ausläuft + violetter Rahmen + 1px-Ring + Glühen.
103     Nicht ausgewählte Kachel bleibt neutral weiß und tritt zurück. */
104  .nhr-tk.is-active{background:linear-gradient(180deg, #F1ECFE 0%, #F1ECFE 58%, #FFFFFF 100%); border-color:var(--violet); box-shadow:0 16px 36px rgba(91,47,228,.20), 0 0 0 1px var(--violet)}
105  .nhr-tk-radio{position:absolute; opacity:0; width:0; height:0}
106  .nhr-tk-check{position:absolute; top:26px; right:26px; width:24px; height:24px; border-radius:50%; border:2px solid var(--lav); background:var(--bg); transition:border-color .15s, background .15s}
107  .nhr-tk.is-active .nhr-tk-check{border-color:var(--violet); background:var(--violet) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%23fff' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round' d='M3 8.5l3.2 3.2L13 4.5'/%3E%3C/svg%3E") center/14px no-repeat}
108  .nhr-tk-radio:focus-visible ~ .nhr-tk-check{outline:3px solid var(--lime); outline-offset:2px}
109  .nhr-tk-flag{position:absolute; top:-13px; left:28px; background:var(--violet); color:#fff; font-size:11px; font-weight:700; letter-spacing:.07em; text-transform:uppercase; padding:6px 13px; border-radius:999px}
110  .nhr-tk-flag .nhr-flag-short{display:none}
111  @media (max-width:600px){
112    .nhr-tk-flag .nhr-flag-full{display:none}
113    .nhr-tk-flag .nhr-flag-short{display:inline}
114  }
115  .nhr-tk-name{display:block; font-size:20px; font-weight:700; letter-spacing:.06em; text-transform:uppercase; color:var(--violet); margin:0 0 16px; padding-right:40px}
116  .nhr-tk-was{display:block; font-size:16px; text-decoration:line-through; color:var(--muted); opacity:.65}
117  .nhr-tk-now{display:block; font-size:30px; font-weight:700; color:var(--ink); line-height:1; letter-spacing:-.02em; margin-top:2px}
118  .nhr-tk-save{display:inline-block; background:var(--lime); color:var(--ink); font-size:12px; font-weight:700; padding:5px 12px; border-radius:999px; margin-top:14px}
119  .nhr-tk-unit{display:block; font-size:14px; color:var(--muted); margin-top:14px}
120  .nhr-tk-list{list-style:none; margin:20px 0 0; padding:20px 0 0; border-top:1px solid var(--line); display:grid; gap:11px}
121  .nhr-tk-list li{font-size:14px; color:var(--ink); padding-left:24px; position:relative; line-height:1.5}
122  .nhr-tk-list li::before{content:'✓'; position:absolute; left:0; color:var(--violet); font-weight:700}
123
124  /* Add-on-Toggle (Simultanübersetzung) — klar abgesetzt von den Tickets */
125  .nhr-eyebrow--addon{margin:34px 0 12px}
126  .nhr-addons{margin-top:0; display:grid; gap:10px}
127  .nhr-addon{display:flex; align-items:center; gap:14px; border:1px solid var(--line); border-radius:14px; padding:16px 18px; cursor:pointer; background:linear-gradient(180deg, #F6F2FE 0%, #F6F2FE 55%, #FFFFFF 100%); transition:border-color .15s, background .15s, box-shadow .15s; min-height:64px}
128  .nhr-addon.is-active{background:linear-gradient(180deg, #F1ECFE 0%, #F1ECFE 55%, #FFFFFF 100%); border-color:var(--violet); box-shadow:0 0 0 1px var(--violet)}
129  .nhr-addon-txt{flex:1; min-width:0}
130  .nhr-addon-title{font-size:16px; font-weight:700; display:block}
131  .nhr-addon-opt{font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.05em; color:var(--muted); background:var(--soft); border:1px solid var(--line); padding:2px 8px; border-radius:999px; margin-left:8px; vertical-align:2px; white-space:nowrap}
132  .nhr-addon-sub{font-size:13px; color:var(--muted); display:block; margin-top:3px}
133  .nhr-addon-price{font-size:16px; font-weight:700; white-space:nowrap}
134
135  /* Checkboxen (Add-ons, Rechnungsadresse, AGB) */
136  .nhr-check{appearance:none; -webkit-appearance:none; width:26px; height:26px; min-width:26px; border:2px solid var(--violet); border-radius:7px; background:var(--bg); cursor:pointer; margin:0; display:inline-block; transition:background .12s}
137  .nhr-check:checked{background:var(--lime) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%230A0A0A' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round' d='M3 8.5l3.2 3.2L13 4.5'/%3E%3C/svg%3E") center/16px no-repeat}
138  .nhr-check:focus-visible{outline:3px solid var(--lime); outline-offset:2px; border-color:var(--ink)}
139
140  /* ---------- Formular ---------- */
141  .nhr-form{margin-top:28px}
142  .nhr-group-label{font-size:15px; font-weight:700; text-transform:uppercase; letter-spacing:.12em; color:var(--violet); margin:34px 0 14px}
143  .nhr-row{display:grid; gap:14px; margin-bottom:14px}
144  .nhr-row-2{grid-template-columns:1fr 1fr}
145  .nhr-row-anrede{grid-template-columns:1.5fr 1fr}     /* Titel schmaler als Anrede */
146  .nhr-row-plz{grid-template-columns:1fr 2fr}          /* PLZ schmal, Ort breit */
147  .nhr-row-land{grid-template-columns:1fr 1fr}         /* Land = schmales Feld (halbe Breite) */
148  @media (max-width:480px){ .nhr-row-2{grid-template-columns:1fr} }
149
150  .nhr-field label{display:block; font-size:14px; font-weight:700; margin-bottom:6px}
151  .nhr-field label .opt{font-weight:500; color:var(--ph)}
152  .nhr-input{
153    width:100%; font:inherit; font-size:16px; color:var(--ink);
154    background:var(--bg); border:1.5px solid var(--line); border-radius:10px;
155    padding:13px 14px; min-height:50px; transition:border-color .12s;
156  }
157  .nhr-input::placeholder{color:var(--ph); opacity:1; font-size:inherit} /* exakt gleiche Größe, klar ausgegraut */
158  .nhr-input:focus{outline:none; border-color:var(--violet); box-shadow:0 0 0 3px rgba(91,47,228,.16)}
159  select.nhr-input{appearance:none; -webkit-appearance:none; background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath fill='none' stroke='%230A0A0A' stroke-width='2' stroke-linecap='round' d='M1.5 1.5L6 6l4.5-4.5'/%3E%3C/svg%3E"); background-repeat:no-repeat; background-position:right 14px center; background-size:12px; padding-right:38px; cursor:pointer}
160  select.nhr-input.is-placeholder{color:var(--ph)} /* "Bitte auswählen" ausgegraut */
161  textarea.nhr-input{min-height:110px; resize:vertical}
162  .nhr-input.is-invalid{border-color:var(--err)}
163  .nhr-check.is-invalid{border-color:var(--err)}        /* rote Checkbox-Umrandung bei fehlendem Häkchen */
164  .nhr-error{display:none; color:var(--err); font-size:13.5px; font-weight:600; margin:6px 0 0}
165  .nhr-error.show{display:block}
166
167  /* Hervorgehobene Box: abweichende Rechnungsadresse */
168  .nhr-billing-box{border:1.5px solid var(--line); border-radius:12px; background:linear-gradient(180deg, #F6F2FE 0%, #F6F2FE 55%, #FFFFFF 100%); margin-top:26px}
169  .nhr-billing-toggle{display:flex; align-items:center; gap:14px; padding:16px; cursor:pointer; min-height:64px}
170  .nhr-billing-toggle svg{flex:none}
171  .nhr-billing-label{font-size:16px; font-weight:700; flex:1}
172  .nhr-collapse{display:grid; grid-template-rows:0fr; transition:grid-template-rows .35s ease}
173  .nhr-collapse.open{grid-template-rows:1fr}
174  .nhr-collapse-inner{overflow:hidden; min-height:0}
175  .nhr-collapse-pad{padding:4px 16px 18px}
176
177  /* AGB-Box, klar abgesetzt */
178  .nhr-agb{background:linear-gradient(180deg, #F6F2FE 0%, #F6F2FE 55%, #FFFFFF 100%); border:1.5px solid var(--line); border-radius:12px; padding:18px 16px; margin-top:30px}
179  .nhr-agb.is-invalid{border-color:var(--err); box-shadow:0 0 0 1px var(--err)} /* Box rot hervorheben, wenn Zustimmung fehlt */
180  .nhr-agb-row{display:flex; align-items:flex-start; gap:14px; cursor:pointer}
181  .nhr-agb-row .nhr-check{margin-top:1px}
182  .nhr-agb-text{font-size:14.5px; font-weight:500}
183  .nhr-agb-text a{color:var(--violet); font-weight:700; text-decoration:underline; text-underline-offset:2px}
184
185  /* Gutschein-/Aktionscode: Eingabe + Einlösen-Button + Statuszeile */
186  .nhr-code-row{display:flex; gap:10px; align-items:stretch}
187  .nhr-code-row .nhr-input{flex:1; min-width:0}
188  .nhr-code-btn{flex:none; font:inherit; font-size:15px; font-weight:700; color:var(--violet); background:var(--bg); border:1.5px solid var(--violet); border-radius:10px; padding:0 20px; min-height:50px; cursor:pointer; transition:background .12s, color .12s}
189  .nhr-code-btn:hover:not(:disabled){background:var(--violet); color:#fff}
190  .nhr-code-btn:focus-visible{outline:3px solid var(--lime); outline-offset:2px}
191  .nhr-code-btn:disabled{opacity:.55; cursor:default}
192  .nhr-code-status{font-size:13.5px; font-weight:600; margin:8px 0 0}
193  .nhr-code-status.ok{color:#157F3C}
194  .nhr-code-status.err{color:var(--err)}
195  .nhr-card-row--disc .val{color:#157F3C}
196
197  /* Honeypot: für Menschen unsichtbar */
198  .nhr-hp{position:absolute !important; left:-9999px !important; width:1px; height:1px; overflow:hidden}
199
200  /* ---------- Buchungs-Karte (hell, Lavendel · Rundum-Rahmen) ---------- */
201  .nhr-card{background:linear-gradient(160deg, #F1ECFE 0%, #F1ECFE 62%, #FFFFFF 100%); color:var(--ink); border:1.5px solid var(--violet); border-radius:18px; padding:26px 24px; box-shadow:0 22px 50px rgba(91,47,228,.15)}
202  .nhr-card-title{font-size:19px; font-weight:700; margin:0 0 16px}
203  .nhr-card-row{display:flex; justify-content:space-between; align-items:baseline; gap:12px; font-size:15px; padding:6px 0}
204  .nhr-card-row[hidden]{display:none}
205  .nhr-card-row .lbl{color:var(--muted)}
206  .nhr-card-row .val{font-weight:700; white-space:nowrap}
207  .nhr-card-div{border:0; border-top:1px solid var(--line); margin:12px 0}
208  .nhr-card-reg{display:flex; justify-content:space-between; font-size:14px; color:var(--muted); padding:2px 0}
209  .nhr-card-reg .val{text-decoration:line-through}
210  .nhr-card-total{display:flex; justify-content:space-between; align-items:baseline; gap:10px; padding:6px 0 2px}
211  .nhr-card-total .lbl{font-size:15px; font-weight:700}
212  .nhr-card-total .val{font-size:30px; font-weight:700; color:var(--violet); white-space:nowrap}
213  .nhr-card .nhr-badge{margin-left:0}
214  /* CTA */
215  .nhr-cta{display:block; width:100%; background:var(--violet); color:#fff; font:inherit; font-size:19px; font-weight:700; letter-spacing:.01em; border:0; border-radius:12px; padding:19px 20px; min-height:60px; cursor:pointer; transition:transform .08s, box-shadow .15s}
216  .nhr-cta:hover:not(:disabled){transform:translateY(-1px); box-shadow:0 10px 26px rgba(91,47,228,.35)}
217  .nhr-cta:focus-visible{outline:3px solid var(--ink); outline-offset:2px}
218  .nhr-card .nhr-cta:focus-visible{outline-color:var(--ink)}
219  .nhr-cta:disabled{background:#DAD8E8; color:#9A98AD; cursor:not-allowed}
220  .nhr-micro{text-align:center; font-size:13px; color:var(--muted); margin:10px 0 0}
221  .nhr-submit-error{display:none; background:#FFFFFF; color:var(--err); font-size:14px; font-weight:600; border-radius:10px; padding:12px 14px; margin-top:12px}
222  .nhr-submit-error.show{display:block}
223  .nhr-submit-error p{margin:0}
224  .nhr-error-actions{display:flex; flex-wrap:wrap; gap:10px; align-items:center; margin-top:10px}
225  .nhr-retry{background:var(--ink); color:#FFFFFF; border:0; border-radius:8px; padding:10px 16px; font:inherit; font-size:14px; font-weight:700; cursor:pointer; min-height:42px}
226  .nhr-retry:focus-visible{outline:3px solid var(--lime); outline-offset:2px}
227  .nhr-mailto{color:var(--ink); font-size:14px; font-weight:700; text-decoration:underline; text-underline-offset:2px; padding:8px 2px}
228
229  /* ---------- Mobile Sticky-Leiste ---------- */
230  .nhr-bar{position:fixed; left:0; right:0; bottom:0; z-index:9999; background:var(--bg); border-top:1.5px solid var(--line); padding:10px 16px calc(10px + env(safe-area-inset-bottom)); display:flex; align-items:center; gap:14px}
231  .nhr-bar-price .reg{display:block; font-size:12px; color:var(--ph); text-decoration:line-through; line-height:1.2}
232  .nhr-bar-price .now{display:block; font-size:20px; font-weight:700; line-height:1.2; white-space:nowrap}
233  .nhr-bar .nhr-cta{flex:1; font-size:15px; padding:14px 12px; min-height:52px}
234  .nhr-bar .nhr-cta:focus-visible{outline-color:var(--ink)}
235  .nhr-bar .nhr-cta:disabled{background:var(--soft); color:var(--ph)}
236  @media (min-width:960px){
237    .nhr-bar{display:none}            /* Desktop: CTA nur in der Karte */
238  }
239  @media (max-width:959.5px){
240    .nhr-card .nhr-cta{display:none}  /* Mobil: CTA nur in der Sticky-Leiste → genau EIN sichtbarer CTA */
241  }
242
243  /* ---------- Erfolgs-Zustand ---------- */
244  .nhr-success{display:none; border:1px solid var(--line); border-radius:16px; padding:40px 24px; text-align:center; max-width:640px; margin:0 auto}
245  .nhr-success.show{display:block}
246  .nhr-success-icon{width:64px; height:64px; border-radius:50%; background:var(--lime); display:flex; align-items:center; justify-content:center; margin:0 auto 18px}
247  .nhr-success h2{font-size:26px; font-weight:700; margin:0 0 10px; letter-spacing:-.01em}
248  .nhr-success p{font-size:16px; color:var(--muted); margin:0}
249
250  /* ---------- Footer ---------- */
251  .nhr-foot{display:flex; flex-wrap:wrap; justify-content:space-between; align-items:center; gap:12px 24px; margin-top:48px; padding-top:24px; border-top:1px solid var(--line); font-size:14px}
252  .nhr-foot a{color:var(--muted); text-decoration:none; font-weight:600; transition:color .15s}
253  .nhr-foot a:hover{color:var(--violet)}
254  .nhr-foot-links{display:flex; flex-wrap:wrap; gap:18px}
255</style>
256
257<div id="nhr-booking">
258
259  <!-- ======================= KOPF ======================= -->
260  <header class="nhr-top">
261    <a class="nhr-logo-link" href="../index.html" aria-label="Zurück zur Startseite"><svg class="nhr-logo" viewBox="0 0 56.7 56.4" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><g><g><path d="M12.1,7.7c-2.1,0-4,0.8-5.1,2.2l0-1.8H0.8v19.9H7V16.6c0-1.8,1.4-3.2,3.2-3.2c1.8,0,3.1,1.3,3.1,3.1v11.5h6.1V14.8 C19.4,10.7,16.3,7.7,12.1,7.7z"/><polygon points="30.7,22.8 30.7,0 21.5,0 21.5,5.3 24.9,5.3 24.9,22.8 21.5,22.8 21.5,28.1 34.1,28.1 34.1,22.8 "/><path d="M47.4,7.7c-2.3,0-4.1,0.8-5.3,2.3l0-1.8h-5.9v25.7c1-0.2,2-0.4,3-0.4c1,0,1.9,0.1,2.8,0.3v-7.8h0c1.4,1.6,3.3,2.5,5.6,2.5 c5,0,9-4.6,9-10.4C56.7,12.3,52.6,7.7,47.4,7.7z M46.4,22.9c-2.6,0-4.4-2-4.4-4.8c0-2.7,1.9-4.8,4.4-4.8c2.6,0,4.4,2,4.4,4.8 C50.7,20.9,48.9,22.9,46.4,22.9z"/></g><g><path d="M3.5,49.2c-2,0-3.5,1.4-3.5,3.3c0,1.9,1.5,3.3,3.5,3.3c2,0,3.4-1.4,3.4-3.3S5.4,49.2,3.5,49.2z"/><path d="M21.5,30v8h0c-1.4-1.6-3.3-2.5-5.6-2.5c-5,0-9,4.6-9,10.4c0,5.8,4.1,10.4,9.3,10.4c2.3,0,4.1-0.8,5.3-2.3l0,1.8h5.9V30 H21.5z M17.2,50.8c-2.6,0-4.4-2-4.4-4.8c0-2.8,1.8-4.8,4.4-4.8c2.6,0,4.4,2,4.4,4.8C21.6,48.7,19.7,50.8,17.2,50.8z"/><path d="M43.1,49.3c-0.7,1.2-2,1.9-3.8,1.9c-2.4,0-4-1.4-4.2-3.6h14v-1.6c0-6.1-4.1-10.4-9.9-10.4c-5.8,0-10.1,4.5-10.1,10.4 c0,6,4.3,10.4,10.3,10.4c4.5,0,7.8-2.2,9.1-6L43.1,49.3z M39.2,40.5c2,0,3.6,1.2,4,3h-8C35.6,41.9,37,40.5,39.2,40.5z"/></g></g></svg></a>
262    <p class="nhr-alumni">Early-Bird-Offer&nbsp;·&nbsp;begrenztes Kontingent</p>
263  </header>
264  <h1 class="nhr-h1">Neuro Hypnotic Repatterning® mit Dr.&nbsp;Richard Bandler</h1>
265  <p class="nhr-sub">6 Tage live<span class="nhr-pipe">|</span>14.–19. November 2026<span class="nhr-pipe">|</span>Alte Lokhalle Mainz</p>
266
267  <!-- ======================= ERFOLGS-ZUSTAND ======================= -->
268  <div class="nhr-success" id="nhrSuccess" role="status" aria-live="polite">
269    <div class="nhr-success-icon" aria-hidden="true">
270      <svg width="30" height="30" viewBox="0 0 16 16"><path fill="none" stroke="#0A0A0A" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" d="M2.5 8.5l3.5 3.5 7-8"/></svg>
271    </div>
272    <h2>Buchung eingegangen</h2>
273    <p>Du erhältst Buchungsbestätigung und Rechnung in Kürze per E-Mail.</p>
274    <p style="margin-top:8px">Wir freuen uns auf dich in Mainz!</p>
275  </div>
276
277  <!-- ======================= HAUPTLAYOUT ======================= -->
278  <div class="nhr-grid" id="nhrGrid">
279
280    <!-- ---------- LINKS: Formular ---------- -->
281    <form id="nhrForm" novalidate>
282
283      <!-- Ticketbereich (beginnt auf gleicher Höhe wie die Karte rechts) -->
284      <section class="nhr-ticket" aria-label="Ticketauswahl">
285        <p class="nhr-eyebrow">Wähle dein Ticket</p>
286        <div class="nhr-tickets">
287          <label class="nhr-tk" id="tkBasic">
288            <input type="radio" class="nhr-tk-radio" name="ticket" value="basic">
289            <span class="nhr-tk-check" aria-hidden="true"></span>
290            <span class="nhr-tk-name">Basic-Ticket</span>
291            <span class="nhr-tk-was" id="basicReg">regulär 3.970&nbsp;€</span>
292            <span class="nhr-tk-now" id="basicNow">2.670&nbsp;€</span>
293            <span class="nhr-tk-save" id="basicSave">Du sparst 1.300&nbsp;€</span>
294            <span class="nhr-tk-unit">6 Tage NHR®-Intensivausbildung</span>
295            <ul class="nhr-tk-list">
296              <li>6 Tage NHR® live mit Dr. Richard Bandler, John &amp; Kathleen La Valle</li>
297              <li>Offizielles Society-of-NLP™-Zertifikat, von Dr. Bandler unterschrieben</li>
298              <li>Kaffee, Tee &amp; Snacks in den Pausen</li>
299            </ul>
300          </label>
301          <label class="nhr-tk nhr-tk--pro is-active" id="tkPro">
302            <input type="radio" class="nhr-tk-radio" name="ticket" value="pro" checked>
303            <span class="nhr-tk-flag"><span class="nhr-flag-full">Beliebt · VIP-Package</span><span class="nhr-flag-short">Beliebt</span></span>
304            <span class="nhr-tk-check" aria-hidden="true"></span>
305            <span class="nhr-tk-name">Pro-Ticket</span>
306            <span class="nhr-tk-was" id="proReg">regulär 4.970&nbsp;€</span>
307            <span class="nhr-tk-now" id="proNow">3.170&nbsp;€</span>
308            <span class="nhr-tk-save" id="proSave">Du sparst 1.800&nbsp;€</span>
309            <span class="nhr-tk-unit">6 Tage NHR®-Intensivausbildung + VIP-Package</span>
310            <ul class="nhr-tk-list">
311              <li>Alles aus dem Basic-Ticket – plus VIP-Package</li>
312              <li>Sitzplatz in den Reihen 1–3 – Bandler aus nächster Nähe modellieren</li>
313              <li>VIP-Registrierung über eigenen Counter – schneller rein</li>
314            </ul>
315          </label>
316        </div>
317
318        <p class="nhr-eyebrow nhr-eyebrow--addon">Optional zubuchbar</p>
319        <div class="nhr-addons">
320          <label class="nhr-addon" id="addonTrBox">
321            <input type="checkbox" class="nhr-check" id="f-tr" name="simultanuebersetzung">
322            <span class="nhr-addon-txt">
323              <span class="nhr-addon-title">Simultanübersetzung <span class="nhr-addon-opt">optional</span></span>
324              <span class="nhr-addon-sub">Englisch → Deutsch · alle 6 Tage</span>
325            </span>
326            <span class="nhr-addon-price">+200&nbsp;€</span>
327          </label>
328        </div>
329
330      </section>
331
332      <!-- Persönliche Daten -->
333      <div class="nhr-form">
334        <p class="nhr-group-label">Deine Daten</p>
335
336        <div class="nhr-row nhr-row-anrede">
337          <div class="nhr-field">
338            <label for="f-anrede">Anrede*</label>
339            <select class="nhr-input is-placeholder" id="f-anrede" name="anrede" required data-error="Bitte Anrede auswählen.">
340              <option value="" selected disabled>Bitte auswählen</option>
341              <option value="Frau">Frau</option>
342              <option value="Herr">Herr</option>
343              <option value="Divers">Divers</option>
344            </select>
345            <p class="nhr-error" id="err-f-anrede"></p>
346          </div>
347          <div class="nhr-field">
348            <label for="f-titel">Titel <span class="opt">(optional)</span></label>
349            <input class="nhr-input" type="text" id="f-titel" name="titel" placeholder="z.B. Dr." autocomplete="honorific-prefix">
350          </div>
351        </div>
352
353        <div class="nhr-row nhr-row-2">
354          <div class="nhr-field">
355            <label for="f-vorname">Vorname*</label>
356            <input class="nhr-input" type="text" id="f-vorname" name="vorname" required autocomplete="given-name" data-error="Bitte Vornamen eingeben.">
357            <p class="nhr-error" id="err-f-vorname"></p>
358          </div>
359          <div class="nhr-field">
360            <label for="f-nachname">Nachname*</label>
361            <input class="nhr-input" type="text" id="f-nachname" name="nachname" required autocomplete="family-name" data-error="Bitte Nachnamen eingeben.">
362            <p class="nhr-error" id="err-f-nachname"></p>
363          </div>
364        </div>
365
366        <div class="nhr-row nhr-row-2">
367          <div class="nhr-field">
368            <label for="f-email">E-Mail*</label>
369            <input class="nhr-input" type="email" id="f-email" name="email" required autocomplete="email" inputmode="email" data-error="Bitte E-Mail-Adresse eingeben.">
370            <p class="nhr-error" id="err-f-email"></p>
371          </div>
372          <div class="nhr-field">
373            <label for="f-telefon">Telefon*</label>
374            <input class="nhr-input" type="tel" id="f-telefon" name="telefon" required autocomplete="tel" inputmode="tel" data-error="Bitte Telefonnummer eingeben.">
375            <p class="nhr-error" id="err-f-telefon"></p>
376          </div>
377        </div>
378
379        <div class="nhr-row">
380          <div class="nhr-field">
381            <label for="f-vorkenntnisse">NLP-Vorkenntnisse <span class="opt">(optional)</span></label>
382            <select class="nhr-input is-placeholder" id="f-vorkenntnisse" name="vorkenntnisse">
383              <option value="" selected>Bitte auswählen</option>
384              <option value="None">Keine Vorkenntnisse</option>
385              <option value="NLP Practitioner">NLP-Practitioner</option>
386              <option value="NLP Master">NLP-Master</option>
387              <option value="NLP Coach">NLP-Coach</option>
388              <option value="NLP Trainer">NLP-Trainer</option>
389            </select>
390          </div>
391        </div>
392
393        <!-- Adresse Teilnehmer:in -->
394        <p class="nhr-group-label">Adresse Teilnehmer:in <span style="text-transform:none; letter-spacing:0; color:var(--muted); font-weight:500">— für dein Zertifikat, zugleich Rechnungsadresse</span></p>
395
396        <div class="nhr-row">
397          <div class="nhr-field">
398            <label for="f-strasse">Straße, Hausnummer*</label>
399            <input class="nhr-input" type="text" id="f-strasse" name="strasse" required autocomplete="street-address" data-error="Bitte Straße und Hausnummer eingeben.">
400            <p class="nhr-error" id="err-f-strasse"></p>
401          </div>
402        </div>
403        <div class="nhr-row nhr-row-plz">
404          <div class="nhr-field">
405            <label for="f-plz">PLZ*</label>
406            <input class="nhr-input" type="text" id="f-plz" name="plz" required autocomplete="postal-code" inputmode="numeric" data-error="Bitte PLZ eingeben.">
407            <p class="nhr-error" id="err-f-plz"></p>
408          </div>
409          <div class="nhr-field">
410            <label for="f-ort">Ort*</label>
411            <input class="nhr-input" type="text" id="f-ort" name="ort" required autocomplete="address-level2" data-error="Bitte Ort eingeben.">
412            <p class="nhr-error" id="err-f-ort"></p>
413          </div>
414        </div>
415        <div class="nhr-row nhr-row-land">
416          <div class="nhr-field">
417            <label for="f-land">Land*</label>
418            <input class="nhr-input" type="text" id="f-land" name="land" required autocomplete="country-name" data-error="Bitte Land eingeben.">
419            <p class="nhr-error" id="err-f-land"></p>
420          </div>
421        </div>
422
423        <!-- Abweichende Rechnungsadresse (hervorgehobene Box mit Icon) -->
424        <div class="nhr-billing-box">
425          <label class="nhr-billing-toggle" for="f-rechnung">
426            <input type="checkbox" class="nhr-check" id="f-rechnung" name="abweichende_rechnungsadresse" aria-expanded="false" aria-controls="billingFields">
427            <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#0A0A0A" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6M8 13h8M8 17h5"/></svg>
428            <span class="nhr-billing-label">Abweichende Rechnungsadresse angeben</span>
429          </label>
430          <div class="nhr-collapse" id="billingFields">
431            <div class="nhr-collapse-inner">
432              <div class="nhr-collapse-pad">
433                <div class="nhr-row nhr-row-anrede">
434                  <div class="nhr-field">
435                    <label for="f-r-anrede">Anrede <span class="opt">(optional)</span></label>
436                    <select class="nhr-input is-placeholder" id="f-r-anrede" name="rechnung_anrede">
437                      <option value="" selected>Bitte auswählen</option>
438                      <option value="Frau">Frau</option>
439                      <option value="Herr">Herr</option>
440                      <option value="Divers">Divers</option>
441                    </select>
442                  </div>
443                  <div class="nhr-field">
444                    <label for="f-r-titel">Titel <span class="opt">(optional)</span></label>
445                    <input class="nhr-input" type="text" id="f-r-titel" name="rechnung_titel" placeholder="z.B. Dr.">
446                  </div>
447                </div>
448                <div class="nhr-row nhr-row-2">
449                  <div class="nhr-field">
450                    <label for="f-r-vorname">Vorname*</label>
451                    <input class="nhr-input" type="text" id="f-r-vorname" name="rechnung_vorname" data-error="Bitte Vornamen eingeben.">
452                    <p class="nhr-error" id="err-f-r-vorname"></p>
453                  </div>
454                  <div class="nhr-field">
455                    <label for="f-r-nachname">Nachname*</label>
456                    <input class="nhr-input" type="text" id="f-r-nachname" name="rechnung_nachname" data-error="Bitte Nachnamen eingeben.">
457                    <p class="nhr-error" id="err-f-r-nachname"></p>
458                  </div>
459                </div>
460                <div class="nhr-row">
461                  <div class="nhr-field">
462                    <label for="f-r-firma">Firma <span class="opt">(optional)</span></label>
463                    <input class="nhr-input" type="text" id="f-r-firma" name="rechnung_firma" autocomplete="organization">
464                  </div>
465                </div>
466                <div class="nhr-row">
467                  <div class="nhr-field">
468                    <label for="f-r-strasse">Straße, Hausnummer*</label>
469                    <input class="nhr-input" type="text" id="f-r-strasse" name="rechnung_strasse" data-error="Bitte Straße und Hausnummer eingeben.">
470                    <p class="nhr-error" id="err-f-r-strasse"></p>
471                  </div>
472                </div>
473                <div class="nhr-row nhr-row-plz">
474                  <div class="nhr-field">
475                    <label for="f-r-plz">PLZ*</label>
476                    <input class="nhr-input" type="text" id="f-r-plz" name="rechnung_plz" inputmode="numeric" data-error="Bitte PLZ eingeben.">
477                    <p class="nhr-error" id="err-f-r-plz"></p>
478                  </div>
479                  <div class="nhr-field">
480                    <label for="f-r-ort">Ort*</label>
481                    <input class="nhr-input" type="text" id="f-r-ort" name="rechnung_ort" data-error="Bitte Ort eingeben.">
482                    <p class="nhr-error" id="err-f-r-ort"></p>
483                  </div>
484                </div>
485                <div class="nhr-row nhr-row-land" style="margin-bottom:0">
486                  <div class="nhr-field">
487                    <label for="f-r-land">Land*</label>
488                    <input class="nhr-input" type="text" id="f-r-land" name="rechnung_land" data-error="Bitte Land eingeben.">
489                    <p class="nhr-error" id="err-f-r-land"></p>
490                  </div>
491                </div>
492              </div>
493            </div>
494          </div>
495        </div>
496
497        <!-- Anmerkungen -->
498        <div class="nhr-row" style="margin-top:26px">
499          <div class="nhr-field">
500            <label for="f-anmerkungen">Anmerkungen <span class="opt">(optional)</span></label>
501            <textarea class="nhr-input" id="f-anmerkungen" name="anmerkungen"></textarea>
502          </div>
503        </div>
504
505        <!-- Gutschein-/Aktionscode (optional) — Live-Prüfung gegen nhr-validate-code -->
506        <div class="nhr-row" style="margin-top:26px">
507          <div class="nhr-field">
508            <label for="f-code">Gutschein-/Aktionscode <span class="opt">(optional)</span></label>
509            <div class="nhr-code-row">
510              <input class="nhr-input" type="text" id="f-code" name="voucher_code" autocomplete="off" autocapitalize="characters" autocorrect="off" spellcheck="false" placeholder="Code eingeben">
511              <button type="button" class="nhr-code-btn" id="codeBtn">Einlösen</button>
512            </div>
513            <p class="nhr-code-status" id="codeStatus" role="status" aria-l
513ive="polite"></p>
514          </div>
515        </div>
516
517        <!-- AGB & Datenschutz -->
518        <div class="nhr-agb" id="agbBox">
519          <label class="nhr-agb-row" for="f-agb">
520            <input type="checkbox" class="nhr-check" id="f-agb" name="agb" required>
521            <span class="nhr-agb-text">Die <a href="../agb.html" target="_blank" rel="noopener">AGB</a> und die <a href="../datenschutz.html" target="_blank" rel="noopener">Datenschutzerklärung</a> habe ich gelesen und stimme diesen zu.*</span>
522          </label>
523          <p class="nhr-error" id="err-f-agb" style="margin-left:40px"></p>
524        </div>
525
526        <!-- License Agreement (Society of NLP) -->
527        <!-- License Agreement (Society of NLP): verlinkte Fassung — Links live auf assets/docs/ (EN-Original + unverbindliche DE-Leseübersetzung; Quelle: shared/NHR_License Agreement_*.pdf, kopiert via build-public.sh). Öffnen im neuen Tab (target="_blank"). Historie/Backup: website/docs/015_entscheidung-license-checkbox. Unterschrift separat im Verlauf (DocuSign). -->
528        <div class="nhr-agb" id="licenseBox" style="margin-top:14px">
529          <label class="nhr-agb-row" for="f-license">
530            <input type="checkbox" class="nhr-check" id="f-license" name="license_agreement" required>
531            <span class="nhr-agb-text">Für die Zertifizierung und Teilnahme an der Ausbildung ist eine Unterzeichnung des <a href="../assets/docs/NHR-License-Agreement-EN.pdf" target="_blank" rel="noopener">License Agreements der Society of NLP</a> (unverbindliche <a href="../assets/docs/NHR-License-Agreement-DE-Leseuebersetzung.pdf" target="_blank" rel="noopener">deutsche Übersetzung</a>) notwendig; hierzu erhalte ich vor der Veranstaltung per E-Mail einen Link zur Unterzeichnung. Hierfür werden meine personenbezogenen Daten (Name, Adresse, Telefonnummer, E-Mail) an die Society of NLP™ in die USA übermittelt. Damit bin ich einverstanden.*</span>
532          </label>
533          <p class="nhr-error" id="err-f-license" style="margin-left:40px"></p>
534        </div>
535
536        <!-- Honeypot: bleibt für Menschen unsichtbar und leer -->
537        <div class="nhr-hp" aria-hidden="true">
538          <label for="f-hp">Firmenwebseite</label>
539          <input type="text" id="f-hp" name="firmenwebseite" tabindex="-1" autocomplete="off">
540        </div>
541      </div>
542    </form>
543
544    <!-- ---------- RECHTS: Sticky Buchungs-Karte ---------- -->
545    <aside class="nhr-aside">
546      <div class="nhr-card" aria-live="polite">
547        <h2 class="nhr-card-title">Deine Buchung</h2>
548
549        <div class="nhr-card-row"><span class="lbl" id="sumTicketLbl">Pro-Ticket · 6 Tage</span><span class="val" id="sumTicketVal">3.170&nbsp;€</span></div>
550        <div class="nhr-card-row" id="sumTr" hidden><span class="lbl">Simultanübersetzung</span><span class="val">+200&nbsp;€</span></div>
551        <div class="nhr-card-row nhr-card-row--disc" id="sumDiscount" hidden><span class="lbl" id="sumDiscountLbl">Rabatt</span><span class="val" id="sumDiscountVal">−0&nbsp;€</span></div>
552
553        <hr class="nhr-card-div">
554        <div class="nhr-card-reg"><span>Regulärer Preis</span><span class="val" id="sReg">4.970&nbsp;€</span></div>
555        <div class="nhr-card-total"><span class="lbl">Dein Spezialpreis</span><span class="val" id="sTotal">3.170&nbsp;€</span></div>
556        <div style="text-align:right; margin-top:2px"><span class="nhr-badge" id="sSave">Du sparst 1.800&nbsp;€</span></div>
557
558        <button type="submit" form="nhrForm" class="nhr-cta" id="ctaDesktop" disabled style="margin-top:18px">Jetzt verbindlich buchen</button>
559        <p class="nhr-micro">Buchungsbestätigung und Rechnung per E-Mail<br>Zahlung per Überweisung<br>Umsatzsteuerbefreit gemäß § 4 Nr. 21 UStG</p>
560        <div class="nhr-submit-error" id="submitError" role="alert">
561          <p id="submitErrorMsg"></p>
562          <div class="nhr-error-actions">
563            <button type="submit" form="nhrForm" class="nhr-retry" id="retryBtn">Erneut senden</button>
564            <a class="nhr-mailto" id="mailtoFallback" href="#" target="_blank" rel="noopener">Buchung per E-Mail senden</a>
565          </div>
566        </div>
567      </div>
568    </aside>
569  </div>
570
571  <!-- ---------- Mobile Sticky-Leiste ---------- -->
572  <div class="nhr-bar" id="nhrBar">
573    <div class="nhr-bar-price">
574      <span class="reg" id="mReg">4.970&nbsp;€</span>
575      <span class="now" id="mTotal">3.170&nbsp;€</span>
576    </div>
577    <button type="submit" form="nhrForm" class="nhr-cta" id="ctaMobile" disabled>Jetzt verbindlich buchen</button>
578  </div>
579
580  <!-- ---------- Footer: zurück + Rechtliches ---------- -->
581  <footer class="nhr-foot">
582    <a href="../index.html">← Zurück zu NHR® 2026</a>
583    <span class="nhr-foot-links">
584      <a href="../impressum.html" target="_blank" rel="noopener">Impressum</a>
585      <a href="../datenschutz.html" target="_blank" rel="noopener">Datenschutz</a>
586      <a href="../agb.html" target="_blank" rel="noopener">AGB</a>
587    </span>
588  </footer>
589</div>
590
591<script>
592(function(){
593  "use strict";
594
595  /* ---------- Konfiguration ---------- */
596  var CONFIG = {
597    webhook: "https://impulsgrad.app.n8n.cloud/webhook/nhr-spear-booking",
598    validateWebhook: "https://impulsgrad.app.n8n.cloud/webhook/nhr-validate-code", // Live-Prüfung Gutschein/Ref (nur Anzeige-Preis); verbindlich rechnet n8n beim Buchungs-Submit neu
599    token: "nhr2026_aX9k2mQ7vR4tZ1",  // gemeinsames Geheimnis mit n8n; wird im JSON-Body als booking_token gesendet (kein Custom-Header → kein CORS-Preflight). Im Client sichtbar, nur Bot-Hürde
600    kontaktEmail: "[email protected]", // <-- Fallback-Kontakt für den Fehlerfall (bitte prüfen)
601    timeoutMs: 20000,                 // bricht hängende Anfragen nach 20 s sauber ab
602    language: "de",                   // Seiten-Sprache → n8n wählt DE/EN-Bestätigungsmail über language
603    base: 2670, regular: 3970,
604    proPrice: 500, proRegular: 1000,   // Pro: +500 auf den Spezialpreis, +1.000 auf regulär (Landing: Pro regulär 4.970)
605    trPrice: 200, trRegular: 200        // Übersetzung: +200 (nicht rabattiert)
606  };
607
608  var $ = function(id){ return document.getElementById(id); };
609  var form = $("nhrForm");
610  var trCb = $("f-tr");
611  var ticketRadios = document.querySelectorAll('input[name="ticket"]');
612  function isPro(){ var r = document.querySelector('input[name="ticket"]:checked'); return !!r && r.value === "pro"; }
613  var ctas = [$("ctaDesktop"), $("ctaMobile")];
614
615  // Idempotenz-Schlüssel: EINMAL pro Seitenaufruf erzeugt, bei jedem (auch wiederholten)
616  // Senden mitgeschickt. So erkennt n8n einen Retry und legt keine zweite Buchung an.
617  var submissionId = (function(){
618    try{ if(window.crypto && crypto.randomUUID) return crypto.randomUUID(); }catch(e){}
619    return "sub-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2,10);
620  })();
621
622  function fmt(n){ return n.toLocaleString("de-DE") + " €"; }
623
624  // Affiliate-/Ref-Zuordnung: ?ref aus URL lesen, persistieren, mit der Buchung senden.
625  var refCode = (function(){
626    try{
627      var u = new URLSearchParams(window.location.search).get("ref") || "";
628      if(u){ localStorage.setItem("nhr_ref", u); }
629      return u || localStorage.getItem("nhr_ref") || "";
630    }catch(e){ return ""; }
631  })();
632
633  // Live-Prüfung Gutschein/Ref: Ergebnis der letzten Prüfung (null = kein Rabatt aktiv).
634  var applied = null;      // {valid, final_price, discount_applied, source, code, base}
635  var validating = false;
636  var rules = null;        // gecachte Rabatt-Regeln {voucher, ref} vom Endpoint → Ticketwechsel lokal, ohne Server-Call
637
638  // Ticket-Vorauswahl über ?ticket: "basic" → Basic-Ticket; sonst (auch ohne Parameter) bleibt Pro (Default aus dem HTML).
639  function applyTicketParam(){
640    try{
641      if(new URLSearchParams(window.location.search).get("ticket") !== "basic") return;
642      var b = document.querySelector('input[name="ticket"][value="basic"]');
643      var p = document.querySelector('input[name="ticket"][value="pro"]');
644      if(b) b.checked = true;
645      if(p) p.checked = false;
646    }catch(e){}
647  }
648
649  /* ---------- Preislogik & training_id ---------- */
650  function calc(){
651    var pro = isPro(), tr = trCb.checked;
652    return {
653      pro: pro, tr: tr,
654      price: CONFIG.base + (pro ? CONFIG.proPrice : 0) + (tr ? CONFIG.trPrice : 0),
655      regular: CONFIG.regular + (pro ? CONFIG.proRegular : 0) + (tr ? CONFIG.trRegular : 0),
656      trainingId: "NHR-26-" + (pro ? "P" : "B") + (tr ? "-TR" : "")
657    };
658  }
659
660  function render(){
661    var c = calc();
662    // Ticket-Karten aktiv/inaktiv
663    $("tkBasic").classList.toggle("is-active", !c.pro);
664    $("tkPro").classList.toggle("is-active", c.pro);
665    // Zusammenfassung (rechts)
666    $("sumTicketLbl").textContent = (c.pro ? "Pro-Ticket" : "Basic-Ticket") + " · 6 Tage";
667    $("sumTicketVal").textContent = fmt(CONFIG.base + (c.pro ? CONFIG.proPrice : 0));
668    $("sumTr").hidden = !c.tr;
669    // Live-Rabatt nur anzeigen, wenn er für GENAU diesen Preis geprüft wurde (sonst stale nach Ticketwechsel)
670    var hasDisc = !!(applied && applied.valid && applied.base === c.price && applied.discount_applied > 0);
671    var finalPrice = hasDisc ? applied.final_price : c.price;
672    $("sumDiscount").hidden = !hasDisc;
673    if(hasDisc){
674      $("sumDiscountLbl").textContent = discountLabel(applied.source, applied.
674code);
675      $("sumDiscountVal").textContent = "−" + fmt(applied.discount_applied);
676    }
677    $("sTotal").textContent = fmt(finalPrice);
678    $("sReg").textContent = fmt(c.regular);
679    $("sSave").textContent = "Du sparst " + fmt(c.regular - finalPrice);
680    // Mobile Leiste
681    $("mTotal").textContent = fmt(finalPrice);
682    $("mReg").textContent = fmt(c.regular);
683    // Add-on-Karte
684    $("addonTrBox").classList.toggle("is-active", c.tr);
685  }
686  // Statische Kartenpreise aus CONFIG (damit ein Basispreis-Update automatisch durchschlägt)
687  function renderCards(){
688    $("basicReg").textContent = "regulär " + fmt(CONFIG.regular);
689    $("basicNow").textContent = fmt(CONFIG.base);
690    $("basicSave").textContent = "Du sparst " + fmt(CONFIG.regular - CONFIG.base);
691    $("proReg").textContent = "regulär " + fmt(CONFIG.regular + CONFIG.proRegular);
692    $("proNow").textContent = fmt(CONFIG.base + CONFIG.proPrice);
693    $("proSave").textContent = "Du sparst " + fmt((CONFIG.regular + CONFIG.proRegular) - (CONFIG.base + CONFIG.proPrice));
694  }
695  /* ---------- Gutschein-/Ref-Live-Prüfung (nur Anzeige-Preis; n8n rechnet beim Submit verbindlich neu) ---------- */
696  var codeInput = $("f-code"), codeBtn = $("codeBtn"), codeStatusEl = $("codeStatus");
697  // Gutscheincode aus ?code= vorbefüllen (Kampagnen-/Mail-Links ohne Abtippen). Wie bei ?ref gemerkt,
698  // damit der Code einen Umweg über die Landing überlebt. IMMER groß — das Sheet führt Codes in Großbuchstaben.
699  function applyCodeParam(){
700    var c = "";
701    try{
702      c = (new URLSearchParams(window.location.search).get("code") || "").trim().toUpperCase();
703      if(c){ localStorage.setItem("nhr_code", c); }
704      else  { c = (localStorage.getItem("nhr_code") || "").trim().toUpperCase(); }
705    }catch(e){}
706    if(c && !codeInput.value.trim()){ codeInput.value = c; }
707  }
708  function discountLabel(src, code){ var b = src === "gutschein" ? "Gutschein-Rabatt" : (src === "ref" ? "Aktions-Rabatt" : "Rabatt"); return code ? b + " (" + code + ")" : b; }
709  function setCodeStatus(msg, kind){
710    codeStatusEl.textContent = msg || "";
711    codeStatusEl.classList.remove("ok","err");
712    if(msg && kind) codeStatusEl.classList.add(kind);
713  }
714  // Lokales Recompute bei Ticketwechsel — gecachte Regeln auf den neuen Preis (Scope-Prefix + günstigere gewinnt). KEIN Server-Call.
715  function scopeOK(scope, id){ return !scope || String(id).toUpperCase().indexOf(String(scope).toUpperCase()) === 0; }
716  function applyDisc(price, r){ if(!r || !r.discount) return price; return String(r.discount_type).toUpperCase() === "PERCENT" ? price * (1 - Number(r.discount)/100) : price - Number(r.discount); }
717  function recomputeLocal(c){
718    var base = c.price, tid = c.trainingId, pv = Infinity, pr = Infinity;
719    if(rules && rules.voucher && scopeOK(rules.voucher.product_scope, tid)) pv = applyDisc(base, rules.voucher);
720    if(rules && rules.ref && scopeOK(rules.ref.product_scope, tid))         pr = applyDisc(base, rules.ref);
721    var fp = Math.max(0, Math.round(Math.min(pv, pr, base)));
722    if(fp < base){
723      var src = (pv <= pr) ? "gutschein" : "ref";
724      applied = { valid:true, final_price:fp, discount_applied:base - fp, source:src, code:(src === "ref" ? (refCode||"") : codeInput.value.trim()), base:base };
725      setCodeStatus("Rabatt angewandt" + (applied.code ? " ("+applied.code+")" : "") + ": −" + fmt(applied.discount_applied), "ok");
726    } else {
727      applied = null;
728      setCodeStatus(codeInput.value.trim() ? "Code gilt nicht für dieses Ticket" : "", codeInput.value.trim() ? "err" : "");
729    }
730    render();
731  }
732  function validateCode(){
733    if(!CONFIG.validateWebhook || validating) return;
734    var c = calc(), code = codeInput.value.trim().toUpperCase(), ref = (refCode || "").trim().toUpperCase();
735    if(!code && !ref){ applied = null; setCodeStatus("",""); render(); return; }  // nichts zu prüfen
736    validating = true;
737    if(code){ codeBtn.disabled = true; codeBtn.textContent = "Prüfe…"; }
738    var ctrl = (typeof AbortController !== "undefined") ? new AbortController() : null;
739    var timer = setTimeout(function(){ if(ctrl) ctrl.abort(); }, CONFIG.timeoutMs);
740    fetch(CONFIG.validateWebhook, {
741      method: "POST", headers: {"Content-Type": "application/json"},
742      body: JSON.stringify({ booking_token: CONFIG.token, training_id: c.trainingId, displayed_price: c.price, voucher_code: code, ref: ref }),
743      signal: ctrl ? ctrl.signal : undefined
744    }).then(function(res){ if(!res.ok) throw new Error("HTTP " + res.status); return res.json(); })
745      .then(function(d){
746        rules = (d && d.rules) ? d.rules : null;   // Regeln cachen → Ticketwechsel danach lokal (kein Server-Call)
747        if(d && d.valid && Number(d.discount_applied) > 0){
748          applied = { valid:true, final_price:Number(d.final_price), discount_applied:Number(d.discount_applied), source:d.source, code:(d.source === "ref" ? ref : code), base:c.price };
749          setCodeStatus(d.message_de || ("Rabatt angewandt" + (applied.code ? " ("+applied.code+")" : "") + ": −" + fmt(applied.discount_applied)), "ok");
750        } else {
751          applied = null;   // nur bei eingegebenem Code eine Fehlermeldung zeigen (Ref bleibt still)
752          setCodeStatus(code ? ((d && d.message_de) ? d.message_de : "Code ungültig") : "", code ? "err" : "");
753          // Aus ?code= gemerkter Code ist ungültig/abgelaufen → verwerfen, sonst erscheint er
754          // bei JEDEM weiteren Besuch erneut im Feld, samt roter Fehlermeldung.
755          try{ if(code && localStorage.getItem("nhr_code") === code){ localStorage.removeItem("nhr_code"); } }catch(e){}
756        }
757        render();
758      }).catch(function(){
759        applied = null;   // Endpoint (noch) nicht erreichbar → voller Preis, Buchung nie blockieren
760        setCodeStatus(code ? "Prüfung derzeit nicht möglich — schreib uns eine E-Mail: " + CONFIG.kontaktEmail : "", code ? "err" : "");
761        render();
762      }).finally(function(){
763        clearTimeout(timer); validating = false; codeBtn.disabled = false; codeBtn.textContent = "Einlösen";
764      });
765  }
766  // Nach Ticket-/Add-on-Wechsel: Regeln gecached → LOKAL rechnen (kein Server-Call); sonst Fallback aufs Server-Validate (alter Endpoint).
767  function priceChanged(){ if(rules){ recomputeLocal(calc()); } else { render(); if(codeInput.value.trim() || refCode) validateCode(); } }
768
769  for(var _i=0; _i<ticketRadios.length; _i++){ ticketRadios[_i].addEventListener("change", priceChanged); }
770  trCb.addEventListener("change", priceChanged);
771  codeBtn.addEventListener("click", validateCode);
772  codeInput.addEventListener("keydown", function(e){ if(e.key === "Enter"){ e.preventDefault(); validateCode(); } });
773  codeInput.addEventListener("input", function(){
774    setCodeStatus("","");
775    rules = null;                                                 // Code geändert → Regel-Cache verwerfen (kein Recompute mit altem Code)
776    if(codeInput.value.trim() === ""){ validateCode(); }          // Feld geleert → ggf. Ref-Rabatt wiederherstellen/entfernen
777    else if(applied){ applied = null; render(); }                 // neuer Code getippt → alten Rabatt entfernen, „Einlösen" abwarten
778  });
779
780  /* ---------- Anrede & Vorkenntnisse: Platzhalter-Grau ---------- */
781  var anrede = $("f-anrede");
782  anrede.addEventListener("change", function(){
783    anrede.classList.toggle("is-placeholder", anrede.value === "");
784    clearError(anrede);
785  });
786  var vk = $("f-vorkenntnisse");
787  vk.addEventListener("change", function(){
788    vk.classList.toggle("is-placeholder", vk.value === "");
789  });
790  var rAnrede = $("f-r-anrede");
791  rAnrede.addEventListener("change", function(){
792    rAnrede.classList.toggle("is-placeholder", rAnrede.value === "");
793  });
794
795  /* ---------- Abweichende Rechnungsadresse auf-/zuklappen ---------- */
796  var billingCb = $("f-rechnung"), billingBlock = $("billingFields");
797  var BILLING_REQUIRED = ["f-r-vorname","f-r-nachname","f-r-strasse","f-r-plz","f-r-ort","f-r-land"];
798  billingCb.addEventListener("change", function(){
799    billingBlock.classList.toggle("open", billingCb.checked);
800    billingCb.setAttribute("aria-expanded", billingCb.checked ? "true" : "false");
801    if(!billingCb.checked){ BILLING_REQUIRED.forEac
801h(function(id){ clearError($(id)); }); }
802  });
803
804  /* ---------- AGB-Status ---------- */
805  /* CTA ist bewusst IMMER klickbar (nur während des Sendens gesperrt):
806     so kann der Klick die fehlenden Felder inkl. AGB-Häkchen markieren,
807     statt den Button kommentarlos deaktiviert zu lassen. */
808  var agb = $("f-agb");
809  var license = $("f-license");
810  var agbBox = $("agbBox");
811  var licenseBox = $("licenseBox");
812  function syncCta(){
813    ctas.forEach(function(b){ b.disabled = sending; });
814  }
815  agb.addEventListener("change", function(){
816    clearError(agb);
817    agbBox.classList.remove("is-invalid");
818  });
819  license.addEventListener("change", function(){
820    clearError(license);
821    licenseBox.classList.remove("is-invalid");
822  });
823
824  /* ---------- Inline-Validierung ---------- */
825  var REQUIRED = ["f-vorname","f-nachname","f-email","f-telefon","f-strasse","f-plz","f-ort","f-land"];
826  var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
827
828  function setError(input, msg){
829    input.classList.add("is-invalid");
830    input.setAttribute("aria-invalid", "true");
831    var e = $("err-" + input.id);
832    if(e){ e.textContent = msg; e.classList.add("show"); input.setAttribute("aria-describedby", e.id); }
833  }
834  function clearError(input){
835    input.classList.remove("is-invalid");
836    input.removeAttribute("aria-invalid");
837    var e = $("err-" + input.id);
838    if(e){ e.textContent = ""; e.classList.remove("show"); }
839  }
840  function validateField(input){
841    var v = input.value.trim();
842    if(v === ""){ setError(input, input.dataset.error || "Bitte ausfüllen."); return false; }
843    if(input.id === "f-email" && !EMAIL_RE.test(v)){ setError(input, "Bitte eine gültige E-Mail-Adresse eingeben."); return false; }
844    clearError(input);
845    return true;
846  }
847  // Live: Fehler beim Verlassen zeigen, beim Tippen entfernen
848  REQUIRED.concat(BILLING_REQUIRED).forEach(function(id){
849    var el = $(id);
850    el.addEventListener("blur", function(){
851      if(id.indexOf("f-r-") === 0 && !billingCb.checked) return;
852      validateField(el);
853    });
854    el.addEventListener("input", function(){ clearError(el); });
855  });
856
857  function validateAll(){
858    var fields = ["f-anrede"].concat(REQUIRED).concat(billingCb.checked ? BILLING_REQUIRED : []);
859    var firstBad = null;
860    fields.forEach(function(id){
861      if(!validateField($(id)) && !firstBad) firstBad = $(id);
862    });
863    if(!agb.checked){
864      setError(agb, "Bitte AGB und Datenschutzerklärung bestätigen.");
865      agbBox.classList.add("is-invalid");
866      if(!firstBad) firstBad = agb;
867    }
868    if(!license.checked){
869      setError(license, "Bitte das License Agreement der Society of NLP bestätigen.");
870      licenseBox.classList.add("is-invalid");
871      if(!firstBad) firstBad = license;
872    }
873    if(firstBad){
874      firstBad.scrollIntoView({behavior:"smooth", block:"center"});
875      firstBad.focus({preventScroll:true});
876      return false;
877    }
878    return true;
879  }
880
881  /* ---------- Absenden ---------- */
882  var sending = false;
883  function showSubmitError(msg){
884    $("submitErrorMsg").textContent = msg;   // nur die Textzeile setzen, Retry/Mailto bleiben erhalten
885    $("submitError").classList.add("show");
886  }
887  // Mailto-Fallback: öffnet das Mailprogramm mit allen Daten vorausgefüllt,
888  // damit die Buchung uns auch ohne funktionierenden Webhook erreicht.
889  function buildMailto(p){
890    var L = [
891      "Neue NHR-Buchung (per Mail-Fallback, Webhook nicht erreichbar)",
892      "",
893      "Training-ID: " + p.training_id,
894      "Spezialpreis: " + p.manual_special_price + " EUR",
895      "",
896      "Anrede: " + p.salutation,
897      "Titel: " + p.title,
898      "Name: " + p.first_name + " " + p.last_name,
899      "E-Mail: " + p.email_primary,
900      "Telefon: " + p.phone,
901      "Vorkenntnisse: " + p.prior_knowledge,
902      "",
903      "Adresse (Teilnehmer):",
904      p.street, p.plz + " " + p.city, p.country,
905      "",
906      "Rechnungsadresse:",
907      (p.invoice_company_name || (p.invoice_first_name + " " + p.invoice_last_name)),
908      p.invoice_street, p.invoice_plz + " " + p.invoice_city, p.invoice_country,
909      "",
910      "Anmerkungen: " + p.notes_participant,
911      "Gutschein-Code: " + p.voucher_code,
912      "Ref: " + p.ref,
913      "AGB akzeptiert: ja",
914      "License Agreement akzeptiert: ja",
915      "Referenz: " + p.submission_id
916    ];
917    var subject = "NHR-Buchung – " + p.first_name + " " + p.last_name;
918    return "mailto:" + CONFIG.kontaktEmail +
919           "?subject=" + encodeURIComponent(subject) +
920           "&body=" + encodeURIComponent(L.join("\n"));
921  }
922  function showInlineSuccess(){
923    $("nhrGrid").style.display = "none";
924    $("nhrBar").style.display = "none";
925    $("nhrSuccess").classList.add("show");
926    document.getElementById("nhr-booking").scrollIntoView({behavior:"smooth", block:"start"});
927  }
928  function showSuccess(){
929    // Echte Buchung → eigene Danke-Seite (URL fürs Conversion-Tracking).
930    window.location.assign("danke/");
931  }
932
933  form.addEventListener("submit", function(ev){
934    ev.preventDefault();
935    $("submitError").classList.remove("show");
936    if(sending) return;
937    if(!validateAll()) return;
938
939    // Honeypot gefüllt → Bot: still "Erfolg" zeigen (inline, KEINE Danke-Seite/kein Tracking), nichts senden
940    if($("f-hp").value !== ""){ showInlineSuccess(); return; }
941
942    var fbConsent = (typeof fbq === "function");
943    function readCookie(n){ var m = document.cookie.match(new RegExp("(?:^|; )" + n + "=([^;]*)")); return m ? decodeURIComponent(m[1]) : ""; }
944    function fbcFromUrl(){ var m = location.search.match(/[?&]fbclid=([^&]+)/); return m ? "fb.1." + Date.now() + "." + m[1] : ""; }
945
946    var c = calc();
947    var sep = billingCb.checked;                 // abweichende Rechnungsadresse angegeben?
948    // Kundendaten (Teilnehmer:in) — dienen zugleich als Quelle für die Kopie in die Rechnungsadresse
949    var salutation = $("f-anrede").value, title = $("f-titel").value.trim();
950    var firstName = $("f-vorname").value.trim(), lastName = $("f-nachname").value.trim();
951    var company = "";                            // Teilnehmer:in hat kein Firmenfeld → immer leer
952    var street = $("f-strasse").value.trim(), plz = $("f-plz").value.trim();
953    var city = $("f-ort").value.trim(), country = $("f-land").value.trim();
954
955    var payload = {
956      submission_id: submissionId,             // Idempotenz (bleibt über Retries gleich)
957      booking_token: CONFIG.token,             // im Body statt Header → kein zusätzlicher CORS-Preflight
958      submitted_at: new Date().toISOString(),
959      language: CONFIG.language,               // "de" → Bestätigungsmail auf Deutsch (n8n-Split)
960      training_id: c.trainingId,               // enthält Pro/Übersetzung (B / B-TR / P / P-TR)
961      displayed_price: c.price,                // angezeigter Preis (was der Kunde sah); Basis, auf die n8n Gutschein/Ref rechnet
962      manual_special_price: c.price,           // vorerst = displayed; n8n überschreibt mit Gutschein-Preis → final_price
963      installment_plan: "",                    // keine Ratenzahlung im Formular (ggf. manuell)
964
965      // --- Person / Teilnehmer:in (Spalten AC–AM) ---
966      salutation: salutation,
967      title: title,
968      first_name: firstName,
969      last_name: lastName,
970      company_name: company,
971      street: street,
972      plz: plz,
973      city: city,
974      country: country,
975      prior_knowledge: vk.value,               // englischer Wert oder "" (nicht gewählt)
976      notes_participant: $("f-anmerkungen").value.trim(),
977
978      // --- Rechnungsadresse (Spalten L–T): ohne abweichende = Kopie der Kundendaten ---
979      email_primary: $("f-email").value.trim(),
980      phone: $("f-telefon").value.trim(),
981      invoice_salutation:   sep ? $("f-r-anrede").value : salutation,
982      invoice_title:        sep ? $("f-r-titel").value.trim() : title,
983      invoice_first_name:   sep ? $("f-r-vorname").value.trim() : firstName,
984      invoice_last_name:    sep ? $("f-r-nachname").value.trim() : lastName,
985      invoice_company_name: sep ? $("f-r-firma").value.trim() : company,
986      invoice_street:       sep ? $("f-r-strasse").value.trim() : street,
987      invoice_plz:          sep ? $("f-r-plz").value.trim() : plz,
988      invoice_city:         sep ? $("f-r-ort").value.trim() : city,
989      invoice_country:      sep ? $("f-r-land").value.trim() : country,
990
991      agb_accepted: true,
992      license_agreement_accepted: true,     // 2. Checkbox (Society of NLP / US-Transfer)
993      voucher_code: $("f-code").value.trim().toUpperCase(),   // immer groß → Sheet-Lookup trifft auch bei "Bandler23" (Handy-Autokorrektur)
994      ref: (refCode || "").trim().toUpperCase(),   // Affiliate-Zuordnung aus ?ref (serverseitig auflösen)
995      fb_consent: fbConsent,                // Facebook-Consent-Proxy: n8n feuert Meta-CAPI NUR bei Einwilligung
996      // Meta-CAPI-Matching (nur mit Einwilligung; sonst leer): _fbp/_fbc setzt der Pixel als First-Party-Cookie.
997      fbp: fbConsent ? readCookie("_fbp") : "",
998      fbc: fbConsent ? (readCookie("_fbc") || fbcFromUrl()) : "",
999      event_source_url: location.href,
1000      // Ad-Attribution: UTM-Parameter aus der URL (werden von den Landing-CTAs weitergereicht) → dbNHRbookings
1001      utm_source:   new URLSearchParams(location.search).get("utm_source")   || "",
1002      utm_campaign: new URLSearchParams(location.search).get("utm_campaign") || "",
1003      utm_content:  new URLSearchParams(location.search).get("utm_content")  || ""
1004    };
1005
1006    sending = true;
1007    var labels = ctas.map(function(b){ return b.textContent; });
1008    ctas.forEach(function(b){ b.textContent = "Wird gesendet…"; });
1009    syncCta();
1010
1011    // Lokale Sicherung: Eingaben überleben so einen versehentlichen Reload/Absturz,
1012    // bis der Server den Eingang bestätigt hat (wird bei Erfolg gelöscht).
1013    try{ localStorage.setItem("nhr_booking_pending", JSON.stringify(payload)); }catch(e){}
1014
1015    // Timeout: bricht eine hängende Anfrage kontrolliert ab, statt ewig "Wird gesendet…" zu zeigen.
1016    var ctrl = (typeof AbortController !== "undefined") ? new AbortController() : null;
1017    var timer = setTimeout(function(){ if(ctrl) ctrl.abort(); }, CONFIG.timeoutMs);
1018
1019    fetch(CONFIG.webhook, {
1020      method: "POST",
1021      headers: {"Content-Type": "application/json"},
1022      body: JSON.stringify(payload),
1023      signal: ctrl ? ctrl.signal : undefined
1024    }).then(function(res){
1025      // ENTSCHEIDEND: Erfolg wird NUR bei echtem HTTP 200 vom Server angezeigt.
1026      // Damit das auch eine erfolgreiche Speicherung bedeutet, muss der n8n-Webhook
1027      // erst NACH dem Speichern antworten (siehe Hinweis im Chat).
1028      if(res.ok){
1029        try{ localStorage.removeItem("nhr_booking_pending"); }catch(e){}
1030        try{  // Conversion-Werte für die Danke-Seite (Browser-Schätzwert; n8n/CAPI bleibt autoritativ)
1031          var _cv = (typeof applied !== "undefined" && applied && applied.final_price) ? applied.final_price : c.price;
1032          localStorage.setItem("nhr_last_conversion", JSON.stringify({ value: Number(_cv)||0, id: payload.submission_id, ticket: payload.training_id, currency: "EUR" }));
1033        }catch(e){}
1034        showSuccess();
1035      } else {
1036        throw new Error("HTTP " + res.status);
1037      }
1038    }).catch(function(){
1039      // Jeder Fehler (Netz, CORS, Timeout, Server-Fehler) → klare Meldung, Eingaben bleiben.
1040      // Retry nutzt dieselbe submission_id (kein Duplikat), Mailto als letzter Fallback.
1041      $("mailtoFallback").href = buildMailto(payload);
1042      showSubmitError(
1043        "Das hat leider nicht geklappt — deine Buchung wurde noch NICHT übermittelt. " +
1044        "Bitte klicke auf „Erneut senden“. Falls es weiterhin nicht funktioniert, klicke auf den " +
1045        "Link „Buchung per E-Mail senden“ — dann sollte sich eine E-Mail mit allen Buchungsdaten " +
1046        "öffnen. Wenn auch das nicht klappt, schreib uns gerne einfach eine Mail an " + CONFIG.kontaktEmail + "."
1047      );
1048    }).finally(function(){
1049      clearTimeout(timer);
1050      sending = false;
1051      ctas.forEach(function(b, i){ b.textContent = labels[i]; });
1052      syncCta();
1053    });
1054  });
1055
1056  /* ---------- Init ---------- */
1057  applyTicketParam();
1058  applyCodeParam();                // Gutscheincode aus ?code= ins Feld schreiben (vor dem ersten Validate)
1059  renderCards();
1060  render();
1061  if(refCode || codeInput.value.trim()){ validateCode(); }   // Rabatt aus ?ref / ?code sofort live anzeigen
1062  syncCta();
1063})();
1064</script>
1064
1065<!-- Cookie-Consent-Loader (cockpit.legal) · SINGLE SOURCE.
1066     Wird von build-public.sh automatisch vor </body> in JEDE Seite injiziert.
1067     Änderungen NUR hier vornehmen (nicht in den einzelnen Seiten). -->
1068<script
1069  src="https://app.cockpit.legal/static/cookieconsent-v2.js?tools=Google%20Analytics,Vimeo,Facebook"
1070  data-cc-tools="Google Analytics,Vimeo,Facebook"
1071  data-cc-privacy="https://nlp.de/datenschutz.html"
1072  data-cc-imprint="https://nlp.de/impressum.html"
1073  data-cc-color="#5B2FE4"
1074  data-cc-theme="browser"
1075  data-cc-language="browser"
1076  data-cc-non-eu-consent="false"
1077  data-cc-enable-logs="false"></script>
1077
1078</body>
1079</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.