Marquee.astro (1746B)
1 --- 2 interface LaneTitle { n: string; title: string; accent: string } 3 interface LaneMeta { tag: string; text: string; accent: string } 4 interface Props { 5 titles: LaneTitle[]; 6 meta: LaneMeta[]; 7 /** The manifest count shown in the fixed cap. */ 8 manifest: string; 9 } 10 const { titles, meta, manifest } = Astro.props; 11 12 /** 13 * The manifest band — two counter-scrolling lanes with a fixed inverse-plate 14 * cap, ported from the main site's dual-lane marquee. The title lane scrolls 15 * left, the meta lane scrolls right; each list is duplicated so the −50% loop 16 * is seamless. Both pause on hover; static under reduced motion. 17 */ 18 const titleRun = [...titles, ...titles]; 19 const metaRun = [...meta, ...meta]; 20 --- 21 <section class="vault-lanes bleed" aria-label="Manifest" data-marquee> 22 <div class="vault-lanes__body"> 23 <div class="vault-lane-wrap vault-lane-wrap--top"> 24 <div class="vault-lane"> 25 {titleRun.map((t, i) => ( 26 <div class="vault-title" style={`--acc: var(--${t.accent});`} aria-hidden={i >= titles.length ? 'true' : undefined}> 27 <span class="n">{t.n}</span><span class="t">{t.title}</span> 28 </div> 29 ))} 30 </div> 31 </div> 32 <div class="vault-lane-wrap"> 33 <div class="vault-lane-rev"> 34 {metaRun.map((m, i) => ( 35 <div class="vault-meta" style={`--acc: var(--${m.accent});`} aria-hidden={i >= meta.length ? 'true' : undefined}> 36 <span class="tag">{m.tag}</span><span class="tx">{m.text}</span><span class="sep">/</span> 37 </div> 38 ))} 39 </div> 40 </div> 41 </div> 42 <div class="vault-cap" aria-hidden="true"> 43 <span>Manifest ^: {manifest}</span> 44 <span class="verified"><i></i>Verified · MMXXVI</span> 45 </div> 46 </section>