/* =========================================================================
   8-Bit Llama Lab — sistema visual
   Todos los colores salen del sprite de Esteban (8 colores exactos) más un
   par de derivados. El fondo es un negro cafetoso sacado del siena del
   sprite, no un gris neutro: la llama y la página comparten familia.
   ========================================================================= */

/* 1. TIPOGRAFÍAS ---------------------------------------------------------- */

@font-face {
  font-family: 'Bricolage';
  src: url('../fonts/bricolage-latin.woff2') format('woff2');
  font-weight: 400 800;
  font-stretch: 75% 100%;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Bricolage';
  src: url('../fonts/bricolage-latin-ext.woff2') format('woff2');
  font-weight: 400 800;
  font-stretch: 75% 100%;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Instrument';
  src: url('../fonts/instrument-latin.woff2') format('woff2');
  font-weight: 400 700;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Instrument';
  src: url('../fonts/instrument-latin-ext.woff2') format('woff2');
  font-weight: 400 700;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Martian';
  src: url('../fonts/martian-latin.woff2') format('woff2');
  font-weight: 400 700;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Martian';
  src: url('../fonts/martian-latin-ext.woff2') format('woff2');
  font-weight: 400 700;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* 2. TOKENS --------------------------------------------------------------- */

:root {
  /* Los ocho colores de Esteban, tal cual salieron del sprite */
  --yellow: #ffde00;
  --tan: #efb792;
  --clay: #c56b37;
  --sienna: #af4a06;
  --pink: #ffaae1;
  --pink-pale: #facee7;
  --pink-hot: #f66471;
  --amber: #ffc94d; /* derivado: entre el amarillo y la arcilla */
  /* Ajenos a Esteban: la marca de HumanFly, que es blanca, negra y roja. El
     negro está levantado sobre la tinta de la página; en negro puro la mitad
     oscura del yin yang desaparecería en el fondo. */
  --blood: #d93b2b;
  --soot: #1b1310;

  /* Fondos y tintas */
  --ink: #0b0705;
  --ink-lift: #150e08;
  --sand: #f2e2d0;
  --sand-deep: #e6d0b8;
  --bone: #f7f0e8;
  --dim: #a99684;
  --sand-ink: #251609;
  --sand-dim: #6f5843;

  /* Tokens de superficie: cada sección los redefine y todo lo demás
     los hereda. Nunca se pone un color literal en un componente. */
  --bg: var(--ink);
  --fg: var(--bone);
  --fg-dim: var(--dim);
  --accent: var(--yellow);

  /* Métrica */
  --gutter: clamp(1.25rem, 5vw, 5.5rem);
  --measure: 62ch;
  --page: min(100% - var(--gutter) * 2, 1560px);

  /* Escala tipográfica */
  --fs-hero: clamp(2.7rem, 11.4vw, 10.5rem);
  --fs-xxl: clamp(2.6rem, 7.6vw, 6.2rem);
  --fs-xl: clamp(2rem, 5vw, 3.9rem);
  --fs-lg: clamp(1.45rem, 2.6vw, 2.15rem);
  --fs-md: clamp(1.05rem, 0.5vw + 0.95rem, 1.35rem);
  --fs-sm: 0.95rem;
  --fs-label: 0.6875rem;

  --ease: cubic-bezier(0.22, 0.68, 0.24, 1);
  --slow: 0.9s var(--ease);

  color-scheme: dark;
}

/* 3. RESET ---------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--ink);
  color: var(--bone);
  font-family: 'Instrument', ui-sans-serif, system-ui, sans-serif;
  font-size: var(--fs-md);
  line-height: 1.55;
  letter-spacing: -0.004em;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

/* `height: auto` no es decorativo: los <img> llevan width/height en el
   marcado para reservar el hueco, y sin esto ese `height` gana sobre el
   ancho que pide el CSS y aplasta la imagen. */
img {
  max-width: 100%;
  height: auto;
  display: block;
}

h1,
h2,
h3,
p,
ul,
ol,
figure {
  margin: 0;
}

ul,
ol {
  padding: 0;
  list-style: none;
}

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

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

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

:focus-visible {
  outline: 3px solid var(--pink);
  outline-offset: 4px;
  border-radius: 2px;
}

.skip {
  position: fixed;
  top: 0.75rem;
  left: 0.75rem;
  z-index: 200;
  padding: 0.7rem 1.1rem;
  background: var(--yellow);
  color: var(--ink);
  font-family: 'Martian', ui-monospace, monospace;
  font-size: var(--fs-label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  transform: translateY(-160%);
  transition: transform 0.25s var(--ease);
}

.skip:focus {
  transform: none;
}

.vh {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* 4. PRIMITIVAS ----------------------------------------------------------- */

.wrap {
  width: var(--page);
  margin-inline: auto;
}

.display {
  font-family: 'Bricolage', ui-sans-serif, system-ui, sans-serif;
  font-weight: 700;
  line-height: 0.88;
  letter-spacing: -0.038em;
  font-variation-settings: 'wdth' 92, 'opsz' 48;
  text-wrap: balance;
}

.label {
  font-family: 'Martian', ui-monospace, monospace;
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  line-height: 1.4;
  color: var(--fg-dim);
}

.lede {
  font-size: clamp(1.15rem, 0.9vw + 1rem, 1.6rem);
  line-height: 1.5;
  max-width: 34ch;
  text-wrap: pretty;
  color: var(--fg);
}

p {
  text-wrap: pretty;
}

.hl {
  color: var(--accent);
  font-style: normal;
}

/* La única "regla" de la página: una fila de píxeles, no una línea.
   Se usa solo entre los proyectos, donde escanear pide una separación. */
.pxrule {
  height: 3px;
  background-image: repeating-linear-gradient(
    90deg,
    currentColor 0 3px,
    transparent 3px 9px
  );
  opacity: 0.26;
}

/* El salto de los dibujos de píxeles: los glifos de la lista, los números del
   proceso y las dos marcas de Esteban. Ninguno flota — saltan **dos píxeles en
   dos tiempos**, con `steps(1)`, porque un `translate` interpolado le pone
   antialias a un dibujo que se renderiza con `crispEdges` y lo emborrona. Es
   el mismo criterio de 8 bits del resto de la página, aplicado al movimiento.

   Va en la propiedad `translate` y NO dentro de `transform` a propósito: el
   hover de la fila y el de la marca usan `transform`, y una animación sobre
   esa misma propiedad se los comería enteros. Son dos canales distintos y se
   suman solos.

   Los retrasos son negativos para que la animación arranque ya empezada: si
   no, la primera vuelta sale toda a la vez y se ve como un parpadeo único en
   lugar de un goteo. `prefers-reduced-motion` lo apaga desde la sección 16. */
@keyframes px-hop {
  0% {
    translate: 0 0;
  }
  50% {
    translate: 0 -2px;
  }
}

.brand-mark,
.foot-mark,
.work-glyph,
.step-num {
  animation: px-hop 3.1s steps(1, end) infinite;
}

/* Cada uno entra en su momento: seis proyectos y cuatro pasos saltando a la
   vez serían un tic, escalonados son un goteo que se nota sin molestar. */
.work-item:nth-child(2) .work-glyph { animation-delay: -0.5s; }
.work-item:nth-child(3) .work-glyph { animation-delay: -1s; }
.work-item:nth-child(4) .work-glyph { animation-delay: -1.55s; }
.work-item:nth-child(5) .work-glyph { animation-delay: -2.05s; }
.work-item:nth-child(6) .work-glyph { animation-delay: -2.6s; }
.step:nth-child(2) .step-num { animation-delay: -0.8s; }
.step:nth-child(3) .step-num { animation-delay: -1.55s; }
.step:nth-child(4) .step-num { animation-delay: -2.3s; }
.foot-mark { animation-delay: -1.6s; }

/* 5. FONDO: lienzo 3D, grano y velos --------------------------------------- */

.stage {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.2s ease;
}

.stage.is-ready {
  opacity: 1;
}

/* El momento en que Esteban se pone POR DELANTE: cruza el panel de arena
   caminando y se planta en la banda de cada proyecto en móvil. Por debajo
   de la barra (z 60), que por eso vive fuera de .page. */
.stage.is-front {
  z-index: 40;
}

/* Reserva por si no hay WebGL: el sprite plano, enorme y discreto. */
.stage-fallback {
  position: fixed;
  right: -4vw;
  bottom: 4vh;
  width: min(46vw, 432px);
  z-index: 0;
  opacity: 0.13;
  image-rendering: pixelated;
  filter: saturate(0.7);
  pointer-events: none;
  display: none;
}

.no-webgl .stage-fallback {
  display: block;
}

/* Velo que garantiza que el texto siempre gana contra la llama */
.veil {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: radial-gradient(
      120% 90% at 30% 45%,
      rgba(11, 7, 5, 0.86) 0%,
      rgba(11, 7, 5, 0.42) 42%,
      rgba(11, 7, 5, 0) 72%
    ),
    linear-gradient(180deg, rgba(11, 7, 5, 0.72) 0%, rgba(11, 7, 5, 0) 22%),
    linear-gradient(0deg, rgba(11, 7, 5, 0.8) 0%, rgba(11, 7, 5, 0) 16%);
}

.grain {
  position: fixed;
  inset: -50%;
  z-index: 2;
  pointer-events: none;
  opacity: 0.05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
  mix-blend-mode: overlay;
}

.page {
  position: relative;
  z-index: 3;
}

/* 6. NAVEGACIÓN ------------------------------------------------------------ */

.nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 60;
  /* Con el logotipo a su tamaño real (36×48) la barra ya tiene cuerpo:
     el relleno se ajusta para que no quede pesada. */
  padding-block: clamp(0.7rem, 1.1vw, 1.15rem);
  transition: background 0.4s ease, backdrop-filter 0.4s ease,
    transform 0.5s var(--ease);
  background: linear-gradient(180deg, rgba(11, 7, 5, 0.72), rgba(11, 7, 5, 0));
}

.nav.is-stuck {
  background: rgba(11, 7, 5, 0.78);
  backdrop-filter: blur(14px) saturate(1.2);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
}

.nav.is-hidden {
  transform: translateY(-115%);
}

/* Esconder la barra al bajar es correcto mientras haya otra salida. Por
   debajo de 900px no la hay: el menú ya se quedó solo con «Contacto», así
   que la barra se llevaba consigo el único enlace a la acción justo
   mientras el visitante bajaba hacia ella. En móvil se queda. */
@media (max-width: 899px) {
  .nav.is-hidden {
    transform: none;
  }
}

.nav.on-sand {
  background: linear-gradient(180deg, rgba(242, 226, 208, 0.82), rgba(242, 226, 208, 0));
  color: var(--sand-ink);
}

.nav.on-sand.is-stuck {
  background: rgba(242, 226, 208, 0.84);
}

.nav-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
}

.brand {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  cursor: pointer;
}

.brand-mark {
  width: 36px;
  flex: none;
  image-rendering: pixelated;
  transition: transform 0.5s var(--ease);
}

.brand:hover .brand-mark,
.brand:focus-visible .brand-mark {
  transform: translateY(-4px) rotate(-6deg);
}

.brand-name {
  font-family: 'Martian', ui-monospace, monospace;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  white-space: nowrap;
}

.nav-links {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 2.4vw, 2.6rem);
}

