/* ===========================
   0. RESET & BASE
   =========================== */

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

html {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* Tema oscuro fijo: sin esto el navegador pinta en claro lo que la hoja no
     controla (barra de scroll, autocompletado de formulario, controles
     nativos). Ver D25. */
  color-scheme: dark;
  /* Compensa el nav fijo (~96px en escritorio, mismo numero que
     `.page-main`, ver D15) al saltar a un ancla o al mover el foco por
     teclado: sin esto el destino queda tapado. Cubre los dos casos a la vez,
     por eso no se usa scroll-margin-top por encabezado. Ver D24. */
  scroll-padding-top: 100px;
}

body {
  font-family: var(--font-body);
  background-color: var(--color-dark);
  color: var(--color-beige);
  line-height: 1.6;
  overflow-x: hidden;
}

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

a {
  text-decoration: none;
  color: inherit;
  -webkit-tap-highlight-color: var(--color-burgundy-dim);
}

ul, ol {
  list-style: none;
}

button {
  border: none;
  background: none;
  cursor: pointer;
  font-family: inherit;
  color: inherit;
  -webkit-tap-highlight-color: var(--color-burgundy-dim);
}

input, textarea, select {
  font-family: inherit;
  font-size: inherit;
  color: inherit;
  border: none;
  outline: none;
  background: none;
}

/* Corta el zoom por doble toque en Safari de iOS sobre cualquier control
   clicable o tactil. Va en el reset porque es una regla del proyecto para
   TODO lo clicable, no de un componente. Ver D27. */
a, button, label, input, select, textarea, [role="button"] {
  touch-action: manipulation;
}

/* ===========================
   1. CUSTOM PROPERTIES
   =========================== */

:root {
  /* Brand palette */
  --color-dark: #332E28;
  --color-dark-alt: #2a2520;
  --color-dark-deep: #241f1b;
  --color-burgundy: #7C2F34;
  --color-burgundy-light: #943a40;
  --color-burgundy-dim: rgba(124, 47, 52, 0.15);
  /* Burgundy aclarado hasta pasar AA sobre fondo oscuro: es el burgundy legible
     como texto. Lo usan el asterisco de campo obligatorio y el estado de error. */
  --color-burgundy-pale: #E7A0A2;
  --color-tan: #B19B86;
  --color-tan-dim: rgba(177, 155, 134, 0.15);
  --color-beige: #D5C7B9;
  --color-gray: #D8D8D8;
  /* Texto apagado que SI cumple AA en todos los fondos donde se usa (D3):
     4.65:1 sobre --color-dark (el mas claro), 5.20:1 sobre la tarjeta del
     formulario, 5.25:1 sobre --color-dark-alt y 5.65:1 sobre
     --color-dark-deep. Mismo tono calido que el #8a7e72 anterior (3.40:1),
     solo mas claro. */
  --color-text-muted: #A2968A;
  --color-border: rgba(177, 155, 134, 0.12);
  --color-overlay: rgba(51, 46, 40, 0.85);

  /* Typography */
  --font-display: 'Cormorant Garamond', Georgia, 'Times New Roman', serif;
  --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;

  /* Fluid type scale. Cada clamp() lleva ahora una base en rem ademas del
     termino vw, para que el minimo ya escale desde ~360px de ancho (antes
     ningun clamp() se movia por debajo de 640px, ver deuda D6 historica en
     docs/ESTILO.md). El maximo de cada token se mantiene identico y se
     alcanza en el mismo ancho de escritorio que antes: ningun titular de
     escritorio cambia. */
  --text-hero: clamp(2.84rem, 1.4rem + 6.4vw, 7rem);
  --text-display: clamp(1.85rem, 1rem + 3.75vw, 4rem);
  --text-heading: clamp(1.3rem, 0.9rem + 1.8vw, 2.25rem);
  --text-subhead: clamp(0.88rem, 0.75rem + 0.6vw, 1.25rem);
  --text-body: clamp(1.01rem, 1rem + 0.048vw, 1.05rem);
  --text-small: 0.85rem;
  --text-micro: 0.7rem;

  /* Spacing */
  --space-xs: 0.25rem;
  --space-sm: 0.5rem;
  --space-md: 1rem;
  --space-lg: 2rem;
  --space-xl: 4rem;
  --space-2xl: 6rem;
  --space-3xl: 8rem;

  /* Layout */
  --container-max: 1200px;
  --container-wide: 1400px;

  /* Radio de pildora: un solo valor para todo control redondeado (D10).
     999px y no 50px: da pildora a cualquier alto, tambien cuando el texto
     envuelve y el boton pasa de 100px. */
  --radius-pill: 999px;

  /* Transitions */
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --duration-fast: 0.3s;
  --duration-normal: 0.6s;
  --duration-slow: 1.2s;
}

/* ===========================
   2. TYPOGRAPHY
   =========================== */

.font-display {
  font-family: var(--font-display);
}

.font-body {
  font-family: var(--font-body);
}

/* ===========================
   3. LAYOUT UTILITIES
   =========================== */

.container {
  width: 100%;
  max-width: var(--container-max);
  margin: 0 auto;
  padding: 0 var(--space-lg);
}

.container--wide {
  max-width: var(--container-wide);
}

/* Section spacing */
.section {
  padding: 5rem 0;
}

/* Section header pattern */
.section-header {
  text-align: center;
  margin-bottom: var(--space-xl);
}

.section-eyebrow {
  font-family: var(--font-body);
  font-size: var(--text-micro);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.3em;
  color: var(--color-tan);
  margin-bottom: var(--space-md);
  display: block;
}

.section-title {
  font-family: var(--font-display);
  font-size: var(--text-display);
  font-weight: 400;
  line-height: 1.15;
  color: var(--color-beige);
  margin-bottom: var(--space-lg);
}

.section-line {
  width: 60px;
  height: 2px;
  background: var(--color-burgundy);
  margin: 0 auto;
}

/* Parrafo de apoyo bajo un .section-header.
   Es la version neutra de `.hero__subtitle`: mismo papel —la linea que explica
   que pasa despues— pero sin quedar atada al hero. Sirve en cualquier
   `.section`, que es justo lo que faltaba en la seccion de cierre de index.
   Color: `--color-beige` (8.13:1 sobre `--color-dark`), no `--color-text-muted`
   (4.65:1: pasa AA, pero con poco margen para un parrafo que vende).
   El efecto de "apagado" frente
   al titular lo da el peso 300, no un color mas flojo.
   El ancho de medida son los mismos 520px de `.hero__subtitle` (no hay token
   de medida en la hoja), que a `--text-body` dan ~70 caracteres por linea. */
.section-header__text {
  font-family: var(--font-body);
  font-size: var(--text-body);
  font-weight: 300;
  line-height: 1.7;
  color: var(--color-beige);
  text-align: center;
  max-width: 520px;
  margin: var(--space-lg) auto 0;
}

/* Texto solo para lector de pantalla: recortado, nunca `display: none` ni
   `visibility: hidden`, que lo sacarian tambien del arbol de accesibilidad.
   Mismo recorte que `.nav__menu-toggle` en celular (seccion 5). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Enlace "Saltar al contenido" (P2.15): primer hijo del <body> en las 9
   paginas, apunta a `<main id="contenido" tabindex="-1">`.
   Fuera de pantalla con `transform`, nunca con `display: none` ni
   `visibility: hidden`: tiene que seguir siendo enfocable y legible para el
   lector. Con foco baja arriba a la izquierda, por encima del nav fijo
   (1000), la hamburguesa (1001) y el lightbox (2000).
   Mismo par que `.btn--primary`: `--color-beige` sobre `--color-burgundy`,
   5.49:1. Pildora y tipografia de `.btn`; 44px de alto minimo. El anillo es
   el de `.btn:focus-visible`. Se muestra con `:focus` y no solo con
   `:focus-visible` para que ningun navegador lo enfoque sin ensenarlo. */
.skip-link {
  position: fixed;
  top: var(--space-sm);
  left: var(--space-sm);
  z-index: 2100;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0.6rem 1.5rem;
  font-family: var(--font-body);
  font-size: var(--text-small);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--color-beige);
  background: var(--color-burgundy);
  border: 1.5px solid var(--color-burgundy);
  border-radius: var(--radius-pill);
  transform: translateY(calc(-100% - var(--space-md)));
  transition: transform var(--duration-fast) var(--ease-out-expo);
}

.skip-link:focus {
  transform: translateY(0);
  outline: none;
}

.skip-link:focus-visible {
  outline: 2px solid var(--color-tan);
  outline-offset: 3px;
}

/* Destino del enlace: `tabindex="-1"` solo para poder recibir el foco desde
   el salto; no es un control y no lleva anillo. El aterrizaje no queda bajo
   el nav: `scroll-padding-top` de `html` (seccion 0) lo compensa. */
main[tabindex="-1"]:focus {
  outline: none;
}

/* ===========================
   4. BUTTONS
   =========================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.9rem 2.2rem;
  font-family: var(--font-body);
  font-size: var(--text-small);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  border-radius: var(--radius-pill);
  transition: background-color var(--duration-fast) var(--ease-out-expo),
              border-color var(--duration-fast) var(--ease-out-expo),
              transform var(--duration-fast) var(--ease-out-expo),
              box-shadow var(--duration-fast) var(--ease-out-expo);
  cursor: pointer;
}

/* `:focus` apaga el contorno de raton y `:focus-visible` lo devuelve, mismo
   patron que `.form__*:focus` (seccion 10): el teclado necesita su propio
   indicador porque, a diferencia del resto de controles, `.btn` no comparte
   el color de `:hover` con el foco (ver D21). */
.btn:focus {
  outline: none;
}

.btn:focus-visible {
  outline: 2px solid var(--color-tan);
  outline-offset: 3px;
}

.btn--primary {
  background: var(--color-burgundy);
  color: var(--color-beige);
  border: 1.5px solid var(--color-burgundy);
}

/* `--color-burgundy-light` (el token de "hover del CTA") da 4.32:1 con
   --color-beige y baja de AA (D21). No se toca el token porque tambien pinta
   el avatar y la insignia del chatbot sobre otro fondo, con otro resultado de
   contraste que habria que auditar aparte. #8C353B es la misma familia de
   color, mas oscuro que el token, y da 4.72:1: sube en vez de bajar. */
.btn--primary:hover,
.btn--primary:focus-visible,
.btn--primary:active {
  background: #8C353B;
  border-color: #8C353B;
}

.btn--primary:hover,
.btn--primary:focus-visible {
  transform: translateY(-2px);
  box-shadow: 0 8px 30px rgba(124, 47, 52, 0.3);
}

.btn--primary:active {
  transform: translateY(0) scale(0.97);
}

.btn--outline {
  background: transparent;
  color: var(--color-tan);
  border: 1.5px solid var(--color-tan);
}

/* Mismo fondo `--color-tan-dim` que antes, pero el texto sube a
   --color-beige en vez de quedarse en --color-tan: 6.33:1 en vez de 3.95:1
   (D21). Aclarar el fondo solo vale si el texto se separa de el, no si se
   queda igual. */
.btn--outline:hover,
.btn--outline:focus-visible,
.btn--outline:active {
  color: var(--color-beige);
  background: var(--color-tan-dim);
}

.btn--outline:hover,
.btn--outline:focus-visible {
  transform: translateY(-2px);
}

.btn--outline:active {
  transform: translateY(0) scale(0.97);
}

/* `min-height` y no mas padding: con 0.6rem el boton se quedaba en ~40px de
   alto (D7). `.btn` es inline-flex centrado, asi que el texto sigue en medio.
   En el nav de escritorio no cambia nada: 44px sigue por debajo de los 48px
   del logo, que es quien fija la altura del nav. */
.btn--small {
  min-height: 44px;
  padding: 0.6rem 1.5rem;
  font-size: var(--text-micro);
}

/* Fila de CTAs fuera del hero.
   `.hero__ctas` NO se puede reutilizar aqui: arranca en `opacity: 0` bajo
   `:where(.has-js)` y solo se la devuelve `.hero--loaded`, asi que los botones
   desaparecerian en cualquier otra seccion. Este bloque repite su layout
   —flex centrado, gap con token y `flex-wrap`— sin ningun estado de entrada.
   `margin-top` propio para que no dependa del margen de `.section-header`:
   colocado como hermano del header, los dos margenes colapsan y la separacion
   sigue siendo una sola de 64px. */
.cta-band {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--space-md);
  margin-top: var(--space-xl);
}

/* ===========================
   5. NAVIGATION
   =========================== */

.nav {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 1000;
  padding: 1.5rem 0;
  /* Barra solida desde el primer pixel, en todas las paginas y tambien sobre
     el hero (decision de Kevin, 2026-11-03): el logo y el CTA del nav se leen
     igual con cualquier foto debajo. La linea inferior es `box-shadow` y no
     `border-bottom`: no suma 1px al alto del nav, del que dependen
     `.page-main`, `scroll-padding-top` y la reserva del hero (D15). */
  background: var(--color-dark);
  box-shadow: 0 1px 0 var(--color-border);
}

/* Con scroll solo se compacta (96px -> 80px en escritorio). `padding` no se
   transiciona: es propiedad de layout y AGENTS.md lo prohibe para dar
   movimiento, asi que el cambio es instantaneo. En celular el nav no cambia
   de alto (ver seccion 14). */
.nav--scrolled {
  padding: 1rem 0;
}

.nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  max-width: var(--container-wide);
  margin: 0 auto;
  padding: 0 var(--space-lg);
}

/* Logo del nav en escritorio: imagen "logo/KIWI LOGO B.png" (logo completo).
   Aqui se ajusta su tamano. En celular se usa otra imagen y otra altura:
   ver ".nav__logo img" dentro de @media (max-width: 767px). */
.nav__logo img {
  height: 48px;
  width: auto;
}

/* Estado interactivo del logo del nav: solo transform y filter, para que la
   caja no cambie de tamano (el desfase de .page-main depende del alto real
   del logo). En celular el hover no existe; queda :active como respuesta. */
.nav__logo {
  position: relative;
  display: inline-flex;
  align-items: center;
  transform-origin: left center;
  transition: transform var(--duration-fast) var(--ease-out-expo),
              filter var(--duration-fast) var(--ease-out-expo);
}

/* Area tactil de al menos 44x44 sin tocar el layout (D7), mismo patron que
   `.nav__hamburger::before`: en celular el icono mide 32px de alto y crecer
   la caja alargaria el nav y el desfase de `.page-main` (D15). `max()` deja
   intacto el ancho del logo de escritorio, que ya pasa de 44px. */
.nav__logo::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: max(100%, 44px);
  height: max(100%, 44px);
  transform: translate(-50%, -50%);
}

.nav__logo:hover,
.nav__logo:focus-visible {
  transform: scale(1.08) translateY(-2px);
  filter: brightness(1.15) drop-shadow(0 0 14px var(--color-tan));
}

.nav__logo:focus-visible {
  outline: 2px solid var(--color-tan);
  outline-offset: 6px;
}

.nav__logo:active {
  transform: scale(0.97);
  filter: brightness(1.05);
}

.nav__logo-mobile {
  display: none;
}

.nav__links {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
}

.nav__link {
  font-family: var(--font-body);
  font-size: var(--text-micro);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: var(--color-beige);
  position: relative;
  padding: 0.25rem 0;
  transition: color var(--duration-fast) ease;
}

/* `width: 100%` fijo + `transform: scaleX(0)`: NEVER prohibe animar `width`
   (layout). El subrayado crece igual, pero solo mueve el compositor (D26). */
.nav__link::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 1px;
  background: var(--color-tan);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--duration-fast) var(--ease-out-expo);
}

.nav__link:hover {
  color: var(--color-tan);
}

.nav__link:hover::after {
  transform: scaleX(1);
}

.nav__actions {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

/* CTA corto del nav en celular ("Cotizar" / "Get a Quote"): el mismo
   `.btn--primary.btn--small` de `.nav__actions`, que en celular se oculta.
   Solo existe por debajo de 768px (seccion 14 lo devuelve a `inline-flex`).
   `margin-left: auto` lo pega a la hamburguesa y deja el logo a la izquierda;
   el margen derecho separa su area tactil de la de la hamburguesa (44x44
   centrada sobre 32x32: 10px libres entre las dos). */
.nav__cta-movil {
  display: none;
  margin-left: auto;
  margin-right: var(--space-md);
  white-space: nowrap;
}

/* Selector de idioma ES/EN: dos botones de texto chico, sin pastilla.
   Vive en `.footer__bottom` (ya no esta en el nav ni en el menu movil).
   El area tactil sigue en 44x44 por padding aunque el texto sea pequeno.
   El contenedor toma prestado el radio de pildora de `.btn` (`--radius-pill`)
   y el borde de 1.5px en `--color-tan` de `.btn--outline`: mismo lenguaje
   visual, ningun valor nuevo.
   El estado activo reutiliza el par --color-tan/--color-dark ya validado en
   5.06:1 en otras partes del nav (ver docs/ESTILO.md) en vez de introducir
   un color de acento nuevo solo para esto. */
.nav__lang-toggle {
  display: flex;
  align-items: center;
}

/* Area tactil 44x44 por padding, no por tamano de fuente (el texto se queda
   en --text-micro, igual que .nav__link). Sin gap entre los dos botones: el
   propio min-width ya reserva el ancho que hace falta y un gap solo lo
   engordaria mas en un nav ya justo de espacio en tablet (768-1023px).
   `background: none`, `border: none`, `cursor: pointer` y `touch-action:
   manipulation` no se repiten: el reset de la seccion 0 ya los pone en todo
   `button` (`:49-56` y `:70-72`). */
.nav__lang-toggle-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0 var(--space-xs);
  font-family: var(--font-body);
  font-size: var(--text-micro);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-tan);
  border-radius: var(--radius-pill);
  transition: background-color var(--duration-fast) var(--ease-out-expo),
              color var(--duration-fast) var(--ease-out-expo);
}

/* Mismo patron que `.btn:focus` / `.btn:focus-visible` (seccion 4): el
   contorno de raton se apaga y el de teclado se sustituye por un anillo
   propio, nunca se pierde. El nav no recorta overflow, asi que el anillo
   nunca queda tapado por el sticky/fixed de encima. */
.nav__lang-toggle-btn:focus {
  outline: none;
}

.nav__lang-toggle-btn:focus-visible {
  outline: 2px solid var(--color-tan);
  outline-offset: 2px;
}

/* Hover/foco/active suben el contraste en vez de bajarlo (regla del
   proyecto): de 5.06:1 en reposo a 6.33:1, el mismo par que ya usa
   `.btn--outline` en sus tres estados. */
.nav__lang-toggle-btn:hover,
.nav__lang-toggle-btn:focus-visible,
.nav__lang-toggle-btn:active {
  color: var(--color-beige);
  background: var(--color-tan-dim);
}

/* Boton activo: fondo solido en --color-tan con texto --color-dark, 5.06:1
   (mismo par que el resto del nav). Hover/foco sobre el propio activo se
   quedan igual en vez de bajar: ya no hay mas contraste que dar sin sumar un
   color nuevo, y especificidad (0,3,0) gana sobre el hover generico de arriba. */
.nav__lang-toggle-btn.is-active,
.nav__lang-toggle-btn.is-active:hover,
.nav__lang-toggle-btn.is-active:focus-visible {
  color: var(--color-dark);
  background: var(--color-tan);
}

