/* ==========================================================================
   Playground — infinite-scroll masonry feed
   Grid replicated 1:1 from shedsgns.me/infinity (.infinity-feed*)
   Flex columns · framed tiles (padded bg + dual radius) · gap 6 · rise-in
   ========================================================================== */

:root {
  --muted: rgba(255, 255, 255, 0.64);
  --muted-soft: rgba(255, 255, 255, 0.54);
}

/* --- backdrop that fades in behind the rising feed --- */
.infinity-feed-backdrop {
  z-index: 0;
  background: linear-gradient(180deg, #0f0f0ffa, #0a0a0a), var(--page-bg);
  pointer-events: none;
  animation: infinity-backdrop-fade 0.22s backwards;
  position: fixed;
  inset: 0;
}

@keyframes infinity-backdrop-fade {
  0% {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* --- feed shell --- */
.infinity-feed {
  z-index: 1;
  position: relative;
  min-height: 100vh;
  background: var(--page-bg);
  padding: 0;
}

/* --- grid = flex row of equal columns --- */
.infinity-feed-grid {
  display: flex;
  gap: 6px;
  width: 100%;
}

.infinity-feed-col {
  display: flex;
  flex-direction: column;
  flex: 1;
  gap: 6px;
  min-width: 0;
}

/* --- tile = the work itself, no frame (bare image, rounded) --- */
.infinity-feed-tile {
  position: relative;
  isolation: isolate;
  width: 100%;
  margin: 0;
  padding: 0;
  border-radius: 12px;
  overflow: hidden;
  pointer-events: none;
  contain: paint;
}

/* Fluid: every work keeps its own aspect ratio at any column width.
   `--tile-aspect` is set inline per tile from the work's natural w/h,
   so nothing squishes on resize and space is reserved before load. */
.infinity-feed-tile img,
.infinity-feed-tile svg,
.infinity-feed-tile video {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: var(--tile-aspect, 4 / 3);
  object-fit: cover;
  border-radius: 12px;
}

.infinity-feed-tile video {
  background: #050505;
}

.infinity-feed-lottie-frame {
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 100%;
  height: auto;
  aspect-ratio: var(--tile-aspect, 1914 / 1632);
  padding: clamp(18px, 9%, 56px);
  background: #000000;
  border-radius: 12px;
}

.infinity-feed-lottie {
  width: 100%;
  height: 100%;
}

.infinity-feed-lottie svg {
  display: block;
  width: 100% !important;
  height: 100% !important;
  max-width: 100%;
  max-height: 100%;
}

/* Soft edge treatment: the original image remains sharp, while this duplicate
   image layer blurs/fades only around the tile edges. The center stays clear
   and the tile geometry stays untouched, so the masonry estimates and infinite
   scroll behavior keep working exactly as before. */
.infinity-feed-tile::after {
  display: none;
}

.infinity-feed-tile--video::after {
  display: none;
}

.infinity-feed-tile--lottie::after {
  display: none;
}

@media (max-width: 960px), (max-height: 760px), (pointer: coarse), (prefers-reduced-motion: reduce) {
  .infinity-feed-tile::after {
    display: none;
  }
}

/* Tile reveal is COMPOSITOR-ONLY: opacity + translate, no blur.
   The shared `.reveal-on-scroll` (styles.css) animates `filter: blur(8px)→0`,
   which is fine for small text but on a 475px image tile forces a full
   main-thread repaint every frame of the transition. During real (momentum)
   trackpad scrolling the compositor then stalls waiting on that paint — the
   "scroll stops" bug. Opacity and transform are composited on the GPU with no
   per-frame paint, so the rise-in stays perfectly smooth. (Text pages keep the
   signature blur — this override is scoped to the feed.) */
.infinity-feed-tile.reveal-on-scroll {
  filter: none;
  transition:
    opacity 0.34s cubic-bezier(0.165, 0.84, 0.44, 1),
    transform 0.34s cubic-bezier(0.165, 0.84, 0.44, 1);
}
.infinity-feed-tile.reveal-on-scroll.is-revealed {
  filter: none;
}

/* After the reveal finishes, drop the compositor-heavy props (transform,
   will-change) so a long feed doesn't hold hundreds of GPU layers.
   NOTE: deliberately no `content-visibility: auto` here — it looks like a win
   but forces an on-demand layout+paint of a full image tile every time one
   re-enters the render margin during a scroll, which produces a ~33ms hitch
   per tile and is the real cause of the fling stutter. Measured: turning it
   off drops long frames from 18→1 over a 200-frame fling. Dropping the GPU
   layers here is enough to keep the feed light. Wins over
   .reveal-on-scroll.is-revealed by source order (loaded after styles.css). */
.infinity-feed-tile.is-settled {
  filter: none;
  transform: none;
  will-change: auto;
}

.infinity-feed-sentinel {
  width: 1px;
  height: 1px;
}

.playground-back-link {
  position: fixed;
  top: calc(48px + env(safe-area-inset-top, 0px));
  left: 50%;
  z-index: 20;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
  padding: 10px 16px;
  border: 0;
  border-radius: 56px;
  background-clip: padding-box;
  -webkit-background-clip: padding-box;
  background-color: #fff;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  box-shadow: var(--site-shadow);
  color: #000;
  font-size: 13px;
  line-height: 18px;
  font-weight: 500;
  letter-spacing: -0.2px;
  text-decoration: none;
  white-space: nowrap;
  transform: translateX(-50%);
}

.playground-mobile-menu-button {
  position: fixed;
  top: calc(24px + env(safe-area-inset-top, 0px));
  left: 50%;
  z-index: 70;
  box-sizing: border-box;
  display: none;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  padding: 0;
  border-radius: 50%;
  border: 0;
  background-clip: padding-box;
  -webkit-background-clip: padding-box;
      background-color: #fff;
      -webkit-backdrop-filter: none;
      backdrop-filter: none;
  box-shadow: var(--site-shadow);
  color: #000;
  cursor: pointer;
  transform: translateX(-50%);
}

.playground-mobile-menu-icon,
.playground-mobile-menu-icon::before,
.playground-mobile-menu-icon::after {
  display: block;
  width: 16px;
  height: 1.6px;
  border-radius: 999px;
  background: currentColor;
  transition: transform 0.2s var(--ease-out), opacity 0.2s var(--ease-out);
}

.playground-mobile-menu-icon {
  position: relative;
}

.playground-mobile-menu-icon::before,
.playground-mobile-menu-icon::after {
  content: "";
  position: absolute;
  left: 0;
}

.playground-mobile-menu-icon::before {
  top: -5px;
}

.playground-mobile-menu-icon::after {
  top: 5px;
}

.playground-mobile-menu-button[aria-expanded="true"] .playground-mobile-menu-icon {
  transform: rotate(45deg);
}

.playground-mobile-menu-button[aria-expanded="true"] .playground-mobile-menu-icon::before {
  transform: translateY(5px) rotate(90deg);
}

.playground-mobile-menu-button[aria-expanded="true"] .playground-mobile-menu-icon::after {
  opacity: 0;
}

    .playground-mobile-menu {
  position: fixed;
  inset: 0;
  z-index: 69;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100vw;
  height: 100dvh;
  padding: calc(104px + env(safe-area-inset-top, 0px)) 24px calc(40px + env(safe-area-inset-bottom, 0px));
  background: rgba(2, 3, 5, 0.82);
      -webkit-backdrop-filter: none;
      backdrop-filter: none;
  opacity: 0;
  transform: scale(1.02);
  overflow: hidden;
  overscroll-behavior: none;
  touch-action: none;
  pointer-events: none;
  transition: opacity 0.24s var(--ease-out), transform 0.24s var(--ease-out);
}

.playground-mobile-menu[hidden] {
  display: none;
}

.playground-mobile-menu.is-open {
  opacity: 1;
  transform: scale(1);
  pointer-events: auto;
}

.playground-mobile-menu a {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 40px;
  padding: 0 16px;
  border-radius: 999px;
  color: rgba(255, 255, 255, 0.72);
  font-size: 20px;
  line-height: 28px;
  font-weight: 400;
  letter-spacing: -0.3px;
  text-decoration: none;
  transition: color 0.18s var(--ease-out), background-color 0.18s var(--ease-out);
}

.playground-mobile-menu a:hover,
.playground-mobile-menu a:focus-visible {
  color: #fff;
  background: rgba(255, 255, 255, 0.08);
  outline: none;
}

html.playground-menu-lock,
body.playground-menu-lock {
  overflow: hidden;
  overscroll-behavior: none;
  touch-action: none;
}

/* --- mobile --- */
@media (max-width: 540px) {
  .infinity-feed {
    padding: 0;
  }

  .infinity-feed-grid {
    flex-direction: column;
    gap: 6px;
  }

  .infinity-feed-tile {
    padding: 0;
    border-radius: 10px;
  }

  .infinity-feed-tile img,
  .infinity-feed-tile svg,
  .infinity-feed-tile video {
    border-radius: 10px;
  }

  .infinity-feed-lottie-frame {
    padding: clamp(14px, 8%, 32px);
    border-radius: 10px;
  }

  .playground-back-link {
    display: none;
  }

}

@media (prefers-reduced-motion: reduce) {
  .infinity-feed,
  .infinity-feed-backdrop {
    animation: none;
  }
}
