.mPreview_card {
  --mPreview-font-family: inherit;
  --mPreview-font-size: 14px;
  --mPreview-text-color: #111827;
  --mPreview-muted-color: #6b7280;
  --mPreview-bg: #ffffff;
  --mPreview-border: 1px solid #e5e7eb;
  --mPreview-radius: 8px;
  --mPreview-thumb-size: 96px;
  --mPreview-gap: 12px;
  /* --mPreview-max-width default provided via fallback in var() below so
     outer wrappers can override by setting the custom property without
     having to fight specificity. */

  display: block;
  max-width: var(--mPreview-max-width, 520px);
  background: var(--mPreview-bg);
  border: var(--mPreview-border);
  border-radius: var(--mPreview-radius);
  font-family: var(--mPreview-font-family);
  font-size: var(--mPreview-font-size);
  color: var(--mPreview-text-color);
  overflow: hidden;
  box-sizing: border-box;
}

.mPreview_card *,
.mPreview_card *::before,
.mPreview_card *::after {
  box-sizing: border-box;
}

.mPreview_link {
  display: flex;
  gap: var(--mPreview-gap);
  padding: var(--mPreview-gap);
  text-decoration: none;
  color: inherit;
}

.mPreview_card[data-variant="hero"] .mPreview_link {
  display: block;
  padding: 0;
}

.mPreview_link--facade {
  position: relative;
  display: block;
  padding: 0;
  cursor: pointer;
}

.mPreview_play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 64px;
  height: 64px;
  opacity: 0.9;
  transition: opacity 120ms ease-out, transform 120ms ease-out;
  filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.4));
  pointer-events: none;
  /* Above both the blurred backdrop (z-index 0) and the foreground
     poster/video (z-index 1) in full variant; also stacks correctly
     on hero where there is no backdrop. */
  z-index: 2;
}

.mPreview_link--facade:hover .mPreview_play,
.mPreview_link--facade:focus-visible .mPreview_play {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1.08);
}

.mPreview_play svg {
  width: 100%;
  height: 100%;
  display: block;
}

.mPreview_iframe {
  display: block;
  width: 100%;
  border: 0;
  background: #000;
}

.mPreview_card[data-embed] {
  --mPreview-embed-ratio: 16 / 9;
}

.mPreview_card[data-embed^="twitter"] {
  --mPreview-embed-ratio: 1 / 1;
}

/* Rich variant honours the provider's native aspect ratio (16/9 YouTube,
   1/1 Twitter). Hero variant is always a square slot — see below. */
.mPreview_card[data-variant="rich"] .mPreview_iframe {
  aspect-ratio: var(--mPreview-embed-ratio);
}

.mPreview_thumb {
  flex: 0 0 var(--mPreview-thumb-size);
  width: var(--mPreview-thumb-size);
  height: var(--mPreview-thumb-size);
  object-fit: cover;
  border-radius: calc(var(--mPreview-radius) - 2px);
  background: #f3f4f6;
}

.mPreview_hero {
  display: block;
  width: 100%;
  aspect-ratio: 1.91 / 1;
  object-fit: cover;
  background: #f3f4f6;
}

/* Hero + full variants: outer card has a fixed media area at the top and
   a body below. Hero is a 1:1 square (65/35 split). Full is wider and
   keeps the same split model (no fixed aspect — caller sizes the column).
   Media (image or video) renders at its natural aspect ratio inside the
   media area via object-fit: contain, so a square video is centred at
   ~65% width (no clipping, no full-width squash), landscape video is
   width-limited with letterbox bars, portrait video is height-limited
   and centred horizontally. */
/* ---------- Hero variant: 1:1 card, 65%/35% media/body split ----------- */
.mPreview_card[data-variant="hero"] {
  display: flex;
  flex-direction: column;
  aspect-ratio: 1 / 1;
}
.mPreview_card[data-variant="hero"] > .mPreview_link,
.mPreview_card[data-variant="hero"] > .mPreview_link--facade,
.mPreview_card[data-variant="hero"] > .mPreview_videoSlot,
.mPreview_card[data-variant="hero"] > .mPreview_iframeSlot {
  /* Media area is 56% of the card's 1:1 height, i.e. 16:9 wide. Leaves
     44% for body text so a 2-line title + 2-line description no longer
     overlaps when the card is squeezed narrow. */
  flex: 0 0 56%;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: #f3f4f6;
}
.mPreview_card[data-variant="hero"] .mPreview_hero,
.mPreview_card[data-variant="hero"] .mPreview_video {
  /* Default: fill the 16:9 slot via cover — side-clipping is acceptable
     for hero images at 1.91:1 (small crop) and for YouTube (no backdrop
     available). Twitter overrides to contain below, letting its blurred
     backdrop fill the letterbox space (square/portrait video mustn't be
     cropped). */
  width: 100%;
  height: 100%;
  aspect-ratio: unset;
  object-fit: cover;
  background: #000;
}
/* Twitter in hero: same treatment as full — foreground contain + blurred
   backdrop. Square/portrait video shows in full, extra slot space is
   filled with a blurred copy of the video frame. */
