/* ===========================================================================
   PORTAFOLIO JHMS - HOJA DE ESTILOS
   ---------------------------------------------------------------------------
   LECTURA DE DISEÑO
   Portafolio de ingeniería para responsables de contratación técnica y para
   clientes del sector eléctrico. Lenguaje editorial-técnico: papel claro,
   filetes de 1px, tipografía apretada, un solo acento. Se construye con CSS
   nativo (grid, custom properties, color-mix) porque el sitio es estático y se
   copia tal cual al servidor, sin paso de compilación: meter React o Tailwind
   aquí rompería el despliegue, no lo mejoraría.

   DIALES
     DESIGN_VARIANCE 7   rejillas asimétricas, nada centrado, desbordes al borde
     MOTION_INTENSITY 6  aparición al entrar en pantalla y respuesta al puntero,
                         sin parallax ni secuestro del scroll
     VISUAL_DENSITY 4    ritmo de aplicación web, no de galería ni de panel

   DECISIONES QUE NO SON OBVIAS
     · RADIO CERO EN TODO. Un único sistema de esquinas. Mezclar botones
       redondos con tarjetas rectas es lo que hace que una página parezca
       montada por partes.
     · ACENTO AZUL CONSERVADO (#1D4ED8). No es continuidad por pereza: las
       capturas del portafolio son interfaces azules. Un acento verde o
       naranja pelearía con cada imagen de la página.
     · SIN TARJETAS. Los bloques se separan con filetes y espacio. Una tarjeta
       solo se justifica cuando la elevación significa algo, y aquí no.
     · CLARO POR DEFECTO, OSCURO A PETICIÓN. El CSS no sigue
       `prefers-color-scheme` a propósito: main.js parte de que sin elección
       previa se está viendo el tema claro, y si el CSS siguiera al sistema el
       primer clic del botón no haría nada visible.
     · SIN RECORTES EN LAS CAPTURAS. Van a su proporción natural. Cada píxel
       recortado de una captura es texto de interfaz que deja de leerse.

   TIPOGRAFÍA: Inter autoalojada. Es una grotesca neutra, que es exactamente lo
   que pide un lector técnico. El carácter lo pone la escala y el interletraje,
   no una tipografía de exhibición.
   =========================================================================== */

/* CINCO PESOS, NO SIETE. Cada peso es un fichero de ~90 KB que el navegador
   descarga entero. La Light (300) tenía un solo uso —el rol bajo el titular—
   y la Black (900) dos —el logotipo y el número del 404—; ninguno de los tres
   distingue nada que el peso contiguo no distinga igual a ese tamaño. Quitar
   los dos ficheros ahorra 184 KB en la primera visita.
   No se subconjunta la fuente para adelgazarla más: la OFL considera eso una
   modificación y obligaría a dejar de llamarla «Inter» (ver
   LICENSES-terceros.md §2). */
@font-face { font-family: Inter; font-style: normal; font-weight: 400; font-display: swap;
             src: url("fonts/Inter-Regular.woff2") format("woff2"); }
@font-face { font-family: Inter; font-style: normal; font-weight: 500; font-display: swap;
             src: url("fonts/Inter-Medium.woff2") format("woff2"); }
@font-face { font-family: Inter; font-style: normal; font-weight: 600; font-display: swap;
             src: url("fonts/Inter-SemiBold.woff2") format("woff2"); }
@font-face { font-family: Inter; font-style: normal; font-weight: 700; font-display: swap;
             src: url("fonts/Inter-Bold.woff2") format("woff2"); }
@font-face { font-family: Inter; font-style: normal; font-weight: 800; font-display: swap;
             src: url("fonts/Inter-ExtraBold.woff2") format("woff2"); }

