/* =========================================================================
   TEMA — todos los tokens de diseño viven aquí. El resto de la hoja sólo
   consume var(). Para reteñir la web entera, se tocan estos valores.

   La web es OSCURA de principio a fin. Hay tres superficies, de más honda a
   más elevada: tinta, grafito y carbón. El fondo del body viaja entre las dos
   primeras con el scroll, y ese viaje es la transición. El salto es de 1.48x,
   frente al 1.23x que tenía.

   Regla de contraste del acento (verde fosforito, el de la marca). Ratios
   medidos sobre el color ya compuesto, no sobre el hex suelto:
     · sobre superficie oscura → --accent       (10.5:1 sobre grafito)
     · segundo nivel           → --accent-soft  (6.4:1 sobre grafito)
     · como RELLENO            → --accent con texto --ink (15.6:1)

   Y la regla que no se puede romper: --ink es superficie o es texto SOBRE
   relleno verde. Nunca es texto sobre una superficie oscura.
   ========================================================================= */

:root {
  /* Base */
  /* #0f1014, no negro puro. Es el negro del manual (página 1 del PDF de
     Canva, junto al verde). Un negro absoluto sobre pantalla OLED apaga el
     píxel del todo y los bordes de las fotos se recortan en seco; este
     tiene la densidad justa para que la imagen funda con el fondo. */
  --ink: #0F1014;
  --ink-rgb: 15, 16, 20;
  /* MISMO NEGRO QUE --ink. El manual (página 1 del PDF) fija UN solo negro,
     #0F1014, para toda la landing. Antes había dos tonos y scroll.js alternaba
     el fondo entre ellos al pasar por marquee, proceso y CTA; con un único
     valor esa alternancia deja de verse. Se conserva el mecanismo por si se
     quiere recuperar un segundo tono: basta con cambiar este valor. */
  --grafito: #0F1014;
  --grafito-rgb: 15, 16, 20;
  /* UN SOLO NEGRO. Era #33333E y hacía de "superficie elevada"; el manual fija
     un único negro, así que las tarjetas dejan de separarse por color. Lo que
     las separa ahora es el halo de humo verde y su filete de un píxel. */
  --carbon: #0F1014;
  --cream: #F5F0E8;
  --white: #FFFFFF;

  /* Acento — verde fosforito. Es el verde del logotipo, así que el cargador
     ya no necesita un color aparte: --spark-green era eso y ahora es el mismo
     acento. --accent-soft es el segundo nivel; se llamaba --accent-soft
     cuando el tema era claro y había que oscurecer el violeta para que se
     leyera sobre papel. Aquí pasa lo contrario, y el nombre lo dice. */
  --accent: #00FF7B;
  --accent-rgb: 0, 255, 123;
  /* El segundo nivel NO es otro verde: es el mismo bajado de intensidad. Si
     fuera un hex aparte volveríamos a tener dos verdes en el sistema, que es
     justo lo que hay que evitar. Un grep de verdes debe devolver uno solo. */
  --accent-soft: rgba(0, 255, 123, .74);
  --accent-soft-rgb: 0, 255, 123;
  --spark-green: var(--accent);

  /* Señales. El oro se retiró entero: las estrellas de los testimonios van en
     el acento verde, y era el último color de fuera del sistema. */
  --danger: #FF7B7B;

  /* Tinta sobre claro */
  --ink-82: rgba(var(--ink-rgb), .82);
  --ink-80: rgba(var(--ink-rgb), .8);
  --ink-74: rgba(var(--ink-rgb), .74);
  --ink-60: rgba(var(--ink-rgb), .6);
  --ink-50: rgba(var(--ink-rgb), .5);
  --ink-45: rgba(var(--ink-rgb), .45);
  --ink-35: rgba(var(--ink-rgb), .35);
  --ink-30: rgba(var(--ink-rgb), .3);
  --ink-18: rgba(var(--ink-rgb), .18);
  --ink-15: rgba(var(--ink-rgb), .15);
  --ink-12: rgba(var(--ink-rgb), .12);
  --ink-10: rgba(var(--ink-rgb), .1);

  /* Crema sobre oscuro */
  --cream-85: rgba(245, 240, 232, .85);
  --cream-82: rgba(245, 240, 232, .82);
  --cream-80: rgba(245, 240, 232, .8);
  --cream-74: rgba(245, 240, 232, .74);
  --cream-62: rgba(245, 240, 232, .62);
  --cream-60: rgba(245, 240, 232, .6);
  --cream-45: rgba(245, 240, 232, .45);
  --cream-40: rgba(245, 240, 232, .4);
  --cream-35: rgba(245, 240, 232, .35);
  --cream-30: rgba(245, 240, 232, .3);
  --cream-22: rgba(245, 240, 232, .22);
  --cream-15: rgba(245, 240, 232, .15);
  --cream-12: rgba(245, 240, 232, .12);
  --cream-08: rgba(245, 240, 232, .08);

  /* Tipografía */
  /* DOS ROLES, NO CUATRO FAMILIAS. El manual de marca es estricto:
       · --titular → titulares, frases clave y claims
       · --texto   → cuerpos, textos planos e interfaz
     Había cuatro familias (Cormorant, Cinzel, Anton, Montserrat) repartidas
     sin esa frontera: había botones en serif y numerales en una tercera voz.

     Tan Harmony ya está auto-alojada (assets/fonts/tan-harmony.woff2, con su
     @font-face en fonts.css); Cormorant queda detrás como respaldo de carga. */
  --titular: 'Tan Harmony', 'Cormorant Garamond', Georgia, serif;
  --texto: Montserrat, Helvetica, Arial, sans-serif;

  /* Alias de compatibilidad: el resto de la hoja se migró a los dos roles de
     arriba, pero se dejan apuntando para que nada quede huérfano. */
  --sans: var(--texto);
  --display: var(--titular);
  --shout: var(--titular);

  /* Layout */
  --gutter: clamp(20px, 5vw, 90px);

  /* RITMO VERTICAL — tres escalones, y nada fuera de ellos. Antes había ocho
     valores sueltos (6, 7, 8, 9, 13vh…) y el aire entre secciones subía y
     bajaba sin criterio. Cada sección elige escalón según el peso que tiene
     en el recorrido, no según lo que pedía el diseño de esa pantalla.
       · ancho  → los respiros del recorrido (manifiesto, antesala, cierre)
       · base   → el paso normal de sección
       · corto  → bloques que ya traen su propio aire dentro */
  --sp-ancho: clamp(100px, 15vh, 200px);
  --sp-base: clamp(60px, 8.5vh, 112px);
  --sp-corto: clamp(38px, 5vh, 68px);

  /* ESCALA DE TITULARES — tres volúmenes, no ocho tamaños sueltos.
     Antes todos los titulares medían entre 66 y 95px: la página gritaba
     igual en cada sección y por eso se leía monótona pese a tener
     movimiento en todas. Ahora cada sección elige volumen según su papel
     en el recorrido, y los picos se oyen porque hay valles.

       · declaración → los momentos en que la página se detiene y afirma
                       (manifiesto, cierre). El hero va por encima, a 200px.
       · escaparate  → las que enseñan trabajo (conjuros, portfolio,
                       testimonios). Voz alta, pero no la última palabra.
       · relato      → las que explican (frankenstein, proceso, formulario).
                       Densas y en voz baja: son el valle que hace el pico. */
  /* Los mínimos separan los tres volúmenes también en vertical: con 44/38/31
     los escalones eran del 16% y la jerarquía volvía a aplanarse. El tope de
     declaración lo marca "extraordinario", la palabra más larga del
     manifiesto, que a 48px todavía entra en una pantalla de 375. */
  /* ESCALA DEL MANUAL. Medida en el propio Canva: el cuerpo marca 28.9 y el
     titular de contacto 121, con interletrado 0 e interlineado 1.7 / 0.9.
     El cuerpo se ancla en 16.5px —el mínimo cómodo de lectura— y de ahí sale
     todo lo demás por proporción. El máximo de cada clamp es el valor a
     1920px, y el coeficiente vw hace que baje proporcionalmente.

     Es una escala PLANA a propósito: los titulares de sección van todos entre
     58 y 71px. Sustituye a los tres volúmenes que había (146/104/62), que
     eran míos y no del manual. */
  --t-declaracion: clamp(34px, 3.68vw, 71px);   /* Frankenstein, contacto  */
  --t-escaparate: clamp(32px, 3.48vw, 67px);    /* conjuros, portfolio…    */
  --t-relato: clamp(29px, 3.03vw, 58px);        /* proceso                 */
  --t-paso: clamp(24px, 2.03vw, 39px);          /* 01 / 02 / 03            */
  --t-frase: clamp(24px, 1.95vw, 37px);         /* No leemos el futuro     */

  /* Interlineados e interletrado del manual: 0.9 en titular, 1.7 en cuerpo,
     tracking 0 en ambos. */
  --lh-titular: .9;
  --lh-texto: 1.7;

  /* Dos pasos que el sistema usaba sin declarar, y que son roles reales: el pie
     de foto y el texto de componente. Estaban repartidos en trece declaraciones
     sueltas entre 9,5 y 15,5px, cuatro de ellas por debajo del suelo de
     legibilidad de 11px. Ahora son tokens y se documentan en DESIGN.md. */
  --t-cinta: 13px;                              /* la cinta del hero       */
  --t-testimonio: 19px;                         /* TODOS los testimonios   */
  --t-caption: 13px;
  --t-micro: 11px;
  --shell: 1400px;
  --nav-h: 55px;

  /* Radios */
  --radius-pill: 999px;
  --radius-xs: 4px;

  /* Sombras */
  --shadow-nav: 0 2px 14px rgba(var(--ink-rgb), .05);
  --shadow-card: 0 8px 24px var(--cream-12);
  --shadow-card-flat: 0 10px 30px var(--cream-12);
  --shadow-card-lift: 0 26px 60px rgba(var(--ink-rgb), .28);
  --shadow-review: 0 22px 50px rgba(var(--ink-rgb), .14);
  --shadow-float: 0 10px 30px var(--cream-30);
  --shadow-accent-sm: 0 14px 34px rgba(var(--accent-rgb), .25);
  --shadow-accent-md: 0 14px 40px rgba(var(--accent-rgb), .3);
  --shadow-accent-lg: 0 20px 50px rgba(var(--accent-rgb), .45);
  --shadow-accent-btn: 0 16px 40px rgba(var(--accent-rgb), .35);

  /* Movimiento */
  --dur-fast: 150ms;
  --dur-base: 200ms;
  --dur-medium: 300ms;
  --dur-slow: 500ms;
  --dur-flip: 700ms;
  --ease-out: cubic-bezier(.25, .46, .45, .94);
  --ease-in: cubic-bezier(.55, .055, .675, .19);
  --ease-smooth: cubic-bezier(.2, .72, .18, 1);
}

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

html { scroll-behavior: smooth; scroll-padding-top: 68px; }

body {
  margin: 0;
  background: var(--grafito);
  color: var(--cream);
  font-family: var(--texto);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

a { color: inherit; text-decoration: none; }

a, button {
  cursor: pointer;
  transition: background var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out), opacity var(--dur-fast) ease;
}

/* Violet smoke trail canvas — painted above everything, never interactive. */
.smoke {
  position: fixed;
  inset: 0;
  /* POR DEBAJO DE LA BARRA (60), no por encima. Estaba en 70, así que al mover
     el ratón hacia el logotipo de la esquina el rastro se pintaba ENCIMA y
     parecía un haz verde del propio diseño tapándolo. El humo sigue flotando
     sobre el contenido; la interfaz fija queda limpia. */
  z-index: 55;
  pointer-events: none;
  /* El desenfoque de canvas es lo que funde los soplos entre sí: sin él se
     distinguen los sprites sueltos y el rastro se lee como un collar de
     manchas en vez de como una columna de humo. */
  filter: blur(3px) saturate(1.06);
}


::selection { background: var(--accent); color: var(--ink); }

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

/* Salto al contenido. Primer elemento tabulable de la página y fuera de la
   pantalla hasta que recibe el foco. Va por encima del cargador (200) para que
   quien navega con teclado no tenga que esperar a que termine. */
.skip {
  position: fixed;
  top: 10px;
  left: 10px;
  z-index: 210;
  transform: translateY(-250%);
  background: var(--accent);
  color: var(--ink);
  font-family: var(--texto);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: .24em;
  padding: 15px 26px;
  border-radius: var(--radius-pill);
  transition: transform var(--dur-base) var(--ease-smooth);
}
.skip:focus { transform: none; }

img { max-width: 100%; }

/* ---------- shared type ---------- */

.eyebrow {
  margin: 0 0 26px;
  font-size: 12px;
  letter-spacing: .34em;
  color: var(--accent-soft);
}
.eyebrow--on-dark { color: var(--accent); }

.h-display {
  margin: 0;
  font-family: var(--titular);
  font-weight: 400;
  line-height: 1;
  letter-spacing: -.02em;
  color: var(--cream);
}

.lede {
  margin: 0;
  font-size: 16.5px;
  line-height: var(--lh-texto);
  font-weight: 400;
  color: var(--cream-74);
}

.muted { font-weight: 400; opacity: .55; }
.accent { font-weight: 400; color: var(--accent-soft); }

.shell {
  max-width: var(--shell);
  width: 100%;
  margin: 0 auto;
}

.section { padding: var(--sp-base) var(--gutter); }
.section--edge { padding-left: 0; padding-right: 0; }

/* UN SOLO MARGEN IZQUIERDO PARA TODAS LAS SECCIONES. Las de borde anulan el
   margen lateral para que los carriles lleguen a sangre, y su .shell recuperaba
   el aire con un `style` inline repetido cuatro veces en el HTML: el resultado
   era que portfolio y testimonios arrancaban 85px más adentro que conjuros y
   proceso, sin ninguna razón de composición. Aquí el .shell se ensancha lo que
   mide el margen y se lo mete como relleno, así que el contenido cae en la
   misma x que en una sección normal, a cualquier ancho. */
.section--edge > .shell {
  max-width: calc(var(--shell) + 2 * var(--gutter));
  padding-inline: var(--gutter);
}

/* ---------- costuras entre secciones ----------
   Ninguna sección arranca con un corte. Los filetes de 1px de lado a lado
   —que eran justamente la línea que se veía— están fuera. Las secciones son
   transparentes, así que quien marca el cambio es el fondo del body, y ese
   viaja con el scroll de forma gradual (ver scroll.js, bloque 5). Las que sí
   tienen fondo o imagen propia llevan su fundido explícito: .hero__media,
   .split__media, .futuro, .cta y .contact. */
.section--rule { position: relative; }

/* El tramo de acento sigue marcando el inicio de sección: es un detalle
   buscado, no una línea de corte, y por eso se queda. */
/* El tramo arranca donde arranca el titular que anuncia, no en el borde de la
   pantalla: colgado del viewport se leía como una marca perdida. La fórmula es
   la misma que sitúa el .shell, así que sirve igual en secciones normales y de
   borde. */
.section--rule::before {
  content: '';
  position: absolute;
  top: 0;
  left: max(var(--gutter), calc((100% - var(--shell)) / 2));
  width: clamp(70px, 12vw, 160px);
  height: 2px;
  z-index: 4;
  background: linear-gradient(90deg, var(--accent), rgba(var(--accent-rgb), 0));
  transform: scaleX(var(--rule-scale, 1));
  transform-origin: left center;
}

/* ---------- buttons ---------- */

.btn {
  display: inline-block;
  font-size: 11px;
  font-weight: 400;
  letter-spacing: .24em;
  padding: 18px 38px;
  min-height: 52px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--cream-30);
  color: var(--cream);
  background: transparent;
  font-family: var(--texto);
}
.btn:hover,
.btn:focus-visible {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--ink);
  transform: translateY(-3px);
  box-shadow: var(--shadow-accent-sm);
}

