/* ==========================================================================
   TODOBAZAR — home
   Hoja de estilos. Bloque :root con la paleta FIRMADA por Pixun el
   2026-09-13 (revisión 3) — ver docs/revision-3-linea-grafica.md. Regla dura
   del cliente: ningún color fuera de :root / .section--inverted en el resto
   de la hoja — todo por var(--token). Tipografía: Montserrat (cuerpo/UI,
   dato del 2026-08-30) + Cooper Heavy (titulares, dato del 2026-09-13).
   ========================================================================== */

/* @font-face rules removed here on purpose — theme.json prints them
   (settings.typography.fontFamilies, cooper-heavy + montserrat). */

:root {
  /* ============================================================
     COLOR — paleta firmada por Pixun, 2026-09-13 (revisión 3).
     Nombres del catálogo Dexigner; no se cambian ni se inventan otros,
     salvo --accent-2, autorizado explícitamente por nombre.
     ============================================================ */
  --bg:              var(--wp--preset--color--bg); /* fondo "claro": 02, 03, 05, 07, 10 */
  --bg-pure:         var(--wp--preset--color--bg-pure); /* fondo oscuro extremo: 01 y 09 (aplicado directo, no vía --bg) */
  --bg-elevated:     var(--wp--preset--color--bg-elevated); /* fondo "gris": 04, 06, 08, 11 — nivel de sección, no de tarjeta */
  --bg-elevated-2:   var(--wp--preset--color--bg-elevated-2); /* superficie elevada DENTRO de una sección (caja de precio
                                  tapada del paso 3 de la 06; rim blanco de la tarjeta de
                                  familia; fondo de tarjeta si necesita distinguirse del
                                  --bg-elevated de su sección) */
  --accent:          var(--wp--preset--color--accent); /* rojo de marca — fill del botón "sticker" primario, ring de
                                  los puntos activos del carrusel, hover fantasma */
  --accent-dim:      var(--wp--preset--color--accent-dim); /* fondo entintado de --accent: hover de botón
                                  fantasma, fila activa */
  --accent-2:        var(--wp--preset--color--accent-2); /* NUEVO, autorizado por nombre (brief §1) — amarillo de marca:
                                  fill del botón secundario y del precio-tag de la 05 */
  --text-primary:    var(--wp--preset--color--text-primary); /* titulares y texto de alto énfasis sobre fondo claro */
  --text-secondary:  var(--wp--preset--color--text-secondary); /* texto de apoyo (bajadas, cuerpo) sobre fondo claro */
  --text-tertiary:   var(--wp--preset--color--text-tertiary); /* leyendas / texto terciario sobre fondo claro */
  --border:          var(--wp--preset--color--border); /* filete fino: tarjetas, inputs, divisores de la 04, filete del
                                  pie. Grosor 1px — ver nota de grosores más abajo */
  --border-strong:   var(--wp--preset--color--border-strong); /* borde GRUESO — botones sticker/pill, campos, paso 3 de la 06,
                                  bloque de la 07. Ancho 2-3px según componente (brief §4) */
  --hero-scrim:      rgba(35, 31, 32, .68); /* velo de legibilidad sobre el fondo fotográfico
                                  del héroe — mismo tono que --bg-pure. Medido en vivo
                                  (revisión 4, brief §03): con .55 el eyebrow (texto normal,
                                  no "grande") daba 3.24:1 sobre el fallback --bg-elevated-2 —
                                  no llega a 4.5:1. .68 da 4.68:1, con margen. */

  /* ============================================================
     TIPO — Montserrat dada por Pixun el 2026-08-30 (cuerpo/UI); Cooper
     Heavy dada el 2026-09-13 (titulares, revisión 3 §2). Ya NO son el mismo
     valor: --font-display es la pila de titulares, --font-body la de
     cuerpo/interfaz. Cooper Heavy es un único peso real (800) — los
     titulares llevan font-weight:800 + font-synthesis:none, nunca 700
     (brief §2, evita el faux bold). La escala de tamaños sigue siendo
     proporción, no elección tipográfica.
     ============================================================ */
  --font-display: var(--wp--preset--font-family--cooper-heavy);
  --font-body:    var(--wp--preset--font-family--montserrat);

  /* Escala de tamaños vía clamp() — proporción, no elección tipográfica.
     Nombres NUEVOS fuera de la familia estándar del catálogo, autorizados
     explícitamente para esta tarea (ver nota al principio del documento). */
  /* Revisión 4 — escala tomada de la maqueta de Pixun (medida a 1440px),
     no ya una proporción libre del estudio. --fs-h1 llega a 66px (hero,
     brief §"Type scale"); --fs-h2 es el suelo por defecto (ningún h2
     visible lo usa hoy sin más — las seis secciones con titular propio
     llevan su propio clamp() en la clase de sección, ver más abajo). Los
     dos usan la misma técnica: recta que vale el mínimo a 380px y el
     máximo medido a 1440px, con clamp() como cinturón de seguridad en los
     dos extremos. */
  --fs-h1:      clamp(2.125rem, 1.408rem + 3.02vw, 4.125rem);  /* 34px → 66px */
  --fs-h2:      clamp(1.625rem, 1.177rem + 1.887vw, 2.875rem); /* 26px → 46px */
  --fs-h3:      clamp(1.125rem, 1.02rem + 0.5vw, 1.375rem); /* 18px → 22px — títulos de paso/tarjeta */
  --fs-body:    clamp(1rem, 0.94rem + 0.25vw, 1.0625rem);   /* 16px → 17px */
  --fs-small:   clamp(0.8125rem, 0.79rem + 0.1vw, 0.875rem);/* 13px → 14px — leyendas, eyebrow, aviso legal */

  --lh-tight:  1.1;  /* H1 / H2 */
  --lh-normal: 1.5;  /* body corto: botones, campos, leyendas */
  --lh-loose:  1.6;  /* bajadas y párrafos largos */

  /* ============================================================
     ESPACIADO — escala de 4px. Índice × 4 = px (misma lógica que
     el resto del catálogo). --sp-5/-10/-20 son intermedios NUEVOS:
     esta maqueta es más densa que northbridge (formularios, tarjetas
     apretadas) y necesita pasos más finos que 4→8→12→16→24.
     ============================================================ */
  --sp-1:  4px;
  --sp-2:  8px;
  --sp-3:  12px;
  --sp-4:  16px;
  --sp-5:  20px;  /* NUEVO — padding de campo, gap fino de lista */
  --sp-6:  24px;
  --sp-8:  32px;
  --sp-10: 40px;  /* NUEVO — suelo de section-pad */
  --sp-12: 48px;
  --sp-16: 64px;
  --sp-20: 80px;  /* NUEVO — inset desktop de la 05 a 1440 */
  --sp-24: 96px;
  --sp-32: 128px;

  /* Padding vertical de sección — tres niveles, no uno.
     El wireframe distingue explícitamente secciones "altas" (03, 05),
     normales (06, 07, 08, 09, 10) y "bajas/compactas" (01, 04, 11);
     un único --section-pad los trataría igual y sería impreciso. */
  --section-pad:    clamp(2.5rem, 6vw, 6rem);  /* 40px → 96px — 06, 07, 08, 09, 10 */
  --section-pad-lg: clamp(3rem, 8vw, 8rem);    /* 48px → 128px — 03 (héroe), 05 (familias) */
  --section-pad-sm: clamp(1.5rem, 4vw, 3rem);  /* 24px → 48px — 04 (franja), 11 (pie compacto) */

  --container-max: 1400px; /* revisión 5, punto 1 (antes 1280px) */
  --gutter: clamp(1rem, 4vw, 2rem); /* 16px → 32px */

  /* ============================================================
     MOTION — revisión 5, punto 15. Números de motion-lab (revealOffset,
     stagger) sin cambiar; duración y curva son la traducción a CSS del
     muelle {60,26,1.2}, derivada en docs/revision-5-rectificaciones.md
     > Motion (ζ≈1.53 sobreamortiguado, sin overshoot; 650ms/curva
     ease-out con el mismo perfil que el ~90% de llegada del muelle).
     ============================================================ */
  --reveal-offset:   28px;
  --reveal-stagger:  60ms;
  --reveal-duration: 650ms;
  --reveal-ease:     cubic-bezier(.22, 1, .36, 1);

  /* ============================================================
     CABECERA FIJA — revisión 1, punto 1. Un solo token para el alto
     real de la cabecera: lo leen .site-header (altura explícita),
     scroll-margin-top de toda sección con id de ancla, y cualquier
     sticky posterior — nunca un número repetido en tres sitios.
     Móvil MÁS BAJA que escritorio (decisión del estudio, leída de
     la recomendación de Pixun — pendiente de confirmar, ver README).
     ============================================================ */
  --header-h: 56px; /* base/móvil. Redefinido a 72px desde 960px, ver query junto a .site-header */

  /* ============================================================
     RADIOS
     ============================================================ */
  --radius-sm:   6px;   /* botones, inputs, miniaturas de la 09 */
  --radius-md:   12px;  /* tarjetas: 05, 06, bloque de la 07 */
  --radius-pill: 999px; /* cápsula de estado de la 09, CTA de la 09, puntos del carrusel */

  /* ============================================================
     SOMBRAS — ronda de corrección 2026-08-30: eran valores rgba()
     literales sueltos en la regla que las usaba (cero literales de
     color fuera de este bloque es regla dura del cliente). Cada
     token lleva la sombra COMPLETA (offset + blur + color), no solo
     el color, porque no hay ningún otro sitio de la hoja que
     necesite ese offset/blur con un color distinto.
     ============================================================ */
  --shadow-card: 0 8px 24px rgba(35,31,32,.12); /* hover de tarjeta ≥768px: familias (05).
                                  Único lugar que la usa. */
  --shadow-bar:  0 -2px 12px rgba(35,31,32,.14); /* barra fija de móvil (12), sombra hacia
                                  arriba para despegarla del contenido que desliza
                                  detrás. Único lugar que la usa. */
  /* Round 1 de fixes (validator warning) — mismos dos literales que ya
     estaban sueltos en revisión 4: la esquina despegada (drop-shadow) y el
     marco de teléfono decorativo (box-shadow, 09 retail__phone). Mismo tono
     ink (35,31,32) que --shadow-card/--shadow-bar, valores sin cambiar.
     Revisión 6, punto 4 — la esquina despegada de 09 retail__cta::after se
     retiró (Pixun); este token queda con un único uso, no se borra. */
  --shadow-sticker-peel: drop-shadow(1px -1px 1.5px rgba(35,31,32,.45)); /* esquina despegada,
                                  10 request__submit-sticker::after. Único lugar que la usa. */
  --shadow-phone: 0 12px 32px rgba(35,31,32,.45); /* marco de teléfono, 09 retail__phone.
                                  Único lugar que la usa. */
}

/* ============================================================
   INVERSIÓN — 01 y 09 (fondo oscuro liso) y, desde la revisión 1,
   también 03 (héroe: fondo fotográfico + velo oscuro var(--hero-scrim),
   texto claro encima). Reasigna las MISMAS variables de lectura dentro
   de su propio scope; el resto de componentes (botón, texto, borde) no
   necesita una versión "oscura" propia — lee la variable que ya
   usa siempre, y aquí vale lo otro. Esto es lo que hace que un
   botón o una tarjeta sean trasladables entre fondo claro y
   fondo oscuro sin reescribirlos. En el héroe, `background:
   var(--bg-pure)` de esta clase queda DEBAJO de las imágenes/velo
   (que son hijos posicionados con z-index propio) — sirve de
   fallback mientras la foto carga o si el fichero no existe.
   ============================================================ */
.section--inverted,
.is-style-inverted {
  --text-primary:   #F7EFDA;
  --text-secondary: #C5BEAE;
  --text-tertiary:  #9E978C;
  --border:         #494441;
  --border-strong:  #F7EFDA; /* botones/campos con borde grueso dentro de una sección invertida */
  --bg-elevated:    #34302F;
  --bg-elevated-2:  #3C3836;
  --accent-dim:     rgba(251,62,22,.25); /* más intensidad que en claro: sobre --bg-pure,
                                  .15 de alpha se pierde */
  background: var(--bg-pure);
  /* B1, ronda de validación 2026-08-30: redefinir --text-primary aquí NO
     re-resuelve un `color` ya heredado — `body` (más abajo) declara
     `color: var(--text-primary)` y ESE valor (#1A1A1A) es el que se
     hereda como color computado, no la variable en sí. Cualquier
     descendiente de .section--inverted que no declare su propio `color`
     (el H2 de la 09 era el caso real, casi negro sobre negro, ~1.09:1)
     se queda con el negro heredado de body. Declarar `color` aquí mismo
     es lo que fuerza una resolución fresca de la variable ya reasignada. */
  color: var(--text-primary);
}

/* ==========================================================================
   RESET / BASE
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

/* Revisión 1, punto 2 — desplazamiento suave en anclas internas. El valor
   SMOOTH vive dentro de su propia media query (no-preference) en vez de
   ponerse suelto en `html` y confiar solo en el !important del bloque
   reduce de más abajo: así el comportamiento por defecto sin JS y sin
   preferencia expresada es explícito, no un efecto colateral de otra
   regla. El bloque @media (prefers-reduced-motion: reduce) ya existente
   más abajo en este fichero fuerza `auto` con !important — no se crea un
   segundo bloque reduce, se reutiliza el que ya hay. */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

/* Revisión 1, punto 1 — scroll-margin-top en toda sección con id de
   ancla, igual al alto real de la cabecera fija. Sin esto, cualquier
   salto a una sección deja su titular tapado por la cabecera sticky.
   Selector genérico por [data-section][id] en el prototipo estático.

   Fase 2 (docs/port-phase-2.md, "block validation trap") — [data-section]
   no puede viajar al marcado de bloque núcleo (mismo motivo que
   [data-reveal]): la clase `.section-anchor` lo sustituye en las 8
   secciones de patterns/*.php que llevan `anchor` (hero, capacity,
   assortment, how-to-buy, tariff, logistics, retail, contact). REGRESIÓN
   real encontrada en vivo (sesión de fase 2): sin esta regla, un salto a
   #tariff aterrizaba con el titular tapado por la cabecera fija de
   132px — medido con getBoundingClientRect() antes y después del cambio. */
[data-section][id],
.section-anchor,
#main {
  scroll-margin-top: var(--header-h);
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-normal);
  -webkit-font-smoothing: antialiased;
}
img, svg { display: block; max-width: 100%; }
ul, ol { margin: 0; padding: 0; list-style: none; }
h1, h2, h3, p { margin: 0; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
input, textarea { font: inherit; color: inherit; }

h1, h2, h3 { font-family: var(--font-display); font-weight: 800; font-synthesis: none; line-height: var(--lh-tight); }
h1 { font-size: var(--fs-h1); }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* .section--inverted en el HTML (no solo aquí) — es lo que da el par
   background/color correcto por token: esa clase ya fija --bg-pure de
   fondo y --text-primary a blanco en su scope. Un color literal aquí
   habría sido invisible en cuanto el fondo es siempre oscuro. */
.skip-link {
  position: absolute;
  top: -100%;
  left: var(--sp-4);
  color: var(--text-primary);
  padding: var(--sp-3) var(--sp-5);
  border-radius: var(--radius-sm);
  z-index: 200;
  transition: top .2s ease;
}
.skip-link:focus {
  top: var(--sp-3);
}

/* Foco visible — mismo mecanismo en fondo claro y en el oscuro de la 09.
   --text-primary ya vale casi-negro en claro y blanco dentro de
   .section--inverted, así que un único par de líneas da contraste alto en
   los dos contextos sin condicional (brief §4). */
:focus { outline: none; }
:focus-visible {
  outline: 2px solid var(--text-primary);
  outline-offset: 2px;
}
/* Revisión 6, punto 5 — decisión de diseño de Pixun: los campos de
   formulario (input, textarea) pierden el anillo de :focus-visible.
   SOLO los campos — botones y enlaces conservan el suyo, de ahí la
   exclusión puntual en vez de tocar la regla genérica de arriba. */
input:focus-visible,
textarea:focus-visible {
  outline: none;
}

.container {
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section-lead {
  color: var(--text-secondary);
  line-height: var(--lh-loose);
}

/* ==========================================================================
   BOTONES
   Los botones "rellenos" se maquetan en gris oscuro neutro (equivalente a
   --bg-pure), no en --accent — brief §5 nota 10: --accent en gris no llega
   a 3:1 de contraste como fondo de botón sin borde propio. --accent queda
   reservado para detalles pequeños (punto de carrusel activo, hover
   fantasma). Cuando llegue la paleta real, esta decisión se puede revisar.
   ========================================================================== */

/* Fase 2 (docs/port-phase-2.md) — `.wp-block-button__link` añadido a la
   base: sin esto, un botón de bloque (quicklane, barra móvil, 09) solo
   recibía color/filete/sombra del estilo is-style-*, nunca el tamaño base
   (min-height, padding, radio, tipo) — se quedaba con el padding por
   defecto de core (wp-includes/blocks/button/style.css), más alto que el
   sticker real. Medido: barra móvil a 380px, 80.34px en vez de ~73px.
   `.wp-block-button.header-cta .wp-block-button__link` (más abajo, mayor
   especificidad) sigue ganando para ese CTA concreto, que es más grande
   a propósito — sin regresión. */
.btn,
.wp-block-button__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 48px;
  padding: var(--sp-3) var(--sp-6);
  border-radius: var(--radius-pill);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  font-weight: 700;
  line-height: var(--lh-normal);
  text-align: center;
  border: 1px solid transparent;
}

/* Componente 1 (brief §4) — botón "sticker" rojo: relleno --accent, texto
   SIEMPRE en --bg-pure (dentro de .section--inverted --text-primary vale
   cream, que falla contraste sobre rojo — de ahí el color fijo aquí, no
   heredado). Efecto "presionado" en vez del filter: brightness(.9) de
   antes (regla dura §3: nada aclara/oscurece el rojo bajo el texto). */
/* Block style "Pegatina roja" (register_block_style, functions.php) — mismo
   tratamiento que .btn--primary; selector añadido a cada regla existente,
   sin duplicar declaraciones (brief §5). */
.btn--primary,
.wp-block-button.is-style-sticker-red .wp-block-button__link {
  background: var(--accent);
  /* Port fix C6.1 — Pixun, dos veces: "los botones ... que son rojos,
     coloca el texto blanco". Antes var(--bg-pure) (ink); ahora el preset
     directo, no el token corto, porque un .section--inverted podria
     redefinir --bg-elevated-2 y aqui no debe (verificado: no lo hace, pero
     el preset es la garantia). AA solo a texto grande (brief C6, su
     decision, sin compensar con tamano/peso). Un color de texto puesto por
     Pixun en el panel del bloque sigue ganando: preset -> has-*-color trae
     !important en el stylesheet de global-styles; color personalizado ->
     inline style. Esta regla no lleva !important, asi que pierde ante los
     dos casos (comprobado en vivo, global-styles-inline-css). */
  color: var(--wp--preset--color--bg-elevated-2);
  border: 3px solid var(--bg-pure);
  /* Revisión 5, punto 10: mecánica del CTA de cabecera (.header-cta,
     más abajo) en todos los botones — 6px en botones de 48px (8px es
     solo para el CTA de cabecera, que es más grande). Antes 4px. */
  box-shadow: 6px 6px 0 var(--bg-pure);
  transition: transform .15s ease, box-shadow .15s ease;
}
.section--inverted .btn--primary,
.section--inverted .wp-block-button.is-style-sticker-red .wp-block-button__link,
.is-style-inverted .wp-block-button.is-style-sticker-red .wp-block-button__link {
  background: var(--accent);
  /* Port fix C6.1 — mismo cambio y mismo motivo que la regla base de
     arriba. */
  color: var(--wp--preset--color--bg-elevated-2);
  border-color: var(--bg-pure);
}
.btn--primary:hover,
.wp-block-button.is-style-sticker-red .wp-block-button__link:hover {
  transform: translate(3px, 3px);
  box-shadow: 3px 3px 0 var(--bg-pure);
}
.btn--primary:active,
.wp-block-button.is-style-sticker-red .wp-block-button__link:active {
  transform: translate(6px, 6px);
  box-shadow: 0 0 0 var(--bg-pure);
}
/* Revisión 7, punto 3 — defecto nuestro (revisión 6 punto 9, hallado por la
   sesión de cuenta, confirmado en f3aa0de): este bloque de cancelación VIVÍA
   aquí, pero varias de las reglas que tiene que anular (`.quicklane__cta:hover`,
   dentro de `@media (min-width: 960px)`; `.header-cta:hover`; `.tariff__submit:hover`,
   que ni siquiera estaba en la lista) están declaradas MÁS ABAJO en esta misma
   hoja — y a igual especificidad, en CSS gana quien viene después en el
   documento, sin que importe si vive dentro de otro `@media`. Con el bloque
   aquí arriba, esas reglas posteriores seguían ganando bajo reduced-motion:
   el botón se seguía moviendo. El bloque se traslada al final físico del
   fichero (sección REDUCED MOTION), después de CUALQUIER regla que pueda
   necesitar anular — ver ese bloque para el contenido real. */

/* Componente 2 (brief §4) — botón amarillo: SIEMPRE con filete ink (§3:
   1.47:1 sin él sobre cream), sin sombra. Sustituye tanto la regla clara
   como la de .section--inverted de antes: ya no cambia con el scope. */
/* Block style "Pegatina amarilla" — mismo tratamiento que .btn--outline;
   selector añadido, sin duplicar declaraciones (brief §5). */
.btn--outline,
.wp-block-button.is-style-sticker-yellow .wp-block-button__link {
  background: var(--accent-2);
  color: var(--bg-pure);
  border: 3px solid var(--bg-pure);
  /* Revisión 5, punto 10 — mismo mecanismo que .btn--primary/.header-cta.
     Sombra ink: válida sobre un fondo claro (mobile-bar, --bg). Dentro de
     una sección invertida el fondo YA es --bg-pure, así que una sombra
     ink se fundiría con él — ver la excepción de `.retail__cta` (09, más
     abajo), que la recolorea a crema para que siga dibujando el gesto. */
  box-shadow: 6px 6px 0 var(--bg-pure);
  transition: transform .15s ease, box-shadow .15s ease;
}
.btn--outline:hover,
.wp-block-button.is-style-sticker-yellow .wp-block-button__link:hover {
  transform: translate(3px, 3px);
  box-shadow: 3px 3px 0 var(--bg-pure);
}
.btn--outline:active,
.wp-block-button.is-style-sticker-yellow .wp-block-button__link:active {
  transform: translate(6px, 6px);
  box-shadow: 0 0 0 var(--bg-pure);
}
.section--inverted .btn--outline,
.section--inverted .wp-block-button.is-style-sticker-yellow .wp-block-button__link,
.is-style-inverted .wp-block-button.is-style-sticker-yellow .wp-block-button__link {
  background: var(--accent-2);
  color: var(--bg-pure);
  border-color: var(--bg-pure);
}

/* Componente 3 (brief §4) — botón negro con filete cream: relleno
   --bg-pure, texto y filete interior SIEMPRE en --bg (cream en todo
   scope), anillo ink exterior vía box-shadow. Comparte tratamiento con la
   cápsula de la 01 (.quicklane__cta, ver su regla de escritorio más abajo
   — la excepción de esquina recta que tenía queda retirada, ya es
   píldora). En una sección invertida el relleno coincide con el fondo de
   la sección: lo único que queda visible es el anillo cream + el texto —
   efecto "sticker" intencionado, no un error de contraste (fill/anillo no
   son el borde que da la forma, lo da el filete cream). */
/* Block style "Píldora tinta" — mismo tratamiento que .btn--pill; selector
   añadido, sin duplicar declaraciones (brief §5). */
.btn--pill,
.wp-block-button.is-style-pill-ink .wp-block-button__link {
  border-radius: var(--radius-pill);
  background: var(--bg-pure);
  color: var(--bg);
  border: 2px solid var(--bg);
  /* Revisión 5, punto 10 — se añade la sombra de presión (segundo valor,
     6px) SIN tocar el anillo existente (primer valor, el "sticker" de
     siempre). En fondo claro el anillo ink ya hace de sombra visible; en
     una sección invertida (fondo ink) esa sombra ink se funde con el
     fondo — la variante de abajo la recolorea a crema para ese caso, el
     mismo criterio que .retail__cta. */
  box-shadow: 0 0 0 3px var(--bg-pure), 6px 6px 0 var(--bg-pure);
  transition: transform .15s ease, box-shadow .15s ease;
}
.btn--pill:hover,
.wp-block-button.is-style-pill-ink .wp-block-button__link:hover {
  transform: translate(3px, 3px);
  box-shadow: 0 0 0 3px var(--bg-pure), 3px 3px 0 var(--bg-pure);
}
.btn--pill:active,
.wp-block-button.is-style-pill-ink .wp-block-button__link:active {
  transform: translate(6px, 6px);
  box-shadow: 0 0 0 3px var(--bg-pure), 0 0 0 var(--bg-pure);
}
.section--inverted .btn--pill,
.section--inverted .wp-block-button.is-style-pill-ink .wp-block-button__link,
.is-style-inverted .wp-block-button.is-style-pill-ink .wp-block-button__link {
  box-shadow: 0 0 0 3px var(--bg-pure), 6px 6px 0 var(--bg);
}
.section--inverted .btn--pill:hover,
.section--inverted .wp-block-button.is-style-pill-ink .wp-block-button__link:hover,
.is-style-inverted .wp-block-button.is-style-pill-ink .wp-block-button__link:hover {
  box-shadow: 0 0 0 3px var(--bg-pure), 3px 3px 0 var(--bg);
}
.section--inverted .btn--pill:active,
.section--inverted .wp-block-button.is-style-pill-ink .wp-block-button__link:active,
.is-style-inverted .wp-block-button.is-style-pill-ink .wp-block-button__link:active {
  box-shadow: 0 0 0 3px var(--bg-pure), 0 0 0 var(--bg);
}

/* ==========================================================================
   PLACEHOLDERS DE IMAGEN
   Sobrios, con proporción reservada (aspect-ratio) para que no haya salto
   de maqueta. Sin fotos reales y sin descargar nada de terceros.
   ========================================================================== */

.media-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-elevated-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text-tertiary);
  overflow: hidden;
}
.media-placeholder__icon {
  width: 15%;
  min-width: 32px;
  max-width: 72px;
  height: auto;
}

