DTS-Timber_FC.png

DTS TIMBER
DORPEL
CONFIGURATOR

kg

Virgin grondstof besparing


Basisgegevens


Basismaten dorpel


Loodsponning


Basismaten neuten


Kalksponningen


Neutbreedtes


Voorsponningen


Boorgatposities - X


Neutbreedtes


Voorsponningen


Boorgatposities - X


Neutbreedtes


Voorsponningen


Boorgatposities - X


Neutbreedtes


Voorsponningen


Boorgatposities - X


Neutbreedtes


Voorsponningen


Boorgatposities - X


Dagmaten


Vakvullingen


Vakvullingen


Vakvullingen


Vakvullingen


Vakvullingen


Vakvullingen


Vakvullingen


Vakvullingen


Vakvullingen


Vakvullingen


Basismaten HS neuten


Kadersponning


Wisselsponning


Draaikiepsponning


Draaikiep nokafmeting


Sluitpot


Toevoegen aan Mijn Bibliotheek

⚠️ Kleur en Brandwerendheid worden niet opgeslagen

//// DELAY CHECKER //// (function () { if (window.__scriptProfilerAuto) return; const AUTO_STOP_MS = 2200; // tijd zonder nieuwe activiteit = meting klaar const MAX_SESSION_MS = 8000; // harde limiet const TOP_N = 15; const state = { activeSession: null, sessions: [], byScript: new Map(), callStack: [], originals: {}, observerMeta: new WeakMap(), timerMeta: new Map(), listenerMap: new WeakMap(), pendingAutoStop: null, pendingHardStop: null, monitorInstalled: false }; function now() { return performance.now(); } function extractScriptNameFromText(text) { if (!text) return "ONBEKEND SCRIPT"; const m = text.match(/\/\/\/\/\s*([^\n\r]+?)\s*\/\/\/\//); return m ? m[1].trim() : "ONBEKEND SCRIPT"; } function currentScriptName() { const cs = document.currentScript; if (!cs) return state.callStack[state.callStack.length - 1] || "ONBEKEND SCRIPT"; return extractScriptNameFromText(cs.textContent || ""); } function ensureBucket(scriptName) { if (!state.byScript.has(scriptName)) { state.byScript.set(scriptName, { script: scriptName, totalMs: 0, events: 0, observers: 0, intervals: 0, timeouts: 0, rafs: 0, querySelectorAll: 0, querySelector: 0, setAttribute: 0, classListOps: 0, styleOps: 0, appendOps: 0, removeOps: 0 }); } return state.byScript.get(scriptName); } function markActivity() { if (!state.activeSession) return; if (state.pendingAutoStop) clearTimeout(state.pendingAutoStop); state.pendingAutoStop = setTimeout(() => { stopSession("idle"); }, AUTO_STOP_MS); } function addTime(scriptName, ms, kind) { if (!state.activeSession) return; const bucket = ensureBucket(scriptName); bucket.totalMs += ms; if (kind === "event") bucket.events += 1; if (kind === "observer") bucket.observers += 1; if (kind === "interval") bucket.intervals += 1; if (kind === "timeout") bucket.timeouts += 1; if (kind === "raf") bucket.rafs += 1; markActivity(); } function withScriptContext(scriptName, kind, fn, args, thisArg) { const start = now(); state.callStack.push(scriptName); try { return fn.apply(thisArg, args || []); } finally { state.callStack.pop(); addTime(scriptName, now() - start, kind); } } function wrapListener(listener, scriptName, eventType) { if (!listener) return listener; if (typeof listener === "function") { const wrapped = function () { return withScriptContext(scriptName, "event", listener, arguments, this); }; state.listenerMap.set(listener, wrapped); return wrapped; } if (typeof listener.handleEvent === "function") { const wrappedObj = { handleEvent: function () { return withScriptContext(scriptName, "event", listener.handleEvent, arguments, listener); } }; state.listenerMap.set(listener, wrappedObj); return wrappedObj; } return listener; } function patchAddEventListener() { const proto = EventTarget.prototype; state.originals.addEventListener = proto.addEventListener; state.originals.removeEventListener = proto.removeEventListener; proto.addEventListener = function (type, listener, options) { const scriptName = currentScriptName(); const wrapped = wrapListener(listener, scriptName, type); return state.originals.addEventListener.call(this, type, wrapped, options); }; proto.removeEventListener = function (type, listener, options) { const wrapped = state.listenerMap.get(listener) || listener; return state.originals.removeEventListener.call(this, type, wrapped, options); }; } function patchTimers() { state.originals.setTimeout = window.setTimeout; state.originals.setInterval = window.setInterval; state.originals.requestAnimationFrame = window.requestAnimationFrame; window.setTimeout = function (fn, delay) { const scriptName = currentScriptName(); const wrapped = typeof fn === "function" ? function () { return withScriptContext(scriptName, "timeout", fn, arguments, this); } : fn; return state.originals.setTimeout.call(window, wrapped, delay); }; window.setInterval = function (fn, delay) { const scriptName = currentScriptName(); const wrapped = typeof fn === "function" ? function () { return withScriptContext(scriptName, "interval", fn, arguments, this); } : fn; return state.originals.setInterval.call(window, wrapped, delay); }; window.requestAnimationFrame = function (fn) { const scriptName = currentScriptName(); const wrapped = function () { return withScriptContext(scriptName, "raf", fn, arguments, this); }; return state.originals.requestAnimationFrame.call(window, wrapped); }; } function patchMutationObserver() { state.originals.MutationObserver = window.MutationObserver; window.MutationObserver = function (callback) { const scriptName = currentScriptName(); const wrapped = function () { return withScriptContext(scriptName, "observer", callback, arguments, this); }; return new state.originals.MutationObserver(wrapped); }; } function patchDomHeavyCalls() { const qsaE = Element.prototype.querySelectorAll; const qsE = Element.prototype.querySelector; const qsaD = Document.prototype.querySelectorAll; const qsD = Document.prototype.querySelector; const setAttr = Element.prototype.setAttribute; const appendChild = Node.prototype.appendChild; const insertBefore = Node.prototype.insertBefore; const removeChild = Node.prototype.removeChild; state.originals.elementQuerySelectorAll = qsaE; state.originals.elementQuerySelector = qsE; state.originals.documentQuerySelectorAll = qsaD; state.originals.documentQuerySelector = qsD; state.originals.setAttribute = setAttr; state.originals.appendChild = appendChild; state.originals.insertBefore = insertBefore; state.originals.removeChild = removeChild; function mark(op) { if (!state.activeSession) return; const scriptName = state.callStack[state.callStack.length - 1]; if (!scriptName) return; const bucket = ensureBucket(scriptName); bucket[op] = (bucket[op] || 0) + 1; markActivity(); } Element.prototype.querySelectorAll = function (sel) { mark("querySelectorAll"); return qsaE.call(this, sel); }; Document.prototype.querySelectorAll = function (sel) { mark("querySelectorAll"); return qsaD.call(this, sel); }; Element.prototype.querySelector = function (sel) { mark("querySelector"); return qsE.call(this, sel); }; Document.prototype.querySelector = function (sel) { mark("querySelector"); return qsD.call(this, sel); }; Element.prototype.setAttribute = function (name, value) { if ( name === "display" || name === "transform" || name === "d" || name === "points" || name === "x" || name === "width" || name === "height" ) { mark("setAttribute"); } return setAttr.call(this, name, value); }; Node.prototype.appendChild = function (node) { mark("appendOps"); return appendChild.call(this, node); }; Node.prototype.insertBefore = function (node, ref) { mark("appendOps"); return insertBefore.call(this, node, ref); }; Node.prototype.removeChild = function (node) { mark("removeOps"); return removeChild.call(this, node); }; const classAdd = DOMTokenList.prototype.add; const classRemove = DOMTokenList.prototype.remove; const classToggle = DOMTokenList.prototype.toggle; state.originals.classAdd = classAdd; state.originals.classRemove = classRemove; state.originals.classToggle = classToggle; DOMTokenList.prototype.add = function () { if (state.activeSession) { const scriptName = state.callStack[state.callStack.length - 1]; if (scriptName) ensureBucket(scriptName).classListOps += 1; markActivity(); } return classAdd.apply(this, arguments); }; DOMTokenList.prototype.remove = function () { if (state.activeSession) { const scriptName = state.callStack[state.callStack.length - 1]; if (scriptName) ensureBucket(scriptName).classListOps += 1; markActivity(); } return classRemove.apply(this, arguments); }; DOMTokenList.prototype.toggle = function () { if (state.activeSession) { const scriptName = state.callStack[state.callStack.length - 1]; if (scriptName) ensureBucket(scriptName).classListOps += 1; markActivity(); } return classToggle.apply(this, arguments); }; const styleSetProperty = CSSStyleDeclaration.prototype.setProperty; state.originals.styleSetProperty = styleSetProperty; CSSStyleDeclaration.prototype.setProperty = function () { if (state.activeSession) { const scriptName = state.callStack[state.callStack.length - 1]; if (scriptName) ensureBucket(scriptName).styleOps += 1; markActivity(); } return styleSetProperty.apply(this, arguments); }; } function startSession(triggerLabel) { if (state.activeSession) stopSession("restart"); state.byScript.clear(); state.activeSession = { label: triggerLabel, startedAt: now() }; if (state.pendingAutoStop) clearTimeout(state.pendingAutoStop); if (state.pendingHardStop) clearTimeout(state.pendingHardStop); state.pendingAutoStop = setTimeout(() => { stopSession("idle"); }, AUTO_STOP_MS); state.pendingHardStop = setTimeout(() => { stopSession("max"); }, MAX_SESSION_MS); console.log(`[AutoProfiler] gestart door: ${triggerLabel}`); } function stopSession(reason) { if (!state.activeSession) return []; if (state.pendingAutoStop) clearTimeout(state.pendingAutoStop); if (state.pendingHardStop) clearTimeout(state.pendingHardStop); const elapsed = now() - state.activeSession.startedAt; const rows = Array.from(state.byScript.values()) .sort((a, b) => b.totalMs - a.totalMs) .map(r => ({ script: r.script, totalMs: Number(r.totalMs.toFixed(1)), events: r.events, observers: r.observers, intervals: r.intervals, timeouts: r.timeouts, rafs: r.rafs, querySelectorAll: r.querySelectorAll, querySelector: r.querySelector, setAttribute: r.setAttribute, classListOps: r.classListOps, styleOps: r.styleOps, appendOps: r.appendOps, removeOps: r.removeOps })); const topRows = rows.slice(0, TOP_N); console.groupCollapsed( `%c[AutoProfiler] ${state.activeSession.label} klaar`, "color:#0a7;font-weight:bold;" ); console.log(`reden: ${reason}`); console.log(`duur: ${elapsed.toFixed(1)} ms`); console.table(topRows); console.log("volledige lijst:", rows); console.groupEnd(); state.sessions.push({ label: state.activeSession.label, reason, elapsedMs: elapsed, rows }); state.activeSession = null; return rows; } function isTargetField(el) { if (!el || !el.name) return false; return ( el.name === "form_fields[dorpeltype]" || el.name === "form_fields[vakken]" ); } function installAutoMonitor() { if (state.monitorInstalled) return; state.monitorInstalled = true; document.addEventListener("change", function (e) { const t = e.target; if (!isTargetField(t)) return; const value = t.getAttribute("data-original-value") || t.value || "(leeg)"; startSession(`${t.name} = ${value}`); }, true); document.addEventListener("input", function (e) { const t = e.target; if (!isTargetField(t)) return; const value = t.getAttribute("data-original-value") || t.value || "(leeg)"; startSession(`${t.name} = ${value}`); }, true); } patchAddEventListener(); patchTimers(); patchMutationObserver(); patchDomHeavyCalls(); installAutoMonitor(); window.__scriptProfilerAuto = { sessions: state.sessions, stopNow() { return stopSession("manual"); }, last() { return state.sessions[state.sessions.length - 1] || null; }, all() { return state.sessions; } }; console.log("[AutoProfiler] actief. Wijzig dorpeltype of vakken; output verschijnt automatisch in de console."); })();
//// DELAY CHECKER //// (function () { if (window.__scriptProfilerAuto) return; const AUTO_STOP_MS = 2200; // tijd zonder nieuwe activiteit = meting klaar const MAX_SESSION_MS = 8000; // harde limiet const TOP_N = 15; const state = { activeSession: null, sessions: [], byScript: new Map(), callStack: [], originals: {}, observerMeta: new WeakMap(), timerMeta: new Map(), listenerMap: new WeakMap(), pendingAutoStop: null, pendingHardStop: null, monitorInstalled: false }; function now() { return performance.now(); } function extractScriptNameFromText(text) { if (!text) return "ONBEKEND SCRIPT"; const m = text.match(/\/\/\/\/\s*([^\n\r]+?)\s*\/\/\/\//); return m ? m[1].trim() : "ONBEKEND SCRIPT"; } function currentScriptName() { const cs = document.currentScript; if (!cs) return state.callStack[state.callStack.length - 1] || "ONBEKEND SCRIPT"; return extractScriptNameFromText(cs.textContent || ""); } function ensureBucket(scriptName) { if (!state.byScript.has(scriptName)) { state.byScript.set(scriptName, { script: scriptName, totalMs: 0, events: 0, observers: 0, intervals: 0, timeouts: 0, rafs: 0, querySelectorAll: 0, querySelector: 0, setAttribute: 0, classListOps: 0, styleOps: 0, appendOps: 0, removeOps: 0 }); } return state.byScript.get(scriptName); } function markActivity() { if (!state.activeSession) return; if (state.pendingAutoStop) clearTimeout(state.pendingAutoStop); state.pendingAutoStop = setTimeout(() => { stopSession("idle"); }, AUTO_STOP_MS); } function addTime(scriptName, ms, kind) { if (!state.activeSession) return; const bucket = ensureBucket(scriptName); bucket.totalMs += ms; if (kind === "event") bucket.events += 1; if (kind === "observer") bucket.observers += 1; if (kind === "interval") bucket.intervals += 1; if (kind === "timeout") bucket.timeouts += 1; if (kind === "raf") bucket.rafs += 1; markActivity(); } function withScriptContext(scriptName, kind, fn, args, thisArg) { const start = now(); state.callStack.push(scriptName); try { return fn.apply(thisArg, args || []); } finally { state.callStack.pop(); addTime(scriptName, now() - start, kind); } } function wrapListener(listener, scriptName, eventType) { if (!listener) return listener; if (typeof listener === "function") { const wrapped = function () { return withScriptContext(scriptName, "event", listener, arguments, this); }; state.listenerMap.set(listener, wrapped); return wrapped; } if (typeof listener.handleEvent === "function") { const wrappedObj = { handleEvent: function () { return withScriptContext(scriptName, "event", listener.handleEvent, arguments, listener); } }; state.listenerMap.set(listener, wrappedObj); return wrappedObj; } return listener; } function patchAddEventListener() { const proto = EventTarget.prototype; state.originals.addEventListener = proto.addEventListener; state.originals.removeEventListener = proto.removeEventListener; proto.addEventListener = function (type, listener, options) { const scriptName = currentScriptName(); const wrapped = wrapListener(listener, scriptName, type); return state.originals.addEventListener.call(this, type, wrapped, options); }; proto.removeEventListener = function (type, listener, options) { const wrapped = state.listenerMap.get(listener) || listener; return state.originals.removeEventListener.call(this, type, wrapped, options); }; } function patchTimers() { state.originals.setTimeout = window.setTimeout; state.originals.setInterval = window.setInterval; state.originals.requestAnimationFrame = window.requestAnimationFrame; window.setTimeout = function (fn, delay) { const scriptName = currentScriptName(); const wrapped = typeof fn === "function" ? function () { return withScriptContext(scriptName, "timeout", fn, arguments, this); } : fn; return state.originals.setTimeout.call(window, wrapped, delay); }; window.setInterval = function (fn, delay) { const scriptName = currentScriptName(); const wrapped = typeof fn === "function" ? function () { return withScriptContext(scriptName, "interval", fn, arguments, this); } : fn; return state.originals.setInterval.call(window, wrapped, delay); }; window.requestAnimationFrame = function (fn) { const scriptName = currentScriptName(); const wrapped = function () { return withScriptContext(scriptName, "raf", fn, arguments, this); }; return state.originals.requestAnimationFrame.call(window, wrapped); }; } function patchMutationObserver() { state.originals.MutationObserver = window.MutationObserver; window.MutationObserver = function (callback) { const scriptName = currentScriptName(); const wrapped = function () { return withScriptContext(scriptName, "observer", callback, arguments, this); }; return new state.originals.MutationObserver(wrapped); }; } function patchDomHeavyCalls() { const qsaE = Element.prototype.querySelectorAll; const qsE = Element.prototype.querySelector; const qsaD = Document.prototype.querySelectorAll; const qsD = Document.prototype.querySelector; const setAttr = Element.prototype.setAttribute; const appendChild = Node.prototype.appendChild; const insertBefore = Node.prototype.insertBefore; const removeChild = Node.prototype.removeChild; state.originals.elementQuerySelectorAll = qsaE; state.originals.elementQuerySelector = qsE; state.originals.documentQuerySelectorAll = qsaD; state.originals.documentQuerySelector = qsD; state.originals.setAttribute = setAttr; state.originals.appendChild = appendChild; state.originals.insertBefore = insertBefore; state.originals.removeChild = removeChild; function mark(op) { if (!state.activeSession) return; const scriptName = state.callStack[state.callStack.length - 1]; if (!scriptName) return; const bucket = ensureBucket(scriptName); bucket[op] = (bucket[op] || 0) + 1; markActivity(); } Element.prototype.querySelectorAll = function (sel) { mark("querySelectorAll"); return qsaE.call(this, sel); }; Document.prototype.querySelectorAll = function (sel) { mark("querySelectorAll"); return qsaD.call(this, sel); }; Element.prototype.querySelector = function (sel) { mark("querySelector"); return qsE.call(this, sel); }; Document.prototype.querySelector = function (sel) { mark("querySelector"); return qsD.call(this, sel); }; Element.prototype.setAttribute = function (name, value) { if ( name === "display" || name === "transform" || name === "d" || name === "points" || name === "x" || name === "width" || name === "height" ) { mark("setAttribute"); } return setAttr.call(this, name, value); }; Node.prototype.appendChild = function (node) { mark("appendOps"); return appendChild.call(this, node); }; Node.prototype.insertBefore = function (node, ref) { mark("appendOps"); return insertBefore.call(this, node, ref); }; Node.prototype.removeChild = function (node) { mark("removeOps"); return removeChild.call(this, node); }; const classAdd = DOMTokenList.prototype.add; const classRemove = DOMTokenList.prototype.remove; const classToggle = DOMTokenList.prototype.toggle; state.originals.classAdd = classAdd; state.originals.classRemove = classRemove; state.originals.classToggle = classToggle; DOMTokenList.prototype.add = function () { if (state.activeSession) { const scriptName = state.callStack[state.callStack.length - 1]; if (scriptName) ensureBucket(scriptName).classListOps += 1; markActivity(); } return classAdd.apply(this, arguments); }; DOMTokenList.prototype.remove = function () { if (state.activeSession) { const scriptName = state.callStack[state.callStack.length - 1]; if (scriptName) ensureBucket(scriptName).classListOps += 1; markActivity(); } return classRemove.apply(this, arguments); }; DOMTokenList.prototype.toggle = function () { if (state.activeSession) { const scriptName = state.callStack[state.callStack.length - 1]; if (scriptName) ensureBucket(scriptName).classListOps += 1; markActivity(); } return classToggle.apply(this, arguments); }; const styleSetProperty = CSSStyleDeclaration.prototype.setProperty; state.originals.styleSetProperty = styleSetProperty; CSSStyleDeclaration.prototype.setProperty = function () { if (state.activeSession) { const scriptName = state.callStack[state.callStack.length - 1]; if (scriptName) ensureBucket(scriptName).styleOps += 1; markActivity(); } return styleSetProperty.apply(this, arguments); }; } function startSession(triggerLabel) { if (state.activeSession) stopSession("restart"); state.byScript.clear(); state.activeSession = { label: triggerLabel, startedAt: now() }; if (state.pendingAutoStop) clearTimeout(state.pendingAutoStop); if (state.pendingHardStop) clearTimeout(state.pendingHardStop); state.pendingAutoStop = setTimeout(() => { stopSession("idle"); }, AUTO_STOP_MS); state.pendingHardStop = setTimeout(() => { stopSession("max"); }, MAX_SESSION_MS); console.log(`[AutoProfiler] gestart door: ${triggerLabel}`); } function stopSession(reason) { if (!state.activeSession) return []; if (state.pendingAutoStop) clearTimeout(state.pendingAutoStop); if (state.pendingHardStop) clearTimeout(state.pendingHardStop); const elapsed = now() - state.activeSession.startedAt; const rows = Array.from(state.byScript.values()) .sort((a, b) => b.totalMs - a.totalMs) .map(r => ({ script: r.script, totalMs: Number(r.totalMs.toFixed(1)), events: r.events, observers: r.observers, intervals: r.intervals, timeouts: r.timeouts, rafs: r.rafs, querySelectorAll: r.querySelectorAll, querySelector: r.querySelector, setAttribute: r.setAttribute, classListOps: r.classListOps, styleOps: r.styleOps, appendOps: r.appendOps, removeOps: r.removeOps })); const topRows = rows.slice(0, TOP_N); console.groupCollapsed( `%c[AutoProfiler] ${state.activeSession.label} klaar`, "color:#0a7;font-weight:bold;" ); console.log(`reden: ${reason}`); console.log(`duur: ${elapsed.toFixed(1)} ms`); console.table(topRows); console.log("volledige lijst:", rows); console.groupEnd(); state.sessions.push({ label: state.activeSession.label, reason, elapsedMs: elapsed, rows }); state.activeSession = null; return rows; } function isTargetField(el) { if (!el || !el.name) return false; return ( el.name === "form_fields[dorpeltype]" || el.name === "form_fields[vakken]" ); } function installAutoMonitor() { if (state.monitorInstalled) return; state.monitorInstalled = true; document.addEventListener("change", function (e) { const t = e.target; if (!isTargetField(t)) return; const value = t.getAttribute("data-original-value") || t.value || "(leeg)"; startSession(`${t.name} = ${value}`); }, true); document.addEventListener("input", function (e) { const t = e.target; if (!isTargetField(t)) return; const value = t.getAttribute("data-original-value") || t.value || "(leeg)"; startSession(`${t.name} = ${value}`); }, true); } patchAddEventListener(); patchTimers(); patchMutationObserver(); patchDomHeavyCalls(); installAutoMonitor(); window.__scriptProfilerAuto = { sessions: state.sessions, stopNow() { return stopSession("manual"); }, last() { return state.sessions[state.sessions.length - 1] || null; }, all() { return state.sessions; } }; console.log("[AutoProfiler] actief. Wijzig dorpeltype of vakken; output verschijnt automatisch in de console."); })();
//// HIDE OVERIGE DORPELTYPE RADIO'S NA SELECTIE //// document.addEventListener("DOMContentLoaded", function () { // ----------------------- // CONFIG // ----------------------- const TOOLTIP_BG = "#0069b4"; const TOOLTIP_TEXT = "Refresh (f5) pagina voor andere keuze."; const radios = Array.from( document.querySelectorAll("input[name='form_fields[dorpeltype]']") ); if (!radios.length) return; // ✅ Zelfde container-aanpak als je andere scripts const container = (radios[0] && (radios[0].closest("main") || radios[0].closest("form"))) || document.querySelector("main") || document.body; const containerStyle = window.getComputedStyle(container); if (containerStyle.position === "static") container.style.position = "relative"; // ----------------------- // SCALE METEN (probe) — zelfde aanpak als NOKKEN_SCHUIF // ----------------------- function measureScale(el) { if (!el) return 1; const probe = document.createElement("div"); probe.style.cssText = "position:absolute; left:0; top:0; width:100px; height:100px; visibility:hidden; pointer-events:none;"; el.appendChild(probe); const rect = probe.getBoundingClientRect(); el.removeChild(probe); const scale = rect.width / 100; return (scale && isFinite(scale)) ? scale : 1; } // ----------------------- // TOOLTIP (VOLGT MUIS) — ZOOM-PROOF // ----------------------- let tooltipEl = null; let tooltipActive = false; function ensureTooltip() { if (tooltipEl) return tooltipEl; tooltipEl = document.createElement("div"); tooltipEl.className = "custom-tooltip-dorpeltype"; tooltipEl.innerText = TOOLTIP_TEXT; tooltipEl.style.position = "absolute"; tooltipEl.style.background = TOOLTIP_BG; tooltipEl.style.color = "white"; tooltipEl.style.padding = "5px"; tooltipEl.style.borderRadius = "5px"; tooltipEl.style.fontSize = "12px"; tooltipEl.style.zIndex = "100000"; tooltipEl.style.pointerEvents = "none"; tooltipEl.style.display = "none"; tooltipEl.style.whiteSpace = "nowrap"; // ✅ append naar container (niet body) container.appendChild(tooltipEl); return tooltipEl; } function positionTooltip(clientX, clientY) { const t = ensureTooltip(); const cRect = container.getBoundingClientRect(); const scale = measureScale(container); const offsetX = 32; const offsetY = 50; const left = ((clientX - cRect.left) / scale) + offsetX; const top = ((clientY - cRect.top) / scale) - offsetY; const nextLeft = `${left}px`; const nextTop = `${top}px`; if (t.style.left !== nextLeft) t.style.left = nextLeft; if (t.style.top !== nextTop) t.style.top = nextTop; } function showTooltip(clientX, clientY) { tooltipActive = true; const t = ensureTooltip(); if (t.style.display !== "block") t.style.display = "block"; positionTooltip(clientX, clientY); } function hideTooltip() { tooltipActive = false; if (tooltipEl && tooltipEl.style.display !== "none") tooltipEl.style.display = "none"; } // ----------------------- // GREY-OUT LOGICA (batched) // ----------------------- let scheduled = false; function setOpacity(opt, value) { const v = String(value); if (opt.style.opacity !== v) opt.style.opacity = v; } function setGreyedFlag(opt, isGreyed) { const next = isGreyed ? "1" : "0"; if (opt.dataset.dorpeltypeGreyed !== next) opt.dataset.dorpeltypeGreyed = next; } function setDisabled(radio, shouldDisable) { if (radio.disabled !== shouldDisable) radio.disabled = shouldDisable; } function updateGreyOutNow() { const checkedRadio = document.querySelector( "input[name='form_fields[dorpeltype]']:checked" ); for (const radio of radios) { const optionSpan = radio.closest(".elementor-field-option"); if (!optionSpan) continue; const isOtherThanChecked = !!checkedRadio && radio !== checkedRadio; setDisabled(radio, isOtherThanChecked); setOpacity(optionSpan, isOtherThanChecked ? 0.4 : 1); setGreyedFlag(optionSpan, isOtherThanChecked); } if (!document.querySelector(".elementor-field-option[data-dorpeltype-greyed='1']")) { hideTooltip(); } } function scheduleGreyOut() { if (scheduled) return; scheduled = true; requestAnimationFrame(() => { scheduled = false; updateGreyOutNow(); }); } radios.forEach((radio) => radio.addEventListener("change", scheduleGreyOut)); const form = radios[0].closest("form"); if (form) { form.addEventListener("reset", () => { requestAnimationFrame(() => { for (const radio of radios) { setDisabled(radio, false); const optionSpan = radio.closest(".elementor-field-option"); if (!optionSpan) continue; setOpacity(optionSpan, 1); setGreyedFlag(optionSpan, false); } hideTooltip(); }); }); } // ----------------------- // HOVER DETECTIE op GREYED OUT opties (event delegation) // ----------------------- function isHoveringGreyedOption(node) { const target = node && node.nodeType === 3 ? node.parentElement : node; if (!target) return false; const opt = target.closest ? target.closest(".elementor-field-option") : null; if (!opt) return false; return opt.dataset.dorpeltypeGreyed === "1"; } document.addEventListener( "mouseover", function (e) { if (isHoveringGreyedOption(e.target)) { showTooltip(e.clientX, e.clientY); } }, true ); document.addEventListener( "mouseout", function (e) { if (!tooltipActive) return; if (!isHoveringGreyedOption(e.relatedTarget)) { hideTooltip(); } }, true ); document.addEventListener( "mousemove", function (e) { if (!tooltipActive) return; positionTooltip(e.clientX, e.clientY); }, { passive: true } ); window.addEventListener("blur", hideTooltip); scheduleGreyOut(); });

Vakvulling dorpel

//// VAKVULLING BI #ALLE-VAKKEN - VASTE SHIFTBOX CAPTURE //// (() => { const SCALE = 3.84615384615; const SVG_ID = "alle-vakken"; const VVI_TO_SUFFIX = { "dubbelglas": "2glas", "tripleglas": "3glas", "paneel": "paneel", "glaspaneel": "glaspaneel", "deur": "deur", "kaderdeur": "kaderdeur", "dubbelkaderdeur": "dubbelkaderdeur", "draaikiep": "draaikiep", "vul in": null }; const BI_SUFFIXES = [ "2glas", "3glas", "paneel", "glaspaneel", "deur", "kaderdeur", "dubbelkaderdeur", "draaikiep" ]; const esc = s => CSS.escape(s); /* * Hier onthouden we EENMALIG welke delen * bij welke shiftbox horen. * * De actuele positie van de shiftbox speelt * daarna geen rol meer bij de selectie. */ let geometryCaptures = new WeakMap(); let gradientCaptures = new WeakMap(); let capturesReady = false; /* ============================================================ * ALGEMEEN * ============================================================ */ function svgRoot() { return document.querySelector( `svg#${esc(SVG_ID)}` ); } function setDisp(el, value) { if (el) { el.setAttribute( "display", value ); } } function readNumById(id) { const el = document.getElementById(id); if (!el) { return null; } const value = String( el.value ?? "" ) .trim() .replace(",", "."); const n = Number(value); return Number.isFinite(n) ? n : 0; } function numField( prefixA, prefixB, suffix ) { const a = readNumById( `form-field-${prefixA}${suffix}` ); if (a != null) { return a; } const b = readNumById( `form-field-${prefixB}${suffix}` ); if (b != null) { return b; } return 0; } function vakCount() { const checked = document.querySelector( 'input[type="radio"][name="form_fields[vakken]"]:checked' ); if ( checked && checked.value ) { const m = String( checked.value ).match(/([1-5])/); if (m) { return Number(m[1]); } } return 5; } function getVVI(n, i) { const select = document.querySelector( `select[name="form_fields[${n}VVI${i}]"]` ); return select ? String(select.value || "") : "vul in"; } /* ============================================================ * VISIBILITY * ============================================================ */ function hideNeverShow(svg) { svg .querySelectorAll( `[id^="vak-"][id*="-BI-shiftbox-"]` ) .forEach(el => { setDisp( el, "none" ); }); } function hideAllBIVariants( svg, i ) { for ( const suffix of BI_SUFFIXES ) { setDisp( svg.querySelector( `#${esc( `vak-${i}-BI-${suffix}` )}` ), "none" ); } } function showGroupAndNestedGs( group ) { if (!group) { return; } if ( String( group.id ).includes("-shiftbox-") ) { setDisp( group, "none" ); return; } setDisp( group, "block" ); group .querySelectorAll("g") .forEach(g => { if ( String( g.id ).includes("-shiftbox-") ) { setDisp( g, "none" ); } else { setDisp( g, "block" ); } }); } function updateVisibility() { const svg = svgRoot(); if (!svg) { return; } hideNeverShow(svg); const n = vakCount(); for ( let i = 1; i <= 5; i++ ) { hideAllBIVariants( svg, i ); } for ( let i = 1; i <= n; i++ ) { const value = getVVI( n, i ); const suffix = VVI_TO_SUFFIX[ value ]; if (!suffix) { continue; } const group = svg.querySelector( `#${esc( `vak-${i}-BI-${suffix}` )}` ); showGroupAndNestedGs( group ); } hideNeverShow(svg); } /* ============================================================ * INPUT POSITIES * ============================================================ */ function BI_LI(n) { return numField( `${n}`, "5", "LI" ); } function BI_TI(n, i) { return numField( `${n}`, "5", `TI${i}` ); } function BI_DMI_scaled( n, i ) { return ( numField( `${n}`, "5", `DMI${i}` ) / SCALE ); } function Sponning() { return ( readNumById( "form-field-BI_sponningbreedte" ) ?? 0 ); } function prefixLeft( n, i ) { let x = BI_LI(n); if (i >= 2) { x += BI_DMI_scaled(n, 1) + BI_TI(n, 1); } if (i >= 3) { x += BI_DMI_scaled(n, 2) + BI_TI(n, 2); } if (i >= 4) { x += BI_DMI_scaled(n, 3) + BI_TI(n, 3); } if (i >= 5) { x += BI_DMI_scaled(n, 4) + BI_TI(n, 4); } return x; } /* ============================================================ * SHIFTBOX ENGINE * ============================================================ */ function desiredXFromDelta( rect, exprNow ) { const currentX = Number( rect.getAttribute("x") ?? "0" ); if ( rect.dataset.baseX == null ) { rect.dataset.baseX = String(currentX); } if ( rect.dataset.baseExpr == null ) { rect.dataset.baseExpr = String(exprNow); } const baseX = Number( rect.dataset.baseX ); const baseExpr = Number( rect.dataset.baseExpr ); return ( baseX + ( exprNow - baseExpr ) ); } function boxFromShiftRect( rect, exprNow, boxId ) { const currentX = Number( rect.getAttribute("x") ?? "0" ); const currentY = Number( rect.getAttribute("y") ?? "0" ); const width = Number( rect.getAttribute("width") ?? "0" ); const height = Number( rect.getAttribute("height") ?? "0" ); const desiredX = desiredXFromDelta( rect, exprNow ); return { id: boxId, rect, desiredX, dxInc: desiredX - currentX, minX: currentX, maxX: currentX + width, minY: currentY, maxY: currentY + height }; } function buildBoxes( items ) { const map = new Map(); for ( const item of items ) { if (!item.gEl) { continue; } const rect = item.gEl.querySelector( "rect" ); if (!rect) { continue; } const box = boxFromShiftRect( rect, item.exprNow(), item.boxId ); if ( !map.has( item.key ) ) { map.set( item.key, [] ); } map .get( item.key ) .push(box); } return map; } function moveBoxes(map) { for ( const boxes of map.values() ) { for ( const box of boxes ) { box.rect.setAttribute( "x", box.desiredX ); } } } /* ============================================================ * EENMALIGE MEMBERSHIP CAPTURE * ============================================================ */ function initialBoxesForVak( svg, vak ) { const result = []; const left = svg.querySelector( `#${esc( `vak-${vak}-BI-shiftbox-L` )} rect` ); const right = svg.querySelector( `#${esc( `vak-${vak}-BI-shiftbox-R` )} rect` ); function add( rect, id ) { if (!rect) { return; } const x = Number( rect.getAttribute("x") ?? 0 ); const y = Number( rect.getAttribute("y") ?? 0 ); const w = Number( rect.getAttribute("width") ?? 0 ); const h = Number( rect.getAttribute("height") ?? 0 ); result.push({ id, minX: x, maxX: x + w, minY: y, maxY: y + h }); } add( left, "L" ); add( right, "R" ); return result; } /* * Alleen deze functie kijkt naar de geometrische positie. * * Dit gebeurt uitsluitend tijdens de capture. */ function captureMembership( x, y, boxes ) { const result = []; for ( const box of boxes ) { if ( x >= box.minX && x <= box.maxX && y >= box.minY && y <= box.maxY ) { result.push( box.id ); } } return result; } /* * Vanaf hier wordt NIET meer gekeken waar een punt ligt. * * Alleen de eerder opgeslagen L/R membership wordt gebruikt. */ function dxFromMembership( membership, boxes ) { let dx = 0; if ( !membership || !membership.length ) { return dx; } for ( const boxId of membership ) { const box = boxes.find( b => b.id === boxId ); if (box) { dx += box.dxInc; } } return dx; } /* ============================================================ * POINTS CAPTURE * ============================================================ */ function parsePoints( value ) { return String( value || "" ) .trim() .replace( /,/g, " " ) .split( /\s+/ ) .filter(Boolean) .map(Number); } function capturePointsElement( element, boxes ) { const nums = parsePoints( element.getAttribute( "points" ) ); const points = []; for ( let i = 0; i < nums.length - 1; i += 2 ) { points.push( captureMembership( nums[i], nums[i + 1], boxes ) ); } geometryCaptures.set( element, { type: "points", points } ); } function shiftPointsCaptured( element, boxes ) { const capture = geometryCaptures.get( element ); if ( !capture || capture.type !== "points" ) { return; } const nums = parsePoints( element.getAttribute( "points" ) ); const out = []; let pointIndex = 0; for ( let i = 0; i < nums.length - 1; i += 2 ) { const x = nums[i]; const y = nums[i + 1]; const membership = capture.points[ pointIndex ] || []; out.push( String( x + dxFromMembership( membership, boxes ) ), String(y) ); pointIndex++; } element.setAttribute( "points", out.join(" ") ); } /* ============================================================ * RECT CAPTURE * ============================================================ */ function captureRect( rect, boxes ) { const x = Number( rect.getAttribute("x") ); const y = Number( rect.getAttribute("y") ); const width = Number( rect.getAttribute("width") ); const height = Number( rect.getAttribute("height") ); if ( !Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(width) ) { return; } const yMid = Number.isFinite(height) ? y + height / 2 : y; geometryCaptures.set( rect, { type: "rect", left: captureMembership( x, yMid, boxes ), right: captureMembership( x + width, yMid, boxes ) } ); } function shiftRectCaptured( rect, boxes ) { const capture = geometryCaptures.get( rect ); if ( !capture || capture.type !== "rect" ) { return; } const x = Number( rect.getAttribute("x") ); const width = Number( rect.getAttribute("width") ); if ( !Number.isFinite(x) || !Number.isFinite(width) ) { return; } const oldRight = x + width; const newLeft = x + dxFromMembership( capture.left, boxes ); const newRight = oldRight + dxFromMembership( capture.right, boxes ); rect.setAttribute( "x", Math.min( newLeft, newRight ) ); rect.setAttribute( "width", Math.abs( newRight - newLeft ) ); } /* ============================================================ * PATH * ============================================================ */ function tokenizePath(d) { return ( String(d || "") .match( /[a-zA-Z]|-?\d*\.?\d+(?:e[-+]?\d+)?/g ) || [] ); } /* * Een path wordt bij capture eenmalig doorlopen. * * Voor ieder X/Y-punt onthouden we alleen: * [] / ["L"] / ["R"] / ["L","R"] */ function capturePath( element, boxes ) { const d = element.getAttribute( "d" ); const tokens = tokenizePath(d); if (!tokens.length) { return; } let i = 0; let cmd = ""; let x = 0; let y = 0; let x0 = 0; let y0 = 0; const memberships = []; const isCmd = token => /^[a-zA-Z]$/.test( token ); const nextNum = () => Number( tokens[i++] ); const addPoint = ( px, py ) => { memberships.push( captureMembership( px, py, boxes ) ); }; while ( i < tokens.length ) { if ( isCmd( tokens[i] ) ) { cmd = tokens[i++]; } const relative = cmd === cmd.toLowerCase(); const C = cmd.toUpperCase(); if ( C === "M" || C === "L" || C === "T" ) { while ( i < tokens.length && !isCmd( tokens[i] ) ) { const px = nextNum(); const py = nextNum(); const ax = relative ? x + px : px; const ay = relative ? y + py : py; addPoint( ax, ay ); x = ax; y = ay; if ( C === "M" ) { x0 = ax; y0 = ay; } } continue; } if ( C === "H" ) { while ( i < tokens.length && !isCmd( tokens[i] ) ) { const px = nextNum(); const ax = relative ? x + px : px; addPoint( ax, y ); x = ax; } continue; } if ( C === "V" ) { while ( i < tokens.length && !isCmd( tokens[i] ) ) { const py = nextNum(); y = relative ? y + py : py; } continue; } if ( C === "C" ) { while ( i < tokens.length && !isCmd( tokens[i] ) ) { const values = []; for ( let j = 0; j < 3; j++ ) { const px = nextNum(); const py = nextNum(); const ax = relative ? x + px : px; const ay = relative ? y + py : py; values.push([ ax, ay ]); } for ( const point of values ) { addPoint( point[0], point[1] ); } x = values[2][0]; y = values[2][1]; } continue; } if ( C === "S" || C === "Q" ) { while ( i < tokens.length && !isCmd( tokens[i] ) ) { const values = []; for ( let j = 0; j < 2; j++ ) { const px = nextNum(); const py = nextNum(); const ax = relative ? x + px : px; const ay = relative ? y + py : py; values.push([ ax, ay ]); } for ( const point of values ) { addPoint( point[0], point[1] ); } x = values[1][0]; y = values[1][1]; } continue; } if ( C === "A" ) { while ( i < tokens.length && !isCmd( tokens[i] ) ) { nextNum(); nextNum(); nextNum(); nextNum(); nextNum(); const px = nextNum(); const py = nextNum(); const ax = relative ? x + px : px; const ay = relative ? y + py : py; addPoint( ax, ay ); x = ax; y = ay; } continue; } if ( C === "Z" ) { x = x0; y = y0; continue; } return; } geometryCaptures.set( element, { type: "path", memberships } ); } function shiftPathCaptured( element, boxes ) { const capture = geometryCaptures.get( element ); if ( !capture || capture.type !== "path" ) { return; } const d = element.getAttribute( "d" ); const tokens = tokenizePath(d); if (!tokens.length) { return; } let tokenIndex = 0; let memberIndex = 0; let cmd = ""; let x = 0; let y = 0; let x0 = 0; let y0 = 0; const out = []; const isCmd = token => /^[a-zA-Z]$/.test( token ); const nextNum = () => Number( tokens[tokenIndex++] ); const membership = () => capture.memberships[ memberIndex++ ] || []; const shiftedPoint = ( px, py ) => { return [ px + dxFromMembership( membership(), boxes ), py ]; }; while ( tokenIndex < tokens.length ) { if ( isCmd( tokens[tokenIndex] ) ) { cmd = tokens[ tokenIndex++ ]; } const relative = cmd === cmd.toLowerCase(); const C = cmd.toUpperCase(); if ( C === "M" ) { out.push("M"); const px = nextNum(); const py = nextNum(); const ax = relative ? x + px : px; const ay = relative ? y + py : py; const p = shiftedPoint( ax, ay ); out.push( p[0], p[1] ); x = ax; y = ay; x0 = ax; y0 = ay; while ( tokenIndex < tokens.length && !isCmd( tokens[ tokenIndex ] ) ) { const lx = nextNum(); const ly = nextNum(); const ax2 = relative ? x + lx : lx; const ay2 = relative ? y + ly : ly; const p2 = shiftedPoint( ax2, ay2 ); out.push( "L", p2[0], p2[1] ); x = ax2; y = ay2; } continue; } if ( C === "L" || C === "T" ) { out.push(C); while ( tokenIndex < tokens.length && !isCmd( tokens[ tokenIndex ] ) ) { const px = nextNum(); const py = nextNum(); const ax = relative ? x + px : px; const ay = relative ? y + py : py; const p = shiftedPoint( ax, ay ); out.push( p[0], p[1] ); x = ax; y = ay; } continue; } if ( C === "H" ) { out.push("H"); while ( tokenIndex < tokens.length && !isCmd( tokens[ tokenIndex ] ) ) { const px = nextNum(); const ax = relative ? x + px : px; const p = shiftedPoint( ax, y ); out.push( p[0] ); x = ax; } continue; } if ( C === "V" ) { out.push("V"); while ( tokenIndex < tokens.length && !isCmd( tokens[ tokenIndex ] ) ) { const py = nextNum(); const ay = relative ? y + py : py; out.push(ay); y = ay; } continue; } if ( C === "C" ) { out.push("C"); while ( tokenIndex < tokens.length && !isCmd( tokens[ tokenIndex ] ) ) { const values = []; for ( let j = 0; j < 3; j++ ) { const px = nextNum(); const py = nextNum(); const ax = relative ? x + px : px; const ay = relative ? y + py : py; values.push([ ax, ay ]); } for ( const point of values ) { const shifted = shiftedPoint( point[0], point[1] ); out.push( shifted[0], shifted[1] ); } x = values[2][0]; y = values[2][1]; } continue; } if ( C === "S" || C === "Q" ) { out.push(C); while ( tokenIndex < tokens.length && !isCmd( tokens[ tokenIndex ] ) ) { const values = []; for ( let j = 0; j < 2; j++ ) { const px = nextNum(); const py = nextNum(); const ax = relative ? x + px : px; const ay = relative ? y + py : py; values.push([ ax, ay ]); } for ( const point of values ) { const shifted = shiftedPoint( point[0], point[1] ); out.push( shifted[0], shifted[1] ); } x = values[1][0]; y = values[1][1]; } continue; } if ( C === "A" ) { out.push("A"); while ( tokenIndex < tokens.length && !isCmd( tokens[ tokenIndex ] ) ) { const rx = nextNum(); const ry = nextNum(); const rotation = nextNum(); const largeArc = nextNum(); const sweep = nextNum(); const px = nextNum(); const py = nextNum(); const ax = relative ? x + px : px; const ay = relative ? y + py : py; const shifted = shiftedPoint( ax, ay ); out.push( rx, ry, rotation, largeArc, sweep, shifted[0], shifted[1] ); x = ax; y = ay; } continue; } if ( C === "Z" ) { out.push("Z"); x = x0; y = y0; continue; } return; } element.setAttribute( "d", out.join(" ") ); } /* ============================================================ * USE / GLAS * ============================================================ */ function getUseHref( useEl ) { return ( useEl.getAttribute( "href" ) || useEl.getAttribute( "xlink:href" ) || useEl.getAttributeNS( "http://www.w3.org/1999/xlink", "href" ) || "" ); } function getReferencedWidth( svg, useEl ) { const href = getUseHref( useEl ); if ( !href || !href.startsWith("#") ) { return null; } const ref = svg.querySelector( `#${esc( href.substring(1) )}` ); if (!ref) { return null; } const width = Number( ref.getAttribute( "width" ) ); if ( Number.isFinite(width) && width > 0 ) { return width; } const viewBox = ref.getAttribute( "viewBox" ); if (viewBox) { const values = viewBox .trim() .split( /[\s,]+/ ) .map(Number); if ( values.length === 4 && Number.isFinite( values[2] ) && values[2] > 0 ) { return values[2]; } } return null; } function parseUseTransform( useEl ) { const transform = useEl.getAttribute( "transform" ) || ""; const NUM = "[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:e[+-]?\\d+)?"; const translateRe = new RegExp( `translate\\(\\s*(${NUM})\\s*(?:[,\\s]\\s*(${NUM}))?\\s*\\)`, "i" ); const scaleRe = new RegExp( `scale\\(\\s*(${NUM})\\s*(?:[,\\s]\\s*(${NUM}))?\\s*\\)`, "i" ); const translate = transform.match( translateRe ); const scale = transform.match( scaleRe ); if ( !translate || !scale ) { return null; } const tx = Number( translate[1] ); const ty = translate[2] != null ? Number( translate[2] ) : 0; const sx = Number( scale[1] ); const sy = scale[2] != null ? Number( scale[2] ) : sx; if ( !Number.isFinite(tx) || !Number.isFinite(ty) || !Number.isFinite(sx) || !Number.isFinite(sy) ) { return null; } return { transform, translateText: translate[0], scaleText: scale[0], tx, ty, sx, sy }; } function captureUse( svg, useEl, boxes ) { const parsed = parseUseTransform( useEl ); if (!parsed) { return; } const sourceWidth = getReferencedWidth( svg, useEl ); if ( !sourceWidth ) { return; } const width = sourceWidth * parsed.sx; geometryCaptures.set( useEl, { type: "use", sourceWidth, left: captureMembership( parsed.tx, parsed.ty, boxes ), right: captureMembership( parsed.tx + width, parsed.ty, boxes ) } ); } function shiftUseCaptured( useEl, boxes ) { const capture = geometryCaptures.get( useEl ); if ( !capture || capture.type !== "use" ) { return; } const parsed = parseUseTransform( useEl ); if (!parsed) { return; } const currentWidth = capture.sourceWidth * parsed.sx; const oldRight = parsed.tx + currentWidth; const newLeft = parsed.tx + dxFromMembership( capture.left, boxes ); const newRight = oldRight + dxFromMembership( capture.right, boxes ); const newWidth = newRight - newLeft; if ( !Number.isFinite( newWidth ) || newWidth <= 0 ) { return; } const newScaleX = newWidth / capture.sourceWidth; /* * translateY blijft onaangetast. */ let newTransform = parsed.transform.replace( parsed.translateText, `translate(${newLeft} ${parsed.ty})` ); /* * scaleY blijft onaangetast. */ newTransform = newTransform.replace( parsed.scaleText, `scale(${newScaleX} ${parsed.sy})` ); useEl.setAttribute( "transform", newTransform ); } /* ============================================================ * GRADIENTEN * ============================================================ */ function ensureDefs(svg) { let defs = svg.querySelector( "defs" ); if (!defs) { defs = document.createElementNS( "http://www.w3.org/2000/svg", "defs" ); svg.insertBefore( defs, svg.firstChild ); } return defs; } function parseFillUrl(fill) { const match = String(fill).match( /^url\((["']?)#([^)'" ]+)\1\)$/ ); return match ? match[2] : null; } function isClearlyUserSpace( gradient ) { const units = ( gradient.getAttribute( "gradientUnits" ) || "" ).trim(); if ( units === "userSpaceOnUse" ) { return true; } if (units) { return false; } for ( const attr of [ "x1", "y1", "x2", "y2", "cx", "cy", "fx", "fy", "r" ] ) { const value = gradient.getAttribute( attr ); if ( value == null ) { continue; } const n = Number( String(value) .trim() .replace( ",", "." ) ); if ( Number.isFinite(n) && ( n < -0.001 || n > 1.001 ) ) { return true; } } return false; } let gradCloneCounter = 0; function ensureGradientClone( svg, element, baseId ) { const defs = ensureDefs(svg); if ( !element.dataset.gradCloneId ) { gradCloneCounter++; element.dataset.gradCloneId = `${baseId}__clone__${gradCloneCounter}`; } const cloneId = element.dataset.gradCloneId; let clone = defs.querySelector( `#${esc( cloneId )}` ); if (!clone) { const original = svg.querySelector( `#${esc( baseId )}` ); if ( !original || !isClearlyUserSpace( original ) ) { return null; } clone = original.cloneNode( true ); clone.setAttribute( "id", cloneId ); clone.setAttribute( "gradientUnits", "userSpaceOnUse" ); clone.setAttribute( "data-base-gradient-transform", original.getAttribute( "gradientTransform" ) || "" ); defs.appendChild( clone ); } element.setAttribute( "fill", `url(#${cloneId})` ); return clone; } function representativePoint( element ) { const tag = element.tagName.toLowerCase(); if ( tag === "rect" ) { const x = Number( element.getAttribute("x") ); const y = Number( element.getAttribute("y") ); const h = Number( element.getAttribute( "height" ) ); return [ x, Number.isFinite(h) ? y + h / 2 : y ]; } if ( tag === "polygon" || tag === "polyline" ) { const values = parsePoints( element.getAttribute( "points" ) ); if ( values.length >= 2 ) { return [ values[0], values[1] ]; } } if ( tag === "path" ) { const nums = ( element.getAttribute( "d" ) || "" ).match( /-?\d*\.?\d+(?:e[-+]?\d+)?/g ); if ( nums && nums.length >= 2 ) { return [ Number(nums[0]), Number(nums[1]) ]; } } return null; } function captureGradient( element, boxes ) { const point = representativePoint( element ); if (!point) { return; } gradientCaptures.set( element, captureMembership( point[0], point[1], boxes ) ); } function applyGradientShift( svg, element, boxes ) { const membership = gradientCaptures.get( element ); if (!membership) { return; } const dx = dxFromMembership( membership, boxes ); if (!dx) { return; } const fill = element.getAttribute( "fill" ); const baseId = parseFillUrl(fill); if (!baseId) { return; } const clone = ensureGradientClone( svg, element, baseId ); if (!clone) { return; } const baseTransform = ( clone.getAttribute( "data-base-gradient-transform" ) || "" ).trim(); const shift = `translate(${dx},0)`; clone.setAttribute( "gradientTransform", baseTransform ? `${baseTransform} ${shift}` : shift ); } /* ============================================================ * CAPTURE VOLLEDIGE GROUP * ============================================================ */ function captureGroup( svg, group, boxes ) { if ( !group || !boxes.length ) { return; } group .querySelectorAll( "polygon, polyline" ) .forEach(el => { capturePointsElement( el, boxes ); }); group .querySelectorAll( "rect" ) .forEach(el => { captureRect( el, boxes ); }); group .querySelectorAll( "path" ) .forEach(el => { capturePath( el, boxes ); }); const id = String( group.id || "" ).toLowerCase(); if ( id.includes("2glas") || id.includes("3glas") ) { group .querySelectorAll( "use" ) .forEach(el => { captureUse( svg, el, boxes ); }); } group .querySelectorAll( "[fill^='url(']" ) .forEach(el => { captureGradient( el, boxes ); }); } /* * DE BELANGRIJKE FUNCTIE: * * Dit gebeurt maar EEN keer. */ function captureEverything() { const svg = svgRoot(); if (!svg) { return false; } geometryCaptures = new WeakMap(); gradientCaptures = new WeakMap(); for ( let vak = 1; vak <= 5; vak++ ) { const boxes = initialBoxesForVak( svg, vak ); if (!boxes.length) { continue; } for ( const suffix of BI_SUFFIXES ) { const group = svg.querySelector( `#${esc( `vak-${vak}-BI-${suffix}` )}` ); captureGroup( svg, group, boxes ); } } capturesReady = true; return true; } /* ============================================================ * APPLY CAPTURED GEOMETRY * ============================================================ */ function applyShiftToGroup( svg, group, boxes ) { if ( !group || !boxes || !boxes.length ) { return; } group .querySelectorAll( "polygon, polyline" ) .forEach(el => { shiftPointsCaptured( el, boxes ); }); group .querySelectorAll( "rect" ) .forEach(el => { shiftRectCaptured( el, boxes ); }); group .querySelectorAll( "path" ) .forEach(el => { shiftPathCaptured( el, boxes ); }); const groupId = String( group.id || "" ).toLowerCase(); if ( groupId.includes( "2glas" ) || groupId.includes( "3glas" ) ) { group .querySelectorAll( "use" ) .forEach(el => { shiftUseCaptured( el, boxes ); }); } group .querySelectorAll( "[fill^='url(']" ) .forEach(el => { applyGradientShift( svg, el, boxes ); }); } /* ============================================================ * BUILD + APPLY * ============================================================ */ function buildAndApplyShifts() { const svg = svgRoot(); if (!svg) { return; } /* * Captures moeten bestaan VOORDAT * shiftboxen/geometrie verplaatst worden. */ if ( !capturesReady ) { if ( !captureEverything() ) { return; } } hideNeverShow(svg); const n = vakCount(); const items = []; for ( let i = 1; i <= n; i++ ) { const left = svg.querySelector( `#${esc( `vak-${i}-BI-shiftbox-L` )}` ); const right = svg.querySelector( `#${esc( `vak-${i}-BI-shiftbox-R` )}` ); items.push({ key: `BI:${i}`, boxId: "L", gEl: left, exprNow: () => prefixLeft( n, i ) - Sponning() }); items.push({ key: `BI:${i}`, boxId: "R", gEl: right, exprNow: () => prefixLeft( n, i ) + BI_DMI_scaled( n, i ) + Sponning() }); } const boxesMap = buildBoxes( items ); for ( let i = 1; i <= n; i++ ) { const boxes = boxesMap.get( `BI:${i}` ) || []; if (!boxes.length) { continue; } for ( const suffix of BI_SUFFIXES ) { const group = svg.querySelector( `#${esc( `vak-${i}-BI-${suffix}` )}` ); if (group) { applyShiftToGroup( svg, group, boxes ); } } } moveBoxes( boxesMap ); hideNeverShow(svg); } /* ============================================================ * SCHEDULING * ============================================================ */ let updateQueued = false; function runUpdateAll() { updateQueued = false; updateVisibility(); buildAndApplyShifts(); } function updateAll() { if ( updateQueued ) { return; } updateQueued = true; queueMicrotask( runUpdateAll ); } /* ============================================================ * LISTENERS * ============================================================ */ function bind( root = document ) { root .querySelectorAll( `select[name^="form_fields["][name*="VVI"]` ) .forEach(el => { if ( el.dataset.biBound === "1" ) { return; } el.dataset.biBound = "1"; el.addEventListener( "change", updateAll ); el.addEventListener( "input", updateAll ); }); root .querySelectorAll( `input[id^="form-field-"]` ) .forEach(el => { if ( el.dataset.biShiftBound === "1" ) { return; } el.dataset.biShiftBound = "1"; el.addEventListener( "change", updateAll ); el.addEventListener( "input", updateAll ); }); root .querySelectorAll( `input[type="radio"][name="form_fields[vakken]"]` ) .forEach(el => { if ( el.dataset.biVakkenBound === "1" ) { return; } el.dataset.biVakkenBound = "1"; el.addEventListener( "change", updateAll ); el.addEventListener( "input", updateAll ); }); } /* ============================================================ * BOOT * ============================================================ */ function boot() { bind(document); /* * EERST CAPTURE. * Daarna pas eerste update. */ captureEverything(); runUpdateAll(); const observer = new MutationObserver( mutations => { let newHtml = false; for ( const mutation of mutations ) { mutation .addedNodes .forEach(node => { if ( !( node instanceof HTMLElement ) ) { return; } newHtml = true; bind(node); }); } if (newHtml) { updateAll(); } } ); observer.observe( document.documentElement, { childList: true, subtree: true } ); } if ( document.readyState === "loading" ) { document.addEventListener( "DOMContentLoaded", boot ); } else { boot(); } /* ============================================================ * HANDMATIGE REBASE * ============================================================ */ window.rebaseAlleVakkenBI = () => { const svg = svgRoot(); if (!svg) { return; } /* * Nieuwe shiftbox nulposities. */ svg .querySelectorAll( `[id*="-BI-shiftbox-"] rect` ) .forEach(rect => { rect.dataset.baseX = String( Number( rect.getAttribute( "x" ) ?? 0 ) ); delete rect.dataset.baseExpr; }); /* * Oude L/R koppelingen vergeten. */ capturesReady = false; geometryCaptures = new WeakMap(); gradientCaptures = new WeakMap(); /* * Eenmalig opnieuw koppelen. */ captureEverything(); runUpdateAll(); }; })();
//// VAKVULLING BI #ALLE-VAKKEN //// (() => { const SCALE = 3.84615384615; const SVG_ID = "alle-vakken"; // VVI value -> BI suffix const VVI_TO_SUFFIX = { "dubbelglas": "2glas", "tripleglas": "3glas", "paneel": "paneel", "glaspaneel": "glaspaneel", "deur": "deur", "kaderdeur": "kaderdeur", "dubbelkaderdeur": "dubbelkaderdeur", "draaikiep": "draaikiep", "vul in": null }; const BI_SUFFIXES = ["2glas","3glas","paneel","glaspaneel","deur","kaderdeur","dubbelkaderdeur","draaikiep"]; const esc = (s) => CSS.escape(s); function svgRoot() { return document.querySelector(`svg#${esc(SVG_ID)}`); } function setDisp(el, v) { if (el) el.setAttribute("display", v); } function ensureDefs(svg) { let defs = svg.querySelector("defs"); if (!defs) { defs = document.createElementNS("http://www.w3.org/2000/svg", "defs"); svg.insertBefore(defs, svg.firstChild); } return defs; } function readNumById(id) { const el = document.getElementById(id); if (!el) return null; const v = (el.value ?? "").toString().trim().replace(",", "."); const n = Number(v); return Number.isFinite(n) ? n : 0; } // Prefer prefixA, else prefixB, else 0 function numField(prefixA, prefixB, suffix) { const a = readNumById(`form-field-${prefixA}${suffix}`); if (a != null) return a; const b = readNumById(`form-field-${prefixB}${suffix}`); if (b != null) return b; return 0; } function vakCount() { const checked = document.querySelector('input[type="radio"][name="form_fields[vakken]"]:checked'); if (checked && checked.value) { const m = String(checked.value).match(/([1-5])/); if (m) return Number(m[1]); } return 5; } // VVI select by name form_fields[VVI] function getVVI(n, i) { const sel = document.querySelector(`select[name="form_fields[${n}VVI${i}]"]`); return sel ? String(sel.value || "") : "vul in"; } function hideNeverShow(svg) { // BI shiftboxen nooit tonen svg.querySelectorAll(`[id^="vak-"][id*="-BI-shiftbox-"]`).forEach(el => setDisp(el, "none")); } function hideAllBIVariants(svg, i) { for (const suf of BI_SUFFIXES) { setDisp(svg.querySelector(`#${esc(`vak-${i}-BI-${suf}`)}`), "none"); } } function showGroupAndNestedGs(groupEl) { if (!groupEl) return; // shiftboxen sowieso niet tonen if (String(groupEl.id).includes("-shiftbox-")) { setDisp(groupEl, "none"); return; } setDisp(groupEl, "block"); groupEl.querySelectorAll("g").forEach(g => { if (String(g.id).includes("-shiftbox-")) setDisp(g, "none"); else setDisp(g, "block"); }); } function updateVisibility() { const svg = svgRoot(); if (!svg) return; hideNeverShow(svg); const n = vakCount(); // reset alleen onze BI-groepen for (let i = 1; i <= 5; i++) hideAllBIVariants(svg, i); // show voor i<=n op basis van VVI for (let i = 1; i <= n; i++) { const value = getVVI(n, i); const suffix = VVI_TO_SUFFIX[value]; if (!suffix) continue; const g = svg.querySelector(`#${esc(`vak-${i}-BI-${suffix}`)}`); showGroupAndNestedGs(g); } hideNeverShow(svg); } /****************************************************************** * BI position building (LI + DMI/TI cumulatief) * Alleen DMI geschaald: DMI/SCALE ******************************************************************/ function BI_LI(n) { return numField(`${n}`, "5", "LI"); } function BI_TI(n, i) { return numField(`${n}`, "5", `TI${i}`); } function BI_DMI_scaled(n, i) { return (numField(`${n}`, "5", `DMI${i}`) / SCALE); } function Sponning() { return (readNumById("form-field-BI_sponningbreedte") ?? 0); } function prefixLeft(n, i) { let x = BI_LI(n); if (i >= 2) x += BI_DMI_scaled(n,1) + BI_TI(n,1); if (i >= 3) x += BI_DMI_scaled(n,2) + BI_TI(n,2); if (i >= 4) x += BI_DMI_scaled(n,3) + BI_TI(n,3); if (i >= 5) x += BI_DMI_scaled(n,4) + BI_TI(n,4); return x; } /****************************************************************** * Shiftbox delta-engine ******************************************************************/ function desiredXFromDelta(rect, exprNow) { const curX = Number(rect.getAttribute("x") ?? "0"); if (rect.dataset.baseX == null) rect.dataset.baseX = String(curX); if (rect.dataset.baseExpr == null) rect.dataset.baseExpr = String(exprNow); const baseX = Number(rect.dataset.baseX); const baseExpr = Number(rect.dataset.baseExpr); return baseX + (exprNow - baseExpr); } function boxFromShiftRect(rect, exprNow) { const curX = Number(rect.getAttribute("x") ?? "0"); const curY = Number(rect.getAttribute("y") ?? "0"); const w = Number(rect.getAttribute("width") ?? "0"); const h = Number(rect.getAttribute("height") ?? "0"); const desiredX = desiredXFromDelta(rect, exprNow); return { rect, desiredX, dxInc: desiredX - curX, minX: curX, maxX: curX + w, minY: curY, maxY: curY + h, }; } function buildBoxes(items) { const map = new Map(); // key -> boxes[] for (const it of items) { if (!it.gEl) continue; const r = it.gEl.querySelector("rect"); if (!r) continue; const b = boxFromShiftRect(r, it.exprNow()); if (!map.has(it.key)) map.set(it.key, []); map.get(it.key).push(b); } return map; } function moveBoxes(map) { for (const boxes of map.values()) { for (const b of boxes) b.rect.setAttribute("x", b.desiredX); } } function dxForPoint(x, y, boxes) { let dx = 0; for (const b of boxes) { if (x >= b.minX && x <= b.maxX && y >= b.minY && y <= b.maxY) dx += b.dxInc; } return dx; } /****************************************************************** * Geometry shifters ******************************************************************/ function shiftPointsAttr(pointsStr, boxes) { const nums = pointsStr.trim().replace(/,/g, " ").split(/\s+/).filter(Boolean).map(Number); if (nums.length < 2 || nums.some(n => !Number.isFinite(n))) return pointsStr; const out = []; for (let i = 0; i < nums.length - 1; i += 2) { const x = nums[i], y = nums[i + 1]; out.push(String(x + dxForPoint(x, y, boxes)), String(y)); } return out.join(" "); } function tokenizePath(d) { return d.match(/[a-zA-Z]|-?\d*\.?\d+(?:e[-+]?\d+)?/g) || []; } function shiftPathD(d, boxes) { const t = tokenizePath(d); if (!t.length) return d; let i = 0, cmd = "", x = 0, y = 0, x0 = 0, y0 = 0; const out = []; const isCmd = tok => /^[a-zA-Z]$/.test(tok); const nextNum = () => Number(t[i++]); const push = v => out.push(v); const shiftAbs = (px, py) => [px + dxForPoint(px, py, boxes), py]; while (i < t.length) { if (isCmd(t[i])) cmd = t[i++]; const rel = (cmd === cmd.toLowerCase()); const C = cmd.toUpperCase(); if (C === "M") { push("M"); const px = nextNum(), py = nextNum(); const ax = rel ? x + px : px, ay = rel ? y + py : py; const p = shiftAbs(ax, ay); push(p[0]); push(p[1]); x = ax; y = ay; x0 = ax; y0 = ay; while (i < t.length && !isCmd(t[i])) { push("L"); const lx = nextNum(), ly = nextNum(); const ax2 = rel ? x + lx : lx, ay2 = rel ? y + ly : ly; const p2 = shiftAbs(ax2, ay2); push(p2[0]); push(p2[1]); x = ax2; y = ay2; } continue; } if (C === "L") { push("L"); while (i < t.length && !isCmd(t[i])) { const px = nextNum(), py = nextNum(); const ax = rel ? x + px : px, ay = rel ? y + py : py; const p = shiftAbs(ax, ay); push(p[0]); push(p[1]); x = ax; y = ay; } continue; } if (C === "H") { push("H"); while (i < t.length && !isCmd(t[i])) { const px = nextNum(); const ax = rel ? x + px : px; const p = shiftAbs(ax, y); push(p[0]); x = ax; } continue; } if (C === "V") { push("V"); while (i < t.length && !isCmd(t[i])) { const py = nextNum(); const ay = rel ? y + py : py; push(ay); y = ay; } continue; } if (C === "C") { push("C"); while (i < t.length && !isCmd(t[i])) { const x1 = nextNum(), y1 = nextNum(), x2 = nextNum(), y2 = nextNum(), x3 = nextNum(), y3 = nextNum(); const ax1 = rel ? x + x1 : x1, ay1 = rel ? y + y1 : y1; const ax2 = rel ? x + x2 : x2, ay2 = rel ? y + y2 : y2; const ax3 = rel ? x + x3 : x3, ay3 = rel ? y + y3 : y3; const p1 = shiftAbs(ax1, ay1); const p2 = shiftAbs(ax2, ay2); const p3 = shiftAbs(ax3, ay3); push(p1[0]); push(p1[1]); push(p2[0]); push(p2[1]); push(p3[0]); push(p3[1]); x = ax3; y = ay3; } continue; } if (C === "S") { push("S"); while (i < t.length && !isCmd(t[i])) { const x2 = nextNum(), y2 = nextNum(), x3 = nextNum(), y3 = nextNum(); const ax2 = rel ? x + x2 : x2, ay2 = rel ? y + y2 : y2; const ax3 = rel ? x + x3 : x3, ay3 = rel ? y + y3 : y3; const p2 = shiftAbs(ax2, ay2); const p3 = shiftAbs(ax3, ay3); push(p2[0]); push(p2[1]); push(p3[0]); push(p3[1]); x = ax3; y = ay3; } continue; } if (C === "Q") { push("Q"); while (i < t.length && !isCmd(t[i])) { const x1 = nextNum(), y1 = nextNum(), x2 = nextNum(), y2 = nextNum(); const ax1 = rel ? x + x1 : x1, ay1 = rel ? y + y1 : y1; const ax2 = rel ? x + x2 : x2, ay2 = rel ? y + y2 : y2; const p1 = shiftAbs(ax1, ay1); const p2 = shiftAbs(ax2, ay2); push(p1[0]); push(p1[1]); push(p2[0]); push(p2[1]); x = ax2; y = ay2; } continue; } if (C === "T") { push("T"); while (i < t.length && !isCmd(t[i])) { const x2 = nextNum(), y2 = nextNum(); const ax2 = rel ? x + x2 : x2, ay2 = rel ? y + y2 : y2; const p2 = shiftAbs(ax2, ay2); push(p2[0]); push(p2[1]); x = ax2; y = ay2; } continue; } if (C === "A") { push("A"); while (i < t.length && !isCmd(t[i])) { const rx = nextNum(), ry = nextNum(), rot = nextNum(), laf = nextNum(), sf = nextNum(), x2 = nextNum(), y2 = nextNum(); const ax2 = rel ? x + x2 : x2, ay2 = rel ? y + y2 : y2; const p2 = shiftAbs(ax2, ay2); push(rx); push(ry); push(rot); push(laf); push(sf); push(p2[0]); push(p2[1]); x = ax2; y = ay2; } continue; } if (C === "Z") { push("Z"); x = x0; y = y0; continue; } return d; } return out.join(" "); } function shiftRect(el, boxes) { const x = Number(el.getAttribute("x")); const y = Number(el.getAttribute("y")); const w = Number(el.getAttribute("width")); if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(w)) return; const h = Number(el.getAttribute("height")); const yMid = Number.isFinite(h) ? (y + h / 2) : y; const xL2 = x + dxForPoint(x, yMid, boxes); const xR = x + w; const xR2 = xR + dxForPoint(xR, yMid, boxes); el.setAttribute("x", Math.min(xL2, xR2)); el.setAttribute("width", Math.abs(xR2 - xL2)); } /****************************************************************** * Gradient shifting (zelfde als BU) ******************************************************************/ function parseFillUrl(fill) { const m = String(fill).match(/^url\((["']?)#([^)'" ]+)\1\)$/); return m ? m[2] : null; } function isClearlyUserSpace(gradEl) { const units = (gradEl.getAttribute("gradientUnits") || "").trim(); if (units === "userSpaceOnUse") return true; if (units) return false; for (const a of ["x1","y1","x2","y2","cx","cy","fx","fy","r"]) { const v = gradEl.getAttribute(a); if (v == null) continue; const n = Number(String(v).trim().replace(",", ".")); if (!Number.isFinite(n)) continue; if (n < -0.001 || n > 1.001) return true; } return false; } let gradCloneCounter = 0; function ensureGradientClone(svg, el, baseGradId) { const defs = ensureDefs(svg); if (!el.dataset.gradCloneId) { gradCloneCounter += 1; el.dataset.gradCloneId = `${baseGradId}__clone__${gradCloneCounter}`; } const cloneId = el.dataset.gradCloneId; let clone = defs.querySelector(`#${esc(cloneId)}`); if (!clone) { const orig = svg.querySelector(`#${esc(baseGradId)}`); if (!orig) return null; if (!isClearlyUserSpace(orig)) return null; clone = orig.cloneNode(true); clone.setAttribute("id", cloneId); clone.setAttribute("gradientUnits", "userSpaceOnUse"); clone.setAttribute("data-base-gradient-transform", orig.getAttribute("gradientTransform") || ""); defs.appendChild(clone); } el.setAttribute("fill", `url(#${cloneId})`); return clone; } function dxRepresentative(el, boxes) { const tag = el.tagName.toLowerCase(); if (tag === "rect") { const x = Number(el.getAttribute("x")); const y = Number(el.getAttribute("y")); const h = Number(el.getAttribute("height")); const yMid = Number.isFinite(h) ? (y + h / 2) : y; if (!Number.isFinite(x) || !Number.isFinite(yMid)) return 0; return dxForPoint(x, yMid, boxes); } if (tag === "polygon" || tag === "polyline") { const pts = (el.getAttribute("points") || "").trim().replace(/,/g, " ").split(/\s+/).filter(Boolean).map(Number); if (pts.length < 2 || pts.some(n => !Number.isFinite(n))) return 0; return dxForPoint(pts[0], pts[1], boxes); } if (tag === "path") { const d = el.getAttribute("d") || ""; const nums = d.match(/-?\d*\.?\d+(?:e[-+]?\d+)?/g); if (!nums || nums.length < 2) return 0; const x = Number(nums[0]), y = Number(nums[1]); if (!Number.isFinite(x) || !Number.isFinite(y)) return 0; return dxForPoint(x, y, boxes); } return 0; } function applyGradientShift(svg, el, dx) { const fill = el.getAttribute("fill"); if (!fill) return; const base = parseFillUrl(fill); if (!base) return; const clone = ensureGradientClone(svg, el, base); if (!clone) return; const baseT = (clone.getAttribute("data-base-gradient-transform") || "").trim(); const t = `translate(${dx},0)`; clone.setAttribute("gradientTransform", baseT ? `${baseT} ${t}` : t); } function applyShiftToGroup(svg, g, boxes) { if (!g || !boxes || !boxes.length) return; const gradDx = new Map(); g.querySelectorAll("[fill^='url(']").forEach(el => { const dx = dxRepresentative(el, boxes); if (dx) gradDx.set(el, dx); }); g.querySelectorAll("polygon").forEach(el => { const p = el.getAttribute("points"); if (p) el.setAttribute("points", shiftPointsAttr(p, boxes)); }); g.querySelectorAll("polyline").forEach(el => { const p = el.getAttribute("points"); if (p) el.setAttribute("points", shiftPointsAttr(p, boxes)); }); g.querySelectorAll("rect").forEach(el => shiftRect(el, boxes)); g.querySelectorAll("path").forEach(el => { const d = el.getAttribute("d"); if (d) el.setAttribute("d", shiftPathD(d, boxes)); }); for (const [el, dx] of gradDx.entries()) applyGradientShift(svg, el, dx); } function buildAndApplyShifts() { const svg = svgRoot(); if (!svg) return; hideNeverShow(svg); const n = vakCount(); // BI shiftboxen per vak const items = []; for (let i = 1; i <= n; i++) { const gL = svg.querySelector(`#${esc(`vak-${i}-BI-shiftbox-L`)}`); const gR = svg.querySelector(`#${esc(`vak-${i}-BI-shiftbox-R`)}`); items.push({ key: `BI:${i}`, gEl: gL, exprNow: () => prefixLeft(n, i) - Sponning() }); items.push({ key: `BI:${i}`, gEl: gR, exprNow: () => prefixLeft(n, i) + BI_DMI_scaled(n, i) + Sponning() }); } const boxesMap = buildBoxes(items); // Apply geometry shifts alleen op onze BI-variantgroepen for (let i = 1; i <= n; i++) { const boxes = boxesMap.get(`BI:${i}`) || []; if (!boxes.length) continue; for (const suf of BI_SUFFIXES) { const g = svg.querySelector(`#${esc(`vak-${i}-BI-${suf}`)}`); if (g) applyShiftToGroup(svg, g, boxes); } } // Verplaats shiftbox rects (blijven hidden) moveBoxes(boxesMap); hideNeverShow(svg); } /****************************************************************** * Scheduling ******************************************************************/ let updateQueued = false; function runUpdateAll() { updateQueued = false; updateVisibility(); buildAndApplyShifts(); } function updateAll() { if (updateQueued) return; updateQueued = true; queueMicrotask(runUpdateAll); } /****************************************************************** * Bind listeners (Elementor friendly) ******************************************************************/ function bind(root = document) { // VVI selects root.querySelectorAll(`select[name^="form_fields["][name*="VVI"]`).forEach(el => { if (el.dataset.biBound === "1") return; el.dataset.biBound = "1"; el.addEventListener("change", updateAll); el.addEventListener("input", updateAll); }); // all numeric inputs (LI/RI/TI*/DMI*/sponning etc.) root.querySelectorAll(`input[id^="form-field-"]`).forEach(el => { if (el.dataset.biShiftBound === "1") return; el.dataset.biShiftBound = "1"; el.addEventListener("change", updateAll); el.addEventListener("input", updateAll); }); // vakken radios root.querySelectorAll(`input[type="radio"][name="form_fields[vakken]"]`).forEach(el => { if (el.dataset.biVakkenBound === "1") return; el.dataset.biVakkenBound = "1"; el.addEventListener("change", updateAll); el.addEventListener("input", updateAll); }); } function boot() { bind(document); runUpdateAll(); const mo = new MutationObserver(muts => { let hasNewHtml = false; for (const mu of muts) { mu.addedNodes.forEach(node => { if (!(node instanceof HTMLElement)) return; hasNewHtml = true; bind(node); }); } if (hasNewHtml) { updateAll(); } }); mo.observe(document.documentElement, { childList: true, subtree: true }); } if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", boot); else boot(); // Optional: reset baselines (als andere scripts eerst iets aanpassen) window.rebaseAlleVakkenBI = () => { const svg = svgRoot(); if (!svg) return; svg.querySelectorAll(`[id*="-BI-shiftbox-"] rect`).forEach(r => { r.dataset.baseX = String(Number(r.getAttribute("x") ?? "0")); delete r.dataset.baseExpr; }); runUpdateAll(); }; })();
//// VAKVULLING BI #ALLE-VAKKEN //// (() => { const SCALE = 3.84615384615; const SVG_ID = "alle-vakken"; // VVI value -> BI suffix const VVI_TO_SUFFIX = { "dubbelglas": "2glas", "tripleglas": "3glas", "paneel": "paneel", "glaspaneel": "glaspaneel", "deur": "deur", "kaderdeur": "kaderdeur", "dubbelkaderdeur": "dubbelkaderdeur", "draaikiep": "draaikiep", "vul in": null }; const BI_SUFFIXES = ["2glas","3glas","paneel","glaspaneel","deur","kaderdeur" ,"dubbelkaderdeur","draaikiep"]; const esc = (s) => CSS.escape(s); function svgRoot() { return document.querySelector(`svg#${esc(SVG_ID)}`); } function setDisp(el, v) { if (el) el.setAttribute("display", v); } function ensureDefs(svg) { let defs = svg.querySelector("defs"); if (!defs) { defs = document.createElementNS("http://www.w3.org/2000/svg", "defs"); svg.insertBefore(defs, svg.firstChild); } return defs; } function readNumById(id) { const el = document.getElementById(id); if (!el) return null; const v = (el.value ?? "").toString().trim().replace(",", "."); const n = Number(v); return Number.isFinite(n) ? n : 0; } // Prefer prefixA, else prefixB, else 0 function numField(prefixA, prefixB, suffix) { const a = readNumById(`form-field-${prefixA}${suffix}`); if (a != null) return a; const b = readNumById(`form-field-${prefixB}${suffix}`); if (b != null) return b; return 0; } function vakCount() { const checked = document.querySelector('input[type="radio"][name="form_fields[vakken]"]:checked'); if (checked && checked.value) { const m = String(checked.value).match(/([1-5])/); if (m) return Number(m[1]); } return 5; } // VVI select by name form_fields[VVI] function getVVI(n, i) { const sel = document.querySelector(`select[name="form_fields[${n}VVI${i}]"]`); return sel ? String(sel.value || "") : "vul in"; } function hideNeverShow(svg) { // BI shiftboxen nooit tonen svg.querySelectorAll(`[id^="vak-"][id*="-BI-shiftbox-"]`).forEach(el => setDisp(el, "none")); } function hideAllBIVariants(svg, i) { for (const suf of BI_SUFFIXES) { setDisp(svg.querySelector(`#${esc(`vak-${i}-BI-${suf}`)}`), "none"); } } function showGroupAndNestedGs(groupEl) { if (!groupEl) return; // shiftboxen sowieso niet tonen if (String(groupEl.id).includes("-shiftbox-")) { setDisp(groupEl, "none"); return; } setDisp(groupEl, "block"); groupEl.querySelectorAll("g").forEach(g => { if (String(g.id).includes("-shiftbox-")) setDisp(g, "none"); else setDisp(g, "block"); }); } function updateVisibility() { const svg = svgRoot(); if (!svg) return; hideNeverShow(svg); const n = vakCount(); // reset alleen onze BI-groepen for (let i = 1; i <= 5; i++) hideAllBIVariants(svg, i); // show voor i<=n op basis van VVI for (let i = 1; i <= n; i++) { const value = getVVI(n, i); const suffix = VVI_TO_SUFFIX[value]; if (!suffix) continue; const g = svg.querySelector(`#${esc(`vak-${i}-BI-${suffix}`)}`); showGroupAndNestedGs(g); } hideNeverShow(svg); } /****************************************************************** * BI position building (LI + DMI/TI cumulatief) * Alleen DMI geschaald: DMI/SCALE ******************************************************************/ function BI_LI(n) { return numField(`${n}`, "5", "LI"); } function BI_TI(n, i) { return numField(`${n}`, "5", `TI${i}`); } function BI_DMI_scaled(n, i) { return (numField(`${n}`, "5", `DMI${i}`) / SCALE); } function Sponning() { return (readNumById("form-field-BI_sponningbreedte") ?? 0); } function prefixLeft(n, i) { let x = BI_LI(n); if (i >= 2) x += BI_DMI_scaled(n,1) + BI_TI(n,1); if (i >= 3) x += BI_DMI_scaled(n,2) + BI_TI(n,2); if (i >= 4) x += BI_DMI_scaled(n,3) + BI_TI(n,3); if (i >= 5) x += BI_DMI_scaled(n,4) + BI_TI(n,4); return x; } /****************************************************************** * Shiftbox delta-engine ******************************************************************/ function desiredXFromDelta(rect, exprNow) { const curX = Number(rect.getAttribute("x") ?? "0"); if (rect.dataset.baseX == null) rect.dataset.baseX = String(curX); if (rect.dataset.baseExpr == null) rect.dataset.baseExpr = String(exprNow); const baseX = Number(rect.dataset.baseX); const baseExpr = Number(rect.dataset.baseExpr); return baseX + (exprNow - baseExpr); } function boxFromShiftRect(rect, exprNow) { const curX = Number(rect.getAttribute("x") ?? "0"); const curY = Number(rect.getAttribute("y") ?? "0"); const w = Number(rect.getAttribute("width") ?? "0"); const h = Number(rect.getAttribute("height") ?? "0"); const desiredX = desiredXFromDelta(rect, exprNow); return { rect, desiredX, dxInc: desiredX - curX, minX: curX, maxX: curX + w, minY: curY, maxY: curY + h, }; } function buildBoxes(items) { const map = new Map(); // key -> boxes[] for (const it of items) { if (!it.gEl) continue; const r = it.gEl.querySelector("rect"); if (!r) continue; const b = boxFromShiftRect(r, it.exprNow()); if (!map.has(it.key)) map.set(it.key, []); map.get(it.key).push(b); } return map; } function moveBoxes(map) { for (const boxes of map.values()) { for (const b of boxes) b.rect.setAttribute("x", b.desiredX); } } function dxForPoint(x, y, boxes) { let dx = 0; for (const b of boxes) { if (x >= b.minX && x <= b.maxX && y >= b.minY && y <= b.maxY) dx += b.dxInc; } return dx; } /****************************************************************** * Geometry shifters ******************************************************************/ function shiftPointsAttr(pointsStr, boxes) { const nums = pointsStr.trim().replace(/,/g, " ").split(/\s+/).filter(Boolean).map(Number); if (nums.length < 2 || nums.some(n => !Number.isFinite(n))) return pointsStr; const out = []; for (let i = 0; i < nums.length - 1; i += 2) { const x = nums[i], y = nums[i + 1]; out.push(String(x + dxForPoint(x, y, boxes)), String(y)); } return out.join(" "); } function tokenizePath(d) { return d.match(/[a-zA-Z]|-?\d*\.?\d+(?:e[-+]?\d+)?/g) || []; } function shiftPathD(d, boxes) { const t = tokenizePath(d); if (!t.length) return d; let i = 0, cmd = "", x = 0, y = 0, x0 = 0, y0 = 0; const out = []; const isCmd = tok => /^[a-zA-Z]$/.test(tok); const nextNum = () => Number(t[i++]); const push = v => out.push(v); const shiftAbs = (px, py) => [px + dxForPoint(px, py, boxes), py]; while (i < t.length) { if (isCmd(t[i])) cmd = t[i++]; const rel = (cmd === cmd.toLowerCase()); const C = cmd.toUpperCase(); if (C === "M") { push("M"); const px = nextNum(), py = nextNum(); const ax = rel ? x + px : px, ay = rel ? y + py : py; const p = shiftAbs(ax, ay); push(p[0]); push(p[1]); x = ax; y = ay; x0 = ax; y0 = ay; while (i < t.length && !isCmd(t[i])) { push("L"); const lx = nextNum(), ly = nextNum(); const ax2 = rel ? x + lx : lx, ay2 = rel ? y + ly : ly; const p2 = shiftAbs(ax2, ay2); push(p2[0]); push(p2[1]); x = ax2; y = ay2; } continue; } if (C === "L") { push("L"); while (i < t.length && !isCmd(t[i])) { const px = nextNum(), py = nextNum(); const ax = rel ? x + px : px, ay = rel ? y + py : py; const p = shiftAbs(ax, ay); push(p[0]); push(p[1]); x = ax; y = ay; } continue; } if (C === "H") { push("H"); while (i < t.length && !isCmd(t[i])) { const px = nextNum(); const ax = rel ? x + px : px; const p = shiftAbs(ax, y); push(p[0]); x = ax; } continue; } if (C === "V") { push("V"); while (i < t.length && !isCmd(t[i])) { const py = nextNum(); const ay = rel ? y + py : py; push(ay); y = ay; } continue; } if (C === "C") { push("C"); while (i < t.length && !isCmd(t[i])) { const x1 = nextNum(), y1 = nextNum(), x2 = nextNum(), y2 = nextNum(), x3 = nextNum(), y3 = nextNum(); const ax1 = rel ? x + x1 : x1, ay1 = rel ? y + y1 : y1; const ax2 = rel ? x + x2 : x2, ay2 = rel ? y + y2 : y2; const ax3 = rel ? x + x3 : x3, ay3 = rel ? y + y3 : y3; const p1 = shiftAbs(ax1, ay1); const p2 = shiftAbs(ax2, ay2); const p3 = shiftAbs(ax3, ay3); push(p1[0]); push(p1[1]); push(p2[0]); push(p2[1]); push(p3[0]); push(p3[1]); x = ax3; y = ay3; } continue; } if (C === "S") { push("S"); while (i < t.length && !isCmd(t[i])) { const x2 = nextNum(), y2 = nextNum(), x3 = nextNum(), y3 = nextNum(); const ax2 = rel ? x + x2 : x2, ay2 = rel ? y + y2 : y2; const ax3 = rel ? x + x3 : x3, ay3 = rel ? y + y3 : y3; const p2 = shiftAbs(ax2, ay2); const p3 = shiftAbs(ax3, ay3); push(p2[0]); push(p2[1]); push(p3[0]); push(p3[1]); x = ax3; y = ay3; } continue; } if (C === "Q") { push("Q"); while (i < t.length && !isCmd(t[i])) { const x1 = nextNum(), y1 = nextNum(), x2 = nextNum(), y2 = nextNum(); const ax1 = rel ? x + x1 : x1, ay1 = rel ? y + y1 : y1; const ax2 = rel ? x + x2 : x2, ay2 = rel ? y + y2 : y2; const p1 = shiftAbs(ax1, ay1); const p2 = shiftAbs(ax2, ay2); push(p1[0]); push(p1[1]); push(p2[0]); push(p2[1]); x = ax2; y = ay2; } continue; } if (C === "T") { push("T"); while (i < t.length && !isCmd(t[i])) { const x2 = nextNum(), y2 = nextNum(); const ax2 = rel ? x + x2 : x2, ay2 = rel ? y + y2 : y2; const p2 = shiftAbs(ax2, ay2); push(p2[0]); push(p2[1]); x = ax2; y = ay2; } continue; } if (C === "A") { push("A"); while (i < t.length && !isCmd(t[i])) { const rx = nextNum(), ry = nextNum(), rot = nextNum(), laf = nextNum(), sf = nextNum(), x2 = nextNum(), y2 = nextNum(); const ax2 = rel ? x + x2 : x2, ay2 = rel ? y + y2 : y2; const p2 = shiftAbs(ax2, ay2); push(rx); push(ry); push(rot); push(laf); push(sf); push(p2[0]); push(p2[1]); x = ax2; y = ay2; } continue; } if (C === "Z") { push("Z"); x = x0; y = y0; continue; } return d; } return out.join(" "); } function shiftRect(el, boxes) { const x = Number(el.getAttribute("x")); const y = Number(el.getAttribute("y")); const w = Number(el.getAttribute("width")); if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(w)) return; const h = Number(el.getAttribute("height")); const yMid = Number.isFinite(h) ? (y + h / 2) : y; const xL2 = x + dxForPoint(x, yMid, boxes); const xR = x + w; const xR2 = xR + dxForPoint(xR, yMid, boxes); el.setAttribute("x", Math.min(xL2, xR2)); el.setAttribute("width", Math.abs(xR2 - xL2)); } /****************************************************************** * Gradient shifting (zelfde als BU) ******************************************************************/ function parseFillUrl(fill) { const m = String(fill).match(/^url\((["']?)#([^)'" ]+)\1\)$/); return m ? m[2] : null; } function isClearlyUserSpace(gradEl) { const units = (gradEl.getAttribute("gradientUnits") || "").trim(); if (units === "userSpaceOnUse") return true; if (units) return false; for (const a of ["x1","y1","x2","y2","cx","cy","fx","fy","r"]) { const v = gradEl.getAttribute(a); if (v == null) continue; const n = Number(String(v).trim().replace(",", ".")); if (!Number.isFinite(n)) continue; if (n < -0.001 || n > 1.001) return true; } return false; } let gradCloneCounter = 0; function ensureGradientClone(svg, el, baseGradId) { const defs = ensureDefs(svg); if (!el.dataset.gradCloneId) { gradCloneCounter += 1; el.dataset.gradCloneId = `${baseGradId}__clone__${gradCloneCounter}`; } const cloneId = el.dataset.gradCloneId; let clone = defs.querySelector(`#${esc(cloneId)}`); if (!clone) { const orig = svg.querySelector(`#${esc(baseGradId)}`); if (!orig) return null; if (!isClearlyUserSpace(orig)) return null; clone = orig.cloneNode(true); clone.setAttribute("id", cloneId); clone.setAttribute("gradientUnits", "userSpaceOnUse"); clone.setAttribute("data-base-gradient-transform", orig.getAttribute("gradientTransform") || ""); defs.appendChild(clone); } el.setAttribute("fill", `url(#${cloneId})`); return clone; } function dxRepresentative(el, boxes) { const tag = el.tagName.toLowerCase(); if (tag === "rect") { const x = Number(el.getAttribute("x")); const y = Number(el.getAttribute("y")); const h = Number(el.getAttribute("height")); const yMid = Number.isFinite(h) ? (y + h / 2) : y; if (!Number.isFinite(x) || !Number.isFinite(yMid)) return 0; return dxForPoint(x, yMid, boxes); } if (tag === "polygon" || tag === "polyline") { const pts = (el.getAttribute("points") || "").trim().replace(/,/g, " ").split(/\s+/).filter(Boolean).map(Number); if (pts.length < 2 || pts.some(n => !Number.isFinite(n))) return 0; return dxForPoint(pts[0], pts[1], boxes); } if (tag === "path") { const d = el.getAttribute("d") || ""; const nums = d.match(/-?\d*\.?\d+(?:e[-+]?\d+)?/g); if (!nums || nums.length < 2) return 0; const x = Number(nums[0]), y = Number(nums[1]); if (!Number.isFinite(x) || !Number.isFinite(y)) return 0; return dxForPoint(x, y, boxes); } return 0; } function applyGradientShift(svg, el, dx) { const fill = el.getAttribute("fill"); if (!fill) return; const base = parseFillUrl(fill); if (!base) return; const clone = ensureGradientClone(svg, el, base); if (!clone) return; const baseT = (clone.getAttribute("data-base-gradient-transform") || "").trim(); const t = `translate(${dx},0)`; clone.setAttribute("gradientTransform", baseT ? `${baseT} ${t}` : t); } function applyShiftToGroup(svg, g, boxes) { if (!g || !boxes || !boxes.length) return; const gradDx = new Map(); g.querySelectorAll("[fill^='url(']").forEach(el => { const dx = dxRepresentative(el, boxes); if (dx) gradDx.set(el, dx); }); g.querySelectorAll("polygon").forEach(el => { const p = el.getAttribute("points"); if (p) el.setAttribute("points", shiftPointsAttr(p, boxes)); }); g.querySelectorAll("polyline").forEach(el => { const p = el.getAttribute("points"); if (p) el.setAttribute("points", shiftPointsAttr(p, boxes)); }); g.querySelectorAll("rect").forEach(el => shiftRect(el, boxes)); g.querySelectorAll("path").forEach(el => { const d = el.getAttribute("d"); if (d) el.setAttribute("d", shiftPathD(d, boxes)); }); for (const [el, dx] of gradDx.entries()) applyGradientShift(svg, el, dx); } function buildAndApplyShifts() { const svg = svgRoot(); if (!svg) return; hideNeverShow(svg); const n = vakCount(); // BI shiftboxen per vak const items = []; for (let i = 1; i <= n; i++) { const gL = svg.querySelector(`#${esc(`vak-${i}-BI-shiftbox-L`)}`); const gR = svg.querySelector(`#${esc(`vak-${i}-BI-shiftbox-R`)}`); items.push({ key: `BI:${i}`, gEl: gL, exprNow: () => prefixLeft(n, i) - Sponning() }); items.push({ key: `BI:${i}`, gEl: gR, exprNow: () => prefixLeft(n, i) + BI_DMI_scaled(n, i) + Sponning() }); } const boxesMap = buildBoxes(items); // Apply geometry shifts alleen op onze BI-variantgroepen for (let i = 1; i <= n; i++) { const boxes = boxesMap.get(`BI:${i}`) || []; if (!boxes.length) continue; for (const suf of BI_SUFFIXES) { const g = svg.querySelector(`#${esc(`vak-${i}-BI-${suf}`)}`); if (g) applyShiftToGroup(svg, g, boxes); } } // Verplaats shiftbox rects (blijven hidden) moveBoxes(boxesMap); hideNeverShow(svg); } function updateAll() { updateVisibility(); buildAndApplyShifts(); } /****************************************************************** * Bind listeners (Elementor friendly) ******************************************************************/ function bind(root = document) { // VVI selects root.querySelectorAll(`select[name^="form_fields["][name*="VVI"]`).forEach(el => { if (el.dataset.biBound === "1") return; el.dataset.biBound = "1"; el.addEventListener("change", updateAll); el.addEventListener("input", updateAll); }); // all numeric inputs (LI/RI/TI*/DMI*/sponning etc.) root.querySelectorAll(`input[id^="form-field-"]`).forEach(el => { if (el.dataset.biShiftBound === "1") return; el.dataset.biShiftBound = "1"; el.addEventListener("change", updateAll); el.addEventListener("input", updateAll); }); // vakken radios root.querySelectorAll(`input[type="radio"][name="form_fields[vakken]"]`).forEach(el => { if (el.dataset.biVakkenBound === "1") return; el.dataset.biVakkenBound = "1"; el.addEventListener("change", updateAll); el.addEventListener("input", updateAll); }); } function boot() { bind(document); updateAll(); const mo = new MutationObserver(muts => { for (const mu of muts) { mu.addedNodes.forEach(node => { if (!(node instanceof HTMLElement)) return; bind(node); }); } updateAll(); }); mo.observe(document.documentElement, { childList: true, subtree: true }); } if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", boot); else boot(); // Optional: reset baselines (als andere scripts eerst iets aanpassen) window.rebaseAlleVakkenBI = () => { const svg = svgRoot(); if (!svg) return; svg.querySelectorAll(`[id*="-BI-shiftbox-"] rect`).forEach(r => { r.dataset.baseX = String(Number(r.getAttribute("x") ?? "0")); delete r.dataset.baseExpr; }); updateAll(); }; })();
//// VAKVULLING BU/HS #ALLE-VAKKEN //// (() => { const SCALE = 3.84615384615; const SVG_ID = "alle-vakken"; // BU mapping (VVU -> BU variant) const VVU_TO_BU_SUFFIX = { "dubbelglas": "2glas-BU", "tripleglas": "3glas-BU", "paneel": "paneel", "glaspaneel": "glaspaneel", "deur": "deur", "kaderdeur": "kaderdeur", "dubbelkaderdeur": "dubbelkaderdeur", // ✅ deze 3 blijven in mapping, maar worden geometrisch als BU-BI bucket behandeld "dubbelglas binnen": "2glas-BI", "tripleglas binnen": "3glas-BI", "draaikiep": "draaikiep", "vul in": null, }; // HS mapping (HS-opties zitten óók in VVU) const VVU_TO_HS_MODE = { "linksschuivend": "LS", "rechtsschuivend": "RS", "linksrechtsschuivend": "LR", "ls": "LS", "rs": "RS", "lr": "LR", }; // ✅ BU split: BU-main vs BU-BI const BU_MAIN_SUFFIXES = ["2glas-BU","3glas-BU","paneel","glaspaneel","deur","kaderdeur","dubbelkaderdeur"]; const BU_BI_SUFFIXES = ["2glas-BI","3glas-BI","draaikiep"]; const HS_MODES = ["LS","RS","LR"]; const esc = (s) => CSS.escape(s); function svgRoot() { return document.querySelector(`svg#${esc(SVG_ID)}`); } function setDisp(el, v) { if (el) el.setAttribute("display", v); } function normalizeKey(v) { return String(v || "") .trim() .toLowerCase() .replace(/\s+/g, "") .replace(/_/g, "") .replace(/-/g, ""); } function vakCount() { const checked = document.querySelector('input[type="radio"][name="form_fields[vakken]"]:checked'); if (checked && checked.value) { const m = String(checked.value).match(/([1-5])/); if (m) return Number(m[1]); } return 5; } function readInputValueById(id) { const el = document.getElementById(id); if (!el) return null; const v = (el.value ?? "").toString().trim().replace(",", "."); const n = Number(v); return Number.isFinite(n) ? n : 0; } // Prefer prefixA, else prefixB, else 0 function numField(prefixA, prefixB, suffix) { const a = readInputValueById(`form-field-${prefixA}${suffix}`); if (a != null) return a; const b = readInputValueById(`form-field-${prefixB}${suffix}`); if (b != null) return b; return 0; } // VVU select by name form_fields[VVU] function getVVU(n, i) { const sel = document.querySelector(`select[name="form_fields[${n}VVU${i}]"]`); return sel ? String(sel.value || "") : "vul in"; } // ✅ Dorpeltype radio (pakt liefst data-original-value, anders value) function getDorpelType() { const checked = document.querySelector('input[type="radio"][name="form_fields[dorpeltype]"]:checked'); if (!checked) return null; return (checked.getAttribute("data-original-value") || checked.value || "").toString().trim(); } /****************************************************************** * ✅ NEW: HS verdiept (nok) inputs ******************************************************************/ function getRadioValueByName(name) { const el = document.querySelector(`input[type="radio"][name="${name}"]:checked`); return el ? String(el.value || "").trim().toLowerCase() : ""; } function isVerdieptVJa() { return getRadioValueByName("form_fields[verdiept_v]") === "ja"; } function isVerdieptSJa() { return getRadioValueByName("form_fields[verdiept_s]") === "ja"; } function nokV() { return (readInputValueById("form-field-nokdiktevast") ?? 0); } function nokS() { return (readInputValueById("form-field-nokdikteschuif") ?? 0); } /****************************************************************** * NEVER SHOW elements (shiftboxes + BU-LR placeholders) ******************************************************************/ function hideNeverShow(svg) { for (let i = 1; i <= 5; i++) { setDisp(svg.querySelector(`#${esc(`vak-${i}-BU-LR`)}`), "none"); } svg.querySelectorAll(`[id*="-BU-shiftbox-"]`).forEach(el => setDisp(el, "none")); svg.querySelectorAll(`[id*="-HS-shiftbox-"]`).forEach(el => setDisp(el, "none")); } /****************************************************************** * VISIBILITY: only whitelist BU & HS groups ******************************************************************/ function showGroupAndNestedGs(groupEl) { if (!groupEl) return; const deny = (id) => { const s = String(id || ""); return s.includes("-shiftbox-") || s.endsWith("-BU-LR") || s.includes("-BU-LR-"); }; if (!deny(groupEl.id)) setDisp(groupEl, "block"); groupEl.querySelectorAll("g").forEach(g => { if (deny(g.id)) setDisp(g, "none"); else setDisp(g, "block"); }); } function hideAllBU(svg, i) { for (const suf of BU_MAIN_SUFFIXES) setDisp(svg.querySelector(`#${esc(`vak-${i}-BU-${suf}`)}`), "none"); for (const suf of BU_BI_SUFFIXES) setDisp(svg.querySelector(`#${esc(`vak-${i}-BU-${suf}`)}`), "none"); } function hideAllHS(svg, i) { for (const m of HS_MODES) setDisp(svg.querySelector(`#${esc(`vak-${i}-HS-${m}`)}`), "none"); } function updateVisibility() { const svg = svgRoot(); if (!svg) return; hideNeverShow(svg); const n = vakCount(); for (let i = 1; i <= 5; i++) { hideAllBU(svg, i); hideAllHS(svg, i); } for (let i = 1; i <= n; i++) { const raw = getVVU(n, i); const key = normalizeKey(raw); const hsMode = VVU_TO_HS_MODE[key] || null; let buSuffix = Object.prototype.hasOwnProperty.call(VVU_TO_BU_SUFFIX, raw) ? VVU_TO_BU_SUFFIX[raw] : null; const dorpelType = getDorpelType(); if (dorpelType === "KVTPKU" && raw === "kaderdeur") { buSuffix = "dubbelkaderdeur"; } if (hsMode) { const g = svg.querySelector(`#${esc(`vak-${i}-HS-${hsMode}`)}`); showGroupAndNestedGs(g); } else if (buSuffix) { const g = svg.querySelector(`#${esc(`vak-${i}-BU-${buSuffix}`)}`); showGroupAndNestedGs(g); } } hideNeverShow(svg); } /****************************************************************** * SHIFTBOX ENGINE (delta vs baseline) ******************************************************************/ function desiredXFromDelta(rect, exprNow) { const curX = Number(rect.getAttribute("x") ?? "0"); if (rect.dataset.baseX == null) rect.dataset.baseX = String(curX); if (rect.dataset.baseExpr == null) rect.dataset.baseExpr = String(exprNow); const baseX = Number(rect.dataset.baseX); const baseExpr = Number(rect.dataset.baseExpr); return baseX + (exprNow - baseExpr); } function boxFromShiftRect(rect, exprNow) { const curX = Number(rect.getAttribute("x") ?? "0"); const curY = Number(rect.getAttribute("y") ?? "0"); const w = Number(rect.getAttribute("width") ?? "0"); const h = Number(rect.getAttribute("height") ?? "0"); const desiredX = desiredXFromDelta(rect, exprNow); return { rect, desiredX, dxInc: desiredX - curX, minX: curX, maxX: curX + w, minY: curY, maxY: curY + h, }; } function buildBoxes(items) { const map = new Map(); for (const it of items) { if (!it.gEl) continue; const r = it.gEl.querySelector("rect"); if (!r) continue; const b = boxFromShiftRect(r, it.exprNow()); if (!map.has(it.key)) map.set(it.key, []); map.get(it.key).push(b); } return map; } function moveBoxes(map) { for (const boxes of map.values()) { for (const b of boxes) b.rect.setAttribute("x", b.desiredX); } } function dxForPoint(x, y, boxes) { let dx = 0; for (const b of boxes) { if (x >= b.minX && x <= b.maxX && y >= b.minY && y <= b.maxY) dx += b.dxInc; } return dx; } /****************************************************************** * Geometry shifters ******************************************************************/ function shiftPointsAttr(pointsStr, boxes) { const nums = pointsStr.trim().replace(/,/g, " ").split(/\s+/).filter(Boolean).map(Number); if (nums.length < 2 || nums.some(n => !Number.isFinite(n))) return pointsStr; const out = []; for (let i = 0; i < nums.length - 1; i += 2) { const x = nums[i], y = nums[i + 1]; out.push(String(x + dxForPoint(x, y, boxes)), String(y)); } return out.join(" "); } function tokenizePath(d) { return d.match(/[a-zA-Z]|-?\d*\.?\d+(?:e[-+]?\d+)?/g) || []; } function shiftPathD(d, boxes) { const t = tokenizePath(d); if (!t.length) return d; let i = 0, cmd = "", x = 0, y = 0, x0 = 0, y0 = 0; const out = []; const isCmd = tok => /^[a-zA-Z]$/.test(tok); const nextNum = () => Number(t[i++]); const push = v => out.push(v); const shiftAbs = (px, py) => [px + dxForPoint(px, py, boxes), py]; while (i < t.length) { if (isCmd(t[i])) cmd = t[i++]; const rel = (cmd === cmd.toLowerCase()); const C = cmd.toUpperCase(); if (C === "M") { push("M"); const px = nextNum(), py = nextNum(); const ax = rel ? x + px : px, ay = rel ? y + py : py; const p = shiftAbs(ax, ay); push(p[0]); push(p[1]); x = ax; y = ay; x0 = ax; y0 = ay; while (i < t.length && !isCmd(t[i])) { push("L"); const lx = nextNum(), ly = nextNum(); const ax2 = rel ? x + lx : lx, ay2 = rel ? y + ly : ly; const p2 = shiftAbs(ax2, ay2); push(p2[0]); push(p2[1]); x = ax2; y = ay2; } continue; } if (C === "L") { push("L"); while (i < t.length && !isCmd(t[i])) { const px = nextNum(), py = nextNum(); const ax = rel ? x + px : px, ay = rel ? y + py : py; const p = shiftAbs(ax, ay); push(p[0]); push(p[1]); x = ax; y = ay; } continue; } if (C === "H") { push("H"); while (i < t.length && !isCmd(t[i])) { const px = nextNum(); const ax = rel ? x + px : px; const p = shiftAbs(ax, y); push(p[0]); x = ax; } continue; } if (C === "V") { push("V"); while (i < t.length && !isCmd(t[i])) { const py = nextNum(); const ay = rel ? y + py : py; push(ay); y = ay; } continue; } if (C === "C") { push("C"); while (i < t.length && !isCmd(t[i])) { const x1 = nextNum(), y1 = nextNum(), x2 = nextNum(), y2 = nextNum(), x3 = nextNum(), y3 = nextNum(); const ax1 = rel ? x + x1 : x1, ay1 = rel ? y + y1 : y1; const ax2 = rel ? x + x2 : x2, ay2 = rel ? y + y2 : y2; const ax3 = rel ? x + x3 : x3, ay3 = rel ? y + y3 : y3; const p1 = shiftAbs(ax1, ay1); const p2 = shiftAbs(ax2, ay2); const p3 = shiftAbs(ax3, ay3); push(p1[0]); push(p1[1]); push(p2[0]); push(p2[1]); push(p3[0]); push(p3[1]); x = ax3; y = ay3; } continue; } if (C === "S") { push("S"); while (i < t.length && !isCmd(t[i])) { const x2 = nextNum(), y2 = nextNum(), x3 = nextNum(), y3 = nextNum(); const ax2 = rel ? x + x2 : x2, ay2 = rel ? y + y2 : y2; const ax3 = rel ? x + x3 : x3, ay3 = rel ? y + y3 : y3; const p2 = shiftAbs(ax2, ay2); const p3 = shiftAbs(ax3, ay3); push(p2[0]); push(p2[1]); push(p3[0]); push(p3[1]); x = ax3; y = ay3; } continue; } if (C === "Q") { push("Q"); while (i < t.length && !isCmd(t[i])) { const x1 = nextNum(), y1 = nextNum(), x2 = nextNum(), y2 = nextNum(); const ax1 = rel ? x + x1 : x1, ay1 = rel ? y + y1 : y1; const ax2 = rel ? x + x2 : x2, ay2 = rel ? y + y2 : y2; const p1 = shiftAbs(ax1, ay1); const p2 = shiftAbs(ax2, ay2); push(p1[0]); push(p1[1]); push(p2[0]); push(p2[1]); x = ax2; y = ay2; } continue; } if (C === "T") { push("T"); while (i < t.length && !isCmd(t[i])) { const x2 = nextNum(), y2 = nextNum(); const ax2 = rel ? x + x2 : x2, ay2 = rel ? y + y2 : y2; const p2 = shiftAbs(ax2, ay2); push(p2[0]); push(p2[1]); x = ax2; y = ay2; } continue; } if (C === "A") { push("A"); while (i < t.length && !isCmd(t[i])) { const rx = nextNum(), ry = nextNum(), rot = nextNum(), laf = nextNum(), sf = nextNum(), x2 = nextNum(), y2 = nextNum(); const ax2 = rel ? x + x2 : x2, ay2 = rel ? y + y2 : y2; const p2 = shiftAbs(ax2, ay2); push(rx); push(ry); push(rot); push(laf); push(sf); push(p2[0]); push(p2[1]); x = ax2; y = ay2; } continue; } if (C === "Z") { push("Z"); x = x0; y = y0; continue; } return d; } return out.join(" "); } function shiftRect(el, boxes) { const x = Number(el.getAttribute("x")); const y = Number(el.getAttribute("y")); const w = Number(el.getAttribute("width")); if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(w)) return; const h = Number(el.getAttribute("height")); const yMid = Number.isFinite(h) ? (y + h / 2) : y; const xL2 = x + dxForPoint(x, yMid, boxes); const xR = x + w; const xR2 = xR + dxForPoint(xR, yMid, boxes); el.setAttribute("x", Math.min(xL2, xR2)); el.setAttribute("width", Math.abs(xR2 - xL2)); } // ---- gradients ---- function ensureDefs(svg) { let defs = svg.querySelector("defs"); if (!defs) { defs = document.createElementNS("http://www.w3.org/2000/svg", "defs"); svg.insertBefore(defs, svg.firstChild); } return defs; } function parseFillUrl(fill) { const m = String(fill).match(/^url\((["']?)#([^)'" ]+)\1\)$/); return m ? m[2] : null; } function getNumericAttr(el, name) { const v = el.getAttribute(name); if (v == null) return null; const n = Number(String(v).trim().replace(",", ".")); return Number.isFinite(n) ? n : null; } function isClearlyUserSpace(gradEl) { const units = (gradEl.getAttribute("gradientUnits") || "").trim(); if (units === "userSpaceOnUse") return true; if (units) return false; for (const a of ["x1","y1","x2","y2","cx","cy","fx","fy","r"]) { const n = getNumericAttr(gradEl, a); if (n == null) continue; if (n < -0.001 || n > 1.001) return true; } return false; } let gradCloneCounter = 0; function ensureGradientClone(svg, el, baseGradId) { const defs = ensureDefs(svg); if (!el.dataset.gradCloneId) { gradCloneCounter += 1; el.dataset.gradCloneId = `${baseGradId}__clone__${gradCloneCounter}`; } const cloneId = el.dataset.gradCloneId; let clone = defs.querySelector(`#${esc(cloneId)}`); if (!clone) { const orig = svg.querySelector(`#${esc(baseGradId)}`); if (!orig) return null; if (!isClearlyUserSpace(orig)) return null; clone = orig.cloneNode(true); clone.setAttribute("id", cloneId); clone.setAttribute("gradientUnits", "userSpaceOnUse"); clone.setAttribute("data-base-gradient-transform", orig.getAttribute("gradientTransform") || ""); defs.appendChild(clone); } el.setAttribute("fill", `url(#${cloneId})`); return clone; } function dxRepresentative(el, boxes) { const tag = el.tagName.toLowerCase(); if (tag === "rect") { const x = Number(el.getAttribute("x")); const y = Number(el.getAttribute("y")); const h = Number(el.getAttribute("height")); const yMid = Number.isFinite(h) ? (y + h / 2) : y; if (!Number.isFinite(x) || !Number.isFinite(yMid)) return 0; return dxForPoint(x, yMid, boxes); } if (tag === "polygon" || tag === "polyline") { const pts = (el.getAttribute("points") || "").trim().replace(/,/g, " ").split(/\s+/).filter(Boolean).map(Number); if (pts.length < 2 || pts.some(n => !Number.isFinite(n))) return 0; return dxForPoint(pts[0], pts[1], boxes); } if (tag === "path") { const d = el.getAttribute("d") || ""; const nums = d.match(/-?\d*\.?\d+(?:e[-+]?\d+)?/g); if (!nums || nums.length < 2) return 0; const x = Number(nums[0]), y = Number(nums[1]); if (!Number.isFinite(x) || !Number.isFinite(y)) return 0; return dxForPoint(x, y, boxes); } return 0; } function applyGradientShift(svg, el, dx) { const fill = el.getAttribute("fill"); if (!fill) return; const base = parseFillUrl(fill); if (!base) return; const clone = ensureGradientClone(svg, el, base); if (!clone) return; const baseT = (clone.getAttribute("data-base-gradient-transform") || "").trim(); const t = `translate(${dx},0)`; clone.setAttribute("gradientTransform", baseT ? `${baseT} ${t}` : t); } function applyShiftToGroup(svg, g, boxes) { if (!g || !boxes || !boxes.length) return; const gradDx = new Map(); g.querySelectorAll("[fill^='url(']").forEach(el => { const dx = dxRepresentative(el, boxes); if (dx) gradDx.set(el, dx); }); g.querySelectorAll("polygon").forEach(el => { const p = el.getAttribute("points"); if (p) el.setAttribute("points", shiftPointsAttr(p, boxes)); }); g.querySelectorAll("polyline").forEach(el => { const p = el.getAttribute("points"); if (p) el.setAttribute("points", shiftPointsAttr(p, boxes)); }); g.querySelectorAll("rect").forEach(el => shiftRect(el, boxes)); g.querySelectorAll("path").forEach(el => { const d = el.getAttribute("d"); if (d) el.setAttribute("d", shiftPathD(d, boxes)); }); for (const [el, dx] of gradDx.entries()) applyGradientShift(svg, el, dx); } /****************************************************************** * SHIFT CALCS ******************************************************************/ // BU uses current n* fields (fallback to 5*) function BU_LU(n) { return numField(`${n}`, "5", "LU"); } function BU_TU(n, i) { return numField(`${n}`, "5", `TU${i}`); } function BU_DMU(n, i) { return (numField(`${n}`, "5", `DMU${i}`) / SCALE); } function buPrefixLeft(n, i) { let x = BU_LU(n); if (i >= 2) x += BU_DMU(n,1) + BU_TU(n,1); if (i >= 3) x += BU_DMU(n,2) + BU_TU(n,2); if (i >= 4) x += BU_DMU(n,3) + BU_TU(n,3); if (i >= 5) x += BU_DMU(n,4) + BU_TU(n,4); return x; } // ✅ dynamic sponning per vak, based on current VVU function sponningBUDefault() { return readInputValueById("form-field-BU_sponningbreedte") ?? 0; } function sponningWissel() { return readInputValueById("form-field-wisselsponningbreedte") ?? 0; } function sponningDraaikiep() { return readInputValueById("form-field-draaikiepsponningbreedte") ?? 0; } function isVVU_BI(raw) { return raw === "dubbelglas binnen" || raw === "tripleglas binnen"; } function isVVU_Draaikiep(raw) { return raw === "draaikiep"; } function sponningForVak(n, i) { const raw = getVVU(n, i); if (isVVU_BI(raw)) return sponningWissel(); if (isVVU_Draaikiep(raw)) return sponningDraaikiep(); return sponningBUDefault(); } // HS uses current n* fields (fallback to 5*) function HS_LU(n) { return numField(`${n}`, "5", "LU"); } function HS_TU(n, i) { return numField(`${n}`, "5", `TU${i}`); } function HS_DMU(n, i) { return (numField(`${n}`, "5", `DMU${i}`) / SCALE); } // scaled for positions function hsPrefixLeft(n, i) { let x = HS_LU(n); if (i >= 2) x += HS_DMU(n,1) + HS_TU(n,1); if (i >= 3) x += HS_DMU(n,2) + HS_TU(n,2); if (i >= 4) x += HS_DMU(n,3) + HS_TU(n,3); if (i >= 5) x += HS_DMU(n,4) + HS_TU(n,4); return x; } function buildAndApplyShifts() { const svg = svgRoot(); if (!svg) return; hideNeverShow(svg); const n = vakCount(); // ---- BU shiftboxes (vak-i-BU-shiftbox-L/R) with dynamic sponning const buItems = []; for (let i = 1; i <= n; i++) { const gL = svg.querySelector(`#${esc(`vak-${i}-BU-shiftbox-L`)}`); const gR = svg.querySelector(`#${esc(`vak-${i}-BU-shiftbox-R`)}`); buItems.push({ key:`BU:${i}`, gEl:gL, exprNow: () => buPrefixLeft(n,i) - sponningForVak(n,i) }); buItems.push({ key:`BU:${i}`, gEl:gR, exprNow: () => buPrefixLeft(n,i) + BU_DMU(n,i) + sponningForVak(n,i) }); } const buBoxes = buildBoxes(buItems); // ---- HS shiftboxes (incl. mm-threshold corrections + verdiept nok-correcties) const hsItems = []; for (let i = 1; i <= n; i++) { const left = () => hsPrefixLeft(n, i); const w = () => HS_DMU(n, i); // scaled for positions const dmuMm = () => numField(`${n}`, "5", `DMU${i}`); // raw mm for thresholds const corr200 = () => (dmuMm() >= 200 ? 31.5 : 0); // for L/R const corr400 = () => (dmuMm() >= 400 ? 47 : 0); // for L1/R1/T1/T3 // Determine HS mode for this vak const rawVVU = getVVU(n, i); const mode = VVU_TO_HS_MODE[normalizeKey(rawVVU)] || null; // ✅ verdiept logic (scaled) const vOn = () => isVerdieptVJa(); const sOn = () => isVerdieptSJa(); const nv = () => nokV(); const ns = () => nokS(); // LS/RS set (L, T, R) // Rules: // - LS + verdiept_v=ja => L - nokV // - RS + verdiept_v=ja => R + nokV // - LS + verdiept_s=ja => R + nokS // - RS + verdiept_s=ja => L - nokS const extraL = () => { let dx = 0; if (mode === "LS" && vOn()) dx += -nv(); if (mode === "RS" && sOn()) dx += -ns(); return dx; }; const extraR = () => { let dx = 0; if (mode === "RS" && vOn()) dx += +nv(); if (mode === "LS" && sOn()) dx += +ns(); return dx; }; hsItems.push({ key:`HS_LTR:${i}`, gEl: svg.querySelector(`#${esc(`vak-${i}-HS-shiftbox-L`)}`), exprNow: () => left() + corr200() + extraL() }); hsItems.push({ key:`HS_LTR:${i}`, gEl: svg.querySelector(`#${esc(`vak-${i}-HS-shiftbox-T`)}`), exprNow: () => left() + (w()/4)*2 }); hsItems.push({ key:`HS_LTR:${i}`, gEl: svg.querySelector(`#${esc(`vak-${i}-HS-shiftbox-R`)}`), exprNow: () => left() + w() - corr200() + extraR() }); // LR set (L1, T1..T3, R1) // Rule: // - LR + verdiept_v=ja => L1 - nokV, R1 + nokV const extraL1 = () => (mode === "LR" && vOn()) ? (-nv()) : 0; const extraR1 = () => (mode === "LR" && vOn()) ? (+nv()) : 0; hsItems.push({ key:`HS_LR:${i}`, gEl: svg.querySelector(`#${esc(`vak-${i}-HS-shiftbox-L1`)}`), exprNow: () => left() + corr400() + extraL1() }); hsItems.push({ key:`HS_LR:${i}`, gEl: svg.querySelector(`#${esc(`vak-${i}-HS-shiftbox-T1`)}`), exprNow: () => left() + (w()/4)*1 + (corr400()/2) }); hsItems.push({ key:`HS_LR:${i}`, gEl: svg.querySelector(`#${esc(`vak-${i}-HS-shiftbox-T2`)}`), exprNow: () => left() + (w()/4)*2 }); hsItems.push({ key:`HS_LR:${i}`, gEl: svg.querySelector(`#${esc(`vak-${i}-HS-shiftbox-T3`)}`), exprNow: () => left() + (w()/4)*3 - (corr400()/2) }); hsItems.push({ key:`HS_LR:${i}`, gEl: svg.querySelector(`#${esc(`vak-${i}-HS-shiftbox-R1`)}`), exprNow: () => left() + w() - corr400() + extraR1() }); } const hsBoxes = buildBoxes(hsItems); // Apply BU geometry to both BU buckets (BU-main + BU-BI) for (let i = 1; i <= n; i++) { const boxes = buBoxes.get(`BU:${i}`) || []; if (!boxes.length) continue; for (const suf of BU_MAIN_SUFFIXES) { const g = svg.querySelector(`#${esc(`vak-${i}-BU-${suf}`)}`); if (g) applyShiftToGroup(svg, g, boxes); } for (const suf of BU_BI_SUFFIXES) { const g = svg.querySelector(`#${esc(`vak-${i}-BU-${suf}`)}`); if (g) applyShiftToGroup(svg, g, boxes); } } // Apply HS geometry for (let i = 1; i <= n; i++) { const bLTR = hsBoxes.get(`HS_LTR:${i}`) || []; const bLR = hsBoxes.get(`HS_LR:${i}`) || []; if (bLTR.length) { const gLS = svg.querySelector(`#${esc(`vak-${i}-HS-LS`)}`); const gRS = svg.querySelector(`#${esc(`vak-${i}-HS-RS`)}`); if (gLS) applyShiftToGroup(svg, gLS, bLTR); if (gRS) applyShiftToGroup(svg, gRS, bLTR); } if (bLR.length) { const gLR = svg.querySelector(`#${esc(`vak-${i}-HS-LR`)}`); if (gLR) applyShiftToGroup(svg, gLR, bLR); } } // Move shiftboxes (remain hidden) moveBoxes(buBoxes); moveBoxes(hsBoxes); hideNeverShow(svg); } /****************************************************************** * Scheduling ******************************************************************/ let updateQueued = false; let updateDeferredQueued = false; function runUpdateAll() { updateQueued = false; updateDeferredQueued = false; updateVisibility(); buildAndApplyShifts(); } function scheduleUpdateAll() { if (updateQueued) return; updateQueued = true; queueMicrotask(runUpdateAll); } function scheduleUpdateAllAfterPaint() { if (updateDeferredQueued) return; updateDeferredQueued = true; requestAnimationFrame(() => { setTimeout(() => { runUpdateAll(); }, 0); }); } function updateAll() { scheduleUpdateAll(); } /****************************************************************** * Bind listeners ******************************************************************/ function bind(root = document) { // VVU selects root.querySelectorAll(`select[name^="form_fields["][name*="VVU"]`).forEach(el => { if (el.dataset.avBound === "1") return; el.dataset.avBound = "1"; el.addEventListener("change", updateAll); el.addEventListener("input", updateAll); }); // all form-field inputs (includes: BU_sponningbreedte, wisselsponningbreedte, draaikiepsponningbreedte, DMU, TU, LU, nokdiktes, etc.) root.querySelectorAll(`input[id^="form-field-"]`).forEach(el => { if (el.dataset.avShiftBound === "1") return; el.dataset.avShiftBound = "1"; el.addEventListener("change", updateAll); el.addEventListener("input", updateAll); }); // vakken radio root.querySelectorAll(`input[type="radio"][name="form_fields[vakken]"]`).forEach(el => { if (el.dataset.avVakkenBound === "1") return; el.dataset.avVakkenBound = "1"; el.addEventListener("change", updateAll); el.addEventListener("input", updateAll); }); // dorpeltype radio root.querySelectorAll(`input[type="radio"][name="form_fields[dorpeltype]"]`).forEach(el => { if (el.dataset.avDorpeltypeBound === "1") return; el.dataset.avDorpeltypeBound = "1"; el.addEventListener("change", scheduleUpdateAllAfterPaint); el.addEventListener("input", scheduleUpdateAllAfterPaint); }); // ✅ verdiept radios (extra expliciet) root.querySelectorAll(`input[type="radio"][name="form_fields[verdiept_v]"], input[type="radio"][name="form_fields[verdiept_s]"]`).forEach(el => { if (el.dataset.avVerdieptBound === "1") return; el.dataset.avVerdieptBound = "1"; el.addEventListener("change", updateAll); el.addEventListener("input", updateAll); }); // ✅ Explicit (requested earlier) listeners – redundant but harmless ["form-field-wisselsponningbreedte","form-field-draaikiepsponningbreedte"].forEach(id => { const el = document.getElementById(id); if (!el || el.dataset.avExplicitSponningBound === "1") return; el.dataset.avExplicitSponningBound = "1"; el.addEventListener("change", updateAll); el.addEventListener("input", updateAll); }); } function boot() { bind(document); runUpdateAll(); const mo = new MutationObserver(muts => { let hasNewHtml = false; for (const mu of muts) { mu.addedNodes.forEach(node => { if (!(node instanceof HTMLElement)) return; hasNewHtml = true; bind(node); }); } if (hasNewHtml) { updateAll(); } }); mo.observe(document.documentElement, { childList: true, subtree: true }); } if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", boot); else boot(); // Optional: rebase if other scripts set a new "nulstand" window.rebaseAlleVakken = () => { const svg = svgRoot(); if (!svg) return; svg.querySelectorAll(`[id*="-shiftbox-"] rect`).forEach(r => { r.dataset.baseX = String(Number(r.getAttribute("x") ?? "0")); delete r.dataset.baseExpr; }); runUpdateAll(); }; })();
//// VAKVULLING BU/HS #ALLE-VAKKEN //// (() => { const SCALE = 3.84615384615; const SVG_ID = "alle-vakken"; // BU mapping (VVU -> BU variant) const VVU_TO_BU_SUFFIX = { "dubbelglas": "2glas-BU", "tripleglas": "3glas-BU", "paneel": "paneel", "glaspaneel": "glaspaneel", "deur": "deur", "kaderdeur": "kaderdeur", "dubbelkaderdeur": "dubbelkaderdeur", // ✅ deze 3 blijven in mapping, maar worden geometrisch als BU-BI bucket behandeld "dubbelglas binnen": "2glas-BI", "tripleglas binnen": "3glas-BI", "draaikiep": "draaikiep", "vul in": null, }; // HS mapping (HS-opties zitten óók in VVU) const VVU_TO_HS_MODE = { "linksschuivend": "LS", "rechtsschuivend": "RS", "linksrechtsschuivend": "LR", "ls": "LS", "rs": "RS", "lr": "LR", }; // ✅ BU split: BU-main vs BU-BI const BU_MAIN_SUFFIXES = ["2glas-BU","3glas-BU","paneel","glaspaneel","deur","kaderdeur","dubbelkaderdeur"]; const BU_BI_SUFFIXES = ["2glas-BI","3glas-BI","draaikiep"]; const HS_MODES = ["LS","RS","LR"]; const esc = (s) => CSS.escape(s); function svgRoot() { return document.querySelector(`svg#${esc(SVG_ID)}`); } function setDisp(el, v) { if (el) el.setAttribute("display", v); } function normalizeKey(v) { return String(v || "") .trim() .toLowerCase() .replace(/\s+/g, "") .replace(/_/g, "") .replace(/-/g, ""); } function vakCount() { const checked = document.querySelector('input[type="radio"][name="form_fields[vakken]"]:checked'); if (checked && checked.value) { const m = String(checked.value).match(/([1-5])/); if (m) return Number(m[1]); } return 5; } function readInputValueById(id) { const el = document.getElementById(id); if (!el) return null; const v = (el.value ?? "").toString().trim().replace(",", "."); const n = Number(v); return Number.isFinite(n) ? n : 0; } // Prefer prefixA, else prefixB, else 0 function numField(prefixA, prefixB, suffix) { const a = readInputValueById(`form-field-${prefixA}${suffix}`); if (a != null) return a; const b = readInputValueById(`form-field-${prefixB}${suffix}`); if (b != null) return b; return 0; } // VVU select by name form_fields[VVU] function getVVU(n, i) { const sel = document.querySelector(`select[name="form_fields[${n}VVU${i}]"]`); return sel ? String(sel.value || "") : "vul in"; } // ✅ Dorpeltype radio (pakt liefst data-original-value, anders value) function getDorpelType() { const checked = document.querySelector('input[type="radio"][name="form_fields[dorpeltype]"]:checked'); if (!checked) return null; return (checked.getAttribute("data-original-value") || checked.value || "").toString().trim(); } /****************************************************************** * ✅ NEW: HS verdiept (nok) inputs ******************************************************************/ function getRadioValueByName(name) { const el = document.querySelector(`input[type="radio"][name="${name}"]:checked`); return el ? String(el.value || "").trim().toLowerCase() : ""; } function isVerdieptVJa() { return getRadioValueByName("form_fields[verdiept_v]") === "ja"; } function isVerdieptSJa() { return getRadioValueByName("form_fields[verdiept_s]") === "ja"; } // ✅ optie A: nokdiktes zijn mm -> schalen naar SVG-units function nokV() { return (readInputValueById("form-field-nokdiktevast") ?? 0); } function nokS() { return (readInputValueById("form-field-nokdikteschuif") ?? 0); } /****************************************************************** * NEVER SHOW elements (shiftboxes + BU-LR placeholders) ******************************************************************/ function hideNeverShow(svg) { // BU-LR placeholders for (let i = 1; i <= 5; i++) { setDisp(svg.querySelector(`#${esc(`vak-${i}-BU-LR`)}`), "none"); } // All BU shiftboxes svg.querySelectorAll(`[id*="-BU-shiftbox-"]`).forEach(el => setDisp(el, "none")); // All HS shiftboxes svg.querySelectorAll(`[id*="-HS-shiftbox-"]`).forEach(el => setDisp(el, "none")); } /****************************************************************** * VISIBILITY: only whitelist BU & HS groups ******************************************************************/ function showGroupAndNestedGs(groupEl) { if (!groupEl) return; const deny = (id) => { const s = String(id || ""); return s.includes("-shiftbox-") || s.endsWith("-BU-LR") || s.includes("-BU-LR-"); }; if (!deny(groupEl.id)) setDisp(groupEl, "block"); groupEl.querySelectorAll("g").forEach(g => { if (deny(g.id)) setDisp(g, "none"); else setDisp(g, "block"); }); } function hideAllBU(svg, i) { for (const suf of BU_MAIN_SUFFIXES) setDisp(svg.querySelector(`#${esc(`vak-${i}-BU-${suf}`)}`), "none"); for (const suf of BU_BI_SUFFIXES) setDisp(svg.querySelector(`#${esc(`vak-${i}-BU-${suf}`)}`), "none"); } function hideAllHS(svg, i) { for (const m of HS_MODES) setDisp(svg.querySelector(`#${esc(`vak-${i}-HS-${m}`)}`), "none"); } function updateVisibility() { const svg = svgRoot(); if (!svg) return; hideNeverShow(svg); const n = vakCount(); // reset only whitelisted for (let i = 1; i <= 5; i++) { hideAllBU(svg, i); hideAllHS(svg, i); } for (let i = 1; i <= n; i++) { const raw = getVVU(n, i); const key = normalizeKey(raw); const hsMode = VVU_TO_HS_MODE[key] || null; let buSuffix = Object.prototype.hasOwnProperty.call(VVU_TO_BU_SUFFIX, raw) ? VVU_TO_BU_SUFFIX[raw] : null; // ✅ extra optie: als dorpeltype = KVTPKU, dan kaderdeur -> dubbelkaderdeur const dorpelType = getDorpelType(); if (dorpelType === "KVTPKU" && raw === "kaderdeur") { buSuffix = "dubbelkaderdeur"; } if (hsMode) { const g = svg.querySelector(`#${esc(`vak-${i}-HS-${hsMode}`)}`); showGroupAndNestedGs(g); } else if (buSuffix) { const g = svg.querySelector(`#${esc(`vak-${i}-BU-${buSuffix}`)}`); showGroupAndNestedGs(g); } } hideNeverShow(svg); } /****************************************************************** * SHIFTBOX ENGINE (delta vs baseline) ******************************************************************/ function desiredXFromDelta(rect, exprNow) { const curX = Number(rect.getAttribute("x") ?? "0"); if (rect.dataset.baseX == null) rect.dataset.baseX = String(curX); if (rect.dataset.baseExpr == null) rect.dataset.baseExpr = String(exprNow); const baseX = Number(rect.dataset.baseX); const baseExpr = Number(rect.dataset.baseExpr); return baseX + (exprNow - baseExpr); } function boxFromShiftRect(rect, exprNow) { const curX = Number(rect.getAttribute("x") ?? "0"); const curY = Number(rect.getAttribute("y") ?? "0"); const w = Number(rect.getAttribute("width") ?? "0"); const h = Number(rect.getAttribute("height") ?? "0"); const desiredX = desiredXFromDelta(rect, exprNow); return { rect, desiredX, dxInc: desiredX - curX, minX: curX, maxX: curX + w, minY: curY, maxY: curY + h, }; } function buildBoxes(items) { const map = new Map(); // key -> boxes[] for (const it of items) { if (!it.gEl) continue; const r = it.gEl.querySelector("rect"); if (!r) continue; const b = boxFromShiftRect(r, it.exprNow()); if (!map.has(it.key)) map.set(it.key, []); map.get(it.key).push(b); } return map; } function moveBoxes(map) { for (const boxes of map.values()) { for (const b of boxes) b.rect.setAttribute("x", b.desiredX); } } function dxForPoint(x, y, boxes) { let dx = 0; for (const b of boxes) { if (x >= b.minX && x <= b.maxX && y >= b.minY && y <= b.maxY) dx += b.dxInc; } return dx; } /****************************************************************** * Geometry shifters ******************************************************************/ function shiftPointsAttr(pointsStr, boxes) { const nums = pointsStr.trim().replace(/,/g, " ").split(/\s+/).filter(Boolean).map(Number); if (nums.length < 2 || nums.some(n => !Number.isFinite(n))) return pointsStr; const out = []; for (let i = 0; i < nums.length - 1; i += 2) { const x = nums[i], y = nums[i + 1]; out.push(String(x + dxForPoint(x, y, boxes)), String(y)); } return out.join(" "); } function tokenizePath(d) { return d.match(/[a-zA-Z]|-?\d*\.?\d+(?:e[-+]?\d+)?/g) || []; } function shiftPathD(d, boxes) { const t = tokenizePath(d); if (!t.length) return d; let i = 0, cmd = "", x = 0, y = 0, x0 = 0, y0 = 0; const out = []; const isCmd = tok => /^[a-zA-Z]$/.test(tok); const nextNum = () => Number(t[i++]); const push = v => out.push(v); const shiftAbs = (px, py) => [px + dxForPoint(px, py, boxes), py]; while (i < t.length) { if (isCmd(t[i])) cmd = t[i++]; const rel = (cmd === cmd.toLowerCase()); const C = cmd.toUpperCase(); if (C === "M") { push("M"); const px = nextNum(), py = nextNum(); const ax = rel ? x + px : px, ay = rel ? y + py : py; const p = shiftAbs(ax, ay); push(p[0]); push(p[1]); x = ax; y = ay; x0 = ax; y0 = ay; while (i < t.length && !isCmd(t[i])) { push("L"); const lx = nextNum(), ly = nextNum(); const ax2 = rel ? x + lx : lx, ay2 = rel ? y + ly : ly; const p2 = shiftAbs(ax2, ay2); push(p2[0]); push(p2[1]); x = ax2; y = ay2; } continue; } if (C === "L") { push("L"); while (i < t.length && !isCmd(t[i])) { const px = nextNum(), py = nextNum(); const ax = rel ? x + px : px, ay = rel ? y + py : py; const p = shiftAbs(ax, ay); push(p[0]); push(p[1]); x = ax; y = ay; } continue; } if (C === "H") { push("H"); while (i < t.length && !isCmd(t[i])) { const px = nextNum(); const ax = rel ? x + px : px; const p = shiftAbs(ax, y); push(p[0]); x = ax; } continue; } if (C === "V") { push("V"); while (i < t.length && !isCmd(t[i])) { const py = nextNum(); const ay = rel ? y + py : py; push(ay); y = ay; } continue; } if (C === "C") { push("C"); while (i < t.length && !isCmd(t[i])) { const x1 = nextNum(), y1 = nextNum(), x2 = nextNum(), y2 = nextNum(), x3 = nextNum(), y3 = nextNum(); const ax1 = rel ? x + x1 : x1, ay1 = rel ? y + y1 : y1; const ax2 = rel ? x + x2 : x2, ay2 = rel ? y + y2 : y2; const ax3 = rel ? x + x3 : x3, ay3 = rel ? y + y3 : y3; const p1 = shiftAbs(ax1, ay1); const p2 = shiftAbs(ax2, ay2); const p3 = shiftAbs(ax3, ay3); push(p1[0]); push(p1[1]); push(p2[0]); push(p2[1]); push(p3[0]); push(p3[1]); x = ax3; y = ay3; } continue; } if (C === "S") { push("S"); while (i < t.length && !isCmd(t[i])) { const x2 = nextNum(), y2 = nextNum(), x3 = nextNum(), y3 = nextNum(); const ax2 = rel ? x + x2 : x2, ay2 = rel ? y + y2 : y2; const ax3 = rel ? x + x3 : x3, ay3 = rel ? y + y3 : y3; const p2 = shiftAbs(ax2, ay2); const p3 = shiftAbs(ax3, ay3); push(p2[0]); push(p2[1]); push(p3[0]); push(p3[1]); x = ax3; y = ay3; } continue; } if (C === "Q") { push("Q"); while (i < t.length && !isCmd(t[i])) { const x1 = nextNum(), y1 = nextNum(), x2 = nextNum(), y2 = nextNum(); const ax1 = rel ? x + x1 : x1, ay1 = rel ? y + y1 : y1; const ax2 = rel ? x + x2 : x2, ay2 = rel ? y + y2 : y2; const p1 = shiftAbs(ax1, ay1); const p2 = shiftAbs(ax2, ay2); push(p1[0]); push(p1[1]); push(p2[0]); push(p2[1]); x = ax2; y = ay2; } continue; } if (C === "T") { push("T"); while (i < t.length && !isCmd(t[i])) { const x2 = nextNum(), y2 = nextNum(); const ax2 = rel ? x + x2 : x2, ay2 = rel ? y + y2 : y2; const p2 = shiftAbs(ax2, ay2); push(p2[0]); push(p2[1]); x = ax2; y = ay2; } continue; } if (C === "A") { push("A"); while (i < t.length && !isCmd(t[i])) { const rx = nextNum(), ry = nextNum(), rot = nextNum(), laf = nextNum(), sf = nextNum(), x2 = nextNum(), y2 = nextNum(); const ax2 = rel ? x + x2 : x2, ay2 = rel ? y + y2 : y2; const p2 = shiftAbs(ax2, ay2); push(rx); push(ry); push(rot); push(laf); push(sf); push(p2[0]); push(p2[1]); x = ax2; y = ay2; } continue; } if (C === "Z") { push("Z"); x = x0; y = y0; continue; } return d; } return out.join(" "); } function shiftRect(el, boxes) { const x = Number(el.getAttribute("x")); const y = Number(el.getAttribute("y")); const w = Number(el.getAttribute("width")); if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(w)) return; const h = Number(el.getAttribute("height")); const yMid = Number.isFinite(h) ? (y + h / 2) : y; const xL2 = x + dxForPoint(x, yMid, boxes); const xR = x + w; const xR2 = xR + dxForPoint(xR, yMid, boxes); el.setAttribute("x", Math.min(xL2, xR2)); el.setAttribute("width", Math.abs(xR2 - xL2)); } // ---- gradients ---- function ensureDefs(svg) { let defs = svg.querySelector("defs"); if (!defs) { defs = document.createElementNS("http://www.w3.org/2000/svg", "defs"); svg.insertBefore(defs, svg.firstChild); } return defs; } function parseFillUrl(fill) { const m = String(fill).match(/^url\((["']?)#([^)'" ]+)\1\)$/); return m ? m[2] : null; } function getNumericAttr(el, name) { const v = el.getAttribute(name); if (v == null) return null; const n = Number(String(v).trim().replace(",", ".")); return Number.isFinite(n) ? n : null; } function isClearlyUserSpace(gradEl) { const units = (gradEl.getAttribute("gradientUnits") || "").trim(); if (units === "userSpaceOnUse") return true; if (units) return false; for (const a of ["x1","y1","x2","y2","cx","cy","fx","fy","r"]) { const n = getNumericAttr(gradEl, a); if (n == null) continue; if (n < -0.001 || n > 1.001) return true; } return false; } let gradCloneCounter = 0; function ensureGradientClone(svg, el, baseGradId) { const defs = ensureDefs(svg); if (!el.dataset.gradCloneId) { gradCloneCounter += 1; el.dataset.gradCloneId = `${baseGradId}__clone__${gradCloneCounter}`; } const cloneId = el.dataset.gradCloneId; let clone = defs.querySelector(`#${esc(cloneId)}`); if (!clone) { const orig = svg.querySelector(`#${esc(baseGradId)}`); if (!orig) return null; if (!isClearlyUserSpace(orig)) return null; clone = orig.cloneNode(true); clone.setAttribute("id", cloneId); clone.setAttribute("gradientUnits", "userSpaceOnUse"); clone.setAttribute("data-base-gradient-transform", orig.getAttribute("gradientTransform") || ""); defs.appendChild(clone); } el.setAttribute("fill", `url(#${cloneId})`); return clone; } function dxRepresentative(el, boxes) { const tag = el.tagName.toLowerCase(); if (tag === "rect") { const x = Number(el.getAttribute("x")); const y = Number(el.getAttribute("y")); const h = Number(el.getAttribute("height")); const yMid = Number.isFinite(h) ? (y + h / 2) : y; if (!Number.isFinite(x) || !Number.isFinite(yMid)) return 0; return dxForPoint(x, yMid, boxes); } if (tag === "polygon" || tag === "polyline") { const pts = (el.getAttribute("points") || "").trim().replace(/,/g, " ").split(/\s+/).filter(Boolean).map(Number); if (pts.length < 2 || pts.some(n => !Number.isFinite(n))) return 0; return dxForPoint(pts[0], pts[1], boxes); } if (tag === "path") { const d = el.getAttribute("d") || ""; const nums = d.match(/-?\d*\.?\d+(?:e[-+]?\d+)?/g); if (!nums || nums.length < 2) return 0; const x = Number(nums[0]), y = Number(nums[1]); if (!Number.isFinite(x) || !Number.isFinite(y)) return 0; return dxForPoint(x, y, boxes); } return 0; } function applyGradientShift(svg, el, dx) { const fill = el.getAttribute("fill"); if (!fill) return; const base = parseFillUrl(fill); if (!base) return; const clone = ensureGradientClone(svg, el, base); if (!clone) return; const baseT = (clone.getAttribute("data-base-gradient-transform") || "").trim(); const t = `translate(${dx},0)`; clone.setAttribute("gradientTransform", baseT ? `${baseT} ${t}` : t); } function applyShiftToGroup(svg, g, boxes) { if (!g || !boxes || !boxes.length) return; const gradDx = new Map(); g.querySelectorAll("[fill^='url(']").forEach(el => { const dx = dxRepresentative(el, boxes); if (dx) gradDx.set(el, dx); }); g.querySelectorAll("polygon").forEach(el => { const p = el.getAttribute("points"); if (p) el.setAttribute("points", shiftPointsAttr(p, boxes)); }); g.querySelectorAll("polyline").forEach(el => { const p = el.getAttribute("points"); if (p) el.setAttribute("points", shiftPointsAttr(p, boxes)); }); g.querySelectorAll("rect").forEach(el => shiftRect(el, boxes)); g.querySelectorAll("path").forEach(el => { const d = el.getAttribute("d"); if (d) el.setAttribute("d", shiftPathD(d, boxes)); }); for (const [el, dx] of gradDx.entries()) applyGradientShift(svg, el, dx); } /****************************************************************** * SHIFT CALCS ******************************************************************/ // BU uses current n* fields (fallback to 5*) function BU_LU(n) { return numField(`${n}`, "5", "LU"); } function BU_TU(n, i) { return numField(`${n}`, "5", `TU${i}`); } function BU_DMU(n, i) { return (numField(`${n}`, "5", `DMU${i}`) / SCALE); } function buPrefixLeft(n, i) { let x = BU_LU(n); if (i >= 2) x += BU_DMU(n,1) + BU_TU(n,1); if (i >= 3) x += BU_DMU(n,2) + BU_TU(n,2); if (i >= 4) x += BU_DMU(n,3) + BU_TU(n,3); if (i >= 5) x += BU_DMU(n,4) + BU_TU(n,4); return x; } // ✅ dynamic sponning per vak, based on current VVU function sponningBUDefault() { return readInputValueById("form-field-BU_sponningbreedte") ?? 0; } function sponningWissel() { return readInputValueById("form-field-wisselsponningbreedte") ?? 0; } function sponningDraaikiep() { return readInputValueById("form-field-draaikiepsponningbreedte") ?? 0; } function isVVU_BI(raw) { return raw === "dubbelglas binnen" || raw === "tripleglas binnen"; } function isVVU_Draaikiep(raw) { return raw === "draaikiep"; } function sponningForVak(n, i) { const raw = getVVU(n, i); if (isVVU_BI(raw)) return sponningWissel(); if (isVVU_Draaikiep(raw)) return sponningDraaikiep(); return sponningBUDefault(); } // HS uses current n* fields (fallback to 5*) function HS_LU(n) { return numField(`${n}`, "5", "LU"); } function HS_TU(n, i) { return numField(`${n}`, "5", `TU${i}`); } function HS_DMU(n, i) { return (numField(`${n}`, "5", `DMU${i}`) / SCALE); } // scaled for positions function hsPrefixLeft(n, i) { let x = HS_LU(n); if (i >= 2) x += HS_DMU(n,1) + HS_TU(n,1); if (i >= 3) x += HS_DMU(n,2) + HS_TU(n,2); if (i >= 4) x += HS_DMU(n,3) + HS_TU(n,3); if (i >= 5) x += HS_DMU(n,4) + HS_TU(n,4); return x; } function buildAndApplyShifts() { const svg = svgRoot(); if (!svg) return; hideNeverShow(svg); const n = vakCount(); // ---- BU shiftboxes (vak-i-BU-shiftbox-L/R) with dynamic sponning const buItems = []; for (let i = 1; i <= n; i++) { const gL = svg.querySelector(`#${esc(`vak-${i}-BU-shiftbox-L`)}`); const gR = svg.querySelector(`#${esc(`vak-${i}-BU-shiftbox-R`)}`); buItems.push({ key:`BU:${i}`, gEl:gL, exprNow: () => buPrefixLeft(n,i) - sponningForVak(n,i) }); buItems.push({ key:`BU:${i}`, gEl:gR, exprNow: () => buPrefixLeft(n,i) + BU_DMU(n,i) + sponningForVak(n,i) }); } const buBoxes = buildBoxes(buItems); // ---- HS shiftboxes (incl. mm-threshold corrections + verdiept nok-correcties) const hsItems = []; for (let i = 1; i <= n; i++) { const left = () => hsPrefixLeft(n, i); const w = () => HS_DMU(n, i); // scaled for positions const dmuMm = () => numField(`${n}`, "5", `DMU${i}`); // raw mm for thresholds const corr200 = () => (dmuMm() >= 200 ? 31.5 : 0); // for L/R const corr400 = () => (dmuMm() >= 400 ? 47 : 0); // for L1/R1/T1/T3 // Determine HS mode for this vak const rawVVU = getVVU(n, i); const mode = VVU_TO_HS_MODE[normalizeKey(rawVVU)] || null; // ✅ verdiept logic (scaled) const vOn = () => isVerdieptVJa(); const sOn = () => isVerdieptSJa(); const nv = () => nokV(); const ns = () => nokS(); // LS/RS set (L, T, R) // Rules: // - LS + verdiept_v=ja => L - nokV // - RS + verdiept_v=ja => R + nokV // - LS + verdiept_s=ja => R + nokS // - RS + verdiept_s=ja => L - nokS const extraL = () => { let dx = 0; if (mode === "LS" && vOn()) dx += -nv(); if (mode === "RS" && sOn()) dx += -ns(); return dx; }; const extraR = () => { let dx = 0; if (mode === "RS" && vOn()) dx += +nv(); if (mode === "LS" && sOn()) dx += +ns(); return dx; }; hsItems.push({ key:`HS_LTR:${i}`, gEl: svg.querySelector(`#${esc(`vak-${i}-HS-shiftbox-L`)}`), exprNow: () => left() + corr200() + extraL() }); hsItems.push({ key:`HS_LTR:${i}`, gEl: svg.querySelector(`#${esc(`vak-${i}-HS-shiftbox-T`)}`), exprNow: () => left() + (w()/4)*2 }); hsItems.push({ key:`HS_LTR:${i}`, gEl: svg.querySelector(`#${esc(`vak-${i}-HS-shiftbox-R`)}`), exprNow: () => left() + w() - corr200() + extraR() }); // LR set (L1, T1..T3, R1) // Rule: // - LR + verdiept_v=ja => L1 - nokV, R1 + nokV const extraL1 = () => (mode === "LR" && vOn()) ? (-nv()) : 0; const extraR1 = () => (mode === "LR" && vOn()) ? (+nv()) : 0; hsItems.push({ key:`HS_LR:${i}`, gEl: svg.querySelector(`#${esc(`vak-${i}-HS-shiftbox-L1`)}`), exprNow: () => left() + corr400() + extraL1() }); hsItems.push({ key:`HS_LR:${i}`, gEl: svg.querySelector(`#${esc(`vak-${i}-HS-shiftbox-T1`)}`), exprNow: () => left() + (w()/4)*1 + (corr400()/2) }); hsItems.push({ key:`HS_LR:${i}`, gEl: svg.querySelector(`#${esc(`vak-${i}-HS-shiftbox-T2`)}`), exprNow: () => left() + (w()/4)*2 }); hsItems.push({ key:`HS_LR:${i}`, gEl: svg.querySelector(`#${esc(`vak-${i}-HS-shiftbox-T3`)}`), exprNow: () => left() + (w()/4)*3 - (corr400()/2) }); hsItems.push({ key:`HS_LR:${i}`, gEl: svg.querySelector(`#${esc(`vak-${i}-HS-shiftbox-R1`)}`), exprNow: () => left() + w() - corr400() + extraR1() }); } const hsBoxes = buildBoxes(hsItems); // Apply BU geometry to both BU buckets (BU-main + BU-BI) for (let i = 1; i <= n; i++) { const boxes = buBoxes.get(`BU:${i}`) || []; if (!boxes.length) continue; for (const suf of BU_MAIN_SUFFIXES) { const g = svg.querySelector(`#${esc(`vak-${i}-BU-${suf}`)}`); if (g) applyShiftToGroup(svg, g, boxes); } for (const suf of BU_BI_SUFFIXES) { const g = svg.querySelector(`#${esc(`vak-${i}-BU-${suf}`)}`); if (g) applyShiftToGroup(svg, g, boxes); } } // Apply HS geometry for (let i = 1; i <= n; i++) { const bLTR = hsBoxes.get(`HS_LTR:${i}`) || []; const bLR = hsBoxes.get(`HS_LR:${i}`) || []; if (bLTR.length) { const gLS = svg.querySelector(`#${esc(`vak-${i}-HS-LS`)}`); const gRS = svg.querySelector(`#${esc(`vak-${i}-HS-RS`)}`); if (gLS) applyShiftToGroup(svg, gLS, bLTR); if (gRS) applyShiftToGroup(svg, gRS, bLTR); } if (bLR.length) { const gLR = svg.querySelector(`#${esc(`vak-${i}-HS-LR`)}`); if (gLR) applyShiftToGroup(svg, gLR, bLR); } } // Move shiftboxes (remain hidden) moveBoxes(buBoxes); moveBoxes(hsBoxes); hideNeverShow(svg); } function updateAll() { updateVisibility(); buildAndApplyShifts(); } /****************************************************************** * Bind listeners ******************************************************************/ function bind(root = document) { // VVU selects root.querySelectorAll(`select[name^="form_fields["][name*="VVU"]`).forEach(el => { if (el.dataset.avBound === "1") return; el.dataset.avBound = "1"; el.addEventListener("change", updateAll); el.addEventListener("input", updateAll); }); // all form-field inputs (includes: BU_sponningbreedte, wisselsponningbreedte, draaikiepsponningbreedte, DMU, TU, LU, nokdiktes, etc.) root.querySelectorAll(`input[id^="form-field-"]`).forEach(el => { if (el.dataset.avShiftBound === "1") return; el.dataset.avShiftBound = "1"; el.addEventListener("change", updateAll); el.addEventListener("input", updateAll); }); // vakken radio root.querySelectorAll(`input[type="radio"][name="form_fields[vakken]"]`).forEach(el => { if (el.dataset.avVakkenBound === "1") return; el.dataset.avVakkenBound = "1"; el.addEventListener("change", updateAll); el.addEventListener("input", updateAll); }); // dorpeltype radio root.querySelectorAll(`input[type="radio"][name="form_fields[dorpeltype]"]`).forEach(el => { if (el.dataset.avDorpeltypeBound === "1") return; el.dataset.avDorpeltypeBound = "1"; el.addEventListener("change", updateAll); el.addEventListener("input", updateAll); }); // ✅ verdiept radios (extra expliciet) root.querySelectorAll(`input[type="radio"][name="form_fields[verdiept_v]"], input[type="radio"][name="form_fields[verdiept_s]"]`).forEach(el => { if (el.dataset.avVerdieptBound === "1") return; el.dataset.avVerdieptBound = "1"; el.addEventListener("change", updateAll); el.addEventListener("input", updateAll); }); // ✅ Explicit (requested earlier) listeners – redundant but harmless ["form-field-wisselsponningbreedte","form-field-draaikiepsponningbreedte"].forEach(id => { const el = document.getElementById(id); if (!el || el.dataset.avExplicitSponningBound === "1") return; el.dataset.avExplicitSponningBound = "1"; el.addEventListener("change", updateAll); el.addEventListener("input", updateAll); }); } function boot() { bind(document); updateAll(); const mo = new MutationObserver(muts => { for (const mu of muts) { mu.addedNodes.forEach(node => { if (!(node instanceof HTMLElement)) return; bind(node); }); } updateAll(); }); mo.observe(document.documentElement, { childList: true, subtree: true }); } if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", boot); else boot(); // Optional: rebase if other scripts set a new "nulstand" window.rebaseAlleVakken = () => { const svg = svgRoot(); if (!svg) return; svg.querySelectorAll(`[id*="-shiftbox-"] rect`).forEach(r => { r.dataset.baseX = String(Number(r.getAttribute("x") ?? "0")); delete r.dataset.baseExpr; }); updateAll(); }; })();
//// HS-*S VERDIEPT AANPASSING VOOR DEURDIKTE //// (function () { const INPUT_SEL = '#form-field-deurdikte'; // eigen "prev" key zodat we andere scripts niet storen const PREV_KEY = 'prevPolyDeurdikte'; // -> input.dataset.prevPolyDeurdikte // 0-based indices in flattened points: [x0,y0,x1,y1,...] const NEUT_IDX_S = [5, 7]; const NEUT_IDX_T = [17, 19]; const BASIS_IDX_ALL = [13, 15]; function parsePoints(s) { if (!s) return null; const arr = s.trim().split(/[\s,]+/).map(Number); if (!arr.length || arr.some(Number.isNaN)) return null; return arr; } function applyDeltaToPoints(el, indices0, delta) { const pts = parsePoints(el.getAttribute('points')); if (!pts) return false; let changed = false; for (const idx of indices0) { if (idx >= 0 && idx < pts.length) { pts[idx] += delta; changed = true; } } if (changed) el.setAttribute('points', pts.join(' ')); return changed; } // Wildcard group detectie function groupType(id) { if (!id) return null; const s = String(id); const endsSU = s.endsWith('-SU'); const endsUS = s.endsWith('-US'); const endsS = s.endsWith('-S'); if (s.includes('-HS-T') && (endsSU || endsUS)) return 'T'; if (s.includes('-HS-') && endsS && !endsSU && !endsUS) return 'S'; return null; } function findNeutPolygons(g) { return Array.from(g.querySelectorAll('polygon')).filter(p => { const pid = (p.id || '').toLowerCase(); const pname = (p.getAttribute('data-name') || '').toLowerCase(); return pid.startsWith('neut') || pname === 'neut'; }); } // schuifbasis* wildcard function findSchuifbasisPolygons(g) { return Array.from(g.querySelectorAll('polygon')).filter(p => { const pid = (p.id || '').toLowerCase(); const pname = (p.getAttribute('data-name') || '').toLowerCase(); return pid.startsWith('schuifbasis') || pname.startsWith('schuifbasis'); }); } function getPrevPoly(input) { const v = input.dataset[PREV_KEY]; const n = Number(v); return Number.isFinite(n) ? n : null; } function setPrevPoly(input, v) { input.dataset[PREV_KEY] = String(v); } function applyDelta(delta) { const groupNodes = Array.from(document.querySelectorAll('svg g[id]')); for (const g of groupNodes) { const t = groupType(g.id); if (!t) continue; const neutIdx = (t === 'T') ? NEUT_IDX_T : NEUT_IDX_S; for (const p of findNeutPolygons(g)) { applyDeltaToPoints(p, neutIdx, delta); } for (const p of findSchuifbasisPolygons(g)) { applyDeltaToPoints(p, BASIS_IDX_ALL, delta); } } } function attach() { const input = document.querySelector(INPUT_SEL); if (!input) return false; // init onze eigen prev (zonder input.dataset.prev te wijzigen) const cur0 = Number(input.value); if (getPrevPoly(input) === null && Number.isFinite(cur0)) { setPrevPoly(input, cur0); } const handler = () => { const cur = Number(input.value); if (!Number.isFinite(cur)) return; const prev = getPrevPoly(input); if (prev === null) { setPrevPoly(input, cur); return; } const delta = cur - prev; if (!delta) return; // BELANGRIJK: alleen onze eigen prev updaten // en andere scripts hun dataset.prev met rust laten applyDelta(delta / 2); setPrevPoly(input, cur); }; // Bubble phase is prima nu (we blokkeren niets) input.addEventListener('input', handler, { passive: true }); input.addEventListener('change', handler, { passive: true }); console.log('[poly-deurdikte] attached (separate prev key)'); return true; } if (attach()) return; const obs = new MutationObserver(() => { if (attach()) obs.disconnect(); }); obs.observe(document.documentElement, { childList: true, subtree: true }); })();
//// HS — shiftboxen L/R bewegen op dorpelbreedte, sponningdiepte_buvd //// (() => { // ✅ unieke singleton (anders kan 2x injecteren) if (window.__HS_SHIFTBOX_LR_DORPEL_SPO_DELEGATED_V1__) return; window.__HS_SHIFTBOX_LR_DORPEL_SPO_DELEGATED_V1__ = true; // ✅ unieke dataset-key (BELANGRIJK: voorkomt clash met andere scripts) const INIT_KEY = 'hsShiftboxLRDorpSpoDelegatedV1Bound'; const ID_DORPEL = 'form-field-dorpelbreedte'; const ID_SPON = 'form-field-sponningdiepte_buvd'; const SHIFTBOX_IDS = ['x-shiftbox-vast-L', 'x-shiftbox-schuif-L', 'x-shiftbox-schuif-R']; const DIR = { [ID_DORPEL]: -1, // + => links [ID_SPON]: +1 // + => rechts (tegenovergesteld) }; const toNum = (v) => { const n = Number(v); return Number.isFinite(n) ? n : null; }; const clampToAttrs = (el, n) => { const minVal = toNum(el.getAttribute('min')); const maxVal = toNum(el.getAttribute('max')); if (!Number.isFinite(n)) return null; if (Number.isFinite(minVal)) n = Math.max(minVal, n); if (Number.isFinite(maxVal)) n = Math.min(maxVal, n); return n; }; function getShiftRects() { const rects = []; for (const gid of SHIFTBOX_IDS) { const g = document.getElementById(gid); const r = g ? g.querySelector('rect') : null; if (r) rects.push(r); } return rects; } function shiftRectsX(dx) { if (!dx) return; const rects = getShiftRects(); if (rects.length !== SHIFTBOX_IDS.length) return; rects.forEach((r) => { const x0 = toNum(r.getAttribute('x')); if (x0 === null) return; r.setAttribute('x', String(x0 + dx)); }); } function capturePrev(el) { const v = clampToAttrs(el, toNum(el.value)); if (v === null) return; el.dataset._prevLive = String(v); } function handleDelta(el) { const dir = DIR[el.id]; if (!dir) return; const prev = toNum(el.dataset._prevLive); const cur = clampToAttrs(el, toNum(el.value)); if (cur === null) return; if (prev === null) { el.dataset._prevLive = String(cur); return; } const delta = cur - prev; if (!delta) return; const dx = delta * dir; shiftRectsX(dx); el.dataset._prevLive = String(cur); } function isOurInput(t) { return t && t.tagName === 'INPUT' && t.type === 'number' && (t.id === ID_DORPEL || t.id === ID_SPON); } function boot() { // ✅ nogmaals: unieke init-guard if (document.documentElement.dataset[INIT_KEY] === '1') return; document.documentElement.dataset[INIT_KEY] = '1'; // prev init (voor pijltjes-klik zonder focusin) [ID_DORPEL, ID_SPON].forEach((id) => { const el = document.getElementById(id); if (el) capturePrev(el); }); // prev pakken vóór wijziging document.addEventListener('focusin', (e) => { const t = e.target; if (!isOurInput(t)) return; capturePrev(t); }, true); document.addEventListener('pointerdown', (e) => { const t = e.target; if (!isOurInput(t)) return; capturePrev(t); }, true); // delta verwerken (Elementor quirks) ['input', 'change', 'keyup', 'pointerup', 'mouseup', 'touchend'].forEach((evt) => { document.addEventListener(evt, (e) => { const t = e.target; if (!isOurInput(t)) return; handleDelta(t); }, true); }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', boot, { once: true }); } else { boot(); } })();
//// HS — VASTE DEURDIKTE #DORPEL //// (() => { if (window.__HS_VASTL_V12_GEOM__) return; window.__HS_VASTL_V12_GEOM__ = true; const DEURDIKTE_ID = 'form-field-deurdikte'; const TUSSENMAAT_ID = 'form-field-hstussenmaat'; const SHIFTBOX_ID = 'x-shiftbox-vast-L'; const DOOR_GROUP_ID = 'HS-vastedeur'; // ===== Schaalfactoren ===== const K_DEURDIKTE_RECT = -1.5; // +1 deurdikte => shiftbox x -= 1.5 const K_DEURDIKTE_PATH = -1.0; // +1 deurdikte => deurvulling punten (in box) x -= 1.0 const K_TUSSENMAAT_RECT = -1.0; // +1 tussenmaat => shiftbox x -= 1.0 (deurvulling: 0) const toNum = (v) => { const n = Number(v); return Number.isFinite(n) ? n : null; }; const clamp = (n, min, max) => { if (!Number.isFinite(n)) return null; if (Number.isFinite(min)) n = Math.max(min, n); if (Number.isFinite(max)) n = Math.min(max, n); return n; }; // ---------- SVG targeting ---------- function getShiftRect() { const g = document.getElementById(SHIFTBOX_ID); return g ? g.querySelector('rect') : null; } // v12: pak de “grote deurvulling” path (gradient fill) in #HS-vastedeur function getDoorFillPath() { const g = document.getElementById(DOOR_GROUP_ID); if (!g) return null; const paths = [...g.querySelectorAll(':scope > path[fill^="url("][d]')]; if (!paths.length) return null; // neem de langste d (meestal het paneel) let best = paths[0]; let bestLen = (best.getAttribute('d') || '').length; for (const p of paths) { const len = (p.getAttribute('d') || '').length; if (len > bestLen) { best = p; bestLen = len; } } return best; } function adjustRectX(rectEl, dx) { if (!rectEl || !dx) return; const x0 = toNum(rectEl.getAttribute('x')); if (x0 === null) return; rectEl.setAttribute('x', String(x0 + dx)); } // ---------- translate helpers ---------- function parseTranslate(transformStr) { const m = /translate\(\s*([-\d.]+)(?:[\s,]+([-\d.]+))?\s*\)/.exec(transformStr || ''); if (!m) return { x: 0, y: 0 }; return { x: Number(m[1]), y: m[2] !== undefined ? Number(m[2]) : 0 }; } // worldOffsetX = som van translateX op element + ouders tot svg function getWorldOffsetXFromElement(el) { let x = 0; let cur = el; while (cur && cur.tagName) { const t = cur.getAttribute && cur.getAttribute('transform'); if (t && /translate\(/.test(t)) x += (parseTranslate(t).x || 0); cur = cur.parentElement; if (cur && cur.tagName && cur.tagName.toLowerCase() === 'svg') break; } return x; } // ---------- PATH helpers ---------- function parseTokens(d) { return d.match(/[a-zA-Z]|-?\d*\.?\d+(?:e[-+]?\d+)?/g); } function round3(v) { return Math.round(v * 1000) / 1000; } // Normaliseer path 1x naar absolute commands function normalizePathToAbsoluteOnce(pathEl) { if (!pathEl) return; if (pathEl.dataset.absNormalized === '1') return; const d = pathEl.getAttribute('d'); if (!d) return; const tokens = parseTokens(d); if (!tokens || !tokens.length) return; let i = 0, curX = 0, curY = 0, lastCmd = ''; const out = []; const specs = { M:2, L:2, T:2, H:1, V:1, C:6, S:4, Q:4, A:7, Z:0 }; const take = () => tokens[i++]; const isCmd = (t) => /^[a-zA-Z]$/.test(t); const toAbs = (x, y, rel) => rel ? [x + curX, y + curY] : [x, y]; while (i < tokens.length) { let cmd = take(); if (!isCmd(cmd)) { i--; cmd = lastCmd; } else { lastCmd = cmd; } const ABS = cmd.toUpperCase(); const rel = cmd !== ABS; if (ABS === 'Z') { out.push('Z'); continue; } const nPer = specs[ABS]; if (!nPer) continue; let buf = []; while (i < tokens.length && !isCmd(tokens[i])) { buf.push(parseFloat(take())); if (buf.length % nPer === 0) { const n = buf.splice(buf.length - nPer, nPer); switch (ABS) { case 'M': case 'L': case 'T': { let [x, y] = toAbs(n[0], n[1], rel); out.push(ABS, round3(x), round3(y)); curX = x; curY = y; break; } case 'H': { let x = rel ? curX + n[0] : n[0]; out.push('H', round3(x)); curX = x; break; } case 'V': { let y = rel ? curY + n[0] : n[0]; out.push('V', round3(y)); curY = y; break; } case 'C': { let [x1, y1] = toAbs(n[0], n[1], rel); let [x2, y2] = toAbs(n[2], n[3], rel); let [x, y ] = toAbs(n[4], n[5], rel); out.push('C', round3(x1), round3(y1), round3(x2), round3(y2), round3(x), round3(y) ); curX = x; curY = y; break; } case 'S': { let [x2, y2] = toAbs(n[0], n[1], rel); let [x, y ] = toAbs(n[2], n[3], rel); out.push('S', round3(x2), round3(y2), round3(x), round3(y)); curX = x; curY = y; break; } case 'Q': { let [x1, y1] = toAbs(n[0], n[1], rel); let [x, y ] = toAbs(n[2], n[3], rel); out.push('Q', round3(x1), round3(y1), round3(x), round3(y)); curX = x; curY = y; break; } case 'A': { const rx=n[0], ry=n[1], rot=n[2], laf=n[3], sf=n[4]; let [x, y] = toAbs(n[5], n[6], rel); out.push('A', rx, ry, rot, laf, sf, round3(x), round3(y)); curX = x; curY = y; break; } } } } } pathEl.setAttribute('d', out.join(' ').replace(/\s+/g, ' ').trim()); pathEl.dataset.absNormalized = '1'; } /** * Shift local X-coördinaten als WORLD-X binnen [minX,maxX] valt: * worldX = localX + worldOffsetX */ function shiftPathXInsideBox_World(pathEl, minX, maxX, dx, worldOffsetX) { if (!pathEl || !dx) return; const d = pathEl.getAttribute('d'); if (!d) return; const tokens = parseTokens(d); if (!tokens || !tokens.length) return; let i = 0, curX = 0, curY = 0, lastCmd = ''; const out = []; const specs = { M:2, L:2, T:2, H:1, V:1, C:6, S:4, Q:4, A:7, Z:0 }; const take = () => tokens[i++]; const isCmd = (t) => /^[a-zA-Z]$/.test(t); const insideWorld = (localX) => { const worldX = localX + worldOffsetX; return worldX >= minX && worldX <= maxX; }; while (i < tokens.length) { let cmd = take(); if (!isCmd(cmd)) { i--; cmd = lastCmd; } else { lastCmd = cmd; } const ABS = cmd.toUpperCase(); const rel = cmd !== ABS; if (ABS === 'Z') { out.push('Z'); continue; } const nPer = specs[ABS]; if (!nPer) continue; const toAbs = (x, y) => rel ? [x + curX, y + curY] : [x, y]; let buf = []; while (i < tokens.length && !isCmd(tokens[i])) { buf.push(parseFloat(take())); if (buf.length % nPer === 0) { const n = buf.splice(buf.length - nPer, nPer); switch (ABS) { case 'M': case 'L': case 'T': { let [x, y] = toAbs(n[0], n[1]); if (insideWorld(x)) x += dx; out.push(ABS, round3(x), round3(y)); curX = x; curY = y; break; } case 'H': { let x = rel ? curX + n[0] : n[0]; if (insideWorld(x)) x += dx; out.push('H', round3(x)); curX = x; break; } case 'V': { let y = rel ? curY + n[0] : n[0]; out.push('V', round3(y)); curY = y; break; } case 'C': { let [x1, y1] = toAbs(n[0], n[1]); let [x2, y2] = toAbs(n[2], n[3]); let [x, y ] = toAbs(n[4], n[5]); if (insideWorld(x1)) x1 += dx; if (insideWorld(x2)) x2 += dx; if (insideWorld(x )) x += dx; out.push('C', round3(x1), round3(y1), round3(x2), round3(y2), round3(x), round3(y) ); curX = x; curY = y; break; } case 'S': { let [x2, y2] = toAbs(n[0], n[1]); let [x, y ] = toAbs(n[2], n[3]); if (insideWorld(x2)) x2 += dx; if (insideWorld(x )) x += dx; out.push('S', round3(x2), round3(y2), round3(x), round3(y)); curX = x; curY = y; break; } case 'Q': { let [x1, y1] = toAbs(n[0], n[1]); let [x, y ] = toAbs(n[2], n[3]); if (insideWorld(x1)) x1 += dx; if (insideWorld(x )) x += dx; out.push('Q', round3(x1), round3(y1), round3(x), round3(y)); curX = x; curY = y; break; } case 'A': { const rx=n[0], ry=n[1], rot=n[2], laf=n[3], sf=n[4]; let [x, y] = toAbs(n[5], n[6]); if (insideWorld(x)) x += dx; out.push('A', rx, ry, rot, laf, sf, round3(x), round3(y)); curX = x; curY = y; break; } } } } } pathEl.setAttribute('d', out.join(' ').replace(/\s+/g, ' ').trim()); } // ---------- Apply dx ---------- function applyDx(dxRect, dxPath) { if (!dxRect && !dxPath) return; const rect = getShiftRect(); if (!rect) return; // bounds vóór verplaatsen (selectie) const x0 = toNum(rect.getAttribute('x')); const w = toNum(rect.getAttribute('width')); if (x0 === null || w === null) return; const minX = x0; const maxX = x0 + w; // v12: deurpaneel path vervormen (geen clippath meer) if (dxPath) { const doorPath = getDoorFillPath(); if (doorPath) { normalizePathToAbsoluteOnce(doorPath); const worldOffsetX = getWorldOffsetXFromElement(doorPath); shiftPathXInsideBox_World(doorPath, minX, maxX, dxPath, worldOffsetX); } } // shiftbox rect if (dxRect) adjustRectX(rect, dxRect); } // ---------- Bind input deltas ---------- function bindNumberDelta(inputEl, prevKey, multRect, multPath) { if (!inputEl) return; const minVal = toNum(inputEl.getAttribute('min')); const maxVal = toNum(inputEl.getAttribute('max')); if (inputEl.dataset[prevKey] == null) { const v0 = clamp(toNum(inputEl.value), minVal, maxVal); inputEl.dataset[prevKey] = String(v0 ?? 0); } const handler = () => { const newVal = clamp(toNum(inputEl.value), minVal, maxVal); if (newVal === null) return; const prevVal = toNum(inputEl.dataset[prevKey]); if (prevVal === null) { inputEl.dataset[prevKey] = String(newVal); return; } const delta = newVal - prevVal; if (!delta) return; applyDx(delta * multRect, delta * multPath); inputEl.dataset[prevKey] = String(newVal); }; inputEl.addEventListener('input', handler); inputEl.addEventListener('change', handler); } function setupOnce() { if (document.documentElement.dataset.hsVastLGeomV12Bound === '1') return true; const rect = getShiftRect(); const deurdikte = document.getElementById(DEURDIKTE_ID); const tussenmaat = document.getElementById(TUSSENMAAT_ID); if (!rect || !deurdikte || !tussenmaat) return false; // ✅ deurdikte: rect 1.5x, path 1.0x bindNumberDelta(deurdikte, 'prevDeurdikteForVastL', K_DEURDIKTE_RECT, K_DEURDIKTE_PATH); // ✅ hstussenmaat: alleen rect 1.0x bindNumberDelta(tussenmaat, 'prevTussenmaatForVastL', K_TUSSENMAAT_RECT, 0); document.documentElement.dataset.hsVastLGeomV12Bound = '1'; return true; } if (setupOnce()) return; const obs = new MutationObserver(() => { if (setupOnce()) obs.disconnect(); }); obs.observe(document.documentElement, { childList: true, subtree: true }); window.addEventListener('load', () => { setupOnce(); }, { once: true }); })();
//// HS — VASTE DEURDIKTE #DORPEL //// (() => { if (window.__HS_VASTL_V12_GEOM__) return; window.__HS_VASTL_V12_GEOM__ = true; const DEURDIKTE_ID = 'form-field-deurdikte'; const TUSSENMAAT_ID = 'form-field-hstussenmaat'; const SHIFTBOX_ID = 'x-shiftbox-vast-L'; const DOOR_GROUP_ID = 'HS-vastedeur'; // ===== Schaalfactoren ===== const K_DEURDIKTE_RECT = -1.5; // +1 deurdikte => shiftbox x -= 1.5 const K_DEURDIKTE_PATH = -1.0; // +1 deurdikte => deurvulling punten (in box) x -= 1.0 const K_TUSSENMAAT_RECT = -1.0; // +1 tussenmaat => shiftbox x -= 1.0 (deurvulling: 0) const toNum = (v) => { const n = Number(v); return Number.isFinite(n) ? n : null; }; const clamp = (n, min, max) => { if (!Number.isFinite(n)) return null; if (Number.isFinite(min)) n = Math.max(min, n); if (Number.isFinite(max)) n = Math.min(max, n); return n; }; // ---------- SVG targeting ---------- function getShiftRect() { const g = document.getElementById(SHIFTBOX_ID); return g ? g.querySelector('rect') : null; } // v12: pak de “grote deurvulling” path (gradient fill) in #HS-vastedeur function getDoorFillPath() { const g = document.getElementById(DOOR_GROUP_ID); if (!g) return null; const paths = [...g.querySelectorAll(':scope > path[fill^="url("][d]')]; if (!paths.length) return null; // neem de langste d (meestal het paneel) let best = paths[0]; let bestLen = (best.getAttribute('d') || '').length; for (const p of paths) { const len = (p.getAttribute('d') || '').length; if (len > bestLen) { best = p; bestLen = len; } } return best; } function adjustRectX(rectEl, dx) { if (!rectEl || !dx) return; const x0 = toNum(rectEl.getAttribute('x')); if (x0 === null) return; rectEl.setAttribute('x', String(x0 + dx)); } // ---------- translate helpers ---------- function parseTranslate(transformStr) { const m = /translate\(\s*([-\d.]+)(?:[\s,]+([-\d.]+))?\s*\)/.exec(transformStr || ''); if (!m) return { x: 0, y: 0 }; return { x: Number(m[1]), y: m[2] !== undefined ? Number(m[2]) : 0 }; } // worldOffsetX = som van translateX op element + ouders tot svg function getWorldOffsetXFromElement(el) { let x = 0; let cur = el; while (cur && cur.tagName) { const t = cur.getAttribute && cur.getAttribute('transform'); if (t && /translate\(/.test(t)) x += (parseTranslate(t).x || 0); cur = cur.parentElement; if (cur && cur.tagName && cur.tagName.toLowerCase() === 'svg') break; } return x; } // ---------- PATH helpers ---------- function parseTokens(d) { return d.match(/[a-zA-Z]|-?\d*\.?\d+(?:e[-+]?\d+)?/g); } function round3(v) { return Math.round(v * 1000) / 1000; } // Normaliseer path 1x naar absolute commands function normalizePathToAbsoluteOnce(pathEl) { if (!pathEl) return; if (pathEl.dataset.absNormalized === '1') return; const d = pathEl.getAttribute('d'); if (!d) return; const tokens = parseTokens(d); if (!tokens || !tokens.length) return; let i = 0, curX = 0, curY = 0, lastCmd = ''; const out = []; const specs = { M:2, L:2, T:2, H:1, V:1, C:6, S:4, Q:4, A:7, Z:0 }; const take = () => tokens[i++]; const isCmd = (t) => /^[a-zA-Z]$/.test(t); const toAbs = (x, y, rel) => rel ? [x + curX, y + curY] : [x, y]; while (i < tokens.length) { let cmd = take(); if (!isCmd(cmd)) { i--; cmd = lastCmd; } else { lastCmd = cmd; } const ABS = cmd.toUpperCase(); const rel = cmd !== ABS; if (ABS === 'Z') { out.push('Z'); continue; } const nPer = specs[ABS]; if (!nPer) continue; let buf = []; while (i < tokens.length && !isCmd(tokens[i])) { buf.push(parseFloat(take())); if (buf.length % nPer === 0) { const n = buf.splice(buf.length - nPer, nPer); switch (ABS) { case 'M': case 'L': case 'T': { let [x, y] = toAbs(n[0], n[1], rel); out.push(ABS, round3(x), round3(y)); curX = x; curY = y; break; } case 'H': { let x = rel ? curX + n[0] : n[0]; out.push('H', round3(x)); curX = x; break; } case 'V': { let y = rel ? curY + n[0] : n[0]; out.push('V', round3(y)); curY = y; break; } case 'C': { let [x1, y1] = toAbs(n[0], n[1], rel); let [x2, y2] = toAbs(n[2], n[3], rel); let [x, y ] = toAbs(n[4], n[5], rel); out.push('C', round3(x1), round3(y1), round3(x2), round3(y2), round3(x), round3(y) ); curX = x; curY = y; break; } case 'S': { let [x2, y2] = toAbs(n[0], n[1], rel); let [x, y ] = toAbs(n[2], n[3], rel); out.push('S', round3(x2), round3(y2), round3(x), round3(y)); curX = x; curY = y; break; } case 'Q': { let [x1, y1] = toAbs(n[0], n[1], rel); let [x, y ] = toAbs(n[2], n[3], rel); out.push('Q', round3(x1), round3(y1), round3(x), round3(y)); curX = x; curY = y; break; } case 'A': { const rx=n[0], ry=n[1], rot=n[2], laf=n[3], sf=n[4]; let [x, y] = toAbs(n[5], n[6], rel); out.push('A', rx, ry, rot, laf, sf, round3(x), round3(y)); curX = x; curY = y; break; } } } } } pathEl.setAttribute('d', out.join(' ').replace(/\s+/g, ' ').trim()); pathEl.dataset.absNormalized = '1'; } /** * Shift local X-coördinaten als WORLD-X binnen [minX,maxX] valt: * worldX = localX + worldOffsetX */ function shiftPathXInsideBox_World(pathEl, minX, maxX, dx, worldOffsetX) { if (!pathEl || !dx) return; const d = pathEl.getAttribute('d'); if (!d) return; const tokens = parseTokens(d); if (!tokens || !tokens.length) return; let i = 0, curX = 0, curY = 0, lastCmd = ''; const out = []; const specs = { M:2, L:2, T:2, H:1, V:1, C:6, S:4, Q:4, A:7, Z:0 }; const take = () => tokens[i++]; const isCmd = (t) => /^[a-zA-Z]$/.test(t); const insideWorld = (localX) => { const worldX = localX + worldOffsetX; return worldX >= minX && worldX <= maxX; }; while (i < tokens.length) { let cmd = take(); if (!isCmd(cmd)) { i--; cmd = lastCmd; } else { lastCmd = cmd; } const ABS = cmd.toUpperCase(); const rel = cmd !== ABS; if (ABS === 'Z') { out.push('Z'); continue; } const nPer = specs[ABS]; if (!nPer) continue; const toAbs = (x, y) => rel ? [x + curX, y + curY] : [x, y]; let buf = []; while (i < tokens.length && !isCmd(tokens[i])) { buf.push(parseFloat(take())); if (buf.length % nPer === 0) { const n = buf.splice(buf.length - nPer, nPer); switch (ABS) { case 'M': case 'L': case 'T': { let [x, y] = toAbs(n[0], n[1]); if (insideWorld(x)) x += dx; out.push(ABS, round3(x), round3(y)); curX = x; curY = y; break; } case 'H': { let x = rel ? curX + n[0] : n[0]; if (insideWorld(x)) x += dx; out.push('H', round3(x)); curX = x; break; } case 'V': { let y = rel ? curY + n[0] : n[0]; out.push('V', round3(y)); curY = y; break; } case 'C': { let [x1, y1] = toAbs(n[0], n[1]); let [x2, y2] = toAbs(n[2], n[3]); let [x, y ] = toAbs(n[4], n[5]); if (insideWorld(x1)) x1 += dx; if (insideWorld(x2)) x2 += dx; if (insideWorld(x )) x += dx; out.push('C', round3(x1), round3(y1), round3(x2), round3(y2), round3(x), round3(y) ); curX = x; curY = y; break; } case 'S': { let [x2, y2] = toAbs(n[0], n[1]); let [x, y ] = toAbs(n[2], n[3]); if (insideWorld(x2)) x2 += dx; if (insideWorld(x )) x += dx; out.push('S', round3(x2), round3(y2), round3(x), round3(y)); curX = x; curY = y; break; } case 'Q': { let [x1, y1] = toAbs(n[0], n[1]); let [x, y ] = toAbs(n[2], n[3]); if (insideWorld(x1)) x1 += dx; if (insideWorld(x )) x += dx; out.push('Q', round3(x1), round3(y1), round3(x), round3(y)); curX = x; curY = y; break; } case 'A': { const rx=n[0], ry=n[1], rot=n[2], laf=n[3], sf=n[4]; let [x, y] = toAbs(n[5], n[6]); if (insideWorld(x)) x += dx; out.push('A', rx, ry, rot, laf, sf, round3(x), round3(y)); curX = x; curY = y; break; } } } } } pathEl.setAttribute('d', out.join(' ').replace(/\s+/g, ' ').trim()); } // ---------- Apply dx ---------- function applyDx(dxRect, dxPath) { if (!dxRect && !dxPath) return; const rect = getShiftRect(); if (!rect) return; // bounds vóór verplaatsen (selectie) const x0 = toNum(rect.getAttribute('x')); const w = toNum(rect.getAttribute('width')); if (x0 === null || w === null) return; const minX = x0; const maxX = x0 + w; // v12: deurpaneel path vervormen (geen clippath meer) if (dxPath) { const doorPath = getDoorFillPath(); if (doorPath) { normalizePathToAbsoluteOnce(doorPath); const worldOffsetX = getWorldOffsetXFromElement(doorPath); shiftPathXInsideBox_World(doorPath, minX, maxX, dxPath, worldOffsetX); } } // shiftbox rect if (dxRect) adjustRectX(rect, dxRect); } // ---------- Bind input deltas ---------- function bindNumberDelta(inputEl, prevKey, multRect, multPath) { if (!inputEl) return; const minVal = toNum(inputEl.getAttribute('min')); const maxVal = toNum(inputEl.getAttribute('max')); if (inputEl.dataset[prevKey] == null) { const v0 = clamp(toNum(inputEl.value), minVal, maxVal); inputEl.dataset[prevKey] = String(v0 ?? 0); } const handler = () => { const newVal = clamp(toNum(inputEl.value), minVal, maxVal); if (newVal === null) return; const prevVal = toNum(inputEl.dataset[prevKey]); if (prevVal === null) { inputEl.dataset[prevKey] = String(newVal); return; } const delta = newVal - prevVal; if (!delta) return; applyDx(delta * multRect, delta * multPath); inputEl.dataset[prevKey] = String(newVal); }; inputEl.addEventListener('input', handler); inputEl.addEventListener('change', handler); } function setupOnce() { if (document.documentElement.dataset.hsVastLGeomV12Bound === '1') return true; const rect = getShiftRect(); const deurdikte = document.getElementById(DEURDIKTE_ID); const tussenmaat = document.getElementById(TUSSENMAAT_ID); if (!rect || !deurdikte || !tussenmaat) return false; // ✅ deurdikte: rect 1.5x, path 1.0x bindNumberDelta(deurdikte, 'prevDeurdikteForVastL', K_DEURDIKTE_RECT, K_DEURDIKTE_PATH); // ✅ hstussenmaat: alleen rect 1.0x bindNumberDelta(tussenmaat, 'prevTussenmaatForVastL', K_TUSSENMAAT_RECT, 0); document.documentElement.dataset.hsVastLGeomV12Bound = '1'; return true; } if (setupOnce()) return; const obs = new MutationObserver(() => { if (setupOnce()) obs.disconnect(); }); obs.observe(document.documentElement, { childList: true, subtree: true }); window.addEventListener('load', () => { setupOnce(); }, { once: true }); })();
//// HS SCHUIFDEURDIKTE #DORPEL //// (() => { if (window.__HS_SCHUIF_GEOM_V12__) return; window.__HS_SCHUIF_GEOM_V12__ = true; const INPUT_ID = 'form-field-deurdikte'; const ROOT_SVG_ID = 'dorpel'; const SHIFTBOX_L_ID = 'x-shiftbox-schuif-L'; const SHIFTBOX_R_ID = 'x-shiftbox-schuif-R'; const SCHUIF_GROUP_ID = 'HS-schuif'; const PREV_KEY = 'hsSchuifPrev'; const BOUND_KEY = 'hsSchuifGeomV12Bound'; const toNum = (v) => { const n = Number(v); return Number.isFinite(n) ? n : null; }; const clamp = (n, min, max) => { if (!Number.isFinite(n)) return null; if (Number.isFinite(min)) n = Math.max(min, n); if (Number.isFinite(max)) n = Math.min(max, n); return n; }; function getRootSvg() { const svg = document.getElementById(ROOT_SVG_ID); if (!svg || svg.tagName.toLowerCase() !== 'svg') return null; return svg; } function escId(id) { if (window.CSS && CSS.escape) return CSS.escape(id); return id.replace(/([ !"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~])/g, '\\$1'); } function byId(root, id) { if (!root) return null; return root.querySelector('#' + escId(id)); } function getShiftRectByGroupId(root, groupId) { const g = byId(root, groupId); if (!g) return null; return g.querySelector('rect'); } function adjustRectX(rectEl, dx) { if (!rectEl || !dx) return; const x0 = toNum(rectEl.getAttribute('x')); if (x0 === null) return; rectEl.setAttribute('x', String(x0 + dx)); } // -------- translate helpers (WORLD offset) ---------- function parseTranslate(transformStr) { const m = /translate\(\s*([-\d.]+)(?:[\s,]+([-\d.]+))?\s*\)/.exec(transformStr || ''); if (!m) return { x: 0, y: 0 }; return { x: Number(m[1]), y: m[2] !== undefined ? Number(m[2]) : 0 }; } function getWorldOffsetXFromElement(el) { let x = 0; let cur = el; while (cur && cur.tagName) { const t = cur.getAttribute && cur.getAttribute('transform'); if (t && /translate\(/.test(t)) x += (parseTranslate(t).x || 0); cur = cur.parentElement; if (cur && cur.tagName && cur.tagName.toLowerCase() === 'svg') break; } return x; } // -------- PATH helpers ---------- function parseTokens(d) { return d.match(/[a-zA-Z]|-?\d*\.?\d+(?:e[-+]?\d+)?/g); } function round3(v) { return Math.round(v * 1000) / 1000; } function normalizePathToAbsoluteOnce(pathEl) { if (!pathEl) return; if (pathEl.dataset.absNormalized === '1') return; const d = pathEl.getAttribute('d'); if (!d) return; const tokens = parseTokens(d); if (!tokens || !tokens.length) return; let i = 0, curX = 0, curY = 0, lastCmd = ''; const out = []; const specs = { M:2, L:2, T:2, H:1, V:1, C:6, S:4, Q:4, A:7, Z:0 }; const take = () => tokens[i++]; const isCmd = (t) => /^[a-zA-Z]$/.test(t); const toAbs = (x, y, rel) => rel ? [x + curX, y + curY] : [x, y]; while (i < tokens.length) { let cmd = take(); if (!isCmd(cmd)) { i--; cmd = lastCmd; } else { lastCmd = cmd; } const ABS = cmd.toUpperCase(); const rel = cmd !== ABS; if (ABS === 'Z') { out.push('Z'); continue; } const nPer = specs[ABS]; if (!nPer) continue; let buf = []; while (i < tokens.length && !isCmd(tokens[i])) { buf.push(parseFloat(take())); if (buf.length % nPer === 0) { const n = buf.splice(buf.length - nPer, nPer); switch (ABS) { case 'M': case 'L': case 'T': { let [x, y] = toAbs(n[0], n[1], rel); out.push(ABS, round3(x), round3(y)); curX = x; curY = y; break; } case 'H': { let x = rel ? curX + n[0] : n[0]; out.push('H', round3(x)); curX = x; break; } case 'V': { let y = rel ? curY + n[0] : n[0]; out.push('V', round3(y)); curY = y; break; } case 'C': { let [x1, y1] = toAbs(n[0], n[1], rel); let [x2, y2] = toAbs(n[2], n[3], rel); let [x, y ] = toAbs(n[4], n[5], rel); out.push('C', round3(x1), round3(y1), round3(x2), round3(y2), round3(x), round3(y)); curX = x; curY = y; break; } case 'S': { let [x2, y2] = toAbs(n[0], n[1], rel); let [x, y ] = toAbs(n[2], n[3], rel); out.push('S', round3(x2), round3(y2), round3(x), round3(y)); curX = x; curY = y; break; } case 'Q': { let [x1, y1] = toAbs(n[0], n[1], rel); let [x, y ] = toAbs(n[2], n[3], rel); out.push('Q', round3(x1), round3(y1), round3(x), round3(y)); curX = x; curY = y; break; } case 'A': { const rx = n[0], ry = n[1], rot = n[2], laf = n[3], sf = n[4]; let [x, y] = toAbs(n[5], n[6], rel); out.push('A', rx, ry, rot, laf, sf, round3(x), round3(y)); curX = x; curY = y; break; } } } } } pathEl.setAttribute('d', out.join(' ').replace(/\s+/g, ' ').trim()); pathEl.dataset.absNormalized = '1'; } function dxForRangesWorld(localX, ranges, worldOffsetX) { const worldX = localX + worldOffsetX; for (const r of ranges) { if (worldX >= r.minX && worldX <= r.maxX) return r.dx; } return 0; } function shiftPathXByRanges_World(pathEl, ranges, worldOffsetX) { if (!pathEl || !ranges || !ranges.length) return; const d = pathEl.getAttribute('d'); if (!d) return; const tokens = parseTokens(d); if (!tokens || !tokens.length) return; let i = 0, curX = 0, curY = 0, lastCmd = ''; const out = []; const specs = { M:2, L:2, T:2, H:1, V:1, C:6, S:4, Q:4, A:7, Z:0 }; const take = () => tokens[i++]; const isCmd = (t) => /^[a-zA-Z]$/.test(t); const toAbs = (x, y, rel) => rel ? [x + curX, y + curY] : [x, y]; while (i < tokens.length) { let cmd = take(); if (!isCmd(cmd)) { i--; cmd = lastCmd; } else { lastCmd = cmd; } const ABS = cmd.toUpperCase(); const rel = cmd !== ABS; if (ABS === 'Z') { out.push('Z'); continue; } const nPer = specs[ABS]; if (!nPer) continue; let buf = []; while (i < tokens.length && !isCmd(tokens[i])) { buf.push(parseFloat(take())); if (buf.length % nPer === 0) { const n = buf.splice(buf.length - nPer, nPer); switch (ABS) { case 'M': case 'L': case 'T': { let [x, y] = toAbs(n[0], n[1], rel); x += dxForRangesWorld(x, ranges, worldOffsetX); out.push(ABS, round3(x), round3(y)); curX = x; curY = y; break; } case 'H': { let x = rel ? curX + n[0] : n[0]; x += dxForRangesWorld(x, ranges, worldOffsetX); out.push('H', round3(x)); curX = x; break; } case 'V': { let y = rel ? curY + n[0] : n[0]; out.push('V', round3(y)); curY = y; break; } case 'C': { let [x1, y1] = toAbs(n[0], n[1], rel); let [x2, y2] = toAbs(n[2], n[3], rel); let [x, y ] = toAbs(n[4], n[5], rel); x1 += dxForRangesWorld(x1, ranges, worldOffsetX); x2 += dxForRangesWorld(x2, ranges, worldOffsetX); x += dxForRangesWorld(x, ranges, worldOffsetX); out.push('C', round3(x1), round3(y1), round3(x2), round3(y2), round3(x), round3(y)); curX = x; curY = y; break; } case 'S': { let [x2, y2] = toAbs(n[0], n[1], rel); let [x, y ] = toAbs(n[2], n[3], rel); x2 += dxForRangesWorld(x2, ranges, worldOffsetX); x += dxForRangesWorld(x, ranges, worldOffsetX); out.push('S', round3(x2), round3(y2), round3(x), round3(y)); curX = x; curY = y; break; } case 'Q': { let [x1, y1] = toAbs(n[0], n[1], rel); let [x, y ] = toAbs(n[2], n[3], rel); x1 += dxForRangesWorld(x1, ranges, worldOffsetX); x += dxForRangesWorld(x, ranges, worldOffsetX); out.push('Q', round3(x1), round3(y1), round3(x), round3(y)); curX = x; curY = y; break; } case 'A': { const rx = n[0], ry = n[1], rot = n[2], laf = n[3], sf = n[4]; let [x, y] = toAbs(n[5], n[6], rel); x += dxForRangesWorld(x, ranges, worldOffsetX); out.push('A', rx, ry, rot, laf, sf, round3(x), round3(y)); curX = x; curY = y; break; } } } } } pathEl.setAttribute('d', out.join(' ').replace(/\s+/g, ' ').trim()); } function getSchuifDoorFillPaths(root) { const g = byId(root, SCHUIF_GROUP_ID); if (!g) return []; const candidates = [...g.querySelectorAll(':scope > path[fill^="url("][d]')].filter(p => p.id !== 'HS_x5F_alu'); if (!candidates.length) return []; let best = candidates[0]; let bestLen = (best.getAttribute('d') || '').length; for (const p of candidates) { const len = (p.getAttribute('d') || '').length; if (len > bestLen) { best = p; bestLen = len; } } return [best]; } function setupOnce(input) { if (!input || input.dataset[BOUND_KEY] === '1') return false; input.dataset[BOUND_KEY] = '1'; const root = getRootSvg(); if (!root) return false; const minVal = toNum(input.getAttribute('min')); const maxVal = toNum(input.getAttribute('max')); if (input.dataset[PREV_KEY] == null) { const v0 = clamp(toNum(input.value), minVal, maxVal); input.dataset[PREV_KEY] = String(v0 ?? 0); } const handler = () => { const newVal = clamp(toNum(input.value), minVal, maxVal); if (newVal === null) return; const prevVal = toNum(input.dataset[PREV_KEY]); if (prevVal === null) { input.dataset[PREV_KEY] = String(newVal); return; } const delta = newVal - prevVal; if (!delta) return; const half = delta / 2; const rectL = getShiftRectByGroupId(root, SHIFTBOX_L_ID); const rectR = getShiftRectByGroupId(root, SHIFTBOX_R_ID); if (!rectL || !rectR) return; const xL = toNum(rectL.getAttribute('x')); const wL = toNum(rectL.getAttribute('width')); const xR = toNum(rectR.getAttribute('x')); const wR = toNum(rectR.getAttribute('width')); if (xL === null || wL === null || xR === null || wR === null) return; const ranges = [ { minX: xL, maxX: xL + wL, dx: -half }, { minX: xR, maxX: xR + wR, dx: +half }, ]; const paths = getSchuifDoorFillPaths(root); for (const p of paths) { normalizePathToAbsoluteOnce(p); const worldOffsetX = getWorldOffsetXFromElement(p); shiftPathXByRanges_World(p, ranges, worldOffsetX); } adjustRectX(rectL, -half); adjustRectX(rectR, +half); document.dispatchEvent(new CustomEvent('hs:applyTussenmaatDelta', { detail: { diff: half } })); input.dataset[PREV_KEY] = String(newVal); }; input.addEventListener('input', handler); input.addEventListener('change', handler); return true; } function tryInit() { const input = document.getElementById(INPUT_ID); if (!input) return false; return setupOnce(input); } if (tryInit()) return; const obs = new MutationObserver(() => { if (tryInit()) obs.disconnect(); }); obs.observe(document.documentElement, { childList: true, subtree: true }); window.addEventListener('load', () => { tryInit(); }, { once: true }); })();

Boorgat>loodsponning botsing

Sponningdiepte HS

Neuten vervangen BU –> HS

FILTER VVU/VVI OPTIES AFH DORPELTYPE

Hs schuifnok verdiept

Y>X boorgatverschuiving #dorpel

Wel/geen HS nok

Tussenmaat HS deuren

//// Tussenmaat HS deuren //// (() => { const INPUT_ID = 'form-field-hstussenmaat'; // === AANGEPASTE indices (zoals gevraagd) === // Binnen #neut*: // - LR-group -> indices 5,7 // - T-group -> indices 17,19 const INDICES_NEUT_LR = [5, 7]; const INDICES_NEUT_T = [17, 19]; // Binnen #vastbasis* -> indices 1,3,5,7,9,11 const INDICES_VASTBASIS = [1, 3, 5, 7, 9, 11]; // Group-detectie ongewijzigd const isLRGroup = (gid) => gid.includes('HS-L-V') || gid.includes('HS-R-V'); const T_KEYS = ['HS-T1-UV','HS-T1-VU','HS-T2-UV','HS-T2-VU','HS-T3-UV','HS-T3-VU','HS-T4-UV','HS-T4-VU']; const isTGroup = (gid) => T_KEYS.some(k => gid.includes(k)); const toNum = (v) => { const n = Number(v); return Number.isFinite(n) ? n : null; }; // -------------------- POLYGON helpers -------------------- const parsePoints = (s) => (s || '').trim().split(/[\s,]+/).map(Number); const serializePoints = (arr) => arr.join(' '); function shiftPolygonByDiff(poly, flatIndices, diff) { if (!diff) return; const ptsStr = poly.getAttribute('points'); if (!ptsStr) return; const pts = parsePoints(ptsStr); let changed = false; for (const idx of flatIndices) { if (idx >= 0 && idx < pts.length && Number.isFinite(pts[idx])) { pts[idx] += diff; changed = true; } } if (changed) poly.setAttribute('points', serializePoints(pts)); } // -------------------- RECT helper -------------------- function shiftRectYByDiff(rect, diff) { if (!diff) return; const y0 = toNum(rect.getAttribute('y')); if (y0 === null) return; rect.setAttribute('y', String(y0 + diff)); } // -------------------- TRANSLATE helpers -------------------- function parseTranslate(transformStr) { const m = /translate\(\s*([-\d.]+)(?:[\s,]+([-\d.]+))?\s*\)/.exec(transformStr || ''); if (!m) return { x: 0, y: 0 }; return { x: Number(m[1]), y: m[2] !== undefined ? Number(m[2]) : 0 }; } function setTranslate(el, x, y) { const t = el.getAttribute('transform') || ''; const re = /translate\(\s*([-\d.]+)(?:[\s,]+([-\d.]+))?\s*\)/; if (re.test(t)) { el.setAttribute('transform', t.replace(re, `translate(${x},${y})`)); } else { el.setAttribute('transform', t ? `translate(${x},${y}) ${t}` : `translate(${x},${y})`); } } function shiftTranslateYByDiff(el, diff) { if (!diff) return; const { x, y } = parseTranslate(el.getAttribute('transform')); setTranslate(el, x, y + diff); } // Shift rect by translate-y if present, otherwise by y attribute function shiftRectYOrTranslateYByDiff(rect, diff) { if (!diff || !rect) return; const t = rect.getAttribute('transform') || ''; if (/translate\(/.test(t)) { shiftTranslateYByDiff(rect, diff); return; } const y0 = toNum(rect.getAttribute('y')); if (y0 === null) return; rect.setAttribute('y', String(y0 + diff)); } // -------------------- GRADIENT helpers -------------------- function parseUrlId(fillValue) { const m = /url\(\s*#([^)]+)\s*\)/.exec(fillValue || ''); return m ? m[1] : null; } function shiftGradientYByDiff(svg, gradientId, diff) { if (!svg || !gradientId || !diff) return; const grad = svg.querySelector(`#${CSS.escape(gradientId)}`); if (!grad) return; const gt = grad.getAttribute('gradientTransform') || ''; if (/translate\(/.test(gt)) { const { x, y } = parseTranslate(gt); const re = /translate\(\s*([-\d.]+)(?:[\s,]+([-\d.]+))?\s*\)/; const newGT = re.test(gt) ? gt.replace(re, `translate(${x},${y + diff})`) : `translate(${x},${y + diff}) ${gt}`.trim(); grad.setAttribute('gradientTransform', newGT); return; } const y1 = toNum(grad.getAttribute('y1')); const y2 = toNum(grad.getAttribute('y2')); if (y1 !== null) grad.setAttribute('y1', String(y1 + diff)); if (y2 !== null) grad.setAttribute('y2', String(y2 + diff)); const cy = toNum(grad.getAttribute('cy')); const fy = toNum(grad.getAttribute('fy')); if (cy !== null) grad.setAttribute('cy', String(cy + diff)); if (fy !== null) grad.setAttribute('fy', String(fy + diff)); } // -------------------- PATH helpers -------------------- function parseTokens(d) { return d?.match(/[a-zA-Z]|-?\d*\.?\d+(?:e[-+]?\d+)?/g); } function round3(v) { return Math.round(v * 1000) / 1000; } function getDirectChild(g, tagName) { for (const node of g.children) { if (node.tagName && node.tagName.toLowerCase() === tagName.toLowerCase()) return node; } return null; } function normalizePathToAbsoluteOnce(pathEl) { if (!pathEl) return; if (pathEl.dataset.absNormalized === '1') return; const d = pathEl.getAttribute('d'); if (!d) return; const tokens = parseTokens(d); if (!tokens || !tokens.length) return; let i = 0, curX = 0, curY = 0, lastCmd = ''; const out = []; const specs = { M:2, L:2, T:2, H:1, V:1, C:6, S:4, Q:4, A:7, Z:0 }; const take = () => tokens[i++]; const isCmd = (t) => /^[a-zA-Z]$/.test(t); const toAbs = (x, y, rel) => rel ? [x + curX, y + curY] : [x, y]; while (i < tokens.length) { let cmd = take(); if (!isCmd(cmd)) { i--; cmd = lastCmd; } else { lastCmd = cmd; } const ABS = cmd.toUpperCase(); const rel = cmd !== ABS; if (ABS === 'Z') { out.push('Z'); continue; } const nPer = specs[ABS]; if (!nPer) continue; let buf = []; while (i < tokens.length && !isCmd(tokens[i])) { buf.push(parseFloat(take())); if (buf.length % nPer === 0) { const n = buf.splice(buf.length - nPer, nPer); switch (ABS) { case 'M': case 'L': case 'T': { let [x, y] = toAbs(n[0], n[1], rel); out.push(ABS, round3(x), round3(y)); curX = x; curY = y; break; } case 'H': { let x = rel ? curX + n[0] : n[0]; out.push('H', round3(x)); curX = x; break; } case 'V': { let y = rel ? curY + n[0] : n[0]; out.push('V', round3(y)); curY = y; break; } case 'C': { let [x1, y1] = toAbs(n[0], n[1], rel); let [x2, y2] = toAbs(n[2], n[3], rel); let [x, y ] = toAbs(n[4], n[5], rel); out.push('C', round3(x1), round3(y1), round3(x2), round3(y2), round3(x), round3(y) ); curX = x; curY = y; break; } case 'S': { let [x2, y2] = toAbs(n[0], n[1], rel); let [x, y ] = toAbs(n[2], n[3], rel); out.push('S', round3(x2), round3(y2), round3(x), round3(y)); curX = x; curY = y; break; } case 'Q': { let [x1, y1] = toAbs(n[0], n[1], rel); let [x, y ] = toAbs(n[2], n[3], rel); out.push('Q', round3(x1), round3(y1), round3(x), round3(y)); curX = x; curY = y; break; } case 'A': { const rx = n[0], ry = n[1], rot = n[2], laf = n[3], sf = n[4]; let [x, y] = toAbs(n[5], n[6], rel); out.push('A', rx, ry, rot, laf, sf, round3(x), round3(y)); curX = x; curY = y; break; } } } } } pathEl.setAttribute('d', out.join(' ').replace(/\s+/g, ' ').trim()); pathEl.dataset.absNormalized = '1'; } function adjustPathXInsideBox_World(pathEl, minX, maxX, deltaX, worldOffsetX) { if (!pathEl || !deltaX) return; const d = pathEl.getAttribute('d'); if (!d) return; const tokens = parseTokens(d); if (!tokens || !tokens.length) return; let i = 0, curX = 0, curY = 0, lastCmd = ''; const out = []; const specs = { M:2, L:2, T:2, H:1, V:1, C:6, S:4, Q:4, A:7, Z:0 }; const take = () => tokens[i++]; const isCmd = (t) => /^[a-zA-Z]$/.test(t); const insideWorld = (localX) => { const worldX = localX + worldOffsetX; return worldX >= minX && worldX <= maxX; }; while (i < tokens.length) { let cmd = take(); if (!isCmd(cmd)) { i--; cmd = lastCmd; } else { lastCmd = cmd; } const ABS = cmd.toUpperCase(); const rel = cmd !== ABS; if (ABS === 'Z') { out.push('Z'); continue; } const nPer = specs[ABS]; if (!nPer) continue; const toAbs = (x, y) => rel ? [x + curX, y + curY] : [x, y]; let buf = []; while (i < tokens.length && !isCmd(tokens[i])) { buf.push(parseFloat(take())); if (buf.length % nPer === 0) { const n = buf.splice(buf.length - nPer, nPer); switch (ABS) { case 'M': case 'L': case 'T': { let [x, y] = toAbs(n[0], n[1]); if (insideWorld(x)) x += deltaX; out.push(ABS, round3(x), round3(y)); curX = x; curY = y; break; } case 'H': { let x = rel ? curX + n[0] : n[0]; if (insideWorld(x)) x += deltaX; out.push('H', round3(x)); curX = x; break; } case 'V': { let y = rel ? curY + n[0] : n[0]; out.push('V', round3(y)); curY = y; break; } case 'C': { let [x1, y1] = toAbs(n[0], n[1]); let [x2, y2] = toAbs(n[2], n[3]); let [x, y ] = toAbs(n[4], n[5]); if (insideWorld(x1)) x1 += deltaX; if (insideWorld(x2)) x2 += deltaX; if (insideWorld(x )) x += deltaX; out.push('C', round3(x1), round3(y1), round3(x2), round3(y2), round3(x), round3(y) ); curX = x; curY = y; break; } case 'S': { let [x2, y2] = toAbs(n[0], n[1]); let [x, y ] = toAbs(n[2], n[3]); if (insideWorld(x2)) x2 += deltaX; if (insideWorld(x )) x += deltaX; out.push('S', round3(x2), round3(y2), round3(x), round3(y)); curX = x; curY = y; break; } case 'Q': { let [x1, y1] = toAbs(n[0], n[1]); let [x, y ] = toAbs(n[2], n[3]); if (insideWorld(x1)) x1 += deltaX; if (insideWorld(x )) x += deltaX; out.push('Q', round3(x1), round3(y1), round3(x), round3(y)); curX = x; curY = y; break; } case 'A': { const rx = n[0], ry = n[1], rot = n[2], laf = n[3], sf = n[4]; let [x, y] = toAbs(n[5], n[6]); if (insideWorld(x)) x += deltaX; out.push('A', rx, ry, rot, laf, sf, round3(x), round3(y)); curX = x; curY = y; break; } } } } } pathEl.setAttribute('d', out.join(' ').replace(/\s+/g, ' ').trim()); } // -------------------- Door logic (svg#dorpel) -------------------- function applyDoorDiff(diff, translateFactor = 1) { if (!diff) return; const g = document.querySelector('svg g#HS-vastedeur'); if (!g) return; const svg = g.ownerSVGElement || document.querySelector('svg'); if (!svg) return; const shiftRect = svg.querySelector('#x-shiftbox-sponning-HS-deur rect'); if (!shiftRect) return; const minX = toNum(shiftRect.getAttribute('x')); const w = toNum(shiftRect.getAttribute('width')); if (minX === null || w === null) return; const maxX = minX + w; const { x: txBefore, y: tyBefore } = parseTranslate(g.getAttribute('transform')); const path = g.querySelector(':scope > path#HSvullat'); if (path) normalizePathToAbsoluteOnce(path); const deltaX = +(diff * translateFactor); if (path) { adjustPathXInsideBox_World(path, minX, maxX, deltaX, txBefore); } const firstRect = getDirectChild(g, 'rect'); if (firstRect) { const x0 = toNum(firstRect.getAttribute('x')); if (x0 !== null) { const worldX = x0 + txBefore; if (worldX >= minX && worldX <= maxX) { firstRect.setAttribute('x', String(x0 + deltaX)); } } } setTranslate(g, txBefore - (diff * translateFactor), tyBefore); } // -------------------- Extra logic (svg#alle-vakken) -------------------- function applyDiffToAlleVakken(diff) { if (!diff) return; const svg = document.querySelector('svg#alle-vakken'); if (!svg) return; const vakSelector = 'g[id^="vak-"][id*="-HS-LS"], g[id^="vak-"][id*="-HS-RS"], g[id^="vak-"][id*="-HS-LR"]'; svg.querySelectorAll(vakSelector).forEach((g) => { const rects = g.querySelectorAll('rect[data-name="vastedeur"], rect[id^="vastedeur"]'); rects.forEach((r) => { shiftRectYOrTranslateYByDiff(r, diff); const fill = r.getAttribute('fill') || ''; const gradId = parseUrlId(fill); if (gradId) shiftGradientYByDiff(svg, gradId, diff); }); }); } // -------------------- Main apply -------------------- function applyDiffToSvg(diff, correctedYFactor = 1, doorTranslateFactor = 1) { const correctedDiff = diff * correctedYFactor; // 1) NEUT polygons: per group indices document.querySelectorAll('svg polygon[id^="neut"]').forEach((poly) => { const gid = poly.closest('g[id]')?.id || ''; const indices = isLRGroup(gid) ? INDICES_NEUT_LR : (isTGroup(gid) ? INDICES_NEUT_T : null); if (!indices) return; shiftPolygonByDiff(poly, indices, correctedDiff); }); // 2) VASTBASIS polygons: indices 1,3,5,7,9,11 document.querySelectorAll('svg polygon[id^="vastbasis"]').forEach((poly) => { shiftPolygonByDiff(poly, INDICES_VASTBASIS, correctedDiff); }); // 3) Vastnok rects (dorpel svg) document .querySelectorAll('svg rect[data-name="vastnok"], svg rect[id^="vastnok"]') .forEach((r) => shiftRectYByDiff(r, correctedDiff)); // 4) Door (dorpel svg) applyDoorDiff(diff, doorTranslateFactor); // 5) Extra: alle-vakken svg applyDiffToAlleVakken(diff); } // ====== Onderhuidse delta vanuit andere scripts (bv. deurdikte) ====== document.addEventListener('hs:applyTussenmaatDelta', (e) => { const diff = e?.detail?.diff; if (!Number.isFinite(diff) || diff === 0) return; // Vanuit form_fields[deurdikte]: // neut / vastbasis / vastnok blijven factor x2. // #HS-vastedeur translate moet ook x2, zodat stapjes 0,5 -> 1 worden. applyDiffToSvg(diff, 2, 2); }); // -------------------- Input (number) delta -------------------- function clamp(n, min, max) { if (!Number.isFinite(n)) return null; if (Number.isFinite(min)) n = Math.max(min, n); if (Number.isFinite(max)) n = Math.min(max, n); return n; } function init() { const input = document.getElementById(INPUT_ID); if (!input) return; const minVal = toNum(input.getAttribute('min')); const maxVal = toNum(input.getAttribute('max')); if (input.dataset.prev == null) { const v0 = clamp(toNum(input.value), minVal, maxVal); input.dataset.prev = String(v0 ?? 0); } const handler = () => { const newValRaw = toNum(input.value); const newVal = clamp(newValRaw, minVal, maxVal); if (newVal === null) return; const prevVal = toNum(input.dataset.prev); if (prevVal === null) { input.dataset.prev = String(newVal); return; } const diff = newVal - prevVal; if (!diff) return; // Vanuit form_fields[hstussenmaat]: // normale factor 1 voor neut / vastbasis / vastnok. applyDiffToSvg(diff, 1); input.dataset.prev = String(newVal); }; input.addEventListener('input', handler); input.addEventListener('change', handler); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init, { once: true }); } else { init(); } })();
//// Tussenmaat HS deuren //// (() => { const INPUT_ID = 'form-field-hstussenmaat'; // === AANGEPASTE indices (zoals gevraagd) === // Binnen #neut*: // - LR-group -> indices 5,7 // - T-group -> indices 17,19 const INDICES_NEUT_LR = [5, 7]; const INDICES_NEUT_T = [17, 19]; // Binnen #vastbasis* -> indices 1,3,5,7,9,11 const INDICES_VASTBASIS = [1, 3, 5, 7, 9, 11]; // Group-detectie ongewijzigd const isLRGroup = (gid) => gid.includes('HS-L-V') || gid.includes('HS-R-V'); const T_KEYS = ['HS-T1-UV','HS-T1-VU','HS-T2-UV','HS-T2-VU','HS-T3-UV','HS-T3-VU','HS-T4-UV','HS-T4-VU']; const isTGroup = (gid) => T_KEYS.some(k => gid.includes(k)); const toNum = (v) => { const n = Number(v); return Number.isFinite(n) ? n : null; }; // -------------------- POLYGON helpers -------------------- const parsePoints = (s) => (s || '').trim().split(/[\s,]+/).map(Number); const serializePoints = (arr) => arr.join(' '); function shiftPolygonByDiff(poly, flatIndices, diff) { if (!diff) return; const ptsStr = poly.getAttribute('points'); if (!ptsStr) return; const pts = parsePoints(ptsStr); let changed = false; for (const idx of flatIndices) { if (idx >= 0 && idx < pts.length && Number.isFinite(pts[idx])) { pts[idx] += diff; changed = true; } } if (changed) poly.setAttribute('points', serializePoints(pts)); } // -------------------- RECT helper -------------------- function shiftRectYByDiff(rect, diff) { if (!diff) return; const y0 = toNum(rect.getAttribute('y')); if (y0 === null) return; rect.setAttribute('y', String(y0 + diff)); } // -------------------- TRANSLATE helpers -------------------- function parseTranslate(transformStr) { const m = /translate\(\s*([-\d.]+)(?:[\s,]+([-\d.]+))?\s*\)/.exec(transformStr || ''); if (!m) return { x: 0, y: 0 }; return { x: Number(m[1]), y: m[2] !== undefined ? Number(m[2]) : 0 }; } function setTranslate(el, x, y) { const t = el.getAttribute('transform') || ''; const re = /translate\(\s*([-\d.]+)(?:[\s,]+([-\d.]+))?\s*\)/; if (re.test(t)) { el.setAttribute('transform', t.replace(re, `translate(${x},${y})`)); } else { el.setAttribute('transform', t ? `translate(${x},${y}) ${t}` : `translate(${x},${y})`); } } function shiftTranslateYByDiff(el, diff) { if (!diff) return; const { x, y } = parseTranslate(el.getAttribute('transform')); setTranslate(el, x, y + diff); } // Shift rect by translate-y if present, otherwise by y attribute function shiftRectYOrTranslateYByDiff(rect, diff) { if (!diff || !rect) return; const t = rect.getAttribute('transform') || ''; if (/translate\(/.test(t)) { shiftTranslateYByDiff(rect, diff); return; } const y0 = toNum(rect.getAttribute('y')); if (y0 === null) return; rect.setAttribute('y', String(y0 + diff)); } // -------------------- GRADIENT helpers -------------------- function parseUrlId(fillValue) { const m = /url\(\s*#([^)]+)\s*\)/.exec(fillValue || ''); return m ? m[1] : null; } function shiftGradientYByDiff(svg, gradientId, diff) { if (!svg || !gradientId || !diff) return; const grad = svg.querySelector(`#${CSS.escape(gradientId)}`); if (!grad) return; const gt = grad.getAttribute('gradientTransform') || ''; if (/translate\(/.test(gt)) { const { x, y } = parseTranslate(gt); const re = /translate\(\s*([-\d.]+)(?:[\s,]+([-\d.]+))?\s*\)/; const newGT = re.test(gt) ? gt.replace(re, `translate(${x},${y + diff})`) : `translate(${x},${y + diff}) ${gt}`.trim(); grad.setAttribute('gradientTransform', newGT); return; } const y1 = toNum(grad.getAttribute('y1')); const y2 = toNum(grad.getAttribute('y2')); if (y1 !== null) grad.setAttribute('y1', String(y1 + diff)); if (y2 !== null) grad.setAttribute('y2', String(y2 + diff)); const cy = toNum(grad.getAttribute('cy')); const fy = toNum(grad.getAttribute('fy')); if (cy !== null) grad.setAttribute('cy', String(cy + diff)); if (fy !== null) grad.setAttribute('fy', String(fy + diff)); } // -------------------- PATH helpers -------------------- function parseTokens(d) { return d?.match(/[a-zA-Z]|-?\d*\.?\d+(?:e[-+]?\d+)?/g); } function round3(v) { return Math.round(v * 1000) / 1000; } function getDirectChild(g, tagName) { for (const node of g.children) { if (node.tagName && node.tagName.toLowerCase() === tagName.toLowerCase()) return node; } return null; } function normalizePathToAbsoluteOnce(pathEl) { if (!pathEl) return; if (pathEl.dataset.absNormalized === '1') return; const d = pathEl.getAttribute('d'); if (!d) return; const tokens = parseTokens(d); if (!tokens || !tokens.length) return; let i = 0, curX = 0, curY = 0, lastCmd = ''; const out = []; const specs = { M:2, L:2, T:2, H:1, V:1, C:6, S:4, Q:4, A:7, Z:0 }; const take = () => tokens[i++]; const isCmd = (t) => /^[a-zA-Z]$/.test(t); const toAbs = (x, y, rel) => rel ? [x + curX, y + curY] : [x, y]; while (i < tokens.length) { let cmd = take(); if (!isCmd(cmd)) { i--; cmd = lastCmd; } else { lastCmd = cmd; } const ABS = cmd.toUpperCase(); const rel = cmd !== ABS; if (ABS === 'Z') { out.push('Z'); continue; } const nPer = specs[ABS]; if (!nPer) continue; let buf = []; while (i < tokens.length && !isCmd(tokens[i])) { buf.push(parseFloat(take())); if (buf.length % nPer === 0) { const n = buf.splice(buf.length - nPer, nPer); switch (ABS) { case 'M': case 'L': case 'T': { let [x, y] = toAbs(n[0], n[1], rel); out.push(ABS, round3(x), round3(y)); curX = x; curY = y; break; } case 'H': { let x = rel ? curX + n[0] : n[0]; out.push('H', round3(x)); curX = x; break; } case 'V': { let y = rel ? curY + n[0] : n[0]; out.push('V', round3(y)); curY = y; break; } case 'C': { let [x1, y1] = toAbs(n[0], n[1], rel); let [x2, y2] = toAbs(n[2], n[3], rel); let [x, y ] = toAbs(n[4], n[5], rel); out.push('C', round3(x1), round3(y1), round3(x2), round3(y2), round3(x), round3(y) ); curX = x; curY = y; break; } case 'S': { let [x2, y2] = toAbs(n[0], n[1], rel); let [x, y ] = toAbs(n[2], n[3], rel); out.push('S', round3(x2), round3(y2), round3(x), round3(y)); curX = x; curY = y; break; } case 'Q': { let [x1, y1] = toAbs(n[0], n[1], rel); let [x, y ] = toAbs(n[2], n[3], rel); out.push('Q', round3(x1), round3(y1), round3(x), round3(y)); curX = x; curY = y; break; } case 'A': { const rx = n[0], ry = n[1], rot = n[2], laf = n[3], sf = n[4]; let [x, y] = toAbs(n[5], n[6], rel); out.push('A', rx, ry, rot, laf, sf, round3(x), round3(y)); curX = x; curY = y; break; } } } } } pathEl.setAttribute('d', out.join(' ').replace(/\s+/g, ' ').trim()); pathEl.dataset.absNormalized = '1'; } function adjustPathXInsideBox_World(pathEl, minX, maxX, deltaX, worldOffsetX) { if (!pathEl || !deltaX) return; const d = pathEl.getAttribute('d'); if (!d) return; const tokens = parseTokens(d); if (!tokens || !tokens.length) return; let i = 0, curX = 0, curY = 0, lastCmd = ''; const out = []; const specs = { M:2, L:2, T:2, H:1, V:1, C:6, S:4, Q:4, A:7, Z:0 }; const take = () => tokens[i++]; const isCmd = (t) => /^[a-zA-Z]$/.test(t); const insideWorld = (localX) => { const worldX = localX + worldOffsetX; return worldX >= minX && worldX <= maxX; }; while (i < tokens.length) { let cmd = take(); if (!isCmd(cmd)) { i--; cmd = lastCmd; } else { lastCmd = cmd; } const ABS = cmd.toUpperCase(); const rel = cmd !== ABS; if (ABS === 'Z') { out.push('Z'); continue; } const nPer = specs[ABS]; if (!nPer) continue; const toAbs = (x, y) => rel ? [x + curX, y + curY] : [x, y]; let buf = []; while (i < tokens.length && !isCmd(tokens[i])) { buf.push(parseFloat(take())); if (buf.length % nPer === 0) { const n = buf.splice(buf.length - nPer, nPer); switch (ABS) { case 'M': case 'L': case 'T': { let [x, y] = toAbs(n[0], n[1]); if (insideWorld(x)) x += deltaX; out.push(ABS, round3(x), round3(y)); curX = x; curY = y; break; } case 'H': { let x = rel ? curX + n[0] : n[0]; if (insideWorld(x)) x += deltaX; out.push('H', round3(x)); curX = x; break; } case 'V': { let y = rel ? curY + n[0] : n[0]; out.push('V', round3(y)); curY = y; break; } case 'C': { let [x1, y1] = toAbs(n[0], n[1]); let [x2, y2] = toAbs(n[2], n[3]); let [x, y ] = toAbs(n[4], n[5]); if (insideWorld(x1)) x1 += deltaX; if (insideWorld(x2)) x2 += deltaX; if (insideWorld(x )) x += deltaX; out.push('C', round3(x1), round3(y1), round3(x2), round3(y2), round3(x), round3(y) ); curX = x; curY = y; break; } case 'S': { let [x2, y2] = toAbs(n[0], n[1]); let [x, y ] = toAbs(n[2], n[3]); if (insideWorld(x2)) x2 += deltaX; if (insideWorld(x )) x += deltaX; out.push('S', round3(x2), round3(y2), round3(x), round3(y)); curX = x; curY = y; break; } case 'Q': { let [x1, y1] = toAbs(n[0], n[1]); let [x, y ] = toAbs(n[2], n[3]); if (insideWorld(x1)) x1 += deltaX; if (insideWorld(x )) x += deltaX; out.push('Q', round3(x1), round3(y1), round3(x), round3(y)); curX = x; curY = y; break; } case 'A': { const rx = n[0], ry = n[1], rot = n[2], laf = n[3], sf = n[4]; let [x, y] = toAbs(n[5], n[6]); if (insideWorld(x)) x += deltaX; out.push('A', rx, ry, rot, laf, sf, round3(x), round3(y)); curX = x; curY = y; break; } } } } } pathEl.setAttribute('d', out.join(' ').replace(/\s+/g, ' ').trim()); } // -------------------- Door logic (svg#dorpel) -------------------- function applyDoorDiff(diff) { if (!diff) return; const g = document.querySelector('svg g#HS-vastedeur'); if (!g) return; const svg = g.ownerSVGElement || document.querySelector('svg'); if (!svg) return; const shiftRect = svg.querySelector('#x-shiftbox-sponning-HS-deur rect'); if (!shiftRect) return; const minX = toNum(shiftRect.getAttribute('x')); const w = toNum(shiftRect.getAttribute('width')); if (minX === null || w === null) return; const maxX = minX + w; const { x: txBefore, y: tyBefore } = parseTranslate(g.getAttribute('transform')); const path = g.querySelector(':scope > path#HSvullat'); if (path) normalizePathToAbsoluteOnce(path); const deltaX = +diff; if (path) { adjustPathXInsideBox_World(path, minX, maxX, deltaX, txBefore); } const firstRect = getDirectChild(g, 'rect'); if (firstRect) { const x0 = toNum(firstRect.getAttribute('x')); if (x0 !== null) { const worldX = x0 + txBefore; if (worldX >= minX && worldX <= maxX) { firstRect.setAttribute('x', String(x0 + deltaX)); } } } setTranslate(g, txBefore - diff, tyBefore); } // -------------------- Extra logic (svg#alle-vakken) -------------------- function applyDiffToAlleVakken(diff) { if (!diff) return; const svg = document.querySelector('svg#alle-vakken'); if (!svg) return; const vakSelector = 'g[id^="vak-"][id*="-HS-LS"], g[id^="vak-"][id*="-HS-RS"], g[id^="vak-"][id*="-HS-LR"]'; svg.querySelectorAll(vakSelector).forEach((g) => { const rects = g.querySelectorAll('rect[data-name="vastedeur"], rect[id^="vastedeur"]'); rects.forEach((r) => { shiftRectYOrTranslateYByDiff(r, diff); const fill = r.getAttribute('fill') || ''; const gradId = parseUrlId(fill); if (gradId) shiftGradientYByDiff(svg, gradId, diff); }); }); } // -------------------- Main apply -------------------- function applyDiffToSvg(diff, correctedYFactor = 1) { const correctedDiff = diff * correctedYFactor; // 1) NEUT polygons: per group indices document.querySelectorAll('svg polygon[id^="neut"]').forEach((poly) => { const gid = poly.closest('g[id]')?.id || ''; const indices = isLRGroup(gid) ? INDICES_NEUT_LR : (isTGroup(gid) ? INDICES_NEUT_T : null); if (!indices) return; shiftPolygonByDiff(poly, indices, correctedDiff); }); // 2) VASTBASIS polygons: indices 1,3,5,7,9,11 document.querySelectorAll('svg polygon[id^="vastbasis"]').forEach((poly) => { shiftPolygonByDiff(poly, INDICES_VASTBASIS, correctedDiff); }); // 3) Vastnok rects (dorpel svg) document .querySelectorAll('svg rect[data-name="vastnok"], svg rect[id^="vastnok"]') .forEach((r) => shiftRectYByDiff(r, correctedDiff)); // 4) Door (dorpel svg) applyDoorDiff(diff); // 5) Extra: alle-vakken svg applyDiffToAlleVakken(diff); } // ====== Onderhuidse delta vanuit andere scripts (bv. deurdikte) ====== document.addEventListener('hs:applyTussenmaatDelta', (e) => { const diff = e?.detail?.diff; if (!Number.isFinite(diff) || diff === 0) return; // Vanuit form_fields[deurdikte]: // neut / vastbasis / vastnok moeten factor x2 blijven bewegen. applyDiffToSvg(diff, 2); }); // -------------------- Input (number) delta -------------------- function clamp(n, min, max) { if (!Number.isFinite(n)) return null; if (Number.isFinite(min)) n = Math.max(min, n); if (Number.isFinite(max)) n = Math.min(max, n); return n; } function init() { const input = document.getElementById(INPUT_ID); if (!input) return; const minVal = toNum(input.getAttribute('min')); const maxVal = toNum(input.getAttribute('max')); if (input.dataset.prev == null) { const v0 = clamp(toNum(input.value), minVal, maxVal); input.dataset.prev = String(v0 ?? 0); } const handler = () => { const newValRaw = toNum(input.value); const newVal = clamp(newValRaw, minVal, maxVal); if (newVal === null) return; const prevVal = toNum(input.dataset.prev); if (prevVal === null) { input.dataset.prev = String(newVal); return; } const diff = newVal - prevVal; if (!diff) return; // Vanuit form_fields[hstussenmaat]: // normale factor 1 voor neut / vastbasis / vastnok. applyDiffToSvg(diff, 1); input.dataset.prev = String(newVal); }; input.addEventListener('input', handler); input.addEventListener('change', handler); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init, { once: true }); } else { init(); } })();
//// Tussenmaat HS deuren //// (() => { const INPUT_ID = 'form-field-hstussenmaat'; // === AANGEPASTE indices (zoals gevraagd) === // Binnen #neut*: // - LR-group -> indices 5,7 // - T-group -> indices 17,19 const INDICES_NEUT_LR = [5, 7]; const INDICES_NEUT_T = [17, 19]; // Binnen #vastbasis* -> indices 1,3,5,7,9,11 const INDICES_VASTBASIS = [1, 3, 5, 7, 9, 11]; // Group-detectie ongewijzigd const isLRGroup = (gid) => gid.includes('HS-L-V') || gid.includes('HS-R-V'); const T_KEYS = ['HS-T1-UV','HS-T1-VU','HS-T2-UV','HS-T2-VU','HS-T3-UV','HS-T3-VU','HS-T4-UV','HS-T4-VU']; const isTGroup = (gid) => T_KEYS.some(k => gid.includes(k)); const toNum = (v) => { const n = Number(v); return Number.isFinite(n) ? n : null; }; // -------------------- POLYGON helpers -------------------- const parsePoints = (s) => (s || '').trim().split(/[\s,]+/).map(Number); const serializePoints = (arr) => arr.join(' '); function shiftPolygonByDiff(poly, flatIndices, diff) { if (!diff) return; const ptsStr = poly.getAttribute('points'); if (!ptsStr) return; const pts = parsePoints(ptsStr); let changed = false; for (const idx of flatIndices) { if (idx >= 0 && idx < pts.length && Number.isFinite(pts[idx])) { pts[idx] += diff; changed = true; } } if (changed) poly.setAttribute('points', serializePoints(pts)); } // -------------------- RECT helper -------------------- function shiftRectYByDiff(rect, diff) { if (!diff) return; const y0 = toNum(rect.getAttribute('y')); if (y0 === null) return; rect.setAttribute('y', String(y0 + diff)); } // -------------------- TRANSLATE helpers -------------------- function parseTranslate(transformStr) { const m = /translate\(\s*([-\d.]+)(?:[\s,]+([-\d.]+))?\s*\)/.exec(transformStr || ''); if (!m) return { x: 0, y: 0 }; return { x: Number(m[1]), y: m[2] !== undefined ? Number(m[2]) : 0 }; } function setTranslate(el, x, y) { const t = el.getAttribute('transform') || ''; const re = /translate\(\s*([-\d.]+)(?:[\s,]+([-\d.]+))?\s*\)/; if (re.test(t)) { el.setAttribute('transform', t.replace(re, `translate(${x},${y})`)); } else { el.setAttribute('transform', t ? `translate(${x},${y}) ${t}` : `translate(${x},${y})`); } } function shiftTranslateYByDiff(el, diff) { if (!diff) return; const { x, y } = parseTranslate(el.getAttribute('transform')); setTranslate(el, x, y + diff); } // Shift rect by translate-y if present, otherwise by y attribute function shiftRectYOrTranslateYByDiff(rect, diff) { if (!diff || !rect) return; const t = rect.getAttribute('transform') || ''; if (/translate\(/.test(t)) { shiftTranslateYByDiff(rect, diff); return; } const y0 = toNum(rect.getAttribute('y')); if (y0 === null) return; rect.setAttribute('y', String(y0 + diff)); } // -------------------- GRADIENT helpers -------------------- function parseUrlId(fillValue) { const m = /url\(\s*#([^)]+)\s*\)/.exec(fillValue || ''); return m ? m[1] : null; } function shiftGradientYByDiff(svg, gradientId, diff) { if (!svg || !gradientId || !diff) return; const grad = svg.querySelector(`#${CSS.escape(gradientId)}`); if (!grad) return; const gt = grad.getAttribute('gradientTransform') || ''; if (/translate\(/.test(gt)) { const { x, y } = parseTranslate(gt); const re = /translate\(\s*([-\d.]+)(?:[\s,]+([-\d.]+))?\s*\)/; const newGT = re.test(gt) ? gt.replace(re, `translate(${x},${y + diff})`) : `translate(${x},${y + diff}) ${gt}`.trim(); grad.setAttribute('gradientTransform', newGT); return; } const y1 = toNum(grad.getAttribute('y1')); const y2 = toNum(grad.getAttribute('y2')); if (y1 !== null) grad.setAttribute('y1', String(y1 + diff)); if (y2 !== null) grad.setAttribute('y2', String(y2 + diff)); const cy = toNum(grad.getAttribute('cy')); const fy = toNum(grad.getAttribute('fy')); if (cy !== null) grad.setAttribute('cy', String(cy + diff)); if (fy !== null) grad.setAttribute('fy', String(fy + diff)); } // -------------------- PATH helpers -------------------- function parseTokens(d) { return d?.match(/[a-zA-Z]|-?\d*\.?\d+(?:e[-+]?\d+)?/g); } function round3(v) { return Math.round(v * 1000) / 1000; } function getDirectChild(g, tagName) { for (const node of g.children) { if (node.tagName && node.tagName.toLowerCase() === tagName.toLowerCase()) return node; } return null; } function normalizePathToAbsoluteOnce(pathEl) { if (!pathEl) return; if (pathEl.dataset.absNormalized === '1') return; const d = pathEl.getAttribute('d'); if (!d) return; const tokens = parseTokens(d); if (!tokens || !tokens.length) return; let i = 0, curX = 0, curY = 0, lastCmd = ''; const out = []; const specs = { M:2, L:2, T:2, H:1, V:1, C:6, S:4, Q:4, A:7, Z:0 }; const take = () => tokens[i++]; const isCmd = (t) => /^[a-zA-Z]$/.test(t); const toAbs = (x, y, rel) => rel ? [x + curX, y + curY] : [x, y]; while (i < tokens.length) { let cmd = take(); if (!isCmd(cmd)) { i--; cmd = lastCmd; } else { lastCmd = cmd; } const ABS = cmd.toUpperCase(); const rel = cmd !== ABS; if (ABS === 'Z') { out.push('Z'); continue; } const nPer = specs[ABS]; if (!nPer) continue; let buf = []; while (i < tokens.length && !isCmd(tokens[i])) { buf.push(parseFloat(take())); if (buf.length % nPer === 0) { const n = buf.splice(buf.length - nPer, nPer); switch (ABS) { case 'M': case 'L': case 'T': { let [x, y] = toAbs(n[0], n[1], rel); out.push(ABS, round3(x), round3(y)); curX = x; curY = y; break; } case 'H': { let x = rel ? curX + n[0] : n[0]; out.push('H', round3(x)); curX = x; break; } case 'V': { let y = rel ? curY + n[0] : n[0]; out.push('V', round3(y)); curY = y; break; } case 'C': { let [x1, y1] = toAbs(n[0], n[1], rel); let [x2, y2] = toAbs(n[2], n[3], rel); let [x, y ] = toAbs(n[4], n[5], rel); out.push('C', round3(x1), round3(y1), round3(x2), round3(y2), round3(x), round3(y) ); curX = x; curY = y; break; } case 'S': { let [x2, y2] = toAbs(n[0], n[1], rel); let [x, y ] = toAbs(n[2], n[3], rel); out.push('S', round3(x2), round3(y2), round3(x), round3(y)); curX = x; curY = y; break; } case 'Q': { let [x1, y1] = toAbs(n[0], n[1], rel); let [x, y ] = toAbs(n[2], n[3], rel); out.push('Q', round3(x1), round3(y1), round3(x), round3(y)); curX = x; curY = y; break; } case 'A': { const rx = n[0], ry = n[1], rot = n[2], laf = n[3], sf = n[4]; let [x, y] = toAbs(n[5], n[6], rel); out.push('A', rx, ry, rot, laf, sf, round3(x), round3(y)); curX = x; curY = y; break; } } } } } pathEl.setAttribute('d', out.join(' ').replace(/\s+/g, ' ').trim()); pathEl.dataset.absNormalized = '1'; } function adjustPathXInsideBox_World(pathEl, minX, maxX, deltaX, worldOffsetX) { if (!pathEl || !deltaX) return; const d = pathEl.getAttribute('d'); if (!d) return; const tokens = parseTokens(d); if (!tokens || !tokens.length) return; let i = 0, curX = 0, curY = 0, lastCmd = ''; const out = []; const specs = { M:2, L:2, T:2, H:1, V:1, C:6, S:4, Q:4, A:7, Z:0 }; const take = () => tokens[i++]; const isCmd = (t) => /^[a-zA-Z]$/.test(t); const insideWorld = (localX) => { const worldX = localX + worldOffsetX; return worldX >= minX && worldX <= maxX; }; while (i < tokens.length) { let cmd = take(); if (!isCmd(cmd)) { i--; cmd = lastCmd; } else { lastCmd = cmd; } const ABS = cmd.toUpperCase(); const rel = cmd !== ABS; if (ABS === 'Z') { out.push('Z'); continue; } const nPer = specs[ABS]; if (!nPer) continue; const toAbs = (x, y) => rel ? [x + curX, y + curY] : [x, y]; let buf = []; while (i < tokens.length && !isCmd(tokens[i])) { buf.push(parseFloat(take())); if (buf.length % nPer === 0) { const n = buf.splice(buf.length - nPer, nPer); switch (ABS) { case 'M': case 'L': case 'T': { let [x, y] = toAbs(n[0], n[1]); if (insideWorld(x)) x += deltaX; out.push(ABS, round3(x), round3(y)); curX = x; curY = y; break; } case 'H': { let x = rel ? curX + n[0] : n[0]; if (insideWorld(x)) x += deltaX; out.push('H', round3(x)); curX = x; break; } case 'V': { let y = rel ? curY + n[0] : n[0]; out.push('V', round3(y)); curY = y; break; } case 'C': { let [x1, y1] = toAbs(n[0], n[1]); let [x2, y2] = toAbs(n[2], n[3]); let [x, y ] = toAbs(n[4], n[5]); if (insideWorld(x1)) x1 += deltaX; if (insideWorld(x2)) x2 += deltaX; if (insideWorld(x )) x += deltaX; out.push('C', round3(x1), round3(y1), round3(x2), round3(y2), round3(x), round3(y) ); curX = x; curY = y; break; } case 'S': { let [x2, y2] = toAbs(n[0], n[1]); let [x, y ] = toAbs(n[2], n[3]); if (insideWorld(x2)) x2 += deltaX; if (insideWorld(x )) x += deltaX; out.push('S', round3(x2), round3(y2), round3(x), round3(y)); curX = x; curY = y; break; } case 'Q': { let [x1, y1] = toAbs(n[0], n[1]); let [x, y ] = toAbs(n[2], n[3]); if (insideWorld(x1)) x1 += deltaX; if (insideWorld(x )) x += deltaX; out.push('Q', round3(x1), round3(y1), round3(x), round3(y)); curX = x; curY = y; break; } case 'A': { const rx = n[0], ry = n[1], rot = n[2], laf = n[3], sf = n[4]; let [x, y] = toAbs(n[5], n[6]); if (insideWorld(x)) x += deltaX; out.push('A', rx, ry, rot, laf, sf, round3(x), round3(y)); curX = x; curY = y; break; } } } } } pathEl.setAttribute('d', out.join(' ').replace(/\s+/g, ' ').trim()); } // -------------------- Door logic (svg#dorpel) -------------------- function applyDoorDiff(diff) { if (!diff) return; const g = document.querySelector('svg g#HS-vastedeur'); if (!g) return; const svg = g.ownerSVGElement || document.querySelector('svg'); if (!svg) return; const shiftRect = svg.querySelector('#x-shiftbox-sponning-HS-deur rect'); if (!shiftRect) return; const minX = toNum(shiftRect.getAttribute('x')); const w = toNum(shiftRect.getAttribute('width')); if (minX === null || w === null) return; const maxX = minX + w; const { x: txBefore, y: tyBefore } = parseTranslate(g.getAttribute('transform')); const path = g.querySelector(':scope > path#HSvullat'); if (path) normalizePathToAbsoluteOnce(path); const deltaX = +diff; if (path) { adjustPathXInsideBox_World(path, minX, maxX, deltaX, txBefore); } const firstRect = getDirectChild(g, 'rect'); if (firstRect) { const x0 = toNum(firstRect.getAttribute('x')); if (x0 !== null) { const worldX = x0 + txBefore; if (worldX >= minX && worldX <= maxX) { firstRect.setAttribute('x', String(x0 + deltaX)); } } } setTranslate(g, txBefore - diff, tyBefore); } // -------------------- Extra logic (svg#alle-vakken) -------------------- function applyDiffToAlleVakken(diff) { if (!diff) return; const svg = document.querySelector('svg#alle-vakken'); if (!svg) return; const vakSelector = 'g[id^="vak-"][id*="-HS-LS"], g[id^="vak-"][id*="-HS-RS"], g[id^="vak-"][id*="-HS-LR"]'; svg.querySelectorAll(vakSelector).forEach((g) => { const rects = g.querySelectorAll('rect[data-name="vastedeur"], rect[id^="vastedeur"]'); rects.forEach((r) => { shiftRectYOrTranslateYByDiff(r, diff); const fill = r.getAttribute('fill') || ''; const gradId = parseUrlId(fill); if (gradId) shiftGradientYByDiff(svg, gradId, diff); }); }); } // -------------------- Main apply -------------------- function applyDiffToSvg(diff) { // 1) NEUT polygons: per group indices document.querySelectorAll('svg polygon[id^="neut"]').forEach((poly) => { const gid = poly.closest('g[id]')?.id || ''; const indices = isLRGroup(gid) ? INDICES_NEUT_LR : (isTGroup(gid) ? INDICES_NEUT_T : null); if (!indices) return; shiftPolygonByDiff(poly, indices, diff); }); // 2) VASTBASIS polygons: indices 1,3,5,7,9,11 document.querySelectorAll('svg polygon[id^="vastbasis"]').forEach((poly) => { shiftPolygonByDiff(poly, INDICES_VASTBASIS, diff); }); // 3) Vastnok rects (dorpel svg) document .querySelectorAll('svg rect[data-name="vastnok"], svg rect[id^="vastnok"]') .forEach((r) => shiftRectYByDiff(r, diff)); // 4) Door (dorpel svg) applyDoorDiff(diff); // 5) Extra: alle-vakken svg applyDiffToAlleVakken(diff); } // ====== Onderhuidse delta vanuit andere scripts (bv. deurdikte) ====== document.addEventListener('hs:applyTussenmaatDelta', (e) => { const diff = e?.detail?.diff; if (!Number.isFinite(diff) || diff === 0) return; applyDiffToSvg(diff); }); // -------------------- Input (number) delta -------------------- function clamp(n, min, max) { if (!Number.isFinite(n)) return null; if (Number.isFinite(min)) n = Math.max(min, n); if (Number.isFinite(max)) n = Math.min(max, n); return n; } function init() { const input = document.getElementById(INPUT_ID); if (!input) return; const minVal = toNum(input.getAttribute('min')); const maxVal = toNum(input.getAttribute('max')); if (input.dataset.prev == null) { const v0 = clamp(toNum(input.value), minVal, maxVal); input.dataset.prev = String(v0 ?? 0); } const handler = () => { const newValRaw = toNum(input.value); const newVal = clamp(newValRaw, minVal, maxVal); if (newVal === null) return; const prevVal = toNum(input.dataset.prev); if (prevVal === null) { input.dataset.prev = String(newVal); return; } const diff = newVal - prevVal; if (!diff) return; applyDiffToSvg(diff); input.dataset.prev = String(newVal); }; input.addEventListener('input', handler); input.addEventListener('change', handler); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init, { once: true }); } else { init(); } })();

Centreer #dorpel

GRID 3 x 3

KALKSPONNINGEN

X-boorgaten

KVT/HS/KVTPKU als BUVD NEUTEN
KVTPKI als BI NEUTEN

DORPELTYPE

DORPELDIKTE

Schuinte aanpassing BI/BU varianten #dorpel

DORPELBREEDTE

HOOGTE AFWATERINGSAANZET

DORPELSPONNING #DORPEL

 // BI (BIV): a – b ≥ 32

// BUVD/KVT: a – b ≥ 7

// HS: a – b ≥ 48 én a ≥ 114, b ≤ 67

Autoselect inputwaarde

Loodsponning 1-2-3

Sponningdikte >invoer< dan reactie, niet eerder

C3 / loodsponning / afwater alert

//// POPUPS BOORGATEN + AWATERING icm C3-STRIP //// const CFG_C3 = { svgId: 'dorpel', inputSelectors: [ '#form-field-dorpeldikte', '#form-field-dorpelbreedte', 'input[name="form_fields[c3strip]"]', 'input[name="form_fields[dorpeltype]"]', 'input[name="form_fields[aantal_loodsponningen]"]', // ✅ nieuw: vakken keuze triggert ook een re-check 'input[name="form_fields[vakken]"]' ], // ✅ gate: boorgaten-popup pas tonen nadat vakken is gekozen vakkenSelector: 'input[name="form_fields[vakken]"]', popupBoorgatenSelector: '#message-popup-BRG-C3', popupAfwateringsgatenSelector: '#message-popup-AFW-C3', c3Selector: '#C3-strip', boorgatenSelector: '[id^="boorgat-"]', afwaterMargeSelectors: [ '#BUVD_x5F_afwater_x5F_marge', '#BIV_x5F_afwater_x5F_marge' ], clearance: 0, debug: false }; /* ---------- Popups ---------- */ function togglePopup(sel, show) { const el = document.querySelector(sel); if (!el) return; el.style.display = show ? 'block' : 'none'; } /* ---------- AABB (boorgaten) ---------- */ function boxesTouchOrOverlap(a, b, clearance) { return !( a.right < b.left + clearance || a.left > b.right - clearance || a.bottom < b.top + clearance || a.top > b.bottom - clearance ); } function getBBoxAbs(el) { const ctm = el.getCTM(); if (!ctm) return null; const b = el.getBBox(); const pts = [ { x: b.x, y: b.y }, { x: b.x + b.width, y: b.y }, { x: b.x + b.width, y: b.y + b.height }, { x: b.x, y: b.y + b.height } ].map(p => ({ x: p.x * ctm.a + p.y * ctm.c + ctm.e, y: p.x * ctm.b + p.y * ctm.d + ctm.f })); const xs = pts.map(p => p.x); const ys = pts.map(p => p.y); return { left: Math.min(...xs), right: Math.max(...xs), top: Math.min(...ys), bottom: Math.max(...ys) }; } /* ---------- SAT (rotatie-bewust) ---------- */ function getBoxCornersAbs(el) { const ctm = el.getCTM(); if (!ctm) return null; const b = el.getBBox(); const cornersLocal = [ { x: b.x, y: b.y }, { x: b.x + b.width, y: b.y }, { x: b.x + b.width, y: b.y + b.height }, { x: b.x, y: b.y + b.height } ]; return cornersLocal.map(p => ({ x: p.x * ctm.a + p.y * ctm.c + ctm.e, y: p.x * ctm.b + p.y * ctm.d + ctm.f })); } function projectOnAxis(points, axis) { let min = Infinity, max = -Infinity; for (const p of points) { const d = p.x * axis.x + p.y * axis.y; if (d < min) min = d; if (d > max) max = d; } return { min, max }; } function rectOverlapSAT(pointsA, pointsB, clearance = 0) { const axes = []; const addAxesFrom = (pts) => { for (let i = 0; i < 2; i++) { const p1 = pts[i], p2 = pts[(i + 1) % 4]; const nx = -(p2.y - p1.y); const ny = (p2.x - p1.x); const len = Math.hypot(nx, ny) || 1; const ax = { x: nx / len, y: ny / len }; const key = ax.x.toFixed(6) + ',' + ax.y.toFixed(6); if (!axes.some(a => a.k === key)) axes.push({ ...ax, k: key }); } }; addAxesFrom(pointsA); addAxesFrom(pointsB); for (const axis of axes) { const a = projectOnAxis(pointsA, axis); const b = projectOnAxis(pointsB, axis); if (a.max < b.min - clearance || b.max < a.min - clearance) return false; } return true; } /* ---------- Misc ---------- */ function isHiddenSvg(el) { if (!el) return true; const dispAttr = (el.getAttribute('display') || '').toLowerCase(); if (dispAttr === 'none') return true; const dispCss = (getComputedStyle(el).display || '').toLowerCase(); return dispCss === 'none'; } function getSelectedDorpeltypeOriginal() { const checked = document.querySelector('input[name="form_fields[dorpeltype]"]:checked'); if (!checked) return null; return (checked.dataset.originalValue || checked.value || '').toUpperCase(); } // ✅ nieuw: is er een vakken-keuze gemaakt? function hasVakkenSelection() { return !!document.querySelector(`${CFG_C3.vakkenSelector}:checked`); } /* ---------- Core check ---------- */ function checkC3CollisionsAndToggle() { const svg = document.getElementById(CFG_C3.svgId); if (!svg) return; const c3Group = svg.querySelector(CFG_C3.c3Selector); const c3Rect = c3Group ? c3Group.querySelector('rect') : null; if (!c3Group || !c3Rect || isHiddenSvg(c3Group)) { togglePopup(CFG_C3.popupBoorgatenSelector, false); togglePopup(CFG_C3.popupAfwateringsgatenSelector, false); return; } const c3Box = getBBoxAbs(c3Rect); // ---- 1) Boorgaten (AABB) ---- const boorgaten = [...svg.querySelectorAll(CFG_C3.boorgatenSelector)]; let hitBoorgat = false; if (c3Box && boorgaten.length) { for (const g of boorgaten) { const gb = getBBoxAbs(g); if (gb && boxesTouchOrOverlap(c3Box, gb, CFG_C3.clearance)) { hitBoorgat = true; break; } } } // ✅ Gate: popup pas tonen na vakken-selectie togglePopup(CFG_C3.popupBoorgatenSelector, hitBoorgat && hasVakkenSelection()); // ---- 2) Afwater-marge (SAT) ---- const originalType = getSelectedDorpeltypeOriginal(); const excluded = ['KVT', 'HS']; if (excluded.includes(originalType)) { togglePopup(CFG_C3.popupAfwateringsgatenSelector, false); return; } const c3Pts = getBoxCornersAbs(c3Rect); let hitAfwater = false; if (c3Pts) { const selectorsToCheck = []; if (originalType === 'BUVD') selectorsToCheck.push('#BUVD_x5F_afwater_x5F_marge'); else if (originalType === 'BI' || originalType === 'BIV') selectorsToCheck.push('#BIV_x5F_afwater_x5F_marge'); else selectorsToCheck.push(...CFG_C3.afwaterMargeSelectors); for (const sel of selectorsToCheck) { const margeEl = svg.querySelector(sel); if (!margeEl) continue; const margePts = getBoxCornersAbs(margeEl); if (!margePts) continue; if (rectOverlapSAT(c3Pts, margePts, CFG_C3.clearance)) { hitAfwater = true; if (CFG_C3.debug) console.log('[C3] HIT afwater marge:', sel); break; } } } // afwater-popup blijft zoals het was (geen vakken-gate) togglePopup(CFG_C3.popupAfwateringsgatenSelector, hitAfwater); } /* ---------- Wiring (throttled) ---------- */ function setupC3Alerts() { let rafPending = false; const requestCheck = () => { if (rafPending) return; rafPending = true; requestAnimationFrame(() => { rafPending = false; checkC3CollisionsAndToggle(); }); }; CFG_C3.inputSelectors.forEach(sel => { document.querySelectorAll(sel).forEach(el => { el.addEventListener('input', requestCheck); el.addEventListener('change', requestCheck); }); }); document.addEventListener('change', requestCheck); const formRoot = document.querySelector('.elementor-form'); if (formRoot) { const mo = new MutationObserver(requestCheck); mo.observe(formRoot, { childList: true, subtree: true, attributes: true }); } requestCheck(); } document.addEventListener('DOMContentLoaded', setupC3Alerts);
//// TOOLTIP ////
Beweeg muis over dorpel om vakvulling te zien
document.addEventListener("DOMContentLoaded", function () { function checkDropdowns() { // Commenting out sessionStorage limit for dtstimber //let popupShown = sessionStorage.getItem("popupShown"); //if (popupShown) return; let dropdowns = document.querySelectorAll("select[name*='VVU']:not(.cfef-hidden), select[name*='VVI']:not(.cfef-hidden)"); let visibleDropdowns = Array.from(dropdowns).filter(select => select.offsetParent !== null); let allFilled = visibleDropdowns.every(select => select.value.toLowerCase() !== 'vul in'); if (allFilled && visibleDropdowns.length > 0) { showPopup(); simulateMouseMovement(); } } function showPopup() { let popup = document.getElementById("tooltip-popup"); if (popup) { setTimeout(() => { popup.style.display = "block"; }, 50); // ⬅️ Delay forces Firefox to properly register the display change document.addEventListener("click", hidePopup, { once: true }); setTimeout(hidePopup, 5000); } } function hidePopup() { let popup = document.getElementById("tooltip-popup"); if (popup) { popup.style.display = "none"; } } function simulateMouseMovement() { let hitboxes = document.querySelectorAll("svg#alle-vakken .hitbox"); if (hitboxes.length === 0) return; let positions = Array.from(hitboxes).map(hitbox => { let rect = hitbox.getBoundingClientRect(); return { x: rect.x, width: rect.width }; }); positions.sort((a, b) => a.x - b.x); // Sort hitboxes by X position let startX = positions[0].x; let endX = positions[positions.length - 1].x + positions[positions.length - 1].width; let svg = document.querySelector("svg#alle-vakken"); if (!svg) return; let cursor = document.createElement("div"); cursor.innerHTML = ``; cursor.style.position = "fixed"; cursor.style.zIndex = "10000"; document.body.appendChild(cursor); let duration = 2500; // 1.5 seconds per movement let frameRate = 60; // Smooth animation let frameTime = 1000 / frameRate; let steps = (duration / frameTime); function easeInOutQuad(t) { return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2; } function animateCursor(start, end, callback) { let step = 0; let moveCursor = setInterval(() => { let progress = easeInOutQuad(step / steps); let currentX = start + (end - start) * progress + 100; let currentY = svg.getBoundingClientRect().top + 20 + 30; cursor.style.left = `${currentX}px`; cursor.style.top = `${currentY}px`; step++; if (step > steps) { clearInterval(moveCursor); if (callback) callback(); } }, frameTime); } animateCursor(startX, endX, () => { animateCursor(endX, startX, () => { document.body.removeChild(cursor); }); }); } document.addEventListener("change", function (event) { if (event.target.matches("select[name*='VVU'], select[name*='VVI']")) { checkDropdowns(); } }); });

WEL OF GEEN VOORSPONNING BI

WEL OF GEEN VOORSPONNING BU

Kozijnmerk check

SPINNER

SLUITPOTJES

SLUITPOTJES

//// SLUITPOTJES //// document.addEventListener("DOMContentLoaded", () => { const sluitpotSelect = document.getElementById("form-field-sluitpot_type"); const xSluitpotInput = document.querySelector('input[name="form_fields[X_sluitpot]"]'); const ySluitpotInput = document.querySelector('input[name="form_fields[Y_sluitpot]"]'); const alleVakkenSvg = document.querySelector("svg#alle-vakken"); const dorpelTypeRadios = document.querySelectorAll('input[name="form_fields[dorpeltype]"]'); const dorpelBreedteInput = document.getElementById("form-field-dorpelbreedte"); // dorpelbreedte veld const resetForm = () => { sluitpotSelect.value = "vul in"; xSluitpotInput.value = ""; ySluitpotInput.value = ""; alleVakkenSvg.querySelectorAll('g[id^="sluitpot-"]').forEach((g) => { g.setAttribute("display", "none"); g.removeAttribute("transform"); }); }; const setSluitpotToNee = () => { const sluitpotRadioNee = document.querySelector('input[name="form_fields[sluitpot]"][value="nee"]'); if (sluitpotRadioNee) { sluitpotRadioNee.checked = true; resetForm(); } }; const calculateTransformValues = (type) => { const xSluitpot = parseFloat(xSluitpotInput.value) || 0; const ySluitpot = parseFloat(ySluitpotInput.value) || 0; // dorpelbreedte: 114 is het nul-punt van y const dorpelBreedte = parseFloat(dorpelBreedteInput?.value) || 114; const dorpelOffset = dorpelBreedte - 114; const fetchCheckedValue = (name) => { // 1) Oud gedrag: eerst kijken naar een geselecteerde radio const checkedInput = document.querySelector(`input[name="${name}"]:checked`); if (checkedInput) { return parseFloat(checkedInput.value) || 0; } // 2) Nieuw: als er geen radio geselecteerd is, kijken naar een number input met dezelfde name const numberInput = document.querySelector(`input[name="${name}"][type="number"]`); if (numberInput) { return parseFloat(numberInput.value) || 0; } // 3) Anders geen waarde return 0; }; const liOrLuValues = { 1: fetchCheckedValue(`form_fields[1L${type}]`), 2: fetchCheckedValue(`form_fields[2L${type}]`), 3: fetchCheckedValue(`form_fields[3L${type}]`), 4: fetchCheckedValue(`form_fields[4L${type}]`), 5: fetchCheckedValue(`form_fields[5L${type}]`), }; const tiOrTuValues = (prefix, count) => { let values = []; for (let i = 1; i <= count; i++) { values.push(fetchCheckedValue(`form_fields[${prefix}T${type}${i}]`)); } return values; }; const dmiOrDmuValues = (prefix, count) => { let values = []; for (let i = 1; i <= count; i++) { values.push(fetchCheckedValue(`form_fields[${prefix}DM${type}${i}]`)); } return values; }; const vvValues = {}; for (let i = 1; i <= 5; i++) { for (let j = 1; j <= i; j++) { vvValues[`${i}VV${type}${j}`] = document.querySelector( `select[name="form_fields[${i}VV${type}${j}]"]` )?.value; } } let xValue = xSluitpot; // y is relatief aan dorpelbreedte, met 114 als nul-punt // bij dorpelbreedte = 114 is gedrag identiek aan vroeger: yValue = -ySluitpot let yValue = -ySluitpot + dorpelOffset; const applyLogic = (vvValue, prefix, liOrLu, tiArray, dmiArray) => { if (vvValue === "kaderdeur" || vvValue === "deur") { let value = xSluitpot - liOrLu - tiArray.reduce((a, b) => a + b, 0) - dmiArray.reduce((a, b) => a + b, 0); xValue = (value + dmiArray.reduce((a, b) => a + b, 0)) / 3.84615384615 + (liOrLu + tiArray.reduce((a, b) => a + b, 0)); } }; for (let i = 1; i <= 5; i++) { for (let j = 1; j <= i; j++) { applyLogic( vvValues[`${i}VV${type}${j}`], `${i}`, liOrLuValues[i], tiOrTuValues(i, j - 1), dmiOrDmuValues(i, j - 1) ); } } return { xValue, yValue }; }; const updateSluitpotDisplay = () => { const selectedOption = sluitpotSelect.value; if (selectedOption === "vul in") { return; } // NIEUW: alleen tonen als zowel X als Y zijn ingevuld const xFilled = xSluitpotInput.value !== "" && !isNaN(parseFloat(xSluitpotInput.value)); const yFilled = ySluitpotInput.value !== "" && !isNaN(parseFloat(ySluitpotInput.value)); if (!xFilled || !yFilled) { // Als één van beide leeg is, sluitpot verbergen alleVakkenSvg.querySelectorAll('g[id^="sluitpot-"]').forEach((g) => { g.setAttribute("display", "none"); }); return; } const selectedDorpelType = [...dorpelTypeRadios].find((radio) => radio.checked)?.value; if (!selectedDorpelType) return; const type = selectedDorpelType === "BUVD" ? "U" : selectedDorpelType === "BI" ? "I" : null; if (!type) return; const { xValue, yValue } = calculateTransformValues(type); alleVakkenSvg.querySelectorAll('g[id^="sluitpot-"]').forEach((g) => { g.setAttribute("display", "none"); }); const selectedG = alleVakkenSvg.querySelector(`g#sluitpot-${selectedOption}`); if (selectedG) { if (xValue && yValue) { selectedG.setAttribute("transform", `translate(${xValue}, ${yValue})`); selectedG.setAttribute("display", "block"); } } }; sluitpotSelect.addEventListener("change", updateSluitpotDisplay); ySluitpotInput.addEventListener("input", updateSluitpotDisplay); // NIEUW: bij wijziging X-sluitpot ook aanpassen if (xSluitpotInput) { xSluitpotInput.addEventListener("input", updateSluitpotDisplay); } // bij wijziging dorpelbreedte y-positie opnieuw berekenen if (dorpelBreedteInput) { dorpelBreedteInput.addEventListener("input", updateSluitpotDisplay); } document.querySelectorAll('input[name="form_fields[sluitpot]"]').forEach((radio) => { radio.addEventListener("change", (e) => { if (e.target.value === "nee") { resetForm(); } }); }); const vvDropdowns = document.querySelectorAll('select[name^="form_fields["][name*="VV"]'); vvDropdowns.forEach((dropdown) => { let previousValue = dropdown.value; dropdown.addEventListener("change", () => { if ( (previousValue === "deur" || previousValue === "kaderdeur" || previousValue === "dubbelkaderdeur") && dropdown.value !== "deur" && dropdown.value !== "kaderdeur" && dropdown.value !== "dubbelkaderdeur" ) { setSluitpotToNee(); } previousValue = dropdown.value; }); }); document .querySelectorAll( 'input[name="form_fields[vakken]"], input[name="form_fields[dorpeltype]"], select[name^="form_fields[3VV"]' ) .forEach((field) => { field.addEventListener("change", () => { setSluitpotToNee(); }); }); });
//// SLUITPOTJES //// document.addEventListener("DOMContentLoaded", () => { const sluitpotSelect = document.getElementById("form-field-sluitpot_type"); const xSluitpotInput = document.querySelector('input[name="form_fields[X_sluitpot]"]'); const ySluitpotInput = document.querySelector('input[name="form_fields[Y_sluitpot]"]'); const alleVakkenSvg = document.querySelector("svg#alle-vakken"); const dorpelTypeRadios = document.querySelectorAll('input[name="form_fields[dorpeltype]"]'); const dorpelBreedteInput = document.getElementById("form-field-dorpelbreedte"); // dorpelbreedte veld const resetForm = () => { sluitpotSelect.value = "vul in"; xSluitpotInput.value = ""; ySluitpotInput.value = ""; alleVakkenSvg.querySelectorAll('g[id^="sluitpot-"]').forEach((g) => { g.setAttribute("display", "none"); g.removeAttribute("transform"); }); }; const setSluitpotToNee = () => { const sluitpotRadioNee = document.querySelector('input[name="form_fields[sluitpot]"][value="nee"]'); if (sluitpotRadioNee) { sluitpotRadioNee.checked = true; resetForm(); } }; const calculateTransformValues = (type) => { const xSluitpot = parseFloat(xSluitpotInput.value) || 0; const ySluitpot = parseFloat(ySluitpotInput.value) || 0; // dorpelbreedte: 114 is het nul-punt van y const dorpelBreedte = parseFloat(dorpelBreedteInput?.value) || 114; const dorpelOffset = dorpelBreedte - 114; const fetchCheckedValue = (name) => { // 1) Oud gedrag: eerst kijken naar een geselecteerde radio const checkedInput = document.querySelector(`input[name="${name}"]:checked`); if (checkedInput) { return parseFloat(checkedInput.value) || 0; } // 2) Nieuw: als er geen radio geselecteerd is, kijken naar een number input met dezelfde name const numberInput = document.querySelector(`input[name="${name}"][type="number"]`); if (numberInput) { return parseFloat(numberInput.value) || 0; } // 3) Anders geen waarde return 0; }; const liOrLuValues = { 1: fetchCheckedValue(`form_fields[1L${type}]`), 2: fetchCheckedValue(`form_fields[2L${type}]`), 3: fetchCheckedValue(`form_fields[3L${type}]`), 4: fetchCheckedValue(`form_fields[4L${type}]`), 5: fetchCheckedValue(`form_fields[5L${type}]`), }; const tiOrTuValues = (prefix, count) => { let values = []; for (let i = 1; i <= count; i++) { values.push(fetchCheckedValue(`form_fields[${prefix}T${type}${i}]`)); } return values; }; const dmiOrDmuValues = (prefix, count) => { let values = []; for (let i = 1; i <= count; i++) { values.push(fetchCheckedValue(`form_fields[${prefix}DM${type}${i}]`)); } return values; }; const vvValues = {}; for (let i = 1; i <= 5; i++) { for (let j = 1; j <= i; j++) { vvValues[`${i}VV${type}${j}`] = document.querySelector( `select[name="form_fields[${i}VV${type}${j}]"]` )?.value; } } let xValue = xSluitpot; // y is relatief aan dorpelbreedte, met 114 als nul-punt // bij dorpelbreedte = 114 is gedrag identiek aan vroeger: yValue = -ySluitpot let yValue = -ySluitpot + dorpelOffset; const applyLogic = (vvValue, prefix, liOrLu, tiArray, dmiArray) => { if (vvValue === "kaderdeur" || vvValue === "deur") { let value = xSluitpot - liOrLu - tiArray.reduce((a, b) => a + b, 0) - dmiArray.reduce((a, b) => a + b, 0); xValue = (value + dmiArray.reduce((a, b) => a + b, 0)) / 3.84615384615 + (liOrLu + tiArray.reduce((a, b) => a + b, 0)); } }; for (let i = 1; i <= 5; i++) { for (let j = 1; j <= i; j++) { applyLogic( vvValues[`${i}VV${type}${j}`], `${i}`, liOrLuValues[i], tiOrTuValues(i, j - 1), dmiOrDmuValues(i, j - 1) ); } } return { xValue, yValue }; }; const updateSluitpotDisplay = () => { const selectedOption = sluitpotSelect.value; if (selectedOption === "vul in") { return; } // NIEUW: alleen tonen als zowel X als Y zijn ingevuld const xFilled = xSluitpotInput.value !== "" && !isNaN(parseFloat(xSluitpotInput.value)); const yFilled = ySluitpotInput.value !== "" && !isNaN(parseFloat(ySluitpotInput.value)); if (!xFilled || !yFilled) { // Als één van beide leeg is, sluitpot verbergen alleVakkenSvg.querySelectorAll('g[id^="sluitpot-"]').forEach((g) => { g.setAttribute("display", "none"); }); return; } const selectedDorpelType = [...dorpelTypeRadios].find((radio) => radio.checked)?.value; if (!selectedDorpelType) return; const type = selectedDorpelType === "BUVD" ? "U" : selectedDorpelType === "BI" ? "I" : null; if (!type) return; const { xValue, yValue } = calculateTransformValues(type); alleVakkenSvg.querySelectorAll('g[id^="sluitpot-"]').forEach((g) => { g.setAttribute("display", "none"); }); const selectedG = alleVakkenSvg.querySelector(`g#sluitpot-${selectedOption}`); if (selectedG) { if (xValue && yValue) { selectedG.setAttribute("transform", `translate(${xValue}, ${yValue})`); selectedG.setAttribute("display", "block"); } } }; sluitpotSelect.addEventListener("change", updateSluitpotDisplay); ySluitpotInput.addEventListener("input", updateSluitpotDisplay); // NIEUW: bij wijziging X-sluitpot ook aanpassen if (xSluitpotInput) { xSluitpotInput.addEventListener("input", updateSluitpotDisplay); } // bij wijziging dorpelbreedte y-positie opnieuw berekenen if (dorpelBreedteInput) { dorpelBreedteInput.addEventListener("input", updateSluitpotDisplay); } document.querySelectorAll('input[name="form_fields[sluitpot]"]').forEach((radio) => { radio.addEventListener("change", (e) => { if (e.target.value === "nee") { resetForm(); } }); }); const vvDropdowns = document.querySelectorAll('select[name^="form_fields["][name*="VV"]'); vvDropdowns.forEach((dropdown) => { let previousValue = dropdown.value; dropdown.addEventListener("change", () => { if ( (previousValue === "deur" || previousValue === "kaderdeur") && dropdown.value !== "deur" && dropdown.value !== "kaderdeur" ) { setSluitpotToNee(); } previousValue = dropdown.value; }); }); document .querySelectorAll( 'input[name="form_fields[vakken]"], input[name="form_fields[dorpeltype]"], select[name^="form_fields[3VV"]' ) .forEach((field) => { field.addEventListener("change", () => { setSluitpotToNee(); }); }); });

DRAAIKIEP NOKAFMETINGEN

VOORSPONNING BLOCKERS

BU DRAAIKIEP/WISSEL INITIEEL

HS NEUTSPONNINGEN

//// Sponningbreedte HS neuten EXCL //// (function () { // Config: hoeveel SVG-units per invoereenheid const SCALE = 1; // Puntindices die aangepast moeten worden (0-based in "x y x y x y ...") const TARGET_POINT_INDICES = [6, 8, 10, 12]; // Zelfde triggerwaarden als bij BU-script const VVU_TRIGGER_VALUES = new Set([ "draaikiep", "dubbelglas binnen", "tripleglas binnen" ]); // === Helpers voor points === function parsePoints(pointsStr) { if (!pointsStr) return []; return pointsStr .trim() .replace(/,/g, " ") .split(/\s+/) .map(function (v) { return parseFloat(v); }) .filter(function (v) { return !isNaN(v); }); } function stringifyPoints(pointsArr) { return pointsArr.join(" "); } // Test of polygon onder een bepaald target-id hangt function polyIsUnderTarget(poly, targetId) { let node = poly; while (node && node.nodeType === 1) { if ((node.id || "") === targetId) return true; if (node.tagName && node.tagName.toLowerCase() === "svg") break; node = node.parentElement; } return false; } // Bepaal actieve VVU-keys (zelfde keys als in EXCEPTIONS_MAP) function getActiveVVUKeys() { const map = window.__BU_EXCEPTIONS_MAP || {}; const keys = Object.keys(map); const active = []; keys.forEach(function (key) { const sel = document.getElementById("form-field-" + key); if (!sel) return; const val = (sel.value || "").toLowerCase(); if (VVU_TRIGGER_VALUES.has(val)) { active.push(key); } }); return active; } // Bepaal welke indices we nog mogen aanpassen voor deze polygon function indicesAfterHSExclusion(baseIndices, poly) { const map = window.__BU_EXCEPTIONS_MAP || {}; const activeKeys = getActiveVVUKeys(); if (!activeKeys.length) return baseIndices; // geen uitzonderingen actief const excluded = new Set(); activeKeys.forEach(function (key) { const rules = map[key] || []; rules.forEach(function (rule) { // Alleen HS-targets relevant voor dit script if (!rule.target || rule.target.indexOf("HS-") === -1) return; if (polyIsUnderTarget(poly, rule.target)) { (rule.exclude || []).forEach(function (idx) { excluded.add(idx); }); } }); }); if (!excluded.size) return baseIndices; return baseIndices.filter(function (idx) { return !excluded.has(idx); }); } // Past polygon-punten aan op basis van delta (alleen x-coördinaten) // -> gebruikt door handleChange function adjustPolygon(poly, delta, indices) { if (!poly) return; if (!indices || !indices.length) return; var pointsStr = poly.getAttribute("points"); if (!pointsStr) return; var points = parsePoints(pointsStr); if (!points.length) return; indices.forEach(function (idx) { if (idx >= 0 && idx < points.length) { points[idx] = points[idx] + delta; } }); poly.setAttribute("points", stringifyPoints(points)); } // Alle relevante neut-polygons van HS-T VU/UV/SU/US function getTargetPolygons() { var selector = 'svg g[id*="HS-T"][id$="-VU"] polygon[data-name="neut"],' + 'svg g[id*="HS-T"][id$="-UV"] polygon[data-name="neut"],' + 'svg g[id*="HS-T"][id$="-SU"] polygon[data-name="neut"],' + 'svg g[id*="HS-T"][id$="-US"] polygon[data-name="neut"]'; return Array.prototype.slice.call(document.querySelectorAll(selector)); } function init() { var input = document.getElementById("form-field-BU_sponningbreedte"); if (!input) return; var polygons = getTargetPolygons(); if (!polygons.length) return; var prevValue = parseFloat(input.value) || parseFloat(input.placeholder) || parseFloat(input.getAttribute("data-last-valid-value")) || 0; function handleChange() { var newVal = parseFloat(input.value); if (isNaN(newVal)) return; var deltaBase = (newVal - prevValue) * SCALE; if (!deltaBase) return; polygons.forEach(function (poly) { var group = poly.closest("g"); var gid = group ? (group.id || "") : ""; // HS-T-VU en HS-T-SU bewegen in omgekeerde richting var effectiveDelta = deltaBase; if (/-VU$/.test(gid) || /-SU$/.test(gid)) { effectiveDelta = -deltaBase; } // Bepaal dynamisch welke indices we nog mogen aanpassen, // rekening houdend met de actieve VVU-uitsluitingen. var indices = indicesAfterHSExclusion(TARGET_POINT_INDICES, poly); if (!indices.length) return; // alles uitgesloten adjustPolygon(poly, effectiveDelta, indices); }); prevValue = newVal; } input.addEventListener("input", handleChange); input.addEventListener("change", handleChange); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } })();
//// Voorsponningbreedte HS neuten //// document.addEventListener('DOMContentLoaded', function () { // Formulierveld const input = document.getElementById('form-field-BU_Vsponningbreedte'); if (!input) return; let previousValue = parseFloat(input.value) || 0; // Helper: check of een bepaalde groep + puntenindex-combinatie is uitgesloten function isExcludedForBU(groupId, positions) { const exclusions = window.__BU_VSPB_EXCEPTIONS_MAP; if (!exclusions || !exclusions.has) return false; const key = `${groupId}:${positions.join(",")}`; return exclusions.has(key); } function shiftPolygonPoints(selector, deltaX) { if (!deltaX) return; const polygons = document.querySelectorAll(selector); polygons.forEach(polygon => { const pid = polygon.id || ""; if (!pid.match(/^neut/)) return; // Alleen #neut en #neut* // Zoek de omliggende met id, bijv. "vak-2-HS-T1-UV" const group = polygon.closest('g[id]'); const groupId = group ? group.id : ""; // Deze HS-neut gebruikt punten 10 en 12 (zoals in de BU-exclusions) const positions = [10, 12]; // Als deze combinatie in de BU-exclusions zit: NIET verschuiven if (groupId && isExcludedForBU(groupId, positions)) { return; } let pts = polygon.getAttribute('points'); if (!pts) return; let tokens = pts.trim().split(/\s+/); positions.forEach(idx => { if (idx < tokens.length) { const v = parseFloat(tokens[idx]); if (!isNaN(v)) { tokens[idx] = (v + deltaX).toString(); } } }); polygon.setAttribute('points', tokens.join(' ')); }); } function applyDelta(delta) { if (!delta) return; // VU & SU → -delta shiftPolygonPoints( 'g[id*="HS-T"][id$="-VU"] polygon, g[id*="HS-T"][id$="-SU"] polygon', -delta ); // UV & US → +delta shiftPolygonPoints( 'g[id*="HS-T"][id$="-UV"] polygon, g[id*="HS-T"][id$="-US"] polygon', delta ); } // Luister naar invoerwijzigingen input.addEventListener('input', function (e) { const newValue = parseFloat(e.target.value); if (isNaN(newValue)) return; const delta = newValue - previousValue; if (!delta) return; applyDelta(delta); previousValue = newValue; }); });

BU NEUTSPONNINGEN VAR/VAST

VARIATIE DRAAIKIEPSPONNINGEN

VARIATIE WISSELSPONNINGEN

21/25MM SPONNING ON KADERDEUR

//// 25mm SPONNING ON DUBBELKADERDEUR / DRAAIKIEP 1VVI T/M 5VVI //// (function ($) { function updatePolygonPoints(elementId, positions, adjustment) { const element = document.getElementById(elementId); if (!element) return; const polygon = element.querySelector("polygon"); if (!polygon) return; const points = polygon.getAttribute("points").split(" ").map(Number); positions.forEach((pos) => { points[pos] = points[pos] + adjustment; }); polygon.setAttribute("points", points.join(" ")); } const fixedSponningValue = 25; const targetTypes = ["dubbelkaderdeur", "draaikiep"]; const state = { "1VVI": [0], "2VVI": [0, 0], "3VVI": [0, 0, 0], "4VVI": [0, 0, 0, 0], "5VVI": [0, 0, 0, 0, 0] }; function getFieldValue(form, fieldName) { return form.find(`[name='form_fields[${fieldName}]']`).val(); } function getSponningbreedte(form) { return parseFloat(form.find("[name='form_fields[BI_sponningbreedte]']").val()) || 0; } function isTargetType(value) { return targetTypes.includes(value); } function applyPart(vviNumber, partIndex, delta) { const part = partIndex + 1; if (vviNumber === 1) { updatePolygonPoints("vak-1-BI-L", [6, 8, 10, 12], -delta); updatePolygonPoints("vak-1-BI-R", [6, 8, 10, 12], delta); return; } if (part === 1) { updatePolygonPoints(`vak-${vviNumber}-BI-L`, [6, 8, 10, 12], -delta); updatePolygonPoints(`vak-${vviNumber}-BI-T1`, [14, 16, 18, 20], delta); return; } if (part === vviNumber) { updatePolygonPoints(`vak-${vviNumber}-BI-T${vviNumber - 1}`, [6, 8, 10, 12], -delta); updatePolygonPoints(`vak-${vviNumber}-BI-R`, [6, 8, 10, 12], delta); return; } updatePolygonPoints(`vak-${vviNumber}-BI-T${part - 1}`, [6, 8, 10, 12], -delta); updatePolygonPoints(`vak-${vviNumber}-BI-T${part}`, [14, 16, 18, 20], delta); } function processVVI(form, vviNumber) { const key = `${vviNumber}VVI`; const BI_sponningbreedteValue = getSponningbreedte(form); for (let i = 0; i < vviNumber; i++) { const fieldName = `${vviNumber}VVI${i + 1}`; const fieldValue = getFieldValue(form, fieldName); const isActive = state[key][i] === 1; const shouldBeActive = isTargetType(fieldValue); if (shouldBeActive && !isActive) { let adjustment = fixedSponningValue - BI_sponningbreedteValue; applyPart(vviNumber, i, adjustment); state[key][i] = 1; } else if (!shouldBeActive && isActive) { let adjustment = BI_sponningbreedteValue - fixedSponningValue; applyPart(vviNumber, i, adjustment); state[key][i] = 0; } } } $(document).on("change", "[name*='BESTELFORMULIER']", function () { const form = $(this).closest("form"); processVVI(form, 1); processVVI(form, 2); processVVI(form, 3); processVVI(form, 4); processVVI(form, 5); }); })(jQuery);
//// 21/25mm SPONNING ON KADERDEUR 1VVI T/M 5VVI //// (function ($) { function updatePolygonPoints(elementId, positions, adjustment) { const element = document.getElementById(elementId); if (!element) return; const polygon = element.querySelector("polygon"); if (!polygon) return; const points = polygon.getAttribute("points").split(" ").map(Number); positions.forEach((pos) => { points[pos] = points[pos] + adjustment; }); polygon.setAttribute("points", points.join(" ")); } const targetType = "kaderdeur"; const state = { "1VVI": { active: [0], oldValues: [0] }, "2VVI": { active: [0, 0], oldValues: [0, 0] }, "3VVI": { active: [0, 0, 0], oldValues: [0, 0, 0] }, "4VVI": { active: [0, 0, 0, 0], oldValues: [0, 0, 0, 0] }, "5VVI": { active: [0, 0, 0, 0, 0], oldValues: [0, 0, 0, 0, 0] } }; function getFieldValue(form, fieldName) { return form.find(`[name='form_fields[${fieldName}]']`).val(); } function getCurrentSponningValue(form) { return parseFloat(form.find("[name='form_fields[BIVsponning]']:checked").val()) || 0; } function getSponningbreedteValue(form) { return parseFloat(form.find("[name='form_fields[BI_sponningbreedte]']").val()) || 0; } function applyPart(vviNumber, partIndex, delta) { const part = partIndex + 1; if (vviNumber === 1) { updatePolygonPoints("vak-1-BI-L", [6, 8, 10, 12], -delta); updatePolygonPoints("vak-1-BI-R", [6, 8, 10, 12], delta); return; } if (part === 1) { updatePolygonPoints(`vak-${vviNumber}-BI-L`, [6, 8, 10, 12], -delta); updatePolygonPoints(`vak-${vviNumber}-BI-T1`, [14, 16, 18, 20], delta); return; } if (part === vviNumber) { updatePolygonPoints(`vak-${vviNumber}-BI-T${vviNumber - 1}`, [6, 8, 10, 12], -delta); updatePolygonPoints(`vak-${vviNumber}-BI-R`, [6, 8, 10, 12], delta); return; } updatePolygonPoints(`vak-${vviNumber}-BI-T${part - 1}`, [6, 8, 10, 12], -delta); updatePolygonPoints(`vak-${vviNumber}-BI-T${part}`, [14, 16, 18, 20], delta); } function calculateAdjustment(currentValue, oldValue, breedteValue) { let adjustment = currentValue - oldValue; if (adjustment > 20 || adjustment === 0) { adjustment = currentValue - breedteValue; } return adjustment; } function rerunActiveParts(form, vviNumber) { const key = `${vviNumber}VVI`; const currentValue = getCurrentSponningValue(form); const breedteValue = getSponningbreedteValue(form); for (let i = 0; i < vviNumber; i++) { if (state[key].active[i] === 1) { const adjustment = calculateAdjustment( currentValue, state[key].oldValues[i], breedteValue ); applyPart(vviNumber, i, adjustment); state[key].oldValues[i] = currentValue; } } } function processFormChanges(form, vviNumber) { const key = `${vviNumber}VVI`; const currentValue = getCurrentSponningValue(form); const breedteValue = getSponningbreedteValue(form); for (let i = 0; i < vviNumber; i++) { const fieldName = `${vviNumber}VVI${i + 1}`; const fieldValue = getFieldValue(form, fieldName); const isActive = state[key].active[i] === 1; if (fieldValue === targetType && !isActive) { const adjustment = calculateAdjustment( currentValue, state[key].oldValues[i], breedteValue ); applyPart(vviNumber, i, adjustment); state[key].active[i] = 1; } else if (fieldValue !== targetType && isActive) { const reverseAdjustment = breedteValue - currentValue; applyPart(vviNumber, i, reverseAdjustment); state[key].active[i] = 0; state[key].oldValues[i] = 0; } if (fieldValue === targetType) { state[key].oldValues[i] = currentValue; } } } $("[name='form_fields[BIVsponning]']").on("change", function () { const form = $(this).closest("form"); rerunActiveParts(form, 1); rerunActiveParts(form, 2); rerunActiveParts(form, 3); rerunActiveParts(form, 4); rerunActiveParts(form, 5); }); $(document).on("change", "[name*='BESTELFORMULIER']", function () { const form = $(this).closest("form"); processFormChanges(form, 1); processFormChanges(form, 2); processFormChanges(form, 3); processFormChanges(form, 4); processFormChanges(form, 5); }); })(jQuery);

HOVER / HITBOXES

VAKVULLING VISUAL #DORPEL

VIEWBOX SCHALEN

SPONNINGMATEN VISUAL #DORPEL

AANTAL VAKKEN / HS SCHEMA

NEUTHOOGTE

NEUTSPONNINGEN

BOORGATEN

NEUTBREEDTE BI

NEUTBREEDTE BU

NEUTBREEDTE HS

DAGMATEN HS

DAGMATEN BI

DAGMATEN BU

DORPELLIJNTJES BU/BI

(FOUT)MELDING BOORGATEN

DORPELLENGTE

CUSTOM FIELDS –> ELEMENTOR INZENDING

//// VOEG EXTRA VELDEN TOE AAN INZENDINGEN //// document.addEventListener("DOMContentLoaded", () => { const form = document.querySelector("form[name='DTS-TIMBER_BESTELFORMULIER']"); if (!form) return; const submitBtn = document.getElementById("SUBMITBUTTON") || form.querySelector('button[type="submit"], input[type="submit"]'); function upsertHidden(key, value) { let h = form.querySelector(`input[name="form_fields[${key}]"]`); if (!h) { h = document.createElement("input"); h.type = "hidden"; h.name = `form_fields[${key}]`; h.id = `form-field-${key}`; form.appendChild(h); } h.value = value; } function isActuallyVisible(el) { const group = el.closest(".elementor-field-group"); const target = group || el; const cs = window.getComputedStyle(target); if (!cs) return false; if (cs.display === "none" || cs.visibility === "hidden") return false; if (el.offsetParent !== null) return true; const r = target.getBoundingClientRect(); if (!r) return false; if (r.width === 0 && r.height === 0) return false; const h = parseFloat(cs.height || "0"); if (h === 0 && cs.overflow === "hidden") return false; const op = parseFloat(cs.opacity || "1"); if (op === 0 && cs.pointerEvents === "none") return false; return true; } function extractKeyFromName(name) { const m = (name || "").match(/^form_fields\[(.+)\]$/); return m && m[1] ? m[1] : null; } function isHsActive() { const checked = form.querySelector('input[name="form_fields[dorpeltype]"]:checked'); return !!checked && checked.getAttribute("data-original-value") === "HS"; } function calculateHsFieldsBeforeWhitelist() { if (typeof window.dtsCalculateHsAfstanden === "function") { window.dtsCalculateHsAfstanden(); } if (typeof window.dtsCalculateAlusleuf === "function") { window.dtsCalculateAlusleuf(); } } function buildVisibleWhitelist() { const els = form.querySelectorAll( 'input[name^="form_fields["], select[name^="form_fields["], textarea[name^="form_fields["]' ); const visible = new Set(); els.forEach((el) => { const name = el.getAttribute("name"); if (!name) return; if (name === "form_fields[dts_visible_fields]") return; const key = extractKeyFromName(name); if (!key) return; if (isActuallyVisible(el)) { visible.add(key); } }); // Altijd meenemen visible.add("dorpeltype_orig"); // C3 hidden fields altijd meenemen visible.add("c3diepte"); visible.add("c3randafstand"); visible.add("c3breedte"); // HS velden alleen meenemen zodra radio HS actief is if (isHsActive()) { [ "deurdikte", "nokbreedteschuif", "nokdikteschuif", "nokdiepteschuif", "nokbreedtevast", "nokdiktevast", "nokdieptevast", "hstussenmaat", "nokken_vast", "nokken_schuif", "verdiept_s", "verdiept_v", // HS berekende / verborgen velden "offset_schuifnok", "offset_vastnok", "offset_alusleuf", "breedte_alusleuf", "diepte_alusleuf", "C", "E" ].forEach((k) => { visible.add(k); }); } } return Array.from(visible); } function writeWhitelist() { // Eerst berekende velden vullen calculateHsFieldsBeforeWhitelist(); // Daarna whitelist schrijven const whitelist = buildVisibleWhitelist(); upsertHidden("dts_visible_fields", JSON.stringify(whitelist)); } if (submitBtn) { submitBtn.addEventListener("click", () => { writeWhitelist(); }, true); } form.addEventListener("submit", () => { writeWhitelist(); }, true); });

SUBMITBUTTON LOGICA

NOKKEN KADER/DRAAIKIEP

COOKIES

//// COOKIES //// (function () { const FORM_NAME = "DTS-TIMBER_BESTELFORMULIER"; const DORPELTYPE_NAME = "form_fields[dorpeltype]"; const KLEUR_NAME = "form_fields[Kleur]"; const BIBLIOTHEEK_SELECTOR = "#form-field-bibliotheek"; const COOKIE_DAYS = 365; const COOKIE_PREFIX = "dtsTimber__"; const DEBUG = true; const log = (...args) => { if (DEBUG) console.log(...args); }; // SVG const SVG_ID = "dorpel"; const SVG_VAKKEN_ID = "alle-vakken"; const FADE_MS = 180; const VAKKEN_SHOW_DELAY_MS = 220; // Wildcard defaults/tolerances const DEFAULT_LR = 67, LR_TOL = 1; const DEFAULT_T = 90, T_TOL = 2; const DEBOUNCE_MS = 350; // Chunked events for deferred wildcard restore const CHUNK_SIZE = 30; const WILDCARD_EVENT_TYPES = ["input"]; // Alleen master vroeg zetten const PRIO2_MASTER = "form_fields[aantal_loodsponningen]"; const PRIO2_FIELDS = [PRIO2_MASTER]; const GROUPS = { ALGEMEEN: [ "form_fields[dorpeldikte]", "form_fields[1Y]","form_fields[2Y]", "form_fields[*L*]", "form_fields[*R*]", "form_fields[*T*]" ], BI_KVTPKI: [ "form_fields[BI_Vsponningbreedte]", "form_fields[sponningdiepte_bi]", "form_fields[BI_Vsponningdiepte]", "form_fields[BIVsponning]" ], BI_KVTPKI_BUVD_KVT_KVTPKU_KVTPBU: [ "form_fields[dorpelbreedte]", "form_fields[aantal_loodsponningen]", "form_fields[ldsp_1_rand]", "form_fields[ldsp_1_diepte]", "form_fields[ldsp_1_breedte]", "form_fields[ldsp_2_rand]", "form_fields[ldsp_2_diepte]", "form_fields[ldsp_2_breedte]", "form_fields[ldsp_3_rand]", "form_fields[ldsp_3_diepte]", "form_fields[ldsp_3_breedte]", "form_fields[KalkspL]", "form_fields[Lrand1]", "form_fields[Ldiepte1]", "form_fields[Lbreedte1]", "form_fields[Lrand2]", "form_fields[Ldiepte2]", "form_fields[Lbreedte2]", "form_fields[Lrand3]", "form_fields[Ldiepte3]", "form_fields[Lbreedte3]", "form_fields[KalkspR]", "form_fields[Rrand1]", "form_fields[Rdiepte1]", "form_fields[Rbreedte1]", "form_fields[Rrand2]", "form_fields[Rdiepte2]", "form_fields[Rbreedte2]", "form_fields[Rrand3]", "form_fields[Rdiepte3]", "form_fields[Rbreedte3]" ], BUVD_KVT_KVTPKU_KVTPBU: [ "form_fields[sponningdiepte_buvd]", "form_fields[wisselsponningdiepte]", "form_fields[wisselvoorsponningdiepte]", "form_fields[draaikiepsponningdiepte]", "form_fields[draaikiepvoorsponningdiepte]" ], BUVD_KVT_KVTPKU_KVTPBU_HS: [ "form_fields[BU_Vsponningbreedte]", "form_fields[BU_Vsponningdiepte]", "form_fields[wisselsponningbreedte]", "form_fields[wisselvoorsponningbreedte]", "form_fields[draaikiepsponningbreedte]", "form_fields[draaikiepvoorsponningbreedte]", "form_fields[draaikiepnokdiepte]", "form_fields[draaikiepnokbreedte]" ], HS: [ "form_fields[dorpelbreedte]", "form_fields[sponningdiepte_buvd]", "form_fields[wisselsponningdiepte]", "form_fields[wisselvoorsponningdiepte]", "form_fields[draaikiepsponningdiepte]", "form_fields[draaikiepvoorsponningdiepte]", "form_fields[deurdikte]", "form_fields[hstussenmaat]", "form_fields[nokbreedtevast]", "form_fields[nokdiktevast]", "form_fields[nokdikteschuif]", "form_fields[nokken_vast]", "form_fields[verdiept_s]", "form_fields[verdiept_v]", "form_fields[nokken_schuif]", "form_fields[nokdieptevast]", "form_fields[nokdiepteschuif]", "form_fields[aantal_loodsponningen]", "form_fields[ldsp_1_rand]", "form_fields[ldsp_1_diepte]", "form_fields[ldsp_1_breedte]", "form_fields[ldsp_2_rand]", "form_fields[ldsp_2_diepte]", "form_fields[ldsp_2_breedte]", "form_fields[ldsp_3_rand]", "form_fields[ldsp_3_diepte]", "form_fields[ldsp_3_breedte]", "form_fields[KalkspL]", "form_fields[Lrand1]", "form_fields[Ldiepte1]", "form_fields[Lbreedte1]", "form_fields[Lrand2]", "form_fields[Ldiepte2]", "form_fields[Lbreedte2]", "form_fields[Lrand3]", "form_fields[Ldiepte3]", "form_fields[Lbreedte3]", "form_fields[KalkspR]", "form_fields[Rrand1]", "form_fields[Rdiepte1]", "form_fields[Rbreedte1]", "form_fields[Rrand2]", "form_fields[Rdiepte2]", "form_fields[Rbreedte2]", "form_fields[Rrand3]", "form_fields[Rdiepte3]", "form_fields[Rbreedte3]" ], }; let form = null; let resolvedGroups = null; let activeGroups = []; let currentOwnerMap = new Map(); let isRestoring = false; let applyRunning = false; let applyQueued = false; let scriptEnabled = false; let deferredRunToken = 0; let fadeStyleInjected = false; let vakkenShowTimer = null; let svgShowTimer = null; const pendingLRValue = new Map(); const pendingTValue = new Map(); const lrTimers = new Map(); const tTimers = new Map(); function ensureFadeCSS() { if (fadeStyleInjected) return; fadeStyleInjected = true; const style = document.createElement("style"); style.textContent = ` #${SVG_ID}.dts-hidden, #${SVG_VAKKEN_ID}.dts-hidden { opacity: 0; pointer-events: none; transition: none !important; will-change: opacity; } #${SVG_ID}.dts-fade-in, #${SVG_VAKKEN_ID}.dts-fade-in { transition: opacity ${FADE_MS}ms ease; will-change: opacity; } #${SVG_ID}.dts-visible, #${SVG_VAKKEN_ID}.dts-visible { opacity: 1; pointer-events: auto; } `; document.head.appendChild(style); } function findForm() { return ( document.querySelector(`.elementor-form[name="${FORM_NAME}"]`) || document.querySelector(".elementor-form") || null ); } function getBibliotheekSelect() { return document.querySelector(BIBLIOTHEEK_SELECTOR); } function isBibliotheekEnabled() { const select = getBibliotheekSelect(); if (!select) return false; // Eerste optie = index 0 return select.selectedIndex === 0; } function cancelAllTimers() { if (vakkenShowTimer) { clearTimeout(vakkenShowTimer); vakkenShowTimer = null; } if (svgShowTimer) { clearTimeout(svgShowTimer); svgShowTimer = null; } lrTimers.forEach((t) => clearTimeout(t)); tTimers.forEach((t) => clearTimeout(t)); lrTimers.clear(); tTimers.clear(); deferredRunToken++; } function resetScriptState() { cancelAllTimers(); activeGroups = []; currentOwnerMap = new Map(); pendingLRValue.clear(); pendingTValue.clear(); log("⛔ Script inactive"); } function updateScriptEnabledState() { const wasEnabled = scriptEnabled; scriptEnabled = isBibliotheekEnabled(); if (!scriptEnabled && wasEnabled) { resetScriptState(); log("⛔ Script disabled by bibliotheek keuze"); } if (scriptEnabled && !wasEnabled) { log("✅ Script enabled by bibliotheek keuze"); } return scriptEnabled; } function hideSVG() { if (!scriptEnabled) return; const el = document.getElementById(SVG_ID); const vakken = document.getElementById(SVG_VAKKEN_ID); if (vakkenShowTimer) { clearTimeout(vakkenShowTimer); vakkenShowTimer = null; } if (svgShowTimer) { clearTimeout(svgShowTimer); svgShowTimer = null; } ensureFadeCSS(); [el, vakken].forEach((node) => { if (!node) return; node.classList.remove("dts-fade-in", "dts-visible"); node.classList.add("dts-hidden"); }); } function showSVG(beforeShow) { if (!scriptEnabled) return; const runToken = deferredRunToken; const el = document.getElementById(SVG_ID); const vakken = document.getElementById(SVG_VAKKEN_ID); if (svgShowTimer) { clearTimeout(svgShowTimer); svgShowTimer = null; } svgShowTimer = setTimeout(() => { if (!scriptEnabled || runToken !== deferredRunToken) return; const finishShow = () => { if (!scriptEnabled || runToken !== deferredRunToken) return; requestAnimationFrame(() => { if (!scriptEnabled || runToken !== deferredRunToken) return; requestAnimationFrame(() => { if (!scriptEnabled || runToken !== deferredRunToken) return; [el, vakken].forEach((node) => { if (!node) return; node.classList.add("dts-fade-in"); node.classList.remove("dts-hidden"); requestAnimationFrame(() => { if (!scriptEnabled || runToken !== deferredRunToken) return; node.classList.add("dts-visible"); }); }); log(`🟪 SVG shown after ${VAKKEN_SHOW_DELAY_MS}ms delay`); svgShowTimer = null; }); }); }; if (typeof beforeShow === "function") { beforeShow(finishShow); } else { finishShow(); } }, VAKKEN_SHOW_DELAY_MS); } function parseCookieMap() { const map = new Map(); const raw = document.cookie || ""; if (!raw) return map; for (const part of raw.split("; ")) { const eq = part.indexOf("="); map.set(eq >= 0 ? part.slice(0, eq) : part, eq >= 0 ? part.slice(eq + 1) : ""); } return map; } function getCookieFromMap(cookieMap, name) { const v = cookieMap.get(name); return v == null ? null : decodeURIComponent(v); } function setCookie(name, value, days = COOKIE_DAYS) { const date = new Date(); date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); document.cookie = `${name}=${encodeURIComponent(value ?? "")}; expires=${date.toUTCString()}; path=/`; } function deleteCookie(name) { document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`; } function safeKey(fieldName) { return fieldName.replace(/\s+/g, "").replace(/[^\w\-]+/g, "_"); } function getKleurBucket(form) { const r = form.querySelector(`input[name="${KLEUR_NAME}"]:checked`); const v = (r?.value || "Z").trim(); return (v === "Z") ? "Z" : "NOTZ"; } function cookieKey(groupName, fieldName, form) { let key = `${COOKIE_PREFIX}${groupName}__${safeKey(fieldName)}`; if (fieldName === "form_fields[dorpeldikte]" && form) key += `__kleur_${getKleurBucket(form)}`; return key; } const wcKeyLR = (g) => `${COOKIE_PREFIX}${g}__WC_LR`; const wcKeyT = (g) => `${COOKIE_PREFIX}${g}__WC_T`; function getSelectedDorpeltypeRaw(form) { const selected = form.querySelector(`input[name="${DORPELTYPE_NAME}"]:checked`); if (!selected) return null; const original = selected.getAttribute("data-original-value"); return (original && original.trim()) ? original.trim() : (selected.value || "").trim(); } function normalizeToken(raw) { const v = (raw || "").toUpperCase(); return (v === "KVT+") ? "KVTP" : v; } function bracketContent(name) { const m = name.match(/^form_fields\[(.*)\]$/); return m ? m[1] : ""; } function isLRField(fieldName) { const inner = bracketContent(fieldName).toUpperCase(); return /^\d[LR][UI]$/.test(inner); } function isTField(fieldName) { const inner = bracketContent(fieldName).toUpperCase(); return /^\dT[UI]\d$/.test(inner); } function inRange(v, def, tol) { const n = Number(String(v).replace(",", ".")); if (!Number.isFinite(n)) return true; return Math.abs(n - def) <= tol; } function readValue(form, fieldName) { const inputs = form.querySelectorAll(`[name="${fieldName}"]`); if (!inputs.length) return null; const first = inputs[0]; if (first.type === "radio") { const checked = form.querySelector(`[name="${fieldName}"]:checked`); if (!checked) return ""; const original = checked.getAttribute("data-original-value"); return (original && original.trim()) ? original.trim() : (checked.value || ""); } return first.value; } function writeValue(form, fieldName, value) { const inputs = form.querySelectorAll(`[name="${fieldName}"]`); if (!inputs.length) return false; const first = inputs[0]; isRestoring = true; try { if (first.type === "radio") { const radio = form.querySelector(`[name="${fieldName}"][data-original-value="${CSS.escape(value)}"]`) || form.querySelector(`[name="${fieldName}"][value="${CSS.escape(value)}"]`); if (!radio) return false; if (radio.checked) return true; radio.checked = true; radio.dispatchEvent(new Event("change", { bubbles: true })); return true; } if (String(first.value ?? "") === String(value)) return true; first.value = value; first.setAttribute("value", value); first.dispatchEvent(new Event("input", { bubbles: true })); first.dispatchEvent(new Event("change", { bubbles: true })); return true; } finally { Promise.resolve().then(() => { isRestoring = false; }); } } function bulkSetInputsNoEvents(inputs, value) { for (const el of inputs) { if (!el || !el.name) continue; if (String(el.value ?? "") === String(value)) continue; el.value = value; el.setAttribute("value", value); } } function dispatchInChunks(inputs, type = "input", chunk = CHUNK_SIZE) { let i = 0; const fire = () => { const end = Math.min(i + chunk, inputs.length); for (; i < end; i++) { inputs[i].dispatchEvent(new Event(type, { bubbles: true })); } if (i < inputs.length) requestAnimationFrame(fire); }; requestAnimationFrame(fire); } function getAllFieldNamesInForm(form) { const els = form.querySelectorAll('[name^="form_fields["]'); const set = new Set(); els.forEach(el => { if (el.name) set.add(el.name); }); return Array.from(set); } function resolveGroupsForForm(form) { const all = getAllFieldNamesInForm(form); const resolved = {}; for (const [g, entries] of Object.entries(GROUPS)) { const out = new Set(); for (const e of entries) { if (!String(e).includes("*")) { out.add(e); continue; } const up = String(e).toUpperCase(); const wantLR = up.includes("*L*") || up.includes("*R*"); const wantT = up.includes("*T*"); for (const name of all) { if (!/^form_fields\[.*\]$/.test(name)) continue; if (wantLR && isLRField(name)) out.add(name); if (wantT && isTField(name)) out.add(name); } } resolved[g] = Array.from(out); } return resolved; } function groupsForDorpeltype(raw, resolvedGroups) { const token = normalizeToken(raw); const names = Object.keys(resolvedGroups); const picked = ["ALGEMEEN"]; for (const g of names) { if (g === "ALGEMEEN") continue; if (g.includes(token)) picked.push(g); } const uniq = Array.from(new Set(picked)); const exact = uniq.filter((g) => g === token); const combos = uniq.filter((g) => g !== "ALGEMEEN" && g !== token); return ["ALGEMEEN", ...combos, ...exact]; } function buildOwnerMap(activeGroups, resolvedGroups) { const owner = new Map(); for (const groupName of activeGroups) { const list = resolvedGroups[groupName] || []; for (const fieldName of list) { if (fieldName === DORPELTYPE_NAME) continue; owner.set(fieldName, groupName); } } return owner; } function saveNormalField(fieldName) { if (!scriptEnabled) return; const groupName = currentOwnerMap.get(fieldName) || "ALGEMEEN"; const v = readValue(form, fieldName); if (v === null) return; setCookie(cookieKey(groupName, fieldName, form), v); log(`🟢 Cookie saved [${groupName}] ${fieldName} =`, v); } function scheduleSaveLR(groupName) { if (lrTimers.has(groupName)) clearTimeout(lrTimers.get(groupName)); lrTimers.set(groupName, setTimeout(() => { if (!scriptEnabled) return; lrTimers.delete(groupName); const v = pendingLRValue.get(groupName); pendingLRValue.delete(groupName); if (v == null) return; const key = wcKeyLR(groupName); if (!inRange(v, DEFAULT_LR, LR_TOL)) { deleteCookie(key); log(`🟥 LR cookie deleted (out-of-range ±${LR_TOL}) [${groupName}] value=${v}`); return; } setCookie(key, String(v)); log(`🟢 LR bucket saved [${groupName}] =`, v); }, DEBOUNCE_MS)); } function scheduleSaveT(groupName) { if (tTimers.has(groupName)) clearTimeout(tTimers.get(groupName)); tTimers.set(groupName, setTimeout(() => { if (!scriptEnabled) return; tTimers.delete(groupName); const v = pendingTValue.get(groupName); pendingTValue.delete(groupName); if (v == null) return; const key = wcKeyT(groupName); if (!inRange(v, DEFAULT_T, T_TOL)) { deleteCookie(key); log(`🟥 T cookie deleted (out-of-range ±${T_TOL}) [${groupName}] value=${v}`); return; } setCookie(key, String(v)); log(`🟢 T bucket saved [${groupName}] =`, v); }, DEBOUNCE_MS)); } function saveFieldCookie(fieldName) { if (!scriptEnabled) return; const groupName = currentOwnerMap.get(fieldName) || "ALGEMEEN"; if (isLRField(fieldName)) { const v = readValue(form, fieldName); if (v === null) return; pendingLRValue.set(groupName, v); scheduleSaveLR(groupName); return; } if (isTField(fieldName)) { const v = readValue(form, fieldName); if (v === null) return; pendingTValue.set(groupName, v); scheduleSaveT(groupName); return; } saveNormalField(fieldName); } function bindOwnedFields(ownerMap) { for (const fieldName of ownerMap.keys()) { const inputs = form.querySelectorAll(`[name="${fieldName}"]`); if (!inputs.length) continue; inputs.forEach((input) => { if (input.dataset.cookieBoundOwned === "1") return; input.dataset.cookieBoundOwned = "1"; const schedulePersist = () => { if (!scriptEnabled) return; if (isRestoring) return; clearTimeout(input._dtsCookieSaveTimer); input._dtsCookieSaveTimer = setTimeout(() => { if (!scriptEnabled) return; if (isRestoring) return; saveFieldCookie(fieldName); }, 80); }; const handler = () => { schedulePersist(); }; input.addEventListener("input", handler, { passive: true }); input.addEventListener("change", handler, { passive: true }); input.addEventListener("blur", handler, { passive: true }); }); } } function cookieForFieldFromAnyGroup(cookieMap, groups, fieldName) { for (const groupName of groups) { const key = cookieKey(groupName, fieldName, form); let stored = getCookieFromMap(cookieMap, key); if (stored === null && fieldName === "form_fields[dorpeldikte]") { stored = getCookieFromMap(cookieMap, `${COOKIE_PREFIX}${groupName}__${safeKey(fieldName)}`); } if (stored !== null) return { groupName, stored }; } return null; } function getCriticalFallback(fieldName, groups) { const isHS = groups.includes("HS"); const isBUVDLike = isHS || groups.some(g => g === "BUVD_KVT_KVTPKU_KVTPBU") || groups.some(g => g === "BUVD_KVT_KVTPKU_KVTPBU_HS") || groups.some(g => g === "BI_KVTPKI_BUVD_KVT_KVTPKU_KVTPBU"); const isBILike = groups.some(g => g === "BI_KVTPKI") || groups.some(g => g === "BI_KVTPKI_BUVD_KVT_KVTPKU_KVTPBU"); const isBreedteFallbackGroup = isBUVDLike || isBILike; if (fieldName === "form_fields[dorpelbreedte]") { if (isBreedteFallbackGroup) return "114"; } if (fieldName === "form_fields[sponningdiepte_buvd]") { if (isBUVDLike) return "67"; } if (fieldName === "form_fields[sponningdiepte_bi]") { if (isBILike) return "57"; } if ([ "form_fields[ldsp_1_breedte]", "form_fields[Lbreedte1]", "form_fields[Lbreedte2]", "form_fields[Lbreedte3]", "form_fields[Rbreedte1]", "form_fields[Rbreedte2]", "form_fields[Rbreedte3]" ].includes(fieldName)) { if (isBreedteFallbackGroup || isHS) return "84"; } return null; } function triggerFieldEvents(fieldNames, done) { const names = Array.isArray(fieldNames) ? fieldNames : [fieldNames]; const runToken = deferredRunToken; requestAnimationFrame(() => { if (!scriptEnabled || runToken !== deferredRunToken) return; requestAnimationFrame(() => { if (!scriptEnabled || runToken !== deferredRunToken) return; for (const fieldName of names) { const inputs = form?.querySelectorAll(`[name="${fieldName}"]`); if (!inputs || !inputs.length) continue; inputs.forEach((input) => { input.value = input.value; input.setAttribute("value", input.value); input.dispatchEvent(new Event("input", { bubbles: true })); input.dispatchEvent(new Event("change", { bubbles: true })); }); log(`🔁 field retriggered ${fieldName}`); } setTimeout(() => { if (!scriptEnabled || runToken !== deferredRunToken) return; requestAnimationFrame(() => { if (!scriptEnabled || runToken !== deferredRunToken) return; requestAnimationFrame(() => { if (!scriptEnabled || runToken !== deferredRunToken) return; if (typeof done === "function") done(); }); }); }, 60); }); }); } function restoreCriticalNow(groups) { const cookieMap = parseCookieMap(); const critical = [ "form_fields[dorpeldikte]", "form_fields[dorpelbreedte]", "form_fields[sponningdiepte_buvd]", "form_fields[sponningdiepte_bi]", "form_fields[ldsp_1_breedte]", "form_fields[Lbreedte1]", "form_fields[Lbreedte2]", "form_fields[Lbreedte3]", "form_fields[Rbreedte1]", "form_fields[Rbreedte2]", "form_fields[Rbreedte3]" ]; const rVariantFields = new Set([ "form_fields[Rbreedte1]", "form_fields[Rbreedte2]", "form_fields[Rbreedte3]" ]); const touchedRFields = new Set(); for (const f of critical) { const hit = cookieForFieldFromAnyGroup(cookieMap, groups, f); if (hit) { const ok = writeValue(form, f, hit.stored); if (ok) { log(`⚡ Critical restored [${hit.groupName}] ${f} =`, hit.stored); if (rVariantFields.has(f)) touchedRFields.add(f); } continue; } const fallback = getCriticalFallback(f, groups); if (fallback !== null) { const ok = writeValue(form, f, fallback); if (ok) { log(`🟡 Critical default applied ${f} =`, fallback); if (rVariantFields.has(f)) touchedRFields.add(f); } } } return Array.from(touchedRFields); } function restorePrio2Now(groups) { const cookieMap = parseCookieMap(); const hit = cookieForFieldFromAnyGroup(cookieMap, groups, PRIO2_MASTER); if (!hit) return; const ok = writeValue(form, PRIO2_MASTER, hit.stored); if (ok) { log(`⚡ Prio2 restored (MASTER) [${hit.groupName}] ${PRIO2_MASTER} =`, hit.stored); form.dispatchEvent(new Event("dts:cookieRestored", { bubbles: true })); } } function restoreDeferred(groups) { if (!scriptEnabled) return; const runToken = deferredRunToken; const cookieMap = parseCookieMap(); const criticalSet = new Set([ "form_fields[dorpeldikte]", "form_fields[dorpelbreedte]", "form_fields[sponningdiepte_buvd]", "form_fields[sponningdiepte_bi]", "form_fields[ldsp_1_breedte]", "form_fields[Lbreedte1]", "form_fields[Lbreedte2]", "form_fields[Lbreedte3]", "form_fields[Rbreedte1]", "form_fields[Rbreedte2]", "form_fields[Rbreedte3]" ]); const prio2Set = new Set(PRIO2_FIELDS); const lrTouched = new Set(); const tTouched = new Set(); let tries = 0; const MAX_TRIES = 10; const RETRY_MS = 150; const FIRST_MS = 0; const runOnce = () => { if (!scriptEnabled || runToken !== deferredRunToken) return; isRestoring = true; try { for (const groupName of groups) { const list = resolvedGroups[groupName] || []; if (!list.length) continue; for (const fieldName of list) { if (fieldName === DORPELTYPE_NAME) continue; if (criticalSet.has(fieldName)) continue; if (prio2Set.has(fieldName)) continue; if (isLRField(fieldName) || isTField(fieldName)) continue; const key = cookieKey(groupName, fieldName, form); const stored = getCookieFromMap(cookieMap, key); if (stored === null) continue; writeValue(form, fieldName, stored); } const lrVal = getCookieFromMap(cookieMap, wcKeyLR(groupName)); if (lrVal !== null) { const lrInputs = []; for (const n of (resolvedGroups[groupName] || []).filter(isLRField)) { const el = form.querySelector(`[name="${n}"]`); if (el) lrInputs.push(el); } if (lrInputs.length) { bulkSetInputsNoEvents(lrInputs, lrVal); lrInputs.forEach(el => lrTouched.add(el)); log(`🟦 LR bucket restored deferred [${groupName}] -> ${lrInputs.length} fields =`, lrVal); } } const tVal = getCookieFromMap(cookieMap, wcKeyT(groupName)); if (tVal !== null) { const tInputs = []; for (const n of (resolvedGroups[groupName] || []).filter(isTField)) { const el = form.querySelector(`[name="${n}"]`); if (el) tInputs.push(el); } if (tInputs.length) { bulkSetInputsNoEvents(tInputs, tVal); tInputs.forEach(el => tTouched.add(el)); log(`🟦 T bucket restored deferred [${groupName}] -> ${tInputs.length} fields =`, tVal); } } } } finally { isRestoring = false; } }; const finalize = () => { if (!scriptEnabled || runToken !== deferredRunToken) return; const lrArr = Array.from(lrTouched); const tArr = Array.from(tTouched); if (lrArr.length) { log(`🟣 Deferred chunk events for LR (${lrArr.length})`); for (const ev of WILDCARD_EVENT_TYPES) dispatchInChunks(lrArr, ev); } if (tArr.length) { log(`🟣 Deferred chunk events for T (${tArr.length})`); for (const ev of WILDCARD_EVENT_TYPES) dispatchInChunks(tArr, ev); } form.dispatchEvent(new Event("dts:cookieRestored", { bubbles: true })); }; const tick = () => { if (!scriptEnabled || runToken !== deferredRunToken) return; runOnce(); tries++; if (tries >= MAX_TRIES) { finalize(); return; } setTimeout(tick, RETRY_MS); }; setTimeout(tick, FIRST_MS); } function retriggerDorpelbreedte() { if (!scriptEnabled) return; const fieldName = "form_fields[dorpelbreedte]"; const input = form?.querySelector(`[name="${fieldName}"]`); if (!input) return; requestAnimationFrame(() => { if (!scriptEnabled) return; requestAnimationFrame(() => { if (!scriptEnabled) return; input.dispatchEvent(new Event("input", { bubbles: true })); input.dispatchEvent(new Event("change", { bubbles: true })); log("🔁 dorpelbreedte retriggered for SVG"); }); }); } function applyStaged(force = true) { updateScriptEnabledState(); if (!scriptEnabled) return; if (applyRunning) { applyQueued = true; return; } applyRunning = true; const t0 = performance.now(); try { resolvedGroups = resolveGroupsForForm(form); const raw = getSelectedDorpeltypeRaw(form); if (!raw) return; const next = groupsForDorpeltype(raw, resolvedGroups); const changed = JSON.stringify(next) !== JSON.stringify(activeGroups); activeGroups = next; currentOwnerMap = buildOwnerMap(activeGroups, resolvedGroups); bindOwnedFields(currentOwnerMap); if (!force && !changed) return; hideSVG(); const touchedRFields = restoreCriticalNow(activeGroups); restorePrio2Now(activeGroups); retriggerDorpelbreedte(); showSVG((doneShow) => { if (touchedRFields.length) { triggerFieldEvents(touchedRFields, doneShow); } else { doneShow(); } }); restoreDeferred(activeGroups); } finally { const dt = performance.now() - t0; log(`⏱ applyStaged() sync part took ${dt.toFixed(1)}ms (rest deferred)`); applyRunning = false; if (applyQueued) { applyQueued = false; applyStaged(true); } } } function bindDorpeltype() { form.querySelectorAll(`input[name="${DORPELTYPE_NAME}"]`).forEach((radio) => { if (radio.dataset.dorpeltypeBound === "1") return; radio.dataset.dorpeltypeBound = "1"; radio.addEventListener("change", () => { updateScriptEnabledState(); if (!scriptEnabled) return; requestAnimationFrame(() => applyStaged(true)); }, { passive: true }); }); } function bindKleur() { form.querySelectorAll(`input[name="${KLEUR_NAME}"]`).forEach((radio) => { if (radio.dataset.kleurBound === "1") return; radio.dataset.kleurBound = "1"; radio.addEventListener("change", (evt) => { updateScriptEnabledState(); if (!scriptEnabled) return; if (!evt || evt.isTrusted !== true) return; const cookieMap = parseCookieMap(); const hit = cookieForFieldFromAnyGroup( cookieMap, activeGroups.length ? activeGroups : ["ALGEMEEN"], "form_fields[dorpeldikte]" ); if (hit) { writeValue(form, "form_fields[dorpeldikte]", hit.stored); log(`🎨 Kleur change -> dorpeldikte restored =`, hit.stored); form.dispatchEvent(new Event("dts:cookieRestored", { bubbles: true })); } }, { passive: true }); }); } function bindBibliotheek() { const select = getBibliotheekSelect(); if (!select || select.dataset.bibliotheekBound === "1") return; select.dataset.bibliotheekBound = "1"; select.addEventListener("change", () => { const enabled = updateScriptEnabledState(); if (!enabled) return; requestAnimationFrame(() => { applyStaged(true); }); }, { passive: true }); } function init() { form = findForm(); if (!form) { console.error("Form not found"); return; } bindBibliotheek(); bindDorpeltype(); bindKleur(); updateScriptEnabledState(); if (!scriptEnabled) return; if (getSelectedDorpeltypeRaw(form)) { applyStaged(true); } } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init, { once: true }); } else { init(); } })();

HS- UV US  VU VS altijd als BU Sponning

Scroll naar boven

Vragen of opmerkingen

over de DTS TIMBER configurator?