/* portfolio.css · home: hero, marquesina, proyectos, filas, stack, sobre mí, contacto, cursor, preloader, diagramas.
   Regla de oro (architecture-v2 §3.3): ninguna regla oculta contenido; los estados iniciales los pone motion.js. */
/* zoom de asentamiento del disco en la entrada (motion.js). Registrada: un cambio sólo recalcula el estilo de la capa */
@property --bh-z{syntax:"<number>";inherits:false;initial-value:1}
@layer components{
/* ---------- hero (addendum mobile-first + hero v3: sólo nombre, rol, lead ≥ 40em e invitación a bajar) ----------
   El vídeo/poster de Gargantua es el fondo a sangre completa en TODOS los anchos; el texto se compone encima, en la zona
   oscura del encuadre. < 60em: fila 1 vacía (el disco), nombre en la mitad inferior, rol + invitación en la base del viewport.
   ≥ 60em: rol/lead/invitación arriba a la izquierda, nombre abajo (12–14 vw), disco arriba a la derecha.
   touch-action: pan-y pinch-zoom → el arrastre horizontal mueve el fondo (parallax) sin secuestrar el scroll vertical. */
.hero{position:relative;min-height:100svh;display:grid;grid-template-rows:minmax(34svh,1fr) auto auto;gap:var(--space-s);padding:4.5rem var(--gutter) var(--space-l);isolation:isolate;overflow:clip;touch-action:pan-y pinch-zoom}
html.motion-ok .hero{position:sticky;top:0;z-index:var(--z-bh)}
html.motion-ok .hero~*{position:relative;z-index:var(--z-cover);background:var(--surface-0)}
.mk-bh{position:absolute;inset:0;z-index:var(--z-bh);overflow:hidden;will-change:transform,opacity;background:var(--surface-0)}
.mk-bh-layer{position:absolute;inset:0;transform-origin:50% 45%;scale:var(--bh-z,1)} /* scale: entrada (--bh-z); transform: parallax (hero-video.js) */
.mk-bh.has-parallax .mk-bh-layer{will-change:transform}
.mk-bh img,.mk-bh video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:50% 50%}
.mk-bh video{opacity:0;transition:opacity .4s linear}
/* velo negro (26 %) sobre todo el fondo y bajo el texto, más una sombra bajo la barra: el halo del disco llega a los enlaces del menú */
.mk-bh::before{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;background:linear-gradient(to bottom,color-mix(in srgb,var(--surface-0) 72%,transparent),transparent min(9rem,20vh)) color-mix(in srgb,var(--surface-0) 26%,transparent)}
/* escritorio: el bucle se escala hacia la esquina superior derecha para que el halo del disco no pise el texto (poster incluido, es el mismo fotograma) */
@media (min-width:60em){.mk-bh-layer img,.mk-bh-layer video{transform-origin:100% 0;transform:scale(.88)}}
.mk-bh.is-video video{opacity:1}
/* viñeta mínima en el borde inferior: garantiza el contraste del nombre cuando el halo del disco crece (addendum §1.2) */
.mk-bh::after{content:"";position:absolute;inset:auto 0 0 0;height:58%;background:var(--hero-shade);pointer-events:none}
.hero-top,.hero-name{position:relative;z-index:var(--z-hero-text)}
/* foco de teclado dentro de rol/invitación: se ve aunque el scrub del despeje lo haya llevado a 0 (WCAG 2.4.7). :has(:focus-visible) y no
   :focus-within: el clic en la invitación también enfoca y no debe congelar el despeje durante el scroll a #proyectos */