/* ==========================================================================
   01 · CARRIL RÁPIDO
   ========================================================================== */

.quicklane {
  padding-block: var(--sp-6); /* revisión 5, punto 14 (antes var(--sp-3)) */
}
.quicklane__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
}
.quicklane__link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  width: 100%;
  font-size: var(--fs-small);
}
.quicklane__text {
  color: var(--text-primary);
}
.quicklane__cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  min-width: 44px;
  min-height: 44px;
}
.quicklane__cta-label { display: none; }
.quicklane__chevron { width: 24px; height: 24px; color: var(--text-primary); }

@media (min-width: 960px) {
  .quicklane__inner { justify-content: center; position: relative; }
  .quicklane__link { justify-content: center; }
  .quicklane__text { text-align: center; }
  .quicklane__cta {
    position: absolute;
    /* M4, ronda de validación 2026-08-30: right:0 mide contra la padding-box
       del ancestro posicionado (.quicklane__inner), que YA lleva el gutter
       como padding — o sea, ignoraba ese gutter y quedaba pegado al canto
       real, ~21px más a la derecha que el CTA de cabecera a 1280. Con
       right: var(--gutter) queda alineado con él. */
    right: var(--gutter);
    min-width: 0;
    min-height: 0;
    border-radius: var(--radius-pill);
    /* Componente 3 del brief (revisión 3, §4): mismo tratamiento que
       .btn--pill — fill --bg-pure, filete interior cream (--bg), anillo
       ink exterior. Ya no es la cápsula clara de antes: es el sticker
       negro con filete cream, y ya no lleva la excepción de esquina recta
       (era el único sitio de la hoja con border-radius:0 a propósito —
       retirada aquí porque el brief la da por superada, ahora es píldora
       en todo el sitio). */
    background: var(--bg-pure);
    color: var(--bg);
    padding: var(--sp-2) var(--sp-5);
    border: 2px solid var(--bg);
    /* Revisión 5, punto 10 — .quicklane está siempre en sección invertida
       (fondo ink), así que la sombra de presión va en crema (--bg), igual
       que .retail__cta/.btn--pill invertido: una sombra ink se fundiría
       con el propio fondo de la franja. */
    box-shadow: 0 0 0 3px var(--bg-pure), 6px 6px 0 var(--bg);
    transition: transform .15s ease, box-shadow .15s ease;
  }
  .quicklane__cta:hover {
    transform: translate(3px, 3px);
    box-shadow: 0 0 0 3px var(--bg-pure), 3px 3px 0 var(--bg);
  }
  .quicklane__cta:active {
    transform: translate(6px, 6px);
    box-shadow: 0 0 0 3px var(--bg-pure), 0 0 0 var(--bg);
  }
  .quicklane__cta-label { display: inline; font-weight: 700; font-size: var(--fs-small); }
  .quicklane__chevron { display: none; }
}

/* ==========================================================================
   02 · CABECERA
   ========================================================================== */

/* Componente 7 (brief §4), corregido tras el CLEAR (defecto residual #2,
   2026-09-13): el wordmark real de la cabecera va SIN la ficha con filete —
   la hoja (sheet-header.png) lo muestra sobre el fondo, sin caja. La ficha
   con --border se queda SOLO en el logo de texto del pie (no firmado, brief
   §4.7), que debe seguir viéndose exactamente igual: por eso el padding/
   borde/radio bajan a un selector propio del pie en vez de vivir aquí. */
.logo {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--fs-body);
  letter-spacing: .04em;
  color: var(--text-primary);
}
.site-footer__logo .logo {
  padding: var(--sp-2) var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
/* Alto del wordmark por CSS, no por el width/height del <img> (que solo
   fija la proporción intrínseca 530.53:71.15) — encaja en --header-h con
   aire: 28px + 2×--sp-2 (16px) de padding + 2×1px de borde = 46px dentro
   de 56px (móvil); 36px + lo mismo = 54px dentro de 72px (escritorio). */
.logo img {
  display: block;
  width: auto;
  height: 21px; /* revisión 5, punto 3: 28px × (260/340) ≈ 21px — proporción del wordmark de escritorio */
}
/* Revisión 4 — a partir de 1280px la cabecera ya no lleva el logo de
   texto pequeño de antes: el wordmark mide por ANCHO (≈340px a 1440,
   brief §02), con el alto siguiendo su proporción intrínseca
   (530.53:71.15, ya fija por los atributos width/height del <img>).
   Escala fluida entre 1280 y 1440 con la misma técnica que el resto del
   grupo (nav-fs, nav-gap, --header-h): recta pura por vw, con el valor
   de 1280 como suelo y el medido a 1440 como techo — así nunca crece
   más allá de la medida real ni se encoge por debajo de lo que cabe en
   1280. */
/* Revisión 5, punto 3 — wordmark más pequeño: 340px → 260px a 1440 (misma
   técnica de recta pura por vw, reescalada por el factor 260/340=0.7647 —
   mismo suelo/techo proporcional que antes, ahora más chico). El alto de
   móvil (`.logo img` base, arriba) baja en la misma proporción: 28px×0.7647
   ≈ 21px. "Report the values" — ver informe de entrega. */
@media (min-width: 1280px) {
  .logo img {
    width: clamp(14.444rem, 18.056vw, 16.25rem); /* 231px @1280 → 260px @1440 */
    height: auto;
  }
}

/* Revisión 1, punto 1 — cabecera fija. `sticky`, no `fixed`: la franja 01
   (`.quicklane`) va ANTES en el flujo normal y no lleva ninguna posición
   especial, así que sigue arriba del todo al cargar la página y desaparece
   sola en cuanto se hace scroll — es la cabecera la que "alcanza" el borde
   superior y se queda pegada ahí, nunca al revés. Con `fixed` la cabecera
   se habría pintado encima de la franja 01 desde el primer fotograma.
   Altura explícita vía `var(--header-h)` (no `padding-block` + contenido):
   es lo que permite que scroll-margin-top (más arriba en este fichero) y
   cualquier cálculo posterior lean el mismo número exacto, sin adivinarlo
   a partir del contenido. */
/* Fase 2 (docs/port-phase-2.md, punto 1 — "stickiness trap") — un template
   part se envuelve SIEMPRE en su propio elemento (aquí `header.wp-block-
   template-part`, ver wp-includes/blocks/template-part.php), tan alto como
   su contenido: si el `position:sticky` fuera solo del `.site-header` de
   AQUÍ (un grupo anidado DENTRO de ese envoltorio), su bloque contenedor
   sería ese mismo envoltorio — exactamente tan alto como el propio header —
   y no habría recorrido de scroll en el que "quedarse pegado": se
   despegaría en el mismo píxel en el que empezaría a pegarse. La clase
   `site-header-wrap` (en la referencia `wp:template-part` de cada plantilla,
   nunca aquí) es la que lleva sticky/top/z-index: ese envoltorio SÍ es hijo
   directo del flujo de la página (mismo nivel que <main>/<footer>), con
   recorrido de sobra para quedarse fijo. Aquí solo quedan fondo/filete/alto. */
.site-header-wrap {
  position: sticky;
  top: 0;
  z-index: 100;
}
.site-header {
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  height: var(--header-h);
}
.header-inner {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  /* Fix F4 (port-phase-2-fixes.md) — ancla de posicionamiento para el
     botón abrir del Navigation, ver más abajo. */
  position: relative;
}

/* .main-nav es DESCENDIENTE de .site-header (no hermano), así que su
   z-index se compara dentro del stacking context del propio header, no
   contra el resto de la página. Sin esto, el overlay (z-index 90) tapaba
   también el logo y el botón hamburguesa de su propio header — comprobado
   en vivo: el panel abría en blanco, sin nada para cerrarlo. */
.header-inner > .logo,
.nav-toggle {
  position: relative;
  z-index: 95;
}

.nav-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
}
.nav-toggle .icon-close { display: none; }
.site-header.nav-open .nav-toggle .icon-menu { display: none; }
.site-header.nav-open .nav-toggle .icon-close { display: block; }

/* `.wp-block-button.header-cta` es también un `.wp-block-buttons > .wp-block-button`
   (functions.php lo envuelve así) y esa regla de núcleo
   (wp-includes/blocks/buttons/style.css: "display:inline-block") tiene más
   especificidad (0,2,0) que una sola clase (0,1,0) — sin el selector
   compuesto de abajo el `display:none` de aquí nunca ganaba y el CTA de
   cabecera se veía siempre, roto, en móvil (mismo tipo de fallo que el
   `position:fixed` del overlay de navegación, más arriba). */
.wp-block-buttons > .wp-block-button.header-cta { display: none; }
/* Fix F4 (port-phase-2-fixes.md, cuenta #4) — con el hijo oculto, el propio
   `.wp-block-buttons` que lo envuelve queda vacío pero SIGUE contando como
   tercer hijo flex de `.header-inner` (logo / nav / este wrapper), así que
   `justify-content:space-between` reparte el hueco en TRES tramos y el
   `<nav>` (el que aloja el botón abrir) se queda flotando a medio camino en
   vez de pegado al borde de contenido. Se oculta también el wrapper: con
   solo dos hijos visibles (logo y nav), nav queda como el último y
   `.wp-block-navigation__responsive-container-open` (posicionado respecto a
   `<nav>`, `position:relative` de núcleo) cae justo en el borde real. */
.header-inner .wp-block-buttons { display: none; }

/* Revisión 4 — la esquina despegada del componente 4 (revisión 3) se
   retira SOLO de este CTA: la maqueta de Pixun lo dibuja liso, sin
   solapa ("no peeled corner on this one", brief §02). El resto de
   stickers con esquina despegada (09, 10) no se tocan. Tamaño propio
   (≈215×55 a 1440, filete 4px, sombra 8px) porque este CTA es más grande
   que el botón de cuerpo estándar (48px).
   Selector acotado a `a.header-cta` (fase 1, port-phase-1.md §6, defecto
   medido por la sesión orquestadora): la maqueta estática pone la clase en
   el propio `<a>`, pero el bloque nativo `core/button` la pone en el `<div
   class="wp-block-button">` que lo envuelve (functions.php) — con el
   selector a secas esta regla caía TAMBIÉN sobre ese div (min-height 55px +
   padding-inline 32px×2 + filete 4px se sumaban a los de
   `.wp-block-button.header-cta .wp-block-button__link`, más abajo, que ya
   cubre el enlace real del bloque), y el CTA de escritorio medía 181×60 en
   vez de 188×58. Acotar a `a.` dispensa el div sin tocar la maqueta
   estática (su CTA sigue siendo un `<a>`) ni la regla del bloque. */
a.header-cta {
  min-height: 55px;
  padding-inline: var(--sp-8);
  border-width: 4px;
  box-shadow: 8px 8px 0 var(--bg-pure);
}
a.header-cta:hover {
  transform: translate(4px, 4px);
  box-shadow: 4px 4px 0 var(--bg-pure);
}
a.header-cta:active {
  transform: translate(8px, 8px);
  box-shadow: 0 0 0 var(--bg-pure);
}

.main-nav {
  position: fixed;
  inset: 0;
  z-index: 90; /* bajo la cabecera (100): la cabecera se pinta encima y el
                  padding-top de aquí abajo deja sitio de sobra bajo ella */
  background: var(--bg);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-2);
  /* Despeja la franja 01 + la cabecera 02, las dos potencialmente visibles
     POR ENCIMA de este overlay si el menú se abre con la página en el
     scroll superior (comprobado en vivo: con --sp-24 el primer enlace
     quedaba debajo del logo). Desde la revisión 1 existe var(--header-h)
     para la cabecera, pero la franja 01 no está tokenizada (altura de
     contenido variable, no fija) — se mantiene el suelo generoso en vez
     de una suma exacta, ahora ya con margen de sobra sobre --header-h. */
  padding: calc(var(--sp-32) + var(--sp-16)) var(--gutter) var(--sp-6);
  transform: translateY(-8px);
  opacity: 0;
  visibility: hidden;
  overflow-y: auto;
  /* `visibility` FUERA de esta lista a propósito (round 1 de fixes,
     comprobado en vivo): el "flip a visible al empezar" que las
     transiciones CSS aplican a una propiedad discreta no se resuelve de
     forma síncrona — ni con un reflow forzado antes del bloqueo hallado
     por el validator, getComputedStyle() seguía devolviendo "hidden"
     justo después de forzarlo. js/main.js fija `visibility` a mano
     (inline style) en el mismo tick que abre el panel, para que el
     primer enlace sea focalizable de inmediato, y retrasa su vuelta a
     "hidden" al cerrar hasta que termine este fundido — ver openNav()/
     closeNav(). La regla de abajo (.nav-open .main-nav) se deja como
     estado documentado/de respaldo; quien manda en la práctica es el
     inline style. */
  transition: opacity .2s ease, transform .2s ease;
}
.main-nav a {
  display: block;
  width: 100%;
  padding: var(--sp-3) 0;
  font-size: var(--fs-h3);
  border-bottom: 1px solid var(--border);
}
.main-nav__cta {
  width: auto;
  margin-top: var(--sp-4);
  border-bottom: none;
}

.site-header.nav-open .main-nav {
  opacity: 1;
  transform: translateY(0);
  visibility: visible;
}

html.scroll-locked,
html.scroll-locked body {
  overflow: hidden;
}

/* JUICIO DEL ESTUDIO (revisión 3): este breakpoint se sube de 960 a
   1280px. Con el wordmark real de imagen (~268px de logo, antes ~140px de
   texto) más el CTA "sticker" del componente 4, los tres elementos del
   header (logo + nav + CTA) ya no caben en una fila a 960-1279px sin que
   el flex-shrink por defecto encoja `.main-nav` por debajo de su
   contenido — comprobado en vivo: a 960px exactos aparecía un scroll
   horizontal DENTRO del propio nav (heredaba `overflow-y:auto` de la
   versión móvil, nunca visible hasta ahora porque nunca se había
   encogido). Bajar el listón habría exigido partir el nav también en ese
   rango; subirlo es la solución mínima — el menú de hamburguesa sigue
   siendo un patrón perfectamente válido en tablet/portátil pequeño, y es
   el que ya existía y funciona. */
@media (min-width: 1280px) {
  .nav-toggle { display: none; }
  .wp-block-buttons > .wp-block-button.header-cta { display: inline-flex; }
  .header-inner .wp-block-buttons { display: flex; }
  .main-nav {
    position: static;
    inset: auto;
    flex-direction: row;
    align-items: center;
    flex-shrink: 0;
    width: auto;
    padding: 0;
    gap: clamp(1.944rem, 2.4306vw, 2.1875rem); /* 31.1px @1280 → 35px @1440 — gaps entre enlaces */
    opacity: 1;
    visibility: visible;
    transform: none;
    background: transparent;
  }
  .main-nav a {
    width: auto;
    padding: 0;
    /* Revisión 4 — tamaño y peso de la maqueta (Montserrat ≈25px/500,
       brief §02), no ya el --fs-body de interfaz. Misma técnica de recta
       pura por vw que el wordmark y --header-h: suelo = lo que cabe a
       1280, techo = lo medido a 1440 — nunca crece más allá de 1440 ni
       envuelve un enlace antes de 1280 (comprobado en vivo). */
    font-size: clamp(1rem, 1.25vw, 1.125rem); /* revisión 5, punto 4: 16px @1280 → 18px @1440 (antes 22.2→25) */
    font-weight: 500;
    border-bottom: none;
    white-space: nowrap; /* nunca partir un enlace en dos líneas (brief §02) */
    flex-shrink: 0;
  }
  /* Especificidad .main-nav a (0,1,1) > .main-nav__cta sola (0,1,0) — un
     display:none con esa especificidad NO ganaba y el CTA duplicado se
     veía en el header de escritorio. Comprobado en vivo. */
  .main-nav a.main-nav__cta { display: none; }

  /* Revisión 1, punto 1 — cabecera MÁS ALTA en escritorio que en móvil
     (56px → 132px desde la revisión 4: la maqueta mide ≈132px a 1440,
     brief §02). Misma técnica de recta pura por vw: a 1280 da ≈117px, a
     1440 da exactamente 132px, y clamp() la retiene ahí más allá de
     1440 — nunca un segundo número que mantener a mano en otro sitio. */
  :root { --header-h: clamp(7.333rem, 9.1667vw, 8.25rem); } /* 117.3px @1280 → 132px @1440 */

  /* Port fix C9 (docs/port-fix-c9.md) — Pixun, verbatim: "cambia header
     altura ... solamente cuando el scroll height no es 0. con animacion,
     no instantaneo. para desktop". `html.is-scrolled` (assets/js/header-
     shrink.js) overrides --header-h only in this query; its specificity
     (0,1,1: element + class) beats `:root` (0,1,0) regardless of source
     order, so this always wins while active. scroll-margin-top (~234) and
     `.site-header { height }` (~685) both already read var(--header-h) —
     nothing else to keep in sync.

     Animated, not instant: he asked for it by name, so this is a
     transition on `height` — dexigner's usual rule prefers transforms, but
     this is the documented exception. Duration/easing read off the site's
     own transitions rather than invented: `ease` is already the keyword
     used for every structural show/hide (`.skip-link` top .2s, the mobile
     nav panel opacity/transform .2s — both ~230–240 lines up in this
     file); `.15s ease` is reserved for small hover micro-interactions
     (buttons, cards); reveal.js's 650ms cubic-bezier is reserved for
     content entrances. A header-height change moves more (~32px+) and more
     of the page than a hover, but is not a content reveal, so 300ms ease
     sits as its own step between the two existing structural transitions
     and the content-entrance one — no new easing curve introduced for one
     property.

     Logo / nav / CTA / mini-cart need no separate transition of their own:
     `.header-inner` is height:100% + align-items:center (~688), so
     flexbox recentres every child on every frame of the ongoing height
     transition for free — confirmed live, nothing clips or jumps.

     `prefers-reduced-motion: reduce` already forces this to one frame: the
     sitewide `*, *::before, *::after { transition-duration: .001ms
     !important }` rule (REDUCED MOTION section, further down this file)
     matches `.site-header` too — no separate override needed here,
     confirmed live. */
  .site-header {
    transition: height 300ms ease;
  }
  html.is-scrolled {
    --header-h: clamp(5.333rem, 8.1667vw, 6.25rem); /* Pixun's value, verbatim */
  }

  .header-inner {
    justify-content: flex-start;
    gap: clamp(1.944rem, 2.4306vw, 2.1875rem); /* 31.1px @1280 → 35px @1440 — logo↔nav y entre enlaces */
  }
  .header-inner > .logo { flex-shrink: 0; }
  .header-cta { flex-shrink: 0; margin-left: auto; }
  /* Fase 1 (port-phase-1.md §6) — mismo empuje a la derecha que la línea de
     arriba, pero para el marcado del bloque nativo: `margin-left:auto` en
     `.header-cta` no llega a `.header-inner` porque en `core/button` esa
     clase vive en el `.wp-block-button` DENTRO de `.wp-block-buttons`
     (functions.php lo envuelve así) — el hijo directo, flex, de
     `.header-inner` es `.wp-block-buttons`, no `.header-cta`. Sin esta
     regla el margen automático no tenía efecto en ese eje y el CTA
     aparecía pegado al final del menú en vez del borde derecho del
     contenedor (defecto medido por la sesión orquestadora, 999px en vez de
     ~1193px a 1440). Logo y enlaces no cambian: nada más se toca en este
     contexto flex. */
  .header-inner > .wp-block-buttons { flex-shrink: 0; margin-left: auto; }
}

/* Revisión 4 — CLEAR de componente 7 (revisión 3): la maqueta pone el
   wordmark a la izquierda, no centrado ("Wordmark on the left... not
   centred", brief §02). Se retira el centrado absoluto y el hueco
   `.main-nav__gap` (borrado también del HTML): el wordmark vuelve al
   flujo normal como primer hijo de `.header-inner`, y `justify-content:
   flex-start` + `margin-left: auto` en `.header-cta` (arriba) hacen el
   resto — logo y nav juntos a la izquierda con el gap fluido del grupo,
   CTA empujado al extremo derecho por el margen automático. */

/* ==========================================================================
   02b · CABECERA — adaptaciones a los bloques nativos (fase 1, port-phase-1.md
   §6). `.nav-toggle` / `.main-nav` / `.site-header.nav-open` de arriba son
   del prototipo estático (js/main.js) y ya no se usan — quedan como están,
   sin tocar (brief: "nothing else edited"), y esta sección los sustituye por
   el enganche real a `core/site-logo` y `core/navigation`.
   ========================================================================== */

/* Wordmark real vía Site Logo — misma proporción/técnica que `.logo img`
   (arriba): alto fijo en móvil, ancho por clamp() desde 1280px. */
.wp-block-site-logo {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
}
.wp-block-site-logo img,
.wp-block-site-logo a {
  display: block;
}
/* `.wp-block-site-logo.is-default-size img { width:120px }` (núcleo,
   wp-includes/blocks/site-logo/style.css) pesa (0,2,1) — más que nuestra
   `.wp-block-site-logo img` a secas (0,1,1) — porque Site Logo añade
   is-default-size cuando el bloque no fija un ancho propio (nunca se lo
   fijamos: el ancho es el clamp de abajo). Sin repetir la clase aquí el
   wordmark se quedaba en 120px fijos a cualquier anchura, nunca en los
   231→260px del prototipo. Mismo fallo de especificidad que el CTA de
   cabecera y el overlay de navegación, arriba. */
.wp-block-site-logo.wp-block-site-logo.is-default-size img {
  width: auto;
  height: 21px;
}
@media (min-width: 1280px) {
  .wp-block-site-logo.wp-block-site-logo.is-default-size img {
    width: clamp(14.444rem, 18.056vw, 16.25rem); /* 231px @1280 → 260px @1440 */
    height: auto;
  }
}

/* CTA de cabecera vía core/button — "Additional CSS class" `header-cta` en
   el panel (editable: Pixun puede quitarla si quiere el tamaño estándar).
   WordPress aplica esa clase al contenedor `.wp-block-button`, no al enlace
   `.wp-block-button__link` (a diferencia del `<a class="header-cta">` del
   HTML original) — de ahí el selector compuesto en vez de reutilizar
   `.header-cta` a secas (esa regla, arriba, queda para el HTML estático y
   no combina con el marcado del bloque). Mismos valores/tokens que arriba,
   sin duplicar la lógica de color (la da el estilo "Pegatina roja").
   `display`/`align-items`/`justify-content`/`padding-block`/`font-weight`
   añadidos en fase 1 (port-phase-1.md §6, defecto medido por la sesión
   orquestadora): el enlace del bloque hereda el centrado y el peso de
   fuente por defecto de `core/button`, no los de `.btn` (que sólo llegan al
   `<a class="header-cta">` de la maqueta estática) — sin ellos el enlace
   medía 181×60 con texto en regular en vez de 188×58 en negrita como
   `.header-cta`. Mismos valores que `.btn` (línea ~318): `--sp-3` de
   relleno vertical, peso 700, centrado vía flex. */
.wp-block-button.header-cta .wp-block-button__link {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 55px;
  padding-block: var(--sp-3);
  padding-inline: var(--sp-8);
  border-width: 4px;
  font-weight: 700;
  box-shadow: 8px 8px 0 var(--bg-pure);
}
.wp-block-button.header-cta .wp-block-button__link:hover {
  transform: translate(4px, 4px);
  box-shadow: 4px 4px 0 var(--bg-pure);
}
.wp-block-button.header-cta .wp-block-button__link:active {
  transform: translate(8px, 8px);
  box-shadow: 0 0 0 var(--bg-pure);
}

/* core/navigation con overlayMenu:"always" (functions.php) — el bloque
   nativo marca el panel `.hidden-by-default` y el botón abrir
   `.always-shown` para que NUNCA se muestren en línea solos (ver
   wp-includes/blocks/navigation.php, get_responsive_container_classes()):
   por diseño, "always" no tiene un punto de quiebre propio. Estas reglas
   son ese punto de quiebre — el mismo 1280px del prototipo estático — sin
   sustituir el bloque por enlaces fijos (brief §6, riesgo técnico 1): a
   partir de 1280px se oculta el botón de abrir y el panel se muestra en
   línea con sus valores por defecto (ya en flex, ver
   wp-includes/blocks/navigation/style.css línea ~483); por debajo, el
   panel se viste como el `.main-nav` móvil de siempre. */
