/**
 * CP-FICHA-V2 · Estilos de la ficha de producto rediseñada.
 *
 * Fuente única de estilos de la ficha (base y combo por-modelo). Se encola solo
 * en is_product() desde inc/modules/woocommerce/ficha-v2.php.
 *
 * Paleta del brief: rojo ÚNICO #e11414 (botón de compra, subrayado de títulos y
 * de la etiqueta activa, viñetas, enlaces de acción y el modelo del breadcrumb;
 * en ningún otro sitio) y el verde del sello de disponibilidad. Nada más de
 * color.
 *
 * Tipografía: Barlow Semi Condensed 600/700 para títulos y precios, Barlow
 * 400/600 para el cuerpo, JetBrains Mono para los códigos. Las tres las declara
 * assets/css/header-racing.css; las Semi Condensed van con font-display:optional
 * y precargadas (inc/modules/header-racing/assets.php) — sin eso los títulos de
 * 52 px reflujan al cargar, que es el defecto que se corrigió en su día.
 *
 * D4 (FIX-FICHA-6) añade una cuarta familia SOLO para el cuerpo de las secciones:
 * ver el @font-face de aquí abajo.
 */

/* CP-FICHA-V2 · Los TOKENS de `.ficha-v2` y las tres reglas de base (el reset
   de los anchos de WooCommerce, el clearfix y el `[hidden]` que hace falta para
   que el stepper se pueda ocultar) se mudaron a assets/css/fv2-componentes.css
   en G2: la modal los necesita también fuera de la ficha. Esta hoja los da por
   puestos porque los declara como dependencia al encolarse. */

/* =========================================================================
   1) BREADCRUMB
   ========================================================================= */

.fv2-breadcrumb {
  max-width: var(--fv2-ancho);
  margin: 0 auto;
  padding: 18px 20px 14px;
}

.fv2-breadcrumb__lista {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 8px;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 13px;
  line-height: 1.4;
  color: var(--fv2-gris-txt);
}

.fv2-breadcrumb__item + .fv2-breadcrumb__item::before {
  content: '/';
  margin-right: 8px;
  color: #c9c9c9;
}

.fv2-breadcrumb__item a {
  color: var(--fv2-gris-txt);
  text-decoration: none;
}

.fv2-breadcrumb__item a:hover { text-decoration: underline; }

/* El modelo, y solo el modelo, en rojo. Es la ÚNICA vez que la ficha combo
   nombra la moto en la parte alta. */
.fv2-breadcrumb__item--modelo a,
.fv2-breadcrumb__item--modelo span {
  color: var(--fv2-rojo);
  font-weight: 600;
}

.fv2-breadcrumb__item--actual span { color: #1a1a1a; }

/* M3 (FIX-FICHA-13) · `.fv2-notactx` SE RETIRA ENTERO.
   Era la línea de J3 —MODELO + nota— bajo el breadcrumb, y ese es justo el problema:
   mismo ancho, mismo padding, mismo tratamiento (modelo en rojo semibold, separador
   gris) que el crumb que tenía encima. En el combo el nombre de la moto salía dos
   veces seguidas y la cabecera se leía como un breadcrumb partido en dos.
   La nota se muda a la caja de compra con la etiqueta «En tu moto:» y sin repetir el
   nombre del modelo — ver `.fv2-entumoto` en la sección de la caja de compra. */

/* =========================================================================
   2) EL CUERPO DE LA FICHA: DOS COLUMNAS Y LA CAJA PEGADA (D3)
   =========================================================================
   Del breadcrumb hacia abajo, en escritorio, la ficha son DOS columnas:

     izquierda (965 px)  galería · barra de anclas · TODAS las secciones
     derecha   (395 px)  el lateral de compra —título, logos, disponibilidad,
                         precio, código, botón y garantías— PEGADO bajo el header
                         durante todo el recorrido.

   POR QUÉ ESTA ESTRUCTURA Y NO OTRA (y esto no es preferencia de estilo: las
   demás no funcionan). Para que algo se quede pegado mientras se recorre el
   contenido, su bloque contenedor tiene que ser TAN ALTO como el recorrido:
     · con la caja dentro de `.fv2-compra` —como estaba— el pegado se acaba donde
       se acaba esa zona, a la altura de la galería, y la caja se va con ella;
     · como ítem suelto de la rejilla, su bloque contenedor es su CELDA, y una
       celda del alto de su contenido no deja viaje al sticky.
   Por eso hay dos envoltorios reales —`__izq` y `__lateral`— y no cinco áreas
   sueltas: la rejilla solo reparte DOS columnas, el lateral viaja dentro de la
   suya —tan alta como la izquierda— y la barra de anclas sigue siendo un bloque
   normal dentro de la izquierda, que es donde su propio sticky tiene recorrido.

   MÓVIL NO CAMBIA (regla del brief). Ahí los dos envoltorios se disuelven con
   `display: contents` y el orden se declara pieza a pieza — título, galería,
   caja, anclas, secciones—, que es exactamente el de siempre. Nada se queda
   flotando: el único pegado de móvil es el que ya había (la barra de anclas, con
   `--fv2-tope` a 0 porque allí el header no se pega). */

.fv2-cuerpo {
  display: flex;
  flex-direction: column;
  max-width: var(--fv2-ancho);
  margin: 0 auto;
  padding: 0 20px;
}

/* En móvil los envoltorios de columna no existen como cajas: sus hijos son los
   ítems del flex y cada uno declara su sitio. Sin esto el orden sería
   «galería, anclas, secciones, título, caja», que no es el de hoy. */
.fv2-cuerpo__izq,
.fv2-cuerpo__lateral,
.fv2-lateral__pegado { display: contents; }

/* F1: las garantías vuelven a estar DENTRO de la caja de compra, así que no son
   un ítem de esta lista y no llevan `order` propio: viajan con la caja. */
.fv2-titulo          { order: 1; }
.fv2-compra__galeria { order: 2; }
.fv2-compra__caja    { order: 3; }
.fv2-anclas          { order: 4; }
.fv2-contenido       { order: 5; }

/* La barra de anclas es la única pieza a sangre: su fondo blanco y su línea
   cruzan la pantalla de lado a lado. El contenedor lleva 20 px de aire, así que
   se los devuelve con márgenes negativos — su <ul> ya pone los suyos. */
.fv2-anclas { margin-left: -20px; margin-right: -20px; }

@media (min-width: 1000px) {
  .fv2-cuerpo {
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--fv2-caja);
    grid-template-areas: 'izq lateral';
    column-gap: var(--fv2-calle);
    align-items: start;
  }

  /* Las dos columnas vuelven a ser CAJAS. Es justo lo que hace que la barra de
     anclas siga pegándose: dentro de un bloque alto su sticky tiene recorrido,
     y como ítem de rejilla en una fila del alto de su contenido no lo tendría. */
  .fv2-cuerpo__izq {
    display: block;
    grid-area: izq;
    min-width: 0;              /* sin esto una tabla ancha estira la columna */
  }

  .fv2-anclas { margin-left: 0; margin-right: 0; }

  /* LA COLUMNA DERECHA, A TODA LA ALTURA DE LA FILA.
     Hasta F2 el pegajoso era ESTA columna, con `align-self: start` para que la
     celda no la estirase y le quedara viaje. Funcionaba mientras solo hubiera que
     pegarse por arriba. Medido en Chrome sobre la ficha del Sym (1440x720, modo
     pie forzado a mano): con `align-self: start` el `bottom` NO pega —la caja se
     queda en su sitio estático y se va por el techo, lat_bottom −273 donde el pin
     tocaba a 704—, y con `align-self: end` pega perfecto (704 clavado) pero
     arranca la página con la caja en el sótano de la columna, encima de la
     cabecera. Ninguno de los dos vale para los dos modos.
     Así que la columna deja de pegarse y pasa a ser lo que tiene que ser: una
     caja tan alta como la fila. El que se pega es `.fv2-lateral__pegado`, un
     bloque normal dentro de ella, y ahí `top` y `bottom` funcionan los dos.
     `stretch` es explícito porque el contenedor lleva `align-items: start`. */
  .fv2-cuerpo__lateral {
    display: block;
    grid-area: lateral;
    align-self: stretch;
    min-width: 0;
  }

  /* EL PEGAJOSO. El desplazamiento sale de `--fv2-tope`, que assets/js/ficha-v2.js
     mide sobre el header REAL (alto + su `top` negativo), el mismo número con el
     que se coloca la barra de anclas; los 16 px son el aire para que la caja no
     quede lamiendo la cabecera.
     E2 · NI SCROLL INTERNO NI TOPE DE ALTO. Aquí hubo un `max-height` con
     `overflow-y: auto` para que una caja más alta que la ventana no se arrastrara.
     Resolvía la geometría creando un problema peor: una segunda barra de
     desplazamiento dentro de la caja donde se compra. */
  .fv2-lateral__pegado {
    display: block;
    position: sticky;
    top: calc(var(--fv2-tope, 46px) + 16px);
    bottom: auto;
  }

  /* -----------------------------------------------------------------------
     F2 · PEGADO INTELIGENTE: LA CAJA CAMBIA EL BORDE POR EL QUE SE PEGA
     -----------------------------------------------------------------------
     El problema que esto cierra es el que quedó abierto en FIX-FICHA-7: una caja
     más alta que el hueco disponible, pegada por ARRIBA, deja su final —el botón
     y las garantías— fuera de la pantalla para siempre. Da igual cuánto se
     recorte el contenido: siempre habrá una ventana lo bastante baja, o una
     cabecera lo bastante asomada, donde vuelva a pasar. Y las tres salidas que
     se probaron son peores que la enfermedad: el scroll interno mete una segunda
     barra en la caja de comprar, el recorte esconde el botón, y bajarla a pulso
     tapa el título con la cabecera.
     La salida buena es no elegir un borde fijo. Si la caja CABE, se pega por
     arriba, que es donde el ojo la espera. Si NO cabe, se pega por su borde
     INFERIOR: la caja acompaña al scroll hasta enseñar su final y ahí se queda,
     con el botón y las garantías siempre a mano. Su principio se sale por arriba
     mientras tanto, sí, pero se recupera con solo subir —y lo que no puede
     perderse es aquello con lo que se compra, no el título, que además está a un
     palmo en la galería.
     Quién decide cuál de los dos toca es assets/js/ficha-v2.js: es una
     comparación entre el alto REAL de la caja y el hueco REAL, y ninguno de los
     dos números existe en CSS. La decisión se rehace cuando cambia la ventana,
     cuando cambia la cabecera (46 ↔ 168, igual que E5) y cuando cambia el alto de
     la caja —al elegir una variante, por ejemplo—.

     Y SE PEGA CON UN `top` NEGATIVO, NO CON `bottom`. Lo intuitivo sería
     `bottom: 16px`, y no funciona: un pegajoso no puede salirse de su bloque
     contenedor, y para sujetar por abajo una caja que empieza ARRIBA del bloque
     habría que empujarla hacia arriba, fuera de él. Medido en Chrome sobre la
     ficha del Sym (1440x720, modo pie a mano): con `bottom: 16px` la caja se
     queda en su sitio estático y se va por el techo —lat_bottom −24 donde el pin
     tocaba a 704—; poniéndola al final del bloque (`align-self: end`) sí clava
     los 704, pero entonces la página arranca con la caja en el sótano.
     Con `top` negativo sale solo: la caja sube con la página hasta que su borde
     superior llega a ese número —que es exactamente cuando el INFERIOR llega al
     pie de la ventana— y ahí se queda. Su principio queda por encima del techo,
     que es el precio de que el final esté siempre a mano, y vuelve solo al subir.
     El número lo calcula y publica el JS en `--fv2-pie-top`: depende del alto de
     la caja, del alto de la ventana y de dónde acabe la cabecera, y de eso el CSS
     no sabe nada. Allí está también la regla del título entero. */
  .fv2-lateral__pegado.fv2-lateral--pie {
    top: var(--fv2-pie-top, 0px);
    bottom: auto;
  }

  /* En la rejilla de dos columnas el orden lo dan las áreas; los `order` de
     móvil se anulan para que no queden dos mecanismos diciendo lo mismo. */
  .fv2-titulo,
  .fv2-compra__galeria,
  .fv2-compra__caja,
  .fv2-anclas,
  .fv2-contenido { order: 0; }
}