/* ----------------------------------------------------------------- Tokens -- */
:root {
  --fuente: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  /* ESCALA TIPOGRÁFICA. Cuatro tamaños de texto y tres de titular, y nada
     fuera de esta lista. Antes había nueve tamaños sueltos entre 11 y 19px
     repartidos por los componentes: dos <h3> del mismo nivel salían a 12px y
     a 28px, y los pies de imagen y las etiquetas de datos caían a 11-14px,
     por debajo del umbral cómodo de lectura.

     La jerarquía la marcan el peso y el color (--suave frente a --tinta), no
     un punto menos de cuerpo. Por eso el texto de apoyo comparte tamaño con
     el cuerpo: bajarlo a 15px no ordena la página, solo la hace más difícil
     de leer.

     --t-etiqueta  13px  SOLO versales con interletraje: índices, rótulos
     --t-ui        15px  controles: menú, botones, chips, notas del formulario
     --t-cuerpo    17px  todo párrafo, todo texto de apoyo, campos del formulario
     --t-guia      19px  entradillas (.lead) y los numerales de índice
     Los campos del formulario van a 17px a propósito: por debajo de 16px
     Safari en iOS hace zoom al enfocarlos. */
  --t-etiqueta: .8125rem;
  --t-ui:       .9375rem;
  --t-cuerpo:   1.0625rem;
  --t-guia:     1.1875rem;

  --t-h1: clamp(2.85rem, 7.5vw, 5rem);
  --t-h2: clamp(2rem, 4.2vw, 3rem);
  --t-h3: clamp(1.35rem, 2.2vw, 1.7rem);

  /* Neutros fríos. Ni negro puro ni blanco puro: los valores absolutos matan
     la profundidad y hacen que los filetes se vean sucios. */
  --base:      #F7F8FA;
  --superficie:#FDFDFE;
  --tinta:     #0D1117;
  --texto:     #2B3440;
  --suave:     #5A6572;
  --filete:    #E1E5EB;
  --filete-fuerte: #C9D0D9;

  --acento:      #1D4ED8;
  --acento-tinta:#1743BE;   /* para texto pequeño sobre claro */
  --acento-velo: color-mix(in srgb, var(--acento) 7%, transparent);

  /* Color del texto CUANDO EL FONDO ES EL ACENTO: botón al pasar el ratón y
     texto seleccionado. Es un token y no un `#fff` literal por una razón
     medida, no por pulcritud: en el tema oscuro el acento sube a #6B93FF, y
     blanco sobre ese azul da 2,90:1 —por debajo del 4,5:1 que pide la norma—.
     Con la tinta oscura son 6,67:1. En claro, blanco sobre #1D4ED8 da 6,70:1.
     Cada tema lleva el suyo. */
  --sobre-acento: #FFF;

  /* Único color de la página aparte del acento. Existe para un solo trabajo:
     decir que un envío falló. Un estado de error no puede ir del color de la
     marca, porque entonces no se distingue de un mensaje informativo. */
  --fallo:      #B42318;
  --fallo-tinta:#8A1B11;
  --fallo-velo: color-mix(in srgb, var(--fallo) 7%, transparent);

  /* Contorno de los campos del formulario. No usa `--filete-fuerte` como el
     resto de separadores: un filete decorativo puede ser tenue, pero el borde
     de un campo es lo que dice DÓNDE se puede escribir, y por eso la norma le
     pide 3:1 contra el fondo (WCAG 1.4.11). Con `--filete-fuerte` se quedaba
     en 1,46:1 y los tres campos casi desaparecían sobre el papel. */
  --campo-borde: #808A97;   /* 3,34:1 sobre --base. Medido, no estimado. */

  --radio: 0;
  --anchura: 1240px;
  --canal: clamp(20px, 4vw, 40px);
  --seccion: clamp(72px, 9vw, 128px);

  --curva: cubic-bezier(.16, 1, .3, 1);
  --rapido: .18s var(--curva);
  --medio:  .42s var(--curva);
}

:root[data-theme="dark"] {
  --base:      #0B0E13;
  --superficie:#12161D;
  --tinta:     #E9ECF2;
  --texto:     #C3CAD5;
  --suave:     #8D97A6;
  --filete:    #222834;
  --filete-fuerte: #333B4A;

  --acento:      #6B93FF;
  --acento-tinta:#8FADFF;
  --acento-velo: color-mix(in srgb, var(--acento) 12%, transparent);

  /* Tinta oscura y no blanco: ver el comentario del token en el tema claro.
     Blanco sobre este azul se queda en 2,90:1. */
  --sobre-acento: #0B0E13;

  --fallo:      #FF8A7A;
  --fallo-tinta:#FFB0A4;
  --fallo-velo: color-mix(in srgb, var(--fallo) 12%, transparent);

  --campo-borde: #6B7484;
}

/* ------------------------------------------------------------------- Base -- */
*, *::before, *::after { box-sizing: border-box; }

/* stylelint-disable-next-line property-no-vendor-prefix -- iOS Safari solo
   entiende la versión con prefijo, y es la que impide que el teléfono agrande
   el texto al girarse. El --fix de stylelint lo quitó una vez; no lo dejes. */
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--base);
  color: var(--texto);
  font-family: var(--fuente);
  font-size: var(--t-cuerpo);
  line-height: 1.65;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-feature-settings: "cv05" 1, "ss01" 1;
  overflow-x: hidden;
}

img { max-width: 100%; height: auto; display: block; }

/* `<picture>` existe solo para ofrecer el AVIF antes que el WebP: no es una
   caja del diseño. Con `display: contents` desaparece de la maquetación y el
   `<img>` sigue siendo hijo directo de la rejilla o de la figura, que es lo
   que suponen las reglas escritas antes de envolverlo.

   ⚠️ La segunda línea NO es opcional. El navegador no le da `display: none`
   a `<source>`: no lo pinta porque cuelga de un `<picture>`, que sí es una
   caja. Al quitar esa caja con `display: contents`, el `<source>` asciende a
   hijo de la rejilla y se convierte en un elemento más, vacío y de ancho
   completo. Medido en «Sobre mí»: el `<source>` ocupaba la primera columna
   (453px) y empujaba el retrato a la segunda (635px), justo la del texto. */
