/* ==========================================================================
   grid.css — UI-2
   #gallery (mosaic | justified | columns) and .tile
   Consumes tokens from tokens.css (UI-1). --space-fluid-md keeps the photo
   wall's side margins matching the header/toolbar/footer above it.
   ========================================================================== */

#gallery {
  padding-inline: var(--space-fluid-md, 2rem);
  padding-block-start: var(--space-5, 1.5rem);
  padding-block-end: var(--space-5, 1.5rem);
}

/* ==========================================================================
   Layout 1 — mosaic (flagship)
   CSS grid, dense auto-flow, JS-assigned --col/--row spans on a fixed
   fine-grained row unit. --mcols drives both the track count and the
   clamp ceiling from a single place per breakpoint.
   ========================================================================== */

#gallery[data-layout="mosaic"] {
  --mcols: 2;
  display: grid;
  grid-template-columns: repeat(var(--mcols), 1fr);
  grid-auto-rows: 2.2rem;
  grid-auto-flow: dense;
  gap: var(--space-2, .5rem);
}
@media (min-width: 48rem) {
  #gallery[data-layout="mosaic"] { --mcols: 3; grid-auto-rows: 2.6rem; gap: var(--space-3, .75rem); }
}
@media (min-width: 64rem) {
  #gallery[data-layout="mosaic"] { --mcols: 4; grid-auto-rows: 3rem; gap: var(--space-4, 1rem); }
}
@media (min-width: 90rem) {
  #gallery[data-layout="mosaic"] { --mcols: 6; grid-auto-rows: 3.4rem; }
}

#gallery[data-layout="mosaic"] .tile {
  /* clamp so an oversized favorite can never exceed the current track
     count — min() against --mcols, redefined per breakpoint above */
  grid-column: span min(var(--col, 1), var(--mcols));
  grid-row: span min(var(--row, 1), 6);
}

/* ==========================================================================
   Layout 2 — justified
   Flexbox rows: fixed row height + flex-grow proportional to --ar gives
   each tile its true aspect ratio while every row fills edge to edge.
   (The final row fills too, by the nature of flex-grow — an accepted,
   deliberate trait of the pure-CSS technique; see report.)
   ========================================================================== */

#gallery[data-layout="justified"] {
  --jrow: 11rem;
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  gap: var(--space-2, .5rem);
}
@media (min-width: 48rem) {
  #gallery[data-layout="justified"] { --jrow: 13rem; gap: var(--space-3, .75rem); }
}
@media (min-width: 64rem) {
  #gallery[data-layout="justified"] { --jrow: 15rem; gap: var(--space-4, 1rem); }
}
@media (min-width: 90rem) {
  #gallery[data-layout="justified"] { --jrow: 17rem; }
}

#gallery[data-layout="justified"] .tile {
  height: var(--jrow, 11rem);
  flex-grow: var(--ar, 1.5);
  flex-shrink: 1;
  flex-basis: calc(var(--ar, 1.5) * var(--jrow, 11rem));
}

/* ==========================================================================
   Layout 3 — columns
   CSS multi-column masonry: natural aspect ratio via aspect-ratio, no
   grid math needed at all. --ccols mirrors the mosaic breakpoints for a
   coherent column count when switching layouts.
   ========================================================================== */

#gallery[data-layout="columns"] {
  --ccols: 2;
  columns: var(--ccols);
  column-gap: var(--space-2, .5rem);
}
@media (min-width: 48rem) {
  #gallery[data-layout="columns"] { --ccols: 3; column-gap: var(--space-3, .75rem); }
}
@media (min-width: 64rem) {
  #gallery[data-layout="columns"] { --ccols: 4; column-gap: var(--space-4, 1rem); }
}
@media (min-width: 90rem) {
  #gallery[data-layout="columns"] { --ccols: 6; }
}

#gallery[data-layout="columns"] .tile {
  width: 100%;
  aspect-ratio: var(--ar, 1.5);
  break-inside: avoid;
  margin-bottom: var(--space-2, .5rem);
}
@media (min-width: 48rem) {
  #gallery[data-layout="columns"] .tile { margin-bottom: var(--space-3, .75rem); }
}
@media (min-width: 64rem) {
  #gallery[data-layout="columns"] .tile { margin-bottom: var(--space-4, 1rem); }
}

/* ==========================================================================
   .tile — shared across all three layouts
   figure.tile > button.tile__btn > img.tile__img + span.tile__veil + span.tile__badge
   ========================================================================== */