/* --- Título ------------------------------------------------------------- */

.fv2-titulo {
  margin: 0 0 14px;
  font-family: var(--fv2-titulos);
  font-weight: 600;
  font-size: 34px;
  line-height: 1.02;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  color: #111;
}

/* B1 · LA ESCALA DE LO GRANDE BAJA UN 10 %.
   Rafael veía la ficha «como con el zoom al 100 %» y al 90 % le parecía bien: el
   contenedor de 1440 no era el problema —eso lo pidió él en A2 y funciona—, lo era
   el TAMAÑO de las piezas grandes dentro de él. Baja lo grande y solo lo grande:
   la escalera del título, el precio, los títulos de sección, el botón de compra y
   los logos de la cabecera, con los aires de esos mismos elementos.
   NO baja nada que ya fuera pequeño (breadcrumb, códigos mono, micro-etiquetas,
   garantías, chips, barra de anclas, todo lo de 16 px o menos): reducir un 10 % un
   texto de 13 px lo deja en 11.7 y eso no es «menos zoom», es letra ilegible.
   Tampoco baja el móvil: allí el título ya está en 34/30/26 y la queja es del
   monitor de 27". */
@media (min-width: 1000px) {
  /* 52 -> 46. Es el peldaño de los títulos cortos (ver los cortes de
     mhw_ficha_clase_titulo). Se queda en 46 tras D3 — ver la nota de abajo. */
  .fv2-titulo {
    font-size: 46px;
    margin-bottom: 16px;
  }
}

/* D-T1: CUATRO PELDAÑOS, Y EL TÍTULO NO SE RECORTA (recalibrado en FIX-FICHA-2).
   Los 52 px del brief dan por hecho «Kit Clutch + Campana Malossi» (28
   caracteres). Los títulos reales llevan además la moto —«Kit Clutch y Campana
   Malossi Yamaha Nmax / Aerox», 47— y a 52 px eso es una torre de cuatro líneas.
   Adivinar el «nombre comercial» cortando por la marca rompería fichas y además
   ese texto alimenta a Google, así que el título se pinta ENTERO y lo que cede
   es el cuerpo.
   Los peldaños de antes (52/44/38, cortes en 34 y 44 caracteres) cortaban con
   elipsis 709 de los 1019 títulos publicados ya en la columna de 452 px, y 962 en
   la de 395. Los de B1 salieron de medir los 1019 con la fuente cargada en la caja
   real (571 px): con esa escalera no se cortaba NINGUNO. El detalle y su control
   positivo, en mhw_ficha_clase_titulo().
   AVISO: los tamaños de ESCRITORIO que se leen aquí abajo ya no son los de B1 —
   D3 estrechó la caja a 395 y hubo que volver a medirlos. Los CORTES por
   caracteres sí siguen siendo los de B1.
   El tope de líneas sigue siendo la red de seguridad, y en escritorio pasa a ser
   de 3 líneas (4 en los dos peldaños de abajo) por la misma razón: a 26 px,
   cuatro líneas miden menos que dos a 46, así que la caja de compra no crece por
   eso más de lo que se ahorra en cuerpo.
   El renombre de títulos es asunto de contenido y se decide en CP-SNIPPETS. */
.fv2-titulo--largo   { font-size: 30px; }
.fv2-titulo--xlargo  { font-size: 26px; }
.fv2-titulo--xxlargo { font-size: 26px; }

/* D3 (FIX-FICHA-6) · LA ESCALERA SE RECALIBRA PORQUE LA CAJA SE ESTRECHA.
   46/40/34/28 se midió en una caja de 571 px. D3 devuelve la columna de compra a
   los 395 px del brief, y una escalera medida en una caja un 45 % más ancha no
   vale: con la de B1 en la caja nueva se cortan con elipsis **306 de los 1019**
   títulos publicados (control positivo de la medición: si ese número hubiera
   salido 0, la caja no habría cambiado y no habría nada que recalibrar).
   La nueva sale de medir otra vez los 1019 con la fuente REAL cargada y en la
   caja REAL de 395 px, probando catorce tamaños (46…20) y contando líneas por
   scrollHeight — ver tests/cdp/_medir-titulos-f6.py:

     46 / 38 / 38 / 26  con topes de 3 / 3 / 4 / 4 líneas  ->  0 cortados de 1019

   LOS CORTES POR CARACTERES NO SE TOCAN (36/46/64) y eso es deliberado: los
   emite mhw_ficha_clase_titulo() en PHP, o sea que la MISMA clase la usan
   escritorio y móvil. Moverlos habría cambiado el tamaño del título en móvil, y
   el brief dice que móvil no cambia. Con los cortes fijos, lo único que se puede
   ajustar es el tamaño y el tope de líneas de escritorio, que es lo que se hace.
   El barrido completo dice que soltando los cortes se llegaría a una media de
   33,2 px por título en vez de 37,6 — o sea que fijarlos no cuesta tamaño: lo da.
   POR QUÉ 38 EN DOS PELDAÑOS SEGUIDOS. En 395 px no caben cuatro escalones
   distintos sin cortar: medido, el peldaño de 37-46 caracteres no pasa de 38 px y
   el de 47-64 tampoco. Lo que los distingue ya no es el cuerpo sino el tope de
   líneas (3 y 4). Se deja la clase igual para no tocar el PHP el día que la caja
   vuelva a moverse.
   Y UNA CUARTA LÍNEA. El brief original pedía «una o dos líneas, nunca tres» para
   un título de 28 caracteres; con la mediana del catálogo en 49 y máximos de 107
   en una caja de 395, cada línea que se le niega al título se la cobra en cuerpo:
   con tope de 3 el peldaño de la mediana caería a 28 px, más pequeño que el
   precio, que es justo la jerarquía al revés. */
@media (min-width: 1000px) {
  .fv2-titulo--largo   { font-size: 38px; }
  .fv2-titulo--xlargo  { font-size: 38px; }   /* sus topes de líneas, más abajo */
  .fv2-titulo--xxlargo { font-size: 26px; }
}

/* Tope de líneas: 3 en móvil y 2 en escritorio. Con un solo nodo de título ya se
   puede declarar sobre `.fv2-titulo` a secas — con las dos copias había que
   hacerlo sobre cada una, porque `display: -webkit-box` habría pisado el
   `display: none` que escondía a la otra y habrían salido los dos títulos. */
.fv2-titulo {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
}

@media (min-width: 1000px) {
  /* D3: el tope de escritorio sube de 2 a 3 líneas (y a 4 en los dos peldaños de
     abajo) por lo mismo que baja la escalera — la caja pasó de 571 a 395 px. Con
     el tope en 2, la única forma de no cortar era bajar el peldaño de la mediana
     a 28 px. La cuenta está arriba. */
  .fv2-titulo { -webkit-line-clamp: 3; }

  /* Los topes de CUATRO líneas de los dos peldaños de abajo van AQUÍ y no arriba,
     con sus tamaños: `.fv2-titulo--xlargo` y `.fv2-titulo` tienen la misma
     especificidad (0,1,0), así que manda la que se declara después. Puestas junto
     al tamaño, estas reglas existían y no se aplicaban — medido:
     `webkitLineClamp` salía 2. */
  .fv2-titulo--xlargo,
  .fv2-titulo--xxlargo { -webkit-line-clamp: 4; }
}

/* --- Galería ------------------------------------------------------------ */

.woocommerce div.product.ficha-v2 div.images,
.ficha-v2 .fv2-compra__galeria .repuestos-single-product-gallery-column {
  float: none;
  width: 100%;
  margin: 0;
}

/* D3/D1: la columna izquierda mide 965 px, pero la galería se queda en el hueco
   de siempre y alineada a la izquierda. Ver `--fv2-galeria`. */
@media (min-width: 1000px) {
  .ficha-v2 .fv2-compra__galeria { max-width: var(--fv2-galeria); }
}

.ficha-v2 .fv2-compra__galeria .woocommerce-product-gallery {
  display: block;
  width: 100%;
  margin: 0;
  /* N3 (FIX-FICHA-14): esto es lo que hace medible el marco. Con la galería
     declarada contenedor de tamaño en línea, `100cqw` vale su ancho — el mismo
     número contra el que se resuelve el `100%` del `max-width` de la foto—, y con
     eso la altura del marco se puede escribir en una ALTURA, donde un porcentaje
     no serviría (se mediría contra la altura del padre, que es justo la incógnita).
     Solo `inline-size`: el alto NO se contiene, faltaría más. */
  container-type: inline-size;
}

/* G1 · FUERA EL CUADRO, Y LA FOTO A LO GRANDE.
   Lo que había —fondo blanco con borde de 1 px y 24 px de padding— seguía siendo
   un CUADRO: una caja dibujada alrededor de una foto que ya viene recortada sobre
   blanco. Al quitarlo, la foto queda sobre el blanco de la página, que es lo que
   pidió Rafael, y el aire lo pone la propia rejilla.
   Antes el marco tenía altura FIJA (300 móvil / 560 desktop) y la foto flotaba
   dentro dejando franjas arriba y abajo: se veía estirada. Ahora la caja crece con
   la foto y el tope lo pone `max-height` en la propia imagen, con `object-fit:
   contain` para que ese tope no la deforme.
   Lo que NO cambia: las reglas siguen prefijadas con `div.product.ficha-v2` para
   subir la especificidad a (0,4,2) y ganarle a la hoja de WooCommerce
   —`.woocommerce div.product div.images img { height:auto }` es (0,3,3)— sin un
   solo !important.
   NOTA DE SINTAXIS (FIX-FICHA-4): aquí había DOS bloques de comentario pegados
   con un cierre de comentario de más en medio, herencia de una edición anterior.
   Los ~14 renglones sueltos que quedaban fuera del comentario los leía el parser
   como CSS, y como dentro había un `img { height:auto }`, la recuperación de
   errores se comía el selector de la regla siguiente: la regla del wrapper de
   aquí abajo NO se estaba aplicando. Un comentario mal cerrado no da error
   visible; se lleva por delante la regla que viene después.

   Y VOLVÍA A PASAR (FIX-FICHA-6). El párrafo de arriba escribía ese cierre de
   comentario TAL CUAL, entre comillas invertidas, para contar la anécdota. Al
   parser las comillas invertidas no le dicen nada: cerraba el comentario ahí
   mismo, leía los seis renglones siguientes como basura y se llevaba por delante
   —otra vez— la regla del wrapper. Medido con
   tests/cdp/_diag-comentario.py: la regla NO aparecía entre las cargadas del
   documento, con el control positivo de otra regla de este mismo fichero (el
   `max-height: 526px` de la foto) que sí aparecía. No se veía porque todo lo que
   declara coincide con los valores por defecto — pero la próxima regla que se
   escribiera detrás habría desaparecido en silencio. Aquí ese cierre se nombra
   con palabras y no con símbolos, que es la única forma de contarlo sin repetirlo. */

