/* exact-runtime:laticrete-exact-runtime.js */ (function (global) { "use strict"; var VERSION = "1.3.0"; var ROOT_SELECTOR = ".qhl-laticrete-exact"; var SHELL_SELECTOR = "[data-laticrete-exact-page]"; var CAROUSEL_SELECTOR = ".carousel, [data-laticrete-carousel]"; var SLIDE_SELECTOR = ".carousel-item, [data-laticrete-slide]"; var ACCORDION_SELECTOR = ".accordion, [data-laticrete-accordion]"; var GALLERY_SELECTOR = "#ProductImageViewer, .ProductImageViewer, [data-laticrete-gallery]"; var VIDEO_SELECTOR = "[data-laticrete-video]"; var existing = global.QHLLaticreteExactRuntime; if (existing && existing.version === VERSION && typeof existing.init === "function") { existing.init(); return; } var doc = global.document || null; var carouselStates = typeof WeakMap === "function" ? new WeakMap() : new Map(); var accordionStates = typeof WeakMap === "function" ? new WeakMap() : new Map(); var galleryStates = typeof WeakMap === "function" ? new WeakMap() : new Map(); var menuStates = typeof WeakMap === "function" ? new WeakMap() : new Map(); var explicitToggleStates = typeof WeakMap === "function" ? new WeakMap() : new Map(); var videoStates = typeof WeakMap === "function" ? new WeakMap() : new Map(); var generatedId = 0; function freeze(value) { return typeof Object.freeze === "function" ? Object.freeze(value) : value; } function toArray(value) { return value ? Array.prototype.slice.call(value) : []; } function queryAll(scope, selector) { if (!scope || typeof scope.querySelectorAll !== "function") return []; try { return toArray(scope.querySelectorAll(selector)); } catch (_error) { return []; } } function query(scope, selector) { if (!scope || typeof scope.querySelector !== "function") return null; try { return scope.querySelector(selector); } catch (_error) { return null; } } function closest(element, selector) { if (!element || typeof element.closest !== "function") return null; try { return element.closest(selector); } catch (_error) { return null; } } function contains(scope, element) { return Boolean( scope && element && (scope === element || (typeof scope.contains === "function" && scope.contains(element))), ); } function getAttribute(element, name) { if (!element || typeof element.getAttribute !== "function") return null; var value = element.getAttribute(name); return value === null || value === undefined ? null : String(value); } function hasAttribute(element, name) { return Boolean( element && typeof element.hasAttribute === "function" && element.hasAttribute(name), ); } function setAttribute(element, name, value) { if (element && typeof element.setAttribute === "function") { element.setAttribute(name, String(value)); } } function removeAttribute(element, name) { if (element && typeof element.removeAttribute === "function") { element.removeAttribute(name); } } function classToggle(element, name, enabled) { if (element && element.classList && typeof element.classList.toggle === "function") { element.classList.toggle(name, Boolean(enabled)); } } function parseBoolean(value, fallback) { if (value === null || value === undefined || value === "") return fallback; var normalized = String(value).trim().toLowerCase(); if (normalized === "false" || normalized === "0" || normalized === "off") { return false; } if (normalized === "true" || normalized === "1" || normalized === "on") { return true; } return fallback; } function firstAttribute(element, names) { for (var index = 0; index < names.length; index += 1) { var value = getAttribute(element, names[index]); if (value !== null) return value; } return null; } function wrapIndex(index, length) { if (!Number.isFinite(index) || !Number.isFinite(length) || length < 1) return 0; return ((Math.trunc(index) % length) + length) % length; } function readInterval(element, fallback) { var raw = firstAttribute(element, [ "data-laticrete-interval", "data-bs-interval", ]); if (raw === null || raw === "") return fallback; if (String(raw).trim().toLowerCase() === "false") return 0; var parsed = Number(raw); if (!Number.isFinite(parsed) || parsed <= 0) return fallback; return Math.max(1000, Math.min(60000, Math.round(parsed))); } function directionFromSwipe(startX, startY, endX, endY, threshold) { var horizontal = Number(endX) - Number(startX); var vertical = Number(endY) - Number(startY); var minimum = Number.isFinite(threshold) ? Math.max(20, threshold) : 40; if (!Number.isFinite(horizontal) || !Number.isFinite(vertical)) return 0; if (Math.abs(horizontal) < minimum || Math.abs(horizontal) <= Math.abs(vertical)) { return 0; } return horizontal < 0 ? 1 : -1; } function safeImageUrl(value, baseHref) { if (typeof value !== "string") return null; var trimmed = value.trim(); if (!trimmed || /[\u0000-\u001f\u007f]/.test(trimmed)) return null; if (/^\/\//.test(trimmed)) return null; try { var URLConstructor = global.URL; if (typeof URLConstructor !== "function") { return /^\/assets\/laticrete\//.test(trimmed) ? trimmed : null; } var reference = baseHref || (global.location && global.location.href) || "https://www.qhl.hu/"; var parsed = new URLConstructor( trimmed, reference, ); var referenceUrl = new URLConstructor(reference, "https://www.qhl.hu/"); var localAsset = /^\/assets\/laticrete\//.test(parsed.pathname) && parsed.origin === referenceUrl.origin; var publishedAsset = /^\/shop_ordered\/15586\/pic\/laticrete\//.test(parsed.pathname) && parsed.origin === "https://www.qhl.hu"; if ((!localAsset && !publishedAsset) || parsed.username || parsed.password) return null; return parsed.pathname + parsed.search + parsed.hash; } catch (_error) { return null; } } function safeYouTubeEmbedUrl(value) { if (typeof value !== "string") return null; var trimmed = value.trim(); if (/^https:\/\/www\.youtube\.com\/embed\/[A-Za-z0-9_-]{11}$/.test(trimmed)) { return trimmed; } if (trimmed === "https://www.youtube.com/embed/FcdV3pDcdXA?si=QST1ptWVRjOphxKx") { return trimmed; } return null; } function ensureId(element, prefix) { if (!element) return ""; var current = getAttribute(element, "id"); if (current) return current; generatedId += 1; var next = String(prefix || "qhl-laticrete") + "-" + String(generatedId); setAttribute(element, "id", next); return next; } function ensureTargetSize(element) { if (!element || !element.style) return; element.style.minWidth = "44px"; element.style.minHeight = "44px"; element.style.touchAction = "manipulation"; } function isButton(element) { var name = element && element.tagName ? String(element.tagName).toUpperCase() : ""; return name === "BUTTON" || name === "SUMMARY"; } function makeButtonLike(element, label) { if (!element) return; if (!isButton(element)) { setAttribute(element, "role", "button"); if (!hasAttribute(element, "tabindex") || getAttribute(element, "tabindex") === "-1") { setAttribute(element, "tabindex", "0"); } } if (label && !getAttribute(element, "aria-label")) { setAttribute(element, "aria-label", label); } ensureTargetSize(element); } function isEditableTarget(element) { var name = element && element.tagName ? String(element.tagName).toUpperCase() : ""; return ( name === "INPUT" || name === "TEXTAREA" || name === "SELECT" || Boolean(element && element.isContentEditable) ); } function safeRun(callback) { try { return callback(); } catch (_error) { return undefined; } } function ownedBy(element, selector, owner) { return closest(element, selector) === owner; } function dispatchChange(element, name, detail) { if (!element || typeof element.dispatchEvent !== "function") return; if (typeof global.CustomEvent !== "function") return; safeRun(function () { element.dispatchEvent( new global.CustomEvent(name, { bubbles: true, detail: detail }), ); }); } function initCarousel(carousel) { if (!carousel || carouselStates.has(carousel)) return; var slides = queryAll(carousel, SLIDE_SELECTOR).filter(function (slide) { return ownedBy(slide, CAROUSEL_SELECTOR, carousel); }); if (!slides.length) return; var indicators = queryAll( carousel, "[data-bs-slide-to], [data-laticrete-slide-to]", ).filter(function (indicator) { return ownedBy(indicator, CAROUSEL_SELECTOR, carousel); }); var previousControls = queryAll( carousel, ".carousel-control-prev, [data-bs-slide=\"prev\"], [data-laticrete-carousel-prev]", ).filter(function (control) { return ownedBy(control, CAROUSEL_SELECTOR, carousel); }); var nextControls = queryAll( carousel, ".carousel-control-next, [data-bs-slide=\"next\"], [data-laticrete-carousel-next]", ).filter(function (control) { return ownedBy(control, CAROUSEL_SELECTOR, carousel); }); var pauseControls = queryAll( carousel, "[data-laticrete-carousel-pause], [data-laticrete-carousel-toggle]", ).filter(function (control) { return ownedBy(control, CAROUSEL_SELECTOR, carousel); }); var activeIndex = slides.findIndex(function (slide) { return Boolean(slide.classList && slide.classList.contains("active")); }); var wraps = parseBoolean( firstAttribute(carousel, ["data-laticrete-wrap", "data-bs-wrap"]), true, ); var rideValue = firstAttribute(carousel, [ "data-laticrete-autoplay", "data-bs-ride", ]); var wantsAutoplay = (rideValue !== null && rideValue !== "" && String(rideValue).trim().toLowerCase() !== "false") || (rideValue === null && getAttribute(carousel, "id") === "home-carousel"); var pauseOnHover = parseBoolean( firstAttribute(carousel, ["data-laticrete-pause", "data-bs-pause"]), true, ); var reducedMotion = null; if (typeof global.matchMedia === "function") { reducedMotion = safeRun(function () { return global.matchMedia("(prefers-reduced-motion: reduce)"); }); } var state = { index: activeIndex < 0 ? 0 : activeIndex, timer: null, userPaused: false, pointerPaused: false, hoverPaused: false, focusPaused: false, hiddenPaused: Boolean(doc && doc.hidden), touchStart: null, }; carouselStates.set(carousel, state); setAttribute(carousel, "role", getAttribute(carousel, "role") || "region"); setAttribute(carousel, "aria-roledescription", "carousel"); if (!getAttribute(carousel, "aria-label") && !getAttribute(carousel, "aria-labelledby")) { setAttribute(carousel, "aria-label", "Képgaléria"); } if (!hasAttribute(carousel, "tabindex")) setAttribute(carousel, "tabindex", "0"); if (carousel.style) carousel.style.touchAction = "pan-y"; function normalize(index) { if (wraps) return wrapIndex(index, slides.length); return Math.max(0, Math.min(slides.length - 1, Math.trunc(index))); } function updateControls() { previousControls.forEach(function (control) { var disabled = !wraps && state.index === 0; setAttribute(control, "aria-disabled", disabled ? "true" : "false"); if ("disabled" in control) control.disabled = disabled; }); nextControls.forEach(function (control) { var disabled = !wraps && state.index === slides.length - 1; setAttribute(control, "aria-disabled", disabled ? "true" : "false"); if ("disabled" in control) control.disabled = disabled; }); } function currentInterval() { return readInterval(slides[state.index], readInterval(carousel, 5000)); } function stopTimer() { if (state.timer !== null && typeof global.clearTimeout === "function") { global.clearTimeout(state.timer); } state.timer = null; } function canAutoplay() { return Boolean( wantsAutoplay && slides.length > 1 && currentInterval() > 0 && !state.userPaused && !state.pointerPaused && !state.hoverPaused && !state.focusPaused && !state.hiddenPaused && !(reducedMotion && reducedMotion.matches), ); } function updatePauseControls() { pauseControls.forEach(function (control) { setAttribute(control, "aria-pressed", state.userPaused ? "true" : "false"); setAttribute( control, "aria-label", state.userPaused ? "Diavetítés indítása" : "Diavetítés szüneteltetése", ); }); } function schedule() { stopTimer(); if (!canAutoplay() || typeof global.setTimeout !== "function") return; state.timer = global.setTimeout(function () { show(state.index + 1, false); }, currentInterval()); } function show(index, userInitiated) { var nextIndex = normalize(index); state.index = nextIndex; slides.forEach(function (slide, slideIndex) { var active = slideIndex === nextIndex; classToggle(slide, "active", active); slide.hidden = !active; setAttribute(slide, "aria-hidden", active ? "false" : "true"); setAttribute(slide, "role", "group"); setAttribute(slide, "aria-roledescription", "slide"); setAttribute( slide, "aria-label", String(slideIndex + 1) + " / " + String(slides.length), ); }); indicators.forEach(function (indicator, indicatorIndex) { var rawIndex = firstAttribute(indicator, [ "data-laticrete-slide-to", "data-bs-slide-to", ]); var targetIndex = rawIndex === null ? indicatorIndex : Number(rawIndex); var active = Number.isFinite(targetIndex) && targetIndex === nextIndex; classToggle(indicator, "active", active); if (active) setAttribute(indicator, "aria-current", "true"); else removeAttribute(indicator, "aria-current"); }); updateControls(); schedule(); dispatchChange(carousel, "laticrete:slide", { index: nextIndex, userInitiated: Boolean(userInitiated), }); } function move(delta, userInitiated) { show(state.index + delta, userInitiated); } function bindControl(control, delta, label) { makeButtonLike(control, label); control.addEventListener("click", function (event) { if (event && typeof event.preventDefault === "function") event.preventDefault(); if (getAttribute(control, "aria-disabled") === "true") return; move(delta, true); }); } previousControls.forEach(function (control) { bindControl(control, -1, "Előző dia"); }); nextControls.forEach(function (control) { bindControl(control, 1, "Következő dia"); }); indicators.forEach(function (indicator, indicatorIndex) { makeButtonLike(indicator, "Ugrás a(z) " + String(indicatorIndex + 1) + ". diára"); indicator.addEventListener("click", function (event) { if (event && typeof event.preventDefault === "function") event.preventDefault(); var rawIndex = firstAttribute(indicator, [ "data-laticrete-slide-to", "data-bs-slide-to", ]); var targetIndex = rawIndex === null ? indicatorIndex : Number(rawIndex); if (Number.isFinite(targetIndex)) show(targetIndex, true); }); }); pauseControls.forEach(function (control) { makeButtonLike(control, "Diavetítés szüneteltetése"); control.addEventListener("click", function (event) { if (event && typeof event.preventDefault === "function") event.preventDefault(); state.userPaused = !state.userPaused; updatePauseControls(); schedule(); }); }); carousel.addEventListener("keydown", function (event) { if (!event || isEditableTarget(event.target)) return; if (event.key === "ArrowLeft") move(-1, true); else if (event.key === "ArrowRight") move(1, true); else if (event.key === "Home") show(0, true); else if (event.key === "End") show(slides.length - 1, true); else return; if (typeof event.preventDefault === "function") event.preventDefault(); }); if (pauseOnHover) { carousel.addEventListener("mouseenter", function () { state.hoverPaused = true; schedule(); }); carousel.addEventListener("mouseleave", function () { state.hoverPaused = false; schedule(); }); } carousel.addEventListener("focusin", function () { state.focusPaused = true; schedule(); }); carousel.addEventListener("focusout", function (event) { if (event && contains(carousel, event.relatedTarget)) return; state.focusPaused = false; schedule(); }); function startSwipe(x, y) { state.pointerPaused = true; state.touchStart = { x: Number(x), y: Number(y) }; schedule(); } function finishSwipe(x, y) { if (state.touchStart) { var direction = directionFromSwipe( state.touchStart.x, state.touchStart.y, x, y, 40, ); if (direction) move(direction, true); } state.touchStart = null; state.pointerPaused = false; schedule(); } function cancelSwipe() { state.touchStart = null; state.pointerPaused = false; schedule(); } if ("PointerEvent" in global) { carousel.addEventListener("pointerdown", function (event) { if (event.pointerType === "touch" || event.pointerType === "pen") { startSwipe(event.clientX, event.clientY); } }); carousel.addEventListener("pointerup", function (event) { if (state.touchStart) finishSwipe(event.clientX, event.clientY); }); carousel.addEventListener("pointercancel", cancelSwipe); } else { carousel.addEventListener( "touchstart", function (event) { var touch = event.touches && event.touches[0]; if (touch) startSwipe(touch.clientX, touch.clientY); }, { passive: true }, ); carousel.addEventListener( "touchend", function (event) { var touch = event.changedTouches && event.changedTouches[0]; if (touch) finishSwipe(touch.clientX, touch.clientY); else cancelSwipe(); }, { passive: true }, ); carousel.addEventListener("touchcancel", cancelSwipe, { passive: true }); } if (doc && typeof doc.addEventListener === "function") { doc.addEventListener("visibilitychange", function () { state.hiddenPaused = Boolean(doc.hidden); schedule(); }); } if (reducedMotion) { var motionChanged = function () { schedule(); }; if (typeof reducedMotion.addEventListener === "function") { reducedMotion.addEventListener("change", motionChanged); } else if (typeof reducedMotion.addListener === "function") { reducedMotion.addListener(motionChanged); } } updatePauseControls(); show(state.index, false); } function directDescendant(item, selector, ownerSelector) { return ( queryAll(item, selector).find(function (candidate) { return closest(candidate, ownerSelector) === item; }) || null ); } function accordionAllowsMultiple(accordion) { var explicit = getAttribute(accordion, "data-laticrete-multiple"); if (explicit !== null) return parseBoolean(explicit, true); if (hasAttribute(accordion, "data-bs-parent")) return false; var properties = getAttribute(accordion, "data-properties"); if (properties) { var parsed = safeRun(function () { return JSON.parse(properties); }); if (parsed && typeof parsed.canOpenMultiple === "boolean") { return parsed.canOpenMultiple; } } return true; } function initAccordion(accordion) { if (!accordion || accordionStates.has(accordion)) return; var itemSelector = ".item, .accordion-item, [data-laticrete-accordion-item]"; var items = queryAll(accordion, itemSelector).filter(function (item) { return ownedBy(item, ACCORDION_SELECTOR, accordion); }); var pairs = items .map(function (item) { var header = directDescendant( item, ".toggle-header, .accordion-button, [data-laticrete-accordion-button]", itemSelector, ); var panel = directDescendant( item, ".toggle-content, .accordion-collapse, [data-laticrete-accordion-panel]", itemSelector, ); return header && panel ? { item: item, header: header, panel: panel } : null; }) .filter(Boolean); if (!pairs.length) return; var multiple = accordionAllowsMultiple(accordion); accordionStates.set(accordion, { pairs: pairs, multiple: multiple }); function setOpen(pair, open, focusHeader) { setAttribute(pair.header, "aria-expanded", open ? "true" : "false"); pair.panel.hidden = !open; classToggle(pair.header, "collapsed", !open); classToggle(pair.panel, "show", open); classToggle(pair.item, "active", open); if (open && !multiple) { pairs.forEach(function (other) { if (other !== pair) setOpen(other, false, false); }); } if (focusHeader && typeof pair.header.focus === "function") pair.header.focus(); dispatchChange(accordion, "laticrete:accordion", { id: getAttribute(pair.panel, "id"), open: open, }); } pairs.forEach(function (pair, pairIndex) { var initiallyOpen = getAttribute(pair.header, "aria-expanded") === "true" || hasAttribute(pair.item, "data-laticrete-open") || Boolean(pair.item.classList && pair.item.classList.contains("active")) || Boolean(pair.panel.classList && pair.panel.classList.contains("show")); var headerId = ensureId(pair.header, "qhl-laticrete-accordion-button"); var panelId = ensureId(pair.panel, "qhl-laticrete-accordion-panel"); makeButtonLike(pair.header, null); setAttribute(pair.header, "aria-controls", panelId); setAttribute(pair.panel, "role", "region"); setAttribute(pair.panel, "aria-labelledby", headerId); setOpen(pair, initiallyOpen, false); pair.header.addEventListener("click", function (event) { var nestedInteractive = event && event.target !== pair.header && closest(event.target, "a, button, input, select, textarea"); if (nestedInteractive && contains(pair.header, nestedInteractive)) return; if (event && typeof event.preventDefault === "function") event.preventDefault(); setOpen(pair, getAttribute(pair.header, "aria-expanded") !== "true", false); }); pair.header.addEventListener("keydown", function (event) { if (!event) return; if (event.key === "Enter" || event.key === " ") { if (typeof event.preventDefault === "function") event.preventDefault(); setOpen(pair, getAttribute(pair.header, "aria-expanded") !== "true", false); return; } if (event.key === "Escape") { if (typeof event.preventDefault === "function") event.preventDefault(); setOpen(pair, false, true); return; } var nextIndex = null; if (event.key === "ArrowDown") nextIndex = wrapIndex(pairIndex + 1, pairs.length); else if (event.key === "ArrowUp") nextIndex = wrapIndex(pairIndex - 1, pairs.length); else if (event.key === "Home") nextIndex = 0; else if (event.key === "End") nextIndex = pairs.length - 1; if (nextIndex !== null) { if (typeof event.preventDefault === "function") event.preventDefault(); if (typeof pairs[nextIndex].header.focus === "function") { pairs[nextIndex].header.focus(); } } }); pair.panel.addEventListener("keydown", function (event) { if (event && event.key === "Escape") { if (typeof event.preventDefault === "function") event.preventDefault(); setOpen(pair, false, true); } }); }); } function findTargetInsideRoot(root, rawTarget) { if (!root || !rawTarget) return null; var id = String(rawTarget).trim().replace(/^#/, ""); if (!/^[A-Za-z][A-Za-z0-9_:.-]*$/.test(id)) return null; var target = doc && typeof doc.getElementById === "function" ? doc.getElementById(id) : null; if (!target) target = query(root, "#" + id); return contains(root, target) ? target : null; } function initExplicitToggle(root, control) { if (!control || explicitToggleStates.has(control)) return; var rawTarget = firstAttribute(control, [ "data-laticrete-toggle-target", "data-laticrete-target", "aria-controls", ]); var panel = findTargetInsideRoot(root, rawTarget); if (!panel) return; explicitToggleStates.set(control, { panel: panel }); makeButtonLike(control, null); setAttribute(control, "aria-controls", ensureId(panel, "qhl-laticrete-toggle-panel")); function setOpen(open, restoreFocus) { setAttribute(control, "aria-expanded", open ? "true" : "false"); panel.hidden = !open; classToggle(control, "active", open); classToggle(panel, "show", open); if (restoreFocus && typeof control.focus === "function") control.focus(); } setOpen( getAttribute(control, "aria-expanded") === "true" || hasAttribute(control, "data-laticrete-open"), false, ); control.addEventListener("click", function (event) { if (event && typeof event.preventDefault === "function") event.preventDefault(); setOpen(getAttribute(control, "aria-expanded") !== "true", false); }); control.addEventListener("keydown", function (event) { if (!event) return; if (!isButton(control) && (event.key === "Enter" || event.key === " ")) { if (typeof event.preventDefault === "function") event.preventDefault(); setOpen(getAttribute(control, "aria-expanded") !== "true", false); } else if (event.key === "Escape") { if (typeof event.preventDefault === "function") event.preventDefault(); setOpen(false, true); } }); panel.addEventListener("keydown", function (event) { if (event && event.key === "Escape") { if (typeof event.preventDefault === "function") event.preventDefault(); setOpen(false, true); } }); } function initGallery(gallery) { if (!gallery || galleryStates.has(gallery)) return; var mainImage = query( gallery, "[data-laticrete-gallery-main], #mainImage img, .lightbox img", ); if (mainImage && String(mainImage.tagName || "").toUpperCase() !== "IMG") { mainImage = query(mainImage, "img"); } if (!mainImage) return; var candidates = queryAll( gallery, "[data-laticrete-gallery-thumb], [data-ngthumb], .multi-carousel-item img, .multi-carousel-item a", ); var seen = new Set(); var thumbnails = []; candidates.forEach(function (candidate) { var control = candidate; var candidateName = candidate.tagName ? String(candidate.tagName).toUpperCase() : ""; if (candidateName === "IMG") { control = closest(candidate, "a, button") || candidate; } if (seen.has(control)) return; var previewUrl = safeImageUrl( firstAttribute(candidate, ["data-laticrete-thumb", "data-ngthumb", "src"]), ); var fullUrl = safeImageUrl( firstAttribute(control, [ "data-laticrete-full", "data-laticrete-image", "data-ngimage", "href", ]) || firstAttribute(candidate, [ "data-laticrete-full", "data-laticrete-image", "data-ngimage", "href", "src", ]), ); if (!fullUrl) fullUrl = previewUrl; if (!fullUrl) return; var image = candidateName === "IMG" ? candidate : query(control, "img"); if (!image && previewUrl && doc && typeof doc.createElement === "function") { image = doc.createElement("img"); setAttribute(image, "src", previewUrl); setAttribute(image, "alt", ""); setAttribute(image, "loading", "lazy"); setAttribute(image, "decoding", "async"); setAttribute(image, "width", "80"); setAttribute(image, "height", "80"); if (typeof control.appendChild === "function") control.appendChild(image); } seen.add(control); thumbnails.push({ control: control, image: image, fullUrl: fullUrl, alt: firstAttribute(control, ["data-laticrete-alt", "aria-label"]) || getAttribute(image, "alt") || getAttribute(mainImage, "alt") || "Termékkép", }); }); if (!thumbnails.length) return; galleryStates.set(gallery, { thumbnails: thumbnails, mainImage: mainImage }); function select(selectedIndex, focusMain) { var selected = thumbnails[selectedIndex]; if (!selected) return; setAttribute(mainImage, "src", selected.fullUrl); removeAttribute(mainImage, "srcset"); removeAttribute(mainImage, "sizes"); setAttribute(mainImage, "alt", selected.alt); setAttribute(mainImage, "data-laticrete-active-image", selected.fullUrl); var picture = closest(mainImage, "picture"); queryAll(picture, "source").forEach(function (source) { removeAttribute(source, "srcset"); }); var mainLink = closest(mainImage, "a"); if (mainLink && contains(gallery, mainLink)) setAttribute(mainLink, "href", selected.fullUrl); thumbnails.forEach(function (thumbnail, index) { var active = index === selectedIndex; classToggle(thumbnail.control, "active", active); if (active) setAttribute(thumbnail.control, "aria-current", "true"); else removeAttribute(thumbnail.control, "aria-current"); }); if (focusMain && typeof mainImage.focus === "function") { if (!hasAttribute(mainImage, "tabindex")) setAttribute(mainImage, "tabindex", "-1"); mainImage.focus(); } dispatchChange(gallery, "laticrete:gallery", { index: selectedIndex, url: selected.fullUrl, }); } thumbnails.forEach(function (thumbnail, index) { makeButtonLike(thumbnail.control, "A(z) " + String(index + 1) + ". termékkép megtekintése"); thumbnail.control.addEventListener("click", function (event) { if (event && typeof event.preventDefault === "function") event.preventDefault(); select(index, false); }); thumbnail.control.addEventListener("keydown", function (event) { if (!event) return; if (!isButton(thumbnail.control) && (event.key === "Enter" || event.key === " ")) { if (typeof event.preventDefault === "function") event.preventDefault(); select(index, false); } else if (event.key === "ArrowRight" || event.key === "ArrowDown") { if (typeof event.preventDefault === "function") event.preventDefault(); var next = thumbnails[wrapIndex(index + 1, thumbnails.length)].control; if (next && typeof next.focus === "function") next.focus(); } else if (event.key === "ArrowLeft" || event.key === "ArrowUp") { if (typeof event.preventDefault === "function") event.preventDefault(); var previous = thumbnails[wrapIndex(index - 1, thumbnails.length)].control; if (previous && typeof previous.focus === "function") previous.focus(); } else if (event.key === "Escape") { if (typeof event.preventDefault === "function") event.preventDefault(); select(index, true); } }); }); var currentSource = getAttribute(mainImage, "src"); var initialIndex = thumbnails.findIndex(function (thumbnail) { return thumbnail.fullUrl === currentSource; }); select(initialIndex < 0 ? 0 : initialIndex, false); } function initMobileMenu(root, menu) { if (!menu || menuStates.has(menu)) return; var toggle = query( menu, "[data-laticrete-mobile-menu-toggle], [data-laticrete-menu-toggle]", ); if (!toggle) return; var rawPanel = firstAttribute(toggle, [ "data-laticrete-menu-target", "aria-controls", ]); var panel = findTargetInsideRoot(root, rawPanel) || query( menu, "[data-laticrete-mobile-menu-panel]", ); if (!panel || !contains(menu, panel)) return; menuStates.set(menu, { toggle: toggle, panel: panel }); makeButtonLike(toggle, "LATICRETE menü"); setAttribute(toggle, "aria-controls", ensureId(panel, "qhl-laticrete-mobile-menu")); var mobileMedia = null; if (typeof global.matchMedia === "function") { mobileMedia = safeRun(function () { return global.matchMedia("(max-width: 992px)"); }); } function isMobile() { return mobileMedia ? Boolean(mobileMedia.matches) : true; } function closeBranches() { queryAll(menu, "[data-laticrete-menu-branch-toggle]").forEach(function (branch) { setAttribute(branch, "aria-expanded", "false"); var target = findTargetInsideRoot( root, firstAttribute(branch, ["data-laticrete-menu-target", "aria-controls"]), ); if (target && contains(menu, target)) target.hidden = true; }); } function setOpen(open, restoreFocus) { if (!isMobile()) open = false; setAttribute(toggle, "aria-expanded", open ? "true" : "false"); panel.hidden = isMobile() ? !open : false; classToggle(menu, "is-open", open); if (!open) closeBranches(); if (restoreFocus && typeof toggle.focus === "function") toggle.focus(); } setOpen(getAttribute(toggle, "aria-expanded") === "true", false); toggle.addEventListener("click", function (event) { if (event && typeof event.preventDefault === "function") event.preventDefault(); setOpen(getAttribute(toggle, "aria-expanded") !== "true", false); }); queryAll(menu, "[data-laticrete-menu-branch-toggle]").forEach(function (branch) { var target = findTargetInsideRoot( root, firstAttribute(branch, ["data-laticrete-menu-target", "aria-controls"]), ); if (!target || !contains(menu, target)) return; makeButtonLike(branch, null); setAttribute(branch, "aria-controls", ensureId(target, "qhl-laticrete-menu-branch")); target.hidden = getAttribute(branch, "aria-expanded") !== "true"; branch.addEventListener("click", function (event) { if (event && typeof event.preventDefault === "function") event.preventDefault(); var open = getAttribute(branch, "aria-expanded") !== "true"; if (open) { queryAll(menu, "[data-laticrete-menu-branch-toggle]").forEach(function (other) { if (other === branch) return; setAttribute(other, "aria-expanded", "false"); var otherTarget = findTargetInsideRoot( root, firstAttribute(other, ["data-laticrete-menu-target", "aria-controls"]), ); if (otherTarget && contains(menu, otherTarget)) otherTarget.hidden = true; }); } setAttribute(branch, "aria-expanded", open ? "true" : "false"); target.hidden = !open; }); }); menu.addEventListener("keydown", function (event) { if (event && event.key === "Escape") { var expandedBranch = query( menu, "[data-laticrete-menu-branch-toggle][aria-expanded=\"true\"]", ); if (!isMobile()) { if (!expandedBranch) return; if (typeof event.preventDefault === "function") event.preventDefault(); closeBranches(); if (typeof expandedBranch.focus === "function") expandedBranch.focus(); return; } if (typeof event.preventDefault === "function") event.preventDefault(); setOpen(false, true); } }); queryAll(panel, "a[href]").forEach(function (link) { link.addEventListener("click", function () { setOpen(false, false); }); }); if (doc && typeof doc.addEventListener === "function") { doc.addEventListener("click", function (event) { if (contains(menu, event.target)) return; closeBranches(); if (getAttribute(toggle, "aria-expanded") === "true") setOpen(false, false); }); } if (mobileMedia) { var viewportChanged = function () { setOpen(false, false); }; if (typeof mobileMedia.addEventListener === "function") { mobileMedia.addEventListener("change", viewportChanged); } else if (typeof mobileMedia.addListener === "function") { mobileMedia.addListener(viewportChanged); } } } function initializeShell(shell) { if (!shell) return; queryAll(shell, "[data-laticrete-mobile-menu]").forEach(function (menu) { safeRun(function () { initMobileMenu(shell, menu); }); }); } function initVideo(placeholder) { if (!placeholder || videoStates.has(placeholder)) return; var control = query(placeholder, "[data-laticrete-video-load]"); if (!control) return; var exactSrc = safeYouTubeEmbedUrl( getAttribute(placeholder, "data-laticrete-video-src"), ); if (!exactSrc) return; videoStates.set(placeholder, { loaded: false }); control.addEventListener("click", function () { var state = videoStates.get(placeholder); if (!state || state.loaded || !doc || typeof doc.createElement !== "function") return; var iframe = doc.createElement("iframe"); setAttribute(iframe, "src", exactSrc); setAttribute(iframe, "width", "560"); setAttribute(iframe, "height", "315"); setAttribute(iframe, "title", "LATICRETE gyártói videó"); setAttribute(iframe, "loading", "eager"); setAttribute(iframe, "allow", "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"); setAttribute(iframe, "referrerpolicy", "strict-origin-when-cross-origin"); setAttribute(iframe, "allowfullscreen", ""); iframe.className = "qhl-laticrete-video-frame"; state.loaded = true; removeAttribute(placeholder, "data-laticrete-video-src"); if (typeof placeholder.replaceChildren === "function") { placeholder.replaceChildren(iframe); } else if (control.parentNode && typeof control.parentNode.replaceChild === "function") { control.parentNode.replaceChild(iframe, control); } }); } function initializeRoot(root) { if (!root) return; queryAll(root, CAROUSEL_SELECTOR).forEach(function (carousel) { safeRun(function () { initCarousel(carousel); }); }); queryAll(root, ACCORDION_SELECTOR).forEach(function (accordion) { safeRun(function () { initAccordion(accordion); }); }); queryAll(root, GALLERY_SELECTOR).forEach(function (gallery) { safeRun(function () { initGallery(gallery); }); }); queryAll(root, "[data-laticrete-toggle]").forEach(function (control) { safeRun(function () { initExplicitToggle(root, control); }); }); queryAll(root, VIDEO_SELECTOR).forEach(function (placeholder) { safeRun(function () { initVideo(placeholder); }); }); } function init(scope) { var context = scope || doc; if (!context) return 0; var roots = []; if (typeof context.matches === "function" && context.matches(ROOT_SELECTOR)) { roots.push(context); } queryAll(context, ROOT_SELECTOR).forEach(function (root) { if (roots.indexOf(root) < 0) roots.push(root); }); roots.forEach(initializeRoot); var shells = []; if (typeof context.matches === "function" && context.matches(SHELL_SELECTOR)) { shells.push(context); } queryAll(context, SHELL_SELECTOR).forEach(function (shell) { if (shells.indexOf(shell) < 0) shells.push(shell); }); shells.forEach(initializeShell); return Math.max(roots.length, shells.length); } var api = freeze({ version: VERSION, init: init, __test: freeze({ directionFromSwipe: directionFromSwipe, ensureTargetSize: ensureTargetSize, parseBoolean: parseBoolean, readInterval: readInterval, safeImageUrl: safeImageUrl, safeYouTubeEmbedUrl: safeYouTubeEmbedUrl, wrapIndex: wrapIndex, }), }); global.QHLLaticreteExactRuntime = api; function boot() { safeRun(function () { init(doc); }); } if (doc) { if (doc.readyState === "loading" && typeof doc.addEventListener === "function") { doc.addEventListener("DOMContentLoaded", boot, { once: true }); } else { boot(); } } })(typeof window !== "undefined" ? window : globalThis); /* exact-runtime:laticrete-exact-filter-runtime.js */ (function (global) { "use strict"; var VERSION = "1.0.0"; var ROOT_SELECTOR = ".qhl-laticrete-exact"; var GRID_SELECTOR = "[data-laticrete-filter-grid]"; var FACET_SELECTOR = "[data-laticrete-filter-facets]"; var OPTION_SELECTOR = "[data-laticrete-filter-option]"; var CARD_SELECTOR = "[data-laticrete-filter-card]"; var existing = global.QHLLaticreteExactFilterRuntime; if (existing && existing.version === VERSION && typeof existing.init === "function") { existing.init(); return; } var doc = global.document || null; var states = typeof WeakMap === "function" ? new WeakMap() : new Map(); function freeze(value) { return typeof Object.freeze === "function" ? Object.freeze(value) : value; } function toArray(value) { return value ? Array.prototype.slice.call(value) : []; } function queryAll(scope, selector) { if (!scope || typeof scope.querySelectorAll !== "function") return []; try { return toArray(scope.querySelectorAll(selector)); } catch (_error) { return []; } } function query(scope, selector) { if (!scope || typeof scope.querySelector !== "function") return null; try { return scope.querySelector(selector); } catch (_error) { return null; } } function closest(element, selector) { if (!element || typeof element.closest !== "function") return null; try { return element.closest(selector); } catch (_error) { return null; } } function getAttribute(element, name) { if (!element || typeof element.getAttribute !== "function") return null; var value = element.getAttribute(name); return value === null || value === undefined ? null : String(value); } function setAttribute(element, name, value) { if (element && typeof element.setAttribute === "function") { element.setAttribute(name, String(value)); } } function removeAttribute(element, name) { if (element && typeof element.removeAttribute === "function") { element.removeAttribute(name); } } function classToggle(element, name, enabled) { if (element && element.classList && typeof element.classList.toggle === "function") { element.classList.toggle(name, Boolean(enabled)); } } function safeRun(callback) { try { return callback(); } catch (_error) { return undefined; } } function uniqueTokens(value) { if (typeof value !== "string") return []; var seen = Object.create(null); return value.trim().split(/\s+/).filter(function (token) { if (!/^[a-f0-9]{64}$/.test(token) || seen[token]) return false; seen[token] = true; return true; }); } function matchesFacetTokens(cardTokens, selectedTokens) { var card = Array.isArray(cardTokens) ? cardTokens : uniqueTokens(cardTokens); var selected = Array.isArray(selectedTokens) ? selectedTokens : uniqueTokens(selectedTokens); if (!selected.length) return true; for (var index = 0; index < selected.length; index += 1) { if (card.indexOf(selected[index]) >= 0) return true; } return false; } function setHidden(element, hidden) { if (!element) return; element.hidden = Boolean(hidden); if (hidden) setAttribute(element, "hidden", ""); else removeAttribute(element, "hidden"); } function ensureTargetSize(element) { if (!element || !element.style) return; element.style.minWidth = "44px"; element.style.minHeight = "44px"; element.style.touchAction = "manipulation"; } function findRouteFacet(root, routePath) { var facets = queryAll(root, FACET_SELECTOR); for (var index = 0; index < facets.length; index += 1) { if (getAttribute(facets[index], "data-laticrete-filter-route") === routePath) { return facets[index]; } } return null; } function initGrid(grid) { if (!grid || states.has(grid)) return; var root = closest(grid, ROOT_SELECTOR); if (!root) return; var routePath = getAttribute(grid, "data-laticrete-filter-route"); if (!routePath || routePath.charAt(0) !== "/") return; var facets = findRouteFacet(root, routePath); if (!facets) return; var options = queryAll(facets, OPTION_SELECTOR); var cards = queryAll(grid, CARD_SELECTOR); var clear = query(facets, "[data-laticrete-filter-clear]"); var status = query(grid, "[data-laticrete-filter-status]"); var empty = query(grid, "[data-laticrete-filter-empty]"); if (!options.length || !cards.length || !clear || !status || !empty) return; var optionTokens = options.map(function (option) { ensureTargetSize(option); return uniqueTokens(getAttribute(option, "data-laticrete-filter-token")); }); if (optionTokens.some(function (tokens) { return tokens.length !== 1; })) return; var cardTokens = cards.map(function (card) { return uniqueTokens(getAttribute(card, "data-laticrete-filter-memberships")); }); ensureTargetSize(clear); function update() { var selected = []; options.forEach(function (option, index) { var active = Boolean(option.checked); if (active) selected.push(optionTokens[index][0]); classToggle(closest(option, ".facet-value"), "active-facet", active); }); var visibleCount = 0; cards.forEach(function (card, index) { var visible = matchesFacetTokens(cardTokens[index], selected); setHidden(card, !visible); setAttribute(card, "aria-hidden", visible ? "false" : "true"); if (visible) visibleCount += 1; }); status.textContent = String(visibleCount) + " termék"; setHidden(empty, visibleCount !== 0); setHidden(clear, selected.length === 0); classToggle(clear, "has-active-facet", selected.length > 0); setAttribute(clear, "aria-disabled", selected.length ? "false" : "true"); } options.forEach(function (option) { option.addEventListener("change", update); }); clear.addEventListener("click", function (event) { if (event && typeof event.preventDefault === "function") event.preventDefault(); options.forEach(function (option) { option.checked = false; }); update(); if (options[0] && typeof options[0].focus === "function") options[0].focus(); }); states.set(grid, freeze({ update: update, routePath: routePath })); update(); } function init(scope) { var context = scope || doc; if (!context) return 0; var grids = []; if (typeof context.matches === "function" && context.matches(GRID_SELECTOR)) { grids.push(context); } queryAll(context, GRID_SELECTOR).forEach(function (grid) { if (grids.indexOf(grid) < 0) grids.push(grid); }); grids.forEach(function (grid) { safeRun(function () { initGrid(grid); }); }); return grids.length; } var api = freeze({ version: VERSION, init: init, __test: freeze({ ensureTargetSize: ensureTargetSize, matchesFacetTokens: matchesFacetTokens, uniqueTokens: uniqueTokens, }), }); global.QHLLaticreteExactFilterRuntime = api; function boot() { safeRun(function () { init(doc); }); } if (doc) { if (doc.readyState === "loading" && typeof doc.addEventListener === "function") { doc.addEventListener("DOMContentLoaded", boot, { once: true }); } else { boot(); } } })(typeof window !== "undefined" ? window : globalThis); /* exact-runtime:laticrete-exact-document-library-runtime.js */ (function (global) { "use strict"; var VERSION = "2.1.0"; var ROOT_SELECTOR = ".qhl-laticrete-exact"; var LIBRARY_SELECTOR = "[data-laticrete-document-library]"; var CARD_SELECTOR = "[data-laticrete-document-card]"; var PAGE_SELECTOR = "[data-laticrete-document-page]"; var FACET_CONTROL_SELECTOR = "[data-laticrete-document-facet-control]"; var FACET_SELECT_SELECTOR = "[data-laticrete-document-facet-select]"; var existing = global.QHLLaticreteExactDocumentLibraryRuntime; if (existing && existing.version === VERSION && typeof existing.init === "function") { existing.init(); return; } var doc = global.document || null; var states = typeof WeakMap === "function" ? new WeakMap() : new Map(); function freeze(value) { return typeof Object.freeze === "function" ? Object.freeze(value) : value; } function toArray(value) { return value ? Array.prototype.slice.call(value) : []; } function queryAll(scope, selector) { if (!scope || typeof scope.querySelectorAll !== "function") return []; try { return toArray(scope.querySelectorAll(selector)); } catch (_error) { return []; } } function query(scope, selector) { if (!scope || typeof scope.querySelector !== "function") return null; try { return scope.querySelector(selector); } catch (_error) { return null; } } function closest(element, selector) { if (!element || typeof element.closest !== "function") return null; try { return element.closest(selector); } catch (_error) { return null; } } function getAttribute(element, name) { if (!element || typeof element.getAttribute !== "function") return null; var value = element.getAttribute(name); return value === null || value === undefined ? null : String(value); } function setAttribute(element, name, value) { if (element && typeof element.setAttribute === "function") { element.setAttribute(name, String(value)); } } function removeAttribute(element, name) { if (element && typeof element.removeAttribute === "function") { element.removeAttribute(name); } } function classToggle(element, name, enabled) { if (element && element.classList && typeof element.classList.toggle === "function") { element.classList.toggle(name, Boolean(enabled)); } } function safeRun(callback) { try { return callback(); } catch (_error) { return undefined; } } function setHidden(element, hidden) { if (!element) return; element.hidden = Boolean(hidden); if (hidden) setAttribute(element, "hidden", ""); else removeAttribute(element, "hidden"); } function ensureTargetSize(element) { if (!element || !element.style) return; element.style.minWidth = "44px"; element.style.minHeight = "44px"; element.style.touchAction = "manipulation"; } function normalizeText(value) { var normalized = String(value || "").toLocaleLowerCase("hu-HU"); if (typeof normalized.normalize === "function") { normalized = normalized.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); } return normalized.replace(/[^a-z0-9®™]+/g, " ").trim(); } function decodeSearchToken(value) { try { return decodeURIComponent(String(value || "")); } catch (_error) { return ""; } } function matchesDocument(searchText, recordType, queryValue, selectedType) { if (selectedType && recordType !== selectedType) return false; var queryText = normalizeText(queryValue); if (!queryText) return true; var haystack = normalizeText(searchText); return queryText.split(/\s+/).every(function (token) { return token && haystack.indexOf(token) >= 0; }); } function tokenList(value) { if (Array.isArray(value)) return value.filter(Boolean).map(String); return String(value || "").split(/\s+/).filter(Boolean); } function matchesFacetGroups(recordMemberships, selectedGroups) { var available = tokenList(recordMemberships); var groups = selectedGroups && typeof selectedGroups === "object" ? selectedGroups : {}; return Object.keys(groups).every(function (facetToken) { var selected = tokenList(groups[facetToken]); if (!selected.length) return true; return selected.some(function (valueToken) { return available.indexOf(valueToken) >= 0; }); }); } function selectedFacetGroups(checkboxes, selects) { var groups = {}; toArray(checkboxes).forEach(function (control) { if (!control || !control.checked || control.disabled) return; var facetToken = getAttribute(control, "data-laticrete-document-facet-token") || ""; var valueToken = getAttribute(control, "data-laticrete-document-value-token") || ""; if (!facetToken || !valueToken) return; if (!groups[facetToken]) groups[facetToken] = []; if (groups[facetToken].indexOf(valueToken) < 0) groups[facetToken].push(valueToken); }); toArray(selects).forEach(function (control) { if (!control || control.disabled) return; var facetToken = getAttribute(control, "data-laticrete-document-facet-token") || ""; var valueToken = String(control.value || ""); if (!facetToken || !valueToken) return; groups[facetToken] = [valueToken]; }); return freeze(groups); } function paginationWindow(total, requestedPage, requestedSize) { var size = Number(requestedSize); if ([10, 50, 100, 200, 500].indexOf(size) < 0) size = 10; var pageCount = Math.max(1, Math.ceil(Math.max(0, Number(total) || 0) / size)); var page = Math.max(1, Math.min(pageCount, Math.trunc(Number(requestedPage) || 1))); return freeze({ page: page, pageCount: pageCount, pageSize: size, start: (page - 1) * size, end: Math.min(Math.max(0, Number(total) || 0), page * size), }); } function numericPageVisible(pageNumber, page, pageCount) { if (pageNumber < 1 || pageNumber > pageCount) return false; var first = Math.max(1, Math.min(page - 2, pageCount - 4)); var last = Math.min(pageCount, first + 4); return pageNumber >= first && pageNumber <= last; } function setControlDisabled(control, disabled) { var item = closest(control, "li"); setAttribute(control, "aria-disabled", disabled ? "true" : "false"); if (disabled) setAttribute(control, "tabindex", "-1"); else removeAttribute(control, "tabindex"); classToggle(control, "inactive", disabled); classToggle(item, "inactive", disabled); } function initLibrary(library) { if (!library || states.has(library)) return; var root = closest(library, ROOT_SELECTOR); if (!root) return; var search = query(library, "[data-laticrete-document-search]"); var submit = query(library, "[data-laticrete-document-search-submit]"); var results = query(root, "[data-laticrete-document-results]"); var list = query(results, "[data-laticrete-document-list]"); var status = query(results, "[data-laticrete-document-status]"); var empty = query(results, "[data-laticrete-document-empty]"); var facetControls = queryAll(root, FACET_CONTROL_SELECTOR); var facetSelects = queryAll(root, FACET_SELECT_SELECTOR); var pageSize = query(root, "[data-laticrete-document-page-size]"); var pager = query(root, "[data-laticrete-document-pager]"); var pagerComponent = closest(pager, "[data-laticrete-document-pager-component]"); var cards = queryAll(results, CARD_SELECTOR); var pageControls = queryAll(pager, PAGE_SELECTOR); if ( !search || !submit || !results || !list || !status || !empty || !pageSize || !pager || !cards.length || !pageControls.length || !facetControls.length || !facetSelects.length ) return; [search, submit, pageSize] .concat(facetControls, facetSelects, pageControls) .forEach(ensureTargetSize); var records = cards.map(function (card) { return freeze({ card: card, type: getAttribute(card, "data-laticrete-document-type") || "", searchText: decodeSearchToken( getAttribute(card, "data-laticrete-document-search-token") || "", ), membershipTokens: tokenList( getAttribute(card, "data-laticrete-document-memberships") || "", ), }); }); var state = { page: 1, pageSize: 10, matching: [] }; function updatePager(windowState) { pageControls.forEach(function (control) { var raw = getAttribute(control, "data-laticrete-document-page") || ""; var item = closest(control, "li"); var pageNumber = Number(raw); if (Number.isInteger(pageNumber) && pageNumber > 0) { var visible = numericPageVisible(pageNumber, windowState.page, windowState.pageCount); setHidden(item, !visible); var active = pageNumber === windowState.page; if (active) setAttribute(control, "aria-current", "page"); else removeAttribute(control, "aria-current"); classToggle(control, "active", active); setControlDisabled(control, active); return; } setHidden(item, false); if (raw === "first" || raw === "previous") { setControlDisabled(control, windowState.page <= 1); } else if (raw === "next" || raw === "last") { setControlDisabled(control, windowState.page >= windowState.pageCount); } else { setControlDisabled(control, true); } }); setHidden(pagerComponent, state.matching.length === 0); } function update() { state.pageSize = Number(pageSize.value); var selectedGroups = selectedFacetGroups(facetControls, facetSelects); state.matching = records.filter(function (record) { return ( matchesDocument(record.searchText, record.type, search.value, "") && matchesFacetGroups(record.membershipTokens, selectedGroups) ); }); var windowState = paginationWindow(state.matching.length, state.page, state.pageSize); state.page = windowState.page; var visibleCards = new Set( state.matching.slice(windowState.start, windowState.end).map(function (record) { return record.card; }), ); records.forEach(function (record) { var visible = visibleCards.has(record.card); setHidden(record.card, !visible); setAttribute(record.card, "aria-hidden", visible ? "false" : "true"); }); var count = state.matching.length; status.textContent = count ? String(count) + " dokumentum · " + String(windowState.page) + "/" + String(windowState.pageCount) + ". oldal" : "0 dokumentum"; setHidden(empty, count !== 0); setHidden(list, count === 0); updatePager(windowState); } function resetAndUpdate() { state.page = 1; update(); } search.addEventListener("input", resetAndUpdate); search.addEventListener("keydown", function (event) { if (!event) return; if (event.key === "Enter") { if (typeof event.preventDefault === "function") event.preventDefault(); resetAndUpdate(); } else if (event.key === "Escape" && search.value) { if (typeof event.preventDefault === "function") event.preventDefault(); search.value = ""; resetAndUpdate(); } }); submit.addEventListener("click", function (event) { if (event && typeof event.preventDefault === "function") event.preventDefault(); resetAndUpdate(); }); facetControls.concat(facetSelects).forEach(function (control) { control.addEventListener("change", resetAndUpdate); }); pageSize.addEventListener("change", resetAndUpdate); pageControls.forEach(function (control) { control.addEventListener("click", function (event) { if (event && typeof event.preventDefault === "function") event.preventDefault(); if (getAttribute(control, "aria-disabled") === "true") return; var raw = getAttribute(control, "data-laticrete-document-page") || ""; if (raw === "first") state.page = 1; else if (raw === "previous") state.page -= 1; else if (raw === "next") state.page += 1; else if (raw === "last") state.page = Number.MAX_SAFE_INTEGER; else if (Number.isInteger(Number(raw)) && Number(raw) > 0) state.page = Number(raw); update(); if (typeof status.focus === "function") { if (!getAttribute(status, "tabindex")) setAttribute(status, "tabindex", "-1"); status.focus(); } }); }); states.set(library, freeze({ update: update })); update(); } function init(scope) { var context = scope || doc; if (!context) return 0; var libraries = []; if (typeof context.matches === "function" && context.matches(LIBRARY_SELECTOR)) { libraries.push(context); } queryAll(context, LIBRARY_SELECTOR).forEach(function (library) { if (libraries.indexOf(library) < 0) libraries.push(library); }); libraries.forEach(function (library) { safeRun(function () { initLibrary(library); }); }); return libraries.length; } var api = freeze({ version: VERSION, init: init, __test: freeze({ ensureTargetSize: ensureTargetSize, decodeSearchToken: decodeSearchToken, matchesFacetGroups: matchesFacetGroups, matchesDocument: matchesDocument, normalizeText: normalizeText, numericPageVisible: numericPageVisible, paginationWindow: paginationWindow, selectedFacetGroups: selectedFacetGroups, tokenList: tokenList, }), }); global.QHLLaticreteExactDocumentLibraryRuntime = api; function boot() { safeRun(function () { init(doc); }); } if (doc) { if (doc.readyState === "loading" && typeof doc.addEventListener === "function") { doc.addEventListener("DOMContentLoaded", boot, { once: true }); } else { boot(); } } })(typeof window !== "undefined" ? window : globalThis); /* exact-runtime:laticrete-exact-estimator-runtime.js */ (function attachLaticreteExactEstimator(global) { "use strict"; var DATA_SCHEMA = "qhl.laticrete.exact-estimator-runtime.v1"; var ENVELOPE_SCHEMA = "qhl.laticrete.unas-json-envelope.v1"; var API_NAME = "QhlLaticreteExactEstimatorRuntime"; function positiveNumber(value, label) { var parsed = Number(value); if (!Number.isFinite(parsed) || parsed <= 0) { throw new Error("Érvénytelen kalkulátorérték: " + label + "."); } return parsed; } function boundedNumber(value, limits, label) { var parsed = positiveNumber(String(value).replace(",", "."), label); if (parsed < limits.min || parsed > limits.max) { throw new Error(label + ": " + limits.min + " és " + limits.max + " közötti értéket adjon meg."); } return parsed; } function roundEven(value, digits) { var places = digits == null ? 2 : digits; var factor = Math.pow(10, places); var scaled = value * factor; var lower = Math.floor(scaled); var fraction = scaled - lower; var epsilon = Number.EPSILON * Math.max(1, Math.abs(scaled)) * 8; if (fraction < 0.5 - epsilon) return lower / factor; if (fraction > 0.5 + epsilon) return (lower + 1) / factor; return (lower % 2 === 0 ? lower : lower + 1) / factor; } function calculateAdhesive(projectArea, coveragePerUnit) { var area = positiveNumber(projectArea, "projectArea"); var coverage = positiveNumber(coveragePerUnit, "coveragePerUnit"); var rawRequired = area / coverage; return { coveragePerUnit: coverage, requiredAmount: roundEven(rawRequired, 2), suggestedQuantity: Math.ceil(rawRequired), }; } function calculateGrout(input) { var projectArea = positiveNumber(input.projectArea, "projectArea"); var productCoverage = positiveNumber(input.productCoverage, "productCoverage"); var tileWidth = positiveNumber(input.tileWidth, "tileWidth"); var tileHeight = positiveNumber(input.tileHeight, "tileHeight"); var tileThickness = positiveNumber(input.tileThickness, "tileThickness"); var jointSize = positiveNumber(input.jointSize, "jointSize"); var moduleArea = (tileWidth + jointSize) * (tileHeight + jointSize); var jointFraction = 1 - (tileWidth * tileHeight) / moduleArea; var coveragePerUnit = roundEven(productCoverage / (144 * jointFraction * tileThickness), 2); var rawRequired = projectArea / coveragePerUnit; return { coveragePerUnit: coveragePerUnit, requiredAmount: roundEven(rawRequired, 2), suggestedQuantity: Math.ceil(rawRequired), }; } function isAllowedProductImageUrl(value) { var candidate = String(value || ""); if (/\\|%(?:2e|2f|5c)/i.test(candidate)) return false; if (candidate.startsWith("/assets/laticrete/")) { return ( candidate.indexOf("?") === -1 && candidate.indexOf("#") === -1 && !/(?:^|\/)\.{1,2}(?:\/|$)/.test(candidate) ); } try { var parsed = new URL(candidate); return ( parsed.origin === "https://www.qhl.hu" && parsed.username === "" && parsed.password === "" && parsed.search === "" && parsed.hash === "" && parsed.pathname.startsWith("/shop_ordered/15586/pic/laticrete/") ); } catch (_error) { return false; } } function validateData(data) { if (!data || data.schema !== DATA_SCHEMA || data.sourceGatesPassed !== true) { throw new Error("A LATICRETE kalkulátor adatcsomagja nem publikálható."); } if ( !Array.isArray(data.supportedBranches) || data.supportedBranches.join(",") !== "adhesives,grout" || !Array.isArray(data.products) || data.products.length !== 9 || data.products.filter(function isAdhesive(product) { return product.branch === "adhesives"; }).length !== 3 || data.products.filter(function isGrout(product) { return product.branch === "grout"; }).length !== 6 || !data.options || data.options.jointSizes.length !== 8 || data.options.tileThicknesses.length !== 6 ) { throw new Error("A LATICRETE kalkulátor bizonyított európai adatkészlete eltér."); } data.products.forEach(function validateProduct(product) { if ( !product.id || !product.name || !isAllowedProductImageUrl(product.localImageUrl) || (product.qhlProductUrl !== null && !String(product.qhlProductUrl).startsWith("https://www.qhl.hu/")) ) { throw new Error("Érvénytelen helyi LATICRETE termékkötés."); } }); return data; } function createSession(sourceData) { var data = validateData(sourceData); var state = { stage: "branches", branch: null, productId: null, quote: null, }; function productById(productId) { return data.products.find(function matches(product) { return product.id === productId; }) || null; } function snapshot() { return { stage: state.stage, branch: state.branch, productId: state.productId, quote: state.quote ? Object.assign({}, state.quote) : null, }; } function chooseBranch(branch) { if (data.supportedBranches.indexOf(branch) === -1) throw new Error("Nem támogatott kalkulátorág."); state.branch = branch; state.productId = null; state.quote = null; state.stage = "products"; return snapshot(); } function chooseProduct(productId) { var product = productById(productId); if (!product || !state.branch || product.branch !== state.branch) { throw new Error("A termék nem tartozik a kiválasztott kalkulátorághoz."); } state.productId = product.id; state.quote = null; state.stage = "details"; return snapshot(); } function backToBranches() { state.stage = "branches"; state.branch = null; state.productId = null; state.quote = null; return snapshot(); } function backToProducts() { if (!state.branch) return backToBranches(); state.stage = "products"; state.productId = null; state.quote = null; return snapshot(); } function calculate(input) { var product = productById(state.productId); if (!product || !state.branch) throw new Error("Válasszon terméket a kalkulációhoz."); var projectArea = boundedNumber(input.projectArea, data.limits.projectArea, "Projektterület"); if (product.branch === "adhesives") { var trowelIndex = Number(input.trowelIndex); var trowel = product.trowelCoverage[trowelIndex]; if (!trowel) throw new Error("Válasszon fogazott simítóméretet."); state.quote = calculateAdhesive(projectArea, trowel.coverage); } else { if (product.groutCoverage == null) throw new Error("A fugázó gyártói lefedettségi adata hiányzik."); state.quote = calculateGrout({ projectArea: projectArea, productCoverage: product.groutCoverage, tileWidth: boundedNumber(input.tileWidth, data.limits.tileWidth, "Lap szélessége"), tileHeight: boundedNumber(input.tileHeight, data.limits.tileHeight, "Lap hossza"), tileThickness: positiveNumber(input.tileThickness, "Lapvastagság"), jointSize: positiveNumber(input.jointSize, "Fugaszélesség"), }); } state.stage = "result"; return Object.assign({}, state.quote); } return Object.freeze({ data: data, getState: snapshot, productById: productById, chooseBranch: chooseBranch, chooseProduct: chooseProduct, backToBranches: backToBranches, backToProducts: backToProducts, calculate: calculate, }); } function one(root, selector) { return root.querySelector(selector); } function all(root, selector) { return Array.prototype.slice.call(root.querySelectorAll(selector)); } function setText(root, selector, value) { var node = one(root, selector); if (node) node.textContent = value; } function updateTrowels(root, product) { var select = one(root, '[data-laticrete-estimator-input="trowel-index"]'); if (!select) return; var options = product.trowelCoverage.map(function makeOption(trowel, index) { var option = root.ownerDocument.createElement("option"); option.value = String(index); option.textContent = trowel.standardName + " — " + trowel.coverage + " ft²/kiszerelés"; return option; }); select.replaceChildren.apply(select, options); select.value = "0"; } function enhance(root, sourceData) { if (!root || root.getAttribute("data-laticrete-estimator-enhanced") === "true") return null; var session = createSession(sourceData); var formatter = new Intl.NumberFormat("hu-HU", { maximumFractionDigits: 2 }); function branchLabel(branch) { var record = session.data.branches.find(function matches(item) { return item.id === branch; }); return record ? record.labelHu : ""; } function currentProduct() { return session.productById(session.getState().productId); } function focusPanel(stage) { var title = one(root, '[data-laticrete-estimator-panel="' + stage + '"] h3[tabindex="-1"]'); if (title && typeof title.focus === "function") title.focus(); } function render(shouldFocus) { var state = session.getState(); var visiblePanel = state.stage === "result" ? "details" : state.stage; root.setAttribute("data-laticrete-estimator-stage", state.stage); all(root, "[data-laticrete-estimator-panel]").forEach(function togglePanel(panel) { panel.hidden = panel.getAttribute("data-laticrete-estimator-panel") !== visiblePanel; }); all(root, "[data-laticrete-estimator-step]").forEach(function toggleStep(marker) { var name = marker.getAttribute("data-laticrete-estimator-step"); var order = { branches: 0, products: 1, details: 2 }; var active = visiblePanel === name; marker.classList.toggle("is-active", active); marker.classList.toggle("is-complete", order[name] < order[visiblePanel]); if (active) marker.setAttribute("aria-current", "step"); else marker.removeAttribute("aria-current"); }); if (state.branch) { var label = branchLabel(state.branch); all(root, "[data-laticrete-estimator-branch-label], [data-laticrete-estimator-detail-branch]") .forEach(function setBranch(node) { node.textContent = label; }); all(root, "[data-laticrete-estimator-products]").forEach(function toggleProducts(list) { list.hidden = list.getAttribute("data-laticrete-estimator-products") !== state.branch; }); all(root, "[data-laticrete-estimator-fields]").forEach(function toggleFields(fields) { fields.hidden = fields.getAttribute("data-laticrete-estimator-fields") !== state.branch; }); } var product = currentProduct(); if (product) { setText(root, "[data-laticrete-estimator-selected-name]", product.name); setText(root, "[data-laticrete-estimator-selected-summary]", product.summaryHu); setText(root, "[data-laticrete-estimator-result-name]", product.name); var image = one(root, ".qhl-laticrete-estimator__selected-image-frame img"); if (image) { image.setAttribute("src", product.localImageUrl); image.setAttribute("alt", product.imageAltHu); } if (product.branch === "adhesives") updateTrowels(root, product); var shop = one(root, "[data-laticrete-estimator-shop-link]"); if (shop) { shop.hidden = !product.qhlProductUrl; if (product.qhlProductUrl) shop.setAttribute("href", product.qhlProductUrl); else shop.removeAttribute("href"); } } var result = one(root, "[data-laticrete-estimator-result]"); if (result) result.hidden = !state.quote; if (state.quote) { setText(root, '[data-laticrete-estimator-output="coverage"]', formatter.format(state.quote.coveragePerUnit) + " ft²"); setText(root, '[data-laticrete-estimator-output="required"]', state.quote.requiredAmount.toLocaleString("hu-HU", { minimumFractionDigits: 2, maximumFractionDigits: 2 })); setText(root, '[data-laticrete-estimator-output="suggested"]', formatter.format(state.quote.suggestedQuantity) + " kiszerelés"); } if (shouldFocus) focusPanel(visiblePanel); } function showError(message) { var error = one(root, "[data-laticrete-estimator-error]"); if (!error) return; error.textContent = message || ""; error.hidden = !message; } function inputValue(name) { var field = one(root, '[data-laticrete-estimator-input="' + name + '"]'); return field ? field.value : ""; } function runCalculation() { showError(""); try { session.calculate({ projectArea: inputValue("project-area"), trowelIndex: inputValue("trowel-index"), tileWidth: inputValue("tile-width"), tileHeight: inputValue("tile-height"), tileThickness: session.data.options.tileThicknesses[Number(inputValue("tile-thickness"))].standardValue, jointSize: session.data.options.jointSizes[Number(inputValue("joint-size"))].standardValue, }); render(false); var result = one(root, "[data-laticrete-estimator-result]"); if (result && typeof result.focus === "function") { result.setAttribute("tabindex", "-1"); result.focus(); } } catch (error) { showError(error instanceof Error ? error.message : "A kalkuláció nem végezhető el."); } } root.addEventListener("click", function handleClick(event) { var target = event.target && typeof event.target.closest === "function" ? event.target.closest("[data-laticrete-estimator-action]") : null; if (!target || !root.contains(target)) return; var action = target.getAttribute("data-laticrete-estimator-action"); showError(""); try { if (action === "choose-branch") { session.chooseBranch(target.getAttribute("data-laticrete-estimator-branch")); render(true); } else if (action === "choose-product") { session.chooseProduct(target.getAttribute("data-laticrete-estimator-product")); render(true); } else if (action === "back-to-branches") { session.backToBranches(); render(true); } else if (action === "back-to-products") { session.backToProducts(); render(true); } else if (action === "calculate") { runCalculation(); } } catch (error) { showError(error instanceof Error ? error.message : "A művelet nem végezhető el."); } }); root.addEventListener("keydown", function handleEnter(event) { if (event.key !== "Enter" || !event.target || !event.target.matches("input, select")) return; if (session.getState().stage !== "details" && session.getState().stage !== "result") return; event.preventDefault(); runCalculation(); }); root.setAttribute("data-laticrete-estimator-enhanced", "true"); render(false); return session; } function parseDataEnvelope(serialized) { var envelope = JSON.parse(String(serialized || "")); if ( !envelope || envelope.schema !== ENVELOPE_SCHEMA || envelope.encoding !== "uri-component" || typeof envelope.payload !== "string" ) { throw new Error("Invalid estimator data envelope."); } return JSON.parse(decodeURIComponent(envelope.payload)); } function initialize(scope) { var documentScope = scope || (typeof document !== "undefined" ? document : null); if (!documentScope) return []; return Array.prototype.slice.call( documentScope.querySelectorAll("[data-laticrete-exact-estimator]"), ).map(function initializeRoot(root) { var dataId = root.getAttribute("data-laticrete-exact-estimator-data-id"); var dataNode = dataId ? root.ownerDocument.getElementById(dataId) : null; if (!dataNode || dataNode.getAttribute("type") !== "application/json") return null; try { return enhance(root, parseDataEnvelope(dataNode.textContent || "")); } catch (error) { root.setAttribute("data-laticrete-estimator-error", "invalid-data"); return null; } }).filter(Boolean); } global[API_NAME] = Object.freeze({ schema: DATA_SCHEMA, roundEven: roundEven, calculateAdhesive: calculateAdhesive, calculateGrout: calculateGrout, validateData: validateData, createSession: createSession, enhance: enhance, initialize: initialize, parseDataEnvelope: parseDataEnvelope, }); if (typeof document !== "undefined") { if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", function ready() { initialize(document); }, { once: true }); } else { initialize(document); } } })(typeof globalThis !== "undefined" ? globalThis : window);