.nav-link {
  font-family: 'Martian', ui-monospace, monospace;
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  position: relative;
  /* 44px de alto contando la etiqueta: el enlace de Contacto es el único que
     sobrevive en móvil y tiene que poder tocarse con el pulgar. */
  padding-block: 0.95rem;
}

/* El subrayado se queda pegado a la letra, no al borde del área táctil:
   el relleno de arriba creció para el pulgar, no para separar la línea. */
.nav-link::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0.65rem;
  height: 2px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.4s var(--ease);
}

.nav-link:hover::after,
.nav-link:focus-visible::after {
  transform: scaleX(1);
}

.lang {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  font-family: 'Martian', ui-monospace, monospace;
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: 0.1em;
}

/* El relleno vertical no es aire: es superficie que tocar. La etiqueta mide
   11px, así que sin él el objetivo se queda en 27px de alto y falla el
   mínimo de 44 en el único sitio donde se cambia de idioma: el teléfono. */
.lang-btn {
  padding: 0.86rem 0.5rem;
  opacity: 0.42;
  transition: opacity 0.3s ease, color 0.3s ease;
}

.lang-btn:hover {
  opacity: 0.8;
}

.lang-btn.is-on {
  opacity: 1;
  color: var(--yellow);
}

.nav.on-sand .lang-btn.is-on {
  color: var(--sienna);
}

