/* ============================================================
   Caliber Studio — v3
   Todo lo que hay aquí sale de medición, no de gusto:
   · pesos 100-400 (ninguna referencia usa 600 como display)
   · UNA voz grande y caída de ~1.6x por peldaño (no la meseta 83·82·69·67)
   · duraciones 0.15-0.3s (las refs miden eso; la v2 iba a 0.4-0.5s)
   · tracking negativo SOLO arriba; peso ligero pide tracking positivo
   ============================================================ */

/* ---------- 1 · TOKENS ---------- */
:root{
  /* Papel — nunca blanco de sistema (benorth #F4F3EF, rideradian #F5F3EF) */
  --papel:      #F4F1EA;
  --papel-alto: #EAE6DC;
  --papel-frio: #ECEDEF;

  /* Oscuro — las 4 superficies del portal, que ya están aprobadas */
  --tinta:   #08090C;
  --sup-1:   #101216;
  --sup-2:   #181B21;
  --sup-3:   #1F232B;

  /* Texto por rampa de opacidad (método del portal), no por 8 grises sueltos */
  --claro-1: rgba(9,10,13,.92);
  --claro-2: rgba(9,10,13,.60);
  --claro-3: rgba(9,10,13,.50);   /* mismo motivo que --osc-3, lado claro */
  --osc-1:   rgba(237,239,243,1);
  --osc-2:   rgba(237,239,243,.62);
  /* .40 daba 3.42 de contraste sobre el fondo #08090C — bajo el 4.5 de AA, y
     este token viste rótulos de 12px, que es justo donde menos se perdona.
     Medido compuesto sobre el fondo (el alfa hay que aplicarlo ANTES de medir;
     ignorarlo devuelve 17.3 y parece que todo está bien). A .50 da 4.76. */
  --osc-3:   rgba(237,239,243,.50);

  /* El único calor. Ya existe y ya lo ven los clientes en el portal */
  --oro:     #D8C58C;
  --oro-vivo:#C6A961;
  --bronce:  #7A5F1F;   /* el oro, pero legible como TEXTO sobre papel (4.84) */

  --linea-clara: rgba(9,10,13,.12);
  --linea-osc:   rgba(255,255,255,.12);

  /* Tipografía — la MISMA del portal. Un cliente que ve el portal y el sitio
     debe sentir una sola marca; eso vale más que estrenar una cara.
     Todo el carácter sale de cómo se usan, no de cuáles son. */
  --display: 'Clash Display', 'General Sans', system-ui, sans-serif;
  --texto:   'General Sans', system-ui, sans-serif;

  /* Escala CON CAÍDA. Cinco peldaños, razón ~1.6 */
  /* UNA sola voz grande — y ahora de verdad. El token decía 132px pero el hero
     lo pisaba con su propio `clamp` (86px medidos), así que el sitio tenía DOS
     escalas grandes y la del cierre gritaba 50% más que la de la apertura. El
     token toma el valor que ya funcionaba en el hero y el override se fue: el
     cierre saca su fuerza de estar solo en una pantalla oscura, no de ser más
     grande que el titular con el que abre. */
  --t1: clamp(2.5rem, 6vw, 5.4rem);    /* 86px */
  --t2: clamp(2.2rem, 5vw, 4.5rem);    /* 72  */
  --t3: clamp(1.6rem, 3vw, 2.75rem);   /* 44  */
  --t4: clamp(1.1rem, 1.6vw, 1.375rem);/* 22  */
  --t5: .9375rem;                      /* 15  */

  /* ---- PESO — la palanca que estaba apagada -----------------------
     Censo del sitio antes de esto: el display vivía en 200/300/400 y el
     tope de TODA la página era 500. Cero negritas. Con siete archivos
     estáticos no había de dónde: la variable ahora da 200–700 continuo.
     Y el eje del peso es lo que permite jerarquía SIN subir el tamaño —
     que es el problema real, porque medido en vivo el sitio renderiza
     15 escalones de tamaño cuando la mediana de las casas de lujo es 5.
     Nombres por FUNCIÓN, no por número: así el rol no se puede usar mal. */
  --p-aire:   200;   /* el hero y nada más: una sola voz respirando */
  --p-voz:    300;   /* titulares de sección */
  --p-cuerpo: 400;   /* párrafo largo — a 22px el 500 se apelmaza */
  --p-ui:     500;   /* interfaz, rótulos, listas, datos */
  --p-golpe:  650;   /* el remate. 650 y no 700: a 130px el 700 se
                        embota y las contraformas de la o se cierran */

  /* Movimiento */
  --rapido: .16s;
  --base:   .24s;
  --lento:  .32s;
  --curva:  cubic-bezier(.23,1,.32,1);

  --borde: clamp(1.25rem, 5vw, 5rem);
  --aire:  clamp(5rem, 12vh, 11rem);

  /* ---- C1 · EL GRANO QUE NUNCA EXISTIÓ ----------------------------
     El CSS lo invocaba desde el día uno y el token jamás se definió:
     `background-image:var(--grano)` resolvía a `none`. Eran 8,887px de
     papel absolutamente liso — ESA era la planitud, no el gusto.
     Va como feTurbulence en data-URI y no como PNG: ~500 bytes, misma
     nitidez a cualquier densidad de pantalla, cero peticiones. */
  --grano: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23g)'/%3E%3C/svg%3E");

  /* ---- C5 · LA SEGUNDA CAPA FIJA ----------------------------------
     Medido: basement 8 capas fijas, obys 3, lusion 2 — la v3 tenía UNA.
     Con `background-attachment:fixed` la rejilla se queda quieta y el
     contenido se desliza encima: es paralaje real sin JS y, a diferencia
     de animar background-position (lo que hace basement), no repinta
     una sección de 3,400px en cada cuadro. */
  --rejilla:
    linear-gradient(rgba(9,10,13,.055) 1px, transparent 1px),
    linear-gradient(90deg, rgba(9,10,13,.055) 1px, transparent 1px);
}

*,*::before,*::after{ box-sizing:border-box }
html{ -webkit-text-size-adjust:100% }
body{
  margin:0; background:var(--papel); color:var(--tx-1);
  font-family:var(--texto); font-size:var(--t5); font-weight:500;
  line-height:1.6; -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
/* `height:auto` es parte del reset, no un adorno: sin él, el atributo
   `height` del HTML se queda fijo mientras `max-width` sí encoge el ancho, y
   la imagen se estira. Salió a la luz con la captura del panel: 741px de
   ancho con 1350 de alto en vez de 417. */
img,video,canvas{ max-width:100%; height:auto; display:block }
a{ color:inherit; text-decoration:none }

/* ---------- 2 · TIPOGRAFÍA ---------- */
.d1,.d2,.d3{ font-family:var(--display); font-weight:var(--p-voz); margin:0 }
.d1{ font-size:var(--t1); line-height:.94; letter-spacing:-.035em; font-weight:var(--p-aire) }
.d2{ font-size:var(--t2); line-height:1.02; letter-spacing:-.02em }
.d3{ font-size:var(--t3); line-height:1.1;  letter-spacing:-.01em }
/* peso ligero ⇒ el tracking ABRE, no cierra (mountstreet mide +0.8px a peso 100) */
.d1.fino{ font-weight:var(--p-aire); letter-spacing:.005em }

/* ---- EL GOLPE — el remate de la frase, en peso, no en tamaño ni en color
   Esto es lo que hacen las marcas y lo que el sitio no hacía en ninguna parte:
   dentro de UN titular, la parte que carga el sentido pesa más. El tamaño no
   cambia, el color no cambia, la familia no cambia — solo el peso. Ventajas
   contra las alternativas que ya estaban descartadas: no mete un color de
   acento en una identidad monocroma, no agrega un escalón a una escala que ya
   tiene 15, y no depende de la cursiva (medido: las 6 refs usan CERO).
   Sobre el tracking me equivoqué a la primera: cerré a -.045em pensando que a
   más masa hace falta menos aire, y a 86px "no tiene." se leyó **"notiene"** —
   el `letter-spacing` negativo encoge TAMBIÉN el espacio entre palabras, y al
   peso 650 las letras ya traen su propia masa, así que el espacio es lo primero
   que se pierde. Queda en -.02em con el `word-spacing` compensado: el remate se
   ve compacto sin que las palabras se peguen. */
.golpe{ font-weight:var(--p-golpe); letter-spacing:-.02em; word-spacing:.06em }

.cursiva{ font-style:italic; font-weight:var(--p-voz) }   /* el segundo registro */
.rotulo{
  font-family:var(--texto); font-size:.75rem; font-weight:500;
  letter-spacing:.14em; text-transform:uppercase;
  /* NO mono: la JetBrains uppercase es lo que leía a "coordenadas" */
  /* `case` — formas para caja alta. Sube el punto medio, los paréntesis y
     los signos de apertura a altura de mayúscula. En un rótulo en versalitas
     un `·` o un `¿` colgados a altura de minúscula se ven caídos, y es de
     esos detalles que nadie nombra pero todos registran.
     Verificado por píxel: Clash Display NO expone NINGUNA feature en la
     versión web de Fontshare; General Sans expone ss01, salt y case. Por eso
     esto solo puede vivir en los elementos puestos en General Sans. */
  font-feature-settings:'case' 1;
}
/* El párrafo grande se queda en 400: a 22px el 500 se apelmaza. El contraste
   NO se busca subiendo todo — se busca ABRIENDO LA BRECHA. Censo de las refs:
   hildenkaira 500×227 nodos, bymonolog 500×187, la v3 iba en 300×31 con TODO
   entre 200 y 400, o sea sin jerarquía. Ahora: display 200-300 · cuerpo 500 ·
   párrafo largo 400. Tres registros que se distinguen a un metro. */
.parrafo{ font-size:var(--t4); line-height:1.55; max-width:34ch; font-weight:var(--p-cuerpo) }
.tenue{ color:var(--tx-2) }
.oscuro .tenue{ color:var(--tx-2) }

/* ---------- 3 · RITMO TONAL ---------- */
.seccion{ padding:var(--aire) var(--borde); position:relative }
/* ── EL MUNDO TONAL ES UNA CLASE, NO UN REFACTOR ──────────────────────────
   Antes cada sección cableaba sus colores de texto: 25 usos de tokens claros y
   23 de oscuros repartidos por todo el archivo. Cambiar una sección de papel a
   tinta dejaba su texto ilegible, así que "comprometerse con un mundo tonal"
   costaba una tarde en vez de una palabra.
   Ahora la clase de tono declara qué significan --tx-1/2/3 y la línea, y todo
   lo demás los consume sin saber en qué mundo vive. Cambiar el tono de una
   sección vuelve a ser cambiar una clase.
   Auditoría que lo motivó (2026-07-30): el sitio cruzaba entre claro y oscuro
   OCHO veces en 19 pantallas; basement, rive y linear no cruzan nunca y
   locomotive cruza una vez. Un sitio no tiene atmósfera si la cambia cada dos
   pantallas. ──────────────────────────────────────────────────────────────── */
.papel, .papel-alto, .frio, body{
  --tx-1: rgba(9,10,13,.92); --tx-2: rgba(9,10,13,.60); --tx-3: rgba(9,10,13,.50);
  --tx-linea: rgba(9,10,13,.12);
}
.oscuro{
  --tx-1: rgba(237,239,243,1); --tx-2: rgba(237,239,243,.62); --tx-3: rgba(237,239,243,.50);
  --tx-linea: rgba(255,255,255,.12);
}
.papel      { background:var(--papel) }
.papel-alto { background:var(--papel-alto) }
.frio       { background:var(--papel-frio) }
.oscuro     { background:var(--tinta); color:var(--tx-1) }
.oscuro .rotulo{ color:var(--tx-3) }
.rotulo{ color:var(--tx-3) }

/* grano + rejilla fija — anti-planitud sobre papel.
   Dos capas en una sola pintura: la rejilla queda FIJA a la ventana
   (segunda capa de profundidad) y el grano viaja con la sección. */
.papel::after,.papel-alto::after,.frio::after{
  /* El grano baja de .55 a .16 en las superficies CLARAS. Medido aislando el
     fondo (todo el contenido escondido): el papel variaba 55 niveles (184-239)
     mientras basement, linear y locomotive varían CERO en el suyo. Ese mismo
     grano sobre tinta lee como grano de película; sobre papel lee como papel
     sucio, y es exactamente la asimetría que se sentía —"los oscuros sí se ven
     bien"—. No se quita: se baja hasta donde da piel sin dar mugre. */
  content:''; position:absolute; inset:0; pointer-events:none; opacity:.16;
  background-image:var(--rejilla), var(--grano);
  background-size:104px 104px, 104px 104px, 160px 160px;
  background-attachment:fixed, fixed, scroll;
  mix-blend-mode:multiply;
}
/* la rejilla se apaga en la sección de más texto: ahí compite con el renglón.
   Va acotado a `.papel` A PROPÓSITO: sin ese acote la regla también le ganaba
   a `.oscuro::after`, y como #trabajo pasó a tinta, le estaba borrando el
   CIELO COMPLETO. Se midió con el contenido escondido: #trabajo daba
   exactamente los mismos números con el cielo encendido y apagado. Un
   `#id::after` sin acotar es más específico que cualquier regla de clase, así
   que se lleva por delante lo que venga después. */
#trabajo.papel::after{ background-image:var(--grano); background-size:160px 160px;
                       background-attachment:scroll }

/* En móvil la rejilla fija se apaga: iOS repinta la capa `fixed` en cada
   cuadro de scroll con rebote, y a 390px de ancho una retícula de 104px es
   ruido, no profundidad. El grano sí se queda — es lo que da la piel. */
@media (max-width:760px){
  .papel::after,.papel-alto::after,.frio::after{
    background-image:var(--grano); background-size:160px 160px;
    background-attachment:scroll;
  }
}

/* ---------- 4 · CABECERA ---------- */
.cab{
  position:fixed; inset:0 0 auto; z-index:60; display:flex;
  align-items:center; justify-content:space-between;
  padding:1.1rem var(--borde);
  transition:background var(--base) var(--curva), color var(--base) var(--curva);
  mix-blend-mode:difference; color:#fff;
}
.cab__marca{ font-family:var(--display); font-weight:400; font-size:1.05rem; letter-spacing:-.01em }

/* ── LA CABECERA SE RETIRA AL BAJAR (sólo teléfono) ────────────────────────
   `mix-blend-mode:difference` garantiza que la cabecera se LEA sobre cualquier
   fondo, pero no impide que se monte encima de otro texto. A 390px eso pasa
   constantemente: capturado, "Caliber Studio" y "Empezar ↗" quedaban impresos
   sobre las palabras de las capturas del panel. En escritorio hay margen
   lateral de sobra y no ocurre, por eso el gesto es sólo de teléfono.

   Se retira al bajar y vuelve al subir — el movimiento la convierte en parte
   del lenguaje en vez de un parche. Nunca se esconde arriba del todo (el hero
   la necesita) ni con movimiento reducido. */
@media (max-width:899px){
  .cab{ transition:background var(--base) var(--curva), color var(--base) var(--curva),
                   transform .34s cubic-bezier(.32,.72,0,1) }
  .cab--oculta{ transform:translateY(-115%) }
}
@media (prefers-reduced-motion:reduce){
  .cab{ transition:none }
  .cab--oculta{ transform:none }
}

/* ---------- 5 · HERO ---------- */
.hero{ min-height:100svh; display:grid; align-content:center; overflow:clip }
.hero__lienzo{ position:absolute; inset:0; z-index:0 }
.hero__lienzo canvas{ width:100%; height:100%; display:block }
.hero__cuerpo{ position:relative; z-index:2 }
/* El hero cabe en UNA pantalla junto con la prueba. Un hero de 1.6 pantallas
   empuja el trabajo fuera de vista, que es justo el error que corregimos. */
.hero .d1{ max-width:13ch; line-height:.98 }   /* el tamaño ya lo da --t1 */
.hero__pie{
  position:relative; z-index:2; display:flex; gap:2.5rem; flex-wrap:wrap;
  margin-top:3.5rem; align-items:flex-end; justify-content:space-between;
}
/* la prueba asoma desde el primer pliegue: se ve que hacemos webs */
.hero__tiras{ display:flex; gap:.75rem }
.tira{
  width:clamp(72px,9vw,124px); aspect-ratio:9/16; border-radius:3px;
  background:var(--sup-2); border:1px solid var(--tx-linea);
  overflow:hidden; position:relative;
}

/* ---------- 6 · HUECOS DE MATERIAL ---------- */
/* Marcados a propósito. Un hueco honesto es mejor que una imagen de relleno. */
.hueco{
  position:relative; background:
     repeating-linear-gradient(135deg,transparent 0 11px,rgba(127,127,127,.10) 11px 12px);
  border:1px solid var(--tx-linea); border-radius:2px;
  display:grid; place-content:center; text-align:center; padding:1rem;
}
.oscuro .hueco{ border-color:var(--tx-linea) }
.hueco span{ font-size:.75rem; letter-spacing:.12em; text-transform:uppercase; color:var(--tx-3) }
.oscuro .hueco span{ color:var(--tx-3) }
.hueco--16x9{ aspect-ratio:16/9 } .hueco--4x5{ aspect-ratio:4/5 }
.hueco--9x16{ aspect-ratio:9/16 } .hueco--1x1{ aspect-ratio:1/1 }

/* ---------- 7 · TRABAJO ---------- */
.trabajo__cab{ display:flex; justify-content:space-between; align-items:end; gap:2rem; margin-bottom:3.5rem }
/* El caso invertido va rotado 2° y el paralaje lo desplaza: entre las dos cosas
   el renglón sacaba 17px de desborde horizontal. `clip` y NO `hidden` a
   propósito — `hidden` crea un contenedor de scroll y rompería cualquier
   `position:sticky` que viva adentro; `clip` recorta sin crearlo. */
.casos{ overflow-x: clip }

.casos{ display:grid; gap:clamp(3rem,7vw,7rem) }
.caso{ display:grid; gap:1.5rem; grid-template-columns:1fr; align-items:end }
@media (min-width:900px){
  .caso{ grid-template-columns:1.35fr .65fr; gap:3rem }
  /* al invertir hay que invertir TAMBIÉN las columnas: si no, el medio cae en
     la columna angosta y el caso alternado se ve raquítico */
  .caso--invertido{ grid-template-columns:.65fr 1.35fr }
  .caso--invertido .caso__media{ order:2 }
}
.caso__meta{ display:flex; flex-direction:column; gap:.6rem }
/* el color entra por CADA CLIENTE, no por una paleta que inventemos */
.caso__punto{ width:10px; height:10px; border-radius:50%; background:var(--color-caso,var(--oro)) }
/* El número era el ancla del caso y no estaba anclando nada: el HTML llevaba
   `class="caso__num mono"` y `.mono` se declara 180 líneas MÁS ABAJO con la
   misma especificidad, así que ganaba entera — el número renderizaba en General
   Sans a 12px peso 500, no en display a 44. Se veía plausible (un numerito
   discreto arriba del nombre) y por eso nadie lo cuestionó; solo aparece
   mirando el estilo COMPUTADO. Se quitó el `mono` del HTML. */
.caso__num{
  font-family:var(--display); font-size:var(--t3);
  font-weight:var(--p-golpe); letter-spacing:-.04em;
  font-variant-numeric:tabular-nums;
  color:var(--tx-3);
}
.caso__titulo{ font-family:var(--display); font-size:var(--t2); font-weight:var(--p-voz); letter-spacing:-.02em; line-height:1.04; margin:.2rem 0 }
/* "diseño entregado", "en operación" — es un SELLO de estado, no un titular.
   A 44px en display y en color competía de frente con el nombre del cliente.
   Baja a CAJA: la segunda voz de la página. Medido en 17 casas de lujo, lo que
   las hace sentirse vivas no es el titular grande, es el segundo registro — y
   la caja alta con tracking es el más barato de todos: no agrega un byte, no
   agrega un escalón a la escala y no necesita una familia más. */
.caso__cifra{
  font-family:var(--texto); font-size:.75rem;
  font-weight:var(--p-ui); letter-spacing:.16em; text-transform:uppercase;
  font-feature-settings:'case' 1;
  /* El sello va en TINTA, no en el color del cliente, y esto es una concesión
     consciente: en rojo se veía mejor. Pero al bajarlo de 44px a 12px el umbral
     de contraste sube de 3:1 a 4.5:1, y medidos sobre el papel los cuatro
     colores dan 6.55 (Black Secret) · 4.34 (Chilita) · 3.08 (Brushia) ·
     1.94 (LEVIA): TRES DE CUATRO no pasan. El rojo que se vio en la primera
     versión era el segundo mejor del lote y tampoco llegaba. En tinta da 13.68.
     El color del cliente sigue presente donde no tiene que ser legible: el
     punto de arriba y el derrame del marco. */
  color:var(--tx-1);
  margin-top:.35rem;
}

/* ---------- 8 · SERVICIOS ---------- */
.servicios{ display:grid; gap:1px; background:var(--tx-linea); margin-top:3.5rem }
@media (min-width:860px){ .servicios{ grid-template-columns:repeat(3,1fr) } }
.servicio{ background:var(--papel); padding:clamp(1.75rem,3vw,2.75rem); display:grid; gap:1rem; align-content:start }
.servicio__icono{ width:44px; height:44px }
.servicio__icono .hueco{ width:44px; height:44px; border-radius:50% }
.servicio h3{ font-family:var(--display); font-weight:300; font-size:var(--t3); margin:0; letter-spacing:-.015em }

/* ---------- 9 · PINES (2-2.5 pantallas, la medida real de las refs) ---------- */
/* Una sección con pin no lleva aire vertical: el `--aire` (108px en un
   portátil) empujaba el lienzo hacia abajo DENTRO del viewport clavado, así
   que un lienzo de 100svh terminaba 108px fuera de la pantalla y se comía el
   pie del marco. El aire ya lo pone el propio lienzo. */
.pin{ position:relative; padding-block:0 }
.pin__lienzo{ height:100svh; display:grid; place-items:center; overflow:clip }
.pasos{ display:grid; gap:.9rem }
.paso{ opacity:.28; transition:opacity var(--base) var(--curva) }
.paso.vivo{ opacity:1 }

/* ---------- 10 · PORTAL (dirección A: la pantalla completa, a sangre) ----------
   La técnica es la de mercury.com (medido: 4 sticky, 0 pin, medio al 0.99 del
   viewport). Aquí NO hay pin: el texto se scrollea y se va, y el marco se PEGA.
   Con pin no cabía — el marco a 96vw mide 864px de alto y el texto arriba pedía
   otros 270 en una pantalla de 900. Al quitar el pin el texto deja de competir. */
/* 328svh = 4 tramos de 82. Escrito como una sola unidad a propósito:
   `calc(4 * 82svh)` NO aplicó (la escena medía 985px en vez de 2952, o sea el
   alto se quedó en auto y no había recorrido que repartir). Medido, no supuesto. */
