﻿/* ============================================================
   M&P — Galería de servicios

   Tres cosas que antes no se podían hacer sin librerías:

   1. <dialog> nativo. El navegador se encarga del foco, del Esc,
      del fondo inerte y del ::backdrop. Sin trampas de foco
      escritas a mano.
   2. View Transitions API. Al abrir una foto, la miniatura se
      transforma en la foto grande: el navegador interpola las
      dos posiciones. Donde no está soportada, entra con un
      fundido y listo.
   3. Animaciones guiadas por scroll (animation-timeline: view()).
      Las fotos aparecen al entrar en pantalla sin una sola línea
      de JavaScript ni IntersectionObserver.
   ============================================================ */

/* ---------- la grilla ---------- */
.gallery-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  grid-auto-rows: 190px;
  grid-auto-flow: dense;      /* rellena los huecos que dejan las piezas grandes */
  gap: 10px;

  /* galerías largas: el navegador no pinta lo que no se ve */
  content-visibility: auto;
  contain-intrinsic-size: 800px;
}

/* Ritmo del mosaico.

   Antes lo daba la posición: cada cuarta foto iba a doble alto y
   cada séptima a doble ancho, mirara lo que mirara. En las galerías
   de campo no se notaba porque hay fotos de todas las formas, pero
   en rental-tools son todas herramientas apaisadas sobre fondo
   blanco: a la primera, la quinta y la novena les tocaba una celda
   vertical y el "cover" les cortaba la herramienta por los costados.
   Se veía media cuña y no se entendía qué era.

   Ahora la forma de la celda la decide la foto, y la clase la pone
   galeria.js midiendo el archivo. La variedad sale de la mezcla real
   de formatos, que es la que hay que respetar.

   El doble ancho va con un mínimo de pantalla porque las columnas
   salen de auto-fill con minmax(220px, 1fr): abajo de esa medida
   entra una sola columna, y un elemento que pide dos obliga a la
   grilla a inventar una segunda columna implícita. En el celular
   eso partía la galería en una columna de 35 px y otra de 280, con
   la mitad de las fotos aplastadas contra el borde. */
@media (min-width: 560px) {
  .gallery-grid .g-item.g-panoramica { grid-column: span 2; }
}

/* Fotos verticales. La clase la pone galeria.js midiendo la
   proporción real de cada archivo.

   La celda pasa a doble alto, que la deja casi con la proporción
   de la foto. Con eso el recorte es mínimo y la miniatura llena
   el hueco.

   Antes probé "contain" para no recortar nada, pero dejaba bandas
   negras a los costados y quedaba peor que un recorte suave: una
   grilla de fotos tiene que ser un mosaico lleno. Lo que no se
   recorta es la foto grande, que es donde se mira en serio. */
.gallery-grid .g-item.g-vertical { grid-row: span 2; }

/* En hytorc.html hay siete miniaturas con g-contain escrito a mano
   en el HTML, de cuando la grilla era clara. style.css les pone
   "contain" sobre un plano oscuro, que es de donde salen las
   bandas negras a los costados. Se las devuelve al mismo criterio
   que las demás: llenan la celda. */
.gallery-grid .g-item.g-contain img {
  object-fit: cover;
  background: transparent;
}


.gallery-grid .g-item {
  position: relative;
  margin: 0;
  overflow: hidden;
  border-radius: 4px;
  background: #0E141D;
  cursor: zoom-in;
  isolation: isolate;
}

.gallery-grid .g-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform .6s cubic-bezier(.22,.61,.36,1), filter .4s ease;
}

/* velo inferior para el pie */
.gallery-grid .g-item::after {
  content: '';
  position: absolute;
  inset: auto 0 0 0;
  height: 55%;
  background: linear-gradient(180deg, rgba(18,23,31,0), rgba(18,23,31,.86));
  opacity: 0;
  transition: opacity .35s ease;
  pointer-events: none;
}

.gallery-grid .g-item:hover img,
.gallery-grid .g-item:focus-visible img { transform: scale(1.06); }
.gallery-grid .g-item:hover::after,
.gallery-grid .g-item:focus-visible::after { opacity: 1; }