.lang-sep {
  opacity: 0.3;
}

/* 7. SECCIONES ------------------------------------------------------------- */

.sec {
  position: relative;
  padding-block: clamp(6rem, 14vh, 12rem);
  background: var(--bg);
  color: var(--fg);
}

.sec-bare {
  background: transparent;
}

.sec-head {
  display: flex;
  flex-direction: column;
  gap: clamp(1.2rem, 2.6vw, 2rem);
  margin-bottom: clamp(3rem, 7vw, 6rem);
}

/* 8. HERO ------------------------------------------------------------------ */

.hero {
  min-height: 100svh;
  display: flex;
  align-items: center;
  padding-block: clamp(8rem, 18vh, 12rem) clamp(4rem, 10vh, 7rem);
  background: transparent;
}

.hero-grid {
  display: grid;
  gap: clamp(1.8rem, 4vw, 3rem);
}

.hero-title {
  font-size: var(--fs-hero);
  font-variation-settings: 'wdth' 88, 'opsz' 96;
  letter-spacing: -0.05em;
  max-width: 15ch;
}

.hero-title .hl {
  display: block;
}

.hero-bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: clamp(1.6rem, 4vw, 3rem);
  margin-top: clamp(1rem, 3vw, 2.5rem);
}

/* El atajo del héroe. Quien ya decidió escribir —el referido, que es medio
   público del sitio— tenía que bajar diez pantallas para encontrar dónde.
   Es el mismo gesto que el correo del final, tipografía que engorda y sin
   caja, a escala de línea: no compite con el titular, lo remata. */
