/* =========================================================
   ProWideWeb — "la portada que te gusta, con otra ejecución"
   Oscuro espacial, marca en dos líneas con la red de partículas
   dentro, portafolio de capturas enmarcadas. Ecos deliberados
   del referente (hero teatral, máquina de escribir, tarjetas
   que se levantan) con técnica y paleta propias: una escala de
   azul medianoche sobre negro, Poppins 800 + Fjalla One.
   ========================================================= */

:root {
  --bg:      #131313;
  --bg-2:    #181818;
  --panel:   #1E1E1E;
  --ink:     #F2F5F4;
  --dim:     #969B97;
  --frame:   #D7DBD9;

  /* La familia del azul medianoche del fondo, en tres luminancias.
     El tono es el mismo en las tres (~226°, el de las esquinas del
     hero); lo único que cambia es cuánta luz tienen. Hace falta la
     escala porque un mismo azul no puede ser a la vez texto sobre
     negro y relleno a pantalla completa. */
  --blue-deep: #16255C;   /* profundo: rellenos grandes y sombras */
  --blue-mid:  #3E5AC4;   /* medio: cortinas y ecos del glitch */
  --accent:    #8098E8;   /* claro: texto, filetes e iconos (7:1 sobre el fondo) */
  --on-accent: #060A18;   /* lo que se escribe encima del acento claro */

  /* Poppins para todo el texto (la geometría redonda del referente);
     Fjalla One, condensada, para etiquetas pequeñas en versal. */
  --sans: 'Poppins', system-ui, -apple-system, sans-serif;
  --mono: 'Fjalla One', 'Arial Narrow', sans-serif;

  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --container: 1240px;
  --pad: clamp(1.25rem, 4vw, 3.5rem);
}

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

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
  /* Reserva el hueco de la scrollbar: al abrir el menú (body.locked)
     la barra desaparece y sin esto todo saltaría ~15px en Windows. */
  scrollbar-gutter: stable;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: clamp(0.9375rem, 0.9rem + 0.2vw, 1.0625rem);
  font-weight: 300;
  line-height: 1.65;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}
body.locked { overflow: hidden; }

h1, h2, h3 { margin: 0; }
p { margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }
a { color: inherit; text-decoration: none; }
img { display: block; max-width: 100%; height: auto; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
address { font-style: normal; }

::selection { background: var(--accent); color: var(--on-accent); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }

.container {
  width: 100%;
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 var(--pad);
}

/* ---------------------------------------------------------
   BARRA
   --------------------------------------------------------- */
.navbar {
  position: fixed;
  top: 0; left: 0;
  width: 100%;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: clamp(1rem, 2.5vw, 2rem);
  padding: clamp(0.9rem, 2.4vh, 1.5rem) clamp(1.25rem, 4vw, 3.75rem);
  /* La franja es transparente: los clics deben atravesarla y llegar
     al contenido; sólo sus hijos son interactivos. */
  pointer-events: none;
  transition: transform .5s var(--ease), visibility 0s;
}
.navbar > * { pointer-events: auto; }
/* visibility con retardo: mientras está fuera de pantalla tampoco es
   tabulable, así el foco de teclado no desaparece en el vacío. */
.navbar.hide {
  transform: translateY(-110%);
  visibility: hidden;
  transition: transform .5s var(--ease), visibility 0s .5s;
}

/* La marca son dos piezas, no una imagen: el globo (bitmap con alfa,
   recortado del master de assets/logo/) y la palabra en texto real.
   Iban juntas dentro de logo.svg, pero un SVG cargado como <img> se
   renderiza aislado —sin acceso a las fuentes web de la página—, así
   que aquel "Poppins" del <text> caía siempre a Arial. En texto de
   verdad sí es Poppins, y además escala nítido y no viaja en píxeles. */
.logo { margin-right: auto; flex: none; }
.logo,
.footer-logo {
  display: inline-flex;
  align-items: center;
  gap: 0.55em;
  text-decoration: none;
  font-family: var(--sans);
  font-weight: 800;
  letter-spacing: 0.3px;
  line-height: 1;
  color: var(--ink);
  /* Un solo mando: manda el tamaño de fuente y el globo va en em, así que
     encogen a la vez. Hace falta porque el logo no puede encoger como
     ítem flex (flex: none): la palabra lleva nowrap, no menguaría con el
     globo, y a 320px se salía de su caja y pisaba el botón de sonido.
     El tramo va de 22px a 16px entre 370px y 320px de ancho; por encima
     de 370px cabe entero y se queda quieto. */
  font-size: clamp(1rem, 12vw - 1.4rem, 1.375rem);
}
.logo-mark { height: 1.91em; width: auto; flex: none; }  /* 42/22 del original */
.logo-word { white-space: nowrap; }
.logo-word > span { color: var(--accent); }

.email-us { font-size: 0.8125rem; color: var(--dim); }
.email-us a {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color .25s;
}
.email-us a:hover { color: var(--accent); }

/* Selector de idioma. En la condensada de las etiquetas pequeñas, para
   que pese lo mismo que el "MENU" de al lado y no compita con la marca.
   Sobrevive al breakpoint de 760px donde .email-us se esconde: es la
   única forma de cambiar de idioma, y en el móvil hace más falta. */
.lang-switch {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--mono);
  font-size: 0.8125rem;
  letter-spacing: 0.08em;
}
.lang-switch a {
  color: var(--dim);
  text-decoration: none;
  transition: color .25s;
}
.lang-switch a:hover { color: var(--accent); }
/* El idioma en curso no es un enlace útil: se marca y se apaga el cursor
   de mano, pero se deja enfocable para no romper el orden de tabulación. */
.lang-switch a[aria-current="page"] {
  color: var(--ink);
  cursor: default;
}
.lang-switch a + a { border-left: 1px solid rgba(242, 245, 244, 0.22); padding-left: 8px; }

/* La copia del menú sólo existe para el móvil; en escritorio manda la de
   la barra. Van las dos en el HTML porque CSS no puede mover un elemento
   de un padre a otro, y el manejador de la cookie coge las dos con un
   único querySelectorAll('.lang-switch a'). */
.overlay .lang-switch {
  display: none;
  margin-top: 1.5rem;
  font-size: 0.9375rem;
}

/* Botón de sonido: un sonar. Los anillos sólo emiten si está activo. */
.sound-btn {
  /* Sin flex:none el círculo se aplasta —lo vimos a 375px, un óvalo de
     12px— porque width en un ítem flex es sólo la base, no un suelo. */
  flex: none;
  width: 40px; height: 40px;
  border: 2px solid rgba(242, 245, 244, 0.18);
  border-radius: 50%;
  display: grid;
  place-items: center;
  transition: border-color .25s;
}
.sound-btn:hover { border-color: var(--accent); }
.sonar { position: relative; width: 8px; height: 8px; display: block; }
.sonar i {
  position: absolute; inset: 0;
  border-radius: 50%;
  background: var(--ink);
  transition: background-color .25s;
}
.sonar::before, .sonar::after {
  content: "";
  position: absolute;
  inset: -7px;
  border-radius: 50%;
  border: 1px solid var(--accent);
  opacity: 0;
}
.sound-btn[aria-pressed="true"] .sonar::before { animation: sonar 2.2s ease-out infinite; }
.sound-btn[aria-pressed="true"] .sonar::after { animation: sonar 2.2s ease-out 1.1s infinite; }
.sound-btn[aria-pressed="true"] .sonar i { background: var(--accent); }
.sound-btn[aria-pressed="false"] .sonar i { background: var(--dim); }
@keyframes sonar {
  0%   { transform: scale(0.4); opacity: .9; }
  100% { transform: scale(2.4); opacity: 0; }
}

