/*
  Réplica de estructura/layout/comportamiento de https://495541-a.cargo.site/
  Valores medidos directamente sobre el sitio de referencia (ver conversación).
  Sustituto tipográfico: Space Grotesk (la referencia usa "Monument Grotesk
  Variable", de pago) manteniendo peso 380 aprox. -> usamos 400 (el grosor
  intermedio más cercano disponible en la variable-font gratuita).
*/

* {
  box-sizing: border-box;
}

bodycopy {
  display: block;
  width: 100%;
}

html, body {
  margin: 0;
  padding: 0;
  background: #FFFFF3;
  color: #1a1a1a;
}

body {
  font-family: "EB Garamond", Georgia, "Times New Roman", serif;
  position: relative;
  min-height: 100vh;
  /* Transición entre páginas (ver js/page-transition.js): arranca
     invisible y se revela con un fundido al cargar; al navegar a otra
     página del sitio se desvanece antes de saltar, en vez del corte
     brusco de una navegación normal. La transición de background-color
     es la red de seguridad de /visual-archive (ver "switch palette" en
     archive.js): si el color cambia sin pasar por la mancha, que sea con
     un cross-fade suave y no un salto. */
  opacity: 0;
  transition: background-color 0.5s ease, opacity 0.32s ease;
}

body.page-visible {
  opacity: 1;
}

a {
  color: inherit;
  text-decoration: none;
}

/* Etiqueta "(ABOUT)" flotante sobre el bloque "noa abad" (ver el bloque
   final de js/script.js): se oculta el cursor por defecto del sistema
   solo mientras se sobrevuela ese enlace en concreto -el resto del sitio
   conserva el cursor normal-, dejando que sea la propia etiqueta la que
   haga ese papel. */
#index-list a[data-cv]:hover {
  cursor: none;
}

.about-label {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 1000;
  pointer-events: none;
  font-family: "EB Garamond", Georgia, "Times New Roman", serif;
  font-style: normal;
  text-transform: uppercase;
  font-size: 24px;
  letter-spacing: 0.02em;
  color: #1a1a1a;
  white-space: nowrap;
  opacity: 0;
  transition: opacity 0.18s ease;
  will-change: transform;
}

.about-label.is-visible {
  opacity: 1;
}

/* Más pequeña cuanto más estrecha la pantalla — mismos cortes que el
   resto del sitio (768px tablet, 480px móvil). */
@media (max-width: 768px) {
  .about-label {
    font-size: 19px;
  }
}

@media (max-width: 480px) {
  .about-label {
    font-size: 15px;
  }
}

/* Selector de idioma: fijo arriba a la derecha, estilo "EN / ESP / VAL"
   igual que la referencia — mayúsculas, gris claro, con el idioma activo
   resaltado en negro. */
.lang-switch {
  position: fixed;
  top: 6.48px;
  right: 6.48px;
  z-index: 998;
  font-size: 13px;
  letter-spacing: 0.05em;
  color: #9a9a9a;
  text-transform: uppercase;
}

.lang-switch .lang-sep {
  margin: 0 0.4em;
  color: #ccc;
}

.lang-switch .lang-link {
  color: inherit;
}

.lang-switch .lang-link:hover {
  color: #1a1a1a;
}

.lang-switch .lang-link.active {
  color: #1a1a1a;
}

@media (max-width: 768px) {
  .lang-switch {
    right: 9.86px;
  }
}

@media (max-width: 480px) {
  .lang-switch {
    right: 6.05px;
  }
}

/* ---------- Índice / portada ---------- */
/* Todo el índice es un único <h1> con <br> entre líneas, igual que la
   referencia. El tamaño de fuente es fluido: se recalcula por JS
   (fitIndexText) para que el bloque de texto ocupe el ancho disponible,
   igual que el mecanismo "fit-text" de Cargo. */

.page-content {
  padding: 6.48px 64.8px 6.48px 6.48px; /* asimétrico: 10x más a la derecha, medido @1440px */
  width: 100%;
}

#index-list {
  margin: 0;
  padding: 0;
  font-weight: 400;
  font-style: normal;
  line-height: 0.95;
  letter-spacing: -0.015em; /* proporcional al font-size, ver script.js */
  white-space: normal;
}

#index-list a {
  display: inline;
}

/* Hover en línea de proyecto: sin subrayado, toda la línea (título, em
   dash y categoría en cursiva, vía color:inherit) pasa al color del punto
   correspondiente (--accent, fijado inline por cada <a> en script.js). */
#index-list a[data-template]:hover {
  color: var(--accent);
}

/* Punto de color delante de cada proyecto: círculo perfecto, tamaño
   proporcional al font-size (fit-text) para mantenerse "pequeño" en
   cualquier breakpoint, alineado a la línea base, sin afectar al
   line-height ni al espaciado del bloque (line-height 0.95 fijo en el h1). */
.dot {
  display: inline-block;
  width: 0.34em;
  height: 0.34em;
  border-radius: 50%;
  background: var(--accent);
  margin-right: 0.32em;
  vertical-align: baseline;
  transform: translateY(-0.06em);
}

/* Categoría: cursiva y visualmente más ligera que el título (EB Garamond
   solo ofrece 400 como mínimo, así que la "aligeramos" con color/opacidad
   en vez de font-weight). El guion que la separa del título ya no es
   texto literal en el HTML (ver buildIndex en script.js): sale de aquí,
   así en móvil (ver media query más abajo) basta con quitar este
   ::before, sin tocar el JS, para que la categoría caiga en su propia
   línea sin un guion suelto delante. */
.category {
  font-style: italic;
  color: inherit;
}

.category::before {
  content: " — ";
}

/* Pila fija abajo a la izquierda: copyright (mismo tamaño/alineación que
   el índice, sincronizado por JS) justo encima de "Last Updated". */
