/* components.css - GaMeZz World UI components (Poki/Playzen-style)
 *
 * Clean white surfaces. 1px borders. Chunky 16px radius on cards.
 * Generous whitespace. Single accent color used sparingly. No gradients,
 * no glow, no fancy ribbon. Just well-margined, well-aligned UI.
 */

/* ============================================================
   HEADER
   ============================================================ */

.gz-header {
  position: sticky;
  top: 0;
  z-index: 100;
  height: var(--header-height);
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
}

.gz-header-inner {
  width: 100%;
  max-width: var(--container-max);
  margin: 0 auto;
  padding: 0 var(--space-l);
  display: grid;
  grid-template-columns: auto auto 1fr auto;
  align-items: center;
  gap: var(--space-l);
}

@media (max-width: 768px) {
  .gz-header-inner {
    grid-template-columns: auto auto 1fr auto;
    gap: var(--space-s);
    padding: 0 var(--space-m);
  }
}

.gz-logo {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  /* The logo image is intrinsically 220×64. We size it responsively
     via the .gz-logo-img rules below — the wrapper itself just needs
     to align the image vertically in the header row. */
  line-height: 0;
}

.gz-logo:hover {
  filter: none;
}

.gz-logo-img {
  display: block;
  height: 44px;          /* desktop default */
  width: auto;
  /* The logo is drawn on a transparent background. The PNG already
     includes the gradient stroke + tagline, so we don't need any
     color/sizing tricks here. */
}

@media (max-width: 768px) {
  .gz-logo-img {
    height: 36px;
  }
}

@media (max-width: 480px) {
  .gz-logo-img {
    /* On phones the header is tight (hamburger + logo + search + theme);
       shrink the logo so the search input keeps its min width. */
    height: 28px;
  }
}

@media (max-width: 380px) {
  /* At 320-360px the 4-col header grid crowds the search input.
     Shrink the input height and let icon buttons sit tighter so the
     row stays one line and no horizontal scroll appears. */
  .gz-search-input {
    height: 36px;
    font-size: var(--fs-sm);
    padding: 0 36px 0 36px;
  }
  .gz-icon-btn {
    width: 36px;
    height: 36px;
  }
  .gz-search-icon,
  .gz-search-clear {
    transform: translateY(-50%) scale(0.9);
    transform-origin: left center;
  }
  .gz-search-clear {
    transform-origin: right center;
  }
}

/* SEARCH */
.gz-search {
  position: relative;
  max-width: 520px;
  width: 100%;
  margin: 0 auto;
}

.gz-search-input {
  width: 100%;
  height: 44px;
  padding: 0 44px 0 44px;
  background: var(--bg-subtle);
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  color: var(--text);
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  outline: none;
}

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

.gz-search-input:hover {
  background: var(--surface-2);
}

.gz-search-input:focus {
  background: var(--bg);
  border-color: var(--accent);
  box-shadow: 0 0 0 4px var(--accent-soft);
}

.gz-search-icon,
.gz-search-clear {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-muted);
  pointer-events: none;
}

.gz-search-icon {
  left: var(--space-m);
}

.gz-search-clear {
  right: var(--space-s);
  pointer-events: auto;
  width: 28px;
  height: 28px;
  display: none;
  place-items: center;
  border-radius: 50%;
  color: var(--text-muted);
}

.gz-search-clear.is-visible {
  display: grid;
}

.gz-search-clear:hover {
  background: var(--surface-2);
  color: var(--text);
}

/* HEADER ACTIONS */
.gz-actions {
  display: inline-flex;
  align-items: center;
  gap: var(--space-s);
}

.gz-icon-btn {
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border-radius: var(--radius-2);
  color: var(--text-muted);
  transition:
    background var(--motion-fast) var(--ease-out),
    color var(--motion-fast) var(--ease-out);
}

.gz-icon-btn:hover,
.gz-icon-btn:focus-visible {
  background: var(--surface-2);
  color: var(--text);
  outline: none;
}

.gz-icon-btn[aria-pressed="true"] {
  color: var(--accent);
}

.gz-sidebar-toggle {
  display: grid;
}

/* ============================================================
   LAYOUT (sidebar + main)
   ============================================================
   The sidebar is the first grid column by default — pinned open
   at all viewports, since the user keeps it visible while
   browsing. On narrow viewports (<768px) it collapses to a
   slide-in drawer triggered by the hamburger toggle. The
   `.is-open` class still works as a JS toggle on the narrow
   layout; on wide layout the sidebar is always shown.
*/

.gz-layout {
  display: grid;
  /* Default: sidebar is OPEN. The grid reserves 280px for it.
     The sidebar itself uses position:fixed and slides in/out via
     .is-open. When .is-open is REMOVED (user closes the drawer),
     this rule collapses to 0 so main takes full width. */
  grid-template-columns: 280px 1fr;
  gap: var(--space-l);
  max-width: var(--container-max);
  margin: 0;
  padding: var(--space-m);
  align-items: start;
  min-height: 100vh;
}

/* When the sidebar does NOT have .is-open (closed state), drop
   its track so main takes the full width. */
.gz-layout:not(:has(.gz-sidebar.is-open)) {
  grid-template-columns: 0 1fr;
  gap: 0;
}

@media (max-width: 767px) {
  .gz-layout {
    grid-template-columns: 1fr;
  }
}

/* The main column needs vertical padding only — horizontal is
   handled by .gz-layout's outer padding + gap. */
.gz-main {
  padding: var(--space-l) 0;
  min-width: 0;
  /* The .gz-sidebar is position:fixed so it doesn't occupy a grid
     cell. Without this, .gz-main would fall into column 1 (280px)
     and render at sidebar width. Explicitly span columns 2-end. */
  grid-column: 2 / -1;
  width: 100%;
  max-width: 100%;
}

@media (max-width: 768px) {
  .gz-main {
    padding: var(--space-m);
  }
}

@media (max-width: 768px) {
  .gz-layout {
    grid-template-columns: 1fr;
    padding: var(--space-m);
  }
}

/* ============================================================
   SIDEBAR
   ============================================================ */

.gz-sidebar {
  position: fixed;
  top: 0;
  left: 0;
  height: 100vh;
  width: 280px;
  max-width: 280px;
  background: var(--bg);
  border-right: 1px solid var(--border);
  overflow-y: auto;
  padding: var(--space-l) var(--space-m) var(--space-l);
  transform: translateX(-100%);
  transition: transform 200ms ease;
  z-index: 200;
  box-shadow: 4px 0 24px rgba(15, 19, 32, 0.08);
}

.gz-sidebar.is-open {
  transform: translateX(0);
}

@media (max-width: 767px) {
  .gz-sidebar {
    width: 50vw;
    max-width: 50vw;
  }
}

.gz-sidebar::-webkit-scrollbar {
  width: 6px;
}

.gz-sidebar::-webkit-scrollbar-thumb {
  background: var(--surface-2);
  border-radius: var(--radius-pill);
}

.gz-sidebar-heading {
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: var(--space-m) var(--space-m) var(--space-s);
  margin: 0;
  border-top: 1px solid var(--border);
  margin-top: var(--space-s);
  list-style: none;
}

.gz-sidebar-heading::marker { content: ""; }

/* Logo header at the top of the sidebar — fills the empty box the
   user pointed out. Sits above the category list. */
.gz-sidebar-logo {
  display: block;
  padding: var(--space-s) var(--space-s) var(--space-m);
  margin: 0;
  text-decoration: none;
  line-height: 0;
}

.gz-sidebar-logo-img {
  display: block;
  width: 100%;
  max-width: 220px;
  height: auto;
  object-fit: contain;
}

/* Sidebar burger toggle — sits directly below the logo, above the
   category list. Same behavior as the header burger (data-gz-open-sidebar
   removes the .is-open class, which collapses the layout track so main
   fills the viewport). Always visible so the user can collapse the
   drawer without hunting for the burger in the header.
   Visual styling (square icon button, hover, color) inherits from
   .gz-icon-btn — this class only adds positioning. */
.gz-sidebar-burger {
  margin: 0 var(--space-s) var(--space-s);
}

.gz-sidebar-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.gz-sidebar-link {
  display: flex;
  align-items: center;
  gap: var(--space-s);
  padding: var(--space-s) var(--space-m);
  border-radius: var(--radius-2);
  color: var(--text-muted);
  text-decoration: none;
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
}

/* Removed duplicate "Puzzle" category — "Puzzles" (1588 games) is the
   canonical one. Sidebar links are hardcoded in every template, so hide
   the stray puzzle link here instead of re-editing thousands of files. */
.gz-sidebar-link[href="/category/puzzle/"] {
  display: none !important;
}

.gz-sidebar-link:hover {
  background: var(--surface-2);
  color: var(--text);
  filter: none;
}

.gz-sidebar-link[aria-current="page"],
.gz-sidebar-link.is-active {
  background: var(--accent-soft);
  color: var(--accent);
}