/* Contenido estatico bilingue: el otro extremo del mismo mecanismo de
   idioma que `.nav__lang-toggle-btn` de arriba. Cualquier bloque de marcado
   (parrafo, label, span, etc.) etiquetado `data-lang="es"` o `data-lang="en"`
   se muestra u oculta segun la clase `lang-en` que `js/i18n.js` pone en
   `<html>` al togglear o al cargar segun `localStorage`. Espanol es el
   default: sin la clase, se ve `[data-lang="es"]` y se oculta
   `[data-lang="en"]`; con `html.lang-en`, al reves.
   `display: none` es intencional y no `visibility`/`opacity`: saca el
   elemento oculto del flujo, asi que no deja huecos en flex/grid ni reserva
   espacio para el idioma que no se muestra. Es un show/hide instantaneo sin
   transicion, por lo que no interactua con `prefers-reduced-motion`. */
html:not(.lang-en) [data-lang="en"] { display: none; }
html.lang-en [data-lang="es"] { display: none; }

/* Hamburguesa.
   El control real es el checkbox `.nav__menu-toggle` que va justo antes en el
   marcado; `.nav__hamburger` es su `<label for>`, que es lo que hace que el
   menu abra sin JavaScript. Como `<label>` no hereda nada del UA (a diferencia
   del `<button>` anterior, que ya llegaba limpio por el reset de la seccion 0),
   aqui se declara todo lo que el icono necesita: no hay `background` ni
   `border` que resetear porque un label no trae ninguno, pero `cursor` si hay
   que ponerlo a mano y ya estaba. */
.nav__menu-toggle {
  /* En escritorio no se pinta la hamburguesa: el checkbox se retira del orden
     de tabulacion con `display: none` para no dejar un control invisible
     enfocable, que es el mismo bug ya corregido en `.mobile-menu`.
     En celular (seccion 14) pasa a `display: block` y queda RECORTADO, nunca
     oculto: con `display: none` o `visibility: hidden` dejaria de ser
     enfocable y el menu no se podria abrir con teclado. */
  display: none;
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* El foco recae en un input recortado que nadie ve: se dibuja sobre la
   hamburguesa, que es lo que el visitante si tiene delante. Mismo indicador
   que `.nav__logo:focus-visible` (regla 7 de la guia). `--color-tan` sobre el
   fondo del nav da 5.06:1, por encima del 3:1 que pide un indicador de foco. */
.nav__menu-toggle:focus-visible + .nav__hamburger {
  outline: 2px solid var(--color-tan);
  outline-offset: 6px;
}

.nav__hamburger {
  display: none;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  position: relative;
  width: 32px;
  height: 32px;
  cursor: pointer;
  z-index: 1001;
}

/* Area tactil de 44x44 sin tocar el layout: el icono sigue midiendo 32x32,
   asi que ni la altura del nav ni el desfase de `.page-main` cambian. El
   pseudo-elemento pertenece al label, de modo que el toque lo activa igual. */
.nav__hamburger::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}

.nav__hamburger-line {
  width: 24px;
  height: 1px;
  background: var(--color-beige);
  transition: transform var(--duration-fast) ease;
}

.nav__hamburger-line:first-child {
  margin-bottom: 7px;
}

/* Las 11 reglas del estado abierto van DUPLICADAS, no agrupadas con coma.
   `:has()` no es un selector indulgente: un navegador que no lo entienda
   descarta la lista de selectores ENTERA, asi que agrupar
   `body.menu-open X, body:has(...) X` romperia tambien la via con JS, que hoy
   funciona. Duplicadas, cada navegador aplica la que entiende: `:has()` abre
   el menu sin JavaScript y `body.menu-open` lo mantiene en los antiguos.
   Al tocar una hay que tocar su gemela. */
body.menu-open .nav__hamburger-line:first-child {
  transform: rotate(45deg) translate(2.5px, 2.5px);
}

body:has(.nav__menu-toggle:checked) .nav__hamburger-line:first-child {
  transform: rotate(45deg) translate(2.5px, 2.5px);
}

body.menu-open .nav__hamburger-line:last-child {
  transform: rotate(-45deg) translate(2.5px, -2.5px);
}

body:has(.nav__menu-toggle:checked) .nav__hamburger-line:last-child {
  transform: rotate(-45deg) translate(2.5px, -2.5px);
}

/* Mobile menu overlay.
   El estado cerrado lleva `visibility: hidden`, no solo `opacity: 0`: sin el,
   los 5 enlaces y los 2 botones del overlay seguian en el orden de tabulacion
   y en el arbol de accesibilidad a CUALQUIER ancho, incluido escritorio, sin
   indicador de foco visible.
   `visibility` se transiciona junto a `opacity` a proposito: es una propiedad
   discreta que el motor resuelve como `visible` en todo valor intermedio, asi
   que al abrir aparece en el primer frame (el fundido de entrada se ve entero)
   y al cerrar no se oculta hasta que termina el fundido de salida. Es el unico
   modo de sacarlo del arbol sin perder ninguna de las dos animaciones, y es
   el mismo patron que ya usa `.portfolio-lightbox` en la seccion 7.
   No se usa `display: none` con `transition-behavior: allow-discrete`: exigiria
   ademas `@starting-style` y soporte de 2024 en adelante, y no aporta nada aqui.
   Tampoco hace falta que el JS gestione `aria-hidden` ni `inert`: `visibility`
   ya retira el subarbol entero, y un atributo duplicado solo puede desincronizarse.
   Los hijos (`.mobile-menu__link`, `.mobile-menu__actions`) no necesitan su
   propia `visibility`: heredan la del overlay.
   `display: none` en la regla base (escritorio) es aparte y no sustituye a
   nada de lo anterior: el overlay solo existe por debajo de 768px, donde la
   seccion 14 lo devuelve a `display: flex`. Sin eso, un visitante sin JS que
   abriera el menu y girase a horizontal se quedaria con el overlay a pantalla
   completa en escritorio, porque nadie desmarcaria el checkbox (con JS si lo
   hace el listener de `resize`). El fundido no se pierde: a los anchos donde
   el menu se abre, `display` vale `flex` en los dos estados. */
.mobile-menu {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100vh;
  height: 100dvh;
  background: var(--color-dark);
  z-index: 999;
  display: none;
  flex-direction: column;
  align-items: center;
  /* Con seis enlaces el bloque ya no cabe centrado en 320x568 y el primero
     ("Inicio") quedaba bajo el nav. El padding superior (80px, la misma
     reserva del hero) libra el nav de 76px; `safe center` centra mientras
     cabe y, si no (celular apaisado), arranca arriba y `overflow-y: auto`
     deja desplazarlo. `center` es el respaldo sin soporte de `safe`. */
  justify-content: center;
  justify-content: safe center;
  padding: calc(var(--space-xl) + var(--space-md)) 0 var(--space-md);
  overflow-y: auto;
  gap: var(--space-md);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  /* Corta el encadenado del scroll: el arrastre sobre el overlay no arrastra
     la pagina de debajo. Es el refuerzo del bloqueo de scroll de la seccion 14. */
  overscroll-behavior: contain;
  transition: opacity var(--duration-fast) ease,
              visibility var(--duration-fast) ease;
}

body.menu-open .mobile-menu {
  opacity: 1;
  visibility: visible;
  pointer-events: all;
}

body:has(.nav__menu-toggle:checked) .mobile-menu {
  opacity: 1;
  visibility: visible;
  pointer-events: all;
}

/* 44px de alto real (area tactil del proyecto): a `--text-heading` el texto
   solo mide ~33px. El hueco entre enlaces baja a `--space-md` (gap del
   overlay) para que el paso entre uno y otro siga siendo el de antes. */
.mobile-menu__link {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--space-md);
  font-family: var(--font-display);
  font-size: var(--text-heading);
  color: var(--color-beige);
  opacity: 0;
  transform: translateY(20px);
  transition: opacity var(--duration-normal) var(--ease-out-expo),
              transform var(--duration-normal) var(--ease-out-expo),
              color var(--duration-normal) var(--ease-out-expo);
}

body.menu-open .mobile-menu__link {
  opacity: 1;
  transform: translateY(0);
}

body:has(.nav__menu-toggle:checked) .mobile-menu__link {
  opacity: 1;
  transform: translateY(0);
}

/* Escalonado de entrada. Cada link real del menu movil ocupa dos nodos
   consecutivos en el DOM (`data-lang="es"` y `data-lang="en"`, solo uno
   visible via display:none), asi que el retardo se agrupa de a pares para que
   ambos idiomas del mismo link entren juntos. Seis links reales (Inicio,
   Portafolio, FRAME, Servicios, Nosotros, Contacto) = seis pares = doce
   nodos. El ultimo par entra a 0.35s, antes que `.mobile-menu__actions`
   (0.4s): el CTA sigue cerrando la secuencia.
   Duplicado por el mismo motivo que el resto: ver la nota de la hamburguesa.
   Los retardos se mantienen identicos en las dos versiones;
   `prefers-reduced-motion` (seccion 13) sigue anulando la duracion de la
   transicion en ambas por igual. */
body.menu-open .mobile-menu__link:nth-child(1),
body.menu-open .mobile-menu__link:nth-child(2) { transition-delay: 0.1s; }
body.menu-open .mobile-menu__link:nth-child(3),
body.menu-open .mobile-menu__link:nth-child(4) { transition-delay: 0.15s; }
body.menu-open .mobile-menu__link:nth-child(5),
body.menu-open .mobile-menu__link:nth-child(6) { transition-delay: 0.2s; }
body.menu-open .mobile-menu__link:nth-child(7),
body.menu-open .mobile-menu__link:nth-child(8) { transition-delay: 0.25s; }
body.menu-open .mobile-menu__link:nth-child(9),
body.menu-open .mobile-menu__link:nth-child(10) { transition-delay: 0.3s; }
body.menu-open .mobile-menu__link:nth-child(11),
body.menu-open .mobile-menu__link:nth-child(12) { transition-delay: 0.35s; }

body:has(.nav__menu-toggle:checked) .mobile-menu__link:nth-child(1),
body:has(.nav__menu-toggle:checked) .mobile-menu__link:nth-child(2) { transition-delay: 0.1s; }
body:has(.nav__menu-toggle:checked) .mobile-menu__link:nth-child(3),
body:has(.nav__menu-toggle:checked) .mobile-menu__link:nth-child(4) { transition-delay: 0.15s; }
body:has(.nav__menu-toggle:checked) .mobile-menu__link:nth-child(5),
body:has(.nav__menu-toggle:checked) .mobile-menu__link:nth-child(6) { transition-delay: 0.2s; }
body:has(.nav__menu-toggle:checked) .mobile-menu__link:nth-child(7),
body:has(.nav__menu-toggle:checked) .mobile-menu__link:nth-child(8) { transition-delay: 0.25s; }
body:has(.nav__menu-toggle:checked) .mobile-menu__link:nth-child(9),
body:has(.nav__menu-toggle:checked) .mobile-menu__link:nth-child(10) { transition-delay: 0.3s; }
body:has(.nav__menu-toggle:checked) .mobile-menu__link:nth-child(11),
body:has(.nav__menu-toggle:checked) .mobile-menu__link:nth-child(12) { transition-delay: 0.35s; }

.mobile-menu__link:hover {
  color: var(--color-tan);
}

.mobile-menu__actions {
  display: flex;
  gap: var(--space-md);
  margin-top: var(--space-lg);
  opacity: 0;
  transform: translateY(20px);
  transition: opacity var(--duration-normal) var(--ease-out-expo) 0.4s,
              transform var(--duration-normal) var(--ease-out-expo) 0.4s;
}

body.menu-open .mobile-menu__actions {
  opacity: 1;
  transform: translateY(0);
}

body:has(.nav__menu-toggle:checked) .mobile-menu__actions {
  opacity: 1;
  transform: translateY(0);
}

/* ===========================
   6. HERO
   =========================== */

/* Sub-page spacing for fixed nav */
.page-main {
  padding-top: 100px;
}

/* `100vh` primero y `100dvh` despues (D8): en Safari iOS `100vh` incluye la
   barra de direcciones y empujaba los CTAs fuera del fold real.
   El padding reserva arriba el nav fijo (96px en escritorio, 80px en
   celular) y abajo el boton de pausa `.hero__motion-toggle` mas su margen
   (76px). Cuando el contenido cabe solo baja el centro 10px; cuando no cabe
   —320x568 o un celular apaisado— el hero crece en vez de meter el eyebrow
   bajo el nav o los CTAs bajo el boton de pausa. */
.hero {
  position: relative;
  min-height: 100vh;
  min-height: 100dvh;
  padding: var(--space-2xl) 0 calc(44px + var(--space-lg));
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  overflow: hidden;
  background: #0a0908;
}

/* ---- Mosaic photo background ---- */
.hero__mosaic {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 6px;
  padding: 6px 0;
  transform: rotate(-4deg) scale(1.15);
  transform-origin: center center;
  /* Desenfoque suave del carrusel (decision de Kevin, 2026-11-03): las fotos
     se siguen viendo correr, pero como textura, y el texto gana el primer
     plano. `filter` en el contenedor y no `backdrop-filter` en
     `.hero__overlay`: el filtro se aplica una vez a la capa ya compuesta del
     mosaico, sin releer el fondo en cada fotograma. 3px en celular (seccion
     14), donde las teselas miden 110x80. El texto no se desenfoca: esta en
     `.hero__content`, fuera de este contenedor. */
  filter: blur(4px);
}

.hero__mosaic-row {
  display: flex;
  gap: 6px;
  width: max-content;
  flex-shrink: 0;
}

/* Scroll left */
.hero__mosaic-row--left {
  animation: mosaicScrollLeft 60s linear infinite;
}

.hero__mosaic-row--right {
  animation: mosaicScrollRight 60s linear infinite;
}

.hero__mosaic-row--slow {
  animation-duration: 80s;
}

.hero__mosaic-row--fast {
  animation-duration: 45s;
}

@keyframes mosaicScrollLeft {
  0% { transform: translateX(0); }
  100% { transform: translateX(-50%); }
}

@keyframes mosaicScrollRight {
  0% { transform: translateX(-50%); }
  100% { transform: translateX(0); }
}