.hero-mail {
  display: inline-block;
  padding-block: 0.78rem;
  font-family: 'Bricolage', sans-serif;
  font-weight: 700;
  font-size: clamp(1.3rem, 2.2vw, 1.85rem);
  line-height: 1;
  letter-spacing: -0.03em;
  font-variation-settings: 'wdth' 88, 'opsz' 32;
  color: var(--yellow);
  transition: font-variation-settings 0.55s var(--ease), color 0.4s var(--ease);
}

.hero-mail:hover,
.hero-mail:focus-visible {
  font-variation-settings: 'wdth' 100, 'opsz' 32;
  color: var(--pink);
}

/* Anotación que presenta a Esteban sin gastarle un párrafo */
.esteban-note {
  font-family: 'Martian', ui-monospace, monospace;
  font-size: var(--fs-label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--tan);
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.esteban-note::before {
  content: '↗';
  font-size: 1.1em;
  animation: nudge 3.4s ease-in-out infinite;
}

@keyframes nudge {
  0%,
  100% {
    transform: translate(0, 0);
  }
  50% {
    transform: translate(4px, -4px);
  }
}

/* 9. ESTUDIO --------------------------------------------------------------- */

.studio-grid {
  display: grid;
  gap: clamp(2.5rem, 6vw, 5rem);
}

.statement {
  font-size: var(--fs-xxl);
  font-variation-settings: 'wdth' 90, 'opsz' 72;
  max-width: 17ch;
}

.studio-body {
  display: grid;
  gap: 1.5rem;
  max-width: 46ch;
  font-size: clamp(1.05rem, 0.4vw + 0.98rem, 1.22rem);
  color: var(--fg-dim);
}

.studio-body strong {
  color: var(--fg);
  font-weight: 600;
}

/* 10. TRABAJO -------------------------------------------------------------- */

.work-title {
  font-size: var(--fs-xl);
  max-width: 16ch;
}

/* En escritorio la lista deja libre una franja a la derecha: es la vitrina
   donde vive el objeto 3D —Esteban, o el proyecto que estés mirando—.
   Ningún texto entra ahí, así que el objeto puede ir a plena luz. */
.work-list {
  display: grid;
  --case: 0px;
}

.work-item {
  position: relative;
}

.work-item .pxrule {
  color: var(--tan);
}

.work-row {
  position: relative;
  display: grid;
  /* Tres columnas, no cuatro: los datos y el enlace bajan debajo de la
     descripción para que el nombre no se parta en dos líneas. */
  grid-template-columns: auto minmax(0, 1.2fr) minmax(0, 1fr);
  grid-template-rows: auto auto;
  align-items: center;
  gap: 0.9rem clamp(1.2rem, 3vw, 3rem);
  padding-block: clamp(1.6rem, 3.4vw, 2.6rem);
  transition: color 0.45s var(--ease);
}

/* El lavado de color a sangre: sale del gutter, sin bordes ni tarjeta */
.work-row::before {
  content: '';
  position: absolute;
  inset-block: 0;
  /* El lavado ignora la vitrina y sigue saliéndose por los dos lados. */
  inset-inline: calc(var(--gutter) * -0.6) calc(-1 * (var(--case) + var(--gutter) * 0.6));
  background: linear-gradient(
    90deg,
    color-mix(in srgb, var(--proj) 13%, transparent),
    transparent 78%
  );
  opacity: 0;
  transform: scaleX(0.94);
  transform-origin: left;
  transition: opacity 0.5s var(--ease), transform 0.6s var(--ease);
  pointer-events: none;
}

.work-row:hover::before,
.work-row:focus-visible::before,
.work-row.is-active::before {
  opacity: 1;
  transform: none;
}

/* La banda: columna del glifo en escritorio, escenario del objeto 3D en
   móvil. El mismo hueco haciendo dos trabajos distintos. */
.work-stage {
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}

.work-glyph {
  width: clamp(52px, 6vw, 92px);
  flex: none;
  color: var(--proj);
  transition: transform 0.55s var(--ease), opacity 0.6s var(--ease);
}

.work-glyph svg {
  width: 100%;
  height: auto;
  display: block;
  shape-rendering: crispEdges;
}

.work-glyph .g1 {
  fill: currentColor;
}

/* El segundo tono es de apoyo, no compite: el color del proyecto manda. */
.work-glyph .g2 {
  fill: var(--bone);
  opacity: 0.58;
}

/* Los glifos que reproducen un logotipo con colores propios no pasan por aquí:
   traen su `fill` puesto desde la tabla INKS de main.js, la misma que tiñe los
   cubos. Teñirlos con el color de la fila sería dibujar otro logotipo. */

.work-row:hover .work-glyph,
.work-row.is-active .work-glyph {
  transform: translateY(-6px) rotate(-4deg);
}

.work-main {
  display: grid;
  gap: 0.35rem;
  min-width: 0;
}

.work-index {
  font-family: 'Martian', ui-monospace, monospace;
  font-size: var(--fs-label);
  letter-spacing: 0.16em;
  color: var(--proj);
  opacity: 0.72;
  transition: opacity 0.4s var(--ease);
}

.work-row:hover .work-index,
.work-row.is-active .work-index {
  opacity: 1;
}

.work-name {
  font-family: 'Bricolage', sans-serif;
  font-weight: 700;
  font-size: clamp(1.9rem, 3.6vw, 3.1rem);
  line-height: 0.94;
  letter-spacing: -0.042em;
  font-variation-settings: 'wdth' 90, 'opsz' 48;
  transition: font-variation-settings 0.55s var(--ease), color 0.4s var(--ease);
}

.work-row:hover .work-name,
.work-row.is-active .work-name {
  font-variation-settings: 'wdth' 100, 'opsz' 48;
  color: var(--proj);
}

.work-desc {
  color: var(--fg-dim);
  font-size: 1rem;
  line-height: 1.5;
  max-width: 44ch;
}

.work-stage { grid-area: 1 / 1 / 3 / 2; }
.work-main { grid-area: 1 / 2 / 3 / 3; }
.work-desc { grid-area: 1 / 3 / 2 / 4; align-self: end; }
.work-aside { grid-area: 2 / 3 / 3 / 4; align-self: start; }

.work-aside {
  display: grid;
  gap: 0.5rem;
  justify-items: start;
}

.work-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem 0.9rem;
  font-family: 'Martian', ui-monospace, monospace;
  font-size: var(--fs-label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-dim);
}

