/* ============================================================================
   07-servicios.css  ·  Las 7 SECCIONES DE SERVICIO
   Contrato: docs/02-DECLARACIONES.md §21
   ----------------------------------------------------------------------------
   DOS CAPAS, Y NO SE MEZCLAN:

     .serv / .serv__*   ESTRUCTURA. Vale para las 7 secciones. Sin prefijo de
                        servicio: si cambia, cambia en las 7 a la vez.
     .dn-*              CONTENIDO de «Diseño web». Es de su escena y no se
                        reutiliza en otro servicio.

   Cero variables propias: todo sale de 01-declaraciones.css.
   Cero !important (§11). Cero colores literales: siempre token (§3.1).

   ORDEN DE ESTE ARCHIVO:
     1) Marco .serv y su scroll
     2) Barra (Volver) y raíl de escenas
     3) Escena, ceja, título, entradilla
     4) Cierre «Hablemos»
     5) Contenido: Diseño web (.dn-)
     6) Movimiento reducido
   ========================================================================== */


/* ===========================================================================
   DERIVADOS DE SECCIÓN (declarados en .serv, no en :root)
   ---------------------------------------------------------------------------
   Estas variables son transparencias del acento y negros de apoyo. Se declaran
   AQUÍ y no en 01-declaraciones.css porque solo las usan las secciones de
   servicio: subirlas a :root obligaría a mantenerlas en las 7 aunque seis no
   las usen. Todas derivan de --acento-rgb, así que permutan con el tema solas.

   --acento-b es el SEGUNDO color de marca (el que no es --acento en este tema).
   Se usa para los titulares de dos colores del original (§9bis).
   =========================================================================== */
.serv {
  --acento-suave:        rgba(var(--acento-rgb), 0.12);
  --acento-tenue:        rgba(var(--acento-rgb), 0.06);
  --acento-borde:        rgba(var(--acento-rgb), 0.42);
  --acento-borde-fuerte: rgba(var(--acento-rgb), 0.55);
  --acento-glow:         rgba(var(--acento-rgb), 0.32);
  --acento-brillo:       rgba(var(--acento-rgb), 0.16);
  --acento-int:          rgba(var(--acento-rgb), 0.07);
  --acento-anillo:       rgba(var(--acento-rgb), 0.10);
  --acento-texto:        rgba(var(--acento-rgb), 0.50);
  --acento-texto-suave:  rgba(var(--acento-rgb), 0.22);

  /* EL OTRO COLOR DE MARCA — el que NO es --acento en este tema.
     Oscuro: --acento es naranja-500 → aquí el AZUL DEL WORDMARK (--azul-marca
     #3D8AF5), no azul-500. El logo usa azul-400 por contraste sobre tinta-950
     (5.67:1); con azul-500 eran 4.17:1 y fallaba AA. Es el mismo azul que el
     visitante ve en el logo de arriba, y ese es justamente el objetivo de un
     titular a dos colores: que se lea como marca. */
  --acento-b:       var(--azul-marca);
  --acento-b-suave: rgba(var(--azul-rgb), 0.12);

  /* Negros de apoyo de esta sección */
  --dn-cristal:       rgba(10, 14, 20, 0.62);
  --dn-velo-fuerte:   rgba(10, 14, 20, 0.92);
  --dn-sombra-texto:  rgba(10, 14, 20, 0.75);
  --dn-caja-fondo:    rgba(16, 20, 28, 0.86);

  /* ESCALERA LOCAL DE LA SECCIÓN (contrato §8).
     NO son peldaños de la escalera de PÁGINA (--z-fondo/--z-contenido/…) y no
     deben confundirse con ellos: estos solo ordenan elementos DENTRO de una
     misma escena, que ya vive en --z-overlay. Por eso son locales y se nombran
     `--serv-*`: el check 8 exige que ningún z-index sea un número suelto, y
     tiene razón — un 9 suelto es el que dejó "netia" naranja. */
  --serv-bajo:    0;   /* por detrás del contenido de su escena */
  /* Las cinco capas del hero necesitan un escalón propio: 0..4. `--serv-bajo`
     es 0 y `--serv-medio` es 1, así que sin esto las capas chocarían con el
     contenido. Se declaran como peldaños con nombre —nunca un número suelto,
     que es lo que exige el check 8— y se usan una vez cada una. */
  --serv-capa-1:  4;   /* la de MÁS adelante: c3 */
  --serv-capa-2:  3;
  --serv-capa-3:  2;
  --serv-capa-4:  1;
  --serv-capa-5:  0;   /* el fondo: c4 */
  --serv-medio:   1;   /* contenido normal */
  --serv-alto:    2;   /* barra, controles */
  --serv-cima:    3;   /* lo que tiene que quedar encima: rótulo, hojas */
  --serv-tapa:    9;   /* velo/tapa sobre la escena */

  /* Canales del blanco, para las mini-demos de «El criterio». NO se usa
     --blanco porque aquí hace falta la tripleta de canales para escribir
     transparencias: rgba(var(--blanco-rgb), .32). */
  --blanco-rgb: 255, 255, 255;
}

/* En claro --acento ya es azul-600, así que el segundo color es el NARANJA de
   marca. OJO: naranja-500 (#FF9900) sobre #F5F7FA da 1.99:1 — ilegible como
   texto. Se usa naranja-700 (#A65C00, 4.71:1 AA), que es la variante de marca
   ya sancionada para fondo claro en 01-declaraciones.css:38.
   ANTES ESTABA MAL: apuntaba a --secundario-texto, que en claro ES azul-600
   (el mismo que --acento). El titular quedaba de un solo color y el énfasis
   desaparecía. */
[data-tema="claro"] .serv {
  --acento-b:       var(--naranja-700);
  --acento-b-suave: rgba(var(--naranja-rgb), 0.12);
}


/* ---------------------------------------------------------------------------
   1) MARCO .serv  ·  §21.2
   ---------------------------------------------------------------------------
   SIN FONDO. Es la regla que más se olvida: si esta sección pinta fondo, tapa
   el shader del cuerpo, que es el fondo de TODO el sitio (salvo CONTACTO, que
   tiene base propia declarada). El velo va en .serv__scroll, que es quien se
   desplaza, no el marco.
   bottom:var(--alto-pie) es lo que deja el pie global intocable abajo, igual
   que en .sec-clientes.
   ------------------------------------------------------------------------- */
.serv {
  position: fixed;
  inset: 0 0 var(--alto-pie, 0px) 0;
  /* --z-contenido (10), NO --z-overlay (500). La cabecera vive en
     --z-cabecera (100): con 500 esta sección la tapaba y el logo y el menú
     desaparecían al entrar. Quiénes y Clientes ni declaran z-index, y la
     rueda usa --z-contenido: mismo peldaño que ellas. Los overlays siguen
     por encima (--z-overlay 500) porque se abren fuera de este marco. */
  z-index: var(--z-contenido);
  padding: 0;                 /* el aire lo pone .serv__escena, no el marco */
  overflow: hidden;           /* el que se desplaza es el hijo */
}

/* Solo el scroll lleva el velo: el marco deja pasar el shader en los bordes */
.serv__scroll {
  position: relative;
  height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;   /* el scroll no se contagia a la página */
  /* La cabecera es `position:fixed` y mide --alto-cabecera (57px). Antes esta
     sección tenía z-index 500 y la tapaba, así que nadie notó que su contenido
     empezaba en y=0: la barra «Volver» vivía en 0..66 y su botón en 8..58, es
     decir DENTRO de la cabecera. Al bajar el marco a --z-contenido (10) la
     cabecera pasó a estar encima y el botón asomó por detrás. Este relleno
     desplaza TODO el contenido (barra y escenas) por debajo de la cabecera. */
  padding-top: var(--alto-cabecera);
  /* SIN FONDO, a propósito. Debajo está el shader neuronal del cuerpo, que es
     el fondo de TODO el sitio; un velo aquí lo tapaba y dejaba la sección
     muerta (orden de Oz, 2026-10-21). Si algún día hace falta más contraste
     para un texto concreto, se resuelve en ESE texto, no pintando el marco. */
  scrollbar-width: thin;
  scrollbar-color: var(--borde) transparent;
  -webkit-overflow-scrolling: touch;
}