/* Individual photo tile */
.hero__mosaic-item {
  width: 180px;
  height: 130px;
  border-radius: 4px;
  overflow: hidden;
  flex-shrink: 0;
  position: relative;
  /* Placeholder gradient — replace with background-image: url(...) for real photos */
  background: linear-gradient(135deg, #1e1a16 0%, #2a2520 50%, #1a1715 100%);
  background-size: cover;
  background-position: center;
}

/* Vary placeholder shades for visual depth */
.hero__mosaic-item:nth-child(10n+1) { background: linear-gradient(145deg, #2a2218 0%, #1c1814 100%); }
.hero__mosaic-item:nth-child(10n+2) { background: linear-gradient(125deg, #1f1b17 0%, #2e2822 100%); }
.hero__mosaic-item:nth-child(10n+3) { background: linear-gradient(160deg, #261f19 0%, #181412 100%); }
.hero__mosaic-item:nth-child(10n+4) { background: linear-gradient(135deg, #221e1a 0%, #332c24 100%); }
.hero__mosaic-item:nth-child(10n+5) { background: linear-gradient(150deg, #2c2520 0%, #1a1614 100%); }
.hero__mosaic-item:nth-child(10n+6) { background: linear-gradient(140deg, #1e1916 0%, #28221c 100%); }
.hero__mosaic-item:nth-child(10n+7) { background: linear-gradient(130deg, #302820 0%, #1c1815 100%); }
.hero__mosaic-item:nth-child(10n+8) { background: linear-gradient(155deg, #241e18 0%, #1f1a16 100%); }
.hero__mosaic-item:nth-child(10n+9) { background: linear-gradient(120deg, #201c18 0%, #2e2820 100%); }
.hero__mosaic-item:nth-child(10n+10) { background: linear-gradient(165deg, #2a241e 0%, #161310 100%); }

/* Subtle label inside placeholder — hidden when real photos are loaded */
.hero__mosaic-item::after {
  content: none;
}

/* ---- Capa oscura para legibilidad ----
   El centro es donde va el texto, asi que manda el centro, medido sobre el
   peor caso: una foto blanca (digitales de modelo con fondo de estudio).
   Con 0.82 el compuesto queda en ~#363534 y TODO el texto del hero pasa AA:
   `--color-tan` del eyebrow 4.59:1 (con 0.72 daba 3.12:1, D28) y
   `--color-beige` de cita y subtitulo 7.37:1 (antes 5.02:1). El borde sube
   a 0.88 para conservar la vineta. Con el desenfoque del mosaico las fotos
   ya no compiten con el texto, asi que oscurecerlas no apaga el movimiento. */
.hero__overlay {
  position: absolute;
  inset: 0;
  z-index: 1;
  background:
    radial-gradient(ellipse 50% 50% at 50% 50%,
      rgba(10, 9, 8, 0.82) 0%,
      rgba(10, 9, 8, 0.88) 100%);
  pointer-events: none;
}

/* Film grain on top of overlay */
.hero::before {
  content: '';
  position: absolute;
  top: -50%;
  left: -50%;
  width: 200%;
  height: 200%;
  z-index: 2;
  pointer-events: none;
  opacity: 0.03;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='1'/%3E%3C/svg%3E");
  background-size: 256px 256px;
  animation: grain 4s steps(6) infinite;
}

@keyframes grain {
  0% { transform: translate(0, 0); }
  16% { transform: translate(-5%, -5%); }
  33% { transform: translate(3%, -8%); }
  50% { transform: translate(-8%, 2%); }
  66% { transform: translate(5%, 5%); }
  83% { transform: translate(-3%, 8%); }
  100% { transform: translate(0, 0); }
}

.hero__content {
  position: relative;
  z-index: 3;
  max-width: 900px;
  padding: 0 var(--space-lg);
}

/* Entrada del hero.
   Estado por defecto VISIBLE: sin JavaScript el eyebrow, la cita, el
   subtitulo y los CTAs se leen igual. El estado inicial oculto solo se activa
   cuando el script inline del <head> confirma que hay JS anadiendo .has-js
   a <html>.
   :where() no suma especificidad: estas reglas siguen valiendo (0,1,0), de
   modo que .hero--loaded (0,2,0) y el bloque de prefers-reduced-motion
   (0,1,0, pero posterior en el archivo) las siguen ganando igual que antes. */
:where(.has-js) .hero__eyebrow,
:where(.has-js) .hero__quote,
:where(.has-js) .hero__subtitle,
:where(.has-js) .hero__ctas {
  opacity: 0;
  transform: translateY(30px);
}

/* Coreografia corta (D4): escalonado de 50ms y 0.5s por pieza. El JS pone
   `.hero--loaded` 100ms despues de DOMContentLoaded, asi que los CTAs
   terminan de entrar a los 0.8s (antes 2.4s) y, con la curva expo, ya se
   leen hacia los 0.4s. La entrada se conserva entera, solo se acorta.
   El H1 de index es solo para lector de pantalla (`.sr-only`, sin
   animacion): la cita `.hero__quote` sigue al eyebrow a los 0.1s y los
   tiempos de los CTAs no cambian. */
.hero--loaded .hero__eyebrow {
  animation: heroFadeUp 0.5s var(--ease-out-expo) 0s forwards;
}

.hero--loaded .hero__quote {
  animation: heroFadeUp 0.5s var(--ease-out-expo) 0.1s forwards;
}

.hero--loaded .hero__subtitle {
  animation: heroFadeUp 0.5s var(--ease-out-expo) 0.15s forwards;
}

.hero--loaded .hero__ctas {
  animation: heroFadeUp 0.5s var(--ease-out-expo) 0.2s forwards;
}

@keyframes heroFadeUp {
  from {
    opacity: 0;
    transform: translateY(30px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.hero__eyebrow {
  font-family: var(--font-body);
  font-size: var(--text-micro);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.3em;
  color: var(--color-tan);
  margin-bottom: var(--space-lg);
}

/* Cita destacada bajo el eyebrow. El H1 de index va en `.sr-only` (no ocupa
   espacio), asi que la cita es la primera pieza grande del hero y los
   32px del eyebrow son el mismo ritmo que entre las demas piezas.
   Voz de la marca, no texto que explica: por eso va en `--font-display`
   italica y no en `--font-body` como el subtitulo. Tamano `--text-heading`
   (21px en celular, 36px en escritorio), un escalon por encima de
   `--text-subhead`.
   Color `--color-beige` por la misma razon que `.hero__subtitle` (D3): es el
   tono de texto principal: 7.37:1 sobre la peor foto bajo `.hero__overlay`.
   Los mismos 520px de medida que el subtitulo, para que los dos bloques
   alineen; `text-wrap: balance` reparte la cita en lineas parejas.
   Margen inferior de 48px, mayor que los 32px entre piezas: separa la cita
   del subtitulo. Las comillas tipograficas vienen en el texto. */
.hero__quote {
  max-width: 520px;
  margin: 0 auto calc(var(--space-lg) + var(--space-md));
  font-family: var(--font-display);
  font-size: var(--text-heading);
  font-style: italic;
  font-weight: 400;
  line-height: 1.3;
  color: var(--color-beige);
  text-wrap: balance;
}

.hero__quote p {
  margin: 0;
}

/* `--color-beige` y no `--color-text-muted` (D3): el fondo real son fotos,
   no `--color-dark`, y el beige es el que mas margen da sobre la peor foto
   bajo la capa oscura (7.37:1, ver `.hero__overlay`). Lo apagado frente a la cita lo da
   el peso 300, igual que en `.section-header__text`. */
.hero__subtitle {
  font-family: var(--font-body);
  font-size: var(--text-subhead);
  font-weight: 300;
  color: var(--color-beige);
  max-width: 520px;
  margin: 0 auto var(--space-xl);
  line-height: 1.7;
}

.hero__ctas {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
  flex-wrap: wrap;
}

/* ---- Pausa del movimiento del hero (D23) ----
   El mosaico y el grano se mueven sin fin detras del H1 y los CTAs: este
   boton los detiene. El JS alterna la clase `.hero--paused` en
   `section.hero` y cambia el texto de su etiqueta `.sr-only` ("Pausar" /
   "Reanudar"); ningun selector depende de `aria-pressed`, que el boton no
   lleva. El CSS solo pausa (`animation-play-state`), no quita la
   animacion, asi que al reanudar sigue donde estaba.
   Abajo a la IZQUIERDA a proposito: abajo a la derecha, con el hero a 100dvh,
   caeria justo bajo la burbuja fija del chatbot.
   Solo existe con JavaScript (`.has-js`): sin JS el boton no haria nada. */
.hero__motion-toggle {
  display: none;
  position: absolute;
  left: calc(var(--space-md) + env(safe-area-inset-left));
  bottom: calc(var(--space-md) + env(safe-area-inset-bottom));
  z-index: 3;
  width: 44px;
  height: 44px;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--color-border);
  background: var(--color-overlay);
  /* `--color-tan` sobre el circulo da >=5:1 con cualquier foto debajo, muy
     por encima del 3:1 que pide un icono. Hover y foco suben a beige. */
  color: var(--color-tan);
  transition: color var(--duration-fast) ease,
              border-color var(--duration-fast) ease;
}

.has-js .hero__motion-toggle {
  display: inline-flex;
}

.hero__motion-toggle:hover,
.hero__motion-toggle:focus-visible {
  color: var(--color-beige);
  border-color: var(--color-tan);
}

.hero__motion-toggle:focus {
  outline: none;
}

.hero__motion-toggle:focus-visible {
  outline: 2px solid var(--color-tan);
  outline-offset: 3px;
}

.hero__motion-icon {
  width: 18px;
  height: 18px;
}

/* Icono de pausa mientras se mueve; icono de play cuando esta pausado. */
.hero__motion-icon--play {
  display: none;
}

.hero--paused .hero__motion-icon--pause {
  display: none;
}

.hero--paused .hero__motion-icon--play {
  display: block;
}

.hero--paused .hero__mosaic-row,
.hero--paused::before {
  animation-play-state: paused;
}


/* ===========================
   6b. INTRO
   =========================== */

/* Bloque breve entre el hero y los servicios, solo en index.html. Se numera 6b
   y no como seccion 7 para no renumerar de la 7 a la 16: esos numeros se citan
   en comentarios de este mismo archivo y en docs/. */

.intro {
  background: var(--color-dark);
}

/* Es texto de apoyo, no una seccion de peso: acorta el padding de .section
   (--space-3xl) para no competir ni con el hero ni con el CTA de cierre.
   Se escribe .section.intro a proposito: mas abajo, @media (min-width: 1024px)
   vuelve a declarar .section { padding: var(--space-3xl) 0 }, y un .intro suelto
   —misma especificidad, antes en el archivo— perderia en escritorio. */
.section.intro {
  padding: 4rem 0;
}

.intro__inner {
  max-width: 680px;
  margin: 0 auto;
  text-align: center;
}

/* Un escalon por debajo de .section-title (--text-display): la intro apoya al
   hero, no titula la pagina. */
.intro__title {
  font-family: var(--font-display);
  font-size: var(--text-heading);
  font-weight: 400;
  line-height: 1.2;
  color: var(--color-beige);
  margin-bottom: var(--space-lg);
}

/* Son los dos unicos parrafos que explican que hace Kiwi Media TV antes de los
   servicios, asi que van en --color-beige (8.13:1 sobre --color-dark) y no en
   --color-text-muted (4.65:1, AA justo), que en un celular al sol se lee peor.
   La jerarquia frente al titulo la sostienen familia y tamano, no el color. */
.intro__text {
  font-family: var(--font-body);
  font-size: var(--text-body);
  line-height: 1.8;
  color: var(--color-beige);
  margin-bottom: var(--space-md);
}

.intro__text:last-child {
  margin-bottom: 0;
}

/* Beneficio de registro (5% en la primera sesion de fotos). Se destaca con un
   bano de `--color-burgundy-dim` y borde fino, no con un boton primario: el
   CTA de la pagina sigue siendo "Pedir Cotizacion" y aqui el boton es
   `.btn--outline`. Sobre el compuesto (~#3E2E2A): beige 7.8:1, el
   `<strong>` en `--color-burgundy-pale` 6.1:1, la nota y el texto del
   outline en tan 4.86:1. */
.intro__descuento {
  margin-top: var(--space-lg);
  padding: var(--space-lg) var(--space-md);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-burgundy-dim);
}

.intro__descuento-texto {
  margin-bottom: var(--space-sm);
  font-family: var(--font-body);
  font-size: var(--text-body);
  line-height: 1.6;
  color: var(--color-beige);
  text-wrap: balance;
}

.intro__descuento-texto strong {
  font-weight: 600;
  color: var(--color-burgundy-pale);
}

/* `.form__note` reutilizada para las condiciones: aqui va pegada al texto y
   no con los 32px de separacion que lleva sobre un formulario. */
.intro__descuento .form__note {
  margin-bottom: var(--space-md);
  text-wrap: balance;
}

/* ===========================
   6c. FRAME
   =========================== */

/* Revista digital FRAME: el adelanto de index.html (`.frame-teaser`) y la
   pagina frame.html (`.frame-cabecera`, `.frame-archivo`, `.frame-edicion`
   con su lector `.frame-lector`). Se numera 6c por el mismo motivo que 6b.
   Escrito mobile-first: la regla base es el celular y las media queries de
   esta seccion (`min-width`) solo anaden el escritorio. */

/* ---- Adelanto en index.html ---- */

/* `--color-dark-alt` separa el bloque del hero y de `.intro` (`--color-dark`).
   Contrastes sobre este fondo: beige 9.18:1, tan 5.71:1. */
.frame-teaser {
  background: var(--color-dark-alt);
}

.frame-teaser__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-lg);
  align-items: center;
}

/* Portada contenida en celular: 62% del ancho util con techo de 15rem
   (193x241px a 375px), para que titulo, texto y boton asomen pronto.
   `aspect-ratio` reserva el hueco antes de que cargue la imagen (lazy).
   El realce de hover va en la imagen, no en el enlace: el enlace lleva
   `[data-reveal]` y `[data-reveal].is-revealed` (seccion 12) pisaria
   cualquier `transform` suyo. */
.frame-teaser__portada {
  position: relative;
  display: block;
  width: min(62%, 15rem);
  margin: 0 auto;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  border-radius: 8px;
  background: var(--color-dark);
  box-shadow: 0 0 0 1px var(--color-border);
}

.frame-teaser__portada:focus {
  outline: none;
}

.frame-teaser__portada:focus-visible {
  outline: 2px solid var(--color-tan);
  outline-offset: 4px;
}

.frame-teaser__imagen {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--duration-normal) var(--ease-out-expo);
}

/* Zoom solo sin `prefers-reduced-motion`: con teclado lo dispara el foco
   (ademas del anillo de arriba) y con raton solo un hover real. */
@media (prefers-reduced-motion: no-preference) {
  .frame-teaser__portada:focus-visible .frame-teaser__imagen {
    transform: scale(1.04);
  }
}

@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  .frame-teaser__portada:hover .frame-teaser__imagen {
    transform: scale(1.04);
  }
}

.frame-teaser__texto {
  min-width: 0;
  text-align: center;
}

.frame-teaser__parrafo {
  max-width: 520px;
  margin: var(--space-lg) auto 0;
  font-family: var(--font-body);
  font-size: var(--text-body);
  line-height: 1.8;
  color: var(--color-beige);
}

/* Misma voz que `.hero__quote`: display italica en `--color-beige`. */
.frame-teaser__cita {
  max-width: 520px;
  margin: var(--space-lg) auto 0;
  font-family: var(--font-display);
  font-size: var(--text-heading);
  font-style: italic;
  font-weight: 400;
  line-height: 1.3;
  color: var(--color-beige);
  text-wrap: balance;
}

.frame-teaser__cita p {
  margin: 0;
}

/* Fila de CTA: mismo tratamiento que `.cta-band` en celular (boton de
   ancho completo con techo de 280px). */
.frame-teaser__ctas {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-md);
  margin-top: var(--space-lg);
}

.frame-teaser__ctas .btn {
  width: 100%;
  max-width: 280px;
}

/* Enlace de texto secundario a contacto. 44px de alto minimo real: no hay
   vecinos encima ni debajo con los que solaparse. Se distingue del texto
   por color y subrayado; hover y foco suben de tan (5.71:1) a beige
   (9.18:1). */
.frame-teaser__enlace {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  margin-top: var(--space-sm);
  font-family: var(--font-body);
  font-size: var(--text-small);
  color: var(--color-tan);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.25em;
  transition: color var(--duration-fast) ease;
}

.frame-teaser__enlace:hover,
.frame-teaser__enlace:focus-visible {
  color: var(--color-beige);
}

.frame-teaser__enlace:focus {
  outline: none;
}

.frame-teaser__enlace:focus-visible {
  outline: 2px solid var(--color-tan);
  outline-offset: 2px;
}

/* Escritorio: dos columnas, portada a la izquierda y texto alineado a la
   izquierda. */
@media (min-width: 768px) {
  .frame-teaser__inner {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    gap: var(--space-xl);
  }

  .frame-teaser__portada {
    width: 100%;
    max-width: 420px;
    margin: 0 0 0 auto;
  }

  .frame-teaser__texto {
    text-align: left;
  }

  .frame-teaser__texto .section-line {
    margin-left: 0;
  }

  .frame-teaser__parrafo,
  .frame-teaser__cita {
    margin-left: 0;
  }

  .frame-teaser__ctas {
    flex-direction: row;
    justify-content: flex-start;
  }

  .frame-teaser__ctas .btn {
    width: auto;
  }

  .frame-teaser__enlace {
    justify-content: flex-start;
  }
}

/* ---- frame.html: cabecera y archivo de ediciones ---- */

/* La cabecera (un `.section-header` normal con el H1) y el archivo se
   acercan: sin esto quedaban 144px vacios entre los dos (64px del header mas
   80px de `.section`). `.section.` delante por el mismo motivo que
   `.section.intro`: la regla de `.section` a 1024px (seccion 14) va mas
   abajo en el archivo. */
.section.frame-cabecera {
  padding-bottom: 0;
}

.section.frame-archivo {
  padding: 0 0 var(--space-lg);
}

/* Rotulo de la lista, con la tipografia de `.section-eyebrow`. */
.frame-archivo__titulo {
  margin-bottom: var(--space-md);
  font-family: var(--font-body);
  font-size: var(--text-micro);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.3em;
  color: var(--color-tan);
}

/* Una columna en celular; rejilla de tarjetas desde 768px para cuando haya
   mas ediciones. Sin vinetas: el reset (seccion 0) ya quita `list-style`. */
.frame-archivo__lista {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-md);
}

/* Tarjeta compacta: miniatura de 5rem (80x100px) a la izquierda y tres
   lineas de texto a la derecha; ~118px de alto, no ocupa la pantalla. */
.frame-archivo__enlace {
  display: grid;
  grid-template-columns: 5rem minmax(0, 1fr);
  grid-template-rows: auto auto auto;
  column-gap: var(--space-md);
  align-content: center;
  padding: var(--space-sm);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-dark-alt);
  transition: border-color var(--duration-fast) ease;
}

.frame-archivo__miniatura {
  grid-row: 1 / -1;
  grid-column: 1;
}

.frame-archivo__portada {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: 4px;
}

.frame-archivo__numero,
.frame-archivo__nombre,
.frame-archivo__meta {
  grid-column: 2;
  min-width: 0;
  align-self: center;
}

/* `--color-tan` sobre `--color-dark-alt`: 5.71:1. */
.frame-archivo__numero {
  font-family: var(--font-body);
  font-size: var(--text-micro);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--color-tan);
}

.frame-archivo__nombre {
  font-family: var(--font-display);
  font-size: var(--text-heading);
  line-height: 1.2;
  color: var(--color-beige);
}

/* `--color-text-muted` sobre `--color-dark-alt`: 5.25:1. */
.frame-archivo__meta {
  font-family: var(--font-body);
  font-size: var(--text-small);
  color: var(--color-text-muted);
}

.frame-archivo__enlace:hover,
.frame-archivo__enlace:focus-visible {
  border-color: var(--color-tan);
}

.frame-archivo__enlace:focus {
  outline: none;
}

.frame-archivo__enlace:focus-visible {
  outline: 2px solid var(--color-tan);
  outline-offset: 3px;
}

@media (min-width: 768px) {
  .frame-archivo__lista {
    grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
  }
}

/* ---- frame.html: edicion y lector ---- */

/* El salto a #n01 no queda bajo el nav: `scroll-padding-top: 100px` de
   `html` (seccion 0, D24) ya compensa cualquier ancla del sitio, y un
   `scroll-margin-top` aqui se sumaria a el. Lo que si se recortan son el
   padding superior y el margen bajo la cabecera, para que al saltar la
   primera pagina entre entera: a 375x667 ocupa 252-641px. */
.section.frame-edicion {
  padding-top: var(--space-md);
}

.frame-edicion__cabecera {
  margin-bottom: var(--space-md);
  text-align: center;
}

.frame-edicion__titulo {
  margin-bottom: var(--space-sm);
  font-family: var(--font-display);
  font-size: var(--text-display);
  font-weight: 400;
  line-height: 1.15;
  color: var(--color-beige);
}

/* `--color-text-muted` sobre `--color-dark`: 4.65:1. */
.frame-edicion__meta {
  font-family: var(--font-body);
  font-size: var(--text-small);
  color: var(--color-text-muted);
  text-wrap: balance;
}

/* Alto disponible para una pagina: la ventana menos el nav (96px, el mas
   alto; ver D15), la fila de controles (44px mas su margen) y un respiro.
   Una pagina 4/5 mide de ancho 0.8 veces ese alto: la pista nunca pasa de
   ahi, asi que en celular apaisado y en escritorio la pagina entera cabe en
   la ventana. Variable local del componente, no un token. */
.frame-lector {
  --frame-alto-pagina: calc(100vh - 96px - 44px - var(--space-md) - var(--space-lg));
}

@supports (height: 100svh) {
  .frame-lector {
    --frame-alto-pagina: calc(100svh - 96px - 44px - var(--space-md) - var(--space-lg));
  }
}

/* Pista de paginas: scroll horizontal propio con imanes, una pagina por
   pantalla en celular. `overscroll-behavior-x: contain` evita que el gesto,
   al llegar al final, arrastre la pagina o dispare el "atras" del
   navegador. Sin barra visible: la paginacion la dicen el contador, los
   botones y el texto de ayuda. Es enfocable (`tabindex="0"`): con foco, las
   flechas del teclado la desplazan. */
.frame-lector__pista {
  display: flex;
  gap: var(--space-sm);
  width: min(100%, calc(var(--frame-alto-pagina) * 0.8));
  margin: 0 auto;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}

.frame-lector__pista::-webkit-scrollbar {
  display: none;
}

.frame-lector__pista:focus {
  outline: none;
}

.frame-lector__pista:focus-visible {
  outline: 2px solid var(--color-tan);
  outline-offset: 4px;
}

@media (prefers-reduced-motion: no-preference) {
  .frame-lector__pista {
    scroll-behavior: smooth;
  }
}

/* Cada pagina ocupa todo el ancho de la pista. `scroll-snap-stop: always`:
   un deslizamiento rapido pasa una pagina, no tres. El fondo es el hueco
   reservado mientras carga la imagen (lazy). */
.frame-lector__pagina {
  flex: 0 0 100%;
  margin: 0;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: var(--color-dark-alt);
  scroll-snap-align: start;
  scroll-snap-stop: always;
}

.frame-lector__imagen {
  display: block;
  width: 100%;
  height: auto;
}

/* ---- Paginas de pago (3-9) bloqueadas ----
   La imagen es una miniatura de 48px ya desenfocada, estirada al ancho de la
   pagina (6.5x a 375px). El `blur` CSS borra la rejilla del reescalado para
   que se vea suave y no pixelada, y el `scale(1.2)` empuja fuera del recorte
   el borde que el desenfoque deja transparente; `overflow: hidden` de
   `.frame-lector__pagina` recorta las dos cosas. Al desbloquear, js/revista.js
   quita el modificador y la pagina real se ve nitida. */
.frame-lector__pagina--bloqueada {
  position: relative;
  /* Contiene el `z-index` de la cinta dentro de la pagina. */
  isolation: isolate;
}

.frame-lector__pagina--bloqueada .frame-lector__imagen {
  filter: blur(12px);
  transform: scale(1.2);
}

/* Capa del aviso: cubre la pagina con `--color-overlay`. Sobre el peor caso
   (pagina blanca) el compuesto queda en ~#524D48 y `--color-beige` da 5.05:1;
   `--color-tan` no pasa ahi (3.14:1), por eso todo el texto del candado es
   beige. Texto arriba, enlaces abajo (`margin-top: auto` en el primero) y la
   cinta, absoluta, cruzando el centro entre los dos. */
.frame-lector__candado {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: var(--space-md);
  background: var(--color-overlay);
  text-align: center;
}

.frame-lector__candado-texto {
  max-width: 22rem;
  font-family: var(--font-body);
  font-size: var(--text-body);
  font-weight: 500;
  line-height: 1.5;
  color: var(--color-beige);
  text-wrap: balance;
}

/* Cinta "Comprar revista": es el boton de compra de la pagina (un `<a>`).
   Banda de 150% del ancho, centrada y girada -12deg; los extremos los recorta
   la pagina. 48px de alto: perpendicular a la banda el area tactil sigue
   siendo de 48px. `--color-beige` sobre `--color-burgundy`: 5.49:1, el par de
   `.btn--primary`, sobre fondo propio y no sobre el desenfoque. Hover y foco
   invierten el par (burgundy sobre beige, 5.49:1): cambio claro sin mover la
   banda. El anillo de foco va fuera (3px) y se ve arriba y abajo de la banda,
   beige sobre la capa (5.05:1). */
.frame-lector__cinta {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 150%;
  min-height: 48px;
  padding: var(--space-sm) 25%;
  border-block: 1px solid var(--color-tan);
  background: var(--color-burgundy);
  color: var(--color-beige);
  font-family: var(--font-body);
  font-size: var(--text-subhead);
  font-weight: 600;
  line-height: 1.3;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  text-align: center;
  transform: translate(-50%, -50%) rotate(-12deg);
  transition: background-color var(--duration-fast) ease,
              color var(--duration-fast) ease;
}

.frame-lector__cinta:hover,
.frame-lector__cinta:focus-visible {
  background: var(--color-beige);
  color: var(--color-burgundy);
}

.frame-lector__cinta:focus {
  outline: none;
}

.frame-lector__cinta:focus-visible {
  outline: 2px solid var(--color-beige);
  outline-offset: 3px;
}

/* Enlaces del pie del candado. Mismo patron que `.frame-teaser__enlace`
   (flex, 44px de alto real, subrayado), pero en beige: el tan no llega a AA
   sobre la capa. "Pide tu cotizacion" es la accion secundaria visible (orden
   del gerente): peso 600 y subrayado tan; "Iniciar sesion", mas discreto. */
.frame-lector__cotizar,
.frame-lector__sesion {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  max-width: 100%;
  font-family: var(--font-body);
  font-size: var(--text-small);
  line-height: 1.4;
  color: var(--color-beige);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.25em;
  text-wrap: balance;
  transition: text-decoration-color var(--duration-fast) ease;
}

.frame-lector__cotizar {
  margin-top: auto;
  font-weight: 600;
  text-decoration-color: var(--color-tan);
}

.frame-lector__sesion {
  font-weight: 400;
}

/* Sin Supabase configurado js/revista.js oculta "Iniciar sesion" con
   `hidden`; el `display: flex` de arriba lo anularia. */
.frame-lector__sesion[hidden] {
  display: none;
}

.frame-lector__cotizar:hover,
.frame-lector__cotizar:focus-visible,
.frame-lector__sesion:hover,
.frame-lector__sesion:focus-visible {
  text-decoration-color: var(--color-beige);
  text-decoration-thickness: 2px;
}

.frame-lector__cotizar:focus,
.frame-lector__sesion:focus {
  outline: none;
}

.frame-lector__cotizar:focus-visible,
.frame-lector__sesion:focus-visible {
  outline: 2px solid var(--color-beige);
  outline-offset: 2px;
}

/* Controles: solo con JavaScript, que es quien los hace funcionar (mismo
   patron que `.hero__motion-toggle`). */
.frame-lector__controles {
  display: none;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
  margin-top: var(--space-md);
}

.has-js .frame-lector__controles {
  display: flex;
}

/* Mismo circulo de 44x44 que `.hero__motion-toggle`: borde fino e icono en
   `--color-tan` (5.06:1 sobre `--color-dark`), que sube a beige con hover y
   foco. */
.frame-lector__boton {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: 1px solid var(--color-border);
  border-radius: 50%;
  color: var(--color-tan);
  transition: color var(--duration-fast) ease,
              border-color var(--duration-fast) ease,
              opacity var(--duration-fast) ease;
}

.frame-lector__boton svg {
  width: 20px;
  height: 20px;
}

.frame-lector__boton:hover:not(:disabled),
.frame-lector__boton:focus-visible {
  color: var(--color-beige);
  border-color: var(--color-tan);
}

.frame-lector__boton:focus {
  outline: none;
}

.frame-lector__boton:focus-visible {
  outline: 2px solid var(--color-tan);
  outline-offset: 3px;
}

/* Primera y ultima pagina: atenuado y sin hover. Un control deshabilitado
   esta exento del minimo de contraste. */
.frame-lector__boton:disabled {
  opacity: 0.35;
  cursor: default;
}

.frame-lector__contador {
  min-width: 4.5em;
  font-family: var(--font-body);
  font-size: var(--text-small);
  font-variant-numeric: lining-nums tabular-nums;
  letter-spacing: 0.1em;
  text-align: center;
  color: var(--color-beige);
}

/* `--color-text-muted` sobre `--color-dark`: 4.65:1. */
.frame-lector__ayuda {
  margin-top: var(--space-sm);
  font-family: var(--font-body);
  font-size: var(--text-small);
  color: var(--color-text-muted);
  text-align: center;
}

/* ---- Panel de compra (#n01-compra) ----
   Tarjeta como `.contact__form`, pero en `--color-dark-alt` solido: sobre el
   beige da 9.18:1, el tan 5.71:1 y `--color-text-muted` 5.25:1.
   `scroll-margin-top` se SUMA al `scroll-padding-top: 100px` de `html` (D24):
   el salto desde la cinta deja la tarjeta a 116px del borde, 40px por debajo
   del nav de celular (76px) y 20px del de escritorio (96px). */
.frame-compra {
  max-width: 520px;
  margin: var(--space-xl) auto 0;
  padding: var(--space-lg) var(--space-md);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-dark-alt);
  text-align: center;
  scroll-margin-top: var(--space-md);
}

.frame-compra__titulo {
  margin-bottom: var(--space-sm);
  font-family: var(--font-display);
  font-size: var(--text-heading);
  font-weight: 400;
  line-height: 1.2;
  color: var(--color-beige);
  text-wrap: balance;
  /* Cormorant trae cifras de estilo antiguo: "Nº 01" se leia "o1". */
  font-variant-numeric: lining-nums;
}

.frame-compra__texto {
  margin-bottom: var(--space-lg);
  font-family: var(--font-body);
  font-size: var(--text-body);
  font-weight: 300;
  line-height: 1.7;
  color: var(--color-beige);
}

/* Boton y opciones de pago a lo ancho de la tarjeta, como `.form__submit`. */
.frame-compra__boton,
.frame-compra__stripe,
.frame-compra__ath {
  width: 100%;
}

/* `.btn` es inline-flex y anularia el `hidden` que pone js/revista.js. */
.frame-compra__boton[hidden],
.frame-compra__stripe[hidden],
.frame-compra__ath[hidden] {
  display: none;
}

.frame-compra__opciones {
  display: grid;
  gap: var(--space-sm);
  margin-top: var(--space-lg);
}

.frame-compra__opciones[hidden] {
  display: none;
}

/* Rotulo con la tipografia de `.form__label`. */
.frame-compra__opciones-titulo {
  font-family: var(--font-body);
  font-size: var(--text-micro);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: var(--color-tan);
}

/* Contenedor del boton oficial de ATH Movil: lo centra y no ocupa sitio
   mientras esta vacio. */
.frame-compra__ath-oficial {
  display: flex;
  justify-content: center;
}

.frame-compra__ath-oficial:empty {
  display: none;
}

/* El estado comparte TODO con `.form__status` (seccion 10: los tres
   modificadores, el foco programatico y `[hidden]`). Aqui solo lo propio:
   alineado a la izquierda dentro de una tarjeta centrada, y plegado cuando
   esta vacio. El marcado no trae `hidden` de inicio, asi que sin esto la caja
   vacia se pintaria desde la carga. No se pliega con `display: none`: el JS
   lo muestra vacio un cuadro antes de escribir para que la region viva se
   anuncie, y tiene que seguir en el arbol de accesibilidad. */
.frame-compra__estado {
  text-align: left;
}

.frame-compra__estado:empty {
  margin-top: 0;
  padding: 0;
  border-width: 0;
  background: none;
}

.frame-compra__estado:empty::before {
  content: none;
}

/* "¿Ya la compraste? Inicia sesion". `> a`: el enlace no lleva clase propia
   (D34). Mismo patron de area tactil que `.form__privacidad-link`. */
.frame-compra__cuenta {
  margin-top: var(--space-md);
  font-family: var(--font-body);
  font-size: var(--text-small);
}

.frame-compra__cuenta > a {
  display: inline-block;
  padding: 0.7rem 0;
  color: var(--color-tan);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.25em;
  transition: color var(--duration-fast) ease;
}

.frame-compra__cuenta > a:hover,
.frame-compra__cuenta > a:focus-visible {
  color: var(--color-beige);
}

.frame-compra__cuenta > a:focus {
  outline: none;
}

.frame-compra__cuenta > a:focus-visible {
  outline: 2px solid var(--color-tan);
  outline-offset: 2px;
}

.frame-edicion__cierre {
  margin-top: var(--space-xl);
  text-align: center;
}

/* Misma voz que `.hero__quote` y `.frame-teaser__cita`. */
.frame-edicion__cita {
  max-width: 520px;
  margin: 0 auto;
  font-family: var(--font-display);
  font-size: var(--text-heading);
  font-style: italic;
  font-weight: 400;
  line-height: 1.3;
  color: var(--color-beige);
  text-wrap: balance;
}

.frame-edicion__cita p {
  margin: 0;
}

.frame-edicion__cierre .cta-band {
  margin-top: var(--space-lg);
}

/* Escritorio: pliego de dos paginas lado a lado, como una revista abierta,
   con un filo de 4px entre ellas. La pista se limita a dos paginas del alto
   disponible mas ese filo. Cada pagina sigue siendo un iman: avanzar mueve
   una pagina. La ayuda "Desliza" sobra con raton y botones. */
@media (min-width: 1024px) {
  .frame-lector__pista {
    gap: var(--space-xs);
    width: min(100%, calc(var(--frame-alto-pagina) * 0.8 * 2 + var(--space-xs)));
  }

  .frame-lector__pagina {
    flex-basis: calc((100% - var(--space-xs)) / 2);
  }

  .frame-lector__ayuda {
    display: none;
  }
}

/* ===========================
   7. PORTFOLIO
   =========================== */

.portfolio-showcase {
  position: relative;
  overflow: hidden;
  padding: clamp(4rem, 9vw, 9rem) 0 5rem;
  background: linear-gradient(155deg, var(--color-dark) 0%, var(--color-dark-deep) 100%);
}

.portfolio-showcase::before {
  content: '';
  position: absolute;
  width: 28rem;
  height: 28rem;
  top: 7rem;
  right: -12rem;
  border-radius: 50%;
  background: var(--color-burgundy-dim);
  filter: blur(12px);
}

.portfolio-showcase .container {
  position: relative;
  z-index: 1;
}

.portfolio-showcase__intro {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(17rem, 0.5fr);
  align-items: end;
  gap: var(--space-xl);
  margin-bottom: var(--space-xl);
}

.portfolio-showcase__eyebrow {
  display: block;
  margin-bottom: var(--space-md);
  font-size: var(--text-micro);
  font-weight: 600;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--color-tan);
}

.portfolio-showcase__title {
  max-width: 12ch;
  font-family: var(--font-display);
  font-size: clamp(3.4rem, 7.8vw, 8.2rem);
  font-weight: 400;
  line-height: 0.82;
  letter-spacing: -0.045em;
  color: var(--color-beige);
}

.portfolio-showcase__title em {
  color: var(--color-tan);
}

.portfolio-showcase__summary {
  padding-left: var(--space-lg);
  border-left: 1px solid var(--color-border);
}

.portfolio-showcase__summary p {
  margin-bottom: var(--space-lg);
  color: var(--color-gray);
  line-height: 1.7;
}

.portfolio-showcase__stats {
  display: flex;
  gap: var(--space-lg);
  color: var(--color-text-muted);
  font-size: var(--text-small);
}

.portfolio-showcase__stats span {
  display: grid;
  gap: var(--space-xs);
}

.portfolio-showcase__stats strong {
  font-family: var(--font-display);
  font-size: var(--text-heading);
  font-weight: 400;
  line-height: 1;
  color: var(--color-beige);
}

.portfolio-ticker {
  overflow: hidden;
  margin: 0 calc(var(--space-lg) * -1) var(--space-xl);
  padding: var(--space-md) 0;
  border-top: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
}

.portfolio-ticker__track {
  display: flex;
  align-items: center;
  width: max-content;
  animation: portfolioTicker 24s linear infinite;
}

/* Se pausa con raton, con foco dentro y con la clase de estado que pone el JS
   (D23): en tactil no hay hover, asi que `:hover` solo no bastaba. */
.portfolio-ticker:hover .portfolio-ticker__track,
.portfolio-ticker:focus-within .portfolio-ticker__track,
.portfolio-ticker--paused .portfolio-ticker__track {
  animation-play-state: paused;
}

.portfolio-ticker__track span {
  padding: 0 var(--space-lg);
  font-family: var(--font-display);
  font-size: var(--text-heading);
  font-style: italic;
  color: var(--color-beige);
}

.portfolio-ticker__track i {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--color-burgundy);
}

@keyframes portfolioTicker {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

.portfolio-toolbar {
  position: sticky;
  top: 84px;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-lg);
  margin-bottom: var(--space-lg);
  padding: var(--space-md);
  border: 1px solid var(--color-border);
  border-radius: 18px;
  background: var(--color-dark-alt);
  box-shadow: 0 14px 40px var(--color-dark-deep);
}

.portfolio-filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
}

/* `min-height: 44px` (D7): con solo el padding se quedaba en ~43px. El
   inline-flex centra el texto en esa altura. `--color-text-muted` ya pasa AA
   aqui: 5.25:1 sobre el `--color-dark-alt` de `.portfolio-toolbar` (D3). */
.portfolio-filter {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0.62rem 1rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  color: var(--color-text-muted);
  font-size: var(--text-small);
  transition: color var(--duration-fast) ease, background var(--duration-fast) ease, transform var(--duration-fast) var(--ease-out-expo);
}

.portfolio-filter:hover,
.portfolio-filter:focus-visible {
  color: var(--color-beige);
  transform: translateY(-2px);
}

.portfolio-filter:focus-visible {
  outline: 2px solid var(--color-tan);
  outline-offset: 2px;
}

.portfolio-filter.is-active {
  border-color: var(--color-burgundy);
  background: var(--color-burgundy);
  color: var(--color-beige);
}

.portfolio-toolbar__count {
  flex-shrink: 0;
  color: var(--color-text-muted);
  font-size: var(--text-small);
}

.portfolio-toolbar__count span {
  color: var(--color-tan);
  font-weight: 600;
}

.portfolio-gallery {
  display: grid;
  grid-template-columns: repeat(24, minmax(0, 1fr));
  grid-auto-flow: dense;
  grid-auto-rows: 36px;
  gap: 8px;
}

.portfolio-card {
  position: relative;
  grid-column: span 4;
  grid-row: span 6;
  min-width: 0;
  overflow: hidden;
  border-radius: 10px;
  background: var(--color-dark-alt);
  text-align: left;
  isolation: isolate;
  transition: transform var(--duration-normal) var(--ease-out-expo), border-radius var(--duration-fast) ease;
}

.portfolio-card[hidden] {
  display: none;
}

.portfolio-card--wide {
  grid-column: span 8;
  grid-row: span 5;
}

.portfolio-card--tall {
  grid-row: span 8;
}

.portfolio-card--compact {
  grid-row: span 4;
}

.portfolio-card::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(180deg, transparent 45%, var(--color-overlay) 100%);
  opacity: 0.78;
  transition: opacity var(--duration-fast) ease;
}

.portfolio-card__picture {
  display: block;
  width: 100%;
  height: 100%;
}

.portfolio-card__image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  transition: transform var(--duration-slow) var(--ease-out-expo), filter var(--duration-fast) ease;
}

.portfolio-card__meta {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 2;
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: var(--space-sm);
}

.portfolio-card__collection {
  font-family: var(--font-display);
  font-size: clamp(0.95rem, 1.2vw, 1.2rem);
  font-weight: 400;
  color: var(--color-beige);
}

.portfolio-card__number {
  display: grid;
  place-items: center;
  width: 1.6rem;
  height: 1.6rem;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--color-tan);
  color: var(--color-dark-deep);
  font-size: var(--text-micro);
  font-weight: 600;
}

.portfolio-card:hover,
.portfolio-card:focus-visible {
  z-index: 3;
  border-radius: 8px 28px 8px 28px;
  transform: translateY(-6px) rotate(-0.45deg);
}

.portfolio-card:focus-visible {
  outline: 3px solid var(--color-tan);
  outline-offset: 3px;
}

.portfolio-card:hover::after,
.portfolio-card:focus-visible::after {
  opacity: 1;
}

.portfolio-card:hover .portfolio-card__image,
.portfolio-card:focus-visible .portfolio-card__image {
  transform: scale(1.055);
}

.portfolio-gallery.is-filtering .portfolio-card:not([hidden]),
.portfolio-gallery.is-filtering .portfolio-cover:not([hidden]) {
  animation: portfolioCardIn var(--duration-normal) var(--ease-out-expo) both;
}

@keyframes portfolioCardIn {
  from { opacity: 0; transform: translateY(18px) scale(0.98); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

/* ---- Portadas por modelo (vista "Todo") ----
   Una portada por modelo; al pulsarla se entra a su catalogo. Viven en el mismo
   `.portfolio-gallery` que las `.portfolio-card` y se ocultan con `hidden`.
   Todas con la misma proporcion 3:4 (las fotos son verticales), sin spans:
   la rejilla de 24/12/3 columnas de las tarjetas no sirve para 2 columnas en
   celular, asi que mientras haya portadas visibles la galeria cambia de rejilla
   con :has() (mobile-first: 2 columnas -> 3 desde 768px -> 4 desde 1024px).
   Al volver al catalogo las portadas llevan `hidden`, :has() deja de cumplirse
   y la rejilla de tarjetas regresa sola. Navegador sin :has(): se queda la
   rejilla de tarjetas (degrada sin romperse, solo pierde las 2 columnas). */
.portfolio-gallery:has(> .portfolio-cover:not([hidden])) {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-auto-rows: auto;
  gap: var(--space-sm);
}

.portfolio-cover {
  position: relative;
  display: block;
  align-self: start;
  width: 100%;
  min-width: 0;
  aspect-ratio: 3 / 4;
  padding: 0;
  overflow: hidden;
  border-radius: 10px;
  background: var(--color-dark-alt);
  text-align: left;
  cursor: pointer;
  isolation: isolate;
}

.portfolio-cover[hidden] {
  display: none;
}

.portfolio-cover__picture {
  position: absolute;
  inset: 0;
  display: block;
}

.portfolio-cover__image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 20%;
  transition: transform var(--duration-slow) var(--ease-out-expo);
}

/* El scrim vive en el propio bloque del texto, no sobre toda la foto: el
   nombre queda siempre sobre --color-overlay casi pleno (beige sobre ese fondo
   da >= 4.5:1 incluso sobre una foto clara) y la cara se ve limpia. */
.portfolio-cover__meta {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--space-lg) var(--space-sm) var(--space-sm);
  background: linear-gradient(180deg, transparent 0%, var(--color-overlay) 35%);
}

.portfolio-cover__name {
  font-family: var(--font-display);
  font-size: clamp(1.1rem, 0.95rem + 0.8vw, 1.6rem);
  font-weight: 600;
  line-height: 1.1;
  color: var(--color-beige);
  overflow-wrap: anywhere;
}

.portfolio-cover__cta {
  font-size: var(--text-micro);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-beige);
  transition: opacity var(--duration-fast) ease, transform var(--duration-fast) var(--ease-out-expo);
}

.portfolio-cover:focus-visible {
  outline: 3px solid var(--color-tan);
  outline-offset: 3px;
}

/* En celular no hay hover: la CTA se ve siempre. Solo donde hay puntero fino
   y hover real (`hover: hover`) se oculta en reposo y aparece al pasar o al
   enfocar con teclado. El hueco se conserva (opacity, no display), asi el
   bloque de texto no cambia de alto al aparecer. */
@media (hover: hover) and (pointer: fine) {
  .portfolio-cover__cta {
    opacity: 0;
    transform: translateY(6px);
  }

  .portfolio-cover:hover .portfolio-cover__cta,
  .portfolio-cover:focus-visible .portfolio-cover__cta {
    opacity: 1;
    transform: translateY(0);
  }

  .portfolio-cover:hover .portfolio-cover__image,
  .portfolio-cover:focus-visible .portfolio-cover__image {
    transform: scale(1.05);
  }
}

@media (min-width: 768px) {
  .portfolio-gallery:has(> .portfolio-cover:not([hidden])) {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-md);
  }
}

@media (min-width: 1024px) {
  .portfolio-gallery:has(> .portfolio-cover:not([hidden])) {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

@media (prefers-reduced-motion: reduce) {
  .portfolio-cover__cta,
  .portfolio-cover:hover .portfolio-cover__image,
  .portfolio-cover:focus-visible .portfolio-cover__image,
  .portfolio-cover:hover .portfolio-cover__cta,
  .portfolio-cover:focus-visible .portfolio-cover__cta {
    transform: none;
  }
}

.portfolio-showcase__noscript {
  margin-top: var(--space-lg);
  color: var(--color-tan);
  text-align: center;
}

body.portfolio-lightbox-open {
  overflow: hidden;
}

.portfolio-lightbox {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: grid;
  grid-template-columns: minmax(3rem, 0.12fr) minmax(0, 1fr) minmax(3rem, 0.12fr);
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-lg);
  background: var(--color-dark-deep);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--duration-fast) ease, visibility var(--duration-fast) ease;
}

.portfolio-lightbox.is-open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

.portfolio-lightbox__figure {
  display: grid;
  justify-items: center;
  gap: var(--space-md);
  min-width: 0;
}

.portfolio-lightbox__picture {
  display: grid;
  place-items: center;
  width: 100%;
  min-width: 0;
}

.portfolio-lightbox__image {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 78vh;
  border-radius: 12px;
  object-fit: contain;
  box-shadow: 0 24px 80px var(--color-dark);
}

.portfolio-lightbox__caption {
  display: flex;
  justify-content: space-between;
  width: min(100%, 62rem);
  color: var(--color-text-muted);
  font-size: var(--text-small);
}

.portfolio-lightbox__collection {
  color: var(--color-beige);
  font-family: var(--font-display);
  font-size: var(--text-heading);
}

.portfolio-lightbox__close,
.portfolio-lightbox__nav {
  display: grid;
  place-items: center;
  border: 1px solid var(--color-border);
  border-radius: 50%;
  color: var(--color-beige);
  transition: background var(--duration-fast) ease, transform var(--duration-fast) var(--ease-out-expo);
}

.portfolio-lightbox__close:hover,
.portfolio-lightbox__nav:hover,
.portfolio-lightbox__close:focus-visible,
.portfolio-lightbox__nav:focus-visible {
  background: var(--color-burgundy);
  transform: scale(1.08);
}

.portfolio-lightbox__close:focus-visible,
.portfolio-lightbox__nav:focus-visible {
  outline: 2px solid var(--color-tan);
  outline-offset: 3px;
}

.portfolio-lightbox__close {
  position: absolute;
  top: var(--space-lg);
  right: var(--space-lg);
  width: 3rem;
  height: 3rem;
  font-size: 1.8rem;
}

.portfolio-lightbox__nav {
  width: 3.5rem;
  height: 3.5rem;
  justify-self: center;
  font-size: 1.35rem;
}

.portfolio-lightbox__nav--prev {
  grid-column: 1;
}

.portfolio-lightbox__figure {
  grid-column: 2;
}

.portfolio-lightbox__nav--next {
  grid-column: 3;
}

@media (max-width: 1023px) {
  .portfolio-showcase__intro {
    grid-template-columns: 1fr;
  }

  .portfolio-showcase__summary {
    max-width: 38rem;
  }

  .portfolio-gallery {
    grid-template-columns: repeat(12, minmax(0, 1fr));
  }

  .portfolio-card {
    grid-column: span 3;
  }

  .portfolio-card--wide {
    grid-column: span 6;
  }
}

@media (max-width: 767px) {
  .portfolio-showcase {
    padding-top: var(--space-xl);
  }

  .portfolio-showcase__intro {
    gap: var(--space-lg);
  }

  .portfolio-showcase__title {
    font-size: clamp(3rem, 17vw, 5.6rem);
  }

  .portfolio-showcase__summary {
    padding-left: 0;
    border-left: 0;
  }

  .portfolio-toolbar {
    top: 68px;
    display: block;
    padding: var(--space-sm);
    border-radius: 14px;
  }

  .portfolio-filters {
    flex-wrap: nowrap;
    overflow-x: auto;
    padding-bottom: var(--space-xs);
    scrollbar-width: none;
  }

  .portfolio-filters::-webkit-scrollbar {
    display: none;
  }

  .portfolio-filter {
    flex-shrink: 0;
  }

  .portfolio-toolbar__count {
    padding: var(--space-sm) var(--space-sm) 0;
  }

  .portfolio-gallery {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-auto-rows: 34px;
    gap: 6px;
  }

  .portfolio-card,
  .portfolio-card--wide {
    grid-column: span 1;
    grid-row: span 4;
    border-radius: 8px;
  }

  .portfolio-card--wide {
    grid-column: span 2;
    grid-row: span 4;
  }

  .portfolio-card--tall {
    grid-row: span 6;
  }

  .portfolio-card--compact {
    grid-row: span 3;
  }

  .portfolio-card__meta {
    display: block;
    padding: 6px;
  }

  .portfolio-card__collection {
    font-size: 0.8rem;
  }

  .portfolio-card__number {
    display: none;
  }

  .portfolio-lightbox {
    grid-template-columns: 1fr 1fr;
    grid-template-rows: minmax(0, 1fr) auto;
    padding: var(--space-md);
  }

  .portfolio-lightbox__figure {
    grid-column: 1 / -1;
    grid-row: 1;
  }

  .portfolio-lightbox__image {
    max-height: 72vh;
  }

  .portfolio-lightbox__nav {
    grid-row: 2;
    margin-top: var(--space-sm);
  }

  .portfolio-lightbox__nav--prev {
    grid-column: 1;
  }

  .portfolio-lightbox__nav--next {
    grid-column: 2;
  }

  .portfolio-lightbox__caption {
    align-items: end;
  }

  .portfolio-lightbox__collection {
    font-size: 1.4rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .portfolio-ticker__track {
    animation: none;
  }

  .portfolio-card:hover,
  .portfolio-card:focus-visible,
  .portfolio-card:hover .portfolio-card__image,
  .portfolio-card:focus-visible .portfolio-card__image {
    transform: none;
  }
}

/* ---- Muestra de trabajo (tira de miniaturas) ----
   Bloque propio, no `.portfolio-*`: aqui no hay filtros, ni lightbox, ni
   mosaico infinito, solo miniaturas que llevan a portafolio.html. Vive al
   final de la seccion 7 porque comparte origen de imagenes con la galeria
   (derivados de `images-optimized/`) y lleva sus media queries locales, como
   el resto de Portfolio.
   Mobile-first: 2 columnas en celular (4 fotos = una rejilla 2x2 de ~390px de
   alto a 375px, no una tira interminable de scroll) y una sola fila de 4 a
   partir de tablet. */
.work-preview {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-md);
}

/* Cada miniatura es el enlace entero: en celular el dedo va a la foto, no al
   boton de debajo. A 375px mide ~147x184px, muy por encima de los 44x44. */
.work-preview__item {
  position: relative;
  display: block;
  overflow: hidden;
  border-radius: 12px;
  aspect-ratio: 4 / 5;
  background: var(--color-dark-alt);
}

/* El <picture> es inline por defecto y no le daria altura al <img>: sin esto,
   `height: 100%` de la imagen no tendria contra que resolverse. Mismo apano que
   `.portfolio-card__picture`. */
.work-preview__picture {
  display: block;
  width: 100%;
  height: 100%;
}

.work-preview__image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--duration-normal) var(--ease-out-expo);
}

/* Mismo efecto para raton y teclado, hecho con `transform` para no mover el
   layout; el foco anade ademas el contorno comun de `--color-tan`. */
.work-preview__item:hover .work-preview__image,
.work-preview__item:focus-visible .work-preview__image {
  transform: scale(1.05);
}

.work-preview__item:focus-visible {
  outline: 2px solid var(--color-tan);
  outline-offset: 3px;
}

@media (min-width: 768px) {
  .work-preview {
    grid-template-columns: repeat(4, 1fr);
  }
}

@media (prefers-reduced-motion: reduce) {
  .work-preview__item:hover .work-preview__image,
  .work-preview__item:focus-visible .work-preview__image {
    transform: none;
  }
}

/* ===========================
   8. SERVICES
   =========================== */

.services {
  background: var(--color-dark-alt);
}

.services__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-lg);
}

.service-card {
  padding: var(--space-xl) 0;
  border-top: 2px solid var(--color-burgundy);
}

.service-card__number {
  font-family: var(--font-display);
  font-size: var(--text-display);
  font-weight: 400;
  color: var(--color-burgundy);
  opacity: 0.3;
  line-height: 1;
  margin-bottom: var(--space-lg);
}

.service-card__title {
  font-family: var(--font-display);
  font-size: var(--text-heading);
  font-weight: 400;
  color: var(--color-beige);
  margin-bottom: var(--space-md);
}

.service-card__description {
  font-family: var(--font-body);
  font-size: var(--text-body);
  color: var(--color-text-muted);
  line-height: 1.7;
  margin-bottom: var(--space-lg);
}

.service-card__link {
  font-family: var(--font-body);
  font-size: var(--text-small);
  font-weight: 500;
  color: var(--color-tan);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
}

.service-card__link svg {
  width: 16px;
  height: 16px;
  transition: transform var(--duration-fast) var(--ease-out-expo);
}

/* Antes el hover tambien crecia el `gap` (layout, prohibido por NEVER, D26):
   el mismo desplazamiento del icono se consigue solo con `transform`, asi
   que el `translateX` absorbe los 8px que aportaba el gap. */
.service-card__link:hover svg {
  transform: translateX(12px);
}

/* ===========================
   8b. PAQUETES
   =========================== */

/* Seccion de precios de `servicios.html`, el ultimo paso antes de la
   cotizacion. Todo aqui sirve a la comparacion: mismo alto de tarjeta, mismo
   CTA abajo del todo y un plan marcado como preferente.
   Se numera "8b" por lo mismo que la seccion 6b: renumerar de la 9 a la 16
   romperia las citas de docs/ESTILO.md y las de los propios comentarios.
   Fondo: ninguno. `.services` es `--color-dark-alt` y esta seccion se queda con
   el `--color-dark` del body, asi que la alternancia sale sola. */

.paquetes__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-lg);
  /* Mobile-first: una sola columna hasta 1024px. Con tres planes, dos columnas
     a 768px dejan una tarjeta huerfana y parten la comparacion justo donde se
     decide; apiladas se leen enteras. No se inventa breakpoint: el corte es el
     mismo 1024px que ya usa `.services__grid`.
     El tope de medida evita la tarjeta de ~950px de ancho en tablet. */
  max-width: 440px;
  margin: 0 auto;
}