/* Primary-nav quick-access group (Home + Recent + Trending + Latest +
   Featured). Sits above the Categories divider and is rendered with a
   distinct chip-style look so the user can tell primary actions from
   the genre-browsing list at a glance: slightly larger size, slightly
   heavier weight, and a soft accent-tinted resting background that
   still leaves room for the active-state pill to stand out. */
.gz-sidebar-link--quick {
  font-size: var(--fs-md);
  font-weight: var(--fw-bold);
  color: var(--text);
  background: var(--accent-soft);
  margin: 2px 0;
}

.gz-sidebar-link--quick:hover {
  background: var(--surface-2);
  color: var(--text);
}

.gz-sidebar-link--quick[aria-current="page"],
.gz-sidebar-link--quick.is-active {
  background: var(--accent);
  color: var(--accent-text, #fff);
}

.gz-sidebar-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  color: inherit;
}

.gz-sidebar-label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.gz-sidebar-count {
  /* The number-pill next to each category is no longer rendered
     (user wants a clean list). The selector is kept inert in case
     it returns in a future build. */
  display: none;
}

.gz-sidebar-link[aria-current="page"] .gz-sidebar-count,
.gz-sidebar-link.is-active .gz-sidebar-count {
  display: none;
}

/* Drawer backdrop (always-on, visibility toggled via .is-visible) */
.gz-sidebar-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(20, 24, 40, 0.35);
  opacity: 0;
  pointer-events: none;
  z-index: 199;
}

.gz-sidebar-backdrop.is-visible {
  opacity: 1;
  pointer-events: auto;
}

/* Backdrop is mobile-only — it's the dimming layer behind the
   off-canvas drawer. On tablet/desktop the sidebar is part of the
   layout, not an overlay, so the backdrop must be hidden or it
   would swallow every click outside the sidebar. */
@media (min-width: 768px) {
  .gz-sidebar-backdrop {
    display: none !important;
  }
}

/* ============================================================
   HERO / FEATURED STRIP
   ============================================================ */

.gz-hero {
  background: var(--bg-canvas);
  border-radius: var(--radius-4);
  padding: var(--space-2xl) var(--space-l);
  margin-bottom: var(--space-l);
}

.gz-hero-eyebrow {
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin: 0 0 var(--space-s);
}

.gz-hero-title {
  font-family: var(--font-display);
  font-size: var(--fs-3xl);
  font-weight: var(--fw-black);
  line-height: var(--lh-tight);
  margin: 0 0 var(--space-s);
  letter-spacing: -0.02em;
  color: var(--text);
}

@media (max-width: 768px) {
  .gz-hero-title {
    font-size: var(--fs-2xl);
  }
}

.gz-hero-subtitle {
  font-size: var(--fs-md);
  color: var(--text-muted);
  margin: 0 0 var(--space-m);
  max-width: 60ch;
  font-weight: var(--fw-regular);
}

.gz-hero-actions {
  display: inline-flex;
  align-items: center;
  gap: var(--space-s);
}

/* Compact hero variant — slimmer, single-line, no big headline.
   Used on the homepage since the sectioned marketing headers are
   gone and the games grid is the focal point. */
.gz-hero--compact {
  padding: var(--space-m) var(--space-l);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  margin: 0 calc(-1 * var(--space-l)) var(--space-m);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-l);
  flex-wrap: wrap;
}
.gz-hero--compact .gz-hero-eyebrow {
  margin: 0;
}
.gz-hero--compact .gz-hero-subtitle {
  margin: 0;
  font-size: var(--fs-sm);
}

/* Split hero — used on category pages. Left side holds the title block;
   right side holds a small grid of "popular in this category" tiles so
   the page doesn't have a wide empty band where the hero sits. */
.gz-hero--split {
  display: grid;
  grid-template-columns: minmax(240px, 1fr) 2fr;
  gap: var(--space-l);
  align-items: center;
  padding: var(--space-l);
}
.gz-hero-split-text {
  min-width: 0;
}
.gz-hero-split-text .gz-hero-eyebrow {
  margin: 0 0 var(--space-s);
}
.gz-hero-split-text .gz-hero-title {
  margin: 0 0 var(--space-s);
}
.gz-hero-split-text .gz-hero-subtitle {
  margin: 0;
}
.gz-hero-split-games {
  min-width: 0;
}
.gz-cat-strip-label {
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin: 0 0 var(--space-s);
}
.gz-cat-strip-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 4px;
}
.gz-cat-strip-grid > .gz-card {
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
}

@media (max-width: 900px) {
  .gz-hero--split {
    grid-template-columns: 1fr;
    gap: var(--space-m);
  }
}
@media (max-width: 600px) {
  .gz-cat-strip-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}
@media (max-width: 380px) {
  .gz-cat-strip-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* Feed container — pure list of games with no section headings.
   Drops sectioned margins so it stretches edge-to-edge. */
.gz-feed {
  margin: 0;
  padding: 0;
}

/* Primary button */
.gz-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-s);
  height: 44px;
  padding: 0 var(--space-l);
  font-size: var(--fs-md);
  font-weight: var(--fw-bold);
  border-radius: var(--radius-pill);
  text-decoration: none;
  cursor: pointer;
  border: none;
  white-space: nowrap;
}

.gz-btn--primary {
  background: var(--accent);
  color: var(--accent-text);
}

.gz-btn--primary:hover {
  background: var(--accent-hover);
  color: var(--accent-text);
  filter: none;
}

.gz-btn--ghost {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--border-strong);
}

.gz-btn--ghost:hover {
  background: var(--surface-2);
  color: var(--text);
  filter: none;
}

/* Featured strip (Poki-style horizontal row of large cards) */
.gz-featured-strip {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: var(--grid-gap);
  margin-bottom: var(--space-l);
}

@media (max-width: 1100px) {
  .gz-featured-strip {
    grid-template-columns: repeat(4, 1fr);
  }
}

@media (max-width: 768px) {
  .gz-featured-strip {
    grid-template-columns: repeat(3, 1fr);
  }
}