/* El sólido declara su color en vez de heredarlo. Con el tema claro daba igual
   —fantasma y sólido querían los dos texto tinta—, pero al invertir el tema el
   fantasma pasa a crema sobre oscuro y el sólido sigue necesitando tinta,
   porque su relleno es verde encendido. Heredar aquí daba 1.19:1. */
.btn--solid {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--ink);
  box-shadow: var(--shadow-accent-md);
}
.btn--solid:hover,
.btn--solid:focus-visible { color: var(--ink); }
.btn--solid:hover,
.btn--solid:focus-visible {
  background: var(--cream);
  border-color: var(--cream);
  transform: translateY(-3px) scale(1.04);
  box-shadow: var(--shadow-accent-lg);
}

/* El botón del hero se distingue por el anillo pulsante, no por ser más
   grande. Llevaba además 13px y una caja de 56px que lo separaban del resto de
   botones de la landing; el tamaño vuelve al estándar de `.btn`. */
.btn--hero {
  letter-spacing: .26em;
  animation: pulseRing 3.4s ease-in-out infinite;
}

/* Variante para las escenas oscuras (hero y cierre): el botón base nace en
   tinta sobre papel, y ahí sería invisible. */
.btn--on-dark {
  color: var(--cream);
  border-color: var(--cream-45);
}
.btn--on-dark:hover,
.btn--on-dark:focus-visible {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--ink);
}

/* La segunda línea dice a dónde lleva el botón. Va dentro de la píldora, no
   debajo, para que la promesa viaje con la acción: cuatro botones decían
   "INVÓCANOS" y ninguno decía qué pasaba al pulsarlo. En minúscula y sin
   tracking para que no compita con la etiqueta, pero a 11px, que es el suelo
   de legibilidad del sistema: no es letra pequeña, es la mitad del mensaje. */
.btn__sub {
  display: block;
  margin-top: 5px;
  font-size: 11px;
  font-weight: 400;
  letter-spacing: .02em;
  text-transform: uppercase;
  opacity: .78;
}

.hero__actions,
.cta__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 14px 18px;
  align-items: center;
}
.hero__actions { justify-content: center; }
.cta__actions { justify-content: center; }


@keyframes pulseRing {
  0%, 100% { box-shadow: 0 0 0 0 rgba(var(--accent-rgb), .55); }
  50% { box-shadow: 0 0 0 14px rgba(var(--accent-rgb), 0); }
}

/* ---------- nav ---------- */

.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px var(--gutter);
  /* TRANSPARENTE SOBRE EL HERO. Nada de fondo ni de borde hasta que se
     scrollea: la barra es parte de la escena, no una franja encima. El estado
     sólido lo pone navbar.js. Se anima background y border, no `backdrop-filter`,
     porque animar el filtro obliga al compositor a rehacer el desenfoque en
     cada fotograma. */
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background-color var(--dur-medium) var(--ease-smooth),
              border-color var(--dur-medium) var(--ease-smooth),
              padding var(--dur-medium) var(--ease-smooth);
}

/* Sombra propia bajo el logo y los enlaces mientras la barra es transparente:
   sin ella, sobre la parte clara de una foto el texto desaparecía. */
.nav:not(.nav--solida) .nav__link,
.nav:not(.nav--solida) .nav__brand,
.nav:not(.nav--solida) .nav__hamburguesa {
  text-shadow: 0 1px 14px rgba(var(--ink-rgb), .85);
}
.nav:not(.nav--solida) .nav__logotipo {
  filter: drop-shadow(0 1px 12px rgba(var(--ink-rgb), .9));
}

.nav--solida {
  padding-top: 10px;
  padding-bottom: 10px;
  background: rgba(var(--ink-rgb), .92);
  border-bottom-color: var(--cream-12);
  backdrop-filter: blur(10px);
}

/* `backdrop-filter` convierte a la barra en bloque contenedor de sus
   descendientes `position: fixed`. Con la barra sólida, el panel del menú se
   posicionaba contra la barra — una caja de 375×124 — en vez de contra la
   pantalla, y "Servicios" y "Proceso" caían por encima del borde superior.
   Mientras el menú está abierto el desenfoque de la barra no aporta nada: el
   panel ya trae su propio fondo opaco y su propio blur. */
.nav[data-abierta="true"] { backdrop-filter: none; }

.nav__brand { display: flex; align-items: center; gap: 10px; }
/* El logotipo lleva el nombre dibujado: no necesita texto al lado. */
.nav__logotipo {
  height: clamp(16px, 1.6vw, 22px);
  width: auto;
  display: block;
  object-fit: contain;
}

.nav__menu {
  display: flex;
  align-items: center;
  gap: clamp(14px, 2vw, 28px);
}

.nav__link {
  font-family: var(--texto);
  font-size: var(--t-micro);
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--cream);
  padding-bottom: 3px;
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur-base) ease, color var(--dur-base) ease;
}
.nav__link:hover,
.nav__link:focus-visible { border-bottom-color: var(--accent); color: var(--accent); }

/* El CTA en verde macizo: es la única acción de la barra y tiene que
   distinguirse de los enlaces a simple vista. */
.nav__cta {
  font-family: var(--texto);
  font-size: var(--t-micro);
  letter-spacing: .2em;
  text-transform: uppercase;
  background: var(--accent);
  color: var(--ink);
  border: 1px solid var(--accent);
  padding: 10px 20px;
  border-radius: var(--radius-pill);
  transition: transform var(--dur-base) var(--ease-smooth), box-shadow var(--dur-base) ease;
}
.nav__cta:hover,
.nav__cta:focus-visible {
  transform: translateY(-2px);
  box-shadow: 0 6px 22px rgba(var(--accent-rgb), .34);
}

/* ---------- hamburguesa ---------- */

.nav__hamburguesa {
  display: none;
  position: relative;
  z-index: 2;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}
.nav__raya {
  position: absolute;
  left: 11px;
  width: 22px;
  height: 2px;
  background: var(--cream);
  transition: transform var(--dur-base) var(--ease-smooth), opacity var(--dur-base) ease;
}
.nav__raya:nth-child(1) { top: 15px; }
.nav__raya:nth-child(2) { top: 21px; }
.nav__raya:nth-child(3) { top: 27px; }

/* Abierta, las rayas se cruzan en aspa: el propio botón dice que ahora cierra. */
.nav[data-abierta="true"] .nav__raya:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.nav[data-abierta="true"] .nav__raya:nth-child(2) { opacity: 0; }
.nav[data-abierta="true"] .nav__raya:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

@media (max-width: 860px) {
  .nav__hamburguesa { display: block; }

  /* El panel ocupa la pantalla entera menos la barra. `visibility` además de
     opacidad: sin ella el menú cerrado seguiría siendo tabulable, y quien usa
     teclado se perdía dentro de un panel invisible. */
  .nav__menu {
    position: fixed;
    inset: 0;
    flex-direction: column;
    justify-content: center;
    gap: clamp(20px, 4.5vh, 34px);
    padding: 84px var(--gutter) 40px;
    background: rgba(var(--ink-rgb), .97);
    backdrop-filter: blur(14px);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-10px);
    transition: opacity var(--dur-medium) ease,
                transform var(--dur-medium) var(--ease-smooth),
                visibility 0s linear var(--dur-medium);
  }
  .nav[data-abierta="true"] .nav__menu {
    opacity: 1;
    visibility: visible;
    transform: none;
    transition-delay: 0s;
  }

  .nav__link { font-size: var(--t-caption); }
  .nav__cta { margin-top: 8px; }
}

@media (prefers-reduced-motion: reduce) {
  .nav, .nav__menu, .nav__raya, .nav__cta { transition: none; }
}

/* Los anclas no pueden caer debajo de la barra fija. Lenis ya aplica un
   desplazamiento, pero esto cubre el caso sin JS y la navegación directa por
   URL con #ancla. */
:target { scroll-margin-top: 84px; }
#conjuros, #proceso, #portfolio, #testimonios, #formulario { scroll-margin-top: 84px; }


/* ---------- hero · el estudio, con cámara y ambiente ---------- */

.hero {
  position: relative;
  display: grid;
  align-items: center;
  min-height: 100vh;
  min-height: 100svh;
  /* Sin color propio: detrás está el fondo del body, que el scroll va
     tiñendo. Así el borde inferior del hero es siempre del color de lo que
     viene después y no queda línea de corte. */
  background: transparent;
  overflow: hidden;
}

/* La imagen no termina: se desvanece. La máscara apaga el último tercio del
   hero hasta dejar pasar el fondo del body, que para entonces ya está
   virando al color de la sección siguiente. */
.hero__media {
  position: absolute;
  inset: 0;
  z-index: 0;
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 58%, rgba(0,0,0,.72) 79%, rgba(0,0,0,.28) 92%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 0%, #000 58%, rgba(0,0,0,.72) 79%, rgba(0,0,0,.28) 92%, transparent 100%);
}

/* La escena al 130% del contenedor: ese 15% sobrante por cada lado es el
   margen que necesita la cámara para moverse sin dejar bordes vacíos. */
/* La escena ocupa exactamente el hero, sin desbordarlo.

   Antes era 130% con `top: -15%`, para dar margen a una cámara que recorría
   mucho. Ese −15% subía la imagen 124px y le cortaba la cabeza a la figura:
   medido, el sombrero caía en −16px, por encima del borde de la pantalla. Y
   `object-position` no podía corregirlo, porque a estas proporciones la foto
   escalada mide justo el alto de la caja y todo el recorte es horizontal.

   Ahora el margen para el zoom lo da el propio tween, que arranca en 1.03 y
   nunca baja de ahí: un 3% por cada lado es de sobra para un zoom que llega
   a 1.10. */
.hero__scene {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  will-change: transform;
  /* El zoom crece hacia ARRIBA, con el borde inferior clavado al del hero.
     Con el origen centrado la escena escalada sobresalía por debajo del
     recorte y el compositor sangraba una fila de píxeles de la foto SIN
     máscara justo en la costura con el marquee: una línea naranja de 1px al
     pie del hero. Anclando el origen abajo no hay nada que sobresalga por
     ese borde y la línea desaparece. */
  transform-origin: 50% 100%;
}

.hero__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* 32%, no 42%. Sólo entra en juego en pantallas anchas y bajas, donde sí
     hay recorte vertical; ahí un 42% empujaba la figura hacia arriba y le
     comía la cabeza. Bajarlo muestra más cielo y deja el sombrero holgado
     sin llegar a cortar el círculo de velas por abajo. */
  object-position: 50% 32%;
}

/* Las llamas empiezan invisibles y el scroll las trae. La opacidad la lleva
   scroll.js con scrub; aquí sólo el punto de partida, para que quien llegue
   con la página ya scrolleada no vea un parpadeo antes de que GSAP arranque. */
.hero__photo--llamas { opacity: 0; }

/* El lienzo del temblor del fuego lo inyecta hero-fuego.js sobre la zona del
   círculo. Va dentro de .hero__scene para viajar con el parallax. */
.hero__fuego {
  position: absolute;
  pointer-events: none;
  z-index: 1;
}

/* Sin JS las llamas no aparecerían nunca: mejor dejar la versión encendida
   visible a media mezcla que servir un hero muerto. */
.no-js .hero__photo--llamas { opacity: .55; }

/* El ambiente lo inyecta hero-ambient.js dentro de .hero__scene, así que
   viaja con la cámara en vez de quedarse pegado a la pantalla. */
.hero__ambient {
  position: absolute;
  inset: 0;
  pointer-events: none;
  mix-blend-mode: screen;
  /* Por encima del lienzo del fuego: el polvo flota delante de todo. */
  z-index: 2;
}

.hero__scrim {
  position: absolute;
  inset: 0;
  /* Tres capas para una composición centrada:
       1. el velo que sostiene el bloque de texto, centrado en el eje;
       2. una viñeta que cierra los bordes y empuja la mirada al centro;
       3. el degradado vertical de siempre, que asienta pie y cabecera.
     Entre la capa 1 y la 2 queda un anillo donde la foto respira a plena luz:
     ese anillo es lo que evita que el hero se lea como una mancha negra. */
  background:
    radial-gradient(74% 54% at 50% 50%, rgba(var(--ink-rgb),.90), rgba(var(--ink-rgb),0) 76%),
    radial-gradient(116% 96% at 50% 50%, rgba(var(--ink-rgb),0) 42%, rgba(var(--ink-rgb),.86) 100%),
    linear-gradient(180deg, rgba(var(--ink-rgb),.62) 0%, rgba(var(--ink-rgb),.34) 34%, rgba(var(--ink-rgb),.74) 100%);
}

/* Cartel simétrico. El bloque se centra en la pantalla y se lee por un eje
   vertical único: antetítulo → monograma → marca → filete → claim → grito.
   La foto deja de ser un fondo con el texto arrinconado y pasa a ser el
   escenario de una portada. */
.hero__panel {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  /* `center` haría que cada hijo se encogiera a fit-content, y un párrafo con
     las líneas partidas en bloques colapsa a min-content: el subtítulo se caía
     a 106px y se leía en trece líneas de una palabra. Los hijos ocupan el ancho
     del panel y se centran por dentro con text-align y sus propios max-width. */
  align-items: stretch;
  justify-content: center;
  gap: clamp(18px, 2.6vh, 30px);
  width: 100%;
  max-width: min(1080px, 92vw);
  margin-inline: auto;
  text-align: center;
  /* Menos aire vertical que antes: ahora el margen lo dibuja el marco, y la
     marca a tamaño de cartel necesita ese alto para no quedarse sin sitio en
     portátiles de 800px. */
  padding: clamp(84px, 11vh, 124px) var(--gutter) clamp(52px, 8vh, 92px);
}

/* El marco interior: el hero se lee como una carta. Es el mismo lenguaje que
   ya usan los conjuros, y es lo que sostiene la composición cuando le quitas
   el texto. Se apaga por abajo con la misma máscara que la foto para no
   reintroducir la línea de corte contra la sección siguiente. */
.hero::after {
  content: '';
  position: absolute;
  inset: clamp(14px, 2.2vw, 34px);
  border: 1px solid var(--cream-22);
  pointer-events: none;
  z-index: 1;
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 52%, rgba(0,0,0,.5) 74%, transparent 92%);
  mask-image: linear-gradient(180deg, #000 0%, #000 52%, rgba(0,0,0,.5) 74%, transparent 92%);
}

/* JERARQUÍA DEL HERO — un solo mensaje manda: "ÚNETE A NUESTRO AKELARRE".
   El antetítulo y el logotipo forman una única frase y por eso van pegados y
   a la misma escala de importancia; todo lo demás baja un escalón para no
   competir. Nivel 1: antetítulo + marca. Nivel 2: el claim en cursiva.
   Nivel 3: el grito, en versales de titular. */
.hero__over {
  /* AIRE ENTRE EL ANTETÍTULO Y LA MARCA. El margen era NEGATIVO —hasta -16px—
     para que ambos se leyeran como una sola frase pegada. Ahora respiran: el
     antetítulo presenta, cae el silencio, y el logotipo entra después. El
     hueco se dobla respecto al primer ajuste (de 22 a 44px de tope) porque
     con el antetítulo ya pequeño el aire es lo único que marca la pausa. */
  margin: 0 0 clamp(20px, 3.2vh, 44px);
  font-family: var(--texto);
  font-size: clamp(10px, 0.95vw, 14px);
  font-weight: 400;
  letter-spacing: .46em;
  line-height: 1;
  color: var(--white);
  text-indent: .46em;          /* compensa el tracking del último carácter */
}

.hero__title {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: clamp(16px, 2.4vh, 28px);
  font-weight: 400;
  /* El h1 está sobre foto oscura: sin esto hereda --ink y cualquier texto que
     se añada dentro nacería negro sobre negro. */
  color: var(--cream);
}