.bottom-stack {
  position: fixed;
  left: 6.48px;
  bottom: 6.48px;
  z-index: 398;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.footer-bar {
  font-size: 13px;
  color: #9a9a9a;
}

.footer-bar a {
  color: inherit;
}

/* Copyright: mismo font-size/line-height/letter-spacing fluidos que
   #index-list (ver fitIndexText en script.js), separado del índice y
   anclado a la parte inferior de la página, justo encima de .footer-bar. */
.site-footer {
  display: block;
  font-weight: 400;
  line-height: 0.95;
}

/* Enlace al Archivo Visual: mismo tamaño/tipografía/color fluidos que el
   índice (sincronizado por JS, no gris como el copyright), anclado abajo
   a la derecha, separado de la lista de proyectos. Sin subrayado ni
   cambio de color al pasar el ratón, igual que el resto de enlaces del
   índice que no son proyectos ("noa abad", copyright). */
.archive-link {
  position: fixed;
  right: 6.48px;
  /* 6.48px (margen base) + 17px (altura de .footer-bar) para que su línea
     de base coincida con la de "© noa.ag_ — '26" (.site-footer), no con
     el fondo de toda la pila .bottom-stack (que incluye "Last Updated"
     debajo). */
  bottom: 23.48px;
  z-index: 398;
  font-weight: 400;
  line-height: 0.95;
  color: #1a1a1a;
  /* OCULTO TEMPORALMENTE (Visual Archive aún no está terminado): la
     página /visual-archive sigue intacta y accesible por URL directa,
     solo se quita este enlace de la portada. Para volver a mostrarlo,
     basta con borrar esta línea "display: none;". */
  display: none;
}

@media (max-width: 768px) {
  .bottom-stack {
    left: 9.86px;
  }

  .archive-link {
    right: 9.86px;
  }
}

@media (max-width: 480px) {
  .bottom-stack {
    left: 6.05px;
  }

  .archive-link {
    right: 6.05px;
  }
}

/* Breakpoint móvil real medido: se activa <= 768px */
@media (max-width: 768px) {
  .page-content {
    padding: 9.86px; /* uniforme, sin la reserva extra de scrollbar */
  }
}

@media (max-width: 480px) {
  .page-content {
    padding: 6.05px; /* uniforme, medido @375px */
  }
}

/* En móvil, cada proyecto pasa a dos líneas -título grande y expresivo
   arriba, categoría más pequeña en cursiva debajo-, en vez de una sola
   línea diminuta (fit-text) como en escritorio. Tamaños en clamp()
   (según el ancho de pantalla, no em heredado del font-size fluido de
   #index-list, ver fitIndexText en script.js): ese tamaño fluido está
   pensado para que TODO el texto -incluida la categoría más larga- quepa
   en una sola línea, así que en un móvil normal sale muy pequeño; aquí ya
   no hace falta que quepa en una línea (se permite el salto natural), así
   que el título puede ser bastante más grande sin depender de ese
   cálculo. El punto de color vive DENTRO de ".project-title" (ver
   buildIndex en script.js), así que crece con él automáticamente. */
@media (max-width: 768px) {
  /* "noa abad / Graphic Designer" al mismo tamaño que los títulos de
     proyecto (a petición) — mismo clamp() que ".project-title" más
     abajo, y algo de margen debajo para separarlo del primer proyecto
     ahora que todo el bloque pesa mucho más visualmente. */
  #index-list a[data-cv] {
    display: block;
    font-size: clamp(22px, 7vw, 30px);
    line-height: 1.15;
    letter-spacing: normal;
    margin-bottom: 0.15em;
  }

  #index-list a[data-template] {
    display: block;
    /* Espaciado ajustado, igual de compacto que en escritorio -antes
       quedaba demasiado suelto-: apenas un pelín más que el propio
       line-height, no un hueco en blanco aparte. */
    margin-bottom: 0.15em;
  }

  /* Con los enlaces ya en display:block se saltan de línea solos -los
     <br> que separaban cada línea en el HTML (ver buildIndex, se unen
     con "<br>\n") sobran y solo añadirían un hueco extra en blanco
     encima del "margin-bottom" de arriba-. */
  #index-list a[data-cv] + br,
  #index-list a[data-template] + br {
    display: none;
  }

  .project-title {
    display: block;
    font-size: clamp(22px, 7vw, 30px);
    line-height: 1.15;
    letter-spacing: normal;
  }

  .category {
    display: block;
    /* Mismo tamaño que ".project-title" (a petición) — la cursiva y el
       propio salto de línea ya bastan para diferenciarla del título,
       sin necesidad de que sea más pequeña. */
    font-size: clamp(22px, 7vw, 30px);
    line-height: 1.3;
    letter-spacing: normal;
    margin-top: 0;
    /* Sangrado, alineada aprox. con el arranque del texto del título
       (no con el punto de color) -ver referencia-, con el guion delante
       en vez de pegada a él. */
    margin-left: 1em;
  }

  .category::before {
    content: "— ";
  }

  /* "© noa.ag_" y "Last Updated" al mismo tamaño que los títulos (a
     petición) — mismo clamp() de arriba. ".site-footer" necesita
     "!important": fitIndexText (ver script.js) le fija un font-size
     inline en cada resize -pensado para el tamaño diminuto de
     escritorio-, y un estilo inline siempre gana a cualquier regla de
     una hoja de estilos por especificidad, sea cual sea el orden. */
  .site-footer {
    font-size: clamp(22px, 7vw, 30px) !important;
    letter-spacing: normal !important;
    line-height: 1.15;
  }

  /* "Last Updated" NO sigue el tamaño de los títulos (a diferencia de
     "© noa.ag_" un poco más arriba) — se pidió expresamente que se
     quedara proporcional al de escritorio, que es un tamaño fijo de
     13px (ver ".footer-bar" al principio de este archivo) igual en
     cualquier pantalla; aquí no hace falta ninguna regla nueva, solo
     NO heredar el clamp() de los títulos. */
}

/* ---------- Overlay de proyecto ---------- */
/* .overlay-content: fixed, cubre todo el viewport, pointer-events:none para
   que el fondo (índice) siga siendo visible e interactivo alrededor/detrás,
   tal y como se comprobó en la referencia. Sin animación de apertura/cierre
   (duration medida = 0s en el sitio original). */

.overlay-content {
  position: fixed;
  inset: 0;
  z-index: 999;
  overflow: auto;
  /* Sin esto, al llegar al final del scroll dentro de este overlay
     (fixed, con su propio scroll interno) el rebote del trackpad
     "encadena" el scroll de sobra a la página de fondo (el índice,
     detrás) — se ve asomar un instante al hacer overscroll arriba/abajo.
     "contain" para el rebote se quede dentro de este contenedor, sin
     tocar nada del layout ni del scroll normal. */
  overscroll-behavior: contain;
  pointer-events: none;
  display: none;
}

.overlay-content.open {
  display: block;
}

.overlay-content .page.overlay {
  position: relative;
  width: 100%;
  pointer-events: none;
}

.overlay-content .page-layout,
.overlay-content .page-content {
  pointer-events: none;
}

/* Solo los elementos de contenido real reciben clics (igual que en la
   referencia: bodycopy a, img, gallery-slideshow, etc. tienen pointer-events:auto) */
.overlay-content bodycopy a,
.overlay-content gallery-slideshow,
.overlay-content bodycopy img,
.overlay-content bodycopy video {
  pointer-events: auto;
}

/* Solo se dibuja borde donde el panel NO coincide con un borde real de la
   pantalla: arriba/izquierda quedan siempre pegados al viewport (sin
   borde ahí). El borde derecho SÍ se dibuja (no toca el borde de la
   pantalla) — es la línea que separa este overlay del panel de C.V./More
   cuando está abierto en solitario. */
.overlay-content .page-content {
  /* Se encoge al contenido (la imagen, ver ".media-item img") en vez de
     ocupar siempre la mitad de la pantalla — así el tamaño mostrado de
     la imagen no cambia nunca por sí solo. min-width evita que una
     imagen muy vertical/estrecha deje la ficha (crédito/enlaces) sin
     sitio; max-width evita que una muy horizontal se coma toda la
     pantalla. El panel "(More...)" (ver "#more-overlay .cv-panel" más
     abajo) usa el ancho real resultante -sincronizado por JS en
     "--project-col-width"- para ocupar exactamente el resto. */
  width: fit-content;
  min-width: 320px;
  max-width: 50%;
  padding: 12.96px;
  background: #fffff3;
  border-right: 1px solid #ddd;
  border-bottom: 1px solid #ddd;
  /* La imagen no puede desbordar su propia caja: sin esto, un redondeo
     de subpíxel entre el ancho medido por JS (--project-col-width, ver
     script.js) y el ancho real pintado por el navegador puede dejar la
     imagen asomando un pelín más allá de este borde, colándose por la
     juntura antes de que empiece el panel "(More...)". */
  overflow: hidden;
}