.hero-top:has(:focus-visible){opacity:1!important;transform:none!important;pointer-events:auto!important}
/* Nombre alineado a la izquierda en todos los anchos. ≥ 60em (y móvil en horizontal) «a medida»: «Gallego Soto» ocupa
   exactamente la medida del hero (100cqi = caja de contenido; --fit = ancho de tinta de esa línea en em, Bricolage 800 con
   tracking −.045em, medido: «Gallego» 3,34 · «Gallego Soto» 5,64; re-medir si cambia la fuente o el tracking).
   < 60em (salvo móvil en horizontal ≤ 30em de alto), el tamaño del hero v2 (24vw, tope 7,5rem). --name-cap limita por altura en todos los casos.
   padding bajo «Jorge»: su «g» cae sobre las ascendentes de «ll» de «Gallego»; .1em las separa sin abrir el interlineado.
   margin -.04em: sangría óptica, la tinta de G/S (lado izquierdo redondo) queda alineada con el borde del rol. */
.hero{container-type:inline-size;--bh-x:66;--bh-y:28} /* centro de la sombra del agujero en el poster (% del hero): destino de la caída de las letras */
.hero-name{--fit:3.34;--name-cap:100cqi;grid-row:2;align-self:end;display:grid;grid-template-columns:max-content;justify-content:start;
  font-size:min(calc(100cqi / var(--fit)),var(--name-cap));line-height:.86;letter-spacing:-.045em;margin-inline-start:-.04em}
.hero-name .w{display:block}
.hero-name .w:first-child{padding-bottom:.1em}
.hero-top{grid-row:3;display:grid;gap:var(--space-s);justify-items:start}
.hero-role{font-size:var(--step-2);font-weight:600;letter-spacing:-.02em;line-height:1.06;color:var(--ink-strong)}
.hero .lead{font-size:var(--step-0);line-height:1.5;max-width:30em}
/* compensación óptica: la altura de x de Instrument Serif es ~15 % menor que la de Bricolage; a 1em «backend» parecía un cuerpo menor */
.hero-role i{font-size:1.14em;line-height:0}
.hero-top p{margin-top:0}
/* ---------- invitación a bajar (sustituye al botón «Ver proyectos») ----------
   Un mini horizonte de sucesos: anillo fino, una partícula azul con estela que orbita como el disco del fondo y, al llegar
   abajo, se suelta y cae; donde se apaga se abre una onda (la «caída» hacia la sección siguiente) y la flecha central
   acompaña. Es un enlace real a #proyectos (nombre accesible en .sr, objetivo 56–72 px). Sin JS o con reduce: estático
   (partícula arriba, sin bucle; la animación sólo existe con .motion-ok y arranca con la entrada del hero, .cue-live).
   Sólo transform/opacity sobre capas HTML (compositor). El bucle se pausa cuando el hero se despeja (.cue-off, motion.js).
   < 60em: a la derecha del rol, bajo el pulgar; ≥ 60em: donde estaba el botón, bajo el lead.
   --cue: diámetro del anillo · --cue-drop: lo que cae la partícula bajo el anillo (y dónde se abre la onda) */
.hero-cue{--cue:3.5rem;--cue-drop:2.25rem;line-height:0}
.cue{position:relative;display:grid;place-items:center;width:calc(var(--cue) + .5rem);height:calc(var(--cue) + .5rem);margin:-.25rem;border-radius:50%;color:var(--ink-strong);text-decoration:none;-webkit-tap-highlight-color:transparent}
.cue-mark{position:relative;display:block;width:var(--cue);height:var(--cue);border-radius:50%;box-shadow:inset 0 0 0 1.5px var(--line-3);transition:scale var(--dur-3) var(--ease)}
/* relleno azul que crece al pasar el puntero o con foco: el horizonte «se cierra» y la flecha queda sobre el acento */
.cue-mark::before{content:"";position:absolute;inset:0;border-radius:50%;background:var(--accent);opacity:0;transform:scale(.4);transition:opacity var(--dur-3) var(--ease),transform var(--dur-3) var(--ease)}
.cue-orbit,.cue-spin{position:absolute;inset:0;border-radius:50%}
.cue-fall{position:absolute;inset:0}
/* estela: un cuarto de anillo que se desvanece detrás de la partícula (cónico recortado a 2 px sobre el anillo) */
.cue-tail{position:absolute;inset:0;border-radius:50%;background:conic-gradient(from 270deg,transparent,var(--accent-text) 90deg,transparent 0);
  -webkit-mask:radial-gradient(farthest-side,transparent calc(100% - 2.5px),black calc(100% - 2px));mask:radial-gradient(farthest-side,transparent calc(100% - 2.5px),black calc(100% - 2px));opacity:.9}
