diff --git a/.changeset/website-hero-animation.md b/.changeset/website-hero-animation.md
new file mode 100644
index 000000000..0feed04d7
--- /dev/null
+++ b/.changeset/website-hero-animation.md
@@ -0,0 +1,4 @@
+---
+---
+
+chore(website): add Framefields hero animation source for the website landing page
diff --git a/.dockerignore b/.dockerignore
index ce192cabc..999ff9e21 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -15,4 +15,6 @@ Dockerfile
*.md
.nostr
postgresql.conf
-test/integration/docker-compose.yml
\ No newline at end of file
+test/integration/docker-compose.yml
+# website tooling (not part of the relay image)
+website
diff --git a/biome.json b/biome.json
index 0891c4033..f04518119 100644
--- a/biome.json
+++ b/biome.json
@@ -32,7 +32,7 @@
},
"overrides": [
{
- "includes": ["migrations/**", "src/clean-db.ts", "src/import-events.ts", "src/scripts/**", "test/**"],
+ "includes": ["migrations/**", "src/clean-db.ts", "src/import-events.ts", "src/scripts/**", "test/**", "website/**"],
"linter": {
"rules": {
"suspicious": {
diff --git a/website/hero-animation/.gitignore b/website/hero-animation/.gitignore
new file mode 100644
index 000000000..9a575bfc7
--- /dev/null
+++ b/website/hero-animation/.gitignore
@@ -0,0 +1,5 @@
+node_modules/
+output/
+.framefields/
+assets/fonts/*.ttf
+assets/gen/
diff --git a/website/hero-animation/.nvmrc b/website/hero-animation/.nvmrc
new file mode 100644
index 000000000..2bd5a0a98
--- /dev/null
+++ b/website/hero-animation/.nvmrc
@@ -0,0 +1 @@
+22
diff --git a/website/hero-animation/README.md b/website/hero-animation/README.md
new file mode 100644
index 000000000..c899ee694
--- /dev/null
+++ b/website/hero-animation/README.md
@@ -0,0 +1,51 @@
+# nostream hero animation (Framefields)
+
+Landing-page hero for the upcoming nostream website: **34 s** at 1920×1080, built with [Framefields](https://github.com/gatewai-dev/framefields) and styled after [omp.sh](https://omp.sh/) (near-black with a dot grid, graded white → gray → pink headlines, teal mono labels, numbered stack rail, terminal strips and a particle planet).
+
+1. **Hero**: "Your relay. Your rules. Nostr, wired in.", `$ nostream start`, key stats
+2. **What is nostream**: Nostr (protocol) → Relays (servers) → nostream (the relay you own)
+3. **How it works**: Nostr clients → nostream core (verify, limits, admission, NIP-11) → PostgreSQL + Redis, with Lightning payments and Docker/Tor deploy. Real `EVENT` / `REQ` / `EOSE` messages, pulses for events in, stored and fanned out, and a stored-event counter
+4. **Why nostream**: supported NIPs (counted from the root `package.json`), paid admission, rate limits, import/export, Tor & I2P, admin console
+5. **Deploy**: typed `nostream start` with boot output and a "built with" logo row
+
+Logos are official marks: [simple-icons](https://simpleicons.org/) (CC0) for PostgreSQL, Redis, Docker, TypeScript, Node.js, Bitcoin, Lightning, Tor and GitHub, and the [Nostr logo](https://github.com/mbarulli/nostr-logo) (CC0).
+
+This folder is a standalone package; it is not part of the relay build or Docker image.
+
+## Requirements
+
+- **Node.js ≥ 22.20** (Framefields + WebCodecs)
+- **WebGPU** (browser preview, or a GPU for headless MP4 export, e.g. Apple Silicon)
+
+```bash
+cd website/hero-animation
+nvm use # reads .nvmrc → 22
+pnpm install
+pnpm run setup # fonts → assets/fonts/, brand logos + planet → assets/gen/
+```
+
+## Preview
+
+```bash
+pnpm run preview
+```
+
+## Export MP4
+
+```bash
+pnpm run render # → output/nostream-hero.mp4 (very high bitrate, ~45 MB)
+pnpm run render:small # → output/nostream-hero-low.mp4 (~4 MB, for sharing / PR previews)
+```
+
+## Sample frames
+
+```bash
+pnpm run frames
+# → output/frames/f0130.png …
+```
+
+## Edit the story
+
+- Timeline & layout: `src/film.ts` (scene windows `HERO`, `WHAT`, `FLOW`, `FEATURES`, `DEPLOY`)
+- Palette & fonts: `src/theme.ts`
+- Logos / planet / grid: `src/build-assets.ts` (source SVGs in `assets/icons-raw/`, Nostr PNG in `assets/logos/`)
diff --git a/website/hero-animation/assets/icons-raw/bitcoin.svg b/website/hero-animation/assets/icons-raw/bitcoin.svg
new file mode 100644
index 000000000..c44ddcc95
--- /dev/null
+++ b/website/hero-animation/assets/icons-raw/bitcoin.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/website/hero-animation/assets/icons-raw/docker.svg b/website/hero-animation/assets/icons-raw/docker.svg
new file mode 100644
index 000000000..0021a8a7b
--- /dev/null
+++ b/website/hero-animation/assets/icons-raw/docker.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/website/hero-animation/assets/icons-raw/github.svg b/website/hero-animation/assets/icons-raw/github.svg
new file mode 100644
index 000000000..538ec5bf2
--- /dev/null
+++ b/website/hero-animation/assets/icons-raw/github.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/website/hero-animation/assets/icons-raw/lightning.svg b/website/hero-animation/assets/icons-raw/lightning.svg
new file mode 100644
index 000000000..89f7f2749
--- /dev/null
+++ b/website/hero-animation/assets/icons-raw/lightning.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/website/hero-animation/assets/icons-raw/nodedotjs.svg b/website/hero-animation/assets/icons-raw/nodedotjs.svg
new file mode 100644
index 000000000..281c82962
--- /dev/null
+++ b/website/hero-animation/assets/icons-raw/nodedotjs.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/website/hero-animation/assets/icons-raw/postgresql.svg b/website/hero-animation/assets/icons-raw/postgresql.svg
new file mode 100644
index 000000000..dcf75b726
--- /dev/null
+++ b/website/hero-animation/assets/icons-raw/postgresql.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/website/hero-animation/assets/icons-raw/redis.svg b/website/hero-animation/assets/icons-raw/redis.svg
new file mode 100644
index 000000000..fc47db864
--- /dev/null
+++ b/website/hero-animation/assets/icons-raw/redis.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/website/hero-animation/assets/icons-raw/torproject.svg b/website/hero-animation/assets/icons-raw/torproject.svg
new file mode 100644
index 000000000..2af1961e4
--- /dev/null
+++ b/website/hero-animation/assets/icons-raw/torproject.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/website/hero-animation/assets/icons-raw/typescript.svg b/website/hero-animation/assets/icons-raw/typescript.svg
new file mode 100644
index 000000000..e8beb7403
--- /dev/null
+++ b/website/hero-animation/assets/icons-raw/typescript.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/website/hero-animation/assets/logos/nostr.png b/website/hero-animation/assets/logos/nostr.png
new file mode 100644
index 000000000..137edae1d
Binary files /dev/null and b/website/hero-animation/assets/logos/nostr.png differ
diff --git a/website/hero-animation/package.json b/website/hero-animation/package.json
new file mode 100644
index 000000000..1f053b814
--- /dev/null
+++ b/website/hero-animation/package.json
@@ -0,0 +1,39 @@
+{
+ "name": "nostream-hero-animation",
+ "version": "1.0.0",
+ "private": true,
+ "type": "module",
+ "description": "Framefields hero animation explaining nostream for the website landing page",
+ "packageManager": "pnpm@10.33.0",
+ "scripts": {
+ "fonts": "tsx src/fetch-fonts.ts",
+ "assets": "tsx src/build-assets.ts",
+ "setup": "tsx src/fetch-fonts.ts && tsx src/build-assets.ts",
+ "preview": "tsx src/render.ts preview --open",
+ "render": "tsx src/render.ts video",
+ "render:small": "tsx src/render.ts video low",
+ "frames": "tsx src/render.ts frames 130 300 500 620 800 1010",
+ "typecheck": "tsc -p ."
+ },
+ "engines": {
+ "node": ">=22.20"
+ },
+ "dependencies": {
+ "framefields": "^2.0.8"
+ },
+ "devDependencies": {
+ "@types/node": "^24.1.0",
+ "sharp": "^0.35.5",
+ "tsx": "^4.20.3",
+ "typescript": "^5.8.3"
+ },
+ "pnpm": {
+ "onlyBuiltDependencies": [
+ "esbuild",
+ "node-av",
+ "sharp",
+ "skia-canvas",
+ "webgpu"
+ ]
+ }
+}
diff --git a/website/hero-animation/pnpm-lock.yaml b/website/hero-animation/pnpm-lock.yaml
new file mode 100644
index 000000000..15b070c38
--- /dev/null
+++ b/website/hero-animation/pnpm-lock.yaml
@@ -0,0 +1,2179 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ framefields:
+ specifier: ^2.0.8
+ version: 2.0.8(@types/node@24.19.1)
+ devDependencies:
+ '@types/node':
+ specifier: ^24.1.0
+ version: 24.19.1
+ sharp:
+ specifier: ^0.35.5
+ version: 0.35.5(@types/node@24.19.1)
+ tsx:
+ specifier: ^4.20.3
+ version: 4.23.15
+ typescript:
+ specifier: ^5.8.3
+ version: 5.9.3
+
+packages:
+
+ '@emnapi/runtime@1.11.3':
+ resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==}
+
+ '@esbuild/aix-ppc64@0.28.2':
+ resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.28.2':
+ resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.28.2':
+ resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.28.2':
+ resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.28.2':
+ resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.28.2':
+ resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.28.2':
+ resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.28.2':
+ resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.28.2':
+ resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.28.2':
+ resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.28.2':
+ resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.28.2':
+ resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.28.2':
+ resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.28.2':
+ resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.28.2':
+ resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.28.2':
+ resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.28.2':
+ resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-arm64@0.28.2':
+ resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.28.2':
+ resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-arm64@0.28.2':
+ resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.28.2':
+ resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openharmony-arm64@0.28.2':
+ resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@esbuild/sunos-x64@0.28.2':
+ resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.28.2':
+ resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.28.2':
+ resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.28.2':
+ resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@fidm/asn1@1.0.4':
+ resolution: {integrity: sha512-esd1jyNvRb2HVaQGq2Gg8Z0kbQPXzV9Tq5Z14KNIov6KfFD6PTaRIO8UpcsYiTNzOqJpmyzWgVTrUwFV3UF4TQ==}
+ engines: {node: '>= 8'}
+
+ '@fidm/x509@1.2.1':
+ resolution: {integrity: sha512-nwc2iesjyc9hkuzcrMCBXQRn653XuAUKorfWM8PZyJawiy1QzLj4vahwzaI25+pfpwOLvMzbJ0uKpWLDNmo16w==}
+ engines: {node: '>= 8'}
+
+ '@img/colour@1.1.0':
+ resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
+ engines: {node: '>=18'}
+
+ '@img/sharp-darwin-arm64@0.35.5':
+ resolution: {integrity: sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-darwin-x64@0.35.5':
+ resolution: {integrity: sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==}
+ engines: {node: '>=20.9.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-freebsd-wasm32@0.35.5':
+ resolution: {integrity: sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==}
+ engines: {node: '>=20.9.0'}
+ os: [freebsd]
+
+ '@img/sharp-libvips-darwin-arm64@1.3.4':
+ resolution: {integrity: sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-x64@1.3.4':
+ resolution: {integrity: sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-linux-arm64@1.3.4':
+ resolution: {integrity: sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-libvips-linux-arm@1.3.4':
+ resolution: {integrity: sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==}
+ cpu: [arm]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-libvips-linux-ppc64@1.3.4':
+ resolution: {integrity: sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-libvips-linux-riscv64@1.3.4':
+ resolution: {integrity: sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-libvips-linux-s390x@1.3.4':
+ resolution: {integrity: sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-libvips-linux-x64@1.3.4':
+ resolution: {integrity: sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.3.4':
+ resolution: {integrity: sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@img/sharp-libvips-linuxmusl-x64@1.3.4':
+ resolution: {integrity: sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@img/sharp-linux-arm64@0.35.5':
+ resolution: {integrity: sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-linux-arm@0.35.5':
+ resolution: {integrity: sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-linux-ppc64@0.35.5':
+ resolution: {integrity: sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==}
+ engines: {node: '>=20.9.0'}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-linux-riscv64@0.35.5':
+ resolution: {integrity: sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==}
+ engines: {node: '>=20.9.0'}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-linux-s390x@0.35.5':
+ resolution: {integrity: sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==}
+ engines: {node: '>=20.9.0'}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-linux-x64@0.35.5':
+ resolution: {integrity: sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==}
+ engines: {node: '>=20.9.0'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-linuxmusl-arm64@0.35.5':
+ resolution: {integrity: sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@img/sharp-linuxmusl-x64@0.35.5':
+ resolution: {integrity: sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==}
+ engines: {node: '>=20.9.0'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@img/sharp-wasm32@0.35.5':
+ resolution: {integrity: sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==}
+ engines: {node: '>=20.9.0'}
+
+ '@img/sharp-webcontainers-wasm32@0.35.5':
+ resolution: {integrity: sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==}
+ engines: {node: '>=20.9.0'}
+ cpu: [wasm32]
+
+ '@img/sharp-win32-arm64@0.35.5':
+ resolution: {integrity: sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@img/sharp-win32-ia32@0.35.5':
+ resolution: {integrity: sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==}
+ engines: {node: ^20.9.0}
+ cpu: [ia32]
+ os: [win32]
+
+ '@img/sharp-win32-x64@0.35.5':
+ resolution: {integrity: sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==}
+ engines: {node: '>=20.9.0'}
+ cpu: [x64]
+ os: [win32]
+
+ '@leichtgewicht/ip-codec@2.0.5':
+ resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==}
+
+ '@lottiefiles/dotlottie-web@0.74.0':
+ resolution: {integrity: sha512-rG12+dJSVhQDdleGr9epR7zU/UJ6hh8jzBDHAurClHP5t5dSrOh3eP3UCFxolpVPcobaRVYsBnYg4HQokQewpg==}
+ engines: {node: '>=18.17.0', npm: '>=9.5.0'}
+
+ '@mediabunny/mp3-encoder@1.61.3':
+ resolution: {integrity: sha512-Qmegig3hXp8dhbJnze1fumsC7GXkMp6NH5mGgvl5o4zzp2cYgmjjbWps5mPyRq3VHcxLbDdiXDNVcl4Mv7c1tQ==}
+ peerDependencies:
+ mediabunny: ^1.0.0
+
+ '@mediabunny/prores@1.61.3':
+ resolution: {integrity: sha512-ZZK+TGzHra6mEH+qFR8OwvfUpo4beI7+FotArwZKzDSsLLCew9Lgj2zVZgsv7vmBf8H7KGxTbF5mrMkWYnMJSg==}
+ peerDependencies:
+ mediabunny: ^1.49.0
+
+ '@mediabunny/server@1.61.3':
+ resolution: {integrity: sha512-KOYbrbjO+JDfmnflcHenjfL+S66X03HeceRra/oX4Twjhp1Rb8iZ7InFanuMsI8XxGU649e1UiG2QVzezpY1Yw==}
+ peerDependencies:
+ mediabunny: ^1.45.0
+
+ '@minhducsun2002/leb128@1.0.0':
+ resolution: {integrity: sha512-eFrYUPDVHeuwWHluTG1kwNQUEUcFjVKYwPkU8z9DR1JH3AW7JtJsG9cRVGmwz809kKtGfwGJj58juCZxEvnI/g==}
+
+ '@napi-rs/webcodecs-darwin-arm64@1.4.2':
+ resolution: {integrity: sha512-mdRVrGTnmREcegTAqjQt9U0l0Fn0RYoR3ydQMPzYEB9+KDLSskEf+oUxoK/S0rkxsK03YTkb/UlgYEsV6EpPug==}
+ engines: {node: ^22.20 || ^24.12 || >=25}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@napi-rs/webcodecs-darwin-x64@1.4.2':
+ resolution: {integrity: sha512-Ltdf1Q7T6l/iLF8OJTaIo9xqHjasqlkR6bi5CTfYB7oozKd0+I98ZSSBkyGSNRRaMoMOGySYFeTwUIOwFhaH/g==}
+ engines: {node: ^22.20 || ^24.12 || >=25}
+ cpu: [x64]
+ os: [darwin]
+
+ '@napi-rs/webcodecs-linux-arm-gnueabihf@1.4.2':
+ resolution: {integrity: sha512-QHgmYq5v1ErAk/g2NVngTtjrr+TuzAB+0ddHobKDzk4zEXWYAGN3MtP0fOm7lYoHLPm4vuJ1+hBcz8rdGLhwHg==}
+ engines: {node: ^22.20 || ^24.12 || >=25}
+ cpu: [arm]
+ os: [linux]
+
+ '@napi-rs/webcodecs-linux-arm64-gnu@1.4.2':
+ resolution: {integrity: sha512-lLMU7NsVCvM56XxaAN6QGMb4KGlCE9P+1oFEUjFLMT1Tmguk2xOVXWiYC1Bm+vfnGLE3juzVocaO+6+WU04I8g==}
+ engines: {node: ^22.20 || ^24.12 || >=25}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@napi-rs/webcodecs-linux-arm64-musl@1.4.2':
+ resolution: {integrity: sha512-EawvUnAX8WMv31PWPx6u+WglLbHCmqQWCRlMrjbmZJl7HekuYFePwM5WkSvhYyVrGuc3hXUXqhu4Zfs7Lh850g==}
+ engines: {node: ^22.20 || ^24.12 || >=25}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@napi-rs/webcodecs-linux-x64-gnu@1.4.2':
+ resolution: {integrity: sha512-008pKr+CtfVPQfpPrvZDhd3KtcqEktM31o3EI4z5F14avFP/yohPILXm0mvzw4lFxq6QTsyfKtjnAA2kzFF1mw==}
+ engines: {node: ^22.20 || ^24.12 || >=25}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@napi-rs/webcodecs-linux-x64-musl@1.4.2':
+ resolution: {integrity: sha512-AABKEHbPeoWCFuwacbpVBv4g4GESCcm7J357DbMkK2XmzHf3N+lJjbLH6hEYBcEz46dXZX8yr9jRUTbSa+NoCA==}
+ engines: {node: ^22.20 || ^24.12 || >=25}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@napi-rs/webcodecs-win32-arm64-msvc@1.4.2':
+ resolution: {integrity: sha512-NPwZn6rGY8tDm51rn3f7rAYeuBr19xsXObhz3mEEXKS4iJw7DdD/sC9/pvShIRcjMsNWGRTHzuRauMnQINBFuA==}
+ engines: {node: ^22.20 || ^24.12 || >=25}
+ cpu: [arm64]
+ os: [win32]
+
+ '@napi-rs/webcodecs-win32-x64-msvc@1.4.2':
+ resolution: {integrity: sha512-wwT8TZ0zDKefaBYCsGbOk1CTZAXMxC7k8xaY97e7+S/sD7rjvxXfK4DPjynSHSZwl/rowrGqmj/ib9dF/4K+BA==}
+ engines: {node: ^22.20 || ^24.12 || >=25}
+ cpu: [x64]
+ os: [win32]
+
+ '@napi-rs/webcodecs@1.4.2':
+ resolution: {integrity: sha512-Dwz1nYx1ZZbzv9rhSuYcELJD7T+wS4vnoDBkSAVsKiCEYQ71lfJ2yv6gWSDoNnjjJd2Dp0P8CvzjFZBdZ4/M0Q==}
+ engines: {node: ^22.20 || ^24.12 || >=25}
+ peerDependencies:
+ '@napi-rs/canvas': '>=0.1.0'
+ peerDependenciesMeta:
+ '@napi-rs/canvas':
+ optional: true
+
+ '@noble/curves@1.9.7':
+ resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==}
+ engines: {node: ^14.21.3 || >=16}
+
+ '@noble/hashes@1.8.0':
+ resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==}
+ engines: {node: ^14.21.3 || >=16}
+
+ '@peculiar/asn1-cms@2.10.0':
+ resolution: {integrity: sha512-CkX0H4NCIOMHOU3rh2xXZywinYZ/EnJlaOlJiGI0e5L4XjFqHf4iH30LMbXWChVppdA9ljbanjtzQFOhDNfTWg==}
+ engines: {node: '>=14'}
+
+ '@peculiar/asn1-csr@2.10.0':
+ resolution: {integrity: sha512-jTPTr/9rxKM+niQLMF3jiAMb0lWHUhUHIuSOhdGCIfpB4FAQYf9SoiXOgxP8bS/68IB+bj+1OHvVnDVCQfVUZw==}
+ engines: {node: '>=14'}
+
+ '@peculiar/asn1-ecc@2.10.0':
+ resolution: {integrity: sha512-GFd3iOjFrWX+QWH2R2dO5QSJyyRGv9CIBKtRlGlPNCvb2RvmCbBDexe91MJgV76c69d998Xsa+CvuQ98seoiPg==}
+ engines: {node: '>=14'}
+
+ '@peculiar/asn1-pfx@2.10.0':
+ resolution: {integrity: sha512-1y3QK9ZH1IPleAMmRoJlPS10eGkAWnULETW8zDFNUK1ffqpG7zmDY+kYBMYQgwlcQZ2iitLm2z2EBP0xzRXpaA==}
+ engines: {node: '>=14'}
+
+ '@peculiar/asn1-pkcs8@2.10.0':
+ resolution: {integrity: sha512-Ri+BZT9bnwqlHWmhs7lvWjvJRxKev2hA2+4EbZajgeYWCbR8hyv0znF4DhsFouOhMj2nSDV/iGJ6DMQtwITnCw==}
+ engines: {node: '>=14'}
+
+ '@peculiar/asn1-pkcs9@2.10.0':
+ resolution: {integrity: sha512-XIXsbDQFezYk6fudczkuvawkRD4GNpISGCqfYhdkJlvcGF/RFknU+0DDJagOaJqBf+PdPzk4J9oMqDGcvfR9HQ==}
+ engines: {node: '>=14'}
+
+ '@peculiar/asn1-rsa@2.10.0':
+ resolution: {integrity: sha512-4Jvmwlh3gZAhNZ4/u7JSyLuce9hofOFZ4o3PYKAccCImGnyaT5CMym/ATgvAvqpOYFNW531oPZvJlEBhJy9VfA==}
+ engines: {node: '>=14'}
+
+ '@peculiar/asn1-schema@2.10.0':
+ resolution: {integrity: sha512-GhokD41lV4gQrrLYm3wCkHfBOnJrnhDMgt4XeMW8gzfE1UdJqIuSwsE+ggf82XBjUXRJylcm+KIGQFa4utIVLw==}
+ engines: {node: '>=14'}
+
+ '@peculiar/asn1-x509-attr@2.10.0':
+ resolution: {integrity: sha512-/85GtKOKmgvuSJNlaFfwGWNdRSZZ+hpF02NyM01XiCdzpaZONiBltDyfluFPvFx966CR+ZHNSG1jniwpy07oGg==}
+ engines: {node: '>=14'}
+
+ '@peculiar/asn1-x509@2.10.0':
+ resolution: {integrity: sha512-ucNVg8+ANveTpMN3fy9lA2alryONdXc2A4cEG2hMniWbvQt+YOZoe8BI80YYNO8FBcuDY1qbDQ4uQGQVraHxGA==}
+ engines: {node: '>=14'}
+
+ '@peculiar/utils@2.0.3':
+ resolution: {integrity: sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==}
+
+ '@peculiar/x509@1.14.3':
+ resolution: {integrity: sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==}
+ engines: {node: '>=20.0.0'}
+
+ '@pinojs/redact@0.4.0':
+ resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==}
+
+ '@resvg/resvg-js-android-arm-eabi@2.6.2':
+ resolution: {integrity: sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [android]
+
+ '@resvg/resvg-js-android-arm64@2.6.2':
+ resolution: {integrity: sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [android]
+
+ '@resvg/resvg-js-darwin-arm64@2.6.2':
+ resolution: {integrity: sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@resvg/resvg-js-darwin-x64@2.6.2':
+ resolution: {integrity: sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@resvg/resvg-js-linux-arm-gnueabihf@2.6.2':
+ resolution: {integrity: sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@resvg/resvg-js-linux-arm64-gnu@2.6.2':
+ resolution: {integrity: sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@resvg/resvg-js-linux-arm64-musl@2.6.2':
+ resolution: {integrity: sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@resvg/resvg-js-linux-x64-gnu@2.6.2':
+ resolution: {integrity: sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@resvg/resvg-js-linux-x64-musl@2.6.2':
+ resolution: {integrity: sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@resvg/resvg-js-win32-arm64-msvc@2.6.2':
+ resolution: {integrity: sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@resvg/resvg-js-win32-ia32-msvc@2.6.2':
+ resolution: {integrity: sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w==}
+ engines: {node: '>= 10'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@resvg/resvg-js-win32-x64-msvc@2.6.2':
+ resolution: {integrity: sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@resvg/resvg-js@2.6.2':
+ resolution: {integrity: sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==}
+ engines: {node: '>= 10'}
+
+ '@rollup/rollup-linux-x64-gnu@4.62.1':
+ resolution: {integrity: sha512-nVQGk/jStQc2V4rrkI+vPD2J+85boKqS4R4nOdPhc3eWw0kyW/b+AYRGoH8qo057XSVqaTx13AliH5qPeLTtgQ==}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@seydx/node-av-darwin-arm64@6.1.1':
+ resolution: {integrity: sha512-hJ07HDFwz96MI/EY1V89/jVWXMfn/xb/32vocNhAeqnvBRMZio+hDz8lQloGynqv8iow5y9mPb7fOuzt5cW1+w==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@seydx/node-av-darwin-x64@6.1.1':
+ resolution: {integrity: sha512-7sfIEA/KkEMPMbfLlU9pGy34vE5iOpWryBFK87jDXeqLo4hB5p5qx2IDKcKfD6WBzEMk5FJScZz4rlT3J546Og==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [darwin]
+
+ '@seydx/node-av-linux-arm64@6.1.1':
+ resolution: {integrity: sha512-E5LopQ+LaTGdjmnk3KYsrKF1qRQHT22xVGijUdAcvbScfLTJz2RuJoevKT53cuNupR1gdmRmvnmq+kac4TkDRA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@seydx/node-av-linux-x64@6.1.1':
+ resolution: {integrity: sha512-2HkBkMUSmA+1sP0PN7eFSkNJhbejgJDCXx0w0ZMRlTLqF+snz5ptf/gl/tQye9yER6I4OGW/7WVL5Y2PVneNCA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@seydx/node-av-win32-arm64-mingw@6.1.1':
+ resolution: {integrity: sha512-inhihhVGetTgpYaUQHfeTbG/PdBzYHqBRz79U/83C/64FHw7+sKQF125B7Hfle7tnTX3w7LoJQ+RxsoAP2RWgw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [win32]
+
+ '@seydx/node-av-win32-arm64-msvc@6.1.1':
+ resolution: {integrity: sha512-gESbRZhCkldciGV+e08PhgJ3Do2PnL1ZY8ZIIDTjutCZdw+lgZigvZGq8jQ+TnsV6Djf1pdcKx3Z2VGnIGId5Q==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [win32]
+
+ '@seydx/node-av-win32-x64-mingw@6.1.1':
+ resolution: {integrity: sha512-DqdNMsGTMcmg/y+DwHu+slFdP5VluCFNrefhUoq7mobu2kKk0i3LBN9xz2NdeG15jKB+DCeqFmt0r5iMGymOKA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [win32]
+
+ '@seydx/node-av-win32-x64-msvc@6.1.1':
+ resolution: {integrity: sha512-GLjjoOYTqh1rvTzawgBMVKgY+8OIXMyrgD1eIqKLy3L6rWJhGvsbY+kUy15XbatGTFwStTlHK9ovTi2Xj/+9lQ==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [win32]
+
+ '@shinyoshiaki/binary-data@0.6.1':
+ resolution: {integrity: sha512-7HDb/fQAop2bCmvDIzU5+69i+UJaFgIVp99h1VzK1mpg1JwSODOkjbqD7ilTYnqlnadF8C4XjpwpepxDsGY6+w==}
+ engines: {node: '>=6'}
+
+ '@shinyoshiaki/jspack@0.0.6':
+ resolution: {integrity: sha512-SdsNhLjQh4onBlyPrn4ia1Pdx5bXT88G/LIEpOYAjx2u4xeY/m/HB5yHqlkJB1uQR3Zw4R3hBWLj46STRAN0rg==}
+
+ '@swc/helpers@0.5.23':
+ resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==}
+
+ '@types/dom-mediacapture-transform@0.1.12':
+ resolution: {integrity: sha512-d7/QsLRwF864A5mgIM/YrfiglHoYn7zgCcAoJgW404r+2DwnNr7EBbLnCWpmOMgH8y0te73L1AV6H1bmauaWFw==}
+
+ '@types/dom-webcodecs@0.1.13':
+ resolution: {integrity: sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ==}
+
+ '@types/node@24.19.1':
+ resolution: {integrity: sha512-aS3/DG0oM05K0RIXXP+hKjinGG5IgSSVGzswZxW3O0sS3pH4/fycXundUC9XsszgKCk4gHXylTEK6hyFxVxnoQ==}
+
+ '@webgpu/types@0.1.74':
+ resolution: {integrity: sha512-lgiI4hbuLcI9unnm2cL/tvCaQU45dp0xcLWJh5uB/9MBGvIA4F8XIk7nSiBeg+K4xWGYwgZKn80LmERI5CxoTA==}
+
+ adm-zip@0.6.1:
+ resolution: {integrity: sha512-Xwrja8nx9e5o2N1my4DsKCeKpdrnACyr1wtbPxBDgGzKzKyE9kRtBFA8mWldI+RVlD7CBZNWY/wQ2+ydwOR6kQ==}
+ engines: {node: '>=14.0'}
+
+ aes-js@3.1.2:
+ resolution: {integrity: sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==}
+
+ agent-base@7.1.4:
+ resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
+ engines: {node: '>= 14'}
+
+ asn1js@3.0.10:
+ resolution: {integrity: sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==}
+ engines: {node: '>=12.0.0'}
+
+ atomic-sleep@1.0.0:
+ resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
+ engines: {node: '>=8.0.0'}
+
+ base64-js@1.5.1:
+ resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
+
+ bluebird@3.7.2:
+ resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==}
+
+ brotli@1.3.3:
+ resolution: {integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==}
+
+ buffer@6.0.3:
+ resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
+
+ clone@2.1.2:
+ resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==}
+ engines: {node: '>=0.8'}
+
+ core-util-is@1.0.3:
+ resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
+
+ d3-array@3.2.4:
+ resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
+ engines: {node: '>=12'}
+
+ d3-color@3.1.0:
+ resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
+ engines: {node: '>=12'}
+
+ d3-format@3.1.2:
+ resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==}
+ engines: {node: '>=12'}
+
+ d3-interpolate@3.0.1:
+ resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
+ engines: {node: '>=12'}
+
+ d3-path@3.1.0:
+ resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
+ engines: {node: '>=12'}
+
+ d3-scale@4.0.2:
+ resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
+ engines: {node: '>=12'}
+
+ d3-shape@3.2.0:
+ resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
+ engines: {node: '>=12'}
+
+ d3-time-format@4.1.0:
+ resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
+ engines: {node: '>=12'}
+
+ d3-time@3.1.0:
+ resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
+ engines: {node: '>=12'}
+
+ debug@4.4.0:
+ resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ define-data-property@1.1.4:
+ resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
+ engines: {node: '>= 0.4'}
+
+ define-properties@1.2.1:
+ resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
+ engines: {node: '>= 0.4'}
+
+ detect-libc@2.1.2:
+ resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+ engines: {node: '>=8'}
+
+ dfa@1.2.0:
+ resolution: {integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==}
+
+ dns-packet@5.6.1:
+ resolution: {integrity: sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==}
+ engines: {node: '>=6'}
+
+ dotenv@16.6.1:
+ resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
+ engines: {node: '>=12'}
+
+ duplexer2@0.1.4:
+ resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==}
+
+ earcut@3.2.4:
+ resolution: {integrity: sha512-FHc6yNpnTORB0tM5kmZ03iD2mDuNakDE2lvf4Hw1XkfxtEHIGVi01B/GqUSwx+NcC1WGjidAfpTXzeALtPtfpg==}
+
+ es-define-property@1.0.1:
+ resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
+ engines: {node: '>= 0.4'}
+
+ es-errors@1.3.0:
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ engines: {node: '>= 0.4'}
+
+ esbuild@0.28.2:
+ resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ escape-string-regexp@4.0.0:
+ resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+ engines: {node: '>=10'}
+
+ fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+ follow-redirects@1.16.1:
+ resolution: {integrity: sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg==}
+ engines: {node: '>=4.0'}
+ peerDependencies:
+ debug: '*'
+ peerDependenciesMeta:
+ debug:
+ optional: true
+
+ fontkit@2.0.4:
+ resolution: {integrity: sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==}
+
+ framefields@2.0.8:
+ resolution: {integrity: sha512-BfBXBDXWc6n6uKymi3Ygc4SGVTkrYtmSQEUKP25iUShfook/uy1Jvxw45LA+CuCrFJIKJ+k8d6DJqtTW8cLcJA==}
+
+ fs-extra@11.3.1:
+ resolution: {integrity: sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==}
+ engines: {node: '>=14.14'}
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ generate-function@2.3.1:
+ resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==}
+
+ gifuct-js@2.1.2:
+ resolution: {integrity: sha512-rI2asw77u0mGgwhV3qA+OEgYqaDn5UNqgs+Bx0FGwSpuqfYn+Ir6RQY5ENNQ8SbIiG/m5gVa7CD5RriO4f4Lsg==}
+
+ global-agent@4.1.3:
+ resolution: {integrity: sha512-KUJEViiuFT3I97t+GYMikLPJS2Lfo/S2F+DQuBWzuzaMPnvt5yyZePzArx36fBzpGTxZjIpDbXLeySLgh+k76g==}
+ engines: {node: '>=10.0'}
+
+ globalthis@1.0.4:
+ resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
+ engines: {node: '>= 0.4'}
+
+ gopd@1.2.0:
+ resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
+ engines: {node: '>= 0.4'}
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ gsap@3.15.0:
+ resolution: {integrity: sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==}
+
+ has-property-descriptors@1.0.2:
+ resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
+
+ https-proxy-agent@7.0.6:
+ resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
+ engines: {node: '>= 14'}
+
+ ieee754@1.2.1:
+ resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
+
+ inherits@2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
+ int64-buffer@1.1.0:
+ resolution: {integrity: sha512-94smTCQOvigN4d/2R/YDjz8YVG0Sufvv2aAh8P5m42gwhCsDAJqnbNOrxJsrADuAFAA69Q/ptGzxvNcNuIJcvw==}
+
+ internmap@2.0.3:
+ resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
+ engines: {node: '>=12'}
+
+ ip@2.0.1:
+ resolution: {integrity: sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==}
+
+ is-plain-object@2.0.4:
+ resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==}
+ engines: {node: '>=0.10.0'}
+
+ is-property@1.0.2:
+ resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==}
+
+ isarray@1.0.0:
+ resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
+
+ isobject@3.0.1:
+ resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==}
+ engines: {node: '>=0.10.0'}
+
+ js-binary-schema-parser@2.0.3:
+ resolution: {integrity: sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg==}
+
+ jsonfile@6.2.1:
+ resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==}
+
+ matcher@4.0.0:
+ resolution: {integrity: sha512-S6x5wmcDmsDRRU/c2dkccDwQPXoFczc5+HpQ2lON8pnvHlnvHAHj5WlLVvw6n6vNyHuVugYrFohYxbS+pvFpKQ==}
+ engines: {node: '>=10'}
+
+ mediabunny@1.61.3:
+ resolution: {integrity: sha512-kUqLKVXQVwHR62GkOJxYosAmXERJbqnXBgJDTtRlxHEYviVkMg/Evhl9XN6kedqS5LP1tV4kFKyzY/V4a4ofow==}
+
+ mp4box@0.5.4:
+ resolution: {integrity: sha512-GcCH0fySxBurJtvr0dfhz0IxHZjc1RP+F+I8xw+LIwkU1a+7HJx8NCDiww1I5u4Hz6g4eR1JlGADEGJ9r4lSfA==}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ multicast-dns@7.2.5:
+ resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==}
+ hasBin: true
+
+ nanoid@5.1.16:
+ resolution: {integrity: sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==}
+ engines: {node: ^18 || >=20}
+ hasBin: true
+
+ node-av@6.1.1:
+ resolution: {integrity: sha512-nuj2cpAelVZQ8hWJB26i9wYIPI/l+GzIAw8zbZuepFpq52khjYQ8s5S+Qi2cG1D+zjb7TYx7QqKpiZH32tqo2A==}
+
+ node-int64@0.4.0:
+ resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
+
+ object-keys@1.1.1:
+ resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
+ engines: {node: '>= 0.4'}
+
+ on-exit-leak-free@2.1.2:
+ resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
+ engines: {node: '>=14.0.0'}
+
+ onnxruntime-common@1.30.0:
+ resolution: {integrity: sha512-7fdVWjAID1dVhH/G8qK3APARunV4VkBFoCQAP7qp4Wkab0mrorvmc+sqiT+mKXOzDqdjN5j+/Z9nb4gzNPWcyA==}
+
+ onnxruntime-node@1.30.0:
+ resolution: {integrity: sha512-twhs1C2C/BFkz1yc5OY0KIU2GUq6DURO7hD4bx5Q2Qy3nAMJwRXW8xU3NVczE29VA9lolLOYepoD8fjTGOfIqw==}
+ os: [win32, darwin, linux]
+
+ pako@0.2.9:
+ resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==}
+
+ parenthesis@3.1.8:
+ resolution: {integrity: sha512-KF/U8tk54BgQewkJPvB4s/US3VQY68BRDpH638+7O/n58TpnwiwnOtGIOsT2/i+M78s61BBpeC83STB88d8sqw==}
+
+ pino-abstract-transport@3.0.0:
+ resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==}
+
+ pino-std-serializers@7.1.0:
+ resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==}
+
+ pino@10.4.0:
+ resolution: {integrity: sha512-bk1ZMTwG/Vymx+zPoa28MhNzucFvXJWp3csrSuxuHPSMeUettaCoQ5Jvg07mtC4JGNpcfTwz41oVCnNgBK9+vA==}
+ hasBin: true
+
+ process-nextick-args@2.0.1:
+ resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
+
+ process-warning@5.1.0:
+ resolution: {integrity: sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==}
+
+ pvtsutils@1.3.6:
+ resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==}
+
+ pvutils@1.2.0:
+ resolution: {integrity: sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==}
+ engines: {node: '>=16.0.0'}
+
+ quick-format-unescaped@4.0.4:
+ resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
+
+ readable-stream@2.3.8:
+ resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
+
+ real-require@1.0.0:
+ resolution: {integrity: sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==}
+
+ reflect-metadata@0.2.2:
+ resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
+
+ restructure@3.0.2:
+ resolution: {integrity: sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==}
+
+ safe-buffer@5.1.2:
+ resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
+
+ safe-stable-stringify@2.5.0:
+ resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
+ engines: {node: '>=10'}
+
+ semver@7.8.5:
+ resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ serialize-error@8.1.0:
+ resolution: {integrity: sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==}
+ engines: {node: '>=10'}
+
+ sharp@0.35.5:
+ resolution: {integrity: sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==}
+ engines: {node: '>=20.9.0'}
+ peerDependencies:
+ '@types/node': '*'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ skia-canvas@3.0.8:
+ resolution: {integrity: sha512-FSYKxp8Ng2vOeeOBiyPhnn6ui6FirPJXMyjk4PKl8N/OWzVrkMawUgY9zubIWHMdYtyWFn0gfX3QlRwg6HBmdg==}
+
+ sonic-boom@4.2.1:
+ resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==}
+
+ split2@4.2.0:
+ resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
+ engines: {node: '>= 10.x'}
+
+ string-split-by@1.0.0:
+ resolution: {integrity: sha512-KaJKY+hfpzNyet/emP81PJA9hTVSfxNLS9SFTWxdCnnW1/zOOwiV248+EfoX7IQFcBaOp4G5YE6xTJMF+pLg6A==}
+
+ string_decoder@1.1.1:
+ resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
+
+ thread-stream@4.2.0:
+ resolution: {integrity: sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==}
+ engines: {node: '>=20'}
+
+ thunky@1.1.0:
+ resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==}
+
+ tiny-inflate@1.0.3:
+ resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
+
+ tslib@1.14.1:
+ resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ tsx@4.23.15:
+ resolution: {integrity: sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==}
+ engines: {node: '>=18.0.0'}
+ hasBin: true
+
+ tsyringe@4.10.0:
+ resolution: {integrity: sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==}
+ engines: {node: '>= 6.0.0'}
+
+ turbores@1.2.2:
+ resolution: {integrity: sha512-dTSKeZ6/BgBKz9T5goMS5ZMcqOiNYO1MbvresYGfWXEF5ZWK+yIZDxNjh86bRj1mnEzhtdsMOJ5GBi8b3Azcpg==}
+
+ tweetnacl@1.0.3:
+ resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==}
+
+ type-fest@0.20.2:
+ resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
+ engines: {node: '>=10'}
+
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ undici-types@7.24.6:
+ resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==}
+
+ unicode-properties@1.4.1:
+ resolution: {integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==}
+
+ unicode-trie@2.0.0:
+ resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==}
+
+ universalify@2.0.1:
+ resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
+ engines: {node: '>= 10.0.0'}
+
+ unzipper@0.12.5:
+ resolution: {integrity: sha512-tXYOi9R57Uj/2Z25SOs5RRSzq886MBQj2gY8dPL+xl/kv6s6SvByoKfAtvfVeEuhntWDgjd2o9p2lb4TVPAz0A==}
+
+ util-deprecate@1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
+ webgpu@0.4.0:
+ resolution: {integrity: sha512-F5pimn3Aoi0zWjuRdiVs5TnrUwSzD2lESBohsIUsqyitWkGRQlXU2fhV6ycXlQTa1bvAf3sjqiUpBEpmSQ5ptA==}
+
+ werift-common@0.0.3:
+ resolution: {integrity: sha512-ma3E4BqKTyZVLhrdfTVs2T1tg9seeUtKMRn5e64LwgrogWa62+3LAUoLBUSl1yPWhgSkXId7GmcHuWDen9IJeQ==}
+ engines: {node: '>=16'}
+
+ werift-dtls@0.5.8:
+ resolution: {integrity: sha512-oH39cdCpVLqKcPZRMGQLcN+tIBixvMGqjCB9qCrH39Tu9Mmu8u2+zTzyl0Fod87EK6PNPgoNqMJt64xlSMfLnw==}
+ engines: {node: '>=16'}
+
+ werift-ice@0.2.3:
+ resolution: {integrity: sha512-k000fIaw8w2D4Xv5EvW6b5h4faJx8p4D8HPuDzRiGTx6UrLv2BuvUkC60LNqxlDf9o3zh90Hx1WZoHbhuw9keA==}
+
+ werift-rtp@0.8.9:
+ resolution: {integrity: sha512-uLFOawIXw8FblIp1akfIVYFuRNtNo7csRxYypLz0t++sUNCN7sPeghVqET/c3Mq7Tg2QA3oAb15fXIIuJEp2AA==}
+ engines: {node: '>=10'}
+
+ werift-sctp@0.0.11:
+ resolution: {integrity: sha512-7109yuI5U7NTEHjqjn0A8VeynytkgVaxM6lRr1Ziv0D8bPcaB8A7U/P88M7WaCpWDoELHoXiRUjQycMWStIgjQ==}
+ engines: {node: '>=10'}
+
+ werift@0.23.0:
+ resolution: {integrity: sha512-/WcIN5DHFG9Ri4anGOmIkp8gxBGFMWSIB/m4sfZ5CWlLfD3iMhiaAUuTBuc+KV3SY9NDmvmLtiN2uaM7k3lVzw==}
+ engines: {node: '>=16'}
+
+ yoga-layout@3.2.1:
+ resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==}
+
+ zod@4.6.5:
+ resolution: {integrity: sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==}
+
+snapshots:
+
+ '@emnapi/runtime@1.11.3':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@esbuild/aix-ppc64@0.28.2':
+ optional: true
+
+ '@esbuild/android-arm64@0.28.2':
+ optional: true
+
+ '@esbuild/android-arm@0.28.2':
+ optional: true
+
+ '@esbuild/android-x64@0.28.2':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.28.2':
+ optional: true
+
+ '@esbuild/darwin-x64@0.28.2':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.28.2':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.28.2':
+ optional: true
+
+ '@esbuild/linux-arm64@0.28.2':
+ optional: true
+
+ '@esbuild/linux-arm@0.28.2':
+ optional: true
+
+ '@esbuild/linux-ia32@0.28.2':
+ optional: true
+
+ '@esbuild/linux-loong64@0.28.2':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.28.2':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.28.2':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.28.2':
+ optional: true
+
+ '@esbuild/linux-s390x@0.28.2':
+ optional: true
+
+ '@esbuild/linux-x64@0.28.2':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.28.2':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.28.2':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.28.2':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.28.2':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.28.2':
+ optional: true
+
+ '@esbuild/sunos-x64@0.28.2':
+ optional: true
+
+ '@esbuild/win32-arm64@0.28.2':
+ optional: true
+
+ '@esbuild/win32-ia32@0.28.2':
+ optional: true
+
+ '@esbuild/win32-x64@0.28.2':
+ optional: true
+
+ '@fidm/asn1@1.0.4':
+ optional: true
+
+ '@fidm/x509@1.2.1':
+ dependencies:
+ '@fidm/asn1': 1.0.4
+ tweetnacl: 1.0.3
+ optional: true
+
+ '@img/colour@1.1.0': {}
+
+ '@img/sharp-darwin-arm64@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-arm64': 1.3.4
+ optional: true
+
+ '@img/sharp-darwin-x64@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-x64': 1.3.4
+ optional: true
+
+ '@img/sharp-freebsd-wasm32@0.35.5':
+ dependencies:
+ '@img/sharp-wasm32': 0.35.5
+ optional: true
+
+ '@img/sharp-libvips-darwin-arm64@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-darwin-x64@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm64@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-ppc64@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-riscv64@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-s390x@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-x64@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-x64@1.3.4':
+ optional: true
+
+ '@img/sharp-linux-arm64@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm64': 1.3.4
+ optional: true
+
+ '@img/sharp-linux-arm@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm': 1.3.4
+ optional: true
+
+ '@img/sharp-linux-ppc64@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-ppc64': 1.3.4
+ optional: true
+
+ '@img/sharp-linux-riscv64@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-riscv64': 1.3.4
+ optional: true
+
+ '@img/sharp-linux-s390x@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-s390x': 1.3.4
+ optional: true
+
+ '@img/sharp-linux-x64@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-x64': 1.3.4
+ optional: true
+
+ '@img/sharp-linuxmusl-arm64@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-arm64': 1.3.4
+ optional: true
+
+ '@img/sharp-linuxmusl-x64@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-x64': 1.3.4
+ optional: true
+
+ '@img/sharp-wasm32@0.35.5':
+ dependencies:
+ '@emnapi/runtime': 1.11.3
+ optional: true
+
+ '@img/sharp-webcontainers-wasm32@0.35.5':
+ dependencies:
+ '@img/sharp-wasm32': 0.35.5
+ optional: true
+
+ '@img/sharp-win32-arm64@0.35.5':
+ optional: true
+
+ '@img/sharp-win32-ia32@0.35.5':
+ optional: true
+
+ '@img/sharp-win32-x64@0.35.5':
+ optional: true
+
+ '@leichtgewicht/ip-codec@2.0.5':
+ optional: true
+
+ '@lottiefiles/dotlottie-web@0.74.0': {}
+
+ '@mediabunny/mp3-encoder@1.61.3(mediabunny@1.61.3)':
+ dependencies:
+ mediabunny: 1.61.3
+
+ '@mediabunny/prores@1.61.3(mediabunny@1.61.3)':
+ dependencies:
+ mediabunny: 1.61.3
+ turbores: 1.2.2
+
+ '@mediabunny/server@1.61.3(mediabunny@1.61.3)':
+ dependencies:
+ '@mediabunny/prores': 1.61.3(mediabunny@1.61.3)
+ mediabunny: 1.61.3
+ node-av: 6.1.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@minhducsun2002/leb128@1.0.0':
+ optional: true
+
+ '@napi-rs/webcodecs-darwin-arm64@1.4.2':
+ optional: true
+
+ '@napi-rs/webcodecs-darwin-x64@1.4.2':
+ optional: true
+
+ '@napi-rs/webcodecs-linux-arm-gnueabihf@1.4.2':
+ optional: true
+
+ '@napi-rs/webcodecs-linux-arm64-gnu@1.4.2':
+ optional: true
+
+ '@napi-rs/webcodecs-linux-arm64-musl@1.4.2':
+ optional: true
+
+ '@napi-rs/webcodecs-linux-x64-gnu@1.4.2':
+ optional: true
+
+ '@napi-rs/webcodecs-linux-x64-musl@1.4.2':
+ optional: true
+
+ '@napi-rs/webcodecs-win32-arm64-msvc@1.4.2':
+ optional: true
+
+ '@napi-rs/webcodecs-win32-x64-msvc@1.4.2':
+ optional: true
+
+ '@napi-rs/webcodecs@1.4.2':
+ optionalDependencies:
+ '@napi-rs/webcodecs-darwin-arm64': 1.4.2
+ '@napi-rs/webcodecs-darwin-x64': 1.4.2
+ '@napi-rs/webcodecs-linux-arm-gnueabihf': 1.4.2
+ '@napi-rs/webcodecs-linux-arm64-gnu': 1.4.2
+ '@napi-rs/webcodecs-linux-arm64-musl': 1.4.2
+ '@napi-rs/webcodecs-linux-x64-gnu': 1.4.2
+ '@napi-rs/webcodecs-linux-x64-musl': 1.4.2
+ '@napi-rs/webcodecs-win32-arm64-msvc': 1.4.2
+ '@napi-rs/webcodecs-win32-x64-msvc': 1.4.2
+
+ '@noble/curves@1.9.7':
+ dependencies:
+ '@noble/hashes': 1.8.0
+ optional: true
+
+ '@noble/hashes@1.8.0':
+ optional: true
+
+ '@peculiar/asn1-cms@2.10.0':
+ dependencies:
+ '@peculiar/asn1-schema': 2.10.0
+ '@peculiar/asn1-x509': 2.10.0
+ '@peculiar/asn1-x509-attr': 2.10.0
+ asn1js: 3.0.10
+ tslib: 2.8.1
+ optional: true
+
+ '@peculiar/asn1-csr@2.10.0':
+ dependencies:
+ '@peculiar/asn1-schema': 2.10.0
+ '@peculiar/asn1-x509': 2.10.0
+ asn1js: 3.0.10
+ tslib: 2.8.1
+ optional: true
+
+ '@peculiar/asn1-ecc@2.10.0':
+ dependencies:
+ '@peculiar/asn1-schema': 2.10.0
+ '@peculiar/asn1-x509': 2.10.0
+ asn1js: 3.0.10
+ tslib: 2.8.1
+ optional: true
+
+ '@peculiar/asn1-pfx@2.10.0':
+ dependencies:
+ '@peculiar/asn1-cms': 2.10.0
+ '@peculiar/asn1-pkcs8': 2.10.0
+ '@peculiar/asn1-rsa': 2.10.0
+ '@peculiar/asn1-schema': 2.10.0
+ asn1js: 3.0.10
+ tslib: 2.8.1
+ optional: true
+
+ '@peculiar/asn1-pkcs8@2.10.0':
+ dependencies:
+ '@peculiar/asn1-schema': 2.10.0
+ '@peculiar/asn1-x509': 2.10.0
+ asn1js: 3.0.10
+ tslib: 2.8.1
+ optional: true
+
+ '@peculiar/asn1-pkcs9@2.10.0':
+ dependencies:
+ '@peculiar/asn1-cms': 2.10.0
+ '@peculiar/asn1-pfx': 2.10.0
+ '@peculiar/asn1-pkcs8': 2.10.0
+ '@peculiar/asn1-schema': 2.10.0
+ '@peculiar/asn1-x509': 2.10.0
+ '@peculiar/asn1-x509-attr': 2.10.0
+ asn1js: 3.0.10
+ tslib: 2.8.1
+ optional: true
+
+ '@peculiar/asn1-rsa@2.10.0':
+ dependencies:
+ '@peculiar/asn1-schema': 2.10.0
+ '@peculiar/asn1-x509': 2.10.0
+ asn1js: 3.0.10
+ tslib: 2.8.1
+ optional: true
+
+ '@peculiar/asn1-schema@2.10.0':
+ dependencies:
+ '@peculiar/utils': 2.0.3
+ asn1js: 3.0.10
+ tslib: 2.8.1
+ optional: true
+
+ '@peculiar/asn1-x509-attr@2.10.0':
+ dependencies:
+ '@peculiar/asn1-schema': 2.10.0
+ '@peculiar/asn1-x509': 2.10.0
+ asn1js: 3.0.10
+ tslib: 2.8.1
+ optional: true
+
+ '@peculiar/asn1-x509@2.10.0':
+ dependencies:
+ '@peculiar/asn1-schema': 2.10.0
+ '@peculiar/utils': 2.0.3
+ asn1js: 3.0.10
+ tslib: 2.8.1
+ optional: true
+
+ '@peculiar/utils@2.0.3':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@peculiar/x509@1.14.3':
+ dependencies:
+ '@peculiar/asn1-cms': 2.10.0
+ '@peculiar/asn1-csr': 2.10.0
+ '@peculiar/asn1-ecc': 2.10.0
+ '@peculiar/asn1-pkcs9': 2.10.0
+ '@peculiar/asn1-rsa': 2.10.0
+ '@peculiar/asn1-schema': 2.10.0
+ '@peculiar/asn1-x509': 2.10.0
+ pvtsutils: 1.3.6
+ reflect-metadata: 0.2.2
+ tslib: 2.8.1
+ tsyringe: 4.10.0
+ optional: true
+
+ '@pinojs/redact@0.4.0': {}
+
+ '@resvg/resvg-js-android-arm-eabi@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-android-arm64@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-darwin-arm64@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-darwin-x64@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-linux-arm-gnueabihf@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-linux-arm64-gnu@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-linux-arm64-musl@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-linux-x64-gnu@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-linux-x64-musl@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-win32-arm64-msvc@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-win32-ia32-msvc@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-win32-x64-msvc@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js@2.6.2':
+ optionalDependencies:
+ '@resvg/resvg-js-android-arm-eabi': 2.6.2
+ '@resvg/resvg-js-android-arm64': 2.6.2
+ '@resvg/resvg-js-darwin-arm64': 2.6.2
+ '@resvg/resvg-js-darwin-x64': 2.6.2
+ '@resvg/resvg-js-linux-arm-gnueabihf': 2.6.2
+ '@resvg/resvg-js-linux-arm64-gnu': 2.6.2
+ '@resvg/resvg-js-linux-arm64-musl': 2.6.2
+ '@resvg/resvg-js-linux-x64-gnu': 2.6.2
+ '@resvg/resvg-js-linux-x64-musl': 2.6.2
+ '@resvg/resvg-js-win32-arm64-msvc': 2.6.2
+ '@resvg/resvg-js-win32-ia32-msvc': 2.6.2
+ '@resvg/resvg-js-win32-x64-msvc': 2.6.2
+
+ '@rollup/rollup-linux-x64-gnu@4.62.1':
+ optional: true
+
+ '@seydx/node-av-darwin-arm64@6.1.1':
+ optional: true
+
+ '@seydx/node-av-darwin-x64@6.1.1':
+ optional: true
+
+ '@seydx/node-av-linux-arm64@6.1.1':
+ optional: true
+
+ '@seydx/node-av-linux-x64@6.1.1':
+ optional: true
+
+ '@seydx/node-av-win32-arm64-mingw@6.1.1':
+ optional: true
+
+ '@seydx/node-av-win32-arm64-msvc@6.1.1':
+ optional: true
+
+ '@seydx/node-av-win32-x64-mingw@6.1.1':
+ optional: true
+
+ '@seydx/node-av-win32-x64-msvc@6.1.1':
+ optional: true
+
+ '@shinyoshiaki/binary-data@0.6.1':
+ dependencies:
+ generate-function: 2.3.1
+ is-plain-object: 2.0.4
+ optional: true
+
+ '@shinyoshiaki/jspack@0.0.6':
+ optional: true
+
+ '@swc/helpers@0.5.23':
+ dependencies:
+ tslib: 2.8.1
+
+ '@types/dom-mediacapture-transform@0.1.12':
+ dependencies:
+ '@types/dom-webcodecs': 0.1.13
+
+ '@types/dom-webcodecs@0.1.13': {}
+
+ '@types/node@24.19.1':
+ dependencies:
+ undici-types: 7.24.6
+
+ '@webgpu/types@0.1.74': {}
+
+ adm-zip@0.6.1: {}
+
+ aes-js@3.1.2:
+ optional: true
+
+ agent-base@7.1.4: {}
+
+ asn1js@3.0.10:
+ dependencies:
+ pvtsutils: 1.3.6
+ pvutils: 1.2.0
+ tslib: 2.8.1
+ optional: true
+
+ atomic-sleep@1.0.0: {}
+
+ base64-js@1.5.1: {}
+
+ bluebird@3.7.2: {}
+
+ brotli@1.3.3:
+ dependencies:
+ base64-js: 1.5.1
+
+ buffer@6.0.3:
+ dependencies:
+ base64-js: 1.5.1
+ ieee754: 1.2.1
+ optional: true
+
+ clone@2.1.2: {}
+
+ core-util-is@1.0.3: {}
+
+ d3-array@3.2.4:
+ dependencies:
+ internmap: 2.0.3
+
+ d3-color@3.1.0: {}
+
+ d3-format@3.1.2: {}
+
+ d3-interpolate@3.0.1:
+ dependencies:
+ d3-color: 3.1.0
+
+ d3-path@3.1.0: {}
+
+ d3-scale@4.0.2:
+ dependencies:
+ d3-array: 3.2.4
+ d3-format: 3.1.2
+ d3-interpolate: 3.0.1
+ d3-time: 3.1.0
+ d3-time-format: 4.1.0
+
+ d3-shape@3.2.0:
+ dependencies:
+ d3-path: 3.1.0
+
+ d3-time-format@4.1.0:
+ dependencies:
+ d3-time: 3.1.0
+
+ d3-time@3.1.0:
+ dependencies:
+ d3-array: 3.2.4
+
+ debug@4.4.0:
+ dependencies:
+ ms: 2.1.3
+ optional: true
+
+ debug@4.4.3:
+ dependencies:
+ ms: 2.1.3
+
+ define-data-property@1.1.4:
+ dependencies:
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
+ define-properties@1.2.1:
+ dependencies:
+ define-data-property: 1.1.4
+ has-property-descriptors: 1.0.2
+ object-keys: 1.1.1
+
+ detect-libc@2.1.2: {}
+
+ dfa@1.2.0: {}
+
+ dns-packet@5.6.1:
+ dependencies:
+ '@leichtgewicht/ip-codec': 2.0.5
+ optional: true
+
+ dotenv@16.6.1: {}
+
+ duplexer2@0.1.4:
+ dependencies:
+ readable-stream: 2.3.8
+
+ earcut@3.2.4: {}
+
+ es-define-property@1.0.1: {}
+
+ es-errors@1.3.0: {}
+
+ esbuild@0.28.2:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.28.2
+ '@esbuild/android-arm': 0.28.2
+ '@esbuild/android-arm64': 0.28.2
+ '@esbuild/android-x64': 0.28.2
+ '@esbuild/darwin-arm64': 0.28.2
+ '@esbuild/darwin-x64': 0.28.2
+ '@esbuild/freebsd-arm64': 0.28.2
+ '@esbuild/freebsd-x64': 0.28.2
+ '@esbuild/linux-arm': 0.28.2
+ '@esbuild/linux-arm64': 0.28.2
+ '@esbuild/linux-ia32': 0.28.2
+ '@esbuild/linux-loong64': 0.28.2
+ '@esbuild/linux-mips64el': 0.28.2
+ '@esbuild/linux-ppc64': 0.28.2
+ '@esbuild/linux-riscv64': 0.28.2
+ '@esbuild/linux-s390x': 0.28.2
+ '@esbuild/linux-x64': 0.28.2
+ '@esbuild/netbsd-arm64': 0.28.2
+ '@esbuild/netbsd-x64': 0.28.2
+ '@esbuild/openbsd-arm64': 0.28.2
+ '@esbuild/openbsd-x64': 0.28.2
+ '@esbuild/openharmony-arm64': 0.28.2
+ '@esbuild/sunos-x64': 0.28.2
+ '@esbuild/win32-arm64': 0.28.2
+ '@esbuild/win32-ia32': 0.28.2
+ '@esbuild/win32-x64': 0.28.2
+
+ escape-string-regexp@4.0.0: {}
+
+ fast-deep-equal@3.1.3: {}
+
+ follow-redirects@1.16.1: {}
+
+ fontkit@2.0.4:
+ dependencies:
+ '@swc/helpers': 0.5.23
+ brotli: 1.3.3
+ clone: 2.1.2
+ dfa: 1.2.0
+ fast-deep-equal: 3.1.3
+ restructure: 3.0.2
+ tiny-inflate: 1.0.3
+ unicode-properties: 1.4.1
+ unicode-trie: 2.0.0
+
+ framefields@2.0.8(@types/node@24.19.1):
+ dependencies:
+ '@lottiefiles/dotlottie-web': 0.74.0
+ '@mediabunny/mp3-encoder': 1.61.3(mediabunny@1.61.3)
+ '@mediabunny/server': 1.61.3(mediabunny@1.61.3)
+ '@napi-rs/webcodecs': 1.4.2
+ '@resvg/resvg-js': 2.6.2
+ d3-format: 3.1.2
+ d3-scale: 4.0.2
+ d3-shape: 3.2.0
+ dotenv: 16.6.1
+ earcut: 3.2.4
+ esbuild: 0.28.2
+ fontkit: 2.0.4
+ gifuct-js: 2.1.2
+ gsap: 3.15.0
+ mediabunny: 1.61.3
+ nanoid: 5.1.16
+ onnxruntime-node: 1.30.0
+ pino: 10.4.0
+ sharp: 0.35.5(@types/node@24.19.1)
+ skia-canvas: 3.0.8
+ webgpu: 0.4.0
+ yoga-layout: 3.2.1
+ zod: 4.6.5
+ transitivePeerDependencies:
+ - '@napi-rs/canvas'
+ - '@types/node'
+ - debug
+ - supports-color
+
+ fs-extra@11.3.1:
+ dependencies:
+ graceful-fs: 4.2.11
+ jsonfile: 6.2.1
+ universalify: 2.0.1
+
+ fsevents@2.3.3:
+ optional: true
+
+ generate-function@2.3.1:
+ dependencies:
+ is-property: 1.0.2
+ optional: true
+
+ gifuct-js@2.1.2:
+ dependencies:
+ js-binary-schema-parser: 2.0.3
+
+ global-agent@4.1.3:
+ dependencies:
+ globalthis: 1.0.4
+ matcher: 4.0.0
+ semver: 7.8.5
+ serialize-error: 8.1.0
+
+ globalthis@1.0.4:
+ dependencies:
+ define-properties: 1.2.1
+ gopd: 1.2.0
+
+ gopd@1.2.0: {}
+
+ graceful-fs@4.2.11: {}
+
+ gsap@3.15.0: {}
+
+ has-property-descriptors@1.0.2:
+ dependencies:
+ es-define-property: 1.0.1
+
+ https-proxy-agent@7.0.6:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ ieee754@1.2.1:
+ optional: true
+
+ inherits@2.0.4: {}
+
+ int64-buffer@1.1.0:
+ optional: true
+
+ internmap@2.0.3: {}
+
+ ip@2.0.1:
+ optional: true
+
+ is-plain-object@2.0.4:
+ dependencies:
+ isobject: 3.0.1
+ optional: true
+
+ is-property@1.0.2:
+ optional: true
+
+ isarray@1.0.0: {}
+
+ isobject@3.0.1:
+ optional: true
+
+ js-binary-schema-parser@2.0.3: {}
+
+ jsonfile@6.2.1:
+ dependencies:
+ universalify: 2.0.1
+ optionalDependencies:
+ graceful-fs: 4.2.11
+
+ matcher@4.0.0:
+ dependencies:
+ escape-string-regexp: 4.0.0
+
+ mediabunny@1.61.3:
+ dependencies:
+ '@types/dom-mediacapture-transform': 0.1.12
+ '@types/dom-webcodecs': 0.1.13
+
+ mp4box@0.5.4:
+ optional: true
+
+ ms@2.1.3: {}
+
+ multicast-dns@7.2.5:
+ dependencies:
+ dns-packet: 5.6.1
+ thunky: 1.1.0
+ optional: true
+
+ nanoid@5.1.16: {}
+
+ node-av@6.1.1:
+ dependencies:
+ unzipper: 0.12.5
+ optionalDependencies:
+ '@seydx/node-av-darwin-arm64': 6.1.1
+ '@seydx/node-av-darwin-x64': 6.1.1
+ '@seydx/node-av-linux-arm64': 6.1.1
+ '@seydx/node-av-linux-x64': 6.1.1
+ '@seydx/node-av-win32-arm64-mingw': 6.1.1
+ '@seydx/node-av-win32-arm64-msvc': 6.1.1
+ '@seydx/node-av-win32-x64-mingw': 6.1.1
+ '@seydx/node-av-win32-x64-msvc': 6.1.1
+ werift: 0.23.0
+ transitivePeerDependencies:
+ - supports-color
+
+ node-int64@0.4.0: {}
+
+ object-keys@1.1.1: {}
+
+ on-exit-leak-free@2.1.2: {}
+
+ onnxruntime-common@1.30.0: {}
+
+ onnxruntime-node@1.30.0:
+ dependencies:
+ adm-zip: 0.6.1
+ global-agent: 4.1.3
+ onnxruntime-common: 1.30.0
+
+ pako@0.2.9: {}
+
+ parenthesis@3.1.8: {}
+
+ pino-abstract-transport@3.0.0:
+ dependencies:
+ split2: 4.2.0
+
+ pino-std-serializers@7.1.0: {}
+
+ pino@10.4.0:
+ dependencies:
+ '@pinojs/redact': 0.4.0
+ atomic-sleep: 1.0.0
+ on-exit-leak-free: 2.1.2
+ pino-abstract-transport: 3.0.0
+ pino-std-serializers: 7.1.0
+ process-warning: 5.1.0
+ quick-format-unescaped: 4.0.4
+ real-require: 1.0.0
+ safe-stable-stringify: 2.5.0
+ sonic-boom: 4.2.1
+ thread-stream: 4.2.0
+
+ process-nextick-args@2.0.1: {}
+
+ process-warning@5.1.0: {}
+
+ pvtsutils@1.3.6:
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ pvutils@1.2.0:
+ optional: true
+
+ quick-format-unescaped@4.0.4: {}
+
+ readable-stream@2.3.8:
+ dependencies:
+ core-util-is: 1.0.3
+ inherits: 2.0.4
+ isarray: 1.0.0
+ process-nextick-args: 2.0.1
+ safe-buffer: 5.1.2
+ string_decoder: 1.1.1
+ util-deprecate: 1.0.2
+
+ real-require@1.0.0: {}
+
+ reflect-metadata@0.2.2:
+ optional: true
+
+ restructure@3.0.2: {}
+
+ safe-buffer@5.1.2: {}
+
+ safe-stable-stringify@2.5.0: {}
+
+ semver@7.8.5: {}
+
+ serialize-error@8.1.0:
+ dependencies:
+ type-fest: 0.20.2
+
+ sharp@0.35.5(@types/node@24.19.1):
+ dependencies:
+ '@img/colour': 1.1.0
+ detect-libc: 2.1.2
+ semver: 7.8.5
+ optionalDependencies:
+ '@img/sharp-darwin-arm64': 0.35.5
+ '@img/sharp-darwin-x64': 0.35.5
+ '@img/sharp-freebsd-wasm32': 0.35.5
+ '@img/sharp-libvips-darwin-arm64': 1.3.4
+ '@img/sharp-libvips-darwin-x64': 1.3.4
+ '@img/sharp-libvips-linux-arm': 1.3.4
+ '@img/sharp-libvips-linux-arm64': 1.3.4
+ '@img/sharp-libvips-linux-ppc64': 1.3.4
+ '@img/sharp-libvips-linux-riscv64': 1.3.4
+ '@img/sharp-libvips-linux-s390x': 1.3.4
+ '@img/sharp-libvips-linux-x64': 1.3.4
+ '@img/sharp-libvips-linuxmusl-arm64': 1.3.4
+ '@img/sharp-libvips-linuxmusl-x64': 1.3.4
+ '@img/sharp-linux-arm': 0.35.5
+ '@img/sharp-linux-arm64': 0.35.5
+ '@img/sharp-linux-ppc64': 0.35.5
+ '@img/sharp-linux-riscv64': 0.35.5
+ '@img/sharp-linux-s390x': 0.35.5
+ '@img/sharp-linux-x64': 0.35.5
+ '@img/sharp-linuxmusl-arm64': 0.35.5
+ '@img/sharp-linuxmusl-x64': 0.35.5
+ '@img/sharp-webcontainers-wasm32': 0.35.5
+ '@img/sharp-win32-arm64': 0.35.5
+ '@img/sharp-win32-ia32': 0.35.5
+ '@img/sharp-win32-x64': 0.35.5
+ '@types/node': 24.19.1
+
+ skia-canvas@3.0.8:
+ dependencies:
+ detect-libc: 2.1.2
+ follow-redirects: 1.16.1
+ https-proxy-agent: 7.0.6
+ string-split-by: 1.0.0
+ transitivePeerDependencies:
+ - debug
+ - supports-color
+
+ sonic-boom@4.2.1:
+ dependencies:
+ atomic-sleep: 1.0.0
+
+ split2@4.2.0: {}
+
+ string-split-by@1.0.0:
+ dependencies:
+ parenthesis: 3.1.8
+
+ string_decoder@1.1.1:
+ dependencies:
+ safe-buffer: 5.1.2
+
+ thread-stream@4.2.0:
+ dependencies:
+ real-require: 1.0.0
+
+ thunky@1.1.0:
+ optional: true
+
+ tiny-inflate@1.0.3: {}
+
+ tslib@1.14.1:
+ optional: true
+
+ tslib@2.8.1: {}
+
+ tsx@4.23.15:
+ dependencies:
+ esbuild: 0.28.2
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ tsyringe@4.10.0:
+ dependencies:
+ tslib: 1.14.1
+ optional: true
+
+ turbores@1.2.2:
+ optionalDependencies:
+ '@rollup/rollup-linux-x64-gnu': 4.62.1
+
+ tweetnacl@1.0.3:
+ optional: true
+
+ type-fest@0.20.2: {}
+
+ typescript@5.9.3: {}
+
+ undici-types@7.24.6: {}
+
+ unicode-properties@1.4.1:
+ dependencies:
+ base64-js: 1.5.1
+ unicode-trie: 2.0.0
+
+ unicode-trie@2.0.0:
+ dependencies:
+ pako: 0.2.9
+ tiny-inflate: 1.0.3
+
+ universalify@2.0.1: {}
+
+ unzipper@0.12.5:
+ dependencies:
+ bluebird: 3.7.2
+ duplexer2: 0.1.4
+ fs-extra: 11.3.1
+ graceful-fs: 4.2.11
+ node-int64: 0.4.0
+
+ util-deprecate@1.0.2: {}
+
+ webgpu@0.4.0:
+ dependencies:
+ '@webgpu/types': 0.1.74
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ werift-common@0.0.3:
+ dependencies:
+ '@shinyoshiaki/jspack': 0.0.6
+ debug: 4.4.0
+ transitivePeerDependencies:
+ - supports-color
+ optional: true
+
+ werift-dtls@0.5.8:
+ dependencies:
+ '@fidm/x509': 1.2.1
+ '@noble/curves': 1.9.7
+ '@peculiar/x509': 1.14.3
+ '@shinyoshiaki/binary-data': 0.6.1
+ debug: 4.4.0
+ tweetnacl: 1.0.3
+ transitivePeerDependencies:
+ - supports-color
+ optional: true
+
+ werift-ice@0.2.3:
+ dependencies:
+ debug: 4.4.0
+ multicast-dns: 7.2.5
+ transitivePeerDependencies:
+ - supports-color
+ optional: true
+
+ werift-rtp@0.8.9:
+ dependencies:
+ buffer: 6.0.3
+ debug: 4.4.0
+ mediabunny: 1.61.3
+ transitivePeerDependencies:
+ - supports-color
+ optional: true
+
+ werift-sctp@0.0.11:
+ dependencies:
+ '@shinyoshiaki/jspack': 0.0.6
+ optional: true
+
+ werift@0.23.0:
+ dependencies:
+ '@fidm/x509': 1.2.1
+ '@minhducsun2002/leb128': 1.0.0
+ '@noble/curves': 1.9.7
+ '@peculiar/x509': 1.14.3
+ '@shinyoshiaki/binary-data': 0.6.1
+ '@shinyoshiaki/jspack': 0.0.6
+ aes-js: 3.1.2
+ buffer: 6.0.3
+ debug: 4.4.0
+ fast-deep-equal: 3.1.3
+ int64-buffer: 1.1.0
+ ip: 2.0.1
+ mp4box: 0.5.4
+ multicast-dns: 7.2.5
+ tweetnacl: 1.0.3
+ werift-common: 0.0.3
+ werift-dtls: 0.5.8
+ werift-ice: 0.2.3
+ werift-rtp: 0.8.9
+ werift-sctp: 0.0.11
+ transitivePeerDependencies:
+ - supports-color
+ optional: true
+
+ yoga-layout@3.2.1: {}
+
+ zod@4.6.5: {}
diff --git a/website/hero-animation/src/build-assets.ts b/website/hero-animation/src/build-assets.ts
new file mode 100644
index 000000000..1f0af7047
--- /dev/null
+++ b/website/hero-animation/src/build-assets.ts
@@ -0,0 +1,191 @@
+import { mkdir, readFile } from 'node:fs/promises'
+import sharp, { type Sharp } from 'sharp'
+
+const RAW = 'assets/icons-raw'
+const OUT = 'assets/gen'
+const GRAY = '#8a8a8a'
+
+/** simple-icons slug -> colour used on a dark background */
+const BRANDS: Record = {
+ postgresql: '#4169E1',
+ redis: '#FF4438',
+ docker: '#2496ED',
+ typescript: '#3178C6',
+ nodedotjs: '#5FA04E',
+ bitcoin: '#F7931A',
+ lightning: '#792EE5',
+ torproject: '#9b59d0',
+ github: '#ffffff',
+}
+
+const colorize = (svg: string, fill: string) => svg.replace(/ {
+ seed = (seed * 1664525 + 1013904223) % 4294967296
+ return seed / 4294967296
+ }
+}
+
+/** omp-style particle planet: magenta body, white dust, cool rim glow */
+async function planet() {
+ const S = 1600
+ const c = S / 2
+ const r = 700
+ const rand = rng(7)
+ const dots: string[] = []
+
+ for (let i = 0; i < 5200; i++) {
+ const a = rand() * Math.PI * 2
+ const d = Math.sqrt(rand()) ** 0.55 * r
+ const x = c + Math.cos(a) * d
+ const y = c + Math.sin(a) * d
+ const edge = d / r
+ const size = 0.6 + rand() * (edge > 0.85 ? 2.4 : 1.6)
+ const op = (0.25 + rand() * 0.75) * (0.35 + edge * 0.65)
+ dots.push(
+ ``,
+ )
+ }
+ for (let i = 0; i < 900; i++) {
+ const a = rand() * Math.PI * 2
+ const d = r + rand() ** 2 * 70
+ const x = c + Math.cos(a) * d
+ const y = c + Math.sin(a) * d
+ dots.push(
+ ``,
+ )
+ }
+
+ const svg = ``
+ await save(sharp(Buffer.from(svg)), `${OUT}/planet.png`)
+}
+
+async function glow() {
+ const svg = ``
+ await save(sharp(Buffer.from(svg)), `${OUT}/glow.png`)
+}
+
+/** Faint dot grid that fades out toward the edges */
+async function grid() {
+ const dots: string[] = []
+ for (let y = 24; y < 1080; y += 40) {
+ for (let x = 24; x < 1920; x += 40) {
+ const dx = (x - 960) / 960
+ const dy = (y - 480) / 600
+ const op = Math.max(0, 0.22 * (1 - Math.sqrt(dx * dx + dy * dy)))
+ if (op > 0.01) {
+ dots.push(``)
+ }
+ }
+ }
+ const svg = ``
+ await save(sharp(Buffer.from(svg)), `${OUT}/grid.png`)
+}
+
+/** Loose particle halo around the planet, rotated against it for parallax */
+async function sparkles() {
+ const S = 2000
+ const c = S / 2
+ const rand = rng(11)
+ const dots: string[] = []
+ for (let i = 0; i < 1400; i++) {
+ const a = rand() * Math.PI * 2
+ const d = 700 + rand() ** 1.6 * 280
+ const x = c + Math.cos(a) * d
+ const y = c + Math.sin(a) * d
+ const color = rand() < 0.3 ? '#ff8cc6' : '#ffffff'
+ dots.push(
+ ``,
+ )
+ }
+ const svg = ``
+ await save(sharp(Buffer.from(svg)), `${OUT}/sparkles.png`)
+}
+
+async function grain() {
+ const w = 1920
+ const h = 1080
+ const rand = rng(3)
+ const buf = Buffer.alloc(w * h * 4)
+ for (let i = 0; i < w * h; i++) {
+ const v = rand() < 0.5 ? 255 : 0
+ buf[i * 4] = v
+ buf[i * 4 + 1] = v
+ buf[i * 4 + 2] = v
+ buf[i * 4 + 3] = Math.floor(rand() * 10)
+ }
+ await save(sharp(buf, { raw: { width: w, height: h, channels: 4 } }), `${OUT}/grain.png`)
+}
+
+await mkdir(OUT, { recursive: true })
+await Promise.all([logos(), planet(), glow(), grain(), grid(), sparkles()])
+console.log(`assets written to ${OUT}/`)
diff --git a/website/hero-animation/src/fetch-fonts.ts b/website/hero-animation/src/fetch-fonts.ts
new file mode 100644
index 000000000..671a7068c
--- /dev/null
+++ b/website/hero-animation/src/fetch-fonts.ts
@@ -0,0 +1,29 @@
+import fs from 'node:fs/promises'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url))
+const dir = path.join(__dirname, '..', 'assets', 'fonts')
+
+const BASE = 'https://raw.githubusercontent.com/google/fonts/main/ofl'
+
+const FONT_FILES: Record = {
+ 'JetBrainsMono.ttf': 'jetbrainsmono/JetBrainsMono%5Bwght%5D.ttf',
+ 'Inter.ttf': 'inter/Inter%5Bopsz,wght%5D.ttf',
+}
+
+await fs.mkdir(dir, { recursive: true })
+
+for (const [file, src] of Object.entries(FONT_FILES)) {
+ const target = path.join(dir, file)
+ if (await fs.stat(target).catch(() => null)) {
+ console.log(`skip ${target}`)
+ continue
+ }
+ const res = await fetch(`${BASE}/${src}`)
+ if (!res.ok) {
+ throw new Error(`${src}: HTTP ${res.status}`)
+ }
+ await fs.writeFile(target, Buffer.from(await res.arrayBuffer()))
+ console.log(`wrote ${target}`)
+}
diff --git a/website/hero-animation/src/film.ts b/website/hero-animation/src/film.ts
new file mode 100644
index 000000000..fbb8b0a2e
--- /dev/null
+++ b/website/hero-animation/src/film.ts
@@ -0,0 +1,921 @@
+import { readFileSync } from 'node:fs'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
+import { Composition, Layer, LayerAnimation, Vignette } from 'framefields'
+import {
+ BG,
+ BODY,
+ DURATION,
+ FG,
+ FG_2,
+ FONT_MONO,
+ FONT_SANS,
+ FPS,
+ H,
+ LINE,
+ MUTED,
+ monoWidth,
+ PANEL,
+ PANEL_BORDER,
+ PINK,
+ PINK_DIM,
+ TEAL,
+ W,
+ WIRE,
+} from './theme.js'
+
+const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..')
+const asset = (name: string) => path.join(ROOT, 'assets', 'gen', `${name}.png`)
+const NIP_COUNT = String(
+ JSON.parse(readFileSync(path.join(ROOT, '..', '..', 'package.json'), 'utf8')).supportedNips.length,
+)
+
+type Node =
+ | ReturnType
+ | ReturnType
+ | ReturnType
+ | ReturnType
+type Key = [number, number] | [number, number, string]
+type Window = [number, number]
+
+/** Scene boundaries (frames) */
+const HERO = { start: 0, end: 165 }
+const WHAT = { start: 165, end: 345 }
+const FLOW = { start: 345, end: 675 }
+const FEATURES = { start: 675, end: 855 }
+const DEPLOY = { start: 855, end: DURATION }
+const LAST = DURATION - 1
+/** Event round-trips in the flow scene */
+const CYCLES = [FLOW.start + 92, FLOW.start + 166, FLOW.start + 240]
+
+/** Sort and merge windows that overlap or sit too close to fade between */
+function mergeWindows(windows: Window[], gap: number): Window[] {
+ const sorted = [...windows].sort((a, b) => a[0] - b[0])
+ const out: Window[] = []
+ for (const w of sorted) {
+ const prev = out[out.length - 1]
+ if (prev && w[0] <= prev[1] + gap) {
+ prev[1] = Math.max(prev[1], w[1])
+ } else {
+ out.push([...w])
+ }
+ }
+ return out
+}
+
+/** Opacity keys for [start, end) windows; end >= DURATION holds to the last frame */
+function windowKeys(windows: Window[], peak = 1, inF = 14, outF = 14): Key[] {
+ const keys: Key[] = []
+ let last = -1
+ const push = (f: number, v: number, ease?: string) => {
+ const frame = Math.max(0, Math.min(LAST, Math.round(f)))
+ if (frame <= last) {
+ return
+ }
+ keys.push(ease ? [frame, v, ease] : [frame, v])
+ last = frame
+ }
+ const merged = mergeWindows(windows, inF + outF)
+ if (merged[0][0] > 0) {
+ push(0, 0)
+ }
+ for (const [start, end] of merged) {
+ push(start, 0)
+ push(start + inF, peak, 'power2.out')
+ if (end < DURATION) {
+ push(end - outF, peak)
+ push(end, 0, 'power2.in')
+ }
+ }
+ return keys
+}
+
+interface Show {
+ start: number
+ end?: number
+ dy?: number
+ inF?: number
+ outF?: number
+ peak?: number
+}
+
+/** Fade (and optionally rise) a layer in for a window */
+function show(y: number, { start, end = DURATION, dy = 0, inF = 14, outF = 14, peak = 1 }: Show) {
+ const anim = LayerAnimation.create().keys('opacity', windowKeys([[start, end]], peak, inF, outF))
+ if (dy) {
+ const yk: Key[] = [
+ [start, y + dy],
+ [start + inF + 8, y, 'power3.out'],
+ ]
+ if (end < DURATION) {
+ yk.push([end - outF, y])
+ yk.push([end, y - dy * 0.6, 'power2.in'])
+ }
+ anim.keys('y', yk)
+ }
+ return anim
+}
+
+const mono = (text: string, x: number, y: number, size: number, opts: Record = {}) =>
+ Layer.text(text, {
+ position: 'absolute',
+ x,
+ y,
+ fontFamily: FONT_MONO,
+ fontSize: size,
+ fill: BODY,
+ opacity: 0,
+ ...opts,
+ })
+
+const sans = (text: string, x: number, y: number, size: number, opts: Record = {}) =>
+ Layer.text(text, {
+ position: 'absolute',
+ x,
+ y,
+ fontFamily: FONT_SANS,
+ fontSize: size,
+ fontWeight: 700,
+ fill: FG,
+ opacity: 0,
+ ...opts,
+ })
+
+const rect = (x: number, y: number, w: number, h: number, opts: Record = {}) =>
+ Layer.box({ position: 'absolute', x, y, width: w, height: h, opacity: 0, ...opts })
+
+const img = (name: string, x: number, y: number, size: number) =>
+ Layer.image(asset(name), { position: 'absolute', x, y, width: size, height: size, opacity: 0 })
+
+const curve = (x1: number, y1: number, x2: number, y2: number) => {
+ if (x1 === x2 || y1 === y2) {
+ return `M ${x1} ${y1} L ${x2} ${y2}`
+ }
+ const mx = (x1 + x2) / 2
+ return `M ${x1} ${y1} C ${mx} ${y1} ${mx} ${y2} ${x2} ${y2}`
+}
+
+const pathLayer = (d: string, color: string, width: number, opts: Record = {}) =>
+ Layer.shape('path', {
+ position: 'absolute',
+ x: 0,
+ y: 0,
+ width: W,
+ height: H,
+ d,
+ fillType: 'none',
+ strokeColor: color,
+ strokeWidth: width,
+ strokeLineCap: 'round',
+ strokeLineJoin: 'round',
+ opacity: 0,
+ ...opts,
+ } as never)
+
+/** Drawn check mark (the font renderer treats ✓ as an emoji) */
+const check = (x: number, y: number, size: number) =>
+ pathLayer(
+ `M ${x} ${y + size * 0.55} L ${x + size * 0.38} ${y + size * 0.9} L ${x + size} ${y + size * 0.1}`,
+ PINK,
+ 2.5,
+ )
+
+const ring = (cx: number, cy: number, r: number, color: string, width: number) =>
+ pathLayer(`M ${cx - r} ${cy} A ${r} ${r} 0 1 0 ${cx + r} ${cy} A ${r} ${r} 0 1 0 ${cx - r} ${cy}`, color, width)
+
+export async function buildFilm(): Promise {
+ const film = new Composition({
+ width: W,
+ height: H,
+ fps: FPS,
+ durationFrames: DURATION,
+ backgroundColor: BG,
+ fonts: [path.join(ROOT, 'assets/fonts/JetBrainsMono.ttf'), path.join(ROOT, 'assets/fonts/Inter.ttf')],
+ })
+ const add = (node: Node, anim: LayerAnimation) => film.add(node.animate(anim) as never)
+
+ /** Rail highlight windows, filled in by each scene and drawn at the end */
+ const lit: Record = {
+ nostr: [],
+ nostream: [],
+ postgresql: [],
+ redis: [],
+ lightning: [],
+ docker: [],
+ }
+ const light = (name: keyof typeof lit, start: number, end: number) => lit[name].push([start, end])
+
+ // ── Backdrop: dot grid, planet, particle halo ─────────────────────────────
+ add(
+ Layer.image(asset('grid'), { position: 'absolute', x: 0, y: 0, width: W, height: H, opacity: 0 }),
+ show(0, { start: 0, inF: 30 }),
+ )
+
+ const planetSize = 1700
+ const haloSize = planetSize * (2000 / 1600)
+ const planetY: Key[] = [
+ [0, 1100],
+ [60, 520, 'power3.out'],
+ [HERO.end - 10, 505],
+ [WHAT.start + 25, 700, 'power2.inOut'],
+ [WHAT.end - 20, 710],
+ [FLOW.start + 20, 790, 'power2.inOut'],
+ [FEATURES.end - 20, 820],
+ [DEPLOY.start + 30, 500, 'power3.out'],
+ [LAST, 480],
+ ]
+ const planetOpacity: Key[] = [
+ [0, 0],
+ [30, 1],
+ [HERO.end - 10, 1],
+ [WHAT.start + 25, 0.6],
+ [WHAT.end - 20, 0.6],
+ [FLOW.start + 20, 0.35],
+ [FEATURES.end - 20, 0.35],
+ [DEPLOY.start + 30, 1],
+ ]
+ add(
+ Layer.image(asset('planet'), {
+ position: 'absolute',
+ x: 1000,
+ y: 1100,
+ width: planetSize,
+ height: planetSize,
+ opacity: 0,
+ }),
+ LayerAnimation.create()
+ .keys('y', planetY)
+ .keys('opacity', planetOpacity)
+ .fromTo('rotation', 0, 50, { start: 0, end: LAST, ease: 'linear' }),
+ )
+ const haloOffset = (haloSize - planetSize) / 2
+ add(
+ Layer.image(asset('sparkles'), {
+ position: 'absolute',
+ x: 1000 - haloOffset,
+ y: 1100 - haloOffset,
+ width: haloSize,
+ height: haloSize,
+ opacity: 0,
+ }),
+ LayerAnimation.create()
+ .keys(
+ 'y',
+ planetY.map(([f, v, e]) => (e ? [f, v - haloOffset, e] : [f, v - haloOffset]) as Key),
+ )
+ .keys('opacity', planetOpacity)
+ .fromTo('rotation', 0, -35, { start: 0, end: LAST, ease: 'linear' }),
+ )
+
+ // ── Persistent chrome: nav, footer ──────────────────────────────────────
+ const always = { start: 0, inF: 12 }
+ add(rect(120, 45, 16, 16, { background: PINK }), show(45, always))
+ add(mono('nostream', 146, 37, 22, { fill: FG, fontWeight: 700 }), show(37, always))
+
+ const navSize = 14
+ const navSpacing = 2
+ let navX = 1800
+ for (const label of ['GITHUB', 'NIPS', 'DOCS']) {
+ navX -= monoWidth(label, navSize, navSpacing)
+ add(mono(label, navX, 42, navSize, { letterSpacing: navSpacing, fill: BODY }), show(42, always))
+ if (label === 'GITHUB') {
+ add(img('github', navX - 28, 40, 20), show(40, { ...always, peak: 0.75 }))
+ navX -= 28
+ }
+ navX -= 44
+ }
+
+ const footerRight = 'OPEN SOURCE · MIT LICENSE · GITHUB.COM/CAMERI/NOSTREAM'
+ add(mono('nostream', 120, 1022, 18, { fill: FG, fontWeight: 700 }), show(1022, always))
+ add(
+ mono(footerRight, 1800 - monoWidth(footerRight, 13, 2), 1025, 13, { letterSpacing: 2, fill: BODY }),
+ show(1025, always),
+ )
+
+ const hx = 160
+ const sceneLabel = (text: string, y: number, start: number, end: number) =>
+ add(mono(text, hx, y, 15, { letterSpacing: 3, fill: TEAL }), show(y, { start, end, dy: 12 }))
+
+ const card = (x: number, y: number, w: number, h: number, start: number, end: number, accent = false) =>
+ add(
+ rect(x, y, w, h, {
+ background: PANEL,
+ borderColor: accent ? PINK : PANEL_BORDER,
+ borderWidth: accent ? 2 : 1,
+ borderRadius: 8,
+ }),
+ show(y, { start, end, dy: 18 }),
+ )
+
+ const glow = (cx: number, cy: number, size: number, windows: Window[], peak: number) =>
+ add(
+ Layer.image(asset('glow'), {
+ position: 'absolute',
+ x: cx - size / 2,
+ y: cy - size / 2,
+ width: size,
+ height: size,
+ opacity: 0,
+ }),
+ LayerAnimation.create().keys('opacity', windowKeys(windows, peak, 20)),
+ )
+
+ /** A pink comet with a soft halo travelling along a path */
+ const pulse = (d: string, start: number, dur = 24) => {
+ for (const [color, width, peak] of [
+ [PINK_DIM, 10, 0.9],
+ [PINK, 3.5, 1],
+ ] as const) {
+ add(
+ pathLayer(d, color, width, { trimStart: 0, trimEnd: 0 }),
+ LayerAnimation.create()
+ .keys('opacity', [
+ [start - 1, 0],
+ [start, peak],
+ [start + dur - 2, peak],
+ [start + dur, 0],
+ ])
+ .keys('trimEnd', [
+ [start, 0],
+ [start + dur * 0.75, 1, 'power1.inOut'],
+ ])
+ .keys('trimStart', [
+ [start + dur * 0.25, 0],
+ [start + dur, 1, 'power1.inOut'],
+ ]),
+ )
+ }
+ }
+
+ const drawWire = (d: string, start: number, end: number, color = WIRE, width = 2) =>
+ add(
+ pathLayer(d, color, width, { trimEnd: 0 }),
+ LayerAnimation.create()
+ .keys('opacity', windowKeys([[start, end]], 1, 4))
+ .keys('trimEnd', [
+ [start, 0],
+ [start + 22, 1, 'power2.out'],
+ ]),
+ )
+
+ const tabs = (y: number, start: number, end: number) => {
+ let x = hx
+ ;['DOCKER', 'CLI', 'TOR'].forEach((label, i) => {
+ const w = monoWidth(label, 14, 2)
+ add(
+ mono(label, x, y, 14, { letterSpacing: 2, fill: i === 0 ? FG : MUTED }),
+ show(y, { start: start + i * 3, end }),
+ )
+ if (i === 0) {
+ add(rect(x, y + 26, w - 2, 2, { background: PINK }), show(y + 26, { start: start + 6, end }))
+ }
+ x += w + 36
+ })
+ }
+
+ const copyButton = (right: number, y: number, start: number, end: number) => {
+ add(
+ rect(right - 84, y, 80, 34, { borderColor: PANEL_BORDER, borderWidth: 1, borderRadius: 3 }),
+ show(y, { start, end }),
+ )
+ add(mono('COPY', right - 68, y + 9, 13, { letterSpacing: 2, fill: BODY }), show(y + 9, { start, end }))
+ }
+
+ // ── Scene 1 · Hero ─────────────────────────────────────────────────────
+ const heroOut = HERO.end
+ light('nostream', 24, heroOut)
+ sceneLabel('NOSTREAM · OPEN-SOURCE NOSTR RELAY', 196, 6, heroOut)
+ const headline: [string, string][] = [
+ ['Your relay.', FG],
+ ['Your rules.', FG_2],
+ ['Nostr, wired in.', PINK],
+ ]
+ headline.forEach(([text, fill], i) => {
+ const y = 228 + i * 112
+ add(
+ sans(text, hx - 6, y, 108, { fill, letterSpacing: -4, fontWeight: 800 }),
+ show(y, { start: 12 + i * 9, end: heroOut, dy: 40, inF: 18 }),
+ )
+ })
+
+ const bodyY1 = 606
+ const bodyY2 = 640
+ add(
+ mono('A production-ready Nostr relay, written in TypeScript.', hx, bodyY1, 21, { fill: BODY }),
+ show(bodyY1, { start: 42, end: heroOut, dy: 14 }),
+ )
+ const prefix = 'Your server. Your data. No '
+ const struck = 'middleman'
+ const strikeX = hx + monoWidth(prefix, 21)
+ add(mono(prefix, hx, bodyY2, 21, { fill: BODY }), show(bodyY2, { start: 48, end: heroOut, dy: 14 }))
+ add(mono(struck, strikeX, bodyY2, 21, { fill: MUTED }), show(bodyY2, { start: 48, end: heroOut, dy: 14 }))
+ add(
+ mono('.', strikeX + monoWidth(struck, 21), bodyY2, 21, { fill: BODY }),
+ show(bodyY2, { start: 48, end: heroOut, dy: 14 }),
+ )
+ add(
+ rect(strikeX - 2, bodyY2 + 14, 0, 2, { background: PINK }),
+ show(bodyY2 + 14, { start: 66, end: heroOut }).keys('width', [
+ [66, 0],
+ [80, monoWidth(struck, 21) + 4, 'power2.out'],
+ ]),
+ )
+
+ tabs(708, 62, heroOut)
+ const heroTerm = { y: 744, w: 780 }
+ add(
+ rect(hx, heroTerm.y, heroTerm.w, 70, {
+ background: PANEL,
+ borderColor: PANEL_BORDER,
+ borderWidth: 1,
+ borderRadius: 4,
+ }),
+ show(heroTerm.y, { start: 68, end: heroOut }),
+ )
+ add(
+ mono('$', hx + 26, heroTerm.y + 20, 23, { fill: PINK, fontWeight: 700 }),
+ show(heroTerm.y + 20, { start: 72, end: heroOut }),
+ )
+ add(
+ mono('nostream start', hx + 58, heroTerm.y + 20, 23, { fill: FG }),
+ show(heroTerm.y + 20, { start: 76, end: heroOut, inF: 2 }).typewriter(78, 106, 'linear'),
+ )
+ add(
+ rect(hx + 252, heroTerm.y + 20, 12, 28, { background: PINK }),
+ LayerAnimation.create().keys(
+ 'opacity',
+ windowKeys(
+ [
+ [106, 118],
+ [128, 140],
+ [150, heroOut],
+ ],
+ 0.9,
+ 2,
+ 2,
+ ),
+ ),
+ )
+ copyButton(hx + heroTerm.w - 18, heroTerm.y + 18, 74, heroOut)
+
+ const stats: [string, string][] = [
+ [NIP_COUNT, 'NIPS SUPPORTED'],
+ ['6', 'LIGHTNING PROCESSORS'],
+ ['MIT', 'OPEN SOURCE'],
+ ]
+ stats.forEach(([value, label], i) => {
+ const x = hx + i * 270
+ const s = 96 + i * 8
+ add(rect(x, 868, 1, 62, { background: LINE }), show(868, { start: s, end: heroOut }))
+ add(
+ sans(value, x + 20, 862, 40, { fill: FG, fontWeight: 800, letterSpacing: -1 }),
+ show(862, { start: s, end: heroOut, dy: 12 }),
+ )
+ add(mono(label, x + 20, 914, 13, { letterSpacing: 2, fill: MUTED }), show(914, { start: s + 4, end: heroOut }))
+ })
+
+ // ── Scene 2 · What is nostream ──────────────────────────────────────────
+ const w0 = WHAT.start
+ const wEnd = WHAT.end
+ sceneLabel('01 — WHAT IS NOSTREAM', 150, w0 + 4, wEnd)
+ add(
+ sans('Nostr runs on relays.', hx - 4, 180, 82, { letterSpacing: -3, fontWeight: 800 }),
+ show(180, { start: w0 + 8, end: wEnd, dy: 30, inF: 18 }),
+ )
+ add(
+ sans('nostream is the one you own.', hx - 4, 272, 82, { letterSpacing: -3, fontWeight: 800, fill: PINK }),
+ show(272, { start: w0 + 16, end: wEnd, dy: 30, inF: 18 }),
+ )
+
+ const whatCards = [
+ {
+ num: '01',
+ title: 'Nostr',
+ body: 'An open protocol for social\napps. You hold your keys,\nso no company owns you.',
+ tag: 'keys · signed events · NIPs',
+ },
+ {
+ num: '02',
+ title: 'Relays',
+ body: 'Servers that receive, store\nand forward signed events\nbetween clients.',
+ tag: 'wss:// · store · forward',
+ },
+ {
+ num: '03',
+ title: 'nostream',
+ body: 'A production-ready relay\nin TypeScript that you\ndeploy and control.',
+ tag: 'self-hosted · open source',
+ },
+ ]
+ const wc = { y: 430, w: 440, h: 330, gap: 140 }
+ whatCards.forEach(({ num, title, body, tag }, i) => {
+ const x = hx + i * (wc.w + wc.gap)
+ const s = w0 + 30 + i * 22
+ const at = (y: number, delay = 4) => show(y, { start: s + delay, end: wEnd, dy: 18 })
+ const accent = i === 2
+ if (accent) {
+ glow(x + wc.w / 2, wc.y + wc.h / 2, 900, [[s + 10, wEnd]], 0.32)
+ }
+ card(x, wc.y, wc.w, wc.h, s, wEnd, accent)
+ add(
+ mono(num, x + wc.w - 150, wc.y + 6, 120, { fill: '#17171b', fontWeight: 800, letterSpacing: -6 }),
+ at(wc.y + 6, 2),
+ )
+ const iy = wc.y + 36
+ if (i === 0) {
+ add(img('nostr', x + 36, iy, 68), at(iy))
+ }
+ if (i === 1) {
+ const cx = x + 70
+ const cy = iy + 34
+ add(ring(cx, cy, 32, WIRE, 2), show(0, { start: s + 4, end: wEnd }))
+ add(ring(cx, cy, 20, BODY, 2), show(0, { start: s + 4, end: wEnd }))
+ add(rect(cx - 6, cy - 6, 12, 12, { background: PINK, borderRadius: 6 }), at(iy))
+ }
+ if (i === 2) {
+ add(rect(x + 44, iy + 12, 44, 44, { background: PINK, borderRadius: 2 }), at(iy))
+ }
+ light(i === 0 ? 'nostr' : 'nostream', s, s + 50)
+ add(sans(title, x + 36, wc.y + 128, 38, { letterSpacing: -1, fill: accent ? PINK : FG }), at(wc.y + 128, 6))
+ add(mono(body, x + 36, wc.y + 186, 17, { fill: BODY, lineHeight: 28 }), at(wc.y + 186, 8))
+ add(rect(x + 36, wc.y + wc.h - 58, wc.w - 72, 1, { background: LINE }), at(wc.y + wc.h - 58, 8))
+ add(
+ mono(tag, x + 36, wc.y + wc.h - 40, 14, { fill: accent ? PINK : MUTED, letterSpacing: 1 }),
+ at(wc.y + wc.h - 40, 10),
+ )
+ })
+ for (let i = 0; i < 2; i++) {
+ const x1 = hx + wc.w + i * (wc.w + wc.gap) + 20
+ const x2 = x1 + wc.gap - 40
+ const y = wc.y + wc.h / 2
+ const s = w0 + 52 + i * 22
+ drawWire(`M ${x1} ${y} L ${x2} ${y}`, s, wEnd, BODY, 2)
+ drawWire(`M ${x2 - 12} ${y - 10} L ${x2} ${y} L ${x2 - 12} ${y + 10}`, s + 14, wEnd, BODY, 2)
+ for (const t of [w0 + 100, w0 + 150]) {
+ pulse(`M ${x1} ${y} L ${x2} ${y}`, t + i * 18, 20)
+ }
+ }
+
+ // ── Scene 3 · How nostream works ────────────────────────────────────────
+ const f0 = FLOW.start
+ const fEnd = FLOW.end
+ sceneLabel('02 — HOW NOSTREAM WORKS', 138, f0 + 4, fEnd)
+ add(
+ sans('Clients publish.', hx - 3, 166, 52, { letterSpacing: -1.5 }),
+ show(166, { start: f0 + 8, end: fEnd, dy: 20 }),
+ )
+ add(
+ sans('nostream verifies, stores & streams.', hx - 3, 226, 52, { letterSpacing: -1.5, fill: PINK }),
+ show(226, { start: f0 + 14, end: fEnd, dy: 20 }),
+ )
+ const atF = (start: number, y: number) => show(y, { start, end: fEnd, dy: 16 })
+
+ const core = { x: 760, y: 500, w: 400, h: 280 }
+ const coreStart = f0 + 18
+ glow(core.x + core.w / 2, core.y + core.h / 2 - 40, 780, [[coreStart, fEnd]], 0.35)
+ card(core.x, core.y, core.w, core.h, coreStart, fEnd, true)
+ add(rect(core.x + 30, core.y + 34, 18, 18, { background: PINK }), atF(coreStart + 4, core.y + 34))
+ add(sans('nostream', core.x + 60, core.y + 20, 36, { letterSpacing: -1 }), atF(coreStart + 4, core.y + 20))
+ add(
+ mono('RELAY CORE', core.x + 30, core.y + 72, 13, { letterSpacing: 3, fill: TEAL }),
+ atF(coreStart + 6, core.y + 72),
+ )
+ add(rect(core.x + 30, core.y + 100, core.w - 60, 1, { background: LINE }), atF(coreStart + 6, core.y + 100))
+ const checks = [
+ 'verify id + schnorr signature',
+ 'rate limits · spam rules · PoW',
+ 'paid admission · NIP-42 auth',
+ 'NIP-11 relay information',
+ ]
+ checks.forEach((text, i) => {
+ const y = core.y + 116 + i * 30
+ const tick = CYCLES[0] + 24 + i * 6
+ add(mono('·', core.x + 30, y, 16, { fill: MUTED }), show(y, { start: coreStart + 8, end: tick + 2, outF: 4 }))
+ add(mono(text, core.x + 56, y, 16, { fill: MUTED }), show(y, { start: coreStart + 8, end: tick + 2, outF: 4 }))
+ add(check(core.x + 28, y + 4, 13), show(0, { start: tick, end: fEnd, inF: 6 }))
+ add(mono(text, core.x + 56, y, 16, { fill: FG }), show(y, { start: tick, end: fEnd, inF: 6 }))
+ })
+ add(img('typescript', core.x + 30, core.y + 242, 22), atF(coreStart + 10, core.y + 242))
+ add(img('nodedotjs', core.x + 60, core.y + 242, 22), atF(coreStart + 10, core.y + 242))
+ add(mono('TypeScript · Node.js', core.x + 94, core.y + 245, 14, { fill: MUTED }), atF(coreStart + 10, core.y + 245))
+
+ // Clients
+ const clients = [
+ { name: 'Damus', sub: 'iOS' },
+ { name: 'Amethyst', sub: 'Android' },
+ { name: 'Primal', sub: 'web' },
+ ]
+ const cliX = 200
+ const cliW = 320
+ add(mono('NOSTR CLIENTS', cliX, 436, 13, { letterSpacing: 3, fill: MUTED }), atF(f0 + 30, 436))
+ const clientWires: { d: string; back: string }[] = []
+ clients.forEach(({ name, sub }, i) => {
+ const y = 470 + i * 130
+ const s = f0 + 30 + i * 6
+ card(cliX, y, cliW, 88, s, fEnd)
+ add(img('nostr', cliX + 18, y + 20, 48), atF(s + 2, y + 20))
+ add(sans(name, cliX + 82, y + 16, 24, { letterSpacing: -0.5 }), atF(s + 2, y + 16))
+ add(mono(`${sub} client · wss://`, cliX + 82, y + 52, 14, { fill: MUTED }), atF(s + 2, y + 52))
+ const sy = y + 44
+ const ey = core.y + 80 + i * 60
+ clientWires.push({ d: curve(cliX + cliW, sy, core.x, ey), back: curve(core.x, ey, cliX + cliW, sy) })
+ })
+
+ // Protocol messages
+ const eventCard = { x: cliX, y: 300, w: cliW, h: 120 }
+ const evStart = CYCLES[0] - 14
+ card(eventCard.x, eventCard.y, eventCard.w, eventCard.h, evStart, fEnd)
+ add(mono('["EVENT", {', eventCard.x + 20, eventCard.y + 16, 15, { fill: PINK }), atF(evStart + 2, eventCard.y + 16))
+ add(
+ mono(' "kind": 1,\n "content": "gm nostr",\n "sig": "a3f9…c41e" }]', eventCard.x + 20, eventCard.y + 38, 15, {
+ fill: BODY,
+ lineHeight: 22,
+ }),
+ atF(evStart + 4, eventCard.y + 38),
+ )
+
+ const stX = 1400
+ const stW = 330
+ const reqCard = { x: stX, y: 300, w: stW, h: 120 }
+ const reqStart = CYCLES[0] + 44
+ card(reqCard.x, reqCard.y, reqCard.w, reqCard.h, reqStart, fEnd)
+ add(
+ mono('["REQ", "feed", {"kinds":[1]}]', reqCard.x + 20, reqCard.y + 16, 14, { fill: PINK }),
+ atF(reqStart + 2, reqCard.y + 16),
+ )
+ add(
+ mono('← ["EVENT", "feed", {…}]\n← ["EOSE", "feed"]\n live updates stream on', reqCard.x + 20, reqCard.y + 40, 14, {
+ fill: BODY,
+ lineHeight: 22,
+ }),
+ atF(reqStart + 4, reqCard.y + 40),
+ )
+
+ // Storage
+ add(mono('STORAGE', stX, 446, 13, { letterSpacing: 3, fill: MUTED }), atF(f0 + 48, 446))
+ const storage = [
+ { logo: 'postgresql', name: 'PostgreSQL', sub: 'events · durable store', y: 480, from: core.y + 90 },
+ { logo: 'redis', name: 'Redis', sub: 'cache · rate limits', y: 650, from: core.y + 190 },
+ ]
+ const storageWires: string[] = []
+ storage.forEach(({ logo, name, sub, y, from }, i) => {
+ const s = f0 + 48 + i * 8
+ card(stX, y, stW, 112, s, fEnd)
+ add(img(logo, stX + 22, y + 27, 58), atF(s + 2, y + 27))
+ add(sans(name, stX + 98, y + 24, 27, { letterSpacing: -0.5 }), atF(s + 2, y + 24))
+ add(mono(sub, stX + 98, y + 66, 14, { fill: MUTED }), atF(s + 2, y + 66))
+ storageWires.push(curve(core.x + core.w, from, stX, y + 56))
+ })
+
+ // Stored-event counter on the PostgreSQL card
+ const counts = ['12,480', '12,483', '12,486', '12,489']
+ counts.forEach((value, i) => {
+ const s = i === 0 ? f0 + 52 : CYCLES[i - 1] + 34
+ const e = i === counts.length - 1 ? fEnd : CYCLES[i] + 34
+ const x = stX + stW - 20 - monoWidth(value, 15)
+ add(
+ mono(value, x, 480 + 28, 15, { fill: i === 0 ? MUTED : PINK, fontWeight: 700 }),
+ show(508, { start: s, end: e, inF: 4, outF: 4 }),
+ )
+ })
+
+ // Lightning (top) & deploy (bottom)
+ const ln = { x: 790, y: 336, w: 340, h: 104 }
+ const lnStart = f0 + 62
+ card(ln.x, ln.y, ln.w, ln.h, lnStart, fEnd)
+ add(img('lightning', ln.x + 18, ln.y + 30, 44), atF(lnStart + 2, ln.y + 30))
+ add(img('bitcoin', ln.x + 68, ln.y + 30, 44), atF(lnStart + 2, ln.y + 30))
+ add(sans('Lightning payments', ln.x + 126, ln.y + 24, 20, { letterSpacing: -0.3 }), atF(lnStart + 2, ln.y + 24))
+ add(mono('paid admission · zaps', ln.x + 126, ln.y + 58, 14, { fill: MUTED }), atF(lnStart + 2, ln.y + 58))
+ const lnWire = curve(core.x + core.w / 2, ln.y + ln.h, core.x + core.w / 2, core.y)
+
+ const dp = { x: 760, y: 846, w: 400, h: 88 }
+ const dpStart = f0 + 70
+ card(dp.x, dp.y, dp.w, dp.h, dpStart, fEnd)
+ add(img('docker', dp.x + 18, dp.y + 22, 44), atF(dpStart + 2, dp.y + 22))
+ add(img('torproject', dp.x + 70, dp.y + 22, 44), atF(dpStart + 2, dp.y + 22))
+ add(sans('Deploy anywhere', dp.x + 130, dp.y + 16, 20, { letterSpacing: -0.3 }), atF(dpStart + 2, dp.y + 16))
+ add(mono('docker compose · --tor · --i2p', dp.x + 130, dp.y + 50, 14, { fill: MUTED }), atF(dpStart + 2, dp.y + 50))
+ const dpWire = curve(core.x + core.w / 2, core.y + core.h, core.x + core.w / 2, dp.y)
+
+ clientWires.forEach(({ d }, i) => drawWire(d, f0 + 40 + i * 4, fEnd))
+ storageWires.forEach((d, i) => drawWire(d, f0 + 58 + i * 4, fEnd))
+ drawWire(lnWire, lnStart + 6, fEnd)
+ drawWire(dpWire, dpStart + 6, fEnd)
+ drawWire(
+ curve(eventCard.x + eventCard.w - 40, eventCard.y + eventCard.h, eventCard.x + eventCard.w - 40, 470),
+ evStart + 6,
+ fEnd,
+ WIRE,
+ 1.5,
+ )
+ drawWire(
+ curve(reqCard.x + reqCard.w - 40, reqCard.y + reqCard.h, reqCard.x + reqCard.w - 40, 480),
+ reqStart + 6,
+ fEnd,
+ WIRE,
+ 1.5,
+ )
+
+ CYCLES.forEach((t, c) => {
+ clientWires.forEach(({ d }, i) => pulse(d, t + i * 4))
+ if (c === 0) {
+ pulse(lnWire, t + 10, 20)
+ }
+ storageWires.forEach((d, i) => pulse(d, t + 30 + i * 3, 22))
+ clientWires.forEach(({ back }, i) => pulse(back, t + 54 + i * 3, 22))
+ glow(core.x + core.w / 2, core.y + core.h / 2 - 40, 780, [[t + 22, t + 50]], 0.6)
+ })
+ const [c0, c1, c2] = CYCLES
+ light('nostr', c0, c0 + 26)
+ light('lightning', c0 + 8, c0 + 34)
+ light('nostream', c0 + 22, c0 + 50)
+ light('postgresql', c1 + 28, c1 + 56)
+ light('redis', c1 + 28, c1 + 56)
+ light('nostr', c2 + 50, c2 + 80)
+
+ const captions: [string, string, number, number][] = [
+ ['01', 'Clients sign & publish EVENTs over WebSocket', c0 - 10, c0 + 24],
+ ['02', 'nostream verifies signatures, limits & admission', c0 + 24, c1 + 20],
+ ['03', 'Events persist in PostgreSQL — Redis keeps rate limits hot', c1 + 20, c2],
+ ['04', 'Matching REQ subscriptions receive them instantly', c2, c2 + 50],
+ ['→', 'One relay, many clients — all events under your control', c2 + 50, fEnd],
+ ]
+ for (const [num, text, s, e] of captions) {
+ add(
+ mono(num, hx, 966, 16, { fill: PINK, fontWeight: 700 }),
+ show(966, { start: s, end: e, dy: 8, inF: 8, outF: 8 }),
+ )
+ add(mono(text, hx + 46, 966, 16, { fill: FG }), show(966, { start: s, end: e, dy: 8, inF: 8, outF: 8 }))
+ }
+
+ // ── Scene 4 · Features ────────────────────────────────────────────────
+ const g0 = FEATURES.start
+ const gEnd = FEATURES.end
+ sceneLabel('03 — WHY NOSTREAM', 150, g0 + 4, gEnd)
+ add(
+ sans('Everything a relay operator needs.', hx - 4, 180, 72, { letterSpacing: -2.5, fontWeight: 800 }),
+ show(180, { start: g0 + 8, end: gEnd, dy: 28, inF: 18 }),
+ )
+ add(
+ sans("Nothing you don't.", hx - 4, 262, 72, { letterSpacing: -2.5, fontWeight: 800, fill: PINK }),
+ show(262, { start: g0 + 16, end: gEnd, dy: 28, inF: 18 }),
+ )
+
+ const features: { logos: string[]; rail?: keyof typeof lit; title: string; body: string }[] = [
+ {
+ logos: ['nostr'],
+ rail: 'nostr',
+ title: `${NIP_COUNT} NIPs supported`,
+ body: 'DMs, reactions, replaceable &\nexpiring events, counts, search',
+ },
+ {
+ logos: ['lightning', 'bitcoin'],
+ rail: 'lightning',
+ title: 'Paid admission',
+ body: 'LNbits, ZEBEDEE, OpenNode,\nNodeless, LNURL or NWC',
+ },
+ {
+ logos: ['redis'],
+ rail: 'redis',
+ title: 'Rate limits & spam rules',
+ body: 'Per-IP and per-pubkey limits,\nproof of work, content filters',
+ },
+ {
+ logos: ['postgresql'],
+ rail: 'postgresql',
+ title: 'Import & export',
+ body: 'Stream events in and out as\n.jsonl or .json, compressed',
+ },
+ {
+ logos: ['torproject'],
+ title: 'Tor & I2P built in',
+ body: 'Serve your relay over .onion\nor I2P with a single flag',
+ },
+ {
+ logos: [],
+ rail: 'nostream',
+ title: 'Admin console',
+ body: 'Network health, NIP-66 probes\nand live relay settings',
+ },
+ ]
+ const fc = { w: 506, h: 200, gapX: 40, gapY: 34, y: 384 }
+ features.forEach(({ logos, rail, title, body }, i) => {
+ const col = i % 3
+ const row = Math.floor(i / 3)
+ const x = hx + col * (fc.w + fc.gapX)
+ const y = fc.y + row * (fc.h + fc.gapY)
+ const s = g0 + 34 + i * 8
+ const at = (yy: number, d = 4) => show(yy, { start: s + d, end: gEnd, dy: 16 })
+ card(x, y, fc.w, fc.h, s, gEnd)
+ add(
+ rect(x + 1, y, 0, 2, { background: PINK }),
+ at(y, 2).keys('width', [
+ [s + 6, 0],
+ [s + 30, fc.w - 2, 'power2.out'],
+ ]),
+ )
+ if (logos.length === 0) {
+ add(rect(x + 34, y + 38, 40, 40, { background: PINK, borderRadius: 2 }), at(y + 38))
+ }
+ logos.forEach((name, j) => add(img(name, x + 28 + j * 34, y + 32 + j * 22, j ? 32 : 52), at(y + 32 + j * 22)))
+ add(sans(title, x + 108, y + 36, 28, { letterSpacing: -0.6 }), at(y + 36, 6))
+ add(mono(body, x + 108, y + 88, 17, { fill: BODY, lineHeight: 28 }), at(y + 88, 8))
+ if (rail) {
+ light(rail, s, s + 46)
+ }
+ })
+ const cfgY = fc.y + 2 * fc.h + fc.gapY + 52
+ add(mono('→', hx, cfgY, 18, { fill: PINK, fontWeight: 700 }), show(cfgY, { start: g0 + 90, end: gEnd, dy: 10 }))
+ add(
+ mono('Every option lives in one file: .nostr/settings.yaml', hx + 36, cfgY, 18, { fill: FG }),
+ show(cfgY, { start: g0 + 92, end: gEnd, dy: 10 }),
+ )
+
+ // ── Scene 5 · Deploy ───────────────────────────────────────────────────
+ const d0 = DEPLOY.start
+ sceneLabel('04 — DEPLOY IN MINUTES', 196, d0 + 4, DURATION)
+ add(
+ sans('Spin up a relay.', hx - 5, 226, 96, { letterSpacing: -3.5, fontWeight: 800 }),
+ show(226, { start: d0 + 8, dy: 30, inF: 18 }),
+ )
+ add(
+ sans('Own your corner of Nostr.', hx - 5, 330, 96, { letterSpacing: -3.5, fontWeight: 800, fill: PINK }),
+ show(330, { start: d0 + 16, dy: 30, inF: 18 }),
+ )
+
+ tabs(486, d0 + 22, DURATION)
+ const term = { y: 524, w: 1000, h: 336 }
+ add(
+ rect(hx, term.y, term.w, term.h, { background: PANEL, borderColor: PANEL_BORDER, borderWidth: 1, borderRadius: 6 }),
+ show(term.y, { start: d0 + 24, dy: 14 }),
+ )
+ add(mono('$', hx + 28, term.y + 26, 23, { fill: PINK, fontWeight: 700 }), show(term.y + 26, { start: d0 + 30 }))
+ const cmd = 'nostream start'
+ add(
+ mono(cmd, hx + 62, term.y + 26, 23, { fill: FG }),
+ show(term.y + 26, { start: d0 + 32, inF: 2 }).typewriter(d0 + 34, d0 + 34 + cmd.length * 2, 'linear'),
+ )
+ copyButton(hx + term.w - 22, term.y + 22, d0 + 30, DURATION)
+ add(rect(hx + 1, term.y + 78, term.w - 2, 1, { background: LINE }), show(term.y + 78, { start: d0 + 30 }))
+
+ const boot: [string, string, string, string][] = [
+ ['postgresql', 'nostream-db', 'PostgreSQL ready', BODY],
+ ['redis', 'nostream-cache', 'Redis ready', BODY],
+ ['docker', 'nostream-migrate', 'migrations applied', BODY],
+ ['nostr', 'relay', 'listening on ws://0.0.0.0:8008', PINK],
+ ['lightning', 'payments', 'lnbits · lnurl · nwc (optional)', BODY],
+ ]
+ boot.forEach(([logo, name, detail, color], i) => {
+ const y = term.y + 102 + i * 44
+ const s = d0 + 66 + i * 10
+ add(img(logo, hx + 28, y - 2, 26), show(y, { start: s, inF: 6 }))
+ add(check(hx + 68, y + 5, 14), show(0, { start: s, inF: 6 }))
+ add(mono(name, hx + 98, y, 19, { fill: FG }), show(y, { start: s, inF: 6 }))
+ add(mono(detail, hx + 340, y, 19, { fill: color }), show(y, { start: s, inF: 6 }))
+ })
+ light('docker', d0 + 40, d0 + 100)
+
+ const built = [
+ 'typescript',
+ 'nodedotjs',
+ 'postgresql',
+ 'redis',
+ 'docker',
+ 'lightning',
+ 'bitcoin',
+ 'torproject',
+ 'nostr',
+ ]
+ add(mono('BUILT WITH', hx, 914, 13, { letterSpacing: 3, fill: MUTED }), show(914, { start: d0 + 40 }))
+ built.forEach((name, i) => {
+ const x = 340 + i * 66
+ add(img(`${name}-gray`, x, 902, 36), show(902, { start: d0 + 40 + i * 2, peak: 0.8 }))
+ add(img(name, x, 902, 36), show(902, { start: d0 + 112 + i * 4, inF: 10 }))
+ })
+
+ // ── Stack rail: 01–06, lit as each part of the stack is explained ─────
+ const allOn: Window = [d0 + 112, DURATION]
+ add(rect(71, 210, 1, 680, { background: LINE }), show(210, always))
+ ;(Object.keys(lit) as (keyof typeof lit)[]).forEach((name, i) => {
+ const y = 250 + i * 110
+ const windows = [...lit[name], allOn]
+ const on = () => LayerAnimation.create().keys('opacity', windowKeys(windows, 1, 8, 10))
+ add(rect(58, y - 8, 28, 54, { background: BG }), show(y, always))
+ if (name === 'nostream') {
+ add(rect(63, y + 3, 20, 20, { background: '#4a4a4a' }), show(y, always))
+ add(rect(63, y + 3, 20, 20, { background: PINK }), on())
+ } else {
+ add(img(`${name}-gray`, 59, y, 27), show(y, { ...always, peak: 0.7 }))
+ add(img(name, 59, y, 27), on())
+ }
+ add(mono(String(i + 1).padStart(2, '0'), 64, y + 32, 11, { fill: MUTED }), show(y, always))
+ add(rect(46, y, 3, 27, { background: PINK }), on())
+ })
+
+ // ── Finish ─────────────────────────────────────────────────────────────
+ film.add(Layer.image(asset('grain'), { position: 'absolute', x: 0, y: 0, width: W, height: H }) as never)
+ film.apply(new Vignette({ strength: 0.16, radius: 0.95, softness: 0.7 }))
+
+ return film
+}
diff --git a/website/hero-animation/src/render.ts b/website/hero-animation/src/render.ts
new file mode 100644
index 000000000..e03bb8170
--- /dev/null
+++ b/website/hero-animation/src/render.ts
@@ -0,0 +1,45 @@
+import fs from 'node:fs/promises'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
+import { HeadlessMediaRenderer, startPreview } from 'framefields'
+import { buildFilm } from './film.js'
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url))
+const OUTPUT = path.join(__dirname, '..', 'output')
+
+const [mode = 'video', ...args] = process.argv.slice(2)
+
+if (mode === 'preview') {
+ const session = await startPreview(
+ { entry: new URL('./film.ts', import.meta.url), export: 'buildFilm' },
+ { title: 'nostream hero', open: args.includes('--open') },
+ )
+ console.log(`\nPreview running at:\n ${session.url}\n`)
+ console.log('Leave the tab open while reviewing. Press Ctrl+C here to stop the server.\n')
+ await session.closed
+ process.exit(0)
+}
+
+const film = await buildFilm()
+await fs.mkdir(path.join(OUTPUT, 'frames'), { recursive: true })
+
+if (mode === 'frames') {
+ const renderer = new HeadlessMediaRenderer()
+ for (const frame of args.map(Number)) {
+ const file = path.join(OUTPUT, 'frames', `f${String(frame).padStart(4, '0')}.png`)
+ await fs.writeFile(file, await film.renderFrame({ frame, renderer }))
+ console.log(file)
+ }
+ process.exit(0)
+}
+
+const [quality = 'very_high'] = args
+console.log(`Rendering MP4 at ${quality} quality (requires GPU / WebGPU in Node)…`)
+const started = Date.now()
+const result = await film.renderVideo({
+ outputPath: path.join(OUTPUT, quality === 'very_high' ? 'nostream-hero.mp4' : `nostream-hero-${quality}.mp4`),
+ quality,
+})
+await result.cleanup?.().catch(() => {})
+console.log(`${result.filePath} (${((Date.now() - started) / 1000).toFixed(1)} s)`)
+process.exit(0)
diff --git a/website/hero-animation/src/theme.ts b/website/hero-animation/src/theme.ts
new file mode 100644
index 000000000..719b99bf8
--- /dev/null
+++ b/website/hero-animation/src/theme.ts
@@ -0,0 +1,24 @@
+export const W = 1920
+export const H = 1080
+export const FPS = 30
+export const DURATION = 1020
+
+/** omp.sh palette: near-black, graded whites, teal labels, hot pink accent */
+export const BG = '#08080a'
+export const FG = '#f7f7f7'
+export const FG_2 = '#a8a8a8'
+export const BODY = '#b0b0b0'
+export const MUTED = '#808080'
+export const TEAL = '#86b3ae'
+export const PINK = '#ff2e88'
+export const PINK_DIM = '#6a1a3f'
+export const LINE = '#26262a'
+export const WIRE = '#48484f'
+export const PANEL = '#0e0e11'
+export const PANEL_BORDER = '#2e2e33'
+
+export const FONT_MONO = 'JetBrainsMono'
+export const FONT_SANS = 'Inter'
+
+/** JetBrains Mono advance width is 600/1000 em */
+export const monoWidth = (text: string, size: number, spacing = 0) => text.length * (size * 0.6 + spacing)
diff --git a/website/hero-animation/tsconfig.json b/website/hero-animation/tsconfig.json
new file mode 100644
index 000000000..84bd433c5
--- /dev/null
+++ b/website/hero-animation/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "NodeNext",
+ "moduleResolution": "NodeNext",
+ "strict": true,
+ "skipLibCheck": true,
+ "noEmit": true,
+ "types": ["node"]
+ },
+ "include": ["src/**/*.ts"]
+}