:root {
  --bg-base: #F5F5F0;
  --bg-pure: #FFFFFF;
  --bg-dark: #111111;
  --text-primary: #111111;
  --text-secondary: #78736C;
  --accent: #E8F400;
  --border: #E0E0DC;
  --error: #D92D20;

  /* Tipografía — spec 09-04, sexta pasada: "Helvética" (Switzer) extendida
     de Home a el resto del sitio, por categoría (H1/H2-H3/texto), salvo los
     7 grupos que Blas pidió mantener sin cambios: navbar+dropdown, footer,
     buscador, hover sobre imágenes, formulario de contacto, epígrafes/citas
     y botones — esos siguen con sus fuentes de siempre. Cada variable es un
     ROL funcional, no una familia fija: reasignar a futuro es cambiar el
     valor acá. Pesos: donde el peso pedido no existe como archivo estático
     de la fuente, se usa el peso disponible más cercano (ver
     decisions/log.md). */
  --font-h1-home: 'Switzer', sans-serif;                   /* H1 de home — 700 */
  --font-logo: 'Switzer', sans-serif;                       /* Logo / marca — 600. Mismo wordmark en las 14 páginas: el cambio se ve en todo el sitio, no solo en Home. */
  --font-home-h2: 'Switzer', sans-serif;                    /* H2 de Home — "Sobre Josefina" (600) y título del CTA de mentorías (700). El CTA es un componente compartido: el cambio también se ve en las otras 13 páginas. */
  --font-home-text: 'Switzer', sans-serif;                  /* Texto de Home — bio breve (500) y subtítulo del CTA de mentorías (500, mismo alcance sitewide que el punto anterior). */
  --font-nav: 'Archivo', sans-serif;                        /* Navbar y dropdown de navegación (comparten token) — sin cambios, 500 medium / 700 en estados bold */
  --font-body: 'Plus Jakarta Sans', sans-serif;             /* Botones, formulario y buscador — sin cambios */
  --font-h1-inner: 'Switzer', sans-serif;                   /* H1 de páginas interiores (fuera de Home) — 700, antes Bespoke Serif */
  --font-h2: 'Switzer', sans-serif;                         /* Subtítulos H2/H3 (fuera de Home) — 600, antes Literata */
  --font-text: 'Switzer', sans-serif;                       /* Texto general / párrafos fuera de Home (y citas de Tropismes) — 500, antes Sentient */
  --font-hover-title: 'Rowan', sans-serif;                  /* Hover sobre imágenes — About y Curaduría, 600 semibold */
  --font-hover-oi: 'Helvetica Neue', Helvetica, Arial, sans-serif;  /* Hover sobre imágenes — excepción Objeto Instalación/Exposiciones: letra más suave, sin negrita (400). Antes Panchang (Fontshare) — cambiado a Helvetica del sistema, sin depender de un CDN externo. */
  --font-hover-lamarencoche: 'Switzer', sans-serif;         /* Hover sobre imágenes — excepción Lamarencoche: Helvética, 600 */
  --font-accent: 'Hoover', sans-serif;                      /* Epígrafes / citas — sin cambios, 300 (sin itálica: Hoover no tiene cursiva) */
  --font-footer: 'Cabinet Grotesk', sans-serif;             /* Footer — 500 (pedido 450, no existe) */
  --font-search-title: 'Author', sans-serif;                /* Buscador — título de resultado, reemplaza a Fraunces itálica */
  --font-search-thumb: 'Clash Display', sans-serif;         /* Buscador — inicial de fallback sin foto, sin cambios */

  --space-xs: 8px;
  --space-sm: 16px;
  --space-md: 24px;
  --space-lg: 32px;
  --space-xl: 48px;
  --space-2xl: 64px;
  --space-3xl: 96px;

  --gap-sm: 16px;
  --gap-md: 32px;
  --gap-lg: 64px;

  --nav-height: 64px;
}

* { box-sizing: border-box; }

/* scroll-behavior: smooth queda desactivado a propósito: interfiere con el
   scrub de ScrollTrigger (el navegador anima el scroll al mismo tiempo que
   GSAP lo lee, y genera tirones). Si se necesita scroll suave a anclas,
   usar ScrollToPlugin de GSAP en vez de esta propiedad CSS. */

body {
  margin: 0;
  padding-top: var(--nav-height);
  background: var(--bg-base);
  color: var(--text-primary);
  font-family: var(--font-text);
  /* El breakout full-bleed de la galería de Objeto Instalación (100vw vía
     left:50%/margin-left:-50vw) puede generar un scrollbar horizontal por
     redondeo de subpíxel — se lo bloquea acá en vez de por página. */
  overflow-x: hidden;
}

/* Sin el flash azul/gris que Chrome Android pinta sobre lo que se toca
   (-webkit-tap-highlight-color es heredable, así que en `html` cubre todo).
   `user-select: none` en las fotos/tiles clickeables evita además el
   resaltado azul de "texto seleccionado" al tocar su leyenda. El texto de
   lectura (bio, statements, etc.) no se toca: sigue seleccionable. */
html { -webkit-tap-highlight-color: transparent; }
.obra-oi-card,
.obra-oi-detail-photo,
.obra-tile,
.cur-tile,
.obra-photo,
.lightbox,
.obra-oi-nav-arrow,
.obra-serie-selector {
  -webkit-user-select: none;
  user-select: none;
}

/* ---------- Efectos hover — solo en dispositivos con hover real ----------
   Bug reportado: en touch, muchos navegadores igual "activan" :hover al
   tocar (compatibilidad con sitios viejos que dependían de hover para
   mostrar contenido) y no lo sueltan hasta el próximo tap en otro lado —
   así, el botón de Mentorías quedaba en fluor después de volver de la
   pestaña de mentorías, las flechitas de navegación de foto se quedaban
   marcadas después de tocarlas, "Ver más" de la Estrategia del Caracol
   seguía subrayado al replegar el texto, etc. TODOS los efectos de :hover
   puro del sitio (ninguno de estos tiene una contraparte en JS/estado que
   los apague solo) se agrupan acá, gateados por `(hover: hover) and
   (pointer: fine)` — la forma correcta de preguntarle al navegador "¿este
   dispositivo tiene un puntero real que puede quedarse posado sin tocar?"
   en vez de intentar detectar touch indirectamente. En touch, esta media
   feature no matchea nunca, así que ninguna de estas reglas llega a
   aplicarse — ergo nunca queda "pegada". Estados persistentes reales
   (`.is-open`, `.is-active`, `.is-current`, `:focus-visible`) NO viven acá:
   esos deben seguir funcionando en cualquier dispositivo, se manejan aparte
   en cada sección (ver comentarios puntuales donde corresponde). */
@media (hover: hover) and (pointer: fine) {
  .btn-pill:hover {
    background: var(--accent);
    color: var(--text-primary);
  }
  .nav-item:hover .underline { width: 34px; }
  .icon-search:hover svg path { stroke-width: 10; }
  .nav-dropdown-list a:hover .underline { width: 100%; }
  /* Levanta la foto sutilmente al hover — señal de que es clickeable. */
  .obra-photo:hover { transform: translateY(-10px); }
  .obra-photo:hover .obra-photo-caption { opacity: 1; }
  .obra-photo:hover + .obra-underline { transform: scaleX(1); }
  .btn-primary:hover {
    background: var(--accent);
    color: var(--text-primary);
  }
  .obra-serie-btn:hover { border-color: var(--accent); }
  .obra-serie-option:hover .obra-serie-option-label::after { transform: scaleX(1); }
  /* Zoom leve al hover — el overflow:hidden de .obra-tile recorta el
     excedente sin mover el layout. */
  .obra-tile:hover img { transform: scale(1.12); }
  .lightbox-close:hover,
  .lightbox-arrow:hover { color: var(--accent); }
  .obra-oi-card:hover .obra-oi-photo-inner img { transform: scale(1.06); }
  .obra-oi-card:hover .obra-oi-caption { opacity: 1; }
  .obra-oi-card:hover .obra-oi-caption-label::after { transform: scaleX(1); }
  .obra-oi-back:hover::after { transform: scaleX(1); }
  .obra-oi-nav-arrow:hover {
    background: var(--accent);
    color: var(--text-primary);
  }
  /* Zoom leve al hover — mismo valor que el resto del sitio (.obra-oi-card).
     El overflow:hidden de .proy-photo recorta el excedente sin mover layout. */
  .proy-tile:hover .proy-photo img { transform: scale(1.06); }
  .proy-tile:hover .proy-caption {
    opacity: 1;
    transform: translateX(0);
  }
  .proy-tile:hover .proy-caption-rule { transform: scaleX(1); }
  .caracol-mas-btn:hover::after { transform: scaleX(1); }
  /* --hover-h la fija el JS por foto (setupHoverHeight): el tope real de
     "despliegue sin recortar los costados" depende de la proporción de cada
     imagen, no es el mismo número para las 2 (ver comentario en el HTML). El
     140px de acá es solo el fallback antes de que el JS mida la imagen. */
  .camp-pegada:hover { height: var(--hover-h, 140px); }
  .tropismes-fr-btn:hover { background: #2a2a2a; }
  .tropismes-strip-wrap:hover .tropismes-strip { animation-play-state: paused; }
  .tropismes-strip-tile:hover img { transform: scale(1.08); }
  .cur-tile:hover img { transform: scale(1.12); }
  .cur-tile:hover .cur-caption { opacity: 1; }
  .cur-tile:hover .cur-caption-label::after { transform: scaleX(1); }
  .site-footer a:hover { color: var(--text-primary); }
  .search-close:hover { color: var(--text-primary); }
  .search-result:hover { background: var(--border); }
}

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

button { font: inherit; border: none; background: none; cursor: pointer; }

/* ---------- Nav ---------- */

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: var(--bg-base);
  border-bottom: 1px solid var(--border);
  padding: 14px var(--space-2xl);
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: var(--nav-height);
  z-index: 100;
  transition: transform 0.35s ease;
  will-change: transform;
}

.nav.nav-hidden {
  transform: translateY(-100%);
}

.nav-left {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  /* Nudge hacia el borde izquierdo (pedido explícito) — el logo y, con él,
     el botón de Mentorías, quedaban centrados de más contra el padding
     estándar de .nav. No se toca ese padding (afectaría también el borde
     derecho del nav) — se corrige acá, solo en este grupo. */
  margin-left: -20px;
}

.logo {
  font-family: var(--font-logo);
  font-size: 26px;
  font-weight: 600;
  letter-spacing: -0.3px;
}

.btn-pill {
  background: var(--text-primary);
  color: #FFFFFF;
  border-radius: 6px;
  padding: 8px 16px;
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 700;
  transition: background-color 0.2s ease, color 0.2s ease;
}

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

.nav-item {
  position: relative;
  display: flex;
  align-items: center;
  padding-bottom: 8px;
  font-family: var(--font-nav);
  font-size: 16px;
  font-weight: 500;
}

.nav-item .underline {
  position: absolute;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  width: 0;
  height: 1px;
  background: var(--accent);
  transition: width 0.25s ease;
}

/* Funciones intercambiadas (spec 09-03, segunda pasada): la negrita marca
   ÚNICAMENTE la sección activa (dónde está el usuario, fija) y el
   subrayado fluor marca el hover (interactividad, temporal) — al revés de
   como estaba antes. Misma lógica de fondo: dos señales distintas que no
   deben pisarse.
   La negrita en sí NO se anima con font-weight (los fonts del sitio no son
   variables, así que el navegador no puede interpolar 500→700, salta seco)
   y además font-weight:700 es más ancho que 500, lo que corría el resto de
   los títulos a la izquierda cada vez que un item pasaba a bold (el bloque
   .nav-items está anclado por la derecha via space-between en .nav, así
   que si crece, todo el bloque se corre). Se resuelve apilando el label 2
   veces en la misma celda de un grid (peso normal + peso 700, uno encima
   del otro): el ancho de la celda siempre lo define la copia más ancha (la
   bold, aunque esté invisible), así que nunca cambia — cero movimiento —
   sea cual sea la sección activa. Ya no hace falta animar el crossfade
   (la sección activa es fija por página, no cambia en vivo) pero se deja
   la transition igual, no estorba. */
.nav-label {
  display: grid;
}

.nav-label-regular,
.nav-label-bold {
  grid-area: 1 / 1;
  transition: opacity 0.2s ease;
}

.nav-label-regular {
  opacity: 1;
}

.nav-label-bold {
  opacity: 0;
  font-weight: 700;
}

.nav-item-active .nav-label-regular {
  opacity: 0;
}

.nav-item-active .nav-label-bold {
  opacity: 1;
}

/* Subrayado fluor en hover. En Curaduría/About/Contacto (sin wrap, sin
   dropdown) esto es pura y llanamente :hover. En Obra/Proyectos en cambio
   se engancha a .is-open — la clase que pone js/nav.js, NO :hover directo
   — para que quede sincronizado 1:1 con qué dropdown está realmente
   abierto (ver el comentario largo más abajo, junto a .nav-dropdown-wrap,
   sobre por qué la apertura dejó de ser :hover puro). */
.nav-dropdown-wrap.is-open > .nav-item .underline {
  width: 34px;
}

.icon-search {
  position: relative;
  top: -5px;
  width: 20px;
  height: 20px;
  padding: 0;
  color: var(--text-primary);
  display: flex;
  align-items: center;
  justify-content: center;
}

.icon-search svg { width: 20px; height: 20px; }

/* Lupa + hamburguesa mobile (afuera de .nav-items — deben verse siempre en
   el header, no solo cuando el menú está desplegado): oculto por defecto,
   .nav-mobile-controls solo se muestra dentro del media query mobile más
   abajo. Vive acá (fuera del media query) para no depender de que ese
   bloque cargue después — visibilidad "off" es el estado seguro por
   defecto en cualquier ancho. */
.nav-mobile-controls { display: none; }

/* Hover "en negrita": el ícono es un solo path relleno (fill), no texto, así
   que no hay font-weight que animar — se engorda con un stroke del mismo
   color por encima del fill (paint-order default: fill primero, stroke
   después, hacia afuera del contorno), de 0 a un ancho visible. Transición
   suave en vez de un salto, mismo criterio "smooth" que el resto del nav. */
