/* ==========================================================================
   FASE 2 — SISTEMA DE DISEÑO APLICADO
   Paleta y tipografía definidas en css/tokens.css. Este archivo consume
   esas variables; no se toca la estructura HTML ni el logo.
   FASE 3 — Efectos de scroll (parallax en el hero, revelado con
   profundidad 3D en tarjetas, inclinación interactiva en galería y
   productos). Ver bloque "Scroll & parallax 3D" al final del archivo.
   ========================================================================== */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  font-family: var(--font-body);
  color: var(--color-text);
  /* Vino, no el crema de --color-bg: todas las secciones ya se pintan
     oscuras de punta a punta, así que este fondo normalmente no se ve
     — solo asoma como respaldo si algo deja un hueco (p. ej. el
     desvanecido del hero al hacer scroll, más abajo), y ahí un fondo
     claro se notaría como un parpadeo fuera de lugar. */
  background: var(--color-wine);
  line-height: 1.6;
}

h1, h2, h3 {
  font-family: var(--font-heading);
  font-weight: 600;
  line-height: 1.15;
  margin: 0;
  color: inherit;
}

h2 {
  font-size: var(--fs-h2);
}

h3 {
  font-size: var(--fs-h3);
}

img {
  max-width: 100%;
  display: block;
}

a {
  color: inherit;
}

.container {
  max-width: 1200px;
  margin-inline: auto;
  padding-inline: 1.5rem;
}

.skip-link {
  position: absolute;
  left: -999px;
  top: 0;
}

.skip-link:focus {
  left: 1rem;
  top: 1rem;
  background: var(--color-surface);
  color: var(--color-text);
  padding: 0.5rem 1rem;
  z-index: 100;
}

/* ---------- Botones ---------- */
.btn {
  display: inline-block;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 0.85rem 1.75rem;
  border: 1px solid var(--color-gold);
  border-radius: var(--radius-sm);
  text-decoration: none;
  white-space: nowrap;
  transition: background-color var(--transition-base), color var(--transition-base), border-color var(--transition-base);
}

.btn-large {
  padding: 1.1rem 2.25rem;
}

.btn-primary {
  background: transparent;
  color: var(--color-gold-deep);
  border-color: var(--color-gold);
}

.btn-primary:hover {
  background: var(--color-gold);
  color: var(--color-black);
}

.btn-whatsapp {
  background: var(--color-burgundy);
  color: var(--color-text-inverse);
  border-color: var(--color-burgundy);
  /* Elevación sutil: con la nueva paleta oscura por sección, este
     botón (el de Contacto, ya que header/hero lo sobreescriben con la
     versión píldora) podía quedar sobre un fondo oscuro similar en
     luminosidad al propio burdeos y "perder el borde". La sombra lo
     despega de cualquier fondo, oscuro o claro, sin cambiarle el color. */
  box-shadow: 0 10px 24px rgba(13, 5, 9, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.08);
}

.btn-whatsapp:hover {
  background: var(--color-burgundy-dark);
  border-color: var(--color-burgundy-dark);
}

/* ---------- Header ---------- */
/* Vino casi negro y semitransparente: se superpone a la imagen del
   hero (igual que en la referencia) en vez de una barra sólida. El
   blur de fondo mantiene el texto legible aunque cambie lo que hay
   debajo al hacer scroll (foto del hero vs. fondo claro de las
   siguientes secciones). rgba en vez de var(--color-wine) porque el
   canal alpha no se puede componer sobre un custom property de color
   sólido sin color-mix(); 13/5/9 es el RGB de --color-wine. */
.site-header {
  border-bottom: 1px solid var(--color-border-dark);
  position: sticky;
  top: 0;
  background: rgba(13, 5, 9, 0.92);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  z-index: 50;
}

.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-block: 1rem;
}

.logo {
  font-family: var(--font-heading);
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--color-text-inverse);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
}

.logo-img {
  height: 56px;
  width: auto;
}

.logo-text {
  color: #FFFFFF;
  white-space: nowrap;
}

.logo-text-accent {
  color: var(--color-gold);
}

@keyframes logo-float {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-6px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero-logo-img {
    animation: none;
  }
}

.site-nav {
  display: flex;
  align-items: center;
  gap: 1.5rem;
}

.site-nav ul {
  list-style: none;
  display: flex;
  gap: 1.25rem;
  margin: 0;
  padding: 0;
}

.site-nav a {
  /* Blanco puro (no el crema de --color-text-inverse): uniforme entre
     los 7 enlaces, a pedido explícito. */
  color: #FFFFFF;
  font-size: 0.95rem;
  font-weight: 500;
  text-decoration: none;
  transition: color var(--transition-base);
}

.site-nav a:hover {
  color: var(--color-gold-light);
}

/* CTA "Reserva cita": vino-lila + forma píldora, replicado tal cual
   entre el header (.site-nav), el hero (.hero-actions) y el botón de
   WhatsApp de Contacto (.contacto-info) — mismo componente, no
   estilos parecidos por separado. El halo usa dorado (no el propio
   plum del botón): un brillo del mismo tono oscuro que el fondo no se
   vería ni sobre el header casi negro, ni sobre el overlay del hero,
   ni sobre el vino-lila de Contacto — el dorado sí se distingue y
   combina con el resto de acentos del sitio. Encendido/apagado tipo
   neón: el box-shadow baja casi a cero y vuelve a subir en bucle. */
.site-nav .btn-whatsapp,
.hero-actions .btn-whatsapp,
.contacto-info .btn-whatsapp {
  position: relative;
  overflow: hidden;
  background: var(--color-plum);
  border-color: var(--color-plum);
  color: #FFFFFF;
  border-radius: 999px;
  transition: transform 0.25s ease, background-color var(--transition-base), border-color var(--transition-base);
  animation: cta-pill-neon 2.8s ease-in-out infinite;
}

/* Barrido de brillo: banda diagonal traslúcida del tamaño exacto del
   botón, aparcada fuera de su borde izquierdo. El overflow:hidden del
   botón (+ su border-radius:999px) la recorta a la forma píldora
   mientras atraviesa. Sin transition en el estado de reposo a
   propósito: al salir el cursor se reubica al instante fuera de la
   vista (invisible en ambos extremos), no "vuelve arrastrándose". */
.site-nav .btn-whatsapp::before,
.hero-actions .btn-whatsapp::before,
.contacto-info .btn-whatsapp::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(115deg, transparent 40%, rgba(255, 255, 255, 0.35) 50%, transparent 60%);
  transform: translateX(-120%);
  pointer-events: none;
}

.site-nav .btn-whatsapp:hover,
.hero-actions .btn-whatsapp:hover,
.contacto-info .btn-whatsapp:hover {
  background: var(--color-plum-light);
  border-color: var(--color-plum-light);
  /* Levantamiento sutil hacia el cursor. */
  transform: translateY(-2px);
  /* Misma animación de neón, con más amplitud (ver keyframes de abajo):
     así el pulso de encendido/apagado se nota "vivo" sin duplicar la
     lógica de sombra en una regla aparte que compita con la animación. */
  animation-name: cta-pill-neon-hover;
}

.site-nav .btn-whatsapp:hover::before,
.hero-actions .btn-whatsapp:hover::before,
.contacto-info .btn-whatsapp:hover::before {
  transition: transform 0.7s ease;
  transform: translateX(120%);
}

@keyframes cta-pill-neon {
  0%, 100% {
    box-shadow:
      0 0 0 1px rgba(228, 205, 141, 0.04),
      0 0 3px 1px rgba(176, 141, 63, 0.05);
  }
  50% {
    box-shadow:
      0 0 0 1px rgba(228, 205, 141, 0.6),
      0 0 6px 1px rgba(176, 141, 63, 0.65),
      0 0 12px 3px rgba(176, 141, 63, 0.35);
  }
}

/* Misma cadencia que cta-pill-neon, pero con el halo más intenso y una
   sombra de elevación (oscura, desplazada hacia abajo) que acompaña al
   translateY(-2px) — el pulso sigue activo en hover, solo "más". */