/* partícula: núcleo claro y halo azul en un único degradado radial (más barato y nítido que sombras apiladas mientras gira) */
.cue-dot{position:absolute;top:0;left:50%;width:22px;height:22px;margin:-11px 0 0 -11px;border-radius:50%;
  background:radial-gradient(closest-side,var(--ink-strong) 0 32%,color-mix(in srgb,var(--accent) 85%,transparent) 42%,color-mix(in srgb,var(--accent) 30%,transparent) 66%,transparent)}
/* onda de llegada: una elipse (perspectiva de superficie) que se abre y se apaga donde desaparece la partícula */
.cue-sink{position:absolute;left:50%;top:calc(100% + var(--cue-drop) * .85);width:1.75rem;height:.625rem;margin:-.3125rem 0 0 -.875rem;border-radius:50%;box-shadow:inset 0 0 0 1.5px var(--accent-text),0 0 6px color-mix(in srgb,var(--accent) 60%,transparent);opacity:0;pointer-events:none}
.cue-arrow{position:absolute;left:50%;top:50%;width:1.15rem;height:1.15rem;margin:-.575rem 0 0 -.575rem;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;transition:color var(--dur-2) var(--ease)}
@media (hover:hover){.cue:hover .cue-mark::before{opacity:1;transform:none}.cue:hover .cue-arrow{color:var(--on-accent)}.cue:hover .cue-mark{scale:1.06}}
.cue:focus-visible .cue-mark::before{opacity:1;transform:none}
.cue:focus-visible .cue-arrow{color:var(--on-accent)}
.cue:active .cue-mark{scale:.94}
/* el despeje al scroll anima opacity y transform de .cue-mark (motion.js; el hover usa scale, que se compone aparte);
   con foco de teclado se ve siempre (WCAG 2.4.7) */
.cue:focus-visible .cue-mark{opacity:1!important;transform:none!important}
/* colores forzados: box-shadow y fondos desaparecen → borde del anillo, flecha en LinkText y contorno de foco propio */
@media (forced-colors:active){.cue-mark{box-shadow:none;border:1px solid CanvasText}.cue-mark::before{display:none}.cue-arrow,.cue:is(:hover,:focus-visible) .cue-arrow{color:LinkText}.cue:focus-visible{outline:2px solid Highlight;outline-offset:2px;border-radius:50%}}
/* bucle (3,6 s): 1,5 vueltas con aceleración hacia abajo → la partícula se suelta y cae (ease-in) → la onda se abre bajo
   el anillo → reaparece arriba. La estela acompaña la órbita y se apaga al soltarse; la flecha cede 3 px con la caída */
html.motion-ok .cue-live .cue-orbit,html.motion-ok .cue-live .cue-spin{animation:cue-orbit 3.6s cubic-bezier(.5,0,.3,1) infinite both}
html.motion-ok .cue-live .cue-tail{animation:cue-tail 3.6s linear infinite both}
html.motion-ok .cue-live .cue-fall{animation:cue-fall 3.6s linear infinite both}
html.motion-ok .cue-live .cue-sink{animation:cue-sink 3.6s linear infinite both}
html.motion-ok .cue-live .cue-arrow{animation:cue-nudge 3.6s ease-in-out infinite both}
html.motion-ok .hero.cue-off .cue *{animation-play-state:paused}
@keyframes cue-orbit{0%{transform:rotate(0deg)}62%,100%{transform:rotate(540deg)}}
@keyframes cue-tail{0%{opacity:0}14%{opacity:.9}56%{opacity:.9}64%,100%{opacity:0}}
@keyframes cue-fall{0%{transform:translateY(0);opacity:0}10%{opacity:1}62%{transform:translateY(0);opacity:1;animation-timing-function:cubic-bezier(.55,0,1,.45)}86%{transform:translateY(var(--cue-drop));opacity:0}100%{transform:translateY(0);opacity:0}}
@keyframes cue-sink{0%,77%{transform:scale(.15);opacity:0;animation-timing-function:cubic-bezier(.2,.7,.3,1)}81%{opacity:1}100%{transform:scale(1);opacity:0}}
@keyframes cue-nudge{0%,58%{transform:translateY(0)}70%{transform:translateY(3px)}86%,100%{transform:translateY(0)}}
/* barra en la intro (nav.js, .is-intro, sólo con JS y sin reduce): el nombre grande ya está en el hero, la marca es sólo el monograma.
   Opacidad, no display: el enlace conserva su nombre accesible */
