/* =========================================================================
   ALEJANDRO PLATA — PORTFOLIO
   Tipografía: Vend Sans (fallback: Helvetica, Arial)
   ========================================================================= */

/* ---------- 1. TOKENS ---------- */
:root{
  --bg:#D7D7D7;
  --bg-rgb:215,215,215; /* el mismo, para usarlo con alfa */
  --ink:#0A0A0A;
  --ink-soft:#8C8C8C;
  --ink-mid:#5A5A5A;
  --frame:#0D0D0D;

  /* easings */
  --ease: cubic-bezier(.16, 1, .3, 1);
  --ease-soft: cubic-bezier(.25, .8, .25, 1);

  /* ---------- ESCALA TIPOGRÁFICA ----------
     Un solo sitio para los tamaños. Cinco niveles, y nada fuera de ellos:

       --fs-display  títulos grandes            peso 600
       --fs-copy     texto editorial grande     peso 500 / 450
       --fs-body     texto de lectura           peso 400 / 450
       --fs-small    metadatos                  peso 400
       --fs-label    etiquetas técnicas         peso 500

     --fs-copy se mide también contra la ALTURA de la ventana porque la
     pantalla de texto de About tiene que caber entera sin scroll; el max()
     le pone suelo para que en el móvil no se quede diminuta. */
  --fs-display: clamp(30px, 4.15vw, 62px);
  --fs-copy:    clamp(22px, 2vw, 30px);
  --fs-body:    clamp(16px, 1.4vw, 18px);
  --fs-small:   14px;
  --fs-label:   11px;

  /* el nombre y el menú van por su cuenta: son el armazón, no contenido */
  /* hasta dónde baja la cabecera: los títulos arrancan por debajo sin
     números a mano */
  --cabecera: 80px;

  --fs-brand: 28px;
  /* el tamaño con el que se dibuja de verdad: en un móvil, "Alejandro Plata®"
     a 39px no cabe de ancho y se partía en dos líneas, comiéndose el menú.
     El tope en vw lo evita sin tener que tocar --fs-brand. */
  --fs-brand-real: min(var(--fs-brand), 8.2vw);
  --fs-nav:   16.5px;
}

/* en pantalla estrecha se reajusta la ESCALA, no cada elemento por su
   cuenta: así la proporción entre niveles se mantiene en todas las páginas */
/* en móvil no puede haber tanto texto grande a la vez: se acorta el salto
   entre niveles y los metadatos y etiquetas se quedan igual */
@media (max-width:900px){
  :root{
    --fs-display: clamp(31px, 8vw, 48px);
    --fs-copy:    clamp(20px, 5.4vw, 25px);
    --fs-body:    clamp(17px, 4.2vw, 19px);
    --fs-small:   14px;
    --fs-label:   11px;
  }
}

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

html,body{margin:0;padding:0}

html{
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  text-rendering:optimizeLegibility;
}

body{
  background:var(--bg);
  color:var(--ink);
  font-family:"Vend Sans",Helvetica,Arial,"Liberation Sans",sans-serif;
  font-weight:400;
  font-size:16px;
  line-height:1.35;
  opacity:0;
  transition:opacity .55s var(--ease-soft);
}
body.is-ready{opacity:1}

img{display:block;max-width:100%}
a{color:inherit;text-decoration:none}
h1,h2,h3,p,ul{margin:0;padding:0}
ul{list-style:none}