picture { display: contents; }
picture source { display: none; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; }

h1, h2, h3, h4 {
  margin: 0;
  color: var(--tinta);
  font-weight: 800;
  letter-spacing: -.03em;
  line-height: 1.08;
  text-wrap: balance;
}
h2 { font-size: var(--t-h2); }
h3 { font-size: var(--t-h3); letter-spacing: -.02em; line-height: 1.2; }

p { margin: 0 0 1.15em; max-width: 68ch; }
p:last-child { margin-bottom: 0; }

/* Entradilla. Un solo tamaño para todas: antes la de «Sobre mí» iba a 19px y
   la de cada proyecto a 17px, con lo que el mismo papel en la página se leía
   distinto según la sección. */
.lead { font-size: var(--t-guia); line-height: 1.55; }

:focus-visible {
  outline: 2px solid var(--acento);
  outline-offset: 3px;
}

::selection { background: var(--acento); color: var(--sobre-acento); }

.container {
  width: 100%;
  max-width: var(--anchura);
  margin-inline: auto;
  padding-inline: var(--canal);
}

.section { padding-block: var(--seccion); }
.section--filete { border-top: 1px solid var(--filete); }

/* Solo para lectores de pantalla. Se usa en los titulares que la composición
   no muestra pero que el documento necesita para no saltarse un nivel de
   encabezado. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* Salto al contenido: invisible hasta que recibe foco por teclado. */
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--tinta); color: var(--base);
  padding: .75rem 1.25rem; font-weight: 600;
}
.skip-link:focus { left: 0; }

/* Encabezado de sección: titular solo, sin la etiqueta pequeña en versales
   encima. Esa etiqueta repetida sección tras sección es la marca de agua de
   una página generada; el titular ya dice de qué va, y su posición en la
   página ya la clasifica. */
.cabecera { max-width: 62ch; margin-bottom: clamp(2.5rem, 5vw, 4rem); }
.cabecera p { margin-top: 1rem; font-size: var(--t-cuerpo); color: var(--suave); }

/* Centinela del scroll. Ver main.js. */
#topSentinel { position: absolute; top: 0; height: 1px; width: 1px; pointer-events: none; }

/* ------------------------------------------------------------- Cabecera -- */
.header {
  position: sticky; top: 0; z-index: 50;
  background: color-mix(in srgb, var(--base) 88%, transparent);
  backdrop-filter: saturate(180%) blur(14px);
  /* stylelint-disable-next-line property-no-vendor-prefix -- Safari sigue
     exigiendo el prefijo para el desenfoque. Sin esta línea la barra pierde
     el velo en todos los iPhone y en el Safari de escritorio. */
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid transparent;
  transition: border-color var(--rapido), background-color var(--rapido);
}
.header.is-scrolled { border-bottom-color: var(--filete); }

.header__inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1.5rem; height: 68px;
}

.logo {
  font-weight: 800; font-size: var(--t-cuerpo); letter-spacing: -.02em;
  color: var(--tinta);
}
.logo::after {
  content: ""; display: inline-block;
  width: 6px; height: 6px; margin-left: 7px; vertical-align: 2px;
  background: var(--acento);
}

.menu { display: flex; align-items: center; gap: clamp(1.25rem, 2.4vw, 2.25rem); }
.menu > a {
  font-size: var(--t-ui); font-weight: 500; color: var(--suave);
  padding-block: .35rem;
  border-bottom: 1px solid transparent;
  transition: color var(--rapido), border-color var(--rapido);
}
.menu > a:hover { color: var(--tinta); border-bottom-color: var(--acento); }

.menu__tools { display: flex; align-items: center; gap: .25rem; margin-left: .5rem; }
.icon-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .35rem;
  height: 34px; min-width: 34px; padding-inline: .5rem;
  background: none; border: 1px solid transparent; border-radius: var(--radio);
  color: var(--suave); cursor: pointer;
  font-size: var(--t-etiqueta); font-weight: 700; letter-spacing: .04em;
  transition: color var(--rapido), border-color var(--rapido), background-color var(--rapido);
}
.icon-btn:hover { color: var(--tinta); border-color: var(--filete); background: var(--superficie); }
.icon-btn svg { width: 17px; height: 17px; fill: none; stroke: currentcolor; stroke-width: 1.7;
                stroke-linecap: round; stroke-linejoin: round; }

:root[data-theme="dark"] .theme__sun { display: none; }
:root:not([data-theme="dark"]) .theme__moon { display: none; }

.burger {
  display: none; flex-direction: column; justify-content: center; gap: 5px;
  width: 40px; height: 40px; padding: 0;
  background: none; border: 0; cursor: pointer;
}
.burger span { display: block; height: 1.5px; width: 20px; background: var(--tinta);
               transition: transform var(--rapido), opacity var(--rapido); }
.burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
.burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }

