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/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