/* ============================================================
   PORTAFOLIO — Editor de Video · Dark UI · Duotono teal + naranja cálido
   Display: Sora · Body: DM Sans · Acento mono: Space Mono
   Layout: navegación lateral izquierda + barra social derecha
   ============================================================
   SISTEMA DE REVEALS: una ANIMACIÓN CSS (@keyframes revealUp)
   disparada por la clase .visible que añade el IntersectionObserver
   de script.js. UNA sola definición de .reveal en este archivo
   (sección "SISTEMA DE REVEAL"). No añadir otra.
   ============================================================ */

:root {
  /* Paleta dark UI con duotono de color grading: teal (acción dominante)
     + naranja cálido (acento secundario, solo badge y detalle puntual). */
  --bg: #14171b;            /* fondo principal */
  --bg-alt: #101215;        /* secciones "hundidas", más oscuras */
  --card: #1b1f24;          /* tarjetas, un paso más claras que el fondo */
  --card-hover: #21262c;
  --line: #2b2f35;          /* bordes sutiles que delinean tarjetas */
  --line-strong: #3a4048;   /* bordes de énfasis (placeholders, hovers de tarjetas) */
  --sidenav-bg: #101317;    /* barra lateral, ligeramente hundida */
  --footer-bg: #0e1013;
  --heading: #f4f5f6;       /* títulos, casi blanco */
  --text: #a9adb4;          /* párrafos, gris claro (AA sobre --bg) */
  --muted: #7d838c;         /* overlines y detalles secundarios */
  --accent: #17b8a6;        /* teal: SOLO acción (CTA, hover, foco, scrollspy) */
  --accent-bright: #5dcaa5; /* teal claro para textos pequeños interactivos */
  --accent-glow: rgba(23, 184, 166, 0.35);
  --accent-warm: #ff7a45;   /* naranja cálido: SOLO badge "Más popular" e ícono Color Grading */

  --font-display: 'Sora', 'Trebuchet MS', sans-serif;
  --font-body: 'DM Sans', 'Segoe UI', sans-serif;
  --font-mono: 'Space Mono', 'Courier New', monospace; /* SOLO overlines de sección y promesas de Sobre Mí */

  --ease: cubic-bezier(0.22, 1, 0.36, 1);

  --radius: 14px;
  --shadow: 0 14px 40px rgba(0, 0, 0, 0.35);
  --shadow-hover: 0 18px 50px rgba(0, 0, 0, 0.45);
  --sidenav-w: 250px;
  --container: 1080px;
  /* Alto de la barra superior móvil. Vive aquí porque lo consumen cuatro
     reglas (la barra, el padding del hero, el del panel y scroll-padding-top):
     con un solo número no pueden desincronizarse. En escritorio no se usa. */
  --topbar-h: 64px;
}

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

/* overflow-x en html Y body: red de seguridad contra el arrastre
   horizontal en móvil (body ya lo tenía; html faltaba). */
html { scroll-behavior: smooth; overflow-x: hidden; }

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 1.0625rem;
  line-height: 1.75;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img { max-width: 100%; display: block; }
a { color: inherit; }
ul { list-style: none; }

.container {
  width: min(var(--container), 88%);
  margin-inline: auto;
}

/* ============ Scrollbar minimalista: línea blanca fina y sutil ============
   OJO: las propiedades estándar (scrollbar-width/color) van aisladas en un
   @supports que solo cumple Firefox. Si Chrome (121+) las viera, ignoraría
   por completo las reglas ::-webkit-scrollbar de abajo. */
/* Firefox */
@supports (-moz-appearance: none) {
  html { scrollbar-width: thin; scrollbar-color: rgba(255, 255, 255, 0.25) transparent; }
}
/* Chrome, Brave, Edge, Safari */
::-webkit-scrollbar { width: 4px; height: 4px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.25); border-radius: 4px; border: none; }
::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.4); }

/* ============ Secciones y títulos ============ */
.section { padding: clamp(4.5rem, 10vw, 7.5rem) 0; position: relative; }
/* Sin borde arriba ni abajo: --line (#2b2f35) es mucho más claro que estos
   fondos, así que un solo píxel se leía como un trazo nítido cruzando todo el
   ancho, incluso pegado a los divisores diagonales, que son el único corte
   que queremos ver. Como esta clase la usan "Sobre Mí" y "Portafolio", ese
   par de declaraciones producía CUATRO líneas. La separación entre secciones
   la da ahora solo el escalón --bg-alt / --bg, deliberadamente suave. */
.section--alt {
  background: var(--bg-alt);
}

.section__head { text-align: center; max-width: 640px; margin: 0 auto 3.2rem; }
.section__head--left { text-align: left; margin: 0 0 1.8rem; }

.section__overline {
  display: inline-block;
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: 0.9rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.8rem;
}

.section__title {
  font-family: var(--font-display);
  font-weight: 700;
  /* Mínimo bajado de 1.75 a 1.45rem: solo actúa por debajo de 644px,
     así ningún título se desborda en móvil. El máximo no cambia. */
  font-size: clamp(1.45rem, 3.6vw, 2.7rem);
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--heading);
}

.section__lead { margin-top: 1rem; }

/* ============ Botones ============ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1rem;
  letter-spacing: 0.01em;
  text-decoration: none;
  padding: 0.85rem 2.1rem;
  border-radius: 50px;
  border: none;
  cursor: pointer;
  transition: transform 0.25s var(--ease), box-shadow 0.25s var(--ease),
              background-color 0.25s ease, color 0.25s ease;
}
.btn:hover { transform: translateY(-3px); }
.btn:active { transform: translateY(-1px) scale(0.98); }
.btn:focus-visible { outline: 2px solid var(--accent-bright); outline-offset: 3px; }

/* El teal vive aquí: el botón de conversión (texto oscuro por contraste AA) */
.btn--primary { background: var(--accent); color: #0e1013; }
.btn--primary:hover { background: var(--accent-bright); box-shadow: 0 12px 30px var(--accent-glow); }

.btn--outline {
  background: transparent;
  color: var(--heading);
  box-shadow: inset 0 0 0 1.5px var(--line-strong);
}
.btn--outline:hover { box-shadow: inset 0 0 0 1.5px var(--accent); color: var(--accent-bright); }

.btn--block { width: 100%; }

/* ============================================================
   NAVEGACIÓN LATERAL IZQUIERDA (desktop) / MENÚ OVERLAY (móvil)
   ============================================================ */
.sidenav {
  position: fixed;
  top: 0; left: 0; bottom: 0;
  width: var(--sidenav-w);
  z-index: 100;
  display: flex;
  flex-direction: column;
  padding: 2.2rem 2.4rem;
  background: var(--sidenav-bg);
  /* Sin border-right: la barra ya se separa por su propio fondo, y ese 1px
     cortaba la foto del hero justo donde tiene que llegar al borde. */
}

/* Marca pequeña y minimalista arriba */
.sidenav__logo {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.05rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--heading);
  text-decoration: none;
}
.sidenav__logo span { color: var(--muted); }

/* La navegación se centra verticalmente en la barra (como la referencia) */
.sidenav nav {
  flex: 1;
  display: flex;
  align-items: center;
}

.sidenav__menu {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  width: 100%;
}

.nav__link {
  display: flex;
  align-items: center;
  font-family: var(--font-display);
  text-decoration: none;
  font-weight: 400;
  font-size: 1rem;
  color: var(--text);
  padding: 0.55rem 0;
  transition: color 0.25s ease;
}
.nav__link:hover { color: var(--heading); }
.nav__link:focus-visible { outline: 2px solid var(--accent-bright); outline-offset: 3px; border-radius: 4px; }

/* Rayita "—" del scrollspy: espacio siempre reservado (los textos no
   se mueven); solo la rayita aparece/desaparece con animación suave */
.nav__link::before {
  content: "";
  width: 24px;
  height: 2px;
  margin-right: 12px;
  background: var(--accent);
  border-radius: 2px;
  flex-shrink: 0;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.4s var(--ease);
}
.nav__link.active { color: var(--heading); }
.nav__link.active::before { transform: scaleX(1); }

/* Botón hamburguesa: solo en móvil */
/* ============ Barra superior (solo móvil) ============
   z-index 120 > 100 del panel: al abrir el menú, la barra con el logo y el
   botón de cerrar quedan siempre por encima y accesibles. */
