/* ============================================================
   Valentina Dussán — Portafolio
   Hoja de estilos única. Sin dependencias ni compilación.
   ============================================================ */

/* ── Tokens ────────────────────────────────────────────────── */
:root {
  --ink:    #0B0B0C;  /* fondo oscuro */
  --carbon: #16161A;  /* placeholders de media */
  --bone:   #EDE7E1;  /* texto claro */
  /* El vino tiene dos vidas. Como MASA (rellenos, bordes, barras) es el
     #7A1C28 de la marca y no se toca: el hueso encima da 8,46:1.
     Como TEXTO no llega al mínimo AA sobre ninguno de los dos fondos, y
     no existe un solo valor que pase en ambos: al aclararlo mejora sobre
     oscuro y empeora sobre arena. Así que --wine-text se redefine según
     el fondo. Los rojos oscuros además se enlodan sobre negro; levantarlos
     ahí es la decisión óptica correcta, no un parche. */
  --wine:      #7A1C28;  /* masa — no cambiar */
  --wine-text: #D04E5B;  /* vino como texto sobre oscuro — 4,61:1 */
  --sand:   #B9A296;  /* secciones claras */
  /* Contraste medido, no estimado. Los valores viejos (#7C7772 y #5E5651)
     daban 4.44:1 y 2.96:1 en texto de 9–10 px, por debajo del mínimo AA
     de 4.5:1. Estos pasan con margen. */
  --smoke:  #8B8680;  /* texto secundario sobre oscuro — 5.45:1 */
  --ash:    #6D6863;  /* etiquetas de placeholder (andamio temporal) */
  --stone:  #3D3733;  /* texto secundario sobre arena — 4.84:1 */
  --coal:   #2E2A28;  /* texto de apoyo sobre arena */

  --hairline-light: rgba(237, 231, 225, 0.14);
  --hairline-dark:  rgba(11, 11, 12, 0.18);

  --serif: 'DM Serif Display', Georgia, serif;
  --sans:  'DM Sans', system-ui, -apple-system, sans-serif;
  --mono:  'Space Mono', ui-monospace, monospace;

  /* Una curva por función. Entrar y salir nunca duran lo mismo:
     salir siempre es más rápido, nadie espera a que algo desaparezca. */
  --ease-in:    cubic-bezier(0.16, 1, 0.3, 1);  /* algo entra: desacelera fuerte */
  --ease-out:   cubic-bezier(0.4, 0, 1, 1);     /* algo sale: acelera y se va */
  --ease-hover: cubic-bezier(0.33, 0, 0.67, 1); /* respuesta al cursor */
  --ease-data:  cubic-bezier(0.22, 1, 0.36, 1); /* barras y datos que crecen */

  /* Interletrado de las etiquetas mono. A 0.25em el ojo lee letra por letra
     en vez de palabra; 0.14em mantiene el aire sin romper la lectura. */
  --track: 0.14em;

  --gutter:  clamp(1.5rem, 6vw, 7rem);
  --shell:   1600px;
  --nav-h:   88px;

  /* Velo del hero: 0.25 – 0.85 */
  --hero-veil: 0.55;
  /* Ancho del panel de foto del hero, sólo en el corte de escritorio. */
  --hero-panel: 44%;
  /* Ancho mínimo de tarjeta de proyecto: 220px compacto / 300px amplio */
  --card-min: 220px;
}

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

html {
  scroll-behavior: smooth;
  scroll-padding-top: var(--nav-h);
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--ink);
  color: var(--bone);
  overflow-x: hidden;
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.5;
}

img, svg, video { display: block; max-width: 100%; }
[hidden] { display: none !important; }
dialog:not([open]) { display: none; }
a { color: inherit; text-decoration: none; }
a:hover { color: var(--wine-text); }
p { margin: 0; }

input, select, textarea, button { font-family: inherit; font-size: inherit; color: inherit; }
select option { background: var(--ink); color: var(--bone); }

::selection { background: var(--wine); color: var(--bone); }

/* El anillo de foco en vino daba 1,9:1 sobre el fondo oscuro, cuando el
   mínimo para un indicador de foco es 3:1: era invisible en casi toda la
   página. Se resuelve igual que --wine-text, por fondo: hueso sobre
   oscuro (16:1) y tinta sobre arena (8,1:1). El acento de marca se
   sacrifica aquí a propósito — un foco que no se ve no es un foco. */
:focus-visible {
  outline: 2px solid var(--focus-ring, var(--bone));
  outline-offset: 3px;
}
.section--sand { --focus-ring: var(--ink); }

.skip {
  position: absolute; left: 12px; top: -100px; z-index: 200;
  background: var(--wine); color: var(--bone);
  font-family: var(--mono); font-size: 11px; letter-spacing: var(--track); text-transform: uppercase;
  padding: 14px 20px; transition: top .2s ease;
}
.skip:focus { top: 12px; color: var(--bone); }

/* ── Utilidades tipográficas ───────────────────────────────── */
.shell { max-width: var(--shell); margin-inline: auto; }

.display {
  margin: 0;
  font-family: var(--serif); font-weight: 400;
  text-transform: uppercase; letter-spacing: -0.01em; line-height: 0.98;
  font-size: clamp(2.2rem, 8vw, 5.5rem);
}

.eyebrow {
  font-family: var(--mono); font-size: 10px;
  letter-spacing: var(--track); text-transform: uppercase;
  color: var(--wine-text);
}

.lede {
  font-size: clamp(15px, 1.15vw, 17px);
  line-height: 1.7; max-width: 65ch; text-wrap: pretty;
}
.lede--light { color: var(--sand); font-size: clamp(16px, 1.2vw, 18px); max-width: 62ch; }

.note {
  font-size: clamp(15px, 1vw, 16px);
  line-height: 1.7; max-width: 58ch; color: var(--sand); text-wrap: pretty;
}

.rule { display: block; height: 1px; background: rgba(237, 231, 225, 0.28); }
.is-muted { color: var(--smoke); }

.section { padding: clamp(6rem, 14vh, 11rem) var(--gutter); }
.section--sand { background: var(--sand); color: var(--ink); }
.section--sand .display { color: var(--ink); }
.section--sand .lede { color: var(--ink); }
/* Sobre arena el vino se oscurece en vez de aclararse: 4,77:1, y a ojo
   es casi indistinguible del #7A1C28 de marca. */
.section--sand { --wine-text: #6E1822; }

/* Quiebre de ritmo. Cuando toda sección mide lo mismo, la página suena a
   metrónomo y se lee como plantilla. Estas dos rompen el compás: la de
   proyectos respira más porque es el argumento central, y la de proceso
   se comprime porque es información de servicio. */
/* Arriba y abajo YA NO son iguales, y el motivo es distinto en cada borde.
   Abajo se conserva el aire grande: separa Proyectos de la Galería, que son
   dos bloques de imágenes seguidos, y ahí el respiro trabaja.
   Arriba vuelve al estándar de `.section` (14vh). Medído a 1280×800, el hueco
   Servicios→Proyectos eran 200 px —dentro del rango normal de la página— pero
   se leía enorme porque Servicios cierra con una línea dura a todo el ancho y
   Proyectos abre con una eyebrow minúscula: esos píxeles quedaban como una
   franja vacía enmarcada. Kevin lo pidió el 27 de agosto de 2026. Queda en
   152 px, que es exactamente la transición más corta que ya tenía la página
   (hero→métricas), así que no rompe el ritmo: se alinea con él. */
.work    { padding-block: clamp(6rem, 14vh, 11rem) clamp(8rem, 20vh, 15rem); }
.process { padding-block: clamp(3.5rem, 8vh, 6rem); }

/* ── Botones ───────────────────────────────────────────────── */
.btn {
  display: inline-block; border: 0; cursor: pointer;
  font-family: var(--mono); font-size: 11px;
  letter-spacing: var(--track); text-transform: uppercase;
  transition: background .16s var(--ease-hover), color .16s var(--ease-hover);
}
.btn--solid { background: var(--wine); color: var(--bone); padding: 19px 34px; }
.btn--solid:hover { background: var(--bone); color: var(--ink); }
.btn--ghost {
  background: none; color: var(--bone);
  border: 1px solid var(--wine); font-size: 10px; padding: 18px 28px;
}
.btn--ghost:hover { background: var(--wine); color: var(--bone); }

.icon-btn {
  flex: none; width: 48px; height: 48px;
  background: none; border: 1px solid rgba(237, 231, 225, 0.3);
  cursor: pointer; color: var(--bone);
  font-family: var(--mono); font-size: 12px;
  transition: border-color .16s var(--ease-hover), color .16s var(--ease-hover);
}
.icon-btn:hover { border-color: var(--wine); color: var(--wine-text); }

/* ── Marcos de media con placeholder ───────────────────────── */
/* El rótulo del archivo queda visible mientras la imagen o el
   vídeo no exista todavía en /assets. Al cargar, se desvanece. */
.media { position: relative; background: var(--carbon); overflow: hidden; }

/* ── Las imagenes se ven SIEMPRE, sin depender del JavaScript ──
   Hasta el 31 de agosto de 2026 era al reves: nacian en `opacity: 0` y solo
   un `is-loaded` puesto por JS las descubria. Ese diseño venia de cuando los
   archivos aun no existian y el marco tenia que enseñar su nombre.

   Ya no existe ese caso -estan todos puestos- y en cambio costo tres fallos
   reales que Kevin reporto sobre la web publicada:
     · el hero "no cargaba" en PC,
     · la foto del hero desaparecia al volver de otra app en el celular,
     · y las portadas se quedaban en el rotulo gris.
   Los tres son el mismo: la imagen estaba cargada y aun asi invisible,
   porque la clase no llego, llego tarde, o se perdio al restaurar la pagina.

   Ahora la imagen es visible por defecto y el rotulo solo aparece si el
   archivo FALLA de verdad. Si el JavaScript no corre, se ven igual. */
.media > img, .media > video, .media > picture > img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
}