/* Flex column + `margin-top: auto` en el CTA: las tres tarjetas se estiran a la
   altura de la mas alta (comportamiento por defecto de la rejilla) y el boton
   queda pegado abajo en todas, sin alturas fijas. */
.paquete {
  display: flex;
  flex-direction: column;
  padding: var(--space-lg);
  background: var(--color-dark-alt);
  border: 1px solid var(--color-border);
  border-radius: 8px;
}

/* Plan preferente. La jerarquia la dan borde, bano de color y sombra; NO un
   `transform`, porque `[data-reveal].is-revealed` (seccion 12, mas abajo en la
   cascada y con mayor especificidad) fija `transform: none` y borraria
   cualquier `scale` de aqui. En celular ademas un `scale` sobre una tarjeta a
   ancho completo la sacaria del contenedor. */
.paquete--destacado {
  position: relative;
  border-width: 2px;
  border-color: var(--color-burgundy);
  background:
    linear-gradient(var(--color-burgundy-dim), var(--color-burgundy-dim)),
    var(--color-dark-alt);
  box-shadow: 0 12px 40px var(--color-burgundy-dim);
}

/* Etiqueta a caballo del borde superior: la mitad de abajo cae dentro de los
   32px de padding de la tarjeta, asi que nunca llega al nombre del paquete.
   Centrada y con tope de ancho para no desbordar: a 320px la tarjeta mide
   256px y la cinta no pasa de 224px. */