/* Monograma + palabra: una sola unidad apilada en el eje. El monograma corona
   la composición y la marca es la espina del cartel. */
.hero__lockup {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(10px, 1.6vh, 22px);
}

/* El logotipo es el artefacto del hero. Va a tamaño de cartel y con una
   sombra larga que lo despega de la foto sin recortarle contraste. */
.hero__logotipo {
  width: min(760px, 62vw);
  height: auto;
  display: block;
  filter: drop-shadow(0 8px 44px rgba(0, 0, 0, .6));
}


@media (max-width: 860px) {
  .hero__lockup { gap: 10px; }
  /* En vertical el logotipo casi cruza la pantalla: es lo que convierte el
     hero en un cartel en lugar de un titular centrado. */
  .hero__logotipo { width: 86vw; }
}

.hero__shout em { font-style: normal; font-weight: 400; color: var(--accent); }

/* El filete nace en el centro y se abre a los dos lados, con el acento
   marcando el eje. Es el mismo tramo de acento que abre cada sección. */
.hero__rule {
  /* SIMETRÍA ALREDEDOR DEL LOGOTIPO. Medido, el aire de arriba y el de abajo
     ya eran casi iguales (52 y 49px), pero abajo el ojo no llega a la frase:
     tropieza antes con este filete, que estaba a 23px del logotipo. Se le da
     el MISMO margen que separa el antetítulo de la marca —el mismo token, no
     un número copiado— y así el logotipo queda centrado entre dos silencios
     iguales. */
  margin-top: clamp(20px, 3.2vh, 44px);
  position: relative;
  height: 1px;
  width: min(420px, 62vw);
  margin-inline: auto;
  background: linear-gradient(90deg,
    rgba(245,240,232,0), var(--cream-22) 28%, var(--cream-22) 72%, rgba(245,240,232,0));
}
.hero__rule::after {
  content: '';
  position: absolute;
  top: -1px;
  left: 50%;
  width: clamp(52px, 7vw, 96px);
  height: 3px;
  transform: translateX(-50%);
  background: linear-gradient(90deg,
    rgba(var(--accent-rgb), 0), var(--accent) 50%, rgba(var(--accent-rgb), 0));
}

.hero__shout {
  margin: 0;
  /* MONTSERRAT, NO TAN HARMONY. Es una frase de lectura, no un rótulo: la
     display en versales a este cuerpo pesaba más de lo que aporta. En caja
     natural, con sus mayúsculas de inicio de frase (ver la regla de caja al
     final del fichero). */
  font-family: var(--texto);
  /* AHORA ES LA ÚNICA FRASE DEL HERO. Bajó a 17px cuando competía con el claim
     —dos voces del mismo cuerpo bajo el logotipo—, pero al retirarse el claim
     se quedó sola y pequeña. Sube a 24px: sigue muy por debajo del logotipo,
     que es el titular de verdad, y recupera el peso de mensaje principal. */
  font-size: clamp(16px, 1.35vw, 20px);
  line-height: 1.6;
  letter-spacing: .01em;
  color: var(--cream-85);
  /* Ancho DEFINIDO, no un simple máximo. Un margen automático en el eje
     transversal anula el `stretch` del flex, así que el párrafo volvía a
     shrink-to-fit y, con las líneas partidas en bloques, se hundía a
     min-content: 106px y trece líneas de una palabra. Con `width` resuelto,
     los márgenes automáticos sólo centran. */
  /* La medida se acuerda con el filete de arriba (420px) en vez de quedarse en
     229px: encajonada era tan estrecha que caía en cuatro renglones cortos
     bajo un logotipo de 760px, y ese desajuste de anchos era la mitad del
     problema. */
  width: min(46ch, 100%);
  margin-inline: auto;
  /* Lo atmosférico lo pone la sombra, no la transparencia: separa el texto de
     la foto sin quitarle ni un punto de contraste. */
  text-shadow: 0 2px 18px rgba(var(--ink-rgb), .85), 0 1px 3px rgba(var(--ink-rgb), .7);
}

@media (max-width: 860px) {
  .hero__panel { padding: clamp(104px, 16vh, 150px) var(--gutter) clamp(56px, 9vh, 90px); }
  .hero__shout { max-width: 24ch; }
  .hero::after { inset: 12px; }
  .hero__scrim {
    background:
      radial-gradient(96% 46% at 50% 50%, rgba(var(--ink-rgb),.76), rgba(var(--ink-rgb),0) 78%),
      radial-gradient(130% 96% at 50% 50%, rgba(var(--ink-rgb),0) 46%, rgba(var(--ink-rgb),.7) 100%),
      linear-gradient(180deg, rgba(var(--ink-rgb),.62) 0%, rgba(var(--ink-rgb),.3) 34%, rgba(var(--ink-rgb),.84) 100%);
  }
}

.cta__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* A SANGRE, SIN MÁSCARA. Aquí había un `mask-image` que desvanecía el 20% de
     arriba y el 18% de abajo: por eso la escena no llegaba a los bordes y
     parecía una franja flotando en negro en vez de ocupar la pantalla. La foto
     ahora cubre el viewport entero y se recorta sola con `cover`.
     La foto nueva es apaisada (2200×1228) con el plano más abierto, así que
     el recorte pasa a ser sobre todo vertical: se ancla algo por encima del
     centro para no cortarle el sombrero ni perder el techo. */
  object-position: 50% 44%;
  background: var(--ink);
}


/* ---------- marquee ---------- */

/* El marquee es lo primero que recibe al hero, así que no puede tener filetes:
   sus bordes se difuminan y la cinta entra y sale por los lados en vez de
   cortarse en seco. */
.marquee {
  position: relative;
  overflow: hidden;
  padding: clamp(22px, 3.4vh, 40px) 0;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
}

.marquee__track {
  display: flex;
  width: max-content;
  /* El desplazamiento lo lleva marquee.js con GSAP. Antes había aquí
     una `animation` que escribía el mismo transform que el tween y se
     pisaban: la cinta se quedaba parada. */
  white-space: nowrap;
}

.marquee__track span {
  /* Montserrat, no Tan Harmony: la cinta es interfaz en movimiento, no un
     titular. Baja de 30px a 13px —cinta discreta, no un segundo titular— y
     conserva el interletrado amplio y el verde de marca, que es justo lo que
     la sostiene a ese cuerpo. */
  font-family: var(--texto);
  font-size: var(--t-cinta);
  letter-spacing: .22em;
  line-height: 1.4;
  /* Verde de marca, como en el manual: la cinta es el primer acento de color
     que se ve al bajar del hero. 10,5:1 sobre #0F1014. */
  color: var(--accent);
  padding-right: 1ch;
}

/* ---------- manifiesto ---------- */

.manifest {
  padding: var(--sp-ancho) var(--gutter);
  text-align: center;
}
.manifest__inner { max-width: 960px; margin: 0 auto; }
.manifest__title { font-size: var(--t-declaracion); line-height: var(--lh-titular); letter-spacing: 0; text-wrap: balance; }
.manifest__title em { font-weight: 400; font-size: 1.12em; color: var(--accent-soft); }
.manifest__lede { margin: clamp(20px, 3vh, 34px) auto 0; max-width: 60ch; }
.manifest__kicker {
  margin: clamp(22px, 3vh, 38px) 0 0;
  font-family: var(--titular);
  font-weight: 400;
  font-size: clamp(28px, 5.6vw, 96px);
  line-height: var(--lh-titular);
  letter-spacing: 0;
}
.manifest__kicker em { font-weight: 400; color: var(--accent-soft); }

/* ---------- la prueba, en el primer tercio ---------- */

.prueba {
  margin: 0 auto;
  padding: clamp(30px, 4vh, 52px) 0 clamp(46px, 7vh, 90px);
  border-top: 1px solid var(--cream-12);
  text-align: center;
}
.prueba__intro,
.prueba__enlace { margin-inline: var(--gutter); }
.prueba__intro {
  margin: 0 0 clamp(18px, 2.4vh, 28px);
  font-size: var(--t-micro);
  letter-spacing: .3em;
  text-transform: uppercase;
  color: var(--cream-60);
}
.prueba__enlace {
  display: inline-block;
  margin-top: clamp(20px, 2.6vh, 30px);
  padding: 12px 4px;
  font-size: var(--t-caption);
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--accent-soft);
  border-bottom: 1px solid rgba(var(--accent-soft-rgb), .4);
  padding-bottom: 4px;
}
.prueba__enlace:hover,
.prueba__enlace:focus-visible { border-bottom-color: var(--accent-soft); }

/* ---------- frankenstein split ---------- */

.split {
  position: relative;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
}

/* LA MÁSCARA SE DIFUMINA POR LOS CUATRO LADOS — no volver al gradiente
   vertical. Antes era `linear-gradient(180deg, ...)`, que sólo funde arriba y
   abajo: los costados quedaban cortados a hacha contra el fondo crema y se
   veía el rectángulo. Una elipse funde el contorno entero, así que la foto se
   posa sobre el papel en vez de estar pegada encima. */
.split__media {
  position: relative;
  min-height: 78vh;
  overflow: hidden;
  /* SIN MÁSCARA, A PROPÓSITO. Esta foto vive sobre papel, así que cualquier
     difuminado la funde en blanco y la deja lavada por los bordes. Va entera,
     con su borde recto. La única foto con difuminado es la del formulario, que
     se funde en negro y ahí sí suma. */
}

/* Una sola foto, sin tiras. Las cinco tiras verticales partían la cara por la
   mitad y, al separarse en la salida, dejaban franjas de papel en blanco a
   media pantalla. Una imagen entera no puede hacer ninguna de las dos cosas. */
.split__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 40%;
  will-change: transform, opacity, filter;
}

.split__body {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 26px;
  /* Relato: denso a propósito (ver el ritmo de volúmenes en :root). */
  /* EL MARGEN LATERAL ES EL DE LA PÁGINA, no uno propio. Con
     `clamp(28px, 4vw, 68px)` este texto arrancaba en 57,6px mientras servicios,
     proceso, portfolio, testimonios, contacto y pie arrancan todos en 72px:
     catorce píxeles de desfase en la única columna que el ojo usa como guía al
     bajar. Ahora comparten margen y la línea de arranque es una sola en toda
     la página. */
  padding: var(--sp-corto) var(--gutter);
}
.split__title { font-size: var(--t-declaracion); line-height: var(--lh-titular); letter-spacing: 0; }
.split__title em { font-weight: 400; color: var(--accent-soft); }
.split__body .lede { max-width: 52ch; }

/* ---------- conjuros / flip cards ---------- */

/* Las cartas entran desde muy lejos (xPercent de hasta ±620) y ese vuelo
   ensanchaba el documento hasta 2167px: se podía arrastrar la página en
   horizontal. Se recorta con `clip` y NO con `hidden`, porque `hidden` crea un
   contenedor de scroll y eso rompería el pin de la sección. */
#conjuros {
  overflow-x: clip;
  overflow-y: visible;
}

/* CONTRASTE: esta sección vive SIEMPRE sobre fondo oscuro, así que no puede
   heredar los colores pensados para el papel. El titular heredaba --ink de
   .h-display: era negro sobre negro, 1:1, invisible. El antetítulo iba en el
   acento oscurecido de entonces, que sobre tinta se quedaba en 3,1:1.
   Ahora los dos van en crema: 18,5:1 sobre tinta. */
.conjuros__title {
  margin: 0 0 clamp(30px, 4vh, 50px);
  font-size: var(--t-escaparate);
  max-width: 20ch;
  color: var(--cream);
  line-height: var(--lh-titular);
  letter-spacing: 0;
}
.conjuros__title em { color: var(--accent); }

/* SERVICIOS era el único eyebrow fuera del sistema: crema a 19px cuando los
   otros siete son verdes a 12. Sobre el negro, crema al peso 400 con 5,7px de
   interletrado se lee apagado — y además rompía la rima de toda la página.
   Vuelve al verde de marca; sólo se conserva el aire de abajo, que esta
   sección sí necesita más ancho por el titular que lleva debajo. */
#conjuros .eyebrow {
  /* Tenía margen propio y, al ir con un id, ganaba al sistema: era la única
     sección con 20px donde el resto tiene 12. */
  margin-bottom: var(--sp-rotulo);
}

.cards {
  position: relative;
  perspective: 1800px;
  perspective-origin: 50% 40%;
  display: flex;
  flex-wrap: nowrap;
  justify-content: center;
  align-items: flex-start;
  max-width: 1320px;
  margin: 0 auto;
  padding: 30px 0 60px;
}

/* El slot posiciona la carta en el abanico — es lo que GSAP anima al
   repartir la baraja. La carta de dentro se queda con el hover y el volteo,
   así los dos transforms no se pisan. */
/* NO PONER `filter` NI EN will-change NI ANIMADO — el texto se vuelve borroso.
   Un `filter`, aunque sea `blur(0px)`, promociona el slot a su propia capa y
   el navegador RASTERIZA su contenido una vez, a la resolución que tenga en
   ese momento. Después la perspectiva y el volteo escalan ese mapa de bits ya
   cocido: las ilustraciones aguantan porque son fotos, pero el texto de la
   cara trasera se ve blando y sucio. Las cartas entran con opacidad, escala y
   giro, que se componen en la GPU sin rasterizar texto. */
/* EL SOLAPE DECIDE SI SE LEEN LOS SERVICIOS. A -46px cada carta tapaba 92px
   (el 38%) de la anterior, y como el nombre del servicio va grabado en la
   cartela inferior de la ilustración, seis de los siete salían cortados:
   "BRANDIN…", "REDES SOCIA…". A -36px el paso es de 178px, el abanico mide
   1318px y cabe justo en los 1320 del contenedor, y la cartela queda casi
   despejada. La lista en texto plano de debajo cierra lo que el abanico no
   puede prometer por sí solo. */
.card-slot {
  position: relative;
  z-index: 2;
  flex: 0 0 auto;
  width: 250px;
  margin: 0 -36px;
  transform-origin: 50% 118%;
  transform-style: preserve-3d;
  will-change: transform, opacity;
}

.card-slot:nth-child(1) { transform: rotate(-16deg) translateY(38px); }
.card-slot:nth-child(2) { transform: rotate(-11deg) translateY(20px); }
.card-slot:nth-child(3) { transform: rotate(-5deg) translateY(7px); }
.card-slot:nth-child(4) { transform: rotate(0deg); }
.card-slot:nth-child(5) { transform: rotate(5deg) translateY(7px); }
.card-slot:nth-child(6) { transform: rotate(11deg) translateY(20px); }
.card-slot:nth-child(7) { transform: rotate(16deg) translateY(38px); }

.card-slot:hover,
.card-slot:focus-within { z-index: 9; }

.card {
  display: block;
  perspective: 1600px;
  transition: transform var(--dur-medium) var(--ease-out);
}

.card:hover,
.card:focus-within { transform: translateY(-30px) scale(1.1); }

.card__inner {
  position: relative;
  aspect-ratio: 1024 / 1536;
  transform-style: preserve-3d;
  transition: transform var(--dur-flip) var(--ease-smooth), box-shadow var(--dur-medium) var(--ease-out);
  box-shadow: var(--shadow-card);
}

.card:hover .card__inner,
.card:focus-within .card__inner {
  transform: rotateY(180deg);
  transition-delay: .12s;
  box-shadow: var(--shadow-card-lift);
}

.card__face,
.card__back {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
  border: 1px solid var(--cream-12);
}

/* LA ILUSTRACIÓN VA EN UN una etiqueta img con loading lazy, NO EN background-image. Como
   fondo CSS, las siete cartas se descargaban en la carga inicial (unos 680 KB)
   aunque estén a seis pantallas de distancia: un fondo no participa de la carga
   diferida. La etiqueta img dentro da el mismo encuadre y llega cuando toca. El color
   de fondo se queda como relleno mientras la imagen no está. */