.gallery-grid .g-caption {
  position: absolute;
  left: 14px; right: 14px; bottom: 12px;
  z-index: 2;
  color: #EEF2F6;
  font-size: .82rem;
  line-height: 1.35;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity .35s ease, transform .35s ease;
}

.gallery-grid .g-item:hover .g-caption,
.gallery-grid .g-item:focus-visible .g-caption { opacity: 1; transform: none; }

/* la lupa */
.gallery-grid .g-zoom {
  position: absolute;
  top: 10px; right: 10px;
  z-index: 2;
  width: 30px; height: 30px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: rgba(255,255,255,.92);
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity .3s ease, transform .3s ease;
}

.gallery-grid .g-item:hover .g-zoom,
.gallery-grid .g-item:focus-visible .g-zoom { opacity: 1; transform: none; }
.gallery-grid .g-zoom svg { width: 15px; height: 15px; }

/* ---------- aparición guiada por el scroll ----------
   Sin JS. Donde no hay soporte, las fotos simplemente ya están. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .gallery-grid .g-item {
      animation: entraFoto linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 46%;
    }
    @keyframes entraFoto {
      from { opacity: 0; transform: translateY(28px) scale(.97); }
      to   { opacity: 1; transform: none; }
    }
  }
}

/* ============================================================
   El visor
   ============================================================ */
.visor {
  border: 0;
  padding: 0;
  background: transparent;
  max-width: 100vw;
  max-height: 100vh;
  width: 100%;
  height: 100%;
  overflow: hidden;
  color: #EEF2F6;
}

.visor::backdrop {
  background: rgba(14, 19, 26, .93);
  backdrop-filter: blur(6px);
}

.visor-figura {
  margin: 0;
  height: 100%;
  display: grid;
  /* minmax(0, 1fr) y no 1fr a secas.

     Un track "1fr" arrastra un mínimo automático igual al
     min-content de lo que tiene adentro, que para una foto es su
     alto real. Con una foto vertical de 1032 px la fila medía
     1032 px dentro de un visor de 800: la foto salía ampliada,
     descentrada y con el pie fuera de pantalla. El max-height de
     la imagen nunca llegaba a aplicar porque el porcentaje se
     resolvía contra una fila que ya se había estirado.

     Poniendo el mínimo en cero la fila puede achicarse y recién
     ahí la foto se ajusta al alto disponible. */
  grid-template-rows: minmax(0, 1fr) auto;
  place-items: center;
  padding: clamp(16px, 4vh, 52px) clamp(16px, 6vw, 96px) clamp(16px, 3vh, 34px);
}

/* galeria.js envuelve la foto del visor en un <picture> para poder
   ofrecer AVIF con respaldo en JPG. display: contents hace que ese
   envoltorio no arme caja: la <img> sigue siendo hija directa de la
   grilla y el minmax(0, 1fr) de arriba la sigue midiendo igual.

   El <source> tiene que ir oculto a mano. Adentro de un <picture>
   normal no se dibuja nunca, pero con display: contents el envoltorio
   desaparece y el <source> pasa a ser una celda más de la grilla: una
   celda vacía que se quedaba con la fila de 1fr y empujaba la foto a
   una fila auto, que no la limita. Por eso la imagen aparecía abajo,
   descentrada, y las chicas quedaban perdidas en el medio. */
.visor-figura > picture { display: contents; }
.visor-figura > picture > source { display: none; }

.visor-figura img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  min-height: 0;
  object-fit: contain;
  border-radius: 3px;
  display: block;
}

.visor-pie {
  padding-top: 16px;
  text-align: center;
  max-width: 62ch;
  font-size: .92rem;
  color: #B7C3D0;
  line-height: 1.5;
}

.visor-contador {
  display: block;
  font-family: var(--font-mono, monospace);
  font-size: .7rem;
  letter-spacing: .18em;
  color: #6E7E90;
  margin-bottom: 8px;
}

