/* =====================================================================
   ESTILOS DE PÁGINA
   =====================================================================
   Layout específico de esta página (portafolio). Los tokens (colores,
   tipografía, grid) viven en tokens.css, y los componentes reutilizables
   (botones, pills) en components.css — acá solo va la disposición de
   las secciones de esta página en particular. */

/* ===== Reset básico =====
   Los navegadores traen márgenes/paddings por defecto distintos
   entre sí. Los ponemos en 0 para partir todos del mismo punto. */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* Scroll suave nativo para los anchors internos (ej. "Work" -> #work),
   sin necesidad de JS ni librerías — es el estándar recomendado para
   este caso (ver MDN/web.dev). Se apaga si el usuario prefiere menos
   movimiento (accesibilidad, WCAG 2.3.3). */
html {
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

body {
  background-color: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
}

/* ===== Grid de layout =====
   Reproduce el "Layout Grid" de Figma: centra el contenido hasta
   1440px, deja 70px de margen a los lados, y define 12 columnas
   con 20px de separación para poder ubicar elementos en columnas
   específicas (grid-column) cuando lo necesitemos. */
.container {
  width: 100%;
  max-width: var(--grid-max-width);
  margin-inline: auto;
  padding-inline: var(--grid-margin);
}

.grid-12 {
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), 1fr);
  column-gap: var(--grid-gutter);
}

/* ===== Header / navegación ===== */
.navbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-block: var(--space-24);
}

.navbar__logo {
  display: block;
  height: 22px;
  width: auto;
}

.navbar__links {
  display: flex;
  align-items: center;
  gap: var(--space-8);
}

/* ===== Hero ===== */
.hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding-block: var(--space-40) var(--space-60);
}

/* Tamaño real en Figma: 216.61px sobre un frame de 1440px de ancho
   (216.61 / 1440 ≈ 15vw). Con clamp() escala en pantallas chicas
   y no pasa de ese tamaño en pantallas grandes. */
.hero__title {
  position: relative;
  z-index: 0;
  font-family: var(--font-display);
  font-weight: 900;
  text-transform: uppercase;
  color: var(--color-brand);
  font-size: clamp(2.75rem, 15vw, 216.61px);
  line-height: 0.85;
  letter-spacing: -0.02em;
}

/* ===== Animación de scroll =====
   Misma posición y tamaño que la imagen estática original (y que el
   Figma): el personaje va cambiando de frame según la dirección del
   scroll, pero se queda en su lugar dentro del flujo normal del hero,
   sin "secuestrar" el scroll de la página.

   El PNG (828 x 1108 nativo) tiene relleno transparente: el personaje
   real ocupa solo de y=72 a y=732 (el resto es aire arriba y, sobre
   todo, abajo de las botas). Por eso los márgenes negativos tienen
   que ser grandes: primero "comen" ese aire transparente y recién
   después generan la superposición real con el texto. */
.hero__stage {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-top: clamp(-90px, -6.5vw, -20px);
  margin-bottom: clamp(-104px, -4.78vw, -54px);
}

/* Tamaño real en Figma: 619 x 828px sobre un frame de 1440px de
   ancho (619 / 1440 ≈ 43vw). El aspect-ratio de los frames de la
   animación (828 x 1108) ya coincide con 619 x 828, así que solo
   fijamos el ancho y el alto se ajusta solo. */
.hero__avatar {
  width: clamp(220px, 43vw, 619px);
  height: auto;
  user-select: none;
  display: block;
}

/* ===== Intro con efecto de máquina de escribir =====
   Aparece una sola vez, la primera vez que arranca la animación de
   scroll del avatar (ver main.js, primer bloque). El texto completo
   real vive en el aria-label del <p> — el <span> que se va llenando
   letra por letra es aria-hidden, así un lector de pantalla escucha
   la frase completa de una vez en vez del tipeo letra por letra.

   El PNG del avatar (828px nativo de ancho) tiene relleno transparente
   también a la izquierda, no solo arriba/abajo: medido con canvas +
   getImageData en varios frames (0, 45, 120), el píxel visible más a
   la izquierda del personaje aparece recién en ~23% del ancho del
   frame (los frames de reposo, 0 y 120, son los que más se acercan;
   con la mano levantada el brazo queda más adentro, no más afuera).
   Por eso NO se ancla con right:100% (eso deja ~145px de aire
   invisible antes de tocar al personaje) — se usa right:80% para que
   el borde derecho del texto quede a solo unos px del punto real donde
   empieza a verse el personaje, en vez del borde del <img>. */