/* El corte del menú es 760px porque main.js cierra el panel por encima de ese
   valor (`window.innerWidth > 760`). Si se cambia aquí, se cambia allí. */
@media (max-width: 760px) {
  .burger { display: flex; }
  .menu {
    position: fixed; inset: 68px 0 auto;
    flex-direction: column; align-items: stretch; gap: 0;
    padding: .5rem var(--canal) 1.5rem;
    background: var(--base);
    border-bottom: 1px solid var(--filete);
    transform: translateY(-8px); opacity: 0; pointer-events: none;
    transition: opacity var(--rapido), transform var(--rapido);
  }
  .menu.is-open { transform: none; opacity: 1; pointer-events: auto; }
  .menu > a { padding-block: .85rem; border-bottom: 1px solid var(--filete); font-size: var(--t-cuerpo); }
  .menu > a:hover { border-bottom-color: var(--filete); }
  .menu__tools { margin: 1rem 0 0; }
}

/* -------------------------------------------------------------- Botones -- */
.btn {
  display: inline-flex; align-items: center; gap: .55rem;
  padding: .85rem 1.4rem;
  border: 1px solid var(--tinta); border-radius: var(--radio);
  background: var(--tinta); color: var(--base);
  font-size: var(--t-ui); font-weight: 600; letter-spacing: -.01em;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--rapido), border-color var(--rapido),
              color var(--rapido), transform var(--rapido);
}
.btn:hover { background: var(--acento); border-color: var(--acento); color: var(--sobre-acento); }
.btn:active { transform: translateY(1px); }
.btn svg { width: 16px; height: 16px; flex: none; fill: none; stroke: currentcolor;
           stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }

/* ----------------------------------------------------------------- Hero -- */
/* Rejilla asimétrica con la fotografía llegando al borde derecho de la
   ventana. Nada centrado: el texto ancla a la izquierda y la imagen sale del
   contenedor, que es lo que le da tensión a la composición. */
.hero { position: relative; overflow: hidden; }

/* El sangrado izquierdo se calcula sobre el ancho del propio bloque (100%, que
   aquí es la ventana sin la barra de desplazamiento) y no sobre `100vw`, que
   la incluye y provocaría un desbordamiento horizontal de unos 15px. Así el
   titular cae exactamente sobre la misma vertical que el resto de secciones,
   mientras la columna derecha sigue llegando al borde de la ventana. */
.hero__grid {
  display: grid;
  gap: clamp(2rem, 5vw, 3.5rem);
  align-items: center;
  padding-left: max(var(--canal), calc((100% - var(--anchura)) / 2 + var(--canal)));

  /* Freno para ventanas desmesuradas (monitor 4K, o el zoom al 50%, que en
     píxeles CSS es lo mismo). El texto se ancla al contenedor de 1240px y la
     foto al borde derecho, así que cuanto más ancha es la ventana más se
     separan: a 3840px quedaba un vacío de más de mil píxeles entre una cosa y
     otra y el encabezado dejaba de leerse como una sola composición.
     Por debajo de 2800px este relleno vale cero y no cambia nada: la foto
     sigue sangrando hasta el borde, que es como está pensado el bloque. */
  padding-right: max(0px, calc((100% - 2800px) / 2));
}

.hero__text {
  padding-right: var(--canal);
  padding-block: clamp(3.5rem, 9vh, 5.5rem) clamp(2.5rem, 6vh, 4rem);
}

/* Las tres líneas del hero se apoyan en la misma escala que el resto de la
   página: el saludo es cuerpo, el rol es un h3 y la descripción es una
   entradilla. Antes cada una llevaba su propio clamp, de forma que en móvil
   el saludo bajaba a 15px sin que nada lo justificara. */
.hero__title { font-size: var(--t-h1); letter-spacing: -.045em; }
.hero__saludo {
  display: block; margin-bottom: .35rem;
  font-size: var(--t-cuerpo); font-weight: 500;
  letter-spacing: -.01em; color: var(--suave);
}
.hero__rol {
  display: block; margin-top: .5rem;
  font-size: var(--t-h3); font-weight: 400;
  letter-spacing: -.02em; color: var(--suave);
}
.hero__desc {
  margin: clamp(1.5rem, 3vh, 2.25rem) 0 clamp(1.75rem, 3.5vh, 2.5rem);
  font-size: var(--t-guia); line-height: 1.55;
  max-width: 46ch; color: var(--texto);
}

.hero__media { position: relative; min-height: 320px; }

/* La foto va en posición absoluta para que el alto de la caja lo mande la
   composición y no la proporción del fichero. Con `height: 100%` colgando de
   un padre que solo declara `min-height`, el alto no resuelve: la imagen se
   dibujaba a su tamaño natural (3:2) y arrastraba la caja. Pasados los
   3000px de ancho el encabezado llegaba a 1127px de alto y dejaba de caber en
   la ventana, que es justo lo que `min-height` pretendía evitar. */