/* El rotulo con el nombre del archivo: oculto salvo que el archivo falle. */
.media::after {
  content: attr(data-label);
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  padding: 0 12px; text-align: center; white-space: pre-line;
  font-family: var(--mono); font-size: 9px;
  letter-spacing: 0.2em; text-transform: uppercase; line-height: 1.8;
  color: var(--ash); pointer-events: none;
  opacity: 0; transition: opacity .4s ease;
}
.media.has-error::after { opacity: 1; }

/* El <video> del hero es el unico que sigue naciendo oculto: es un extra que
   hoy no existe, y un mp4 ausente no puede tapar la foto. Se descubre solo
   cuando tiene fotogramas de verdad. */
.hero__bg > video { opacity: 0; }
.hero__bg.has-video > video { opacity: 1; }

.media--3x4  { aspect-ratio: 3 / 4; }
.media--9x16 { aspect-ratio: 9 / 16; }

/* ── Revelado al hacer scroll ──────────────────────────────────
   Gesto firma del sitio: la sección no aparece con un fundido, se
   DESTAPA de arriba hacia abajo, como un vertical que se llena. Es el
   mismo formato en el que vive el trabajo de Valentina: 9:16, en la mano.

   Una sola propiedad y sólo `transform`, así que corre en el compositor.
   `transform-origin: bottom` + `scaleY(0)` hace que la tapa se recoja
   dentro de su propia caja: no hace falta `overflow: hidden` ni se
   derrama sobre la sección anterior. */
.js [data-reveal] { position: relative; }
.js [data-reveal]::before {
  content: "";
  position: absolute; inset: 0; z-index: 3;
  background: var(--reveal-cover, var(--ink));
  transform: scaleY(1); transform-origin: bottom;
  transition: transform .9s var(--ease-in);
  pointer-events: none;
}
.js [data-reveal].is-revealed::before { transform: scaleY(0); }
.section--sand { --reveal-cover: var(--sand); }

/* ── Navegación ────────────────────────────────────────────── */
.nav, .navbar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 60;
  align-items: center; justify-content: space-between;
  background: rgba(11, 11, 12, 0.82);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid rgba(237, 231, 225, 0.12);
  transform: translateY(-100%); opacity: 0; pointer-events: none;
  /* Se retira rápido y acelerando; entra despacio y frenando. */
  transition: transform .25s var(--ease-out), opacity .25s var(--ease-out);
}
.nav.is-visible, .navbar.is-visible {
  transform: none; opacity: 1; pointer-events: auto;
  transition: transform .55s var(--ease-in), opacity .55s var(--ease-in);
}

.nav { display: flex; gap: 24px; padding: 18px clamp(20px, 4vw, 64px); }
.navbar { display: none; padding: 14px 20px; }

.nav__mark {
  font-family: var(--serif); font-size: 15px;
  letter-spacing: 0.18em; text-transform: uppercase;
  /* Medía 21 px de alto: por debajo del objetivo táctil de 44. */
  display: inline-flex; align-items: center; min-height: 44px;
}
.nav__links { display: flex; align-items: center; gap: clamp(14px, 2.2vw, 34px); }
.nav__links a {
  font-family: var(--mono); font-size: 10px;
  letter-spacing: var(--track); text-transform: uppercase;
}
/* Los enlaces de la navegacion no tenian NINGUN estado: ni respuesta al
   cursor ni forma de saber en que seccion estas. Eso es buena parte de por
   que la pagina se siente un documento y no un sitio. La regla se dibuja de
   izquierda a derecha -el gesto del hero- y separa las dos cosas por color.

   El vino MASA (#7A1C28) da 1,84:1 sobre el fondo y aqui no vale: esta barra
   no es adorno, dice en que seccion estas, y un indicador de estado tiene
   que llegar a 3:1. Por eso la actual va en --wine-text, que da 4,61:1. */
.nav__links a:not(.nav__cta) { position: relative; padding-block: 4px; }
.nav__links a:not(.nav__cta)::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: var(--smoke); transform: scaleX(0); transform-origin: left;
  transition: transform .18s var(--ease-hover);
}
.nav__links a:not(.nav__cta):hover::after,
.nav__links a:not(.nav__cta):focus-visible::after { transform: scaleX(1); }
.nav__links a.is-current::after {
  background: var(--wine-text); transform: scaleX(1);
  transition: transform .32s var(--ease-in);
}

.nav__cta { border: 1px solid var(--wine); padding: 11px 20px; }
.nav__cta:hover { background: var(--wine); color: var(--bone); }

.navbar__burger {
  display: flex; flex-direction: column; align-items: flex-end; justify-content: center;
  gap: 6px; width: 48px; height: 44px;
  background: none; border: 0; cursor: pointer;
}
.navbar__burger i { display: block; width: 26px; height: 1px; background: var(--bone); }
.navbar__burger i:last-child { width: 18px; }

/* Menú móvil */
.menu {
  width: 100%; max-width: 100%; height: 100%; max-height: 100%;
  margin: 0; padding: 24px clamp(20px, 6vw, 64px);
  border: 0; background: var(--ink); color: var(--bone);
}
.menu::backdrop { background: var(--ink); }
.menu__close {
  position: absolute; top: 18px; right: 20px;
  width: 48px; height: 48px;
  background: none; border: 0; cursor: pointer;
  font-family: var(--mono); font-size: 14px; letter-spacing: var(--track); color: var(--smoke);
}
.menu__links {
  display: flex; flex-direction: column; justify-content: center;
  gap: 8px; min-height: 100%;
}
.menu__links a {
  font-family: var(--serif); text-transform: uppercase;
  letter-spacing: -0.01em; font-size: clamp(2.2rem, 10vw, 3.6rem); line-height: 1.05;
}
.menu__cta { color: var(--wine-text); }

/* ── 01 · Hero ─────────────────────────────────────────────── */
.hero {
  position: relative; min-height: 100svh;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: clamp(2rem, 7vw, 5rem); overflow: hidden;
}
.hero__bg { position: absolute; inset: 0; }
.hero__bg::after {
  align-items: flex-start; justify-content: flex-end; text-align: right;
  padding: clamp(76px, 9vw, 110px) clamp(20px, 4vw, 48px) 0;
}
.hero__veil {
  position: absolute; inset: 0;
  background: var(--ink); opacity: var(--hero-veil);
}
/* ── Hero partido ───────────────────────────────────────────────
   40 de las 41 fotos del material son VERTICALES. Un fondo apaisado a
   sangre obligaría a recortar una vertical a 16:9 y tirar más de la mitad
   de la composición — el mismo destrozo que ya se evitó en la galería.

   Así que la foto no es fondo: es un panel de altura completa en el flanco
   derecho, con proporción vertical, que es el formato en el que vive su
   trabajo. El nombre respira sobre tinta plana a la izquierda. El día que
   haya vídeo de hero, ese mismo panel admite un 9:16 sin recortarlo.

   Sólo desde 1000 px: por debajo no hay dos columnas que repartir, y la
   foto vuelve a sangre completa con su velo, exactamente como antes. */
