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/SearchBar.astro8 * Desc : Instant client-side search — Fuse.js lazy-loaded on first focus9 * ============================================================10 */11---1213<div class="sg-search">14 <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true">15 <circle cx="11" cy="11" r="7" />16 <path d="m20 20-3.5-3.5" />17 </svg>18 <input19 type="search"20 id="sg-search-input"21 placeholder="Search animations… (title, tag, technique)"22 autocomplete="off"23 aria-label="Search animations"24 />25 <kbd aria-hidden="true">/</kbd>26</div>2728<script>29 import type Fuse from 'fuse.js';3031 const input = document.getElementById('sg-search-input') as HTMLInputElement;32 let fuse: Fuse<{ slug: string }> | null = null;33 let loading: Promise<void> | null = null;3435 // Lazy-load Fuse.js + the index only when the visitor actually searches.36 const ensureIndex = () =>37 (loading ??= Promise.all([38 import('fuse.js'),39 fetch('/search-index.json').then((r) => r.json()),40 ]).then(([{ default: FuseCtor }, docs]) => {41 fuse = new FuseCtor(docs, {42 keys: [43 { name: 'title', weight: 3 },44 { name: 'tags', weight: 2 },45 { name: 'techniques', weight: 2 },46 { name: 'category', weight: 1 },47 { name: 'desc', weight: 1 },48 ],49 threshold: 0.35,50 ignoreLocation: true,51 });52 }));5354 const emit = (slugs: string[] | null) =>55 document.dispatchEvent(new CustomEvent('sg:search', { detail: { slugs } }));5657 input.addEventListener('focus', () => void ensureIndex(), { once: true });58 input.addEventListener('input', async () => {59 const q = input.value.trim();60 if (!q) return emit(null);61 await ensureIndex();62 emit(fuse!.search(q).map((r) => r.item.slug));63 });6465 // `/` focuses search from anywhere (unless already typing in a field)66 document.addEventListener('keydown', (e) => {67 if (e.key === '/' && !(e.target instanceof HTMLInputElement) && !(e.target instanceof HTMLTextAreaElement)) {68 e.preventDefault();69 input.focus();70 }71 });72</script>73