.serv__scroll::-webkit-scrollbar        { width: 8px; }
.serv__scroll::-webkit-scrollbar-track  { background: transparent; }
.serv__scroll::-webkit-scrollbar-thumb  { background: var(--borde); border-radius: 100px; }


/* ---------------------------------------------------------------------------
   2) RAÍL DE ESCENAS
   ---------------------------------------------------------------------------
   SIN barra «Volver»: se regresa con el ítem «Servicios» del menú (orden de Oz,
   2026-10-22: dos regresos en la misma pantalla sobran). Aquí vivía
   `.serv__barra` + `.serv__volver`, y se fueron con el botón.
   ------------------------------------------------------------------------- */

/* Raíl: fijo a la derecha, centrado en la ventana. aria-current="true" marca
   la escena visible; el color no lo pone el JS, lo pone esta regla. */
.serv__raiz {
  position: fixed;
  inset-inline-end: clamp(14px, 2.4vw, 30px);
  top: 50%;
  transform: translateY(-50%);
  z-index: var(--z-overlay);
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.75rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.serv__raiz a,
.serv__raiz button {
  display: flex;
  align-items: center;
  gap: 0.5625rem;
  padding: 0;
  background: none;
  border: 0;
  color: var(--mut);
  font-family: var(--fuente-texto);
  font-size: 0.7rem;
  cursor: pointer;
  text-decoration: none;
}

/* El texto solo aparece en el activo y al pasar por encima: si sale siempre,
   seis etiquetas compiten con el titular de la escena. */
.serv__raiz .punto {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  flex: 0 0 auto;
  transition: transform 0.3s, background-color 0.3s;
}

.serv__raiz .texto {
  opacity: 0;
  transform: translateX(6px);
  transition: opacity 0.3s, transform 0.3s;
  white-space: nowrap;
}

.serv__raiz a:hover .texto,
.serv__raiz button:hover .texto,
.serv__raiz a:focus-visible .texto,
.serv__raiz button:focus-visible .texto,
.serv__raiz [aria-current="true"] .texto {
  opacity: 1;
  transform: none;
}

.serv__raiz [aria-current="true"] {
  color: var(--acento);
}

.serv__raiz [aria-current="true"] .punto {
  transform: scale(1.5);
}


/* ---------------------------------------------------------------------------
   3) ESCENA, CEJA, TÍTULO, ENTRADILLA
   ---------------------------------------------------------------------------
   min-height:100dvh y NO 100vh: con 100vh, en un portátil con barra de
   direcciones la última línea de la escena se corta.
   ------------------------------------------------------------------------- */
.serv__escena {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--e-4);
  min-height: 100dvh;
  padding: var(--e-6) var(--margen-borde) var(--e-5);
  scroll-margin-top: var(--e-6);   /* las anclas no quedan bajo la barra */
}