@media (max-width: 540px) {
  .gz-featured-strip {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* ============================================================
   GAME CARD (clean: image only, caption rises into view on hover)
   ============================================================ */

.gz-card {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  background: var(--surface-2);
  border-radius: var(--radius-4);
  overflow: hidden;
  /* Containment hints:
     - `contain: layout style` tells the browser that nothing
       outside this card's box affects its layout, and internal
       style changes don't affect siblings. Lets the browser
       skip work on cards outside the viewport during scroll.
     - `contain-intrinsic-size` reserves the eventual size so
       scrollbar/layout stays stable when lazy-loaded cards
       swap from empty to thumb-bearing. */
  contain: layout style;
  contain-intrinsic-size: 142px 142px;
  text-decoration: none;
  color: inherit;
  /* Hover "pop": a real lift in px and a real scale in ratio. No
     3D rotation — the user reported that the tilt looked like a
     stretch/fill on their external monitor and only the simple
     lift+scale is wanted everywhere. -10px reads at any tile size;
     1.06 isn't so large it overflows the surrounding grid cells. */
  --gz-lift: 0px;
  transform: translate3d(0, var(--gz-lift), 0)
             scale(var(--gz-scale, 1));
  transition: transform 180ms var(--ease-out),
              box-shadow 180ms var(--ease-out);
  box-shadow: 0 0 0 1px transparent;
}

/* Skeleton shimmer: shown in the reserved card box until the thumbnail
   paints. The opaque <img> covers it visually; the .is-loaded class
   (added by site.js on img load/complete) removes it so no animation
   runs under a loaded card. */
.gz-card::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: linear-gradient(
    100deg,
    var(--surface-2) 30%,
    rgba(255, 255, 255, 0.10) 50%,
    var(--surface-2) 70%
  );
  background-size: 200% 100%;
  animation: gz-card-shimmer 1.4s ease-in-out infinite;
}
.gz-card.is-loaded::before {
  display: none;
}
@keyframes gz-card-shimmer {
  0%   { background-position: 150% 0; }
  100% { background-position: -50% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .gz-card::before { animation: none; }
}

.gz-card:hover,
.gz-card:focus-visible {
  outline: none;
  --gz-lift: -10px;
  --gz-scale: 1.06;
  z-index: 5;
  box-shadow: var(--shadow-lg);
}
.gz-card:focus-visible {
  box-shadow: 0 0 0 2px var(--accent), var(--shadow-lg);
}

/* Reduced motion: keep the static lift + scale (visual feedback that
   the card is focused/hovered) but tighten the transition. */
@media (prefers-reduced-motion: reduce) {
  .gz-card {
    transition: transform 80ms linear, box-shadow 80ms linear;
  }
  .gz-card:hover,
  .gz-card:focus-visible {
    transform: translate3d(0, -4px, 0) scale(1.03);
    box-shadow: 0 0 0 2px var(--accent);
  }
}

.gz-card-thumb-img,
.gz-card-thumb-frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border: 0;
  display: block;
  border-radius: var(--radius-4);
}

/* Hover caption: gradient overlay (dark at bottom, light/transparent
   toward the top) with the game title rising into view. Hidden
   until the user hovers/focuses the card. */
.gz-card-caption {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: flex-end;
  padding: 8px 10px;
  border-radius: inherit;
  pointer-events: none;
  /* Dark at bottom fading up to light — the user wanted the
     "light to dark shade" gradient overlay back. */
  background: linear-gradient(
    to top,
    rgba(0, 0, 0, 0.85) 0%,
    rgba(0, 0, 0, 0.55) 35%,
    rgba(0, 0, 0, 0) 70%
  );
  opacity: 0;
  transition: opacity 140ms var(--ease-out);
}

.gz-card-caption-title {
  display: block;
  width: 100%;
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  line-height: 1.25;
  color: #fff;
  letter-spacing: 0.005em;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transform: translateY(4px);
  transition: transform 140ms var(--ease-out);
}

.gz-card:hover .gz-card-caption,
.gz-card:focus-visible .gz-card-caption {
  opacity: 1;
}

.gz-card:hover .gz-card-caption-title,
.gz-card:focus-visible .gz-card-caption-title {
  transform: translateY(0);
}

/* On small tiles, keep the title compact so it doesn't overflow. */
.gz-mosaic > .gz-card--1x1 .gz-card-caption-title,
.gz-rail > .gz-card--1x1 .gz-card-caption-title {
  font-size: var(--fs-xs);
}

/* Reduced-motion: drop the transform; opacity alone is enough. */
@media (prefers-reduced-motion: reduce) {
  .gz-card-caption,
  .gz-card-caption-title {
    transition: opacity 60ms linear;
    transform: none !important;
  }
}

.gz-card-thumb-img {
  z-index: 1;
}

.gz-card-thumb-frame {
  z-index: 2;
  opacity: 0;
  pointer-events: none;
  background: #000;
}

/* Hover swap: static thumb fades, the live game iframe fades in. */
.gz-card.is-previewing .gz-card-thumb-img {
  opacity: 0;
}

.gz-card.is-previewing .gz-card-thumb-frame {
  opacity: 1;
}

/* Reduce-motion: skip the swap entirely so the static image stays. */
@media (prefers-reduced-motion: reduce) {
  .gz-card.is-previewing .gz-card-thumb-img {
    opacity: 1;
  }
  .gz-card.is-previewing .gz-card-thumb-frame {
    opacity: 0;
  }
}

/* The legacy body / title / rating classes are now empty (no markup
   references them) but we keep them at 0 height in case future
   scripts inject them, so they don't break the grid alignment. */
.gz-card-body,
.gz-card-title,
.gz-card-rating,
.gz-card-badge,
.gz-card-overlay,
.gz-card-play {
  display: none;
}

/* ============================================================
   RECENTLY PLAYED
   ============================================================ */

.gz-recent {
  margin-bottom: var(--space-l);
}

.gz-recent[hidden] {
  display: none;
}

/* ============================================================
   SECTIONS + SECTION HEADERS
   ============================================================ */

.gz-section {
  margin-bottom: var(--space-xl);
}

/* Alternating section backgrounds give the page rhythm without
   images or animations. Use --gz-section-bg / --gz-section-border
   data attributes set by the template to pick the variation. */
.gz-section--soft {
  background: var(--bg-soft);
  border: 1px solid var(--border);
  border-radius: var(--radius-4);
  padding: var(--space-l);
  margin-left: calc(-1 * var(--space-l));
  margin-right: calc(-1 * var(--space-l));
}

.gz-section--canvas {
  background: var(--bg-canvas);
  border: 1px solid var(--border);
  border-radius: var(--radius-4);
  padding: var(--space-l);
  margin-left: calc(-1 * var(--space-l));
  margin-right: calc(-1 * var(--space-l));
}

.gz-section--soft .gz-section-title,
.gz-section--canvas .gz-section-title {
  color: var(--text);
}

/* On mobile the negative margins would push past the viewport; clamp. */
@media (max-width: 768px) {
  .gz-section--soft,
  .gz-section--canvas {
    margin-left: calc(-1 * var(--space-m));
    margin-right: calc(-1 * var(--space-m));
    padding: var(--space-m);
  }
}

.gz-section-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-m);
  padding: 0 var(--space-xs);
  margin: 0 0 var(--space-m);
}

.gz-section-title {
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  font-weight: var(--fw-black);
  color: var(--text);
  margin: 0;
  letter-spacing: -0.01em;
}

.gz-section-meta {
  font-size: var(--fs-sm);
  color: var(--text-dim);
  font-weight: var(--fw-medium);
  white-space: nowrap;
}

.gz-section-link {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--accent);
  background: transparent;
  border: 0;
  padding: 4px 8px;
  border-radius: var(--radius-2);
  cursor: pointer;
}

.gz-section-link:hover {
  background: var(--accent-soft);
  color: var(--accent-hover);
}

.gz-search-empty {
  font-size: var(--fs-sm);
  color: var(--text-dim);
  padding: var(--space-l);
  text-align: center;
  background: var(--bg);
  border: 1px dashed var(--border);
  border-radius: var(--radius-3);
  margin-bottom: var(--space-l);
}

/* ============================================================
   GAME GRID — uniform grid used by every row (Trending now,
   Editor's picks, New releases, All games).
   The grid auto-fills with cards of the same height. Cards
   are NOT oversized — every row has uniform tiles like Poki.
   ============================================================ */

.gz-game-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: var(--grid-gap);
}

@media (min-width: 1280px) {
  .gz-game-grid {
    grid-template-columns: repeat(6, 1fr);
  }
}

@media (max-width: 1100px) {
  .gz-game-grid {
    grid-template-columns: repeat(5, 1fr);
  }
}

@media (max-width: 900px) {
  .gz-game-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

@media (max-width: 540px) {
  .gz-game-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* ============================================================
   RAIL (kept available for future use, unused on home now)
   ============================================================ */

.gz-rail {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  grid-auto-rows: 1fr;
  gap: 6px;
}
.gz-rail > .gz-card {
  grid-column: span 2;
  grid-row: span 1;
  aspect-ratio: 1 / 1;
}
@media (max-width: 900px) {
  .gz-rail {
    grid-template-columns: repeat(6, 1fr);
  }
  .gz-rail > .gz-card {
    grid-column: span 2;
  }
}
@media (max-width: 600px) {
  .gz-rail {
    grid-template-columns: repeat(4, 1fr);
    gap: 4px;
  }
  .gz-rail > .gz-card {
    grid-column: span 2;
  }
}

/* ============================================================
   CHIPS — Top categories row
   ============================================================ */

.gz-chip-row {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-s);
}

.gz-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-s);
  padding: var(--space-s) var(--space-l);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  text-decoration: none;
}

.gz-chip:hover,
.gz-chip:focus-visible {
  background: var(--surface-2);
  border-color: var(--accent);
  color: var(--accent);
  filter: none;
}

.gz-chip-name {
  line-height: 1;
}

.gz-chip-count {
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  background: var(--surface-2);
  color: var(--text-dim);
  padding: 2px 8px;
  border-radius: var(--radius-pill);
}

.gz-chip:hover .gz-chip-count {
  background: var(--accent-soft);
  color: var(--accent);
}

/* ============================================================
   CARD BADGES (LATEST / NEW / TRENDING / RECENT) — corner pills
   ============================================================
   Server-rendered: LATEST, NEW, TRENDING (driven by badge_for()
   on the build side, see scripts/build_templates.py).
   JS-rendered: RECENT (added by site.js when a card matches
   localStorage "gamequartz.recentlyPlayed" — most visible on
   the /recent/ page).

   Half-cards (gz-card--half) are the smallest tile and visually
   too small for a pill — badges auto-hide on those via the
   descendant-selector rule below. */

.gz-card-badge {
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 4;
  padding: 4px 10px;
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: var(--fw-black);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  border-radius: var(--radius-pill);
  color: #fff;
  pointer-events: none;
  box-shadow: var(--shadow-sm);
  white-space: nowrap;
}

.gz-card-badge--latest {
  background: #8b5cf6;             /* purple — top ~10 by recency */
}

.gz-card-badge--new {
  background: #ff3b30;             /* red — next ~14 by recency */
}

.gz-card-badge--trending {
  background: #10b981;             /* green — popularity rank 24-99 */
}

/* Blue dot — used for "recently played" by JS. Sits at top-right
   (so it doesn't collide with text-pills at top-left) and is
   smaller than the server-rendered text badges. */
