/* ════════════════════════════════════════════════════════════════════════
 * dp-attraction-card.css — styles for the card emitted by
 * public/js/dp-attraction-card.js. Read that file's header first.
 *
 * THE ONE TECHNIQUE THAT MATTERS: this card has NO media queries and NO
 * vw units. `.dpc` is a container (container-type: inline-size) and every
 * internal dimension is expressed in cqw — percentages of the card's own
 * width. So the card is proportionally IDENTICAL at 180px in a 6-up
 * desktop grid, at 300px in a 2-up mobile grid, and at 260px in the hero
 * reel, with zero per-breakpoint tuning. Anything that reads correctly at
 * one viewport width and wrong at another means a vw or a media query got
 * added here — that is the bug, not the symptom.
 *
 * Corollary: the card is never sized from the OUTSIDE by height. Layout
 * containers (public/css/dp-home.css) set its width; the aspect-ratio
 * does the rest. The reel derives a width from its row height for exactly
 * this reason.
 *
 * Every selector is .dpc-prefixed with no element selectors and no
 * inherited-cascade dependencies, so the same card renders correctly on
 * the white homepage AND inside the dark pop-out panel.
 * ════════════════════════════════════════════════════════════════════ */

.dpc {
  container-type: inline-size;
  container-name: dpc;
  position: relative;
  display: block;
  margin: 0;
  min-width: 0;                 /* survives being a flex/grid item */
  -webkit-tap-highlight-color: transparent;
}

/* ── Media stack ───────────────────────────────────────────────────────
 * aspect-ratio reserves the box before any bytes arrive. Combined with
 * width/height on every <img> and a fixed-height chip strip, the card
 * contributes exactly zero to CLS. */
.dpc-media {
  position: relative;
  aspect-ratio: 9 / 16;
  width: 100%;
  overflow: hidden;
  border-radius: clamp(14px, 7cqw, 26px);
  background: #f1f1f4;
  isolation: isolate;           /* keeps the preview rect's shadow local */
}

.dpc-poster,
.dpc-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  border: 0;
}

.dpc-poster { z-index: 1; }

/* Bottom scrim. The floating preview rect has to read as a deliberate
 * inset over ANY clip — a bright sky, a white-walled room. Kept subtle
 * enough that it never looks like a gradient slapped on the artwork. */
.dpc-media::after {
  content: "";
  position: absolute;
  z-index: 3;
  left: 0;
  right: 0;
  bottom: 0;
  height: 48%;
  pointer-events: none;
  background: linear-gradient(to top, rgba(0, 0, 0, .36), rgba(0, 0, 0, 0));
}

/* The video composites OVER the poster and fades in only once it is
 * genuinely playing (dp-video-director.js sets data-dpv). The poster is
 * never removed, so there is no black frame and no swap flash — and when
 * the director revokes the slot and strips the src, the poster is simply
 * revealed again. */
.dpc-video {
  z-index: 2;
  opacity: 0;
  transition: opacity .28s ease;
  pointer-events: none;
}
.dpc-video[data-dpv="playing"] { opacity: 1; }

/* ── No-clip state ─────────────────────────────────────────────────────
 * Attractions without a clip still get a card — the preview rect renders
 * and this fills the video slot. Temporary by design while clips are
 * being uploaded, so it should read as "loading", not as "broken". */