.site-header .wp-block-navigation__responsive-container-open.always-shown {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
}
/* Fix F4 (port-phase-2-fixes.md, cuenta #4) — en el HTML el toggle está a
   ras del borde de contenido del contenedor (380: x 320-364) y dibuja tres
   líneas con trazo grueso a 32px (index.html 130-135, styles.css ~676-700).
   El botón nativo de core/navigation queda dentro del flujo flex de
   `.header-inner` (x 246, icono core de 24px) — se saca del flujo y se
   ancla al borde de contenido real (`.header-inner` ya tiene
   `padding-inline: var(--gutter)` vía `.container`; `right: var(--gutter)`
   desde el borde del padding-box cae justo en el borde de contenido). El
   svg de core se oculta y las tres líneas se dibujan con un CSS mask del
   SVG del tema — mismo trazo, mismo grosor, coloreado por `currentColor`,
   así que sigue vivo si el color de texto cambia desde Global Styles. */
.site-header .wp-block-navigation__responsive-container-open.always-shown {
  /* `right:0`, no `var(--gutter)`: el ancla real no es `.header-inner`
     (posicionado, pero NO el ancestro posicionado más cercano) sino el
     propio `<nav class="wp-block-navigation">` (núcleo lo marca
     `position:relative`) — y ese `<nav>`, con el wrapper del CTA oculto
     arriba, cae YA en el borde de contenido real (x 364 a 380 = 0 ancho,
     `justify-content:space-between` con solo dos hijos). */
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-primary);
}
.site-header .wp-block-navigation__responsive-container-open.always-shown svg {
  display: none;
}
.site-header .wp-block-navigation__responsive-container-open.always-shown::before {
  content: "";
  display: block;
  width: 32px;
  height: 32px;
  background-color: currentColor;
  -webkit-mask: url('../images/icons/icon-menu.svg') no-repeat center / contain;
  mask: url('../images/icons/icon-menu.svg') no-repeat center / contain;
}
.site-header .wp-block-navigation__responsive-container.hidden-by-default:not(.is-menu-open) {
  display: none;
}
.site-header .wp-block-navigation__responsive-container.is-menu-open {
  padding: calc(var(--sp-32) + var(--sp-16)) var(--gutter) var(--sp-6);
}
/* background/color repetidos aparte, con más especificidad que las reglas
   del propio core (wp-includes/blocks/navigation/style.css ~606-611:
   ".wp-block-navigation:not(.has-background) …is-menu-open:not(.disable-
   default-overlay) { background-color:#fff }" y su gemela de color #000,
   cinco clases cada una) — sin esto el panel se abría blanco con texto
   negro en vez del panel de la marca (brief §6: "background bg"). La
   clase repetida es la técnica que ya usa el propio core más abajo en
   este mismo fichero para ganar por especificidad, no por origen. */
.site-header .wp-block-navigation__responsive-container.hidden-by-default.is-menu-open.is-menu-open:not(.disable-default-overlay) {
  background-color: var(--bg);
  color: var(--text-primary);
}
.site-header .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content .wp-block-navigation__container {
  gap: var(--sp-2);
}
.site-header .wp-block-navigation-item > .wp-block-navigation-item__content {
  font-size: var(--fs-h3);
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--border);
}
@media (min-width: 1280px) {
  .site-header .wp-block-navigation__responsive-container-open.always-shown {
    display: none;
  }
  .site-header .wp-block-navigation__responsive-container.hidden-by-default:not(.is-menu-open) {
    display: flex;
    /* Sin esto el contenedor se queda con el position:fixed + inset:0 de
       wp-includes/blocks/navigation/style.css línea ~472 (esa regla sólo
       lleva una clase, la nuestra gana por especificidad en display pero
       nunca tocaba position): con display:flex y position:fixed a la vez
       el panel ocupaba el viewport entero y el menú "en línea" aparecía
       fuera de la cabecera, tapando el resto de la página. Vuelve al flujo
       normal, como hermano del logo y el CTA dentro de .header-inner. */
    position: static;
    inset: auto;
    width: auto;
    z-index: auto;
    background-color: transparent;
  }
  .site-header .wp-block-navigation__responsive-close,
  .site-header .wp-block-navigation__responsive-dialog,
  .site-header .wp-block-navigation__responsive-container-content {
    display: contents;
  }
  .site-header .wp-block-navigation__responsive-container-close {
    display: none;
  }
  .site-header .wp-block-navigation__container {
    flex-direction: row;
    align-items: center;
    gap: clamp(1.944rem, 2.4306vw, 2.1875rem); /* 31.1px @1280 → 35px @1440 */
  }
  .site-header .wp-block-navigation-item > .wp-block-navigation-item__content {
    font-size: clamp(1rem, 1.25vw, 1.125rem); /* 16px @1280 → 18px @1440 */
    font-weight: 500;
    padding: 0;
    border-bottom: none;
    white-space: nowrap;
  }
}

/* Round 3 fix — el CTA "Pide tu tarifa" DUPLICADO dentro del panel, igual
   que el HTML original (`.main-nav__cta`, `index.html` línea 151): el
   overlay de `core/navigation` sólo renderiza sus propios hijos, así que el
   botón hermano de fuera (`.header-cta`, visible sólo desde 1280px) nunca
   podía aparecer ahí. Añadido como un segundo `core/buttons` DENTRO del
   post `wp_navigation` (id 16, editable desde el panel de Navegación, igual
   que los cinco enlaces) en vez de con CSS/JS que lo duplicara: mismo
   mecanismo que enlaces + CTA usa el HTML, sólo que aquí ambos viven en el
   bloque nativo. Mismo estilo "Pegatina roja" que el CTA de escritorio,
   sin su tamaño ampliado (ese es sólo para `.header-cta`, 55px/filete 4px) -
   aquí el botón de cuerpo estándar de 48px, como `.main-nav__cta`. */
.site-header .wp-block-navigation__responsive-container-content > .nav-overlay-cta {
  display: block;
  margin-top: var(--sp-4);
}
/* Nota, no aplicada: en el HTML real `.main-nav a { display:block; width:
   100% }` (0,1,1) pesa más que `.main-nav__cta { width:auto }` (0,1,0) sola,
   así que ese CTA renderiza como barra a todo el ancho (medido en vivo,
   http://localhost:8938 a 1279px) - probablemente una deriva de
   especificidad no vista, no una decisión, del mismo tipo que el
   `display:none` de abajo (esa sí documentada y corregida por el autor).
   No replicado aquí: los cinco enlaces de este panel YA renderizan más
   estrechos que el HTML (bloque `core/navigation` nativo, cada uno un
   `<li>` de ancho por contenido, no un `<a>` de ancho 100%) y esa anchura
   quedó aceptada en una ronda de validación anterior sin marcarse como
   hallazgo - homogéneo con eso, este botón se queda también a ancho de
   contenido. Un CTA a todo el ancho junto a enlaces estrechos sería una
   inconsistencia nueva que nadie pidió. Señalado para quien decide el
   diseño, no resuelto unilateralmente aquí. */
@media (min-width: 1280px) {
  /* Mismo motivo que `.main-nav a.main-nav__cta { display: none }` en el
     HTML: por encima de 1280px el `.wp-block-navigation__responsive-
     container-content` pasa a `display: contents` (regla de arriba) y este
     botón se uniría en línea a la fila de enlaces — el CTA de escritorio ya
     lo cubre fuera del bloque de navegación. */
  .site-header .wp-block-navigation__responsive-container-content > .nav-overlay-cta {
    display: none;
  }
}

/* ==========================================================================
   03 · HÉROE — reescrito en la revisión 1, punto 4.
   Fuera la columna de imagen de la derecha; el héroe pasa a fondo a
   sangre (carrusel decorativo con fundido) detrás de la columna de texto,
   que se queda intacta y vive en el `.container` estándar (punto 3: el
   texto nunca sangra, solo el fondo). `.section--inverted` reutilizada
   aquí (ver su bloque más arriba) — es lo que da texto/botones claros
   sobre foto oscurecida sin un solo color nuevo.
   ========================================================================== */

.hero {
  position: relative;
  overflow: hidden;
  isolation: isolate; /* nuevo contexto de apilamiento propio, para que el
                          z-index interno (fondo/velo/texto) no tenga que
                          competir con nada fuera de esta sección */
  display: flex;
  align-items: center;
  padding-block: var(--section-pad-lg);
  /* Alto reservado ANTES de que cargue cualquier imagen — cero dependencia
     del tamaño intrínseco de la foto, así que no hay salto de maqueta
     posible por esta vía (punto 4.5). Cifras propuestas por el estudio,
     no dictadas por el wireframe (el héroe de dos columnas no las traía) —
     anotado en el informe de entrega para que Pixun las confirme. */
  min-height: 460px;
}

/* Fondo decorativo — aria-hidden en el HTML, sin controles, fuera del
   orden de tabulación (ningún <img> es focalizable por defecto y aquí no
   hay ninguno con tabindex). Punto 4.3. */
.hero__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  background: var(--bg-elevated-2); /* relleno mientras no hay foto real o
                                        si el fichero aún no existe — nunca
                                        un héroe vacío (punto 4.7) */
}
.hero__bg-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  /* La transición vive en la regla BASE, no solo en .is-active: así se
     anima igual entrando (0→1) que saliendo (1→0) cuando el JS quita la
     clase de la diapositiva anterior. Puesta solo en .is-active, la
     diapositiva saliente pierde la regla junto con la clase y el opacity
     salta sin animar. */
  transition: opacity 1s ease;
  /* Ronda de corrección — las tres diapositivas son hoy .media-placeholder
     (mismo componente que 05/08/09), no <img>: sin src, cero peticiones de
     red, cero 404. .media-placeholder trae borde + radio pensados para una
     tarjeta suelta; aquí el marcador es a sangre completa (edge to edge
     con el propio héroe), así que se anulan los dos — un filete y unas
     esquinas redondeadas cortados por el borde de la ventana se verían
     como un error, no como un marcador sobrio. El resto del componente
     (fondo --bg-elevated-2, icono --text-tertiary) se mantiene: son los
     mismos tokens que ya usa el resto del sitio para "foto pendiente",
     sin inventar ningún valor nuevo (punto 5). */
  border: none;
  border-radius: 0;
}
/* La primera diapositiva lleva .is-active YA en el HTML — es lo que
   garantiza "sin JS se ve la primera imagen" (punto 4.7) sin depender de
   que el script llegue a ejecutarse. Al ser el estado ya presente en el
   primer render, no hay fundido de entrada indeseado en la carga. */
.hero__bg-img.is-active {
  opacity: 1;
}
/* Velo de legibilidad — capa propia encima de las fotos, token nuevo
   (--hero-scrim, declarado en :root en gris — punto 4.2). Pseudo-elemento
   porque es puramente decorativo (sin contenido editable, ver punto 5). */
.hero__bg::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--hero-scrim);
  pointer-events: none;
}

.hero__text {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 0;
  width: 100%;
}
/* Revisión 4 — el sticker del héroe (componente 9, revisión 3) se retiró
   (brief "What is NOT taken", punto 4), así que el hueco reservado en
   eyebrow/H1 para no chocar con él ya no hace falta. Eyebrow en sentence
   case (se quita el uppercase — brief §03) y en cream real
   (--text-primary dentro de .section--inverted), no --text-secondary. */
.hero__eyebrow {
  font-size: clamp(1rem, 0.866rem + 0.566vw, 1.375rem); /* 16px → 22px */
  color: var(--text-primary);
  letter-spacing: .02em;
  margin-bottom: var(--sp-3);
}
/* H1 en Montserrat, no en Cooper (brief §03) — se sobreescriben aquí los
   tres valores que h1 hereda por defecto de la regla base (h1,h2,h3):
   familia, peso y line-height. font-synthesis:none no aplica (Montserrat
   es variable y sí tiene 700 real). */
/* Revisión 6, punto 2 — el H1 del héroe vuelve a Cooper Heavy: "eso no hace
   nada" se refería a que un intento anterior de tocar esto no se veía en el
   navegador (font-family de la regla base h1,h2,h3 ya la ponía en Cooper,
   pero .hero__title la sobreescribía a Montserrat aquí mismo — de ahí el
   "parece un fallo"). font-weight:800 + font-synthesis:none porque Cooper
   Heavy es un único peso real (mismo razonamiento que el resto de titulares,
   ver :root). line-height:1.05 se mantiene (valor propio del héroe, no
   tocado por este punto). */
.hero__title {
  font-family: var(--font-display);
  font-weight: 800;
  font-synthesis: none;
  line-height: 1.05;
  margin-bottom: var(--sp-4);
}
.hero__subtitle {
  color: var(--text-secondary);
  line-height: var(--lh-loose);
  max-width: 46ch;
  margin-bottom: var(--sp-6);
}
.hero__actions {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
/* Fase 3 (docs/port-phase-3.md §2.1) — el botón del héroe llega ahora como
   core/button: `.wp-block-buttons` propio de núcleo añade `is-layout-flex`
   con su `align-items` (no `stretch`), así que ni el `.wp-block-button` (div
   envoltorio) ni el `.wp-block-button__link` (el `<a>`, que hacía de `.btn`
   en el HTML estático) heredan el 100% de ancho solos — medido en vivo
   (165.969px, contenido, en vez de 348px). Los dos necesitan el ancho
   explícito para que el `width:100%` del enlace tenga contra qué resolver. */
.hero__actions .btn,
.hero__actions .wp-block-button,
.hero__actions .wp-block-button__link { width: 100%; }

@media (min-width: 960px) {
  /* Revisión 6, punto 1 — corrige la nota de más abajo: DESDE 960px el
     bloque de texto SÍ recorta su propio ancho (20% de padding a la
     derecha), a petición explícita de Pixun. Por debajo de 960 se queda en
     el 100% de .container sin recorte — a 380px, un 20% quitaría 76px a
     una columna ya estrecha (brief revisión 6, punto 1), así que la regla
     vive dentro de este media query, nunca suelta en `.hero__text`. */
  .hero__text { padding-right: 20%; }
  /* Alto y layout de los botones — la legibilidad del párrafo ya la
     resuelve el max-width:46ch propio de .hero__subtitle, sin tocar el
     contenedor. */
  /* Revisión 4 — alto del héroe medido en la maqueta: ≈588px a 1440
     (brief §03), no ya la cifra libre del estudio (620px). */
  .hero { min-height: 588px; }
  .hero__actions { flex-direction: row; gap: var(--sp-4); }
  .hero__actions .btn,
  .hero__actions .wp-block-button,
  .hero__actions .wp-block-button__link { width: auto; }
}

/* ==========================================================================
   04 · FRANJA DE CAPACIDAD
   ========================================================================== */

/* Revisión 4 — la franja gris se sustituye por una tarjeta amarilla que
   sobremonta el borde inferior del héroe (brief §04): fondo de sección
   CREAM (--bg, no ya --bg-elevated — el gris deja de ser un fondo de
   sección, brief "What is NOT taken", punto 3), margen negativo en la
   tarjeta para el solape. El héroe conserva su alto reservado (min-height
   en .hero, sin tocar) — el solape es puramente visual, no reduce layout. */
.capacity {
  background: var(--bg);
  padding-top: 0;
  padding-bottom: clamp(1.5rem, 5.417vw, 4.875rem); /* ~24px @380 → 78px @1440, "cream ~78px below the card" */
}
.capacity__card {
  position: relative;
  z-index: 1;
  margin-top: -38px; /* solape con el borde inferior del héroe */
  max-width: initial; /* revisión 6, punto 3 (antes 1080px) — Pixun por su nombre exacto */
  margin-inline: auto;
  background: var(--accent-2);
  border-radius: 24px;
  padding: var(--sp-6) var(--sp-4);
}
/* Ronda de fix 1 (validator B4), causa real medida en vivo: `.capacity__grid`
   es un `core/columns` (inc/patterns.php), y ese bloque núcleo se auto-asigna
   la clase `is-layout-flex`; el tema global de bloques (`theme.json`) imprime
   `body .is-layout-flex{display:flex}` (especificidad 0,1,1), que gana por
   especificidad al `.capacity__grid{display:grid}` de una sola clase (0,1,0)
   aunque este último cargue después en la cascada — confirmado leyendo las
   reglas realmente encontradas por el navegador (`document.styleSheets`),
   no por selector CSS a ciegas. Selector reforzado a dos clases
   (`.wp-block-columns.capacity__grid`, especificidad 0,2,0) para ganar
   siempre, sin `!important`. */
.wp-block-columns.capacity__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-6) var(--sp-4);
  /* core imprime `:where(.wp-block-columns){margin-bottom:1.75em}` (especificidad
     0) que esta regla no tocaba: dentro de `.capacity__card` (padding propio,
     sin colapso de margen) ese sobrante se sumaba a la altura de la tarjeta
     (222px medidos vs 192px de referencia a 1440). Ver docs/port-phase-2.md,
     defecto 2. */
  margin-bottom: 0;
}
/* Revisión 1, punto 5: sin :nth-child ni :last-child — dependen de la
   posición del elemento entre sus hermanos, y Gutenberg puede envolver
   cada celda en un div propio al convertir esto en patrón, con lo que
   una regla posicional deja de encontrar lo que buscaba. Cada celda lleva
   su propio modificador explícito (--a/--b/--c/--d), fijo en el elemento
   mismo, sea cual sea su envoltorio. Revisión 4: sin divisores (brief
   §04 "no dividers") — los modificadores se quedan solo como gancho, sin
   borde. */
.capacity__cell {
  text-align: center;
}
.capacity__icon {
  display: inline-flex;
  margin-bottom: var(--sp-3);
  /* Ink siempre sobre amarillo — nunca --text-tertiary (brief §04: 3.5:1,
     insuficiente). --text-primary da 9.6:1. */
  color: var(--text-primary);
}
.capacity__icon svg { width: 58px; height: 58px; }
.capacity__figure {
  font-family: var(--font-body); /* dato, no titular (brief §2) */
  font-weight: 700;
  font-size: var(--fs-h3);
  color: var(--text-primary);
  margin-bottom: var(--sp-1);
}
.capacity__label {
  font-size: var(--fs-small);
  color: var(--text-secondary); /* 5.4:1 sobre amarillo, dentro de lo permitido */
}

@media (min-width: 960px) {
  .wp-block-columns.capacity__grid { grid-template-columns: repeat(4, 1fr); }
  .capacity__card { padding: var(--sp-8) var(--sp-6); }
}

/* ==========================================================================
   05 · FAMILIAS
   ========================================================================== */

.families {
  background: var(--bg);
  padding-block: var(--section-pad-lg);
  /* Revisión 6, punto 8 — contenedor de consulta de tamaño, SOLO para que
     `.families__track` (más abajo) pueda leer su ancho real en `cqw` en vez
     de `vw`. `inline-size` aplica layout+style+inline-size containment: no
     cambia el ancho de esta sección (sigue viniendo de su padre, un bloque
     a ancho completo), solo permite medirlo desde dentro sin la barra de
     scroll de por medio (ver la nota junto a `padding-left` del track). */
  container-type: inline-size;
}
.families__head {
  margin-bottom: var(--sp-10);
  text-align: center;
}
.families__head h2 {
  margin-bottom: var(--sp-8); /* revisión 5, punto 22 (antes var(--sp-3)) */
  font-size: clamp(1.75rem, 1.347rem + 1.7vw, 2.875rem); /* 28px → 46px, brief "Type scale" */
}
.families__head .section-lead {
  max-width: 46ch;
  margin-inline: auto;
}
/* Revisión 7, punto 2 — el botón "Pausar movimiento" se retira (markup,
   JS y esta regla, incluida la sub-regla de reduced-motion). Decisión de
   Pixun, verbatim: "el boton pausar el movimiento no hace falta ponerlo
   porque si el usuario se pone con el cursor encima del carousel se para
   el carousel y cuando sale sigue su curso en autoplay" — WCAG 2.2.2 queda
   como su decisión registrada (ver LOG), no un defecto que bloquee. */

/* Revisión 4 (brief §05) — vuelve a ser una fila que sangra por la
   derecha en escritorio, ya no la rejilla de 2/3 columnas de la
   revisión 3: el track sale de `.container` en el HTML (cabecera y
   puntos se quedan dentro del suyo) y calcula su propio padding-left
   para que la primera tarjeta arranque exactamente en el borde izquierdo
   del contenedor — `max(var(--gutter), …)` cubre tanto el caso viewport
   ≤ 1280px (gutter a secas) como > 1280px (donde el contenedor deja de
   crecer y el hueco extra hay que calcularlo). Sin padding-right: la
   sangría por la derecha es exactamente eso, ningún tope. padding-top
   reserva sitio para la corona (componente nuevo) que sobresale por
   encima de la foto — igual que ya exigía el overflow-x:auto: al fijar
   un eje a "auto" el otro dejaría de ser "visible" (se computa a "auto"
   también, spec de CSS Overflow), así que sin este padding la corona se
   recortaría contra el propio borde superior del track. */
.families__track {
  display: flex;
  gap: 25px;
  overflow-x: auto;
  /* Revisión 7, punto 2 — "mandatory" a "proximity": con "mandatory" el
     navegador fuerza el snap incluso a media inercia táctil, y en el
     comportamiento que pide el brief (el dedo maneja el scroll nativo, JS
     nunca escribe scrollLeft durante el gesto ni el momentum) eso podía
     competir con la propia inercia. Probado en vivo con el arrastre de
     ratón y con gestos táctiles sintéticos (ver informe de entrega): con
     "proximity" el asentado en cada tarjeta se sigue notando sin pelearse
     con el desplazamiento en curso — no hizo falta llegar a "none".
     `scroll-snap-align: start` (en la tarjeta) y `scroll-padding-*` (más
     abajo) no cambian. */
  scroll-snap-type: x proximity;
  /* Revisión 7, punto 2 — cursor de agarre en el track: "comodo, que los
     elementos seanan swipeables comodamente" (Pixun, verbatim). Solo
     afecta a ratón (touch no pinta cursor); grabbing durante el arrastre,
     ver `.is-dragging` más abajo. */
  cursor: grab;
  /* Revisión 5, punto 15 (motion) — el motor de autoplay mueve scrollLeft
     a mano, fotograma a fotograma (js/main.js): scroll-behavior tiene que
     quedarse en "auto" (instantáneo) SIEMPRE en este elemento, sin
     heredar el `scroll-behavior:smooth` que la hoja pone en `html` para
     anclas internas — si no, cada asignación de scrollLeft competiría
     contra una transición suave a medio terminar. */
  scroll-behavior: auto;
  /* 30px, no 24: medido en vivo a 380px, la corona quedaba recortada
     ≈2.6px contra el borde superior de este mismo elemento (overflow-x
     auto fuerza overflow-y a auto también, spec CSS Overflow — el
     recorte ocurre en el borde del padding-box, así que hace falta más
     aire del que el ancho de la corona por sí solo sugiere). */
  padding-top: 30px;
  padding-bottom: var(--sp-2);
  /* Revisión 6, punto 8 — defecto nuestro: `100vw` INCLUYE el ancho de la
     barra de scroll vertical, así que este cálculo se pasaba ~7px hacia la
     derecha respecto al borde real del contenido de `.container` a 1440
     (medido en vivo). `100cqw` es el ancho de consulta de `.families`
     (`container-type: inline-size`, arriba) — el ancho YA RENDERIZADO de la
     sección, que excluye la barra de scroll porque no es el viewport, es
     layout real. Misma fórmula, unidad distinta. */
  padding-left: max(var(--gutter), calc((100cqw - var(--container-max)) / 2 + var(--gutter)));
  padding-right: 0;
  /* Revisión 6, punto 8 (segunda causa, no la de arriba) — encontrado en
     vivo: la fórmula de `padding-left` de arriba SÍ calcula bien (44.5px a
     1440, igual que el borde de contenido de `.container`), pero con
     `scroll-snap-align: start` en la primera tarjeta (ver .families__card),
     el propio padding-left ENTRA en el espacio de scroll — su coordenada de
     contenido es 0..padding-left..resto, así que el snapport (que empieza
     en la coordenada 0 del contenido scrolleable) alinea el borde de la
     primera tarjeta ahí solo si scrollLeft = padding-left. Sin overflow
     manual eso es exactamente lo que el navegador hace en reposo: fija
     scrollLeft en el valor del padding, no en 0, y la tarjeta vuelve a
     quedar a ras del borde real (padding cancelado). Medido: sin este
     scroll-padding-left, track.scrollLeft no baja de 45px aunque se fuerce
     a 0 o se intente hacer scroll manual más allá — 45px es el suelo real,
     no una lectura a medio animar. `scroll-padding-left` con la MISMA
     fórmula desplaza el punto de snap objetivo en esa misma cantidad, así
     que el reposo natural vuelve a ser scrollLeft: 0 y la tarjeta sí
     aparece en el borde de contenido. Mismo patrón que ya usa la media
     query de móvil de abajo (ahí al revés: quitan el padding y dejan solo
     scroll-padding a propósito para que la 1ª tarjeta sangre). */
  scroll-padding-left: max(var(--gutter), calc((100cqw - var(--container-max)) / 2 + var(--gutter)));
  scrollbar-width: none;
}
.families__track::-webkit-scrollbar { display: none; }
/* docs/port-fix-c5.md — view.js añade esta clase al insertar el set de
   clones que ahora se antepone (además del que ya se añadía detrás).
   overflow-anchor:none apaga el scroll-anchoring nativo: sin él, el
   navegador podría reajustar scrollLeft por su cuenta al insertar
   contenido delante de lo visible, peleando con el valor exacto que
   view.js calcula y fija a continuación. Ninguna fórmula de padding
   cambia con esta clase — ni aquí ni en la media query de móvil de más
   abajo, que se queda tal cual estaba (revisión 5, punto 21: reposo
   siempre en el gutter, sangre solo durante el arrastre). Ronda 2
   (coordinador) — view.js ya no apunta la tarjeta 1 a "su x de hoy" bajo
   600px (ese x, ~-1px a 380px, era un accidente del suelo de scrollLeft,
   no una posición de diseño): apunta al mismo gutter donde este
   scroll-padding-inline ya asienta el resto de tarjetas, así que el
   propio snap deja de tener nada que corregir. */
