SPB Git

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%
2.7 KB · 81 lines astro
Raw Blame History
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