.menu-group { display: flex; align-items: center; gap: 12px; }
.menu-group b {
  font-family: var(--mono);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.14em;
}

.burger {
  width: 44px; height: 44px;
  border: 2px solid rgba(242, 245, 244, 0.18);
  border-radius: 50%;
  position: relative;
  will-change: transform;
  transition: border-color .25s, background-color .25s;
}
.burger span {
  position: absolute;
  left: 12px;
  width: 18px; height: 2px;
  background: var(--ink);
  transition: .3s ease-in-out;
}
.burger span:nth-child(1) { top: 16px; }
.burger span:nth-child(2) { top: 24px; }
.burger.magnet { border-color: rgba(242, 245, 244, 0.5); }
.burger[aria-expanded="true"] { background: var(--ink); border-color: var(--ink); }
.burger[aria-expanded="true"] span { background: var(--bg); }
.burger[aria-expanded="true"] span:nth-child(1) { top: 20px; transform: rotate(135deg); }
.burger[aria-expanded="true"] span:nth-child(2) { top: 20px; transform: rotate(-135deg); }

/* ---------------------------------------------------------
   MENÚ OVERLAY — dos cortinas que caen desde arriba
   --------------------------------------------------------- */
.overlay {
  position: fixed;
  inset: 0;
  z-index: 20;
  visibility: hidden;
  overflow: hidden;
  transition: visibility 0s .9s;
}
.overlay.open { visibility: visible; transition-delay: 0s; }

.curtain {
  position: absolute;
  inset: 0;
  transform: translateY(-101%);
  transition: transform .55s var(--ease);
}
/* La cortina que asoma primero va en el azul medio: a pantalla
   completa, el claro deslumbraría y el profundo no se notaría. */
.c1 { background: var(--blue-mid); z-index: 1; transition-delay: .12s; }
.c2 { background: #0B1116; z-index: 2; transition-delay: 0s; }
.overlay.open .c1 { transform: translateY(0); transition-delay: 0s; }
.overlay.open .c2 { transform: translateY(0); transition-delay: .12s; }

.overlay-inner {
  position: relative;
  z-index: 3;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: clamp(2rem, 6vh, 3.5rem);
  opacity: 0;
  transform: translateY(16px);
  transition: opacity .4s var(--ease), transform .4s var(--ease);
}
.overlay.open .overlay-inner { opacity: 1; transform: none; transition-delay: .42s; }

.overlay-menu { text-align: center; }
.overlay-menu li + li { margin-top: .4rem; }
.overlay-menu a {
  display: inline-flex;
  align-items: baseline;
  gap: 1rem;
  font-size: clamp(2rem, 6vw, 3.75rem);
  font-weight: 800;
  letter-spacing: -0.02em;
  position: relative;
  padding: 0 .35em;
}
.overlay-menu a em {
  font-family: var(--mono);
  font-style: normal;
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--accent);
}
/* La barra que sube por detrás del enlace, herencia del referente
   pero en vertical y en azul medianoche. */
.overlay-menu a::before {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: .08em;
  height: .18em;
  background: var(--accent);
  z-index: -1;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .35s var(--ease);
}
.overlay-menu a:hover::before { transform: scaleX(1); }

.overlay-mail a {
  font-family: var(--sans);
  font-weight: 300;
  font-size: 1.0625rem;
  color: var(--dim);
  border-bottom: 1px solid rgba(242, 245, 244, 0.25);
  padding-bottom: 2px;
  transition: color .25s, border-color .25s;
}
.overlay-mail a:hover { color: var(--accent); border-color: var(--accent); }

/* ---------------------------------------------------------
   PORTADA — fija al fondo; el contenido se desliza encima
   --------------------------------------------------------- */
/* ---------------------------------------------------------
   EL FONDO — negro en el centro, azul medianoche en los bordes

   Tres capas apiladas, todas en CSS y sin una línea de JS:
     1. .hero      el lienzo: negro tintado que se azula al salir
                   del centro, con las esquinas más profundas.
     2. .hero::after  el grano. Imprescindible: un degradado oscuro
                   de tan poco contraste se ve a bandas en cuanto la
                   pantalla no es de 10 bits; el ruido las disuelve.
     3. .aurora    una respiración lentísima del azul, para que el
                   fondo esté vivo sin llamar la atención.
   --------------------------------------------------------- */
.hero {
  position: fixed;
  inset: 0;
  height: 100vh;
  height: 100svh;
  z-index: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* El fondo es NEGRO con un matiz azul, no un azul medianoche. La
     diferencia se mide en la separación entre el canal azul y el rojo:
     antes las esquinas llegaban a 64 puntos —eso ya se lee como azul—
     y ahora se quedan en torno a 20, que es matiz.

     Se baja en las tres capas a la vez, porque se suman: si sólo tocas
     una, las otras dos vuelven a teñir. La cuarta es .aurora, que va
     encima y lleva su propia rebaja. */
  background:
    /* Las cuatro esquinas: donde el azul cuaja más. */
    radial-gradient(ellipse 52% 58% at 0% 0%,   rgba(24, 46, 104, 0.17), transparent 68%),
    radial-gradient(ellipse 52% 58% at 100% 0%, rgba(20, 40, 96, 0.15), transparent 68%),
    radial-gradient(ellipse 58% 62% at 0% 100%, rgba(17, 34, 84, 0.14), transparent 70%),
    radial-gradient(ellipse 58% 62% at 100% 100%, rgba(22, 42, 100, 0.16), transparent 70%),
    /* El halo general: el centro queda limpio y el azul crece
       hacia fuera de forma continua. */
    radial-gradient(ellipse 78% 86% at 50% 48%, #07080B 38%, #08090F 70%, #0B1020 100%),
    #07080B;
  overflow: hidden;
  /* La coreografía del scroll (main.js) transforma el hero entero:
     encoge, redondea y enmarca. El overflow:hidden recorta el
     contenido a las esquinas redondeadas. */
  transform-origin: 50% 50%;
  will-change: transform;
}

/* La leyenda del cuadro: aparece bajo la portada encogida, con el
   mismo lenguaje que las tarjetas del portafolio (nombre + dominio).
   Su posición vertical y su opacidad las gobierna main.js. */
.hero-caption {
  position: fixed;
  left: 50%;
  top: 100vh;
  transform: translateX(-50%);
  z-index: 1;
  margin: 0;
  text-align: center;
  opacity: 0;
  pointer-events: none;
}
.hero-caption .hc-name {
  display: block;
  font-weight: 600;
  font-size: 1.0625rem;
  color: var(--ink);
}
.hero-caption .hc-domain {
  font-weight: 400;
  font-size: 0.875rem;
  color: var(--accent);
}

/* Grano. Sin él, todo lo de arriba se ve a bandas.
   Va en su propia capa y ANTES del contenido en el DOM: así queda
   sobre el fondo pero debajo del título. No se le puede dar z-index
   al contenido para adelantarlo, porque eso crearía un contexto de
   apilamiento que aislaría el mix-blend-mode del canvas del título. */
.grain-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.5;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)' opacity='0.42'/%3E%3C/svg%3E");
}