.tile {
  position: relative;
  margin: 0; /* figure has UA default margins — must zero */
  overflow: hidden;
  border-radius: 0; /* flush, printed-spread aesthetic — deliberate, not a token gap */
  background: var(--color-surface, #14110f);
  isolation: isolate;
  /* not covered by base.css's generic a/button transition list (this is a
     <figure>) — added so the .is-active ring below fades in/out instead
     of snapping when the lightbox opens/closes on a different tile */
  transition: box-shadow var(--dur-base, 300ms) var(--ease-out, cubic-bezier(.22, .61, .36, 1));

  /* perf at 639 tiles */
  contain: layout paint;
  content-visibility: auto;
  contain-intrinsic-size: 200px 220px;
}
@media (min-width: 48rem) {
  .tile { contain-intrinsic-size: 260px 240px; }
}
@media (min-width: 64rem) {
  .tile { contain-intrinsic-size: 320px 260px; }
}
@media (min-width: 90rem) {
  .tile { contain-intrinsic-size: 380px 280px; }
}

.tile.is-active {
  box-shadow: inset 0 0 0 .1875rem var(--color-accent, #cf9484);
}

/* -------------------------- button -------------------------- */

.tile__btn {
  appearance: none;
  display: block;
  position: relative;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: inherit;
  cursor: pointer;
}
/* base.css's generic :focus-visible halo is a non-inset box-shadow, which
   .tile's overflow:hidden would clip. Two INSET layers instead — the first
   listed paints on top, so this reads as a dark 3px gap then a bright 3px
   ring, same construction as the sitewide halo, just turned inward. Cinema
   tokens because the button sits directly on a photo, not the page surface. */
.tile__btn:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 3px var(--color-overlay, rgba(11, 9, 8, .72)),
              inset 0 0 0 6px var(--color-scrim-text, #f7f1e6);
}

/* -------------------------- image + blur-up -------------------------- */

.tile__img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;

  /* pre-load state: the 20px LQIP, scaled up to hide its blurred edges */
  filter: blur(var(--blur-lg, 28px)) saturate(1.05);
  transform: scale(1.08);
  opacity: .96;

  transition:
    filter var(--dur-cinematic, 900ms) var(--ease-out-expo, cubic-bezier(.16, 1, .3, 1)),
    transform var(--dur-cinematic, 900ms) var(--ease-out-expo, cubic-bezier(.16, 1, .3, 1)),
    opacity var(--dur-cinematic, 900ms) var(--ease-out-expo, cubic-bezier(.16, 1, .3, 1));
}

.tile.is-loaded .tile__img {
  filter: blur(0) saturate(1);
  transform: scale(1);
  opacity: 1;
}

/* -------------------------- veil (hover tint) -------------------------- */

.tile__veil {
  position: absolute;
  inset: 0;
  opacity: 0;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0) 45%, var(--color-overlay-soft, rgba(11, 9, 8, .45)) 100%);
  transition: opacity var(--dur-base, 300ms) var(--ease-out, cubic-bezier(.22, .61, .36, 1));
}

/* -------------------------- favorite badge -------------------------- */

/* the secondary "gold" accent — tokens.css names this exact use case
   (favorite badges, small icon fills) rather than the primary rose accent */
.tile[data-favorite="true"] .tile__badge {
  position: absolute;
  top: .6rem;
  right: .6rem;
  width: .5rem;
  height: .5rem;
  border-radius: 50%;
  background: var(--color-accent-gold, #c2a26b);
  box-shadow: 0 0 0 .1875rem rgba(255, 255, 255, .55), 0 1px 3px rgba(0, 0, 0, .45);
}

/* ==========================================================================
   Hover — desktop only, restrained. Touch devices get none of this.
   Scoped to .is-loaded so a still-resolving placeholder never invites the
   zoom (avoids a visible snap between the load-scale and hover-scale too).
   ========================================================================== */
@media (hover: hover) {
  .tile:hover .tile__veil {
    opacity: 1;
  }
  .tile.is-loaded:hover .tile__img {
    /* spring-soft on the way in only — tokens.css calls out "tile hovers"
       by name for this curve. Hovering back off falls through to the base
       rule's ease-out-expo, a deliberate snap-to/settle-back asymmetry. */
    transition-timing-function: var(--ease-spring-soft, cubic-bezier(.3, 1.2, .4, 1));
    transform: scale(1.04);
    will-change: transform;
  }
  /* :not(.is-active) so this hairline never outranks the persistent
     "you are here" ring below on specificity when both would apply */
  .tile.is-loaded:hover:not(.is-active) {
    box-shadow: inset 0 0 0 1px var(--color-border, #332c26);
  }
}

/* ==========================================================================
   Reduced motion — kill the blur-up transition and the hover zoom;
   state changes still happen, just instantly.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .tile,
  .tile__img,
  .tile__veil {
    transition: none;
  }
  .tile.is-loaded:hover .tile__img {
    transform: none;
  }
}