@keyframes cta-pill-neon-hover {
  0%, 100% {
    box-shadow:
      0 0 0 1px rgba(228, 205, 141, 0.45),
      0 0 5px 1px rgba(176, 141, 63, 0.35),
      0 6px 14px rgba(13, 5, 9, 0.35);
  }
  50% {
    box-shadow:
      0 0 0 1px rgba(228, 205, 141, 0.75),
      0 0 9px 2px rgba(176, 141, 63, 0.85),
      0 0 18px 5px rgba(176, 141, 63, 0.5),
      0 8px 18px rgba(13, 5, 9, 0.4);
  }
}

/* Botón "Comprar aquí" (Productos): mismo componente de píldora que
   "Reserva cita" arriba (forma, pulso neón, shine sweep, levantamiento
   al hover) — reutiliza tal cual @keyframes cta-pill-neon/-hover, que
   ya son doradas (rgba de --color-gold-light/--color-gold, nunca el
   plum del botón de WhatsApp), así que el halo "combina con el color
   del texto" sin duplicar ninguna keyframe. Solo cambia su propia
   identidad de color: negro ceniza oscuro de fondo (#1C1A18, más
   neutro que --color-black-soft, que tiene un matiz café más cálido)
   en vez del vino-lila de "Reserva cita", y texto dorado en vez de
   blanco — contraste dorado claro sobre este negro casi tan alto como
   sobre --color-black-soft, sin necesidad de ajustar el texto. Sin
   cambio de background-color en :hover: un filter:brightness sutil
   cumple el mismo rol de "highlight" que el plum-light del otro
   botón. */
.productos-header .btn-primary {
  position: relative;
  overflow: hidden;
  background: #1C1A18;
  border-color: #1C1A18;
  color: var(--color-gold-light);
  border-radius: 999px;
  transition: transform 0.25s ease, filter var(--transition-base), border-color var(--transition-base);
  animation: cta-pill-neon 2.8s ease-in-out infinite;
}

.productos-header .btn-primary::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(115deg, transparent 40%, rgba(255, 255, 255, 0.35) 50%, transparent 60%);
  transform: translateX(-120%);
  pointer-events: none;
}

.productos-header .btn-primary:hover {
  background: #1C1A18;
  filter: brightness(1.22);
  color: var(--color-gold-light);
  transform: translateY(-2px);
  animation-name: cta-pill-neon-hover;
}

.productos-header .btn-primary:hover::before {
  transition: transform 0.7s ease;
  transform: translateX(120%);
}

.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 40px;
  height: 40px;
  background: none;
  border: 1px solid var(--color-border-dark);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.nav-toggle span {
  display: block;
  height: 2px;
  background: var(--color-text-inverse);
}

/* ---------- Secciones genéricas ---------- */
main > section {
  padding-block: 4.5rem;
}

/* ---------- Paleta oscura variada por sección ---------- */
/* Cada sección redefine LOCALMENTE los mismos tokens semánticos que ya
   consumen sus propios componentes (--color-bg/-alt, --color-text-muted,
   --color-border, --color-surface, --color-accent, --color-gold-deep
   reutilizado aquí como "acento de la sección") — así heredan el nuevo
   esquema sin tocar cada regla de servicio/producto/testimonio una por
   una. Contraste verificado sección por sección (acento vs. fondo) —
   ver el bloque de ratios documentado en tokens.css junto a cada
   color. Fondos sin repetirse entre secciones contiguas para que se
   sienta variado, salvo dos excepciones deliberadas: Contacto y
   Productos comparten la familia "vino-lila" (ver comentario junto a
   .opiniones más abajo), y Opiniones reutiliza el mismo ceniza frío de
   "Colores a tu Medida" (no son contiguas, así que no repite contra su
   vecino inmediato).*/
.servicios {
  --color-bg: var(--color-obsidian);
  --color-text-muted: var(--color-text-inverse-muted);
  --color-border: var(--color-border-dark);
  --color-gold-deep: var(--color-gold-light);
  --color-accent: var(--color-gold-light);
  position: relative;
  /* z-index:0 (no "auto"): crea contexto de apilamiento PROPIO, para
     que el z-index:-1 de .servicios::before quede contenido aquí
     dentro (detrás del contenido, delante solo de este fondo) en vez
     de escaparse al contexto raíz de toda la página — sin esto, el
     fondo opaco de la sección (pintado como elemento z-index:auto del
     contexto raíz) tapaba por completo el degradado de costura. Mismo
     motivo en las otras 5 secciones de abajo. */
  z-index: 0;
  background: var(--color-bg);
  color: var(--color-text-inverse);
}

/* Transición difuminada entre el hero y "Servicios": en vez de un
   corte seco entre el vino del hero y el negro de esta sección, un
   lavado de ese mismo vino se desvanece hacia transparente sobre el
   negro — la costura se siente gradual, con los dos colores reales de
   cada lado en vez de un tono neutro inventado a mitad de camino. */
.servicios::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  /* Más alta y con un tramo intermedio (antes iba directo a
     transparente y se perdía enseguida): se nota más al hacer scroll,
     sin llegar a tapar el encabezado de la sección. */
  height: clamp(160px, 26vw, 320px);
  background: linear-gradient(to bottom, var(--color-wine) 0%, rgba(13, 5, 9, 0.55) 45%, transparent 100%);
  pointer-events: none;
  /* z-index: -1 — sin esto, este pseudo-elemento (posicionado, sin
     z-index) se pintaba DELANTE del contenido normal de la sección
     (.container, el encabezado incluido): un descendiente en flujo
     normal se pinta antes que uno posicionado con z-index:auto dentro
     del mismo contexto de apilamiento. El resultado era el título de
     cada sección "lavado"/opacado por este degradado oscuro encima.
     Con z-index:-1 el lavado queda detrás de todo el contenido, delante
     solo del fondo propio de la sección — como estaba pensado. */
  z-index: -1;
}

/* Ceniza fría (azulado, B>G>R): distinta a propósito del ceniza cálido
   de Galería (--color-black-soft) y del negro absoluto de Servicios
   (--color-obsidian, #000000 — ver comentario junto a su definición en
   tokens.css) — para que el cambio de sección se note con claridad al
   hacer scroll tanto contra Servicios como contra Galería, aun cuando
   el lavado de abajo siga suavizando la costura (antes era "lavanda
   humo", retirado tras revisión con la clienta). Ya no hay
   isla marfil envolviendo los carruseles (ver .paleta-color__grid más
   abajo): todo el bloque de Esmaltes y Tintes queda directamente sobre
   este fondo oscuro, así que --color-text-muted/--color-border/
   --color-gold-deep pasan a sus versiones "sobre oscuro" tanto para
   los elementos externos (eyebrow) como para el contenido de los
   carruseles (título, ayuda, botón de catálogo). Los dos círculos de
   piezas/miniaturas siguen siendo blancos (--color-surface, sin
   cambios) y por eso su icono/etiqueta sí necesitan texto oscuro fijo
   — ver comentario en color-carousel.css. */
.esmaltes {
  --color-bg-alt: var(--color-ash-cool);
  --color-text-muted: var(--color-text-inverse-muted);
  --color-border: var(--color-border-dark);
  --color-gold-deep: var(--color-gold-light);
  position: relative;
  /* z-index:0 — ver comentario en .servicios: contiene el z-index:-1
     de .esmaltes::before dentro de esta sección. */
  z-index: 0;
  background: var(--color-bg-alt);
  color: var(--color-text-inverse);
}

/* Mismo lavado que .servicios::before, con el color real de la sección
   anterior (Servicios = negro absoluto) en vez de reinventar un tono
   neutro por costura — sigue suavizando la costura aunque los dos
   negros ya se lean como claramente distintos uno del otro. Se repite
   igual en las 4 secciones siguientes. */
.esmaltes::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: clamp(160px, 26vw, 320px);
  background: linear-gradient(to bottom, var(--color-obsidian) 0%, rgba(0, 0, 0, 0.55) 45%, transparent 100%);
  pointer-events: none;
  /* z-index: -1 — ver comentario en .servicios::before: sin esto tapaba
     el encabezado de la sección en vez de quedar detrás. */
  z-index: -1;
}