/* Respiración: el azul de los bordes sube y baja muy despacio, y
   se desplaza un pelín. Nadie lo mira, todos lo notan. */
.aurora {
  position: absolute;
  inset: -12%;
  pointer-events: none;
  background:
    radial-gradient(ellipse 46% 40% at 12% 22%, rgba(38, 66, 150, 0.09), transparent 72%),
    radial-gradient(ellipse 42% 38% at 88% 78%, rgba(28, 52, 128, 0.075), transparent 72%);
  filter: blur(28px);
  animation: aurora-breathe 24s ease-in-out infinite alternate;
  will-change: transform, opacity;
}
@keyframes aurora-breathe {
  from { opacity: 0.55; transform: translate3d(-1.2%, -0.8%, 0) scale(1); }
  to   { opacity: 1;    transform: translate3d(1.4%, 1%, 0) scale(1.08); }
}

/* Sin z-index a propósito: crearía un contexto de apilamiento que
   aislaría el mix-blend-mode del canvas del título, y la red dejaría
   de recortarse contra las letras. El orden del DOM ya lo pinta
   encima de #stars. */
.mast {
  position: relative;
  width: 100%;
  max-width: 1380px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  text-align: left;
  gap: clamp(1.25rem, 3.5vh, 2.25rem);
  padding: 0 var(--pad);
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: .65rem;
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--dim);
}
.pulse {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--accent);
  animation: pulse 2.4s ease-out infinite;
}
@keyframes pulse {
  0%   { box-shadow: 0 0 0 0 rgba(59, 201, 255, 0.55); }
  70%  { box-shadow: 0 0 0 12px rgba(59, 201, 255, 0); }
  100% { box-shadow: 0 0 0 0 rgba(59, 201, 255, 0); }
}

/* ---------- El título: dos líneas apiladas, sólida + hueca ---------- */
.mast-title { margin: 0; line-height: 1; }

.masker {
  position: relative;
  display: block;
  /* La aberración cromática la escribe JS aquí según el ratón. */
  --ab-x: 0;
  --ab-y: 0;
}

.t-line {
  display: block;
  position: relative;
  font-size: clamp(3.4rem, min(21vw, 28vh), 20rem);
  font-weight: 800;
  line-height: 0.88;
  letter-spacing: -0.04em;
}
.t-solid { color: var(--ink); }
/* La segunda línea es hueca: sólo el contorno. */
.t-hollow {
  color: transparent;
  -webkit-text-stroke: 2px var(--ink);
}

/* Cursor de terminal al final de la marca. */
.t-caret {
  display: inline-block;
  width: 0.12em;
  height: 0.68em;
  margin-left: 0.07em;
  background: var(--accent);
  -webkit-text-stroke: 0;
  animation: blink 1s steps(1) infinite;
}

/* Aberración cromática: dos ecos de color detrás de cada línea que
   se desplazan siguiendo al ratón. Nada de bucles de recorte: el
   color sólo se mueve si el visitante se mueve. */
.t-line::before,
.t-line::after {
  content: attr(data-text);
  position: absolute;
  left: 0;
  top: 0;
  z-index: -1;
  pointer-events: none;
  -webkit-text-stroke: 0;
}
.t-hollow::before,
.t-hollow::after { -webkit-text-stroke: 2px transparent; }
.t-line::before {
  color: var(--blue-mid);
  opacity: 0.5;
  transform: translate(calc(var(--ab-x) * 1px), calc(var(--ab-y) * 1px));
}
.t-line::after {
  color: var(--accent);
  opacity: 0.45;
  transform: translate(calc(var(--ab-x) * -1px), calc(var(--ab-y) * -1px));
}
.t-hollow::before { color: transparent; -webkit-text-stroke: 2px var(--blue-mid); }
.t-hollow::after { color: transparent; -webkit-text-stroke: 2px var(--accent); }

/* Ráfaga ocasional: durante ~200ms los ecos se cortan en franjas.
   Un espasmo puntual, no un tic constante. */
.masker.burst .t-line::before { animation: burst-a 0.22s steps(3) 1; }
.masker.burst .t-line::after { animation: burst-b 0.22s steps(3) 1; }
@keyframes burst-a {
  0%   { clip-path: inset(65% 0 10% 0); transform: translate(6px, -2px); }
  50%  { clip-path: inset(15% 0 70% 0); transform: translate(-5px, 2px); }
  100% { clip-path: inset(40% 0 35% 0); transform: translate(4px, 0); }
}
@keyframes burst-b {
  0%   { clip-path: inset(10% 0 72% 0); transform: translate(-6px, 2px); }
  50%  { clip-path: inset(58% 0 18% 0); transform: translate(5px, -2px); }
  100% { clip-path: inset(28% 0 48% 0); transform: translate(-4px, 0); }
}

/* La red de partículas, recortada a las letras: en modo darken sólo
   asoma sobre el blanco del texto y desaparece contra el fondo. */
#title-net {
  position: absolute;
  inset: 0;
  /* Imprescindible en un replaced element: con inset:0 a secas un
     canvas NO se estira — su width/height auto es el tamaño
     intrínseco (300×150) y la red quedaría en una esquina. */
  width: 100%;
  height: 100%;
  mix-blend-mode: darken;
  pointer-events: none;
}

/* Fila inferior del hero: el subtítulo tecleado a la izquierda y el
   botón a la derecha, sobre un filete. Composición asimétrica. */
.mast-foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: clamp(1rem, 3vw, 2.5rem);
  border-top: 1px solid rgba(242, 245, 244, 0.14);
  padding-top: clamp(1rem, 2.6vh, 1.6rem);
}

.type-line {
  font-family: var(--sans);
  font-weight: 300;
  font-size: clamp(1.0625rem, 1.9vw, 1.375rem);
  color: var(--ink);
  opacity: 0.88;
  min-height: 1.8em;
  margin: 0;
}
/* En pantallas estrechas la frase más larga parte en dos líneas:
   se reserva el alto de ambas para que el hero no bote al teclear. */
@media (max-width: 480px) {
  .type-line { min-height: 3.6em; }
}
.type-prefix { color: var(--accent); margin-right: .5em; }
#typewriter { color: inherit; }
.caret {
  font-style: normal;
  color: var(--accent);
  animation: blink .85s steps(1) infinite;
}
@keyframes blink { 50% { opacity: 0; } }

.hero-btn {
  height: 56px;
  line-height: 52px;
  display: inline-block;
  border: 2px solid rgba(242, 245, 244, 0.4);
  color: var(--ink);
  padding: 0 44px;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  border-radius: 100px;
  transition: .3s var(--ease);
}
.hero-btn:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
  box-shadow: 0 12px 40px -12px rgba(59, 201, 255, 0.5);
}

