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 @@ +Bitcoin \ 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 @@ +Docker \ 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 @@ +GitHub \ 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 @@ +Lightning \ 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 @@ +Node.js \ 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 @@ +PostgreSQL \ 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 @@ +Redis \ 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 @@ +Tor Project \ 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 @@ +TypeScript \ 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 = ` + + + + + + + + + + + + + + + + + + ${dots.join('')} +` + 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 = `${dots.join('')}` + 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 = `${dots.join('')}` + 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"] +}