/* Cuando el panel de C.V./More está abierto A LA VEZ, se pinta encima (va
   después en el DOM) y su propio border-left cae exactamente en el mismo
   punto — sería tapado por el fondo de ese panel de todos modos, así que
   se oculta aquí para que la línea visible sea una sola. Importante: se
   vuelve TRANSPARENTE, no "none" — como el ancho de esta caja es
   fit-content, quitar el borde del todo (en vez de solo su color) haría
   que el propio cálculo de ancho encogiera 1px menos de "hueco ocupado
   por el borde", y ese 1px extra se lo llevaría la imagen (max-width:100%
   de esta caja): se vería mover/crecer un pelín justo al abrir el panel.
   Dejando el grosor (1px) pero sin color, el tamaño no cambia nunca. */
body:has(.cv-overlay.open) .overlay-content .page-content {
  border-right-color: transparent;
}

@media (max-width: 768px) {
  .overlay-content .page-content {
    width: 100%;
    min-width: 0;
    max-width: 100%;
    padding: 9.86px;
    /* a este ancho ocupa toda la pantalla: el borde derecho coincidiría
       con el borde real de la pantalla, así que se quita. */
    border-right: none;
  }

  /* ".overlay-content" es normalmente fixed/inset:0 -una caja que SIEMPRE
     mide el alto exacto de la pantalla, sin importar cuánto contenido
     real tenga dentro (imagen + ficha + "More" cerrado o abierto)-. Con
     poco contenido (proyecto sin manual/galería, o con "More" cerrado)
     ese alto fijo deja un hueco vacío al final -y como ni el overlay ni
     ese hueco tienen fondo propio, se cuela el índice de la portada de
     detrás (la "siguiente" ficha del índice, vista a través)-. En vez de
     taparlo con un color de fondo, se corrige el motivo real: en móvil
     ".overlay-content" pasa a flujo normal, con su alto determinado por
     su propio contenido -nunca fijo-, así que nunca sobra hueco ni
     falta. Si el contenido acaba siendo más alto que la pantalla (con
     "More" desplegado y su descripción larga), es la propia página la
     que hace scroll con normalidad, no un contenedor interno de alto
     fijo. El índice de la portada se oculta mientras tanto -ver
     "body:has(...) > .page-content" más abajo-: en escritorio se deja
     ver a propósito por los márgenes (ver el resto de este archivo),
     pero en móvil ".page-content" ya ocupa el 100% del ancho -no hay
     margen que enseñar-, así que aquí solo estorbaría detrás. */
  .overlay-content.open {
    position: static;
    inset: auto;
    height: auto;
    overflow: visible;
  }

  body:has(.overlay-content.open) > .page-content {
    display: none;
  }

  /* Selector de idioma y "© noa.ag_ / Last Updated" son "position:fixed"
     -por eso se ven siempre encima de todo, incluido el proyecto ya
     convertido a flujo normal de arriba-. Solo deben verse en la
     pantalla principal (a petición): con cualquier overlay abierto
     (proyecto o C.V.) en móvil, se ocultan del todo. */
  body:has(.overlay-content.open) .lang-switch,
  body:has(.overlay-content.open) .bottom-stack,
  body:has(#cv-overlay.open) .lang-switch,
  body:has(#cv-overlay.open) .bottom-stack {
    display: none;
  }
}

/* ---------- Panel de C.V. ---------- */
/* Segundo tipo de overlay, independiente del de proyectos: el wrapper es
   fixed/inset:0/pointer-events:none (igual que .overlay-content) para que
   el índice de la izquierda quede intacto -visible, interactivo y con su
   scroll- detrás; solo .cv-panel (mitad derecha) recibe pointer-events y
   tiene su propio scroll interno. Sin animación, igual que el overlay de
   proyectos. */
.cv-overlay {
  position: fixed;
  inset: 0;
  z-index: 999;
  pointer-events: none;
  display: none;
}

.cv-overlay.open {
  display: block;
}

.cv-panel {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: 50%;
  overflow-y: auto;
  /* Mismo motivo que ".overlay-content" (ver ahí el porqué): sin esto,
     el rebote del trackpad al final de este scroll interno se encadena
     al de la página de fondo. */
  overscroll-behavior: contain;
  pointer-events: auto;
  background: #fffff3;
  border-left: 1px solid #ddd;
  padding: 12.96px;
  font-size: 16px;
  line-height: 1.4;
}

/* El panel "(More...)" de un proyecto es un caso especial de .cv-panel:
   en vez de la mitad fija de la pantalla, su borde izquierdo empieza
   justo donde termina el ancho real de ".page-content" del proyecto (que
   varía con cada imagen — ver "--project-col-width", sincronizada por JS
   en script.js) y ocupa todo lo que quede hasta el borde derecho. El
   panel de C.V. (información personal, sin imagen de por medio) no se
   toca: sigue siendo la mitad fija de siempre. */
#more-overlay .cv-panel {
  /* -1px: se solapa un pelín sobre la esquina de ".page-content" en vez
     de tocarla justo, para tragarse cualquier resto de hueco de
     subpíxel entre los dos anchos (ver overflow:hidden en
     ".overlay-content .page-content" más arriba) — de sobra cubierto
     por el propio border-left, así que no se nota el solape. */
  left: calc(var(--project-col-width, 50%) - 1px);
  width: auto;
}

/* Cada panel (overlay de proyecto y C.V./More) dibuja siempre su propia
   línea divisoria — así cada uno se ve correctamente cuando está abierto
   en solitario. Cuando ambos coinciden a la vez en el mismo punto (x=50%),
   el panel de C.V./More se pinta encima (va después en el DOM), así que
   es su border-left el que se ve; el border-right del overlay de proyecto
   quedaría tapado por su fondo igualmente, así que se desactiva para no
   sumar grosor (ver regla más abajo, junto a .overlay-content). */

.cv-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  border-bottom: 1px solid #ddd;
  padding-bottom: 1em;
  margin-bottom: 1em;
}

.cv-header p {
  margin: 0;
}

.cv-bio {
  margin: 0 0 2em;
  max-width: 40em;
}

.cv-section {
  border-top: 1px solid #ddd;
  padding-top: 1em;
  margin-bottom: 2em;
}

.cv-section h2 {
  font-size: 1.15em;
  font-weight: 400;
  font-style: italic;
  margin: 0 0 1em;
}

.cv-entry {
  margin-bottom: 1.25em;
}

/* Lista de tareas de un puesto (ver "bullets" en CV_I18N.experience,
   opcional) — mismo tamaño/tipografía que el resto de la ficha, un
   guion en vez del disco de viñeta por defecto para que no desentone con
   el resto del sitio (sin listas nativas en ningún otro sitio). */
.cv-entry-bullets {
  list-style: none;
  margin: 0.5em 0 0;
  padding: 0;
}

.cv-entry-bullets li {
  position: relative;
  padding-left: 1em;
}

.cv-entry-bullets li::before {
  content: "–";
  position: absolute;
  left: 0;
}

/* Sección "Languages": una línea por idioma, nivel en cursiva debajo
   (mismo patrón visual que .cv-entry-title, sin fecha). */
.cv-languages {
  display: flex;
  flex-direction: column;
  gap: 0.75em;
}

.cv-language {
  margin: 0;
}

.cv-language-level {
  font-style: italic;
}