.brandlink span,.brandlink i{transition:opacity .4s var(--ease)}
.bar.is-intro .brandlink span,.bar.is-intro .brandlink i{opacity:0}
/* con foco o puntero encima, el enlace enseña lo que nombra: el anillo de foco y la zona activa no rodean un hueco vacío */
.bar.is-intro .brandlink:is(:focus-visible,:hover) :is(span,i){opacity:1}
.bar.no-tr .brandlink span,.bar.no-tr .brandlink i{transition:none}
/* máscaras de SplitText: la caja de recorte cubre ascendentes, tildes y descendentes sin cambiar el interlineado */
.hero-name .char-mask{display:inline-block;line-height:1;padding:.2em .08em;margin:-.27em -.08em}
[data-lines]:has(>.line-mask){display:flex;flex-direction:column;align-items:stretch}
.line-mask{padding-block:.1em;margin-block:-.1em}
@media (max-width:59.99em){
  /* sin JS la barra es estática (dos filas) y el hero no necesita hueco arriba */
  html:not(.js) .hero{padding-top:var(--space-s)}
  /* la invitación queda a la altura del pulgar, separado del borde inferior (barra de gestos / del navegador) */
  .hero{padding-bottom:3rem;grid-template-rows:minmax(22svh,1fr) auto auto}
  /* tope por altura: alto − banda mínima del disco (22svh) − barra, rol, lead y márgenes (19rem; 13rem sin lead; la invitación va junto al rol, no en otra fila),
     entre las 3 líneas del nombre (3 × .86 + .1 = 2,68em). Así el rol cabe también con la barra del navegador a la vista
     (390×664, 375×548) */
  .hero-name{font-size:min(24vw,7.5rem,var(--name-cap));--name-cap:max(3rem,calc((78svh - 19rem) / 2.68))}
  /* la invitación a bajar va a la derecha del rol (y del lead ≥ 40em), en la zona del pulgar; alineada a la base */
  .hero-top{grid-template-columns:minmax(0,1fr) auto;column-gap:var(--space-m);align-items:end}
  .hero-top>*{grid-column:1}
  .hero-top>.hero-cue{grid-column:2;grid-row:1 / span 2;align-self:end}
}
/* poster vertical (780×1688: misma proporción que 390×844) sólo en vertical; en tablet se ancla arriba para que el disco
   quede bajo la barra. En horizontal (< 60em) va el poster apaisado: el disco arriba a la derecha, lejos del nombre */
@media (max-width:59.99em) and (orientation:portrait){.mk-bh img,.mk-bh video{object-position:50% 42%}}
@media (max-width:59.99em) and (orientation:landscape){
  .hero{--bh-x:86;--bh-y:15} /* sombra del disco tras el scale(.74) hacia la esquina superior derecha */
  .mk-bh-layer img,.mk-bh-layer video{transform-origin:100% 0;transform:scale(.74)}
  .mk-bh::after{height:82%}
}
@media (max-width:39.99em){
  .hero-name{--name-cap:max(3rem,calc((78svh - 13rem) / 2.68))}
  /* el lead queda fuera de la primera pantalla del móvil (addendum §1.2: nombre, rol y una única llamada); su contenido está en «Sobre mí» */
  .hero .lead{display:none}
  .hero-role{font-size:var(--step-1)}
  .hero-top>.hero-cue{grid-row:1}
}
/* móvil vertical: el rol ocupa casi todo el ancho; el anillo sube al hueco a la derecha de «Soto» (≥ 40 % del ancho libre a
   24vw), con su base en la línea base del nombre y su borde derecho en el de la hamburguesa: logo y nombre a la izquierda,
   menú e invitación a la derecha. Bajo el pulgar (≈ 80 % del alto) */