.hero__intro {
  position: absolute;
  top: calc(14% + 40px);
  right: 80%;
  width: 267px;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--text-body-lg-size);
  line-height: var(--text-body-lg-line);
  color: var(--color-text-black);
  text-align: center;
}

/* Solo el nombre va en medium (600), como en el navbar — el resto de
   la frase queda en regular. */
.hero__intro-name {
  font-weight: 600;
}

/* "Costa Rica" tachado, como corrigiéndose a "Germany" — chiste
   visual, no informativo, por eso el aria-label del <p> ya dice
   directamente "based in Germany" en vez de intentar leer el tachado. */
.hero__intro-strike {
  text-decoration: line-through;
}

/* Oculto (display:none, no opacity) hasta que arranca el tipeo — ver
   main.js, recién ahí se le agrega .is-active. display:none en vez de
   opacity:0 porque si no, el keyframe de blink (que anima opacity)
   pisaría el valor base y el cursor nunca se vería. */
.hero__intro-cursor {
  display: none;
  width: 2px;
  height: 1em;
  margin-left: 2px;
  vertical-align: text-bottom;
  background-color: currentColor;
}

.hero__intro-cursor.is-active {
  display: inline-block;
  animation: hero-intro-blink 0.8s steps(1, start) infinite;
}

@keyframes hero-intro-blink {
  50% {
    opacity: 0;
  }
}

.hero__subtitle {
  position: relative;
  z-index: 0;
  font-family: var(--font-display);
  font-weight: 900;
  text-transform: capitalize;
  color: var(--color-brand);
  font-size: clamp(2.75rem, 15vw, 216.61px);
  line-height: 0.85;
  letter-spacing: -0.02em;
}

/* ===== Work / tarjetas de proyecto =====
   Medidas tomadas a mano de la primera tarjeta del Figma (banners
   colgando de una feria, proyecto "Leo"): la imagen ocupa el ancho
   completo del container, sin bordes redondeados. */
.work-list {
  padding-block: 0 var(--space-60);
}

/* 146px es una medida puntual del gap entre tarjetas en el Figma, no
   un múltiplo de la escala general — se deja como valor literal en
   vez de forzarla dentro de --space-* (mismo criterio que las medidas
   del hero más abajo).

   max-width: 1080px = 10 columnas del grid de 12 (--grid-columns),
   calculado con la misma fórmula del grid: 10 columnas de 90px
   (colWidth = (1300 - 11*20) / 12) + 9 gutters de --grid-gutter (20px)
   = 900 + 180 = 1080px. Comparado en vivo contra 12 (ancho completo)
   y 8 columnas (860px) antes de elegir esta opción. */
.project-card {
  max-width: 1080px;
  margin-inline: auto;
  margin-bottom: 146px;
}

.project-card:last-child {
  margin-bottom: 0;
}

.project-card__image {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1299 / 670;
  object-fit: cover;
}

/* Segunda tarjeta ("Fischbach, onboarding tool"): acá la "imagen" es
   un video corto en loop (mockup de celular animando entre pantallas).
   Mismo patrón que .project-card__image, pero con el aspect-ratio
   real del video (1300 x 1462 — vertical, porque es la composición
   completa del header + bloque rojo/celular, no solo el teléfono). */
.project-card__video {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1300 / 1462;
  object-fit: cover;
  background-color: #fcfcfb;
}

/* ===== Tercera tarjeta ("YouTube Compass"): cover animada, no imagen =====
   David todavía no tiene un cover real para este proyecto — en vez del
   placeholder gris, esto recrea el hero real de
   project-youtube-discovery.html (mismo degradé cian/rosa, mismo título
   "YouTube [compás] Compass", misma animación 3D "flyIn") directo acá,
   con el mismo aspect-ratio que las otras tarjetas (1299/670) para que
   el grid no salte de tamaño.

   Colores/tipografía escritos literales (no var(--font-display)/
   var(--font-body)) porque index.html no redefine esos tokens como sí
   hace project-youtube-discovery.css — hacerlo acá rompería Work
   Sans/Inter en el resto de la home. "YouTubeDisplay" igual está
   disponible: el @font-face vive en components.css (que index.html sí
   carga), no depende de la redefinición de --font-display.

   overflow:hidden + perspective en el MISMO elemento — mismo motivo que
   .ytd-hero en project-youtube-discovery.css: la animación pasa por
   posiciones intermedias más grandes que el tamaño final, y sin esto
   desborda el ancho de la página (encontrado en vivo por David, ya nos
   mordió una vez con el hero real). */
