/* ════════════════════════════════════════════════════════════════════════
 * 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;
  /* ROOM FOR THE OVERHANG. The preview rect now hangs below the video
   * (see .dpc-preview), and this is the card box growing to contain it —
   * which does two things that are easy to miss. The grid lays out
   * against the real occupied height instead of overlapping the next
   * row, and .dpc-hit (inset:0, absolutely positioned, so it covers the
   * PADDING box) keeps the whole rect tappable including the part
   * hanging past the clip. Without it the bottom of the rect is a dead
   * zone that looks pressable.
   *
   * A percentage, not cqw: this element IS the container, and a
   * container cannot query itself. Percentage padding resolves against
   * the containing block's WIDTH, which is what cqw would have meant
   * here anyway — so the overhang stays proportional at every card size,
   * exactly like the rest of this file.
   *
   * MORE than the 8cqw overhang on .dpc-preview, deliberately, and the
   * extra is in PIXELS rather than percent. The grid virtualises past the
   * first twelve cards with content-visibility: auto (dp-home.css), which
   * turns on PAINT containment — descendants are clipped to the padding
   * box, always, not only while skipped. At exactly 8% the rect's bottom
   * edge lands ON that boundary and its drop shadow is sliced off, so card
   * 13 would render visibly flatter than card 12 on the same screen.
   *
   * The shadow is `0 6px 26px` (see .dpc-preview), which reaches about
   * 6 + 26/2 = 19px below the rect — a fixed distance, because a blur
   * radius does not scale with the card. Expressing the clearance as a
   * percentage meant it was only ever enough on cards wider than ~317px,
   * which is past the 240px ceiling the grid clamps to: it was never
   * enough anywhere. 22px is the shadow plus a little.
   *
   * Keep in step with dp-home.js's --dp-card-h, which reserves the same
   * height for virtualised cards. */
  padding-bottom: calc(8% + 22px);
}

/* ── 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%;
  /* VISIBLE, and deliberately. This was overflow:hidden, which is the
   * obvious way to round a video's corners and also the thing that made
   * an overhanging preview rect impossible — the part below the clip was
   * simply clipped away. So the clipping moves ONTO each layer instead:
   * every child that fills this box carries border-radius:inherit, and
   * the scrim rounds its own bottom corners. Same rounded card, one
   * element that can now hang out of it.
   *
   * If you add a new full-bleed layer in here, give it
   * border-radius:inherit or it will paint square corners over the
   * rounded ones. */
  overflow: visible;
  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;
  border-radius: inherit;       /* .dpc-media no longer clips — see above */
}

.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));
  /* Only the BOTTOM corners: this sits at the foot of the media box, and
   * inheriting all four would round a top edge that is nowhere near it. */
  border-bottom-left-radius: inherit;
  border-bottom-right-radius: inherit;
}

/* 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;
  border-radius: inherit;       /* .dpc-media no longer clips — see above */
  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;
  border-radius: inherit;       /* .dpc-media no longer clips — see above */
  /* 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 hero overlay ──────────────────────────────────────────────────
 * The call to action on a reel tile: a game logo and one big question.
 * Emitted only in `bare` mode and only when the tile has a headline —
 * see dp-attraction-card.js.
 *
 * PLACEMENT IS NOT SET HERE. This block gives the overlay its type, its
 * mark and its scrim; whether it sits at the top or the bottom of the
 * card comes from a modifier on the reel container in dp-home.css, so
 * the two can be swapped with one class. Without one of those modifiers
 * the overlay defaults to the bottom, which is where a single card
 * rendered outside the reel (a preview page, the developer console)
 * should put it.
 *
 * SIZED IN cqw LIKE EVERYTHING ELSE. 11cqw is ~34px on a 310px tile and
 * ~56px on a 506px one — the same optical weight at both ends of the
 * reel's clamp, which a vw-based size would not be. The px clamp only
 * catches the extremes.
 *
 * LEGIBILITY IS THE SCRIM'S JOB, NOT THE SHADOW'S. Footage is
 * unpredictable — a snow level is white, a night level is black — so the
 * gradient does the work and the text shadow is a hairline backstop for
 * the bright frames where even a scrim leaves the ascenders swimming.
 * dp-home.css switches OFF the card's own bottom scrim in the reel, so
 * this is the only one on a hero tile and it moves with the text. */
.dpc-hero {
  position: absolute;
  z-index: 4;
  left: 0;
  right: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;      /* LEFT ALIGNED, both placements */
  gap: 3cqw;
  padding: 7cqw;
  /* The hit target is a sibling that covers the whole card; this must
   * never intercept the tap that opens the panel. */
  pointer-events: none;
}

/* Default placement — see the note above. */
.dpc-hero {
  bottom: 0;
  justify-content: flex-end;
  padding-top: 26cqw;
  background: linear-gradient(to top,
    rgba(0, 0, 0, .68) 0%,
    rgba(0, 0, 0, .42) 34%,
    rgba(0, 0, 0, 0) 100%);
  border-bottom-left-radius: inherit;
  border-bottom-right-radius: inherit;
}