.families__track.is-looped {
  overflow-anchor: none;
}
/* Revisión 5, punto 5 — mientras el motor de autoplay conduce el track,
   el snap se desactiva (si no, cada fotograma pelearía contra un punto de
   snap); en cuanto el usuario interactúa (hover/foco/scroll) JS quita
   esta clase y el snap vuelve, ver js/main.js.
   BUG real, encontrado en vivo (no en el código): con scroll-snap-type
   activo, una asignación programática de scrollLeft durante el arrastre
   con ratón (pointermove) queda anulada por el propio snap del
   navegador ANTES de poder leerse — el arrastre se sentía "atascado",
   scrollLeft no se movía nunca. `.is-dragging` (añadida en pointerdown,
   quitada en pointerup/pointercancel) desactiva el snap por la misma
   razón que `.is-autoplaying`, y se reactiva al soltar. */
/* docs/port-phase-3-fixes.md C2.1 — `.is-mouse-held` (ratón encima o
   arrastrando) apaga el snap igual que `.is-autoplaying`/`.is-dragging`:
   sin ella, en cuanto el ratón se va, el snap volvía ANTES de que el motor
   alcanzara 0.05px/s, saltando hacia atrás a la tarjeta más cercana. */
.families__track.is-autoplaying,
.families__track.is-mouse-held,
.families__track.is-dragging {
  scroll-snap-type: none;
}
.families__track.is-dragging {
  cursor: grabbing;
}

/* Revisión 5, punto 21 — en móvil las tarjetas tienen que llegar a los dos
   bordes del viewport (no recortadas por el contenedor) mientras el swipe
   para de asentarse en el gutter del contenedor: se retira el padding-left
   calculado (que aquí, por debajo de 1280px, resolvía siempre a
   var(--gutter), empujando la primera tarjeta hacia dentro) y se usa
   scroll-padding-inline en su lugar — el snap calcula sus puntos como si
   el "puerto" empezara `gutter` px más adentro, así que a partir de la
   segunda tarjeta cada posición de reposo alinea con el mismo margen que
   el resto de la página, mientras que DURANTE el arrastre la tarjeta sí
   llega al borde físico. La primera tarjeta, en cambio, arranca a ras del
   borde en scrollLeft:0 (scrollLeft no puede ser negativo) — deliberado:
   es el patrón de "primera tarjeta a sangre, resto con gutter" que pide
   el punto 21, no un error de redondeo. */
@media (max-width: 599.98px) {
  .families__track {
    padding-left: 0;
    scroll-padding-inline: var(--gutter);
  }
}

.families__card {
  flex: 0 0 86%;
  scroll-snap-align: start;
  position: relative;
  /* SIN overflow:hidden — a propósito: la corona (ver más abajo) se sale
     a propósito del borde de la imagen. */
}
.families__photo { position: relative; }
.families__media {
  aspect-ratio: 223 / 233;
  border: none; /* revisión 4: sin rim blanco (brief §05 "no white rim") */
  border-radius: 18px;
}
/* Corona decorativa (brief §05, componente nuevo — sustituye al
   precio-tag de la revisión 3). Original, no calca la corona del logo
   (regla dura del cliente §4 del proyecto).
   Port fix C7 (docs/port-fix-c7.md §C7.2) — geometry replaced with Pixun's
   own artwork (M:\MAIN PROJECTS\todobazar\SVG\icon_boxes.svg, viewBox
   34.96×26.84, was 70×60): his drawing is already tilted, so the
   `transform: rotate(12deg)` this theme added to fake a tilt on the old
   straight three-ball crown is REMOVED here — keeping it would double the
   tilt. Also removes the stroke/paint-order rim: his drawing has no
   outline. Position/size unchanged: same top/right, same rendered
   width (70px); height now follows the new viewBox ratio instead of the
   old 60/70 (see the report for the measured px change). This class is
   shared with .step__crown (06, same element carries both classes via
   className), so dropping the rotation here also un-rotates that crown —
   intended: 06's badge switches to the same tilted artwork below. */
.families__crown {
  position: absolute;
  top: -20px;
  right: -10px;
  width: 70px;
  height: auto;
  z-index: 1;
}
.families__crown-body {
  fill: var(--accent-2);
}
.families__crown-shine {
  /* --bg-elevated-2 is #FFFFFF exactly (theme.json) — matches the source
     circles' fill:#fff with no signed-token substitution needed. */
  fill: var(--bg-elevated-2);
}
/* Leyenda debajo de la foto, centrada (brief §05) — ya no una franja
   superpuesta a la imagen. */
.families__caption {
  margin-top: var(--sp-3);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-1);
  text-align: center;
}
.families__name {
  font-family: var(--font-body);
  font-size: 20px;
  font-weight: 600;
  color: var(--text-primary);
}
.families__units {
  font-size: var(--fs-small);
  color: var(--text-tertiary);
}

/* Revisión 5, punto 5 — pista infinita: los puntos ya no representan una
   posición (el track duplica el set de tarjetas y da la vuelta sin
   salto), así que se retiran del todo (marcado, CSS y JS — ver el HTML y
   js/main.js). */

/* Revisión 4: escritorio deja de ser una rejilla — vuelve a ser la misma
   fila desplazable de móvil, solo que con tarjetas de ancho fijo
   (≈223px, brief §05) en vez de porcentaje, para que a 1440px se vean
   ≈5.5 tarjetas y la última quede cortada por el borde real de la
   ventana (la sangría del track, arriba). */
@media (min-width: 600px) {
  .families__card { flex: 0 0 260px; }
}
@media (min-width: 1280px) {
  .families__card { flex: 0 0 223px; }
}

/* Revisión 5, punto 5 — "hay una sombra fea al hover, quítala": se retira
   el hover de tarjeta entero (lift + --shadow-card), no solo la sombra —
   el lift leía como parte del mismo efecto. --shadow-card queda sin usar
   en esta hoja (token vivo, no se borra por si un futuro componente lo
   retoma; ver docs/design-tokens.md). */

/* ==========================================================================
   06 · ESCALA DE COMPRA
   ========================================================================== */

.scale {
  background: var(--bg-elevated);
  padding-block: var(--section-pad) 0; /* revisión 4: sin padding inferior — 07 sigue justo debajo (brief "same ground... ≈26px gap") */
}
.scale__head {
  margin-bottom: var(--sp-10);
  text-align: center;
}
.scale__head h2 {
  margin-bottom: var(--sp-3);
  font-size: clamp(1.875rem, 1.382rem + 2.08vw, 3.25rem); /* 30px → 52px, brief "Type scale" */
}
.scale__head .section-lead {
  color: var(--text-primary); /* brief §06: lead ink, no --text-secondary */
  max-width: 415px;
  margin-inline: auto;
}

/* Revisión 4 (brief §06): tres tarjetas de FOTO en fila, sin marco ni
   fondo propios, texto alineado a la izquierda debajo — ya no la caja
   con borde de la revisión 3. Sin flechas entre pasos (ocultas más
   abajo). Fila centrada a ≈888px (medida de la maqueta a 1440). */
.scale__row {
  display: flex;
  flex-direction: column;
  gap: var(--sp-8);
  /* Fase 4 fix round (account session, 2026-09-14) — same escape as
     `.capacity__grid` above (validator B4): core prints
     `:where(.wp-block-columns){margin-bottom:1.75em}` (specificity 0), and
     `.scale__row` is the LAST child of `.scale` (inc/patterns.php) with
     nothing after it and no padding-bottom/border on `.scale` or its
     `.container` wrapper to stop the collapse — the 1.75em escaped through
     both and landed as extra space between 06 and 07 (measured live: 28px
     at 380, ~29.75-29.8px at 768+ — 1.75 × the body font-size in px at
     each width, confirming the source). `.tariff`'s own 26px top padding
     (below) was the only INTENDED gap; this margin was stacking on top of
     it. A single class already beats `:where()`'s zero specificity, so no
     selector reinforcement is needed here (unlike the capacity fix, which
     had a display:grid-vs-is-layout-flex conflict to win as well). */
  margin-bottom: 0;
}
.step-arrow,
.scale__arrow-down {
  display: none;
}

.step {
  /* Port fix C13 — cause: on page 20 (id 20, hand-placed by Pixun) the step 3
     card's `.step__crown` figure sits as a direct child of `.step`, a sibling
     of `.step__photo`, instead of nested inside it like steps 1/2 and the
     `inc/patterns.php` default. `.families__crown`/`.step__crown` is
     `position:absolute; top:-15px; right:-10px`, so it needs a positioned
     ancestor. `.step__photo` (position:relative, below) covers steps 1/2.
     For step 3, absent this rule the ONLY thing giving `.step` a containing
     block was an accident: `.step` also carries `.reveal`, and
     `.js .reveal { animation: reveal-fallback .01s linear 2.5s forwards }`
     targets `transform`, which per spec makes the animated element a
     containing block for as long as the animation is in effect — `forwards`
     holds that indefinitely in normal browsing. Under
     `prefers-reduced-motion: reduce` the theme correctly cancels that very
     animation (`.js .reveal { animation: none }`, ~3577), which removes the
     accidental containing block too: the crown then escapes to the page's
     initial containing block, landing at `right:-10px` of the FULL VIEWPORT
     WIDTH regardless of breakpoint (measured: scrollWidth 390 at 380,
     1450 at 1440 — always innerWidth+10, matching the account session's
     report) and `top:-15px` of the document (off-screen vertically too).
     Explicit `position: relative` here replaces that accident with a real
     containing block, independent of animation/reduced-motion state.
     `.step` has zero padding and `.step__photo` is its first child with no
     top margin, so both share the same top/right edges (full column width,
     no side padding) — anchoring to `.step` instead of `.step__photo`
     computes the identical box, confirmed live (see docs/port-fix-c13.md):
     no change for steps 1/2 (their `.step__photo` stays the nearer
     positioned ancestor), no change to 05's `.families__crown` (different
     ancestor chain), no DOM/DB write. */
  position: relative;
  background: none;
  border: none;
  padding: 0;
  text-align: left;
}
.step__photo { position: relative; margin-bottom: var(--sp-4); }
.step__media {
  aspect-ratio: 282 / 235;
  border-radius: 10px;
}
.step__crown { width: 60px; top: -15px; }
.step__title {
  font-family: var(--font-body); /* interfaz/dato, no titular Cooper (brief §06) */
  font-weight: 600;
  font-size: 16px;
  margin-bottom: var(--sp-2);
}
.step__desc {
  font-size: 14px;
  color: var(--text-secondary);
  margin-bottom: var(--sp-4);
}
.step__price {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}
.step__price-label {
  font-size: 14px;
  color: var(--text-tertiary);
}
.step__price-value { font-weight: 700; font-size: 14px; }

.step--locked { border: none; } /* brief §06: "no thick border" */
.step__price--locked {
  background: none;
  border-color: transparent;
}
.step__lock { width: 18px; height: 18px; color: var(--text-primary); }

/* Defecto medido en vivo a 380/768px (viewport real, no maqueta), dos causas
   del bloque núcleo `core/columns`, ambas con !important y pensadas para un
   `.wp-block-columns` en fila (flex-direction: row) — aquí `.scale__row`
   fuerza flex-direction: column por debajo de 960px para apilar los 3 pasos,
   así que las dos caen sobre el eje que no tocan en fila:
   1) `.wp-block-columns { flex-wrap: wrap !important }` (sin media query,
      solo se anula a nowrap desde 782px) — con dirección columna y alto
      indefinido, wrap hace que el motor reparta los 3 pasos en líneas
      sintéticas de alto igual en vez de apilarlos.
   2) `.wp-block-columns:not(.is-not-stacked-on-mobile) > .wp-block-column
      { flex-basis: 100% !important }` (bajo 782px) — flex-basis cae sobre
      el ALTO (eje principal en columna), no el ancho, y ambas cosas juntas
      fijaban cada `.step` a ~345px de alto repartido entre los tres,
      mientras su contenido real (foto 290 + título + descripción + precio)
      mide ~392px — 14px de solape entre pasos consecutivos.
   Selectores de más especificidad que los de core (2 y 4 clases vs. 1 y 3)
   con el mismo !important los neutralizan sin tocar la fila de escritorio
   (regla de abajo, sin !important, no se ve afectada). */
@media (max-width: 959px) {
  .wp-block-columns.scale__row {
    flex-wrap: nowrap !important;
  }
  .wp-block-columns.scale__row > .wp-block-column.step {
    flex: 0 0 auto !important;
  }
}

@media (min-width: 960px) {
  .scale__row {
    flex-direction: row;
    align-items: flex-start;
    gap: 40px; /* revisión 5, punto 6 (antes 16px) */
    max-width: initial; /* revisión 5, punto 6 (antes 888px) */
    margin-inline: auto;
  }
  .step { flex: 1 1 0; }
}

/* ==========================================================================
   07 · ACCESO A TARIFA
   ========================================================================== */

/* Revisión 4 (brief §07): mismo fondo que la 06 y pegado a ella (~26px),
   panel amarillo (ya no filete ink sobre cream) sobre una franja diagonal
   que asoma por sus lados. */
.tariff {
  background: var(--bg-elevated);
  padding-block: 26px var(--section-pad);
  /* Revisión 5, punto 16 — la franja (`.tariff__stripe`, más abajo) sigue
     usando width:100vw para centrarse de verdad detrás del panel, pero
     100vw INCLUYE el ancho de la barra de scroll vertical (si la hay),
     así que sobra unos pixeles por la derecha de la página real y
     provoca scroll horizontal — reportado dos veces. La solución NO es
     overflow-x:hidden en html/body (regla dura: oculta el síntoma y
     puede romper la cabecera sticky): se recorta aquí, en la propia
     sección, con overflow: clip (las dos dimensiones, no solo -x).
     Comprobado en vivo a 380px: con bottom:-45px/height:110px (nuevos
     valores del punto 16) la franja se salía ~5px por DEBAJO del propio
     borde de la sección hacia la 08 (el suelo del padding vertical de la
     sección, --section-pad, tiene un mínimo de 40px vía clamp — menos de
     los 45px que pide la franja) — con solo overflow-x:clip esos 5px se
     pintaban encima del arranque de la 08 (franja diagonal roja/crema
     sobre fondo gris). overflow:clip a secas la contiene también en
     vertical; el único coste es un recorte de ~5px de la propia franja
     decorativa en el viewport más estrecho, invisible frente a colarse
     en la sección siguiente. */
  overflow: clip;
}
.tariff__panel-wrap {
  position: relative;
}
.tariff__panel {
  position: relative;
  z-index: 1;
  background: var(--accent-2);
  border: none;
  border-radius: 16px;
  padding: 34px;
  max-width: 888px;
  margin-inline: auto;
}
.tariff__intro h2 { margin-bottom: var(--sp-3); font-size: clamp(1.375rem, 1.196rem + 0.75vw, 1.875rem); } /* 22px → 30px, brief "Type scale" */
.tariff__points {
  margin-top: var(--sp-6);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
/* .tariff__points li — alineación, peso, gap de icono y color (--text-secondary,
   5.4:1 sobre el amarillo de esta sección) vienen del bloque combinado con
   `.logistics__list li` en la sección 08, más arriba en la hoja (revisión 5,
   punto 17: mismo componente, mismo token, valores de contraste distintos
   solo porque el fondo detrás cambia). */
.tariff__lock {
  display: block;
  width: 22px;
  height: 22px;
  margin-top: var(--sp-4);
  color: var(--accent);
}
.tariff__lock rect { fill: currentColor; }
.tariff__lock path { stroke: currentColor; }

/* Franja diagonal detrás del panel (brief §07): a diferencia de la del
   pie (.stripe-band base), esta es más gruesa (60px) y de periodo mayor
   (76px) — modificador propio en vez de tocar los valores compartidos. */
.tariff__panel-wrap .tariff__stripe {
  position: absolute;
  left: 50%;
  bottom: -45px; /* revisión 5, punto 16 (antes -25px) */
  transform: translateX(-50%);
  width: 100vw;
  height: 110px; /* revisión 5, punto 16 (antes 60px) */
  z-index: 0;
  background: repeating-linear-gradient(
    -45deg,
    var(--accent) 0, var(--accent) 38px,
    var(--bg) 38px, var(--bg) 76px
  );
}

.tariff__form {
  margin-top: var(--sp-8);
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}
.tariff__submit {
  width: 100%;
  /* Revisión 4 (brief §07): filete BLANCO (no ink) + sombra ink 6px —
     variante propia de .btn--primary, que por defecto lleva filete ink. */
  border-color: var(--bg-elevated-2);
  box-shadow: 6px 6px 0 var(--bg-pure);
}
.tariff__submit:hover {
  transform: translate(3px, 3px);
  box-shadow: 3px 3px 0 var(--bg-pure);
}
.tariff__submit:active {
  transform: translate(6px, 6px);
  box-shadow: 0 0 0 var(--bg-pure);
}

.form-legal {
  font-size: var(--fs-small);
  color: var(--text-tertiary);
  margin-top: var(--sp-3);
}
.form-legal a { text-decoration: underline; }

@media (min-width: 960px) {
  .tariff__panel {
    padding: 78px; /* revisión 5, punto 7 (antes var(--sp-12), 48px). Solo desde 960px —
                       por debajo se queda en 34px (base, sin tocar): a 380px, 78px por
                       cada lado dejaría menos de 200px de formulario. */
    display: grid;
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    gap: var(--sp-10);
  }
  .tariff__form { margin-top: 0; }
}

/* Port fix C11 (docs/port-fix-c11.md) — editor style «Ancho del contenedor»
   (functions.php, todobazar_register_block_styles): lleva el panel al 100%
   del content-box de `.container` en vez de los 888px de siempre. Sin estilo,
   el bloque no cambia un byte (regla del brief) — de ahí que todo lo de aquí
   abajo cuelgue de `.is-style-container-width`, nunca de `.tariff__panel` a
   secas.
   Registrado en core/group (Gutenberg no deja limitar un block style a una
   clase): un Group cualquiera fuera de 07 que reciba este estilo no tiene
   selector que lo alcance aquí y no cambia de aspecto. */
.tariff__panel.is-style-container-width {
  max-width: none;
}

/* Port fix C11.1 (docs/port-fix-c11.md, ronda 2) — Pixun eligió, sobre las
   opciones (a)/(b)/(c), «c mejor»: la columna del formulario se queda a un
   ancho fijo (480px) y la intro se lleva TODO el sobrante como 1fr, en vez
   del reparto proporcional 7fr/5fr o los máximos 460/420 + space-between de
   la primera ronda (medido entonces: sin tope la intro llegaba a 595-665px y
   el formulario a 425-475px, con ~150-200px de amarillo vacío bajo la lista
   — ver el historial de este fichero para esos números).

   Trampa medida antes de fijar el punto de corte: aplicar minmax(0,1fr) 480px
   + column-gap:78px YA a partir de 960px (el breakpoint que usa el resto de
   la sección) deja la intro en 182px / 8 líneas — peor que el propio problema
   que esto corrige. Entre 960px y el punto de corte de abajo, el estilo NO
   lleva regla propia: hereda el grid de siempre (`.tariff__panel` sin
   estilo, más arriba — 7fr/5fr, gap var(--sp-10)) ya estirado al 100% del
   container por el `max-width:none` de arriba.

   Punto de corte — el viewport donde la intro alcanza exactamente 460px con
   la fórmula de (c), para no bajar de ahí:
     ancho del content-box de .container  = viewport − 2×32px (--gutter a
       partir de 800px de viewport, ver tokens más arriba) — para
       viewport < 1400px (--container-max), el content-box crece 1:1 con el
       viewport, así que esto se puede despejar en viewport directamente.
     intro = content-box − 2×78px (padding del panel) − 78px (column-gap
       nuevo) − 480px (form fijo) ≥ 460px
     ⇒ content-box ≥ 460 + 156 + 78 + 480 = 1174px
     ⇒ viewport ≥ 1174 + 64 = 1238px
   Comprobado en vivo: a 1238px la intro mide exactamente 460.00px; a 1237px
   quedaría en 459px (por debajo del mínimo pedido) — de ahí `min-width: 1238px`,
   no 1200px ni un redondeo a la centena. */
@media (min-width: 1238px) {
  .tariff__panel.is-style-container-width {
    grid-template-columns: minmax(0, 1fr) 480px;
    column-gap: 78px;
  }
}

/* ==========================================================================
   FORMULARIOS — campos, estados de foco/error/éxito
   ========================================================================== */

/* Revisión 4 (brief §10, reusado en §07 — "same inside-label treatment
   in 07") — la etiqueta deja de ir ENCIMA del campo y pasa a vivir DENTRO
   de la misma caja, en su esquina superior izquierda, con el texto del
   input debajo. El borde/fondo ya no los lleva el input: los lleva
   `.field` (la caja entera), y el input queda transparente y sin borde
   propio — así la etiqueta y el texto comparten un único marco visual.
   Pill por defecto (07, brief "pill inputs"); la 10 pide rectángulo
   (ver `.request__form .field` más abajo). El textarea no puede ser
   píldora (caja multilínea) — ver su propia regla. */
/* Revisión 6, punto 6 — "haz los labels un poco más grandes y deja que
   respiren más por dentro" (Pixun, verbatim): +6px de aire vertical (top
   6→12, bottom 8→14) y +8px de padding en línea (22→30) sobre los valores
   de la revisión 4/5, en las dos formas (07 píldora, 10 rectángulo — ver
   `.request__form .field` más abajo, que solo pisa el padding-inline). */
.field {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  min-height: 56px;
  padding: 12px 30px 14px;
  border: 3px solid var(--border-strong);
  border-radius: var(--radius-pill);
  background: var(--bg-elevated-2);
  transition: box-shadow .15s ease;
}
.field label {
  font-size: 13px; /* revisión 6, punto 6 (antes 11px) — +2px, peso sin cambios */
  font-weight: 600;
  line-height: 1.2;
  color: var(--text-tertiary); /* 5.9:1 sobre blanco (brief §10) */
}
.field input,
.field textarea {
  min-height: auto;
  padding: 0;
  border: none;
  border-radius: 0;
  background: transparent;
  color: var(--text-primary);
}
.field textarea {
  min-height: 70px;
  resize: vertical;
}
/* Revisión 6, punto 5 — el halo de :focus-within (4px --accent-dim) se
   retira: "quita el ::focus o ::active de los inputs de los formularios"
   (Pixun, verbatim). Decisión de diseño suya, no un bug — los estados de
   error y corregido (más abajo) se quedan intactos. */

.field-error {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  font-size: var(--fs-small);
  color: var(--text-primary);
  margin: 0;
}
/* El atributo [hidden] tiene que ganar siempre al display:flex de arriba —
   si no, el icono "!" queda visible antes de que el campo se valide. */
.field-error[hidden] {
  display: none;
}
.field-error__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--text-primary);
  font-size: var(--fs-small);
  font-weight: 700;
  line-height: 1;
  margin-top: 2px;
}

/* Distinguible sin depender del color (brief §4.5): el filete ya es ink
   por defecto (--border-strong), así que el estado de error no puede
   "ponerse más oscuro" — sube de grosor (3px → 5px), un cambio de FORMA,
   más el icono "!" y el texto del mensaje (field-error), que ya son
   señales no cromáticas por sí mismas. */
.field.has-error {
  border-width: 5px;
}
.field.is-valid .field-error {
  color: var(--text-secondary);
}

.form-status {
  font-size: var(--fs-small);
  font-weight: 600;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-elevated);
}
.form-status[data-status="success"] {
  background: var(--bg-elevated-2);
}

/* ==========================================================================
   08 · LOGÍSTICA Y COBERTURA
   ========================================================================== */

.logistics {
  background: var(--bg-elevated);
  padding-block: var(--section-pad);
}
.logistics__text h2 {
  margin-bottom: var(--sp-10); /* revisión 5, punto 18 (antes var(--sp-4)) */
  font-size: clamp(2rem, 1.418rem + 2.45vw, 3.625rem); /* 32px → 58px, brief "Type scale" */
}
/* Revisión 4 (brief §08): viñetas de corona en ink ≈26px en vez de los
   cuadrados anteriores — ya no un ::before, un <svg> real con
   currentColor, y ≈39px de separación entre filas.
   Revisión 5, punto 17 — componente COMPARTIDO con `.tariff__points` (07):
   el bullet pasa a llamarse `.crown-bullet` (antes `.logistics__bullet`,
   nombre demasiado específico de sección para algo que ahora vive en dos
   sitios) y las reglas de alineación/peso se agrupan bajo un selector que
   cubre las dos listas — ver el bloque combinado más abajo. */