.gz-card-badge--recent {
  top: 10px;
  right: 10px;
  left: auto;
  width: 22px;
  height: 22px;
  padding: 0;
  border-radius: 50%;
  background: #0ea5e9;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0;                     /* hide the dot's inner text */
}
.gz-card-badge--recent::before {
  content: "";
  width: 6px;
  height: 6px;
  background: #fff;
  border-radius: 50%;
}

/* Half-cards are ~25% the area of a 1x1 and visually crowded
   with their tiny thumbs — hide badges on those so each half-card
   reads as a single tile. */
.gz-card--half .gz-card-badge {
  display: none;
}

@media (max-width: 480px) {
  .gz-card-badge {
    top: 8px;
    left: 8px;
    padding: 3px 8px;
    font-size: 10px;
  }
  .gz-card-badge--recent {
    top: 8px;
    right: 8px;
    width: 18px;
    height: 18px;
  }
}

/* ============================================================
   HOVER-PREVIEW ZOOM EFFECT (currently disabled per design spec)
   The user requested no animation. Only the iframe swap remains:
   the static thumb fades out and the iframe fades in.
   ============================================================ */

.gz-card.is-previewing,
.gz-card.is-previewing:hover,
.gz-card.is-previewing:focus-visible {
  border-color: var(--accent);
  box-shadow: var(--shadow-md);
  z-index: 5;
}

.gz-card.is-previewing .gz-card-thumb-img {
  opacity: 0;
}

.gz-card.is-previewing .gz-card-thumb-frame {
  opacity: 1;
}

/* ============================================================
   AD SLOT — restyled wrapper, DOM hook preserved
   ============================================================
   The CloudArcade live theme uses:
     <div class="grid-item span-2 item-grid" id="ads">
       <div class="adst"><span>ADVERTISEMENT</span></div>
       <div class="ads"><!-- ad script here --></div>
     </div>
   We keep id, classes, and ADVERTISEMENT text intact so any ad script
   that binds to these hooks still works. The .gz-ad class provides
   the new visual frame; .grid-item / .span-2 / .item-grid are
   neutralised below so they don't fight the new CSS grid.
*/

.gz-ad.grid-item,
.gz-ad.grid-item.span-2.item-grid,
.gz-ad.span-2,
.gz-ad.item-grid {
  grid-column: auto / span 1;
}

/* Ad slots are hidden by default. The site.js ad-visibility logic
   adds `.gz-ad-has-content` to a slot only when a real ad provider
   has populated it (iframe with src, an <img>, an AdSense <ins>,
   or non-trivial injected markup). Without that class the empty
   placeholder stays hidden so the page never shows a blank box. */
.ad-zone,
.gz-ad {
  display: none !important;
}
.ad-zone.gz-ad-has-content,
.gz-ad.gz-ad-has-content {
  display: flex !important;
}

.gz-ad {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-s);
  padding: var(--space-l);
  background: var(--bg-subtle);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-4);
  min-height: 220px;
  text-align: center;
}

.gz-ad .adst {
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: 0.08em;
  color: var(--text-dim);
  text-transform: uppercase;
}

.gz-ad .adst span {
  display: inline-block;
}

.gz-ad .ads {
  width: 100%;
  min-height: 200px;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ============================================================
   RELATED CATEGORIES (injected by site.js)
   ============================================================ */

.gz-related-categories {
  margin-top: var(--space-2xl);
  padding: var(--space-l) 0;
  border-top: 1px solid var(--border);
}

.gz-related-categories .gz-section-header {
  margin-bottom: var(--space-m);
}

.gz-related-categories .gz-section-title {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  margin: 0;
  color: var(--text);
}

/* Category directory: a 2-column vertical list (no horizontal scroll).
   Each item is a row-style link with icon + name + count. */
.gz-cat-tiles {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-s);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The site.js injector wraps each tile in an <li>; display: contents
   makes the <li> layout-transparent so the <a.gz-cat-tile> becomes the
   direct grid item. */
.gz-cat-tiles > li {
  display: contents;
}

.gz-cat-tile {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: flex-start;
  gap: var(--space-s);
  width: auto;
  min-width: 0;
  padding: var(--space-s) var(--space-m);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  text-decoration: none;
  color: var(--text);
  text-align: left;
  transition: transform 120ms ease, border-color 120ms ease, background 120ms ease;
}

.gz-cat-tile:hover {
  transform: translateY(-2px);
  border-color: var(--accent);
  background: var(--bg-soft);
}

.gz-cat-tile-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  color: var(--accent);
}

.gz-cat-tile-icon svg {
  width: 28px;
  height: 28px;
}

.gz-cat-tile-name {
  flex: 1 1 auto;
  min-width: 0;
  font-weight: var(--fw-semibold);
  font-size: var(--fs-sm);
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.gz-cat-tile-count {
  flex: 0 0 auto;
  margin-left: auto;
  font-size: var(--fs-xs);
  color: var(--text-muted);
  white-space: nowrap;
}

@media (max-width: 600px) {
  .gz-cat-tiles {
    gap: var(--space-xs);
  }
}

/* ============================================================
   SCROLL-TO-TOP (injected by site.js)
   ============================================================ */

.gz-scroll-top {
  position: fixed;
  right: var(--space-l);
  bottom: var(--space-l);
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  color: var(--accent-fg, #fff);
  border: 0;
  border-radius: var(--radius-pill);
  box-shadow: 0 6px 18px rgba(15, 19, 32, 0.18);
  cursor: pointer;
  opacity: 0;
  transform: translateY(8px);
  pointer-events: none;
  transition: opacity 160ms ease, transform 160ms ease, background 120ms ease;
  z-index: 90; /* below sidebar (200) and modal, above page content */
}

.gz-scroll-top.is-visible {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.gz-scroll-top:hover {
  background: var(--accent-strong, var(--accent));
}

.gz-scroll-top:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@media (max-width: 600px) {
  .gz-scroll-top {
    right: var(--space-m);
    bottom: var(--space-m);
    width: 40px;
    height: 40px;
  }
}

/* ============================================================
   PROFESSIONAL FOOTER (injected by site.js, sits above the
   existing minimal .gz-footer)
   ============================================================ */

.gz-footer-pro {
  margin-top: var(--space-3xl);
  padding: var(--space-2xl) var(--space-l);
  background: var(--bg-soft);
  border-top: 1px solid var(--border);
  color: var(--text);
}

.gz-footer-pro-inner {
  max-width: var(--content-max, 1280px);
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1.4fr 2fr;
  gap: var(--space-2xl);
  align-items: start;
}

.gz-footer-pro-brand {
  display: flex;
  flex-direction: column;
  gap: var(--space-m);
}

.gz-footer-pro-logo {
  width: 180px;
  max-width: 100%;
  height: auto;
  display: block;
}

.gz-footer-pro-tagline {
  margin: 0;
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: var(--text-muted);
  max-width: 38ch;
}

.gz-footer-pro-cols {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-xl);
}

.gz-footer-pro-col-title {
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-dim);
  margin: 0 0 var(--space-m);
}

.gz-footer-pro-col-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
}

.gz-footer-pro-col-list a {
  color: var(--text);
  text-decoration: none;
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  transition: color 120ms ease;
}

.gz-footer-pro-col-list a:hover {
  color: var(--accent);
}

.gz-footer-pro-bottom {
  grid-column: 1 / -1;
  margin-top: var(--space-l);
  padding-top: var(--space-l);
  border-top: 1px solid var(--border);
}

.gz-footer-pro-copy {
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--text-dim);
  text-align: center;
}

@media (max-width: 900px) {
  .gz-footer-pro-inner {
    grid-template-columns: 1fr;
    gap: var(--space-xl);
  }
  .gz-footer-pro-cols {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 600px) {
  .gz-footer-pro {
    padding: var(--space-xl) var(--space-m);
  }
  .gz-footer-pro-cols {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-l);
  }
  .gz-footer-pro-tagline {
    max-width: none;
  }
}

/* ============================================================
   FOOTER
   ============================================================ */

.gz-footer {
  margin-top: var(--space-3xl);
  padding: var(--space-2xl) var(--space-l);
  background: var(--bg-subtle);
  border-top: 1px solid var(--border);
  text-align: center;
  color: var(--text-muted);
  font-size: var(--fs-sm);
}

.gz-footer-links {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: var(--space-l);
  flex-wrap: wrap;
  margin: 0 0 var(--space-m);
  padding: 0;
  list-style: none;
}

.gz-footer-links a {
  color: var(--text-muted);
  font-weight: var(--fw-medium);
}

.gz-footer-links a:hover {
  color: var(--text);
}

.gz-footer-copy {
  color: var(--text-dim);
  font-size: var(--fs-xs);
}

/* The footer is a sibling of .gz-layout (outside it), so its
   centered text would otherwise center in the full viewport. When
   the sidebar is open on desktop, the rest of the page content
   sits in the area to the right of the 280px sidebar — shift the
   footer to match so the links + copyright align visually.

   Previously this rule animated `width`/`margin-left` for 200ms,
   but neither property transitions smoothly between the two states
   (browsers snap). Removed the transition — the snap happens on
   the same frame as the sidebar's GPU-animated `translateX` slide,
   so the user perceives both as one motion. */