.galeria {
  --color-bg-alt: var(--color-black-soft);
  --color-text-muted: var(--color-text-inverse-muted);
  --color-border: var(--color-border-dark);
  --color-gold-deep: var(--color-cream);
  position: relative;
  /* z-index:0 — ver comentario en .servicios: contiene el z-index:-1
     de .galeria::before dentro de esta sección. */
  z-index: 0;
  background: var(--color-bg-alt);
  color: var(--color-text-inverse);
}

.galeria::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: clamp(160px, 26vw, 320px);
  /* Lavado con el color real de Esmaltes (--color-ash-cool, RGB
     21,23,27 en el rgba — ceniza fría). */
  background: linear-gradient(to bottom, var(--color-ash-cool) 0%, rgba(21, 23, 27, 0.55) 45%, transparent 100%);
  pointer-events: none;
  /* z-index: -1 — ver comentario en .servicios::before: sin esto tapaba
     el encabezado de la sección en vez de quedar detrás. */
  z-index: -1;
}

.productos {
  --color-bg-alt: var(--color-plum-deep);
  --color-text-muted: var(--color-text-inverse-muted);
  --color-border: var(--color-border-dark);
  --color-surface: rgba(255, 255, 255, 0.06);
  --color-gold-deep: var(--color-gold-light);
  --color-accent: var(--color-gold-light);
  position: relative;
  /* z-index:0 — ver comentario en .servicios: contiene el z-index:-1
     de .productos::before dentro de esta sección. */
  z-index: 0;
  background: var(--color-bg-alt);
  color: var(--color-text-inverse);
}

.productos::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: clamp(160px, 26vw, 320px);
  background: linear-gradient(to bottom, var(--color-black-soft) 0%, rgba(36, 28, 23, 0.55) 45%, transparent 100%);
  pointer-events: none;
  /* z-index: -1 — ver comentario en .servicios::before: sin esto tapaba
     el encabezado de la sección en vez de quedar detrás. */
  z-index: -1;
}

/* Opiniones reutiliza el mismo ceniza frío de "Colores a tu Medida"
   (--color-ash-cool, ver .esmaltes más arriba) en vez de seguir la
   familia vino-lila de Contacto/Productos: no son secciones contiguas
   en el scroll (Galería y Productos quedan entre medio), así que
   repetir el tono no genera un salto sin transición. Contacto conserva
   su color original (--color-plum) y Productos su variación más
   oscura de esa misma familia (--color-plum-deep) — ninguno de los dos
   se toca, este ajuste es solo para Opiniones. El lavado de cada
   ::before sigue usando el color real de la sección anterior en el
   scroll para mantener la costura continua. */
.opiniones {
  --color-bg: var(--color-ash-cool);
  --color-text-muted: var(--color-text-inverse-muted);
  --color-border: var(--color-border-dark);
  --color-surface: rgba(255, 255, 255, 0.06);
  --color-gold-deep: var(--color-blush);
  --color-accent: var(--color-blush);
  position: relative;
  /* z-index:0 — ver comentario en .servicios: contiene el z-index:-1
     de .opiniones::before dentro de esta sección. */
  z-index: 0;
  background: var(--color-bg);
  color: var(--color-text-inverse);
}

.opiniones::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: clamp(160px, 26vw, 320px);
  /* Lavado con el color real de Productos (--color-plum-deep, RGB
     31,16,25 en el rgba). */
  background: linear-gradient(to bottom, var(--color-plum-deep) 0%, rgba(31, 16, 25, 0.55) 45%, transparent 100%);
  pointer-events: none;
  /* z-index: -1 — ver comentario en .servicios::before: sin esto tapaba
     el encabezado de la sección en vez de quedar detrás. */
  z-index: -1;
}

.contacto {
  --color-bg: var(--color-plum);
  --color-text-muted: var(--color-text-inverse-muted);
  --color-border: var(--color-border-dark);
  --color-surface: rgba(255, 255, 255, 0.06);
  --color-gold-deep: var(--color-blush-soft);
  position: relative;
  /* z-index:0 — ver comentario en .servicios: contiene el z-index:-1
     de .contacto::before dentro de esta sección. */
  z-index: 0;
  background: var(--color-bg);
  color: var(--color-text-inverse);
}

.contacto::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: clamp(160px, 26vw, 320px);
  /* Lavado con el color real de Opiniones (--color-ash-cool, RGB
     21,23,27 en el rgba). */
  background: linear-gradient(to bottom, var(--color-ash-cool) 0%, rgba(21, 23, 27, 0.55) 45%, transparent 100%);
  pointer-events: none;
  /* z-index: -1 — ver comentario en .servicios::before: sin esto tapaba
     el encabezado de la sección en vez de quedar detrás. */
  z-index: -1;
}

/* ---------- Nuestra Paleta de Color (esmaltes + tintes) ---------- */
.paleta-color__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 3rem;
}

.paleta-color__block {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

.paleta-color__subtitle {
  margin: 0 0 0.4rem;
}

.paleta-color__hint {
  color: var(--color-text-muted);
  margin: 0 0 1.5rem;
  font-size: 0.95rem;
}

/* Gancho + CTA secundario debajo de cada carrusel de 12 miniaturas:
   invita a pedir el catálogo completo por WhatsApp sin competir con
   el "Reserva cita" del sitio (ese es el CTA principal, relleno y con
   el pulso de neón). Aquí, contorno + tono más suave + sin animación
   — visible pero deliberadamente secundario. */
.paleta-color__cta {
  margin-top: 1.75rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.65rem;
}

.paleta-color__cta-text {
  margin: 0;
  font-size: 0.85rem;
  color: var(--color-text-muted);
  max-width: 30ch;
}

/* Botón "Descúbrelos" (Esmaltes/Tintes): mismo componente PixelCanvas
   (círculo/píldora cristal azul-oscuro + destellos blanco-azulados al
   hover) que las flechas de navegación de ColorCarousel — ver
   .color-carousel__nav en css/color-carousel.css, mismos valores de
   fondo/blur/borde/sombra, aquí en forma de píldora con texto en vez
   de círculo con glifo. Reutiliza el <pixel-canvas> ya registrado
   globalmente por js/pixel-canvas.js, sin JS adicional. */
.btn-pixel-pill {
  position: relative;
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 0.65rem 1.75rem;
  border-radius: 999px;
  background: rgba(16, 24, 37, 0.55);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.2);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
  color: #F2F8FC;
  text-decoration: none;
  cursor: pointer;
  transition: border-color var(--transition-base);
}

.btn-pixel-pill pixel-canvas {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.btn-pixel-pill:hover {
  border-color: rgba(255, 255, 255, 0.4);
}

.btn-pixel-pill:hover .btn-pixel-pill__label {
  color: #EAF3FA;
}

.btn-pixel-pill__label {
  position: relative;
  z-index: 1;
  transition: color var(--transition-base);
}

@media (min-width: 768px) {
  .paleta-color__grid {
    /* Lado a lado, con un divisor sutil (border) en vez de mezclarse:
       siguen leyéndose como dos bloques dentro de la misma sección. */
    grid-template-columns: 1fr 1fr;
    gap: 2.5rem;
    align-items: start;
  }

  .paleta-color__block--tintes {
    border-left: 1px solid var(--color-border);
    padding-left: 2.5rem;
  }

  /* Cada bloque ocupa la mitad del ancho: se achican los círculos
     respecto a la versión de una sola columna para que las 2 filas
     de 6 miniaturas quepan sin apretarse. */
  .paleta-color__grid .color-carousel__stage {
    width: clamp(170px, 24vw, 230px);
    height: clamp(170px, 24vw, 230px);
  }

  .paleta-color__grid .color-carousel__thumb {
    width: 48px;
    height: 48px;
  }

  .paleta-color__grid .color-carousel__thumbs {
    grid-template-columns: repeat(6, 48px);
    gap: 1.75rem 0.7rem;
  }
}

.section-header {
  max-width: 640px;
  margin-bottom: 2.5rem;
}

.eyebrow {
  font-family: var(--font-body);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--color-gold-deep);
  margin: 0 0 0.6rem;
}

.section-intro {
  color: var(--color-text-muted);
}

/* ---------- Hero ---------- */
.hero {
  position: relative;
  min-height: 70vh;
  display: flex;
  align-items: center;
  color: var(--color-text-inverse);
}