.paquete__cinta {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-50%, -50%);
  max-width: calc(100% - var(--space-lg));
  padding: var(--space-xs) var(--space-md);
  background: var(--color-burgundy);
  border-radius: var(--radius-pill);
  font-family: var(--font-body);
  font-size: var(--text-micro);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  line-height: 1.6;
  color: var(--color-beige);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.paquete__nombre {
  font-family: var(--font-display);
  font-size: var(--text-heading);
  font-weight: 400;
  line-height: 1.2;
  color: var(--color-tan);
  margin-bottom: var(--space-xs);
}

/* El dato que decide la cotizacion: el tamano mas grande de la tarjeta.
   Precio publicado (hoy "$195", Headshots); el calculo de cada cifra vive en
   docs/PRECIOS.md. `tabular-nums` para que las cifras se alineen y comparen
   igual entre planes (AGENTS.md, Reglas de interfaz). `lining-nums` porque
   Cormorant Garamond trae cifras de estilo antiguo por defecto: el "1" queda a
   altura de x y se lee como "I" ("$I95"), y el precio pierde altura frente al
   nombre. Con cifras de caja alta se lee de un vistazo. `overflow-wrap: anywhere`
   queda como red de seguridad si un precio futuro mas largo no cabe en la
   columna de 234px de tres planes a 1024px: se parte en vez de desbordar. Mismo
   recurso que en `.form__status`. */
.paquete__precio {
  font-family: var(--font-display);
  font-size: var(--text-display);
  font-weight: 600;
  line-height: 1.1;
  font-variant-numeric: lining-nums tabular-nums;
  color: var(--color-beige);
  margin-bottom: var(--space-md);
  overflow-wrap: anywhere;
}

/* `--color-beige` (9.18:1 sobre la tarjeta), no `--color-text-muted` (5.25:1
   ahi): es el texto que decide la cotizacion. El "apagado" lo da el peso 300. */
.paquete__resumen {
  font-family: var(--font-body);
  font-size: var(--text-body);
  font-weight: 300;
  line-height: 1.7;
  color: var(--color-beige);
  padding-bottom: var(--space-lg);
  border-bottom: 1px solid var(--color-border);
  margin-bottom: var(--space-lg);
}

/* El `margin-bottom` de aqui es la separacion minima garantizada frente al CTA:
   `margin-top: auto` del boton solo aporta cuando sobra espacio, y en la
   tarjeta mas alta vale 0. */
.paquete__lista {
  margin-bottom: var(--space-lg);
}

/* Mismo marcador que `.legal__list-item`: raya de 16px en burgundy a 0.8em de
   la linea base. No se duplica el selector para no atar dos bloques BEM
   distintos a una sola regla. */
.paquete__item {
  position: relative;
  font-family: var(--font-body);
  font-size: var(--text-body);
  font-weight: 300;
  line-height: 1.7;
  color: var(--color-beige);
  padding-left: var(--space-lg);
  margin-bottom: var(--space-md);
}

.paquete__item:last-child {
  margin-bottom: 0;
}

.paquete__item::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.8em;
  width: 16px;
  height: 1px;
  background: var(--color-burgundy);
}

/* Solo ancho, posicion y nada mas: el padding, el radio, la tipografia y la
   transicion los pone `.btn`. Mismo patron que `.form__submit`. */
.paquete__cta {
  width: 100%;
  margin-top: auto;
}

.paquetes__nota {
  font-family: var(--font-body);
  font-size: var(--text-small);
  font-weight: 300;
  line-height: 1.7;
  color: var(--color-tan);
  text-align: center;
  max-width: 520px;
  margin: var(--space-xl) auto 0;
}

/* Escritorio: las tres columnas juntas, que es donde la comparacion vale.
   `max-width: none` deshace el tope de medida de la version apilada. */
@media (min-width: 1024px) {
  .paquetes__grid {
    grid-template-columns: repeat(3, 1fr);
    max-width: none;
  }

  .paquete {
    padding: var(--space-xl) var(--space-lg);
  }

  /* Respaldo sin `:has()`: si el navegador descarta la regla de "paquete
     unico" de abajo, la tarjeta sola ocupa las tres columnas en vez de quedar
     angosta en la primera. */
  .paquete:only-child {
    grid-column: 1 / -1;
  }
}

/* Paquete unico (hoy solo Headshots). La rejilla esta pensada para tres planes:
   con uno solo dejaria una columna de ~370px pegada a la izquierda en
   escritorio y una tarjeta de 440px en tablet. `:has(> .paquete:only-child)` la
   vuelve de una sola columna, centrada y con un tope de 880px: ancho de sobra
   para que la muestra de 4 columnas se vea, sin lineas de texto kilometricas.
   Especificidad (0,3,0): gana al tope de 440px y a las 3 columnas de 1024px
   sin importar el orden. Sin marcado extra: si manana hay tres planes, la regla
   deja de cumplirse sola y vuelve la rejilla de comparacion. */
.paquetes__grid:has(> .paquete:only-child) {
  grid-template-columns: minmax(0, 1fr);
  max-width: 880px;
}

