/* style.css's own html/body overflow:hidden is now scoped to <html class="feed"> (index.html
   only) precisely so grid.html doesn't inherit it — this used to be a real bug: overriding just
   body wasn't enough on its own, since body's overflow only becomes the page's actual scrolling
   behavior when html's own overflow is the default `visible` (CSS overflow-propagation rule).
   This explicit height/overflow is now redundant with that upstream fix (html here was never
   `hidden` to begin with), but kept as a belt-and-suspenders local guarantee — inexpensive,
   and it's the thing that was actually verified working in production. */
html { overflow-y: auto; overflow-x: hidden; height: auto; }
/* `overflow: auto` here used to make body a scroll container of its own, nested inside the
   html one above — a second, sideways-scrollable surface that a horizontal fav/hide swipe could
   grab instead of the gesture ever reaching JS. The page has exactly one scroller (html), so
   body leaves overflow alone; touch-action:pan-y is what keeps a horizontal touch out of the
   scroller in the first place. */
.grid-body {
  touch-action: pan-y; height: auto;
}
/* Nothing on this page is draggable content — see cellFor() in grid.js. */
.cell img, .cell video, a.cell { -webkit-user-drag: none; user-drag: none; }
/* Stacked view is where the horizontal fav/hide swipe lives, so a horizontal touch there must
   belong to the gesture outright, never to any scrolling. */
body.stacked .cell { touch-action: pan-y; }
#gridBar {
  position: sticky; top: 0; z-index: 5;
  display: flex; align-items: center; gap: 12px;
  padding: 8px 10px; background: rgba(0,0,0,0.85);
  font-size: 13px; border-bottom: 1px solid #333;
}
#gridCount { color: #888; margin-left: auto; }
#gridBar .btn { min-width: 38px; padding: 4px 8px; min-height: 34px; font-size: 16px; line-height: 1; }
#gridBar .btn:disabled { opacity: 0.3; }
#gridWrap { padding: 8px; padding-bottom: 60px; }
#gridEl {
  display: grid;
  /* --cols is driven by the pinch/zoom controls in grid.js */
  grid-template-columns: repeat(var(--cols, 3), 1fr);
  gap: 4px;
}
.cell {
  position: relative; aspect-ratio: 1 / 1; background: #111;
  overflow: hidden; border-radius: 4px;
  /* skip layout/paint work for offscreen cells so a full page of
     thumbnails stays cheap even at 200/page */
  content-visibility: auto;
  contain-intrinsic-size: 200px 200px;
}
.cell img, .cell video { width: 100%; height: 100%; object-fit: cover; display: block; background: #1a1a1a; will-change: transform; }

/* Single-column "stacked" view: full-size media at its own aspect ratio, not square-cropped —
   the point of zooming all the way in is to actually see the whole image. */
body.stacked #gridEl { gap: 10px; }
body.stacked .cell {
  aspect-ratio: auto;
  contain-intrinsic-size: auto 600px;
  border-radius: 6px;
  /* Reserve height before the image arrives. Without it an unloaded tile is 0px tall, so the
     page height lurches as each one loads and the scroll position drifts under the reader.
     Matches the loaded media's own max-height (below) rather than guessing something smaller —
     50vh undershot almost every real image, so the tile visibly grew from half-screen to full
     the moment it loaded instead of just being there already. A shorter-than-reserved landscape
     image settling into less space reads as far less janky than a tile visibly growing. */
  min-height: 90vh;
}
body.stacked .cell img, body.stacked .cell video {
  height: auto; max-height: 90vh; object-fit: contain;
}
.cell.vid::after {
  content: '▶'; position: absolute; right: 4px; bottom: 4px;
  font-size: 13px; color: #fff; text-shadow: 0 1px 3px rgba(0,0,0,0.9);
}
/* Brief acknowledgement for a fav-swipe in stacked view — same ★ the feed bursts on favouriting,
   just anchored to the tile itself rather than the centre of the screen. */
.cell.fav-flash::before {
  content: '★'; position: absolute; inset: 0; z-index: 1;
  display: flex; align-items: center; justify-content: center;
  font-size: 15vh; color: #fff; background: rgba(0,0,0,0.25);
  animation: cellfavburst 400ms ease-out;
  pointer-events: none;
}
@keyframes cellfavburst {
  0% { opacity: 0; transform: scale(0.6); }
  35% { opacity: 1; transform: scale(1.1); }
  100% { opacity: 0; transform: scale(1.25); }
}
/* A restore that the server refused (usually: something else now owns that path). Reuses the
   fav-flash animation, in red and with a ✕, so a failed double-tap is visibly not a no-op. */
.cell.untrash-failed::before {
  content: '✕'; position: absolute; inset: 0; z-index: 1;
  display: flex; align-items: center; justify-content: center;
  font-size: 15vh; color: #ff6b6b; background: rgba(0,0,0,0.35);
  animation: cellfavburst 400ms ease-out;
  pointer-events: none;
}
/* Trash tiles have no href (they navigate nowhere) — keep the pointer cursor anyway, since a
   double-tap on them does do something. */
body.trash .cell { cursor: pointer; }
#gridHint { color: #888; font-size: 13px; padding: 20px 4px; text-align: center; }
#gridHint[hidden] { display: none; }
/* Watched by the infinite scroller — sits below the grid so it comes into view slightly
   before the user reaches the bottom, giving the next page time to load. */
#gridSentinel { height: 1px; }
/* Same idea, above the grid — triggers restoring pruned pages when scrolling back up. */
#gridTopSentinel { height: 1px; }
