/* Greybox ads layout — rows, reserved cells, page rails and in-shelf cards
 * for js/ads.js.
 *
 * Contract (pinned by tests/ads.test.cjs):
 *   - .gx-ad-row   flex row, wraps, centers: several same-kind ads sharing
 *                  one placement line up side by side with an even gap
 *                  instead of stacking, drifting or overlapping content.
 *   - .gx-ad-cell  ONE slot box: position:relative + overflow:hidden + a
 *                  translateZ(0) stacking context — absolutely/fixed-
 *                  positioned descendants of ad code are clipped to the
 *                  reserved box, so an ad can never paint over page
 *                  content. --gx-ad-w/--gx-ad-h (unitless px set by
 *                  js/ads.js) reserve the box BEFORE the creative paints
 *                  and clamp proportionally (min(Wpx, 100%)) when the
 *                  column is narrower than the slot's declared size.
 *   - .gx-ad-body  the mounted-code layer, z-indexed above the faint
 *                  "ADVERTISEMENT" label that marks a box still waiting
 *                  for its creative; a mount that stays dead (ad-blocker,
 *                  failed request — the ad never renders) hides its cell
 *                  and its whole row entirely (gx-ad-cell--void / gx-
 *                  ad-row--void below), so blocked users see no shell.
 *   - .gxad-rail   side column used by the search hero and the settings
 *                  page — real grid columns BESIDE the centered content
 *                  (css/search.css / css/settings.css own the grid), so a
 *                  mounted rail ad can never overlap the page. Hidden
 *                  below the widths where a rail fits.
 *   - .gx-ad-card  in-shelf ad card (placement `in-shelf`): lives directly
 *                  in a .gx-shelf-track between real cards with contain:
 *                  size, so the track's HEIGHT stays owned by the real
 *                  cards while the row's stretch gives the ad the cards'
 *                  height; the WIDTH is the operator's per-slot Card
 *                  width (js/ads.js fitShelfCard, weighted per-ad by
 *                  js/pages.js shelf windowing).
 *
 * Every color/edge comes from tokens.css (dark/light themes flip
 * automatically; the search page forces a black canvas but keeps the dark
 * token set, so no per-page overrides live here).
 */

.gx-ad-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 12px;
  width: 100%;
  margin-block: 6px;
}

.gx-ad-cell {
  position: relative;
  overflow: hidden;
  max-width: 100%;
  transform: translateZ(0); /* stacking context + containing block: ad CSS stays inside */
  border: 1px solid var(--gx-border-faint);
  border-radius: 10px;
  background: var(--gx-glass-wash);
}

/* Reserved box: proportional clamp when the column is narrower than the
 * declared size — calc(min(Wpx, 100%) * H / W) keeps the aspect so the row
 * never goes lopsided. Applied only when js/ads.js sets the vars. */
.gx-ad-cell--sized {
  width: min(calc(var(--gx-ad-w) * 1px), 100%);
  height: calc(min(calc(var(--gx-ad-w) * 1px), 100%) * var(--gx-ad-h) / var(--gx-ad-w));
}

/* Faint label under the mounted layer: shows the box is an intentional ad
 * slot while the creative loads (or when an ad-blocker blanks it). */
.gx-ad-cell::before {
  content: 'ADVERTISEMENT';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 3px;
  text-align: center;
  font: 600 9px/1 'Space Grotesk', system-ui, sans-serif;
  letter-spacing: .16em;
  color: var(--gx-text-faint);
  opacity: .6;
  pointer-events: none;
}

.gx-ad-body {
  position: relative;
  z-index: 1;
  text-align: center;
  /* Fill the reserved box so an isolated duplicate (.gx-ad-frame, height
   * 100%) can fill it too — resolves to auto in hugging (unsized) cells. */
  height: 100%;
}

/* Never let a creative's own markup push wider than its reserved cell. */
.gx-ad-body > * {
  max-width: 100%;
}

/* Duplicate-code isolation frame (js/ads.js mountIsolated): a same-origin
 * srcdoc iframe — NO sandbox attribute — so identical ad code gets its own
 * document and renders again, while scripts, target=_blank links and
 * user-gesture popups behave exactly like a raw mount. Sized cells: fills
 * the reserved box; unsized cells get 300x250 inline from JS. */
.gx-ad-frame {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  background: transparent;
}

/* Blocked ads leave NO shell behind (js/ads.js runVoidPass): a cell whose
 * creative produced nothing — ad-blocker aborted the request, network
 * failure, empty renderer — hides itself completely, and a row whose cells
 * are all empty hides with it. Ad-block users therefore see no box, no
 * reserved hole, no white frame and no dead gap; the pass re-runs whenever
 * content arrives, so a late creative un-hides its box the moment it
 * renders. Declared after the base cell/row rules so it wins the cascade. */
.gx-ad-cell--void,
.gx-ad-row--void {
  display: none;
}

/* ---- in-shelf ad cards (placement `in-shelf`, js/ads.js refreshInShelf) ----
 * A boxed ad that lives INSIDE a shelf track between real cards. The cell
 * wears the shelf's card tokens (bg, hairline, radius, contact shadow), so
 * it reads as one of the row's cards, and the flex line's align-items:
 * stretch fits it to the row's real card height. Its WIDTH is the
 * operator's per-slot Card width (slot.shelfWidth — the drawer's px
 * number, applied as-is by js/ads.js fitShelfCard, which also stretches
 * the creative into that box); pages.js weights each ad by its real
 * footprint when mapping scroll position to card indexes.
 *
 * `contain: size` is load-bearing: the cell contributes a zero hypothetical
 * cross size to the flex line, so the track's height stays owned by the
 * real cards (a 600px creative inside can never stretch the row) while
 * stretch still fits the cell to full card height. The clamp below is the
 * fallback box only (a slot with no Card width set). */
.gx-shelf-track > .gx-ad-card {
  flex: 0 0 auto;
  width: clamp(var(--gx-card-min, 176px), calc(var(--gx-card-vw, 14) * 1vw), var(--gx-card-max, 197px));
  contain: size layout;
  border: 1px solid var(--gx-glass-line);
  border-radius: var(--gx-radius-card);
  background: var(--gx-card-bg);
  box-shadow: var(--gx-shadow-card), inset 0 1px 0 rgba(255, 255, 255, 0.07);
}

/* The mounted-code layer fills the stretched card box; anything the
 * creative draws outside the card is clipped (base .gx-ad-cell already
 * provides overflow: hidden + the translateZ(0) stacking context). */
.gx-shelf-track > .gx-ad-card .gx-ad-body {
  height: 100%;
  overflow: hidden;
}

/* ---- side rails (search hero, settings shell) ---- */

.gxad-rail {
  justify-self: center;
  width: 100%;
  max-width: 340px;
  min-width: 0;
}

/* Sticky variant (settings): the rail stretches the full page height so the
 * anchor can travel inside ITS OWN COLUMN while scrolling — it can never
 * cross into the centered content track. */
.gxad-rail--sticky {
  align-self: stretch;
}

.gxad-rail--sticky > .gx-ad-anchor {
  position: sticky;
  top: 104px; /* below the 74px site header + breathing room */
}

@media (prefers-reduced-motion: reduce) {
  .gxad-rail--sticky > .gx-ad-anchor { position: static; }
}