.hero__media img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
}
.hero__media::after {
  /* Velo del color del acento sobre la fotografía, para que la foto pertenezca
     a la página en lugar de estar pegada encima. */
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(120deg, var(--base) 0%, transparent 42%);
  pointer-events: none;
}

@media (min-width: 900px) {
  .hero__grid {
    /* La columna de la foto tiene tope, igual que lo tiene el titular.
       El titular deja de crecer en su máximo de 5rem hacia los 1070px de
       ventana; la columna era `44vw` a secas y no dejaba de crecer nunca, así
       que a partir de ahí la foto se estiraba sola. Medido: pasaba de 7 veces
       el cuerpo del titular a 1280px, a 21 veces a 3840px. Ese mismo efecto es
       el que se ve al bajar el zoom, porque el zoom agranda la ventana en
       píxeles CSS: el texto se queda en su tope y la imagen sigue creciendo.

       53rem es el ancho que la columna alcanza en una ventana de 1920, que es
       donde está calibrada la composición. Por debajo de eso no cambia nada;
       por encima, la proporción entre foto y texto queda congelada. */
    grid-template-columns: minmax(0, 1fr) minmax(0, min(44vw, 53rem));
    min-height: min(88dvh, 760px);
    gap: clamp(2.5rem, 4vw, 4.5rem);
  }
  .hero__text { padding-block: clamp(4rem, 8vh, 6rem); }
  .hero__media { min-height: min(88dvh, 760px); }
  .hero__media::after { background: linear-gradient(100deg, var(--base) 0%, transparent 30%); }
}
@media (max-width: 899px) {
  /* `min-height: 0` anula el mínimo de la regla base, que aquí sobra y hace
     daño: con la foto fuera del flujo, la caja se dimensiona sola, y 320px de
     alto obligado más una proporción de 16/10 dan 512px de ancho. En una
     pantalla de 390px eso desbordaba, y solo no se veía porque el `body`
     recorta el desbordamiento horizontal. Con el mínimo a cero manda el ancho
     de la columna y el alto sale de la proporción, que es lo que se buscaba. */
  .hero__media { aspect-ratio: 16 / 10; min-height: 0; }
  .hero__media::after { background: linear-gradient(0deg, var(--base) 0%, transparent 45%); }
}

/* ---------------------------------------------------------------- Cifras -- */
/* Fila de datos separada por filetes verticales, sin cajas. Las cifras van en
   cifras tabulares para que las comas decimales caigan alineadas. */
.cifras {
  display: grid; gap: 1px;
  background: var(--filete);
  border-block: 1px solid var(--filete);
  list-style: none; margin: 0; padding: 0;
}
@media (min-width: 620px) { .cifras { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 980px) { .cifras { grid-template-columns: repeat(4, 1fr); } }

.cifra { background: var(--base); padding: clamp(1.5rem, 3vw, 2.25rem) clamp(1.25rem, 2.5vw, 2rem); }
.cifra b {
  display: block; margin-bottom: .5rem;
  font-size: clamp(2rem, 4vw, 2.75rem); font-weight: 800; letter-spacing: -.04em;
  color: var(--tinta); font-variant-numeric: tabular-nums;
}
.cifra span { display: block; font-size: var(--t-cuerpo); line-height: 1.5; color: var(--suave); max-width: 30ch; }

/* --------------------------------------------------------------- Sobre mí -- */
.sobre { display: grid; gap: clamp(2.5rem, 5vw, 4.5rem); align-items: start; }
@media (min-width: 860px) { .sobre { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); } }

.sobre__foto { border: 1px solid var(--filete); }
.sobre__texto h2 { margin-bottom: 1.5rem; }
.sobre__texto p { color: var(--texto); }
.sobre__texto p.lead { color: var(--tinta); font-weight: 500; }

.stack { display: grid; gap: 1.75rem; margin-top: 2.5rem; padding-top: 2rem; border-top: 1px solid var(--filete); }
@media (min-width: 560px) { .stack { grid-template-columns: repeat(3, 1fr); gap: 1.5rem; } }
/* Único <h3> de la página que no usa la escala de titulares: aquí el elemento
   es un rótulo de grupo, no un subtítulo. Va en versales con interletraje
   abierto, que es lo que permite bajar a 13px sin perder legibilidad. */
.stack h3 {
  font-size: var(--t-etiqueta); font-weight: 700; letter-spacing: .09em;
  line-height: 1.4; text-transform: uppercase;
  color: var(--suave); margin-bottom: .9rem;
}
.chips { display: flex; flex-wrap: wrap; gap: .4rem; list-style: none; margin: 0; padding: 0; }
.chips li {
  font-size: var(--t-ui); font-weight: 500; padding: .3rem .65rem;
  color: var(--texto); background: var(--superficie);
  border: 1px solid var(--filete); border-radius: var(--radio);
}

/* ---------------------------------------------------------------- Método -- */
/* Numeración grande en el canalón izquierdo. No son tarjetas: cada paso se
   apoya en un filete superior y en el número, que hace de ancla visual. */
