/* ═══════════════════════════════════════════════════════════════
   BASE — reset, variables de tema y utilidades compartidas.
   La carga TODA página. Nada específico de una sección va aquí.
   ═══════════════════════════════════════════════════════════════ */

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

html { scroll-behavior: smooth; }

/* ── Tema oscuro (el de la portada y design) ───────────────────── */
.tema-oscuro {
  --bg:        #0c0c0c;
  --card:      #141414;
  --tinta:     rgba(255,255,255,0.88);
  --tinta-alta:#ffffff;
  --muted:     rgba(255,255,255,0.45);
  --dim:       rgba(255,255,255,0.25);
  --border:    rgba(255,255,255,0.08);
  --acento:    #ff7a1a;
  --gris:      #17181a;
  --gris-on:   #1e1f22;

  /* Los que consumen header.css y footer.css */
  --nav-bg:        rgba(10,10,10,0.92);
  --nav-alto:      66px;
  --nav-borde:     rgba(255,255,255,0.08);
  --nav-tinta:     rgba(255,255,255,0.55);
  --nav-tinta-on:  #ffffff;
  --nav-hover:     rgba(255,255,255,0.07);
  --nav-menu-bg:   #0c0c0c;
  --nav-menu-brd:  rgba(255,255,255,0.10);
  --nav-menu-somb: 0 8px 24px rgba(0,0,0,.5);
  --nav-btn-brd:   rgba(255,255,255,0.15);
  --nav-btn-brd-h: rgba(255,255,255,0.40);
  --nav-cta-bg:    #ffffff;
  --nav-cta-tinta: #0c0c0c;
  --nav-movil-bg:  rgba(10,10,10,0.97);

  /* El desplegable del header. La píldora del rótulo va BLANCA y el
     panel va en un gris carbón: más claro que el header, para que se
     despegue, pero sin encender media pantalla. Los dos necesitan su
     propia tinta porque son fondos opuestos —oscuro sobre la píldora,
     claro sobre el panel. */
  --hd-pastilla:       #ffffff;
  --hd-pastilla-tinta: #14110f;
  --hd-panel:          #27272b;
  --hd-panel-tinta:    #e9e7e3;
  --hd-panel-hover:    rgba(255,255,255,0.075);
}

/* ── Tema claro ────────────────────────────────────────────────────
   Hoy no lo usa ninguna página (labs, la única que lo tenía, se
   eliminó). Se conserva porque el mecanismo es el que permite que una
   sección nueva sea clara escribiendo $tema = 'claro' y nada más: sin
   esto, volver a necesitarlo cuesta duplicar el header entero, que es
   justo el problema del que se venía. */
.tema-claro {
  --bg:        #fafafa;
  --card:      #ffffff;
  --card2:     #f0ebe3;
  --tinta:     #6b625a;
  --tinta-alta:#1a1714;
  --muted:     rgba(26,23,20,0.42);
  --dim:       rgba(26,23,20,0.30);
  --border:    rgba(26,23,20,0.09);
  --acento:    #ff7a1a;

  /* EL GRIS NEUTRO, y ojo: NO es --card2.
     --card2 vale #f0ebe3, que es un crema —lleva amarillo dentro— y
     sobre una superficie grande se lee tostado, no gris. Lo descubrió
     la FAQ (las píldoras salían del color equivocado) y lo volvió a
     descubrir el formulario de contacto meses después, porque cada una
     se había escrito su propio hex a mano. Ahora es una sola variable,
     así que no puede volver a pasar.
     --gris    superficie: las píldoras de la FAQ, el panel del formulario
     --gris-on la misma, al pasar el puntero por encima */
  --gris:      #f1f2f4;
  --gris-on:   #e9ebee;

  --nav-bg:        rgba(250,250,250,0.92);
  --nav-alto:      66px;
  --nav-borde:     rgba(26,23,20,0.08);
  --nav-tinta:     rgba(26,23,20,0.52);
  --nav-tinta-on:  #1a1714;
  --nav-hover:     rgba(26,23,20,0.05);
  --nav-menu-bg:   #ffffff;
  --nav-menu-brd:  rgba(26,23,20,0.10);
  --nav-menu-somb: 0 8px 24px rgba(0,0,0,.08);
  --nav-btn-brd:   rgba(26,23,20,0.20);
  --nav-btn-brd-h: rgba(26,23,20,0.45);
  --nav-cta-bg:    #1a1714;
  --nav-cta-tinta: #fafafa;
  --nav-movil-bg:  rgba(250,248,245,0.98);

  /* Sobre header claro se invierte: píldora oscura con tinta clara. */
  --hd-pastilla:       #1a1714;
  --hd-pastilla-tinta: #f5f3ee;
  --hd-panel:          #1a1714;
  --hd-panel-tinta:    #f5f3ee;
  --hd-panel-hover:    rgba(245,243,238,0.10);
}