.mPreview_card[data-variant="hero"][data-embed^="twitter"] .mPreview_link--facade > .mPreview_hero:not(.mPreview_hero--backdrop),
.mPreview_card[data-variant="hero"][data-embed^="twitter"] .mPreview_video:not(.mPreview_video--backdrop) {
  position: relative;
  object-fit: contain;
  z-index: 1;
  /* Transparent so the blurred backdrop shows through letterbox bars.
     Overrides the black background set by the general hero img rule
     and the base video element rule. */
  background: transparent;
}
.mPreview_card[data-variant="hero"][data-embed^="twitter"] .mPreview_hero--backdrop,
.mPreview_card[data-variant="hero"][data-embed^="twitter"] .mPreview_video--backdrop {
  display: block;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  aspect-ratio: unset;
  object-fit: cover;
  filter: blur(24px);
  transform: scale(1.15);
  z-index: 0;
  pointer-events: none;
  background: transparent;
}
.mPreview_card[data-variant="hero"] .mPreview_iframe {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  aspect-ratio: var(--mPreview-embed-ratio);
}
.mPreview_card[data-variant="hero"] > .mPreview_body {
  flex: 1 1 44%;
  overflow: hidden;
  min-height: 0;
}
.mPreview_card[data-variant="hero"] .mPreview_hero--textTweet {
  width: 100%;
  height: 100%;
  max-width: unset;
  max-height: unset;
}

/* ---------- Full variant: natural height, media flush top/sides ------- */
/* Full cards flow naturally: hero image at its own aspect ratio across
   the full card width with no padding or dark letterbox bars, body below
   with regular text padding. Video/iframe providers still use a slot so
   the facade play button has a predictable box, but the slot adopts the
   media's native aspect (16:9 YouTube, 1:1 Twitter) rather than a forced
   1.91/1. */
.mPreview_card[data-variant="full"] {
  display: flex;
  flex-direction: column;
}
.mPreview_card[data-variant="full"] > .mPreview_link {
  display: block;
  width: 100%;
  padding: 0;
  background: transparent;
}
.mPreview_card[data-variant="full"] > .mPreview_link > .mPreview_hero {
  width: 100%;
  height: auto;
  aspect-ratio: unset;
  object-fit: unset;
  background: transparent;
  display: block;
}
.mPreview_card[data-variant="full"] > .mPreview_link--facade,
.mPreview_card[data-variant="full"] > .mPreview_videoSlot,
.mPreview_card[data-variant="full"] > .mPreview_iframeSlot {
  /* All full-variant media slots are 16:9 regardless of provider so
     Twitter (square/portrait) and YouTube (landscape) land in the same
     visual box. Square/portrait content is letterboxed via contain and
     the empty space is filled by a blurred copy of the same frame
     (Instagram/TikTok pattern) via .mPreview_hero--backdrop /
     .mPreview_video--backdrop. */
  position: relative;
  display: block;
  width: 100%;
  padding: 0;
  overflow: hidden;
  background: #000;
  aspect-ratio: 16 / 9;
}
.mPreview_card[data-variant="full"] .mPreview_link--facade > .mPreview_hero:not(.mPreview_hero--backdrop),
.mPreview_card[data-variant="full"] .mPreview_video:not(.mPreview_video--backdrop) {
  position: relative;
  width: 100%;
  height: 100%;
  aspect-ratio: unset;
  object-fit: contain;
  display: block;
  z-index: 1;
  /* Foreground must be transparent so the blurred backdrop shows through
     the letterbox bars. Without this, the base .mPreview_video rule
     paints the element black and hides the backdrop. */
  background: transparent;
}
/* YouTube posters are 1.91:1 (our Bunny hero variant) in a 16:9 slot —
   contain would leave thin black bars top/bottom. We have no blurred
   backdrop on YouTube, so use cover instead and accept a small side crop. */