/* Panel "(More...)" de un proyecto: reutiliza .cv-overlay/.cv-panel
   (misma posición, medidas y estilo), con su propia estructura interna.
   Este panel ahora ocupa "lo que sobra" junto a la imagen (ver
   "#more-overlay .cv-panel", más arriba) y ese ancho sobrante puede
   variar mucho de un proyecto a otro (imagen estrecha -> panel enorme).
   Para que el texto y las imágenes de aquí dentro no cambien de tamaño
   con ese vaivén, se les pone un máximo propio (--more-content-max):
   el panel puede seguir siendo más ancho, pero su contenido no lo
   ocupa entero. */
.more-header,
.more-manual,
.more-gallery {
  max-width: var(--more-content-max, 640px);
}

/* Separa la imagen (arriba) del bloque de título/texto (abajo) — mismo
   criterio que ".more-close-row" (ancho completo del área de contenido,
   alineada con el arranque del texto), pero sin enlace dentro. */
.more-divider {
  border-top: 1px solid #ddd;
  margin-bottom: 1.5em;
}

.more-header {
  display: flex;
  gap: 2em;
  margin-bottom: 1.5em;
}

/* "moreWide" (ver renderMorePanel en script.js, de momento solo Casa
   Anna): en escritorio, el bloque de texto/manual deja de limitarse a
   "--more-content-max" y ocupa todo el ancho real del panel -así no
   queda un hueco en blanco enorme a la derecha en proyectos con panel
   muy ancho-, y el texto largo pasa a dos columnas en vez de una sola
   columna larga y estrecha. En móvil (donde el panel ya ocupa toda la
   pantalla apilado) se desactiva: una columna de toda la vida, más
   fácil de leer en una pantalla estrecha. */
@media (min-width: 769px) {
  .more-header.more-wide {
    max-width: 100%;
  }

  .more-header.more-wide .more-header-right {
    column-count: 2;
    column-gap: 2.5em;
  }

  /* El manual (y su contador/flechas de debajo) ocupan SOLO el ancho de
     la columna de texto de la derecha (".more-header-right"), no el
     bloque entero con el título — mismo desplazamiento y ancho que esa
     columna calcula el propio flexbox de ".more-header" (30% para el
     título + 2em de gap), replicado aquí a mano porque el manual es un
     bloque hermano, no puede heredarlo directo del layout flex. */
  .more-manual.more-wide,
  .more-manual-nav.more-wide {
    max-width: 100%;
    margin-left: calc(30% + 2em);
    width: calc(70% - 2em);
  }

  .more-header.more-wide .more-header-right p {
    break-inside: avoid;
  }
}

.more-header-left {
  flex: 0 0 30%;
}

.more-header-left .cv-entry-title {
  margin: 0;
}

.more-header-right {
  flex: 1;
}

.more-header-right p {
  margin: 0 0 1em;
}

.more-header-right p.more-indent {
  text-indent: 2em;
}

.more-close-row {
  /* A diferencia de ".more-header"/".more-manual"/".more-gallery", esta
     línea SÍ ocupa todo el ancho del área de contenido (no se limita a
     "--more-content-max") — empieza justo donde arrancan las letras del
     texto de arriba (mismo borde izquierdo, sin margen extra) y termina
     en el borde derecho de esa misma área; como ".cv-panel" ya tiene el
     mismo padding a los dos lados, el hueco hasta el borde real del
     panel sale igual en ambos lados sin tener que forzarlo aquí. */
  border-top: 1px solid #ddd;
  padding-top: 1em;
  margin-bottom: 2em;
  text-align: right;
}

/* Manual de identidad (u otro documento paginado): un único bloque a
   ancho completo bajo el texto — muestra la portada y, al pulsar sobre
   él, va pasando el resto de páginas en orden (ver el listener de
   ".more-manual" en script.js), en vez de listar todas las páginas
   seguidas. */
.more-manual {
  width: 100%;
  cursor: pointer;
  margin-bottom: 1.5em;
}

.more-manual-item img,
.more-manual-item video {
  display: block;
  width: 100%;
  height: auto;
}

.more-manual-item .placeholder-img {
  width: 100%;
  display: block;
  background: linear-gradient(135deg, #e2e2e2, #cfcfcf);
}

/* Contador "n/total" + flechas debajo del manual (ver renderMorePanel /
   setManualSlide en script.js). Mismo tamaño de letra que el resto del
   texto del panel (font-size:1em, hereda de .cv-panel) — no es un
   control de interfaz aparte, se lee como parte del propio texto. */
.more-manual-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75em;
  margin-top: -0.5em;
  margin-bottom: 1.5em;
  font-style: italic;
}

.more-manual-nav button {
  font-family: inherit;
  font-size: 1em;
  font-style: inherit;
  line-height: 1;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  color: inherit;
  cursor: pointer;
  opacity: 0.7;
  transition: opacity 0.2s ease;
}

.more-manual-nav button:hover {
  opacity: 1;
}

.more-manual-counter {
  min-width: 2.5em;
  text-align: center;
}

/* Galería del panel "More...": ocupa todo el ancho disponible bajo el
   texto de cabecera, una imagen debajo de otra a ancho completo (nada de
   columnas ni recortes) — pensada para manuales de identidad / páginas de
   PDF exportadas como imágenes, donde interesa ver cada página entera. */
.more-gallery {
  display: flex;
  flex-direction: column;
  gap: 1.5em;
}

.more-gallery:not(:empty) {
  margin-bottom: 1.5em;
}

.more-gallery-item {
  margin: 0;
  width: 100%;
}

.more-gallery-item img,
.more-gallery-item video {
  display: block;
  width: 100%;
  height: auto;
}

.more-gallery-item .placeholder-img {
  width: 100%;
  display: block;
  background: linear-gradient(135deg, #e2e2e2, #cfcfcf);
}

@media (max-width: 768px) {
  .more-header {
    flex-direction: column;
    gap: 1em;
  }

  .more-header-left {
    flex: none;
  }
}

.cv-entry-title {
  margin: 0;
}

@media (max-width: 768px) {
  .cv-panel {
    width: 100%;
    padding: 9.86px;
    /* a este ancho el panel ocupa toda la pantalla, así que su borde
       izquierdo coincidiría con el borde real de la pantalla: se quita. */
    border-left: none;
  }

  /* A este ancho ".page-content" también pasa a ocupar el 100% (ver su
     propio media query), así que el "More" debe volver a la pantalla
     completa en vez de arrancar en "--project-col-width". Solo aplica
     mientras el panel siga dentro de "#more-overlay" -en cuanto
     positionMorePanel() lo mueve a ".is-inline" (ver script.js), esta
     regla deja de encajar sola (ya no es descendiente de
     "#more-overlay") y manda la de abajo. */
  #more-overlay .cv-panel {
    left: 0;
    width: 100%;
  }

  /* Panel "(More...)" en móvil: en vez de la pantalla fija de arriba,
     fluye en el sitio real donde lo coloca positionMorePanel() (ver
     script.js) -justo debajo de la imagen principal del proyecto,
     dentro del propio overlay que ya es scrollable-, como un bloque
     más de la página, no como una capa aparte encima de todo. */
  #more-panel.is-inline {
    position: static;
    inset: auto;
    width: 100%;
    height: auto;
    max-height: none;
    overflow: visible;
    border-left: none;
    /* Sin línea propia aquí arriba: "#more-panel" es HERMANO de
       ".page-content" (no vive dentro), así que no hereda su padding
       lateral -de ahí que el texto llegara casi al borde-. Mismo
       9.86px que ".page-content" a los dos lados, así el título, la
       categoría, "More/Close" y el texto expandido comparten un único
       margen horizontal, sin inventar uno aparte aquí. La única línea
       divisoria que queda es la de ".more-close-row" (ver más abajo,
       ya existía antes de este cambio) justo antes del "(Close)" final
       -dos líneas seguidas (esta y esa) eran redundantes-. */
    padding: 9.86px;
    margin-top: 9.86px;
  }

  /* La ficha (crédito/categoría) del proyecto ya se ve justo encima, en
     el propio bloque de la imagen (".page-content", con sus enlaces
     "(More...)"/"(Close)") — repetirla aquí debajo sería un título
     duplicado sin ningún motivo. Se oculta solo esa mitad de la
     cabecera; ".more-header-right" (el texto real, lo único nuevo que
     aporta el panel) se queda. */
  #more-panel.is-inline .more-header-left {
    display: none;
  }

  /* El "(Close)" propio del panel sobra aquí (a petición): el mismo
     botón "(More...)" de arriba ya hace de interruptor -pasa a
     "(Less...)" y vuelve a colapsar el panel, ver el toggle en
     script.js- así que tener además este segundo enlace para lo mismo
     era redundante. En escritorio se queda tal cual (panel lateral
     aparte, sigue haciendo falta su propio cierre). */
  #more-panel.is-inline .more-close-row {
    display: none;
  }
}