.icon-search svg path {
  stroke: currentColor;
  stroke-width: 0;
  transition: stroke-width 0.2s ease;
}
/* Acordeón de subsecciones del nav (Obra/Proyectos — spec en design-system.md
   Sección 7-8; About quedó sin acordeón porque sus 3 subsecciones viven en
   una sola página, alcanza con el subrayado). Panel anclado abajo a la
   derecha del nav — no debajo del ítem puntual que lo dispara, así los 2
   ítems abren el panel siempre en el mismo lugar. Esto funciona porque
   .nav-dropdown-wrap NO es positioned (position:static): el position:absolute
   de .nav-dropdown salta a su ancestro positioned más cercano, que es .nav
   (position:fixed), no el wrap — por eso "right" queda relativo al borde
   derecho del nav entero, igual sea Obra o Proyectos el que lo abre.
   Curaduría/About/Contacto no llevan wrap (sin dropdown, spec: "solo
   subrayan"). El link está centrado en la altura del nav, pero el panel
   arranca en top:100% del nav (su ancestro positioned) — más abajo que el
   borde inferior del propio link, por el padding vertical del nav. Sin
   nada más, ese padding queda fuera del wrap y el mouse "sale" del :hover
   al bajar, cerrando el panel antes de llegar. Se puentea con
   padding-bottom (extiende el área hover del wrap hasta el borde del nav)
   + margin-bottom negativo de igual magnitud (cancela el efecto en el
   layout, no empuja nada) — el wrap queda hovereable sin cortes desde el
   link hasta el panel.
   Apertura vía JS (spec 09-03, tercera pasada — dejó de ser :hover puro):
   js/nav.js agrega/saca la clase .is-open sobre el .nav-dropdown-wrap que
   corresponde. La regla es "solo cambia de dropdown si se hoverean las
   LETRAS del título de otra sección; la zona ampliada (el padding de acá
   abajo) solo abre si no había nada abierto o si ya era esta misma
   sección" — con :hover puro esto es imposible (no hay forma de que una
   regla CSS sepa "qué estaba abierto antes"), así que necesita estado en
   JS. El padding/margin de abajo se mantiene igual (sigue siendo la zona
   que hoverear para que JS decida abrir), pero quién CONTROLA la
   visibilidad ahora es .is-open, no :hover. Ver el detalle completo del
   algoritmo en el comentario de js/nav.js.
   La animación de apertura usa clip-path (no transform/height) para lograr
   un desplegado real hacia
   abajo que se repliega hacia arriba al sacar el mouse, sin distorsionar el
   contenido interno — el inset de izquierda/derecha va siempre en -40px
   (nunca en 0): clip-path recorta TODO lo pintado del elemento, box-shadow
   incluida, así que si el inset llegara a 0 en algún lado la sombra de ese
   lado quedaría invisible (pasó en la pasada 09-03: sombra "notoria" que
   no se veía en el hover real — 0 0 0% 0 recortaba el shadow entero). El
   inset de ARRIBA en cambio se deja fijo en 0 a propósito — el panel
   arranca pegado al nav, no tiene sentido sombra ahí y generaba una línea
   dura justo en esa unión (spec 09-03: "para el contorno superior
   eliminala"). Con -40px fijo a los costados hay margen de sobra para el
   blur y solo el inset de abajo anima (100% cerrado → -40px abierto).
   Zona de hover horizontal ampliada entre Obra y Proyectos (spec 09-03):
   "dividí la zona a la mitad" — cada wrap extiende su hitbox invisible con
   padding + margin negativo de igual magnitud en el lado que corresponde
   (mismo truco que el padding-bottom/margin-bottom de abajo, aplicado en
   horizontal): no mueve nada visualmente porque el padding de más lo
   cancela el margin negativo, solo agranda el área hovereable.
   El hueco Proyectos↔Curaduría lo sostiene enteramente Proyectos, con el
   mismo truco (padding-right/margin-right de 32px). */
.nav-dropdown-wrap {
  display: flex;
  align-items: center;
  padding-bottom: 20px;
  margin-bottom: -20px;
}

/* Obra: dueño de la mitad derecha del hueco Obra↔Proyectos (16px = mitad
   de los 32px de --space-lg entre items). */
.nav-dropdown-wrap:nth-child(1) {
  padding-right: 16px;
  margin-right: -16px;
}

/* Puente extra hacia la izquierda, SOLO con el dropdown de Obra ya abierto
   (pedido explícito) — con los títulos ahora a la izquierda del panel
   (dropdown rotado, spec 09-04) el trayecto diagonal del mouse desde "Obra"
   hacia esos títulos salía de la franja angosta de .nav-dropdown-wrap
   (ancho = solo el texto "Obra") antes de llegar al panel, cerrando el
   dropdown a mitad de camino. Un pseudo-elemento (no padding+margin
   negativo sobre el wrap, que sí es un flex item y correría a los demás
   ítems del nav) resuelve esto sin tocar el layout: al no estar
   posicionado el wrap, escapa a `.nav` igual que `.nav-dropdown` (misma
   ancla, right:0) pero mucho más ancho y alto — cubre de sobra desde la
   altura del propio nav-item hasta bien pasado el panel. Solo existe
   mientras `.is-open` (no toca en absoluto la lógica de "zona ampliada"
   que decide si ABRIR un dropdown, ver js/nav.js) y no tiene fondo ni
   z-index, así que no tapa el hover de Proyectos/Curaduría/etc. — esos
   pintan encima por venir después en el DOM. */
.nav-dropdown-wrap:nth-child(1).is-open::before {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  width: 900px;
  height: 400px;
}

/* Proyectos: dueño de la mitad izquierda del hueco con Obra (16px) y de
   todo el hueco con Curaduría (32px) del otro lado. */
.nav-dropdown-wrap:nth-child(2) {
  padding-left: 16px;
  margin-left: -16px;
  padding-right: 32px;
  margin-right: -32px;
}

.nav-dropdown {
  position: absolute;
  top: 100%;
  right: 0;
  width: 720px;
  padding: var(--space-lg);
  background: var(--bg-base);
  box-shadow: 0 12px 30px -14px rgba(17, 17, 17, 0.3);
  display: flex;
  /* row-reverse (spec 09-04): mismo orden de DOM (foto primero, lista
     después) y mismo JS — solo se invierte el sentido visual para que los
     títulos queden a la izquierda y el thumbnail a la derecha. */
  flex-direction: row-reverse;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-lg);
  opacity: 0;
  pointer-events: none;
  clip-path: inset(0 -40px 100% -40px);
  transition: clip-path 0.35s cubic-bezier(.25,.46,.45,.94), opacity 0.2s ease;
}

.nav-dropdown-wrap.is-open .nav-dropdown {
  opacity: 1;
  pointer-events: auto;
  clip-path: inset(0 -40px -40px -40px);
}

.nav-dropdown-photo {
  position: relative;
  flex: 0 0 250px;
  width: 250px;
  height: 232px;
  overflow: hidden;
  background: var(--bg-base);
}

.nav-dropdown-photo img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.nav-dropdown-photo img.is-active {
  opacity: 1;
}

/* Foto panorámica (5 personas, mucho más ancha que el box 250x232): el
   centrado exacto (50%, default) corta un poco a la persona 1 y a la 5 en
   los bordes. Ajustado a mano para que el recorte excluya A LAS DOS por
   completo (ni un pedazo) y deje a las 3 del medio enteras, con la persona
   del medio (la 3) centrada en el box — spec 09-03, segunda pasada. */
.nav-dropdown-photo img[data-photo="campamento-base"] {
  object-position: 54% center;
}

.nav-dropdown-list {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-sm);
}

.nav-dropdown-list a {
  position: relative;
  padding-bottom: 4px;
  font-family: var(--font-nav);
  font-weight: 500;
  font-size: 17px;
  color: var(--text-primary);
}

/* Ancla a la izquierda (no centrado): crece de izquierda a derecha, en línea
   con el texto left-aligned de la lista (antes right-aligned, spec 09-04:
   dropdown rotado — títulos a la izquierda, thumbnail a la derecha). Más
   gruesa (2px) y más lenta (0.7s) — spec 09-03, tercera pasada. */
.nav-dropdown-list a .underline {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 0;
  height: 2px;
  background: var(--accent);
  transition: width 0.7s ease;
}

/* Negrita = "estás en esta subsección ahora" (.is-current, fijo por
   página, ver build-nav.js) — NO por hover. Subrayado = hover, sin
   importar si es o no la subsección actual. Mismo criterio ya usado en los
   títulos de arriba (activo = negrita fija, hover = subrayado), spec
   09-03 tercera pasada: "en caso de no estar en la subsección, al
   hoverear los títulos del dropdown no deben ponerse en negrita, solo
   subrayarse". */
.nav-dropdown-list a.is-current {
  font-weight: 700;
}

/* ---------- Hero ---------- */

.hero {
  position: relative;
  z-index: 1; /* pinta por encima de lo que sigue mientras el shrink lo tapa */
  width: 100%;
  height: min(640px, calc(100vh - var(--nav-height) - 72px));
  overflow: hidden;
  display: flex;
  align-items: flex-end;
  padding: var(--space-2xl);
}

.hero-bg {
  position: absolute;
  inset: 0;
  background-image: url('../assets/home/hero.jpg');
  background-size: cover;
  background-position: center;
  will-change: filter;
}

.hero-title {
  position: relative;
  margin: 0;
  color: #FFFFFF;
  font-family: var(--font-h1-home);
  font-size: clamp(48px, 8vw, 120px);
  font-weight: 700;
  letter-spacing: -2px;
  line-height: 1;
  /* Iniciales en mayúscula (pedido explícito) — el HTML ya trae el texto
     como "Josefina Fossatti", así que alcanza con no forzar ningún
     text-transform: se muestra tal cual está escrito. */
  /* Empuje sutil hacia abajo dentro de .hero (align-items:flex-end lo pega
     al padding inferior de la sección) — pedido explícito de bajarlo un
     poco más. */
  transform: translateY(20px);
}

/* ---------- Obras Seleccionadas ----------
   Fila única de 4 fotos, mismo tamaño (misma altura fija — NO aspect-ratio:
   el ancho de cada foto depende del ancho del grid, pero la altura no debe
   crecer con él, así que están desacoplados a propósito), sin pin ni
   scroll-hijack (las versiones anteriores — pin+scrub, skewY elástico — se
   descartaron por completo).
   Efecto de scroll actual: "lag" vertical diferencial (inspirado en
   Tympanus: Elastic Grid Scroll, reducido a un desfase de velocidad, sin
   deformar/escalar nada) — ver js/main.js. Se anima ".obra-col" (el
   contenedor, no el <a>) para que no choque con el transform del hover-lift,
   que vive en ".obra-photo" y es puro CSS. */

.obras-destacadas {
  width: 100%;
  /* Vertical = como estaba antes de ensanchar (esos márgenes estaban bien);
     horizontal, más chico a propósito, para que las fotos ganen ancho. */
  padding: var(--space-2xl) var(--space-lg);
  display: flex;
  justify-content: center;
}

.obras-grid {
  width: 100%;
  max-width: 1600px;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--gap-sm);
}

/* Variante de 3 columnas — grid de entrada de About (Sobre Josefina / Sobre
   su obra / Formación de tutoría), misma .obras-grid (tamaño, gap, hover,
   márgenes) que Obras Seleccionadas de home, solo cambia la cantidad de
   columnas. (09-01 probó un efecto escalonado acá vía margin-top por
   columna — se descartó el 09-02, "volvé a alinear las 3 fotos": las 3
   quedan a la misma altura, sin offset.) */
.obras-grid--3 {
  grid-template-columns: repeat(3, 1fr);
}

.obra-col {
  min-width: 0;
}

.obra-photo {
  position: relative;
  display: block;
  /* Altura fija e independiente del ancho de columna: al ensanchar el grid
     las fotos ganan ancho (revelan más de los márgenes laterales de la foto
     original) sin volverse más altas. */
  height: clamp(150px, 26.5vw, 382px);
  overflow: hidden;
  cursor: pointer;
  transition: transform 0.4s cubic-bezier(.22, 1, .36, 1);
}

.obra-photo-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* Solo la 1ra foto del grid de About (sobrejosefina.jpg) — spec 09-01: "que
   se vea más abajo de la foto original", corre el recorte de cover hacia
   abajo en vez del centro por defecto. */
.obra-photo-img--pos-bottom {
  object-position: center bottom;
}

/* Título de la subsección superpuesto, oculto hasta el hover — hoy solo lo
   usa el grid de entrada de About. Arrancó como copia exacta de
   .cur-caption/.cur-caption-label de Curaduría (caption abajo-izquierda
   con degradé, label itálica) y después se diferenció (spec 09-02: "letra
   más grande, sin subrayado") — nombres de clase propios porque conviven
   con el resto del sistema de hover de .obra-photo (lift + línea centrada
   abajo), que Curaduría no tiene. pointer-events:none para no interferir
   con el click en <a>. */
.obra-photo-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: var(--space-md) var(--space-xs) var(--space-xs);
  background: linear-gradient(to top, rgba(17, 17, 17, 0.7), rgba(17, 17, 17, 0));
  opacity: 0;
  transition: opacity 0.25s ease;
  pointer-events: none;
}

.obra-photo-title {
  display: inline-block;
  font-family: var(--font-hover-title);
  font-weight: 600;
  font-size: 20px;
  color: #fff;
}

/* Línea fluor debajo de la foto al hover — se traza del centro hacia los
   costados (scaleX + transform-origin:center), no de punta a punta: marca
   la foto sin copiar el ancho completo. Vive en .obra-col (no dentro de
   .obra-photo, que tiene overflow:hidden) para no quedar recortada por el
   propio hover-lift que sube la foto 10px. */
.obra-underline {
  display: block;
  width: 44%;
  height: 2px;
  margin: 14px auto 0;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: center;
  transition: transform 0.35s ease;
}

.section-spacer {
  width: 100%;
  height: var(--space-xl);
}

/* El espaciador justo antes del CTA de mentorías necesita el mismo margen
   que separa el CTA de las imágenes en Pintura/Objeto Instalación (96px,
   el padding-bottom de .obra-page) — antes tenía el --space-xl genérico
   (48px) y el CTA quedaba visiblemente más pegado al contenido en Home que
   en el resto del sitio. */
.section-spacer-cta {
  height: var(--space-3xl);
}

/* ---------- Bio Breve ---------- */

.bio-breve {
  position: relative;
  width: 100%;
  height: 620px;
  background-image: url('../assets/home/fondo-seccion-bio.jpg');
  background-size: cover;
  background-position: center;
  display: flex;
  align-items: flex-end;
  justify-content: flex-end;
  padding: var(--space-2xl);
}

.bio-text {
  width: 480px;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--gap-sm);
  text-align: right;
}

.bio-text h2 {
  margin: 0;
  font-family: var(--font-home-h2);
  font-size: 32px;
  font-weight: 600;
  letter-spacing: 0.5px;
}

.bio-text p {
  margin: 0;
  font-family: var(--font-home-text);
  font-weight: 500;
  font-size: 15px;
  line-height: 1.5;
}

/* ---------- Mentoría CTA ---------- */

.mentoria-cta {
  position: relative;
  width: 100%;
  height: 170px;
  overflow: hidden;
  display: flex;
  align-items: flex-end;
  justify-content: flex-end;
  padding: var(--space-md) var(--space-2xl);
  gap: var(--gap-md);
}

