/* ════════════════════════════════════════════════════════════════════════
 * dp-attraction-feed.css — the Hot/Latest attraction grid, as a component.
 *
 * Lifted verbatim out of dp-home.css so a second page can mount the same
 * grid instead of growing a second copy of it. The rules below are
 * unchanged; the only addition is the token block directly underneath,
 * which is what makes the component work off the white homepage.
 *
 * THE CARD ITSELF IS NOT HERE. public/css/dp-attraction-card.css owns it
 * and is deliberately independent of everything in this file — same card,
 * white page or dark share page. This file owns only the furniture
 * AROUND the cards: the sort tabs, the grid box, the load-more affordance.
 *
 * THEMING IS ONE OPT-IN MODIFIER, AND THE DEFAULT IS "INHERIT".
 * ──────────────────────────────────────────────────────────
 * Every rule below already read --dp-* custom properties that the
 * homepage defines under .dp-light on <body>. `.dp-feed` therefore
 * declares NOTHING: it inherits whatever the host page set, so the white
 * homepage resolves exactly the values it always did.
 *
 * That is not a stylistic preference, it is the bug this shape avoids.
 * The first version put dark defaults on `.dp-feed` "for a dark host,
 * with .dp-light overriding them" — which is backwards. `.dp-light` is on
 * <body> and `.dp-feed` is its DESCENDANT, so the descendant wins for its
 * whole subtree, and the homepage's Show more button came out white on
 * white. A host that actually needs the dark set opts in with
 * `.dp-feed--dark` (views/partials/attraction-feed.ejs: `feedDark`).
 *
 * --dp-gutter is the other one: the homepage runs edge to edge and pays
 * for its own side padding here, while a share page is already inside
 * .sh-wrap's 20px column and must not pay twice. Hence 0 on
 * .dp-feed--flush rather than a negative margin, which is what the first
 * attempt did and it broke the grid's auto-fill maths at the ends.
 * ════════════════════════════════════════════════════════════════════ */

/* A dark host — any /share page. Mapped onto the --sh-* set so the grid
 * picks up that page's own theme colour rather than a second violet. */