/* ---------- Galería ---------- */

/* Experimento de layout "editorial": el bloque de imagen se ajusta a la
   proporción real de la imagen mostrada (no al revés). gallery-slideshow
   deja de ocupar todo el ancho de la columna: se encoge a su contenido
   (width:fit-content) y se centra, así que una imagen horizontal da un
   bloque ancho, una vertical uno más estrecho y alto, y una cuadrada uno
   cuadrado — cambiar de imagen puede dejar más o menos aire alrededor, y
   es intencional. cursor-zone/slideshow-nav-btn (position:absolute
   dentro de gallery-slideshow) quedan automáticamente ajustados a los
   límites reales de la imagen visible, no a los de la columna entera. */
gallery-slideshow {
  position: relative;
  display: block;
  width: fit-content;
  max-width: 100%;
  margin: 0 auto 2.5em;
  user-select: none;
}

.media-item {
  position: relative;
}

.media-item img,
.media-item video {
  /* Nunca ocupa toda la altura de la columna: max-height la limita a una
     fracción del viewport (deja aire debajo siempre, y evita que la
     página entera necesite scroll) — dentro de ese límite, la imagen se
     muestra a su tamaño y proporción reales, sin recortar ni deformar.
     Mismo criterio para ".gif" antiguos ya servidos como vídeo mp4 (ver
     mediaMarkup en script.js): mismo aspecto, solo mucho menos peso. */
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 76vh;
}