.topbar {
  display: none;                 /* en escritorio no existe; se activa en ≤1023px */
  position: fixed;
  top: 0; left: 0; right: 0;     /* left+right en vez de width: no puede desbordar */
  /* min-height en vez de height: el alto queda fijado por el token, así el
     layout no salta cuando cargue la imagen del logo (BLOQUE G). */
  min-height: var(--topbar-h);
  z-index: 120;
  align-items: center;
  justify-content: space-between;
  padding: 0 1.25rem;
  background: rgba(20, 23, 27, 0.85);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line);
}
/* Mismo tratamiento tipográfico que el logo de la barra lateral */
.topbar__logo {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.05rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--heading);
  text-decoration: none;
}
.topbar__logo span { color: var(--muted); }

/* ============ Marca: logo de imagen ============
   .brand es el enlace contenedor (lo llevan el logo de la barra superior y el
   de la barra lateral). Contiene <img class="brand__logo" src="assets/…">.
   Rutas siempre RELATIVAS (assets/…), nunca absolutas.
   flex-shrink: 0 evita que el logo se comprima contra el botón de menú. */
/* Deliberadamente NO fija display ni gap: un gap de flex separaría las partes
   del enlace. Cada logo conserva el display que ya tenía. */
.brand {
  flex-shrink: 0;
  text-decoration: none;
}
/* El logo real es HORIZONTAL con texto (538x105, relación 5.12:1), no un
   círculo: se retiraron aspect-ratio: 1 y border-radius: 50%, que lo habrían
   deformado y recortado. La caja se reserva con los atributos width/height
   del HTML, así que no hay salto de layout al cargar. Sin fondo, borde ni
   sombra: el PNG es transparente y se apoya sobre el fondo de la página.
   Medida base = barra lateral de escritorio. */
.brand__logo {
  display: block;
  width: 160px;
  height: auto;
  object-fit: contain;
  flex-shrink: 0;
}
/* Pie de página: algo menor que en la barra lateral */
.footer__logo .brand__logo { width: 140px; }

/* Símbolo suelto del panel del menú móvil. Oculto en escritorio: allí la barra
   lateral ya muestra el logo completo y esto sería un duplicado. */
.sidenav__mark { display: none; }

/* El botón ya no flota suelto: vive dentro de .topbar, en flujo normal. */
.nav__toggle {
  display: none;
  position: relative;
  flex-shrink: 0;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
  width: 48px; height: 48px;
  border-radius: 50%;
  background: rgba(27, 31, 36, 0.94);
  border: 1px solid var(--line);
  cursor: pointer;
  transition: border-color 0.25s ease, transform 0.25s var(--ease);
}
.nav__toggle:hover { border-color: var(--accent); transform: scale(1.06); }
.nav__toggle span {
  width: 20px; height: 2px;
  background: var(--heading);
  transition: transform 0.3s var(--ease), opacity 0.3s ease;
}
.nav__toggle.open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav__toggle.open span:nth-child(2) { opacity: 0; }
.nav__toggle.open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* El contenido deja sitio a la navegación lateral en desktop */
main, .footer { margin-left: var(--sidenav-w); }

/* ============================================================
   BARRA SOCIAL DERECHA (minimalista, neutra)
   ============================================================ */
/* Cápsula centrada verticalmente y PEGADA al borde derecho: redondeada
   solo del lado izquierdo (el derecho va al ras de la pantalla).
   A media altura no se superpone al botón volver arriba. */
.socialbar {
  position: fixed;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  z-index: 90;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
  background: var(--card);
  border: 1px solid var(--line);
  border-right: none;
  border-radius: 40px 0 0 40px;
  /* Cápsula ceñida a los íconos: los logos son los protagonistas */
  padding: 1rem 0.5rem 0.9rem;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
  transition: transform 0.5s var(--ease), opacity 0.5s var(--ease);
}

/* Oculta en secciones intermedias (la clase la pone/quita el scrollspy en JS):
   se desliza hacia el borde y se desvanece, sin capturar clics */
.socialbar.is-hidden {
  transform: translateY(-50%) translateX(24px);
  opacity: 0;
  pointer-events: none;
}

.socialbar__label {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.7rem;
  letter-spacing: 0.35em;
  color: var(--muted);
  writing-mode: vertical-rl;
  user-select: none;
}

/* Línea blanca fina y visible (como la referencia).
   Blanco sólido y no rgba(255,255,255,0.65): ese 0.65 sobre el fondo --card
   de la cápsula se mezclaba a rgb(175,177,179), un gris claro que a 1.5px de
   ancho se leía como una raya apagada en vez de una línea blanca. */
.socialbar__line {
  width: 1.5px;
  height: 40px;
  background: #ffffff;
}

/* Los íconos en columna simple: la superficie la pone la cápsula (.socialbar) */
.socialbar__icons {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}
.socialbar__icons a {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px; height: 40px;
  border-radius: 50%;
  color: var(--heading); /* blancos en reposo para resaltar sobre la foto */
  transition: transform 0.25s var(--ease), background-color 0.25s ease, color 0.25s ease;
}
.socialbar__icons a:hover {
  transform: translateY(-3px);
  background: rgba(23, 184, 166, 0.14);
  color: var(--accent-bright);
}
.socialbar__icons svg { width: 18px; height: 18px; }

/* En pantallas muy bajas la cápsula centrada (~300px de alto) llegaría a
   tocar el botón volver arriba (abajo a la derecha): se oculta antes */
@media (max-height: 520px) {
  .socialbar { display: none; }
}

/* ============ Hero (foto a todo el alto, pegada al borde derecho) ============ */
.hero {
  min-height: 100svh;
  display: flex;
  align-items: center;
  position: relative;
  overflow: hidden;
  /* Atmósfera neutra con un velo teal mínimo */
  background:
    radial-gradient(50% 55% at 15% 20%, rgba(255, 255, 255, 0.025), transparent 60%),
    radial-gradient(45% 50% at 30% 90%, rgba(23, 184, 166, 0.05), transparent 60%),
    var(--bg);
}

/* Divisor DIAGONAL al final del hero: una cuña del color de la
   siguiente sección corta el hero en ángulo (como la referencia) */
.hero::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: clamp(60px, 9vw, 130px);
  background: var(--bg-alt);
  clip-path: polygon(0 100%, 0 30%, 100% 100%);
  z-index: 3;
  pointer-events: none;
}

.hero__inner {
  position: relative;
  z-index: 2;
  padding-top: 3rem;
  padding-bottom: 5rem;
}

/* El texto va a la izquierda, POR ENCIMA de la foto de fondo
   (z-index 3 > foto z1 y su degradado z2, para que los botones sean clicables) */
.hero__content { max-width: 520px; position: relative; z-index: 3; }

.hero__greeting {
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: 1.05rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.8rem;
}

.hero__title {
  font-family: var(--font-display);
  font-weight: 800;
  /* Mínimo bajado de 2.7 a 2.1rem: solo actúa por debajo de 720px.
     De 720px en adelante el valor es idéntico al de antes. */
  /* 84px de techo. El mínimo y el vw se quedan como estaban: solo cambia
     dónde deja de crecer. */
  font-size: clamp(2.1rem, 6vw, 5.25rem);
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--heading);
  margin-bottom: 0.9rem;
}

.hero__role {
  font-family: var(--font-display);
  font-weight: 600;
  /* 27px de techo: a 34px la promesa se partía en tres líneas. */
  font-size: clamp(1.15rem, 2.2vw, 1.6875rem);
  color: var(--heading);
  margin-bottom: 1.3rem;
}
.hero__role span { color: var(--muted); }

.hero__sub { max-width: 46ch; margin-bottom: 2.2rem; }

.hero__actions { display: flex; flex-wrap: wrap; gap: 1rem; }

/* Foto protagónica: TODO el ancho del hero, de borde a borde, sin marcos.
   El sujeto va a la derecha del encuadre; el texto se superpone a la izquierda. */
.hero__media {
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: 100%;
  z-index: 1;
  /* Capa puramente decorativa: jamás intercepta clics */
  pointer-events: none;
}
/* La foto real (img/hero.jpg) llena el contenedor sin deformarse.
   object-position ancla el encuadre a la derecha: el sujeto no se corta
   aunque la pantalla sea más ancha o más angosta. */
.hero__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: right center;
}
/* Scrim: oscurece la izquierda lo justo para que el texto se lea, y se
   disuelve pronto para que la foto siga presente. Va sobre la imagen
   (z-index 2 dentro de .hero__media) y bajo el texto, que vive en
   .hero__inner. */
.hero__media::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(to right, rgba(0, 0, 0, 0.35) 0%, transparent 25%);
}

/* Entrada orquestada del hero.
   fill-mode backwards: al terminar, la animación suelta el transform
   y el parallax de JS puede moverlo sin conflicto. */