@media (min-width: 768px) {
  body:has(.gz-sidebar.is-open) .gz-footer {
    width: calc(100% - 280px);
    margin-left: 280px;
  }
}

/* ============================================================
   POKI-STYLE MOSAIC — mixed-size masonry (1x1 / 2x2 / 3x3)
   ============================================================
   Three square tile sizes pack together via CSS Grid:
     1x1 small  — 1 col × 1 row, square (the default tile)
     2x2 medium — 2 cols × 2 rows, square
     3x3 large  — 3 cols × 3 rows, square

   Distribution:
     1x1 = MOST common  (~80% of tiles)
     2x2 = less common  (~15% of tiles)
     3x3 = least common (~5% of tiles)

   `grid-auto-flow: dense` back-fills holes left by 2x2 and
   3x3 cards with subsequent 1x1 cards, so the layout packs
   tightly without gaps. Every tile is a 1:1 square.

   Column count adapts to viewport:
     >= 1280px -> 9 cols
     >= 1024px -> 9 cols
     >=  820px -> 6 cols
     >=  500px -> 6 cols
     <   500px -> 3 cols
     <   380px -> 3 cols
*/

.gz-mosaic {
  display: grid;
  /* 18 sub-columns so a real half-width tile (1 sub-col) fits next to
     the existing 1x/2x/3x sizes. Each card-size is expressed in
     sub-units: half = 1, 1x1 = 2, 2x2 = 4, 3x3 = 6. The 1x/2x/3x
     sizes stay at the same pixel size as before — half is the new
     smallest tile (~25% the area of a 1x1).

     `grid-auto-rows: 1fr` keeps every implicit row equal to the
     column width, which combined with each card's
     `aspect-ratio: 1 / 1` guarantees a square tile at every
     viewport without stretching. Don't drop this — without it,
     the grid sizes rows to the tallest content, which on mixed
     tile heights produces portrait/landscape stretching on desktop.
     The mobile overrides below reset the row-spans so phones get
     uniform square tiles regardless of size variant. */
  grid-template-columns: repeat(18, 1fr);
  grid-auto-rows: 1fr;
  grid-auto-flow: dense;
  gap: 4px;
}

.gz-mosaic > .gz-card {
  width: 100%;
  height: 100%;
  aspect-ratio: 1 / 1;
}

/* At desktop (≥601px) the two large variants — 2x2 and 3x3 —
   collapse to the same footprint as --half so the feed stays
   uniform. The two smaller variants — --half (0.5x0.5) and --1x1
   — keep their original spans so the user still sees the rhythm
   between the smallest tile and the standard square tile. */
@media (min-width: 601px) {
  .gz-mosaic > .gz-card--2x2,
  .gz-mosaic > .gz-card--3x3 {
    grid-column: span 1 !important;
    grid-row: span 1 !important;
  }
}

/* Smallest tile — half the width AND height of a 1x1. The dominant
   tile (~60% of the feed). Half-cards are pure thumbs — caption
   stays overlay-only so the tile reads as a single tile. */
.gz-mosaic > .gz-card--half {
  grid-column: span 1;
  grid-row: span 1;
}

/* Standard tile. ~25% of the feed — used as a visual-rhythm break
   inside the half-dense stream. */
.gz-mosaic > .gz-card--1x1 {
  grid-column: span 2;
  grid-row: span 2;
}

/* Medium tile — twice a 1x1. ~10% of the feed — sparse anchors. */
.gz-mosaic > .gz-card--2x2 {
  grid-column: span 4;
  grid-row: span 4;
}

/* Hero tile — three times a 1x1. ~5% of the feed — rare Poki-style
   anchors that give the page focal points. */
.gz-mosaic > .gz-card--3x3 {
  grid-column: span 6;
  grid-row: span 6;
}

@media (max-width: 1280px) {
  .gz-mosaic {
    grid-template-columns: repeat(18, 1fr);
  }
}

@media (max-width: 1024px) {
  .gz-mosaic {
    grid-template-columns: repeat(18, 1fr);
  }
}

@media (max-width: 820px) {
  .gz-mosaic {
    grid-template-columns: repeat(12, 1fr);
  }
}

@media (max-width: 640px) {
  .gz-mosaic {
    grid-template-columns: repeat(12, 1fr);
    gap: 3px;
  }
  /* At 12 cols a 3x3 hero with row-span 6 would render 33% wide × 50%
     tall — taller than wide, so it stops looking square. Collapse the
     3x3 to a 4×4 cell (33% × 33%) so it reads as a 2x2 hero on tablet
     widths. The phone override at ≤500px keeps the same 4×4 value. */
  .gz-mosaic > .gz-card--3x3 {
    grid-column: span 4;
    grid-row: span 4;
  }
  /* `min-height: 0` lets the card's aspect-ratio win over the row's
     intrinsic content height when a caption or badge tries to grow
     the row. Without it, dense-flow packing at 640px can produce a
     single tall row that breaks the square guarantee. */
  .gz-mosaic > .gz-card {
    min-height: 0;
  }
}

/* Phone breakpoint: collapse to two uniform sizes so the feed
   stays readable and every tile is square regardless of size
   variant. Without this, the 3x3 hero card becomes a portrait
   rectangle on narrow screens because its row-span (6) is too tall
   for the column count (8/6). */
@media (max-width: 500px) {
  .gz-mosaic {
    grid-template-columns: repeat(8, 1fr);
    gap: 3px;
  }
  /* Drop the 1fr rows on phones — with the row-spans reset below,
     auto rows keep each card naturally square via aspect-ratio. */
  .gz-mosaic {
    grid-auto-rows: auto;
  }
  .gz-mosaic > .gz-card--half,
  .gz-mosaic > .gz-card--1x1 {
    grid-column: span 2;
    grid-row: span 2;
  }
  .gz-mosaic > .gz-card--2x2,
  .gz-mosaic > .gz-card--3x3 {
    grid-column: span 4;
    grid-row: span 4;
  }
}

@media (max-width: 380px) {
  .gz-mosaic {
    grid-template-columns: repeat(6, 1fr);
    gap: 2px;
  }
  .gz-mosaic > .gz-card--half,
  .gz-mosaic > .gz-card--1x1 {
    grid-column: span 2;
    grid-row: span 2;
  }
  .gz-mosaic > .gz-card--2x2,
  .gz-mosaic > .gz-card--3x3 {
    grid-column: span 3;
    grid-row: span 3;
  }
}


/* Phone mosaic: alternating "1:2 / 2:1" rhythm in a 3-column grid.

   Layout cycle (repeats every 6 cards):
     card 1: BIG square  (2 cols × 2 rows) — on the LEFT
     card 2: small       (1 col  × 1 row ) — top-right
     card 3: small       (1 col  × 1 row ) — bottom-right
     — "1:2" pattern: 1 big card paired with 2 small cards —
     card 4: small       (1 col  × 1 row ) — top-left
     card 5: BIG square  (2 cols × 2 rows) — on the RIGHT (offset)
     card 6: small       (1 col  × 1 row ) — bottom-left
     — "2:1" pattern: 2 small cards paired with 1 big card —

   Both big and small cards are SQUARE. The "1:2 / 2:1" labels
   describe the count rhythm of big-vs-small groupings, not the
   aspect ratio of individual cards. With auto-flow:dense the
   placement happens naturally; the explicit grid-column-start on
   card 5 nudges it onto the right edge to keep the big-left /
   big-right alternation visible. */
@media (max-width: 600px) {
  .gz-mosaic {
    grid-template-columns: repeat(3, 1fr);
    grid-auto-rows: 1fr;
    gap: 6px;
  }

  /* Flatten all size variants so the alternation drives the layout. */
  .gz-mosaic > .gz-card,
  .gz-mosaic > .gz-card--half,
  .gz-mosaic > .gz-card--1x1,
  .gz-mosaic > .gz-card--2x2,
  .gz-mosaic > .gz-card--3x3 {
    grid-column: auto;
    grid-row: auto;
    aspect-ratio: 1 / 1;
  }

  /* Default card size on phone: 1 col × 1 row, square.
     Applied via reset on every card; the big rules below
     override for cards 1 and 5 in each 6-card cycle. */
  .gz-mosaic > .gz-card:nth-child(6n+2),
  .gz-mosaic > .gz-card:nth-child(6n+3),
  .gz-mosaic > .gz-card:nth-child(6n+4),
  .gz-mosaic > .gz-card:nth-child(6n+6) {
    grid-column: span 1;
    grid-row: span 1;
  }

  /* Big square on the LEFT — first card in each cycle. */
  .gz-mosaic > .gz-card:nth-child(6n+1) {
    grid-column: span 2;
    grid-row: span 2;
  }

  /* Big square on the RIGHT — fifth card in each cycle. Starts
     at column line 2 so it occupies cols 2-3 instead of 1-2. */
  .gz-mosaic > .gz-card:nth-child(6n+5) {
    grid-column: 2 / span 2;
    grid-row: span 2;
  }
}


