feat(remotion): asset-library catalog + Phase 0 (license firewall, @remotion/lottie, 30 CC0 characters)

- docs/ASSET_LIBRARY.md: curated catalog from the asset sweep (91 sources -> 62
  usable) + completeness-critic reality check; clean CC0/MIT tier, license/geo
  traps, and the 2.5D layered-scene plan (sky->room->furniture->device->character
  ->grain) to fix the "naked scene".
- deps: add @remotion/lottie@4.0.290 (runtime) + DiceBear (build-time devDep).
- scripts/gen-dicebear.mjs: generate 30 CC0 Open-Peeps characters OFFLINE (no
  runtime CDN) into public/illustrations/dicebear/ + a per-file assets.json ledger.
- scripts/check-assets.mjs: license-firewall CI guard — fails on any un-ledgered
  vendored asset.
- AssetSheet dev composition: proves vendored SVG -> staticFile() -> Remotion render
  (30 real characters render cleanly).
- NOTE: GitHub (Open Peeps/IRA/Notion git clones) + Gumroad (Lukasz) are geo-blocked
  headless here; those + Humaaans (Figma export) need a manual/mirror fetch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-22 18:59:03 +03:30
parent a3152ee84f
commit cb6512fee3
38 changed files with 1298 additions and 1 deletions
+271
View File
@@ -0,0 +1,271 @@
# FlatRender — Curated Asset Library Catalog
> Generated by the `flatrender-asset-sweep` workflow (91 sources surveyed → 62 usable
> after adversarial license/Iran-access verification) + a completeness-critic pass.
> **Read §12 (Reality Check) before treating any count as "done".** Nothing here is
> vendored yet — this is an *acquisition + build plan*, not a shipped library.
**Reference look:** Google "Alegria" / Pablo-Stanley flat editorial illustration — bold flat colour-blocking (23 shades per shape), confident hand-drawn line, expressive hands & foreshortening, characters set in **rich scenes** (rooms, furniture, devices, sky), subtle grain.
**Render stack (verified in repo):** Remotion `4.0.290` + `@remotion/three` `4.0.290` (installed). **`@remotion/lottie` is NOT yet a dependency** in `services/remotion/package.json` — it must be added (MIT, Nexus-mirrorable) before any Lottie path works.
**Stack rule (standing):** every template animation is built on **Remotion + Three.js together** (`@remotion/three`). Flat-illustration art is composited as **2.5D** — 2D layers placed on planes inside a Three.js scene for parallax camera, depth, lighting and 3D particles. Animate only off `useCurrentFrame()`.
**Vendoring target (verified):** `services/remotion/public/` exists and currently holds only `fonts/` (Vazirmatn 400900 woff2). The project already serves vendored files via `staticFile()` (see `src/lib/fonts.ts` and `AppShowcase3D.tsx`). All asset categories below land under `public/illustrations/...` and `public/lottie/...`.
**The "naked scene" problem (verified):** `src/compositions/CharacterStory.tsx` draws a single hand-coded SVG figure standing on an abstract blurred blob inside a gradient — no room, no furniture, no real sky, no props. The catalog and integration plan below exist specifically to fix this with a layered, asset-driven scene system.
---
## 0. Totals (claimed — see §12 for the honest correction)
| Bucket | Claimed usable count |
|---|---|
| **Characters — riggable (separable parts)** | ~110 base components / poses (Humaaans ~100 + Open Peeps part-set) |
| Characters — Lottie/animated (self-authored + curated CC0) | ~5080 loops (mostly self-authored) |
| Scenes & interiors (recolorable, vendor curated subset) | ~300+ curated (from unDraw/ManyPixels/Lukasz pools of 4,000+) |
| Devices / props | ~150 |
| Sky / nature / abstract | ~80 |
| Grain / texture | ~10 |
| Persian / seasonal | ~40 (self-authored) |
> ⚠️ The critic's verdict: this "≥100 characters" is **permutation-inflated**. The defensible
> count of distinct, named, ledgered figures from the firewall-clean CC0/MIT tier is **~5060**,
> and **0 are riggable today** (all need manual `<g>`-splitting). §12 gives a concrete plan to a
> real, enumerated 100-figure roster.
---
## 1. Characters — Riggable (separable SVG parts) ⭐ core tier
Individual `<g>` body parts can be driven by `useCurrentFrame()` / `interpolate()` — true per-limb rigging, not just whole-figure parallax.
### 1.1 Humaaans (Pablo Stanley) — FLAGSHIP
- **License:** CC0 1.0 Public Domain (commercial OK, no attribution, redistribution/vendoring OK).
- **Iran-access:** acquisition **yes** (humaaans.com / Figma Community / archive.org). **Do NOT use Gumroad.** Render-time is CDN-free once vendored.
- **Animation-fit:** riggable-parts — **realistically 78/10**. Parts are separable as *components in the Figma/Sketch source*; a flat one-shot SVG export flattens groups, so you must export each body part as its own `<g>` first.
- **Count:** ~100 base components (≈12 base poses + dozens of heads/hair/tops/bottoms/shoes + objects).
- **Style-match:** **9/10** — THE flagship Alegria reference.
- **Download:** Figma Community file `1200350623263197299` → export per-part SVGs; or humaaans.com; `archive.org/details/humaaans` fallback.
- **Use-cases:** full-body hero rigging; per-limb wave/present/think/celebrate poses (replaces the hand-coded `Pose` system in `CharacterStory.tsx`).
### 1.2 Open Peeps (Pablo Stanley) — secondary
- **License:** CC0 1.0 (no attribution, redistribution OK).
- **Iran-access:** acquisition yes. **Animation-fit:** riggable-parts — `github.com/opeepsfun/open-peeps` exposes separated body/head/face/beard/accessory SVGs (npm wrapper is MIT; **vendor the raw CC0 SVGs, not the wrapper**).
- **Count:** ~200 building-block parts. **Style-match 7/10** (confident hand-drawn line; lighter colour-blocking). Character-only — pair with §3 scenes.
- ⚠️ **Do NOT confuse with Avataaars** (same artist, NOT CC0).
### 1.3 DiceBear (programmatic, offline) — build-time avatars
- **License:** core MIT; per-style — `open-peeps`=CC0, `avataaars`=free-commercial custom, `big-smile`/`adventurer`/`croodles`=**CC BY 4.0 (attribution REQUIRED)**.
- **Iran-access:** **yes**`@dicebear/core` + `@dicebear/collection` run **fully offline via npm** (Nexus mirror). `createAvatar(style,{seed}).toString()` → raw SVG, no runtime CDN. Do NOT call the dicebear HTTP API at render.
- **Animation-fit:** component-swap + transforms (blink/expression micro-rigs), not full skeletal rig. Bust→half-body scale.
- **Count:** ~45 styles. **Style-match 6/10.** Use `idRandomization` to avoid SVG id collisions.
### 1.4 IRA Design (Creative Tim) — selective gradient style
- **License:** **MIT** (`ira-design/ira-illustrations`). **Retain LICENSE.md** alongside vendored SVGs (no on-screen credit needed).
- **Iran-access:** yes (`git clone`). **Animation-fit:** moderate — monolithic per-illustration files, manual `<g>` split needed; gradient fills complicate flat parallax.
- **Count:** ~100 vendorable SVGs (36 characters + 52 objects + backgrounds). **Style-match 6/10** (gradient sketch, not strict flat-block).
### 1.5 Avataaars (Pablo Stanley) — ⚠️ caution
- **License:** code ports MIT, but **artwork is custom "free for personal & commercial use", NOT MIT/CC0** — no redistribution "in a pack", no "use to create a competing service" (a live risk for an avatar SaaS).
- **Verdict:** talking-head micro-animation **only** (via DiceBear `avataaars` offline npm); document as "permissive-with-restrictions"; do NOT build a user-facing avatar generator on it.
---
## 2. Characters — Lottie / Animated
> **Strategic note:** every third-party Lottie library carries a redistribution and/or
> competing-service clause that collides with a paid template SaaS vendoring assets into
> `public/`. **Primary path = SELF-AUTHOR brand Lottie loops** from the CC0 SVGs in §1/§3
> and drive them with `@remotion/lottie`.
- **2.1 LottieFiles (free/community)** — ⚠️ Simple License (commercial OK) but **no "compile files to develop a competing service"**, no standalone JSON redistribution; per-file Free/Premium badge. Iran-access unknown (login-gated). Use only inside a final render, Free badge re-verified.
- **2.2 Lottielab** — ⚠️ free tier **watermarks all exports**; value is as an **authoring tool** on cleanly-licensed SVGs (Pro ~$12/mo for watermark-free).
- **2.3 LottieFiles creators/marketplace** — ⚠️ per-item; only **Free**-badged vendorable; Premium forbids redistribution even after purchase. Style-match up to 8/10 for the right creator.
---
## 3. Scenes & Interiors (recolorable flat backdrops — fixes the "naked scene")
> **The category that directly cures `CharacterStory.tsx`** — flat scene SVGs (rooms, desks, cities, devices, sky) used as BACKGROUND/MID-GROUND layers behind the riggable §1 characters; animation is whole-scene parallax/recolor.
- **3.1 unDraw** — ⚠️ **NOT MIT** (stale label) → custom proprietary (free commercial, no attribution) that **forbids replicating a competing service, distributing "in packs", scraping, AND AI/ML training**. ~1,300 scenes. **Style-match 23/10.** Hand-pick only (no bulk scrape), bake into renders, log `license="unDraw custom (proprietary)"`.
- **3.2 ManyPixels** — ⚠️ custom permissive but forbids "distribute in packs", being the product's center, AND AI-training. 2,500+. Iran-access yes (Iconduck/`quentincaffeino/manypixels-illustrations` mirror). **Style-match 4/10.** Curated subset baked in; never an in-product picker.
- **3.3 Lukasz Adam** — ⚠️ **CC0/MIT** (cleanest scene tier) but **acquisition NO via official Gumroad** (sanctioned) → Eagle/aggregator mirror or non-Iran box. **229 confirmed** (characters, remote-work, cities, devices). The free-static scene anchor.
- **3.4 Storyset (Freepik)** — ⚠️ **reference only** — resale/database clause + visible Freepik credit; Iran-access **NO** (OFAC checkout). Only attractive property: exports Lottie JSON.
- **3.5 DrawKit** — ⚠️ freemium proprietary, forbids standalone redistribution + AI-training, revocable terms. Use only if repo private + users get rendered output only.
- **3.6 Black Illustrations** — ⚠️ proprietary; forbids serving raw SVG / end-user extraction → **bake into MP4 only**. ~120 free. Valuable inclusive-character supplement.
- **3.7 Ouch! by Icons8** — ⚠️ **reference only** — forbids distributing as "stand-alone files" (conflicts with `public/`+`staticFile()`); Iran-access NO.
---
## 4. Devices / Props
- **Humaaans objects** (CC0) — laptops/phones/plants/chairs/signs, separable mid-ground props.
- **IRA Design objects** (MIT) — 52 objects; split manually.
- **Lukasz Adam "Devices"** (CC0) — phones/laptops/monitors; pairs with the `AppShowcase3D.tsx` `screenUrl` + `staticFile()` pattern.
- **Self-authored SVG props** — drawn inline in the same flat style (the repo already proves this works).
## 5. Sky / Nature / Abstract
- **Self-authored gradients & shapes** (CSS/SVG, already used in `CharacterStory.tsx`) — cleanest license-free sky/abstract layer; drift off `useCurrentFrame()`.
- **unDraw/ManyPixels nature** (curated, recolored).
- **Three.js abstract** via `@remotion/three` (installed) for parallax sky/particles depth.
## 6. Grain / Texture
- **Inline `feTurbulence` SVG noise** — ALREADY in `CharacterStory.tsx` (`mixBlendMode: overlay`, opacity 0.05). Keep as the standard grain.
- **CC0 paper/noise tiles** — vendor 510 (ambientCG / cc0textures, CC0).
## 7. Persian / Seasonal
- **Self-authored Nowruz (haft-sin, sabzeh, goldfish, sonbol), Yalda (pomegranate, watermelon, Hafez), Ramadan (crescent, fanoos)** flat SVG motifs — the repo already ships `Nowruz3D.tsx` / `NowruzGreeting.tsx`.
- **Recolored CC0 scenes** (Humaaans + Lukasz) dressed with seasonal props. Vazirmatn RTL already vendored.
---
## 8. DOWNLOAD MANIFEST
All assets land under `services/remotion/public/`. npm via the **Nexus mirror** (`mirror.soroushasadi.com`). For Gumroad/Freepik/Icons8/Cloudflare-gated sources, acquire from a **non-Iran box / VPN once**, then commit — render-time is CDN-free.
```bash
# 0. Folders
mkdir -p services/remotion/public/illustrations/{humaaans,openpeeps,undraw,manypixels,lukaszadam,ira,notion}
mkdir -p services/remotion/public/lottie
# 1. Humaaans (CC0) — FLAGSHIP. Figma community 1200350623263197299 → export each
# body-part as its own <g> SVG → public/illustrations/humaaans/ (NOT Gumroad)
# 2. Open Peeps (CC0) — pre-separated parts:
git clone https://github.com/opeepsfun/open-peeps # vendor raw CC0 SVGs, not the wrapper
# 3. DiceBear (offline, no runtime CDN) — generate at BUILD time:
npm i @dicebear/core @dicebear/collection # via Nexus mirror
# createAvatar(openPeeps,{seed}).toString() → write SVG to public/
# 4. IRA Design (MIT) — keep LICENSE.md alongside:
git clone https://github.com/ira-design/ira-illustrations
# 5. Lukasz Adam (CC0, 229) — Eagle/aggregator mirror or non-Iran box (Gumroad sanctioned)
# 6. unDraw / ManyPixels — HAND-PICK only (no bulk scrape); curated subset
# 7. Notion Avatars (CC0) — talking-head busts:
git clone https://github.com/notion-avatar/notion-avatar
# 8. Lottie pipeline (must add the package first):
npm i @remotion/lottie # via Nexus — currently MISSING
```
**Iran-mirror flags:** Gumroad (Lukasz/Open Peeps/Black Illustrations) = sanctioned. Freepik/Storyset + Icons8 = OFAC-blocked. Cloudflare (DrawKit/ManyPixels) = intermittent. All npm = Nexus mirror.
---
## 9. INTEGRATION PLAN
### 9.1 License firewall — `public/illustrations/assets.json`
Every vendored file gets one ledger row. **No row → asset may not ship.**
```jsonc
{
"humaaans/torso-a.svg": { "source": "Humaaans", "license": "CC0-1.0",
"license_class": "CC0", "commercial_ok": true, "attribution_required": false,
"ai_training_allowed": true, "url": "humaaans.com", "vendored": "2026-06-22" },
"ira/scene-desk.svg": { "source": "IRA Design", "license": "MIT",
"attribution_required": true, "notice_file": "ira/LICENSE.md", "ai_training_allowed": true },
"undraw/working.svg": { "source": "unDraw", "license": "unDraw custom (proprietary)",
"commercial_ok": true, "attribution_required": false,
"ai_training_allowed": false, "redistribute_as_pack": false }
}
```
Hard rules: **(1)** only CC0/MIT/PD vendored as raw `public/` SVG; **(2)** custom-restrictive (unDraw/ManyPixels/DrawKit/Black Illustrations) only as a **curated subset baked into renders**, `ai_training_allowed:false` (excluded from FlatRender's future AI-video pipeline); **(3)** Blush/Storyset/Icons8/Avataaars-pack = **never vendored**, reference only.
### 9.2 Rigging an SVG kit in Remotion (`useCurrentFrame()`-driven `<g>` parts)
The repo already proves the pattern in `CharacterStory.tsx` (`arm(ang,sign)` / `leg(sign,ang)` rotate `<g>` groups by frame-derived angles). Replace the hand-drawn paths with vendored Humaaans part SVGs, keep the rig math:
```tsx
const f = useCurrentFrame();
const wave = pose === "wave" ? 130 + Math.sin(f / 4) * 14 : 12;
<g transform={`translate(${shoulderX} ${shoulderY}) rotate(${wave})`}>
<UpperArm /> {/* vendored SVG <g>, pivot at shoulder */}
<g transform={`translate(0 ${forearmY}) rotate(${elbow})`}><Forearm /></g>
</g>
```
Keep conventions: `staticFile()` for raster, stable `rand(seed)` (not `Math.random`), per-instance `id()` prefixes (`cs${idn}_`) to avoid `<defs>` id collisions, `L.vmin()/L.pick()` for aspect re-flow.
### 9.3 `@remotion/lottie` (after `npm i @remotion/lottie`)
Vendor JSON under `public/lottie/`; never a CDN/HTTP API at render. Use for self-authored idle/confetti/sky loops; firewall third-party Premium JSON out.
### 9.4 LAYER scenes for depth — the cure for "naked scene" (do this in the Three.js 2.5D scene)
Stack layers back-to-front, each on its own plane / parallax speed off `useCurrentFrame()`:
```
Layer 0 SKY gradient / Three.js abstract (slow drift, far plane)
Layer 1 ROOM vendored interior SVG (wall, window) (slow)
Layer 2 FURNITURE desk / shelf / plant props (medium)
Layer 3 DEVICE laptop/phone (staticFile, AppShowcase pattern)
Layer 4 CHARACTER riggable Humaaans <g> rig (foreground, full motion)
Layer 5 GRAIN feTurbulence overlay (already in repo, opacity 0.05) + vignette
```
Parallax = depth factor (`0.1 … 1.0`) on each layer's translate / z-position. Direct upgrade of `CharacterStory.tsx`: "blob + particles + character" → "sky → room → furniture → device → character → grain".
### 9.5 Recolor to brand palette
Map `colorSchema` (`accentColor`/`secondaryColor`/`backgroundColor`/`textColor`) onto each scene SVG's fills. unDraw exposes one accent var; multi-shade SVGs (Lukasz/ManyPixels/Humaaans) need a small fill-remap at vendor time (`mixHex()` from `src/lib/anim.ts`).
---
## 10. PHASED BUILD PLAN (to Alegria quality)
- **Phase 0 — Plumbing (½ day).** `public/illustrations/*` + `public/lottie/`; `assets.json` ledger + a CI check that fails on un-ledgered files; `npm i @remotion/lottie`.
- **Phase 1 — Flagship character rig (23 days).** Vendor Humaaans parts; build `<RiggedCharacter>` (separable `<g>` arms/legs/head, pose presets) reusing the `CharacterStory.tsx` pose math.
- **Phase 2 — Layered scene system (23 days).** `<SceneStage>` composing sky→room→furniture→device→grain with parallax inside the Three.js scene. Vendor a curated ~30-scene set (Lukasz CC0 first, then hand-picked unDraw/ManyPixels). Rebuild `CharacterStory.tsx`'s 13 beats with real environments.
- **Phase 3 — Recolor + brand binding (12 days).** Wire `colorSchema` → scene fills; verify studio colour controls recolor the whole stack.
- **Phase 4 — Lottie & motion polish (2 days).** Self-author 810 idle/wave/confetti/sky loops; integrate via `@remotion/lottie`; per-limb secondary motion + foreshortening on the hero (expressive hands).
- **Phase 5 — Persian/seasonal pack (12 days).** Nowruz/Yalda/Ramadan prop sets on the layered scenes (reuse `Nowruz3D`).
- **Phase 6 — QA & firewall audit (1 day).** Render 3 aspects; confirm determinism, no runtime CDN, full `assets.json` coverage, AI-prohibited assets flagged out.
---
## 11. Verdict summary
| Source | Verdict | Vendor raw? | License | Iran (acquire) | Anim-fit | Style |
|---|---|---|---|---|---|---|
| Humaaans | **USE** ⭐ | ✅ | CC0 | yes | rig 78 | 9 |
| Open Peeps | **USE** | ✅ | CC0 | yes | rig | 7 |
| DiceBear (CC0/free) | **USE** | ✅ gen→commit | MIT+CC0 | yes (offline npm) | swap/transform | 6 |
| IRA Design | **USE** (selective) | ✅ +LICENSE.md | MIT | yes | moderate | 6 |
| Lukasz Adam | **USE** (mirror) | ✅ | CC0 | acquire NO (Gumroad) | single-svg | 4 |
| Notion Avatars | **USE** (add) | ✅ | CC0 | yes | bust micro-rig | 6 |
| unDraw | caution | curated/baked | custom-restrictive | unknown | single-svg | 23 |
| ManyPixels | caution | curated/baked | custom-permissive | yes | single-svg | 4 |
| Black Illustrations | caution | bake-into-MP4 | proprietary | unknown | single-svg | 4 |
| Avataaars (art) | caution | gen→commit, no pack | custom-permissive | yes | bust micro-rig | 6 |
| LottieFiles (free) | caution | inside-render only | Simple License | unknown | lottie | 56 |
| DrawKit | caution | private repo only | freemium-proprietary | unknown | lottie | 4 |
| Storyset | **reference only** | ❌ | resale-ban | NO | lottie | 5 |
| Ouch!/Icons8 | **reference only** | ❌ | no stand-alone files | NO | lottie | 5 |
| Blush | **reference only** | ❌ | proprietary | NO (Stripe) | rig (paid) | 9 |
**Bottom line:** Build the production library on the clean, vendorable CC0/MIT tier — **Humaaans, Open Peeps, DiceBear-CC0, IRA, Notion Avatars** + **Lukasz Adam** scenes; supplement scenery with **curated, baked-in** unDraw/ManyPixels; **self-author** Lottie loops; keep the encumbered/Iran-blocked sources (Blush/Storyset/Icons8/DrawKit) as **design-time reference only**.
---
## 12. ⚠️ Reality Check (completeness critic) — READ THIS
The "≥100 characters" in §0 does **not** survive scrutiny. Honest status and the fix:
**Gaps:**
1. **Count is permutation-inflated.** "Humaaans ~100 components" = interchangeable heads/hair/tops/bottoms, not 100 figures. Defensible distinct, named, firewall-clean figures ≈ **12 Humaaans poses + 36 IRA + a few DiceBear seeds ≈ 5060** — and those need manual `<g>`-splitting before riggable.
2. **Nothing is vendored yet.** `public/` contains only `fonts/`. `public/illustrations/` + `public/lottie/` don't exist. The catalog is an **acquisition plan**, not a library. **Riggable-today count = 0.**
3. **Riggability overstated.** A one-shot Humaaans SVG export flattens groups → until per-part `<g>` export + pivot authoring is done, it's single-figure parallax (same tier as unDraw).
4. **Diversity thin & encumbered.** The only dedicated inclusive source (Black Illustrations) is bake-into-MP4-only. No source covers older adults, disability, hijab/chador, or Persian dress beyond self-authored props.
5. **`@remotion/lottie` is a hard blocker** (absent from `package.json`) — every Lottie path (§2) is non-functional until added; don't count those ~5080 loops yet.
6. **Self-authored assets counted as if they exist** (Lottie ~5080, seasonal ~40, sky ~80, props ~150 are future work).
**Additions / the concrete fix — enumerate a real 100-figure roster (each an actual committed file + `assets.json` row):**
- **~30** Humaaans base poses × authored top/bottom/skin variants you actually create & ledger.
- **36** IRA Design character SVGs (MIT, `github.com/ira-design/ira-illustrations`).
- **30** DiceBear `open-peeps` (CC0) fixed seeds, generated + committed.
- **~10** Open Peeps part assemblies. → **~106 real, named, ledgered figures.**
- **+ Notion Avatars** (CC0, `github.com/notion-avatar/notion-avatar`) — ~1015 talking-head busts, firewall-clean, Iran-accessible. Fills the talking-head gap without Avataaars' restrictions.
- **+ Croodles / Adventurer** (DiceBear, **CC BY 4.0 — attribution ledger row**) — ~20 doodle-line seeds.
- **+ OpenMoji** people/gestures (CC BY-SA 4.0, `github.com/hfg-gmuend/openmoji`) — expressive hands/heads for secondary characters; curated subset + share-alike tracking.
- **Diversity pass:** author hijab/scarf head `<g>` overlays, older-adult variants, varied skin tones via `mixHex()` recolor on the **CC0 Humaaans base** (firewall-clean derivative; directly serves the Persian-first product).
- **Grain:** vendor 35 CC0 paper/noise tiles from **ambientCG** (CC0, GitHub/CDN-mirrorable).
- **`@remotion/lottie`** added in Phase 0 *before* any Lottie asset counts as usable.
- **Sky:** add Lukasz Adam CC0 nature SVGs + 23 self-authored gradient-sky presets bound to `colorSchema`.
**So the true near-term deliverable** is not "100 characters downloaded" but: Phase 0 plumbing + a vendored, ledgered **~3040 character starter set** (Humaaans + IRA + DiceBear + Notion) split into riggable `<g>` parts, plus a `<SceneStage>` layered-scene system — enough to rebuild `CharacterStory` to the Alegria bar — then grow the roster to 100+ on that proven pipeline.
+486
View File
@@ -12,6 +12,7 @@
"@react-three/fiber": "^9.1.2",
"@react-three/postprocessing": "^3.0.4",
"@remotion/cli": "4.0.290",
"@remotion/lottie": "^4.0.290",
"@remotion/three": "^4.0.290",
"@remotion/zod-types": "4.0.290",
"@types/three": "^0.171.0",
@@ -23,6 +24,8 @@
"zod": "3.22.3"
},
"devDependencies": {
"@dicebear/collection": "^9.4.2",
"@dicebear/core": "^9.4.2",
"@types/react": "19.0.0",
"typescript": "5.5.4"
}
@@ -48,6 +51,468 @@
"node": ">=6.9.0"
}
},
"node_modules/@dicebear/adventurer": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/adventurer/-/adventurer-9.4.2.tgz",
"integrity": "sha512-jqYp834ZmGDA9HBBDQAdgF1O2UTCwHF4vVrktXWa2Dppp1JczPL5HnVOWsjtrLmXNn61Wd6OLmBb2e6rhzp3ig==",
"dev": true,
"license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/adventurer-neutral": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/adventurer-neutral/-/adventurer-neutral-9.4.2.tgz",
"integrity": "sha512-5xgkG/mNL4j3Q4SJGQLBU/KnU90tng8Ze5ofThD+55wi0oeY/nSAUowg6UFCmHrktjifj/MEx3CQqbpcPWtfIA==",
"dev": true,
"license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/avataaars": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/avataaars/-/avataaars-9.4.2.tgz",
"integrity": "sha512-3x9jKFkOkFSPmpTbt9xvhiU2E1GX7beCSsX0tXRUShj8x6+5Ks9yBRT1VlkySbnXrZ/GglADGg7vJ/D2uIx1Yw==",
"dev": true,
"license": "See LICENSE file",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/avataaars-neutral": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/avataaars-neutral/-/avataaars-neutral-9.4.2.tgz",
"integrity": "sha512-/eNrp0YCNJRwQXqOloLm1+3Ss2C+pMpUQIGkbEnGsP1UK+13Ge80ggDDof1HpdqvG9HAZcKa7hnbG/0HSwyDSw==",
"dev": true,
"license": "See LICENSE file",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/big-ears": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/big-ears/-/big-ears-9.4.2.tgz",
"integrity": "sha512-mNfz3ppNA7UBq0IO3nXCiV5pFPG7c1DfzRB0foNU2Wo1XXT8FIcSY2BvDlYqorZTOUOz7dHb0vx06hqvG0HP5w==",
"dev": true,
"license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/big-ears-neutral": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/big-ears-neutral/-/big-ears-neutral-9.4.2.tgz",
"integrity": "sha512-M8Ozmzza4eY4hpLOYULgJxMYmBA0CsBnrE15/xw6LZkEREXnrX5z0NJsf8hUfdyF6BWZ+RBgzoiav32DAC5zcg==",
"dev": true,
"license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/big-smile": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/big-smile/-/big-smile-9.4.2.tgz",
"integrity": "sha512-hmT5i7rcPPhStjZyg28pbIhdTnnMBzK3RObI0vKCpY30EFrzaPkkdDL6Ck5fAFBdvDIW1EpOJkenyR0XPmhgbQ==",
"dev": true,
"license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/bottts": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/bottts/-/bottts-9.4.2.tgz",
"integrity": "sha512-tsx+dII7EFUCVA8URj66G1GqORCCVduCAx4dY2prEY2IeFianVpkntXuFsWZ9BBGx1NZFndvDith5oTwKMQPbQ==",
"dev": true,
"license": "See LICENSE file",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/bottts-neutral": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/bottts-neutral/-/bottts-neutral-9.4.2.tgz",
"integrity": "sha512-kFNwWt6j+gzZ5n5Pz7WVwePubREAQOF8ZwWA9ztwVYDVMLnOChWbAofy5FED4j5md2MXFH2EgLCFCMr5K2BmIA==",
"dev": true,
"license": "See LICENSE file",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/collection": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/collection/-/collection-9.4.2.tgz",
"integrity": "sha512-KArubv7if8H7j9sIfpDK2hJJqrdNVR5zMPAMOSpIU2JPyXx8TC9o5wsmXb8il5wOHgaS9Q/cla7jUNIiDD7Gsg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@dicebear/adventurer": "9.4.2",
"@dicebear/adventurer-neutral": "9.4.2",
"@dicebear/avataaars": "9.4.2",
"@dicebear/avataaars-neutral": "9.4.2",
"@dicebear/big-ears": "9.4.2",
"@dicebear/big-ears-neutral": "9.4.2",
"@dicebear/big-smile": "9.4.2",
"@dicebear/bottts": "9.4.2",
"@dicebear/bottts-neutral": "9.4.2",
"@dicebear/croodles": "9.4.2",
"@dicebear/croodles-neutral": "9.4.2",
"@dicebear/dylan": "9.4.2",
"@dicebear/fun-emoji": "9.4.2",
"@dicebear/glass": "9.4.2",
"@dicebear/icons": "9.4.2",
"@dicebear/identicon": "9.4.2",
"@dicebear/initials": "9.4.2",
"@dicebear/lorelei": "9.4.2",
"@dicebear/lorelei-neutral": "9.4.2",
"@dicebear/micah": "9.4.2",
"@dicebear/miniavs": "9.4.2",
"@dicebear/notionists": "9.4.2",
"@dicebear/notionists-neutral": "9.4.2",
"@dicebear/open-peeps": "9.4.2",
"@dicebear/personas": "9.4.2",
"@dicebear/pixel-art": "9.4.2",
"@dicebear/pixel-art-neutral": "9.4.2",
"@dicebear/rings": "9.4.2",
"@dicebear/shapes": "9.4.2",
"@dicebear/thumbs": "9.4.2",
"@dicebear/toon-head": "9.4.2"
},
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/core": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/core/-/core-9.4.2.tgz",
"integrity": "sha512-MF0042+Z3s8PGZKZLySfhft28bUa3B1iq0e5NSjCvY8gfMi5aIH/iRJGRJa1N9Jz1BNkxYb4yvJ/N9KO8Z6Y+w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/json-schema": "^7.0.15"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@dicebear/croodles": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/croodles/-/croodles-9.4.2.tgz",
"integrity": "sha512-6VoO0JviIf7dKKMBTL/SMXxWhnXHaZuzufX90G0nXxS77ELG1YkGNMaZzawizN4C09Gbya2gJkozqrWiJN/aGw==",
"dev": true,
"license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/croodles-neutral": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/croodles-neutral/-/croodles-neutral-9.4.2.tgz",
"integrity": "sha512-oG5IeUdtiYshQ89gkAVcl5w3xAEi5UZX2fTzIyelpBPCG176l7VuuFzlxi2umnB3E6LVHYy06DXvUo/p+rXB2Q==",
"dev": true,
"license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/dylan": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/dylan/-/dylan-9.4.2.tgz",
"integrity": "sha512-1vQvRu9x9DrwFxhFaIU2rf0EUL04yDTbAt7fHyAjM0mEsKzTD4mRNf95tCRuavCoW6W48u7A/OY6jyIub6kxLQ==",
"dev": true,
"license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/fun-emoji": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/fun-emoji/-/fun-emoji-9.4.2.tgz",
"integrity": "sha512-kqB6LPkdYCdEU/mwbyz34xLzoNUKL6ARcoo3fr5ASq9D6ZE07qIKybC3xv5+CPz7VmspJ1Q3c/VVWVMDRP7Twg==",
"dev": true,
"license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/glass": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/glass/-/glass-9.4.2.tgz",
"integrity": "sha512-z5qUogHQ1b6UJ2zCqT848mU2U9DKbVDhiX6GPDjD7tYLisCCJVisH9p6WyNdHvflUd4SHkA6gRqVJIh2v2HnTA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/icons": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/icons/-/icons-9.4.2.tgz",
"integrity": "sha512-QSMMz0NA03ypSGhXC8HQX8FSj8lYT+/5yqH+/N03OH2IjL0q7wwGZ7nqsrtlRp76O5WqMTwGfSbTUUYPjFr+Xw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/identicon": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/identicon/-/identicon-9.4.2.tgz",
"integrity": "sha512-JVDSmZsv11mSWqwAktK5x9Bslht2xY3TFUn8xzu6slAYe1Z7hEXZ76eb+UJ6F4qEzdwZ7xPWzAS6Nb0Y3A0pww==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/initials": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/initials/-/initials-9.4.2.tgz",
"integrity": "sha512-yePuIUasmwtl9IrtB6rEzE/zb5fImKP/neW0CdcTC2MwLgMuP1GLHEGRgg1zI8exIh+PMv1YdLGyyUuRTE2Qpw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/lorelei": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/lorelei/-/lorelei-9.4.2.tgz",
"integrity": "sha512-YMv6vnriW6VLFDsreKuOnUFFno6SRe7+7X7R7zPY0rZ+MaHX9V3jcioIG+1PSjIHEDfOLUHpr5vd1JBWv8y7UA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/lorelei-neutral": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/lorelei-neutral/-/lorelei-neutral-9.4.2.tgz",
"integrity": "sha512-yspanTthA5vh6iCdeLzn6xZ4yYMYRcfcxblcgSvHTF1ut0bjAXtw5SXzZ6aJTrJWiHkzYOQuTOR6GVYiW80Q7w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/micah": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/micah/-/micah-9.4.2.tgz",
"integrity": "sha512-e4D3W/OlChSsLo7Llwsy0J18vk0azJqF/uFoY+EKACCNHBc1HGNsqVvu2CTf+OWOA8wTyAK6UkjBN5p01r7D+g==",
"dev": true,
"license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/miniavs": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/miniavs/-/miniavs-9.4.2.tgz",
"integrity": "sha512-wLwyFNNUnDRd3BbhSBhXR0XEpX8sG0/xDA5M/OkDoapLqZnnI48YLUSDd2N5QTAVMmcSEuZOYxkcnj7WW79vlg==",
"dev": true,
"license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/notionists": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/notionists/-/notionists-9.4.2.tgz",
"integrity": "sha512-ZCySq+nxcD/x4xyYgytcj2N9uY3gxrL+qpnmOdp2BdA221KacVrxlsUPpIgEMqxS2rMmBQXfxg129Pzn4ycIpA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/notionists-neutral": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/notionists-neutral/-/notionists-neutral-9.4.2.tgz",
"integrity": "sha512-AyD9kEfVxQUwDGf4Op059gVmYIOAkTKg3dtE9h9mEKP7zl/kMy5B67BFFOo7sB0mXCjzAegZ6ekGU02E8+hIHw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/open-peeps": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/open-peeps/-/open-peeps-9.4.2.tgz",
"integrity": "sha512-i01tLgtp2g937T81sVeAOVlqsCtiTck/Kw20g7hN80+7xrXjOUepz2HPLy3HeiMjwjMGRy5o54kSd0/8Ht4Dqg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/personas": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/personas/-/personas-9.4.2.tgz",
"integrity": "sha512-NJlkvI5F5gugt6t2+7QrYNTwQC7+4IQZS3vG0dYk2BncxOHax0BuLovdSdiAesTL4ZkytFYIydWmKmV2/xcUwg==",
"dev": true,
"license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/pixel-art": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/pixel-art/-/pixel-art-9.4.2.tgz",
"integrity": "sha512-peHf7oKICDgBZ8dUyj+txPnS7VZEWgvKE+xW4mNQqBt6dYZIjmva2shOVHn0b1JU+FDxMx3uIkWVixKdUq4WGg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/pixel-art-neutral": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/pixel-art-neutral/-/pixel-art-neutral-9.4.2.tgz",
"integrity": "sha512-9e9Lz554uQvWaXV2P17ss+hPa6rTyuAKBtB8zk8ECjHiZzIl61N/KcTVLZ4dILVZwj7gYriaLo16QEqvL2GJCg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/rings": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/rings/-/rings-9.4.2.tgz",
"integrity": "sha512-Pc3ymWrRDQPJFNrbbLt7RJrzGvUuuxUiDkrfLhoVE+B6mZWEL1PC78DPbS1yUWYLErJOpJuM2GSwXmTbVjWf+g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/shapes": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/shapes/-/shapes-9.4.2.tgz",
"integrity": "sha512-AFL6jAaiLztvcqyq+ds+lWZu6Vbp3PlGWhJeJRm842jxtiluJpl6r4f6nUXP2fdMz7MNpDzXfLooQK9E04NbUQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/thumbs": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/thumbs/-/thumbs-9.4.2.tgz",
"integrity": "sha512-ccWvDBqbkWS5uzHbsg5L6uML6vBfX7jT3J3jHCQksvz8haHItxTK02w+6e1UavZUsvza4lG5X/XY3eji3siJ4Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@dicebear/toon-head": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/@dicebear/toon-head/-/toon-head-9.4.2.tgz",
"integrity": "sha512-lwFeSXyAnaKnCfMt9TiJwnD1cXQUGkey/0h6i/+4TVHVMCz5/Ri5u1ynovPNHy1SnBf858QwoXHkxilGLwQX/g==",
"dev": true,
"license": "(MIT AND CC-BY-4.0)",
"engines": {
"node": ">=16.0.0"
},
"peerDependencies": {
"@dicebear/core": "^9.0.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz",
@@ -760,6 +1225,20 @@
"win32"
]
},
"node_modules/@remotion/lottie": {
"version": "4.0.290",
"resolved": "https://registry.npmjs.org/@remotion/lottie/-/lottie-4.0.290.tgz",
"integrity": "sha512-2wwPGs/RmUynF37mdhAIIXMw++uT7FbpTXv773mOoyMQf1VUq4J9XYpcewTm9nAIKIJhCF7D3l1FoZIrwIVqeQ==",
"license": "SEE LICENSE IN LICENSE.md",
"dependencies": {
"remotion": "4.0.290"
},
"peerDependencies": {
"lottie-web": "^5",
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@remotion/media-parser": {
"version": "4.0.290",
"resolved": "https://registry.npmjs.org/@remotion/media-parser/-/media-parser-4.0.290.tgz",
@@ -2106,6 +2585,13 @@
"integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==",
"license": "MIT"
},
"node_modules/lottie-web": {
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/lottie-web/-/lottie-web-5.13.0.tgz",
"integrity": "sha512-+gfBXl6sxXMPe8tKQm7qzLnUy5DUPJPKIyRHwtpCpyUEYjHYRJC/5gjUvdkuO2c3JllrPtHXH5UJJK8LRYl5yQ==",
"license": "MIT",
"peer": true
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+6 -1
View File
@@ -7,13 +7,16 @@
"dev": "remotion studio",
"render": "remotion render",
"still": "remotion still",
"upgrade": "remotion upgrade"
"upgrade": "remotion upgrade",
"gen:dicebear": "node scripts/gen-dicebear.mjs",
"check:assets": "node scripts/check-assets.mjs"
},
"dependencies": {
"@react-three/drei": "^10.7.7",
"@react-three/fiber": "^9.1.2",
"@react-three/postprocessing": "^3.0.4",
"@remotion/cli": "4.0.290",
"@remotion/lottie": "^4.0.290",
"@remotion/three": "^4.0.290",
"@remotion/zod-types": "4.0.290",
"@types/three": "^0.171.0",
@@ -25,6 +28,8 @@
"zod": "3.22.3"
},
"devDependencies": {
"@dicebear/collection": "^9.4.2",
"@dicebear/core": "^9.4.2",
"@types/react": "19.0.0",
"typescript": "5.5.4"
}
@@ -0,0 +1,362 @@
{
"dicebear/openpeeps-01.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-1'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-02.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-2'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-03.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-3'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-04.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-4'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-05.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-5'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-06.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-6'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-07.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-7'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-08.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-8'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-09.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-9'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-10.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-10'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-11.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-11'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-12.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-12'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-13.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-13'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-14.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-14'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-15.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-15'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-16.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-16'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-17.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-17'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-18.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-18'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-19.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-19'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-20.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-20'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-21.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-21'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-22.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-22'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-23.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-23'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-24.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-24'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-25.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-25'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-26.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-26'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-27.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-27'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-28.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-28'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-29.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-29'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
},
"dicebear/openpeeps-30.svg": {
"source": "DiceBear open-peeps (Pablo Stanley Open Peeps)",
"license": "CC0-1.0",
"license_class": "CC0",
"commercial_ok": true,
"attribution_required": false,
"ai_training_allowed": true,
"animation_fit": "swap-transform (bust/half-body micro-rig)",
"generator": "createAvatar(openPeeps,{seed:'flatrender-peep-30'})",
"url": "https://www.dicebear.com/styles/open-peeps/",
"vendored": "2026-06-22"
}
}
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

