mediaBanner.html
<!-- MediaBanner - Full viewport decorative media -->
<div class="mediaBanner">
	<img
		class="mediaBanner__content"
		src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4"
		alt=""
	/>
</div>

<!-- MediaBanner with video -->
<div class="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>
</div>

<!-- MediaBanner with video inside container -->
<section class="section">
	<div class="container">
		<h2 class="h3">
			With Optional Header inside a container
		</h2>
		<div class="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>
		</div>
	</div>
</section>

<!-- MediaBanner with overlay -->
<div class="mediaBanner -withOverlay">
	<img
		class="mediaBanner__content"
		src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4"
		alt=""
	/>
	<div class="mediaBanner__overlay"></div>
</div>

<!-- MediaBanner with text content - Center alignment (overlay added automatically) -->
<div class="mediaBanner -withOverlay">
	<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>
</div>

<!-- MediaBanner with text content - Left alignment (overlay added automatically) -->
<div class="mediaBanner -withOverlay">
	<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>
</div>

<!-- MediaBanner with text content - Right alignment (overlay added automatically) -->
<div class="mediaBanner -withOverlay">
	<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>
</div>
index.scss
.mediaBanner {
  position: relative;
  width: 100%;
  overflow: hidden;
  display: grid;
  grid-template-areas: "media";
  --media-banner-aspect-ratio: 65 / 24;

  // When inside a container, add border radius
  .container & {
    border-radius: var(--root-border-radius);
  }

  // Target p-lazy wrapper div to be in grid area
  > 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 {
    grid-area: media;
    display: grid;
    width: 100%;
    // 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 rgba(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;
    }
    
    grid-area: media;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 2;
    appearance: none;
    background: transparent;
    border: 0;
    cursor: pointer;
    padding: 0;
  }

  &__overlay {
    grid-area: media;
    background-color: rgba(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"></slot>
  </div>
  <slot name="placeholder" v-bind="{ load, loaded, modifier }"></slot>
</template>

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