::selection{background:#0A0A0A;color:#D7D7D7}

/* ---------- 2. CURSOR ESFERA ----------
   any-hover/any-pointer en vez de hover/pointer: estos últimos describen
   solo el dispositivo primario, así que en un portátil con pantalla
   táctil darían false aunque haya ratón, y el cursor nativo no se
   ocultaría ni aparecería la esfera. */
@media (any-hover:hover) and (any-pointer:fine){
  html,body,a,button,input,textarea{cursor:none}
}

.cursor{
  position:fixed; top:0; left:0;
  width:11px; height:11px;
  border-radius:50%;
  background:radial-gradient(circle at 33% 28%, #ffffff 0%, #ededed 42%, #a8a8a8 78%, #7d7d7d 100%);
  mix-blend-mode:difference;      /* -> invierte sobre las imágenes */
  pointer-events:none;
  z-index:9999;
  transform:translate3d(-100px,-100px,0) translate(-50%,-50%);
  will-change:transform;
  transition:opacity .3s linear,
             width .28s var(--ease), height .28s var(--ease),
             border-radius .28s var(--ease), background .28s var(--ease);
}
.cursor.is-hidden{ opacity:0 }

/* sobre algo pulsable (enlace o botón) crece y se vuelve cuadrado. La clase
   la pone el JS, que excluye lo marcado como [data-cursor="media"]: las
   portadas son un <a> alrededor de una imagen y ahí debe seguir pequeño.
   El centrado no se pierde al crecer porque el translate(-50%,-50%) que
   escribe el bucle va sobre la caja, sea del tamaño que sea. */
.cursor.is-link{
  width:32px; height:32px;
  border-radius:3px;
  background:#fff;
}

/* solo se oculta si NO hay ningún puntero fino disponible (táctil puro:
   no hay cursor nativo que sustituir). La preferencia del sistema
   prefers-reduced-motion no se respeta a propósito: las animaciones y el
   cursor deben verse siempre. */
@media not all and (any-pointer:fine){ .cursor{display:none} }

/* ---------- IMÁGENES Y VÍDEO NO DESCARGABLES ----------
   Corta los gestos con los que se guardan sin querer: arrastrar a otra
   ventana, seleccionar, y sobre todo el menú de "Guardar imagen" que sale
   en iOS al mantener el dedo pulsado. El menú del botón derecho lo bloquea
   el JS, solo sobre imágenes y vídeo. */
img, video{
  -webkit-user-drag:none;
  user-drag:none;
  -webkit-user-select:none;
  user-select:none;
  -webkit-touch-callout:none;
}

/* ---------- 3. NAV + SHELL PERSISTENTE ----------
   El header, el cursor y el copyright nunca se sustituyen al navegar:
   solo cambia el contenido de #app ("rearme de la interfaz"). */
/* estático a propósito: en position:fixed creaba un contexto de apilamiento
   y encerraba dentro el mix-blend-mode de .nav__links, que entonces no
   llegaba a mezclarse con la imagen. Lo que va fijo es .nav__links. */
.nav{ position:static }
.nav__brand{
  position:fixed; top:24px; left:28px;
  /* por encima del panel de secciones (300): al abrirlo, el nombre y el
     botón tienen que seguir viéndose. El invertir los pone en blanco sobre
     el oscuro del panel sin tocar nada. */
  z-index:310;
  font-size:var(--fs-brand-real); font-weight:700; letter-spacing:-.01em;
  /* EL EFECTO DE INVERTIR. En blanco y con difference, cada píxel sale como
     el inverso de lo que tiene debajo: sobre negro se ve blanco y sobre
     claro se ve oscuro, al instante y sin medir nada. Antes se calculaba el
     brillo con un canvas y se cambiaba el color de golpe: con un vídeo
     llegaba tarde y daba saltos. */
  color:#fff;
  mix-blend-mode:difference;
}
.nav__brand sup{
  font-size:.4em; font-weight:600;
  vertical-align:super;
  margin-left:1px;
}
.nav__links{
  /* todo el grupo contra el margen derecho: la fila mide lo que ocupan los
     enlaces y se ancla por la derecha, así que Contact queda a 28px del
     borde y el resto a su izquierda, seguidos */
  position:fixed; left:auto; top:40px;
  right:28px;
  z-index:200;
  translate:0 -50%;
  mix-blend-mode:difference; /* mismo invertir que la marca */
  display:flex; gap:26px;
  /* en mayúsculas y pequeño, como una etiqueta. El tracking abierto no es
     opcional aquí: en caja alta las letras se tocan sin él. */
  font-size:var(--fs-nav); font-weight:500;
  text-transform:uppercase; letter-spacing:.09em;
}
/* con difference el color marca la INTENSIDAD del invertido: blanco puro
   invierte al máximo, y un blanco apagado da el gris suave de las secciones
   no activas. Funciona igual sobre fondo claro que sobre oscuro. */
.nav__links a{
  color:#b4b4b4;
  transition:color .35s var(--ease-soft);
}
.nav__links a:hover,
.nav__links a.is-current{ color:#fff }

/* la cabecera se retira al bajar y vuelve al subir.
   El desplazamiento va en px y no en %: el nombre y el menú están
   separados del borde (top:24px / 40px) y un -100% de su propia altura
   no llegaría a sacarlos de la pantalla. */
/* el desplazamiento y la opacidad los escribe updateNavAutohide() frame a
   frame a partir del scroll recorrido: aquí NO puede haber transition sobre
   transform ni opacity, porque interpolaría por su cuenta y se desfasaría
   del scroll. Solo el color (el efecto invertir de la landing) se anima. */
.nav__links,
.nav__brand{
  transition:color .9s var(--ease-soft);
  will-change:transform, opacity;
}

.copyright{
  position:fixed;
  bottom:calc(22px + env(safe-area-inset-bottom, 0px));
  right:calc(28px + env(safe-area-inset-right, 0px)); /* igual que .wrap */
  z-index:200;
  font-size:var(--fs-label); letter-spacing:.06em;
  color:#a0a0a0;
  mix-blend-mode:difference;
}

/* ---------- BOTÓN DE MENÚ (solo móvil) ----------
   Una circunferencia del alto de la letra del nombre. Vacía en reposo y
   rellena al abrir. Lleva el mismo invertir que el nombre, así que se ve
   sobre cualquier imagen sin tener que medir nada. */
.nav__burbuja{
  display:none; /* se enciende en el tramo de móvil */
  position:fixed;
  z-index:310;
  padding:0;
  width:calc(var(--fs-brand-real) * .62);
  height:calc(var(--fs-brand-real) * .62);
  border:1.5px solid #fff;
  border-radius:50%;
  background:transparent;
  color:#fff;
  mix-blend-mode:difference;
  transition:background .3s var(--ease-soft);
}
body.is-menu-open .nav__burbuja{ background:#fff }

/* ---------- PANEL DE SECCIONES (solo móvil) ----------
   Baja desde arriba, como el de contacto sube desde abajo, y ocupa solo lo
   que necesita. Mismo oscuro y mismos tokens de color que aquél. */
.menu-panel{
  position:fixed; left:0; right:0; top:0;
  z-index:300;
  --ink:#F2F2F2;
  --ink-mid:#A8A8A8;
  background:#0D0D0D;
  color:var(--ink);
  padding:calc(var(--cabecera) + 18px) 28px clamp(30px,5vh,46px);
  transform:translate3d(0,-100%,0);
  /* CERRADO = INVISIBLE, no solo desplazado: en iOS los fijos se colocan
     contra la ventana CON las barras puestas y el panel asomaba al
     esconderse éstas. El cambio de visibilidad espera a que acabe la salida. */
  visibility:hidden;
  transition:transform .6s var(--ease), visibility 0s linear .6s;
}
body.is-menu-open .menu-panel{
  transform:translate3d(0,0,0);
  visibility:visible;
  transition:transform .6s var(--ease), visibility 0s;
}
.menu-panel__links{
  display:flex; flex-direction:column;
  gap:clamp(10px,1.8vh,18px);
  text-align:left;
}
.menu-panel__links a{
  font-size:var(--fs-copy);
  font-weight:500;
  text-transform:uppercase;
  letter-spacing:.04em;
  color:var(--ink);
}
.menu-panel__links a.is-current{ color:var(--ink-mid) }

@media (max-width:560px){
  /* sin espacio para el centrado absoluto: se apilan. La separación se
     calcula a partir de --fs-brand para que, si se cambia el tamaño del
     nombre, el menú baje solo y no se solapen nunca. */
  /* más abajo para dejar libre la isla dinámica o la muesca de la cámara.
     El env() suma el hueco real que declare el dispositivo si algún día se
     usa viewport-fit=cover; sin él vale 0 y no estorba. */
  /* ya no hay menú debajo del nombre: la cabecera es solo el nombre y su aire */
  :root{
    --fs-brand: 22px;
    --cabecera:calc(12px + env(safe-area-inset-top, 0px) + var(--fs-brand-real) * 1.34 + 26px);
  }
  /* 28px, los mismos que el padding de .wrap: así el nombre y el menú caen
     en la misma vertical que los títulos y textos de las páginas */
  .nav__brand{ top:calc(12px + env(safe-area-inset-top, 0px)); left:28px }
  /* en móvil el menú no va desplegado: se abre desde el botón */
  .nav__links{ display:none }
  .nav__burbuja{
    display:block;
    top:calc(12px + env(safe-area-inset-top, 0px) + var(--fs-brand-real) * .34);
    right:calc(28px + env(safe-area-inset-right, 0px));
  }
}

/* ---------- RÁFAGA DE TRABAJOS AL PASAR POR EL NOMBRE ----------
   Capa a pantalla completa por debajo de la cabecera (z-index 150 < 200):
   así el nombre y el menú siguen encima y, con su mix-blend-mode, se
   invierten también contra las imágenes de la ráfaga. */
.brand-reel{
  position:fixed; inset:0;
  z-index:150;
  overflow:hidden;
  background:#0d0d0d;
  opacity:0;
  visibility:hidden;
  pointer-events:none; /* no debe robarle el hover al nombre */
  transition:opacity .28s var(--ease-soft), visibility .28s;
}
.brand-reel.is-on{ opacity:1; visibility:visible }
/* Cada fotograma es un grupo, no una imagen suelta: así una apaisada puede
   llenar la pantalla ella sola y, en cambio, las verticales y cuadradas
   salen de dos o tres en dos o tres, que a pantalla completa dejarían medio
   lienzo negro. */
.brand-reel__frame{
  position:absolute; inset:0;
  display:flex;
  opacity:0;
  /* corte seco, sin fundido: la ráfaga tiene que sentirse rápida */
}
.brand-reel__frame.is-active{ opacity:1 }
.brand-reel__frame img{
  display:block;
  object-fit:cover;
}
/* una apaisada: de borde a borde */
.brand-reel__frame--llena img{ width:100%; height:100%; }
/* GRUPO DE ALTAS, en collage y no en fila. Cada pieza lleva su sitio, su
   altura y su desfase vertical, puestos desde el JS: unas se salen por
   arriba, otras no llegan abajo y algunas se montan sobre la siguiente. El
   max-width evita que una cuadrada, mucho más ancha que una vertical a la
   misma altura, se coma el fotograma entero. */
.brand-reel__frame--grupo{ display:block }
.brand-reel__frame--grupo img{
  position:absolute;
  left:var(--x); top:var(--y);
  height:var(--h);
  width:auto;
  /* el tope solo lo llega a tocar una cuadrada, que a la misma altura es casi
     el doble de ancha que una vertical y se comería el fotograma */
  max-width:54%;
}
/* algunos fotogramas sobre claro, para que la ráfaga no sea toda negra */
.brand-reel__frame--claro{ background:#f1f1f1 }

/* ---------- PANEL DE CONTACTO ----------
   Sube desde abajo en vez de llevar a otra página. Del color del fondo de
   la web, y por encima de todo menos del cursor. */
.contact-panel{
  position:fixed; left:0; right:0; bottom:0;
  z-index:300;
  /* oscuro igual que la página de contacto. Los tokens se redefinen aquí
     dentro porque el panel sube sobre CUALQUIER página, también las claras:
     no puede heredar el color del fondo que tenga detrás. */
  --ink:#F2F2F2;
  --ink-mid:#A8A8A8;
  background:#0D0D0D;
  color:var(--ink);
  /* +6px: con solo el 100% justo, en iOS quedaba asomando una franja por
     redondeo de subpíxeles */
  transform:translate3d(0,calc(100% + 6px),0);
  transition:transform .72s var(--ease);
  will-change:transform;
  /* con viewport-fit=cover el panel llega al borde: hay que dejar libre el
     hueco del indicador de inicio */
  padding-bottom:env(safe-area-inset-bottom, 0px);
  /* CERRADO = INVISIBLE, no solo desplazado. En iOS los elementos fijos se
     colocan contra la ventana CON las barras del navegador puestas, así que
     el panel quedaba aparcado justo en la franja que tapa la barra; al
     esconderse ésta al hacer scroll, la franja se veía y con ella el panel.
     La visibilidad cambia de golpe al final de la animación de salida, así
     que el deslizamiento se sigue viendo igual. */
  visibility:hidden;
  transition:transform .72s var(--ease), visibility 0s linear .72s;
}
body.is-contact-open .contact-panel{
  transform:translate3d(0,0,0);
  visibility:visible;
  transition:transform .72s var(--ease), visibility 0s;
}
.contact-panel__inner{
  display:flex;
  justify-content:space-between;
  align-items:flex-start;
  gap:clamp(28px,6vw,120px);
  padding:clamp(38px,6vh,64px) 28px clamp(46px,7vh,72px);
}
.contact-panel__social{
  display:flex; flex-direction:column; gap:6px;
  font-size:var(--fs-copy);
  font-weight:500;
  letter-spacing:-.012em;
}
.contact-panel__mail{
  text-align:right;
  font-size:var(--fs-copy);
  font-weight:500;
  letter-spacing:-.012em;
}
.contact-panel__hi{ color:var(--ink-mid) }
.contact-panel__social a,
.contact-panel__mail a{
  position:relative;
  transition:opacity .35s var(--ease-soft);
}
.contact-panel__social a::after,
.contact-panel__mail a::after{
  content:''; position:absolute; left:0; right:0; bottom:-.06em;
  height:1px; background:currentColor;
  transform:scaleX(0); transform-origin:left;
  transition:transform .45s var(--ease);
}
.contact-panel__social a:hover::after,
.contact-panel__mail a:hover::after{ transform:scaleX(1) }

/* la pastillita de abajo: cierra el panel */
.contact-panel__close{
  display:block;
  width:46px; height:5px;
  margin:0 auto 12px;
  padding:0; border:0;
  border-radius:99px;
  background:var(--ink);
  opacity:.28;
  transition:opacity .3s var(--ease-soft), width .3s var(--ease);
}
.contact-panel__close:hover{ opacity:.6; width:58px }
@media (any-hover:hover) and (any-pointer:fine){
  .contact-panel__close{ cursor:none }
}

/* con el panel abierto, el © 2026 quedaría tapado a medias */
body.is-contact-open .copyright{ opacity:0 }

@media (max-width:700px){
  .contact-panel__inner{
    flex-direction:column;
    align-items:flex-start;
    gap:clamp(18px,3.4vh,28px);
    padding:clamp(26px,4vh,40px) 28px clamp(30px,4.6vh,44px);
  }
  .contact-panel__mail{ text-align:left }
  /* baja un nivel entero de la escala, a --fs-body, en vez de a un tamaño
     inventado: a --fs-copy el correo son 30 caracteres que no caben de ancho
     en un móvil y se cortaba la última letra. */
  .contact-panel__social,
  .contact-panel__mail{ font-size:var(--fs-body) }
}

/* ---------- 4. LANDING ---------- */
/* dvh, no vh: en Safari 100vh mide la ventana con las barras del navegador
   escondidas, así que el contenido no encajaba con lo que se ve. dvh sigue
   el alto real. El vh queda antes como respaldo para navegadores viejos. */
/* fondo oscuro también en el lienzo: en Safari, con las barras del navegador
   entrando y saliendo, cualquier franja que asome lo hace en negro y no en
   el gris claro del resto del sitio, que se veía como un borde blanco */
.page-home,
body.page-reel{ background:#0D0D0D }
.page-home{ height:100vh; height:100dvh; overflow:hidden }

.stage{
  position:relative;
  height:100vh;
  height:100dvh;
}

/* Capa clicable de la portada: cubre la pantalla por debajo del texto. Es
   hermana suya, no su envoltorio, para no encerrarle el mix-blend-mode. */
.hero-link{
  position:fixed; inset:0;
  z-index:2;
}

.hero-welcome{
  /* fija y sin envoltorio con z-index: cualquier ancestro que cree contexto
     de apilamiento encerraría su mix-blend-mode y el invertir no llegaría a
     la imagen */
  position:fixed;
  z-index:2;
  /* arranca en el centro exacto, alineada con "About" del menú */
  left:50%;
  top:50%;
  translate:0 -50%;
  max-width:calc(50% - 56px);
  pointer-events:none; /* que no estorbe al paso de imágenes de debajo */
  text-align:left;
  /* derivada de --fs-copy en vez de un clamp aparte: así guarda SIEMPRE la
     misma proporción con las descripciones (36/31 en escritorio), también en
     móvil, donde antes se quedaba relativamente más pequeña */
  font-size:calc(var(--fs-copy) * 1.08);
  font-weight:450;
  line-height:1.2;
  letter-spacing:-.014em;
  /* la descripción también se invierte sobre la imagen */
  color:#fff;
  mix-blend-mode:difference;
}
/* en escritorio va anclada al cursor: JS escribe --lx/--ly cada frame */
.stage.sigue-cursor .hero-welcome{
  left:0;
  top:0;
  translate:none;
  max-width:min(46vw, 760px);
  transform:translate3d(var(--lx,0px), var(--ly,0px), 0) translateY(-50%);
}

/* las imágenes, a pantalla completa. La capa usa el viewport GRANDE de
   Safari (lvh), se centra respecto al viewport visible y queda transparente.
   Así siempre sobra imagen arriba y abajo cuando aparecen las barras del
   navegador, sin estirar ni deformar la fotografía. */
.hero-visual{
  position:fixed;
  top:50%; right:0; bottom:auto; left:0;
  width:auto;
  height:100vh;  /* respaldo para navegadores antiguos */
  height:100lvh; /* altura máxima disponible, con las barras recogidas */
  transform:translateY(-50%);
  z-index:1;
  overflow:hidden;
  background:transparent;
}
/* Igual que en la ráfaga: el fotograma es un grupo. Casi todos llevan una
   sola apaisada a pantalla completa, y uno de ellos varias verticales de
   Inner Noise en fila sobre negro. */
.hero-frame{
  position:absolute; inset:0;
  display:flex;
  opacity:0;
  transition:opacity 1.1s var(--ease-soft);
}
.hero-frame.is-active{ opacity:1 }
.hero-frame img{
  display:block;
  object-fit:cover;
  filter:saturate(1.05) contrast(1.02);
}
.hero-frame:not(.hero-frame--collage) img{
  position:absolute; inset:0;
  width:100%; height:100%;
}
/* el collage: fondo negro propio y las piezas desordenadas a propósito, cada
   una con su altura y su desfase, como el collage de la ráfaga */
.hero-frame--collage{
  background:#0d0d0d;
  display:block;
  overflow:hidden;
}
/* En móvil el collage se deshace y cada pieza va en su propio fotograma. Se
   ve ENTERA, con contain, y no recortada: una vertical a pantalla completa en
   un móvil perdería 85px por cada lado. El negro es el que rellena lo que
   sobra por arriba y por abajo. */
.hero-frame--suelta{ background:#0d0d0d }
.hero-frame--suelta img{ object-fit:contain }
/* salvo las que, enteras, dejarían demasiado negro alrededor: esas llenan el
   fotograma aunque haya que recortarlas. Lo marca el JS midiendo cuánto
   cubre cada una. */
.hero-frame--llenar img{ object-fit:cover }
/* girada 90° en móvil (la marca el JS): ancho y alto se intercambian para
   que, una vez girada, cubra la pantalla vertical entera */
.hero-frame.hero-frame--girada img{
  inset:auto;
  top:50%; left:50%;
  width:100vh;  width:100lvh;
  height:100vw;
  /* el reset global pone max-width:100% a toda img y, sin anularlo, el ancho
     se queda en el del móvil y la girada no llega a cubrir el alto */
  max-width:none;
  object-fit:cover;
  transform:translate(-50%,-50%) rotate(90deg);
}

.hero-frame--collage img{
  position:absolute;
  left:var(--x); top:var(--y);
  height:var(--h);
  width:auto;
  /* el tope solo lo toca una cuadrada, que a la misma altura es casi el doble
     de ancha que una vertical y se comería el fotograma */
  max-width:54%;
}

/* (el efecto de invertir se declara en cada elemento, junto a su color) */

/* ---------- 5. TRANSICIÓN "REARME" ENTRE PÁGINAS ----------
   #app es lo único que se sustituye al navegar: se desarma (fade +
   ligero desplazamiento) y se vuelve a armar con el contenido nuevo,
   sin salto de página ni recarga. */
#app{
  transition:opacity .32s var(--ease-soft), transform .32s var(--ease-soft);
}
#app.is-transitioning{
  opacity:0;
  transform:translateY(10px);
}

/* ---------- 6. SUBPÁGINAS ---------- */
.scroll{
  position:fixed; top:0; left:0; width:100%;
  will-change:transform;
  backface-visibility:hidden;
}
.no-smooth .scroll{ position:static; transform:none !important }

.wrap{ padding:0 28px }

.intro{ padding:clamp(120px,17vh,190px) 0 0 }
/* contacto arranca algo más arriba para caber en una pantalla */
body[data-page="contact"] .intro{ padding-top:clamp(96px,15vh,150px) }
.statement{
  font-size:var(--fs-display);
  font-weight:600;
  line-height:1.02;
  letter-spacing:-.032em;
  text-transform:uppercase;
  max-width:20ch;
}
.statement--wide{ max-width:none }
.statement em{ font-style:normal; color:var(--ink-soft) }

/* ---------- REVELADO DE ENTRADA (about.html) ----------
   .about-hero vive fuera de #scroll a propósito: si estuviera dentro,
   heredaría el transform del scroll suave y no podría desvanecerse de
   forma independiente mientras el título flota hacia arriba debajo. Su
   opacidad y la posición/rotación de cada palabra las controla JS cada
   frame en función del scroll -> el efecto es reversible sin más: subir
   y bajar simplemente recalcula el mismo progreso. */
.about-hero{
  /* MINIATURA INCRUSTADA (1,1 KB, 28x28 px) como fondo del contenedor, por
     debajo de las dos capas. Se ve desde el primer instante y ya no se mueve:
     el retrato entra encima con un único fundido. Antes vivía en la capa
     desenfocada y ésta la sustituía de golpe por la imagen real al llegar, y
     ese cambio brusco era el tirón que se veía.
     Va girada 180 grados en el propio archivo, no por CSS: el transform del
     contenedor lo maneja el JS y se pisarían. */
  background-image:url("data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBMRXhpZgAATU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAHKADAAQAAAABAAAAHAAAAAD/wgARCAAcABwDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAwIEAQUABgcICQoL/8QAwxAAAQMDAgQDBAYEBwYECAZzAQIAAxEEEiEFMRMiEAZBUTIUYXEjB4EgkUIVoVIzsSRiMBbBctFDkjSCCOFTQCVjFzXwk3OiUESyg/EmVDZklHTCYNKEoxhw4idFN2WzVXWklcOF8tNGdoDjR1ZmtAkKGRooKSo4OTpISUpXWFlaZ2hpand4eXqGh4iJipCWl5iZmqClpqeoqaqwtba3uLm6wMTFxsfIycrQ1NXW19jZ2uDk5ebn6Onq8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAABAgADBAUGBwgJCgv/xADDEQACAgEDAwMCAwUCBQIEBIcBAAIRAxASIQQgMUETBTAiMlEUQAYzI2FCFXFSNIFQJJGhQ7EWB2I1U/DRJWDBROFy8ReCYzZwJkVUkiei0ggJChgZGigpKjc4OTpGR0hJSlVWV1hZWmRlZmdoaWpzdHV2d3h5eoCDhIWGh4iJipCTlJWWl5iZmqCjpKWmp6ipqrCys7S1tre4ubrAwsPExcbHyMnK0NPU1dbX2Nna4OLj5OXm5+jp6vLz9PX29/j5+v/bAEMACgoKCgoKEgoKEhkSEhIZIhkZGRkiKyIiIiIiKzQrKysrKys0NDQ0NDQ0ND4+Pj4+PklJSUlJUVFRUVFRUVFRUf/bAEMBDQ0NFRMVJBMTJFU6LzpVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/aAAwDAQACEQMRAAABp23pHmpF31tD1NVnnfp6mFD0iFKf/9oACAEBAAEFAlxSROritJZHHEmJCykIOr21RPa4QZIepJ25OMfag7//2gAIAQMRAT8BGgOn/9oACAECEQE/AdCNP//aAAgBAQAGPwL6QU7a9IeAZK+D0a0nXspCeJdFMkgip+9//8QAMxABAAMAAgICAgIDAQEAAAILAREAITFBUWFxgZGhscHw0RDh8SAwQFBgcICQoLDA0OD/2gAIAQEAAT8hUhPbqx6Kpj2NOdPflvBnJZoH0KMh0Iu/84ejLMGZ4it5YDs/4WTUJ/7/AP/aAAwDAQACEQMRAAAQzLpi/8QAMxEBAQEAAwABAgUFAQEAAQEJAQARITEQQVFhIHHwkYGhsdHB4fEwQFBgcICQoLDA0OD/2gAIAQMRAT8QGyWFt//aAAgBAhEBPxBct8Mv/9oACAEBAAE/EGoQ+S+EyswIyhP/AJdpyQnH4O6UomlBKcrYZFQiSD1VLOdQ7CcPqxppIfITbF+ZwolEY+4smUdI0fivJARKAGRPhmaNTVA0HFCT7orS/wD/2Q==");
  background-size:cover;
  background-position:center;
  position:fixed;
  top:50%; right:0; bottom:auto; left:0;
  /* Igual que la landing: la imagen ocupa el viewport máximo y se centra
     para que el exceso quede repartido por arriba y por abajo. */
  width:auto;
  height:100vh;
  height:100lvh;
  transform:translateY(-50%);
  /* 0, no 50: el retrato se queda por DEBAJO del texto, que ahora se lee
     encima en vez de aparecer cuando la foto se desvanece */
  z-index:0;
  overflow:hidden;
  /* transparente para que Safari no extienda un color plano sobre la foto
     en la zona de la barra superior. El body conserva el color de respaldo
     mientras carga la imagen. */
  background-color:transparent; /* -color y no la abreviada: ésta borraría
     la miniatura incrustada de arriba */
  pointer-events:none;
}
.about-hero__media{
  /* NACE YA AMPLIADA, al 109% (4,5% de más por cada lado, = HERO_ZOOM), y el
     JS la encoge a 1/1,09 de partida. Así el acercamiento del scroll va de
     ~0,92 a 1 y nunca pasa del tamaño al que se dibujó. Antes iba de 1 a
     1,09: Safari, cada vez que una capa crece, la vuelve a dibujar a más
     resolución -con el desenfoque incluido-, y eso eran los tirones al
     bajar. Al subir no los había porque encoger no obliga a redibujar. */
  position:absolute;
  top:-4.5%; right:-4.5%; bottom:-4.5%; left:-4.5%;
  transform:scale(.9174);
  /* solo se animan transform y opacity, que las lleva el compositor sin
     repintar; el acercamiento evita además que el desenfoque deje un borde
     translúcido al recortarse contra overflow:hidden */
  will-change:transform, opacity;
}
/* el radio lo fija JS desde HERO_BLUR, para que siga habiendo un único
   sitio donde tocarlo. Se rasteriza una vez, no en cada frame. */
.about-hero__media--blur{ filter:blur(var(--hero-blur, 26px)) }
/* EN MÓVIL, LA FOTO QUIETA DEL TODO.
   - Sin acercamiento (el JS tampoco lo aplica, ver heroEscala): la nítida va
     a su tamaño y la desenfocada se queda algo más grande, para que su borde
     difuminado caiga fuera de la pantalla.
   - Anclada ARRIBA y no centrada. Centrada (top:50% + translateY(-50%)),
     cuando Safari esconde su barra al bajar la pantalla crece y el centro
     baja con ella: la foto se desplazaba hacia abajo en cada frame de la
     animación de la barra, y eso eran los tirones. Arriba y con 100lvh (el
     alto con la barra escondida) la cubre siempre sin moverse. */
@media (max-width:900px){
  .about-hero{ top:0; transform:none }
  .about-hero__media--sharp{ top:0; right:0; bottom:0; left:0; transform:none }
  .about-hero__media--blur{ transform:none }
}
/* la nítida va encima y es la que se atenúa al hacer scroll */
.about-hero__media--sharp{ opacity:1 }
/* El retrato es un JPEG PROGRESIVO: el navegador lo pinta entero en baja
   calidad y lo va afinando por pasadas, y cada pasada se ve como un salto.
   Aquí la copia nítida se mantiene invisible hasta que la imagen está
   descargada Y descodificada del todo, y entonces entra con un fundido. Lo
   que se ve mientras tanto es la capa de abajo, que va desenfocada y por eso
   disimula las pasadas.
   La opacidad va en el <img> y no en .about-hero__media--sharp porque la del
   contenedor la escribe el JS en cada frame del scroll y se pisarían. */
.about-hero__media img{
  opacity:0;
  transition:opacity .75s var(--ease-soft);
}
.about-hero.retrato-listo .about-hero__media img{ opacity:1 }
.about-hero__media img{
  width:100%; height:100%; object-fit:cover;
  filter:saturate(1.05) contrast(1.02);
  /* el retrato va del revés, con la cabeza hacia abajo. Va aquí, en el
     <img>, y no en .about-hero__media, porque el transform del contenedor
     lo maneja JS (el acercamiento) y se pisarían. */
  transform:rotate(180deg);
}
/* velo del color de la web: sube de opacidad con el scroll hasta dejar el
   texto legible sin llegar a tapar el retrato */
.about-hero__veil{
  position:absolute; inset:0;
  background:var(--bg);
  opacity:0;
}
/* el recorrido de scroll de About: no hay nada que ver en él, solo da
   distancia para aclarar el retrato y escribir el texto. Cuanto más alto,
   más scroll hace falta para escribirlo entero. */
.about-hero__spacer{ height:340vh; height:340lvh }

/* la capa del texto: fija y a pantalla completa, por encima del retrato.
   El texto está siempre en su sitio final; el scroll no la mueve. */
.about-capa{
  position:fixed; inset:0;
  z-index:1;
  overflow:hidden;
}

/* About: el texto se escribe letra a letra. La palabra no se parte entre
   renglones; cada letra va sola para poder condensarse por su cuenta. */
.reveal-palabra{
  /* flex con wrap y tope de ancho: una palabra normal no se parte nunca,
     pero una que no cabe en el renglón (el "Hellooooo…" en un móvil) baja
     a la línea siguiente por dentro en vez de ensanchar la página */
  display:inline-flex;
  flex-wrap:wrap;
  max-width:100%;
}
/* las dos últimas palabras de cada párrafo, juntas: nunca queda una sola
   en el último renglón */
.reveal-cola{ white-space:nowrap }
.reveal-letra{
  display:inline-block;
  opacity:0; /* estado inicial antes de que el primer frame lo posicione */
}
.about-text__foot{ opacity:0 }

.about-lede{
  margin-top:18px;
  font-size:var(--fs-body);
  color:var(--ink-soft);
  max-width:36ch;
}

/* ---------- ABOUT: PANTALLA DE TEXTO ----------
   el texto ocupa el ancho completo por encima del retrato desenfocado, y
   mide exactamente 100vh: como no hay nada detrás, la página son dos
   pantallas justas y el scroll se detiene con todo encuadrado. Para que no
   se corte nada, el cuerpo se dimensiona también contra la ALTURA de la
   ventana (min(vw,vh)), no solo contra el ancho. */
.about-text{
  /* la medida del bloque de texto: se extiende por la página pero deja
     respiro por la derecha, sin llegar al límite */
  /* el tope en px es para monitores grandes: solo con el 90% las líneas se
     iban a 140 caracteres y no había forma de seguirlas */
  --about-medida:min(90%, 1500px);
  height:100vh; height:100dvh;
  display:flex; flex-direction:column;
  /* arriba, hueco para la cabecera fija; abajo, para el © 2026 */
  padding:clamp(96px,14vh,150px) 0 clamp(46px,6vh,64px);
}
/* título y descripción comparten el mismo borde derecho: el texto se
   extiende por la página dejando un margen a la derecha, en vez de cortarse
   a media pantalla y dejar un hueco vacío */
.about-text .statement{
  max-width:var(--about-medida);
  /* --about-escala la pone el JS solo si el texto no cabe en la pantalla
     (en móvil): reduce título y párrafos a la vez hasta que entra entero */
  font-size:calc(var(--fs-display) * var(--about-escala, 1));
  /* aquí no en mayúsculas: se lee tal como está escrito en el HTML */
  text-transform:none;
}
.about-text .about-lede{
  margin-top:clamp(12px,1.8vh,22px);
  font-size:var(--fs-body);
  max-width:52ch;
  /* el gris de --ink-soft se pierde por completo sobre las zonas oscuras
     del retrato (medido: 1,14:1); este tono mantiene la jerarquía frente
     al cuerpo de texto y se sigue leyendo encima de la foto */
  color:#3D3D3D;
}

/* el menú y el © 2026 ya no necesitan excepción aquí: se invierten solos
   sobre el retrato */
.about-bio{ margin-top:clamp(26px,4.6vh,58px) }
.about-text .body-copy{
  /* grande y cómodo de leer, por debajo del título pero muy por encima de
     un cuerpo de texto normal. Como antes, se dimensiona también contra la
     ALTURA de la ventana para que quepa en una pantalla baja. */
  /* el tope mantiene la jerarquía frente al título (62px): sin él, en un
     monitor grande el cuerpo se acercaba demasiado al titular */
  font-size:calc(var(--fs-copy) * var(--about-escala, 1));
  font-weight:450;
  line-height:1.3;
  letter-spacing:-.015em;
  max-width:var(--about-medida);
}
.about-text .body-copy + .body-copy{ margin-top:.75em }
.about-text__foot{
  margin-top:auto;
  padding-top:clamp(16px,2.4vh,30px);
  font-size:var(--fs-small);
  letter-spacing:.02em;
}

/* en móvil, una sola columna con tipografía de lectura. Puede medir algo
   más que la pantalla: lo que sobra por abajo lo sube el JS con el scroll */
@media (max-width:900px){
  /* lo que sube el JS: en su propia capa, para que moverla no obligue a
     repintar el texto */
  .about-capa .wrap{ will-change:transform }
  /* en móvil, más recorrido: el texto es más largo y cada gesto escribe
     menos, así se acompaña mejor con el dedo */
  .about-hero__spacer{ height:460vh; height:460lvh }
  .about-text{
    height:auto;
    /* lvh y svh no cambian al esconderse la barra del navegador: con dvh, cada
       vez que se movía la barra se recolocaba todo el texto en pleno scroll */
    min-height:100vh; min-height:100lvh;
    /* el texto arranca bastante más abajo que en el resto de páginas: así
       hay sitio para escribir varias líneas antes de que tenga que subir.
       Suma las zonas seguras del iPhone. */
    padding:
      calc(clamp(170px,30svh,280px) + env(safe-area-inset-top, 0px))
      0
      calc(clamp(60px,9vh,90px) + env(safe-area-inset-bottom, 0px));
  }
  .about-text{ --about-medida:100% }
  .about-text .body-copy{
    max-width:none;
    font-size:calc(19px * var(--about-escala, 1));
    font-weight:400;
    line-height:1.42;
    letter-spacing:-.01em;
  }
  .about-text__foot{ margin-top:clamp(40px,7vh,64px) }
}

/* imágenes, párrafos y listas que van entrando conforme haces scroll:
   opacidad + un pequeño ascenso, con un leve desfase entre ellos (fijado
   por JS via transition-delay) para que la página se sienta "armándose" */
/* Utilidad de entrada: opacidad más un leve ascenso al asomar. Ahora mismo
   NO la usa nadie: las fotos del collage la llevaban y se quitó porque, al ir
   seguidas y a todo el ancho, el ascenso se notaba como un tirón constante al
   bajar en vez de como una aparición. Se deja montada por si vuelve a hacer
   falta: basta con añadir la clase reveal-up a un elemento. */
.reveal-up{
  opacity:0;
  transform:translateY(56px);
  transition:opacity 1.05s var(--ease-soft), transform 1.05s var(--ease-soft);
}
.reveal-up.is-visible{
  opacity:1;
  transform:translateY(0);
}

.meta-row{
  display:grid;
  grid-template-columns:repeat(4,1fr);
  gap:20px;
  margin-top:clamp(34px,5vh,64px);
  padding-bottom:18px;
}
.meta-row > div:last-child{ text-align:right }
.meta-row dt, .meta-label{
  display:block;
  /* el contraste de estas etiquetas viene del tracking y el peso, no del
     tamaño: por eso van más pequeñas que --fs-label pero más abiertas */
  font-size:10px; letter-spacing:.14em; font-weight:500;
  text-transform:uppercase;
  color:var(--ink-soft); margin-bottom:6px;
}
.meta-value{ font-size:var(--fs-small); letter-spacing:.04em; font-weight:400; text-transform:uppercase; color:var(--ink) }
@media (max-width:700px){
  .meta-row{grid-template-columns:repeat(2,1fr)}
  .meta-row > div:last-child{text-align:left}
}

/* rejilla de imágenes / trabajos */
.grid{
  display:grid;
  grid-template-columns:repeat(2,1fr);
  gap:14px;
  margin-top:14px;
}
.grid--three{ grid-template-columns:repeat(3,1fr) }
@media (max-width:780px){ .grid,.grid--three{grid-template-columns:1fr} }

.tile{ display:block }
.tile__media{
  display:block; /* es un <span>: sin esto, aspect-ratio no tiene efecto
                     en una caja inline y la altura la marca la imagen */
  position:relative;
  overflow:hidden;
  border-radius:4px;
  background:#111;
  aspect-ratio:1 / 1;
}
.tile__media--tall{ aspect-ratio:3 / 4 }
.tile__media img{
  width:100%; height:100%; object-fit:cover;
  transform:scale(1.01);
  transition:transform 1.4s var(--ease), filter 1.1s var(--ease-soft);
}
/* al activarse: la imagen se acerca y se desenfoca lo justo para que el
   nombre en blanco se lea sin pelearse con la foto. El zoom (1.08) también
   evita que se vea el borde translúcido que deja el blur al recortarse
   contra overflow:hidden. .is-active la pone initTileReveal() en móvil. */
.tile:hover .tile__media img,
.tile.is-active .tile__media img{
  transform:scale(1.08);
  filter:blur(13px) brightness(.62) saturate(.9);
}
.tile__title{
  position:absolute; inset:0;
  display:flex; align-items:center; justify-content:center;
  padding:0 clamp(18px,4vw,40px);
  text-align:center;
  color:#fff;
  font-size:var(--fs-copy);
  font-weight:500;
  line-height:1.15;
  letter-spacing:-.02em;
  text-wrap:balance;
  opacity:0;
  transform:translateY(6px);
  transition:opacity .7s var(--ease-soft), transform .7s var(--ease-soft);
  pointer-events:none; /* el clic es del <a>, y así no rompe data-cursor */
}
.tile:hover .tile__title,
.tile.is-active .tile__title{ opacity:1; transform:translateY(0) }

/* ---------- WORKS: CARRUSEL A PANTALLA COMPLETA ----------
   Cada proyecto ocupa la pantalla con su propia portada. Sin scroll: la
   página mide justo una pantalla, como la landing. */
/* .page-reel, no [data-page="works"]: ese atributo lo llevan también las
   páginas de proyecto para resaltar Works en el menú, y con el selector
   anterior se quedaban sin scroll -> solo se veía la portada. */
body.page-reel{ height:100vh; height:100dvh; overflow:hidden }

.work-stage{
  /* fija y a 100lvh por lo mismo que la landing: las portadas tienen que
     llegar a los bordes de la pantalla, también detrás de las barras */
  position:fixed; left:0; top:0; width:100%;
  height:100vh;
  height:100lvh;
  overflow:hidden; /* recorta las diapositivas aparcadas fuera */
  /* pan-y: los gestos verticales siguen siendo del navegador (esconder la
     barra de direcciones) y los horizontales llegan al carrusel */
  touch-action:pan-y;
}
.work-track{ position:absolute; inset:0 }

/* aparcadas fuera por la derecha; el barrido lo dispara initWorks()
   moviendo la que sale y la que entra en el sentido de la flecha pulsada */
.work-slide{
  position:absolute; inset:0;
  display:block;
  overflow:hidden;
  background:#111;
  transform:translate3d(100%,0,0);
  transition:transform .95s var(--ease);
  will-change:transform;
}
.work-slide.is-current{ transform:translate3d(0,0,0) }
.work-slide__media{ position:absolute; inset:0 }
.work-slide__media img,
.work-slide__media video{
  width:100%; height:100%; object-fit:cover; display:block;
  filter:saturate(1.05) contrast(1.02);
}

/* el nombre del proyecto: empieza en el centro exacto y ocupa la mitad
   derecha, como la descripción de la landing */
/* EL RÓTULO: nombre + año, en la misma línea. El invertir va AQUÍ, en el
   contenedor, y no en el título: al seguir al cursor necesita un transform
   propio, y un transform sobre el padre crearía un contexto de apilamiento
   que encerraría la mezcla del hijo y la dejaría sin efecto. */
.work-slide__label{
  position:absolute;
  left:50%;
  top:50%;
  top:50dvh; /* centrado en lo que se ve, no en la pantalla completa */
  translate:0 -50%;
  width:max-content;
  max-width:calc(50% - clamp(28px,4vw,90px));
  display:flex;
  align-items:baseline;
  gap:clamp(10px,1vw,20px);
  color:#fff;
  mix-blend-mode:difference;
  /* sin z-index: con él crearía contexto de apilamiento y encerraría la
     mezcla. El orden del DOM ya lo coloca por encima de la portada. */
  pointer-events:none;
}
/* en escritorio va anclado al cursor: JS escribe --lx/--ly cada frame */
.work-stage.sigue-cursor .work-slide__label{
  left:0;
  top:0;
  translate:none;
  max-width:min(46vw, 760px);
  transform:translate3d(var(--lx,0px), var(--ly,0px), 0) translateY(-50%);
}
/* el título: quieto, siempre visible y sin nada debajo. Lo único que lo hace
   legible es el invertir, igual que el nombre y el menú. */
.work-slide__title{
  position:relative;
  /* algo por debajo de --fs-display: funciona como rótulo editorial sobre
     la imagen, no como un titular de página */
  font-size:clamp(28px, 3.8vw, 58px);
  font-weight:600;
  line-height:1;
  letter-spacing:-.032em;
}

/* las flechas */
.work-arrow{
  position:absolute;
  top:50%;
  top:50dvh; /* igual que el nombre: centradas en lo que se ve */
  translate:0 -50%;
  z-index:5;
  width:42px; height:42px;
  display:grid; place-items:center;
  padding:0; border:0; background:none;
  color:#e8e8e8;
  opacity:.7;
  transition:opacity .3s var(--ease-soft), transform .45s var(--ease);
  mix-blend-mode:difference;
}
.work-arrow:hover{ opacity:1 }
.work-arrow--prev{ left:clamp(10px,1.6vw,26px) }
.work-arrow--next{ right:clamp(10px,1.6vw,26px) }
.work-arrow--prev:hover{ transform:translateX(-4px) }
.work-arrow--next:hover{ transform:translateX(4px) }
.work-arrow svg{
  width:24px; height:24px;
  fill:none; stroke:currentColor; stroke-width:1.5;
  stroke-linecap:round; stroke-linejoin:round;
}
@media (any-hover:hover) and (any-pointer:fine){
  .work-arrow{ cursor:none }
}

@media (max-width:760px){
  /* a lo ancho de un móvil la mitad derecha no da para un título grande */
  /* centrado: a este ancho, alineado a la izquierda quedaba descolgado. Y el
     año debajo del nombre, no al lado. */
  .work-slide__label{
    left:50%;
    translate:-50% -50%;
    max-width:calc(100% - 2*clamp(24px,6vw,40px));
    text-align:center;
    flex-direction:column;
    align-items:center;
    gap:6px;
  }
}

/* bloques de texto */
.section{ padding:clamp(70px,11vh,140px) 0 0 }
.section__head{
  font-size:var(--fs-label); letter-spacing:.12em; text-transform:uppercase;
  color:var(--ink-soft); margin-bottom:22px;
}
.cols{
  display:grid; grid-template-columns:1fr 1fr; gap:clamp(28px,5vw,90px);
}
@media (max-width:820px){ .cols{grid-template-columns:1fr} }

.body-copy{
  font-size:var(--fs-body);
  line-height:1.5;
  letter-spacing:-.011em;
  max-width:46ch;
}
.body-copy + .body-copy{ margin-top:1.1em }

.list{ border-top:1px solid rgba(10,10,10,.18) }
.list li{
  display:flex; justify-content:space-between; gap:20px;
  padding:13px 0;
  border-bottom:1px solid rgba(10,10,10,.18);
  font-size:var(--fs-small); letter-spacing:.02em;
}
.list li span:last-child{ color:var(--ink-mid); text-align:right }

/* ---------- PÁGINA DE PROYECTO ---------- */
/* la intro (título, meta, statement + primera imagen) ocupa una pantalla;
   a partir de ahí las imágenes pasan a ocupar todo el ancho y protagonizan */
.project-intro{
  display:flex; /* flex, no grid: su stretch por altura es fiable y así
                   los hijos (cover, meta al fondo) tienen una altura
                   definida contra la que resolver flex:1 / height:100% */
  gap:clamp(28px,5vw,90px);
  height:100vh;
  padding:clamp(120px,17vh,190px) 0 60px;
}
.project-intro__left{
  flex:1 1 0; min-width:0;
  display:flex; flex-direction:column;
}
.project-title{
  font-size:var(--fs-display);
  font-weight:600;
  line-height:1.02;
  letter-spacing:-.032em;
}
.project-lede{
  margin-top:clamp(12px,2vh,22px);
  /* secundaria respecto al título: no comparte nivel con las descripciones
     editoriales grandes */
  font-size:clamp(17px, 1.35vw, 18px);
  font-weight:400;
  line-height:1.42;
  color:var(--ink-soft);
  max-width:48ch;
}
.project-meta{
  margin-top:auto;
  /* con los rótulos a 14px el bloque crece: en una pantalla baja hay que
     apretar el aire para que la intro siga cabiendo en una pantalla */
  padding-top:clamp(24px,5vh,60px);
  display:flex; flex-direction:column; gap:clamp(14px,2.6vh,28px);
}
.project-meta__list{ margin-top:2px }
.project-meta__list li{
  font-size:var(--fs-small); letter-spacing:-.005em;
  padding:3px 0;
}
/* Services / Project type / Year, al mismo tamaño que el menú. Atados a
   --fs-nav para que si se cambia uno, cambien con él. Va DESPUÉS de las
   reglas base a propósito: misma especificidad, decide el orden. */
@media (min-width:901px){
  .project-meta .section__head{ font-size:var(--fs-nav) }
}

.project-intro__right{
  flex:1 1 0; min-width:0;
  display:flex; flex-direction:column;
}
.project-statement{
  font-size:var(--fs-body);
  line-height:1.5;
  letter-spacing:-.011em;
  max-width:34ch;
}
.project-cover{
  margin-top:clamp(28px,4vh,48px);
  position:relative;
  /* La caja toma la proporción del archivo, que cada página declara en
     --ratio. Antes se estiraba a lo que quedara de columna y salía casi
     cuadrada: con un vídeo 16:10 o uno 9:16 dentro, object-fit:cover se
     comía el 40% del fotograma. Con la proporción correcta no se recorta
     nada y se ve la pieza entera.
     flex:0 1 auto y max-height para que, si por la proporción no cupiera a
     lo alto, encoja en vez de desbordar la columna. */
  aspect-ratio:var(--ratio, 16/10);
  /* El align-self hace falta porque, por defecto, el flex estira al hijo a
     todo el ancho de la columna y la proporción dejaría de mandar. */
  align-self:flex-start;
  flex:0 0 auto;
  min-height:0;
  border-radius:4px;
  overflow:hidden;
  background:#111;
}
.project-cover img{
  width:100%; height:100%; object-fit:cover;
  transform:scale(1.01);
  transition:transform 1.4s var(--ease), filter 1.4s var(--ease);
}
.project-cover:hover img{ transform:scale(1.05) }
/* si el vídeo es lo único de la columna derecha, alinea arriba con el título */
/* Qué eje manda al encajar la portada en la columna. Hace falta elegirlo a
   mano: si se fijan los dos, el navegador ignora la proporción y vuelve a
   recortar. Un vídeo más apaisado que la columna se mide por el ancho; uno
   más vertical, por el alto. */
.project-cover--ancho{ width:100%; height:auto; max-height:100% }
/* el vertical se pega al borde derecho de la columna, que acaba justo en los
   28px de margen de la página: así guarda la misma distancia al borde que el
   texto de la izquierda. Sin esto se quedaba a media pantalla, porque el
   align-self:flex-start de la caja lo empujaba contra el centro. */
.project-cover--alto{ height:100%; width:auto; max-width:100%; align-self:flex-end }

.project-intro__right > .project-cover:first-child{ margin-top:0 }
/* el apaisado no llena la columna a lo alto, así que se centra en vertical:
   los márgenes automáticos reparten el hueco por igual arriba y abajo. Va
   después y con la misma especificidad que la regla de arriba, que si no le
   pisaría el margin-top. */
.project-intro__right > .project-cover--ancho:first-child{ margin-top:auto; margin-bottom:auto }

/* ---------- VÍDEO DEL PROYECTO (con controles al hover) ---------- */
.project-video{ background:#111 }
/* un vídeo con controles dentro del collage: la caja la marca la pieza, no
   las reglas de la portada de proyecto */
.collage__item.project-video{
  /* sin esto los controles, que van absolutos, se anclaban al contenedor de
     la página y aparecían muy a la derecha, fuera del vídeo */
  position:relative;
  background:transparent;
  border-radius:0;
  aspect-ratio:auto;
  height:auto;
  max-height:none;
  flex:0 0 auto;
  overflow:visible;
}
.project-video__el{
  width:100%; height:100%;
  object-fit:cover;
  display:block;
}
/* A PANTALLA COMPLETA NO SE RECORTA. Fuera de ella el vídeo rellena su hueco
   con object-fit:cover, que recorta lo que sobra; en pantalla completa eso
   se comía la parte de arriba y de abajo de los vídeos verticales, que es
   como está montado Inner Noise. Con contain se ve entero y lo que sobra a
   los lados queda en negro.
   Las dos reglas van separadas a propósito: si el navegador no entiende uno
   de los dos selectores, descarta la regla ENTERA, y perderíamos también la
   que sí entiende. En iPhone manda el reproductor nativo del sistema, que ya
   respeta la proporción por su cuenta. */
.project-video__el:fullscreen{
  object-fit:contain;
  background:#000;
}
.project-video__el:-webkit-full-screen{
  object-fit:contain;
  background:#000;
}
/* la interfaz solo asoma al pasar el cursor por encima */
.project-video__ui{
  position:absolute; bottom:14px; right:14px;
  display:flex; gap:8px;
  opacity:0;
  transition:opacity .35s var(--ease-soft);
}
.project-video:hover .project-video__ui,
.project-video__ui:focus-within{ opacity:1 }

.project-video__btn{
  width:38px; height:38px;
  display:grid; place-items:center;
  padding:0;
  border:none;
  border-radius:50%;
  background:rgba(10,10,10,.55);
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  color:#fff;
  transition:background .3s var(--ease-soft);
}
.project-video__btn:hover{ background:rgba(10,10,10,.8) }
.project-video__btn svg{ width:15px; height:15px; display:block; fill:currentColor }
@media (any-hover:hover) and (any-pointer:fine){
  .project-video__btn{ cursor:none }
}

@media (max-width:820px){
  /* el título se metía debajo del menú: arranca por debajo de la cabecera
     con 46px de respiro */
  .project-intro{
    flex-direction:column;
    height:auto;
    padding-top:calc(var(--cabecera) + 46px);
  }
  .intro,
  body[data-page="contact"] .intro{ padding-top:calc(var(--cabecera) + 46px) }
  .project-meta{ margin-top:40px; padding-top:0 }

  /* la portada, en cuadrado y recortando: el vídeo es vertical y a su
     altura natural se comía la pantalla entera. Y sin min-height, que
     dejaba asomar el fondo negro de la caja por debajo. */
  /* en móvil también manda la proporción del archivo: el cuadrado de antes
     recortaba igual que en escritorio. El tope de alto evita que un vídeo
     vertical se coma la pantalla entera, que era el motivo del cuadrado. */
  /* en móvil la columna es ancha y baja para los dos vídeos, así que manda
     siempre el ancho; el cuadrado de antes recortaba igual que en escritorio */
  .project-cover{ min-height:0; width:100%; height:auto; max-height:none; max-width:100% }
  .project-cover img,
  .project-video__el{ height:100%; object-fit:cover }
  /* en táctil no hay hover: los controles quedan siempre visibles */
  .project-video__ui{ opacity:1 }
}

/* galería a pantalla completa: sale del padding de .wrap para sangrar
   de borde a borde, imágenes pegadas entre sí sin espacio */
.project-gallery{ margin:0 -28px }
.project-frame{
  margin:0; /* <figure> trae 16px 40px por defecto: sin esto no sangraba
               bien y sobraba hueco entre imágenes */
  display:block;
  width:100%; height:100vh;
}
.project-frame img,
.project-frame video{
  width:100%; height:100%; object-fit:cover;
  display:block;
  filter:saturate(1.05) contrast(1.02);
}
/* los vídeos intercalados son solo pieza visual: ni controles ni clics */
.project-frame video{ pointer-events:none }

/* pantalla dividida: dos piezas a media pantalla, pegadas y sin hueco */
.project-split{
  display:flex;
  width:100%; height:100vh;
}
.project-split figure{
  margin:0; /* <figure> trae márgenes por defecto */
  flex:1 1 0;
  min-width:0;
  height:100%;
}
.project-split img,
.project-split video{
  width:100%; height:100%; object-fit:cover;
  display:block;
  filter:saturate(1.05) contrast(1.02);
}
.project-split video{ pointer-events:none }
@media (max-width:780px){
  /* EN MÓVIL LAS IMÁGENES VAN AL ANCHO, NO A PANTALLA COMPLETA.
     Recortar a 100vh en una pantalla alta y estrecha amplía tantísimo la
     imagen que solo se ve un trozo; con altura automática se ve entera. */
  .project-frame{ height:auto; background:#0D0D0D }
  /* 'fill' y no 'contain': con height:auto la caja YA tiene la proporción
     real de la imagen, así que contain no recorta nada pero sí puede dejar
     una línea de un subpíxel sin pintar, y por ahí asomaba el fondo claro
     de la página. El fondo oscuro de la caja es el segundo seguro. */
  .project-frame img,
  .project-frame video{ height:auto; object-fit:fill }

  /* y aquí no hay pantalla partida: a este ancho cada mitad quedaría
     minúscula, así que el par se convierte en dos imágenes normales */
  .project-split{ flex-direction:column; height:auto }
  .project-split figure{ height:auto; background:#0D0D0D }
  .project-split img,
  .project-split video{ height:auto; object-fit:fill }
}

/* ---------- LIENZO DE TEXTO SOBRE VÍDEO ----------
   El vídeo es una textura de grano casi blanca que corre en bucle por detrás,
   y el texto va encima en negro, con el mismo revelado línea a línea que el
   bloque de Hublot.
   Ocupa la pantalla entera, como las imágenes de la galería: en franja se
   leía como un recorte suelto en medio de la página. Va con min-height y no
   con height para que, si el texto es más largo que la pantalla (en móvil
   son once líneas), el bloque crezca en vez de cortarlo.
   100vh y no 100dvh a propósito: dvh cambia de valor según entran y salen
   las barras de Safari, y una sección a mitad de página que se reajusta al
   bajar da un salto. Es la misma unidad que usa .project-frame. */
.project-canvas{
  position:relative;
  margin:0 -28px;   /* sale de los 28px de .wrap, igual que .project-gallery */
  overflow:hidden;
  min-height:100vh;
  display:flex;
  align-items:center;  /* el texto, centrado en vertical sobre el vídeo */
  padding:clamp(60px,10vh,120px) 28px;
}
.project-canvas__bg{
  position:absolute; inset:0;
  width:100%; height:100%;
  object-fit:cover;
  pointer-events:none; /* es fondo: ni controles ni clics */
}
/* variante que NO llena la pantalla: la altura la marca el texto más el aire
   del padding. Se usa en el segundo bloque de texto del proyecto. */
.project-canvas--auto{ min-height:0 }
/* sin esto el texto quedaría por debajo del vídeo, que es position:absolute.
   El width:100% lo necesita por ser hijo de un flex: si no, se encoge al
   ancho de su línea más larga y el max-width de 60ch deja de mandar. */
.project-canvas__text{ position:relative; width:100% }

@media (max-width:780px){
  .project-canvas{ padding:clamp(40px,7vh,80px) 28px }
}

/* ---------- COLLAGE (proyectos de fotos verticales) ----------
   Las fotos de Inner Noise son verticales (9:16). A pantalla completa, como
   las de Hublot, habría que recortarlas muchísimo, así que aquí van en
   columna, cada una alineada a un lado distinto y sobre el fondo de la web.
   Las que caen a lados opuestos se solapan un poco en vertical para que se
   lean como una pareja y no como dos bloques sueltos. */
.collage{
  display:flex; flex-direction:column;
  /* UNA SOLA MEDIDA DE AIRE para todo el collage: la que separa dos piezas
     es la misma que la que lo separa de lo que tiene encima y debajo. Antes
     eran dos cifras distintas (54 entre fotos y 99 en los bordes). */
  --gap: clamp(40px,6vh,80px);
  gap:var(--gap);
  padding:var(--gap) 0;
  /* tres anchos para que el ritmo no sea plano. Al ser fotos 9:16, cada px
     de ancho son 1,78 de alto: por eso las grandes pasan de la altura de la
     pantalla, que es lo normal en una foto vertical y se recorre bajando. */
  --foto-s: clamp(200px,34vw,480px);
  --foto-m: clamp(240px,42vw,600px);
  --foto-l: clamp(280px,50vw,700px);
  /* el cruce es lo que de verdad quita hueco muerto entre parejas */
  --solape: clamp(200px,38vh,480px);
  /* cruce calculado, no a ojo: sube la foto hasta que su borde superior cae
     en la MITAD de la anterior. En una columna flex la de abajo empieza en
     (alto de la de arriba + gap), así que hay que restarle medio alto más el
     gap. Las fotos son 9:16, de modo que su alto es el ancho por 1,78. */
  --mitad: calc(var(--foto-m) * 1.78 / 2 + var(--gap));
}
/* el ancho base y los modificadores tienen la misma especificidad: los de
   abajo ganan por orden, que es justo lo que se quiere.
   El margin:0 no es de adorno: el navegador le pone a <figure> un margen
   propio de 1em 40px, y con él las fotos no cuadraban con los 28px del
   resto de la página ni el espacio entre ellas era el declarado. */
.collage__item{ width:var(--foto-m); margin:0 }
.collage__item--s{ width:var(--foto-s) }
.collage__item--l{ width:var(--foto-l) }
.collage__item--xl{ width:calc(var(--foto-l) * 1.15) } /* el grande, un 15% más */
/* a sangre, de borde a borde de la pantalla: se sale de los 28px de .wrap
   igual que las tiras. Va después de --s y --l, que tienen su misma
   especificidad, para que gane por orden si se combinan por error. */
.collage__item--sangre{
  width:calc(100% + 56px);
  margin-left:-28px;
  align-self:flex-start;
}
/* PAR A LA MISMA ALTURA: una contra el margen izquierdo y otra contra el
   derecho, las dos arrancando en la misma línea. Es una fila, no una
   columna, así que aquí NO valen --izq / --der: en una fila esas clases
   mueven en vertical, que es el eje contrario, y las descuadrarían. */
/* DOS PIEZAS CRUZADAS. El grupo va centrado en la página, no contra los
   márgenes: una queda algo a la izquierda y otra algo a la derecha, se solapan
   un tercio de su ancho y la segunda baja para que no estén a la misma altura.
   Como es una fila, aquí tampoco valen --izq / --centro / --der: en una fila
   mueven en vertical, que es el eje contrario. */
.collage__cruce{
  display:flex;
  justify-content:center;
  align-items:flex-start;
  width:100%;
  /* todo se mide contra el ancho de la pieza, así el cruce se mantiene igual
     de proporcionado en cualquier pantalla */
  /* El tope evita que el grupo se salga de los márgenes: con el solape al
     24%, dos piezas ocupan 1,76 veces el ancho de una, así que ese es el
     máximo que cabe. En pantallas anchas no llega a aplicarse. */
  --pieza: min(calc(var(--foto-m) * 1.656), calc(100% / 1.76)); /* otro 20% sobre el 1,38 anterior */
  --solapa: 0.24;                                              /* un 20% menos de solape */
}
.collage__cruce > .collage__item{ width:var(--pieza) }
.collage__cruce > .collage__item:nth-child(2){
  margin-left:calc(var(--pieza) * var(--solapa) * -1);
  margin-top:calc(var(--pieza) * 0.16); /* y baja, para romper la línea */
  position:relative;
  z-index:1; /* la segunda queda por encima */
}

/* DOS EN FILA, repartiéndose el ancho. A diferencia de .collage__par, que
   empuja cada pieza contra su margen y deja un hueco grande en medio, aquí
   las dos ocupan la mitad y solo las separa un hueco fino. */
.collage__fila{
  display:flex;
  align-items:flex-start;
  gap:clamp(12px,1.6vw,30px);
  width:100%;
}
.collage__fila > .collage__item{ flex:1 1 0; width:auto; min-width:0 }

/* DOS EN DIAGONAL: una a la izquierda y la otra a la derecha pero más abajo.
   El desfase va en % del ancho del contenedor, no en píxeles, para que la
   diagonal se mantenga igual de inclinada en cualquier pantalla. */
.collage__diagonal{
  display:flex;
  justify-content:space-between;
  align-items:flex-start;
  width:100%;
}
.collage__diagonal > .collage__item{ flex:0 0 46%; width:auto }
.collage__diagonal > .collage__item:nth-child(2){ margin-top:11% }

/* VARIAS A SANGRE Y PEGADAS, sin nada entre ellas: se leen como una tira
   continua y no como piezas sueltas. */
.collage__pegadas{
  width:calc(100% + 56px);
  margin-left:-28px;
  display:flex;
  flex-direction:column;
  gap:0;
}
.collage__pegadas img{ width:100%; height:auto; display:block }

/* Fila DENTRO del collage: dos piezas a la misma altura, una contra cada
   margen. Hace lo mismo que .collage--par pero sin abrir una sección aparte,
   de modo que el aire con lo de alrededor es el del propio collage y no el
   mayor que separa un proyecto del siguiente. */
.collage__par{
  display:flex;
  justify-content:space-between;
  align-items:flex-start;
  width:100%;
}

.collage--par{
  flex-direction:row;
  justify-content:space-between;
  align-items:flex-start;
}
.collage__item--izq{ align-self:flex-start }
.collage__item--centro{ align-self:center }
.collage__item--der{ align-self:flex-end }
/* sube la foto hasta cruzarse con la anterior. Solo se pone en fotos que
   van al lado contrario de la de arriba: si no, se taparían. */
.collage__item--sube{ margin-top:calc(var(--solape) * -1) }
/* variante medida: el borde de arriba coincide con la mitad de la anterior */
.collage__item--mitad{ margin-top:calc(var(--mitad) * -1) }
.collage__item img,
.collage__item video{
  width:100%; height:auto; display:block;
  border-radius:3px;
  filter:saturate(1.05) contrast(1.02);
}

/* MEDIDA POR EL ALTO, no por el ancho. Una vertical grande a 600px de ancho
   mide 1067 de alto y se sale de la pantalla, obligando a bajar para verla
   entera; así entra justo, con algo de margen.
   Va después de la regla de arriba porque tienen la misma especificidad y
   decide el orden, y solo de 781px para arriba: en móvil las piezas van a
   todo el ancho y el alto lo marca la proporción. */
@media (min-width:781px){
  .collage__item--cabe{ width:auto; max-width:100% }
  .collage__item--cabe > img,
  .collage__item--cabe > video{
    height:min(96vh, 984px); /* el 80vh/820px de partida, un 20% más */
    width:auto;
    max-width:100%;
  }
}

/* TIRA DE TRES VÍDEOS, de borde a borde de la pantalla.
   Es hija del flex de .collage, así que se sale de los 28px de .wrap con un
   ancho de 100% + 56 y un margen izquierdo negativo.
   Los tres archivos son 1280x2302, la misma proporción, de modo que en tres
   columnas iguales salen del mismo tamaño sin deformar nada. El max-height
   es el que recorta: sin él, en una pantalla ancha la tira mediría más de
   mil píxeles de alto. El recorte lo absorbe object-fit:cover. */
.collage__tira{
  width:calc(100% + 56px);
  margin-left:-28px;
  align-self:flex-start;
  display:grid;
  grid-template-columns:repeat(3,1fr);
  /* Tres columnas de 1143x2048 hacen un bloque de 3429x2048. En una pantalla
     más ancha que 15:9 (un monitor 16:9, por ejemplo) tres vídeos verticales
     NO caben a la vez a todo lo ancho y a todo lo alto sin cortar por arriba
     y por abajo: no es cosa del código, es la proporción. Entre recortar en
     vertical y que la tira sobresalga un poco de la pantalla, se elige lo
     segundo, que es lo que se pidió. En pantallas más estrechas manda el
     min-height y la tira mide justo una pantalla.
     La proporción va aquí y no en vh porque vh no descuenta la barra de
     scroll y el cálculo saldría torcido. */
  /* la proporción se puede ajustar por tira, porque no todas llevan piezas
     del mismo tamaño: las fotos son 1143x2048 y los vídeos 1280x2302, y usar
     una sola cifra obligaría a recortar un poco a la otra */
  aspect-ratio:var(--tira, 3429 / 2048);
  min-height:100vh;
}
.collage__tira figure{
  margin:0;
  /* la tira ocupa la pantalla entera: ancho completo y alto completo. Sin
     aspect-ratio, que ataba el alto a la proporción del archivo y obligaba a
     recortar por arriba y por abajo. Ahora la caja manda y el vídeo se
     adapta: con object-fit:cover llena los dos lados y lo que sobra se va
     por los costados, nunca por el borde superior ni el inferior.
     vh y no dvh, igual que el lienzo de grano: dvh cambia al entrar y salir
     las barras de Safari y la tira daría un salto a mitad de scroll. */
  width:100%;
  height:100%;  /* el alto lo marca la tira; aquí solo se estira */
  overflow:hidden;
}
.collage__tira img,
.collage__tira video{
  width:100%; height:100%;
  object-fit:cover;
  display:block;
  pointer-events:none; /* pieza visual: ni controles ni clics */
}

@media (max-width:780px){
  .collage{
    /* seguidas, sin aire entre ellas: una termina y empieza la siguiente.
       El hueco con el bloque de texto anterior lo sigue dando el padding. */
    gap:0;
    padding:clamp(48px,8vh,80px) 0;
    /* en % del hueco y no en vw: así la grande llega justo al margen de la
       página y no se sale por la derecha */
    --foto-s: 72%;
    --foto-m: 88%;
    --foto-l: 100%;
    /* sin cruce: a este ancho dos fotos enfrentadas no caben una al lado
       de la otra y se montarían encima */
    --solape: 0px;
    --mitad: 0px;
  }
  /* En una pantalla estrecha el collage escalonado no se lee como collage:
     se ve como fotos descolocadas y pequeñas. Aquí van rectas, una debajo de
     otra y de borde a borde. El calc sale de los 28px de margen de .wrap a
     cada lado; el selector lleva dos clases para ganar al align-self de
     --izq / --centro / --der, que si no seguirían mandando. */
  /* En Playground, el salto ENTRE PROYECTOS a la mitad: a este ancho los 270px
     de escritorio dejaban una pantalla casi vacía entre serie y serie. El
     hueco con el texto de entrada NO se toca, que ese se pidió grande a
     propósito; por eso el selector mira solo los collage que van detrás de
     otro. */
  body[data-page="playground"] .collage + .collage{ padding-top:clamp(48px,8vh,80px) }

  /* a este ancho dos fotos no caben una al lado de la otra: el par se apila */
  .collage--par,
  .collage__par,
  .collage__fila,
  .collage__diagonal,
  .collage__cruce{ flex-direction:column }
  /* Al apilarse, el eje principal pasa a ser el vertical: un flex con base 0
     repartiría la ALTURA entre las piezas en vez del ancho, y se quedaban
     una encima de otra con altura cero. */
  .collage__fila > .collage__item,
  .collage__diagonal > .collage__item{ flex:0 0 auto }
  .collage__diagonal > .collage__item:nth-child(2){ margin-top:0 }
  .collage__fila{ gap:0 }
  .collage__pegadas{ width:calc(100% + 56px); margin-left:-28px }
  /* a este ancho no hay sitio para cruzarlas: se apilan como el resto */
  .collage__cruce > .collage__item:nth-child(2){ margin-left:-28px; margin-top:0 }
  .collage .collage__item{
    width:calc(100% + 56px);
    margin-left:-28px;
    align-self:flex-start;
    /* recorta el píxel que sobresale, abajo */
    overflow:hidden;
  }
  /* Safari deja un hilo claro en el borde lateral de las fotos que amplía
     mucho: las dos de 540px de ancho se estiran x2,17 en una pantalla de
     1170 px reales, y al interpolar, la columna de píxeles del borde se
     mezcla con la nada. La caja mide exactamente lo que debe (medido con
     decimales a 375, 390, 393 y 430), así que no es cosa del cálculo.
     La imagen se saca un píxel por cada lado y el overflow se lo come: así
     el borde interpolado cae fuera de la vista. */
  .collage .collage__item img{
    width:calc(100% + 2px);
    /* el reset global pone max-width:100% a toda img, y sin anularlo recorta
       justo el desbordamiento que se busca aquí: el margen negativo movía la
       imagen a la izquierda y el hilo aparecía en el lado contrario */
    max-width:none;
    margin-left:-1px;
  }
  /* el rótulo, más pegado a su lista: los 22px de .section__head dejaban el
     título demasiado suelto de lo que titula */
  .project-meta .section__head{ margin-bottom:8px }
}

/* ---------- BLOQUE DE TEXTO ENTRE IMÁGENES ----------
   ocupa solo lo que ocupa el texto, sobre el fondo de la página, con
   mucho aire arriba y abajo para que respire */
.project-note{ padding:clamp(120px,22vh,240px) 0 }
/* en móvil, 22vh arriba y abajo era demasiado hueco: se reduce casi a la
   mitad y sigue quedando aire de sobra. Va DESPUÉS de la regla de arriba a
   propósito: con la misma especificidad, gana la última de la hoja. */
@media (max-width:780px){
  .project-note{ padding:clamp(64px,11vh,120px) 0 }
}
.project-note__text{
  max-width:60ch;
  text-align:left;
  font-size:var(--fs-copy);
  font-weight:450;
  line-height:1.3;
  letter-spacing:-.012em;
  color:var(--ink);
}
/* aire entre párrafos, para que no se lean como un bloque seguido */
.project-note__text + .project-note__text{ margin-top:clamp(26px,3.4vh,44px) }
/* cada línea se materializa: entra muy desenfocada y translúcida y va
   aclarándose hasta quedar nítida, escalonada línea a línea (el delay lo
   pone JS). Se rearma cada vez que el bloque vuelve a entrar por abajo,
   así que la animación se repite si se sube y se baja de nuevo. */
.reveal-line{
  display:block;
  opacity:0;
  filter:blur(14px);
  transform:translateY(16px) scale(1.012);
  transform-origin:0 50%;
  transition:opacity 1.15s var(--ease-soft),
             filter 1.15s var(--ease-soft),
             transform 1.15s var(--ease-soft);
  will-change:opacity, filter, transform;
}
.reveal-line.is-visible{
  opacity:1;
  filter:blur(0);
  transform:translateY(0) scale(1);
}

/* .project-next ya no se usa: las páginas de proyecto terminan en su última
   imagen, pegada al borde inferior. Se deja la regla por si vuelve. */
.project-next{ text-align:center }
.project-next .section__head{ text-align:center }
/* sin ese bloque de cierre, el relleno inferior del collage dejaba un hueco
   gris entre la última pieza y el final de la página */
.wrap > .collage:last-child{ padding-bottom:0 }
/* En Playground, las piezas apaisadas y cuadradas van un 20% más grandes que
   el tamaño base del collage. Las verticales no entran aquí: esas se miden
   por el alto con --cabe y no dependen de estos anchos. */
body[data-page="playground"] .collage{
  --foto-l: calc(clamp(280px,50vw,700px) * 1.2);
}

/* En Playground la galería arranca mucho más abajo que en un proyecto: no hay
   nada entre el texto de entrada y la primera pieza, y con el aire normal
   quedaban demasiado juntos. */
body[data-page="playground"] .collage{ padding-top:clamp(140px,24vh,280px) }

/* contacto */
.big-link{
  display:inline-block;
  font-size:var(--fs-display);
  font-weight:600; letter-spacing:-.032em;
  line-height:1.02;
  position:relative;
}
.big-link::after{
  content:""; position:absolute; left:0; bottom:.06em;
  width:100%; height:2px; background:currentColor;
  transform:scaleX(0); transform-origin:left;
  transition:transform .7s var(--ease);
}
.big-link:hover::after{ transform:scaleX(1) }

.social{ display:flex; flex-wrap:wrap; gap:26px; margin-top:clamp(40px,7vh,64px) }
.social a{
  font-size:var(--fs-small); letter-spacing:.08em; text-transform:uppercase;
  border-bottom:1px solid rgba(10,10,10,.3);
  padding-bottom:3px;
  transition:opacity .4s var(--ease-soft);
}
.social a:hover{ opacity:.5 }

/* REMATE DE PÁGINA. Una franja del color del fondo para que el contenido no
   termine a hueso en la última imagen y se pueda bajar un poco más. Sin línea
   separadora ni fondo propio: solo aire.
   El enlace copia la receta entera del © fijo de la esquina —mismo tamaño,
   tracking, color y mezcla— y se apoya a su misma altura sobre el final de la
   página, de modo que los dos se leen como una sola línea. */
.cierre{
  padding-top:clamp(56px,8vh,110px);
  padding-bottom:calc(22px + env(safe-area-inset-bottom, 0px));
}
.cierre a{
  font-size:var(--fs-label); letter-spacing:.06em;
  color:#a0a0a0;
  mix-blend-mode:difference;
}

.site-foot{
  display:flex; justify-content:space-between; gap:20px;
  margin-top:clamp(80px,14vh,170px);
  padding:20px 0 34px;
  border-top:1px solid rgba(10,10,10,.18);
  font-size:var(--fs-label); letter-spacing:.1em; text-transform:uppercase;
  color:var(--ink-soft);
}

/* Based in / Working, en la esquina inferior derecha del footer (contact) */
.contact-meta{ display:flex; gap:28px; margin-left:auto }
.contact-meta > div{ text-align:right }

/* La sección de contacto va en oscuro para que destaque del resto del sitio.
   Se hace redefiniendo los TOKENS, no pintando cada regla: así el título, el
   email, los rótulos y el borde del footer se recolocan solos y no hay que
   duplicar nada. El nombre y el menú no necesitan tocarse porque llevan
   mix-blend-mode:difference y se invierten contra el fondo que tengan. */
body[data-page="contact"]{
  --bg:#0D0D0D;
  --bg-rgb:13,13,13;
  --ink:#F2F2F2;
  --ink-mid:#A8A8A8;
  /* sobre negro, el #8C8C8C de la web clara se apagaba y el #6E6E6E que
     le corresponde por simetría se quedaba en 3,8:1. A #7E7E7E los rótulos
     de 11px llegan a 4,8:1 y siguen leyéndose como secundarios. */
  --ink-soft:#7E7E7E;
}

/* página de contacto: sin scroll, los links sociales se apoyan casi contra
   el footer en vez de quedar pegados al email */
body[data-page="contact"] .wrap{
  display:flex; flex-direction:column;
  min-height:100vh;
  padding-bottom:46px; /* deja hueco para que el © fijo no choque con el footer */
}
body[data-page="contact"] .section{
  flex:1 1 auto;
  display:flex; flex-direction:column;
}
/* El email es una sola palabra de 30 caracteres y no se puede partir: a
   --fs-display se salía de la pantalla en todo ancho por debajo de ~900px.
   El divisor sale de medir el texto: ocupa unas 15 veces el cuerpo de la
   fuente, y el hueco real es la ventana menos los 28px de margen a cada
   lado. El min() deja intacto el tamaño de escritorio. */
body[data-page="contact"] .big-link{
  font-size:min(var(--fs-display), calc((100vw - 56px) / 15));
}
body[data-page="contact"] .social{ margin-top:auto; padding-bottom:8px }
body[data-page="contact"] .site-foot{ margin-top:0; border-top:none }

/* ---------- 7. RESPONSIVE LANDING ---------- */
@media (max-width:760px){
  /* en móvil la portada se queda solo con las imágenes */
  .hero-welcome,
  .hero-link{ display:none }
}