/* Entrada escalonada de la portada */
.js .reveal-hero { opacity: 0; transform: translateY(18px); }
.js .reveal-hero.in {
  opacity: 1;
  transform: none;
  transition: opacity .9s var(--ease), transform .9s var(--ease);
}

.social {
  position: absolute;
  right: clamp(1.25rem, 3.5vw, 3.25rem);
  top: 50%;
  transform: translateY(-50%);
  z-index: 3;
  display: grid;
  gap: 10px;
}
.social a {
  width: 34px; height: 34px;
  display: grid;
  place-items: center;
  color: var(--dim);
  border: 1px solid rgba(242, 245, 244, 0.12);
  border-radius: 50%;
  transition: .25s;
}
.social a:hover {
  color: var(--on-accent);
  background: var(--accent);
  border-color: var(--accent);
  transform: translateX(-3px);
}

.scroll-cue {
  position: absolute;
  right: clamp(1.4rem, 3.7vw, 3.45rem);
  bottom: 24px;
  z-index: 3;
  width: 1px;
}
.scroll-cue b {
  display: block;
  width: 110px;
  font-family: var(--mono);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.2em;
  color: var(--dim);
  transform: rotate(90deg);
  transform-origin: left;
  margin-left: -6px;
}
/* Un "cometa": raíl tenue y un tramo de luz que cae por dentro.
   (Nada de la animación de estiramiento del referente.) */
.scroll-cue span {
  display: block;
  position: relative;
  height: 70px;
  margin-top: 66px;
  overflow: hidden;
  background: rgba(242, 245, 244, 0.12);
}
.scroll-cue span::before {
  content: "";
  position: absolute;
  left: 0; top: 0;
  width: 1px; height: 45%;
  background: linear-gradient(to bottom, transparent, var(--accent));
  animation: comet 1.7s ease-in-out infinite;
}
@keyframes comet {
  0%   { transform: translateY(-110%); }
  60%, 100% { transform: translateY(240%); }
}

/* ---------------------------------------------------------
   CONTENIDO — se desliza sobre la portada
   --------------------------------------------------------- */
/* 165vh y no 100: el margen extra es la "pista" donde se rueda la
   coreografía del scroll — la portada se encoge hasta quedar como un
   cuadro enmarcado (primer tramo) y el portafolio sube y la cubre
   (segundo tramo). Con movimiento reducido vuelve a 100. */
main {
  position: relative;
  z-index: 2;
  margin-top: 165vh;
  margin-top: 165svh;
}

.section { padding: clamp(5rem, 12vh, 8.5rem) 0; }

.portfolio {
  background: var(--bg-2);
  border-top: 1px solid rgba(242, 245, 244, 0.06);
}

/* El filete que le da un borde definido a la lámina que sube y tapa el
   cuadro de la portada. Lo llevaba el portafolio por ser la primera
   sección; ahora se lo queda la primera que haya, sea cual sea. */
main > .section:first-child {
  border-top: 1px solid rgba(242, 245, 244, 0.06);
}

.section-head { text-align: center; margin-bottom: clamp(2.5rem, 7vh, 4.5rem); }
.section-head h2 {
  font-size: clamp(1.75rem, 4.2vw, 3rem);
  font-weight: 800;
  letter-spacing: -0.01em;
}
.section-head p { color: var(--dim); margin-top: .8rem; }

/* ---------- La rejilla de capturas ---------- */
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(2rem, 4.5vw, 3.5rem) clamp(1.5rem, 3vw, 2.25rem);
}

.project { text-align: center; will-change: transform; }

/* El marco claro alrededor de la captura: el guiño más directo al
   referente, con levitación y halo azul propios. */
.shot {
  display: block;
  border: 6px solid var(--frame);
  border-radius: 2px;
  overflow: hidden;
  background: #000;
  transition: transform .45s var(--ease), border-color .45s var(--ease), box-shadow .45s var(--ease);
}
.shot img { width: 100%; transition: transform .6s var(--ease); }
.project:hover .shot {
  transform: translateY(-10px);
  border-color: var(--accent);
  box-shadow: 0 32px 60px -28px rgba(59, 201, 255, 0.35);
}
.project:hover .shot img { transform: scale(1.045); }

.project h3 {
  font-size: 1.0625rem;
  font-weight: 600;
  margin: 1.1rem 0 .3rem;
}
.domain {
  font-family: var(--sans);
  font-weight: 400;
  font-size: 0.875rem;
  color: var(--accent);
  position: relative;
  padding-right: 0;
  transition: padding-right .3s var(--ease);
}
.domain::before {
  content: "";
  position: absolute;
  left: 0; bottom: -3px;
  width: 0; height: 1px;
  background: var(--accent);
  transition: width .3s var(--ease);
}
.domain::after {
  content: "↗";
  position: absolute;
  right: -2px;
  opacity: 0;
  transform: translate(-6px, 4px);
  transition: .3s var(--ease);
}
.project:hover .domain { padding-right: 18px; }
.project:hover .domain::before { width: 100%; }
.project:hover .domain::after { opacity: 1; transform: translate(0, 0); }

/* ---------- Servicios ---------- */
.services { background: var(--bg); }

.svc-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(2rem, 4.5vw, 3.5rem) clamp(1.5rem, 3vw, 2.5rem);
}
.svc {
  border-top: 1px solid rgba(242, 245, 244, 0.1);
  padding-top: 1.5rem;
}
.svc-icon {
  display: inline-block;
  color: var(--accent);
  margin-bottom: 1rem;
  transition: transform .45s var(--ease);
}
.svc:hover .svc-icon { transform: translateY(-5px) rotate(-5deg); }
.svc h3 { font-size: 1rem; font-weight: 600; margin-bottom: .6rem; }
.svc p { font-size: 0.875rem; color: var(--dim); }


/* ---------- Llamada ---------- */
.cta {
  background: var(--bg-2);
  text-align: center;
  position: relative;
}
/* El filete superior que respira color: eco del bloque camaleón del
   referente, reducido a una línea. */
.cta::before {
  content: "";
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 2px;
  background: linear-gradient(90deg, var(--accent), var(--blue-mid), var(--blue-deep), var(--accent));
  background-size: 300% 100%;
  animation: hueflow 9s linear infinite;
}
@keyframes hueflow { to { background-position: 300% 0; } }

.cta-kicker { font-family: var(--mono); font-size: 0.8125rem; color: var(--dim); letter-spacing: 0.08em; }
.cta-title {
  font-size: clamp(2rem, 5.5vw, 4rem);
  font-weight: 800;
  letter-spacing: -0.02em;
  margin: 1rem 0 2.4rem;
}
.cta-title em { font-style: normal; color: var(--accent); }
.cta-btn {
  display: inline-block;
  height: 60px;
  line-height: 60px;
  padding: 0 52px;
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 600;
  font-size: 0.9375rem;
  border-radius: 100px;
  will-change: transform;
  transition: box-shadow .3s;
}
.cta-btn:hover { box-shadow: 0 16px 50px -16px rgba(59, 201, 255, 0.6); }
.cta-note { margin-top: 1.6rem; font-size: 0.8125rem; color: var(--dim); }

