UI Components Easy
Hero Video Dialog
A click-to-play video modal for hero sections. Displays a thumbnail with a centered play button that opens a fullscreen video overlay with smooth transitions.
Open in Lab
MCP
css javascript vue svelte
Targets: TS JS HTML React Vue Svelte
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, -apple-system, sans-serif;
background: #0a0a0a;
color: #f1f5f9;
min-height: 100vh;
display: grid;
place-items: center;
padding: 2rem;
}
/* ── Hero ── */
.hero-wrapper {
width: min(700px, 100%);
display: flex;
flex-direction: column;
align-items: center;
gap: 2.5rem;
}
.hero-content {
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.75rem;
}
.hero-badge {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #a78bfa;
background: rgba(167, 139, 250, 0.1);
padding: 0.25rem 0.75rem;
border-radius: 999px;
border: 1px solid rgba(167, 139, 250, 0.2);
}
.hero-heading {
font-size: 2.5rem;
font-weight: 800;
color: #f8fafc;
line-height: 1.1;
}
.hero-subheading {
font-size: 1rem;
color: #94a3b8;
max-width: 440px;
line-height: 1.6;
}
/* ── Thumbnail ── */
.video-thumbnail {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
border-radius: 1rem;
overflow: hidden;
cursor: pointer;
border: 1px solid rgba(255, 255, 255, 0.08);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.video-thumbnail:hover {
transform: scale(1.01);
box-shadow: 0 20px 60px rgba(99, 102, 241, 0.15);
}
.thumbnail-image {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #1e1b4b, #312e81, #4c1d95);
position: relative;
}
.thumbnail-image::after {
content: "";
position: absolute;
inset: 0;
background: radial-gradient(ellipse at center, transparent 30%, rgba(0, 0, 0, 0.4));
}
/* ── Play button ── */
.play-button {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 72px;
height: 72px;
border-radius: 50%;
border: none;
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
color: #fff;
cursor: pointer;
display: grid;
place-items: center;
z-index: 2;
transition: background 0.2s ease, transform 0.2s ease;
}
.play-button:hover {
background: rgba(255, 255, 255, 0.25);
transform: translate(-50%, -50%) scale(1.08);
}
.play-pulse {
position: absolute;
inset: -6px;
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.3);
animation: pulse-ring 2s ease-out infinite;
}
@keyframes pulse-ring {
0% {
transform: scale(1);
opacity: 1;
}
100% {
transform: scale(1.4);
opacity: 0;
}
}
/* ── Modal ── */
.video-modal {
position: fixed;
inset: 0;
z-index: 100;
display: grid;
place-items: center;
padding: 2rem;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
}
.video-modal.open {
opacity: 1;
visibility: visible;
}
.modal-backdrop {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
.modal-content {
position: relative;
width: min(900px, 100%);
z-index: 1;
transform: scale(0.9);
transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}
.video-modal.open .modal-content {
transform: scale(1);
}
.modal-close {
position: absolute;
top: -2.5rem;
right: 0;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 0.5rem;
color: #f1f5f9;
cursor: pointer;
padding: 0.35rem;
display: grid;
place-items: center;
transition: background 0.15s ease;
}
.modal-close:hover {
background: rgba(255, 255, 255, 0.2);
}
.modal-video-container {
border-radius: 0.875rem;
overflow: hidden;
background: #000;
box-shadow: 0 25px 80px rgba(0, 0, 0, 0.6);
}
/* ── Reduced motion ── */
@media (prefers-reduced-motion: reduce) {
.play-pulse {
animation: none;
}
.video-modal,
.modal-content,
.video-thumbnail {
transition: none;
}
}(function () {
"use strict";
var thumbnail = document.getElementById("video-thumbnail");
var playButton = document.getElementById("play-button");
var modal = document.getElementById("video-modal");
var backdrop = document.getElementById("modal-backdrop");
var closeButton = document.getElementById("modal-close");
var video = document.getElementById("hero-video");
if (!thumbnail || !modal || !video) return;
function openModal() {
modal.classList.add("open");
modal.setAttribute("aria-hidden", "false");
document.body.style.overflow = "hidden";
video.play().catch(function () {
// Auto-play may be blocked by browser
});
}
function closeModal() {
modal.classList.remove("open");
modal.setAttribute("aria-hidden", "true");
document.body.style.overflow = "";
video.pause();
}
// Open on thumbnail or play button click
thumbnail.addEventListener("click", openModal);
// Close on backdrop click
backdrop.addEventListener("click", closeModal);
// Close on close button click
closeButton.addEventListener("click", closeModal);
// Close on Escape key
document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && modal.classList.contains("open")) {
closeModal();
}
});
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Hero Video Dialog</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="hero-wrapper">
<div class="hero-content">
<span class="hero-badge">New Release</span>
<h1 class="hero-heading">Experience the Future</h1>
<p class="hero-subheading">Watch our latest product showcase and see what's possible.</p>
</div>
<div class="video-thumbnail" id="video-thumbnail">
<!-- Gradient placeholder as thumbnail -->
<div class="thumbnail-image"></div>
<button class="play-button" id="play-button" aria-label="Play video">
<span class="play-pulse"></span>
<svg width="28" height="28" viewBox="0 0 24 24" fill="currentColor">
<path d="M8 5v14l11-7z"/>
</svg>
</button>
</div>
</div>
<!-- Video modal -->
<div class="video-modal" id="video-modal" aria-hidden="true">
<div class="modal-backdrop" id="modal-backdrop"></div>
<div class="modal-content">
<button class="modal-close" id="modal-close" aria-label="Close video">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 6 6 18M6 6l12 12"/>
</svg>
</button>
<div class="modal-video-container">
<!-- Using a sample video with poster. Replace src with your video URL -->
<video
id="hero-video"
controls
preload="metadata"
poster=""
style="width: 100%; border-radius: 0.75rem;"
>
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>import { useState, useRef, useEffect, useCallback } from "react";
interface HeroVideoDialogProps {
videoSrc?: string;
thumbnailSrc?: string;
title?: string;
subtitle?: string;
badge?: string;
}
export default function HeroVideoDialog({
videoSrc = "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
thumbnailSrc,
title = "Experience the Future",
subtitle = "Watch our latest product showcase and see what's possible.",
badge = "New Release",
}: HeroVideoDialogProps) {
const [isOpen, setIsOpen] = useState(false);
const videoRef = useRef<HTMLVideoElement>(null);
const open = useCallback(() => {
setIsOpen(true);
}, []);
const close = useCallback(() => {
setIsOpen(false);
videoRef.current?.pause();
}, []);
useEffect(() => {
if (isOpen) {
document.body.style.overflow = "hidden";
videoRef.current?.play().catch(() => {});
} else {
document.body.style.overflow = "";
}
return () => {
document.body.style.overflow = "";
};
}, [isOpen]);
useEffect(() => {
function handleKey(e: KeyboardEvent) {
if (e.key === "Escape" && isOpen) close();
}
document.addEventListener("keydown", handleKey);
return () => document.removeEventListener("keydown", handleKey);
}, [isOpen, close]);
return (
<div
style={{
minHeight: "100vh",
background: "#0a0a0a",
display: "grid",
placeItems: "center",
padding: "2rem",
fontFamily: "system-ui, -apple-system, sans-serif",
color: "#f1f5f9",
}}
>
<div
style={{
width: "min(700px, 100%)",
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "2.5rem",
}}
>
{/* Hero text */}
<div
style={{
textAlign: "center",
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "0.75rem",
}}
>
{badge && (
<span
style={{
fontSize: "0.75rem",
fontWeight: 600,
textTransform: "uppercase",
letterSpacing: "0.08em",
color: "#a78bfa",
background: "rgba(167,139,250,0.1)",
padding: "0.25rem 0.75rem",
borderRadius: 999,
border: "1px solid rgba(167,139,250,0.2)",
}}
>
{badge}
</span>
)}
<h1 style={{ fontSize: "2.5rem", fontWeight: 800, color: "#f8fafc", lineHeight: 1.1 }}>
{title}
</h1>
<p style={{ fontSize: "1rem", color: "#94a3b8", maxWidth: 440, lineHeight: 1.6 }}>
{subtitle}
</p>
</div>
{/* Thumbnail */}
<div
onClick={open}
style={{
position: "relative",
width: "100%",
aspectRatio: "16 / 9",
borderRadius: "1rem",
overflow: "hidden",
cursor: "pointer",
border: "1px solid rgba(255,255,255,0.08)",
transition: "transform 0.3s ease, box-shadow 0.3s ease",
}}
onMouseEnter={(e) => {
e.currentTarget.style.transform = "scale(1.01)";
e.currentTarget.style.boxShadow = "0 20px 60px rgba(99,102,241,0.15)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = "";
e.currentTarget.style.boxShadow = "";
}}
>
{thumbnailSrc ? (
<img
src={thumbnailSrc}
alt="Video thumbnail"
style={{ width: "100%", height: "100%", objectFit: "cover" }}
/>
) : (
<div
style={{
width: "100%",
height: "100%",
background: "linear-gradient(135deg, #1e1b4b, #312e81, #4c1d95)",
}}
/>
)}
{/* Play button */}
<button
aria-label="Play video"
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 72,
height: 72,
borderRadius: "50%",
border: "none",
background: "rgba(255,255,255,0.15)",
backdropFilter: "blur(12px)",
WebkitBackdropFilter: "blur(12px)",
color: "#fff",
cursor: "pointer",
display: "grid",
placeItems: "center",
zIndex: 2,
}}
>
<svg width="28" height="28" viewBox="0 0 24 24" fill="currentColor">
<path d="M8 5v14l11-7z" />
</svg>
</button>
</div>
</div>
{/* Modal overlay */}
{isOpen && (
<div
style={{
position: "fixed",
inset: 0,
zIndex: 100,
display: "grid",
placeItems: "center",
padding: "2rem",
animation: "fadeIn 0.3s ease",
}}
>
<style>{`
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes scaleIn { from { transform: scale(0.9); opacity: 0; } to { transform: scale(1); opacity: 1; } }
`}</style>
{/* Backdrop */}
<div
onClick={close}
style={{
position: "absolute",
inset: 0,
background: "rgba(0,0,0,0.8)",
backdropFilter: "blur(8px)",
WebkitBackdropFilter: "blur(8px)",
}}
/>
{/* Content */}
<div
style={{
position: "relative",
width: "min(900px, 100%)",
zIndex: 1,
animation: "scaleIn 0.3s cubic-bezier(0.22,1,0.36,1)",
}}
>
<button
onClick={close}
aria-label="Close video"
style={{
position: "absolute",
top: "-2.5rem",
right: 0,
background: "rgba(255,255,255,0.1)",
border: "1px solid rgba(255,255,255,0.15)",
borderRadius: "0.5rem",
color: "#f1f5f9",
cursor: "pointer",
padding: "0.35rem",
display: "grid",
placeItems: "center",
}}
>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
>
<path d="M18 6 6 18M6 6l12 12" />
</svg>
</button>
<div
style={{
borderRadius: "0.875rem",
overflow: "hidden",
background: "#000",
boxShadow: "0 25px 80px rgba(0,0,0,0.6)",
}}
>
<video
ref={videoRef}
controls
preload="metadata"
style={{ width: "100%", display: "block", borderRadius: "0.75rem" }}
>
<source src={videoSrc} type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>
</div>
</div>
)}
</div>
);
}<script setup>
import { ref, watch, onMounted, onUnmounted } from "vue";
const props = defineProps({
videoSrc: {
type: String,
default: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
},
thumbnailSrc: { type: String, default: "" },
title: { type: String, default: "Experience the Future" },
subtitle: { type: String, default: "Watch our latest product showcase and see what's possible." },
badge: { type: String, default: "New Release" },
});
const isOpen = ref(false);
const videoEl = ref(null);
function open() {
isOpen.value = true;
}
function close() {
isOpen.value = false;
if (videoEl.value) videoEl.value.pause();
}
watch(isOpen, (val) => {
if (val) {
document.body.style.overflow = "hidden";
if (videoEl.value) videoEl.value.play().catch(() => {});
} else {
document.body.style.overflow = "";
}
});
function handleKey(e) {
if (e.key === "Escape" && isOpen.value) close();
}
onMounted(() => {
document.addEventListener("keydown", handleKey);
});
onUnmounted(() => {
document.removeEventListener("keydown", handleKey);
document.body.style.overflow = "";
});
function handleThumbEnter(e) {
e.currentTarget.style.transform = "scale(1.01)";
e.currentTarget.style.boxShadow = "0 20px 60px rgba(99,102,241,0.15)";
}
function handleThumbLeave(e) {
e.currentTarget.style.transform = "";
e.currentTarget.style.boxShadow = "";
}
</script>
<template>
<div class="hero-video-dialog">
<div class="hero-inner">
<!-- Hero text -->
<div class="hero-text">
<span v-if="props.badge" class="badge">{{ props.badge }}</span>
<h1 class="title">{{ props.title }}</h1>
<p class="subtitle">{{ props.subtitle }}</p>
</div>
<!-- Thumbnail -->
<div
class="thumbnail"
@click="open"
@mouseenter="handleThumbEnter"
@mouseleave="handleThumbLeave"
>
<img v-if="props.thumbnailSrc" :src="props.thumbnailSrc" alt="Video thumbnail" class="thumb-img" />
<div v-else class="thumb-placeholder" />
<button aria-label="Play video" class="play-btn">
<svg width="28" height="28" viewBox="0 0 24 24" fill="currentColor">
<path d="M8 5v14l11-7z" />
</svg>
</button>
</div>
</div>
<!-- Modal overlay -->
<div v-if="isOpen" class="modal-overlay">
<!-- Backdrop -->
<div class="backdrop" @click="close" />
<!-- Content -->
<div class="modal-content">
<button @click="close" aria-label="Close video" class="close-btn">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 6 6 18M6 6l12 12" />
</svg>
</button>
<div class="video-container">
<video ref="videoEl" controls preload="metadata" class="video-el">
<source :src="props.videoSrc" type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>
</div>
</div>
</div>
</template>
<style scoped>
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes scaleIn { from { transform: scale(0.9); opacity: 0; } to { transform: scale(1); opacity: 1; } }
.hero-video-dialog {
min-height: 100vh;
background: #0a0a0a;
display: grid;
place-items: center;
padding: 2rem;
font-family: system-ui, -apple-system, sans-serif;
color: #f1f5f9;
}
.hero-inner {
width: min(700px, 100%);
display: flex;
flex-direction: column;
align-items: center;
gap: 2.5rem;
}
.hero-text {
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.75rem;
}
.badge {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #a78bfa;
background: rgba(167,139,250,0.1);
padding: 0.25rem 0.75rem;
border-radius: 999px;
border: 1px solid rgba(167,139,250,0.2);
}
.title {
font-size: 2.5rem;
font-weight: 800;
color: #f8fafc;
line-height: 1.1;
margin: 0;
}
.subtitle {
font-size: 1rem;
color: #94a3b8;
max-width: 440px;
line-height: 1.6;
margin: 0;
}
.thumbnail {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
border-radius: 1rem;
overflow: hidden;
cursor: pointer;
border: 1px solid rgba(255,255,255,0.08);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.thumb-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.thumb-placeholder {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #1e1b4b, #312e81, #4c1d95);
}
.play-btn {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 72px;
height: 72px;
border-radius: 50%;
border: none;
background: rgba(255,255,255,0.15);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
color: #fff;
cursor: pointer;
display: grid;
place-items: center;
z-index: 2;
}
.modal-overlay {
position: fixed;
inset: 0;
z-index: 100;
display: grid;
place-items: center;
padding: 2rem;
animation: fadeIn 0.3s ease;
}
.backdrop {
position: absolute;
inset: 0;
background: rgba(0,0,0,0.8);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
.modal-content {
position: relative;
width: min(900px, 100%);
z-index: 1;
animation: scaleIn 0.3s cubic-bezier(0.22,1,0.36,1);
}
.close-btn {
position: absolute;
top: -2.5rem;
right: 0;
background: rgba(255,255,255,0.1);
border: 1px solid rgba(255,255,255,0.15);
border-radius: 0.5rem;
color: #f1f5f9;
cursor: pointer;
padding: 0.35rem;
display: grid;
place-items: center;
}
.video-container {
border-radius: 0.875rem;
overflow: hidden;
background: #000;
box-shadow: 0 25px 80px rgba(0,0,0,0.6);
}
.video-el {
width: 100%;
display: block;
border-radius: 0.75rem;
}
</style><script>
import { onMount, onDestroy } from "svelte";
export let videoSrc =
"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
export let thumbnailSrc = "";
export let title = "Experience the Future";
export let subtitle = "Watch our latest product showcase and see what's possible.";
export let badge = "New Release";
let isOpen = false;
let videoEl;
function open() {
isOpen = true;
}
function close() {
isOpen = false;
if (videoEl) videoEl.pause();
}
$: if (isOpen) {
document.body.style.overflow = "hidden";
if (videoEl) videoEl.play().catch(() => {});
} else {
document.body.style.overflow = "";
}
function handleKey(e) {
if (e.key === "Escape" && isOpen) close();
}
onMount(() => {
document.addEventListener("keydown", handleKey);
});
onDestroy(() => {
document.removeEventListener("keydown", handleKey);
document.body.style.overflow = "";
});
function handleThumbEnter(e) {
e.currentTarget.style.transform = "scale(1.01)";
e.currentTarget.style.boxShadow = "0 20px 60px rgba(99,102,241,0.15)";
}
function handleThumbLeave(e) {
e.currentTarget.style.transform = "";
e.currentTarget.style.boxShadow = "";
}
</script>
<div class="hero-video-dialog">
<div class="hero-inner">
<!-- Hero text -->
<div class="hero-text">
{#if badge}
<span class="badge">{badge}</span>
{/if}
<h1 class="title">{title}</h1>
<p class="subtitle">{subtitle}</p>
</div>
<!-- Thumbnail -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="thumbnail"
on:click={open}
on:mouseenter={handleThumbEnter}
on:mouseleave={handleThumbLeave}
role="button"
tabindex="0"
>
{#if thumbnailSrc}
<img src={thumbnailSrc} alt="Video thumbnail" class="thumb-img" />
{:else}
<div class="thumb-placeholder" />
{/if}
<button aria-label="Play video" class="play-btn">
<svg width="28" height="28" viewBox="0 0 24 24" fill="currentColor">
<path d="M8 5v14l11-7z" />
</svg>
</button>
</div>
</div>
<!-- Modal overlay -->
{#if isOpen}
<div class="modal-overlay">
<!-- Backdrop -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="backdrop" on:click={close} role="button" tabindex="-1" />
<!-- Content -->
<div class="modal-content">
<button on:click={close} aria-label="Close video" class="close-btn">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 6 6 18M6 6l12 12" />
</svg>
</button>
<div class="video-container">
<video bind:this={videoEl} controls preload="metadata" class="video-el">
<source src={videoSrc} type="video/mp4" />
<track kind="captions" />
Your browser does not support the video tag.
</video>
</div>
</div>
</div>
{/if}
</div>
<style>
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes scaleIn { from { transform: scale(0.9); opacity: 0; } to { transform: scale(1); opacity: 1; } }
.hero-video-dialog {
min-height: 100vh;
background: #0a0a0a;
display: grid;
place-items: center;
padding: 2rem;
font-family: system-ui, -apple-system, sans-serif;
color: #f1f5f9;
}
.hero-inner {
width: min(700px, 100%);
display: flex;
flex-direction: column;
align-items: center;
gap: 2.5rem;
}
.hero-text {
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.75rem;
}
.badge {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #a78bfa;
background: rgba(167,139,250,0.1);
padding: 0.25rem 0.75rem;
border-radius: 999px;
border: 1px solid rgba(167,139,250,0.2);
}
.title {
font-size: 2.5rem;
font-weight: 800;
color: #f8fafc;
line-height: 1.1;
margin: 0;
}
.subtitle {
font-size: 1rem;
color: #94a3b8;
max-width: 440px;
line-height: 1.6;
margin: 0;
}
.thumbnail {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
border-radius: 1rem;
overflow: hidden;
cursor: pointer;
border: 1px solid rgba(255,255,255,0.08);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.thumb-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.thumb-placeholder {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #1e1b4b, #312e81, #4c1d95);
}
.play-btn {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 72px;
height: 72px;
border-radius: 50%;
border: none;
background: rgba(255,255,255,0.15);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
color: #fff;
cursor: pointer;
display: grid;
place-items: center;
z-index: 2;
}
.modal-overlay {
position: fixed;
inset: 0;
z-index: 100;
display: grid;
place-items: center;
padding: 2rem;
animation: fadeIn 0.3s ease;
}
.backdrop {
position: absolute;
inset: 0;
background: rgba(0,0,0,0.8);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
.modal-content {
position: relative;
width: min(900px, 100%);
z-index: 1;
animation: scaleIn 0.3s cubic-bezier(0.22,1,0.36,1);
}
.close-btn {
position: absolute;
top: -2.5rem;
right: 0;
background: rgba(255,255,255,0.1);
border: 1px solid rgba(255,255,255,0.15);
border-radius: 0.5rem;
color: #f1f5f9;
cursor: pointer;
padding: 0.35rem;
display: grid;
place-items: center;
}
.video-container {
border-radius: 0.875rem;
overflow: hidden;
background: #000;
box-shadow: 0 25px 80px rgba(0,0,0,0.6);
}
.video-el {
width: 100%;
display: block;
border-radius: 0.75rem;
}
</style>Hero Video Dialog
A click-to-play video modal designed for hero sections. Displays an attractive thumbnail with a pulsing play button. Clicking opens a fullscreen overlay with the embedded video, complete with backdrop blur and smooth enter/exit animations.
How it works
- A thumbnail container with a centered play icon button sits in the hero area.
- Clicking the play button opens a modal overlay with the video element.
- The video auto-plays when the modal opens and pauses when closed.
- Clicking the backdrop or the close button dismisses the modal.
Features
- Animated play button with pulse ring effect
- Smooth modal entrance with scale + fade animation
- Backdrop blur overlay
- Auto-play on open, pause on close
- Escape key to dismiss