@media (min-width: 1000px) {
  /* El contenido se queda en el flanco izquierdo sin tocar .hero__inner:
     basta con reservarle el hueco al panel en el relleno de la sección. */
  .hero { padding-right: calc(var(--hero-panel) + clamp(2rem, 7vw, 5rem)); }
  .hero__bg { inset: 0 0 0 auto; width: var(--hero-panel); }
  /* El velo baja de 0,55 a 0,24: ya no tiene que rescatar el contraste de
     todo el texto —que ahora vive sobre tinta plana—, sólo asentar la foto
     contra ella. La foto se ve casi a calidad plena. */
  .hero__veil { inset: 0 0 0 auto; width: var(--hero-panel); opacity: 0.24; }
}

.hero__inner {
  position: relative; z-index: 2;
  display: flex; flex-direction: column; gap: clamp(24px, 4vw, 44px);
  max-width: var(--shell); width: 100%; margin-inline: auto;
}
.hero__title {
  margin: 0; font-family: var(--serif); font-weight: 400;
  text-transform: uppercase; letter-spacing: -0.01em; line-height: 0.98;
  font-size: clamp(3rem, 13vw, 9rem);
}
/* En el corte de escritorio la columna de texto se estrecha, asi que el
   titular tiene que bajar con ella. Va DESPUES de la regla de arriba a
   proposito: misma especificidad, y en ese empate manda el orden de la hoja.
   Medido en el navegador: a 144 px los glifos de «VALENTINA» ocupan 718 px
   y la columna deja 638, o sea que el nombre invadia el panel de la foto. */
@media (min-width: 1000px) {
  .hero__title { font-size: clamp(2.6rem, 7.8vw, 7rem); }
}
/* Las dos líneas van en hueso. El vino a 1,9:1 sobre negro no sólo
   incumplía AA: se enlodaba y hundía la primera línea del nombre.

   «Dussán» llevaba un `margin-left: 0.42em` para que el nombre no fuera un
   bloque alineado y la jerarquía la diera la composición. Kevin lo pidió
   a bandera izquierda el 27 de agosto de 2026, ya con el hero partido: con
   la foto ocupando el flanco derecho, el borde recto de la izquierda es el
   que sostiene la composición, y el desplazamiento lo desordenaba. */
.hero__title-a, .hero__title-b {
  display: block; color: var(--bone);
  /* La ventana que recorta. El relleno superior da aire al acento de la Á:
     con `line-height: .98` la caja va tan justa que el recorte se lo comia,
     y el margen negativo devuelve el titular a su sitio. */
  overflow: hidden; padding-top: .1em; margin-top: -.1em;
}
/* La pagina abria completamente quieta -de ahi lo de «parece un PDF»-. Ahora
   entra UNA cosa, el nombre, y en el mismo gesto que firma el sitio: un
   barrido vertical, no un fundido con desplazamiento. El <i> es lo que se
   mueve y el <span> lo recorta; sin elemento interior habria que animar
   `clip-path`, que no es ni transform ni opacity. */
.hero__title i { display: block; font-style: normal; }
.js .hero__title i {
  transform: translateY(105%);
  transition: transform .62s var(--ease-in);
}
.js .hero.is-in .hero__title i { transform: none; }
/* La segunda linea sale detras: son hermanas comparables, que es cuando el
   escalonado dice algo. */
.js .hero.is-in .hero__title-b i { transition-delay: .12s; }

.hero__roles {
  display: flex; flex-direction: column; gap: 14px;
  max-width: 520px; width: fit-content;
}
/* El vino como MASA y no como texto: un bloque sólido de color lee mucho
   mejor que el mismo tono en 11 px. Era una barra corta de 108 px; Kevin la
   vio «muy perdida» el 28 de agosto de 2026 y ahora cubre el ancho del texto.

   Ese ancho lo da el `width: fit-content` de arriba, no una cifra fija: la
   columna se encoge al ancho de la fila MODELO / CREADORA DE CONTENIDO y la
   barra la sigue con un 100 %. Si el texto cambia, la barra cambia con él, y
   donde no quepa, `fit-content` cede al ancho disponible en vez de desbordar.
   Sigue sin ser una divisoria de ancho completo: mide el texto, no la
   columna. */
.hero__roles .rule { height: 4px; width: 100%; background: var(--wine); }
.mono-row {
  display: flex; flex-wrap: wrap; gap: 8px 22px;
  font-family: var(--mono); font-size: 11px;
  letter-spacing: var(--track); text-transform: uppercase;
}
.mono-row__sep { color: var(--smoke); }

.hero__cta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 20px;
  padding-bottom: clamp(20px, 4vw, 40px);
}
.hero__claim { font-size: 13px; color: var(--sand); max-width: 34ch; line-height: 1.6; }

/* El contenido del hero se amontonaba abajo y dejaba medio flanco vacio
   arriba. Ahora la columna se ancla en TRES puntos en vez de uno: la sede
   arriba, el nombre en el centro y el pie abajo.

   Lo hacen dos `margin-top: auto`, que reparten el espacio sobrante entre
   esos tres grupos. Si no sobra espacio -pantallas cortas- los autos se
   colapsan a cero y el bloque se comporta como antes: no hay que rehacer
   nada para movil. */
.hero__inner { flex: 1; }
.hero__place { color: var(--smoke); }

/* Por debajo del corte NO hay tinta plana: la foto va a sangre y el texto
   queda encima. Medido con la foto real puesta -no con el placeholder gris
   con el que se audito en su dia-, ahi fallan dos cosas:

   - La fila de sede cae sobre el ventanal, la zona mas clara de la imagen:
     1,52:1 contra el minimo de 4,5. Se oculta. Existe para llenar el vacio
     del flanco izquierdo en escritorio, y en movil ese vacio no existe; el
     dato sigue estando en Sobre mi y en el pie.
   - El rotulo SCROLL daba 3,36:1. Ese fallo es anterior y lo destapo la foto.
   - El claim daba 3,93:1 al reordenar el bloque, que lo dejo sobre una zona
     mas clara que antes.

   Se resuelven con un refuerzo degradado en la mitad inferior, no subiendo el
   velo: para salvar 4,5 por velo hacia falta un 91 % y eso mata la foto. El
   48 % de alto sale de medir donde cae el claim, no de tantear. */
/* ── Hero en movil: la foto sigue de fondo ─────────────────────
   Kevin la quiere de fondo, no en franja. Lo que fallaba no era la idea sino
   el archivo: se estaba estirando el recorte de ESCRITORIO -un panel casi
   cuadrado, 1400x1459- sobre una pantalla mucho mas alta y estrecha, asi que
   el navegador se comia mas de la mitad del ancho y le cortaba la cabeza.

   Ahora hay un archivo propio para movil, vertical de verdad (1080x2160),
   encuadrado para que ella quede en el tercio superior y el texto caiga sobre
   el sofa -zona uniforme y oscura- en vez de sobre su cara.

   Los refuerzos de contraste se quedan, porque el texto sigue viviendo encima
   de la foto: */
@media (max-width: 999px) {
  /* La fila de sede cae sobre el ventanal, la zona mas clara: 1,52:1 contra
     el minimo de 4,5. Se oculta; el dato sigue en Sobre mi y en el pie. */
  .hero__place { display: none; }

  /* Todo el texto baja al tercio inferior, que en el recorte de movil es el
     sofa: una superficie oscura y uniforme. Asi ella queda entera y visible
     en la mitad de arriba y el titular deja de cruzarle la cara.

     En escritorio el bloque se ancla en tres puntos (sede arriba, nombre en
     el centro, pie abajo) y por eso el pie lleva `margin-top: auto`. Aqui
     solo hay dos, asi que ese auto sobra: sin el, todo el hueco se lo queda
     el `auto` del titulo y el bloque entero se va abajo de una pieza. */
  /* `.hero .hero__foot` y no `.hero__foot` a secas: la regla que pone el auto
     vive MAS ABAJO en la hoja, y una media query no gana por serlo -a igual
     especificidad manda la ultima-. Con la clase del padre delante, si. */
  .hero .hero__foot { margin-top: 28px; }

  /* Con el texto fuera de ella, el velo ya no tiene que rescatar contraste en
     toda la imagen: solo asienta la foto. Baja de 0,55 a 0,32 y ella se ve.
     El refuerzo de abajo hace el trabajo pesado donde vive el texto. */
  .hero { --hero-veil: 0.50; }
  /* Degradado en la mitad inferior, que es donde vive el texto. No se sube el
     velo porque para salvar 4,5 por velo hacia falta un 91 % y eso mata la
     foto. El 48 % sale de medir donde cae el claim, no de tantear. */
  .hero::after {
    content: ""; position: absolute; left: 0; right: 0; bottom: 0;
    height: 48%; z-index: 1; pointer-events: none;
    background: linear-gradient(to top, rgba(11, 11, 12, 0.92), rgba(11, 11, 12, 0));
  }
}
.hero__title { margin-top: auto; }
.hero__foot { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; margin-top: auto; }
.scroll-hint { display: flex; align-items: center; gap: 14px; }
.scroll-hint span {
  font-family: var(--mono); font-size: 10px;
  letter-spacing: var(--track); text-transform: uppercase; color: var(--smoke);
}
.scroll-hint__line {
  display: block; width: 1px; height: 52px; background: var(--wine);
  animation: scrollLine 2.4s var(--ease-in) infinite;
}
@keyframes scrollLine {
  0%   { transform: scaleY(0); transform-origin: top; }
  45%  { transform: scaleY(1); transform-origin: top; }
  55%  { transform: scaleY(1); transform-origin: bottom; }
  100% { transform: scaleY(0); transform-origin: bottom; }
}

