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%
7.4 KB

# SVGarden 🌱

A massive, searchable bank of SVG + CSS animations with copy-ready code snippets.

Live Snippets Categories Techniques Lighthouse performance Lighthouse accessibility Visitor dependencies Astro Node License

Production: https://www.svgarden.dev · Author: Simon-Pierre Boucher — contact@spboucher.ai

Every animation is one self-contained .html file: paste it into a blank page and it works. No frameworks, no CDNs, no build step required by the snippets themselves — and each one ships with a short "How it works" lesson. The site is static (Astro), searched client-side (lazy Fuse.js), highlighted at build time (Shiki), and customized live through CSS custom properties.

# 📊 Metrics

# The bank

Metric Value
🟣 Snippets 74
🗂 Categories 14
🧪 Distinct techniques taught 177
⚡ JavaScript-free snippets 67 / 74 (90 %)
🧭 Snippets with a browser-support caveat (support) 13
📏 Average snippet size 57 lines (max 116, hard cap 150)
🧩 Pages generated per build 84

# Difficulty

Level Count Share
🟢 beginner 9 12 %
🟣 intermediate 27 36 %
🔴 advanced 38 51 %

# Categories

Category Count Headline techniques
🔘 buttons 8 goo filter, clipPath sweeps, @property hue trace, linear() springs, multi-stage morph
🎛 filters 8 feTurbulence, feDisplacementMap, goo matrix, feMorphology, feDropShadow stacks, backdrop-filter
🖼 backgrounds 7 mix-blend-mode aurora, SMIL topo morphs, turbulence rain refraction, seamless tiling
🌀 loaders 6 dasharray chase, counter-rotation, choreographed keyframes
text-fx 6 mask knockout, variable-font weight waves, liquid clipPath fill, scramble decode
📜 scroll 6 animation-timeline: scroll() / view(), animation-range, sticky scenes
📈 charts 6 conic-gradient() + @property sweeps, live polyline updates, --sg-i cascades
🎚 interactive 6 :checked scenes, reversed-radio rating, <details> accordion, pseudo-3D tilt
🛰 motion-path 5 offset-path, animateMotion rotate="auto", keyPoints/keyTimes pacing
✏️ stroke-draw 4 pathLength normalization, staggered dashoffset
🖱 hover 4 transform-box, transition choreography
gauges 3 dashoffset data-mapping, calc() needle angles
🔤 text 3 textPath, SMIL startOffset, glyph stroke tracing
🫧 morph 2 SMIL d-morphing, CSS d: with fallback

# Quality gates (every build)

Gate Status
validate-snippets.mjs (headers, frontmatter, scoping, self-containment, size) 🟢 74/74
npm run build warnings 🟢 0
Lighthouse performance / accessibility (home + detail) 🟢 100 / 100
Paste-test (copy output → blank .html) 🟢 verified per batch

# Quick start

bash
npm install
npm run dev        # local dev server on :4321
npm run build      # validate snippets + build static site into dist/
npm run serve      # serve dist/ with the production Express server (:4321, /healthz)

# Adding a snippet

bash
npm run new        # interactive scaffolder → snippets/<category>/<slug>.html
npm run validate   # the same gate the build runs

One file = one animation. The gallery, detail page, category pages, search index and tag filters all regenerate at build time — never edit generated pages.

Snippet hard rules (enforced by scripts/validate-snippets.mjs, which fails the build):

  • SVGarden author header at the top (attribution travels with every copy/download);
  • <!--svgarden ... --> YAML frontmatter (title, slug, category, tags, difficulty, techniques, how_it_works, customizable, created, and optional support);
  • self-contained (no external assets), all classes prefixed sg-<slug>,
  • customizable values as --sg-* custom properties declared on the root element,
  • ≤ 150 lines, js tag iff the snippet contains a <script>,
  • every <svg> is aria-hidden="true" or role="img" + <title>;
  • cutting-edge CSS (@property, animation-timeline, :has(), backdrop-filter, linear(), CSS d) must sit behind @supports with a graceful static fallback, and declare a support: <one-line caveat> frontmatter field — the detail page renders it as a browser-support note.

# Architecture

Piece Where
Snippet bank snippets/<category>/<slug>.html
Parser / single source of truth src/lib/snippets.mjs
Site (Astro, static output) src/pages, src/components, src/layouts
Search index endpoint src/pages/search-index.json.js (Fuse.js, lazy-loaded client-side)
Build info for /healthz src/pages/build-info.json.js
Production server server.mjs (Express, gzip/brotli, immutable caching for hashed assets)
Process manager ecosystem.config.cjs (pm2: svgarden-web + svgarden-tunnel)
Deploy scripts/deploy.sh

# Deployment (node M3U96b + ngrok)

The site runs on cluster node M3U96b, served at 127.0.0.1:4321 and exposed publicly through an ngrok tunnel at https://www.svgarden.dev.

bash
./scripts/deploy.sh

The script syncs the source to the node (via git pull once a git remote exists on the node; via rsync until then), runs npm ci + npm run build, restarts both pm2 apps, then health-checks http://127.0.0.1:4321/healthz and https://www.svgarden.dev/healthz.

# One-time ngrok requirements (operator steps)

  1. ngrok installed and authenticated on the node (ngrok config add-authtoken <token>). Never commit or share the token.
  2. www.svgarden.dev reserved as a custom domain in the ngrok dashboard (Universal Gateway → Domains).
  3. DNS: a CNAME for www.svgarden.dev pointing at the target ngrok gives you when reserving the domain.
  4. Tunnel definition in the node's ngrok config (~/Library/Application Support/ngrok/ngrok.yml on macOS — add the block, don't overwrite the file):
yaml
tunnels:
  svgarden:
    proto: http
    addr: 4321
    domain: www.svgarden.dev

pm2 keeps both alive across reboots (pm2 save + pm2 startup): svgarden-web runs server.mjs, svgarden-tunnel runs ngrok start svgarden --log=stdout.

© Simon-Pierre Boucher — contact@spboucher.ai — svgarden.dev