.hero-media {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  /* Fallback visible solo una fracción de segundo mientras carga la
     foto (o si llegara a fallar la carga) — con "cover" de abajo la
     imagen ya tapa el 100% del área, este color no debería verse. */
  background: var(--color-wine);
}

.hero-media img {
  width: 100%;
  height: 100%;
  /* cover, no contain: la prioridad es cubrir el hero de borde a
     borde en cualquier pantalla, sin huecos. La foto actual es
     panorámica (1920x1080), así que el recorte de "cover" es leve —
     se sigue reconociendo casi toda la composición. */
  object-fit: cover;
  object-position: center;
  /* Escalada de más para dejar margen al desplazamiento del parallax
     sin descubrir bordes; --parallax-shift lo actualiza el scroll (JS). */
  transform: translateY(var(--parallax-shift, 0px)) scale(1.12);
  /* Punto medio pedido (3-5px): reconocible (mesas, sillas, espejos,
     plantas) pero suavizado. Menos blur = focos más definidos, de ahí
     el overlay algo más fuerte de abajo. */
  filter: blur(4px);
}

.hero-media::after {
  content: "";
  position: absolute;
  inset: 0;
  /* Única capa de oscurecimiento (se quitó el halo radial redundante
     que vivía en .hero-content — leía como una "máscara" ovalada de
     más). Recalculado para la foto actual (1920x1080, focos de techo +
     luz lavanda en los espejos, más brillantes que la foto anterior):
     a blur(4px) el subtítulo (color sólido) da ~5:1 en la franja donde
     vive el texto la mayor parte del tiempo, y el eyebrow dorado ~3.7:1
     ahí mismo — algo por debajo del mínimo AA de 4.5:1 en las zonas más
     brillantes, porque el dorado es un color intrínsecamente menos
     luminoso que el crema. Subir más el overlay para tapar eso también
     empezaba a verse demasiado oscuro para "reconocible"; queda como
     límite conocido en vez de forzarlo. */
  background: linear-gradient(180deg, rgba(28, 20, 15, 0.68), rgba(20, 12, 10, 0.92));
}

.hero-content {
  /* Texto centrado sobre la imagen difuminada, en vez de anclado abajo
     a la izquierda: logo, eyebrow, h1, subtítulo y botones quedan
     apilados y centrados. */
  width: 100%;
  padding-block: 3rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

.hero .eyebrow {
  color: var(--color-gold-light);
}

.hero h1 {
  font-size: var(--fs-h1);
  margin: 0 0 1rem;
}

.hero h1 em {
  color: var(--color-gold-light);
  font-style: italic;
}

.hero-logo {
  margin-bottom: 2rem;
}

.hero-logo-img {
  /* Notablemente más grande (antes 112px) — el del header (.logo-img,
     56px) queda igual. clamp() para que en móvil no se coma la mitad
     de la pantalla ni empuje demasiado el resto del contenido. */
  height: clamp(140px, 20vw, 200px);
  animation: logo-float 6s ease-in-out infinite;
}

.hero-subtitle {
  max-width: 560px;
  margin-bottom: 1.5rem;
  /* Color sólido (no --color-text-inverse-muted, que es translúcido):
     sobre una foto real con zonas brillantes, un texto semitransparente
     "hereda" parte de ese brillo y pierde contraste justo donde más
     falta hace. Ver nota de contraste en .hero-media::after. */
  color: var(--color-text-inverse);
}

.hero-actions {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
  justify-content: center;
}

/* "Ver servicios": misma píldora que "Reserva cita" (mismo
   border-radius, mismo padding heredado de .btn) pero sin relleno —
   solo contorno, texto crema, sin pulso ni barrido, un hover simple de
   relleno tenue. Escopeado a .hero para no afectar otros .btn-primary
   del sitio (p.ej. "Comprar aquí" en Productos). */
.hero .btn-primary {
  border-radius: 999px;
  color: var(--color-text-inverse);
  border-color: var(--color-text-inverse);
}

.hero .btn-primary:hover {
  background: rgba(255, 255, 255, 0.12);
  border-color: var(--color-text-inverse);
  color: var(--color-text-inverse);
}

/* ---------- Stats del hero ---------- */
/* Sin banda/visera ni contenedor propio: viven dentro de .hero-content,
   debajo de .hero-actions, con el mismo tratamiento tipográfico y de
   color que el resto del texto del hero (el número reutiliza el mismo
   dorado claro del eyebrow/h1 em; la etiqueta, el mismo crema
   translúcido del subtítulo). */
.hero-stats {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 2rem;
  list-style: none;
  margin: 2rem 0 0;
  padding: 0;
}

.hero-stats li {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

.stat-number {
  font-family: var(--font-heading);
  font-size: 1.6rem;
  font-weight: 600;
  color: var(--color-gold-light);
}

.stat-label {
  font-size: 0.8rem;
  color: var(--color-text-inverse-muted);
}

/* ---------- Servicios ---------- */
/* 25 servicios repartidos en 8 subcategorías (Peluquería: Secado,
   Corte, Color, Mechas/Reflejos — Estética: Masajes, Facial, Manicura,
   Pedicura), agrupadas bajo 2 encabezados, todo siempre visible (sin
   acordeón). Densidad ajustada a un punto medio: se probó una versión
   muy espaciosa (se sentía interminable con las 25 líneas reales) y
   luego una muy comprimida (se sentía apretada) — este es el balance
   entre ambas. Las líneas divisorias finas (1px) del segundo intento
   se mantienen tal cual, solo se recuperó aire en el espaciado y en
   .service-duration. Nada de nombre/tiempo/precio se abrevia ni oculta. */
.service-group + .service-group {
  margin-top: 2.5rem;
}

/* Antes este título (h3) tenía el mismo font-size que el h4 de cada
   categoría de abajo (Secado, Masajes...) — misma jerarquía visual,
   nada lo diferenciaba como encabezado de bloque. Ahora: tamaño
   notablemente mayor, versalitas con tracking amplio, dorado claro con
   un glow sutil (text-shadow), y una línea dorada que se desvanece a
   la derecha en vez del borde inferior recto de antes — más presencia
   de "título de sección", coherente entre Peluquería y Estética. */
.service-group-title {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  font-size: clamp(1.5rem, 2.6vw, 1.9rem);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-gold-light);
  text-shadow: 0 0 16px rgba(228, 205, 141, 0.3);
  margin: 0 0 1.75rem;
}

.service-group-title::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, var(--color-gold), transparent);
}

.service-categories {
  display: grid;
  /* auto-fit en vez de repeat(2, 1fr): con tarjetas de este ancho caben
     2-3 por fila en escritorio ancho — menos filas totales sin tocar
     el contenido de cada tarjeta. Colapsa solo a 1 columna ya de por sí
     en móvil (sin necesitar una media query aparte). */
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1.75rem 2.25rem;
  perspective: 1200px;
}

.service-category h4 {
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: var(--fs-h3);
  padding-bottom: 0.5rem;
  margin: 0 0 0.4rem;
  border-bottom: 1px solid var(--color-gold);
}

/* Solo Estética (Peluquería sigue con el auto-fit genérico de arriba):
   3 columnas por áreas nombradas en vez de un grid uniforme — Masajes
   (2 servicios) y Facial (3) se apilan en la primera columna, uno
   sobre otro, mientras Manicura y Pedicura (5 servicios cada una, la
   razón de este layout) quedan cada una en su propia columna alta al
   lado, en formato vertical simple — sin el mini-grid interno de 2
   columnas que se probó antes para estas dos y se revirtió. La altura
   combinada de Masajes+Facial queda así comparable a la de Manicura o
   Pedicura por sí solas, en vez de dejar una columna mucho más corta
   que las otras dos. Los 4 <article> son hijos directos en el mismo
   orden del HTML (Masajes, Facial, Manicura, Pedicura); nth-child fija
   cuál va en cada área. */
.service-categories--estetica {
  grid-template-columns: repeat(3, 1fr);
  grid-template-areas:
    "masajes  manicura pedicura"
    "facial   manicura pedicura";
}

.service-categories--estetica .service-category:nth-child(1) {
  grid-area: masajes;
}

.service-categories--estetica .service-category:nth-child(2) {
  grid-area: facial;
}

.service-categories--estetica .service-category:nth-child(3) {
  grid-area: manicura;
}

