/*
Theme Name: Sutil Fragancia Astra Child
Theme URI: https://sutilfragancia.com/
Description: Child theme de Astra optimizado para Sutil Fragancia, compatible con WooCommerce y FunnelKit.
Author: Sutil Fragancia
Template: astra
Version: 3.2.75
Text Domain: sutil-fragancia-astra-child
*/

/*
 * v3.2.75 — Fix cart badge del header (contador de productos en el carrito):
 *   User reportó "no aparece el número". Causa: el badge usaba
 *   `background: var(--sf-accent)` + `color: var(--sf-text)` (mobile) o
 *   `var(--sf-accent-contrast, var(--sf-bg))` (desktop). Dependiendo de la paleta
 *   activa, los dos colores podían terminar siendo oscuros similares → texto
 *   invisible sobre fondo oscuro. Fix: hardcoded a bg rojo (`var(--sf-sale, #CC3A2F)`)
 *   + texto blanco (`var(--sf-sale-contrast, #FFFFFF)`) con `!important`. Es la
 *   convención universal para badges de carrito (Amazon, Mercado Libre, todos usan
 *   rojo/blanco) y garantiza contraste AA sin importar la paleta que elija el admin.
 * v3.2.74 — Tamaños de texto del hero independientes por device (Opción A).
 *   User reportó: "un tamaño se puede ver bien en computadora pero pésimo en celular".
 *   Antes el campo `size_custom` aplicaba el mismo tamaño en todas las resoluciones.
 *   Ahora 3 campos nuevos per-slide ("mobile" suffix):
 *     · Eyebrow — tamaño mobile
 *     · Título — tamaño mobile
 *     · Subtítulo — tamaño mobile
 *   Aplica solo en `@media (max-width: 920px)`. Si vacío, cae al valor desktop.
 *   Implementación: el render emite CSS custom properties (--sf-eb-size-d, -m, etc)
 *   inline en el elemento. Las reglas de homepage.css consumen esas vars con media
 *   query. Esto permite override via media query (que con font-size inline no
 *   funciona). 15 campos nuevos (3 mobile × 5 slides).
 * v3.2.73 — Personalización tipográfica completa del hero + fix eyebrow editorial:
 *   (A) Eyebrow nuevo per-slide en hero (antes no existía campo). Se renderiza
 *   arriba del título con clase .sf-hero-slide__eyebrow. Opcional.
 *   (B) Typography fine-grained per-slide para los 3 elementos (eyebrow/headline/sub).
 *   Cada uno con: color · tamaño custom (cualquier valor CSS: "48px", "3rem",
 *   "clamp(24px, 5vw, 56px)") · peso (300-900) · estilo (normal/italic). 10 campos
 *   nuevos por slide × 5 slides = 50 controles adicionales en Customizer.
 *   (C) HTML seguro dentro de eyebrow/headline/sub: el admin ahora puede escribir
 *   `<strong>negrita</strong>`, `<em>cursiva</em>`, `<br>`, `<span style="color:X">x</span>`
 *   dentro de los campos. wp_kses con whitelist estricto (strong/b/em/i/u/br/span/small).
 *   Sanitización en save + render con wp_kses para no ejecutar JS ni atributos inseguros.
 *   (D) FIX: el eyebrow del editorial banner no respetaba el color configurado.
 *   Causa: CSS line 1077 hardcoded `color: var(--sf-accent)`. Ahora:
 *   `color: var(--sf-editorial-eyebrow-color, var(--sf-editorial-text, var(--sf-accent)))`.
 *   + Campo nuevo `sf_ed_banner_eyebrow_color` en Customizer › Editorial Banner para
 *   darle un color independiente al eyebrow. Si vacío, hereda el color del texto
 *   general; si ambos vacíos, cae al accent como antes.
 * v3.2.72 — Dos fixes del hero:
 *   (1) CTA no funcionaba al click: `.sf-hero-slide__overlay` tenía `z-index:1`
 *   sobre todo el slide sin `pointer-events:none`, así que tapaba los clicks
 *   al <a> del CTA (el overlay se los comía). Fix: overlay con
 *   `pointer-events: none` + `.sf-hero-slide__inner` con `position: relative;
 *   z-index: 2` para quedar por encima del overlay en z-stack.
 *   (2) El padding-bottom 56px/44px de v3.2.71 dejaba el botón demasiado arriba
 *   (casi en el medio del slide). Reducido a 38px desktop / 30px mobile para que
 *   el CTA quede JUSTO arriba de los dots, no "flotando" al medio.
 * v3.2.71 — Fix: con valign "bottom" en el hero, el CTA quedaba detrás de los dots
 *   de navegación del carrusel. Causa: los dots viven en `position: absolute; bottom:
 *   14px` (desktop) / `10px` (mobile) con ~22-26px de alto, y `valign: bottom` en el
 *   slide hacía `justify-content: flex-end` llevando el contenido hasta el fondo.
 *   Fix: agregar `padding-bottom: 56px` (desktop) / `44px` (mobile) al
 *   `.sf-hero-slide__inner` cuando tiene la clase `--valign-bottom`. Esto reserva el
 *   espacio para los dots y deja respiro. Solo aplica cuando valign es "bottom".
 * v3.2.70 — Agregado campo "Padding del botón (relleno interno)" per-slide en el hero.
 *   User reportó: "el botón está muy pequeño justo al texto". El padding default de
 *   los CTAs era demasiado apretado. Ahora en Customizer › Hero Slide N aparece un
 *   campo nuevo donde el admin puede escribir "14px 28px", "16px 32px", etc. para
 *   dar más respiro al texto del botón. Vacío = default del estilo del CTA.
 *   Se aplica inline en el <a>/<span> del CTA, gana por specificity.
 * v3.2.69 — Personalización fina de Hero + Banners + Editorial + Chips compactos:
 *   (A) Hero CTA fix color — ahora inline style en el <a>, gana sobre cualquier CSS.
 *   (B) Hero CTA render con solo texto O solo URL (antes exigía ambos). Si solo URL,
 *   label fallback "Ver más". Si solo texto, renderea como <span> (no link).
 *   (C) Hero per-slide — 7 campos nuevos por slide en Customizer › Hero Slide N:
 *   headline_color, sub_color, cta_bg, cta_text_color, cta_radius, content_padding,
 *   content_gap. Todos opcionales (vacío = hereda global/paleta). Emitidos como
 *   inline style en el render para ganar specificity.
 *   (D) Banners per-banner — 7 campos por banner en Customizer › Banners:
 *   title_color, meta_color, title_size (sm/md/lg/xl), text_align (L/C/R),
 *   text_vpos (top/center/bottom), padding, gap. Los de align/vpos/size aplican
 *   via modifier classes `sf-hp-banner--align-*`, `--vpos-*`, `--title-*`. El padding
 *   y gap van inline en el __caption.
 *   (E) Editorial banner — padding + gap configurables en Customizer › Editorial.
 *   También: CTAs ahora renderean con solo texto o solo URL (consistente con hero).
 *   (F) Chips de categoría mobile MUCHO más compactos: alto 28px (antes ~43px),
 *   gap 6px, padding 5×12, font 12px. Ocupa ~35% menos de espacio vertical.
 * v3.2.68 — Hotfix: el footer seguía sin mostrar lo que estaba en el customizer.
 *   Causa: el condicional `if ( $col_title && ! empty($links) )` exigía que la
 *   columna tuviera TÍTULO Y al menos 1 link. Si el admin tenía links configurados
 *   pero el título vacío (o viceversa), la columna se descartaba silenciosamente.
 *   Además el link individual requería label Y url — un link con solo label (sin
 *   url) también se descartaba.
 *   Fix: (a) la columna renderea si tiene título O links (antes era AND).
 *   (b) el link se acepta con solo label (si no hay url, renderea como <span>
 *   texto plano en vez de <a>). (c) el título ahora es opcional en el HTML: si no
 *   hay, la lista arranca sin <h4>. De esta forma TODO lo que el admin configuró
 *   en customizer se ve reflejado en el footer.
 * v3.2.67 — Correcciones sobre v3.2.66 + feature nuevo de colores de botones:
 *   (1) REVERT Bug 4 de v3.2.66: testimonios mobile vuelven al grid 2 cols × 3 filas
 *   (antes lo había convertido a carrusel horizontal por error).
 *   (2) REVERT Bug 10 de v3.2.66 + nuevo target correcto: badge "−58%" sobre la
 *   imagen del card vuelve a ser NEGRO (palette default). El ROJO ahora SÍ se aplica
 *   al porcentaje que aparece junto al precio (.sf-hp-prod-card__save dentro de
 *   .sf-hp-prod-card__price). No toca tags, savings del PDP, ni badges status.
 *   (3) REVERT defaults hardcoded del footer (v3.2.59): se habían metido links
 *   inventados (Caballero/Dama/Árabes/Envíos/Devoluciones/Términos...) que NO son
 *   lo que el admin configura. Ahora el footer renderiza solo lo que esté en el
 *   customizer. Si no hay columnas configuradas, solo aparece logo + tagline + redes
 *   + copy (comportamiento original del theme).
 *   (4) NUEVO — 10 campos en el customizer para configurar colores de botones sobre
 *   banners/imágenes (Opción C "Mix" que se discutió). 2 campos por cada tipo de CTA:
 *   Hero (slides), Editorial primario, Editorial ghost, Banners de categoría, Popup.
 *   Cada par = color fondo + color texto. Defaults vacíos → hereda var(--sf-accent)
 *   / var(--sf-accent-contrast) de la paleta (comportamiento actual preservado).
 *   Mecanismo: functions.php emite CSS custom properties en :root cuando hay valor;
 *   CSS rules en homepage.css consumen var(--sf-x-cta-bg, var(--sf-accent)).
 *   (5) Quitar bg NEGRO de la sección de testimonios en la home (.sf-hp-testimonios).
 *   Antes tenía inversión hardcoded bg=var(--sf-text) + color=var(--sf-bg) que
 *   rompía legibilidad cuando las cards tenían bg claro pero texto dark. Ahora
 *   bg transparent, texto palette normal, botón "Ver más opiniones" con borde oscuro
 *   sobre bg claro + hover sólido.
 * v3.2.66 — Auditoría homepage: 10 bugs de código arreglados:
 *   (1) Hero dots — al cargar ningún dot tenía clase active (JS solo lo aplicaba al
 *   cambiar de slide). Ahora dots[0] se marca en el init.
 *   (2) Hero desktop ocupaba 1072px (aspect 16/9 de 1920 ancho) > viewport 867 →
 *   cliente no veía nada debajo. Cap a max-height: 80vh. Mobile sin cambios.
 *   (3) Footer solo renderizaba 1 columna (Tienda). Causa: get_theme_mod retorna ''
 *   guardado en vez del default cuando el key existe. Fallback manual para title/label/url.
 *   (4) Testimonios mobile = 2358px (6 cards × ~500 en 2 cols). Convertido a carrusel
 *   horizontal scroll-snap en <768px con cards 75vw. Desktop/tablet sigue grid.
 *   (5) Samples no mostraba CTA "Ver todas" porque sf_samples_cta_url default era ''.
 *   Fallback a get_term_link(slug) o home_url('/product-category/{slug}/').
 *   (6) URLs chips y drawer hardcoded a producción (sutilfragancia.com). Helper
 *   sf_normalize_site_url() rewriter de host al current (home_url). En prod no-op.
 *   (7) Instagram iframe sin cap de altura — 1116 desktop / 633 mobile. Cap 600/460.
 *   (8) Precio de card con trailing whitespace ("$2,350 $990    "). Template estaba
 *   con newlines + indent dentro del <p>. trim() + template sin whitespace interno.
 *   (9) Back to top button faltante. Nuevo FAB bottom-right 44px con smooth-scroll.
 *   Aparece tras 400px scroll. En mobile sube a bottom:86px para no tapar sticky CTA.
 *   (10) Badge porcentaje de descuento (.sf-hp-prod-card__badge) ahora ROJO
 *   (var(--sf-sale)). Target con :not(--status) para no afectar al badge bestseller.
 *   NO toca etiquetas, tags, savings del PDP, ni cualquier otro elemento.
 * v3.2.65 — Fix mínimo CSS: botón "Filtrar" de Astra se ocultaba detrás del header
 *   en mobile. User clarificó: "el problema solo es que se oculta el botón detrás
 *   de la cabecera" — no era necesario reemplazar todo el filtro.
 *   Causa: botón con `position: sticky; top: 60px; z-index: 5` vs header fixed
 *   z-index: 10000 → el sticky intentaba pegarse detrás del header. Además el
 *   parent wrapper mide 43px (igual que el botón) → el sticky no tenía zona útil.
 *   Fix mínimo: subir `top` a 70px mobile / 84px desktop (debajo del header +
 *   respiro), z-index a 10001 (arriba del header), y `overflow: visible` en los
 *   ancestor wrappers. Revertidos los cambios de v3.2.64 (panel z-index arriba) y
 *   el filtro custom que se estaba construyendo — solución simple solo CSS.
 * v3.2.64 — Filtro catálogo mobile: panel por encima del header + botón de cerrar
 *   (Opción A + C). User reportó: "el filtro desplegable se sigue ocultando detrás
 *   de la cabecera en mobile" con restricción "prohibido hacer la cabecera transparente".
 *   Causa: `.astra-off-canvas-sidebar-wrapper` tenía z-index 99, header mobile 10000.
 *   Fix dos capas:
 *   (1) CSS: panel z-index 10002, overlay z-index 10001. Panel tiene bg sólido
 *   (--sf-bg) y shadow para indicar overlay. Body scroll bloqueado cuando abierto.
 *   Header no transparente — simplemente cubierto por el panel/overlay sólidos.
 *   (2) JS: inyecta barra sticky arriba del panel con título "Filtros" (Cormorant
 *   22px) + botón ✕ de cerrar (40px pill con hover). El botón intenta (a) clickear
 *   el overlay de Astra, (b) remover classes `ast-sidebar-flyout-active`,
 *   `ast-off-canvas-active`, `ast-html-off-canvas-open` como fallback.
 *   MutationObserver + event listener en `.astra-shop-filter-button` aseguran que
 *   la barra se inyecte tanto si el panel viene pre-rendered como si se inserta
 *   dinámicamente al clickear "Filtrar".
 * v3.2.63 — Hotfix: cabecera NO se ocultaba en checkout. Verificado en `/checkout-2/`
 *   (FunnelKit) y confirmado en cliente real. Causa doble:
 *   (i) Las reglas CSS `body.sf-checkout-minimal header { display:none }` estaban en
 *   `pdp-notino.css` que SOLO carga en páginas de producto (is_product()). En checkout
 *   no se cargaba el archivo, así que las reglas nunca se aplicaban.
 *   (ii) Los selectores cubrían Astra (#masthead, .site-header, .ast-*) y FunnelKit
 *   (.wfacp-header, .wffn-oc-header) pero NO incluían el header custom del theme
 *   (.sf-desktop-header y .sf-mobile-header) que es el que realmente renderiza.
 *   Fix: bloque nuevo en homepage.css (carga global) con todos los selectores del
 *   header custom + Astra + FunnelKit + footer y shipping bar, body padding-top:0.
 *   Cubre /checkout/, /checkout-2/, /finalizar-compra/ y cualquier página con la
 *   clase sf-checkout-minimal. Funciona tanto en staging como en producción.
 * v3.2.62 — Fix sticky gallery "no se detiene al iniciar el primer carrusel, solo se
 *   oculta detrás". Causa: CSS `position: sticky` en grid items respeta al PARENT
 *   (div.product), no al grid cell. div.product contiene TODO el contenido del PDP
 *   (gallery + summary + support + pairs + testimonios + recently viewed + shipping)
 *   ≈ 4226px. La galería sticky seguía pegada hasta el fondo del div.product,
 *   flotando sobre los carruseles siguientes.
 *   Fix: JS `initStickyGalleryRelease()` en pdp-sticky-cta.js que escucha scroll y
 *   aplica translateY negativo a la galería cuando `supportPanel.bottom` cruza el
 *   punto donde terminaría la galería sticky. Efecto: galería sticky mientras el user
 *   lee summary+acordeones, y se despega naturalmente al final del support panel
 *   (antes de los pair cards). Mobile no afectado (matchMedia 921px).
 * v3.2.61 — Galería PDP sticky en desktop + espacio header→PDP reducido:
 *   (1) Gallery STICKY: la columna de imágenes (col 1) ahora queda fija en el viewport
 *   mientras el usuario scrollea por la info (título/precio/CTA/acordeones). Se despega
 *   automáticamente cuando el cell del grid termina (último acordeón del support panel) y
 *   empieza el carrusel "Combina muy bien con este perfume". Implementación: position:
 *   sticky, top: 78px (header fixed 70 + 8 respiro), align-self: start. Como la galería
 *   ya span las 2 filas del grid (info + support) por el fix v3.2.60, el sticky
 *   naturalmente dura hasta el final del support panel.
 *   (2) Espacio gigante entre cabecera y contenido del PDP eliminado. Antes: 184px de
 *   whitespace (body 70 + content 20 + site-main 30 + breadcrumb 24 + div.product 40).
 *   Ahora: ~60px. Cambios: `.site-main` y `.content-area` padding-top:0, breadcrumb
 *   padding-top 24→12, div.product padding-top 40→12. Desktop only (mobile sin cambios).
 * v3.2.60 — Hotfix reportado por el cliente: "los acordeones actuales se renderizan
 *   atrás del título del producto". Causa: el grid-template-areas del contenedor
 *   div.product era solo `"gallery info"` (1 fila). Mi v3.2.58 puso
 *   `grid-area: info` al support panel → TANTO summary COMO support panel tenían la
 *   misma named area → CSS Grid los stackeaba en la misma celda (ambos en y=241
 *   x=1074 w=560) con support panel pisando el título. Fix: extender
 *   grid-template-areas a 2 filas `"gallery info" "gallery support"` y asignar
 *   cada sección a su propia área. Gallery ahora span 2 filas automáticamente.
 * v3.2.59 — Auditoría real de 10 recorridos de compra. Cuatro ALTOS confirmados + fixes:
 *   (A1) PDP desktop: imagen del frasco seguía renderizando 818×1022 con 204px de banda
 *   blanca top/bottom. El wrapper tenía aspect 1/1 (fix v3.2.58) pero pdp-notino.css:412
 *   dentro de `@media (min-width:921px)` forzaba `aspect-ratio: 4/5 !important` en la IMG,
 *   sobreescribiendo al wrapper. Cambiado a 1/1. Además padding interno 48px → 24px
 *   (era excesivo y hacía ver el frasco diminuto).
 *   (A2) Editorial banner de home: `.sf-hp-editorial` renderizaba 572px de alto pero
 *   sin imagen ni background → hueco vacío entre secciones. Ahora: (i) si admin no tiene
 *   ni imagen ni CTA ni texto, la función retorna sin renderizar nada. (ii) si hay texto
 *   pero sin imagen, fallback visual con gradient del color overlay y monograma "SF"
 *   a 96px opacity 18% para no dejar la columna derecha del split vacía.
 *   (A3) Footer columnas vacías: las 3 columnas (Tienda/Ayuda/Legal) quedaban sin links
 *   si el admin no las había configurado en customizer → footer mostraba solo logo,
 *   redes sociales y copy. Ahora: defaults sensatos con 5/5/3 links por columna apuntando
 *   a product-category/ y pages comunes. Admin sigue pudiendo overridear cada campo.
 *   (A6) Mi Cuenta `/mi-cuenta/`: página fue creada con Elementor sin el shortcode
 *   `[woocommerce_my_account]` → usuario logueado llega y NO ve navegación, pedidos,
 *   direcciones. Fix via filter `the_content` que detecta is_account_page() sin el
 *   shortcode y lo inyecta al final del output de Elementor. Elementor sigue controlando
 *   el header/hero custom del cliente y WC aparece abajo.
 *   Falsos positivos descartados: A4 (sidebar h=0 es intencional — body.ast-no-sidebar),
 *   A5 ("Sorted by popularity" está dentro de .screen-reader-text oculto), A7 (WC tabs
 *   eliminados intencionalmente en v3.2.41 por pedido del user), A8 (urgency/stock son
 *   config per-product, no bug del theme).
 * v3.2.58 — Seis fixes visuales reportados por el cliente:
 *   (1) Footer solo aparecía en home. Antes sf_hp_branded_footer() se llamaba solo en
 *   front-page.php. Ahora se hookea a wp_footer (prioridad 5) con guard de doble render
 *   para que aparezca en TODA la web (PDP, catálogo, cuenta, etc.). Saltea checkout
 *   con body.sf-checkout-minimal para no romper UX de compra.
 *   (2) Botón "Añadir al carrito" en cards de "combina muy bien con este perfume" (pair
 *   cards) — removido. La card entera sigue siendo clicable vía .sf-pair-card__media.
 *   (3) Texto PDP muy apretado. Line-height del título era 41/42 (lh<fs). Subido a 1.25
 *   en title, 1.5 en acordeones, 1.65 en bodies, 1.7 en pitch card. Separación 18px entre
 *   elementos del summary en desktop.
 *   (4) Cabecera en PDP se hacía más grande por logo más ancho (374px vs 240px en home).
 *   Causa: Astra aplica body.ast-inherit-site-logo-transparent en PDP que usa un archivo
 *   de logo diferente. Fix: max-width 240px / max-height 56px forzado en todos los
 *   contextos del header desktop. Mobile: 160x40.
 *   (5) Badge "Ahorras" sobre imagen cargaba en dorado (flash) y después negro (FOUC).
 *   Causa: pdp.css:3512 tenía `background: var(--sf-stars)` (dorado) que la cascada
 *   resolvía a negro en pdp.css:3688. Fix: pdp.css ahora usa `var(--sf-savings-bg, #1C1A17)`
 *   desde el primer paint. Plus override en pdp-notino.css con alta especificidad.
 *   (6) Support panel (acordeón info) en desktop iba full-width spanning 2 cols,
 *   rompiendo el layout. Fix: `grid-column: 2` para meterlo en la columna derecha junto
 *   al summary, max-width 560 alineado. Plus: imagen de producto aspect-ratio 4/5 →
 *   1/1 (los archivos son 713×713 square, el 4/5 añadía barras blancas top/bottom que
 *   el user interpretaba como recorte). Padding interno 48px desktop / 16px mobile.
 * v3.2.57 — Tres fixes críticos reportados por el cliente:
 *   (1) PDP mobile rota: la imagen del frasco NO se veía (hueco blanco). Causa: WooCommerce
 *   fija opacity:0 + transition:0.25s en .woocommerce-product-gallery al cargar y confía en
 *   su JS flexslider para hacer fade a 1. Nuestra cascada de setProperty() en functions.php
 *   sobre gallery/flex-viewport/wrapper interrumpía la transition y dejaba la opacity
 *   congelada en 0. Fix: forzar `opacity:1 !important; transition:none !important` en
 *   todos los wrappers de galería (pdp-notino.css), así no hay transición que se pueda
 *   congelar.
 *   (2) Grid de reseñas en mobile mostraba 1 sola columna. User pidió 2 columnas mobile.
 *   Cambiado JS (functions.php:4647) a: `window.innerWidth >= 1200 ? repeat(4,1fr) : 1fr 1fr`
 *   (eliminado el breakpoint intermedio de 768).
 *   (3) Cambiar paleta a "Personalizada" en customizer no aplicaba los colores. Causa:
 *   homepage.css tenía un bloque `:root { --sf-bg: #FFFFFF; --sf-text: #000000; ... }`
 *   que cargaba DESPUÉS del <style id="sf-palette-vars"> inyectado por palette-system.php
 *   en wp_head → el :root hardcoded ganaba la cascada y overrideaba la paleta del customizer.
 *   Fix: removido ese bloque :root de homepage.css; palette-system.php ya tiene defaults
 *   internos (?:) si algún theme_mod viene vacío, así que el fallback era redundante.
 * v3.2.56 — (1) Footer duplicado resuelto: si .sf-hp-footer del theme existe, ocultar .site-footer
 *   de Astra en lugar de llenarlo. Bg oscuro forzado en .sf-hp-footer + imagen logo invertida.
 *   (2) Shipping bar "Te faltan $XX para envío gratis" OCULTO cuando cart vacío — body class
 *   sf-cart-empty server-side + toggle client-side via eventos added_to_cart / wc_fragments_refreshed.
 *   Nuevos usuarios no ven el bar hasta que agregan su primer producto.
 *   (3) Hero carousel fix: CTAs "detrás" del slide activo ocultos (opacity 0 + visibility hidden +
 *   pointer-events none). (4) Hero arrows desktop: prev/next flechas redondas 44px con backdrop-blur,
 *   position left/right 20px, bg rgba(255,255,255,0.9), scale hover. Hide en mobile (<921).
 *   (5) Category chips mobile con borde visible: border 1px rgba(0,0,0,0.15) + bg white + radius 999 +
 *   padding 8×16 + shadow sutil. Hover/active con bg negro, letras blancas.
 * v3.2.55 — Fix footer duplicado: el theme tiene footer custom (.sf-hp-footer) con contenido real
 *   ("Política de privacidad © 2026 Sutil..."), pero mi fallback v3.2.54 llenó también el .site-footer
 *   de Astra → 2 footers en la página. Ahora: si .sf-hp-footer existe, se oculta el .site-footer de
 *   Astra en lugar de generar fallback. Plus: bg oscuro + color blanco aplicado al .sf-hp-footer que
 *   estaba transparente en desktop. Imagen del logo invertida para visibility en bg oscuro.
 * v3.2.54 — **BUG CRÍTICO ENCONTRADO**: el require_once de palette-system.php se había perdido en un
 *   edit previo. Por eso las CSS variables --sf-text, --sf-bg, --sf-accent estaban VACÍAS en toda la web,
 *   y todas mis reglas CSS que usaban var(--sf-text) resolvían a transparent. Restaurado con
 *   if_exists guard. Plus: fallback :root con defaults hardcoded (en caso que vuelva a fallar).
 *   Hamburger bars: specificity 0,0,2,2 para vencer la rule var(--sf-text). Footer fallback: JS en
 *   wp_footer que detecta si el footer está vacío (<50 chars) y genera uno mínimo con logo + tienda
 *   + ayuda + copyright en bg oscuro. Bg dark del footer #1C1A17 forzado.
 * v3.2.53 — Hotfix: ícono hamburguesa invisible. Los 3 spans .sf-mobile-header__bar tenían
 *   background transparent — son divs vacíos que necesitan bg-color (no text color) para
 *   renderizar las 3 líneas del ícono. Fix: background-color #000 + width 22px + height 2px +
 *   border-radius 2px. Close button del drawer también con color negro forzado.
 * v3.2.52 — **FIX DEFINITIVO** del header/drawer transparent en TODA la web. Mis fixes previos
 *   (v3.2.48/49/50/51) metían las reglas en pdp-notino.css que SOLO carga en páginas de producto.
 *   En home/shop/cart/etc el CSS no cargaba → header seguía transparent. Ahora reglas movidas a
 *   homepage.css (carga en todas las páginas sin condicional). Bump de version en el enqueue de
 *   homepage.css a 3.2.52 para forzar cache-bust. Cubre .sf-mobile-header, .sf-desktop-header +
 *   familia, .sf-drawer + __panel/__inner/etc, Astra headers, dropdowns.
 * v3.2.51 — FIX REAL del "header transparente". Con elementsFromPoint encontré que el elemento real
 *   que el usuario ve es .sf-desktop-header (custom del theme, position:fixed, z-index:9998, bg
 *   rgba(0,0,0,0)). No era Astra ni sf-mobile-header. Mis fixes previos apuntaban a elementos que
 *   NO eran el header visible. Ahora forzado bg blanco + border-bottom + shadow a .sf-desktop-header
 *   y toda su familia (__inner, __nav, __actions, __logo + dropdowns + megamenu).
 * v3.2.50 — Headers + drawers SIEMPRE con bg blanco sólido + border-bottom + shadow sutil. Aplica a
 *   TODAS las variantes posibles: .site-header, #masthead, .ast-primary-header-bar, .main-header-bar,
 *   .ast-above-header, .ast-mobile-header-wrap, .sf-mobile-header, .sf-drawer + toda su familia de
 *   clases __panel/__inner/__content/__body/__header/__footer. También el menú hamburguesa de Astra
 *   (.main-header-menu, .ast-hf-mobile-menu, .ast-mobile-header-content). Hardcoded #FFFFFF como
 *   seguro si var(--sf-bg) falla. Border-bottom 1px rgba(0,0,0,0.08) + box-shadow sutil para que se
 *   vea la divisoria visual header/contenido.
 * v3.2.49 — Header + drawer bg sólido SIN depender de body.sf-mm-active. Mi rule de v3.2.48 requería
 *   la body class sf-mm-active (solo activa cuando drawer open), pero aparecen transparent SIEMPRE.
 *   Ahora selectors directos html body .sf-mobile-header y .sf-drawer__panel aplican bg var(--sf-bg)
 *   con fallback hardcoded #FFFFFF. También background-color además de background para máxima cobertura.
 * v3.2.48 — Fix cabecera y drawer transparentes en toda la web. Confirmado con inspect:
 *   .sf-mobile-header tenía bg rgba(0,0,0,0) y .sf-drawer__panel también transparent (solo el
 *   overlay oscuro .sf-drawer__overlay tenía bg). Ahora bg sólido var(--sf-bg) con border-bottom
 *   en header y bg sólido + color texto en drawer panel/inner/content/body. Search input del drawer
 *   con bg card + border. Legibilidad garantizada en toda la web.
 * v3.2.47 — Fix raíz del "solo 2 cols" en testimonios. Había un JS en functions.php:4646 que hardcodeaba
 *   `grid-template-columns: 1fr 1fr` con !important INLINE para todos los .sf-testimonios__grid (inline !important
 *   vence cualquier CSS). Ahora el JS es responsive: 4 cols desktop ≥1200, 2 cols tablet ≥768, 1 col mobile.
 *   Gap aumentado de 12px a 16px.
 * v3.2.46 — Testimonios desktop: grid 4 cols (antes 3) y container full-width con padding 64/80px.
 *   Sin max-width container, aprovecha todo el ancho del viewport.
 * v3.2.45 — Desktop fixes urgentes post-audit v3.2.44. (1) QUITADA position:sticky del gallery — causaba
 *   overlap brutal con secciones after-summary (la imagen se quedaba flotando sobre testimonios y
 *   acordeones). Ahora gallery es relative/stretch normal. (2) Pair cards y recently-viewed ahora SÍ son
 *   carousel flex con 4 cards desktop (antes mostraba 2 cards gigantes). (3) Header sticky Astra con
 *   background sólido var(--sf-bg) y z-index 999 para que NO se vean los textos del summary pasando
 *   detrás del menú. (4) Support panel acordeones con max-width 900px centrado + borders top/bottom
 *   consistentes. (5) Testimonios y carousels con container max-width y padding 20px.
 * v3.2.44 — Fix diseño DESKTOP (mobile intacto). Bugs detectados: (a) secciones after-summary
 *   (sf-testimonios, sf-product-pairs, sf-product-support-panel, sf-pdp-recently-viewed) quedaban
 *   apiladas en la COLUMNA 2 del grid en vez de span full-width. Ahora grid-column: 1/-1.
 *   (b) Gallery tocaba el borde izquierdo del viewport sin padding. Agregado padding 64px lateral
 *   en div.product para desktop ≥921px. (c) Pill "Ahorras $600" flotando fuera de imagen — forzado
 *   position absolute con parent gallery position sticky. Top 20px, left 20px para savings; top 20px
 *   right 20px para status-badge (Bestseller/Nuevo). (d) Gallery sticky top cambiado a 80px con
 *   max-height calc(100vh - 120px) para no tapar header. (e) Header z-index bajado a 5 para que el
 *   summary pase por ENCIMA al hacer scroll. (f) Testimonios grid 3 cols desktop (como en home).
 *   (g) Pair cards carousel desktop: 4 cards visibles con scroll horizontal snap. Todas las reglas
 *   dentro de @media (min-width: 921px) — CERO cambios en mobile.
 * v3.2.43 — Hotfix: REST endpoint v3.2.42 devolvía HTML JSON-encoded (WP_REST_Response serializa a JSON
 *   por default), JS hacía .text() y pegaba el string entre comillas como innerHTML → 0 cards parseadas.
 *   Fix: endpoint devuelve JSON array con {id, name, url, img, price_html}, JS arma el HTML cliente-side
 *   iterando con template strings. URL endpoint cambió de /product-cards a /recent-products.
 * v3.2.42 — 4 mejoras: (1) CTA "Añadir al carrito" OCULTO en cards de carousels (.sf-pair-card__cta) tanto
 *   en "Combina bien" como en "Vistos recientemente" — cards quedan con title + price sin botón.
 *   (2) Sticky CTA integra QTY SELECTOR: ahora tiene "− 1 +" a la izquierda del precio, botón "Añadir al
 *   carrito" a la derecha. Sync automático con input qty del form principal. Click sticky usa qty actual.
 *   (3) Stars rating MOVIDAS a order 4 (justo antes del price order 5, después del subtitle order 3) —
 *   antes estaban en 2.5. Ahora: brand → title → subtitle → STARS → price. WC native rating oculta.
 *   (4) "Productos vistos recientemente" fijado: JS ahora lee localStorage y SOLO muestra productos vistos
 *   REALES (antes de llegar al actual). Si localStorage vacío u solo el actual → OCULTA la sección
 *   completamente en lugar de mostrar fallback random. Si hay vistos reales → fetch AJAX al REST endpoint
 *   nuevo /wp-json/sf/v1/product-cards?ids=X,Y,Z que devuelve cards reales. Fallback server-side solo
 *   para SEO inicial, sobrescrito cliente-side.
 * v3.2.41 — 3 cambios solicitados + reviews al final. (1) WC tabs (Envío/Garantía) ELIMINADAS por completo
 *   — user pidió dejar solo los acordeones originales (support panel: Autenticidad/Descripción/Envío y tiempos).
 *   Support panel movido a priority 20 en woocommerce_after_single_product_summary (donde estaban los WC tabs).
 *   add_action de woocommerce_output_product_data_tabs comentado. (2) Stars rating ⭐⭐⭐⭐⭐ añadidas después
 *   del título del producto con conteo "X.X · N reseñas" y link a #sf-reviews — click hace smooth scroll a
 *   la sección de testimonios. Rating calculado desde CPT sf_testimonio meta 'rating' con fallback 5.
 *   (3) Badges "Ahorras $X" (rojo) y "Nuevo/Bestseller" (negro) RESTAURADAS sobre la imagen del producto
 *   — revertidos el hide CSS y el JS remove. Estilizadas como pills con shadow. (4) Reviews/testimonios
 *   MOVIDOS al FINAL de la página (priority 90 en woocommerce_after_single_product_summary), después de
 *   pairs, vistos recientes y shipping info. Section tiene id="sf-reviews" para el scroll anchor.
 * v3.2.40 — 5 fixes finales + revert reviews carousel. (1) Subtitle dedup — dedup exact + substring match
 *   (evita "Diseñador · Diseñador unisex"). (2) Sticky CTA text — hardcodeado "Añadir al carrito" directo en
 *   innerHTML (antes solo JS override que fallaba). (3) Card titles — fuente 11px + line-clamp 3 + min-height
 *   3.05em para títulos largos de perfumes de nicho. (5) Acordeones WC + support panel sin border-radius
 *   (estilo Notino minimal con línea horizontal solamente). (6) WC tab "Envío" OCULTA — duplicaba el
 *   acordeón "Envío y tiempos estimados" del support panel. Queda solo "Garantía" en WC tabs.
 *   REVERT: reviews/testimonios en mobile vuelven al diseño original stack vertical (no carousel horizontal),
 *   como en el homepage. Botón "Ver más opiniones" restaurado.
 * v3.2.39 — Fix 10 bugs visuales detectados como cliente exigente: (A) WC tabs como acordeones
 *   clicables reales — panels no tenían <h2> internos (WC los pone en ul.tabs como nav); JS ahora
 *   inyecta sf-wc-accordion-trigger button ANTES de cada panel con el título del tab correspondiente,
 *   click toggles .is-open, transición max-height. Panel Descripción ocultado (duplicaba pitch card).
 *   (C) Titles cards -webkit-line-clamp 2 con min-height 2.7em, fuente 12px. (D) Badge "Recomendado"
 *   ahora pill oscura text/bg palette, uppercase 9px bold. (E) Counter "1/4" del carousel: pill oscura
 *   11px bold con padding 5×12px posicionada esquina superior derecha de card. (F) Sticky CTA texto
 *   "Añadir al carrito" sin flecha — JS setea textContent directo. (G) Acordeones con fuentes normalizadas
 *   (h4/strong 14px, p 13px) para contenido largo legible. (H+I) Shipping-info 4-cards OCULTO —
 *   redundante con trust-inline 3 USPs. (J) Urgency countdown fuente 13px weight 500 con b 700 consistente.
 *   Bug B (footer vacío) queda como config de Astra Customizer → Header Footer Builder — no código.
 * v3.2.38 — Hotfix: el toggle sf_pdp_hide_native_cart que agregaba body class sf-hide-native-cart
 *   (default=1 en v3.2.30) generó 3 reglas CSS acumuladas (v3.2.28/31/33) que ocultaban el botón
 *   "Añadir al carrito" nativo de la info column. Aunque en v3.2.37 reverti UNA de las reglas, las
 *   otras 2 quedaron activas. Fix definitivo: default=0 del setting → la clase ya no se añade al body
 *   → NINGUNA regla sf-hide-native-cart aplica → botón vuelve. El plegable viejo de Astra
 *   (ast-sticky-add-to-cart) se sigue eliminando vía JS remove en DOMContentLoaded.
 *   Cliente puede re-activar el hide en Customizer si lo necesita: 00 PDP Global > "Ocultar botón
 *   nativo añadir al carrito".
 * v3.2.37 — Hotfixes visuales reportados por cliente: (1) botón "Añadir al carrito" DESAPARECIDO — mi fix
 *   agresivo de v3.2.36 ocultaba TODOS los .single_add_to_cart_button. Revertido: el botón NATIVO de la info
 *   column se queda, solo ocultamos el plegable viejo de Astra .ast-sticky-add-to-cart-wrap vía CSS
 *   display:none + JS remove(). (2) Badge "Ahorras $670" flotante sobre la imagen — tenía inline style
 *   display:inline-flex !important que vencía cualquier CSS, ahora se remueve del DOM via JS. (3) WC tabs
 *   (Descripción/Envío/Garantía) escondidos por inline style display:none !important puesto por JS ajeno —
 *   JS ahora removeProperty en load + MutationObserver para que no vuelva a esconderse. Footer mobile vacío
 *   reportado como <footer></footer> sin contenido: es un tema de config Astra Customizer (Header Footer
 *   Builder sin footer configurado), no un bug de código. Nota al cliente: configurar footer en
 *   Apariencia › Personalizar › Header/Footer Builder.
 * v3.2.36 — Fix visual completo post-navegación real (27 bugs). CRÍTICOS: (#1) imagen producto
 *   invisible — FlexSlider transform/width neutralizado via CSS high-specificity, slides width:100%,
 *   transform:none. (#2) doble CTA — sf-hide-native-cart con selector amplio + visibility+width+height+padding
 *   para garantizar ocultamiento. (#3) sticky CTA appearing too early — JS reescrito: solo aparece cuando
 *   form.cart.bottom < 0 (scrolleaste completamente pasado). (#4) footer mobile invisible — regla explícita
 *   min-height:120px visibility:visible. (#5) WC tabs ocultas — REMOVIDO .woocommerce-tabs y .wc-tabs-wrapper
 *   del display:none block en pdp.css línea 4647. IMPORTANTES: (#6) hide sf-savings-badge flotante.
 *   (#7) trust-inline sin borde/card wrapper. (#8) testimonios carousel mobile max-height 450px, photos
 *   max-height 200px. (#9) shipping-info 2x2 grid mobile en vez de columna vertical 2000px.
 *   (#10) "Ver más opiniones" hide (carousel navega). (#11) counter carousels pill negra visible.
 *   (#12) titles cards -webkit-line-clamp 2 con ellipsis. (#15) cards 45% width mobile sin cortes.
 *   (#19) support panel acordeones todos colapsados al cargar (JS removeAttribute open).
 *   (#21) top bar mobile compacto. (#25) drawer overlay rgba(0,0,0,0.55) + blur.
 *   (#27) cards CTA pill con bg text/bg. Gallery dots: pill translúcida centrada, active blanco
 *   scale 1.25, inactive rgba(255,255,255,0.55) — ya no transparente ni off-screen.
 * v3.2.35 — Gallery multi-imagen real + audit pendientes. Causa raíz del bug de "solo 1 imagen": Astra
 *   marcó su propio slider como slider-disabled → FlexSlider nunca inicializa → slides quedan display:block
 *   apilados + el wrapper con overflow:hidden clipea el segundo. Los dots custom (.sf-gallery-dots) hacían
 *   item.click() en thumbnails de Astra que estaban disabled. Fix: (1) CSS hide todos los slides no-activos
 *   + show only .flex-active-slide, (2) JS nuevo navigateTo() que toggla flex-active-slide directamente en
 *   main slides sin depender de Astra, (3) dots centrados con pill translúcida + blur, active dot con
 *   var(--sf-bg) visible, (4) swipe táctil mobile >50px deltaX. BONUS del audit: WC tabs desenmascaradas
 *   (pdp.css línea 4647-4654 las tenía en display:none!), testimonios carousel mobile con grid-template
 *   reseteado, padding summary 0 en mobile. Recently-viewed REFACTORIZADO al formato sf-product-pairs exacto
 *   con __shell, __carousel, __track, sf-pair-card con h4/price/cta. Priorities ajustadas: shipping-info 80,
 *   recently-viewed 60.
 * v3.2.34 — Mobile-focused + F11 Gallery multi-image recovery. F11 (cross-device): FlexSlider rotto por
 *   reglas v3.2.33 — wrapper overflow:hidden + width:100% en cada li rompía el multi-slide. Fix: overflow
 *   visible en wrapper, aspect-ratio solo en flex-viewport/wrapper desktop, no forzar width en slides,
 *   reactivar thumbs verticales desktop. F1: header mobile invisible — causa raíz identificada: .sf-mobile-header
 *   tenía bg transparent. Fix: forzar bg var(--sf-bg) + border-bottom + z-index + top (con ajuste admin bar).
 *   F2: sticky CTA JS arreglado — ahora observa form.cart (visible) en lugar del button nativo (oculto por
 *   sf-hide-native-cart). Fallback scroll listener adicional. F3: Gallery full-width mobile (margin -16px
 *   lateral). F4: overlap pairs/recently — removido grid-row:2 forzado. F5: testimonios carousel horizontal
 *   snap mobile (max-height lift). F6: padding lateral 16px consistente. F7: price-wrap width 100% mobile.
 *   F8-F9-F10: spacing + fonts mobile optimizados. Todos los selectores con prefix html body.sf-pdp-notino
 *   para evitar leaks.
 * v3.2.33 — Fix completo: regresiones de v3.2.32 + layout PDP real. Regresiones: (R1) header transparente +
 *   menú hamburguesa roto por regla display:revert (eliminada). (R3) shop catalog (misma causa). (R4) etiqueta
 *   % descuento de cards transparente → .sf-hp-prod-card__save usa var(--sf-sale, #CC3A2F) !important.
 *   Layout PDP: (B) grid-row:1 forzado en gallery+summary para alinear arriba. (C) wrapper de galería
 *   width:100% + aspect-ratio 4:5 + bg + radius con specificity html-prefix. (D) native cart hide con spec
 *   0,0,8,4 > rival. (E) WC tabs filter priority 999 para ganar a Astra __return_empty_array. (F) thumbs
 *   :has() condicional. Unhook legacy del summary: testimonios/support/pair-cards/payment-assurance salen de
 *   woocommerce_after_add_to_cart_form → re-hook en woocommerce_after_single_product_summary en orden
 *   Pitch→Tabs(20)→Editorial(30)→Testimonios(40)→Pairs(50)→Vistos(60)→Support(70)→Shipping-info(80).
 *   Payment-assurance REMOVIDO permanente (decisión user). Precio en UNA sola fila (flex + gap). Urgency
 *   SIN rojo (bg secondary + border-left text). Sticky CTA mobile rules completas. Borde del "panel
 *   unificado" de add-to-cart + MP + shipping ELIMINADO (no va en Notino).
 * v3.2.32 — FIX LAYOUT ESTRUCTURAL de la PDP: el "no se parece a Notino". Auditoría objetiva reveló 3 bugs
 *   críticos: (1) div.product era display:block sin grid, (2) gallery ocupaba full-width 1865px con imagen
 *   diminuta 113×141 (sin aspect-ratio), (3) summary flotaba en float:right 48% overlapping la galería.
 *   Tanda A (layout estructural): div.product → grid 1.25fr/1fr gap 64-80px padding 40-80px lateral.
 *   Gallery → grid-column 1, aspect-ratio 4:5, object-fit contain con padding 24px, sticky top 24px desktop.
 *   Summary → grid-column 2 max-width 560px sin float. Mobile (<921px): single column stacked, gallery 1:1.
 *   Secciones after-summary (editorial / recently-viewed / shipping-info / tabs / similares) full-width bajo
 *   el grid con max-width container y padding 40-80px. Tanda B (hotfixes QA): checkout body-class guard
 *   (is_product/is_shop/is_product_category/is_home nunca reciben sf-checkout-minimal) — fix regresión
 *   v3.2.31 donde el filter disparaba false-positive en PDP; subtitle fallback endurecido con cadena
 *   atributos → tags → cats → excerpt primeras 6 palabras; rehabilitación de header/footer fuera de checkout
 *   via :not(.sf-checkout-minimal) safety rule. Tanda C (detalles Notino): thumbs verticales 72px cuando
 *   producto tiene >1 imagen (con border + opacity hover), breadcrumb alineado al container con padding
 *   24px vertical + 40-80px lateral, pitch card con border-top + padding/margin top 24px, sticky-cta mobile
 *   z-index 9990 + fixed bottom seguro.
 * v3.2.31 — Hotfixes QA de v3.2.30 (5 issues detectados en staging):
 *   (a) #4 Native cart aún visible: pdp.css tenía display:inline-flex !important con especificidad 0,0,6,2
 *       (.single-product div.product form.cart:not(.variations_form):not(.grouped_form) .single_add_to_cart_button)
 *       que ganaba a mi 0,0,4,3. Nueva regla con especificidad 0,0,6,3 + !important gana.
 *   (b) #5 Recently viewed no render: hook a wp_head con priority tardía — la sección se emitía cuando Astra
 *       full-width template ya había cerrado el product wrapper. Hook cambiado a template literal + remove del
 *       filtro wc_get_products fallback cuando falla, ahora usa query WP nativa robusta.
 *   (c) #7 MP button invisible: el hook woocommerce_after_add_to_cart_button metía el button INSIDE form.cart
 *       como sibling del botón nativo → Astra lo colapsaba. Revertido a _after_add_to_cart_form (afuera del form).
 *       Reorden CSS corregido: form.cart=9, MP=10, shipping=11, trust-inline=12, pitch=13.
 *   (d) #10 WC tabs ausentes: Astra page-builder template NO llama woocommerce_output_product_data_tabs por defecto.
 *       Añadido add_action('woocommerce_after_single_product_summary', 'woocommerce_output_product_data_tabs', 10)
 *       condicionado a notino mode.
 *   (e) Subtitle vacío: fallback a product_tag no disparó porque el producto sí tiene atributos pero get_attribute()
 *       devuelve strings vacíos en algunos casos. Cadena de fallback endurecida.
 * v3.2.30 — Tanda única con 12 items decididos por el cliente:
 *   #1  Cabecera oculta en checkout (FunnelKit + WC) vía body class sf-checkout-minimal + toggle.
 *   #2  Filter dropdown z-index 9999 por encima del header sticky.
 *   #3  Paleta: 2 campos nuevos sf_palette_sale_color + sf_palette_sale_contrast (default #CC3A2F / #FFFFFF),
 *       emiten --sf-sale y --sf-sale-contrast. Badge de descuento ahora usa ambos (legible). Hero badge también.
 *   #4  Botón añadir al carrito duplicado resuelto: body class sf-hide-native-cart (default ON) oculta el nativo
 *       dentro de form.cart cuando hay plegable (Astra sticky/FunnelKit). Toggle Customizer para desactivar.
 *   #5  "Productos similares" DEFAULT OFF. Nueva sección "Productos vistos recientemente" como carrusel horizontal
 *       con scroll-snap, localStorage-tracked, fallback server-side a productos recientes de la misma categoría.
 *   #6  Shipping promise 100% templatizable: sf_pdp_shipping_promise_template + 3 labels (tomorrow/days/after-cutoff) +
 *       date_format + cutoff_minute + weekdays_only toggle + holidays textarea. Placeholders: {delivery_label}, {date},
 *       {weekday}, {hours}, {minutes}, {cutoff_time}.
 *   #7  MP button cambió hook de woocommerce_after_add_to_cart_form a _after_add_to_cart_button → ahora pegado al
 *       botón "Añadir al carrito", antes del shipping promise. CSS order=10 confirma posición en reorder.
 *   #8  Trust-inline USPs: nueva sección con grid 3 cols fijas (100% original / Envío / 30 días). Customizer permite
 *       editar ícono (7 opciones), título y copy de cada uno. Aparece bajo shipping promise (order=11).
 *   #9  Fondo unificado del panel de compra: form.cart + MP button + shipping promise comparten var(--sf-card-bg),
 *       bordes conectados, un solo card visual. Selector de cantidad con bg:transparent.
 *   #10 Acordeones WC restaurados (descripción + envío + garantía). Sección "Sobre el perfume" (editorial) OFF por
 *       default (toggle para reactivar). Tabs horizontales convertidas a accordions con JS inline (primero abierto,
 *       click para toggle). Títulos y bodies de envío/garantía configurables en Customizer.
 *   #11 Dots galería de producto: pill translúcida rgba(0,0,0,0.3) + backdrop-blur, centrados abs-bottom-14px, sobre
 *       la imagen, dot activo escala 1.25x con bg var(--sf-bg). Target táctil 24x24 con dot visible 9px.
 *   #12 Bullets legacy eliminados: función sf_render_product_bullets() convertida a stub vacío, add_action removido,
 *       belt-and-suspenders CSS display:none. Settings Customizer sf_bullet* quedan dead-code (sin impacto).
 *   Extra: Panel "Sutil Fragancia — Bloques de producto" ELIMINADO del Customizer (prioridad 999 remove_panel +
 *   remove_sections hijas) — choca con el código actual según reporte del cliente.
 * v3.2.29 — PDP QA profundo: (a) REPARACIÓN de pdp.css — dos llaves { sin cerrar preexistentes (l.1187 tenía
 *            "@media (max-width:390px) { {" duplicado, y @media (min-width:922px) en l.1322 nunca cerraba antes
 *            del siguiente @media) rompían el parser CSSOM del navegador y hacían que todas las reglas v3.2.27
 *            y v3.2.28 del final del archivo NUNCA se aplicaran (browser sólo parseaba 155 reglas). (b) Reglas
 *            Notino extraídas a archivo aislado assets/css/pdp-notino.css con enqueue propio — si pdp.css se
 *            vuelve a romper, la nueva maqueta sigue cargando. (c) Especificidad elevada de reorden y legacy
 *            hide con prefijo body.sf-pdp-notino.single-product div.product .summary > y !important en las 4
 *            reglas estructurales (display flex, flex-direction, order, display none). (d) Breadcrumb: marca
 *            ahora es LINK (prueba product_brand / pwb-brand / brand / pa_marca taxonomies, fallback a search);
 *            y ya no se muestra el nombre del producto al final — la ruta termina en la marca. (e) Opción B:
 *            remove_action() condicional (wp hook, priority 20) de 7 renderers legacy del summary:
 *            sf_render_product_tags_bar, sf_render_social_proof_bar, sf_render_review_summary,
 *            sf_render_stock_indicator, sf_render_product_sales_count, sf_render_short_description_after_price,
 *            sf_render_product_guarantee. El DOM queda físicamente limpio. (f) Defaults ON: sf_pdp_urgency_show
 *            y sf_pdp_mp_button_show pasan a default=1 tanto en render como en Customizer. (g) Subtitle fallback
 *            a product_tag: si no hay atributos visibles ni slugs configurados, usa hasta 3 tags del producto.
 *            (h) Fondo de imagen del main gallery + thumbs verticales + similares + shipping-info cards ahora
 *            hereda var(--sf-card-img-bg, --sf-card-bg) de la paleta — NO se agregó campo nuevo en Customizer,
 *            reutiliza el setting existente de "tarjetas de producto". (i) Hardcoded cleanup en pdp-notino.css:
 *            --sf-sale ahora es var(--sf-savings-text), badge usa var(--sf-savings-bg/--sf-bg), MP button usa
 *            var(--sf-text)/var(--sf-bg). Resultado: 0 hex hardcoded fuera de fallbacks de var(). (j) Hero dots
 *            del homepage — las clases .sf-hero-carousel__dots / __dot eran renderizadas por PHP pero no tenían
 *            CSS (el archivo sólo estilaba las clases legacy .sf-hero-dots / .sf-hero-dot). Agregadas reglas
 *            en homepage.css con bg pill translúcida + blur + breakpoint mobile para target táctil adecuado.
 *            (k) Hardcoded cleanup en homepage.css: sf-hero-slide__badge--sale ahora usa var(--sf-savings-text)/
 *            var(--sf-savings-bg); 6 usos de #F3EDE3 (search drawer ivory highlight) ahora var(--sf-card-img-bg);
 *            #a83b2a en .sf-hp-best__limited o similar ahora var(--sf-savings-text). Único hardcoded intencional:
 *            WhatsApp FAB #25D366 (color de marca oficial, se mantiene por reconocimiento).
 * v3.2.28 — PDP v3.2.27 QA: (a) Fix duplicación de pitch/shipping/urgency/mp dentro del sticky add-to-cart de Astra
 *            (ast-sticky-add-to-cart-action-wrap) vía CSS display:none + static $done en cada render. (b) Fix
 *            contaminación del filtro woocommerce_get_price_html — el wrapper --sf-sale + badge + MSI ahora
 *            SOLO envuelve la .price dentro de la .summary principal (tracker de contexto $GLOBALS["sf_pdp_in_summary"]),
 *            ya no revienta precios en pair cards, similar grid ni testimonios. (c) Modo Notino (toggle ON por default)
 *            que oculta legacy del summary que duplica/compite con la nueva maqueta: sf-social-proof-bar, sf-product-tags,
 *            sf-review-summary, sf-product-short-description, sf-sales-count, sf-stock-indicator, sf-cta-block; además
 *            reordena via CSS order para matchear Notino (brand→title→subtitle→rating→price→urgency→bullets→ship→form→mp→pitch).
 *            (d) Saca sf-testimonios y sf-product-support-panel de dentro del summary vía position/order para no reventar
 *            el ancho de la columna. (e) Subtitle auto-discovery: si los slugs configurados no devuelven nada, usa los
 *            primeros 3 atributos WC visibles del producto. (f) Urgency: muestra stock sin necesidad de timer configurado.
 *            (g) Editorial y similar grid con mejores defaults y fallback body desde product description.
 * v3.2.27 — PDP redesign alineado con mockup Notino MX Hi-fi (pdp.jsx + pdp-pages.jsx). Tanda 1: breadcrumb
 *            estilizado, brand caps + subtitle en info column, PriceBlock con var --sf-sale + percent + MSI line,
 *            urgency strip (stock + countdown), shipping promise box (llega mañana + muestra gratis),
 *            Mercado Pago secondary CTA, short pitch card. Tanda 2: gallery desktop vertical thumbs sticky 4:5,
 *            reviews UGC estilo Robols + rating distribution bar chart, sticky CTA mobile custom con
 *            IntersectionObserver. Tanda 3: Editorial section 2-col sticky "Sobre el perfume", Similar products
 *            grid 5-col (separado de pair cards), Shipping info 4-card grid al final de PDP. +20 settings
 *            Customizer nuevos organizados en panel "PDP · Página de producto".
 * v3.2.26 — QA fixes adicionales reportados en staging: (a) Card bg MOBILE — eliminado hardcode
 *            var(--sf-bg) !important en reglas legacy v3.1.10 (homepage.css:3162-3172), ahora todas las tarjetas
 *            respetan --sf-card-bg / --sf-card-img-bg en cualquier viewport. (b) Brand logo bg configurable —
 *            nuevo setting sf_brands_logo_bg_color (default #FFFFFF) emite --sf-brand-logo-bg, aplica al círculo
 *            y tile. (c) Text color en banners configurable — nuevo sf_ed_banner_text_color (editorial) y
 *            sf_banners_overlay_text_color (category tiles overlay). (d) Category label aparece en publicado —
 *            sincronizado default 'overlay' en render y Customizer, removido fallback CSS frágil :not().
 *            (e) Strings/colores hardcoded configurables: sf_brands_tile_badge_show/text (oficial),
 *            sf_instagram_empty_text, sf_ed_banner_overlay_color, sf_testimonios_verified_color.
 * v3.2.25 — QA fixes reportados: (a) footer: logo imagen + monograma + wordmark + privacy label + copyright configurables
 *            (antes hardcoded "SF Sutil Fragancia" y "Política de privacidad"). Nuevos settings en 16 · Footer. (b) Card bg
 *            default cambiado a #FFFFFF blanco con opción "white" agregada al select del palette. (c) Banners categorías:
 *            imagen ocupa 100% del contenedor (background-size cover forzado), label default ahora es "overlay" (encima de
 *            la imagen con gradient), desktop grid simplificado a 4-col uniforme (antes 2fr/1fr/1fr × 2 rows causaba
 *            celdas vacías con 4 banners).
 * v3.2.24 — Grid auto-adapta al count de productos: cada sección (bestsellers, nuevos, ofertas, muestras) emite
 *            grid-template-columns dinámico basado en el número real de productos configurados. Resuelve que
 *            bestsellers con limit=4 se vea con 1 celda vacía cuando el CSS definía 5 cols. Ahora todas las
 *            secciones de producto llenan su ancho correctamente sin celdas sueltas.
 * v3.2.23 — Wide breakpoints + ritmo visual + reseñas Robols-style. Container max-width escalable (1280/1520/1680)
 *            según viewport. Padding entre secciones 32→56px en desktop. Alt bg alternating (.sf-hp-sec--alt en
 *            new-arrivals + muestras). Eyebrow/title gap 6px. Product card img-wrap padding 0 (más aire al frasco).
 *            Hero valign variants aplicadas correctamente (--valign-top/center/bottom → justify-content). Reseñas
 *            rediseñadas estilo Robols: sección dark noir bg, cards marfil con shadow + radius 8px, foto 4:5 full-bleed
 *            top, fecha agregada (DD/M/YYYY), estrellas oro 20px (antes 11px), sin overlay "Foto del cliente", sin
 *            botón dorado (mini-card clickeable limpio), grid desktop 3-col natural height.
 * v3.2.22 — Fixes críticos de QA en staging. (a) Footer movido dentro de <main> (evita render como sidebar por flex-row).
 *            (b) Hero active slide mantiene position:absolute (evita colapso a 44px cuando el slide no tiene contenido).
 *            (c) Guard null en sf_is_product_bestseller contra cached transients + categories nulas.
 *            Features: hero modo "image-link" (sf_hero_slide_N_image_only + link_url), banners label toggle
 *            (position below/overlay/hidden), brands slot repeater subido a 50, bestsellers migrado a helper v318
 *            (respeta sf_badge_format percent), brands tiles con contraste + sin duplicar monograma, testimonios
 *            eyebrow configurable, banners con section head pattern + eyebrow.
 * v3.2.21 — Alineación visual con mockup Notino MX Home B. Tanda A: product cards aspect configurable (default 3:4),
 *            variant line + save-percent badge, section titles 36px desktop con head grid 2-col, testimonio mini-card
 *            inline (sin botón dorado), selector de producto WC (Select2) en CPT sf_testimonio. Tanda B: hero desktop
 *            split opcional con tile derecho "Nuevos lanzamientos", badge-above-title por slide, category tiles con
 *            caption estructurado (título + meta) y sf_banner_N_meta. Tanda C: editorial banner 2fr/3fr desktop,
 *            samples con bg secondary y grid 1fr/2fr, brands tiles desktop (grid 6-col con monogram + producto),
 *            USPs variante bordered con subtítulos.
 * v3.2.20 — Feature fixes + product image consistency. Hero slides: fit por slide (cover/contain/fill) +
 *            aplicación de image_position inline. USPs: icon type=select + color picker + estilo card.
 *            Más vendidos: quitado cta_mode legacy, data-* legacy del HTML, toggle show + eyebrow agregados.
 *            Grids: botón "Ver todos" oculto cuando URL vacía; descriptions explican la lógica de cada query.
 *            Marcas: skip solo si name+image vacíos; label del nombre opcional + toggle global
 *            sf_brands_hide_labels. Muestras: imagen opcional. Reseñas: nueva sección sf_testimonios_home_section
 *            en panel homepage + botón "Ver producto →" cuando hay product_id vinculado. Banners: toggle show.
 *            Shipping bar: template con placeholders {{falta}} {{umbral}}. Chips + brands con role=navigation.
 *            Product images: unificación CSS con var(--sf-img-fit) default contain, padding, 4 settings nuevos
 *            (fit_catalog, fit_banner, fit_pdp, img_padding), eliminado JS override forzado en testimonios.
 *            Invalidación transients ampliada: trashed_post + deleted_post para productos.
 * v3.2.19 — Cleanup + Customizer reorder: eliminadas funciones legacy (sf_hp_reviews_carousel, sf_hp_microclaims,
 *            sf_hp_category_tabs), handler AJAX sf_filter_products, secciones Customizer sf_tabs_section y
 *            sf_microclaims_section (~30 settings huérfanos), módulo JS de tabs (~82 líneas) y CSS stubs.
 *            Customizer reordenado (panel homepage) con prefijos "01 ·… 16 ·…" en el mismo orden del render.
 *            Migración automática after_switch_theme limpia theme_mods obsoletos.
 * v3.2.18 — Homepage redesign completo: nueva estructura de secciones (chips, nuevos lanzamientos, banner editorial, ofertas relámpago,
 *            muestras), rediseño visual de todos los widgets existentes al estilo Notino MX grid utilitario (Cormorant + DM Sans,
 *            esquinas 2px, 0.5px border, paleta de CSS vars), campo _sf_testimony_product_id en CPT sf_testimonio,
 *            control de aspect ratio del hero (sf_hero_aspect_mobile), y 5 nuevas secciones de Customizer.
 * v3.2.17 — Fix definitivo: card bg escrito inline en wp_head con valor calculado. Elimina dependencia de CSS cacheado.
 * v3.2.16 — Fix caché: version strings de CSS bumpeados a 3.2.16 (homepage.css, pdp.css). Sin esto LiteSpeed servía el CSS viejo.
 * v3.2.15 — Fix homepage cards: !important en review-card, prod-card__body, testi-card homepage (sin JS).
 * v3.2.14 — var(--sf-card-bg) aplicado a todas las tarjetas: catálogo, homepage prod, review carousel, testimonios (JS), pair cards PDP. Fix !important en grid.
 * v3.2.13 — Fondo de tarjetas de producto configurable: select (paleta/custom/transparente) + color picker + toggle imagen independiente. Customizer → Paleta.
 * v3.2.12 — object-fit contain para imágenes de producto (grid + PDP gallery). Toggle + color picker en Customizer → sección Paleta.
 * v3.2.11 — Cabecera desktop custom: sticky 70px, paleta dinámica, nav horizontal configurable (6 items), íconos search/cuenta/carrito, border inferior. Oculta Astra native header en desktop. Search btn reusa el overlay del mobile. Breakpoint 921px coherente con mobile header.
 * v3.2.10 — Quadruple defensive fix: stars y pills ahora universales con especificidad aumentada (body-level), palette-system.php fuerza display/visibility/opacity, eliminadas reglas duplicadas legacy.
 * v3.2.5 — Fix badge bestseller PDP gallery: regex que parseaba el CSS dinámico no hacía match con var(--), caía a fallback hardcoded #B8924A. Eliminado regex, ahora usa var() directamente.
 * v3.2.4 — Fix DEFINITIVO: 27 setProperty() en JS embebido inyectaban hex hardcoded via DOM (badges, estrellas, leer más, CTA card, status badge). Ahora todos usan var(--sf-*). También bloque sf-pdp-custom CSS ahora lee del sistema de paleta en vez de Customizer hex independientes.
 * v3.2.3 — Fix raíz: theme-overrides.css legacy variables ahora son alias del sistema de paleta. Inline header search también respeta la paleta.
 * v3.2.2 — Refactor masivo: 84 hex hardcoded restantes (mostazas, blancos, negros) → variables. Forzados de estrellas y badges expandidos.
 * v3.2.1 — Paleta robusta: accent_contrast, stars/stock fijos, banner palette toggle, fixes de contraste en CTAs/badges/pills
 * v3.2.0 — Sistema de paleta dinámica: 6 presets × 2 modos + personalización completa + guardar paletas custom
 * v3.1.12 — Catalog title cleaner (limpia "Perfume", marca duplicada, género al final — solo en grids, NO en PDP, SEO intacto)
 * v3.1.11 — Catálogo WooCommerce alineado con homepage (cards Cormorant, marca dorada, badges, sin add-to-cart)
 * v3.1.10 — Fase B homepage: pulido visual + fix UGC roto en homepage (CSS missing)
 * v3.1.9 — Fase A homepage: Customizer expandido (tabs 12, banners 12, marcas 20, CTA dinámico, micro-claims, UGC homepage, footer expandido)
 * v3.1.8 — Fix botón hamburger (focus pegado + feedback visual)
 * v3.1.7 — Fix bugs históricos del mobile menu (toggle hamburger + defensas iOS)
 * v3.1.6 — Fix FOUC del header search (visible 3-5s al cargar el home)
 * v3.1.5 — Hotfix crítico: 3 bugs del search (CSS price, header layout, drawer listener)
 * v3.1.4 — Search mejorado: velocidad, UX, header independiente,
 *          historial, búsquedas populares reales, highlight
 *
 * OPTIMIZACIÓN DE VELOCIDAD
 * - Transient cache 5 min por query (repeticiones <50ms)
 * - Taxonomía de marca cacheada 1 día (elimina iteración de 5 tax)
 * - Queries consolidadas donde posible
 * - Estimado: 300-800ms → 80-200ms (primera), <50ms (repetidas)
 *
 * FIX RACE CONDITION "NO SE ENCONTRARON PRODUCTOS"
 * - Backend devuelve status explícito: too_short / empty / results
 * - JS descarta respuestas stale (query cambió mientras llegaba)
 * - AbortController usado consistentemente
 * - Estado "too_short" muestra sugerencias en vez de empty state
 *
 * SEARCH INDEPENDIENTE EN HEADER (alternativa B — expandible)
 * - Ícono lupa del header se expande in-place sobre el header
 * - Input toma focus automático, ESC o X cierran
 * - Click fuera cierra
 * - Resultados caen directo bajo el header (no abre drawer)
 * - Fallback: "sf_mm_header_search_mode" = 'drawer' vuelve al
 *   comportamiento anterior (abre drawer + focus)
 *
 * HIGHLIGHT DEL TÉRMINO EN RESULTADOS
 * - El término buscado se resalta con <mark> en el nombre del producto
 * - Color de highlight: dorado semi-transparente
 *
 * HISTORIAL DE BÚSQUEDAS RECIENTES
 * - Guardado en localStorage del navegador (privado por usuario)
 * - Muestra las últimas 5 al enfocar el input sin escribir
 * - Botón "Limpiar" para borrarlas
 * - Checkbox en Customizer para desactivar
 *
 * BÚSQUEDAS POPULARES REALES (CON REGISTRO AUTOMÁTICO)
 * - Cada búsqueda exitosa se registra en wp_options (sf_search_log)
 * - Decay semanal del 10% para reflejar tendencias actuales
 * - Bootstrap con 5 defaults manuales hasta que haya 20+ búsquedas
 * - Tope de 100 términos para evitar bloat
 * - Filtros de privacidad: solo 3-40 chars, solo alfa+num+espacios,
 *   máximo 5 palabras, no se loggea si usuario es admin
 * - Endpoint wp_ajax_sf_log_search (nonce sf_drawer_search)
 * - Cron semanal sf_search_log_decay (lunes 3am)
 * - Admin action sf_reset_search_log para limpiar manualmente
 *
 * JS REFACTORIZADO: MOTOR COMPARTIDO
 * - sfCreateSearchEngine() — factory reutilizable
 * - Drawer y header usan el mismo engine, solo cambian selectores
 * - Reducción de duplicación ~40%
 *
 * CUSTOMIZER
 * - Nuevos campos en sección Search:
 *   · Acción del ícono de búsqueda (expandir/drawer)
 *   · Mostrar búsquedas recientes (checkbox)
 *   · Mostrar búsquedas populares (checkbox)
 *   · Número de sugerencias (3/5/7)
 *   · Umbral de bootstrap (número)
 *   · 5 slots de defaults manuales
 *
 * v3.1.3 — Buscador custom AJAX (reemplaza Ivory Search)
 * - Motor de búsqueda propio dentro del drawer: no depende de plugins
 * - Busca por: título del producto, SKU (_sku meta), marca (taxonomía)
 * - Resultados live mientras escribes (debounce 300ms, min 2 caracteres)
 * - Cada resultado muestra: imagen, marca, nombre, precio con descuento
 * - Navegación con teclado (flechas + Enter)
 * - Fallback: Enter sin seleccionar → va a /tienda/?s={query}
 * - Loading spinner dorado durante búsqueda
 * - Empty state cuando no hay resultados
 * - Resultados se limpian al cerrar el drawer
 * - AJAX handler: wp_ajax_sf_drawer_search / wp_ajax_nopriv_sf_drawer_search
 * - Nonce: sf_drawer_search (pasado via sfHomeData.searchNonce)
 * - Resuelve definitivamente: Ivory Search interceptaba queries, form
 *   nativo no funcionaba, pre_get_posts era pisado por plugins
 *
 * v3.1.2 — Hotfix: Ivory Search integration
 * - Drawer search ahora usa shortcode [ivory-search id="..."] por default
 * - Resuelve: búsqueda devolvía posts en vez de productos (el pre_get_posts
 *   filter no servía porque Ivory Search intercepta las queries con su
 *   propio engine)
 * - 2 campos nuevos Customizer (sección Search): modo de búsqueda
 *   (Ivory/Nativo) + ID del form de Ivory (default 8975)
 * - CSS overrides para que el form de Ivory herede estética del drawer
 *   (input marfil + border piedra + ícono lupa + focus dorado)
 * - Ajax live search de Ivory estilizado (si está activado en el form)
 * - Fallback automático a form nativo si el shortcode de Ivory no está
 *   disponible (plugin desactivado o no instalado)
 *
 * v3.1.1 — Fixes mobile menu: search, logo, WhatsApp, header layout
 *
 * SEARCH DEL DRAWER (fix)
 * - Form action ahora apunta a la URL del shop de WooCommerce
 * - Filtro pre_get_posts fuerza post_type=product en resultados de
 *   búsqueda cuando el parámetro está presente en la URL
 * - Resuelve: búsqueda devolvía posts en vez de productos
 *
 * LOGO CONFIGURABLE (nueva sección "Header mobile")
 * - Header fijo: select tipo de logo (sitio / imagen custom / SF)
 * - Drawer: logo independiente O compartido con header (checkbox)
 * - Media uploaders separados para header y drawer
 * - Max-height: 32px header / 36px drawer
 *
 * WHATSAPP CTA (fix)
 * - Campos nuevos en Customizer: número + mensaje pre-cargado
 * - URL construida como: https://wa.me/{num}?text={msg url-encoded}
 * - Fallback a sf_get_whatsapp_url() si no hay número configurado
 * - Resuelve: botón WhatsApp no funcionaba (link vacío)
 *
 * HEADER EXTENDIDO (nuevos campos en "Header mobile")
 * - Posición del logo: centro / izquierda (junto al hamburguesa)
 * - Toggle: mostrar/ocultar ícono búsqueda
 * - Toggle: mostrar/ocultar ícono carrito
 *
 * CARRITO CLICK (fix defensivo)
 * - z-index del header subido a 10000 (antes 9998)
 * - .sf-drawer:not(.is-open) fuerza pointer-events: none !important
 * - Fallback explícito de cart URL si wc_get_cart_url() vacío
 * - Clase .sf-mobile-header__cart lista para selector de Funnel Kit
 * - Resuelve: click en ícono carrito solo recargaba la página
 *
 * ACCIÓN REQUERIDA POST-UPDATE: ir a Funnel Kit → Cart Menu →
 * Visibility Method → "Use CSS Selector" → pegar: .sf-mobile-header__cart
 *
 * v3.1.0 — Mobile Menu custom + mejoras hero carousel
 *
 * MOBILE MENU (nuevo, global)
 * - Reemplaza el mobile header de Astra con drawer custom brand-aligned
 * - Renderiza en TODAS las páginas (front, producto, categoría, cuenta)
 * - Drawer con: search funcional WC, categorías auto-expandibles,
 *   marcas scrollables, links secundarios, USPs, CTA WhatsApp
 * - Auto-detección de sub-categorías desde WooCommerce (si URL de
 *   categoría padre coincide, sus hijos se listan automáticamente)
 * - ~80 campos configurables en Customizer (panel "Mobile Menu"):
 *   7 secciones (General, Search, Categorías, Marcas, Links, USPs, WA)
 * - Accesibilidad: focus trap, Escape cierra, body scroll lock con
 *   restore de posición, prefers-reduced-motion, ARIA completo
 * - Breakpoint configurable (768/921/1024px)
 * - Ancho configurable (80/83/85/100%)
 * - Astra mobile header auto-oculto cuando el custom está activo
 *
 * HERO CAROUSEL — mejoras
 * - touch-action: pan-y para evitar que swipe horizontal arrastre
 *   la página verticalmente
 * - Espaciado entre H1/Sub/CTA/Social proof aumentado (mobile +
 *   desktop) para mejor jerarquía visual
 * - Nuevos campos por slide: tamaño del titular y subtítulo
 *   (Compacto/Medio/Grande) para control editorial sin romper
 *   el brand book
 *
 * ENQUEUE
 * - homepage.css y homepage.js ahora cargan globalmente (necesario
 *   para que el mobile menu funcione en todas las páginas)
 * - pdp.css solo se carga en páginas de producto/categoría/tienda
 *   (ahorra 143KB en páginas donde no se usa)
 *
 * v3.0.8 — Hotfix: image control del hero slide
 * - WP_Customize_Image_Control → WP_Customize_Media_Control
 *   (el primero guarda URL como string, el segundo guarda attachment ID
 *    que es lo que espera absint() + wp_get_attachment_image_url())
 * - Acción requerida post-update: re-seleccionar la imagen en cada slide
 *   del Customizer (el valor guardado con la versión anterior es URL
 *   string, no ID — no es compatible con el renderer)
 *
 * v3.0.7 — Hero convertido en carrusel configurable (hasta 5 slides)
 * - Nueva sección Customizer: "Hero · Carrusel (configuración general)"
 *   + 5 secciones "Hero · Slide 1..5" cada una con ~20 campos
 * - Hero con imagen de fondo full, overlay configurable (color + opacidad),
 *   texto + CTA + social proof + timer por slide
 * - Auto-play con pausa inteligente (hover, focus, swipe, off-screen),
 *   dots, flechas desktop opcionales, swipe mobile, keyboard nav
 * - Altura configurable por device (mobile/desktop × compact/medium/tall)
 * - Respeta prefers-reduced-motion (WCAG 2.2.2)
 * - Fallback: si ningún slide tiene imagen, renderiza texto sobre noir
 *   usando valores del Slide 1 (backward compatible con v3.0.6)
 * - Cache-bust version bump
 *
 * v3.0.6 — Polish pass: 9 correcciones visuales
 * - Tabs: text-transform none (Astra override)
 * - Hero: CTA + rating forzados en línea
 * - Hero: subtítulo más conciso
 * - Espaciado entre secciones reducido
 * - WhatsApp flotante: no tapa "Ver todo"
 * - Footer con identidad de marca (logo, claim, redes, links)
 * - Astra footer oculto en homepage
 *
 * v3.0.5 — Hero recortado + carrusel de reviews independiente
 * - Hero: sin testimonio incrustado, CTA + rating en línea
 * - Nuevo widget: carrusel horizontal de reviews entre hero y USPs
 * - Cards compactas con avatar, nombre, estrellas, texto truncado 2 líneas
 * - Scroll horizontal con snap, datos del CPT sf_testimonio
 *
 * v3.0.4 — Testimonios CSS independiente de JS
 * - Todos los estilos de tarjetas estilo B movidos de JS a CSS
 * - Grid, cards, imagen 1:1, initials, header, stars, text clamp, "Leer más"
 * - Funciona en homepage y PDP sin depender de is_product()
 *
 * v3.0.3 — Alineación visual homepage ↔ PDP
 * - Product cards: imagen full-width sin padding, object-fit cover, 1:1 forzado
 * - Card body: fondo blanco (como cross-sell PDP)
 * - Badge "Ahorras": fondo oscuro (como PDP)
 * - Badge "Más vendido": dorado, agregado al homepage
 * - FAQ: estilos de acordeón idénticos al PDP
 * - Botones outline pill unificados
 *
 * v3.0.2 — Tabs como filtros AJAX
 * - Tabs de categoría ahora filtran el grid sin recargar página
 * - Customizer: campo URL reemplazado por slug de categoría
 * - AJAX endpoint para consultar productos por categoría
 * - Animación de carga en el grid al cambiar de tab
 * - "Ver todo" actualiza su URL según tab activo
 *
 * v3.0.0 — Rediseno completo homepage
 * - Hero: testimonio integrado, un solo CTA
 * - USPs: SVG icons configurables desde Customizer
 * - Tabs de categoria como filtro sobre el grid
 * - Product cards sin boton (card clicable completa)
 * - Marca y rating en cada product card
 * - Badge de ahorro en pesos
 * - 6 banners de categoria con imagenes
 * - UGC + testimonios como seccion propia
 * - Marcas con circulos
 * - FAQ configurable desde Customizer
 * - WhatsApp como boton flotante
 * - Un solo titulo por seccion (sin eyebrows)
 * - Todo configurable desde Customizer
 */