.social { display: flex; align-items: center; gap: 18px; margin: 0; padding: 0; list-style: none; }
.social a {
  display: flex; width: 44px; height: 44px;
  align-items: center; justify-content: center;
  transition: color .16s var(--ease-hover);
}
/* El dibujo sube, la caja no. Los iconos vivían a 20 px dentro de un objetivo
   táctil de 44 y en el hero se leían pequeños; Kevin los pidió más grandes el
   28 de agosto de 2026. Se cambia aquí y no en los `width` de cada <svg>
   porque son seis iconos en dos listas y el CSS gana a ese atributo. Los
   44 px del objetivo táctil se quedan: son el piso de accesibilidad. */
.social a svg { width: 26px; height: 26px; }

/* ── 02 · Métricas ─────────────────────────────────────────── */
.metrics {
  background: var(--ink);
  border-top: 1px solid var(--hairline-light);
  border-bottom: 1px solid var(--hairline-light);
  padding: clamp(4rem, 9vh, 7rem) clamp(1.5rem, 6vw, 7rem);
}
.metrics__grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
  gap: clamp(1.75rem, 4vw, 3rem);
}
.metric {
  position: relative;
  display: flex; flex-direction: column; gap: 12px;
  padding-left: clamp(0px, 2vw, 28px);
}
/* La regla vertical era un `border-left`. Pasa a pseudo-elemento por una
   razon concreta: un borde no se puede animar con `transform`, y aqui se
   dibuja de arriba abajo en vez de aparecer de golpe.

   El numero NO se anima, y es deliberado. Un contador subiendo desde cero es
   el cliche de tablero de mandos; ademas obliga a leer una cifra que todavia
   es mentira. Se anima el contexto —la regla que estructura la columna— y la
   cifra esta desde el primer fotograma.

   El sentido descendente no es casual: es el mismo del barrido que destapa
   la seccion. Un idioma, no dos. */
.metric::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 1px;
  background: var(--hairline-light);
  transform-origin: top;
}
/* Sin JS no hay estado inicial y las reglas salen ya dibujadas. */
.js .metrics:not(.is-revealed) .metric::before { transform: scaleY(0); }
/* --ease-data es la curva de lo que crece; 800 ms entra en la franja larga y
   desacelerada que pide ese caso. El retardo base espera a que la tapa del
   revelado (0,9 s) haya pasado por la fila: dibujar debajo de una tapa opaca
   es gastar la animacion donde nadie la ve. */
.js .metrics.is-revealed .metric::before {
  transform: scaleY(1);
  transition: transform .8s var(--ease-data);
}
.js .metrics.is-revealed .metric:nth-child(1)::before { transition-delay: .50s; }
.js .metrics.is-revealed .metric:nth-child(2)::before { transition-delay: .58s; }
.js .metrics.is-revealed .metric:nth-child(3)::before { transition-delay: .66s; }
.js .metrics.is-revealed .metric:nth-child(4)::before { transition-delay: .74s; }
.metric__value {
  font-family: var(--serif); letter-spacing: -0.01em;
  font-size: clamp(2rem, 6vw, 3.5rem); line-height: 0.9;
  font-variant-numeric: tabular-nums;
}
.metric__label {
  font-family: var(--mono); font-size: 10px;
  letter-spacing: var(--track); text-transform: uppercase;
  color: var(--smoke); line-height: 1.6;
}

/* ── 03 · Sobre mí ─────────────────────────────────────────── */
/* Retrato y texto no pesan lo mismo: la columna de la imagen es más
   estrecha y baja desplazada respecto al titular. Rompe el bloque de dos
   columnas iguales, que es la retícula por defecto de toda plantilla. */
.about__grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: clamp(36px, 6vw, 90px); align-items: start;
}
@media (min-width: 900px) {
  .about__grid { grid-template-columns: 0.72fr 1.28fr; }
  .about__figure { margin-top: clamp(2rem, 7vw, 6rem); }
}
/* Única sección que rompe el margen: el retrato sale a sangre por la
   izquierda. Las ocho zonas de contenido medían canaletas idénticas, y una
   página donde nada se sale del recuadro se lee como plantilla. Se hace
   quitando el relleno izquierdo de la sección, no con márgenes negativos:
   así no depende de si la caja de 1600 px está centrada o no. */
@media (min-width: 1100px) {
  .about { padding-left: 0; }
}
.about__figure { margin: 0; }
.about__body { display: flex; flex-direction: column; gap: clamp(24px, 3vw, 40px); }

.specs {
  margin: 0; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 0 clamp(24px, 4vw, 56px);
}
.specs__row {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 16px; padding: 14px 0; border-bottom: 1px solid var(--hairline-dark);
}
.specs dt {
  font-family: var(--mono); font-size: 10px;
  letter-spacing: var(--track); text-transform: uppercase; color: var(--stone);
}
.specs dd { margin: 0; font-size: 14px; text-align: right; }

/* ── 04 · Servicios ────────────────────────────────────────── */
/* El relleno inferior es aproximadamente un tercio del superior, y la asimetría es a propósito.
   Servicios es la única sección que termina en una línea a todo el ancho —el
   borde de la última fila—, y un remate duro ya cierra la sección: el aire que
   venía después se leía como un hueco, no como respiro. Se toca aquí y no en
   `.work`, cuyo aire extra es una decisión tomada (es el argumento central de
   la página y por eso respira más que el resto). Matiz de 27 ago 2026: ese
   aire extra se conserva sólo ABAJO; el de arriba volvió al estándar porque
   los 200 px de negro entre las dos secciones se leían como un vacío. */
.services { padding-inline: 0; padding-bottom: clamp(2.5rem, 5vh, 4.5rem); }
.services__head {
  padding-inline: var(--gutter);
  display: flex; flex-direction: column; gap: clamp(16px, 2vw, 28px);
}
.services__head .display { margin-bottom: clamp(2.5rem, 6vw, 4rem); }
.services__list {
  margin: 0; padding: 0; list-style: none;
  max-width: var(--shell); margin-inline: auto;
  border-top: 1px solid var(--hairline-light);
}
.service { border-bottom: 1px solid var(--hairline-light); }
.service__head { margin: 0; font-weight: inherit; }

.service__btn {
  display: flex; align-items: baseline; gap: clamp(18px, 4vw, 64px);
  width: 100%; padding: clamp(22px, 3vw, 34px) var(--gutter);
  background: none; border: 0; cursor: pointer;
  text-align: left; color: inherit;
  /* Hover: respuesta, no espectáculo. 180 ms se siente inmediato.
     Antes esto animaba `padding-left`, que es una propiedad de layout:
     provocaba un reflow por fotograma en una fila de texto a ancho
     completo. Ahora el fondo se pinta y el contenido se desplaza con
     `transform`, que corre en el compositor. Mismo efecto, sin reflow. */
  transition: background .18s var(--ease-hover);
}
.service__btn > .service__n,
.service__btn > .service__t { transition: transform .18s var(--ease-hover); }

/* El vino ya no marca sólo el hover: marca también cuál está desplegado.
   El mismo gesto sirve para «aquí está el cursor» y para «esta es la
   abierta», así que la fila abierta se queda encendida al soltar. */
.service__btn:hover,
.service__btn[aria-expanded="true"] { background: var(--wine); }
.service__btn:hover > .service__n,
.service__btn:hover > .service__t,
.service__btn[aria-expanded="true"] > .service__n,
.service__btn[aria-expanded="true"] > .service__t {
  transform: translateX(clamp(1rem, 4vw, 4rem));
}

.service__n {
  flex: none; font-family: var(--mono); font-size: 11px;
  letter-spacing: var(--track); color: var(--smoke);
}
/* El mismo «+» de la galería: es el vocabulario que la página ya usa para
   decir «hay más aquí». Al abrir gira 45° y se lee como una ×. Sólo
   `transform`, y va excluido del desplazamiento de la fila porque está
   anclado a la derecha: moverlo lo empujaría fuera del borde. */
