/* a3.mailgate — підтвердження пошти кодом прямо у формі, без перезавантаження. * Людина вийшла з поля email → (у фоні розвʼязано PoW) → лист із 6-значним кодом → * під полем зʼявляється віконце «введіть код» → код вірний → пошта підтверджена (vt). * Без vt сервер форму не приймає. Модуль не залежить від решти сайту: його ж підключає * лендінг womans-day. * * var g = MailGate.attach({ input, root, form: 'contact', lang: 'uk', T: {...}, first: fn, onChange: fn }); * g.vt() → токен або '' ; g.email() → підтверджена адреса ; g.common() → {t, beh} для /submit */ (function (W) { "use strict"; var API = "/wp-json/andrii/v1/contact/"; var T0 = { sending: "надсилаю код на пошту…", pow: "секунду — перевіряю, що ви людина…", sent: "Ми надіслали 6-значний код на %s. Введіть його, щоб підтвердити пошту.", code: "Код із листа", ok: "✓ Пошту підтверджено", change: "Змінити пошту", resend: "Надіслати код ще раз", resendIn: "Надіслати ще раз через %s с", resent: "Новий код надіслано.", wrong: "Невірний код. Лишилось спроб: %s", expired: "Код прострочений — надішлемо новий.", checking: "перевіряю…", spam: "Немає листа за хвилину — гляньте в «Спам».", errEmail: "Перевір адресу пошти", errTypo: "Можливо, %s?", errMx: "Цей домен не приймає пошту — перевір адресу", errDisposable: "Тимчасова скринька не підійде — потрібна справжня пошта", errRate: "Забагато спроб. Спробуйте за годину.", errMail: "Не вдалося надіслати лист із кодом. Перевірте адресу.", errBlocked: "Не вдалося перевірити. Оновіть сторінку й спробуйте ще раз.", }; var TYPO = { "gmial.com": "gmail.com", "gmai.com": "gmail.com", "gamil.com": "gmail.com", "gmail.co": "gmail.com", "gmail.dk": "gmail.com", "hotmial.com": "hotmail.com", "hotmal.com": "hotmail.com", "outlok.com": "outlook.com", "outlook.co": "outlook.com", "yaho.com": "yahoo.com", "icloud.co": "icloud.com", "iclod.com": "icloud.com", "live.co": "live.com", }; var RX = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/; var WORKER = "onmessage=async function(e){var d=e.data,enc=new TextEncoder(),t=d.challenge,b=new Uint8Array(32);" + "for(var i=0;i<32;i++)b[i]=parseInt(t.substr(i*2,2),16);" + 'for(var n=0;n<=d.max;n++){var h=new Uint8Array(await crypto.subtle.digest("SHA-256",enc.encode(d.salt+n))),ok=1;' + "for(var j=0;j<32;j++){if(h[j]!==b[j]){ok=0;break}}if(ok){postMessage(n);return}}postMessage(-1)}"; function solve(p) { return new Promise(function (res, rej) { try { var w = new Worker( URL.createObjectURL(new Blob([WORKER], { type: "text/javascript" })), ); w.onmessage = function (e) { w.terminate(); if (e.data < 0) rej(new Error("pow")); else res(e.data); }; w.onerror = function () { w.terminate(); rej(new Error("worker")); }; w.postMessage({ salt: p.salt, challenge: p.challenge, max: p.maxnumber, }); } catch (x) { rej(x); } }); } function post(path, body) { return fetch(API + path, { method: "POST", credentials: "same-origin", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }).then(function (r) { return r .json() .catch(function () { return {}; }) .then(function (j) { j.status = r.status; return j; }); }); } function el(tag, cls, html) { var e = document.createElement(tag); if (cls) e.className = cls; if (html != null) e.innerHTML = html; return e; } function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { "&": "&", "<": "<", ">": ">", '"': """ }[c]; }); } var CSS = ".mg-st{display:block;font-size:.78rem;line-height:1.45;margin-top:.35rem;min-height:1.1em;color:var(--mg-muted,#888)}" + ".mg-st.ok{color:var(--mg-ok,#3a9d5d)}.mg-st.err{color:var(--mg-err,#d33)}.mg-st button{font:inherit;color:inherit;background:none;border:0;padding:0;text-decoration:underline;cursor:pointer}" + ".mg{margin-top:.6rem;padding:.9rem 1rem;border:1px solid var(--mg-accent,#555);background:var(--mg-bg,transparent);display:grid;gap:.6rem;animation:mgIn .35s ease}" + "@keyframes mgIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}" + ".mg[hidden]{display:none}.mg__t{margin:0;font-size:.9rem;line-height:1.5}" + ".mg__code{width:100%;box-sizing:border-box;font:600 1.5rem/1.2 ui-monospace,Menlo,Consolas,monospace;letter-spacing:.45em;padding:.55rem .7rem;" + "border:1px solid var(--mg-border,#999);background:var(--mg-input,transparent);color:inherit;border-radius:var(--mg-radius,0);outline:none}" + ".mg__code:focus{border-color:var(--mg-accent,#555)}.mg__code.bad{border-color:var(--mg-err,#d33)}" + ".mg__msg{font-size:.78rem;min-height:1.1em;color:var(--mg-muted,#888)}.mg__msg.err{color:var(--mg-err,#d33)}" + ".mg__more{display:flex;justify-content:space-between;gap:1rem;flex-wrap:wrap}" + ".mg__link{background:none;border:0;padding:0;font:inherit;font-size:.78rem;color:var(--mg-muted,#888);text-decoration:underline;text-underline-offset:3px;cursor:pointer}" + ".mg__link:disabled{text-decoration:none;cursor:default;opacity:.75}" + "input.mg-done{opacity:.85}"; function attach(o) { var T = {}; Object.keys(T0).forEach(function (k) { T[k] = (o.T && o.T[k]) || T0[k]; }); if (o.api) API = o.api; if (!document.getElementById("mgCss")) { var st = el("style"); st.id = "mgCss"; st.textContent = CSS; document.head.appendChild(st); } var inp = o.input, root = o.root || inp.form || document.body; var host = o.mount || inp.parentNode; var status = el("span", "mg-st"); status.setAttribute("aria-live", "polite"); var box = el("div", "mg"); box.hidden = true; box.setAttribute("role", "group"); box.innerHTML = '
' + '"; host.appendChild(status); host.parentNode.insertBefore(box, host.nextSibling); var code = box.querySelector(".mg__code"), bmsg = box.querySelector(".mg__msg"), bRes = box.querySelector("[data-r]"), bChg = box.querySelector("[data-c]"); var S = { pow: null, t: "", issued: 0, id: "", sentTo: "", vt: "", verified: "", busy: false, timer: 0, }; var beh = { k: 0, p: 0 }; root.addEventListener("keydown", function () { beh.k++; }); ["pointerdown", "touchstart"].forEach(function (ev) { root.addEventListener( ev, function () { beh.p++; }, { passive: true }, ); }); function getPow(force) { if (S.pow && !force) return S.pow; var t0 = Date.now(); S.pow = fetch(API + "challenge?_=" + t0, { credentials: "same-origin", cache: "no-store", }) .then(function (r) { return r.json(); }) .then(function (j) { if (!j || !j.ok) throw new Error((j && j.error) || "challenge"); if (!S.t) { S.t = j.t; S.issued = t0; } // токен форми — від першого фокуса return solve(j.pow).then(function (n) { j.pow.number = n; return j.pow; }); }); S.pow.catch(function () { S.pow = null; }); return S.pow; } root.addEventListener( "focusin", function () { getPow(); }, { once: true }, ); function say(text, cls, html) { status.className = "mg-st" + (cls ? " " + cls : ""); if (html) status.innerHTML = text; else status.textContent = text; } function changed() { if (o.onChange) o.onChange(api); } function countdown(sec) { clearInterval(S.timer); bRes.disabled = true; function tick() { if (sec <= 0) { clearInterval(S.timer); bRes.disabled = false; bRes.textContent = T.resend; return; } bRes.textContent = T.resendIn.replace("%s", sec); sec--; } tick(); S.timer = setInterval(tick, 1000); } function reset() { clearInterval(S.timer); S.vt = ""; S.verified = ""; S.id = ""; S.sentTo = ""; box.hidden = true; code.value = ""; bmsg.textContent = ""; inp.readOnly = false; inp.classList.remove("mg-done"); inp.removeAttribute("aria-invalid"); say(""); changed(); } function value() { return inp.value.trim().toLowerCase(); } function send() { var v = value(); if (S.busy || !v || v === S.verified || v === S.sentTo) return; if (!RX.test(v)) { say(T.errEmail, "err"); inp.setAttribute("aria-invalid", "true"); return; } var dom = v.split("@")[1]; if (TYPO[dom]) { var fix = v.split("@")[0] + "@" + TYPO[dom]; say( T.errTypo.replace( "%s", '", ), "err", true, ); status.querySelector("button").onclick = function () { inp.value = fix; say(""); send(); }; inp.setAttribute("aria-invalid", "true"); return; } inp.removeAttribute("aria-invalid"); S.busy = true; say(T.pow); getPow() .then(function (pow) { var wait = 2200 - (Date.now() - S.issued); return new Promise(function (r) { setTimeout(r, wait > 0 ? wait : 0); }).then(function () { say(T.sending); return post("email/send", { email: v, first: o.first ? o.first() : "", lang: o.lang || "uk", form: o.form || "contact", t: S.t, pow: pow, beh: beh, }); }); }) .then(function (r) { S.pow = null; getPow(true); if (r.ok) { S.id = r.id; S.sentTo = v; box.querySelector(".mg__t").textContent = T.sent.replace( "%s", r.mail || v, ); box.hidden = false; say(T.spam); bmsg.textContent = ""; code.value = ""; code.classList.remove("bad"); countdown(r.resendIn || 45); setTimeout(function () { code.focus(); }, 60); if (o.onEvent) o.onEvent("otp_sent"); return; } inp.setAttribute("aria-invalid", "true"); var f = r.fields && r.fields.email; if (f) say( f === "mx" ? T.errMx : f === "disposable" ? T.errDisposable : T.errEmail, "err", ); else if (r.error === "rate") say(T.errRate, "err"); else if (r.error === "mail") say(T.errMail, "err"); else if (r.error === "expired") { S.t = ""; say(""); setTimeout(send, 50); } else say(T.errBlocked, "err"); }) .catch(function () { S.pow = null; say(T.errBlocked, "err"); }) .then(function () { S.busy = false; changed(); }); } function verify() { var c = code.value.replace(/\D/g, ""); if (c.length !== 6 || !S.id) return; bmsg.className = "mg__msg"; bmsg.textContent = T.checking; code.disabled = true; post("email/verify", { id: S.id, code: c }) .then(function (r) { code.disabled = false; if (r.ok) { clearInterval(S.timer); S.vt = r.vt; S.verified = r.email || S.sentTo; box.hidden = true; inp.readOnly = true; inp.classList.add("mg-done"); say( T.ok + ' · ", "ok", true, ); status.querySelector("button").onclick = function () { reset(); inp.focus(); }; if (o.onEvent) o.onEvent("email_verified"); changed(); return; } code.classList.add("bad"); bmsg.className = "mg__msg err"; if (r.error === "code") { bmsg.textContent = T.wrong.replace("%s", r.left); code.select(); } else if (r.error === "expired") { bmsg.textContent = T.expired; S.sentTo = ""; setTimeout(send, 900); } else bmsg.textContent = T.errBlocked; }) .catch(function () { code.disabled = false; bmsg.className = "mg__msg err"; bmsg.textContent = T.errBlocked; }); } inp.addEventListener("blur", send); inp.addEventListener("keydown", function (e) { if (e.key === "Enter") { e.preventDefault(); send(); } }); inp.addEventListener("input", function () { var v = value(); if (S.verified && v !== S.verified) reset(); else if (S.sentTo && v !== S.sentTo) { clearInterval(S.timer); S.sentTo = ""; S.id = ""; box.hidden = true; say(""); } else if (status.className.indexOf("err") > -1) say(""); }); code.addEventListener("input", function () { var v = code.value.replace(/\D/g, "").slice(0, 6); if (code.value !== v) code.value = v; code.classList.remove("bad"); if (v.length === 6) verify(); }); code.addEventListener("keydown", function (e) { if (e.key === "Enter") { e.preventDefault(); verify(); } }); bRes.addEventListener("click", function () { post("email/resend", { id: S.id }).then(function (r) { bmsg.className = "mg__msg"; if (r.ok) { bmsg.textContent = T.resent; countdown(r.resendIn || 45); } else if (r.error === "wait") countdown(r.wait || 30); else if (r.error === "expired") { S.sentTo = ""; send(); } else { bmsg.className = "mg__msg err"; bmsg.textContent = T.errRate; bRes.disabled = true; } }); }); bChg.addEventListener("click", function () { reset(); inp.focus(); inp.select(); }); var api = { vt: function () { return S.verified && value() === S.verified ? S.vt : ""; }, email: function () { return S.verified; }, pending: function () { return !!S.sentTo && !S.verified; }, common: function () { return { t: S.t, beh: beh }; }, reset: reset, focus: function () { if (!box.hidden) code.focus(); else inp.focus(); }, }; return api; } W.MailGate = { attach: attach }; })(window); ; /* andrii.dk/uk/womans-day/ — ліди лендінгу через andrii/v1/contact з підтвердженням пошти кодом. * Збирається в один файл разом із site/js/mailgate.js і wd_quiz_v3.js (scripts/upload_wd_js.py). * Квіз викликає WDLead.attachQuiz(wrap) і WDLead.submitQuiz(...); контакт-форму внизу сторінки * цей файл підхоплює сам. Без підтвердженої пошти жодна з форм не відправляється. */ (function (W) { "use strict"; var API = "/wp-json/andrii/v1/contact/"; var NAME = /^\p{L}[\p{L}\p{M}' ’\-.]*$/u; var css = document.createElement("style"); css.textContent = ".wd-landing{--mg-accent:var(--wd-primary,#6b2d5c);--mg-muted:var(--wd-muted,#5d5870);--mg-err:var(--wd-bad,#c44a4a);" + "--mg-ok:var(--wd-success,#2d8659);--mg-bg:var(--wd-bg,#fff9f5);--mg-border:rgba(107,45,92,.28);--mg-radius:10px;--mg-input:#fff}" + ".wd-landing .mg{border-radius:12px;margin:2px 0 14px}.wd-landing .mg-st{font-size:13px}" + ".wd-landing [aria-disabled=true]{opacity:.6}"; document.head.appendChild(css); function nameOk(v) { v = (v || "").trim(); return v.length >= 2 && v.length <= 40 && NAME.test(v); } function phoneOk(v) { if (/[^\d+\s().\-\/]/.test(v)) return false; var d = v.replace(/[^\d+]/g, ""); if (d.indexOf("00") === 0) d = "+" + d.slice(2); if (d && d[0] !== "+") d = d.length === 8 ? "+45" + d : "+" + d; if (!/^\+[1-9]\d{6,14}$/.test(d)) return false; return d.indexOf("+45") !== 0 || /^\+45[2-9]\d{7}$/.test(d); } function submit(body) { return fetch(API + "submit", { method: "POST", credentials: "same-origin", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }).then(function (r) { return r.json().catch(function () { return {}; }); }); } function errText(r) { if (r.error === "invalid" && r.fields) { var f = r.fields; if (f.first || f.last) return "Імʼя і прізвище — лише літери, від 2 до 40."; if (f.phone) return "Перевір телефон: 8 цифр для Данії або з кодом країни."; if (f.consent) return "Постав галочку погодження."; return "Перевір поля форми."; } if (r.error === "unverified") return "Підтвердь пошту кодом із листа."; if (r.error === "rate") return "Забагато спроб. Спробуй за годину або напиши на info@andrii.dk."; return "Не вдалося надіслати. Онови сторінку або напиши на info@andrii.dk."; } /* ── квіз: email — окремим рядком під сіткою, щоб віконце коду не ламало сітку ── */ var quizGate = null; function attachQuiz(wrap, emailField, btn) { if (!W.MailGate) return null; quizGate = MailGate.attach({ input: emailField.querySelector("input"), root: wrap, mount: emailField, api: API, form: "quiz", lang: "uk", first: function () { var v = (document.getElementById("wdlFirst") || {}).value || ""; return nameOk(v) ? v.trim() : ""; }, onChange: function (g) { btn.setAttribute("aria-disabled", g.vt() ? "false" : "true"); }, }); btn.setAttribute("aria-disabled", "true"); return quizGate; } function submitQuiz(d) { var g = quizGate; if (!nameOk(d.first)) return Promise.resolve({ ok: false, msg: "Введи імʼя (лише літери)" }); if (!nameOk(d.last)) return Promise.resolve({ ok: false, msg: "Введи прізвище (лише літери)" }); if (!phoneOk(d.phone)) return Promise.resolve({ ok: false, msg: "Перевір телефон: 8 цифр для Данії або з кодом країни" }); if (!g || !g.vt()) { if (g) g.focus(); return Promise.resolve({ ok: false, msg: g && g.pending() ? "Введи код із листа, щоб підтвердити пошту" : "Вкажи пошту й підтвердь її кодом із листа" }); } if (!d.consent) return Promise.resolve({ ok: false, msg: "Постав галочку погодження" }); var x = g.common(); return submit({ form: "quiz", first: d.first, last: d.last, phone: d.phone, email: g.email(), vt: g.vt(), consent: true, t: x.t, beh: x.beh, lang: "uk", quiz: d.quiz, ctx: { source: "womans-day:quiz-v3", onPage: location.pathname + location.search }, }).then(function (r) { if (r.ok) return { ok: true }; if (r.error === "unverified") g.reset(); return { ok: false, msg: errText(r) }; }); } /* ── контакт-форма внизу сторінки ── */ function contact() { var form = document.getElementById("wdContactForm"); if (!form || !W.MailGate) return; form.removeAttribute("action"); form.noValidate = true; // свої підказки замість спливних браузера var mail = document.getElementById("wdcEmail"), field = mail.closest(".wd-field"), grid = field.parentNode; grid.parentNode.insertBefore(field, grid.nextSibling); // email — окремим рядком var msg = document.getElementById("wdcMsgEl"), btn = form.querySelector("button[type=submit]"); var g = MailGate.attach({ input: mail, root: form, mount: field, api: API, form: "wdcontact", lang: "uk", first: function () { var v = document.getElementById("wdcName").value; return nameOk(v) ? v.trim() : ""; }, onChange: function () { btn.setAttribute("aria-disabled", g.vt() ? "false" : "true"); }, }); btn.setAttribute("aria-disabled", "true"); function show(t, ok) { msg.className = "wd-form-msg " + (ok ? "is-ok" : "is-error"); msg.textContent = t; } form.addEventListener("submit", function (ev) { ev.preventDefault(); var v = function (id) { return document.getElementById(id).value.trim(); }; if (!nameOk(v("wdcName")) || !nameOk(v("wdcLast"))) return show("Імʼя і прізвище — лише літери, від 2 до 40.", false); if (!phoneOk(v("wdcPhone"))) return show("Перевір телефон: 8 цифр для Данії або з кодом країни.", false); if (!g.vt()) { g.focus(); return show(g.pending() ? "Введи код із листа, щоб підтвердити пошту." : "Вкажи пошту й підтвердь її кодом із листа.", false); } if (!form.querySelector('input[name="consent"]').checked) return show("Постав галочку погодження.", false); var x = g.common(), hp = form.querySelector('input[name="hp_website"]'); btn.disabled = true; btn.textContent = "Надсилаю…"; submit({ form: "wdcontact", first: v("wdcName"), last: v("wdcLast"), phone: v("wdcPhone"), email: g.email(), vt: g.vt(), message: v("wdcMsg"), consent: true, website: hp ? hp.value : "", t: x.t, beh: x.beh, lang: "uk", ctx: { source: "womans-day:contact", onPage: location.pathname }, }).then(function (r) { if (r.ok) { form.reset(); g.reset(); show("✓ Повідомлення надіслано — Андрій відповість протягом доби.", true); btn.textContent = "📩 Надіслати ще"; } else { if (r.error === "unverified") g.reset(); show(errText(r), false); btn.textContent = "📩 Надіслати повідомлення"; } }).catch(function () { show(errText({}), false); btn.textContent = "📩 Надіслати повідомлення"; }) .then(function () { btn.disabled = false; }); }); } if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", contact); else contact(); W.WDLead = { attachQuiz: attachQuiz, submitQuiz: submitQuiz }; })(window); ; /* andrii.dk/uk/womans-day/ — Quiz v3 * 15 questions, adaptive branches, 12 categories, pain-point logic * Lead capture: first_name + last_name + phone + email + GDPR */ (function () { "use strict"; // Ліди (з 2026-10-10): andrii/v1/contact/submit через landing/wd_leads.js — пошта підтверджується кодом. // ============== QUIZ DEFINITION ============== // Each Q has: id, category, title, hint, options[{label, points, key, [signal]}] // Adaptive Q has `build(answers)` returning {title, hint, options}. // signal = додаткові маркери, які впливають на залежне Q. var QUIZ = [ // --- БЛОК 1: ВИДИМІСТЬ (соцмережі перві — у Данії так) --- { id: "q1", category: "social", title: "Які соцмережі ти ведеш для бізнесу зараз?", hint: "Бізнесові — не особисті. У Данії клієнти часто шукають у Instagram, не в Google.", options: [ { label: "Instagram + ще щось (TikTok/Facebook/YouTube)", points: 2, key: "A", }, { label: "Тільки Instagram", points: 1, key: "B" }, { label: "Тільки Facebook (зрідка)", points: 0, key: "C" }, { label: "Не веду жодних", points: 0, key: "D" }, ], }, // Q2 адаптивне залежно від Q1 { id: "q2", category: "social_consistency", adaptive: true, build: function (a) { var q1 = a.q1 && a.q1.key; if (q1 === "D") { return { title: "Ти плануєш почати вести соцмережі?", hint: "У Данії 70%+ клієнтів вирішують купувати побачивши останні 5-10 публікацій.", options: [ { label: "Так, у найближчі 30 днів", points: 1, key: "A" }, { label: "Так, але не знаю з чого почати", points: 0, key: "B" }, { label: "Ні, не вважаю потрібним", points: 0, key: "C" }, ], }; } return { title: "Як часто публікуєш контент про послуги (не лайфстайл)?", hint: "Лайфстайл-пости важливі, але клієнт хоче побачити твою РОБОТУ — фото робіт, кейси, відгуки.", options: [ { label: "3+ рази на тиждень + Stories щодня", points: 2, key: "A", }, { label: "1-2 рази на тиждень", points: 1, key: "B" }, { label: "Раз на місяць або рідше", points: 0, key: "C" }, ], }; }, }, // --- БЛОК 2: GOOGLE BUSINESS / КАРТИ --- { id: "q3", category: "gbp", title: "Чи є у тебе Google Business Profile (відмітка на Google Maps)?", hint: "У Данії цим часто пренебригають — і це чудова можливість стати помітнішою за конкурентів.", options: [ { label: "Так, заповнений з фото, регулярно оновлюю", points: 2, key: "A", }, { label: "Створив(ла), але рідко оновлюю", points: 1, key: "B" }, { label: "Ні, не маю", points: 0, key: "C" }, ], }, // --- БЛОК 3: САЙТ --- { id: "q4", category: "website", title: "Що клієнт побачить, коли захоче дізнатися більше?", hint: "Перевір зараз: пошук Google по своєму імені/бренду — що в результатах?", options: [ { label: "Сучасний сайт з послугами, прайсом, формою запису", points: 2, key: "A", }, { label: "Простий лендінг або one-pager", points: 1, key: "B" }, { label: "Linktree / digital-візитка", points: 1, key: "C" }, { label: "Нічого, тільки соцмережі", points: 0, key: "D" }, ], }, // Q5 адаптивне за Q4 { id: "q5", category: "website_quality", adaptive: true, build: function (a) { var q4 = a.q4 && a.q4.key; if (q4 === "A" || q4 === "B") { return { title: "Як швидко завантажується твій сайт на телефоні?", hint: "Кожна додаткова секунда понад 3с = -7% конверсій. Перевір на pagespeed.web.dev", options: [ { label: "До 3 секунд (тестував/ла)", points: 2, key: "A" }, { label: "3-5 секунд", points: 1, key: "B" }, { label: "Не знаю / повільно", points: 0, key: "C" }, ], }; } return { title: "Де клієнт побачить твої ціни і портфоліо?", hint: "Якщо людині треба писати щоб дізнатись ціну — більшість піде до того, хто показує одразу.", options: [ { label: "Все опубліковано (Stories Highlights / Linktree)", points: 2, key: "A", }, { label: "Тільки в DM по запиту", points: 1, key: "B" }, { label: "Ніде не публікую — все по запиту", points: 0, key: "C" }, ], }; }, }, // --- БЛОК 4: СОЦІАЛЬНИЙ ДОКАЗ --- { id: "q6", category: "reviews", title: "Скільки публічних відгуків у тебе зараз (Google + Instagram + сайт)?", hint: "Один новий відгук цього місяця > десять старих. Свіжість важлива.", options: [ { label: "20+ свіжих за останні 6 міс", points: 2, key: "A" }, { label: "5-19, але не всі свіжі", points: 1, key: "B" }, { label: "Менше 5 або їх взагалі немає публічно", points: 0, key: "C" }, ], }, // --- БЛОК 5: ВОРОНКА ЛІДІВ — ЯК ПРИХОДЯТЬ --- { id: "q7", category: "lead_inflow", title: "Через що зазвичай приходять нові заявки?", hint: "Якщо все через одне джерело — це ризик. Кожен канал, що зламається, заберає клієнтів.", options: [ { label: "Кілька каналів: форма + DM + рекомендації", points: 2, key: "A", }, { label: "Тільки DM в Instagram/Facebook", points: 1, key: "B" }, { label: "Тільки рекомендації знайомих", points: 0, key: "C" }, { label: "Не знаю, бувають хаотично", points: 0, key: "D" }, ], }, // --- БЛОК 6: ЗБЕРЕЖЕННЯ ЛІДІВ --- { id: "q8", category: "lead_storage", title: "Як ти зберігаєш контакти потенційних клієнтів?", hint: "Лід — це людина, яка цікавилася, але ще не купила. Без системи ти губиш їх щотижня.", options: [ { label: "CRM (Pipedrive / HubSpot / Notion CRM)", points: 2, key: "A", }, { label: "Excel або Notion-таблиця", points: 1, key: "B" }, { label: "У DM-листуванні / в голові / на папері", points: 0, key: "C", }, ], }, // --- БЛОК 7: ПОВЕРНЕННЯ ДО ЛІДІВ --- { id: "q9", category: "lead_followup", title: "Як часто повертаєшся до старих лідів (хто колись цікавився, але не купив)?", hint: "В середньому 60% угод закриваються після 5-12 контакту. Один контакт — це 6% продажів.", options: [ { label: "Регулярно: контент-розсилка / нагадування 1 раз на місяць", points: 2, key: "A", }, { label: "Інколи, коли згадую або є привід", points: 1, key: "B" }, { label: "Майже ніколи", points: 0, key: "C" }, ], }, // --- БЛОК 8: ШВИДКІСТЬ ВІДПОВІДІ --- { id: "q10", category: "response_speed", title: "Як швидко відповідаєш на нову заявку (DM, форма, дзвінок)?", hint: "Між 1-ю і 4-ю годиною шанс закрити заявку падає вдвічі.", options: [ { label: "Протягом 30 хвилин у робочий час", points: 2, key: "A" }, { label: "Протягом дня", points: 1, key: "B" }, { label: "Наступного дня або довше", points: 0, key: "C" }, ], }, // --- БЛОК 9: АВТО-ВІДПОВІДІ --- { id: "q11", category: "automation", title: "Чи є у тебе автоматичні відповіді на типові запити (час роботи, ціни, як записатися)?", hint: "Bot чи auto-reply економить 5-10 годин на тиждень і ловить ліди коли ти спиш.", options: [ { label: "Так — chatbot / auto-reply і на DM, і на сайті", points: 2, key: "A", }, { label: "Тільки auto-reply на Instagram", points: 1, key: "B" }, { label: "Ні, всі питання обробляю вручну", points: 0, key: "C" }, ], }, // --- БЛОК 10: AI --- { id: "q12", category: "ai", title: "Чи використовуєш AI у щоденній роботі бізнесу?", hint: "Не «використати ChatGPT раз на тиждень», а інтеграція в реальні процеси.", options: [ { label: "Так — генерую контент, обробляю DM, маю custom-агентів", points: 2, key: "A", }, { label: "Інколи (ChatGPT для тексту)", points: 1, key: "B" }, { label: "Не використовую", points: 0, key: "C" }, ], }, // --- БЛОК 11: АНАЛІТИКА --- { id: "q13", category: "analytics", title: "Чи знаєш, з якого каналу прийшли твої останні 5 клієнтів?", hint: "Без аналітики ти не знаєш, у що інвестувати час і гроші. Можеш «годувати» те, що не дає результату.", options: [ { label: "Так — є таблиця або CRM з джерелами", points: 2, key: "A" }, { label: "Приблизно памʼятаю", points: 1, key: "B" }, { label: "Не знаю", points: 0, key: "C" }, ], }, // --- БЛОК 12: ПЛАНУВАННЯ --- { id: "q14", category: "planning", title: "Як виглядає твій план просування на наступний місяць?", hint: "План з 3 пунктами краще за «список ідей з 30». Реальність бʼє план — але без плану нема куди стріляти.", options: [ { label: "Контент-план + KPI + (за бажанням) бюджет реклами", points: 2, key: "A", }, { label: "Список ідей у голові / нотатнику", points: 1, key: "B" }, { label: "Нема плану — як вийде", points: 0, key: "C" }, ], }, // --- БЛОК 13: PAIN POINT (фінальне, для адаптивних рекомендацій) --- { id: "q15", category: "pain", title: "Що для тебе зараз — головна болюча точка?", hint: "Це визначить, з чого Андрій почне у відповідь на твою діагностику.", options: [ { label: "Мало клієнтів — нікого не приходить", points: 0, key: "A", signal: "no_clients", }, { label: "Ліди приходять, але «відвалюються» дорогою", points: 0, key: "B", signal: "leaks", }, { label: "Багато рутини — не до маркетингу", points: 0, key: "C", signal: "burnout", }, { label: "Все працює, але хочу масштабувати", points: 1, key: "D", signal: "scale", }, { label: "Не знаю куди витрачати час/гроші", points: 0, key: "E", signal: "lost", }, ], }, ]; // ============== ADVICE per category, per option key ============== var ADVICE = { social: { A: { ic: "good", title: "Соцмережі — багатоканальність є", msg: "Сильна позиція. Перевір що кожен канал має свою «роль» — не дублюй контент 1-в-1, інакше алгоритми будуть тебе занижувати.", }, B: { ic: "mid", title: "Тільки Instagram — все яйця в одному кошику", msg: "Якщо алгоритм Instagram змінить охоплення (а він робить це регулярно), у тебе немає запасного каналу. Додай TikTok або YouTube Shorts — той самий контент, нова аудиторія.", }, C: { ic: "bad", title: "Тільки Facebook — застаріла стратегія для DK", msg: "У Данії FB майже мертвий для нових клієнтів у beauty/expert-сферах. Перейди на Instagram + (опційно) TikTok — це там, де твоя аудиторія.", }, D: { ic: "bad", title: "Без соцмереж — ти не існуєш для нових клієнтів", msg: "У Данії 70%+ клієнтів вирішують купувати побачивши останні 5-10 публікацій. Старт: Instagram, 3 пости на тиждень, 30 днів — і побачиш перший приріст заявок.", }, }, social_consistency: { A: { ic: "good", title: "Регулярний контент — фундамент є", msg: "Тримай ритм. Перевір що серед публікацій є мінімум 30% конкретики (фото робіт, кейси, відгуки) — лайфстайл-перепост хороший, але не продає.", }, B: { ic: "mid", title: "Контент є, але рідкий", msg: "Алгоритм любить регулярність. Постав цілі 3 пости + 5 Stories на тиждень. Через 30 днів охоплення зросте 2-3x.", }, C: { ic: "bad", title: "Майже немає контенту — невидима", msg: "Без свіжого контенту акаунт не показується новим людям. Перший крок: 7 днів × по 1 пост і 3 Stories. Не для досконалості — для перезапуску алгоритму.", }, }, gbp: { A: { ic: "good", title: "Google Business Profile активний", msg: "Чудово. Тримай: фото раз на тиждень, пост раз на 2 тижні, відповідай на ВСІ відгуки. У DK це дає реальну перевагу — конкуренти цим часто пренебригають.", }, B: { ic: "mid", title: "Профіль є, але «спить»", msg: "Запостився раз — і забула. Алгоритм Google за це зрізає видимість. Виділи 30 хв на тиждень: 1 фото + 1 пост + відповідь на відгуки. Ефект помітний за 2-3 тижні.", }, C: { ic: "bad", title: "Тебе нема в Google Maps — велика дірка", msg: "У Копенгагені люди шукають «manicure near me», «coach near me». Без Google Business тебе не покажуть. Заведи безкоштовно за 30 хв на business.google.com — це найдешевша видимість, що існує.", }, }, website: { A: { ic: "good", title: "Сучасний сайт — є", msg: "Перевір 3 речі: швидкість на телефоні, видні ціни, кнопка запису в один клік. Якщо все ОК — це твоя конкурентна перевага.", }, B: { ic: "mid", title: "Простий лендінг — ОК для старту", msg: "Працює як «цифрова візитка». Через 6-12 місяців стане замало. Думай про апгрейд: блог/портфоліо/booking-форму. Можна поетапно.", }, C: { ic: "mid", title: "Linktree / digital-візитка", msg: "OK для початку, але клієнт не може порівняти твої ціни з конкурентом без переходу в DM. Розглянь свій сайт — навіть простий lendingpage додає довіри і конверсії.", }, D: { ic: "bad", title: "Немає сайту — обмежуєш зростання", msg: "Соцмережі — оренда чужої платформи. Алгоритм змінили — клієнтів менше. Сайт — твоя власність. Простий one-pager робиться за тиждень.", }, }, website_quality: { A: { ic: "good", title: "Сайт швидкий — конверсії не падають", msg: "Тримай. Слідкуй за швидкістю кожних 2-3 місяці після додавання нового контенту чи плагінів.", }, B: { ic: "mid", title: "Швидкість середня", msg: "3-5 секунд = -7-15% конверсій. Перевір images compression, прибери непотрібні плагіни. Цільова — до 2.5 секунд на 4G.", }, C: { ic: "bad", title: "Швидкість невідома або повільна", msg: "Це найшвидший фікс із найбільшим ефектом. Тест на pagespeed.web.dev. Більшість проблем — несжаті зображення.", }, }, reviews: { A: { ic: "good", title: "20+ свіжих відгуків — сильна довіра", msg: "Браво. Продовжуй просити після кожного гарного клієнта. Відповідай на ВСІ — особливо на негативні (професійно). Це частина демонстрації клієнтського сервісу.", }, B: { ic: "mid", title: "Відгуки є, але мало свіжих", msg: "Зроби систему: автоматичний SMS / email через 1 день після візиту з посиланням на Google-відгук. За місяць +10-20 відгуків без зусиль.", }, C: { ic: "bad", title: "Майже немає публічних відгуків", msg: "Найдешевший інструмент довіри. Попроси 5 поточних задоволених клієнтів написати — це підніме твою позицію в Google за 2 тижні.", }, }, lead_inflow: { A: { ic: "good", title: "Кілька каналів лідів — диверсифіковано", msg: "Це здорова система — якщо один канал «провалиться», інші ще працюють. Виміряй конверсію кожного — десь може бути «слабка ланка».", }, B: { ic: "mid", title: "Тільки DM — крихка модель", msg: "Алгоритм Instagram змінив охоплення — і ти втратила 50% лідів за тиждень. Додай форму на сайті/Linktree з телефоном — і простий «whatsapp business» з широким посиланням.", }, C: { ic: "mid", title: "Тільки рекомендації — стелля", msg: "Чудовий канал, але обмежений твоїм колом. Щоб рости — потрібен «холодний» приплив через Google/Instagram/TikTok.", }, D: { ic: "bad", title: "Хаос у лідах", msg: "Без систематичного припливу ти не плануєш бюджет, не плануєш час. Перший крок — почати рахувати: звідки прийшов кожен новий клієнт. Excel вистачить.", }, }, lead_storage: { A: { ic: "good", title: "CRM — професійний підхід", msg: "Чудово. Перевір що користуєшся хоча б 50% можливостей — теги, нагадування, етапи. Інакше це той самий Excel з красивим UI.", }, B: { ic: "mid", title: "Excel/Notion — OK для малого бізнесу", msg: "Працює до 30-50 лідів на місяць. Потім — хаос. Думай про CRM коли почнеш втрачати ліди через те, що «забула передзвонити».", }, C: { ic: "bad", title: "Контакти не зберігаються — щотижня губиш гроші", msg: "DM-листування зникає в потоці. Постав мінімум: Excel з колонками «імʼя, телефон, дата звернення, статус». 5 хвилин в день — це 5-10 врятованих угод на місяць.", }, }, lead_followup: { A: { ic: "good", title: "Системний follow-up — рідкість", msg: "Це 5% власників бізнесу роблять. Тримай: розсилка раз на місяць про новинки, акції, корисний контент. Конверсія в продаж старих лідів — 5-15%.", }, B: { ic: "mid", title: "Інколи повертаєшся — гроші лежать", msg: "60% угод у середньому закриваються після 5-12 контакту. Якщо ти контактуєш 1-2 рази — губиш 60% потенційних клієнтів.", }, C: { ic: "bad", title: "Не повертаєшся — головна дірка в продажах", msg: "З 100 лідів за рік ти продав, скажімо, 20. Інші 80 — це не «ні», це «не зараз». Розсилка раз на місяць може повернути 5-10 угод/рік без витрат.", }, }, response_speed: { A: { ic: "good", title: "Швидка відповідь — конкурентна перевага", msg: "У DK конкуренти часто відповідають годинами. Твоя 30-хвилинна швидкість продає сама собою.", }, B: { ic: "mid", title: "Відповідь у той же день — нормально", msg: "Між 1-ю і 4-ю годиною шанс закрити заявку падає вдвічі. Постав auto-reply на перші 5-15 хвилин з підтвердженням, що ти бачиш заявку.", }, C: { ic: "bad", title: "Завтрашня відповідь — клієнт пішов", msg: "У Копенгагені конкуренти відповідають за хвилини. Або делегуй (асистент / бот), або переходь на канал з push-нотифікаціями.", }, }, automation: { A: { ic: "good", title: "Автоматизація працює", msg: "Це звільнює години щотижня. Раз на квартал переглядай — чи всі шаблони відповідей актуальні.", }, B: { ic: "mid", title: "Часткова автоматизація", msg: "Тільки на Instagram — добре, але на сайті/whatsapp клієнти теж пишуть. Розшир: chatbot з 5-10 типовими питаннями + контактна форма з auto-reply.", }, C: { ic: "bad", title: "Все вручну — час витікає", msg: "На 50 типових питань на місяць ти витрачаєш ~5 годин. Бот відповідає за 10 секунд. Налаштування — 1 день.", }, }, ai: { A: { ic: "good", title: "AI як інструмент — топ-5%", msg: "Ти випереджаєш 95% конкурентів. Тримай: тестуй нові інструменти раз на місяць — швидкість змін зараз величезна.", }, B: { ic: "mid", title: "AI як «помічник з текстом»", msg: "Хороший старт. Наступний рівень: автоматизація — пост-генератор з твого стилю, аналіз DM, обробка відгуків. Це -10 годин на тиждень.", }, C: { ic: "bad", title: "AI не використовуєш — програєш у часі і вартості", msg: "Не «AI замість тебе» — «AI робить рутину, ти робиш роботу». Один ChatGPT для текстів = -5 годин/тиждень. Custom AI-агент = -15 годин/тиждень.", }, }, analytics: { A: { ic: "good", title: "Аналітика — є", msg: "Тепер можна оптимізувати: лий бюджет тільки в те, що приносить клієнтів. Раз на місяць переглядай тренди.", }, B: { ic: "mid", title: "Орієнтуєшся приблизно — недостатньо", msg: "Постав простий лист: «дата · імʼя · звідки · сума». За 1 місяць побачиш реальну картину і відключиш половину марних активностей.", }, C: { ic: "bad", title: "Працюєш наосліп", msg: "Без аналітики — це лотерея. Перший крок: питай у кожного нового клієнта «як про мене дізналися?» і записуй. 5 секунд на клієнта = ціла маркетингова стратегія за квартал.", }, }, planning: { A: { ic: "good", title: "План з KPI — професійно", msg: "Це твоя конкурентна перевага. Раз на місяць ретроспектива: що спрацювало, що ні, який % від плану виконано.", }, B: { ic: "mid", title: "Список ідей — старт є", msg: "Перетвори у план з 3 пунктами на місяць + 1 KPI на кожен. Інакше «список ідей» = «нічого не зроблено».", }, C: { ic: "bad", title: "Без плану — без напрямку", msg: "Маркетинг без плану — це 80% часу витрачено даремно. Постав 1 ціль на місяць (напр., +10 лідів через Instagram) і 3 кроки до неї.", }, }, }; // ============== PAIN POINT recommendations ============== var PAIN_PLANS = { no_clients: { title: "Мало клієнтів — стратегія видимості", msg: "Перші 3 кроки: (1) Google Business Profile — 30 хв; (2) Instagram — 5 постів за тиждень + 10 Stories; (3) Linktree з телефоном і прайсом. Це базовий «цифровий мінімум». За 3 тижні буде +5-15 нових заявок. Якщо ні — пиши, зробимо безкоштовну сесію.", }, leaks: { title: "Ліди приходять, але «відвалюються»", msg: "Це найдорожча проблема — ти платиш за них трафіком, часом, рекламою. План: (1) auto-reply на DM/форму за перші 5 хв; (2) Excel «дата-імʼя-телефон-статус»; (3) follow-up через 24 год + ще раз через 7 днів. Конверсія мусить вирости в 1.5-2x за місяць.", }, burnout: { title: "Багато рутини — не до маркетингу", msg: "Класична пастка. Час лікується автоматизаціями: (1) chatbot з 5 типових питань — економить 5 год/тиждень; (2) auto-розсилка відгуків після візиту; (3) AI-генератор постів з твого стилю. Інвестиція 1 день — звільнення 10+ годин на тиждень.", }, scale: { title: "Масштабування — ти готова", msg: "Ти у топ-5% за організованістю. Наступний рівень: (1) запуск своїх курсів/інфопродуктів; (2) команда (помічниця, SMM, асистент); (3) платна реклама з аналітикою ROI. Поговоримо про конкретний фокус — є кілька варіантів залежно від того, що тобі цікаво.", }, lost: { title: "Не знаєш куди витрачати час/гроші", msg: "Це сигнал — потрібна стратегічна сесія. Подивимось на твою точку А, точку Б на 6-12 місяців, складемо план з 3 фокусних напрямів. Без розпорошення на 10 каналів. Перший контакт — безкоштовно.", }, }; // ============== LEVELS (4-tier on 30-point scale) ============== var LEVELS = [ { min: 0, max: 8, emoji: "🌱", title: "Старт з нуля", msg: "Більшість каналів і процесів ще не побудовані. Це нормально — зараз перший етап. Хороша новина: 80% твоїх конкурентів зупинилися на тому самому. Якщо зробиш базовий «цифровий мінімум» за 30 днів — обженеш половину.", }, { min: 9, max: 15, emoji: "🌿", title: "Базовий рівень", msg: "Основа є, але багато ручної роботи й «дірок» у воронці лідів. Сильна точка для старту автоматизацій — 1 день інвестиції економить 10+ годин на тиждень.", }, { min: 16, max: 22, emoji: "🌳", title: "Зростання", msg: "Машинка вже працює. Зараз головна задача — налаштувати слабкі ланки і вийти на масштаб через системне просування + AI-автоматизацію.", }, { min: 23, max: 30, emoji: "🚀", title: "Ефективність — топ-5%", msg: "Ти у топ-5% за організованістю бізнесу онлайн. Наступний рівень — масштаб, команда, нові продукти. Поговоримо предметно.", }, ]; // ============== STATE ============== var state = { idx: 0, answers: {}, score: 0, root: null }; function $(s, c) { return (c || document).querySelector(s); } function el(tag, attrs, children) { var n = document.createElement(tag); if (attrs) for (var k in attrs) { if (k === "className") n.className = attrs[k]; else if (k === "html") n.innerHTML = attrs[k]; else if (k.indexOf("on") === 0) n.addEventListener(k.slice(2).toLowerCase(), attrs[k]); else n.setAttribute(k, attrs[k]); } if (children) children.forEach(function (c) { if (typeof c === "string") n.appendChild(document.createTextNode(c)); else if (c) n.appendChild(c); }); return n; } function getQuestion(i) { var q = QUIZ[i]; if (q.adaptive) return Object.assign({}, q, q.build(state.answers)); return q; } function categoryLabel(cat) { var map = { social: "Соцмережі", social_consistency: "Регулярність контенту", gbp: "Google Business", website: "Сайт / лендінг", website_quality: "Якість сайту", reviews: "Відгуки", lead_inflow: "Воронка лідів", lead_storage: "Збереження лідів", lead_followup: "Повернення лідів", response_speed: "Швидкість відповіді", automation: "Авто-відповіді", ai: "AI у роботі", analytics: "Аналітика", planning: "Планування", pain: "Болюча точка", }; return map[cat] || cat; } function renderStep() { var root = state.root; root.innerHTML = ""; var q = getQuestion(state.idx); var total = QUIZ.length; var pct = Math.round((state.idx / total) * 100); root.appendChild( el("div", { className: "wd-quiz__header" }, [ el("span", null, [ "Питання " + (state.idx + 1) + " / " + total + " · " + categoryLabel(q.category), ]), el("span", null, [pct + "%"]), ]), ); root.appendChild( el("div", { className: "wd-quiz__progress" }, [ el("div", { className: "wd-quiz__progress-fill", style: "width:" + pct + "%", }), ]), ); var step = el("div", { className: "wd-quiz__step" }); step.appendChild(el("h3", { className: "wd-quiz__question" }, [q.title])); if (q.hint) step.appendChild(el("p", { className: "wd-quiz__hint" }, [q.hint])); var opts = el("div", { className: "wd-quiz__options" }); var sel = state.answers[q.id] && state.answers[q.id].key; q.options.forEach(function (opt) { var lbl = el( "label", { className: "wd-quiz__option" + (sel === opt.key ? " is-selected" : ""), onclick: function () { state.answers[q.id] = { key: opt.key, points: opt.points, label: opt.label, category: q.category, signal: opt.signal, }; renderStep(); setTimeout(next, 240); }, }, [ el("span", { className: "wd-quiz__option-mark" }), el("span", null, [opt.label]), ], ); opts.appendChild(lbl); }); step.appendChild(opts); step.appendChild( el("div", { className: "wd-quiz__nav" }, [ el( "button", { className: "wd-btn wd-btn--ghost", type: "button", disabled: state.idx === 0 ? true : null, onclick: prev, }, ["← Назад"], ), el("span", null, []), ]), ); root.appendChild(step); } function prev() { if (state.idx > 0) { state.idx--; renderStep(); } } function next() { if (state.idx < QUIZ.length - 1) { state.idx++; renderStep(); } else renderResult(); } function calcScore() { var s = 0; for (var k in state.answers) s += state.answers[k].points || 0; return s; } function findLevel(score) { for (var i = 0; i < LEVELS.length; i++) { if (score >= LEVELS[i].min && score <= LEVELS[i].max) return LEVELS[i]; } return LEVELS[0]; } function getPainPlan() { var p15 = state.answers.q15; if (!p15 || !p15.signal) return null; return PAIN_PLANS[p15.signal] || null; } function renderResult() { var root = state.root; root.innerHTML = ""; var score = calcScore(); state.score = score; var level = findLevel(score); var pain = getPainPlan(); root.appendChild( el("div", { className: "wd-result__hero" }, [ el("div", { className: "wd-result__emoji" }, [level.emoji]), el("div", { className: "wd-result__score" }, [ "Твій бал: " + score + " / 30", ]), el("h3", { className: "wd-result__level" }, ["Рівень: " + level.title]), el("p", { className: "wd-result__msg" }, [level.msg]), ]), ); if (pain) { var painBlock = el("div", { className: "wd-result__pain" }); painBlock.appendChild( el("div", { className: "wd-result__pain-eyebrow" }, [ "⚡ Твоя болюча точка → план виходу", ]), ); painBlock.appendChild( el("h4", { className: "wd-result__pain-title" }, [pain.title]), ); painBlock.appendChild( el("p", { className: "wd-result__pain-text" }, [pain.msg]), ); root.appendChild(painBlock); } var grid = el("div", { className: "wd-result__grid" }); QUIZ.forEach(function (q) { if (q.category === "pain") return; // pain is рекомендований окремо var ans = state.answers[q.id]; if (!ans) return; var advice = ADVICE[ans.category]; if (!advice) return; var item = advice[ans.key]; if (!item) return; var icon = el("div", { className: "wd-result__icon is-" + item.ic }); icon.textContent = item.ic === "good" ? "✓" : item.ic === "mid" ? "!" : "×"; grid.appendChild( el("div", { className: "wd-result__row" }, [ icon, el("div", null, [ el("h4", { className: "wd-result__title" }, [item.title]), el("p", { className: "wd-result__action" }, [item.msg]), ]), ]), ); }); root.appendChild(grid); root.appendChild(buildLeadForm(score, level, pain)); } function buildLeadForm(score, level, pain) { var wrap = el("div", { className: "wd-lead" }); wrap.appendChild( el("h3", { className: "wd-lead__title" }, [ "Хочеш персональний план дій під свій бізнес?", ]), ); wrap.appendChild( el("p", { className: "wd-lead__desc" }, [ "Залиш контакти — Андрій вивчає твій результат і відповідає письмово зі списком конкретних кроків (≈30 хв роботи). Безкоштовно. Без розсилок.", ]), ); var msg = el("div", { className: "wd-form-msg", id: "wdLeadMsgEl" }); wrap.appendChild(msg); var grid = el("div", { className: "wd-grid-2" }); grid.appendChild( el("div", { className: "wd-field" }, [ el("label", { for: "wdlFirst" }, ["Імʼя"]), el("input", { type: "text", id: "wdlFirst", name: "first_name", required: "required", placeholder: "Як до тебе звертатися", }), ]), ); grid.appendChild( el("div", { className: "wd-field" }, [ el("label", { for: "wdlLast" }, ["Прізвище"]), el("input", { type: "text", id: "wdlLast", name: "last_name", required: "required", placeholder: "Прізвище", }), ]), ); grid.appendChild( el("div", { className: "wd-field" }, [ el("label", { for: "wdlPhone" }, ["Телефон"]), el("input", { type: "tel", id: "wdlPhone", name: "phone", required: "required", placeholder: "+45 12345678", }), ]), ); var emailField = ( el("div", { className: "wd-field" }, [ el("label", { for: "wdlEmail" }, ["Email — надішлемо код для підтвердження"]), el("input", { type: "email", id: "wdlEmail", name: "email", required: "required", placeholder: "you@example.com", }), ]) ); wrap.appendChild(grid); wrap.appendChild(emailField); wrap.appendChild( el("label", { className: "wd-checkbox" }, [ el("input", { type: "checkbox", name: "consent", required: "required", }), el("span", null, [ "Я погоджуюсь, що Андрій звʼяжеться зі мною з персональною відповіддю на quiz. Без спаму, без передачі даних.", ]), ]), ); var btn = el( "button", { className: "wd-btn wd-btn--primary", type: "button", onclick: function () { submitLead(score, level, pain, btn, wrap); }, }, ["📨 Отримати персональну відповідь"], ); wrap.appendChild(btn); if (window.WDLead) window.WDLead.attachQuiz(wrap, emailField, btn); return wrap; } function submitLead(score, level, pain, btn, wrap) { var first = $("#wdlFirst").value.trim(); var last = $("#wdlLast").value.trim(); var phone = $("#wdlPhone").value.trim(); var consent = wrap.querySelector('input[name="consent"]').checked; var msgEl = document.getElementById("wdLeadMsgEl"); function showMsg(text, ok) { if (!msgEl) return; msgEl.className = "wd-form-msg " + (ok ? "is-ok" : "is-error"); msgEl.textContent = text; } if (!window.WDLead) return showMsg("Не вдалося завантажити форму. Онови сторінку.", false); btn.disabled = true; btn.textContent = "Надсилаю…"; // ліди йдуть у andrii/v1/contact/submit лише з підтвердженою кодом поштою (landing/wd_leads.js) window.WDLead.submitQuiz({ first: first, last: last, phone: phone, consent: consent, quiz: { score: score, level: level.title, pain: pain ? pain.title : "не вказано", answers: JSON.stringify(state.answers) }, }).then(function (r) { if (r.ok) return showSuccess(first, wrap); btn.disabled = false; btn.textContent = "📨 Отримати персональну відповідь"; showMsg(r.msg, false); }).catch(function () { btn.disabled = false; btn.textContent = "📨 Отримати персональну відповідь"; showMsg("Не вдалося надіслати. Онови сторінку або напиши на info@andrii.dk.", false); }); } function showSuccess(first, wrap) { wrap.innerHTML = ""; var box = el("div", { style: "text-align:center;padding:14px 0" }, [ el("div", { style: "font-size:48px;line-height:1;margin-bottom:10px" }, [ "🎉", ]), el("h3", { className: "wd-lead__title", style: "text-align:center" }, [ "Дякую, " + first + "!", ]), el( "p", { className: "wd-lead__desc", style: "text-align:center;max-width:520px;margin:0 auto 18px", }, [ "Твій quiz-результат отримано. Андрій вивчає його і відповість письмово протягом 24-48 годин на твій email і телефон.", ], ), el("p", { style: "font-size:13.5px;color:var(--wd-muted)" }, [ "Поки чекаєш — поглянь свій звіт по 12 категоріях вище. Там вже є конкретні дії, які можна почати робити сьогодні.", ]), ]); wrap.appendChild(box); // celebration: fire heart confetti from center of success box try { var r = box.getBoundingClientRect(); window.dispatchEvent( new CustomEvent("wd:lead-success", { detail: { x: r.left + r.width / 2, y: r.top + 80 }, }), ); } catch (e) {} } function init() { state.root = document.getElementById("wdQuizRoot"); if (!state.root) return; renderStep(); } if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init); else init(); })();