.card__face {
  overflow: hidden;
  background-color: var(--cream);
  filter: grayscale(1) contrast(1.05) brightness(.92);
  transition: filter var(--dur-medium) var(--ease-out);
}

.card__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  display: block;
}
.card:hover .card__face,
.card:focus-within .card__face { filter: grayscale(1) contrast(1.1) brightness(1.04); }

.card__back {
  overflow: hidden;
  transform: rotateY(180deg);
  background: var(--carbon);
  /* Mismo humo verde que en los testimonios: la cara con texto queda en el
     mismo negro que el fondo y necesita despegarse. Va por dentro (`inset`)
     porque la carta se voltea en 3D y una sombra exterior se recortaría
     contra el plano de la cara. */
  box-shadow:
    inset 0 0 60px rgba(var(--accent-rgb), .10),
    inset 22px 18px 90px rgba(var(--accent-rgb), .07);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 14px;
  padding: 22px;
}

.card__tag {
  flex: 0 0 auto;
  align-self: flex-start;
  max-width: 100%;
  background: var(--accent);
  color: var(--ink);
  font-size: var(--t-micro);
  font-weight: 400;
  letter-spacing: .16em;
  padding: 8px 13px;
  border-radius: var(--radius-pill);
}

.card__copy {
  margin: 0;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  font-family: var(--texto);
  font-size: 15px;
  line-height: 1.34;
  text-wrap: pretty;
}

/* La firma va en --ink-60 y no en --ink-45: a 9,5px sobre hueso, el 45% daba
   3,06:1 y esto es texto, no un adorno. El 60% da 5,0:1 y sigue leyéndose como
   una marca de agua al lado del copy en negro. */
.card__mark {
  flex: 0 0 auto;
  font-family: var(--texto);
  font-size: var(--t-micro);
  letter-spacing: .26em;
  color: var(--cream-60);
}

/* La lista de conjuros en texto plano, bajo el abanico. Vive sobre fondo
   oscuro, así que va en crema. */
.conjuros__lista {
  list-style: none;
  margin: clamp(6px, 1.6vh, 22px) 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px 0;
  font-size: var(--t-caption);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--cream-80);
}
.conjuros__lista li { display: flex; align-items: center; }
.conjuros__lista li::after {
  content: '✦';
  margin: 0 14px;
  color: var(--accent);
  font-size: var(--t-micro);
}
.conjuros__lista li:last-child::after { content: none; }

@media (max-width: 860px) {
  .conjuros__lista { font-size: 12px; letter-spacing: .12em; }
  .conjuros__lista li::after { margin: 0 9px; }
}

@media (min-width: 861px) and (max-width: 1420px) {
  .cards { transform: scale(.82); transform-origin: top center; margin-bottom: -8%; }
}
@media (min-width: 861px) and (max-width: 1180px) {
  .cards { transform: scale(.68); margin-bottom: -14%; }
}
@media (min-width: 861px) and (max-width: 1000px) {
  .cards { transform: scale(.58); margin-bottom: -20%; }
}

/* Flatten the flip on touch / small screens */
@media (hover: none), (max-width: 860px) {
  .cards { flex-wrap: wrap; padding: 0; gap: clamp(20px, 2.2vw, 34px); }
  .card-slot {
    flex: 1 1 100%;
    max-width: 460px;
    width: auto;
    margin: 0;
    transform: none !important;
  }
  .card { perspective: none; transform: none !important; }
  .card__inner {
    position: static;
    aspect-ratio: auto;
    display: flex;
    flex-direction: column;
    transform: none !important;
    transform-style: flat;
    box-shadow: var(--shadow-card-flat);
  }
  .card__face {
    position: relative;
    inset: auto;
    aspect-ratio: 1024 / 1536;
    backface-visibility: visible;
    filter: grayscale(1) contrast(1.05);
  }
  .card__back {
    position: static;
    inset: auto;
    transform: none;
    backface-visibility: visible;
    overflow: visible;
    padding: 24px;
  }
  .card__copy { font-size: 17px; overflow: visible; }
}

/* ---------- proceso ---------- */

/* Sección de relato: respira menos a propósito. Comprimir los valles es lo
   que hace que el manifiesto y el cierre se sientan amplios. */
.proceso { position: relative; overflow: hidden; padding-block: var(--sp-corto); }
.proceso__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 25%;
  opacity: .1;
}
.proceso__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(33,33,40,.9), rgba(33,33,40,.84) 50%, rgba(33,33,40,.96));
}
.proceso__inner { position: relative; }
.proceso__title { margin: 0 0 clamp(34px, 4.5vh, 56px); font-size: var(--t-relato); line-height: var(--lh-titular); letter-spacing: 0; max-width: 26ch; }
.proceso__title em { font-weight: 400; color: var(--accent-soft); }

.steps {
  position: relative;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: clamp(28px, 4vw, 70px);
  padding-top: 54px;
  counter-reset: paso;
}

/* Hilo que une los tres pasos; GSAP lo dibuja con el scroll. */
.steps__line {
  position: absolute;
  top: 6px;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(90deg, var(--accent-soft), var(--accent));
  /* El progreso va en una variable para que cada breakpoint escoja su eje:
     horizontal en escritorio, vertical en móvil. Una sola animación. */
  transform: scaleX(var(--line-p, 1));
  transform-origin: left center;
}
html.has-scroll .steps__line { --line-p: 0; }

.step {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 0 clamp(14px, 1.6vw, 24px) 0 0;
  counter-increment: paso;
}

/* Numeral gigante de fondo: decorativo, no toca el copy. */
.step__ghost::before { content: counter(paso, decimal-leading-zero); }
.step__ghost {
  position: absolute;
  top: -46px;
  /* Anclado a la izquierda de SU paso. A la derecha, el 01 y el 02 se metían
     en la columna del siguiente y sólo el 03 caía sobre su propio texto. */
  left: 0;
  font-family: var(--texto);
  font-size: clamp(72px, 8vw, 128px);
  line-height: 1;
  color: var(--accent);
  opacity: .16;
  pointer-events: none;
  user-select: none;
}

/* Nudo sobre el hilo, a la altura de cada paso. */
.step__dot {
  position: absolute;
  top: -55px;
  left: 0;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: var(--grafito);
  border: 2px solid var(--accent-soft);
}
.step__dot::after {
  content: '';
  position: absolute;
  inset: 2px;
  border-radius: 50%;
  background: var(--accent-soft);
  transition: transform var(--dur-medium) var(--ease-out);
}
html.has-scroll .step__dot::after { transform: scale(0); }
html.has-scroll .step.is-on .step__dot::after { transform: scale(1); }

.step__num {
  font-family: var(--texto);
  font-size: 12px;
  letter-spacing: .24em;
  color: var(--accent-soft);
}
.step__title {
  font-size: var(--t-paso);
  line-height: var(--lh-titular);
  letter-spacing: 0;
  margin: 0;
  font-family: var(--titular);
  font-weight: 400;
}
.step__body { margin: 0; font-size: 16.5px; line-height: 1.75; font-weight: 400; color: var(--cream-74); }

@media (max-width: 860px) {
  .steps { grid-template-columns: 1fr; gap: 56px; padding-top: 46px; }
  .steps__line {
    top: 0;
    bottom: 0;
    left: 5px;
    right: auto;
    width: 2px;
    height: auto;
    background: linear-gradient(180deg, var(--accent-soft), var(--accent));
    transform: scaleY(var(--line-p, 1));
    transform-origin: center top;
  }
  .step { padding-left: 34px; padding-right: 0; }
  .step__dot { top: 4px; left: 0; }
  .step__ghost { top: -30px; }
}

/* ---------- rails (portfolio + testimonios) ---------- */

/* Carril pineado: cuando GSAP lo desplaza, el scroll nativo se apaga. */
/* Pineado: el visor deja de ser un contenedor de scroll por completo. `clip`
   recorta sin crear caja desplazable —con `hidden` el navegador sigue pudiendo
   mover scrollLeft— y se anulan snap y desplazamiento suave, que sólo tenían
   sentido cuando el carril se navegaba a mano. El visor NO se transforma. */
/* Doble clase A PROPÓSITO: `.rail` se declara más abajo y, a igualdad de
   especificidad, ganaba por orden — el visor se quedaba con su `overflow-x:
   auto` y seguía siendo desplazable pese a estar pineado. Esa era la causa de
   los enganchones. */
.rail.rail--pinned {
  overflow: clip;
  scroll-snap-type: none;
  scroll-behavior: auto;
  padding-right: 0;
  touch-action: pan-y;
}

/* Lo único que se mueve. Las tarjetas recuperan aquí el respiro lateral que
   antes ponía el padding del visor. */
.rail__track {
  display: flex;
  gap: inherit;
  padding-right: var(--gutter);
  will-change: transform;
}

.rail.rail--pinned > .rail__track > * { scroll-snap-align: none; }

/* Carril del portfolio: CSS puro cuando no está pineado. El navegador ya sabe
   desplazar con inercia y encajar tarjetas; cualquier cosa que montemos
   encima sólo puede empeorarlo. */
.rail {
  display: flex;
  gap: clamp(18px, 2.4vw, 34px);
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-padding-inline-start: var(--gutter);
  scroll-behavior: smooth;
  padding: var(--sp-corto) var(--gutter) 34px;
  scrollbar-width: thin;
  scrollbar-color: var(--accent) transparent;
}

/* LOS EXTREMOS DEL CARRIL, DISUELTOS. El carril del portfolio llega hasta el
   borde del viewport y las miniaturas se cortaban ahí en seco, con media foto
   partida por una línea recta. Con el desvanecido, lo que hay fuera se apaga
   en el negro y además se lee que el carril continúa. Sólo en horizontal: en
   vertical se llevaría por delante los pies de foto. Va sobre `#portfolio`
   para no tocar el carril de testimonios, que en móvil es una columna. */
#portfolio .rail {
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 4%, #000 96%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 4%, #000 96%, transparent);
}

.rail::-webkit-scrollbar { height: 6px; }
.rail::-webkit-scrollbar-track { background: var(--cream-08); }
.rail::-webkit-scrollbar-thumb { background: var(--accent-soft); border-radius: 999px; }

/* SIN `max-width`: los saltos van en el marcado con <br> y una caja estrecha
   volvería a partir cada línea por su cuenta. El tamaño se limita también por
   ancho de ventana para que la línea más larga —"ESTRATEGIA, IDENTIDAD,
   DIRECCIÓN Y MUCHAS DECISIONES DETRÁS.", 59 caracteres— quepa entera sin
   reventar la caja en pantallas medianas. */
.portfolio__title {
  font-size: min(var(--t-escaparate), 3.6vw);
  max-width: none;
  line-height: var(--lh-titular);
  letter-spacing: 0;
  text-wrap: nowrap;
}
@media (max-width: 1100px) {
  /* Por debajo de este ancho la línea larga ya no cabe entera: se deja que
     envuelva dentro de su unidad en vez de desbordar. */
  .portfolio__title { font-size: var(--t-escaparate); text-wrap: wrap; }
}
.portfolio__title em { font-weight: 400; color: var(--accent-soft); }

.project {
  margin: 0;
  flex: 0 0 auto;
  width: clamp(280px, 42vw, 560px);
  display: flex;
  flex-direction: column;
  gap: 18px;
  scroll-snap-align: start;
  scroll-snap-stop: always;
}

/* Misma razón que en las cartas: las seis miniaturas eran fondos CSS y se
   descargaban de entrada. Ahora son una etiqueta img con loading lazy dentro del marco. */
.project__media {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  /* Carbón, no un gris claro: mientras carga la foto no puede
     encenderse un rectángulo blanco sobre la página negra. */
  background-color: var(--carbon);
  border: 1px solid var(--cream-08);
  transition: transform var(--dur-slow) var(--ease-out);
}

.project__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  display: block;
}
.project:hover .project__media { transform: scale(1.02); }

.project__caption { display: flex; align-items: baseline; gap: 12px; }
.project__star { color: var(--accent-soft); font-size: var(--t-caption); }
/* El descriptor del proyecto, según el PDF de la clienta (pág. 9): mismo
   renglón que el nombre, un escalón por debajo en peso visual. */
.project__sub {
  font-family: var(--texto);
  font-weight: 400;
  font-size: var(--t-caption);
  letter-spacing: .12em;
  color: var(--cream-60);
}

.project__name {
  font-family: var(--texto);
  font-weight: 400;
  /* Tope 28px: es Montserrat, y el manual no deja pasar de 28,5px. Con el 34px
     anterior se lo saltaba en cualquier pantalla de 1295px en adelante. */
  font-size: clamp(22px, 2.2vw, 28px);
  letter-spacing: .01em;
}

.reviews__title { margin: 0 0 26px; font-size: var(--t-escaparate); line-height: var(--lh-titular); max-width: none;
  letter-spacing: 0;
}
.reviews__title em { font-weight: 400; color: var(--accent-soft); }
.reviews__lede { max-width: 62ch; }

/* ---------- testimonios: la baraja ----------
   En escritorio no desfilan: se reparten. La sección se pina y las opiniones
   se apilan como un mazo — sólo la de arriba se lee, grande y enfocada,
   mientras las demás esperan turno al fondo. La profundidad es real: el
   escenario lleva perspectiva y testimonios.js coloca cada carta en Z según
   lo lejos que esté del turno. */

/* La sección se queda pinada, así que tiene que caber ENTERA en la pantalla:
   con la altura natural sobraban ~100px y el pie de la última carta se
   cortaba. Se reparte en columna y el mazo se queda con lo que sobre después
   del titular, en vez de reclamar una altura fija que nadie garantiza. */
.reviews-pin {
  /* Fijado, no heredado: scroll.js tiñe el body por tramos y esta sección
     tiene que quedarse en el negro del manual pase lo que pase. */
  background: var(--ink);
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-top: clamp(20px, 4vh, 52px);
  padding-bottom: clamp(20px, 4vh, 52px);
  overflow: clip;
}

.reviews-pin > .shell { flex: 0 0 auto; }

.deck {
  position: relative;
  margin: clamp(18px, 3vh, 40px) auto 0;
  max-width: min(820px, 92vw);
  width: 100%;
  perspective: 1400px;
  perspective-origin: 50% 42%;
  transform-style: preserve-3d;
}

/* Dentro de la sección pinada toma el hueco restante. El mínimo cubre la
   opinión más larga (366px medidos), así que nunca se recorta una carta. */
.reviews-pin .deck {
  flex: 1 1 auto;
  min-height: 380px;
  max-height: 560px;
}

/* Portátiles bajos: el titular, la entradilla y la propia carta se aprietan a
   la vez. Si sólo se encogiera el escenario, la carta se saldría por arriba y
   el recorte de la sección se la comería. */
@media (min-width: 861px) and (max-height: 780px) {
  /* Portátiles bajos: el hero se queda 18px largo con el relleno normal y la
     última línea del grito se sale por debajo del pliegue. */
  .hero__panel { padding-top: clamp(58px, 9vh, 96px); padding-bottom: clamp(38px, 6vh, 68px); }
  .hero__panel { gap: clamp(14px, 2vh, 24px); }

  .reviews-pin { padding-top: 14px; padding-bottom: 14px; }
  .reviews-pin .reviews__title { font-size: clamp(28px, 3.2vw, 46px); margin-bottom: 14px; }
  .reviews-pin .reviews__lede { font-size: 14.5px; line-height: 1.55; }
  .reviews-pin .deck { min-height: 300px; margin-top: 14px; }
  .deck .review { padding: 22px 26px; gap: 14px; }
  .deck .review__quote { font-size: 54px; }
}


.deck .review {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 100%;
  margin: 0;
  transform: translate(-50%, -50%);
  transform-origin: 50% 100%;
  backface-visibility: hidden;
  will-change: transform, opacity, filter;
}