.cta-bg {
  position: absolute;
  inset: 0;
  background-image: url('../assets/home/cta-mentorias-crop-v2.jpg');
  background-size: cover;
  background-position: center;
}

.cta-scrim {
  position: absolute;
  inset: 0;
  background: rgba(255, 255, 255, 0.4);
}

.cta-content {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--gap-sm);
}

.cta-text {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--space-xs);
  text-align: right;
}

.cta-text h2 {
  margin: 0;
  /* Pedido explícito (spec 09-04, quinta pasada): "pasá todas las
     tipografías de Home a Helvética, incluyendo el CTA de mentorías" — deja
     de ser la excepción en Helvetica Neue (stack de sistema, solo real en
     Mac) y pasa a Switzer, la misma familia del resto de Home. */
  font-family: var(--font-home-h2);
  font-size: 20px;
  font-weight: 700;
}

.cta-text p {
  margin: 0;
  font-family: var(--font-home-text);
  font-weight: 500;
  font-size: 13px;
}

.btn-primary {
  display: inline-block;
  background: var(--text-primary);
  color: #FFFFFF;
  padding: 10px 22px;
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 700;
  transition: background-color 0.2s ease, color 0.2s ease;
}

/* ---------- Obra (Pintura) ----------
   Columnas propias en JS (no CSS `columns`) para poder aplicarle a cada
   columna el mismo "lag" diferencial de velocidad de scroll ya usado en
   Obras Seleccionadas de Home (columnas centrales reaccionan rápido,
   exteriores se quedan atrás) — con `columns` de CSS no hay un nodo por
   columna al que animarle un transform. Todas las fotos con igual tamaño
   (aspect-ratio fijo + object-fit:cover), como en el diseño de Pencil
   (`fill: mode "fill"`): al repartir las fotos round-robin entre N columnas
   de igual ancho y alto de tile, el resultado visual es una cuadrícula
   simétrica de N por fila, no un masonry. Ver js/obra.js. */

.obra-page {
  width: 100%;
  max-width: 1600px;
  margin: 0 auto;
  /* Márgenes tal como están definidos en el diseño de Pencil: top 32,
     laterales 64, bottom 96. */
  padding: var(--space-lg) var(--space-2xl) var(--space-3xl);
}

.obra-header {
  display: flex;
  align-items: center;
  gap: var(--gap-md);
}

.obra-page-title {
  margin: 0;
  font-family: var(--font-h1-inner);
  font-size: 40px;
  font-weight: 700;
}

/* Excepción explícita del usuario (spec 09-04): el título de Contacto se
   queda con la tipografía anterior de los H1 interiores, no pasa a
   Red Hat Display — "formulario de contacto: mantené la versión anterior,
   incluso el título, todo". */
.contact-page .obra-page-title {
  font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

.obra-serie-selector {
  position: relative;
}

.obra-serie-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 18px;
  border: 1px solid var(--text-primary);
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 700;
  transition: border-color 0.2s ease, color 0.2s ease;
}

.obra-serie-selector.is-open .obra-serie-btn {
  border-color: var(--accent);
}

.obra-serie-selector.is-open .obra-serie-btn svg {
  transform: rotate(180deg);
}

.obra-serie-btn svg { transition: transform 0.2s ease; }

.obra-serie-dropdown {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 10;
  min-width: 180px;
  background: var(--bg-pure);
  border: 1px solid var(--border);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-8px);
  transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s;
}

.obra-serie-selector.is-open .obra-serie-dropdown {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.obra-serie-option {
  display: block;
  width: 100%;
  padding: 12px 18px;
  text-align: left;
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--text-primary);
}

.obra-serie-option.is-active { font-weight: 700; }

/* Hover = subrayado fluor de izquierda a derecha (no negrita — eso queda
   reservado para marcar la serie activa). El ::after vive en el span de
   texto (inline-block, ancho = ancho real del título), no en el botón
   entero (que ocupa todo el acordeón), para que el trazo mida justo lo que
   mide "Situaciones" / "Insistencia". */
.obra-serie-option-label {
  position: relative;
  display: inline-block;
}

.obra-serie-option-label::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -4px;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.3s ease;
}

.obra-intro {
  /* Llega hasta el borde derecho de la página, mismo margen que a la
     izquierda (sin max-width angosto). */
  margin: var(--space-lg) 0 0;
  font-family: var(--font-text);
  font-weight: 500;
  font-size: 15px;
  line-height: 1.6;
  color: var(--text-primary);
}

.obra-grid {
  display: flex;
  gap: var(--space-xs);
  margin-top: var(--space-2xl);
}

.obra-grid[hidden] { display: none; }

.obra-grid-col {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  min-width: 0;
}

/* Todas las fotos con el mismo tamaño (cuadrícula simétrica, 5 por fila en
   desktop): relación de aspecto fija + recorte centrado, en vez de masonry
   con alto natural. Coincide con el crop `mode:"fill"` ya usado en el diseño
   de Pencil para este grid. */
.obra-tile {
  display: block;
  position: relative;
  width: 100%;
  aspect-ratio: 5 / 4;
  overflow: hidden;
  cursor: pointer;
  background: var(--bg-pure);
}

.obra-tile img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  transition: transform 0.5s cubic-bezier(.22, 1, .36, 1);
}

/* ---------- Lightbox ---------- */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(17, 17, 17, 0.92);
}

.lightbox[hidden] { display: none; }

.lightbox-img {
  max-width: 88vw;
  max-height: 88vh;
  object-fit: contain;
}

.lightbox-close,
.lightbox-arrow {
  position: absolute;
  color: #FFFFFF;
  font-size: 28px;
  line-height: 1;
  padding: 12px;
  cursor: pointer;
}

.lightbox-close { top: var(--space-md); right: var(--space-md); }
.lightbox-prev { left: var(--space-md); top: 50%; transform: translateY(-50%); font-size: 72px; }
.lightbox-next { right: var(--space-md); top: 50%; transform: translateY(-50%); font-size: 72px; }

/* ---------- Obra (Objeto Instalación) ----------
   Reutiliza .obra-page/.obra-header/.obra-page-title/.obra-serie-* de Pintura
   (mismo patrón exacto de título+dropdown "Serie", confirmado en el diseño).
   Galería horizontal: réplica del mecanismo de
   tympanus.net/Tutorials/HorizontalParallaxGallery (versión 2D/DOM) — el
   scroll/wheel mueve el track horizontalmente con inercia (lerp), y cada
   foto tiene un "buffer" interno (125% ancho, -12.5% de margen) sobre el que
   se le aplica un contra-desplazamiento según su distancia al centro del
   viewport, dando la sensación de parallax. No es autoplay: se mueve solo
   cuando el usuario scrollea. Full-bleed: ocupa el ancho completo de la
   pantalla, no el de .obra-page (ver el "breakout" de abajo). */

.obra-oi-gallery-viewport {
  width: 100vw;
  position: relative;
  left: 50%;
  right: 50%;
  margin-left: -50vw;
  margin-right: -50vw;
  overflow: hidden;
  margin-top: var(--space-2xl);
  /* pan-y (no "none"): el drag horizontal ya lo maneja a mano el JS
     (pointerdown/move, ver oiOnPointerDown) — dejarle pan-y al navegador
     evita que compita por el gesto (algunos navegadores intentan su propio
     manejo de swipe horizontal, p.ej. "volver atrás" con edge-swipe) sin
     bloquear el scroll vertical normal de la página, que sigue pasando de
     largo sobre la galería. */
  touch-action: pan-y;
}

.obra-oi-gallery-track {
  display: flex;
  gap: var(--space-sm);
  width: max-content;
  will-change: transform;
}

.obra-oi-card {
  flex: 0 0 360px;
  width: 360px;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  cursor: pointer;
}

.obra-oi-photo {
  width: 360px;
  height: 360px;
  overflow: hidden;
  position: relative;
  background: var(--bg-pure);
}

/* Autorretratos: fotos más grandes que el resto de las series, calculadas
   para que entren 3 fotos y media en el ancho de pantalla (3.5 tarjetas +
   3 gaps completos = 100vw) — proporcional, se agranda ancho y alto por
   igual para mantener el cuadrado. Compuestos/Señalamientos se quedan en
   los 360px de siempre. Al depender de 100vw, escala solo con el ancho de
   pantalla real — por especificidad (2 clases) gana incluso dentro del
   media query mobile, así que el "3.5 en el ancho" se mantiene también ahí,
   sin necesidad de repetirlo. */
.obra-oi-gallery-track.is-serie-autorretratos .obra-oi-card {
  flex: 0 0 calc((100vw - 3 * var(--space-sm)) / 3.5);
  width: calc((100vw - 3 * var(--space-sm)) / 3.5);
}

.obra-oi-gallery-track.is-serie-autorretratos .obra-oi-photo {
  width: calc((100vw - 3 * var(--space-sm)) / 3.5);
  height: calc((100vw - 3 * var(--space-sm)) / 3.5);
}

/* Exposiciones: tarjetas más anchas que el cuadrado de 360px de siempre
   (mismo tamaño para las 5, "equitativamente" — no varía por foto) — 500px
   de ancho, alto sin cambios, para las fotos de sala/instalación, que en la
   vida real son panorámicas y se leen mejor en un rectángulo horizontal. */
.page-exposiciones .obra-oi-card {
  flex: 0 0 500px;
  width: 500px;
}

.page-exposiciones .obra-oi-photo {
  width: 500px;
}

/* Buffer de parallax: 150% de ancho / -25% de margen para tener recorrido
   hacia ambos lados sin dejar huecos — el JS anima su transform según la
   posición de la tarjeta respecto al centro del viewport. Mismo buffer y
   mismo multiplicador de xPercent (16.67, ver oiTick) en las 3 series de
   Objeto Instalación y en Exposiciones — se igualó acá (antes Objeto
   Instalación tenía la mitad de recorrido, 125%/-12.5%, a pedido de Blas). */
.obra-oi-photo-inner {
  position: absolute;
  top: 0;
  left: -25%;
  width: 150%;
  height: 100%;
  will-change: transform;
}

.obra-oi-photo-inner img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.5s cubic-bezier(.22, 1, .36, 1);
}

/* La leyenda ocupa siempre su alto (no hace layout shift) pero solo se hace
   visible al hover; el subrayado fluor vive en el span interno para que
   mida el ancho del texto, no el de toda la tarjeta (mismo patrón que el
   dropdown "Serie"). */
.obra-oi-caption {
  display: block;
  /* 24px (antes 18px, fijado cuando el label era 11px) — a 14px con Rowan
     600 la caja anterior quedaba más baja que el alto real de línea, y el
     descendente de letras como "g"/"y" (ver "Testigo") se recortaba contra
     el borde inferior. */
  height: 24px;
  opacity: 0;
  transition: opacity 0.25s ease;
}

.obra-oi-caption-label {
  position: relative;
  display: inline-block;
  /* Letra más suave, sin negrita (pedido explícito) — desacoplada de
     --font-hover-title (Rowan, que siguen usando About y Curaduría). */
  font-family: var(--font-hover-oi);
  font-weight: 400;
  font-size: 14px;
  color: var(--text-primary);
}

.obra-oi-caption-label::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  /* -6px (antes -3px): a 14px, el descendente de letras como "g" ("Testigo")
     llegaba a tocar la línea fluor y se leía como si la cortara — más aire
     entre el texto y la línea. */
  bottom: -6px;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.3s ease;
}

/* ---------- Obra Instalación — vista de detalle (las 3 series) ----------
   Mismo template para Autorretratos, Compuestos y Señalamientos: si la obra
   tiene texto, foto+epígrafe a la izquierda y título+texto+miniaturas a la
   derecha (.is-with-text); si no tiene texto, la foto sola centrada en la
   página (.is-textless). Las obras con más de una foto (CDF) muestran
   flechas a los costados de la foto principal en cualquiera de los dos
   casos — ver .obra-oi-nav-arrow. */

.obra-oi-detail {
  padding-top: var(--space-lg);
}

.obra-oi-toolbar {
  margin-bottom: var(--space-lg);
}

.obra-oi-back {
  position: relative;
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 700;
  color: var(--text-primary);
  padding-bottom: 4px;
}

.obra-oi-back::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.25s ease;
}

/* flex-start (no el stretch por defecto): cada columna toma su propio alto
   natural — necesario para que la caja de la foto (ver fitOiPhotoBox en el
   JS) pueda achicarse en fotos panorámicas sin que la columna de texto la
   "estire" de vuelta a los 480px de siempre. */
.obra-oi-detail-inner {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2xl);
}

/* Cuarto Intermedio (Exposiciones): 2 filas apiladas en vez del template
   estándar de una sola foto+texto — cada fila reutiliza literalmente
   .obra-oi-detail-inner (foto izquierda, texto derecha); la segunda
   (.is-reverse) invierte el orden visual (row-reverse) y alinea su texto
   a la derecha, sin duplicar ningún estilo. */
.obra-oi-cuarto-intermedio {
  display: flex;
  flex-direction: column;
  /* Más chico que el space-3xl de siempre entre secciones — a propósito:
     con las 2 filas apiladas, si el espacio entre ellas es muy generoso la
     segunda fila queda debajo del pliegue en pantallas de alto normal y no
     se entiende que hay que scrollear. Ver también FILA_MAX_HEIGHT en el JS
     (el alto de foto de cada fila también se achicó por la misma razón). */
  gap: var(--space-lg);
}

.obra-oi-fila.is-reverse {
  flex-direction: row-reverse;
}

.obra-oi-fila.is-reverse .obra-oi-detail-text {
  text-align: right;
}

/* Alto máximo de foto más chico que el estándar (480px) — mismo valor que
   FILA_MAX_HEIGHT en obra-exposiciones.js, este es solo el fallback antes
   de que el JS mida y ajuste. */
.obra-oi-fila .obra-oi-detail-photo {
  height: 320px;
}