.portal__escena{ height:328svh }
.portal__pegado{
  position:sticky; top:0; height:100svh;
  /* Dos columnas: los pasos mandan a la izquierda, la vista obedece a la
     derecha. Antes el marco iba centrado y solo y las pestañas vivían encima de
     él, robándole el pie a la interfaz. */
  display:grid; grid-template-columns:minmax(15rem, .8fr) minmax(0, 2.2fr);
  gap:clamp(1.5rem, 4vw, 3.5rem);
  /* ANCLADO ARRIBA, no centrado. Medido: con `center` quedaban 273px de hueco
     entre la cabecera de la sección y los pasos —114 eran holgura de centrado y
     el resto relleno—, y ese hueco es lo primero que se ve al llegar, antes de
     que el pegado se enganche. Anclado arriba se lee como un bloque, y de paso
     la posición no cambia entre el estado de llegada y el de recorrido. */
  align-items:start; align-content:start;
  /* aire para la cabecera fija: sin esto la barra del chasis quedaba DEBAJO del
     menú y el marco se veía sucio por arriba */
  padding-top:5.25rem;
}
/* El marco se dimensiona por el ALTO disponible, no solo por el ancho: si se
   fija a 96vw, en una laptop de 900 el propio marco mide 898 y no queda aire
   para las pestañas. Así llega a 0.96 en monitor grande y baja solo cuando la
   ventana es corta — pero nunca se sale. */
.portal__marco{
  /* Tres topes: el ancho de la ventana, el ancho REAL de la captura (1440
     lógicos — pasarse sería interpolar) y el alto disponible. */
  width:min(100%, 1440px, calc((100svh - 8.75rem) * 1.6));
  position:relative;
  margin-inline:auto; border-radius:8px;
  background:var(--sup-1); border:1px solid var(--tx-linea);
  box-shadow:0 40px 120px rgba(0,0,0,.6); overflow:hidden;
}
.portal__barra{ height:34px; background:var(--sup-2); border-bottom:1px solid var(--tx-linea);
  display:flex; align-items:center; gap:6px; padding:0 12px }
.portal__punto{ width:9px; height:9px; border-radius:50%; background:rgba(255,255,255,.16) }

/* Las cuatro vistas ocupan el mismo hueco. El `aspect-ratio` va en el
   contenedor —no en la imagen— para que el alto sea determinista antes de que
   cargue cualquier archivo: cero salto de layout. */
.portal__vistas{ position:relative; aspect-ratio:2880/1800; isolation:isolate }
/* La transición: un crossfade puro deja ver DOS pantallas superpuestas un
   instante, y eso es exactamente lo que se lee como "salto". Se añaden dos
   cosas mínimas que lo cosen:
     · un pelo de escala (1.015 → 1) para que la entrante llegue acercándose,
     · un desenfoque de 7px que se va, que hace de puente entre las dos capas
       en vez de mostrarlas como dos objetos distintos.
   Duraciones distintas a propósito: la opacidad cierra antes que la escala, así
   la saliente ya no está cuando la entrante termina de asentarse.
   Nada de scale(0): en el mundo real nada aparece de la nada. */
.portal__vistas > .vista{
  position:absolute; inset:0; margin:0;
  opacity:0; transform:scale(1.015); filter:blur(7px);
  transition:opacity .38s var(--curva),
             transform .62s var(--curva),
             filter .34s var(--curva);
  pointer-events:none;
}
.portal__vistas > .vista.viva{ opacity:1; transform:none; filter:blur(0) }
/* Con movimiento reducido se queda el fade y nada más: la escala y el
   desenfoque son adorno, la legibilidad no depende de ellos. */
@media (prefers-reduced-motion:reduce){
  .portal__vistas > .vista{ transform:none; filter:none; transition:opacity .2s linear }
}
.portal__vistas img{ display:block; width:100%; height:100% ; object-fit:cover }

/* Pestañas DENTRO del marco, sobre un velo: fuera se comían ~80px de alto y en
   una laptop de 800 la interfaz caía a escala 0.66 (12px de texto → 8). Adentro
   además leen como un control del propio producto, no como un widget del sitio. */
/* LOS PASOS, que ahora son las pestañas y el eje de la sección.
   Medido antes de tocar nada: #portal medía 3,442px —3.8 pantallas— para 219
   caracteres de texto, con solo TRES tamaños de tipo (12 · 22 · 72) y un hueco
   de 6× entre el más chico y el más grande, cuando las referencias viven entre
   3.3 y 4.8. Y los cuatro pasos —la respuesta literal al rótulo "cómo se
   trabaja con nosotros"— estaban a 12px, el tamaño más pequeño de la sección.
   El contraste NUNCA fue el problema: cero fallos AA, el peor a 11.65 sobre un
   mínimo de 4.5. Era jerarquía invertida.
   Ahora el paso activo vive a ~28px y los demás se retiran, así que cada
   pantalla de la escena tiene un protagonista en vez de cuatro pastillas. */
/* `order:-1` y no reordenar el HTML: en el marcado el marco va primero porque
   es el contenido y los pasos son su control, y así lo lee un lector de
   pantalla. Visualmente los pasos van a la izquierda —se leen antes— y el marco
   se queda con la columna ancha. Sin esto el marco caía en 400px y los pasos
   ocupaban 900: exactamente al revés. */
.portal__pest{
  order:-1;
  list-style:none; margin:0; padding:0;
  display:grid; gap:.15rem; align-content:center;
  min-width:0;
}
.portal__pest button{
  font:inherit; text-align:left; width:100%;
  background:none; border:0; cursor:pointer;
  display:grid; grid-template-columns:2.4rem minmax(0,1fr);
  gap:0 .9rem; align-items:baseline;
  padding:.85rem 0; border-top:1px solid var(--tx-linea);
  color:var(--tx-3);
  transition:color var(--base) var(--curva), opacity var(--base) var(--curva);
}
.portal__pest li:last-child button{ border-bottom:1px solid var(--tx-linea) }
.pp__n{ font-size:.75rem; font-variant-numeric:tabular-nums; letter-spacing:.06em;
        color:var(--tx-3) }
.pp__t{ font-size:clamp(1rem, 1.35vw, 1.188rem); line-height:1.28;
        font-weight:var(--p-ui) }
/* La línea de detalle solo existe en el paso vivo. Sin esto los cuatro pasos
   eran cuatro títulos secos y la sección seguía diciendo 219 caracteres en 3.7
   pantallas; ahora el paso activo explica lo que se está viendo en la pantalla
   de al lado. Se anima con `grid-template-rows` y no con `height:auto`, que no
   es animable. */
.pp__d{
  grid-column:2; font-size:.875rem; line-height:1.45; color:var(--tx-2);
  display:grid; grid-template-rows:0fr; opacity:0; overflow:hidden;
  transition:grid-template-rows var(--base) var(--curva), opacity var(--base) var(--curva),
             margin-top var(--base) var(--curva);
}
.pp__d > *{ min-height:0 }
.portal__pest button[aria-selected=true] .pp__d{
  grid-template-rows:1fr; opacity:1; margin-top:.45rem;
}
.portal__pest button:hover{ color:var(--tx-2) }
/* El paso vivo: sube de tamaño y de peso, y su número se vuelve el único punto
   cálido de la columna. El resto NO se apaga a un gris ilegible —se quedan en
   --tx-3, que ya mide 11.65 de contraste— porque son la lista completa del
   proceso y se tienen que poder leer todos. */
.portal__pest button[aria-selected=true]{ color:var(--tx-1) }
/* el portal también vive sobre tinta: bronce claro, ver §piezas */
.portal__pest button[aria-selected=true] .pp__n{ color:var(--bronce-claro, #C6A961) }
.portal__pest button[aria-selected=true] .pp__t{
  font-size:clamp(1.25rem, 2.1vw, 1.75rem); font-weight:var(--p-ui);
  transition:font-size var(--base) var(--curva);
}
.portal__pest button:focus-visible{ outline:2px solid var(--oro); outline-offset:3px }

@media (max-width:760px){
  /* En teléfono el portal se captura vertical (390×844 lógicos), así que el
     hueco cambia de forma. Y el marco puede irse casi a todo el ancho porque
     ya no hay texto compitiendo. */
  .portal__vistas{ aspect-ratio:780/1688 }
  /* ── EL MARCO SE VUELVE TELÉFONO ──────────────────────────────────────
     Lo cazó paco: dentro iba la captura vertical del portal (correcta) pero
     alrededor seguía el cromo de un NAVEGADOR DE ESCRITORIO —tres puntitos y
     `portal.caliberstudio.site`—. Un teléfono dentro de una ventana de Mac no
     es un objeto: es un error de escala, y se lee como tal.
     La barra se va y el marco toma silueta de aparato. La URL no se pierde:
     la captura trae la cabecera real del portal dentro. */
  .portal__barra{ display:none }
  .portal__marco{ border-radius:30px; padding:5px; background:#101318;
                  box-shadow:0 0 0 1px rgba(255,255,255,.07),
                             0 26px 60px -22px rgba(0,0,0,.85) }
  .portal__vistas{ border-radius:25px; overflow:clip }
  .portal__marco{ width:min(94vw, 390px, calc((100svh - 7.75rem) * 0.462)) }
  /* A 390px dos columnas dejarían los pasos en 120px de ancho: se apilan, y el
     paso vivo baja de tamaño para no comerse la pantalla del marco. */
  .portal__pegado{ grid-template-columns:1fr; align-content:center; gap:1rem }
  .portal__pest button{ padding:.5rem 0 }
  .portal__pest button[aria-selected=true] .pp__t{ font-size:1.0625rem }
  .pp__t{ font-size:.875rem }
  .portal__escena{ height:312svh }
}


/* ---------- RECORRIDO POR SCROLL ----------------------------------------
   ANTES iba en HOVER, y eso estaba medido como defecto: en las 5 referencias
   vivas del scout (Exo Ape, OBYS, Hello Monday, Phantom, Basic/Dept) el hover
   mueve entre 0.0% y 3.6% de la pantalla —allá es adorno—, mientras que aquí
   era el evento ENTERO. Consecuencia: en teléfono, donde no hay hover, los
   cuatro casos eran cuatro fotos muertas; y al bajar por la sección no pasaba
   nada salvo que el cursor cayera justo encima.

   Ahora lo mueve el scroll: `--rec` va de 0 a 1 conforme el caso cruza la
   ventana y la captura se recorre sola. Se conserva el truco de la traslación
   —`100cqh` es el alto de la VENTANA y `100%` el de la imagen, así que la
   resta da exactamente lo que hay que subir, sin número mágico— y se le
   multiplica el progreso. Necesita `container-type:size` en la ventana.
   Frente a un video del mismo recorrido: 130-189 KB contra 800-1800. */
.recorre__ventana{
  position:relative; aspect-ratio:16/10; overflow:clip;
  container-type:size;
}
.recorre__foto{
  position:absolute; top:0; left:0; width:100%; height:auto;
  transform:translateY(calc((100cqh - 100%) * var(--rec, 0)));
  will-change:transform;
}

/* ── QUIÉN MANDA EL RECORRIDO, POR DISPOSITIVO ───────────────────
   Se probó moverlo TODO con el scroll y paco lo cazó: las cuatro capturas
   viajando a la vez es ruido, no un evento — y tenía razón, con el cursor se
   recorre la que TÚ elegiste. Pero el hover solo tampoco servía: en teléfono
   no existe, y ahí quedaban cuatro fotos muertas (era el defecto medido).
   Así que manda el cursor donde hay cursor, y el scroll donde no lo hay.
   El barrido de Black Secret NO entra en este reparto: va por scroll en los
   dos, porque es UNA cosa que ocurre al pasar, no cuatro. */
@media (hover: hover){
  .recorre__foto{ transition:transform .8s var(--curva) }
  .recorre:hover .recorre__foto,
  .recorre:focus-visible .recorre__foto{
    transform:translateY(calc(100cqh - 100%));
    /* 20s y no 9: a 9 el recorrido corría a ~209 px/s y no se alcanzaba a LEER
       nada. A 20s van ~102 px/s. Lineal: un recorrido no acelera ni frena.
       `--recorrido-s` va por caso porque las capturas NO miden lo mismo; con
       una duración fija, la más alta correría más rápido. */
    transition:transform var(--recorrido-s, 20s) linear;
  }
}
.recorre:focus-visible{ outline:2px solid var(--oro); outline-offset:6px }

/* La pista: sin ella nadie sabe que hay algo que hacer. Se apaga al entrar,
   porque a partir de ahí el movimiento ya lo dice. Sin cursor se calla: ahí
   el recorrido lo lleva el scroll y no hay nada que pedirle a nadie. */
.recorre__pista{
  position:absolute; right:.9rem; bottom:.9rem; z-index:4;
  padding:.3rem .6rem; border-radius:999px;
  font-size:.75rem; letter-spacing:.06em; text-transform:uppercase;
  color:var(--tx-2); background:rgba(8,9,12,.72);
  backdrop-filter:blur(6px);
  opacity:1; transition:opacity var(--base) var(--curva);
  pointer-events:none;
}
.recorre:hover .recorre__pista{ opacity:0 }
/* ⚠️ El segundo selector NO sobra. `.recorre__pista` pesa (0,1,0) y
   `.marco > *{ display:block }` —200 líneas más abajo— pesa (0,1,1): le gana,
   y la media query no suma especificidad. Resultado medido a 390px con
   `(hover:none)` en verdadero: la primera pista sí se ocultaba (su padre es
   `.chasis`) y las OTRAS TRES seguían pidiendo "pasa el cursor para
   recorrerlo" en un teléfono, que es donde no hay cursor. Lo cazó paco. */
@media (hover:none){
  .recorre__pista,
  .marco > .recorre__pista{ display:none }
}

/* ── EL BARRIDO (solo Black Secret) ──────────────────────────────
   La plantilla anterior está ENCIMA y se retira de izquierda a derecha.
   `clip-path:inset` y no opacidad: un fundido cruzado deja ver las dos a la
   vez y se lee a error de carga; un barrido se lee a mano que retira algo. */
.recorre__antes{
  position:absolute; inset:0; z-index:1;
  width:100%; height:100%; object-fit:cover; object-position:50% 0;
  clip-path:inset(0 0 0 var(--barre, 0%));
}
/* La costura: el canto que avanza. Sin ella el barrido se lee a imagen que
   falla; con ella se lee a objeto que se abre. Se apaga cuando termina de
   barrer —una línea parada no significa nada.
   1px de oro NO bastaba: medido contra la plantilla vieja, que es casi blanca,
   la línea desaparecía. Lleva además una sombra corta hacia la IZQUIERDA, o
   sea proyectada sobre lo entregado: es la capa vieja al retirarse la que la
   tira. Eso es lo que le da espesor en vez de parecer un corte de recorte. */
.recorre__costura{
  position:absolute; top:0; bottom:0; left:var(--barre, 0%); width:2px; z-index:2;
  background:linear-gradient(to bottom,
    color-mix(in oklab, var(--oro) 40%, transparent), var(--oro) 22%,
    var(--oro) 78%, color-mix(in oklab, var(--oro) 40%, transparent));
  opacity:var(--costura-op, 0);
  box-shadow:-10px 0 22px -6px rgba(0,0,0,.55),
             0 0 12px 0 color-mix(in oklab, var(--oro) 45%, transparent);
  pointer-events:none;
}
/* El rótulo VIAJA con la costura y se queda siempre del lado viejo. Fijo abajo
   a la izquierda quedaba encima de lo entregado, que es justo lo contrario de
   lo que nombra. */
.recorre__rotulo{
  position:absolute; left:calc(var(--barre, 0%) + .7rem); bottom:1rem; z-index:3;
  font-size:.75rem; letter-spacing:.14em; text-transform:uppercase;
  /* Placa y no sombra de texto: lo que hay debajo es el sitio VIEJO del
     cliente, o sea un fondo que no controlamos —ahí medía casi blanco y el
     rótulo en blanco no pasaba contraste—. La placa lo garantiza sea cual
     sea el pixel que le toque. */
  color:#fff; background:rgba(8,9,12,.84);
  padding:.22rem .5rem; border-radius:2px;
  opacity:var(--costura-op, 0);
  white-space:nowrap; pointer-events:none;
}

@media (prefers-reduced-motion:reduce){
  /* sin movimiento se enseña lo ENTREGADO, no la plantilla vieja: si la
     animación no corre, el barrido se quedaría congelado en el "antes" y el
     caso contaría justo lo contrario de lo que quiere contar */
  .recorre__foto{ transform:none }
  .recorre__antes{ clip-path:inset(0 0 0 100%) }
  .recorre__costura, .recorre__rotulo{ opacity:0 }
}

/* ---------- 11 · ENTRADA + CIERRE ---------- */
.carriles{ display:grid; gap:1px; background:var(--tx-linea); margin-top:3rem }
@media (min-width:860px){ .carriles{ grid-template-columns:repeat(3,1fr) } }
.carril{ background:var(--papel); padding:2rem; display:grid; gap:.75rem; align-content:start }
.carril--eje{ background:var(--papel-alto); outline:1px solid var(--oro) }
.boton{
  display:inline-flex; align-items:center; gap:.6rem; padding:.85rem 1.5rem;
  border:1px solid currentColor; border-radius:999px; font-weight:500;
  transition:transform var(--rapido) var(--curva), background var(--rapido) var(--curva), color var(--rapido) var(--curva);
}
.boton:hover{ background:var(--oro); border-color:var(--oro); color:var(--tinta) }
.boton:active{ transform:scale(.97) }

.cierre{ min-height:92svh; display:grid; align-content:center; overflow:clip }

/* ---------- 12 · REVELADOS ---------- */
/* Nada arranca en opacity:0 desde CSS — si el JS falla, el texto igual se lee. */
.linea{ display:block; overflow:hidden }

@media (prefers-reduced-motion:reduce){
  *{ animation:none!important; transition-duration:.01ms!important }
}

/* ============================================================
   v3.1 — lo que el audit de composición dejó en evidencia.
   Medido contra 6 referencias (AUDIT_COMPOSICION.md):
   · densidad 10 cajas/pantalla vs 27-137  → hay que LLENAR
   · 33% de texto ≤13px vs 59-70%          → texto chico por todos lados
   · 4 enlaces y sin pie vs 40-69 y 1 pantalla de pie
   · 25% centrado vs 7-13%                 → descentrar
   · 0 carriles horizontales vs 2-11
   · cursivas: 9 vs CERO en las seis referencias
   ============================================================ */

/* --- El texto chico ya NO es mono. Por qué:
   Puse JetBrains Mono, que es la letra de VS Code. Usar la fuente de un editor
   de código como taquigrafía de "técnico" es el tell más barato que hay, y a
   105 nodos convertía el sitio entero en una máquina de escribir.
   Las referencias que sí usan mono (benorth, bymonolog) usan la HERMANA de su
   propia familia — Aeonik Mono, Suisse Mono — no una letra prestada de otro
   mundo. General Sans no tiene hermana mono, así que la respuesta correcta es
   quitarla, no cambiarla: el texto chico vive en la misma voz, a peso 500.
   `tabular-nums` conserva lo único que la mono aportaba de verdad: que los
   números de columnas distintas alineen. --- */
.mono{ font-family:var(--texto); font-size:.75rem; font-weight:500;
       letter-spacing:.02em; font-variant-numeric:tabular-nums;
       font-feature-settings:'case' 1 }   /* los separadores `·` suben a caja alta */

/* --- un solo lienzo fijo detrás de todo --- */
#mundo{ position:fixed; inset:0; width:100%; height:100%; z-index:-1; display:block }
.oscuro{ background:transparent }        /* el shader se ve a través */
/* Respaldo sin WebGL. El fondo oscuro dependía POR COMPLETO del lienzo: al
   esconder `#mundo` la sección se volvía PAPEL con texto blanco encima
   —ilegible—, y eso es justo lo que veía cualquier motor sin WebGL o con la
   aceleración apagada. No se puede arreglar poniéndole tinta opaca a
   `.oscuro`: taparía el lienzo, que vive detrás. La marca la pone el propio
   `mundo.js` cuando no consigue contexto. */
.sin-mundo .oscuro{ background:var(--tinta) }

/* ═══════════════════════════════════════════════════════════════════════
   LAS PLACAS DE ATMÓSFERA — solo en dos secciones, a propósito
   ───────────────────────────────────────────────────────────────────────
   Medido en izanami, la referencia que dio paco: su fondo NO cambia todo el
   tiempo. Doce de dieciocho muestras viven entre 6.2 y 6.7 de luminancia y
   hay apenas CUATRO saltos grandes en todo el documento. Son ISLAS DE LUZ
   dentro de un negro largo, no variedad constante — y esa es justo la parte
   que se malinterpreta al mirarlo.

   Por eso va UNA sola, en #sistemas. NO en el hero ni en el cierre: ahí a paco
   le gustan los gradientes como están, y son justamente las dos secciones que
   ya tienen su propio cielo con fuente de luz propia (`--n1/--n2/--n3`) más el
   cuerpo del cierre. Meterles una placa encima habría tapado lo que funciona.
   Precios se queda CLARA, también por decisión suya.

   Va DEBAJO de la niebla —que no se toca— y ENCIMA de la veladura de tinta:
   debajo de ella, el 55% de opacidad la aplastaría a ~6 y no se vería.
   Es cálida (R-B +7.9), como el rgb(10,8,1) de izanami y al revés de nuestro
   #08090C frío.
   ═══════════════════════════════════════════════════════════════════════ */
#sistemas{ position:relative }
.placa{
  position:absolute; inset:0; z-index:0; pointer-events:none;
  background-size:cover; background-position:center; background-repeat:no-repeat;
  /* se apaga contra el borde de abajo para que la costura con la sección
     siguiente no sea un corte a cuchillo */
  -webkit-mask-image:linear-gradient(to bottom, #000 62%, transparent 100%);
          mask-image:linear-gradient(to bottom, #000 62%, transparent 100%);
}
.placa--polvo{
  background-image:url('../media/materia/placa-polvo.webp');
  /* .58 y no 1: a plena fuerza la placa dejaba la sección en luminancia 33.4,
     que es exactamente la zona donde el fondo empieza a aplanar lo que se le
     pone encima —el error que costó media sesión arreglar con la niebla—. Aquí
     encima van las vistas del panel, así que la isla de luz tiene que
     notarse SIN comerse el contraste de la captura. */
  opacity:.58;
}
/* La segunda isla. Se desvanece hacia ARRIBA —al revés que la de #sistemas—
   para que las dos no repitan el mismo gesto, y entra más baja (.46) porque el
   horizonte reparte su luz por todo el ancho mientras el haz la concentra. */
/* `placa-caida.webp` queda sin usar: paco la vio en #portal y el conjunto se
   leía anaranjado. Se conserva por si sirve en otro sitio con menos calor. */


/* El contenido va por encima de la placa */
#sistemas > :not(.placa){ position:relative; z-index:1 }
.oscuro::before{ content:''; position:absolute; inset:0; background:var(--tinta);
                 opacity:.55; z-index:-1 }
.papel,.papel-alto,.frio,.pie{ position:relative; z-index:1 }
.hero__lienzo{ display:none }            /* ya no hay lienzos por sección */

/* ============================================================
   C2 · EL CIELO — la lección de linear.app
   ------------------------------------------------------------
   Medido en vivo: linear.app no tiene UN SOLO canvas. Todo su
   cósmico son 10 radial-gradient pasados por filter:blur(80px) y
   montados con mix-blend-mode overlay/lighten. Su fondo es
   rgb(8,9,10); el nuestro rgb(8,9,12). La oscuridad ya estaba
   bien — lo que faltaba era LUZ ENCIMA.

   Aquí NO se copia el blur(80px): ellos lo necesitan porque sus
   gradientes salen de divs con borde duro. Un radial-gradient con
   caída larga ya llega desenfocado de fábrica, y desenfocar una
   capa a pantalla completa cuesta pintura en cada cuadro.

   `lighten` y no `screen`: screen levanta TODO el negro y el papel
   pierde su negro real; lighten solo deja pasar lo que supera al
   fondo, así la sombra se queda en sombra. Ley cósmica: sin luz
   ambiental de relleno.
   ============================================================ */
.oscuro::after{
  content:''; position:absolute; inset:0; z-index:-1; pointer-events:none;
  background:
    radial-gradient(62% 46% at var(--n1,78% 6%),  rgba(150,178,216,.13), transparent 63%),
    radial-gradient(48% 40% at var(--n2,10% 88%), rgba(216,197,140,.06), transparent 66%),
    radial-gradient(34% 30% at var(--n3,44% 54%), rgba(122,152,202,.05), transparent 70%),
    /* C3 · EL TERMINADOR — la línea donde la luz muere. En una esfera
       iluminada por una sola fuente es la imagen más cósmica que hay, y
       es literalmente un gradiente. Aquí vive el guiño; no hace falta
       dibujar un planeta. Va arriba y abajo: la costura contra el papel
       deja de ser un corte a cuchillo. */
    linear-gradient(to bottom, rgba(244,241,234,.04), transparent 190px),
    linear-gradient(to top,    rgba(244,241,234,.025), transparent 150px);
  background-size: auto;
  background-position: center;
  background-repeat: no-repeat;
  mix-blend-mode:lighten;
}
/* H1 · LA PLACA DE MATERIA — UNA capa fija para todo el sitio.
   Antes iba dentro del apilado de CADA sección oscura, y ahí estaba el
   problema que se sentía como "el oscuro no me termina de convencer":

   con `lighten` el píxel más claro gana, así que una placa cuya cima está
   horneada en 26/255 se impone en TODA la ventana sobre un fondo cuyo piso
   real es 11. Resultado medido: el fondo pasaba de 11 a 31 de luminancia y su
   rango de 15 a 71 niveles. Eso no es atmósfera, es un velo gris uniforme —y
   un velo uniforme aplana todo lo que se le ponga encima—. Las referencias
   oscuras miden 8-9 de fondo con 13-21 de rango; nosotros íbamos al TRIPLE.

   La placa se había horneado al 10% para competir contra un shader que
   entonces vivía en 29. Al bajar el shader a su sitio, la misma placa pasó de
   acompañar a dominar. Sacarla a UNA capa fija arregla las dos cosas: su
   fuerza vuelve a ser un número que se ajusta aquí —sin re-hornear el webp— y
   se pinta una vez en lugar de una por sección.

   Sobre papel no hace nada: con `lighten`, el papel (241) le gana siempre.

   La niebla NO se apagó: se volvió VARIABLE. Al medirla se vio que el problema
   nunca fue que existiera —se ve bien y da la atmósfera— sino que era PAREJA:
   el mismo velo en las seis secciones oscuras y siempre al mismo valor, así que
   todo lo que se le ponía encima perdía contraste contra ella por igual. Ahora
   cada sección declara su densidad con `data-niebla` y esta capa la persigue:

     densa    (1.00)  #top y #cierre — ahí la niebla ES el contenido y no
                      compite con nada; es donde paco la vio premium
     media    (0.60)  #sistemas — hay que leer, pero la escena es del fondo
     delgada  (0.34)  #trabajo, #servicios, #portal — hay trabajo que MIRAR,
                      y el negro real es lo que le devuelve el contraste
     nula     (0.00)  #entrada, la isla de papel

   La transición es larga a propósito (1.1s): a menos de eso el cambio se
   percibe como un parpadeo al cruzar la costura en vez de como aire que se
   despeja. `opacity` es de las dos propiedades que no cuestan reflow. */
.materia{
  position:fixed; inset:0; z-index:-1; pointer-events:none;
  background:url('../media/materia/velada.webp') center/cover no-repeat;
  mix-blend-mode:lighten;
  opacity:var(--niebla, .34);
  transition:opacity 1.1s cubic-bezier(.33,0,.2,1);
}
@media (prefers-reduced-motion:reduce){ .materia{ transition:none } }

/* Cada cielo con su propia fuente. Cuatro secciones oscuras con el mismo
   gradiente es exactamente la linealidad que había que romper. */
#top   { --n1:82% 4%;  --n2:6%  92%; --n3:52% 46% }
#portal{ --n1:14% 12%; --n2:88% 78%; --n3:38% 60% }
/* H2 · EL CUERPO CON TERMINADOR — la única pieza figurativa del sitio.
   Va SOLO en el cierre y va UNA vez: dos ya es decorado espacial.
   Su luz entra por la derecha, así que el cielo de esta sección mueve su
   fuente a 88% 18% para que coincidan. Dos fuentes distintas en la misma
   escena es exactamente lo que hace que algo se lea a pegote.
   El archivo lleva ALFA por luminancia: el vacío es transparente, así que
   puede ir ARRIBA de los gradientes sin taparlos, y solo sobrevive el
   creciente. Se sale del encuadre a propósito — un cuerpo entero y centrado
   es un planeta de stock; un fragmento gigante es escala. */
#cierre{ --n1:88% 18%; --n2:8% 86%; --n3:40% 50% }
#cierre::after{
  background-image:
    url('../media/materia/cuerpo.webp'),
    radial-gradient(62% 46% at var(--n1), rgba(150,178,216,.13), transparent 63%),
    radial-gradient(48% 40% at var(--n2), rgba(216,197,140,.06), transparent 66%),
    radial-gradient(34% 30% at var(--n3), rgba(122,152,202,.05), transparent 70%),
    linear-gradient(to bottom, rgba(244,241,234,.04), transparent 190px),
    linear-gradient(to top,    rgba(244,241,234,.025), transparent 150px);
  /* Bajo y hacia afuera: el limbo iluminado cruzaba el titular y competía.
     Así el arco entra por abajo-izquierda, se lee la curva y el terminador,
     y la tipografía se queda sola arriba. La placa ya no va aquí: es una sola
     capa fija para todo el sitio (ver `.materia`). */
  background-size: min(72vw,880px) auto, auto, auto, auto, auto, auto;
  background-position: left -20vw bottom -22%, center, center, center, center, center;
  background-repeat: no-repeat;
}
@media (max-width:760px){
  /* en móvil el cuerpo se va casi todo fuera: a 390px ocuparía media pantalla
     y el creciente pasaría por detrás del texto */
  #cierre::after{ background-size: 130vw auto, auto,auto,auto,auto,auto;
                  background-position: left -78vw top 8%, center,center,center,center,center }
}
.pie   { --n1:96% 96%; --n2:4%  10%; --n3:60% 70% }

/* ============================================================
   C6 · EL BLUR COMO JERARQUÍA, NO COMO EFECTO
   ------------------------------------------------------------
   Medido: benorth 3 niveles (3/6/14px), cosmos.so 4 (1/10/30/32),
   trionn 1, la v3 CERO. Y ahí está el punto: cuando hay UN blur
   es decoración; cuando hay una escala, el desenfoque dice a qué
   distancia está cada cosa. Tres escalones y ninguno más:

     medio  →  8px   el marco de una captura flotando sobre el cielo
     lejos  → 16px   la superficie del portal, el plano más hondo

   Hubo un tercer escalón, `cerca → 3px`, para la banda del carril
   horizontal. El carril se quitó por decisión de paco y el token se fue
   con él: un peldaño de una escala que nadie pisa es ruido.

   El backdrop NO se ve si el fondo del elemento es opaco: hay que
   abrirle una rendija. Por eso los fondos pasan a color-mix con
   transparencia — antes eran sólidos y el blur no habría hecho nada.

   NO va en `.cab`: la cabecera lleva mix-blend-mode:difference, y
   backdrop-filter crea contexto de apilamiento — el difference
   acabaría invirtiendo el resultado ya borroso.
   ============================================================ */
:root{ --vidrio-medio:8px; --vidrio-lejos:16px }

/* Las tiras del hero son lo único que hoy FLOTA sobre el cielo; `.marco`
   sigue en el selector porque ahí caerá solo cuando una captura pase a
   una sección oscura, sin tener que volver a tocar esto. */
.oscuro .tira, .oscuro .marco{
  backdrop-filter:blur(var(--vidrio-medio));
  background:color-mix(in oklab, var(--sup-2) 72%, transparent);
}
.portal__marco{
  backdrop-filter:blur(var(--vidrio-lejos));
  background:color-mix(in oklab, var(--sup-1) 78%, transparent);
}
/* Safari viejo y cualquier motor sin backdrop-filter: se devuelven los
   fondos sólidos. Sin esto el marco quedaría semitransparente y el texto
   de encima se leería sobre el cielo. */
@supports not (backdrop-filter:blur(1px)){
  .oscuro .tira, .oscuro .marco{ background:var(--sup-2) }
  .portal__marco{ background:var(--sup-1) }
}

/* --- CABECERA con navegación real --- */
.cab{ gap:2rem }
.cab__nav{ display:none; gap:1.6rem }
@media (min-width:900px){ .cab__nav{ display:flex } }
.cab__nav a{ font-size:.8125rem; font-weight:600; opacity:.75;
             transition:opacity var(--rapido) var(--curva) }
.cab__nav a:hover{ opacity:1 }
.cab__cta{ font-size:.8125rem; font-weight:500; display:flex; gap:.4rem; align-items:center }
.cab__punto{ opacity:.45 }

/* --- HERO --- */
.hero__cuerpo{ display:grid; gap:2rem }
.hero__bajo{ display:grid; gap:2rem; align-items:end }
@media (min-width:900px){ .hero__bajo{ grid-template-columns:1fr auto; gap:4rem } }
.ficha{ display:grid; grid-template-columns:repeat(2,auto); gap:.5rem 2.5rem; margin:0 }
@media (min-width:700px){ .ficha{ grid-template-columns:repeat(4,auto) } }
.ficha div{ display:grid; gap:.25rem }
.ficha dt{ color:var(--tx-3) } .ficha dd{ margin:0; color:var(--tx-1) }
.hero__tiras{ display:flex; gap:.75rem; margin-top:3rem; position:relative; z-index:2 }
.tira{ margin:0; width:clamp(64px,10vw,150px); display:grid; gap:.5rem }
.tira .hueco{ aspect-ratio:9/16 }
.tira figcaption{ color:var(--tx-3) }
.hero__baja{ margin:2.5rem 0 0; color:var(--tx-3) }

/* --- cabecera de sección: 3 registros en vez de 1 --- */
.cabecera-seccion{ display:grid; gap:1rem; margin-bottom:clamp(2.5rem,6vw,5rem); max-width:62ch }
.cabecera-seccion .d2{ margin:0 }

/* --- METADATOS por caso: aquí vive el texto chico --- */
.meta-lista{ display:grid; gap:.55rem; margin:1.25rem 0 0;
             border-top:1px solid var(--tx-linea); padding-top:1rem }
.meta-lista div{ display:grid; grid-template-columns:5.5rem 1fr; gap:1rem }
.meta-lista dt{ color:var(--tx-3); font-weight:500 }
.meta-lista dd{ margin:0; color:var(--tx-2); font-weight:500 }
.pie-foto{ margin:.65rem 0 0; color:var(--tx-3) }
.caso__texto{ font-size:.9375rem; line-height:1.6; max-width:42ch; font-weight:var(--p-ui) }
.caso__num{ margin:0; color:var(--tx-3) }
/* En teléfono el caso se apila y el número queda pegado al pie de la foto de
   arriba: medido, 33px de encimado ("04" pintado sobre "Marca propia. La
   tienda en vivo…"). En escritorio no pasa porque van en columnas distintas.
   El aire va aquí y no en `.caso{gap}` para no separar también los metadatos. */
/* El aire va en `.caso__meta` y NO en `.caso__num`: el primer hijo de la columna
   es `.caso__punto`, el círculo de color del cliente, así que un margen en el
   número deja al punto sin holgura y se encima con el pie de foto de arriba.
   Se vio al subir el pie a 15px —pasó a dos líneas— y el punto cayó sobre la
   segunda. El margen en el contenedor cubre a los dos. */
@media (max-width:899px){
  .caso__meta{ margin-top:2rem }
  .caso__num{ margin-top:0 }
}

/* --- SERVICIOS con más dentro --- */
.servicio__num{ color:var(--tx-3); margin:0 }
.lista-fina{ list-style:none; margin:.5rem 0 0; padding:0; display:grid; gap:.4rem }
.lista-fina li{ color:var(--tx-2); padding-left:.9rem; position:relative }
.lista-fina li::before{ content:''; position:absolute; left:0; top:.55em;
                        width:4px; height:1px; background:var(--tx-3) }
.servicio__plazo{ color:var(--oro-vivo); margin:.75rem 0 0 }

/* --- SISTEMAS y PORTAL: descentrados (25% → objetivo <12%) --- */
.sistemas__caja,.portal__caja{ display:grid; gap:2.5rem; width:100%; max-width:1180px;
                               text-align:left }
.sistemas__cuerpo{ display:grid; gap:2rem }
/* Igual que el portal: el lienzo clavado reparte en columna y el panel se queda
   con el hueco que sobra. Los tres enunciados NO se encogen. */
#sistemas .pin__lienzo{ display:flex; flex-direction:column;
                        padding:clamp(5rem,11vh,7.5rem) 0 clamp(1.5rem,4vh,3rem) }
#sistemas .sistemas__caja{ flex:1; min-height:0; margin-inline:auto;
                           display:flex; flex-direction:column; gap:1.5rem }