/* -------------------------------------------------------------------------
   CP-GALERIA · P1 · EL TOPE DE ALTO DEL MARCO, EN UN SOLO SITIO
   -------------------------------------------------------------------------
   Este número —420 en móvil, 526 en escritorio— lo declaraban TRES reglas por su
   cuenta: el `min-height` del marco, el `max-height` de la foto y (desde P1) haría
   falta una cuarta para el tope de la tira de miniaturas. Cuatro literales del
   mismo número es cómo se llega a que uno de ellos cambie y los otros no.

   De dónde sale el 526, que es lo único que este bloque hereda de D1 y no
   reescribe: son 2/3 de los 790 px del hueco de la galería. Es ABSOLUTO y no un
   porcentaje a propósito — lo que tiene que parecerse es el tamaño de la foto
   entre UNA ficha y OTRA, y el hueco cambia de una a otra (790 sin tira de
   miniaturas, 686 con ella); un tope en porcentaje daría dos topes distintos para
   el mismo trabajo. Medido a 1500 px cuando se fijó: la Koso 789x592 pasó a
   789x526 (mancha 702x526, la liga del kit), y el kit (741x494) y el filtro
   quedaron por debajo del tope, intactos.

   LA TIRA LO USA COMO RESERVA, NO COMO MEDIDA. El tope de la tira tiene que ser el
   alto REAL del marco, que casi siempre es MENOR que este número (la ficha del
   exosto Leovince: marco 479, tope 526). Ese alto real lo publica
   assets/js/ficha-v2.js en `--fv2-tira-tope` midiendo el marco pintado; esto es lo
   que vale mientras el script no ha llegado, o si no llega nunca. */
.ficha-v2 .fv2-compra__galeria {
  --fv2-marco-tope: 420px;
}

@media (min-width: 1000px) {
  .ficha-v2 .fv2-compra__galeria {
    --fv2-marco-tope: 526px;
  }
}

.woocommerce div.product.ficha-v2 div.images .woocommerce-product-gallery__wrapper {
  margin: 0;
  background: transparent;
  border: 0;
  padding: 0;
  box-sizing: border-box;

  /* N3 (FIX-FICHA-14) · EL MARCO. Antes esto era `display: block` sin altura, así
     que el bloque se amoldaba a la foto puesta y cambiar de miniatura movía todo
     lo de abajo: medido en el morral, el encabezado DESCRIPCIÓN saltaba 90 px.
     `--fv2-marco` lo emite el servidor en el `style` de la galería con el alto de
     la foto MÁS ALTA del producto (ver mhw_ficha_alto_marco): una sola altura por
     producto, resuelta antes del primer pintado.
     El tope de alto se aplica AQUÍ y no en la cuenta del servidor porque cambia
     con el breakpoint; sacarlo fuera del máximo no altera el resultado, porque
     min() distribuye sobre max().
     Sin la variable —producto sin metadatos de imagen— el reserva es `0px` y esto
     se queda exactamente en lo que había.
     Es `min-height` y no `height`: si algún día una foto se sale de la cuenta
     (la de una variación, por ejemplo), el marco crece en vez de recortarla.
     CP-GALERIA/P1: el tope, que cambia con el breakpoint, deja de escribirse aquí
     como literal y pasa a ser `--fv2-marco-tope` (ver el bloque de arriba). Los dos
     valores son los mismos de antes; lo que cambia es que ahora se declaran una vez. */
  min-height: min(var(--fv2-marco-tope), var(--fv2-marco, 0px));
  display: grid;
  align-content: center;
}

/* B2 · UNA FOTO NUNCA SE PINTA MÁS GRANDE DE LO QUE ES.
   G1 puso `width: 100%` para que una foto pequeña no quedara diminuta en una
   columna de 540 px, y con la columna a 789 (A2) eso se volvió el defecto que
   Rafael ve: los archivos viejos —muchos de 800 px o menos— se estiraban y salían
   borrosos. Un píxel inventado por interpolación no es información: la foto vieja
   se ve MEJOR pequeña y nítida que grande y blanda.
   El tope es `min(100%, --fv2-nat)`, o sea min(ancho de la columna, ancho REAL del
   archivo). `--fv2-nat` lo pone el servidor en el `style` de la imagen con el
   ancho del original (mhw_ficha_sizes_galeria) — el CSS no puede consultar el
   tamaño natural de una imagen, y el atributo `width` del HTML no sirve para esto
   porque es el de la variante `woocommerce_single` (600 px), no el del archivo.
   Con `margin: 0 auto` la que no llega al ancho de la columna queda centrada, a su
   tamaño, sin marco alrededor.
   El reserva `100%` mantiene el comportamiento de antes si algún día llega una
   imagen sin metadatos de tamaño.

   C1 · Y ADEMÁS SE NORMALIZA EL PESO VISUAL.
   `--fv2-k` es el factor de encuadre que emite el servidor (ver
   mhw_ficha_factor_ocupacion): 1 para las fotos que ya traen aire alrededor de la
   pieza, y menor que 1 para las que la llevan pegada al borde del lienzo. La foto
   apretada se pinta un poco más pequeña dentro del MISMO marco, y así deja de
   verse «con zoom» al lado de las demás.
   La cuenta es `tope x k` con dos topes que no se negocian:
     · por arriba, `--fv2-tope` = min(columna, archivo) — la regla de B2 sigue
       mandando, el factor solo puede REDUCIR;
     · por abajo, el 45 % de la columna, para que una foto con la pieza muy pequeña
       no acabe siendo un sello. Ese piso va en `%` a propósito: es del HUECO, y el
       ancho del hueco solo lo conoce el navegador (789 px, 685 con miniaturas,
       92vw en móvil).
   Sin `--fv2-k` —imágenes de las que el censo se abstuvo— el reserva es 1 y esto
   se queda exactamente en lo que había. */
.woocommerce div.product.ficha-v2 .woocommerce-product-gallery__image img {
  --fv2-tope: min(100%, var(--fv2-nat, 100%));
  display: block;
  width: 100%;
  height: auto;
  max-width: min(var(--fv2-tope), max(45%, calc(var(--fv2-tope) * var(--fv2-k, 1))));
  /* CP-GALERIA/P1: era `420px` aquí y `526px` en un override de escritorio. Mismo
     número, mismo resultado, un solo sitio donde se dice. Ver `--fv2-marco-tope`. */
  max-height: var(--fv2-marco-tope);
  padding: 0;
  object-fit: contain;
  margin: 0 auto;
  /* H1 (FIX-FICHA-10): aquí vivía `box-shadow: var(--fv2-sombra-foto)`. Se retira
     entera: la foto principal va limpia sobre el fondo de la página, sin marco,
     sin borde y sin sombra, igual que la de las tarjetas. El porqué —y la
     medición que demostró que no había ninguna sombra que «restaurar»— está en
     assets/css/fv2-componentes.css, donde estaba el token. */
}

/* D5 · CAMBIO DE FOTO EN LÍNEA, Y AHORA DE VERDAD.
   Sin JS se ve la primera (:first-child); en cuanto el script arranca marca el
   wrapper y entonces manda .is-activa. Los slides que no se ven siguen EN EL DOM
   porque PhotoSwipe indexa por ellos.

   POR QUÉ EL PREFIJO ES TAN LARGO. Estas tres reglas se escribieron con
   `.ficha-v2 …` (0,4,0) y NO se aplicaban: perdían por especificidad contra dos
   hojas a la vez —el bloque de la galería vieja de theme.css (0,7,2), ya acotado
   allí con `:not(.ficha-v2)`, y el fallback sin-JS del core de WooCommerce
   (`div.images .woocommerce-product-gallery__image:nth-child(n+2) {width:25%;
   display:inline-block}`, 0,5,2)—. El síntoma que veía Rafael era exactamente
   este: se pulsaba una miniatura, la clase saltaba en el DOM y la foto grande no
   se movía. Con `div.product.ficha-v2 div.images` el peso sube a (0,6,2) y gana a
   los dos sin un solo !important y sin depender del orden de las hojas.

   CP-GALERIA · P2 · Y AHORA SOLO EN ESCRITORIO.
   Enseñar-y-esconder es lo correcto donde hay una tira de miniaturas al lado y un
   ratón que pulsarla. En móvil no hay ni una cosa ni la otra: el gesto es el dedo, y
   un carrusel que solo responde a los guiones es un carrusel que la mitad de la
   gente no descubre. Por debajo de 1000 la galería pasa a ser un CARRIL deslizable
   de verdad (más abajo), y para eso los slides tienen que estar TODOS pintados —que
   es justo lo que estas tres reglas impedían—. Se acotan, no se borran.

   Y con esto el carril funciona SIN JAVASCRIPT: los once slides están en el flujo,
   el `overflow-x` los deja recorrer con el dedo y el `scroll-snap` los para en su
   sitio. Lo único que aporta el script en móvil es pintar los guiones. */
@media (min-width: 1000px) {
  .woocommerce div.product.ficha-v2 div.images .woocommerce-product-gallery__wrapper > .woocommerce-product-gallery__image {
    display: none;
    width: 100%;
  }

  .woocommerce div.product.ficha-v2 div.images .woocommerce-product-gallery__wrapper > .woocommerce-product-gallery__image:first-child,
  .woocommerce div.product.ficha-v2 div.images .woocommerce-product-gallery__wrapper > .woocommerce-product-gallery__image.is-activa {
    display: block;
  }

  .woocommerce div.product.ficha-v2 div.images .woocommerce-product-gallery__wrapper.fv2-tiene-activa
    > .woocommerce-product-gallery__image:first-child:not(.is-activa) {
    display: none;
  }
}

/* -------------------------------------------------------------------------
   CP-GALERIA · P2 · EL CARRIL TÁCTIL DE MÓVIL (< 1000)
   -------------------------------------------------------------------------
   LO QUE HABÍA. La foto grande cambiaba por JS —enseñar uno, esconder los demás— y
   el único mando eran los guiones y una tira de miniaturas de 296 px de alto
   apilada debajo. En un teléfono eso es un carrusel que no se puede arrastrar:
   deslizar sobre la foto no hacía nada, que es exactamente el gesto que cualquiera
   prueba primero.

   LO QUE HAY. Un carril horizontal nativo. Cada slide ocupa el 100 % del ancho, el
   `scroll-snap-type: x mandatory` lo para donde toca y el arrastre lo hace el
   navegador — sin librería, sin listeners de `touchmove` y sin una posición
   guardada en ninguna variable. La tira de miniaturas SE OCULTA: con el carril, es
   un segundo mando para lo mismo ocupando un tercio de la pantalla.

   `scroll-snap-align: center` y no `start`: con `start`, un slide cuya foto no llena
   el ancho queda pegado al borde izquierdo del carril y el aire cae todo a la
   derecha; centrado, el aire se reparte y la foto queda donde la vista la busca.

   EL PELLIZCO-ZOOM SE QUEDA: el `<a>` de cada slide sigue abriendo PhotoSwipe al
   tocarlo, y PhotoSwipe indexa por la posición del slide en el DOM
   (`$(clicked).index()` en el single-product.js de WooCommerce), que es la misma con
   los once slides pintados que con diez escondidos.

   La barra de scroll se esconde en los dos motores: en un carril de fotos es ruido,
   y los guiones ya dicen cuántas hay y en cuál se está. */