.media-item .placeholder-img {
  /* Sin imagen real aún no hay proporción "real" que seguir: se usa un
     tamaño de relleno razonable (ver ratio en data.js) para no colapsar
     a 0 dentro de un contenedor que ahora se encoge a su contenido. */
  display: block;
  width: min(60vw, 600px);
  max-height: 76vh;
  background: linear-gradient(135deg, #e2e2e2, #cfcfcf);
}

/* "fixedGalleryWidth" (ver computeFixedGalleryWidth en script.js): en vez
   de que cada imagen tenga su propio ancho según su proporción, todas
   comparten el ancho fijado por JS en el propio "gallery-slideshow"
   (style="width:...") y solo el alto se ajusta según cada una. */
gallery-slideshow.fixed-width .media-item img,
gallery-slideshow.fixed-width .media-item video,
gallery-slideshow.fixed-width .media-item .placeholder-img {
  width: 100%;
  max-height: none;
}

/* Nota: el gallery-slideshow muestra una sola imagen a la vez (las demás
   quedan en display:none), así que NO debe haber margen entre
   ".media-item" según su orden en el DOM — si no, la imagen que ocupa el
   2º/3º/... puesto en el DOM (aunque sea la única visible) queda con más
   separación arriba que la 1ª, y el margen superior "salta" al navegar
   con las flechas. Por eso aquí no se define ningún margin-top. */

/* Zonas de cursor: mitad izquierda / mitad derecha (navigation="cursor-zones").
   Sin icono nativo w-resize/e-resize -con el cursor personalizado (ver
   custom-cursor.js) ya no se vería igualmente, tapado por el punto- ni
   botón visible en escritorio: el propio punto turquesa avisa de que se
   puede navegar, transformándose en un triángulo que apunta al lado
   real por el que se pasaría de imagen (ver ".custom-cursor-triangle"
   y NAV_LEFT_SELECTOR/NAV_RIGHT_SELECTOR en custom-cursor.js). */
gallery-slideshow.cursor-zones .cursor-zone {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 50%;
  z-index: 2;
}

gallery-slideshow.cursor-zones .cursor-zone.left {
  left: 0;
}

gallery-slideshow.cursor-zones .cursor-zone.right {
  right: 0;
}

/* Botón de navegación (← / →, mismas flechas que .more-manual-nav, sin
   círculo ni fondo, solo la flecha blanca): solo en móvil
   (mobile-navigation="buttons"). En escritorio se queda oculto del
   todo. Abajo de la imagen, no a los lados a media altura -mismo
   margen (15px) que deja por los lados, para que quede igual de
   "pegado" a los tres bordes-. */
.slideshow-nav-btn {
  display: none;
  position: absolute;
  bottom: 15px;
  width: 30px;
  height: 30px;
  background: none;
  border: none;
  color: #fff;
  font-size: 22px;
  align-items: center;
  justify-content: center;
  z-index: 3;
  cursor: pointer;
  opacity: 0.7;
  transition: opacity 0.2s ease;
}

.slideshow-nav-btn:hover { opacity: 1; }

.slideshow-nav-btn.prev { left: 15px; }
.slideshow-nav-btn.next { right: 15px; }

@media (max-width: 768px) {
  gallery-slideshow.cursor-zones .cursor-zone {
    display: none;
  }
  .slideshow-nav-btn {
    display: flex;
  }
}

/* Fila de crédito / caption, 8/4 columnas, gutter 0.5rem (medido) */
column-set {
  display: flex;
  gap: 0.5rem;
  width: 100%;
}

column-set .col-8 {
  flex: 0 0 calc(66.666% - 0.25rem);
}

column-set .col-4 {
  flex: 0 0 calc(33.333% - 0.25rem);
  text-align: right;
}

.caption-credit {
  margin: 0;
}

.caption-sub {
  font-style: italic;
}

@media (max-width: 768px) {
  column-set {
    flex-wrap: wrap;
  }
  column-set .col-8,
  column-set .col-4 {
    flex: 0 0 100%;
    text-align: left;
  }
  column-set .col-4 {
    margin-top: 0.25em;
  }
}

/* ---------- Página /visual-archive ---------- */
/* Estructura calcada de la referencia (594995-a.cargo.site): dos filas
   de piezas -una encima y otra debajo del título/botones, que quedan
   centrados en medio de la página- ocupando todo el ancho de la
   ventana, sin scroll (ver ".archive-page"/".archive-grid" más abajo).
   Misma tipografía (EB Garamond) y fondo (#fffff3) que el resto del
   sitio — solo se copia la estructura, no el estilo visual de la
   referencia (que usa Monument Grotesk Mono y fondo blanco). */
.archive-page {
  /* Dos filas horizontales -nunca columnas- con el título/controles en
     medio: ".archive-page" reparte sus 3 hijos (fila de arriba, centro,
     fila de abajo) a lo ancho de al menos una pantalla completa
     (min-height:100vh) con "justify-content:space-between", así que el
     bloque del título cae solo, sin JS, exactamente en el hueco que
     dejan las dos filas -por poco o mucho que varíe ese hueco según las
     alturas reales de cada pieza-. Sin límite de alto ni overflow:hidden:
     si la fila de abajo pesa más que el resto de la pantalla, sigue
     su curso normal y la ventana la recorta por su borde inferior -eso
     es justo lo que se busca (piezas a media altura ahí abajo), no un
     bug a evitar. Un margen vertical propio (padding, con
     box-sizing:border-box para que min-height:100vh siga midiendo el
     alto TOTAL, margen incluido) separa la primera/última pieza del
     borde real de la pantalla -ni pegadas a él ni, con ese margen ya
     restando espacio disponible, con tanto sobrante como para necesitar
     scroll en pantallas normales-. */
  position: relative;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  min-height: 100vh;
  justify-content: space-between;
  padding: 2.5rem 0;
  /* El "fan" del hover empuja/escala piezas hacia los lados (ver
     initGalleryHoverEffect en archive.js) — el propio JS ya frena ese
     movimiento antes de tocar el borde real de la ventana, pero este
     overflow:hidden es la red de seguridad final para que, pase lo que
     pase, nunca aparezca una barra de scroll horizontal ni una pieza
     "a medias" fuera del margen. */
  overflow-x: hidden;
}

/* Fila horizontal de piezas -ancho completo, todas las columnas del
   mismo ancho-, nunca una columna vertical. Cada pieza lleva su propia
   altura (ver TOP_RATIOS/BOTTOM_RATIOS en archive.js), distinta de sus
   vecinas, para un borde irregular en el lado que da hacia el título. */
.archive-grid {
  display: flex;
  column-gap: 1.2rem;
  padding: 0 1.2rem;
  /* Por encima de ".palette-wave" (ver más abajo): las imágenes se
     quedan siempre visibles durante el cambio de color de fondo, en vez
     de quedar tapadas un instante por la mancha mientras se expande. */
  position: relative;
  z-index: 2;
}

/* Fila de arriba: piezas anclada por ARRIBA (creciendo hacia abajo), así
   el borde irregular queda abajo, mirando al título. */
.archive-grid--top {
  align-items: flex-start;
}

/* Fila de abajo: piezas ancladas por ABAJO (creciendo hacia arriba), así
   el borde irregular queda arriba, mirando al título; el borde de abajo
   es común a todas y, si pesa más que el hueco libre de la pantalla,
   la ventana lo recorta -piezas a media altura, nunca una franja vacía-. */
.archive-grid--bottom {
  align-items: flex-end;
  padding-bottom: 0;
}

.archive-grid .archive-item {
  flex: 1 1 0;
  min-width: 0;
}

/* Flecha de vuelta a la portada: fija en su esquina, como el selector de
   idioma — ya no forma parte de la fila del título (que ahora está
   centrada en medio de la página, ver ".archive-center"). */
.archive-back {
  position: fixed;
  top: 6.48px;
  left: 6.48px;
  z-index: 998;
  font-size: 1.7em;
  line-height: 0.95;
  transition: opacity 0.2s;
}

/* Título + botones: tercer hijo de ".archive-page" (space-between), así
   que cae solo -sin medir nada por JS- en el hueco real que dejan las
   dos filas, centrado tanto vertical como horizontalmente en él. */
.archive-center {
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  margin: auto 0;
}

.archive-header {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.9em;
}

.archive-back:hover {
  opacity: 0.6;
}

/* Aún más grande: el título es el punto focal fuerte de la composición,
   el elemento que domina todo el hueco central. */
.archive-title {
  margin: 0;
  font-size: 5em;
  font-weight: 400;
  line-height: 0.95;
  order: 2;
}

.archive-title i {
  font-style: italic;
}

/* Botones de cabecera ("+" info y "switch palette"): mismo lenguaje
   tipográfico que el resto del sitio (EB Garamond), discretos, sin
   parecer controles genéricos de interfaz. */
.archive-btn {
  font-family: inherit;
  background: none;
  /* el borde siempre es del mismo color que el texto (currentColor): si
     el contraste automático pone el texto en blanco (fondo oscuro) o en
     negro (fondo claro), el recuadro del botón sigue exactamente igual,
     nunca un gris intermedio de por medio. */
  border: 1px solid currentColor;
  color: #1a1a1a;
  cursor: pointer;
  line-height: 1;
  opacity: 0.75;
  transition: opacity 0.2s;
}

.archive-btn:hover {
  opacity: 1;
}

.archive-btn--square {
  width: 1.7em;
  height: 1.7em;
  padding: 0;
  font-size: 1em;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Orden visual de los controles alrededor del título -sin tocar el
   orden real del HTML-: "+" a la izquierda, título en medio, "switch
   palette" a la derecha y "×" pegado a su lado. */
#archive-info-toggle {
  order: 1;
}

#archive-palette-toggle {
  order: 3;
}

#archive-palette-reset {
  order: 4;
}

/* La "×" (ver #archive-palette-reset) se queda siempre en el layout
   -mismo hueco reservado, mismo "order" fijo al lado de "switch
   palette"- y solo aparece/desaparece con opacidad: así nunca cambia el
   ancho real de la cabecera al pulsar "switch palette", y el bloque
   entero (título incluido, centrado como un todo) no se desplaza. */
.archive-btn--square.is-hidden {
  opacity: 0;
  pointer-events: none;
}

.archive-btn--pill {
  padding: 0.35em 0.9em;
  font-size: 0.85em;
  font-style: italic;
}

.archive-info-panel {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  margin-bottom: 0;
  transition: grid-template-rows 0.4s cubic-bezier(0.22, 1, 0.36, 1),
    opacity 0.3s ease, margin-bottom 0.4s cubic-bezier(0.22, 1, 0.36, 1);
}

.archive-info-panel.open {
  grid-template-rows: 1fr;
  opacity: 1;
  margin-bottom: 2em;
}

.archive-info-panel p {
  margin: 0;
  max-width: 40em;
  overflow: hidden;
  font-size: 1em;
  line-height: 1.4;
}

/* Cada pieza estira su imagen al ancho completo de su hueco en la fila
   (todos los huecos miden lo mismo, así que esto ya basta para que se
   note la variedad real de altura entre piezas) — el alto sale solo del
   aspect-ratio propio de cada una (ver TOP_RATIOS/BOTTOM_RATIOS en
   archive.js), nunca igualado al de sus vecinas. */
.archive-grid .archive-item img,
.archive-grid .archive-item video,
.archive-grid .archive-item .placeholder-img {
  display: block;
  /* La altura de cada pieza sale de su proporción REAL (ver "ratio" en
     ARCHIVE_ITEMS), nunca de un valor inventado -pero con un tope en vh
     para que ninguna pieza especialmente vertical pueda, ella sola,
     hacer más alta que la pantalla la fila entera y forzar scroll-. Al
     llegar a ese tope se hace más ESTRECHA (nunca se recorta ni se
     deforma: sigue siendo exactamente su misma proporción, solo más
     pequeña), y se centra en el hueco de su columna -que sigue midiendo
     lo mismo que las demás, la estructura no cambia-. */
  width: auto;
  max-width: 100%;
  height: auto;
  max-height: 34vh;
  margin: 0 auto;
}

