⌂ Kelas Sekejap Knowledge BaseBrandDesign SystemDocsLogo
Docs · Where the art lives and what it’s called

Mascot assets

Roly the Pangolin — asset structure & naming

Where every mascot image lives, and what it's called.

Status (16 Jul): the 3D rendered art is the in-product art (team decision) — not a separate flat-SVG tier. 16/16 renders live; tiers.interim.active stays true in the manifest so <Mascot> serves them. A flat SVG tier is optional, not required — add one later only if the look calls for it.

Naming convention

One rule, deterministic in both directions:

Nothing else. No version suffixes, no _final, no v2. Git holds history; filenames don't.

Manifest key Product file Marketing file
greeting mascot-greeting.svg mascot-greeting.png
thinking mascot-thinking.svg mascot-thinking.png
tip mascot-tip.svg mascot-tip.png
studying mascot-studying.svg mascot-studying.png
correcting mascot-correcting.svg mascot-correcting.png
celebrating mascot-celebrating.svg mascot-celebrating.png
jubilant mascot-jubilant.svg mascot-jubilant.png
triumphant mascot-triumphant.svg mascot-triumphant.png
cheering mascot-cheering.svg mascot-cheering.png ⚠️ shares the jubilant pose, confetti stripped
encouraging mascot-encouraging.svg mascot-encouraging.png
grateful mascot-grateful.svg mascot-grateful.png
resting mascot-resting.svg mascot-resting.png
speaking mascot-speaking.svg mascot-speaking.png
offline mascot-offline.svg mascot-offline.png
streakBroken mascot-streak-broken.svg mascot-streak-broken.png
premium mascot-premium.svg mascot-premium.png

Sixteen expressions. That's the ceiling for a while — the next one needs to justify itself against a product state that doesn't exist yet.

Folder tree

ks-design-system/
├── public/assets/mascot/          ← SERVED by Vite at /assets/mascot/…
│   ├── mascot.manifest.json       ← source of truth. Edit this, not the component.
│   ├── product/                   ← flat neo-brutalist SVG. Target state. EMPTY today.
│   └── marketing/                 ← 3D PNG. The in-product art too (team decision).
│
└── assets/mascot/source/          ← archive. NOT served, NOT shipped to the browser.
    ├── sheet-01-core-8.png
    ├── sheet-02-celebration-4.png
    ├── sheet-03-states-4.png
    ├── hero-greeting-original.png
    └── README.md

Why the split: Vite only serves public/. Art that must resolve at runtime lives there. The source sheets are 1.5 MB each and must never be shipped to a browser, so they stay in assets/, versioned but unserved.

Old (pre-14 Jul) layout — kept for reference
        ├── product/                  ← flat neo-brutalist SVG.
        │   ├── mascot-greeting.svg
        │   ├── mascot-thinking.svg
        │   ├── mascot-tip.svg
        │   ├── mascot-studying.svg
        │   ├── mascot-correcting.svg
        │   ├── mascot-celebrating.svg
        │   ├── mascot-jubilant.svg
        │   ├── mascot-triumphant.svg
        │   ├── mascot-cheering.svg
        │   ├── mascot-encouraging.svg
        │   ├── mascot-grateful.svg
        │   ├── mascot-resting.svg
        │   ├── mascot-speaking.svg
        │   ├── mascot-offline.svg
        │   ├── mascot-streak-broken.svg
        │   └── mascot-premium.svg
        │
        ├── marketing/                ← 3D rendered PNG, transparent, 2048px. The in-product art too (via <Mascot>).
        │   ├── mascot-greeting.png
        │   ├── … (same 16 names)
        │   └── mascot-premium.png
        │
        └── source/                   ← originals. Not shipped, not imported.
            ├── sheet-01-core-8.png
            ├── sheet-02-celebration-4.png
            ├── sheet-03-states-4.png
            └── README.md

Why source/ exists

When someone asks "can we get a variant of premium facing left," you need the sheet it came from and the prompt or file that made it. Losing that is how mascots drift over time. source/README.md maps each expression back to its origin.

Product-tier export rules

The product SVG is the pose only. Strip these out before export:

Element In the art? Why
Held props — mic, calendar, gold card, trophy, book, pom-pom, water bottle, backpack ✅ Keep Part of the pose. The mascot is holding them.
Particles — sparkles, confetti, music notes, hearts, motion lines ❌ Strip Separate animated layer. Declared per-expression in the manifest.
Live UI — the waveform speech bubble on speaking ❌ Strip Must render the user's real audio. A drawn squiggle is a lie.
System status — the wifi-off badge on offline ❌ Strip That's a UI icon (Lucide), not illustration. The app owns it.

So mascot-speaking.svg is the pangolin holding a mic — no bubble, no waveform. The app composes the real waveform beside it. mascot-offline.svg is the worried pangolin — no wifi glyph. The app pairs it with the Lucide wifi-off icon.

This matters more than it sounds. Bake the waveform in and you ship a recording screen that looks like it's listening whether or not the mic is actually open.

Marketing-tier export rules

Opposite: keep everything. Props, particles, the wifi badge, the waveform bubble — all baked in. Shiva needs a single PNG she can drop into a deck or a story without compositing anything.

Rendering the mascot

Everything goes through <Mascot>, which reads the manifest and serves the 3D renders from marketing/. Don't hardcode asset paths in product code — import the component, not the PNG, so the art can be swapped centrally (e.g. if a flat SVG tier is added later).