@media (max-width: 999.98px) {
  /* (El `display: none` de la tira NO va aquí: la regla base que la declara
     `display: flex` se escribe más abajo en esta misma hoja y pesa lo mismo, así que
     ganaría por orden. Va al final, junto a los guiones — ver «LA TIRA NO SE PINTA
     EN MÓVIL».) */

  .woocommerce div.product.ficha-v2 div.images .woocommerce-product-gallery__wrapper {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;

    /* F3 (CP-GALERIA/FIX-1) · EL MARCO UNIFORME DEL CARRIL.
       Rafael comparó dos fichas suyas en el teléfono y la marca, el precio y el
       botón caían a alturas distintas. Medido a 412 px (Playwright): el carril
       mide 248.1 px en FM01218, 223.3 en FM01093 y 279 en el exosto de 11 fotos,
       con el hueco IDÉNTICO (372 px) en las tres. La diferencia no es el ancho:
       sin marco, el alto del carril lo ponía la foto más alta de cada producto, y
       eso lo deciden dos cosas ajenas al diseño — el aspecto del archivo (1.5 en
       unas, 1.33 en otras) y `--fv2-k`, el factor de encuadre por foto (la
       principal de FM01093 trae 0.8412 y por eso su carril era el más bajo).
       Los 24.8 px de diferencia entre las dos fichas de Rafael reaparecían clavados
       en la Y de los logos, del precio y del botón: 697.9 contra 673.1.

       EL 3:2 NO ES UN NÚMERO A OJO, Y EL 4:3 QUE HABÍA AQUÍ ERA EL CRITERIO
       EQUIVOCADO. Censo de las 1655 fotos de galería del catálogo
       (tests/_censo-aspecto-galeria.php, tests/_censo-k-galeria.php):
           1.33 → 1159 fotos (70.0 %) · 1.50 → 464 (28.0 %) · 1.00 → 26 · 0.77 → 6
       FIX-1 eligió 4:3 por «cuál llena el hueco de borde a borde para más fotos».
       En un teléfono lo que se paga es el ALTO, y con ese criterio el 28 % de las
       fotos dejaba una franja muerta arriba y abajo — Rafael la vio en el variador
       Malossi: 51 px de nada entre la foto y el logo, con el código fuera de
       pantalla.

       CORRECCIÓN (CP-FUENTES · A4). Aquí ponía «ninguna foto del catálogo es MÁS
       ancha que 3:2», y no es verdad: SÍ las hay, 57 de las 1655 (3,44 %). El error
       no fue del razonamiento sino del censo, que redondeaba el aspecto a dos
       decimales ANTES de contar, de modo que todo lo que estaba entre 1,495 y 1,505
       caía en el cubo «1.50» y desaparecía del recuento. Recontado sin redondear
       (tests/_censo-aspecto-exacto.php), la más ancha del catálogo es 1,502146
       (#625, 700×466).

       EL CONTRATO AGUANTA IGUAL, y por poco margen pero por una razón sólida: lo que
       importa no es el aspecto sino el AIRE que deja. En un marco de ancho A el aire
       vertical de una foto de aspecto r es A·(1/1,5 − 1/r), y con A = 372 px —el
       ancho real del carril a 412 px de pantalla— las 57 dejan 0,1 px cada una salvo
       una que deja 0,4. El peor caso del catálogo entero son 0,354 px: por debajo de
       un píxel de pantalla, o sea invisible e irredondeable. «Ninguna deja aire» era
       falso; «ninguna deja aire que se pueda ver» es lo que se midió y es lo que
       sostiene el marco. La diferencia importa el día que entre una foto
       panorámica de verdad: 16:9 dejaría 60 px de franja muerta y habría que
       revisar esta decisión, cosa que la frase vieja invitaba a no hacer nunca.

       Con eso dicho, y sobre las 1655:

           marco          aire vertical medio     alto medio de la foto
           4:3 con k         47.8 px                 231.2 px   ← lo que había
           4:3 sin k          8.7 px                 270.3 px
           3:2 con k         25.3 px                 222.7 px
           3:2 sin k          0.0 px                 248.0 px   ← lo que hay

       Y el marco baja de 279 a 248 px a 412 px de pantalla: 31 px que suben el
       logo, el precio y el código en TODA ficha.

       El tope es el mismo `--fv2-marco-tope` que gobierna la foto en escritorio: por
       encima de ~560 px de hueco el 4:3 pediría más de 420 px de alto y ahí manda el
       tope — el marco deja de ser 4:3 pero SIGUE siendo el mismo para toda ficha,
       que es lo que se pedía. Se recalcula solo con el ancho; ninguna foto lo mueve.

       `--fv2-k` SE NEUTRALIZA EN EL CARRIL (ver la regla de la foto, más abajo), y
       no es un capricho: es lo que hace que el aire sea cero. `k` es el factor de
       encuadre por foto —cuánto ocupa el objeto dentro de su lienzo— y en escritorio
       hace su trabajo, porque allí la columna es ancha y sobra sitio. En el carril
       la foto ya está en el ancho de la pantalla: encogerla otro 15 % (k medio 0.854,
       y 1124 de las 1655 fotos vienen con k < 1) solo servía para volver a abrir la
       franja muerta que se acaba de cerrar. Con `k` fuera, 985 fotos (59.5 %) se ven
       MÁS GRANDES que antes, 144 igual y 526 (31.8 %) algo más pequeñas —las que hoy
       llenaban los 372 px de borde a borde, que pasan a 330—, y ninguna deja aire. */
    aspect-ratio: 3 / 2;
    height: auto;
    max-height: var(--fv2-marco-tope);

    /* Y LAS DOS LÍNEAS QUE EL GATE OBLIGÓ A ESCRIBIR, cada una por su fallo:

       `min-height: 0` — la regla base reserva `min(--fv2-marco-tope, --fv2-marco)`,
       que es el marco de ESCRITORIO calculado por el servidor para cada producto.
       En clutch-yamaha-n-max-connected-version2 esa reserva son 371.4 px y le ganaba
       al 279 del 4:3: el carril volvía a medir distinto que en las demás fichas, o
       sea el fallo de Rafael otra vez, ahora por el otro lado. En móvil el marco es
       el de aquí y la reserva de escritorio sobra.

       `width: 100%` — sin ancho declarado, un `aspect-ratio` con alto definido
       resuelve el ANCHO: la misma ficha salía con el carril a 495.2 px dentro de una
       columna de 372 y el documento se iba a 515 px de ancho en una pantalla de 412.
       Eso es scroll horizontal en el teléfono, que es peor que el defecto que se
       venía a arreglar. Lo cazó el check de «mide lo mismo en todas las fichas»
       porque los huecos salieron distintos; sin él habría llegado a las capturas. */
    width: 100%;
    min-height: 0;
  }

  .woocommerce div.product.ficha-v2 div.images .woocommerce-product-gallery__wrapper::-webkit-scrollbar {
    display: none;
  }

  .woocommerce div.product.ficha-v2 div.images .woocommerce-product-gallery__wrapper > .woocommerce-product-gallery__image {
    /* `flex: 0 0 100%` y no `width: 100%`: en un contenedor flexible la base de
       flex manda sobre el ancho, y con `auto` el slide se encoge hasta la foto —
       con lo que dejarían de caber justo uno por pantalla y el `snap` pararía en
       sitios que no son el principio de ninguna foto. */
    flex: 0 0 100%;
    max-width: 100%;
    scroll-snap-align: center;
    scroll-snap-stop: always;
    display: flex;
    align-items: center;
    justify-content: center;
    /* F3 · el slide llena el marco de arriba abajo; la foto se centra dentro. */
    height: 100%;
  }

  /* F3 · Y LA FOTO SE CONTIENE EN EL MARCO, NUNCA LO ESTIRA.
     El `max-height: var(--fv2-marco-tope)` de la regla base es un tope de 420 px que
     aquí llegaría tarde: el marco del carril mide lo que mida el hueco (279 px a
     412 px de pantalla), así que el tope del móvil es el marco y no un absoluto.
     `object-fit: contain` con `width/height: auto` deja que el aspecto real mande y
     el aire caiga a los lados o arriba y abajo, según la foto — que es exactamente
     lo que hace uniforme el hueco sin recortar a nadie. */
  /* FIX-2 · EL ENLACE TIENE QUE LLENAR EL SLIDE, Y NO ES ESTÉTICA.
     La foto no cuelga del slide: cuelga de un <a> (el que abre PhotoSwipe). Ese <a>
     no tenía alto declarado, así que era el bloque contenedor de la imagen y su
     alto era `auto` — o sea INDEFINIDO. Contra un alto indefinido, el
     `max-height: 100%` de la foto no tiene contra qué resolverse y Chrome
     sencillamente no lo aplica: en el exosto (fotos 379×284) la foto salía a 279 px
     dentro de un marco de 248 y el `overflow` se comía los 31 de abajo. Con el <a>
     a `height: 100%` el porcentaje ya tiene un número detrás. */
  .woocommerce div.product.ficha-v2 div.images .woocommerce-product-gallery__wrapper > .woocommerce-product-gallery__image > a {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    min-height: 0;
  }

  .woocommerce div.product.ficha-v2 div.images .woocommerce-product-gallery__wrapper > .woocommerce-product-gallery__image img {
    max-height: 100%;
    height: auto;
    object-fit: contain;
    /* FIX-2 · el ancho máximo vuelve a ser el hueco entero: aquí NO manda `--fv2-k`
       (el porqué, con las cifras del censo, en el bloque del marco de arriba). Es
       `max-width` y no `width`, así que una foto cuyo archivo sea más pequeño que
       el hueco sigue sin estirarse — el tope de `--fv2-nat` de la regla base seguía
       siendo el correcto y ese no se toca. */
    max-width: min(100%, var(--fv2-nat, 100%));
  }
}