.mPreview_card[data-variant="full"][data-embed^="youtube"] .mPreview_link--facade > .mPreview_hero:not(.mPreview_hero--backdrop) {
  object-fit: cover;
}
/* Blurred backdrop: cover-fills the slot, heavily blurred, scaled up so
   the blurred edges don't reveal the slot border. Sits behind the
   foreground via z-index. Hidden by default in other variants. */
.mPreview_hero--backdrop,
.mPreview_video--backdrop {
  display: none;
}
.mPreview_card[data-variant="full"] .mPreview_hero--backdrop,
.mPreview_card[data-variant="full"] .mPreview_video--backdrop {
  display: block;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  aspect-ratio: unset;
  object-fit: cover;
  filter: blur(24px);
  transform: scale(1.15);
  z-index: 0;
  pointer-events: none;
  background: transparent;
}
.mPreview_card[data-variant="full"] .mPreview_iframe {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}
.mPreview_card[data-variant="full"] > .mPreview_body {
  overflow: visible;
}
/* Text-tweet placeholder keeps a defined shape since it has no intrinsic
   height. */
.mPreview_card[data-variant="full"] .mPreview_hero--textTweet {
  width: 100%;
  aspect-ratio: 1.91 / 1;
  height: auto;
  max-width: unset;
  max-height: unset;
}

/* ---------- Rich variant: 550px max, natural height, flush media ----- */
/* Rich caps at the Twitter widget's max (550px) so the X iframe fits
   whatever column it's in. Non-provider rich reuses hero's chrome but
   skips the 1:1 aspect and the media-slot padding — image renders at
   its natural ratio flush with the top and sides. Provider video/iframe
   slots get their native aspect. */
.mPreview_card[data-variant="rich"] {
  display: flex;
  flex-direction: column;
  max-width: 550px;
}
.mPreview_card[data-variant="rich"] > .mPreview_link {
  display: block;
  width: 100%;
  padding: 0;
  background: transparent;
}
.mPreview_card[data-variant="rich"] > .mPreview_link > .mPreview_hero {
  width: 100%;
  height: auto;
  aspect-ratio: unset;
  object-fit: unset;
  background: transparent;
  display: block;
}
.mPreview_card[data-variant="rich"] > .mPreview_link--facade,
.mPreview_card[data-variant="rich"] > .mPreview_videoSlot,
.mPreview_card[data-variant="rich"] > .mPreview_iframeSlot {
  position: relative;
  display: block;
  width: 100%;
  padding: 0;
  overflow: hidden;
  background: #000;
  aspect-ratio: var(--mPreview-embed-ratio, 16 / 9);
}
.mPreview_card[data-variant="rich"] .mPreview_link--facade > .mPreview_hero,
.mPreview_card[data-variant="rich"] .mPreview_video {
  width: 100%;
  height: 100%;
  aspect-ratio: unset;
  object-fit: cover;
  display: block;
}
.mPreview_card[data-variant="rich"] > .mPreview_body {
  overflow: visible;
}
.mPreview_card[data-variant="rich"] .mPreview_hero--textTweet {
  width: 100%;
  aspect-ratio: 1.91 / 1;
  height: auto;
  max-width: unset;
  max-height: unset;
}

.mPreview_body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.mPreview_card[data-variant="hero"] .mPreview_body {
  padding: var(--mPreview-gap);
}