.dpc-shimmer {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: block;
  background: linear-gradient(100deg, #f3f3f5 20%, #e8e8ee 40%, #f3f3f5 60%);
  background-size: 220% 100%;
  animation: dpcShimmer 1.7s linear infinite;
}
@keyframes dpcShimmer {
  from { background-position: 140% 0; }
  to   { background-position: -40% 0; }
}

/* THE BORROWED STILL. An out-of-focus frame from public/video-placeholders,
 * picked per card in dp-attraction-card.js — read the placeholder note
 * there for why a clipless card gets a picture at all.
 *
 * It sits at the shimmer's z-index and later in DOM order, so it simply
 * covers the shimmer once it decodes: no swap, no flash, and the shimmer
 * is still what fills the box in the milliseconds before that. */
.dpc-placeholder {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border: 0;
  /* The stills are already blurred; this only takes the edge off the
   * colour so a placeholder can never out-shout a real clip sitting
   * next to it in the same grid row. */
  filter: saturate(.8) brightness(.94);
}

/* THE SPINNER — and it never completes, because nothing is loading. See
 * the placeholder note in dp-attraction-card.js.
 *
 * Subtle is the whole brief: a hairline ring with one brighter quarter,
 * white so it reads over any of the stills, and small enough that it
 * says "not yet" rather than demanding attention. Sized in cqw like
 * everything else on this card, so it holds its proportion from a 6-up
 * desktop grid down to a 2-up mobile one.
 *
 * Dead centre is safe at every width: the preview rect's top edge is
 * 55cqw off the bottom of a 177cqw-tall box, i.e. ~69% down, so a ring
 * centred at 50% clears it by roughly a fifth of the card's height. */
.dpc-spinner {
  position: absolute;
  z-index: 4;
  top: 50%;
  left: 50%;
  box-sizing: border-box;
  width: clamp(20px, 11cqw, 38px);
  height: clamp(20px, 11cqw, 38px);
  transform: translate(-50%, -50%);
  border-radius: 50%;
  border: clamp(2px, .9cqw, 3px) solid rgba(255, 255, 255, .34);
  border-top-color: #fff;
  /* The stills range from a bright beach to a dark arcade, so the ring
   * needs a shadow to stay readable on both — not decoration. */
  filter: drop-shadow(0 1px 5px rgba(11, 11, 15, .45));
  animation: dpcSpin .9s linear infinite;
  pointer-events: none;
}

/* The translate has to be repeated inside the keyframes: an animated
 * transform replaces the static one outright, and without it the ring
 * jumps a half-width down and right the moment the animation starts. */
@keyframes dpcSpin {
  from { transform: translate(-50%, -50%) rotate(0deg); }
  to   { transform: translate(-50%, -50%) rotate(360deg); }
}

/* ── Experimental flag ─────────────────────────────────────────────────
 * Top-left, small, and above the scrim so it survives a bright clip. The
 * only words on a card by design — see the note in dp-attraction-card.js
 * about why this one earns its place. */
.dpc-flag {
  position: absolute;
  z-index: 4;
  top: 5cqw;
  left: 5cqw;
  padding: 0 8px;
  height: 20px;
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  background: rgba(11, 11, 15, .62);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: #fff;
  font-family: var(--dpc-font, 'Outfit', system-ui, -apple-system, sans-serif);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: none;
}

/* ── The preview rect ──────────────────────────────────────────────────
 * A square card-within-the-card: what the attraction IS, sitting on a
 * clip of what it FEELS like.
 *
 * PROPORTION, worked out rather than nudged. Call the rect's side R
 * (= 50cqw — half the card's width, big enough to read as a real object
 * rather than a stamp in the corner, small enough that the clip behind
 * it is still the thing you are looking at).
 *
 * Everything inside is sized in cqw of the CARD, not of the rect, so
 * shrinking R does not shrink the type with it. When R last changed the
 * logo and the name padding were re-cut to match, which is why the
 * interior percentages below are the same as they were at 58cqw.
 *
 *   logo   0.24R, centred ON the top edge — half of it (0.12R) is the
 *          only intrusion into the interior. Naked: no plate, no ring.
 *   name   EXACTLY ONE LINE, always, pinned to the bottom. One line is
 *          what makes it consistent: every card's name baseline is at
 *          the same height and every card's art box is the same size,
 *          so a five-word attraction can never push the art around or
 *          spill past the rounded corner. Long names truncate with an
 *          ellipsis; the full name is one tap away in the panel.
 *   art    everything else — 0.63R tall x 0.87R wide, centred on both
 *          axes so the subject sits in the middle of its box whatever
 *          the source aspect ratio is. The largest element in the rect
 *          by a wide margin, which is the entire point of the rect.
 *
 * Three earlier versions failed the same way: 12cqw of dead air above
 * the art; then a two-line RESERVED name block eating 35% of the
 * height; then a fluid two-line clamp that top-aligned short names and
 * let long ones spill outside the rect. All three left the preview art
 * as the smallest thing in its own box. Numbers above are measured at
 * 390/430/744/834/1024/1280/1440/1920/2560, not eyeballed.
 * ───────────────────────────────────────────────────────────────── */
.dpc-preview {
  position: absolute;
  z-index: 4;
  box-sizing: border-box;       /* the 2px border is INSIDE R — no drift */
  left: 5cqw;
  bottom: 5cqw;
  width: 50cqw;
  aspect-ratio: 1;
  margin: 0;
  /* visible, not hidden: the logo straddles the top edge on purpose. */
  overflow: visible;
  border-radius: clamp(12px, 6cqw, 24px);
  /* GLASS, not a white card. The recipe is --mdp-glass-* in
   * public/css/mydreampark-ui.css — the same material as the sign-in
   * badge — with the values repeated as fallbacks because this file has
   * to render correctly on its own (see the header). Semi-transparent
   * and blurred, so the clip behind it reads through as fog instead of
   * being covered up, and a 1px inset white top edge for the lip. */
  border: 1px solid var(--mdp-glass-border, rgba(255, 255, 255, .55));
  background: var(--mdp-glass-bg,
    linear-gradient(180deg, rgba(255, 255, 255, .86) 0%, rgba(255, 255, 255, .64) 100%));
  -webkit-backdrop-filter: blur(var(--mdp-glass-blur, 18px)) saturate(160%);
  backdrop-filter: blur(var(--mdp-glass-blur, 18px)) saturate(160%);
  box-shadow:
    inset 0 1px 0 var(--mdp-glass-top, rgba(255, 255, 255, .95)),
    0 6px 26px rgba(0, 0, 0, .3);
  display: flex;
  flex-direction: column;
}

/* NAKED on the edge — no white ring, no plate. Just the mark, half on
 * and half off, with enough shadow to hold its own against whatever the
 * clip behind it is doing. */
.dpc-preview-logo {
  position: absolute;
  z-index: 2;
  top: 0;
  left: 50%;
  width: 12cqw;
  height: 12cqw;
  max-width: 56px;
  max-height: 56px;
  transform: translate(-50%, -50%);
  border-radius: 26%;
  object-fit: cover;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, .35));
}

