PageSourceSearch

https://grupomarmo.es/wp-content/themes/hello-theme-child-master/js/custom_dj.js?ver=7.1.2

js grupomarmo.es collected 2026-10-04 02:40:42 UTC 42,093 bytes, 1,077 lines download raw bytes

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.