(function() { const BACKEND_URL = "https://agent.eenlijstje.nl"; const KLANT_ID = "eenlijstje-001"; const SECRET_TOKEN = "qBaXjyUDr-6wRuT6wV8xSY9_T9SPMu-RY5ijVZeRETk"; const DELAY_SECONDEN = 15; const BOT_NAAM = "Cadeauassistent"; const BEDRIJF_NAAM = ""; const TEAL = "#E8625A"; const TEAL_DARK = "#C94D45"; const CHARCOAL = "#1E1E1C"; const AVATAR_URL = "https://agent.sprintmarketing.nl/static/gift_avatar.svg"; const HEADER_URL = ""; const CALENDLY_URL = "https://calendly.com/sprintmarketing-info/30min"; // ── Feature flags (server-side geïnjecteerd) ─────────────────────────────── var GTM_EVENTS_ACTIEF = true; var IS_BESCHIKBAAR = true; var BUITEN_UREN_MODUS = "offline_bericht"; var EMAIL_GATE = false; var EMAIL_GATE_VERPLICHT = false; var PRIVACY_URL = ""; // ── State ────────────────────────────────────────────────────────────────── var sessionId = null; var chatOpen = false; var bezig = false; var proactief_getoond = false; var openerGetoond = false; var _berichtTeller = 0; var _gatewayEmail = null; // Closure flag — voorkomt dubbele dataLayer-patch zonder publieke global te vervuilen var _consentListenerGeinstalleerd = false; // ── Consent helpers (GDPR — functionality_storage) ──────────────────────── // Cookie wordt elke aanroep opnieuw gelezen zodat revocatie mid-sessie wordt gehonoreerd. // Contract CookieMonster: cookie_consent='all' of cookie_consent=JSON{preferences:true/false} var _CONSENT_COOKIE_PREFIX = 'cookie_consent='; function _heeftFunctioneleConsent() { try { var cookies = document.cookie.split('; '); for (var i = 0; i < cookies.length; i++) { if (cookies[i].indexOf(_CONSENT_COOKIE_PREFIX) === 0) { var val = cookies[i].substring(_CONSENT_COOKIE_PREFIX.length); if (val === 'all') return true; try { var parsed = JSON.parse(decodeURIComponent(val)); return parsed.preferences === true; } catch(e) { return false; } } } } catch(e) {} return false; } function _consentVerleend() { _markeerBezoek(); // alsnog markeren als bezoek al geweest was zonder consent } function _activeerConsentListener() { if (typeof window.dataLayer === 'undefined') return; if (_consentListenerGeinstalleerd) return; // closure flag, niet op publieke global _consentListenerGeinstalleerd = true; var origPush = window.dataLayer.push.bind(window.dataLayer); window.dataLayer.push = function(obj) { origPush(obj); if (obj && obj.event === 'cookie_consent') { var heeftPrefs = (obj.consent_type === 'all') || (obj.consent_type === 'selected' && obj.preferences === true); if (heeftPrefs) { _consentVerleend(); } else if (eerderGeweest) { // Alleen wissen als er daadwerkelijk data staat (event vuurt bij elke paginaload) _wisConsentData(); eerderGeweest = false; } } }; } // ── Returning visitor herkenning (localStorage, functioneel gebruik) ──────── // Gezet bij eerste chat-opening, niet bij paginabezoek — anders triggert het // bij bouncers die terugkomen zonder ooit een gesprek te hebben gevoerd. var SM_BEZOEK_KEY = "sm_bezoek_" + KLANT_ID + "_" + location.hostname; var eerderGeweest = false; if (_heeftFunctioneleConsent()) { try { eerderGeweest = !!localStorage.getItem(SM_BEZOEK_KEY); } catch(e) { /* private browsing — geen probleem */ } } function _markeerBezoek() { if (!_heeftFunctioneleConsent()) return; try { localStorage.setItem(SM_BEZOEK_KEY, Date.now().toString()); } catch(e) {} } // ── Sessieherstel: onderwerp opslaan/lezen (90 dagen TTL) ───────────────── var SM_ONDERWERP_KEY = "sm_onderwerp_" + KLANT_ID + "_" + location.hostname; function _leesOnderwerp() { if (!_heeftFunctioneleConsent()) return null; try { var raw = localStorage.getItem(SM_ONDERWERP_KEY); if (!raw) return null; var data = JSON.parse(raw); var TTL_MS = 90 * 24 * 60 * 60 * 1000; if (Date.now() - data.opgeslagen_op > TTL_MS) { localStorage.removeItem(SM_ONDERWERP_KEY); return null; } return data.onderwerp; } catch(e) { return null; } } function _slaOnderwerpOp(onderwerp) { if (!_heeftFunctioneleConsent()) return; try { localStorage.setItem(SM_ONDERWERP_KEY, JSON.stringify({ onderwerp: onderwerp, opgeslagen_op: Date.now() })); } catch(e) {} } // ── Email gate: e-mail opslaan/lezen ────────────────────────────────────── var SM_EMAIL_KEY = "sm_email_" + KLANT_ID + "_" + location.hostname; function _wisConsentData() { // Verwijder localStorage-data bij revocatie (GDPR: consent is intrekbaar) // Staat bewust ná SM_BEZOEK_KEY, SM_ONDERWERP_KEY en SM_EMAIL_KEY declaraties try { localStorage.removeItem(SM_BEZOEK_KEY); localStorage.removeItem(SM_ONDERWERP_KEY); localStorage.removeItem(SM_EMAIL_KEY); } catch(e) {} } function _leesEmailGate() { if (!_heeftFunctioneleConsent()) return null; try { return localStorage.getItem(SM_EMAIL_KEY) || null; } catch(e) { return null; } } // ── GTM helper ──────────────────────────────────────────────────────────── function _gtmEvent(eventNaam, extraData) { if (!GTM_EVENTS_ACTIEF) return; if (typeof window.dataLayer === "undefined") return; var payload = Object.assign({ event: eventNaam, klant_id: KLANT_ID }, extraData || {}); window.dataLayer.push(payload); } // ── Pagina-tracking (sessiegeheugen alleen, geen cookies/localStorage) ───── // Geen consent nodig: data verdwijnt bij sluiten tab, wordt niet opgeslagen. var bezochteSegmenten = {}; function _registreerPagina(pad) { var p = pad.toLowerCase(); if (p.indexOf("pakket") !== -1 || p.indexOf("prijs") !== -1) bezochteSegmenten["prijs"] = true; if (p.indexOf("seo") !== -1) bezochteSegmenten["seo"] = true; if (p.indexOf("ads") !== -1 || p.indexOf("sea") !== -1 || p.indexOf("google") !== -1) bezochteSegmenten["ads"] = true; if (p.indexOf("automat") !== -1 || p.indexOf("systeem") !== -1) bezochteSegmenten["automaat"] = true; if (p === "/" || p === "") bezochteSegmenten["home"] = true; } _registreerPagina(window.location.pathname); // ── 3 keuze-opties (server-side geïnjecteerd per tenant) ────────────────── var KEUZE_OPTIES = [{"label": "Ik zoek een cadeau, maar weet niet wat.", "bericht": "Ik zoek een cadeau maar weet nog niet wat. Kun je me helpen?"}, {"label": "Hoe werkt lootjes trekken via eenlijstje?", "bericht": "Hoe werkt lootjes trekken via eenlijstje.nl?"}, {"label": "Kan ik een verlanglijstje delen met vrienden?", "bericht": "Hoe deel ik een verlanglijstje met vrienden of familie?"}]; // ── Fonts ────────────────────────────────────────────────────────────────── if (!document.getElementById("sm-fonts")) { var lnk = document.createElement("link"); lnk.id = "sm-fonts"; lnk.rel = "stylesheet"; lnk.href = "https://fonts.googleapis.com/css2?family=Bitter:wght@400;500;600&family=Young+Serif&display=swap"; document.head.appendChild(lnk); } // ── Stijlen ──────────────────────────────────────────────────────────────── var stl = document.createElement("style"); stl.textContent = [ // Floating button "#sm-chat-btn{position:fixed;bottom:24px;right:24px;width:64px;height:64px;", "border-radius:14px;background:transparent;border:none;cursor:pointer;", "z-index:2147483646;padding:0;transition:transform 0.2s;}", "#sm-chat-btn:hover{transform:scale(1.06);}", "#sm-btn-wrap{position:relative;width:64px;height:64px;}", "#sm-btn-avatar{width:64px;height:64px;border-radius:14px;object-fit:cover;object-position:center top;", "box-shadow:0 4px 16px rgba(0,0,0,0.18);display:block;}", "#sm-online-dot{position:absolute;bottom:4px;right:4px;width:13px;height:13px;", "border-radius:50%;background:#22c55e;border:2.5px solid #fff;", "box-shadow:0 0 0 0 rgba(34,197,94,0.5);animation:sm-pulse 2s infinite;}", "@keyframes sm-pulse{0%{box-shadow:0 0 0 0 rgba(34,197,94,0.5);}", "70%{box-shadow:0 0 0 8px rgba(34,197,94,0);}100%{box-shadow:0 0 0 0 rgba(34,197,94,0);}}", // Chatvenster "#sm-chat-window{position:fixed;bottom:100px;right:24px;width:368px;max-height:580px;", "background:#fff;border-radius:16px;box-shadow:0 8px 40px rgba(0,0,0,0.14);", "z-index:2147483645;display:none;flex-direction:column;", "font-family:'Bitter',Georgia,serif;overflow:hidden;}", "@media(max-width:480px){#sm-chat-window{width:calc(100vw - 24px);right:12px;bottom:92px;max-height:65vh;}}", // Header "#sm-chat-header{background:" + TEAL + ";color:#fff;padding:12px 14px;", "display:flex;align-items:center;gap:10px;flex-shrink:0;}", "#sm-header-photo{display:none;}", "#sm-header-bar{display:contents;}", "#sm-header-avatar{width:40px;height:40px;border-radius:8px;object-fit:cover;object-position:center top;flex-shrink:0;}", "#sm-header-info{flex:1;}", "#sm-header-naam{font-family:'Young Serif',Georgia,serif;font-weight:400;font-size:15px;line-height:1.2;}", "#sm-header-status{font-size:11px;opacity:0.85;display:flex;align-items:center;gap:5px;margin-top:2px;}", "#sm-status-dot{width:7px;height:7px;border-radius:50%;background:#7dffb3;display:inline-block;flex-shrink:0;}", "#sm-header-actions{display:flex;gap:4px;align-items:center;}", ".sm-hdr-btn{background:none;border:none;color:rgba(255,255,255,0.85);cursor:pointer;", "font-size:18px;line-height:1;padding:5px 6px;border-radius:6px;transition:background 0.15s;}", ".sm-hdr-btn:hover{background:rgba(255,255,255,0.15);color:#fff;}", // Berichten "#sm-chat-messages{flex:1;overflow-y:auto;padding:14px;", "display:flex;flex-direction:column;gap:10px;background:#f8fafa;}", ".sm-msg-user{align-self:flex-end;background:" + TEAL + ";color:#fff;", "padding:9px 13px;border-radius:14px 14px 3px 14px;max-width:80%;", "font-size:14px;line-height:1.5;word-break:break-word;}", ".sm-msg-bot-wrap{display:flex;gap:8px;align-items:flex-start;max-width:90%;}", ".sm-msg-mini-avatar{width:28px;height:28px;border-radius:6px;object-fit:cover;", "object-position:center top;flex-shrink:0;}", ".sm-msg-bot{background:#fff;color:" + CHARCOAL + ";padding:9px 13px;", "border-radius:14px 14px 14px 3px;font-size:14px;line-height:1.6;", "box-shadow:0 1px 4px rgba(0,0,0,0.07);word-break:break-word;}", ".sm-msg-bot strong{color:" + TEAL_DARK + ";font-weight:600;}", ".sm-msg-bot a{color:" + TEAL_DARK + ";text-decoration:underline;}", // Keuze-knoppen: solid teal "#sm-keuzes{display:flex;flex-direction:column;gap:7px;padding-left:36px;}", ".sm-keuze-btn{background:" + TEAL + ";color:#fff;border:none;", "border-radius:10px;padding:9px 14px;font-size:13px;", "font-family:'Bitter',Georgia,serif;cursor:pointer;text-align:left;", "transition:background 0.15s;line-height:1.45;width:100%;}", ".sm-keuze-btn:hover{background:" + TEAL_DARK + ";}", // Typeer-bellen ".sm-typing{background:#fff;padding:10px 14px;border-radius:14px 14px 14px 3px;", "box-shadow:0 1px 4px rgba(0,0,0,0.07);display:flex;gap:5px;align-items:center;}", ".sm-typing span{width:7px;height:7px;border-radius:50%;background:" + TEAL + ";", "animation:sm-bounce 1.2s infinite;}", ".sm-typing span:nth-child(2){animation-delay:0.2s;}", ".sm-typing span:nth-child(3){animation-delay:0.4s;}", "@keyframes sm-bounce{0%,60%,100%{transform:translateY(0);opacity:0.4;}", "30%{transform:translateY(-6px);opacity:1;}}", // Woord-animatie "@keyframes sm-fadeword{from{opacity:0;transform:translateY(2px);}to{opacity:1;transform:translateY(0);}}", ".sm-word{display:inline;animation:sm-fadeword 0.12s ease forwards;opacity:0;}", // Inputbalk "#sm-chat-input-area{padding:10px 12px 6px;border-top:1px solid #e8eded;", "display:flex;gap:8px;background:#fff;flex-shrink:0;}", "#sm-honeypot{display:none!important;}", "#sm-chat-input{flex:1;border:1.5px solid #dde8e8;border-radius:10px;", "padding:9px 12px;font-size:14px;font-family:'Bitter',Georgia,serif;", "outline:none;transition:border-color 0.2s;color:" + CHARCOAL + ";background:#f8fafa;}", "#sm-chat-input:focus{border-color:" + TEAL + ";background:#fff;}", "#sm-chat-send{background:" + TEAL + ";color:#fff;border:none;border-radius:10px;", "padding:9px 14px;cursor:pointer;font-size:16px;transition:background 0.2s;flex-shrink:0;}", "#sm-chat-send:hover{background:" + TEAL_DARK + ";}", "#sm-chat-send:disabled{opacity:0.5;cursor:not-allowed;}", // Privacy tekst onderaan "#sm-privacy-bar{font-size:11px;color:#aaa;background:#fff;", "padding:4px 12px 10px;text-align:center;flex-shrink:0;}", "#sm-privacy-bar a{color:#aaa;text-decoration:underline;}", "#sm-privacy-bar a:hover{color:" + TEAL_DARK + ";}", // Bronvermelding onder bot-antwoord ".sm-bron{font-size:11px;color:#999;margin-top:5px;display:block;}", ".sm-bron a{color:#999;text-decoration:none;}", ".sm-bron a:hover{color:" + TEAL_DARK + ";text-decoration:underline;}", // Offline formulier ".sm-offline-form{padding:20px 16px;display:flex;flex-direction:column;gap:12px;}", ".sm-offline-titel{font-family:'Young Serif',Georgia,serif;font-size:16px;color:" + CHARCOAL + ";margin:0;}", ".sm-offline-tekst{font-size:13px;color:#666;margin:0;line-height:1.5;}", ".sm-offline-field{display:flex;flex-direction:column;gap:4px;}", ".sm-offline-label{font-size:12px;color:#888;}", ".sm-offline-input{border:1.5px solid #dde8e8;border-radius:10px;padding:9px 12px;", "font-size:14px;font-family:'Bitter',Georgia,serif;outline:none;", "transition:border-color 0.2s;color:" + CHARCOAL + ";background:#f8fafa;}", ".sm-offline-input:focus{border-color:" + TEAL + ";background:#fff;}", ".sm-offline-btn{background:" + TEAL + ";color:#fff;border:none;border-radius:10px;", "padding:10px 16px;font-size:14px;font-family:'Bitter',Georgia,serif;", "cursor:pointer;transition:background 0.2s;}", ".sm-offline-btn:hover{background:" + TEAL_DARK + ";}", ".sm-offline-fout{font-size:12px;color:#e74c3c;margin:0;}", ".sm-offline-bevestiging{font-size:14px;color:#22c55e;text-align:center;padding:20px;line-height:1.6;}", // Email gate ".sm-emailgate-form{padding:20px 16px;display:flex;flex-direction:column;gap:12px;}", ".sm-emailgate-titel{font-family:'Young Serif',Georgia,serif;font-size:16px;color:" + CHARCOAL + ";margin:0;}", ".sm-emailgate-avg{font-size:11px;color:#999;margin:0;line-height:1.5;}", ".sm-emailgate-avg a{color:#999;text-decoration:underline;}", ".sm-emailgate-fout{font-size:12px;color:#e74c3c;margin:0;}", ".sm-emailgate-actions{display:flex;gap:8px;align-items:center;}", ".sm-emailgate-skip{background:none;border:none;color:#aaa;font-size:13px;", "cursor:pointer;padding:4px;text-decoration:underline;}", // Product kaarten ".sm-producten{display:flex;flex-direction:column;gap:8px;margin-top:4px;width:100%;max-width:340px;}", ".sm-product-kaart{display:flex;align-items:center;gap:10px;background:#fff;", "border:1.5px solid #e8eded;border-radius:10px;padding:8px 10px;cursor:pointer;", "transition:border-color 0.15s,box-shadow 0.15s;position:relative;}", ".sm-product-kaart:hover{border-color:" + TEAL + ";box-shadow:0 2px 8px rgba(0,0,0,0.08);}", ".sm-product-kaart.geselecteerd{border-color:" + TEAL + ";background:" + TEAL + "12;}", ".sm-product-check{width:18px;height:18px;border:2px solid #ccc;border-radius:4px;", "flex-shrink:0;display:flex;align-items:center;justify-content:center;", "transition:background 0.15s,border-color 0.15s;}", ".sm-product-kaart.geselecteerd .sm-product-check{background:" + TEAL + ";border-color:" + TEAL + ";color:#fff;}", ".sm-product-afb{width:44px;height:44px;border-radius:6px;object-fit:cover;flex-shrink:0;background:#f0f0f0;}", ".sm-product-info{flex:1;min-width:0;}", ".sm-product-naam{font-size:13px;font-weight:600;color:" + CHARCOAL + ";", "white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block;}", ".sm-product-meta{font-size:12px;color:#888;margin-top:1px;", "white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block;}", ".sm-product-link{font-size:11px;color:" + TEAL_DARK + ";text-decoration:none;margin-top:3px;display:block;}", ".sm-product-link:hover{text-decoration:underline;}", ".sm-lijstje-acties{display:flex;gap:6px;margin-top:6px;flex-wrap:wrap;}", ".sm-lijstje-btn{background:" + TEAL + ";color:#fff;border:none;border-radius:8px;", "padding:7px 12px;font-size:12px;font-family:'Bitter',Georgia,serif;", "cursor:pointer;transition:background 0.15s;}", ".sm-lijstje-btn:hover{background:" + TEAL_DARK + ";}", ".sm-lijstje-btn:disabled{opacity:0.6;cursor:not-allowed;}", ".sm-lijstje-select{border:1.5px solid #dde8e8;border-radius:8px;", "padding:6px 10px;font-size:12px;font-family:'Bitter',Georgia,serif;", "background:#f8fafa;color:" + CHARCOAL + ";outline:none;}", ".sm-lijstje-status{font-size:12px;color:#22c55e;margin-top:4px;}", ".sm-lijstje-fout{font-size:12px;color:#e74c3c;margin-top:4px;}", ".sm-nieuw-lijstje-form{display:flex;gap:6px;margin-top:4px;align-items:center;}", ".sm-nieuw-lijstje-input{flex:1;border:1.5px solid #dde8e8;border-radius:8px;", "padding:6px 10px;font-size:12px;font-family:'Bitter',Georgia,serif;", "background:#f8fafa;color:" + CHARCOAL + ";outline:none;min-width:0;}", ".sm-nieuw-lijstje-input:focus{border-color:" + TEAL + ";background:#fff;}" ].join(""); document.head.appendChild(stl); // ── DOM: floating button ─────────────────────────────────────────────────── var btn = document.createElement("button"); btn.id = "sm-chat-btn"; btn.setAttribute("aria-label", "Chat openen met " + BOT_NAAM); btn.innerHTML = '
' + '' + BOT_NAAM + '' + '' + '
'; document.body.appendChild(btn); // Verberg button + online-dot als widget offline is en modus = verborgen if (_isOffline() && BUITEN_UREN_MODUS === "verborgen") { btn.style.display = "none"; } // ── DOM: chatvenster ─────────────────────────────────────────────────────── var venster = document.createElement("div"); venster.id = "sm-chat-window"; venster.setAttribute("role", "dialog"); venster.setAttribute("aria-label", "Chat met " + BOT_NAAM); venster.innerHTML = '
' + '' + BOT_NAAM + '' + '
' + '
' + BOT_NAAM + (BEDRIJF_NAAM ? ' van ' + BEDRIJF_NAAM : '') + '
' + '
Online
' + '
' + '
' + '' + '' + '
' + '
' + '
' + '
' + '' + '' + '' + '
' + '
' + 'Door te chatten ga je akkoord met onze privacyverklaring.' + '
'; document.body.appendChild(venster); // ── Helpers ──────────────────────────────────────────────────────────────── function _markdownNaarHtml(tekst) { // Strip [CONTACTVERZOEK: ...] tags — intern systeem, niet tonen aan bezoeker tekst = tekst.replace(/\[CONTACTVERZOEK:[^\]]*\]/g, "").trim(); return tekst .replace(/&/g, "&").replace(//g, ">") .replace(/\*\*(.+?)\*\*/g, "$1") .replace(/\*(.+?)\*/g, "$1") .replace(/\[([^\]]+)\]\((https?:\/\/[^\)]+)\)/g, '$1') // Kale URLs klikbaar maken .replace(/(^|[\s>])(https?:\/\/[^\s<&]+)/g, '$1$2') .replace(/\n/g, "
"); } function _maakBotWrap() { var wrap = document.createElement("div"); wrap.className = "sm-msg-bot-wrap"; var mini = document.createElement("img"); mini.className = "sm-msg-mini-avatar"; mini.src = AVATAR_URL; mini.alt = BOT_NAAM; wrap.appendChild(mini); return wrap; } function _verwijderKeuzes() { var el = document.getElementById("sm-keuzes"); if (el) el.parentNode.removeChild(el); } function toonKeuzeOpties() { _verwijderKeuzes(); var berichten = document.getElementById("sm-chat-messages"); var container = document.createElement("div"); container.id = "sm-keuzes"; KEUZE_OPTIES.forEach(function(opt) { var knop = document.createElement("button"); knop.className = "sm-keuze-btn"; knop.textContent = opt.label; knop.addEventListener("click", function() { _gtmEvent("sm_keuze_geklikt", { keuze_label: opt.label }); _verwijderKeuzes(); stuurBericht(opt.bericht); }); container.appendChild(knop); }); berichten.appendChild(container); setTimeout(function() { container.scrollIntoView({ block: "end", behavior: "smooth" }); }, 50); } function toonTypeerAnimatie() { var berichten = document.getElementById("sm-chat-messages"); var wrap = _maakBotWrap(); var typing = document.createElement("div"); typing.className = "sm-typing"; typing.id = "sm-typing-indicator"; typing.innerHTML = ""; wrap.appendChild(typing); berichten.appendChild(wrap); berichten.scrollTop = berichten.scrollHeight; return wrap; } function verwijderTypeerAnimatie(wrap) { if (wrap && wrap.parentNode) wrap.parentNode.removeChild(wrap); } function voegUserBerichtToe(tekst) { var berichten = document.getElementById("sm-chat-messages"); var div = document.createElement("div"); div.className = "sm-msg-user"; div.textContent = tekst; berichten.appendChild(div); berichten.scrollTop = berichten.scrollHeight; } function toonBotAntwoordAnimated(tekst, metKeuzes, bron) { var berichten = document.getElementById("sm-chat-messages"); var wrap = _maakBotWrap(); var div = document.createElement("div"); div.className = "sm-msg-bot"; wrap.appendChild(div); berichten.appendChild(wrap); setTimeout(function() { wrap.scrollIntoView({ block: "start", behavior: "smooth" }); }, 50); var html = _markdownNaarHtml(tekst); var delen = html.split(/(<[^>]+>|&[^;]+;)/); var vertraging = 0; var WOORD_MS = 38; delen.forEach(function(deel) { if (!deel) return; if (deel.charAt(0) === "<" || deel.charAt(0) === "&") { var s = document.createElement("span"); s.innerHTML = deel; div.appendChild(s); } else { deel.split(" ").forEach(function(woord, i) { if (!woord && i === 0) return; var s = document.createElement("span"); s.className = "sm-word"; s.innerHTML = (div.childNodes.length > 0 ? " " : "") + woord; s.style.animationDelay = vertraging + "ms"; vertraging += WOORD_MS; div.appendChild(s); }); } }); if (bron) { setTimeout(function() { var bronDiv = document.createElement("div"); bronDiv.className = "sm-bron"; var bronTekst = bron.replace(/^https?:\/\//, ""); bronDiv.innerHTML = 'Bron: ' + bronTekst + ''; div.appendChild(bronDiv); }, vertraging + 50); } if (metKeuzes) { setTimeout(function() { toonKeuzeOpties(); }, vertraging + 200); } } function zetStuurKnopStatus(disabled) { var knop = document.getElementById("sm-chat-send"); var input = document.getElementById("sm-chat-input"); if (knop) knop.disabled = disabled; if (input) input.disabled = disabled; } // ── Product kaarten + lijstje UI ─────────────────────────────────────────── var LIJSTJES_APP_URL = "https://eenlijstje.nl"; var SUPABASE_STORAGE_KEY = "sb-guwfglbunzgytwwtxpke-auth-token"; function _getSupabaseToken() { try { var raw = localStorage.getItem(SUPABASE_STORAGE_KEY); if (!raw) return null; var parsed = JSON.parse(raw); return (parsed && parsed.access_token) ? parsed.access_token : null; } catch(e) { return null; } } function toonProductKaarten(producten, container) { if (!producten || producten.length === 0) return; var geselecteerd = []; var wrap = document.createElement("div"); wrap.className = "sm-producten"; producten.forEach(function(product, idx) { var kaart = document.createElement("div"); kaart.className = "sm-product-kaart"; kaart.setAttribute("data-idx", idx); var check = document.createElement("div"); check.className = "sm-product-check"; check.innerHTML = "✓"; check.style.visibility = "hidden"; var afbEl = ""; if (product.afbeelding_url || product.afbeelding) { var img = document.createElement("img"); img.className = "sm-product-afb"; img.src = product.afbeelding_url || product.afbeelding; img.alt = ""; img.onerror = function() { this.style.display = "none"; }; kaart.appendChild(check); kaart.appendChild(img); } else { kaart.appendChild(check); } var info = document.createElement("div"); info.className = "sm-product-info"; var naam = document.createElement("span"); naam.className = "sm-product-naam"; naam.textContent = product.titel || product.naam || ""; info.appendChild(naam); var meta = document.createElement("span"); meta.className = "sm-product-meta"; var prijsTekst = product.prijs ? "€" + Number(product.prijs).toFixed(2) : ""; var winkelTekst = product.winkel || ""; meta.textContent = [prijsTekst, winkelTekst].filter(Boolean).join(" · "); info.appendChild(meta); var url = product.affiliate_url || product.product_url || null; if (url) { var link = document.createElement("a"); link.className = "sm-product-link"; link.href = url; link.target = "_blank"; link.rel = "noopener"; link.textContent = "Bekijk product →"; link.addEventListener("click", function(e) { e.stopPropagation(); }); info.appendChild(link); } kaart.appendChild(info); kaart.addEventListener("click", function() { var i = geselecteerd.indexOf(idx); if (i === -1) { geselecteerd.push(idx); kaart.classList.add("geselecteerd"); check.style.visibility = "visible"; } else { geselecteerd.splice(i, 1); kaart.classList.remove("geselecteerd"); check.style.visibility = "hidden"; } _updateLijstjeActies(); }); wrap.appendChild(kaart); }); // Actie-balk (verborgen totdat er iets geselecteerd is) var actiesWrap = document.createElement("div"); actiesWrap.style.display = "none"; var actiesRij = document.createElement("div"); actiesRij.className = "sm-lijstje-acties"; var nieuwBtn = document.createElement("button"); nieuwBtn.className = "sm-lijstje-btn"; nieuwBtn.textContent = "Nieuw lijstje"; actiesRij.appendChild(nieuwBtn); var bestaandSelect = document.createElement("select"); bestaandSelect.className = "sm-lijstje-select"; bestaandSelect.innerHTML = ''; actiesRij.appendChild(bestaandSelect); var toevoegenBtn = document.createElement("button"); toevoegenBtn.className = "sm-lijstje-btn"; toevoegenBtn.textContent = "Toevoegen"; toevoegenBtn.disabled = true; actiesRij.appendChild(toevoegenBtn); actiesWrap.appendChild(actiesRij); var nieuwForm = document.createElement("div"); nieuwForm.className = "sm-nieuw-lijstje-form"; nieuwForm.style.display = "none"; var nieuwInput = document.createElement("input"); nieuwInput.className = "sm-nieuw-lijstje-input"; nieuwInput.type = "text"; nieuwInput.placeholder = "Naam van je lijstje"; var nieuwMaakBtn = document.createElement("button"); nieuwMaakBtn.className = "sm-lijstje-btn"; nieuwMaakBtn.textContent = "Aanmaken"; nieuwForm.appendChild(nieuwInput); nieuwForm.appendChild(nieuwMaakBtn); actiesWrap.appendChild(nieuwForm); var statusEl = document.createElement("div"); actiesWrap.appendChild(statusEl); container.appendChild(wrap); container.appendChild(actiesWrap); // Laad bestaande lijstjes als ingelogd var token = _getSupabaseToken(); if (token) { fetch(LIJSTJES_APP_URL + "/api/extension/lists", { headers: { "Authorization": "Bearer " + token } }).then(function(r) { return r.json(); }).then(function(data) { if (data.lists && data.lists.length > 0) { data.lists.forEach(function(lst) { var opt = document.createElement("option"); opt.value = lst.slug; opt.textContent = lst.name; bestaandSelect.appendChild(opt); }); } }).catch(function() {}); } bestaandSelect.addEventListener("change", function() { toevoegenBtn.disabled = !bestaandSelect.value; }); nieuwBtn.addEventListener("click", function() { nieuwForm.style.display = nieuwForm.style.display === "none" ? "flex" : "none"; }); function _getGeselecteerdeProducten() { return geselecteerd.map(function(idx) { return producten[idx]; }); } function _voegToeAanSlug(slug, callback) { var tok = _getSupabaseToken(); if (!tok) { callback(null, "Je bent niet ingelogd op eenlijstje.nl"); return; } var prods = _getGeselecteerdeProducten(); var promises = prods.map(function(p) { return fetch(LIJSTJES_APP_URL + "/api/extension/add-item", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": "Bearer " + tok }, body: JSON.stringify({ listSlug: slug, name: p.titel || p.naam || "", url: p.affiliate_url || p.product_url || null, price: p.prijs || null, image: p.afbeelding_url || p.afbeelding || null }) }).then(function(r) { return r.json(); }); }); Promise.all(promises).then(function() { callback(slug, null); }).catch(function(e) { callback(null, "Toevoegen mislukt"); }); } nieuwMaakBtn.addEventListener("click", function() { var naam = nieuwInput.value.trim(); if (!naam) return; var tok = _getSupabaseToken(); if (!tok) { statusEl.className = "sm-lijstje-fout"; statusEl.textContent = "Log in op eenlijstje.nl om een lijstje te maken."; return; } nieuwMaakBtn.disabled = true; nieuwMaakBtn.textContent = "..."; fetch(LIJSTJES_APP_URL + "/api/extension/create-list", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": "Bearer " + tok }, body: JSON.stringify({ name: naam }) }).then(function(r) { return r.json(); }).then(function(data) { if (data.error) { throw new Error(data.error); } return new Promise(function(resolve) { _voegToeAanSlug(data.slug, function(slug, err) { resolve({ slug: slug, err: err }); }); }); }).then(function(res) { if (res.err) { throw new Error(res.err); } statusEl.className = "sm-lijstje-status"; statusEl.innerHTML = "Lijstje aangemaakt! Bekijk lijstje →"; nieuwForm.style.display = "none"; actiesRij.style.display = "none"; }).catch(function(e) { statusEl.className = "sm-lijstje-fout"; statusEl.textContent = e.message || "Er ging iets mis."; }).finally(function() { nieuwMaakBtn.disabled = false; nieuwMaakBtn.textContent = "Aanmaken"; }); }); toevoegenBtn.addEventListener("click", function() { var slug = bestaandSelect.value; if (!slug) return; toevoegenBtn.disabled = true; toevoegenBtn.textContent = "..."; _voegToeAanSlug(slug, function(s, err) { toevoegenBtn.disabled = false; toevoegenBtn.textContent = "Toevoegen"; if (err) { statusEl.className = "sm-lijstje-fout"; statusEl.textContent = err; } else { statusEl.className = "sm-lijstje-status"; statusEl.innerHTML = "Toegevoegd! Bekijk lijstje →"; actiesRij.style.display = "none"; nieuwForm.style.display = "none"; } }); }); function _updateLijstjeActies() { if (geselecteerd.length > 0) { actiesWrap.style.display = "block"; } else { actiesWrap.style.display = "none"; nieuwForm.style.display = "none"; } } // Scroll kaarten in beeld setTimeout(function() { wrap.scrollIntoView({ block: "end", behavior: "smooth" }); }, 100); } // ── Begroeting op basis van bezochte pagina's + returning visitor ────────── function _bepaalBegroeting() { var seg = bezochteSegmenten; var huidig = window.location.pathname.toLowerCase(); if (eerderGeweest) { // Sessieherstel: onderwerp-begroeting heeft prioriteit var bekendOnderwerp = _leesOnderwerp(); if (bekendOnderwerp) { var onderwerpBegroetingen = { "google-ads": "Je vroeg vorige keer naar Google Ads. Ben je er al uit, of kan Dash je nog ergens mee helpen?", "seo": "Je was vorige keer bezig met vragen over SEO. Nog steeds aan het oriënteren?", "automaat": "Je vroeg vorige keer naar de Automaat. Wil je weten wat dat voor jouw bedrijf oplevert?", "prijs": "Je keek vorige keer naar de pakketten en kosten. Heb je nog vragen over wat past bij jou?", "check": "Je vroeg vorige keer naar de gratis check. Wil je die nu inplannen?" }; if (onderwerpBegroetingen[bekendOnderwerp]) return onderwerpBegroetingen[bekendOnderwerp]; } // Returning visitor: persoonlijker, geen intro herhalen if (seg["prijs"] || huidig.indexOf("pakket") !== -1 || huidig.indexOf("prijs") !== -1) { return "Welkom terug! Je bent al eerder langs geweest. De founding prijs van 1.500 euro is er nog. Wil je weten wat er nog openstaat?"; } if (seg["seo"] && seg["ads"]) { return "Welkom terug! Je bent al eerder langs geweest. Nog aan het twijfelen tussen SEO en Google Ads? Ik help je kiezen."; } if (seg["seo"]) { return "Welkom terug! Nog bezig met SEO onderzoeken? Stel je vraag, ik geef je een eerlijk beeld van wat het voor jou oplevert."; } if (seg["ads"]) { return "Welkom terug! Nog bezig met Google Ads onderzoeken? Vertel me wat je situatie is, dan geef ik je een concreet antwoord."; } if (seg["automaat"]) { return "Welkom terug! Nog nadenken over automatisering? Vertel me welk proces je wil aanpakken, dan kijk ik wat realistisch is."; } return "Welkom terug! Fijn dat je er weer bent. Waarmee kan ik je verder helpen?"; } // Eerste bezoek if (seg["prijs"] || huidig.indexOf("pakket") !== -1 || huidig.indexOf("prijs") !== -1) { return "Hey! Ik zie dat je naar de pakketten kijkt. De founding prijs van 1.500 euro is nog beschikbaar. Zal ik uitleggen wat je daarvoor krijgt?"; } if (seg["seo"] && seg["ads"]) { return "Hey! Je hebt zowel onze SEO- als Ads-diensten bekeken. Wil je weten welke het beste bij jouw situatie past?"; } if (seg["seo"]) { return "Hey! Ben je benieuwd hoe snel je vindbaar kunt zijn in Google en in ChatGPT? Ik leg het je graag uit."; } if (seg["ads"]) { return "Hey! Meer aanvragen uit hetzelfde advertentiebudget halen. Dat is precies wat we doen. Zal ik je vertellen hoe?"; } if (seg["automaat"]) { return "Hey! Benieuwd hoeveel uur je per week kunt besparen met automatisering? Ik geef je snel een eerlijk beeld."; } return BEDRIJF_NAAM ? "Hey! Ik ben " + BOT_NAAM + " van " + BEDRIJF_NAAM + ". Waarmee kan ik je helpen?" : "Hey! Ik ben " + BOT_NAAM + ". Waarmee kan ik je helpen?"; } // ── Beschikbaarheid ──────────────────────────────────────────────────────── function _isOffline() { return !IS_BESCHIKBAAR; } function _toonOfflineFormulier() { var berichten = document.getElementById("sm-chat-messages"); berichten.innerHTML = ""; var form = document.createElement("div"); form.className = "sm-offline-form"; form.innerHTML = '

We zijn even niet beschikbaar

' + '

Laat je naam en e-mailadres achter. We nemen zo snel mogelijk contact op.

' + '
' + '' + '' + '
' + '
' + '' + '' + '
' + '
' + '' + '' + '
' + '' + ''; berichten.appendChild(form); document.getElementById("sm-offline-submit").addEventListener("click", function() { var naam = (document.getElementById("sm-offline-naam").value || "").trim(); var email = (document.getElementById("sm-offline-email").value || "").trim(); var bericht = (document.getElementById("sm-offline-bericht").value || "").trim(); var foutEl = document.getElementById("sm-offline-fout"); if (!naam || !email) { foutEl.textContent = "Vul naam en e-mailadres in."; foutEl.style.display = "block"; return; } if (!/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(email)) { foutEl.textContent = "Vul een geldig e-mailadres in."; foutEl.style.display = "block"; return; } foutEl.style.display = "none"; fetch(BACKEND_URL + "/offline-lead", { method: "POST", headers: { "Content-Type": "application/json", "X-Secret-Token": SECRET_TOKEN }, body: JSON.stringify({ naam: naam, email: email, bericht: bericht || undefined, klant_id: KLANT_ID }) }).then(function(res) { if (res.ok) { berichten.innerHTML = '
Bedankt! We nemen zo snel mogelijk contact op.
'; } else { foutEl.textContent = "Er ging iets mis. Probeer het opnieuw."; foutEl.style.display = "block"; } }).catch(function() { foutEl.textContent = "Er ging iets mis. Probeer het opnieuw."; foutEl.style.display = "block"; }); }); } // ── Email gate ───────────────────────────────────────────────────────────── function _toonEmailGate(onKlaar) { var berichten = document.getElementById("sm-chat-messages"); berichten.innerHTML = ""; var avgExtra = PRIVACY_URL ? ' Meer info: privacybeleid.' : ""; var skipKnop = EMAIL_GATE_VERPLICHT ? "" : ''; var form = document.createElement("div"); form.className = "sm-emailgate-form"; form.innerHTML = '

Stel je voor

' + '
' + '' + '' + '
' + '

Je e-mailadres wordt alleen gebruikt om contact met je op te nemen.' + avgExtra + '

' + '' + '
' + '' + skipKnop + '
'; berichten.appendChild(form); document.getElementById("sm-gate-submit").addEventListener("click", function() { var email = (document.getElementById("sm-gate-email").value || "").trim(); var foutEl = document.getElementById("sm-gate-fout"); if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(email)) { foutEl.textContent = "Vul een geldig e-mailadres in."; foutEl.style.display = "block"; return; } if (_heeftFunctioneleConsent()) { try { localStorage.setItem(SM_EMAIL_KEY, email); } catch(e) {} } _gatewayEmail = email; _gtmEvent("sm_email_gate_ingevuld", {}); berichten.innerHTML = ""; if (onKlaar) onKlaar(); }); if (!EMAIL_GATE_VERPLICHT) { document.getElementById("sm-emailgate-skip").addEventListener("click", function() { berichten.innerHTML = ""; if (onKlaar) onKlaar(); }); } } // ── Gesprek resetten ─────────────────────────────────────────────────────── function resetGesprek() { sessionId = null; openerGetoond = false; _verwijderKeuzes(); var berichten = document.getElementById("sm-chat-messages"); berichten.innerHTML = ""; setTimeout(function() { toonBotAntwoordAnimated(_bepaalBegroeting(), true); }, 200); } // ── Chat openen/sluiten ──────────────────────────────────────────────────── function openChat(triggerOpener) { chatOpen = true; venster.style.display = "flex"; _markeerBezoek(); // Offline heeft hoogste prioriteit if (_isOffline() && BUITEN_UREN_MODUS === "offline_bericht") { _toonOfflineFormulier(); return; } // Email gate (alleen als e-mail nog niet bekend) var bestaandEmail = _leesEmailGate(); if (EMAIL_GATE && !bestaandEmail) { _toonEmailGate(function() { // Na gate: begroeting tonen setTimeout(function() { toonBotAntwoordAnimated(_bepaalBegroeting(), true); }, 200); }); return; } // Normale flow document.getElementById("sm-chat-input").focus(); _gtmEvent("sm_widget_geopend", { opener_type: triggerOpener ? "manual" : "proactief" }); if (triggerOpener && !openerGetoond) { openerGetoond = true; var berichten = document.getElementById("sm-chat-messages"); if (berichten.children.length === 0) { setTimeout(function() { toonBotAntwoordAnimated(_bepaalBegroeting(), true); }, 350); } } } function sluitChat() { chatOpen = false; venster.style.display = "none"; } function toggleChat() { if (chatOpen) { sluitChat(); } else { openChat(true); } } // ── Netwerk ──────────────────────────────────────────────────────────────── function stuurBericht(tekst) { if (bezig || !tekst) return; bezig = true; _berichtTeller++; _verwijderKeuzes(); zetStuurKnopStatus(true); voegUserBerichtToe(tekst); document.getElementById("sm-chat-input").value = ""; _gtmEvent("sm_bericht_verstuurd", { bericht_nummer: _berichtTeller }); var typeerWrap = toonTypeerAnimatie(); var honeypot = (document.getElementById("sm-honeypot") || {}).value || ""; var startTijd = Date.now(); var MIN_WACHT = 700; var segmenten = Object.keys(bezochteSegmenten).join(","); var requestBody = { klant_id: KLANT_ID, session_id: sessionId, bericht: tekst, pagina: window.location.pathname, pagina_context: segmenten, honeypot: honeypot }; // E-mail meesturen bij eerste bericht na email gate if (_berichtTeller === 1 && _gatewayEmail) { requestBody.email = _gatewayEmail; } fetch(BACKEND_URL + "/chat", { method: "POST", headers: { "Content-Type": "application/json", "X-Secret-Token": SECRET_TOKEN }, body: JSON.stringify(requestBody) }).then(function(res) { var verstreken = Date.now() - startTijd; var restant = Math.max(0, MIN_WACHT - verstreken); return new Promise(function(resolve) { setTimeout(function() { resolve(res); }, restant); }); }).then(function(res) { verwijderTypeerAnimatie(typeerWrap); if (!res.ok) { toonBotAntwoordAnimated("Er ging iets mis. Probeer het opnieuw.", false); return; } return res.json().then(function(data) { sessionId = data.session_id; if (data.onderwerp) { _slaOnderwerpOp(data.onderwerp); } if (data.lead_aangemaakt) { _gtmEvent("sm_lead_aangemaakt", { pagina: location.pathname }); } toonBotAntwoordAnimated(data.antwoord, false, data.bron || null); if (data.producten && data.producten.length > 0) { var berWrap = document.createElement("div"); berWrap.style.paddingLeft = "36px"; document.getElementById("sm-chat-messages").appendChild(berWrap); toonProductKaarten(data.producten, berWrap); } }); }).catch(function() { verwijderTypeerAnimatie(typeerWrap); toonBotAntwoordAnimated("Er ging iets mis. Probeer het opnieuw.", false); }).finally(function() { bezig = false; zetStuurKnopStatus(false); }); } // ── Events ───────────────────────────────────────────────────────────────── btn.addEventListener("click", toggleChat); document.getElementById("sm-close-btn").addEventListener("click", sluitChat); document.getElementById("sm-reset-btn").addEventListener("click", resetGesprek); document.getElementById("sm-chat-send").addEventListener("click", function() { var tekst = document.getElementById("sm-chat-input").value.trim(); if (tekst) stuurBericht(tekst); }); document.getElementById("sm-chat-input").addEventListener("keydown", function(e) { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); var tekst = e.target.value.trim(); if (tekst) stuurBericht(tekst); } }); // ── Proactieve begroeting na X seconden ──────────────────────────────────── function proactieveBegroeting() { if (proactief_getoond || chatOpen) return; proactief_getoond = true; openerGetoond = true; openChat(false); setTimeout(function() { toonBotAntwoordAnimated(_bepaalBegroeting(), true); _resetInactiviteitTimer(); }, 350); } setTimeout(proactieveBegroeting, DELAY_SECONDEN * 1000); // ── 3-minuten inactiviteit trigger ──────────────────────────────────────── // Als de chat open is maar de bezoeker 3 min niets heeft gedaan, stel een LSD-vraag var inactiviteitTimer = null; var INACTIVITEIT_MS = 3 * 60 * 1000; function _lsdVraagOpBasisVanPaginas() { var seg = bezochteSegmenten; var huidig = window.location.pathname.toLowerCase(); if (seg["prijs"] || huidig.indexOf("pakket") !== -1 || huidig.indexOf("prijs") !== -1) { return "Je bekijkt de pakketten. Heb je een specifiek budget in gedachten, of wil je gewoon weten wat realistisch is voor jouw situatie?"; } if (seg["seo"] && seg["ads"]) { return "Je hebt zowel SEO als Google Ads bekeken. Wil je weten welke van de twee het snelste resultaat geeft voor jouw bedrijf?"; } if (seg["seo"]) { return "Je bent al een tijdje aan het lezen over SEO. Wat is het grootste obstakel waarvoor je nu een oplossing zoekt?"; } if (seg["ads"]) { return "Je hebt de Google Ads-dienst bekeken. Heb je al een lopende campagne, of begin je vanaf nul?"; } if (seg["automaat"]) { return "Automatisering klinkt interessant. Welk proces kost jou nu de meeste tijd in de week?"; } return "Je bent al een tijdje aan het rondkijken. Zal ik je helpen de juiste keuze te maken? Wat is je situatie?"; } function _resetInactiviteitTimer() { if (inactiviteitTimer) clearTimeout(inactiviteitTimer); if (!chatOpen) return; inactiviteitTimer = setTimeout(function() { var berichten = document.getElementById("sm-chat-messages"); if (!chatOpen || !berichten || bezig) return; toonBotAntwoordAnimated(_lsdVraagOpBasisVanPaginas(), false); }, INACTIVITEIT_MS); } // Reset timer bij elke interactie document.getElementById("sm-chat-input").addEventListener("input", _resetInactiviteitTimer); document.getElementById("sm-chat-send").addEventListener("click", function() { _resetInactiviteitTimer(); }); btn.addEventListener("click", function() { if (chatOpen) setTimeout(_resetInactiviteitTimer, 100); }); // Activeer dataLayer-listener voor real-time consent (nieuwe bezoekers) _activeerConsentListener(); // ── SPA-navigatie tracking (Next.js history API) ─────────────────────────── (function() { var origPush = history.pushState; var origReplace = history.replaceState; function _onNav() { _registreerPagina(window.location.pathname); } history.pushState = function() { origPush.apply(this, arguments); _onNav(); }; history.replaceState = function() { origReplace.apply(this, arguments); _onNav(); }; window.addEventListener("popstate", _onNav); })(); })();