.anim { animation: heroUp 0.85s var(--ease) backwards; }
.anim-d1 { animation-delay: 0.15s; }
.anim-d2 { animation-delay: 0.28s; }
.anim-d3 { animation-delay: 0.42s; }
.anim-d4 { animation-delay: 0.56s; }
.anim-d5 { animation-delay: 0.7s; }
@keyframes heroUp {
  from { opacity: 0; transform: translateY(28px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Decoración del hero (parallax leve vía JS), muy discreta */
.hero__glow { position: absolute; border-radius: 50%; z-index: 0; pointer-events: none; }
.hero__glow--1 {
  width: 340px; height: 340px;
  top: -90px; right: 38%;
  background: radial-gradient(circle, rgba(23, 184, 166, 0.11), transparent 70%);
}
/* Segundo resplandor: el acento cálido, muy suave — ambiente de marca duotono */
.hero__glow--2 {
  width: 200px; height: 200px;
  bottom: 8%; left: -70px;
  background: radial-gradient(circle, rgba(255, 122, 69, 0.035), transparent 70%);
}
.hero__dots {
  position: absolute;
  width: 150px; height: 150px;
  top: 18%; left: 38%;
  z-index: 1;
  pointer-events: none;
  opacity: 0.35;
  background-image: radial-gradient(rgba(245, 245, 247, 0.28) 1.5px, transparent 1.5px);
  background-size: 17px 17px;
}

/* ============ Placeholders (a reemplazar por el usuario) ============ */
.placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--card);
  border: 1.5px dashed var(--line-strong);
  color: var(--text);
  text-align: center;
}
.placeholder__tag {
  font-size: 0.85rem;
  font-weight: 500;
  padding: 1rem;
  max-width: 90%;
  opacity: 0.8;
}

/* ============ Sobre Mí ============ */
.about__grid {
  display: grid;
  grid-template-columns: 1fr;          /* móvil: una sola columna */
  gap: clamp(2.5rem, 5vw, 4.5rem);
  align-items: center;
}

.about__media {
  width: 100%;                          /* llena su columna hasta el max-width */
  max-width: 560px;
}
.placeholder--photo {
  position: relative;              /* ancla la línea de acento (::before) */
  aspect-ratio: 4 / 5;
  border-radius: var(--radius);
  overflow: hidden;
  /* Ya no es un marcador de posición: se anula el borde discontinuo
     que hereda de .placeholder (esa clase base la siguen usando las
     tarjetas de Portafolio, por eso se sobrescribe solo aquí). */
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
  transition: transform 0.4s var(--ease), box-shadow 0.4s var(--ease);
}
.placeholder--photo:hover {
  transform: translateY(-4px);
  box-shadow: 0 30px 70px rgba(0, 0, 0, 0.55);
}

/* Acento: línea teal de 3px pegada al borde izquierdo, a toda la altura.
   Va en absolute para no alterar el tamaño del video, y sin eventos de
   puntero para no interceptar nada. */
.placeholder--photo::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 3px;
  background: var(--accent);
  z-index: 2;
  pointer-events: none;
}

/* Video ambiental de Sobre Mí: llena el marco 4:5 sin deformarse.
   El overflow:hidden del contenedor recorta las esquinas redondeadas. */
/* Filtro frío: baja el amarillo de la luz y empuja los medios hacia el azul
   verdoso para que la pieza deje de pelearse con el teal de la paleta.
   OJO: esto corrige el COLOR, no el CONTENIDO. El archivo que se ve sigue
   siendo assets/sobre-mi.mp4; para cambiar el plano hay que reemplazar ese
   archivo (y su cartel, assets/sobre-mi-poster.jpg). */
.about__video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
  filter: saturate(0.85) hue-rotate(-8deg) contrast(1.05) brightness(0.95);
}

.about__p { margin-bottom: 1.2rem; }
/* Los 28px que antes ponía la línea de garantías, ahora que ya no está. */
.about__text .about__p:last-of-type { margin-bottom: 28px; }

/* El paso siguiente aquí es ver el trabajo, no escribir: por eso es un enlace
   y no un botón relleno. El relleno se reserva para el CTA de verdad. */
.about__link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-family: var(--font-body);
  font-size: 15px;
  font-weight: 600;
  color: var(--accent);
  text-decoration: none;
}
.about__link span { transition: transform 0.25s var(--ease); }
.about__link:hover { text-decoration: underline; }
.about__link:hover span { transform: translateX(4px); }
.about__link:focus-visible { outline: 2px solid var(--accent-bright); outline-offset: 3px; border-radius: 4px; }

/* ============ Servicios ============ */
.services__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.4rem;
}

.service {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 2.4rem 2rem;
  text-align: center;
  /* Columna flex para poder anclar el "Ideal si:" al fondo con margin-top:auto.
     Sin esto las tres divisorias caen a alturas distintas según lo que ocupe
     cada cuerpo, y las tarjetas dejan de leerse como una fila. */
  display: flex;
  flex-direction: column;
  transition: transform 0.3s var(--ease), border-color 0.3s ease,
              background-color 0.3s ease, box-shadow 0.3s var(--ease);
}
.service:hover {
  transform: translateY(-6px);
  border-color: var(--line-strong);
  background: var(--card-hover);
  box-shadow: var(--shadow-hover);
}

.service__icon {
  width: 84px; height: 84px;
  margin: 0 auto 1.4rem;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--line);
  color: var(--heading);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.35s var(--ease), background-color 0.3s ease,
              color 0.3s ease, border-color 0.3s ease;
}
.service__icon svg {
  width: 42px; height: 42px;
  transition: transform 0.35s var(--ease);
}
/* Al hover (interacción) el ícono se llena de teal */
.service:hover .service__icon {
  transform: scale(1.08);
  background: var(--accent);
  border-color: var(--accent);
  color: #0e1013;
}
.service:hover .service__icon svg { transform: rotate(-5deg) scale(1.04); }

.service h3 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.22rem;
  line-height: 1.3;
  /* Dos líneas reservadas siempre: "Edición Short-form" ocupa una y
     "Clips de Podcast y Entrevistas" dos, así que sin esto los cuerpos de las
     tres tarjetas arrancan a alturas distintas. */
  min-height: 2.6em;
  color: var(--heading);
  margin-bottom: 0.6rem;
  /* BLOQUE D: reparte las líneas a largos parejos en vez de dejar una
     huérfana. Se ignora sin más en navegadores que no lo soporten. */
  text-wrap: balance;
}
.service p { font-size: 0.97rem; }
/* Las plataformas van DEBAJO del nombre del servicio, no encima: arriba
   competían con el título por ser lo primero que se lee. Como pie son lo que
   son, una aclaración de dónde se publica.
   El min-height reserva la fila en las tres tarjetas, que es lo que mantiene
   los cuerpos de texto alineados a la misma altura. */
.service__platforms {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 14px;
  min-height: 18px;
  margin-bottom: 1.1rem;
}

/* Blanco puro y a media intensidad en reposo: los ocho van monocromos a
   propósito. Con sus colores de marca —el degradado de Instagram, el morado de
   Twitch, el verde de Kick— la fila se convertiría en un semáforo y rompería
   el teal sobre negro del sitio.
   currentColor es lo que hace que basten estas dos reglas para los dos tipos
   de ícono: los de Simple Icons lo reciben en fill y los de Lucide en stroke. */
.service__platforms svg {
  height: 18px;
  width: auto;
  color: #fff;
  opacity: 0.7;
  transition: opacity 0.25s ease;
}
.service:hover .service__platforms svg { opacity: 1; }

/* "Ideal si:" — el filtro de autoselección de la tarjeta. Va separado del
   cuerpo por una línea, no por color, para que se lea como un pie y no como
   otro párrafo más. */
.service__ideal {
  /* auto en vez de una medida fija: se come el sobrante de la tarjeta y deja
     las tres divisorias a la misma altura. El aire mínimo lo pone el padding
     de abajo, que no depende de que haya hueco que repartir. */
  margin-top: auto;
  padding-top: 1.1rem;
  border-top: 1px solid var(--line);
  font-size: 0.9rem;
  color: var(--muted);
  text-align: left;
}
.service__ideal strong { color: var(--heading); font-weight: 600; }

/* ---- Franja de incluidos ----
   Sin fondo ni borde de tarjeta: si fuera una caja más competiría con las
   tres de arriba, y esto no es una cuarta opción — es lo que las tres
   traen puesto. Solo una línea arriba que la separa. */