.obra-oi-detail-left {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* La "caja" de la foto queda SIEMPRE en el flujo normal — es el <img>/
   <video> de adentro el que se vuelve position:fixed durante la transición
   FLIP, nunca esta caja. Así el epígrafe (y en Compuestos/Señalamientos, el
   resto de la página) no se corre de lugar mientras dura la animación —
   antes la caja entera se sacaba del flujo y todo lo de abajo saltaba
   arriba por un instante.
   480px es el alto MÁXIMO, no uno fijo: en fotos con "contain" (todo salvo
   Autorretratos) el JS (fitOiPhotoBox) lo achica al alto real que ocupa la
   imagen dentro de la caja según su proporción — sin esto, una foto
   panorámica (p. ej. Señalamientos III/V/VI) queda con un enorme margen
   transparente abajo, adentro de la caja, antes de llegar al texto/CTA. En
   Autorretratos (is-cover) siempre se queda en los 480px: "cover" llena la
   caja completa recortando, nunca deja espacio de sobra. */
.obra-oi-detail-photo {
  width: 100%;
  height: 480px;
  overflow: hidden;
  position: relative;
  /* Transparente a propósito (no --bg-pure): con fondo blanco se veía un
     "marco" blanco alrededor de la foto mientras el FLIP todavía la está
     agrandando desde la miniatura — al ser transparente, en ese instante se
     ve el fondo de la página (--bg-base) en vez de un blanco que contrasta
     y se nota como un recuadro aparte. */
  background: transparent;
}

/* El target de FLIP es siempre el hijo directo de .obra-oi-detail-photo: el
   <img> cuando es una foto simple, o .obra-oi-media-wrap cuando es video
   (para que video+portada viajen pegados como una sola unidad durante la
   animación — si el target fuera solo el <video>, la portada quedaría fija
   en su tamaño final mientras el video vuela desde la miniatura).
   object-fit:contain por defecto (foto entera, sin recortar; como la caja
   es transparente, el margen que deja adentro se ve como el fondo de la
   página) — Autorretratos vuelve a "cover" vía .is-cover, como estaba antes
   de la corrección que llevó todo a "contain". */
.obra-oi-detail-photo > img,
.obra-oi-media-wrap {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  /* Volver a clickear la foto la abre en grande (ver openOiZoom en el JS). */
  cursor: zoom-in;
}

.obra-oi-media-wrap video,
.obra-oi-media-wrap img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.obra-oi-detail-photo.is-cover > img,
.obra-oi-detail-photo.is-cover .obra-oi-media-wrap,
.obra-oi-detail-photo.is-cover .obra-oi-media-wrap video,
.obra-oi-detail-photo.is-cover .obra-oi-media-wrap img {
  object-fit: cover;
}

/* Señalamientos III y V: la foto (contain) se alinea arriba en vez de
   centrarse verticalmente en la caja de 480px, para que su borde superior
   coincida con el del bloque de texto de al lado. */
.obra-oi-detail-photo.is-top-aligned > img {
  object-position: top;
}

/* Portada superpuesta al video de Remanencia hasta que arranca la
   reproducción real — sin esto se ve un frame negro ~1s mientras decodifica
   (ver openDetail/buildPhotoMedia en el JS). */
.obra-oi-video-poster {
  transition: opacity 0.3s ease;
  pointer-events: none;
}

.obra-oi-epigrafe p {
  margin: 0 0 var(--space-xs);
  font-family: var(--font-accent);
  font-weight: 300;
  font-style: normal;
  font-size: 14px;
  line-height: 1.5;
  color: var(--text-secondary);
}

.obra-oi-epigrafe p:last-child { margin-bottom: 0; }

/* Alto natural (ya no fijo a 480px): antes quedaba siempre a la altura de
   la foto aunque el texto fuera corto, y con fotos panorámicas (que
   "fitOiPhotoBox" achica) terminaba siendo la columna más alta de las dos
   y el CTA no se acercaba nunca, aunque la foto ocupara mucho menos lugar. */
.obra-oi-detail-right {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

.obra-oi-detail-text h2 {
  position: relative;
  display: inline-block;
  margin: 0 0 var(--space-sm);
  font-family: var(--font-h2);
  font-size: 28px;
  font-weight: 600;
}

/* Subrayado que se traza de izquierda a derecha al abrir la obra (pedido
   explícito) — span real, no ::after, porque GSAP necesita un nodo del DOM
   para animar transform de forma confiable. Arranca en scaleX(0) acá (en
   vez de depender de que el JS lo setee antes del primer paint) para que
   nunca se vea "saltar" de golpeado a lleno si algo tarda un frame de más;
   el JS de cada página (openDetail/openDetailStatic) lo anima a scaleX(1)
   lentamente. */
.obra-oi-detail-underline {
  position: absolute;
  left: 0;
  bottom: -4px;
  width: 100%;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
}

/* Excepción puntual (pedido explícito): en "Hay un punto exacto" (Cuarto
   Intermedio) el trazo va de derecha a izquierda. Mismo scaleX 0→1 por
   GSAP, solo cambia el origen de la transformación. */
.obra-oi-detail-underline--rtl {
  transform-origin: right;
}

.obra-oi-texto p {
  margin: 0 0 var(--space-sm);
  font-family: var(--font-text);
  font-weight: 500;
  font-size: 15px;
  line-height: 1.6;
  color: var(--text-primary);
}

.obra-oi-texto p:last-child { margin-bottom: 0; }

/* Obras sin texto propio: la foto sola, centrada en la página (sin columna
   derecha ni epígrafe). */
.obra-oi-detail-inner.is-textless {
  display: flex;
  justify-content: center;
}

.obra-oi-detail-inner.is-textless .obra-oi-detail-left {
  width: 100%;
  max-width: 720px;
}

/* Flechas a los costados de la foto principal para las obras con más de una
   foto (CDF) — mismo lugar tanto si la foto está centrada (sin texto) como
   si está en la columna izquierda (con texto). Fondo circular translúcido
   para que se lean sobre cualquier foto, oscura o clara. */
.obra-oi-nav-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(17, 17, 17, 0.35);
  color: #FFFFFF;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  z-index: 2;
  transition: background-color 0.2s ease, color 0.2s ease;
}

.obra-oi-nav-prev { left: var(--space-sm); }
.obra-oi-nav-next { right: var(--space-sm); }

/* ---------- Proyectos (Lamarencoche) ----------
   Reutiliza .obra-page/.obra-header/.obra-page-title/.obra-intro de Pintura
   (la navegación cruzada entre subsecciones vive en el acordeón del nav).
   Grid índice
   fijo de 4 fotos (no infinite scroll — se probó y se descartó, ver
   decisions/log.md 2026-08-31), 2 filas de 2, centrado: cada tarjeta cruza
   un modificador de tamaño (--sq 316×300 recortada / --wide 400×300 sin
   recortar, según el ancho máximo que permite la proporción real de la foto)
   con uno de posición (--left / --right) que define hacia qué lado se
   despliega el título al hover. */

.proy-gallery-wrap {
  display: flex;
  justify-content: center;
  margin-top: var(--space-2xl);
}

.proy-grid {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.proy-fila {
  display: flex;
  gap: var(--space-sm);
}

.proy-tile {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  cursor: pointer;
}

.proy-tile--sq .proy-photo,
.proy-tile--sq img { width: 316px; }

.proy-tile--wide .proy-photo,
.proy-tile--wide img { width: 400px; }

.proy-tile { position: relative; }

.proy-photo {
  display: block;
  position: relative;
  height: 300px;
  overflow: hidden;
}

.proy-tile--sq img { object-fit: cover; }
.proy-tile--wide img { object-fit: contain; }

.proy-photo img {
  display: block;
  width: 100%;
  height: 100%;
  transition: transform 0.5s cubic-bezier(.22, 1, .36, 1);
}

/* Título oculto en reposo — se despliega en el margen, al costado de la
   foto (no debajo), alineado con su borde superior: tarjetas de la
   izquierda lo abren hacia la izquierda de la foto, tarjetas de la derecha
   hacia la derecha. Ancho fijo (no nowrap) para que títulos largos envuelvan
   en 2 líneas en vez de desbordar hacia afuera del margen disponible.
   position:absolute para no empujar el layout del grid mientras está oculto
   — por eso no hace falta reservar alto como en .obra-oi-caption. */
.proy-caption {
  position: absolute;
  top: 0;
  width: 190px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease, transform 0.35s cubic-bezier(.22, 1, .36, 1);
}

.proy-tile--left .proy-caption { right: 100%; margin-right: var(--space-md); transform: translateX(14px); }
.proy-tile--right .proy-caption { left: 100%; margin-left: var(--space-md); transform: translateX(-14px); }

.proy-caption-label {
  display: block;
  /* Antes seguía a H2/H3 (spec 09-04, cuarta pasada). Al pasar H2/H3 a
     Switzer (sexta pasada), se desacopla de nuevo a su propio token
     (--font-hover-lamarencoche, Literata) porque "hover sobre imágenes" es
     uno de los grupos que Blas pidió dejar sin cambios — sin este
     desacople, hubiera heredado Switzer sin que se pidiera. */
  font-family: var(--font-hover-lamarencoche);
  font-weight: 600;
  font-style: normal;
  font-size: 26px;
  line-height: 1.15;
  color: var(--text-primary);
}

.proy-tile--left .proy-caption-label { text-align: right; }
.proy-tile--right .proy-caption-label { text-align: left; }

/* El subrayado se traza desde el extremo pegado a la foto hacia el margen
   exterior, acompañando la salida del título: en las tarjetas de la
   izquierda el origen del trazo es el borde derecho (crece hacia la
   izquierda); en las de la derecha, el borde izquierdo (crece hacia la
   derecha). Ya no vive como ::after del label (el título ahora puede
   envolver en 2 líneas): es su propio bloque debajo del texto. */
.proy-caption-rule {
  display: block;
  width: 100%;
  height: 2px;
  margin-top: 8px;
  background: var(--accent);
  transform: scaleX(0);
  /* Trazo más lento que el resto de los subrayados del sitio (0.3s ahí) —
     pedido puntual de Blas para el hover del grid índice. */
  transition: transform 0.7s ease;
}

.proy-tile--left .proy-caption-rule { transform-origin: right; }
.proy-tile--right .proy-caption-rule { transform-origin: left; }

/* ---------- Proyectos — Lamarencoche — La estrategia del caracol ----------
   Layout especial (última estructura pedida, ajustada 08-22 — spec 08-20
   quedó descartada: varios párrafos por foto en vez de 1, para evitar
   espacio en blanco con un texto tan extenso): fila 1 = párrafos 1-3
   (alineados a la derecha, hacia la foto) + foto a la derecha (`-01`); fila
   2 = foto a la izquierda (`-02`) + párrafos 4-6 (alineados a la izquierda,
   hacia la foto) + botón "Ver más". El botón despliega en acordeón el resto
   del texto completo (párrafo 7 en adelante + cita + cierre — confirmado
   08-17/08-21, no se muestra directo en la página). Fotos sin recorte: la
   caja usa la proporción real 4:3 de ambas fotos (720×540 y 960×720), así
   que object-fit:contain no genera letterboxing.
   align-items:flex-start en las 2 filas (no flex-end en la reversa): pedido
   explícito — el tope del primer párrafo debe alinear con el tope de la
   foto de su fila. Como el alto de la foto siempre iguala el alto exacto
   del texto (ver JS), alinear arriba o abajo da el mismo resultado en la
   base (ambos bordes coinciden a la vez) — flex-start es además más robusto
   si alguna vez la foto queda un poco más baja que el texto, porque el
   desajuste quedaría abajo, no arriba.
   gap fijo (--gap-md, 32px — mismo espaciado usado en el resto de la
   página, corregido 2026-08-31): el texto vuelve a flex-grow:1 (ocupa
   exactamente "lo que sobra" después del ancho de la foto), ya NO
   flex:0 0 50% + justify-content:space-between — ese approach evitaba la
   divergencia pero el hueco entre foto y texto dejaba de ser el gap
   declarado cada vez que el ancho de la foto no coincidía con el 50%
   exacto (medido: 32px en la fila 1 pero 70px en la fila 2, con el mismo
   texto de 3 párrafos). Con flex-grow:1 el gap es SIEMPRE exactamente
   `gap` sin importar cuánto mida la foto — la estabilidad numérica se
   resuelve en el JS (ver fitCaracolRow), no en el layout. */
.caracol-row {
  display: flex;
  align-items: flex-start;
  gap: var(--gap-md);
  margin-top: var(--space-2xl);
}

.caracol-row.is-reverse {
  flex-direction: row-reverse;
}

.caracol-text {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  font-family: var(--font-text);
  font-weight: 500;
  font-size: 15px;
  line-height: 1.6;
  color: var(--text-primary);
  text-align: right;
}

.caracol-row.is-reverse .caracol-text { text-align: left; }

.caracol-text p { margin: 0; }

/* flex:0 0 auto: el ancho y el alto los fija el JS (fitCaracolRow),
   igualando la altura de la foto a la del texto de su misma fila y
   calculando el ancho a partir de la proporción real de la imagen — así no
   queda hueco en blanco cuando el texto es más alto que un box de aspect-
   ratio fijo, ni se recorta la foto (mismo criterio que fitFilaPhoto en
   Cuarto Intermedio de Exposiciones). aspect-ratio:4/3 queda como valor de
   arranque antes de que el JS corra. max-width es un techo de seguridad
   generoso (no el 50% ajustado de antes, ya no hace falta: el JS converge
   con damping) para que un texto extremo no le pida a la foto más ancho del
   que puede haber en la fila. */
.caracol-photo {
  flex: 0 0 auto;
  max-width: 75%;
  aspect-ratio: 4 / 3;
  overflow: hidden;
}

.caracol-photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Flecha hacia abajo, alineada a la derecha de la columna de texto —
   independiente del text-align de los párrafos (align-self, no text-align):
   con la fila 2 en alignItems:end, queda apoyado en la base de la foto. */
.caracol-mas-btn {
  align-self: flex-end;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  position: relative;
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 700;
  color: var(--text-primary);
  padding-bottom: 4px;
}

.caracol-mas-btn svg { transition: transform 0.3s ease; }
.caracol-mas-btn.is-open svg { transform: rotate(180deg); }

.caracol-mas-btn::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.25s ease;
}

/* max-height con un techo generoso (nunca se llega a medir el contenido
   real) en vez de height:auto — permite transición suave sin JS de medición;
   una vez el contenido real queda expuesto, seguir animando hacia el techo
   no tiene efecto visual. */
.caracol-full-text {
  overflow: hidden;
  max-height: 0;
  transition: max-height 0.5s ease;
}

.caracol-full-text.is-open { max-height: 6000px; }

.caracol-full-text-inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  /* Mismo espaciado que el gap foto-texto de las filas de arriba (--gap-md,
     pedido explícito 2026-08-31: "aplicá ese mismo espaciado" al margen
     entre la foto de la fila 2 y este texto). */
  padding-top: var(--gap-md);
  font-family: var(--font-text);
  font-weight: 500;
  font-size: 15px;
  line-height: 1.6;
  color: var(--text-primary);
}

.caracol-full-text-inner p { margin: 0; }

.caracol-quote {
  margin: 0;
  padding-left: var(--space-md);
  border-left: 2px solid var(--accent);
}

.caracol-quote p {
  font-family: var(--font-accent);
  font-weight: 300;
  font-style: normal;
  font-size: 19px;
  margin: 0 0 var(--space-xs);
}

.caracol-quote-author {
  font-family: var(--font-accent);
  font-weight: 300;
  font-style: normal;
  font-size: 13px;
  color: var(--text-secondary);
}

.caracol-nota {
  font-family: var(--font-accent);
  font-weight: 300;
  font-size: 13px;
  color: var(--text-secondary);
}