.logistics__list {
  display: flex;
  flex-direction: column;
  gap: 39px;
}
.logistics__list strong { color: var(--text-primary); }

/* Componente compartido (revisión 5, punto 17): `.tariff__points` (07) y
   `.logistics__list` (08). `align-items: center` (antes flex-start en la
   08, punto suelto en la 07) para que el icono quede EXACTAMENTE centrado
   con la línea de texto — medido en vivo, sin el margin-top:2px que la 08
   necesitaba con flex-start. Peso 600 en todo el texto (antes solo el
   `<strong>` de la 08 llevaba ink; el resto del texto no llevaba peso
   propio). */
.tariff__points,
.logistics__list {
  font-weight: 600;
}
/* Block style "Coronas" / "Coronas rojas" / "Coronas amarillas" (core/list,
   port fix C7 §C7.1) — mismo tratamiento de fila que .tariff__points li /
   .logistics__list li; selectores añadidos, sin duplicar declaraciones
   (brief §5, extendido por C7). El icono de corona en sí (.crown-bullet) se
   inserta a mano dentro de cada elemento de lista, igual que hoy — no hay
   equivalente automático en el bloque nativo. */
.tariff__points li,
.logistics__list li,
.wp-block-list.is-style-crowns li,
.wp-block-list.is-style-crowns-red li,
.wp-block-list.is-style-crowns-yellow li {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  color: var(--text-secondary);
}
.crown-bullet {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  color: var(--text-primary);
}

.logistics__media {
  margin-top: var(--sp-8);
}
.logistics__media .media-placeholder {
  aspect-ratio: 16 / 9;
  border-radius: 0; /* brief §08: "square corners" */
}

@media (min-width: 960px) {
  .logistics__grid {
    display: grid;
    grid-template-columns: minmax(0, 6fr) minmax(0, 6fr);
    /* Revisión 7, punto 1 — "ahi hay mas gap, que esta muy junto ahora"
       (Pixun, verbatim, sobre este mismo gap de la revisión 5, punto 8):
       40px a 80px entre foto y texto en el rango de escritorio. El apilado
       de móvil no lleva `gap` en esta regla — no se toca. */
    gap: var(--sp-20);
    align-items: center;
  }
  /* Foto a la izquierda, texto a la derecha (brief §08) — orden visual
     por `order`, el DOM se queda con el texto primero (nada focalizable
     en la media, así que no hay salto de tabulación que corregir). */
  .logistics__media { margin-top: 0; order: -1; }
  .logistics__media .media-placeholder { aspect-ratio: 613 / 337; }
}

/* ==========================================================================
   09 · CANAL MINORISTA
   ========================================================================== */

/* Revisión 4 (brief §09): ≈330px de alto a 1440, un solo teléfono que
   sobresale por debajo del borde de la sección (overflow:hidden aquí es
   lo que lo recorta, no el propio marco del teléfono). */
/* Port fix C10 (docs/port-fix-c10.md): `.retail` es ahora la raíz de un
   `core/cover` (antes `core/group`), para que Pixun pueda poner imagen de
   fondo y opacidad del velo desde el panel. `core/cover` trae su propio
   CSS (wp-includes/blocks/cover/style.css): min-height:430px, padding:1em,
   display:flex, justify-content:center, align-items:center, overflow:
   hidden/clip. Esta hoja se encola DESPUÉS de esa (functions.php,
   dependencia 'global-styles'), así que gana por orden de cascada sin
   !important — pero hay que neutralizar el padding (nunca lo hubo aquí) y
   dejar explícitos min-height/display/align-items para que el diseño sin
   imagen quede pixel-idéntico al de antes. justify-content:center de cover
   no cambia nada visualmente: el único hijo flex es
   `.wp-block-cover__inner-container`, que cover mismo pone a width:100%. */
.retail {
  position: relative;
  overflow: hidden;
  /* Revisión 5, punto 9: min-height:500px A TODO ANCHO (antes solo ≥768px, y
     a 330px) — se retira el padding-block de aquí (el min-height + el
     centrado vertical ya reservan el espacio; misma técnica que ya usaba
     la query de ≥768px, ahora también en móvil por juicio del estudio: sin
     centrar, el contenido quedaría arriba con un hueco muerto debajo). */
  min-height: 500px;
  padding: 0; /* anula el padding:1em de core/cover (brief §09) */
  display: flex;
  align-items: center;
}
/* La vieja `.retail > .container { width: 100%; }` ya NO aplica ni hace
   falta: existía porque `.container.retail__grid` era hijo directo del
   `.retail` con display:flex (flex-item que se encogía sin ese width:100%).
   Con `core/cover`, `.container` cuelga de `.wp-block-cover__inner-
   container` (que ya trae width:100% de core) como bloque normal, no como
   flex-item — y `width:100%` explícito ahí rompía el centrado
   max-width:1400px/margin-inline:auto en ≥1440px (medido: el gutter
   izquierdo se quedaba en 32px en vez de 52/292px). Igual que el resto de
   `.container` de la hoja (regla base, línea ~304): sin `width` propio,
   auto + max-width + margin:auto centran solos. */
.retail__media-col {
  position: relative;
  display: flex;
  justify-content: center;
  min-height: 260px;
}
/* C10 round 2, punto 3 — apilado (<768px): el CTA (con su filete/sombra de
   6px) y el teléfono quedaban a 0px, y el teléfono a 0px del borde inferior
   de la sección — `overflow:hidden` recortaba la sombra del teléfono
   (--shadow-phone, 12px + 32px de blur). `.retail{align-items:center}` no
   añade ningún hueco entre hijos en flujo normal (solo centra el bloque
   entero cuando sobra alto, y aquí no sobra: el contenido apilado ya excede
   los 500px de min-height). Aire explícito, solo <768px — a ≥768px la fila
   en grid ya no apila y esto no debe tocarla. */
@media (max-width: 767px) {
  .retail__media-col { margin-top: 48px; }
  /* 64px, no 48: --shadow-phone (0 12px 32px) reaches visually ≈44px past
     the phone's own border box; 48px of padding left only ~4px of clean
     buffer before the section's own overflow:hidden edge — measured,
     bumped to a value that keeps the whole blur inside the box. */
  .retail { padding-bottom: 64px; }
}
.retail__text-col {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  text-align: center;
}
.retail__text-col .section-lead { margin-bottom: var(--sp-3); }
/* Revisión 4 (brief §09) — sustituye al .btn--pill negro de antes:
   amarillo, etiqueta ink, filete CREAM ≈6px (no ink). Revisión 6, punto 4 —
   ya sin la esquina despegada (Pixun: "parece un fallo, quítala"); con ella
   se retira también `position: relative`, que solo existía para dar
   contexto de posicionamiento a ese `::after`. */
/* Fix F3 (port-phase-2-fixes.md) — en el HTML `.retail__cta` vive en el
   propio `<a>` (mismo elemento que `.btn--outline`), así que el filete/
   sombra de 6px se dibujan sobre la píldora. En el bloque, `retail__cta`
   la pone Gutenberg en el `.wp-block-button` (el div contenedor, radio 0,
   NUNCA el link) — ahí el filete/sombra dibujaban un rectángulo roto
   detrás de la píldora. Se retargetiza al enlace real, mismo patrón que
   `.wp-block-button.header-cta .wp-block-button__link` (más arriba). El
   `align-self`/`margin-top` SÍ quedan en el wrapper, pero el que de verdad
   es el hijo flex de `.retail__text-col` es `.wp-block-buttons` (el
   `.wp-block-button` va anidado un nivel más adentro), así que se
   retargetizan también un nivel arriba. */
.retail__text-col > .wp-block-buttons {
  margin-top: var(--sp-3);
  align-self: center;
}
.wp-block-button.retail__cta .wp-block-button__link {
  border-width: 6px;
}
/* Especificidad: .section--inverted .btn--outline (0,2,0) puesta ink por
   defecto en las secciones oscuras — hace falta igualar especificidad
   para que el filete cream de este CTA gane (brief §09: "cream rim"). */
.section--inverted .wp-block-button.retail__cta .wp-block-button__link {
  border-color: var(--bg);
  /* Revisión 5, punto 10 — sombra de presión en crema, no ink: la 09 es
     una sección invertida (fondo ink), una sombra ink ahí sería
     invisible. Mismo criterio que .quicklane__cta/.btn--pill invertido.
     Prevalece sobre el box-shadow: 6px ink de .is-style-sticker-yellow
     (misma especificidad, esta regla está más abajo en la hoja). */
  box-shadow: 6px 6px 0 var(--bg);
}
.wp-block-button.retail__cta .wp-block-button__link:hover { box-shadow: 3px 3px 0 var(--bg); }
.wp-block-button.retail__cta .wp-block-button__link:active { box-shadow: 0 0 0 var(--bg); }
/* C10 round 2, punto 4 — si Pixun elige un color de overlay claro en el
   panel de Cover, el editor calcula isDark:false y añade `is-light` al
   propio bloque (misma clase que usa core, wp-includes/blocks/cover;
   compositeIsDark() en block-library.js). `.section--inverted` no se
   quita — sigue en el className — así que sin esto el texto se queda
   crema sobre un fondo ahora claro (medido: H2 1.47:1, lead 1.09:1).
   Reasigna las MISMAS variables de lectura que `.section--inverted` ya
   toca, de vuelta a sus valores de fondo claro (`:root`, arriba de la
   hoja) — mayor especificidad (.retail.is-light, dos clases) que
   `.section--inverted` sola, gana sin !important. El filete/sombra cream
   del CTA (pensados para fondo oscuro) pasan a ink por la misma razón; el
   marco del teléfono (--bg-elevated/-2) NO se toca aquí — es un mockup de
   dispositivo, se queda oscuro tenga la sección el fondo que tenga. El
   chip ya no depende de estas variables (ver arriba), así que no hace
   falta tocarlo aparte. */
.retail.is-light {
  --text-primary: var(--wp--preset--color--text-primary);
  --text-secondary: var(--wp--preset--color--text-secondary);
  --text-tertiary: var(--wp--preset--color--text-tertiary);
  --border: var(--wp--preset--color--border);
  --border-strong: var(--wp--preset--color--border-strong);
  --accent-dim: var(--wp--preset--color--accent-dim);
  color: var(--text-primary);
}
.retail.is-light .wp-block-button.retail__cta .wp-block-button__link {
  border-color: var(--bg-pure);
  box-shadow: 6px 6px 0 var(--bg-pure);
}
.retail.is-light .wp-block-button.retail__cta .wp-block-button__link:hover { box-shadow: 3px 3px 0 var(--bg-pure); }
.retail.is-light .wp-block-button.retail__cta .wp-block-button__link:active { box-shadow: 0 0 0 var(--bg-pure); }
/* C10.3 (account session) — la bajada (.section-lead) pasa a --text-primary
   (ink pleno) en vez de --text-secondary SOLO aquí: a dimRatio 80 el amarillo
   (accent-2) se mezcla con el ink de debajo y --text-secondary (el gris
   medio) se quedaba en 3.71:1, por debajo de 4.5. --text-primary sí llega.
   El H2 no se toca — ya usaba --text-primary por herencia y no tenía este
   problema. El overlay rojo NO se ajusta con esto: el editor también lo
   marca is-light y ni con ink llega a 4.5 — queda registrado aquí como
   fondo NO recomendado para esta sección, decisión de Pixun si insiste en
   usarlo, no algo que este fix intente arreglar. */
.retail.is-light .section-lead {
  color: var(--text-primary);
}
/* Revisión 6, punto 4 — la esquina despegada (::after) se retira de este
   CTA: "hace rato que está allí y parece un fallo. Quítala. Por si acaso
   no está en sus planes." (Pixun, verbatim). El 10 (.request__submit-
   sticker::after) conserva la suya — no la comparten, y --shadow-sticker-
   peel sigue usándose ahí, no se toca el token. */

/* Marco de teléfono decorativo (brief §09) — CSS puro, ningún icono de
   terceros.
   Port fix C10 (docs/port-fix-c10.md) — Pixun: "la foto se queda pegada
   abajo y se corta, céntrala verticalmente". `top:25px` en position:absolute
   lo sacaba del flujo, así que `.retail__media-col` (min-height:260px) no
   reservaba sus ≈474px reales, y el sobrante quedaba recortado por
   `.retail` (overflow:hidden) — medido antes del fix: el teléfono se salía
   ≈147-267px por debajo del borde de la sección, en TODOS los anchos
   (380/768/1280/1440/1920), no solo desktop. Pasar a `position:relative`
   (sin `top`) lo devuelve al flujo: `.retail__media-col` (ya
   display:flex;justify-content:center) pasa a medir lo que el teléfono mide
   de verdad, y el centrado vertical que ya existía —`.retail__grid{align-
   items:center}` en ≥768px, `.retail{align-items:center}` en todos los
   anchos— lo centra sin tocar ancho, radio, notch ni sombra. A 1280/1440/
   1920: fila de ≈474-480px dentro de una sección con min-height:500px →
   cabe con ~10-13px de aire arriba y abajo (ver docs/port-fix-c10.md, tabla
   de geometría). A 380/768 (apilado, sin grid): el teléfono queda debajo
   del texto y la sección crece más allá de 500px para darle sitio completo
   (min-height es un suelo, nunca un techo) — el propio comentario de la
   Revisión 5 ya lo daba por hecho ("sin centrar, el contenido quedaría
   arriba con un hueco muerto debajo"). */
.retail__phone {
  position: relative;
  width: 220px;
  aspect-ratio: 260 / 560;
  background: var(--bg-elevated);
  border: 4px solid var(--bg-elevated-2);
  border-radius: 40px;
  overflow: hidden;
  /* Mismo tono ink (35,31,32) que el resto de sombras/filtros decorativos
     de la hoja (p. ej. .header-cta::after, .families__tag en revisiones
     anteriores, ya retirados) — no un negro puro nuevo. Tokenizado en el
     round 1 de fixes, ver --shadow-phone en :root. */
  box-shadow: var(--shadow-phone);
}
.retail__phone-notch {
  position: absolute;
  top: 10px;
  left: 50%;
  transform: translateX(-50%);
  width: 70px;
  height: 16px;
  background: var(--bg-pure);
  border-radius: var(--radius-pill);
  z-index: 2;
}
.retail__phone-screen {
  position: absolute;
  inset: 8px;
  border-radius: 32px;
  overflow: hidden;
  background: var(--bg-elevated-2);
}
.retail__video {
  position: absolute;
  inset: 0;
  border: none;
  border-radius: 0;
}
/* Port fix C10 — foto del móvil editable: Pixun inserta un `core/image`
   normal (sin clase propia, sin atributos que un `save()` de core no
   produciría) dentro de `.retail__phone-screen`; estas reglas puramente
   estructurales lo estiran a pantalla completa (el propio
   `.retail__phone-screen{overflow:hidden;border-radius:32px}` de arriba le
   recorta ya las esquinas) y, vía `:has()`, apagan el placeholder del play
   en cuanto hay una imagen — así Pixun no tiene que borrar nada a mano para
   ponerla, y basta con borrar la imagen (o su bloque) para volver al
   placeholder de siempre. `:has()` soportado en Edge/Chrome/Safari/Firefox
   desde 2023 — coherente con `color-mix()` ya usado más arriba en esta
   misma hoja. */
/* C10 round 2 fix — el descendiente `.wp-block-image` (sin `>`) también
   igualaba el `<figure class="wp-block-image media-placeholder__icon">` del
   PROPIO placeholder (anidado dos niveles más adentro, dentro de
   `.media-placeholder.retail__video`): el placeholder se ponía
   `display:none` a sí mismo y su icono absolute — SIN foto puesta. Acotado
   a hijo directo: la foto de Pixun entra como hijo directo de
   `.retail__phone-screen` (tal cual la inserta el editor), el placeholder
   nunca lo es. */
.retail__phone-screen > .wp-block-image {
  position: absolute;
  inset: 0;
  margin: 0;
}
.retail__phone-screen > .wp-block-image img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.retail__phone-screen:has(> .wp-block-image) .retail__video {
  display: none;
}
/* Brief §5 (heredado) — el filete fino queda en 1.4:1 sobre el fondo
   oscuro de esta sección; --border-strong (cream dentro de
   .section--inverted) en vez de --border. Aquí además hace de chip
   flotante sobre el vídeo (brief §09), esquina superior izquierda.
   C10 round 2, punto 4 — el chip flota SIEMPRE sobre la pantalla oscura del
   móvil (el marco del teléfono no cambia con `.is-light`, ver más abajo), así
   que su color/borde/punto se fijan al valor "invertido" literal en vez de
   leer `--text-secondary`/`--border-strong`/`--text-tertiary` — si
   `.retail.is-light` los reasigna a los tonos ink (para el TEXTO de la
   sección sobre fondo claro), un chip que siguiera esas variables pasaría a
   texto Y PUNTO ink sobre placa oscura: casi invisibles. Mismos valores que
   producía `.section--inverted` antes de round 2, ahora explícitos y no
   condicionados — el punto (`.retail__status-dot` aquí abajo, y su gemelo
   real `.retail__status--chip::before` más abajo en la hoja, que es el que
   de verdad pinta en el bloque) se corrigió en la ronda de validación
   siguiente: seguía leyendo `--text-tertiary` y se apagaba igual que el
   texto lo hacía antes de este mismo fix. */
.retail__status--chip {
  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  border: 1px solid #F7EFDA;
  border-radius: var(--radius-pill);
  padding: var(--sp-1) var(--sp-3);
  font-size: 11px;
  color: #C5BEAE;
  background: color-mix(in srgb, var(--bg-pure) 55%, transparent);
}
.retail__status-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #9E978C;
  flex-shrink: 0;
}

@media (min-width: 768px) {
  /* Revisión 5, punto 9 — min-height:500px ya vive en la regla base (todo
     ancho); aquí solo queda el layout de rejilla de dos columnas. */
  .retail__grid {
    display: grid;
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    gap: var(--sp-10);
    align-items: center;
  }
  .retail__text-col { text-align: left; }
  .retail__text-col > .wp-block-buttons { align-self: flex-start; }
}

/* ==========================================================================
   10 · PIDE SURTIDO
   ========================================================================== */

.request {
  background: var(--bg);
  padding-block: var(--section-pad);
}
/* Revisión 4 (brief §10): "drop the centred narrow layout" — ya no se
   estrecha ni se centra, pasa a ser una rejilla de dos columnas (H2 rojo
   + bajada / formulario ≈483px) desde 960px; en móvil se apila, a ancho
   completo y alineado a la izquierda. */
.request__narrow { text-align: left; }
.request__head { margin-bottom: var(--sp-8); }
.request__head h2 {
  margin-bottom: var(--sp-3);
  color: var(--accent); /* brief §10: H2 en rojo — ≥24px, 3.16:1 permitido para texto grande */
  font-size: clamp(2.125rem, 1.206rem + 3.87vw, 4.6875rem); /* 34px → 75px, brief "Type scale" */
}

.request__form {
  margin-top: var(--sp-8);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.request__form .field { border-radius: 8px; padding-inline: 24px; } /* brief §10: rectángulo, no píldora; revisión 6 punto 6: 16px→24px, +8px sobre su propio valor */
.request__submit {
  position: relative;
  width: 100%;
  min-height: 45px;
  margin-top: var(--sp-6);
  /* Revisión 5, punto 19 — la barra roja desaparece: el sticker (hijo,
     ver más abajo) es todo lo que se ve. El botón entero se queda como
     área de clic/foco (el anillo de :focus-visible sigue viniendo del
     elemento real <button>, no del sticker). */
  background: none;
  border: 0px solid var(--bg-pure);
  border-radius: 8px;
}
/* Etiqueta como sticker que desborda la barra (brief §10): Cooper,
   blanco/cream (--bg, ≥24px pasa como texto grande sobre rojo), relleno
   rojo, filete blanco + contorno ink, ligeramente rotada, esquina
   despegada abajo a la derecha.
   Revisión 5, punto 10 — el sticker lleva su PROPIA sombra de presión
   (ink, visible sobre el fondo cream de la 10) además del anillo blanco
   ya existente (dos valores de box-shadow). El desplazamiento de presión
   vive en las variables --press-x/--press-y en vez de escribir el
   transform entero en :hover/:active — así se compone con el
   translate(-50%,-50%) rotate(-3deg) de reposo (que centra la pieza) sin
   que un estado pise al otro. */
.request__submit-sticker {
  position: absolute;
  top: 50%;
  left: 50%;
  --press-x: 0px;
  --press-y: 0px;
  transform: translate(calc(-50% + var(--press-x)), calc(-50% + var(--press-y))) rotate(-3deg);
  transition: transform .15s ease, box-shadow .15s ease;
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  font-family: var(--font-display);
  font-weight: 800;
  font-synthesis: none;
  font-size: clamp(1.5rem, 1.4rem + 0.5vw, 1.75rem); /* ≥24px, brief §10 */
  color: var(--bg);
  background: var(--accent);
  border: 3px solid var(--bg-pure);
  border-radius: 12px;
  box-shadow: 0 0 0 5px var(--bg), 6px 6px 0 var(--bg-pure);
  padding: var(--sp-2) var(--sp-6);
}
.request__submit:hover .request__submit-sticker {
  --press-x: 3px;
  --press-y: 3px;
  box-shadow: 0 0 0 5px var(--bg), 3px 3px 0 var(--bg-pure);
}
.request__submit:active .request__submit-sticker {
  --press-x: 6px;
  --press-y: 6px;
  box-shadow: 0 0 0 5px var(--bg), 0 0 0 var(--bg-pure);
}
.request__submit-sticker::after {
  content: "";
  position: absolute;
  bottom: 2px;
  right: 8px;
  width: 11px;
  height: 11px;
  background: var(--bg-elevated-2);
  clip-path: polygon(100% 100%, 0 100%, 100% 0%);
  filter: var(--shadow-sticker-peel);
  pointer-events: none;
}

@media (min-width: 960px) {
  .request__narrow {
    display: grid;
    grid-template-columns: 1fr minmax(0, 483px);
    gap: var(--sp-10);
    align-items: start;
  }
  .request__head { margin-bottom: 0; }
  .request__form { margin-top: 0; }
}

/* ==========================================================================
   FRANJA DIAGONAL — componente 8 (revisión 3), una sola vez, entre
   `</main>` y `<footer>`. Revisión 4 (brief §11): más fina — ≈27px de
   alto, periodo ≈13px, rojo la mitad. Ángulo -45deg: en
   linear-gradient() la banda de color queda PERPENDICULAR al vector del
   ángulo — -45deg (vector hacia arriba-izquierda) da bandas orientadas
   como "/" (de abajo-izquierda a arriba-derecha), que es el sentido que
   pide el brief. Puramente decorativa: aria-hidden en el HTML.
   ========================================================================== */
.stripe-band {
  height: 27px;
  background: repeating-linear-gradient(
    -45deg,
    var(--accent) 0,
    var(--accent) 6.5px,
    var(--bg) 6.5px,
    var(--bg) 13px
  );
}

/* ==========================================================================
   11 · PIE — revisión 4 (brief §11): fondo ROJO (--accent), no ya gris.
   Esto es LO CONTRARIO de una sección invertida: --text-primary normal
   (ink, 4.50:1) ya es la lectura que pasa AA sobre rojo, así que el pie
   NO lleva `.section--inverted` — llevarla pondría el texto en cream,
   que falla a los tamaños pequeños de aquí. Todo lo que antes leía
   --text-secondary/--text-tertiary (enlaces, franja legal, [PENDIENTE])
   pasa a --text-primary: "white text only where ≥19px+700, anything
   smaller goes in ink" (brief) — nada de este pie es tan grande.
   ========================================================================== */

.site-footer {
  background: var(--accent);
  padding-block: var(--section-pad-sm);
}

.footer-accordion summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-block: var(--sp-4);
  border-bottom: 1px solid var(--bg); /* --border no llega a 3:1 sobre el rojo del pie (brief §11) */
  cursor: pointer;
  list-style: none;
}
.footer-accordion summary::-webkit-details-marker { display: none; }
.footer-accordion summary h3 { font-family: var(--font-body); font-size: var(--fs-body); font-weight: 700; } /* interfaz, no titular (brief §2); peso 700 revisión 5 punto 20 (antes 600) */
.footer-accordion__icon {
  position: relative;
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}
.footer-accordion__icon::before,
.footer-accordion__icon::after {
  content: "";
  position: absolute;
  background: var(--text-primary);
}
.footer-accordion__icon::before {
  top: 50%; left: 0; width: 100%; height: 2px; transform: translateY(-50%);
}
.footer-accordion__icon::after {
  left: 50%; top: 0; height: 100%; width: 2px; transform: translateX(-50%);
  transition: transform .15s ease;
}
.footer-accordion[open] .footer-accordion__icon::after { transform: translateX(-50%) scaleY(0); }

.footer-links {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-4) 0;
}
.footer-links a { color: var(--text-primary); text-decoration: none; font-weight: 700; } /* revisión 5, punto 20 */
.footer-links a:hover { text-decoration: underline; }