.included {
  margin-top: 3.4rem;
  padding-top: 2.6rem;
  border-top: 1px solid var(--line);
}

.included__title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.25rem;
  color: var(--heading);
  text-align: center;
  margin-bottom: 1.6rem;
}

.included__list {
  list-style: none;
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.85rem 2.4rem;
  max-width: 900px;
  margin: 0 auto;
}
.included__list li {
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  font-size: 0.95rem;
  line-height: 1.5;
}
.included__check {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  /* 0.2em de desplazamiento: alinea el check con la primera línea de texto
     en vez de con el borde de la caja, que es lo que lo dejaba flotando alto
     cuando el ítem ocupa dos líneas. */
  margin-top: 0.2em;
  color: var(--accent);
}

/* ---- Proceso ---- */
.process {
  margin-top: 3.4rem;
  padding-top: 2.6rem;
  border-top: 1px solid var(--line);
}

.process__title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.25rem;
  color: var(--heading);
  text-align: center;
  margin-bottom: 2rem;
}

.process__steps {
  list-style: none;
  display: grid;
  grid-template-columns: 1fr;
  gap: 2rem;
  counter-reset: none;
}
.process__step { text-align: center; }

/* El número es decorativo (va aria-hidden): el orden real lo da el <ol>. */
.process__num {
  display: block;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 2.8rem;
  line-height: 1;
  color: var(--accent);
  opacity: 0.35;
  margin-bottom: 0.6rem;
}
.process__step h4 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.05rem;
  color: var(--heading);
  margin-bottom: 0.45rem;
}
.process__step p {
  font-size: 0.94rem;
  line-height: 1.6;
  color: var(--text);
}

.services__cta {
  margin-top: 3rem;
  text-align: center;
}
.services__cta-note {
  margin-top: 0.9rem;
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--muted);
}

/* ============ Portafolio ============ */

/* Divisor diagonal al final del portafolio, hacia Contacto
   (inclinado al lado contrario que el del hero, para dar ritmo) */
.portfolio::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: clamp(50px, 7vw, 100px);
  background: var(--bg);
  clip-path: polygon(0 100%, 100% 25%, 100% 100%);
  z-index: 3;
  pointer-events: none;
}

/* ---- EL RIEL ----
   Seis portadas 9:16 en rejilla serían un muro de scroll vertical. En riel se
   recorren de lado y, sobre todo, SE VE QUE HAY MÁS: la tarjeta siguiente
   asoma por el borde derecho, que es la única señal honesta de que la fila
   continúa. La barra nativa se oculta, pero el scroll sigue siendo scroll:
   rueda, gesto, arrastre y teclado funcionan igual. */
.portfolio__railwrap {
  position: relative;
}

.portfolio__rail {
  display: flex;
  gap: 24px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  /* El padding vertical deja respirar al anillo de foco y a la sombra de la
     tarjeta levantada, que si no se recortarían contra el borde del riel. */
  padding: 0.35rem 0.35rem 1rem;
  /* El imán alinea el inicio de la tarjeta con el inicio del "snapport", y el
     snapport arranca en el borde del padding: sin esto la posición de reposo
     del riel es scrollLeft 5.6 y no 0, con dos efectos feos — la flecha de
     "anterior" aparece nada más cargar, y el riel nace medio píxel movido. */
  scroll-padding-inline-start: 0.35rem;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.portfolio__rail::-webkit-scrollbar { display: none; }

/* Ancho fijo en todos los anchos de pantalla: 236px por 420px de alto es el
   9:16 de la portada. Cuántas caben lo decide la ventana, no una fórmula —
   en escritorio salen unas cinco y en un móvil una y media, que ya avisa de
   que la fila sigue. */
.portfolio__rail > .work {
  flex: 0 0 236px;
  scroll-snap-align: start;
}

/* Mientras se arrastra, el cursor lo dice y el imán se suelta: con
   scroll-snap activo el riel pelearía contra el ratón en cada píxel. */
.portfolio__rail.is-dragging {
  cursor: grabbing;
  scroll-snap-type: none;
  scroll-behavior: auto;
}
.portfolio__rail.is-dragging .work { pointer-events: none; }

/* ---- Flechas del riel ----
   Ocultas por debajo de 768px: ahí el gesto es el dedo, no un botón. */
.rail__arrow {
  display: none;
  position: absolute;
  top: 38%;
  z-index: 5;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: rgba(13, 15, 18, 0.72);
  backdrop-filter: blur(6px);
  border: 1px solid var(--line-strong);
  color: var(--accent);
  cursor: pointer;
  align-items: center;
  justify-content: center;
  transition: background-color 0.25s ease, border-color 0.25s ease, transform 0.25s var(--ease);
}
.rail__arrow svg { width: 20px; height: 20px; }
.rail__arrow:hover { background: rgba(13, 15, 18, 0.92); border-color: var(--accent); transform: scale(1.08); }
.rail__arrow:focus-visible { outline: 2px solid var(--accent-bright); outline-offset: 3px; }
.rail__arrow[hidden] { display: none; }
/* Apagada, no escondida: si desapareciera, el bloque daría un salto al
   cambiar de página. */
.rail__arrow.is-off {
  opacity: 0.3;
  cursor: default;
  pointer-events: none;
}
.rail__arrow--prev { left: -14px; }
.rail__arrow--next { right: -14px; }

/* Dos puntos, uno por página. Sustituyen al contador "n / 6", que contaba
   piezas mientras la navegación avanzaba por posiciones de scroll: dos
   unidades distintas midiendo la misma barra. */
.portfolio__dots {
  display: flex;
  justify-content: center;
  gap: 12px;
  margin-top: 0.9rem;
}
/* Aro blanco en lugar de relleno translúcido: el 25% de blanco sobre el fondo
   de la sección se mezclaba a rgb(76,77,80), un contraste de 2.22:1 — o sea,
   invisible. Vaciados y con borde se leen como "posiciones que existen y no
   estás", y el relleno macizo queda reservado para la posición actual. */
.portfolio__dot {
  width: 8px;
  height: 8px;
  padding: 0;
  /* 1.5px se redondea a 1 en pantallas normales; con 2 el aro se ve igual de
     nítido en todas y el hueco interior sigue midiendo 4px. */
  border: 2px solid rgba(255, 255, 255, 0.6);
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
  transition: background-color 0.25s ease, border-color 0.25s ease;
}
.portfolio__dot.is-active {
  background: var(--accent);
  border-color: var(--accent);
}
.portfolio__dot:focus-visible { outline: 2px solid var(--accent-bright); outline-offset: 3px; }


.work { cursor: pointer; }
.work:focus-visible { outline: 2px solid var(--accent-bright); outline-offset: 4px; border-radius: var(--radius); }

.work__thumb {
  position: relative;
  /* Vertical: es el formato en el que se entrega el trabajo, así que la
     portada se muestra tal como se va a publicar. Es la tarjeta entera: aquí
     dentro van la portada, el preview, el badge y el play, y nada más. */
  aspect-ratio: 9 / 16;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--card);
  /* Ya no es un marcador de posición: borde sólido y sombra, igual que el
     contenedor del video de Sobre Mí. Cambio de regla BASE (autorizado):
     también afecta al escritorio, a propósito, para que ambos coincidan. */
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.35);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text);
  transition: transform 0.3s var(--ease), border-color 0.3s ease, box-shadow 0.3s var(--ease);
}
.work:hover .work__thumb {
  transform: translateY(-5px);
  border-color: var(--accent);
  box-shadow: var(--shadow-hover);
}

.work__thumb.has-thumb { border: 1px solid var(--line); }

/* Sin portada todavia: un degradado suave en vez de un rectangulo plano, para
   que se lea como un hueco preparado y no como una imagen que fallo. En cuanto
   el archivo exista, .has-thumb lo tapa con la imagen real. */
.work__thumb:not(.has-thumb) {
  background: linear-gradient(160deg, var(--card-hover) 0%, var(--card) 60%, var(--bg-alt) 100%);
}

/* La instrucción de "pega aquí el ID" no se dibuja en ningún ancho: mientras
   no haya miniatura, el área tiene que leerse como un bloque neutro y limpio,
   no como una tarjeta con un cartel encima del play. Sigue en el DOM y en el
   árbol de accesibilidad (por eso no es display:none), y la explicación de
   cómo pegar el ID vive en el comentario grande de index.html, justo encima
   de las tarjetas, que es donde se busca de verdad. */
