/* =============================================================
   1. Tokens
   ============================================================= */
:root {
  --bg:        #0a0a0a;
  --bg-2:      #111111;
  --surface:   #1a1a1a;
  --surface-2: #222222;
  /* Un escalón por encima de --surface-2, para resaltar una fila dentro de
     un panel que ya usa --surface-2 (menús desplegables del filtrado). Se
     usaba desde 2026-08-22 en `.facet-sort-option:hover` pero NUNCA llegó a
     declararse: `var(--surface-3)` sin definir invalida la declaración, así
     que el menú "Ordenar por" llevaba desde entonces sin hover visible.
     Detectado al reutilizarlo para los desplegables de dimensión. */
  --surface-3: #2c2c2c;
  --ink:       #f2f2f2;
  --ink-soft:  #d6d6d6;
  --ink-mute:  #8f8f8f;
  --line:      rgba(242,242,242,0.10);
  --line-2:    rgba(242,242,242,0.18);
  /* Texto deshabilitado — token propio, no --line-2 (que es de BORDE). */
  --ink-disabled: rgba(242,242,242,0.34);

  /* ===== TINTA SOBRE SUPERFICIE SIEMPRE OSCURA ("on-dark") =============
     La chapa metalizada, el pie de marca y el panel del histórico son
     oscuros en LOS DOS temas, así que su tinta no puede seguir a --ink:
     en tema claro --ink es casi negro y desaparecería. Hasta ahora cada
     componente se lo resolvía a mano — 30 declaraciones con 12 alfas
     distintos, de .45 a .85, puestos a ojo uno por uno.
     Los alfas son altísimos (1,0 y .90, no los .82/.55 de antes) porque la
     chapa del tema claro es un gris MEDIO: su techo absoluto es 4,51, el
     del blanco pleno. Por eso --ink-od-soft es blanco puro igual que
     --ink-od: sobre esta chapa no hay hueco para un tercer nivel, y la
     jerarquía la hacen el tamaño y el peso. El mute se queda en 4,00,
     por debajo del 4,5 de AA — es el precio de este gris, y está asumido
     (hoy en producción ese mismo texto da 2,51).
     Estos valores NO cambian entre temas: ese es justo el punto. */
  --ink-od:      #f2f2f2;
  --ink-od-soft: #f2f2f2;
  --ink-od-mute: rgba(242,242,242,.90);
  --line-od:     rgba(242,242,242,.16);
  /* Línea fuerte sobre oscuro: ejes de gráfico, cursores, la raya del PVP.
     No es un borde de caja, es una línea que se tiene que LEER. */
  --line-od-2:   rgba(242,242,242,.38);
  /* Velos: fondo levemente más claro sobre una pieza oscura (una fila que
     resalta, un hover, una chapita dentro de otra). */
  --veil-od:        rgba(242,242,242,.06);
  --veil-od-strong: rgba(242,242,242,.10);
  /* Cristal: el chip translúcido con desenfoque que va sobre las fotos. */
  --glass-od:    rgba(10,10,10,.72);
  /* Superficie oscura FIJA, igual en los dos temas: pie de marca, panel del
     histórico. No es --surface, que sí sigue al tema. */
  --surface-od:  #2c2c31;
  /* Filo de acento a media opacidad, y la tinta oscura que va ENCIMA del
     lima (el contorno del punto del gráfico). */
  --line-accent:   rgba(198,255,61,.3);
  --ink-on-accent: rgba(10,10,10,.5);

  --accent:      #C6FF3D;
  --accent-ink:  #0a0a0a;
  --accent-soft: rgba(198,255,61,0.14);
  --accent-fg:   #C6FF3D;   /* accent usado como color de texto/icono: legible en oscuro */
  --accent-2-od: #FF5A1F;  /* el naranja legible sobre oscuro, se pida desde donde se pida */
  --accent-2:    var(--accent-2-od);
  --accent-2-soft: rgba(255,90,31,0.16);
  /* Tinta sobre --accent-2 a plena saturación. No puede seguir al tema
     igual que --accent-2: sobre el naranja neón del oscuro se lee el
     casi-negro; sobre el naranja quemado del claro hace falta blanco.
     OJO: no vale para .precio-row-badge, que va sobre --sello (siempre
     el neón, porque su panel es chapa oscura en los dos temas). */
  --accent-2-ink: #0a0a0a;

  /* Series del comparador (hasta 4 palas). Los dos primeros son los
     acentos de la marca; los dos de relleno van pegados a ellos en la
     rueda —teal al lado del lima, ámbar al lado del naranja— para que las
     cuatro se lean como un arco continuo de la casa. Sustituyen a un azul
     #4EA8FF y un magenta #FF4ECD que estaban sueltos en main.js.
     Contraste sobre --surface: 14,7 / 5,6 / 7,0 / 8,9. */
  /* Rojo de promoción. NO es un color de marca — es una señal, y por eso
     vive solo en un sitio: el botón de cupón que de verdad BATE el mejor
     precio verificado (`.precio-row-promo--mejorable`). El botón de cupón
     que no mejora nada se queda en gris neutro, así el rojo significa algo
     en vez de decorar. Mismo valor en los dos temas: la chapita se apoya
     siempre sobre chapa o sobre --surface, oscuras en ambos.

     Dice "Cupón", no "−10 %": la fila ya enseña su propio "−30 %" (el
     descuento sobre PVP) y una segunda cifra al lado del precio hacía
     competir dos números que significan cosas distintas. El porcentaje
     sigue estando dentro del panel, al abrirlo.
     Se probó dejarlo SOLO con el icono y no funciona — se pierde. Lo que
     hace que el "−30 %" de al lado se lea no es su color sino que lleva
     texto en negrita: un icono de 12 px en contorno no tiene masa
     suficiente para sostener una chapita.
     Tinta BLANCA, obligatoriamente: sobre este tinte los salmones claros
     no pasan de 3,9 y el texto pequeño pide 4,5. En blanco da 5,7 en la
     peor superficie (chapa gris clara) y 9,6 en la fila oscura. */
  --promo-solido: #B4231A;  /* el rojo OPACO, para chapitas que no pueden depender del fondo */
  --promo:        rgba(212,42,32,.55);  /* chapita sobre superficie oscura */
  --promo-hover:  rgba(212,42,32,.75);
  --promo-ink:    #ffffff;
  /* El mismo rojo, como TEXTO sobre el panel de cupones. Este par SÍ sigue
     al tema (a diferencia de --promo): el panel es oscuro en tema oscuro y
     blanco en tema claro, así que la tinta tiene que invertirse.
     Aquí, sobre el panel oscuro: 6,4-7,4. */
  --promo-od:     #FF9E92;  /* el rojo legible sobre oscuro, se pida desde donde se pida */
  --promo-fg:     var(--promo-od);
  --promo-soft:   rgba(212,42,32,.22);

  /* Descuento YA aplicado sobre PVP (el "−30 %" de la fila). SIN chapita:
     "100 € · −30 % · hoy" es una frase, y meter una píldora en medio la
     parte. Además el relleno se reserva aquí para lo que lo merece — el
     sello "Mejor precio verificado" y el botón de cupón, que es la única
     acción de la fila. Con el "−30 %" también relleno, la fila llegaba a
     tener tres chapitas cálidas compitiendo y el dato derivado pesaba
     tanto como la acción.
     Blanco CÁLIDO, no un salmón: sobre la chapa gris clara los salmones no
     pasan de 4,05 y hace falta 4,5. #FFF3EC da 4,64 ahí (mejor incluso que
     el blanco puro, 4,51) y conserva algo de temperatura, que es lo que lo
     separa del texto normal ahora que no lleva chapita. */
  --descuento-ink: #FFF3EC;

  /* Los dos radares de la ficha de pala. En tema oscuro valen el lima y el
     naranja de siempre; en claro el bloque entero se va a chapa y ahí se
     redefinen (ver .producto-puntuaciones). Tienen token propio y no tiran
     de --accent-fg/--accent-2 porque su fondo NO es el de la página. */
  /* Color de los mini-títulos (.kicker). Token propio y no --accent-fg
     porque en tema claro NO va en el verde: ver el bloque del tema claro. */
  --kicker-fg: var(--accent-fg);

  --radar-1: var(--accent-fg);
  --radar-2: var(--accent-2);

  /* Las cuatro series del comparador salen TODAS de la paleta de la casa
     (07-09-26, Sergio: "colores que no son de marca"). Antes la 3 era un teal
     #2FB89A y la 4 un ámbar #E3B341, dos colores que no existen en ningún otro
     sitio del sistema. Ahora: el lima, el naranja, el rojo en su versión para
     oscuro y el gris metalizado, que es la cuarta seña de identidad del sitio.

     Historia de las dos que no son de marca, porque explica el criterio: el
     rojo salmón y la plata se lavaban contra la pista de la barra; el ámbar que
     los sustituyó competía con el lima (dos cálidos claros); el blanco tenía
     contraste de sobra pero, dicho por Sergio, "no lo usaría como color".

     Ahora son un MAGENTA y un AZUL ELÉCTRICO. El azul es la única dirección
     fría disponible; empezó siendo un acero apagado (#6FA8FF) y Sergio pidió
     "uno más vivo que contraste un poco más". Se midieron seis y este gana en
     las dos cosas a la vez: sube el contraste (6,19 → 6,36 en claro) y sobre
     todo el par flojo del conjunto, magenta/azul, que pasa de 74 a 92. El
     magenta es
     exactamente el #FF4ECD que esta misma auditoría retiró del radar en su
     primer commit — y no es una vuelta atrás: entonces era un literal suelto
     en main.js que nadie había decidido, ahora es un token elegido midiendo.
     La diferencia entre un accidente y una decisión es que la segunda se puede
     justificar con números.

     Se aclararon el 07-09-26 y se REVIRTIERON el mismo día, a peticion de
     Sergio: "quiero que dejes los colores que había antes, lima, naranja, el
     rosa ese y el azul eléctrico". Decisión suya, tomada con los números
     delante, y aquí quedan escritos para que nadie los redescubra dentro de
     seis meses creyendo que es un descuido:

     Sobre la chapa CLARA, que es un gris medio, las series dan 4,27 / 1,62 /
     1,74 / 2,16, y un gráfico pide 3. O sea que en tema claro tres de las
     cuatro barras, las tres líneas de nombre, tres puntos de leyenda y tres
     trazos de radar van por debajo del umbral. En tema OSCURO no hay ningún
     problema: 13,46 / 5,10 / 5,47 / 6,80.

     La alternativa medida era aclararlas —naranja #FFCA8A, rosa #FCBDFF y un
     turquesa en lugar del azul, porque en el extremo claro no queda azul que
     se distinga del lima— y llegaban justo a 3,3. Se probó y no convenció.

     Lo que NO cambia con la reversión es la separación entre series, que sigue
     siendo la buena: 110 lima/naranja, 166 lima/rosa, 133 lima/azul, 94
     naranja/rosa, 132 naranja/azul y 92 rosa/azul. Mirar solo el mínimo fue en
     su día el error que dejó pasar un ámbar a 57 del lima; por eso se listan
     los seis pares. Estas cuatro son, con diferencia, las más separadas entre
     sí de todo lo que se ha probado — el precio es el contraste en claro.

     Y la nota ganadora se queda en BLANCO en cualquier caso ("las notas de
     momento todo en blanco"): es texto pequeño, pide 4,5, y sobre esta chapa
     el techo absoluto es 5,05 con blanco puro y 4,16 con negro puro. El único
     color saturado que pasaría es el amarillo (#FFFF33, 4,71, por ser el tono
     de mayor luminancia que existe), y está a 24 de ΔE del lima. */
  /* El rebaje en versión OPACA. El velo `rgba(0,0,0,.5)` vale para un bloque
     quieto, pero no para uno pegajoso: al pasar por encima de las filas se ven
     a través. Estos son ese mismo velo ya compuesto sobre su fondo. */
  --rebaje-solido: #0d0d0d;
  --serie-1: #C6FF3D;   /* lima de marca, sin tocar */
  --serie-2: #FF5A1F;   /* naranja */
  --serie-3: #FF4ECD;   /* rosa */
  --serie-4: #00B4FF;   /* azul eléctrico */

  --bg-rgb: 10,10,10;       /* para fondos "cristal" translúcidos (nav, menú móvil, badge del hero) */

  --display: "Archivo", sans-serif;
  --sans:    "Inter", sans-serif;
  --mono:    "JetBrains Mono", monospace;

  --radius:   16px;
  --radius-sm: 10px;
  --gutter:   1.25rem;
  --container: 1280px;

  --ease-out:  cubic-bezier(0.16, 1, 0.3, 1);
  --ease-soft: cubic-bezier(0.25, 0.46, 0.45, 0.94);
  /* Se retiran --ease-in y --ease-bounce: cero usos desde que se
     declararon. Una curva que nadie usa no es una opción, es ruido. */

  /* ===================================================================
     ESCALAS (06-09-26, fase 2 de la auditoría de estilos)
     Los valores NO se han sacado de una progresión bonita: salen de los
     picos reales del fichero, para que migrar cada declaración suelta
     sea mover como mucho un 12 % y no rediseñar nada.
     Esto es ADITIVO — se declaran, todavía no se ha migrado nada.
     =================================================================== */

  /* --- Tipografía. 8 pasos para las 37 medidas sueltas que hay hoy.
     Los títulos siguen con sus clamp(), que ya estaban bien. --- */
  --fs-micro:   .66rem;   /* eyebrows mono, contadores, badges */
  --fs-label:   .72rem;   /* etiquetas de dato, títulos de sección pequeños */
  --fs-caption: .78rem;   /* kickers, notas al pie */
  --fs-meta:    .85rem;   /* botones sm, metadatos, texto de apoyo */
  --fs-body-s:  .92rem;   /* texto secundario de tarjeta */
  --fs-body:    1rem;     /* texto corrido */
  --fs-lead:    1.15rem;  /* entradilla */
  --fs-title:   1.4rem;   /* título de tarjeta grande */

  /* --- Elevación. Cuatro alturas y dos capas que se SUMAN, no
     sustituyen: el acento nunca es una altura. --- */
  --sh-1: 0 1px 3px rgba(0,0,0,.28);          /* chip, píldora */
  --sh-2: 0 6px 16px -8px rgba(0,0,0,.45);    /* control elevado, hover de botón */
  --sh-3: 0 24px 48px -20px rgba(0,0,0,.55);  /* tarjeta en hover */
  --sh-4: 0 40px 80px -30px rgba(0,0,0,.6);   /* panel flotante, modal */
  --glow: 0 0 40px -12px var(--accent-soft);  /* destacado, se suma a una altura */
  --ring: 0 0 0 1px var(--accent);            /* aro de selección, se suma */
  --ring-focus: 0 0 0 3px var(--accent-soft); /* halo de foco, más ancho y difuso */

  /* --- Filos de material. NO son elevación: no levantan la pieza del
     fondo, describen su canto. Van aparte porque mezclarlos con la
     escalera de arriba es lo que llevó a tener 29 recetas. --- */
  --edge-top:    inset 0 1px 0 rgba(242,242,242,.08);   /* canto superior iluminado */
  --edge-relief: inset 0 1px 0 rgba(255,255,255,.1), inset 0 -1px 0 rgba(0,0,0,.3);
  --edge-well:   inset 0 1px 2px rgba(0,0,0,.09);       /* hueco fresado */

  /* --- Espacio. Nueve escalones. Empezaron siendo siete (.35 a 3rem) y al
     migrar de verdad se vio que faltaba por los dos extremos: sin un
     escalón abajo, los .2/.25rem se iban a .35 (+75 %), y sin uno arriba
     los 2,4-2,6rem caían a 2 (8 px de un tirón). Con estos nueve la deriva
     media es de 1,08 px y solo tres componentes pasan de 4.
     Los espacios de sección (4rem y más) se quedan fuera a propósito: los
     lleva .section con su padding-block de 6rem. --- */
  --sp-1: .25rem;
  --sp-2: .35rem;
  --sp-3: .55rem;
  --sp-4: .8rem;
  --sp-5: 1rem;
  --sp-6: 1.4rem;
  --sp-7: 1.75rem;
  --sp-8: 2.2rem;
  --sp-9: 2.8rem;

  /* --- Movimiento. Los escalones se reparten por lo que RECORRE cada
     cosa, no por importancia: lo que no viaja no tiene por qué tardar lo
     mismo que mover una tarjeta. Es lo que separa un movimiento
     consistente de uno que se siente físico. --- */
  --t-micro: .12s;  /* NO viaja: color, borde, opacidad, relleno */
  --t-fast:  .15s;  /* viaja poco: un icono, una chapita */
  --t-base:  .3s;   /* viaja: tarjetas, botones, sombras que crecen */
  --t-slow:  .6s;   /* viaja mucho o entra en escena: reveals, barras */
  --t-flip:  .7s;   /* el volteo 3D del comparador, que recorre media
                       vuelta: al mismo tiempo que una tarjeta se siente
                       atropellado. */
}

/* Tema claro: misma identidad (negro/antracita + lima/naranja neón), invertido
   a base crema/blanco. El lima puro no se lee bien como texto sobre claro,
   así que --accent-fg pasa a una variante más oscura solo para eso. */
:root[data-theme="light"] {
  --bg:        #f5f4f0;
  --bg-2:      #ebe9e2;
  --surface:   #ffffff;
  --surface-2: #f1efe7;
  /* Aquí el escalón va hacia ABAJO (más oscuro que --surface-2), al revés
     que en tema oscuro: en los dos casos el resultado es "una fila que
     destaca sobre el panel", que es lo que significa el token. */
  --surface-3: #e6e3d8;
  --ink:       #16160f;
  --ink-soft:  #3c3c34;
  /* #65645a, no #75746a (06-09-26, auditoría de estilos): el gris anterior
     daba 4,28 sobre --bg y 3,87 sobre --bg-2, por debajo del 4,5 de AA — y
     este token se usa por todo el sitio, así que era la causa de la
     práctica totalidad de los fallos de contraste del tema claro (el
     oscuro no tenía ninguno). El nuevo da 5,41 / 4,91 / 5,96. */
  --ink-mute:  #65645a;
  --line:      rgba(20,20,15,0.10);
  --line-2:    rgba(20,20,15,0.20);
  /* Texto deshabilitado: .pager-link.is-disabled tiraba de --line-2, un
     token de BORDE — 1,54 de contraste, invisible en claro. */
  --ink-disabled: rgba(20,20,15,0.42);

  --accent-fg: #146B45;
  /* #B04520, no #D9633B (misma auditoría). El terracota anterior daba 3,62
     sobre blanco. Se probó primero #B84A24 (5,19 sobre blanco) pero no
     bastaba donde de verdad se usa: los "−10 %" del panel de códigos
     promocionales van sobre --accent-2-soft, un tinte naranja que aclara
     el fondo a rgb(255,229,219), y ahí #B84A24 se quedaba en 4,31.
     #B04520 da 4,69 sobre ese chip y 5,65 sobre blanco. */
  --accent-2:  #B04520;
  --accent-2-ink: #ffffff;

  /* Las 4 series, bajadas de valor para la base crema. La serie 1 es
     #146B45 y no el lima puro porque sobre blanco el lima da 1,18 — que es
     exactamente por lo que existe --accent-fg. No es una excepción del
     comparador: es lo que vale "el lima" en tema claro en todo el sitio.
     Contraste sobre --surface: 6,5 / 5,7 / 5,2 / 5,4. */
  /* Sobre el panel BLANCO del tema claro se invierte: rojo oscuro sobre
     tinte muy leve. 6,75. */
  /* Los mini-títulos NO van en verde en tema claro (06-09-26, petición de
     Sergio). Va en el gris apagado — que es, por cierto, el que ya tenía
     "Colaboraciones" sin que nadie lo decidiera: ese kicker es un <p> y
     `.brands-cta p { color: var(--ink-mute) }` le ganaba por especificidad.
     Ahora es la norma y no un accidente.
     Se apunta a --ink-mute y no a un literal para que, dentro de un bloque
     que redefina sus tokens (una chapa, por ejemplo), el kicker siga al
     apagado de ESE bloque y no al de la página. */
  --kicker-fg: var(--ink-mute);

  --promo-fg:   #9E2016;
  --promo-soft: rgba(212,42,32,.10);

  /* Las series NO se redefinen en claro: el dashboard del comparador va sobre
     fondo oscuro en los dos temas (ver más abajo), así que valen las mismas.
     Aquí había cuatro versiones oscurecidas —#146B45, #B04520, #0F7A6B y
     #8A6410— que existían solo porque las tarjetas eran blancas, y la primera
     era justo el verde que ya se quitó del radar y de las barras de la ficha. */
  --bg-rgb: 245,244,240;
}

/* =============================================================
   2. Reset & base
   ============================================================= */
*, *::before, *::after { box-sizing: border-box; margin: 0; }
html {
  -webkit-text-size-adjust: 100%;
  tab-size: 2;
  scroll-behavior: smooth;
}

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: clip;
  overscroll-behavior-y: none;
}

img, svg, video { display: block; max-width: 100%; }
button { font: inherit; color: inherit; cursor: pointer; border: 0; background: none; }
a { color: inherit; text-decoration: none; }
p { text-wrap: pretty; }
h1, h2, h3, h4 { text-wrap: balance; line-height: 1.05; letter-spacing: -0.02em; font-family: var(--display); font-weight: 800; }
ul, ol { list-style: none; padding: 0; }
::selection { background: var(--accent); color: var(--accent-ink); }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

/* =============================================================
   3. Utilities
   ============================================================= */
.container { max-width: var(--container); margin-inline: auto; padding-inline: var(--gutter); }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.skip-link {
  position: fixed; top: -100px; left: 1rem;
  padding: var(--sp-4) var(--sp-5); background: var(--accent); color: var(--accent-ink);
  z-index: 9999; border-radius: 8px; font-weight: 700; font-size: var(--fs-body-s);
  transition: top var(--t-base) var(--ease-out);
}
.skip-link:focus { top: 1rem; }

.kicker {
  font-family: var(--mono);
  font-size: var(--fs-caption);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--kicker-fg);
  margin-bottom: .9rem;
}

/* Reveal (scroll-triggered fade/rise) */
.reveal { opacity: 0; transform: translateY(28px); transition: opacity var(--t-slow) var(--ease-out), transform var(--t-slow) var(--ease-out); }
.reveal.is-visible { opacity: 1; transform: none; }
.reveal[data-split] { opacity: 1; transform: none; } /* defensive: split-text handles its own children */

/* =============================================================
   4. Typography
   ============================================================= */
/* UN SOLO tamaño de titular para todo el sitio (07-09-26, decisión de Sergio:
   "habría que igualar toda la familia con palas, paleteros y guías"). El h1
   por defecto es el de TRABAJO, 41,6 px de tope — el mismo que ya usaban la
   ficha de producto y las guías.

   Antes el global era el de PORTADA y quien quisiera uno legible tenía que
   acordarse de bajarlo: ficha, contacto y guías lo hacían; comparador,
   ranking, categoría, marca y novedades no, y se comían 155 px de alto de
   titular en la primera pantalla. Invertido el defecto, el que se sale de la
   norma es quien tiene que declararlo, y una página nueva que se olvide
   hereda el sensato.

   Con esto sobran las excepciones de `.product-hero-copy h1` y
   `.legal-content h1`, que pedían exactamente este valor: se quedan solo con
   sus márgenes. */
h1 { font-size: clamp(1.9rem, 4vw, 2.6rem); }
h2 { font-size: clamp(1.9rem, 4.2vw, 3rem); }
h3 { font-size: clamp(1.15rem, 2vw, 1.4rem); }

.section-sub, .hero-sub { color: var(--ink-mute); font-size: var(--fs-body); max-width: 52ch; }

/* =============================================================
   5. Components
   ============================================================= */

/* --- Buttons --- */
.btn {
  display: inline-flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-7); border-radius: 999px;
  font-weight: 700; font-size: var(--fs-body-s);
  transition: transform var(--t-base) var(--ease-out), background var(--t-base) var(--ease-out), box-shadow var(--t-base) var(--ease-out), color var(--t-base) var(--ease-out);
  white-space: nowrap;
}
.btn-lg { padding: var(--sp-5) var(--sp-7); font-size: var(--fs-body); }
.btn-sm { padding: var(--sp-3) var(--sp-5); font-size: var(--fs-meta); }
.btn-block { width: 100%; justify-content: center; }

.btn-accent { background: var(--accent); color: var(--accent-ink); }
.btn-accent:hover { transform: translateY(-2px); box-shadow: var(--glow), var(--ring); }

.btn-ghost { background: transparent; color: var(--ink); border: 1px solid var(--line-2); }
.btn-ghost:hover { border-color: var(--accent); color: var(--accent-fg); transform: translateY(-2px); }

.btn-icon { padding: var(--sp-4); }
.btn-icon.is-copied { border-color: var(--accent); color: var(--accent-fg); }

/* --- Placeholder image blocks --- */
.ph {
  position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--sp-3);
  border-radius: var(--radius);
  overflow: hidden;
  background:
    repeating-linear-gradient(135deg, var(--surface) 0 18px, var(--surface-2) 18px 36px);
  border: 1px solid var(--line);
  color: var(--ink-mute);
  text-align: center;
}
.ph img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Foto real de producto — recorte sin fondo (rembg, 2026-08-10), la pala
   flota directamente sobre la tarjeta en vez de ir dentro de una caja
   blanca. Primera versión (mismo día) probó un "passe-partout" claro
   detrás de la foto con fondo de estudio todavía sin recortar — se
   descartó en cuanto hubo recorte real: con transparencia real ya no
   hace falta ningún marco, solo una sombra para que no quede "pegada". */
.ph:has(img) {
  background: none;
  border: none;
  padding: var(--sp-5);
}
.ph:has(img) img {
  object-fit: contain;
  filter: drop-shadow(0 16px 22px rgba(0, 0, 0, .45));
}
/* Excepción: .ph--card/.ph--hero son las fotos de categoría de la home
   (hero + 5 tarjetas de "Cada categoría, a fondo") — fotografías
   rectangulares a sangre, no recortes de producto con fondo transparente,
   así que no les toca el tratamiento "flotante" de arriba (sombra dura +
   contain). Con él puesto, cada foto se veía como una tarjeta suelta con
   sombra propia, desconectada del bloque de texto de debajo — apenas se
   notaba en tema oscuro (la sombra se funde con el fondo) pero saltaba
   mucho en tema claro (foto de Sergio, 2026-08-16). Vuelven a cover +
   sin sombra, como el resto de fotos "a sangre" del sitio. */
.ph--card:has(img), .ph--hero:has(img) {
  background: none;
  border: 1px solid var(--line);
  padding: 0;
}
.ph--card:has(img) img, .ph--hero:has(img) img {
  object-fit: cover;
  filter: none;
}
/* Logo de marca en el placeholder "Foto pendiente" (shared.js::phSquare,
   2026-08-14) — mismo interruptor claro/oscuro que .nav-logo-icon, pero con
   su propia clase: .nav-logo-icon lleva un override específico del propio
   nav (styles.css, sección 6) que no nos conviene aquí. */
.ph-logo { display: block; opacity: .35; }
.ph-logo--light { display: none; }
.ph-tag {
  font-family: var(--mono); font-size: var(--fs-label); letter-spacing: .02em;
  padding: var(--sp-1) var(--sp-4); border-radius: 999px;
  background: rgba(0,0,0,.4); border: 1px solid var(--line-2);
}
.ph--hero { aspect-ratio: 4 / 3; }
.ph--card { aspect-ratio: 16 / 10; }
.ph--square { aspect-ratio: 1 / 1; }

/* --- Links with arrow --- */
.link-arrow {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  font-weight: 700; color: var(--ink);
  transition: gap var(--t-base) var(--ease-out), color var(--t-base) var(--ease-out);
}
.link-arrow svg { transition: transform var(--t-base) var(--ease-out); color: var(--accent-fg); }
.link-arrow:hover { gap: var(--sp-3); color: var(--accent-fg); }
.link-arrow:hover svg { transform: translateX(3px); }

/* --- Rating --- */
.rating { display: inline-flex; align-items: center; gap: var(--sp-1); color: var(--accent-fg); font-weight: 700; font-size: var(--fs-body-s); }
.rating svg { color: var(--accent-fg); }
.rating small { color: var(--ink-mute); font-weight: 500; margin-left: .15rem; }

/* --- Nota general (media de las 7 puntuaciones) --- */
/* Mismo lenguaje visual que .rating, icono distinto (icon-target) para no
   confundirla con la valoración editorial de estrellas. */
.nota-general { display: inline-flex; align-items: center; gap: var(--sp-1); color: var(--accent-fg); font-weight: 700; font-size: var(--fs-body-s); }
.nota-general svg { color: var(--accent-fg); }
.nota-general small { color: var(--ink-mute); font-weight: 500; margin-left: .15rem; text-transform: uppercase; font-size: var(--fs-label); letter-spacing: .02em; }
/* Mismo criterio que `.nota-ring`/`.score-card-nota`: en claro, lima real
   como relleno en vez de como texto (--accent-fg ahí es verde oscuro por
   contraste). */
:root[data-theme="light"] .nota-general {
  color: var(--accent-ink); background: var(--accent); padding: .15rem var(--sp-3); border-radius: 999px;
}
:root[data-theme="light"] .nota-general svg { color: var(--accent-ink); }
:root[data-theme="light"] .nota-general small { color: var(--accent-ink); opacity: .7; }

/* --- Badge --- */
.badge {
  position: absolute; top: 1rem; left: 1rem; z-index: 2;
  font-family: var(--mono); font-size: var(--fs-label); letter-spacing: .03em; text-transform: uppercase;
  padding: var(--sp-2) var(--sp-4); border-radius: 999px; font-weight: 700;
}
.badge--accent2 { background: var(--accent-2); color: var(--accent-2-ink); }

/* --- Mini list (pros/cons) --- */
.mini-list { display: flex; flex-direction: column; gap: var(--sp-3); margin: 1rem 0; }
.mini-list li { display: flex; align-items: flex-start; gap: var(--sp-3); font-size: var(--fs-body-s); color: var(--ink-soft); }
.mini-list li svg { color: var(--accent-fg); flex-shrink: 0; margin-top: .15rem; }
.mini-list-neg svg { color: var(--ink-mute) !important; }

.fine-note { color: var(--ink-mute); font-size: var(--fs-meta); margin-top: 1.5rem; }

/* =============================================================
   6. Sections — Nav
   ============================================================= */
.nav {
  position: fixed; inset-inline: 0; top: 0; z-index: 100;
  background: transparent;
  transition: background var(--t-micro) var(--ease-out), border-color var(--t-micro) var(--ease-out);
  border-bottom: 1px solid transparent;
}
.nav.is-scrolled {
  background: rgba(var(--bg-rgb),0.78);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border-bottom-color: var(--line);
}
.nav-inner { display: flex; align-items: center; justify-content: space-between; height: 74px; }
.nav-logo { display: inline-flex; align-items: center; gap: var(--sp-3); font-family: var(--display); font-weight: 800; font-size: var(--fs-title); }
.nav-logo-icon { color: var(--accent-fg); display: block; }
.nav-logo-icon--light { display: none; }
.nav-logo em { font-style: normal; color: var(--accent-fg); }
/* La chapa de marca SOLO aparece en tema claro: en oscuro el header se
   queda transparente (más premium, sin necesitarla — el lima ya se lee
   bien ahí). En claro, logo + "Sapiens" en su combinación fija (trazo
   blanco + lima puro) sobre el mismo gris metalizado que el resto de
   chapas del sitio (26-08-26, --metal-row en vez del gris plano fijo). */
[data-theme="light"] .nav .nav-logo {
  background: var(--metal-row);
  padding: var(--sp-2) var(--sp-4);
  border-radius: 999px;
  color: var(--ink-od);
}
[data-theme="light"] .nav .nav-logo-icon--dark { display: block !important; }
[data-theme="light"] .nav .nav-logo-icon--light { display: none !important; }
[data-theme="light"] .nav .nav-logo em {
  color: var(--accent);
}
.nav-links { display: none; align-items: center; gap: var(--sp-7); font-size: var(--fs-body-s); font-weight: 600; color: var(--ink-soft); }
.nav-links a { position: relative; padding-block: var(--sp-1); }
.nav-links a::after {
  content: ""; position: absolute; left: 0; bottom: 0; height: 1px; width: 0;
  background: var(--accent); transition: width var(--t-base) var(--ease-out);
}
.nav-links a:hover { color: var(--ink); }
.nav-links a:hover::after { width: 100%; }
.nav-actions { display: flex; align-items: center; gap: var(--sp-4); }
.nav-actions > .btn { display: none; }
.nav-toggle { display: inline-flex; padding: var(--sp-2); }

.theme-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  position: relative;
  width: 2.35rem; height: 2.35rem;
  border-radius: 50%;
  border: 1px solid var(--line-2);
  background: var(--surface);
  color: var(--ink-mute);
  flex-shrink: 0;
  transition: border-color var(--t-micro) var(--ease-out), color var(--t-micro) var(--ease-out);
}
.theme-toggle:hover { color: var(--accent-fg); border-color: var(--accent); }
.theme-toggle-icon { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
.theme-toggle-icon--moon { display: none; }

.mobile-menu {
  display: none; flex-direction: column; gap: var(--sp-5);
  padding: 1.5rem var(--gutter) 2rem;
  background: rgba(var(--bg-rgb),0.97);
  border-bottom: 1px solid var(--line);
  font-weight: 600;
}
.mobile-menu.is-open { display: flex; }

@media (min-width: 960px) {
  .nav-links { display: flex; }
  .nav-actions > .btn { display: inline-flex; }
  .nav-toggle { display: none; }
  .mobile-menu { display: none !important; }
}

/* El menú completo no cabía entre 960 y 1199 px, y no es culpa de
   "Novedades": MEDIDO el 2026-09-05, con seis enlaces la barra ya pedía
   1012 px a partir de los 960 en que aparece, o sea que llevaba
   desbordando desde antes en portátiles de 1024. Con el séptimo pedía
   1123. Aquí se arreglan las dos cosas a la vez, quitando lo que sobra en
   vez de esconder el menú detrás del hamburguesa en un portátil normal:

     · el aire entre enlaces baja de 1,9rem a 1rem (ahorra 86 px), y
     · el buscador se queda en icono, sin la etiqueta ni la tecla "/"
       (ahorra 88 px) — el mismo tratamiento que ya tenía por debajo de
       720 px, así que no estrena un aspecto nuevo.

   Con eso la barra pide 949 px y entra a 960 con margen. De 1200 en
   adelante se recupera el diseño de siempre, entero. */
@media (min-width: 960px) and (max-width: 1199px) {
  .nav-links { gap: var(--sp-5); }
  .nav-search-trigger-label, .nav-search-trigger-kbd { display: none; }
  .nav-search-trigger { width: 2.35rem; padding: 0; justify-content: center; }
}

/* =============================================================
   5.1 Metabuscador de cabecera
   (2026-08-15, pedido explícito de Sergio: "en la cabecera... no tenemos
   un metabuscador de cualquier producto de la web. debemos implementarlo
   [...] Quiero que el navegador sea premium en cuanto a estética y
   funcionalidad." — HTML en templates/layout.js::searchOverlay(), lógica
   en main.js::initHeaderSearch(), índice en /search-index.json.)
   ============================================================= */

/* Disparador: pastilla con icono + etiqueta + atajo "/", visible siempre
   (a diferencia de .nav-links, que se pliega al menú móvil) — buscar es
   una acción de primer nivel, no algo que esconder detrás del hamburguesa. */
.nav-search-trigger {
  display: inline-flex; align-items: center; gap: var(--sp-3);
  height: 2.35rem; padding: 0 var(--sp-4) 0 var(--sp-4);
  border-radius: 999px; border: 1px solid var(--line-2);
  background: var(--surface); color: var(--ink-mute);
  font-family: var(--sans); font-size: var(--fs-meta); font-weight: 600;
  cursor: pointer; flex-shrink: 0;
  transition: border-color var(--t-micro) var(--ease-out), color var(--t-micro) var(--ease-out), background-color var(--t-micro) var(--ease-out);
}
.nav-search-trigger:hover { border-color: var(--accent); color: var(--ink); }
.nav-search-trigger-label { white-space: nowrap; }
.nav-search-trigger-kbd {
  font-family: var(--mono); font-size: var(--fs-micro); font-weight: 600; line-height: 1;
  color: var(--ink-mute); background: var(--surface-2); border: 1px solid var(--line-2);
  border-radius: .35rem; padding: var(--sp-1) var(--sp-2);
}
@media (max-width: 719px) {
  /* Icono solo por debajo de 720px — mismo umbral que aparece más abajo
     para el resto de ajustes de nav en móvil; con la etiqueta + kbd cabía
     justo pero apretaba el logo contra el toggle del menú. */
  .nav-search-trigger-label, .nav-search-trigger-kbd { display: none; }
  .nav-search-trigger { width: 2.35rem; padding: 0; justify-content: center; }
}

/* Overlay: hermano de <header class="nav">, no hijo — position:fixed de
   pantalla completa no necesita el nav como contenedor, y así el scroll o
   la altura del propio nav no le afectan. z-index 300: por encima del nav
   (100) pero por debajo del banner de cookies (9999) a propósito — si
   alguna vez coinciden los dos abiertos, el aviso legal gana. */
/* Bloquea el scroll de fondo mientras el buscador está abierto (el menú
   móvil no lo hace, pero aquí sí importa: el propio overlay ya tiene su
   scroll interno para el listado de resultados, y sin esto el gesto de
   scroll se "cuela" a la página de detrás en móvil). */
html.search-locked, html.search-locked body { overflow: hidden; }

.search-overlay {
  position: fixed; inset: 0; z-index: 300;
  display: flex; justify-content: center;
  padding: 12vh var(--sp-6) var(--sp-8);
  overflow-y: auto;
}
.search-overlay[hidden] { display: none; }
.search-backdrop {
  position: fixed; inset: 0;
  background: rgba(var(--bg-rgb), .7);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
}
.search-panel {
  position: relative; z-index: 1;
  width: 100%; max-width: 38rem; max-height: 75vh;
  display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--line-2); border-radius: var(--radius);
  box-shadow: var(--sh-4);
  overflow: hidden;
  animation: search-panel-in .22s var(--ease-out);
}
@keyframes search-panel-in {
  from { opacity: 0; transform: translateY(-.6rem) scale(.98); }
  to { opacity: 1; transform: none; }
}

.search-panel-field {
  display: flex; align-items: center; gap: var(--sp-4);
  padding: var(--sp-5) var(--sp-6); border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}
.search-panel-icon { color: var(--ink-mute); flex-shrink: 0; }
.search-panel:focus-within .search-panel-icon { color: var(--accent-fg); }
.search-panel-input {
  flex: 1; min-width: 0; border: none; background: transparent; outline: none;
  color: var(--ink); font-family: var(--sans); font-size: var(--fs-body);
}
.search-panel-input::placeholder { color: var(--ink-mute); }
/* Cross nativo del <input type="search"> fuera — usamos el nuestro, que
   además cierra el overlay entero, no solo vacía el campo. */
.search-panel-input::-webkit-search-cancel-button { display: none; }
.search-panel-close {
  flex-shrink: 0; width: 1.8rem; height: 1.8rem; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line-2); background: var(--surface-2); color: var(--ink-mute);
  cursor: pointer; transition: color var(--t-micro) var(--ease-out), border-color var(--t-micro) var(--ease-out);
}
.search-panel-close:hover { color: var(--ink); border-color: var(--accent); }

.search-panel-body { overflow-y: auto; flex: 1 1 auto; }
.search-panel-status {
  margin: 0; padding: var(--sp-7) var(--sp-6); text-align: center;
  color: var(--ink-mute); font-size: var(--fs-meta); line-height: 1.6;
}

.search-panel-results { list-style: none; margin: 0; padding: var(--sp-3); }
.search-panel-results[hidden] { display: none; }
/* Mismo indent que el texto de .search-result-link (.5rem del <ul> +
   .7rem del propio link) para que la etiqueta quede alineada con los
   nombres de debajo, no con el borde del panel. */
.search-result-group-label {
  margin: .7rem 0 .1rem; padding: 0 var(--sp-5);
  font-family: var(--mono); font-size: var(--fs-label); text-transform: uppercase;
  letter-spacing: .05em; color: var(--ink-mute);
}
.search-result-group-label[hidden] { display: none; }
.search-result-link {
  display: flex; align-items: center; gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-4); border-radius: var(--radius-sm);
  transition: background-color var(--t-micro) var(--ease-out);
}
.search-result-link:hover,
.search-result.is-active .search-result-link {
  background: var(--surface-2);
  box-shadow: inset 3px 0 0 var(--accent);
}
.search-result-thumb {
  width: 3.6rem; height: 3.6rem; flex-shrink: 0; border-radius: var(--radius-sm);
  overflow: hidden; background: var(--surface-2); border: 1px solid var(--line);
  display: flex; align-items: center; justify-content: center;
}
.search-result-thumb img { width: 100%; height: 100%; object-fit: contain; }
.search-result-thumb svg { color: var(--ink-mute); }
.search-result-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: .15rem; }
.search-result-name {
  font-size: var(--fs-body-s); font-weight: 700; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.search-result-name mark { background: none; color: var(--accent-fg); font-weight: 800; }
.search-result-meta {
  font-family: var(--mono); font-size: var(--fs-label); color: var(--ink-mute);
  display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
}
/* Mismo color que STATUS_LABEL/.badge--accent2 en productPage.js — el
   banner "Próximamente" de la propia ficha, para que el badge del
   buscador y el de la página de destino se lean como el mismo estado. */
.search-result-badge {
  font-family: var(--mono); font-size: var(--fs-micro); font-weight: 700; text-transform: uppercase;
  letter-spacing: .03em; color: var(--accent-2-ink);
  background: var(--accent-2); border-radius: .3rem; padding: .1rem var(--sp-2);
}
.search-result-aside {
  flex-shrink: 0; text-align: right; display: flex; flex-direction: column; gap: var(--sp-1); align-items: flex-end;
}
.search-result-nota {
  font-family: var(--mono); font-size: var(--fs-caption); font-weight: 800; color: var(--accent-ink);
  background: var(--accent); border-radius: 999px; padding: .1rem var(--sp-2); line-height: 1.4;
}
.search-result-price { font-family: var(--mono); font-size: var(--fs-label); color: var(--ink-soft); white-space: nowrap; }

.search-panel-foot {
  flex-shrink: 0; display: flex; gap: var(--sp-5); padding: var(--sp-4) var(--sp-6);
  border-top: 1px solid var(--line); background: var(--surface-2);
  font-size: var(--fs-label); color: var(--ink-mute);
}
.search-panel-foot kbd {
  font-family: var(--mono); font-size: var(--fs-label); color: var(--ink-soft);
  background: var(--surface); border: 1px solid var(--line-2); border-radius: .3rem;
  padding: .05rem var(--sp-2); margin-right: .2rem;
}
@media (max-width: 560px) {
  .search-panel-foot { display: none; }
  .search-overlay { padding-top: 6vh; }
}

/* =============================================================
   6.1 Hero
   ============================================================= */
.hero { position: relative; padding-block: 9.5rem 5rem; overflow: clip; }
.hero-glow {
  position: absolute; inset: -20% -10% auto -10%; height: 120%;
  background: radial-gradient(circle 520px at var(--mx, 30%) var(--my, 20%), var(--accent-soft) 0%, transparent 65%);
  filter: blur(10px);
  pointer-events: none; z-index: 0;
  transition: background var(--t-micro) linear;
}
.hero-grid { position: relative; z-index: 1; display: grid; gap: var(--sp-9); }
/* La portada SÍ es display: es lo único del sitio que hace de titular de
   revista, así que conserva el tamaño que tenía el h1 global antes de bajarlo.
   Se declara aquí para que el cambio sea explícito y no un efecto colateral. */
.hero-title { margin-block: var(--sp-1) var(--sp-5); font-size: clamp(2.4rem, 6.4vw, 4.6rem); }
/* font-size propio (no el 1.05rem compartido con .section-sub): Sergio pidió
   subirlo (2026-08-14) solo aquí, donde convive con el H1 grande del hero —
   .section-sub se queda igual en el resto del sitio, no es lo que pidió. */
.hero-sub { margin-bottom: 1.9rem; font-size: var(--fs-lead); max-width: 46ch; }
.hero-actions { display: flex; flex-wrap: wrap; gap: var(--sp-5); margin-bottom: 2.6rem; }

.hero-stats { display: flex; flex-wrap: wrap; gap: var(--sp-8); }
/* En móvil solo caben 2 de las 3 cifras por fila (las etiquetas son largas),
   así que la tercera baja a una fila propia — con el gap normal (2.2rem)
   se salía por debajo del pliegue en pantallas de ~812px de alto. Menos
   separación solo entre filas (row-gap), la horizontal entre las 2 primeras
   cifras no cambia. */
@media (max-width: 480px) {
  .hero-stats { row-gap: var(--sp-5); }
}
.stat-num { font-family: var(--mono); font-size: 1.9rem; font-weight: 700; color: var(--ink); }
.stat dd { color: var(--ink-mute); font-size: var(--fs-meta); margin-top: .15rem; }

.hero-visual { position: relative; }
.hero-badge {
  position: absolute; left: 1.2rem; bottom: 1.2rem; right: 1.2rem;
  display: flex; align-items: center; gap: var(--sp-4);
  background: rgba(var(--bg-rgb),0.72);
  border: 1px solid var(--line-2);
  backdrop-filter: blur(10px);
  border-radius: var(--radius-sm);
  padding: var(--sp-4) var(--sp-5);
  font-size: var(--fs-meta); color: var(--ink-soft);
}
.hero-badge svg { color: var(--accent-fg); flex-shrink: 0; }
.hero-badge small { color: var(--ink-mute); font-weight: 400; }

@media (min-width: 960px) {
  .hero-grid { grid-template-columns: 1.05fr .95fr; align-items: center; gap: var(--sp-8); }
}

/* =============================================================
   6.2 Generic section scaffolding
   ============================================================= */
.section { padding-block: 6rem; position: relative; }
/* La sección que ABRE la página no necesita las 6rem de arriba. `.section`
   las reparte porque su trabajo es separar dos bloques de contenido; la
   primera no separa nada —es lo primero que hay— y encima viene precedida por
   las migas, que ya traen su propio margen. Medido en el comparador: 154 px
   entre el final de las migas y el título, de los cuales 96 eran esto.

   Ya se había arreglado a mano en `.product-hero` (07-09-26, para que el
   seguimiento de precio entrara en la primera pantalla). Esto lo generaliza a
   las demás familias que abren igual —comparador, ranking, categoría, marca—,
   que daban exactamente los mismos 154 px. */
.breadcrumbs + .section { padding-top: 2rem; }
.section--alt { background: var(--bg-2); }
.section-head { max-width: 60ch; margin-bottom: 3rem; }
.section-head h2 { margin-bottom: .7rem; }
.section-cta {
  text-align: center; margin-top: 2.5rem;
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-4);
}
/* Enlace secundario junto al botón principal (2026-08-09) — sin flecha,
   sin ser otro `.btn`: un solo botón de acento por bloque, este solo
   queda como alternativa discreta debajo. */
.section-cta-secondary {
  color: var(--ink-mute); font-weight: 600; font-size: var(--fs-body-s);
  text-decoration: underline; text-underline-offset: .2rem; text-decoration-color: var(--line-2);
  transition: color var(--t-micro) var(--ease-out), text-decoration-color var(--t-micro) var(--ease-out);
}
.section-cta-secondary:hover { color: var(--accent-fg); text-decoration-color: var(--accent-fg); }

/* Adelanto de "Compara y decide" (2026-08-14, ver historial completo en
   home.js::comparaShowcase()) — 3 pasos del comparador real, elegir →
   puntuaciones → recomendación. El botón real ("Abrir comparador
   interactivo") va debajo, en `.section-cta` — este bloque no es clicable,
   solo ilustra.
   v5 (2026-08-15, Sergio: "dale una vuelta [a la estética], no estoy
   conforme"): hasta aquí usaba el mismo "surface + borde tenue" que
   cualquier tarjeta de contenido del sitio — se sentía genérico. Ahora
   reutiliza el tratamiento que el sitio ya reserva para anunciar algo
   especial, el mismo que `.sapi-promo` (fondo en degradado, borde de
   acento a toda opacidad en vez del --line al 10%, resplandor difuminado
   detrás vía `.compara-explainer-orb`) — y suma numeración de paso para
   que se lea como secuencia, no como 3 iconos sueltos. align-items:
   flex-start (no center) para que los 3 círculos queden siempre a la
   misma altura pase lo que pase con el número de líneas de cada
   etiqueta — con "center" cada `.compara-step` se centra como bloque
   entero y el paso con la etiqueta más corta se descuadra. max-width en
   vez de estirarse a todo el ancho del container: con contenido más
   estrecho que el container, un borde tan tenue se perdía y no se leía
   como una pieza centrada — con un ancho propio, la tarjeta abraza su
   contenido. */
/* max-width:60rem, no 52rem (2026-08-15, Sergio: "haz maz ancho el marco,
   para que quepa bien, que tenemos hueco de sobra"): al igualar la
   longitud de las 3 etiquetas, 52rem se quedaba justo y el paso 3 caía a
   su propia línea sin la flecha en anchos intermedios (~1000-1200px). En
   vez de apretar las columnas, se ensancha el marco — sigue muy por debajo
   del container completo (1216px), con margen de sobra a los lados en
   cualquier pantalla de escritorio normal. */
.compara-explainer {
  position: relative; overflow: clip;
  display: flex; align-items: flex-start; justify-content: center; gap: var(--sp-7);
  background: linear-gradient(135deg, var(--surface) 0%, var(--surface-2) 100%);
  border: 1px solid var(--accent); border-radius: var(--radius);
  padding: var(--sp-8) var(--sp-8); margin: 0 auto 2.5rem; max-width: 60rem;
  box-shadow: var(--glow);
  flex-wrap: wrap;
}
.compara-explainer-orb {
  position: absolute; top: 50%; left: 50%; width: 22rem; height: 22rem;
  background: radial-gradient(circle, var(--accent-soft) 0%, transparent 70%);
  transform: translate(-50%, -50%); pointer-events: none; z-index: 0;
}
/* Gris metalizado — SOLO en tema claro (26-08-26, Sergio: "la tarjeta del
   comparador la pondría con el gris metalizado también unicamente en el
   tema claro"), mismo `--metal-row` que el resto de chapas (ver esa
   variable en la sección de Rankings). Borde/orb se quedan en lima
   (theme-invariant, ya funcionaban bien sobre cualquier fondo). El texto
   y los iconos de dentro usan `--ink`/`--accent-fg`/`--line-2`, pensados
   para fondo CLARO en este tema — se sobrescriben aquí, en el propio
   contenedor, para que los `var(--accent-fg)`/`var(--line-2)` de los SVG
   inline (fill/stroke en el propio markup, home.js) hereden el valor
   correcto sin tocar cada icono uno a uno. */
:root[data-theme="light"] .compara-explainer {
  background: var(--metal-row);
  --accent-fg: #C6FF3D;
  --line-2: rgba(242,242,242,.3);
  --ink-mute: rgba(242,242,242,.5);
}
:root[data-theme="light"] .compara-step-label { color: var(--ink-od); }
.compara-step { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); max-width: 13rem; text-align: center; }
.compara-step-icon {
  flex-shrink: 0; display: grid; place-items: center; margin-bottom: .3rem;
  width: 4rem; height: 4rem; border-radius: 999px; background: var(--accent-soft);
  box-shadow: var(--glow);
}
.compara-step-num { font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: .06em; text-transform: uppercase; color: var(--accent-fg); }
.compara-step-label { font-family: var(--display); font-weight: 800; font-size: var(--fs-body-s); color: var(--ink); }
/* margin-top: centra la flecha contra el círculo del icono, no contra el
   step entero (el contenedor es align-items:flex-start). */
.compara-explainer-arrow { flex-shrink: 0; z-index: 1; margin-top: 1.3rem; opacity: .6; }
/* 900px, no 560px (2026-08-15): con las 3 columnas a max-width:13rem, la
   fila completa necesita ~880px libres para no envolver — por debajo de
   eso "flex-wrap: wrap" decidía por su cuenta cuántos steps cabían en cada
   línea, y en el rango medio (~600-880px) el resultado era 2 steps en la
   primera línea + el tercero solo en la segunda, sin su flecha visible:
   roto a medio camino entre el layout de escritorio y el de móvil. Forzar
   flex-basis:100% en AMBOS (step y flecha) por debajo de ese umbral da un
   estado binario limpio: fila completa de 3 o columna apilada de 3, nunca
   un 2+1 a medias. */
@media (max-width: 900px) {
  .compara-step { flex-basis: 100%; }
  .compara-explainer-arrow { transform: rotate(90deg); flex-basis: 100%; margin: .2rem 0; }
}

/* --- Bento categories --- */
.bento { display: grid; gap: var(--sp-6); grid-template-columns: 1fr; }
.bento-card {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--sp-6);
  display: flex; flex-direction: column; gap: var(--sp-5);
  transform-style: preserve-3d;
  will-change: transform;
}
/* La rejilla ya estira las 4 tarjetas de Equipamiento a la misma altura
   (comportamiento por defecto de CSS Grid), pero el botón "Explorar X"
   quedaba a una altura distinta en cada una según cuántas líneas ocupara
   la descripción (Pelotas/Accesorios con 2 líneas vs Zapatillas/Paleteros
   con 3) — captura real de Sergio, 2026-08-19. `.bento-body` pasa a
   columna flex que rellena el alto ya estirado de la tarjeta, y el botón
   se ancla siempre al fondo con margin-top:auto — así queda alineado sin
   depender de que el texto ocupe el mismo número de líneas en las 4.
   El resto de tarjetas que reusan `.bento-body` (Palas, Camisetas...) no
   cambian: al llenar toda su tarjeta con texto no dejan hueco que rellenar. */
.bento-body { display: flex; flex-direction: column; flex: 1; }
.bento-body h3 { margin-bottom: .5rem; }
.bento-body p { color: var(--ink-mute); font-size: var(--fs-body-s); margin-bottom: .9rem; }
.bento-body .link-arrow { margin-top: auto; }

/* Enlace estirado (mismo patrón que .compare-card-link en compareCard.js):
   toda la tarjeta de Palas es clicable, no solo el texto/flecha — Sergio
   pidió (2026-08-14) que la foto también funcionara como CTA. Mismo href
   que .link-arrow, así que da igual cuál intercepte el clic. */
.bento-card-link { position: absolute; inset: 0; z-index: 1; border-radius: inherit; }

/* --t-base y no --t-slow: venía de .45s, justo a mitad de camino entre los
   dos escalones, y el reparto automático lo mandó al lento por un redondeo
   de coma flotante. Un hover a .6s se siente pastoso: la respuesta a un
   gesto del usuario va en el escalón medio, el lento es para lo ambiental
   (reveals, barras que se llenan). */
.card-tilt { transition: transform var(--t-base) var(--ease-out), box-shadow var(--t-base) var(--ease-out), border-color var(--t-base) var(--ease-out); }
.card-tilt:hover, .card-tilt.is-tilting {
  border-color: var(--line-2);
  box-shadow: var(--sh-3), var(--glow);
}

@media (min-width: 720px) { .bento { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1280px) { .bento { grid-template-columns: repeat(3, 1fr); } }

/* Palas es la categoría estrella (ver docs/plan-reestructuracion-nav.md):
   tarjeta única a todo el ancho, con foto más panorámica que el resto. */
.bento--hero { grid-template-columns: 1fr; }
/* 21/8 en todos los anchos — es la ratio exacta de assets/img/palas.webp
   (1200x457, recorte local de hero_ia_generado). Antes se forzaba 21/6 a
   partir de 1280px para verse más panorámica, pero eso recortaba la propia
   imagen por arriba/abajo con object-fit: cover más allá de lo ya recortado
   — Sergio lo notó ("se corta un poco de arriba a abajo"). Con la misma
   ratio en ambos casos, la imagen se ve completa siempre. */
.bento-card--hero .ph--card { aspect-ratio: 21 / 8; }

.category-star { background: var(--accent); color: var(--accent-ink); }

/* Rótulo que separa Palas del resto de categorías ("Equipamiento" en el
   nav) — mismo idioma tipográfico que .kicker, en horizontal con una
   línea que ocupa el resto del ancho. También es el ancla real de
   #categorias (movida aquí desde la <section> completa: el enlace
   "Equipamiento" del nav llevaba a la tarjeta de Palas, arriba del todo
   de esa sección, en vez de a este grupo — Sergio, 2026-08-19).
   scroll-margin-top compensa el nav fijo (74px de alto, ver .nav) para
   que el rótulo no quede tapado al saltar aquí. */
.category-group-label {
  display: flex; align-items: center; gap: var(--sp-5);
  margin: 2.2rem 0 1.5rem;
  font-family: var(--mono); font-size: var(--fs-caption); letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-mute);
  scroll-margin-top: 90px;
}
.category-group-label::after { content: ""; flex: 1; height: 1px; background: var(--line); }

/* Las 4 categorías "de siempre" de Equipamiento van en su propia rejilla —
   4 columnas iguales en desktop, para que no sobre una tarjeta suelta en
   la última fila. Camisetas (añadida 2026-08-16) NO entra aquí: en vez de
   forzar un 5º tile igual a los demás en una rejilla pensada para 4, tiene
   su propio tratamiento "spotlight" debajo (ver `.spotlight-card`) — se
   apoya en tener un diferencial real (comparador de precio entre tiendas)
   que las demás no tienen, así que se lo gana visualmente en vez de
   camuflarse como una tarjeta más. */
.bento--equip { grid-template-columns: 1fr; }
@media (min-width: 720px) { .bento--equip { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1280px) { .bento--equip { grid-template-columns: repeat(4, 1fr); } }

/* --- Spotlight de Camisetas ---
   Tarjeta horizontal (foto | contenido) en vez del formato vertical de
   `.bento-card` — mismo lenguaje visual (superficie/borde/radio, badge,
   link-arrow) pero una silueta distinta a propósito, para que se lea como
   un tercer nivel jerárquico: Palas (hero a sangre) > rejilla de
   Equipamiento (4 tiles iguales) > Camisetas (spotlight, un diferencial
   que se explica, no solo se enlaza). La foto reutiliza `.ph.ph--card`
   tal cual (mismo marco con borde propio que las otras 4 categorías,
   2026-08-16 — antes iba a sangre dentro de la tarjeta y desentonaba) en
   vez de un tratamiento a sangre propio: dentro de un `.bento-card` con
   su padding normal, no de un contenedor con overflow:hidden aparte. */
.spotlight-card {
  position: relative;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--sp-6);
  margin-top: 2rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--sp-6);
}
.spotlight-card .ph--card { aspect-ratio: 16 / 10; }
/* Imagen apaisada dentro del spotlight (una imagen oficial de fabricante
   suele ser una tira ancha): la caja de 16/10 con `cover` le corta los
   lados, que es donde está el producto. Mismo criterio que
   `.guide-cover--natural`. */
.spotlight-card .ph--card.ph--ancha { aspect-ratio: auto; align-self: center; height: auto; }
.spotlight-card .ph--card.ph--ancha img { height: auto; object-fit: contain; }
.spotlight-body { display: flex; flex-direction: column; align-items: flex-start; justify-content: center; gap: var(--sp-1); }
.spotlight-body h3 { margin-bottom: .1rem; }
/* Subtítulo ("Viste como los jugadores del top") entre el título y la
   descripción. Dos intentos previos descartados por Sergio (2026-08-19):
   negrita de la sans del cuerpo (se veía como párrafo normal más gordo,
   sin personalidad) y cursiva de Inter (tampoco convenció). Versión
   final: reutiliza literalmente `.kicker` — el mismo idioma tipográfico
   que "COMPARA Y DECIDE" más abajo (mono, versalitas, tracking, color de
   acento) — para que las dos "etiquetas cortas" del sitio lean como el
   mismo patrón, aunque aquí vaya debajo del título en vez de encima.
   `h3 + .kicker` separa el bloque del título (el margen normal de
   `.kicker` es solo el de DEBAJO, pensado para ir delante de un
   heading). */
/* `.spotlight-body p { color: var(--ink-mute) }` de abajo es más
   específica que `.kicker` sola (clase+elemento vence a clase sola) y le
   robaba el color lima, dejándolo gris como un párrafo más — se repone
   aquí explícitamente. Sergio lo pidió aparte ("resaltaría en verde
   lima"), pero es la intención original de reusar `.kicker`, no un
   cambio de diseño nuevo. */
/* --kicker-fg, no --accent-fg (06-09-26): esta regla existe solo para
   reponer el color que `.spotlight-body p` le roba por especificidad, así
   que tiene que reponer EL MISMO que el resto de kickers, no el acento. */
.spotlight-body .kicker { color: var(--kicker-fg); font-size: var(--fs-caption); margin-bottom: .9rem; }
.spotlight-body h3 + .kicker { margin-top: .5rem; }
.spotlight-body p { color: var(--ink-mute); font-size: var(--fs-body-s); margin-bottom: 1.1rem; max-width: 42ch; }
.spotlight-vendors {
  font-family: var(--mono); font-size: var(--fs-label); color: var(--ink-mute); line-height: 1.6;
  margin-bottom: 1.1rem; padding-top: .9rem; border-top: 1px solid var(--line);
  width: 100%;
}
.spotlight-vendors strong { color: var(--ink-soft); font-weight: 600; }
.spotlight-card-link { position: absolute; inset: 0; z-index: 1; border-radius: inherit; }
/* Desktop: la foto deja de tener un aspect-ratio fijo y pasa a estirarse
   (align-items: stretch) a la altura completa de la fila, que marca la
   columna de texto — si no, con center + aspect-ratio fijo, la foto queda
   corta dentro de una fila alta y se ve flotando con un hueco vacío
   arriba/abajo (detectado por Sergio, 2026-08-16). En móvil (columna
   única) sí conserva el 16:10 de siempre, ahí no hay fila que igualar. */
@media (min-width: 720px) {
  .spotlight-card { grid-template-columns: minmax(240px, 380px) 1fr; align-items: stretch; gap: var(--sp-8); padding: var(--sp-6); }
  .spotlight-card .ph--card { aspect-ratio: auto; }
  /* Excepción para una imagen apaisada (una tira oficial de fabricante):
     se queda a UNA columna, con la imagen a todo el ancho y el texto
     debajo. En dos columnas la imagen se topa en 380 px y una tira de
     4,33:1 acaba midiendo 87 px de alto — un sello, no una portada.
     Se puede permitir porque las novedades son pocas y la sección enseña
     una cada vez, así que la tarjeta no compite por sitio con nada. */
  .spotlight-card--ancha { grid-template-columns: 1fr; gap: var(--sp-6); }
}

/* --- Comparativa ---
   Flexbox en vez de grid a propósito: con grid, una última fila incompleta
   (p. ej. 7 tarjetas en una rejilla de 4 columnas) deja un hueco vacío sin
   ocupar a la derecha; con flex-wrap + justify-content:center esa fila
   sobrante queda centrada, sin huecos raros, tenga las tarjetas que tenga. */
.compare-grid { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-6); }
.compare-card {
  position: relative;
  display: flex; flex-direction: column;
  flex: 1 1 100%; max-width: 100%;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--sp-6);
  transition: transform var(--t-base) var(--ease-out), box-shadow var(--t-base) var(--ease-out), border-color var(--t-base) var(--ease-out);
}
.compare-card:hover, .compare-card.is-tilting {
  border-color: var(--line-2);
  box-shadow: var(--sh-3), var(--glow);
}
.compare-tag { font-family: var(--mono); font-size: var(--fs-label); color: var(--accent-fg); text-transform: uppercase; letter-spacing: .04em; margin: 1rem 0 .35rem; }
.compare-card h3 { margin-bottom: .5rem; font-size: var(--fs-lead); }
.price { font-family: var(--mono); font-weight: 700; color: var(--ink); margin-top: .3rem; }

/* Tarjeta estilo "carta" (2026-08-08, inspirado en el repaso de un
   competidor): nota destacada + favorito sobre la foto, precio con
   descuento cuando lo haya de verdad, fila compacta de 5 ejes. El wrapper
   de la foto es su propio contexto de posicionamiento (no el `.compare-card`
   entero) para que el badge/favorito queden pegados a la imagen aunque el
   resto de la tarjeta crezca. NOTA: si algún día un producto lleva
   `badges` (hoy siempre vacío) coincidiría en la misma esquina que
   `.badge` — no es un problema hoy, revisar si se rellena ese campo. */
/* position:relative SIN z-index a propósito: si este contenedor llevara su
   propio z-index, crearía un contexto de apilamiento nuevo y toda la
   foto/badge quedarían "soldados" por encima de `.compare-card-link` sin
   remedio (bug real, detectado 2026-08-09 al verificar el clic) — el
   z-index:2 de `.compare-card-fav`/`.compare-card-badge` ya basta para que
   solo ELLOS floten sobre el enlace estirado; la foto, sin z-index propio,
   se queda por debajo y deja pasar el clic hacia el enlace. */
.compare-card-visual { position: relative; }
.compare-card-badge {
  position: absolute; top: .75rem; left: .75rem; z-index: 2;
  min-width: 2.5rem; height: 2.5rem; padding: 0 var(--sp-2);
  display: flex; align-items: center; justify-content: center;
  border-radius: 999px;
  /* --accent (lima puro) y NO --accent-fg a propósito: --accent-fg se
     oscurece a verde bosque en tema claro (para texto legible), lo que
     dejaba esta chapa en verde apagado con el número casi negro encima
     — "está fea con ese color" (Sergio, 2026-08-15, captura en tema
     claro). --accent no tiene variante clara, así que la chapa es lima
     en los dos temas; --accent-ink (negro fijo) ya contrasta bien sobre
     lima sin necesidad de cambiar según el tema. */
  background: var(--accent); color: var(--accent-ink);
  font-family: var(--mono); font-weight: 800; font-size: var(--fs-body);
  box-shadow: var(--sh-2);
}
.compare-card-fav {
  position: absolute; top: .75rem; right: .75rem; z-index: 2;
  width: 2.3rem; height: 2.3rem; border-radius: 999px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  /* Mismo cristal oscuro fijo que `.compare-card-price-chip` (2026-08-15,
     Sergio: "pon también el corazón con una estética mejor"), no ligado
     al tema: antes usaba var(--line-2)/var(--ink), que en tema claro se
     oscurecen y dejaban un corazón gris apagado sobre un fondo también
     oscurecido — plano, poco premium. Icono en blanco fijo, siempre
     legible sobre el cristal. */
  background: var(--glass-od); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  border: 1px solid rgba(255,255,255,.16); color: var(--ink-od-soft);
  box-shadow: var(--sh-2);
  cursor: pointer; transition: border-color var(--t-fast) var(--ease-out), color var(--t-fast) var(--ease-out), transform var(--t-fast) var(--ease-out);
}
/* --accent (lima fijo), no --accent-fg: mismo motivo que la chapa de nota
   y el precio — sobre este cristal oscuro fijo, --accent-fg oscurecido en
   tema claro casi no se veía al marcar favorito. */
.compare-card-fav:hover { border-color: var(--accent); color: var(--accent); }
.compare-card-fav svg { fill: none; stroke: currentColor; stroke-width: 2; }
.compare-card-fav.is-favorito { color: var(--accent); border-color: var(--accent); }
.compare-card-fav.is-favorito svg { fill: currentColor; }
.compare-card-fav.is-favorito { animation: fav-pop .3s var(--ease-out); }
@keyframes fav-pop { 0% { transform: scale(1); } 40% { transform: scale(1.25); } 100% { transform: scale(1); } }

/* Precio como chip sobre la foto (2026-08-09) — antes vivía en el flujo
   normal de texto (entre el nombre y las puntuaciones), donde un nombre a
   dos líneas lo desplazaba a distinta altura en cada tarjeta. Sobre la foto,
   como la nota y el favorito, su posición ya no depende del texto de
   alrededor. z-index:2 mismo motivo que `.compare-card-fav`/`.compare-card-badge`:
   flota por encima de `.compare-card-link` sin necesidad de subir el
   z-index del contenedor `.compare-card-visual` entero. */
.compare-card-price-chip {
  position: absolute; left: .75rem; bottom: .75rem; z-index: 2;
  padding: var(--sp-2) var(--sp-4); border-radius: 999px; max-width: calc(100% - 1.5rem);
  display: flex; align-items: baseline; gap: var(--sp-2);
  /* Vidrio oscuro fijo, NO ligado a --line-2/--ink del tema (2026-08-15,
     Sergio: "se ve horrible" en tema claro). Antes el fondo era un negro
     translúcido fijo pero el texto sí seguía al tema (--ink pasa a casi
     negro en tema claro) → texto casi negro sobre chip casi negro,
     ilegible. Aquí el chip siempre es un cristal oscuro, así que su
     texto también va fijo (blanco/gris), nunca por variable de tema. */
  background: var(--glass-od); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  border: 1px solid rgba(255,255,255,.14); box-shadow: var(--sh-2);
  font-family: var(--mono); font-size: var(--fs-label); font-weight: 700; color: var(--ink-od);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  /* No es interactivo (a diferencia del favorito) — sin esto, su zona
     quedaría como un hueco muerto que tapa `.compare-card-link` sin hacer
     nada al clicar. */
  pointer-events: none;
}
.compare-card-price-chip-tachado { font-weight: 500; font-size: var(--fs-micro); color: var(--ink-od-mute); text-decoration: line-through; }
/* --accent (lima fijo), no --accent-fg: mismo motivo que la chapa de nota
   — sobre el cristal oscuro fijo de arriba, --accent-fg oscurecido en
   tema claro se volvía casi invisible. */
.compare-card-price-chip-final { color: var(--accent); font-weight: 800; }
/* Tema claro: gris metalizado (`--metal-row`, 26-08-26) en vez del cristal
   translúcido fijo — mismo criterio que `.nav-logo`/`.nota-ring`/
   `.score-card-nota`. En oscuro se queda el cristal de siempre, sin tocar
   (mismo caso que el resto de chapas: el metalizado es solo para claro). */
:root[data-theme="light"] .compare-card-price-chip { background: var(--metal-row); border-color: var(--ink-od-mute); }

.compare-card-stats {
  display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--sp-2);
  list-style: none; margin: .9rem 0 0; padding: var(--sp-4) 0; border-block: 1px solid var(--line);
  /* margin-top:auto — con `.compare-card` en flex column, empuja las
     puntuaciones al fondo de la tarjeta pase lo que pase arriba (nombre a
     dos líneas, precio con tachado, etc.), así quedan a la misma altura
     entre tarjetas de una misma fila (2026-08-09, ver cardLink en
     compareCard.js). */
  margin-top: auto;
  /* position+z-index (2026-08-14, corrige bug real): el enlace estirado de
     toda la tarjeta (.compare-card-link, z-index:1) tapaba esta fila por
     encima para efectos de puntero — el `title` de cada `<li>` (tooltip con
     el nombre completo del eje) nunca se activaba porque el navegador veía
     el enlace, no el `<li>`, debajo del cursor (confirmado con
     elementFromPoint). Mismo principio que `.compare-card-fav` (z-index:2):
     un pequeño "hueco muerto" para el clic-navega-a-la-ficha aquí a cambio
     de que el tooltip sí funcione — a diferencia de `.compare-card-price-chip`,
     que sí necesita dejar pasar el clic porque no tiene nada propio que
     mostrar al pasar el ratón. */
  position: relative; z-index: 2;
}
.compare-card-stats li { display: flex; flex-direction: column; align-items: center; gap: var(--sp-1); }
.compare-card-stat-label { font-family: var(--mono); font-size: var(--fs-micro); color: var(--ink-mute); text-transform: uppercase; }
.compare-card-stat-value { font-family: var(--mono); font-weight: 700; font-size: var(--fs-meta); color: var(--ink); }

/* Tarjeta entera clicable (2026-08-09): sustituye al botón "Ver precio" que
   quedaba descuadrado entre tarjetas. z-index por debajo de
   `.compare-card-visual` (donde vive el favorito) para que el corazón
   siga interceptando su propio clic sin disparar la navegación de la
   tarjeta. */
.compare-card-link { position: absolute; inset: 0; z-index: 1; border-radius: inherit; }

/* El ancho se DERIVA del gap, no se escribe a mano (06-09-26). Antes ponía
   `calc(50% - .625rem)`, donde .625rem era medio gap de 1.25rem copiado a
   pelo: un número mágico atado a otro sitio. Al mover el gap a la escala
   (1.25 -> 1.4rem) la cuenta dejó de cuadrar por 1,4 px y la rejilla de
   marca se cayó de dos columnas a una. Para N columnas con hueco g el
   ancho es 100%/N - g*(N-1)/N; así el día que cambie el gap, esto se
   ajusta solo. */
@media (min-width: 720px) { .compare-card { flex-basis: calc(50% - var(--sp-6) / 2); max-width: calc(50% - var(--sp-6) / 2); } }
@media (min-width: 1280px) { .compare-card { flex-basis: calc(25% - var(--sp-6) * 3 / 4); max-width: calc(25% - var(--sp-6) * 3 / 4); } }

/* --- Guías ---
   Rediseño premium 2026-08-25: antes `.guide-card` era una tarjeta de solo
   texto (sin foto, sin jerarquía), la más pobre del sitio frente a
   `.compare-card`/`.bento-card`. Ahora lleva la portada IA de la guía como
   media 16:10 a sangre (mismo tratamiento cover que `.ph--card:has(img)`)
   con la categoría como chapa de cristal encima — mismo lenguaje que
   `.compare-card-badge`/`.guide-card-chip` — y reutiliza `.card-tilt` para
   el mismo hover (elevación + resplandor lima) que categorías y comparativa,
   en vez del translateY suelto que tenía antes. */
/* `.guide-meta` — cabecera de la página de artículo (guias.js), no de la
   tarjeta: "PALAS · 5 min de lectura · Publicado el...". La tarjeta usa su
   propia `.guide-card-time` (ver abajo), con la categoría movida a la
   chapa sobre la foto en vez de repetirse aquí en texto. */
.guide-meta { font-family: var(--mono); font-size: var(--fs-label); color: var(--accent-fg); letter-spacing: .04em; margin-bottom: .8rem; }

.guide-row { display: grid; gap: var(--sp-6); grid-template-columns: 1fr; }
.guide-card {
  position: relative;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden;
  /* Columna flex + link-arrow anclado al fondo (mismo arreglo que
     .bento-body, ver comentario ahí): el resumen de cada guía ocupa un
     número de líneas distinto, así que sin esto "Leer guía" quedaba a una
     altura distinta en cada tarjeta de la misma fila — Sergio, 2026-08-21. */
  display: flex; flex-direction: column;
}
.guide-card-media { position: relative; aspect-ratio: 16 / 10; background: var(--surface-2); }
.guide-card-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Placeholder para guías todavía sin portada IA — mismo patrón rayado que
   `.ph` sin foto, pero clase propia (no reutiliza `.ph` tal cual) porque
   aquí nunca lleva logo ni tag "Pendiente": es contenido editorial en
   curso, no una foto de producto por recortar. */
.guide-card-media-empty {
  width: 100%; height: 100%;
  background: repeating-linear-gradient(135deg, var(--surface) 0 18px, var(--surface-2) 18px 36px);
}
.guide-card-chip {
  position: absolute; top: .85rem; left: .85rem; z-index: 2;
  font-family: var(--mono); font-size: var(--fs-micro); font-weight: 700; letter-spacing: .05em;
  padding: var(--sp-2) var(--sp-4); border-radius: 999px;
  background: var(--glass-od); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  border: 1px solid rgba(255,255,255,.16); color: var(--ink-od);
}
.guide-card-body { display: flex; flex-direction: column; flex: 1; padding: var(--sp-6) var(--sp-6) var(--sp-7); }
.guide-card-time { font-family: var(--mono); font-size: var(--fs-label); color: var(--accent-fg); letter-spacing: .04em; margin-bottom: .7rem; }
.guide-card-body h3 { margin-bottom: .7rem; font-size: var(--fs-lead); }
.guide-card-body p { color: var(--ink-mute); font-size: var(--fs-body-s); margin-bottom: 1.1rem; }
/* La etiqueta de arriba de la tarjeta es un `<p>`, así que `.guide-card-body p`
   —0-1-1— le ganaba a `.guide-card-time`/`.guide-meta`, que son 0-1-0, y se
   la llevaba al gris del resumen. Efecto real, no teórico: el "X min de
   lectura" de TODAS las tarjetas de guía del sitio salía gris en vez de lima
   desde el rediseño del 25-08-26, y nadie lo vio porque el gris es un color
   plausible ahí. Se arregla subiendo la especificidad, no bajando la del
   párrafo: así sigue valiendo para cualquier `<p>` que se añada. */
.guide-card-body .guide-card-time,
.guide-card-body .guide-meta { color: var(--accent-fg); }
.guide-card-body .link-arrow { margin-top: auto; }
/* Tarjeta entera clicable, mismo patrón que .bento-card-link/.spotlight-card-link
   — Sergio pidió (2026-08-21) el mismo comportamiento que Palas/Camisetas. */
.guide-card-link { position: absolute; inset: 0; z-index: 1; border-radius: inherit; }
/* SIN ESTO EL FILTRO DEL HUB NO OCULTA NADA, y el fallo es de los que no se
   ven leyendo el JS. `.guide-card` trae `display: flex`, y `[hidden]` a secas
   tiene la MISMA especificidad que una clase; como la regla de la tarjeta va
   después en la hoja, gana ella y la tarjeta marcada como oculta se sigue
   pintando. El JS hacía su parte —17 de 18 con el atributo puesto, el contador
   en "1 guía"— y en pantalla seguían las 18.

   Este sitio no declara un `[hidden]` global: lo pone componente a componente
   (`.search-overlay[hidden]`, `.guias-sin-resultados[hidden]`…), así que esta
   sigue el mismo patrón en vez de estrenar una regla general. */
.guide-card[hidden] { display: none; }

@media (min-width: 960px) { .guide-row { grid-template-columns: repeat(3, 1fr); } }

/* Sección "Destacadas" del hub /guias/ (docs/plan-hub-guias.md, ampliado
   2026-08-25): mismas tarjetas, pero solo 2 por fila en desktop para que
   la foto y el título respiren más — la misma idea que ya usa
   `.bento--hero`/`.spotlight-card` de darle más peso visual a lo que
   importa más, aplicada aquí a las guías con `destacada: true`. */
@media (min-width: 720px) { .guide-row--featured { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 960px) { .guide-row--featured { grid-template-columns: repeat(2, 1fr); } }
.guide-row--featured .guide-card-media { aspect-ratio: 16 / 9; }

/* ===== ÍNDICE DEL HUB /guias/ — v2, una rejilla y un filtro (07-09-26) ====
   La v1 eran siete secciones, una por tema, y medida daba 9.330 px: 10,4
   pantallas para 18 guías. Tres temas tienen UNA sola guía, así que cada uno
   gastaba ~800 px en un titular, su aire y una tarjeta suelta con dos huecos
   al lado. Ahora es una rejilla única y los chips —que antes solo eran
   anclas— filtran. Reutilizan `.chip`, el mismo pill que los filtros de
   /categoria/palas/, así que aquí no se estrena lenguaje visual. */
/* La barra del filtro va en la CHAPA metalizada del sitio y es PEGAJOSA
   (07-09-26, Sergio: "démosle una vuelta al filtro y pongamos algo muy muy
   premium"). Las dos cosas van juntas y ninguna es decorativa:

   - La chapa es el material de marca. Un índice cuyo único control iba en
     chips sueltos sobre el fondo no se parecía al resto del sitio; sobre
     chapa, el filtro deja de ser un apaño y pasa a ser una pieza.
   - Pegajosa porque es un ÍNDICE: con 18 tarjetas te vas al final de la
     rejilla y, para cambiar de tema, tenías que volver arriba. Ahora el
     control viaja contigo. El `6.5rem` no es a ojo: es el mismo desplazamiento
     que ya usa la otra barra pegajosa del sitio, así que las dos se paran a la
     misma altura bajo la cabecera.

   Los tokens on-dark son obligatorios aquí, no un adorno: en tema claro la
   chapa es un gris MEDIO y la tinta normal se queda corta encima. */
/* El filtro usa `.facet-panel` entero, el mismo de /categoria/palas/, así que
   aquí solo queda lo específico: qué marca el tema ACTIVO y cómo se comporta
   la fila en móvil.

   El estado activo va por `aria-pressed` y no por una clase propia: el estado
   ya vive en el atributo que necesita el lector de pantalla, y una clase
   paralela solo podría desincronizarse. El relleno lima es el mismo que usan
   el resto de controles seleccionados del sitio. */
.guias-chip[aria-pressed="true"] {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}
.guias-chip[aria-pressed="true"] .facet-option-count { color: var(--accent-ink); opacity: .75; }
/* Un escalón menos de separación que el panel de palas, y por 6 px: medido,
   las nueve píldoras piden 1.158 y la fila tiene 1.152, así que con el gap de
   serie la última caía sola a una segunda línea. Con `--sp-3` entran con 20 px
   de sobra.

   Ojo, que 20 px es un margen fino: el día que entre un tema con nombre largo
   —o el noveno pase a décimo— vuelve a partirse, y entonces la salida honesta
   es dejarla envolver o hacerla deslizable como en móvil, no seguir recortando
   la separación hasta que las píldoras se toquen. */
.guias-filtro .facet-nav-dims { gap: var(--sp-3); }
/* El conmutador, al extremo derecho de la cabecera. `margin-left: auto` y no
   `space-between`, por lo mismo que el contador del panel de palas: el rótulo
   y la píldora tienen que quedarse juntos a la izquierda, y con
   `space-between` se repartirían todo el ancho. */
.guias-vista { margin-left: auto; }
/* El chip activo se marca con el mismo relleno lima que el resto de
   controles seleccionados del sitio, y por `aria-pressed` y no por una clase
   propia: el estado ya vive en el atributo que necesita el lector de
   pantalla, así que una clase paralela solo podría desincronizarse. */
.guias-chip[aria-pressed="true"] {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}
.guias-chip-n {
  font-family: var(--mono); font-size: var(--fs-micro); opacity: .7;
  margin-left: .25rem;
}
/* Rejilla densa: 18 tarjetas caben en tres pantallas en vez de diez. El
   `auto-fill` con mínimo de 16rem da 4 columnas en escritorio ancho, 3 en
   portátil y 2 en tableta sin un solo breakpoint escrito a mano. */
.guide-row--indice {
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  gap: var(--sp-5); margin-top: var(--sp-6);
}
/* Dos columnas en móvil, con SUELO en 360 px. El `auto-fill` de arriba deja
   una sola columna por debajo de 512, y así el índice medía 10,2 pantallas en
   un teléfono: prácticamente lo mismo que la v1, o sea el arreglo se quedaba
   en el escritorio. A dos columnas baja a 5,6.

   El suelo no es un número redondo elegido a ojo: medido a 320 px la tarjeta
   se queda en 132 px de ancho, los títulos llegan a SIETE líneas y uno se
   recorta. A 375 son 160 px, cinco líneas como mucho y ninguno recortado. Por
   debajo de 360, pues, una sola columna. */
@media (min-width: 360px) and (max-width: 639px) {
  .guide-row--indice { grid-template-columns: repeat(2, 1fr); gap: var(--sp-4); }
  .guide-card--compacta .guide-card-body { padding: var(--sp-4); }
}
/* En móvil los nueve chips caían en CINCO filas, y con la barra pegada eso se
   comía media pantalla justo mientras miras tarjetas: el pegajoso, que en
   escritorio ayuda, ahí jugaba en contra. Pasan a una sola fila deslizable
   —el patrón de pestañas que ya usa cualquier app— y la barra baja de ~280 px
   a ~100.

   `flex-shrink: 0` en el chip es lo que hace que la fila deslice en vez de
   apretar los nueve hasta que no se lea ninguno. Y el rótulo y el contador
   comparten línea arriba, que si no serían dos filas más.

   La máscara de la derecha no es un adorno: sin ella, una fila que desliza
   parece una fila que se acaba, y los últimos temas no existirían para quien
   no arrastre por probar. */
/* En móvil los nueve temas caían en CINCO filas. Pasan a una sola fila
   deslizable —el patrón de pestañas de cualquier app—, que aquí se aplica a
   `.facet-nav-dims`, que es quien envuelve las píldoras en el panel de palas.

   `flex-shrink: 0` en la píldora es lo que hace que la fila DESLICE en vez de
   apretar las nueve hasta que no se lea ninguna. Y la máscara de la derecha no
   es adorno: sin ella una fila que desliza parece una fila que se acaba, y los
   últimos temas no existirían para quien no arrastre por probar. */
@media (max-width: 767px) {
  .guias-filtro { padding: var(--sp-5); }
  .guias-filtro .facet-nav-dims {
    flex-wrap: nowrap; overflow-x: auto;
    scrollbar-width: none; padding-bottom: var(--sp-1);
    mask-image: linear-gradient(to right, #000 calc(100% - 2rem), transparent 100%);
  }
  .guias-filtro .facet-nav-dims::-webkit-scrollbar { display: none; }
  .guias-chip { flex-shrink: 0; }
}
/* La compacta se queda sin resumen (lo quita la plantilla) y con la foto en
   16:9: es lo que baja la tarjeta de ~550 px a ~300. En un índice, el título
   y la categoría bastan para elegir; el resumen es trabajo de la guía. */
.guide-card--compacta .guide-card-media { aspect-ratio: 16 / 9; }
.guide-card--compacta .guide-card-body { padding: var(--sp-5); }
.guide-card--compacta .guide-card-body h3 { font-size: var(--fs-body-s); margin-bottom: var(--sp-4); }
.guide-card--compacta .guide-card-time { margin-bottom: var(--sp-3); }
/* VISTA DE LISTA del hub (07-09-26, Sergio: "quiero que tengamos también la
   opción de lista y mosaico"). Misma tarjeta, tumbada: miniatura a la
   izquierda y cuerpo a la derecha. Y aquí SÍ va el resumen, que es lo que la
   compacta del mosaico quita — esa es la razón de que existan dos vistas y no
   un capricho: el mosaico se ojea por la foto, la lista se lee.

   El `flex: 0 0` de la miniatura y no un porcentaje: el cuerpo tiene texto de
   largo variable y con porcentajes la foto encogía en las guías de resumen
   corto, dejando una columna de fotos con anchos distintos. */
.guide-lista { display: flex; flex-direction: column; gap: var(--sp-4); margin-top: var(--sp-6); }
.guide-card--fila { flex-direction: row; }
.guide-card--fila .guide-card-media { flex: 0 0 15rem; aspect-ratio: 16 / 10; }
.guide-card--fila .guide-card-body { padding: var(--sp-5) var(--sp-6); justify-content: center; }
.guide-card--fila .guide-card-body h3 { font-size: var(--fs-lead); }
/* Por debajo de 640 la fila se vuelve a poner de pie: a ese ancho, 15rem de
   foto dejan el título en una columna de 100 px. */
@media (max-width: 639px) {
  .guide-card--fila { flex-direction: column; }
  .guide-card--fila .guide-card-media { flex: none; aspect-ratio: 16 / 9; }
}
.guias-sin-resultados {
  margin-top: var(--sp-7); text-align: center; color: var(--ink-mute);
}
.guias-sin-resultados[hidden] { display: none; }
/* La cabecera del hub no necesita los 96 px de abajo de una sección normal:
   lo siguiente que viene es el filtro, que es parte del mismo bloque. */
/* Recortes de aire, medidos y no a ojo. Antes de esto había 615 px hasta la
   primera tarjeta —más de media pantalla sin contenido— repartidos así: 90 px
   entre migas y H1, y 98 px entre el contador de guías y el filtro, que salían
   de sumar el padding de abajo de la cabecera con el de arriba del índice. Son
   dos bloques del MISMO bloque conceptual, así que ese aire no separaba nada.

   El resto de la página conserva sus 96 px de sección: aquí se acota con
   `.section--head-guias` y `.guias-indice` para no tocar las demás. */
.breadcrumbs + .section--head-guias { padding-top: var(--sp-5); }
.section--head-guias { padding-bottom: var(--sp-4); }
.guias-indice { padding-top: var(--sp-4); }

/* Cifra real bajo el H1 del hub (2026-08-25) — mismo idioma mono/lima que
   `.guide-card-time`, calculada del catálogo de guías en cada build, nunca
   a mano (misma política de honestidad que `heroFacts()` en home.js). */
.guias-hub-stat {
  font-family: var(--mono); font-size: var(--fs-caption); letter-spacing: .03em;
  color: var(--accent-fg); margin-top: 1.1rem;
}

/* --- Hub de novedades ---
   Lista cronológica, no rejilla: cada fila es la fecha como rótulo y debajo
   la MISMA `.spotlight-card` de la home. La fecha va fuera de la tarjeta a
   propósito — dentro obligaría a tocar un componente que comparten la home
   y Camisetas, y fuera es lo que hace que la página se lea como un archivo.
   `.spotlight-card` trae `margin-top: 2rem` de su uso en la home (donde va
   sola dentro de una sección); aquí se reduce para que la tarjeta quede
   pegada a SU fecha y no flotando entre dos, que es lo que rompía la
   lectura de la lista. */
.novedad-fila + .novedad-fila { margin-top: 3rem; }
.novedad-fila .novedad-fecha { margin-bottom: 0; }
.novedad-fila .spotlight-card { margin-top: .55rem; }

/* --- Rankings ---
   .ranking-list/.ranking-row/.rank-* (fila de texto plano) retirados el
   26-08-26: era el diseño PRE-rediseño del ranking, solo lo usaba el
   teaser de la home — ahora reutiliza `.ranking-list-view`/
   `.ranking-list-row` (rankingListRow en rankingCard.js), el mismo
   componente premium que /ranking/palas/, en vez de mantener dos diseños
   de fila distintos para la misma pieza de contenido. */
/* Sin layout ni margen propios desde el 27-08-26: `.ranking-tabs` pasó a
   vivir DENTRO de `.facet-nav-dims` (el panel compartido con categoría),
   que ya aporta el flex, el gap y el espaciado. El `margin` que tenía aquí
   sobrevivía a la refactorización y metía un hueco extra dentro de la
   tarjeta. */
.ranking-tabs { min-width: 0; }
/* Píldoras un punto más apretadas SOLO aquí (el panel de categoría no se
   toca): son siete fijas —General, tres formas y tres temporadas— más las
   herramientas de la derecha, y con el ancho de píldora de categoría la
   fila pedía 827px teniendo 826 en una pantalla de 1440. Un píxel. Al
   crecer el catálogo los recuentos ganaron una cifra y "2024 (1)" se caía a
   una segunda línea (Sergio, 27-08-26: "el filtro se ha puesto en dos
   líneas, quiero que vuelva a ocupar solo 1").

   Recortando el relleno horizontal, el hueco entre etiqueta y recuento y el
   hueco entre píldoras se ganan ~65px, que es margen de sobra para hoy.
   Aviso para el futuro: cuando entre la temporada 2027 esto vuelve a estar
   justo — ahí ya no valdrá apretar, habrá que mover las herramientas a la
   fila de la cabecera, que está medio vacía. */
.facet-nav-dims.ranking-tabs { gap: var(--sp-3); }

/* Y llegó el día que avisaba el comentario de arriba: con la temporada 2027
   (31-08-26) las pestañas pasan a ocho y apretar la píldora ya no da. Medido
   en vivo antes de tocar nada: piden 842px y tienen 826 — 16px de más — así
   que caían a una segunda línea, que es lo que Sergio vio ("me gustaría que
   no se descuadrase el filtro, quiero seguir manteniendo una línea").

   La salida ya estaba escrita: las herramientas —Sexo y Lista/Mosaico— se
   van a la fila de la cabecera, donde había 962px libres al lado de "Elige
   un ranking" y su chapa. Las píldoras se quedan con el ancho entero del
   panel: de 826 a 1162, con sitio para varias temporadas más antes de
   volver a estar justo.

   Acotado a `.ranking-panel` a propósito: `.facet-nav-tools` es de la pieza
   compartida con el filtrado de categoría, donde las herramientas siguen a
   la derecha de las dimensiones y no sobra sitio en su cabecera (ahí vive
   el buscador de texto libre).

   El filo vertical se apaga aquí: en `.facet-nav` separaba dos columnas de
   la misma fila y significaba algo; en la cabecera, pegado al borde
   derecho, sería una raya suelta. */
.ranking-panel .facet-panel-head .facet-nav-tools {
  margin-left: auto; align-self: center;
  border-left: 0; padding-left: 0;
}

/* Pestañas planas Forma+Temporada de /ranking/palas/ (rediseño
   inspirado en padelreview.com.ar/mejores-palas, ver
   ranking_hombre_mujer_palas_backlog) — una sola fila, sin agrupar por eje
   ni repetir "Todas" (versión anterior con 3 filas Género/Forma/Temporada,
   corregida a petición de Sergio: "quitaría Todas que sale 3 veces").
   Mismo componente `.chip`/`.chip--current` que el panel de filtros.
   Comparte fila con `.ranking-toolbar-controls` (desplegable de género +
   Lista/Mosaico, pegado a la derecha vía `margin-left:auto` en esa regla)
   — antes vivían en dos filas separadas con el contador "N palas
   analizadas" de por medio; fusionadas al quitarlo (26-08-26). */

/* El ranking usa el MISMO panel que el filtrado de categoría (27-08-26,
   Sergio: "el filtro de ranking déjalo similar al de palas"), así que sus
   píldoras dejan de ser la chapa metalizada suelta sobre la página y pasan
   a la píldora clara de `.facet-chip-summary`, dentro de la tarjeta blanca
   del panel. El metalizado se queda donde sigue teniendo sentido: las
   filas del ranking y el desplegable de Sexo (petición explícita anterior).

   Estas píldoras NO son un filtro aunque lo parezcan: cada una es un
   enlace a OTRO ranking (páginas SSR distintas). Por eso el panel se
   titula "Elige un ranking" y no "Filtrar por", y por eso la activa lleva
   `aria-current="page"` en vez de un estado de filtro. */
.ranking-tabs .chip {
  display: inline-flex; align-items: center; gap: var(--sp-2); padding: var(--sp-3) var(--sp-4);
  background: var(--surface-2); border: 1px solid transparent; border-radius: 999px;
  font-size: var(--fs-meta); font-weight: 600; color: var(--ink-soft); text-decoration: none;
  transition: border-color var(--t-fast) var(--ease-out), color var(--t-fast) var(--ease-out), background var(--t-fast) var(--ease-out),
              transform var(--t-fast) var(--ease-out), box-shadow var(--t-fast) var(--ease-out);
}
.ranking-tabs .chip:hover {
  background: var(--surface-3); color: var(--ink);
  transform: translateY(-1px); box-shadow: var(--sh-2);
}
/* Los controles de la derecha (Sexo y el interruptor de vista) usan los
   mismos colores que el resto del panel (27-08-26, Sergio: "quiero que el
   color del filtro de sexo sea del mismo color que el resto, que lo has
   dejado gris"). El metalizado se queda SOLO en el desglose desplegable
   —eso sí lo pidió expresamente en gris metalizado— y en las filas del
   ranking; el control en reposo ya no. Sin reglas propias aquí, heredan
   `.facet-chip-summary` y `.ranking-view-btn`, que ya son de tema. */
.ranking-tabs .chip--current {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink); font-weight: 700;
}
/* Sin el guiño naranja "esto se quita" del resto del sitio: aquí no es un
   filtro removible, es la página en la que ya estás — clicarlo no hace
   nada distinto a quedarte. */
.ranking-tabs .chip--current:hover { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
/* Colores de tema, no fijos claros: desde que la píldora vive sobre la
   tarjeta blanca del panel, el gris-sobre-oscuro de antes se volvía
   invisible. */
.ranking-tabs .chip--disabled {
  background: var(--surface-2); border-color: transparent; color: var(--ink-mute);
  opacity: .5; cursor: not-allowed;
}
.ranking-tabs .chip--disabled:hover { transform: none; box-shadow: none; background: var(--surface-2); color: var(--ink-mute); }
.ranking-toolbar-controls .facet-chip-group[open] .facet-chip-summary {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}
/* Desplegable de Sexo del ranking — misma maquetación `.facet-option*` que
   los de `/categoria/palas/` (26-08-26, "para unificar formatos"), pero con
   el panel en `--metal-row` en vez del claro de categoría: aquí convive con
   las tarjetas metalizadas del ranking, y un panel claro cantaría. Todo lo
   de dentro pasa a colores fijos claros, porque el fondo es oscuro en los
   dos temas. */
.ranking-toolbar-controls .facet-chips {
  background: var(--metal-row); border-color: var(--line-2);
}
:root[data-theme="light"] .ranking-toolbar-controls .facet-chips { border-color: var(--ink-od-mute); }
.ranking-toolbar-controls .facet-option { color: var(--ink-od-mute); }
.ranking-toolbar-controls .facet-option:hover { background: var(--veil-od-strong); color: var(--ink-od); }
.ranking-toolbar-controls .facet-option[aria-selected="true"] { color: var(--ink-od); }
.ranking-toolbar-controls .facet-option-count { color: var(--ink-od-mute); }
/* Tick en lima (pedido de Sergio: "el tick ponlo en lima"). Aquí sí puede
   ser `--accent` a plena saturación: sobre la chapa oscura se lee perfecto,
   a diferencia del desplegable claro de categoría, donde el mismo lima
   sobre crema desaparecería y se queda el verde `--accent-fg`. */
.ranking-toolbar-controls .facet-option-check { color: var(--accent); }

/* Alturas cuadradas con las de `.chip` (pedido de Sergio: "cuadraría el
   tema de los filtros, así se ve raro" / luego "los botones no están a la
   misma altura, unos más arriba que otros") — antes `.facet-chip-summary`
   (.6rem de padding vertical) y `.ranking-view-toggle`/`.ranking-view-btn`
   quedaban ~9px y ~4.8px más altos que los chips de al lado en la misma
   fila. Mismo padding vertical Y mismo font-size que `.chip` en los tres
   (el font-size también entra en la altura vía line-height, por eso no
   basta con igualar el padding) — ajustado directamente en sus reglas
   base cuando ha hecho falta (`.ranking-view-toggle`/`-btn` más abajo),
   no aquí, porque ahí volvían a perder contra la declaración original por
   orden de fuente (misma especificidad, un único selector de clase). */
/* Regla retirada el 27-08-26: bajaba el padding de "Sexo" a .35rem para
   cuadrarlo con las píldoras del ranking de ENTONCES, que eran pequeñas.
   Desde que las píldoras adoptaron el tamaño del panel de categoría
   (.6rem), esta regla dejaba "Sexo" 8,7px más bajo que sus vecinas
   (34,2 vs 42,9 px medidos en vivo). Sin nada aquí hereda
   `.facet-chip-summary`, que ya es el tamaño bueno. */

/* Rejilla del ranking completo: mismas `.compare-card` que categoría/
   comparador (misma ficha premium en todo el sitio), con el puesto como
   una "chapa" numerada que sobresale de la esquina superior izquierda de
   la tarjeta — por fuera de `.compare-card-badge` (la nota, dentro de la
   foto), nunca se pisan. */
/* Columna flex (no un bloque suelto) desde que el desplegable "otras
   versiones" cuelga de aquí, debajo de la tarjeta: con `display: block` y
   la tarjeta a `height: 100%`, el desplegable se salía de la celda y se
   pintaba encima de la fila siguiente del mosaico. */
.ranking-card-wrap { position: relative; flex: 1 1 100%; max-width: 100%; display: flex; flex-direction: column; }
/* BUG real (encontrado por Sergio en capturas, 2026-08-25): `.compare-card`
   sigue llevando su propio `max-width: calc(50%/25% - gap)` de las media
   queries de `.compare-grid` (categoría/comparador) — esas reglas no usan
   combinador `>`, así que también alcanzan a esta `.compare-card` aunque ya
   no sea hijo directo del grid (ahora lo es `.ranking-card-wrap`). Sin este
   `max-width: 100%` de más especificidad, la tarjeta quedaba encogida al
   25% del 25% de su columna (~6% del ancho real) y la foto cuadrada
   (aspect-ratio 1/1, ligada al ancho) se volvía invisible. */
/* `flex: 1 1 auto` en vez de `height: 100%`: iguala las alturas dentro de
   la fila del mosaico igual que antes, pero dejando sitio al desplegable
   de versiones que va detrás. */
.ranking-card-wrap .compare-card { width: 100%; max-width: 100%; flex: 1 1 auto; }
/* Sangrado igual al del cuerpo de la tarjeta (1.3rem) para que el
   desplegable no quede colgando del borde de la celda. */
.ranking-card-wrap .ranking-variantes { margin-top: .6rem; padding-inline: var(--sp-6); }
.ranking-card-pos {
  position: absolute; top: -.7rem; left: -.7rem; z-index: 3;
  width: 2.4rem; height: 2.4rem; border-radius: 999px;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--mono); font-weight: 800; font-size: var(--fs-body-s);
  background: var(--surface-2); color: var(--ink-soft);
  border: 1px solid var(--line-2);
  box-shadow: var(--sh-2);
}
/* Podio (puestos 1-3): misma chapa lima que `.compare-card-badge` — el
   ranking premia visualmente el top 3 antes incluso de leer la tarjeta. */
.ranking-card-pos--top {
  background: var(--accent); color: var(--accent-ink); border-color: var(--accent);
  box-shadow: var(--sh-2);
}

/* Mismos puntos de corte que `.compare-grid` en categoría/comparador (2/4
   columnas) — aquí en `.ranking-card-wrap`, el hijo directo del grid, en
   vez de en `.compare-card` (que ha dejado de serlo al quedar envuelta). */
@media (min-width: 720px) { .ranking-card-wrap { flex-basis: calc(50% - var(--sp-6) / 2); max-width: calc(50% - var(--sp-6) / 2); } }
@media (min-width: 1280px) { .ranking-card-wrap { flex-basis: calc(25% - var(--sp-6) * 3 / 4); max-width: calc(25% - var(--sp-6) * 3 / 4); } }

/* Barra "N palas analizadas" + interruptor Lista/Mosaico (2026-08-25, ver
   ranking_hombre_mujer_palas_backlog) — Lista por defecto (SSR visible),
   Mosaico es la rejilla `.compare-card` de siempre. `initRankingView` en
   main.js solo alterna `hidden`, las dos vistas viven siempre en el HTML.
   BUG real encontrado en la propia verificación: `.ranking-list-view` y
   `.compare-grid` ya llevan `display:flex` incondicional más abajo — el
   atributo `hidden` compite con esa regla en especificidad (misma, 1 clase
   = 1 atributo) y una regla de autor SIEMPRE gana a la hoja de estilos por
   defecto del navegador (donde vive `[hidden]{display:none}`), así que sin
   este selector explícito las dos vistas se quedaban visibles a la vez
   aunque el JS pusiera `hidden` bien. */
[data-ranking-view][hidden] { display: none; }
/* Desplegable "Excluir palas de mujer/hombre" + interruptor Lista/Mosaico,
   agrupados a la derecha de la MISMA fila que los chips de Forma/Temporada
   (pedido de Sergio, 26-08-26: "quita lo de 85 palas analizadas para que
   se pongan a la misma altura los botones de General, Diamante, etc con
   los de Todas/Lista/Mosaico" — antes vivían en un `.ranking-toolbar`
   aparte, una fila entera por debajo de los chips, con el contador de
   palas analizadas ocupando el hueco a su izquierda). `margin-left: auto`
   los pega al borde derecho de `.ranking-tabs`; si no cabe todo en una
   línea, el propio `flex-wrap` de `.ranking-tabs` los baja de línea sin
   CSS extra. `.ranking-gender-filter` reutiliza `.facet-chip-group`/
   `.facet-sort-list` tal cual (mismo desplegable que "Ordenar por" en el
   panel de categoría), solo se acota el ancho mínimo del panel aquí
   porque fuera del panel de filtros no hereda su contexto. */
.ranking-toolbar-controls { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; margin-left: auto; }
.ranking-gender-filter .facet-sort-list { min-width: 12rem; }
/* padding: 0 — el propio `.ranking-view-btn` ya lleva el mismo padding vertical (.35rem) que `.chip`/`.facet-chip-summary`; cualquier padding
   aquí en el wrapper suma altura extra y desalinea el toggle respecto a
   los demás controles del toolbar (hallazgo en vivo: 38.97px vs 34.17px). */
.ranking-view-toggle { display: inline-flex; border: 1px solid var(--line-2); border-radius: 999px; padding: 0; gap: .1rem; flex-shrink: 0; }
/* BUG real encontrado por Sergio en captura ("el botón de Mosaico se ve
   mal"): esta regla es la que de verdad gana (misma especificidad que la
   de la línea ~1122, un solo selector de clase, pero ésta va después en
   el archivo) y todavía tenía `color: var(--ink-mute)`/`var(--ink)` de
   antes del rediseño — colores de tema pensados para fondo CLARO, ilegibles
   sobre la chapa metalizada siempre oscura de `.ranking-view-toggle`. Los
   colores fijos ya existían en la línea ~1122 pero nunca ganaban. */
.ranking-view-btn {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  font-family: var(--sans); font-size: var(--fs-meta); font-weight: 600; color: var(--ink-od-mute);
  background: none; border: none; border-radius: 999px; padding: var(--sp-2) var(--sp-4); cursor: pointer;
  transition: background var(--t-micro) var(--ease-out), color var(--t-micro) var(--ease-out);
}
.ranking-view-btn:hover { color: var(--ink-od); }
.ranking-view-btn.is-active { background: var(--accent); color: var(--accent-ink); }

/* Mismo interruptor Lista/Mosaico, reutilizado en /categoria/palas/
   (26-08-26) — pero dentro del PANEL de filtros, no sobre las tarjetas
   metalizadas del ranking. Ahí `--metal-row` (chapa oscura) es lo correcto
   porque convive con chips igual de oscuros; aquí conviviría con "Ordenar
   por", que es un `.facet-chip-summary` claro sobre `var(--surface)` — un
   bloque oscuro al lado se leía como un cuerpo extraño (captura de Sergio:
   "ahora mismo no queda bien"). Se reviste con los mismos tokens que el
   desplegable de al lado para que los dos controles se lean como pareja. */
/* Control segmentado de verdad, no dos botones en una cápsula (26-08-26,
   Sergio con captura: "no me termina de convencer este difuminado, busco
   estética premium"). El estado activo usaba `--accent-soft` —lima al 14%
   de opacidad— que sobre el crema de `--surface-2` daba un verde lavado y
   sucio, sin filo: leído como un borrón, no como una pastilla.

   El patrón que sí se lee premium es el de cualquier segmented control
   nativo: la PISTA va hundida (sombra interior) y la pastilla activa va
   ELEVADA y sólida, sin transparencias. El color no hace el trabajo de
   señalar el estado — lo hace la profundidad; el lima se queda solo en el
   texto y el icono, a plena saturación en vez de al 14%. */
/* El conmutador se estiliza para DOS sitios: las herramientas de la fila
   (donde lo pone /categoria/palas/) y la cabecera del panel (donde lo pone el
   hub de guías, porque abajo los nueve temas ya llenan la fila).

   Sin esto el botón inactivo salía en 1,11 de contraste, o sea invisible: la
   regla base de `.ranking-view-btn` usa tinta casi blanca porque ese
   conmutador vive normalmente sobre la chapa OSCURA del ranking, y la
   excepción para el panel claro apuntaba solo a `.facet-nav-tools`. Es
   exactamente el mismo fallo que ya documenta el comentario de la regla base,
   reaparecido por un contenedor nuevo. */
.facet-nav-tools .ranking-view-toggle,
.facet-panel-head .ranking-view-toggle {
  background: var(--surface-2); border-color: transparent;
  padding: 3px; gap: 2px;
  box-shadow: var(--edge-well);
}
/* Mismo font-size que `.facet-chip-summary` (el padding baja a .5rem para
   compensar los 3px de la pista y que el conjunto siga midiendo lo mismo
   que "Ordenar por" al lado — sin esto el toggle quedaba 8,8px más bajo,
   42,9 vs 34,2 px medidos en vivo). */
/* .41rem de padding vertical, no .5rem: el interruptor quedaba 2,9px más
   alto que las píldoras de al lado (45,8 vs 42,9 px medidos en vivo) en
   categoría Y en ranking. Sale de igualar las dos cajas, que comparten
   fuente y por tanto altura de línea (L):
     píldora  = 2·1px borde + 2·.6rem  + L = 21,2 + L
     toggle   = 2·1px borde + 2·3px pista + (2·X + L) = 8 + 2X + L
   igualando, X = 6,6px ≈ .41rem. */
.facet-nav-tools .ranking-view-btn,
.facet-panel-head .ranking-view-btn {
  color: var(--ink-mute); padding: var(--sp-2) var(--sp-5); font-size: var(--fs-meta);
  transition: background var(--t-fast) var(--ease-out), color var(--t-fast) var(--ease-out), box-shadow var(--t-fast) var(--ease-out);
}
.facet-nav-tools .ranking-view-btn:hover,
.facet-panel-head .ranking-view-btn:hover { color: var(--ink-soft); background: none; }
/* Pastilla en lima sólido (26-08-26, Sergio: "quiero que se vea como un
   interruptor y que usemos el lima"). La versión anterior era blanca con
   texto verde: leía como pastilla pero no como INTERRUPTOR, porque el
   estado activo no se distinguía de un simple fondo de tarjeta. Con lima a
   plena saturación + `--accent-ink` encima (los mismos dos tokens que
   `.btn-accent`, fijos en los dos temas) el estado se lee de un vistazo, y
   la pista hundida de detrás hace el resto del trabajo de "esto conmuta". */
.facet-nav-tools .ranking-view-btn.is-active,
.facet-panel-head .ranking-view-btn.is-active {
  background: var(--accent); color: var(--accent-ink); font-weight: 700;
  box-shadow: 0 1px 2px rgba(0,0,0,.2), 0 2px 8px -3px rgba(198,255,61,.5);
}
.facet-nav-tools .ranking-view-btn.is-active svg,
.facet-panel-head .ranking-view-btn.is-active svg { color: var(--accent-ink); }

/* --- Ranking: vista de lista (por defecto) ---
   Vuelta de tuerca sobre el diseño de referencia (padelreview.com.ar/
   mejores-palas, captura de Sergio 25-08-26): fila horizontal con foto,
   marca/forma, nombre, stats en línea y un anillo de nota compacto a la
   derecha — mucha más densidad de información por pantalla que el mosaico,
   más cerca del sitio de referencia. */
.ranking-list-view { display: flex; flex-direction: column; gap: var(--sp-4); list-style: none; margin: 0; padding: 0; }
/* Fila siempre oscura (26-08-26, probado en vivo con captura de Sergio
   sobre fondo blanco y confirmado: "dejándolo así"): toda la fila pasa a
   la misma chapa siempre-oscura que antes solo llevaban nota/favorito/CTA
   (`.nota-ring`), en vez de `var(--surface)` (blanco en tema claro). Todo
   el texto de dentro pasa a colores fijos pensados para fondo oscuro. */
/* Gris metalizado — SOLO en tema claro (26-08-26, Sergio: "las tarjetas
   del ranking en modo oscuro no deberían aparecer en este gris
   metalizado"). Probamos varios tonos en un comparador aparte y el
   metalizado ganaba claramente sobre fondo claro ("la que está ahora
   mismo en localhost es la que más me gusta para el tema blanco"), pero
   en oscuro se prefiere la chapa neutra original (`var(--surface-2)`,
   sin degradado ni filo de luz). De ahí la variable `--metal-row`: en
   tema oscuro resuelve a la chapa plana de siempre, en claro al
   degradado — y la reutilizan también los botones de filtro de abajo
   ("los botones de arriba de los filtros, los pondría del mismo color,
   para que quede bien estéticamente"), así que cambiar un tono aquí
   cambia los dos sitios a la vez. */
:root { --metal-row: var(--surface-2); }
/* El gris de producción, sin tocar (decisión de Sergio 06-09-26: "quiero
   que lo pruebes aun así, porque me gusta mucho").

   Lo que cuesta, medido, para que quede escrito: sobre esta chapa el techo
   absoluto es 4,51 —el blanco PLENO— y AA pide 4,5 para texto pequeño. O
   sea que aquí no cabe un nivel apagado de verdad; el "mute" se queda en
   4,00. Se probaron dos pasos más oscura (#4a4a53, margen de sobra) y uno
   (#5c5c65, tres niveles limpios) y se descartaron por aspecto.

   Aun así esto mejora mucho lo que hay hoy en producción, donde ese mismo
   texto va en rgba(242,242,242,.55) y da 2,51. La jerarquía sobre chapa la
   hacen ahora el tamaño y el peso, no el tinte. */
:root[data-theme="light"] {
  --metal-row: linear-gradient(155deg, #6e6e77 0%, #58585f 45%, #6a6a73 100%);
  /* El tramo superior del metalizado, suelto. Lo necesita cualquier pieza que
     tenga que ser OPACA sobre la chapa sin poder heredar su degradado —una
     cabecera pegajosa, por ejemplo: si repitiera el degradado se vería el
     salto al despegarse. */
  --metal-top: #6e6e77;
  /* El velo del rebaje compuesto sobre la chapa clara: rgba(0,0,0,.5) sobre
     #6e6e77 da esto. Ver --rebaje-solido en :root. */
  --rebaje-solido: #37373c;
}
/* Acabado de la chapa de la tienda con mejor precio: mismo material, otro
   baño. Misma inclinación de degradado que `--metal-row`, para que se lean
   como dos piezas de la misma familia y no como dos componentes distintos.
   NO cambia entre temas: la pieza es oscura en los dos.

   Neutro profundo, no verde oliva (06-09-26). El oliva era un cuarto tono
   cálido-verdoso en una paleta que ya tiene lima, naranja y rojo, y encima
   esa fila ya se señala por otras tres vías: el sello, el filo lima y el
   precio en lima. Al quitarle el tinte, la fila gana en todo — el nombre
   de tienda pasa de 8,87 a 12,57, el precio en lima de 8,4 a 11,91 — y,
   contra lo que uno esperaría, se DISTINGUE MÁS de las demás chapas: 1,6
   de separación contra el 1,13 que daba el oliva. Destaca por ser la más
   profunda, no por tener otro color. */
:root { --metal-elegida: linear-gradient(155deg, #2b2b31 0%, #1f1f24 48%, #27272d 100%); }
/* `flex-wrap: wrap` para que el desplegable de versiones, que es hijo
   directo del `<li>` y pide el 100%, caiga a su propia línea con el ancho
   entero de la fila. El resto del contenido nunca llega a envolver: el
   cuerpo encoge (`min-width: 0`) y las acciones son de tamaño fijo. */
.ranking-list-row {
  display: flex; align-items: center; gap: var(--sp-5); flex-wrap: wrap;
  background: var(--metal-row);
  border: 1px solid var(--line-2); border-radius: var(--radius);
  padding: var(--sp-4) var(--sp-5);
  transition: border-color var(--t-base) var(--ease-out), transform var(--t-base) var(--ease-out);
}
/* El filo de luz (efecto metal) y el borde más claro son cosas del
   degradado — sin sentido sobre la chapa plana de tema oscuro, por eso
   viven aquí y no en la regla base de arriba. */
:root[data-theme="light"] .ranking-list-row {
  box-shadow: var(--edge-relief);
  border-color: var(--ink-od-mute);
}
.ranking-list-row:hover { border-color: var(--accent); transform: translateX(3px); }
.ranking-list-pos {
  flex-shrink: 0; width: 2rem; text-align: center;
  font-family: var(--mono); font-weight: 800; font-size: var(--fs-body-s); color: var(--ink-od-mute);
}
.ranking-list-pos--top {
  color: var(--accent-ink); background: var(--accent); border-radius: 999px;
  width: 2rem; height: 2rem; line-height: 2rem; display: inline-block;
}
.ranking-list-thumb { flex-shrink: 0; width: 3.75rem; }
.ranking-list-thumb .ph:has(img) { padding: .15rem; }
.ranking-list-thumb .ph-logo { width: 22px; height: 22px; }
.ranking-list-thumb .ph-tag { display: none; }
@media (min-width: 480px) { .ranking-list-thumb { width: 5.5rem; } }
/* Base 0, no `auto`: con `flex-wrap: wrap` en la fila, el reparto de
   líneas usa el tamaño BASE de cada hijo, no el ya encogido — con base
   `auto` el ancho natural del nombre bastaba para mandar el cuerpo a una
   línea propia en móvil y la fila se desmontaba en tres pisos. */
.ranking-list-body { flex: 1 1 0; min-width: 0; }
.ranking-list-tag {
  font-family: var(--mono); font-size: var(--fs-label); text-transform: uppercase; letter-spacing: .03em;
  color: var(--accent); margin-bottom: .2rem;
}
/* Recorte a 2 líneas con ellipsis (probado en mobile, 2026-08-26): la
   primera versión sin `nowrap` dejaba que el nombre más largo del catálogo
   ("Nox AT10 Genius 18K Alum by Agustín Tapia 2026") hiciera wrap palabra
   por palabra en los ~140px que quedan de columna en 375px — filas
   altísimas y feas. `line-clamp` corta en 2 líneas siempre, quede corto o
   largo el nombre. */
.ranking-list-body h3 {
  font-size: var(--fs-body); margin: 0; line-height: 1.3; color: var(--ink-od);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.ranking-list-body h3 a { text-decoration: none; color: inherit; }
.ranking-list-body h3 a:hover { color: var(--accent); }
.ranking-list-stats { display: none; list-style: none; margin: .5rem 0 0; padding: 0; gap: var(--sp-5); }
.ranking-list-stats li { display: flex; flex-direction: column; align-items: center; gap: .1rem; }
.ranking-list-stat-label { font-family: var(--mono); font-size: var(--fs-micro); color: var(--ink-od-mute); text-transform: uppercase; }
.ranking-list-stat-value { font-family: var(--mono); font-size: var(--fs-meta); font-weight: 700; color: var(--ink-od-soft); }
@media (min-width: 720px) {
  .ranking-list-stats { display: flex; }
}

/* --- "Otras versiones de esta pala" (familias de modelo del ranking
   general, ver dedupeFamilias en generators/rankings.js) ---
   `<details>` nativo: la fila se queda igual de compacta y despliega bajo
   demanda, sin JS. Los colores base son los del tema porque en mosaico
   este bloque cuelga de `.ranking-card-wrap`, sobre el fondo normal de la
   página; dentro de la fila de lista se reescriben abajo, porque allí el
   fondo es la chapa metalizada y todo va en claro sobre oscuro. */
.ranking-variantes { margin-top: .5rem; min-width: 0; }
.ranking-variantes-summary {
  display: inline-flex; align-items: center; gap: var(--sp-2); cursor: pointer;
  font-family: var(--mono); font-size: var(--fs-label); letter-spacing: .02em;
  color: var(--ink-mute); list-style: none; user-select: none; white-space: nowrap;
}
.ranking-variantes-tail { display: none; }
@media (min-width: 720px) { .ranking-variantes-tail { display: inline; } }
.ranking-variantes-summary::-webkit-details-marker { display: none; }
.ranking-variantes-summary:hover { color: var(--ink); }
.ranking-variantes-count {
  font-weight: 700; color: var(--accent-fg);
  background: var(--accent-soft); border: 1px solid var(--line);
  border-radius: 999px; padding: .1rem var(--sp-2);
}
.ranking-variantes[open] .ranking-variantes-summary { margin-bottom: .4rem; }
/* Píldoras, no una tabla dentro de la fila (Sergio, 27-08-26: primero
   "las notas no se ven de una forma premium, parece que está medio
   superpuesto", y sobre el panel que lo sustituyó, "no me gusta del todo
   cómo se ve"). Se le montaron cuatro alternativas sobre la fila real y
   eligió esta.

   El diagnóstico de fondo: cualquier versión de lista pesaba tanto como la
   propia pala — nueve líneas convertían la fila en una tabla. En píldoras
   esas nueve versiones ocupan tres líneas, la fila sigue siendo una fila y
   la nota se lee de un vistazo dentro de cada píldora. Además es el
   lenguaje que ya usan el panel de filtros, la ficha técnica y las
   pestañas del ranking — cero vocabulario visual nuevo. */
.ranking-variantes-list {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: var(--sp-2);
  max-width: 46rem;
}
.ranking-variante {
  display: inline-flex; align-items: center; gap: var(--sp-2); text-decoration: none;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: 999px; padding: var(--sp-1) var(--sp-1) var(--sp-1) var(--sp-3);
  transition: background var(--t-micro) var(--ease-out), border-color var(--t-micro) var(--ease-out);
}
.ranking-variante:hover { background: var(--surface-3); border-color: var(--accent-fg); }
.ranking-variante-nombre { font-size: var(--fs-caption); color: var(--ink-soft); white-space: nowrap; }
.ranking-variantes-nota {
  font-family: var(--mono); font-size: var(--fs-label); font-weight: 700; line-height: 1.4;
  font-variant-numeric: tabular-nums;
  color: var(--accent-fg); background: var(--accent-soft);
  border-radius: 999px; padding: .1rem var(--sp-2);
}
/* Los nombres ya vienen recortados a lo que distingue a cada versión (ver
   `etiquetaVariante`), así que en móvil caben sin cortar: las píldoras
   simplemente fluyen a más líneas. Solo se aprieta un poco la tipografía. */
@media (max-width: 719px) {
  .ranking-variantes-list { gap: var(--sp-2); }
  .ranking-variante-nombre { font-size: var(--fs-label); }
}

/* Línea propia dentro de la fila. El sangrado de escritorio lo alinea con
   el nombre de la pala: puesto (2rem) + hueco (1rem) + foto (5.5rem) +
   hueco (1rem). En móvil no se sangra — ahí hace falta cada píxel. */
.ranking-list-row > .ranking-variantes { flex: 1 0 100%; margin-top: 0; }
@media (min-width: 720px) { .ranking-list-row > .ranking-variantes { margin-left: 9.5rem; } }

/* Sobre la chapa metalizada de la fila de lista: los tokens del tema no
   valen (la fila es oscura en los dos temas), así que las píldoras se
   labran con blanco translúcido sobre el propio degradado y la nota va en
   lima puro, que es lo que de verdad destaca ahí. */
.ranking-list-row .ranking-variantes-summary { color: var(--ink-od-mute); }
.ranking-list-row .ranking-variantes-summary:hover { color: var(--ink-od); }
.ranking-list-row .ranking-variantes-count {
  color: var(--accent-ink); background: var(--accent); border-color: transparent;
}
.ranking-list-row .ranking-variante {
  background: rgba(255,255,255,.07); border-color: rgba(255,255,255,.1);
}
.ranking-list-row .ranking-variante:hover {
  background: rgba(255,255,255,.13); border-color: var(--accent);
}
/* Tinta plena: es el nombre del producto, contenido principal de su fila,
   no un dato secundario. Con --ink-od-soft se quedaba en 4,41 sobre la
   chapa clara. */
.ranking-list-row .ranking-variante-nombre { color: var(--ink-od); }
.ranking-list-row .ranking-variantes-nota { color: var(--accent); background: rgba(0,0,0,.28); }
/* Excepción de tema claro, y aquí SÍ hace falta: el velo del chip tiene que
   ir al revés en cada tema porque la chapa vale cosas distintas. En oscuro
   `--metal-row` es casi negro y un velo BLANCO al 7 % separa el chip de la
   fila; en claro es un gris MEDIO y ese mismo velo lo aclara todavía más,
   dejando el nombre —que va en --ink-od, casi blanco— en 3,90.
   Con velo negro en claro sube a 6,3 y el chip sigue distinguiéndose, ahora
   por ser más oscuro que la fila en vez de más claro. Se probó el velo negro
   en los dos temas y en oscuro el chip desaparecía: 1,09 contra la fila. */
:root[data-theme="light"] .ranking-list-row .ranking-variante {
  background: rgba(0,0,0,.22); border-color: rgba(0,0,0,.3);
}
:root[data-theme="light"] .ranking-list-row .ranking-variante:hover {
  background: rgba(0,0,0,.34); border-color: var(--accent);
}

/* Nota+favorito+CTA en línea, sin chapa propia (pedido de Sergio,
   26-08-26): la chapa individual dejó de hacer falta en cuanto la fila
   ENTERA pasó a ser la misma chapa oscura ("dejándolo así, quitaría la
   chapa, ya no sería necesaria") — antes distinguía este grupo de un
   fondo de fila claro, ahora ya no hay contraste que salvar. El resto de
   colores fijos (arco, número, corazón) se quedan igual: siguen sobre
   fondo oscuro, ahora el de la fila en vez de uno propio. */
.ranking-list-actions { display: inline-flex; align-items: center; gap: var(--sp-3); flex-shrink: 0; }

/* Precio de la fila — SOLO en /categoria/palas/, nunca en el ranking (ver
   `showPrice` en rankingCard.js). Colores fijos claros, como el resto del
   contenido de la fila: vive sobre la chapa siempre oscura, no sobre el
   fondo de página. `tabular-nums` para que las cifras no bailen de fila a
   fila al tener distinto número de dígitos. */
.ranking-list-price {
  margin: 0; flex-shrink: 0; text-align: right;
  font-family: var(--mono); font-size: var(--fs-body-s); font-weight: 700;
  color: var(--ink-od); font-variant-numeric: tabular-nums;
}
/* PVP tachado + final, mismas clases que la chapa de precio del mosaico
   (`precioContenido()` las emite en los dos sitios) — aquí se apilan en
   dos líneas porque la columna es estrecha, no en fila como sobre la foto. */
.ranking-list-price .compare-card-price-chip-tachado {
  display: block; font-size: var(--fs-label); font-weight: 500;
  color: var(--ink-od-mute); text-decoration: line-through;
}
.ranking-list-price .compare-card-price-chip-final { color: var(--accent); }
/* Por debajo de 900px el precio se va: la fila ya compite por ancho con
   nombre, nota, favorito y CTA, y el precio completo está a un clic en la
   ficha. Mismo criterio que las stats (que aparecen a partir de 720px) y
   que favorito/CTA (que desaparecen por debajo de 720px). */
@media (max-width: 899px) { .ranking-list-price { display: none; } }

.mini-ring { position: relative; width: 48px; height: 48px; line-height: 0; flex-shrink: 0; }
.mini-ring svg { display: block; transform: rotate(-90deg); }
.mini-ring-track { fill: none; stroke: var(--line-od); stroke-width: 5; }
.mini-ring-fill { fill: none; stroke: var(--accent); stroke-width: 5; stroke-linecap: round; transition: stroke-dashoffset var(--t-slow) var(--ease-out); }
.mini-ring-num {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  font-family: var(--mono); font-weight: 700; font-size: var(--fs-meta); color: var(--ink-od);
}

.ranking-list-fav {
  width: 2.1rem; height: 2.1rem; border-radius: 999px; padding: 0; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: none; border: 1px solid rgba(242,242,242,.25); color: var(--ink-od-mute); cursor: pointer;
  transition: border-color var(--t-micro) var(--ease-out), color var(--t-micro) var(--ease-out);
}
.ranking-list-fav:hover { border-color: var(--accent); color: var(--accent); }
.ranking-list-fav svg { fill: none; stroke: currentColor; stroke-width: 2; }
.ranking-list-fav.is-favorito { color: var(--accent); border-color: var(--accent); }
.ranking-list-fav.is-favorito svg { fill: currentColor; }

/* El CTA usa `.btn-accent` (lima sólido, pedido de Sergio 26-08-26: "el
   botón de ver ficha será lima") — a diferencia de `.btn-ghost`, ya trae
   colores fijos (fondo lima + texto oscuro) que no dependen del tema, así
   que no hace falta ningún override aquí dentro de la chapa siempre
   oscura, a diferencia del favorito de arriba. */

@media (max-width: 719px) {
  /* El favorito y el CTA se quitan en mobile — libera ancho para el
     nombre (recortado a 2 líneas arriba), que es lo que importa de verdad
     en una columna tan estrecha. Sigue disponible desde la ficha del
     producto. */
  .ranking-list-actions .btn, .ranking-list-fav { display: none; }
}

/* --- Reviews --- */
.review-row { display: grid; gap: var(--sp-6); grid-template-columns: 1fr; }
.review-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: var(--sp-7);
}
.review-card h3 { font-size: var(--fs-lead); margin: .6rem 0 .8rem; }

@media (min-width: 960px) { .review-row { grid-template-columns: repeat(3, 1fr); } }

/* --- Cómo puntuamos (teaser de metodología en la home) --- */
.metodo-row { display: grid; gap: var(--sp-5); grid-template-columns: 1fr; }
.metodo-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: var(--sp-6); display: flex; flex-direction: column; gap: var(--sp-4);
}
.metodo-icon {
  width: 38px; height: 38px; display: grid; place-items: center; border-radius: 12px;
  background: var(--accent-soft); color: var(--accent-fg); border: 1px solid var(--line);
}
.metodo-card h3 { font-size: var(--fs-body); }
.metodo-card p { color: var(--ink-soft); font-size: var(--fs-body-s); line-height: 1.55; }

@media (min-width: 960px) { .metodo-row { grid-template-columns: repeat(3, 1fr); } }

/* --- Marcas / marquee --- */
.brands { padding-block: 5rem 5.5rem; }
.brands .section-head { margin-bottom: 2.4rem; text-align: center; margin-inline: auto; }
/* Banda transparente con dos hairlines, a sangre — el diseño de siempre.
   Se probó (06-09-26) ponerla sobre chapa metalizada con las letras en
   blanco: a sangre partía la página en dos, y contenida con esquina tampoco
   convenció. Vuelve tal cual estaba. */
.marquee { overflow: hidden; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); padding-block: var(--sp-7); }
.marquee-track {
  display: flex; gap: 3.5rem; width: max-content;
  animation: marqueeScroll 32s linear infinite;
}
@keyframes marqueeScroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* La marquesina pasa a ser navegable (2026-09-05): sus marcas eran <span>
   muertos. Se conserva el aspecto —mismo tamaño y color— y solo se le quita
   el subrayado, para que no cambie nada visualmente salvo el cursor. */
.marquee-track a {
  font-family: var(--display); font-weight: 800; font-size: var(--fs-title);
  color: var(--ink-mute); white-space: nowrap; text-decoration: none;
  transition: color var(--t-micro) var(--ease-out);
}
/* Al pasar por encima el nombre sube a tinta PLENA, sin color (06-09-26,
   Sergio: "el verde que salía al pasar por el texto, no").
   No es que no se quisiera el lima: sobre la crema del tema claro el lima da
   1,18 y es invisible — por eso existía ahí el verde oscuro de --accent-fg.
   Quedaban el naranja, que en este sitio ya significa "descuento", o quitar
   el color. Se quita: el énfasis lo hace el contraste, no el tinte, y así
   funciona igual en los dos temas sin ninguna excepción. */
.marquee-track a:hover,
.marquee-track a:focus-visible { color: var(--ink); }
/* Se detiene al pasar por encima. Con <span> daba igual, pero ahora son
   enlaces: perseguir una marca en movimiento para poder pulsarla no es
   navegable, y con el dedo en móvil menos. Se pausa también al enfocar con
   el tabulador, que es el mismo problema para quien no usa ratón. */
.marquee:hover .marquee-track,
.marquee:focus-within .marquee-track { animation-play-state: paused; }
.marquee-cta { text-align: center; margin: 2.2rem 0 0; }

/* Directorio de marcas (/marcas/). Sin logo: ver el comentario de
   generators/marcasHub.js. La chapa de iniciales ocupa su sitio, así que el
   día que haya logos la tarjeta no cambia de forma. */
.marca-grid {
  display: grid; gap: var(--sp-4); margin-top: 2rem;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
}
.marca-card {
  display: flex; align-items: center; gap: var(--sp-4);
  padding: var(--sp-5) var(--sp-5); text-decoration: none;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius);
  transition: border-color var(--t-fast) var(--ease-out), transform var(--t-fast) var(--ease-out);
}
.marca-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.marca-card-media {
  display: grid; place-items: center; flex-shrink: 0;
  /* Rectangular, no cuadrada: casi todos los logos de marca son wordmarks
     apaisados y en una chapa cuadrada de 48 px salen a 48x15, ilegibles. */
  width: 5rem; height: 3.25rem; border-radius: .7rem;
  background: var(--surface-2); border: 1px solid var(--line);
  font-family: var(--mono); font-size: var(--fs-body-s); font-weight: 700;
  color: var(--ink-mute); letter-spacing: .02em;
}
/* Logo de marca: dos ficheros, uno por tema, conmutados por CSS igual que
   `.ph-logo` y el logo del nav. `object-fit: contain` porque cada marca trae
   una proporción distinta —de un wordmark muy apaisado a un emblema
   cuadrado— y recortar cualquiera de ellos sería mutilar la marca. */
.marca-logo { display: block; max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; }
.marca-logo--light { display: none; }
/* Placa clara fija para los logos de color: es el fondo para el que la marca
   los diseñó, y sin ella cinco de ellos no llegan a 2:1 de contraste en uno
   de los dos temas. No cambia con el tema a propósito. */
/* Los dos ÚNICOS literales que quedan fuera de :root, y se quedan a
   propósito: es la placa sobre la que se apoyan los logos de marca, y
   tiene que ser clara SIEMPRE — también en tema oscuro — porque casi
   todos los logos vienen en su versión oscura sobre fondo blanco. No es
   una superficie del sistema: es un sustituto del papel del logotipo. */
.marca-card-media--placa { background: #f4f4f2; border-color: rgba(20,20,15,.12); }
.marca-card-media img { padding: var(--sp-2); }
.marca-card-body { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
.marca-card-name { font-weight: 700; color: var(--ink); }
.marca-card-meta { font-family: var(--mono); font-size: var(--fs-label); color: var(--accent-fg); }
.marca-card-cats {
  font-size: var(--fs-caption); color: var(--ink-mute); line-height: 1.35;
  overflow: hidden; text-overflow: ellipsis;
}

.brands-cta { max-width: 560px; margin: 3.2rem auto 0; text-align: center; }
.brands-cta .accent-rule { width: 2.4rem; height: 3px; background: var(--accent); border-radius: 2px; margin: 0 auto 1.3rem; }
.brands-cta h3 { margin-bottom: .7rem; }
.brands-cta p { color: var(--ink-mute); margin-bottom: 1.7rem; }
/* El kicker de este bloque es un <p>, así que la regla de arriba le ganaba
   a `.kicker` por especificidad y lo dejaba en gris — en tema claro daba la
   casualidad de que era el color correcto, pero en OSCURO se quedaba gris
   él solo mientras los otros nueve iban en lima. Nadie lo decidió: era un
   accidente. Se repone el token, que es lo que hace también
   `.spotlight-body .kicker` por el mismo motivo. */
.brands-cta .kicker { color: var(--kicker-fg); }

/* --- Newsletter --- */
.newsletter { padding-block: 6.5rem; }
.newsletter-inner { max-width: 640px; margin-inline: auto; text-align: center; }
.newsletter-inner h2 { margin-bottom: .8rem; }
.newsletter-inner > p:not(.fine-note) { color: var(--ink-mute); margin-bottom: 2.2rem; }
.newsletter-form {
  display: flex; flex-direction: column; gap: var(--sp-4); max-width: 460px; margin-inline: auto;
}
.newsletter-form input {
  padding: var(--sp-5) var(--sp-5); border-radius: 999px; border: 1px solid var(--line-2);
  background: var(--surface); color: var(--ink); font-size: var(--fs-body-s);
}
.newsletter-form input:focus { border-color: var(--accent); outline: none; }
.newsletter-form .btn { justify-content: center; }
.newsletter .fine-note { text-align: center; }

@media (min-width: 640px) {
  .newsletter-form { flex-direction: row; }
  .newsletter-form input { flex: 1; }
}

/* =============================================================
   7. Footer
   ============================================================= */
.footer { border-top: 1px solid var(--line); padding-block: 4rem var(--sp-8); background: var(--bg-2); }
.footer-grid { display: grid; gap: var(--sp-8); grid-template-columns: 1fr; margin-bottom: 3rem; }
/* Mismo criterio que la cabecera: la columna de marca (logo + texto) vive
   sobre un bloque rectangular gris neutro propio — aquí sin cuña, ya que no
   hay enlaces al lado que justifiquen el corte en diagonal. El logo se
   queda siempre en su variante oscura (trazo blanco) y "Sapiens" en lima
   puro, igual en los dos temas. */
.footer-brand {
  background: var(--surface-od);
  border-radius: var(--radius-sm);
  padding: var(--sp-7) var(--sp-7);
  color: var(--ink-od);
}
/* Tema claro: la chapa de marca del pie pasa al mismo gris metalizado que
   el logo de cabecera, la fila del ranking y las tarjetas de contacto
   (31-08-26, Sergio: "la del footer también habría que cambiarla"). Era la
   última chapa del sitio que seguía con el gris plano fijo.

   Sin borde, a propósito: es la misma decisión que en `.nav-logo`, que
   tampoco lo lleva — el filo de luz basta para que se lea como metal, y un
   borde claro sobre el fondo del pie marcaría un rectángulo que ahora no
   está. El texto ya era `#f2f2f2` y el "Sapiens" ya iba en `--accent`, así
   que no hay que tocar ningún color. */
:root[data-theme="light"] .footer-brand {
  background: var(--metal-row);
  box-shadow: var(--edge-relief);
}
.footer-brand .nav-logo {
  color: var(--ink-od);
}
.footer-brand .nav-logo-icon--dark { display: block !important; }
.footer-brand .nav-logo-icon--light { display: none !important; }
.footer-brand .nav-logo em {
  color: var(--accent);
}
.footer-brand p { color: var(--ink-od-mute); font-size: var(--fs-body-s); margin-top: .9rem; max-width: 32ch; }
.footer-col h4 { font-size: var(--fs-meta); text-transform: uppercase; letter-spacing: .04em; color: var(--ink-mute); margin-bottom: 1rem; font-family: var(--mono); font-weight: 500; }
.footer-col { display: flex; flex-direction: column; gap: var(--sp-3); font-size: var(--fs-body-s); color: var(--ink-soft); }
.footer-col a:hover { color: var(--accent-fg); }
.footer-bottom {
  display: flex; flex-direction: column; gap: var(--sp-3);
  border-top: 1px solid var(--line); padding-top: 1.6rem;
  font-size: var(--fs-caption); color: var(--ink-mute);
}

@media (min-width: 720px) { .footer-grid { grid-template-columns: 1.4fr repeat(3, 1fr); } }
@media (min-width: 720px) { .footer-bottom { flex-direction: row; justify-content: space-between; } }

/* =============================================================
   8. Responsive tune-ups
   ============================================================= */
@media (min-width: 540px) { .hero { padding-block: 10rem 5.5rem; } }
@media (min-width: 1280px) { .container { padding-inline: var(--sp-8); } }

/* =============================================================
   9. Reduced-motion — only truly intrusive effects
   ============================================================= */
/* El movimiento reducido se respeta de forma GENERAL, no pieza a pieza —
   ver el bloque al final del fichero. */

/* =============================================================
   10. Consentimiento de cookies (CMP propio + GA4 Consent Mode v2)
   ============================================================= */
.pst-consent {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 9999;
  padding: var(--sp-5);
  background: var(--surface-2);
  border-top: 1px solid var(--line-2);
}
.pst-consent[hidden] { display: none; }

.pst-consent__box {
  max-width: 60rem;
  margin: 0 auto;
  display: flex;
  gap: var(--sp-5);
  align-items: center;
  flex-wrap: wrap;
}

.pst-consent__text {
  flex: 1 1 20rem;
  margin: 0;
  color: var(--ink-mute);
  font-size: var(--fs-body-s);
  line-height: 1.5;
}
.pst-consent__link { color: var(--accent-fg); text-decoration: underline; }

.pst-consent__actions {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  flex: 0 0 auto;
}

/* Personalizar: tercera opción, neutral, con menos peso que Aceptar/
   Rechazar a propósito (no compite en el mismo eje aceptar/rechazar). */
.pst-consent__customize {
  padding: var(--sp-3) var(--sp-1);
  font-size: var(--fs-meta);
  color: var(--ink-mute);
  background: none;
  border: 0;
  text-decoration: underline;
  cursor: pointer;
}
.pst-consent__customize:hover { color: var(--accent-fg); }

/* Aceptar y Rechazar: MISMO tamaño y peso visual (exigencia AEPD). Mismo
   lima sólido que el resto de botones primarios del sitio (.btn-accent). */
.pst-consent__btn {
  padding: var(--sp-3) var(--sp-6);
  font-size: var(--fs-body-s);
  font-weight: 700;
  border-radius: 999px;
  border: 1px solid transparent;
  background: var(--accent);
  color: var(--accent-ink);
  cursor: pointer;
  transition: transform var(--t-base) var(--ease-out), box-shadow var(--t-base) var(--ease-out);
}
.pst-consent__btn:hover { transform: translateY(-2px); box-shadow: var(--glow), var(--ring); }

/* === Panel de gestión (2ª capa): categorías + toggles === */

.pst-consent-modal {
  position: fixed;
  inset: 0;
  z-index: 10000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-6);
}
.pst-consent-modal[hidden] { display: none; }

.pst-consent-modal__overlay {
  position: absolute;
  inset: 0;
  background: rgba(0,0,0,.6);
  border: 0;
  padding: 0;
  cursor: pointer;
}

.pst-consent-modal__box {
  position: relative;
  width: 100%;
  max-width: 40rem;
  max-height: 85vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
}

.pst-consent-modal__header {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-5);
  padding: var(--sp-6) var(--sp-6);
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}
.pst-consent-modal__title {
  margin: 0;
  font-size: var(--fs-body);
  font-weight: 700;
  font-family: var(--display);
}
.pst-consent-modal__close {
  flex: 0 0 auto;
  width: 2rem;
  height: 2rem;
  line-height: 1;
  font-size: var(--fs-title);
  color: var(--ink-mute);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.pst-consent-modal__close:hover { color: var(--ink); border-color: var(--line-2); }

.pst-consent-modal__body { padding: var(--sp-6) var(--sp-6); }

.pst-consent-modal__intro {
  margin: 0 0 1rem;
  font-size: var(--fs-meta);
  line-height: 1.5;
  color: var(--ink-mute);
}

.pst-consent-cat {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  margin-bottom: 0.75rem;
  overflow: hidden;
}
.pst-consent-cat:last-child { margin-bottom: 0; }

.pst-consent-cat__row {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-4);
  background: var(--surface-2);
}

.pst-consent-cat__toggle-row {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-width: 0;
  padding: 0;
  font: inherit;
  color: var(--ink);
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
}

.pst-consent-cat__chevron {
  flex: 0 0 auto;
  color: var(--ink-mute);
  transition: transform var(--t-fast) var(--ease-out);
}
.pst-consent-cat.is-open .pst-consent-cat__chevron { transform: rotate(90deg); }

.pst-consent-cat__title { font-size: var(--fs-body-s); font-weight: 700; }

.pst-consent-cat__badge {
  flex: 0 0 auto;
  font-family: var(--mono);
  font-size: var(--fs-label);
  font-weight: 600;
  padding: 0.15rem var(--sp-3);
  border-radius: 999px;
  color: var(--accent-fg);
  background: var(--accent-soft);
  border: 1px solid var(--line-2);
}

.pst-consent-cat__detail {
  padding: var(--sp-4);
  border-top: 1px solid var(--line);
  font-size: var(--fs-meta);
  line-height: 1.5;
  color: var(--ink-mute);
}
.pst-consent-cat__detail[hidden] { display: none; }
.pst-consent-cat__detail p { margin: 0 0 0.5rem; }

.pst-consent-service {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding-bottom: 0.5rem;
  margin-bottom: 0.5rem;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
  font-size: var(--fs-meta);
}

.pst-consent-table-wrap { overflow-x: auto; margin-top: 0.5rem; }
.pst-consent-table { width: 100%; border-collapse: collapse; font-size: var(--fs-caption); }
.pst-consent-table th,
.pst-consent-table td {
  text-align: left;
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.pst-consent-table td:nth-child(2) { white-space: normal; }

.pst-consent-modal__actions {
  position: sticky;
  bottom: 0;
  display: flex;
  gap: var(--sp-3);
  flex-wrap: wrap;
  padding: var(--sp-5) var(--sp-6);
  background: var(--surface);
  border-top: 1px solid var(--line);
}
.pst-consent-modal__actions .pst-consent__btn { flex: 1 1 10rem; text-align: center; }

.pst-consent-modal__save {
  flex: 1 1 10rem;
  padding: var(--sp-3) var(--sp-6);
  font-size: var(--fs-body-s);
  font-weight: 700;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-2);
  background: var(--surface-2);
  color: var(--ink-mute);
  cursor: pointer;
}
.pst-consent-modal__save:hover { color: var(--ink); border-color: var(--ink-mute); }

/* Toggle (switch) reutilizado por la categoría y por el servicio dentro. */
.pst-toggle {
  position: relative;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  cursor: pointer;
}
.pst-toggle input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.pst-toggle__track {
  display: inline-block;
  position: relative;
  width: 2.5rem;
  height: 1.4rem;
  border-radius: 999px;
  background: var(--line-2);
  transition: background var(--t-micro) var(--ease-out);
}
.pst-toggle__thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 1.15rem;
  height: 1.15rem;
  border-radius: 50%;
  background: var(--ink);
  transition: transform var(--t-fast) var(--ease-out);
}
.pst-toggle input:checked + .pst-toggle__track { background: var(--accent); }
.pst-toggle input:checked + .pst-toggle__track .pst-toggle__thumb { background: var(--accent-ink); transform: translateX(1.1rem); }
.pst-toggle input:disabled { cursor: default; }
.pst-toggle--locked { opacity: 0.6; cursor: default; }

/* =============================================================
   11. Páginas legales (Aviso legal, Privacidad, Cookies)
   ============================================================= */
.legal-page { padding-block: 8.5rem 5rem; }
.legal-content { max-width: 68ch; margin-inline: auto; }
.legal-content h1 { margin-bottom: .5rem; }
.legal-updated { color: var(--ink-mute); font-size: var(--fs-meta); margin-bottom: 2.5rem; font-family: var(--mono); }
.legal-content h2 {
  font-size: var(--fs-lead); margin-top: 2.4rem; margin-bottom: .8rem;
  padding-top: 1.6rem; border-top: 1px solid var(--line);
}
.legal-content h2:first-of-type { border-top: 0; padding-top: 0; margin-top: 0; }
.legal-content h3 { font-size: var(--fs-body); margin-top: 1.6rem; margin-bottom: .6rem; color: var(--ink); }
.legal-content p { color: var(--ink-soft); font-size: var(--fs-body-s); line-height: 1.7; margin-bottom: 1rem; }
.legal-content ul { margin: 0 0 1rem; padding-left: 1.2rem; list-style: disc; }
.legal-content li { color: var(--ink-soft); font-size: var(--fs-body-s); line-height: 1.65; margin-bottom: .5rem; }
.legal-content strong { color: var(--ink); }
/* --accent-fg (no --accent): en tema claro --accent es lima puro y no
   pasa contraste como texto sobre el fondo casi blanco de estas páginas
   (Word #41, Sergio: "los textos en verde neón no se leen bien"). --accent-fg
   ya tiene el override oscuro (#146B45) para esto en tema claro. */
.legal-content a { color: var(--accent-fg); text-decoration: underline; }
/* Portada editorial de guía (IA, opcional) y diagramas de datos hechos a
   mano en SVG dentro del cuerpo — mismo radio/línea que el resto del sitio,
   con scroll horizontal en el diagrama si no cabe en pantallas estrechas. */
.guide-cover {
  width: 100%; height: auto; aspect-ratio: 3 / 2; object-fit: cover;
  border-radius: var(--radius); border: 1px solid var(--line);
  margin-bottom: 2rem;
}
/* Portada que conserva su proporción real, para imágenes oficiales de
   fabricante muy apaisadas: el recorte a 3/2 de `.guide-cover` se comería
   los extremos, que es donde están las palas. Ver guias.js. */
.guide-cover--natural { aspect-ratio: auto; object-fit: contain; }
.guide-diagram {
  margin: 2rem 0; padding: var(--sp-6); overflow-x: auto;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface);
}
.guide-diagram svg { display: block; max-width: 100%; height: auto; margin-inline: auto; }
.legal-cookie-table { margin: 1.2rem 0; }
.legal-cookie-table .pst-consent-table { font-size: var(--fs-meta); }
.legal-cookie-table th { color: var(--ink); font-family: var(--mono); font-size: var(--fs-label); text-transform: uppercase; }

/* =============================================================
   12. Breadcrumbs (páginas generadas del catálogo)
   ============================================================= */
/* 6.75rem, no 8.5rem (06-09-26). Este margen hace DOS trabajos a la vez:
   dejar pasar la cabecera, que es fija y mide 75 px, y separar las migas de
   ella. Con 8.5rem el segundo trabajo se llevaba 61 px, más que la propia
   cabecera. A 6.75rem quedan 33, que siguen leyéndose como aire y no como
   apretujón, y se recuperan 28 px en todas las páginas con migas. */
.breadcrumbs { margin-top: 6.75rem; margin-bottom: 1.5rem; }
.breadcrumb-list {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1);
  list-style: none; margin: 0; padding: 0;
  font-family: var(--mono); font-size: var(--fs-caption); color: var(--ink-mute);
}
.breadcrumb-list li:not(:last-child)::after { content: "/"; margin-left: .3rem; color: var(--line-2); }
.breadcrumb-list a { color: var(--ink-mute); text-decoration: none; }
.breadcrumb-list a:hover { color: var(--accent-fg); }
.breadcrumb-list li[aria-current="page"] { color: var(--ink); }

/* =============================================================
   13. Fichas de producto
   ============================================================= */

/* Estructura general de la ficha: bloque principal a la izquierda (hero +
   ficha técnica compacta + puntuaciones + otras palas de la marca +
   análisis) y lateral a la derecha (precios) — estructura inspirada en el
   repaso de un competidor (2026-08-08). Por debajo de 960px el lateral baja
   a ancho completo debajo del bloque principal, sin sticky. La ficha
   técnica vivió en el lateral junto al precio hasta el 2026-08-14; ahora
   solo la ficha de pelotas usa ese patrón (`.ficha-lateral-card`, ver más
   abajo) — en palas/zapatillas es `.specs-compact-*` en el bloque
   principal. */
.product-layout { display: grid; grid-template-columns: 1fr; gap: var(--sp-9); align-items: start; }
.product-main { display: flex; flex-direction: column; gap: 4rem; min-width: 0; }
.product-aside { min-width: 0; }
.product-aside-sticky { display: flex; flex-direction: column; gap: var(--sp-6); }

/* "Dónde comprar" duplicado para el orden de scroll en móvil (2026-08-20,
   ver `preciosMobileInline` en productPage.js): por debajo de 960px se
   enseña la copia en línea (dentro de `.product-main`, justo antes del
   Dashboard) y se apaga el lateral sticky; a partir de 960px es al revés,
   el lateral ya hace ese trabajo junto al hero y la copia en línea sobra.
   Solo la variante de precios (`--precios`) se ve afectada — el lateral de
   ficha técnica de pelotas no tiene copia móvil, sigue igual que siempre. */
.product-precios-mobile { display: block; }
.product-aside--precios { display: none; }

@media (min-width: 960px) {
  /* align-items:stretch (2026-08-13, corrige bug detectado por Sergio):
     con "start" (el valor base de arriba) el item `.product-aside` se
     encoge a la altura de su contenido, así que su hijo sticky no tiene
     recorrido dentro del propio aside y jamás llega a fijarse — se nota
     en cuanto la columna principal es más alta (siempre), el sticky no
     hacía nada y la ficha técnica desaparecía de pantalla sin más.
     Con "stretch", `.product-aside` iguala la altura de `.product-main`
     y el sticky interno sí tiene espacio para acompañar el scroll. */
  .product-layout { grid-template-columns: minmax(0, 1fr) minmax(340px, 400px); align-items: stretch; }
  /* Red de seguridad para el scroll: si el lateral acaba siendo más alto que
     la pantalla —un producto con cinco o seis tiendas, o con seguimiento de
     precio—, un sticky normal se queda con el final fuera de la ventana y el
     bloque parece cortado al bajar. Con un alto máximo, el lateral se
     desplaza por dentro y siempre se puede llegar al último enlace. En
     pantallas donde cabe entero, `max-height` no se nota. */
  .product-aside-sticky {
    position: sticky; top: 6.5rem;
    max-height: calc(100vh - 7.5rem); overflow-y: auto;
    scrollbar-width: thin;
  }
  /* La red de seguridad de arriba tiene un efecto secundario que no se
     buscaba: `overflow-y: auto` convierte el lateral en contenedor de
     recorte, y el panel de cupones —que es `position: absolute` y sale por
     debajo de su fila— se corta contra el borde en vez de flotar por
     encima. Medido en la ficha de la Arrow Hit Attack: 73,3 px del panel
     por fuera, que es justo el trozo donde vive el aviso de "puede que ya
     hayas usado el código".

     Mientras hay un panel abierto se deja de recortar. No se pierde nada:
     el recorte existe para poder alcanzar el último enlace en laterales muy
     altos, y con un popover abierto nadie está recorriendo el lateral —
     está leyendo el popover. Al cerrarlo vuelve el recorte solo.

     Esto NO lo trajo la auditoría de estilos: el `overflow-y` está en main
     desde que se puso la red de seguridad. */
  .product-aside-sticky:has(.precio-row-promo[open]) { overflow: visible; }
  .product-precios-mobile { display: none; }
  .product-aside--precios { display: block; }
}

/* Cabecera compacta de los bloques apilados en la columna principal
   (Puntuaciones, Otras palas de la marca, Análisis) — mismo .section-head,
   solo con menos margen porque aquí van varios seguidos, no uno por
   sección de ancho completo. */
.product-block .section-head--tight h2 { font-size: var(--fs-title); }

/* Tarjeta de ficha técnica del lateral — solo la usa pelotas desde el
   2026-08-14 (ver `fichaLateral()` en productPage.js); en palas/zapatillas
   la sustituye `.specs-compact-*` en el bloque principal, más abajo. Mismo
   "surface" que el resto de tarjetas del sitio, pensada para ocupar poco
   sitio. */
.ficha-lateral-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: var(--sp-6) var(--sp-6);
}
.ficha-lateral-card h3 { font-size: var(--fs-body); margin-bottom: 1rem; }
.ficha-lateral-list { display: flex; flex-direction: column; gap: 0; margin: 0; }
.ficha-lateral-row {
  display: flex; justify-content: space-between; align-items: baseline; gap: var(--sp-4);
  padding: var(--sp-3) 0; border-bottom: 1px solid var(--line); font-size: var(--fs-meta);
}
.ficha-lateral-row:last-child { border-bottom: 0; padding-bottom: 0; }
.ficha-lateral-row dt { color: var(--ink-mute); flex-shrink: 0; }
.ficha-lateral-row dd { margin: 0; color: var(--ink); font-weight: 600; text-align: right; }

/* Ficha técnica en píldoras (2026-08-14, v5) — ver el porqué en
   productPage.js::fichaCompacta(). Reemplaza el hueco que antes reservaba
   la galería de fotos secundarias (siempre "Pendiente": nunca hubo acceso
   real a esas fotos). Selección curada de 6-8 campos por categoría
   (`specRowsCompactPala`/`specRowsCompactZapatilla`).
   v5: tercer hijo de `.product-hero-grid`, con grid-column:1/-1 para que
   ocupe el ancho completo (foto + copy) en vez de vivir solo en la columna
   de precio — así rellena el hueco que dejaba la foto, más corta que el
   precio+ficha técnica cuando esta vivía solo a la derecha (v4, Sergio:
   "dudas de si sería mejor estender la ficha técnica también por debajo de
   la pala, para que no quede mucho hueco vacío"). En el layout de una sola
   columna (<720px) el span da igual — ya ocupa todo el ancho. */
.specs-pills-block { grid-column: 1 / -1; margin-top: .5rem; }
.specs-pills-block .kicker { margin-bottom: .7rem; }
.specs-pills { display: flex; flex-wrap: wrap; gap: var(--sp-3); }
.specs-pill {
  display: inline-flex; align-items: baseline; gap: var(--sp-2);
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 999px;
  padding: var(--sp-3) var(--sp-5); font-size: var(--fs-meta); line-height: 1;
}
.specs-pill-dot { width: .4rem; height: .4rem; border-radius: 999px; background: var(--accent-fg); flex-shrink: 0; align-self: center; }
.specs-pill-label { color: var(--ink-mute); }
.specs-pill-value { color: var(--ink); font-weight: 700; }

/* Segunda tanda de la ficha técnica (2026-08-31): perfil, superficie,
   gramaje y temporada. Deliberadamente MÁS LIGERA que las píldoras — sin
   caja ni borde, texto pequeño y separadores finos — para que no compita
   con los ocho esenciales de arriba, que son los que se miran primero.
   Se envuelve sola en varias líneas si no cabe. */
.specs-extra {
  display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-6);
  margin: .85rem 0 0; padding-top: .8rem;
  border-top: 1px solid var(--line);
}
.specs-extra-row { display: inline-flex; align-items: baseline; gap: var(--sp-2); }
.specs-extra dt { color: var(--ink-mute); font-size: var(--fs-caption); }
.specs-extra dd { margin: 0; color: var(--ink-soft); font-size: var(--fs-caption); font-weight: 600; }

/* En claro, --surface-2 es un crema casi idéntico al fondo de la página
   (#f1efe7 sobre #f5f4f0) y la píldora se pierde contra el fondo. Mismo
   criterio que `.score-card-nota`/`.nota-ring`: gris metalizado
   (`--metal-row`, 26-08-26) en vez de un fondo claro, así el lima queda
   para el punto y el texto pasa a colores fijos claros porque ahora el
   fondo siempre es oscuro. */
:root[data-theme="light"] .specs-pill {
  background: var(--metal-row); border-color: var(--ink-od-mute);
  /* Primitivo on-dark: se redefinen los tokens EN el componente y todo lo
     de dentro hereda. Antes aquí había tres reglas más reparando a mano la
     tinta de cada pieza. */
  --ink:       var(--ink-od);
  --ink-mute:  var(--ink-od-mute);
  --line:      var(--line-od);
  --accent-fg: var(--accent);
  color: var(--ink);
}

/* "Otras palas de la marca" reutiliza .compare-card, pero fijo a 2 columnas
   siempre — vive en el bloque principal, que ya es más estrecho que el
   container completo; la regla de 4-en-fila a 1280px se vería apretada. */
.compare-grid--brand { justify-content: flex-start; }

/* Ficha de pelotas (2026-08-11) — estructura deliberadamente distinta a la
   de palas/zapatillas, a petición de Sergio: sin hero de foto grande +
   lateral de precios (poca información que enseñar por producto), sino una
   tarjeta horizontal por cada tamaño de pack (foto a la izquierda, precio y
   botón a la derecha), apiladas. Reutiliza `.product-layout`/`.product-aside`
   de siempre (2026-08-13): antes era una columna única con la ficha técnica
   apilada debajo de los packs, dejando la columna derecha vacía en desktop;
   ahora la ficha técnica vive en el aside, sticky igual que en palas. */
.product-pelota-head { display: flex; flex-direction: column; gap: var(--sp-1); margin-bottom: 2rem; }
.product-pelota-head h1 { margin: 0; }
.product-pelota-head .product-hero-quickactions { margin-top: 1rem; }

.pelota-packs { display: flex; flex-direction: column; gap: var(--sp-5); }
.pelota-pack-card {
  display: grid; grid-template-columns: 7rem 1fr; align-items: center; gap: var(--sp-6);
  padding: var(--sp-5); background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
}
.pelota-pack-photo .ph { margin: 0; }
.pelota-pack-info { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-2); min-width: 0; }
.pelota-pack-nombre { font-weight: 800; font-size: var(--fs-body); }
.pelota-pack-vendor { font-size: var(--fs-caption); color: var(--ink-mute); }
.pelota-pack-precio { font-family: var(--mono); font-size: 1.6rem; font-weight: 700; color: var(--accent-fg); }
.pelota-pack-precio--pendiente { font-family: var(--sans); font-size: var(--fs-body-s); font-weight: 600; color: var(--ink-mute); }
.pelota-pack-card--agotado { opacity: .55; }
.pelota-pack-card .btn { margin-top: .2rem; }

@media (max-width: 560px) {
  .pelota-pack-card { grid-template-columns: 5rem 1fr; gap: var(--sp-4); padding: var(--sp-4); }
  .pelota-pack-precio { font-size: var(--fs-title); }
}
.compare-grid--brand .compare-card { flex-basis: calc(50% - var(--sp-6) / 2); max-width: calc(50% - var(--sp-6) / 2); }
@media (max-width: 480px) {
  .compare-grid--brand .compare-card { flex-basis: 100%; max-width: 100%; }
}

/* El hero de producto abre con menos aire que una sección normal (06-09-26,
   Sergio: "me gustaría que cupiera en un primer vistazo hasta el seguimiento
   de precios"). `.section` reparte 6rem arriba y abajo porque su trabajo es
   separar dos bloques de contenido; el hero no separa nada —es lo primero
   que hay— y encima ya viene precedido por las migas, que traen su propio
   margen. Con 6rem, la tarjeta de precios acababa en 917 px con 900 de
   pantalla y el seguimiento de precio se quedaba justo debajo del corte.
   Bajando a 2rem acaba en 825 y entra entero, gráfico y veredicto incluidos,
   en pantallas de ~830 px de alto para arriba —que es lo que hay que mirar,
   porque una ventana de portátil deja bastante menos de los 900 nominales.
   Solo afecta al hero: el resto de secciones de la ficha conservan su aire. */
.product-hero { padding-top: 2rem; }
.product-hero-grid { display: grid; gap: var(--sp-8); grid-template-columns: 1fr; align-items: start; }
.product-hero-visual .ph { border-radius: var(--radius); }
.product-hero-copy .price { font-size: var(--fs-title); font-weight: 700; margin: .5rem 0 1.25rem; }
.product-hero-copy .badge { margin-bottom: .75rem; }

/* Título + anillo de nota en la misma fila — el anillo no compite con el
   título porque flex-shrink:0 lo deja a su tamaño fijo mientras el título
   ocupa el resto. La etiqueta de categoría (.compare-tag) y el rating
   viven FUERA de esta fila (arriba/abajo, a todo el ancho) a propósito
   desde 2026-08-20: antes estaban dentro de `.product-hero-titletext`
   junto al h1, así que el anillo quedaba alineado con el bloque entero
   (tag+h1+rating) y no con el título en sí — se notaba "descolgado"
   cuando el título ocupaba una sola línea. Con el anillo como hermano
   directo del h1 (align-items:center), queda centrado con el título
   siempre, tenga 1, 2 o 3 líneas — ver [[score_card_nota_rediseno_backlog]]. */
.product-hero-copy h1 { margin: .5rem 0 .75rem; }

/* Anillo de progreso de la nota general — inspirado en el repaso de un
   competidor (2026-08-08), reemplaza el chip de texto plano en el hero
   (el chip sigue vivo en compareCard/reviews, más compacto). Cálculo del
   dasharray/dashoffset hecho en shared.js::notaGeneralRing, no en CSS.
   El número va dentro de .nota-ring-circle (una caja de 76x76 aparte,
   línea 0) en vez de centrarse contra .nota-ring entero — si se centra
   contra el contenedor completo, la etiqueta "NUESTRA NOTA" de debajo
   suma altura y el cálculo del 50% ya no coincide con el centro real del
   círculo (bug detectado 2026-08-08 por Sergio: el número quedaba
   descentrado, desplazado hacia abajo). */
/* Envuelto en la MISMA tarjeta que `.score-card-nota` (2026-08-20, segunda
   ronda de "unificar formatos") — gris metalizado en claro (`--metal-row`,
   26-08-26), gris neutro en oscuro. Como la tarjeta es SIEMPRE oscura en
   los dos temas del sitio, todo lo de dentro (número, icono, arco) deja de
   necesitar variantes por tema: un solo juego de colores vale para los
   dos. */
.nota-ring {
  position: relative; flex-shrink: 0; text-align: center;
  padding: var(--sp-4) var(--sp-5); background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius-sm);
}
:root[data-theme="light"] .nota-ring {
  background: var(--metal-row); border-color: transparent;
  /* Primitivo on-dark: aquí había dos reglas más reparando el número y la
     etiqueta a mano. */
  --ink:       var(--ink-od);
  --ink-mute:  var(--ink-od-mute);
  --line:      var(--line-od);
  --accent-fg: var(--accent);
  color: var(--ink);
}
/* Icono real de PadelSapiens al mismo tamaño que el arco (petición de
   Sergio: "el logo y el círculo tienen que tener el mismo tamaño"). */
.nota-ring-row { display: flex; align-items: center; gap: var(--sp-3); }
.nota-ring-logo { display: block; width: 50px; height: 50px; flex-shrink: 0; }
.nota-ring-circle { position: relative; width: 50px; height: 50px; line-height: 0; flex-shrink: 0; }
.nota-ring-circle svg { display: block; transform: rotate(-90deg); }
/* Colores fijos, NO dependientes del token de tema (--accent-fg/--line-2
   cambian entre claro/oscuro para leerse sobre el fondo DE LA PÁGINA) —
   esta tarjeta es siempre oscura en los dos temas, así que el arco usa
   --accent (lima puro, nunca se oscurece) y la pista un blanco translúcido
   fijo, igual que ya hace `.nota-ring-num`/`-label` de abajo. Bug real
   corregido 2026-08-20: con --accent-fg el arco se veía verde oscuro
   apagado en tema claro en vez de lima. */
.nota-ring-track { fill: none; stroke: var(--line-od); stroke-width: 6; }
.nota-ring-fill { fill: none; stroke: var(--accent); stroke-width: 6; stroke-linecap: round; transition: stroke-dashoffset var(--t-slow) var(--ease-out); }
.nota-ring-num {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  font-family: var(--mono); font-weight: 700; font-size: var(--fs-body-s); color: var(--ink);
}
/* Tema claro: el número y la etiqueta ya no pueden usar --ink/--ink-mute
   normales (se oscurecen en claro, pero la tarjeta sigue siendo oscura) —
   mismo criterio que `.score-card-nota-wordmark`/`-eyebrow`, colores fijos
   pensados para fondo oscuro en los dos temas. El arco (`-track`/`-fill`)
   NO necesita override: `var(--line-2)`/`var(--accent-fg)` del tema por
   defecto ya son correctos sobre esta tarjeta siempre oscura — supera a la
   versión "tono-sobre-tono" de 4 rondas del 2026-08-20 (pensada para un
   arco suelto sobre el fondo CLARO de la página, contexto que ya no
   aplica con la tarjeta nueva). Ver [[nota_lima_tema_claro_backlog]]. */

.nota-ring-label { display: block; margin-top: .4rem; font-size: var(--fs-micro); color: var(--ink-mute); text-transform: uppercase; letter-spacing: .02em; }


/* Acciones secundarias compactas (comparar/compartir) — icono en círculo +
   etiqueta debajo, para no competir en peso visual con el CTA de compra. */
/* La chapa de la nota comparte fila con Comparar y Compartir (2026-09-04, a
   petición de Sergio). Antes iba al lado del H1, en `.product-hero-titlebar`,
   y le robaba ancho justo al elemento que más lo necesita: "Adidas Arrow Hit
   Pro EDT 2026 Ari Sánchez" se partía en cinco líneas. Aquí abajo el título
   recupera la columna entera y la chapa se alinea con dos elementos de su
   mismo peso visual en vez de con el titular. */
.product-hero-quickactions { display: flex; align-items: center; gap: var(--sp-7); margin-top: 1.3rem; }
/* Empujada al extremo derecho: mantiene la posición que ya ocupaba en el hero
   —el ojo la sigue encontrando donde estaba— y deja los dos botones agrupados
   a la izquierda con su propio ritmo. */
.product-hero-quickactions .nota-ring { margin-left: auto; }
.quickaction {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-2);
  background: none; border: none; padding: 0; cursor: pointer;
  color: var(--ink-soft); text-decoration: none; font-family: inherit;
}
/* Mismo material y peso que la chapa de la nota, con la que ahora comparten
   fila (2026-09-04, Sergio: "los pondría grises y más grandes para que estén un
   poco más acordes a la chapa"). Antes eran círculos casi transparentes de
   2,6 rem sobre `--surface`, y al lado de una chapa de 103 px se leían como un
   elemento de otra categoría.
   Al ser oscuros en los dos temas —igual que `.nota-ring` y el resto de
   chapas— el icono pasa a color fijo claro; la etiqueta NO, porque vive fuera
   del círculo, sobre el fondo de la página. */
.quickaction-icon {
  width: 3.25rem; height: 3.25rem; border-radius: 999px;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-2); border: 1px solid var(--line); color: var(--ink);
  transition: border-color var(--t-micro) var(--ease-out), color var(--t-micro) var(--ease-out), background-color var(--t-micro) var(--ease-out);
}
:root[data-theme="light"] .quickaction-icon { background: var(--metal-row); border-color: transparent; color: var(--ink-od); }
/* Lima puro en el hover, no `--accent-fg`: sobre el círculo oscuro el verde
   apagado al que ese token se degrada en tema claro no se vería. */
.quickaction:hover .quickaction-icon { border-color: var(--accent); color: var(--accent); }
.quickaction-label { font-size: var(--fs-label); color: var(--ink-mute); }

/* Pros y contras como dos tarjetas independientes, no una lista corrida —
   inspirado en el repaso de un competidor (2026-08-08): más fácil de
   escanear que una lista corrida. */
.proscontras-grid { display: grid; grid-template-columns: 1fr; gap: var(--sp-5); }
.proscontras-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: var(--sp-6) var(--sp-6);
}
.proscontras-card h3 { font-size: var(--fs-body); margin-bottom: 1rem; }
.proscontras-card .mini-list { margin: 0; }

@media (min-width: 640px) {
  .proscontras-grid { grid-template-columns: 1fr 1fr; }
}
@media (min-width: 720px) {
  .product-hero-grid { grid-template-columns: minmax(220px, 360px) 1fr; }
}

/* =============================================================
   14. Panel de filtros (facetas) y paginación de listados
   ============================================================= */
.section-head--tight { margin-bottom: 1.75rem; }

/* Caja contenedora del bloque de filtros — mismo lenguaje visual que el
   resto de tarjetas del sitio (surface + line + radius), para que el
   filtro se lea como una herramienta, no como una lista de enlaces suelta.

   Pasada de diseño 26-08-26 (Sergio: "funcionalmente parece que está
   mejor, pero el filtro no es bonito"): la caja era plana —mismo borde y
   mismo relleno que cualquier tarjeta de contenido— y los controles de
   dentro eran píldoras fantasma (solo borde) todas del mismo peso, así que
   nada guiaba la mirada. Ahora la caja tiene elevación propia y un filo de
   acento arriba que la marca como herramienta, y los controles pasan a
   tener relleno real (ver `.facet-chip-summary`) para leerse como botones
   pulsables y no como etiquetas. */
/* OJO: nada de `overflow: hidden` aquí. Los paneles de cada desplegable
   (`.facet-chips`) son `position: absolute` y se salen por abajo del panel
   a propósito — con overflow recortado quedaban cortados a media altura y
   el filtro dejaba de funcionar en la práctica (bug real introducido y
   cazado por Sergio en captura el 26-08-26: "parece que el filtro está
   roto, no funciona ni en lista ni en mosaico").

   Se probó y se DESCARTÓ un filo lima de 2px en el borde superior (la firma
   de acento que sí funciona en `.sapi-promo`/`.compara-explainer`): ahí es
   un borde completo alrededor de una pieza promocional, pero aquí, como
   línea suelta sobre el borde de una caja blanca, se leía como un artefacto
   de render y no como diseño ("parece que el filtro tiene una raya lima
   arriba"). La elevación y el relleno de los controles ya hacen que el
   panel se lea como herramienta; no le hace falta acento propio. */
.facet-panel {
  position: relative;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: calc(var(--radius) + 4px);
  padding: var(--sp-6) var(--sp-7); margin-bottom: 2.5rem;
  box-shadow: var(--sh-2);
}
.facet-panel-head {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3) var(--sp-5);
  margin-bottom: 1.2rem; padding-bottom: 1.2rem; border-bottom: 1px solid var(--line);
}
.facet-panel-kicker { margin-bottom: 0; }
/* El contador deja de ser texto gris de relleno y pasa a ser el dato que
   es: mono + tabular (el JS del slider lo reescribe en vivo, y sin
   `tabular-nums` el ancho saltaba a cada arrastre) sobre una chapa suave.
   OJO: `initPriceRange()` reescribe su `textContent` entero, así que este
   elemento no puede tener hijos — todo el estilo vive en el propio nodo. */
.facet-result-count {
  font-family: var(--mono); font-size: var(--fs-caption); font-weight: 600;
  color: var(--ink-soft); font-variant-numeric: tabular-nums;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: 999px; padding: var(--sp-1) var(--sp-4);
}
.facet-clear { margin-left: auto; }

/* Buscador de texto libre por categoría (2026-08-19) — vive en la misma
   cabecera que el contador y "quitar filtros", entre ambos. Con
   `.facet-clear` ya usando `margin-left: auto` para pegarse a la derecha,
   este flex item se queda tal cual en medio del flujo, sin `auto` propio. */
.facet-search { position: relative; flex: 1 1 13rem; max-width: 20rem; min-width: 10rem; }
.facet-search-field {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-5); border: 1px solid var(--line-2); border-radius: 999px;
  background: var(--surface-2);
  transition: border-color var(--t-fast) var(--ease-out), box-shadow var(--t-fast) var(--ease-out);
}
/* Anillo de foco además del cambio de borde: el buscador es la acción
   principal del panel y con solo 1px de borde lima el foco se perdía
   entre seis píldoras del mismo tamaño. */
.facet-search:focus-within .facet-search-field {
  border-color: var(--accent); box-shadow: var(--ring-focus);
}
.facet-search-icon { color: var(--ink-mute); flex-shrink: 0; }
.facet-search:focus-within .facet-search-icon { color: var(--accent-fg); }
.facet-search-input {
  flex: 1; min-width: 0; border: none; background: transparent; outline: none;
  color: var(--ink); font-family: var(--sans); font-size: var(--fs-meta);
}
.facet-search-input::placeholder { color: var(--ink-mute); }
.facet-search-input::-webkit-search-cancel-button { display: none; }
/* Desplegable flotante, mismo criterio visual que `.facet-chips` (superficie
   elevada + sombra). Nace anclado a la izquierda del buscador pero NO atado a
   su ancho (2026-08-30): el campo mide 20rem como mucho y ahí el nombre del
   producto se quedaba en 140px, así que "Bullpadel Pearl 2027 Bea González"
   salía cortado a la mitad. El campo sigue igual —es un filtro, no debe
   comerse la fila de chips—; lo que crece es solo el panel, que es flotante y
   no le quita sitio a nada. `max()` para que nunca sea más estrecho que el
   propio campo, y el tope en `vw` para que en pantallas intermedias no se
   salga por la derecha. */
.facet-search-panel {
  position: absolute; top: calc(100% + .5rem); left: 0; right: auto; z-index: 25;
  width: max(100%, 30rem); max-width: min(34rem, calc(100vw - 3rem));
  background: var(--surface-2); border: 1px solid var(--line-2); border-radius: var(--radius-sm);
  box-shadow: var(--sh-3);
  max-height: 22rem; overflow-y: auto;
}
.facet-search-panel[hidden] { display: none; }
.facet-search-status {
  margin: 0; padding: var(--sp-5) var(--sp-5); text-align: center;
  color: var(--ink-mute); font-size: var(--fs-meta); line-height: 1.5;
}
/* `.search-panel-results`/`.search-result*` (buscador de cabecera) se
   reutilizan tal cual para las filas — mismo shape de dato, mismo aspecto,
   cero CSS nuevo para thumb/nombre/precio. Este único ajuste de padding es
   porque el panel aquí es más estrecho que el modal de cabecera. */
.facet-search-results.search-panel-results { padding: var(--sp-2); }
/* Dos líneas para el nombre, solo en este desplegable (2026-08-30). Con el
   panel a 30rem entran de sobra los nombres normales, pero la cola larga del
   catálogo no cabe en una línea a ningún ancho razonable para un desplegable:
   el más largo son 67 caracteres ("Wilson Bela V3 Super Tour Padel Black/Red
   2025 Fernando Belasteguin") y hay 144 palas cuyo nombre incluye marca, año
   y jugador. El modal del metabuscador de cabecera es más ancho y no lo
   necesita, por eso el ajuste va acotado a `.facet-search-results`. */
.facet-search-results .search-result-name {
  white-space: normal; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  line-height: 1.3;
}
/* Franja intermedia (tablet en vertical): el campo sigue midiendo 20rem pero
   arranca ya muy a la derecha de su fila, así que un panel de 30rem anclado a
   la IZQUIERDA se sale por el borde. Medido a 741px: el panel terminaba justo
   en el píxel del borde, y por debajo de ahí lo pasaba. La solución no es
   estrecharlo —volveríamos a cortar el nombre— sino anclarlo por la derecha:
   crece hacia dentro desde el borde derecho del campo, que por construcción
   siempre está dentro del contenedor. */
@media (max-width: 820px) {
  .facet-search-panel { left: auto; right: 0; }
}
@media (max-width: 640px) {
  .facet-search { flex: 1 1 100%; max-width: none; order: 3; }
  /* Aquí el campo ya ocupa el ancho entero, así que el panel vuelve a
     pegarse a sus dos bordes: ensancharlo solo lo sacaría de la pantalla. */
  .facet-search-panel { left: 0; right: 0; width: auto; max-width: none; }
  /* Sin ancho que repartir, el sitio sale de la miniatura: bajándola de
     3.6rem a 2.6rem el nombre gana 32px y con tres líneas entran los 8
     resultados enteros (medido: 7 de 8 cortados antes, 0 después). La fila
     además queda MÁS baja que antes, porque el alto lo marcaba la miniatura
     y no el texto. */
  .facet-search-results .search-result-thumb { width: 2.6rem; height: 2.6rem; }
  .facet-search-results .search-result-link { gap: var(--sp-3); padding: var(--sp-3); }
  .facet-search-results .search-result-name { -webkit-line-clamp: 3; }
}

/* Fila de desplegables (uno por dimensión), no una rejilla de listas
   siempre abiertas — con 15 marcas posibles, tenerlas todas a la vista
   permanentemente desequilibraba el panel y no se leía premium.

   Dos grupos en la misma fila (26-08-26, "reestructúralo dándole un diseño
   verdaderamente premium a todo el filtrado"): a la izquierda las
   DIMENSIONES del catálogo (Marca/Género/Forma/Temporada/Precio/Nota — qué
   producto quiero), a la derecha las HERRAMIENTAS de presentación (Ordenar
   por + Lista/Mosaico — cómo lo quiero ver). Antes "Ordenar por" era un
   desplegable más al final de la fila, visualmente idéntico a los que sí
   filtran, y el interruptor de vista vivía fuera del panel. El separador
   vertical marca la división sin gastar una segunda cabecera. */
.facet-nav { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--sp-4) var(--sp-5); }
.facet-nav-dims { display: flex; flex-wrap: wrap; gap: var(--sp-4); flex: 1 1 auto; min-width: 0; }
.facet-nav-tools { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-4); margin-left: auto; }
/* En escritorio los dos grupos son DOS COLUMNAS que nunca se rompen entre
   sí (`nowrap` en el contenedor): las dimensiones envuelven por dentro, en
   su propia columna, y las herramientas se quedan ancladas arriba a la
   derecha con el filo vertical recorriendo toda la altura.

   Sin el `nowrap` (26-08-26, captura de Sergio: "al filtrar, rompemos el
   formato del filtro"), en cuanto un filtro se activaba las píldoras
   crecían con su valor dentro —"Género: Hombre", "Nota PadelSapiens: De 6
   a 7"— dejaban de caber, y el grupo de herramientas se caía a una segunda
   línea; con `margin-left: auto` aterrizaba pegado a la derecha, solo, con
   un hueco enorme a su izquierda y el filo vertical colgando de la nada.
   El ancho del panel no puede depender de qué filtro esté activo.

   Por debajo de 900px se deja envolver a propósito: ahí los dos grupos en
   líneas distintas es el layout correcto, y el filo vertical se apaga
   porque entre dos filas apiladas no significaría nada. */
@media (min-width: 900px) {
  .facet-nav { flex-wrap: nowrap; }
  .facet-nav-tools {
    flex-shrink: 0; align-self: stretch;
    padding-left: 1.1rem; border-left: 1px solid var(--line);
  }
}
/* Los desplegables del grupo derecho abren su panel anclado a la DERECHA,
   no a la izquierda como los de dimensiones: pegados al borde del panel,
   un `left: 0` se salía del contenedor y quedaba cortado. */
.facet-nav-tools .facet-chips { left: auto; right: 0; }

/* --- Filtros activos (26-08-26) ---
   Fila propia bajo los controles, separada por la misma línea que la
   cabecera: se lee de arriba abajo como "cuántos hay / cómo filtro / qué
   tengo puesto". Cada ficha lleva su dimensión arriba en versalitas y el
   valor debajo en grande — así el valor se puede leer entero sin alargar
   la píldora del desplegable, que es lo que descuadraba la fila. */
.facet-active {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3) var(--sp-4);
  margin-top: 1.2rem; padding-top: 1.2rem; border-top: 1px solid var(--line);
}
.facet-active-label {
  margin: 0; flex-shrink: 0;
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-mute);
}
.facet-active-list { display: flex; flex-wrap: wrap; gap: var(--sp-3); list-style: none; margin: 0; padding: 0; }
/* Toda la ficha es el enlace de quitar — no solo la ✕: un objetivo de
   ~11px sería un blanco pequeño en táctil, y aquí la ficha entera no tiene
   ninguna otra acción con la que competir. La ✕ se queda como señal visual
   de qué hace al pulsar. */
.facet-active-chip {
  display: inline-flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3) var(--sp-2) var(--sp-4); border-radius: 999px;
  background: var(--accent-soft); border: 1px solid var(--accent);
  text-decoration: none; line-height: 1.15;
  transition: background var(--t-fast) var(--ease-out), transform var(--t-fast) var(--ease-out);
}
.facet-active-chip:hover { background: var(--accent); transform: translateY(-1px); }
.facet-active-dim {
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-mute);
}
.facet-active-value { font-size: var(--fs-meta); font-weight: 700; color: var(--ink); }
/* En hover la ficha se vuelve lima sólido: el texto tiene que pasar a
   tinta oscura o se queda ilegible (`--ink` ya es oscuro en claro, pero en
   tema oscuro es casi blanco sobre lima). */
.facet-active-chip:hover .facet-active-dim,
.facet-active-chip:hover .facet-active-value,
.facet-active-chip:hover .facet-active-x { color: var(--accent-ink); }
.facet-active-x {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.15rem; height: 1.15rem; border-radius: 999px; flex-shrink: 0;
  background: rgba(0,0,0,.08); color: var(--ink-soft);
}
.facet-active-chip:hover .facet-active-x { background: rgba(0,0,0,.16); }
/* La dimensión encima del valor solo cabe apilada a partir de cierto
   ancho; en móvil van en línea para no hacer fichas de dos pisos en una
   columna ya estrecha. */
@media (min-width: 560px) {
  .facet-active-chip { padding: var(--sp-1) var(--sp-2) var(--sp-1) var(--sp-4); }
  .facet-active-chip > .facet-active-dim { align-self: center; }
}

.facet-chip-group { position: relative; }
/* Relleno real (`--surface-2`) en vez de píldora fantasma de solo borde
   (26-08-26): sobre el blanco del panel, seis contornos de 1px del mismo
   gris no se leían como controles pulsables. Con fondo propio + borde
   transparente el control tiene cuerpo, y el hover puede permitirse elevar
   en vez de limitarse a cambiar el color del borde. */
.facet-chip-summary {
  display: inline-flex; align-items: center; gap: var(--sp-3); padding: var(--sp-3) var(--sp-5);
  background: var(--surface-2); border: 1px solid transparent; border-radius: 999px;
  font-size: var(--fs-meta); color: var(--ink-soft); cursor: pointer; user-select: none;
  list-style: none;
  transition: border-color var(--t-fast) var(--ease-out), color var(--t-fast) var(--ease-out), background var(--t-fast) var(--ease-out),
              transform var(--t-fast) var(--ease-out), box-shadow var(--t-fast) var(--ease-out);
}
.facet-chip-summary::-webkit-details-marker { display: none; }
.facet-chip-summary:hover {
  border-color: var(--accent); color: var(--ink);
  transform: translateY(-1px); box-shadow: var(--sh-2);
}
/* Abierto en lima SÓLIDO, no en `--accent-soft` + `--accent-fg` (26-08-26,
   Sergio: "¿podríamos evitar ese verde? no es color de marca"). Ese verde
   (#146B45) es lo que vale `--accent-fg` en tema claro, y existe por un
   motivo real: el lima de marca sobre blanco no se lee como texto. Pero
   aquí el control va RELLENO, así que se puede usar el lima de verdad de
   fondo con `--accent-ink` encima — legible, y el mismo par de tokens que
   `.btn-accent` y que la pastilla activa del interruptor, así que los tres
   estados activos del panel hablan el mismo idioma. */
/* Dimensión CON filtro activo, esté abierta o no — el estado se marca por
   color en vez de metiendo el valor dentro del texto (26-08-26, ver
   facetChips.js): así la píldora conserva su ancho y la fila no se
   recompone al filtrar. Mismo lima que el estado abierto a propósito: las
   dos cosas significan "esta dimensión es la que manda ahora mismo", y
   distinguirlas con dos colores distintos solo añadiría ruido. El chevron
   girado sigue diferenciando abierto de cerrado. */
.facet-chip-group[open] .facet-chip-summary,
.facet-chip-summary.is-active {
  border-color: var(--accent); background: var(--accent); color: var(--accent-ink);
  font-weight: 700; transform: none; box-shadow: none;
}
/* El hover genérico (borde lima + `--ink`) dejaría el texto oscuro sobre
   lima ilegible en las que ya están activas — se mantiene el par de
   colores lima/tinta y solo se eleva. */
.facet-chip-summary.is-active:hover {
  color: var(--accent-ink); transform: translateY(-1px);
  box-shadow: var(--sh-2);
}
.facet-chip-heading { font-weight: 600; }
.facet-chip-chevron { flex-shrink: 0; transition: transform var(--t-fast) var(--ease-out); }
.facet-chip-group[open] .facet-chip-chevron { transform: rotate(180deg); }

/* El panel de chips flota sobre el resto del contenido en vez de empujarlo
   hacia abajo al abrirse — así abrir "Marca" (15 valores) no estira toda la
   fila de desplegables ni desplaza la rejilla de productos. */
.facet-chips {
  position: absolute; top: calc(100% + .6rem); left: 0; z-index: 20;
  display: flex; flex-wrap: wrap; gap: var(--sp-3); width: max-content; max-width: 22rem;
  max-height: 16rem; overflow-y: auto;
  background: var(--surface-2); border: 1px solid var(--line-2); border-radius: var(--radius-sm);
  padding: var(--sp-5); box-shadow: var(--sh-3);
}
/* En móvil, un botón como "Precio" puede quedar muy a la derecha de la fila
   (envuelta en 2 líneas) — un panel flotante anclado a su izquierda se
   saldría de la pantalla. Ahí se abandona el flotante y se empuja el
   contenido hacia abajo en su lugar, a todo el ancho disponible. */
@media (max-width: 640px) {
  .facet-chip-group { position: static; }
  .facet-chips {
    position: static; transform: none; margin-top: .6rem;
    width: 100%; max-width: none; box-shadow: none;
  }
}

/* Menú "Ordenar por" (2026-08-22) — mismo componente <details>/.facet-chips
   que Marca/Forma/Temporada/Precio/Nota, pero pegado a la derecha de la fila
   (`margin-left: auto`, pedido explícito de Sergio: "abajo a la derecha de
   los filtros") y con su panel anclado a la derecha (`right: 0` en vez de
   `left: 0`) para no salirse de la pantalla al ser el último de la fila. */
.facet-sort-group { margin-left: auto; }
.facet-sort-list {
  left: auto; right: 0; flex-direction: column; flex-wrap: nowrap;
  width: max-content; min-width: 13rem; padding: var(--sp-2);
}
.facet-sort-option {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-5);
  width: 100%; padding: var(--sp-3) var(--sp-4); border-radius: var(--radius-sm);
  background: none; border: none; font: inherit; font-size: var(--fs-meta); color: var(--ink-soft);
  text-align: left; cursor: pointer; transition: background-color var(--t-micro) var(--ease-out), color var(--t-micro) var(--ease-out);
}
.facet-sort-option:hover { background: var(--surface-3); color: var(--ink); }

/* --- Opciones de una dimensión, en menú (26-08-26) ---
   Mismo componente visual que "Ordenar por" de aquí arriba: los
   desplegables de Marca/Género/Forma/Temporada/Nota dejan de ser una bolsa
   de chips envueltos en 2-4 columnas irregulares (según lo largo que fuera
   cada valor) y pasan a filas alineadas, con el recuento pegado a la
   derecha y un check en el valor activo. */
.facet-option-list {
  flex-direction: column; flex-wrap: nowrap; align-content: stretch;
  width: max-content; min-width: 14rem; max-width: 22rem;
  max-height: 20rem; padding: var(--sp-2); gap: .1rem;
}
.facet-option {
  display: flex; align-items: center; gap: var(--sp-4);
  width: 100%; padding: var(--sp-3) var(--sp-4); border-radius: var(--radius-sm);
  font-size: var(--fs-meta); color: var(--ink-soft); text-decoration: none;
  transition: background-color var(--t-micro) var(--ease-out), color var(--t-micro) var(--ease-out);
}
.facet-option:hover { background: var(--surface-3); color: var(--ink); }
/* El nombre se queda con todo el ancho sobrante, así el recuento de todas
   las filas cae en la misma columna pase lo que pase con la longitud del
   valor ("Lok" vs "Black Crown"). */
.facet-option-label { flex: 1 1 auto; min-width: 0; }
.facet-option-count {
  flex-shrink: 0; font-family: var(--mono); font-size: var(--fs-label);
  color: var(--ink-mute); font-variant-numeric: tabular-nums;
}
/* El check ocupa su hueco siempre (opacidad 0, no `display:none`) para que
   las etiquetas no se desplacen al cambiar de valor activo. */
.facet-option-check { flex-shrink: 0; color: var(--accent-fg); opacity: 0; }
.facet-option[aria-selected="true"] { color: var(--ink); font-weight: 700; }
.facet-option[aria-selected="true"] .facet-option-check { opacity: 1; }
.facet-option--disabled { color: var(--ink-mute); opacity: .5; cursor: not-allowed; }
.facet-option--disabled:hover { background: none; color: var(--ink-mute); }
.facet-sort-check { flex-shrink: 0; color: var(--accent-fg); opacity: 0; }
.facet-sort-option[aria-selected="true"] { color: var(--ink); font-weight: 600; }
.facet-sort-option[aria-selected="true"] .facet-sort-check { opacity: 1; }
@media (max-width: 640px) {
  .facet-sort-group { margin-left: 0; }
  .facet-sort-list { right: auto; width: 100%; min-width: 0; }
}

/* Slider de rango de precio "Precio" (2026-08-22) — a partir de una
   captura de referencia de Sergio (doble tirador azul, valores debajo).
   Mismo componente <details>/.facet-chips que el resto de grupos, pero el
   panel lleva el slider en vez de una lista de chips: dos <input
   type="range"> nativos superpuestos sobre una pista común (ver
   initPriceRange() en main.js). `--accent-fg` para el tramo activo y los
   tiradores, igual que el resto del panel de filtros (foco/hover de los
   chips ya usan ese mismo tono), así el slider se lee como parte del mismo
   sistema y no como un componente ajeno. */
.facet-price-panel { width: 15.5rem; flex-direction: column; gap: 0; }
.facet-price-track {
  position: relative; height: 4px; margin: 1.5rem .4rem 1.1rem;
  border-radius: 999px; background: var(--line-2);
}
.facet-price-track-fill {
  position: absolute; top: 0; bottom: 0; border-radius: 999px; background: var(--accent-fg);
}
/* Dos inputs ocupan la misma pista, superpuestos — pointer-events:none en
   el input entero (así el que no se toca no "roba" el arrastre del otro) y
   pointer-events:auto solo en el thumb, único punto realmente clicable.
   Truco estándar para un slider de doble tirador sin JS de arrastre propio. */
.facet-price-input {
  position: absolute; top: 50%; left: 0; width: 100%; margin: 0; padding: 0;
  transform: translateY(-50%); background: none; pointer-events: none;
  -webkit-appearance: none; appearance: none;
}
.facet-price-input:focus-visible { outline: none; } /* el foco ya se ve en el propio thumb */
.facet-price-input::-webkit-slider-runnable-track { -webkit-appearance: none; background: none; height: 4px; }
.facet-price-input::-moz-range-track { background: none; height: 4px; border: none; }
.facet-price-input::-webkit-slider-thumb {
  /* Chrome/Safari colocan el thumb pegado al borde superior de la pista
     (`::-webkit-slider-runnable-track`, 4px de alto) salvo que se le indique
     lo contrario con `margin-top` — sin esto el círculo quedaba "colgando"
     medio píxel por debajo de la línea en vez de centrado (captura real de
     Sergio, 2026-08-22). -(alto del thumb − alto de la pista) / 2 = -6px. */
  -webkit-appearance: none; pointer-events: auto; cursor: pointer;
  width: 16px; height: 16px; margin-top: -6px; border-radius: 50%;
  background: var(--accent-fg); border: 2px solid var(--surface);
  box-shadow: var(--sh-1);
}
.facet-price-input::-moz-range-thumb {
  pointer-events: auto; cursor: pointer; width: 16px; height: 16px; border-radius: 50%;
  background: var(--accent-fg); border: 2px solid var(--surface);
  box-shadow: var(--sh-1);
}
.facet-price-input:focus-visible::-webkit-slider-thumb { box-shadow: var(--ring-focus); }
.facet-price-input:focus-visible::-moz-range-thumb { box-shadow: var(--ring-focus); }
.facet-price-values {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  font-size: var(--fs-meta); font-variant-numeric: tabular-nums; color: var(--ink-soft);
}
.facet-price-values-sep { color: var(--ink-mute); }
@media (max-width: 640px) {
  .facet-price-panel { width: 100%; }
}

.chip {
  display: inline-flex; align-items: center; gap: var(--sp-2); padding: var(--sp-2) var(--sp-4);
  border: 1px solid var(--line-2); border-radius: 999px;
  font-size: var(--fs-meta); color: var(--ink-mute); text-decoration: none;
  transition: border-color var(--t-micro) var(--ease-out), color var(--t-micro) var(--ease-out), background var(--t-micro) var(--ease-out);
}
.chip:hover { border-color: var(--accent-fg); color: var(--ink); }
.chip--current {
  border-color: var(--accent-fg); background: var(--accent-soft); color: var(--accent-fg); font-weight: 600;
}
/* Hover en naranja (mismo código que .compare-slot-remove) para dar a entender
   que, al ser ya la faceta activa, este clic la quita en vez de aplicarla. */
.chip--current:hover { border-color: var(--accent-2); background: var(--accent-2-soft); color: var(--accent-2); }
.chip-x { display: inline-flex; font-size: var(--fs-label); opacity: .8; }
.chip-count { color: inherit; opacity: .6; font-size: .78em; }
/* Valor del filtro sin ningún producto que lo cumpla hoy — se muestra para
   transmitir el alcance completo del catálogo (marcas, formas...) en vez de
   ocultarse, pero no es clicable. */
.chip--disabled { opacity: .38; cursor: default; }
.chip--disabled:hover { border-color: var(--line-2); color: var(--ink-mute); }

.pager { display: flex; align-items: center; justify-content: center; gap: var(--sp-5); margin-top: 3rem; }
.pager-nums { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.pager-num, .pager-link {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 2.1rem; height: 2.1rem; padding: 0 var(--sp-3);
  border-radius: var(--radius-sm); font-family: var(--mono); font-size: var(--fs-meta);
  color: var(--ink-mute); text-decoration: none;
}
.pager-num:hover, .pager-link:hover:not(.is-disabled) { color: var(--ink); background: var(--surface); }
.pager-num.is-current { background: var(--accent); color: var(--accent-ink); font-weight: 700; }
.pager-link.is-disabled { color: var(--ink-disabled); pointer-events: none; }

/* =============================================================
   15. Comparador de palas (Fase 3)
   ============================================================= */
/* --- Barra "Estás comparando": 2-4 slots fijos, siempre visibles --- */
.compare-slots-label {
  margin-top: 2.2rem; font-family: var(--mono); font-size: var(--fs-label);
  text-transform: uppercase; letter-spacing: .04em; color: var(--ink-mute);
}
.compare-slots { display: grid; gap: var(--sp-4); grid-template-columns: repeat(2, 1fr); margin-top: .8rem; }
/* Apilado de base y en fila cuando hay sitio (07-09-26). Con la miniatura en
   retrato, a 375 px el chip mide 161 y la foto se llevaba 67: al nombre le
   quedaban 15 px, o sea nada. Antes de agrandar la foto le quedaban 28, que
   tampoco servían — el chip en fila nunca funcionó a dos columnas en móvil.
   Apilado, el nombre recupera el ancho entero del chip. */
/* El chip elegido habla el MISMO idioma que el dashboard (07-09-26, Sergio:
   "cambiaría la estética de la parte de arriba para hacerla igual de premium
   que luego las puntuaciones y comparaciones"). Antes era `--surface` —blanco
   en tema claro— con filo lima: la mitad de arriba de la página iba en un
   material y la de abajo en otro, y eran la misma pantalla.

   Ahora es la chapa, con sus tokens on-dark, igual que `.dash-celda`. El filo
   lima se va: lo sustituye la LÍNEA del color de su serie, que es la misma
   señal que identifica a esa pala en su columna, su barra y su radar. Así se
   sabe qué color le ha tocado a cada una antes de bajar. */
.compare-slot {
  position: relative; display: flex; flex-direction: column; align-items: center;
  gap: var(--sp-3); min-width: 0; text-align: center;
  padding: var(--sp-5) var(--sp-4); border-radius: var(--radius-sm);
}
/* Los tokens on-dark van SOLO en la variante LLENA, no en el elemento base.
   Primero se pusieron en la base y se intentó deshacerlos en el hueco con
   `initial`, y eso no devuelve el valor de página: invalida la variable, así
   que `color: var(--ink-mute)` se quedaba sin valor y "Añadir pala" salía en
   negro sobre el fondo oscuro — 1,09 medido. Con un `:not()` no hay nada que
   deshacer. */
.compare-slot:not(.compare-slot--empty) {
  background: var(--metal-row); border: 1px solid var(--line-od-2);
  --ink:       var(--ink-od);
  --ink-soft:  var(--ink-od-soft);
  --ink-mute:  var(--ink-od);
  --line:      var(--line-od);
  --line-2:    var(--line-od-2);
  --accent-fg: var(--accent);
  color: var(--ink);
}
/* SIN color de serie en el chip (07-09-26, Sergio: "no quiero que mostremos
   los colores en el filtro"). Se probó una línea con el color que esa pala
   tendrá abajo, para saberlo antes de bajar, y no convenció: arriba se elige,
   abajo se compara, y meter aquí el código de color adelanta ruido que todavía
   no significa nada. */

/* El hueco vacío NO lleva chapa: es el negativo del chip lleno, y esa
   diferencia de material es la que dice de un vistazo cuántos sitios quedan.
   Con los dos en chapa había que leer el texto para distinguirlos. */
/* El hueco vacío NO es on-dark, así que DESHACE los tokens que hereda del chip
   lleno. Sin esto, su punteado salía en `--line-od-2` —un velo BLANCO al 38 %—
   sobre el fondo crema de la página: invisible. Era lo que Sergio vio como
   "el hueco de las palas se ve fatal", y lo había metido yo al poner la chapa.

   Es el mismo error de siempre con este primitivo: los tokens on-dark valen
   para lo que va SOBRE oscuro, y una variante transparente del mismo
   componente no lo está. */
.compare-slot--empty {
  justify-content: center; gap: var(--sp-3);
  background: transparent; border: 1px dashed var(--line-2); color: var(--ink-soft);
  font-size: var(--fs-meta); cursor: pointer; box-shadow: none;
  transition: border-color var(--t-micro) var(--ease-out), color var(--t-micro) var(--ease-out), background-color var(--t-micro) var(--ease-out);
}
/* En reposo lleva `--surface-3`, no `--surface-2`: sin fondo el hueco era un
   rectángulo de aire con un punteado al 20 % y no se leía como un sitio donde
   falta algo, y con `--surface-2` tampoco —medido, 1,05 contra la página en
   tema claro, o sea invisible—. `--surface-3` (#e6e3d8 en claro) sí es un tono
   de verdad y el hueco se ve. El hover va hacia `--surface-2`, más claro, y el
   peso lo lleva el borde: así el estado se nota sin que la caja dé un salto.

   Y el hover en gris, no en verde: `--accent-fg` en tema claro es #146B45, un
   verde oscuro, y un recuadro punteado verde no se parece a nada más de la
   página. Mismo criterio que el foco del campo, dos reglas más arriba. */
.compare-slot--empty { background: var(--surface-3); }
.compare-slot--empty:hover { border-color: var(--ink-mute); color: var(--ink); background: var(--surface-2); }
/* Caja en RETRATO, no cuadrada (07-09-26, Sergio: "las palas prácticamente no
   se ven"). Las fotos son PNG cuadrados de 1000x1000, pero una pala es alta y
   estrecha: medida sobre 19 fichas, ocupa el 89 % del alto y solo el 52 % del
   ancho. En una caja cuadrada con `contain`, la foto escala por el ANCHO, así
   que la pala se quedaba en unos 23x39 px reales dentro de 45x45 de caja —
   casi la mitad del recuadro era margen transparente.

   En retrato con `cover` escala por el ALTO y el recorte se lleva margen
   vacío, no pala. */
.compare-slot-thumb { width: 4.2rem; height: 5rem; flex-shrink: 0; }
.compare-slot-thumb .ph, .compare-slot-thumb .ph--square { width: 100%; height: 100%; border-radius: var(--radius-sm); }
/* .ph:has(img) trae padding:1rem pensado para las tarjetas grandes del
   catálogo — a este tamaño mini se comía casi toda la foto (44px de caja,
   imagen real de ~10px, Sergio 2026-08-24: "aparecen extremadamente
   pequeñas"). Padding reducido + sombra más discreta, proporcional a un
   icono en vez de una tarjeta. */
/* Sin padding y con `cover`: la foto llena la caja y el recorte lateral se
   queda en el 84 % del ancho. Ese 84 no es a ojo — se midió el margen
   transparente de 60 palas y el peor caso es la Nox AT10 Genius 18K, que no
   está centrada (12,5 % por la izquierda contra 24,5 % por la derecha) y
   admite como mucho un recorte al 75 %. Con 84 quedan 4,5 puntos de holgura
   sobre el peor caso conocido. Si algún día entra una foto con la pala más
   ancha o más descentrada, hay que volver a medir antes de apretar esto.

   La sombra se va: con la foto a sangre no tiene dónde caer y `overflow:
   hidden` la cortaría contra el filo. */
.compare-slot-thumb .ph:has(img) { padding: 0; }
.compare-slot-thumb .ph:has(img) img { object-fit: cover; filter: none; }
/* `width: 100%` además de `min-width: 0`: en la versión apilada el contenedor
   es una columna flex, que NO limita el ancho de sus hijos, así que el nombre
   —que va con `nowrap`— estiraba el cuerpo hasta 281 px dentro de un chip de
   161 y se salía por los lados en vez de recortarse con puntos suspensivos. */
.compare-slot-body { display: flex; flex-direction: column; gap: .1rem; min-width: 0; width: 100%; flex: 1; }
/* El nombre entero, en varias líneas si hace falta: al quitar el precio el
   chip tiene sitio, y saber CUÁL es la pala es justo lo único que ese control
   tiene que decir. Antes se recortaba con puntos suspensivos y dos versiones
   de la misma pala se veían idénticas. */
.compare-slot-name { font-size: var(--fs-meta); font-weight: 600; color: var(--ink); line-height: 1.3; }
.compare-slot-remove {
  flex-shrink: 0; width: 1.6rem; height: 1.6rem; display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px; border: 1px solid var(--line-2); background: transparent; color: var(--ink-mute);
  transition: border-color var(--t-micro) var(--ease-out), color var(--t-micro) var(--ease-out);
}
.compare-slot-remove:hover { border-color: var(--accent-2); color: var(--accent-2); }

.compare-warning { margin-top: 1rem; font-size: var(--fs-meta); color: var(--accent-2); }
.compare-warning[hidden] { display: none; }
.compare-empty { padding: var(--sp-6); text-align: center; color: var(--ink-mute); font-size: var(--fs-body-s); }
.compare-empty[hidden] { display: none; }

/* --- Estado vacío: <2 palas seleccionadas, dashboard/tabla ocultos --- */
.compare-empty-state {
  margin-top: 2.5rem; padding: var(--sp-8) var(--sp-6); text-align: center;
  border: 1px dashed var(--line-2); border-radius: var(--radius-sm);
  color: var(--ink-mute); font-size: var(--fs-body-s);
}
.compare-empty-state[hidden] { display: none; }

/* --- Buscador --- */
.compare-search { position: relative; margin-top: 1.8rem; }
.compare-search-icon { position: absolute; top: 50%; left: 1.1rem; transform: translateY(-50%); color: var(--ink-mute); pointer-events: none; }
/* El campo y su desplegable, en la MISMA chapa que los chips (07-09-26,
   Sergio: "el propio despliegue del buscador también se ve mal"). Se veía mal
   por algo concreto: al pasar los chips a chapa, el buscador se quedó en
   blanco justo debajo, así que la mitad de arriba de la pantalla tenía dos
   materiales peleándose a dos centímetros uno del otro. Ahora la zona de
   selección entera es una sola pieza. */
.compare-search-input {
  width: 100%; padding: var(--sp-4) var(--sp-5) var(--sp-4) var(--sp-9);
  border-radius: 999px; border: 1px solid var(--line-2);
  background: transparent; color: var(--ink); font-size: var(--fs-body-s);
}
/* Transparente y no en chapa (07-09-26, Sergio: "el buscador no me gusta en
   gris, lo prefiero transparente"). Se probó en chapa para que la zona de
   selección fuese una sola pieza y quedaba pesado: tres bloques grises
   apilados —chips, campo y desplegable— antes de llegar al primer dato. El
   campo respira sobre el fondo de la página y la chapa se reserva a lo que de
   verdad son piezas: los chips y la lista. */
.compare-search-input::placeholder { color: var(--ink-mute); }
/* El foco marca en GRIS, no en lima (07-09-26, Sergio: "el contorno del
   buscador yo lo pondría gris si acaso"). Sigue siendo un salto claro y
   perfectamente visible —de un filo al 20 % de negro a un gris sólido— pero
   deja de gritar en un campo que está enfocado casi todo el rato en esta
   página: aquí se entra a buscar. El lima se reserva para lo que de verdad es
   una acción de marca. */
.compare-search-input:focus { border-color: var(--ink-mute); outline: none; }
.compare-search-input:focus + .compare-search-icon,
.compare-search:focus-within .compare-search-icon { color: var(--accent-fg); }

/* --- Lista de resultados: desplegable tipo autocompletar, solo con texto en el
   buscador (vacío = nada que mostrar); compacta y con scroll interno, escala a
   cualquier tamaño de catálogo sin rediseñar. */
/* EN FLUJO, no flotando (07-09-26, Sergio: "el filtro pisa a las palas que hay
   debajo"). Iba en `position: absolute`, que es lo normal en un desplegable de
   autocompletar —abre sobre el contenido y no mueve nada—, pero aquí lo que
   hay justo debajo es la comparación entera: al abrirse tapaba las fotos de
   las palas que estás comparando, que es lo último que debería taparse en esta
   página. En flujo, la página crece y no se oculta nada. El salto de layout es
   el precio, y es un precio pequeño: el buscador es un control principal de
   esta pantalla, no un accesorio pasajero. */
.compare-list-wrap {
  margin-top: .5rem;
  max-height: 22rem; overflow-y: auto;
  border: 1px solid var(--line-od-2); border-radius: var(--radius-sm); background: var(--metal-row);
  box-shadow: var(--sh-3);
  --ink:       var(--ink-od);
  --ink-soft:  var(--ink-od-soft);
  /* Tinta plena también en la meta (marca · precio): `--ink-od-mute` es blanco
     al 90 % y sobre la chapa clara da 3,90, por debajo del 4,5 que pide un
     texto de 12,5 px. */
  --ink-mute:  var(--ink-od);
  --line:      var(--line-od);
  --line-2:    var(--line-od-2);
  /* El resalte de fila va por `--surface-2`, y sobre la chapa CLARA tiene que
     OSCURECER, no aclarar: cualquier velo blanco encima sube la chapa y hunde
     el texto blanco —medido, la fila elegida se quedaba en 3,56—. En oscuro
     hace falta lo contrario, así que el velo se invierte por tema. Mismo
     patrón que el chip del ranking y que la pista de las barras. */
  --surface-2: var(--veil-od-strong);
  --accent-fg: var(--accent);
  color: var(--ink);
}
:root[data-theme="light"] .compare-list-wrap { --surface-2: rgba(0,0,0,.18); }
/* La fila ya elegida se marca con el RAÍL lima, no con un lavado lima de
   fondo: el lavado aclaraba la chapa (ese 3,56) y además competía con el
   propio check redondo, que ya va en lima sólido. El raíl reutiliza la misma
   receta que `.is-active`, así que no estrena sombra. */
.compare-row:has(.compare-row-input:checked) .compare-row-label {
  background: var(--surface-2); box-shadow: inset 2px 0 0 var(--accent);
}
.compare-list-wrap[hidden] { display: none; }
.compare-list { list-style: none; margin: 0; padding: 0; }
.compare-row { border-bottom: 1px solid var(--line); }
.compare-row:last-child { border-bottom: 0; }
.compare-row[hidden] { display: none; }
/* La fila, en REJILLA y no en flex (07-09-26, Sergio: "mejóralo un poco el
   filtro"). Medida antes de tocar: 1210 px de ancho, filas de 83 px y solo 5
   visibles, con el nombre pegado a la izquierda, el check al extremo derecho y
   medio metro de vacío en medio. Parecía una lista sin terminar.

   Ahora la marca y el precio se van a su propia columna, alineados a la
   derecha: llenan ese hueco con el dato que se compara al elegir, y la fila
   pasa a leerse como una tabla. `.compare-row-info` va en `display: contents`
   para que sus dos hijos entren directos en la rejilla del padre — así no hace
   falta tocar el marcado que genera el build. */
.compare-row-label {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: center; gap: var(--sp-4); padding: var(--sp-3) var(--sp-5); cursor: pointer;
  transition: background-color var(--t-micro) var(--ease-out);
}
.compare-row-info { display: contents; }
.compare-row-meta { text-align: right; white-space: nowrap; }
/* En estrecho, la marca y el precio vuelven DEBAJO del nombre: medido a 375,
   en la misma línea se recortaban cinco de cada cinco. La miniatura y el check
   ocupan las dos filas para que sigan centrados frente al bloque de texto. */
@media (max-width: 600px) {
  /* Selectores con el padre delante a propósito: las reglas base de
     `.compare-row-name` y `.compare-row-meta` están MÁS ABAJO en el fichero,
     así que con la misma especificidad ganaban ellas por orden y el nombre
     seguía en `nowrap` — medido, cuatro de cinco recortados a 375. */
  .compare-row-label { grid-template-columns: auto minmax(0, 1fr) auto; row-gap: .15rem; }
  .compare-row-thumb { grid-row: 1 / 3; }
  /* El nombre se PARTE en vez de recortarse con puntos suspensivos: en un
     desplegable de selección, "Adidas Arrow Hit Carbon…" y "Adidas Arrow Hit
     Carbon Ctrl…" se leen idénticos, que es justo el error que se va a
     cometer. Dos líneas caben; una ambigüedad, no. */
  .compare-row-label .compare-row-name { grid-column: 2; grid-row: 1; white-space: normal; overflow: visible; }
  .compare-row-label .compare-row-meta { grid-column: 2; grid-row: 2; text-align: left; white-space: normal; }
  .compare-row-check { grid-column: 3; grid-row: 1 / 3; }
}
.compare-row-label:hover { background: var(--surface-2); }
.compare-row-label.is-active { background: var(--surface-2); box-shadow: inset 2px 0 0 var(--accent); }
.compare-row-input { position: absolute; opacity: 0; pointer-events: none; }
/* 2,6rem y no 3,6: con la fila ya en rejilla, la miniatura era lo que la
   estiraba a 83 px de alto. A 2,6 la fila baja a ~60 y entran siete palas
   donde antes cabían cinco, que es la diferencia entre elegir de un vistazo y
   tener que hacer scroll dentro de un desplegable. */
.compare-row-thumb { width: 2.6rem; height: 2.6rem; flex-shrink: 0; }
.compare-row-thumb .ph, .compare-row-thumb .ph--square { width: 100%; height: 100%; border-radius: var(--radius-sm); }
/* Mismo motivo que .compare-slot-thumb arriba: sin este override, la foto
   real quedaba aplastada a ~12px dentro de una caja de 44px. */
.compare-row-thumb .ph:has(img) { padding: var(--sp-1); }
.compare-row-thumb .ph:has(img) img { filter: drop-shadow(0 2px 4px rgba(0, 0, 0, .35)); }
.compare-row-name { font-size: var(--fs-body-s); font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.compare-row-name mark { background: none; color: var(--accent-fg); font-weight: 700; }
.compare-row-meta { font-family: var(--mono); font-size: var(--fs-caption); color: var(--ink-mute); }
.compare-suggest-label {
  margin: 0; padding: var(--sp-3) var(--sp-5) var(--sp-1); font-family: var(--mono); font-size: var(--fs-label);
  text-transform: uppercase; letter-spacing: .04em; color: var(--ink-mute);
}
.compare-suggest-label[hidden] { display: none; }
/* Antes esta sección tenía su propia cabecera "kicker + h2 grande + sub",
   igual que el hero de arriba — con el h1 de la página justo encima, leía
   como un segundo título compitiendo con el primero. Ahora es una fila
   compacta: etiqueta de sección a la izquierda, acción a la derecha —
   mismo lugar que ocuparía el botón de compartir, pero integrado en vez
   de flotar solo debajo de un párrafo. */
.dashboard-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  flex-wrap: wrap; gap: var(--sp-5) var(--sp-6); margin-bottom: 2.2rem;
}
.dashboard-head-text .kicker { margin-bottom: .4rem; }
.dashboard-title { margin: 0; font-size: clamp(1.3rem, 2.2vw, 1.7rem); }
.compare-share-btn { flex-shrink: 0; }
.compare-row-check {
  flex-shrink: 0; width: 1.4rem; height: 1.4rem; border-radius: 999px; border: 1px solid var(--line-2);
  display: inline-flex; align-items: center; justify-content: center; color: var(--accent-ink);
  transition: background-color var(--t-micro) var(--ease-out), border-color var(--t-micro) var(--ease-out);
}
.compare-row-check svg { opacity: 0; transition: opacity var(--t-micro) var(--ease-out); }
.compare-row:has(.compare-row-input:checked) .compare-row-check { background: var(--accent); border-color: var(--accent); }
.compare-row:has(.compare-row-input:checked) .compare-row-check svg { opacity: 1; }

/* align-items: stretch (el valor por defecto de grid) a propósito: la caja
   del radar y la de las flip-cards casi nunca miden lo mismo por sí solas
   (la leyenda crece con cada pala añadida, las tarjetas crecen por filas de
   2 en 2) — con "stretch" ambas columnas quedan siempre a la misma altura,
   la del contenido más alto, y el lado corto centra su contenido dentro
   (ver justify-content/align-content más abajo) en vez de dejar un hueco
   vacío pegado abajo. SOLO para el comparador multi-pala
   (comparadorPalas.js) — el dashboard de una pala suelta usa
   `.producto-puntuaciones` más abajo, clase distinta a propósito para no
   arrastrar este layout donde no aplica. */
/* El dashboard es UNA rejilla y cada FILA es un tema: leyenda/palas, radar de
   ejes/ejes, radar de orientación/orientación. Antes eran dos tarjetas
   independientes y sus contenidos no se alineaban porque nada los obligaba.
   Aquí la alineación es por construcción: dos celdas de la misma fila miden lo
   mismo. `align-items: stretch` es justamente lo que se busca ahora. */
.puntuaciones-dashboard {
  /* Sin caja: el dashboard es SOLO la rejilla. Las tarjetas son las celdas
     (07-09-26, Sergio: "quitar la caja del fondo que está en gris metalizado y
     dejar únicamente las cajas principales"). */
  display: grid; gap: var(--sp-4); grid-template-columns: 1fr; align-items: stretch;
}
/* Cada celda es un panel rebajado, el mismo lenguaje que ya tenían los grupos
   de filas. La celda vacía —la que acompaña a la ficha técnica— no pinta nada:
   está para que la rejilla no descoloque las columnas. */
/* Metalizado en LOS DOS temas (07-09-26, Sergio: "el acabado gris metalizado
   me gusta mucho más y es el que estamos introduciendo en producción, es la
   imagen de marca"). En oscuro la celda era `--surface`, un gris plano: el
   acabado solo aparecía en tema claro, justo al revés de lo que pide una
   imagen de marca. El degradado de `--metal-elegida` es idéntico en los dos
   temas, así que basta con ponerlo aquí; la regla de tema claro de más abajo
   sigue haciendo falta, pero por los tokens de tinta on-dark, no por el fondo.

   Va en `--metal-row`, la chapa del sitio (07-09-26, Sergio: "quiero el gris
   claro con el que estábamos trabajando, lo has vuelto a oscurecer"). Es el
   mismo token que el resto de chapas: metalizado CLARO en tema claro, casi
   negro en oscuro. */
.dash-celda {
  --col-valor: 2.6rem;
  background: var(--metal-row); border-radius: var(--radius-sm);
  padding: var(--sp-6);
  display: flex; flex-direction: column; justify-content: center; min-width: 0;
  --cols: var(--label-col, 7.5rem) repeat(var(--n, 4), minmax(0, 1fr));
}
/* `justify-content: center` va en TODAS las celdas, no solo en la leyenda: dos
   celdas de la misma fila miden lo mismo pero su contenido no, así que la que
   tiene menos se quedaba pegada arriba con el sobrante colgando abajo —medido,
   90 px en la fila de ejes y 151 en la de orientación, porque tiene cinco
   filas en vez de siete—. Centrado, cada bloque de datos queda a la altura del
   centro de su radar. */
.dash-celda.radar-uno { align-items: center; gap: var(--sp-4); }
/* Misma "caja" que las flip-cards (superficie + borde + radio) para que el
   radar no quede como un bloque suelto al lado de tarjetas con chrome —
   esa asimetría era buena parte de por qué el dashboard no se veía premium.
   Compartida entre el comparador multi-pala y cada sección del dashboard
   de una pala suelta (`.puntuaciones-grupo-body` más abajo). */
.radar-wrap {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--sp-6);
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: var(--sp-7) var(--sp-6);
}
.radar-svg { width: 100%; max-width: 380px; height: auto; overflow: visible; }
.radar-ring { fill: none; stroke: var(--line-2); stroke-width: 1; }
.radar-axis { stroke: var(--line-2); stroke-width: 1; }
/* 12px y no 10 (07-09-26). Estas medidas son unidades de viewBox, no píxeles:
   lo que se ve es `font-size x (ancho renderizado / ancho del viewBox)`. Al
   ensanchar el viewBox de 340 a 380 para que "Juego de fondo" cupiera dentro,
   los 10px pasaron a verse como 8,8 — demasiado pequeño sobre la chapa, donde
   además el contraste va justo. Con 12 se ven a ~10,5. */
.radar-axis-label { fill: var(--ink-mute); font-size: 12px; font-family: var(--sans); }
.radar-data { fill-opacity: .16; stroke-width: 2; }
.radar-legend { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-4) var(--sp-6); list-style: none; margin: 0; padding: 0; }
.radar-legend-item { display: inline-flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-meta); color: var(--ink-soft); }
.radar-legend-swatch { width: .65rem; height: .65rem; border-radius: 999px; flex-shrink: 0; }
/* En el dashboard la leyenda va en LISTA, una pala por línea (07-09-26,
   Sergio: "arriba, a la altura de las palas, deberían estar en formato lista
   los títulos"). El flex horizontal con envoltura vale cuando la leyenda
   cuelga de un radar suelto; aquí ocupa la celda que hace pareja con la
   cabecera de palas, así que una línea por pala es lo que la hace legible y lo
   que la empareja con las cuatro columnas de enfrente. */
.dash-celda--leyenda .radar-legend {
  flex-direction: column; align-items: flex-start; justify-content: center;
  gap: var(--sp-4); width: 100%;
}
.dash-celda--leyenda .radar-legend-item { font-size: var(--fs-caption); }

/* Radar reversible del comparador multi-pala (2026-08-09, sustituye al
   "mega radar" de 12 ejes: con hasta 4 palas superpuestas se veía
   saturado) — mismo mecanismo de giro que .flip-card (click/Enter,
   rotateY 180°), aplicado encima de .radar-wrap en vez de duplicar sus
   estilos de caja: cara A = radar de 7 ejes (igual que siempre), cara B
   = radar de 5 situaciones, cada una tan limpia como si fuera la única.
   Ver main.js::initRadarFlip()/drawRadarFace(). */
.radar-flip { min-height: 32rem; cursor: pointer; outline-offset: 4px; perspective: 1600px; }
.radar-flip-inner {
  position: relative; width: 100%; height: 100%; min-height: inherit;
  transform-style: preserve-3d; transition: transform var(--t-flip) var(--ease-out);
}
.radar-flip.is-flipped .radar-flip-inner { transform: rotateY(180deg); }
.radar-flip-face { position: absolute; inset: 0; backface-visibility: hidden; }
.radar-flip-back { transform: rotateY(180deg); }

/* Dashboard de puntuaciones de UNA pala (ficha de producto) — tarjeta
   única con clase propia (2026-08-09, corrige el desalineamiento que
   señaló Sergio entre el radar de "Orientación de juego" y sus barras:
   antes vivían en dos columnas independientes que se estiraban cada una
   a su aire; ahora cada sección tiene su radar y sus barras en la misma
   fila de grid, así que quedan alineados por construcción). NO es
   `.puntuaciones-dashboard` — esa la sigue usando solo el comparador
   multi-pala, con su propio layout radar+flip-cards, sin tocar. */
/* Una sola pieza, sin cajas dentro. En tema oscuro es la superficie normal;
   en claro, el gris metalizado de siempre (Sergio 06-09-26: "usemos el gris
   metalizado de producción, que cuadra bien con el lima").
   Se probó un panel casi negro para tener margen de sobra y quedaba duro
   contra la crema — descartado. */
.producto-puntuaciones {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: var(--sp-7);
}
:root[data-theme="light"] .producto-puntuaciones {
  background: var(--metal-row); border-color: transparent;
  box-shadow: var(--edge-top);
}
/* Los tokens se redefinen para el bloque entero, en los dos temas: es una
   pieza oscura siempre, así que su tinta no puede seguir a --ink. */
.producto-puntuaciones {
  --ink:       var(--ink-od);
  --ink-soft:  var(--ink-od-soft);
  --ink-mute:  var(--ink-od-mute);
  --line:      var(--line-od);
  --line-2:    var(--line-od);
  --accent-fg: var(--accent);
  --accent-soft: rgba(198,255,61,.16);
  --radar-1: var(--accent);
  /* #F2CE6B y no el #E3B341 de antes: sobre la chapa metalizada el ámbar
     cerrado se queda en 2,59 y un trazo de gráfico pide 3,0. Este da 3,32 y
     sigue siendo el mismo dorado que hace pareja con el lima (4,27). */
  --radar-2: #F2CE6B;
  color: var(--ink);
}
/* Las etiquetas de las barras van en tinta PLENA, no apagada: sobre esta
   chapa el apagado se queda en 4,00 y son el texto que hay que leer. El
   título del grupo sí puede ir apagado — es una etiqueta de sección. */
.producto-puntuaciones .score-bar-label { color: var(--ink-od); }
/* La nota no es una caja: es la CABECERA del bloque. Y su línea divisoria
   no es una raya aparte — es el propio medidor de la nota, a todo el ancho
   (06-09-26). Antes había una barrita de 96 px suelta bajo el número Y
   además una raya de separación: dos elementos para dos trabajos que hace
   uno. Ahora la parte lima de la línea es el 82 % de una nota de 8,2. */
.producto-puntuaciones .score-card-nota {
  position: relative;
  background: none; border: 0; border-radius: 0;
  padding: var(--sp-1) 0 var(--sp-7); margin-bottom: 0;
  align-items: flex-end;
}
.producto-puntuaciones .score-card-nota-bar {
  position: absolute; left: 0; right: 0; bottom: 0;
  width: auto; height: 2px; border-radius: 999px;
  background: var(--line-od);
}
.producto-puntuaciones .score-card-nota-stat { gap: 0; }
/* Tema claro: el bloque entero a chapa (06-09-26, a petición de Sergio: "el
   verde oscuro no me convence"). Sobre la tarjeta blanca, el lima daba 1,18
   y había que sustituirlo por --accent-fg, el verde oscuro — que sobre las
   manchas grandes del radar y las barras pesaba demasiado. Sobre chapa el
   lima vuelve a ser lima: 7,42.

   Esta regla es el primitivo "on-dark" del informe de auditoría: se
   redefinen los tokens EN EL COMPONENTE y todo lo de dentro hereda, hasta
   el fill/stroke de los SVG. Sin él harían falta una docena de reglas de
   excepción, una por pieza — que es exactamente lo que hacen hoy los otros
   ocho componentes con chapa. */

/* Dos cajas de dentro tiran de --surface, que en tema claro sigue siendo
   BLANCO aunque el padre ya sea chapa. Sin esto, el radar seguía dibujado
   sobre blanco —o sea, el problema original intacto— y la tarjeta de nota
   se quedaba como un rectángulo blanco en mitad de la chapa.

   El escalón va hacia ABAJO (hueco), no hacia arriba (relieve): al
   aclararlo, todo lo que lleva encima perdía contraste — las etiquetas del
   radar se quedaban en 4,08. Rebajado, suben a 5,26 y el ámbar del segundo
   radar pasa de 3,85 a 5,30. Y visualmente encaja mejor: un hueco fresado
   en la placa, no una pegatina encima. */

/* La tarjeta de nota ya traía su propia chapa de antes: dentro de un padre
   que ahora es chapa quedaría chapa sobre chapa y se perdería. La regla de
   arriba la gana por especificidad, pero se deja dicho aquí. */
/* SIN caja (06-09-26, Sergio: "no me gusta como queda, le daría un lavado
   de cara más limpio"). Se probó encajar cada grupo en un hueco fresado y
   salieron cajas dentro de cajas dentro de cajas: tres grises apilados,
   pesado y turbio. Ahora el bloque es UNA pieza y dentro no hay ninguna
   más — lo que separa es el aire y, entre columnas, una línea fina. */
.puntuaciones-grupo-titulo {
  font-family: var(--mono); font-size: var(--fs-label); text-transform: uppercase; letter-spacing: .03em;
  color: var(--ink-mute); margin: 0 0 .85rem;
}
.puntuaciones-grupo-body { display: grid; gap: var(--sp-6); grid-template-columns: 1fr; align-items: center; }
/* Divisor entre secciones — combinador de hermano adyacente a propósito:
   solo aparece ENTRE dos secciones consecutivas (nunca antes de la
   primera, nunca si solo hay una porque falten ejes por puntuar). */
.puntuaciones-grupo + .puntuaciones-grupo {
  margin-top: 1.75rem; padding-top: 1.75rem; border-top: 1px solid var(--line);
}
/* Radar más compacto que el `.radar-wrap` "a secas" del comparador — ahí
   comparte caja con una leyenda de varias palas y necesita más aire; aquí
   vive pegado a sus propias barras, sin nada más dentro de la caja. */
/* Dentro del dashboard, el radar ya NO lleva caja propia: la lleva el grupo
   entero (ver .puntuaciones-grupo). Antes el radar iba encajado y sus
   barras al aire, así que cada grupo se leía como dos piezas sueltas sobre
   una plancha en vez de como un bloque. */
.radar-wrap--compacto {
  padding: var(--sp-1) 0 var(--sp-3); background: none; border: none; border-radius: 0;
}
/* La etiqueta va DENTRO del viewBox (340 unidades), así que su tamaño real
   depende de a cuánto se escale el SVG. En este radar, limitado a 250 px, la
   escala es 0,74 — con las 10 unidades de siempre rendía a 7,4 px, ilegible.
   A 13,5 unidades sale a 10 px reales. No se toca el radar del comparador,
   que se escala distinto. */
.radar-wrap--compacto .radar-axis-label { font-size: 13.5px; }
/* Escritorio: los dos grupos EN PARALELO, cada uno con su radar arriba y
   sus barras debajo (06-09-26, Sergio: "excesivamente grande y se
   desaprovecha muchísimo espacio").

   Antes iban apilados y, dentro de cada uno, radar a la izquierda y barras
   a la derecha. El alto de la fila lo mandaba el radar (308 px) y las
   barras solo llenaban 200 y 157 — o sea ~260 px de hueco muerto, más el
   doble de alto por apilarlos. Puestos en paralelo cada columna se ajusta
   a su propio contenido y no sobra nada.

   El radar se limita a 250 px: su viewBox lleva DENTRO las etiquetas, así
   que agrandarlo o encogerlo las escala con él. A 250 px rinden a ~9,5 px
   (antes 7,8 px, que ya era demasiado pequeño). */
@media (min-width: 860px) {
  .producto-puntuaciones {
    display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-6) var(--sp-7);
    align-items: start;
  }
  .producto-puntuaciones .score-card-nota { grid-column: 1 / -1; margin-bottom: 0; }
  .producto-puntuaciones .puntuaciones-grupo-body { grid-template-columns: 1fr; gap: var(--sp-5); }
  /* ACOTADO a la ficha (07-09-26), mismo caso que el sangrado de aquí abajo:
     sin el prefijo, este tope de 250 px también capaba los radares del
     comparador, que viven en un panel de 380 y se quedaban con 276 px de
     hueco vacío debajo. */
  .producto-puntuaciones .radar-svg { max-width: 250px; }
  /* El separador pasa de horizontal a vertical, y se desvanece por los dos
     extremos en vez de cortar a hueso contra el borde del panel. Un borde
     no admite degradado, así que va como pseudoelemento. */
  /* ACOTADO a .producto-puntuaciones (07-09-26). Sin el prefijo, esta regla
     también le caía a las tarjetas del comparador, donde los dos grupos van
     APILADOS y no en paralelo: "Orientación de juego" y sus barras salían
     sangradas 30,4 px respecto a "Ejes", con una línea divisoria vertical
     colgando de la nada. Sergio: "orientación de juego está descolocado".
     El sangrado y la divisoria solo tienen sentido donde hay dos columnas. */
  .producto-puntuaciones .puntuaciones-grupo + .puntuaciones-grupo {
    margin-top: 0; padding-top: 0; border-top: 0;
    position: relative; padding-left: 1.9rem;
  }
  .producto-puntuaciones .puntuaciones-grupo + .puntuaciones-grupo::before {
    content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 1px;
    background: linear-gradient(to bottom,
      transparent 0%, var(--line-od) 12%, var(--line-od) 88%, transparent 100%);
  }
}

/* La foto de la pala en su tarjeta. Va con `contain` y sin recorte: aquí hay
   ancho de sobra (la tarjeta mide 392 px) y lo que manda es que se vea la pala
   entera, no llenar la caja. A 9.5rem de alto la pala pintada ronda los
   78x134 px, contra los 42x71 del chip de selección y los 23x39 que había al
   empezar la auditoría.

   La sombra es más marcada que en el chip porque aquí la foto SÍ flota sobre
   el metalizado: es lo que la separa del fondo sin necesidad de un marco. */
.score-card-foto { height: 9.5rem; margin-bottom: var(--sp-4); }
.score-card-foto .ph { width: 100%; height: 100%; border-radius: var(--radius-sm); }
.score-card-foto .ph:has(img) { padding: 0; }
.score-card-foto .ph:has(img) img { object-fit: contain; filter: drop-shadow(0 12px 18px rgba(0, 0, 0, .55)); }

.score-card-name { display: flex; align-items: center; gap: var(--sp-3); font-size: var(--fs-body-s); margin-bottom: .9rem; }
.score-card-name-link { color: var(--ink); text-decoration: none; }
.score-card-name-link:hover { color: var(--accent-fg); text-decoration: underline; text-underline-offset: .15rem; }

/* Nota PadelSapiens resaltada al principio del score-card, antes de los 7
   ejes — mismo dato que el anillo del hero (shared.js::notaGeneralRing),
   aquí como tarjeta de marca (2026-08-08, rediseñada 2026-08-20 a petición
   de Sergio: wordmark igual que el logo de cabecera a la izquierda, icono
   real + número + barra de progreso al eje real a la derecha — 6 rondas de
   ajuste en vivo, ver [[score_card_nota_rediseno_backlog]]).
   Tarjeta neutra en los dos temas (mismo lenguaje que la cabecera: color de
   marca en el CONTENIDO, no en el fondo) — solo cambia el tono de gris. */
.score-card-nota {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4);
  padding: var(--sp-5) var(--sp-5); margin-bottom: 1.1rem;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.score-card-nota-brand { display: flex; flex-direction: column; gap: .15rem; text-align: left; }
.score-card-nota-eyebrow {
  font-family: var(--mono); font-size: var(--fs-micro); font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink-mute);
}
.score-card-nota-wordmark { font-family: var(--display); font-weight: 800; font-size: var(--fs-lead); line-height: 1; color: var(--ink); }
.score-card-nota-wordmark em { font-style: normal; color: var(--accent-fg); }
/* Enlace a /metodologia.html (26-08-26) — vivía también junto al anillo
   de nota del hero (`.nota-ring`), pero Sergio pidió quitarlo de ahí y
   dejarlo solo aquí, en la tarjeta del Dashboard. Colores fijos en tema
   claro porque la chapa de abajo sigue siendo oscura pase lo que pase. */
/* Subrayado fino y separado, no el de serie pegado a la letra: a este
   tamaño el subrayado por defecto se come las descendentes. */
.score-card-nota-link {
  font-size: var(--fs-micro); color: var(--ink-mute); margin-top: .25rem;
  text-decoration: underline; text-decoration-thickness: 1px;
  text-underline-offset: .28em;
  text-decoration-color: var(--line-od);
  transition: color var(--t-micro) var(--ease-out), text-decoration-color var(--t-micro) var(--ease-out);
}
.score-card-nota-link:hover { text-decoration-color: currentColor; }
.score-card-nota-link:hover { color: var(--accent-fg); }
.score-card-nota-stat { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-2); }
.score-card-nota-stat-row { display: flex; align-items: center; gap: var(--sp-3); }
.score-card-nota-num {
  font-family: var(--mono); font-size: 2.4rem; font-weight: 700; color: var(--accent-fg);
  line-height: 1; letter-spacing: -.03em; font-variant-numeric: tabular-nums;
}
.score-card-nota-icon { display: block; width: 40px; height: 40px; flex-shrink: 0; }
/* La barra empieza siempre alineada con el borde izquierdo del número
   (align-items: flex-start en .score-card-nota-stat), nunca centrada
   respecto al icono — a petición expresa de Sergio. */
.score-card-nota-bar { width: 96px; height: 6px; border-radius: 999px; background: var(--accent-soft); overflow: hidden; display: block; }
.score-card-nota-bar-fill { display: block; height: 100%; border-radius: 999px; background: var(--accent-fg); }

/* Tema claro: mismo gris metalizado (`--metal-row`, 26-08-26) que
   `.nav-logo`/`.nota-ring`/`.compare-card-price-chip` — el lima puro SÍ se
   puede usar aquí porque el fondo sigue siendo oscuro, solo el "Padel" del
   wordmark queda en blanco para dar identidad (a petición expresa de
   Sergio: "el logo tiene que ser igual, con el Padel en blanco"). Tema
   oscuro se queda con la tarjeta neutra de arriba sin tocar (a petición de
   Sergio: "las tarjetas del ranking en modo oscuro no deberían aparecer en
   este gris metalizado" — mismo criterio en todas las chapas). */
/* Aquí había OCHO reglas de excepción de tema claro para esta tarjeta —
   fondo, eyebrow, wordmark, número, enlace, hover, barra y relleno—, todas
   para compensar que la pieza es oscura en un tema claro. Se borraron el
   06-09-26: `.producto-puntuaciones` redefine --ink, --ink-mute y
   --accent-fg para todo el bloque, y estas reglas heredan lo correcto
   solas. Es el ejemplo de libro de lo que hace el primitivo on-dark. */
.score-color-dot { width: .7rem; height: .7rem; border-radius: 999px; background: var(--score-color); flex-shrink: 0; }
.score-bars { display: flex; flex-direction: column; gap: var(--sp-3); list-style: none; margin: 0; padding: 0; }
.score-bar { display: grid; grid-template-columns: 6.5rem 1fr 2rem; align-items: center; gap: var(--sp-4); }
.score-bar-label { font-family: var(--mono); font-size: var(--fs-label); color: var(--ink-mute); }
.score-bar-track { height: .4rem; border-radius: 999px; background: var(--line-2); overflow: hidden; }
.score-bar-fill { display: block; height: 100%; border-radius: 999px; background: var(--score-color); transition: width var(--t-slow) var(--ease-out); }
.score-bar-value { font-family: var(--mono); font-size: var(--fs-meta); font-weight: 700; color: var(--ink); text-align: right; }

.ia-veredicto { margin-top: 2rem; padding-top: 2rem; border-top: 1px solid var(--line); text-align: center; }
.ia-veredicto-nota { margin: .75rem auto 0; max-width: 32rem; font-size: var(--fs-meta); color: var(--ink-mute); }
.ia-veredicto-result { margin-top: 1.5rem; text-align: left; }
.ia-veredicto-result[hidden] { display: none; }
/* Estados de texto plano (carga/error) — la tarjeta de éxito (grid de
   niveles) se estiliza aparte, sin este marco único. */
.ia-veredicto-result.is-cargando,
.ia-veredicto-result.is-error {
  padding: var(--sp-6) var(--sp-6); border-radius: var(--radius-sm);
  border: 1px solid var(--line); background: var(--surface);
  font-size: var(--fs-body-s); line-height: 1.65;
}
.ia-veredicto-result.is-cargando { color: var(--ink-mute); font-style: italic; }
.ia-veredicto-result.is-error { border-color: var(--accent-2); color: var(--accent-2); }

/* Flexbox, no grid — con grid, "justify-content: center" centra el
   conjunto de columnas pero NO el contenido de una fila con menos
   elementos que columnas (la última fila de 2 se queda pegada a la
   izquierda con hueco a la derecha). Con flex-wrap, cada fila que se
   envuelve se centra de forma independiente — así la última fila
   incompleta también queda centrada de verdad.

   Pero centrar una fila huérfana no es lo mismo que no tenerla. Las
   categorías son CINCO y el ancho base era fijo (240 px), así que el reparto
   lo decidía el ancho de pantalla: medido, 4+1 a 1240 y a 1456, y 2+2+1 a 768.
   Una tarjeta sola debajo de cuatro no es un diseño, es un resto de división.

   Ahora el ancho base va en porcentaje del propio contenedor, con lo que el
   reparto deja de depender del azar y se declara: 5 en fila a partir de
   1000 px, 3+2 entre 760 y 999 —que además es el corte real del contenido,
   tres niveles y dos perfiles de juego— y 1 por fila por debajo. Ninguno de
   los tres deja huérfana. */
.ia-recomendaciones-grid { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-5); }
.nivel-card {
  /* `flex-grow: 0` a propósito. Con grow, las dos tarjetas de la última fila
     se repartían el ancho entero y salían a 317 y 484 px mientras las tres de
     arriba iban a 317: una fila de dos tarjetas más gordas que las de encima.
     Sin grow, todas miden lo mismo y la fila corta se centra. */
  flex: 0 1 var(--base-tarjeta, 100%); max-width: none;
  display: flex; flex-direction: column;
  background: var(--metal-row); border: 1px solid var(--line-od-2); border-radius: var(--radius-sm);
  padding: var(--sp-6);
  /* Mismo material y misma tinta que las celdas del dashboard. Antes era
     `--surface` con una excepción de tema claro que la pintaba del metalizado
     OSCURO: la conclusión acababa en un gris distinto al de todo lo que tenía
     encima. Una sola chapa en toda la página. */
  --ink:       var(--ink-od);
  --ink-soft:  var(--ink-od-soft);
  --ink-mute:  var(--ink-od);
  --line:      var(--line-od);
  --line-2:    var(--line-od-2);
  --surface-2: var(--veil-od);
  --surface-3: var(--veil-od-strong);
  --accent-fg: var(--accent);
  color: var(--ink);
}
/* El `- 1px` es un colchón de redondeo: sin él, un contenedor de ancho impar
   deja el quinto por 0,5 px fuera y la fila se parte en 4+1, que es justo lo
   que se está evitando. */
/* El corte a 3 va en 760 y no en 900: a 768 —la tableta de referencia— con el
   corte en 900 caía en la banda de una por fila y salían cinco tarjetas de
   728 px de ancho con dos líneas de texto dentro. A 3 quedan en 232, que es
   estrecho pero se lee. */
@media (min-width: 760px) {
  .ia-recomendaciones-grid { --base-tarjeta: calc((100% - 2 * var(--sp-5)) / 3 - 1px); }
}
/* Las cinco en fila desde 1000 y no desde 1200 (Sergio: "las 5 en la misma
   fila"). A 1000 la tarjeta baja a 179 px: estrecho —los nombres largos se
   parten en tres líneas— pero legible. Por debajo ya no lo es, y ahí manda el
   3+2 de la regla de arriba. */
@media (min-width: 1000px) {
  .ia-recomendaciones-grid { --base-tarjeta: calc((100% - 4 * var(--sp-5)) / 5 - 1px); }
}
/* En tinta plena. Iba en `var(--score-color)`, que aquí NO existe: esa
   variable la pone cada pala en el dashboard, y estas tarjetas son por
   categoría de jugador, no por pala. La declaración era inválida y el kicker
   llevaba desde siempre heredando el color del texto. Ahora se declara lo que
   de verdad pasa. Y coloreado tampoco podría ir: son 11,5 px, texto pequeño,
   y sobre la chapa piden 4,5. */
.nivel-card-kicker { font-family: var(--mono); font-size: var(--fs-label); letter-spacing: .06em; text-transform: uppercase; color: var(--ink-od); margin-bottom: .5rem; }
.nivel-card-name { display: flex; align-items: center; gap: var(--sp-3); font-size: var(--fs-body-s); margin-bottom: .6rem; }
.nivel-card-name a, .nivel-card-name span { color: var(--ink); text-decoration: none; }
.nivel-card-name a:hover { color: var(--accent-fg); text-decoration: underline; }
/* ===== LA CELDA ES LA CHAPA; LOS DATOS VAN HUNDIDOS (07-09-26) ===========
   El comparador va sobre oscuro en los dos temas, y esa es la condición para
   que las series sean de marca: sobre blanco el lima da 1,18 y hay que
   degradarlo a un verde oscuro —el que Sergio lleva rechazando toda la
   sesión—; sobre oscuro vuelve a ser lima.

   La tarjeta es `--metal-row`, la chapa del sitio (Sergio: "quiero el gris
   claro con el que estábamos trabajando"). Pero en tema claro esa chapa es un
   gris MEDIO y encima no despega ninguna de las cuatro series: medido,
   4,27 / 1,62 / 1,74 / 2,16 contra la chapa, y peor contra la pista, porque la
   pista es un velo BLANCO que la aclara todavía más. 37 fallos.

   Por eso la zona de datos va HUNDIDA en un velo negro y las series viven ahí.
   La tarjeta sigue siendo el gris de Sergio y los datos recuperan fondo
   oscuro. Es el mismo recurso del chip del ranking, unas 2.000 líneas más
   arriba: el velo va AL REVÉS en cada tema porque la chapa vale cosas
   distintas. En oscuro `--metal-row` ya es casi negro, así que ahí el hundido
   es un velo negro más suave (25 %): ahí no hace falta rescatar nada —el tema
   oscuro ya medía 0 fallos— y el velo es solo el escalón visual.

   El reparto del padding (marco `--sp-3`, hundido `--sp-5`) es idéntico en los
   dos temas a propósito: lo único que cambia entre temas es el color del velo,
   nunca la caja, para que no haya dos maquetaciones que mantener. */

.dash-celda {
  --ink:       var(--ink-od);
  --ink-soft:  var(--ink-od-soft);
  --ink-mute:  var(--ink-od-mute);
  --line:      var(--line-od);
  --line-2:    var(--line-od-2);
  --surface-2: var(--veil-od);
  --surface-3: var(--veil-od-strong);
  --accent-fg: var(--accent);
  border-color: var(--line-od-2);
  color: var(--ink);
}
/* `.nivel-card` sigue siendo claro en tema claro y por eso conserva su regla
   con tema: su fondo NO es on-dark en oscuro. */


/* NADA de velo hundido aquí (07-09-26, Sergio: "no vuelvas a poner ese velo
   negro, quiero que solo sea la tarjeta de gris metálico"). La celda es la
   chapa y punto: sin caja dentro de caja.

   Lo que la chapa clara SÍ obliga a tocar es la tinta, y eso no es una caja:
   sobre ella el blanco al 90 % (`--ink-mute`) da 3,90 y el blanco pleno 4,51,
   así que dentro de la celda no hay tinta apagada. */
.dash-celda { --ink-mute: var(--ink-od); }

.nivel-card-texto { font-size: var(--fs-meta); line-height: 1.6; color: var(--ink-soft); margin: 0; }
/* `margin-top: auto` empuja el enlace al fondo de la tarjeta (07-09-26,
   Sergio: "el ver ficha completa quiero que esté alineado en todas las
   tarjetas"). Las tarjetas ya median todas lo mismo de alto —son hermanas
   flex y se estiran—, pero el enlace colgaba pegado a su propio texto, así que
   subía y bajaba según lo largo que fuera: cinco cotas distintas en una fila.
   Con `auto`, el sobrante de la tarjeta se lo come el margen y los cinco caen
   a la misma altura. El aire mínimo lo pone el padding y no el margen, porque
   un margen fijo volvería a competir con el `auto`. */
.nivel-card-link { margin-top: auto; padding-top: var(--sp-5); font-size: var(--fs-meta); }

/* Esqueleto de carga (streaming, 2026-08-24): la tarjeta nace así, vacía, y
   se rellena con el contenido real en cuanto Claude termina esa categoría
   (main.js::rellenarTarjeta) — sustituye a la única pantalla en blanco de
   "Consultando…" que había antes durante los ~7s de generación completa. */
.nivel-card-skeleton { display: flex; flex-direction: column; gap: var(--sp-3); }
.skeleton-line {
  display: block; height: .7rem; border-radius: 999px;
  background: linear-gradient(90deg, var(--surface-2) 25%, var(--line) 50%, var(--surface-2) 75%);
  background-size: 200% 100%;
  animation: skeleton-shimmer 1.6s ease-in-out infinite;
}
.skeleton-line--name { width: 55%; height: .85rem; }
.skeleton-line--short { width: 40%; }
/* La tarjeta final es centrada (sin punto de color ni nombre) — sus barras
   de esqueleto se centran también, si no se veían pegadas a la izquierda
   dentro de una tarjeta con todo lo demás centrado. */
.nivel-card--final .nivel-card-skeleton { align-items: center; }
@keyframes skeleton-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
/* La tarjeta se atenúa un poco mientras carga, para diferenciarla a simple
   vista de las que ya tienen contenido real sin necesidad de leer el texto. */
.nivel-card.is-loading { opacity: .8; }
@keyframes nivel-card-reveal { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
.nivel-card:not(.is-loading) { animation: nivel-card-reveal .4s var(--ease-out); }


/* Veredicto de síntesis final — a propósito no recomienda una pala
   concreta (por eso no lleva punto de color ni nombre), así que se trata
   como una pieza de marca aparte: centrado, destacado, a todo lo ancho.
   flex-basis: 100% fuerza que ocupe su propia fila completa, sin
   necesidad del truco grid-column: 1 / -1. */
/* Su borde lima llevaba pisado desde que la excepción de tema claro le ponía
   `border-color: var(--line-od-2)` con más especificidad: medido, la
   conclusión salía con el mismo borde blanco al 38 % que las demás y no se
   distinguía de ellas en nada. Retirada la excepción, el lima vuelve.

   Se distingue por ACENTO y no por material —misma chapa que el resto— para
   que la página siga hablando de un solo gris. */
.nivel-card--final {
  flex: 1 1 100%; max-width: none; text-align: center; padding: var(--sp-7) var(--sp-8);
  border-color: var(--accent); background: var(--metal-row);
}
/* En blanco, no en lima: son 18,4 px y el umbral de "texto grande" está en
   18,66, así que se queda del lado del texto pequeño y pide 4,5 — el lima da
   4,27 sobre la chapa. Por 0,26 px. El lima se queda donde sí vale: en el
   borde de la tarjeta, que es un gráfico. */
.nivel-card--final .nivel-card-kicker {
  display: flex; align-items: center; justify-content: center; gap: var(--sp-3);
  color: var(--ink-od); font-size: var(--fs-lead); margin-bottom: .9rem;
}
.nivel-card-logo { flex-shrink: 0; }
/* La variante BLANCA siempre (07-09-26, Sergio: "cambiaría el logo al
   blanco"). La tarjeta es chapa oscura en los DOS temas, así que el
   interruptor por tema no sirve aquí: en claro sacaba el trazo oscuro sobre
   fondo oscuro.

   Ojo con el nombre de estas clases, que ya costó dos intentos en el banner de
   SAPI: describen el TEMA DE DESTINO, no el color del trazo. `--dark` carga
   `logo-oscuro.webp`, que es el de trazo BLANCO. Mismo forzado que hacen
   `.footer-brand` y `.sapi-promo-icon`, y por lo mismo. */
.nivel-card-logo.nav-logo-icon--dark { display: block !important; }
.nivel-card-logo.nav-logo-icon--light { display: none !important; }

/* Filo de luz sobre la chapa, solo en tema claro —en oscuro `--metal-row` es
   plana y el relieve no tiene sentido, mismo criterio que `.ranking-list-row`—
   (07-09-26, Sergio: "quizá resaltaría alguna cosa para que no esté tan
   plano"). Es el token que ya existe, no una sombra nueva: el listón cuenta
   recetas distintas de `box-shadow` y estrenar una habría tirado el build. */
:root[data-theme="light"] .nivel-card,
:root[data-theme="light"] .dash-celda,
:root[data-theme="light"] .compare-slot { box-shadow: var(--edge-relief); }
.nivel-card--final .nivel-card-texto { font-size: var(--fs-body-s); max-width: 42rem; margin: 0 auto; }

/* Titular entre la Recomendación PadelSapiens (arriba, sola) y la rejilla de
   niveles/estilos (debajo) — 2026-08-09, pedido de Sergio para separar
   claramente "la conclusión" del "detalle por perfil". */
.ia-recomendaciones-headline {
  text-align: center; margin: 1.75rem auto 1.25rem; max-width: 34rem;
  color: var(--ink-mute); font-size: var(--fs-body-s);
}

.flip-cards { display: block; }
/* El dashboard se parte en dos columnas a partir de 1100px, no de 960
   (07-09-26, revisando el responsive). Con el radar ocupando 380 fijos, a
   1024 px a la tabla le quedaban 582 y las barras se quedaban en 21 px de
   ancho: ilegibles.

   El umbral es 1240 y no 1080, que fue mi primera cuenta sobre el papel:
   medido de verdad, a 1100 px las dos columnas dejaban la barra en 40 px
   —PEOR que los 122 de una sola columna a 1024—, porque la cuenta olvidaba
   los gutters del container. A 1240 la barra sale a ~70.

   Apilados, los radares se ponen EN PARALELO en vez de uno sobre otro: si no,
   la tarjeta se hace larguísima con 380 px de radar centrado en 960 de
   ancho. */
@media (min-width: 1240px) {
  /* Ancho FIJO y no minmax: la ficha técnica cruza las dos columnas y necesita
     saber cuánto mide la izquierda para que sus cuatro palas caigan justo
     debajo de las de arriba. Con un rango no se puede calcular. */
  .puntuaciones-dashboard { --col-radar: 23.75rem; grid-template-columns: var(--col-radar) 1fr; }
  /* La ficha técnica vuelve a su columna: su pareja a la izquierda es ahora el
     gráfico de precios, así que la fila ya no queda coja y sus columnas siguen
     alineadas con las de arriba sin necesidad de calcular nada. */
  /* El radar no necesita tope: el viewBox ya lleva las etiquetas dentro (ver
     comparadorPalas.js). Se probó antes limitándolo a 18.5rem y no servía —la
     etiqueta se salía igual, porque el problema no era el tamaño del radar
     sino que el texto caía FUERA del cuadro de coordenadas. */
}
@media (max-width: 1239px) and (min-width: 768px) {
  .radar-panel { display: grid; grid-template-columns: 1fr 1fr; align-items: start; }
  .radar-panel .radar-legend { grid-column: 1 / -1; margin-top: var(--sp-4); }
}

/* En el comparador el polígono va SIN relleno. Con una sola pala el relleno
   al 16 % da cuerpo a la forma —por eso la ficha lo conserva—, pero con cuatro
   superpuestas se acumula: donde coinciden las cuatro, 1-(1-.16)^4 = 50 % de
   tinta, y el resultado es una mancha que tapa la retícula y las propias
   siluetas. Sin relleno cada pala es un contorno limpio y los cruces se
   siguen leyendo. El trazo sube a 2.5 para compensar la pérdida de cuerpo. */
/* Apuntaba a `.radar-panel`, una tarjeta que dejó de existir al pasar el
   dashboard a rejilla por filas: la regla llevaba desde entonces sin aplicarse
   y los polígonos seguían rellenos al 16 %. Con cuatro palas eso son cuatro
   rellenos superpuestos enturbiando la chapa. Solo contorno, y a 3 px: el
   grosor no cambia el ratio de contraste pero sí lo que el ojo distingue. */
.dash-celda .radar-data { fill: none; stroke-width: 3; }

/* La rejilla del radar, en filo OSCURO en tema claro, por lo mismo que la
   pista de las barras. Iba en `--line-2`, un velo BLANCO que sobre la chapa
   clara acaba en #a0a0a6, o sea MÁS CLARO que la propia tarjeta: donde una
   serie cruzaba un anillo el contraste que cuenta —serie contra anillo— caía a
   1,62-2,20, peor que contra la chapa. La rejilla competía con los datos en
   vez de sostenerlos. En oscuro el velo blanco es correcto y se queda. */
/* Los alfas los fija la serie MÁS FLOJA, que con la paleta viva es el naranja
   (luminancia 0,287): para que llegue a 3 contra un anillo, ese anillo no
   puede pasar de 0,062 de luminancia. En claro eso es velo negro al 45 % sobre
   la chapa; con el 30 % que había el naranja se quedaba en 2,69.

   Y en OSCURO hacía falta lo contrario y no lo había visto: allí el anillo
   hereda `--line-od-2`, un velo BLANCO al 38 % que sobre el casi negro deja un
   gris claro, y el naranja caía a 1,56 — peor que en tema claro. Baja al 10 %.

   O sea que el anillo va oscuro en claro y claro en oscuro, pero en los dos
   casos SUAVE: la rejilla es cromo, tiene que sostener los datos y no
   competir con ellos. */
:root[data-theme="light"] .dash-celda .radar-ring,
:root[data-theme="light"] .dash-celda .radar-axis { stroke: rgba(0,0,0,.45); }
.dash-celda .radar-ring,
.dash-celda .radar-axis { stroke: var(--veil-od-strong); }

/* La tarjeta del radar: los dos radares apilados y la leyenda una sola vez
   abajo. Es lo que iguala su alto con el de la tabla de al lado — antes era
   una tarjeta que giraba, se quedaba a media altura y dejaba la columna coja.
   `align-self` vuelve a `stretch` justamente por eso. */
.radar-uno { width: 100%; }
/* Centrados y un escalón más grandes (07-09-26, Sergio). Iban alineados a la
   izquierda mientras el radar iba centrado, así que título y dibujo no
   compartían eje. */
.radar-uno .puntuaciones-grupo-titulo { align-self: center; text-align: center; margin: 0; }
.radar-uno, .radar-panel .radar-legend { align-self: center; }
.radar-panel .radar-legend { margin-top: auto; }

/* El gráfico de precios ocupa la celda izquierda de la fila de ficha técnica,
   la que antes iba vacía. Con él, la columna izquierda queda entera de
   gráficos —dos radares y un comparador de precios— y la derecha entera de
   datos, que es lo que hace que las dos se lean como dos lenguajes y no como
   una tabla con un hueco. */
.precio-grafico-mini { display: flex; flex-direction: column; gap: var(--sp-4); width: 100%; }
.precio-grafico-mini .puntuaciones-grupo-titulo { text-align: center; margin: 0; }
.precio-mini-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-4); }
/* Columna de valor FIJA y no `auto`: con auto, un "109 €" dejaba más sitio a
   su barra que un "749,95 €", así que las rayas salían de distinto largo
   (07-09-26, Sergio: "la raya de abajo es más larga que las otras"). Es el
   mismo fallo que ya se corrigió en las celdas de la tabla. */
.precio-mini-item {
  display: grid; grid-template-columns: minmax(0, 1fr) 5.2rem; align-items: center; gap: var(--sp-3);
}
.precio-mini-valor { text-align: right; }
.precio-mini-valor {
  font-family: var(--mono); font-size: var(--fs-caption); color: var(--ink-soft);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* El más barato, en el color de su pala, igual que el máximo de cada eje en la
   tabla. La diferencia de significado —aquí gana el más BAJO— la dice la nota
   de debajo, no el signo. */
/* El precio más bajo, en blanco y negrita por lo mismo que la nota ganadora de
   las puntuaciones: es texto pequeño sobre la chapa y pide 4,5, que ningún
   color de la paleta alcanza. Coloreado se quedaba en 3,16. */
.precio-mini-item.is-min .precio-mini-valor { color: var(--ink-od); font-weight: 700; }
.precio-mini-nota {
  margin: 0; font-size: var(--fs-label); color: var(--ink-mute); text-align: center;
}

/* ===== COMPARATIVA POR FILAS ==============================================
   La matriz va transpuesta: una fila por eje y las cuatro palas dentro. Todas
   las filas y la cabecera comparten `--cols`, que es lo que mantiene las
   columnas cuadradas sin necesidad de una tabla real.

   `--n` lo pone el JS con el número de palas seleccionadas, así que la rejilla
   se ajusta sola a 2, 3 o 4 sin reglas por cada caso. */
/* Ya no es una tarjeta: la tarjeta es el dashboard entero y esto vive dentro
   de una celda. `--cols` lo pone el JS en cada celda con el número de palas
   seleccionadas, y es lo que mantiene las columnas cuadradas entre la
   cabecera y todos los grupos aunque cada uno viva en su propia celda. */
.compara-tabla { display: contents; }
.compara-tabla-head, .compara-fila {
  display: grid; grid-template-columns: var(--cols); gap: var(--sp-4); align-items: center;
}
.compara-fila { padding-block: var(--sp-3); }
.compara-fila + .compara-fila { border-top: 1px solid var(--line); }
/* Ya NO es pegajosa. Lo fue mientras la tabla era un bloque continuo, pero
   desde que el dashboard es una rejilla por filas cada grupo vive en su propia
   celda: un `sticky` solo se desplazaría dentro de la suya y, sobre todo,
   rompería la fila que ahora empareja cada radar con su bloque de datos. La
   identidad de cada columna la sostienen la foto de arriba y el color de las
   barras. */
.compara-tabla-head {
  /* Sin caja propia: su celda ya es el panel rebajado. `align-items: start` y
     no `end` porque con las columnas alineadas por abajo, un nombre de tres
     líneas empujaba su foto 6 px más arriba que las de dos. */
  align-items: start;
}
/* La foto se centra por sí misma y el nombre va debajo con alto reservado, así
   la fila de fotos queda a la misma altura aunque un nombre ocupe dos líneas y
   otro una (Sergio: "las palas deberían estar bien centradas sin tener en
   cuenta lo que ocupe el título"). */
/* Sin compensación: al irse la columna del punto, el centro de la celda vuelve
   a ser el de la barra y su número. */
.compara-pala { display: flex; flex-direction: column; align-items: center; min-width: 0; text-align: center; }
.compara-pala .score-card-foto { width: 100%; height: 7rem; margin-bottom: var(--sp-3); }
/* El nombre en columna: primero la LÍNEA de color, luego el texto. Antes el
   color era un punto en línea delante del nombre, que lo empujaba a la derecha
   y lo dejaba descentrado respecto a la foto. Una línea ocupa el ancho entero
   y no desplaza nada. */
.compara-pala .score-card-name {
  margin-bottom: 0; font-size: var(--fs-caption); flex-direction: column;
  align-items: stretch; justify-content: flex-start; min-height: 2.6em; gap: var(--sp-3);
}
/* 3px (07-09-26, Sergio: "esta línea un poco más gruesa para que resalte
   más"). Es lo que identifica a la pala con su serie en toda la tabla. */
.score-color-linea { display: block; height: 3px; border-radius: 999px; background: var(--score-color); }

/* El grupo NO es una caja: la celda que lo contiene YA es el panel rebajado.
   Tenerlo también con fondo, radio y sombra metía una caja dentro de otra
   idéntica a 16 px de distancia —dos velos negros del 50 % sumando 75 %, dos
   filos redondeados concéntricos—, y eso es lo que hacía que la pantalla se
   leyera recargada en vez de premium (07-09-26, Sergio: "dale un toque de
   elegancia y simplifícalo un poco"). */
/* Sin hueco entre filas: separa la LÍNEA, no el aire (07-09-26, Sergio: "una
   fina línea entre cada una de las puntuaciones para dar sensación de tabla
   premium"). Al hacerlo la línea, el espacio se convierte en padding DENTRO de
   cada fila, que es lo que da altura de renglón sin dispersar el bloque. */
.compara-grupo { display: flex; flex-direction: column; }
/* Un escalón por encima del resto de rótulos del sitio: aquí encabezan un
   bloque entero de la comparativa, no una fila. */
.compara-grupo .puntuaciones-grupo-titulo,
.radar-uno .puntuaciones-grupo-titulo { font-size: var(--fs-meta); letter-spacing: .06em; }
.compara-grupo .puntuaciones-grupo-titulo { margin-bottom: var(--sp-3); }
.compara-fila-label {
  font-size: var(--fs-caption); color: var(--ink-mute); min-width: 0;
  overflow: hidden; text-overflow: ellipsis;
}
/* Rejilla fija, no flex (07-09-26, Sergio: "todas las líneas deberían tener
   mismo tamaño para dar sensación de orden"). Con flex, la pista se encogía lo
   que creciera el número: un "9,6" dejaba la barra más corta que un "7", así
   que ninguna fila medía lo mismo. Con columnas fijas para el valor y para el
   punto, la pista mide siempre igual.

   El punto del máximo tiene su propia columna, SIEMPRE presente aunque esté
   vacía — es lo que hace que todos caigan alineados en vertical en vez de
   colgar de cada número (Sergio: "podrían ir a la derecha del nombre todos
   juntos para que se viera en un mismo sitio"). */
/* Barra y número, sin más: el máximo se marca pintando el propio número del
   color de su pala, así que no hace falta una tercera columna para un punto.
   Con ella fuera la celda son dos columnas: la barra y el valor. Ojo, que eso
   NO deja la cabecera alineada sola — el valor sigue ocupando su columna y la
   cabecera tiene que compensarlo; está justo debajo. */
.compara-celda {
  display: grid; grid-template-columns: minmax(0, 1fr) var(--col-valor);
  align-items: center; gap: var(--sp-2); min-width: 0;
}
/* La pala se centra sobre su BARRA, no sobre la celda entera (07-09-26,
   Sergio: "cuando solo hay dos palas pierden un poco el centro"). La celda es
   barra + valor + gap, así que centrar en la celda dejaba la foto 23,6 px a la
   derecha de su barra. El desfase es el mismo con 2 y con 4 palas —es medio
   ancho de la columna del valor, no depende de cuántas haya—, pero con 2 las
   barras miden el doble y ahí canta.

   Por `transform` y no por `padding` a propósito: con padding el nombre
   perdería 47 px de ancho y se partiría en una línea más, y la foto no ganaría
   nada a cambio porque la limita su altura (7rem), no su ancho. Y el desfase
   se calcula desde `--col-valor`, el mismo token que usa la celda, para que no
   puedan separarse sin que la cabecera lo siga. */
.compara-tabla-head .compara-pala {
  transform: translateX(calc((var(--col-valor) + var(--sp-2)) / -2));
}
/* La RANURA de la barra, con relleno oscuro en claro y claro en oscuro —el
   mismo criterio que la rejilla del radar, justo debajo, y por el mismo
   motivo: es cromo, no marca.

   Con la paleta viva las barras se apoyaban directamente en la chapa y daban
   4,27 / 1,62 / 1,74 / 2,16, o sea tres de las cuatro por debajo del 3 que
   pide un gráfico. Dentro de una ranura oscura suben a 9,21 / 3,49 / 3,74 /
   4,65 sin tocar un solo color.

   Ojo con lo que esto NO es: no es el rebaje que Sergio retiró. Aquello era un
   panel bajo TODA la zona de datos; esto son 6,4 px de alto, la ranura del
   propio componente de barra, la misma que usa la ficha de una pala suelta. Si
   aun así sobra, se quita en una línea. */
.dash-celda .score-bar-track {
  background: rgba(0,0,0,.45); border: 0; box-sizing: border-box;
}
/* En oscuro la ranura va al revés, velo blanco: sobre el casi negro un velo
   negro no se vería y sin ranura no se sabe si una barra al 60 % es un 6 o es
   el máximo. */
:root:not([data-theme="light"]) .dash-celda .score-bar-track { background: var(--veil-od-strong); }

/* 500 y no el 700 que trae `.score-bar-value` de serie: la ganadora ya iba en
   700, o sea que TODAS iban en negrita y la ganadora no destacaba por peso —
   solo por color— (07-09-26, Sergio: "las puntuaciones coloreadas, en negrita,
   para que resalten más"). La negrita se gana quitándosela a las demás.
   JetBrains Mono se carga en 400/500/700, así que subir la ganadora a 800 solo
   habría dado una negrita sintética. */
.compara-celda .score-bar-value { text-align: right; font-variant-numeric: tabular-nums; font-weight: 500; }
/* El máximo: en BLANCO como todos, y marcado solo por el peso (07-09-26,
   Sergio: "prefiero que vayan todas en blanco").

   Aquí está el recorrido entero, porque el resultado parece pobre y no lo es:
   sobre esta chapa el techo de contraste es 5,05 (blanco puro) y 4,16 (NEGRO
   puro), así que para un texto pequeño, que pide 4,5, no hay nada más que los
   casi-blancos. Se probó coloreado a 13,6 px (imposible, ninguna serie pasa de
   4,27), blanco y negrita ("no se aprecian"), grande y coloreado ("queda
   feísimo") y con subrayado de color ("no me gusta tampoco").

   La única excepción medida es el AMARILLO, y por una razón física: es el tono
   saturado de mayor luminancia que existe, porque suma rojo y verde y el verde
   pesa el 71 % de la luminancia. `#FFFF33` llega a 4,71 con croma 90. Pero cae
   justo al lado del lima de la serie 1, que es exactamente el emparejamiento
   que Sergio ya rechazó una vez.

   Así que el que marca al ganador es el largo de su barra, que para eso es un
   gráfico comparativo. El peso se queda porque no cuesta nada, no porque
   resuelva. */
.compara-celda.is-max .score-bar-value { font-weight: 700; }
/* El valor MÁS ALTO de la fila, no "el mejor": en una pala más potencia o
   menos manejo no es mejor ni peor, depende del jugador. Por eso se marca con
   peso y un punto, no con una medalla ni un color de acierto. */

/* Los valores de ficha técnica van en la tipografía de DATOS y un escalón por
   debajo: "274,95 € – 400 €" en tipografía de texto no cabía en los 139 px de
   la columna y se partía en dos líneas, rompiendo el ritmo de las filas. En
   mono y a --fs-label entra de una sola vez, y de paso el bloque se lee como
   una hoja de especificaciones en vez de como prosa metida en una rejilla. */
.compara-fila--texto .compara-celda {
  font-family: var(--mono); font-size: var(--fs-label); color: var(--ink);
  display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}


/* Por debajo de 720px la rejilla de 4 columnas no cabe: la tabla se desplaza
   en horizontal dentro de su propia caja, con la columna de etiquetas fija
   para no perder de vista qué eje se está leyendo. */
/* Por debajo de 768px la tabla se desplaza en horizontal dentro de su caja.
   El `min-width` es 42rem y no 34 por una cuenta, no por gusto: con 34rem la
   tabla CABÍA a 600 px de ventana —así que no se desplazaba— y las barras se
   quedaban en 16 px de ancho, y a 375 en 12. Con 46rem (736 px) siempre hay
   desplazamiento por debajo de esa anchura y la barra no baja de ~60 px.

   La cuenta: columna de etiquetas 120 + cuatro veces (barra 70 + valor 42 +
   punto 9 + huecos 16). */
/* Por debajo de 768px cada celda de datos se desplaza en horizontal por dentro.
   Es la celda y no un contenedor común porque desde que el dashboard es una
   rejilla por filas ya no hay contenedor común: cada grupo vive en su celda.

   El min-width es 44rem por una cuenta: columna de etiquetas 96 px (se aprieta
   de 7.5rem a 6rem aquí, que las etiquetas caben) más cuatro veces
   barra+valor+punto+huecos. Sin esto la fila se quedaba en 224 px a 375 de
   ventana, con 13 px por pala y las barras a cero. */
/* MÓVIL: FUERA LAS BARRAS, SE QUEDAN LOS NÚMEROS (07-09-26) ===============
   Sergio, con una captura del móvil: "no es responsive... en la versión de
   móvil deberíamos incluir los puntos únicamente para que se vean bien los 4
   datos y adaptar las 2-4 palas para que quepan en la pantalla de un vistazo".

   Lo que había era una tabla de 44rem (704 px) metida en 375 con scroll
   lateral. El problema no era que hubiera que arrastrar: es que al arrastrar
   se iba la COLUMNA DE ETIQUETAS, así que quedaban cuatro barras y cuatro
   números sin saber si eran de potencia o de control. Un comparador que no
   dice qué compara no compara nada.

   La barra es lo que pedía esos 704 px, y es justo lo prescindible: el número
   dice lo mismo con más precisión. Sin ella, una fila son 5,5rem de etiqueta y
   cuatro columnas de números — entra de sobra en 320 px sin arrastrar nada, y
   las cuatro palas se ven de un vistazo, que es lo que pedía.

   El radar se queda: ahí es donde vive la lectura visual de la forma, y no le
   hace falta el ancho de la tabla. */
@media (max-width: 767px) {
  .dash-celda { --label-col: 5.5rem; padding: var(--sp-4); }
  /* Sin scroll lateral: ya no hay nada más ancho que la pantalla. */
  .dash-celda[data-comparador-slot] { overflow-x: visible; }
  .dash-celda[data-comparador-slot] > * { min-width: 0; }
  .compara-tabla-head, .compara-fila { gap: var(--sp-2); }
  /* La celda deja de ser barra + valor y pasa a ser solo el valor, centrado
     bajo su pala. La barra se oculta con `display: none` y no quitando la
     columna, para que la rejilla siga siendo la misma en las dos anchuras. */
  .compara-celda { grid-template-columns: minmax(0, 1fr); }
  .compara-celda .score-bar-track { display: none; }
  .compara-celda .score-bar-value { text-align: center; }
  /* La cabecera, proporcionada a lo que queda: cuatro palas en 320 px son
     unos 65 px por columna, así que la foto baja de 7 a 4rem y el nombre a la
     escala más pequeña que sigue siendo legible. */
  /* La foto, más alta (07-09-26, Sergio: "las fotos podrían ocupar toda la
     chapa"). El aire que se veía a los lados NO es margen de la caja: la caja
     ya va a `width: 100%` sin padding. Es que la foto se escala con `contain`
     y a 4rem de alto la pala quedaba pequeña dentro de una columna de 55 px.
     A 6rem la pala mide unos 50 de esos 55 y la caja se llena sola.

     No se pasa a `cover` —que es lo que hacen las miniaturas del buscador—
     porque aquí la caja es MÁS ALTA que ancha: `cover` escalaría por el ancho
     y cortaría la cabeza y el puño de la pala. */
  .compara-pala .score-card-foto { height: 6rem; }
  .compara-tabla-head .compara-pala { transform: none; }
  .score-color-linea { height: 2px; }
  /* La etiqueta se parte en dos líneas en vez de comerse 88 px de ancho fijo:
     con la columna a 5,5rem, las cuatro palas se repartían 210 px de 320 y el
     nombre salía en SIETE líneas —la cabecera medía 183 px de alto, que es el
     mismo problema de antes con otra cara—. A 4,2rem "Punto dulce" y "Bajada
     de pared" caben en dos líneas y las columnas ganan un 45 % de ancho. */
  .dash-celda { --label-col: 4.2rem; }
  .compara-fila-label { white-space: normal; overflow: visible; line-height: 1.15; }
  /* Y el nombre, a DOS líneas como mucho. A este ancho no hay forma de que
     entero quepa, y la alternativa —dejarlo correr— era una cabecera de 183 px
     que empujaba los datos fuera de la primera pantalla, que es justo lo que
     Sergio pidió arreglar. El nombre COMPLETO no se pierde: está en la leyenda,
     justo encima, con su color. */
  .compara-pala .score-card-name {
    font-size: var(--fs-micro); min-height: 0; line-height: 1.2;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  }
  /* La ficha técnica se PARTE en vez de recortarse (07-09-26, Sergio:
     "necesito que se vea bien el peso"). Sus celdas van con `nowrap` +
     ellipsis, que en escritorio está bien porque la columna mide 139 px; en
     móvil mide 55 y dejaba "360–37…", "365–37…" y "Diaman…". Un peso a medias
     no es un dato: es la fila entera perdida, y peso y forma son de las
     primeras cosas que se miran al comparar palas. */
  .compara-fila--texto .compara-celda {
    white-space: normal; overflow: visible; text-overflow: clip;
    line-height: 1.25; text-align: center;
    /* Guionado, y romper por dentro como último recurso. Los pesos parten
       solos —tienen espacio y raya—, pero "Diamante" o "Babolat" son palabras
       sin ningún punto de corte y piden 55 px en una columna de 41 a 320. Sin
       esto pintan fuera de su celda; con `hyphens` parten por sílaba (la
       página va en `lang="es"`) y solo si aun así no cabe se rompe a lo bruto. */
    hyphens: auto; overflow-wrap: break-word;
  }
}

.flip-card-hint {
  margin-top: auto; padding-top: .75rem; display: flex; align-items: center; gap: var(--sp-2);
  font-family: var(--mono); font-size: var(--fs-label); text-transform: uppercase; letter-spacing: .04em;
  color: var(--ink-mute);
}
.flip-card-hint svg { color: var(--score-color); flex-shrink: 0; }
.ficha-specs { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-3); }
.ficha-spec { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-5); padding-bottom: .6rem; border-bottom: 1px solid var(--line); }
.ficha-spec:last-child { padding-bottom: 0; border-bottom: none; }
.ficha-spec-label { font-family: var(--mono); font-size: var(--fs-caption); color: var(--ink-mute); }
.ficha-spec-value { font-size: var(--fs-meta); font-weight: 600; color: var(--ink-soft); text-align: right; }

@media (min-width: 640px) {
  .compare-slots { grid-template-columns: repeat(4, 1fr); }
  /* Con cuatro columnas el chip vuelve a tener 293 px, así que la foto y el
     nombre caben uno al lado del otro y el chip baja de alto. */
  .compare-slot { flex-direction: row; align-items: center; gap: var(--sp-4); text-align: left; }
  .compare-slot .compare-slot-remove { position: static; }
}
@media (min-width: 960px) {
  /* El radar deja de estirarse (06-09-26, auditoría del comparador). Con el
     `align-items: stretch` de la rejilla, su columna se estiraba al alto de la
     de tarjetas: medido, 1.206 px de caja para 375 de contenido —radar 250 y
     leyenda 125—, o sea 780 px de hueco. Era el "muchísimo espacio muerto".

     Y ya que deja de estirarse, se queda pegado al hacer scroll: el radar es
     justo la pieza que quieres tener delante mientras lees las tarjetas de una
     y otra pala, así que el hueco no solo desaparece, se convierte en algo
     útil. Mismo patrón que el lateral de precios de la ficha. */
  /* El radar ya NO se despega ni se encoge: al llevar los dos radares dentro,
     su tarjeta iguala en alto a la de la tabla, que es lo que Sergio pedía
     ("que la chapa de la izquierda tenga el mismo tamaño que la de la
     derecha"). El `stretch` por defecto de la rejilla hace el trabajo. */
  .compare-list-wrap { max-height: 26rem; }
}

/* =============================================================
   16. Página de contacto
   ============================================================= */
.contact-page { padding-block: 8.5rem 5rem; }
.contact-content { position: relative; z-index: 1; max-width: 78ch; margin-inline: auto; }
.contact-content h1 { margin-bottom: 1.1rem; font-size: clamp(2.1rem, 5vw, 3.2rem); }
.contact-lead { margin-bottom: 3rem; }

.contact-card {
  display: flex; flex-direction: column; gap: var(--sp-7); align-items: flex-start;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: var(--sp-8) var(--sp-8); margin-bottom: 3rem;
}
.contact-card-label {
  font-family: var(--mono); font-size: var(--fs-caption); letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-mute); margin-bottom: .5rem;
}
.contact-email {
  display: inline-block; font-family: var(--display); font-weight: 800;
  font-size: clamp(1.3rem, 3.6vw, 1.9rem); color: var(--ink);
  word-break: break-word; transition: color var(--t-micro) var(--ease-out);
}
.contact-email:hover { color: var(--accent-fg); }

.contact-reasons { display: grid; gap: var(--sp-6); grid-template-columns: 1fr; margin-bottom: 2.6rem; }

/* Las cuatro tarjetas de contacto, en la chapa metalizada — SOLO en tema
   claro (31-08-26, Sergio: "aprovecha en el tema claro para poner las
   tarjetas en el gris metalizado de la chapa"). Mismo `--metal-row` y mismo
   filo de luz que la fila del ranking, el logo de cabecera y el resto de
   chapas del sitio: cero vocabulario visual nuevo.

   Los colores NO se ponen uno a uno: se sobrescriben los tokens dentro de
   la tarjeta, igual que hace `.compara-explainer`. Así `.guide-meta` y el
   icono de la flecha —que ya leen `--accent-fg`— pasan a lima puro solos, y
   el resumen, que lee `--ink-mute`, se aclara sin tocar su regla.

   Lima puro y no `--accent-fg` del tema claro: ese está oscurecido a
   propósito para leerse sobre blanco, y sobre esta chapa oscura se apagaba
   a verde oliva — el mismo fallo que ya se corrigió en el icono de SAPI. */
:root[data-theme="light"] .contact-reasons .guide-card {
  background: var(--metal-row);
  border-color: var(--ink-od-mute);
  box-shadow: var(--edge-relief);
  /* Por token y no por literal (06-09-26). El `color` explícito NO sobra:
     redefinir --ink solo cambia lo que LEE --ink, y el texto que no fija
     color (un h3, por ejemplo) hereda del documento, no de esta tarjeta. */
  --ink:       var(--ink-od);
  --ink-mute:  var(--ink-od-mute);
  --line:      var(--line-od);
  --accent-fg: var(--accent);
  color: var(--ink);
}
/* El título se queda en blanco y el lima se reserva para la etiqueta y la
   flecha — misma jerarquía que la fila del ranking sobre esta chapa. Con
   los tres en lima, ninguno destaca.
   Ya no hace falta una regla para el h3: `.guide-card-body h3` no fija
   color, así que hereda el --ink de la tarjeta, que aquí ya es la tinta
   on-dark. La de la flecha SÍ se queda: `.link-arrow` nace en --ink y sin
   ella quedaría blanca, indistinguible del título. */
/* La flecha, en lima ya en reposo: `.link-arrow` nace en `--ink` y sobre la
   chapa quedaba blanca, indistinguible del título. */
:root[data-theme="light"] .contact-reasons .link-arrow { color: var(--accent-fg); }

@media (min-width: 640px) {
  .contact-card { flex-direction: row; align-items: center; justify-content: space-between; gap: var(--sp-8); }
  .contact-card .btn { flex-shrink: 0; }
}
@media (min-width: 720px) {
  .contact-reasons { grid-template-columns: repeat(2, 1fr); }
}

.contact-brand-cta {
  background: linear-gradient(155deg, var(--surface) 0%, var(--surface-2) 100%);
  border: 1px solid var(--accent); border-radius: var(--radius);
  padding: var(--sp-8) var(--sp-8) var(--sp-8); margin-bottom: 2.6rem;
  box-shadow: var(--glow);
}
.contact-brand-cta .kicker { margin-bottom: .6rem; }
.contact-brand-cta h2 { font-size: clamp(1.4rem, 3vw, 1.9rem); margin-bottom: .8rem; }
.contact-brand-cta > p:not(.kicker) { color: var(--ink-mute); margin-bottom: 1.7rem; max-width: 60ch; }

/* Tema claro: la llamada a marcas también en la chapa metalizada
   (31-08-26, Sergio: "quizá esta también, que es la que más tiene que
   resaltar porque es la que más nos interesa").

   Y ahí está el cuidado: si las cinco tarjetas de la página comparten el
   mismo metal, esta deja de destacar por el fondo — así que lo hace por lo
   que YA la distinguía y aquí se conserva entero: borde lima frente al
   borde apagado de las otras cuatro, resplandor lima debajo, y el único
   botón sólido de la página. Sobre el metal esos tres se leen más, no
   menos, porque el lima gana contraste contra el gris oscuro.

   El resplandor sube de `--accent-soft` a un lima con alfa propio: sobre
   fondo claro `--accent-soft` bastaba, pero bajo una chapa oscura casi no
   se veía.

   NO se toca `--accent`: de ahí sale el relleno del botón, y sobrescribirlo
   lo apagaría justo en el elemento que más tiene que destacar. */
:root[data-theme="light"] .contact-brand-cta {
  background: var(--metal-row);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.12), 0 30px 60px -30px rgba(198,255,61,.45);
  /* Por token y no por literal (06-09-26): esta pieza ya usaba el patrón
     on-dark antes de que existiera la familia, con los valores a pelo. */
  --ink:       var(--ink-od);
  --ink-mute:  var(--ink-od-mute);
  --line:      var(--line-od);
  --accent-fg: var(--accent);
  color: var(--ink);
}


/* =============================================================
   17. SAPI — "Encuentra tu pala con SAPI" (docs/adr-sapi.md)
   ============================================================= */

/* --- Banner premium en /categoria/palas/ --- */
.sapi-promo {
  position: relative; overflow: clip;
  display: flex; align-items: center; gap: var(--sp-5);
  background: linear-gradient(135deg, var(--surface) 0%, var(--surface-2) 100%);
  border: 1px solid var(--accent); border-radius: var(--radius);
  padding: var(--sp-6) var(--sp-6); margin-bottom: 2.2rem;
  box-shadow: var(--glow);
  transition: transform var(--t-base) var(--ease-out), box-shadow var(--t-base) var(--ease-out);
}
.sapi-promo:hover { transform: translateY(-3px); box-shadow: var(--glow), var(--ring); }
.sapi-promo-orb {
  position: absolute; top: 50%; right: -4rem; width: 12rem; height: 12rem;
  background: radial-gradient(circle, var(--accent-soft) 0%, transparent 70%);
  transform: translateY(-50%); pointer-events: none;
}
/* Sin disco de fondo (27-08-26): la mascota va directa sobre la chapa y
   gana tamaño. El círculo que había era `--accent-soft` (lima al 14%), y
   sobre la chapa metalizada gris ese lavado daba un verde oliva apagado
   que ni se leía como lima ni separaba la mascota del fondo — no estaba
   haciendo ningún trabajo. Sergio ya había rechazado ese mismo difuminado
   dos veces en otros controles ("no me termina de convencer este
   difuminado, busco estética premium"), y al comparar cuatro alternativas
   en vivo eligió esta: quitar el contenedor y dejar que el peso visual lo
   lleve la mascota, no un disco.

   Se mantiene el `place-items: center` y una caja de tamaño fijo para que
   la fila no se recomponga (el `flex` de al lado depende de un ancho
   estable), pero ya sin fondo ni borde. */
.sapi-promo-icon {
  flex-shrink: 0; display: grid; place-items: center;
  width: 4.5rem; height: 4.5rem;
}
.sapi-promo-icon img { width: 4rem; height: 4rem; }
/* Trazo blanco siempre, en los dos temas — a petición de Sergio: "con la
   nueva placa de sapi, deberíamos meter el logo blanco para que quede
   bien". OJO con los nombres: la clase `--dark` es la que carga
   `logo-oscuro.webp` (trazo BLANCO, pensado para fondos oscuros) y
   `--light` carga `logo-claro.webp` (trazo OSCURO/negro, para fondos
   claros) — el nombre de la clase describe el TEMA al que está destinada,
   no el color del propio trazo. Aquí hace falta el trazo blanco (clase
   `--dark`) porque la mascota va sobre la chapa del banner, oscura en los
   dos temas (metalizada en claro, degradado oscuro en oscuro) — sigue
   siendo cierto tras quitar el disco de arriba. Primer intento equivocado
   (forzaba `--light`, trazo oscuro casi invisible ahí) corregido tras
   verificar el contenido real de los dos webp, no solo qué clase estaba
   en `display:block`. */
.sapi-promo-icon .nav-logo-icon--light { display: none !important; }
.sapi-promo-icon .nav-logo-icon--dark { display: block !important; }
.sapi-promo-body { flex: 1; min-width: 0; position: relative; z-index: 1; }
.sapi-promo-kicker { font-family: var(--mono); font-size: var(--fs-label); letter-spacing: .06em; text-transform: uppercase; color: var(--accent-fg); margin-bottom: .3rem; }
.sapi-promo-title { display: block; font-family: var(--display); font-weight: 800; font-size: var(--fs-body); color: var(--ink); letter-spacing: -0.01em; }
.sapi-promo-cta {
  flex-shrink: 0; display: inline-flex; align-items: center; gap: var(--sp-2);
  font-family: var(--mono); font-size: var(--fs-meta); font-weight: 700; color: var(--accent-fg);
  white-space: nowrap;
}
/* Tema claro: gris metalizado (`--metal-row`, 26-08-26, a petición de
   Sergio: "resaltar a nuestro querido sapi con ese gris metalizado") en
   vez del degradado --surface/--surface-2 (casi blanco en claro, que
   dejaba el banner deslucido frente al resto de chapas ya metalizadas).
   El borde lima se queda igual — --accent es fijo, no cambia por tema.
   Texto a colores fijos claros porque el fondo pasa a ser oscuro siempre
   en este tema, igual que el resto de chapas metalizadas. */
:root[data-theme="light"] .sapi-promo {
  background: var(--metal-row);
  /* Primitivo on-dark: se redefinen los tokens EN el componente y todo lo
     de dentro hereda. Antes aquí había tres reglas más reparando a mano la
     tinta de cada pieza. */
  --ink:       var(--ink-od);
  --ink-mute:  var(--ink-od-mute);
  --line:      var(--line-od);
  --accent-fg: var(--accent);
  color: var(--ink);
}
@media (max-width: 640px) {
  .sapi-promo { flex-wrap: wrap; }
  .sapi-promo-cta { margin-left: calc(4.5rem + 1.1rem); }
}

/* --- Hero de /sapi/ --- */
.sapi-hero { overflow: clip; }
.sapi-orb { position: absolute; border-radius: 999px; filter: blur(60px); pointer-events: none; z-index: 0; }
.sapi-orb--a { top: -6rem; left: -6rem; width: 22rem; height: 22rem; background: var(--accent-soft); }
.sapi-orb--b { bottom: -8rem; right: -8rem; width: 26rem; height: 26rem; background: var(--accent-2-soft); }
.sapi-kicker { display: inline-flex; align-items: center; gap: var(--sp-2); }
.sapi-kicker svg { color: var(--accent-fg); }

/* --- Wizard --- */
.sapi-wizard {
  position: relative; z-index: 1; max-width: 46rem; margin-inline: auto;
  background: var(--surface); border: 1px solid var(--line-2); border-radius: var(--radius);
  padding: var(--sp-8) var(--sp-6) var(--sp-8);
  box-shadow: var(--sh-4);
}
@media (min-width: 640px) { .sapi-wizard { padding: var(--sp-9) var(--sp-9) var(--sp-9); } }

.sapi-progress {
  display: flex; align-items: center; gap: var(--sp-3); justify-content: center;
  margin-bottom: 2.2rem;
}
.sapi-progress-dot {
  width: 2.2rem; height: 3px; border-radius: 2px; background: var(--line-2);
  transition: background var(--t-base) var(--ease-out), transform var(--t-base) var(--ease-out);
}
.sapi-progress-dot.is-done { background: var(--accent-fg); }
.sapi-progress-dot.is-active { background: var(--accent); transform: scaleY(2); }

.sapi-step legend { display: block; width: 100%; margin-bottom: 1.6rem; }
.sapi-step-num { display: block; font-family: var(--mono); font-size: var(--fs-label); letter-spacing: .06em; text-transform: uppercase; color: var(--accent-fg); margin-bottom: .6rem; }
.sapi-step legend h2 { font-size: clamp(1.25rem, 3vw, 1.55rem); margin-bottom: .4rem; }
.sapi-step-help { color: var(--ink-mute); font-size: var(--fs-meta); }
.sapi-step--error { animation: sapiShake .35s var(--ease-out); }
@keyframes sapiShake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-6px); }
  75% { transform: translateX(6px); }
}

/* Tarjetas de opción (nivel, tipo de juego, forma, presupuesto) — mismo
   input de radio nativo, oculto, con el label haciendo de tarjeta clicable
   (accesible: sigue siendo un radio real, con foco visible). */
.sapi-tramos { display: grid; gap: var(--sp-4); grid-template-columns: 1fr; }
.sapi-tramos--3col { grid-template-columns: 1fr; }
@media (min-width: 560px) { .sapi-tramos--3col { grid-template-columns: repeat(3, 1fr); } }

.sapi-tile { position: relative; cursor: pointer; }
.sapi-tile input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.sapi-tile-body {
  display: flex; flex-direction: column; gap: var(--sp-1);
  border: 1px solid var(--line-2); border-radius: var(--radius-sm);
  padding: var(--sp-4) var(--sp-5); height: 100%;
  transition: border-color var(--t-base) var(--ease-out), background var(--t-base) var(--ease-out), transform var(--t-fast) var(--ease-out);
}
.sapi-tile:hover .sapi-tile-body { border-color: var(--accent-fg); transform: translateY(-2px); }
.sapi-tile input:checked + .sapi-tile-body {
  border-color: var(--accent); background: var(--accent-soft);
  box-shadow: var(--ring);
}
.sapi-tile input:focus-visible + .sapi-tile-body { outline: 2px solid var(--accent); outline-offset: 2px; }
.sapi-tile-top { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3); }
.sapi-tile-title { font-family: var(--display); font-weight: 700; font-size: var(--fs-body-s); color: var(--ink); }
.sapi-tile-rango { font-family: var(--mono); font-size: var(--fs-label); color: var(--ink-mute); flex-shrink: 0; }
.sapi-tile-desc { font-size: var(--fs-meta); color: var(--ink-mute); line-height: 1.5; }
.sapi-tile--simple .sapi-tile-body { flex-direction: row; align-items: center; }
.sapi-tile--icon .sapi-tile-body { align-items: center; text-align: center; gap: var(--sp-3); }
.sapi-tile-icon { color: var(--accent-fg); }
/* Un emoji a color no admite `color`/currentColor (no es un glifo de
   texto) — se aplana a negro con brightness(0) (conserva el alfa) y se
   tiñe de vuelta con el resto del filtro, aproximando el mismo lima
   --accent-fg que usan los iconos SVG de al lado. Tema claro: --accent-fg
   es un verde más oscuro (#146B45), así que el filtro cambia con el tema
   en vez de quedarse fijo en el lima del tema oscuro. */
.sapi-tile-emoji {
  display: block; font-size: var(--fs-title); line-height: 1;
  filter: brightness(0) invert(85%) sepia(58%) saturate(886%) hue-rotate(6deg) brightness(1.03) contrast(1.05);
}
:root[data-theme="light"] .sapi-tile-emoji {
  filter: brightness(0) invert(29%) sepia(80%) saturate(690%) hue-rotate(107deg) brightness(0.92) contrast(1.02);
}

.sapi-nolose {
  display: block; margin: .9rem auto 0; font-family: var(--mono); font-size: var(--fs-caption);
  color: var(--ink-mute); text-decoration: underline; text-underline-offset: 3px;
}
.sapi-nolose:hover { color: var(--accent-fg); }
.sapi-fallback { margin-top: 1.1rem; padding-top: 1.1rem; border-top: 1px dashed var(--line-2); }

/* Marcas: chips en vez de tarjetas grandes (hasta 3, se deshabilitan las
   demás al llegar al límite — igual que describe el ADR sección 2). */
.sapi-chips { display: flex; flex-wrap: wrap; gap: var(--sp-3); }
.sapi-chip { position: relative; cursor: pointer; }
.sapi-chip input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.sapi-chip span {
  display: inline-block; padding: var(--sp-3) var(--sp-5); border-radius: 999px;
  border: 1px solid var(--line-2); font-size: var(--fs-meta); color: var(--ink-soft);
  transition: all var(--t-base) var(--ease-out);
}
.sapi-chip input:checked + span { border-color: var(--accent); background: var(--accent); color: var(--accent-ink); font-weight: 700; }
.sapi-chip input:disabled:not(:checked) + span { opacity: .35; cursor: not-allowed; }
.sapi-chip input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }

.sapi-nav { display: flex; justify-content: space-between; align-items: center; gap: var(--sp-5); margin-top: 2rem; }
.sapi-nav .btn:only-child { margin-left: auto; }
/* .btn fija su propio `display`, que gana por especificidad al `display:
   none` del atributo `[hidden]` del navegador — sin esto, "Atrás" se ve
   aunque tenga `hidden` en el paso 1. */
.sapi-nav [hidden] { display: none !important; }
.sapi-error { margin-top: 1rem; text-align: center; font-size: var(--fs-meta); color: var(--accent-2); }

/* --- Estado "consultando" --- */
.sapi-loading { text-align: center; padding: var(--sp-9) var(--sp-5); }
.sapi-loading-orb {
  display: block; width: 3.2rem; height: 3.2rem; margin: 0 auto 1.2rem;
  border-radius: 999px; background: conic-gradient(from 0deg, var(--accent), var(--accent-2), var(--accent));
  animation: sapiSpin 1.1s linear infinite;
}
.sapi-loading p { color: var(--ink-mute); font-size: var(--fs-body-s); }
@keyframes sapiSpin { to { transform: rotate(360deg); } }

/* --- Resultados --- */
.sapi-resultados-head { display: flex; flex-wrap: wrap; gap: var(--sp-5); align-items: flex-end; justify-content: space-between; margin-bottom: 1.8rem; }
.sapi-resultados-head h2 { font-size: clamp(1.3rem, 3vw, 1.7rem); }

.sapi-cards { display: grid; gap: var(--sp-5); grid-template-columns: 1fr; }
@media (min-width: 640px) { .sapi-cards { grid-template-columns: repeat(2, 1fr); } }

.sapi-card {
  position: relative; background: var(--bg-2); border: 1px solid var(--line);
  border-radius: var(--radius); padding: var(--sp-5);
  transition: transform var(--t-base) var(--ease-out), box-shadow var(--t-base) var(--ease-out), border-color var(--t-base) var(--ease-out);
}
.sapi-card:hover { transform: translateY(-3px); border-color: var(--line-2); box-shadow: var(--sh-3); }
.sapi-card--top { border-color: var(--accent); box-shadow: var(--ring); }
.sapi-card-rank {
  position: absolute; top: -.6rem; left: 1rem; z-index: 1;
  font-family: var(--mono); font-size: var(--fs-label); font-weight: 700;
  background: var(--accent); color: var(--accent-ink);
  border-radius: 999px; padding: var(--sp-1) var(--sp-3);
}
.sapi-card h3 { font-size: var(--fs-body); margin-bottom: .3rem; }
.sapi-card h3 a:hover { color: var(--accent-fg); }
.sapi-card-jugador {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  font-family: var(--mono); font-size: var(--fs-label); letter-spacing: .03em; text-transform: uppercase;
  color: var(--accent-2); margin: .3rem 0 0;
}
.sapi-card-jugador svg { flex-shrink: 0; }
.sapi-card-explicacion { margin-top: .8rem; font-size: var(--fs-meta); color: var(--ink-soft); line-height: 1.55; }

.sapi-nota { margin-top: 1.2rem; font-size: var(--fs-meta); color: var(--ink-mute); text-align: center; }
.sapi-comparar { margin-top: 1.8rem; text-align: center; }

/* =============================================================
   18. Dónde comprar — comparativa de precio por tienda, en el lateral de
   la ficha (tools/lib/templates/preciosDashboard.js::preciosLateral).
   Hueco preparado para Awin: hoy siempre se ve el estado "pendiente" (0
   tiendas con price real), el mismo layout pasa a mostrar filas reales sin
   tocar CSS ni plantilla. La galería de fotos vive aparte, en el bloque
   principal (shared.js::galleryThumbs, sección 13).
   ============================================================= */
.precio-row-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-5); }
/* Tarjetas más juntas en el lateral, mismo motivo que el rediseño de
   `.precios-lateral .precio-row` de más abajo: caben más tiendas visibles
   sin scroll. */
/* Naranja del sello de "Mejor precio verificado". `--accent-2` no vale
   directamente porque cambia de tono según el tema (#FF5A1F en oscuro,
   #D9633B —terracota apagado— en claro) para leerse sobre el fondo DE LA
   PÁGINA; aquí el fondo es la chapa de la tarjeta, que es oscura en los dos.
   Con el terracota, el sello llegaba descafeinado en claro y la misma pieza
   pesaba distinto en cada tema ("parece que la línea es más gorda en oscuro":
   se detectó en el contorno, cuando todavía era naranja, pero el sello tenía
   exactamente el mismo problema). Mismo criterio que el lima de la chapa. */
.precios-lateral { --sello: var(--accent-2); }
:root[data-theme="light"] .precios-lateral { --sello: #FF5A1F; }
.precios-lateral.ficha-lateral-card { padding: var(--sp-4) var(--sp-5); }
/* Un pelín más de aire del que pediría el título por sí solo: cuando la
   tienda más barata es la primera de la lista (el caso normal), la etiqueta
   "Mejor precio verificado" sube por encima del borde de su tarjeta y con
   .7rem quedaba pegada al título. */
.precios-lateral h3 { margin-bottom: .95rem; }
.precios-lateral .precio-row-list { gap: var(--sp-2); }
/* Fila de tienda en dos alturas de texto (2026-09-03, "algo más chiquitita
   sin perder UX"): logo y botón ocupan las dos filas de la rejilla, el nombre
   de la tienda va arriba y todo lo secundario —PVP tachado, ahorro, frescura
   del dato— comparte la línea de abajo. Antes eran tres alturas (nombre /
   fecha completa / precio+botón) y cada tienda se llevaba 96 px del lateral. */
.precio-row {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  grid-template-areas:
    "logo vendor precio boton"
    "logo meta   meta   boton";
  align-items: center; gap: 0 var(--sp-4);
  padding: var(--sp-3) var(--sp-4); border: 1px solid var(--line); border-radius: var(--radius-sm);
}
/* La tarjeta del mejor precio se marca con DOS cosas y ninguna más: la píldora
   en lima sólido y un contorno lima alrededor. Nada de relleno.
   Llegó a acumular seis señales a la vez —píldora naranja, fondo lima, borde
   lima, raíl lima, precio lima y botón lima— y Sergio lo cortó ("queda algo
   sobrecargado"). Lo que sobraba no era el contorno: era el relleno teñido, el
   raíl, y sobre todo que la píldora fuera NARANJA metiendo un tercer color en
   una fila que ya era lima por todas partes. Contorno + píldora hablan el mismo
   idioma, así que se leen como una sola marca en vez de como tres.
   Se destaca por MATERIAL, no por filo. Antes de llegar aquí se probaron, y se
   descartaron con Sergio delante, cinco formas de enmarcarla: relleno lima,
   raíl lateral, contorno lima de 1px y de 2px y contorno naranja. Todas
   comparten el mismo defecto —son la misma pieza con una pegatina encima— y
   ninguna acababa de convencer. Cambiarle el acabado a la chapa la convierte
   en otra pieza, que es de donde sale la sensación de premium.
   El filo de luz lima de arriba (`inset 0 1px`) es lo que hace que el oliva se
   lea como metal iluminado y no como un rectángulo verde. */
.precio-row--mejor {
  background: var(--metal-elegida);
  border-color: var(--line-accent);
  box-shadow: inset 0 1px 0 rgba(198, 255, 61, .22), 0 .5rem 1.2rem -.5rem rgba(0, 0, 0, .5);
}
/* A caballo del contorno lima de la tarjeta, no dentro con su propia banda
   (2026-09-03, tercera y última posición que probamos). Sergio: "lo de mejor
   precio verificado, con el color rojo que tenía antes y a la altura del borde
   de color lima, para que dé mejor sensación". Y es cierto: naranja sobre el
   filo lima se engancha con él en vez de competir —que era el problema cuando
   el naranja flotaba sobre un relleno lima— y la etiqueta pasa a leerse como
   un sello cosido a la tarjeta.
   El `padding-top` de la tarjeta sube lo justo para que la mitad de abajo de
   la etiqueta no pise el nombre de tienda: .95rem deja ~6px de aire. Antes
   estaba en 1.5rem porque la etiqueta iba entera por dentro. */
.precio-row-badge {
  position: absolute; top: -.55rem; left: .7rem;
  font-family: var(--mono); font-size: var(--fs-micro); line-height: 1; text-transform: uppercase;
  letter-spacing: .03em; font-weight: 700;
  background: var(--sello, var(--accent-2)); color: var(--accent-ink); padding: var(--sp-1) var(--sp-3); border-radius: 999px;
}
.precio-row--mejor { padding-top: .95rem; }
/* Insignia con la inicial de la tienda — hasta que Awin traiga logos reales,
   da al ojo el mismo punto de anclaje visual sin fingir una marca que no
   tenemos. */
.precio-row-logo {
  grid-area: logo; align-self: center;
  width: 2.6rem; height: 2.6rem; border-radius: 999px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-2); border: 1px solid var(--line-2);
  font-family: var(--mono); font-weight: 700; font-size: var(--fs-body); color: var(--ink);
}
/* Logo real de la tienda (2026-08-10) en vez de la inicial — mismo círculo,
   la imagen solo se centra y se limita para que quepa con margen. */
.precio-row-logo img { width: 60%; height: 60%; object-fit: contain; }
/* Logo que ya es un círculo completo de fábrica (p. ej. PadelProShop) — al
   60% por defecto se veía un círculo pequeño flotando dentro de la
   insignia con un anillo del fondo alrededor, así que este ocupa casi todo
   el círculo de verdad. */
.precio-row-logo img.precio-row-logo-img--full { width: 96%; height: 96%; }

/* Interruptor de variante por tema. El nombre de estas clases describe el TEMA
   DE DESTINO, no el color del propio trazo: `--dark` carga el trazo BLANCO (o
   lima, en Padel Nuestro) para el disco oscuro del tema por defecto, y
   `--light` el trazo oscuro para el disco crema del tema claro. Es una fuente
   de errores conocida — verificar SIEMPRE el contenido real del asset, no
   deducirlo del nombre de la clase. */
.precio-row-logo-img--light { display: none; }
/* `line-height` explícito y apretado en las tres líneas de la fila: con el
   1.6 que hereda del body, el interlineado por sí solo se llevaba 11 px por
   tienda — más que el propio recorte de padding. */
.precio-row-vendor {
  grid-area: vendor; font-weight: 700; font-size: var(--fs-body-s); line-height: 1.25;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Línea secundaria: "390 € · −33% · hoy". Los separadores los pone la
   plantilla (`·`), así que aquí solo manda el tono — todo apagado menos el
   ahorro, que es lo único de esta línea que el ojo debe cazar. */
.precio-row-meta {
  grid-area: meta; font-size: var(--fs-label); color: var(--ink-mute);
  display: flex; align-items: center; gap: 0 var(--sp-1);
  min-width: 0; line-height: 1.4;
}
.precio-row-meta > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.precio-row-meta s { text-decoration: line-through; }
/* "Precio no visible aquí — puede ser más bajo", dentro de la fila de la
   tienda sin precio (26-08-26, antes vivía como párrafo suelto al final de
   todo el bloque). Más apagado que `.precio-row-meta` y en cursiva: es una
   salvedad sobre la fila, no un dato de la tienda. */
.precio-row-nota {
  grid-area: nota; font-size: var(--fs-label); font-style: italic; line-height: 1.35;
  color: var(--ink-mute); opacity: .85; text-wrap: pretty;
}
/* La fila sin precio (Amazon) añade una tercera altura solo para su salvedad,
   que ocupa el ancho completo debajo — es una nota sobre la fila, no un dato
   de la tienda, así que no compite por el carril del nombre. */
.precio-row--sinprecio {
  grid-template-areas:
    "logo vendor precio boton"
    "logo meta   meta   boton"
    "nota nota   nota   nota";
  row-gap: var(--sp-1);
}
/* Esquina superior derecha de la tarjeta, fuera del flujo a propósito (2026-
   08-20, a petición de Sergio: antes era una línea de texto siempre visible
   que cargaba la fila) — mismo anclaje absoluto que `.precio-row-badge` en
   la esquina opuesta. El summary es un botón circular compacto (solo
   icono, o icono + "-X%" cuando algún código dejaría el precio por debajo
   del mínimo verificado del producto, ver `precioRowPromo()`); el panel
   solo aparece al abrir, flotando sobre el resto de la tarjeta para no
   empujar el layout. */
/* Empujado al final de su línea: es lo que mantiene el panel desplegable
   anclado a un punto fijo (el borde derecho del carril de la línea
   secundaria) en vez de a un texto que cambia de ancho en cada fila. */
.precio-row-promo {
  position: relative; margin-left: auto; padding-left: .6rem; z-index: 2;
  font-size: var(--fs-caption); flex-shrink: 0;
}
/* Siempre en rojo (06-09-26, Sergio: "el botón de cupones en el rojo, que lo
   hemos quitado"). Antes el rojo lo llevaba solo la variante --mejorable —el
   cupón que batiría al mejor precio verificado— y el resto salía en un gris
   neutro, así que dos fichas seguidas enseñaban el mismo botón de dos
   colores. Esa distinción no se pierde: sigue dicha con palabras dentro del
   panel ("¡Sería el mejor precio!"), que es donde se puede explicar. */
.precio-row-promo summary {
  display: flex; align-items: center; gap: .15rem; cursor: pointer; line-height: 1;
  list-style: none; width: fit-content; padding: var(--sp-1) var(--sp-2); border-radius: 999px;
  background: var(--promo); border: 1px solid transparent; color: var(--promo-ink);
  transition: background-color var(--t-micro) var(--ease-out), border-color var(--t-micro) var(--ease-out), color var(--t-micro) var(--ease-out);
}
.precio-row-promo summary:hover { background: var(--promo-hover); color: var(--promo-ink); }
.precio-row-promo summary::-webkit-details-marker { display: none; }
.precio-row-promo summary svg { flex-shrink: 0; }
.precio-row-promo summary span { font-family: var(--mono); font-weight: 700; font-size: var(--fs-micro); }
.precio-row-promo--mejorable summary {
  background: var(--promo); border-color: transparent; color: var(--promo-ink);
}
/* El :hover genérico de arriba pone `color: var(--ink)`, que sobre el rojo
   se cae (casi-negro en tema claro: 3,9). Aquí la tinta se queda blanca y lo
   que responde es el fondo. */
.precio-row-promo--mejorable summary:hover {
  background: var(--promo-hover); border-color: transparent; color: var(--promo-ink);
}
/* Panel "premium" (2026-08-20, a petición de Sergio): fondo con degradado
   sutil + borde de doble capa (línea real + halo de sombra) en vez de una
   tarjeta plana, flecha apuntando al botón que la abre para que se lea como
   un popover de verdad y no una caja flotando suelta, y una pequeña entrada
   animada al abrirse (fade + escala) en vez de aparecer en seco. */
.precio-row-promo-panel {
  position: absolute; top: calc(100% + .55rem); right: 0; z-index: 3;
  background: linear-gradient(165deg, var(--surface) 0%, var(--surface-2) 100%);
  border: 1px solid var(--line-2); border-radius: 14px;
  padding: var(--sp-4) var(--sp-5) var(--sp-5); min-width: 16.5rem;
  box-shadow: 0 1px 0 rgba(255, 255, 255, .04) inset, 0 1.2rem 2.6rem -.6rem rgba(0, 0, 0, .5), 0 .3rem .8rem rgba(0, 0, 0, .25);
  animation: precio-row-promo-in .2s var(--ease-out);
}
.precio-row-promo-panel::before {
  content: ''; position: absolute; top: -.4rem; right: 1.05rem; width: .8rem; height: .8rem;
  background: var(--surface); border: 1px solid var(--line-2); border-right: none; border-bottom: none;
  transform: rotate(45deg); border-radius: 2px 0 0 0;
}
/* Un panel CERRADO no debe ocupar sitio. Chrome oculta el contenido de un
   `<details>` cerrado sin sacarlo del cálculo de tamaño, así que el panel
   —304 px de alto— seguía contando dentro del lateral, que es contenedor de
   scroll: la barra aparecía por algo que ni siquiera se ve. Medido 28 px de
   scroll fantasma con todo cerrado. Con `display:none` el panel entra en
   juego solo al abrirse, y la animación de entrada sigue igual porque se
   dispara justo entonces. */
.precio-row-promo:not([open]) .precio-row-promo-panel { display: none; }

@keyframes precio-row-promo-in {
  from { opacity: 0; transform: translateY(-.35rem) scale(.97); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
/* Tema claro: el panel entero a chapa. El panel nace de una fila de tienda,
   que YA es una chapa oscura con el precio en lima; que al desplegarse
   apareciera una tarjeta blanca con el precio en verde rompía la pieza en
   dos mitades que no se parecían. En chapa el popover se lee como lo que
   es —la misma fila, abierta— y el lima se sostiene solo.

   Primitivo on-dark: se redefinen los tokens EN el contenedor y hereda todo
   lo de dentro (título, código, botón de copiar, porcentajes, notas), en vez
   de una regla de excepción por pieza. --surface-2 pasa a un velo claro
   porque en claro es un crema que sobre la chapa se vería como un parche.

   El fondo es --surface-od (#2c2c31) y NO el gris metalizado de las filas.
   Medido: sobre el tramo más claro del metalizado (#6e6e77) el lima se
   quedaba en 4,27 y los rojos de los porcentajes en 2,54 — ese gris tiene
   techo en 4,51 aunque le pongas blanco puro, así que no era cuestión de
   elegir mejor la tinta. Sobre --surface-od hay sitio de sobra. Es el mismo
   camino que el bloque de puntuaciones: cuando un bloque necesita margen de
   verdad se le da su propia pieza oscura, en vez de oscurecer la chapa de
   todo el sitio. Y de paso el panel queda más oscuro que la fila de la que
   sale, que es justo lo que hace que se lea como algo que flota encima. */
:root[data-theme="light"] .precio-row-promo-panel {
  background: var(--surface-od); border-color: var(--ink-od-mute);
  --ink:       var(--ink-od);
  --ink-mute:  var(--ink-od-mute);
  --line:      var(--line-od);
  --line-2:    var(--line-od-2);
  --surface-2: var(--veil-od-strong);
  --accent-fg: var(--accent);
  --promo-fg:  var(--promo-od);
  color: var(--ink);
}
/* La flecha es una pieza aparte y hay que chapearla igual, o queda un
   triangulito blanco colgando del panel. */
:root[data-theme="light"] .precio-row-promo-panel::before {
  background: var(--surface-od); border-color: var(--ink-od-mute);
  border-right: none; border-bottom: none;
}
.precio-row-promo-titulo {
  margin: 0 0 .7rem; font-family: var(--mono); font-size: var(--fs-micro); font-weight: 700;
  text-transform: uppercase; letter-spacing: .08em; color: var(--ink-mute);
}
.precio-row-promo ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.precio-row-promo-item { padding: var(--sp-3) 0; }
.precio-row-promo-item + .precio-row-promo-item { border-top: 1px solid var(--line); }
.precio-row-promo-fila { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.precio-row-promo-codigo-wrap { display: flex; align-items: stretch; min-width: 0; }
/* Filo continuo, no discontinuo (06-09-26): el borde punteado leía como
   "campo de formulario a medio hacer" más que como cupón recortable, y era
   el cuarto tipo de línea distinto dentro de un panel de 16rem. */
.precio-row-promo-codigo {
  font-family: var(--mono); font-weight: 700; font-size: var(--fs-meta); letter-spacing: .06em; color: var(--ink);
  padding: var(--sp-1) var(--sp-3); border: 1px solid var(--line-2); border-right: none; border-radius: 7px 0 0 7px;
  background: var(--surface-2); display: flex; align-items: center;
}
/* Botón de copiar (2026-08-20, a petición de Sergio) — pegado al chip del
   código formando una sola pieza visual (mismo borde discontinuo, sin
   separación), para que quede claro que copia justo lo que tiene al lado.
   Feedback de "copiado" vía `initCopyCodes()` en main.js, mismo patrón que
   `initShareButtons()` (icono que cambia a un check 2s y vuelve). */
.precio-row-promo-copy {
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  width: 2rem; padding: 0; cursor: pointer; color: var(--ink-mute);
  border: 1px solid var(--line-2); border-radius: 0 7px 7px 0; background: var(--surface-2);
  transition: background-color var(--t-micro) var(--ease-out), color var(--t-micro) var(--ease-out);
}
.precio-row-promo-copy:hover { color: var(--ink); background: var(--surface-2); }
.precio-row-promo-copy.is-copied { color: var(--accent-fg); border-style: solid; border-color: var(--accent-soft); }
.precio-row-promo-notas { margin: .4rem 0 0; font-size: var(--fs-micro); font-style: italic; color: var(--ink-mute); }
/* El resultado va SIN caja de color (06-09-26). Antes cada código que
   mejoraba el precio arrastraba una losa en --accent-soft, y con dos
   códigos el panel eran dos bloques verdes pálidos que se comían todo lo
   demás — el propio precio incluido, que iba al mismo peso que su
   etiqueta. Ahora la jerarquía la hace el tamaño: la cifra manda, y el
   lima aparece UNA sola vez en todo el panel, justo en ella. */
.precio-row-promo-resultado {
  margin-top: .5rem; display: flex; align-items: baseline; flex-wrap: wrap; gap: 0 var(--sp-3);
  font-size: var(--fs-label); color: var(--ink-mute);
}
.precio-row-promo-resultado-cifra { font-family: var(--mono); font-weight: 700; }
/* La cifra se queda en LIMA en los dos temas (06-09-26, Sergio: "estoy
   buscando que el color del precio sea el lima también"). Lo que cambia
   para conseguirlo no es el color: es el FONDO. Sobre el panel blanco del
   tema claro el lima da 1,18 y había que degradarlo a --accent-fg, el verde
   oscuro que ya quitamos del radar y de las barras. Ahora el panel va a
   chapa en tema claro (ver más abajo) y el lima vuelve a ser lima.
   Mismo camino que el bloque de puntuaciones: cuando el lima no se lee, se
   oscurece la pieza, no se cambia el lima. */
.precio-row-promo-resultado--mejor .precio-row-promo-resultado-cifra {
  color: var(--accent-fg); font-size: var(--fs-lead); line-height: 1.1;
}
.precio-row-promo-resultado--mejor em {
  font-style: normal; font-weight: 700; font-size: var(--fs-micro); letter-spacing: .01em; color: var(--ink);
}
.precio-row-promo-aviso { margin: .7rem 0 0; padding-top: .65rem; border-top: 1px solid var(--line-2); font-size: var(--fs-micro); color: var(--ink-mute); line-height: 1.5; }
/* ===== ESTÁNDAR DEL PORCENTAJE ================================
   Un porcentaje se pinta SIEMPRE igual en toda la ficha, viva donde viva:
   píldora RELLENA, sin filo, en un tinte del mismo color que el número.
   Lo único que cambia entre sitios es ese color, y cambia porque significa
   algo:
       lima   descuento YA aplicado sobre PVP
       rojo   lo que te ahorra un CUPÓN

   La chapita va SÓLIDA con tinta oscura o blanca encima, no en tinte. Con
   tinte el número comparte color con su propio fondo y se apoya entero en el
   contraste con la fila: el naranja de producción da 1,57 sobre la chapa gris
   del tema claro, o sea ilegible (ese fallo ya está publicado). Sólida, el
   contraste lo pone la propia chapita y sale igual en los dos temas: 12,7 el
   lima con tinta oscura y 5,3 el rojo con tinta blanca.

   Se probó con CONTORNO (06-09-26) y hubo que retirarlo. El problema no era
   el contraste —el filo a currentColor iba sobrado— sino la forma: un anillo
   de 1 px alrededor de cuatro caracteres es una cápsula cuyos tramos rectos
   de arriba y abajo son largos comparados con los casquetes, y se lee como
   un óvalo rebanado. Sergio, tres veces seguidas: "se rompe", "siguen
   descuadradas", "se ve cortado". Un relleno no tiene ese problema porque no
   hay línea fina que cortar, y además no necesita el padding lateral que
   exige el filo, así que no ensancha la fila.

   Tamaño y forma son EXACTAMENTE los de producción (medido en vivo:
   36,5 x 15,6 px, .66rem, padding .05rem/--sp-2, radio 999px). No se toca:
   es el tamaño que hace que el gráfico de seguimiento entre en la primera
   pantalla. */
.precio-row-ahorro,
.precio-row-promo-pct {
  font-family: var(--mono); font-size: var(--fs-micro); font-weight: 700; font-style: normal;
  letter-spacing: -.01em; flex-shrink: 0; white-space: nowrap;
  padding: .05rem var(--sp-2); border-radius: 999px;
}
/* Lima el descuento (06-09-26, Sergio: "se podrían poner los descuentos en
   color lima"). Cambia respecto a lo publicado, donde iba en naranja: el
   naranja pasa a significar solo "mejor precio verificado", que es la otra
   chapa de la tarjeta, y así no hay dos naranjas distintos compitiendo. */
.precio-row-ahorro    { background: var(--accent); color: var(--accent-ink); }
.precio-row-promo-pct { background: var(--promo-solido); color: var(--promo-ink); }
.precio-row-amount {
  grid-area: precio; font-family: var(--mono); font-size: var(--fs-title); font-weight: 700;
  line-height: 1.2; color: var(--accent-fg); white-space: nowrap; justify-self: end;
}

/* Tienda temporalmente sin stock — se mantiene visible (nunca se oculta,
   coherente con la transparencia del resto del sitio) pero atenuada, con un
   badge en vez del botón de compra real. Primer caso: Head Coello Pro 2026
   en Amazon, 2026-08-10. */
.precio-row--agotado { opacity: .55; }
.precio-row a.btn { grid-area: boton; align-self: center; }
.precio-row-agotado-tag {
  grid-area: boton; align-self: center;
  font-family: var(--mono); font-size: var(--fs-label); text-transform: uppercase; letter-spacing: .03em;
  cursor: default; pointer-events: none;
}

.precios-disclaimer { margin-top: .55rem; font-size: var(--fs-label); color: var(--ink-mute); text-align: center; }

/* --- Seguimiento de precio (histórico propio, ver historicoPrecios.js) ---
   Rediseño 2026-09-03 (petición de Sergio: "muchísimo más premium, que se vea
   perfecto y sea intuitivo"). Lo que cambia respecto al trazo suelto de 30px
   que había antes:
     · ejes con carril propio (izquierda para los precios, banda inferior para
       las fechas) — antes la fecha de inicio iba dentro del área y la línea la
       atravesaba en la mitad de los productos;
     · área con degradado bajo el trazo y la línea en acento, no un hilo gris;
     · cursor con la cifra del día por el que pasa el puntero (main.js).
   ------------------------------------------------------------------------- */
/* Chapa, no un bloque separado por una línea (2026-09-03, Sergio: "en la
   versión clara usaríamos nuestro gris metalizado y el lima para seguir el
   estándar de la página"). Mismo tratamiento que `.specs-pill`, `.nota-ring`,
   `.score-card-nota` y las demás chapas del sitio: fondo `--metal-row` en tema
   claro, y por tanto oscuro en LOS DOS temas.
   Eso es lo que desbloquea el lima de verdad: sobre crema, `--accent` tiene
   ~1,1:1 de contraste y desaparece —de ahí que `--accent-fg` se degrade a
   verde oscuro en claro para todo el sitio—, pero sobre la chapa se lee tal
   cual. Al ser oscura siempre, nada de lo que va dentro necesita variante por
   tema: un solo juego de colores fijos (lima puro + blancos translúcidos)
   vale para ambos, igual que dentro de `.nota-ring`. */
.precio-seguimiento {
  margin-top: .75rem; padding: var(--sp-3) var(--sp-4) var(--sp-3);
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius-sm);
  display: flex; flex-direction: column; gap: var(--sp-1);
}
:root[data-theme="light"] .precio-seguimiento { background: var(--metal-row); border-color: transparent; }
/* Los blancos translúcidos de aquí abajo están calibrados para el caso PEOR de
   los dos fondos, que es la chapa clara: es un gris MEDIO (#58585f-#6e6e77),
   no el casi negro de `--surface-2`, así que las opacidades que bastarían en
   tema oscuro dejan el eje ilegible en claro. */
.precio-seguimiento-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.precio-seguimiento-titulo {
  font-family: var(--mono); font-size: var(--fs-micro); text-transform: uppercase;
  letter-spacing: .04em; color: var(--ink-od-mute);
}
/* Recorrido de la serie: es el dato que da (o quita) peso a todo lo que
   afirma el gráfico, así que va en la cabecera y no escondido en el eje. */
.precio-seguimiento-dias {
  font-family: var(--mono); font-size: var(--fs-micro); font-weight: 700; letter-spacing: .03em;
  /* Tinta plena y fondo al .06: la chapita se apoya sobre la chapa, así que
     su propio fondo la aclara todavía más. Con --ink-od-soft sobre un .1 se
     quedaba en 4,19. */
  color: var(--ink-od); background: var(--veil-od);
  border: 1px solid var(--line-od);
  padding: .12rem var(--sp-2); border-radius: 999px; white-space: nowrap;
}

.precio-grafico { position: relative; margin: .15rem 0 .1rem; }
/* `crosshair` es la única pista de que el gráfico se puede consultar; sin ella
   el cursor con el precio del día pasa desapercibido. La clase la pone
   initGraficoPrecio() solo cuando hay serie que consultar — una recta plana no
   la lleva, para no prometer un cursor que no va a aparecer. */
.precio-grafico--interactivo { cursor: crosshair; }
.precio-grafico-svg { width: 100%; height: auto; display: block; overflow: visible; }
/* Líneas de nivel del mínimo y del máximo observados: anclan la etiqueta de
   precio del eje a la altura que describe. Muy tenues a propósito — son la
   cuadrícula, no el dato. */
.precio-grafico-eje line { stroke: var(--line-od); stroke-width: 1; stroke-dasharray: 1 3; vector-effect: non-scaling-stroke; }
/* Un <text> de SVG se pinta con `fill`, no con `color`, así que estos no
   heredan la familia on-dark por la vía normal — hay que dársela a mano.
   El .55 del PVP daba 3,61 y el .6 de las fechas 4,01, los dos por debajo
   de 4,5. Al escalón --ink-od-mute (.68) suben a 4,63. */
.precio-grafico-nivel-texto { font-family: var(--mono); font-size: 8px; fill: var(--ink-od-mute); }
.precio-grafico-nivel-texto--min { fill: var(--ink-od); font-weight: 700; }
.precio-grafico-nivel-texto--pvp { font-size: 7.5px; fill: var(--ink-od-mute); }
.precio-grafico-fecha { font-family: var(--mono); font-size: 8px; fill: var(--ink-od-mute); }

.precio-grafico-area { fill: url(#precio-grafico-degradado); }
/* Paradas del degradado (definido en el sprite, ver layout.js::ICON_SPRITE):
   por CSS y no en el SVG para que cambien con el tema. */
.precio-grafico-degradado-alto { stop-color: var(--accent); stop-opacity: .22; }
.precio-grafico-degradado-medio { stop-color: var(--accent); stop-opacity: .08; }
.precio-grafico-degradado-bajo { stop-color: var(--accent); stop-opacity: 0; }
/* `--accent` y no `--accent-fg`: la chapa es oscura en los dos temas, así que
   aquí el lima puro es legible y no hace falta el verde oscuro al que
   `--accent-fg` se degrada en claro (mismo caso que `.nota-ring-fill`). */
.precio-grafico-linea {
  fill: none; stroke: var(--accent); stroke-width: 1.8;
  stroke-linejoin: round; stroke-linecap: round;
}
/* Línea de PVP: solo se dibuja cuando el precio nunca ha llegado ahí (ver
   trazadoGrafico), así que marca un techo que la serie no toca. Más apagada
   que el trazo del precio a propósito: es contexto, no el dato. */
.precio-grafico-pvp { stroke: var(--line-od-2); stroke-width: 1; }
/* Punto de hoy: el halo lo separa del trazo sin necesidad de agrandar el
   punto, que a este tamaño taparía el último escalón. */
.precio-grafico-hoy { fill: var(--accent); stroke: var(--ink-on-accent); stroke-width: 1.2; }
.precio-grafico-hoy-halo { fill: var(--accent); opacity: .18; }
@media (prefers-reduced-motion: no-preference) {
  .precio-grafico-hoy-halo { animation: precio-grafico-latido 2.8s var(--ease-soft) infinite; transform-box: fill-box; transform-origin: center; }
}
@keyframes precio-grafico-latido {
  0%, 100% { opacity: .16; transform: scale(1); }
  50%      { opacity: .05; transform: scale(1.35); }
}

/* Cursor: aparece al pasar el puntero (o al tocar en móvil) y dice qué precio
   estaba vigente ese día. Ver initGraficoPrecio() en main.js. */
/* `hidden` en un elemento SVG no lo oculta: la hoja del navegador aplica
   `[hidden] { display: none }` solo al HTML. Sin esta regla el cursor salía
   dibujado en el borde izquierdo de todos los gráficos. */
.precio-grafico-cursor[hidden] { display: none; }
.precio-grafico-cursor line { stroke: var(--line-od-2); stroke-width: 1; stroke-dasharray: 2 3; }
.precio-grafico-cursor circle { fill: var(--ink-od); stroke: var(--ink-on-accent); stroke-width: 1.2; }
.precio-grafico-tip {
  position: absolute; top: 0; z-index: 4; pointer-events: none;
  /* `--tx`/`--ty` los pone initGraficoPrecio() para apartar la etiqueta de
     los bordes del gráfico; los valores de aquí son el caso centrado. */
  transform: translate(var(--tx, -50%), var(--ty, -122%)); white-space: nowrap;
  padding: var(--sp-1) var(--sp-3); border-radius: 8px;
  background: var(--surface-od); border: 1px solid var(--line-od);
  box-shadow: var(--sh-2);
  font-family: var(--mono); font-size: var(--fs-micro); line-height: 1.35; color: var(--ink-od);
}
.precio-grafico-tip b { color: var(--accent); }
.precio-grafico-tip i { font-style: normal; color: var(--ink-od-mute); }

/* Misma tipografía que el resto del bloque — antes era un punto más grande y
   cantaba dentro de un bloque tan pequeño. El color sí distingue: acento solo
   cuando el precio está en su mínimo (buena noticia, petición de Sergio),
   gris cuando está por encima. Así el color significa algo en vez de decorar;
   en blanco y negrita, como estaba al principio, era un reclamo que además le
   salía a casi todos los productos. */
.precio-seguimiento-veredicto { font-size: var(--fs-label); color: var(--ink-od-soft); line-height: 1.45; }
.precio-seguimiento-veredicto--minimo { color: var(--accent); }

.precios-pendiente {
  display: flex; flex-direction: column; align-items: center; text-align: center; gap: var(--sp-4);
  padding: var(--sp-8) var(--sp-5); color: var(--ink-mute);
}
.precios-pendiente svg { color: var(--ink-mute); }
.precios-pendiente p { max-width: 34ch; font-size: var(--fs-body-s); line-height: 1.6; margin: 0; }

/* El lateral es estrecho en cualquier viewport (no solo en móvil), así que
   .precio-row se fuerza siempre al layout compacto de 2 columnas — antes
   esto solo pasaba por debajo de 560px, cuando la sección ocupaba el ancho
   completo de la página. */
/* Ver TODAS las tiendas de un vistazo sin scroll dentro del lateral es una de
   las propuestas de valor del sitio (Sergio, 2026-08-20), así que en el
   lateral la fila aprieta un paso más que la de base: mismas cuatro áreas,
   solo con menos aire y un cuerpo de letra por debajo. */
/* `row-gap` y no 0 (2026-09-03, Sergio: "se solapan un poco, por ejemplo lo
   de los cupones; dejaría un poquito más de espacio"): el chip de códigos es
   la única cosa de la línea secundaria con fondo propio, así que sin un
   milímetro de separación se lee pegado a la cifra de arriba y al botón de
   compra de al lado.

   06-09-26: se probó subir ese milímetro a --sp-2 porque la chapita parecía
   cortada por arriba, pero el culpable era el CONTORNO, no el hueco: con
   filo la chapita medía 16,26 px contra los 14,8 de su línea. Devuelta a
   relleno mide 15,6 —lo mismo que en producción— y el hueco de 2,4 px vuelve
   a bastar. Se queda en .15rem: es el valor de producción y el que mantiene
   el gráfico de precio dentro de la primera pantalla. */
/* 06-09-26: el padding se queda en --sp-3. Se probó subirlo a --sp-4 para dar
   aire, pero el aire que hacía falta era el de ENTRE LÍNEAS (el row-gap de
   aquí arriba), no el del borde: medido, subir el padding engordaba la
   tarjeta 49 px y eso echaba el gráfico de seguimiento de precio fuera de la
   primera pantalla a 1440x900 —acababa en 926 con 900 de alto—, que es
   justo lo que Sergio veía "más pulido" en producción. Con --sp-3 el gráfico
   vuelve a entrar entero y el hueco de la chapita sigue arreglado. */
.precios-lateral .precio-row { gap: .15rem var(--sp-4); padding: var(--sp-3); }
.precios-lateral .precio-row--mejor { padding-top: .95rem; }
.precios-lateral .precio-row-logo { width: 1.75rem; height: 1.75rem; font-size: var(--fs-label); }
.precios-lateral .precio-row-vendor { font-size: var(--fs-meta); }
.precios-lateral .precio-row-meta { font-size: var(--fs-micro); }
.precios-lateral .precio-row-amount { font-size: var(--fs-body); }
/* El botón pierde su texto ("Ver en X") en esta vista compacta — el
   nombre de la tienda ya está arriba, repetirlo en el botón solo ocupaba
   sitio que hacía falta para caber junto al precio en la misma fila.
   Sigue siendo un botón con nombre accesible real vía `aria-label`
   (2026-08-20), no un icono huérfano. */
.precios-lateral .precio-row-btn-label { display: none; }
.precios-lateral .precio-row a.btn {
  width: 1.9rem; height: 1.9rem; padding: 0; flex-shrink: 0;
  border-radius: 999px; justify-content: center;
}
/* La etiqueta "Agotado" es texto completo, no un icono — no puede forzarse
   al mismo círculo de 2.15rem que el botón real (el texto se desbordaba).
   Va en la misma grid-area (mismo hueco que el botón) pero como píldora
   que se ajusta a su contenido. */
.precios-lateral .precio-row-agotado-tag {
  flex-shrink: 0; white-space: nowrap;
  padding: var(--sp-1) var(--sp-3); border-radius: 999px; background: var(--surface-2); justify-content: center;
}
/* --- Tema claro: cada tienda es una chapa metalizada --------------------
   2026-09-03. Sergio: "las tarjetas de los vendors también se podrían poner
   con el gris y el lima". Mismo tratamiento que `.specs-pill`, `.nota-ring` y
   el resto de chapas del sitio: fondo `--metal-row` en tema claro, y por tanto
   oscuro en LOS DOS temas.

   Se intentó primero con UNA chapa detrás de toda la lista, y falló dos veces
   por el mismo sitio: con las filas transparentes dejaban de ser tarjetas y
   quedaban en bandas con filete ("se rompen las mini tarjetas de cada
   vendor"), y devolviéndoles su relleno el de la chapa asomaba alrededor como
   un canalón gris. Una chapa por tienda no tiene ese problema — la chapa ES la
   pieza, no su marco — y encima deja la lista y el bloque de seguimiento
   hablando el mismo idioma sin anidar dos superficies oscuras.

   Al ser oscura en los dos temas (igual que `.precio-seguimiento`), todo lo de
   dentro pasa a colores FIJOS: lima puro para el precio —en claro,
   `--accent-fg` lo degradaría a verde oscuro— y blancos translúcidos para el
   texto. Los discos de logo se quedan en `--surface-2`, o sea crema: así los
   logos siguen usando su variante clara (trazo oscuro) y no hay que tocar el
   interruptor `--dark`/`--light`, que es donde ya nos equivocamos una vez con
   el icono de SAPI.

   El estado "sin tiendas" no se ve afectado: ahí no hay `.precio-row` ninguna
   (se pinta `.precios-pendiente` en su lugar).
   ------------------------------------------------------------------------ */
:root[data-theme="light"] .precios-lateral .precio-row {
  background: var(--metal-row); border-color: transparent;
}
/* La regla de arriba pone el gris a TODAS las tarjetas y gana por
   especificidad, así que la elegida necesita que se le repita aquí su acabado
   propio. Mismo valor, un solo sitio donde tocarlo (`--metal-elegida`). */
:root[data-theme="light"] .precios-lateral .precio-row--mejor {
  background: var(--metal-elegida); border-color: var(--line-accent);
}
:root[data-theme="light"] .precios-lateral .precio-row-vendor { color: var(--ink-od); }
:root[data-theme="light"] .precios-lateral .precio-row-meta { color: var(--ink-od-mute); }
/* La fila del mejor precio va sobre --metal-elegida (verde oliva oscuro) en
   LOS DOS temas, así que su meta necesita tinta on-dark también en oscuro:
   ahí heredaba --ink-mute (#8f8f8f) y sobre el oliva daba 3,07. */
.precio-row--mejor .precio-row-meta { color: var(--ink-od-mute); }
:root[data-theme="light"] .precios-lateral .precio-row-nota { color: var(--ink-od-mute); }
:root[data-theme="light"] .precios-lateral .precio-row-amount { color: var(--accent); }
/* Sin excepción de tema claro: --descuento vale igual en los dos, porque en
   los dos la fila es oscura. Aquí había un `rgba(255,120,70,.2)` con
   `#ffb599` encima — 2,66 sobre la chapa gris. */
/* El botón de cupones NO lleva excepción de tema claro: su rojo (--promo) es
   el mismo en los dos, porque en los dos se apoya sobre una superficie
   oscura. Aquí hubo un `rgba(255,120,70,.2)` con `#ffb599` encima —1,54 de
   contraste, ilegible— y después una excepción que pintaba de gris el cupón
   que no mejora el precio; esa se retira al pasar el botón a rojo siempre. */
/* Agotado: el .55 de opacidad que basta sobre fondo claro deja la fila casi
   ilegible sobre la chapa, así que aquí se atenúa menos. */
:root[data-theme="light"] .precios-lateral .precio-row--agotado { opacity: .72; }
/* Fondo al .06 y no al .12: sobre la chapa oscurecida ese .12 subía el chip
   a rgb(94,94,102) y la tinta se quedaba en 4,47. Al .06 da 5,14. (La
   opacidad .72 de la fila no entra en esa cuenta: atenúa chip y tinta a la
   vez, así que el ratio entre ambos no cambia.) */
:root[data-theme="light"] .precios-lateral .precio-row-agotado-tag {
  background: var(--veil-od); color: var(--ink-od-soft);
}

.precios-lateral .precios-pendiente { padding: var(--sp-6) var(--sp-1); }
.precios-lateral .precios-pendiente svg { width: 18px; height: 18px; }
.precios-lateral .precios-pendiente p { font-size: var(--fs-meta); max-width: none; }

/* En un móvil de 375 px la tarjeta se queda en ~300 px y la línea secundaria
   no da para el PVP tachado, el ahorro, la frescura del dato Y el chip de
   códigos a la vez: el texto se cortaba en "hace 3 …". El chip baja a su
   propia línea antes que dejar el dato a medias — sigue pegado al mismo borde
   derecho, así que el panel desplegable no se mueve de sitio. */
@media (max-width: 560px) {
  .precio-row-meta { flex-wrap: wrap; }
  .precio-row-meta > span { flex: 1 0 100%; }
  /* Un punto menos en el nombre y en la cifra: con los cuerpos de escritorio,
     "Tienda Padelpoint" se queda a cuatro píxeles de caber y salía cortado
     ("Tienda Padelp…") justo en la fila del mejor precio. El recorte con
     puntos suspensivos sigue ahí como red por si entra una tienda de nombre
     más largo. */
  .precios-lateral .precio-row-vendor { font-size: var(--fs-meta); }
  .precios-lateral .precio-row-amount { font-size: var(--fs-body); }
}

/* ============================================================================
   INICIO — Portada de marca (/marca/<id>/), rama `marca-pagina-rica`
   La página de marca deja de ser una rejilla plana y pasa a tener jerarquía:
   cabecera con contexto + botón al catálogo, novedades, destacados y, al
   final, la rejilla completa de siempre bajo el ancla `#todos`. Casi todo
   reutiliza clases existentes (.section-head--tight, .section-sub, .kicker,
   .compare-grid, .guide-row, .btn.btn-accent.btn-lg, .pager); lo de aquí
   abajo es solo el ritmo vertical de los bloques nuevos.
   ========================================================================= */

/* El botón cuelga de la cabecera, no del bloque siguiente: por eso el margen
   grande va debajo y arriba solo se recupera el hueco que `.section-head--tight`
   ya dejó (1.75rem). */
.brand-actions { margin: 0 0 1rem; }

/* Novedades y Destacados dentro de la MISMA `.section` que la cabecera, no
   cada uno en su propia sección: con el `padding-block: 6rem` de `.section`, tres secciones seguidas dejaban una pantalla casi vacía entre el h1 y la primera tarjeta. 4rem entre bloques separa lo justo sin ese vacío. */ .brand-block { margin-top: 4rem; }
.brand-block > .section-head { margin-bottom: 1.5rem; }
.brand-block .section-sub { font-size: var(--fs-body); }

/* El ancla del botón: sin esto el título "Todos los productos" queda pegado al
   borde superior de la ventana (y debajo de la cabecera fija) al saltar. */
#todos { scroll-margin-top: 5rem; }

/* FIN — Portada de marca
   ========================================================================= */

/* =============================================================
   Piezas que solo existen en un tema
   =============================================================
   Cinco componentes traen dos variantes del mismo grafismo —el logo en
   su versión clara y en la oscura, el sol y la luna del interruptor— y
   enseñan una u otra según el tema. Eran DIEZ reglas repitiendo el mismo
   par junto a cada componente. Aquí van juntas: el mecanismo es uno.

   La excepción es `.nav .nav-logo-icon`, que va justo al revés y por eso
   se queda aparte, con su `!important` y su explicación: el logo del nav
   se apoya sobre chapa oscura también en tema claro, así que ahí manda la
   variante oscura pase lo que pase.

   Especificidad (0,2,0) frente al (0,1,0) de las reglas base, así que
   ganan estén donde estén en el fichero. */
[data-theme="light"] .ph-logo--dark,
[data-theme="light"] .nav-logo-icon--dark,
[data-theme="light"] .theme-toggle-icon--sun,
[data-theme="light"] .marca-logo--dark,
[data-theme="light"] .precio-row-logo-img--dark { display: none; }
[data-theme="light"] .ph-logo--light,
[data-theme="light"] .nav-logo-icon--light,
[data-theme="light"] .theme-toggle-icon--moon,
[data-theme="light"] .marca-logo--light,
[data-theme="light"] .precio-row-logo-img--light { display: block; }

/* =============================================================
   Movimiento reducido
   =============================================================
   Quien tiene activado "reducir movimiento" en su sistema suele hacerlo
   por vértigo, migraña o mareo — no por preferencia estética. Hasta ahora
   se respetaba a medias: cuatro reglas sueltas cubrían los reveals, el
   panel de búsqueda y los esqueletos, pero el resto del sitio seguía
   moviéndose. Y lo peor quedaba fuera: la marquesina de marcas, que gira
   sin parar 32 segundos por vuelta, que es justo el caso para el que
   existe el ajuste.

   El `!important` está justificado: esta regla tiene que ganarle a
   cualquier otra del fichero, venga de donde venga. (Los otros nueve del
   fichero son interruptores de logo, que también tienen su motivo.)

   Se acelera a .01ms en vez de poner `none` a propósito: así los estados
   finales se aplican igual (el .reveal llega a opacity:1, el hover cambia
   de color) pero sin recorrido. Con `transition: none` a secas hay
   navegadores que no disparan el `transitionend` del que depende algún
   guion. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  /* La marquesina se PARA, no se acelera: con la receta de arriba correría
     su vuelta entera en .01ms y la ristra se quedaría clavada a mitad de
     recorrido (translateX(-50%)), con los nombres cortados. */
  .marquee-track { animation: none !important; }
}