:root {
  --pad: 64px;         /* respiro lateral del contenido */
  --hd-pad: 30px;      /* respiro mínimo del header en pantallas angostas */
  --ancho-max: 1440px; /* ancho del contenido del header */
  --hd-velo: 230px;    /* hasta dónde baja el degradado del header */
}

body {
  font-family: 'Sora', system-ui, -apple-system, 'Segoe UI', sans-serif;
  background: var(--bg);
  color: var(--tinta);
  -webkit-font-smoothing: antialiased;
}

/* El nav está fijo: sin esto, un enlace a #precios deja el título tapado. */
[id] { scroll-margin-top: calc(var(--nav-alto) + 20px); }

/* ── Accesibilidad ─────────────────────────────────────────────── */
.saltar-al-contenido {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--nav-cta-bg); color: var(--nav-cta-tinta);
  padding: 12px 20px; border-radius: 0 0 8px 0; font-size: 13.5px; font-weight: 600;
  text-decoration: none;
}
.saltar-al-contenido:focus { left: 0; }

/* Se lee pero no se ve. Para etiquetas de campo cuyo hueco ya ocupa el
   marcador de posición, y para titulares que la estructura del
   documento necesita pero el diseño no dibuja.

   clip-path y no display:none ni visibility:hidden: esos dos lo sacan
   también del árbol de accesibilidad, que es justo lo contrario de lo
   que se busca aquí. Y white-space:nowrap para que el texto no se
   parta en un píxel de ancho y lo lea a trozos.

   NOTA — blog.css trae su propia .bl-visualmente-oculto, idéntica.
   Se deja por ahora para no tocar el blog en un cambio de contacto,
   pero es la misma regla dos veces y toca unificarla. */