.work-go {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-family: 'Martian', ui-monospace, monospace;
  font-size: var(--fs-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--fg-dim);
  transition: color 0.4s var(--ease), transform 0.5s var(--ease);
}

.work-row:hover .work-go,
.work-row.is-active .work-go {
  color: var(--proj);
  transform: translateX(6px);
}

/* «Bajo llave» es información, no decoración: dice por qué esa fila no
   lleva a ningún sitio. Estaba a 0.42 de opacidad sobre --fg-dim, o sea
   2:1 de contraste — el único texto de la página que no se podía leer. */
.work-go[data-static] {
  opacity: 1;
  color: var(--fg-dim);
}

.work-row:hover .work-go[data-static],
.work-row.is-active .work-go[data-static] {
  transform: none;
  color: var(--fg-dim);
}

/* La lista sin JavaScript: solo nombres, en la misma voz de la página. */
.work-plain {
  display: grid;
  gap: 1rem;
  font-family: 'Bricolage', sans-serif;
  font-weight: 700;
  font-size: clamp(1.6rem, 3vw, 2.4rem);
  line-height: 1;
  letter-spacing: -0.04em;
  font-variation-settings: 'wdth' 90, 'opsz' 48;
}

.work-plain a {
  color: var(--yellow);
}

/* 11. PROCESO (panel de arena) --------------------------------------------- */

.process {
  --bg: var(--sand);
  --fg: var(--sand-ink);
  --fg-dim: var(--sand-dim);
  --accent: var(--sienna);
}

.process .pxrule {
  color: var(--sienna);
}

.process-title {
  font-size: var(--fs-xl);
  max-width: 14ch;
}

.steps {
  display: grid;
  gap: clamp(2.6rem, 5vw, 3.5rem);
  counter-reset: step;
}

.step {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: clamp(1.2rem, 3vw, 2.4rem);
  align-items: start;
}

.step-num {
  width: clamp(30px, 3.4vw, 44px);
  flex: none;
  color: var(--sienna);
  margin-top: 0.35rem;
}

.step-num svg {
  width: 100%;
  height: auto;
  display: block;
  fill: currentColor;
  shape-rendering: crispEdges;
}

.step-body {
  display: grid;
  gap: 0.5rem;
}

.step-name {
  font-family: 'Bricolage', sans-serif;
  font-weight: 700;
  font-size: var(--fs-lg);
  line-height: 1;
  letter-spacing: -0.03em;
  font-variation-settings: 'wdth' 92, 'opsz' 32;
}

.step-text {
  color: var(--fg-dim);
  max-width: 44ch;
  font-size: 1.02rem;
}

/* Los precios. Tres filas de tipografía separadas por la regla de píxeles,
   nunca tres tarjetas: la rejilla de cajas con precio es el mueble de la
   plantilla comprada. El nombre y la cifra son la misma letra al mismo
   tamaño —es una sola línea de texto, no una etiqueta y su valor—; lo que
   los separa es el color, que es como se separan las cosas en esta página. */
.prices {
  margin-top: clamp(3.5rem, 8vw, 6rem);
  /* La página llega a 1560px y el nombre y la cifra viven en los extremos:
     sin tope, en un monitor ancho hay medio metro entre «Landing page» y su
     precio y la fila deja de leerse como una fila. Mismo ancho que el
     bloque de contacto. */
  max-width: min(100%, 62rem);
}

