1/* ========================================================= 2 GRUPO MARMO â JS por GCON DIGITAL 3 ========================================================= */ 4 5;(function(window, document){ 6 'use strict'; 7 8 /* ----------------------------------------------------- 9 * Utilidades comunes 10 * --------------------------------------------------- */ 11 var hasDOM = !!(document && document.querySelector); 12 var $ = window.jQuery; // puede no existir 13 14 function onReady(fn){ 15 if (!hasDOM) return; 16 if (document.readyState === 'loading') { 17 document.addEventListener('DOMContentLoaded', fn, { once:true }); 18 } else { 19 fn(); 20 } 21 } 22 23 function on(el, evt, selOrFn, fn){ 24 // Pequeño helper para addEventListener + delegación 25 if (!el) return; 26 if (typeof selOrFn === 'function') { 27 el.addEventListener(evt, selOrFn); 28 return; 29 } 30 el.addEventListener(evt, function(e){ 31 var target = e.target.closest(selOrFn); 32 if (target && el.contains(target)) fn.call(target, e); 33 }); 34 } 35 36 /* ----------------------------------------------------- 37 * 1) Control de secciones dentro del popup Elementor 38 * (detecta botones con clases que empiezan por "btn-") 39 * --------------------------------------------------- */ 40 (function popupSectionSwitcher(){ 41 if (!hasDOM) return; 42 window.addEventListener('elementor/popup/show', function(event){ 43 var popup = event && event.detail && event.detail.instance && event.detail.instance.$element && event.detail.instance.$element[0]; 44 if (!popup) return; 45 46 // Delegación dentro del popup 47 on(popup, 'click', "[class*='btn-']", function(e){ 48 // Encuentra el slug a partir de la clase btn-* 49 var btn = this; 50 var slug = ''; 51 btn.classList.forEach(function(c){ if (c.indexOf('btn-') === 0) slug = c.replace('btn-', ''); }); 52 if (!slug) return; 53 54 // Cierra todas 55 popup.querySelectorAll('.seccion-general').forEach(function(sec){ sec.style.display = 'none'; }); 56 // Abre 57 var target = popup.querySelector('.seccion-' + slug); 58 if (target) target.style.display = 'flex'; 59 }); 60 }); 61 })(); 62 63/* ----------------------------------------------------- 64 * 2) Formulario PIDE TU CITA â select de talleres => botones 65 * (solo dentro de .form-pasos) + popups + DOM dinámico 66 * --------------------------------------------------- */ 67(function talleresSelectAsButtons(){ 68 if (!hasDOM) return; 69 70 var ROOT_SELECTOR = '.form-pasos'; 71 72 var MARCAS = [ 73 'FORD','HYUNDAI','SUZUKI','HONDA','VOLVO','TRUCKS','CITROEN', 74 'PEUGEOT','OPEL','SUBARU','KGM','JAECOO','OMODA','MITSUBISHI' 75 ]; 76 var SELECTORS_PRIORITARIOS = [ 77 '#form-field-talleres', 78 'select[name="form_fields[talleres]"]' 79 ]; 80 81 function isTallerSelect(sel){ 82 if (!sel || sel.tagName !== 'SELECT') return false; 83 var txts = Array.from(sel.options).map(function(o){ return (o.text||'').toUpperCase().trim(); }); 84 return MARCAS.some(function(m){ return txts.some(function(t){ return t.includes(m); }); }); 85 } 86 87 // Busca selects SOLO dentro de .form-pasos (en el contexto dado) 88 function getTallerSelectsIn(ctx){ 89 var scopes = Array.from((ctx || document).querySelectorAll(ROOT_SELECTOR)); 90 // Si el propio ctx es .form-pasos, inclúyelo 91 if ((ctx && ctx.matches && ctx.matches(ROOT_SELECTOR))) scopes.unshift(ctx); 92 93 var found = []; 94 scopes.forEach(function(scope){ 95 // 1) Prioritarios 96 for (var i=0;i<SELECTORS_PRIORITARIOS.length;i++){ 97 var direct = Array.from(scope.querySelectorAll(SELECTORS_PRIORITARIOS[i])) 98 .filter(function(el){ return el.tagName==='SELECT'; }); 99 if (direct.length){ found = found.concat(direct); return; } 100 } 101 // 2) Fallback: cualquier select que cumpla MARCAS 102 found = found.concat(Array.from(scope.querySelectorAll('select')).filter(isTallerSelect)); 103 }); 104 return found; 105 } 106 107 function ensureId(el){ 108 if (!el.id) el.id = 'talleres_' + Math.random().toString(36).slice(2,9); 109 return el.id; 110 } 111 112 function brandKey(val){ 113 var v = (val||'').toLowerCase().trim(); 114 if (v === 'volvo' || v === 'truck' || v === 'trucks' || v === 'volvo trucks') return 'volvo-trucks'; 115 return v; 116 } 117 118 function buildButtonsFor(select){ 119 if (!select || select.dataset.enhanced === 'true') return; 120 if (!isTallerSelect(select)) return; 121 122 var opts = Array.from(select.options).filter(function(o){ return (o.value||'').trim() !== ''; }); 123 if (!opts.length) return; 124 125 // Limpia posible botonera previa 126 var next = select.nextElementSibling; 127 if (next && next.classList && next.classList.contains('select-as-buttons')) next.remove(); 128 129 var wrap = document.createElement('div'); 130 wrap.className = 'select-as-buttons'; 131 wrap.setAttribute('role', 'group'); 132 133 var selId = ensureId(select); 134 wrap.dataset.forSelect = selId; 135
136 opts.forEach(function(opt){ 137 var val = (opt.value||'').trim(); 138 var text = (opt.textContent||'').trim(); 139 var brand = brandKey(val); 140 141 var btn = document.createElement('button'); 142 btn.type = 'button'; 143 btn.className = 'sab-btn'; 144 btn.dataset.value = val; 145 btn.dataset.brand = brand; 146 btn.dataset.forSelect = selId; 147 btn.textContent = text; 148 btn.setAttribute('aria-pressed', (select.value === opt.value) ? 'true' : 'false'); 149 wrap.appendChild(btn); 150 }); 151 152 select.insertAdjacentElement('afterend', wrap); 153 select.dataset.enhanced = 'true'; 154 } 155 156 // Click global RESTRINGIDO a .form-pasos 157 on(document, 'click', ROOT_SELECTOR + ' .select-as-buttons .sab-btn', function(e){ 158 var btn = this; 159 var wrapper = btn.closest('.select-as-buttons'); 160 var scope = btn.closest(ROOT_SELECTOR); 161 if (!wrapper || !scope) return; 162 163 var selId = btn.dataset.forSelect; 164 var select = selId ? document.getElementById(selId) : (wrapper ? wrapper.previousElementSibling : null); 165 if (!select) return; 166 167 wrapper.querySelectorAll('.sab-btn').forEach(function(b){ b.setAttribute('aria-pressed', 'false'); }); 168 btn.setAttribute('aria-pressed', 'true'); 169 170 select.value = btn.dataset.value || ''; 171 select.dispatchEvent(new Event('change', { bubbles:true })); 172 select.dispatchEvent(new Event('input', { bubbles:true })); 173 174 e.preventDefault(); 175 }); 176 177 function initIn(context){ 178 getTallerSelectsIn(context).forEach(buildButtonsFor); 179 } 180 181 // Init inicial 182 onReady(function(){ initIn(document); }); 183 184 // En popups de Elementor (solo si dentro hay .form-pasos) 185 window.addEventListener('elementor/popup/show', function(){ 186 setTimeout(function(){ 187 document.querySelectorAll('.elementor-popup-modal, .dialog-widget, .dialog-message') 188 .forEach(function(modal){ initIn(modal); }); 189 }, 150); 190 }); 191 192 // Observer para DOM dinámico, pero limitado a .form-pasos 193 var obs = new MutationObserver(function(muts){ 194 muts.forEach(function(m){ 195 (m.addedNodes || []).forEach(function(n){ 196 if (n.nodeType !== 1) return; 197 198 // Si aparece un scope completo 199 if (n.matches && n.matches(ROOT_SELECTOR)) { initIn(n); return; } 200 201 // Si aparecen selects dentro de un .form-pasos 202 var scope = n.closest ? n.closest(ROOT_SELECTOR) : null; 203 if (scope){ 204 if (n.matches && n.matches('select') && isTallerSelect(n)) buildButtonsFor(n); 205 n.querySelectorAll && n.querySelectorAll('select').forEach(function(sel){ 206 if (isTallerSelect(sel)) buildButtonsFor(sel); 207 }); 208 } else if (n.querySelectorAll) { 209 // Si el nodo no está en scope, pero contiene uno nuevo 210 n.querySelectorAll(ROOT_SELECTOR).forEach(initIn); 211 } 212 }); 213 }); 214 }); 215 obs.observe(document.documentElement, { childList:true, subtree:true }); 216})(); 217 218 219 /* ----------------------------------------------------- 220 * 3) Comportamientos jQuery (si existe) 221 * â Formularios mostrar/ocultar por IDs simples 222 * â Marcar radio-rows activos y reset 223 * â Upload: mostrar nombre del archivo 224 * â Toggle de filtros (texto del botón) 225 * â Top bar admin hover 226 * â Recarga al aceptar/denegar cookies (Complianz) 227 * â Ocultar flechas slider si disabled 228 * â Tooltip sencillo 229 * --------------------------------------------------- */ 230 (function behaviorsWithjQuery(){ 231 if (!$ || !$.fn) return; // salir si no hay jQuery 232 233 $(function(){ 234 /* ---------- FORM OPEN/CLOSE por IDs (#form-a/b/c) ---------- */ 235 (function formsABCSimple(){ 236 var $btnBack = $('#btn-back'); 237 function ocultarForms(){ $('#form-a, #form-b, #form-c').hide(); } 238 function init(){ ocultarForms(); $btnBack.hide(); } 239 init(); 240 241 $(document).off('click.formsABC').on('click.formsABC', '#btn-form-a, #btn-form-b, #btn-form-c', function(e){ 242 e.preventDefault(); 243 var target = this.id.replace('btn-', ''); // form-a/b/c 244 ocultarForms(); 245 $('#col-botones').hide(); 246 $('#'+target).fadeIn(); 247 $btnBack.show(); 248 }); 249 250 $(document).off('click.formsABCBack').on('click.formsABCBack', '#btn-back', function(e){ 251 e.preventDefault(); 252 ocultarForms(); 253 $('#col-botones').fadeIn(); 254 $btnBack.hide(); 255 }); 256 })(); 257 258 /* ---------- Jet radio rows: clases especÃficas ---------- */ 259 $('.jet-radio-list__row').each(function(){ 260 var text = $(this).text().toLowerCase(); 261 if (text.includes('rÃgido')) $(this).addClass('is-rigido'); 262 else if (text.includes('remolque')) $(this).addClass('is-remolque'); 263 else if (text.includes('tractora')) $(this).addClass('is-tractora'); 264 }); 265 266 /* ---------- Upload: mostrar nombre archivo ---------- */ 267 (function uploadFilename(){ 268 var $input = $('#form-field-field_82c6f7f'); 269 if ($input.length){ 270 $input.off('change.showFileName').on('change.showFileName', function(){ 271 var file = this.files && this.files[0]; 272 var $info = $('#archivo-info'); 273 if ($info.length){ $info.text(file ? ('Archivo seleccionado: ' + file.name) : 'Archivo eliminado.'); } 274 }); 275 } 276 $(document).off('click.uploadProxy').on('click.uploadProxy', '.elementor-field-type-upload', function(){ 277 $('.elementor-upload-field').trigger('click'); 278 }); 279 })(); 280 281 /* ---------- Botón mostrar/ocultar filtros + texto ---------- */
282 (function filtrosToggle(){ 283 function actualizarTexto(){ 284 var $txt = $('.ocultar-filtros').find('.elementor-icon-list-text'); 285 if (!$txt.length) return; 286 $txt.text($('.contenedor-filtros').is(':visible') ? 'OCULTAR FILTROS' : 'MOSTRAR FILTROS'); 287 } 288 actualizarTexto(); 289 $(document).off('click.toggleFiltros').on('click.toggleFiltros', '.ocultar-filtros', function(e){ 290 e.preventDefault(); 291 $('.contenedor-filtros').toggle(); 292 actualizarTexto(); 293 }); 294 })(); 295 296 /* ---------- JetFilters: marcar activo por grupo + reset ---------- */ 297 (function jetFiltersActive(){ 298 var groupSelectors = [ 299 '.filtros-modelo', '.filtros-combustible', '.filtros-traccion', '.filtros-color', 300 '.filtros-tipo-de-vehiculo' 301 ].join(','); 302 303 // change en radios -> clase active solo dentro del grupo 304 $(document).off('change.jetRadioActive').on('change.jetRadioActive', '.jet-radio-list__row.jet-filter-row input[type="radio"]', function(){ 305 var $input = $(this); 306 var $group = $input.closest(groupSelectors); 307 if ($group.length){ $group.find('.jet-radio-list__row.jet-filter-row').removeClass('active'); } 308 if ($input.prop('checked')){ $input.closest('.jet-radio-list__row.jet-filter-row').addClass('active'); } 309 }); 310 311 // reset â un único listener que cubre todos los botones y enlaces 312 var resetTriggers = '.jet-remove-all-filters__button, .jet-active-filter__val, .jet-active-filter__remove'; 313 $(document).off('click.jetResetAll').on('click.jetResetAll', resetTriggers, function(){ 314 $('.jet-radio-list__row.jet-filter-row input[type="checkbox"], .jet-radio-list__row.jet-filter-row input[type="radio"]').prop('checked', false); 315 $('.jet-radio-list__row.jet-filter-row').removeClass('active'); 316 }); 317 })(); 318 319 /* ---------- Hover admin bar ---------- */ 320 $('header > div, #wpadminbar').hover( 321 function(){ $('#wpadminbar').addClass('bar-admin'); }, 322 function(){ $('#wpadminbar').removeClass('bar-admin'); } 323 ); 324 325 /* ---------- Complianz: recargar tras aceptar/denegar ---------- */ 326 $(document).off('click.cmplzReload').on('click.cmplzReload', '.cmplz-buttons .cmplz-btn.cmplz-accept, .cmplz-buttons .cmplz-btn.cmplz-deny', function(){ 327 setTimeout(function(){ location.reload(); }, 500); 328 }); 329 330 /* ---------- Ocultar flechas swiper si ambas disabled ---------- */ 331 setTimeout(function(){ 332 $('.elementor-widget-container').each(function(){ 333 var $c = $(this); 334 var $prev = $c.find('.elementor-swiper-button-prev'); 335 var $next = $c.find('.elementor-swiper-button-next'); 336 var bothDisabled = $prev.hasClass('swiper-button-disabled') && $next.hasClass('swiper-button-disabled'); 337 $prev.toggle(!bothDisabled); 338 $next.toggle(!bothDisabled); 339 }); 340 }, 1000); 341 342 /* ---------- Tooltip básico (respetando title) ---------- */ 343 (function basicTooltip(){ 344 var $win = $(window); 345 $(document).off('mouseenter.simpleTooltip').on('mouseenter.simpleTooltip', '[rel~=tooltip]', function(){ 346 var $t = $(this); var tip = $t.attr('title'); if (!tip) return; 347 $t.data('title', tip).removeAttr('title'); 348 var $tp = $('<div id="tooltip"></div>').css('opacity', 0).html(tip).appendTo('body'); 349 350 function place(){ 351 if ($win.width() < $tp.outerWidth() * 1.5) $tp.css('max-width', $win.width()/2); else $tp.css('max-width', 340); 352 var pos_left = $t.offset().left + ($t.outerWidth()/2) - ($tp.outerWidth()/2); 353 var pos_top = $t.offset().top - $tp.outerHeight() - 20; 354 $tp.toggleClass('left', pos_left < 0); 355 if (pos_left < 0) pos_left = $t.offset().left + $t.outerWidth()/2 - 20; 356 $tp.toggleClass('right', pos_left + $tp.outerWidth() > $win.width()); 357 if (pos_left + $tp.outerWidth() > $win.width()) pos_left = $t.offset().left - $tp.outerWidth() + $t.outerWidth()/2 + 20; 358 $tp.toggleClass('top', pos_top < 0); 359 if (pos_top < 0) pos_top = $t.offset().top + $t.outerHeight(); 360 $tp.css({ left: pos_left, top: pos_top }).animate({ top: '+=10', opacity: 1 }, 50); 361 } 362 place(); 363 $win.on('resize.simpleTooltip', place); 364 365 function remove(){ 366 $tp.animate({ top: '-=10', opacity: 0 }, 50, function(){ $(this).remove(); }); 367 $t.attr('title', $t.data('title')); 368 $win.off('resize.simpleTooltip', place); 369 }
370 $t.one('mouseleave.simpleTooltip', remove); 371 $tp.on('click', remove); 372 }); 373 })(); 374 }); 375 })(); 376 377 /* ----------------------------------------------------- 378 * 4) Sistema de paneles por scope con data-attributes 379 * (compatible con el punto 3 â IDs simples â sin colisiones) 380 * --------------------------------------------------- */ 381 (function scopedForms(){ 382 if (!$ || !$.fn) return; // usa jQuery para conciliar con estilos existentes 383 384 function initIn(context){ 385 $(context).find('[data-forms-scope]').each(function(){ 386 var $scope = $(this); 387 $scope.find('.form-panel').hide().removeClass('is-active'); 388 $scope.find('#col-botones').show(); 389 $scope.find('.js-back, #btn-back, [data-action="back"]').hide(); 390 }); 391 } 392 393 function ensureVisible($panel){ 394 var display = $panel.attr('data-display') || 'flex'; 395 $panel.get(0).style.setProperty('--panel-display', display); 396 $panel.css('display', display).addClass('is-active'); 397 } 398 399 function getScopeFrom($el){ 400 var $scope = $el.closest('[data-forms-scope]'); 401 if ($scope.length) return $scope; 402 var id = $el.attr('data-back-scope'); 403 if (id) { 404 var $byId = $('#'+id); 405 if ($byId.length) return $byId; 406 } 407 return $('[data-forms-scope]').first(); 408 } 409 410 // Abrir panel dentro del scope 411 $(document).off('click.formsScopeOpen').on('click.formsScopeOpen', '[data-forms-scope] [data-target]', function(e){ 412 e.preventDefault(); 413 var $scope = $(this).closest('[data-forms-scope]'); 414 var key = ($(this).attr('data-target')||'').replace(/^#+/,'').trim(); 415 if (!key) return; 416 var $panel = $scope.find('#'+key); 417 if (!$panel.length) $panel = $('#'+key); // fallback global por si está fuera 418 419 $scope.find('.form-panel').hide().removeClass('is-active'); 420 if ($panel.length) ensureVisible($panel); 421 ($scope.find('#col-botones').length ? $scope.find('#col-botones') : $('#col-botones')).hide(); 422 ($scope.find('.js-back, #btn-back, [data-action="back"]').length 423 ? $scope.find('.js-back, #btn-back, [data-action="back"]'): $('.js-back, #btn-back, [data-action="back"]')).show(); 424 }); 425 426 // Volver 427 $(document).off('click.formsScopeBack').on('click.formsScopeBack', '[data-action="back"], .js-back, #btn-back', function(e){ 428 e.preventDefault(); 429 var $scope = getScopeFrom($(this)); 430 $scope.find('.form-panel').hide().removeClass('is-active'); 431 $scope.find('#col-botones').fadeIn(); 432 $scope.find('.js-back, #btn-back, [data-action="back"]').hide(); 433 }); 434 435 onReady(function(){ initIn(document); }); 436 window.addEventListener('elementor/popup/show', function(_e, _id, inst){ 437 if (inst && inst.elements) initIn(inst.elements.$content); 438 }); 439 })(); 440 441 /* ----------------------------------------------------- 442 * 5) Icono delante de .etiqueta-post según texto 443 * --------------------------------------------------- */ 444 (function etiquetaPostIcons(){ 445 if (!hasDOM) return; 446 var ICON_BASE = '/imagenes/logos-etiquetas-blog/'; 447 var FILE_MAP = { 'volvo-trucks':'trucks-blue.svg', 'trucks':'trucks-blue.svg', 'grupo-marmo':'marmo-blue.svg', 'grupo-marmo-albacete':'marmo-blue.svg', 'grupo-marmo-murcia':'marmo-blue.svg', 'oferta':'marmo-blue.svg', 'DFSK':'dfsk-blue.svg', }; 448 449 function normalize(str){ 450 return (str||'').toString().trim().toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/\s+/g, '-'); 451 } 452 function getIconFile(label){ 453 var key = normalize(label); 454 return FILE_MAP[key] || (key + '-blue.svg'); 455 } 456 function enhanceOne(el){ 457 if (!el || el.dataset.iconified === '1') return; 458 var raw = el.textContent || ''; 459 var file = getIconFile(raw); 460 var icon = document.createElement('span'); 461 icon.className = 'marca-icon'; 462 icon.style.backgroundImage = 'url(' + ICON_BASE + file + ')'; 463 icon.setAttribute('aria-hidden', 'true'); 464 el.insertBefore(icon, el.firstChild); 465 el.dataset.iconified = '1'; 466 } 467 function enhanceAll(ctx){ (ctx||document).querySelectorAll('.etiqueta-post').forEach(enhanceOne); } 468 469 onReady(function(){ enhanceAll(document); }); 470 window.addEventListener('elementor/popup/show', function(){ enhanceAll(document); }); 471 472 var obs = new MutationObserver(function(muts){
473 muts.forEach(function(m){ 474 m.addedNodes && m.addedNodes.forEach(function(n){ 475 if (n.nodeType !== 1) return; 476 if (n.matches && n.matches('.etiqueta-post')) enhanceOne(n); 477 n.querySelectorAll && n.querySelectorAll('.etiqueta-post').forEach(enhanceOne); 478 }); 479 }); 480 }); 481 obs.observe(document.documentElement, { childList:true, subtree:true }); 482 })(); 483 484/* ----------------------------------------------------- 485 * 6) Mostrar/Ocultar Mapas (general + CTA Murcia/Albacete) 486 * --------------------------------------------------- */ 487document.addEventListener('click', function(e) { 488 const btn = e.target.closest('.elementor-button'); 489 if (!btn) return; 490 491 // El botón debe vivir dentro de un widget con clase btn-mapa-* 492 const widget = btn.closest('.elementor-element[class*="btn-mapa-"]'); 493 if (!widget) return; 494 495 // c puede ser: "btn-mapa-murcia-ford" o "btn-mapa-murcia" 496 const mapaClass = (widget.className || '').split(/\s+/).find(c => c.indexOf('btn-mapa-') === 0); 497 if (!mapaClass) return; 498 499 const parts = mapaClass.split('-'); // ["btn","mapa","murcia","ford"] o ["btn","mapa","murcia"] 500 const city = parts[2] || ''; 501 const slug = parts.slice(3).join('-'); // vacÃo en el CTA 502 503 // Helpers de ocultar/mostrar con clases de Elementor 504 const H = ['elementor-hidden-desktop','elementor-hidden-tablet','elementor-hidden-mobile','elementor-hidden-widescreen']; 505 const hide = el => el && H.forEach(c => el.classList.add(c)); 506 const show = el => el && H.forEach(c => el.classList.remove(c)); 507 508 // === CASO CTA: solo ciudad (Murcia / Albacete) dentro de #cta === 509 if (city && !slug) { 510 const rootCTA = btn.closest('#cta'); 511 if (!rootCTA) return; 512 513 // Oculta ambos mapas del CTA y muestra el de la ciudad 514 rootCTA.querySelectorAll('.mapa-web').forEach(hide); 515 const targetCTA = rootCTA.querySelector('.mapa-web-' + city); 516 if (targetCTA) show(targetCTA); 517 518 // (Opcional) marcar botón activo 519 const ctaBtns = rootCTA.querySelectorAll('.btn-mapa-murcia .elementor-button, .btn-mapa-albacete .elementor-button'); 520 ctaBtns.forEach(b => b.classList.remove('is-active')); 521 (btn.closest('.elementor-button') || btn).classList.add('is-active'); 522 523 e.preventDefault(); 524 return; // no seguimos al caso general 525 } 526 527 // === CASO GENERAL: ciudad + slug (ej. murcia-ford, albacete-mitsubishi) === 528 if (city && slug) { 529 const root = btn.closest('#concesionarios, .seccion-mapas-web') || document; 530 531 // Oculta todos y muestra el que corresponde 532 root.querySelectorAll('.mapa-web').forEach(hide); 533 let target = root.querySelector(`.mapa-web-${city}-${slug}`) 534 || root.querySelector(`.mapa-web-${city}-${slug}-web`); 535 if (target) show(target); 536 537 e.preventDefault(); 538 } 539}); 540 541 542/* ----------------------------------------------------- 543 * 7) Reemplazar por "VENDIDO" repetido en .etiqueta-vendido 544 * --------------------------------------------------- */ 545(function vendedorTag(){ 546 if (!hasDOM) return; 547 548 // Cambia aquà cuántas veces y el separador 549 var veces = 4; // pon 3 si lo prefieres 550 var separador = ' · '; // usa ' ' para espacios o ' · ' para puntos intermedios 551 552 function aplicar(ctx){ 553 (ctx || document) 554 .querySelectorAll('.etiqueta-vendido .elementor-icon-list-text') 555 .forEach(function(el){ 556 var texto = Array(veces).fill('VENDIDO').join(' '); 557 if (el.textContent.trim() !== texto) el.textContent = texto; 558 }); 559 } 560 561 // Al cargar DOM 562 onReady(function(){ aplicar(document); }); 563 564 // Para contenido que inyecta Elementor/WordPress después 565 var mo = new MutationObserver(function(muts){ 566 var toca = muts.some(function(m){ 567 return Array.prototype.some.call(m.addedNodes || [], function(n){ 568 return n.nodeType === 1 && ( 569 (n.matches && n.matches('.etiqueta-vendido, .etiqueta-vendido .elementor-icon-list-text')) || 570 (n.querySelector && n.querySelector('.etiqueta-vendido .elementor-icon-list-text')) 571 ); 572 }); 573 }); 574 if (toca) aplicar(document); 575 }); 576 mo.observe(document.documentElement, { childList: true, subtree: true }); 577})(); 578 579 580/* -----------------------------------------------------
581 * 8) Tabs por HOVER (solo .pestanas-jet-pc) 582 * - Bloqueo inicial (neutraliza la pestaña abierta por defecto) 583 * - Abre por hover (dispara click nativo) 584 * - Cierra al salir del bloque 585 * - ð§ Forzamos que solo haya 1 panel visible (evita âMarcaâ pegada) 586 * --------------------------------------------------- */ 587(function tabsHoverClosePestanasJet(){ 588 var canHover = window.matchMedia && window.matchMedia('(hover: hover) and (pointer: fine)').matches; 589 if (!canHover) return; 590 591 var ROOT = '.pestanas-jet-pc'; 592 var HOVER_DELAY = 120; 593 594 // CSS de bloqueo (una sola vez) 595 (function injectStyleOnce(){ 596 if (document.getElementById('tabs-locked-style')) return; 597 var css = 598 '.pestanas-jet-pc.tabs-locked .elementor-tab-content,'+ 599 '.pestanas-jet-pc.tabs-locked [role="tabpanel"]{display:none !important;}'; 600 var st = document.createElement('style'); 601 st.id = 'tabs-locked-style'; 602 st.textContent = css; 603 document.head.appendChild(st); 604 })(); 605 606 function lock(root){ root && root.classList.add('tabs-locked'); } 607 function unlock(root){ root && root.classList.remove('tabs-locked'); } 608 609 // ð§ Asegura que solo quede 1 panel visible 610 function forceSingleVisible(root){ 611 if (!root) return; 612 613 // 1) ¿Qué tab está activa? 614 var activeTab = 615 root.querySelector('[role="tab"][aria-selected="true"]') || 616 root.querySelector('.elementor-tab-title.elementor-active'); 617 618 // 2) Resolución del panel activo vÃa aria-controls, con fallback a clases activas 619 var activePanel = null; 620 if (activeTab){ 621 var pid = activeTab.getAttribute('aria-controls'); 622 if (pid){ 623 try { activePanel = root.querySelector('#'+CSS.escape(pid)); } 624 catch(_){ activePanel = root.querySelector('#'+pid.replace(/([:.])/g,'\\$1')); } 625 } 626 } 627 if (!activePanel){ 628 activePanel = root.querySelector('.elementor-tab-content.elementor-active, [role="tabpanel"].e-active'); 629 } 630 631 // 3) Mostrar solo el activo; ocultar el resto (inline + ARIA + clases) 632 var panels = root.querySelectorAll('.elementor-tab-content, [role="tabpanel"]'); 633 panels.forEach(function(p){ 634 var isActive = (p === activePanel); 635 p.style.display = isActive ? '' : 'none'; 636 p.setAttribute('aria-hidden', isActive ? 'false' : 'true'); 637 p.classList.toggle('elementor-active', isActive); 638 p.classList.toggle('e-active', isActive); 639 }); 640 } 641 642 function closeAllTabs(root){ 643 if (!root) return; 644 root.querySelectorAll('.elementor-tab-title.elementor-active') 645 .forEach(function(tab){ tab.classList.remove('elementor-active'); tab.setAttribute('aria-selected','false'); }); 646 root.querySelectorAll('[role="tab"][aria-selected="true"], [role="tab"].e-active') 647 .forEach(function(tab){ tab.classList.remove('e-active'); tab.setAttribute('aria-selected','false'); }); 648 // Ocultamos todos los paneles (para arrancar limpio) 649 root.querySelectorAll('.elementor-tab-content, [role="tabpanel"]').forEach(function(panel){ 650 panel.style.display = 'none'; 651 panel.setAttribute('aria-hidden','true'); 652 panel.classList.remove('elementor-active','e-active'); 653 }); 654 } 655 656 function bindOneRoot(root){ 657 if (!root || root._tabsHoverBound) return; 658 root._tabsHoverBound = true; 659 660 // Bloqueo y cierre iniciales 661 lock(root); 662 closeAllTabs(root); 663 664 // Hover en tÃtulos â desbloquear, click y forzar 1 visible 665 var tabs = root.querySelectorAll('.elementor-tab-title, [role="tab"]');
666 tabs.forEach(function(tab){ 667 var timer; 668 tab.addEventListener('mouseenter', function(){ 669 clearTimeout(timer); 670 timer = setTimeout(function(){ 671 unlock(root); 672 tab.click(); 673 // tras el click de Elementor, forzamos coherencia 674 requestAnimationFrame(function(){ forceSingleVisible(root); }); 675 }, HOVER_DELAY); 676 }); 677 tab.addEventListener('mouseleave', function(){ clearTimeout(timer); }); 678 }); 679 680 // También forzamos al hacer click manual (por si hay clicks en lugar de hover) 681 root.addEventListener('click', function(e){ 682 if (e.target.closest('.elementor-tab-title, [role="tab"]')){ 683 unlock(root); 684 requestAnimationFrame(function(){ forceSingleVisible(root); }); 685 } 686 }); 687 688 // Al salir del contenedor: cerrar y bloquear de nuevo 689 root.addEventListener('mouseleave', function(){ 690 closeAllTabs(root); 691 lock(root); 692 }); 693 } 694 695 function initAll(){ document.querySelectorAll(ROOT).forEach(bindOneRoot); } 696 697 // Inicia cuando el DOM está listo 698 if (typeof onReady === 'function') onReady(initAll); 699 else if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initAll, {once:true}); 700 else initAll(); 701 702 // Hooks de Elementor por si renderiza más tarde 703 if (window.elementorFrontend && elementorFrontend.hooks && elementorFrontend.hooks.addAction) { 704 elementorFrontend.hooks.addAction('frontend/element_ready/tabs.default', function(scope){ 705 var node = scope && scope[0]; 706 if (!node) return; 707 if (node.matches && node.matches(ROOT)) bindOneRoot(node); 708 else if (node.querySelectorAll) node.querySelectorAll(ROOT).forEach(bindOneRoot); 709 requestAnimationFrame(function(){ forceSingleVisible(node); }); 710 }); 711 elementorFrontend.hooks.addAction('frontend/element_ready/nested-tabs.default', function(scope){ 712 var node = scope && scope[0]; 713 if (!node) return; 714 if (node.matches && node.matches(ROOT)) bindOneRoot(node); 715 else if (node.querySelectorAll) node.querySelectorAll(ROOT).forEach(bindOneRoot); 716 requestAnimationFrame(function(){ forceSingleVisible(node); }); 717 }); 718 } 719 720 // Fallback ante inserciones dinámicas 721 var mo = new MutationObserver(function(muts){ 722 var touched = false; 723 muts.forEach(function(m){ 724 (m.addedNodes || []).forEach(function(n){ 725 if (n.nodeType === 1 && ((n.matches && n.matches(ROOT)) || (n.querySelector && n.querySelector(ROOT)))) touched = true; 726 }); 727 }); 728 if (touched) { initAll(); requestAnimationFrame(initAll); } 729 }); 730 mo.observe(document.documentElement, { childList:true, subtree:true }); 731})(); 732 733/* ----------------------------------------------------- 734 * 9) Si existe .jet-sombreado en la página, 735 * cambiar header-web a position: inherit !important 736 * --------------------------------------------------- */ 737(function setHeaderPositionOnJetSombreado(){ 738 function applyHeaderStyle() { 739 var header = document.querySelector('.header-web'); 740 var sombreado = document.querySelector('.jet-sombreado'); 741 if (header && sombreado) { 742 header.style.setProperty('position', 'inherit', 'important'); 743 } 744 } 745 746 // Ejecutar al cargar 747 document.addEventListener('DOMContentLoaded', applyHeaderStyle); 748 749 // Por si Elementor inyecta .jet-sombreado después 750 var mo = new MutationObserver(function(muts){ 751 muts.forEach(function(m){ 752 (m.addedNodes || []).forEach(function(n){ 753 if (n.nodeType === 1 && n.matches && n.matches('.jet-sombreado')){ 754 applyHeaderStyle(); 755 } else if (n.querySelector && n.querySelector('.jet-sombreado')){ 756 applyHeaderStyle(); 757 } 758 }); 759 }); 760 }); 761 mo.observe(document.documentElement, { childList: true, subtree: true }); 762})(); 763 764document.addEventListener("DOMContentLoaded", function() { 765 const seccion = document.querySelector(".seccion-promociones-extra"); 766 const cols = seccion.querySelectorAll(".col-uno, .col-dos, .col-tres"); 767 768 const todasVacias = Array.from(cols).every(col => { 769 const titulo = col.querySelector(".titulo"); 770 return !titulo || titulo.textContent.trim() === ""; 771 }); 772 773 if (todasVacias) { 774 seccion.style.marginTop = "0"; 775 } 776}); 777 778/* ----------------------------------------------------- 779 * 10) Mantener los filtros de los coches al entrar a la plantilla 780 * de uno de ellos y volver atras 781 * --------------------------------------------------- */ 782(function () { 783 // === DEBUG: pon a true para ver logs en consola === 784 var DEBUG = true; 785 function log(){ if(DEBUG && window.console){ console.log.apply(console, ['[KeepFilters]'].concat([].slice.call(arguments))); } } 786 787 // Ruta del listado (ajusta si cambia)
788 var LISTING_PATH = '/coches-segunda-mano-y-ocasion/'; 789 var KEY = 'jetsf:lastFiltersURL:' + LISTING_PATH; 790 791 // Solo actuamos en el listado 792 if (window.location.pathname !== LISTING_PATH) { return; } 793 794 // Obtén el query actual o, si no existe (AJAX-only), el último guardado 795 function getActiveQuery() { 796 if (window.location.search) return window.location.search; 797 try { 798 var saved = sessionStorage.getItem(KEY); 799 if (!saved) return ''; 800 var u = new URL(saved, window.location.origin); 801 return u.search || ''; 802 } catch(e){ return ''; } 803 } 804 805 // Guarda la URL filtrada actual (solo si hay query) 806 function saveCurrentFilteredURL() { 807 try { 808 if (window.location.search) { 809 sessionStorage.setItem(KEY, window.location.href); 810 log('Saved URL', window.location.href); 811 } 812 } catch(e){} 813 } 814 815 // Parcheo de enlaces 816 var SEL = 'a[href*="/vehiculos/"]'; 817 818 function addQueryToUrl(u, qs) { 819 try { 820 var url = new URL(u, window.location.origin); 821 var cur = new URLSearchParams(qs); 822 cur.forEach(function (v, k) { 823 if (!url.searchParams.has(k)) url.searchParams.set(k, v); 824 }); 825 return url.toString(); 826 } catch (e) { return u; } 827 } 828 829 function patchLinks(root) { 830 var qs = getActiveQuery(); 831 if (!qs) { log('No query to apply'); return 0; } 832 var links = (root || document).querySelectorAll(SEL); 833 var patched = 0; 834 links.forEach(function(a){ 835 var newHref = addQueryToUrl(a.href, qs); 836 if (a.href !== newHref) { a.href = newHref; patched++; } 837 }); 838 if (patched || DEBUG) log('Patched links:', patched, 'Total found:', links.length, 'Using qs:', qs); 839 return patched; 840 } 841 842 // Ejecutar cuanto antes y después 843 function runAll(){ patchLinks(document); saveCurrentFilteredURL(); } 844 845 if (document.readyState === 'loading') { 846 document.addEventListener('DOMContentLoaded', runAll); 847 } else { 848 runAll(); 849 } 850 window.addEventListener('load', runAll); 851 window.addEventListener('pageshow', function(e){ if(e.persisted) runAll(); }); 852 853 // Reintentos durante 6s por si el grid llega por AJAX en anónimos 854 (function retryPatch(maxMs){ 855 var start = performance.now(); 856 (function loop(){ 857 var p = patchLinks(document); 858 if (p > 0) return; 859 if (performance.now() - start < maxMs) setTimeout(loop, 200); 860 })(); 861 })(6000); 862 863 // Eventos JetSmartFilters (cuando hay Apply/AJAX) 864 var jsf = (window.JetSmartFilters && window.JetSmartFilters.events) ? window.JetSmartFilters.events : null; 865 if (jsf && jsf.subscribe) { 866 ['setFilters/after','provider/load','ajax/success'].forEach(function(evt){ 867 jsf.subscribe(evt, function(){ setTimeout(runAll, 0); }); 868 }); 869 log('JSF events hooked'); 870 } else { 871 log('JSF events NOT available'); 872 } 873 874 // Evento JetEngine (Listing Grid) 875 document.addEventListener('jet-engine/ajax-request/success', function(){ setTimeout(runAll, 0); }); 876 877 // Mutations por si hay infinitescroll/paginación 878 try { 879 var mo = new MutationObserver(function(){ patchLinks(document); }); 880 mo.observe(document.documentElement, { childList: true, subtree: true }); 881 log('MutationObserver running'); 882 } catch(e){} 883 884 // Click/auxclick: asegura params justo antes de navegar 885 document.addEventListener('click', function (ev) { 886 var a = ev.target && ev.target.closest ? ev.target.closest(SEL) : null; 887 if (!a) return; 888 var qs = getActiveQuery(); 889 if (!qs) return; 890 var patched = addQueryToUrl(a.href, qs); 891 if (a.href !== patched) a.href = patched; 892 }, true); 893 document.addEventListener('auxclick', function (ev) { 894 if (ev.button !== 1) return; 895 var a = ev.target && ev.target.closest ? ev.target.closest(SEL) : null; 896 if (!a) return; 897 var qs = getActiveQuery(); 898 if (!qs) return; 899 var patched = addQueryToUrl(a.href, qs); 900 if (a.href !== patched) a.href = patched; 901 }, true); 902 903 // Marca de vida para que puedas comprobar que el script carga en incógnito 904 window.gmKeepFilters = { alive: true }; 905 log('Script loaded, path OK:', window.location.pathname); 906})(); 907 908/* ----------------------------------------------------- 909 * 11) Mantener los filtros de los coches al entrar a la plantilla 910 * de uno de ellos y volver atras 911 * --------------------------------------------------- */
912/* Elimina del DOM el .jet-listing-grid__item cuya .card-coche 913 tenga .precio p = "VENDIDO" SOLO si la URL tiene: 914 ?jsf=jet-engine&sort=orderby:meta_value_num;order:ASC;meta_key:pvp_contado 915*/ 916(function () { 917 var GRID_ITEM = '.jet-listing-grid__item'; 918 var CARD = '.card-coche'; 919 var PRICE_P = '.precio p'; 920 var SOLD_WORD = 'VENDIDO'; 921 922 function isPriceAscActive() { 923 var sp = new URLSearchParams(window.location.search || ''); 924 if (sp.get('jsf') !== 'jet-engine') return false; 925 var sort = sp.get('sort') || ''; 926 var map = {}; 927 sort.split(';').forEach(function (pair) { 928 var i = pair.indexOf(':'); 929 if (i > -1) map[pair.slice(0, i).trim()] = pair.slice(i + 1).trim(); 930 }); 931 var orderbyOK = map.orderby === 'meta_value_num' || map.orderby === 'meta_value'; 932 var orderOK = (map.order || '').toUpperCase() === 'ASC'; 933 var keyOK = map.meta_key === 'pvp_contado'; 934 return orderbyOK && orderOK && keyOK; 935 } 936 937 function pruneSoldGridItems(root) { 938 if (!isPriceAscActive()) return; 939 940 (root || document).querySelectorAll(GRID_ITEM).forEach(function (gi) { 941 var card = gi.querySelector(CARD); 942 if (!card) return; 943 944 var priceNode = card.querySelector(PRICE_P); 945 if (!priceNode) return; 946 947 var txt = (priceNode.textContent || '').trim().toUpperCase(); 948 var isSold = txt.indexOf(SOLD_WORD) > -1; 949 950 if (isSold) { 951 gi.parentNode && gi.parentNode.removeChild(gi); 952 } 953 }); 954 955 // Reflow por si el grid calcula alturas 956 setTimeout(function () { 957 window.dispatchEvent(new Event('resize')); 958 document.dispatchEvent(new CustomEvent('gm/grid-updated')); 959 }, 0); 960 } 961 962 function run(){ pruneSoldGridItems(document); } 963 964 // Al cargar 965 if (document.readyState === 'loading') { 966 document.addEventListener('DOMContentLoaded', run); 967 } else { 968 run(); 969 } 970 window.addEventListener('load', run); 971 window.addEventListener('pageshow', run); 972 973 // Reaplicar tras AJAX de JetSmartFilters 974 var jsf = (window.JetSmartFilters && window.JetSmartFilters.events) ? window.JetSmartFilters.events : null; 975 if (jsf && jsf.subscribe) { 976 ['setFilters/after','provider/load','ajax/success'].forEach(function (evt) { 977 jsf.subscribe(evt, function(){ setTimeout(run, 0); }); 978 }); 979 } 980 981 // Reaplicar tras AJAX de JetEngine (Listing Grid) 982 document.addEventListener('jet-engine/ajax-request/success', function(){ setTimeout(run, 0); }); 983 984 // Reaplicar si el DOM cambia (paginación / load more / lazy) 985 try { 986 var mo = new MutationObserver(function(){ run(); }); 987 mo.observe(document.querySelector('.jet-listing-grid__items') || document.documentElement, { childList: true, subtree: true }); 988 } catch(e){} 989 990 // Si cambias el orden (back/forward), reevaluar 991 window.addEventListener('popstate', run); 992})(); 993 994/* ----------------------------------------------------- 995 * 1) Enlaces a campos ACF en la URL de Recambios 996 * --------------------------------------------------- */ 997document.addEventListener('DOMContentLoaded', function() { 998 // Selecciona todos los bloques con clase .enlace-email 999 document.querySelectorAll('.enlace-email').forEach(function(block) { 1000 // Dentro de cada bloque, busca los enlaces con href="#" o sin href 1001 block.querySelectorAll('a[href="#"], a:not([href])').forEach(function(a) { 1002 const textEl = a.querySelector('.elementor-icon-list-text'); 1003 if (!textEl) return; 1004 1005 const email = (textEl.textContent || '').trim(); 1006 1007 // Valida si el texto es un email 1008 if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { 1009 a.setAttribute('href', 'mailto:' + email); 1010 a.removeAttribute('target'); // quita target="_blank" si lo hay 1011 a.setAttribute('rel', 'noopener'); 1012 } 1013 }); 1014 }); 1015}); 1016 1017document.addEventListener('DOMContentLoaded', function() { 1018 1019 // --- EMAIL --- 1020 document.querySelectorAll('.enlace-email').forEach(function(block) { 1021 block.querySelectorAll('a[href="#"], a:not([href])').forEach(function(a) { 1022 const textEl = a.querySelector('.elementor-icon-list-text'); 1023 if (!textEl) return; 1024 1025 const email = (textEl.textContent || '').trim(); 1026 if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { 1027 a.setAttribute('href', 'mailto:' + email); 1028 a.removeAttribute('target'); 1029 a.setAttribute('rel', 'noopener'); 1030 } 1031 }); 1032 }); 1033 1034 // --- WHATSAPP --- 1035 document.querySelectorAll('.enlace-whatsapp').forEach(function(block) { 1036 block.querySelectorAll('a[href="#"], a:not([href])').forEach(function(a) { 1037 const textEl = a.querySelector('.elementor-icon-list-text'); 1038 if (!textEl) return; 1039 1040 let phone = (textEl.textContent || '').trim(); 1041 phone = phone.replace(/[^0-9]/g, ''); // limpiar caracteres no numéricos 1042 1043 if (phone.length >= 6) { 1044 const message = encodeURIComponent('Hola! ð Quisiera más información.'); 1045 const url = 'https://wa.me/' + phone + '?text=' + message; 1046 1047 a.setAttribute('href', url); 1048 a.setAttribute('target', '_blank'); // abrir en nueva pestaña 1049 a.setAttribute('rel', 'noopener'); 1050 } 1051 }); 1052 }); 1053 1054 // --- TELÃFONO --- 1055 document.querySelectorAll('.enlace-telefono').forEach(function(block) { 1056 block.querySelectorAll('a[href="#"], a:not([href])').forEach(function(a) { 1057 const textEl = a.querySelector('.elementor-icon-list-text'); 1058 if (!textEl) return; 1059 1060 let phone = (textEl.textContent || '').trim(); 1061 phone = phone.replace(/[^0-9+]/g, ''); // permite mantener el prefijo + 1062 1063 if (phone.length >= 6) { 1064 a.setAttribute('href', 'tel:' + phone); 1065 a.removeAttribute('target'); 1066 a.setAttribute('rel', 'noopener'); 1067 } 1068 }); 1069 }); 1070 1071}); 1072 1073}
1073)(window, document); 1074 1075 1076 1077
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.