Een footer die zich onthult terwijl je hem nadert. Pure CSS, native animation-timeline.
// Mechanisme: footer-css-scroll-driven-editorial
// Pure CSS scroll-driven animations met animation-timeline: view().
// "Fin" letters reveal char-by-char via scroll-progress; tagline fade in op cover-range.
// JS-fallback: scroll-listener voor browsers zonder scroll-timeline support.
(() => {
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const supportsTimeline = CSS.supports('animation-timeline: view()');
const root = document.querySelector('.fcs-ed');
if (!root) return;
const letters = root.querySelectorAll('.fcs-ed-fin span');
const tagline = root.querySelector('.fcs-ed-tagline');
if (reduce) {
letters.forEach(l => { l.style.opacity = '1'; l.style.transform = 'translateY(0) rotate(0)'; });
if (tagline) tagline.style.opacity = '1';
return;
}
if (supportsTimeline) return; // CSS doet het werk
const sentinel = root.querySelector('.fcs-ed-sentinel');
if (!sentinel) return;
const onScroll = () => {
const r = sentinel.getBoundingClientRect();
const vh = window.innerHeight;
const p = Math.max(0, Math.min(1, 1 - (r.top / vh)));
letters.forEach((l, i) => {
const start = i * 0.08;
const end = start + 0.45;
const lp = Math.max(0, Math.min(1, (p - start) / (end - start)));
l.style.opacity = String(lp);
l.style.transform = 'translateY(' + ((1 - lp) * 60) + 'px) rotate(' + ((1 - lp) * -8) + 'deg)';
});
if (tagline) tagline.style.opacity = String(Math.max(0, Math.min(1, (p - 0.5) / 0.4)));
};
window.addEventListener('scroll', onScroll, { passive: true });
onScroll();
})(); <!-- Skeleton: footer-css-scroll-driven-editorial -->
<div class="fcs-ed">
<div class="fcs-ed-spacer"><span class="fcs-ed-eyebrow">scroll</span></div>
<footer class="fcs-ed-foot">
<div class="fcs-ed-sentinel"></div>
<aside class="fcs-ed-side"><span class="fcs-ed-eyebrow">colophon</span></aside>
<h2 class="fcs-ed-fin"><span>F</span><span>i</span><span>n</span><em>.</em></h2>
<p class="fcs-ed-tagline">Crafted in Amsterdam.</p>
<div class="fcs-ed-meta"><span>Issue 04</span><span>2026</span></div>
</footer>
</div> /* Styling: footer-css-scroll-driven-editorial */
:root {
--ed-paper: #F4F1EB;
--ed-ink: #0A0A0A;
--ed-rule: rgba(10,10,10,.18);
--ed-accent: #FF4A1C;
}
.fcs-ed { background: var(--ed-paper); color: var(--ed-ink); font-family: 'Fraunces', serif; }
.fcs-ed-foot { display: grid; grid-template-columns: minmax(140px,1fr) 4fr 1fr; }
.fcs-ed-fin span {
display: inline-block; opacity: 0;
transform: translateY(60px) rotate(-8deg);
animation: edFin 1.4s cubic-bezier(.19,1,.22,1) both;
animation-timeline: view();
animation-range: entry 10% cover 55%;
}
.fcs-ed-tagline {
opacity: 0;
animation: edFade 1.6s ease-out both;
animation-timeline: view();
animation-range: cover 30% cover 80%;
}
@keyframes edFin { to { opacity: 1; transform: translateY(0) rotate(0); } }
@keyframes edFade { to { opacity: 1; } }
@supports not (animation-timeline: view()) { .fcs-ed-fin span, .fcs-ed-tagline { animation: none; } }
@media (prefers-reduced-motion: reduce) {
.fcs-ed-fin span { opacity: 1 !important; transform: none !important; animation: none !important; }
.fcs-ed-tagline { opacity: 1 !important; animation: none !important; }
}