.metodo { display: grid; gap: 0; list-style: none; margin: 0; padding: 0; counter-reset: paso; }
@media (min-width: 780px) { .metodo { grid-template-columns: 1fr 1fr; column-gap: clamp(2.5rem, 5vw, 5rem); } }

.metodo > li {
  counter-increment: paso;
  display: grid; grid-template-columns: auto 1fr; gap: 1.25rem;
  padding-block: clamp(1.75rem, 3vw, 2.5rem);
  border-top: 1px solid var(--filete);
}
/* El numeral hace de ancla visual del paso, así que tiene que leerse como
   tal. A 13px quedaba por debajo del texto que acompaña y el ancla no se
   veía; a 19px tabular sostiene el titular sin competir con él. */
.metodo > li::before {
  content: counter(paso, decimal-leading-zero);
  font-size: var(--t-guia); font-weight: 700; letter-spacing: .01em;
  color: var(--acento); font-variant-numeric: tabular-nums;
  line-height: 1.2; padding-top: .1rem;
}
.metodo h3 { margin-bottom: .6rem; }
.metodo p { font-size: var(--t-cuerpo); color: var(--suave); }

/* ------------------------------------------------------------- Portafolio -- */
.aviso {
  display: flex; gap: .85rem; align-items: flex-start;
  padding: 1.15rem 1.35rem;
  background: var(--acento-velo);
  border-left: 2px solid var(--acento);
  font-size: var(--t-cuerpo); line-height: 1.6;
  margin-bottom: clamp(3rem, 6vw, 5rem);
}
.aviso p { max-width: 88ch; }
.aviso svg { flex: none; width: 19px; height: 19px; margin-top: .2rem;
             fill: none; stroke: var(--acento); stroke-width: 1.7; }
.aviso strong { color: var(--tinta); font-weight: 600; }

.caso + .caso { margin-top: clamp(4.5rem, 9vw, 8rem); }

/* Fila de encabezado del proyecto: índice y título a la izquierda, acciones a
   la derecha, filete debajo. Es la única fila del sitio con esta forma, así
   que marca el arranque de cada caso sin necesidad de etiquetas. */
.caso__head {
  display: grid; gap: 1.25rem; align-items: end;
  padding-bottom: 1.5rem; border-bottom: 1px solid var(--filete-fuerte);
}
@media (min-width: 780px) { .caso__head { grid-template-columns: 1fr auto; gap: 2rem; } }

/* Mismo tratamiento que el numeral del método: un índice se lee o no sirve
   de índice. */
.caso__indice {
  display: block; margin-bottom: .5rem;
  font-size: var(--t-guia); font-weight: 700; letter-spacing: .02em;
  line-height: 1.2; color: var(--acento); font-variant-numeric: tabular-nums;
}
.caso__kicker { margin-top: .6rem; font-size: var(--t-cuerpo); color: var(--suave); max-width: 52ch; }

.caso__figura { margin: clamp(1.75rem, 3.5vw, 2.75rem) 0 0; }
.caso__figura img { width: 100%; border: 1px solid var(--filete); }

.caso__cuerpo { display: grid; gap: clamp(2rem, 4vw, 3.5rem); margin-top: clamp(2rem, 4vw, 3rem); }
@media (min-width: 860px) { .caso__cuerpo { grid-template-columns: minmax(0, 6fr) minmax(0, 5fr); } }
.caso__cuerpo .lead { color: var(--texto); }

.datos { display: grid; gap: 1.35rem; margin: 0; }
.datos > div { display: grid; gap: .35rem; padding-top: 1rem; border-top: 1px solid var(--filete); }
.datos dt {
  font-size: var(--t-etiqueta); font-weight: 700; letter-spacing: .09em;
  line-height: 1.4; text-transform: uppercase;
  color: var(--acento-tinta);
}
.datos dd { margin: 0; font-size: var(--t-cuerpo); line-height: 1.6; color: var(--suave); }

.caso__acciones { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; }
@media (min-width: 780px) {
  /* 440px y no 400: las etiquetas de tecnología subieron de 13 a 15px y con el
     ancho anterior la fila se partía en tres. */
  .caso__acciones { max-width: 440px; justify-content: flex-end; }
  .caso__acciones .chips { justify-content: flex-end; }
}

/* ---------------------------------------------------------- Trayectoria -- */
.trayecto { list-style: none; margin: 0; padding: 0; }
.trayecto > li {
  display: grid; gap: .5rem 2.5rem;
  padding-block: clamp(1.75rem, 3vw, 2.5rem);
  border-top: 1px solid var(--filete);
}
@media (min-width: 720px) { .trayecto > li { grid-template-columns: 150px 1fr; } }
.trayecto h3 { margin-bottom: .25rem; }
.trayecto p { font-size: var(--t-cuerpo); color: var(--suave); }