.service__mas {
  flex: none; margin-left: auto;
  font-family: var(--mono); font-size: 17px; line-height: 1;
  color: var(--smoke);
  transition: transform .22s var(--ease-hover), color .16s var(--ease-hover);
}
.service__btn:hover .service__mas { color: var(--bone); }
.service__btn[aria-expanded="true"] .service__mas {
  transform: rotate(45deg); color: var(--bone);
}

/* El panel abre con grid-template-rows de 0fr a 1fr. Es layout, y la regla
   de la casa es animar sólo transform y opacity — la excepción está
   pensada: esto pasa UNA vez por clic y en una sola fila, no sesenta veces
   por segundo mientras el cursor recorre la lista, que es el caso que sacó
   a padding y width de las animaciones. No hay forma de abrir un acordeón a
   su altura real sin tocar el layout, y fijar una altura máxima a ojo
   rompería con cualquier descripción más larga. */
.service__panel {
  display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows .34s var(--ease-in);
}
/* `visibility` saca el texto cerrado del árbol de accesibilidad; el retardo
   espera a que el panel termine de plegarse antes de ocultarlo. */
.service__panel > * {
  overflow: hidden;
  visibility: hidden; transition: visibility 0s linear .34s;
}
.service.is-open .service__panel { grid-template-rows: 1fr; }
.service.is-open .service__panel > * {
  visibility: visible; transition-delay: 0s;
}

.service__d {
  padding: 2px var(--gutter) clamp(26px, 3vw, 36px);
  /* 58ch de TEXTO, la misma medida que `.note`. Hay que sumarle los dos
     gutters porque el reset pone `box-sizing: border-box` y si no el
     relleno se comería la medida desde dentro: a 58ch pelados la línea
     quedaba en 400 px en escritorio. */
  max-width: calc(58ch + 2 * var(--gutter));
  font-size: clamp(14px, 1vw, 15.5px); line-height: 1.75;
  color: var(--sand); text-wrap: pretty;
  opacity: 0; transform: translateY(-6px);
  transition: opacity .28s var(--ease-in), transform .28s var(--ease-in);
}
.service.is-open .service__d {
  opacity: 1; transform: none;
}

/* Sin JavaScript el botón no conmuta nada, así que las cinco descripciones
   quedan a la vista y el «+» sobra: es contenido, no adorno. */
html:not(.js) .service__panel { grid-template-rows: 1fr; }
html:not(.js) .service__panel > * { visibility: visible; }
html:not(.js) .service__d { opacity: 1; transform: none; }
html:not(.js) .service__mas { display: none; }
.service__t {
  /* `min-width: 0` para que el título largo pueda partirse dentro del flex
     en vez de estirar la fila y sacar el «+» del borde. */
  margin: 0; min-width: 0;
  font-family: var(--serif); font-weight: 400;
  text-transform: uppercase; letter-spacing: -0.01em; line-height: 0.98;
  font-size: clamp(1.4rem, 3.5vw, 2rem);
}

/* ── 05 · Proyectos ────────────────────────────────────────── */
.work__inner { display: flex; flex-direction: column; gap: clamp(2.5rem, 5vw, 4rem); }

.filters {
  display: flex; flex-wrap: wrap; gap: 10px 26px;
  padding-top: 8px; border-top: 1px solid var(--hairline-light);
}
.filter {
  background: none; border: 0; cursor: pointer;
  /* Medían 34 px de alto. El subrayado activo sigue pegado al borde
     inferior porque el texto se centra dentro de los 44. */
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; padding: 12px 0 6px;
  font-family: var(--mono); font-size: 10px;
  letter-spacing: var(--track); text-transform: uppercase;
  color: var(--smoke); border-bottom: 1px solid transparent;
  transition: color .16s var(--ease-hover), border-color .16s var(--ease-hover);
}
.filter:hover { color: var(--bone); }
.filter.is-active { color: var(--bone); border-bottom-color: var(--wine); }

.work__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--card-min), 1fr));
  gap: clamp(12px, 1.6vw, 24px);
}
.project {
  position: relative; padding: 0; border: 0;
  aspect-ratio: 9 / 16; cursor: pointer;
  display: flex; flex-direction: column; justify-content: space-between;
  text-align: left; color: var(--bone);
}
/* El contorno pasa de transparente a color en vez de aparecer de golpe, y
   se usa --wine-text porque el vino de marca sobre el carbón de la tarjeta
   queda por debajo de 3:1 y no se vería. El movimiento va por dentro. */
.project { outline: 1px solid transparent; transition: outline-color .16s var(--ease-hover); }
.project:hover { outline-color: var(--wine-text); }
.project > img { transition: opacity .6s ease, transform .5s var(--ease-in); }
.project:hover > img { transform: scale(1.04); }
/* Una pieza puede pertenecer a dos categorias y la tarjeta anuncia las dos.
   Van dentro de un envoltorio en FILA porque .project es una columna flex:
   dos chips sueltos se apilarian uno debajo del otro. La posicion y el margen
   viven ahora en el envoltorio; el chip solo se ocupa de su propio fondo. */
.project__cats {
  position: relative; z-index: 1; align-self: flex-start;
  display: flex; flex-wrap: wrap; gap: 6px; margin: 10px;
}
.project__cat {
  font-family: var(--mono); font-size: 9px;
  letter-spacing: var(--track); text-transform: uppercase;
  background: rgba(11, 11, 12, 0.7); padding: 8px 10px;
}
.work__empty {
  font-family: var(--mono); font-size: 10px;
  letter-spacing: var(--track); text-transform: uppercase; color: var(--smoke);
}

/* Visor de proyecto */
.viewer, .lightbox {
  width: 100%; max-width: 100%; height: 100%; max-height: 100%;
  margin: 0; border: 0; padding: clamp(20px, 5vw, 64px);
  background: rgba(11, 11, 12, 0.95); color: var(--bone);
}
.viewer::backdrop, .lightbox::backdrop { background: rgba(11, 11, 12, 0.95); }

/* `display` sólo cuando el diálogo está abierto: si no, pisaríamos el
   `display:none` que el navegador aplica a `dialog:not([open])`. */
.viewer[open] {
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 18px;
}
.viewer__close, .lightbox__close { position: absolute; top: 20px; right: 20px; }
.viewer__frame { height: min(72svh, 80vw); aspect-ratio: 9 / 16; }
.viewer__frame > video { object-fit: contain; background: var(--carbon); }
.viewer__meta { display: flex; flex-wrap: wrap; gap: 8px 22px; align-items: baseline; }
.viewer__cat {
  font-family: var(--mono); font-size: 10px;
  letter-spacing: var(--track); text-transform: uppercase; color: var(--wine-text);
}

/* ── 06 · Galería ──────────────────────────────────────────── */
.gallery__inner { display: flex; flex-direction: column; gap: clamp(2.5rem, 5vw, 4rem); }
.gallery__head { display: flex; flex-direction: column; gap: 14px; }
/* Era un masonry de alturas aleatorias: el patrón genérico de siempre, y
   encima negaba el material. El trabajo de Valentina es vertical 9:16, así
   que la galería usa esa misma unidad. Para no acabar idéntica a la rejilla
   de proyectos, las piezas pares bajan y crean un ritmo escalonado: sostiene
   la idea del vertical en vez de aplicarla una sola vez. */
.gallery__masonry {
  display: grid;
  /* El mínimo escala con el viewport: 2 columnas en móvil (si fuera una
     sola, nueve verticales serían un scroll interminable) y 5 en escritorio
     (con un mínimo fijo de 150 px saldrían ocho columnas diminutas). */
  grid-template-columns: repeat(auto-fill, minmax(clamp(150px, 18vw, 230px), 1fr));
  gap: clamp(12px, 1.6vw, 24px); align-items: start;
}
.shot {
  display: block; width: 100%; aspect-ratio: 9 / 16;
  padding: 0; border: 0; margin: 0; cursor: pointer;
}
.shot:nth-child(even) { margin-top: clamp(1.5rem, 5vw, 4.5rem); }
/* Antes el hover atenuaba la foto a 0.78: una afordancia invertida — pasar
   el cursor la hacía MENOS visible. Ahora se mueve el contenido dentro del
   marco (la imagen encuadra un poco más) y el signo + responde. El marco
   no se toca: mover la tarjeta entera es el gesto genérico de siempre. */
/* Las dos propiedades juntas: `transition` es abreviada y esta regla es
   más específica que `.media > img`, así que declararla sola habría
   borrado el fundido con el que la imagen entra al cargar. */
