/* ─── TRP-Exclude: Konfigurator sofort vor Übersetzung schützen ─── */ (function () { var sels = ['.mkl_pc_container', '.mkl_pc', 'ul.layers', '.mkl_pc_toolbar', '.mx-variant-header', '.mx-tech-specs']; function mark() { for (var i = 0; i < sels.length; i++) { var els = document.querySelectorAll(sels[i]); for (var j = 0; j < els.length; j++) els[j].setAttribute('data-no-translation', ''); } } mark(); document.addEventListener('DOMContentLoaded', mark); setTimeout(mark, 0); setTimeout(mark, 50); // ← PERF: 50ms statt 200ms reicht für TRP })(); /* ─── MX Pre-Hide: Layer-/Choice-Namen auf Nicht-DE Seiten sofort verstecken ─── * Verhindert den deutschen "Flash" bevor mxTranslateDom() läuft. * Nutzt das lang-Attribut des -Tags. * Sobald mxTranslateDom() den .mx-tl Span einfuegt, wird die uebersetzte * Version sichtbar und das Original bleibt via mx-has-tl versteckt. * * WICHTIG: Kein !important – stattdessen wird das ').appendTo('head'); } /* ─── Schlitzungsmuster Info-Popup CSS → ausgelagert in configurator.css ─── */ /* ─── MX Translation Helper ─── * Nutzt window.MX_TRANSLATIONS (aus PHP geladen) um deutsche Texte * in die aktuelle Sprache zu übersetzen. * WICHTIG: Die internen Keys (für Logik) bleiben IMMER deutsch. * Nur sichtbare DOM-Texte werden übersetzt. */ var MX_T = window.MX_TRANSLATIONS || {}; var MX_IS_DE = !window.MX_LANG || window.MX_LANG === 'DE'; /* ─── MX Translation CSS: Sofort injizieren (verhindert deutschen Flash) ─── * Auf nicht-DE Seiten werden layer-name / choice-name etc. SOFORT per * visibility:hidden unsichtbar gemacht – noch bevor mxTranslateDom() feuert. * Das verhindert den kurzen Aufblitz der deutschen Texte. * Sobald .mx-has-tl gesetzt + .mx-tl Span eingefügt wird, ist der * englische Text sichtbar und das Original bleibt per display:none verborgen. */ if (!MX_IS_DE && !document.getElementById('mx-translation-css')) { $('').appendTo('head'); } // Case-insensitive Lookup-Map: lowercase(DE) → translated var MX_T_LC = {}; (function () { for (var k in MX_T) { if (MX_T.hasOwnProperty(k)) { MX_T_LC[k.toLowerCase()] = MX_T[k]; } } })(); /** * Übersetze einen deutschen Text. Case-insensitive. * Behält die Original-Schreibweise bei: * "HART" → "HARD" (alles uppercase) * "Hart" → "Hard" (Title case) * "hart" → "hard" (lowercase) */ function mxT(text) { if (MX_IS_DE || !text) return text; var trimmed = (typeof text === 'string') ? text.trim() : String(text); if (!trimmed) return trimmed; // 1) Exakter Match if (MX_T[trimmed]) return MX_T[trimmed]; // 2) Case-insensitive Match var lc = trimmed.toLowerCase(); var translated = MX_T_LC[lc]; if (!translated) return trimmed; // 3) Casing vom Original beibehalten var isAllUpper = trimmed === trimmed.toUpperCase(); var isAllLower = trimmed === trimmed.toLowerCase(); var isTitleCase = !isAllUpper && !isAllLower && trimmed[0] === trimmed[0].toUpperCase(); if (isAllUpper) return translated.toUpperCase(); if (isAllLower) return translated.toLowerCase(); if (isTitleCase) return translated.charAt(0).toUpperCase() + translated.slice(1); return translated; } /** * Übersetzt alle sichtbaren Texte im Konfigurator DOM. * * Strategie für .layer-name und .choice-name: * - Originaltext bleibt UNBERÜHRT ($.text() gibt immer deutsch zurück) * - Ein Geschwister-Span .mx-tl wird NEBEN dem Original eingefügt * - Das Original wird per CSS versteckt, der .mx-tl ist sichtbar * - So funktioniert die interne Logik weiter mit deutschen Namen */ function mxTranslateDom() { if (MX_IS_DE) return; if (!Object.keys(MX_T).length) return; mxLog('Translating DOM to', window.MX_LANG, '(' + Object.keys(MX_T).length + ' entries)'); // ── Helper: Translated sibling neben Element einfügen ── function setTranslatedSibling($el, translated, tagClass) { // Prüfe ob schon ein .mx-tl Geschwister existiert var $existing = $el.next('.mx-tl'); if ($existing.length) { // Update falls Text sich geändert hat if ($existing.text() !== translated) { $existing.text(translated); } } else { // Neuen Span einfügen mit gleicher CSS-Klasse wie das Original var origClasses = $el.attr('class') || ''; // Entferne mx-has-tl aus den kopierten Klassen var newClasses = origClasses.replace(/\bmx-has-tl\b/g, '').trim(); var $tl = $('').text(translated); $el.after($tl); } $el.addClass('mx-has-tl'); } // ── Helper: Entferne Übersetzung wenn nicht mehr nötig ── function removeTranslatedSibling($el) { $el.next('.mx-tl').remove(); $el.removeClass('mx-has-tl'); // Sichtbarkeit wiederherstellen: mx-show überschreibt die pre-hide CSS-Regel $el.addClass('mx-show'); } // 1) Layer-Namen $('ul.layers > li.layers-list-item').each(function () { var $nameEl = $(this).find('> .layer-item .layer-name').first(); var original = $.trim($nameEl.text()); if (!original) return; var translated = mxT(original); if (translated !== original) { setTranslatedSibling($nameEl, translated); } else { removeTranslatedSibling($nameEl); } }); // 2) Choice-Namen $('ul.layers .layer_choices li.choice').each(function () { var $nameEl = $(this).find('.choice-name').first(); var original = $.trim($nameEl.text()); if (!original) return; var translated = mxT(original); if (translated !== original) { setTranslatedSibling($nameEl, translated); } else { removeTranslatedSibling($nameEl); } }); // 3) Selected-choice Labels $('ul.layers > li.layers-list-item .selected-choice').each(function () { var $el = $(this); var original = $.trim($el.text()); if (!original) { removeTranslatedSibling($el); return; } var translated = mxT(original); if (translated !== original) { setTranslatedSibling($el, translated); } else { removeTranslatedSibling($el); } }); // 3b) Aufnahme-Radio Labels (nur reine Text-Radios, NICHT innerhalb Bild-Tiles) $('.mx-aufnahme-radio-label').each(function () { var $el = $(this); if ($el.hasClass('mx-tl')) return; // Skip wenn innerhalb eines Bild-Tiles (Aufnahme-Cat, Drehrichtung) if ($el.closest('.mx-aufnahme-tile, .mx-dreh-tile, .mx-aufnahme-cat-tile').length) return; // Skip wenn Geschwister ein img hat (Bild-Tile) if ($el.siblings('img').length) return; var original = $.trim($el.text()); if (!original) return; var translated = mxT(original); if (translated !== original) { setTranslatedSibling($el, translated); } else { // Kein Übersetzungs-Span nötig → Sichtbarkeit wiederherstellen removeTranslatedSibling($el); } }); // 3c) Drehrichtung-Tile Labels — SKIP: Bilder enthalten bereits den Text // 3d) Besatz-Tile Labels $('.mx-besatz-tile-label, .mx-besatz-tile span').each(function () { var $el = $(this); if ($el.hasClass('mx-tl')) return; var original = $.trim($el.text()); if (!original) return; var translated = mxT(original); if (translated !== original) { $el.text(translated); } // Sichtbarkeit immer wiederherstellen (war per :not(.mx-show) CSS versteckt) $el.addClass('mx-show'); }); // 3e) Aufnahme category tiles — SKIP: Bilder enthalten bereits den Text // 3f) Drehrichtung tiles — SKIP: Bilder enthalten bereits den Text // 3f) Edition / Evo tile labels $('.mx-evo-tile-label, .mx-edition-label').each(function () { var original = $.trim($(this).text()); if (!original) return; var translated = mxT(original); if (translated !== original) { $(this).text(translated); } }); // 3g) Reinigungsvlies / Kraftband no-summary tile labels $('.mx-edition-no-summary-tile-label').each(function () { var $el = $(this); if ($el.hasClass('mx-tl')) return; var original = $.trim($el.text()); if (!original) return; var translated = mxT(original); if (translated !== original) { $el.text(translated); } }); // 4) Tech-Spec Labels (sicher per .text() zu ändern) $('.mx-spec-item .mx-spec-label').each(function () { var original = $(this).attr('data-mx-original') || $.trim($(this).text()); if (!original) return; var translated = mxT(original); if (translated !== original) { $(this).text(translated); } }); // 5) Tech-Spec Values $('.mx-spec-item .mx-spec-value').each(function () { var original = $.trim($(this).text()); if (!original) return; if (/^\d/.test(original)) return; var translated = mxT(original); if (translated !== original) { $(this).text(translated); } }); // 6) Tab-Header Texte $('.wc-tabs li a, .woocommerce-tabs .tabs li a, .elementor-tab-title').each(function () { var original = $.trim($(this).text()); if (!original) return; var translated = mxT(original); if (translated !== original) { $(this).text(translated); } }); // 7) Statische Texte $('.mx-tech-specs p, .mkl_pc_container .no-data-text').each(function () { var original = $.trim($(this).text()); if (!original) return; var translated = mxT(original); if (translated !== original) { $(this).text(translated); } }); // 8) Produkttitel + Artikel-ID Bereich $('.mx-variant-title, .product_title, h1.entry-title').each(function () { var $el = $(this); var original = $.trim($el.text()); if (!original) return; var translated = mxT(original); if (translated !== original) { $el.text(translated); } }); // 9) Breadcrumb (letztes Element = Produktname) $('.woocommerce-breadcrumb, .breadcrumbs').find('span:last, a:last').each(function () { var original = $.trim($(this).text()); if (!original) return; var translated = mxT(original); if (translated !== original) { $(this).text(translated); } }); // 10) Reset-Button Label ("Konfiguration zurücksetzen") $('.mx-reset-label').each(function () { var $outer = $(this); var $target = $outer.children('span').first(); if (!$target.length) $target = $outer; if ($target.hasClass('mx-tl')) return; var original = $.trim($target.text()); if (!original) return; var translated = mxT(original); if (translated && translated !== original) { $target.text(translated); } }); // 11) Hersteller-Bar Prefix ("Werkzeuge für") $('.mx-manufacturer-bar-prefix').each(function () { var $el = $(this); if ($el.hasClass('mx-tl')) return; var original = $.trim($el.text()); if (!original) return; var translated = mxT(original); if (translated && translated !== original) { $el.text(translated); } }); // 12) Maschinenhersteller-Reset Button ("Maschinenhersteller ändern") $('#mx-reset-manufacturer, .mx-reset-manufacturer').each(function () { var $el = $(this); if ($el.hasClass('mx-tl')) return; var original = $.trim($el.text()); if (!original) return; var translated = mxT(original); if (translated && translated !== original) { $el.text(translated); } }); // 13) Artikel-ID Label ("ARTIKEL ID ") $('.mx-variant-id-label').each(function () { var $el = $(this); if ($el.hasClass('mx-tl')) return; var raw = $el.text(); var trimmed = $.trim(raw); if (!trimmed) return; var translated = mxT(trimmed); if (translated && translated !== trimmed) { // Leerzeichen am Ende beibehalten (z.B. "ARTIKEL ID " → "ARTICLE ID ") var trailingSpace = raw.slice(trimmed.length); $el.text(translated + trailingSpace); } }); mxLog('✓ DOM translation complete'); } /** * MutationObserver: Wenn .selected-choice Text dynamisch geändert wird, * den .mx-tl Geschwister-Span automatisch aktualisieren. */ if (!MX_IS_DE && typeof MutationObserver !== 'undefined') { (function () { var selChoiceObserver = new MutationObserver(function (mutations) { for (var i = 0; i < mutations.length; i++) { var target = mutations[i].target; if (!target || !target.classList) continue; var isSelectedChoice = target.classList.contains('selected-choice'); if (!isSelectedChoice && target.parentElement) { isSelectedChoice = target.parentElement.classList.contains('selected-choice'); if (isSelectedChoice) target = target.parentElement; } if (isSelectedChoice) { var $el = $(target); var original = $.trim($el.text()); var translated = original ? mxT(original) : ''; if (original && translated !== original) { var $tl = $el.next('.mx-tl'); if ($tl.length) { $tl.text(translated); } else { var cls = ($el.attr('class') || '').replace(/\bmx-has-tl\b/g, '').trim(); $('').text(translated).insertAfter($el); } $el.addClass('mx-has-tl'); } else { $el.next('.mx-tl').remove(); $el.removeClass('mx-has-tl'); // Sichtbarkeit wiederherstellen (war per :not(.mx-show) CSS versteckt) $el.addClass('mx-show'); } } } }); setTimeout(function () { var container = document.querySelector('ul.layers') || document.querySelector('.mkl_pc_container'); if (container) { selChoiceObserver.observe(container, { childList: true, subtree: true, characterData: true }); } }, 500); })(); } function mxLog() { if (!DBG || !window.console) return; try { var args = Array.prototype.slice.call(arguments); args.unshift('[MX-DBG]'); console.log.apply(console, args); } catch (e) { } } function mxWarn() { if (!window.console) return; try { var args = Array.prototype.slice.call(arguments); args.unshift('[MX-DBG]'); console.warn.apply(console, args); } catch (e) { } } function mxErr() { if (!window.console) return; try { var args = Array.prototype.slice.call(arguments); args.unshift('[MX-DBG]'); console.error.apply(console, args); } catch (e) { } } var MX_DBG_REQ_COUNT = 0; var MX_AVAIL_XHR = null; var MX_AVAIL_SEQ = 0; var MX_ALL_CHOICES_CACHE = null; var MX_AVAIL_RESP_CACHE = new Map(); var MX_AVAIL_CACHE_TTL_MS = 5 * 60 * 1000; var MX_AVAIL_CACHE_MAX = 500; function mxStableStringify(obj) { if (!obj || typeof obj !== 'object') return String(obj); if (Array.isArray(obj)) return '[' + obj.map(mxStableStringify).join(',') + ']'; var keys = Object.keys(obj).sort(); var parts = []; for (var i = 0; i < keys.length; i++) { var k = keys[i]; parts.push(JSON.stringify(k) + ':' + mxStableStringify(obj[k])); } return '{' + parts.join(',') + '}'; } function mxMakeAvailCacheKey(productId, manufacturer, selectedOptions, ignoreSelections, heroOnly) { return [ 'p:', productId, '|m:', (manufacturer || ''), '|i:', ignoreSelections ? '1' : '0', '|h:', heroOnly ? '1' : '0', '|c:', (window.MX_LAST_CLICKED_LAYER || ''), '|s:', mxStableStringify(selectedOptions || {}) ].join(''); } function mxCacheGet(key) { var e = MX_AVAIL_RESP_CACHE.get(key); if (!e) return null; if ((Date.now() - e.t) > MX_AVAIL_CACHE_TTL_MS) { MX_AVAIL_RESP_CACHE.delete(key); return null; } // touch (LRU) MX_AVAIL_RESP_CACHE.delete(key); MX_AVAIL_RESP_CACHE.set(key, e); return e.v; } function mxCacheSet(key, value) { MX_AVAIL_RESP_CACHE.set(key, { t: Date.now(), v: value }); while (MX_AVAIL_RESP_CACHE.size > MX_AVAIL_CACHE_MAX) { var firstKey = MX_AVAIL_RESP_CACHE.keys().next().value; MX_AVAIL_RESP_CACHE.delete(firstKey); } } function safeUpdatePopupPosition() { if (window.MX_Popup && typeof window.MX_Popup.updatePosition === 'function') { window.MX_Popup.updatePosition(); } } function moveResetButtonToViewer() { var viewer = document.querySelector('.mkl_pc_viewer'); var footer = document.querySelector('footer.mkl_pc_footer'); if (!viewer || !footer) return; var btn = footer.querySelector('button.reset-configuration'); if (!btn) return; if (getComputedStyle(viewer).position === 'static') { viewer.style.position = 'relative'; } if (btn.parentNode !== viewer) { viewer.appendChild(btn); } btn.classList.add('mx-reset-in-viewer'); if (!btn.querySelector('.mx-reset-label')) { var labelSpan = document.createElement('span'); labelSpan.className = 'mx-reset-label'; var nodesToMove = []; for (var i = 0; i < btn.childNodes.length; i++) { var n = btn.childNodes[i]; var isSvgIcon = n.nodeType === 1 && n.tagName && n.tagName.toLowerCase() === 'svg' && n.classList && n.classList.contains('mx-reset-icon'); if (!isSvgIcon) nodesToMove.push(n); } nodesToMove.forEach(function (n) { labelSpan.appendChild(n); }); btn.appendChild(labelSpan); } if (!btn.querySelector('svg.mx-reset-icon')) { btn.insertAdjacentHTML( 'afterbegin', '' + '' + '' ); } if (!btn.getAttribute('aria-label')) { btn.setAttribute('aria-label', mxT('Zurücksetzen')); } if (!btn.classList.contains('mx-reset-bound')) { btn.classList.add('mx-reset-bound'); btn.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); if (window.MX_INITIAL_AUTO_SELECTION && typeof window.MX_INITIAL_AUTO_SELECTION === 'object') { MX_HERO_LOCK_ACTIVE = false; HAS_INTERACTED = false; MX_USER_JUST_CLICKED = {}; window.MX_LAST_CLICKED_LAYER = ''; applyAutoSelection(window.MX_INITIAL_AUTO_SELECTION, { skipAjax: false }); } else { clearAllSelections(); sendAvailability(true, false); } }); } } function debounce(fn, wait) { var timeout; return function () { var ctx = this; var args = arguments; clearTimeout(timeout); timeout = setTimeout(function () { fn.apply(ctx, args); }, wait); }; } var MX_PENDING_VARIANT_IMAGE = null; // hold variant image until selection stabilizes var HAS_INTERACTED = false; var imageCache = {}; var LAST_KNOWN_PK = null; // MX: global exponieren damit functions.php darauf zugreifen kann Object.defineProperty(window, 'LAST_KNOWN_PK', { get: function() { return LAST_KNOWN_PK; }, configurable: true }); var MX_HERO_LOCK_ACTIVE = false; var MX_INITIAL_AUTO_SELECTION = null; var MX_LAST_WRITTEN_CONFIG = null; var MX_LAST_WRITTEN_CONFIG_SOURCE = ''; var MX_LAST_WRITTEN_CONFIG_AT = 0; function mxCloneConfig(config) { if (!Array.isArray(config)) return []; return config.map(function (item) { var cloned = {}; for (var k in item) { if (item.hasOwnProperty(k)) cloned[k] = item[k]; } return cloned; }); } function mxReadHiddenConfigField() { var $field = $('form.cart').find('input[name="pc_configurator_data"]').first(); if (!$field.length) return []; try { var parsed = JSON.parse($field.val() || '[]'); return Array.isArray(parsed) ? parsed : []; } catch (e) { return []; } } function mxRememberSerializedConfig(config, source) { if (!Array.isArray(config) || !config.length) return; MX_LAST_WRITTEN_CONFIG = mxCloneConfig(config); MX_LAST_WRITTEN_CONFIG_SOURCE = source || ''; MX_LAST_WRITTEN_CONFIG_AT = Date.now(); } var CACHED_SELECTED_OPTIONS = null; var CACHE_TIMESTAMP = 0; var PNG_MAP = { 'STANDARD': 'Standard.png', 'PROFESSIONAL': 'Professional.png', 'HERO': 'Hero.png', 'DIAMOND': 'Diamond.png', 'PLUS PROFESSIONAL': 'Professional+.png', 'HERO R2': 'HERO R2.png' }; var ASSET_BASE = '/wp-content/plugins/mx-pc-conditions/assets/images/'; var ASSET_DIR_BY_TYPE = { editionTile: 'editions', editionSummary: 'editions-material', evolutionStage: 'evolutionsstufe', besatzTile: 'Besatz' }; // Produktname → Unterordner für Besatz-Bilder var BESATZ_PRODUCT_FOLDERS = { 'spiralrundbürste': 'Spiralrundbürste', 'spiralrundbuerste': 'Spiralrundbürste', 'tellerbürste': 'Tellerbürste', 'tellerburste': 'Tellerbürste' }; // Besatz-Optionen mit zugehörigem Bild-Dateinamen // Schlüssel: Label-Text (uppercase), Wert: Dateiname ohne Ordner var BESATZ_IMAGE_MAP = { 'F': 'F.png', 'FS': 'FS.png', 'S': 'S.png', 'SMS': 'SMS.png', 'TF': 'TF.png', 'W': 'W.png' }; function getProductFolderKey() { var cats = window.MX_LAST_CATEGORIES; if (Array.isArray(cats) && cats.length) { var s = cats.join(' ').toLowerCase(); if (s.indexOf('entgratteller') !== -1) return 'Entgratteller'; if (s.indexOf('entgratwalze') !== -1) return 'Entgratwalze'; if (s.indexOf('fiberscheibe') !== -1) return 'Fiberscheibe'; if (s.indexOf('entgratbürstensegment') !== -1 || s.indexOf('entgratbuerstensegment') !== -1) return 'Entgratbürstensegment'; if (s.indexOf('entgratblock') !== -1) return 'Entgratblock'; if (s.indexOf('entgratbürstenpinsel') !== -1) return 'Entgratbürstenpinsel'; if (s.indexOf('lamellenschleifrad') !== -1) return 'Lamellenschleifrad'; if (s.indexOf('schleifbürstenteller') !== -1) return 'Schleifbürstenteller'; if (s.indexOf('schleifbürste') !== -1) return 'Schleifbürste'; if (s.indexOf('entgratbürstenteller') !== -1) return 'Entgratbürstenteller'; if (s.indexOf('schleifgewebebogen') !== -1) return 'Schleifgewebebogen'; if (s.indexOf('schleifgewebeband') !== -1) return 'Schleifgewebeband'; if (s.indexOf('schleifpapierband') !== -1) return 'Schleifpapierband'; if (s.indexOf('schleifstern') !== -1) return 'Schleifstern'; if (s.indexOf('schleifklettscheibe') !== -1) return 'Schleifklettscheibe'; if (s.indexOf('entgratsegment') !== -1) return 'Entgratsegment'; if (s.indexOf('schleifvliesteller') !== -1) return 'Schleifvliesteller'; if (s.indexOf('oxidbürste') !== -1 || s.indexOf('oxidbuerste') !== -1) return 'Oxidbürste'; if (s.indexOf('reinigungsvlies') !== -1) return 'Reinigungsvlies'; if (s.indexOf('kraftband') !== -1) return 'Kraftband'; } var title = (window.MX_PRODUCT_TITLE || '').toLowerCase(); if (title.indexOf('entgratteller') !== -1) return 'Entgratteller'; if (title.indexOf('entgratwalze') !== -1) return 'Entgratwalze'; if (title.indexOf('fiberscheibe') !== -1) return 'Fiberscheibe'; if (title.indexOf('entgratbürstensegment') !== -1 || title.indexOf('entgratbuerstensegment') !== -1) return 'Entgratbürstensegment'; if (title.indexOf('entgratblock') !== -1) return 'Entgratblock'; if (title.indexOf('entgratbürstenpinsel') !== -1) return 'Entgratbürstenpinsel'; if (title.indexOf('lamellenschleifrad') !== -1) return 'Lamellenschleifrad'; if (title.indexOf('schleifbürstenteller') !== -1) return 'Schleifbürstenteller'; if (title.indexOf('schleifbürste') !== -1) return 'Schleifbürste'; if (title.indexOf('entgratbürstenteller') !== -1) return 'Entgratbürstenteller'; if (title.indexOf('schleifgewebebogen') !== -1) return 'Schleifgewebebogen'; if (title.indexOf('schleifgewebeband') !== -1) return 'Schleifgewebeband'; if (title.indexOf('schleifpapierband') !== -1) return 'Schleifpapierband'; if (title.indexOf('schleifstern') !== -1) return 'Schleifstern'; if (title.indexOf('schleifklettscheibe') !== -1) return 'Schleifklettscheibe'; if (title.indexOf('entgratsegment') !== -1) return 'Entgratsegment'; if (title.indexOf('schleifvliesteller') !== -1) return 'Schleifvliesteller'; if (title.indexOf('oxidbürste') !== -1 || title.indexOf('oxidbuerste') !== -1) return 'Oxidbürste'; if (title.indexOf('reinigungsvlies') !== -1) return 'Reinigungsvlies'; if (title.indexOf('kraftband') !== -1) return 'Kraftband'; if (title.indexOf('spiralrundbürste') !== -1 || title.indexOf('spiralrundbuerste') !== -1) return 'Spiralrundbürste'; if (title.indexOf('tellerbürste') !== -1 || title.indexOf('tellerburste') !== -1) return 'Tellerbürste'; return ''; } function getBesatzBasePath() { var title = (window.MX_PRODUCT_TITLE || '').toLowerCase(); var folder = null; for (var key in BESATZ_PRODUCT_FOLDERS) { if (BESATZ_PRODUCT_FOLDERS.hasOwnProperty(key) && title.indexOf(key) !== -1) { folder = BESATZ_PRODUCT_FOLDERS[key]; break; } } if (!folder) return null; // kein bekanntes Produkt → kein Tile-Modus return ASSET_BASE + folder + '/'; } function getAssetBasePath(type) { var dir = ASSET_DIR_BY_TYPE[type] || ''; if (!dir) return ASSET_BASE; var folder = getProductFolderKey(); return ASSET_BASE + dir + '/' + (folder ? (folder + '/') : ''); } var EDITION_PREFIX_BY_EVO = { 'STANDARD': 'S-', 'PROFESSIONAL': 'P-', 'HERO': 'H-', 'PLUS PROFESSIONAL': 'PP-', 'HERO R2': 'HR2-' }; var EDITION_FILE_PART = { 'BLUE': 'Blue', 'BROWN': 'Brown', 'DARK BROWN': 'Dark-Brown', 'DARK-BROWN': 'Dark-Brown', 'GOLD': 'Gold', 'GREEN': 'Green', 'GREY': 'Grey', 'GRAY': 'Grey', 'PURPLE': 'Purple', 'RED': 'Red', 'SYNTHETIC': 'Synthetic', 'CUBITRON II': 'Cubi', '987C CUBITRON II': '987C CUBITRON II', '982C CUBITRON II': '982C CUBITRON II', 'ACTIROX AF799': 'ACTIROX AF799', 'ACTIROX AF890': 'ACTIROX AF890', 'VLIESBAND': 'Vliesband', 'VLIESBOGEN': 'Vliesbogen', 'KRAFTBAND': 'Kraftband', 'KRAFTBAND BG': 'Kraftband BG' }; var EDITION_NO_EVO = { '987C CUBITRON II': true, '982C CUBITRON II': true, 'ACTIROX AF799': true, 'ACTIROX AF890': true, 'VLIESBAND': true, 'VLIESBOGEN': true, 'KRAFTBAND': true, 'KRAFTBAND BG': true }; function ensureEditionDisabledStyles() { if (document.getElementById('mx-edition-disabled-style')) return; var css = ".mkl_pc_viewer.mx-variant-loading { cursor: progress; } " + "ul.layers > li.layers-list-item.mx-edition-layer li.choice.mx-disabled, " + "ul.layers > li.layers-list-item.mx-edition-layer li.choice.not-available { " + "opacity: .35; filter: grayscale(1); pointer-events: auto !important; cursor: not-allowed !important; " + "} " + "ul.layers > li.layers-list-item.mx-edition-layer li.choice.mx-disabled button.choice-item, " + "ul.layers > li.layers-list-item.mx-edition-layer li.choice.not-available button.choice-item { " + "pointer-events: none !important; cursor: not-allowed !important; " + "} "; var style = document.createElement('style'); style.id = 'mx-edition-disabled-style'; style.type = 'text/css'; style.appendChild(document.createTextNode(css)); document.head.appendChild(style); } var AUFNAHME_CATEGORY_PATTERNS = { 'BOHRUNG': [/^BOHRUNG/i, /^(?:◯|Ø)?\s*\d+(?:,\d+)?\s*MM/i], 'GEWINDE': [/^GEWINDE/i, /^M\d+/i], 'SCHNELLSPANN': [/SCHNELLSPANN/i, /^COSTA/i, /^QUICK/i], 'SCHAFT': [/^SCHAFT/i], 'AUFNAHMEBOLZEN': [/^AUFNAHMEBOLZEN/i] }; var AUFNAHME_CATEGORY_ORDER = ['BOHRUNG', 'GEWINDE', 'SCHNELLSPANN', 'SCHAFT', 'AUFNAHMEBOLZEN']; var MANUFACTURER_SELECT = '#form-field-herstellerAuswahl,' + '#herstellerAuswahl,' + 'select#hersteller,' + 'select[id*="herstellerAuswahl"],' + 'select[name*="herstellerAuswahl"],' + 'select[id*="hersteller"],' + 'select[name*="hersteller"]'; var HAS_INITIAL_CONFIG = !!( window.MX_INITIAL_CONFIG && typeof window.MX_INITIAL_CONFIG === 'object' && Object.keys(window.MX_INITIAL_CONFIG).length > 0 ); window.MX_HAS_INITIAL_CONFIG = false; var URL_HAS_LOAD_CONFIG_FROM_CART = window.location.search.indexOf('load_config_from_cart=') !== -1; var URL_HAS_LOAD_CONFIG_FROM_ORDER = window.location.search.indexOf('load_config_from_order=') !== -1; if (URL_HAS_LOAD_CONFIG_FROM_CART || URL_HAS_LOAD_CONFIG_FROM_ORDER) { HAS_INITIAL_CONFIG = false; } var MX_APPLYING_FALLBACK = false; var MX_AVAILABILITY_REENTRY_GUARD = false; var MX_SUPPRESS_AVAIL = false; var MX_AVAIL_DIRTY = false; var MX_RECALC_TIMER = null; function mxBeginBatchAvailability() { MX_SUPPRESS_AVAIL = true; MX_AVAIL_DIRTY = false; mxLog('Begin batch availability (suppress requests)'); } function mxEndBatchAvailability(cb) { MX_SUPPRESS_AVAIL = false; mxLog('End batch availability', { dirty: MX_AVAIL_DIRTY }); if (MX_AVAIL_DIRTY) { MX_AVAIL_DIRTY = false; mxLog('Batch dirty -> firing one availability request'); sendAvailability(false, false, cb); } else { if (typeof cb === 'function') cb(); } } function mxScheduleRecalc() { if (MX_SUPPRESS_AVAIL) { MX_AVAIL_DIRTY = true; mxLog('Recalc requested while suppressed -> marked dirty'); return; } if (MX_RECALC_TIMER) return; MX_RECALC_TIMER = setTimeout(function () { MX_RECALC_TIMER = null; if (MX_AVAIL_DIRTY) { MX_AVAIL_DIRTY = false; mxLog('Scheduled recalc fired -> sending availability'); sendAvailability(false, false); } }, 120); } function txt($el) { // FIX-1: Nach Translation enthält das Element BEIDE Texte (DE original + EN .mx-tl span). // .text() würde z.B. "Zweireihig Double Row" zurückgeben → Match schlägt fehl. // Lösung: .mx-tl bevorzugen, sonst Original ohne .mx-tl Kinder. var $tl = $el.find('.mx-tl').first(); if ($tl.length) { var tlText = $.trim(($tl.text() || '').replace(/\s+/g, ' ')); if (window.MX_DBG_TXT) console.log('[MX-TXT] using .mx-tl:', tlText, '| from:', $el[0]); return tlText; } var $clone = $el.clone(); $clone.find('.mx-tl').remove(); var origText = $.trim(($clone.text() || '').replace(/\s+/g, ' ')); if (window.MX_DBG_TXT) console.log('[MX-TXT] using original:', origText, '| from:', $el[0]); return origText; } function objSize(o) { return (o && typeof o === 'object') ? Object.keys(o).length : 0; } function parseChoiceId(id) { var m = String(id || '').match(/^choice_(\d+)_(.+)$/); return m ? { layerId: String(m[1]), choiceId: String(m[2]) } : null; } // --- Guided Mode: First layer always shows all possible values (unfiltered) --- function mxGetFirstLayerId() { // first visible layer in configurator list var $first = $('ul.layers > li.layers-list-item:visible').first(); if (!$first.length) return null; var layerId = String($first.attr('data-layer') || ''); if (!layerId) { var $ul = $first.find('.layer_choices .choices-list ul[data-layer-id]').first(); if ($ul.length) layerId = String($ul.attr('data-layer-id') || ''); } return layerId || null; } function isChoiceEnabled($choice) { if (!$choice || !$choice.length) return false; var $btn = $choice.find('button.choice-item').first(); if (!$btn.length) return false; var disabledByClass = $choice.hasClass('mx-disabled') || $choice.hasClass('not-available'); var disabledByAttr = !!$btn.prop('disabled') || $btn.attr('aria-disabled') === 'true'; return !disabledByClass && !disabledByAttr; } function setChoiceDisabled($choice, disabled) { if (!$choice || !$choice.length) return; var $btn = $choice.find('button.choice-item').first(); if (!$btn.length) return; // ✅ FIX: Dirty-check — nur DOM ändern wenn nötig var isCurrentlyDisabled = $choice.hasClass('mx-disabled'); if (disabled && isCurrentlyDisabled) return; if (!disabled && !isCurrentlyDisabled) return; if (disabled) { $choice.addClass('mx-disabled not-available'); $btn .prop('disabled', true) .attr('aria-disabled', 'true') .attr('tabindex', '-1'); } else { $choice.removeClass('mx-disabled not-available'); $btn .prop('disabled', false) .removeAttr('disabled') .removeAttr('aria-disabled') .removeAttr('tabindex'); } } function getActiveChoice($layer) { var $active = $layer.find( '.layer_choices li.choice.active, ' + '.layer_choices li.choice.selected, ' + '.layer_choices li.choice.mxactive' ).first(); if (!$active.length) { $active = $layer.find('.layer_choices li.choice button.choice-item[aria-pressed="true"]').closest('li.choice').first(); } return $active; } function setCookie(name, value, days) { var expires = ''; if (days) { var d = new Date(); d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000)); expires = '; expires=' + d.toUTCString(); } document.cookie = name + '=' + encodeURIComponent(value || '') + expires + '; path=/'; } function getCookie(name) { var m = document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)'); return m ? decodeURIComponent(m.pop()) : ''; } function normalizeManufacturerValue(raw) { raw = decodeURIComponent((raw || '') + '').trim(); if (!raw) return ''; var target = raw.toLowerCase(); var found = ''; // 1. Versuch: gegen geladene -Werte im Select matchen (case-insensitiv) $(MANUFACTURER_SELECT).find('option').each(function () { var val = ($(this).attr('value') || $(this).val() || '').trim(); if (val && val.toLowerCase() === target) { found = val; return false; } }); if (found) return found; // 2. Versuch: gegen die zuletzt geladene Hersteller-Liste matchen (case-insensitiv) // Dadurch funktioniert z.B. "gecam" → "GECAM" auch bevor der Select gefüllt ist if (window.MX_MANUFACTURERS_LIST && Array.isArray(window.MX_MANUFACTURERS_LIST)) { for (var i = 0; i < window.MX_MANUFACTURERS_LIST.length; i++) { var m = window.MX_MANUFACTURERS_LIST[i]; if (m && m.toLowerCase() === target) { return m; // exakter Wert aus der API (z.B. "GECAM") } } } // 3. Fallback: Originalschreibweise beibehalten (kein Title-Case verfälschen) return raw; } function getManufacturerFromUrl() { try { var params = new URLSearchParams(window.location.search || ''); var manu = params.get('mx_manufacturer') || params.get('rmx_manufacturer') || params.get('manufacturer') || ''; return normalizeManufacturerValue(manu); } catch (e) { return ''; } } function updateManufacturerLabel() { var manufacturer = normalizeManufacturerValue(getCookie('mx_selected_manufacturer') || ''); var $label = $('#mx-selected-manufacturer-label'); if (!$label.length) return; if (!manufacturer) { $label.text(''); $('#mx-manufacturer-bar').hide(); return; } $('#mx-manufacturer-bar').show(); $label.text( manufacturer === '__NONE__' ? mxT('Kein Hersteller') : manufacturer === '__ALL__' ? mxT('Alle Hersteller') : manufacturer ); } (function syncManufacturerFromUrl() { if (!window.location.search) return; try { var manu = getManufacturerFromUrl(); if (!manu) return; setCookie('mx_selected_manufacturer', manu, 365); } catch (e) { } })(); // Returns current manufacturer (URL param if present, otherwise cookie; defaults to '__ALL__') function mxGetCurrentManufacturer() { try { var manu = getManufacturerFromUrl(); if (manu) return manu; } catch (e) { } try { var c = getCookie('mx_selected_manufacturer'); if (c) return normalizeManufacturerValue(c); } catch (e2) { } return '__ALL__'; } function updateUrlManufacturerParam(newVal) { try { var url = new URL(window.location.href); url.searchParams.delete('load_config_from_cart'); url.searchParams.delete('load_config_from_order'); var normalized = normalizeManufacturerValue(newVal); if (normalized && normalized !== '') { url.searchParams.set('mx_manufacturer', normalized); } else { url.searchParams.delete('mx_manufacturer'); } window.history.replaceState(null, '', url.toString()); } catch (e) { } } function markConfiguratorReady() { var container = document.querySelector('.mkl_pc_container'); if (container) { container.classList.remove('mx-pc-loading'); container.classList.add('mx-pc-ready'); } // Lade-Animation ausblenden var loader = document.getElementById('mx-konfig-loader'); if (loader) { loader.classList.add('mx-loader-hidden'); setTimeout(function () { if (loader.parentNode) loader.parentNode.removeChild(loader); }, 600); } // Always recount visible tiles after DOM settles setTimeout(mxSetTileGridColumns, 150); setTimeout(mxSetTileGridColumns, 500); // ─── DEBUG: Abschlussbericht ─── if(DBG) console.log('%c✅ Konfigurator READY [+' + MX_PERF.elapsed() + 'ms]', 'color:#2ecc71;font-size:14px;font-weight:bold'); setTimeout(function () { MX_PERF.summary(); }, 100); } function markConfiguratorLoading() { var container = document.querySelector('.mkl_pc_container'); if (container) { container.classList.remove('mx-pc-ready'); } } window.MX_PC_markReady = markConfiguratorReady; window.MX_PC_onAjaxComplete = function () { markConfiguratorReady(); setTimeout(mxSetTileGridColumns, 50); setTimeout(mxTranslateDom, 100); }; function getProductId() { if (window.mxPcConditionsAjax && window.mxPcConditionsAjax.product_id) { var pid = parseInt(window.mxPcConditionsAjax.product_id, 10); if (!isNaN(pid) && pid > 0) return pid; } return 0; } function preloadImage(url, immediate) { if (!url || imageCache[url]) return; MX_PERF.img(url); var img = new Image(); img.src = url; imageCache[url] = img; } /* Lazy preload: only on hover (for edition tiles etc.) */ function preloadImageLazy(url) { if (!url || imageCache[url]) return; // Don't load immediately — will be triggered by hover or visibility if (!window.MX_LAZY_PRELOAD_QUEUE) window.MX_LAZY_PRELOAD_QUEUE = []; window.MX_LAZY_PRELOAD_QUEUE.push(url); } function setImageInstant(url, pk) { var $viewer = jQuery('.mkl_pc_container .mkl_pc_viewer').first(); if (!$viewer.length || !url) return; var $varImg = $viewer.find('#mx_variant_image'); $viewer.addClass('mx-has-variant'); $viewer.find('.mkl_pc_layers img.simple').hide(); if (!$varImg.length) { $varImg = jQuery('', { id: 'mx_variant_image', class: 'mx-variant-image', alt: '' }).appendTo($viewer); } var currentSrc = $varImg.attr('src') || ''; if (currentSrc === url) { $varImg.css('opacity', 1).show(); } else if (imageCache[url] && imageCache[url].complete) { $varImg.off('load').off('error'); $varImg.css('opacity', 1).attr('src', url).show(); } else { // Preload -> swap-on-load $viewer.addClass('mx-variant-loading'); var pre = new Image(); pre.onload = function () { imageCache[url] = pre; $varImg.off('load').off('error'); $varImg.css({ opacity: 0, transition: 'opacity 0.15s ease' }).attr('src', url).show(); // next tick to allow transition setTimeout(function () { $varImg.css('opacity', 1); }, 0); $viewer.removeClass('mx-variant-loading'); }; pre.onerror = function () { $viewer.removeClass('mx-variant-loading'); }; pre.src = url; } if (pk) { LAST_KNOWN_PK = pk; } updateVariantHeader(pk); preloadImage(url); } function updateVariantImage(info) { if (!info) return; var url = (typeof info === 'string') ? info : info.url; var pk = (typeof info === 'string') ? null : (info.pk || null); if (!url) return; setImageInstant(url, pk); var $wcImg = jQuery('.woocommerce-product-gallery__wrapper .woocommerce-product-gallery__image img').first(); if (!$wcImg.length) $wcImg = jQuery('.product .images img').first(); if ($wcImg.length) { $wcImg .attr('src', url) .attr('data-src', url) .attr('data-large_image', url) .attr('srcset', '') .removeAttr('data-lazy-src') .removeAttr('data-lazy-original') .removeClass('lazyloaded lazyload') .css({ opacity: 1, visibility: 'visible' }); } } /** * Zeigt einen Lade-Skeleton in den technischen Details. * Verhindert dass unvollständige statische Felder kurz sichtbar werden. */ function mxShowTechSkeleton() { var $techPanel = jQuery('.mx-desc-panel[data-panel="tech"]'); if (!$techPanel.length) return; var $specsContainer = $techPanel.find('.mx-tech-specs'); if (!$specsContainer.length) { $techPanel.empty(); var $wrapper = jQuery(''); $specsContainer = jQuery(''); $wrapper.append($specsContainer); $techPanel.append($wrapper); } $specsContainer.html( '' + '' + '' + '' + '' ); } /** * Update technical details panel with new data from AJAX response */ function updateTechnicalDetails(techData) { if (!techData) { mxLog('No technical details in response'); return; } mxLog('Updating technical details:', techData); var $techPanel = jQuery('.mx-desc-panel[data-panel="tech"]'); if (!$techPanel.length) { mxWarn('Technical details panel not found'); return; } var $specsContainer = $techPanel.find('.mx-tech-specs'); if (!$specsContainer.length) { mxLog('Tech specs container not found - creating it'); $techPanel.empty(); var $wrapper = jQuery(''); $specsContainer = jQuery(''); $wrapper.append($specsContainer); $techPanel.append($wrapper); } var MX_EXTRA_FIELDS_BY_TYPE = { 'Entgratbürstenteller': [{ label: 'Reihen', key: 'Reihen' }, { label: 'Bündeldurchmesser', key: 'Bündeldurchmesser' }], 'Entgratbürstensegment': [{ label: 'Besatzhöhe', key: 'Besatzhöhe' }], 'Oxidbürste': [{ label: 'Reihen', key: 'Reihen' }, { label: 'Besatz', key: 'Besatz' }], 'Entgratbürstenpinsel': [{ label: 'Besatzhöhe', key: 'Besatzhöhe' }, { label: 'Bündeldurchmesser', key: 'Bündeldurchmesser' }], }; var productType = getProductFolderKey(); var specs = { 'Aufnahme': techData.Aufnahme || null, 'Länge': techData.Länge || null, 'Gewinde': techData.Gewinde || null, 'Pin': techData.Pin || null, 'Breite': techData.Breite || null, 'Höhe': techData.Höhe || null, 'Bohrung': techData.Bohrung || null, 'Besatzausführung': techData.Besatzausführung || null, 'Schleifgewebe': techData.Schleifgewebe || null, 'Schlitzung': techData.Schlitzung || null, 'Besatzversion': techData.Besatzversion || null, 'Wuchtgüte': techData.Wuchtgüte || null, 'Nassbearbeitung': techData.Nassbearbeitung || null, 'Drehzahl (max.)': techData.Drehzahl_max || null, 'Filzqualität': techData.Filzqualität || null, 'A-Schleifgewebe': techData['A-Schleifgewebe'] || null, 'Beborstung': techData.Beborstung || null, 'Fasermaterial': techData.Fasermaterial || null, 'Anzahl Bohrungen': techData['Anzahl Bohrungen'] || null, 'Ø-Bohrungen': techData['Ø-Bohrungen'] || null, 'Passend für': techData['Passend für'] || null, 'Gewicht': techData.Gewicht || null, 'Schleifkorn': techData.Schleifkorn || null, 'Trägermaterial': techData.Trägermaterial || null, 'Bündeldurchmesser': techData.Bündeldurchmesser || null, 'Bemerkung': techData.Bemerkung || null, 'Zolltarifnummer': techData.Zolltarifnummer || null, 'Ursprungsland': techData.Ursprungsland || null, 'Schaftlänge': techData.Schaftlänge || null, 'Gesamtlänge': techData.Gesamtlänge || null }; // ── Oxidwalze: Länge ausblenden, Durchmesser ganz oben anzeigen ── var _title = (window.MX_PRODUCT_TITLE || '').toLowerCase(); if (_title.indexOf('oxidwalze') !== -1) { delete specs['Länge']; if (techData.Durchmesser) { var oxidSpecs = { 'Durchmesser': techData.Durchmesser }; for (var k in specs) { if (specs.hasOwnProperty(k)) oxidSpecs[k] = specs[k]; } specs = oxidSpecs; } } var extraFields = MX_EXTRA_FIELDS_BY_TYPE[productType] || []; for (var ef = 0; ef < extraFields.length; ef++) { specs[extraFields[ef].label] = techData[extraFields[ef].key] || null; } var html = ''; var hasSpecs = false; // Einheiten-Mapping für technische Details var SPEC_UNITS = { 'Länge': 'mm', 'Durchmesser': 'mm', 'Breite': 'mm', 'Bohrung': 'mm', 'Bündeldurchmesser': 'mm', 'Schaftlänge': 'mm', 'Gesamtlänge': 'mm', 'Gesamthöhe': 'mm', 'Außendurchmesser': 'mm', 'Schaftdurchmesser': 'mm', 'Körperdurchmesser': 'mm', 'Ø-Bohrungen': 'mm', 'Drehzahl (max.)': '/min' }; for (var label in specs) { if (!specs.hasOwnProperty(label)) continue; var value = specs[label]; if (value !== null && value !== '') { hasSpecs = true; var displayValue = String(value); var unit = SPEC_UNITS[label] || ''; // Einheit anhängen falls noch nicht enthalten if (unit && displayValue.toLowerCase().indexOf(unit.toLowerCase()) === -1) { displayValue = displayValue + unit; } // ✅ MX Translation: Label + Value übersetzen var displayLabel = mxT(label); if (!/^\d/.test(displayValue)) { displayValue = mxT(displayValue); } html += ''; html += '' + escapeHtml(displayLabel) + ''; html += '' + escapeHtml(displayValue) + ''; html += ''; } } if (!hasSpecs) { html = '' + mxT('Keine technischen Details verfügbar.') + ''; } $specsContainer.html(html); updateBlechVisuals(techData); mxLog('✓ Technical details updated successfully'); } function updateBlechVisuals(techData) { var $blechContainer = jQuery('.mx-blech-container'); if (!$blechContainer.length) { mxLog('Blech container not found, skipping visual update'); return; } var positions = ['Eins', 'Zwei', 'Drei', 'Vier']; // ── Helper: apply opacity logic to one data-type group ────────────── function applyTypeVisuals(dataType, keyFn, customPositions) { var pos = customPositions || positions; var hasAny = false; pos.forEach(function (p) { var v = techData[keyFn(p)] || ''; if (v && v !== 'null') hasAny = true; }); // Find the section that owns this data-type var $section = $blechContainer.find('.mx-blech-box[data-type="' + dataType + '"]').first().closest('.mx-blech-section'); if (!hasAny) { if ($section.length && $section.is(':visible')) $section.hide(); return false; } if ($section.length && !$section.is(':visible')) $section.show(); pos.forEach(function (p, idx) { var i = idx + 1; var val = techData[keyFn(p)] || ''; var $imgs = $blechContainer.find('.mx-blech-box[data-type="' + dataType + '"] img[data-position="' + i + '"]'); $imgs.each(function () { var $img = jQuery(this); var $box = $img.closest('.mx-blech-box'); var isGreen = $box.hasClass('green'); var isOrange = $box.hasClass('orange'); var isRed = $box.hasClass('red'); $img.attr('data-value', val); if (!val || val === '' || val === 'null') { $img.css({ display: 'none' }); } else if (val === '0') { // Nicht geeignet → Red voll, Rest 0.2 if (isRed) $img.css({ opacity: '1', display: 'block' }); else if (isGreen || isOrange) $img.css({ opacity: '0.2', display: 'block' }); else $img.css({ display: 'none' }); } else if (val === '1') { // Geeignet → Orange voll, Rest 0.2 if (isOrange) $img.css({ opacity: '1', display: 'block' }); else if (isGreen || isRed) $img.css({ opacity: '0.2', display: 'block' }); else $img.css({ display: 'none' }); } else if (val === '2') { // Perfekt geeignet → Green voll, Rest 0.2 if (isGreen) $img.css({ opacity: '1', display: 'block' }); else if (isOrange || isRed) $img.css({ opacity: '0.2', display: 'block' }); else $img.css({ display: 'none' }); } else { $img.css({ display: 'none' }); } }); }); return hasAny; } // ──────────────────────────────────────────────────────────────────── var hasKontur = applyTypeVisuals('kontur', function (p) { return p + '_Kontur'; }); var hasStaerke = applyTypeVisuals('staerke', function (p) { return p + '_Stärke'; }); var hasReflektion = applyTypeVisuals('reflektion', function (p) { return p + '_Reflektion'; }); var hasAbtrag = applyTypeVisuals('abtrag', function (p) { return p + '_Abtrag'; }); // Grat hat nur 3 Positionen (kein Vier_Grat) var hasGrat = applyTypeVisuals('grat', function (p) { return p + '_Grat'; }, ['Eins', 'Zwei', 'Drei']); var hasAnyData = hasKontur || hasStaerke || hasReflektion || hasAbtrag || hasGrat; if (!hasAnyData) { if ($blechContainer.is(':visible')) { $blechContainer.hide(); mxLog('✓ Blech container hidden (no data)'); } } else { if (!$blechContainer.is(':visible')) { $blechContainer.show(); } } mxLog('✓ Blech visuals updated', { Kontur: hasKontur, Stärke: hasStaerke, Reflektion: hasReflektion, Abtrag: hasAbtrag, Grat: hasGrat }); } function escapeHtml(text) { if (text === null || text === undefined) return ''; var map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; return String(text).replace(/[&<>"']/g, function (m) { return map[m]; }); } function formatArticleId(pk) { if (!pk) return ''; return String(pk); } function updateVariantHeader(pk) { var $container = jQuery('.mkl_pc_container'); if (!$container.length) return; var $viewer = $container.find('.mkl_pc_viewer').first(); var $header = $container.find('.mx-variant-header'); if (!$header.length) { $header = jQuery( '' + '' + '' + 'ARTIKEL ID ' + '' + '' + '' ); if ($viewer.length) { $viewer.append($header); } else { $container.prepend($header); } } var title = window.MX_PRODUCT_TITLE || jQuery('h1.product_title').text() || ''; $header.find('.mx-variant-title').text(mxT(title || '')); var idText = pk ? formatArticleId(pk) : ''; $header.find('.mx-variant-id').text(idText); // Hersteller-Tab automatisch neu laden wenn Variante sich ändert if (pk) { var $mfTab = jQuery('.mx-desc-panel[data-panel="manufacturers"]'); if ($mfTab.length) { // Nur neu laden wenn sich der PK geändert hat var lastPk = $mfTab.data('mx-loaded-pk'); if (lastPk !== pk) { $mfTab.data('mx-loaded-pk', pk); $mfTab.data('mx-mfr-loaded', false); // Reset → nächster Tab-Klick lädt neu // Falls Tab gerade aktiv ist → sofort neu laden if ($mfTab.hasClass('active')) { mxReloadManufacturers(pk); } } } } } window.mxReloadManufacturers = mxReloadManufacturers; function mxReloadManufacturers(pk) { var $panel = jQuery('.mx-desc-panel[data-panel="manufacturers"]'); if (!$panel.length) return; var $loading = $panel.find('.mx-manufacturers-loading'); var $list = $panel.find('.mx-manufacturers-list'); // Nur beim allerersten Laden den Spinner zeigen, danach still aktualisieren var isFirstLoad = !$panel.data('mx-mfr-loaded'); if (isFirstLoad) { $loading.show(); $list.hide(); } jQuery.post( (window.mxPcConditionsAjax && mxPcConditionsAjax.ajax_url) || '/wp-admin/admin-ajax.php', { action: 'mx_pc_get_compatible_manufacturers', nonce: (window.mxPcConditionsAjax && mxPcConditionsAjax.nonce) || '', product_id: pk }, function (response) { $loading.hide(); if (response && response.success && response.data && response.data.manufacturers && response.data.manufacturers.length) { var html = ''; response.data.manufacturers.forEach(function (m) { // Immer UPPERCASE anzeigen var safe = jQuery('').text(String(m).toUpperCase()).html(); html += '' + safe + ''; }); $list.html(html).show(); } else { $list.html('Keine Herstellerinformationen verfügbar.').show(); } $panel.data('mx-mfr-loaded', true); } ); } var MX_DESC_ORIGINAL_PARENT = null; var MX_DESC_ORIGINAL_NEXT = null; var MX_DESC_MQ = window.matchMedia('(min-width: 661px)'); function moveDescriptionBelowImage() { var desc = document.querySelector('.mx-wc-description'); var img = document.querySelector('#mx_variant_image'); var viewer = img && img.closest('.mkl_pc_viewer'); if (!desc || !viewer) return; if (!MX_DESC_ORIGINAL_PARENT) { MX_DESC_ORIGINAL_PARENT = desc.parentNode; MX_DESC_ORIGINAL_NEXT = desc.nextSibling; } if (MX_DESC_MQ.matches) { if (!viewer.contains(desc)) { viewer.appendChild(desc); } viewer.classList.add('mx-desc-below'); } else { viewer.classList.remove('mx-desc-below'); if (MX_DESC_ORIGINAL_PARENT && desc.parentNode !== MX_DESC_ORIGINAL_PARENT) { if (MX_DESC_ORIGINAL_NEXT && MX_DESC_ORIGINAL_PARENT.contains(MX_DESC_ORIGINAL_NEXT)) { MX_DESC_ORIGINAL_PARENT.insertBefore(desc, MX_DESC_ORIGINAL_NEXT); } else { MX_DESC_ORIGINAL_PARENT.appendChild(desc); } } } } function updateVariantImageInstant($btn) { if (!$btn || !$btn.length) return; var url = $btn.data('variant-image'); var pk = $btn.data('variant-pk'); if (url) { if (imageCache[url] && imageCache[url].complete) { setImageInstant(url, pk); } else { preloadImage(url); } } else if (pk) { updateVariantHeader(pk); } } function initEvolutionsstufeDesign() { var found = false; $('ul.layers > li.layers-list-item').each(function () { var $layer = $(this); // In restore mode: do not auto-fill layers that were NOT part of the restored config. if (!HAS_INTERACTED && window.MX_RESTORE_MODE && Array.isArray(window.MX_RESTORE_KEYS)) { var layerName = $.trim($layer.find('> .layer-item .layer-name').first().text()); if (layerName && window.MX_RESTORE_KEYS.indexOf(layerName) === -1) { return; // keep empty (e.g., Länge = NULL for Klett) } } var layerNameText = $.trim($layer.find('> .layer-item .layer-name').first().text()); if (layerNameText === 'Evolutionsstufe') { found = true; if (!$layer.hasClass('mx-evolutionsstufe-layer')) { $layer.addClass('mx-evolutionsstufe-layer'); } var basePath = getAssetBasePath('evolutionStage'); $layer.find('.layer_choices li.choice').each(function () { var $choice = $(this); var $btn = $choice.find('button.choice-item').first(); var choiceText = $.trim($choice.find('.choice-name').first().text()).toUpperCase(); if (!choiceText) return; $choice.attr('data-choice-name', choiceText); if ($btn.find('.mx-evo-image').length > 0) return; var pngFile = PNG_MAP[choiceText]; if (pngFile) { var $img = $('', { 'class': 'mx-evo-image', 'src': basePath + pngFile, 'alt': choiceText, 'loading': 'eager' }); $btn.prepend($img); } }); $layer.addClass('mx-no-swiper'); } }); return found; } function hasEvolutionLayer() { return $('ul.layers > li.layers-list-item').filter(function () { return $.trim($(this).find('> .layer-item .layer-name').first().text()) === 'Evolutionsstufe'; }).length > 0; } function getCurrentEvolutionKey() { if (!hasEvolutionLayer()) return 'STANDARD'; var $evoLayer = $('ul.layers > li.layers-list-item.mx-evolutionsstufe-layer'); if (!$evoLayer.length) return 'STANDARD'; var $active = $evoLayer.find('.layer_choices li.choice.active, .layer_choices li.choice.selected, .layer_choices li.choice.mxactive').first(); if (!$active.length) $active = $evoLayer.find('.layer_choices li.choice').first(); var evoKey = ($active.attr('data-choice-name') || '').toUpperCase(); if (!evoKey || !EDITION_PREFIX_BY_EVO[evoKey]) evoKey = 'STANDARD'; return evoKey; } function getEditionFileName(evoKey, editionKey) { var filePart = EDITION_FILE_PART[editionKey]; if (!filePart) return null; var folderKey = getProductFolderKey(); var needsEnImages = false; // Bilder sprachunabhaengig var _edSuffix = (!MX_IS_DE && needsEnImages) ? '_en' : ''; if (EDITION_NO_EVO[editionKey]) { return filePart + _edSuffix + '.png'; } if (!evoKey) { return filePart + '.png'; } var prefix = EDITION_PREFIX_BY_EVO[evoKey] || 'S-'; if (evoKey === 'HERO' || evoKey === 'HERO R2') { var missingInHero = ['GREEN', 'RED', 'SYNTHETIC', 'DARK BROWN', 'DARK-BROWN', 'GOLD']; if (missingInHero.indexOf(editionKey) !== -1) { return null; } } return prefix + filePart + '.png'; } function updateEditionImages($layer) { ensureEditionDisabledStyles(); var hasEvo = hasEvolutionLayer(); var evoKey = hasEvo ? getCurrentEvolutionKey() : ''; $layer.find('.layer_choices li.choice').each(function () { var $choice = $(this); var $btn = $choice.find('button.choice-item').first(); var editionKey = $.trim($choice.find('.choice-name').first().text()).toUpperCase(); if (!editionKey) return; if (hasEvo) { if (editionKey === 'CUBITRON II' && (evoKey !== 'HERO' && evoKey !== 'HERO R2')) { setChoiceDisabled($choice, true); } if (EDITION_NO_EVO[editionKey] && (evoKey !== 'HERO' && evoKey !== 'HERO R2')) { setChoiceDisabled($choice, true); } } var fileName = getEditionFileName(evoKey, editionKey); if (!fileName) { setChoiceDisabled($choice, true); $choice.addClass('mx-disabled-by-image'); return; } else { if ($choice.hasClass('mx-disabled-by-image')) { $choice.removeClass('mx-disabled-by-image'); setChoiceDisabled($choice, false); } } var $img = $btn.find('img.mx-edition-image'); if (!$img.length) { $img = $('', { 'class': 'mx-edition-image', 'alt': editionKey, 'loading': 'eager' }); $btn.prepend($img); } var basePath = getAssetBasePath('editionTile'); var primaryUrl = basePath + fileName; // ✅ FIX: Fallback NUR für S- (Standard) erlauben. // P- (Professional) und H- (Hero) haben eigene Bilder – // bei fehlendem File KEIN Fallback auf alte generische Bilder. var unprefixedFileName = fileName.replace(/^S-/, ''); var fallbackUrl = (unprefixedFileName !== fileName) ? (basePath + unprefixedFileName) : null; $img.off('error.mxEditionFallback'); if (fallbackUrl) { $img.on('error.mxEditionFallback', function () { $(this).off('error.mxEditionFallback'); $(this).attr('src', fallbackUrl); }); } // ✅ FIX: Nur src setzen wenn es sich tatsächlich ändert (verhindert Bild-Flackern) if ($img.attr('src') !== primaryUrl) { $img.attr('src', primaryUrl); } }); updateEditionSummary($layer); if (typeof ensureValidEditionSelection === 'function') { ensureValidEditionSelection($layer); updateEditionSummary($layer); } } function ensureValidEditionSelection($layer) { if (!HAS_INTERACTED && window.MX_RESTORE_MODE) { return; } var $active = $layer.find( '.layer_choices li.choice.active, ' + '.layer_choices li.choice.selected, ' + '.layer_choices li.choice.mxactive' ).first(); if (!$active.length || !isChoiceEnabled($active)) { var $firstEnabled = $layer.find('.layer_choices li.choice').filter(function () { return isChoiceEnabled($(this)); }).first(); if ($firstEnabled.length) { $layer.find('.layer_choices li.choice') .removeClass('active mxactive selected') .find('button.choice-item').attr('aria-pressed', 'false'); $firstEnabled.addClass('active mxactive selected') .find('button.choice-item').attr('aria-pressed', 'true'); var label = $.trim($firstEnabled.find('.choice-name').text()); $layer.find('> .layer-item .selected-choice').text(label); } else { $layer.find('.layer_choices li.choice') .removeClass('active mxactive selected') .find('button.choice-item').attr('aria-pressed', 'false'); $layer.find('> .layer-item .selected-choice').text(''); } } } function updateEditionSummary($layer) { var $summary = $layer.find('.mx-edition-summary'); if (!$summary.length) return; var $selected = $layer.find( '.layer_choices li.choice.active, ' + '.layer_choices li.choice.selected, ' + '.layer_choices li.choice.mxactive' ).first(); if (!$selected.length || !isChoiceEnabled($selected)) { $selected = $layer.find('.layer_choices li.choice').filter(function () { return isChoiceEnabled($(this)); }).first(); } if (!$selected.length) return; var label = $.trim($selected.find('.choice-name').text()); var hasEvo = hasEvolutionLayer(); var evoKey = hasEvo ? getCurrentEvolutionKey() : ''; var editionKey = label.toUpperCase(); var fileName = getEditionFileName(evoKey, editionKey); if (!fileName) return; var bgUrl = getAssetBasePath('editionSummary') + fileName; var safeBgUrl = encodeURI(bgUrl); $summary.find('.mx-edition-summary-name').text(label); $summary.css('background-image', 'url("' + safeBgUrl + '")'); } function initEditionLayer($layer) { if ($layer.hasClass('mx-edition-layer')) return; $layer.addClass('mx-edition-layer'); // Für Reinigungsvlies / Kraftband: keine Summary-Leiste, Tiles direkt offen anzeigen var _folderKey = getProductFolderKey(); var isReinigungsvlies = (_folderKey === 'Reinigungsvlies' || _folderKey === 'Kraftband'); var $choicesWrapper = $layer.find('.layer_choices').first(); if (!isReinigungsvlies && $choicesWrapper.length && !$layer.find('.mx-edition-summary').length) { var summaryHtml = '' + '' + '▾' + '' + '' + '' + '' + '' + ''; $(summaryHtml).insertBefore($choicesWrapper); $layer.on('click', '.mx-edition-summary', function () { $layer.toggleClass('mx-edition-open'); }); } // Reinigungsvlies: Choices immer sichtbar, eigenes Tile-Grid wie mx-dreh-tiles if (isReinigungsvlies) { $layer.addClass('mx-edition-open mx-no-summary'); $layer.find('.mx-edition-summary').hide(); if (!$layer.find('.mx-edition-no-summary-tiles').length) { var $tilesWrap = $('', { 'class': 'mx-edition-no-summary-tiles' }); $layer.find('.layer_choices li.choice').each(function () { var $choice = $(this); var label = $.trim($choice.find('.choice-name').text()); var $btn = $choice.find('button.choice-item').first(); var $tile = $('', { 'class': 'mx-edition-no-summary-tile', 'type': 'button' }); $tile.data('$choice', $choice); var $img = $('', { 'class': 'mx-edition-no-summary-tile-img', 'alt': label, 'loading': 'eager' }); $tile.append($img); // Label-Text unter dem Bild anzeigen (wie bei Aufnahme/Drehrichtung) if (label) { var $label = $('', { 'class': 'mx-edition-no-summary-tile-label' }).text(label); $tile.append($label); } if ($choice.hasClass('active') || $choice.hasClass('mxactive') || $choice.hasClass('selected')) { $tile.addClass('active'); } if ($choice.hasClass('mx-disabled') || $choice.hasClass('not-available')) { $tile.addClass('disabled'); } $tile.on('click', function () { if ($tile.hasClass('disabled')) return; $btn.trigger('click'); }); $tilesWrap.append($tile); }); $choicesWrapper.after($tilesWrap); $choicesWrapper.css({ position: 'absolute', left: '-9999px', visibility: 'hidden' }); // Sync: Bilder + aktiver/disabled Status von original choices in custom tiles übernehmen var syncTiles = function () { $layer.find('.layer_choices li.choice').each(function (i) { var $c = $(this); var $t = $tilesWrap.find('.mx-edition-no-summary-tile').eq(i); if (!$t.length) return; var imgSrc = $c.find('img.mx-edition-image').attr('src'); if (imgSrc) $t.find('.mx-edition-no-summary-tile-img').attr('src', imgSrc); $t.toggleClass('active', $c.hasClass('active') || $c.hasClass('mxactive') || $c.hasClass('selected')); $t.toggleClass('disabled', $c.hasClass('mx-disabled') || $c.hasClass('not-available') || $c.hasClass('mx-disabled-by-image')); }); }; var observer = new MutationObserver(syncTiles); observer.observe($choicesWrapper[0], { subtree: true, attributes: true, attributeFilter: ['class', 'src'] }); // Sync nach init direkt aufrufen (Bilder könnten schon gesetzt sein) $layer.data('mxSyncNoSummaryTiles', syncTiles); $layer.on('click', '.layer_choices li.choice button.choice-item', function () { setTimeout(syncTiles, 20); }); } } updateEditionImages($layer); // Falls custom tiles vorhanden: Bilder sofort syncen var _syncFn = $layer.data('mxSyncNoSummaryTiles'); if (_syncFn) { setTimeout(_syncFn, 50); setTimeout(_syncFn, 200); } $layer.on('click', '.layer_choices li.choice button.choice-item', function () { setTimeout(function () { updateEditionImages($layer); }, 10); }); } function initEditionDesign() { var found = false; $('ul.layers > li.layers-list-item').each(function () { var $layer = $(this); // In restore mode: do not auto-fill layers that were NOT part of the restored config. if (!HAS_INTERACTED && window.MX_RESTORE_MODE && Array.isArray(window.MX_RESTORE_KEYS)) { var layerName = $.trim($layer.find('> .layer-item .layer-name').first().text()); if (layerName && window.MX_RESTORE_KEYS.indexOf(layerName) === -1) { return; // keep empty (e.g., Länge = NULL for Klett) } } if ($.trim($layer.find('> .layer-item .layer-name').first().text()) === 'Edition') { found = true; initEditionLayer($layer); } }); return found; } function syncPerformanceToggle($layer) { if (!$layer || !$layer.length) return; var $toggle = $layer.find('.mx-performance-toggle'); if (!$toggle.length) return; var $offChoice = $layer.data('mxPerfOffChoice'); var $onChoice = $layer.data('mxPerfOnChoice'); if (!$offChoice || !$onChoice) return; var heroActive = (typeof getCurrentEvolutionKey === 'function' && (getCurrentEvolutionKey() === 'HERO' || getCurrentEvolutionKey() === 'HERO R2')); if (heroActive) { setChoiceDisabled($onChoice, true); setChoiceDisabled($offChoice, true); $layer.find('.layer_choices .choices-list li.choice') .removeClass('active mxactive selected') .find('button.choice-item').attr('aria-pressed', 'false'); $offChoice .addClass('active mxactive selected') .find('button.choice-item').attr('aria-pressed', 'true'); $toggle .attr('data-state', 'off') .find('.mx-performance-label').text('OFF'); $toggle.addClass('mx-performance-toggle-disabled'); return; } var onEnabled = isChoiceEnabled($onChoice); var offEnabled = isChoiceEnabled($offChoice); var isOn = onEnabled && ($onChoice.hasClass('active') || $onChoice.hasClass('mxactive') || $onChoice.hasClass('selected') || $onChoice.find('button.choice-item[aria-pressed="true"]').length > 0); var labelText = isOn ? 'AKTIV' : 'OFF'; $toggle .attr('data-state', isOn ? 'on' : 'off') .find('.mx-performance-label') .text(labelText); var toggleShouldBeEnabled = onEnabled && offEnabled; $toggle.toggleClass('mx-performance-toggle-disabled', !toggleShouldBeEnabled); } function normalizePerfLabel(raw) { var t = String(raw || '').trim().toUpperCase(); if (t === '0') return 'OFF'; if (t === '1') return 'AKTIV'; if (t.indexOf('OFF') !== -1 || t.indexOf('AUS') !== -1) return 'OFF'; if (t.indexOf('AKTIV') !== -1 || t.indexOf('ON') !== -1 || t.indexOf('AN') !== -1) return 'AKTIV'; return t || '—'; } function initPerformanceLayer() { $('ul.layers > li.layers-list-item').each(function () { var $layer = $(this); // In restore mode: do not auto-fill layers that were NOT part of the restored config. if (!HAS_INTERACTED && window.MX_RESTORE_MODE && Array.isArray(window.MX_RESTORE_KEYS)) { var layerName = $.trim($layer.find('> .layer-item .layer-name').first().text()); if (layerName && window.MX_RESTORE_KEYS.indexOf(layerName) === -1) { return; // keep empty (e.g., Länge = NULL for Klett) } } if ($.trim($layer.find('> .layer-item .layer-name').first().text()) !== 'Performance-Boost') return; var evoKey = (typeof getCurrentEvolutionKey === 'function') ? getCurrentEvolutionKey() : ''; // Wenn HERO aktiv ist → Performance Layer komplett verstecken if (evoKey === 'HERO' || evoKey === 'HERO R2') { $layer.hide(); return; } else { $layer.show(); } var $choices = $layer.find('.layer_choices .choices-list li.choice'); // Wenn keine echten Choices existieren → ebenfalls verstecken if (!$choices.length) { $layer.hide(); return; } // Entferne NULL Choices $choices = $choices.filter(function () { var txt = $.trim($(this).find('.choice-name').text()); return txt && txt !== 'NULL' && txt !== '(NULL)'; }); if ($choices.length < 2) { $layer.hide(); return; } // Standard Init wie vorher var alreadyInit = $layer.hasClass('mx-performance-layer'); $layer.addClass('mx-performance-layer'); var $offChoice = null; var $onChoice = null; $choices.each(function () { var $c = $(this); var raw = $.trim($c.find('.choice-name').text()); var norm = normalizePerfLabel(raw); if (norm === 'OFF') $offChoice = $c; else if (norm === 'AKTIV') $onChoice = $c; }); if (!$offChoice) $offChoice = $choices.eq(0); if (!$onChoice) $onChoice = $choices.eq(1); $layer.data('mxPerfOffChoice', $offChoice); $layer.data('mxPerfOnChoice', $onChoice); if (!alreadyInit && !$layer.find('.mx-performance-toggle').length) { var $toggle = $('OFF'); // Info icon + tooltip var infoText = 'Mit Cubitron Boost für noch höheren Abtrag: Schleifmittel bestehen aus präzisionsgeformten, dreieckigen Körnern, die senkrecht ausgerichtet sind und eine besonders scharfe Oberfläche für extreme Abtragsleistung bieten.'; var $infoWrap = $('ⓘ' + infoText + ''); var $layerItem = $layer.find('> .layer-item'); $layerItem.append($infoWrap); $layerItem.append($toggle); // Move tooltip out of layer-item into the li, so absolute positioning = full li width var $tooltip = $infoWrap.find('.mx-perf-info-tooltip'); $layer.append($tooltip); // Reusable function: position tooltip + update arrow function mxUpdateTooltipPosition() { var $icon = $infoWrap.find('.mx-perf-info-icon'); var layerItemBottom = $layerItem.position().top + $layerItem.outerHeight(true); var $searchRoot = $layer.closest('ul.layers'); if (!$searchRoot.length) $searchRoot = $layer.closest('.mkl_pc_toolbar'); if (!$searchRoot.length) $searchRoot = $layer.closest('.mkl_pc_container'); if (!$searchRoot.length) $searchRoot = $layer.parent(); var $rangeWrap = $searchRoot.find('.mx-range-wrap').first(); if ($rangeWrap.length) { var liOffset = $layer.offset(); var rangeOffset = $rangeWrap.offset(); var relLeft = rangeOffset.left - liOffset.left; var relRight = (liOffset.left + $layer.outerWidth()) - (rangeOffset.left + $rangeWrap.outerWidth()); $tooltip.css({ top: (layerItemBottom + 8) + 'px', left: relLeft + 'px', right: relRight + 'px', width: '', transform: 'none' }); } else { $tooltip.css({ top: (layerItemBottom + 8) + 'px' }); } var iconOffset = $icon.offset(); var tooltipOffset = $tooltip.offset(); var arrowLeft = Math.round((iconOffset.left + $icon.outerWidth() / 2) - tooltipOffset.left); $tooltip[0].style.setProperty('--mx-arrow-left', arrowLeft + 'px'); } // Toggle tooltip on click $infoWrap.find('.mx-perf-info-icon').on('click', function (e) { e.stopPropagation(); var isOpen = $infoWrap.hasClass('mx-perf-info-open'); $infoWrap.toggleClass('mx-perf-info-open'); if (!isOpen) { $tooltip.css('display', 'block'); mxUpdateTooltipPosition(); } else { $tooltip.css('display', ''); } }); // Re-calculate arrow position on window resize $(window).on('resize.mxPerfInfo', function () { if ($infoWrap.hasClass('mx-perf-info-open')) { mxUpdateTooltipPosition(); } }); $(document).on('click.mxPerfInfo', function () { $infoWrap.removeClass('mx-perf-info-open'); $tooltip.css('display', ''); }); $toggle.on('click', function () { var $t = $(this); if ($t.hasClass('mx-performance-toggle-disabled')) return; var toOn = ($t.attr('data-state') !== 'on'); var $targetChoice = toOn ? $onChoice : $offChoice; if (!$targetChoice || !isChoiceEnabled($targetChoice)) return; $targetChoice.find('button.choice-item').trigger('click'); setTimeout(function () { syncPerformanceToggle($layer); }, 30); }); } syncPerformanceToggle($layer); }); } function initDrehrichtungLayer() { $('ul.layers > li.layers-list-item').each(function () { var $layer = $(this); if ($.trim($layer.find('> .layer-item .layer-name').first().text()) !== 'Drehrichtung') return; $layer.addClass('mx-drehrichtung-layer'); // Hide original choices visually (already handled by CSS) var $choices = $layer.find('.layer_choices .choices-list li.choice'); if (!$choices.length) return; // Build image URL base from theme directory // Images: Links.png, Rechts.png, Neutral.png in /Drehrichtung/ folder var _drehSuffix = ''; // Bilder sprachunabhaengig var baseUrl = ASSET_BASE + 'Drehrichtung/'; // Already rendered? if ($layer.find('.mx-dreh-tiles').length) { // Just sync active state syncDrehrichtungTiles($layer); return; } // Build tile container var $tilesWrap = $(''); // Feste Reihenfolge: LINKS, NEUTRAL, RECHTS var orderedChoices = [null, null, null]; // [links, neutral, rechts] $choices.each(function () { var $c = $(this); var t = $.trim($c.find('.choice-name').text()).toUpperCase(); if (t.indexOf('LINKS') !== -1 || t.indexOf('LEFT') !== -1) orderedChoices[0] = $c; else if (t.indexOf('RECHTS') !== -1 || t.indexOf('RIGHT') !== -1) orderedChoices[2] = $c; else orderedChoices[1] = $c; }); // Nicht vorhandene Slots entfernen orderedChoices = orderedChoices.filter(function (x) { return x !== null; }); $.each(orderedChoices, function (i, $choice) { var label = $.trim($choice.find('.choice-name').text()).toUpperCase(); var displayLabel = mxT(label); // ✅ MX Translation // Determine image filename (use original german label for image logic) var imgFile; if (label.indexOf('LINKS') !== -1 || label.indexOf('LEFT') !== -1 || label.indexOf('LINKSLAUF') !== -1) { imgFile = 'Links' + _drehSuffix + '.png'; } else if (label.indexOf('RECHTS') !== -1 || label.indexOf('RIGHT') !== -1 || label.indexOf('RECHTSLAUF') !== -1) { imgFile = 'Rechts' + _drehSuffix + '.png'; } else { imgFile = 'Neutral' + _drehSuffix + '.png'; } var isActive = $choice.hasClass('active') || $choice.hasClass('selected') || $choice.find('button.choice-item').attr('aria-pressed') === 'true'; var isDisabled = $choice.hasClass('mx-disabled') || $choice.hasClass('not-available'); var $tile = $('' + '' + '' + $('').text(displayLabel).html() + '' + ''); $tile.data('choiceEl', $choice); $tile.on('click', function () { if ($(this).hasClass('disabled')) return; var $btn = $(this).data('choiceEl').find('button.choice-item').first(); if ($btn.length) { $btn.trigger('click'); setTimeout(function () { syncDrehrichtungTiles($layer); }, 30); } }); $tilesWrap.append($tile); }); $layer.find('> .layer-item').after($tilesWrap); syncDrehrichtungTiles($layer); }); } function syncDrehrichtungTiles($layer) { if (!$layer || !$layer.length) return; $layer.find('.mx-dreh-tile').each(function () { var $tile = $(this); var $choice = $tile.data('choiceEl'); if (!$choice || !$choice.length) return; var isActive = $choice.hasClass('active') || $choice.hasClass('mxactive') || $choice.hasClass('selected') || $choice.find('button.choice-item').attr('aria-pressed') === 'true'; var isDisabled = $choice.hasClass('mx-disabled') || $choice.hasClass('not-available') || $choice.find('button.choice-item').prop('disabled'); $tile.toggleClass('active', isActive); $tile.toggleClass('disabled', isDisabled); }); } function detectAufnahmeCategory(choiceText) { var text = choiceText.toUpperCase().trim(); for (var category in AUFNAHME_CATEGORY_PATTERNS) { var patterns = AUFNAHME_CATEGORY_PATTERNS[category]; for (var i = 0; i < patterns.length; i++) { if (patterns[i].test(text)) return category; } } return null; } function extractAufnahmeDisplayName(choiceText, category) { if (!choiceText) return ''; var text = choiceText.trim().replace(/\s+/g, ' '); if (category === 'BOHRUNG') { var prefixMatch = text.match(/^\s*([^\dA-Za-z]+)\s*/); var prefix = prefixMatch ? prefixMatch[1].trim() : ''; var m = text.match(/(\d+(?:[.,]\d+)?)\s*mm\b/i); if (m) { var num = m[1]; return (prefix ? prefix + ' ' : '') + num + ' MM'; } return text; } if (category === 'GEWINDE' || category === 'SCHAFT' || category === 'AUFNAHMEBOLZEN') { text = text.replace(new RegExp('^' + category + '\\s*', 'i'), '').trim(); text = text.replace(/(\d+(?:[.,]\d+)?)\s*mm\b/i, '$1 MM'); return text; } return text; } function groupAufnahmeChoices($choices) { var groups = {}, uncategorized = []; $choices.each(function () { var $choice = $(this), choiceName = $.trim($choice.find('.choice-name').text()); if (!choiceName || choiceName === '(NULL)' || choiceName === 'NULL') return; var category = detectAufnahmeCategory(choiceName); if (category) { if (!groups[category]) groups[category] = []; groups[category].push({ $element: $choice, name: choiceName, displayName: extractAufnahmeDisplayName(choiceName, category), category: category }); } else { uncategorized.push({ $element: $choice, name: choiceName, displayName: choiceName.toUpperCase(), category: null }); } }); if (!Object.keys(groups).length && uncategorized.length) { groups['OPTIONEN'] = uncategorized; uncategorized = []; } return { groups: groups, uncategorized: uncategorized }; } function isAufnahmeChoiceActive($choice) { return $choice.hasClass('active') || $choice.hasClass('mxactive') || $choice.hasClass('selected') || $choice.find('button.choice-item[aria-pressed="true"]').length > 0; } function getAufnahmeActiveCategory(groupedData) { for (var category in groupedData.groups) { for (var i = 0; i < groupedData.groups[category].length; i++) { if (isAufnahmeChoiceActive(groupedData.groups[category][i].$element)) { return category; } } } var orderedEnabled = AUFNAHME_CATEGORY_ORDER.filter(function (c) { return groupedData.groups[c] && groupedData.groups[c].some(function (item) { return isChoiceEnabled(item.$element); }); }); if (orderedEnabled.length) { return orderedEnabled[0]; } for (var cat in groupedData.groups) { if (groupedData.groups.hasOwnProperty(cat)) { return cat; } } return null; } var _aufSuffix = ''; // Bilder sprachunabhaengig var AUFNAHME_CATEGORY_IMAGES = { 'BOHRUNG': ASSET_BASE + 'Aufnahme/Bohrung' + _aufSuffix + '.png', 'GEWINDE': ASSET_BASE + 'Aufnahme/Gewinde' + _aufSuffix + '.png', 'SCHNELLSPANN': ASSET_BASE + 'Aufnahme/Schnellspann' + _aufSuffix + '.png', 'SCHAFT': ASSET_BASE + 'Aufnahme/Schaft' + _aufSuffix + '.png', 'AUFNAHMEBOLZEN': ASSET_BASE + 'Aufnahme/Aufnahmebolzen' + _aufSuffix + '.png' }; function renderAufnahmeUI($layer, groupedData) { $layer.find('.mx-aufnahme-container').remove(); var categories = AUFNAHME_CATEGORY_ORDER.filter(function (c) { return groupedData.groups[c] && groupedData.groups[c].length; }); for (var c in groupedData.groups) { if (categories.indexOf(c) === -1) categories.push(c); } if (!categories.length) return; var activeCategory = getAufnahmeActiveCategory(groupedData); var $container = $(''); // Image tile selector (like Evolutionsstufe) if (categories.length > 1) { var $tiles = $(''); // Always use 3 columns for consistent tile sizing (matches Schlitzungsmuster/Drehrichtung) if (window.innerWidth > 660) { $tiles.css('grid-template-columns', 'repeat(3, 1fr)'); } categories.forEach(function (cat) { var hasEnabled = groupedData.groups[cat].some(function (item) { return isChoiceEnabled(item.$element); }); var imgSrc = AUFNAHME_CATEGORY_IMAGES[cat] || ''; var $tile = $('').attr('data-category', cat); if (imgSrc) { $tile.append($('').attr('src', imgSrc).attr('alt', cat).addClass('mx-aufnahme-tile-img')); } // Label bleibt deutsch — Bild enthält bereits den Text $tile.append($('').text(mxT(cat))); if (cat === activeCategory) $tile.addClass('active'); if (!hasEnabled) $tile.addClass('disabled'); $tiles.append($tile); }); $container.append($tiles); } else { activeCategory = categories[0]; } // Sub-options panel (radio-button style) var $arrowWrapper = $(''); var $arrow = $(''); $arrowWrapper.append($arrow); var $optionsContainer = $(''); categories.forEach(function (cat) { var $options = $('').attr('data-category', cat); if (cat !== activeCategory) $options.addClass('mx-aufnahme-options-hidden').hide(); groupedData.groups[cat].forEach(function (item, idx) { var radioId = 'mx-aufnahme-radio-' + cat + '-' + idx; var $wrap = $(''); var $radio = $(''); var labelText = (item.displayName || '') .replace(/\(LINKSLAUF\)/i, '(LL)') .replace(/\(RECHTSLAUF\)/i, '(RL)'); var $label = $('').text(mxT(labelText)); // ✅ MX Translation var $btn = $('') .attr('data-choice-name', item.name) .attr('title', item.name) .append($radio) .append($label); if (isAufnahmeChoiceActive(item.$element)) $btn.addClass('active'); if (!isChoiceEnabled(item.$element)) $btn.addClass('disabled'); $btn.data('originalChoice', item.$element); // Add separator between items if (idx > 0) $options.append($('')); $options.append($btn); }); // Smart grid: set columns + border classes based on item count (function applyAufnahmeGrid($opts) { var $btns = $opts.find('.mx-aufnahme-option'); var count = $btns.length; // 1-3 items: single row, fill equally; 4+: max 3 per row var cols = count <= 3 ? count : 3; var rows = Math.ceil(count / cols); $opts[0].style.setProperty('--mx-aufnahme-cols', cols, ''); $btns.each(function (i) { var col = (i % cols) + 1; var row = Math.floor(i / cols) + 1; $(this) .toggleClass('mx-last-in-row', col === cols) .toggleClass('mx-last-row', row === rows); }); })($options); $optionsContainer.append($options); }); $container.append($arrowWrapper); $container.append($optionsContainer); $layer.find('> .layer-item').after($container); $layer.data('mxAufnahmeGroups', groupedData).data('mxAufnahmeActiveCategory', activeCategory); // Position arrow under active tile setTimeout(function () { mxPositionAufnahmeArrow($layer); }, 50); // Reposition arrow whenever the tiles container resizes (viewport changes) if ('ResizeObserver' in window) { var tilesEl = $container.find('.mx-aufnahme-tiles')[0]; if (tilesEl) { var _layerRef = $layer; new ResizeObserver(function () { mxPositionAufnahmeArrow(_layerRef); }).observe(tilesEl); } } } function syncAufnahmeState($layer) { var groupedData = $layer.data('mxAufnahmeGroups'); if (!groupedData) return; var activeCategory = getAufnahmeActiveCategory(groupedData); $layer.data('mxAufnahmeActiveCategory', activeCategory); $layer.find('.mx-aufnahme-tile').each(function () { var $tile = $(this), cat = $tile.attr('data-category'); $tile.toggleClass('active', cat === activeCategory); $tile.toggleClass('disabled', !groupedData.groups[cat] || !groupedData.groups[cat].some(function (item) { return isChoiceEnabled(item.$element); })); }); $layer.find('.mx-aufnahme-options').each(function () { var $options = $(this), cat = $options.attr('data-category'); if (cat === activeCategory) $options.removeClass('mx-aufnahme-options-hidden').show(); else $options.addClass('mx-aufnahme-options-hidden').hide(); $options.find('.mx-aufnahme-option').each(function () { var $option = $(this), $orig = $option.data('originalChoice'); if ($orig) { $option.toggleClass('active', isAufnahmeChoiceActive($orig)); $option.toggleClass('disabled', !isChoiceEnabled($orig)); } }); }); var $activeOptions = $layer.find('.mx-aufnahme-options[data-category="' + activeCategory + '"]'); if ($activeOptions.length && !$activeOptions.find('.mx-aufnahme-option.active').length) { var $enabledOptions = $activeOptions.find('.mx-aufnahme-option:not(.disabled)'); if ($enabledOptions.length === 1) { $enabledOptions.addClass('active'); } } setTimeout(function () { mxPositionAufnahmeArrow($layer); }, 50); } function mxPositionAufnahmeArrow($layer) { var $activeTile = $layer.find('.mx-aufnahme-tile.active'); var $arrow = $layer.find('.mx-aufnahme-arrow'); var $arrowWrapper = $layer.find('.mx-aufnahme-arrow-wrapper'); var $tiles = $layer.find('.mx-aufnahme-tiles'); if (!$activeTile.length || !$arrow.length || !$tiles.length) return; // Use getBoundingClientRect for accurate position after reflow/resize var tileRect = $activeTile[0].getBoundingClientRect(); var wrapperRect = $arrowWrapper[0].getBoundingClientRect(); var tileCenterX = (tileRect.left + tileRect.width / 2) - wrapperRect.left; $arrow.css('left', tileCenterX + 'px'); } // Reposition all aufnahme arrows on resize (debounced) var _mxArrowResizeTimeout = null; function _mxRepositionAllArrows() { clearTimeout(_mxArrowResizeTimeout); _mxArrowResizeTimeout = setTimeout(function () { $('ul.layers > li.layers-list-item.mx-aufnahme-layer').each(function () { mxPositionAufnahmeArrow($(this)); }); // Also reapply grid columns when crossing the 660px breakpoint mxSetTileGridColumns(); }, 80); } window.addEventListener('resize', _mxRepositionAllArrows); // Set grid-template-columns on evo/edition tile ULs based on actual item count function mxSetTileGridColumns() { // Under 660px: CSS handles auto-fill, no JS override needed if (window.innerWidth <= 660) return; var selectors = [ '.mx-evolutionsstufe-layer .layer_choices ul', '.mx-edition-layer .layer_choices ul' ]; selectors.forEach(function (sel) { $(sel).each(function () { var $ul = $(this); // Count only visible items (exclude display:none) var count = $ul.find('> li.choice').filter(function () { return $(this).css('display') !== 'none'; }).length; var isEdition = $ul.closest('li.layers-list-item').hasClass('mx-edition-layer'); // Always reset first (important when switching between 1/2 and 3+) $ul.css('transform', ''); // For 1-2 tiles: // - keep CSS natural sizing (no grid override) // - BUT for Edition align left like the summary (your console fix) if (count <= 2) { $ul.css('grid-template-columns', ''); // clear any previous inline override if (isEdition) $ul.css('transform', 'translateX(8px)'); return; } // 3+ tiles: enforce max 3 columns var cols = Math.min(count, 3); $ul.css('grid-template-columns', 'repeat(' + cols + ', 1fr)'); }); }); } function initAufnahmeLayer() { $('ul.layers > li.layers-list-item').each(function () { var $layer = $(this); // In restore mode: do not auto-fill layers that were NOT part of the restored config. if (!HAS_INTERACTED && window.MX_RESTORE_MODE && Array.isArray(window.MX_RESTORE_KEYS)) { var layerName = $.trim($layer.find('> .layer-item .layer-name').first().text()); if (layerName && window.MX_RESTORE_KEYS.indexOf(layerName) === -1) { return; // keep empty (e.g., Länge = NULL for Klett) } } if ($.trim($layer.find('> .layer-item .layer-name').first().text()) !== 'Aufnahme') return; var alreadyInit = $layer.hasClass('mx-aufnahme-layer'); $layer.addClass('mx-aufnahme-layer'); var $choices = $layer.find('.layer_choices .choices-list li.choice, .layer_choices ul li.choice'); if (!$choices.length) return; var groupedData = groupAufnahmeChoices($choices); if (!alreadyInit) { renderAufnahmeUI($layer, groupedData); $layer.on('click', '.mx-aufnahme-tile:not(.disabled)', function () { var $tile = $(this), cat = $tile.attr('data-category'); $layer.find('.mx-aufnahme-tile').removeClass('active'); $tile.addClass('active'); $layer.find('.mx-aufnahme-options').addClass('mx-aufnahme-options-hidden').hide(); $layer.find('.mx-aufnahme-options[data-category="' + cat + '"]').removeClass('mx-aufnahme-options-hidden').show(); $layer.data('mxAufnahmeActiveCategory', cat); mxPositionAufnahmeArrow($layer); var $activeOptions = $layer.find('.mx-aufnahme-options[data-category="' + cat + '"]'); if (!$activeOptions.find('.mx-aufnahme-option.active').length) { var $first = $activeOptions.find('.mx-aufnahme-option:not(.disabled)').first(); if ($first.length) $first.trigger('click'); } }); $layer.on('click', '.mx-aufnahme-option:not(.disabled)', function () { var $orig = $(this).data('originalChoice'); if ($orig && $orig.length) { var $btn = $orig.find('button.choice-item').first(); if ($btn.length) { $btn.trigger('click'); // ✅ FIX: 150ms statt 30ms – variation.js setzt active-Klassen async setTimeout(function () { syncAufnahmeState($layer); }, 150); } } }); } else { $layer.data('mxAufnahmeGroups', groupedData); syncAufnahmeState($layer); } }); } window.MX_reinitAufnahme = initAufnahmeLayer; // ============================================================ // SCHLITZUNGSMUSTER: Radio (MM-Größe) + Bildkacheln (G/S-Line) // ============================================================ var SCHLITZ_MM_ORDER = ['3&3', '3&5', '5&5']; var SCHLITZ_LINE_IMAGES = { 'G-LINE': ASSET_BASE + 'Schlitzungsmuster/G-Line.png', 'S-LINE': ASSET_BASE + 'Schlitzungsmuster/S-Line.png' }; function detectSchlitzMM(choiceText) { var t = choiceText.toUpperCase().replace(/\s/g, ''); var m = t.match(/(\d+)[&X×](\d+)/); if (!m) return null; var a = parseInt(m[1], 10), b = parseInt(m[2], 10); if (a > b) { var tmp = a; a = b; b = tmp; } return a + '&' + b; } function detectSchlitzLine(choiceText) { var t = choiceText.toUpperCase(); if (/G-?LINE|G\s*LINE/.test(t)) return 'G-LINE'; if (/S-?LINE|S\s*LINE/.test(t)) return 'S-LINE'; return null; } function groupSchlitzChoices($choices) { // groups: { '3&3': { 'G-LINE': {$element, name}, 'S-LINE': ... }, ... } var groups = {}; $choices.each(function () { var $choice = $(this); var choiceName = $.trim($choice.find('.choice-name').text()); if (!choiceName || choiceName === '(NULL)' || choiceName === 'NULL') return; var mm = detectSchlitzMM(choiceName); var line = detectSchlitzLine(choiceName); if (!mm || !line) return; if (!groups[mm]) groups[mm] = {}; groups[mm][line] = { $element: $choice, name: choiceName }; }); return groups; } function getSchlitzActiveState(groups) { // Returns { mm: '3&3', line: 'G-LINE' } for the currently active choice for (var mm in groups) { for (var line in groups[mm]) { var $el = groups[mm][line].$element; if ($el.hasClass('active') || $el.hasClass('mxactive') || $el.hasClass('selected') || $el.find('button.choice-item[aria-pressed="true"]').length) { return { mm: mm, line: line }; } } } // fallback: first enabled for (var i = 0; i < SCHLITZ_MM_ORDER.length; i++) { var mmKey = SCHLITZ_MM_ORDER[i]; if (!groups[mmKey]) continue; var lines = ['G-LINE', 'S-LINE']; for (var j = 0; j < lines.length; j++) { var entry = groups[mmKey][lines[j]]; if (entry && isChoiceEnabled(entry.$element)) { return { mm: mmKey, line: lines[j] }; } } } return null; } function renderSchlitzUI($layer, groups) { $layer.find('.mx-schlitz-container').remove(); var availMMs = SCHLITZ_MM_ORDER.filter(function (mm) { return !!groups[mm]; }); var extraMMs = Object.keys(groups).filter(function (mm) { return SCHLITZ_MM_ORDER.indexOf(mm) === -1; }); availMMs = availMMs.concat(extraMMs); if (!availMMs.length) return; var active = getSchlitzActiveState(groups); var activeMM = active ? active.mm : availMMs[0]; var activeLine = active ? active.line : null; var $container = $(''); // --- Row 1: MM Radio-Buttons --- var $radioGroup = $(''); availMMs.forEach(function (mm, idx) { var hasEnabled = groups[mm] && Object.keys(groups[mm]).some(function (line) { return isChoiceEnabled(groups[mm][line].$element); }); var label = mm.replace('&', ' & ') + ' MM'; var $radio = $(''); var $lbl = $('').text(label); var $btn = $('') .attr('data-mm', mm) .append($radio).append($lbl); if (mm === activeMM) $btn.addClass('active'); if (!hasEnabled) $btn.addClass('disabled'); $btn.data('originalMM', mm); if (idx > 0) $radioGroup.append($('')); $radioGroup.append($btn); }); $container.append($radioGroup); // --- Tiles pro MM-Gruppe --- availMMs.forEach(function (mm) { var lines = ['G-LINE', 'S-LINE'].filter(function (l) { return groups[mm] && groups[mm][l]; }); var numCols = lines.length; var $tiles = $('') .attr('data-mm', mm) .css({ 'display': 'none', 'grid-template-columns': 'repeat(3, 1fr)' }); lines.forEach(function (line) { var entry = groups[mm][line]; var imgSrc = SCHLITZ_LINE_IMAGES[line] || ''; var $tile = $('') .attr('data-mm', mm).attr('data-line', line); if (imgSrc) { $tile.append($('').attr('src', imgSrc).attr('alt', line).addClass('mx-schlitz-tile-img')); } var isActive = activeLine === line && mm === activeMM; var enabled = isChoiceEnabled(entry.$element); if (isActive) $tile.addClass('active'); if (!enabled) $tile.addClass('disabled'); $tile.data('originalChoice', entry.$element); $tiles.append($tile); }); // Aktive MM-Gruppe anzeigen if (mm === activeMM) { $tiles.css('display', 'grid').addClass('mx-schlitz-visible'); } $container.append($tiles); }); $layer.find('> .layer-item').after($container); $layer.data('mxSchlitzGroups', groups).data('mxSchlitzActiveMM', activeMM); setTimeout(function () { mxPositionSchlitzArrow($layer); }, 50); } function mxPositionSchlitzArrow($layer) { var activeMM = $layer.data('mxSchlitzActiveMM'); var $activeBtn = $layer.find('.mx-schlitz-mm-btn[data-mm="' + activeMM + '"]'); var $arrow = $layer.find('.mx-schlitz-arrow-wrapper .mx-aufnahme-arrow'); if (!$activeBtn.length || !$arrow.length) return; var btnLeft = $activeBtn.position().left; var btnWidth = $activeBtn.outerWidth(); $arrow.css('left', (btnLeft + btnWidth / 2) + 'px'); } function syncSchlitzState($layer) { var groups = $layer.data('mxSchlitzGroups'); if (!groups) return; var active = getSchlitzActiveState(groups); var activeMM = active ? active.mm : $layer.data('mxSchlitzActiveMM'); var activeLine = active ? active.line : null; $layer.data('mxSchlitzActiveMM', activeMM); $layer.find('.mx-schlitz-mm-btn').each(function () { var $btn = $(this), mm = $btn.attr('data-mm'); var hasEnabled = groups[mm] && Object.keys(groups[mm]).some(function (line) { return isChoiceEnabled(groups[mm][line].$element); }); $btn.toggleClass('active', mm === activeMM); $btn.toggleClass('disabled', !hasEnabled); }); // Show/hide tiles via inline style (beats all CSS) $layer.find('.mx-schlitz-tiles').each(function () { var $tiles = $(this), mm = $tiles.attr('data-mm'); if (mm === activeMM) { $tiles.css('display', 'grid').addClass('mx-schlitz-visible'); } else { $tiles.css('display', 'none').removeClass('mx-schlitz-visible'); } }); $layer.find('.mx-schlitz-tile').each(function () { var $tile = $(this); var $orig = $tile.data('originalChoice'); var mm = $tile.attr('data-mm'); var line = $tile.attr('data-line'); var isActive = (mm === activeMM && line === activeLine); $tile.toggleClass('active', isActive); $tile.toggleClass('disabled', $orig ? !isChoiceEnabled($orig) : true); }); mxPositionSchlitzArrow($layer); } function initSchlitzungsmusterLayer() { $('ul.layers > li.layers-list-item').each(function () { var $layer = $(this); var name = $.trim($layer.find('> .layer-item .layer-name').first().text()); if (name !== 'Schlitzungsmuster') return; var alreadyInit = $layer.hasClass('mx-schlitz-layer'); $layer.addClass('mx-schlitz-layer'); // Hide original choices $layer.find('.layer_choices, .layer_choices .choices-list, .layer_choices ul, .layer_choices li.choice').css({ 'display': 'none', 'visibility': 'hidden', 'height': '0', 'overflow': 'hidden', 'position': 'absolute', 'left': '-9999px' }); var $choices = $layer.find('.layer_choices .choices-list li.choice, .layer_choices ul li.choice'); if (!$choices.length) return; var groups = groupSchlitzChoices($choices); if (!Object.keys(groups).length) return; if (!alreadyInit) { renderSchlitzUI($layer, groups); // ── Schlitzungsmuster Info-Popup (nur Entgratwalze) ── (function () { var folder = getProductFolderKey(); if (folder !== 'Entgratwalze') return; if ($layer.find('.mx-schlitz-info-icon').length) return; var imgBase = ASSET_BASE + 'Entgratwalze/Schlitzungsmuster/'; // Info icon – reuses .mx-perf-info-icon class from configurator.css var $infoIcon = $('ⓘ'); var $layerItem = $layer.find('> .layer-item'); $layerItem.append($infoIcon); // Popup HTML – matches Figma 1:1 var popupHtml = '' + '' + '×' + '' + '' + 'LAMELLENBREITE 3 MM' + '' + '' + '' + 'FÜR VIELE KLEINEAUSSPARUNGEN' + '' + '' + '' + '' + 'LAMELLENBREITE 5 MM' + '' + '' + '' + 'FÜR AUßENKANTEN' + '' + '' + '' + '' + 'LAMELLENBREITE 3 & 5 MM' + '' + '' + '' + 'DER ALLROUNDER' + '' + '' + '' + '' + ''; var $backdrop = $(''); var $popup = $(popupHtml); $('body').append($backdrop).append($popup); // Position popup left of radio group, arrow always points at radio group center function positionPopup() { var $radioGroup = $layer.find('.mx-schlitz-mm-group'); if (!$radioGroup.length) $radioGroup = $layer.find('.mx-schlitz-container'); if (!$radioGroup.length) $radioGroup = $layer; // getBoundingClientRect = viewport-relative (matches position:fixed) var rgRect = $radioGroup[0].getBoundingClientRect(); var rgCenterY = rgRect.top + rgRect.height / 2; var popupW = $popup.outerWidth(); var popupH = $popup.outerHeight(); var arrowW = 32; var winW = $(window).width(); var winH = $(window).height(); // Left of the radio group var leftPos = rgRect.left - popupW - arrowW - 6; // Vertically: center popup on radio group center var topPos = rgCenterY - popupH / 2; // Clamp to viewport edges if (topPos < 10) topPos = 10; if (topPos + popupH > winH - 10) topPos = winH - popupH - 10; // Arrow points at rgCenterY relative to popup top var arrowTop = rgCenterY - topPos; arrowTop = Math.max(40, Math.min(popupH - 40, arrowTop)); $popup[0].style.setProperty('--schlitz-arrow-top', arrowTop + 'px'); // If no room on left, center on screen and hide arrow if (leftPos < 10) { leftPos = Math.max(10, (winW - popupW) / 2); topPos = (winH - popupH) / 2; $popup.find('.mx-schlitz-popup-arrow').hide(); } else { $popup.find('.mx-schlitz-popup-arrow').show(); } $popup.css({ top: topPos + 'px', left: leftPos + 'px' }); } function openPopup() { // Measure first, then animate $popup.css({ display: 'block', opacity: 0 }); positionPopup(); $popup.css({ display: 'none', opacity: '' }); $popup.fadeIn(200); $backdrop.fadeIn(200); } function closePopup() { $popup.fadeOut(200); $backdrop.fadeOut(200); } $infoIcon.find('.mx-schlitz-info-icon').on('click', function (e) { e.stopPropagation(); if ($popup.is(':visible')) { closePopup(); } else { openPopup(); } }); $popup.find('.mx-schlitz-popup-close').on('click', function (e) { e.stopPropagation(); closePopup(); }); $backdrop.on('click', function () { closePopup(); }); $(document).on('keydown.mxSchlitzPopup', function (e) { if (e.keyCode === 27 && $popup.is(':visible')) closePopup(); }); // On scroll/resize: close if radio group left viewport, otherwise reposition $(window).on('scroll.mxSchlitzPopup resize.mxSchlitzPopup', function () { if (!$popup.is(':visible')) return; var $rg = $layer.find('.mx-schlitz-mm-group'); if (!$rg.length) $rg = $layer; var rect = $rg[0].getBoundingClientRect(); var winH = $(window).height(); // If radio group is completely above or below viewport → close if (rect.bottom < 0 || rect.top > winH) { closePopup(); } else { positionPopup(); } }); })(); // MM radio click $layer.on('click', '.mx-schlitz-mm-btn:not(.disabled)', function () { var $btn = $(this), mm = $btn.attr('data-mm'); $layer.find('.mx-schlitz-mm-btn').removeClass('active'); $btn.addClass('active'); $layer.data('mxSchlitzActiveMM', mm); // Show/hide via inline style $layer.find('.mx-schlitz-tiles').each(function () { var $t = $(this); if ($t.attr('data-mm') === mm) { $t.css('display', 'grid').addClass('mx-schlitz-visible'); } else { $t.css('display', 'none').removeClass('mx-schlitz-visible'); } }); mxPositionSchlitzArrow($layer); // Auto-select first enabled tile in new MM var $visibleTiles = $layer.find('.mx-schlitz-tiles[data-mm="' + mm + '"]'); if (!$visibleTiles.find('.mx-schlitz-tile.active').length) { var $first = $visibleTiles.find('.mx-schlitz-tile:not(.disabled)').first(); if ($first.length) $first.trigger('click'); } }); // Tile (G/S-Line) click $layer.on('click', '.mx-schlitz-tile:not(.disabled)', function () { var $tile = $(this); var $orig = $tile.data('originalChoice'); if ($orig && $orig.length) { var $btn = $orig.find('button.choice-item').first(); if ($btn.length) { $btn.trigger('click'); setTimeout(function () { syncSchlitzState($layer); }, 30); setTimeout(function () { // Update Schlitzungswinkel images when Schlitzungsmuster changes if (typeof window.MX_reinitSchlitzungswinkel === 'function') window.MX_reinitSchlitzungswinkel(); }, 60); } } }); } else { $layer.data('mxSchlitzGroups', groups); syncSchlitzState($layer); } }); } window.MX_reinitSchlitzungsmuster = initSchlitzungsmusterLayer; // ============================================================ // SCHLITZUNGSWINKEL: Bildkacheln (G-CUT / X-CUT) // abhängig von aktiver Schlitzungsmuster-Line (G-LINE / S-LINE) // ============================================================ var SCHLITZWINKEL_IMAGES = { 'G-LINE': { '0': ASSET_BASE + 'Schlitzungswinkel/G-G-Cut.png', '60': ASSET_BASE + 'Schlitzungswinkel/G-X-Cut.png' }, 'S-LINE': { '0': ASSET_BASE + 'Schlitzungswinkel/S-G-Cut.png', '60': ASSET_BASE + 'Schlitzungswinkel/S-X-Cut.png' } }; var SCHLITZWINKEL_LABELS = { '0': 'G-CUT', '60': 'X-CUT' }; function getActiveSchlitzLine() { var $schlitzLayer = $('ul.layers > li.layers-list-item').filter(function () { return $.trim($(this).find('> .layer-item .layer-name').first().text()) === 'Schlitzungsmuster'; }).first(); if (!$schlitzLayer.length) return 'G-LINE'; var $active = $schlitzLayer.find('.layer_choices .choices-list li.choice.active, .layer_choices .choices-list li.choice.mxactive, .layer_choices .choices-list li.choice.selected').first(); if ($active.length) { var name = $.trim($active.find('.choice-name').text()).toUpperCase(); if (/S-?LINE|S\s*LINE/.test(name)) return 'S-LINE'; } return 'G-LINE'; } function getSchlitzwinkelImageSrc(angleKey, line) { var lineImages = SCHLITZWINKEL_IMAGES[line] || SCHLITZWINKEL_IMAGES['G-LINE']; return lineImages[angleKey] || ''; } function renderSchlitzungswinkelUI($layer, choices) { $layer.find('.mx-schlitzwinkel-container').remove(); var $container = $(''); var $tiles = $(''); var activeLine = getActiveSchlitzLine(); // Find exactly ONE active angleKey (aria-pressed first, then classes, then first enabled) var activeAngleKey = null; for (var ci = 0; ci < choices.length; ci++) { if (choices[ci].$element.find('button.choice-item[aria-pressed="true"]').length) { activeAngleKey = choices[ci].angleKey; break; } } if (activeAngleKey === null) { for (var ci2 = 0; ci2 < choices.length; ci2++) { var $el = choices[ci2].$element; if ($el.hasClass('active') || $el.hasClass('mxactive') || $el.hasClass('selected')) { activeAngleKey = choices[ci2].angleKey; break; } } } if (activeAngleKey === null && choices.length > 0) { activeAngleKey = choices[0].angleKey; } choices.forEach(function (choice) { var $tile = $('') .attr('data-angle', choice.angleKey); var imgSrc = getSchlitzwinkelImageSrc(choice.angleKey, activeLine); if (imgSrc) { $tile.append($('').attr('src', imgSrc).attr('alt', SCHLITZWINKEL_LABELS[choice.angleKey] || choice.angleKey).addClass('mx-schlitzwinkel-tile-img')); } var isActive = (choice.angleKey === activeAngleKey); var enabled = isChoiceEnabled(choice.$element); if (isActive) $tile.addClass('active'); if (!enabled) $tile.addClass('disabled'); $tile.data('originalChoice', choice.$element); $tiles.append($tile); }); $container.append($tiles); $layer.find('> .layer-item').after($container); $layer.data('mxSchlitzwinkelChoices', choices); } function syncSchlitzungswinkelImages($layer) { var activeLine = getActiveSchlitzLine(); $layer.find('.mx-schlitzwinkel-tile').each(function () { var $tile = $(this); var angleKey = $tile.attr('data-angle'); var $img = $tile.find('.mx-schlitzwinkel-tile-img'); var newSrc = getSchlitzwinkelImageSrc(angleKey, activeLine); if ($img.length && newSrc) $img.attr('src', newSrc); }); } function syncSchlitzungswinkelState($layer) { var choices = $layer.data('mxSchlitzwinkelChoices') || []; syncSchlitzungswinkelImages($layer); // Find exactly ONE active angleKey var activeAngleKey = null; for (var ci = 0; ci < choices.length; ci++) { if (choices[ci].$element.find('button.choice-item[aria-pressed="true"]').length) { activeAngleKey = choices[ci].angleKey; break; } } if (activeAngleKey === null) { for (var ci2 = 0; ci2 < choices.length; ci2++) { var $el = choices[ci2].$element; if ($el.hasClass('active') || $el.hasClass('mxactive') || $el.hasClass('selected')) { activeAngleKey = choices[ci2].angleKey; break; } } } $layer.find('.mx-schlitzwinkel-tile').each(function () { var $tile = $(this); var angleKey = $tile.attr('data-angle'); var choice = null; for (var i = 0; i < choices.length; i++) { if (choices[i].angleKey === angleKey) { choice = choices[i]; break; } } if (!choice) return; var enabled = isChoiceEnabled(choice.$element); $tile.toggleClass('active', angleKey === activeAngleKey); $tile.toggleClass('disabled', !enabled); }); } function initSchlitzungswinkelLayer() { $('ul.layers > li.layers-list-item').each(function () { var $layer = $(this); var name = $.trim($layer.find('> .layer-item .layer-name').first().text()); if (name !== 'Schlitzungswinkel') return; var alreadyInit = $layer.hasClass('mx-schlitzwinkel-layer'); $layer.addClass('mx-schlitzwinkel-layer'); // Hide original choices and range elements $layer.find('.layer_choices, .layer_choices .choices-list, .layer_choices ul, .layer_choices li.choice').css({ 'display': 'none', 'visibility': 'hidden', 'height': '0', 'overflow': 'hidden', 'position': 'absolute', 'left': '-9999px' }); $layer.find('.mx-range-wrap').hide(); var $choiceEls = $layer.find('.layer_choices .choices-list li.choice, .layer_choices ul li.choice'); if (!$choiceEls.length) return; var choices = []; $choiceEls.each(function () { var $li = $(this); var label = $.trim($li.find('.choice-name').text()); if (!label || label === '(NULL)' || label === 'NULL') return; var m = label.match(/(\d+)/); if (!m) return; var angleKey = m[1]; // '0' or '60' choices.push({ angleKey: angleKey, label: label, $element: $li }); }); if (!choices.length) return; if (!alreadyInit) { renderSchlitzungswinkelUI($layer, choices); $layer.on('click', '.mx-schlitzwinkel-tile:not(.disabled)', function () { var $tile = $(this); var $orig = $tile.data('originalChoice'); if ($orig && $orig.length) { var $btn = $orig.find('button.choice-item').first(); if ($btn.length) { // Immediately update visual active state $layer.find('.mx-schlitzwinkel-tile').removeClass('active'); $tile.addClass('active'); $btn.trigger('click'); setTimeout(function () { syncSchlitzungswinkelState($layer); }, 100); } } }); } else { $layer.data('mxSchlitzwinkelChoices', choices); syncSchlitzungswinkelState($layer); } }); } window.MX_reinitSchlitzungswinkel = initSchlitzungswinkelLayer; var _styledLayersInitialized = false; // ✅ NEU: Generische Radio-Button Konvertierung für Standard-Button-Layer // Alle Layer die nicht speziell behandelt werden bekommen den Radio-Button Stil // wie die Aufnahme-Sub-Options — mit fit-content Breite statt full-width. function initGenericRadioLayers() { var SKIP_LAYERS = [ 'Evolutionsstufe', 'Edition', 'Drehrichtung', 'Aufnahme', 'Schlitzungsmuster', 'Schlitzungswinkel', 'Performance-Boost', 'A-Edition', // Range-Slider Layer 'Körnung', 'Durchmesser', 'Länge', 'Breite', 'Bündeldurchmesser', 'Besatzneigung', 'Gesamthöhe', 'Schaftdurchmesser', 'Außendurchmesser', 'Borstendurchmesser', 'Körperdurchmesser', 'Motorwelle', 'A-Korn', 'Durchmesser Breite' ]; $('ul.layers > li.layers-list-item').each(function () { var $layer = $(this); var layerName = $.trim($layer.find('> .layer-item .layer-name').first().text()); var isKoernungRadio = layerName === 'Körnung' && isVliesklettscheibeProduct(); if (!layerName || (SKIP_LAYERS.indexOf(layerName) !== -1 && !isKoernungRadio)) return; if ($layer.hasClass('mx-generic-radio-layer')) return; if ($layer.hasClass('mx-range-layer')) return; // Slider-Layer überspringen (z.B. Bohrung bei Entgratwalze) if ($layer.hasClass('mx-besatz-tile-layer')) return; // Besatz → wird als Image-Tiles gerendert var $choicesWrapper = $layer.find('.layer_choices').first(); if (!$choicesWrapper.length) return; $layer.addClass('mx-generic-radio-layer'); var DROPDOWN_THRESHOLD = 5; // FIX-RADIO: Statt kompletten Rebuild bei jeder Mutation → // 1. Einmalig bauen (buildRadioGroup) // 2. Danach nur noch State synchronisieren (syncRadioGroup) // Das verhindert die Observer-Kaskade: rebuild → DOM-Change → Observer feuert → rebuild → ... function buildRadioGroup() { var $choices = $choicesWrapper.find('.choices-list li.choice'); if (!$choices.length) return; if ($choices.length >= DROPDOWN_THRESHOLD) { // Dropdown-Pfad: einmalig bauen, dann via syncRadioGroup updaten if ($choicesWrapper.find('.mx-generic-dropdown').length) return; // bereits gebaut $choicesWrapper.find('.mx-generic-radio-group').remove(); $choicesWrapper.find('.mx-generic-dropdown-wrap').remove(); var $wrap = $(''); var $select = $(''); var activeVal = ''; $select.on('mousedown focus', function () { $select.data('mx-open', true); }); $select.on('blur change', function () { $select.data('mx-open', false); }); $choices.each(function () { var $li = $(this); var $btn = $li.find('button.choice-item').first(); if (!$btn.length) return; var label = $.trim($li.find('.choice-name').first().text()); if (!label) return; var isDisabled = $li.hasClass('mx-disabled') || $li.hasClass('not-available') || $btn.prop('disabled') || $btn.attr('aria-disabled') === 'true'; var isActive = $li.hasClass('active') || $li.hasClass('selected') || $li.hasClass('mxactive') || $btn.attr('aria-pressed') === 'true'; var idx = $li.index(); var $opt = $('').val(idx).text(label).prop('disabled', isDisabled); if (isActive) { $opt.prop('selected', true); activeVal = String(idx); } $select.append($opt); }); if (!activeVal) { $select.prepend($('').text('— bitte wählen —')); } $select.on('change', function () { var selectedIdx = parseInt($(this).val(), 10); var $li = $choicesWrapper.find('.choices-list li.choice').eq(selectedIdx); $li.find('button.choice-item').first().trigger('click'); }); $wrap.append($select); $choicesWrapper.append($wrap); return; } // Radio-Pfad: einmalig bauen if ($choicesWrapper.find('.mx-generic-radio-group').length) return; // bereits gebaut $choicesWrapper.find('.mx-generic-radio-group').remove(); $choicesWrapper.find('.mx-generic-dropdown-wrap').remove(); var $group = $(''); var isFirst = true; $choices.each(function () { var $li = $(this); var $btn = $li.find('button.choice-item').first(); if (!$btn.length) return; var label = txt($li.find('.choice-name').first()); if (!label) label = $.trim($li.find('.choice-name').first().text()); if (!label) return; var isDisabled = $li.hasClass('mx-disabled') || $li.hasClass('not-available') || $btn.prop('disabled') || $btn.attr('aria-disabled') === 'true'; var isActive = $li.hasClass('active') || $li.hasClass('selected') || $li.hasClass('mxactive') || $btn.attr('aria-pressed') === 'true'; if (!isFirst) $group.append($('')); isFirst = false; var $dot = $(''); var translatedLabel = mxT(label); var $item = $('') .append($dot) .attr('data-mx-choice-label', label); if (!MX_IS_DE && translatedLabel !== label) { // FIX-VISIBILITY: Translation vorhanden → // Original mit mx-has-tl (wird per CSS display:none), .mx-tl Span sichtbar $item.append($('').text(label)); $item.append($('').text(translatedLabel)); } else { // FIX-VISIBILITY: Kein Translation → mx-show damit CSS-Verstecker nicht greift // (ohne mx-show wäre .mx-aufnahme-radio-label per CSS visibility:hidden) $item.append($('').text(label)); } if (isActive) $item.addClass('active'); if (isDisabled) $item.addClass('disabled').prop('disabled', true); $item.on('click', function () { if ($item.hasClass('disabled')) return; if(DBG) console.log('[MX-RADIO] User clicked:', label, '| layer:', layerName); $btn.trigger('click'); }); $group.append($item); }); $choicesWrapper.append($group); } // FIX-SYNC: Nur State (active/disabled) updaten, kein DOM-Rebuild. // Wird vom MutationObserver aufgerufen statt buildRadioGroup. function syncRadioGroup() { var $choices = $choicesWrapper.find('.choices-list li.choice'); // Dropdown-Pfad var $select = $choicesWrapper.find('.mx-generic-dropdown'); if ($select.length) { if ($select.data('mx-open')) return; // offen → nicht anfassen $choices.each(function () { var $li = $(this); var $btn = $li.find('button.choice-item').first(); var isDisabled = $li.hasClass('mx-disabled') || $li.hasClass('not-available') || $btn.prop('disabled') || $btn.attr('aria-disabled') === 'true'; var isActive = $li.hasClass('active') || $li.hasClass('selected') || $li.hasClass('mxactive') || $btn.attr('aria-pressed') === 'true'; var $opt = $select.find('option').eq($li.index()); $opt.prop('disabled', isDisabled); if (isActive) $select.val(String($li.index())); }); return; } // Radio-Pfad: nur active/disabled-Klassen der bestehenden Buttons anpassen var $radioItems = $choicesWrapper.find('.mx-generic-radio-group .mx-generic-radio-item'); if (!$radioItems.length) { // Noch nicht gebaut → einmalig bauen buildRadioGroup(); return; } $choices.each(function (idx) { var $li = $(this); var $btn = $li.find('button.choice-item').first(); var $item = $radioItems.eq(idx); if (!$item.length) return; var isDisabled = $li.hasClass('mx-disabled') || $li.hasClass('not-available') || $btn.prop('disabled') || $btn.attr('aria-disabled') === 'true'; var isActive = $li.hasClass('active') || $li.hasClass('selected') || $li.hasClass('mxactive') || $btn.attr('aria-pressed') === 'true'; var wasActive = $item.hasClass('active'); var wasDisabled = $item.hasClass('disabled'); // Nur ändern wenn wirklich anders → verhindert DOM-Thrashing if (isActive !== wasActive) { $item.toggleClass('active', isActive); } if (isDisabled !== wasDisabled) { $item.toggleClass('disabled', isDisabled).prop('disabled', isDisabled); } }); } buildRadioGroup(); var $ul = $choicesWrapper.find('.choices-list').first(); if ($ul.length) { // FIX-OBS: Observer ruft syncRadioGroup (kein Rebuild!) mit Debounce. // FIX-DESYNC: Waehrend applyAutoSelection (MX_SUPPRESS_AVAIL=true) wartet // der Observer bis die Batch-Phase vorbei ist, sonst wuerde er mit den // alten DOM-Klassen den gerade gesetzten aktiven State wieder ueberschreiben. var _radioSyncTimer = null; function scheduleSyncRadio() { if (_radioSyncTimer) clearTimeout(_radioSyncTimer); _radioSyncTimer = setTimeout(function () { if (MX_SUPPRESS_AVAIL) { scheduleSyncRadio(); // Batch noch aktiv -> nochmal warten return; } syncRadioGroup(); }, 30); } new MutationObserver(function (mutations) { scheduleSyncRadio(); }).observe($ul[0], { subtree: true, attributes: true, attributeFilter: ['class', 'disabled', 'aria-pressed', 'aria-disabled'] }); } }); } function initStyledLayers() { // ✅ FIX: Nur einmal ausführen — Klassen ändern sich nicht nach Init if (_styledLayersInitialized) return; var RED_BUTTON_LAYERS = [ 'Bohrung', 'Bohrungstyp', 'Schlitzungsmuster', 'Schlitzung', 'Besatzausführung', 'Reihen', 'A-Schlitzung', 'Besatz', 'A-Edition', 'Härte', 'Schleifvlies', 'Filzdichte', 'Gewinde', 'Flügel', 'Härte Puffer', 'Durchmesser Breite', 'Aufnehmbarer Durchmesser', 'Justierhilfe', 'Rastbolzen' ]; $('ul.layers > li.layers-list-item').each(function () { var $layer = $(this); var name = $.trim($layer.find('> .layer-item .layer-name').first().text()); if (RED_BUTTON_LAYERS.indexOf(name) !== -1) { $layer.addClass('mx-red-button-layer'); } else { $layer.removeClass('mx-red-button-layer'); } }); _styledLayersInitialized = true; } function mxLayerHasRealEnabledChoices($layer) { if (!$layer || !$layer.length) return false; var $choices = $layer.find('.layer_choices .choices-list li.choice'); if (!$choices.length) return false; var has = false; $choices.each(function () { var $li = $(this); var $btn = $li.find('button.choice-item').first(); if (!$btn.length) return; // disabled / not available -> zählt nicht if ($btn.prop('disabled') || $li.hasClass('mx-disabled') || $li.hasClass('not-available')) return; var label = $.trim($li.find('.choice-name').first().text() || ''); if (!label) return; var up = label.toUpperCase(); if (up === 'NULL' || up === '(NULL)') return; has = true; return false; // break each() }); return has; } function mxRangeLayerHasValues($layer) { if (!$layer || !$layer.length) return false; // ✅ FIX: Prüfe die ECHTEN enabled Choices, nicht veraltete Slider-Daten var hasEnabled = false; $layer.find('.layer_choices .choices-list li.choice').each(function () { var $li = $(this); var $btn = $li.find('button.choice-item').first(); if (!$btn.length) return; if ($btn.prop('disabled') || $li.hasClass('mx-disabled') || $li.hasClass('not-available')) return; var label = $.trim($li.find('.choice-name').first().text() || ''); if (!label) return; var up = label.toUpperCase(); if (up === 'NULL' || up === '(NULL)') return; hasEnabled = true; return false; }); return hasEnabled; } function mxHideLayerIfEmpty($layer) { if (!$layer || !$layer.length) return; // Never hide the first layer (it must always be visible & fully selectable) var __firstLayerId = mxGetFirstLayerId(); var __layerId = String($layer.attr('data-layer') || ''); if (!__layerId) { var __ul = $layer.find('.layer_choices .choices-list ul[data-layer-id]').first(); if (__ul.length) __layerId = String(__ul.attr('data-layer-id') || ''); } if (__firstLayerId && __layerId && String(__layerId) === String(__firstLayerId)) { $layer.show(); return; } // ✅ Range-Layer: Prüfe ob Slider Werte hat if ($layer.hasClass('mx-range-layer')) { if (!mxRangeLayerHasValues($layer)) { if ($layer.is(':visible')) { // Range zurücksetzen var $wrap = $layer.find('.mx-range-wrap').first(); if ($wrap.length) { var $range = $wrap.find('.mx-range-slider'); $range.val(0); var labels = $range.data('labels') || []; var values = $range.data('values') || []; $wrap.find('.mx-range-value').text(labels.length ? labels[0] : (values.length ? values[0] : '-')); } $layer.hide(); } } else { if (!$layer.is(':visible')) $layer.show(); } return; } // ✅ Choice-Layer: Prüfe ob enabled Choices existieren if (!mxLayerHasRealEnabledChoices($layer)) { if ($layer.is(':visible')) { // Selection UI leeren (damit kein "ghost selection" bleibt) $layer.find('.layer_choices li.choice') .removeClass('active mxactive selected') .find('button.choice-item').attr('aria-pressed', 'false'); $layer.find('> .layer-item .selected-choice').text(''); // ggf. Custom-Container entfernen (z.B. Aufnahme) $layer.next('.mx-aufnahme-container').remove(); $layer.hide(); } } else { if (!$layer.is(':visible')) $layer.show(); } } function mxHideAllEmptyLayers() { $('ul.layers > li.layers-list-item').each(function () { var $layer = $(this); // ✅ ALLE Layer-Typen prüfen (inkl. Range) mxHideLayerIfEmpty($layer); }); } function mxClearHiddenLayerSelections() { $('ul.layers > li.layers-list-item').each(function () { var $layer = $(this); if (!$layer.is(':visible')) { $layer.find('.layer_choices .choices-list li.choice') .removeClass('active mxactive selected') .find('button.choice-item') .attr('aria-pressed', 'false'); $layer.find('> .layer-item .selected-choice').text(''); // Cache wird NICHT mehr gelöscht - so funktioniert Cache richtig! // CACHED_SELECTED_OPTIONS = null; ❌ GELÖSCHT if (DBG) { var layerName = txt($layer.find('> .layer-item .layer-name').first()); } } }); } function initBesatzTiles() { var basePath = getBesatzBasePath(); if (!basePath) return; // Nur für Spiralrundbürste + Tellerbürste $('ul.layers > li.layers-list-item').each(function () { var $layer = $(this); var layerName = $.trim($layer.find('> .layer-item .layer-name').first().text()); if (layerName !== 'Besatz') return; $layer.addClass('mx-besatz-tile-layer'); var $choices = $layer.find('.layer_choices .choices-list li.choice'); if (!$choices.length) return; // Tiles bereits gebaut → nur Sync if ($layer.find('.mx-besatz-tiles').length) { syncBesatzTiles($layer); return; } var $tilesWrap = $(''); $choices.each(function () { var $choice = $(this); var $btn = $choice.find('button.choice-item').first(); var label = $.trim($choice.find('.choice-name').first().text()).toUpperCase(); if (!label || !$btn.length) return; var imgFile = BESATZ_IMAGE_MAP[label]; var isActive = $choice.hasClass('active') || $choice.hasClass('selected') || $btn.attr('aria-pressed') === 'true'; var isDisabled = $choice.hasClass('mx-disabled') || $choice.hasClass('not-available') || $btn.prop('disabled') || $btn.attr('aria-disabled') === 'true'; var imgHtml = imgFile ? '' : ''; var $tile = $('' + imgHtml + ''); $tile.data('choiceEl', $choice); $tile.on('click', function () { if ($(this).hasClass('disabled')) return; $(this).data('choiceEl').find('button.choice-item').first().trigger('click'); setTimeout(function () { syncBesatzTiles($layer); }, 30); }); $tilesWrap.append($tile); }); $layer.find('> .layer-item').after($tilesWrap); syncBesatzTiles($layer); // MutationObserver: Aktiv/Disabled-Status synchron halten var $ul = $layer.find('.layer_choices .choices-list').first(); if ($ul.length) { new MutationObserver(function () { syncBesatzTiles($layer); }) .observe($ul[0], { subtree: true, attributes: true, attributeFilter: ['class', 'disabled', 'aria-pressed', 'aria-disabled'] }); } }); } function syncBesatzTiles($layer) { if (!$layer || !$layer.length) return; $layer.find('.mx-besatz-tile').each(function () { var $tile = $(this); var $choice = $tile.data('choiceEl'); if (!$choice || !$choice.length) return; var $btn = $choice.find('button.choice-item').first(); var isActive = $choice.hasClass('active') || $choice.hasClass('selected') || $btn.attr('aria-pressed') === 'true'; var isDisabled = $choice.hasClass('mx-disabled') || $choice.hasClass('not-available') || $btn.prop('disabled') || $btn.attr('aria-disabled') === 'true'; $tile.toggleClass('active', isActive); $tile.toggleClass('disabled', isDisabled); }); } function initAllDesigns() { initEvolutionsstufeDesign(); initEditionDesign(); if (window.MX_Range && typeof window.MX_Range.initDesign === 'function') { window.MX_Range.initDesign(); } initPerformanceLayer(); initDrehrichtungLayer(); initAufnahmeLayer(); initSchlitzungsmusterLayer(); initSchlitzungswinkelLayer(); initStyledLayers(); initBesatzTiles(); // ✅ NEU: Besatz → Image-Tiles für Spiralrundbürste & Tellerbürste initGenericRadioLayers(); // ✅ NEU: Standard-Layer → Radio-Stil } function initWithRetry(maxAttempts, delay) { var attempts = 0; function tryInit() { attempts++; initAllDesigns(); if (attempts < maxAttempts) setTimeout(tryInit, delay); } tryInit(); } function updateHiddenConfigField(source, directConfig) { var config = []; if (directConfig && typeof directConfig === 'object' && Object.keys(directConfig).length > 0) { Object.keys(directConfig).forEach(function (layerName) { var choiceName = directConfig[layerName]; if (!choiceName) return; var $layer = $('ul.layers > li.layers-list-item').filter(function () { return txt($(this).find('> .layer-item .layer-name').first()) === layerName; }).first(); if (!$layer.length) return; var layerId = String($layer.attr('data-layer') || ''); if (!layerId) { var $ul = $layer.find('.layer_choices .choices-list ul[data-layer-id]').first(); if ($ul.length) layerId = String($ul.attr('data-layer-id') || ''); } if (!layerId) return; var $choice = $layer.find('.layer_choices .choices-list li.choice').filter(function () { var label = txt($(this).find('.choice-name').first()) || txt($(this)); return label === choiceName; }).first(); if (!$choice.length) return; var choiceId = ''; var $btn = $choice.find('button.choice-item').first(); if ($btn.length) { var ids = parseChoiceId($btn.attr('id')); if (ids) choiceId = ids.choiceId; } var sku = $choice.attr('data-sku') || ''; config.push({ layer_id: layerId, layer_name: layerName, choice_id: choiceId, choiceName: choiceName, name: choiceName, value: choiceName, sku: sku, is_choice: true }); }); } else { $('ul.layers > li.layers-list-item').each(function () { var $layer = $(this), layerName = txt($layer.find('> .layer-item .layer-name').first()); if (!layerName) return; var layerId = String($layer.attr('data-layer') || ''); if (!layerId) { var $ul = $layer.find('.layer_choices .choices-list ul[data-layer-id]').first(); if ($ul.length) layerId = String($ul.attr('data-layer-id') || ''); } // ✅ FIX: Range-Layer (Durchmesser, Körnung etc.) — Slider-Wert direkt lesen if ($layer.hasClass('mx-range-layer')) { var $rangeWrap = $layer.find('.mx-range-wrap').first(); if ($rangeWrap.length) { var $slider = $rangeWrap.find('.mx-range-slider'); var rangeValues = $slider.data('values') || []; var rangeLabels = $slider.data('labels') || []; var rangeIdx = parseInt($slider.val(), 10); if (!isNaN(rangeIdx) && rangeValues.length > 0) { if (rangeIdx < 0) rangeIdx = 0; if (rangeIdx >= rangeValues.length) rangeIdx = rangeValues.length - 1; // ✅ FIX: Nur den rohen Zahlenwert verwenden (ohne Einheit wie "mm"), // damit das Plugin die passende li.choice im DOM findet. var rangeLabel = String(rangeValues[rangeIdx]); // Finde die passende Choice für choice_id var rangeChoiceId = ''; var $rangeActive = $layer.find('.layer_choices .choices-list li.choice.mxactive').first(); if (!$rangeActive.length) $rangeActive = $layer.find('.layer_choices .choices-list li.choice.active').first(); if ($rangeActive.length) { var $rangeBtn = $rangeActive.find('button.choice-item').first(); if ($rangeBtn.length) { var rm = ($rangeBtn.attr('id') || '').match(/^choice_\d+_([A-Za-z0-9_-]+)$/); if (rm) rangeChoiceId = rm[1]; } } config.push({ layer_id: layerId, layer_name: layerName, choice_id: rangeChoiceId, choiceName: rangeLabel, name: rangeLabel, value: rangeLabel, sku: '', is_choice: true }); } } return; // nächste Layer } var $activeChoice = $layer.find('.layer_choices .choices-list li.choice.mxactive').first(); if (!$activeChoice.length) $activeChoice = $layer.find('.layer_choices .choices-list li.choice.selected.active').first(); if (!$activeChoice.length) $activeChoice = $layer.find('.layer_choices .choices-list li.choice.selected').first(); if (!$activeChoice.length) $activeChoice = $layer.find('.layer_choices .choices-list li.choice.active').first(); if ($activeChoice.length) { var choiceName = txt($activeChoice.find('.choice-name').first()) || txt($activeChoice); var $btn = $activeChoice.find('button.choice-item').first(), choiceId = ''; if ($btn.length) { var m = ($btn.attr('id') || '').match(/^choice_\d+_([A-Za-z0-9_-]+)$/); if (m) choiceId = m[1]; } config.push({ layer_id: layerId, layer_name: layerName, choice_id: choiceId, choiceName: choiceName, name: choiceName, value: choiceName, sku: $activeChoice.attr('data-sku') || '', is_choice: true }); } }); } var heroSelected = config.some(function (item) { return item.layer_name === 'Evolutionsstufe' && (item.choiceName || item.value || '').toString().toUpperCase() === 'HERO' || (item.choiceName || item.value || '').toString().toUpperCase() === 'HERO R2'; }); if (heroSelected) config = config.filter(function (item) { return item.layer_name !== 'Performance-Boost'; }); if (source === 'form-submit') { var rememberedConfig = Array.isArray(MX_LAST_WRITTEN_CONFIG) ? mxCloneConfig(MX_LAST_WRITTEN_CONFIG) : []; var existingHiddenConfig = mxReadHiddenConfigField(); var currentJson = JSON.stringify(config || []); var existingJson = JSON.stringify(existingHiddenConfig || []); var rememberedJson = JSON.stringify(rememberedConfig || []); if (!config.length && existingHiddenConfig.length) { config = mxCloneConfig(existingHiddenConfig); if(DBG) console.warn('[MX Config Field] form-submit fallback -> bestehendes hidden field wird verwendet (DOM leer)'); } else if (!config.length && rememberedConfig.length) { config = mxCloneConfig(rememberedConfig); if(DBG) console.warn('[MX Config Field] form-submit fallback -> zuletzt serialisierte Config wird verwendet (DOM leer)'); } else if (existingHiddenConfig.length && existingJson !== currentJson && existingJson === rememberedJson) { config = mxCloneConfig(existingHiddenConfig); if(DBG) console.warn('[MX Config Field] form-submit preserve -> hidden field bleibt maßgeblich (DOM/Plugin-State ist veraltet)'); } else if (rememberedConfig.length && rememberedJson !== currentJson && (MX_LAST_WRITTEN_CONFIG_SOURCE === 'click' || MX_LAST_WRITTEN_CONFIG_SOURCE === 'external')) { config = mxCloneConfig(rememberedConfig); if(DBG) console.warn('[MX Config Field] form-submit preserve -> letzte Interaktions-Config wird verwendet statt veraltetem DOM-State'); } } // --- DEBUG: updateHiddenConfigField --- if(DBG) console.group('[MX Config Field] updateHiddenConfigField | Quelle: ' + source); if(DBG) console.log('[MX Config Field] Config-Eintraege (' + config.length + '):', config.map(function(c) { return c.layer_name + '=' + c.choiceName; })); var drehEntry = config.filter(function(c){ return c.layer_name === 'Drehrichtung'; })[0]; var aufnahmeEntry = config.filter(function(c){ return c.layer_name === 'Aufnahme'; })[0]; if(DBG) console.log('[MX Config Field] Drehrichtung im Config:', drehEntry ? drehEntry.choiceName : '*** FEHLT ***'); if(DBG) console.log('[MX Config Field] Aufnahme im Config:', aufnahmeEntry ? aufnahmeEntry.choiceName : '*** FEHLT ***'); // Zeige was WIRKLICH aktiv im DOM ist fuer Drehrichtung var $drLayer = $('ul.layers > li.layers-list-item').filter(function() { return $.trim($(this).find('> .layer-item .layer-name').first().text()) === 'Drehrichtung'; }).first(); var $drActive = $drLayer.find('.layer_choices .choices-list li.choice.mxactive, .layer_choices .choices-list li.choice.active, .layer_choices .choices-list li.choice.selected').first(); if(DBG) console.log('[MX Config Field] Drehrichtung DOM aktiv (li.choice):', $drActive.length ? $.trim($drActive.find('.choice-name').text()) : '*** KEINE AKTIVE CHOICE ***'); if(DBG) console.log('[MX Config Field] Drehrichtung Tile aktiv:', $drLayer.find('.mx-dreh-tile.active').map(function(){ return $(this).text().trim(); }).get()); if(DBG) console.groupEnd(); if (!config.length) return false; var $form = $('form.cart'); if (!$form.length) return false; $form.find('input[name="pc_configurator_data"]').remove(); $('').attr('type', 'hidden').attr('name', 'pc_configurator_data').val(JSON.stringify(config)).appendTo($form); mxRememberSerializedConfig(config, source); return true; } window.updateHiddenConfigField = function () { updateHiddenConfigField('external'); }; function buildSelectedOptions() { var now = Date.now(); if (CACHED_SELECTED_OPTIONS && (now - CACHE_TIMESTAMP) < 100) { return CACHED_SELECTED_OPTIONS; } var selected = {}; $('ul.layers > li.layers-list-item').each(function () { var $layer = $(this), layerName = txt($layer.find('> .layer-item .layer-name').first()); if (!layerName) return; // ✅ FIX: Versteckte Layer nicht mitsenden (verhindert Ghost-Selections) if (!$layer.is(':visible')) return; var choiceName = ''; var $activeLi = $layer.find('.layer_choices .choices-list li.choice').filter(function () { var $btn = $(this).find('button.choice-item').first(); return $btn.length && $btn.attr('aria-pressed') === 'true'; }).last(); if (!$activeLi.length) $activeLi = $layer.find('.layer_choices .choices-list li.choice.active, .layer_choices .choices-list li.choice.mxactive, .layer_choices .choices-list li.choice.selected').last(); if ($activeLi.length) { var $nm = $activeLi.find('.choice-name').first(); choiceName = txt($nm.length ? $nm : $activeLi); } else { var labelText = txt($layer.find('> .layer-item .selected-choice').first()); if (labelText) choiceName = labelText; } if (choiceName) selected[layerName] = { choiceName: choiceName }; }); CACHED_SELECTED_OPTIONS = selected; CACHE_TIMESTAMP = now; return selected; } function buildAllChoicesMap() { var map = {}; $('ul.layers > li.layers-list-item').each(function () { var $layer = $(this), layerName = txt($layer.find('> .layer-item .layer-name').first()); if (!layerName) return; var layerId = String($layer.attr('data-layer') || ''); if (!layerId) { var $ul = $layer.find('.layer_choices .choices-list ul[data-layer-id]').first(); if ($ul.length) layerId = String($ul.attr('data-layer-id') || ''); } if (!layerId) return; var arr = []; $layer.find('.layer_choices .choices-list li.choice').each(function () { var $li = $(this), $btn = $li.find('button.choice-item').first(); if (!$btn.length) return; var ids = parseChoiceId($btn.attr('id')); var cid = ids ? ids.choiceId : String(($btn.attr('id') || '').replace(/^choice_\d+_/, '')); if (!cid) return; var name = txt($li.find('.choice-name').first()) || txt($btn); if (name) arr.push({ id: String(cid), name: name }); }); if (arr.length) map[layerName] = { layerId: String(layerId), choices: arr }; }); return map; } function applyInitialAvailableOptions() { var available = window.MX_INITIAL_AVAILABLE; if (!available || typeof available !== 'object') return; $('ul.layers > li.layers-list-item').each(function () { var $layer = $(this), layerName = txt($layer.find('> .layer-item .layer-name').first()); if (!layerName) return; var availableValues = available[layerName]; if (!availableValues || !Array.isArray(availableValues)) return; $layer.find('.layer_choices .choices-list li.choice').each(function () { var $li = $(this), $btn = $li.find('button.choice-item').first(); if (!$btn.length) return; var choiceName = txt($li.find('.choice-name').first()) || txt($li); if (availableValues.indexOf(choiceName) === -1) { $li.addClass('mx-disabled not-available'); $btn.prop('disabled', true).attr('aria-disabled', 'true').attr('tabindex', '-1'); } }); }); } var MX_USER_JUST_CLICKED = {}; function applyAutoSelection(autoSelection, options) { if (!autoSelection || typeof autoSelection !== 'object') return; options = options || {}; if(DBG) console.group('[MX AutoSelect] applyAutoSelection aufgerufen'); if(DBG) console.log('[MX AutoSelect] autoSelection vom Server:', JSON.parse(JSON.stringify(autoSelection))); if(DBG) console.log('[MX AutoSelect] MX_USER_JUST_CLICKED:', JSON.parse(JSON.stringify(MX_USER_JUST_CLICKED))); if(DBG) console.log('[MX AutoSelect] HAS_INTERACTED:', HAS_INTERACTED); var userSelections = buildSelectedOptions(); $('ul.layers > li.layers-list-item').each(function () { var $layer = $(this), layerName = txt($layer.find('> .layer-item .layer-name').first()); if (!layerName) return; var wanted = autoSelection[layerName]; if (!wanted) return; if(DBG) console.group('[MX AutoSelect] Layer: "' + layerName + '" -> gewuenscht: "' + wanted + '"'); if (!$layer.is(':visible')) { if(DBG) console.warn('[MX AutoSelect] SKIP - Layer ist nicht sichtbar (display:none)'); if(DBG) console.groupEnd(); return; } if (MX_USER_JUST_CLICKED[layerName]) { // Ausnahme: Wenn der Server fuer diesen Layer nur EINE Wahl moeglich macht, // muss die Auto-Selection trotz User-Klick durchgesetzt werden. var $enabledChoices = $layer.find('.layer_choices .choices-list li.choice').filter(function () { return !$(this).hasClass('mx-disabled') && !$(this).hasClass('not-available'); }); var isForced = ($enabledChoices.length === 1); if(DBG) console.warn('[MX AutoSelect] User hat diesen Layer geklickt (MX_USER_JUST_CLICKED=true)'); if(DBG) console.log('[MX AutoSelect] Enabled Choices Anzahl:', $enabledChoices.length, '-> isForced:', isForced); if ($enabledChoices.length) { var enabledLabels = []; $enabledChoices.each(function() { enabledLabels.push($.trim($(this).find('.choice-name').text())); }); if(DBG) console.log('[MX AutoSelect] Enabled Choices Labels:', enabledLabels); } if (!isForced) { if(DBG) console.warn('[MX AutoSelect] SKIP - User-Klick schuetzt diesen Layer (mehr als 1 Option verfuegbar)'); if(DBG) console.groupEnd(); return; } if(DBG) console.log('[MX AutoSelect] Trotz User-Klick: nur 1 Option -> Auto-Selection wird durchgesetzt'); } var shouldOverride = true; var allChoiceLabels = []; $layer.find('.layer_choices .choices-list li.choice').each(function() { var lbl = txt($(this).find('.choice-name').first()) || txt($(this)); allChoiceLabels.push('"' + lbl + '"'); }); if(DBG) console.log('[MX AutoSelect] Alle Choices im DOM:', allChoiceLabels.join(', ')); var $choice = $layer.find('.layer_choices .choices-list li.choice').filter(function () { var label = txt($(this).find('.choice-name').first()) || txt($(this)); function norm(s) { return (s || '').replace(/\s+/g, ' ').trim(); } var normalizedLabel = norm(label); var normalizedWanted = norm(wanted); var match = normalizedLabel === normalizedWanted; if (!match) { var caseNote = (normalizedLabel.toLowerCase() === normalizedWanted.toLowerCase()) ? ' <- NUR GROSSSCHREIBUNG!' : ''; if(DBG) console.log('[MX AutoSelect] Kein Match: DOM="' + normalizedLabel + '" vs. Server="' + normalizedWanted + '"' + caseNote); } return match; }).first(); // FIX: Numeric fallback for range slider layers ("100mm" vs "100") if (!$choice.length && window.MX_RANGE_LAYER_CONFIG && window.MX_RANGE_LAYER_CONFIG[layerName]) { if(DBG) console.log('[MX AutoSelect] Range-Slider Fallback wird versucht fuer "' + wanted + '"'); var wantedNum = parseFloat(String(wanted).replace(',', '.')); if (!isNaN(wantedNum)) { $choice = $layer.find('.layer_choices .choices-list li.choice').filter(function () { var label = txt($(this).find('.choice-name').first()) || txt($(this)); var labelNum = parseFloat(String(label).replace(',', '.')); return !isNaN(labelNum) && labelNum === wantedNum; }).first(); if ($choice.length) { if(DBG) console.log('[MX AutoSelect] Range-Fallback Match gefunden fuer Zahl:', wantedNum); } } } if ($choice.length) { var foundLabel = $.trim($choice.find('.choice-name').text()); if(DBG) console.log('[MX AutoSelect] MATCH gefunden! Choice-Label im DOM: "' + foundLabel + '"'); $layer.find('.layer_choices .choices-list li.choice') .removeClass('active mxactive selected') .find('button.choice-item').attr('aria-pressed', 'false'); $choice.addClass('active mxactive selected') .find('button.choice-item').attr('aria-pressed', 'true'); $layer.find('> .layer-item .selected-choice').text(wanted); if (layerName === 'Edition') { setTimeout(function () { updateEditionImages($layer); }, 10); } if (layerName === 'Drehrichtung') { if(DBG) console.log('[MX AutoSelect] Drehrichtung: syncDrehrichtungTiles wird in 10ms aufgerufen'); setTimeout(function () { syncDrehrichtungTiles($layer); if(DBG) console.log('[MX AutoSelect] Drehrichtung: syncDrehrichtungTiles ausgefuehrt. Aktive Tiles:', $layer.find('.mx-dreh-tile.active').map(function() { return $(this).text().trim(); }).get()); }, 10); } } else { console.error('[MX AutoSelect] KEIN MATCH fuer Layer "' + layerName + '" - Server will "' + wanted + '" aber kein passendes gefunden!'); if(DBG) console.log('[MX AutoSelect] Pr fe ob der Layer eine Custom-UI hat (Drehrichtung-Tiles, Aufnahme-Tabs)?'); } if(DBG) console.groupEnd(); }); if(DBG) console.groupEnd(); updateHiddenConfigField('applyAutoSelection', autoSelection); // ✅ FIX: Sync Aufnahme-Layer UI nach Auto-Selection // Wichtig nach Server-getriebener Auto-Selection damit Tabs korrekt angezeigt werden // Sofort syncen (für schnelle Fälle) UND nochmal nach 300ms (für variation.js AJAX-Responses) $('ul.layers > li.layers-list-item.mx-aufnahme-layer').each(function () { syncAufnahmeState($(this)); }); setTimeout(function () { $('ul.layers > li.layers-list-item.mx-aufnahme-layer').each(function () { syncAufnahmeState($(this)); }); }, 350); if (!window.MX_INITIAL_AUTO_SELECTION && !HAS_INTERACTED) { setTimeout(function () { var currentState = buildSelectedOptions(); var simplified = {}; for (var layer in currentState) { if (currentState[layer] && currentState[layer].choiceName) { simplified[layer] = currentState[layer].choiceName; } } window.MX_INITIAL_AUTO_SELECTION = simplified; }, 300); } if (!options.skipAjax) { MX_AVAIL_DIRTY = true; mxScheduleRecalc(); } } function resetAllChoicesEnabled() { $('ul.layers .layer_choices .choices-list li.choice').each(function () { var $li = $(this), $btn = $li.find('button.choice-item').first(); $li.removeClass('mx-disabled not-available'); if ($btn.length) $btn.prop('disabled', false).removeAttr('disabled').removeAttr('aria-disabled').removeAttr('tabindex'); }); } function disableChoicesUntilManufacturer() { $('ul.layers .layer_choices .choices-list li.choice').each(function () { var $li = $(this), $btn = $li.find('button.choice-item').first(); $li.addClass('mx-locked-before-manufacturer'); if ($btn.length) $btn.prop('disabled', true).attr('aria-disabled', 'true').attr('tabindex', '-1'); }); $('ul.layers > li.layers-list-item.mx-range-layer').each(function () { var $wrap = $(this).find('.mx-range-wrap').first(); if (!$wrap.length) return; $wrap.find('.mx-range-slider').prop('disabled', true).data('values', []).val(0); $wrap.find('.mx-range-value').text('-'); }); } function enableChoicesAfterManufacturer() { $('ul.layers .layer_choices .choices-list li.choice').each(function () { var $li = $(this), $btn = $li.find('button.choice-item').first(); $li.removeClass('mx-locked-before-manufacturer'); if ($btn.length) $btn.prop('disabled', false).removeAttr('aria-disabled').removeAttr('tabindex'); }); $('ul.layers .mx-range-slider').prop('disabled', false).removeClass('mx-range-disabled'); } function clearAllSelections() { $('ul.layers > li.layers-list-item').each(function () { var $layer = $(this); // In restore mode: do not auto-fill layers that were NOT part of the restored config. if (!HAS_INTERACTED && window.MX_RESTORE_MODE && Array.isArray(window.MX_RESTORE_KEYS)) { var layerName = $.trim($layer.find('> .layer-item .layer-name').first().text()); if (layerName && window.MX_RESTORE_KEYS.indexOf(layerName) === -1) { return; // keep empty (e.g., Länge = NULL for Klett) } } $layer.find('.layer_choices .choices-list li.choice').removeClass('active mxactive selected').find('button.choice-item').attr('aria-pressed', 'false'); $layer.find('> .layer-item .selected-choice').text(''); }); $('ul.layers > li.layers-list-item.mx-range-layer').each(function () { var $wrap = $(this).find('.mx-range-wrap').first(); if (!$wrap.length) return; var $range = $wrap.find('.mx-range-slider'), values = $range.data('values') || [], labels = $range.data('labels') || []; $range.val(0); $wrap.find('.mx-range-value').text(labels.length ? labels[0] : (values.length ? values[0] : '-')); }); } function normalizeLayerUIFromLabels() { $('ul.layers > li.layers-list-item').each(function () { var $layer = $(this), selectedLabel = txt($layer.find('> .layer-item .selected-choice').first()); if (!selectedLabel) return; var $match = null; $layer.find('.layer_choices .choices-list li.choice').each(function () { var label = txt($(this).find('.choice-name').first()) || txt($(this)); if (label === selectedLabel) { $match = $(this); return false; } }); if ($match && $match.length) { $layer.find('.layer_choices .choices-list li.choice').removeClass('active mxactive selected').find('button.choice-item').attr('aria-pressed', 'false'); $match.addClass('active mxactive selected').find('button.choice-item').attr('aria-pressed', 'true'); } }); } function mxClearInvalidSelectionsFromAvailability(availMap) { if (!availMap || typeof availMap !== 'object') return; document.querySelectorAll('ul.layers > li.layers-list-item').forEach(layer => { const activeBtn = layer.querySelector('li.choice button.choice-item[aria-pressed="true"]'); if (!activeBtn) return; const m = (activeBtn.id || '').match(/^choice_(\d+)_([A-Za-z0-9_-]+)$/); if (!m) return; const layerId = m[1]; const choiceId = m[2]; if (availMap[layerId] && availMap[layerId][choiceId] === false) { layer.querySelectorAll('li.choice').forEach(li => li.classList.remove('active', 'mxactive', 'selected') ); layer.querySelectorAll('button.choice-item').forEach(btn => btn.setAttribute('aria-pressed', 'false') ); const sel = layer.querySelector('.selected-choice'); if (sel) sel.textContent = ''; } }); } function applyChoiceAvailability(availableMap) { var selectionChanged = false; var updates = []; var firstLayerId = mxGetFirstLayerId(); $('ul.layers .layer_choices .choices-list li.choice').each(function () { var $li = $(this); var $btn = $li.find('button.choice-item').first(); if (!$btn.length) return; var ids = parseChoiceId($btn.attr('id')); if (!ids) return; var layerId = String(ids.layerId); var choiceId = String(ids.choiceId); var layerMap = availableMap[layerId]; var isAvail = true; // First layer is always selectable (unfiltered), even if current selection combination yields no matches. // ✅ FIX: PHP liefert jetzt disjunktive Facetten → erste Layer korrekt gefiltert. // Für __ALL__ (kein Hersteller) → komplett ungefiltert (alle Werte zeigen). // Für spezifischen Hersteller → PHP-Facette nutzen (zeigt alle Hersteller-Werte). if (firstLayerId && layerId === String(firstLayerId)) { var curManu = mxGetCurrentManufacturer(); var isAll = !curManu || curManu === '__ALL__'; if (isAll) { layerMap = null; isAvail = true; } // Für spezifischen Hersteller: layerMap normal nutzen (disjunktiv vom Server) } if (layerMap) { if (layerMap.hasOwnProperty(choiceId)) { isAvail = !!layerMap[choiceId]; } else { isAvail = false; } } var $layer = $li.closest('li.layers-list-item'); var layerName = txt($layer.find('> .layer-item .layer-name').first()); var choiceLabel = txt($li.find('.choice-name').first()); updates.push({ $li: $li, $btn: $btn, isAvail: isAvail }); }); updates.forEach(function (u) { // ✅ FIX: Dirty-checking — nur DOM ändern wenn sich State wirklich ändert var isCurrentlyDisabled = u.$li.hasClass('mx-disabled'); if (!u.isAvail && !isCurrentlyDisabled) { // Muss disabled werden (war vorher enabled) u.$li.addClass('mx-disabled not-available').removeClass('active mxactive selected'); u.$btn .prop('disabled', true) .attr('aria-disabled', 'true') .attr('tabindex', '-1') .attr('aria-pressed', 'false'); } else if (u.isAvail && isCurrentlyDisabled) { // Muss enabled werden (war vorher disabled) u.$li.removeClass('mx-disabled not-available'); u.$btn .prop('disabled', false) .removeAttr('disabled') .removeAttr('aria-disabled') .removeAttr('tabindex'); } // else: Zustand unverändert → kein DOM-Update }); $('ul.layers > li.layers-list-item:visible').each(function () { var $layer = $(this); // In restore mode: do not auto-fill layers that were NOT part of the restored config. if (!HAS_INTERACTED && window.MX_RESTORE_MODE && Array.isArray(window.MX_RESTORE_KEYS)) { var layerName = $.trim($layer.find('> .layer-item .layer-name').first().text()); if (layerName && window.MX_RESTORE_KEYS.indexOf(layerName) === -1) { return; // keep empty (e.g., Länge = NULL for Klett) } } var $active = $layer.find('.layer_choices .choices-list li.choice.active, .layer_choices .choices-list li.choice.mxactive, .layer_choices .choices-list li.choice.selected').first(); var activeIsDisabled = false; if ($active.length) { activeIsDisabled = $active.find('button.choice-item').prop('disabled') || $active.hasClass('mx-disabled') || $active.hasClass('not-available'); } if (!$active.length || activeIsDisabled) { // ✅ FIX: Only mark as changed if there WAS an active selection before var hadActiveSelection = $layer.find('.layer_choices .choices-list li.choice.active, .layer_choices .choices-list li.choice.mxactive, .layer_choices .choices-list li.choice.selected').length > 0; if (hadActiveSelection) { // NICHT automatisch erste Option wählen $layer.find('.layer_choices .choices-list li.choice') .removeClass('active mxactive selected') .find('button.choice-item').attr('aria-pressed', 'false'); $layer.find('> .layer-item .selected-choice').text(''); selectionChanged = true; // Nur wenn wirklich etwas geändert wurde! } } }); $('ul.layers > li.layers-list-item.mx-range-layer').each(function () { if (window.MX_Range && typeof window.MX_Range.rebuildForLayer === 'function') { window.MX_Range.rebuildForLayer($(this)); } }); // ✅ FIX: Sync Aufnahme-Layer UI nach Availability-Updates // Ohne diesen Call bleiben die Custom-Tabs im alten State, auch wenn die Original-Choices updated wurden $('ul.layers > li.layers-list-item.mx-aufnahme-layer').each(function () { syncAufnahmeState($(this)); }); if (selectionChanged && !MX_AVAILABILITY_REENTRY_GUARD) { MX_AVAILABILITY_REENTRY_GUARD = true; MX_AVAIL_DIRTY = true; mxScheduleRecalc(); setTimeout(function () { MX_AVAILABILITY_REENTRY_GUARD = false; }, 0); } } function refreshEditionUI() { initEditionDesign(); $('ul.layers > li.layers-list-item').each(function () { var $layer = $(this); if ($.trim($layer.find('> .layer-item .layer-name').first().text()) === 'Edition') { updateEditionImages($layer); } }); } var debouncedSendAvailability = debounce(function (force, ignoreSelections, callback) { _sendAvailabilityCore(force, ignoreSelections, callback); }, 120); // ← PERF-OPT: 120ms für User-Interaktionen function sendAvailability(force, ignoreSelections, callback) { if (MX_SUPPRESS_AVAIL) { MX_AVAIL_DIRTY = true; mxLog('sendAvailability suppressed -> marked dirty', { force: !!force, ignoreSelections: !!ignoreSelections }); MX_PERF.skippedAjax('suppressed (force=' + !!force + ', ignore=' + !!ignoreSelections + ')'); if (typeof callback === 'function') callback(); return; } MX_PERF.mark('sendAvailability(force=' + !!force + ',ignore=' + !!ignoreSelections + ')'); mxLog('sendAvailability (debounced)', { force: !!force, ignoreSelections: !!ignoreSelections }); debouncedSendAvailability(force, ignoreSelections, callback); } function _sendAvailabilityCore(force, ignoreSelections, callback) { var productId = getProductId(); if (!productId) { markConfiguratorReady(); if (typeof callback === 'function') callback(); return; } var manufacturer = normalizeManufacturerValue(getCookie('mx_selected_manufacturer') || ''); var hasManufacturer = !!manufacturer; var selectedOptions = (!hasManufacturer || ignoreSelections) ? {} : buildSelectedOptions(); if (!HAS_INTERACTED && window.MX_RESTORE_MODE && Array.isArray(window.MX_RESTORE_KEYS)) { var filtered = {}; window.MX_RESTORE_KEYS.forEach(function (k) { if (selectedOptions[k]) { filtered[k] = selectedOptions[k]; } }); selectedOptions = filtered; } var ctx = { product_id: productId, manufacturer: manufacturer || '(none)', hasManufacturer: hasManufacturer, force: !!force, ignoreSelections: !!ignoreSelections, selectedKeys: Object.keys(selectedOptions || {}) }; if (!Object.keys(selectedOptions).length && !hasManufacturer && !force) { resetAllChoicesEnabled(); markConfiguratorReady(); if (typeof callback === 'function') callback(); return; } var allChoices = (MX_ALL_CHOICES_CACHE || (MX_ALL_CHOICES_CACHE = buildAllChoicesMap())); var currentEvo = selectedOptions['Evolutionsstufe'] && selectedOptions['Evolutionsstufe'].choiceName ? selectedOptions['Evolutionsstufe'].choiceName.toUpperCase() : ''; if (currentEvo === 'HERO' || currentEvo === 'HERO R2') { delete selectedOptions['Performance-Boost']; delete allChoices['Performance-Boost']; } var seq = ++MX_AVAIL_SEQ; ctx.seq = seq; function handleAvail(resp) { if (seq !== MX_AVAIL_SEQ) { return; } mxLog('Availability response DONE', { seq: seq, success: !!(resp && resp.success), hasData: !!(resp && resp.data) }); if (!resp || !resp.success || !resp.data) { MX_PENDING_VARIANT_IMAGE = null; resetAllChoicesEnabled(); markConfiguratorReady(); if (typeof callback === 'function') callback(); return; } var data = resp.data; mxLog('Data summary', { seq: seq, matching_count: data ? data.matching_count : undefined, has_available_options: !!(data && data.available_options && Object.keys(data.available_options).length), has_auto_selection: !!(data && data.auto_selection), pk: data && data.variant_image ? data.variant_image.pk : null }); if (data && data.perf && data.perf.marks_ms) { try { if(DBG) if (console.table) console.table(data.perf.marks_ms); } catch (e) { } } window.MX_LAST_CATEGORIES = Array.isArray(data.categories) ? data.categories : []; if (data.product_title) { window.MX_PRODUCT_TITLE = data.product_title; } applyEntgrattellerLayerRules(); MX_PENDING_VARIANT_IMAGE = (data && data.variant_image && data.variant_image.url) ? data.variant_image : null; if (data.nearby_images && Array.isArray(data.nearby_images)) { data.nearby_images.forEach(function (url) { preloadImage(url); }); } var matchCount = data.matching_count || 0; if (matchCount > 0) { // FIX-4: Nur resetten wenn kein User-Klick gerade aktiv ist. // Vorher: Reset bei jedem erfolgreichen AJAX-Response → applyAutoSelection // konnte die User-Auswahl im nächsten Cycle überschreiben. if (!HAS_INTERACTED) { MX_USER_JUST_CLICKED = {}; window.MX_LAST_CLICKED_LAYER = ''; } else { } } // ✅ FIX: Prüfe ob auto_selection ein Objekt mit Keys ist (nicht leeres Array []) var hasValidAutoSelection = data.auto_selection && typeof data.auto_selection === 'object' && !Array.isArray(data.auto_selection) && Object.keys(data.auto_selection).length > 0; if (matchCount === 0 && hasValidAutoSelection) { if (!MX_APPLYING_FALLBACK) { MX_APPLYING_FALLBACK = true; window.MX_LAST_AUTO_SELECTION = data.auto_selection; $('ul.layers > li.layers-list-item').each(function () { var $l = $(this); var ln = txt($l.find('> .layer-item .layer-name').first()); if (!ln) return; if (!data.auto_selection.hasOwnProperty(ln)) { $l.find('.layer_choices .choices-list li.choice') .removeClass('active mxactive selected') .find('button.choice-item').attr('aria-pressed', 'false'); $l.find('> .layer-item .selected-choice').text(''); } }); CACHED_SELECTED_OPTIONS = null; CACHE_TIMESTAMP = 0; // ✅ FIX: Versteckte Layer die in auto_selection vorkommen wieder zeigen $('ul.layers > li.layers-list-item').each(function () { var $l = $(this); if ($l.is(':visible')) return; var ln = txt($l.find('> .layer-item .layer-name').first()); if (!ln) return; if (data.auto_selection.hasOwnProperty(ln)) { $l.show(); } }); mxBeginBatchAvailability(); applyAutoSelection(data.auto_selection, { skipAjax: true }); // ✅ Wenn resolved_data vorhanden → kein zweiter AJAX nötig! var resolved = data.resolved_data; if (resolved && resolved.matching_count > 0) { setTimeout(function () { var $editionLayer = $('ul.layers > li.layers-list-item.mx-edition-layer'); if ($editionLayer.length) { updateEditionImages($editionLayer); ensureValidEditionSelection($editionLayer); } // Bild + TechDetails direkt aus resolved_data if (resolved.variant_image && resolved.variant_image.url) { MX_PENDING_VARIANT_IMAGE = resolved.variant_image; updateVariantImage(resolved.variant_image); } if (resolved.technical_details) { updateTechnicalDetails(resolved.technical_details); } // Available options aus resolved_data anwenden if (resolved.available_options) { mxClearInvalidSelectionsFromAvailability(resolved.available_options); applyChoiceAvailability(resolved.available_options); mxHideAllEmptyLayers(); mxClearHiddenLayerSelections(); // ✅ FIX: applyChoiceAvailability kann active-Klassen entfernen wenn // eine Choice als unavailable markiert wird. Danach nochmal autoSelection // anwenden damit der visuelle Zustand (roter Punkt) korrekt bleibt. applyAutoSelection(data.auto_selection, { skipAjax: true }); // ✅ FIX: Custom-UIs (Schlitzungswinkel, Schlitzungsmuster) nach // resolved_data sync aktualisieren — sonst bleibt X-CUT visuell disabled // obwohl das hidden bereits auf enabled gesetzt wurde. if (typeof window.MX_reinitSchlitzungswinkel === 'function') { setTimeout(window.MX_reinitSchlitzungswinkel, 0); } if (typeof window.MX_reinitSchlitzungsmuster === 'function') { setTimeout(window.MX_reinitSchlitzungsmuster, 0); } } // ✅ FIX: Range-Slider nach AJAX/applyAutoSelection neu synchronisieren. // initSliders() läuft VOR dem AJAX-Callback → Slider zeigt korrekt 125mm. // Aber applyChoiceAvailability() + applyAutoSelection() ändern danach die // DOM-Choices (active-Klassen), ohne den Slider neu zu bauen → fällt auf // Index 0 (= kleinster Wert, z.B. 115mm) zurück. // Lösung: rebuildRangeForLayer() für alle Range-Layer nochmal aufrufen // nachdem der AJAX-Callback alle Choices korrekt gesetzt hat. if (window.MX_Range && typeof window.MX_Range.rebuildForLayer === 'function') { $('ul.layers > li.layers-list-item.mx-range-layer').each(function () { window.MX_Range.rebuildForLayer($(this)); }); } initStyledLayers(); initPerformanceLayer(); normalizeLayerUIFromLabels(); MX_USER_JUST_CLICKED = {}; window.MX_LAST_CLICKED_LAYER = ''; // Batch beenden OHNE neuen AJAX-Request MX_SUPPRESS_AVAIL = false; MX_AVAIL_DIRTY = false; MX_APPLYING_FALLBACK = false; MX_PENDING_VARIANT_IMAGE = null; markConfiguratorReady(); }, 0); // Variant-Text anwenden (function applyResolvedDesc(rd) { var panel = document.querySelector('.mx-desc-panel[data-panel="description"]'); if (!panel) return; if (!panel.dataset.wcFallback) panel.dataset.wcFallback = panel.innerHTML; var html = rd && rd.variant_texts && rd.variant_texts.description_html ? rd.variant_texts.description_html : ''; panel.innerHTML = html || panel.dataset.wcFallback; })(resolved); markConfiguratorReady(); if (typeof callback === 'function') callback(); return; } // Fallback: kein resolved_data → normaler Weg (zweiter AJAX) setTimeout(function () { var $editionLayer = $('ul.layers > li.layers-list-item.mx-edition-layer'); if ($editionLayer.length) { updateEditionImages($editionLayer); ensureValidEditionSelection($editionLayer); } MX_AVAIL_DIRTY = true; mxEndBatchAvailability(function () { MX_APPLYING_FALLBACK = false; }); }, 0); markConfiguratorReady(); if (typeof callback === 'function') callback(); return; } // ✅ FIX: Fallback läuft bereits → NICHT zum destruktiven Pfad durchfallen! markConfiguratorReady(); if (typeof callback === 'function') callback(); return; } (function applyVariantDescription(data) { const panel = document.querySelector('.mx-desc-panel[data-panel="description"]'); if (!panel) return; // wcFallback = Original WC-Produktbeschreibung (einmalig beim ersten Laden gespeichert) if (!panel.dataset.wcFallback) { panel.dataset.wcFallback = panel.innerHTML; } // ✅ SPRACH-FIX: VT hat korrekte Sprache (Key enthält _lang), AJAX-Transient kann // veralteten DE-Text zurückgeben. Wenn VT bereits geladen → deren Text bevorzugen. if (window.MX_VT && window.MX_VT.isReady()) { var vtRows = window.MX_VT.getRows(); if (vtRows && vtRows.length > 0) { var pk = data && data.variant_image && data.variant_image.pk ? data.variant_image.pk : null; var vtDesc = null; if (pk) { for (var ri = 0; ri < vtRows.length; ri++) { if (parseInt(vtRows[ri]._pk, 10) === pk && vtRows[ri]._desc_html) { vtDesc = vtRows[ri]._desc_html; break; } } } if (!vtDesc) { for (var rj = 0; rj < vtRows.length; rj++) { if (vtRows[rj]._desc_html) { vtDesc = vtRows[rj]._desc_html; break; } } } if (vtDesc) { panel.innerHTML = vtDesc; return; } } } const html = data && data.variant_texts && data.variant_texts.description_html ? data.variant_texts.description_html : ''; if (html) { panel.innerHTML = html; } })(data); var availMap = data.available_options || {}; if (!hasManufacturer) { if (data.technical_details) { updateTechnicalDetails(data.technical_details); } markConfiguratorReady(); if (typeof callback === 'function') callback(); return; } if (matchCount === 0 && !objSize(availMap)) { if (data.technical_details) { updateTechnicalDetails(data.technical_details); } MX_PENDING_VARIANT_IMAGE = null; resetAllChoicesEnabled(); markConfiguratorReady(); if (typeof callback === 'function') callback(); return; } mxClearInvalidSelectionsFromAvailability(availMap); applyChoiceAvailability(availMap); $('ul.layers > li.layers-list-item.mx-drehrichtung-layer').each(function () { syncDrehrichtungTiles($(this)); }); mxHideAllEmptyLayers(); mxClearHiddenLayerSelections(); setTimeout(function () { var $editionLayer = $('ul.layers > li.layers-list-item.mx-edition-layer'); if ($editionLayer.length) { updateEditionImages($editionLayer); ensureValidEditionSelection($editionLayer); } }, 50); initStyledLayers(); initPerformanceLayer(); if (force && !HAS_INTERACTED && hasManufacturer && data.auto_selection && typeof data.auto_selection === 'object') { window.MX_LAST_AUTO_SELECTION = data.auto_selection; if (!window.MX_HAS_INITIAL_CONFIG) { applyAutoSelection(data.auto_selection); } else if (window.MX_INITIAL_CONFIG) { updateHiddenConfigField('InitialConfig', window.MX_INITIAL_CONFIG); } } // ✅ FIX: Nach Fallback-Rückkehr (matchCount > 0) Layer die vorher versteckt wurden // aber jetzt wieder sichtbar sind und keine Auswahl haben → aus auto_selection re-selektieren. // Betrifft z.B. "Aufnahme" und "Reihen" die bei Durchmesser=410 versteckt wurden, // aber bei Durchmesser=115 wieder sichtbar sind. if (matchCount > 0 && data.auto_selection && typeof data.auto_selection === 'object' && !Array.isArray(data.auto_selection) && Object.keys(data.auto_selection).length > 0) { var selToRestore = data.auto_selection; var needsRestore = false; $('ul.layers > li.layers-list-item').each(function () { var $layer = $(this); if (!$layer.is(':visible')) return; // nur sichtbare Layer var ln = txt($layer.find('> .layer-item .layer-name').first()); if (!ln) return; if (!selToRestore.hasOwnProperty(ln)) return; // Hat dieser Layer eine aktive Auswahl? var hasActive = $layer.find('.layer_choices .choices-list li.choice.active, .layer_choices .choices-list li.choice.mxactive, .layer_choices .choices-list li.choice.selected').length > 0; if (!hasActive) { needsRestore = true; } }); if (needsRestore) { window.MX_LAST_AUTO_SELECTION = selToRestore; mxBeginBatchAvailability(); applyAutoSelection(selToRestore, { skipAjax: true }); CACHED_SELECTED_OPTIONS = null; CACHE_TIMESTAMP = 0; MX_SUPPRESS_AVAIL = false; MX_AVAIL_DIRTY = false; } } // ✅ FIX: 200ms statt 10ms – variation.js feuert nach AJAX-Response removeClass auf Choices. // Zusätzlich: zweiter syncAufnahmeState nach 450ms fängt späte AJAX-Responses ab. if (typeof window.MX_reinitAufnahme === 'function') { setTimeout(window.MX_reinitAufnahme, 200); setTimeout(function () { $('ul.layers > li.layers-list-item.mx-aufnahme-layer').each(function () { syncAufnahmeState($(this)); }); }, 450); } if (typeof window.MX_reinitSchlitzungsmuster === 'function') setTimeout(window.MX_reinitSchlitzungsmuster, 10); if (typeof window.MX_reinitSchlitzungswinkel === 'function') setTimeout(window.MX_reinitSchlitzungswinkel, 50); setTimeout(initGenericRadioLayers, 60); // ✅ NEU: Radio-Layer nach Availability-Update setTimeout(initBesatzTiles, 60); // ✅ NEU: Besatz-Tiles nach Availability-Update // ✅ FIX: Range-Slider nach AJAX-Callback neu synchronisieren. // initSliders() lief bereits VOR dem AJAX → Slider zeigte korrekt 125mm. // applyChoiceAvailability() + applyAutoSelection() ändern danach die DOM-Choices // ohne den Slider zu aktualisieren → er fällt auf Index 0 (z.B. 115mm) zurück. if (window.MX_Range && typeof window.MX_Range.rebuildForLayer === 'function') { $('ul.layers > li.layers-list-item.mx-range-layer').each(function () { window.MX_Range.rebuildForLayer($(this)); }); } normalizeLayerUIFromLabels(); if (MX_PENDING_VARIANT_IMAGE && matchCount > 0) { updateVariantImage(MX_PENDING_VARIANT_IMAGE); } // ✅ FIX: Technical details should ALWAYS be updated if available (not just when variant image pending) if (data.technical_details) { updateTechnicalDetails(data.technical_details); } MX_PENDING_VARIANT_IMAGE = null; MX_HERO_LOCK_ACTIVE = false; markConfiguratorReady(); // ✅ MX Translation: DOM nach Availability-Update übersetzen setTimeout(mxTranslateDom, 80); if (typeof callback === 'function') callback(); } var cacheKey = mxMakeAvailCacheKey(productId, manufacturer, selectedOptions, !!ignoreSelections, !!ctx.heroOnly); // ✅ NEU: Variant Table → lokales Matching (0ms statt ~400ms) if (window.MX_VT && window.MX_VT.isReady() && hasManufacturer && !ignoreSelections) { var localResp = window.MX_VT.match({ selectedOptions: selectedOptions, manufacturer: manufacturer, allChoices: allChoices, clickedLayer: window.MX_LAST_CLICKED_LAYER || '', productId: productId }); if (localResp) { // _desc_html ist direkt in der Variantentabelle → immer vollständig lokal, kein AJAX nötig mxLog('VT local match (no AJAX)', { seq: seq, matchCount: localResp.data.matching_count }); MX_PERF.skippedAjax('VT local match (seq ' + seq + ', ' + localResp.data.matching_count + ' matches)'); mxCacheSet(cacheKey, localResp); handleAvail(localResp); return; } } var cachedResp = mxCacheGet(cacheKey); if (cachedResp) { mxLog('Availability cache HIT', { seq: seq }); MX_PERF.skippedAjax('client-cache HIT (seq ' + seq + ')'); handleAvail(cachedResp); return; } mxLog('Availability request START', ctx); MX_PERF.mark('avail-ajax-' + seq); if (MX_AVAIL_XHR && MX_AVAIL_XHR.readyState !== 4) { try { MX_AVAIL_XHR.abort(); } catch (e) { } mxLog('Aborted previous availability XHR'); } var _ajaxPayload = { action: 'mx_pc_check_availability', nonce: mxPcConditionsAjax.nonce, product_id: productId, selected_options: JSON.stringify(selectedOptions), all_choices: JSON.stringify(allChoices), manufacturer: manufacturer, clicked_layer: window.MX_LAST_CLICKED_LAYER || '', category_label: (mxPcConditionsAjax.category_label || ''), mx_lang: (document.documentElement.getAttribute('lang') || 'de').substring(0, 2).toLowerCase() }; var _perfEntry = MX_PERF.ajax('check_availability (seq ' + seq + ')', (mxPcConditionsAjax.endpoint_url || mxPcConditionsAjax.ajax_url), Math.round(JSON.stringify(_ajaxPayload).length / 1024) + 'KB'); MX_AVAIL_XHR = $.ajax({ url: (mxPcConditionsAjax.endpoint_url || mxPcConditionsAjax.ajax_url), method: 'POST', dataType: 'json', data: _ajaxPayload }).done(function (resp) { MX_PERF.ajaxDone(_perfEntry, resp ? Math.round(JSON.stringify(resp).length / 1024) + 'KB' : '?'); if (resp && resp.success && resp.data) { mxCacheSet(cacheKey, resp); } handleAvail(resp); }).fail(function (xhr, status) { if (status === 'abort') { MX_PERF.ajaxDone(_perfEntry, 'ABORTED'); return; } MX_PERF.ajaxDone(_perfEntry, 'FAIL'); if(DBG) console.error('[DEBUG] AJAX Call Failed!'); resetAllChoicesEnabled(); markConfiguratorReady(); if (typeof callback === 'function') callback(); }); } function broadcastManufacturerChange(value, oldMfr) { if (window.MX_VT) { var pid = getProductId(); var newMfr = value || mxGetCurrentManufacturer() || '__ALL__'; var loadedMfr = window.MX_VT.loadedMfr ? window.MX_VT.loadedMfr() : null; // Hersteller hat gewechselt → VT invalidieren und neu laden if (pid && loadedMfr !== null && loadedMfr !== newMfr) { window.MX_VT.invalidate(pid, loadedMfr); } // VT (noch) nicht geladen → mit aktuellem Hersteller laden if (pid && !window.MX_VT.isReady()) { window.MX_VT.load(pid, newMfr); } } } var MX_MANUFACTURERS_LOADED = false; var MX_MANUFACTURERS_LOADING = false; function loadManufacturersForCategory(forceReload) { var productId = getProductId(); if (!productId) return; // ─── PERF-OPT: Nicht doppelt laden ─── if (!forceReload && (MX_MANUFACTURERS_LOADED || MX_MANUFACTURERS_LOADING)) { MX_PERF.skippedAjax('get_manufacturers already loaded/loading'); return; } MX_MANUFACTURERS_LOADING = true; var _mfrPerfEntry = MX_PERF.ajax('get_manufacturers', (mxPcConditionsAjax.endpoint_url || mxPcConditionsAjax.ajax_url), '~1KB'); $.ajax({ url: (mxPcConditionsAjax.endpoint_url || mxPcConditionsAjax.ajax_url), method: 'POST', dataType: 'json', data: { action: 'mx_pc_get_manufacturers', nonce: mxPcConditionsAjax.nonce, product_id: productId, selected_options: '{}', category_label: (mxPcConditionsAjax.category_label || ''), mx_lang: (document.documentElement.getAttribute('lang') || 'de').substring(0, 2).toLowerCase() } }).done(function (resp) { MX_MANUFACTURERS_LOADING = false; MX_MANUFACTURERS_LOADED = true; MX_PERF.ajaxDone(_mfrPerfEntry, resp ? Math.round(JSON.stringify(resp).length / 1024) + 'KB' : '?'); function applyManufacturers() { var $select = $(MANUFACTURER_SELECT).first(); if (!$select.length) { safeUpdatePopupPosition(); return; } $select.empty().append('' + mxT('-- Hersteller wählen --') + ''); if (!resp || !resp.success || !resp.data || !Array.isArray(resp.data.manufacturers)) { setCookie('mx_selected_manufacturer', '', -1); safeUpdatePopupPosition(); return; } var manufacturers = resp.data.manufacturers || []; // Global speichern, damit normalizeManufacturerValue() case-insensitiv // matchen kann, auch bevor der befüllt ist (z.B. "gecam" → "GECAM") window.MX_MANUFACTURERS_LIST = manufacturers; $.each(manufacturers, function (_, m) { var label = (m === '__NONE__') ? mxT('Kein Hersteller') : m; $select.append('' + label + ''); }); var cookieMan = normalizeManufacturerValue(getCookie('mx_selected_manufacturer')); if (cookieMan && cookieMan !== '__ALL__') { var matched = manufacturers.find(function (m) { return m.toLowerCase() === cookieMan.toLowerCase(); }); if (matched) { $select.val(matched); // ✅ FIX: Slider und Choices entsperren, nachdem der Hersteller // aus Cookie/URL erfolgreich in der geladenen Liste gefunden wurde. // Ohne diesen Call bleibt der Range-Slider disabled, weil // applyManufacturers() asynchron nach enableChoicesAfterManufacturer() // läuft und den Slider via initSliders() neu aufbaut (disabled). enableChoicesAfterManufacturer(); // Cookie auf exakten API-Wert normalisieren (z.B. "gecam" → "Gecam") setCookie('mx_selected_manufacturer', matched, 365); } else { setCookie('mx_selected_manufacturer', '', -1); $select.val(''); } } else $select.val(''); safeUpdatePopupPosition(); } if ($(MANUFACTURER_SELECT).length) { applyManufacturers(); } else { setTimeout(applyManufacturers, 300); } }).fail(function () { MX_MANUFACTURERS_LOADING = false; safeUpdatePopupPosition(); }); } function injectManufacturerResetButton() { if ($('#mx-manufacturer-bar').length) { updateManufacturerLabel(); return; } var $bar = $('', { id: 'mx-manufacturer-bar', class: 'mx-manufacturer-bar' }); var $topRow = $('', { class: 'mx-manufacturer-bar-top' }); var $prefix = $('', { class: 'mx-manufacturer-bar-prefix', text: mxT('Werkzeuge für') }); var $label = $('', { id: 'mx-selected-manufacturer-label', class: 'mx-selected-manufacturer-label' }); $topRow.append($prefix).append($label); var $btn = $('', { type: 'button', id: 'mx-reset-manufacturer', class: 'mx-reset-manufacturer', text: mxT('Maschinenhersteller ändern') }); $bar.append($topRow).append($btn); var $firstLayer = $('ul.layers > li.layers-list-item').first(); if ($firstLayer.length) { $bar.insertBefore($firstLayer); } else { $('ul.layers').first().prepend($bar); } updateManufacturerLabel(); var manufacturerLabel = document.getElementById('mx-selected-manufacturer-label'); if (manufacturerLabel && !manufacturerLabel.dataset.popupBound) { manufacturerLabel.dataset.popupBound = "1"; manufacturerLabel.style.cursor = 'pointer'; manufacturerLabel.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); var popup = document.querySelector('.mx-hersteller-popup'); if (!popup || !window.MX_Popup) return; // >>> CENTER-MODUS DEAKTIVIEREN popup.classList.remove('mx-center-popup'); popup.classList.add('mx-slideout'); document.body.classList.remove('mx-dim-background'); document.body.classList.remove('mx-lock-scroll'); if (popup.classList.contains('mx-popup-open')) { MX_Popup.close(); } else { MX_Popup.open(); } }, true); } } function resetManufacturer() { var _oldMfrReset = getCookie('mx_selected_manufacturer') || '__ALL__'; setCookie('mx_selected_manufacturer', '', -1); broadcastManufacturerChange('', _oldMfrReset); updateUrlManufacturerParam(''); var $select = $(MANUFACTURER_SELECT).first(); if ($select.length) $select.val(''); HAS_INTERACTED = false; window.MX_HAS_INITIAL_CONFIG = false; window.MX_LAST_AUTO_SELECTION = null; window.MX_INITIAL_AUTO_SELECTION = null; window.MX_INITIAL_TECH_DETAIL_LOADED = false; clearAllSelections(); disableChoicesUntilManufacturer(); $('#frmherstellerfloat .elementor-message').remove(); $('#mx-reset-manufacturer').hide(); sendAvailability(true, true); loadManufacturersForCategory(true); // force reload after reset safeUpdatePopupPosition(); updateManufacturerLabel(); } function restoreConfigFromHiddenField() { var $hiddenField = $('form.cart input[name="pc_configurator_data"]'); if (!$hiddenField.length) return null; var raw = $hiddenField.val(); if (!raw || !raw.trim()) return null; try { var configData = JSON.parse(raw); if (!Array.isArray(configData) || !configData.length) return null; var restored = {}; configData.forEach(function (item) { if (item.layer_name && item.choiceName) { restored[item.layer_name] = item.choiceName; } }); return Object.keys(restored).length ? restored : null; } catch (e) { return null; } } function bindEvents() { $(document).on('click', 'li.layers-list-item .layer_choices button.choice-item', function (e) { var $btn = $(this); var $li = $btn.closest('li.choice'); var $layer = $li.closest('li.layers-list-item'); var layerName = txt($layer.find('> .layer-item .layer-name').first()); var choiceLabel = $.trim($li.find('.choice-name').text()).toUpperCase(); if (layerName) { MX_USER_JUST_CLICKED[layerName] = true; window.MX_LAST_CLICKED_LAYER = layerName; } // ✅ Kein HERO-Sonderblock mehr! HERO läuft jetzt durch den normalen Click-Pfad: // 1. Alle Selections bleiben erhalten (Durchmesser, Aufnahme etc.) // 2. sendAvailability sendet ALLES an Server inkl. Evolutionsstufe=HERO // 3. Server pinnt Evolutionsstufe=HERO und findet besten Match // 4. Performance-Boost wird bereits in sendAvailability gelöscht wenn HERO aktiv // 5. Edition-UI aktualisiert sich über handleAvail Response HAS_INTERACTED = true; window.MX_RESTORE_MODE = false; window.MX_RESTORE_KEYS = null; updateVariantImageInstant($btn); $layer.find('li.choice') .removeClass('active mxactive selected') .find('button.choice-item') .attr('aria-pressed', 'false'); $li.addClass('active mxactive selected'); $btn.attr('aria-pressed', 'true'); var txtLabel = $.trim($li.find('.choice-name').text()); $layer.find('> .layer-item .selected-choice').text(txtLabel); CACHED_SELECTED_OPTIONS = null; setTimeout(function () { // ✅ Keine hardgecodeten Besatz/Layer-Regeln mehr. // Layer-Sichtbarkeit wird DYNAMISCH über Server-Response gesteuert: // 1. sendAvailability → Server prüft welche Optionen für aktuelle Selektion existieren // 2. applyChoiceAvailability → disabled Choices ohne DB-Match // 3. mxHideAllEmptyLayers → versteckt Layer ohne enabled Choices (inkl. Range) // 4. mxClearHiddenLayerSelections → räumt Ghost-Selections auf CACHED_SELECTED_OPTIONS = null; updateHiddenConfigField('click'); MX_AVAIL_DIRTY = true; mxScheduleRecalc(); }, 10); }); $(document).on('click', '#mx-reset-manufacturer', function () { resetManufacturer(); }); // ✅ FIX: Immer aktuellen DOM-Stand ins hidden field schreiben bevor Formular abgesendet wird. // Verhindert dass MX_INITIAL_CONFIG (alte Konfiguration) statt der aktuellen Auswahl gesendet wird. $(document).on('submit', 'form.cart', function () { CACHED_SELECTED_OPTIONS = null; CACHE_TIMESTAMP = 0; updateHiddenConfigField('form-submit'); // MX: Varianten-PK (Artikel-ID) mitsenden var $form = $(this); $form.find('input[name="mx_variant_pk"]').remove(); if (LAST_KNOWN_PK) { $('').attr('type', 'hidden').attr('name', 'mx_variant_pk').val(LAST_KNOWN_PK).appendTo($form); } }); // ✅ "Produkte für alle Hersteller anzeigen" Button → setzt __ALL__ und submittet Form $(document).on('click', '#mxShowAllProducts', function () { var $select = $('#frmherstellerfloat').find('select[id*="hersteller"], select[name*="hersteller"]').first(); if (!$select.length) $select = $(MANUFACTURER_SELECT).first(); // __ALL__ Option temporär einfügen falls nicht vorhanden if (!$select.find('option[value="__ALL__"]').length) { $select.append('' + mxT('Alle Hersteller') + ''); } $select.val('__ALL__'); $('#frmherstellerfloat').trigger('submit'); }); $(document).on('submit', '#frmherstellerfloat', function (e) { e.preventDefault(); var $form = $(this); var $select = $form.find(MANUFACTURER_SELECT).first(); if (!$select.length) return; var val = $select.val() || ''; HAS_INTERACTED = false; window.MX_HAS_INITIAL_CONFIG = false; window.MX_LAST_AUTO_SELECTION = null; // ✅ FIX: Initial-Auto-Selection zurücksetzen damit nach Hersteller-Wechsel // die neue Auto-Selection gespeichert wird → Reset-Button springt zur // korrekten Erst-Auswahl des NEUEN Herstellers zurück (nicht des alten). window.MX_INITIAL_AUTO_SELECTION = null; var _oldMfrBeforeChange = getCookie('mx_selected_manufacturer') || '__ALL__'; setCookie('mx_selected_manufacturer', normalizeManufacturerValue(val), 365); broadcastManufacturerChange(val, _oldMfrBeforeChange); updateUrlManufacturerParam(val); safeUpdatePopupPosition(); updateManufacturerLabel(); if (val) { clearAllSelections(); CACHED_SELECTED_OPTIONS = null; CACHE_TIMESTAMP = 0; enableChoicesAfterManufacturer(); injectManufacturerResetButton(); $('#mx-reset-manufacturer').show(); } else { clearAllSelections(); CACHED_SELECTED_OPTIONS = null; CACHE_TIMESTAMP = 0; disableChoicesUntilManufacturer(); $('#mx-reset-manufacturer').hide(); } // ─── PERF-OPT: EIN Call statt 2 (ignoreSelections=true + 1s later false) ─── // Erster Call mit ignoreSelections=true zeigt ALLE Optionen. // Nach Auto-Selection (1s) feuert der einzige Follow-Up-Call. sendAvailability(true, true); // ✅ FIX: Skeleton zeigen + follow-up Call nach Auto-Selection mxShowTechSkeleton(); window.MX_INITIAL_TECH_DETAIL_LOADED = false; setTimeout(function () { if (!window.MX_INITIAL_TECH_DETAIL_LOADED) { window.MX_INITIAL_TECH_DETAIL_LOADED = true; MX_PERF.mark('mfr-submit-followup'); CACHED_SELECTED_OPTIONS = null; CACHE_TIMESTAMP = 0; sendAvailability(true, false); } }, 1000); }); } function shouldHideBesatzausfuehrung() { var title = (window.MX_PRODUCT_TITLE || '').toLowerCase().trim(); var HIDE_TITLES = [ 'entgratteller', 'schleifvliesteller', 'entgratblock', ]; return HIDE_TITLES.indexOf(title) !== -1; } function shouldHideSchlitzung() { var title = (window.MX_PRODUCT_TITLE || '').toLowerCase().trim(); var HIDE_TITLES = [ 'entgratteller', 'entgratsegment', 'entgratblock', 'schleifbürstenteller', 'schleifbuerstenteller' ]; return HIDE_TITLES.indexOf(title) !== -1; } function removeLayerByName(layerName) { var $layer = jQuery('ul.layers > li.layers-list-item').filter(function () { return jQuery.trim(jQuery(this).find('> .layer-item .layer-name').first().text()) === layerName; }).first(); if (!$layer.length) return false; $layer.next('.mx-aufnahme-container').remove(); $layer.remove(); return true; } function applyEntgrattellerLayerRules() { var removedAny = false; if (shouldHideBesatzausfuehrung()) { removedAny = removeLayerByName('Besatzausführung') || removedAny; } if (shouldHideSchlitzung()) { removedAny = removeLayerByName('Schlitzung') || removedAny; } if (removedAny && typeof window.updateHiddenConfigField === 'function') { window.updateHiddenConfigField(); } } function mxSyncVariantHeaderSpacing() { const header = document.querySelector('.mx-variant-header'); const viewer = document.querySelector('.mkl_pc_viewer'); if (!header || !viewer) return; const apply = () => { if (window.innerWidth <= 660) { viewer.style.paddingTop = ''; return; } const headerTop = header.offsetTop || 0; const headerH = header.offsetHeight || 0; const buffer = window.innerWidth > 1024 ? 100 : 48; const needed = Math.ceil(headerTop + headerH + buffer); viewer.style.paddingTop = needed + 'px'; }; apply(); if ('ResizeObserver' in window) { new ResizeObserver(apply).observe(header); } window.addEventListener('resize', apply); } function initializePage() { MX_PERF.mark('initializePage-start'); window.MX_PC_CONFIGURATOR_INIT = true; // ✅ TRP-Exclude: Konfigurator-Container nochmals markieren var noTransSels = ['.mkl_pc_container', '.mkl_pc', 'ul.layers', '.mkl_pc_toolbar', '.mx-variant-header', '.mx-tech-specs']; for (var nti = 0; nti < noTransSels.length; nti++) { $(noTransSels[nti]).attr('data-no-translation', ''); } if (window.MX_INITIAL_STATE) { var state = window.MX_INITIAL_STATE; if (state.product_title) { window.MX_PRODUCT_TITLE = state.product_title; } if (state.variant_image) { updateVariantImage(state.variant_image); } // ✅ FIX: NICHT die unvollständigen statischen Felder aus MX_INITIAL_STATE rendern. // Stattdessen Lade-Skeleton zeigen → nach 1s kommt der echte vollständige AJAX-Response. mxShowTechSkeleton(); } var manufacturer = getCookie('mx_selected_manufacturer') || ''; if (window.MX_INITIAL_AVAILABLE && manufacturer) { applyInitialAvailableOptions(); } loadManufacturersForCategory(); resetAllChoicesEnabled(); if (!manufacturer) { clearAllSelections(); disableChoicesUntilManufacturer(); } else { enableChoicesAfterManufacturer(); } HAS_INTERACTED = false; var restoredConfig = restoreConfigFromHiddenField(); // ─── PERF-OPT: Track which init branch we take ─── var _initBranch = 'unknown'; if (restoredConfig) { _initBranch = 'restoredConfig'; window.MX_HAS_INITIAL_CONFIG = true; window.MX_RESTORE_MODE = true; window.MX_RESTORE_KEYS = Object.keys(restoredConfig || {}); applyAutoSelection(restoredConfig, { skipAjax: true }); // ← skipAjax: true! Wir feuern EINEN Call weiter unten // ✅ FIX: Range Sliders synchronisieren (Länge etc.) setTimeout(function () { $('ul.layers > li.layers-list-item.mx-range-layer').each(function () { if (window.MX_Range && typeof window.MX_Range.rebuildForLayer === 'function') { window.MX_Range.rebuildForLayer($(this)); } }); // ─── PERF-OPT: EIN einziger Availability-Call nach Auto-Selection + Range-Sync ─── MX_PERF.mark('init-avail-consolidated'); CACHED_SELECTED_OPTIONS = null; CACHE_TIMESTAMP = 0; sendAvailability(true, false); }, 300); } else if (URL_HAS_LOAD_CONFIG_FROM_CART || URL_HAS_LOAD_CONFIG_FROM_ORDER) { _initBranch = 'cart/order-load'; window.MX_HAS_INITIAL_CONFIG = false; setTimeout(function () { if (typeof updateHiddenConfigField === 'function') { updateHiddenConfigField('after-order-or-cart-load'); } sendAvailability(true, false); }, 800); } else if (HAS_INITIAL_CONFIG) { _initBranch = 'initialConfig'; window.MX_HAS_INITIAL_CONFIG = true; window.MX_RESTORE_MODE = true; window.MX_RESTORE_KEYS = Object.keys(window.MX_INITIAL_CONFIG || {}); applyAutoSelection(window.MX_INITIAL_CONFIG, { skipAjax: true }); // ← skipAjax: true! MX_PERF.mark('init-avail-consolidated'); CACHED_SELECTED_OPTIONS = null; CACHE_TIMESTAMP = 0; _sendAvailabilityCore(true, false); // direkt, kein Debounce beim Init } else { _initBranch = 'fresh'; window.MX_HAS_INITIAL_CONFIG = false; // ✅ FIX: Wenn Hersteller bereits vorhanden (URL/Cookie), dann ignoreSelections=true // damit ALLE Optionen angezeigt werden (z.B. Durchmesser 200 + 260) var hasManufacturer = !!(manufacturer && manufacturer !== '' && manufacturer !== '__ALL__'); sendAvailability(true, hasManufacturer); } MX_PERF.mark('init-branch:' + _initBranch); if (window.MX_Range && typeof window.MX_Range.initSliders === 'function') { window.MX_Range.initSliders(); } setTimeout(function () { if (window.MX_Range && typeof window.MX_Range.rebuildForLayer === 'function') { $('ul.layers > li.layers-list-item.mx-range-layer').each(function () { window.MX_Range.rebuildForLayer($(this)); }); } else if (window.MX_Range && typeof window.MX_Range.initSliders === 'function') { window.MX_Range.initSliders(); } }, 150); if (window.MX_Range && typeof window.MX_Range.startWatcher === 'function') { window.MX_Range.startWatcher(); } if (window.MX_VT && typeof window.MX_VT.load === 'function') { // Hersteller-spezifisch laden (oder __ALL__ wenn kein Hersteller gewählt) MX_PERF.mark('vt-load-start'); window.MX_VT.load(mxPcConditionsAjax.product_id, mxGetCurrentManufacturer() || '__ALL__', function() { // ✅ SPRACH-FIX: Nach VT-Load Beschreibung korrigieren falls AJAX #2 bereits // einen falschen (veralteten DE-Transient) Text gesetzt hat. // Die VT ist sprachkorrekt gecacht (Key enthält _lang) → zuverlässige Quelle. var panel = document.querySelector('.mx-desc-panel[data-panel="description"]'); if (!panel) return; var vtRows = window.MX_VT.getRows(); if (!vtRows || !vtRows.length) return; var vtDesc = null; for (var ri = 0; ri < vtRows.length; ri++) { if (vtRows[ri]._desc_html) { vtDesc = vtRows[ri]._desc_html; break; } } if (vtDesc && panel.innerHTML !== vtDesc) { panel.innerHTML = vtDesc; mxLog('[MX-LANG-FIX] Beschreibung nach VT-Load korrigiert'); } }); } initWithRetry(15, 200); injectManufacturerResetButton(); safeUpdatePopupPosition(); applyEntgrattellerLayerRules(); markConfiguratorReady(); mxSyncVariantHeaderSpacing(); moveDescriptionBelowImage(); // ✅ MX Translation: DOM-Texte sofort übersetzen (kein Delay → kein deutscher Flash) mxTranslateDom(); // Nochmal nach kurzer Zeit für dynamisch erstellte Elemente (AJAX, Radio-Labels etc.) setTimeout(mxTranslateDom, 150); // Nochmal nach AJAX-Responses (Tech Details etc.) setTimeout(mxTranslateDom, 1500); // Set dynamic grid columns for evo/edition tiles setTimeout(mxSetTileGridColumns, 100); setTimeout(mxSetTileGridColumns, 600); if (!window.MX_DESC_LISTENER_BOUND) { window.MX_DESC_LISTENER_BOUND = true; MX_DESC_MQ.addEventListener('change', function () { setTimeout(moveDescriptionBelowImage, 0); }); } moveResetButtonToViewer(); setTimeout(moveResetButtonToViewer, 200); setTimeout(moveResetButtonToViewer, 800); jQuery('.mx-blech-container').hide(); if (_initBranch === 'fresh' && !window.MX_INITIAL_TECH_DETAIL_LOADED) { window.MX_INITIAL_TECH_DETAIL_LOADED = true; setTimeout(function () { MX_PERF.mark('tech-detail-followup'); CACHED_SELECTED_OPTIONS = null; CACHE_TIMESTAMP = 0; MX_SUPPRESS_AVAIL = false; MX_AVAIL_DIRTY = false; sendAvailability(true, false); }, 400); } MX_PERF.measure('initializePage', 'initializePage-start'); } function syncAllEditionSummariesFromDom() { $('ul.layers > li.layers-list-item.mx-edition-layer').each(function () { updateEditionImages($(this)); }); } var observerStarted = false; function startObserver() { if (observerStarted) return; var target = document.querySelector('.mkl_pc_container') || document.querySelector('.product') || document.body; if (!target) return; observerStarted = true; var observer = new MutationObserver(function (mutations) { var shouldReinit = false; var shouldResyncEdition = false; for (var i = 0; i < mutations.length; i++) { var mutation = mutations[i]; if (mutation.addedNodes && mutation.addedNodes.length) { for (var j = 0; j < mutation.addedNodes.length; j++) { var node = mutation.addedNodes[j]; if (node.nodeType === 1) { var $node = $(node); if ( $node.hasClass('layers-list-item') || $node.find('.layers-list-item').length || $node.hasClass('layer_choices') || $node.find('.layer_choices').length ) { shouldReinit = true; } } } } if (mutation.type === 'attributes' && mutation.attributeName === 'class') { var $target = $(mutation.target); if ($target.closest('ul.layers').length) { if ($target.closest('.mx-edition-layer').length) { shouldResyncEdition = true; } } } } if (shouldReinit) { setTimeout(function () { initAllDesigns(); moveResetButtonToViewer(); if (window.MX_Range && typeof window.MX_Range.initSliders === 'function') { window.MX_Range.initSliders(); } injectManufacturerResetButton(); safeUpdatePopupPosition(); mxTranslateDom(); }, 50); } else if (shouldResyncEdition) { setTimeout(function () { syncAllEditionSummariesFromDom(); }, 50); } }); observer.observe(target, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'disabled'] }); } $(document).ready(function () { ensureEditionDisabledStyles(); startObserver(); }); jQuery(document).on('elementor/popup/show', function () { if (window.MX_PC_CONFIGURATOR_INIT) { MX_PERF.skippedAjax('popup/show → manufacturers already loaded by initializePage'); return; } setTimeout(function () { loadManufacturersForCategory(); }, 300); }); $(function () { MX_PERF.mark('dom-ready'); bindEvents(); var _lazyBound = false; function bindLazyPreload() { if (_lazyBound) return; _lazyBound = true; $(document).on('mouseenter', 'button.choice-item[data-variant-image]', function () { var url = $(this).data('variant-image'); if (url) preloadImage(url, true); }); MX_PERF.mark('lazy-preload-bound'); } bindLazyPreload(); $('li.choice.active button.choice-item[data-variant-image], li.choice.mxactive button.choice-item[data-variant-image]').each(function () { var url = $(this).data('variant-image'); if (url) preloadImage(url, true); }); setTimeout(function () { var pid = getProductId(); if (pid && window.MX_VT && !window.MX_VT.isReady()) { var preloadMfr = mxGetCurrentManufacturer() || '__ALL__'; if(DBG) console.log('[MX-VT] Background preload start … (mfr:', preloadMfr + ')'); window.MX_VT.load(pid, preloadMfr); } }, 200); var _initCheckCount = 0; function checkForConfigurator() { var $layers = $('ul.layers > li.layers-list-item'); var $manSel = $(MANUFACTURER_SELECT).first(); if ($layers.length > 0 || $manSel.length > 0) { initializePage(); return; } if (++_initCheckCount < 100) { requestAnimationFrame(checkForConfigurator); } } requestAnimationFrame(checkForConfigurator); }); (function mxEditionHeightFix() { function run() { var section = document.querySelector('.mkl_pc .mkl_pc_container .mkl_pc_toolbar section.choices'); if (!section) return; function update() { var hasScroll = section.scrollHeight > section.clientHeight; document.documentElement.style.setProperty('--mx-edition-h', hasScroll ? '66px' : '69px'); } update(); if (window.ResizeObserver) new ResizeObserver(update).observe(section); new MutationObserver(update).observe(section, { childList: true, subtree: true }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function () { setTimeout(run, 300); }); } else { setTimeout(run, 300); } })(); })(jQuery);
' + mxT('Keine technischen Details verfügbar.') + '
' + infoText + '