.site-footer__logo {
  display: flex;
  justify-content: center;
  margin-top: var(--sp-8);
}
/* Sello con rim blanco troquelado (brief §11) — misma técnica que el
   sticker del héroe (revisión 3, retirado de ahí): círculo concéntrico
   con el disco rojo del propio SVG (centro 48.06%/50%, radio 44.27% del
   ancho, ≈6% más grande), en --bg-elevated-2 (blanco real, esta sección
   no lleva .section--inverted). El fichero del logo no se toca. */
.footer-sello {
  position: relative;
  z-index: 0; /* Revisión 5, punto 20 — BUG real encontrado en vivo (razonado por orden
                 de pintado CSS, no por ojo): sin esto, `.footer-sello` (position:relative,
                 z-index:auto) no abre su PROPIO contexto de apilamiento, así que el
                 z-index:-1 de su ::before (el rim) se resuelve contra el contexto RAÍZ de
                 la página en vez de contra este componente. En el contexto raíz, las capas
                 de z-index negativo se pintan ANTES que los descendientes normales no
                 posicionados — y el propio <footer> (fondo rojo) es uno de esos
                 descendientes normales, así que su rojo se pintaba ENCIMA del rim blanco,
                 tapándolo por completo (el "disco rojo se funde con el fondo" que reportó
                 Pixun). Con z-index:0 aquí, `.footer-sello` abre su propio contexto: el
                 rim (-1 DENTRO de ese contexto) queda por debajo solo de sus propios
                 hermanos (el <img> del sello), y el conjunto entero se pinta como unidad
                 después del fondo del <footer>, como cualquier otro descendiente normal. */
  display: block;
  width: 217px;
  /* core imprime `.wp-block-image{margin:0 0 1em}` (WP core/image style):
     ese 1em (17px) se sumaba a la altura de `.site-footer__top` (la
     columna del sello marcaba la fila del grid) — 209px medidos en vez
     de los 192px del propio sello, y esos 17px de sobra eran exactamente
     los que separaban el pie de 382px de los 365px de referencia a 1440.
     Ver docs/port-phase-2.md, defecto 3. */
  margin-bottom: 0;
}
.footer-sello::before {
  content: "";
  position: absolute;
  left: 48.06%;
  top: 50%;
  width: 93.85%;
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  background: var(--bg-elevated-2);
  border-radius: 50%;
  z-index: -1;
}
.footer-sello img {
  display: block;
  width: 100%;
  height: auto;
}
.site-footer__groups {
  display: flex;
  flex-direction: column;
}

.site-footer__rule {
  border: none;
  border-top: 0px solid var(--bg); /* revisión 5, punto 12 (antes 1px) — literal, la línea desaparece */
  margin: var(--sp-8) 0 0;
}

.site-footer__bottom {
  padding-top: var(--sp-6);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  font-size: var(--fs-small);
  color: var(--text-primary);
}
.footer-legal {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
}
.footer-legal a { color: var(--text-primary); text-decoration: none; font-weight: 600; } /* revisión 5, puntos 13 y 20 */
.pending-tag {
  font-size: var(--fs-small);
  color: var(--text-primary); /* --text-tertiary no llega a 4.5:1 sobre el rojo del pie (brief §11) */
}
.footer-copy { font-weight: 600; } /* revisión 5, punto 20 */

@media (min-width: 960px) {
  .site-footer { min-height: 295px; display: flex; align-items: center; }
  .site-footer > .container { width: 100%; }
  .site-footer__top {
    display: grid;
    grid-template-columns: auto; /* revisión 5, punto 11 (antes auto 1fr) — .site-footer__groups
                                     sigue pidiendo grid-column:2 más abajo, así que el navegador
                                     crea esa segunda columna implícita (grid-auto-columns:auto):
                                     el resultado son DOS columnas de ancho auto (a su contenido)
                                     en vez de auto+1fr (la segunda ya no rellena el resto). */
    gap: var(--sp-16);
    align-items: start;
  }
  /* Orden visual invertido respecto al DOM (lectura: grupos → logo, igual
     que en móvil) — el logo va a la izquierda solo visualmente, vía
     colocación explícita de grid (no depende del orden de origen). */
  .site-footer__groups { grid-column: 2; grid-row: 1; }
  .site-footer__logo { grid-column: 1; grid-row: 1; }

  /* B2, ronda de validación 2026-08-30: sin pointer-events:none. El
     apaño anterior dejaba los tres <summary> focalizables por teclado
     pero inactivos al ratón (M10) Y, sobre todo, dependía de
     display:flex!important para forzar visible el contenido de un
     <details> SIN `open` — eso no funciona: el navegador oculta el
     slot interno desde la hoja del UA, no el <ul>. Cuatro enlaces
     desaparecían en escritorio sin JS. Ahora los tres <details> llevan
     `open` en el HTML siempre (por eso ya no hace falta ningún
     display:flex forzado aquí) y es JS quien los fija abiertos en este
     ancho y neutraliza el toggle nativo — ver js/main.js § "pie
     plegable". El <summary> sigue siendo un control real: focalizable
     y activable, solo que a este ancho no puede cerrar el grupo. */
  .footer-accordion summary { border-bottom: none; padding-block: 0; margin-bottom: var(--sp-4); cursor: default; }
  .footer-accordion__icon { display: none; }
  .footer-accordion .footer-links { padding: 0; }

  .site-footer__groups {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
  }
  .site-footer__logo { justify-content: flex-start; margin-top: 0; }
  .site-footer__bottom { flex-direction: row; justify-content: space-between; }
}

/* ==========================================================================
   12 · BARRA FIJA — solo móvil
   ========================================================================== */

.mobile-bar {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: 90;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-3);
  background: var(--bg);
  border-top: 1px solid var(--border);
  padding: var(--sp-3) var(--gutter);
  padding-bottom: calc(var(--sp-3) + env(safe-area-inset-bottom));
  box-shadow: var(--shadow-bar);
}
.mobile-bar__btn { width: 100%; }

/* Espacio reservado para que la barra fija no tape el pie al llegar al
   final del scroll (brief §3, sección 12). 79px = altura real medida de
   .mobile-bar (ver "FASE 2 — 12 · Barra fija" más abajo, mismo cálculo
   que --mox-chat-lift) — corregido de 73px, que asumía min-height:48px
   sin contar el borde de 3px del botón "sticker" ni su line-height real. */
@media (max-width: 959.98px) {
  .site-footer {
    /* Fix F2 round 2 (port-phase-2-fixes.md) — el HTML (styles.css 2172-2176)
       usa 73px aquí, no 79: este valor es el hueco que el pie reserva bajo
       su propio contenido para que la barra fija no lo tape, y NO tiene que
       coincidir con la altura real de `.mobile-bar` (79px, --mox-chat-lift,
       sin tocar) — son dos cálculos distintos que comparten cifra por
       casualidad en el HTML actual. 6px de los +25.2 del pie a 380/768. */
    padding-bottom: calc(73px + var(--section-pad-sm) + env(safe-area-inset-bottom));
  }
}

@media (min-width: 960px) {
  .mobile-bar { display: none; }
}

/* ==========================================================================
   FASE 4 — FORMULARIOS REALES (Secure Mail Forms 2.0.1, docs/port-phase-4.md §5)
   El marcado lo pinta el plugin (wp-content/plugins/secure-mail-forms/templates/
   *.php), con su propia hoja (css/frontend.css) escrita entera dentro de
   :where() — especificidad cero a propósito, para que cualquier regla normal
   del tema gane sin !important. Todo lo de aquí abajo sigue esa misma técnica:
   nunca !important, solo más especificidad que :where(0).

   Alcance: `.tariff .secure-form-container` (07, formId 3, Tarifa) y
   `.request .secure-form-container` (10, formId 4, Surtido) — nunca
   `.secure-form-container` a secas, que tocaría cualquier Secure Form futuro
   fuera de estas dos secciones.
   ========================================================================== */

/* ---- Contenedor / ritmo vertical — igual que .tariff__form / .request__form
   (más arriba en esta hoja), ahora aplicado al contenedor real del plugin. */
.tariff .secure-form-container,
.request .secure-form-container {
  margin-top: var(--sp-8);
}
@media (min-width: 960px) {
  .tariff .secure-form-container,
  .request .secure-form-container {
    margin-top: 0;
  }
}
.tariff .secure-form-container .smf-form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}
.request .secure-form-container .smf-form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

/* ---- Agrupación por pares (.grouped-fields) — el HTML no empareja ningún
   campo en ninguno de los dos formularios (docs/port-phase-4.md §5): se
   neutraliza el grid a 2 columnas del plugin con display:contents, así el
   grupo desaparece de la caja y sus .form-field pasan a ser hijos directos
   del flex de .smf-form, con el mismo gap que el resto de campos. Medido en
   vivo: 10/Surtido agrupa "Nombre del negocio" + "Persona de contacto" (los
   dos primeros campos de tipo text consecutivos); 07/Tarifa no agrupa nada,
   sus tres campos son de tipo distinto — la regla se deja en las dos
   secciones de todos modos, por si un campo futuro los emparejase. */
.tariff .secure-form-container .grouped-fields,
.request .secure-form-container .grouped-fields {
  display: contents;
}

/* ---- Campo = caja completa (etiqueta dentro, arriba a la izquierda) ------
   Mismo componente que `.field` (más arriba en esta hoja): en el marcado del
   plugin el borde/fondo los lleva `.form-control` (el input real); aquí
   pasan a `.form-field` (la caja) y el control queda transparente y sin
   borde propio, igual que en el HTML. La fila de aceptación (`.terms-field`)
   NO es un campo de datos y se excluye explícitamente de esta caja — su
   propio estilo de marca (checkbox cuadrado, check que se sale por la
   esquina) vive más abajo, sección "Checkbox de consentimiento" (phase 4
   completion, 2026-09-14; hasta esa fecha se dejó sin estilo por decisión
   pendiente de Pixun — ya resuelta, autorizada en persona). */
.tariff .secure-form-container .form-field:not(.terms-field),
.request .secure-form-container .form-field:not(.terms-field) {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  min-height: 56px;
  padding: 12px 30px 14px;
  border: 3px solid var(--border-strong);
  border-radius: var(--radius-pill);
  background: var(--bg-elevated-2);
  transition: box-shadow .15s ease;
}
/* 10 pide rectángulo, no píldora (brief §10, ya aplicado a `.request__form
   .field` más arriba) — mismo criterio para el campo real del plugin. */
.request .secure-form-container .form-field:not(.terms-field) {
  border-radius: 8px;
  padding-inline: 24px;
}
.tariff .secure-form-container .form-field:not(.terms-field) > label,
.request .secure-form-container .form-field:not(.terms-field) > label {
  font-size: 13px;
  font-weight: 600;
  line-height: 1.2;
  color: var(--text-tertiary);
}
.tariff .secure-form-container .form-control,
.request .secure-form-container .form-control {
  min-height: auto;
  padding: 0;
  border: none;
  border-radius: 0;
  background: transparent;
  color: var(--text-primary);
  font: inherit;
}
.tariff .secure-form-container textarea.form-control,
.request .secure-form-container textarea.form-control {
  min-height: 70px;
  resize: vertical;
}
/* Revisión 6, punto 5 del HTML: sin halo de :focus-within en los campos —
   decisión de Pixun, no un olvido. El botón y la casilla de consentimiento
   conservan el anillo :focus-visible del propio plugin (frontend.css, sin
   tocar). */
.tariff .secure-form-container .form-control:focus-visible,
.request .secure-form-container .form-control:focus-visible {
  outline: none;
}

/* ---- Asterisco "required" — el HTML no marca ningún campo; ya lleva
   aria-hidden en el plugin, aquí solo se oculta visualmente. -------------- */
.tariff .secure-form-container .required,
.request .secure-form-container .required {
  display: none;
}

/* ---- Estado de error — no solo color: el borde de la caja engorda
   (3px→5px, como `.field.has-error` en el HTML) más el texto y un icono "!"
   tipográfico (el plugin no imprime el <span> del HTML; se repone con
   ::before sobre el propio mensaje, un elemento real, vacío hasta que
   frontend.js lo rellena — `:has()`, soportado por el Edge/Chromium usado
   para validar este build). ------------------------------------------------ */
.tariff .secure-form-container .form-field:has(.form-control[aria-invalid="true"]),
.request .secure-form-container .form-field:has(.form-control[aria-invalid="true"]) {
  border-width: 5px;
}
.tariff .secure-form-container .smf-field-error,
.request .secure-form-container .smf-field-error {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  margin: 0;
  font-size: var(--fs-small);
  color: var(--text-primary);
}
.tariff .secure-form-container .smf-field-error:not(:empty)::before,
.request .secure-form-container .smf-field-error:not(:empty)::before {
  content: "!";
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  margin-top: 2px;
  border: 1px solid var(--text-primary);
  border-radius: var(--radius-pill);
  font-weight: 700;
  line-height: 1;
}

/* ---- Mensajes de éxito/error (.smf-message) — como `.form-status`. ------- */
.tariff .secure-form-container .smf-message,
.request .secure-form-container .smf-message {
  font-size: var(--fs-small);
  font-weight: 600;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-elevated);
  color: var(--text-primary);
}
.tariff .secure-form-container .smf-message-success,
.request .secure-form-container .smf-message-success {
  background: var(--bg-elevated-2);
}

/* ---- Aviso de privacidad del formulario (07 solo; 10 lo lleva vacío) —
   como `.form-legal`. -------------------------------------------------------- */
/* Fase 5 (aviso del sesión de cuenta, 2026-09-14) — --text-tertiary mide
   3.5:1 sobre el panel amarillo de 07 (rgb(254,188,21)), bajo el 4.5:1 que
   pide este texto. Mismo criterio que .checkbox-text de más abajo: solo
   cambia el color, en 07 únicamente — 10 lo lleva vacío (no forma parte de
   esta corrección) y no está tocado. */
.tariff .secure-form-container .smf-privacy-notice {
  margin-block-start: var(--sp-3);
  font-size: var(--fs-small);
  color: var(--text-primary);
  opacity: 1;
}
.tariff .secure-form-container .smf-privacy-notice a {
  color: var(--text-primary);
  text-decoration: underline;
}

/* Fase 4 fix round 2 (account session, 2026-09-14) — restores the plugin's
   OWN default (:where(.checkbox-text a){text-decoration:underline}, zero
   specificity), which the theme's global `a{text-decoration:none}`
   (components.css:249, specificity 0,0,1) was beating; empty today because
   get_privacy_policy_url() is empty while page 3 is a draft, but the link
   would have rendered indistinguishable from its surrounding text the day
   page 3 publishes (WCAG 1.4.1). Not brand styling: no colour, no size, no
   border added to the checkbox anywhere in this file, by design.
   Consent text contrast (measured live, in-app browser): 9.64:1 on 07's
   yellow panel, 14.22:1 on 10's cream ground — both far over 4.5:1. */
.tariff .secure-form-container .checkbox-text a,
.request .secure-form-container .checkbox-text a {
  text-decoration: underline;
}

/* ---- Checkbox de consentimiento — estilo de marca (phase 4 completion,
   2026-09-14, autorizado en persona por Pixun). Transcrito de la guía de
   estilo (M:\MAIN PROJECTS\todobazar\Buildig material\assets\style-guide.png,
   sección "UI Elements") por la sesión de cuenta — los agentes no ven
   imágenes: cuadrado de esquina pequeña, mismo filete grueso ink que los
   campos (3px, --border-strong), fondo del campo (--bg-elevated-2), y al
   marcarlo un check ink grueso cuyo trazo se sale por la esquina superior
   derecha del cuadrado. Sin corona.
   Sigue siendo un checkbox real (teclado, lector de pantalla, required):
   appearance:none solo quita el dibujo nativo del navegador, nunca cambia
   el elemento ni lo oculta detrás de otra cosa. */
.tariff .secure-form-container .terms-field .checkbox-label,
.request .secure-form-container .terms-field .checkbox-label {
  position: relative; /* ancla del check que se sale por la esquina */
}
.tariff .secure-form-container .terms-field input[type="checkbox"],
.request .secure-form-container .terms-field input[type="checkbox"] {
  -webkit-appearance: none;
  appearance: none;
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  margin: 0;
  border: 3px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--bg-elevated-2);
  cursor: pointer;
}
/* El check se pinta sobre `.checkbox-label` (los navegadores no admiten
   ::before/::after en un elemento reemplazado como <input>), asumiendo que
   el checkbox es el primer hijo flex, alineado arriba-izquierda sin margen
   (align-items:flex-start, plugin frontend.css, sin tocar) — su esquina
   superior derecha cae en (26px, 0) desde `.checkbox-label`. El icono es
   más grande que la caja y se desplaza para que su propia esquina superior
   derecha quede fuera de la del cuadrado, como en la guía de estilo. */
.tariff .secure-form-container .terms-field .checkbox-label:has(input:checked)::after,
.request .secure-form-container .terms-field .checkbox-label:has(input:checked)::after {
  content: "";
  position: absolute;
  top: -6px;
  left: 10px;
  width: 22px;
  height: 22px;
  background-color: var(--border-strong);
  -webkit-mask: url('../images/icons/icon-check.svg') no-repeat center / contain;
  mask: url('../images/icons/icon-check.svg') no-repeat center / contain;
  pointer-events: none;
  /* Sin transición — brief §3 "reduced motion: no transition on the check";
     más simple no ponerla nunca que ponerla y tener que anularla después. */
}
/* :focus-visible propio (brief §3): los campos de texto no llevan anillo
   por decisión de Pixun (revisión 6, punto 5, ver más arriba), pero el
   checkbox sí, igual que los botones. */
.tariff .secure-form-container .terms-field input[type="checkbox"]:focus-visible,
.request .secure-form-container .terms-field input[type="checkbox"]:focus-visible {
  outline: 2px solid var(--border-strong);
  outline-offset: 2px;
}
/* Error — no solo color: el filete engorda (3px→5px, mismo criterio que
   `.form-field.has-error` más arriba); el texto de error real del plugin
   (`.smf-field-error`, ya con su "!" tipográfico, regla de arriba) hace el
   resto. */
.tariff .secure-form-container .terms-field input[type="checkbox"][aria-invalid="true"],
.request .secure-form-container .terms-field input[type="checkbox"][aria-invalid="true"] {
  border-width: 5px;
}
/* `.checkbox-text` al tamaño/estilo de las etiquetas de los campos del HTML
   (mismo valor que `.form-field:not(.terms-field) > label` más arriba). */
.tariff .secure-form-container .checkbox-text,
.request .secure-form-container .checkbox-text {
  font-size: 13px;
  font-weight: 600;
  line-height: 1.2;
  color: var(--text-tertiary);
}
/* Fase 4 completion fix (account session, 2026-09-14) — --text-tertiary
   (rgb(105,99,93)) is 5.93:1 on the HTML's white field, but this text sits
   directly on 07's yellow panel (rgb(254,188,21)), not on a white field
   box: measured 3.5:1 there, under the 4.5:1 this text needs. 10's cream
   ground measures 5.17:1 with the same colour — fine, left as is. Same
   size/weight, only the colour changes, and only in 07; --text-primary is
   9.64:1 on that yellow (already measured for the checkbox border, same
   value). Not touching `.smf-privacy-notice` — same yellow, same 3.5:1,
   but it deliberately copies the HTML's own `.form-legal` and is the
   account session's call, not this fix's. */
.tariff .secure-form-container .checkbox-text,
.tariff .secure-form-container .checkbox-text a {
  color: var(--text-primary);
}

/* ---- Botón — la flecha del plugin se oculta en los dos formularios: ni
   `.tariff__submit` ni `.request__submit-sticker` la llevan en el HTML. --- */
.tariff .secure-form-container .button-text svg,
.request .secure-form-container .button-text svg {
  display: none;
}

/* 07 — pegatina roja, igual que .btn + .btn--primary + .tariff__submit
   (filete --bg-elevated-2, no --bg-pure: única diferencia del botón base). */
.tariff .secure-form-container .submit-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  width: 100%;
  min-height: 48px;
  padding: var(--sp-3) var(--sp-6);
  border-radius: var(--radius-pill);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  font-weight: 700;
  line-height: var(--lh-normal);
  text-align: center;
  background: var(--accent);
  /* Port fix C6.1 — mismo cambio que .wp-block-button.is-style-sticker-red
     de arriba en esta misma hoja: texto blanco sobre fondo rojo, preset
     directo por la misma razon. */
  color: var(--wp--preset--color--bg-elevated-2);
  border: 3px solid var(--bg-elevated-2);
  box-shadow: 6px 6px 0 var(--bg-pure);
  transition: transform .15s ease, box-shadow .15s ease;
  cursor: pointer;
}
.tariff .secure-form-container .submit-button:hover {
  transform: translate(3px, 3px);
  box-shadow: 3px 3px 0 var(--bg-pure);
}
.tariff .secure-form-container .submit-button:active {
  transform: translate(6px, 6px);
  box-shadow: 0 0 0 var(--bg-pure);
}

/* 10 — barra invisible + sticker rotado, igual que .request__submit +
   .request__submit-sticker: el sticker es `.button-text` (único hijo
   visible, ya que la flecha SVG queda oculta arriba). */
.request .secure-form-container .submit-button {
  position: relative;
  display: block;
  width: 100%;
  min-height: 45px;
  margin-top: var(--sp-6);
  padding: 0;
  background: none;
  border: 0px solid var(--bg-pure);
  border-radius: 8px;
  cursor: pointer;
}
.request .secure-form-container .button-text {
  position: absolute;
  top: 50%;
  left: 50%;
  --press-x: 0px;
  --press-y: 0px;
  transform: translate(calc(-50% + var(--press-x)), calc(-50% + var(--press-y))) rotate(-3deg);
  transition: transform .15s ease, box-shadow .15s ease;
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  font-family: var(--font-display);
  font-weight: 800;
  font-synthesis: none;
  font-size: clamp(1.5rem, 1.4rem + 0.5vw, 1.75rem);
  /* Port fix C6.1 — encontrado por el inventario en vivo, no estaba en la
     lista de "esperados" del brief: este sticker leia var(--bg) (crema),
     no ink, pero sigue siendo texto sobre fondo rojo y la regla de Pixun
     es sobre TODOS los botones rojos. Mismo preset y mismo motivo que el
     resto de esta ronda. */
  color: var(--wp--preset--color--bg-elevated-2);
  background: var(--accent);
  border: 3px solid var(--bg-pure);
  border-radius: 12px;
  box-shadow: 0 0 0 5px var(--bg), 6px 6px 0 var(--bg-pure);
  padding: var(--sp-2) var(--sp-6);
}
.request .secure-form-container .submit-button:hover .button-text {
  --press-x: 3px;
  --press-y: 3px;
  box-shadow: 0 0 0 5px var(--bg), 3px 3px 0 var(--bg-pure);
}
.request .secure-form-container .submit-button:active .button-text {
  --press-x: 6px;
  --press-y: 6px;
  box-shadow: 0 0 0 5px var(--bg), 0 0 0 var(--bg-pure);
}
.request .secure-form-container .button-text::after {
  content: "";
  position: absolute;
  bottom: 2px;
  right: 8px;
  width: 11px;
  height: 11px;
  background: var(--bg-elevated-2);
  clip-path: polygon(100% 100%, 0 100%, 100% 0%);
  filter: var(--shadow-sticker-peel);
  pointer-events: none;
}

/* ==========================================================================
   MOTION — revelado en scroll (revisión 5, punto 15)
   Ver docs/revision-5-rectificaciones.md > Motion para la derivación de
   --reveal-duration/--reveal-ease (declaradas en :root) y para la tabla de
   qué sección lleva qué atributo. Sin `.js` en <html> (JS bloqueado, o
   nunca llegó a correr) [data-reveal] no se oculta NUNCA — es la propia
   presencia de la clase la que activa la ocultación, no un
   `html:not(.js)` aparte.
   ========================================================================== */
/* Fase 2 (docs/port-phase-2.md, "block validation trap") — [data-reveal]
   pasa a .reveal: los bloques núcleo rechazan atributos data-* desconocidos
   en su marcado guardado. assets/js/reveal.js usa la misma clase. */
.js .reveal {
  opacity: 0;
  transform: translateY(var(--reveal-offset));
  transition: opacity var(--reveal-duration) var(--reveal-ease),
              transform var(--reveal-duration) var(--reveal-ease);
  /* Red de seguridad de ~2.5s: si `main.js`/reveal.js nunca llega a añadir
     .is-visible (404, error de parseo, CSP) el script inline de <head>
     ya puso .js, así que sin esto el elemento se quedaría oculto para
     siempre. `animation-delay` no lo toca el bloque REDUCED MOTION de más
     abajo (que solo pone animation-duration a .001ms) — por eso esta
     regla vive FUERA de ese bloque y una entrada aparte, más abajo,
     anula esta animación explícitamente bajo esa preferencia. */
  animation: reveal-fallback .01s linear 2.5s forwards;
}
.js .reveal.is-visible {
  opacity: 1;
  transform: none;
}
@keyframes reveal-fallback {
  to { opacity: 1; transform: none; }
}

/* ==========================================================================
   FASE 5 — WOOCOMMERCE (docs/port-phase-5.md §3)
   Plantillas construidas a partir de las propias de WooCommerce en bloques
   (wp-content/plugins/woocommerce/templates/templates/blockified/*.html),
   leídas antes de escribir esto, envueltas en el .container del sitio en
   vez de alignwide/alignfull — mismo criterio que F7 (sin
   "layout":"constrained" en ningún wrapper del tema). Las cabeceras de
   sección heredan Cooper Heavy vía styles.elements.heading de theme.json;
   aquí solo lo que ese fichero no alcanza.
   ========================================================================== */