@media (min-width: 1000px) {
  /* La columna de miniaturas SOLO cuando hay miniaturas. Sin el :has(), un
     producto con una única foto (que son mayoría hoy) reservaba igualmente los
     88 px y la foto quedaba desplazada, con una franja vacía a su izquierda. */
  .ficha-v2 .fv2-compra__galeria .repuestos-single-product-gallery-column:has(.rafas-gallery-thumbs) {
    display: grid;
    /* P4: la columna la manda el tile, que ahora es apaisado (136). Antes 88 fijo. */
    grid-template-columns: var(--fv2-mini-w) minmax(0, 1fr);
    gap: 16px;
    align-items: start;
  }

  /* La tira de miniaturas pasa a la columna izquierda. Es el mismo nodo que ya
     pinta product-image.php desde el servidor: no se reconstruye, se recoloca.

     CP-GALERIA · P1 · Y NO PASA DEL ALTO DEL MARCO.
     EL DEFECTO, medido en /tienda/mofle-yamaha-mt-09-leovince/ (11 fotos) a 1500 px:
     tira 1011 px de alto contra un marco de 479 — la columna entera se iba a 1068 px
     y quien manda en el alto de la galería deja de ser la foto para pasar a ser una
     lista de sellos. Con 11 miniaturas la columna crecía 589 px por la tira.
     El tope es `--fv2-tira-tope`, que publica assets/js/ficha-v2.js con el alto
     PINTADO del marco (479 en esa ficha, no 526): un `max-height` en píxeles fijos
     sería otra vez un número mágico, y el declarado —`--fv2-marco-tope`— solo es el
     techo, no la medida. El reserva es ese techo: sin JS la tira queda acotada a 526
     en vez de a 479, que es peor que con script y muchísimo mejor que 1011.
     `min-height: 0` porque es un ítem de rejilla: sin él, el contenido le pondría un
     mínimo automático y el `max-height` no llegaría a recortar nada.
     `overscroll-behavior: contain` para que al llegar al final de la tira el gesto NO
     siga arrastrando la página: la tira es un cajón, no una parte del scroll. */
  .ficha-v2 .fv2-compra__galeria .rafas-gallery-thumbs {
    order: -1;
    grid-column: 1;
    display: flex;
    flex-direction: column;
    /* `nowrap` EXPLÍCITO, y no es redundante: la regla base declara `wrap` para la
       fila de móvil, y en una columna flexible con tope de alto ese `wrap` no lo
       ignora nadie — parte la tira en COLUMNAS. Medido antes de escribir esta línea
       en el exosto Leovince: las 11 miniaturas «cabían» en 453 px porque se habían
       repartido en tres columnas, dos de ellas fuera de la caja y tapadas por el
       `overflow-x: hidden`. La tira parecía completa y no había nada que desplazar.
       Antes de P1 esto no se veía porque sin `max-height` no hay contra qué
       envolver: el `wrap` estaba puesto y no mordía. */
    flex-wrap: nowrap;
    gap: 12px;
    margin: 0;
    min-height: 0;
    max-height: var(--fv2-tira-tope, var(--fv2-marco-tope));
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior: contain;
    /* La tira para en casillas enteras. Va con el tope de alto, que también es un
       número entero de casillas (ver medirTira() en assets/js/ficha-v2.js): sin el
       snap, un gesto de rueda deja media miniatura cortada arriba y otra media
       abajo, y el «+N» —que va en la última ENTERA— acabaría con un trozo de la
       siguiente asomando debajo, que es justo la lectura que hay que evitar. */
    scroll-snap-type: y mandatory;
    /* Barra discreta: la tira es un detalle de la galería, no un panel. Firefox por
       la propiedad estándar; Chrome por el pseudoelemento, que es lo único que
       entiende. Los dos dicen lo mismo. */
    scrollbar-width: thin;
    scrollbar-color: #c9c9c9 transparent;
  }

  .ficha-v2 .fv2-compra__galeria .rafas-gallery-thumbs::-webkit-scrollbar {
    width: 6px;
  }

  .ficha-v2 .fv2-compra__galeria .rafas-gallery-thumbs::-webkit-scrollbar-track {
    background: transparent;
  }

  .ficha-v2 .fv2-compra__galeria .rafas-gallery-thumbs::-webkit-scrollbar-thumb {
    background: #c9c9c9;
    border-radius: 3px;
  }

  /* La pareja del `scroll-snap-type` de arriba: cada casilla es una parada. */
  .ficha-v2 .fv2-compra__galeria .rafas-gallery-thumbs button {
    scroll-snap-align: start;
  }

  .ficha-v2 .fv2-compra__galeria .woocommerce-product-gallery { grid-column: 2; }

  /* Con tira de miniaturas la columna es una rejilla, así que el aviso de
     ampliar tiene que decir a cuál de las dos pertenece: va bajo la FOTO, no
     bajo las miniaturas. */
  .ficha-v2 .fv2-compra__galeria .fv2-lupa { grid-column: 2; }

  /* D1 · EL MARCO TIENE TOPE DE ALTO: DOS TERCIOS DE LA COLUMNA.
     C1 normalizó el ANCHO de la mancha (factor de ocupación) y con eso las fotos
     apretadas dejaron de verse con zoom, pero quedaba la otra mitad del problema:
     una foto de lienzo alto —la Koso, 790x592— llenaba los 789 px de columna y se
     iba a 592 px de alto, mientras el kit se quedaba en 494. Al lado la Koso
     seguía pareciendo otra liga, no por su ancho sino por su ALTO.
     Convive con lo de antes sin pelearse: `max-width` sigue poniendo el tope de
     ancho (columna, archivo y factor) y el tope de alto lo pone `--fv2-marco-tope`;
     con `object-fit: contain` el recorte no existe —la foto se pinta entera y más
     pequeña— y la regla de NUNCA AGRANDAR se mantiene, porque un tope solo puede
     reducir.
     CP-GALERIA/P1 · AQUÍ VIVÍA EL `max-height: 526px` DE ESCRITORIO. Se retira la
     regla, no el tope: el 526 pasó a `--fv2-marco-tope`, que esta misma media query
     ya declara más arriba y que la regla base de la foto consume. Mismo valor
     computado, un solo sitio donde cambiarlo, y de paso la tira de miniaturas puede
     leerlo — que es lo que P1 necesitaba y con el número escrito aquí dentro no
     podía hacer. */
}

/* D5 · MINIATURAS COMO EN EL PATRÓN MALOSSI: FONDO, NO CUADRO.
   Lo que se leyó en su ficha (camiseta 4120775_Y4, con el navegador y en
   lectura): la tira es vertical, cada miniatura lleva un borde de 2 px
   TRANSPARENTE —sitio reservado, para que nada salte— y la activa NO lleva
   cuadro negro; el reposo es fondo transparente y opacidad 1. Aquí se replica el
   patrón con nuestros grises: la activa y la que está bajo el ratón se marcan
   con un fondo gris claro y ningún borde.
   Se retira el cuadro negro de 2 px que pedía el brief original (y con él el
   truco de restarle 2 px al padding para que la foto no diera el salto): con el
   fondo no hay caja que aparezca, así que no hay salto que compensar.
   `opacity: 1` está declarado a propósito: la galería vieja de theme.css deja las
   miniaturas al 0.7 y esa regla —que no dimensiona nada— seguía aplicándose aquí.
   Medido: las cuatro miniaturas de /tienda/zapatas-clutch-nmax/ salían a 0.7. */
/* P4 (FIX-14-P) · LA CAJA DE LA MINIATURA, CON LAS PROPORCIONES DE MALOSSI.
   Medido en su ficha con el navegador (misma camiseta 4120775_Y4 del precedente
   D4, solo lectura):
     · caja de contenido 188 x 112,5 px  ->  relación 1,671. APAISADA, no cuadrada.
     · foto dentro a `contain`, pintada 169 x 112,5: ocupa el 89,9 % del ancho de
       la caja y el 100 % del alto, con 9,5 px de lienzo a cada lado.
     · 12 px de separación entre miniaturas. Sin radio de esquina.
   Aquí se copia la RELACIÓN (1,679 = 136/81, a 0,5 % de la suya) y la ocupación,
   no sus píxeles: su tira mide 192 y la nuestra tiene que caber en una galería de
   790. Con 136 la columna de la foto queda en 638 (antes 686): la miniatura pasa
   de 88 x 88 a 136 x 81 y la foto grande cede 48 px. Ese es el precio declarado.
   Con una foto típica nuestra (1,499) en esta caja, `contain` la pinta a
   121 x 81: 89,3 % del ancho y 7,3 px de lienzo por lado — las cifras de Malossi.
   El padding es 0, como el suyo: el margen que hace falta para que la fusión no
   se vea como un recuadro NO lo pone la caja, lo pone el lienzo de la propia foto,
   y para eso lo que había que arreglar era el recorte cuadrado de fábrica (ver
   woocommerce/single-product/product-image.php). */
.ficha-v2 .fv2-compra__galeria {
  --fv2-mini-w: 108px;
  --fv2-mini-h: 64px;
}

@media (min-width: 1000px) {
  .ficha-v2 .fv2-compra__galeria {
    --fv2-mini-w: 136px;
    --fv2-mini-h: 81px;
  }
}

/* La tira es una columna flexible; la vuelve columna la regla de la rejilla de más
   arriba (solo cambia `flex-direction`).

   CP-GALERIA · P2 · AQUÍ DECÍA `flex-wrap: wrap`, Y SE RETIRA.
   Existía para la tira de MÓVIL, que era una fila que envolvía en varias líneas.
   Con P2 esa tira ya no se pinta por debajo de 1000 —el carril la releva— así que
   el único ancho donde esta regla se aplica es escritorio, donde la tira es una
   columna y envolver es justo lo que no puede hacer.
   Y no era inofensivo dejarlo: en cuanto P1 le puso tope de alto a la columna, el
   `wrap` tuvo contra qué envolver y partió la tira en TRES columnas —las dos de la
   derecha fuera de la caja, tapadas por el `overflow-x: hidden`—. La tira parecía
   completa, no había nada que desplazar y el «+N» no aparecía nunca.
   El `nowrap` se declara además EXPLÍCITAMENTE en la regla de escritorio, y no por
   duplicar: si algún día vuelve un `wrap` a esta regla base, ganaría por orden
   —pesan lo mismo y esta se escribe después—, y ese es exactamente el fallo que
   costó una medición encontrar. */
.ficha-v2 .fv2-compra__galeria .rafas-gallery-thumbs {
  display: flex;
  gap: 12px;
}

.ficha-v2 .fv2-compra__galeria .rafas-gallery-thumbs button {
  width: var(--fv2-mini-w);
  height: var(--fv2-mini-h);
  /* `flex` explícito y no solo `width`: en móvil la tira vieja de theme.css declara
     `flex: 0 0 64px` para su carrusel deslizable, y en un ítem flexible la base de
     flex manda sobre el ancho. Medido antes de esto: el tile salía a 64x64 en 390 px
     —cuadrado otra vez, justo lo que P4 viene a quitar— mientras el alto sí obedecía.
     Con `0 0 auto` la caja vuelve a ser la que dice `width`. */
  flex: 0 0 auto;
  padding: 0;
  /* El blanco NO es el gris que P6 retira: es la base opaca sobre la que se apoya
     un PNG con transparencia, y sin él se vería la página a través de la pieza.
     Se queda. */
  background: #fff;
  border: 0;
  opacity: 1;
  cursor: pointer;
  box-sizing: border-box;
  /* P6: el velo se pinta como `::after` DENTRO del botón, así que el botón tiene
     que ser su contenedor posicionado. (Aquí vivía `isolation: isolate`, que
     existía solo para acorralar el `mix-blend-mode` de E4; sin mezcla no acorrala
     nada y se va con ella.) */
  position: relative;
}