.shot > img { transition: opacity .6s ease, transform .5s var(--ease-in); }
.shot:hover > img { transform: scale(1.05); }
.shot__plus { transition: transform .16s var(--ease-hover); }
.shot:hover .shot__plus { transform: scale(1.35); }
.shot__plus {
  position: absolute; bottom: 12px; right: 14px; z-index: 1;
  font-family: var(--mono); font-size: 16px; color: var(--bone);
}

/* Lightbox */
.lightbox[open] {
  display: flex; align-items: center; justify-content: center;
  gap: clamp(10px, 3vw, 40px);
}
.lightbox__frame {
  flex: 1; max-width: 760px; height: min(74svh, 110vw);
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 10px;
}
.lightbox__frame > img { object-fit: contain; }
.lightbox__counter {
  position: absolute; bottom: 16px; left: 0; right: 0; z-index: 1;
  text-align: center;
  font-family: var(--mono); font-size: 10px; letter-spacing: var(--track); color: var(--wine-text);
}

/* ── 07 · La audiencia ─────────────────────────────────────── */
.audience__inner { display: flex; flex-direction: column; gap: clamp(3rem, 6vw, 4.5rem); }
.audience__head { display: flex; flex-direction: column; gap: 18px; }
.audience__grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: clamp(2.5rem, 5vw, 4rem);
}
.panel {
  display: flex; flex-direction: column; gap: 18px;
  padding-top: 20px; border-top: 1px solid var(--hairline-light);
}
.panel__title {
  font-family: var(--mono); font-size: 10px;
  letter-spacing: var(--track); text-transform: uppercase; color: var(--smoke);
}

/* Las barras conservan su ancho real y sólo se escalan: `width` es una
   propiedad de layout y animarla en diez barras a la vez era un reflow
   por fotograma. `scaleX` con origen izquierdo da exactamente el mismo
   crecimiento y corre en el compositor. Además el ancho sigue escrito en
   el CSS, así que sin JavaScript las barras se ven completas. */
.split {
  display: flex; height: 44px; width: 100%;
  transform-origin: left; transition: transform 1.1s var(--ease-data);
}
.split__a, .split__b { display: block; height: 100%; width: var(--w); }
.split__a { background: var(--wine); }
.split__b { background: var(--smoke); }
.split__legend {
  display: flex; justify-content: space-between; gap: 16px;
  font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.15em; text-transform: uppercase;
}

.bars { display: flex; flex-direction: column; gap: 18px; }
.bar { display: grid; grid-template-columns: 64px 1fr 60px; align-items: center; gap: 14px; }
.bar__k { font-family: var(--mono); font-size: 10px; letter-spacing: 0.1em; }
.bar__t {
  display: block; height: 8px; background: var(--wine);
  width: var(--w);
  transform-origin: left; transition: transform 1.1s var(--ease-data);
}
.bar__v { font-family: var(--mono); font-size: 10px; color: var(--smoke); text-align: right; }

.markets { display: flex; flex-direction: column; gap: 18px; }
.market { display: flex; flex-direction: column; gap: 8px; }
.market__row {
  display: flex; justify-content: space-between; gap: 12px;
  font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.18em; text-transform: uppercase;
}
.market__t {
  display: block; height: 2px; background: var(--wine);
  width: var(--w);
  transform-origin: left; transition: transform 1.1s var(--ease-data);
}
.cities {
  font-family: var(--mono); font-size: 9px;
  letter-spacing: 0.18em; line-height: 1.9;
  text-transform: uppercase; color: var(--smoke);
}

/* Las barras crecen desde cero sólo cuando hay JS para dispararlas.
   La barra de género se escala como una pieza entera (el contenedor), no
   segmento por segmento: así el bloque bicolor entra como una sola cosa. */
.js .audience:not(.is-revealed) .split,
.js .audience:not(.is-revealed) .bar__t,
.js .audience:not(.is-revealed) .market__t { transform: scaleX(0); }

/* Escalonado. Antes las diez barras crecian a la vez y desde el instante
   cero, o sea que la mayor parte del crecimiento pasaba DEBAJO de la tapa
   opaca del revelado: cuando la tapa se iba, ya estaban casi llenas y el
   gesto no se leia. Ahora entran en cascada y despues de que la tapa pase.

   Estos tiempos estan replicados en la COREOGRAFIA de js/main.js, que es la
   que lanza los numeros: cada cifra sube mientras crece SU barra. Si se
   cambia un valor aqui, hay que cambiarlo alli. */
.js .audience.is-revealed .split { transition-delay: .35s; }
.js .audience.is-revealed .bars .bar:nth-child(1) .bar__t { transition-delay: 0.40s; }
.js .audience.is-revealed .bars .bar:nth-child(2) .bar__t { transition-delay: 0.45s; }
.js .audience.is-revealed .bars .bar:nth-child(3) .bar__t { transition-delay: 0.50s; }
.js .audience.is-revealed .bars .bar:nth-child(4) .bar__t { transition-delay: 0.55s; }
.js .audience.is-revealed .bars .bar:nth-child(5) .bar__t { transition-delay: 0.60s; }
.js .audience.is-revealed .bars .bar:nth-child(6) .bar__t { transition-delay: 0.65s; }
.js .audience.is-revealed .bars .bar:nth-child(7) .bar__t { transition-delay: 0.70s; }

.js .audience.is-revealed .markets .market:nth-child(1) .market__t { transition-delay: 0.45s; }
.js .audience.is-revealed .markets .market:nth-child(2) .market__t { transition-delay: 0.51s; }
.js .audience.is-revealed .markets .market:nth-child(3) .market__t { transition-delay: 0.57s; }
.js .audience.is-revealed .markets .market:nth-child(4) .market__t { transition-delay: 0.63s; }
.js .audience.is-revealed .markets .market:nth-child(5) .market__t { transition-delay: 0.69s; }

.engagement {
  display: flex; flex-direction: column; gap: 14px;
  padding-top: clamp(20px, 3vw, 36px); border-top: 1px solid var(--hairline-light);
}
/* Los tres números medían lo mismo, así que «28 MIL Me gusta» ganaba por
   ser la cifra más alta — justo lo contrario de lo que la página afirma
   dos veces. «Guardados» pasa a mandar y los otros dos bajan a apoyo:
   la jerarquía visual ahora dice lo mismo que el texto. */
.engagement__grid {
  display: grid; grid-template-columns: 1.7fr 1fr 1fr;
  gap: clamp(20px, 3vw, 40px); align-items: baseline;
}
.engagement__item { display: flex; flex-direction: column; gap: 8px; }
.engagement__n {
  font-family: var(--serif); letter-spacing: -0.02em;
  font-size: clamp(1.5rem, 4vw, 2.3rem); color: var(--smoke);
}
.engagement__item--lead .engagement__n {
  font-size: clamp(3.4rem, 11vw, 6.5rem); color: var(--bone); line-height: 0.85;
}
.engagement__item--lead .engagement__l { color: var(--wine-text); }

@media (max-width: 700px) {
  .engagement__grid { grid-template-columns: 1fr 1fr; }
  .engagement__item--lead { grid-column: 1 / -1; }
}
.engagement__l {
  font-family: var(--mono); font-size: 10px;
  letter-spacing: var(--track); text-transform: uppercase; color: var(--smoke);
}
.engagement__foot {
  font-family: var(--mono); font-size: 9px;
  letter-spacing: var(--track); text-transform: uppercase; color: var(--wine-text);
}

/* ── 08 · Cómo trabajamos ──────────────────────────────────── */
.process__inner { display: flex; flex-direction: column; gap: clamp(2.5rem, 5vw, 4rem); }
.process__head { display: flex; flex-direction: column; gap: 14px; }
.steps {
  margin: 0; padding: 0; list-style: none;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: clamp(20px, 2.5vw, 32px);
}
.step {
  position: relative;
  display: flex; flex-direction: column; gap: 14px;
  /* 23 y no 22: la regla de arriba era un `border-top` de 1px y con
     box-sizing:border-box el contenido empezaba a 1+22. Al pasar la regla a
     pseudo-elemento -que no ocupa- ese pixel se recupera en el padding, y el
     bloque queda exactamente donde estaba. */
  padding-top: 23px;
}
/* El punto de origen. */
.step::before {
  content: ""; position: absolute; top: -3px; left: 0;
  width: 7px; height: 7px; background: var(--wine); border-radius: 50%;
}
/* La regla horizontal, antes `border-top`. Es pseudo-elemento porque un borde
   no se puede animar con transform, y aqui se DIBUJA de izquierda a derecha
   saliendo del punto: la seccion es una linea de tiempo y el gesto la recorre.
   Mismo grosor y mismo color que el borde que sustituye. */