.prices-head {
  margin-bottom: clamp(1.4rem, 3vw, 2rem);
}

.price {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.2rem 2rem;
  padding-block: clamp(1rem, 2.2vw, 1.5rem);
}

.price-name,
.price-fig {
  font-family: 'Bricolage', sans-serif;
  font-weight: 700;
  font-size: var(--fs-lg);
  line-height: 1.05;
  letter-spacing: -0.03em;
  font-variation-settings: 'wdth' 92, 'opsz' 32;
}

.price-name {
  max-width: 22ch;
}

.price-fig {
  color: var(--accent);
  white-space: nowrap;
}

.prices-foot {
  margin-top: clamp(1.4rem, 3vw, 2rem);
  max-width: 52ch;
  font-size: 1.02rem;
  color: var(--fg-dim);
}

.process-note {
  margin-top: clamp(3rem, 7vw, 5rem);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.6rem 1rem;
  font-family: 'Martian', ui-monospace, monospace;
  font-size: var(--fs-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-dim);
}

/* 12. CONTACTO ------------------------------------------------------------- */

.contact {
  padding-block: clamp(7rem, 20vh, 15rem);
  background: transparent;
}

.contact-grid {
  display: grid;
  justify-items: start;
  gap: clamp(1.1rem, 2.4vw, 1.8rem);
  max-width: min(100%, 62rem);
}

.contact-ask {
  font-size: var(--fs-xl);
  max-width: 13ch;
  margin-bottom: clamp(0.6rem, 2vw, 1.4rem);
}

/* El gesto grande de la página. El mínimo era 1.75rem: en un teléfono de
   390px eso son 28px de letra y 27 de alto tocable, o sea que la acción
   principal del sitio era el objetivo más pequeño de la pantalla. El
   relleno vertical lo lleva por encima de los 44px sin tocar la línea. */
.mail {
  display: inline-block;
  padding-block: 0.35rem;
  font-family: 'Bricolage', sans-serif;
  font-weight: 700;
  font-size: clamp(2.35rem, 6.6vw, 5.4rem);
  line-height: 0.95;
  letter-spacing: -0.045em;
  font-variation-settings: 'wdth' 86, 'opsz' 72;
  color: var(--yellow);
  word-break: break-word;
  transition: font-variation-settings 0.6s var(--ease), color 0.4s var(--ease);
}

.mail:hover,
.mail:focus-visible {
  font-variation-settings: 'wdth' 100, 'opsz' 72;
  color: var(--pink);
}

/* El botón de WhatsApp. Deliberadamente por debajo del correo en tamaño: el
   correo sigue siendo el gesto grande de la sección y este es el atajo para
   quien prefiere escribir desde el teléfono. Sale de `hidden` solo cuando
   main.js le ha puesto el número. */
/* `display` en una clase le gana al `[hidden]` del navegador, así que el
   botón se veía igual sin JavaScript: visible, sin número detrás y sin
   llevar a ningún sitio, que es justo lo que el marcado quería evitar.
   La regla de abajo devuelve al atributo el mando que le corresponde. */
.wa[hidden] {
  display: none;
}

.wa {
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  margin-top: 0.4rem;
  padding: 0.92rem 1.25rem;
  cursor: pointer;
  font-family: 'Martian', ui-monospace, monospace;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink);
  background: var(--tan);
  transition: background 0.35s var(--ease), color 0.35s var(--ease),
    translate 0.35s var(--ease);
}

.wa:hover,
.wa:focus-visible {
  background: var(--yellow);
  translate: 0 -3px;
}

/* El bocadillo del icono: seis cuadros, dibujados con sombras en vez de con
   un SVG, porque a este tamaño el dibujo es más corto que su etiqueta. */
.wa-glyph {
  width: 3px;
  height: 3px;
  flex: none;
  margin: 0 9px 6px 0;
  background: currentColor;
  box-shadow:
    3px 0 0 currentColor, 6px 0 0 currentColor, 9px 0 0 currentColor,
    0 3px 0 currentColor, 9px 3px 0 currentColor,
    0 6px 0 currentColor, 3px 6px 0 currentColor, 6px 6px 0 currentColor,
    9px 6px 0 currentColor,
    3px 9px 0 currentColor;
}

.contact-text {
  color: var(--fg-dim);
  max-width: 44ch;
  margin-top: clamp(0.6rem, 2vw, 1.2rem);
}

/* Lo del dinero. No lleva cifra —no hay tarifa publicada y no se inventa—,
   lleva el mecanismo, que es lo que de verdad se pregunta quien escribe:
   cuándo sabe lo que cuesta y qué pasa si no le cuadra. Va en Martian y no
   en la sans para que se lea como una condición y no como más argumento. */