@media (max-width:39.99em) and (orientation:portrait){
  .hero-top{display:block;position:relative}
  .hero-role{max-width:none}
  .hero-cue{position:absolute;right:0;bottom:calc(100% + var(--space-s))}
}
/* ≤ 23em (320–368 px): «Gallego» llega casi al borde derecho y el rol pasa por debajo del anillo: anillo menor (ni él ni la
   órbita rozan la «o»), caída corta que se apaga antes de la altura de las mayúsculas del rol y sin onda */
@media (max-width:23em) and (orientation:portrait){.hero-cue{--cue:3rem;--cue-drop:1rem}.cue-sink{display:none}}
/* 40–59,99em (tableta vertical, móvil horizontal): el anillo apoya en el padding inferior del hero; caída corta para que
   partícula y onda no salgan del margen ni rocen el borde (indicador de inicio del iPhone en horizontal) */
@media (min-width:40em) and (max-width:59.99em){.hero-cue{--cue-drop:1.5rem}}
/* móvil vertical bajo (≤ 576 px de alto: 375×548, iPhone SE con la barra de Safari): banda del disco y margen inferior más
   ajustados para que el nombre conserve el tamaño v2 en vez de caer a ~17vw; la invitación sigue a la vista */
@media (max-width:39.99em) and (orientation:portrait) and (max-height:36em){
  .hero{grid-template-rows:minmax(16svh,1fr) auto auto;padding-bottom:2.25rem}
  .hero-name{--name-cap:max(3rem,calc((84svh - 12rem) / 2.68))}
}
/* móvil en horizontal (≤ 480 px de alto): la composición de escritorio en dos líneas («Jorge» / «Gallego Soto») con el tope
   por altura; el nombre conserva la jerarquía (≥ 4× el rol). Rol e invitación quedan bajo el pliegue: el hero se desplaza hasta su
   base antes de quedar fijo (motion.js) */