.step::after {
  content: ""; position: absolute; top: 0; left: 0; right: 0;
  height: 1px; background: rgba(11, 11, 12, 0.28);
  transform-origin: left;
}

/* ── Entrada escalonada de los pasos ───────────────────────────
   Escalonar aqui si procede: son cinco hermanos comparables de una lista, no
   una seccion entera. Y el orden ES la informacion — brief, propuesta,
   produccion, entrega, reporte —, asi que verlos aparecer en fila explica el
   proceso en vez de decorarlo.

   Tres tiempos por paso, con funciones distintas: el punto cae con rebote
   (tiene peso), la linea sale corriendo desde el (algo entra) y el texto se
   asienta detras. Sin JS no hay estado inicial y los cinco pasos salen
   completos. */
.js .process:not(.is-revealed) .step::before { transform: scale(0); }
.js .process:not(.is-revealed) .step::after  { transform: scaleX(0); }
.js .process:not(.is-revealed) .step__n,
.js .process:not(.is-revealed) .step__t,
.js .process:not(.is-revealed) .step__d { opacity: 0; transform: translateY(10px); }

.js .process.is-revealed .step::before {
  transform: scale(1);
  transition: transform .3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.js .process.is-revealed .step::after {
  transform: scaleX(1);
  transition: transform .52s var(--ease-in);
}
.js .process.is-revealed .step__n,
.js .process.is-revealed .step__t,
.js .process.is-revealed .step__d {
  opacity: 1; transform: none;
  transition: opacity .46s var(--ease-in), transform .46s var(--ease-in);
}

/* El retardo base deja pasar la tapa opaca del revelado; los 170 ms entre
   pasos son lo que hace que se lean uno detras de otro. */
.js .process.is-revealed .step:nth-child(1)::before { transition-delay: 550ms; }
.js .process.is-revealed .step:nth-child(1)::after  { transition-delay: 610ms; }
.js .process.is-revealed .step:nth-child(1) .step__n,
.js .process.is-revealed .step:nth-child(1) .step__t,
.js .process.is-revealed .step:nth-child(1) .step__d { transition-delay: 690ms; }
.js .process.is-revealed .step:nth-child(2)::before { transition-delay: 720ms; }
.js .process.is-revealed .step:nth-child(2)::after  { transition-delay: 780ms; }
.js .process.is-revealed .step:nth-child(2) .step__n,
.js .process.is-revealed .step:nth-child(2) .step__t,
.js .process.is-revealed .step:nth-child(2) .step__d { transition-delay: 860ms; }
.js .process.is-revealed .step:nth-child(3)::before { transition-delay: 890ms; }
.js .process.is-revealed .step:nth-child(3)::after  { transition-delay: 950ms; }
.js .process.is-revealed .step:nth-child(3) .step__n,
.js .process.is-revealed .step:nth-child(3) .step__t,
.js .process.is-revealed .step:nth-child(3) .step__d { transition-delay: 1030ms; }
.js .process.is-revealed .step:nth-child(4)::before { transition-delay: 1060ms; }
.js .process.is-revealed .step:nth-child(4)::after  { transition-delay: 1120ms; }
.js .process.is-revealed .step:nth-child(4) .step__n,
.js .process.is-revealed .step:nth-child(4) .step__t,
.js .process.is-revealed .step:nth-child(4) .step__d { transition-delay: 1200ms; }
.js .process.is-revealed .step:nth-child(5)::before { transition-delay: 1230ms; }
.js .process.is-revealed .step:nth-child(5)::after  { transition-delay: 1290ms; }
.js .process.is-revealed .step:nth-child(5) .step__n,
.js .process.is-revealed .step:nth-child(5) .step__t,
.js .process.is-revealed .step:nth-child(5) .step__d { transition-delay: 1370ms; }
.step__n { font-family: var(--mono); font-size: 10px; letter-spacing: var(--track); color: var(--wine-text); }
.step__t {
  margin: 0; font-family: var(--serif); font-weight: 400;
  text-transform: uppercase; letter-spacing: -0.01em;
  font-size: clamp(1.4rem, 3.5vw, 2rem); line-height: 1;
}
.step__d { font-size: 14px; line-height: 1.7; color: var(--coal); max-width: 30ch; }

/* ── 09 · Contacto ─────────────────────────────────────────── */
.contact__inner { display: flex; flex-direction: column; gap: clamp(3rem, 6vw, 4.5rem); }
.contact__head { display: flex; flex-direction: column; gap: 14px; }
.contact__grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: clamp(36px, 5vw, 90px); align-items: start;
}

.form {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: clamp(22px, 3vw, 34px);
}
.field {
  display: flex; flex-direction: column; gap: 10px;
  margin: 0; padding: 0; border: 0; min-width: 0;
}
.field--wide { grid-column: 1 / -1; }
.field__label {
  padding: 0;
  font-family: var(--mono); font-size: 9px;
  letter-spacing: var(--track); text-transform: uppercase; color: var(--smoke);
}
/* 16px, no 15. En iPhone cualquier campo por debajo de 16 hace que la pagina
   se acerque sola al tocarlo, y luego hay que alejarla a mano. El salto de un
   pixel no se nota; el zoom si. */
.field input[type="text"],
.field input[type="email"],
.field input[type="date"],
.field select,
.field textarea {
  background: none; border: 0;
  border-bottom: 1px solid rgba(237, 231, 225, 0.28);
  padding: 10px 0; font-size: 16px; min-height: 44px;
  transition: border-color .16s var(--ease-hover);
}

/* El desplegable NO lo dibuja este CSS: lo dibuja el sistema operativo, con
   sus propias medidas internas. Por eso `getComputedStyle` decia que estaba
   alineado con los otros campos y aun asi se veia metido hacia adentro, mas
   grande y con una flecha ajena a la pagina. Se apaga ese dibujo nativo y se
   pone una flecha propia, en vino, con el hueco justo a la derecha para que
   el texto no la pise. */
.field select {
  appearance: none; -webkit-appearance: none;
  background-color: transparent;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='9' viewBox='0 0 14 9' fill='none' stroke='%23D04E5B' stroke-width='1.6'%3E%3Cpath d='M1 1.5 7 7.5l6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 2px center;
  background-size: 12px 8px;
  padding-right: 26px;
  cursor: pointer;
  /* Firefox deja un texto sangrado aunque se apague la apariencia. */
  text-indent: 0;
}
/* Sobre arena la flecha en vino claro se pierde: alli va la version oscura. */
.section--sand .field select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='9' viewBox='0 0 14 9' fill='none' stroke='%236E1822' stroke-width='1.6'%3E%3Cpath d='M1 1.5 7 7.5l6-6'/%3E%3C/svg%3E");
}
.field textarea { line-height: 1.7; resize: vertical; }
.field input:focus, .field select:focus, .field textarea:focus {
  border-bottom-color: var(--wine); outline: none;
}
.field__err {
  font-family: var(--mono); font-size: 9px;
  letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--wine-text); min-height: 12px;
}
.field.has-error input,
.field.has-error select { border-bottom-color: var(--wine); }

.choices { display: flex; flex-wrap: wrap; gap: 14px 28px; }
.choices--row { gap: 28px; }
.choice {
  display: flex; align-items: center; gap: 10px;
  min-height: 44px; font-size: 14px; cursor: pointer;
}
.choice input { width: 16px; height: 16px; accent-color: var(--wine); }

.form__actions {
  grid-column: 1 / -1;
  display: flex; flex-wrap: wrap; align-items: center; gap: 20px;
}
.form__note, .form__error {
  font-family: var(--mono); font-size: 9px;
  letter-spacing: 0.2em; text-transform: uppercase; color: var(--smoke);
}
.form__error { grid-column: 1 / -1; color: var(--wine-text); }

/* Trampa antispam. Fuera de la pantalla, no `display:none`: hay robots que
   saltan lo que está oculto de esa forma, y así siguen cayendo. Ocupa cero
   en la rejilla del formulario. */