/* ---------- Proyectos — Campamento Base ----------
   Página bio del colectivo, no galería: foto grupal + 2 fotos "pegadas"
   (campamentobase/campabase, apiladas) + CTA a linktr.ee en la columna
   izquierda, epígrafe + texto corrido en la derecha.
   Corregido 2026-08-31 (tercera vuelta) tras feedback puntual de Blas:
   - El contorno fluor de las 2 fotos ya no se duplica en la unión — cada
     foto dibuja su propio trazo vía JS (ver el <script> al final del
     HTML), pero solo la de ARRIBA dibuja la arista compartida (el "seam");
     la de abajo la deja abierta, así el trazo mide 1 línea de ancho ahí,
     no 2 apiladas.
   - Solo la foto de ABAJO (campabase) tiene esquinas inferiores
     redondeadas; la de arriba (campamentobase) es un rectángulo recto.
   - El hover ya no hace zoom: la foto hovereada "se despliega" hacia
     abajo (crece de alto con una transición suave), revelando más de la
     imagen original sin recortarla más de lo necesario — el trazo y los
     2 piquitos se re-sincronizan en cada frame vía JS mientras dura la
     transición, para que sigan pegados al borde real en todo momento.
   - Los 2 piquitos pasan a ser triángulos simples (antes una forma tipo
     bandera/pestaña con 5 vértices).
   - El trazo del botón "Ver más" (disparado al hoverear cualquiera de las
     2 fotos) pasa a ser una línea fina.
   Corregido 2026-08-31 (cuarta vuelta), feedback de precisión:
   - Piquitos ya no translúcidos (opacidad 1, antes 0.55).
   - Trazo del botón un poco más grueso (3px, antes 2px); se dibuja
     progresivo al entrar y se retrae al salir (GSAP + pathLength=1, ver
     HTML) — ya no se recalcula en cada frame del rAF (el botón no cambia
     de tamaño salvo resize/fuente web), lo que además saca de en medio
     cualquier interferencia con la animación de dashoffset.
   - Bug real de precisión encontrado y corregido: el JS medía las cajas
     con offsetWidth/clientWidth (redondean a entero) y usaba eso como
     viewBox del SVG — con cajas de tamaño fraccionario (p. ej. el botón,
     130.32×46.4px reales) el viewBox quedaba un pelo más chico que la
     caja real, y el navegador estiraba el trazo ~0.2-0.3% para llenarla:
     esa mínima diferencia de escala era la línea ínfima de imagen/botón
     que se veía asomar por fuera del contorno. Se mide con
     getBoundingClientRect (subpíxel exacto) en vez de offsetWidth/
     clientWidth en todos lados.
   - El alto de "despliegue" al hoverear ya no es un mismo número fijo
     para las 2 fotos: por encima de cierto alto (que depende de la
     proporción real de cada imagen), object-fit:cover deja de revelar
     más arriba/abajo y empieza a recortar los costados — eso es lo que
     hacía ver "zoomeada" a `campabase.jpg` (panorámico 2084×411) al
     desplegarse hasta los mismos 140px que sí le quedan cómodos a
     `campamentobase.jpg` (1200×675). El JS calcula ese tope por imagen
     real (`boxWidth / aspectRatioNatural`, con 140px de techo) y lo fija
     como variable CSS `--hover-h` que usa el `:hover` — misma mecánica de
     despliegue en las 2 fotos, cada una hasta donde tiene sentido para su
     propia proporción. */
.camp-header {
  display: flex;
  align-items: flex-start;
  gap: var(--gap-lg);
  margin-top: var(--space-lg);
}

.camp-left {
  flex: 0 0 480px;
  max-width: 480px;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.camp-photo {
  width: 100%;
  aspect-ratio: 480 / 276;
  overflow: hidden;
}

.camp-photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Contenedor de las 2 fotos pegadas: position:relative para anclar los
   piquitos absolutos (ver más abajo) — sin overflow:hidden acá, porque el
   piquito del medio (--seam) necesita sobresalir sobre la 2ª foto y el
   piquito de abajo (--gap) necesita sobresalir hacia el hueco antes del
   botón; el recorte de cada foto vive en .camp-pegada, no acá. Alto
   automático (suma de las 2 fotos, ya no depende de aspect-ratio): cada
   foto ahora tiene un alto propio en píxeles que cambia al hoverear. */
.camp-pegadas {
  position: relative;
}

/* Alto fijo en vez de aspect-ratio: necesitamos poder cambiarlo al
   hoverear (transición de "height") independiente del ancho, cosa que
   aspect-ratio no permite. Sin redondeo acá — cada foto define el suyo
   (ver más abajo, solo la de abajo lo tiene). */
.camp-pegada {
  position: relative;
  width: 100%;
  height: 80px;
  overflow: hidden;
  transition: height 0.45s cubic-bezier(.22, 1, .36, 1);
}

#camp-pegada-2 {
  border-radius: 0 0 14px 14px;
}

.camp-pegada img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Overlay del trazo: un <path> con pathLength="1" (normaliza el dasharray
   a 0-1 sin importar el largo real del contorno), recalculado en cada
   frame por el JS a partir del tamaño real de la foto (ver tick()/
   syncPegadas) — así el trazo queda siempre pegado al borde exacto,
   también mientras la foto cambia de alto al hoverear. Arranca invisible
   (dashoffset:1) y el JS lo anima a 0 al cargar la página.
   `stroke-dasharray`/`stroke-dashoffset` NO se definen acá a propósito:
   una regla de hoja de estilos le gana siempre al atributo SVG que anima
   el JS (los atributos de presentación son lo último en prioridad en la
   cascada) — si quedaran acá, el trazo se vería SIEMPRE oculto sin
   importar lo que el JS animara. Arrancan como atributos en el HTML
   (`stroke-dasharray="1" stroke-dashoffset="1"`) y de ahí en más los
   controla solo GSAP (`attr:{...}`, ver el <script>). */
.camp-pegada-trace {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.camp-pegada-trace path {
  fill: none;
  stroke: var(--accent);
  stroke-width: 5;
}

/* Piquitos: triángulos simples fluor, sutiles (opacidad baja), centrados
   sobre el ancho de la columna. El JS fija su "top" en cada frame
   (píxeles exactos, no porcentajes) porque el alto de cada foto cambia al
   hoverear: --seam cuelga desde la unión real entre las 2 fotos, --gap
   arranca donde termina la 2ª foto y ocupa el hueco de 8px (--space-xs,
   el mismo gap de .camp-left) antes del botón. */
.camp-piquito {
  position: absolute;
  left: 50%;
  width: 16px;
  height: 8px;
  transform: translateX(-50%);
  fill: var(--accent);
  pointer-events: none;
  z-index: 2;
}

.camp-cta-btn {
  position: relative;
  align-self: flex-start;
  padding: 14px 32px;
  border-radius: 6px;
}

.camp-cta-trace {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}

.camp-cta-trace path {
  fill: none;
  stroke: var(--accent);
  stroke-width: 4;
}

.camp-right {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.camp-epigrafe {
  margin: 0;
  font-family: var(--font-accent);
  font-weight: 300;
  font-style: normal;
  font-size: 16px;
  line-height: 1.4;
  color: var(--text-primary);
  white-space: pre-line;
}

.camp-text {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.camp-text p {
  margin: 0;
  font-family: var(--font-text);
  font-weight: 500;
  font-size: 15px;
  line-height: 1.5;
  color: var(--text-primary);
}

/* ---------- Proyectos — Tropismes ----------
   3 bloques alternados foto/texto (fotos 01/02/03, sin recorte — cada caja
   coincide con la proporción real 3:2 de las 3 fotos, así no queda
   letterboxing sin necesitar recorte) + acordeón francés (un botón
   rectangular por bloque, no por párrafo) + tira final de 5 fotos
   restantes con leve movimiento infinito. Spec completa en
   design-system.md ("Proyectos — Tropismes, reconstrucción 08-22" y
   correcciones de las pasadas 3ª/4ª/18ª). */
/* El orden visual (foto-izq/texto-der vs. texto-izq/foto-der) lo da
   directamente el orden de los 2 hijos en el HTML, no flex-direction — con
   solo align-items:flex-start acá, ".is-reverse" existe únicamente para el
   text-align/alineación de columna y las citas (ver más abajo), no cambia
   el layout de la fila. (Corregido: una versión anterior usaba
   flex-direction:row-reverse en ".is-reverse" con el texto primero en el
   HTML — el resultado real de esa combinación es foto-izq/texto-der, lo
   mismo que la fila normal, no el texto-izq/foto-der que pedía la spec.) */
.tropismes-row {
  display: flex;
  align-items: flex-start;
  gap: var(--gap-md);
  margin-top: var(--space-2xl);
}

.tropismes-photo {
  flex: 0 0 480px;
  max-width: 480px;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  display: block;
  cursor: pointer;
}

.tropismes-photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.tropismes-text {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-sm);
  font-family: var(--font-text);
  font-weight: 500;
  font-size: 15px;
  line-height: 1.6;
  color: var(--text-primary);
}

.tropismes-row.is-reverse .tropismes-text {
  align-items: flex-end;
  text-align: right;
}

.tropismes-text p { margin: 0; }

.tropismes-quote {
  margin: 0;
  padding-left: var(--space-md);
  border-left: 2px solid var(--accent);
}

.tropismes-row.is-reverse .tropismes-quote {
  padding-left: 0;
  padding-right: var(--space-md);
  border-left: none;
  border-right: 2px solid var(--accent);
}

/* Antes en --font-accent (Hoover, tratamiento de epígrafe): quedaba con otra
   tipografía que el resto de la sección — pedido explícito de Blas de
   igualarla al cuerpo de texto de Tropismes (--font-text, mismo criterio que
   .tropismes-text p). */
.tropismes-quote p {
  font-family: var(--font-text);
  font-weight: 500;
  font-style: normal;
  font-size: 15px;
  margin: 0 0 var(--space-xs);
}

.tropismes-quote-author {
  font-family: var(--font-text);
  font-weight: 500;
  font-style: normal;
  font-size: 13px;
  color: var(--text-secondary);
}

/* Botón de acordeón francés: rectangular, ancho fijo (spec 08-22, pasada
   18ª — antes ancho automático de ~36px), negro en reposo, fluor + chevron
   hacia arriba cuando está abierto (no es un hover, es el estado abierto). */
.tropismes-fr-btn {
  flex: 0 0 auto;
  width: 56px;
  height: 32px;
  border: none;
  border-radius: 9px;
  background: var(--text-primary);
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.25s ease, color 0.25s ease;
}

.tropismes-fr-btn svg { transition: transform 0.3s ease; }
.tropismes-fr-btn.is-open svg { transform: rotate(180deg); }

.tropismes-fr-btn.is-open {
  background: var(--accent);
  color: var(--text-primary);
}

/* max-height animado a un valor exacto medido por JS (scrollHeight, ver
   el <script>), no a un techo genérico como .caracol-full-text — con un
   techo grande (ej. 2000px) la transición de 0.5s recorre esa distancia
   completa y el contenido real (~150px) queda cubierto en una fracción
   ínfima de ese tiempo (150/2000 del recorrido), así que se veía como un
   salto en vez de un despliegue suave. Animando hacia la altura real, el
   0.5s completo se nota en el tramo visible. El valor de acá (0) es solo
   el estado inicial antes de que el JS mida nada. */
.tropismes-fr {
  width: 100%;
  overflow: hidden;
  max-height: 0;
  transition: max-height 0.5s ease;
}

.tropismes-fr-inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding-top: var(--space-sm);
  font-family: var(--font-text);
  font-weight: 500;
  font-size: 15px;
  line-height: 1.6;
  color: var(--text-primary);
}

.tropismes-fr-inner p { margin: 0; }

/* Tira final: franja que se desplaza sola (translateX 0 → -50%, loop
   infinito porque el contenido son 2 copias idénticas de las 5 fotos) y se
   pausa al hoverear para poder clickear con precisión. */
.tropismes-strip-wrap {
  margin-top: var(--space-2xl);
  overflow: hidden;
}

.tropismes-strip {
  display: flex;
  gap: var(--space-sm);
  width: max-content;
  animation: tropismes-strip-scroll 32s linear infinite;
}

@keyframes tropismes-strip-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* Ancho calculado para que entren exactamente 5 tiles en el ancho de
   contenido de la página (100vw menos el padding lateral de .obra-page,
   --space-2xl a cada lado, menos los 4 gaps entre los 5 tiles) — mismo
   criterio de calc ya usado en .proy-tile--sq/.proy-tile--wide. */
.tropismes-strip-tile {
  flex: 0 0 auto;
  width: calc((100vw - 2 * var(--space-2xl) - 4 * var(--space-sm)) / 5);
  aspect-ratio: 10 / 7;
  overflow: hidden;
  display: block;
}

/* Zoom al hoverear (la franja completa ya está pausada en ese momento, ver
   .tropismes-strip-wrap:hover más arriba). El overflow del tile recorta el
   zoom de la imagen, así que no hace falta separar la imagen visualmente
   de sus vecinas. */
.tropismes-strip-tile img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.45s ease;
}

/* ---------- Curaduría ----------
   Grid único con las 35 fotos rescatadas (Colectivas 24 + GRAPA 7 + El
   Querido 4), mezcladas entre sí, sin dropdown "Serie" ni texto
   introductorio. Reutiliza el mecanismo real de "elastic grid scroll" ya
   construido en Obra — Pintura (js/obra.js: N columnas propias en el DOM +
   lag diferencial de scroll, `gsap.quickTo` por columna con duración corta
   en el centro y larga en los extremos) — NO el mecanismo de galería
   horizontal con scroll/wheel de Objeto Instalación/Exposiciones (esa es
   otra animación del sitio, para otra sección). Mismo criterio de tile que
   Pintura: aspect-ratio fijo + `object-fit:cover` (cuadrícula simétrica de
   N por fila, no masonry — la nota vieja de la Sección 8 sobre "tiles de
   tamaño variable" describe la etapa de diseño en Pencil, superada por esta
   decisión explícita ya tomada al construir Pintura en código). Clases
   propias (`.cur-*`) en vez de reutilizar `.obra-grid`/`.obra-tile`
   directamente porque acá cada tile suma un caption con la categoría al
   hover (Colectivas/GRAPA/El Querido, spec de Curaduría) que Pintura no
   tiene. Click abre el lightbox genérico del sitio (mismo componente que
   Tropismes/Lamarencoche/Pintura) con flechas que ciclan por las 35 fotos
   en el mismo orden mezclado del grid. */
.cur-grid {
  display: flex;
  gap: var(--space-xs);
  margin-top: var(--space-2xl);
}

/* Mismo gap vertical que .obra-grid-col de Pintura (antes var(--space-md),
   dejaba mucho más aire entre filas que Obra). */
.cur-grid-col {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  min-width: 0;
}