/* El mazo vive sobre el fondo claro, así que las cartas siguen siendo blancas
   y opacas —la legibilidad manda— y lo que las separa del mazo es la sombra,
   que se hace más profunda cuanto más al frente está la carta. */
.deck .review {
  background: var(--carbon);
  border: 1px solid var(--cream-12);
  /* HUMO VERDE, NO UN RESPLANDOR LIMPIO. Con la tarjeta y el fondo en el mismo
     negro hace falta algo que la despegue, y un halo simétrico se lee como un
     botón encendido. Son tres sombras descentradas, de radios distintos y sin
     desplazamiento común: al solaparse dejan el borde irregular, que es lo que
     hace que parezca humo y no un aura. La cuarta, en tinta, la asienta. */
  box-shadow:
    -22px -14px 70px rgba(var(--accent-rgb), .13),
     26px  10px 90px rgba(var(--accent-rgb), .10),
      0px  26px 60px rgba(var(--accent-rgb), .07),
      0    34px 70px rgba(var(--ink-rgb), .5);
  transition: none;                 /* aquí manda el scroll, no el hover */
}

/* Respira mientras espera al frente del mazo — sólo box-shadow, nunca
   transform ni filter: esos los pinta scroll.js a mano en cada frame vía
   pintar(), y una animation ganaría el cascade y pelearía con el scroll.
   Sólo la carta al frente lleva aria-hidden="false" (lo pone testimonios.js
   en pintar()), así que el pulso no compite con las que esperan detrás. */
@media (prefers-reduced-motion: no-preference) {
  .deck .review[aria-hidden="false"] {
    animation: reviewBreathe 4.2s ease-in-out infinite;
  }
}
@keyframes reviewBreathe {
  0%, 100% {
    box-shadow:
      -22px -14px 70px rgba(var(--accent-rgb), .13),
       26px  10px 90px rgba(var(--accent-rgb), .10),
        0px  26px 60px rgba(var(--accent-rgb), .07),
        0    34px 70px rgba(var(--ink-rgb), .5);
  }
  50% {
    box-shadow:
      -30px -18px 90px rgba(var(--accent-rgb), .22),
       34px  14px 110px rgba(var(--accent-rgb), .16),
        0px  32px 76px rgba(var(--accent-rgb), .12),
        0    34px 70px rgba(var(--ink-rgb), .5);
  }
}

.deck .review__quote { color: rgba(var(--accent-rgb), .5); }

/* Lista: la versión vertical y la de movimiento reducido. */
.rail--reviews.reviews--lista {
  display: flex;
  flex-direction: column;
  gap: clamp(16px, 2.6vw, 26px);
  overflow: visible;
  padding: var(--sp-corto) var(--gutter) 34px;
  scroll-snap-type: none;
}
.rail--reviews.reviews--lista .review { width: 100%; }

.review {
  margin: 0;
  flex: 0 0 auto;
  width: clamp(268px, 80vw, 420px);
  display: flex;
  flex-direction: column;
  gap: 22px;
  background: var(--carbon);
  border: 1px solid var(--cream-12);
  /* EL HUMO VERDE VA EN LA REGLA BASE, no sólo en la del mazo. En móvil los
     testimonios se leen en columna, no en baraja, y ahí las tarjetas se
     quedaban en negro sobre negro con sólo el filete de un píxel: justo el
     caso que este halo viene a resolver. */
  box-shadow:
    -18px -12px 56px rgba(var(--accent-rgb), .11),
     20px   8px 72px rgba(var(--accent-rgb), .08),
      0px  20px 48px rgba(var(--accent-rgb), .06),
      0    26px 56px rgba(var(--ink-rgb), .45);
  padding: clamp(28px, 2.6vw, 40px);
  transition: transform var(--dur-medium) var(--ease-out), box-shadow var(--dur-medium) var(--ease-out), border-color var(--dur-medium) var(--ease-out);
}
.review:hover {
  transform: translateY(-8px);
  border-color: rgba(var(--accent-rgb), .6);
  box-shadow: var(--shadow-review);
}

.review__quote {
  font-family: var(--titular);
  font-weight: 400;
  font-size: 82px;
  line-height: .55;
}
.review__text {
  margin: 0;
  flex: 1;
  font-family: var(--texto);
  /* UN SOLO TAMAÑO, EN TODOS LOS ESTADOS. Antes había tres reglas compitiendo
     —23px de base, 27px en el mazo y 20px en portátiles bajos— así que el
     mismo testimonio cambiaba de cuerpo según dónde y en qué pantalla se
     leyera. Ahora sale de un token y no se sobrescribe en ningún sitio: la
     opinión larga y la corta se leen exactamente igual de grandes. */
  font-size: var(--t-testimonio);
  line-height: 1.6;
  text-wrap: pretty;
}
/* Verde, no oro. La paleta final es negro, grafito, blanco y verde: el oro
   era el último color de fuera del sistema que quedaba en la página. */
.review__stars { letter-spacing: .18em; font-size: var(--t-caption); color: var(--accent); }
.review__author {
  padding-top: 18px;
  border-top: 1px solid var(--cream-15);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: .22em;
  text-transform: uppercase;
}

/* ---------- CTA final ---------- */

.cta {
  position: relative;
  /* PANTALLA COMPLETA. Antes se topaba en 860px, así que en un monitor alto la
     foto quedaba como una franja con fondo por arriba y por abajo. Ahora ocupa
     el viewport entero; `svh` es la altura sin las barras del navegador móvil,
     que es la que de verdad se ve. */
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  /* EL TEXTO BAJA, LA CARA QUEDA LIBRE. Centrado, el bloque caía justo sobre
     la figura y la partía. En el tercio inferior no la toca y además es la
     banda más oscura de la foto (brillo medio 37 frente a 64-73 arriba), así
     que el blanco contrasta mejor con menos velo. */
  place-items: end center;
  padding: var(--sp-ancho) var(--gutter) clamp(48px, 9vh, 110px);
  overflow: hidden;
  text-align: center;
}

.cta__scrim {
  position: absolute;
  inset: 0;
  background:
    /* LA FOTO ES LA PROTAGONISTA. El velo baja otra vez: el radial pasa de .62
       a .28 y se mueve al pie, que es donde ahora vive el texto; el resto de
       la escena queda prácticamente sin filtro para que el blanco y negro se
       lea con nitidez. */
    radial-gradient(78% 42% at 50% 88%, rgba(var(--ink-rgb),.58), rgba(var(--ink-rgb),0) 76%),
    linear-gradient(180deg, rgba(var(--ink-rgb),.24) 0%, rgba(var(--ink-rgb),0) 26%, rgba(var(--ink-rgb),0) 62%, rgba(var(--ink-rgb),.28) 100%);
}
.cta__inner { position: relative; max-width: 900px; }
.cta__title {
  margin: 0;
  font-family: var(--titular);
  font-weight: 400;
  /* AL MISMO CUERPO QUE "CUÉNTANOS QUÉ QUIERES INVOCAR": mismo token, no un
     clamp propio, para que no vuelvan a separarse. Se conserva el 0,85 de
     opacidad y la sombra, que son las que dejan intuir la foto por detrás. */
  font-size: var(--t-declaracion);
  opacity: .85;
  text-shadow: 0 2px 22px rgba(var(--ink-rgb), .9), 0 1px 4px rgba(var(--ink-rgb), .8);
  line-height: var(--lh-titular);
  letter-spacing: 0;
  color: var(--white);
}
.cta__title em { font-weight: 400; color: var(--accent); }
.cta__lede {
  margin: clamp(22px, 3vh, 36px) auto 0;
  max-width: 40ch;
  font-size: 16.5px;
  line-height: 1.75;
  font-weight: 400;
  color: var(--cream-82);
}
.cta__actions { margin-top: clamp(20px, 3vh, 34px); }
/* El hueco vertical se convierte en relleno de los enlaces: el correo y los dos
   teléfonos quedan en cajas de 44px sin mover el ritmo de la fila. Los puntos
   separadores se centran contra ellas. */
.cta__meta {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 2px 22px;
  margin-top: clamp(14px, 2vh, 24px);
  font-size: 11px;
  letter-spacing: .12em;
  color: var(--cream-60);
}
.cta__meta a { padding: 15px 4px; }
.cta__meta a:hover { color: var(--accent); }
.cta__sep { color: rgba(var(--accent-rgb), .6); }

/* ---------- formulario ---------- */

.contact {
  position: relative;
  background: var(--ink);
  /* UNA SOLA CAPA, NO DOS COLUMNAS. La escena va detrás de toda la sección y
     el formulario encima, en su panel. Antes eran dos columnas y el vídeo
     vivía apretado en la derecha. */
  display: grid;
  /* Las dos capas comparten celda: el vídeo detrás, el panel centrado encima. */
  grid-template-areas: 'escena';
  place-items: center;
  min-height: 100vh;
  min-height: 100svh;
  padding: var(--sp-base) var(--gutter);
}

/* Dos cosas distintas se arreglan aquí.
   1) `min-width: 0`: un hijo de rejilla arranca en `min-width: auto` y no puede
      encoger por debajo de su contenido, así que el formulario imponía el ancho
      de la columna en pantallas estrechas.
   2) `overflow: clip` en la sección: la foto entra desde `scale: 1.1` (ver
      scroll.js), y ese 5% sobrante por la derecha era el desbordamiento
      horizontal del documento entero, tapado hasta ahora por el
      `overflow-x: hidden` del body. Se recorta aquí, en su sitio. `clip` y no
      `hidden`, que crearía un contenedor de scroll. */
.contact { overflow: clip; }

.contact__body { min-width: 0; }

.contact__body {
  display: flex;
  flex-direction: column;
  gap: clamp(22px, 3vh, 34px);
  padding: var(--sp-base) var(--gutter);
}
.contact__title {
  margin: 0;
  font-family: var(--titular);
  font-weight: 400;
  font-size: var(--t-declaracion);
  line-height: var(--lh-titular);
  letter-spacing: 0;
  color: var(--white);
  max-width: 18ch;
}
.contact__title em { font-weight: 400; color: var(--accent); }
.contact__lede {
  margin: 0;
  max-width: 46ch;
  font-size: 16.5px;
  line-height: 1.75;
  font-weight: 400;
  color: var(--cream-80);
}

.form {
  display: flex;
  flex-direction: column;
  gap: 22px;
  margin-top: 8px;
  max-width: min(560px, 100%);
}
.form__field { display: flex; flex-direction: column; gap: 10px; }
/* `nowrap`: con `letter-spacing: .24em` una palabra como MENSAJE ocupa
   mucho más de lo que mide, y en la columna estrecha del formulario se
   partía en "MENSAJ / E". No hay ningún caso en que convenga partir un
   label de tres palabras como mucho. */
.form__label {
  font-family: var(--texto);
  font-size: 11.5px;
  letter-spacing: .24em;
  color: var(--accent);
  white-space: nowrap;
}

.form__input,
.form__textarea {
  width: 100%;
  background: transparent;
  color: var(--cream);
  font-family: var(--texto);
  font-size: 16.5px;
  font-weight: 400;
  outline: none;
}
.form__input {
  border: 0;
  border-bottom: 1px solid var(--cream-22);
  padding: 12px 2px;
  transition: border-color var(--dur-base) var(--ease-out);
}
.form__input:focus { border-bottom-color: var(--accent); }
.form__textarea {
  border: 1px solid var(--cream-22);
  border-radius: var(--radius-xs);
  padding: 16px;
  line-height: 1.7;
  resize: vertical;
  transition: border-color var(--dur-base) var(--ease-out);
}
.form__textarea:focus { border-color: var(--accent); }
.form__input::placeholder,
.form__textarea::placeholder { color: var(--cream-60); }

.form__honey { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }

/* El aviso de privacidad va ANTES del botón, no debajo: quien va a entregar su
   nombre y su correo tiene que leerlo mientras decide, no después de decidir. */
.form__legal {
  margin: 0;
  font-size: 13px;
  line-height: 1.6;
  color: var(--cream-62);
  max-width: 46ch;
}
.form__legal a {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.form__legal a:hover { color: var(--cream); }

.form__actions { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; margin-top: 6px; }

.form__submit {
  background: var(--accent);
  color: var(--ink);
  border: 0;
  font-family: var(--texto);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: .24em;
  padding: 19px 42px;
  min-height: 56px;
  border-radius: var(--radius-pill);
  /* Igual que los labels: el interletrado ensancha la palabra y el botón se
     encogía por debajo de su propio texto, partiéndolo en dos líneas.
     `nowrap` + `width: auto` lo dejan crecer hasta lo que necesita. */
  white-space: nowrap;
  width: auto;
  align-self: flex-start;
}
.form__submit:hover,
.form__submit:focus-visible {
  background: var(--cream);
  transform: translateY(-3px) scale(1.03);
  box-shadow: var(--shadow-accent-btn);
}
.form__submit[disabled] { opacity: .55; transform: none; box-shadow: none; }

.form__msg { font-family: var(--texto); font-size: var(--t-caption); }
.form__msg--ok { color: var(--accent); }
.form__msg--err { color: var(--danger); }

/* SIN MÁSCARA — no volver a ponerla. Una máscara animada aquí dejaba la foto
   en negro durante buena parte de la sección: el recorte tapaba la imagen
   mientras el formulario ya se leía entero al lado, y encima chocaba con el
   lienzo de la disolución al pasar el cursor. Un recuadro negro junto a un
   formulario visible no es un efecto, es una foto rota. La entrada de esta
   foto la hace scroll.js con escala y opacidad, que no puede fallar así. */
.contact__media {
  /* PEGADO A LA ALTURA DEL VIEWPORT, NO ESTIRADO A LA SECCIÓN. El formulario
     hace la sección más alta que la pantalla (1205px en un portátil), y un
     vídeo de 16:9 estirado a esa caja pierde el 40% por los lados: el portal
     verde se quedaba a un 2,9% del borde de sobrevivir. Con `sticky` la caja
     del vídeo mide siempre una pantalla —mucho más cerca de 16:9—, el recorte
     baja al 16% y la escena se ve entera mientras el panel pasa por encima. */
  grid-area: escena;
  position: sticky;
  top: 0;
  align-self: start;
  justify-self: stretch;
  width: 100%;
  height: 100vh;
  height: 100svh;
  /* SIN MÁSCARA. Aquí había dos máscaras multiplicadas —una elipse al 58% y un
     degradado vertical— pensadas para fundir una foto con el humo que se
     pintaba encima. El portal verde de esta escena cae en el borde derecho,
     justo en la zona que la elipse borraba: lo más potente de la imagen era lo
     primero que desaparecía. La escena va entera. */
  overflow: hidden;
}
.contact__media img,
.contact__media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* El portal y ella viven en la franja central-alta; anclarlo ahí evita que
     un viewport muy apaisado se coma la parte de arriba de la escena. */
  object-position: 50% 42%;
  display: block;
  background: var(--ink);
}

/* Velo general: sube el contraste del panel sin apagar la escena. */
.contact__scrim {
  grid-area: escena;
  position: sticky;
  top: 0;
  align-self: start;
  justify-self: stretch;
  width: 100%;
  height: 100vh;
  height: 100svh;
  z-index: 1;
  /* DEGRADADO LATERAL, NO UNA CAPA UNIFORME. Oscurece la banda izquierda, que
     es donde cae el texto, y se disuelve antes de llegar al portal: a partir
     del 72% del ancho no hay velo ninguno. El toque vertical es sólo para que
     el borde de arriba y el de abajo no corten en seco. */
  background:
    linear-gradient(90deg,
      rgba(var(--ink-rgb),.94) 0%,
      rgba(var(--ink-rgb),.88) 30%,
      rgba(var(--ink-rgb),.62) 48%,
      rgba(var(--ink-rgb),.24) 62%,
      rgba(var(--ink-rgb),0) 72%),
    linear-gradient(180deg, rgba(var(--ink-rgb),.5) 0%, rgba(var(--ink-rgb),0) 18%,
                            rgba(var(--ink-rgb),0) 84%, rgba(var(--ink-rgb),.5) 100%);
}