/* Interacción "fan" al pasar el ratón (ver initGalleryHoverEffect en
   archive.js): la pieza activa crece y las vecinas se apartan de verdad
   (translate calculado por JS), nunca se solapan por z-index sin más —
   el z-index de la pieza activa solo evita que su propio crecimiento
   quede tapado por la vecina que tiene justo al lado, que a su vez ya se
   ha desplazado. Transición suave y sin rebote, sensación editorial. */
.archive-item {
  margin: 0;
  position: relative;
  z-index: 1;
  transform-origin: center;
  transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
  will-change: transform;
}

/* La primera pieza de la fila crece solo hacia la derecha y la última
   solo hacia la izquierda -nunca hacia fuera de la pantalla-, en vez de
   crecer por igual a los dos lados como el resto. */
.archive-grid .archive-item:first-child {
  transform-origin: left center;
}

.archive-grid .archive-item:last-child {
  transform-origin: right center;
}

.archive-item.is-hovered {
  z-index: 2;
}

.archive-item .placeholder-img {
  width: 100%;
  background: linear-gradient(135deg, #e2e2e2, #cfcfcf);
}

.archive-item img,
.archive-item video {
  /* Sin esto, un <img>/<video> real con solo "aspect-ratio" puesto por
     inline style (ver gridMediaMarkup en archive.js) y sin ancho
     definido cae en el tamaño de relleno por defecto del navegador para
     imágenes sin cargar/rotas -que puede acabar usando los propios
     números del ratio como si fuesen píxeles literales-, reventando el
     layout de la rejilla entera. display:block + width:100% asegura que
     el aspect-ratio se calcula contra el ancho real de la columna, igual
     que ".placeholder-img". */
  display: block;
  width: 100%;
  height: auto;
}

.archive-caption {
  margin-top: 0.4em;
  font-size: 11px;
  line-height: 1.3;
  color: rgba(26, 26, 26, 0.45);
}

/* Fila de arriba: la ficha va ANTES que la imagen en el propio DOM (ver
   renderItem en archive.js, no un giro visual de la imagen -las
   imágenes están siempre rectas-), así que aquí se invierte el margen:
   pegada al texto de abajo (la imagen), no al de arriba. */
.archive-grid--top .archive-caption {
  margin-top: 0;
  margin-bottom: 0.4em;
}

.archive-caption a {
  color: inherit;
}

.archive-caption a:hover {
  color: #1a1a1a;
}

.archive-item .placeholder-img,
.archive-item img,
.archive-item video {
  cursor: pointer;
}

/* ---------- Visor de imagen a pantalla completa ---------- */
/* Se abre al pulsar una pieza de la rejilla: ".archive-viewer-media" nace
   con position:fixed en el sitio y tamaño EXACTOS de la imagen pulsada
   (fijados por JS antes de esta transición) y anima hacia el recuadro
   grande centrado — efecto "crece desde su sitio" (técnica FLIP). Al
   navegar con la rueda/trackpad, solo cambia de tamaño/posición (según la
   proporción real de cada imagen) y hace crossfade del contenido; nunca
   fuerza un recuadro fijo igual para todas. */
.archive-viewer {
  position: fixed;
  inset: 0;
  z-index: 10000;
  /* Color de relleno por si el visor llega a pintarse un instante antes
     de que JS fije el de verdad (ver applyViewerBackdrop en archive.js):
     el mismo fondo de la página por defecto, algo transparente. El color
     real -que seas cual sea el fondo actual, incluida cualquier paleta
     elegida con "switch palette"- lo pone JS al abrir, para que el visor
     no de un fondo negro que no pega con el resto del sitio. */
  background-color: rgba(255, 255, 243, 0.9);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.35s ease, background-color 0.35s ease;
}

.archive-viewer.open {
  opacity: 1;
  pointer-events: auto;
}

.archive-viewer-media {
  position: fixed;
  overflow: hidden;
  transition: top 0.5s cubic-bezier(0.22, 1, 0.36, 1),
    left 0.5s cubic-bezier(0.22, 1, 0.36, 1),
    width 0.5s cubic-bezier(0.22, 1, 0.36, 1),
    height 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}

.archive-viewer-media img,
.archive-viewer-media video,
.archive-viewer-media .placeholder-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  opacity: 0;
  transition: opacity 0.25s ease;
}