.serv__ceja {
  margin: 0;
  color: var(--acento);
  font-family: var(--fuente-texto);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.serv__titulo {
  max-width: 22ch;
  margin: 0;
  color: var(--fg);
  font-family: var(--fuente-texto);
  font-size: clamp(1.75rem, 4.2vw, 3.25rem);
  font-weight: 700;
  line-height: 1.08;
  text-wrap: balance;
}

/* Outfit no tiene itálica (§3.2): el destacado se hace con color y peso, NUNCA
   con font-style:italic, que el navegador sintetiza y se ve sucio. */
.serv__titulo em {
  color: var(--acento);
  font-style: normal;
}

/* <u> NO es un subrayado: es el SEGUNDO color de marca. Se usa <u> porque es
   énfasis sin semántica de enlace, pero el subrayado se quita SIEMPRE.

   POR QUÉ ESTA REGLA ES GENERAL Y NO SOLO DE `.dn-frase`: antes existía solo
   `.dn-frase u`, así que funcionaba en La frase y NO en La prueba — donde el
   <u> salía con subrayado y sin color. Medido en el navegador: La frase
   `none`/azul, La prueba `underline`/color de texto. Una regla por escena
   obliga a acordarse en cada sección nueva; esta cubre las siete.

   COLOR: `--acento`, no `--acento-b`. Orden de Oz (2026-10-23): el <u> va en
   el NARANJA OFICIAL de la marca en tema oscuro y en el AZUL DE LA MARCA en
   tema claro. Eso es exactamente lo que hace `--acento`:
     oscuro → --naranja-500 (#FF9900, el naranja de marca)
     claro  → --azul-600    (el azul de marca, AA sobre fondo claro)
   `--acento-b` hacía lo CONTRARIO (azul en oscuro, naranja oscuro en claro),
   así que el <u> salía del color equivocado en los dos temas. */
.serv__titulo u {
  text-decoration: none;
  color: var(--acento);
}

.serv__dek {
  max-width: 46ch;
  margin: 0;
  color: var(--mut);
  font-family: var(--fuente-texto);
  font-size: clamp(0.95rem, 1.15vw, 1.0625rem);
  line-height: 1.65;
}

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


/* ---------------------------------------------------------------------------
   4) CIERRE «Hablemos»
   ---------------------------------------------------------------------------
   Reutiliza .btn y .btn-hablemos (§20.7): NO se declara un botón nuevo.
   El listener ya es global en rueda-accion.js (conectarHablemos).
   ------------------------------------------------------------------------- */
/* CENTRADO DE VERDAD, en los DOS ejes. `align-items` es el eje transversal: con
   flex-start los textos salían centrados pero el botón pegado a la izquierda,
   que es justo lo que se veía. `justify-content` centra la columna. */
/* Centrado en los DOS ejes. `align-items` es el eje transversal: con
   flex-start los textos salían centrados pero el botón pegado a la izquierda.
   NO lleva velo: se probó un radial de --fondo al 88% y medido en la banda del
   texto no cambiaba ni un píxel (--fondo al 88% sobre --fondo es el mismo
   color). El shader se queda en las esquinas y no toca el centro. */
.serv__cierre {
  align-items: center;
  justify-content: center;
  text-align: center;
  border-top: 1px solid var(--borde);
}

/* El titular y la entradilla del cierre también se centran y se estrechan:
   con max-width:22ch heredado quedaba una columna torcida a la izquierda. */
.serv__cierre .serv__titulo {
  max-width: 26ch;
  margin-inline: auto;
}

/* LA ENTRADILLA DEL CIERRE SE REFUERZA. Medido: con --mut y 15.2px de peso
   normal daba 6.32:1 (pasa AA, pero leído en pantalla se queda corto: gris
   fino y pequeño sobre negro, "se lee con dificultad"). Aquí es la última
   frase antes del botón, no una nota al pie.
   Se sube a casi --fg (13:1) y un punto y medio de cuerpo. Sin literales y
   sin tocar las otras seis secciones: solo el cierre. */
.serv__cierre .serv__dek {
  margin-inline: auto;
  max-width: 54ch;
  color: color-mix(in srgb, var(--fg) 86%, transparent);
  font-size: clamp(1.0625rem, 1.3vw, 1.25rem);
  line-height: 1.55;
}


/* ===========================================================================
   5) CONTENIDO · DISEÑO WEB (.dn-)   ·   contrato §21.5
   ---------------------------------------------------------------------------
   Disposición copiada de ds-v2/docs/diseno/overlay-diseno-web.html.
   Vestido: tokens de 01-declaraciones.css. Cero colores literales (§3.1),
   cero !important (§11), cero @font-face (las fuentes ya están en 02-base.css).

   Los tres colores de marca se usan ENFRENTADOS a propósito en los titulares
   (§9bis «énfasis de dos colores»): --acento es el color del tema, y aquí
   además hay --acento-b, que es el OTRO color de marca. Se permutan con el tema.
   =========================================================================== */

/* ── ESCENA 1 · LA IMAGEN ────────────────────────────────────────────────── */

.dn-hero {
  position: relative;
  overflow: hidden;
  padding: 0;
  align-items: center;
}

.dn-hero__contenedor {
  position: absolute;
  inset: 0;
  z-index: var(--serv-medio);
  display: flex;
  /* COLUMNA: lienzo arriba, rótulo debajo (orden de Oz, 2026-10-22). Antes era
     una fila con el rótulo a la izquierda del lienzo y un margen negativo para
     solaparlo; ahora el texto va debajo y alineado a la izquierda del lienzo. */
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: clamp(16px, 3vh, 48px) clamp(16px, 3vw, 48px);
  pointer-events: none;
}

/* RÓTULO DEBAJO DE LA IMAGEN, alineado a su borde izquierdo. El contenedor es
   una columna con `align-items: center`, así que el rótulo se alinea con
   `align-self: stretch` + `text-align: left` y no con márgenes mágicos: su
   izquierda es la del lienzo, medido en el navegador. Tamaño mínimo 3rem
   (orden de Oz); antes heredaba 16px porque NADIE le fijaba font-size y esta
   regla no lo declaraba. */
.dn-hero__rotulo {
  position: relative;
  z-index: var(--serv-tapa);
  flex: 0 0 auto;
  /* El rótulo mide lo mismo que el lienzo y arranca en su borde izquierdo:
     `width: 100%` sobre el bloque centrado, sin márgenes que lo saquen. Con
     `align-self: stretch` se estiraba a todo el contenedor (medido: 1344px
     frente a los 1075 del lienzo) y el texto no arrancaba donde la imagen. */
  /* El rótulo NO intenta calcular el ancho del lienzo en CSS: el lienzo se
     dimensiona por ALTO (`height` en % + `aspect-ratio`), y una fórmula copiada
     a mano se desincroniza al primer cambio (medido: rótulo de 1075 frente a un
     lienzo de 812). El ancho se copia del lienzo EN EL NAVEGADOR desde JS
     (js/diseno-web.js, `igualarRotulo()`), y aquí queda un 100% de respaldo por
     si el JS no corre. */
  width: 100%;
  text-align: left;
  /* Justo debajo de la imagen, sin pisarla. Este -100px es el punto que Oz dio
     por bueno: NO se toca sin que él lo pida. */
  margin-block-start: -100px;
  font-size: clamp(3rem, 4.4vw, 4.5rem);
  white-space: nowrap;
  font-family: var(--fuente-titular);
  font-weight: 800;   /* Outfit: aquí SÍ se puede pedir peso (no es el logo) */
  line-height: 0.95;
  letter-spacing: -0.02em;
  color: var(--acento);
  pointer-events: none;
  text-shadow:
    0 1px 2px rgba(20, 22, 26, 0.22),
    0 6px 22px rgba(20, 22, 26, 0.28),
    0 0 1px rgba(20, 22, 26, 0.18);
}

/* Cuando las partículas ya están montadas, el texto se oculta: lo dibuja el
   canvas. Lo pone el JS, no el CSS, para que sin JS siga viéndose el texto. */
.dn-hero__rotulo[data-particulas-listo] .dn-hero__rotulo-txt {
  opacity: 0;
}

.dn-hero__rotulo-p {
  position: absolute;
  inset: 0;
  display: block;
}

.dn-hero__rotulo-p canvas {
  display: block;
  width: 100%;
  height: 100%;
}

.dn-hero__lienzo {
  position: relative;
  height: min(72%, 900px);   /* 96% -> 72%: el rótulo de debajo ocupa altura */
  aspect-ratio: 1080 / 810;      /* mismo ratio que los .webp */
  max-width: 80%;
  flex: 0 0 auto;
}

.dn-hero__capas {
  position: absolute;
  inset: 0;
}

/* CINCO CAPAS APILADAS (2026-10-22). Las cinco imágenes de Oz son 1080x810, el
   mismo ratio que este lienzo (1080/810), así que cada una lo cubre ENTERO con
   `inset: 0` + `object-fit: contain`: no hay que colocarlas por porcentaje, su
   propio encuadre ya está alineado entre ellas. Venimos de dos intentos
   fallidos que conviene no repetir: (1) las cinco capas con `opacity: 0`
   esperando un motor de parallax que nunca existió (nadie escribía
   --mx/--my/--esc), y (2) piezas recortadas colocadas por porcentaje.
   PARALLAX ACTIVO (orden de Oz, 2026-10-22). El desplazamiento lo calcula
   js/diseno-web.js y lo deja en --capa-x / --capa-y; aquí solo se aplica con
   translate3d (no cambiando left/top: así lo compone la GPU). */
.dn-hero__capa {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  opacity: 1;
  /* Las variables caen a 0px si el JS no corre: sin parallax se ve la escena
     quieta y completa, nunca descuadrada. `will-change` avisa al compositor
     para que no repinte en cada movimiento del ratón. */
  transform: translate3d(var(--capa-x, 0px), var(--capa-y, 0px), 0);
  will-change: transform;
}

/* Apilado. EL PRIMERO DEL DOM VA DELANTE: el z-index DECRECE por posición.
   Se resolvió con nth-child para que el orden viva en un solo sitio (el HTML) y
   no en una clase por nivel que se desincronice. Reparto, de delante a fondo:
     1º -> 4  (c3)   2º -> 3  (c2)   3º -> 2  (c1)   4º -> 1  (c0)   5º -> 0 (c4)
   OJO: en ds-v2 la nomenclatura era al contrario (c0 era el fondo). Aquí manda
   la numeración de esta escena, dictada por Oz. */
.dn-hero__capa:nth-child(1) { z-index: var(--serv-capa-1); }
.dn-hero__capa:nth-child(2) { z-index: var(--serv-capa-2); }
.dn-hero__capa:nth-child(3) { z-index: var(--serv-capa-3); }
.dn-hero__capa:nth-child(4) { z-index: var(--serv-capa-4); }
.dn-hero__capa:nth-child(5) { z-index: var(--serv-capa-5); }

.dn-hero__velo {
  position: absolute;
  inset: 0;
  z-index: var(--serv-alto);
  pointer-events: none;
}


/* ── ESCENA 2 · LA FRASE ─────────────────────────────────────────────────── */

/* La frase se centra; las tres escenas con rejilla van a la IZQUIERDA y
   estiradas. Sin esto, cada bloque se centraba por su cuenta y los titulares
   arrancaban en tres x distintas (226 / 279 / 327 px en el original). */
.dn-frase {
  align-items: center;
  text-align: center;
  /* SIN fondo: el resplandor que traía el original tapaba el shader. El aire
     alrededor del titular ya lo da el padding de .serv__escena. */
}

.dn-frase .serv__titulo {
  max-width: 16ch;
}

.dn-frase .serv__dek {
  margin-top: var(--e-4);
  max-width: 52ch;
}

/* <u> no es un subrayado: es el SEGUNDO color de marca. En HTML se usa <u>
   porque es énfasis sin semántica de link; el subrayado se quita siempre. */
.dn-frase u {
  text-decoration: none;
  color: var(--acento-b);
}


/* ── ESCENA 3 · LA PRUEBA ────────────────────────────────────────────────── */

.dn-prueba,
.dn-rango,
.dn-criterio {
  align-items: stretch;
  text-align: start;
  gap: clamp(28px, 4vh, 52px);
}

.dn-prueba__enc,
.dn-rango__enc {
  max-width: 70ch;
}

.dn-riel {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(12px, 1.6vw, 22px);
  margin: 0;
}

.dn-pieza {
  position: relative;
  margin: 0;
  border-radius: 14px;
  overflow: hidden;
  border: 1px solid var(--borde);
  background: var(--superficie);
  box-shadow: var(--sombra-2);
  aspect-ratio: 16 / 11;
}

.dn-pieza__video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Recuadro de la cifra. Fondo translúcido + blur para que el número se lea
   sobre CUALQUIER video, sin taparlo: es una pieza pequeña. */
.dn-pieza__cifra {
  position: absolute;
  z-index: var(--serv-cima);
  inset-block-start: 16px;
  inset-inline-end: 16px;
  padding: 8px 13px 9px;
  border-radius: 11px;
  background: var(--dn-cristal);
  backdrop-filter: blur(9px) saturate(1.2);
  border: 1px solid var(--acento-borde);
  box-shadow:
    0 0 0 1px var(--acento-anillo),
    0 0 18px var(--acento-glow),
    0 0 42px var(--acento-brillo),
    inset 0 0 22px var(--acento-int);
}

.dn-pieza__num {
  display: block;
  font-weight: 800;
  line-height: 1;
  font-size: clamp(1.35rem, 2.1vw, 1.7rem);
  letter-spacing: -0.03em;
  color: var(--acento);
  text-shadow: 0 0 14px var(--acento-texto), 0 0 30px var(--acento-texto-suave);
  font-variant-numeric: tabular-nums;   /* el número no baila al contar */
  font-style: normal;
}

.dn-pieza__rot {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  padding: 26px 15px 13px;
  background: linear-gradient(180deg, transparent, var(--dn-velo-fuerte));
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--acento);
  text-shadow: 0 1px 8px var(--dn-sombra-texto);
}