/* NADA DE PANEL OPACO. Antes el formulario iba dentro de una caja translúcida
   con desenfoque, y esa caja tapaba la escena entera: el cementerio quedaba
   reducido a dos franjas laterales. Ahora el texto se apoya directamente sobre
   el degradado del velo, ocupa sólo la mitad izquierda, y la derecha —donde
   está el portal— queda limpia. */
.contact__panel {
  grid-area: escena;
  position: relative;
  z-index: 2;
  justify-self: start;
  width: min(52%, 620px);
  padding: 0;
  background: none;
  border: 0;
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

.contact__body {
  padding: 0;
}

/* ---------- footer ---------- */

.footer {
  background: var(--ink);
  color: var(--cream);
  padding: var(--sp-corto) var(--gutter) 22px;
}

.footer__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: clamp(24px, 2.8vw, 44px);
  align-items: start;
}

.footer__brand { display: flex; flex-direction: column; gap: 18px; }
.footer__claim {
  margin: 0;
  font-family: var(--titular);
  font-weight: 400;
  font-size: clamp(30px, 3.6vw, 52px);
  line-height: var(--lh-titular);
  letter-spacing: 0;
  text-transform: uppercase;
  color: var(--white);
  max-width: 15ch;
  text-wrap: balance;
}
.footer__lede {
  margin: 0;
  max-width: 42ch;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 400;
  color: var(--cream-74);
}
.footer__lede strong {
  font-weight: 400;
  color: var(--cream);
}

.footer__cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: clamp(22px, 2.4vw, 36px); }

/* El pie de las páginas legales conserva el logotipo; sin esta regla sale a
   sus 520px naturales y deformado por el height del atributo. */
.footer__logotipo {
  width: clamp(150px, 17vw, 210px);
  height: auto;
  display: block;
}

@media (min-width: 900px) {
  .footer__grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1.45fr); }
}
/* El aire entre enlaces se pasa del `gap` al relleno de cada uno: mismo ritmo
   visual, pero la caja pulsable pasa de 21px a 45px. Once enlaces seguidos a
   21px en un pie que se lee con el pulgar era el peor sitio de la web para
   fallar el toque. */
.footer__col { display: flex; flex-direction: column; gap: 0; }
.footer__head { font-size: 11.5px; letter-spacing: .28em; color: var(--accent); margin-bottom: 8px; }
.footer__col a,
.footer__col span:not(.footer__head) { font-size: var(--t-caption); line-height: 1.6; color: var(--cream-85); padding: 12px 0; }
.footer__col a { overflow-wrap: break-word; }
.footer__col a:hover { color: var(--accent); }
.footer__col--legal a { color: var(--cream-62); }

.footer__base {
  max-width: var(--shell);
  margin: clamp(18px, 2.4vh, 28px) auto 0;
  padding-top: 14px;
  border-top: 1px solid var(--cream-12);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}
.footer__base p { margin: 0; font-size: var(--t-caption); letter-spacing: .22em; color: var(--cream-74); }

/* ---------- legal pages ---------- */

.legal {
  background: var(--grafito);
  padding: calc(var(--nav-h) + clamp(40px, 7vh, 80px)) var(--gutter) clamp(48px, 8vh, 88px);
}

.legal__inner { max-width: 74ch; margin: 0 auto; }

.legal__title {
  margin: 0;
  font-family: var(--titular);
  font-weight: 400;
  font-size: clamp(38px, 6vw, 84px);
  line-height: var(--lh-titular);
  letter-spacing: 0;
  text-wrap: balance;
}

.legal__updated {
  margin: 18px 0 0;
  font-size: 11px;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--cream-45);
}

/* El filete era tinta: sobre papel separaba, sobre grafito desaparecía. Va en
   crema al 30%, la misma intensidad que el filete del cierre de proyectos. */
.legal__rule { height: 2px; background: var(--cream-30); margin: clamp(28px, 4vh, 44px) 0 clamp(24px, 3.5vh, 38px); }

.legal__body h2 {
  margin: clamp(38px, 5vh, 58px) 0 16px;
  font-family: var(--titular);
  font-weight: 400;
  font-size: clamp(25px, 2.8vw, 38px);
  line-height: 1.16;
  letter-spacing: -.01em;
}

.legal__body h3 {
  margin: clamp(26px, 3.5vh, 36px) 0 12px;
  font-family: var(--titular);
  font-weight: 400;
  font-size: 15px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--accent-soft);
}

.legal__body > h2:first-child { margin-top: 0; }

.legal__body p,
.legal__body li {
  font-size: 16px;
  line-height: 1.8;
  font-weight: 400;
  color: var(--cream-80);
}

.legal__body p { margin: 0 0 18px; }
.legal__body ul { margin: 0 0 18px; padding-left: 1.1em; }
.legal__body li { margin-bottom: 10px; }
.legal__body li::marker { color: var(--accent-soft); }

.legal__body a {
  color: var(--accent-soft);
  text-decoration: underline;
  text-underline-offset: 3px;
  word-break: break-word;
}
.legal__body a:hover { color: var(--cream); }

.legal__data {
  margin: 0 0 24px;
  padding: clamp(20px, 2.6vw, 30px);
  background: var(--carbon);
  border: 1px solid var(--cream-12);
}
.legal__data dl { margin: 0; display: grid; grid-template-columns: minmax(120px, auto) 1fr; gap: 10px 22px; }
.legal__data dt { font-size: 11px; letter-spacing: .18em; text-transform: uppercase; color: var(--accent-soft); align-self: baseline; }
.legal__data dd { margin: 0; font-size: 15.5px; line-height: 1.6; font-weight: 400; }

@media (max-width: 560px) {
  .legal__data dl { grid-template-columns: 1fr; gap: 4px 0; }
  .legal__data dd { margin-bottom: 12px; }
}

.legal__back {
  display: inline-block;
  margin-top: clamp(36px, 5vh, 56px);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: .24em;
  border: 1px solid var(--cream-30);
  padding: 16px 32px;
  border-radius: var(--radius-pill);
}
.legal__back:hover,
.legal__back:focus-visible {
  background: var(--accent);
  border-color: var(--accent);
  transform: translateY(-3px);
  box-shadow: var(--shadow-accent-sm);
}

/* ---------- whatsapp ---------- */

.wa {
  position: fixed;
  right: clamp(16px, 2.4vw, 30px);
  bottom: clamp(16px, 2.4vw, 30px);
  z-index: 60;
  display: none;
  align-items: center;
  justify-content: center;
  width: 54px;
  height: 54px;
  border-radius: var(--radius-pill);
  background: var(--ink);
  box-shadow: 0 10px 30px var(--cream-30);
}
.wa.is-visible { display: flex; animation: waIn .4s ease both; }
.wa:hover, .wa:focus-visible { transform: translateY(-4px) scale(1.06); background: var(--accent); }
.wa:hover svg path, .wa:focus-visible svg path { fill: var(--cream); }

@keyframes waIn {
  from { opacity: 0; transform: translateY(14px) scale(.9); }
  to { opacity: 1; transform: none; }
}

/* ---------- responsive ---------- */

@media (max-width: 720px) {
  /* Aquí VIVÍA `.nav__link { display: none }`: en móvil no había menú, así que
     los enlaces de sección simplemente se escondían. Ahora hay hamburguesa y
     esa regla los dejaba invisibles DENTRO del panel abierto — se veía el
     aspa y el botón verde flotando sobre negro, sin los cinco enlaces.
     El `.nav__links` que tenía al lado tampoco existe ya: el contenedor pasó
     a llamarse `.nav__menu`. */
  /* La cinta ya no se sobrescribe aquí: manda `--t-cinta` (13px) en todos los
     tamaños de pantalla. Este 17px era un resto de cuando medía 22px. */
  /* El CTA es lo que más se toca con el dedo en la barra: caja de 44px. */
  .nav__cta { padding: 16px 18px; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}

/* ---------- página de proyectos ---------- */

.work {
  background: var(--grafito);
  padding: calc(var(--nav-h) + clamp(36px, 6vh, 72px)) var(--gutter) clamp(40px, 6vh, 72px);
}

.work__cover,
.work__closing {
  display: block;
  width: 100%;
  max-width: var(--shell);
  height: auto;
  margin: 0 auto;
  border: 1px solid var(--cream-12);
}
.work__cover { margin-bottom: clamp(30px, 5vh, 56px); }
.work__closing { margin-top: clamp(36px, 6vh, 64px); }

.work__head { max-width: var(--shell); margin: 0 auto clamp(30px, 5vh, 54px); }
.work__title {
  margin: 0;
  font-family: var(--titular);
  font-weight: 400;
  font-size: clamp(40px, 7vw, 104px);
  line-height: var(--lh-titular);
  letter-spacing: 0;
}
.work__title em { font-weight: 400; color: var(--accent-soft); }
.work__lede { margin-top: clamp(16px, 2.4vh, 26px); max-width: 56ch; }

.grid {
  list-style: none;
  margin: 0 auto;
  padding: 0;
  max-width: var(--shell);
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(18px, 2.4vw, 34px);
}

.proj { margin: 0; }

.proj__btn {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  text-align: left;
  font: inherit;
  color: inherit;
}

.proj__img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  background: var(--carbon);
  border: 1px solid var(--cream-12);
  transition: transform var(--dur-medium) var(--ease-out), box-shadow var(--dur-medium) var(--ease-out), border-color var(--dur-medium) var(--ease-out);
}
.proj__btn:hover .proj__img,
.proj__btn:focus-visible .proj__img {
  transform: translateY(-6px);
  border-color: rgba(var(--accent-rgb), .55);
  box-shadow: var(--shadow-review);
}

.proj__meta { display: flex; flex-direction: column; gap: 4px; padding-top: 14px; }
.proj__name {
  font-family: var(--titular);
  font-weight: 400;
  font-size: clamp(19px, 1.9vw, 28px);
  line-height: 1.15;
  letter-spacing: .01em;
}
.proj__sub { font-size: 12px; line-height: 1.5; font-weight: 400; color: var(--cream-60); }

.work__cta {
  max-width: var(--shell);
  margin: clamp(48px, 8vh, 96px) auto 0;
  padding-top: clamp(30px, 5vh, 52px);
  border-top: 2px solid var(--cream-30);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}
.work__cta-title {
  margin: 0;
  font-family: var(--titular);
  font-weight: 400;
  font-size: clamp(28px, 4vw, 56px);
  line-height: var(--lh-titular);
  letter-spacing: 0;
}

@media (max-width: 700px) {
  .grid { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- visor ---------- */

body.is-locked { overflow: hidden; }

.viewer {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: grid;
  place-items: center;
  padding: clamp(48px, 7vh, 80px) clamp(16px, 6vw, 90px);
  background: rgba(var(--ink-rgb), .95);
}
.viewer[hidden] { display: none; }

.viewer__stage { margin: 0; display: flex; flex-direction: column; gap: 16px; align-items: center; max-width: 100%; }

.viewer__img {
  display: block;
  max-width: 100%;
  max-height: 76vh;
  width: auto;
  height: auto;
  object-fit: contain;
  background: var(--ink);
}

.viewer__caption {
  font-size: 11px;
  font-weight: 400;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--cream-80);
  text-align: center;
}

.viewer__close,
.viewer__nav {
  position: absolute;
  display: grid;
  place-items: center;
  border: 1px solid var(--cream-22);
  border-radius: var(--radius-pill);
  background: rgba(var(--ink-rgb), .6);
  color: var(--cream);
  font-family: var(--texto);
  line-height: 1;
}
.viewer__close:hover,
.viewer__nav:hover,
.viewer__close:focus-visible,
.viewer__nav:focus-visible {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--ink);
}

.viewer__close {
  top: clamp(14px, 2.5vh, 26px);
  right: clamp(14px, 2.5vw, 30px);
  width: 46px;
  height: 46px;
  font-size: 26px;
}

.viewer__nav {
  top: 50%;
  transform: translateY(-50%);
  width: 50px;
  height: 50px;
  font-size: 30px;
}
.viewer__nav--prev { left: clamp(8px, 2vw, 26px); }
.viewer__nav--next { right: clamp(8px, 2vw, 26px); }
.viewer__nav:hover { transform: translateY(-50%) scale(1.08); }

@media (max-width: 700px) {
  .viewer__nav { top: auto; bottom: clamp(14px, 3vh, 26px); transform: none; }
  .viewer__nav:hover { transform: scale(1.08); }
  .viewer__img { max-height: 66vh; }
}

/* ---------- scroll scrubbing ---------- */

/* Estados de partida. Sólo se aplican si el JS ha arrancado y hay movimiento
   permitido — así, sin JS o con reduced-motion, nada se queda invisible. */
html.has-scroll [data-reveal] { opacity: 0; transform: translateY(34px); }
html.has-scroll [data-split-words] .word { opacity: 0; }
html.has-scroll [data-reveal-quiet] { opacity: 0; }

/* El revelado palabra a palabra.
   Las palabras nacen OPACAS: quien controla su entrada es su propio tween en
   los titulares, y multiplicar opacidades sólo servía para dejar texto
   ilegible sin que nada lo recuperase. */
.word { display: inline-block; }

/* Letras sueltas del hero: se dispersan al bajar */
.word-box { display: inline-block; }
.char {
  display: inline-block;
  transform-origin: 50% 100%;
}

/* WILL-CHANGE SOLO MIENTRAS EL TRAMO ESTÁ CERCA DE LA PANTALLA. Declararlo en
   .word y .char de forma permanente promocionaba una capa por cada palabra y
   cada letra: más de cien capas reservadas en reposo, memoria de GPU compitiendo
   con los seis lienzos. La clase la pone y la quita scroll.js con el propio
   ScrollTrigger del contenedor, así que la pista llega justo antes de que haga
   falta y se retira al salir. */
.is-live .word { will-change: opacity, transform; }
.is-live .char { will-change: opacity, transform, filter; }
/* El rotateX de las letras necesita profundidad en el contenedor. */
[data-split-chars] { perspective: 700px; display: inline-block; }

/* La cinta se desplaza también con la velocidad del scroll */
.marquee__track { will-change: transform; }

.hero__media img, .cta__img, .proceso__img { will-change: transform; }

@media (prefers-reduced-motion: reduce) {
  html.has-scroll [data-reveal],
  html.has-scroll [data-reveal-quiet] { opacity: 1; transform: none; }
  html.has-scroll .word { opacity: 1; }
  html.has-scroll .char { opacity: 1; }
  html.has-scroll .steps__line { --line-p: 1; }
  html.has-scroll .step__dot::after { transform: scale(1); }
}

/* ---------- pantalla de carga ---------- */

.loader {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: clamp(26px, 4vh, 42px);
  background: var(--ink);
  transition: opacity .75s var(--ease-out), visibility .75s var(--ease-out);
}
.loader.is-done { opacity: 0; visibility: hidden; }

body.is-loading { overflow: hidden; }

.loader__stage {
  position: relative;
  display: grid;
  place-items: center;
  width: clamp(190px, 46vw, 260px);
  aspect-ratio: 1;
}

/* Halo que respira detrás del logotipo */
.loader__halo {
  position: absolute;
  inset: 8%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(var(--accent-rgb), .30), rgba(var(--accent-rgb), 0) 68%);
  animation: haloPulse 2.6s var(--ease-out) infinite;
}

@keyframes haloPulse {
  0%, 100% { transform: scale(.86); opacity: .5; }
  50% { transform: scale(1.12); opacity: 1; }
}