.service-categories--estetica .service-category:nth-child(4) {
  grid-area: pedicura;
}

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

.service-item {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  padding-block: 0.5rem;
  /* box-shadow en vez de border-bottom: misma línea fina de 1px, pero
     sin sumar altura a la caja de cada fila. */
  box-shadow: 0 1px 0 0 var(--color-border);
}

.service-name {
  /* Sin esto, un nombre largo ("Limar y esmaltar manos normal") no
     puede encogerse por debajo de su ancho de contenido dentro del
     flex row y desborda la tarjeta en móvil — con min-width:0 se
     parte en dos líneas en vez de desbordar. */
  min-width: 0;
}

.service-dots {
  flex: 1;
  border-bottom: 1px dotted var(--color-gold-light);
  margin-bottom: 0.35rem;
}

.service-duration {
  font-size: 0.8rem;
  color: var(--color-text-muted);
  white-space: nowrap;
}

.service-price {
  font-weight: 700;
  color: var(--color-accent);
  white-space: nowrap;
}

/* ---------- Galería ---------- */
.gallery-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 1rem;
  perspective: 1200px;
}

.gallery-item {
  margin: 0;
}

/* Contenedor de foto: cuadrado fijo (encuadre consistente entre fotos
   verticales y horizontales reales) con las imágenes apiladas encima
   unas de otras para los casilleros con más de una foto — el
   mini-carrusel alterna cuál queda con .is-active, y el cross-fade lo
   hace la propia transición de opacity/filter de abajo. Radio de
   esquina más orgánico (antes 2px) + sombra difuminada propia para que
   cada casillero se despegue del fondo de la sección. */
.gallery-photo {
  position: relative;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border-radius: 18px;
  background: var(--color-black-soft);
  box-shadow: 0 18px 40px rgba(13, 5, 9, 0.4);
}

/* Viñeta fotográfica: oscurece sutilmente las esquinas sin tapar el
   centro de la foto — por encima de las imágenes (z-index implícito
   por orden en el DOM) pero sin bloquear el cross-fade de abajo. */
.gallery-photo::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 70px 12px rgba(13, 5, 9, 0.55);
  pointer-events: none;
}

.gallery-photo img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* De vuelta a "cover" (revertido "contain"): prioridad es que el
     casillero quede siempre relleno de imagen, sin huecos, aunque
     recorte parte de la composición. */
  object-fit: cover;
  object-position: center;
  opacity: 0;
  /* Estado de reposo YA zoomeado (scale 1.1, no 1) con el paneo en su
     extremo opuesto al de .is-active: con "cover" la imagen ya cubre
     el 100% del casillero a escala 1, así que cualquier paneo sin
     zoom de sobra dejaría un borde vacío del lado contrario al
     desplazamiento. Partiendo ya zoomeada, sobra suficiente imagen
     alrededor para paneársela sin descubrir nunca el fondo. */
  transform: scale(1.1) translate(1.5%, -1%);
  /* "Destello difuminado" al cambiar de foto: entra desde un blur
     suave hacia foco nítido, mismo tratamiento que el blur-a-foco del
     overlay de resultado en el ColorCarousel (color-carousel.css).
     El transform (Ken Burns) usa una transición larga, de la misma
     duración que GALLERY_INTERVAL_MS en main.js: por transición (no
     @keyframes) el zoom se revierte suavemente si la foto se vuelve
     inactiva a mitad de camino, en vez de saltar de golpe al reinicio. */
  transition: opacity 1.1s ease, filter 1.1s ease, transform 4s ease-out;
}

.gallery-photo img.is-active {
  opacity: 1;
  filter: blur(0);
  /* Ken Burns: sigue zoomeando (1.1→1.2) mientras panea hacia el
     extremo opuesto — el mínimo de zoom en todo el recorrido es 1.1,
     de sobra para que el paneo (máximo 1.5%) nunca descubra el fondo
     del casillero. */
  transform: scale(1.2) translate(-1.5%, 1%);
}

/* Recepción: única foto de su casillero, nunca cicla — pero heredaba
   igual el zoom/paneo de la regla de arriba (is-active queda puesto
   desde la carga y nunca se saca), así que terminaba fija en un
   encuadre 20% más cerca de lo previsto, no realmente "estática".
   Encuadre propio y sin transform: queda fija de verdad, sin Ken Burns.
   object-position: 52% recorta la ventana de "cover" (900x900 sobre la
   foto de 1600x900) al rango horizontal ≈364px-1264px del archivo
   original: se pierde el pelo/lado izquierdo de la cara y todo el lado
   izquierdo de la sala, y queda visible la mitad derecha de su rostro
   (ojo, ceja, nariz y boca — reconocible, no un recorte irreconocible),
   su hombro con el logo, el hueco oscuro de la puerta y gran parte del
   monitor blanco del escritorio delante de ella. Si hace falta más
   monitor y menos cara (o al revés), subir/bajar este porcentaje
   corre la ventana hacia la derecha/izquierda en la misma proporción. */
.gallery-photo img.gallery-photo-recepcion {
  object-position: 52% center;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .gallery-photo img {
    transition: none;
  }

  .gallery-photo img.is-active {
    transform: none;
  }
}

.gallery-item figcaption {
  padding-top: 0.5rem;
  font-size: 0.9rem;
}

/* ---------- Opiniones ---------- */
/* Carrusel horizontal automático ("marquee"), no un grid estático: las
   8 reseñas reales viven UNA vez en el HTML (accesibles, indexables);
   js/main.js clona ese set completo (aria-hidden) y lo agrega a
   continuación para que el bucle de translateX(-50%) sea perfectamente
   continuo, sin salto — a los 50% del ancho total la copia duplicada
   queda pixel a pixel donde arrancó el set original. Solo si el
   usuario no pidió menos movimiento: sin esa clase (.has-marquee, la
   agrega el JS), la fila queda estática con scroll horizontal nativo
   — mismo fallback tanto sin JS como con prefers-reduced-motion. */
.testimonials-marquee-wrap {
  position: relative;
  overflow-x: auto;
  padding-block: 0.25rem;
}

.testimonials-marquee-wrap.has-marquee {
  overflow: hidden;
}

/* Difuminado en los bordes hacia el fondo de la sección (--color-bg,
   vino) en vez de un corte abrupto de las tarjetas — solo tiene
   sentido una vez el marquee está animado y recortado (overflow:
   hidden); sin él, el usuario necesita ver el borde real para saber
   que puede scrollear. */
.testimonials-marquee-wrap.has-marquee::before,
.testimonials-marquee-wrap.has-marquee::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: clamp(32px, 8vw, 110px);
  z-index: 2;
  pointer-events: none;
}

.testimonials-marquee-wrap.has-marquee::before {
  left: 0;
  background: linear-gradient(to right, var(--color-bg), transparent);
}

.testimonials-marquee-wrap.has-marquee::after {
  right: 0;
  background: linear-gradient(to left, var(--color-bg), transparent);
}

.testimonials-track {
  display: flex;
  gap: 1.1rem;
  width: max-content;
}

.has-marquee .testimonials-track {
  /* ~30-40s por vuelta completa: rango elegante, ni apurado ni
     tan lento que se sienta estático. */
  animation: testimonials-marquee 34s linear infinite;
}

/* Pausa al pasar el cursor — equivalente vanilla del
   group-hover:[animation-play-state:paused] del componente original. */
.testimonials-marquee-wrap:hover .testimonials-track {
  animation-play-state: paused;
}