/* Los dos selectores llevan el `p` delante para empatar en especificidad con
   `.trayecto p` y ganar por orden. Sin él la regla genérica se imponía y ni
   la fecha ni el nombre de la empresa recibían nunca su tamaño ni su color:
   un fallo silencioso, porque el texto se veía, solo que mal. */
p.trayecto__fecha {
  font-weight: 600; letter-spacing: .01em;
  color: var(--suave); font-variant-numeric: tabular-nums; padding-top: .15rem;
}

/* Cargos dentro de una misma empresa. Sin viñetas y sin filetes por fila: el
   bloque de la empresa ya viene separado por el suyo, y una línea por cargo
   cuando hay uno o dos convierte una lista corta en una tabla de datos.
   Los años se alinean a la derecha en cifras tabulares para que las cuatro
   columnas de fechas caigan sobre la misma vertical. */
/* En estrecho cada cargo ocupa dos líneas (puesto encima, años debajo), así
   que la separación ENTRE cargos tiene que ser mayor que la de dentro de uno,
   o los cuatro renglones se leen como una sola lista de cuatro cosas. */
.cargos { list-style: none; margin: .7rem 0 0; padding: 0; display: grid; gap: 1rem; }
.cargos > li { display: grid; gap: .1rem 1.5rem; }
@media (min-width: 560px) {
  .cargos { gap: .55rem; }
  .cargos > li { grid-template-columns: minmax(0, 1fr) auto; align-items: baseline; }
}
.cargos__rol { color: var(--texto); }
.cargos__anios { color: var(--suave); font-variant-numeric: tabular-nums; }

.formacion { display: grid; gap: 1px; background: var(--filete); border: 1px solid var(--filete); list-style: none; margin: 0; padding: 0; }
@media (min-width: 720px) { .formacion { grid-template-columns: 1fr 1fr; } }
.formacion > li { background: var(--base); padding: clamp(1.5rem, 3vw, 2.25rem); }
/* Sin tamaño propio: es un <h3> y se lee como los demás <h3> de la página.
   Estaba a 1.25rem mientras los de método y trayectoria iban a 1.75rem. */
.formacion h3 { margin-bottom: .4rem; }
.formacion p { font-size: var(--t-cuerpo); color: var(--suave); }

/* Mismo empate de especificidad que en la trayectoria: el rótulo iba con el
   color gris de la regla genérica en vez de con el del acento.
   Se llamaba `formacion__anio` cuando llevaba una fecha; ahora nombra el tipo
   de estudio, así que el nombre de la clase acompaña. */
p.formacion__tipo { font-weight: 600; color: var(--acento-tinta); margin-bottom: .6rem; }

/* -------------------------------------------------------------- Contacto -- */
.contacto { display: grid; gap: clamp(2.5rem, 5vw, 4.5rem); align-items: start; }
@media (min-width: 880px) { .contacto { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); } }

.contacto__pitch h2 { margin-bottom: 1.25rem; }
.contacto__pitch p { color: var(--suave); font-size: var(--t-cuerpo); }

