1. Mechanisme — kopieer 1-op-1, geen styling-keuzes
// Mechanisme: gallery-marquee-scrub-gallery
import gsap from 'https://esm.sh/[email protected]';
import{ScrollTrigger}from 'https://esm.sh/[email protected]/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
if(window.matchMedia('(prefers-reduced-motion: reduce)').matches)return;
const track=document.querySelector('.msg-track');
const sec=document.querySelector('.msg-section');
const tween=gsap.to(track,{
x:()=>-(track.scrollWidth/2),
ease:'none',
scrollTrigger:{trigger:sec,start:'top bottom',end:'bottom top',scrub:1}
});
let last=0;
window.addEventListener('scroll',()=>{
const v=Math.min(3,Math.abs(window.scrollY-last)*0.06+1);last=window.scrollY;
gsap.to(tween,{timeScale:v,duration:0.2,overwrite:true});
},{passive:true}); 2. Skeleton — DOM + class-namen, mag herschikken
<!-- Skeleton: gallery-marquee-scrub-gallery -->
<section class="msg-section">
<header class="msg-head">
<h2>Field Notes ’26</h2>
<span>Scroll — track scrubs to scroll progress</span>
</header>
<div class="msg-viewport">
<div class="msg-track">
<figure class="msg-tile"><canvas data-i="0"></canvas><figcaption>Atrium · 01</figcaption></figure>
<!-- 12 tiles totaal (6 unique × 2 voor seamless loop) -->
</div>
</div>
</section> 3. Styling-template — verplicht eigen invulling per merk
/* Styling: gallery-marquee-scrub-gallery — image-asset-forward, generous bleed */
.msg-section{background:#1a1a1a;color:#F4F1EB;padding:clamp(4rem,9vw,9rem) 0;min-height:90vh}
.msg-head h2{font-family:'Fraunces',serif;font-weight:300;font-size:clamp(2.5rem,5vw,4.5rem);margin:0;letter-spacing:-.02em}
.msg-viewport{overflow:hidden;padding:0 clamp(1.5rem,5vw,4rem)}
.msg-track{display:flex;gap:2rem;will-change:transform}
.msg-tile{flex:0 0 clamp(260px,28vw,420px);margin:0}
.msg-tile canvas{width:100%;aspect-ratio:4/5;display:block}
.msg-tile figcaption{font-family:'Inter',sans-serif;font-weight:300;font-size:.78rem;letter-spacing:.14em;text-transform:uppercase;color:rgba(244,241,235,.65);margin-top:.9rem}