/* -------------------------------------------------------------------------
   P6 (FIX-FICHA-15) · LA ACTIVA SE MARCA CON UN VELO ENCIMA, COMO MALOSSI
   -------------------------------------------------------------------------
   Lo que había —tile gris DEBAJO y la foto fundida con él por `multiply`— era una
   forma indirecta de conseguir esto mismo: teñir el tile activo sin que se viera
   un recuadro. Funcionaba mientras el lienzo de la foto fuera blanco, porque
   multiplicar por blanco devuelve el fondo; en cuanto el lienzo era oscuro, la
   mezcla ensuciaba la foto y no había forma de arreglarlo desde la caja (era el
   defecto P5, capturado y sin tocar).

   El velo no tiene ese problema porque no depende del contenido: una capa
   rgba(0,0,0,0.1) por ENCIMA oscurece lo que haya debajo, sea lienzo blanco o
   pieza negra, y siempre en la misma proporción. Con foto clara lo que se
   agrisa es toda la caja; con foto oscura, el margen de lienzo alrededor de la
   pieza — que es exactamente lo que hace Malossi.

   La INACTIVA no lleva nada: el velo existe siempre en el DOM pero a opacidad 0,
   que es lo que permite la transición sin pintar una caja de más.

   ORDEN DE LAS DOS REGLAS DE ABAJO, Y NO ES CASUAL: `:hover::after` y
   `[aria-current="true"]::after` pesan lo mismo —(0,4,2) las dos, porque una
   pseudo-clase y un atributo cuentan igual—, así que quien gana es la última
   escrita. La activa va DESPUÉS para que pasar el ratón por encima de la que ya
   está puesta no le baje el velo. */
.ficha-v2 .fv2-compra__galeria .rafas-gallery-thumbs button::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.1);
  opacity: 0;
  transition: opacity 0.12s ease;
  /* El velo tapa el botón entero: sin esto se comería el clic. */
  pointer-events: none;
}

/* Medio velo al pasar el ratón: el tile gris de antes daba esa señal y quitarla
   sin más sería perder afordancia, no simplificar. */
.ficha-v2 .fv2-compra__galeria .rafas-gallery-thumbs button:hover::after {
  opacity: 0.45;
}

.ficha-v2 .fv2-compra__galeria .rafas-gallery-thumbs button[aria-current="true"]::after,
.ficha-v2 .fv2-compra__galeria .rafas-gallery-thumbs button.is-activa::after {
  opacity: 1;
}

/* -------------------------------------------------------------------------
   CP-GALERIA · P1 · «+N»: LO QUE QUEDA DEBAJO DEL CORTE
   -------------------------------------------------------------------------
   Con la tira acotada al alto del marco, en el exosto Leovince se ven 5 de 11
   miniaturas y las otras 6 quedan fuera de la vista sin que nada lo diga: una tira
   recortada y muda parece una tira completa. La ÚLTIMA CASILLA VISIBLE lleva el
   aviso, que es donde la mirada ya está cuando se acaba lo que se ve.

   NO ES UN NODO NUEVO: es el MISMO `::after` que ya marca la activa, con más velo y
   con texto. Reusarlo tiene dos consecuencias buenas — el velo no se puede
   descuadrar respecto a la caja porque es el mismo pseudoelemento de siempre, y el
   estado «+N» y el estado «activa» no pueden pintarse a la vez sobre la misma
   casilla (una sola capa, un solo contenido).

   VA DESPUÉS DE LA REGLA DE LA ACTIVA A PROPÓSITO, y por el mismo motivo por el que
   la activa va después de `:hover`: las tres pesan lo mismo —(0,4,2)— así que gana
   la última escrita. Si la casilla del corte fuera además la activa, manda el «+N»:
   el aviso de que hay más fotos es información que el usuario no tiene por otra vía,
   mientras que cuál está puesta ya lo dice la foto grande.

   El `content` sale de `data-fv2-mas`, que escribe assets/js/ficha-v2.js con la
   cuenta REAL de fotos ocultas y recalcula al desplazar, al cambiar de foto y al
   cambiar el ancho. Sin script no hay atributo, no hay clase y esto no se pinta:
   la tira queda acotada y sin aviso, que es exactamente lo que había antes de P1. */
.ficha-v2 .fv2-compra__galeria .rafas-gallery-thumbs button.fv2-mini--mas::after {
  content: attr(data-fv2-mas);
  opacity: 1;
  background: rgba(0, 0, 0, 0.62);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--fv2-titulos);
  font-size: 20px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.02em;
  color: #fff;
}

/* El «+N» es un botón que hace OTRA cosa que sus vecinos —desplaza la tira en vez
   de cambiar la foto—, así que el puntero lo dice antes del clic. */
.ficha-v2 .fv2-compra__galeria .rafas-gallery-thumbs button.fv2-mini--mas {
  cursor: s-resize;
}

/* El foco por teclado SÍ se ve, y no como el fondo: quien navega con Tab tiene
   que distinguir «dónde estoy» de «cuál está puesta». */
.ficha-v2 .fv2-compra__galeria .rafas-gallery-thumbs button:focus-visible {
  outline: 2px solid var(--fv2-rojo);
  outline-offset: 2px;
}

/* N1: el mismo silenciador de fv2-componentes.css, repetido con el peso de este
   selector (0,4,1) porque si no la regla genérica (0,2,0) perdería aquí. */
.ficha-v2 .fv2-compra__galeria .rafas-gallery-thumbs button.fv2-sin-anillo:focus-visible {
  outline: none;
}

/* EL PREFIJO LARGO, OTRA VEZ, Y AHORA SÍ HACÍA FALTA. Esta regla se escribió como
   `.ficha-v2 … img`, que pesa (0,3,1), y pierde contra la hoja de WooCommerce
   —`.woocommerce div.product div.images img { width:100%; height:auto }`, (0,3,3)—.
   No se notaba porque la miniatura salía de un recorte CUADRADO: con `height:auto`
   sobre una fuente 1:1 el alto coincidía por casualidad con el de la caja. En
   cuanto P4 pasó a servir la foto sin recortar (relación 1,5), el `height:auto` de
   WooCommerce la sacó 10 px por debajo del tile. Con `div.product.ficha-v2
   div.images` sube a (0,4,3) y gana sin un solo !important. */
.woocommerce div.product.ficha-v2 div.images .rafas-gallery-thumbs img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/* AQUÍ VIVÍA LA FUSIÓN (E4, N2 de FIX-FICHA-14). RETIRADA ENTERA EN P6.
   Era una regla sola —`mix-blend-mode: multiply` sobre la imagen de la tira— con
   treinta líneas de comentario explicando cuándo se portaba bien y cuándo no. Se
   va con el gris de debajo: el velo de P6 (más arriba, junto al botón) hace lo
   mismo sin depender del color del lienzo, y de paso cierra P5 —la foto de lienzo
   oscuro que multiply ensuciaba— sin necesidad de decidir nada caso por caso.
   Antes de esto ya se habían retirado el clasificador de JS y sus dos clases
   (`fv2-mini--cubre` / `--funde`): la suite del gate sigue vigilando que nadie las
   reponga. */

/* D5 · EL AVISO DE AMPLIAR («la lupa»).
   Confirmado en malossistore: su foto principal lleva `cursor: zoom-in` y, dentro
   del propio slide, un pie de 182x26 con un icono y el aviso de ampliar. Se
   replica el patrón con NUESTRA interacción y nuestras palabras: aquí no hay zoom
   al pasar el ratón, hay visor a pantalla completa al hacer clic, así que el
   aviso dice eso y no otra cosa. Solo en escritorio —en móvil no hay ratón y el
   aviso sería ruido—, igual que ellos lo marcan como de escritorio. */
.fv2-lupa { display: none; }

@media (min-width: 1000px) {
  .fv2-lupa {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    margin: 10px 0 0;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--fv2-gris-tenue);
  }

  .ficha-v2 .fv2-lupa svg {
    width: 15px;
    height: 15px;
    display: block;
    flex: 0 0 auto;
  }

  /* La foto grande abre el visor: el puntero lo dice antes del clic. */
  .woocommerce div.product.ficha-v2 div.images .woocommerce-product-gallery__image a { cursor: zoom-in; }
}

/* Guiones de paginación en móvil (los pinta ficha-v2.js). */
.fv2-guiones {
  display: flex;
  justify-content: center;
  gap: 6px;
  padding: 12px 0 0;
}

.fv2-guiones__g {
  width: 22px;
  height: 3px;
  background: #d6d6d6;
  border: 0;
  padding: 0;
  cursor: pointer;
}

.fv2-guiones__g.is-activo { background: #111; }

@media (min-width: 1000px) {
  .fv2-guiones { display: none; }
}

/* CP-GALERIA · P2 · LA TIRA NO SE PINTA EN MÓVIL.
   Su sitio es aquí abajo y no junto al resto del bloque del carril, y el motivo es
   de orden y no de gusto: la regla base que la declara `display: flex` pesa
   exactamente lo mismo (0,3,0) y se escribe más arriba en esta hoja. Puesta allí,
   perdía por orden y la tira seguía viéndose. Escrita después, gana sin subir la
   especificidad ni recurrir a un !important.
   Con el carril, la tira era un SEGUNDO mando para lo mismo, y uno caro: 296 px de
   alto medidos a 390 de ancho en la ficha del exosto Leovince, o sea un tercio de
   la pantalla dedicado a repetir lo que el dedo ya hace sobre la foto. */
@media (max-width: 999.98px) {
  .ficha-v2 .fv2-compra__galeria .rafas-gallery-thumbs { display: none; }
}

/* --- Caja de compra ----------------------------------------------------- */

.fv2-compra__caja { padding-top: 18px; }

@media (min-width: 1000px) {
  .fv2-compra__caja { padding-top: 0; }
}

/* F2 + G4: 26 px se quedaban en nada al lado de un título de 52. Subieron a
   40/32 y ahora a 46 px en escritorio y 36 en móvil, con la MISMA separación de
   28 px. `align-items: baseline` no sirve aquí (una imagen no tiene línea base
   útil): se alinean por el centro de su caja, que con los dos logos recortados
   al ras es su mancha óptica. Sin cajas ni líneas alrededor.
   Cada vez que esta altura se mueve hay que mover con ella el `CAJA_FICHA_CSS`
   de tests/cdp/cdp-imagenes-f3.py, que mide justo esta caja para saber si el
   raster que hay dentro del SVG sigue teniendo resolución de sobra. */
.fv2-logos {
  display: flex;
  align-items: center;
  gap: 26px;               /* B1: 28 -> 26, el aire baja con el logo */
  margin: 0 0 18px;        /* B1: 20 -> 18 */
}

/* Altura FIJA, nunca el tamaño natural: los logos son SVG y a tamaño natural se
   comerían la caja. */
.ficha-v2 .fv2-logos__img {
  height: 36px;
  width: auto;
  max-width: 190px;
  object-fit: contain;
  display: block;
}

/* B1: 46 -> 42 en escritorio. El de MÓVIL sigue en 36, y esto importa más de lo
   que parece: el `CAJA_FICHA_CSS` de tests/cdp/cdp-imagenes-f3.py mide esta caja a
   390 px de ancho, o sea con los 36 — por eso ese número (88.7) no se mueve con
   este cambio. Si algún día baja el de móvil, hay que moverlo con él. */
@media (min-width: 1000px) {
  .ficha-v2 .fv2-logos__img { height: 42px; max-width: 210px; }
}

.fv2-precio-bloque {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas:
    'precio sello'
    'codigo codigo'
    'entumoto entumoto';
  align-items: baseline;
  column-gap: 14px;
  margin: 0 0 20px;
}

@media (min-width: 1000px) {
  .fv2-precio-bloque {
    grid-template-columns: 1fr;
    grid-template-areas:
      'sello'
      'precio'
      'codigo'
      'entumoto';
    row-gap: 2px;
  }
}

/* M4 (FIX-FICHA-13) · «EN TU MOTO: {nota}», BAJO LA LÍNEA DE CÓD.
   La fila existe en la rejilla pase lo que pase, pero el elemento solo lo emite la
   plantilla cuando hay nota para ESTA pareja (caja-compra.php): una fila de rejilla
   sin contenido no ocupa alto, así que en la ficha base y en las parejas sin nota la
   caja queda EXACTAMENTE como estaba.

   Deliberadamente apagada: 12.5 px, gris del sistema, sin caja, sin borde y sin una
   gota del rojo de marca. No compite con el precio ni con el botón — es una
   precisión sobre la pieza, no una llamada a la acción. La etiqueta va en 600 solo
   para separarla de la nota; ni mayúsculas ni letter-spacing, que aquí leerían como
   otro sello. */
.fv2-entumoto {
  grid-area: entumoto;
  margin: 6px 0 0;
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--fv2-gris-txt);
}