.dp-feed--dark {
  --dp-ink: var(--sh-ink, #fff);
  --dp-ink-soft: var(--sh-dim, rgba(235, 228, 245, .62));
  --dp-surface: var(--sh-surface, rgba(255, 255, 255, .03));
  --dp-surface-2: var(--sh-surface-2, rgba(255, 255, 255, .06));
  --dp-line: var(--sh-line, rgba(255, 255, 255, .12));
  --dp-violet: var(--sh-theme, #9d4edd);
  --dp-ease: cubic-bezier(.16, 1, .3, 1);
  /* Only a fallback. A dark host that is ALSO --flush gets 0 from the rule
   * below, which is later in this file and therefore wins at equal
   * specificity. Without a value here the `padding: X var(--dp-gutter) 0`
   * shorthand would be invalid and drop entirely. */
  --dp-gutter: clamp(14px, 3vw, 32px);
}

/* Inside a column that already has side padding (any /share page's
 * .sh-wrap), the grid must not add its own or the cards inset twice and
 * stop lining up with the section headings above them. */
.dp-feed--flush { --dp-gutter: 0px; }

/* ── Hot / Latest ──────────────────────────────────────────────────────
 * The unselected tab is faded rather than greyed so it still reads as a
 * word you can press. The underline is one element that slides — see
 * dp-home.js; it is measured from the active tab, never hard-coded. */
.dp-tabs {
  position: relative;
  display: flex;
  gap: clamp(18px, 3vw, 30px);
  align-items: flex-end;
  padding: clamp(20px, 3.4vw, 34px) var(--dp-gutter) 0;
}
.dp-tab {
  appearance: none;
  -webkit-appearance: none;
  background: none;
  border: 0;
  padding: 0 0 10px;
  margin: 0;
  cursor: pointer;
  /* --dp-ink, NOT inherit. On the white homepage the two are the same
     value, so nothing there changes. On a dark share page `inherit` picked
     up the body's --sh-ink-body, which is white at 78% — so the SELECTED
     tab, the one at full opacity, still read as a muted grey. The ink token
     is the page's actual foreground and resolves to #fff there. */
  color: var(--dp-ink);
  font: 800 clamp(20px, 3.4vw, 30px)/1 'Outfit', system-ui, sans-serif;
  letter-spacing: -.025em;
  opacity: .42;
  transition: opacity .22s ease;
}
.dp-tab[aria-selected="true"] { opacity: 1; }
.dp-tab:focus-visible { outline: 3px solid var(--dp-violet); outline-offset: 4px; border-radius: 4px; }
.dp-tabs-underline {
  position: absolute;
  bottom: 0;
  /* left:0, NOT the gutter. dp-home.js positions this purely with
   * translateX measured from the tabs container's own box, so any CSS
   * inset here is added on top and pushes the bar one gutter too far. */
  left: 0;
  width: 0;
  height: 3px;
  border-radius: 2px;
  background: currentColor;
  transform: translateX(0);
  transition: transform .24s var(--dp-ease), width .24s var(--dp-ease);
  pointer-events: none;
}

/* ── The grid ──────────────────────────────────────────────────────────
 * auto-fill + a clamped minimum, so the column count is a consequence of
 * the viewport rather than a set of breakpoints to maintain. */
.dp-grid {
  display: grid;
  /* TWO gaps, not one, and the row gap is the bigger of them. The preview
   * rect hangs below its clip now (.dpc-preview in dp-attraction-card.css)
   * and it needs somewhere to hang INTO — at the old uniform gap the
   * overhanging rect of one row nearly touched the clip of the next, which
   * is the thing that made the corner placement look safer. Columns stay
   * tighter because nothing overhangs sideways. */
  column-gap: clamp(10px, 2vw, 20px);
  row-gap: clamp(26px, 4vw, 46px);
  grid-template-columns: repeat(auto-fill, minmax(clamp(148px, 21vw, 240px), 1fr));
  padding: clamp(14px, 2.4vw, 24px) var(--dp-gutter) 0;
}

/* content-visibility keeps a several-hundred-card grid at 60fps. Two
 * things about it are easy to get wrong and both were measured:
 *
 *  · It must not apply to the cards that are on screen at first paint.
 *    Un-rendering an off-screen subtree of a card the browser has
 *    already laid out is recorded as a layout shift — 0.25 CLS from the
 *    one line meant to make scrolling smoother.
 *
 *  · contain-intrinsic-size has to be RIGHT. The column width is
 *    auto-filled, so no fixed px guess can be; dp-home.js keeps
 *    --dp-card-h exact from a real measurement (the card is now simply
 *    its 9:16 media) and re-measures on resize.
 *
 * POSITION, not provenance. This used to be a `dpc--virt` class that
 * dp-home.js added to client-appended cards only — which meant Hot
 * (server-rendered first page) and Latest (fetched and appended) were
 * literally two differently-styled grids of the same card. They are one
 * grid now: the rule keys off nth-child, so card 13 onwards virtualises
 * whether it arrived in the HTML or over the wire, and cards 1-12 never
 * do. Twelve covers the first screenful at every column count from 2-up
 * (six rows) to 9-up (one and a bit). */
.dp-grid > .dpc:nth-child(n + 13) {
  content-visibility: auto;
  contain-intrinsic-size: auto var(--dp-card-h, 430px);
}

/* The infinite-scroll trigger. Deliberately paints nothing: an element
 * that renders no pixels can be moved down the page by an append without
 * the browser recording a layout shift, which a visible "Show more"
 * button in the same position cannot. */
.dp-sentinel { height: 1px; margin: 0; padding: 0; pointer-events: none; }

/* Only ever drawn when a host page passes `feedEmpty`. The homepage does
 * not: an empty catalogue there is an outage, not a state worth wording.
 * A creator with no published attractions yet is a real, ordinary state. */
.dp-feed-empty {
  margin: 0;
  padding: clamp(28px, 6vw, 56px) var(--dp-gutter);
  text-align: center;
  color: var(--dp-ink-soft);
  font: 500 15px/1.6 'Outfit', system-ui, sans-serif;
}

/* Placeholder for a page still in flight. Must match a real card's height
 * EXACTLY — which is now simply its 9:16 media — or reserving the space
 * defeats its own purpose. */
.dpc--skel { pointer-events: none; }
.dpc--skel .dpc-media { background: var(--dp-surface-2); }

.dp-more {
  display: flex;
  justify-content: center;
  padding: clamp(24px, 4vw, 44px) var(--dp-gutter) clamp(48px, 8vw, 96px);
}
.dp-more[hidden] { display: none; }
.dp-more-btn {
  appearance: none;
  border: 1px solid var(--dp-line);
  background: var(--dp-surface);
  color: var(--dp-ink);
  border-radius: 999px;
  padding: 12px 26px;
  cursor: pointer;
  font: 700 13.5px/1 'Outfit', system-ui, sans-serif;
  transition: border-color .18s ease, background .18s ease;
}
.dp-more-btn:hover { border-color: var(--dp-violet); background: var(--dp-surface-2); }
.dp-more-btn:disabled { opacity: .45; cursor: default; }
.dp-more-btn:focus-visible { outline: 3px solid var(--dp-violet); outline-offset: 3px; }

@media (prefers-reduced-motion: reduce) {
  /* dp-home.css kills the same two transitions for the homepage; repeated
   * here because a share page mounting this component does not load that
   * sheet, and a sliding underline is exactly the kind of motion this
   * preference is about. */
  .dp-tab, .dp-tabs-underline { transition: none; }
}