.contact-terms {
  max-width: 46ch;
  font-family: 'Martian', ui-monospace, monospace;
  font-size: 0.78rem;
  line-height: 1.55;
  letter-spacing: 0.01em;
  color: var(--tan);
}

/* Lo que dice Esteban. Sigue a su cabeza fotograma a fotograma: main.js le
   pasa la posición ya proyectada a píxeles y recortada para que no se salga
   de la pantalla, y el `translate` la centra encima de él.

   **Va en la capa de Esteban, no encima de todo.** Empezó en z 45 y estaba
   mal por dos motivos a la vez: tapaba el texto, y se leía como pegada a la
   pantalla mientras el dueño de la burbuja estaba al fondo, detrás del velo.
   En z 0 —justo encima del lienzo por orden de marcado, debajo del velo, del
   grano y del contenido— la burbuja se apaga con él y el texto le gana
   siempre, que es la regla de toda la página.

   Y si el lienzo se pone por delante, la burbuja se va con él: son la misma
   cosa y separarlas es justo el error que había. */
.says {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.5s var(--ease);
  will-change: transform;
}

.stage.is-front ~ .says {
  z-index: 45;
}

.says.is-on {
  opacity: 1;
}

.says-bubble {
  position: relative;
  transform: translate(-50%, -100%);
  max-width: min(15rem, 62vw);
  padding: 0.7rem 0.9rem;
  /* Oscura con el borde claro, no al revés: estando por debajo del contenido,
     un bloque color hueso deja el texto de la página escribiendo blanco sobre
     blanco cuando se cruzan. Así el texto que pase por encima se sigue
     leyendo igual que sobre la página, y el relleno tapa los cubos de detrás
     lo justo para que la burbuja se lea como un objeto y no como niebla.
     El contorno va en `box-shadow` y no en `border` para no mover el relleno
     ni redondear nada. */
  background: var(--ink-lift);
  color: var(--bone);
  box-shadow: 0 0 0 2px var(--bone);
  font-family: 'Martian', ui-monospace, monospace;
  font-size: 0.78rem;
  line-height: 1.45;
  text-align: center;
  text-wrap: balance;
}

/* El cursor del tecleado: un cuadro, no una barra. Es el `::after` porque
   tiene que ir DETRÁS del texto, en el flujo; la cola se queda con el
   `::before`, que al ir posicionado en absoluto le da igual el orden. */
.says-bubble::after {
  content: '';
  display: inline-block;
  width: 0.5em;
  height: 0.95em;
  margin-left: 0.12em;
  vertical-align: -0.12em;
  background: currentColor;
  animation: says-caret 1s steps(1, end) infinite;
}

/* La cola, en escalera de dos cuadros: una punta suave desentona con una
   página que dibuja hasta las reglas con píxeles. */
.says-bubble::before {
  content: '';
  position: absolute;
  left: calc(50% - 9px);
  top: 100%;
  width: 6px;
  height: 6px;
  background: var(--bone);
  box-shadow: 6px 6px 0 var(--bone);
}

@keyframes says-caret {
  0% {
    opacity: 1;
  }
  50% {
    opacity: 0;
  }
}

/* Sin escena no hay a quién señalar. */
.no-webgl .says {
  display: none;
}

.clock {
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
  font-family: 'Martian', ui-monospace, monospace;
  font-size: var(--fs-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-dim);
}

.clock-time {
  color: var(--tan);
  font-variant-numeric: tabular-nums;
  font-size: 0.95rem;
  letter-spacing: 0.08em;
}

/* 13. PIE ------------------------------------------------------------------ */

.foot {
  padding-block: clamp(2.5rem, 5vw, 4rem) clamp(2rem, 4vw, 3rem);
  position: relative;
}

.foot-rule {
  color: var(--tan);
  margin-bottom: clamp(2rem, 4vw, 3rem);
}

.foot-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1.2rem 2rem;
  font-family: 'Martian', ui-monospace, monospace;
  font-size: var(--fs-label);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--fg-dim);
}

.foot-brand {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--fg);
}

.foot-mark {
  width: 18px;
  image-rendering: pixelated;
}

.foot a:hover {
  color: var(--yellow);
}

/* 14. REVELADOS ------------------------------------------------------------ */

.rv {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity 0.85s var(--ease), transform 0.95s var(--ease);
  transition-delay: calc(var(--i, 0) * 70ms);
}

.rv.is-in {
  opacity: 1;
  transform: none;
}

/* El título del hero entra letra por letra: es el único momento
   coreografiado de la página, y coincide con la compilación de Esteban. */
.tw-word {
  display: inline-block;
  white-space: nowrap;
}

.tw {
  display: inline-block;
  opacity: 0;
  transform: translateY(0.42em) rotate(2deg);
  animation: tw-in 0.9s var(--ease) forwards;
  animation-delay: calc(var(--d) * 34ms + 120ms);
}

@keyframes tw-in {
  to {
    opacity: 1;
    transform: none;
  }
}

