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 · 158 lines markdown
Rendered Raw Blame History
1# SVGarden 🌱23> A massive, searchable bank of SVG + CSS animations with copy-ready code snippets.45<p>6  <a href="https://www.svgarden.dev"><img alt="Live" src="https://img.shields.io/badge/live-svgarden.dev-7F77DD?style=flat-square&logo=ngrok&logoColor=white"></a>7  <img alt="Snippets" src="https://img.shields.io/badge/snippets-74-7F77DD?style=flat-square">8  <img alt="Categories" src="https://img.shields.io/badge/categories-14-8b84e0?style=flat-square">9  <img alt="Techniques" src="https://img.shields.io/badge/techniques_taught-177-9c95e8?style=flat-square">10  <img alt="Lighthouse performance" src="https://img.shields.io/badge/lighthouse_perf-100-1a7f4e?style=flat-square&logo=lighthouse&logoColor=white">11  <img alt="Lighthouse accessibility" src="https://img.shields.io/badge/lighthouse_a11y-100-1a7f4e?style=flat-square&logo=lighthouse&logoColor=white">12  <img alt="Visitor dependencies" src="https://img.shields.io/badge/visitor_deps-0-1a7f4e?style=flat-square">13  <img alt="Astro" src="https://img.shields.io/badge/Astro-5-BC52EE?style=flat-square&logo=astro&logoColor=white">14  <img alt="Node" src="https://img.shields.io/badge/node-%E2%89%A5%2020-339933?style=flat-square&logo=node.js&logoColor=white">15  <img alt="License" src="https://img.shields.io/badge/license-MIT-blue?style=flat-square">16</p>1718**Production:** https://www.svgarden.dev · **Author:** Simon-Pierre Boucher — contact@spboucher.ai1920Every animation is one self-contained `.html` file: paste it into a blank page and it works.21No frameworks, no CDNs, no build step required by the snippets themselves — and each one22ships with a short "How it works" lesson. The site is static (Astro), searched client-side23(lazy Fuse.js), highlighted at build time (Shiki), and customized live through CSS custom24properties.2526## 📊 Metrics2728### The bank2930| Metric | Value |31|---|---|32| 🟣 Snippets | **74** |33| 🗂 Categories | **14** |34| 🧪 Distinct techniques taught | **177** |35| ⚡ JavaScript-free snippets | **67 / 74** (90 %) |36| 🧭 Snippets with a browser-support caveat (`support`) | **13** |37| 📏 Average snippet size | **57 lines** (max 116, hard cap 150) |38| 🧩 Pages generated per build | **84** |3940### Difficulty4142| | Level | Count | Share |43|---|---|---|---|44| 🟢 | beginner | 9 | 12 % |45| 🟣 | intermediate | 27 | 36 % |46| 🔴 | advanced | 38 | 51 % |4748### Categories4950| Category | Count | Headline techniques |51|---|---:|---|52| 🔘 `buttons` | 8 | goo filter, clipPath sweeps, `@property` hue trace, `linear()` springs, multi-stage morph |53| 🎛 `filters` | 8 | feTurbulence, feDisplacementMap, goo matrix, feMorphology, feDropShadow stacks, backdrop-filter |54| 🖼 `backgrounds` | 7 | mix-blend-mode aurora, SMIL topo morphs, turbulence rain refraction, seamless tiling |55| 🌀 `loaders` | 6 | dasharray chase, counter-rotation, choreographed keyframes |56| ✨ `text-fx` | 6 | mask knockout, variable-font weight waves, liquid clipPath fill, scramble decode |57| 📜 `scroll` | 6 | `animation-timeline: scroll()` / `view()`, `animation-range`, sticky scenes |58| 📈 `charts` | 6 | `conic-gradient()` + `@property` sweeps, live polyline updates, `--sg-i` cascades |59| 🎚 `interactive` | 6 | `:checked` scenes, reversed-radio rating, `<details>` accordion, pseudo-3D tilt |60| 🛰 `motion-path` | 5 | `offset-path`, `animateMotion rotate="auto"`, keyPoints/keyTimes pacing |61| ✏️ `stroke-draw` | 4 | pathLength normalization, staggered dashoffset |62| 🖱 `hover` | 4 | transform-box, transition choreography |63| ⏲ `gauges` | 3 | dashoffset data-mapping, calc() needle angles |64| 🔤 `text` | 3 | textPath, SMIL startOffset, glyph stroke tracing |65| 🫧 `morph` | 2 | SMIL d-morphing, CSS `d:` with fallback |6667### Quality gates (every build)6869| Gate | Status |70|---|---|71| `validate-snippets.mjs` (headers, frontmatter, scoping, self-containment, size) | 🟢 74/74 |72| `npm run build` warnings | 🟢 0 |73| Lighthouse performance / accessibility (home + detail) | 🟢 100 / 100 |74| Paste-test (copy output → blank .html) | 🟢 verified per batch |7576## Quick start7778```bash79npm install80npm run dev        # local dev server on :432181npm run build      # validate snippets + build static site into dist/82npm run serve      # serve dist/ with the production Express server (:4321, /healthz)83```8485## Adding a snippet8687```bash88npm run new        # interactive scaffolder → snippets/<category>/<slug>.html89npm run validate   # the same gate the build runs90```9192One file = one animation. The gallery, detail page, category pages, search index and93tag filters all regenerate at build time — never edit generated pages.9495Snippet hard rules (enforced by `scripts/validate-snippets.mjs`, which **fails the build**):9697- SVGarden author header at the top (attribution travels with every copy/download);98- `<!--svgarden ... -->` YAML frontmatter (title, slug, category, tags, difficulty,99  techniques, how_it_works, customizable, created, and optional `support`);100- self-contained (no external assets), all classes prefixed `sg-<slug>`,101- customizable values as `--sg-*` custom properties declared on the root element,102- ≤ 150 lines, `js` tag iff the snippet contains a `<script>`,103- every `<svg>` is `aria-hidden="true"` or `role="img"` + `<title>`;104- cutting-edge CSS (`@property`, `animation-timeline`, `:has()`, `backdrop-filter`,105  `linear()`, CSS `d`) must sit behind `@supports` with a graceful static fallback,106  **and** declare a `support: <one-line caveat>` frontmatter field — the detail page107  renders it as a browser-support note.108109## Architecture110111| Piece | Where |112|---|---|113| Snippet bank | `snippets/<category>/<slug>.html` |114| Parser / single source of truth | `src/lib/snippets.mjs` |115| Site (Astro, static output) | `src/pages`, `src/components`, `src/layouts` |116| Search index endpoint | `src/pages/search-index.json.js` (Fuse.js, lazy-loaded client-side) |117| Build info for `/healthz` | `src/pages/build-info.json.js` |118| Production server | `server.mjs` (Express, gzip/brotli, immutable caching for hashed assets) |119| Process manager | `ecosystem.config.cjs` (pm2: `svgarden-web` + `svgarden-tunnel`) |120| Deploy | `scripts/deploy.sh` |121122## Deployment (node M3U96b + ngrok)123124The site runs on cluster node **M3U96b**, served at `127.0.0.1:4321` and exposed publicly125through an ngrok tunnel at **https://www.svgarden.dev**.126127```bash128./scripts/deploy.sh129```130131The script syncs the source to the node (via `git pull` once a git remote exists on the132node; via `rsync` until then), runs `npm ci` + `npm run build`, restarts both pm2 apps,133then health-checks `http://127.0.0.1:4321/healthz` and `https://www.svgarden.dev/healthz`.134135### One-time ngrok requirements (operator steps)1361371. ngrok installed and authenticated on the node (`ngrok config add-authtoken <token>`).138   **Never** commit or share the token.1392. `www.svgarden.dev` reserved as a **custom domain** in the ngrok dashboard140   (Universal Gateway → Domains).1413. DNS: a `CNAME` for `www.svgarden.dev` pointing at the target ngrok gives you when142   reserving the domain.1434. Tunnel definition in the node's ngrok config (`~/Library/Application Support/ngrok/ngrok.yml`144   on macOS — add the block, don't overwrite the file):145146```yaml147tunnels:148  svgarden:149    proto: http150    addr: 4321151    domain: www.svgarden.dev152```153154pm2 keeps both alive across reboots (`pm2 save` + `pm2 startup`): `svgarden-web` runs155`server.mjs`, `svgarden-tunnel` runs `ngrok start svgarden --log=stdout`.156157© Simon-Pierre Boucher — contact@spboucher.ai — svgarden.dev158