/* ---- Páginas de tienda — separación del contenido dentro de .container --- */
.shop-page > .container,
.single-product-page > .container,
.cart-page > .container,
.checkout-page > .container,
.order-confirmation-page > .container {
  padding-block: var(--sp-10) var(--sp-16);
}
.shop-page__toolbar {
  margin-block: var(--sp-4) var(--sp-8);
  padding-block-end: var(--sp-4);
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-small);
  color: var(--text-secondary);
  flex-wrap: wrap;
  row-gap: var(--sp-2);
}

/* ---- "Ordenar por" (woocommerce/catalog-sorting) — F5.3
   (docs/port-phase-5-fixes.md). Mismo componente de campo que la 10
   (`.request .secure-form-container .form-field:not(.terms-field)` +
   `.form-control`, leídas antes de escribir esto): mismo borde grueso,
   mismo fondo, mismo tamaño de texto y el mismo padding en línea. Una
   diferencia real frente al paráfrasis del brief: la 10 pide RECTÁNGULO
   (8px), no píldora — así lo deja su propia hoja ("10 pide rectángulo, no
   píldora", línea ~2650); la píldora es el valor por DEFECTO de `.field`
   (07), no el de la 10. Se sigue la regla — el código gana sobre la
   paráfrasis. Sigue siendo un <select> nativo: sin JS de reemplazo, teclado
   y lector de pantalla intactos; la flecha es un ::after decorativo
   (pointer-events:none) sobre el propio <form> del plugin
   (`.woocommerce-ordering`), nunca la del user-agent. Foco: igual que los
   campos de la 10 — Pixun: los campos no llevan estilo de foco propio. */
.woocommerce-ordering {
  position: relative;
  display: inline-flex;
  max-width: 100%;
}
select.orderby {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  display: block;
  box-sizing: border-box;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  min-height: 56px;
  padding: 12px 44px 14px 24px;
  border: 3px solid var(--border-strong);
  border-radius: 8px;
  background-color: var(--bg-elevated-2);
  color: var(--text-primary);
  font: inherit;
  font-size: var(--fs-body);
  cursor: pointer;
}
select.orderby::-ms-expand {
  display: none;
}
.woocommerce-ordering::after {
  content: "";
  position: absolute;
  top: 50%;
  right: 20px;
  width: 16px;
  height: 16px;
  transform: translateY(-50%);
  background-color: var(--text-primary);
  -webkit-mask: url('../images/icons/icon-chevron-down.svg') no-repeat center / contain;
  mask: url('../images/icons/icon-chevron-down.svg') no-repeat center / contain;
  pointer-events: none;
}
/* Revisión 6, punto 5 (F5.3 aplica el mismo criterio) — sin halo propio de
   foco en el campo: el select conserva el foco por defecto del navegador
   (visible, sin retirarlo vía outline aquí). */

/* ---- Migas de pan — mismo tono que .section-lead. -------------------- */
.wp-block-woocommerce-breadcrumbs,
.woocommerce-breadcrumb {
  font-size: var(--fs-small);
  color: var(--text-tertiary);
  margin-block-end: var(--sp-6);
}
.wp-block-woocommerce-breadcrumbs a,
.woocommerce-breadcrumb a {
  color: var(--text-tertiary);
  text-decoration: underline;
}

/* ---- Precio "[PENDIENTE]" (inc/woocommerce.php) — reutiliza .pending-tag
   ya definido para el pie de página; aquí solo el hueco entre título y
   precio en la rejilla de producto. --------------------------------------- */
.wp-block-woocommerce-product-price {
  margin-block: var(--sp-2) var(--sp-4);
}

/* ---- Botones de WooCommerce (Add to Cart / checkout / mini-carrito) al
   mismo tratamiento que el resto del sitio — misma base que `.btn` /
   `.wp-block-button__link` (components.css ~333), sticker rojo.
   Corregido en port fix C6.1: el comentario decia que estos botones "no se
   pintan hoy" porque los productos placeholder no son comprables. Medido
   en vivo (headless, /tienda/, una categoria) — es FALSO para
   `.wc-block-components-product-button__button`: WooCommerce sustituye
   "Add to cart" por un enlace "Read more" con esta misma clase de boton de
   bloque cuando el producto no es comprable, y SI se pinta — 8 veces en
   /tienda/, una vez en la categoria de prueba, fondo rojo confirmado
   (rgb(251,62,22)). Las otras tres clases del selector (carrito/checkout/
   mini-carrito) siguen sin comprobar en vivo con productos reales en el
   carrito; se tratan igual por prudencia. -------------------------------- */
.wc-block-components-product-button__button,
.wc-block-cart__submit-button,
.wc-block-components-checkout-place-order-button,
.wc-block-mini-cart__footer-cart .wp-element-button,
.wc-block-mini-cart__footer-checkout .wp-element-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 48px;
  padding: var(--sp-3) var(--sp-6);
  border-radius: var(--radius-pill);
  font-family: var(--font-body);
  font-weight: 700;
  background: var(--accent);
  /* Port fix C6.1 — mismo cambio, mismo motivo. A 13.02px/700 (el "Read
     more" medido en vivo) NO llega al umbral de texto grande (18.66px en
     negrita): decision de Pixun, sin compensar tamano/peso — solo se mide
     y se reporta el contraste. */
  color: var(--wp--preset--color--bg-elevated-2);
  border: 4px solid var(--bg-pure);
  box-shadow: 8px 8px 0 var(--bg-pure);
}

/* ---- Carrito/checkout vacíos — WooCommerce añade por defecto un bloque
   "New in store" (woocommerce/product-new) al contenido propio del carrito
   vacío, con enlaces que llevan la clase histórica `add_to_cart_button`
   aunque el texto sea "Read more" (los placeholder no son comprables). El
   brief pide "no add-to-cart button anywhere" en estas páginas — y un
   escaparate de ocho productos [PENDIENTE] tampoco tiene sentido mientras
   no hay catálogo real. Se oculta solo ese bloque (contenido propio de
   WooCommerce, sin tocar la página en base de datos) y su encabezado/regla
   huérfanos; el resto del contenido "Cart is currently empty" queda. */
/* Ronda de fix 3 (validator) — el atributo `data-block-name` SÍ llega al
   front end (comprobado en vivo por la sesión, headless Edge en /carrito/),
   pero el ocultamiento no debe depender de un solo selector: se añade la
   clase real del wrapper (`.wp-block-woocommerce-product-new`, la que
   WooCommerce imprime en el propio `<div>` del bloque) junto al atributo,
   sin quitar ninguno de los dos. */
[data-block-name="woocommerce/product-new"],
.wp-block-woocommerce-product-new {
  display: none;
}
.wp-block-woocommerce-empty-cart-block h2:has(+ [data-block-name="woocommerce/product-new"]),
.wp-block-woocommerce-empty-cart-block h2:has(+ .wp-block-woocommerce-product-new),
.wp-block-woocommerce-empty-cart-block hr:has(+ h2 + [data-block-name="woocommerce/product-new"]),
.wp-block-woocommerce-empty-cart-block hr:has(+ h2 + .wp-block-woocommerce-product-new) {
  display: none;
}

/* ---- Mini-carrito en la cabecera (plan §8, brief §3) -------------------
   El bloque `woocommerce/mini-cart` es un CUARTO hijo flex de .header-inner
   (logo / nav / CTA ya eran tres — components.css ~675). El ancla del
   hamburguesa de F4 (port-phase-2-fixes.md) depende de que, por debajo de
   1280px, `<nav>` sea el ÚLTIMO hijo flex VISIBLE de .header-inner (con el
   CTA oculto, `right:0` relativo a <nav> cae justo en el borde de
   contenido real). Un cuarto hermano visible ahí rompería ese cálculo.
   Por eso, por debajo de 1280px el mini-carrito SALE del flujo flex
   (position:absolute, mismo ancestro posicionado que ya usa el
   hamburguesa: .header-inner) y se ancla aparte del hueco de 44px del
   hamburguesa — F4 queda intacto, sin tocar una sola regla suya.
   Desde 1280px el hamburguesa no existe (el menú se muestra en línea,
   port-phase-1.md §6), así que ahí no hay nada que proteger: el
   mini-carrito vuelve al flujo normal, después del CTA — el
   `margin-left:auto` del CTA (línea ~891) ya arrastra consigo a cualquier
   hermano posterior hasta el borde derecho real, sin CSS adicional. */
.header-mini-cart {
  position: absolute;
  /* Medido en vivo (in-app, 380px): el hamburguesa ancla a right:0 de <nav>,
     cuya caja ya está dentro del padding-inline de .header-inner (var(--gutter),
     16px a 380px) — el bloque contenedor de un position:absolute es la caja de
     PADDING de .header-inner, así que su propio right:0 caería en el borde
     exterior, gutter píxeles más afuera que <nav>. Sin sumar --gutter aquí el
     mini-carrito quedaba 4px DENTRO del hamburguesa (overlap), no con hueco. */
  right: calc(44px + var(--sp-3) + var(--gutter));
  top: 50%;
  transform: translateY(-50%);
  z-index: 95; /* mismo nivel que .header-inner > .logo / .nav-toggle */
}
.header-mini-cart .wc-block-mini-cart__button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--text-primary);
  cursor: pointer;
}
/* Icono propio, trazo grueso, mismo mecanismo que el hamburguesa de F4 y el
   check del checkbox de consentimiento (fase 4): se oculta el SVG que trae
   el bloque (trazo fino) y se dibuja el de la cesta ya usado en 04
   (icon-capacity-basket.svg, trazo 4.5, currentColor) con una máscara CSS. */
.header-mini-cart .wc-block-mini-cart__button svg {
  display: none;
}
.header-mini-cart .wc-block-mini-cart__quantity-badge {
  position: relative;
  display: inline-flex;
}
.header-mini-cart .wc-block-mini-cart__quantity-badge::before {
  content: "";
  display: block;
  /* Ronda de fix 3 (validator) — 26px se veía más ligero que el hamburguesa
     de al lado (32px, F4). Mismo tamaño exacto que
     .site-header .wp-block-navigation__responsive-container-open.always-shown::before
     (components.css ~1025), dentro del mismo botón de 44×44. */
  width: 32px;
  height: 32px;
  background-color: currentColor;
  -webkit-mask: url('../images/icons/icon-capacity-basket.svg') no-repeat center / contain;
  mask: url('../images/icons/icon-capacity-basket.svg') no-repeat center / contain;
}
.header-mini-cart .wc-block-mini-cart__badge {
  position: absolute;
  top: -6px;
  right: -8px;
  min-width: 16px;
  height: 16px;
  padding: 0 3px;
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: var(--bg-pure);
  font-size: 10px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
}
@media (min-width: 1280px) {
  .header-mini-cart {
    position: static;
    top: auto;
    right: auto;
    transform: none;
  }
}

/* ---- Cajón del mini-carrito (parts/mini-cart.html) — paleta y tipos de
   marca; WooCommerce pinta el resto (posición fija, overlay, foco). ------ */
.wc-block-mini-cart__drawer {
  --wc-mini-cart-font: var(--font-body);
}
.wc-block-mini-cart__drawer .wc-block-mini-cart__title,
.wc-block-mini-cart__drawer h2,
.wc-block-mini-cart__drawer h3 {
  font-family: var(--font-display);
}
.wc-block-mini-cart__drawer,
.wc-block-mini-cart__drawer p,
.wc-block-mini-cart__drawer span {
  font-family: var(--font-body);
  color: var(--text-primary);
}
.wc-block-mini-cart__drawer .wc-block-mini-cart__shopping-button .wp-element-button,
.wc-block-mini-cart__footer-cart .wp-element-button,
.wc-block-mini-cart__footer-checkout .wp-element-button {
  border-radius: var(--radius-pill);
}


/* ==========================================================================
   REDUCED MOTION
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  /* Revisión 5, punto 15 (motion) — tiene que vivir EN este bloque
     existente (más abajo en la cascada que la regla base de más arriba)
     para ganar a igual especificidad, y anula tanto la transición base
     como el fallback de 2.5s: nada se oculta nunca, nada se mueve nunca,
     haya corrido JS o no. */
  .js .reveal {
    opacity: 1;
    transform: none;
    transition: none;
    animation: none;
  }

  /* Revisión 7, punto 3 — bloque de cancelación de la presión de botón,
     trasladado aquí desde justo debajo de `.btn--primary` (ver el comentario
     que queda en su sitio original). Tiene que ser la ÚLTIMA regla de la
     hoja para las clases que toca: a igual especificidad, CSS resuelve por
     orden de aparición en el documento, sin importar si la regla que hay que
     anular vive dentro de otro `@media` — con el bloque más arriba en el
     fichero, `.quicklane__cta:hover` (dentro de `@media (min-width: 960px)`,
     declarada después), `.header-cta:hover`, `.tariff__submit:hover` (que ni
     siquiera estaba cubierto) y `.request__submit:hover
     .request__submit-sticker` seguían ganando y el botón se seguía moviendo
     bajo `prefers-reduced-motion: reduce`. Cada selector recibe `transform:
     none` y su sombra vuelta al valor EXACTO de reposo (no solo una
     transición más rápida al valor de :hover, que es lo que ya hace el
     `transition-duration: .001ms` de arriba) — "nada se mueve" cubre tanto
     el desplazamiento como el propio cambio de forma de la sombra. */
  .btn--primary:hover,
  .btn--primary:active,
  .wp-block-button.is-style-sticker-red .wp-block-button__link:hover,
  .wp-block-button.is-style-sticker-red .wp-block-button__link:active,
  .btn--outline:hover,
  .btn--outline:active,
  .wp-block-button.is-style-sticker-yellow .wp-block-button__link:hover,
  .wp-block-button.is-style-sticker-yellow .wp-block-button__link:active,
  .tariff__submit:hover,
  .tariff__submit:active {
    transform: none;
    box-shadow: 6px 6px 0 var(--bg-pure);
  }
  /* .header-cta es más grande (filete 4px, min-height 55px) y su sombra de
     reposo real es 8px, no la de 6px que comparten los botones estándar
     (ver su regla base, línea ~708). Agrupada con esas antes, la sombra
     seguía encogiendo de 8px a 6px bajo esta preferencia — el mismo
     artefacto de movimiento residual que este bloque existe para eliminar. */
  .header-cta:hover,
  .header-cta:active,
  .wp-block-button.header-cta .wp-block-button__link:hover,
  .wp-block-button.header-cta .wp-block-button__link:active {
    transform: none;
    box-shadow: 8px 8px 0 var(--bg-pure);
  }
  .btn--pill:hover,
  .btn--pill:active,
  .wp-block-button.is-style-pill-ink .wp-block-button__link:hover,
  .wp-block-button.is-style-pill-ink .wp-block-button__link:active {
    transform: none;
    box-shadow: 0 0 0 3px var(--bg-pure), 6px 6px 0 var(--bg-pure);
  }
  /* .btn--pill dentro de una sección invertida recolorea el segundo valor de
     la sombra a crema (ver su regla base, más arriba) — el reposo que hay
     que restaurar aquí es ESE, no el de fondo claro. */
  .section--inverted .btn--pill:hover,
  .section--inverted .btn--pill:active,
  .section--inverted .wp-block-button.is-style-pill-ink .wp-block-button__link:hover,
  .section--inverted .wp-block-button.is-style-pill-ink .wp-block-button__link:active,
  .is-style-inverted .wp-block-button.is-style-pill-ink .wp-block-button__link:hover,
  .is-style-inverted .wp-block-button.is-style-pill-ink .wp-block-button__link:active {
    box-shadow: 0 0 0 3px var(--bg-pure), 6px 6px 0 var(--bg);
  }
  .wp-block-button.retail__cta .wp-block-button__link:hover,
  .wp-block-button.retail__cta .wp-block-button__link:active {
    transform: none;
  }
  /* .retail__cta solo existe dentro de la 09 (`.section--inverted`) — su
     sombra de reposo real es la variante crema, no la ink de
     .is-style-sticker-yellow. */
  .section--inverted .wp-block-button.retail__cta .wp-block-button__link:hover,
  .section--inverted .wp-block-button.retail__cta .wp-block-button__link:active {
    box-shadow: 6px 6px 0 var(--bg);
  }
  @media (min-width: 960px) {
    .quicklane__cta:hover,
    .quicklane__cta:active {
      transform: none;
      box-shadow: 0 0 0 3px var(--bg-pure), 6px 6px 0 var(--bg);
    }
  }
  /* .request__submit-sticker no usa transform:none a secas — perdería la
     rotación de reposo (-3deg), que no es "movimiento", es su orientación
     fija. Se anulan solo las variables de presión y la sombra vuelve a su
     par de reposo (anillo crema + offset ink sin reducir). */
  .request__submit:hover .request__submit-sticker,
  .request__submit:active .request__submit-sticker {
    --press-x: 0px;
    --press-y: 0px;
    box-shadow: 0 0 0 5px var(--bg), 6px 6px 0 var(--bg-pure);
  }
  /* Fase 4 (docs/port-phase-4.md §5) — mismo defecto que .tariff__submit /
     .request__submit-sticker de arriba: sus equivalentes reales del plugin
     (.submit-button/.button-text, sección FASE 4 más arriba en esta hoja)
     necesitan su propia cancelación aquí, por la misma razón de cascada
     (revisión 7, punto 3: a igual especificidad gana quien viene después en
     el documento, y esas reglas base viven más arriba que este bloque). */
  .tariff .secure-form-container .submit-button:hover,
  .tariff .secure-form-container .submit-button:active {
    transform: none;
    box-shadow: 6px 6px 0 var(--bg-pure);
  }
  .request .secure-form-container .submit-button:hover .button-text,
  .request .secure-form-container .submit-button:active .button-text {
    --press-x: 0px;
    --press-y: 0px;
    box-shadow: 0 0 0 5px var(--bg), 6px 6px 0 var(--bg-pure);
  }
}

/* Ancho de referencia del wireframe: sin cambios estructurales nuevos a
   1440, solo el clamp() llegando a su techo (brief §2). Más allá de 1440
   (ultrawide), --container-max no crece — el contenido se queda centrado a
   1280px. Riesgo documentado en el informe de entrega: el sangrado del
   héroe no tiene techo definido más allá de 1440px.

   ==========================================================================
   FASE 2 — adaptaciones a bloques núcleo (docs/port-phase-2.md)
   Las reglas de arriba son del prototipo estático; esta sección las
   extiende para el marcado real de core/group, core/columns, core/list,
   core/details y core/button que construyen 01, 04, 06, 07, 08, 09, 10, 11
   y 12 en patterns/*.php y parts/*.html. Nada se duplica: se AÑADEN
   selectores a las reglas ya existentes donde el tratamiento es el mismo
   (misma técnica que ya usa el fichero para los estilos de botón).
   ========================================================================== */

/* ---- 01 · Carril rápido — core/group + core/buttons ------------------- */
/* El botón real vive en `.wp-block-button.quicklane__cta-btn` (el div que
   envuelve el enlace) — su look "pegatina" ya lo da el estilo de bloque
   "Píldora tinta" (is-style-pill-ink, ver más arriba, mismo tratamiento que
   .btn--pill). Nombrado `-btn`, NUNCA `.quicklane__cta` a secas: esa clase
   ya la usa el prototipo estático (`.quicklane__cta`, más arriba en esta
   hoja) para el `<span>` ABSOLUTAMENTE POSICIONADO dentro del `<a>` único
   del HTML clásico — BUG real, encontrado en vivo (headless Edge, 1440px):
   reusar ese nombre en el botón de bloque heredaba `position:absolute` de
   esa regla vieja, un elemento absoluto no aporta nada al cálculo de ancho
   "shrink-to-fit" de su contenedor (`.quicklane__cta-wrap`, flex,
   igualmente absoluto), así que ese contenedor computaba 0px de ancho y el
   texto "Ir al canal" se partía letra a letra en una columna vertical de
   ~1px. Aquí solo el LAYOUT que en el HTML estático llevaba el `<a>`
   completo: icono/etiqueta ocultos en móvil (no hay icono en la versión de
   bloque, solo la etiqueta) y colocación absoluta en escritorio, sobre el
   WRAP (`.wp-block-buttons`), nunca sobre el botón mismo. */
/* Ronda de fix 1 (validator B3), causa real medida en vivo: el prototipo
   estático fijaba `font-size: var(--fs-small)` en `.quicklane__link` (el
   `<a>` que envolvía texto + cta); ese envoltorio no existe en el marcado de
   bloque (`core/paragraph` + `core/button` como hermanos directos de
   `.quicklane__inner`), así que `.quicklane__text` nunca heredaba ese
   tamaño y caía al cuerpo por defecto (16px/24px de interlineado) — medido
   en vivo: 3 líneas en vez de 2 a 380px, banda 120px vs 92px de referencia.
   Aquí se fija el tamaño donde el elemento vive de verdad. */
.quicklane__text { font-size: var(--fs-small); }
.quicklane__cta-wrap { flex-shrink: 0; }
/* Fix F6 (port-phase-2-fixes.md, cuenta #6) — bajo 960px el HTML no muestra
   la etiqueta "Ir al canal", solo un target de 44x44 con el chevron de 24px
   (index.html 105-120, styles.css 518-547). Decidido por la sesión de
   cuenta: la etiqueta SIGUE en el bloque (editable, nombre accesible), solo
   se oculta visualmente — `font-size:0` (nunca display:none/visibility:
   hidden, que sí la sacarían del árbol de accesibilidad) más overflow:
   hidden; el chevron se dibuja con un CSS mask del SVG del tema, coloreado
   por el token (no currentColor: el link queda con font-size:0 pero el
   color sigue siendo el real, así que currentColor habría servido igual —
   se fija al token de todos modos para que un ::after sin texto no dependa
   de la resolución de color de su ancestro). Desde 960px no se toca nada:
   la píldora completa (is-style-pill-ink, con la etiqueta) sigue como en la
   fase 2. */
@media (max-width: 959px) {
  /* Ronda de fix 2 (port-phase-2-fixes.md, F6) — el `box-shadow:none` de
     abajo NO ganaba: `.section--inverted .wp-block-button.is-style-pill-ink
     .wp-block-button__link` (4 clases) tiene más especificidad que
     `.wp-block-button.quicklane__cta-btn .wp-block-button__link` (3), y la
     01 SIEMPRE es `.section--inverted` — se veía el anillo ink + la sombra
     crema de la píldora completa detrás del chevron. Con `.section--
     inverted` añadido aquí (4 clases, igual especificidad, regla posterior
     en la hoja) gana de verdad. Estados de pulsación también anulados:
     bajo 960 no hay píldora que presionar, solo un target transparente. */
  .section--inverted .wp-block-button.quicklane__cta-btn .wp-block-button__link,
  .wp-block-button.quicklane__cta-btn .wp-block-button__link,
  .section--inverted .wp-block-button.quicklane__cta-btn .wp-block-button__link:hover,
  .wp-block-button.quicklane__cta-btn .wp-block-button__link:hover,
  .section--inverted .wp-block-button.quicklane__cta-btn .wp-block-button__link:active,
  .wp-block-button.quicklane__cta-btn .wp-block-button__link:active,
  .section--inverted .wp-block-button.quicklane__cta-btn .wp-block-button__link:focus,
  .wp-block-button.quicklane__cta-btn .wp-block-button__link:focus {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    min-width: 44px;
    min-height: 44px;
    padding: 0;
    font-size: 0;
    line-height: 0;
    overflow: hidden;
    border: none;
    box-shadow: none;
    background: transparent;
    transform: none;
    border-radius: 0;
  }
  .wp-block-button.quicklane__cta-btn .wp-block-button__link::after {
    content: "";
    display: block;
    width: 24px;
    height: 24px;
    flex-shrink: 0;
    background-color: var(--text-primary);
    -webkit-mask: url('../images/icons/icon-chevron-right.svg') no-repeat center / contain;
    mask: url('../images/icons/icon-chevron-right.svg') no-repeat center / contain;
  }
}
@media (min-width: 960px) {
  .quicklane__inner { position: relative; justify-content: center; }
  .quicklane__text { text-align: center; }
  .quicklane__cta-wrap {
    position: absolute;
    width: max-content;
    right: var(--gutter);
    top: 50%;
    transform: translateY(-50%);
  }
}

/* ---- 04/06/07 · Icono de candado — core/image de ancho fijo ----------- */
/* El `width` del bloque Image (18/22px) ya limita el ancho vía atributo;
   aquí solo se asegura que el `<figure>` no herede el 100% de ancho del
   grupo flex que lo contiene (comportamiento por defecto de core/image).
   Fix F5 (port-phase-2-fixes.md) — mismo defecto que `.footer-sello` (más
   arriba en este fichero): core imprime `.wp-block-image{margin:0 0 1em}`,
   y ese margen inferior se sumaba a la altura de la fila del paso 3 (06)
   — la causa real de los "+8px a cada ancho" del hallazgo, no solo el
   icono relleno. */
.step__lock,
.tariff__lock { flex-shrink: 0; margin-bottom: 0; }
.step__lock img,
.tariff__lock img { width: 100%; height: auto; }