.mPreview_title {
  margin: 0;
  font-size: 1em;
  font-weight: 600;
  line-height: 1.3;
  color: var(--mPreview-text-color);
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.mPreview_title a {
  color: inherit;
  text-decoration: none;
}

.mPreview_title a:hover {
  text-decoration: underline;
}

.mPreview_description {
  margin: 0;
  font-size: 0.92em;
  line-height: 1.4;
  color: var(--mPreview-muted-color);
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.mPreview_meta {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: auto;
  padding-top: 4px;
  font-size: 0.85em;
  color: var(--mPreview-muted-color);
}

.mPreview_logo {
  width: 16px;
  height: 16px;
  object-fit: contain;
  border-radius: 2px;
}

.mPreview_site {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Video element after facade click — the hero-variant scoped rules above
   control layout. This keeps base styles for contexts where a video
   appears outside hero (future full/rich expansion). */
.mPreview_video {
  display: block;
  background: #000;
}
.mPreview_videoSlot {
  display: block;
}

/* Photo-count badge for multi-photo tweets (+N in the corner of the hero). */
.mPreview_card {
  position: relative;
}
.mPreview_photoCount {
  position: absolute;
  bottom: var(--mPreview-gap);
  right: var(--mPreview-gap);
  background: rgba(0, 0, 0, 0.7);
  color: #fff;
  padding: 2px 8px;
  border-radius: 12px;
  font-size: 0.85em;
  font-weight: 600;
  pointer-events: none;
}

/* Text-only tweet: CSS-composed hero placeholder. Neutral bg, centred
   avatar, X logo in the corner. No server-side image generation. */
.mPreview_hero--textTweet {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  aspect-ratio: 1.91 / 1;
  background: #f5f8fa;
  position: relative;
  object-fit: unset;
}
.mPreview_hero--textTweet .mPreview_tweetAvatar {
  width: 30%;
  max-width: 96px;
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  object-fit: cover;
  background: #e1e8ed;
}
.mPreview_xLogo {
  position: absolute;
  bottom: 12px;
  right: 12px;
  width: 20px;
  height: 20px;
  color: #0f1419;
  opacity: 0.85;
}

/* Tweet body chrome (shown under video/photo/text hero, not under rich). */
.mPreview_tweetAuthor {
  display: flex;
  gap: 6px;
  align-items: baseline;
  font-size: 0.9em;
  line-height: 1.3;
}
.mPreview_tweetName {
  font-weight: 600;
  color: var(--mPreview-text-color);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mPreview_tweetHandle {
  color: var(--mPreview-muted-color);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mPreview_tweetText {
  /* Allow tweets to show more text than the generic 2-line description. */
  -webkit-line-clamp: 4;
}

/* ---------- layout=square ------------------------------------------------
   Media-dominant 1:1 card for uniform masonry grids. Hero fills the whole
   slot via object-fit: cover; title overlays the bottom with a black→clear
   gradient scrim. Auto-contrast text colouring and blurred-backdrop
   letterboxing are deferred to a follow-up. */
.mPreview_card[data-layout="square"] {
  aspect-ratio: 1 / 1;
  overflow: hidden;
  display: block;
  padding: 0;
  background: #0f1419;
}
.mPreview_card[data-layout="square"] .mPreview_link,
.mPreview_card[data-layout="square"] .mPreview_link--facade {
  display: block;
  position: absolute;
  inset: 0;
}
.mPreview_card[data-layout="square"] .mPreview_hero,
.mPreview_card[data-layout="square"] .mPreview_video {
  width: 100%;
  height: 100%;
  aspect-ratio: unset;
  object-fit: cover;
}
.mPreview_card[data-layout="square"] .mPreview_hero--textTweet {
  aspect-ratio: unset;
  height: 100%;
}
.mPreview_card[data-layout="square"] .mPreview_body {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: var(--mPreview-gap);
  background: linear-gradient(to top, rgba(0, 0, 0, 0.85) 0%, rgba(0, 0, 0, 0.55) 55%, transparent 100%);
  color: #fff;
  gap: 2px;
  pointer-events: none;
}
.mPreview_card[data-layout="square"] .mPreview_body a {
  pointer-events: auto;
}
.mPreview_card[data-layout="square"] .mPreview_title,
.mPreview_card[data-layout="square"] .mPreview_title a,
.mPreview_card[data-layout="square"] .mPreview_description,
.mPreview_card[data-layout="square"] .mPreview_site {
  color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}
.mPreview_card[data-layout="square"] .mPreview_description {
  -webkit-line-clamp: 1;
}
.mPreview_card[data-layout="square"] .mPreview_meta {
  color: rgba(255, 255, 255, 0.85);
}
/* Play button + photo count stay above the scrim. */
.mPreview_card[data-layout="square"] .mPreview_play,
.mPreview_card[data-layout="square"] .mPreview_photoCount {
  z-index: 2;
}

/* ---------- variant=full -------------------------------------------------
   Detail-page presentation: single card per row, ~1020px max width, auto
   height, larger type, description shows up to ~8 lines instead of 2. Media
   sizing mirrors hero — landscape 1.91/1, syndication-aware for X. */
.mPreview_card[data-variant="full"] {
  /* No inner cap — let the consumer container dictate width. Consumers
     that want a wider card set --mPreview-max-width to 100% on the host
     (home page does this via .admin-card). */
  max-width: none;
  margin: 0 auto;
}
.mPreview_card[data-variant="full"] .mPreview_body {
  padding: calc(var(--mPreview-gap) * 1.5);
  gap: 8px;
}
.mPreview_card[data-variant="full"] .mPreview_title {
  font-size: 1.4em;
  -webkit-line-clamp: 3;
}
.mPreview_card[data-variant="full"] .mPreview_description {
  font-size: 1em;
  -webkit-line-clamp: 8;
}
.mPreview_card[data-variant="full"] .mPreview_tweetText {
  -webkit-line-clamp: 10;
}