This commit is contained in:
Felix Klement committed 2026-10-03 12:23:33 +02:00
1 parent 755d91d0f4
commit c8740fb1d3
3 files changed
+229 -4

No files matched your search

+33 -4
View File
@@ -1,6 +1,6 @@
---
import { Picture } from 'astro:assets';
import background from '../../assets/images/background.png';
import poster from '../../assets/images/hero-field.png';
import { t, type Lang } from '../../i18n/ui';
interface Props { lang: Lang }
@@ -9,9 +9,12 @@ const ui = t(lang);
---
<section class="hero night" aria-labelledby="hero-title">
<div class="hero__media">
<!-- Abstract and decorative, so it carries no alt text. -->
<!-- Abstract and decorative, so neither layer carries alt text. The poster is a frame of
the noise field, so it carries the hero until the canvas is drawing, and stays for
anyone who never gets one: reduced motion, no WebGL, no script. -->
<div class="hero__layers">
<Picture
src={background}
src={poster}
alt=""
quality={70}
formats={['avif']}
@@ -24,6 +27,8 @@ const ui = t(lang);
class="hero__img img-cover"
pictureAttributes={{ class: 'hero__picture' }}
/>
<canvas class="hero__field" aria-hidden="true" data-hero-field></canvas>
</div>
<div class="hero__overlay"></div>
</div>
@@ -55,9 +60,23 @@ const ui = t(lang);
height: 100%;
object-fit: cover;
object-position: 50% 50%;
}
.hero__layers {
position: absolute;
inset: 0;
transform-origin: 50% 60%;
animation: hero-settle var(--dur-hero) var(--ease) both;
}
.hero__field {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
opacity: 0;
transition: opacity var(--dur-slow) var(--ease);
}
/* Set by the first drawn frame, so the poster never cuts to black. */
.hero__field[data-ready] { opacity: 1; }
.hero__overlay {
position: absolute;
inset: 0;
@@ -89,7 +108,17 @@ const ui = t(lang);
}
@media (prefers-reduced-motion: reduce) {
.hero__img, :global(.hero__img), .hero__line { animation: none; opacity: 1; transform: none; }
.hero__layers, .hero__line { animation: none; opacity: 1; transform: none; }
.hero__field { display: none; }
}
</style>
<script>
import { startNoiseField } from '../../scripts/noise-field';
const canvas = document.querySelector<HTMLCanvasElement>('[data-hero-field]');
const hero = canvas?.closest<HTMLElement>('.hero');
if (canvas && hero && !window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
startNoiseField(canvas, hero);
}
</script>