progress
This commit is contained in:
1 parent
49850fe485
commit
39bb0f480c
10 files changed
+248
-117
No files matched your search
@@ -1,6 +1,6 @@
|
||||
---
|
||||
import { Picture } from 'astro:assets';
|
||||
import plant from '../../assets/images/plant-at-dusk.jpg';
|
||||
import background from '../../assets/images/background.png';
|
||||
import { t, type Lang } from '../../i18n/ui';
|
||||
|
||||
interface Props { lang: Lang }
|
||||
@@ -9,33 +9,21 @@ const ui = t(lang);
|
||||
---
|
||||
<section class="hero night" aria-labelledby="hero-title">
|
||||
<div class="hero__media">
|
||||
<!-- The still is the video's first frame, so it carries the hero until the video is ready
|
||||
and stays put for anyone who never gets one: no script, reduced motion, saved data. -->
|
||||
<!-- Abstract and decorative, so it carries no alt text. -->
|
||||
<Picture
|
||||
src={plant}
|
||||
alt={ui.hero.imageAlt}
|
||||
src={background}
|
||||
alt=""
|
||||
quality={70}
|
||||
formats={['avif']}
|
||||
fallbackFormat="webp"
|
||||
widths={[640, 960, 1440, 1920, 2400, 3024]}
|
||||
widths={[640, 960, 1440, 1920, 2400]}
|
||||
sizes="100vw"
|
||||
loading="eager"
|
||||
fetchpriority="high"
|
||||
decoding="async"
|
||||
class="hero__img img-cover img-doc"
|
||||
class="hero__img img-cover"
|
||||
pictureAttributes={{ class: 'hero__picture' }}
|
||||
/>
|
||||
<video
|
||||
class="hero__video img-cover img-doc"
|
||||
muted
|
||||
loop
|
||||
playsinline
|
||||
disablepictureinpicture
|
||||
preload="none"
|
||||
aria-hidden="true"
|
||||
data-hero-video
|
||||
data-mp4="/video/hero-plant.mp4"
|
||||
data-webm="/video/hero-plant.webm"
|
||||
></video>
|
||||
<div class="hero__overlay"></div>
|
||||
</div>
|
||||
|
||||
@@ -53,8 +41,10 @@ const ui = t(lang);
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
/* Fills the screen under the header, and never less than the headline needs. */
|
||||
min-height: max(28rem, calc(100svh - var(--header-height)));
|
||||
/* Runs up under the floating header so the glass bar sits on the image, fills the screen,
|
||||
and is never less than the headline needs. */
|
||||
margin-top: calc(-1 * var(--header-height));
|
||||
min-height: max(28rem, 100svh);
|
||||
overflow: hidden;
|
||||
}
|
||||
.hero__media { position: absolute; inset: 0; }
|
||||
@@ -64,22 +54,10 @@ const ui = t(lang);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: 60% 50%;
|
||||
object-position: 50% 50%;
|
||||
transform-origin: 50% 60%;
|
||||
animation: hero-settle var(--dur-hero) var(--ease) both;
|
||||
}
|
||||
.hero__video {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: 60% 50%;
|
||||
opacity: 0;
|
||||
transition: opacity var(--dur-slow) var(--ease);
|
||||
}
|
||||
/* Set once the video can actually play, so the still never cuts to black. */
|
||||
.hero__video[data-ready] { opacity: 1; }
|
||||
.hero__overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@@ -109,32 +87,6 @@ const ui = t(lang);
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.hero__img, :global(.hero__img), .hero__line { animation: none; opacity: 1; transform: none; }
|
||||
.hero__video { display: none; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
/* The sources are attached here rather than in the markup, so the file is only fetched for
|
||||
someone who will actually see it move: not under reduced motion, not on saved data, and not
|
||||
on a phone, where a 21:9 frame is cropped to a sliver and the bytes cost the most. */
|
||||
const video = document.querySelector<HTMLVideoElement>('[data-hero-video]');
|
||||
const wants = window.matchMedia('(min-width: 48rem)').matches
|
||||
&& !window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
const connection = (navigator as Navigator & { connection?: { saveData?: boolean } }).connection;
|
||||
if (video && wants && !connection?.saveData) {
|
||||
const add = (src: string | null, type: string) => {
|
||||
if (!src) return;
|
||||
const source = document.createElement('source');
|
||||
source.src = src;
|
||||
source.type = type;
|
||||
video.append(source);
|
||||
};
|
||||
add(video.dataset.webm ?? null, 'video/webm');
|
||||
add(video.dataset.mp4 ?? null, 'video/mp4');
|
||||
video.addEventListener('canplay', () => video.setAttribute('data-ready', ''), { once: true });
|
||||
video.preload = 'auto';
|
||||
video.load();
|
||||
/* A blocked autoplay leaves the still in place, which is a fine hero on its own. */
|
||||
video.play().catch(() => {});
|
||||
}
|
||||
</script>
|
||||
Reference in new issue
Block a user