/* ---------- Pie ---------- */
.footer {
  position: relative;
  z-index: 2;
  background: #0D0D0D;
  text-align: center;
  padding: clamp(3.5rem, 9vh, 5.5rem) 0 2.5rem;
}
/* Va centrado por el text-align: center del pie — es inline-flex, así que
   los márgenes auto no pintan nada aquí; el que importa es el de abajo.
   Tamaño fijo: en el pie hay ancho de sobra y no necesita el clamp. */
.footer-logo { margin-bottom: 1.6rem; font-size: 1.25rem; }
.footer-mail a {
  font-size: clamp(1.25rem, 3vw, 1.75rem);
  font-weight: 600;
  color: var(--ink);
  transition: color .25s;
}
.footer-mail a:hover { color: var(--accent); }
.footer-legal { margin: 1.8rem 0 .8rem; font-size: 0.8125rem; color: var(--dim); }
.footer-legal a { margin: 0 10px; transition: color .25s; }
.footer-legal a:hover { color: var(--accent); }
/* Sin bajar la opacidad: el gris ya es discreto y con .7 el contraste
   caía a ~3.7:1, por debajo del mínimo para texto pequeño. */
.footer-copy { font-size: 0.75rem; color: var(--dim); }

/* ---------------------------------------------------------
   REVELADOS
   --------------------------------------------------------- */
.js .reveal { opacity: 0; transform: translateY(32px); }
.js .reveal.in {
  opacity: 1;
  transform: none;
  transition: opacity .8s var(--ease), transform .8s var(--ease);
}
/* Tilt e imán conviven con el reveal: en esos elementos la transición
   sólo anima opacity, porque su transform lo escribe JS cada frame y
   una transición (más su retardo de escalonado) lo volvería elástico. */
.js .reveal.in[data-tilt],
.js .reveal.in[data-magnet] { transition: opacity .8s var(--ease); }

/* ---------------------------------------------------------
   RESPONSIVE
   --------------------------------------------------------- */
@media (max-width: 1024px) {
  .grid { grid-template-columns: repeat(2, 1fr); }
  .svc-grid { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 760px) {
  .email-us { display: none; }
  .menu-group b { display: none; }
  /* Con el selector de idioma en la barra ya no caben cinco piezas a
     16px de separación: a 375px el burger se salía del margen. Se
     aprieta el hueco y encogen el sonar y el selector. */
  /* El selector se baja al menú. En la barra no cabía: la fila estaba
     ajustada al milímetro y a 375px dejaba el burger fuera del margen.
     Apretando todo cabía por 0px de holgura, que no es caber. */
  .navbar .lang-switch { display: none; }
  .overlay .lang-switch { display: flex; }
  .grid { grid-template-columns: 1fr; }
  .svc-grid { grid-template-columns: 1fr; }
  .social { display: none; }
  .scroll-cue { display: none; }
  /* 18.5vw y no más: "ProWide" a 23vw desbordaba su línea en 390px. */
  .t-line { font-size: clamp(2.6rem, 18.5vw, 5rem); }
  .t-hollow, .t-hollow::before, .t-hollow::after { -webkit-text-stroke-width: 1.5px; }
  .mast-foot { flex-direction: column; align-items: flex-start; gap: 1.25rem; }
}

/* ---------------------------------------------------------
   MOVIMIENTO REDUCIDO
   --------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    /* Sin esto, los desfases de las cortinas del menú seguirían
       vivos: 120ms de nada antes de que aparezca el contenido. */
    transition-delay: 0s !important;
  }
  .js .reveal, .js .reveal-hero { opacity: 1; transform: none; }
  /* Sin movimiento, fuera los ecos de color: título limpio. */
  .t-line::before, .t-line::after { display: none; }
  /* Y sin coreografía de scroll: pista normal y nada de leyenda. */
  main { margin-top: 100vh; margin-top: 100svh; }
  .hero-caption { display: none; }
}

/* =====================================================
   TRAVESÍA — vídeo dirigido por el scroll
   =====================================================
   El contenedor mide 190vh y dentro hay un sticky de 100vh. Ese
   recorrido de 90vh es el que "gasta" el vídeo, pero sólo en su primer
   70%: en el 30% restante el vídeo ya está en su último fotograma
   mientras el portafolio sube por encima.

   El portafolio además llega CON VENTAJA: cuando el vídeo toca su
   último fotograma, el titular ya está en el tercio bajo de la
   pantalla y el portátil asoma — no hay que scrollear más para que
   aparezca. Y puede solaparse tan pronto porque entra DESNUDO: su
   fondo (el ::before de .from-voyage) llega con opacidad 0 y durante
   la ventaja sólo flota su contenido sobre el vídeo vivo. El fondo lo
   funde scrollvideo.js (variable --fondo) en el último 7% del vídeo,
   cuando los fotogramas ya son el campo de estrellas quieto: se funde
   sobre un plano idéntico y no se ve.

   La versión anterior metía la sección CON su lámina estática mientras
   el vídeo aún iba por el estallido: mitad de pantalla en movimiento,
   mitad congelada, y la raya entre ambas — el "se ve como cortado" del
   vídeo de WhatsApp del 04-09.

   ⚠ Los números están acoplados: 218vh de sección, -100svh de margen
   en .from-voyage y TRAMO = 63/118 en scrollvideo.js. Las cuentas,
   en svh desde el inicio de la travesía (recorrido = 218 − 100 = 118):

     entra la sección   118 − 100 = 18
     congela el vídeo   118 × TRAMO = 63   (ventaja = 63 − 18 = 45)
     costura arriba     118 − 100 + 100 = 118 = fin del recorrido

   La última igualdad es la que no se puede romper: la costura llega al
   borde superior EXACTAMENTE cuando el sticky se despega. Si el margen
   es más corto que 100svh, entre el despegue y la costura el último
   fotograma se desliza hacia arriba mientras el fondo fijo se queda
   quieto, y los dos campos de estrellas patinan uno sobre otro.
   ===================================================== */

.voyage { height: 218vh; position: relative; }

.voyage-sticky {
  position: sticky;
  top: 0;
  height: 100svh;
  width: 100%;
  overflow: hidden;
  background: #000;
}

.voyage-poster,
.voyage-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  will-change: transform;
}

.voyage-video { opacity: 0; transition: opacity .6s ease; }
.voyage-video.on { opacity: 1; }
.voyage-poster.off { opacity: 0; transition: opacity .6s ease; }

.voyage-copy {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 0 var(--pad);
  will-change: transform, opacity;
}

.voyage-kicker {
  display: flex;
  align-items: center;
  gap: .6rem;
  margin: 0 0 1.4rem;
  font-family: var(--mono);
  font-size: clamp(.62rem, 1.4vw, .78rem);
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--accent);
}

.voyage-title {
  margin: 0;
  font-weight: 800;
  letter-spacing: -.035em;
  line-height: 1.04;
  font-size: clamp(1.9rem, 6vw, 4.2rem);
  color: var(--ink);
  /* Sin velo oscuro encima del vídeo: el plano ya es casi todo negro y
     un overlay sólo apagaría el azul. La sombra basta para el contraste
     en los fotogramas del estallido, que son los únicos claros. */
  text-shadow: 0 2px 70px rgba(0, 0, 0, .95);
}