#sistemas .sistemas__cuerpo{ flex:1; min-height:0; display:flex;
                             flex-direction:column; gap:1.5rem }
#sistemas .pasos{ flex:none }
/* ═══════════════════════════════════════════════════════════════════════
   EL PANEL EN ESQUIRLAS — sustituye a la captura completa
   ───────────────────────────────────────────────────────────────────────
   La captura entera al 100% del ancho salía CORTADA por los dos lados y
   además plana y de frente. Medido en 8 sitios cuyo producto es una
   interfaz: raycast enseña 17 recortes de 38 piezas, family 9 de 20, arc
   6 de 6 — entran DENTRO de su interfaz en vez de enseñarla completa, y a
   25-57% del ancho. La perspectiva existe pero es SUAVE y minoritaria
   (linear 1000px con giro -30°, resend 2000px, family 600px; raycast, arc,
   vercel y rive no usan ninguna). Y la técnica que de verdad domina son
   las MÁSCARAS: clerk 85, linear 16, raycast 13.

   Las tres esquirlas salen del MISMO archivo con distinto encuadre: son
   recortes reales del panel que operamos, no maquetas nuevas. Un solo
   asset, tres profundidades.
   ═══════════════════════════════════════════════════════════════════════ */
.panel3d{
  margin:0; flex:1; width:100%; min-height:260px; position:relative;
  /* 1400px: dentro del rango medido y del lado suave. Con 600px el giro de
     8° deformaría el texto de la captura hasta hacerlo ilegible. */
  perspective:1400px; perspective-origin:58% 42%;
}
/* UNA VISTA A LA VEZ. Las tres viven apiladas en el mismo sitio y solo una
   está descubierta; el relevo es una CORTINA, no un fundido.
   `clip-path:inset(0 100% 0 0)` es el mecanismo exacto de izanami —17
   clip-paths y cero máscaras en todo su sitio—: la imagen se descubre
   barriendo. Un fundido entre dos capturas del mismo panel casi no se
   percibe, y eso era justo el problema: "no se ve cambio a la vista". */
.vista-p{
  position:absolute; inset:0; margin:auto;
  max-width:min(78%, 780px); max-height:100%;
  width:auto; height:auto; object-fit:contain;
  border-radius:10px;
  box-shadow:0 40px 90px rgba(0,0,0,.55);
  clip-path:inset(0 100% 0 0);
  transition:clip-path .62s cubic-bezier(.62,0,.2,1);
  will-change:clip-path;
}
.vista-p.viva{ clip-path:inset(0 0 0 0) }
/* La que sale se va por el OTRO lado: si entrara y saliera por el mismo borde
   el gesto se leería como un rebobinado. */
.vista-p.saliendo{ clip-path:inset(0 0 0 100%) }
@media (prefers-reduced-motion:reduce){
  .vista-p{ transition:none }
}
.panel3d figcaption{ position:absolute; right:0; bottom:-1.35rem; z-index:2;
  font-size:.688rem; letter-spacing:.06em; text-transform:uppercase;
  color:var(--tx-3) }
@media (max-width:899px){
  /* A 390px tres esquirlas superpuestas se tapan entre ellas. Se apilan en
     columna, sin perspectiva: el 3D a media pantalla es ruido, no profundidad. */
  /* A 390px la vista ocupa el ancho entero: el relevo se sigue viendo igual
     de bien y no hace falta perspectiva —el 3D a media pantalla es ruido. */
  .panel3d{ perspective:none; min-height:clamp(12rem,52vw,20rem) }
  .vista-p{ max-width:100%; border-radius:8px; box-shadow:0 18px 40px rgba(0,0,0,.5) }
}
@media (min-width:960px){
  .sistemas__cuerpo{ grid-template-columns:1.25fr .75fr; align-items:center }
  /* El texto se acuesta en dos columnas para dejarle el alto a la lente:
     titular a la izquierda, el cómo a la derecha. Antes el texto era una
     columna vertical y empujaba el marco a un tercio del ancho. */
  /* El párrafo se alinea por ABAJO con el titular, no por arriba. Con
     `align-items:start` arrancaba por encima de la primera línea del titular y
     a media pantalla de distancia: dos bloques sueltos que no se hablaban.
     Alineados por la base, la última línea del párrafo cae al ras de "propio
     lugar." y el conjunto se lee como un solo renglón editorial.
     La columna del titular crece porque es la que tiene 72px que colocar. */
  .portal__texto{ grid-template-columns:1.18fr .82fr; gap:clamp(2rem,4vw,3.5rem);
                  align-items:end }
  .portal__texto > :last-child{ padding-bottom:.35rem }
  .portal__texto .parrafo{ max-width:34ch }
}
/* El contenedor sigue al marco: en una pantalla ancha el tope de 1180 dejaba
   el producto flotando chico en medio de dos márgenes enormes. */
.portal__caja{ max-width:1440px }
.portal__texto{ display:grid; gap:1.25rem }
.portal__url{ margin-left:auto; color:var(--tx-3) }
.pasos-num{ list-style:none; margin:1rem 0 0; padding:0; display:grid; gap:.7rem;
            border-top:1px solid var(--tx-linea); padding-top:1rem }
.pasos-num li{ display:grid; grid-template-columns:2.2rem 1fr; gap:.75rem; color:var(--tx-2) }
.pasos-num span{ color:var(--oro) }

/* ═══ #entrada · LAS TRES PUERTAS ═════════════════════════════════════════
   Cuatro elementos por puerta: marca, plazo, una línea, CTA. Nada más — paco:
   "está muy cargado, no se ve clean". Las referencias limpias ponen DOS cosas
   por elemento (hello monday: una imagen, un título y dos etiquetas); nosotros
   traíamos cinco. */
.puertas{ margin-top:clamp(2.5rem,5vw,3.75rem); display:grid;
          border-top:1px solid var(--tx-linea) }
.pu{
  display:grid; align-items:baseline;
  grid-template-columns:1fr; gap:.5rem;
  padding:clamp(1.75rem,3.5vw,2.5rem) 0;
  border-bottom:1px solid var(--tx-linea);
}
@media (min-width:900px){
  /* marca · nº · nombre · plazo · una línea · CTA — un solo renglón que se lee
     de izquierda a derecha, sin tarjetas y sin columnas que compitan. */
  .pu{ grid-template-columns:2.75rem 2rem minmax(0,1.05fr) auto minmax(0,1.35fr) auto;
       gap:0 clamp(1.25rem,2.6vw,2.25rem); align-items:center }
}
.pu__n{ font-size:.75rem; color:var(--tx-3); margin:0 }
.pu__t{ font-family:var(--display); font-weight:400;
        font-size:clamp(1.375rem,2.1vw,1.75rem); letter-spacing:-.015em;
        line-height:1.05; margin:0 }
/* El plazo ocupa el lugar del precio, que es lo que la ley medida le da
   (webflow 32 contra título 24 · squarespace 40). Peso 400: la moda de las seis
   referencias frescas; veníamos en 500 y éramos de los pesados del grupo. */
.pu__plazo{ font-family:var(--display); font-weight:400;
            font-size:clamp(2.25rem,4.2vw,3.5rem); line-height:.9;
            letter-spacing:-.04em; margin:0; white-space:nowrap }
.pu__plazo em{ font-style:normal; font-size:.813rem; color:var(--tx-2); letter-spacing:0 }
.pu__una{ font-size:clamp(.938rem,1.2vw,1.031rem); line-height:1.5;
          color:var(--tx-2); margin:0; max-width:40ch }
.pu__una b{ font-weight:500; color:var(--tx-1) }
.pu__cta{ margin:0 }
.pu__cta .boton{ padding:.6rem 1.15rem; font-size:.875rem; white-space:nowrap }

/* ── LA MARCA QUE SE DIBUJA ────────────────────────────────────────────────
   NO es un ícono decorativo encima del título — eso es un tic reconocible que
   el canon nombra. Es una línea que OCURRE al llegar: se traza sola con
   `stroke-dashoffset` y el punto de acento aparece al final.
   Trazo 0.9 sobre un viewBox de 24 (el archivo trae 1.6): a 44px de tamaño,
   1.6 daría 2.9px efectivos y se vería gordo. La regla del canon es línea
   ultraligera. */
.pu__marca{ width:2.75rem; height:2.75rem; color:var(--tx-1); overflow:visible }
.pu__marca *{ fill:none; stroke:currentColor; stroke-width:.9;
              stroke-linecap:round; stroke-linejoin:round }
/* NADA de `vector-effect:non-scaling-stroke` aquí. Medido: con él, el navegador
   mide el guion en PÍXELES DE PANTALLA e ignora el `pathLength`, así que
   `stroke-dasharray:1` se volvía un guion de 1px con hueco de 1px y las marcas
   salían dibujadas a la mitad. Sin él, el guion vive en unidades del viewBox y
   `pathLength="1"` normaliza: un solo valor sirve para rectángulos, arcos y
   curvas por igual. El trazo escala con el tamaño, que es lo correcto. */
.pu__marca .ll{ fill:currentColor; stroke:none }
.pu-mov .pu__marca *{ stroke-dasharray:1; stroke-dashoffset:1;
                      /* `pathLength` normaliza: cada trazo mide 1, así que un
                         solo valor sirve para rectángulos, arcos y curvas */
                      }
