spb/svgarden Public
SVGarden — searchable bank of 74 self-contained SVG+CSS animation snippets (svgarden.dev)
HTML 79.2%
Astro 10.3%
JavaScript 6%
CSS 3.7%
Shell 0.8%
1---2/**3 * ============================================================4 * SVGarden — https://www.svgarden.dev5 * Author : Simon-Pierre Boucher6 * Contact: contact@spboucher.ai7 * File : src/components/SnippetCard.astro8 * Desc : Gallery card — lazy live preview, title, category + difficulty badges9 * ============================================================10 */11import LivePreview from './LivePreview.astro';1213interface Props {14 snippet: {15 slug: string;16 title: string;17 category: string;18 difficulty: string;19 tags: string[];20 techniques: string[];21 code: string;22 created: string;23 };24}2526const { snippet } = Astro.props;27// "New" badge for snippets created in the last 14 days (evaluated at build time)28const isNew = Date.now() - Date.parse(snippet.created) < 14 * 24 * 60 * 60 * 1000;29---3031<article32 class="sg-card"33 data-slug={snippet.slug}34 data-category={snippet.category}35 data-difficulty={snippet.difficulty}36 data-techniques={snippet.techniques.join('|')}37>38 <LivePreview snippet={snippet} variant="card" />39 <div class="sg-card-body">40 <h2 class="sg-card-title">41 <a href={`/snippet/${snippet.slug}`}>{snippet.title}</a>42 </h2>43 <div class="sg-card-meta">44 {isNew && <span class="sg-badge sg-badge-new">New</span>}45 <span class="sg-badge sg-badge-cat">{snippet.category}</span>46 <span class={`sg-badge sg-badge-${snippet.difficulty}`}>{snippet.difficulty}</span>47 {snippet.tags.slice(0, 3).map((t) => <span class="sg-badge">{t}</span>)}48 </div>49 </div>50</article>5152<script>53 // Lazy-render previews when cards approach the viewport, and keep the54 // preview background in sync with the site theme. Runs once per page.55 const setBg = (frame: HTMLIFrameElement) => {56 const dark = document.documentElement.dataset.theme === 'dark';57 frame.contentDocument?.body?.style.setProperty('--sg-preview-bg', dark ? '#111113' : '#ffffff');58 };5960 const frames = [...document.querySelectorAll<HTMLIFrameElement>('iframe[data-sg-lazy-srcdoc]')];6162 const io = new IntersectionObserver(63 (entries) => {64 for (const entry of entries) {65 if (!entry.isIntersecting) continue;66 const frame = entry.target as HTMLIFrameElement;67 frame.addEventListener('load', () => setBg(frame), { once: true });68 frame.srcdoc = frame.dataset.sgLazySrcdoc!;69 frame.removeAttribute('data-sg-lazy-srcdoc');70 io.unobserve(frame);71 }72 },73 { rootMargin: '300px' }74 );75 frames.forEach((f) => io.observe(f));7677 new MutationObserver(() => {78 document.querySelectorAll<HTMLIFrameElement>('.sg-card-preview iframe').forEach(setBg);79 }).observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });80</script>81