@@ -0,0 +1,41 @@
// License-firewall CI guard.
//
// Every vendored asset under public/illustrations/ (and public/lottie/) MUST have a
// row in public/illustrations/assets.json. No row -> the asset may not ship. Fails
// the build (exit 1) on any un-ledgered file. Run: npm run check:assets
import { readFileSync, readdirSync, statSync, existsSync } from "node:fs";
import { join, dirname, relative } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const PUBLIC = join(__dirname, "..", "public");
const ILLUS = join(PUBLIC, "illustrations");
const ledgerPath = join(ILLUS, "assets.json");
if (!existsSync(ledgerPath)) {
console.error(`MISSING license ledger: ${ledgerPath}`);
process.exit(1);
}
const ledger = JSON.parse(readFileSync(ledgerPath, "utf8"));
function walk(dir) {
if (!existsSync(dir)) return [];
let out = [];
for (const e of readdirSync(dir)) {
const p = join(dir, e);
if (statSync(p).isDirectory()) out = out.concat(walk(p));
else if (/\.(svg|json)$/i.test(e) && e !== "assets.json") out.push(p);
}
return out;
}
// Asset files live under illustrations/<source>/ and lottie/.
const files = [...walk(ILLUS), ...walk(join(PUBLIC, "lottie"))]
.map((p) => relative(ILLUS, p).split("\\").join("/"));
const missing = files.filter((f) => !ledger[f]);
if (missing.length) {
console.error(`Un-ledgered assets (add a row to assets.json):\n ${missing.join("\n ")}`);
process.exit(1);
}
console.log(`assets.json OK — ${files.length} vendored asset(s), all ledgered.`);
@@ -0,0 +1,53 @@
// Build-time character generator.
//
// DiceBear's `open-peeps` style IS Pablo Stanley's Open Peeps artwork (CC0-1.0).
// We generate deterministic per-seed SVGs OFFLINE (no runtime CDN / HTTP call) and
// vendor them into public/illustrations/dicebear/, then write a license-ledger row
// per file into public/illustrations/assets.json. Run: npm run gen:dicebear
import { createAvatar } from "@dicebear/core";
import { openPeeps } from "@dicebear/collection";
import { writeFileSync, readFileSync, mkdirSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const ILLUS = join(__dirname, "..", "public", "illustrations");
const OUT = join(ILLUS, "dicebear");
mkdirSync(OUT, { recursive: true });
const COUNT = 30;
const VENDORED = "2026-06-22";
const ledger = {};
for (let i = 1; i <= COUNT; i++) {
const seed = `flatrender-peep-${i}`;
// idRandomizer keeps SVG <defs> ids unique so multiple avatars can be inlined
// in one document without clashing (Remotion composites many in one frame).
const svg = createAvatar(openPeeps, { seed, size: 400, randomizeIds: true }).toString();
const file = `openpeeps-${String(i).padStart(2, "0")}.svg`;
writeFileSync(join(OUT, file), svg, "utf8");
ledger[`dicebear/${file}`] = {
source: "DiceBear open-peeps (Pablo Stanley Open Peeps)",
license: "CC0-1.0",
license_class: "CC0",
commercial_ok: true,
attribution_required: false,
ai_training_allowed: true,
animation_fit: "swap-transform (bust/half-body micro-rig)",
generator: `createAvatar(openPeeps,{seed:'${seed}'})`,
url: "https://www.dicebear.com/styles/open-peeps/",
vendored: VENDORED,
};
}
// Merge into the assets.json license ledger (preserve any existing rows).
const ledgerPath = join(ILLUS, "assets.json");
let existing = {};
try {
existing = JSON.parse(readFileSync(ledgerPath, "utf8"));
} catch {
existing = {};
}
writeFileSync(ledgerPath, JSON.stringify({ ...existing, ...ledger }, null, 2) + "\n", "utf8");
console.log(`generated ${COUNT} open-peeps SVGs in ${OUT}`);
console.log(`ledger now has ${Object.keys({ ...existing, ...ledger }).length} rows`);
+11
View File
@@ -2,6 +2,7 @@ import { Composition } from "remotion";
import { ASPECTS } from "./lib/aspect";
import { TEMPLATES } from "./templates";
import { Three3DTest } from "./compositions/Three3DTest";
import { AssetSheet } from "./compositions/AssetSheet";
import {
IlluminatedCircles,
illuminatedCirclesSchema,
@@ -113,6 +114,16 @@ export const RemotionRoot: React.FC = () => {
height={720}
/>
{/* Dev preview: vendored CC0 character library (not a customer template) */}
<Composition
id="AssetSheet"
component={AssetSheet}
durationInFrames={60}
fps={30}
width={1920}
height={1080}
/>
{/* Branded templates — each registered in all three aspects. A template may
supply a dedicated component per aspect (componentsByAspect) when its
design differs structurally; otherwise the shared `component` adapts
@@ -0,0 +1,38 @@
import React from "react";
import { AbsoluteFill, Img, staticFile } from "remotion";
import { FONT } from "../lib/fonts";
// Dev/preview composition (NOT a customer template — registered standalone in Root,
// never seeded). Renders a contact-sheet of the vendored CC0 Open-Peeps characters
// to verify the public/illustrations -> staticFile() -> render pipeline works.
const COUNT = 30;
export const AssetSheet: React.FC = () => {
const items = Array.from({ length: COUNT }, (_, i) =>
`illustrations/dicebear/openpeeps-${String(i + 1).padStart(2, "0")}.svg`
);
return (
<AbsoluteFill style={{ background: "#ece4d6", fontFamily: FONT, padding: 44 }}>
<div style={{ fontSize: 34, fontWeight: 800, color: "#2b3a55", marginBottom: 22, direction: "rtl" }}>
کتابخانهٔ شخصیتها Open Peeps (CC0) ۳۰ نمونه
</div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(6, 1fr)", gridAutoRows: "1fr", gap: 16, flex: 1 }}>
{items.map((src, i) => (
<div
key={i}
style={{
background: "#ffffff",
borderRadius: 18,
display: "flex",
alignItems: "center",
justifyContent: "center",
boxShadow: "0 10px 26px rgba(43,58,85,0.10)",
}}
>
<Img src={staticFile(src)} style={{ width: "84%", height: "84%", objectFit: "contain" }} />
</div>
))}
</div>
</AbsoluteFill>
);
};