/* El logotipo se revela de abajo arriba y se asienta */
.loader__logotipo {
  position: relative;
  display: block;
  width: clamp(190px, 40vw, 300px);
  height: auto;
  clip-path: inset(100% 0 0 0);
  transform: scale(.82);
  opacity: 0;
  animation: logoReveal 1.15s var(--ease-smooth) .12s forwards;
}

@keyframes logoReveal {
  0%   { clip-path: inset(100% 0 0 0); transform: scale(.82); opacity: 0; }
  55%  { opacity: 1; }
  100% { clip-path: inset(0 0 0 0); transform: scale(1); opacity: 1; }
}

/* Destellos: 14 chispas que suben girando. Las impares en verde. */
.loader__spark {
  position: absolute;
  width: 6px;
  height: 6px;
  background: var(--accent);
  clip-path: polygon(50% 0, 60% 40%, 100% 50%, 60% 60%, 50% 100%, 40% 60%, 0 50%, 40% 40%);
  opacity: 0;
  animation: sparkFloat 2.6s ease-in-out infinite;
}
.loader__spark:nth-child(odd) { background: var(--spark-green); width: 5px; height: 5px; }

.loader__spark:nth-child(1)  { left: 12%; top: 62%; animation-delay: .00s; }
.loader__spark:nth-child(2)  { left: 82%; top: 55%; animation-delay: .18s; }
.loader__spark:nth-child(3)  { left: 26%; top: 80%; animation-delay: .36s; }
.loader__spark:nth-child(4)  { left: 68%; top: 78%; animation-delay: .54s; }
.loader__spark:nth-child(5)  { left: 6%;  top: 40%; animation-delay: .72s; }
.loader__spark:nth-child(6)  { left: 92%; top: 34%; animation-delay: .90s; }
.loader__spark:nth-child(7)  { left: 44%; top: 90%; animation-delay: 1.08s; }
.loader__spark:nth-child(8)  { left: 58%; top: 12%; animation-delay: 1.26s; }
.loader__spark:nth-child(9)  { left: 18%; top: 20%; animation-delay: 1.44s; }
.loader__spark:nth-child(10) { left: 78%; top: 16%; animation-delay: 1.62s; }
.loader__spark:nth-child(11) { left: 34%; top: 48%; animation-delay: 1.80s; }
.loader__spark:nth-child(12) { left: 88%; top: 68%; animation-delay: 1.98s; }
.loader__spark:nth-child(13) { left: 4%;  top: 72%; animation-delay: 2.16s; }
.loader__spark:nth-child(14) { left: 50%; top: 4%;  animation-delay: 2.34s; }

@keyframes sparkFloat {
  0%   { opacity: 0; transform: translateY(14px) scale(.5) rotate(0deg); }
  22%  { opacity: 1; transform: translateY(0) scale(1) rotate(70deg); }
  70%  { opacity: .7; }
  100% { opacity: 0; transform: translateY(-30px) scale(.35) rotate(200deg); }
}

/* El nombre se compone abriendo el interletraje */
.loader__word {
  margin: 0;
  font-family: var(--titular);
  font-size: clamp(11px, 2.4vw, 13px);
  color: var(--cream-80);
  letter-spacing: .1em;
  opacity: 0;
  animation: wordSettle 1.1s var(--ease-out) .5s forwards;
}

@keyframes wordSettle {
  from { opacity: 0; letter-spacing: .1em; }
  to   { opacity: 1; letter-spacing: .52em; }
}

/* Con movimiento reducido: sin animaciones y fuera en cuanto se pueda. */
@media (prefers-reduced-motion: reduce) {
  .loader__logotipo { clip-path: none; transform: none; opacity: 1; animation: none; }
  .loader__word { opacity: 1; letter-spacing: .52em; animation: none; }
  .loader__spark, .loader__halo { animation: none; opacity: .5; }
}

/* ---------- humo detrás de las cartas ---------- */

/* Va en z-index 0: las cartas están en 2 (y en 9 al pasar por encima), así que
   nunca puede taparlas. El desenfoque le quita el borde de sprite. */
.cards-smoke {
  position: absolute;
  left: 0;
  bottom: 0;
  z-index: 0;
  pointer-events: none;
  opacity: .68;
  filter: blur(9px);
}

@media (max-width: 860px) {
  .cards-smoke { opacity: .48; filter: blur(7px); }
}


/* ---------- logotipo en pie y cargador ----------
   El isotipo de la K se retira: donde aparece la marca va el logotipo
   completo, aquí y en las páginas legales y de proyectos. En el cargador, la
   palabra suelta se queda en el DOM (el copy no se toca) pero no se pinta,
   porque el logotipo ya la dibuja y verla dos veces sería un error de
   composición, no de contenido. El tamaño y el revelado del cargador viven
   arriba, con el resto de la pantalla de carga. */
.loader__word { display: none; }


.proceso__cta {
  display: flex;
  justify-content: center;
  margin-top: clamp(30px, 4.4vh, 58px);
}


/* ---------- carrusel de marcas ----------
   Desfila solo en bucle continuo. El truco para que no se vea la costura es
   que la pista lleva el contenido DUPLICADO y se desplaza exactamente el 50%
   de su ancho: al terminar, la copia está justo donde estaba el original.

   Va por animación CSS y no por scroll: es un elemento de ambiente, no un
   dispositivo de lectura, y así no compite con los ScrollTrigger de la página
   ni añade trabajo al hilo principal en cada rueda. */

.marcas {
  overflow: hidden;
  margin: clamp(22px, 3vh, 40px) 0 clamp(26px, 3.4vh, 44px);
  /* MÁSCARA POR LOS CUATRO LADOS. Antes sólo se desvanecía por los costados y
     la tira quedaba cortada a hacha por arriba y por abajo contra el negro:
     dos líneas rectas de lado a lado justo donde la foto se encuentra con el
     fondo. Las dos máscaras se multiplican con `mask-composite`, así que la
     banda se disuelve en el negro por su contorno entero. El desvanecido
     vertical es corto (8%) para no comerse la escena de cada marca. */
  -webkit-mask-image:
    linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent),
    linear-gradient(180deg, transparent, #000 8%, #000 92%, transparent);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent),
    linear-gradient(180deg, transparent, #000 8%, #000 92%, transparent);
  mask-composite: intersect;
}

.marcas__pista {
  display: flex;
  width: max-content;
  /* El desplazamiento lo lleva marquee.js: clona los sets que hagan falta
     según el ancho de pantalla, y eso una animación CSS de porcentaje fijo no
     puede hacerlo. */
  will-change: transform;
}

/* SE PUEDE AGARRAR. La mano abierta anuncia que la cinta se mueve con el
   ratón; sin ella nadie descubriría que además de rular sola se puede
   empujar. */
.marcas[data-marquee-arrastrable] {
  cursor: grab;
  /* `pan-y` deja pasar el scroll vertical de la página al dedo, pero reserva
     el horizontal para la cinta. Sin esto, arrastrar de lado en un móvil
     movería la página en diagonal. */
  touch-action: pan-y;
}
.marcas[data-marquee-arrastrable].is-agarrado { cursor: grabbing; }
/* Arrastrando, el navegador seleccionaría el contenido de alrededor y dejaría
   media sección en azul. */
.marcas[data-marquee-arrastrable].is-agarrado,
.marcas[data-marquee-arrastrable].is-agarrado * {
  user-select: none;
  -webkit-user-select: none;
}
.marcas[data-marquee-arrastrable] .marca {
  -webkit-user-drag: none;
  user-select: none;
  -webkit-user-select: none;
}


/* A sangre: sin hueco entre piezas. La pieza ES la imagen; no lleva pie
   porque cada escena ya trae el nombre de la marca dentro. */
.marca {
  flex: 0 0 auto;
  display: block;
  width: clamp(210px, 21vw, 330px);
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  margin: 0;
}



@media (prefers-reduced-motion: reduce) {
  .marcas { overflow-x: auto; }
  /* La cinta de texto tampoco anima ni clona con movimiento reducido: con el
     overflow oculto la frase quedaba amputada sin manera de leer el resto.
     Desplazable a mano, como el carrusel de marcas. */
  .marquee { overflow-x: auto; }
}


/* ---------- vídeos de fondo ----------
   Sustituyen a las fotos estáticas manteniendo el mismo hueco: heredan la
   clase de la imagen que había, así que el layout no cambia. */

.hero__photo,
.split__photo { object-fit: cover; }

/* La sección del tarot, a pantalla completa: el vídeo es el fondo y la frase
   va encima con su propio velo para que se lea sobre cualquier fotograma. */
.futuro {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  place-items: center;
  overflow: clip;
}
.futuro__media {
  position: absolute;
  inset: 0;
  /* Se funde por arriba y por abajo con el fondo del body: sin corte seco. */
  -webkit-mask-image: linear-gradient(180deg, transparent, #000 12%, #000 88%, transparent);
  mask-image: linear-gradient(180deg, transparent, #000 12%, #000 88%, transparent);
}
.futuro__video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  background: var(--ink);
}
.futuro__scrim {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(74% 46% at 50% 62%, rgba(0,0,0,.72), rgba(0,0,0,0) 78%),
    linear-gradient(180deg, rgba(0,0,0,.45) 0%, rgba(0,0,0,.1) 34%, rgba(0,0,0,.6) 100%);
}
.futuro__frase {
  /* UNA SOLA LÍNEA, Y COLOCADA A MANO. No va centrada en la sección: va en el
     hueco que deja la escena entre la última carta y la taza de café, para no
     cruzarle la cara ni ningún objeto de la mesa. De ahí el posicionamiento
     absoluto al 71% de la altura en vez de `place-items: center`.
     `nowrap` impide el salto y el tamaño se limita en vw para que quepa en
     una línea también en pantallas estrechas: es la anchura, no la altura, la
     que manda aquí. */
  position: absolute;
  left: 50%;
  top: 71%;
  transform: translate(-50%, -50%);
  z-index: 2;
  margin: 0;
  padding: 0 var(--gutter);
  max-width: none;
  white-space: nowrap;
  text-align: center;
  font-family: var(--titular);
  /* MÁS PRESENCIA, SIN PERDER LA LÍNEA ÚNICA. Antes era
     `min(var(--t-escaparate), 5vw)` y mandaba siempre el token, no el vw, así
     que la frase lleva su propio clamp. El tope en px es lo que aguanta en una
     línea en pantalla ancha; el vw, lo que la mantiene dentro en móvil. */
  font-size: clamp(20px, 6.4vw, 78px);
  line-height: var(--lh-titular);
  letter-spacing: 0;
  color: var(--cream);
  text-shadow: 0 4px 34px rgba(0, 0, 0, .8);
}


/* ---------- lightbox del portfolio ----------
   Se abre al pulsar un proyecto del carril. El diálogo lo monta
   portfolio-lightbox.js leyendo el propio carril, así que esto sirve igual
   para 4 proyectos que para 40. */

.project--abrible { cursor: pointer; }
.project--abrible:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}

/* Mientras el modal está abierto la página de detrás no se mueve. */
html.lightbox-abierto,
html.lightbox-abierto body { overflow: hidden; }

/* Lo mismo con el menú móvil abierto: la clase la pone navbar.js. Es el
   respaldo cuando Lenis no gobierna el scroll (reduced-motion, GSAP sin
   cargar), que era justo cuando el panel dejaba la página moviéndose detrás. */
html.menu-abierto,
html.menu-abierto body { overflow: hidden; }

/* Flex y no `grid` + `place-items: center`: centrando con place-items el
   hijo se encoge a su contenido y la caja se quedaba en 567px de ancho en una
   pantalla de 1280 — un "lightbox" más pequeño que la propia miniatura. */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(14px, 3vw, 46px);
}
.lightbox[hidden] { display: none; }

.lightbox__fondo {
  position: absolute;
  inset: 0;
  background: rgba(var(--ink-rgb), .93);
  backdrop-filter: blur(6px);
}

.lightbox__caja {
  position: relative;
  display: flex;
  align-items: center;
  gap: clamp(6px, 1.6vw, 22px);
  width: min(1500px, 100%);
}

.lightbox__figura {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
}

.lightbox__img {
  display: block;
  width: 100%;
  height: auto;
  /* Cabe entera en pantalla contando el pie: sin este tope, en portátiles
     bajos la imagen se salía por abajo y el nombre quedaba fuera. */
  max-height: 76vh;
  object-fit: contain;
  border-radius: 4px;
  background: var(--carbon);
}

.lightbox__pie {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 18px;
  margin-top: 16px;
  font-family: var(--texto);
  letter-spacing: .12em;
  text-transform: uppercase;
}
.lightbox__nombre { color: var(--cream); font-size: var(--t-caption); }
.lightbox__cuenta { color: var(--cream-45); font-size: var(--t-micro); }

.lightbox__flecha,
.lightbox__x {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  padding: 0;
  border: 1px solid var(--cream-35);
  border-radius: 50%;
  background: rgba(var(--ink-rgb), .55);
  color: var(--cream);
  cursor: pointer;
  transition: border-color .18s ease, background-color .18s ease, color .18s ease;
}
.lightbox__flecha svg,
.lightbox__x svg { width: 22px; height: 22px; }

.lightbox__flecha:hover,
.lightbox__x:hover {
  border-color: var(--accent);
  background: rgba(var(--accent-rgb), .14);
  color: var(--accent);
}
.lightbox__flecha:focus-visible,
.lightbox__x:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.lightbox__x {
  position: absolute;
  top: -14px;
  right: -6px;
  z-index: 2;
  width: 42px;
  height: 42px;
}

/* Sólo para lectores de pantalla: anuncia el cambio de proyecto. */
.lightbox__voz {
  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;
}