/* THE SUBJECT. Takes every pixel the logo and the single-line name do
 * not, and centres the image inside it on both axes — so a 16:9 render,
 * a square icon and a tall portrait all sit dead centre in the same box
 * instead of each hanging off a different edge. The top margin clears
 * the half-logo (7cqw) plus a hair of breathing room. */
/* THE IMAGE IS ABSOLUTELY POSITIONED, and that is load-bearing rather
 * than stylistic. An in-flow <img> with height:100% inside a flex item
 * of indefinite height falls back to the source's INTRINSIC height, the
 * flex item then content-sizes to it, and the rect's aspect-ratio:1
 * loses — a 540x960 preview stretched the rect to twice its width and a
 * 960x540 one squashed it, so every card in the grid was a different
 * shape. Out of flow, the image contributes nothing to layout: the art
 * box takes exactly the space the logo and the name leave it, and the
 * rect is square by construction at every card width. */
.dpc-preview-art {
  flex: 1 1 auto;
  min-height: 0;
  position: relative;
  display: block;
  margin: 6.5cqw 2.5cqw 0;
  overflow: hidden;
}
.dpc-preview-art img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  /* contain, never cover: preview art is a product shot of the
   * attraction, and cropping it cuts the object in half. The box is as
   * wide as the rect allows so landscape sources — the common case —
   * letterbox as little as possible, and object-position centres the
   * painted result on both axes for every source aspect. */
  object-fit: contain;
  object-position: center;
}