@media (max-width:59.99em) and (orientation:landscape) and (max-height:30em){
  .hero{grid-template-rows:auto 1fr auto;padding-top:4.5rem}
  /* 1,82 = 2 × .86 + .1 (padding bajo «Jorge») */
  .hero-name{--fit:5.64;--name-cap:calc((100svh - 9rem) / 1.82);font-size:min(calc(100cqi / var(--fit)),var(--name-cap));grid-template-columns:max-content max-content;column-gap:.2em}
  .hero-name .w:first-child{grid-column:1}
  .hero-name .w:nth-child(2){grid-column:1;grid-row:2}
  .hero-name .w:nth-child(3){grid-column:2;grid-row:2}
}
@media (min-width:60em){
  .hero{grid-template-rows:auto 1fr;padding-top:5.5rem;--bh-x:83;--bh-y:21}
  .hero-top{grid-row:1;max-width:26rem}
  .hero-cue{--cue:4rem;--cue-drop:1.5rem;margin-top:var(--space-xs)} /* caída corta: bajo el anillo empieza la «J» */
  /* dos líneas: «Jorge» / «Gallego Soto»; el espacio entre apellidos es un hueco de columna (.2em ≈ espacio de palabra con tracking).
     1,82 = 2 × .86 + .1 (padding bajo «Jorge»); suelo 6,6rem (× 1,82 ≈ 7rem × 1,72): con poca altura el bloque no crece por el padding */
  .hero-name{--fit:5.64;--name-cap:max(6.6rem,calc((100svh - 30rem) / 1.82));grid-template-columns:max-content max-content;column-gap:.2em}
  .hero-name .w:first-child{grid-column:1}
  .hero-name .w:nth-child(2){grid-column:1;grid-row:2}
  .hero-name .w:nth-child(3){grid-column:2;grid-row:2}
  .mk-bh::after{height:46%}
}
/* ---------- marquesina ---------- */
.marquee{overflow:hidden;padding-block:var(--space-s);border-block:1px solid var(--line-1)}
html.motion-ok .marquee{white-space:nowrap}
.marquee-track{display:inline-block;font-size:var(--marquee);font-weight:700;letter-spacing:-.03em;line-height:1.15;color:var(--ink-strong);will-change:transform}
.marquee-item,.marquee .dot{display:inline-block;padding-inline:.225em}
.marquee .dot{color:var(--accent-text);font-weight:400}
.marquee-track[aria-hidden]{display:none}
/* estática (sin JS o con reduce): completa y a un cuerpo que no se coma media pantalla */
html:not(.motion-ok) .marquee-track{font-size:var(--step-2)}
html.motion-ok .marquee-track[aria-hidden]{display:inline-block}
/* ---------- secciones ---------- */
.sec{padding-block:clamp(3rem,6vw,5.5rem);scroll-margin-top:4rem}
.sec-head{display:grid;gap:var(--space-m);margin-bottom:var(--space-m)}
.sec-head h2{font-size:var(--step-4)}
.sec-head .meta{display:grid;gap:var(--space-s);align-content:end}
@media (min-width:60em){.sec-head{grid-template-columns:minmax(min-content,7fr) minmax(0,5fr);gap:var(--space-l);align-items:end}}
/* < 40em: títulos de sección en ≤ 2 líneas a 390 px y títulos de proyecto más cortos (addendum §3.10) */
@media (max-width:39.99em){.sec-head h2{font-size:clamp(2.1rem,9.4vw,3rem);text-wrap:pretty}.project h3{font-size:clamp(2rem,9vw,2.8rem);max-width:none}.sub{font-size:var(--step-2)}.sec-head{gap:var(--space-s)}}
/* ---------- proyectos ---------- */
.project{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--space-l);padding-block:var(--space-l);border-top:1px solid var(--line-1)}
.project>.mono,.project>h3{grid-column:1/-1}
.project>h3{max-width:14ch}
.project h3{font-size:var(--step-5);transition:transform 350ms var(--ease)}
@media (hover:hover){.project:hover h3{transform:translateX(.08em)}}
.project .text{display:grid;gap:var(--space-m);align-content:start;max-width:62ch}
.project .text p{color:var(--ink-1)}
.project .links{display:flex;flex-wrap:wrap;gap:var(--space-s) var(--space-l);align-items:baseline}
.facts{display:grid;grid-template-columns:6.5em minmax(0,1fr);gap:.55rem var(--space-m);padding-top:var(--space-s);border-top:1px solid var(--line-1)}
.facts dt{font-family:var(--font-mono);font-size:var(--step--1);letter-spacing:.02em;color:var(--ink-faint);font-weight:400;padding-top:.15em}
.facts dd{margin:0;color:var(--ink-muted)}
.csp{max-width:100%;overflow:auto;padding:var(--space-s) var(--space-m);border:1px solid var(--line-1);border-radius:var(--r-md);background:var(--surface-1);color:var(--ink-muted);white-space:pre-wrap;overflow-wrap:anywhere}
.panel{padding:var(--space-l);border:1px solid var(--line-1);border-radius:var(--r-md);background:var(--surface-1)}
.panel figcaption{margin-top:var(--space-m)}
@media (min-width:60em){
  .project{grid-template-columns:minmax(0,5fr) minmax(0,7fr);gap:var(--space-l) var(--space-xl);align-items:start}
  .project .panel{position:sticky;top:6rem}
}
/* diagramas (diagram.css fundido): una sola variante vertical (340 unidades) en todos los anchos, escalada entre 20 y 26 rem
   para que el texto nunca baje de ~12 px (brief §4.8); en < 40em el panel sangra hasta los márgenes y, si aun así no cabe, desplaza */