.work__thumb .placeholder__tag {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
/* Portada y preview ocupan exactamente el mismo hueco, apiladas. El video
   entra por encima con un fundido de 200ms cuando empieza a reproducir; si el
   archivo no existe nunca llega a reproducir y no se ve nada raro: se queda
   la portada, que es justo el poster del propio video. */
.work__cover,
.work__preview {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}
.work__cover { transition: transform 0.55s var(--ease); }
.work:hover .work__cover { transform: scale(1.06); }

.work__preview {
  z-index: 1;
  opacity: 0;
  transition: opacity 0.2s ease;
  pointer-events: none;
}
.work.is-previewing .work__preview { opacity: 1; }

/* PLAY — abajo a la izquierda, no en el centro. El centro de la portada tiene
   que quedar despejado: es donde ocurre el video del preview, y un disco de
   58px encima lo tapaba justo en el momento en que hay algo que mirar.
   Sin glow: el halo competía con el badge de la esquina opuesta. */
.work__play {
  position: absolute;
  left: 16px;
  bottom: 16px;
  z-index: 3;
  width: 36px; height: 36px;
  border-radius: 50%;
  background: rgba(13, 15, 18, 0.62);
  backdrop-filter: blur(4px);
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0.75;
  transition: transform 0.3s var(--ease), opacity 0.3s ease, background-color 0.3s ease;
}
.work:hover .work__play,
.work:focus-visible .work__play {
  opacity: 1;
  transform: scale(1.12);
  background: rgba(13, 15, 18, 0.82);
}
.work__play svg { width: 16px; height: 16px; margin-left: 2px; }

/* ---- Pie de la tarjeta ----
   Fuera del área del video a propósito: encima del poster el texto competía
   con la propia pieza, que es lo único que hay que mirar ahí. */
.work__meta { padding-top: 10px; }

.work__line {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.6rem;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #7a8484;
  margin-bottom: 5px;
}
.work__dur { flex-shrink: 0; }

/* min-height de dos líneas: los titulares de una sola línea reservan el hueco
   igual, y así las bases de toda la fila caen a la misma altura. */
.work__headline {
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 600;
  line-height: 1.35;
  min-height: 2.7em;
  color: #e8eded;
  transition: color 0.3s ease;
}
.work:hover .work__headline { color: var(--accent-bright); }


/* La línea auxiliar va bajo el subtítulo: más chica y más apagada que él,
   para que se lea como instrucción y no como parte del argumento. */
.portfolio__hint {
  margin-top: 0.6rem;
  font-size: 0.88rem;
  color: var(--muted);
}

.portfolio__note {
  margin-top: 1.8rem;
  text-align: center;
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--muted);
}

.portfolio__cta {
  margin-top: 2.6rem;
  padding-top: 2.4rem;
  border-top: 1px solid var(--line);
  text-align: center;
}
.portfolio__cta-text {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.12rem;
  color: var(--heading);
  margin-bottom: 1.1rem;
}

/* En táctil no hay hover que revele nada: el play se muestra ya opaco, porque
   es la única pista de que la tarjeta se abre. */
@media (hover: none) {
  .work__play { opacity: 1; }
}

/* ============ Lightbox ============ */
/* CRÍTICO: sin esta regla, el display:flex de abajo anula el atributo
   hidden del HTML y el overlay (invisible, inset 0, z-index 200) queda
   montado SIEMPRE, robando todos los clics de la página. */
.lightbox[hidden] { display: none; }

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: rgba(9, 11, 13, 0.93);
  backdrop-filter: blur(6px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 5vh 4vw;
  opacity: 0;
  transition: opacity 0.35s ease;
}
.lightbox.open { opacity: 1; }

/* El escenario reparte el espacio entre el video y su texto: en columna
   cuando la pantalla es angosta y en fila cuando hay ancho de sobra (ver el
   bloque de 900px más abajo). */
.lightbox__stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.2rem;
  width: 100%;
  max-height: 100%;
}

/* 9:16: el reproductor tiene la forma de lo que reproduce. El alto manda
   (min-height del viewport) para que la pieza entre entera sin scroll. */
.lightbox__player {
  height: min(72vh, 620px);
  aspect-ratio: 9 / 16;
  background: #000;
  border-radius: var(--radius);
  overflow: hidden;
  transform: scale(0.94);
  transition: transform 0.4s var(--ease);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text);
  text-align: center;
}
.lightbox.open .lightbox__player { transform: scale(1); }
.lightbox__player iframe,
.lightbox__player video { width: 100%; height: 100%; border: 0; object-fit: contain; background: #000; }
.lightbox__player .lightbox__notice { padding: 2rem; font-weight: 500; max-width: 46ch; }

/* Pie del reproductor: aqui va el contexto ENTERO de la pieza, el que en la
   tarjeta se recorta a dos lineas. */
.lightbox__caption {
  width: min(60ch, 100%);
  text-align: center;
  color: var(--text);
}
.lightbox__caption:empty { display: none; }
.lightbox__badge {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 0.35rem;
}
.lightbox__caption h3 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.1rem;
  color: var(--heading);
  margin-bottom: 0.3rem;
}
.lightbox__caption p {
  font-size: 0.9rem;
  line-height: 1.6;
  max-width: 62ch;
  margin: 0 auto;
}

.lightbox__close {
  position: absolute;
  top: 1.4rem; right: 1.8rem;
  background: none;
  border: none;
  color: var(--heading);
  font-size: 1.6rem;
  cursor: pointer;
  padding: 0.5rem;
  line-height: 1;
  transition: transform 0.25s var(--ease), color 0.25s ease;
}
.lightbox__close:hover { transform: rotate(90deg); color: var(--accent-bright); }
.lightbox__close:focus-visible,
.lightbox__nav:focus-visible { outline: 2px solid var(--accent-bright); outline-offset: 3px; }

/* Pasar de pieza sin salir del reproductor. Se quedan pegadas a los bordes
   para no invadir el video, que es angosto y no admite nada encima. */
.lightbox__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 3;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: rgba(13, 15, 18, 0.7);
  border: 1px solid var(--line-strong);
  color: var(--heading);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background-color 0.25s ease, border-color 0.25s ease, color 0.25s ease;
}
.lightbox__nav svg { width: 22px; height: 22px; }
.lightbox__nav:hover { background: rgba(13, 15, 18, 0.95); border-color: var(--accent); color: var(--accent-bright); }
.lightbox__nav--prev { left: 1rem; }
.lightbox__nav--next { right: 1rem; }

/* ============ Contacto ============ */
/* start (y no stretch): cada tarjeta toma su alto natural y las dos arrancan
   a la misma altura. Estirarlas obligaba a la del correo a llenar el alto del
   formulario, que tiene más contenido, y el sobrante se acumulaba en un hueco
   muerto en su interior. Que la izquierda termine antes es lo correcto. */
.contact__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 3rem;
  align-items: start;
}

/* TARJETA — el mismo tratamiento que .service y que el que ya tenía .form:
   superficie --card, borde --line, --radius y el mismo padding. Se declara
   UNA vez y la comparten las dos columnas. Esa simetría es justo lo que le
   faltaba a la sección: antes el formulario era una tarjeta y el correo era
   texto suelto al aire, y esa diferencia era la que se leía como inacabada. */
.contact__card {
  background: var(--card);
  /* El mismo borde en las dos columnas. Antes la del correo llevaba --line y
     la del formulario un teal rebajado, y esa diferencia se leía como que una
     estaba apagada. */
  border: 1px solid rgba(23, 184, 166, 0.25);
  border-radius: var(--radius);
  padding: 2.4rem 2.2rem;
}

/* Acento teal bajo los overlines de las dos tarjetas ("Mensaje formal" y
   "Mensaje rápido"). Corto a propósito: marca dónde empieza cada columna
   sin convertirse en otra línea cruzando la sección. Va dentro de
   .contact__grid para no alcanzar al overline de la cabecera. */
.contact__grid .section__overline::after {
  content: "";
  display: block;
  width: 40px;
  height: 2px;
  background: var(--accent);
  margin-top: 0.55rem;
}

.contact__subtitle {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.2rem;
  color: var(--heading);
  margin-bottom: 1.2rem;
}

/* ---- El correo, contenido y copiable ----
   El fondo NO es --card: la tarjeta que lo envuelve ya es --card y quedaría
   invisible. --bg-alt es el escalón que el sitio ya usa para "campo dentro
   de tarjeta" (los inputs del formulario), así que no inventa nada nuevo.
   width:fit-content es lo que mantiene correo y botón como una sola unidad:
   la caja se ciñe a los dos y no queda hueco muerto detrás del botón. Cuando
   el ancho disponible no da para la línea entera (columna estrecha en 1024px,
   móviles por debajo de ~410px), la caja se queda en ese máximo y flex-wrap
   baja el botón a su propia línea, pegado a la izquierda bajo el correo:
   apilado limpio, sin espacio muerto, sin depender de un breakpoint. */