.visualmente-oculto {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

:where(a, button, [tabindex]):focus-visible {
  outline: 2px solid var(--acento);
  outline-offset: 3px;
  border-radius: 4px;
}

/* ── Utilidades compartidas ────────────────────────────────────── */
.divider {
  width: 100%; height: 1px;
  background: linear-gradient(90deg, transparent, var(--border) 30%, var(--border) 70%, transparent);
}

.sec-label {
  display: block; margin-bottom: 16px;
  font-size: 11px; font-weight: 600; letter-spacing: .14em; text-transform: uppercase;
  color: var(--muted);
}
.sec-title {
  margin-bottom: 16px;
  font-size: clamp(28px, 3.4vw, 44px); font-weight: 400; letter-spacing: -.02em; line-height: 1.12;
  color: var(--tinta-alta);
}
.sec-title em { font-style: normal; color: var(--acento); }

/* ── Revelado al hacer scroll (js/reveal.js) ───────────────────── */
.reveal {
  opacity: 0; transform: translateY(20px);
  transition: opacity .7s cubic-bezier(.22,1,.36,1), transform .7s cubic-bezier(.22,1,.36,1);
}
.reveal.visible { opacity: 1; transform: none; }

/* Entrada lateral. Mismo observador y misma clase .visible: lo único
   que cambia es desde dónde llega. Se declara DESPUÉS de .reveal para
   que su transform gane al translateY de arriba. */
.reveal-izda { transform: translateX(-64px); }
.reveal-dcha { transform: translateX(64px); }
.reveal-izda.visible,
.reveal-dcha.visible { transform: none; }

/* Un desplazamiento lateral necesita más recorrido de tiempo que uno
   vertical de 20px para no parecer un tirón. */
.reveal-izda, .reveal-dcha {
  transition: opacity .9s cubic-bezier(.16,1,.3,1), transform .9s cubic-bezier(.16,1,.3,1);
}
.reveal-d1 { transition-delay: .08s; }
.reveal-d2 { transition-delay: .16s; }
.reveal-d3 { transition-delay: .24s; }
.reveal-d4 { transition-delay: .32s; }

/* Quien pidió menos movimiento no debería tener que aguantarlo. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
  .reveal { opacity: 1; transform: none; }
}

@media (max-width: 960px) { :root { --pad: 32px; --hd-pad: 22px; } }
@media (max-width: 600px) { :root { --pad: 20px; --hd-pad: 18px; } }

/* ── Secciones todavía sin contenido ───────────────────────────────
   news/, works/, pricing/ y pages/ existen para que el menú no tenga
   enlaces muertos. Comparten este estilo hasta que cada una tenga el
   suyo; entonces se crea css/<seccion>.css y se declara en $css. */
.stub {
  max-width: 720px; margin: 0 auto;
  padding: calc(var(--nav-alto) + 140px) var(--pad) 160px;
}
.stub .sec-title { font-size: clamp(34px, 5vw, 60px); }
.stub-nota {
  font-size: 16px; line-height: 1.7; color: var(--muted);
  max-width: 480px; margin-bottom: 40px;
}
.stub-cta {
  display: inline-block;
  font-size: 14px; font-weight: 500;
  color: var(--nav-cta-tinta); background: var(--nav-cta-bg);
  padding: 13px 26px; border-radius: 99px; text-decoration: none;
  transition: opacity .18s;
}
.stub-cta:hover { opacity: .82; }

/* Las cuatro verticales del hero. Lo único que las distingue de una
   sección vacía cualquiera es que llevan su color de acento: el mismo
   que traía la diapositiva desde la que se llegó, para que el salto no
   se sienta como caer en una página ajena. El color entra por
   --acento-slide, que la página pone desde config/hero.php. */
.stub-vertical { position: relative; }
.stub-vertical .sec-label { color: var(--acento-slide, var(--muted)); }
.stub-vertical::before {
  content: '';
  position: absolute; inset: 0 auto auto 0;
  width: 100%; height: 340px;
  pointer-events: none; opacity: .16;
  background: radial-gradient(70% 100% at 22% 0%, var(--acento-slide, transparent) 0%, transparent 70%);
}

/* ── La burbuja de contacto ────────────────────────────────────────
   Solo sale donde la página declara $chat_wa = true — hoy la portada.

   Lleva los colores del sitio y un icono de globo de diálogo, no el
   verde ni el glifo de WhatsApp: sobre un hero oscuro ese verde canta
   y rompe la paleta. Que el destino sea WhatsApp lo cuenta el enlace,
   no el color.

   Va a la DERECHA y anclada a la ventana, no al contenedor: es un
   elemento flotante y se espera en la esquina, no alineado con el
   texto. A la izquierda chocaba con la columna del hero. */
.wa-burbuja {
  position: fixed; right: 22px; bottom: 22px; z-index: 90;
  display: inline-flex; align-items: center; gap: 10px;
  height: 50px; padding: 0 22px 0 17px;
  color: #fff; text-decoration: none;
  font-size: 14px; font-weight: 500; letter-spacing: -.01em;
  /* Cristal oscuro, como el desplegable del header: se apoya en lo que
     tiene detrás en vez de taparlo con un bloque plano. */
  background: rgba(18, 18, 20, .72);
  border: 1px solid rgba(255, 255, 255, .16);
  border-radius: 99px;
  backdrop-filter: blur(14px) saturate(1.3);
  -webkit-backdrop-filter: blur(14px) saturate(1.3);
  box-shadow: 0 10px 30px rgba(0, 0, 0, .38);
  transition: transform .3s cubic-bezier(.16,1,.3,1),
              border-color .3s ease, background .3s ease;
}
.wa-burbuja:hover {
  transform: translateY(-3px);
  background: rgba(24, 24, 27, .86);
  border-color: rgba(255, 255, 255, .34);
}
.wa-burbuja svg {
  width: 19px; height: 19px; flex-shrink: 0;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}
.wa-texto { white-space: nowrap; }

/* En pantalla estrecha se queda solo el icono: un rótulo ahí compite
   con el control del carrusel y con el propio contenido. El aria-label
   sigue diciendo qué es, así que no se pierde nada para quien no ve. */
@media (max-width: 600px) {
  .wa-burbuja { right: 16px; bottom: 16px; height: 46px; width: 46px; padding: 0; justify-content: center; }
  .wa-texto { display: none; }
}

/* Quien pidió menos movimiento no recibe el salto al pasar el puntero. */
@media (prefers-reduced-motion: reduce) {
  .wa-burbuja:hover { transform: none; }
}

/* ── El header sobre secciones de distinto tono ────────────────────
   Una página puede ser de dos colores —banda blanca arriba, vídeo
   oscuro debajo— y el header, que va fijo, pasa por encima de las dos.
   Con tinta blanca desaparece sobre lo blanco; con tinta oscura
   desaparece sobre el vídeo.

   La solución no es cambiar el tema de la PÁGINA, que repintaría el
   fondo entero al hacer scroll: es redefinir las variables del nav
   sobre el propio .hd. Como las consume él y sus descendientes, basta
   con ponerlas ahí para que ganen a las heredadas.

   Quién manda lo decide js/header-tema.js mirando qué sección queda
   detrás. Una sección lo declara con data-header="claro|oscuro". */
.hd.es-claro {
  --nav-tinta:     rgba(26,23,20,0.55);
  --nav-tinta-on:  #14110f;
  --nav-hover:     rgba(26,23,20,0.06);
  --hd-velo-rgb:   255 255 255;   /* el velo se invierte: blanco sobre claro */
  --hd-pastilla:       #14110f;
  --hd-pastilla-tinta: #f5f3ee;
}
.hd.es-oscuro {
  --nav-tinta:     rgba(255,255,255,0.55);
  --nav-tinta-on:  #ffffff;
  --nav-hover:     rgba(255,255,255,0.07);
  --hd-velo-rgb:   0 0 0;
  --hd-pastilla:       #ffffff;
  --hd-pastilla-tinta: #14110f;
}
/* El cambio se acompaña, no se salta de golpe. */
.hd, .hd .hd-link, .hd .hd-logo, .hd .hd-icono {
  transition: color .45s ease;
}

/* ── Páginas de fondo blanco a sangre ──────────────────────────────
   El tema claro pinta el cuerpo de #fafafa. Una página cuyo contenido
   va sobre blanco —la de cada obra— dejaba asomar dos franjas grises a
   los lados del contenedor en pantalla ancha. Con esto el blanco llega
   de filo a filo. */
body.cuerpo-blanco { background: #fff; }


/* ── Vuelta arriba (partials/footer.php con $subir) ─────────────────
   Compartido: lo piden el blog y el portafolio, y lo pedirá cualquier
   página larga. Vivía dentro de blog.css y se estaba a punto de copiar
   tal cual en portfolio.css. */
.subir {
  position: fixed;
  right: 22px; bottom: 22px;
  z-index: 90;
  width: 54px; height: 54px;
  border: 0; border-radius: 50%;
  /* En TINTA, no en el naranja de acento. Era un círculo naranja fijo
     en la esquina, y en /portfolio quedaba justo al lado de la barra de
     filtros —una píldora oscura— peleándose con ella. El acento del
     sitio sirve para señalar UNA cosa por pantalla; un control que está
     siempre puesto no es esa cosa.

     Sale de las variables del tema y no de un negro escrito a mano: en
     tema claro es tinta sobre fondo claro, y en oscuro se invierte solo.
     Hoy las nueve páginas que lo llevan son claras, pero el día que una
     oscura lo pida no hará falta acordarse de esto. */
  background: var(--tinta-alta);
  color: var(--bg);
  cursor: pointer;
  display: grid; place-items: center;
  /* La sombra también deja de ser naranja: con un botón oscuro, un halo
     de color se lee como un error de impresión. */
  box-shadow: 0 8px 22px rgba(10, 10, 14, .28);
  /* Entra desde abajo en vez de aparecer de golpe: un botón que se
     materializa a media pantalla se lee como un error de la página. */
  opacity: 0; transform: translateY(14px);
  transition: opacity .35s ease, transform .35s cubic-bezier(.16,1,.3,1);
}
.subir.esta-visible { opacity: 1; transform: none; }

/* IMPRESCINDIBLE. display:grid de arriba tiene la misma especificidad
   que el [hidden]{display:none} del navegador y llega después, así que
   gana: sin esta línea el botón sigue ocupando su esquina aunque esté
   oculto, transparente pero capturando el puntero. Es exactamente el
   fallo que dejó el buscador tapando el header. */
.subir[hidden] { display: none; }

.subir svg {
  width: 21px; height: 21px;
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}


/* ── Titulares enmascarados (js/titulos.js) ─────────────────────────
   El texto sube desde detrás de un borde invisible en vez de fundirse.
   js/titulos.js parte el titular en palabras y mete cada una en su
   .tl-mascara; aquí solo se define el movimiento.

   El disparo es el mismo .reveal de arriba: estos titulares llevan las
   dos clases, así que el observador que ya existe les pone .visible al
   entrar en pantalla. No hay un segundo observador. */

/* El fundido de .reveal se anula: si el titular entero se atenuara, el
   recorte de cada palabra no se vería. Aquí lo que aparece son las
   palabras, no el bloque. */
.titulo-anim.reveal { opacity: 1; transform: none; }

.tl-mascara {
  display: inline-block;
  overflow: hidden;
  /* Los rasgos que bajan de la línea base —la g, la j, la p— caen fuera
     de la caja y el recorte los cortaría. Se le da aire abajo y se le
     quita por fuera con el margen, así que ocupa lo mismo que antes. */
  padding-bottom: .14em;
  margin-bottom: -.14em;
  vertical-align: bottom;
}

.tl-palabra {
  display: inline-block;
  /* 115% y no 100%: con el relleno de arriba, al 100% asomaría por abajo
     la punta de las letras con descendente. */
  transform: translateY(115%);
  transition: transform .95s cubic-bezier(.16, 1, .3, 1);
  /* Escalonadas. 45 ms es corto a propósito: el titular tiene que leerse
     como una frase que entra, no como palabras sueltas cayendo una a
     una. Con seis palabras, la última llega 225 ms después de la
     primera —se nota el barrido y no la espera. */
  transition-delay: calc(var(--i, 0) * 45ms);
}

.titulo-anim.visible .tl-palabra { transform: none; }

@media (prefers-reduced-motion: reduce) {
  /* js/titulos.js ni siquiera parte el texto, pero por si el titular ya
     venía partido de una carga anterior. */
  .tl-palabra { transform: none; transition: none; }
}
