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%
3.3 KB · 92 lines astro
Raw Blame History
1---2/**3 * ============================================================4 * SVGarden — https://www.svgarden.dev5 * Author : Simon-Pierre Boucher6 * Contact: contact@spboucher.ai7 * File   : src/pages/index.astro8 * Desc   : Gallery home — all snippets, live previews, search + filters9 * ============================================================10 */11import Base from '../layouts/Base.astro';12import SnippetCard from '../components/SnippetCard.astro';13import SearchBar from '../components/SearchBar.astro';14import TagFilter from '../components/TagFilter.astro';15import { loadSnippets, loadCategories } from '../lib/snippets.mjs';1617const snippets = await loadSnippets();18const categories = await loadCategories();1920// Technique facet: most-used techniques across the bank (top 18)21const techniqueCounts = new Map();22for (const s of snippets) {23  for (const t of s.techniques) techniqueCounts.set(t, (techniqueCounts.get(t) ?? 0) + 1);24}25const techniques = [...techniqueCounts.entries()]26  .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))27  .slice(0, 18)28  .map(([name, count]) => ({ name, count }));29---3031<Base>32  <section class="sg-hero">33    <h1>A garden of SVG + CSS animations</h1>34    <p>35      {snippets.length} self-contained snippets you can preview live, customize, and copy.36      Every one works when pasted into a blank <code>.html</code> file — no frameworks, no build step,37      and each comes with a short lesson on how it works.38    </p>39  </section>4041  <div class="sg-toolbar">42    <SearchBar />43    <span class="sg-count" data-sg-count aria-live="polite">{snippets.length} snippets</span>44  </div>45  <div class="sg-toolbar" style="padding-top: 0;">46    <TagFilter categories={categories} techniques={techniques} />47  </div>4849  <div class="sg-grid" data-sg-grid>50    {snippets.map((s) => <SnippetCard snippet={s} />)}51  </div>52  <p class="sg-empty" data-sg-empty hidden>No snippets match — try a broader search.</p>53</Base>5455<script>56  // Combines search results (sg:search) with chip filters (sg:filter).57  const cards = [...document.querySelectorAll<HTMLElement>('[data-sg-grid] .sg-card')];58  const count = document.querySelector('[data-sg-count]')!;59  const empty = document.querySelector<HTMLElement>('[data-sg-empty]')!;6061  let matched: string[] | null = null; // null → no active search62  let category = '';63  let difficulty = '';64  let technique = '';6566  const apply = () => {67    const order = new Map(matched?.map((slug, i) => [slug, i]));68    let visible = 0;69    for (const card of cards) {70      const okSearch = matched === null || order.has(card.dataset.slug!);71      const okCat = !category || card.dataset.category === category;72      const okDiff = !difficulty || card.dataset.difficulty === difficulty;73      const okTech = !technique || (card.dataset.techniques ?? '').split('|').includes(technique);74      const show = okSearch && okCat && okDiff && okTech;75      card.hidden = !show;76      card.style.order = String(order.get(card.dataset.slug!) ?? 0);77      if (show) visible++;78    }79    count.textContent = `${visible} snippet${visible === 1 ? '' : 's'}`;80    empty.hidden = visible > 0;81  };8283  document.addEventListener('sg:search', (e) => {84    matched = (e as CustomEvent).detail.slugs;85    apply();86  });87  document.addEventListener('sg:filter', (e) => {88    ({ category, difficulty, technique } = (e as CustomEvent).detail);89    apply();90  });91</script>92