.voyage-sub {
  margin: 1.3rem 0 0;
  max-width: 34ch;
  font-weight: 300;
  line-height: 1.6;
  font-size: clamp(.95rem, 2vw, 1.15rem);
  color: rgba(242, 245, 244, .72);
  text-shadow: 0 1px 40px rgba(0, 0, 0, 1);
}

/* Barra de progreso: se mueve con transform, no con width, para que la
   resuelva el compositor. */
.voyage-bar {
  position: absolute;
  left: 0; bottom: 0;
  width: 100%; height: 2px;
  background: rgba(242, 245, 244, .12);
  z-index: 3;
}
.voyage-bar i {
  display: block;
  height: 100%;
  width: 100%;
  transform: scaleX(0);
  transform-origin: left;
  background: var(--accent);
  will-change: transform;
}

/* ---- La sección que encadena con el vídeo ----
   Sube por encima del sticky arrastrando por delante un degradado que
   disuelve el vídeo en el color de la sección. Cuando llega, lo que hay
   encima ya es de su mismo tono: no hay línea que ver.

   El modificador no está atado a .cta a propósito: se lo queda la
   sección que vaya justo detrás de la travesía, sea cual sea. Hoy es el
   portafolio; antes era la llamada. */
.from-voyage {
  position: relative;
  margin-top: -100svh;   /* la igualdad del ⚠ de .voyage: ni un svh menos */
  z-index: 1;
  /* La base plana bajo todo el montaje. Nace transparente (durante la
     ventaja se ve el VÍDEO a través de la sección) y scrollvideo.js la
     enciende con la foto; a diferencia de la foto, nunca se vuelve a
     apagar: es lo que evita que asome el gris del body cuando la foto
     se disuelve y quedan solo las estrellas del canvas. El tono es el
     suelo de end.avif, muestreado: casi negro con un punto de azul. */
  background: rgb(2 4 10 / var(--fondo-base, 1));
}

/* El fondo no es "una imagen parecida al final del vídeo": ES el
   último fotograma, encuadrado exactamente igual que el vídeo.
   La clave es el "fixed": con attachment fijo el área de posicionado
   pasa a ser la ventana, así que "cover" se calcula contra el viewport
   igual que el object-fit: cover del <video>, y "center center" iguala
   su object-position por defecto. No hay dos fondos que empalmar: hay
   un solo plano que la sección continúa.

   Va en el ::before y no en la propia sección por la opacidad: entra
   a 0, scrollvideo.js lo funde (--fondo) sobre el vídeo ya congelado
   en este mismo fotograma, lo sostiene mientras el filo de la sección
   cruza la pantalla, y LO VUELVE A DISOLVER en cuanto el vídeo queda
   atrás: a partir de ahí el cielo son las estrellas vivas del canvas
   (.voyage-stars), que una foto quieta bajo contenido que scrollea
   parece un empapelado. El valor por defecto es 1: en las ramas sin
   rebobinado (móvil, equipo justo, reduced motion) nadie escribe la
   variable y la imagen se pinta normal desde el principio. */
.from-voyage::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -3;
  pointer-events: none;
  background: url("../media/end.avif") center center / cover no-repeat fixed;
  opacity: var(--fondo, 1);
}

/* Las estrellas vivas, entre la foto (-3) y el velo (-1): el velo las
   apaga hacia abajo igual que apagaba la foto, y el contenido pisa por
   encima de todo. El canvas cubre la sección entera. */
.voyage-stars {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.from-voyage::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  /* El velo arranca en opacidad CERO, y esa es toda la regla: cualquier
     valor distinto de 0 en el 0% dibuja un escalón justo en el borde
     superior, que es exactamente la raya que se veía. Antes empezaba en
     #050B1E opaco y los dos lados del corte se llevaban 27 niveles.

     A partir de ahí sí puede oscurecer: hace falta para que el texto
     tenga contraste sobre las partículas, y para aterrizar en el fondo
     de la página y enlazar con el pie. */
  background:
    linear-gradient(to bottom,
      rgba(5, 11, 30, 0) 0%,
      rgba(5, 11, 30, .30) 26%,
      rgba(5, 11, 30, .72) 62%,
      var(--bg-2) 100%);
}

.from-voyage > .container { position: relative; z-index: 1; }

/* ---- Móvil y accesibilidad ----
   En táctil el scroll-driven va mal y además el vídeo pesado no
   compensa: la sección se queda en una altura normal y el vídeo pasa a
   ser un bucle ligero (lo cambia scrollvideo.js). */