/* botones */
.visor button {
  position: absolute;
  z-index: 3;
  display: grid;
  place-items: center;
  width: 46px; height: 46px;
  border: 1px solid rgba(255,255,255,.18);
  border-radius: 50%;
  background: rgba(12,17,25,.72);
  color: #fff;
  cursor: pointer;
  transition: background .25s ease, border-color .25s ease, transform .25s ease;
}

.visor button:hover { background: rgba(179,40,45,.9); border-color: transparent; }
.visor button:active { transform: scale(.94); }
.visor button svg { width: 20px; height: 20px; }

.visor-cerrar { top: 18px; right: 18px; }
.visor-prev { left: 18px; top: 50%; transform: translateY(-50%); }
.visor-next { right: 18px; top: 50%; transform: translateY(-50%); }
.visor-prev:active, .visor-next:active { transform: translateY(-50%) scale(.94); }

/* ---------- la transición entre miniatura y visor ---------- */
::view-transition-old(foto-activa),
::view-transition-new(foto-activa) {
  animation-duration: .42s;
  animation-timing-function: cubic-bezier(.22,.61,.36,1);
}

/* respaldo donde no hay View Transitions */
@media (prefers-reduced-motion: no-preference) {
  .visor[open] .visor-figura img { animation: visorEntra .3s ease both; }
  @keyframes visorEntra { from { opacity: 0; transform: scale(.985); } to { opacity: 1; transform: none; } }
}

@media (max-width: 720px) {
  .gallery-grid { grid-auto-rows: 150px; gap: 8px; }
  /* En el celular la grilla tiene dos columnas, así que una
     panorámica a doble ancho ocupa el renglón entero. Es justo lo
     que necesita una foto de 3 a 1 para que se entienda. */
  .visor-prev { left: 10px; }
  .visor-next { right: 10px; }
  .visor button { width: 40px; height: 40px; }
}

@media (prefers-reduced-motion: reduce) {
  .gallery-grid .g-item img,
  .gallery-grid .g-caption,
  .gallery-grid .g-zoom { transition: none; }
  ::view-transition-old(foto-activa),
  ::view-transition-new(foto-activa) { animation: none; }
}

/* ============================================================
   Las miniaturas

   Antes cada miniatura descargaba la foto original entera. Ahora
   cada figura trae dos versiones en WebP y el navegador elige la
   que corresponde a su ancho y a su densidad de pantalla.

   Mientras la foto viaja, el hueco se pinta con el color medio de
   esa misma foto: no hay salto de layout ni rectángulos grises.
   ============================================================ */

