Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions blocks/hero/hero.css
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,162 @@
background-color: var(--color-rust);
}

/* Video variant */
.hero.video {
display: grid;
align-content: end;
min-height: clamp(520px, calc(100svh - var(--nav-height, 64px)), 760px);
padding: 56px var(--content-padding-inline, 24px) 64px;
overflow: hidden;
background:
linear-gradient(115deg, rgb(161 79 43 / 70%), transparent 46%),
linear-gradient(35deg, rgb(197 160 89 / 48%), transparent 48%),
var(--color-obsidian, #060d0d);
color: var(--color-parchment, #f5f2ed);
}

.hero.video video {
position: absolute;
z-index: 0;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
object-position: 50% 50%;
}

.hero.video::before,
.hero.video::after {
content: '';
position: absolute;
pointer-events: none;
}

.hero.video::before {
z-index: 1;
inset: 0;
background:
linear-gradient(90deg, rgb(245 242 237 / 9%) 1px, transparent 1px) 0 0 / 56px 56px,
linear-gradient(180deg, rgb(245 242 237 / 7%) 1px, transparent 1px) 0 0 / 56px 56px,
linear-gradient(90deg, rgb(6 13 13 / 88%) 0%, rgb(6 13 13 / 58%) 42%, rgb(6 13 13 / 28%) 100%),
linear-gradient(0deg, rgb(6 13 13 / 82%) 0%, rgb(6 13 13 / 18%) 48%, rgb(6 13 13 / 50%) 100%);
}

.hero.video::after {
z-index: 2;
inset: 0;
background: radial-gradient(circle at 22% 75%, rgb(197 160 89 / 24%), transparent 34%);
mix-blend-mode: screen;
}

.hero-video-content {
position: relative;
z-index: 3;
width: 100%;
max-width: 1040px;
margin: 0 auto;
}

.hero.video p:not(.button-container, .hero-tagline) {
display: block;
max-width: 680px;
margin: 24px 0 0;
color: rgb(245 242 237 / 88%);
font-size: var(--body-font-size-l, 18px);
line-height: 1.45;
letter-spacing: 0;
text-shadow: 0 10px 28px rgb(0 0 0 / 42%);
}

.hero.video p:first-child {
margin: 0 0 16px;
color: var(--color-gold, #c5a059);
font-family: var(--font-family-mono, monospace);
font-size: var(--body-font-size-xs, 12px);
font-weight: 600;
letter-spacing: 0.28em;
line-height: 1.4;
text-transform: uppercase;
}

.hero.video h1 {
max-width: 920px;
margin: 0;
color: var(--color-parchment, #f5f2ed);
font-family: var(--font-family-serif, georgia, serif);
font-size: clamp(3rem, 12vw, 6.5rem);
font-weight: 500;
line-height: 0.96;
text-shadow: 0 16px 48px rgb(0 0 0 / 52%);
}

.hero.video h1 strong {
font-weight: 500;
}

.hero.video h1 em:not(.hero-tagline) {
display: inline-block;
padding-inline: 0.1em;
color: var(--color-gold, #c5a059);
font-style: italic;
background: none;
}

.hero-video-controls {
position: absolute;
right: var(--content-padding-inline, 24px);
bottom: 24px;
z-index: 4;
display: flex;
gap: 6px;
}

.hero-video-control {
padding: 8px 12px;
border: 1px solid rgb(245 242 237 / 52%);
border-radius: 4px;
background: rgb(6 13 13 / 62%);
color: var(--color-parchment, #f5f2ed);
font: 600 var(--body-font-size-xs, 12px) / 1 var(--font-family-sans, sans-serif);
cursor: pointer;
}

.hero-video-control:hover,
.hero-video-control:focus {
background: rgb(6 13 13 / 82%);
}

.hero-video-control[aria-pressed='false'] {
opacity: 0.68;
}

@media (width >= 900px) {
.hero.video {
padding: 72px var(--content-padding-inline, 32px) 84px;
}

.hero.video p {
font-size: var(--body-font-size-xl, 20px);
}
}

@media (width < 600px) {
.hero.video {
min-height: 620px;
}

.hero.video video {
object-position: 55% 50%;
}
}

@media (prefers-reduced-motion: reduce) {
.hero.video video,
.hero-video-controls {
display: none;
}
}

.hero.rust h1,
.hero.rust p:not(.button-container, .hero-tagline) {
color: var(--color-parchment);
Expand Down
145 changes: 145 additions & 0 deletions blocks/hero/hero.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,150 @@
function getVideoSources(block) {
const videoSources = {};

[...block.children].forEach((row) => {
const link = row.querySelector('a[href]');
if (link && /\.(mp4|webm|ogg)(\?|#|$)/i.test(link.href)) {
const label = link.textContent.trim().toLowerCase();
if (label.includes('light')) videoSources.light = link.href;
else if (label.includes('dark')) videoSources.dark = link.href;
else if (!videoSources.dark) videoSources.dark = link.href;
else if (!videoSources.light) videoSources.light = link.href;
row.remove();
}
});

if (!videoSources.light) videoSources.light = videoSources.dark;
if (!videoSources.dark) videoSources.dark = videoSources.light;

return videoSources;
}

function decorateVideoHero(block) {
const videoSources = getVideoSources(block);
const picture = block.querySelector('picture');
const poster = picture?.querySelector('img')?.currentSrc || picture?.querySelector('img')?.src;
const posterRow = picture?.parentElement?.parentElement;
posterRow?.remove();

const content = document.createElement('div');
content.className = 'hero-video-content';
[...block.children].forEach((row) => {
[...row.children].forEach((cell) => {
while (cell.firstChild) content.append(cell.firstChild);
});
});

block.textContent = '';

if (videoSources.light || videoSources.dark) {
const video = document.createElement('video');
video.autoplay = true;
video.muted = true;
video.defaultMuted = true;
video.loop = true;
video.playsInline = true;
video.preload = 'auto';
video.setAttribute('autoplay', '');
video.setAttribute('muted', '');
video.setAttribute('loop', '');
video.setAttribute('playsinline', '');
video.setAttribute('webkit-playsinline', '');
video.setAttribute('aria-hidden', 'true');
if (poster) video.poster = poster;

const source = document.createElement('source');
video.append(source);
block.append(video);
block.classList.add('has-video');

const controls = document.createElement('div');
controls.className = 'hero-video-controls';

const playToggle = document.createElement('button');
playToggle.className = 'hero-video-control hero-video-play-toggle';
playToggle.type = 'button';
playToggle.setAttribute('aria-label', 'Pause background video');
playToggle.textContent = 'Pause';

const loopToggle = document.createElement('button');
loopToggle.className = 'hero-video-control hero-video-loop-toggle';
loopToggle.type = 'button';
loopToggle.setAttribute('aria-label', 'Disable background video loop');
loopToggle.setAttribute('aria-pressed', 'true');
loopToggle.textContent = 'Loop';

controls.append(playToggle, loopToggle);
block.append(controls);

const setPlayToggleState = () => {
const paused = video.paused || video.ended;
playToggle.textContent = paused ? 'Play' : 'Pause';
playToggle.setAttribute('aria-label', `${paused ? 'Play' : 'Pause'} background video`);
};

const setLoopToggleState = () => {
loopToggle.setAttribute('aria-pressed', String(video.loop));
loopToggle.setAttribute('aria-label', `${video.loop ? 'Disable' : 'Enable'} background video loop`);
};

const playVideo = () => {
if (video.ended) video.currentTime = 0;
const playPromise = video.play();
if (playPromise) playPromise.catch(() => {});
setPlayToggleState();
};

const getTheme = () => {
const theme = document.documentElement.dataset.theme;
if (theme === 'light' || theme === 'dark') return theme;
return document.body.classList.contains('light-scheme') ? 'light' : 'dark';
};

const setVideoSource = (theme = getTheme()) => {
const src = videoSources[theme] || videoSources.dark || videoSources.light;
if (!src || source.src === src) return;
source.src = src;
source.type = src.endsWith('.webm') ? 'video/webm' : 'video/mp4';
video.dataset.themeVideo = theme;
video.load();
playVideo();
};

playToggle.addEventListener('click', () => {
if (video.paused || video.ended) playVideo();
else video.pause();
setPlayToggleState();
});
loopToggle.addEventListener('click', () => {
video.loop = !video.loop;
if (video.loop) video.setAttribute('loop', '');
else video.removeAttribute('loop');
setLoopToggleState();
});
video.addEventListener('play', setPlayToggleState);
video.addEventListener('pause', setPlayToggleState);
video.addEventListener('ended', setPlayToggleState);
setLoopToggleState();

setVideoSource();
window.addEventListener('aem-theme-change', (e) => {
setVideoSource(e.detail?.theme);
});

if (document.visibilityState === 'visible') playVideo();
else document.addEventListener('visibilitychange', playVideo, { once: true });
}

block.append(content);
}

/** @param {Element} block The hero block element */
export default function decorate(block) {
if (block.classList.contains('video')) {
decorateVideoHero(block);
return;
}

const pictures = block.querySelectorAll('picture');

if (pictures.length >= 2) {
Expand Down
6 changes: 6 additions & 0 deletions blocks/video-hero/video-hero.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@import url('../hero/hero.css');

.video-hero-container .video-hero-wrapper {
max-width: unset;
padding: 0;
}
6 changes: 6 additions & 0 deletions blocks/video-hero/video-hero.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import decorateHero from '../hero/hero.js';

export default function decorate(block) {
block.classList.add('hero', 'video');
decorateHero(block);
}
Loading