.project-card__ytd-hero {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1299 / 670;
  background-color: #ffffff;
  background-image:
    radial-gradient(ellipse 75% 85% at 85% -15%, rgba(77, 238, 255, 0.95), transparent 65%),
    radial-gradient(ellipse 80% 70% at 25% 130%, rgba(255, 200, 200, 0.95), transparent 60%);
  perspective: 25vw;
  /* Ancho de 8 de las 12 columnas del grid del sitio — mismo criterio
     y fórmula que .ytd-hero__title-frame en project-youtube-discovery.css
     (ver ese archivo para el álgebra completa), acá como variable local
     porque el título Y el video de preview de abajo comparten este
     mismo ancho. */
  --ytd-8col: calc(
    (2 * (min(100vw, var(--grid-max-width)) - (2 * var(--grid-margin))) - var(--grid-gutter)) / 3
  );
}

/* .project-card__ytd-hero-flip: a diferencia de .ytd-hero__flip (que
   anima apenas carga la página), acá el estado de reposo POR DEFECTO es
   el primer frame de la animación (rotate3d 90°, "de canto") — la
   tarjeta puede tardar en entrar en pantalla, así que no tiene sentido
   que ya esté asentada antes de que el usuario la vea. Recién cuando
   IntersectionObserver agrega .is-inview (ver js/main.js, dispara al
   70% visible) se reproduce la animación hacia el frame final (0°). */
.project-card__ytd-hero-flip {
  transform: translateZ(25vw) rotate3d(1, 0, 0, 90deg);
  transform-style: preserve-3d;
  transform-origin: center;
}

.project-card__ytd-hero-flip.is-inview {
  animation: project-card-ytd-flyIn 0.75s forwards cubic-bezier(0.9, 0, 0.1, 1);
}

/* Salida — David pidió que, 4s después de asentarse, el título+compás
   se vayan hacia arriba usando "el mismo efecto" que la entrada: mismo
   mecanismo (translateZ + rotate3d en X, mismo cubic-bezier/duración),
   pero en reversa y sumando translateY hacia arriba en vez de solo la
   rotación. -500px de translateY alcanza sobrado para salir del alto
   real de la tarjeta (aspect-ratio 1299/670, nunca pasa de ~600px) sin
   depender del alto propio del título (que es mucho más chico) — el
   overflow:hidden de .project-card__ytd-hero recorta la salida en el
   borde de arriba, por eso "sale" en vez de solo desaparecer de golpe.
   .is-leaving se agrega vía JS 4s después de .is-inview (ver
   js/main.js) — el estado final de is-inview (0°) es el "from" acá,
   así que no hace falta declarar ese frame otra vez. */
.project-card__ytd-hero-flip.is-leaving {
  animation: project-card-ytd-flyOut 0.75s forwards cubic-bezier(0.9, 0, 0.1, 1);
}

.project-card__ytd-hero-flip-inner {
  transform: translateZ(calc(-1 * 25vw));
}

@keyframes project-card-ytd-flyIn {
  from {
    transform: translateZ(25vw) rotate3d(1, 0, 0, 90deg);
  }
  to {
    transform: translateZ(25vw) rotate3d(1, 0, 0, 0deg);
  }
}