.gallery-grid .g-item {
  background-color: var(--lqip, #0E141D);
}

.gallery-grid .g-item picture {
  display: block;
  width: 100%;
  height: 100%;
}

/* entrada de la foto cuando termina de decodificarse */
.gallery-grid .g-item img {
  opacity: 0;
  transition: opacity .5s ease, transform .6s cubic-bezier(.22,.61,.36,1), filter .4s ease;
}

.gallery-grid .g-item img.lista,
.no-js .gallery-grid .g-item img { opacity: 1; }

/* La grilla se mide contra su contenedor, no contra la ventana:
   si mañana la galería va en una columna angosta, se adapta sola. */
.gallery-seccion { container-type: inline-size; }

@container (max-width: 700px) {
  .gallery-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
}

/* Al pasar el mouse por una foto, las demás bajan un punto.
   Se resuelve con :has(), sin tocar una clase desde JavaScript. */
@media (hover: hover) {
  .gallery-grid:has(.g-item:hover) .g-item:not(:hover) img {
    filter: brightness(.62) saturate(.85);
  }
}

/* el color medio también tiñe el fondo del visor mientras carga */
.visor-figura img { background-color: var(--lqip, transparent); }

/* ============================================================
   VARIANTE CINTA — se activa agregando g-cinta al contenedor:
   <div class="gallery-grid g-cinta">

   El mosaico de arriba muestra todo de un vistazo, pero deja cada
   foto chica. La cinta hace lo contrario: una foto grande por vez,
   recorridas de costado. Con galerías de tres a cinco fotos, que
   es lo que hay en estas páginas, el mosaico desperdicia media
   pantalla.

   El visor no cambia en nada. El morph de foto-activa que ya hace
   galeria.js sigue funcionando igual, porque no depende de cómo
   estén acomodadas las miniaturas: solo necesita que UNA lleve el
   nombre de transición al abrir. Esta variante es puro layout.

   Es opt-in a propósito. Sacando la clase del HTML, la galería
   vuelve al mosaico sin tocar una línea de CSS ni de JS.

   Va al final del archivo porque las páginas de servicios traen su
   propio bloque <style> con reglas para .gallery-grid, y ese
   bloque va después del <link> a esta hoja. Con dos clases
   (.gallery-grid.g-cinta) le gana por especificidad sin importar
   el orden. Lo que queda de aquel bloque —grid-column, grid-row,
   grid-auto-rows— no molesta: en un contenedor flex es inerte.
   ============================================================ */

.gallery-grid.g-cinta {
  /* Qué fracción del ancho ocupa cada foto. Se usa una sola vez,
     abajo, en el padding: el ancho del ítem sale solo. */
  --cinta-frac: .62;

  display: flex;
  gap: 14px;

  /* El mosaico usa content-visibility para no pintar lo que está
     abajo de todo. En una cinta horizontal es contraproducente: el
     navegador necesita medir los ítems para saber cuánto scroll
     hay a los costados. */
  content-visibility: visible;
  contain-intrinsic-size: none;

  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;

  /* Espacio a los costados para que la primera y la última también
     puedan quedar centradas al calzar.

     Este padding hace doble trabajo, y conviene saberlo antes de
     tocarlo: un porcentaje de padding se resuelve contra el ancho
     del PADRE, así que deja el área de contenido de la cinta
     valiendo exactamente --cinta-frac del ancho total. */
  padding-inline: max(0px, calc((100% - 100% * var(--cinta-frac)) / 2));

  /* Los bordes se desvanecen: avisa que hay más cinta al costado
     sin dibujar una sombra que después habría que adaptar al tema
     claro y al oscuro. */
  mask-image: linear-gradient(to right, transparent 0, #000 4%, #000 96%, transparent 100%);
}
.gallery-grid.g-cinta::-webkit-scrollbar { display: none; }

.gallery-grid.g-cinta .g-item {
  /* 100%, no --cinta-frac.

     Un flex-basis en porcentaje se resuelve contra el área de
     CONTENIDO del contenedor flex, que el padding de arriba ya
     dejó valiendo --cinta-frac del ancho total. Poner acá la
     fracción otra vez la aplicaría dos veces: con .62 la foto
     terminaba midiendo el 38% y quedaba una estampilla. */
  flex: 0 0 100%;
  block-size: 420px;
  scroll-snap-align: center;
  /* Sin esto, al frenar el gesto entre dos fotos queda una a medio
     camino. */
  scroll-snap-stop: always;

  /* Convierte cada foto en contenedor de estado de scroll, para
     poder preguntar más abajo si es la que quedó calzada. A
     diferencia de container-type: inline-size, este no aplica
     contención de layout, así que no interfiere con view(). */
  container-type: scroll-state;
}

/* En pantalla angosta la foto ocupa casi todo el ancho: con 62%
   quedarían tres pedazos de foto y ninguna entera. */
@media (max-width: 720px) {
  /* Basta con mover la fracción: el padding la lee y el ancho del
     ítem sale de ahí. */
  .gallery-grid.g-cinta { --cinta-frac: .84; }
  .gallery-grid.g-cinta .g-item { block-size: 300px; }
}

.gallery-grid.g-cinta:focus-visible {
  outline: 3px solid var(--rojo, #b3282d);
  outline-offset: 4px;
}

/* ---------- la foto crece al acercarse al centro ----------

   El reloj de esta animación no es el tiempo: es la posición de la
   foto dentro de la cinta, así que sigue al dedo sin que haya nada
   que sincronizar. El 50% de la animación es el centro exacto.

   La detección incluye (animation-range: entry) a propósito: hay
   navegadores con soporte parcial que dicen que sí a
   animation-timeline y después ignoran los rangos. */
@supports ((animation-timeline: view()) and (animation-range: entry)) {
  @media (prefers-reduced-motion: no-preference) {
    .gallery-grid.g-cinta .g-item {
      animation: mp-cinta-foco auto linear both;
      /* Después del atajo `animation`, nunca antes: el atajo
         reinicia animation-timeline si va después. */
      animation-timeline: view(inline);
    }
  }
}

@keyframes mp-cinta-foco {
  0%   { scale: .9; filter: saturate(.5) brightness(.75); }
  50%  { scale: 1;  filter: none; }
  100% { scale: .9; filter: saturate(.5) brightness(.75); }
}

/* ---------- énfasis de la foto calzada ----------

   Complementa lo de arriba donde existe: aquella animación es
   continua y sigue al gesto, esto marca el estado final una vez
   que el scroll se asentó. Donde no hay scroll-driven animations
   (Firefox), esta consulta es lo único que da el énfasis.

   Ojo con el alcance: una consulta de contenedor estila
   DESCENDIENTES, así que acá se tocan la lupa y el pie, no el
   .g-item, que es el contenedor mismo. */
@media (prefers-reduced-motion: no-preference) {
  @container scroll-state(snapped: x) {
    /* Los selectores van completos, no `.g-zoom` a secas.

       Una consulta de contenedor NO aporta especificidad: solo
       decide si la regla se evalúa. Adentro sigue compitiendo con
       todo lo demás por las reglas normales, y las bases de acá
       arriba son `.gallery-grid .g-zoom` (0,2,0). Escrito corto,
       `.g-zoom` da (0,1,0) y pierde: la consulta se cumplía, la
       regla se aplicaba, y aun así la lupa quedaba invisible.

       Llevan .g-item en el medio, sumando cuatro clases (0,4,0),
       por una segunda razón: rental-tools trae en su style propio
       `.gallery-grid .g-item .g-caption` con opacity 0, que es
       (0,3,0). Con tres clases quedaba empatada y el bloque de la
       página, al ir después, ganaba por orden. */
    .gallery-grid.g-cinta .g-item .g-zoom { opacity: 1; transform: none; }
    .gallery-grid.g-cinta .g-item .g-caption { opacity: 1; transform: none; }
  }
}

@media (prefers-reduced-motion: reduce) {
  .gallery-grid.g-cinta .g-item {
    animation: none !important;
    scale: 1 !important;
    filter: none !important;
  }
}

/* ---------- puntos que genera el navegador ----------

   Sin marcado y sin scripts: ::scroll-marker-group arma la
   paginación con un punto por foto y marca el actual con
   :target-current.

   Dentro de @supports selector(): donde no exista no se genera
   nada y la cinta se sigue usando a dedo, con la rueda o con el
   teclado. Es puro extra. */
@supports selector(::scroll-marker-group) {
  .gallery-grid.g-cinta { scroll-marker-group: after; }

  .gallery-grid.g-cinta::scroll-marker-group {
    display: flex;
    justify-content: center;
    gap: 8px;
    padding-block-start: 16px;
  }

  .gallery-grid.g-cinta .g-item::scroll-marker {
    content: "";
    inline-size: 8px;
    block-size: 8px;
    border-radius: 50%;
    background: var(--gris-linea, #dcdcdc);
    cursor: pointer;
    transition: background-color .16s ease, inline-size .16s ease;
  }

  .gallery-grid.g-cinta .g-item::scroll-marker:target-current {
    inline-size: 24px;
    border-radius: 4px;
    background: var(--rojo, #b3282d);
  }

  .gallery-grid.g-cinta .g-item::scroll-marker:focus-visible {
    outline: 3px solid var(--rojo, #b3282d);
    outline-offset: 3px;
  }
}

/* En pantalla ancha la cinta mostraba una foto y una rebanada de la
   siguiente, y la sección parecía una sola imagen. Con la mitad del
   ancho entran dos enteras y se entiende que hay más para recorrer.
   Sigue con snap al centro, así que la foto calzada queda en el
   medio y las dos vecinas asoman a los costados. */
@media (min-width: 1180px) {
  .gallery-grid.g-cinta { --cinta-frac: .5; }
}