.directo { list-style: none; margin: 2rem 0 0; padding: 0; }
.directo > li { border-top: 1px solid var(--filete); }
.directo a, .directo span.dato {
  display: flex; align-items: center; gap: .85rem;
  padding-block: .95rem;
  font-size: var(--t-cuerpo); color: var(--texto);
  transition: color var(--rapido);
}
.directo a:hover { color: var(--acento); }
.directo svg { width: 18px; height: 18px; flex: none; fill: none; stroke: currentcolor;
               stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.directo b { font-weight: 600; color: var(--tinta); min-width: 104px; }

.form { display: grid; gap: 1.25rem; }
.form label { display: grid; gap: .45rem; }
.form label > span { font-size: var(--t-ui); font-weight: 600; color: var(--tinta); letter-spacing: -.005em; }
.form input, .form textarea {
  width: 100%; padding: .8rem .9rem;
  font: inherit; font-size: var(--t-cuerpo);
  color: var(--tinta); background: var(--superficie);
  border: 1px solid var(--campo-borde); border-radius: var(--radio);
  transition: border-color var(--rapido), box-shadow var(--rapido);
}
.form input::placeholder, .form textarea::placeholder { color: var(--suave); }
.form input:focus, .form textarea:focus {
  outline: none; border-color: var(--acento);
  box-shadow: 0 0 0 3px var(--acento-velo);
}
.form textarea { resize: vertical; min-height: 132px; }
.form .btn { justify-self: start; }

/* Mientras el mensaje viaja. El botón se apaga al gris medio para que nadie
   envíe lo mismo tres veces creyendo que no pasó nada, y el aviso de al lado
   cuenta lo que está ocurriendo.
   No se apaga con `opacity`, que es lo habitual: el texto blanco sobre el
   resultado quedaba en 3,5:1. Los controles inactivos están exentos del
   mínimo de contraste, pero este no es un control muerto en un formulario que
   no toca nadie, es un estado de dos segundos que el visitante está mirando.
   Con `--suave` de fondo el texto se lee a 5,4:1 en claro y a 7,8:1 en
   oscuro. */
.form .btn[disabled],
.form .btn[disabled]:hover,
.form .btn[disabled]:active {
  background: var(--suave); border-color: var(--suave); color: var(--base);
  cursor: progress; transform: none;
}

/* Trampa para robots. Fuera de la pantalla en lugar de `display:none`: hay
   robots que descartan lo que está oculto, y este campo solo sirve si lo
   rellenan. */
.form__trampa {
  position: absolute; left: -9999px; top: auto;
  width: 1px; height: 1px; overflow: hidden;
}

/* Aviso del formulario. Tres tonos: informativo con el acento de la marca,
   correcto igual (no hace falta un verde para decir que algo salió bien) y
   fallo en rojo. El rojo es el único color de la página fuera del acento, y
   está porque comunica un estado real, no porque decore. */
.form__nota {
  margin: 0; padding: .8rem 1rem; font-size: var(--t-ui);
  background: var(--acento-velo); border-left: 2px solid var(--acento); color: var(--tinta);
}
.form__nota--ok { font-weight: 500; }
.form__nota--error {
  background: var(--fallo-velo); border-left-color: var(--fallo); color: var(--fallo-tinta);
}

/* Aviso de privacidad. Va bajo el botón, sin caja y en el gris de apoyo: es
   información que hay que dar antes de pulsar, no una advertencia que asustar.
   El tamaño es el de los controles y no el de las etiquetas: 13px sería la
   letra pequeña de un contrato, y esto es justo lo contrario de letra pequeña.
   `max-width` en medida de texto para que no se lea como una línea larga. */
.form__privacidad {
  margin: -.4rem 0 0; font-size: var(--t-ui); line-height: 1.55;
  color: var(--suave); max-width: 62ch;
}
.form__privacidad a {
  color: var(--acento-tinta); font-weight: 500;
  text-decoration: underline; text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
.form__privacidad a:hover { color: var(--acento); }

/* ------------------------------------------------------------------ Pie -- */
.footer { border-top: 1px solid var(--filete); padding-block: 2.5rem 3rem; }
.footer__inner { display: grid; gap: .6rem; font-size: var(--t-ui); color: var(--suave); }
@media (min-width: 780px) {
  .footer__inner { grid-template-columns: 1fr auto; align-items: center; gap: 1rem 2rem; }
  .footer__credit { grid-column: 1 / -1; padding-top: 1rem; border-top: 1px solid var(--filete); }
}
.footer p { margin: 0; max-width: none; }
.footer a { color: var(--texto); border-bottom: 1px solid var(--filete-fuerte); }
.footer a:hover { color: var(--acento); border-bottom-color: var(--acento); }

/* -------------------------------------------------------------- Error 404 -- */
.error { min-height: 62dvh; display: grid; align-items: center; padding-block: var(--seccion); }
.error__code {
  font-size: clamp(4rem, 14vw, 8rem); font-weight: 800; letter-spacing: -.05em;
  line-height: 1; color: var(--acento); margin-bottom: 1rem;
}
.error h1 { font-size: var(--t-h2); margin-bottom: 1rem; }
.error .lead { color: var(--suave); margin-bottom: 2rem; max-width: 46ch; }

/* -------------------------------------------------------------- Movimiento -- */
/* Todas las reglas de aparición cuelgan de `.js`, que añade main.js. Si ese
   fichero no llega a cargarse, `.js .reveal` no casa con nada y la página se
   ve entera. Nunca al revés: ocultar por defecto y confiar en el JS es como
   una animación decorativa deja un sitio en blanco. */
@media (prefers-reduced-motion: no-preference) {
  .js .reveal {
    opacity: 0; transform: translateY(18px);
    transition: opacity .7s var(--curva), transform .7s var(--curva);
    transition-delay: calc(var(--i, 0) * 70ms);
  }
  .js .reveal.is-visible { opacity: 1; transform: none; }

  /* Escalonado por posición entre hermanos. Va en CSS y no en un atributo
     `style` porque la CSP del sitio prohíbe estilos en línea. */
  .js .reveal:nth-child(2) { --i: 1; }
  .js .reveal:nth-child(3) { --i: 2; }
  .js .reveal:nth-child(4) { --i: 3; }

  .caso__figura img, .sobre__foto {
    transition: border-color var(--medio), transform var(--medio);
  }
  .caso__figura:hover img { border-color: var(--filete-fuerte); }
}

/* Sin `prefers-reduced-motion` no se anima nada: ni aparición, ni
   desplazamiento suave, ni transiciones de puntero. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

/* ----------------------------------------------------------------- Papel -- */
@media print {
  .header, .burger, .menu, .skip-link, .form, .hero__media::after { display: none !important; }
  body { background: #fff; color: #000; font-size: 11pt; }
  .section { padding-block: 1.5rem; }
  .js .reveal { opacity: 1 !important; transform: none !important; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 9pt; color: #555; }
}