.fv2-entumoto__et {
  font-weight: 600;
  color: var(--fv2-gris-txt);
}

.fv2-sello {
  grid-area: sello;
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.2;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--fv2-verde);
  white-space: nowrap;
}

.fv2-sello__punto {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--fv2-verde-punto);
  flex: 0 0 auto;
}

.fv2-sello--agotado { color: var(--fv2-gris-micro); }

/* G2: el precio baja de 48 a 34 px en escritorio (40 -> 28 en móvil). A 48 px
   competía con el título y era lo primero que se leía de la ficha; a 34 sigue
   siendo la cifra más grande de la caja de compra sin gritar. Misma familia y
   mismo peso: Barlow Semi Condensed 700. */
.fv2-precio {
  grid-area: precio;
  margin: 0;
  font-family: var(--fv2-titulos);
  font-weight: 700;
  font-size: 28px;
  line-height: 1.05;
  color: #111;
}

/* B1: 34 -> 30 en escritorio. El de móvil (28) no se toca. */
@media (min-width: 1000px) {
  .fv2-precio { font-size: 30px; }
}

/* WooCommerce envuelve el precio en <span class="woocommerce-Price-amount">, y
   los productos en oferta añaden un <del>. Se hereda el tamaño para que la
   cifra no encoja dentro de su propio contenedor. */
.fv2-precio .woocommerce-Price-amount { font: inherit; color: inherit; }
.fv2-precio del { font-size: 0.5em; color: var(--fv2-gris-micro); font-weight: 400; }
.fv2-precio ins { text-decoration: none; }

/* -------------------------------------------------------------------------
   D2 + E1 · PRECIO CON DESCUENTO (patrón Malossi completo)
   -------------------------------------------------------------------------
   Dos piezas, no tres: la cifra que se paga a tamaño completo y en casi-negro, y
   UN resaltado amarillo que agrupa el porcentaje y el precio regular tachado.
   Esa agrupación es la enmienda de E1 sobre D2: antes el tachado iba suelto en
   gris y el porcentaje aparte en rojo, o sea tres cosas que el ojo tenía que
   juntar solo. En el resaltado se leen de un golpe «cuánto bajó» y «desde
   cuánto», que es la misma pieza de información.
   El orden visual es el de la lectura —«cuánto cuesta» antes que «cuánto
   costaba»— aunque `get_price_html()` de WooCommerce lo emita al revés; por eso
   el marcado lo arma mhw_ficha_precio_html() y no se reordena con CSS: un `<del>`
   movido de sitio con flex se lee mal en voz alta.
   El porcentaje va redondeado al entero, con el signo menos tipográfico (−) y no
   un guion. */
.fv2-precio__ahora { color: #111; }

/* Tachado SUELTO, sin resaltado: es el caso del descuento que redondea a 0 %, en
   el que mhw_ficha_precio_html() no emite etiqueta porque «−0 %» es ruido. Sin
   porcentaje no hay nada que agrupar, así que tampoco hay amarillo: el amarillo
   anuncia una rebaja con cifra. */
.fv2-precio__antes {
  margin-left: 10px;
  font-size: 0.55em;
  font-weight: 400;
  color: var(--fv2-gris-micro);
  text-decoration: line-through;
}

.fv2-precio__oferta {
  display: inline-flex;
  align-items: baseline;
  gap: 7px;
  margin-left: 10px;
  padding: 4px 8px;
  vertical-align: 0.2em;       /* el resaltado se alinea con la cifra, no con su base */
  border-radius: 2px;
  background: var(--fv2-oferta);
  color: var(--fv2-oferta-tinta);
  font-family: var(--fv2-cuerpo);
  font-size: 13px;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0.03em;
  white-space: nowrap;
}

/* Dentro del resaltado nadie trae color propio: el amarillo es el fondo de los
   dos y la tinta es la misma, así que el tachado se distingue por la raya y no
   por un gris que sobre amarillo se leería peor. */
.fv2-precio__oferta .fv2-precio__dto { font-weight: 700; }

.fv2-precio__oferta .fv2-precio__antes {
  font-size: 1em;
  font-weight: 500;
  color: inherit;
  text-decoration: line-through;
}

.fv2-codigo {
  grid-area: codigo;
  margin: 4px 0 0;
  font-family: var(--fv2-mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fv2-gris-micro);
}

/* --- Caja de garantías -------------------------------------------------- */

/* G3: LAS GARANTÍAS SE DESPEGAN DEL BOTÓN.
   Antes eran una caja con borde propio pegada al botón de compra: leídas juntas
   parecían parte de la compra y le robaban peso al botón. No hay borde exterior
   —solo los hairlines que separan las tres filas— y media aire entre el botón y
   la primera. El padding lateral se va con el borde: sin caja que rellenar, las
   filas se alinean con el resto de la columna.

   F1 · VERSIÓN COMPACTA. Vuelven a la caja de compra con los aires recortados
   frente a la original: 28 px de separación del botón pasan a 20, el relleno de
   cada fila de 14 a 9, el título de 14 a 13 y el detalle de 13 a 12. La forma
   —cuadrito rojo, título en negrita, detalle debajo, hairline entre filas— es la
   misma; lo que cambia es cuánto respiran. Medido: de 221 px a los que anota el
   informe, con los textos intactos.
   Los dos detalles cortos caben en una línea; el de «Envío VIP» son 76
   caracteres y en una columna de 395 px ocupa dos, que es aritmética y no una
   decisión: cabría en una sola recortando el texto, y los textos no se tocan. */
.fv2-garantias {
  margin-top: 20px;
  border: 0;
}

.fv2-garantias__fila {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 9px 0;
}

.fv2-garantias__fila + .fv2-garantias__fila { border-top: 1px solid var(--fv2-hairline); }

.fv2-cuadro {
  width: 8px;
  height: 8px;
  background: var(--fv2-rojo);
  flex: 0 0 auto;
  margin-top: 5px;
}

.fv2-garantias__titulo {
  margin: 0;
  font-size: 13px;
  font-weight: 700;
  line-height: 1.3;
  color: #1a1a1a;
}

.fv2-garantias__detalle {
  margin: 1px 0 0;
  font-size: 12px;
  line-height: 1.35;
  color: var(--fv2-gris-txt);
}

/* (Aquí vivió, entre E2 de FIX-FICHA-7 y F1 de FIX-FICHA-8, una fila de tres
   columnas para cuando las garantías estaban bajo la galería. Se retira con
   ellas: dentro de la caja el ancho es 395 px y tres columnas no caben.) */

/* =========================================================================
   3) BOTÓN DE COMPRA Y STEPPER — vive en assets/css/fv2-componentes.css
   =========================================================================
   Se mudó entero (con el 3b del formulario nativo de los variables) porque la
   tarjeta de recambio lo lleva dentro y esa tarjeta se pinta también en las
   modales de los listados. */

/* =========================================================================
   4) BARRA DE ANCLAS
   ========================================================================= */

/* F4: LA BARRA SE PEGA DEBAJO DE LA CABECERA, NO DETRÁS.
   El header del sitio (`header.mh-racing`) es `position: sticky` con
   `top: -122px` y 168 px de alto: al bajar deja 46 px pegados arriba. La barra
   de anclas estaba en `top: 0` con z-index 20 contra el 50 del header, así que
   se metía DEBAJO de esos 46 px y desaparecía — por eso no salía en las
   capturas. Y con `scroll-margin-top: 68px` el título de cada sección aterrizaba
   justo donde la cabecera lo tapaba.
   `--fv2-tope` lo mide assets/js/ficha-v2.js sobre el borde inferior REAL de la
   cabecera, y en móvil vale 0 porque allí el header no se pega. Los valores de
   reserva son los medidos en local por si el JS no llega.
   E5: y ya no es un número fijo. Al subir, header-racing.js le pone `.mh-reveal`
   al header, su `top` va de -122 a 0 y pasa a tapar 168 px en vez de 46; el JS
   sigue esa transición y republica la variable, así que la barra —y el lateral
   pegado, que usa la misma— bajan con ella en vez de quedarse debajo. La
   transición es la del propio `top` de la cabecera: aquí no se declara ninguna
   para que las dos cosas se muevan a la vez y no una detrás de otra. */
.fv2-anclas {
  position: sticky;
  top: var(--fv2-tope, 0px);
  z-index: 20;
  margin-top: 34px;
  background: #fff;
  border-bottom: 1px solid var(--fv2-borde);   /* una sola línea, y va DEBAJO */
}

@media (min-width: 1000px) {
  .fv2-anclas { top: var(--fv2-tope, 46px); }
}

.fv2-anclas__lista {
  display: grid;
  grid-template-columns: repeat(var(--fv2-anclas-cols, 4), 1fr);
  max-width: var(--fv2-ancho);
  margin: 0 auto;
  padding: 0 20px;
  list-style: none;
}

@media (min-width: 1000px) {
  .fv2-anclas__lista {
    display: flex;
    gap: 48px;
    justify-content: flex-start;
    /* D3: la barra ya no cruza la pantalla — vive dentro de la columna
       izquierda, que es quien pone el aire lateral. Su propio centrado a 1440 y
       sus 20 px sobran y desalinearían las etiquetas respecto de la galería. */
    max-width: none;
    margin: 0;
    padding: 0;
  }
}

.fv2-anclas__item { display: flex; }

.fv2-anclas__enlace {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 48px;
  padding: 12px 0;
  font-family: var(--fv2-titulos);
  font-weight: 600;
  font-size: 13px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-align: center;
  text-decoration: none;
  color: var(--fv2-gris-micro);
  border-bottom: 3px solid transparent;   /* pegado a la palabra en desktop */
}

@media (min-width: 1000px) {
  .fv2-anclas__enlace {
    width: auto;
    justify-content: flex-start;
    text-align: left;
    font-size: 14px;
  }
}

.fv2-anclas__enlace.is-activa {
  color: #111;
  border-bottom-color: var(--fv2-rojo);
}

/* B3 · LA QUINTA ETIQUETA. En escritorio la barra es un flex y «Se aplica a» entra
   sin tocar nada. En móvil son columnas iguales: con cinco, 390 px dan ~70 px por
   etiqueta y «Se aplica a» se parte en tres renglones. Solo cuando hay cinco o más
   —lo marca la plantilla con esta clase— se aprieta el cuerpo y desaparece el
   interletrado, que es lo que más ancho gasta en versalitas. Con cuatro o menos, la
   barra sigue exactamente como estaba. */
/* CP-FUENTES · A4 · 999 -> 999.98, para que el corte de la ficha se escriba igual en
   todas partes. Aquí el hueco de la banda fraccionaria era inofensivo (entre 999 y
   1000 la barra apretada se quedaba sin apretar, y a ese ancho ya sobra sitio), pero
   un mismo corte escrito de dos maneras acaba valiendo dos cosas distintas — y a
   veinte líneas de aquí la pareja `min-width: 1000px` es la que decide qué etiqueta
   se ve. Un solo número. */
@media (max-width: 999.98px) {
  .fv2-anclas__lista--apretada .fv2-anclas__enlace {
    font-size: 12px;
    letter-spacing: 0;
    line-height: 1.2;
    padding: 10px 2px;
  }
}

.fv2-anclas__largo { display: none; }
.fv2-anclas__corto { display: inline; }

@media (min-width: 1000px) {
  .fv2-anclas__largo { display: inline; }
  .fv2-anclas__corto { display: none; }
}

/* Al saltar a un ancla, el título de la sección tiene que quedar POR DEBAJO de
   todo lo que está pegado arriba: cabecera + barra de anclas, más un respiro. */
.fv2-seccion,
.fv2-banner {
  scroll-margin-top: calc(var(--fv2-tope, 0px) + var(--fv2-anclas-alto, 49px) + 12px);
}

@media (min-width: 1000px) {
  .fv2-seccion,
  .fv2-banner {
    scroll-margin-top: calc(var(--fv2-tope, 46px) + var(--fv2-anclas-alto, 49px) + 12px);
  }
}

/* =========================================================================
   5) CONTENIDO
   ========================================================================= */

/* La fuente del cuerpo (D4) se declara ahora en assets/css/fv2-componentes.css,
   junto a `.fv2-modal`, que la usa igual. */

.fv2-contenido {
  max-width: var(--fv2-lectura);
  margin: 0 auto;
  padding: 0 0 60px;
}

.fv2-seccion { margin-top: 34px; }

@media (min-width: 1000px) {
  .fv2-seccion { margin-top: 50px; }   /* B1: 56 -> 50, el aire del título de sección */
}

/* `.fv2-h2` y `.fv2-h3` viven en assets/css/fv2-componentes.css: los usan
   también los dos bloques de la modal. */

/* --- Prosa de la descripción ------------------------------------------- */

/* -------------------------------------------------------------------------
   E2 · NOTA DE LA VARIACIÓN ELEGIDA
   -------------------------------------------------------------------------
   Lo primero de la Descripción cuando hay una opción puesta. Es una NOTA
   DESTACADA y no un párrafo más: lo que dice depende de lo que el visitante
   acaba de elegir arriba, así que tiene que verse que no es texto de catálogo.
   El realce es el filete rojo a la izquierda —el acento de la ficha— y no un
   fondo: el fondo gris ya tiene dueño en esta página (E3, el bloque informativo
   del párrafo de modelo) y dos cajas grises seguidas dirían lo mismo de dos
   cosas distintas.
   La rellena assets/js/ficha-v2.js; nace con `hidden`. */
.fv2-notavar {
  max-width: 72ch;
  margin: 0 0 22px;
  padding: 2px 0 2px 16px;
  border-left: 3px solid var(--fv2-rojo);
}

.fv2-notavar[hidden] { display: none; }

.fv2-notavar__opcion {
  margin: 0 0 4px;
  font-family: var(--fv2-cuerpo);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fv2-gris-txt);
}