.pu-mov .pu__marca .ll{ opacity:0 }
.pu.puesta .pu__marca *{ stroke-dashoffset:0;
  transition:stroke-dashoffset 1.05s cubic-bezier(.22,1,.3,1) }
.pu.puesta .pu__marca .ll{ opacity:1; transition:opacity .35s ease .75s }

/* ── LA SECCIÓN ANCLADA: UNA PUERTA A LA VEZ ───────────────────────────────
   Reusa `[data-paso]` del mecanismo de pin, que ya toggle-a `.vivo`. Lo que
   antes era un desplazamiento parejo de tres renglones —que a paco no se le
   veía, y con razón: 22.2% de cambio de pantalla— ahora es un relevo: la
   puerta activa se abre y las otras dos retroceden.

   Sigue cumpliendo la ley medida: nada se mueve solo. Todo está colgado del
   avance del pin, así que al parar el scroll se detiene en seco. */
#entrada .pin__lienzo{ display:grid; align-content:center;
                       /* `justify-items:stretch` explícito: `.pin__lienzo` trae
                          `place-items:center` y centraba la cabecera, que en
                          esta sección va alineada a la izquierda como el resto
                          del sitio. */
                       justify-items:stretch;
                       padding:clamp(3rem,7vh,5rem) 0 clamp(2rem,5vh,3.5rem) }
#entrada .cabecera-seccion{ margin-bottom:clamp(1.5rem,4vh,2.5rem) }

.pu{ transition:opacity .5s var(--curva) }
.pu-mov:not(.vivo){ opacity:.26 }
.pu-mov:not(.vivo) .pu__cta{ opacity:0; pointer-events:none }
.pu-mov.vivo{ opacity:1 }

/* La puerta activa CRECE. Es lo que hace que el relevo se vea sin necesidad de
   más tinta en pantalla: cambia la escala, no la cantidad. */
.pu-mov > *{ transition:transform .55s var(--curva), opacity .5s var(--curva) }
.pu-mov .pu__t{ font-size:clamp(1.125rem,1.5vw,1.313rem); transition:font-size .55s var(--curva) }
.pu-mov.vivo .pu__t{ font-size:clamp(1.5rem,2.4vw,2rem) }
/* La puerta activa no "crece un poco": SE APODERA. Medido, el escalón de 1.16
   daba 1.6% de cambio de pantalla por paso —invisible—. El plazo de la activa
   salta a escala de titular y el de las dormidas se encoge a una línea; eso sí
   reescribe la pantalla en cada relevo, sin agregar una sola cosa nueva. */
.pu-mov .pu__plazo{ font-size:clamp(1.5rem,2.4vw,2rem); transition:font-size .55s var(--curva) }
.pu-mov.vivo .pu__plazo{ font-size:clamp(4rem,9vw,7.5rem); transform:none }
.pu-mov .pu__una{ opacity:0; max-height:0; overflow:hidden;
                  transition:opacity .45s var(--curva), max-height .55s var(--curva) }
.pu-mov.vivo .pu__una{ opacity:1; max-height:9rem }
.pu-mov .pu__cta{ transition:opacity .45s var(--curva) .1s }

/* La marca se traza cuando SU puerta se enciende, no las tres a la vez. */
.pu-mov .pu__marca *{ stroke-dasharray:1; stroke-dashoffset:1 }
.pu-mov .pu__marca .ll{ opacity:0 }
.pu-mov.vivo .pu__marca *{ stroke-dashoffset:0;
  transition:stroke-dashoffset .95s cubic-bezier(.22,1,.3,1) }
.pu-mov.vivo .pu__marca .ll{ opacity:1; transition:opacity .3s ease .7s }
.pu-mov.vivo .pu__marca{ transform:scale(1.1) }

@media (prefers-reduced-motion:reduce){
  .pu-mov:not(.vivo){ opacity:1 }
  .pu-mov:not(.vivo) .pu__cta{ opacity:1; pointer-events:auto }
  .pu-mov .pu__una{ opacity:1 }
  .pu-mov .pu__marca *{ stroke-dasharray:none; stroke-dashoffset:0 }
  .pu-mov .pu__marca .ll{ opacity:1 }
  .pu-mov > *{ transform:none }
}

.entrada__nota{ margin:clamp(1.25rem,2.5vw,1.75rem) 0 0; font-size:.813rem;
                color:var(--tx-3); max-width:56ch }
.entrada__prueba{
  list-style:none; margin:clamp(2rem,4vw,2.75rem) 0 0; padding:1.15rem 0 0;
  border-top:1px solid var(--tx-linea);
  display:flex; flex-wrap:wrap; gap:.65rem 2.5rem;
  font-size:.875rem; color:var(--tx-2);
}
.entrada__prueba b{ font-family:var(--display); font-weight:400;
                    font-size:1.5rem; letter-spacing:-.02em; color:var(--tx-1);
                    margin-right:.35rem }

.cierre{ text-align:left }
.cierre .d1{ max-width:15ch }

/* --- EL PIE: una SECCIÓN (0 → ~1 pantalla, 17 enlaces) --- */
.pie{ padding:var(--aire) var(--borde) 2rem; display:grid; gap:clamp(3rem,7vw,6rem);
      border-top:1px solid var(--tx-linea) }
.pie__alto{ display:grid; gap:1.25rem; max-width:46ch }
.pie__marca{ font-family:var(--display); font-weight:300; margin:0 }
.pie__rejilla{ display:grid; gap:2.5rem 1.5rem; grid-template-columns:repeat(2,1fr) }
@media (min-width:860px){ .pie__rejilla{ grid-template-columns:repeat(5,1fr) } }
.pie__col{ display:grid; gap:.55rem; align-content:start }
.pie__col a{ font-size:.8125rem; color:var(--tx-2);
             transition:color var(--rapido) var(--curva) }
.pie__col a:hover{ color:var(--tx-1) }
.pie__dato{ color:var(--tx-3); margin:0; line-height:1.7 }
.pie__bajo{ display:flex; gap:1.5rem; flex-wrap:wrap; justify-content:space-between;
            color:var(--tx-3); border-top:1px solid var(--tx-linea); padding-top:1.5rem }

.boton--claro{ border-color:var(--tx-1) }

/* --- el hueco se ve igual en claro y en oscuro --- */
.hueco{ background:repeating-linear-gradient(135deg,transparent 0 11px,rgba(127,127,127,.10) 11px 12px) }

/* ============================================================
   v3.2 — OFICIOS: el renglón que sustituye a las tres tarjetas
   ------------------------------------------------------------
   El problema no era el estilo de la tarjeta, era la REPETICIÓN:
   la misma caja tres veces alinea todo a tres ejes X y el ojo lee
   plantilla. Aquí cada renglón entra con sangría distinta, escala
   de título distinta y profundidad distinta, así que ningún borde
   coincide con el de arriba.
   ============================================================ */

/* La escena: perspectiva en el PADRE. Sin esto, translateZ no hace
   absolutamente nada — el hijo se aplana. Es el error clásico. */
.oficios{
  perspective: 1400px;
  perspective-origin: 30% 50%;
  transform-style: preserve-3d;
  margin-top: clamp(3rem, 8vw, 6rem);
  display: grid;
}


/* ═══════════════════════════════════════════════════════════════════════
   #servicios v3 — EL RENGLÓN A SANGRE.

   Por qué se rehízo, con los números del barrido de referencias (basement,
   locomotive, linear, family, rive, medidos en vivo el 2026-07-30):

   · LARGO. El sitio medía 22.6 pantallas contra 6.5 de basement y 7.3 de
     locomotive. Esta sección sola se llevaba 7.2 pantallas para decir TRES
     cosas: tres bloques de 240vh con el visor pegado. Ese peaje de scroll por
     idea es lo que cansa, no el texto. Ahora cada servicio es un renglón que
     mide lo que mide su contenido: la sección entera baja a ~3.5 pantallas.

   · ESCALA. Nuestro titular máximo era 86px repartido en 11 escalones de
     tamaño. Locomotive usa 115px con CINCO escalones. El nombre del servicio
     sube a 160px con peso 200 —el registro caro de un display vive arriba, en
     tamaño grande y peso bajo— y de paso desaparecen escalones.

   · DÓNDE CAMBIA. Medido: nuestro movimiento ocurría dentro de una caja de
     690px al centro, y se siente como un widget que se actualiza. En
     locomotive y rive el cambio ocupa la PANTALLA ENTERA y se siente como que
     el mundo se mueve. Por eso el visor sangra hasta los bordes.

   · Y la media NO era el problema: teníamos 0.49 de área por pantalla, más que
     cuatro de los cinco referentes. Meter más recurso habría sido el mismo
     error al revés.
   ═══════════════════════════════════════════════════════════════════════ */

.oficio{
  display: block;
  padding: clamp(2.5rem, 5vw, 4.5rem) 0 0;
  border-top: 1px solid var(--tx-linea);
}
/* Las sangrías de 7vw/14vw se fueron: eran para tres renglones cortos en una
   rejilla de dos columnas. A ancho completo, un renglón sangrado se lee como un
   error de alineación, no como escalera. */
.oficio--medio, .oficio--hondo{ margin-left: 0 }

/* El renglón: índice, nombre y plazo en una sola línea de base. */
.oficio__renglon{
  display: grid; grid-template-columns: auto minmax(0,1fr) auto;
  align-items: baseline; gap: clamp(.9rem, 2.5vw, 2.25rem);
}
.oficio__idx{
  font-family: var(--display); font-weight: var(--p-aire);
  font-size: clamp(1.25rem, 2.2vw, 1.75rem); line-height: 1;
  color: var(--tx-3); margin: 0; font-variant-numeric: tabular-nums;
}
/* 158px con peso 200 quedaba FUERA de todo precedente medido: en 11 referencias
   el titular más grande es 132px (trionn) y la mediana 72px; el peso más común
   es 400 y solo dept usa 900. Estábamos fuera por arriba en tamaño y por abajo
   en peso a la vez, y eso es lo que hacía que la sección no casara con el resto
   aunque cada pieza suelta se viera bien. El salto contra el cuerpo también se
   normaliza: las referencias viven en 3.3–4.8× y nosotros íbamos en ~9×. */
.oficio__titulo{
  font-family: var(--display); font-weight: 400;
  /* SUBE de 72 a 104px al quitarse la marca de agua. La escala grande hacía
     falta —era lo único que daba presencia al renglón— y estaba delegada a un
     texto fantasma detrás; ahora la carga la palabra de verdad. 104 sigue por
     debajo del máximo medido en las once referencias, que es 132, y el peso se
     queda en 400, que es la moda (5 de 11 ponen el titular grande en ligera). */
  font-size: clamp(2.75rem, 7vw, 6.5rem); line-height: .94;
  letter-spacing: -.02em; margin: 0;
  /* el tracking cerrado se come TAMBIÉN el espacio entre palabras: se compensa */
  word-spacing: .06em;
}
/* El descenso 01→03 lo hacía el peso del título. A 160px y peso 200 ya no hay
   de dónde bajar sin romper la escala, así que lo lleva el índice. */
.oficio--medio .oficio__idx{ opacity: .78 }
.oficio--hondo .oficio__idx{ opacity: .58 }

.oficio__bajo{
  display: grid; grid-template-columns: minmax(0,1fr) auto;
  gap: clamp(1.5rem, 5vw, 4rem); align-items: start;
  margin-top: clamp(1.1rem, 2.5vw, 1.8rem);
}
.oficio__dicho{ margin: 0; color: var(--tx-2); max-width: 46ch; font-size: var(--t4);
                line-height: 1.5; font-weight: var(--p-cuerpo) }
.oficio__partes{ list-style: none; margin: 0; padding: 0;
                 display: grid; grid-template-columns: repeat(2, auto);
                 gap: .5rem 1.75rem; align-content: start }
.oficio__partes li{ position: relative; padding-left: 1.35rem; font-weight: var(--p-ui);
                    font-size: .9375rem; color: var(--tx-2) }
.oficio__partes li::before{ content:''; position: absolute; left: 0; top: .68em;
                            width: .75rem; height: 1px; background: var(--tx-3) }
.oficio__plazo{ margin: 0; color: var(--tx-3); font-size: .75rem; white-space: nowrap }
/* El oro #C6A961 sobre papel da 1.82 a 12px — casi invisible. El bronce
   mantiene la familia cálida y da 4.84. */