@media (max-width: 767px) {
  .voyage { height: 100svh; }
  .voyage-sticky { position: relative; }
  .voyage-bar { display: none; }
  /* iOS trata attachment:fixed como scroll y de paso lo pinta a tirones.
     En móvil la sección va pegada al vídeo sin solaparlo, así que no hay
     costura que disolver y el fondo puede desplazarse con normalidad. */
  .from-voyage::before { background-attachment: scroll; background-position: center top; }
  .from-voyage { margin-top: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .voyage { height: 100svh; }
  .voyage-sticky { position: relative; }
  .voyage-video { display: none; }
  .voyage-poster { opacity: 1 !important; }
  .voyage-bar { display: none; }
  .from-voyage { margin-top: 0; }
}

/* =====================================================
   TÍTULO DE PORTADA — versión neón
   =====================================================
   Recrea en texto la pieza de referencia: "ProWide" sólida con la red
   dentro, "Web" hueca en neón azul, el "www" pequeño encima y el
   destello que lo cruza.

   Se hizo con texto y no con el PNG por tres motivos, en este orden:
   el PNG traía fondo negro opaco y recortaba un rectángulo contra la
   aurora; pesaba 57 kB en AVIF frente a 0 del texto; y una imagen no
   reacciona al ratón, que es de lo que vive esta portada (red de
   partículas, aberración cromática y glitch).

   Todo cuelga de .neon en el h1: las reglas base de .t-line siguen
   intactas más arriba, así que quitar esa clase devuelve el título
   anterior tal cual.
   ===================================================== */

/* La medida base sube al contenedor para que el "www" y el destello
   puedan expresarse en em y escalen con el título sin repetir la
   fórmula del clamp en cada sitio. */
.neon .masker {
  font-size: clamp(3.4rem, min(21vw, 28vh), 20rem);
  isolation: isolate;
}
.neon .t-line { font-size: 1em; }

/* ---- "ProWide": blanca, con el halo azul alrededor ----
   text-shadow y no drop-shadow: el relleno es opaco, así que la sombra
   del glifo sirve, y encadenar cuatro sombras de texto es bastante más
   barato de pintar que cuatro filtros. */
.neon .t-solid {
  color: #fbfdff;
  /* Segunda bajada, otro ~25% (la primera fue del 18%). El halo grande
     es el que más pesa en la sensación de "demasiado brillo": cuanto más
     ancho el radio, más superficie tiñe, así que baja más que los
     cercanos, que son los que definen el borde del glifo y conviene
     conservar. */
  text-shadow:
    0 0 .035em rgba(190, 225, 255, .62),
    0 0 .085em rgba( 90, 165, 255, .45),
    0 0 .20em  rgba( 40, 110, 235, .31),
    0 0 .42em  rgba( 22,  70, 190, .20);
}

/* ---- "Web": hueca, tubo de neón ----
   Aquí SÍ drop-shadow: con el relleno transparente, text-shadow dibuja
   la sombra a partir de la silueta maciza del glifo y saldría una
   mancha rellena dentro de la letra hueca. drop-shadow trabaja sobre lo
   que de verdad se ha pintado —sólo el trazo—, que es lo que hace que
   parezca un tubo y no un bloque. */
.neon .t-hollow {
  color: transparent;
  -webkit-text-stroke: .022em #a9dcff;
  /* El tubo baja menos que la palabra sólida: aquí el resplandor no es
     decoración, es lo que hace que se lea como neón. Sin él queda un
     contorno fino y se pierde el efecto. */
  filter:
    drop-shadow(0 0 .02em rgba(200, 235, 255, .68))
    drop-shadow(0 0 .07em rgba( 80, 175, 255, .52))
    drop-shadow(0 0 .19em rgba( 35, 115, 240, .33));
}
.neon .t-hollow::before,
.neon .t-hollow::after { -webkit-text-stroke-width: .022em; }

/* El cursor no está en la pieza de referencia. La línea de máquina de
   escribir de abajo ya tiene el suyo, y dos cursores parpadeando a la
   vez compiten. Quitar esta regla lo devuelve. */
.neon .t-caret { display: none; }

/* ---- El "www" ----
   Posición en % (caja del .masker) y tamaño en em (medida del título):
   así el em resuelve contra el padre y no contra su propio font-size,
   que es la trampa clásica al mezclar las dos unidades en un elemento. */
.neon .t-www {
  position: absolute;
  left: 14%;
  top: 44.5%;
  z-index: 2;
  font-size: .44em;
  font-weight: 800;
  letter-spacing: -.02em;
  line-height: 1;
  color: #eef7ff;
  text-shadow:
    0 0 .05em rgba(190, 225, 255, .9),
    0 0 .16em rgba( 80, 165, 255, .7);
  pointer-events: none;
}

/* ---- El destello que cruza el "www" ----
   Una barra en degradado, girada. Se mueve con transform y opacity, así
   que la resuelve el compositor. */
.neon .t-streak {
  position: absolute;
  left: 5%;
  /* Calculado, no a ojo: con origen a la izquierda y -5deg, la linea
     tiene que pasar por el centro del "www" (57% de alto) justo en su
     centro horizontal. De ahi sale el 62%. Si mueves el www o cambias
     el angulo, este numero hay que recalcularlo o la tachada se va a
     cruzar por encima de ProWide. */
  top: 62%;
  width: 58%;
  height: .020em;
  z-index: 3;
  transform: rotate(-5deg);
  transform-origin: left center;
  background: linear-gradient(90deg,
    rgba(160, 215, 255, 0) 0%,
    rgba(190, 230, 255, .55) 18%,
    rgba(255, 255, 255, .95) 52%,
    rgba(190, 230, 255, .5) 82%,
    rgba(160, 215, 255, 0) 100%);
  filter: drop-shadow(0 0 .05em rgba(120, 195, 255, .9));
  pointer-events: none;
}

/* Cada pocos segundos lo recorre un pulso de luz: es el detalle que la
   imagen no podía tener. */
.neon .t-streak::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg,
    transparent 0%, rgba(255, 255, 255, .95) 50%, transparent 100%);
  transform: translateX(-100%);
  animation: streak-sweep 7s var(--ease) infinite;
}
@keyframes streak-sweep {
  0%, 62%  { transform: translateX(-100%); opacity: 0; }
  70%      { opacity: 1; }
  86%, 100%{ transform: translateX(100%); opacity: 0; }
}

@media (max-width: 700px) {
  .neon .t-www { font-size: .42em; left: 12%; }
  .neon .t-streak { width: 62%; left: 3%; top: 63%; }
}

@media (prefers-reduced-motion: reduce) {
  .neon .t-streak::after { animation: none; opacity: 0; }
}

/* =====================================================
   LOGO — hexagono de neon pww://
   =====================================================
   Bitmap con alfa, no vectorial: el halo del neon es un degradado
   fotografico y al vectorizarlo se pierde. Eso obliga a un srcset de
   tres densidades, y esas medidas SI estan acopladas a las alturas de
   aqui abajo: si cambias .navbar .logo-mark hay que regenerar los
   pww-neon-*.webp (fuente: imprenta/logo-transparente-recortado.png,
   escalado con Lanczos).

   Los ficheros salen ya recortados al contenido, asi que el aire que
   rodea al hexagono lo pone el layout, no pixeles transparentes.
   ===================================================== */

.logo picture,
.footer-logo picture { display: block; flex: none; line-height: 0; }

.logo-mark {
  width: auto;
  flex: none;
  /* Sin drop-shadow: la marca ya trae su propio resplandor y una sombra
     encima solo lo ensucia. La placa azul de antes si la necesitaba. */
}

/* En la barra va sola, sin el nombre al lado, asi que puede crecer. El
   hexagono es casi cuadrado y a la altura que llevaba la placa (46px,
   que era apaisada) se leia diminuto: necesita mas alto para pesar lo
   mismo en la esquina. */
.navbar .logo-mark { height: 64px; }

/* En el pie sigue acompanada del nombre, y ahi manda el texto. */
.footer-logo .logo-mark { height: 2.1em; }

@media (max-width: 700px) {
  .navbar .logo-mark { height: 50px; }
  .footer-logo .logo-word { font-size: .95em; }
}

/* =====================================================
   LA MARCA www → pww
   =====================================================
   El chiste de la casa, en el pie de la portada. Se dibuja una sola
   vez al entrar y se queda quieto: la maquina de escribir de al lado
   ya va en bucle y dos cosas moviendose ahi seria ruido.
   ===================================================== */

.wmark {
  --strike: #FF3B47;          /* rojo solo para esto: no es de la paleta */
  flex: none;
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: .06em;
  font-family: var(--sans);
  font-weight: 800;
  line-height: .98;
  letter-spacing: -.015em;
  font-size: clamp(1.05rem, 2.3vw, 1.55rem);
}

.wmark-old {
  position: relative;
  color: var(--dim);
  opacity: 0;
  animation: wmark-in .5s var(--ease) .35s forwards;
}

/* El tachon: crece de izquierda a derecha, como trazado a mano, e
   inclinado como el de las tarjetas impresas.

   Van en dos piezas a proposito. El giro tiene que pivotar en el CENTRO
   para que la raya cruce la palabra por el medio; el trazado tiene que
   crecer desde la IZQUIERDA. En un solo elemento no caben los dos
   origenes: con "left center" el extremo derecho sube el 16% del ancho
   y la raya sale volando por encima de la www. */
.wmark-cut {
  position: absolute;
  left: -9%;
  top: 50%;
  width: 118%;
  height: .11em;
  transform: translateY(-50%) rotate(-9deg);
}
.wmark-cut::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: .1em;
  background: var(--strike);
  box-shadow: 0 0 .45em rgba(255, 59, 71, .6);
  transform: scaleX(0);
  transform-origin: left center;
  animation: wmark-cut .4s cubic-bezier(.65, 0, .35, 1) .85s forwards;
}