.contact__email {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  width: fit-content;
  max-width: 100%;
  gap: 0.3rem;
  background: var(--bg-alt);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 0.25rem 0.4rem 0.25rem 0.9rem;
  margin-bottom: 1.4rem;
}

.contact__email-plain {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--heading);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1rem;
  text-decoration: none;
  overflow-wrap: anywhere;
}
.contact__email-plain:hover { color: var(--accent-bright); }
/* Resaltado teal en vez del azul del navegador: cuando el respaldo del botón
   selecciona el correo (sin HTTPS no hay portapapeles), lo que se ve es esto.
   Va acotado al correo a propósito, para no cambiar cómo se selecciona el
   texto en el resto del sitio. */
.contact__email-plain::selection { background: var(--accent); color: #0e1013; }

/* Botón de copiar: 44x44 de área tocable aunque el icono mida 16px. */
.contact__copy {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  min-width: 44px;
  min-height: 44px;
  padding: 0 0.7rem;
  background: none;
  border: none;
  border-radius: 8px;
  color: var(--muted);
  font-family: var(--font-body);
  font-size: 0.85rem;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  transition: color 0.25s ease, background-color 0.25s ease;
}
/* Ancho reservado para la palabra más larga de los tres estados ("Copiar",
   "Copiado", "Cópialo"): así el botón mide lo mismo siempre y la caja del
   correo no se mueve ni cambia de alto cuando el texto cambia. nowrap es el
   seguro de que ninguno se parta en dos líneas. */
.contact__copy-text {
  min-width: 3.6rem;
  white-space: nowrap;
}
.contact__copy:hover { color: var(--heading); background: rgba(255, 255, 255, 0.05); }
.contact__copy:focus-visible { outline: 2px solid var(--accent-bright); outline-offset: 2px; }
.contact__copy-ico { width: 16px; height: 16px; flex-shrink: 0; }
/* Los dos iconos viven en el HTML y se alternan por clase: así el feedback
   no depende de reescribir el SVG desde JS. */
.contact__copy-ico--done { display: none; }
.contact__copy.is-copied { color: var(--accent-bright); }
.contact__copy.is-copied .contact__copy-ico--copy { display: none; }
.contact__copy.is-copied .contact__copy-ico--done { display: block; }

/* ---- SECUNDARIO: el único botón de correo ----
   Borde teal y fondo transparente. El teal SÓLIDO de la sección está
   reservado para "Enviar mensaje": si los dos fueran macizos, ninguno
   sería el camino principal. Ocupa el ancho de su tarjeta, que es lo que
   ahora marca el borde de la columna (antes lo forzaban 272px a mano). */
.contact__mail {
  width: 100%;
  font-size: 0.95rem;
  display: flex;
  padding: 0.85rem 1.2rem;
  white-space: nowrap;
  box-shadow: inset 0 0 0 1.5px var(--accent);
}
.contact__mail:hover {
  box-shadow: inset 0 0 0 1.5px var(--accent-bright);
  color: var(--accent-bright);
}

/* ---- "Más trabajo mío" ----
   Filas de texto, no píldoras: aquí las redes son un destino secundario y no
   deben pesar lo mismo que el correo, que es el camino principal de la
   tarjeta. Sin fondo, sin borde y sin relleno de caja; lo único que se mueve
   al pasar el ratón es el color. */
.contact__more {
  margin-top: 24px;
  padding-top: 20px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.contact__more-label {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #7a8484;
  margin-bottom: 14px;
}

.contact__social {
  display: flex;
  align-items: center;
  height: 34px;
  margin-bottom: 4px;
  text-decoration: none;
  color: #7a8484;
  transition: color 0.25s ease;
}
.contact__social:last-child { margin-bottom: 0; }
.contact__social svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}
.contact__social-handle {
  font-family: var(--font-mono);
  font-size: 13px;
  color: #b8c0c0;
  margin-left: 12px;
  transition: color 0.25s ease;
}
/* margin-left:auto en vez de space-between en el contenedor: así el hueco
   entre el icono y el handle se queda en los 12px pedidos y todo el sobrante
   se va a la derecha, donde tiene que estar. */
.contact__social-red {
  margin-left: auto;
  font-family: var(--font-body);
  font-size: 12px;
  /* Sube de rgba(255,255,255,0.45) — que sobre --card daba rgb(130,132,135) y
     un contraste de 4.42:1, por debajo del mínimo — a 6.42:1. Sigue muy por
     detrás del @usuario (8.94:1), que es quien manda en la fila. El 500 es un
     peso que la fuente carga de verdad, no uno sintetizado por el navegador. */
  font-weight: 500;
  color: #9aa3a3;
}
.contact__social:hover,
.contact__social:hover .contact__social-handle { color: var(--accent); }
.contact__social:focus-visible { outline: 2px solid var(--accent-bright); outline-offset: 2px; border-radius: 4px; }

/* ---- PRIMARIO: el formulario ----
   La superficie se la da .contact__card, igual que a la otra columna. Lo
   único que lo distingue es el borde: un teal muy rebajado frente al
   --line neutro de la tarjeta de al lado. Nada de halos: un box-shadow
   teal aquí se leía como luz encendida y le peleaba el protagonismo al
   botón "Enviar mensaje", que es el único teal sólido de la sección.
   position:relative es para el honeypot, que se esconde fuera de la
   tarjeta pero anclado a ella. */
.form {
  position: relative;
}

/* Trampa anti-spam: fuera de la vista pero presente en el DOM y sin
   display:none, que es lo primero que un bot descarta. */
.form__gotcha {
  position: absolute;
  top: 0;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form__field { margin-bottom: 1.4rem; }

.form__field label {
  display: block;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.92rem;
  color: var(--heading);
  margin-bottom: 0.5rem;
}

.form__field input,
.form__field textarea {
  width: 100%;
  background: var(--bg-alt);
  border: 1.5px solid var(--line);
  border-radius: 10px;
  padding: 0.85rem 1rem;
  color: var(--heading);
  font-family: var(--font-body);
  font-size: 1rem;
  transition: border-color 0.25s ease, box-shadow 0.25s ease;
  resize: vertical;
}
.form__field input::placeholder,
.form__field textarea::placeholder { color: #77777f; }
.form__field input:focus,
.form__field textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(23, 184, 166, 0.2);
}
.form__field input.invalid,
.form__field textarea.invalid { border-color: #ff6b6b; }

.form__error {
  color: #ff9a9a;
  font-size: 0.82rem;
  margin-top: 0.35rem;
}

/* Línea de confianza bajo el botón: dice quién responde. Discreta a
   propósito (--muted, tamaño chico): informa, no vende. */
.form__trust {
  margin-top: 0.7rem;
  text-align: center;
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--muted);
}

.form__status {
  margin-top: 1rem;
  font-weight: 600;
  font-size: 0.95rem;
  text-align: center;
  min-height: 1.4em;
}
.form__status.ok { color: #6fe3a8; }
.form__status.err { color: #ff9a9a; }

/* ============ Footer (marca + redes + copyright, centrado) ============ */
/* Sin border-top, por lo mismo que .section--alt: el pie ya se separa solo,
   porque --footer-bg (#0e1013) es más oscuro que el fondo de Contacto. */
.footer {
  background: var(--footer-bg);
  padding: 3.4rem 0 2.8rem;
}
.footer__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.5rem;
  text-align: center;
}
.footer__logo {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.15rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--heading);
  text-decoration: none;
  transition: color 0.25s ease;
}
.footer__logo span { color: var(--muted); }
.footer__logo:hover { color: var(--accent-bright); }

.footer__socials {
  display: flex;
  justify-content: center;
  gap: 0.8rem;
  min-height: 40px;
}
.footer__socials a {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px; height: 40px;
  border-radius: 50%;
  border: 1.5px solid var(--line);
  color: var(--text);
  transition: transform 0.25s var(--ease), border-color 0.25s ease, color 0.25s ease;
}
.footer__socials a:hover { transform: translateY(-3px); border-color: var(--accent); color: var(--accent-bright); }
.footer__socials svg { width: 17px; height: 17px; }

.footer__copy { color: #85858d; font-size: 0.85rem; }

/* ============ Botón volver arriba ============ */
.to-top {
  position: fixed;
  right: 1.5rem;
  bottom: 1.5rem;
  z-index: 95;
  width: 48px; height: 48px;
  border-radius: 50%;
  background: var(--card);
  border: 1.5px solid var(--line);
  color: var(--heading);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  /* Oculto al inicio; .show lo hace aparecer con animación */
  opacity: 0;
  transform: translateY(18px);
  pointer-events: none;
  transition: opacity 0.35s ease, transform 0.35s var(--ease),
              border-color 0.25s ease, color 0.25s ease, background-color 0.25s ease;
}
.to-top.show { opacity: 1; transform: translateY(0); pointer-events: auto; }
.to-top:hover { border-color: var(--accent); color: #0e1013; background: var(--accent); transform: translateY(-4px); }
.to-top:focus-visible { outline: 2px solid var(--accent-bright); outline-offset: 3px; }
.to-top svg { width: 20px; height: 20px; transition: transform 0.25s var(--ease); }
.to-top:hover svg { transform: translateY(-2px); }

/* Giro completo de la flecha al hacer clic (clase .spin puesta por JS) */
@keyframes toTopSpin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}
.to-top.spin svg { animation: toTopSpin 0.5s var(--ease); }

/* ============================================================
   SISTEMA DE REVEAL — ÚNICA definición en todo el archivo.
   script.js añade .visible al entrar al viewport y asigna
   --stagger (0s, 0.09s, 0.18s…) a los hermanos de un grupo.
   Animación con fill backwards: al terminar suelta transform
   y no choca con los hovers ni con el parallax.
   ============================================================ */
.reveal { opacity: 0; }

.reveal.visible {
  opacity: 1;
  animation: revealUp 0.75s var(--ease) backwards;
  animation-delay: var(--stagger, 0s);
}

@keyframes revealUp {
  from { opacity: 0; transform: translateY(34px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ============================================================
   RESPONSIVE — mobile-first: lo de arriba es móvil, esto amplía
   ============================================================ */
@media (min-width: 768px) {
  .services__grid { grid-template-columns: repeat(2, 1fr); }
  /* La tarjeta mide lo mismo a cualquier ancho (regla base): aquí solo
     aparecen las flechas, que en táctil no pintan nada. */
  .portfolio__rail {
    padding: 0.35rem;
  }
  .rail__arrow { display: flex; }
  /* 3 + 2 en vez de 2+2+1: con flujo por columnas y tres filas, la izquierda
     se lleva los tres primeros y la derecha los dos ultimos, en vez de dejar
     un quinto item solo en una fila propia. */
  .included__list {
    grid-template-columns: repeat(2, 1fr);
    grid-template-rows: repeat(3, auto);
    grid-auto-flow: column;
    align-items: start;
  }
  .process__steps { grid-template-columns: repeat(3, 1fr); gap: 2.4rem; }
  /* minmax(0, …) evita que el contenido imponga un ancho mínimo y deje
     holgura sin repartir dentro de la columna. El gap lo pone el clamp
     de la regla base. */
  .about__grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); }
  /* El correo va primero en el HTML, así que cae solo en la columna
     izquierda y el formulario en la derecha: no hace falta colocarlos a
     mano. Ese mismo orden es el de móvil, el del teclado y el del lector
     de pantalla. */
  .contact__grid { grid-template-columns: 0.85fr 1.15fr; gap: 4rem; }
}

@media (min-width: 1200px) {
  .services__grid { grid-template-columns: repeat(3, 1fr); }
}

/* De 1280 en adelante el reparto no cambia: siguen siendo tres por vista y
   dos páginas. Lo que cambia es que las tarjetas crecen con el contenedor,
   que es justo lo que se pidió. La regla de 768 ya lo hace, así que aquí no
   queda nada que declarar. */

/* Con ancho de sobra el texto se va al costado del video en vez de debajo:
   así la pieza vertical no queda aplastada por su propio pie. */
@media (min-width: 900px) and (min-height: 600px) {
  .lightbox__stage {
    flex-direction: row;
    align-items: center;
    justify-content: center;
    gap: 2.5rem;
    width: auto;
  }
  .lightbox__caption { text-align: left; }
  .lightbox__caption p { margin: 0; }
}

/* ---- Móvil / tablet: la navegación lateral se vuelve menú overlay ---- */
@media (max-width: 1023px) {
  /* El contenido ocupa todo el ancho (no hay barra lateral). El alto de la
     barra superior lo define --topbar-h, no un número suelto. */
  main, .footer { margin-left: 0; }
  /* Sin padding-top en <main>: la foto del hero tiene que pasar por debajo
     de la barra translúcida (BLOQUE C). El hueco lo reserva .hero__inner. */
  /* A cambio, los anclas del menú no deben aterrizar tapadas por la barra */
  html { scroll-padding-top: var(--topbar-h); }

  /* ═══ BLOQUE C — hueco muerto al inicio de "Sobre Mí" ═══
     El divisor diagonal del hero mide 60px y su color (--bg-alt) es EXACTAMENTE
     el de la sección "Sobre Mí", así que se lee como parte de ella: sumaba
     ~40px sobre el video además de los 56px de padding, mientras el resto de
     secciones solo tiene 56px. El padding NO era el culpable (las cuatro
     secciones miden 56-57px idénticos). Se recorta el divisor a la mitad. */
  .hero::after { height: 30px; }

  .topbar { display: flex; }
  .nav__toggle { display: flex; }

  /* La sidenav se convierte en panel de pantalla completa */
  .sidenav {
    width: 100%;
    border-right: none;
    align-items: center;
    justify-content: center;
    text-align: center;
    /* Fondo sólido: el panel tapa por completo, sin transparencias */
    background: var(--bg);
    /* El panel no traía altura propia: dependía de top:0 + bottom:0 de la
       base, que el navegador resuelve contra el viewport de diseño (estático).
       Al esconderse la barra de direcciones el área visible crece y el panel
       no la seguía: quedaba una franja abajo con la página y el botón ↑ a la
       vista. Con dvh la altura sigue al viewport dinámico. bottom:auto evita
       que top+bottom+height queden sobre-restringidos. */
    bottom: auto;
    height: 100vh;   /* respaldo para navegadores sin dvh */
    height: 100dvh;
    /* Deja libre la franja de la barra superior (que va por encima, z-120) */
    padding-top: calc(var(--topbar-h) + 2rem);
    transform: translateX(-100%);
    /* visibility, no solo transform: cerrado el panel sigue en el DOM y sus
       enlaces seguían siendo enfocables con Tab, fuera de pantalla. Va en la
       transición para que no desaparezca de golpe al cerrarse. */
    visibility: hidden;
    /* visibility con duración 0 y retardo: al abrir aparece de inmediato, al
       cerrar espera a que termine el desplazamiento. No se interpola, así que
       no depende de que el navegador anime una propiedad discreta. */
    transition: transform 0.28s var(--ease), visibility 0s linear 0.28s;
  }
  .sidenav.open {
    transform: translateX(0);
    visibility: visible;
    transition: transform 0.28s var(--ease), visibility 0s linear 0s;
  }

  /* El logo ya lo muestra la barra superior: aquí sería un duplicado */
  .sidenav__logo { display: none; }
  /* Símbolo de marca dentro del panel: ocupa el vacío que quedaba entre la
     cabecera y los enlaces. Decorativo (alt="" + aria-hidden), así que no
     entra en la navegación por lector de pantalla. */
  .sidenav__mark {
    display: block;
    width: 85px;
    height: auto;
    object-fit: contain;
    margin-bottom: 2.5rem;
    flex-shrink: 0;
  }
  .sidenav nav { flex: 0; }
  .sidenav__menu { gap: 0.4rem; align-items: center; }
  /* Área tocable de 44px por enlace, en Sora grande */
  .nav__link {
    font-size: 1.5rem;
    justify-content: center;
    min-height: 44px;
    padding: 0.5rem 1rem;
  }
  /* En el overlay móvil no hay rayita lateral; el activo se marca por color */
  .nav__link::before { display: none; }
  .nav__link.active { color: var(--accent-bright); }

  /* ---- Redes: NO se duplica el marcado. La misma cápsula .socialbar se
     reubica dentro del panel, en fila y sin posicionamiento fijo. El
     display:flex también anula la regla de @media (max-height: 520px). ---- */
  .socialbar {
    display: flex;
    position: static;
    transform: none;
    flex-direction: row;
    gap: 1rem;
    margin-top: 2.8rem;
    padding: 0;
    background: none;
    border: none;
    border-radius: 0;
    box-shadow: none;
  }
  /* El scrollspy le pone .is-hidden para esconderla en escritorio;
     dentro del menú eso no aplica. */
  .socialbar.is-hidden { transform: none; opacity: 1; pointer-events: auto; }
  .socialbar__label { writing-mode: horizontal-tb; letter-spacing: 0.25em; }
  .socialbar__line { width: 28px; height: 1.5px; }
  .socialbar__icons { flex-direction: row; gap: 0.4rem; }
  .socialbar__icons a { width: 44px; height: 44px; }

  /* ---- Una sola columna: el video de "Sobre Mí" arriba, el texto abajo.
     En 768–1023px el grid de 2 columnas dejaba la columna del video en
     ~310px, demasiado apretada para un 4:5. ---- */
  .about__grid,
  .contact__grid { grid-template-columns: 1fr; }
  /* Tope por altura de ventana: 64vh × 1.25 (proporción 4:5) = 80vh de alto */
  .about__media { max-width: min(560px, 64vh); }

  /* CTA y botones: área tocable mínima de 44px */
  .btn { min-height: 44px; }

  /* ═══ BLOQUE C — HERO: foto a sangre de fondo con el texto encima ═══
     Se elimina el apilado que había aquí. .hero__media recupera la posición
     absoluta de su regla base, así que deja de ser hermana flex de
     .hero__inner y ya no le roba ancho: ese era el motivo del apilado.
     .hero ya trae min-height: 100svh en la base, no hay que repetirlo. */
  .hero__media {
    position: absolute;
    inset: 0;
    width: 100%;
    margin: 0;
  }
  /* La foto es 16:9 y aquí el hueco es vertical: solo cabe un 26% del ancho
     original, así que el encuadre decide qué se ve. Comparados 50 / 58 / 66%
     con el velo nuevo: al 66% entran LAS DOS cosas — el monitor con el
     timeline a la izquierda y la silueta a la derecha. Al 50% la silueta se
     quedaba fuera. */
  .hero__media img {
    height: 100%;
    aspect-ratio: auto;
    border-radius: 0;
    object-position: 66% center;
  }
  /* El degradado de la base es horizontal, pensado para el texto a la
     izquierda del escritorio. En móvil el texto ocupa todo el ancho, así
     que el velo tiene que ser vertical. */
  /* BLOQUE A: velo mucho más ligero. La zona 40–70% baja a 0.42–0.55, que es
     donde cae la pantalla encendida: la foto pasa a leerse de verdad. Los
     extremos siguen densos porque ahí van la barra superior y el divisor. */
  .hero__media::before {
    display: block;
    background: linear-gradient(180deg,
                rgba(20, 23, 27, 0.78) 0%,
                rgba(20, 23, 27, 0.42) 40%,
                rgba(20, 23, 27, 0.55) 70%,
                rgba(20, 23, 27, 0.90) 100%);
  }
  .hero__dots { display: none; }

  /* BLOQUE B: el contenido se ancla abajo en vez de flotar centrado.
     .hero venía con align-items: center, que centraba .hero__inner y dejaba
     209px muertos arriba y 181px abajo. Con stretch, .hero__inner ocupa todo
     el alto y reparte por dentro con justify-content: flex-end. */
  /* Con el velo aligerado, el subtítulo y el párrafo caen sobre la pantalla
     encendida de la foto y perdían contraste. Sombra discreta en el texto ANTES
     que volver a oscurecer el degradado: la prioridad es que la foto se vea. */
  .hero__greeting,
  .hero__title,
  .hero__role,
  .hero__sub { text-shadow: 0 2px 12px rgba(0, 0, 0, 0.6); }

  .hero { align-items: stretch; }
  .hero__inner {
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    /* La barra superior es translúcida para que la foto pase por debajo;
       el hueco lo reserva el texto, no el <main>. */
    padding-top: calc(var(--topbar-h) + 1rem);
    padding-bottom: clamp(3rem, 10vh, 6rem);
  }

  /* Botones del hero: mismo ancho, mismo alto, uno sobre otro */
  .hero__actions { flex-direction: column; align-items: stretch; gap: 0.75rem; }
  .hero__actions .btn { width: 100%; }

  /* Cerrar del lightbox: en escritorio mide ~41px (1.6rem + 0.5rem de
     padding), justo por debajo del mínimo tocable. Solo se agranda el área;
     el icono, la posición y la lógica del lightbox no cambian. */
  .lightbox__close {
    min-width: 44px;
    min-height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  /* ---- Resto de áreas tocables por debajo de 44px, medidas a 390px. En
     todos los casos solo crece el área de toque: ni el tamaño de letra, ni
     los iconos, ni la posición cambian. ---- */
  /* 29px de alto: ocupa la altura completa de la barra */
  .topbar__logo { display: flex; align-items: center; min-height: 44px; }
  /* Barra superior: se fija el ALTO y el ancho sale solo, para que el logo
     ocupe una franja constante sea cual sea su relación de aspecto. El
     min-height de .topbar mantiene la barra estable mientras la imagen carga. */
  .topbar__logo .brand__logo { width: auto; height: 26px; }
  /* 29px: ya es inline-flex centrado, solo le falta altura */
  .contact__email-plain { min-height: 44px; }
  /* Redes del pie: círculos de 40px, los cuatro píxeles que faltaban */
  .footer__socials { min-height: 44px; }
  .footer__socials a { width: 44px; height: 44px; }
  /* 32px: es un enlace a #inicio, mismo criterio que el logo de la barra */
  .footer__logo { display: inline-flex; align-items: center; min-height: 44px; }

  /* ═══ BLOQUE B — volver arriba ═══
     Ya tenía fondo opaco (var(--card)), borde y el z-index correcto
     (95, sobre el contenido y bajo la sidenav 100 y la barra 120). Lo que
     faltaba era tamaño y separación. 42px visuales como pediste, con 48px
     de área tocable vía ::after para no romper el mínimo del BLOQUE H. */
  .to-top {
    width: 42px;
    height: 42px;
    right: 1rem;
    bottom: 1rem;
    border-width: 1px;
  }
  .to-top svg { width: 17px; height: 17px; }
  /* Con el menú abierto el botón desaparece del todo. Reutiliza el estado que
     ya pone script.js (.open sobre #sidenav): son hermanos en <body> y el
     botón va después, así que basta el combinador ~. Sin clase nueva ni JS.
     Gana a .to-top.show por especificidad (0,2,1 > 0,2,0) y hereda la misma
     transición de opacity/transform que ya tenía el botón. */
  #sidenav.open ~ .to-top {
    opacity: 0;
    transform: translateY(18px);
    pointer-events: none;
  }
  .to-top::after {
    content: "";
    position: absolute;
    inset: -3px;
    border-radius: 50%;
  }
  /* El botón descansa de forma permanente sobre el pie: aire para que no se
     pose encima de los iconos de redes ni del copyright. */
  .footer { padding-bottom: 4.5rem; }

  /* ═══ BLOQUE D — alineación única en Contacto ═══
     Ya no hace falta forzar anchos a mano: las dos columnas son tarjetas
     con el mismo padding, y su borde es el que alinea la sección. Lo que
     queda es bajar ese padding en pantallas estrechas para no comerse el
     ancho útil. Los 44px tocables de las píldoras ya van en la regla base. */
  .contact__card { padding: 1.6rem 1.25rem; }

  /* ═══ BLOQUE E — ritmo vertical y márgenes laterales ═══
     OJO: el padding horizontal NO va en .section (ahí vale 0 a propósito);
     los márgenes laterales los pone .container, y sumar ambos duplicaría la
     sangría. Por eso el clamp horizontal se aplica en .container. */
  .section { padding-block: clamp(3.5rem, 9vw, 7rem); }
  .container {
    width: min(var(--container), calc(100% - 2 * clamp(1.25rem, 5vw, 2rem)));
  }
  .section__head { margin-bottom: 2.2rem; }
  .contact__grid { gap: 2.2rem; }

  /* ═══ BLOQUE H4 — respuesta al toque ═══
     Fuera el destello azul del navegador; en su lugar un hundido discreto.
     .btn ya trae su propio :active en la base, no se duplica. */
  .btn, .nav__link, .work, .to-top, .contact__mail,
  .contact__email-plain, .contact__copy, .socialbar__icons a, .footer__socials a,
  .topbar__logo, .footer__logo, .nav__toggle, .lightbox__close {
    -webkit-tap-highlight-color: transparent;
  }
  .work:active,
  .to-top:active,
  .socialbar__icons a:active,
  .footer__socials a:active { transform: scale(0.98); }
}