@keyframes testimonials-marquee {
  from {
    transform: translateX(0);
  }
  to {
    /* La mitad del ancho total = exactamente un set completo, gracias
       al gap uniforme entre tarjetas (también entre la última del set
       original y la primera del clonado). */
    transform: translateX(-50%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .testimonials-track {
    animation: none !important;
  }
}

.testimonial {
  margin: 0;
  /* Ancho fijo y compacto (no un ítem de grid flexible): en un
     carrusel horizontal cada tarjeta necesita su propio tamaño
     predecible para que el bucle de translateX se calcule bien. */
  flex: 0 0 270px;
  width: 270px;
  padding: 1rem 1.1rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-top: 3px solid var(--color-gold);
  border-radius: var(--radius-sm);
}

.stars {
  margin: 0 0 0.4rem;
  font-size: 0.8rem;
  letter-spacing: 0.12em;
  color: var(--color-gold);
}

.rating-line {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.rating-line .stars {
  margin: 0;
}

.testimonial-text {
  margin: 0 0 0.65rem;
  font-size: 0.85rem;
  line-height: 1.45;
  /* Recorta a 3 líneas con "…" (antes 4): con 4 columnas en vez de 3,
     cada tarjeta es más angosta y las mismas 4 líneas ocupaban más
     alto de lo necesario. Las reseñas varían mucho de largo (de una
     frase a un párrafo) y sin este recorte las más largas estiraban
     toda la fila y alargaban la sección entera — justo lo que hacía
     que, con la disolución hacia Contacto, el contenido de más abajo
     se alcanzara a difuminar antes de leerse bien. Con la altura ya
     pareja entre tarjetas, ninguna reseña queda cerca de esa zona. El
     texto completo sigue en el HTML para lectores de pantalla. */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.testimonial-author {
  /* Serif (la misma familia de los títulos) para el nombre, sans-serif
     para el texto de la reseña (.testimonial-text ya hereda el body
     sans por defecto) — el mismo reparto tipográfico que usa el resto
     del sitio entre encabezados y texto de cuerpo. */
  font-family: var(--font-heading);
  font-size: 0.95rem;
  font-weight: 700;
  font-style: normal;
}

.testimonial-date {
  font-weight: normal;
  color: var(--color-text-muted);
}

.testimonial-tag {
  margin: 0.45rem 0 0;
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  /* --color-blush-deep (pensado para texto sobre fondos claros) da
     solo ~2.8:1 sobre el nuevo fondo oscuro de Opiniones — por eso usa
     el acento de la sección en vez de un color fijo. */
  color: var(--color-accent);
}

/* ---------- Productos (bento grid) ---------- */
/* Rejilla de 6 columnas: las 2 casillas "banner" ocupan las 6 (fila
   completa, más anchas que el resto) y las 6 casillas pequeñas ocupan 2
   cada una (3 por fila) — los spans siempre suman exactamente el ancho
   total de la rejilla, así que no quedan huecos sin encajar en ningún
   punto de corte. */
.products-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 0.85rem;
  /* Tope de ancho para que la rejilla completa se lea comedida, no
     agresiva, dentro del .container de 1200px — mismas proporciones
     relativas (span 6 vs span 2), solo a menor escala absoluta. */
  max-width: 860px;
  margin-inline: auto;
  perspective: 1200px;
}

.product-card {
  position: relative;
  grid-column: span 2;
  aspect-ratio: 1 / 1;
  border-radius: 18px;
  overflow: hidden;
  isolation: isolate;
  /* Viñeta sutil en el tono café ya establecido en Productos
     (--color-black-soft, RGB 36,28,23 — el mismo lavado que usa
     .productos::before) en vez de un negro puro, para que la sombra se
     sienta parte de la misma paleta cálida de la sección. */
  box-shadow: 0 10px 26px rgba(36, 28, 23, 0.4);
  transition: box-shadow var(--transition-base), transform var(--transition-base);
}

.product-card--wide {
  grid-column: span 6;
  aspect-ratio: 21 / 9;
}

.product-card:hover {
  box-shadow: 0 14px 34px rgba(36, 28, 23, 0.5);
}

.product-card__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* --img-zoom: gancho por-imagen para recortar más allá de lo que
     hace "cover" por sí solo (ver .product-card__img--tight-crop). */
  transform: scale(var(--img-zoom, 1));
  transition: transform 0.5s ease;
}

.product-card:hover .product-card__img {
  transform: scale(calc(var(--img-zoom, 1) * 1.06));
}

/* Envuelve la foto: dispara la vista ampliada (ver product-lightbox.js
   más abajo). Invisible por sí mismo — sin esto la foto no sería un
   elemento enfocable/accionable por teclado. */
.product-card__trigger {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
  display: block;
}

/* La foto de Sens.us Diamond Liss Pack (cuadrada) trae su propio marco
   negro redondeado ya "horneado" en el archivo. Al encajarla en la
   casilla ancha, "cover" solo recorta arriba/abajo (la casilla es más
   ancha que alta que la propia foto cuadrada), así que ese marco
   sobrevive intacto en los bordes izquierdo/derecho y se lee como un
   hueco negro. Un zoom extra recorta también esos bordes sin perder
   los productos, que ocupan el bloque central de la composición. */
.product-card__img--tight-crop {
  --img-zoom: 1.16;
}

.product-card__overlay {
  position: absolute;
  inset: auto 0 0 0;
  display: flex;
  flex-direction: row;
  align-items: center;
  /* flex-start (no space-between): el nombre ya no crece para ocupar
     todo el ancho de la fila, así que el botón queda pegado a él con
     el mismo gap compacto en las casillas pequeñas Y en las anchas,
     en vez de irse al extremo opuesto de la casilla. */
  justify-content: flex-start;
  gap: 0.65rem;
  padding: 0.8rem 0.75rem 0.7rem;
  /* Degradado a negro puro (no el café/dorado de la viñeta exterior):
     más contraste y limpieza detrás del texto blanco. */
  background: linear-gradient(to top, rgba(0, 0, 0, 0.92) 0%, rgba(0, 0, 0, 0.5) 55%, transparent 100%);
  transition: background var(--transition-base);
  /* No intercepta clics: deja pasar hasta .product-card__trigger (la
     foto, debajo) en toda la franja, salvo donde el propio carrito
     reclama el puntero abajo. Así "clic en la casilla" (incluida la
     zona del nombre) abre la vista ampliada, y solo el carrito abre
     WhatsApp. */
  pointer-events: none;
}

.product-card:hover .product-card__overlay {
  background: linear-gradient(to top, rgba(0, 0, 0, 0.95) 0%, rgba(0, 0, 0, 0.6) 60%, transparent 100%);
}

.product-card__name {
  margin: 0;
  color: var(--color-text-inverse);
  font-size: 0.72rem;
  line-height: 1.3;
  font-weight: 600;
  /* No crece (flex-grow: 0): su caja se ajusta a su propio contenido
     en vez de estirarse a todo el ancho de la fila, que era lo que
     alejaba el botón del texto en las casillas anchas. Sigue
     pudiendo encogerse (flex-shrink: 1) y pasar a 2 líneas si no cabe. */
  flex: 0 1 auto;
  min-width: 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.product-card--wide .product-card__name {
  font-size: 0.88rem;
  max-width: 55ch;
}

/* Botón carrito: mismo componente PixelCanvas (círculo cristal
   azul-oscuro + destellos blanco-azulados al hover) que las flechas
   de navegación de ColorCarousel (ver .color-carousel__nav en
   color-carousel.css) — solo cambia el glifo central por un ícono de
   carrito. Reutiliza el <pixel-canvas> ya registrado globalmente por
   js/pixel-canvas.js, sin necesidad de JS adicional aquí. */
.product-card__cta {
  position: relative;
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: rgba(16, 24, 37, 0.55);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.2);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
  color: #F2F8FC;
  text-decoration: none;
  cursor: pointer;
  /* Único hijo del pie que sigue reclamando el puntero (ver
     .product-card__overlay): el clic aquí abre WhatsApp, no la vista
     ampliada. */
  pointer-events: auto;
  transition: border-color var(--transition-base);
}

.product-card__cta pixel-canvas {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.product-card__cta:hover {
  border-color: rgba(255, 255, 255, 0.4);
}

.product-card__cta:hover .product-card__cta-icon {
  transform: scale(1.18);
  color: #EAF3FA;
}

.product-card__cta-icon {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  transition: transform var(--transition-base), color var(--transition-base);
}

.product-card__cta-svg {
  width: 15px;
  height: 15px;
  fill: currentColor;
}

.productos-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1.5rem;
}

.productos-header .section-intro {
  margin-bottom: 0;
}

/* ---------- Productos: vista ampliada (lightbox) ---------- */
/* Mismo mecanismo que la capa de resultado de ColorCarousel (ver
   .color-carousel__overlay* en css/color-carousel.css y su lógica
   ensure/open/close en js/color-carousel.js): fondo desenfocado que
   funde su opacidad, panel que expande de scale(0.65) a 1, foto que
   pasa de blur(18px) a nítida — mismas duraciones/easings ya afinadas
   allí. Aquí no hay secuencia de despiece: el clic en la casilla abre
   esta capa directamente con la foto ya cargada (ver
   js/product-lightbox.js). */
.product-lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}

.product-lightbox[hidden] {
  display: none;
}

.product-lightbox__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(28, 23, 18, 0.5);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  opacity: 0;
  transition: opacity 0.55s ease-in-out;
}