/* Phone-only dedicated square ad slot was removed — the slot was
   too dominant on small screens. The inline row ads that ship in
   each template continue to handle desktop placement; mobile
   intentionally has no extra ad slot. */


@media (max-width: 375px) {
  :root {
    --header-height: 56px;
  }

  .gz-game-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-s);
  }

  .gz-card-body {
    padding: var(--space-s) var(--space-m);
  }

  .gz-card-title {
    font-size: var(--fs-xs);
  }

  .gz-hero {
    padding: var(--space-m);
  }

  .gz-hero-title {
    font-size: var(--fs-xl);
  }
}

/* ============================================================
   MOTION: respect prefers-reduced-motion
   ============================================================ */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ============================================================
   SECTION META, PAGE HEADER, GAME FRAME
   ============================================================ */

.gz-section-meta {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  font-weight: var(--fw-medium);
}

.gz-page-header {
  margin: var(--space-l) 0 var(--space-m);
}

.gz-page-title {
  font-family: var(--font-display);
  font-size: var(--fs-2xl);
  font-weight: var(--fw-bold);
  margin: 0 0 var(--space-s);
  letter-spacing: -0.01em;
  color: var(--text);
}

@media (min-width: 768px) {
  .gz-page-title {
    font-size: var(--fs-3xl);
  }
}

.gz-page-meta {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  margin: 0;
}

.gz-game-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 10;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-4);
  overflow: hidden;
  margin: var(--space-m) 0 var(--space-l);
  /* The wrapper paints the game's thumbnail as a backdrop via an
     inline style="background-image:url(...)" emitted by build_templates.
     Slight blur so it reads as a preview behind glass rather than
     competing with the live game for visual attention. Dark overlay
     on top keeps it from being too bright while the iframe is empty. */
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}

/* Backdrop overlay — a soft dim layer between the poster image and
   the live iframe. Visible only while the iframe hasn't painted its
   first frame (gamemonetize takes ~2s on a fresh visit). The iframe
   itself is opaque white once its document loads, so this overlay
   disappears under it. */
.gz-game-frame::before {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  pointer-events: none;
  z-index: 0;
}

.gz-game-frame iframe {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

/* Fullscreen button — sits in the iframe's BOTTOM-RIGHT corner.
   No transition / no animation. (Replaces the previous top-right
   text-label variant. The corner-button style lives further down in
   the GAME DETAIL PAGE section.) */

@media (max-width: 540px) {
  .gz-game-frame {
    aspect-ratio: 4 / 3;
  }
}

/* ============================================================
   GAME DETAIL PAGE — Playzen-style layout
   ============================================================
   Reference: sidebar on the left (open by default), ad reserves
   flanking the iframe on both sides (visibly empty, reserved for
   future ad scripts), iframe in the center, "Recommended Games"
   rail below. The two side strips collapse on narrow viewports so
   the iframe gets the full content width on mobile/tablet.
*/

.gz-game {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 100vh;
  padding: var(--space-l);
  gap: var(--space-l);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  /* .gz-sidebar is position:fixed, so it vacates its grid cell.
     Auto-placement would drop .gz-game into column 1 (280px). Force
     it into column 2 so it fills the remaining width. Mirrors
     .gz-main's rule. */
  grid-column: 2 / -1;
}

/* Stage: 3-column grid. Center column is the iframe (clamped to
   1080px max). Side columns are ad reserves that flex between
   220px and ~1.5fr depending on viewport. The gap between columns
   gives the iframe breathing room — the side strips read as
   "future ad slot" without crowding the player. */
.gz-game-stage {
  display: grid;
  grid-template-columns:
    minmax(220px, 1.5fr)
    minmax(0, 1080px)
    minmax(220px, 1.5fr);
  align-items: stretch;
  gap: var(--space-l);
  width: 100%;
  min-height: 0;
}

/* Ad reserves — visible-empty rectangles so future ad scripts
   have a stable home. Dashed border + faded "Ad slot" label keeps
   the visual weight low without hiding the space. Inner padding
   keeps the dashed border away from the column edges so the
   strips don't run flush against the iframe. */
.gz-game-ad {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: var(--space-l) var(--space-l);
  min-width: 220px;
  background: var(--bg-soft);
  border: 1px dashed var(--border);
  border-radius: var(--radius-3);
  /* Vertical centering of the label inside the iframe's height. */
  align-self: stretch;
}

.gz-game-ad:first-of-type {
  border-left: 0;
}

.gz-game-ad:last-of-type {
  border-right: 0;
}

.gz-game-ad-label {
  display: inline-block;
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: var(--fw-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  background: var(--surface-2);
  border: 1px dashed var(--border);
  padding: 4px 10px;
  border-radius: var(--radius-pill);
  white-space: nowrap;
  user-select: none;
}

/* AdSense placeholder zones.
   Light grey boxes with "[ Ad Zone ]" centered text. Used as
   stand-ins for real AdSense units until the publisher ID + slot
   IDs are confirmed and the <ins> tags can be swapped in.
   Each placeholder is wrapped in the comment
   `<!-- ADSENSE ZONE: replace with ad unit code -->` so a single
   find-and-replace pass can drop real units in later. Width tracks
   the surrounding content (the markup uses full-width containers,
   so .ad-zone just expands to fill its parent — no max-width cap). */
.ad-zone {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 96px;
  margin: var(--space-l) 0;
  padding: var(--space-m);
  background: var(--bg-soft);
  border: 1px dashed var(--border);
  border-radius: var(--radius-3);
  color: var(--text-dim);
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: var(--fw-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  user-select: none;
}

/* Slimmer variant for in-feed placements between card rows so the
   zone doesn't push the next row off-screen on short viewports. */
.ad-zone--row {
  min-height: 64px;
  margin: var(--space-m) 0;
}

/* Iframe cell — matches the height of the reserves so the dashed
   border lines up across all three columns. */
.gz-game-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 10;
  background: #000;
  border: 1px solid var(--border);
  border-radius: var(--radius-4);
  overflow: hidden;
  min-width: 0;
  min-height: 0;
}

.gz-game-frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

/* Fullscreen icon — bottom-right corner of the iframe. NO circular
   background, no pill border-radius — the user found the round
   dark-blob button distracting over the live game. The button is
   now a bare SVG icon (with a soft hover-state background that
   appears only when the user hovers it), so it stays discoverable
   but doesn't compete with the game for attention. */
.gz-game-frame-fullscreen {
  position: absolute;
  bottom: 14px;
  right: 14px;
  z-index: 10;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 4px;
  color: rgba(255, 255, 255, 0.7);
  cursor: pointer;
  line-height: 1;
}

.gz-game-frame-fullscreen:hover,
.gz-game-frame-fullscreen:focus-visible {
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  outline: none;
}

.gz-game-frame-fullscreen svg {
  display: block;
  flex-shrink: 0;
}

/* The bindFullscreen JS helper appends a <span>Fullscreen</span> label
   for the modal version, where the text is wanted. On the game-page
   corner button we want icon-only — hide the span so the button
   stays a clean 32×32 square, not a wider text pill. */
.gz-game-frame-fullscreen span {
  display: none;
}

@media (max-width: 540px) {
  .gz-game-frame-fullscreen {
    bottom: 10px;
    right: 10px;
    width: 28px;
    height: 28px;
  }
}

/* Title + maximize bar — visible UI below the iframe on the game
   page ONLY (cards stay clean). Contains the game title on the left
   and a labeled fullscreen button on the right. */
.gz-game-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-m);
  margin-top: var(--space-m);
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  background: var(--surface-1, rgba(255, 255, 255, 0.04));
}

.gz-game-bar-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--text);
  line-height: 1.3;
  /* Keep the title to a single line so the bar stays compact. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

.gz-game-bar-fullscreen {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface-2, transparent);
  color: var(--text);
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  transition: background 140ms var(--ease-out),
              color 140ms var(--ease-out),
              border-color 140ms var(--ease-out);
  flex: 0 0 auto;
}
.gz-game-bar-fullscreen:hover,
.gz-game-bar-fullscreen:focus-visible {
  background: var(--accent);
  color: var(--accent-text, #fff);
  border-color: var(--accent);
  outline: none;
}
.gz-game-bar-fullscreen svg {
  display: block;
}

@media (max-width: 540px) {
  .gz-game-bar-title {
    font-size: var(--fs-sm);
  }
  .gz-game-bar-fullscreen {
    padding: 7px 12px;
    font-size: var(--fs-xs);
  }
}

/* Below the iframe: rail of related games. Uniform 1×1 mosaic,
   full content width, capped at 220px row height so it doesn't
   compete with the playing iframe for visual attention. */