.cur-tile {
  display: block;
  position: relative;
  width: 100%;
  aspect-ratio: 5 / 4;
  overflow: hidden;
  cursor: pointer;
  background: var(--bg-pure);
}

.cur-tile img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  transition: transform 0.5s cubic-bezier(.22, 1, .36, 1);
}

/* Caption superpuesto DENTRO de la imagen, abajo a la izquierda (antes
   quedaba debajo, fuera de la foto) — degradé oscuro para que el texto
   blanco se lea sin importar el brillo de la foto de fondo. */
.cur-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: var(--space-md) var(--space-xs) var(--space-xs);
  background: linear-gradient(to top, rgba(17, 17, 17, 0.7), rgba(17, 17, 17, 0));
  opacity: 0;
  transition: opacity 0.25s ease;
  pointer-events: none;
}

.cur-caption-label {
  position: relative;
  display: inline-block;
  font-family: var(--font-hover-title);
  font-weight: 600;
  font-size: 13px;
  color: #fff;
}

.cur-caption-label::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -3px;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.3s ease;
}

/* ---------- About ----------
   Página única (no 3 pantallas separadas — spec final en design-system.md,
   "About vuelve a ser una sola página", corrige un intento intermedio de 3
   pantallas) con las 3 subsecciones (Sobre Josefina / Sobre su obra /
   Formación de tutoría) apiladas en orden de scroll, cada una con su propio
   título inline — no hay un título de página "About" arriba. Mismo patrón
   de fila foto/texto que Tropismes/estrategia del caracol: el orden visual
   (foto-izq/texto-der vs. texto-izq/foto-der) lo da el orden de los 2 hijos
   en el HTML, ".is-reverse" solo cambia el text-align. */
/* Reducido de $space-3xl (96px, quedaba mucho aire entre subsecciones) a
   $space-xl (48px) a cada lado del borde separador. */
.about-section + .about-section {
  margin-top: var(--space-xl);
  padding-top: var(--space-xl);
  border-top: 1px solid var(--border);
}

/* Título más pegado a la fila (antes $space-lg, 32px) — mismo ritmo
   ajustado que marionaespinet.com/pages/about (título+subtítulo a ~20-24px
   del primer párrafo, ver decisions/log.md 09-01). */
.about-title {
  margin: 0 0 var(--space-md);
  font-family: var(--font-h1-inner);
  font-size: 40px;
  font-weight: 700;
  color: var(--text-primary);
}

/* Gap foto-texto ampliado a $gap-lg (64px, antes $gap-md/32px) para que
   respire igual que marionaespinet.com/pages/about (~92px de gap real a
   escala de esa página) — spec en decisions/log.md 09-01. */
/* Con .about-text a ancho fijo (ya no flex-fill), la fila entera pesa menos
   que el contenedor — centrada (justify-content: center) en vez de pegada
   a un borde, igual en las 3 subsecciones (spec explícita 09-01: "centrá
   cada uno de los bloques"). is-reverse ya no necesita un justify-content
   propio para eso — sigue existiendo solo para el text-align. */
.about-row {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: var(--gap-lg);
}

/* Margen entre filas reducido (antes $space-2xl, 64px) para que asome más
   de la fila/sección siguiente en la primera pantalla y quede claro que hay
   que scrollear — mismo espíritu que FILA_MAX_HEIGHT en Cuarto Intermedio
   de Exposiciones (Obra), acá vía menos margen en vez de un alto máximo. */
.about-row + .about-row {
  margin-top: var(--space-lg);
}

/* Corregido: el método de Estrategia del Caracol (ancho de foto = alto real
   del texto × proporción) se probó y se descartó acá — con textos de 1 solo
   párrafo corto (Sobre su obra, Formación de tutoría) daba una foto
   diminuta (~86×48px), nada "proporcional" al resto de la página. Vuelve a
   ser tamaño fijo por CSS, pero más grande que la versión original (315×480
   / 420×236) y coherente entre sí: la pareja de Sobre Josefina queda
   exactamente igual entre sí (spec explícita), y la pareja de Sobre su
   obra/Formación de tutoría también, con un peso visual (ancho×alto)
   parecido al de la primera pareja pese a la proporción distinta. (09-01:
   Formación de tutoría cambió de foto y de caja — ver nota en
   .about-photo--portrait/.about-photo--wide más abajo — esta relación de
   peso visual entre parejas ya no aplica tal cual, queda como historial.) */
.about-photo {
  overflow: hidden;
  background: var(--bg-pure);
}

.about-photo img {
  display: block;
  width: 100%;
  height: 100%;
}

/* Retratos de Sobre Josefina (sobre-mi.jpg 0.645, about-5.jpg 0.667) —
   mismo tamaño exacto entre sí, recorte mínimo (cover) para igualar sus
   proporciones reales, casi idénticas. Agrandadas (antes 360×548) hasta
   que la primera (junto al párrafo largo de la fila 1) iguale el alto del
   bloque de texto a su lado (680px medidos) — spec 09-01: "aumentá
   equitativamente el tamaño de ambas fotos... hasta que la primera
   coincida con el largo del bloque de texto". Misma proporción 360/548,
   la segunda foto (about-5.jpg) queda más alta que su propio texto (más
   corto) y así está previsto. */
.about-photo--portrait {
  flex: 0 0 447px;
  max-width: 447px;
  height: 680px;
}

.about-photo--portrait img {
  object-fit: cover;
}

/* Formacióntutoría.jpeg (reemplazó a formacion-tutoria.jpg el 09-01,
   archivado en /archives/projects/web-jf/assets/about/) — mantiene su
   propia clase en vez de compartir .about-photo--portrait/--wide porque
   necesita su propio object-fit:cover (recortada) en vez de contain.
   Alto recortado a mano (spec 09-04): con la altura previa (548px) la foto
   (cuadrada, 3024x3024) no se recortaba arriba/abajo en absoluto — cover
   con esa caja angosta y alta solo recorta los costados, mostrando el
   cuadrado entero de punta a punta. Se bajó a la altura real del texto de
   al lado (216px), comprobado con un preview de Jimp que no cortaba
   ninguna cabeza del grupo de la foto. Tamaño final (spec 09-04, segunda
   pasada — "aumentá el tamaño... tal como está la de sobre su obra"):
   mismas medidas que .about-photo--wide (560x315) para que ambas fotos de
   la fila de abajo se vean del mismo porte — el recorte vertical logrado
   arriba se mantiene intacto (216/360 vs. 315/560 son casi la misma
   proporción, la diferencia es imperceptible; sigue siendo cover, no
   contain, así que no vuelve a mostrarse la foto entera). */
.about-photo--portrait-sm {
  flex: 0 0 560px;
  max-width: 560px;
  height: 315px;
}

.about-photo--portrait-sm img {
  object-fit: cover;
}

/* Apaisada ~16:9 (sobre-mi-obra.jpg, ~1.778) — sin recorte (la caja ya
   coincide con su proporción real). */
.about-photo--wide {
  flex: 0 0 560px;
  max-width: 560px;
  height: 315px;
}

.about-photo--wide img {
  object-fit: contain;
}

/* Antes flex:1 1 0 (estiraba el texto a todo el ancho restante — hasta
   ~1080px en las filas retrato). Ahora ancho fijo de lectura (480px, misma
   lógica que el max-width:455px de marionaespinet.com/pages/about escalado
   a este layout) en vez de flex-fill, igual en las 3 subsecciones — el
   sobrante queda como margen derecho, mismo criterio que esa referencia.
   Ver decisions/log.md 09-01. */
.about-text {
  flex: 0 1 480px;
  max-width: 480px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  font-family: var(--font-text);
  font-weight: 500;
  font-size: 15px;
  line-height: 1.6;
  color: var(--text-primary);
}

.about-row.is-reverse .about-text {
  text-align: right;
}

.about-text p {
  margin: 0;
}

/* ---------- Política de Privacidad ----------
   Página de texto plano, sin diseño propio en Pencil (contenido legal, no
   parte del sitemap visual) — reutiliza .obra-page/.obra-page-title como el
   resto de las páginas interiores. Columna angosta (680px) para legibilidad
   de párrafos largos, mismo criterio tipográfico que .about-text. */
.legal-page {
  max-width: 680px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

.legal-updated {
  margin: 0;
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--text-secondary);
}

.legal-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.legal-section h2 {
  margin: 0;
  font-family: var(--font-h1-inner);
  font-size: 20px;
  font-weight: 700;
  color: var(--text-primary);
}

.legal-section p,
.legal-section li {
  margin: 0;
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.6;
  color: var(--text-primary);
}

.legal-section ul {
  margin: 0;
  padding-left: 20px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* ---------- Contacto ----------
   Construida en código (2026-09-02), spec de design-system.md ("Contacto —
   construida 08-22") y contenido.md: título + subtítulo + Instagram
   centrados, formulario de 3 campos centrado debajo. Reutiliza .obra-page
   (mismo contenedor/márgenes que el resto del sitio) y .obra-page-title
   (mismo título 40px sitewide) — .contact-page solo centra todo lo que va
   adentro. */
.contact-page {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

.contact-intro {
  max-width: 480px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
}

/* Antes 18px (spec original de Pencil) — achicado a 14px (spec 09-02). */
.contact-subtitle {
  margin: 0;
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--text-secondary);
}

.contact-instagram {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: 14px;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.contact-form {
  width: 100%;
  max-width: 480px;
  margin-top: var(--space-2xl);
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  text-align: left;
}

.contact-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.contact-field-label-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-xs);
}

.contact-field label {
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 500;
  color: var(--text-secondary);
}

/* "Campo obligatorio" — oculto por default ([hidden]), lo muestra/oculta
   el JS. Mismo patrón de "Reference / Contacto — Campo obligatorio" en
   web-jf.pen: aparece junto al label si se intenta enviar con ese campo
   vacío, se oculta apenas el usuario lo completa. */
.contact-field-error {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--error);
}

/* Inputs con borde inferior nada más (sin caja), mismo lenguaje spare del
   resto del sitio (líneas finas en $border, mucho blanco) en vez de un
   formulario "boxeado" como en la referencia de Pencil — el borde rojo de
   ahí se traduce acá a esa misma línea inferior, en rojo. */
.contact-field input,
.contact-field textarea {
  border: none;
  border-bottom: 1px solid var(--border);
  background: transparent;
  padding: var(--space-xs) 0;
  font-family: var(--font-body);
  font-size: 15px;
  color: var(--text-primary);
  transition: border-color 0.2s ease;
  resize: vertical;
}

.contact-field input:focus,
.contact-field textarea:focus {
  outline: none;
  border-bottom-color: var(--text-primary);
}

.contact-field--error input,
.contact-field--error textarea {
  border-bottom-color: var(--error);
}

/* El botón "Enviar" ya no se deshabilita (spec 09-02: siempre clickeable —
   si falta algún campo, el click no manda nada y marca el/los campos
   vacíos en vez de bloquear el botón). Mismo .btn-primary y su hover de
   siempre, sin estado disabled. */
.contact-submit {
  align-self: center;
  border: none;
  cursor: pointer;
  margin-top: var(--space-sm);
}

/* Confirmación de envío (spec 09-02) — chica, centrada, debajo del botón.
   Oculta por default ([hidden]), el JS la muestra al enviar bien y la
   vuelve a ocultar si el usuario retoma algún campo para un envío nuevo. */
.contact-success {
  margin: var(--space-xs) 0 0;
  align-self: center;
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--text-secondary);
  text-align: center;
}

/* ---------- Footer ---------- */

.site-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: var(--bg-base);
  padding: 20px var(--space-2xl);
}

.site-footer span,
.site-footer a {
  font-family: var(--font-footer);
  font-weight: 500;
  font-size: 12px;
  color: var(--text-secondary);
}

.site-footer a { transition: color 0.2s ease; }

/* Crédito de Blas ("Diseño web: ...") apilado debajo del link de Instagram
   — mismo lado derecho del footer (pedido explícito: "debajo del instagram
   de josefina"), no un 3er ítem en la fila. */
.site-footer-links {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
}

/* Política de Privacidad del lado izquierdo (pedido explícito), apilada
   debajo del © igual que Diseño web se apila debajo de Instagram del lado
   derecho. */
.site-footer-left {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}

/* ---------- Buscador (spec design-system.md, "Nuevo Reference / Nav Search
   Overlay" + rediseño a modal centrado) ----------
   Modal de 640px centrado sobre un scrim gris translúcido, estilo
   marionaespinet.com. Se abre con la lupa del nav (js/search.js, único
   script compartido entre las 14 páginas para esto — mismo criterio que
   js/nav.js), con fade+scale-in; cierra con X / Esc / click en el scrim.
   z-index por encima del nav (100) y del lightbox (200): en la práctica
   nunca conviven, pero si alguna vez se abre el buscador con un lightbox
   detrás, debe quedar por encima. */

.search-overlay {
  position: fixed;
  inset: 0;
  z-index: 300;
  background: #0000004D;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 12vh;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
}

.search-overlay.is-open {
  opacity: 1;
  pointer-events: auto;
}

.search-modal {
  width: 640px;
  max-width: calc(100vw - var(--space-lg) * 2);
  max-height: 70vh;
  background: var(--bg-base);
  border-radius: 8px;
  box-shadow: 0 24px 60px -20px rgba(17, 17, 17, 0.35);
  display: flex;
  flex-direction: column;
  transform: scale(0.96);
  opacity: 0;
  transition: transform 0.25s cubic-bezier(.22, 1, .36, 1), opacity 0.2s ease;
}

.search-overlay.is-open .search-modal {
  transform: scale(1);
  opacity: 1;
}

.search-input-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-md) var(--space-lg);
  border-bottom: 1px solid var(--border);
  flex: 0 0 auto;
}

.search-input-row svg { width: 20px; height: 20px; color: var(--text-secondary); flex: 0 0 auto; }

.search-input {
  flex: 1 1 auto;
  border: none;
  background: none;
  outline: none;
  font-family: var(--font-body);
  font-size: 18px;
  color: var(--text-primary);
}

.search-input::placeholder { color: var(--text-secondary); }

.search-close {
  color: var(--text-secondary);
  font-size: 20px;
  line-height: 1;
  padding: 4px;
  cursor: pointer;
  transition: color 0.2s ease;
}

.search-results {
  overflow-y: auto;
  padding: var(--space-sm) 0;
}

.search-result {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  width: 100%;
  padding: var(--space-xs) var(--space-lg);
  text-align: left;
  cursor: pointer;
  transition: background 0.15s ease;
}

.search-result:focus-visible { background: var(--border); }

.search-result-thumb {
  flex: 0 0 56px;
  width: 56px;
  height: 56px;
  border-radius: 4px;
  overflow: hidden;
  background: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-search-thumb);
  font-weight: 700;
  font-size: 20px;
  color: var(--text-primary);
}