/* mismo caso que `.pieza__si`: 12px de bronce de papel sobre la tinta */
.oficio__plazo .mono{ color: var(--bronce-claro, #C6A961); font-size: .75rem; font-weight: var(--p-ui) }

/* ═══════════════════════════════════════════════════════════════════════
   LOS FRAGMENTOS — sustituyen al visor a sangre
   ───────────────────────────────────────────────────────────────────────
   El visor a sangre se va, y la razón está medida: de 11 referencias,
   stripe y rive —los dos más limpios— tienen CERO piezas a sangre completa.
   Ponerlo a sangre fue sobrecorrección mía tras medir que la sección no
   tenía media; paco lo dijo enseguida ("se ve muy grande, el texto pasa a
   ser secundario") y los números le dan la razón.

   Lo que entra es la escuela de stripe (251 piezas medidas) y family (259):
   · CERO bordes y CERO sombras — ninguna de esas 510 piezas tiene
   · proporciones de 0.59 a 2.48 en stripe; aquí de ~0.6 a ~4.7
   · rango de ancho del 6% al 96.7% de la ventana; tres niveles de tamaño
   · radios: solo 0 y 6px
   Lo que separa una pieza de otra NO es un borde: es su TONO y su forma.

   Y la regla que las hace valer, que salió de la corrección de paco: cada
   fragmento demuestra UNA de las partes que el renglón promete arriba. El
   visual deja de decorar y pasa a ser la prueba del texto de al lado.
   ═══════════════════════════════════════════════════════════════════════ */
/* El recorte va en la SECCIÓN y no en el artículo: recortando el artículo se
   perdería el sangrado de la pieza que se sale por la derecha, que es parte del
   lenguaje. Y va `clip` y no `hidden` — `hidden` crea un contenedor de scroll y
   rompería cualquier `sticky` de adentro.

   SIN MARCA DE AGUA. Estuvo aquí el nombre del oficio en enorme al 6% detrás de
   todo; paco lo quitó: "parece marca de agua y se ve barato". Tenía razón en lo
   que señala — un texto gigante al 6% es un recurso de plantilla, no una capa
   con intención, y encima obligaba a que TODO lo demás cargara `z-index` para
   librarlo. La presencia que daba la escala grande vuelve donde corresponde:
   al titular de verdad, que subió (ver `.oficio__titulo`). */
#servicios{ overflow-x: clip }
.oficio{ position: relative }

.oficio__frag{
  display: grid; grid-template-columns: repeat(12, 1fr);
  gap: .625rem; align-items: start;
  margin: clamp(1.75rem, 4vw, 2.75rem) 0 0;
  /* Se sale SOLO por la derecha, y con el mismo token que pone el borde de la
     sección — nunca `100vw`, que incluye la barra de scroll. Que una pieza
     rebase el margen es parte del lenguaje: stripe llega al 96.7% del ancho. */
  margin-right: calc(var(--borde) * -1);
}
.pieza{
  border-radius: 6px; padding: 1.05rem 1.2rem; overflow: hidden;
  display: grid; gap: .7rem; align-content: start;
  background: var(--pz-1);
  /* el ensamblado por scroll las mueve: se les da su propia capa */
  will-change: transform;
}
/* Tres tonos, que es lo que hace el trabajo del borde ausente.
   SUBIERON de luminancia respecto a la primera versión: paco pidió que se leyera
   más claro, y las piezas vivían en #101216–#1F232B, o sea gris sobre gris a
   dos o tres niveles de la tinta. Ahora la separación contra el fondo es real
   sin que ninguna deje de ser oscura. */
:root{ --pz-1:#181C24; --pz-2:#12151B; --pz-3:#232833 }
.pieza--hundida{ background: var(--pz-2) }
.pieza--alta   { background: var(--pz-3) }
/* Dos piezas SIN caja: viven directo sobre la tinta. Es lo que impide que la
   rejilla entera se lea como un tablero de control. */
.pieza--suelta{ background: none; border-radius: 0; padding: 0 1rem 0 0 }

/* Los rótulos subieron de --tx-3 (.50) a --tx-2 (.62) y de 9.5 a 10.5px: a
   .594rem sobre una pieza oscura eran letra decorativa, no información. Y las
   filas subieron a --tx-1: son el dato que prueba la promesa del renglón, así
   que no pueden ser lo más tenue de la pantalla. */
.pieza__rot{ font-size: .656rem; letter-spacing: .11em; text-transform: uppercase;
             color: var(--tx-2); font-weight: var(--p-ui); margin: 0 }
.pieza__cifra{ font-family: var(--display); font-weight: var(--p-aire);
               letter-spacing: -.01em; line-height: 1; margin: 0;
               font-size: clamp(1.5rem, 3vw, 2.875rem) }
.pieza__nota{ font-size: .75rem; color: var(--tx-2); margin: 0 }
.pieza__fila{ display: flex; justify-content: space-between; gap: .75rem;
              font-size: .781rem; color: var(--tx-1);
              padding: .47rem 0; border-bottom: 1px solid var(--tx-linea) }
.pieza__fila:last-of-type{ border-bottom: 0 }
.pieza__fila b{ font-weight: var(--p-ui); color: var(--tx-1) }
/* ── EL BRONCE TIENE DOS VERSIONES, Y SE ESTABA USANDO LA EQUIVOCADA ───────
   El token dice de sí mismo, arriba: "el oro, pero legible como TEXTO sobre
   PAPEL (4.84)". #7A5F1F está calculado contra fondo claro. Estas piezas son
   oscuras, y ahí el mismo color cae a ~3.3:1 con texto de 12px — por debajo
   del 4.5 que pide AA. Calculado contra la tinta real (#08090C) y contra la
   pieza más clara (#232833): #C6A961 da 8.8:1 y 6.9:1.
   La portada SÍ es papel, así que ahí vuelve el bronce oscuro. */
:root{ --bronce-claro: #C6A961 }
.pieza__si{ color: var(--bronce-claro); font-weight: var(--p-ui) }
.pieza--portada .pieza__si{ color: var(--bronce) }
.pieza__punto{ width: 5px; height: 5px; border-radius: 50%; background: var(--bronce);
               display: inline-block; vertical-align: middle; margin-right: .375rem }

/* EL ANCLA — el recorte de la portada entregada. Es la única pieza vertical
   (razón ~0.6, como la más alta de stripe) y el único papel dentro de la tinta
   en toda la sección. Es correcto justamente porque el papel ES el entregable:
   no es un hueco claro, es el producto. */
.pieza--portada{
  background: var(--papel); color: #0A1F3D; padding: 0;
  height: clamp(19rem, 30vw, 26.875rem);
  grid-template-rows: auto 1fr; gap: 0;
}
.portada__barra{ display: flex; justify-content: space-between; align-items: center;
                 padding: .7rem .875rem; border-bottom: 1px solid rgba(10,31,61,.10) }
.portada__marca{ font-family: var(--display); font-size: .813rem }
.portada__menu{ display: flex; gap: .7rem; color: rgba(10,31,61,.55); font-size: .594rem }
.portada__cuerpo{ padding: 1.25rem .875rem; display: grid; gap: .625rem; align-content: start }
.portada__rot{ font-size: .531rem; letter-spacing: .13em; color: rgba(10,31,61,.45) }
.portada__tit{ font-family: var(--display); font-size: clamp(1.25rem, 2.4vw, 1.688rem);
               line-height: 1.03; font-weight: var(--p-aire); margin: 0 }
.portada__tit b{ font-weight: 600 }
.portada__sub{ font-size: .625rem; color: rgba(10,31,61,.60); line-height: 1.45; margin: 0 }
.portada__cta{ justify-self: start; background: #0A1F3D; color: var(--papel);
               border-radius: 999px; padding: .44rem .94rem; font-size: .594rem }
.portada__foto{ margin-top: .25rem; height: 7.375rem; border-radius: 4px;
                background: linear-gradient(148deg, #D9CFC0, #B9A88F 62%, #8E7C64) }

/* ── EL LIENZO: una superficie de imagen, no una caja ──────────────────────
   La diferencia con `.pieza` no es de estilo, es de naturaleza: aquí la prueba
   ES la captura del producto, no una lista que lo describe. Por eso no lleva
   fondo, ni relleno, ni sombra, ni borde — medido en 510 piezas de referencia:
   cero bordes y cero sombras, sin una sola excepción. Lo único que la enmarca
   es su propio recorte. */
.pieza--lienzo{
  background: none; padding: 0; border-radius: 0; gap: .55rem;
  overflow: visible;                     /* el recorte lo hace la sección */
  /* `figure` trae `margin: 0 40px` del navegador. Sin esto el lienzo medía 1143
     dentro de una celda de 1223 —los 80px del margen— y el sangrado que se
     había calculado se lo comía el margen antes de llegar al borde. En teléfono
     era peor: 270 de ancho en una celda de 350. */
  margin: 0;
}
.pieza--lienzo picture{ display: block }
.pieza--lienzo img{ display: block; width: 100%; height: auto; border-radius: 6px }
.pieza__pie{ font-size: .656rem; letter-spacing: .08em; text-transform: uppercase;
             color: var(--tx-3); font-weight: var(--p-ui); margin: 0 }

/* ── LA CORTINA ────────────────────────────────────────────────────────────
   El gesto grande del renglón. Es el mecanismo de izanami, la referencia que
   dio paco: 17 clip-paths y CERO máscaras en todo su documento. Con esto el
   sitio se queda con un solo idioma para revelar imagen —clip-path— en vez de
   mezclarlo con las máscaras que traía #sistemas.

   El estado cerrado lo pone el JS con `.cortina`, no esta hoja: si el script no
   corre, la imagen se ve. Esconder desde el CSS y depender de JS para mostrar
   es la manera de dejar la sección en blanco cuando algo falla. */
.cortina{ clip-path: inset(0 100% 0 0) }
.cortina.abierta{
  clip-path: inset(0 0 0 0);
  /* Curva de SALIDA, no de entrada. Se midió la primera versión con
     `cubic-bezier(.62,0,.2,1)` —la misma del relevo de #sistemas— y a los 330 ms
     la cortina llevaba abierto el 1.8%: esa curva se queda casi quieta el primer
     tercio y luego corre. Es lo contrario de lo medido en las referencias, donde
     el 100% del cambio de pantalla ocurre en los primeros 150 ms. Con esta, a
     los 150 ms ya va por ~70% y el resto es el asentamiento. */
  transition: clip-path .78s cubic-bezier(.22, 1, .3, 1);
}
@media (prefers-reduced-motion: reduce){ .cortina{ clip-path: none } }

/* ── LA COMPOSICIÓN DEL RENGLÓN 02 ─────────────────────────────────────────
   Colocación explícita, no automática: el ancla ocupa la mitad derecha y tres
   filas de alto, y las piezas de texto se apilan a su izquierda. Es la inversa
   del renglón 01 —ancla vertical a la izquierda— para que los dos renglones no
   se lean como la misma plantilla con otras palabras, que es exactamente lo
   que pasaba (mismas piezas al píxel en los tres).
   Va por `nth-child` y no por clases porque el orden del HTML ES la lectura:
   primero la prueba visual, luego lo que la acompaña. */
.oficio__frag--sistemas > :nth-child(1){ grid-column: 5 / -1; grid-row: 1 / span 3 }
.oficio__frag--sistemas > :nth-child(2){ grid-column: 1 / 5;  grid-row: 1 }
.oficio__frag--sistemas > :nth-child(3){ grid-column: 1 / 5;  grid-row: 2 }
.oficio__frag--sistemas > :nth-child(4){ grid-column: 1 / 5;  grid-row: 3 }
/* La última fila NO llega al borde: dos columnas de aire a la derecha. Con
   `7 / -1` la caja de reportes medía 915 px para dos renglones de texto y se
   leía vacía, y además competía con el sangrado del panel — en un renglón solo
   una cosa debe salirse. */
.oficio__frag--sistemas > :nth-child(5){ grid-column: 1 / 6;  grid-row: 4 }
.oficio__frag--sistemas > :nth-child(6){ grid-column: 6 / 11; grid-row: 4 }
/* Y que de verdad la corte la ventana. La rejilla ya termina en el borde por el
   margen negativo; este ancho extra empuja la imagen un tanto MÁS allá, y el
   `overflow-x:clip` de la sección la rebana. Sin esto la captura termina justo
   en el borde, que se lee como una foto bien encuadrada y no como un recorte. */
.oficio__frag--sistemas > :nth-child(1) img{
  width: calc(100% + var(--borde)); max-width: none;
}

/* ── LA COMPOSICIÓN DEL RENGLÓN 03 · LA PAUTA ──────────────────────────────
   Tercera forma. El 01 ancla una superficie vertical a la izquierda, el 02 una
   panorámica a la derecha, y este pone CINCO piezas en secuencia — que es lo que
   de verdad se entrega en un mes de contenido. */
.oficio__frag--contenido > :nth-child(1){ grid-column: 1 / 9;   grid-row: 1 }
.oficio__frag--contenido > :nth-child(2){ grid-column: 9 / -1;  grid-row: 1 / span 2 }
.oficio__frag--contenido > :nth-child(3){ grid-column: 1 / 5;   grid-row: 2 }
.oficio__frag--contenido > :nth-child(4){ grid-column: 5 / 9;   grid-row: 2 }

/* Se llama `pauta` y NO `tira`: ya existía un `.tira` en esta misma hoja
   (línea ~859) con `width: clamp(64px,10vw,150px)`. Mi bloque quedaba de 150px
   de ancho y los cinco recuadros a 22 — medido. Una clase genérica agarra otro
   componente en silencio; el síntoma no dice nunca que fue una colisión. */
.pauta{ display: grid; gap: .625rem; align-content: start; min-width: 0 }
.pauta__cab{ display: flex; justify-content: space-between; align-items: baseline; gap: 1rem }
.pauta__nota{ color: var(--tx-3) }
/* `align-items:start` es lo que hace el escalonado: las 9:16 caen más abajo que
   las 1:1 sin que haya que darles una posición a mano. */
.pauta__posts{ display: grid; grid-template-columns: repeat(5, 1fr); gap: .625rem;
              align-items: start }

.post{ margin: 0; display: grid; gap: 0; border-radius: 6px; overflow: hidden;
       background: var(--pz-2) }
.post__marco{
  position: relative; display: grid; align-content: space-between; gap: .5rem;
  padding: .7rem .75rem;
  /* Cada formato trae su propia superficie. NO son cinco recuadros iguales con
     distinto texto: el reel es un degradado en diagonal, el carrusel plano con
     su indicador, la historia una barra arriba, el testimonio centrado. */
  aspect-ratio: 9 / 16;
}
.post--carr .post__marco, .post--test .post__marco{ aspect-ratio: 1 / 1 }
.post--reel .post__marco{ background: linear-gradient(158deg, #262D3A, #14181F 72%) }
.post--carr .post__marco{ background: linear-gradient(180deg, #212734, #171B23) }
.post--hist .post__marco{ background: linear-gradient(200deg, #232A36, #12161D) }
.post--test .post__marco{ background: var(--pz-3) }
.post--test .post__marco{ align-content: center; justify-items: center; text-align: center }

.post__fmt{ font-size: .625rem; letter-spacing: .1em; color: var(--tx-3); justify-self: start }
.post__tit{ font-family: var(--display); font-weight: var(--p-aire);
            font-size: clamp(.9rem, 1.15vw, 1.125rem); line-height: 1.12;
            letter-spacing: -.01em; color: var(--tx-1); margin: 0 }
.post__meta{ font-size: .625rem; letter-spacing: .06em; color: var(--bronce-claro, #C6A961);
             justify-self: start }
.post--test .post__meta{ justify-self: center }
/* La historia lleva su barra de progreso arriba — el único gesto de formato que
   se dibuja, porque es el que identifica el formato de un vistazo. */
.post--hist .post__marco::before{
  content: ''; position: absolute; top: .5rem; left: .75rem; right: .75rem; height: 2px;
  background: linear-gradient(to right, var(--tx-1) 38%, var(--tx-linea) 38%);
  border-radius: 2px;
}
.post--hist .post__fmt{ margin-top: .5rem }
.post__pie{ padding: .55rem .75rem .65rem; display: grid; gap: .1rem }
.post__d{ font-size: .625rem; color: var(--tx-3) }
.post__q{ font-size: .75rem; color: var(--tx-1); line-height: 1.2 }

@media (max-width: 899px){
  .oficio__frag--contenido > :nth-child(n){ grid-column: 1 / -1; grid-row: auto }
  /* Cinco piezas a 70px cada una no se leen. En teléfono la tira se recorre a
     lo ancho, que además es exactamente el gesto del formato que enseña. */
  /* Los dos rótulos a los lados NO caben en 350px: cada uno se partía en dos
     renglones y quedaban entrelazados. Apilados se leen. */
  .pauta__cab{ display: grid; gap: .1rem }
  .pauta__posts{ display: flex; overflow-x: auto; scroll-snap-type: x mandatory;
                margin-right: calc(var(--borde) * -1); padding-right: var(--borde);
                scrollbar-width: none }
  .pauta__posts::-webkit-scrollbar{ display: none }
  .post{ flex: 0 0 58vw; max-width: 15rem; scroll-snap-align: start }
}

@media (max-width: 899px){
  /* En teléfono la rejilla de 12 se vuelve de 6: las piezas conservan su
     jerarquía relativa (la de span 3 sigue siendo la mitad de la de span 6)
     sin quedar de 60px de ancho. La portada pasa a ancho completo porque
     vertical y estrecha a la vez es ilegible. */
  .oficio__frag{ grid-template-columns: repeat(6, 1fr); margin-right: 0 }
  .pieza--portada{ grid-column: 1 / -1 !important; height: auto }
  .pieza--suelta{ padding-right: 0 }
  /* La composición de dos columnas no cabe: todo se apila y el lienzo va
     primero, entero y sin sangrado —cortado a 390px no quedaría nada legible
     de la tabla. El `auto` de la fila deja que el flujo natural mande. */
  /* `:nth-child(n)` y no `*`: con el universal esta regla pesa (0,1,0) y pierde
     contra las de escritorio, que llevan `:nth-child(1)` y pesan (0,2,0). Se
     midió el resultado — el lienzo quedaba de 17px de ancho en el teléfono,
     porque seguía obedeciendo `grid-column: 5 / -1` sobre una rejilla de 6. */
  .oficio__frag--sistemas > :nth-child(n){ grid-column: 1 / -1; grid-row: auto }
  .oficio__frag--sistemas > :nth-child(1) img{ width: 100% }
}

/* ── LA COMPOSICIÓN EN TELÉFONO: apilar NO es adaptar ──────────────────────
   Reporte de paco: "en servicios se ve demasiado largo y poco estético".
   Medido a 390px y era literal: #servicios medía 5.5 pantallas en teléfono
   contra 3.6 en escritorio, y de las 16 piezas 11 salían a ancho completo.
   Con todo a 350px de ancho la jerarquía desaparece —el dato suelto pesa lo
   mismo que la prueba— y la sección se lee como una lista de tarjetas, que es
   justo el defecto que la rejilla de 12 existe para evitar.

   El arreglo no es encoger: es RE-COMPONER. En la rejilla de 6 la PRUEBA
   (el lienzo, la pauta, la portada) se queda a ancho completo, y los DATOS
   —que son bloques bajos de dos o tres renglones— se aparean de tres columnas.
   Se conserva la relación grande/chico del escritorio y se ahorran renglones.

   Se aparean SOLO los bajos. Las piezas con filas `space-between` a 170px
   parten las palabras, así que las que llevan tabla siguen enteras. */
/* ── EN TELÉFONO, TRES PIEZAS POR RENGLÓN Y NO SEIS ────────────────────────
   Reporte de paco: "se ve mucho texto y agobia mucho".
   No era percepción: es REPETICIÓN literal. `.oficio__bajo` ya lista, en
   viñetas, "Panel de administración · Cobro y facturación · Clientes y
   seguimiento · Reportes que sí lees" — y la rejilla de abajo vuelve a decir
   esos MISMOS rótulos en tarjetas. La pieza "Entrega · 5 sem" duplica el plazo
   que el encabezado del renglón ya trae ("4 – 8 semanas"). En escritorio eso se
   lee como una composición porque las piezas conviven a lo ancho; apiladas en
   390px se leen como la misma frase dicha tres veces.

   Se quedan las TRES primeras de cada renglón, que es lo que no se repite:
     · la PRUEBA  — la portada de papel, la captura del panel, la tira de posts
     · una CIFRA  — $18,420 · $126,400 · $38 por mensaje
     · el DIFERENCIADOR — "tú cambias el precio", "cada cliente ve su proyecto"
   Y se van de la 4ª en adelante, que son los rótulos ya dichos arriba.

   Efecto lateral buscado: la que se iba encima del título del oficio siguiente
   era justamente la ÚLTIMA (`.pieza--hundida`). Al no estar, el choque que paco
   siguió viendo en su foto desaparece por construcción, no por z-index. */
@media (max-width: 899px){
  .oficio__frag > :nth-child(n+4){ display: none }

  /* La rejilla no tenía margen INFERIOR: en escritorio no hace falta porque el
     renglón siguiente trae su propio aire y el ancho separa, pero apilado a
     390px el último dato ("Transferencia · 3 · $6,520") quedaba pegado al
     titular del oficio siguiente y se leía como una sola pieza rota. El
     ensamblado, que aún empuja la pieza un poco hacia abajo, lo agravaba.
     Aquí va el aire, y va abajo de la rejilla y no arriba del titular, para que
     el último oficio también cierre con respiro antes del final de la sección. */
  .oficio__frag{ margin-bottom: clamp(3rem, 9vw, 4.5rem) }

  /* Cinturón que se queda igual: si alguna pieza viaja de más durante el
     ensamblado, el título del oficio siguiente pinta ENCIMA en vez de quedar
     tapado. Las piezas llevan fondo opaco. */
  .oficio__renglon{ position: relative; z-index: 2 }
  .oficio__frag{ position: relative; z-index: 1 }

  /* Con tres piezas, las dos de datos se aparean debajo de la prueba: una
     columna de tres tarjetas iguales vuelve a ser la lista que estorbaba. */
  .oficio__frag > :nth-child(2),
  .oficio__frag > :nth-child(3){ grid-column: span 3 }
  /* salvo cuando la 2ª es la pieza alta con tabla: a 170px parte las palabras */
  .oficio__frag > .pieza--alta{ grid-column: 1 / -1 }
}
.oficio:last-child{ border-bottom: 1px solid var(--tx-linea) }

@media (max-width: 899px){
  /* En teléfono las partes vuelven a una columna y el bajo se apila: dos
     columnas de lista en 390px dejan las palabras partidas. */
  .oficio__bajo{ grid-template-columns: 1fr }
  .oficio__partes{ grid-template-columns: 1fr }
}

/* Se quitó el acercamiento en Z al pasar el cursor. Con renglones cortos en dos
   columnas leía como profundidad; con un renglón a sangre de borde a borde, que
   la franja entera avance 38px hacia la cara se lee como un salto, no como
   craft. El movimiento de esta sección ya lo lleva el gesto del visor. */

/* OJO: `ch` en el contenedor se mide con la fuente del CONTENEDOR (General
   Sans 15px), no con la del titular. Un `max-width:24ch` aquí valía ~168px y
   partía el título en cinco líneas. En un bloque que mezcla dos familias, la
   medida va en rem. */
.cabecera-seccion--ancha{ max-width: min(92vw, 44rem) }

/* ============================================================
   v3.3 — MARCO: cómo se presenta una interfaz capturada
   ------------------------------------------------------------
   Técnica medida en outrank.so (2026-07-29): los paneles de
   producto NO van planos ni a sangre. Van en un marco con
   hairline y sombra suave, y ROTADOS ±2° ALTERNANDO — divs de
   614×317 con rotate(-2deg) / rotate(2deg) y transition-transform
   para que se enderecen al pasar el cursor. Sin perspectiva ni
   matrix3d: es rotación 2D pura.

   Lo que cambio respecto a ellos:
   · su inclinación es FIJA; aquí se endereza conforme el panel
     entra en la ventana, que es la misma idea pero viva.
   · su marco es blanco de SaaS; aquí es la cáscara concéntrica
     del sistema (radio interno = radio externo − padding), en
     los tokens de papel/tinta. El oro sigue siendo el único calor.
   · NO copio su resolución: sirven 825px estirados a 1440. Con
     fotos eso se ve suave; aquí el sobremuestreo es 2× real.
   ============================================================ */

.marco{
  --r: 14px;
  --inc: -2deg;                       /* la alternancia la pone :nth-child */
  padding: 10px;
  border-radius: var(--r);
  background: color-mix(in oklab, var(--papel) 82%, white);
  border: 1px solid var(--tx-linea);
  /* sombra en DOS capas: una de contacto corta y una de ambiente larga.
     Una sola sombra es lo que hace que las tarjetas se lean a plantilla. */
  box-shadow:
    0 1px 2px rgba(9,10,13,.05),
    0 18px 40px -18px rgba(9,10,13,.22);
  transform: rotate(var(--inc));
  transform-origin: 50% 60%;
  transition: transform .5s var(--curva), box-shadow .5s var(--curva);
  will-change: transform;
}
.marco > *{
  /* radio concéntrico: el interior tiene que ser el externo MENOS el padding,
     si no las curvas no son paralelas y se nota aunque nadie sepa por qué */
  border-radius: calc(var(--r) - 10px);
  overflow: hidden;
  display: block;
}

/* Alternancia. OJO: NO se puede colgar de .marco:nth-child(even) — cada marco
   es el primer hijo de SU propio contenedor, así que todos son :first-child y
   los cuatro salían inclinados hacia el mismo lado. La alternancia tiene que
   leerse del ancestro que sí tiene hermanos. */
.caso:nth-child(even) .marco,
.marco:nth-of-type(even){ --inc: 2deg }

/* enderezado: la clase la pone el JS al entrar en la ventana */
.marco.derecho{ --inc: 0deg }

/* ============================================================
   INTEGRACIÓN DE CAPTURAS — la "magia" SIN filtro
   ------------------------------------------------------------
   El problema: cada cliente tiene su propio tema y sus propios
   colores; pegados sobre esta página se leen a recorte. La salida
   fácil es un filtro —desaturar, duotono, velo de color— y es
   justo lo que hace que un portafolio se vea a plantilla, porque
   deja de enseñar el trabajo y empieza a enseñar el filtro.

   La técnica real es la inversa: NO se toca la imagen, se hace que
   LA PÁGINA REACCIONE A ELLA. Tres capas, todas alimentadas por un
   solo dato — `--derrame`, el color dominante medido de cada
   captura (Black Secret ámbar 30°, Chilita rojo 355° a 77%):

     1 · DERRAME — la pieza emite su propia luz sobre el fondo.
         Es lo que hace que "deba ir ahí": la página se tiñe de la
         captura, no al revés.
     2 · SOMBRA TEÑIDA — la sombra larga lleva el color de la pieza
         en vez de gris neutro. Un objeto real tiñe su sombra.
     3 · ARISTA QUE ATRAPA LA LUZ — 1px claro en el canto superior.
         Sin esto el marco es un rectángulo; con esto es un objeto
         con espesor bajo una fuente de luz.
   ============================================================ */
.marco{
  --derrame: rgba(160,160,170,.55);        /* neutro si nadie lo declara */
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.5),                                   /* 3 */
    0 1px 2px rgba(9,10,13,.05),
    0 18px 40px -18px rgba(9,10,13,.22),
    0 46px 90px -40px color-mix(in oklab, var(--derrame) var(--tinte-sombra,34%), transparent); /* 2 */
}
.marco::before{                                                            /* 1 */
  content:''; position:absolute; inset:-7% -5%; z-index:-1;
  pointer-events:none; border-radius:50%;
  /* Sobre PAPEL el derrame va bajísimo. Un rojo al 52% sobre un fondo claro
     inunda la sección y se convierte justo en el filtro que había que evitar;
     sobre tinta el mismo valor apenas se nota. La regla no es un número: es
     que el derrame se SIENTA y no se VEA. Aquí se siente a 16%. */
  background:radial-gradient(closest-side,
              color-mix(in oklab, var(--derrame) 16%, transparent), transparent 70%);
  filter:blur(66px);
  opacity:0; transition:opacity .9s var(--curva);
}
/* el derrame se enciende cuando la pieza entra de verdad — la misma clase
   que ya endereza el marco, así luz y gesto ocurren en el mismo momento */
.marco.derecho::before{ opacity:1 }
/* sobre tinta sí puede crecer: el negro se come la luz y ahí es donde el
   derrame hace el trabajo de integrar */
.oscuro .marco::before{
  inset:-13% -9%; filter:blur(54px);
  background:radial-gradient(closest-side,
              color-mix(in oklab, var(--derrame) 44%, transparent), transparent 72%);
}
/* La sombra teñida también se modera sobre papel */
.marco{ --tinte-sombra:34% }
.oscuro .marco{ --tinte-sombra:62% }

/* Un vídeo dentro del chasis se comporta igual que una captura */
.chasis video{ display:block; width:100%; height:auto }
.chasis--telefono video{ border-radius:22px }

@media (hover: hover){
  .marco:hover{
    --inc: 0deg;
    box-shadow: 0 2px 4px rgba(9,10,13,.06), 0 30px 60px -20px rgba(9,10,13,.28);
  }
}

/* sobre fondo oscuro el marco se invierte: cáscara de superficie, no de papel */
.oscuro .marco{
  background: var(--sup-2);
  border-color: var(--tx-linea);
  box-shadow: 0 1px 2px rgba(0,0,0,.4), 0 24px 50px -20px rgba(0,0,0,.6);
}

/* nadie inclina en móvil: a una columna los bordes rotados chocan entre sí */
@media (max-width: 760px){
  .marco{ --inc: 0deg !important; padding: 7px; --r: 11px }
}

@media (prefers-reduced-motion: reduce){
  .marco{ --inc: 0deg !important; transition: none }
}

/* ============================================================
   MARCO DESNUDO — el estuche, medido
   ------------------------------------------------------------
   Scout del 2026-07-30, 73 imágenes de 5 estudios en vivo (Exo Ape,
   OBYS, Hello Monday, Phantom, Basic/Dept): **0 con sombra, 0 con
   borde, 0 anidadas** en un contenedor con relleno + fondo + radio,
   y radio mediano **0**. Nuestro marco tenía las cuatro cosas a la
   vez, y encima un `.chasis` dentro = doble bisel. Ésa —y no la
   captura— es la parte que se lee "hecha por IA".

   Contradice al skill `high-end-visual-design`, que ordena doble
   bisel. Manda lo medido; es la tercera vez que la misma medición
   da lo mismo en este sitio.

   Lo que SÍ se conserva: el DERRAME (`.marco::before`). Eso es luz,
   no caja — es lo que integra la captura con la página sin recurrir
   a un filtro, que es la salida barata. El `.chasis` también se
   queda: la barra del navegador no decora, dice "esto es un sitio".
   ============================================================ */
.marco--desnudo,
.oscuro .marco.marco--desnudo{
  --inc: 0deg;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
.marco--desnudo > *{ border-radius: 0 }
@media (hover: hover){ .marco--desnudo:hover{ box-shadow: none } }
/* el chasis de teléfono conserva su cáscara: un teléfono SIN marco deja de
   leerse como teléfono, y ahí el borde no es adorno sino el objeto */
.marco--desnudo .chasis--telefono{ border-radius: 30px }

/* ============================================================
   NINGÚN CASO MIDE LO QUE EL ANTERIOR (ley Exo Ape)
   ------------------------------------------------------------
   Estaban los cuatro en la misma rejilla: tres de cuatro ocupaban
   exactamente el 60% del ancho. En Exo Ape ninguna pieza mide lo
   que la de arriba —cambian de escala Y de sangría—, y eso es lo
   que se lee compuesto en vez de listado. No es motion: es medida.
   ============================================================ */
@media (min-width: 900px){
  .caso{ grid-template-columns: var(--m, 1.35fr) var(--t, .65fr) }
  .caso--invertido{ grid-template-columns: var(--t, .65fr) var(--m, 1.35fr) }

  .casos > .caso:nth-child(1){ --m: 1.9fr;  --t: .75fr }   /* el más grande: lleva el barrido */
  .casos > .caso:nth-child(2){ --m: .58fr;  --t: 1.55fr }  /* un teléfono debe leerse pequeño */
  .casos > .caso:nth-child(3){ --m: 1.15fr; --t: .9fr }
  .casos > .caso:nth-child(4){ --m: 1.55fr; --t: .8fr }

  /* y ninguno arranca en el mismo renglón que el de arriba */
  .casos > .caso:nth-child(2){ margin-left: 8% }
  .casos > .caso:nth-child(2) .caso__media{ margin-top: 3.5rem }
  .casos > .caso:nth-child(3){ margin-right: 6% }
  .casos > .caso:nth-child(4) .caso__media{ margin-top: 2rem }
}

/* ============================================================
   v3.4 — ICONOGRAFÍA derivada de la C
   ------------------------------------------------------------
   La gramática sale del c-mark real, no de un gusto: relleno
   plano sin contorno, construcción sobre retícula, DOS esquinas
   opuestas abiertas, un módulo cuadrado suelto en cada hueco,
   grosor de barra constante y remates a escuadra.

   No se cortan en 14 archivos: las dos rejillas son SVG de
   2048×2048 con 3×3 celdas de 682.67px, y cada icono se
   direcciona por posición. Dos peticiones de red en vez de
   catorce.

   Van con `mask-image`, no con `background-image`: así el icono
   toma currentColor y el MISMO archivo sirve sobre papel y sobre
   oscuro. Con background habría que producir una versión blanca.
   ============================================================ */

.ico{
  --hoja: url('./iconos/rejilla-b.svg');
  --cx: 0; --cy: 0;                       /* columna y fila, 0-2 */
  display: inline-block;
  width: 1em; height: 1em;
  background: currentColor;
  -webkit-mask-image: var(--hoja);        mask-image: var(--hoja);
  -webkit-mask-size: 300% 300%;           mask-size: 300% 300%;
  -webkit-mask-repeat: no-repeat;         mask-repeat: no-repeat;
  -webkit-mask-position: calc(var(--cx) * 50%) calc(var(--cy) * 50%);
          mask-position: calc(var(--cx) * 50%) calc(var(--cy) * 50%);
  flex: none;
}
.ico--a{ --hoja: url('./iconos/rejilla-a.svg') }

/* Hoja B — lo que se entrega */
.ico-tienda    { --cx:0; --cy:0 }
.ico-cobro     { --cx:1; --cy:0 }
.ico-correo    { --cx:2; --cy:0 }
.ico-panel     { --cx:0; --cy:1 }
.ico-documento { --cx:1; --cy:1 }
.ico-grafica   { --cx:2; --cy:1 }
.ico-calendario{ --cx:0; --cy:2 }
.ico-bolsa     { --cx:1; --cy:2 }
.ico-engrane   { --cx:2; --cy:2 }

/* Hoja A — el proceso y la gente */
.ico-persona   { --cx:0; --cy:0 }
.ico-megafono  { --cx:1; --cy:0 }
.ico-palomita  { --cx:0; --cy:1 }
.ico-archivero { --cx:2; --cy:1 }
.ico-reproducir{ --cx:0; --cy:2 }
.ico-reloj     { --cx:1; --cy:2 }
/* las celdas 2,0 · 1,1 · 2,2 de la hoja A quedaron fuera del sistema
   (la llave se lee a código de barras, las cajas a signo de admiración
   y el acuerdo a bloques sueltos). No se usan. */

/* ============================================================
   v3.5 — CHASIS: el error de mis primeras capturas
   ------------------------------------------------------------
   Lo que hacía mal: pegaba una foto de pantalla del navegador
   —con la burbuja de chat, el popup y la barra de anuncio del
   sitio incluidas— dentro de una caja. Eso se lee a captura de
   soporte técnico, no a portafolio.

   Lo que hacen las marcas: la captura va DESNUDA (recortada a
   ras del contenido, sin el cromo del navegador real) y el
   cromo se DIBUJA alrededor, limpio y consistente. Así las
   cuatro fichas tienen el mismo marco aunque las capturas
   vengan de sitios distintos.

   Va en CSS y no en SVG a propósito: el cromo hereda los tokens
   (sirve igual sobre papel y sobre oscuro), queda nítido a
   cualquier densidad y no cuesta una petición más.

   Anidado: .marco (cáscara + sombra + rotación) > .chasis > img
   ============================================================ */

.chasis{
  --cromo: color-mix(in oklab, var(--papel-alto) 88%, var(--tx-1) 12%);
  --raya:  var(--tx-linea);
  position: relative;
  background: var(--cromo);
  overflow: hidden;
  display: block;
}
.oscuro .chasis{
  --cromo: var(--sup-3);
  --raya: var(--tx-linea);
}
.chasis img, .chasis video{
  display: block; width: 100%; height: auto;
}

/* ── 1 · ESCRITORIO ───────────────────────────────────────────
   Barra de 30px con tres puntos y una píldora de dirección. Los
   puntos NO son de colores: en monocromo el semáforo de macOS es
   lo que grita "captura de pantalla". Van al 22% de la tinta. */
.chasis--escritorio{ padding-top: 30px }
.chasis--escritorio::before{
  content:''; position:absolute; inset:0 0 auto; height:30px;
  border-bottom:1px solid var(--raya);
  background:
    /* tres puntos */
    radial-gradient(circle 3px at 14px 15px, currentColor 99%, transparent 100%),
    radial-gradient(circle 3px at 27px 15px, currentColor 99%, transparent 100%),
    radial-gradient(circle 3px at 40px 15px, currentColor 99%, transparent 100%);
  opacity:.22;
}
/* la píldora de dirección va aparte para poder darle su propio tono */
.chasis--escritorio::after{
  content:''; position:absolute; top:8px; left:58px; right:14px; height:14px;
  border-radius:99px; background:currentColor; opacity:.07;
}

/* ── 2 · TELÉFONO ─────────────────────────────────────────────
   Marco parejo de 8px y la isla arriba. Nada de botones de
   volumen ni de reflejo de cristal: eso es maqueta de dribbble. */
.chasis--telefono{
  padding: 8px; border-radius: 30px;
  background: var(--cromo); border: 1px solid var(--raya);
}
.chasis--telefono > *{ border-radius: 23px; overflow:hidden; display:block }
.chasis--telefono::after{
  content:''; position:absolute; top:15px; left:50%; transform:translateX(-50%);
  width:64px; height:8px; border-radius:99px;
  background:currentColor; opacity:.30; z-index:2;
}

/* ── 3 · VENTANA DE PANEL ─────────────────────────────────────
   Para el software a la medida: riel lateral + barra corta. Es
   el chasis que dice "esto es una herramienta", no "esto es un
   sitio". */
.chasis--panel{ padding: 26px 0 0 44px }
.chasis--panel::before{
  content:''; position:absolute; inset:0 0 auto; height:26px;
  border-bottom:1px solid var(--raya);
  background: radial-gradient(circle 2.5px at 13px 13px, currentColor 99%, transparent 100%);
  opacity:.22;
}
.chasis--panel::after{
  content:''; position:absolute; top:26px; bottom:0; left:0; width:44px;
  border-right:1px solid var(--raya);
  /* cinco renglones de menú, insinuados */
  background:
    linear-gradient(currentColor,currentColor) 12px 18px / 20px 3px no-repeat,
    linear-gradient(currentColor,currentColor) 12px 34px / 20px 3px no-repeat,
    linear-gradient(currentColor,currentColor) 12px 50px / 20px 3px no-repeat,
    linear-gradient(currentColor,currentColor) 12px 66px / 20px 3px no-repeat,
    linear-gradient(currentColor,currentColor) 12px 82px / 20px 3px no-repeat;
  background-color: color-mix(in oklab, var(--cromo) 70%, var(--tx-1) 6%);
  opacity:1;
}
.chasis--panel::after{ }

/* en móvil el chasis de escritorio se encoge: una barra de 30px
   sobre una captura de 300px de ancho ocupa el 10% del alto */
@media (max-width: 620px){
  .chasis--escritorio{ padding-top: 22px }
  .chasis--escritorio::before{ height:22px;
    background:
      radial-gradient(circle 2.2px at 11px 11px, currentColor 99%, transparent 100%),
      radial-gradient(circle 2.2px at 21px 11px, currentColor 99%, transparent 100%),
      radial-gradient(circle 2.2px at 31px 11px, currentColor 99%, transparent 100%) }
  .chasis--escritorio::after{ top:6px; left:44px; height:10px }
  .chasis--panel{ padding-left: 34px }
  .chasis--panel::after{ width:34px }
}

/* El caso vertical no puede ocupar el mismo ancho que uno de escritorio:
   un teléfono a 16:9 de ancho se vuelve una torre de 1,700px de alto. */
.marco--vertical{ width:min(300px,62vw); margin-inline:auto }
@media (min-width:900px){ .marco--vertical{ margin-inline:0 } }

/* (La captura del portal ya no se recorta con object-fit: entra completa y la
   lente la recorre — ver §10.) */

/* ============================================================
   v3.5 — LA PUERTA · el calibre que cierra
   ------------------------------------------------------------
   Por qué existe. Medido el arranque del sitio cada 250 ms: el
   hero llegaba a su estado final a los 0.25 s y de ahí la
   diferencia entre cuadros caía a 0.3 y luego a 0.0. No es que la
   entrada fuera sutil: NO HABÍA. La página simplemente ya estaba.
   De las seis referencias medidas, cuatro sí cierran la puerta
   antes de dejar entrar (exoape 1.75 s · lusion 3.0 · obys 5.0 ·
   basic con su logotipo). Las dos que no lo hacen —igloo y
   14islands— son también las dos que menos se sienten producidas.

   Por qué ESTE gesto y no un contador cualquiera. La marca cae
   exacta en una retícula de 5x5 con módulo 140, y se parte sola en
   DOS piezas: la de arriba-izquierda y la de abajo-derecha, con el
   cuadrado suelto en medio. O sea que el logo YA ERA un calibre:
   dos mordazas y lo que miden. Aquí entran separadas por el eje de
   45° —el mismo ángulo de sus chaflanes, que van por delante como
   filo—, se cierran, y el cuadrado del centro es la apertura por
   la que se ve el sitio.

   Presupuesto 1.6 s, no 5. Obys puede cobrar cinco segundos; un
   estudio que le vende velocidad a negocios mexicanos no, y en 4G
   una puerta larga se lee como sitio roto.

   ⚠️ SEGURIDAD: una puerta que no abre es una pantalla negra y un
   visitante perdido. Por eso hay TRES salidas independientes:
   (1) el JS abre cuando la carga real terminó; (2) si el JS nunca
   corre, el rescate en CSS de aquí abajo la borra a los 2.9 s;
   (3) si `@property` no existe, `--a` salta a su valor final y la
   apertura ocurre de golpe — sin animar, pero ocurre.
   ============================================================ */

/* La apertura se mide en FRACCIÓN de media pantalla, no en píxeles. Con una
   sola longitud contra media pantalla el hueco crecía a la misma velocidad en
   los dos ejes, y como el alto es menor que el ancho terminaba de abrir arriba
   y abajo mucho antes que de lado: se leía como una persiana, no como una
   apertura. Con una fracción, los cuatro bordes llegan juntos. */
/* Arranca en NEGATIVO: las cuatro hojas se traslapan un pelo en el centro en
   vez de dejar un hueco. Con 0.02 quedaba una ventanita de ~18 px por la que
   se veía la página, y como el contador empujaba la marca hacia arriba, ese
   hueco asomaba DEBAJO del logo como una mancha gris. La apertura tiene que
   nacer detrás del cuadrado de la marca, no al lado. */
@property --k{ syntax:'<number>'; inherits:true; initial-value:-0.006 }

.puerta{
  position:fixed; inset:0; z-index:200;
  display:grid; place-items:center;
  color:var(--papel);
  transition:--k .58s var(--curva);
  animation:puerta-rescate .4s 2.4s forwards;   /* salida (2) */
}
.puerta--abre{ --k:1.06 }
.puerta--fuera{ opacity:0; visibility:hidden; pointer-events:none;
                transition:opacity .3s ease, visibility 0s .3s }

/* Las cuatro hojas son cada una la ventana COMPLETA, corridas media
   pantalla más la apertura. Así el hueco del centro es exactamente
   2·--a y todo el movimiento es `transform` — cero reflujo. */
.puerta__hoja{ position:absolute; inset:0; background:var(--tinta) }
.puerta__hoja--n{ transform:translateY(calc(-50% - var(--k) * 50%)) }
.puerta__hoja--s{ transform:translateY(calc( 50% + var(--k) * 50%)) }
.puerta__hoja--o{ transform:translateX(calc(-50% - var(--k) * 50%)) }
.puerta__hoja--e{ transform:translateX(calc( 50% + var(--k) * 50%)) }

/* El contador va ABSOLUTO: si estuviera en el flujo, su altura correría la
   marca hacia arriba y la marca dejaría de estar en el centro de la ventana,
   que es justo donde nace la apertura. */
.puerta__centro{ position:relative; z-index:2; display:grid; place-items:center }
.puerta__marca{
  /* Medido en las referencias: la marca de exoape ocupa ~2.8% del ancho
     de la ventana durante su precarga. A 82px iba en 5.7% y se leía como
     pantalla de logo, no como un detalle. 64px la deja en 4.4%. */
  width:clamp(44px, 4.4vw, 64px); height:auto; display:block;
  fill:currentColor; overflow:visible;      /* las mordazas empiezan FUERA de su caja */
}
.puerta__sup, .puerta__inf, .puerta__nucleo{ transform-box:view-box; transform-origin:center }
.puerta__sup{ animation:calibre-sup .72s .06s var(--curva) both }
.puerta__inf{ animation:calibre-inf .72s .06s var(--curva) both }
.puerta__nucleo{ animation:calibre-nucleo .3s .74s var(--curva) both }
.puerta__medida{
  position:absolute; top:calc(100% + 1.15rem); left:50%; transform:translateX(-50%);
  margin:0; white-space:nowrap;
  /* NO `--tx-3`: ese token se calcula sobre la tinta y aquí el fondo ES la
     tinta — el número salía en rgba(9,10,13,.5) sobre #08090C, o sea invisible.
     No se vio en un solo cuadro de la grabación. Se mezcla contra el papel
     que la puerta ya tiene como color propio. */
  font-size:.6875rem; letter-spacing:.22em;
  color:color-mix(in oklab, currentColor 62%, transparent);
  animation:calibre-medida .4s .18s var(--curva) both;
}

@keyframes calibre-sup{ from{ transform:translate(-300px,-300px); opacity:0 } to{ transform:none; opacity:1 } }
@keyframes calibre-inf{ from{ transform:translate( 300px, 300px); opacity:0 } to{ transform:none; opacity:1 } }
/* nunca desde scale(0): nada en el mundo real aparece de la nada */
@keyframes calibre-nucleo{ from{ transform:scale(.74); opacity:0 } to{ transform:none; opacity:1 } }
@keyframes calibre-medida{ from{ opacity:0 } to{ opacity:1 } }
@keyframes puerta-rescate{ to{ opacity:0; visibility:hidden; pointer-events:none } }

/* El titular del hero no puede jugar detrás de la puerta: si se parte y sube
   mientras el calibre está cerrado, cuando la apertura abre el hero YA PASÓ.
   Se esconde solo si la puerta de verdad va a correr —la clase la pone el JS—,
   así que si el JS nunca arranca el hero se ve, como siempre. */
html.puerta-activa .hero [data-rev] > *{ opacity:.001; transform:translate3d(0,26px,0) }

/* La marca del encabezado nace apagada SOLO con la puerta activa: es la que
   recibe el vuelo del calibre al final. Sin puerta, se ve desde el principio. */
.cab__marca{ display:flex; align-items:center; gap:.5rem }
.cab__marca svg{ width:.95em; height:.95em; fill:currentColor; flex:none }
html.puerta-activa .cab__marca svg{ opacity:0 }

/* Segunda visita en la misma sesión: se entra directo. Y con movimiento
   reducido no hay puerta en absoluto. */
html.visto .puerta{ display:none }
@media (prefers-reduced-motion:reduce){
  .puerta{ display:none }
  html.puerta-activa .hero [data-rev] > *{ opacity:1; transform:none }
  html.puerta-activa .cab__marca svg{ opacity:1 }
}
.puerta--abre .puerta__medida{ opacity:0; transition:opacity .22s ease }

/* La marca firmada en el pie. El símbolo va al tamaño del texto que acompaña
   —`1em` sobre un `d3`— y alineado por su caja óptica, no por la línea base:
   una C cuadrada sentada en la línea base se ve caída junto a una mayúscula. */
.pie__marca{ display:flex; align-items:center; gap:.62em; margin:0 }
.pie__simbolo{ width:.92em; height:.92em; fill:currentColor; flex:none; translate:0 -.02em }

/* ============================================================
   v3.6 — EL ECLIPSE en el hero
   ------------------------------------------------------------
   Va DEBAJO del texto y ENCIMA del lienzo del shader. El orden
   importa: la niebla del shader pasa por arriba del cuerpo y lo
   mete dentro de la escena — sin eso se lee como estampa pegada,
   que es la misma trampa del PNG sobrepuesto ya anotada.
   Por eso el grano y la viñeta del sitio también van encima del
   video, no debajo: un plano limpio sobre un fondo con grano se
   delata al instante.
   ============================================================ */
.eclipse{ position:absolute; inset:0; z-index:1; overflow:clip; pointer-events:none }
.eclipse__foto{
  position:absolute; inset:0; width:100%; height:100%;
  object-fit:cover; object-position:58% 46%; display:block;
}
/* la izquierda se apaga para que el titular no pelee con el filo */
.eclipse__cortina{
  position:absolute; inset:0;
  background:linear-gradient(100deg, #08090C 20%, rgba(8,9,12,.86) 38%, rgba(8,9,12,0) 62%);
}
.eclipse__polvo{
  position:absolute; inset:0; mix-blend-mode:overlay; opacity:.4;
  background-image:url('../media/materia/velada.webp'); background-size:cover;
}
.eclipse__vineta{
  position:absolute; inset:0;
  background:radial-gradient(120% 92% at 62% 46%, rgba(0,0,0,0) 32%, rgba(0,0,0,.7) 100%);
}
.hero__cuerpo{ position:relative; z-index:3 }

/* En teléfono NO es el mismo encuadre encogido: es su propia toma, generada en
   vertical. Encogiendo la horizontal, el filo cruzaba por detrás del titular.
   El wow se RE-COMPONE, no se reduce. */
@media (max-width:760px){
  .eclipse__foto{ object-position:50% 0% }
  .eclipse__cortina{ background:linear-gradient(185deg, rgba(8,9,12,0) 22%, rgba(8,9,12,.55) 46%, #08090C 66%) }
  .eclipse__vineta{ background:radial-gradient(110% 60% at 62% 18%, rgba(0,0,0,0) 30%, rgba(0,0,0,.6) 100%) }
}

/* Las dos rutas de contacto del pie */
.pie__rutas{ display:flex; align-items:center; gap:1.4rem; flex-wrap:wrap; margin:0 }
.pie__correo{ font-size:.9375rem; color:var(--tx-2); border-bottom:1px solid currentColor;
              padding-bottom:.1em; transition:color var(--base) var(--curva) }
.pie__correo:hover{ color:var(--tx-1) }

/* ============================================================
   v3.7 — PISO DE LECTURA EN TELÉFONO
   ------------------------------------------------------------
   Reporte de paco: "en móvil se ve demasiado texto y eso cansa",
   y autorizó que la página se ALARGUE si a cambio se digiere mejor.

   Medido en vivo a 390px antes de tocar nada, y el número desmiente
   la primera hipótesis —la mía incluida—: el sitio tiene 941 palabras
   repartidas en 22.2 pantallas, o sea 42 por pantalla. Eso NO es mucho
   texto. El problema es otro y sale en la tabla de tamaños:

     10px  →  98 palabras
     12px  → 286 palabras
     14px  →  77
     15px  → 242
     18px  → 124

   **384 palabras, el 41% del texto del sitio, viven entre 10 y 12px**
   en un teléfono que se lee a 40 cm. Eso es letra de contrato. Lo que
   cansa no es la cantidad: es que casi la mitad haya que descifrarla.

   Y la carga está concentrada, no repartida:
     #servicios  76 palabras por pantalla
     #trabajo    55
     #entrada    36
     #portal     24
     #sistemas   13   ← la que se siente premium y aireada
     #cierre     11

   Subir el tamaño resuelve las dos cosas a la vez: se lee sin esfuerzo
   y, como el bloque ocupa más alto, la densidad por pantalla BAJA sola.
   La página se alarga; es exactamente el intercambio que paco aceptó.

   ⚠️ LO QUE NO SE TOCA: el texto dentro de las maquetas —`.portada__*`
   de la portada de papel y `.post__*` de la tira de publicaciones—. Ahí
   la letra chica ES el diseño de la pantalla que se está simulando; al
   agrandarla deja de leerse como una captura y pasa a leerse como
   nuestro propio texto mal maquetado. Por eso esta regla enumera clases
   y no usa un selector universal.
   ============================================================ */
@media (max-width: 899px){
  /* 1 · TEXTO DE LECTURA — nada por debajo de 15px */
  .pieza__nota,
  .pieza__fila, .pieza__fila b, .pieza__fila span, .pieza__si,
  .pie-foto, .caso__cifra, .entrada__nota,
  .meta-lista dt, .meta-lista dd,
  .lista-fina li, .oficio__partes li{
    font-size: .9375rem;
    line-height: 1.5;
  }

  /* 2 · RÓTULOS EN CAJA ALTA — se leen en ráfaga, no en línea, así que
     pueden ir un punto abajo. Pero 10px con tracking es un jeroglífico:
     el piso son 13. Y baja el tracking, porque a mayor tamaño la misma
     separación empieza a desarmar la palabra. */
  .pieza__rot, .pieza__pie, .rotulo, .oficio__plazo .mono{
    font-size: .8125rem;
    letter-spacing: .07em;
    line-height: 1.35;
  }

  /* 3 · AIRE. Subir el cuerpo sin subir el interlineado deja el párrafo
     más apretado que antes, no más legible. */
  .caso__texto, .oficio__dicho{ line-height: 1.65 }
  .pieza{ gap: .85rem; padding: 1.15rem 1.25rem }
  .meta-lista{ gap: .8rem }
  .oficio__partes{ gap: .85rem }
}

/* ============================================================
   v3.8 — EN TELÉFONO MANDA LA IMAGEN
   ------------------------------------------------------------
   Reporte de paco: "el texto opaca a las imágenes, parece una
   biblia de texto con números y no se le da peso a las imágenes".

   Medido: superficie de imagen dividida entre superficie de texto,
   por sección, móvil contra escritorio.

     sección      móvil   escritorio
     #trabajo      2.71      7.50     ← se desploma casi 3×
     #servicios    0.15      0.30     ← texto casi puro
     #portal       6.84      5.84
     #sistemas     0         0

   O sea que no es impresión: en el teléfono la prueba visual pierde
   dos tercios de su peso relativo justo en la sección que existe para
   enseñar trabajo. Y la causa es geométrica, no de cantidad de texto:
   la captura mide 350px dentro de una ventana de 390 —no llega a los
   bordes— y su ventana es 16/10, o sea 350×219. Una imagen que deja
   márgenes a los lados se lee como TARJETA; una que toca los dos
   bordes se lee como PLANO. Es la misma imagen y pesa distinto.

   Tres movimientos, sólo aquí:
     · sangrado completo — la captura toca los dos bordes
     · la ventana pasa de 16/10 a 1/1 — casi el doble de área
     · fuera el estuche (relleno, radio, inclinación): a sangre el
       marco es contradictorio, no hay borde que enmarcar

   NO se toca `.marco--vertical` (el teléfono de Chilita): a sangre
   dejaría de leerse como un aparato. Ése sólo crece.
   ============================================================ */
@media (max-width: 899px){
  #trabajo .caso__media .marco:not(.marco--vertical){
    margin-inline: calc(var(--borde) * -1);
    padding: 0;
    --r: 0px;
    --inc: 0deg;
    border-radius: 0;
    box-shadow: none;
  }
  #trabajo .caso__media .marco:not(.marco--vertical) .recorre__ventana{
    border-radius: 0;
    /* 350×219 → 390×390: el área pasa de 76k a 152k px². El recorrido
       vertical de la captura sigue funcionando igual —lo mueve `--rec`
       contra `100cqh`, que se recalcula solo. */
    /* 4/5 y no 1/1: la captura de un sitio se recorre a lo ALTO, así que
       cada píxel de alto es recorrido visible. 350×219 → 390×487, o sea el
       área pasa de 76k a 190k px² — dos veces y media. */
    aspect-ratio: 4 / 5;
  }
  /* el pie vuelve al margen del texto: es texto, no imagen */
  #trabajo .pie-foto{ margin-inline: var(--borde) }

  /* el teléfono de Chilita no va a sangre —dejaría de ser un aparato—
     pero sí crece: 300px de tope era medio ancho de pantalla */
  .marco--vertical{ width: min(340px, 86vw) }

  /* #servicios, ratio 0.15. Aquí hay DOS pruebas visuales y sólo una es
     una imagen de verdad:
       · `.pieza--portada` es una maqueta hecha con CSS —por eso no aparecía
         en la medición de superficie de imagen, que sólo cuenta img/video—
         pero ocupa pantalla igual y se lee como prueba.
       · `.pieza--lienzo` sí es una captura real del panel, y medía 310px.
     Las dos toman el ancho entero. */
  .pieza--portada,
  .oficio__frag .pieza--lienzo{
    margin-inline: calc(var(--borde) * -1);
    border-radius: 0;
  }
  .oficio__frag .pieza--lienzo img{ border-radius: 0 }
  /* el pie del lienzo es texto: vuelve al margen */
  .oficio__frag .pieza--lienzo .pieza__pie{ margin-inline: var(--borde) }
}

/* Consecuencia del piso de lectura (§v3.7): a 15px las filas de datos ya no
   caben en una línea dentro de una pieza de ~310px y partían la palabra
   ("Pieza mediana · 22 / cm", "$740 → / $690"). `wrap` deja que el valor baje a
   su propio renglón SÓLO cuando no cabe; las cortas siguen en una línea con el
   valor a la derecha, que es lo que hace que se lean como tabla. */
@media (max-width: 899px){
  .pieza__fila{ flex-wrap: wrap; row-gap: .1rem }
  /* Se probó darle a la etiqueta una base del 58% para que bajara entera en vez
     de partirse: MEDIDO, empeoró de 2 filas partidas a 6 — al fijarle base se
     vuelve más angosta de lo que el contenido pide. `wrap` solo es mejor. */
}

/* ============================================================
   v3.9 — PATRONES DE TELÉFONO: ACORDEÓN Y PUERTAS SUELTAS
   ------------------------------------------------------------
   Propuesta de paco: en móvil no apilar todo hacia abajo, usar
   acordeones y carruseles donde den versatilidad, con sensación
   de app y mejor conversión.

   DÓNDE SÍ — los tres oficios de #servicios. Son hermanos
   equivalentes y mutuamente excluyentes (quien llega quiere UNO)
   y suman 5.1 pantallas de teléfono. El acordeón cambia "bajar
   cinco pantallas" por "elegir uno": scroll → decisión.

   DÓNDE NO — los cuatro casos de #trabajo. Son la prueba de que
   sabemos trabajar; en un carrusel la mayoría no pasa del primer
   elemento y esconderíamos tres de cuatro. Ahí la longitud no es
   un defecto, es el argumento. Se quedan verticales.
   ============================================================ */
@media (max-width: 899px){
  /* El renglón es una rejilla de tres columnas; con el botón dentro, el botón
     era UNA celda y el signo no llegaba al extremo. En teléfono el renglón pasa
     a bloque y el botón hereda la rejilla: así el `auto` del signo empuja
     contra el borde derecho de verdad. */
  .oficio__renglon{ display: block }
  .oficio__disparo{
    all: unset; box-sizing: border-box;
    display: grid !important;
    grid-template-columns: auto minmax(0,1fr) auto auto;
    align-items: baseline; gap: clamp(.7rem, 2.5vw, 1.5rem);
    width: 100%;
    min-height: 44px;                    /* objetivo táctil */
    padding-block: .35rem;
    cursor: pointer; -webkit-tap-highlight-color: transparent;
  }
  .oficio__disparo:focus-visible{ outline: 2px solid var(--bronce-claro); outline-offset: 6px }
  /* el signo empuja a la derecha y gira: el único indicador que hace falta */
  .oficio__signo{
    margin-left: auto; flex: none; position: relative;
    width: 1.1rem; height: 1.1rem; align-self: center;
    transition: transform .42s var(--curva);
  }
  .oficio__signo::before,
  .oficio__signo::after{
    content: ''; position: absolute; inset: 50% 0 auto 0;
    height: 1px; background: currentColor; opacity: .55;
  }
  .oficio__signo::after{ transition: transform .42s var(--curva); transform: rotate(90deg) }
  .oficio.abierto .oficio__signo::after{ transform: rotate(0deg) }

  /* El alto se anima con la rejilla 0fr→1fr: no hace falta medir nada en JS y
     funciona con contenido de alto desconocido. */
  .oficio__panel{
    display: grid; grid-template-rows: 0fr;
    transition: grid-template-rows .42s var(--curva);
  }
  .oficio.abierto .oficio__panel{ grid-template-rows: 1fr }
  .oficio__panel-int{ min-height: 0 }
  /* ⚠️ El recorte SÓLO mientras dura la transición. Permanente recortaría las
     capturas que salen a sangre con margen negativo (§v3.8) — justo lo que se
     acaba de conseguir. */
  .oficio.animando .oficio__panel-int{ overflow: hidden }
  .oficio:not(.abierto) .oficio__panel-int{ overflow: hidden }

  @media (prefers-reduced-motion: reduce){
    .oficio__panel, .oficio__signo, .oficio__signo::after{ transition: none }
  }

}

/* ============================================================
   v3.10 — MENOS CIFRAS Y MÁS PRODUCTO (teléfono)
   ------------------------------------------------------------
   Reporte de paco, sobre su foto de #servicios: "alguien que no
   mueve mucho la tecnología se puede ver afectado por tanto
   número". Y tenía delante la prueba: "$126,400 · Cobrado en el
   mes · 9 pagos" pegado a "Diseño aprobado · 3 de 5", dos tarjetas
   de datos en columnas de 170px con las palabras partidas en dos
   y tres renglones. Eso deja de leerse como prueba y pasa a
   leerse como estado de cuenta.

   El público de Caliber son negocios que NO son técnicos. Una
   pared de cifras no les dice "esto funciona": les dice "esto es
   complicado". El argumento humano ya está arriba, en el párrafo
   y en las cuatro viñetas ("Sitio o tienda completa", "Cobro en
   línea"), que sí hablan su idioma.

   Así que en teléfono cada renglón se queda con DOS piezas:
     · la PRUEBA — la portada de papel, la captura del panel, la
       tira de publicaciones. Es lo que enseña el producto.
     · UNA sola pieza de datos, a ANCHO COMPLETO. Una tabla que
       cabe se lee; dos apretadas a media pantalla, no.
   En escritorio siguen las seis: ahí hay ancho y se leen como
   composición, no como tablero.
   ============================================================ */
@media (max-width: 899px){
  .oficio__frag > :nth-child(n+3){ display: none }
  /* y la que queda deja de estar apareada: el ancho entero es lo que evita
     que la frase se parta y que la tabla se lea apretada */
  /* `!important` porque el primer renglón lleva `grid-column:span 5` en estilo
     INLINE desde el HTML, y un estilo inline le gana a cualquier hoja. */
  .oficio__frag > :nth-child(2){ grid-column: 1 / -1 !important }
}

/* ============================================================
   v3.11 — EL PORTAL SE VE (teléfono)
   ------------------------------------------------------------
   "Aquí no se aprecia mucho el portal, se ve más el texto."
   Medido: los cuatro pasos ocupaban ocho renglones —cada uno se
   partía en dos— antes de que apareciera el teléfono, y el marco
   quedaba en 325px porque su ancho se calcula contra el ALTO que
   sobra. Menos alto para el texto = más teléfono, directo.

   Los pasos que no están activos se recortan a UNA línea: siguen
   dando el mapa de dónde estás sin gastar media pantalla. El
   activo conserva sus dos líneas y su detalle.
   ============================================================ */
@media (max-width: 760px){
  .portal__pest button:not([aria-selected=true]) .pp__t{
    display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .portal__pest button{ padding: .34rem 0 }
  /* La cabecera fija se retira al bajar (§4), así que ya no hace falta
     reservarle 5.25rem de aire aquí arriba. */
  .portal__pegado{ padding-top: 3rem }
  .portal__marco{ width: min(96vw, 420px, calc((100svh - 4.5rem) * 0.462)) }
}

/* ============================================================
   v3.12 — LOS PROYECTOS SE RECORREN A LO ANCHO (teléfono)
   ------------------------------------------------------------
   Idea de paco, y va CONTRA lo que yo había argumentado. Mi
   objeción era buena en general —en un carrusel la mayoría no
   pasa del primer elemento, y aquí eso escondería tres de los
   cuatro casos, que son la única prueba de que sabemos trabajar—.
   Su contra-argumento es mejor: apilados, cada caso compite con
   el siguiente por la misma pantalla; en horizontal cada web
   ocupa el ancho entero y tiene su momento sola.

   Lo que hace la diferencia entre un carrusel que se recorre y
   uno que se abandona en el primero son dos señales, y las dos
   van aquí:
     · el siguiente ASOMA (87vw, no 100vw). Un borde cortado es
       la señal más barata y más clara de que hay más.
     · un CONTADOR de posición, para que se sepa cuántos faltan.

   ⚠️ El sangrado de §v3.8 se ANULA aquí: una captura con margen
   negativo dentro de una tarjeta de carrusel se sale de su celda.
   Aquí la tarjeta ya ocupa el ancho, que era el objetivo.
   ⚠️ Y el paralaje del `.caso` (§caliber.js) se pone a cero: un
   desplazamiento vertical dentro de un carril horizontal
   descuadra las tarjetas entre sí.
   ============================================================ */
@media (max-width: 899px){
  .casos{
    display: flex !important;
    gap: 1rem;
    overflow-x: auto;
    overflow-y: visible;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: var(--borde);
    margin-inline: calc(var(--borde) * -1);
    padding-inline: var(--borde);
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .casos::-webkit-scrollbar{ display: none }

  .caso{
    flex: 0 0 87vw;
    scroll-snap-align: start;
    display: grid; grid-template-columns: 1fr;
    gap: 1.25rem; align-content: start;
  }
  /* dentro del carril, la captura llena la tarjeta y NO se sale */
  #trabajo .caso__media .marco:not(.marco--vertical){
    margin-inline: 0;
  }
  #trabajo .pie-foto{ margin-inline: 0 }
  .caso__meta{ margin-top: 0 }

  /* el contador: una sola línea, discreta, encima del carril */
  .casos-guia{
    display: flex; align-items: center; gap: .75rem;
    margin: 0 0 1rem; color: var(--tx-3);
  }
  .casos-guia__n{ font-variant-numeric: tabular-nums; letter-spacing: .06em }
  .casos-guia__barra{
    flex: 1; height: 1px; background: var(--tx-linea); position: relative;
  }
  .casos-guia__barra::after{
    content: ''; position: absolute; inset: 0 auto 0 0;
    width: calc(100% / var(--total, 4));
    background: var(--bronce-claro, #C6A961);
    transform: translateX(calc(var(--i, 0) * 100%));
    transition: transform .3s var(--curva);
  }
}
@media (min-width: 900px){ .casos-guia{ display: none } }

/* ============================================================
   v3.13 — LA PROFUNDIDAD 3D ROMPÍA EL TOQUE
   ------------------------------------------------------------
   Bug reportado dos veces por paco: "Sistemas sigue sin poder
   desplegarse". Diagnóstico MEDIDO, no supuesto:

     getBoundingClientRect del botón  → y ≈ 389–441
     elementFromPoint(194, 414)       → DIV.oficios  ← ¡no el botón!
     eventos que llegan al botón      → CERO

   O sea: el botón se PINTA en un sitio y el toque aterriza en el
   contenedor. Es la firma de `translateZ` bajo una perspectiva:
   `.oficios` declara `perspective:1400px` con origen en 30% 50%,
   y cada oficio lleva `data-z` (-90 y -45), así que la proyección
   no coincide con la caja de layout y el hit-testing falla.
   Por eso "algunos sí y otros no": depende de la Z de cada uno.

   En teléfono la perspectiva se va. Ya está escrito antes en esta
   misma hoja para `.panel3d`: "el 3D a media pantalla es ruido, no
   profundidad". Aquí además rompía la interacción.
   ============================================================ */
@media (max-width: 899px){
  .oficios{ perspective: none; transform-style: flat }
}

/* ============================================================
   v3.14 — EL RECORRIDO CON EL DEDO
   ------------------------------------------------------------
   Pedido de paco: "cuando pongas el dedo encima que se bajen en
   los casos, igual que en escritorio cuando pones el cursor".

   Hasta ahora en teléfono el recorrido lo movía el scroll VERTICAL
   —así se resolvió cuando los casos iban apilados—. Con el carril
   horizontal (§v3.12) ese motor dejó de tener sentido: la tarjeta
   ya no cruza la pantalla de arriba abajo. El gesto natural pasa a
   ser el mismo que en escritorio, con el dedo en vez del cursor.

   `--rec` se registra con `@property` para que pueda TRANSICIONAR:
   una variable sin registrar salta de 0 a 1 sin pasar por en medio,
   y el recorrido no se vería. */
@property --rec{ syntax: '<number>'; inherits: true; initial-value: 0 }

@media (hover: none){
  /* 3.4 s de ida: es el ritmo del hover de escritorio (~100 px/s),
     que es donde se alcanza a LEER lo que va pasando. */
  .recorre{ transition: --rec 3.4s linear }
  .recorre.recorriendo{ --rec: 1 }
  /* la vuelta es rápida: soltar es una cancelación, no un recorrido */
  .recorre:not(.recorriendo){ transition: --rec .45s var(--curva) }
}
@media (prefers-reduced-motion: reduce){
  .recorre, .recorre.recorriendo{ transition: none }
}

/* ============================================================
   v3.15 — EL CARRIL, MÁS LIMPIO
   ------------------------------------------------------------
   Menos cromo alrededor de cada web y más aire entre tarjetas:
   lo que se mira es la captura, no la caja que la contiene.
   ============================================================ */
@media (max-width: 899px){
  .casos{ gap: .75rem }
  .caso{ gap: 1rem }
  /* la ficha técnica pesaba tanto como el caso: baja a dato de pie */
  .caso .meta-lista{ margin-top: .9rem; padding-top: .75rem; gap: .5rem }
  .caso__texto{ max-width: none }
  /* el carril no necesita el borde de la sección: respira solo */
  .casos-guia{ margin-bottom: .85rem }
}

/* ============================================================
   v3.16 — #entrada vuelve a anclarse, con el reparto de teléfono
   ------------------------------------------------------------
   paco pidió "el mismo movimiento que en escritorio". Vuelve el
   anclaje y el relevo de las tres puertas, pero con el recorrido
   acortado (1.1 pantallas en vez de 1.7) y menos separación entre
   puertas: repartir 1.7 pantallas entre tres bloques apilados a
   390px es lo que dejaba los huecos verticales con dos precios
   fantasma que él mismo fotografió. Mismo gesto, re-repartido.
   ============================================================ */
@media (max-width: 899px){
  #entrada .puertas{ gap: clamp(1rem, 3vw, 1.75rem) }
  /* la puerta dormida se atenúa MENOS que en escritorio: a media pantalla el
     26% de opacidad se lee como "está roto", no como "no es tu turno" */
  #entrada .pu-mov:not(.vivo){ opacity: .42 }

  /* ── Y AQUÍ ESTABA EL HUECO DE VERDAD ──────────────────────────────────
     No era el `gap`: la puerta dormida escondía su párrafo y su botón con
     OPACIDAD, y una cosa invisible sigue ocupando su altura. Medido en la
     captura: ~250px de vacío entre una puerta y la siguiente. En escritorio no
     molesta porque hay ancho de sobra; apilado a 390px es media pantalla en
     blanco entre precio y precio.
     Se colapsan de verdad, con `max-height` para que la transición siga
     existiendo — `display:none` haría saltar el layout en cada relevo. */
  #entrada .pu-mov:not(.vivo) .pu__una,
  #entrada .pu-mov:not(.vivo) .pu__cta,
  #entrada .pu-mov:not(.vivo) .pu__marca{
    max-height: 0; margin-block: 0; opacity: 0; overflow: hidden;
    transition: max-height .5s var(--curva), opacity .35s var(--curva),
                margin-block .5s var(--curva);
  }
  #entrada .pu-mov .pu__una,
  #entrada .pu-mov .pu__cta,
  #entrada .pu-mov .pu__marca{
    max-height: 14rem;
    transition: max-height .55s var(--curva), opacity .45s var(--curva),
                margin-block .55s var(--curva);
  }
}

/* ============================================================
   v3.17 — EL TELÉFONO DEL PORTAL, COMPLETO
   ------------------------------------------------------------
   "Que no se vea la imagen completa se me hace raro." Y medido,
   se salía de la pantalla:

     ventana                    844
     relleno superior            48
     los cuatro pasos           205   → el marco arranca en 269
     alto disponible            575
     alto real del marco        758   → SE SALÍA 182px

   La causa es un número mágico: el ancho se calculaba con
   `calc((100svh - 4.5rem) * 0.462)`, o sea descontando 72px de
   cabecera… cuando arriba hay 269. Cualquier cambio en los pasos
   volvía a romperlo.

   El arreglo no es afinar la resta: es dejar de restar. El pegado
   pasa a rejilla de dos filas —pasos `auto`, marco `1fr`— y el
   marco se dimensiona por ALTO con `aspect-ratio`, así que el
   ancho lo deduce el navegador de lo que de verdad sobra. Si
   mañana los pasos crecen o encogen, el teléfono se ajusta solo.
   ============================================================ */
@media (max-width: 760px){
  .portal__pegado{
    grid-template-columns: 1fr;
    grid-template-rows: auto minmax(0, 1fr);
    align-items: stretch;
    padding-top: 3rem;
    padding-bottom: 1.25rem;
    gap: .9rem;
  }
  .portal__marco{
    /* el ALTO manda y el ancho sale del ratio: exactamente al revés que antes */
    width: auto;
    height: 100%;
    max-width: 94vw;
    aspect-ratio: 780 / 1688;
    margin-inline: auto;
    justify-self: center;
    align-self: center;
  }
  /* la vista ya no impone su propio ratio: lo hereda del marco */
  .portal__vistas{ height: 100%; aspect-ratio: auto }
  .portal__vistas > .vista{ height: 100% }

  /* Y ahora el reparto: cada píxel que sueltan los pasos se lo queda el
     teléfono, porque el ancho sale del alto. El detalle del paso activo cuesta
     ~50px y lo que dice ya lo dice el propio portal en la captura de al lado;
     el título del paso se queda, que es el mapa. Medido: con el detalle el
     marco daba 257px de ancho; sin él, 300. */
  .portal__pest .pp__d{ display: none }
  .portal__pegado{ padding-top: 1.5rem }
}

/* ═══════════════════════════════════════════════════════════════════════
   §v3.18 · CALIDAD DE VIDA — los detalles que separan "bien hecho" de premium

   Medido contra tres referencias vivas (31 jul 2026):
     · Linear   → foco `1px solid #5E69D1` con `outline-offset:2px`, del color
                  de MARCA, no el azul del navegador. Enlace de salto como
                  primer tabulable.
     · Resend   → `-webkit-tap-highlight-color:transparent` global,
                  `text-wrap:balance/pretty`, `scroll-margin-top` en anclas.
     · Apple    → el anillo de foco es un TOKEN
                  (`--sk-focus-color` / `--sk-focus-offset`), no un valor suelto.

   Estado de Caliber antes de este bloque: 0 reglas de `::selection`,
   4 de `:focus-visible` para 31 elementos interactivos, 1 de `:active`,
   0 de `scroll-margin` con 18 anclas y una cabecera fija de 62px.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── 1 · EL LIENZO DEL DOCUMENTO ────────────────────────────────────────
   LA FRANJA BLANCA. `body` pinta `--papel` y, como `html` no tenía fondo,
   ese color se PROPAGABA al lienzo del navegador. El hero y el pie son
   `oscuro`, así que el rebote elástico de iOS al llegar arriba o abajo
   destapaba #ECEDEF: una franja casi blanca contra secciones negras.
   No sale en capturas de escritorio porque no hay rebote — solo en el
   teléfono, que es justo donde paco la vio.
   Dándole fondo a `html` se corta la propagación: `body` sigue pintando su
   caja igual (el sitio se ve idéntico) y solo el hueco del rebote se
   oscurece.

   ⚠️ TRAMPA, y me costó una regresión completa: NO basta con darle fondo a
   `html`. En cuanto `html` tiene fondo propio, `body` DEJA DE PROPAGAR y pasa
   a pintar su papel sobre su propia caja — que cubre el documento entero — y
   tapa el lienzo `#mundo` que va debajo. El sitio salió color crema de arriba
   abajo, sin un solo error en consola. Por eso van las DOS reglas: `html`
   pinta el hueco del rebote y `body` se queda transparente para que el
   lienzo siga viéndose. */
html{ background:var(--tinta); color-scheme:dark }
body{ background:transparent }

/* ── 2 · SELECCIÓN DE TEXTO ─────────────────────────────────────────────
   Seleccionar texto mostraba el azul de sistema, que es el único color de
   toda la página que no eligió nadie. */
::selection    { background:var(--oro); color:var(--tinta); text-shadow:none }
::-moz-selection{ background:var(--oro); color:var(--tinta); text-shadow:none }
.papel ::selection, .papel::selection{ background:var(--bronce); color:var(--papel) }

/* ── 3 · ANILLO DE FOCO, TOKENIZADO ─────────────────────────────────────
   Un token y no un valor suelto, como Apple: la sección clara necesita
   bronce (el oro no contrasta contra papel) y hereda sola.
   `:focus:not(:focus-visible)` apaga el anillo con el ratón sin quitárselo
   al teclado — que es la razón de que exista `:focus-visible`. */
:root  { --foco:var(--oro-vivo) }
.papel { --foco:var(--bronce) }
a:focus-visible, button:focus-visible, [tabindex]:focus-visible,
summary:focus-visible, input:focus-visible{
  outline:2px solid var(--foco); outline-offset:3px; border-radius:2px;
}
a:focus:not(:focus-visible), button:focus:not(:focus-visible){ outline:none }

/* ── 4 · SALTO DE ANCLA BAJO LA CABECERA ────────────────────────────────
   18 enlaces internos contra una cabecera fija de 62px y CERO
   `scroll-margin`: cada salto metía el arranque de la sección debajo de la
   cabecera. Es el rótulo "OPERANDO" pisado que quedó sin diagnosticar.
   No se añade `scroll-behavior:smooth`: pelea con las secciones ancladas
   de ScrollTrigger. */
/* El mínimo subió de 4.75rem a 5.5rem cuando el CTA se volvió píldora y la
   cabecera pasó de 62 a 72px: en teléfono el mínimo mandaba y dejaba 4px de
   holgura contra la barra. Ahora deja 16. */
[id]{ scroll-margin-top:clamp(5.5rem, 9vh, 7rem) }

/* ── 5 · ENLACE DE SALTO ────────────────────────────────────────────────
   Primer tabulable del documento, como en Linear. Invisible hasta que el
   teclado lo toca. z-index por encima de la puerta, que si no nace debajo. */
.saltar{
  position:fixed; top:.75rem; left:.75rem; z-index:200;
  padding:.7rem 1.1rem; border-radius:999px;
  background:var(--oro); color:var(--tinta);
  font-size:.8125rem; font-weight:600; text-decoration:none;
  transform:translateY(-160%); transition:transform .22s var(--curva);
}
.saltar:focus{ transform:translateY(0) }

/* ── 6 · TACTO ──────────────────────────────────────────────────────────
   Sin esto, cada toque en el teléfono pinta el rectángulo gris del sistema
   por encima del diseño. Estaba puesto en UN elemento de treinta y uno. */
html{ -webkit-tap-highlight-color:transparent }

/* Respuesta al pulsar. El navegador no da ninguna en móvil —sin `:hover`,
   un botón que no se hunde se siente muerto. Solo en controles reales: una
   tarjeta de caso entera encogiéndose se vería como un fallo. */
.boton:active, .cab__cta:active, .cab__marca:active,
.pie__col a:active, .pie__correo:active, .portal__pest button:active{
  transform:scale(.97);
}
.oficio__disparo:active{ opacity:.72 }
@media (prefers-reduced-motion:reduce){
  .boton:active, .cab__cta:active, .cab__marca:active,
  .pie__col a:active, .pie__correo:active, .portal__pest button:active{ transform:none }
}

/* ── 7 · BLANCOS TÁCTILES ───────────────────────────────────────────────
   Medido en vivo: los enlaces del pie daban 163×21 px y el CTA de cabecera
   69×21. El mínimo cómodo es 44.
   En la cabecera se agranda con un pseudo-elemento y NO con relleno: el
   relleno crecería la barra de 62 a 79px. Así el blanco llega a 44 y la
   cabecera no se mueve un píxel. */
.cab__cta, .cab__marca{ position:relative }
.cab__cta::after, .cab__marca::after{
  content:''; position:absolute; inset:-12px -10px;
}
@media (max-width:899px){
  .pie__col{ gap:0 }
  .pie__col a{ min-height:44px; display:flex; align-items:center }
  .pie__bajo a{ min-height:44px; display:inline-flex; align-items:center }
}

/* ── 8 · REMATE TIPOGRÁFICO ─────────────────────────────────────────────
   `balance` reparte el título entre sus renglones en vez de dejar una
   palabra huérfana abajo; `pretty` hace lo mismo con el último renglón de
   un párrafo. Dos líneas que quitan el defecto más visible de un titular
   grande en móvil. */
h1, h2, h3, .d1, .d2, .d3, .caso__titulo, .oficio__nombre{ text-wrap:balance }
p, .pieza__nota, .entrada__nota, .pie-foto{ text-wrap:pretty }

/* ── 9 · BARRA DE DESPLAZAMIENTO ────────────────────────────────────────
   PROBADO Y DESCARTADO. Un `::-webkit-scrollbar` propio obliga al navegador
   a pasar de barra superpuesta a barra CLÁSICA, que ocupa layout: medido,
   el viewport pasó de 390 a 379px y todo el ancho de la página se recorrió.
   Ninguna de las tres referencias vivas (Linear, Resend, Apple) la pinta —
   Apple solo la ESCONDE dentro de sus galerías horizontales.
   `color-scheme:dark` (arriba, §1) ya deja la barra nativa oscura y a juego
   sin tocar una sola medida. No volver a intentarlo. */

/* ── 10 · LA CAPTURA CLARA CONTRA EL FONDO OSCURO ───────────────────────
   Medido en la captura completa de móvil: la única zona clara real dentro
   del tramo oscuro es la ventana de Black Secret — 200px que pasan de 10 a
   255 de luminancia en un píxel. El sitio del cliente es blanco y no se
   toca; lo que se arregla es el CANTO, que hoy es un corte a navaja.
   Una viñeta interior hace que la captura ENTRE en la página en vez de
   estrellarse contra ella, y el filo de oro al 14% la enmarca como pieza. */
.recorre__ventana::after{
  content:''; position:absolute; inset:0; pointer-events:none; z-index:2;
  box-shadow:
    inset 0  0 0 1px rgba(216,197,140,.14),
    inset 0  54px 60px -56px rgba(8,9,12,.50),
    inset 0 -54px 60px -56px rgba(8,9,12,.58);
}

/* ═══════════════════════════════════════════════════════════════════════════
   v3.19 · QoL — SEGUNDA TANDA
   Tres cosas que la primera tanda no cubrió, más una corrección de mi propio
   diagnóstico anterior.

   ⚠️ CORRECCIÓN — la alarma del `vh` era FALSA. Conté 13 usos de `vh` y avisé
   de salto de viewport en Safari de iPhone. Al mirarlos uno por uno: TODOS
   están dentro de `clamp()` para ESPACIADO, donde el salto no aplica. Todo el
   dimensionado real ya usa `svh` —`.hero`, `.pin__lienzo`, `.portal__escena`,
   `.cierre`—, que es justo la unidad estable que no se mueve cuando la barra
   de Safari se encoge. Ya estaba bien. No cambiar a `dvh`: `dvh` SÍ se mueve
   con la barra, sería un retroceso.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1 · ZONA SEGURA · IPHONE EN HORIZONTAL ──────────────────────────────
   La cabecera es `position:fixed; inset:0 0 auto`, o sea de borde a borde.
   En vertical no hay problema: Safari ya reserva la muesca. En HORIZONTAL la
   muesca pasa a un costado y se come la esquina donde viven la marca y el
   CTA — precisamente los dos controles que nunca deben quedar tapados.

   `max()` y no suma: en un teléfono sin muesca `env()` vale 0 y el borde
   normal manda; donde sí hay muesca, gana la que sea mayor. Sumar los dos
   habría empujado la cabecera hacia adentro en TODOS los aparatos. */
.cab{
  padding-left:  max(var(--borde), env(safe-area-inset-left));
  padding-right: max(var(--borde), env(safe-area-inset-right));
}
/* El enlace de salto está anclado a la esquina superior izquierda con `fixed`:
   mismo riesgo, misma cura. */
.saltar{ left:max(.75rem, env(safe-area-inset-left)) }

/* ── 2 · ALTO CONTRASTE Y COLORES FORZADOS ───────────────────────────────
   Windows en modo alto contraste sustituye TODOS los colores del autor por la
   paleta del sistema. Un diseño que confía en color de fondo para separar
   piezas se queda sin separación: aquí las ventanas de las capturas y las
   tarjetas del portal se volvían rectángulos invisibles. `forced-colors` es
   la única forma de recuperar el borde, y va con colores de SISTEMA
   (`CanvasText`), nunca con los nuestros — el modo los ignoraría. */
@media (forced-colors: active){
  .recorre__ventana, .portal__pest button, .oficio__disparo, .boton{
    border:1px solid CanvasText;
  }
  .recorre__ventana::after{ display:none }   /* la viñetita no sobrevive, estorba */
  a:focus-visible, button:focus-visible{ outline:2px solid Highlight }
}

/* ── 3 · IMPRESIÓN ───────────────────────────────────────────────────────
   Un estudio manda cotizaciones y hay clientes que las imprimen o las guardan
   en PDF desde el navegador. Sin esto salía el fondo `--tinta` a toda página:
   litros de tinta y texto claro sobre negro que muchas impresoras degradan a
   gris ilegible.

   Se invierte a papel blanco con texto negro, se retira todo lo que no
   significa nada en papel (el lienzo WebGL, la niebla, la cabecera fija, el
   enlace de salto) y se le pide al navegador que NO recorte los bloques a la
   mitad entre páginas. Los enlaces externos revelan su destino: en papel un
   subrayado sin URL es un callejón sin salida. */
@media print{
  html, body{ background:#fff !important; color:#000 !important }
  #mundo, .cab, .saltar, video, .niebla, [data-niebla]::before{ display:none !important }

  /* Las revelaciones por scroll dejan piezas en `opacity:0` esperando a GSAP,
     que en la vista de impresión puede no haber corrido. Sin esto, media página
     sale en blanco. */
  *{ opacity:1 !important; transform:none !important;
     box-shadow:none !important; text-shadow:none !important;
     color:#000 !important; background:transparent !important }

  .seccion{ page-break-inside:avoid; break-inside:avoid; padding:1.5rem 0 }
  h1, h2, h3{ page-break-after:avoid; break-after:avoid }
  img{ max-width:100% !important }

  a[href^="http"]::after{ content:" (" attr(href) ")"; font-size:.75em; color:#555 !important }
  a[href^="#"]::after, a[href^="mailto"]::after{ content:"" }   /* internos: la URL no aporta */
}

/* ═══════════════════════════════════════════════════════════════════════════
   v3.20 · LA CAPA DE TEMPERATURA · Y EL CTA SIEMPRE VISIBLE
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1 · LA CAPA DE TEMPERATURA ──────────────────────────────────────────
   El color del momento firma vivía en `body`. Funcionaba solo mientras `html`
   no tenía fondo: entonces `body` PROPAGABA su color al lienzo del navegador
   y no pintaba su propia caja. Al darle fondo a `html` en la v34 —para tapar
   la franja blanca del rebote de iOS— la propagación se cortó y `body` empezó
   a pintar 19,306px de casi-blanco ENCIMA del shader. Ese era el fallo que
   paco veía al volver a subir.

   Aquí el color tiene caja propia, fija, DEBAJO del lienzo. Arranca en
   `--tinta` para que antes de llegar al portal el sitio ya esté oscuro, que es
   como se comportaba con `immediateRender:false`. */
.temperatura{
  position:fixed; inset:0; z-index:-2; pointer-events:none;
  background:var(--tinta);
}

/* ── 2 · EL CTA DE LA CABECERA SE LEE SIEMPRE ────────────────────────────
   paco: "siempre dejar visible el empezar, porque se ve transparente y no se
   aprecia mucho".

   La causa es `mix-blend-mode:difference` en `.cab`. Esa mezcla es un acierto
   para la marca y el menú —garantiza que se lean sobre cualquier fondo sin
   pintarles una placa detrás— pero al CTA le quitaba el color de marca y sobre
   medios tonos lo dejaba lavado. Un botón de conversión no puede depender del
   fondo que le toque.

   La mezcla NO se quita del sitio: se BAJA del contenedor a los dos hijos que
   sí la quieren. Así el CTA queda fuera del grupo mezclado y puede ser una
   píldora de oro sólida, con su color real, siempre.

   Medido: la cabecera pasa de 62 a 67px de alto. Las anclas aterrizan en 76px
   (`scroll-margin-top`, §v3.18-4), así que siguen despejadas. */
.cab{ mix-blend-mode:normal }
.cab__marca, .cab__nav{ mix-blend-mode:difference; color:#fff }

.cab__cta{
  padding:.5rem .9rem; border-radius:999px;
  background:var(--oro); color:var(--tinta);
  font-weight:600; text-decoration:none;
  transition:background var(--base) var(--curva), transform .22s var(--curva);
}
.cab__cta:hover{ background:var(--oro-vivo, #C6A961) }
/* El área táctil ya la da el pseudo-elemento de §v3.18-7; la píldora es lo
   que se VE, y con 32px de alto real más ese margen invisible cumple los 44. */

/* ═══════════════════════════════════════════════════════════════════════════
   v3.21 · LOS CASOS SE PUEDEN ABRIR
   Hasta la v39, #trabajo enseñaba cuatro casos y NINGUNO era clicable: 28
   bloques de caso y cero enlaces. Las dos páginas que existían (levia, chilita)
   sólo se alcanzaban por el sitemap. Ahora el título es la puerta.
   Se enlaza el TÍTULO y no el <article> entero a propósito: el bloque de media
   lleva el recorrido por hover/tacto y envolverlo en un <a> le robaría el gesto.
   ═══════════════════════════════════════════════════════════════════════════ */

.caso__enlace{
  color:inherit; text-decoration:none;
  display:inline-flex; align-items:baseline; gap:.4em;
  position:relative;
}
/* Área táctil por pseudo-elemento, no por relleno: el título no cambia de caja
   y la maqueta del caso no se mueve ni un píxel (§v3.18-7). */
.caso__enlace::before{ content:''; position:absolute; inset:-14px -10px }

/* El subrayado crece desde la izquierda al pasar el cursor. Es el gesto más
   discreto que dice "esto lleva a algún lado" sin pintar un botón dentro de una
   ficha que ya tiene tres niveles de texto. */
.caso__enlace::after{
  content:''; position:absolute; left:0; right:0; bottom:-.14em; height:1px;
  background:currentColor; transform:scaleX(0); transform-origin:left;
  transition:transform .42s var(--curva);
}
.caso__enlace:hover::after, .caso__enlace:focus-visible::after{ transform:scaleX(1) }

.caso__flecha{
  font-size:.5em; color:var(--oro); opacity:.7;
  transition:transform .42s var(--curva), opacity .3s;
}
.caso__enlace:hover .caso__flecha{ transform:translate(2px,-2px); opacity:1 }

/* Una salida explícita además del título: un titular que TAMBIÉN es enlace no
   siempre se lee como enlace, y este es el único camino a la página del caso. */
.caso__vermas{ margin-top:1.1rem }
.caso__vermas a{
  display:inline-flex; align-items:center; gap:.5em; min-height:44px;
  font-size:.875rem; font-weight:500; color:var(--oro);
  text-decoration:none; transition:gap .35s var(--curva);
}
.caso__vermas a:hover{ gap:.9em }

@media (prefers-reduced-motion:reduce){
  .caso__enlace::after{ transition:none }
  .caso__flecha, .caso__vermas a{ transition:none }
}

/* ═══════════════════════════════════════════════════════════════════════════
   v3.22 · CERRAR LO QUE QUEDABA
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1 · EL RÓTULO QUE MENTÍA ──
   Decía "pasa el cursor para recorrerlo" en los cuatro casos. Desde la v29, en
   teléfono el recorrido ARRANCA SOLO al tocar: el rótulo pedía un gesto que no
   existe en ese aparato.
   No se resuelve detectando el dispositivo desde JS —Playwright siempre reporta
   `hover:hover` y nunca podría verificarlo—. Se resuelve dejando que lo decida
   quien sí sabe: la media query corre EN el teléfono. */
.solo-tacto{ display:none }
@media (hover:none), (pointer:coarse){
  .solo-cursor{ display:none }
  .solo-tacto { display:inline }
}

/* ── 2 · LOS ÚLTIMOS OBJETIVOS TÁCTILES ──
   Medido en producción a 390px: enlace de salto 43px (a UNO de distancia), los
   tres pasos del portal 31-32px, el correo del pie 27px. Todos crecen por
   pseudo-elemento para no mover una sola caja de la maqueta. */
.saltar{ min-height:44px; display:inline-flex; align-items:center }

.portal__pest button{ position:relative }
.portal__pest button::after{ content:''; position:absolute; inset:-7px 0 }

.pie__correo{ position:relative; display:inline-flex; align-items:center }
.pie__correo::after{ content:''; position:absolute; inset:-9px 0 }

/* Los tres botones de #entrada quedaban en 44 justos: el redondeo los tiraba
   por debajo en algunos anchos. Un pelo más y dejan de depender del decimal. */
@media (max-width:899px){
  .boton{ min-height:46px }
}


/* ═══════════════════════════════════════════════════════════════════════════
   v3.22 · LO QUE EL AUDIT DE AGOSTO ENCONTRÓ ROTO
   Tres hallazgos medidos en producción (1440x900 y 390x844), no supuestos.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1 · EN TELÉFONO NO HABÍA NAVEGACIÓN ─────────────────────────────────────
   `.cab__nav{display:none}` por debajo de 900px, y NADA que lo sustituyera: no
   había botón de menú en ninguna parte del documento. Medido en 390px, la
   cabecera entera se reducía a la marca y el CTA. Para llegar a Precios había
   que recorrer 13,974px a mano — 16.6 pantallas.

   El panel no vive dentro de `.cab` a propósito: la cabecera le pone
   `mix-blend-mode:difference` a sus hijos y un panel mezclado sobre el shader
   del mundo se vuelve ilegible. Fuera de ella puede llevar tinta sólida. */
.visualmente-oculto{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0;
}

.cab__abre{
  display:none; background:none; border:0; padding:.6rem; margin:-.6rem;
  color:inherit; cursor:pointer; line-height:0;
}
.cab__rayas{ display:block; width:22px }
.cab__rayas i{
  display:block; height:1.5px; background:currentColor; border-radius:1px;
  transition:transform .3s var(--curva), opacity .2s linear;
}
.cab__rayas i + i{ margin-top:6px }
/* La cruz se arma girando las dos rayas sobre el mismo centro. Se traslada
   ANTES de girar (3.75px = medio hueco + medio grosor) o el aspa queda abierta. */
[aria-expanded="true"] .cab__rayas i:first-child{ transform:translateY(3.75px) rotate(45deg) }
[aria-expanded="true"] .cab__rayas i:last-child { transform:translateY(-3.75px) rotate(-45deg) }

@media (max-width:899px){
  .cab__abre{ display:block }
  /* El botón mezcla como la marca y el menú: sobre el hero oscuro y sobre el
     papel de #entrada tiene que leerse igual, y es la misma garantía que ya
     usan los otros dos. */
  .cab__abre{ mix-blend-mode:difference; color:#fff }
}

.menu{
  position:fixed; inset:0; z-index:70;
  /* El panel es hijo de <body>, y `body` declara la terna de texto para FONDO
     CLARO (--tx-1 = tinta al 92%). Sobre la tinta del panel eso es tinta sobre
     tinta: la primera prueba salió con el fondo negro y CERO texto, sólo el
     filo de oro del botón. La terna se vuelve a declarar aquí, igual que hace
     `.oscuro`, en vez de heredar la equivocada. */
  --tx-1: rgba(237,239,243,1); --tx-2: rgba(237,239,243,.62); --tx-3: rgba(237,239,243,.50);
  --tx-linea: rgba(255,255,255,.12);
  background:var(--tinta); color:var(--tx-1);
  display:flex; flex-direction:column; justify-content:center;
  gap:clamp(2rem,7vh,3.5rem); padding:5rem var(--borde) calc(2rem + env(safe-area-inset-bottom));
  overscroll-behavior:contain;
}
/* Sin esta línea el atributo `hidden` NO cierra el panel: un `display:flex` de
   autor le gana siempre al `display:none` del user-agent. Ya nos costó una
   vuelta en otro proyecto — queda escrito. */
.menu[hidden]{ display:none }

.menu__lista{ display:grid; gap:.15rem }
.menu__lista a{
  display:flex; align-items:baseline; gap:.9rem;
  font-family:var(--display); font-weight:var(--p-aire);
  font-size:clamp(1.75rem,9vw,2.5rem); line-height:1.18;
  letter-spacing:-.025em; color:var(--tx-1); text-decoration:none;
  padding-block:.35rem;                       /* 44px de alto real sin tocar la maqueta */
}
.menu__n{ font-size:.6875rem; letter-spacing:.14em; color:var(--tx-3); flex:none }
.menu__pie{ display:flex; align-items:center; gap:1.25rem; flex-wrap:wrap }
.menu__wa{
  font-size:.875rem; color:var(--tx-2); text-decoration:none;
  display:inline-flex; align-items:center; gap:.35rem; min-height:44px;
}
@media (prefers-reduced-motion:no-preference){
  .menu__lista a{ transition:opacity .2s linear }
  .menu__lista a:active{ opacity:.55 }
}
/* Con el menú abierto la cabecera no puede retirarse: se llevaría el botón de
   cerrar fuera de pantalla y dejaría al panel sin salida visible. */
html.menu-abierto .cab--oculta{ transform:none }
html.menu-abierto{ overflow:hidden }
/* Y tiene que quedar POR ENCIMA del panel. La primera prueba salió con el panel
   a z-70 tapando la cabecera a z-60: el menú abría bien y no había forma de
   cerrarlo más que con Escape, que en un teléfono no existe. */
html.menu-abierto .cab{ z-index:80 }
/* Y el CTA de la cabecera se retira mientras el panel está abierto: el panel ya
   lleva el suyo, más grande y al alcance del pulgar. Dos "Empezar" a 40px de
   distancia no son dos oportunidades, son una duda. */
html.menu-abierto .cab__cta{ opacity:0; pointer-events:none }

/* ── 2 · LA CABECERA DESAPARECÍA SOBRE EL PAPEL ──────────────────────────────
   Medido sobre los PÍXELES de la captura, no sobre el DOM: en #entrada los
   enlaces daban 1.13:1 contra el fondo. AA pide 4.5:1. Al ojo, invisibles.

   La causa es que `mix-blend-mode:difference` mezcla contra el trasfondo de SU
   grupo de apilado, y #entrada está pinneada por GSAP —o sea que su transform
   le abre un grupo propio y la mezcla nunca la ve—. Lo que la cabecera acaba
   restando es el fondo oscuro de `html` (#08090C), y 255-8 = 247: blanco sobre
   crema. La mezcla no está mal escrita; está midiendo la capa equivocada.

   El DOM decía 19.9:1 y mintió. Por eso el arreglo no confía en la mezcla para
   la corrección: la APAGA sobre papel y pinta tinta explícita. La mezcla se
   queda donde sí funciona, que es todo lo oscuro. */
.cab--claro .cab__marca,
.cab--claro .cab__nav,
.cab--claro .cab__abre{
  mix-blend-mode:normal;
  color:var(--tinta);
}
.cab--claro .cab__nav a{ opacity:.72 }
.cab--claro .cab__nav a:hover{ opacity:1 }
/* El CTA ya era una píldora sólida fuera del grupo mezclado: sobre papel el oro
   pierde separación contra el crema, así que ahí se apoya en un filo de tinta. */
.cab--claro .cab__cta{ box-shadow:inset 0 0 0 1px rgba(9,10,13,.22) }

/* ── 3 · EL TAMAÑO DE TITULAR EN TELÉFONO ────────────────────────────────────
   `--t1` es `clamp(2.5rem, 6vw, 5.4rem)`: a 390px el término vw da 23px, así
   que gana el SUELO de 40px. Ese suelo se escribió mirando un escritorio. 40px
   son el 10.3% del ancho de pantalla, y el titular se lee como un muro.
   Sólo se toca por debajo de 560px para no mover ni un pixel de escritorio. */
@media (max-width:560px){
  :root{ --t1: clamp(1.9rem, 8.2vw, 2.6rem) }   /* 390px → 32px = 8.2% */
}

/* ── 4 · LA ANIMACIÓN SE COMÍA EL CONTENIDO ──────────────────────────────────
   Los relevos apagaban lo inactivo a .28 y .26. La animación se ve bien en
   movimiento, pero la gente PARA de scrollear: en reposo dos de cada tres
   renglones de #sistemas y dos de tres precios de #entrada quedaban ilegibles.
   Se sube el piso lo justo para que se lean sin que el relevo pierda su
   jerarquía — lo vivo sigue siendo lo único a opacidad plena. */
.paso{ opacity:.46 }
.pu-mov:not(.vivo){ opacity:.5 }
/* El CTA de la tarjeta apagada sigue oculto: leer la fila no es poder pulsarla. */