.product-lightbox__frame {
  position: relative;
  max-width: min(560px, 90vw);
  width: 100%;
}

/* THERAPY (producto-01) y Sens.us Diamond Liss Pack (producto-03): se
   veían chicas dentro de la capa (la primera por su aspecto panorámico,
   la segunda por el margen de su propia composición) — capa algo más
   grande solo para estas 2, vía data-lightbox-large en el trigger (ver
   js/product-lightbox.js). El resto conserva el tamaño ya afinado. */
.product-lightbox.is-large .product-lightbox__frame {
  max-width: min(760px, 94vw);
}

.product-lightbox.is-large .product-lightbox__img {
  max-height: 68vh;
}

.product-lightbox__panel {
  background: var(--color-surface);
  border: none;
  box-shadow: 0 20px 50px rgba(20, 15, 10, 0.28), 0 0 26px 6px rgba(176, 141, 63, 0.22);
  border-radius: var(--radius-sm);
  padding: 0.75rem;
  opacity: 0;
  transform: scale(0.65);
  transition: opacity 0.45s ease-out, transform 0.45s ease-out;
}

.product-lightbox.is-open .product-lightbox__backdrop {
  opacity: 1;
}

.product-lightbox.is-open .product-lightbox__panel {
  opacity: 1;
  transform: scale(1);
}

.product-lightbox.is-open .product-lightbox__close {
  opacity: 1;
  transform: scale(1);
}

.product-lightbox__close {
  position: absolute;
  top: -18px;
  right: -18px;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  box-shadow: 0 4px 12px rgba(28, 23, 18, 0.25);
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
  /* Mismo fundido + escala que el panel (opacity/transform 0.45s
     ease-out): entran y salen como una sola unidad visual, sin
     desfase entre foto y botón. */
  opacity: 0;
  transform: scale(0.65);
  transition: background-color var(--transition-base), color var(--transition-base),
    opacity 0.45s ease-out, transform 0.45s ease-out;
}

.product-lightbox__close:hover {
  background: var(--color-gold);
  color: var(--color-black);
}

.product-lightbox__img {
  display: block;
  width: 100%;
  max-height: 60vh;
  object-fit: contain;
  border-radius: var(--radius-sm);
  filter: blur(18px);
  transition: filter 0.45s ease-out;
}

.product-lightbox.is-open .product-lightbox__img {
  filter: blur(0);
}

.product-lightbox__caption {
  margin: 0.75rem 0 0.15rem;
  text-align: center;
  color: var(--color-text);
  font-weight: 600;
  font-size: 0.95rem;
}

@media (prefers-reduced-motion: reduce) {
  .product-lightbox__backdrop,
  .product-lightbox__panel,
  .product-lightbox__close,
  .product-lightbox__img {
    transition: none !important;
  }
}

/* ---------- Contacto ---------- */
/* Áreas nombradas (no solo 2 columnas): el título/intro ocupa la fila
   de arriba a todo el ancho, y la fila de abajo pone el bloque de
   datos (Dirección/Teléfono/Horario) y el mapa como dos columnas que
   arrancan en la misma línea — así el mapa queda a la altura de ese
   bloque en vez de a la altura del título, que es más alto/corto
   según el idioma y descuadraba la alineación visual. */
.contacto-grid {
  display: grid;
  grid-template-columns: 1.1fr 1fr;
  grid-template-areas:
    "heading heading"
    "info    map";
  column-gap: 3rem;
  row-gap: 1.5rem;
  align-items: start;
}

.contacto-heading {
  grid-area: heading;
}

.contacto-info {
  grid-area: info;
}

.contact-details {
  list-style: none;
  margin: 1.5rem 0;
  padding: 0;
}

.contact-details li {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  padding-block: 0.75rem;
}

/* Divisor sutil entre bloques (no en el primero): usa --color-border,
   que .contacto ya redefine a --color-border-dark (crema translúcido)
   para leerse bien sobre el fondo vino-lila de la sección. */
.contact-details li + li {
  border-top: 1px solid var(--color-border);
}

/* Etiqueta ("Dirección"/"Teléfono"/"Horario"): dorado claro (el tono
   pensado para fondos oscuros, no --color-gold-deep que es para
   fondos claros), serif de títulos, semi-negrita, versalitas con
   letter-spacing amplio y un tamaño algo mayor que el dato de abajo —
   la jerarquía etiqueta > contenido que pedía el diseño. El icono
   hereda el mismo color vía fill: currentColor. */
.contact-details__label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 1rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-gold-light);
}

.contact-details__icon {
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
  fill: currentColor;
}

/* Dato real: deliberadamente discreto (crema atenuado, peso normal,
   tamaño de cuerpo) para no competir con la etiqueta — alineado con
   el texto de la etiqueta, no con el icono (de ahí el padding-left). */
.contact-details__value {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 0.95rem;
  color: var(--color-text-inverse-muted);
  padding-left: calc(16px + 0.5rem);
}

.contact-details__value a {
  color: inherit;
  text-decoration: none;
  transition: color var(--transition-base);
}

.contact-details__value a:hover {
  color: var(--color-gold-light);
}

.social-links {
  display: flex;
  gap: 1rem;
  margin-top: 1.5rem;
}

.social-links a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--color-gold-deep);
  text-decoration: none;
  transition: color var(--transition-base), opacity var(--transition-base);
}

.social-links a:hover {
  /* Antes burdeos fijo: sobre el nuevo fondo vino-lila de Contacto
     apenas daba 1.3:1 (burdeos sobre un fondo ya de por sí oscuro y
     rojizo). Dorado claro sí se distingue del reposo (blush-soft) y
     contrasta bien contra el nuevo fondo. */
  color: var(--color-gold-light);
  opacity: 0.85;
}

.social-links svg {
  width: 22px;
  height: 22px;
  fill: currentColor;
}

.contacto-map {
  /* Solo la posición dentro del grid: el tamaño/estilo visual vive en
     .contacto-map-inner (ver más abajo). Bug real de Chrome/Android:
     un iframe de origen cruzado (Google Maps) deja de responder al
     touch (arrastre/pinch) cuando su padre DIRECTO es un ítem de CSS
     Grid con grid-area — probado y confirmado en aislamiento. Meter un
     bloque normal (.contacto-map-inner, sin grid) entre el ítem del
     grid y el <iframe> resuelve el touch sin tocar el layout visual. */
  grid-area: map;
}