.dn-pieza__rot small {
  display: block;
  font-weight: 400;
  font-size: 0.68rem;
  color: var(--fg);
  margin-top: 3px;
  line-height: 1.35;
  text-shadow: 0 1px 6px var(--dn-sombra-texto);
}


/* ── ESCENA 4 · LA SOLUCIÓN (carpetas de cristal) ────────────────────────── */

.dn-rango {
  /* SIN fondo, igual que .dn-frase: aquí también mandan el shader y las
     carpetas de cristal, que necesitan ver a través. */
}

/* Las hojas ABREN hacia ARRIBA hasta 125px: esta fila necesita AIRE encima o
   solapa el texto. margin-top es funcional, no decorativo. */
.dn-carpetas {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 26px;
  margin-top: clamp(110px, 15vh, 150px);
  align-items: end;
  width: 100%;            /* sin esto el grid se encoge a su ancho mínimo */
  align-self: stretch;
}

.dn-carpeta {
  position: relative;
  perspective: 800px;
  cursor: pointer;
  padding: 0;
  background: none;
  border: 0;
  font: inherit;
  color: inherit;
  text-align: start;

  /* ── TOKENS DEL COMPONENTE CARPETA · ÚNICO SITIO DONDE SE DEFINEN ───────
     UN componente para los siete servicios. Si hay que retocar la carpeta o
     el abanico, se retoca AQUÍ y cambia en las siete secciones.

     Antes estos dos valores estaban sueltos más abajo, en un segundo bloque
     `.dn-carpeta`, y el de tablet vertical tenía que ir al final del archivo
     para que el orden de aparición no le pisara el ajuste. Un token vive donde
     se declara la caja: ahora hay un solo bloque y no hay carrera de orden. */
  /* ── ABANICO EN PROPORCIÓN, NO EN PÍXELES (orden de Oz, 2026-10-22: «si crece
     la carpeta debe medir proporcionalmente el movimiento»).

     Estaban en 122px y 30px: si la carpeta crecía (tablet, teléfono, columna
     más ancha), el ancho de las hojas crecía pero el abanico seguía subiendo
     los mismos 122px — se quedaba corto y las hojas se hundían en la carpeta.

     Ahora son un % DEL ALTO DE LA CARPETA, y el alto sale de la proporción
     (ancho × 0.62). O sea: todo deriva de UN número. Si cambias
     `--dn-carpeta-proporcion`, abanico y separación se reajustan solos y las
     hojas siguen asomando lo mismo en cualquier tamaño.

       122 / 148 (alto de carpeta en PC) ≈ 82%   → alto del abanico
        30 / 148                        ≈ 20%   → separación entre hojas

     `translate()` con % se mide sobre la ALTURA DE LA PROPIA HOJA, no de la
     carpeta, así que un % suelto no vale. Uso `cqh`: % de la altura de ESTE
     contenedor, que se activa con `container-type: size` (abajo). Es CSS puro:
     crece con la carpeta sin JS y sin medir nada.

     Si el navegador no entiende `cqh`, el `@supports` de más abajo devuelve los
     px aprobados y todo queda como estaba. */
  /* NO poner `container-type` aquí: la carpeta saca su alto de su contenido
     (aspect-ratio + texto), y `container-type: size` la deja en 0×0. Pasó y se
     vio: las tres vistas con carpeta 0×0. */
  --dn-alto-carpeta: 148px;   /* alto real de la carpeta; lo escribe el JS */

  /* ── PUESTA A PRUEBA: TRES RELATIVOS INDEPENDIENTES (solo carpeta 1) ────
     Cálculo relativo de Oz, corregido de sentido: el alto de la carpeta se
     divide por lo que asoma, así que en el CSS se MULTIPLICA por la fracción
     inversa. Cada hoja lleva su propio relativo —57/148, 95/148, 131/148— en
     vez de derivar unas de otras, porque medido no están a la misma distancia
     (38px entre 1-2 y 36px entre 2-3: el ancla `bottom:54%` y los giros meten
     ese desfase y no es un error de dibujo).

     Resultado: las tres hojas caen exactamente donde estaban aprobadas
     (57 / 95 / 131 sobre una carpeta de 148) y si la carpeta crece, crecen en
     la misma proporción. Cero medidas estáticas. */
  --dn-rel-h1: 0.385135135;   /* 57  / 148 */
  --dn-rel-h2: 0.641891892;   /* 95  / 148 */
  --dn-rel-h3: 0.885135135;   /* 131 / 148 */

  /* Tres usos, uno por hoja. Los usan las CINCO carpetas (y las siete
     secciones de servicio): no hay rama por carpeta ni por sección. */
  --dn-abanico-uso-1: calc(var(--dn-alto-carpeta) * var(--dn-rel-h1));
  --dn-abanico-uso-2: calc(var(--dn-alto-carpeta) * var(--dn-rel-h2));
  --dn-abanico-uso-3: calc(var(--dn-alto-carpeta) * var(--dn-rel-h3));
}

.dn-carpeta__abanico {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: var(--serv-bajo);
}

.dn-carpeta__cuerpo {
  position: relative;
  z-index: var(--serv-alto);
  display: block;
  border-radius: 13px 13px 15px 15px;
  background: var(--superficie-2);
  border: 1px solid var(--borde);
  backdrop-filter: blur(11px) saturate(1.2);
  box-shadow: var(--sombra-1), inset 0 1px 0 rgba(var(--blanco-rgb), 0.11);
  padding: 19px 15px 15px;

  /* ── PROPORCIÓN INMUTABLE (orden de Oz, 2026-10-22) ───────────────────
     La carpeta tenía `min-height: 154px`: un alto suelto sin relación con el
     ancho. Al ensancharse la columna (tablet, teléfono, escritorio) el alto no
     acompañaba y la carpeta se Aplanaba — medido antes: ratio 1.38 en tablet
     vertical y 2.27 en teléfono vertical, o sea la misma carpeta vista con
     formas distintas.

     Ahora la forma la define UN número: `--dn-carpeta-proporcion`. El alto sale
     SIEMPRE del ancho. La carpeta puede crecer o encogerse, pero no se puede
     deformar: ancho y alto no se tocan por separado nunca.

     `height: auto` + el contenido manda si algún día el texto es más largo que
     la proporción: se usa `min-height` con el ratio calculado, no `height`, para
     que un texto largo NO se recorte (desbordaría del recuadro). Si el texto es
     corto, la proporción manda. */
  --dn-carpeta-proporcion: 1 / 0.62;   /* ancho : alto  →  ≈1.61 de ratio */
  /* ── EL ANCHO LO MANDA EL PADRE, NO LA PROPORCIÓN ──────────────────────
     Fallo medido a 1280×800: la columna del grid mide 209.6 px y el cuerpo
     del contenido crecía a 238.7 — 29 px de desborde, en las TRES secciones
     por igual (Diseño web, Automatizaciones, Herramientas IA).

     CAUSA REAL: `aspect-ratio` con `min-height: 148px` hace que el navegador
     derive el ANCHO desde el ALTO: 148 × (1/0.62) = 238.7. O sea, el
     `aspect-ratio` decide el ancho y la columna del grid se queda corta. No
     era el padding (box-sizing: border-box ya es global) ni el contenido.

     `inline-size: 100%` fuerza el ancho del padre. Entonces el alto sale de la
     proporción, que es lo que se quería desde el principio (el comentario de
     arriba lo dice: «el alto sale SIEMPRE del ancho»). El reparto queda al
     revés de como estaba. */
  inline-size: 100%;
  max-inline-size: 100%;
  aspect-ratio: var(--dn-carpeta-proporcion);

  /* `height: auto` + `min-height: 0` dejaba la carpeta en 0×0 (medido: las tres
     vistas con caja 0x0). Es hija de un <button> sin alto propio y el
     `aspect-ratio` no puede inventarse el alto si no hay ni contenido ni suelo.
     El suelo de 148px NO es el bug: el bug era no tener ninguno. Por encima de
     ese suelo manda la proporción, así que la carpeta crece sola con el ancho
     (encima de 238px de columna, el alto ya sale del ratio). */
  min-height: 148px;

  transform-origin: 50% 100%;
  transition: box-shadow 0.45s, border-color 0.45s;
}