/* 15. ADAPTATIVO ----------------------------------------------------------- */

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

  .studio-grid {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
    align-items: start;
  }

}

@media (min-width: 900px) {
  .work-list {
    --case: clamp(200px, 21vw, 340px);
    padding-right: var(--case);
  }

  .steps {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: clamp(3rem, 6vw, 5rem) clamp(3rem, 7vw, 7rem);
  }

  .sec-head {
    flex-direction: row;
    align-items: flex-end;
    justify-content: space-between;
    gap: clamp(2rem, 6vw, 6rem);
  }

  .sec-head .lede {
    max-width: 32ch;
    padding-bottom: 0.35rem;
  }
}

/* Móvil: la lista de trabajo se reordena y cada proyecto lleva su lavado
   de color puesto, no escondido tras un hover que no existe. */
@media (max-width: 899px) {
  /* En móvil la fila deja de ser una fila: cada proyecto se lleva su
     propia banda, y el objeto 3D aterriza ahí en cuanto queda centrado
     en pantalla. Bajar la lista es pasar seis objetos, no seis párrafos. */
  .work-row {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: none;
    align-items: start;
    gap: 1rem;
    padding-block: 2.2rem 2.8rem;
  }

  /* Aquí no hay rejilla de tres columnas que colocar: una sola pila. */
  .work-stage,
  .work-main,
  .work-desc,
  .work-aside {
    grid-area: auto;
    align-self: auto;
  }

  .work-row::before {
    opacity: 0.42;
    transform: none;
    inset-inline: calc(var(--gutter) * -1);
    background: linear-gradient(
      165deg,
      color-mix(in srgb, var(--proj) 20%, transparent),
      transparent 68%
    );
  }

  .work-row.is-active::before {
    opacity: 1;
  }

  .work-stage {
    width: 100%;
    height: clamp(180px, 30vh, 280px);
    margin-bottom: 0.4rem;
  }

  .work-glyph {
    width: clamp(120px, 34vw, 180px);
  }

  /* Cuando los cubos llegan a la banda, el glifo plano se aparta: es el
     mismo dibujo, no hacen falta los dos. Sin WebGL se queda él solo. */
  .work-row.is-active .work-glyph {
    opacity: 0.1;
    transform: scale(0.9);
  }

  .no-webgl .work-row.is-active .work-glyph {
    opacity: 1;
    transform: translateY(-6px) rotate(-4deg);
  }

  .work-main {
    gap: 0.5rem;
  }

  .work-go {
    color: var(--proj);
  }

  /* En móvil esto se escondía y la fila quedaba sin explicación: seis
     proyectos idénticos y tres que no responden al tocarlos. Se queda, en
     el tono apagado, para que no se lea como un enlace. */
  .work-go[data-static] {
    color: var(--fg-dim);
  }

  .work-name {
    color: var(--proj);
  }

  .hero-title {
    max-width: 11ch;
  }

  .statement,
  .lede {
    max-width: none;
  }
}

@media (max-width: 620px) {
  .nav-links .nav-link {
    display: none;
  }

  .nav-links .nav-link[data-keep] {
    display: inline-block;
  }

  .brand-name {
    font-size: 0.62rem;
    letter-spacing: 0.12em;
  }

  /* En pantallas muy estrechas el logotipo se queda solo con la llama:
     entre el nombre y el enlace de contacto, gana el enlace. */
  /* El logotipo aguanta hasta donde le da la barra: primero encoge el
     nombre y solo en lo más estrecho se queda la llama sola. */
  @media (max-width: 430px) {
    .brand-name {
      font-size: 0.55rem;
      letter-spacing: 0.09em;
    }

    .nav-link {
      font-size: 0.625rem;
    }
  }

  /* Por debajo de esto ya no caben logotipo y enlace a la vez. Se va el
     enlace, no la marca: el contacto está a un scroll y en el pie. */
  @media (max-width: 380px) {
    .nav-links .nav-link[data-keep] {
      display: none;
    }
  }

  /* El velo se centraba en mitad de la pantalla, que es exactamente donde
     vive Esteban en el hero de móvil: lo tapaba a él en vez de despejar el
     texto. Ahora el foco cae sobre la columna de texto, a la izquierda y algo
     más abajo, y pesa menos. */
  .veil {
    background: radial-gradient(
        135% 75% at 34% 52%,
        rgba(11, 7, 5, 0.86) 0%,
        rgba(11, 7, 5, 0.5) 44%,
        rgba(11, 7, 5, 0.08) 76%
      ),
      linear-gradient(180deg, rgba(11, 7, 5, 0.8) 0%, rgba(11, 7, 5, 0) 26%);
  }
}

/* 16. MOVIMIENTO REDUCIDO -------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }

  .rv,
  .tw {
    opacity: 1;
    transform: none;
  }
}