/* ---- 04 · Iconos de capacidad — core/image en vez de <svg> inline ----- */
.capacity__icon img { width: 58px; height: 58px; margin-inline: auto; }

/* ---- 06 · Corona de paso — core/image (icon-crown-badge.svg) ---------- */
/* Port fix C7 (docs/port-fix-c7.md §C7.2) — icon-crown-badge.svg's own
   content was replaced with Pixun's icon_boxes.svg (viewBox 34.96×26.84,
   was 70×60). This rule's box stays width:100% of the 60px-wide
   .step__crown figure; height follows the new intrinsic ratio
   (26.84/34.96 vs the old 60/70) — see the report for the measured px
   change, nothing to declare here since height was always auto. */
.step__crown img { width: 100%; height: auto; }

/* ---- 07/08 · Listas "Coronas" — corona vía CSS mask, no <svg> inline -- */
/* El bloque core/list no admite un <svg> propio por elemento sin que el
   validador del editor lo señale como contenido inesperado — la corona se
   dibuja con una máscara CSS. #icon-crown (parts/svg-sprite.html) sigue sin
   tocar y sin usarse aquí: nunca fue la fuente de esta máscara. */
.wp-block-list.is-style-crowns li,
.wp-block-list.is-style-crowns-red li,
.wp-block-list.is-style-crowns-yellow li {
  position: relative;
  padding-left: 0;
}
/* Port fix C7 (docs/port-fix-c7.md §C7.1) — Pixun's own list-crown artwork
   (M:\MAIN PROJECTS\todobazar\SVG\icon-list-*.svg, one path, viewBox
   25.55×19.29) replaces the placeholder bullet this theme drew before.
   New file icon-crown-list-mask.svg carries just that path at fill:#000;
   icon-crown-bullet-mask.svg (the previous mask, viewBox 24×24, no balls)
   is now referenced by nothing in this theme — left on disk untouched,
   same as icon-crown-mask.svg above it (cleanup list, Pixun's permission).
   Box: width kept at 26px; height set to the path's own ratio (26 ×
   19.29/25.55 = 19.63px) instead of a 26×26 square, so `mask … contain`
   has nothing to letterbox — measured via a DPR4 crop against the source
   SVG rendered at the same box, see the report. Colour via currentColor
   (mask can't read fill), `color` set per style below — background-color
   on the shared box would have collided with the li's own text colour. */
.wp-block-list.is-style-crowns li::before,
.wp-block-list.is-style-crowns-red li::before,
.wp-block-list.is-style-crowns-yellow li::before {
  content: "";
  display: block;
  flex-shrink: 0;
  width: 26px;
  height: 19.63px;
  background-color: currentColor;
  -webkit-mask: url('../images/icons/icon-crown-list-mask.svg') no-repeat center / contain;
  mask: url('../images/icons/icon-crown-list-mask.svg') no-repeat center / contain;
}
.wp-block-list.is-style-crowns li::before { color: var(--text-primary); }
.wp-block-list.is-style-crowns-red li::before { color: var(--accent); }
.wp-block-list.is-style-crowns-yellow li::before { color: var(--accent-2); }

/* Pixun's CSS, 2026-09-14 (C7 round 2) — exact values, not touched. Placed
   here, after the rules above, so it wins by cascade order at equal
   specificity (declared later in the document) without needing
   !important: align-items overrides the row rule at ~line 2050 above
   (.tariff__points li etc.), and the box size overrides the ::before rule
   immediately above this comment. His align-items line names only
   is-style-crowns (not -red/-yellow) — left exactly as sent, not extended. */
.tariff__points li, .logistics__list li, .wp-block-list.is-style-crowns li{ align-items: start; }

.wp-block-list.is-style-crowns li::before, .wp-block-list.is-style-crowns-red li::before, .wp-block-list.is-style-crowns-yellow li::before {width: 21px; height: 20.63px;}

/* ---- 09 · Chip de estado — punto decorativo vía ::before -------------- */
/* El HTML estático llevaba un <span class="retail__status-dot"> vacío,
   puramente decorativo; en el patrón de bloque ese hueco se cubre con un
   pseudo-elemento sobre el propio grupo del chip, mismo tamaño/color que
   .retail__status-dot (arriba, regla del prototipo, sin tocar). Este
   ::before es el que de verdad pinta en el bloque (el patrón no lleva
   ningún <span class="retail__status-dot">) — C10 round 2 validación:
   seguía en `var(--text-tertiary)`, que `.retail.is-light` reasigna a ink;
   con overlay claro el punto se apagaba sobre la placa oscura del chip,
   igual que le pasaba al texto/borde antes del fix de la ronda anterior.
   Mismo valor fijo #9E978C que .retail__status-dot y que --text-tertiary
   daba dentro de .section--inverted — no depende ya de esa variable. */
.retail__status--chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}
.retail__status--chip::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #9E978C;
  flex-shrink: 0;
}
.retail__status--chip p { margin: 0; }

/* ---- 11 · Pie — core/details sustituye a <details class="footer-accordion"> */
/* Mismo tratamiento visual que .footer-accordion (arriba, prototipo
   estático, sin tocar) — selectores añadidos, sin duplicar declaraciones.
   core/details no tiene un <h3> propio dentro de <summary> (solo texto
   enriquecido) ni el span .footer-accordion__icon: el "+/-" se dibuja
   sobre el propio <summary> con ::before/::after en vez de sobre un hijo. */
.wp-block-details.footer-group,
.footer-accordion {
  border: none;
}
.wp-block-details.footer-group > summary,
.footer-accordion summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-block: var(--sp-4);
  border-bottom: 1px solid var(--bg);
  cursor: pointer;
  list-style: none;
  font-family: var(--font-body);
  font-size: var(--fs-body);
  font-weight: 700;
  /* Fix F2 round 2 (port-phase-2-fixes.md) — en el HTML el texto del
     summary vive dentro de un <h3> (regla base h1,h2,h3, styles.css 294):
     line-height: var(--lh-tight) (1.1), 17.6px a 16px. El `core/details`
     del bloque pone el texto directo en el <summary>, que hereda
     --lh-normal (1.5) del body — 50.6px vs 57px medidos por fila, ×3
     grupos = +19.2px de los +25.2 del pie a 380/768. */
  line-height: var(--lh-tight);
  position: relative;
}
.wp-block-details.footer-group > summary::-webkit-details-marker,
.footer-accordion summary::-webkit-details-marker { display: none; }
/* Cruz "+" que se pliega a "−" cuando el <details> está abierto — dos
   trazos sobre un hueco de 16px a la derecha del summary (mismo hueco que
   .footer-accordion__icon ocupaba en el prototipo). */
.wp-block-details.footer-group > summary::before {
  content: "";
  position: absolute;
  right: 0;
  top: 50%;
  width: 16px;
  height: 2px;
  background: var(--text-primary);
  transform: translateY(-50%);
}
.wp-block-details.footer-group > summary::after {
  content: "";
  position: absolute;
  right: 7px;
  top: 50%;
  width: 2px;
  height: 16px;
  background: var(--text-primary);
  transform: translateY(-50%);
  transition: transform .15s ease;
}
.wp-block-details.footer-group[open] > summary::after { transform: translateY(-50%) scaleY(0); }

.wp-block-details.footer-group .footer-links,
.footer-accordion .footer-links {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-4) 0;
  margin: 0;
}
.wp-block-details.footer-group .footer-links a,
.footer-accordion .footer-links a { color: var(--text-primary); text-decoration: none; font-weight: 700; }
.wp-block-details.footer-group .footer-links a:hover,
.footer-accordion .footer-links a:hover { text-decoration: underline; }

@media (min-width: 960px) {
  .wp-block-details.footer-group > summary {
    border-bottom: none;
    padding-block: 0;
    margin-bottom: var(--sp-4);
    cursor: default;
  }
  .wp-block-details.footer-group > summary::before,
  .wp-block-details.footer-group > summary::after { display: none; }
  .wp-block-details.footer-group .footer-links { padding: 0; }
}

/* Enlaces legales y copyright — el marcado de bloque envuelve cada enlace
   en su propio <p> (core/paragraph) en vez del <a> suelto del prototipo;
   mismo tratamiento visual, selector ampliado. */
.footer-legal p { margin: 0; }
.footer-legal a { color: var(--text-primary); text-decoration: none; font-weight: 600; }
.footer-legal .pending-tag { font-size: var(--fs-small); color: var(--text-primary); }

/* ---- 12 · Barra fija — contrato UASYS (plan §14) ----------------------- */
/* UASYS lee --mox-chat-lift en :root y sube su lanzador/ventana esa altura.
   Con la barra visible (<960px) se fija a la altura REAL de .mobile-bar,
   medida en vivo (headless Edge, 380px): padding-block 2×12px + borde
   superior 1px + alto del botón real (54px: line-height 24px + padding
   12px×2 + borde 3px×2, box-sizing:border-box) = 79px — no la cifra
   redondeada de 73px que asumía min-height:48px sin contar el borde de
   3px del estilo "sticker" ni el line-height real del texto. La nota de
   .site-footer más arriba (73px) queda desactualizada por esta misma
   razón — ver docs/port-phase-2.md LOG. Fuera de la media query, 0. Sin
   JS, dentro de la misma media query que muestra la barra (plan §14). */
:root { --mox-chat-lift: 0px; }
@media (max-width: 959.98px) {
  :root { --mox-chat-lift: 79px; }
}

/* ---- 12b · Lanzador de UASYS, estilo IDENTICO al CTA de cabecera
   (port fix C6, ronda 2 — reemplaza la ronda 1 entera, Pixun por la sesion
   de cuenta): "el boton del asistente ha de ser el mismo estilo que el
   boton pide tu tarifa del header, con el mismo hover todo identico, sin
   el destello de luz, el mismo tamaño tambien. El label i el icono
   dejalos, porque son su contenido."

   RONDA C12 (2026-09-15): plugin-factory (commit aaecaa2) añadió seis
   variables de color del lanzador que Ajustes de UASYS ahora guarda con el
   visto bueno de Pixun — `--mc-launcher-bg`, `--mc-launcher-fg`,
   `--mc-launcher-border`, `--mc-launcher-shadow` (solo color, el offset es
   nuestro), `--mc-launcher-hover-bg`, `--mc-launcher-hover-fg` — impresas
   inline en `.mox-chat` por `Mox_Chat_Settings::palette_style()`
   (class-mox-chat-settings.php ~660-672, mapa de nombres en
   `element_vars()` ~457-466). Confirmado en la pagina servida (no en el
   codigo solo): hoy valen #FB3E16 / #FFFFFF / #231F20 / #231F20 / #FB3E16 /
   #FFFFFF — antes de que se guardaran, `--mc-launcher-bg` era #FEBC15
   (amarillo, el respaldo estatico del critical CSS), asi que fiarse del
   código sin mirar la pagina habria sido un error.

   DE AQUI EN ADELANTE, EL TEMA YA NO PONE NINGUN COLOR EN EL LANZADOR: fondo,
   texto/icono, borde, sombra (color) y fondo/texto al pasar por encima
   salen TODOS de esas seis variables. Lo que el tema sigue poniendo es SOLO
   geometria: tamaño, padding, radio, grosor de borde, offset de sombra
   (8/4/0 px), transition, los transforms de hover/active, la cancelacion
   del halo y el alto igualado a la barra movil — nada de eso es color.

   CORREGIDO EN C12.1 (2026-09-15) — esta nota decia "sin valor de respaldo,
   nunca var(--x, algo)". Era falso en un caso real: "restablecer grupo" en
   Ajustes deja los seis `el_launcher_*` vacios, y entonces el plugin SOLO
   imprime `--mc-launcher-bg`/`--mc-launcher-fg` (via `accent_2`, si tiene
   hex) — `--mc-launcher-border`, `--mc-launcher-shadow`,
   `--mc-launcher-hover-bg` y `--mc-launcher-hover-fg` no existen en
   absoluto. Sin respaldo, eso es `border-color:currentcolor` (inicial),
   `box-shadow:none` (la propiedad entera invalida, no solo el color) y
   `background:transparent` en hover — halllado por la sesion de cuenta.
   Border/sombra/hover-bg/hover-fg llevan ahora un respaldo, pero SIGUE
   SIENDO del plugin, nunca del tema: caen a `--mc-launcher-fg` (borde y
   sombra) o a la pareja de reposo `--mc-launcher-bg`/`-fg` (hover) — las
   dos variables que el plugin imprime en cuanto hay una paleta guardada,
   con o sin los seis campos del lanzador. `--mc-launcher-bg`/`-fg` en sí
   siguen sin respaldo: esas dos SIEMPRE llegan si hay paleta, y si algún
   día no llegasen ninguna, no hay ya ninguna otra variable del plugin de la
   que colgar un respaldo sin inventar un color propio del tema — preferible
   que se rompa a que el tema decida por Ajustes.

   Solo CSS del tema, solo estos selectores; plugin y `mox_chat_settings`
   sin tocar.

   ESPECIFICIDAD: `.mox-chat[data-mox-chat][data-cursor="native"]
   .mox-chat__launcher` en vez del `.mox-chat .mox-chat__launcher` de la
   ronda 1 (0,2,0) — hace falta mas fuerza. mox-chat.css tiene una regla SIN
   :hover que fija `transition:transform .5s cubic-bezier(...)` en el
   lanzador cuando esta cerrado y hay raton (`.mox-chat:not([data-mox-state])
   .mox-chat__launcher` / `[data-mox-state="closed"]`, especificidad 0,3,0):
   con la base de la ronda 1 (0,2,0) esa regla ganaba y el hover viajaba a
   0.5s cubic-bezier en vez de los .15s ease del CTA. `data-mox-chat` y
   `data-cursor="native"` son atributos REALES del marcado del plugin
   (render.php), siempre presentes — no un truco: suman (0,4,0), por encima
   de cualquier competidor de mox-chat.css que hayamos encontrado salvo el
   viaje del PIN (html[data-pin-inside]..., 0,4,1: fuera de alcance, ronda 1
   ya lo dejaba fuera — hover mientras esta pinned-centrado es un caso de
   borde no pedido). */
.mox-chat[data-mox-chat][data-cursor="native"] .mox-chat__launcher {
  /* Tipografia del BOTON ENTERO, no solo la etiqueta (pedido explicito de
     esta ronda): Montserrat 700 var(--fs-body), igual que el CTA. Esto
     cambia la base `em` de la que colgaban gap/icono/padding del lanzador
     (critical CSS, render.php: font-size:27px) — se recalculan todos a px
     fijos aqui abajo para que nada "explote" ni encoja, sin tocar el SVG
     ni el texto (§5, "el label i el icono dejalos"). */
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--fs-body);
  /* line-height 1 (critical CSS) -> 1.5 (--lh-normal): la etiqueta hereda
     esto y su caja de linea pasa a medir fs*1.5, igual que el texto del
     CTA (tambien flex, tambien hereda el line-height del contenedor) — es
     lo que hace que el alto del lanzador coincida con el del CTA (ver mas
     abajo) usando la MISMA formula que ya usa cualquier boton `.btn`. */
  line-height: var(--lh-normal);
  /* Gap frissco (antes .2em de 27px = 5.4px): un token real en vez de un
     numero suelto. Solo se ve cuando la etiqueta esta en el flujo flex
     (ajuste "mostrar texto en movil" activado, o escritorio). */
  gap: var(--sp-2);
  /* Padding-inline igual al CTA siempre (--sp-8, 32px): es "mismo estilo"
     (§1). Padding-block igual al CTA en escritorio (--sp-8... no, --sp-3,
     12px) y ajustado -1px por lado bajo 960px — ver la media query de
     abajo y su nota sobre por que. */
  padding-block: var(--sp-3);
  padding-inline: var(--sp-8);
  border-radius: var(--radius-pill);
  /* Grosor de borde: geometria del tema, igual al CTA siempre (4px). El
     color YA NO es nuestro (C12): antes `var(--bg-pure)`, un ink fijo del
     tema; ahora `--mc-launcher-border`, la variable que Ajustes de UASYS
     escribe inline en `.mox-chat`.
     CORRECCION C12.1 — SI HAY UN VALOR DE RESPALDO, pero nunca un color del
     tema: `var(--mc-launcher-border, var(--mc-launcher-fg))`. Defecto real,
     hallado por la sesion de cuenta: con los seis `el_launcher_*` vacios
     (lo que deja "restablecer grupo"), el plugin solo imprime
     `--mc-launcher-bg`/`--mc-launcher-fg` — `--mc-launcher-border` no
     existe en absoluto, y un `var()` a una propiedad inexistente hace
     invalido el valor computado; `border-color` no es heredable, asi que
     cae a su inicial, `currentcolor` — el filete se leia del color de
     texto por accidente, no a proposito. El respaldo aqui es OTRA variable
     del plugin (`--mc-launcher-fg`, que el plugin SIEMPRE imprime), nunca
     `--bg-pure` ni ningun token del tema: sigue siendo su paleta, con un
     escalon menos si ese campo en concreto esta vacio. */
  border-width: 4px;
  border-style: solid;
  border-color: var(--mc-launcher-border, var(--mc-launcher-fg));
  /* Fondo y texto/icono: C12 los mueve a las variables del plugin en vez de
     los presets del tema que traía la ronda 2 (`--wp--preset--color--accent`
     / `--bg-elevated-2`). Estas dos SIEMPRE las imprime el plugin en cuanto
     `accent_2` tiene un hex guardado (class-mox-chat-settings.php ~663), sin
     depender de los campos `el_launcher_*` — por eso no llevan respaldo:
     no hace falta. `--mc-launcher-bg`/`--mc-launcher-fg` ya existían desde
     antes de C6 (el critical CSS los usa con respaldo #0d0d0b/#fed700 si
     Ajustes no ha guardado nada); aquí se leen SIN ese respaldo porque hoy
     SÍ están guardados — confirmado en la página servida, no solo en el
     código (ver nota de ronda C12 más arriba, el aviso de la trampa). */
  background: var(--mc-launcher-bg);
  color: var(--mc-launcher-fg);
  /* Offset 8/4/0 px: geometría del tema, igual al CTA siempre. El color del
     tercer valor ya no es `var(--bg-pure)`: es `--mc-launcher-shadow`
     (C12), con el mismo respaldo a `--mc-launcher-fg` que el borde
     (C12.1) y por el mismo motivo — sin `el_launcher_shadow` guardado la
     variable no existe y la sombra entera (offset incluido: la propiedad
     compuesta cae entera) desaparecia, inicial `none`. */
  box-shadow: 8px 8px 0 var(--mc-launcher-shadow, var(--mc-launcher-fg));
  /* Duracion/easing del CTA (`.btn--primary` et al., ~367): .15s ease en
     transform Y box-shadow — NO los .5s cubic-bezier que mox-chat.css pone
     para el viaje del pin. Solo estas dos propiedades (nunca `transition:
     all`): la ronda 1 ya dejaba opacity/visibility del fundido
     abierto/cerrado fuera de esta regla, y eso sigue igual — se combina con
     lo del plugin, no lo sustituye entero. */
  transition: transform .15s ease, box-shadow .15s ease;
}
/* Alto igual al boton rojo de la barra movil bajo 960px (Pixun, ronda 2
   §4), sin tocar el filete (4px, igual que el CTA siempre — §1). La barra
   mide borde(3)+padding-block(12)+caja-de-linea(fs*1.5)+padding-block(12)+
   borde(3) = fs*1.5+30. El lanzador con borde 4 iguala esa suma con
   padding-block 11 en vez de 12 (4+11 = 3+12 = 15 por lado): fs*1.5+30,
   identico para cualquier valor de --fs-body dentro de su clamp — no un
   pixel fijo, una formula que se mantiene sola. Frontera 959.98px: la
   misma que usa --mox-chat-lift, arriba, para la misma barra. */
@media (max-width: 959.98px) {
  .mox-chat[data-mox-chat][data-cursor="native"] .mox-chat__launcher {
    padding-block: calc(var(--sp-3) - 1px);
  }
}
/* Etiqueta: mismo tamaño de texto que el CTA (antes 0.6em de 27px = 16.2px,
   casualmente parecido; ahora el token real) y SIN las versalitas
   (`font-variant:all-small-caps`, mox-chat.css/critical CSS) — el CTA no
   las lleva, y esta ronda pide tipografia identica. El texto en si no se
   toca (§5): solo tamaño/caja, nunca contenido. */
.mox-chat[data-mox-chat][data-cursor="native"] .mox-chat__launcher-label {
  font-size: var(--fs-body);
  font-variant: normal;
}
/* Icono: 20px fijo SIEMPRE (antes 1em => 27px en movil, 20px fijo en
   escritorio vía el critical CSS) — se unifica al valor de escritorio en
   vez de dejarlo en 27px, que junto al texto mas pequeño de esta ronda se
   veria sobredimensionado ("que no explote", pedido explicito). 20px es
   menor que la caja de linea de la etiqueta (24-25.5px, fs*1.5) en todo el
   rango del clamp, así que la etiqueta sigue siendo quien manda el alto del
   boton — la fórmula del comentario de arriba se sostiene con el icono
   presente o no. Margen a 0: el offset vertical de 4px del critical CSS
   (render.php ~1017) es centrado óptico contra las versalitas, que aquí ya
   no existen. Ni el SVG ni el `<img>` propio (--own, mascara) cambian de
   fichero: solo su caja. */
.mox-chat[data-mox-chat][data-cursor="native"] .mox-chat__launcher-icon {
  width: 20px;
  height: 20px;
  margin: 0;
}
/* Sin destello de luz (Pixun, ronda 2 §3): el único brillo/pulso que toca
   el lanzador es el halo `::after` del critical CSS (render.php ~925-935:
   box-shadow difuso + `animation:mox-latido` infinita, mas su variante
   atenuada bajo reduced-motion) — comprobado que mox-chat.css no añade
   ningún otro (su único `glow`/`filter`/`@keyframes` es
   `.mox-chat__head[data-mox-head-fx="glow"]`, la cabecera DENTRO del panel
   ya abierto, no el lanzador). `content:none` retira la caja generada
   entera — nada que pintar, con o sin `prefers-reduced-motion`, sin
   necesidad de pisar `animation`/`box-shadow`/`opacity` uno a uno. */
.mox-chat[data-mox-chat][data-cursor="native"] .mox-chat__launcher::after {
  content: none;
}
/* Hover/active — transform y offset de sombra idénticos al CTA (geometria,
   misma sección de arriba). No se toca `opacity`/`visibility` (contrato de
   estados abierto/cerrado del plugin).
   C12: el color de la sombra en los tres estados (reposo/hover/active) sale
   de `--mc-launcher-shadow`. El fondo/texto AL PASAR POR ENCIMA sale de
   `--mc-launcher-hover-bg` / `--mc-launcher-hover-fg`, puestos SOLO en
   `:hover` — no hay variable de "activo" propia en el plugin, y con el
   ratón pulsado sobre el botón `:hover` sigue casando a la vez que
   `:active`, así que el color no necesita repetirse en la regla de
   `:active` (nada en ella lo pisa).
   CORRECCION C12.1 — "sin respaldo" (comentario de C12) ya NO es cierto:
   las cuatro variables de esta sección llevan respaldo a OTRA variable del
   plugin, nunca a un color del tema, por el mismo defecto que el borde de
   arriba ("restablecer grupo" deja los seis `el_launcher_*` vacios, y el
   plugin entonces no imprime ni `--mc-launcher-shadow` ni
   `--mc-launcher-hover-bg`/`-fg` en absoluto — hallado por la sesion de
   cuenta, medido: la sombra desaparecia (box-shadow entero invalido ->
   `none`) y el fondo al pasar por encima se volvia transparente
   (background-color inicial)). El respaldo de fondo/texto de hover es la
   pareja de reposo (`--mc-launcher-bg`/`-fg`), que el plugin SI imprime
   siempre que `accent_2` tenga hex guardado — no hace que el hover deje de
   cambiar nada visible mientras Ajustes tenga un color base, y evita el
   transparente cuando faltan solo los cuatro campos nuevos de esta ronda. */
.mox-chat[data-mox-chat][data-cursor="native"] .mox-chat__launcher:hover {
  transform: translate(4px, 4px);
  box-shadow: 4px 4px 0 var(--mc-launcher-shadow, var(--mc-launcher-fg));
  background: var(--mc-launcher-hover-bg, var(--mc-launcher-bg));
  color: var(--mc-launcher-hover-fg, var(--mc-launcher-fg));
}
.mox-chat[data-mox-chat][data-cursor="native"] .mox-chat__launcher:active {
  transform: translate(8px, 8px);
  box-shadow: 0 0 0 var(--mc-launcher-shadow, var(--mc-launcher-fg));
}
/* Reducción de movimiento — igual que `.header-cta:hover/:active` en la
   sección REDUCED MOTION (~3306): el `*{transition-duration:.001ms!important}`
   global ya corta la interpolación; esto devuelve el valor de reposo exacto
   (offset de sombra, geometria) — el color sigue viniendo de la variable
   (con el mismo respaldo de C12.1), nunca se fija aquí. */
@media (prefers-reduced-motion: reduce) {
  .mox-chat[data-mox-chat][data-cursor="native"] .mox-chat__launcher:hover,
  .mox-chat[data-mox-chat][data-cursor="native"] .mox-chat__launcher:active {
    transform: none;
    box-shadow: 8px 8px 0 var(--mc-launcher-shadow, var(--mc-launcher-fg));
  }
}