/* La pestaña de la carpeta */
.dn-carpeta__cuerpo::before {
  content: "";
  position: absolute;
  top: -11px;
  inset-inline-start: 14px;
  width: 76px;
  height: 13px;
  border-radius: 8px 8px 0 0;
  background: var(--superficie-2);
  border: 1px solid var(--borde);
  border-bottom: none;
}

.dn-carpeta__titulo {
  display: block;
  font-size: clamp(0.86rem, 1.1vw, 1.03rem);
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0 0 5px;
  color: var(--fg);
}

.dn-carpeta__desc {
  display: block;
  font-size: 0.72rem;
  line-height: 1.45;
  color: var(--mut);
}

.dn-carpeta__par {
  display: block;
  font-size: 0.6rem;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--mut);
  margin-top: 10px;
  font-weight: 500;
}

/* El punto de la carpeta */
.dn-carpeta__nodo {
  position: relative;
  display: block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  margin-bottom: 13px;
  background: var(--acento);
  box-shadow: 0 0 11px var(--acento-borde);
  transition: box-shadow 0.5s;
}

.dn-carpeta__nodo::after {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  border: 1px solid transparent;
  transition: border-color 0.5s, inset 0.5s;
}

.dn-carpeta:hover .dn-carpeta__nodo::after,
.dn-carpeta[aria-expanded="true"] .dn-carpeta__nodo::after {
  border-color: var(--acento-borde-fuerte);
  inset: -7px;
}

/* ACTIVA = estado (borde). HOVER = respuesta (brillo). No se mezclan
   (regla de Oz en el original). */
.dn-carpeta[aria-expanded="true"] .dn-carpeta__cuerpo {
  border-color: var(--acento-borde);
}

@media (hover: hover) and (pointer: fine) {
  .dn-carpeta:hover .dn-carpeta__cuerpo {
    border-color: var(--acento-borde-fuerte);
    box-shadow: 0 8px 26px var(--acento-suave), inset 0 1px 0 rgba(var(--blanco-rgb), 0.14);
  }
  .dn-carpeta[aria-expanded="true"]:hover .dn-carpeta__cuerpo {
    box-shadow: 0 12px 34px var(--acento-suave), inset 0 1px 0 rgba(var(--blanco-rgb), 0.16);
  }
}

/* Las hojas: creadas por JS desde data-hojas. Van por DETRÁS del cuerpo para
   que parezca que salen de la carpeta. Se centran con left:50% +
   translateX(-50%), NUNCA con margin-inline-start en % (se calculaba sobre el
   padre y las descentraba: bug ya pagado en el original). */

.dn-hoja {
  position: absolute;
  left: 50%;
  bottom: 54%;
  /* ── ANCHO DE LA HOJA (orden de Oz, 2026-10-22): «salen demasiado, restales
     anchura». Estaban las tres al 86% del ancho de la carpeta: solo 7px de
     aire a cada lado sobre una carpeta de 238px, y el abanico parecía tres
     cartulinas apiladas a la misma anchura.
     Ahora cada hoja es más estrecha y la de arriba es la que más se aprieta,
     así el abanico se abre en profundidad y no en bloque:
       hoja 1 (abajo)  86% → 78%
       hoja 2          86% → 74%
       hoja 3 (arriba) 86% → 70%
     El ancho NO fija la forma: las hojas no llevan proporción propia y su
     `min-height` es un suelo para que quepa el texto, no una altura. */
  position: absolute;
  left: 50%;
  bottom: 54%;

  /* ── ANCHO DE LA HOJA (orden de Oz, 2026-10-22). Estaban las tres al 86% del
     ancho de la carpeta: solo 7px de aire a cada lado sobre una carpeta de
     238px, y el abanico parecía tres cartulinas apiladas de la misma anchura.
     Ahora cada una es más estrecha que la de abajo, así el abanico se abre en
     profundidad y no en bloque. */
  --dn-hoja-ancho: 78%;       /* hoja 1 (la de más abajo) */
  --dn-hoja-ancho-2: 74%;     /* hoja 2 */
  --dn-hoja-ancho-3: 70%;     /* hoja 3 (la de más arriba) */

  --dn-hoja-alto: 64px;       /* altura de la hoja (orden de Oz: más alta) */

  width: var(--dn-hoja-ancho);
  min-height: var(--dn-hoja-alto);
  display: flex;
  align-items: flex-start;
  text-align: start;
  padding: 13px 14px;
  border-radius: 9px;
  font-size: 0.67rem;
  line-height: 1.3;
  background: var(--superficie);
  border: 1px solid var(--acento-borde);
  color: var(--fg);
  box-shadow: var(--sombra-1);
  opacity: 0;
  transform: translate(-50%, 2px) rotate(0deg) scale(0.88);
  transform-origin: 50% 100%;
  transition: opacity 0.3s cubic-bezier(0.22, 1, 0.36, 1),
              transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
  pointer-events: none;
}

.dn-carpeta[aria-expanded="true"] .dn-hoja {
  opacity: 1;
}

/* ── ABANICO · MOVIMIENTO RELATIVO AL ALTO (vigente, 2026-10-22) ───────────
   Tres transform, uno por hoja, sin keyframes: el componente tiene UN efecto
   para las siete secciones (contrato §21.8).

   HISTORIA QUE YA NO APLICA: antes esto era un solo token `--dn-abanico-alto`
   en píxeles (142 / 132 / 127 / 122 según se bajaba el abanico), del que se
   derivaban las otras dos hojas restando un paso. Se abandonó porque el paso no
   era constante (medido: 38px entre 1-2 y 36px entre 2-3) y porque en píxeles
   el abanico no acompañaba al crecimiento de la carpeta. Ese token YA NO EXISTE.
   Lo que hay hoy son tres relativos independientes —`--dn-rel-h1/2/3`— que se
   multiplican por el alto REAL de la carpeta (`--dn-alto-carpeta`). Ver §21.8. */
.dn-carpeta[aria-expanded="true"] .dn-hoja:nth-child(1) {
  transform: translate(-50%, calc(-1 * var(--dn-abanico-uso-1))) rotate(0deg) scale(1);
  z-index: var(--serv-cima);
  transition-delay: 0.02s;
}

.dn-carpeta[aria-expanded="true"] .dn-hoja:nth-child(2) {
  width: var(--dn-hoja-ancho-2);
  transform: translate(-45%, calc(-1 * var(--dn-abanico-uso-2)))
             translateX(-4%) rotate(5deg) scale(1);
  z-index: var(--serv-alto);
  transition-delay: 0.09s;
}

.dn-carpeta[aria-expanded="true"] .dn-hoja:nth-child(3) {
  width: var(--dn-hoja-ancho-3);
  transform: translate(-55%, calc(-1 * var(--dn-abanico-uso-3)))
             translateX(8%) rotate(10deg) scale(1);
  z-index: var(--serv-medio);
  transition-delay: 0.16s;
}

.dn-rango__polos {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: clamp(18px, 2.6vh, 28px);
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--mut);
  font-weight: 500;
}

.dn-rango__polos span {
  display: inline-flex;
  align-items: center;
  gap: var(--e-2);
}

.dn-rango__polos span::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--acento);
}


/* ── ESCENA 5 · EL CRITERIO (cuatro mini-demos) ──────────────────────────── */

.dn-demo {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(10px, 1.3vw, 18px);
}

.dn-d {
  border-radius: 14px;
  border: 1px solid var(--borde);
  background: var(--superficie);
  overflow: hidden;
  transition: opacity 0.5s, border-color 0.5s, transform 0.5s;
}

