mediaBanner.html
<!-- MediaBanner - Full viewport decorative media (image stretches entirely, section bg won't show) -->
<section class="section mediaBanner">
  <img class="mediaBanner__content" src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4" alt="" />
</section>

<!-- MediaBanner with video (image stretches entirely, section bg won't show) -->
<section class="section mediaBanner">
  <p-lazy>
    <video class="mediaBanner__content" inert controls autoplay playsinline muted>
      <source data-src="https://www.w3schools.com/html/mov_bbb.mp4" type="video/mp4" />
      Your browser does not support the video tag.
    </video>
    <template #placeholder="{ load, modifier }">
      <div class="media">
        <picture class="mediaBanner__mask media__mask" :class="modifier">
          <img src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4" alt="" />
        </picture>
        <button class="mediaBanner__trigger media__trigger -circle" @click="load" :class="modifier">
          <span class="srOnly">play</span>
          <svg class="media__icon icon -circle" viewBox="0 0 96 96">
            <use href="/main-icons-sprite.svg#play" />
          </svg>
        </button>
      </div>
    </template>
  </p-lazy>
</section>

<!-- MediaBanner with video inside container -->
<section class="section mediaBanner">
  <div class="container">
    <h2 class="h3">With Optional Header inside a container</h2>
    <div class="mediaBanner__container">
      <p-lazy>
        <video class="mediaBanner__content" inert controls autoplay playsinline muted>
          <source data-src="https://www.w3schools.com/html/mov_bbb.mp4" type="video/mp4" />
          Your browser does not support the video tag.
        </video>
        <template #placeholder="{ load, modifier }">
          <div class="media">
            <picture class="mediaBanner__mask media__mask" :class="modifier">
              <img src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4" alt="" />
            </picture>
            <button class="mediaBanner__trigger media__trigger -circle" @click="load" :class="modifier">
              <span class="srOnly">play</span>
              <svg class="media__icon icon -circle" viewBox="0 0 96 96">
                <use href="/main-icons-sprite.svg#play" />
              </svg>
            </button>
          </div>
        </template>
      </p-lazy>
    </div>
  </div>
</section>

<!-- MediaBanner with overlay (image stretches entirely, section bg won't show) -->
<section class="section mediaBanner -withOverlay">
  <img class="mediaBanner__content" src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4" alt="" />
  <div class="mediaBanner__overlay"></div>
</section>

<!-- MediaBanner with text content - Center alignment (overlay added automatically) -->
<section class="section mediaBanner -withOverlay accent-primary">
  <img class="mediaBanner__content" src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4" alt="" />
  <div class="mediaBanner__overlay"></div>
  <div class="mediaBanner__content -white -center">
    <div class="container">
      <div class="mediaBanner__eyebrow">Featured Content</div>
      <h2 class="mediaBanner__title h3">Center Aligned Headline</h2>
      <div class="mediaBanner__actions">
        <button class="button -navy">Call to Action</button>
      </div>
    </div>
  </div>
</section>

<!-- MediaBanner with text content - Left alignment (overlay added automatically) -->
<section class="section mediaBanner -withOverlay accent-secondary">
  <img class="mediaBanner__content" src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4" alt="" />
  <div class="mediaBanner__overlay"></div>
  <div class="mediaBanner__content -white -left">
    <div class="container">
      <div class="mediaBanner__eyebrow">Featured Content</div>
      <h2 class="mediaBanner__title h3">Left Aligned Headline</h2>
      <div class="mediaBanner__actions">
        <button class="button -navy">Call to Action</button>
      </div>
    </div>
  </div>
</section>

<!-- MediaBanner with text content - Right alignment (overlay added automatically) -->
<section class="section mediaBanner -withOverlay accent-tertiary">
  <img class="mediaBanner__content" src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4" alt="" />
  <div class="mediaBanner__overlay"></div>
  <div class="mediaBanner__content -white -right">
    <div class="container">
      <div class="mediaBanner__eyebrow">Featured Content</div>
      <h2 class="mediaBanner__title h3">Right Aligned Headline</h2>
      <div class="mediaBanner__actions">
        <button class="button -navy">Call to Action</button>
      </div>
    </div>
  </div>
</section>
index.scss
.mediaBanner {
  --media-banner-aspect-ratio: 65 / 24;

  position: relative;
  width: 100%;
  overflow: hidden;
  display: grid;
  grid-template-areas: 'media';

  // Container instance - grid and border radius
  &__container {
    display: grid;
    grid-template-areas: 'media';
    border-radius: var(--root-border-radius);
  }

  // When inside container, position within container's grid
  .container > &__container {
    grid-column: main / main;
  }

  // Target p-lazy wrapper div to be in grid area (works for both full-width and container)
  > div:first-child,
  &__container > div:first-child {
    grid-area: media;
    position: relative;
    width: 100%;

    video {
      width: 100%;
      height: auto;
      aspect-ratio: var(--media-banner-aspect-ratio);
      display: block;
    }
  }

  // Media class (works for both full-width and container)
  .media,
  &__container .media {
    grid-area: media;
    display: grid;
    width: 100%;
    margin-bottom: 0;
    aspect-ratio: var(--media-banner-aspect-ratio);

    // Override section's --accent-color: #fff to use default purple for icons
    --accent-color: var(--color-purple);
  }

  &__content {
    grid-area: media;
    width: 100%;
    height: auto;
    aspect-ratio: var(--media-banner-aspect-ratio);
    object-fit: cover;
    display: block;
    z-index: 0;

    // Content overlay styling (when used for text content)
    &.-white {
      grid-area: media;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: flex-start;
      padding: 2rem;
      color: white;
      z-index: 2;
      background: none;
      object-fit: initial;

      // Ensure all child elements inherit white color
      * {
        color: inherit;
      }

      // Don't override container - let it work with Pronto grid system
      .container {
        // Only add minimal styles that don't break the grid
        width: 100%;
        row-gap: 0;
      }

      // Text alignment modifiers
      &.-center {
        align-items: center;
        text-align: center;
      }

      &.-left {
        align-items: flex-start;
        text-align: left;
      }

      &.-right {
        align-items: flex-end;
        text-align: right;
      }
    }
  }

  &__eyebrow {
    font-size: 0.875rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-bottom: 0.5rem;
    opacity: 0.9;
  }

  &__title {
    margin-bottom: 1.5rem;
    text-shadow: 0 2px 4px rgb(0 0 0 / 0.3);
  }

  &__actions {
    margin-top: 1rem;
  }

  &__mask {
    .media & {
      grid-area: 1 / 1;
    }

    grid-area: media;
    position: relative;
    z-index: 1;
    background: white;
    margin: 0;
    transition: opacity 0.3s ease;
    border-radius: var(--root-border-radius);

    img {
      width: 100%;
      height: auto;
      aspect-ratio: var(--media-banner-aspect-ratio);
      object-fit: cover;
    }

    &.-loaded {
      opacity: 0;
      pointer-events: none;
    }
  }

  &__trigger {
    // When inside media class, use media's grid system (media__trigger handles positioning)
    .media & {
      grid-area: 1 / 1;
      place-self: center center;
      z-index: 2; // Above the mask (z-index: 1)
      position: relative;
    }

    appearance: none;
    background: transparent;
    border: 0;
    cursor: pointer;
    padding: 0;
  }

  &__overlay {
    grid-area: media;
    background-color: rgb(0 0 0 / 0.3);
    pointer-events: none;
    z-index: 1;
  }

  // Modifier for different overlay styles
  &.-withOverlay {
    .mediaBanner__overlay {
      opacity: 1;
    }
  }
}
PLazy.vue
<script setup>
import { computed, ref, nextTick, useTemplateRef } from 'vue'

/**
 * PLazy is a vue component that lazy loads its content after it's been clicked.
 *
 * @slot default - the content to lazyload. It will remove inert attributes, change data-src
 *                 attributes to src attributes, and call .load() on videos.
 * @slot placeholder - the content to display before it's lazyloaded. The following properties
 *                     are exposed to this slot:
 *       load {function} - function to call to load the content.
 *       loaded {boolean} - whether the content is loaded or not.
 *       modifier {string} - either '-loaded' or '' based on loaded.
 */

const loaded = ref(false)
const content = useTemplateRef('content')
const modifier = computed(() => (loaded.value ? '-loaded' : ''))

const load = async () => {
  if (loaded.value) return

  await nextTick()

  const promises = []

  /* Change data-src attributes to src attributes */
  content.value.querySelectorAll('[data-src]').forEach(element => element.setAttribute('src', element.dataset.src))

  /* remove inert attribute */
  content.value.querySelectorAll('[inert]').forEach(element => (element.inert = false))

  /* load videos */
  content.value.querySelectorAll('video').forEach(element => {
    element.load()
    promises.push(
      new Promise(resolve => {
        element.addEventListener('loadeddata', resolve, { once: true })
      }),
    )
  })

  /* load iframes */
  content.value.querySelectorAll('iframe').forEach(element => {
    promises.push(
      new Promise(resolve => {
        element.addEventListener('load', resolve, { once: true })
      }),
    )
  })

  /* wait for all videos and iframes to load, then update loaded.value */
  Promise.all(promises).then(() => (loaded.value = true))
}
</script>

<template>
  <div ref="content">
    <slot name="default" />
  </div>
  <slot name="placeholder" v-bind="{ load, loaded, modifier }" />
</template>

<style scoped>
div {
  container-type: size;
}
</style>