/* NAKED, like the mark on the preview rect one level down: no plate, no
 * ring, just the logo with enough shadow to hold against the footage.
 * Bigger than that one because it is doing more work here — on a reel
 * tile it is the ONLY thing identifying the game.
 *
 * HEIGHT-LOCKED, FREE-WIDTH, NEVER CROPPED. Game marks are not square —
 * Wizard's Way is a wide wordmark, Crash Course is nearly one — and the
 * square `cover` box this used to be cut the ends off both. A logo is a
 * name: half of one is not a smaller logo, it is a mistake. So the mark
 * gets ONE fixed height and whatever width its artwork needs, which is
 * also what makes a row of different marks look like a set — they sit
 * on the same optical line whether they are a circle or a lockup.
 * `object-fit: contain` is the backstop and `aspect-ratio: auto` is the
 * one that matters: it drops the 1:1 ratio the UA stylesheet derives
 * from an <img>'s width/height attributes, so cached markup from before
 * the renderer stopped emitting them still fits instead of cropping.
 * max-width is a fitting guard, not a size — only an extremely wide
 * lockup can reach it, and shrinking beats overhanging the tile. */
.dpc-hero-logo {
  flex: 0 0 auto;
  display: block;
  width: auto;
  height: 17cqw;
  max-height: 84px;
  max-width: 100%;
  aspect-ratio: auto;
  object-fit: contain;
  object-position: left center;
  filter: drop-shadow(0 3px 10px rgba(0, 0, 0, .45));
}

/* THE QUESTION. Tight leading and a negative track because it is set
 * large and left-aligned in two or three words — at 1.0/normal the words
 * read as separate lines rather than one block.
 *
 * EXACTLY TWO LINES, and they are BROKEN IN THE MARKUP: the renderer
 * emits one <span> per line (heroLines() in dp-attraction-card.js), so
 * the shape is the same on every tile instead of depending on where the
 * text happened to run out of width. Nothing here may re-wrap them.
 *
 * NOTHING IS CLIPPED. This used to be a -webkit-line-clamp box, and a
 * clamp crops to the LINE BOX — which at 1.02 leading is shorter than
 * the type, so the descenders of the last line ("Ready", "Fight?") were
 * sliced off along the bottom. There is no third line to defend against
 * now that the break is authored, so the clamp is gone and the leading
 * is 1.08: still tight enough to read as one block, loose enough that a
 * 'y' has somewhere to go. */
.dpc-hero-line {
  margin: 0;
  color: #fff;
  font-family: var(--dpc-font, 'Outfit', system-ui, -apple-system, sans-serif);
  font-size: clamp(21px, 11cqw, 58px);
  font-weight: 800;
  line-height: 1.08;
  letter-spacing: -.03em;
  text-shadow: 0 2px 14px rgba(0, 0, 0, .45);
  overflow-wrap: break-word;
}

/* One line each, and they stay that way. */
.dpc-hero-line > span {
  display: block;
}

/* ── 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 */
  /* CENTRED, AND HANGING OFF THE BOTTOM. It used to sit inside the
   * bottom-left corner, which read as a sticker applied to the clip. On
   * the centre line, straddling the bottom edge, it reads as an object
   * resting ON the video — the same trick the game logo already plays on
   * this rect's own top edge, one level up.
   *
   * -8cqw puts roughly a sixth of the rect below the clip. Any deeper and
   * the name starts floating in the gap between cards instead of hanging
   * off the thing above it; .dpc's padding-bottom is the matching pair
   * and .dp-home.css opens up row-gap so it has somewhere to hang INTO. */
  left: 50%;
  bottom: -8cqw;
  transform: translateX(-50%);
  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. */
/* The hit target is a <button> on a card that opens the panel and an
 * <a href> on a reel LINK tile (the Discord, /download — homeReel's
 * linkType 'url'), so this has to reset both. It is empty either way:
 * the accessible name is on the element, never inside it. */
.dpc-hit {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: block;
  color: inherit;
  text-decoration: none;
  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);
  }
  /* translateX is doing the centring, so the hover scale has to CARRY it.
   * A bare scale(1.03) here replaces the transform outright and snaps the
   * rect back to the left edge on hover. */
  .dpc:hover .dpc-preview { transform: translateX(-50%) 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 { transform: none; }
  /* NOT `none` — that would un-centre the rect. Reduced motion is about
   * dropping the lift, not about moving the thing. */
  .dpc:hover .dpc-preview { transform: translateX(-50%); }
}

/* ── Bare tiles ────────────────────────────────────────────────────────
 * The hero reel renders `bare` (dp-attraction-card.js): just the video,
 * no preview rect, no flag. Nothing hangs off it, so the room reserved
 * for an overhang would just be a gap under every reel tile. */
.dpc--reel { padding-bottom: 0; }

/* ── 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; }