.diagram-v{width:100%;max-width:26rem;height:auto;display:block;margin-inline:auto;overflow:visible}
.diagram-v .box{fill:var(--surface-0);stroke:var(--line-3);stroke-width:1}
.diagram-v .box.strong{stroke:var(--ink-strong)}
.diagram-v .ln{stroke:var(--line-3);stroke-width:1;fill:none}
.diagram-v .ln.dash{stroke-dasharray:3 4}
.diagram-v .div{stroke:var(--line-3);stroke-dasharray:1 5}
.diagram-v .gate{stroke:var(--accent);stroke-width:1.5;fill:none}
.diagram-v .lbl{font-family:var(--font-body);font-size:14px;font-weight:500;fill:var(--ink-1)}
.diagram-v .sub{font-family:var(--font-body);font-size:14px;font-weight:400;fill:var(--ink-muted)}
.diagram-v .gate-lbl{font-family:var(--font-body);font-size:14px;font-weight:500;fill:var(--accent-text)}
.diagram-v .mark circle{fill:var(--surface-0);stroke:var(--line-3);stroke-width:1;r:9}
.diagram-v .mark text{font-family:var(--font-mono);font-size:12px;font-weight:400;fill:var(--ink-1);text-anchor:middle;dominant-baseline:central}
@media (max-width:39.99em){
  .project .panel{margin-inline:calc(-1 * var(--gutter));padding-inline:var(--gutter);border-inline:0;border-radius:0}
  .dg{overflow-x:auto;overscroll-behavior-x:contain}
  .diagram-v{min-width:20rem}
}
/* ---------- filas (trayectoria, contacto) ---------- */
.sub{font-size:var(--step-3);font-weight:800;margin-top:var(--space-l);margin-bottom:var(--space-s)}
.rows{list-style:none;padding:0;margin:0}
.rows li{display:grid;gap:.25rem var(--space-l);padding:var(--space-m) var(--space-s);margin-inline:calc(-1 * var(--space-s));border-top:1px solid var(--line-1);border-radius:var(--r-sm);transition:background-color var(--dur-1) var(--ease)}
.rows li:last-child{border-bottom:1px solid var(--line-1)}
@media (hover:hover){.rows li:hover{background:var(--surface-2)}.rows li:hover .k{color:var(--ink-muted)}}
.rows .t{font-size:var(--step-2);font-weight:600;letter-spacing:-.02em;line-height:1.1;color:var(--ink-strong)}
.rows .d{color:var(--ink-muted);max-width:62ch}
.rows .k{font-family:var(--font-mono);font-size:var(--step--1);letter-spacing:.02em;color:var(--ink-faint);line-height:1.6;padding-top:.35em}
.rows .mid{font-size:var(--step-1);font-weight:600;letter-spacing:-.01em;line-height:1.2;overflow-wrap:anywhere}
@media (min-width:60em){.rows li{grid-template-columns:12.5rem minmax(0,1fr)}.rows .d{grid-column:2}}
/* ---------- stack ---------- */
.stack-row{display:grid;gap:var(--space-s);padding-block:var(--space-m);border-top:1px solid var(--line-1)}
.stack-row:last-child{border-bottom:1px solid var(--line-1)}
.stack-row h3{font:400 var(--step--1)/1.6 var(--font-mono);letter-spacing:.02em;color:var(--ink-faint);padding-top:.5em}
.stack-row ul{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:.3rem var(--space-m);font-size:var(--step-1);font-weight:600;letter-spacing:-.01em;line-height:1.2;color:var(--ink-strong)}
.stack-row li{display:inline-flex;align-items:center;gap:.4em}
.stk-logo{width:20px;height:20px;flex:none}
@media (min-width:60em){.stack-row{grid-template-columns:12.5rem minmax(0,1fr)}}
/* ---------- sobre mí ---------- */
.about{display:grid;gap:var(--space-l)}
.about .text{display:grid;gap:var(--space-m);max-width:62ch;align-content:start}
.portrait{aspect-ratio:4/5;max-width:28rem;overflow:hidden;border-radius:var(--r-md);background:var(--surface-1)}
.portrait picture{display:block;height:100%}
.portrait img{width:100%;height:100%;object-fit:cover;filter:grayscale(1)}
@media (min-width:60em){.about{grid-template-columns:minmax(0,4fr) minmax(0,8fr);gap:var(--space-xl);align-items:start}}
/* ---------- contacto ---------- */
.email-row{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--space-s) var(--space-l);margin-bottom:var(--space-l)}
.email{position:relative;display:inline-block;font-size:var(--email);font-weight:800;letter-spacing:-.045em;line-height:1;color:var(--ink-strong);text-decoration:none;overflow-wrap:anywhere;padding-bottom:.08em}
.email::after{content:"";position:absolute;left:0;right:0;bottom:0;height:.06em;background:var(--accent);transform:scaleX(0);transform-origin:right;transition:transform var(--dur-3) var(--ease)}
@media (hover:hover){.email:hover{color:var(--ink-strong)}.email:hover::after{transform:scaleX(1);transform-origin:left}}
.email:focus-visible::after{transform:scaleX(1);transform-origin:left}
@media (pointer:coarse){.email{padding-block:.45rem;margin-block:-.25rem}.email::after{bottom:.45rem}}
.copy{font-size:var(--step-0);font-weight:500;color:var(--accent-text)}
.contact-grid{display:grid;gap:var(--space-l)}
@media (min-width:60em){.contact-grid{grid-template-columns:minmax(0,5fr) minmax(0,7fr);gap:var(--space-xl)}}
/* ---------- cursor (sólo html.motion-ok.fine) ---------- */
.cursor,.cursor-ring{display:none;position:fixed;top:0;left:0;z-index:var(--z-cursor);pointer-events:none;border-radius:50%;will-change:transform}
html.motion-ok.fine .cursor{display:block}
html.motion-ok.fine .cursor-ring{display:grid}
html.motion-ok.fine .cursor.is-hidden,html.motion-ok.fine .cursor-ring.is-hidden{display:none}
html.motion-ok.fine,html.motion-ok.fine a,html.motion-ok.fine button{cursor:none}
.cursor{width:8px;height:8px;margin:-4px 0 0 -4px;background:var(--accent)}
.cursor-ring{width:36px;height:36px;margin:-18px 0 0 -18px;border:1px solid var(--accent-text);place-items:center;font:500 .55rem/1 var(--font-body);color:var(--on-accent);transition:background-color .2s var(--ease),border-color .2s var(--ease)}
.cursor-ring span{display:none}
.cursor-ring.is-copy{background:var(--accent);border-color:var(--accent)}
.cursor-ring.is-copy span{display:block}
/* ---------- preloader (sólo html.js.pre:not(.reduce)) ---------- */
.preloader{display:none;position:fixed;inset:0;z-index:var(--z-pre);grid-template-rows:1fr auto;align-items:end;padding:var(--gutter);background:var(--surface-0);color:var(--ink-strong)}
html.js.pre:not(.reduce) .preloader{display:grid}
.pre-n{font-size:18vw;font-weight:800;line-height:.86;letter-spacing:-.045em;font-variant-numeric:tabular-nums}
.pre-foot{display:grid;gap:var(--space-s);padding-top:var(--space-m)}
.pre-bar{height:1px;background:var(--ink-strong);transform-origin:left}
}
/* Lenis (antes vendor/lenis.css, MIT · LICENSE-lenis.txt); sin capa para conservar su prioridad */
html.lenis,html.lenis body{height:auto}
.lenis:not(.lenis-autoToggle).lenis-stopped{overflow:clip}
.lenis [data-lenis-prevent],.lenis [data-lenis-prevent-wheel],.lenis [data-lenis-prevent-touch],.lenis [data-lenis-prevent-vertical],.lenis [data-lenis-prevent-horizontal]{overscroll-behavior:contain}
.lenis.lenis-smooth iframe{pointer-events:none}
.lenis.lenis-autoToggle{transition-property:overflow;transition-duration:1ms;transition-behavior:allow-discrete}