.contacto-map-inner {
  position: relative;
  /* Rectangular horizontal (antes cuadrado 380x380 ≈ 144 400px² de
     área): 16:9 a 500px de ancho ≈ 140 600px² de área, prácticamente
     la misma escala, solo cambia el ratio. max-width + margin-inline:
     auto lo centra dentro de su columna del grid en vez de estirarlo
     a todo el ancho disponible. El zoom/arrastre del embed no dependen
     del tamaño del iframe — sigue siendo completamente interactivo. */
  aspect-ratio: 16 / 9;
  width: 100%;
  max-width: 500px;
  margin-inline: auto;
  background: var(--color-surface);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.contacto-map-inner iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

/* ---------- Footer ---------- */
.site-footer {
  /* Gris ceniza oscuro difuminado (misma familia neutra que
     --color-ash-cool, #15171B, ya usada en "Colores a tu Medida" y
     Opiniones) en vez del negro cálido/café de --color-black —
     degradado sutil de arriba a abajo para que no se lea como un
     plano sólido. Contraste verificado: el texto claro
     (--color-text-inverse / -muted, sobre --color-cream) sigue de
     sobra por encima de WCAG AA contra ambos extremos, casi tan
     oscuros como --color-black. */
  background: linear-gradient(180deg, #1B1E23 0%, #0E1013 100%);
  color: var(--color-text-inverse);
  padding-block: 0.7rem;
}

.footer-inner {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 0.4rem 1.2rem;
}

.footer-logo {
  display: inline-block;
  line-height: 0;
}

.footer-logo-img {
  width: 82px;
  height: auto;
  max-width: 100%;
}

.footer-brand p {
  color: var(--color-text-inverse-muted);
  font-size: 0.7rem;
  margin-top: 0.2rem;
}

/* Jerarquía deliberadamente más discreta que el nav del header (letra
   más chica, color apagado en reposo, separadores en punto en vez de
   ítems tipo botón/píldora): que se lea como pie de página, no como
   una segunda barra de navegación principal. El punto reutiliza el
   mismo separador "·" que ya aparece en la línea de valoración de
   Opiniones (★★★★★ 4,7 · 45 reseñas), para mantener un único lenguaje
   visual de separador en todo el sitio. */
.footer-nav {
  display: flex;
  gap: 0.2rem 0.5rem;
  flex-wrap: wrap;
  align-items: center;
}

.footer-nav a {
  text-decoration: none;
  font-size: 0.66rem;
  letter-spacing: 0.02em;
  color: var(--color-text-inverse-muted);
  transition: color var(--transition-base);
}

.footer-nav a:hover {
  color: var(--color-gold-light);
}

.footer-nav a:not(:last-child)::after {
  content: "·";
  display: inline-block;
  margin-left: 0.5rem;
  color: var(--color-border-dark);
}

/* Franja final: copyright a la izquierda y enlaces legales a la
   derecha, en la misma línea (en vez de apilados) — así la línea legal
   no agrega una fila extra de alto al footer ya afinado. */
.footer-bottom {
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.2rem 1rem;
}

.footer-copy {
  color: var(--color-text-inverse-muted);
  font-size: 0.64rem;
  margin: 0;
}

/* Línea legal: un nivel más discreto que .footer-copy (gris claro
   neutro, no el crema cálido de --color-text-inverse-muted que ya usa
   el resto del footer) para que no compita con la fila principal ni
   con el dorado de marca — solo enlaces de trámite. Alineada a la
   derecha de la franja final y con la letra más chica de todo el
   footer, para ocupar el mínimo espacio posible. */
.footer-legal {
  margin: 0;
  text-align: right;
  font-size: 0.52rem;
  letter-spacing: 0.02em;
  color: rgba(210, 212, 216, 0.6);
}

.footer-legal a {
  color: inherit;
  text-decoration: none;
  transition: color var(--transition-base);
}

.footer-legal a:hover {
  color: rgba(255, 255, 255, 0.9);
}

/* ---------- Scroll & parallax 3D (Fase 3) ---------- */
/* .reveal: aparición simple (fundido + ascenso). Se usa en cabeceras de
   sección y en el bloque de contacto. */
.reveal {
  opacity: 0;
  transform: translateY(32px);
  transition: opacity 0.7s ease, transform 0.7s ease;
  transition-delay: var(--reveal-delay, 0s);
}

.reveal.is-visible {
  opacity: 1;
  /* transform: none (no translateY(0)): un transform "en cero" sigue
     siendo un transform real y crea una capa de composición propia.
     Sobre .contacto-map eso rompía el touch (arrastre/pinch) del
     iframe de Google Maps en móvil — bug conocido de Chrome/Android
     con iframes de origen cruzado dentro de un ancestro con transform,
     aunque sea un no-op. none limpia la propiedad del todo y anima
     igual (los navegadores interpolan transform → none con
     normalidad). */
  transform: none;
}

/* .reveal-3d: aparición con profundidad (la tarjeta "se endereza" al
   entrar en pantalla). Requiere `perspective` en el contenedor de la
   rejilla para que el giro se perciba en 3D y no como un simple estiro.
   Una vez visible, --tilt-x/--tilt-y permiten además la inclinación
   interactiva al mover el cursor (galería y productos). */
.reveal-3d {
  opacity: 0;
  transform: translateY(28px) rotateX(12deg);
  transform-origin: bottom center;
  transition: opacity 0.7s ease, transform 0.7s ease;
  transition-delay: var(--reveal-delay, 0s);
}

.reveal-3d.is-visible {
  opacity: 1;
  transform: translateY(0) rotateX(var(--tilt-x, 0deg)) rotateY(var(--tilt-y, 0deg));
}

@media (prefers-reduced-motion: reduce) {
  .reveal,
  .reveal-3d {
    opacity: 1;
    filter: none;
    transform: none;
    transition: none;
  }

  .hero-media img {
    transform: scale(1.12);
  }

  .site-nav .btn-whatsapp,
  .hero-actions .btn-whatsapp,
  .contacto-info .btn-whatsapp {
    animation: none;
    box-shadow:
      0 0 0 1px rgba(228, 205, 141, 0.45),
      0 0 6px 1px rgba(176, 141, 63, 0.5);
  }

  /* Sin barrido ni levantamiento: solo el cambio de color/sombra que
     ya trae :hover de por sí, sin movimiento. Misma especificidad que
     las reglas de arriba y declarada después → gana pese a estar
     dentro de la media query. */
  .site-nav .btn-whatsapp:hover,
  .hero-actions .btn-whatsapp:hover,
  .contacto-info .btn-whatsapp:hover {
    animation: none;
    transform: none;
  }

  .site-nav .btn-whatsapp::before,
  .hero-actions .btn-whatsapp::before,
  .contacto-info .btn-whatsapp::before {
    display: none;
  }

  .productos-header .btn-primary {
    animation: none;
    box-shadow:
      0 0 0 1px rgba(228, 205, 141, 0.45),
      0 0 6px 1px rgba(176, 141, 63, 0.5);
  }

  .productos-header .btn-primary:hover {
    animation: none;
    transform: none;
  }

  .productos-header .btn-primary::before {
    display: none;
  }
}

/* ---------- Responsive ---------- */
@media (max-width: 900px) {
  /* Las 3 columnas por áreas de .service-categories--estetica
     necesitan más ancho que el auto-fit genérico de Peluquería (cada
     columna sostiene hasta 5 servicios) — por debajo de este punto se
     aprietan demasiado, así que colapsan a 1 columna apilada en el
     mismo orden del HTML: Masajes, Facial, Manicura, Pedicura. */
  .service-categories--estetica {
    grid-template-columns: 1fr;
    grid-template-areas:
      "masajes"
      "facial"
      "manicura"
      "pedicura";
  }
}

@media (max-width: 860px) {
  .contacto-grid {
    grid-template-columns: 1fr;
    grid-template-areas:
      "heading"
      "info"
      "map";
  }
}

@media (max-width: 720px) {
  .nav-toggle {
    display: flex;
  }

  .site-nav {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    /* Mismo fondo oscuro semitransparente que el header (con blur
       propio, por si el header y el panel llegan a verse por separado
       en algún salto de scroll), para que el texto claro del menú
       siga siendo legible al desplegarse en móvil. */
    background: rgba(13, 5, 9, 0.98);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    border-bottom: 1px solid var(--color-border-dark);
    display: none;
  }

  .site-nav.is-open {
    display: flex;
  }

  .site-nav ul {
    flex-direction: column;
    gap: 0;
    padding: 1rem 1.5rem 0;
  }

  .site-nav li {
    border-bottom: 1px solid var(--color-border-dark);
  }

  .site-nav a {
    display: block;
    padding: 0.9rem 0;
  }

  .site-nav .btn-whatsapp {
    margin: 1rem 1.5rem 1.5rem;
    text-align: center;
  }

  /* Bento de Productos: de 3 pequeñas por fila a 2, manteniendo el
     banner a todo el ancho (los spans se recalculan sobre 2 columnas en
     vez de 6, siguen sumando el total exacto). */
  .products-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 0.9rem;
  }

  .product-card {
    grid-column: span 1;
  }

  .product-card--wide {
    grid-column: span 2;
    aspect-ratio: 16 / 9;
  }
}

@media (max-width: 420px) {
  /* Por debajo de este ancho hasta 2 columnas se sienten apretadas:
     todo pasa a una sola columna, banner y pequeñas por igual. */
  .products-grid {
    grid-template-columns: 1fr;
  }

  .product-card,
  .product-card--wide {
    grid-column: span 1;
    aspect-ratio: 4 / 3;
  }
}
