1(function () { 2 'use strict'; 3 4 function initSignupPromo() { 5 var cfg = window.MOTORICA_SIGNUP || {}; 6 var i18n = cfg.i18n || {}; 7 var urls = cfg.urls || {}; 8 var csrf = cfg.csrf || {}; 9 var defaultPricing = cfg.defaultPricing || {}; 10 11 var codeInput = document.getElementById('signup-promo-code'); 12 var applyBtn = document.getElementById('signup-promo-apply'); 13 var errorEl = document.getElementById('signup-promo-error'); 14 var successEl = document.getElementById('signup-promo-success'); 15 var successTitleEl = document.getElementById('signup-promo-success-title'); 16 var successSavingsEl = document.getElementById('signup-promo-success-savings'); 17 var savingsYearEl = document.getElementById('signup-promo-savings-year'); 18 var savingsMonthEl = document.getElementById('signup-promo-savings-month'); 19 var monthlyNetEl = document.getElementById('signup-price-monthly-net'); 20 var monthlyGrossEl = document.getElementById('signup-price-monthly-gross'); 21 var annualPaymentEl = document.getElementById('signup-price-annual-payment'); 22 var detailEl = document.getElementById('signup-price-detail'); 23 var applyBtnDefaultHtml = applyBtn ? applyBtn.innerHTML : ''; 24 var isBusy = false; 25 26 function formatEuro(cents) { 27 var value = (Number(cents) || 0) / 100; 28 return value.toLocaleString('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' â¬'; 29 } 30 31 function replaceAmount(template, amount) { 32 return String(template || '').replace('{amount}', amount || ''); 33 } 34 35 function monthlyGrossFromNet(monthlyNetCents, vatPercent) { 36 var net = Number(monthlyNetCents) || 0; 37 if (net <= 0) return 0; 38 var vat = Number(vatPercent); 39 if (!isFinite(vat)) vat = Number(defaultPricing.vat_percent) || 19; 40 return net + Math.round(net * (vat / 100)); 41 } 42 43 function resolvePricing(pricing) { 44 var vatPercent = pricing.vat_percent != null 45 ? pricing.vat_percent 46 : (defaultPricing.vat_percent || 19); 47 var monthlyNet = pricing.monthly_net_cents != null 48 ? Number(pricing.monthly_net_cents) 49 : Math.round((Number(pricing.net_cents) || 0) / 12); 50 var monthlyGross = pricing.monthly_gross_cents != null 51 ? Number(pricing.monthly_gross_cents) 52 : monthlyGrossFromNet(monthlyNet, vatPercent); 53 54 return { 55 net_cents: Number(pricing.net_cents) || 0, 56 gross_cents: Number(pricing.gross_cents) || 0, 57 vat_percent: vatPercent, 58 monthly_net_cents: monthlyNet, 59 monthly_gross_cents: monthlyGross, 60 net_formatted: pricing.net_formatted || formatEuro(pricing.net_cents), 61 gross_formatted: pricing.gross_formatted || formatEuro(pricing.gross_cents), 62 monthly_formatted: pricing.monthly_formatted || formatEuro(monthlyNet), 63 monthly_gross_formatted: pricing.monthly_gross_formatted || formatEuro(monthlyGross), 64 }; 65 } 66 67 function setPricing(pricing) { 68 if (!pricing) return; 69 var resolved = resolvePricing(pricing); 70 71 if (monthlyNetEl) { 72 monthlyNetEl.textContent = resolved.monthly_formatted; 73 } 74 if (monthlyGrossEl) { 75 monthlyGrossEl.textContent = resolved.monthly_gross_formatted; 76 }
77 if (annualPaymentEl) { 78 if (i18n.price_annual_payment) { 79 annualPaymentEl.textContent = String(i18n.price_annual_payment) 80 .replace('{gross}', resolved.gross_formatted); 81 } else { 82 annualPaymentEl.textContent = resolved.gross_formatted; 83 } 84 } 85 if (detailEl) { 86 var vatPercent = String(resolved.vat_percent || 19).replace('.0', ''); 87 if (i18n.price_breakdown) { 88 detailEl.textContent = String(i18n.price_breakdown) 89 .replace('{net}', resolved.net_formatted) 90 .replace('{vat}', vatPercent); 91 } else { 92 detailEl.textContent = resolved.net_formatted + ' · ' + vatPercent + ' %'; 93 } 94 } 95 } 96 97 function defaultPricingPayload() { 98 return { 99 gross_cents: defaultPricing.gross_cents, 100 net_cents: defaultPricing.net_cents, 101 vat_percent: defaultPricing.vat_percent, 102 monthly_net_cents: defaultPricing.monthly_net_cents, 103 monthly_gross_cents: defaultPricing.monthly_gross_cents, 104 gross_formatted: formatEuro(defaultPricing.gross_cents), 105 net_formatted: formatEuro(defaultPricing.net_cents), 106 monthly_formatted: formatEuro(defaultPricing.monthly_net_cents), 107 monthly_gross_formatted: formatEuro(defaultPricing.monthly_gross_cents), 108 }; 109 } 110 111 function showError(message) { 112 if (errorEl) { 113 errorEl.textContent = message || ''; 114 errorEl.hidden = !message; 115 } 116 if (successEl) successEl.hidden = true; 117 } 118 119 function showSuccess(message, savings) { 120 if (!successEl) return; 121 if (!message) { 122 successEl.hidden = true; 123 if (successTitleEl) successTitleEl.textContent = ''; 124 if (successSavingsEl) successSavingsEl.hidden = true; 125 return; 126 } 127 128 if (successTitleEl) { 129 successTitleEl.textContent = message; 130 } 131 if (errorEl) errorEl.hidden = true; 132 successEl.hidden = false; 133 134 var yearAmount = savings && savings.year_net_formatted ? savings.year_net_formatted : ''; 135 var monthAmount = savings && savings.month_net_formatted ? savings.month_net_formatted : ''; 136 var hasSavings = savings && (Number(savings.year_net_cents) > 0 || Number(savings.month_net_cents) > 0); 137 138 if (successSavingsEl && savingsYearEl && savingsMonthEl && hasSavings) { 139 savingsYearEl.textContent = replaceAmount(i18n.promo_savings_year, yearAmount); 140 savingsMonthEl.textContent = replaceAmount(i18n.promo_savings_month, monthAmount); 141 successSavingsEl.hidden = false; 142 } else if (successSavingsEl) { 143 successSavingsEl.hidden = true; 144 } 145 } 146 147 function setApplyBusy(busy) { 148 isBusy = busy; 149 if (!applyBtn) return; 150 applyBtn.disabled = busy; 151 applyBtn.classList.toggle('is-loading', busy); 152 applyBtn.setAttribute('aria-busy', busy ? 'true' : 'false'); 153 if (busy) { 154 applyBtn.innerHTML = '<i class="fas fa-spinner fa-spin" aria-hidden="true"></i> ' + 155 (i18n.promo_applying || ''); 156 } else { 157 applyBtn.innerHTML = applyBtnDefaultHtml; 158 } 159 if (window.MotoricaBusy) { 160 if (busy) { 161 window.MotoricaBusy.show({ busyMessage: i18n.promo_busy_message || i18n.promo_applying || '' }); 162 } else { 163 window.MotoricaBusy.hide(); 164 } 165 } 166 } 167 168 function validatePromo(options) { 169 options = options || {}; 170 var showBusyUi = options.showBusyUi !== false; 171 172 if (!codeInput || !urls.validatePromo || !applyBtn) return; 173 if (isBusy) return; 174 175 var code = (codeInput.value || '').trim(); 176 if (code === '') { 177 setPricing(defaultPricingPayload()); 178 showError(''); 179 showSuccess(''); 180 return; 181 } 182 183 if (showBusyUi) { 184 setApplyBusy(true); 185 } 186 187 var body = new URLSearchParams(); 188 body.append('promo_code', code); 189 if (cfg.lang) body.append('lang', cfg.lang); 190 if (csrf.name && csrf.hash) body.append(csrf.name, csrf.hash); 191 192 fetch(urls.validatePromo, { 193 method: 'POST', 194 headers: { 195 'Content-Type': 'application/x-www-form-urlencoded', 196 'Accept': 'application/json', 197 'X-Requested-With': 'XMLHttpRequest', 198 }, 199 body: body.toString(), 200 credentials: 'same-origin', 201 }) 202 .then(function (r) { return r.json(); }) 203 .then(function (data) { 204 if (!data || typeof data !== 'object') return; 205 if (!data.valid) { 206 setPricing(defaultPricingPayload()); 207 showError(data.message || ''); 208 showSuccess(''); 209 return; 210 } 211 setPricing(data.pricing || null); 212 showSuccess(i18n.promo_applied || '', data.savings || null); 213 }) 214 .catch(function () { 215 showError(''); 216 }) 217 .finally(function () { 218 if (showBusyUi) { 219 setApplyBusy(false); 220 } 221 }); 222 } 223
224 if (applyBtn) { 225 applyBtn.addEventListener('click', function () { 226 validatePromo({ showBusyUi: true }); 227 }); 228 } 229 if (codeInput && (codeInput.value || '').trim() !== '') { 230 validatePromo({ showBusyUi: false }); 231 } 232 233 var form = document.getElementById('signup-form'); 234 var submitBtn = document.getElementById('signup-submit-btn'); 235 var submitStarted = false; 236 237 if (form && submitBtn) { 238 form.addEventListener('submit', function () { 239 if (submitStarted) { 240 return; 241 } 242 submitStarted = true; 243 submitBtn.disabled = true; 244 submitBtn.classList.add('is-loading'); 245 submitBtn.setAttribute('aria-busy', 'true'); 246 submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin" aria-hidden="true"></i> ' + 247 (i18n.submit_processing || 'â¦'); 248 if (window.MotoricaBusy) { 249 window.MotoricaBusy.show({ 250 busyMessage: i18n.submit_processing || '', 251 }); 252 } 253 // Keep default submit; overlay stays until navigation. 254 }); 255 } 256 } 257 258 if (document.readyState === 'loading') { 259 document.addEventListener('DOMContentLoaded', initSignupPromo); 260 } else { 261 initSignupPromo(); 262 } 263})();
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.