/* Muestra de trabajo dentro del paquete: 8 retratos verticales. 2 columnas en
   celular (a 320px: ~92x138px por foto; a 375px: ~119x179px) y 4 desde 768px.
   Son imagenes, no controles: sin enlace, sin foco, sin cursor. El
   `margin-bottom` separa de lo que sigue (lista o CTA); `margin-top: auto` del
   CTA sigue empujando el boton al fondo. */
.paquete__muestra {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-sm);
  margin-bottom: var(--space-lg);
}

/* `aspect-ratio` reserva la caja antes de que cargue la foto (cero salto de
   layout) y el fondo evita un hueco blanco mientras llega. La imagen va
   absoluta para no depender de que el `<picture>` (inline por defecto) le
   preste altura. Radio de 8px, el mismo de `.paquete`: no anade valor nuevo. */
.paquete__foto {
  position: relative;
  margin: 0;
  aspect-ratio: 2 / 3;
  overflow: hidden;
  border-radius: 8px;
  background: var(--color-dark);
}

/* `center 20%` conserva el rostro si el recorte llega a ajustar: en un retrato
   vertical la cara cae en el tercio superior, no en el centro. */
.paquete__foto-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 20%;
}

@media (min-width: 768px) {
  .paquete__muestra {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/* Zoom suave solo con raton real. En tactil no hay hover y la foto no se mueve.
   `no-preference` va en la misma consulta: quien pide menos movimiento no ve el
   zoom, sin necesitar un bloque de `reduce` aparte. Solo `transform`. */
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  .paquete__foto-img {
    transition: transform var(--duration-normal) var(--ease-out-expo);
  }

  .paquete__foto:hover .paquete__foto-img {
    transform: scale(1.04);
  }
}

/* ===========================
   9. ABOUT
   =========================== */

.about {
  background: var(--color-dark);
}

.about__inner {
  display: grid;
  grid-template-columns: 1.2fr 1fr;
  gap: var(--space-xl);
  align-items: center;
}

.about__image-wrapper {
  aspect-ratio: 3 / 4;
  border-radius: 4px;
  overflow: hidden;
  background: linear-gradient(135deg, var(--color-dark-alt) 0%, var(--color-dark-deep) 100%);
}

.about__picture {
  display: block;
  width: 100%;
  height: 100%;
}

/* La foto es cuadrada y el marco es 3/4 (4/3 en celular): cover recorta y
   `left center` conserva el rostro, que esta a la izquierda del encuadre. */
.about__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: left center;
}

.about__content {
  padding: var(--space-lg) 0;
}

.about__eyebrow {
  font-family: var(--font-body);
  font-size: var(--text-micro);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.3em;
  color: var(--color-tan);
  margin-bottom: var(--space-md);
}

.about__title {
  font-family: var(--font-display);
  font-size: var(--text-display);
  font-weight: 400;
  line-height: 1.15;
  color: var(--color-beige);
  margin-bottom: var(--space-lg);
}

.about__text {
  font-family: var(--font-body);
  font-size: var(--text-body);
  color: var(--color-text-muted);
  line-height: 1.8;
  margin-bottom: var(--space-xl);
}

.about__stats {
  display: flex;
  gap: var(--space-xl);
}

.stat {
  text-align: left;
}

.stat__number {
  font-family: var(--font-display);
  font-size: var(--text-heading);
  font-weight: 600;
  color: var(--color-burgundy);
  line-height: 1;
  margin-bottom: var(--space-xs);
}

.stat__label {
  font-family: var(--font-body);
  font-size: var(--text-small);
  color: var(--color-text-muted);
}

/* ===========================
   10. CONTACT
   =========================== */

.contact {
  background:
    radial-gradient(ellipse 60% 50% at 50% 60%,
      rgba(124, 47, 52, 0.08) 0%,
      transparent 70%),
    var(--color-dark-alt);
}

.contact .section-header {
  margin-bottom: var(--space-xl);
}

.contact__inner {
  display: grid;
  grid-template-columns: 1fr 1.2fr;
  gap: var(--space-xl);
}

.contact__info {
  padding: var(--space-lg) 0;
}

.contact__info-title {
  font-family: var(--font-display);
  font-size: var(--text-heading);
  font-weight: 400;
  color: var(--color-beige);
  margin-bottom: var(--space-lg);
}

.contact__info-item {
  display: flex;
  align-items: flex-start;
  gap: var(--space-md);
  margin-bottom: var(--space-lg);
}

.contact__info-icon {
  width: 20px;
  height: 20px;
  color: var(--color-tan);
  flex-shrink: 0;
  margin-top: 2px;
}

.contact__info-label {
  font-family: var(--font-body);
  font-size: var(--text-micro);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: var(--color-tan);
  margin-bottom: var(--space-xs);
}

.contact__info-value {
  font-family: var(--font-body);
  font-size: var(--text-body);
  color: var(--color-beige);
}

.contact__info-value a {
  transition: color var(--duration-fast) ease;
}

.contact__info-value a:hover {
  color: var(--color-tan);
}

.contact__socials {
  display: flex;
  gap: var(--space-md);
  margin-top: var(--space-xl);
}

/* 44x44: area tactil minima del proyecto (D7; antes 40x40). */
.contact__social-link {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--color-border);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-text-muted);
  transition: color var(--duration-fast) ease,
              border-color var(--duration-fast) ease,
              transform var(--duration-fast) ease;
}

.contact__social-link:hover {
  color: var(--color-tan);
  border-color: var(--color-tan);
  transform: translateY(-2px);
}

.contact__social-link svg {
  width: 18px;
  height: 18px;
}

/* Contact form */
.contact__form {
  padding: var(--space-xl);
  background: rgba(42, 37, 32, 0.5);
  border: 1px solid var(--color-border);
  border-radius: 8px;
}

/* Nota "los campos marcados con * son obligatorios", encima de los campos.
   Informacion secundaria: tamano pequeno y color de etiqueta, no de titular. */
.form__note {
  margin-bottom: var(--space-lg);
  font-family: var(--font-body);
  font-size: var(--text-small);
  color: var(--color-tan);
}

/* Asterisco de campo obligatorio: se separa del tan de la etiqueta sin gritar. */
.form__required {
  color: var(--color-burgundy-pale);
  font-weight: 600;
}

/* `minmax(0, 1fr)` y no `1fr` (que es `minmax(auto, 1fr)`): Safari iOS da
   al `<input type="date">` un ancho minimo intrinseco propio, y con `auto`
   la columna crecia hasta ese minimo y desbordaba la tarjeta a 320px. */
.form__row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-md);
}

.form__group {
  min-width: 0;
  margin-bottom: var(--space-lg);
}

/* Nota de ayuda bajo un campo (el aviso del 5% bajo #email en contacto.html,
   "Minimo 8 caracteres" en cuenta.html): pegada al campo que explica, sin
   los 32px que `.form__note` lleva como nota de cabecera del formulario. */
.form__group .form__note {
  margin: var(--space-sm) 0 0;
}

.form__label {
  display: block;
  font-family: var(--font-body);
  font-size: var(--text-micro);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: var(--color-tan);
  margin-bottom: var(--space-sm);
}

.form__input,
.form__select,
.form__textarea {
  width: 100%;
  padding: 0.8rem 0;
  border-bottom: 1px solid var(--color-border);
  color: var(--color-beige);
  font-size: var(--text-body);
  transition: border-color var(--duration-fast) ease;
}

/* Sin la `opacity: 0.6` de antes, que dejaba el placeholder en ~2.3:1. Con
   el token corregido da 5.20:1 sobre la tarjeta del formulario (D3). Se
   distingue de un valor escrito (`--color-beige`, 9.08:1) por tono. */
.form__input::placeholder,
.form__textarea::placeholder {
  color: var(--color-text-muted);
  opacity: 1;
}

.form__input:focus,
.form__select:focus,
.form__textarea:focus {
  border-bottom-color: var(--color-tan);
}

.form__select {
  cursor: pointer;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%23B19B86' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10l-5 5z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0 center;
}

.form__select option {
  background: var(--color-dark);
  color: var(--color-beige);
}

.form__textarea {
  resize: vertical;
  min-height: 100px;
}

/* ---- Campo de fecha (fecha del evento, opcional) ----
   Usa la misma `.form__input` que los demas; esto solo corrige lo que cada
   navegador le pone de suyo a `type="date"`:
   - `appearance: none` quita el boton nativo de Safari iOS, y `min-width: 0`
     su ancho minimo intrinseco (con `.form__row` en `minmax(0, 1fr)`, el
     campo ya no desborda a 320px).
   - Safari iOS vacio no pinta texto y el campo se encoge: `min-height` lo
     iguala al resto (padding de 0.8rem por lado + una linea de ~1.2em + el
     borde de 1px = ~46px a 16.16px), y el valor se alinea a la izquierda
     como en los otros campos.
   - Chrome y Edge pintan el icono del calendario en gris oscuro sobre la
     tarjeta oscura: se sustituye por el mismo SVG en `--color-tan` (#B19B86,
     5.65:1, por encima del 3:1 de un icono) que la flecha de `.form__select`.
     Firefox lo pinta claro por `color-scheme: dark` (seccion 0). */
.form__input[type="date"] {
  -webkit-appearance: none;
  appearance: none;
  display: block;
  min-width: 0;
  max-width: 100%;
  min-height: calc(1.2em + 1.6rem + 1px);
  text-align: left;
}

.form__input[type="date"]::-webkit-date-and-time-value {
  text-align: left;
}

.form__input[type="date"]::-webkit-calendar-picker-indicator {
  width: 18px;
  height: 18px;
  padding: 0;
  margin-left: var(--space-sm);
  cursor: pointer;
  opacity: 1;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' fill='none' stroke='%23B19B86' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' viewBox='0 0 24 24'%3E%3Crect x='3' y='4' width='18' height='18' rx='2'/%3E%3Cpath d='M16 2v4M8 2v4M3 10h18'/%3E%3C/svg%3E") center / 18px 18px no-repeat;
}

/* ---- Boton de enviar ----
   El marcado lleva el texto en `.form__submit-label` y un
   `.form__submit-spinner` vacio al lado. El JS ya no cambia el texto: pone
   `.form__submit--sending` y `disabled` mientras dura el `fetch`, y el
   spinner aparece junto a la etiqueta. `.btn` ya es inline-flex centrado;
   aqui solo se anade el `gap`. */
.form__submit {
  width: 100%;
  margin-top: var(--space-md);
  gap: var(--space-sm);
}

/* `min-width: 0`: la etiqueta envuelve en dos lineas a 320px ("ENVIAR" /
   "MENSAJE", ver docs/ESTILO.md) y, con el spinner al lado, tiene que poder
   encogerse en vez de empujarlo fuera del boton. */
.form__submit-label {
  min-width: 0;
}

/* Anillo de 3/4 en el color del texto del boton (`--color-beige`), a 1em
   para que escale con `--text-small`. Oculto fuera del envio. */
.form__submit-spinner {
  display: none;
  flex-shrink: 0;
  width: 1em;
  height: 1em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
}

.form__submit--sending .form__submit-spinner {
  display: inline-block;
  animation: form-submit-spin 0.8s linear infinite;
}

/* Solo `transform`. Con `prefers-reduced-motion` el giro se quita y el anillo
   queda quieto (seccion 13): sigue diciendo "en curso" junto al estado
   `--sending` de `.form__status`, que lo dice tambien en texto. */
@keyframes form-submit-spin {
  to {
    transform: rotate(360deg);
  }
}

/* Estado no interactivo mientras el `fetch` a Web3Forms esta en vuelo: el JS
   deshabilita el boton al lanzar la peticion y lo libera en el `finally`, asi
   que el bloqueo dura exactamente lo que dure el envio. Anula el realce de
   `.btn--primary:hover` (la especificidad de `:disabled:hover` gana) para que
   el boton no se levante ni cambie de color cuando ya no responde. */
.form__submit:disabled {
  opacity: 0.7;
  cursor: not-allowed;
}

.form__submit--sending:disabled {
  cursor: progress;
}

.form__submit:disabled:hover {
  background: var(--color-burgundy);
  border-color: var(--color-burgundy);
  transform: none;
  box-shadow: none;
}

/* Aviso de privacidad en el punto de recogida, justo debajo del boton.
   Informa sin competir con el CTA —`--text-small`, peso 300— pero es texto
   legal y tiene que poder leerse: va en `--color-tan`, que sobre la tarjeta del
   formulario da 5.58:1. `--color-text-muted` (5.20:1 ahi desde D3) tambien
   pasaria, pero el tan lo emparenta con `.form__note`.
   Alineado a la izquierda como `.form__note`: los dos son notas sobre el
   formulario y hacen pareja al principio y al final del bloque. */
.form__privacidad {
  margin-top: var(--space-md);
  font-family: var(--font-body);
  font-size: var(--text-small);
  font-weight: 300;
  line-height: 1.8;
  color: var(--color-tan);
}

/* El enlace se distingue por dos senales, no solo por color: sube a
   `--color-beige` (8.97:1, mas claro que el parrafo que lo rodea) y lleva
   subrayado propio.
   `display: inline-block` con padding vertical es lo que le da area tactil:
   24.5px de caja de linea + 22.4px de padding = ~47px de alto real. Con el
   `display: inline` por defecto el padding pintaria por encima de las lineas de
   arriba y de abajo y volveria pulsable medio parrafo — en una pagina cuyo
   objetivo es que el visitante ENVIE, un toque accidental que se lo lleva fuera
   sale caro. El subrayado es `text-decoration`, no `border-bottom`, para que el
   padding no lo separe del texto.
   Los 0.7rem no tienen token: es un valor ajustado para pasar de 44px sin
   inflar la ultima linea mas de la cuenta. Mismo caso que el padding de `.btn`. */
.form__privacidad-link {
  display: inline-block;
  padding: 0.7rem 0;
  color: var(--color-beige);
  text-decoration: underline;
  text-decoration-color: var(--color-tan);
  text-underline-offset: 0.2em;
  transition: color var(--duration-fast) var(--ease-out-expo),
              text-decoration-color var(--duration-fast) var(--ease-out-expo);
}

.form__privacidad-link:hover,
.form__privacidad-link:focus-visible {
  color: var(--color-beige);
  text-decoration-color: var(--color-beige);
}

/* Mensaje de estado del envio, bajo el boton.
   El JS lo muestra y lo oculta con el atributo `hidden`, asi que el `display`
   de aqui necesita si o si la regla `[hidden]` de abajo: sin ella el mensaje
   quedaria visible desde la carga y el formulario arrancaria con un estado
   falso. Los tres estados se distinguen por forma (barra lateral, contorno,
   marcador) ademas de por color, para no depender solo del color. */
.form__status,
.frame-compra__estado {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
  margin-top: var(--space-md);
  padding: var(--space-md);
  border-left: 2px solid var(--color-border);
  border-radius: 8px;
  font-family: var(--font-body);
  font-size: var(--text-body);
  line-height: 1.5;
  color: var(--color-beige);
  /* El correo del mensaje de error casi no cabe a 375px; hay que poder partirlo. */
  word-break: break-word; /* respaldo para Safari iOS < 15.4 */
  overflow-wrap: anywhere;
  animation: form-status-in var(--duration-fast) var(--ease-out-expo) both;
}

.form__status[hidden],
.frame-compra__estado[hidden] {
  display: none;
}

/* La region recibe foco programatico (`tabindex="-1"`) al enviar, para que el
   lector anuncie el resultado y el foco no caiga en <body> al deshabilitar el
   boton. Ese foco llega casi siempre tras un toque, asi que el contorno se
   apaga en `:focus` y solo vuelve en `:focus-visible`, que es cuando el
   navegador considera que hay un usuario de teclado. Mismo anillo que
   `.nav__logo`, `.portfolio-filter` y `.portfolio-card`. */
.form__status:focus,
.frame-compra__estado:focus {
  outline: none;
}

.form__status:focus-visible,
.frame-compra__estado:focus-visible {
  outline: 2px solid var(--color-tan);
  outline-offset: 3px;
}

/* El marcador cuelga a la izquierda y no se encoge al envolver el texto. */
.form__status::before,
.frame-compra__estado::before {
  flex-shrink: 0;
  font-weight: 600;
}

@keyframes form-status-in {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* Transitorio: sin fondo y con la barra discontinua, "todavia no es un
   resultado". */
.form__status--sending,
.frame-compra__estado--sending {
  border-left-style: dashed;
  border-left-color: var(--color-tan);
  color: var(--color-tan);
}

.form__status--sending::before,
.frame-compra__estado--sending::before {
  content: "\2026";
}

.form__status--success,
.frame-compra__estado--success {
  background: var(--color-tan-dim);
  border-left-color: var(--color-tan);
  color: var(--color-beige);
}

.form__status--success::before,
.frame-compra__estado--success::before {
  content: "\2713";
}

/* Error: clave de acceso invalida, respuesta de fallo del servicio o caida de
   red; mientras el `access_key` siga pendiente lo ve todo el mundo en cada
   envio. Contorno completo, fondo propio, texto mas pesado y el unico caliente. */
.form__status--error,
.frame-compra__estado--error {
  background: var(--color-burgundy-dim);
  border: 1px solid var(--color-burgundy-pale);
  border-left-width: 2px;
  color: var(--color-burgundy-pale);
  font-weight: 500;
}

.form__status--error::before,
.frame-compra__estado--error::before {
  content: "!";
}

/* ===========================
   10b. CUENTA
   =========================== */

/* cuenta.html: aviso de "pronto", paneles de invitado (entrar, recuperar,
   crear), contrasena nueva y vista con sesion. Se numera 10b por el mismo
   motivo que 6b. Los formularios reutilizan `.form__group`, `.form__label`,
   `.form__input`, `.form__note`, `.form__submit` y `.form__status` de la
   seccion 10 tal cual; aqui solo va lo propio de la cuenta.
   Varios bloques llevan `display` propio y js/revista.js los muestra y oculta
   con `hidden`: cada uno trae su regla `[hidden]`, o quedarian visibles. */

.cuenta__inner {
  max-width: 880px;
  margin: 0 auto;
}

/* Aviso general (sin JS, sin configurar o mientras carga). La `.cta-band`
   de dentro se acerca al texto: 32px en vez de los 64px entre secciones. */
.cuenta__aviso {
  max-width: 560px;
  margin: 0 auto;
  text-align: center;
}

.cuenta__aviso[hidden] {
  display: none;
}

.cuenta__aviso-texto {
  font-family: var(--font-body);
  font-size: var(--text-body);
  font-weight: 300;
  line-height: 1.7;
  color: var(--color-beige);
}

.cuenta__aviso .cta-band {
  margin-top: var(--space-lg);
}

/* Paneles: una columna en celular; desde 768px, "Inicia sesion" (y
   "Recupera tu contrasena" debajo, cuando se abre) a la izquierda y "Crea tu
   cuenta" a la derecha ocupando las dos filas. `:last-child` es el panel de
   crear: es el ultimo hijo de `.cuenta__paneles` en el marcado. */
.cuenta__paneles {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-lg);
  align-items: start;
}

.cuenta__paneles[hidden] {
  display: none;
}

/* Tarjeta en `--color-dark-alt`: labels tan 5.71:1, placeholders
   `--color-text-muted` 5.25:1, texto beige 9.18:1. El panel suelto de
   "contrasena nueva" (hijo directo de `.cuenta__inner`) se limita y centra. */
.cuenta__panel {
  min-width: 0;
  padding: var(--space-lg) var(--space-md);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-dark-alt);
}