.hp {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

.sent {
  display: flex; flex-direction: column; gap: 18px;
  padding: clamp(28px, 4vw, 48px); border: 1px solid var(--wine);
}
.sent p { font-size: 16px; line-height: 1.7; max-width: 40ch; }
/* El botón no se estira al ancho de la caja: en una columna flex, un enlace
   sin esto ocuparía toda la línea y dejaría de leerse como botón. */
.sent .btn { align-self: flex-start; }

/* Respaldo sin JavaScript. Mismo tratamiento que la caja de confirmación
   porque cumple el mismo papel: es lo que ocupa el lugar del formulario.
   El formulario se oculta porque sin JS no puede entregar la solicitud a
   ningún sitio, y además su envío nativo volcaría el nombre y el correo de
   quien escribe a la barra de direcciones y al historial. */
.form-fallback {
  display: flex; flex-direction: column; gap: 18px;
  padding: clamp(28px, 4vw, 48px); border: 1px solid var(--wine);
}
.form-fallback p { font-size: 16px; line-height: 1.7; max-width: 40ch; }
.form-fallback .btn { align-self: flex-start; }
.js .form-fallback { display: none; }
html:not(.js) .form { display: none; }

.contact__aside {
  display: flex; flex-direction: column;
  border-top: 1px solid var(--hairline-light);
}
.contact__link {
  display: flex; flex-direction: column; gap: 10px;
  padding: 26px 0; border-bottom: 1px solid var(--hairline-light);
}
.contact__link-k {
  font-family: var(--mono); font-size: 9px;
  letter-spacing: var(--track); text-transform: uppercase; color: var(--smoke);
}
.contact__link-v {
  font-family: var(--serif); font-size: clamp(1.4rem, 3.5vw, 2rem); letter-spacing: -0.02em;
}
.contact__link-v--sm {
  font-size: clamp(1.1rem, 2.4vw, 1.5rem);
  letter-spacing: -0.01em; line-height: 1.15; word-break: break-word;
}
.contact__kit { margin-top: 28px; align-self: flex-start; }

/* ── Pie de página ─────────────────────────────────────────── */
.footer {
  position: relative; background: var(--ink);
  border-top: 1px solid var(--hairline-light);
  padding: clamp(5rem, 10vh, 8rem) var(--gutter) clamp(2rem, 4vw, 3rem);
  overflow: hidden;
}
.footer__inner {
  position: relative;
  display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between;
  gap: 32px;
}
.footer__legal {
  font-family: var(--mono); font-size: 9px;
  letter-spacing: var(--track); text-transform: uppercase; color: var(--smoke);
}

/* ── Responsive ────────────────────────────────────────────── */
@media (max-width: 900px) {
  .nav { display: none; }
  .navbar { display: flex; }
  .nav__mark { font-size: 14px; }
}

@media (max-width: 700px) {
  /* Los proyectos pasan a carrusel con anclaje horizontal */
  .work__grid {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: 66%;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .work__grid > * { scroll-snap-align: start; }
  .work__grid::-webkit-scrollbar { display: none; }

  /* La galería hace lo mismo. En vertical eran doce fotos 9:16 una debajo de
     otra: en el móvil esa sección sola medía más que el resto de la página
     junta. Kevin pidió el gesto de los proyectos, que ya le gustaba. */
  .gallery__masonry {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: 66%;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .gallery__masonry > * { scroll-snap-align: start; }
  .gallery__masonry::-webkit-scrollbar { display: none; }
  /* El escalonado de las pares existe para romper la retícula cuando las
     fotos caen en columnas. En una fila única no rompe nada: sólo desalinea
     la mitad de las tarjetas y deja el carrusel cojo. */
  .shot:nth-child(even) { margin-top: 0; }
  .lightbox { gap: 10px; padding: 20px; }
  .lightbox .icon-btn { width: 40px; height: 40px; }
}

/* ── Impresión ─────────────────────────────────────────────────
   Esta página es un media kit: una marca la va a imprimir o guardar en PDF.
   Sin estas reglas, imprimir sin haber hecho scroll daba el hero y después
   OCHO RECTÁNGULOS NEGROS — las tapas del revelado son opacas y las barras
   de audiencia arrancan en scaleX(0). Todo el contenido desaparecía. */
@media print {
  @page { margin: 15mm; }

  /* Lo primero: deshacer los estados de animación. Nada de lo que sólo se
     activa al hacer scroll puede decidir qué sale en el papel. */
  .js [data-reveal]::before { display: none !important; }
  .js [data-reveal] > *,
  .js .audience .split,
  .js .audience .bar__t,
  .js .audience .market__t { transform: none !important; opacity: 1 !important; }

  /* La regla de las metricas dejo de ser un `border-left` y paso a fondo de
     un pseudo-elemento, asi que la anulacion de fondos de mas abajo la
     borraria del papel. Se le devuelve el color y se le quita el estado
     inicial de la animacion. Gana por especificidad al `*::before`. */
  .js .metric::before {
    transform: none !important;
    background-color: #c9c4bf !important;
  }

  /* Papel blanco y tinta oscura. NO se redefinen --ink ni --sand: los dos
     se usan a la vez como fondo Y como color de texto, así que cambiarlos
     dejaba texto blanco sobre papel blanco. Se hace al revés — se anulan
     todos los fondos y se fuerza tinta oscura, y luego se devuelve el color
     sólo a las tres cosas que lo necesitan. */
  *, *::before, *::after {
    background-color: transparent !important;
    color: #111 !important;
    box-shadow: none !important;
    text-shadow: none !important;
  }
  body { background: #fff !important; font-size: 11pt; }
  :root { --hairline-light: rgba(0, 0, 0, 0.25); --hairline-dark: rgba(0, 0, 0, 0.25); }

  /* 1 · Las etiquetas de sección conservan el vino: 8,4:1 sobre blanco. */
  .eyebrow, .step__n, .engagement__foot { color: #7A1C28 !important; }
  /* 2 · Las barras son el dato, no decoración: sin relleno no dicen nada. */
  .split__a, .bar__t, .market__t, .step::before { background-color: #7A1C28 !important; }

  /* La regla de los pasos dejo de ser `border-top` y paso a fondo de un
     pseudo-elemento, asi que la anulacion de fondos de arriba la borraria del
     papel. Se le devuelve un gris equivalente al rgba(11,11,12,.28) sobre
     blanco, y se deshacen los estados iniciales de la entrada escalonada. */
  .js .step::after {
    transform: none !important;
    background-color: #b7b7b7 !important;
  }
  .js .step__n, .js .step__t, .js .step__d {
    transform: none !important; opacity: 1 !important;
  }
  .split__b { background-color: #9A9A9A !important; }
  /* 3 · Texto secundario, más claro pero legible en papel. */
  .metric__label, .panel__title, .bar__v, .cities, .specs dt,
  .contact__link-k, .footer__legal, .form__note, .mono-row__sep { color: #555 !important; }

  /* Fuera lo que sólo sirve en pantalla. */
  .page-header, .skip, dialog,
  .hero__bg, .hero__veil, .scroll-hint,
  .filters, .form, .form-fallback, .shot__plus, .social { display: none !important; }
  .media::after { display: none; }  /* rótulos de andamio */

  /* El hero ocupaba una página entera para nada. */
  .hero { min-height: auto; padding: 0 0 10mm; }
  .hero__title { font-size: 32pt; }
  .section, .metrics, .footer { padding: 0 0 8mm; }
  .work, .process { padding-block: 0 8mm; }
  .about { padding-left: 0; }

  /* Los marcos vacíos no se imprimen como manchas. */
  .media { border: 1px solid rgba(0, 0, 0, 0.2); }
  /* El reset de arriba también apagó el fondo del velo y del hero. */
  .hero__title-a, .hero__title-b { color: #111 !important; }

  /* Los servicios se imprimen desplegados. En papel no hay clic, y la
     descripción es justo lo que una marca se lleva del media kit: dejarla
     plegada la borraría de la hoja. Misma razón que las reglas de arriba. */
  .service__panel { grid-template-rows: 1fr !important; }
  .service__panel > * { visibility: visible !important; }
  .service__d { opacity: 1 !important; transform: none !important; color: #111 !important; }
  .service__mas { display: none !important; }

  /* Nada se parte por la mitad entre páginas. */
  section, .project, .shot, .step, .metric, .panel, .specs__row, .service {
    break-inside: avoid;
  }
  h1, h2, h3 { break-after: avoid; }

  /* En papel un enlace sin su dirección no sirve de nada. */
  .contact__link[href^="mailto"]::after,
  .contact__link[href^="http"]::after {
    content: " — " attr(href);
    font-family: var(--mono); font-size: 8pt; color: #555;
    word-break: break-all;
  }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation: none !important;
    transition-duration: 0.01ms !important;
    /* El retardo tambien, no solo la duracion: una transicion con delay
       seguiria haciendo esperar aunque dure 0,01 ms, y las reglas de las
       metricas tardarian medio segundo en aparecer sin motivo. */
    transition-delay: 0ms !important;
  }
  /* La tapa del revelado no existe: la sección ya está a la vista. */
  .js [data-reveal]::before { display: none; }
  /* Y el titular ya está puesto, sin esperar a que el JS suelte la clase. */
  .js .hero__title i { transform: none !important; }
}