.archive-viewer-media .placeholder-img {
  background: linear-gradient(135deg, #e2e2e2, #cfcfcf);
}

.archive-viewer-media img.is-visible,
.archive-viewer-media video.is-visible,
.archive-viewer-media .placeholder-img.is-visible {
  opacity: 1;
}

.archive-viewer-close {
  position: fixed;
  top: 1.5em;
  right: 1.5em;
  width: 2em;
  height: 2em;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: inherit;
  font-size: 1.4em;
  line-height: 1;
  background: none;
  border: none;
  /* Mismo criterio de contraste que el resto de la cabecera (ver
     "body.on-dark-bg" más abajo): como el fondo del visor es ahora el
     mismo color de la página -no siempre negro-, el "×" tiene que seguir
     ese mismo contraste automático en vez de un blanco fijo que en un
     fondo claro sería invisible. */
  color: rgba(26, 26, 26, 0.75);
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, color 0.2s ease;
}

body.on-dark-bg .archive-viewer-close {
  color: rgba(255, 255, 255, 0.85);
}

.archive-viewer.open .archive-viewer-close {
  opacity: 1;
  pointer-events: auto;
}

.archive-viewer-close:hover {
  color: #1a1a1a;
}

body.on-dark-bg .archive-viewer-close:hover {
  color: #fff;
}

/* "Switch palette" (ver archive.js): mancha radial difuminada que nace en
   el centro del botón y se expande hasta cubrir el viewport. Blur +
   gradiente radial con parada intermedia = borde orgánico, nunca un
   círculo duro. Tras la transición, archive.js pone el color como fondo
   sólido de <body> y quita este elemento (mismo color, sin parpadeo). */
.palette-wave {
  position: fixed;
  left: 0;
  top: 0;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  transform: translate(-50%, -50%) scale(0);
  transform-origin: center;
  pointer-events: none;
  /* Por debajo de ".archive-grid" (ver más arriba): el efecto de cambio
     de color queda siempre detrás de las imágenes, nunca las tapa
     mientras se expande. */
  z-index: 1;
  filter: blur(10px);
  transition: transform 0.7s cubic-bezier(0.45, 0, 0.2, 1), opacity 0.3s ease;
}

/* Contraste automático de texto/iconos según el color de fondo elegido
   (ver applyContrast en archive.js, que calcula la luminancia y añade
   esta clase a <body> cuando el fondo es oscuro). */
body.on-dark-bg {
  color: #fff;
}

body.on-dark-bg .archive-title,
body.on-dark-bg .archive-info-panel p {
  color: #fff;
}

body.on-dark-bg .archive-caption {
  color: rgba(255, 255, 255, 0.6);
}

body.on-dark-bg .archive-caption a:hover {
  color: #fff;
}

body.on-dark-bg .archive-btn {
  color: #fff;
}

body.on-dark-bg .lang-switch {
  color: rgba(255, 255, 255, 0.5);
}

body.on-dark-bg .lang-switch .lang-sep {
  color: rgba(255, 255, 255, 0.3);
}

body.on-dark-bg .lang-switch .lang-link.active,
body.on-dark-bg .lang-switch .lang-link:hover {
  color: #fff;
}

@media (max-width: 768px) {
  .archive-title {
    font-size: 2.2em;
  }

  .archive-back {
    font-size: 1.4em;
  }

  .archive-grid {
    column-gap: 0.6rem;
    padding-left: 0.6rem;
    padding-right: 0.6rem;
  }

  .archive-header {
    gap: 0.5em;
  }
}

/* ---------- Cursor personalizado (punto turquesa) ---------- */
/* Solo con ratón real (hover:hover + pointer:fine): en táctil no se
   toca nada, se deja el comportamiento normal (ver custom-cursor.js,
   que además comprueba lo mismo antes de crear el punto). "!important"
   a propósito: de sobra hay reglas por su cuenta con "cursor: pointer"
   en alguna clase (".more-manual", los botones, las piezas del
   archivo...) que, al tener más especificidad que el selector universal
   "*", ganarían y colarían la manita nativa por encima del punto -este
   cursor es una excepción de verdad "siempre gana", no un valor por
   defecto más a pisar. */
@media (hover: hover) and (pointer: fine) {
  * {
    cursor: none !important;
  }
}

.custom-cursor {
  position: fixed;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  pointer-events: none;
  z-index: 99999;
  will-change: transform;
  transition: opacity 0.15s ease;
}

/* Oculto por completo sobre "noa abad" (ver HIDE_SELECTOR en
   custom-cursor.js): ahí ya aparece "(ABOUT)" pegada al cursor, así que
   el punto no debe verse a la vez por encima. También oculto si el
   ratón sale de la ventana (ver "is-outside" en custom-cursor.js). */
.custom-cursor.is-suppressed,
.custom-cursor.is-outside {
  opacity: 0;
}

/* El punto, centrado en la posición del ratón: mismo turquesa y redondez
   que el favicon, pero dibujado con "background-color" (círculo liso,
   sin ningún detalle que perder) en vez de la imagen SVG -así se puede
   pasar a un triángulo con clip-path (ver ".custom-cursor-triangle" más
   abajo) sin recortar una imagen de forma rara-. Tamaño/opacidad
   normales aquí; el estado "is-active" (ver custom-cursor.js) es lo
   único que cambia al pasar por encima de algo interactivo genérico, y
   "is-nav" (ver más abajo) lo apaga del todo cuando lo que se ve es el
   triángulo direccional de la imagen de proyecto. */
/* Misma curva/duración que el resto de transiciones "editoriales" del
   sitio (ver p.ej. ".archive-item" en style.css): un pelín de
   desaceleración al final en vez de una velocidad lineal constante -es
   lo que hace que un cambio de forma/tamaño se perciba como fluido y no
   como un simple "fundido a velocidad fija"-. */
.custom-cursor-dot,
.custom-cursor-triangle {
  /* Base -sobre todo para la transición círculo↔triángulo, ver
     ".is-nav"/".is-nav-left" más abajo-: 350ms con una curva ease-in-out
     de verdad (empieza y termina despacio), para que el cambio de forma
     se sienta como una única transformación continua, no como un
     elemento sustituyendo a otro. ".is-active" (crecer al pasar por
     encima de un enlace/botón) pisa esto con su propia duración más
     ágil -eso no es lo que se pedía suavizar-, así que solo se nota
     este ritmo más pausado al entrar/salir del triángulo direccional y
     al soltar el hover genérico. */
  transition-duration: 0.35s;
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

.custom-cursor-dot {
  position: absolute;
  top: 0;
  left: 0;
  width: 14px;
  height: 14px;
  margin: -7px 0 0 -7px;
  background-color: #00fff6;
  border-radius: 50%;
  opacity: 0.85;
  transform: scale(1);
  transition-property: transform, opacity;
}

.custom-cursor-dot.is-active {
  /* Solo esta entrada (crecer al pasar por encima de un enlace/botón
     normal) mantiene el ritmo ágil de siempre -no era esto lo que se
     pedía suavizar, solo el cambio círculo↔triángulo (ver arriba)-. Al
     soltar el hover sí se usa la duración base más pausada, que es la
     que de verdad se nota al volver de un estado a otro. */
  transition-duration: 0.2s;
  transition-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
  transform: scale(1.7);
  opacity: 1;
}

.custom-cursor.is-nav .custom-cursor-dot {
  opacity: 0;
  transform: scale(0.6);
}

/* Pulso al hacer clic (ver custom-cursor.js): un "tic" táctil muy breve,
   no un anillo aparte que se quede flotando -mismo punto, misma
   posición, un único disparo de @keyframes-. "--click-base-scale" lo
   fija JS con el tamaño que ya tuviera el punto en ese instante (1
   normal, 1.7 sobre algo interactivo), así el pulso siempre crece
   "desde donde esté" en vez de partir de un tamaño fijo que no
   encajaría con el estado de hover real en el momento del clic. */
@keyframes cursor-click-pulse {
  0% {
    transform: scale(var(--click-base-scale, 1));
    opacity: var(--click-base-opacity, 0.85);
  }
  35% {
    transform: scale(calc(var(--click-base-scale, 1) * 1.5));
    opacity: 0.35;
  }
  100% {
    transform: scale(var(--click-base-scale, 1));
    opacity: var(--click-base-opacity, 0.85);
  }
}

.custom-cursor-dot.is-clicking {
  animation: cursor-click-pulse 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}

@media (prefers-reduced-motion: reduce) {
  .custom-cursor-dot.is-clicking {
    animation: none;
  }
}

/* Triángulo direccional sobre las mitades de la imagen de proyecto (ver
   NAV_LEFT_SELECTOR/NAV_RIGHT_SELECTOR en custom-cursor.js): mismo
   turquesa liso que el punto, mismo tamaño de "huella" aproximado -nada
   dominante-, centrado en el cursor exactamente igual que el punto
   (mismo criterio de margen que ".custom-cursor-dot": la mitad del
   ancho/alto en negativo) para que pasar de uno a otro no dé ningún
   salto de posición. Apunta a la derecha por defecto (mitad derecha =
   siguiente imagen) y se refleja con scaleX(-1) en la mitad izquierda,
   con "transform-origin" en el centro -así el giro pivota sobre su
   propio sitio en vez de desplazarse- para que el cambio de dirección
   se vea como un giro suave y no como un salto entre dos iconos
   distintos. */
.custom-cursor-triangle {
  position: absolute;
  top: 0;
  left: 0;
  width: 12px;
  height: 15px;
  margin: -7.5px 0 0 -6px;
  transform-origin: 50% 50%;
  background-color: #00fff6;
  clip-path: polygon(0% 0%, 100% 50%, 0% 100%);
  opacity: 0;
  transform: scale(0.6) scaleX(1);
  transition-property: transform, opacity;
}

.custom-cursor.is-nav .custom-cursor-triangle {
  opacity: 1;
  transform: scale(1) scaleX(1);
}

.custom-cursor.is-nav-left .custom-cursor-triangle {
  transform: scale(1) scaleX(-1);
}