.cuenta__panel[hidden] {
  display: none;
}

.cuenta__inner > .cuenta__panel {
  max-width: 560px;
  margin: 0 auto;
}

/* Beneficio del 5%: mismo tratamiento que el plan destacado de paquetes
   (borde burgundy y bano `--color-burgundy-dim`, sin `transform`). Sobre el
   compuesto (~#3E2E2A) el beige da 7.8:1. */
.cuenta__panel--beneficio {
  border-color: var(--color-burgundy);
  background: var(--color-burgundy-dim);
}

.cuenta__panel--beneficio .btn {
  margin-top: var(--space-md);
}

.cuenta__titulo {
  margin-bottom: var(--space-md);
  font-family: var(--font-display);
  font-size: var(--text-heading);
  font-weight: 400;
  line-height: 1.2;
  color: var(--color-beige);
}

/* Linea del beneficio en el panel de crear cuenta: barra burgundy a la
   izquierda para que se lea como ventaja y no como texto legal. */
.cuenta__beneficio {
  font-family: var(--font-body);
  font-size: var(--text-body);
  line-height: 1.6;
  color: var(--color-beige);
}

.cuenta__panel:not(.cuenta__panel--beneficio) .cuenta__beneficio {
  margin-bottom: var(--space-lg);
  padding-left: var(--space-md);
  border-left: 2px solid var(--color-burgundy-pale);
}

/* Ancla del boton "Crear cuenta" de index.html. Se suma al
   `scroll-padding-top: 100px` de `html` (D24), igual que `.frame-compra`.
   Hoy `id="crear"` esta en el `<form>` y el salto deja fuera de vista el
   titulo y el beneficio del panel: pendiente de marcado moverlo a su
   `<section class="cuenta__panel">` (D35); la regla ya cubre los dos casos. */
.cuenta__panel[id],
.cuenta__form[id] {
  scroll-margin-top: var(--space-md);
}

/* Error en linea bajo cada campo. No depende del color: lleva el marcador
   "!" delante (mismo que `.form__status--error`), peso 500, y el campo
   marcado con `aria-invalid` cambia su linea inferior. `--color-burgundy-pale`
   sobre `--color-dark-alt`: 7.2:1. */
.cuenta__error {
  display: flex;
  align-items: flex-start;
  gap: var(--space-xs);
  margin-top: var(--space-sm);
  font-family: var(--font-body);
  font-size: var(--text-small);
  font-weight: 500;
  line-height: 1.5;
  color: var(--color-burgundy-pale);
}

.cuenta__error::before {
  content: "!";
  flex-shrink: 0;
  font-weight: 700;
}

.cuenta__error[hidden] {
  display: none;
}

.cuenta__form .form__input[aria-invalid="true"] {
  border-bottom-color: var(--color-burgundy-pale);
}

/* "¿Olvidaste tu contrasena?": un `<button>` con aspecto de enlace, 44px de
   alto real. Tan subrayado (5.71:1) que sube a beige con hover y foco. */
.cuenta__enlace {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-top: var(--space-sm);
  font-family: var(--font-body);
  font-size: var(--text-small);
  color: var(--color-tan);
  text-align: left;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.25em;
  transition: color var(--duration-fast) ease;
}

/* Enlace dentro de un parrafo ("Ver FRAME Nº 01"). Patron de
   `.form__privacidad-link`: inline-block con padding vertical para llegar a
   44px sin volver pulsable el texto de alrededor. */
.cuenta__enlace-texto {
  display: inline-block;
  padding: 0.7rem 0;
  color: var(--color-tan);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.25em;
  transition: color var(--duration-fast) ease;
}

.cuenta__enlace:hover,
.cuenta__enlace:focus-visible,
.cuenta__enlace-texto:hover,
.cuenta__enlace-texto:focus-visible {
  color: var(--color-beige);
}

.cuenta__enlace:focus,
.cuenta__enlace-texto:focus {
  outline: none;
}

.cuenta__enlace:focus-visible,
.cuenta__enlace-texto:focus-visible {
  outline: 2px solid var(--color-tan);
  outline-offset: 2px;
}

/* Vista con sesion: columna de bloques centrada. */
.cuenta__usuario {
  display: grid;
  gap: var(--space-lg);
  max-width: 560px;
  margin: 0 auto;
}

.cuenta__usuario[hidden] {
  display: none;
}

/* `--color-text-muted` sobre `--color-dark`: 4.65:1; el correo en beige. */
.cuenta__sesion {
  font-family: var(--font-body);
  font-size: var(--text-small);
  color: var(--color-text-muted);
  text-align: center;
}

/* Un correo largo no tiene donde partir: sin esto desborda a 320px. */
.cuenta__email {
  font-weight: 500;
  color: var(--color-beige);
  word-break: break-word; /* respaldo para Safari iOS < 15.4 */
  overflow-wrap: anywhere;
}

.cuenta__continuar,
.cuenta__salir {
  justify-self: center;
}

.cuenta__continuar[hidden] {
  display: none;
}

.cuenta__revistas {
  display: grid;
  gap: var(--space-sm);
}

.cuenta__revistas:empty {
  display: none;
}

/* Cada revista comprada: tarjeta enlazada en `--color-dark` dentro del panel
   (beige 8.13:1, fecha en `--color-text-muted` 4.65:1). Mismo borde y foco
   que `.frame-archivo__enlace`. */
.cuenta__revista-enlace {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-xs);
  min-height: 44px;
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-dark);
  transition: border-color var(--duration-fast) ease;
}

.cuenta__revista-enlace:hover,
.cuenta__revista-enlace:focus-visible {
  border-color: var(--color-tan);
}

.cuenta__revista-enlace:focus {
  outline: none;
}

.cuenta__revista-enlace:focus-visible {
  outline: 2px solid var(--color-tan);
  outline-offset: 3px;
}

.cuenta__revista-nombre {
  min-width: 0;
  font-family: var(--font-display);
  font-size: var(--text-subhead);
  line-height: 1.3;
  color: var(--color-beige);
  overflow-wrap: anywhere;
}

.cuenta__revista-fecha {
  font-family: var(--font-body);
  font-size: var(--text-small);
  color: var(--color-text-muted);
}

/* Estado vacio: `--color-text-muted` sobre `--color-dark-alt`, 5.25:1. */
.cuenta__vacio {
  font-family: var(--font-body);
  font-size: var(--text-body);
  line-height: 1.6;
  color: var(--color-text-muted);
}

@media (min-width: 768px) {
  .cuenta__paneles {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .cuenta__paneles > .cuenta__panel:last-child {
    grid-column: 2;
    grid-row: 1 / span 2;
  }

  .cuenta__panel {
    padding: var(--space-lg);
  }
}

/* ===========================
   11. FOOTER
   =========================== */

.footer {
  background: var(--color-dark-deep);
  padding: var(--space-xl) 0 var(--space-lg);
}

.footer__top {
  display: flex;
  justify-content: center;
  margin-bottom: var(--space-lg);
}

.footer__logo img {
  height: 52px;
  width: auto;
}

/* Mismo tratamiento interactivo que el logo del nav (seccion 5), con menos
   escala porque aqui la imagen mide 52px, casi el mismo alto que el logo del
   nav (48px), y un realce mayor se notaria desproporcionado. */
.footer__logo {
  display: inline-flex;
  align-items: center;
  transition: transform var(--duration-fast) var(--ease-out-expo),
              filter var(--duration-fast) var(--ease-out-expo);
}

.footer__logo:hover,
.footer__logo:focus-visible {
  transform: scale(1.06) translateY(-2px);
  filter: brightness(1.15) drop-shadow(0 0 14px var(--color-tan));
}

.footer__logo:focus-visible {
  outline: 2px solid var(--color-tan);
  outline-offset: 6px;
}

.footer__logo:active {
  transform: scale(0.98);
  filter: brightness(1.05);
}

.footer__columns {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-lg);
  max-width: 800px;
  margin: 0 auto var(--space-lg);
  text-align: center;
}

.footer__col-title {
  font-family: var(--font-body);
  font-size: var(--text-micro);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: var(--color-tan);
  margin-bottom: var(--space-sm);
}

/* 44px de alto por enlace (D7; antes ~30px con 4px de separacion). Crecer
   la caja SI conviene aqui: los enlaces van apilados y un pseudo-elemento de
   44px se solaparia con el vecino, asi que el paso de 44px tiene que ser
   real. Cuesta ~170px de scroll en celular (12 enlaces visibles), el precio
   de que cada uno se pueda tocar sin errar. `--color-text-muted` ya pasa AA
   sobre `--color-dark-deep`: 5.65:1 (D3). */
.footer__col-link {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  font-family: var(--font-body);
  font-size: var(--text-small);
  color: var(--color-text-muted);
  padding: var(--space-xs) 0;
  transition: color var(--duration-fast) ease;
}

.footer__col-link:hover {
  color: var(--color-beige);
}

.footer__divider {
  width: 100%;
  height: 1px;
  background: var(--color-border);
  margin-bottom: var(--space-md);
}

/* Cuatro hijos visibles: copyright, ubicacion, selector de idioma ES/EN
   (`.nav__lang-toggle`, que vive aqui y no en el nav) y redes. En tablet
   (768px) la suma roza el ancho disponible, asi que se permite envolver con
   separacion propia en vez de dejar que se toquen. */
.footer__bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-md) var(--space-lg);
}

/* Copyright y ubicacion comparten tratamiento: mismo peso visual, ninguno
   compite con los enlaces de las columnas. */
.footer__copyright,
.footer__location {
  font-family: var(--font-body);
  font-size: var(--text-small);
  color: var(--color-text-muted);
}

.footer__socials {
  display: flex;
  gap: var(--space-md);
}

/* El SVG mide 20x20, pero el enlace (Instagram, el objetivo mas pequeno que
   habia en el sitio) ocupa 44x44 con el icono centrado (D7). */
.footer__social-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  color: var(--color-text-muted);
  transition: color var(--duration-fast) ease;
}

.footer__social-icon:hover {
  color: var(--color-tan);
}

.footer__social-icon svg {
  width: 20px;
  height: 20px;
}

/* ===========================
   12. SCROLL REVEAL
   =========================== */

/* Estado por defecto VISIBLE: sin JavaScript, con el JS caido o todavia sin
   ejecutar, todo el contenido marcado con [data-reveal] se ve. Eso incluye el
   formulario de contacto.html, que de otro modo quedaba invisible pero
   enfocable con el teclado.
   El estado inicial oculto solo se permite cuando .has-js confirma que hay JS.
   :where() no suma especificidad: la regla sigue en (0,1,0), asi que
   .is-revealed (0,2,0) y prefers-reduced-motion la ganan igual que antes. */
:where(.has-js) [data-reveal] {
  opacity: 0;
  transform: translateY(30px);
  transition: opacity var(--duration-normal) var(--ease-out-expo),
              transform var(--duration-normal) var(--ease-out-expo);
}

[data-reveal].is-revealed {
  opacity: 1;
  transform: translateY(0);
}

/* ===========================
   13. REDUCED MOTION
   =========================== */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  [data-reveal] {
    opacity: 1;
    transform: none;
  }

  .hero__eyebrow,
  .hero__quote,
  .hero__subtitle,
  .hero__ctas {
    opacity: 1;
    transform: none;
  }

  .hero__mosaic-row {
    animation-play-state: paused !important;
  }

  /* El movimiento del hero ya esta detenido: el boton de pausa no tendria
     nada que hacer y solo seria una parada de tabulacion sin efecto. */
  .has-js .hero__motion-toggle {
    display: none;
  }

  /* Spinner del boton de enviar quieto: el anillo de 3/4 sigue indicando
     "en curso" sin girar. */
  .form__submit--sending .form__submit-spinner {
    animation: none;
  }

  /* Los logos conservan brillo y halo en hover/focus, pero sin movimiento. */
  .nav__logo:hover,
  .nav__logo:focus-visible,
  .nav__logo:active,
  .footer__logo:hover,
  .footer__logo:focus-visible,
  .footer__logo:active {
    transform: none;
  }
}

/* ===========================
   14. MEDIA QUERIES
   =========================== */

/* Small devices */
@media (max-width: 639px) {
  .hero__mosaic-item {
    width: 110px;
    height: 80px;
  }

  .hero__mosaic {
    gap: 4px;
    transform: rotate(-4deg) scale(1.25);
    filter: blur(3px);
  }

  .hero__mosaic-row {
    gap: 4px;
  }

  /* El subtitulo es largo y hay tres CTAs apilados. La mitad del margen bajo
     el subtitulo (32px en vez de 64px) acerca los CTAs al fold: a 375x667
     el primario, "Pedir Cotizacion", termina a 505px (ES) y el ultimo CTA a
     642px, los tres dentro; con 64px el ultimo se saldria. */
  .hero__subtitle {
    margin-bottom: var(--space-lg);
  }

  .hero__ctas {
    flex-direction: column;
    width: 100%;
  }

  .hero__ctas .btn {
    width: 100%;
    max-width: 280px;
  }

  /* Controles equivalentes, tratamiento equivalente: la fila de CTAs de
     cualquier seccion se apila igual que la del hero. A 320px el boton mide
     256px de ancho y ~48px de alto (area tactil por encima de 44px) y
     "PEDIR COTIZACION" entra en una sola linea. */
  .cta-band {
    flex-direction: column;
    width: 100%;
  }

  .cta-band .btn {
    width: 100%;
    max-width: 280px;
  }

  .about__stats {
    flex-direction: column;
    gap: var(--space-lg);
  }

  /* Cierra D17. Con `--space-xl` (64px por lado) mas los 32px de `.container`,
     a 320px quedaban 126px utiles dentro de la tarjeta: descontando el pill de
     `.form__submit` (70px de padding + borde) sobraban 53px de texto, menos que
     una palabra. Con `--space-lg` el ancho util sube a 190px y el CTA entra. */
  .contact__form {
    padding: var(--space-lg);
  }
}

/* Celular bajo (375x553, un iPhone SE/8 con las barras de Safari o del
   navegador de Instagram; 320x568, iPhone SE de primera generacion): se
   compacta el ritmo del hero solo aqui, para que el primario quede holgado
   dentro del fold y asome al menos un CTA secundario: 16px entre
   piezas en lugar de 32px, y 24px bajo la cita (sigue un escalon por encima
   del resto). Medidas en docs/ESTILO.md ("Hero: reservas y coreografia"). */
@media (max-width: 639px) and (max-height: 600px) {
  .hero__eyebrow,
  .hero__subtitle {
    margin-bottom: var(--space-md);
  }

  .hero__quote {
    margin-bottom: calc(var(--space-md) + var(--space-sm));
  }
}

/* Tablet and below */
@media (max-width: 767px) {
  .nav__links,
  .nav__actions {
    display: none;
  }

  /* Aqui si hay hamburguesa, asi que el checkbox vuelve al flujo: sigue
     recortado (seccion 5) pero enfocable y operable con teclado. */
  .nav__menu-toggle {
    display: block;
  }

  .nav__hamburger {
    display: flex;
  }

  .nav__cta-movil {
    display: inline-flex;
  }

  /* Con el CTA de 44px en la fila (antes mandaba el logo de 32px) el nav
     mediria 92px con 1.5rem de padding. Con `--space-md` mide 76px
     (16 + 44 + 16), por debajo de los 80px que reserva el hero y de los
     100px de `.page-main` y `scroll-padding-top` (D15). Es el mismo valor
     que `.nav--scrolled` (1rem): en celular el nav ya no cambia de alto al
     hacer scroll. */
  .nav {
    padding: var(--space-md) 0;
  }

  .mobile-menu {
    display: flex;
  }

  /* Bloqueo del scroll de fondo con el menu abierto.
     Vive en el bloque de celular a proposito: el overlay solo existe aqui, y
     un candado en la regla base dejaria la pagina bloqueada en escritorio a
     quien abriera el menu sin JS y girase el telefono.
     Se declara en `html` Y en `body`: con `html` en `visible`, el que propaga
     el desplazamiento al viewport es `body`, y basta con bloquear `body`;
     pero en cuanto `html` deja de ser `visible` el que propaga es `html` y
     `body` —que ya trae `overflow-x: hidden` del reset— se convierte el mismo
     en un contenedor desplazable. Con los dos en `hidden` no queda nada que
     mover.
     `html:has()` no tiene gemela con `.menu-open`: la clase la pone el JS en
     `body` y desde ahi no se alcanza `html` sin `:has()`. En un navegador sin
     `:has()` el candado se queda en `body`, que es justo donde funciona.
     `scrollbar-gutter: stable` evita el salto de ~15px al desaparecer la barra
     en una ventana de escritorio estrecha; en celular las barras son
     superpuestas y la reserva es cero, no cambia nada.
     LIMITE CONOCIDO (iOS): en Safari de iOS `overflow: hidden` no siempre
     frena el arrastre tactil. `overscroll-behavior: contain` en `.mobile-menu`
     (seccion 5) corta el encadenado y cubre el caso normal; el remedio total
     —`position: fixed` en el body mas restaurar `scrollTop` al cerrar— exige
     JavaScript y no puede salir de esta hoja. */
  html {
    scrollbar-gutter: stable;
  }

  html:has(.nav__menu-toggle:checked) {
    overflow: hidden;
  }

  body.menu-open {
    overflow: hidden;
  }

  body:has(.nav__menu-toggle:checked) {
    overflow: hidden;
  }

  .nav__logo-desktop {
    display: none;
  }

  .nav__logo-mobile {
    display: block;
  }

  /* Logo del nav en celular: imagen "logo/KIWI ICON W.png" (solo el icono).
     No es la misma imagen que en escritorio y tiene otra proporcion, por eso
     no lleva la altura de ".nav__logo img" de la seccion 5 (NAVIGATION). */
  .nav__logo img {
    height: 32px;
  }

  /* En celular el nav mide 76px (16px + CTA de 44px + 16px, ver `.nav` mas
     arriba en este bloque), no 96px: la reserva superior del hero (seccion 6)
     se ajusta a ese alto y deja 4px de aire bajo la barra solida.
     80px = `--space-xl` + `--space-md`. */
  .hero {
    padding-top: calc(var(--space-xl) + var(--space-md));
  }

  .services__grid {
    grid-template-columns: 1fr;
  }

  .about__inner {
    grid-template-columns: 1fr;
  }

  .about__image-wrapper {
    aspect-ratio: 4 / 3;
  }

  .contact__inner {
    grid-template-columns: 1fr;
  }

  .form__row {
    grid-template-columns: 1fr;
  }

  .footer__columns {
    grid-template-columns: 1fr;
    gap: var(--space-lg);
  }

  .footer__bottom {
    flex-direction: column;
    gap: var(--space-md);
    text-align: center;
  }
}

/* Tablet */
@media (min-width: 768px) and (max-width: 1023px) {
  .services__grid {
    grid-template-columns: repeat(2, 1fr);
  }

  /* Centra la tarjeta huerfana SOLO cuando de verdad queda sola en su fila.
     `:last-child` a secas daba por hecho que siempre habia 3 tarjetas: con 4
     estiraba la cuarta a dos columnas aunque la fila estuviese completa.
     `:nth-child(odd):last-child` es cierto con 1, 3, 5... tarjetas y falso con
     un 2x2 limpio, asi que la regla desaparece sola al anadir la cuarta linea
     de servicio.
     El ancho se iguala al de una columna real —(100% - gap) / 2— para que la
     huerfana no salga 32px mas ancha que sus hermanas. */
  .service-card:nth-child(odd):last-child {
    grid-column: span 2;
    max-width: calc(50% - var(--space-lg) / 2);
    margin: 0 auto;
  }
}