@media (hover: hover) and (pointer: fine) {
  /* Al pasar por la rejilla, TODAS se apagan y solo la señalada se enciende.
     Es un gesto de lectura: dice "mire una cada vez". */
  .dn-demo:hover .dn-d {
    opacity: 0.45;
  }
  .dn-demo:hover .dn-d:hover {
    opacity: 1;
    border-color: var(--acento-borde);
    transform: translateY(-5px);
  }
}

.dn-d__txt {
  padding: 15px 15px 17px;
}

.dn-d__num {
  display: block;
  font-size: 0.6rem;
  letter-spacing: 0.2em;
  color: var(--acento);
  font-weight: 700;
  margin-bottom: 7px;
}

.dn-d__h {
  display: block;
  font-size: clamp(0.84rem, 1.05vw, 1rem);
  font-weight: 600;
  margin-bottom: 6px;
  letter-spacing: -0.01em;
  color: var(--fg);
}

.dn-d__p {
  margin: 0;
  font-size: 0.73rem;
  line-height: 1.5;
  color: var(--mut);
}

.dn-d__caja {
  height: 104px;
  position: relative;
  overflow: hidden;
  border-bottom: 1px solid var(--borde);
  display: grid;
  place-items: center;
  /* Las mini-demos están dibujadas con blanco translúcido (rgba(255,255,255,x))
     tomado del original. En TEMA CLARO desaparecerían sobre --superficie
     clara: por eso su caja lleva un fondo oscuro propio. Es el único punto del
     archivo con un color fijo, y está justificado aquí. */
  background: var(--dn-caja-fondo);
}

/* demo 01 · JERARQUÍA — bloques que se ordenan por peso */
.dn-d__caja--jerarquia span:nth-child(1) {
  position: absolute;
  left: 16%;
  top: 26%;
  height: 11px;
  border-radius: 4px;
  background: var(--acento);
  animation: dn-jer1 3.4s ease-in-out infinite;
}
.dn-d__caja--jerarquia span:nth-child(2) {
  position: absolute;
  left: 16%;
  top: 48%;
  height: 6px;
  border-radius: 3px;
  background: rgba(var(--blanco-rgb), 0.32);
  animation: dn-jer2 3.4s ease-in-out infinite;
}
.dn-d__caja--jerarquia span:nth-child(3) {
  position: absolute;
  left: 16%;
  top: 63%;
  height: 6px;
  border-radius: 3px;
  background: rgba(var(--blanco-rgb), 0.18);
  animation: dn-jer3 3.4s ease-in-out infinite;
}

@keyframes dn-jer1 { 0%, 100% { width: 56%; opacity: 1; } 50% { width: 34%; opacity: 0.6; } }
@keyframes dn-jer2 { 0%, 100% { width: 38%; opacity: 0.5; } 50% { width: 58%; opacity: 1; } }
@keyframes dn-jer3 { 0%, 100% { width: 28%; } 50% { width: 44%; } }

/* demo 02 · ESPACIO — cuatro puntos que respiran */
.dn-d__caja--espacio span {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 14px;
  height: 14px;
  margin: -7px 0 0 -7px;
  border-radius: 5px;
  background: rgba(var(--blanco-rgb), 0.28);
}
.dn-d__caja--espacio span:nth-child(1) { animation: dn-esp1 4s ease-in-out infinite; }
.dn-d__caja--espacio span:nth-child(2) { animation: dn-esp2 4s ease-in-out infinite; }
.dn-d__caja--espacio span:nth-child(3) { animation: dn-esp3 4s ease-in-out infinite; }
.dn-d__caja--espacio span:nth-child(4) { animation: dn-esp4 4s ease-in-out infinite; }

@keyframes dn-esp1 { 0%, 100% { transform: translate(-26px, -24px); opacity: 0.3; } 50% { transform: translate(-34px, -32px); opacity: 0.85; } }
@keyframes dn-esp2 { 0%, 100% { transform: translate(26px, -24px); opacity: 0.3; } 50% { transform: translate(34px, -32px); opacity: 0.85; } }
@keyframes dn-esp3 { 0%, 100% { transform: translate(-26px, 24px); opacity: 0.3; } 50% { transform: translate(-34px, 32px); opacity: 0.85; } }
@keyframes dn-esp4 { 0%, 100% { transform: translate(26px, 24px); opacity: 0.3; } 50% { transform: translate(34px, 32px); opacity: 0.85; } }

/* demo 03 · MOVIMIENTO — algo que se desliza guiando
   La caja NO hereda position:relative de .dn-d__caja a propósito: se declara
   aquí explícita, porque los hijos (.dn-d__g / .dn-d__pt) se posicionan contra
   ella y una regla ausente es justo lo que dejó "netia" naranja (check #19). */
.dn-d__caja--movimiento {
  position: relative;
  display: block;
}

.dn-d__g {
  position: absolute;
  left: 6%;
  top: 50%;
  width: 88%;
  height: 1px;
  background: linear-gradient(90deg,
    rgba(var(--blanco-rgb), 0.1),
    rgba(var(--blanco-rgb), 0.34) 50%,
    rgba(var(--blanco-rgb), 0.1));
}
.dn-d__pt {
  position: absolute;
  top: 50%;
  width: 9px;
  height: 9px;
  margin-top: -4.5px;
  border-radius: 50%;
  background: var(--acento);
  box-shadow: 0 0 14px var(--acento);
  animation: dn-mov 3s cubic-bezier(0.5, 0, 0.5, 1) infinite;
}
@keyframes dn-mov {
  0%   { left: 12%; opacity: 0; }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  100% { left: 88%; opacity: 0; }
}

/* demo 04 · VELOCIDAD — barra que se llena */
.dn-d__caja--velocidad {
  position: relative;
  display: block;
}

.dn-d__barra {
  position: absolute;
  left: 9%;
  top: 50%;
  width: 82%;            /* ancho explícito: no left+right sueltos */
  height: 5px;
  border-radius: 3px;
  background: rgba(var(--blanco-rgb), 0.14);
  overflow: hidden;
}
.dn-d__barra i {
  display: block;
  height: 100%;
  width: 100%;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--acento), var(--acento-b));
  transform-origin: 0 50%;
  transform: scaleX(0);
  animation: dn-vel 3.6s cubic-bezier(0.3, 0, 0.2, 1) infinite;
}
.dn-d__ms {
  position: absolute;
  left: 9%;
  top: 64%;
  font-size: 0.62rem;
  color: var(--fg);
  letter-spacing: 0.06em;
  animation: dn-msn 3.6s ease-in-out infinite;
}
@keyframes dn-vel { 0% { transform: scaleX(0); } 70% { transform: scaleX(1); } 100% { transform: scaleX(1); } }
@keyframes dn-msn { 0% { opacity: 0; } 40% { opacity: 1; } 100% { opacity: 0.75; } }


/* ── ESCENA 6 · CIERRE ─────────────────────────────────────────────────────
   No lleva reglas propias: usa .serv__cierre (estructura) y dentro el botón
   del patrón §20.7. Reglas .dn- específicas aquí solo si alguna vez las
   necesita; hoy no.
   ------------------------------------------------------------------------- */


/* ---------------------------------------------------------------------------
   6) MOVIMIENTO REDUCIDO
   ---------------------------------------------------------------------------
   No se anula la transición por completo: se pone a duración cero, para que
   .vis siga aplicándose y el contenido no se quede invisible.
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .serv__raiz .punto,
  .serv__raiz .texto,
  [data-rev] {
    transition-duration: 0.01ms;
  }
  /* CON movimiento reducido el JS pone .vis de golpe (§diseno-web.js), pero si
     por lo que sea no corriera, esto evita que algo se quede invisible. */
  [data-rev] {
    opacity: 1;
    transform: none;
  }
}


/* ---------------------------------------------------------------------------
   REVELADO AL HACER SCROLL  ·  [data-rev] / .vis
   ---------------------------------------------------------------------------
   La clase la pone js/diseno-web.js, igual en las 7 secciones. Se deja en la
   capa de ESTRUCTURA porque no es de esta escena: cualquier servicio la usa.

   Sin JS, el contenido se quedaría con opacity:0 PARA SIEMPRE. Por eso el
   estado inicial no vive aquí: el JS añade la clase, y si no hay JS nunca se
   oculta nada (el elemento arranca visible y así se queda).
   Es decir: aquí solo se define CÓMO se revela, no que arranque oculto.
   ------------------------------------------------------------------------- */
