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 · begrenztes Kontingent</p> 263 </header> 264 <h1 class="nhr-h1">Neuro Hypnotic Repatterning® mit Dr. 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 â¬</span> 292 <span class="nhr-tk-now" id="basicNow">2.670 â¬</span> 293 <span class="nhr-tk-save" id="basicSave">Du sparst 1.300 â¬</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 & Kathleen La Valle</li> 297 <li>Offizielles Society-of-NLPâ¢-Zertifikat, von Dr. Bandler unterschrieben</li> 298 <li>Kaffee, Tee & 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 â¬</span> 307 <span class="nhr-tk-now" id="proNow">3.170 â¬</span> 308 <span class="nhr-tk-save" id="proSave">Du sparst 1.800 â¬</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 â¬</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 â¬</span></div> 550 <div class="nhr-card-row" id="sumTr" hidden><span class="lbl">Simultanübersetzung</span><span class="val">+200 â¬</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 â¬</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 â¬</span></div> 555 <div class="nhr-card-total"><span class="lbl">Dein Spezialpreis</span><span class="val" id="sTotal">3.170 â¬</span></div> 556 <div style="text-align:right; margin-top:2px"><span class="nhr-badge" id="sSave">Du sparst 1.800 â¬</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 â¬</span> 575 <span class="now" id="mTotal">3.170 â¬</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.