/* Desktop */
@media (min-width: 1024px) {
  .section {
    padding: 5rem 0;
  }

  /* `.services__grid` vale `repeat(3, 1fr)` a partir de aqui. Solo sobra una
     huerfana cuando el numero de tarjetas deja resto 1 (4, 7, 10...), y eso es
     exactamente lo que detecta `:nth-child(3n+1):last-child`, sin contar
     tarjetas a mano: con 3 o 6 la fila esta completa y la regla no se aplica.
     Con 4 tarjetas la cuarta se coloca en la columna central en vez de quedar
     descolgada a la izquierda.
     Va dentro de esta media query a proposito: en la cascada base pondria
     `grid-column: 2` sobre la rejilla de una sola columna de celular y crearia
     una segunda columna implicita. */
  .service-card:nth-child(3n+1):last-child {
    grid-column: 2;
  }
}

/* Wide screens */
@media (min-width: 1400px) {
  .container {
    padding: 0 var(--space-xl);
  }
}

/* ===========================
   15. AI CHATBOT
   =========================== */

/* Floating bubble */
.chatbot-bubble {
  position: fixed;
  bottom: calc(32px + env(safe-area-inset-bottom));
  right: calc(32px + env(safe-area-inset-right));
  z-index: 900;
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: var(--color-burgundy);
  border: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4px 20px rgba(124, 47, 52, 0.4);
  transition: background-color var(--duration-fast) var(--ease-out-expo),
              border-color var(--duration-fast) var(--ease-out-expo),
              transform var(--duration-fast) var(--ease-out-expo),
              box-shadow var(--duration-fast) var(--ease-out-expo),
              opacity var(--duration-fast) var(--ease-out-expo),
              visibility var(--duration-fast) var(--ease-out-expo);
}

/* Burbuja retirada mientras los CTAs del hero estan en pantalla en celular
   (D29): a 375x667 se montaba sobre el extremo del tercer CTA. La clase la
   pone y la quita el JS (`js/main.js`, solo por debajo de 640px).
   Patron de overlays del sitio: `opacity` + `visibility` + `pointer-events`,
   las dos primeras transicionadas con la misma duracion. `visibility` vale
   `visible` en todo valor intermedio, asi que el fundido se ve entero al
   salir y al volver; al terminar de salir, la burbuja deja el orden de
   tabulacion y el arbol de accesibilidad. Con `prefers-reduced-motion` el
   bloque global de la seccion 13 deja el cambio instantaneo. */
.chatbot-bubble--oculta {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

/* Pulso de llamada de atencion (D23 y D26): 3 latidos al cargar y se queda
   quieto, en vez de latir sin fin en todas las paginas. Ya no anima
   `box-shadow` (fuera del compositor): es un disco del mismo burgundy que
   crece y se desvanece solo con `transform` y `opacity`.
   `z-index: -1` dentro del contexto de apilamiento de la burbuja (que lo
   crea su `position: fixed` + `z-index`): queda bajo el icono. Al empezar el
   latido el disco coincide con la burbuja y no se distingue de ella. */
.chatbot-bubble::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: 50%;
  background: var(--color-burgundy);
  opacity: 0;
  pointer-events: none;
  animation: bubblePulse 3s ease-out 3;
}

.chatbot-bubble:hover {
  transform: scale(1.08);
  box-shadow: 0 6px 28px rgba(124, 47, 52, 0.5);
}

.chatbot-bubble svg {
  width: 26px;
  height: 26px;
  color: var(--color-beige);
  transition: transform var(--duration-fast) ease;
}

.chatbot-bubble .chatbot-bubble__close {
  display: none;
}

.chatbot-bubble.is-active .chatbot-bubble__icon {
  display: none;
}

.chatbot-bubble.is-active .chatbot-bubble__close {
  display: block;
}

.chatbot-bubble.is-active {
  background: var(--color-dark-alt);
  border: 1px solid var(--color-border);
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}

.chatbot-bubble.is-active::after {
  animation: none;
}

/* El disco sale con la opacidad del antiguo anillo (0.25) y se desvanece a
   1.4x, que es el mismo alcance de 12px del `box-shadow` anterior sobre una
   burbuja de 60px. Del 60% al 100% descansa invisible: la pausa entre
   latidos que antes daba el `ease-in-out` de vuelta. */
@keyframes bubblePulse {
  0% {
    opacity: 0.25;
    transform: scale(1);
  }
  60%,
  100% {
    opacity: 0;
    transform: scale(1.4);
  }
}

/* Badge indicator */
.chatbot-bubble__badge {
  position: absolute;
  top: -2px;
  right: -2px;
  width: 16px;
  height: 16px;
  background: var(--color-tan);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 9px;
  font-weight: 700;
  color: var(--color-dark);
  font-family: var(--font-body);
}

.chatbot-bubble.is-active .chatbot-bubble__badge {
  display: none;
}

/* Chat window */
.chatbot {
  position: fixed;
  bottom: 104px;
  right: 32px;
  z-index: 899;
  width: 380px;
  max-height: 520px;
  background: var(--color-dark);
  border: 1px solid var(--color-border);
  border-radius: 16px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  box-shadow:
    0 20px 60px rgba(0, 0, 0, 0.4),
    0 0 0 1px rgba(177, 155, 134, 0.05);
  /* Cerrado = fuera de la vista, del orden de tabulacion y del arbol de
     accesibilidad (D22): `opacity` a solas dejaba el campo y el boton de
     enviar como dos paradas de tabulacion invisibles en las 8 paginas.
     Convencion de overlays (docs/ESTILO.md), con un matiz: `visibility` NO
     se transiciona con la misma duracion al abrir, porque el JS hace
     `chatInput.focus()` en el mismo instante en que pone `.is-open`, y en el
     primer frame de una transicion de `visibility` el valor sigue siendo
     `hidden` y el foco se perderia. Al abrir cambia ya (duracion 0, sin
     retardo); al cerrar espera a que termine el fundido (retardo de
     `--duration-fast`). */
  opacity: 0;
  visibility: hidden;
  transform: translateY(16px) scale(0.96);
  pointer-events: none;
  transition: opacity var(--duration-fast) var(--ease-out-expo),
              transform var(--duration-fast) var(--ease-out-expo),
              visibility 0s linear var(--duration-fast);
}

.chatbot.is-open {
  opacity: 1;
  visibility: visible;
  transform: translateY(0) scale(1);
  pointer-events: all;
  transition-delay: 0s;
}

/* Chat header */
.chatbot__header {
  padding: 1.1rem 1.25rem;
  background: var(--color-dark-alt);
  border-bottom: 1px solid var(--color-border);
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-shrink: 0;
}

.chatbot__avatar {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--color-burgundy), var(--color-burgundy-light));
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.chatbot__avatar svg {
  width: 18px;
  height: 18px;
  color: var(--color-beige);
}

.chatbot__header-info {
  flex: 1;
  min-width: 0;
}

.chatbot__header-name {
  font-family: var(--font-body);
  font-size: var(--text-small);
  font-weight: 600;
  color: var(--color-beige);
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

/* Renombrada desde .ai-badge: la etiqueta dice "Bot", no "AI", y el chatbot
   no usa IA. Requiere el mismo nombre en js/main.js. */
.chatbot__header-badge {
  font-size: 0.55rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  background: var(--color-burgundy-dim);
  color: var(--color-burgundy-light);
  padding: 0.15rem 0.45rem;
  border-radius: var(--radius-pill);
  border: 1px solid rgba(124, 47, 52, 0.25);
}

.chatbot__header-status {
  font-family: var(--font-body);
  font-size: var(--text-micro);
  color: var(--color-text-muted);
  display: flex;
  align-items: center;
  gap: 0.35rem;
}

.chatbot__header-status::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  /* Punto de "en linea" en `--color-tan` (D14): el verde #5cb85c era el unico
     color del sitio fuera de la paleta. El estado lo dice el texto de al
     lado; el punto solo lo acompana. */
  background: var(--color-tan);
  flex-shrink: 0;
}

/* Chat messages area */
.chatbot__messages {
  flex: 1;
  overflow-y: auto;
  padding: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  min-height: 280px;
  max-height: 320px;
  scrollbar-width: thin;
  scrollbar-color: var(--color-border) transparent;
  /* Corta el encadenado del scroll con la pagina de fondo, igual que
     `.mobile-menu` (seccion 5). */
  overscroll-behavior: contain;
}

.chatbot__messages::-webkit-scrollbar {
  width: 4px;
}

.chatbot__messages::-webkit-scrollbar-track {
  background: transparent;
}

.chatbot__messages::-webkit-scrollbar-thumb {
  background: var(--color-border);
  border-radius: 4px;
}

/* Message bubbles */
.chat-msg {
  max-width: 85%;
  display: flex;
  flex-direction: column;
  animation: chatMsgIn 0.3s var(--ease-out-expo) forwards;
}

@keyframes chatMsgIn {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.chat-msg--bot {
  align-self: flex-start;
}

.chat-msg--user {
  align-self: flex-end;
}

.chat-msg__bubble {
  padding: 0.75rem 1rem;
  border-radius: 14px;
  font-family: var(--font-body);
  font-size: var(--text-small);
  line-height: 1.5;
}

.chat-msg--bot .chat-msg__bubble {
  background: var(--color-dark-alt);
  border: 1px solid var(--color-border);
  color: var(--color-beige);
  border-bottom-left-radius: 4px;
}

.chat-msg--user .chat-msg__bubble {
  background: var(--color-burgundy);
  color: var(--color-beige);
  border-bottom-right-radius: 4px;
}

.chat-msg__time {
  font-size: 0.6rem;
  color: var(--color-text-muted);
  margin-top: 0.3rem;
  padding: 0 0.25rem;
}

.chat-msg--user .chat-msg__time {
  text-align: right;
}

/* Typing indicator */
.chat-typing {
  align-self: flex-start;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem 1rem;
  background: var(--color-dark-alt);
  border: 1px solid var(--color-border);
  border-radius: 14px;
  border-bottom-left-radius: 4px;
}

.chat-typing__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-tan);
  animation: typingBounce 1.4s ease-in-out infinite;
}

.chat-typing__dot:nth-child(2) {
  animation-delay: 0.2s;
}

.chat-typing__dot:nth-child(3) {
  animation-delay: 0.4s;
}

@keyframes typingBounce {
  0%, 60%, 100% {
    transform: translateY(0);
    opacity: 0.4;
  }
  30% {
    transform: translateY(-4px);
    opacity: 1;
  }
}

/* Quick action chips */
.chat-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.5rem;
}

.chat-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.45rem 0.85rem;
  background: transparent;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  font-family: var(--font-body);
  font-size: 0.75rem;
  color: var(--color-tan);
  cursor: pointer;
  transition: background-color var(--duration-fast) ease,
              border-color var(--duration-fast) ease,
              color var(--duration-fast) ease;
  white-space: nowrap;
}

.chat-chip:hover {
  background: var(--color-burgundy-dim);
  border-color: var(--color-burgundy);
  color: var(--color-beige);
}

.chat-chip svg {
  width: 13px;
  height: 13px;
  flex-shrink: 0;
}

/* Chat input */
.chatbot__input {
  padding: 0.85rem 1.25rem;
  background: var(--color-dark-alt);
  border-top: 1px solid var(--color-border);
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-shrink: 0;
}

.chatbot__input-field {
  flex: 1;
  padding: 0.55rem 0.85rem;
  background: var(--color-dark);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  font-family: var(--font-body);
  /* `--text-body` (16.16px minimo) y no 0.82rem = 13.1px: Safari iOS hace
     zoom al enfocar cualquier campo bajo 16px (D2). En todos los anchos, no
     solo en celular: el iPad tambien hace zoom. */
  font-size: var(--text-body);
  color: var(--color-beige);
  transition: border-color var(--duration-fast) ease;
}

/* Sin la `opacity: 0.7` de antes: el token corregido ya da 4.65:1 sobre el
   `--color-dark` del campo (D3). */
.chatbot__input-field::placeholder {
  color: var(--color-text-muted);
  opacity: 1;
}

.chatbot__input-field:focus {
  border-color: var(--color-tan);
  outline: none;
}

.chatbot__input-field:focus-visible {
  outline: 2px solid var(--color-tan);
  outline-offset: 3px;
}

.chatbot__send {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--color-burgundy);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background-color var(--duration-fast) ease,
              transform var(--duration-fast) ease;
  flex-shrink: 0;
  border: none;
}

.chatbot__send:hover {
  background: var(--color-burgundy-light);
  transform: scale(1.05);
}

.chatbot__send svg {
  width: 16px;
  height: 16px;
  color: var(--color-beige);
}

/* Powered by line */
.chatbot__footer {
  text-align: center;
  padding: 0.45rem;
  background: var(--color-dark-alt);
  font-family: var(--font-body);
  font-size: 0.6rem;
  color: var(--color-text-muted);
  letter-spacing: 0.03em;
  border-top: 1px solid rgba(177, 155, 134, 0.06);
}

.chatbot__footer span {
  color: var(--color-tan);
}

/* Link inside bot messages */
.chat-msg__bubble a {
  color: var(--color-tan);
  text-decoration: underline;
  text-underline-offset: 2px;
  transition: color var(--duration-fast) ease;
}

.chat-msg__bubble a:hover {
  color: var(--color-beige);
}

/* Mobile chatbot */
@media (max-width: 767px) {
  .chatbot-bubble {
    bottom: calc(20px + env(safe-area-inset-bottom));
    right: calc(20px + env(safe-area-inset-right));
    width: 54px;
    height: 54px;
  }

  .chatbot-bubble svg {
    width: 22px;
    height: 22px;
  }

  .chatbot {
    bottom: env(safe-area-inset-bottom);
    right: 0;
    left: 0;
    width: 100%;
    max-height: 100vh;
    max-height: 100dvh;
    border-radius: 0;
    border: none;
  }

  .chatbot.is-open {
    border-radius: 16px 16px 0 0;
    max-height: 75vh;
    max-height: 75dvh;
  }

  .chatbot__messages {
    max-height: none;
    flex: 1;
  }
}

/* La burbuja no puede tapar el boton de enviar ni el estado del envio (D1).
   Por debajo de 640px, `.form__submit` y `.form__status` llegan hasta 64px
   del borde derecho (32px de `.container` + 32px de `.contact__form`), y la
   burbuja de 54px a 20px del borde ocupaba de 74px a 20px: se montaba 10px
   sobre el boton —15px sobre su anillo de foco— y como es `fixed`, a
   cualquier altura de scroll. Reservar espacio abajo no sirve: solo libra el
   final de la pagina, no el boton mientras pasa por debajo.
   En la pagina que tiene el formulario la burbuja se aparta en horizontal:
   48px (sigue sobre los 44px de area tactil) a 8px del borde ocupa de 56px a
   8px, asi que queda a 8px del boton y a 3px de su anillo de foco
   (`outline` de 2px + `outline-offset` de 3px) en cualquier ancho de celular
   y en cualquier punto del scroll. Desde 640px el padding de la tarjeta sube
   a 64px y ya no hay solape: la regla no hace falta.
   Sin `:has()` la burbuja se queda donde estaba: es el estado de antes, no
   uno roto. */
@media (max-width: 639px) {
  body:has(.contact__form) .chatbot-bubble {
    right: calc(8px + env(safe-area-inset-right));
    width: 48px;
    height: 48px;
  }
}

/* ===========================
   16. LEGAL
   =========================== */

.legal__inner {
  max-width: 780px;
  margin: 0 auto;
}

.legal__updated {
  font-family: var(--font-body);
  font-size: var(--text-micro);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--color-tan);
  padding-bottom: var(--space-md);
  border-bottom: 1px solid var(--color-border);
  margin-bottom: var(--space-lg);
}

.legal__intro {
  font-family: var(--font-display);
  font-size: var(--text-subhead);
  line-height: 1.7;
  color: var(--color-beige);
  margin-bottom: var(--space-xl);
}

.legal__block {
  margin-bottom: var(--space-lg);
}

.legal__block-title {
  font-family: var(--font-display);
  font-size: var(--text-heading);
  font-weight: 400;
  line-height: 1.3;
  color: var(--color-beige);
  margin-bottom: var(--space-md);
}

/* Subtitulo <h3> dentro de un `.legal__block` ("2.1 Formulario de contacto",
   "2.2 Cuenta de cliente"...). Hasta ahora no tenia regla y salia con el estilo
   por defecto del navegador.
   Cambia de FAMILIA, no solo de tamano: `.legal__block-title` es serif de
   display y esto es sans de cuerpo con peso 500. Hace falta porque en celular
   los dos tokens estan clavados en su minimo (deuda D6) y la distancia queda en
   24px contra 16px, poca cosa para leerse como dos niveles distintos.
   Frente a `.legal__text` (15.2px, peso 300, `--color-text-muted`) separa por
   peso y por color: `--color-beige` da 8.13:1 sobre `--color-dark`.
   El margen superior colapsa con el `margin-bottom` del parrafo anterior, asi
   que la separacion real es de 32px arriba y 8px abajo: el subtitulo queda
   pegado al parrafo que encabeza. */
.legal__block-subtitle {
  font-family: var(--font-body);
  font-size: var(--text-subhead);
  font-weight: 500;
  line-height: 1.5;
  color: var(--color-beige);
  margin-top: var(--space-lg);
  margin-bottom: var(--space-sm);
}

.legal__text {
  font-size: var(--text-body);
  font-weight: 300;
  line-height: 1.8;
  color: var(--color-text-muted);
  margin-bottom: var(--space-md);
}

.legal__text:last-child {
  margin-bottom: 0;
}

/* Parrafo de acciones al final de `gracias.html` (y de `404.html`): dos `.btn`
   dentro de un `.legal__text`. Como inline-flex, al envolver en celular se
   apilaban sin separacion, pegados por la caja de linea. Con flex + `gap` se
   separan 16px en los dos ejes, igual que `.cta-band`, sin clase nueva en el
   marcado. Sin `:has()` el parrafo se queda como estaba. */
.legal__text:has(> .btn) {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-md);
}

.legal__list {
  margin-bottom: var(--space-md);
}

.legal__list-item {
  position: relative;
  font-size: var(--text-body);
  font-weight: 300;
  line-height: 1.8;
  color: var(--color-text-muted);
  padding-left: var(--space-lg);
  margin-bottom: var(--space-sm);
}

.legal__list-item::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.8em;
  width: 16px;
  height: 1px;
  background: var(--color-burgundy);
}

.legal__list-item strong {
  font-weight: 500;
  color: var(--color-beige);
}

.legal__link {
  color: var(--color-tan);
  border-bottom: 1px solid var(--color-tan-dim);
  transition: color var(--duration-fast) var(--ease-out-expo),
              border-color var(--duration-fast) var(--ease-out-expo);
}

.legal__link:hover {
  color: var(--color-beige);
  border-bottom-color: var(--color-beige);
}

@media (max-width: 767px) {
  .legal__block {
    margin-bottom: var(--space-lg);
  }

  .legal__list-item {
    padding-left: var(--space-md);
  }

  .legal__list-item::before {
    width: 8px;
  }
}
