SPB Git

spb/svgarden

Public

SVGarden — searchable bank of 74 self-contained SVG+CSS animation snippets (svgarden.dev)

feat(core): scaffold Astro platform — snippet parser, validation gate, server, pm2, deploy

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Aug 10, 2026) Browse files

11 changed files +7,226 −0

added .gitignore +15 −0
@@ -0,0 +1,15 @@
1+# ============================================================
2+# SVGarden — https://www.svgarden.dev
3+# Author : Simon-Pierre Boucher
4+# Contact: contact@spboucher.ai
5+# File : .gitignore
6+# Desc : Never commit secrets, build output, or dependencies
7+# ============================================================
8+node_modules/
9+dist/
10+.astro/
11+.env
12+.env.*
13+ngrok.yml
14+*.log
15+.DS_Store
added README.md +96 −0
@@ -0,0 +1,96 @@
1+# SVGarden 🌱
2+
3+> A massive, searchable bank of SVG + CSS animations with copy-ready code snippets.
4+> **Production:** https://www.svgarden.dev — **Author:** Simon-Pierre Boucher — contact@spboucher.ai
5+
6+Every animation is one self-contained `.html` file: paste it into a blank page and it works.
7+No frameworks, no CDNs, no build step required by the snippets themselves — and each one
8+ships with a short "How it works" lesson.
9+
10+## Quick start
11+
12+```bash
13+npm install
14+npm run dev # local dev server on :4321
15+npm run build # validate snippets + build static site into dist/
16+npm run serve # serve dist/ with the production Express server (:4321, /healthz)
17+```
18+
19+## Adding a snippet
20+
21+```bash
22+npm run new # interactive scaffolder → snippets/<category>/<slug>.html
23+npm run validate # the same gate the build runs
24+```
25+
26+One file = one animation. The gallery, detail page, category pages, search index and
27+tag filters all regenerate at build time — never edit generated pages.
28+
29+Snippet hard rules (enforced by `scripts/validate-snippets.mjs`, which **fails the build**):
30+
31+- SVGarden author header at the top (attribution travels with every copy/download);
32+- `<!--svgarden ... -->` YAML frontmatter (title, slug, category, tags, difficulty,
33+ techniques, how_it_works, customizable, created);
34+- self-contained (no external assets), all classes prefixed `sg-<slug>`,
35+- customizable values as `--sg-*` custom properties declared on the root element,
36+- ≤ 120 lines, `js` tag iff the snippet contains a `<script>`,
37+- every `<svg>` is `aria-hidden="true"` or `role="img"` + `<title>`.
38+
39+## Architecture
40+
41+| Piece | Where |
42+|---|---|
43+| Snippet bank | `snippets/<category>/<slug>.html` |
44+| Parser / single source of truth | `src/lib/snippets.mjs` |
45+| Site (Astro, static output) | `src/pages`, `src/components`, `src/layouts` |
46+| Search index endpoint | `src/pages/search-index.json.js` (Fuse.js, lazy-loaded client-side) |
47+| Build info for `/healthz` | `src/pages/build-info.json.js` |
48+| Production server | `server.mjs` (Express, gzip/brotli, immutable caching for hashed assets) |
49+| Process manager | `ecosystem.config.cjs` (pm2: `svgarden-web` + `svgarden-tunnel`) |
50+| Deploy | `scripts/deploy.sh` |
51+
52+## Deployment (node M3U96b + ngrok)
53+
54+The site runs on cluster node **M3U96b**, served at `127.0.0.1:4321` and exposed publicly
55+through an ngrok tunnel at **https://www.svgarden.dev**.
56+
57+```bash
58+./scripts/deploy.sh
59+```
60+
61+The script syncs the source to the node (via `git pull` once a git remote exists on the
62+node; via `rsync` until then), runs `npm ci` + `npm run build`, restarts both pm2 apps,
63+then health-checks `http://127.0.0.1:4321/healthz` and `https://www.svgarden.dev/healthz`.
64+
65+### One-time ngrok requirements (operator steps)
66+
67+1. ngrok installed and authenticated on the node (`ngrok config add-authtoken <token>`).
68+ **Never** commit or share the token.
69+2. `www.svgarden.dev` reserved as a **custom domain** in the ngrok dashboard
70+ (Universal Gateway → Domains).
71+3. DNS: a `CNAME` for `www.svgarden.dev` pointing at the target ngrok gives you when
72+ reserving the domain. This step happens at your DNS provider; the deploy script
73+ degrades gracefully (local server stays up) until it propagates.
74+4. Tunnel definition in the node's ngrok config (`~/Library/Application Support/ngrok/ngrok.yml`
75+ on macOS — add the block, don't overwrite the file):
76+
77+```yaml
78+tunnels:
79+ svgarden:
80+ proto: http
81+ addr: 4321
82+ domain: www.svgarden.dev
83+```
84+
85+pm2 then keeps both alive across reboots (`pm2 save` + `pm2 startup` already configured
86+on the node): `svgarden-web` runs `server.mjs`, `svgarden-tunnel` runs
87+`ngrok start svgarden --log=stdout`.
88+
89+## Quality gates
90+
91+1. `npm run validate` passes;
92+2. `npm run build` completes with zero warnings;
93+3. new snippets visually verified on light **and** dark preview backgrounds;
94+4. copy-button output pastes into a blank `.html` file and renders correctly.
95+
96+© Simon-Pierre Boucher — contact@spboucher.ai — svgarden.dev
added astro.config.mjs +22 −0
@@ -0,0 +1,22 @@
1+/**
2+ * ============================================================
3+ * SVGarden — https://www.svgarden.dev
4+ * Author : Simon-Pierre Boucher
5+ * Contact: contact@spboucher.ai
6+ * File : astro.config.mjs
7+ * Desc : Astro configuration — static output for svgarden.dev
8+ * ============================================================
9+ */
10+import { defineConfig } from 'astro/config';
11+
12+export default defineConfig({
13+ site: 'https://www.svgarden.dev',
14+ output: 'static',
15+ trailingSlash: 'never',
16+ build: {
17+ format: 'file',
18+ },
19+ server: {
20+ port: 4321,
21+ },
22+});
added ecosystem.config.cjs +29 −0
@@ -0,0 +1,29 @@
1+/**
2+ * ============================================================
3+ * SVGarden — https://www.svgarden.dev
4+ * Author : Simon-Pierre Boucher
5+ * Contact: contact@spboucher.ai
6+ * File : ecosystem.config.cjs
7+ * Desc : pm2 config — static server (svgarden-web) + ngrok tunnel (svgarden-tunnel)
8+ * ============================================================
9+ */
10+module.exports = {
11+ apps: [
12+ {
13+ name: 'svgarden-web',
14+ script: 'server.mjs',
15+ interpreter: 'node',
16+ env: { PORT: 4321, HOST: '127.0.0.1', NODE_ENV: 'production' },
17+ max_restarts: 10,
18+ restart_delay: 2000,
19+ },
20+ {
21+ name: 'svgarden-tunnel',
22+ script: '/opt/homebrew/bin/ngrok',
23+ interpreter: 'none',
24+ args: ['start', 'svgarden', '--log=stdout'],
25+ max_restarts: 10,
26+ restart_delay: 5000,
27+ },
28+ ],
29+};
added package-lock.json +6533 −0
Large diff (6,533 lines) is not loaded by default to keep the page fast.Load diff
added package.json +27 −0
Large diff (27 lines) is not loaded by default to keep the page fast.Load diff
added scripts/deploy.sh +66 −0
Large diff (66 lines) is not loaded by default to keep the page fast.Load diff
added scripts/new-snippet.mjs +82 −0
Large diff (82 lines) is not loaded by default to keep the page fast.Load diff
added scripts/validate-snippets.mjs +122 −0
Large diff (122 lines) is not loaded by default to keep the page fast.Load diff
added server.mjs +65 −0
Large diff (65 lines) is not loaded by default to keep the page fast.Load diff
added src/lib/snippets.mjs +169 −0
Large diff (170 lines) is not loaded by default to keep the page fast.Load diff
Menu

Theme