.search-result-thumb img { width: 100%; height: 100%; object-fit: cover; }

.search-result-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

.search-result-title {
  font-family: var(--font-search-title);
  font-weight: 500;
  font-size: 16px;
  color: var(--text-primary);
}

.search-result-crumb {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--text-secondary);
}

.search-empty {
  padding: var(--space-lg);
  text-align: center;
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--text-secondary);
}

/* ---------- Efectos que dependen de NO tener hover real ----------
   Contraparte del bloque "@media (hover:hover)" de más arriba — acá van
   reglas pensadas para cualquier dispositivo sin hover real (celular Y
   iPad, sin importar el ancho: un iPad en horizontal mide más de 900px y
   usa el layout de escritorio igual que una notebook, pero sigue sin poder
   "hoverear" nada). Antes estas 2 vivían adentro de "@media (max-width:
   900px)", así que en iPad horizontal (>900px, layout de escritorio) nunca
   se veían: el título de las 3 fotos de entrada de About quedaba invisible
   (dependía de :hover) y las fotos "pegadas" de Campamento Base nunca se
   desplegaban al tocarlas. Gateado por ancho de pantalla en vez de por
   capacidad real del dispositivo.
   Va ACÁ (después de .obra-photo-caption/.camp-pegada base, no arriba junto
   a "@media (hover:hover)") a propósito: misma especificidad que esas
   reglas base (0,1,0), así que sin @media de por medio gana la que aparece
   última en el archivo — puesto arriba, la base (más abajo) lo pisaba
   siempre sin importar si el media matcheaba o no. */
@media (hover: none) {
  /* Título de las 3 fotos de entrada de About: en desktop se revela con el
     hover del mouse (.obra-photo:hover .obra-photo-caption, ver bloque
     "@media (hover:hover)" de arriba) — sin hover real nunca se llega a ver.
     Pedido explícito: visible siempre, y a un tamaño de lectura normal
     (antes 20px, pensado para un título grande que aparece solo al hover). */
  .obra-photo-caption { opacity: 1; }
  .obra-photo-title { font-size: 14px; }
  /* Mismo efecto visual que .camp-pegada:hover (bloque de arriba) pero
     activado por tap vía JS (ver proyectos-campamento-base.html) en vez de
     :hover puro — dispositivos sin hover real no pueden expresar "hoverear
     esta foto" con CSS solo, hace falta la clase que pone el JS al tocar. */
  .camp-pegada.is-tapped { height: var(--hover-h, 140px); }
}

/* ---------- Responsive ---------- */