.gz-game-rail {
  width: 100%;
}

.gz-game-rail .gz-rail {
  margin-top: var(--space-m);
}

/* About This Game block — injected by site.js on per-game pages.
   Sits between the iframe stage and the Recommended Games rail.
   Max width keeps paragraphs readable on wide displays; phone
   widths fall back to full container width. */
.gz-about-game {
  width: 100%;
  max-width: 760px;
  margin: var(--space-2xl) auto;
  padding: var(--space-xl);
  background: var(--bg-soft);
  border: 1px solid var(--border);
  border-radius: var(--radius-4);
  text-align: left;
}

.gz-about-game-title {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  color: var(--text);
  margin: 0 0 var(--space-m);
}

.gz-about-game-desc {
  margin: 0 0 var(--space-m);
  font-size: var(--fs-base);
  line-height: 1.7;
  color: var(--text);
}

.gz-about-game-instr {
  margin: 0;
  padding: var(--space-m);
  background: var(--bg-subtle);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-2);
  font-size: var(--fs-sm);
  color: var(--text);
}

.gz-about-game-instr strong {
  color: var(--text);
  font-weight: var(--fw-semibold);
}

@media (max-width: 600px) {
  .gz-about-game {
    margin: var(--space-l) auto;
    padding: var(--space-m);
  }
}

/* Narrow viewports: drop the side reserves, iframe gets full width.
   The reserves stay in the DOM (display: none) so the layout
   doesn't shift if the viewport is resized back to desktop. */
@media (max-width: 1023px) {
  .gz-game-stage {
    grid-template-columns: minmax(0, 1fr);
  }
  .gz-game-ad {
    display: none;
  }
}

@media (max-width: 767px) {
  .gz-game {
    padding: var(--space-m);
    gap: var(--space-m);
  }
  .gz-game-frame {
    aspect-ratio: 4 / 3;
  }
}

@media (max-width: 540px) {
  .gz-game-frame {
    aspect-ratio: 1 / 1;
    border-radius: var(--radius-3);
  }
}

/* True fullscreen — when the iframe goes fullscreen, hide the
   pill + reserves + rail. The page body gets .gz-fs-active from
   the fullscreenchange listener (same as the modal). */
body.gz-fs-active .gz-game-stage > .gz-game-ad,
body.gz-fs-active .gz-game-rail,
body.gz-fs-active .gz-page-header {
  display: none !important;
}
body.gz-fs-active .gz-game {
  padding: 0;
  gap: 0;
}
body.gz-fs-active .gz-game-stage {
  grid-template-columns: minmax(0, 1fr);
}
body.gz-fs-active .gz-game-frame {
  border-radius: 0;
  border: 0;
  aspect-ratio: auto;
  width: 100vw;
  height: 100vh;
}

.gz-search-empty {
  text-align: center;
  color: var(--text-muted);
  font-size: var(--fs-sm);
  padding: var(--space-2xl) var(--space-l);
  background: var(--bg-subtle);
  border-radius: var(--radius-4);
}

/* ============================================================
   LOAD MORE (homepage pagination)
   ============================================================ */

.gz-paged-out {
  display: contents;
}

.gz-paged-out[hidden] {
  display: none;
}

.gz-loadmore-wrap {
  display: flex;
  justify-content: center;
  margin: var(--space-2xl) 0;
}

.gz-loadmore {
  min-width: 220px;
}

.gz-loadmore-count {
  font-weight: var(--fw-medium);
  color: var(--text-muted);
  margin-left: var(--space-s);
}

.gz-loadmore[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ============================================================
   POKI-STYLE HOVER/TOUCH LIVE PREVIEW
   The preview sits inside .gz-card (which is position: relative)
   and overlays the static thumbnail. JS sets `preview.hidden = false`
   on hover / long-press, hides it again on leave / touch-end. The
   iframe itself has pointer-events: none so the surrounding <a>
   keeps receiving clicks (navigates to /game/<slug>/). The 3D tilt
   on the parent card continues to apply because the preview is
   inside the transformed element — that's intentional.
   ============================================================ */
.gz-card-preview {
  position: absolute;
  inset: 0;
  z-index: 2;
}
.gz-card-preview[hidden] {
  display: none;
}
.gz-card-preview > iframe {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
  pointer-events: none;
}
.gz-card-thumb-img.is-fading {
  opacity: 0;
  transition: opacity 160ms var(--ease-out);
}
.gz-card-preview.revealing {
  opacity: 1;
  transition: opacity 160ms var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
  .gz-card-thumb-img.is-fading,
  .gz-card-preview.revealing { transition: none; }
}

/* ============================================================
   GAME MODAL (small-window layout)
   ============================================================
   Clicking a game card opens a sidebar + game + rail layout —
   NOT a full-page redirect. The page chrome (sidebar categories,
   search, recently played) lives in the modal's left sidebar.

   Layout (desktop, ≥1024px):
     ┌─ sidebar ─┬─ ad slot L ─┬── iframe ──┬─ ad slot R ─┐
     │ categories│  (reserved) │   game     │  (reserved) │
     │ ...       │             │            │             │
     │  ✕ close  │             └────────────┘             │
     └───────────┴───────────── ─ rail of "More games" ────┘
                             [card card card card ...]
                              [Show more ▾ / less]
   The pill (fullscreen + close + game title) floats in the
   top-right of the modal box. It auto-dims; clicking brings it
   back to full opacity. When real iframe fullscreen engages
   (body.gz-fs-active), the pill, sidebar, ad slots, and rail
   all hide — only the iframe remains.

   Below 1024px: sidebar collapses to a drawer controlled by
   the existing toggle, ad slots hide, layout goes single-column.
   Below 540px (mobile): sidebar fully collapsed, rail is a
   single row of small cards.
   ============================================================ */

.gz-modal[hidden] { display: none !important; }

.gz-modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: rgba(15, 18, 32, 0.62);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  backdrop-filter: blur(10px) saturate(140%);
  display: grid;
  /* Single-row layout on small widths: collapses to one column. */
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
  padding: 0;
  animation: gz-modal-fade 140ms ease-out;
}

@keyframes gz-modal-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* The modal's main "card" — sits centered inside the backdrop.
   On wide screens it gains sidebar + ad-slot columns. */
.gz-modal-shell {
  position: relative;
  width: 100%;
  height: 100%;
  display: grid;
  /* Default: sidebar is OPEN. Track = 240px. Ad reserves flank the
     iframe (left + right). When the sidebar is closed (no .is-open),
     the sidebar track collapses to 0 so the iframe gets full width. */
  grid-template-columns:
    240px
    minmax(120px, 1fr)
    minmax(0, 960px)
    minmax(120px, 1fr);
  grid-template-rows: minmax(0, 1fr) auto;
  grid-template-areas:
    "side  adl    main    adr"
    "side  rail   rail    rail";
  gap: 0;
  background: var(--surface);
  border-left: 1px solid var(--border);
  border-right: 1px solid var(--border);
  overflow: hidden;
}

/* ---- Sidebar (left) ---- */
.gz-modal-sidebar {
  grid-area: side;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border-right: 1px solid var(--border);
  min-width: 0;
  overflow: hidden;
}

.gz-modal-sidebar-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-s);
  padding: var(--space-s) var(--space-m);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}

.gz-modal-sidebar-title {
  margin: 0;
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  color: var(--text);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.gz-modal-sidebar-close {
  /* Reuses the .gz-sidebar-toggle styling — same off-canvas
     trigger used elsewhere in the app. */
  display: none;
  /* Shown by JS on small widths when sidebar needs to collapse. */
}

.gz-modal-sidebar-body {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  padding: var(--space-s) 0;
}

.gz-modal-sidebar-body::-webkit-scrollbar {
  width: 6px;
}
.gz-modal-sidebar-body::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: var(--radius-pill);
}

/* The sidebar's <ul> of categories reuses the homepage sidebar
   styling. We give it a class so we can scope the styles. */
.gz-modal-sidebar-body .gz-sidebar-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* When the modal sidebar does NOT have .is-open (closed state),
   collapse the sidebar track to 0 so the iframe + ad reserves
   take the full width. The sidebar itself uses position:fixed
   and slides off-screen via .gz-modal.is-sidebar-open toggle. */
.gz-modal-shell:not(:has(.gz-modal-sidebar.is-open)) {
  grid-template-columns:
    0
    minmax(120px, 1fr)
    minmax(0, 960px)
    minmax(120px, 1fr);
}

/* ---- Ad reserves (left + right of iframe) ---- */
.gz-modal-ad {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-soft);
  color: var(--text-muted, var(--text));
  border-left: 1px dashed var(--border);
  border-right: 1px dashed var(--border);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  min-width: 120px;
  min-height: 0;
}