/* ONE line, always. Fixed height is what keeps the art box identical on
 * every card and makes it impossible for a long name to escape the
 * rect. nowrap + ellipsis rather than -webkit-line-clamp: line-clamp
 * needs a -webkit-box, which does not clip reliably next to a flex
 * sibling — that is how "Wizards Way Dream… Sequence" ended up printed
 * outside the rounded corner. */
.dpc-preview-name {
  flex: 0 0 auto;
  /* Side padding kept deliberately tight (1.5cqw, not the 3cqw the art
   * gets): every pixel of it is a character that truncates. At 1.5cqw a
   * 14-character name like "Snow Man Ruins" fits whole; at 3cqw it did
   * not. */
  padding: 0.5cqw 1.5cqw 2cqw;
  color: #0b0b0f;
  font-family: var(--dpc-font, 'Outfit', system-ui, -apple-system, sans-serif);
  /* Smaller than it was. At 7cqw a two-word attraction ellipsised on
   * most card widths — "Wizards Way Dream Sequence" showed as
   * "Wizards Wa…" — which is the opposite of readable. */
  font-size: clamp(10px, 5cqw, 18px);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -.015em;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  align-self: stretch;
}

/* ── Hit target ────────────────────────────────────────────────────────
 * Last in DOM order, covers the whole card. This is the only focusable
 * thing in a card and it carries the entire accessible name. */
.dpc-hit {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: block;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  border-radius: clamp(14px, 7cqw, 26px);
  appearance: none;
  -webkit-appearance: none;
}
.dpc-hit:focus { outline: none; }
.dpc-hit:focus-visible {
  outline: 3px solid var(--dpc-focus, #7700ff);
  outline-offset: 3px;
}

/* ── Interaction ───────────────────────────────────────────────────────
 * Transform on .dpc-media only, never on .dpc — transforming the
 * container would create a containing block and break the absolutely
 * positioned hit target's inset in some engines. */
@media (hover: hover) and (pointer: fine) {
  .dpc:hover .dpc-media {
    transform: translateY(-2px);
    box-shadow: 0 12px 34px rgba(11, 11, 15, .14);
  }
  .dpc:hover .dpc-preview { transform: scale(1.03); }
}
.dpc-media,
.dpc-preview {
  transition: transform .24s cubic-bezier(.16, 1, .3, 1), box-shadow .24s ease;
}
.dpc:active .dpc-media { transform: translateY(0) scale(.988); }

/* ── Reduced motion ────────────────────────────────────────────────────
 * The director already refuses to grant any play slot under this setting,
 * so no video ever fades in. Here we flatten the shimmer (an infinite
 * animation is exactly what this setting is about) and drop the lift. */
@media (prefers-reduced-motion: reduce) {
  .dpc-shimmer { animation: none; background: #f0f0f3; }
  /* Still a ring, still says "nothing here yet" — just not turning. An
   * even stroke, since a brighter quarter with no rotation reads as a
   * design flourish rather than as a spinner. */
  .dpc-spinner {
    animation: none;
    border-color: rgba(255, 255, 255, .5);
  }
  .dpc-media,
  .dpc-preview,
  .dpc-video { transition: none; }
  .dpc:hover .dpc-media,
  .dpc:active .dpc-media,
  .dpc:hover .dpc-preview { transform: none; }
}

/* ── Dark surfaces (the pop-out panel) ─────────────────────────────────
 * The card is cascade-independent by design; a dark host sets these four
 * custom properties and nothing else changes. */
.dpc--on-dark {
  --dpc-focus: #c77dff;
}
.dpc--on-dark .dpc-media { background: #1a1426; }
.dpc--on-dark .dpc-shimmer {
  background: linear-gradient(100deg, #1d1729 20%, #292039 40%, #1d1729 60%);
  background-size: 220% 100%;
}
.dpc--on-dark .dpc-preview {
  border-color: rgba(255, 255, 255, .22);
  background: #1a1426;
}
.dpc--on-dark .dpc-preview-name { color: #f4f2ff; }