@media (max-width: 700px) {
  /* En móvil las flechas se bajan bajo la imagen: a los lados robaban ancho
     justo donde menos sobra. */
  .lightbox__caja { flex-wrap: wrap; justify-content: center; }
  .lightbox__figura { flex: 1 0 100%; order: 1; }
  .lightbox__flecha { order: 2; }
  .lightbox__x { top: -8px; right: 0; }
  .lightbox__img { max-height: 62vh; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   REGLA TIPOGRÁFICA DE MARCA
   Va al final de la hoja a propósito: así gana sin tener que repetir
   selectores ni subir especificidad en cada sitio.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ---------- 1. UN SOLO PESO EN TODA LA PÁGINA ----------
   Tan Harmony sólo existe en 400: la fundición no entrega más cortes. Cada
   `font-weight: 600` o `700` obligaba al navegador a fabricar una negrita
   sintética engordando los trazos, y eso es lo que se veía sucio.
   Se incluye lo que el navegador pone por su cuenta —<strong>, <b>, <th>,
   <button>— porque ahí no hay ninguna declaración que corregir. */
body,
strong, b, th,
h1, h2, h3, h4, h5, h6,
button, input, select, textarea {
  font-weight: 400;
}

/* ---------- 2. MAYÚSCULAS: TODO LO QUE ES TAN HARMONY ----------
   Es la voz protagonista: titulares, claims y la cinta. Se listan también las
   piezas en que scroll.js parte el texto (.word, .char), porque el texto real
   acaba dentro de esos <span> y la regla del padre no bastaría si algún día
   se les diera un text-transform propio. */
h1, h2, h3, h4, h5, h6,
.h-display,
.footer__claim,
.step__title,
.conjuros__title, .manifest__title, .split__title,
.proceso__title, .portfolio__title, .reviews__title,
.cta__title, .contact__title, .work__title,
.futuro__frase,
.manifest__kicker,
.marquee__track, .marquee__track span,
.word, .char {
  text-transform: uppercase;
}

/* ---------- 3. MAYÚSCULAS: LO CORTO EN MONTSERRAT ----------
   Etiquetas, botones, navegación, pies y microtexto. Son rótulos, no lectura:
   en mayúsculas funcionan como señalización. */
.eyebrow,
.nav__link, .nav__cta,
.btn, .btn__sub,
.form__label, .form__submit,
.footer__head,
.card__tag, .card__mark,
.project__name, .project__sub,
.prueba__intro, .prueba__enlace,
.hero__over,
.step__num,
.review__author,
.legal__data dt,
.skip,
.lightbox__nombre, .lightbox__cuenta,
.conjuros__lista li,
.footer__col a[href^="https://www.instagram"],
.footer__col a[href^="https://www.linkedin"],
.footer__col a[href^="https://www.tiktok"],
.footer__col--legal a,
.footer__col span,
.footer__base {
  text-transform: uppercase;
}

/* ---------- 4. CAJA NATURAL: LO QUE SE LEE DE VERDAD ----------
   Párrafos largos en Montserrat. Aquí manda la legibilidad: un bloque de 350
   caracteres en mayúsculas pierde la silueta de las palabras y se lee mucho
   más despacio. También los datos que se escriben como se escriben —correo,
   teléfono, URL— y el texto legal, que debe leerse tal cual está redactado. */
p.lede, .card__copy, .review__text, .step__body, .cta__lede, .hero__shout,
.contact__lede, .footer__lede, .split__body p,
.form__msg, .form__legal,
a[href^="mailto:"], a[href^="tel:"],
a[href*="akelarreagenciacreativa.com"],
code, kbd, samp {
  text-transform: none;
}

/* Los campos del formulario, aparte: `text-transform` afecta a lo que el
   visitante ESCRIBE, no sólo al texto de ayuda. Con la regla puesta, su correo
   saldría en mayúsculas mientras lo teclea y parece un fallo de la página. El
   marcador de posición sí va en mayúsculas, que es lo que se pedía. */
.form__input, .form__textarea { text-transform: none; }
.form__input::placeholder,
.form__textarea::placeholder { text-transform: uppercase; }

/* ---------- 5. SIN CURSIVA ----------
   Ni Tan Harmony ni Montserrat tienen corte cursivo: en assets/fonts/ no hay
   un solo fichero itálico de ninguna de las dos. Cada `font-style: italic`
   obligaba al navegador a inclinar mecánicamente la letra recta — el mismo
   defecto que la negrita sintética, y en una display como Tan Harmony se nota
   todavía más porque le deforma los remates.

   El manual de marca no menciona la cursiva en ningún sitio, así que no forma
   parte del sistema. Se retiran las 15 declaraciones que había y se neutraliza
   la que el navegador pone por su cuenta en <em> e <i>: los cinco <em> del
   marcado siguen marcando énfasis semántico, que es para lo que están, pero lo
   expresan con el color de acento y no con la inclinación. */
em, i, cite, var, address, dfn,
.accent,
.marquee__track span, .review__text,
.manifest__title em, .manifest__kicker em, .split__title em,
.proceso__title em, .portfolio__title em, .reviews__title em,
.cta__title em, .contact__title em, .work__title em,
.futuro__frase em, .footer__claim em, .prueba__intro em,
.word, .char {
  font-style: normal;
}


/* ---------- la barra aparece al empezar a bajar ----------
   No está al cargar: la clase la pone navbar.js al pasar de 60px de scroll.
   Se anima opacidad y desplazamiento, no `display`, para que la transición
   pueda verse; `visibility` evita que sea alcanzable con el tabulador
   mientras está fuera. Las páginas interiores (proyectos y legales) no
   cargan navbar.js, así que llevan nav--visible puesto en su propio HTML. */
.nav {
  opacity: 0;
  transform: translateY(-100%);
  visibility: hidden;
  transition: opacity .45s ease, transform .45s cubic-bezier(.22,.61,.36,1),
              visibility 0s linear .45s,
              background-color .3s ease, border-color .3s ease;
}
.nav--visible {
  opacity: 1;
  transform: translateY(0);
  visibility: visible;
  transition: opacity .45s ease, transform .45s cubic-bezier(.22,.61,.36,1),
              visibility 0s, background-color .3s ease, border-color .3s ease;
}

/* Con reduced-motion aparece sin deslizarse. */
@media (prefers-reduced-motion: reduce) {
  .nav { transform: none; transition: opacity .2s linear, visibility 0s linear .2s; }
  .nav--visible { transform: none; }
}


/* ---------- fotos quietas ----------
   Las secciones de la poción y de Frankenstein no llevan vídeo, sino una foto
   fija. Estas reglas son el sitio donde se apoya. */

/* EL BORDE DIFUMINADO. La foto se recorta con una elipse que sólo desvanece el
   contorno exterior: el centro queda intacto y el filo se funde con el negro
   del fondo, así que no se ve el rectángulo. Va en `mask`, no en un degradado
   superpuesto, porque un velo encima aclararía también el humo y el polvo que
   se pintan sobre la foto. */
.split__media--fundida {
  -webkit-mask-image: radial-gradient(ellipse 92% 88% at 50% 50%, #000 58%, transparent 100%);
  mask-image: radial-gradient(ellipse 92% 88% at 50% 50%, #000 58%, transparent 100%);
}

@media (max-width: 860px) {
  /* En una sola columna la foto ocupa todo el ancho: se afloja la máscara para
     no comerse los laterales de la escena. */
  .split__media--fundida {
    -webkit-mask-image: radial-gradient(ellipse 112% 92% at 50% 50%, #000 62%, transparent 100%);
    mask-image: radial-gradient(ellipse 112% 92% at 50% 50%, #000 62%, transparent 100%);
  }
}


/* ---------- contacto en vertical ----------
   Media pantalla para el texto sólo tiene sentido en apaisado. En una columna
   el formulario necesita el ancho entero, así que el velo pasa a ser vertical:
   oscurece arriba, donde va el texto, y deja respirar la escena abajo. */
@media (max-width: 900px) {
  .contact__panel { width: 100%; justify-self: stretch; }
  .contact__scrim {
    background:
      linear-gradient(180deg,
        rgba(var(--ink-rgb),.92) 0%,
        rgba(var(--ink-rgb),.86) 45%,
        rgba(var(--ink-rgb),.6) 72%,
        rgba(var(--ink-rgb),.4) 100%);
  }
}

/* ---------- el CTA en vertical ----------
   UNA ESCENA ANCHA NO CABE EN UNA PANTALLA ALTA. Con la foto de fondo a
   pantalla completa, `cover` recortaba el 74% del ancho y sólo dejaba la franja
   central —la figura sobre la pared oscura, brillo medio 50 de 255—: las
   ventanas y el escritorio ardiendo, que es lo que da fuerza a la sección,
   quedaban fuera de plano. Aquí la foto deja de ser fondo y pasa a ser una
   banda con su proporción real, y el texto va debajo. Se ve la escena entera
   y el texto se lee sobre negro, sin velo que valga. */
@media (max-width: 700px) {
  .cta {
    min-height: 0;
    grid-template-areas: 'foto' 'texto';
    grid-template-rows: auto auto;
    place-items: stretch;
    padding: 0 0 var(--sp-base);
    gap: clamp(26px, 5vh, 44px);
  }
  .cta__img {
    grid-area: foto;
    position: relative;
    inset: auto;
    height: auto;
    aspect-ratio: 2200 / 1228;
    object-position: 50% 50%;
  }
  /* El velo sobraba: el texto ya no se apoya en la foto. */
  .cta__scrim { display: none; }
  .cta__inner {
    grid-area: texto;
    padding-inline: var(--gutter);
  }
  /* Sin foto detrás, el titular recupera opacidad y sombra normales. */
  .cta__title { opacity: 1; text-shadow: none; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   RITMO VERTICAL — un solo sistema para toda la landing
   ═══════════════════════════════════════════════════════════════════════════
   El problema no era que faltase espacio, sino que cada sección lo generaba a
   su manera: unas con `margin` (bloque) y otras con `gap` de flex. Y en las
   flex el gap SE SUMA al margen del antetítulo, así que la misma regla daba
   26px en el manifiesto y 52 en Frankenstein. Medido antes de tocar nada:

     antetítulo → titular : 20 · 26 · 48 · 52   (cuatro valores distintos)
     titular → párrafo    : 22 · 26             (dos)
     titular → bloque     :  0 · 26 · 30 · 34   (cuatro)

   Ahora hay tres relaciones y tres únicos valores. La regla es la cercanía: el
   antetítulo pertenece al titular y va pegado; el párrafo respira un escalón
   más; la acción, otro. Se controla TODO con márgenes y se anula el gap de los
   contenedores flex, para que bloque y flex se comporten igual.            */

:root {
  --sp-rotulo:  clamp(12px, 1.5vh, 20px);   /* antetítulo → titular */
  --sp-titular: clamp(18px, 2.4vh, 30px);   /* titular → párrafo o bloque */
  --sp-parrafo: clamp(22px, 2.8vh, 36px);   /* entre párrafos, y párrafo → acción */
}

/* Los dos contenedores flex dejan de aportar espacio propio. */
.split__body,
.contact__body { gap: 0; }

.eyebrow { margin: 0 0 var(--sp-rotulo); }

.manifest__title, .split__title, .conjuros__title, .proceso__title,
.portfolio__title, .reviews__title, .contact__title {
  margin-top: 0;
  margin-bottom: var(--sp-titular);
}

.manifest__lede, .reviews__lede, .contact__lede,
.split__body p.lede {
  margin-top: 0;
  margin-bottom: var(--sp-parrafo);
}

/* El último párrafo no arrastra hueco: lo pone la acción que venga detrás. */
.split__body p.lede:last-of-type,
.manifest__lede:last-of-type { margin-bottom: 0; }

/* La acción cierra el bloque con el escalón mayor. */
.split__body > div:has(.btn),
.manifest__inner .btn,
.reviews__cta, .proceso__cta { margin-top: var(--sp-parrafo); }


/* ═══════════════════════════════════════════════════════════════════════════
   CONDICIONES GENERALES DE CONTRATACIÓN
   ═══════════════════════════════════════════════════════════════════════════
   Es la única legal con índice y con cuatro niveles de título, así que añade
   dos piezas que las demás no necesitaban. Todo lo demás —h2 en Tan Harmony
   mayúsculas, h3 en verde, cuerpo en Montserrat— ya venía de `.legal__body`. */

/* Las listas numeradas se comportaban distinto que las de puntos: el documento
   usa ambas y tenían que verse iguales. */
.legal__body ol { margin: 0 0 18px; padding-left: 1.3em; }
.legal__body ol > li::marker { color: var(--accent-soft); }
.legal__body li > ul,
.legal__body li > ol { margin: 10px 0 0; }
.legal__body li > p { margin: 10px 0 0; }

/* Cuarto nivel: "QUÉ INCLUYE" / "QUÉ NO INCLUYE" y los 5.1.1, 5.2.1… No son
   titulares, son etiquetas que abren una lista, así que van en Montserrat
   —el corto en mayúsculas— y no en Tan Harmony, para no competir con el h3
   verde que tienen encima. */
.legal__body h4 {
  margin: clamp(20px, 2.6vh, 28px) 0 10px;
  font-family: var(--texto);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--cream-62);
}

/* El índice: mismo fondo y borde que `.legal__data`, para que se lea como una
   pieza del mismo sistema y no como un añadido. */
.legal__indice {
  margin: 0 0 clamp(34px, 5vh, 54px);
  padding: clamp(20px, 2.6vw, 30px);
  background: var(--carbon);
  border: 1px solid var(--cream-12);
}
.legal__indice-head {
  margin: 0 0 16px;
  font-family: var(--texto);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--accent-soft);
}
.legal__indice-lista,
.legal__indice-lista ul {
  margin: 0;
  padding: 0;
  list-style: none;
}
.legal__indice-lista > li { margin-bottom: 9px; }
.legal__indice-lista ul { margin: 8px 0 0 1.2em; }
.legal__indice-lista ul li { margin-bottom: 6px; }
.legal__indice-lista a {
  font-family: var(--texto);
  font-size: 14px;
  line-height: 1.5;
  color: var(--cream-74);
  text-decoration: none;
  border-bottom: 1px solid transparent;
}
.legal__indice-lista ul a { font-size: 13px; color: var(--cream-60); }
.legal__indice-lista a:hover,
.legal__indice-lista a:focus-visible {
  color: var(--accent);
  border-bottom-color: rgba(var(--accent-rgb), .4);
}

/* Al saltar desde el índice, el titular no puede quedar debajo de la barra. */
.legal__body h2[id],
.legal__body h3[id] { scroll-margin-top: calc(var(--nav-h) + 20px); }


/* ═══════════════════════════════════════════════════════════════════════════
   MÓVIL: PROPORCIÓN ENTRE CARTAS, TITULARES Y CINTA
   ═══════════════════════════════════════════════════════════════════════════
   En el móvil la escala se había invertido: la carta medía 715px de alto —el
   88% de la pantalla— mientras el titular de su sección se quedaba en 32px.
   Al bajar, cada carta llenaba el viewport y los titulares parecían pies de
   foto. Se corrige por los dos lados: las cartas bajan, los titulares suben. */

@media (max-width: 860px) {
  /* LAS CARTAS. No se recorta la ilustración —es una carta de tarot, su
     proporción 2:3 es parte del dibujo—, se estrecha el hueco y se centra. */
  .card-slot { margin-inline: auto; }
  /* El reverso iba a 17px, más grande que el cuerpo de la página (16,5). Se
     alinea con él y el bloque de texto pierde una línea. */
  .card__copy { font-size: 15.5px; }
  .card__back { padding: 20px; }
}

/* Atada sólo al ancho, la carta ocupaba casi toda la pantalla en los móviles
   bajos (un iPhone SE o un 8) y llegaba al borde inferior. Acotarla también
   por la altura la encoge ahí y siempre deja aire por debajo.
   SÓLO EN VERTICAL: en apaisado la altura es el lado corto y el mismo tope
   dejaba la carta en una tira, con las píldoras fuera de su caja.
   La primera declaración es el respaldo para los navegadores sin `svh`. */
@media (max-width: 860px) and (orientation: portrait) {
  .card-slot {
    max-width: min(460px, 68vw, 30vh);
    max-width: min(460px, 68vw, 30svh);
  }
}

@media (max-width: 700px) {
  /* LOS TITULARES. Los tres tokens de sección sólo suben su suelo, que es el
     valor que manda en móvil; el techo de escritorio no se toca, así que las
     pantallas grandes quedan exactamente igual. */
  :root {
    --t-declaracion: clamp(41px, 3.68vw, 71px);   /* manifiesto, contacto, CTA */
    --t-escaparate:  clamp(38px, 3.48vw, 67px);   /* conjuros, portfolio       */
    --t-relato:      clamp(35px, 3.03vw, 58px);   /* proceso                   */
  }

  /* LA CINTA. A 13px con .22em de interletrado, la frase medía 1177px: en una
     pantalla de 375 se leía un tercio y pasaba de largo antes de completarse.
     Baja el cuerpo y se recoge el tracking, que es lo que más ancho comía. */
  .marquee__track span {
    font-size: 11px;
    letter-spacing: .14em;
  }

  /* VIUDAS. Al crecer el cuerpo, dos titulares dejaban una palabra suelta en
     su propia línea: "Transformamos lo / corriente" y "MARCAS REALES QUE /
     DEJARON". Los saltos por sentido que marca el <br> se respetan; lo que se
     impide es que el navegador parta por dentro de estos dos sintagmas. */
  .manifest__title .muted,
  .portfolio__title .junto { white-space: nowrap; }

  /* La frase del tarot va en una sola línea y no puede plegarse, así que el
     cuerpo lo limita el ancho disponible. Con el gutter estándar se quedaba a
     1px del borde; con un margen propio y menor, cabe con holgura. */
  .futuro__frase { padding-inline: 10px; }
}