@media (max-width: 900px) {
  .nav { padding: var(--space-sm) var(--space-md); }

  /* Bug reportado: logo pegado al borde. .nav-left trae un margin-left:-20px
     pensado para escritorio (nudge hacia el borde del nav ancho, ver
     comentario en la regla original) — acá, con el padding de .nav ya
     reducido arriba a var(--space-md), ese mismo negativo lo empuja casi
     hasta pegarlo al borde real de la pantalla. Se resetea a 0: el padding
     de .nav (24px) alcanza solo como margen. */
  .nav-left { margin-left: 0; gap: 12px; flex-wrap: nowrap; }

  /* En mobile el wordmark completo se reemplaza por "JF" (pedido explícito),
     en la misma tipografía y color que el favicon: Arial/Helvética bold,
     negro. Se hace por CSS (font-size:0 colapsa el texto real del <a>, el
     ::before pinta "JF") para no tocar el HTML de las 14 páginas ni la
     versión de escritorio. `vertical-align: middle` + `line-height: 1` lo
     dejan centrado con el resto de la navbar (botón Mentorías, lupa,
     hamburguesa) — sin offset vertical propio. */
  .logo { font-size: 0; }
  .logo::before {
    content: "JF";
    display: inline-block;
    vertical-align: middle;
    line-height: 1;
    font-family: Arial, "Helvetica Neue", Helvetica, "Liberation Sans", sans-serif;
    /* 26px: la marca tiene que pesar más que el resto de la navbar (ítems
       16px, lupa/hamburguesa 22px) — pedido explícito. */
    font-size: 26px;
    font-weight: 700;
    letter-spacing: -0.02em;
    color: #000;
  }
  /* Más chico todavía (pedido explícito, ronda siguiente a la primera
     achicada) — mismo criterio de peso visual liviano junto al logo. */
  .btn-pill { padding: 4px 8px; font-size: 9px; }

  /* ---------- Menú mobile (hamburguesa) ----------
     Reutiliza el MISMO <nav class="nav-items"> de escritorio (dropdowns,
     estado activo/is-current por página, etc. ya vienen correctos, no hace
     falta duplicar nada) — en vez de display:none pasa a ser un panel fijo
     que entra deslizando desde el borde derecho (pedido explícito: "se
     desplegará hacia la izquierda... se plegará hacia la derecha"), a la
     mitad del ancho de pantalla. La lupa y la hamburguesa viven AFUERA de
     este panel (.nav-mobile-controls, sibling de .nav-items dentro de
     .nav) porque deben verse siempre en el header, no solo con el menú
     abierto — ver js/nav.js para el toggle. */
  .nav-items {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    position: fixed;
    top: var(--nav-height);
    right: 0;
    /* height (no bottom:0): .nav tiene will-change:transform, que lo
       convierte en containing block de sus propios descendientes fixed
       (spec: will-change nombrando "transform" cuenta como si transform
       ya estuviera aplicado a estos fines) — un .nav-items fixed queda
       contenido en la CAJA DE .nav (alta solo var(--nav-height), 64px), no
       en el viewport real. "bottom:0" ahí adentro colapsa la altura a casi
       nada. vh sí sigue siendo relativo al viewport real sin importar la
       containing block, así que calc(100vh - nav-height) da el alto
       correcto igual. */
    height: calc(100vh - var(--nav-height));
    width: 50vw;
    padding: var(--space-lg) var(--space-md);
    background: var(--bg-base);
    box-shadow: -12px 0 30px -14px rgba(17, 17, 17, 0.3);
    overflow-y: auto;
    transform: translateX(100%);
    transition: transform 0.4s cubic-bezier(.25, .46, .45, .94);
    z-index: 99;
  }

  .nav-items.is-mobile-open {
    transform: translateX(0);
  }

  body.nav-mobile-locked { overflow: hidden; }

  /* La lupa de escritorio queda como ÚLTIMO hijo de .nav-items — se oculta
     acá para no duplicar el buscador dentro del panel (el pedido explícito
     la quiere junto a la hamburguesa, afuera). */
  .nav-items > .icon-search { display: none; }

  /* Alineado a la derecha (pedido explícito) — justify-content:flex-end
     empuja el .nav-label (único hijo real en el flujo flex; el .underline
     es position:absolute, no cuenta) al borde derecho del panel. */
  .nav-item { width: 100%; padding: var(--space-sm) 0; justify-content: flex-end; }

  /* Acordeón de Obra/Proyectos: sin hover (touch), toda la mecánica de
     "zona ampliada" de escritorio (padding-bridge + el pseudo-elemento de
     abajo) no aplica — se resetea para que no intercepte taps del resto de
     la página cuando el dropdown queda .is-open (js/nav.js agrega esa
     misma clase acá también, ahora disparada por click, ver ese archivo). */
  .nav-dropdown-wrap {
    flex-direction: column;
    align-items: stretch;
    padding-bottom: 0;
    margin-bottom: 0;
    width: 100%;
  }
  .nav-dropdown-wrap:nth-child(1),
  .nav-dropdown-wrap:nth-child(2) {
    padding-left: 0;
    margin-left: 0;
    padding-right: 0;
    margin-right: 0;
  }
  .nav-dropdown-wrap:nth-child(1).is-open::before { content: none; }

  /* Subrayado del título con dropdown: de derecha a izquierda al abrirse
     (pedido explícito), no el guioncito centrado de 34px de escritorio —
     se ancla al borde derecho y el ancho crece hacia la izquierda (mismo
     resultado visual que el scaleX+transform-origin del H2 de Objeto
     Instalación/Exposiciones, más simple acá porque no hace falta GSAP:
     una transición de "width" con el lado derecho fijo ya traza en ese
     sentido sola). */
  .nav-dropdown-wrap .nav-item .underline {
    left: auto;
    right: 0;
    transform: none;
    width: 0;
    /* 3px (base 1px): en mobile a 1px el subrayado no se notaba (pedido
       explícito). */
    height: 3px;
    transition: width 0.5s ease;
  }
  .nav-dropdown-wrap.is-open > .nav-item .underline { width: 100%; }

  /* El panel deja de ser overlay flotante (position:absolute + clip-path)
     y pasa a acordeón in-flow: sin foto (no hay hover para cruzarla, spec
     explícita "sin imágenes porque no se puede hoverear"), solo la lista
     de subsecciones, alto animado con max-height en vez de clip-path. */
  .nav-dropdown {
    position: static;
    width: 100%;
    padding: 0;
    background: transparent;
    box-shadow: none;
    display: block;
    opacity: 1;
    pointer-events: auto;
    clip-path: none;
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.4s cubic-bezier(.25, .46, .45, .94);
  }
  .nav-dropdown-wrap.is-open .nav-dropdown { max-height: 300px; }
  .nav-dropdown-photo { display: none; }
  /* Alineados a la derecha (pedido explícito), igual que las secciones de
     arriba. */
  .nav-dropdown-list { padding: var(--space-sm) 0 var(--space-xs) var(--space-sm); gap: var(--space-md); align-items: flex-end; }
  .nav-dropdown-list a { text-align: right; }

  /* Línea fluor al final del dropdown (pedido explícito): mismo tamaño
     (1px, var(--accent)), velocidad (0.5s ease) y dirección (ancla a la
     derecha, ancho 0→100% crece hacia la izquierda) que el subrayado del
     título de la sección — para diferenciar visualmente "esto son
     subsecciones" de una sección plana (Curaduría/About/Contacto, que solo
     llevan el subrayado del título, sin esta línea). margin-left:auto en
     vez de position:absolute: un bloque con ancho variable y margin-left
     auto queda igual de anclado al borde derecho a medida que crece, sin
     necesitar un ancestro posicionado. */
  .nav-dropdown-list::after {
    content: '';
    display: block;
    width: 0;
    height: 3px;
    margin: var(--space-sm) 0 0 auto;
    background: var(--accent);
    transition: width 0.5s ease;
  }
  .nav-dropdown-wrap.is-open .nav-dropdown-list::after { width: 100%; }

  /* Subrayado del título en el click detail de Obra (Objeto Instalación /
     Exposiciones): 4px en vez de 2px — a 2px se perdía en pantalla chica
     (pedido explícito). El resto de los subrayados fluor del sitio son
     efectos de :hover, que en touch no se muestran. */
  .obra-oi-detail-underline { height: 4px; bottom: -6px; }

  /* ---------- Controles mobile (lupa + hamburguesa) ----------
     Mismo tamaño y alineadas (pedido explícito) — un punto medio entre los
     20px de .icon-search (escritorio) y los 24×18px que tenía la
     hamburguesa: 22×22px cuadrados para las 2. .icon-search trae un
     "top:-5px" pensado para su alineación óptica junto al texto del nav de
     escritorio — acá, junto a la hamburguesa, ese offset la desalineaba
     verticalmente; se resetea a 0 solo para esta instancia. */
  .nav-mobile-controls { display: flex; align-items: center; gap: var(--space-md); }
  .nav-mobile-controls .icon-search { width: 22px; height: 22px; top: 0; }
  .nav-mobile-controls .icon-search svg { width: 22px; height: 22px; }

  .nav-burger {
    position: relative;
    width: 22px;
    height: 22px;
    padding: 0;
  }
  .nav-burger span {
    position: absolute;
    left: 0;
    width: 100%;
    height: 2px;
    background: var(--text-primary);
    transition: transform 0.25s ease, opacity 0.25s ease, top 0.25s ease;
  }
  .nav-burger span:nth-child(1) { top: 4px; }
  .nav-burger span:nth-child(2) { top: 10px; }
  .nav-burger span:nth-child(3) { top: 16px; }
  .nav-burger.is-open span:nth-child(1) { top: 10px; transform: rotate(45deg); }
  .nav-burger.is-open span:nth-child(2) { opacity: 0; }
  .nav-burger.is-open span:nth-child(3) { top: 10px; transform: rotate(-45deg); }

  /* ---------- Sticky footer ----------
     Bug reportado en Exposiciones (y potencialmente cualquier página cuyo
     contenido en mobile no llegue a llenar la pantalla): el body, por
     comportamiento normal del navegador, siempre ocupa como mínimo el 100%
     del viewport — pero CTA+footer, al ser más cortos que ese mínimo,
     quedaban arriba pegados al contenido, con un hueco real del fondo de
     la página por debajo del footer en vez de quedar anclados abajo como
     se espera en una página corta. Sticky-footer clásico (flex column +
     margin-top:auto en el footer, que es el último hijo de body) — no
     cambia nada en desktop, donde el contenido ya excede el alto de
     pantalla en casi todas las páginas (margin:auto no hace nada si no
     sobra espacio de sobra que repartir). .nav es position:fixed, así que
     queda afuera de este flex (no consume espacio ni lo desordena). */
  body {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
  }
  .site-footer { margin-top: auto; }

  .hero, .bio-breve, .mentoria-cta, .site-footer {
    padding-left: var(--space-md);
    padding-right: var(--space-md);
  }
  .obras-destacadas { padding: var(--space-md) var(--space-sm); }
  .obras-grid { gap: 4px; }
  /* Pedido explícito: 4 fotos quedan muy apretadas en mobile — se oculta
     la 3ra (Situaciones, obra-pintura-20-79.jpg) y el grid pasa de 4 a 3
     columnas, así las 3 restantes se agrandan proporcionalmente para
     ocupar el mismo ancho total (no quedan más chicas de lo que ya
     estaban). Scoped a #obras-destacadas: About usa la misma clase
     .obras-grid pero con el modificador .obras-grid--3 (ya 3 columnas fijo,
     sin media query) — no se toca. */
  #obras-destacadas .obra-col:nth-child(3) { display: none; }
  #obras-destacadas .obras-grid { grid-template-columns: repeat(3, 1fr); }
  /* Título de las 3 fotos de entrada de About y tap de Campamento Base:
     movido a "@media (hover: none)" cerca del tope del archivo — no
     depende del ancho de pantalla, depende de si el dispositivo tiene
     hover real o no (afecta también a iPad en horizontal, >900px). */
  /* Mismo criterio que en desktop: igualar el margen al de .obra-page en
     este breakpoint (--space-2xl, 64px), no al --space-xl genérico. */
  .section-spacer-cta { height: var(--space-2xl); }
  .bio-text { width: 100%; }
  /* Revertido (pedido explícito): vuelve a texto negro sólido, sin
     mix-blend-mode — la foto de fondo nueva (sobrej.jpg) ya deja al texto
     siempre sobre la pared gris (ver background-position más abajo), así
     que no hace falta que el color se adapte solo. */
  .bio-text { color: var(--text-primary); }
  /* Título y texto más chicos (pedido explícito) — mismo criterio de
     achicar todo lo que en escritorio estaba pensado para una caja mucho
     más ancha. */
  .bio-text h2 { font-size: 22px; }
  .bio-text p { font-size: 13px; }
  /* Foto de fondo propia de mobile (pedido explícito, "sin tocar
     computadora"): la persona de sobrej.jpg ocupa el tercio izquierdo de
     la imagen, el resto es pared gris lisa — el texto vive anclado abajo a
     la derecha (.bio-breve, ver regla sin media query), así que con
     background-position:right el recorte de "cover" en una caja angosta
     (390px vs. una foto bastante más ancha que alta) muestra la porción
     derecha de la foto, dejando a la persona completamente afuera del
     encuadre — el texto queda siempre sobre la pared, nunca superpuesto a
     ella. */
  .bio-breve {
    background-image: url('../assets/home/sobrej.jpg');
    background-position: 68% center;
  }
  .hero { height: 480px; }
  .bio-breve, .mentoria-cta { height: auto; }

  .obra-page { padding: var(--space-lg) var(--space-md) var(--space-2xl); }
  /* Pedido explícito: el botón "Serie" siempre a la derecha del título,
     nunca debajo — antes se apilaba en columna acá. min-width:0 en el
     título permite que se achique/haga wrap sin forzar overflow horizontal
     si el nombre de la serie es largo; flex-shrink:0 en el selector evita
     que el botón se comprima primero. */
  .obra-header { flex-direction: row; align-items: center; justify-content: space-between; gap: var(--space-sm); }
  .obra-page-title { font-size: 28px; min-width: 0; }
  .obra-serie-selector { flex-shrink: 0; }
  .lightbox-prev, .lightbox-next { font-size: 44px; padding: 8px; }

  /* Grid de Proyectos — Lamarencoche: las 2 filas de 2 pasan a apilarse en
     una sola columna de 4, cada foto al mismo ancho relativo al viewport
     (ya no 316/400 fijos) manteniendo su proporción real 4:3. */
  .proy-fila { flex-direction: column; }
  .proy-tile { flex-direction: column; gap: var(--space-xs); }
  .proy-tile--sq .proy-photo, .proy-tile--wide .proy-photo,
  .proy-tile--sq img, .proy-tile--wide img { width: min(320px, 78vw); }
  .proy-photo { height: auto; aspect-ratio: 4 / 3; }
  /* Sin margen lateral disponible en mobile: el título vuelve a vivir debajo
     de la foto, siempre visible (no hay hover en touch) y a un tamaño de
     lectura normal en vez de "grande". */
  .proy-caption {
    position: static;
    width: 100%;
    margin-top: var(--space-xs);
    opacity: 1;
    transform: none;
  }
  .proy-caption-label { font-size: 14px; line-height: 1.4; }
  .proy-tile--left .proy-caption-label,
  .proy-tile--right .proy-caption-label { text-align: left; }
  /* 4px (base 2px) + un poco más de aire — igualado al subrayado del título
     del click detail de Obra (misma corrección de esta tanda: a 2px no se
     notaba en pantalla chica). */
  .proy-caption-rule { transform: scaleX(1); margin-top: 6px; height: 4px; }

  /* La estrategia del caracol: filas a columna, texto siempre arriba de la
     foto (is-reverse no invierte en mobile, mismo criterio que el resto del
     sitio — ver .obra-oi-fila.is-reverse). */
  .caracol-row, .caracol-row.is-reverse { flex-direction: column; }
  .caracol-row { gap: var(--space-sm); margin-top: var(--space-lg); }
  /* Pedido explícito: los primeros 3 párrafos (fila 1, alineados a la
     derecha en escritorio "hacia la foto") pasan a la izquierda en mobile
     — apilado en una sola columna angosta, alinear hacia una foto que ya
     no está al lado no tiene sentido (mismo criterio ya aplicado en
     Tropismes). La fila 2 (.is-reverse) ya estaba a la izquierda, sin
     cambios — esta regla no le pisa nada porque esa selectora es más
     específica (2 clases). */
  .caracol-text { text-align: left; }
  /* En mobile las filas se apilan (foto y texto ya no están lado a lado),
     así que dejan de matchear altura — el JS resetea el width/height inline
     que fija en desktop y esta regla vuelve a mandar. */
  /* max-width acá también: el de escritorio (calc(50% - gap), para que la
     foto no le pida más ancho del que le queda libre a la fila) se queda
     pisando el 100% de mobile si no se resetea, porque es la misma regla de
     clase simple y no se sobrescribe sola por especificidad. */
  .caracol-photo { flex: 0 0 auto; width: 100%; max-width: 100%; height: auto; aspect-ratio: 4 / 3; }

  /* flex-basis (parte del shorthand "flex" de .obra-oi-card en desktop)
     manda sobre "width" en un flex item — sin resetear "flex" acá, la
     tarjeta seguía midiendo su ancho de escritorio aunque width bajara a
     220px. Mismo motivo por el que el override de Exposiciones de abajo
     también resetea "flex", no solo "width". */
  /* Mismo tamaño para las 3 series de Objeto Instalación (Autorretratos,
     Compuestos, Señalamientos) y para Exposiciones (pedido explícito, "un
     punto medio, igual para todas") — antes Autorretratos usaba una
     fórmula atada a 100vw/3.5 (calc((100vw - 3*gap)/3.5), pensada para
     encajar 3.5 tarjetas en el ancho de pantalla) que a los anchos de
     celular típicos daba tarjetas bastante más chicas (~100-150px) que las
     otras 2 series y Exposiciones (220px fijo) — 180px es el punto medio
     elegido entre ambos extremos. */
  .obra-oi-card, .obra-oi-photo { flex: 0 0 180px; width: 180px; }
  .obra-oi-photo { height: 180px; }
  /* ".is-serie-autorretratos .obra-oi-card/.obra-oi-photo" (fórmula vw)
     tiene más especificidad que la línea de arriba y ganaría también en
     mobile — se repite acá con la misma especificidad para que converja al
     180px estándar en este breakpoint. */
  .obra-oi-gallery-track.is-serie-autorretratos .obra-oi-card,
  .obra-oi-gallery-track.is-serie-autorretratos .obra-oi-photo {
    flex: 0 0 180px;
    width: 180px;
  }
  /* Autorretratos también fija "height" con esa misma especificidad en
     escritorio (calc atado a vw) — se repite acá para que no le gane a los
     180px parejos de arriba. */
  .obra-oi-gallery-track.is-serie-autorretratos .obra-oi-photo { height: 180px; }

  /* Exposiciones QUEDA AFUERA del 180px parejo de arriba (pedido explícito
     de esta pasada, revierte el "mismo tamaño para todas" de la pasada
     anterior): sin texto introductorio y con una sola fila de fotos, a
     180px la página quedaba muy corta — mucho aire antes del CTA y, por el
     sticky-footer (ver más abajo en este mismo bloque), ese aire terminaba
     empujado entre el CTA y el footer en vez de desaparecer. Fotos más
     grandes, misma proporción 500:360 que en escritorio (ver
     ".page-exposiciones .obra-oi-card/.obra-oi-photo" sin media query),
     asi la sección tiene "proporciones normales" y el contenido real
     alcanza para no dejar un hueco. */
  .page-exposiciones .obra-oi-card, .page-exposiciones .obra-oi-photo {
    flex: 0 0 340px;
    width: 340px;
  }
  .page-exposiciones .obra-oi-photo { height: 245px; }
  /* align-items:stretch (no el flex-start de desktop): en columna, el eje
     cruzado es el ancho — con flex-start las columnas se encogían a 0
     porque su contenido (la foto, width:100%) depende del ancho del propio
     padre para tener alguno. */
  .obra-oi-detail-inner { flex-direction: column; align-items: stretch; }
  /* .obra-oi-fila.is-reverse (Cuarto Intermedio, fila 2) por especificidad
     le gana a la regla de arriba y se quedaría en row-reverse en mobile —
     se fuerza a columna acá también. */
  .obra-oi-fila.is-reverse { flex-direction: column; }
  .obra-oi-cuarto-intermedio { gap: var(--space-2xl); }
  /* .obra-oi-detail-right ya es alto natural por defecto (ver arriba); acá
     solo hace falta pisar el 480px máximo de la foto por un aspect-ratio
     fijo — el ajuste dinámico por JS (fitOiPhotoBox) no corre en mobile. */
  .obra-oi-detail-photo { height: auto; aspect-ratio: 4 / 3; }
  /* La regla desktop ".obra-oi-fila .obra-oi-detail-photo{height:320px}"
     tiene más especificidad que la de arriba y ganaría también acá — se
     repite con la misma especificidad (2 clases) para que gane el mobile. */
  .obra-oi-fila .obra-oi-detail-photo { height: auto; aspect-ratio: 4 / 3; }
  .obra-oi-nav-arrow { width: 32px; height: 32px; font-size: 16px; }

  /* Campamento Base: columna izquierda (fotos + CTA) pasa a ancho completo,
     apilada arriba del texto — sin margen lateral disponible para un layout
     de 2 columnas de 480px + resto. */
  .camp-header { flex-direction: column; }
  .camp-left { flex: 0 0 auto; max-width: 100%; }

  /* Tropismes: filas a columna, foto siempre arriba del texto, texto
     siempre alineado a la izquierda (el align-end del bloque 2 no tiene
     sentido apilado en una sola columna angosta). */
  .tropismes-row { flex-direction: column; gap: var(--space-sm); margin-top: var(--space-lg); }
  .tropismes-photo { flex: 0 0 auto; width: 100%; max-width: 100%; }
  .tropismes-row.is-reverse .tropismes-text {
    align-items: flex-start;
    text-align: left;
  }
  .tropismes-row.is-reverse .tropismes-quote {
    padding-left: var(--space-md);
    padding-right: 0;
    border-left: 2px solid var(--accent);
    border-right: none;
  }
  /* Pedido explícito: la 2ª foto (bloque 2, is-reverse) va debajo de la
     flechita que despliega el francés del bloque 1 — visualmente, el panel
     de francés colapsado no ocupa lugar (max-height:0), así que lo último
     que se ve del bloque 1 es esa flechita; con esta fila en DOM
     texto→foto, column-reverse pone la foto PRIMERO, justo a continuación
     del bloque 1, en vez de al final de su propio bloque de texto. */
  .tropismes-row.is-reverse { flex-direction: column-reverse; }
  /* Formula de 5-por-pantalla de escritorio no aplica en mobile (darían
     tiles ilegibles) — tamaño fijo, más chico, con la misma proporción. */
  .tropismes-strip-tile { width: 150px; aspect-ratio: 10 / 7; }

  /* About: filas a columna, foto siempre arriba del texto (is-reverse no
     invierte en mobile, mismo criterio que .tropismes-row/.caracol-row),
     texto siempre alineado a la izquierda, fotos a ancho completo con la
     proporción real de su caja de escritorio en vez del tamaño fijo en px. */
  /* Pedido explícito: títulos de subsección más chicos — mismo tamaño que
     el resto de los H1 interiores en mobile (.obra-page-title). */
  .about-title { font-size: 28px; }
  .about-row { flex-direction: column; gap: var(--space-sm); }
  .about-row + .about-row { margin-top: var(--space-lg); }
  .about-section + .about-section { margin-top: var(--space-lg); padding-top: var(--space-lg); }
  .about-photo--portrait, .about-photo--portrait-sm, .about-photo--wide { flex: 0 0 auto; width: 100%; max-width: 100%; height: auto; }
  .about-photo--portrait { aspect-ratio: 447 / 680; }
  .about-photo--portrait-sm { aspect-ratio: 560 / 315; }
  .about-photo--wide { aspect-ratio: 560 / 315; }
  .about-photo img { height: auto; }
  /* Bug real, no solo de tamaño: .about-text hereda de escritorio
     "flex: 0 1 480px" — un flex-basis pensado como ANCHO ahí (fila en
     row). Acá .about-row pasa a flex-direction:column (más arriba en este
     mismo bloque), así que el eje principal es vertical y ese mismo 480
     se aplica como ALTO del bloque de texto. Con párrafos largos (Sobre
     Josefina) el contenido real ya supera 480px y no se nota, pero con un
     solo párrafo corto (Sobre su obra, Sobre su práctica) el texto queda
     en una caja fantasma de 480px con aire vacío debajo — eso es lo que
     alejaba a "Sobre su práctica" del párrafo de "Sobre su obra", y a la
     foto de "Sobre su práctica" de su propio texto. Se resetea el flex
     completo, mismo criterio que el reseteo de "flex" en .obra-oi-card
     (ver ese comentario más arriba). */
  .about-text { flex: 0 1 auto; max-width: 100%; }
  .about-row.is-reverse .about-text { text-align: left; }

  /* Fotos de Sobre Josefina: mismo tamaño que el resto de la sección
     (Sobre su obra / Sobre su práctica, ambas 560:315 a ancho completo) —
     pedido explícito, reemplaza el achicado + aspect-ratio propio de
     pasadas anteriores (60%/240px centrado, luego 3:2). Las fotos nuevas
     (sobrejosefina3/1, ver about.html) ya son horizontales de por sí, así
     que igualar el aspect-ratio no las recorta de más — "contain" se
     mantiene por las dudas (sigue sin cortar a la persona si alguna vez se
     cambia por una foto menos horizontal que 560:315). */
  #sobre-josefina .about-photo--portrait {
    aspect-ratio: 560 / 315;
  }
  #sobre-josefina .about-photo--portrait img { object-fit: contain; }

  /* Pedido explícito: la 2ª foto de Sobre Josefina (fila is-reverse) va
     PRIMERO, quedando entre el párrafo 3 (fin de la fila 1) y el párrafo 4
     (inicio del texto de la fila 2) — y la foto de Sobre su práctica
     (misma clase is-reverse) también va primero, con el texto debajo.
     column-reverse invierte el orden VISUAL sin tocar el DOM (que sigue
     siendo texto,foto — eso es lo que necesita escritorio, donde si
     importa el orden real de los hijos para decidir qué lado queda cada
     uno, ver comentario de .about-row de más arriba). */
  .about-row.is-reverse { flex-direction: column-reverse; }

  /* Buscador: modal a ancho completo (menos margen), pegado más arriba —
     70vh de alto máximo en desktop deja poco lugar en pantallas chicas. */
  .search-overlay { padding-top: 8vh; padding-left: var(--space-sm); padding-right: var(--space-sm); }
  .search-modal { width: 100%; max-height: 80vh; }
  .search-input-row { padding: var(--space-sm) var(--space-md); }
  .search-result { padding: var(--space-xs) var(--space-md); }
}