.gz-modal-ad:first-of-type { border-right: 1px dashed var(--border); border-left: 0; }
.gz-modal-ad:last-of-type { border-left: 1px dashed var(--border); border-right: 0; }

/* ---- Main (game iframe) ---- */
.gz-modal-main {
  grid-area: main;
  position: relative;
  background: #000;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}

.gz-modal-main-frame {
  position: relative;
  width: 100%;
  height: 100%;
  background: #000;
}

.gz-modal-main-frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

/* ---- "More games" rail (below the iframe) ---- */
.gz-modal-rail {
  grid-area: rail;
  background: var(--surface-2, var(--surface));
  border-top: 1px solid var(--border);
  padding: var(--space-m);
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
  min-width: 0;
}

.gz-modal-rail-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-s);
  flex-shrink: 0;
}

.gz-modal-rail-title {
  margin: 0;
  font-size: var(--fs-md);
  font-weight: var(--fw-bold);
  color: var(--text);
}

.gz-modal-rail-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  padding: 6px 14px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  white-space: nowrap;
}

.gz-modal-rail-toggle:hover,
.gz-modal-rail-toggle:focus-visible {
  border-color: var(--accent);
  color: var(--accent);
  outline: none;
}

.gz-modal-rail-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: var(--space-s);
  min-width: 0;
  max-height: 220px;
  overflow: hidden;
}

.gz-modal-rail-grid.is-expanded {
  max-height: none;
}

.gz-modal-rail-card {
  display: block;
  position: relative;
  aspect-ratio: 16 / 10;
  border-radius: var(--radius-2);
  overflow: hidden;
  background: var(--surface-2);
  border: 1px solid var(--border);
  text-decoration: none;
  color: inherit;
  min-width: 0;
}

.gz-modal-rail-card:hover,
.gz-modal-rail-card:focus-visible {
  border-color: var(--accent);
  outline: none;
}

.gz-modal-rail-card img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.gz-modal-rail-card-cap {
  position: absolute;
  inset: auto 0 0 0;
  padding: 6px 8px;
  font-size: 11px;
  line-height: 1.2;
  font-weight: var(--fw-semibold);
  color: #fff;
  background: linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.7) 100%);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The first N (default 8) cards are visible; the rest are hidden
   until the "Show more" toggle flips .gz-modal-rail is-expanded. */
.gz-modal-rail-toggle[data-target] ~ .gz-modal-rail-grid .gz-modal-rail-card[data-collapsed="1"] {
  display: none;
}

.gz-modal-rail.is-expanded .gz-modal-rail-card[data-collapsed="1"] {
  display: block;
}

/* ---- Floating control pill (modal: fixed to viewport) ----
            The pill inside the modal (.gz-modal .gz-modal-pill) pins
            to the viewport so it stays accessible no matter where the
            user is scrolled inside the modal. */
.gz-modal .gz-modal-pill {
  position: fixed;
  top: 12px;
  right: 12px;
  z-index: 1001;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  padding: 4px 6px 4px 12px;
  border-radius: var(--radius-pill);
  background: rgba(20, 22, 30, 0.78);
  -webkit-backdrop-filter: blur(12px) saturate(160%);
  backdrop-filter: blur(12px) saturate(160%);
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  color: #fff;
  opacity: 0.55;
  transform: translateY(0);
  transition: opacity 200ms ease, transform 200ms ease;
  pointer-events: auto;
}

/* ---- Floating control pill (game page: anchored to iframe) ----
            Travels with the iframe so the page can scroll cleanly to
            the top without the pill pinning to the viewport. Sits in
            the top-right corner of .gz-game-frame via absolute
            positioning. The pill just carries the game title +
            back-to-home link — fullscreen is handled by the browser
            (F11) or the game itself. */
.gz-game-frame > .gz-modal-pill {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 1001;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  padding: 4px 6px 4px 12px;
  border-radius: var(--radius-pill);
  background: rgba(20, 22, 30, 0.78);
  -webkit-backdrop-filter: blur(12px) saturate(160%);
  backdrop-filter: blur(12px) saturate(160%);
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  color: #fff;
  opacity: 0.55;
  transform: translateY(0);
  transition: opacity 200ms ease, transform 200ms ease;
  pointer-events: auto;
}

.gz-modal-pill:hover,
.gz-modal-pill:focus-within {
  opacity: 1;
}

.gz-modal-pill-title {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 220px;
  min-width: 0;
}

.gz-modal-pill-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-pill);
  border: 0;
  background: rgba(255, 255, 255, 0.10);
  color: #fff;
  cursor: pointer;
  padding: 0;
  line-height: 1;
}

.gz-modal-pill-btn:hover,
.gz-modal-pill-btn:focus-visible {
  background: rgba(255, 255, 255, 0.22);
  outline: none;
}

.gz-modal-pill-btn--close:hover,
.gz-modal-pill-btn--close:focus-visible {
  background: rgba(220, 60, 60, 0.85);
}

/* ---- Responsive: drop sidebar / ad slots below 1024px ---- */
@media (max-width: 1023px) {
  .gz-modal-shell {
    grid-template-columns: 1fr;
    grid-template-rows: 1fr auto;
    grid-template-areas:
      "main"
      "rail";
  }
  .gz-modal-sidebar {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 280px;
    transform: translateX(-100%);
    transition: transform 200ms ease;
    z-index: 5;
    border-right: 1px solid var(--border);
  }
  .gz-modal.is-sidebar-open .gz-modal-sidebar {
    transform: translateX(0);
  }
  .gz-modal-sidebar-close { display: inline-flex; }
  .gz-modal-ad { display: none; }
}

/* Sidebar backdrop, shown only when sidebar drawer is open on
   narrow screens. Clicking it closes the drawer. */
.gz-modal-sidebar-backdrop {
  display: none;
}
@media (max-width: 1023px) {
  .gz-modal.is-sidebar-open .gz-modal-sidebar-backdrop {
    display: block;
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.4);
    z-index: 4;
  }
}

/* ---- Mobile: edge-to-edge, rail collapses to a small row ---- */
@media (max-width: 640px) {
  .gz-modal-rail {
    padding: var(--space-s);
  }
  .gz-modal-rail-title {
    font-size: var(--fs-sm);
  }
  .gz-modal-rail-grid {
    grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
    gap: 6px;
    max-height: 110px;
  }
  .gz-modal-rail.is-expanded .gz-modal-rail-grid {
    max-height: none;
  }
  .gz-modal-rail-card-cap {
    font-size: 10px;
    padding: 4px 6px;
  }
  .gz-modal-pill-title {
    max-width: 100px;
    font-size: 12px;
  }
}

/* ---- Fullscreen mode ----
   When the iframe enters real fullscreen (iframe.requestFullscreen
   succeeds), the body gets gz-fs-active. We hide EVERYTHING in
   the modal shell except the iframe (which IS the fullscreen
   element). Sidebar, ad slots, rail, and pill all disappear. */
body.gz-fs-active .gz-modal-shell {
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
  grid-template-areas: "main";
}
body.gz-fs-active .gz-modal-sidebar,
body.gz-fs-active .gz-modal-ad,
body.gz-fs-active .gz-modal-rail {
  display: none !important;
}
body.gz-fs-active .gz-modal-pill {
  display: none !important;
}

body.gz-modal-open {
  overflow: hidden;
}

/* ============================================================
   RECENT PAGE / FILTER PAGES (/recent/, /trending/, /latest/, /featured/)
   ============================================================ */

.gz-filter-page {
  margin-top: var(--space-l);
}

.gz-page-header {
  margin-bottom: var(--space-l);
}

.gz-page-title {
  font-family: var(--font-display);
  font-size: var(--fs-2xl);
  font-weight: var(--fw-bold);
  letter-spacing: -0.01em;
  margin: 4px 0 0;
}

.gz-recent-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-m);
  padding: var(--space-3xl) var(--space-m);
  text-align: center;
  border: 1px dashed var(--border);
  border-radius: var(--radius-3);
  background: var(--bg-soft);
  color: var(--text-muted);
}

.gz-recent-empty svg {
  color: var(--text-muted);
  opacity: 0.6;
}

.gz-recent-empty-text {
  font-size: var(--fs-md);
  margin: 0;
  /* verbatim user-provided copy — preserve the double-dot. */
}

.gz-recent-empty .gz-btn {
  margin-top: var(--space-s);
}

.gz-recent-empty[hidden] {
  display: none;
}

.gz-recent-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: var(--space-m);
}

.gz-recent-list > .gz-card {
  height: auto;
  aspect-ratio: 1 / 1;
}

.gz-recent-list[hidden] {
  display: none;
}

/* On narrow widths the recent grid drops to 3 columns to match
   the homepage rhythm. */
@media (max-width: 600px) {
  .gz-recent-list {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-s);
  }
}

@media (max-width: 380px) {
  .gz-recent-list {
    grid-template-columns: repeat(2, 1fr);
  }
}