.fv2-notavar__texto {
  font-family: var(--fv2-cuerpo);
  font-size: 15px;
  line-height: 1.55;
  color: #2a2a2a;
}

/* El texto llega del editor tal cual lo dejó (viene de la variación), así que
   sus párrafos y saltos se normalizan aquí y no se confía en cómo venga. */
.fv2-notavar__texto p { margin: 0 0 8px; }
.fv2-notavar__texto p:last-child { margin-bottom: 0; }

.fv2-prosa {
  font-size: 15px;
  line-height: 1.62;
  color: #2a2a2a;
}

/* A2 · TOPE DE LECTURA. La sección crece a 1200 px, pero el PÁRRAFO no: a ese
   ancho una línea de texto de 15 px pasa de los 120 caracteres y al saltar de
   renglón el ojo se pierde. 72ch es el largo cómodo de siempre; las tablas y las
   listas sí usan el ancho entero, porque ahí la vista salta de columna y no
   recorre la línea. */
.fv2-prosa p,
.fv2-prosa ul li { max-width: 72ch; }

.fv2-prosa p { margin: 0 0 14px; }
.fv2-prosa strong { font-weight: 600; }

.fv2-prosa ul {
  list-style: none;
  margin: 0 0 16px;
  padding: 0;
}

.fv2-prosa ul li {
  position: relative;
  padding-left: 18px;
  margin-bottom: 7px;
}

/* Viñeta cuadrada roja de 7 px. */
.fv2-prosa ul li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 7px;
  height: 7px;
  background: var(--fv2-rojo);
}

.fv2-prosa table {
  width: 100%;
  border-collapse: collapse;
  margin: 0 0 16px;
}

.fv2-prosa th,
.fv2-prosa td {
  padding: 9px 0;
  text-align: left;
  border-bottom: 1px solid var(--fv2-hairline);
  font-size: 14px;
}

/* -------------------------------------------------------------------------
   E3 · EL PÁRRAFO DE MODELO, COMO BLOQUE GRIS INFORMATIVO
   -------------------------------------------------------------------------
   Leído en malossistore (`.c-shop__infobox` del cilindro y de la camiseta): caja
   de fondo gris claro, esquina de 4 px, relleno de 16 y texto de 14/21. Es su
   forma de decir «esto es información de contexto, no es el producto». El
   párrafo de modelo de los combos hace exactamente ese trabajo, así que se viste
   igual (ver los tokens --fv2-info-* arriba).
   FUERA LA BARRA ROJA. `theme.css` le pone a `.model-intro` un filete rojo de
   3 px a la izquierda, que en los archivos de modelo está bien pero aquí es el
   color de la marca puesto sobre una nota secundaria: la barra la reclamaba como
   si fuera un aviso. No se toca theme.css —esa regla la usan las páginas de
   modelo, que no son esto—: se anula acotada a la sección de la ficha.
   Y NO SE USA EL ROSADO/ÁMBAR: en Malossi ese es su color de advertencia y esto
   es información. */
.fv2-seccion .fit-context-intro,
.fv2-seccion .model-intro {
  max-width: 78ch;
  margin: 34px 0 0;
  padding: 16px;
  border-left: 0;
  border-radius: 4px;
  background: var(--fv2-info-fondo);
  color: var(--fv2-info-tinta);
}

.fv2-seccion .fit-context-intro p,
.fv2-seccion .model-intro p {
  margin: 0;
  font-family: var(--fv2-cuerpo);
  font-size: 14px;
  line-height: 1.5;
  color: inherit;
}

/* El enlace hereda el gris del bloque y se distingue por el subrayado: dentro de
   una caja gris, el rojo de marca vuelve a gritar. */
.fv2-seccion .fit-context-intro a,
.fv2-seccion .model-intro a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* --- Tabla de especificaciones ------------------------------------------ */

.fv2-specs {
  width: 100%;
  border-collapse: collapse;
}

.fv2-specs th,
.fv2-specs td {
  padding: 11px 0;
  border-bottom: 1px solid var(--fv2-hairline);
  font-size: 14px;
  line-height: 1.4;
  vertical-align: top;
}

.fv2-specs th {
  text-align: left;
  font-weight: 400;
  color: var(--fv2-gris-txt);
}

.fv2-specs td {
  text-align: right;
  font-weight: 600;
  color: #1a1a1a;
}

/* Ítem que no tiene forma de «Etiqueta: valor» y ocupa la fila entera. Se pinta
   como un dato más y no como una etiqueta huérfana con la celda derecha vacía. */
.fv2-specs__suelta {
  font-weight: 600;
  color: #1a1a1a;
}

.fv2-specs + .fv2-h3 { margin-top: 32px; }

/* --- Compatibles: en assets/css/fv2-componentes.css (la modal pinta la misma
       tabla) --------------------------------------------------------------- */

/* --- Recambios: en assets/css/fv2-componentes.css (la misma tarjeta se pinta
       dentro de las modales) ----------------------------------------------- */

/* --- Manuales ----------------------------------------------------------- */

.fv2-manuales {
  list-style: none;
  margin: 0;
  padding: 0;
}

.fv2-manuales__fila {
  display: grid;
  grid-template-columns: 8px minmax(0, 1fr) auto;
  grid-template-areas:
    'cuadro nombre accion'
    '.      formato accion';
  align-items: center;
  column-gap: 12px;
  padding: 14px 0;
  border-bottom: 1px solid var(--fv2-hairline);
}

.fv2-manuales__fila .fv2-cuadro { grid-area: cuadro; margin: 0; }

.fv2-manuales__nombre {
  grid-area: nombre;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.35;
  color: #1a1a1a;
}

.fv2-manuales__formato {
  grid-area: formato;
  font-size: 12px;
  color: var(--fv2-gris-micro);
}

.fv2-manuales__accion {
  grid-area: accion;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--fv2-rojo);
  white-space: nowrap;
}

.fv2-manuales__accion:hover { text-decoration: underline; }

/* =========================================================================
   6) BANNER DE RECAMBIOS
   ========================================================================= */

/* CORRECCIÓN C1: título y línea de apoyo APILADOS, con separación limpia. En el
   mock salen superpuestos porque comparten la misma caja; aquí son un flex en
   columna y no pueden pisarse. */
.fv2-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-top: 28px;
  padding: 18px 20px;
  background: var(--fv2-fondo-banner);
  border-left: 4px solid var(--fv2-rojo);
  text-decoration: none;
}

.fv2-banner__texto {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.fv2-banner__titulo {
  font-family: var(--fv2-titulos);
  font-weight: 600;
  font-size: 19px;
  line-height: 1.15;
  text-transform: uppercase;
  color: #111;
}

@media (min-width: 1000px) {
  .fv2-banner__titulo { font-size: 22px; }
}

.fv2-banner__apoyo {
  font-size: 13px;
  line-height: 1.4;
  color: var(--fv2-gris-txt);
}

.fv2-banner__flecha {
  font-size: 22px;
  line-height: 1;
  color: var(--fv2-rojo);
  flex: 0 0 auto;
}

.fv2-banner:hover .fv2-banner__flecha { transform: translateX(3px); }
.fv2-banner__flecha { transition: transform 0.15s ease; }

/* =========================================================================
   7) MODALES — viven en assets/css/fv2-componentes.css
   ========================================================================= */

/* =========================================================================
   8) LO QUE SE RETIRA DE LA FICHA VIEJA
   ========================================================================= */

/* Restos que otros módulos pueden seguir imprimiendo en la ficha y que la
   sección 11 del brief manda quitar. Se ocultan aquí además de desengancharlos
   en PHP: si alguno vuelve por otra vía, no reaparece en pantalla.
   El bloque gris «Ver carrito» posterior a añadir lo emite el JS viejo de
   WooCommerce (.added_to_cart / .wc-forward). */
.ficha-v2 .fit-context,
.ficha-v2 .added_to_cart,
.ficha-v2 .woocommerce-message .wc-forward,
.ficha-v2 .product_meta,
.ficha-v2 .woocommerce-tabs,
.ficha-v2 .custom-tabs-nav,
.ficha-v2 .related.products,
.ficha-v2 .up-sells {
  display: none !important;
}