.wmark-new {
  color: var(--ink);
  opacity: 0;
  transform: translateY(.28em);
  animation: wmark-in .55s var(--ease) 1.2s forwards;
}
.wmark-new b { font-weight: 800; color: var(--accent); }

@keyframes wmark-in  { to { opacity: 1; transform: none; } }
@keyframes wmark-cut { to { transform: translateY(-50%) rotate(-9deg) scaleX(1); } }

/* Sin animacion se muestra ya el estado final: el chiste se entiende
   igual, solo que sin el gesto. */
@media (prefers-reduced-motion: reduce) {
  .wmark-old, .wmark-new { opacity: 1; transform: none; animation: none; }
  .wmark-cut { transform: translateY(-50%) rotate(-9deg) scaleX(1); animation: none; }
}

/* =====================================================
   PORTAFOLIO — la pieza dentro de un portatil
   =====================================================
   El marco del portatil es CSS, no una imagen: pesa cero, queda nitido
   en cualquier densidad y el unico bitmap que viaja es la captura.
   ===================================================== */

.work { text-align: center; }

.work-kicker {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .6rem;
  margin: 0 0 1rem;
  font-family: var(--mono);
  font-size: clamp(.62rem, 1.4vw, .78rem);
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--accent);
  /* Las mismas sombras que el texto de la travesía, y por el mismo
     motivo: desde que la sección entra desnuda, estas líneas flotan un
     tramo sobre el vídeo vivo — estallido incluido, que es el único
     plano claro. Sobre la lámina oscura no molestan. */
  text-shadow: 0 1px 24px rgba(0, 0, 0, 1);
}

.work-title {
  margin: 0;
  font-size: clamp(1.9rem, 5vw, 3.4rem);
  font-weight: 800;
  letter-spacing: -.02em;
  text-shadow: 0 2px 70px rgba(0, 0, 0, .95);
}

.work-sub {
  margin: .75rem auto 0;
  max-width: 46ch;
  color: var(--dim);
  font-size: clamp(.95rem, 1.7vw, 1.0625rem);
  text-shadow: 0 1px 40px rgba(0, 0, 0, 1);
}

/* La rejilla del portafolio. auto-fit con un minimo: con una sola
   pieza ocupa todo el ancho y con dos se parten en columnas, sin tener
   que tocar nada al anadir la tercera. El minimo va con min() para que
   en un movil estrecho no fuerce desbordamiento horizontal. */
.work-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 340px), 1fr));
  gap: clamp(2.6rem, 5vw, 4rem);
  margin-top: clamp(2.2rem, 6vh, 3.6rem);
  text-align: center;
}
.work-item { margin: 0; }

.work-name {
  margin: clamp(.9rem, 2.2vh, 1.4rem) 0 0;
  font-size: clamp(1.15rem, 2.2vw, 1.5rem);
  font-weight: 800;
  letter-spacing: -.015em;
}
.work-desc {
  margin: .45rem auto 0;
  max-width: 34ch;
  color: var(--dim);
  font-size: clamp(.875rem, 1.5vw, .9375rem);
  line-height: 1.55;
}

/* Sin ancho fijo: dentro de la rejilla manda la columna. El max-width
   es para cuando queda una sola pieza y la columna se hace enorme. */
.laptop {
  display: block;
  width: 100%;
  max-width: 880px;
  margin: 0 auto;
  text-decoration: none;
  color: inherit;
  transition: transform .55s var(--ease);
}
.laptop:hover { transform: translateY(-8px); }
.laptop:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 14px;
  border-radius: 18px;
}

.laptop-lid {
  display: block;
  position: relative;
  padding: 1.5%;
  border-radius: 14px 14px 5px 5px;
  background: linear-gradient(#3a3f49, #1b1e24 55%, #14171c);
  box-shadow:
    0 0 0 1px rgba(242, 245, 244, .09),
    0 46px 100px -34px rgba(0, 0, 0, .9);
}

.laptop-cam {
  position: absolute;
  top: .6%;
  left: 50%;
  width: 5px;
  height: 5px;
  margin-left: -2.5px;
  border-radius: 50%;
  background: #05070A;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .10);
}

.laptop-screen {
  display: block;
  overflow: hidden;
  border-radius: 4px;
  background: #000;
  /* Fija el hueco antes de que llegue la captura: sin esto la seccion
     pega un salto al cargar y se lleva por delante el scroll. */
  aspect-ratio: 16 / 10;
}
.laptop-screen img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
}

/* La base va algo mas ancha que la tapa: es lo que hace que se lea
   como un portatil de frente y no como un marco de cuadro.

   El vuelo es del 6% y no del 13% que tenia: con dos piezas en rejilla,
   el sobrante invadia la columna vecina y las dos bases se fundian en
   una sola barra continua de lado a lado. */
.laptop-base {
  display: block;
  position: relative;
  width: 106%;
  height: clamp(10px, 1.5vw, 16px);
  margin-left: -3%;
  border-radius: 0 0 10px 10px;
  background: linear-gradient(#262A31, #15181D 55%, #0C0E12);
  box-shadow: 0 22px 44px -20px rgba(0, 0, 0, .92);
}
/* La muesca por donde se abre. */
.laptop-base::after {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  width: 11%;
  height: 40%;
  transform: translateX(-50%);
  border-radius: 0 0 6px 6px;
  background: rgba(0, 0, 0, .5);
}

.laptop-url {
  display: block;
  margin-top: clamp(.9rem, 2vh, 1.25rem);
  font-family: var(--mono);
  font-size: .8125rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--dim);
  transition: color .3s;
}
.laptop:hover .laptop-url { color: var(--accent); }

/* =====================================================
   CONTACTO — dos salidas
   ===================================================== */

.cta-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: clamp(.7rem, 2vw, 1.1rem);
}

/* inline-flex y no inline-block: con inline-block el icono se sienta
   sobre la linea base y descuadra el texto medio pixel. */
.cta-actions .cta-btn {
  display: inline-flex;
  align-items: center;
  gap: .6rem;
  padding: 0 clamp(1.5rem, 4vw, 2.6rem);
  line-height: 1;
}

.cta-btn-ghost {
  background: transparent;
  color: var(--ink);
  box-shadow: inset 0 0 0 2px rgba(242, 245, 244, .28);
}
.cta-btn-ghost:hover {
  color: var(--accent);
  box-shadow: inset 0 0 0 2px var(--accent);
}

/* justify-content y no el text-align:center del pie: al ser flex, el
   text-align del padre no lo centra y se quedaba pegado a la izquierda
   mientras el logo y el copyright iban centrados. */
.footer-contact {
  margin: 0 0 .4rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: .35rem .7rem;
  font-size: .8125rem;
  color: var(--dim);
}
.footer-contact a {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color .25s;
}
.footer-contact a:hover { color: var(--accent); }

@media (max-width: 700px) {
  /* En estrecho no caben la frase y la marca en la misma fila. */
  .mast-foot { flex-direction: column; align-items: flex-start; gap: 1.1rem; }
  .wmark { align-items: flex-start; }
  .cta-actions .cta-btn { font-size: .875rem; }
}