/* La regla cuelga de `[data-rev-listo]`, que pone el JS al arrancar:
   si el JS no corre, este bloque NO EXISTE y todo el contenido se ve.
   Si lo pusiera en `[data-rev]` a secas, un fallo del script dejaría la
   sección en blanco — el peor fallo posible, y el más difícil de ver. */
[data-rev-listo] [data-rev] {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity 0.45s cubic-bezier(0.4, 0, 0.2, 1),
              transform 0.45s cubic-bezier(0.4, 0, 0.2, 1);
}

[data-rev-listo] [data-rev].vis {
  opacity: 1;
  transform: none;
}

/* ── TABLET VERTICAL · EL ABANICO 15px MÁS BAJO ───────────────────────────
/* TABLET VERTICAL: ya no necesita regla propia. Antes el abanico iba en píxeles y
   había que restarle 15px a mano aquí («en vertical sale demasiado, redúcelo
   15px»); hoy es una fracción del alto de la carpeta (`--dn-rel-h1/2/3`), y en
   vertical la carpeta es más baja, así que el abanico baja SOLO —en la misma
   proporción— sin regla ni valor duplicado. Es exactamente lo que pidió Oz:
   «si crece la carpeta debe medir proporcionalmente el movimiento».
   El comentario anterior hablaba de un `--dn-abanico-alto: 142px` y de `82cqh`:
   NINGUNO de los dos existe ya. El token es relativo y `cqh` se descartó porque
   `container-type: size` dejaba la carpeta en 0×0. */


/* ── REPARTO 2 · 2 · 1 EN TABLET (orden de Oz, 2026-10-22) ─────────────────
   Contrato: docs/02-DECLARACIONES.md §21.8 «REPARTO 2 + 2 + 1 EN TABLET».

   El fallo: con `repeat(auto-fit, minmax(190px, 1fr))` en tablet caían 3 + 2 y
   la fila de abajo quedaba PEGADA A LA IZQUIERDA (medido en 768px: x = 38 / 277
   / 517 arriba y 38 / 277 abajo). `auto-fit` reparte columnas y no sabe centrar
   lo que sobra.

   En PC (1440) van las cinco en fila y en teléfono (390) de una en una: esas
   dos vistas ya estaban bien y NO se tocan. Este bloque solo entra entre 600 y
   1099px, que es donde cae auto-fit en 3 o 4 columnas.

   LA FORMA NO CAMBIA: la carpeta conserva su ancho de PC (238px) y su
   `aspect-ratio: 1 / 0.62` → 238×148. `max-width` + `justify-self: center` son
   los que lo garantizan: sin ellos la carpeta se estiraba al ancho de la
   columna (332px en tablet) y la proporción se rompía — que es justo lo que el
   contrato prohíbe («ancho y alto no se fijan por separado»).

   Cuatro columnas, dos por carpeta → dos por fila. Solo la quinta necesita
   colocación propia: al quedar sola, ocupa las columnas 2 y 3 y sale centrada.
   Las cuatro primeras fluyen solas. */