@keyframes project-card-ytd-flyOut {
  from {
    transform: translateY(0) translateZ(25vw) rotate3d(1, 0, 0, 0deg);
  }
  to {
    transform: translateY(-500px) translateZ(25vw) rotate3d(1, 0, 0, -90deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .project-card__ytd-hero-flip {
    animation: none;
    transform: translateZ(25vw) rotate3d(1, 0, 0, 0deg);
  }

  .project-card__ytd-hero-flip.is-leaving {
    /* Se queda asentado — a alguien que pidió menos movimiento no le
       conviene que el contenido encima desaparezca solo. */
    animation: none;
  }
}

/* .project-card__ytd-hero-title-frame: ancho fijo a 8 de las 12
   columnas del grid del sitio — mismo criterio y misma fórmula que
   .ytd-hero__title-frame en project-youtube-discovery.css (David pidió
   ahí 10 columnas para el hero real, acá pidió 8 para esta versión
   chica dentro de la tarjeta). Álgebra: N columnas + (N-1) gutters
   sobre 12 columnas + 11 gutters se simplifica a
   (N×contenedor + gutter×(N-12)) / 12 — para N=8 da (2×contenedor -
   gutter) / 3. contenedor = ancho de las 12 columnas completas
   (min(100vw, grid-max-width) - 2×grid-margin), el mismo que ya usa
   .container en toda la página.

   container-type: inline-size + font-size en cqi en
   .project-card__ytd-hero-title (no clamp/vw) — el texto LLENA esas 8
   columnas en vez de solo no desbordarlas, igual que en el hero real.
   El ancho en sí vive en --ytd-8col (definida en .project-card__ytd-hero
   más arriba) — el video de preview de abajo comparte esa misma
   variable en vez de repetir la fórmula dos veces. */
.project-card__ytd-hero-title-frame {
  width: var(--ytd-8col);
  margin-inline: auto;
  container-type: inline-size;
}

/* font-size en cqi: misma relación medida en vivo que
   .ytd-hero__title en project-youtube-discovery.css (texto puro real
   de YouTubeDisplay ExtraBold + letter-spacing -0.02em = 6:1 contra su
   propio font-size) — mismo font-family/weight/tracking acá, así
   que la misma relación aplica. Se resta el gap fijo (2 × 12px, no
   escala con el texto) del 100cqi antes de dividir, mismo motivo que
   allá: si no, el gap pesaría de más en pantallas angostas y el
   título no llegaría a llenar el frame. flex-wrap: nowrap por la
   misma razón que allá — al tamaño exacto, "wrap" puede saltar
   "Compass" a una segunda línea por redondeo de subpíxel. */
.project-card__ytd-hero-title {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: nowrap;
  gap: 0 12px;
  margin: 0;
  font-family: "YouTubeDisplay", "Arial Black", system-ui, sans-serif;
  font-weight: 800;
  font-size: calc((100cqi - (2 * 12px)) / 6);
  line-height: 0.95;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  color: #212121;
}

.project-card__ytd-hero-icon {
  display: block;
  width: auto;
  height: 1.1em;
}

/* ===== Video de preview (segundo acto de la cover) =====
   Entra con el MISMO mecanismo 3D que el título (translateZ + rotate3d
   en X, mismo cubic-bezier/duración) — position:absolute + inset:0
   para ocupar el mismo centro de .project-card__ytd-hero sin depender
   del layout flex del título (que para este momento ya salió de
   pantalla). .is-inview se agrega vía JS (js/main.js) justo cuando
   termina el flyOut del título, no en el mismo IntersectionObserver
   que el título — ver comentario ahí para la cadena completa. */
.project-card__ytd-hero-video-flip {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  transform: translateZ(25vw) rotate3d(1, 0, 0, 90deg);
  transform-style: preserve-3d;
  transform-origin: center;
}

.project-card__ytd-hero-video-flip.is-inview {
  animation: project-card-ytd-flyIn 0.75s forwards cubic-bezier(0.9, 0, 0.1, 1);
}

/* Salida del video — mismo efecto que la salida del título (reusa el
   mismo @keyframes project-card-ytd-flyOut), a pedido de David. Forma
   parte del ciclo continuo título↔video cada 4s: ver js/main.js. */
.project-card__ytd-hero-video-flip.is-leaving {
  animation: project-card-ytd-flyOut 0.75s forwards cubic-bezier(0.9, 0, 0.1, 1);
}

.project-card__ytd-hero-video-flip-inner {
  transform: translateZ(calc(-1 * 25vw));
}

@media (prefers-reduced-motion: reduce) {
  .project-card__ytd-hero-video-flip {
    animation: none;
    transform: translateZ(25vw) rotate3d(1, 0, 0, 0deg);
  }

  .project-card__ytd-hero-video-flip.is-leaving {
    animation: none;
  }
}

/* Mismo ancho de 8 columnas que el título (--ytd-8col, definida en
   .project-card__ytd-hero) — David pidió que el video ocupe el mismo
   ancho. No necesita container query units como el título: acá no hay
   texto que "llenar", el video ya tiene su aspect-ratio real (16:9,
   1920×1080 nativo) y solo necesita ese ancho fijo. */
.project-card__ytd-hero-video-frame {
  width: var(--ytd-8col);
}

/* ===== Borde degradado animado + resplandor =====
   David dio este efecto tal cual (colores/tiempos/valores literales,
   no tokens del sitio — es una identidad puntual de este video, no
   parte del sistema de diseño).

   El degradé "gira" animando su propio background-position sobre un
   background-size inflado a 300% (no se mueve el elemento) — 3px de
   padding entre este contenedor (12px de radius) y el contenedor
   interno (9px = 12-3) simulan el grosor del borde, dejando los dos
   radios concéntricos. */
.project-card__ytd-hero-video-border {
  background: linear-gradient(
    135deg,
    #4285f4,
    #9b72cb,
    #1ba1e3,
    #d96570,
    #9b72cb,
    #4285f4
  );
  background-size: 300% 300%;
  animation: project-card-ytd-video-border-rotate 6s ease infinite;
  border-radius: 12px;
  padding: 3px;
  box-shadow:
    0 0 20px rgba(66, 133, 244, 0.35),
    0 0 40px rgba(155, 114, 203, 0.25),
    0 0 60px rgba(217, 101, 112, 0.15);
}

@keyframes project-card-ytd-video-border-rotate {
  0% {
    background-position: 0% 50%;
  }
  50% {
    background-position: 100% 50%;
  }
  100% {
    background-position: 0% 50%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .project-card__ytd-hero-video-border {
    animation: none;
  }
}

/* Contenedor interno: el que realmente recorta el video (radius 9px,
   3px menos que el de afuera) — el <video> ya no lleva su propio
   border-radius. */
.project-card__ytd-hero-video-border-inner {
  border-radius: 9px;
  overflow: hidden;
  background-color: #000000;
}

.project-card__ytd-hero-video {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

.project-card__title {
  margin-top: var(--space-24);
  font-family: var(--font-body);
  font-size: 18px;
  font-weight: 600;
  color: var(--color-text-black);
}

/* max-width bajado de 900px a 600px: a 900px las líneas llegaban a
   113-116 caracteres en desktop (medido con Range/getBoundingClientRect
   por carácter), muy por encima del límite citable de WCAG 2.1 SC 1.4.8
   (~80 caracteres) y del rango tipográfico ideal (45-75). A 600px
   quedan en ~75-78 caracteres. */
.project-card__description {
  margin-top: var(--space-8);
  max-width: 600px;
  font-family: var(--font-body);
  font-size: var(--text-body-lg-size);
  line-height: var(--text-body-lg-line);
  color: var(--color-text-black);
}

.project-card__cta {
  display: flex;
  justify-content: flex-start;
  margin-top: var(--space-24);
}

/* =====================================================================
   RESPONSIVE — Tablet / Mobile
   =====================================================================
   Cortes documentados en tokens.css (Mobile <768px, Tablet 768–1023px).
   --grid-margin/--grid-gutter responsive acá (no --grid-columns, para
   no romper los grid-column:X/Y ya ubicados en todo el sitio) — con
   solo esto, .container/.grid-12 ya ganan márgenes/gutters más chicos
   en las 4 páginas que cargan este archivo (Home, las 2 de proyecto, y
   Resume), sin re-maquetar nada. El resto de esta sección es contenido
   específico de esta página (.hero, .navbar, .project-card) — Resume y
   Design System no usan esas clases, quedan intactas. */
@media (max-width: 1023px) {
  :root {
    --grid-margin: var(--grid-tablet-margin);
    --grid-gutter: var(--grid-tablet-gutter);
  }
}

@media (max-width: 767px) {
  :root {
    --grid-margin: var(--grid-mobile-margin);
    --grid-gutter: var(--grid-mobile-gutter);
  }

  /* Navbar: "About"/"Get in Touch" (o el logo solo, en Resume) pasan a
     su propia fila centrada debajo del logo — a este ancho no entran
     los tres en una sola fila (bug real encontrado con la vista previa
     Mobile del dev-tool: los botones se superponían). */
  .navbar {
    flex-wrap: wrap;
    justify-content: center;
    row-gap: var(--space-16);
  }

  .navbar__links {
    width: 100%;
    justify-content: center;
  }

  .hero {
    padding-block: var(--space-32) var(--space-40);
  }

  /* .hero__intro en absolute (ver esa regla arriba) se ubica contra el
     ancho de .hero__stage — que a este ancho ya tocó el piso de 220px
     del clamp() del avatar. El texto (267px fijo) ya no entra ahí y
     termina desbordando la pantalla hacia la izquierda (verificado en
     vivo con la vista previa Mobile). Pasa a flujo normal — dentro de
     .hero__stage (flex-direction:column) cae naturalmente antes del
     avatar, que es donde ya está en el HTML. */
  .hero__intro {
    position: static;
    top: auto;
    right: auto;
    width: auto;
    max-width: 280px;
    margin-bottom: var(--space-16);
  }

  .project-card {
    margin-bottom: var(--space-40);
  }
}