@media (min-width: 600px) and (max-width: 1099px) {
  .dn-carpetas {
    grid-template-columns: repeat(4, minmax(0, 1fr));

    /* AIRE TEXTO→CARPETAS EN TABLET (orden de Oz, 2026-10-22: «reduce un poco el
       espacio entre el texto y las carpetas, que se vea balanceado»).

       La base trae `clamp(110px, 15vh, 150px)`, y 15vh en tablet son ~154px:
       medido, el hueco salía de casi el alto de una carpeta (148px), lo que
       dejaba el bloque descolgado del texto.

       Aquí baja a 122px. NO se pone a cero ni se baja más: ese margen es AIRE
       PARA EL ABANICO —las tres hojas abren hacia ARRIBA y la de más arriba
       sube unos 141px sobre la caja de la carpeta—. Con 92px las hojas abiertas
       SOLAPABAN el subtítulo 30px (medido: hoja arriba=3392, texto abajo=3422).
       Con 122px el hueco cerrado queda en ~141px y abierto toca justo el texto
       (0px de aire). Solo tablet: PC (135px) y teléfono (126px) conservan su
       valor. */
    /* AJUSTE ÓPTICO DE OZ (2026-10-22, segunda orden): «acerca más las carpetas
       al texto al menos 50% del espacio actual». 141px → 70px. Es un número
       suyo, no una fórmula: se aplica tal cual.
       CONSECUENCIA ACEPTADA: con 70px el abanico ABIERTO pasa ~71px por encima
       del subtítulo (las hojas suben 141px desde la carpeta). Solo se nota al
       desplegar una carpeta de la PRIMERA fila. */
    margin-top: 70px;
  }

  .dn-carpetas > .dn-carpeta {
    /* 238px es el ANCHO APROBADO EN PC (§21.8). No es un valor nuevo: es el
       mismo de la vista buena, traído aquí para que la carpeta no crezca. */
    max-width: 238px;
    justify-self: center;
    grid-column: span 2;
  }

  /* La quinta va sola y centrada: columnas 2 y 3 de las 4. */
  .dn-carpetas > .dn-carpeta:nth-child(5) {
    grid-column: 2 / span 2;
  }

  /* ── LA PRUEBA · LOS TRES VIDEOS EN COLUMNA (orden de Oz, 2026-10-22) ────
     `.dn-riel` base son TRES columnas: los tres videos de «La prueba» van en
     fila. En tablet Oz los quiere uno debajo del otro, así que aquí pasa a una
     sola columna.

     `minmax(0, 1fr)` y no `1fr`: con `1fr` la pista se encoge al min-content y
     caben dos videos por fila en vez de uno (trampa ya documentada en el
     contrato). El `aspect-ratio: 16 / 11` de `.dn-pieza` se queda como está:
     el video crece de ancho al ocupar la columna entera y el alto lo sigue. */
  .dn-riel {
    grid-template-columns: minmax(0, 1fr);
  }

  /* ── EL CRITERIO · CUATRO TARJETAS EN 2 × 2 (orden de Oz, 2026-10-22) ────
     `.dn-demo` base es una rejilla de CUATRO columnas: las cuatro tarjetas de
     «El criterio» van en una sola fila. En tablet cada tarjeta caía a menos de
     la mitad de ancho y el texto de `.dn-d__p` se partía en cinco líneas.

     Aquí pasa a DOS columnas → 2 × 2. Es la única propiedad que cambia: el
     hueco, el orden y el contenido siguen siendo los del componente. */
  .dn-demo {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}


/* ── TELÉFONO · LA IMAGEN LLENA LA PANTALLA (orden de Oz, 2026-10-22) ──────
   Oz: «en teléfono la imagen, centrado verticalmente ese espacio en pantalla».

   Lo medido en 390×844 ANTES: lienzo **286×571px** (68% del alto) con **164px
   de aire arriba y 164px abajo**. Faltaba que la imagen creciera.

   La causa: `height: min(72%, 900px)` de la base. El 72% se mide sobre el alto
   del contenedor (844), pero `max-width: 80%` cortaba el ancho a 312px y el
   `aspect-ratio` dejaba el alto en 571. El 72% (607px) nunca se alcanzaba.

   MEDIDO Y DESCARTADO: `height: auto` + `width: 100%`. El lienzo COLAPSÓ a
   350×269 (32% del alto): dentro de un flex en columna el `aspect-ratio` no
   tiene contra qué resolverse si no hay alto fijado, y el aire subió a 315px.
   Queda escrito para no reintentarlo.

   La vía que sí sirve: subir el PORCENTAJE. `height: 78%` con el ancho igual.
   Resultado medido: lienzo **323×646px** (77% del alto), aire 79px arriba y
   abajo. No se sube más porque el rótulo «Diseño Web» va DEBAJO del lienzo con
   `margin-block-start: -100px` a 48px de cuerpo, y por encima de ~80% el rótulo
   empieza a salirse por el pie. El tope lo pone el rótulo, no la imagen.

   Breakpoint `max-width: 719px` = teléfono. La base y el bloque de tablet
   (600px–1099px) quedan intactos: tablet sigue en 68% (578×693 en 768) y PC
   igual (812×609). */
/* ── TELÉFONO · TAMBIÉN TUMBADO (orden de Oz, 2026-10-22) ─────────────────
   BUG MEDIDO Y CERRADO: filtrar «teléfono» por `max-width: 719px` deja FUERA al
   teléfono en horizontal, que mide **844px de ancho** y cae en el bloque de
   tablet (600–1099). Diagnóstico real en 844×390:
       media_719: false · media_600_1099: true · max-height: none
   o sea que los videos se trataban como tablet: 760×522px de alto con solo
   321px de altura útil, saliéndose de la pantalla.

   El discriminante correcto es la ALTURA: un teléfono tumbado tiene ~390px de
   alto, y un tablet tumbado no baja de ~600. `max-height: 500px` entra al
   teléfono en las dos orientaciones sin tocar ningún tablet.
   ------------------------------------------------------------------------- */
@media (max-width: 719px), (max-height: 500px) {
  .dn-hero__lienzo {
    height: 78%;
  }

  /* ── CENTRADO DE VERDAD EN TELÉFONO (orden de Oz, 2026-10-22) ──────────
     Oz: «en la sección la imagen ves cómo todo está abajo, deseo que se vea
     centrado como la siguiente [«La frase»], que se ve balanceado».

     El bloque no estaba descentrado por el `justify-content` (que es center):
     lo empujaba hacia ABAJO el `margin-block-start: -100px` del rótulo. Ese
     margen resta 100px por ARRIBA del bloque y no toca el aire de abajo, así
     que el centro óptico caía 50px por debajo del centro de la franja.

     NO se toca el -100px: es el punto que Oz aprobó («no tocar sin pedirlo»).
     Se compensa en el padding del contenedor, y la compensación es **la MITAD**
     del solape (50px), no el solape entero: con 100px me pasé al otro lado
     (medido: 122px arriba y 222px abajo — el bloque subió de más).

     Con 50px: aire 72px arriba y 172px abajo CONTANDO el rótulo, pero el rótulo
     entra 100px dentro del lienzo, así que el peso visual queda 72/72.
     Cuentas: aire_arriba + aire_abajo_visible = 72 + (172-100) = 72/72.

     Solo teléfono. Tablet y PC conservan su padding de clamp() y su -100px.

     ── Y EL BLOQUE SUBE (3ª orden, 2026-10-22) ────────────────────────────
     Oz: «la imagen debe subir y el título conservar la distancia de la imagen,
     aún se ve todo abajo».

     El desplazamiento va en el CONTENEDOR, no en el lienzo: se mueve el
     CONJUNTO (lienzo + rótulo). Si se sube solo el lienzo, el rótulo se queda y
     la distancia entre ambos cambia.

     `translateY` y no `margin-top`: el margen participa en el reparto del flex
     y volvería a mover el centrado; el transform desplaza la pieza YA colocada,
     así que la distancia lienzo→rótulo (-100px del rótulo) queda intacta y el
     aire de abajo crece en la misma cantidad.

     -56px = los 112px de aire que faltaban arriba, repartidos con el bloque.
     Medido: aire 71px arriba y 233px bajo el rótulo, con la distancia
     lienzo→rótulo en 54px SIN CAMBIOS. Aprobado por Oz («la imagen está genial»).

     Solo teléfono. Tablet y PC no llevan transform (comprobado: siguen con
     193/193 y 165/165 de aire). */
  .dn-hero__contenedor {
    padding-block: 16px calc(16px + 50px);
    transform: translateY(-56px);
  }

  /* ── LA PRUEBA · LOS TRES VIDEOS UNO DEBAJO DEL OTRO (orden de Oz,
     2026-10-22). Oz: «los videos uno debajo del otro en vertical y con el
     teléfono horizontal igual, solo que limita la altura, o sea reduce el
     tamaño del video, no lo cortes para que no salga de pantalla; recuerda
     restar cabecera y footer del cálculo».

     En teléfono estaban en FILA a 109×75px (ilegibles). Aquí pasan a una
     columna: 1 + 1 + 1, ancho completo de la franja.

     `minmax(0, 1fr)` y NO `1fr`: con `1fr` la pista se encoge al min-content y
     caben dos videos por fila. Trampa ya documentada en el contrato §21.8.

     ALTO LIMITADO POR LA PANTALLA ÚTIL. La pieza tiene `aspect-ratio: 16 / 11`:
     a ancho completo en vertical da 350×241, y en HORIZONTAL (844×390) daría
     ~844 de ancho → 580 de alto, que se sale de la pantalla. El tope usa la
     altura REAL, medida, no una constante:

         --alto-pie        → lo publica js/quienes.js en el :root, medido
         --alto-cabecera-real → lo publica js/diseno-web.js, medido
                              (el token --alto-cabecera MIENTE en tablet y
                              teléfono: dice 57 y mide 41)

     Con `aspect-ratio` + `max-height` + `width: 100%` hay que fijar el ancho
     con `width:100%` y dejar que el ratio recalcule: si se deja el ancho
     automático, cada pieza del riel toma un ancho distinto (medido 498/498/302
     en tablet en una tanda anterior). Y `object-fit: cover` NO: se usa
     `contain`-equivalente, que aquí es dejar que la caja crezca de ancho con el
     ratio y recortar solo el alto por el tope — el video lleva `object-fit:
     cover` de la base, así que el tope por alto recortaría la imagen. Por eso
     el tope se aplica a la CAJA y la caja mantiene el ratio: la pieza encoge
     entera, no se recorta.

     El hueco entre videos se conserva: es el `gap` del riel, no un margen.

     MEDIDO Y CORREGIDO EN EL PRIMER INTENTO (dejarlo escrito para no
     repetirlo): el tope NO actuaba. En horizontal 844×390 los videos salían a
     760×**522px** con solo 321px de altura útil. Dos causas:

       (a) `--alto-cabecera-real` está VACÍO en :root — js/diseno-web.js lo
           escribe sobre el ELEMENTO de la sección, no sobre la raíz, así que
           `var()` no lo ve y caía al respaldo. Aquí se usa directamente el
           token `--alto-cabecera` (57px) y, por si el pie no se ha medido, el
           respaldo de `--alto-pie` es 28px y no 0.
       (b) `max-height` sobre una caja con `width:100%` + `aspect-ratio` NO
           encoge: el ratio impone el alto y el tope solo recorta. La pieza hay
           que dimensionarla por el ALTO disponible y dejar que el ratio saque
           el ANCHO: `max-width` calculado desde la altura útil por el ratio.

           16 / 11 = 1.4545 de ancho por cada 1 de alto, así que
           ancho_máximo = altura_útil × 1.4545. */

  .dn-riel {
    grid-template-columns: minmax(0, 1fr);
    /* El tope de altura disponible, en un solo sitio. */
    --dn-video-alto-max: calc(100dvh - var(--alto-cabecera) - var(--alto-pie, 28px) - var(--e-5));
  }

  .dn-pieza {
    /* El ancho sale del ALTO disponible, no al revés: así la pieza ENCOGE
       entera cuando la pantalla es baja (teléfono horizontal) y no se recorta.
       `min()` se queda con el ancho de la franja si cabe, y con el que dicta la
       altura si la pantalla es baja. */
    width: min(100%, calc(var(--dn-video-alto-max) * 1.4545));
    max-height: var(--dn-video-alto-max);
    margin-inline: auto;
  }

  /* ── EL CRITERIO · LAS CUATRO TARJETAS UNA SOBRE OTRA (orden de Oz,
     2026-10-22). Oz: «el criterio, las tarjetas una sobre otra».

     En teléfono seguían en las CUATRO columnas de la base: medido 80×399px
     cada una, o sea un texto de 8 caracteres por línea y 399px de alto. Era el
     peor reparto de la sección.

     Aquí pasan a UNA columna: 1 · 1 · 1 · 1, a todo el ancho de la franja.

     `minmax(0, 1fr)` y NO `1fr`: con `1fr` la pista se encoge al min-content y
     podrían caber dos. La misma trampa que en `.dn-riel`.

     No hace falta tocar nada más: cada tarjeta es un bloque con su mini-demo
     arriba y su texto debajo, y al ocupar el ancho completo el párrafo deja de
     partirse en columnas de una palabra. */
  .dn-demo {
    grid-template-columns: minmax(0, 1fr);
  }
}