Compare commits

...
Sign in to create a new pull request.
Author SHA1 Message Date
Meta Vens
22c96f3bdd Cast stickers: render fixes, generated CAST.md, README and PROCESS guide
Cheer, surprised and waving hands moved clear of the hair; Sleipnir's
extra legs show; Gjallar's horn has a bell; Huginn's wings separate
from his cloak; Void lifted off the dark ground; Tengu's beard and nose
cut back. CAST.md is now generated from the trait sheets. PROCESS.md is
a working guide for the next agent: style rules, coordinates, layer
order, render checks, before/after renders and known weak spots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 04:05:49 +02:00
Meta Vens
b9df97c75e Cast stickers: chibi rig, 24 trait sheets, generated SVGs and gallery
A dependency-free Node generator (cast/rig/build.mjs) composes poses,
a separate expression face layer, bases, hair, accessories, props and
emblems into standalone SVG stickers, and writes cast/gallery.html with
character and pose filters and the CSS die-cut border.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 04:00:28 +02:00
311 changed files with 1910 additions and 0 deletions

32
cast/CAST.md Normal file
View file

@ -0,0 +1,32 @@
# Cast
Generated by `cast/rig/build.mjs` from `cast/rig/characters.mjs`; edit the trait sheet, not this file.
★ marks the characters rendered in every pose and expression. "Look source" is the reference the design
was drawn from (used as inspiration only; every sticker is original), or **invented** where no appearance exists.
| Name | What they are | Look source | Palette | Trait sheet |
|---|---|---|---|---|
| Eureka ★ | Leader of the swarm; the Self that routes the faculty pipeline. Her mind lives in Huginn. | **invented** | `#ffe0c8` `#ffb347` `#59b7ff` `#ff8a2a` `#ffd84a` | base humanoid; hair bun; outfit coat; acc pencil, starClip; prop wand; emblem star |
| Huginn ★ | Memory organ: owns each instance's mind as typed state. Odin's raven of thought. | `F:/Projects/VoidBot/assets/repo-faces/huginn.png` | `#2b3247` `#59d8ff` | base raven; hair crest; outfit egg + cape; acc collarRing; prop tablet; emblem none |
| Libby ★ | Face of CultLib, the shared runtime library (CultCache, CultNet, CultMesh, CultMath). | `F:/Projects/CultLib/.voidbot/voice/libby.png` | `#ffe0c8` `#a98ad8` `#2e2440` `#c9a24a` `#8a6cd1` `#c9a6ff` | base humanoid; hair long; outfit robe; acc glasses, bow; prop book; emblem cube |
| Epiphany ★ | The archived organizational layer above frontier AI workers; Eureka absorbed what lived. JSON heresy hunter. | `F:/Projects/VoidBot/assets/repo-faces/epiphany.png`; `F:/Projects/Epiphany/docs/assets` | `#ffe0c8` `#8fd8b4` `#f4efe6` `#e8384f` `#3fbf8f` `#7cf0b8` | base humanoid; hair twintails; outfit coat; acc bow; prop magnifier; emblem bolt |
| Weksa ★ | Face of weksa, the procedural language engine for alien grammars; nerdy anthropologist. | `F:/Projects/VoidBot/assets/repo-faces/weksa.png` | `#a8693f` `#4a2e1c` `#f1e3c4` `#2f9c8f` `#8a5a2b` `#ffd84a` | base humanoid; hair curly; outfit tunic; acc glasses; prop cards; emblem glyph |
| Nibu | Face of AetheriaLore, the Aetheria setting and lore vault. | `F:/Projects/AetheriaLore/.voidbot/voice/nibu.png` | `#ffe0c8` `#b9a9f0` `#7fd6e8` `#3a2f58` `#7a7fd8` `#ff9ad5` | base humanoid; hair long; outfit dress; acc bow, headphones; prop quill; emblem star |
| Mimir | Realtime field machine: cameras, microphones and feeds into one field; keeper of the well. | `F:/Projects/VoidBot/assets/repo-faces/mimir.png` | `#c08a52` `#8a5a32` `#3a2a20` `#3fe0d0` | base humanoid; hair none; outfit robe; acc cubes; prop cube; emblem wave |
| Eve | CultMesh surface web and portable control layer; the Eve/CultUI interface DSL. | `F:/Projects/Eve/.voidbot/voice/eve.png` | `#0e1a2e` `#e9eef5` `#59b7ff` | base humanoid; hair helmet; outfit tunic; acc none; prop panel; emblem eye |
| Kiko | Face of StreamPixels, the stream overlay of persistent pixel characters. Fox-coded. | `F:/Projects/VoidBot/assets/repo-faces/kiko.png` | `#d9a074` `#f4f0f6` `#e8386f` `#2a2440` `#3fc8e8` `#ff6fae` | base humanoid; hair messy; outfit dress; acc foxEars, foxMask, tail; prop heart; emblem heart |
| Heimdall | Shared auth authority for GameCult-hosted experiments; keeper of the keys. | `F:/Projects/Heimdall/assets/heimdall-profile.png` | `#7a4a2c` `#22202a` `#1c1e2a` `#d9a43a` `#ffc34a` | base humanoid; hair messy; outfit coat; acc earring; prop keys; emblem key |
| Fensalir | Native C# runtime and field engine under the interactive work. Frigg's hall. The avatar is a crystal spire, not a person; the spire became her hat and the person around it is invented. | `F:/Projects/Fensalir/.voidbot/voice/fensalir.png` | `#ffe0c8` `#2a2a4a` `#ff5ab0` `#4fe8f0` | base humanoid; hair bob; outfit dress; acc spire; prop orb; emblem wave |
| Druzkai | Face of Zyphos, a biopolitical ecology setting with a biological memetic engine. | `F:/Projects/Zyphos/.voidbot/voice/druzkai.png` | `#c4553e` `#9a3a2a` `#4a2a5a` `#d9a43a` `#3fd0c8` | base humanoid; hair none; outfit robe; acc ridges, forehead; prop seed; emblem spiral |
| Norn | Graph viewer for typed graph state: the thread-readers who see how things are woven. | `F:/Projects/Norn/.voidbot/voice/norn.png` | `#a8693f` `#d8dce4` `#24283a` `#c9a24a` `#4fe0e8` | base humanoid; hair braids; outfit robe; acc circlet; prop spool; emblem rune |
| Sai | Ink stories that wake up inside static sites. Nonbinary, androgynous, monochrome manga ink. | `F:/Projects/Sai/.voidbot/voice/sai.png`; `F:/Projects/VoidBot/docs/persona-intake/sai.persona-intake.yaml` | `#f4f1ec` `#1e1e22` `#3a3a40` `#e8e6e0` `#3a3a3a` `#ffffff` | base humanoid; hair messy; outfit coat; acc spiralBand; prop brush; emblem spiral |
| Brokkr | Creative-tool broker daemon: Unity and Blender authoring surfaces over CultMesh. The Eddic smith. | **invented (README role; Brokkr of Skaldskaparmal)** | `#f6c9a4` `#c4562a` `#5a4636` `#2a2420` `#5a8ad8` `#ff8a2a` | base humanoid; hair short; outfit tunic; acc beard, goggles; prop hammer; emblem gear |
| Aetheria | Face of Aetheria, the open-source tactical action RPG with a sprawling sci-fi setting. | **invented (README role)** | `#f6c9a4` `#3a2a4a` `#e8ecf2` `#ff8a2a` `#59b7ff` | base humanoid; hair bob; outfit tunic; acc headphones, goggles; prop ship; emblem star |
| Ymir | World-body physics substrate around Box3D: body, field, contact, impulse. The frost giant. | `F:/Projects/VoidBot/assets/repo-faces/ymir.png` | `#b8d4ec` `#e8f4ff` `#3a5a8a` `#bff0ff` `#3a8ad8` | base humanoid; hair short; outfit robe; acc beard, crystals; prop crystal; emblem flake |
| Tengu | Native Persona, the first migrated to canonical persona state. Mountain yokai. | `F:/Projects/VoidBot/assets/native-personas/tengu.png` | `#c8443a` `#f2efe8` `#efe8da` `#4a3a7a` `#ffd84a` | base humanoid; hair wild; outfit robe; acc goatee, longNose, tokin; prop fan; emblem feather |
| Muninn | Local capture broker for the swarm: which streams a host can offer. Odin's raven of memory. | `F:/Projects/VoidBot/assets/repo-faces/muninn-concept.png` | `#2a2230` `#ffb030` | base raven; hair crest; outfit egg; acc collarRing; prop camera; emblem none |
| Sleipnir | Input mirroring and virtual HID: speaks through the hands of the machine. Odin's eight-legged horse. | `F:/Projects/Odin/assets/personas/sleipnir-avatar.png` | `#5a6478` `#2fc8e0` `#2a3040` `#3fe0f0` | base horse; hair mane; outfit tunic; acc horseTail; prop pad; emblem bolt |
| Hermodr | Eve browser lowering: resolves providers through Odin's catalog and renders them. The messenger. | `F:/Projects/Hermodr/assets/hermodr-avatar.png` | `#f6c9a4` `#2a3a4a` `#2a3a44` `#c9902a` `#e8a83a` `#3fd0c0` | base humanoid; hair messy; outfit tunic; acc wingCap, scarf; prop envelope; emblem none |
| Metame | Native Persona (メタめ) projected from the operator's public history; discussion and rumination. | **invented (persona-intake has no appearance; the design avoids depicting the operator)** | `#ffe0c8` `#59b7ff` `#1d2a3a` `#ff8a2a` | base humanoid; hair bob; outfit tunic; acc twoTone; prop mirror; emblem glyph |
| Void | VoidBot's built-in Discord moderator mind. Its state was lost; the look is new. | **invented (VoidBot README role)** | `#161226` `#4a3a7a` `#c9a6ff` | base humanoid; hair none; outfit robe; acc hood, hoodRim; prop shield; emblem moon |
| Gjallar | Renderer-neutral compositor for Odin-visible Eve surfaces; Odin's branding Persona. The horn. | `F:/Projects/Odin/assets/personas/gjallar-avatar-pixel-256.png` | `#ffe0c8` `#2a2440` `#1e2238` `#d9a43a` `#59b7ff` `#ffd84a` | base humanoid; hair short; outfit tunic; acc helm; prop horn; emblem rainbow |

188
cast/PROCESS.md Normal file
View file

@ -0,0 +1,188 @@
# How the sticker pack works
A guide for whoever fixes or extends this pack next. `README.md` has the short version and the
step lists for adding a character, pose or expression; this file explains the machine underneath,
the conventions that keep the style intact, how to check a render, and where the pack is weak.
## What it is
24 characters, 6 poses, 8 expressions, 297 SVG stickers (about 1.6 MB). Eureka and four others
have every pose and expression (48 stickers each); the rest have the neutral pose with smile,
grin and surprised. Nothing is drawn per sticker: `rig/build.mjs` asks `rig/rig.mjs` to compose
each one from a trait sheet in `rig/characters.mjs`.
The four besides Eureka with full coverage, and why:
- **Huginn**: Eureka's mind lives in Huginn, and as a raven it exercises the creature base in
every pose, which a humanoid-only set would never test.
- **Libby**: CultLib is the foundation the swarm runs on, and her long hair is the hardest case
for arm placement.
- **Epiphany**: Eureka's predecessor, whose living parts Eureka absorbed; twintails are the
other hard hair case.
- **Weksa**: the next Face slated for its own mind (`eureka-body:follow_up:weksa-mind`), and the
only full-coverage character with curly hair and dark skin, which checks blush and line contrast.
## The style rules
These come from the STICKERS block of the swarm-council page the operator liked. Breaking any
of them makes a sticker look like it came from a different pack.
- **One outline.** Every edge is `#3b2618` (warm dark brown, never black), stroke width 3,
round joins and caps. `inked()` in `rig.mjs` wraps fragments in that stroke. Small details
(emblem glyphs, hair shine, brows) go thinner, 1.5-2.6, never thicker than 3.4.
- **Flat fills.** No gradients, no SVG filters, no opacity except blush and a few highlights.
Shading is a second flat colour (`shade(hex, f)`), used sparingly: raven bellies, inner ears.
- **Limbs are double strokes**, not filled shapes: `limb()` draws the path in ink at width
`w + 6`, then in colour at width `w`, both round-capped. That is what keeps arms legible when
they cross the body. Arms are 7 wide, legs 8, raven wings 11.
- **Eyes**: an iris ellipse (6.3 x 7.6) in the character's eye colour with a 2.5 ink edge, a
dark pupil `#2a1a12`, a 3.2 ink upper lid arc, a white shine dot r 2.3 up and right plus a
tiny r 1 dot down and left. The shine dot is what makes them read as cute at 128 px; keep it on
every open eye.
- **Blush**: two `#ff9a8a` ellipses under the eyes. Opacity carries the mood: 0.45 worried,
0.7 default, 0.9 happy.
- **Proportions**: head about half the figure. The head ellipse is 80 x 72; the whole figure
stands about 136 tall.
- **Die-cut border** is CSS on the `<img>` (four white offset drop-shadows and a soft shadow),
never baked into the SVG, so the files stay usable on any background.
## Coordinate conventions
All in `rig.mjs`. The viewBox is `-80 -10 160 172`, so x is centred on 0.
| Point | Value | Used by |
|---|---|---|
| Head centre | `(0, 52)`, ellipse rx 40 ry 36 | head base, hair, accessories, face |
| Neck (head rotation pivot) | `(0, 88)` | pose head tilt |
| Shoulders | `(±15, 95)` | arm start |
| Hips | `(±8, 126)` | leg start |
| Feet | `y = 149` plus the pose's foot offset | shoes |
| Ground | about `y = 152` | |
| Chest emblem | disc r 6.8 at `(0, 106)`, ravens `(0, 104)` | `emblem()` |
Face anchors come from `BASES` so each expression works on every base:
| Base | eyeX | eyeY | mouthY | browY | cheekX | cheekY |
|---|---|---|---|---|---|---|
| humanoid | 15 | 61 | 76 | 47 | 26 | 71 |
| raven | 15 | 55 | 70 | 41 | 27 | 66 |
| horse | 17 | 50 | 82 | 37 | 29 | 62 |
**Props** are drawn around a grip point at `(0, 0)` and extend upward to about `y = -32`. The
pose places that grip at a hand plus an offset and rotation (`POSES.*.prop`); the hand circle is
drawn after the prop so it reads as holding it. Presenting puts the grip 17 above the open palm.
**Poses**, as hand points (L is the viewer's left):
| Pose | Hands | Other |
|---|---|---|
| neutral | `(-24,120)`, `(24,120)` | prop in right hand |
| cheer | `(-46,36)`, `(46,36)` | hops 6, feet apart, sparkles |
| thinking | left across the belly `(4,113)`, right at the chin `(13,92)` drawn over the head | head tilt 7, thought dots, no prop |
| surprised | `(-51,88)`, `(51,88)` | hops 7, one foot kicked, shock lines, no prop |
| presenting | right palm out at `(45,103)` | prop sits on the palm, sparkles |
| waving | right raised to `(50,50)` | prop moves to the left hand, motion arcs |
**Layer order** in `render()`, back to front: hair back and back accessories (rotating with the
head), cape, tails, legs, torso and body accessories, emblem, arms, prop, hands, then the head
group (skin, `mid` accessories such as beards, the face, hair front and front accessories, brows
and expression extras), then any arm marked `front`, then the pose effect. Accessories return
`back`, `mid`, `front` or `body` fragments and are slotted in by that key.
## Checking renders
Look at every change at sticker size. Two tools, both from the repo root in Git Bash:
```sh
# Whole gallery, or one character / pose, as Edge renders it (with the die-cut border):
cast/rig/shot.sh /tmp/all.png # everything, 1400 x 1800
cast/rig/shot.sh /tmp/kiko.png kiko # one character
cast/rig/shot.sh /tmp/cheer.png "" cheer 1400 1200 # one pose for everyone
# A 128 px contact sheet of one sticker per character, on a light ground:
cd cast/stickers && mkdir -p /tmp/r && for d in */; do d=${d%/}; \
magick -background none -density 72 $d/neutral-smile.svg -resize x128 /tmp/r/$d.png; done && \
magick montage /tmp/r/*.png -tile 8x -geometry +8+8 -background '#f2efe8' /tmp/sheet128.png
```
`shot.sh` launches Edge headless with a throwaway profile and waits for the PNG, because Edge can
return before the file is written. The gallery accepts `?c=<id>&p=<pose>`, which is how the
script filters. ImageMagick's own SVG renderer is close to Edge's but not identical (fonts in the
sleepy "z" differ); trust Edge for final checks.
What to look for: hands that vanish into hair, props that lose their outline against the outfit,
dark characters that sink into the `#07111a` gallery ground, and anything that reads as an ear
or horn when it is not one.
## How the cast was designed
Looks come from the avatars in `F:/Projects/VoidBot/assets/repo-faces/`, each repo's
`.voidbot/voice/*.png`, `F:/Projects/Odin/assets/personas/`, `F:/Projects/Heimdall/assets/`,
`F:/Projects/Hermodr/assets/`, and Sai's self-description in the persona intake. Each avatar was
reduced to three or four traits that survive at 128 px: a hair shape and colour, one headpiece,
one prop, a palette. `CAST.md` lists the source and traits per character.
- **Huginn and Muninn** share the raven base and are told apart by eye colour (cyan vs amber,
both from their avatars), Huginn's blue cloak and tablet, and Muninn's brass collar and camera.
- **Mimir**'s avatar is a stone face crowned with floating cubes; the chibi keeps the bronze skin,
teal eyes and the cube crown, and holds a cube.
- **Eve**'s avatar is two glowing eyes in a black visor; the chibi is a white helmet with a dark
face window, and her eyes and mouth are drawn in glowing blue (`faceInk`).
- **Ymir** keeps the ice-blue skin, crystal crown and white beard of the frost-giant avatar.
- **Fensalir**'s avatar is a crystal spire, not a person, so the spire became a hat and the
person is invented in its pink and cyan.
- **Sleipnir** has eight limbs as in the myth: two hoof-arms, two legs and four extra legs behind.
- **Tengu** keeps the red face, long nose, white mane and tokin cap; the feather fan is from the
yokai myth.
- **Invented**: Eureka (bun with a pencil, a star clip and a star wand, in the brand orange and
blue), Brokkr (the Eddic smith: ginger beard, goggles, hammer), Aetheria (a pilot in white with
a small starship), Void (a hooded purple figure with a moon shield, since its state was lost),
and Metame (blue and orange split hair and a mirror; the design deliberately does not depict the
operator, whose public history Metame is projected from).
## Iterations
Each pair is before on the left, after on the right, cropped from gallery screenshots.
![Cheer arms](process/01-cheer-arms.png)
Raised hands sat inside the head outline and disappeared behind the hair. Cheer hands moved out
and up to `(±46, 36)`, clear of the head and side locks; surprised and waving moved the same way.
![Eureka's feather](process/02-eureka-feather.png)
A raven feather in Eureka's hair (a nod to Huginn) read as a rabbit ear at sticker size. It was
removed; the pencil and star clip carry the idea of her.
![Void contrast](process/03-void-contrast.png)
Void in near-black purple vanished into the dark gallery ground and had black hands. The robe and
hood went to mid purple with a lighter hood rim, and the hands to lavender (`hands` trait).
![Sleipnir's legs](process/04-sleipnir-legs.png)
The extra legs were hidden under the tunic hem. They now start wider and splay outward, two
pairs behind the main legs, so the eight limbs read.
![Gjallar's horn](process/05-gjallar-horn.png)
The first horn was a bent cone that read as a rolled scroll. It gained a dark bell opening.
![Huginn's wings](process/06-huginn-wings.png)
Huginn's wings, body and cloak were three near-identical dark blues and merged. The wing and belly
were lightened and the cloak turned a clearer blue.
Smaller fixes without images: long hair narrowed so it frames the body instead of swallowing it,
Tengu's full beard cut to a goatee and his nose shortened so the mouth shows, Tengu's white fan
turned dark so it reads against his white robe.
## Known weak spots, and what to do next
- **Small props at the side**: in the neutral pose a thin prop such as Sai's brush or Muninn's
camera is only a few pixels wide at 128 px. A per-pose prop scale (1.2 for neutral) would help.
- **Raven thinking** puts the wing across the belly rather than at the chin; it reads as a folded
wing more than as thinking. A raven-specific variant with a stronger head tilt would sell it.
- **Mimir's cube crown** is busy at 128 px; fewer, larger cubes would be cleaner.
- **Kiko's fox mask** hides one fox ear. Moving the mask to the back of the head would keep both.
- **One body silhouette** for all humanoids: Brokkr is no stouter than Libby. A `build` trait
(torso width and leg length) would give the dwarf, the giant and the children different shapes.
- **No back or side views**, which visual-novel scenes will want; the rig is front-facing only.
- **Expressions are face-only**: there is no sweat-drop-and-slump body language. Pairing an
expression with a small pose adjustment (shoulders up for surprised, head down for sleepy)
would add a lot of life cheaply.
- Rendering more characters in full coverage is one flag (`full: true`) and about 48 files each.

76
cast/README.md Normal file
View file

@ -0,0 +1,76 @@
# Swarm stickers
Chibi stickers of the GameCult swarm cast, built by a small rig instead of drawn one by one.
Every sticker is a standalone SVG under `stickers/<character>/<pose>-<expression>.svg`;
`gallery.html` shows them all with character and pose filters; `CAST.md` lists who is who.
Open `gallery.html` in a browser to browse. To use a sticker, copy its SVG. The white die-cut
edge is not in the file; it is CSS, so it can be dropped where it does not suit:
```css
img.sticker { filter: drop-shadow(3px 0 0 #fff) drop-shadow(-3px 0 0 #fff)
drop-shadow(0 3px 0 #fff) drop-shadow(0 -3px 0 #fff) drop-shadow(0 8px 10px rgba(0,0,0,.45)); }
```
## Layout
| Path | What it is |
|---|---|
| `rig/rig.mjs` | The rig: bases, poses, expressions, hair, accessories, props, emblems, `render()` |
| `rig/characters.mjs` | One trait sheet per character, and which characters get every pose |
| `rig/build.mjs` | Writes the SVGs, `gallery.html` and `CAST.md` |
| `rig/shot.sh` | Screenshots the gallery with Edge headless, for checking renders |
| `stickers/`, `gallery.html`, `CAST.md` | Generated; never edit by hand |
| `PROCESS.md` | How the rig works, its coordinate conventions, how to check renders, known weak spots |
| `process/` | Before/after renders referenced from `PROCESS.md` |
## Regenerate
Node 18 or newer, no packages:
```sh
node cast/rig/build.mjs # everyone
node cast/rig/build.mjs kiko eve # only these characters (the gallery is rebuilt from disk)
```
## Add a character
1. Append a trait sheet to `CAST` in `rig/characters.mjs`. Copy the nearest existing one.
- `id` (folder name, lowercase), `name`, `role` (one line from the repo README), `source`
(path of the avatar or self-description the look comes from, or a string starting with
`invented`), optional `note` for a design decision worth keeping.
- `base`: `humanoid`, `raven` or `horse`. Raven needs `beak`; horse needs `muzzle`.
- `skin`, `eyes` (iris colour), `accent` (pose sparkles), optional `hands`, `faceInk`
(eye and mouth lines on a dark face), `noBlush`.
- `hair: { style, color, accent? }` — a key of `HAIR` in `rig.mjs`.
- `outfit: { shape, main, trim, collar, belt? }` — shapes `tunic`, `dress`, `robe`, `coat`
(ravens always use `egg`, with `belly` and `wing`); collars `v`, `round`, `high`, `none`.
- `legs`, `shoes`, optional `cape`, `sleeve`, `extraLegs` (0-4).
- `acc`: list of `[name, ...colours]`, keys of `ACC`. `prop`: `[name, ...colours]`, a key of
`PROPS`, or `null`. `emblem`: `{ shape, color, disc? }`, a key of `EMBLEMS`, or `null`.
- `full: true` renders every pose and expression; otherwise neutral pose with the three
expressions in `COMMON_EXPRESSIONS`.
2. Run the build, then check the render at sticker size (see "Checking renders" in `PROCESS.md`).
3. Commit the trait sheet together with the generated files.
## Add a pose
Add an entry to `POSES` in `rig/rig.mjs`: hop height, head offset and tilt, elbow and hand points
for each arm (`front: true` draws that arm over the head), feet offsets, where the prop sits
(or `null`), and an optional effect from `fx()`. Coordinates are in `PROCESS.md`. Every `full`
character picks it up on the next build. Check hands against long hair and twintails: a hand
inside the head outline disappears behind the hair.
## Add an expression
Add a `case` to `face()` in `rig/rig.mjs` and the name to `EXPRESSIONS`. Draw relative to the
base's anchors (`BASES[...].eyeX/eyeY/mouthY/browY/cheekX/cheekY`), never fixed numbers, so the
raven and horse faces follow. If ravens should open their beak for it, add it to `beak()`.
## Licence
All art here is the project's own: drawn as hand-written SVG shapes by this rig, not traced or
derived from any third-party asset. The avatars named in `CAST.md` are GameCult's own images and
served only as reference for colours and traits. The repository has no licence file at the time of
writing, so the stickers carry no licence grant beyond GameCult's own; add one here if they are
meant to be reused outside GameCult.

340
cast/gallery.html Normal file
View file

@ -0,0 +1,340 @@
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>Swarm Stickers</title>
<style>
:root { --ground: #07111a; --orange: #ff8a2a; --blue: #59b7ff; --ink: #e8eef5; --dim: #8aa0b4; }
* { box-sizing: border-box; }
body { margin: 0; background: var(--ground); color: var(--ink); font: 15px/1.45 system-ui, -apple-system, "Segoe UI", sans-serif; }
header { padding: 28px 16px 8px; max-width: 1360px; margin: 0 auto; }
h1 { margin: 0 0 4px; font-size: 26px; } h1 span { color: var(--orange); }
p.lede { margin: 0 0 14px; color: var(--dim); }
nav { display: flex; flex-wrap: wrap; gap: 6px; margin: 6px 0; }
nav button { font: inherit; font-size: 13px; color: var(--ink); background: #10202e; border: 1px solid #1e3446; border-radius: 999px; padding: 4px 11px; cursor: pointer; }
nav button[aria-pressed="true"] { background: var(--orange); border-color: var(--orange); color: #1a0d02; font-weight: 600; }
nav.poses button[aria-pressed="true"] { background: var(--blue); border-color: var(--blue); color: #021220; }
main { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 22px 14px; padding: 18px 16px 48px; max-width: 1360px; margin: 0 auto; }
figure { margin: 0; text-align: center; }
figure img { width: 100%; max-width: 150px; height: auto; display: block; margin: 0 auto;
filter: drop-shadow(3px 0 0 #fff) drop-shadow(-3px 0 0 #fff) drop-shadow(0 3px 0 #fff) drop-shadow(0 -3px 0 #fff) drop-shadow(0 8px 10px rgba(0,0,0,.45));
transform: rotate(var(--tilt, -3deg)); transition: transform .2s ease; }
figure:nth-child(3n) img { --tilt: 3deg; } figure:nth-child(4n+1) img { --tilt: -1deg; }
figure:hover img { transform: rotate(0deg) scale(1.06); }
figcaption { font-size: 12px; color: var(--dim); margin-top: 6px; } figcaption b { color: var(--ink); font-weight: 600; }
[hidden] { display: none !important; }
@media (prefers-reduced-motion: reduce) { figure img { transition: none; } }
</style></head><body>
<header><h1>Swarm <span>stickers</span></h1>
<p class="lede">297 stickers, 24 characters. Generated by <code>cast/rig/build.mjs</code>; the white die-cut edge is CSS on the image.</p>
<nav class="chars" aria-label="Character"><button type="button" data-f="" aria-pressed="true">All</button><button type="button" data-f="eureka">Eureka</button><button type="button" data-f="huginn">Huginn</button><button type="button" data-f="libby">Libby</button><button type="button" data-f="epiphany">Epiphany</button><button type="button" data-f="weksa">Weksa</button><button type="button" data-f="nibu">Nibu</button><button type="button" data-f="mimir">Mimir</button><button type="button" data-f="eve">Eve</button><button type="button" data-f="kiko">Kiko</button><button type="button" data-f="heimdall">Heimdall</button><button type="button" data-f="fensalir">Fensalir</button><button type="button" data-f="druzkai">Druzkai</button><button type="button" data-f="norn">Norn</button><button type="button" data-f="sai">Sai</button><button type="button" data-f="brokkr">Brokkr</button><button type="button" data-f="aetheria">Aetheria</button><button type="button" data-f="ymir">Ymir</button><button type="button" data-f="tengu">Tengu</button><button type="button" data-f="muninn">Muninn</button><button type="button" data-f="sleipnir">Sleipnir</button><button type="button" data-f="hermodr">Hermodr</button><button type="button" data-f="metame">Metame</button><button type="button" data-f="void">Void</button><button type="button" data-f="gjallar">Gjallar</button></nav>
<nav class="poses" aria-label="Pose"><button type="button" data-p="" aria-pressed="true">All poses</button><button type="button" data-p="neutral">neutral</button><button type="button" data-p="cheer">cheer</button><button type="button" data-p="thinking">thinking</button><button type="button" data-p="surprised">surprised</button><button type="button" data-p="presenting">presenting</button><button type="button" data-p="waving">waving</button></nav></header>
<main>
<figure data-ch="eureka" data-pose="neutral"><img src="stickers/eureka/neutral-neutral.svg" alt="Eureka, neutral, neutral" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> neutral · neutral</figcaption></figure>
<figure data-ch="eureka" data-pose="neutral"><img src="stickers/eureka/neutral-smile.svg" alt="Eureka, neutral, smile" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> neutral · smile</figcaption></figure>
<figure data-ch="eureka" data-pose="neutral"><img src="stickers/eureka/neutral-grin.svg" alt="Eureka, neutral, grin" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> neutral · grin</figcaption></figure>
<figure data-ch="eureka" data-pose="neutral"><img src="stickers/eureka/neutral-happy.svg" alt="Eureka, neutral, happy" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> neutral · happy</figcaption></figure>
<figure data-ch="eureka" data-pose="neutral"><img src="stickers/eureka/neutral-worried.svg" alt="Eureka, neutral, worried" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> neutral · worried</figcaption></figure>
<figure data-ch="eureka" data-pose="neutral"><img src="stickers/eureka/neutral-surprised.svg" alt="Eureka, neutral, surprised" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> neutral · surprised</figcaption></figure>
<figure data-ch="eureka" data-pose="neutral"><img src="stickers/eureka/neutral-determined.svg" alt="Eureka, neutral, determined" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> neutral · determined</figcaption></figure>
<figure data-ch="eureka" data-pose="neutral"><img src="stickers/eureka/neutral-sleepy.svg" alt="Eureka, neutral, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> neutral · sleepy</figcaption></figure>
<figure data-ch="eureka" data-pose="cheer"><img src="stickers/eureka/cheer-neutral.svg" alt="Eureka, cheer, neutral" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> cheer · neutral</figcaption></figure>
<figure data-ch="eureka" data-pose="cheer"><img src="stickers/eureka/cheer-smile.svg" alt="Eureka, cheer, smile" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> cheer · smile</figcaption></figure>
<figure data-ch="eureka" data-pose="cheer"><img src="stickers/eureka/cheer-grin.svg" alt="Eureka, cheer, grin" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> cheer · grin</figcaption></figure>
<figure data-ch="eureka" data-pose="cheer"><img src="stickers/eureka/cheer-happy.svg" alt="Eureka, cheer, happy" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> cheer · happy</figcaption></figure>
<figure data-ch="eureka" data-pose="cheer"><img src="stickers/eureka/cheer-worried.svg" alt="Eureka, cheer, worried" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> cheer · worried</figcaption></figure>
<figure data-ch="eureka" data-pose="cheer"><img src="stickers/eureka/cheer-surprised.svg" alt="Eureka, cheer, surprised" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> cheer · surprised</figcaption></figure>
<figure data-ch="eureka" data-pose="cheer"><img src="stickers/eureka/cheer-determined.svg" alt="Eureka, cheer, determined" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> cheer · determined</figcaption></figure>
<figure data-ch="eureka" data-pose="cheer"><img src="stickers/eureka/cheer-sleepy.svg" alt="Eureka, cheer, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> cheer · sleepy</figcaption></figure>
<figure data-ch="eureka" data-pose="thinking"><img src="stickers/eureka/thinking-neutral.svg" alt="Eureka, thinking, neutral" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> thinking · neutral</figcaption></figure>
<figure data-ch="eureka" data-pose="thinking"><img src="stickers/eureka/thinking-smile.svg" alt="Eureka, thinking, smile" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> thinking · smile</figcaption></figure>
<figure data-ch="eureka" data-pose="thinking"><img src="stickers/eureka/thinking-grin.svg" alt="Eureka, thinking, grin" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> thinking · grin</figcaption></figure>
<figure data-ch="eureka" data-pose="thinking"><img src="stickers/eureka/thinking-happy.svg" alt="Eureka, thinking, happy" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> thinking · happy</figcaption></figure>
<figure data-ch="eureka" data-pose="thinking"><img src="stickers/eureka/thinking-worried.svg" alt="Eureka, thinking, worried" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> thinking · worried</figcaption></figure>
<figure data-ch="eureka" data-pose="thinking"><img src="stickers/eureka/thinking-surprised.svg" alt="Eureka, thinking, surprised" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> thinking · surprised</figcaption></figure>
<figure data-ch="eureka" data-pose="thinking"><img src="stickers/eureka/thinking-determined.svg" alt="Eureka, thinking, determined" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> thinking · determined</figcaption></figure>
<figure data-ch="eureka" data-pose="thinking"><img src="stickers/eureka/thinking-sleepy.svg" alt="Eureka, thinking, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> thinking · sleepy</figcaption></figure>
<figure data-ch="eureka" data-pose="surprised"><img src="stickers/eureka/surprised-neutral.svg" alt="Eureka, surprised, neutral" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> surprised · neutral</figcaption></figure>
<figure data-ch="eureka" data-pose="surprised"><img src="stickers/eureka/surprised-smile.svg" alt="Eureka, surprised, smile" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> surprised · smile</figcaption></figure>
<figure data-ch="eureka" data-pose="surprised"><img src="stickers/eureka/surprised-grin.svg" alt="Eureka, surprised, grin" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> surprised · grin</figcaption></figure>
<figure data-ch="eureka" data-pose="surprised"><img src="stickers/eureka/surprised-happy.svg" alt="Eureka, surprised, happy" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> surprised · happy</figcaption></figure>
<figure data-ch="eureka" data-pose="surprised"><img src="stickers/eureka/surprised-worried.svg" alt="Eureka, surprised, worried" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> surprised · worried</figcaption></figure>
<figure data-ch="eureka" data-pose="surprised"><img src="stickers/eureka/surprised-surprised.svg" alt="Eureka, surprised, surprised" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> surprised · surprised</figcaption></figure>
<figure data-ch="eureka" data-pose="surprised"><img src="stickers/eureka/surprised-determined.svg" alt="Eureka, surprised, determined" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> surprised · determined</figcaption></figure>
<figure data-ch="eureka" data-pose="surprised"><img src="stickers/eureka/surprised-sleepy.svg" alt="Eureka, surprised, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> surprised · sleepy</figcaption></figure>
<figure data-ch="eureka" data-pose="presenting"><img src="stickers/eureka/presenting-neutral.svg" alt="Eureka, presenting, neutral" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> presenting · neutral</figcaption></figure>
<figure data-ch="eureka" data-pose="presenting"><img src="stickers/eureka/presenting-smile.svg" alt="Eureka, presenting, smile" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> presenting · smile</figcaption></figure>
<figure data-ch="eureka" data-pose="presenting"><img src="stickers/eureka/presenting-grin.svg" alt="Eureka, presenting, grin" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> presenting · grin</figcaption></figure>
<figure data-ch="eureka" data-pose="presenting"><img src="stickers/eureka/presenting-happy.svg" alt="Eureka, presenting, happy" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> presenting · happy</figcaption></figure>
<figure data-ch="eureka" data-pose="presenting"><img src="stickers/eureka/presenting-worried.svg" alt="Eureka, presenting, worried" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> presenting · worried</figcaption></figure>
<figure data-ch="eureka" data-pose="presenting"><img src="stickers/eureka/presenting-surprised.svg" alt="Eureka, presenting, surprised" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> presenting · surprised</figcaption></figure>
<figure data-ch="eureka" data-pose="presenting"><img src="stickers/eureka/presenting-determined.svg" alt="Eureka, presenting, determined" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> presenting · determined</figcaption></figure>
<figure data-ch="eureka" data-pose="presenting"><img src="stickers/eureka/presenting-sleepy.svg" alt="Eureka, presenting, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> presenting · sleepy</figcaption></figure>
<figure data-ch="eureka" data-pose="waving"><img src="stickers/eureka/waving-neutral.svg" alt="Eureka, waving, neutral" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> waving · neutral</figcaption></figure>
<figure data-ch="eureka" data-pose="waving"><img src="stickers/eureka/waving-smile.svg" alt="Eureka, waving, smile" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> waving · smile</figcaption></figure>
<figure data-ch="eureka" data-pose="waving"><img src="stickers/eureka/waving-grin.svg" alt="Eureka, waving, grin" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> waving · grin</figcaption></figure>
<figure data-ch="eureka" data-pose="waving"><img src="stickers/eureka/waving-happy.svg" alt="Eureka, waving, happy" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> waving · happy</figcaption></figure>
<figure data-ch="eureka" data-pose="waving"><img src="stickers/eureka/waving-worried.svg" alt="Eureka, waving, worried" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> waving · worried</figcaption></figure>
<figure data-ch="eureka" data-pose="waving"><img src="stickers/eureka/waving-surprised.svg" alt="Eureka, waving, surprised" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> waving · surprised</figcaption></figure>
<figure data-ch="eureka" data-pose="waving"><img src="stickers/eureka/waving-determined.svg" alt="Eureka, waving, determined" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> waving · determined</figcaption></figure>
<figure data-ch="eureka" data-pose="waving"><img src="stickers/eureka/waving-sleepy.svg" alt="Eureka, waving, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Eureka</b> waving · sleepy</figcaption></figure>
<figure data-ch="huginn" data-pose="neutral"><img src="stickers/huginn/neutral-neutral.svg" alt="Huginn, neutral, neutral" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> neutral · neutral</figcaption></figure>
<figure data-ch="huginn" data-pose="neutral"><img src="stickers/huginn/neutral-smile.svg" alt="Huginn, neutral, smile" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> neutral · smile</figcaption></figure>
<figure data-ch="huginn" data-pose="neutral"><img src="stickers/huginn/neutral-grin.svg" alt="Huginn, neutral, grin" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> neutral · grin</figcaption></figure>
<figure data-ch="huginn" data-pose="neutral"><img src="stickers/huginn/neutral-happy.svg" alt="Huginn, neutral, happy" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> neutral · happy</figcaption></figure>
<figure data-ch="huginn" data-pose="neutral"><img src="stickers/huginn/neutral-worried.svg" alt="Huginn, neutral, worried" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> neutral · worried</figcaption></figure>
<figure data-ch="huginn" data-pose="neutral"><img src="stickers/huginn/neutral-surprised.svg" alt="Huginn, neutral, surprised" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> neutral · surprised</figcaption></figure>
<figure data-ch="huginn" data-pose="neutral"><img src="stickers/huginn/neutral-determined.svg" alt="Huginn, neutral, determined" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> neutral · determined</figcaption></figure>
<figure data-ch="huginn" data-pose="neutral"><img src="stickers/huginn/neutral-sleepy.svg" alt="Huginn, neutral, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> neutral · sleepy</figcaption></figure>
<figure data-ch="huginn" data-pose="cheer"><img src="stickers/huginn/cheer-neutral.svg" alt="Huginn, cheer, neutral" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> cheer · neutral</figcaption></figure>
<figure data-ch="huginn" data-pose="cheer"><img src="stickers/huginn/cheer-smile.svg" alt="Huginn, cheer, smile" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> cheer · smile</figcaption></figure>
<figure data-ch="huginn" data-pose="cheer"><img src="stickers/huginn/cheer-grin.svg" alt="Huginn, cheer, grin" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> cheer · grin</figcaption></figure>
<figure data-ch="huginn" data-pose="cheer"><img src="stickers/huginn/cheer-happy.svg" alt="Huginn, cheer, happy" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> cheer · happy</figcaption></figure>
<figure data-ch="huginn" data-pose="cheer"><img src="stickers/huginn/cheer-worried.svg" alt="Huginn, cheer, worried" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> cheer · worried</figcaption></figure>
<figure data-ch="huginn" data-pose="cheer"><img src="stickers/huginn/cheer-surprised.svg" alt="Huginn, cheer, surprised" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> cheer · surprised</figcaption></figure>
<figure data-ch="huginn" data-pose="cheer"><img src="stickers/huginn/cheer-determined.svg" alt="Huginn, cheer, determined" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> cheer · determined</figcaption></figure>
<figure data-ch="huginn" data-pose="cheer"><img src="stickers/huginn/cheer-sleepy.svg" alt="Huginn, cheer, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> cheer · sleepy</figcaption></figure>
<figure data-ch="huginn" data-pose="thinking"><img src="stickers/huginn/thinking-neutral.svg" alt="Huginn, thinking, neutral" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> thinking · neutral</figcaption></figure>
<figure data-ch="huginn" data-pose="thinking"><img src="stickers/huginn/thinking-smile.svg" alt="Huginn, thinking, smile" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> thinking · smile</figcaption></figure>
<figure data-ch="huginn" data-pose="thinking"><img src="stickers/huginn/thinking-grin.svg" alt="Huginn, thinking, grin" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> thinking · grin</figcaption></figure>
<figure data-ch="huginn" data-pose="thinking"><img src="stickers/huginn/thinking-happy.svg" alt="Huginn, thinking, happy" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> thinking · happy</figcaption></figure>
<figure data-ch="huginn" data-pose="thinking"><img src="stickers/huginn/thinking-worried.svg" alt="Huginn, thinking, worried" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> thinking · worried</figcaption></figure>
<figure data-ch="huginn" data-pose="thinking"><img src="stickers/huginn/thinking-surprised.svg" alt="Huginn, thinking, surprised" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> thinking · surprised</figcaption></figure>
<figure data-ch="huginn" data-pose="thinking"><img src="stickers/huginn/thinking-determined.svg" alt="Huginn, thinking, determined" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> thinking · determined</figcaption></figure>
<figure data-ch="huginn" data-pose="thinking"><img src="stickers/huginn/thinking-sleepy.svg" alt="Huginn, thinking, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> thinking · sleepy</figcaption></figure>
<figure data-ch="huginn" data-pose="surprised"><img src="stickers/huginn/surprised-neutral.svg" alt="Huginn, surprised, neutral" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> surprised · neutral</figcaption></figure>
<figure data-ch="huginn" data-pose="surprised"><img src="stickers/huginn/surprised-smile.svg" alt="Huginn, surprised, smile" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> surprised · smile</figcaption></figure>
<figure data-ch="huginn" data-pose="surprised"><img src="stickers/huginn/surprised-grin.svg" alt="Huginn, surprised, grin" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> surprised · grin</figcaption></figure>
<figure data-ch="huginn" data-pose="surprised"><img src="stickers/huginn/surprised-happy.svg" alt="Huginn, surprised, happy" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> surprised · happy</figcaption></figure>
<figure data-ch="huginn" data-pose="surprised"><img src="stickers/huginn/surprised-worried.svg" alt="Huginn, surprised, worried" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> surprised · worried</figcaption></figure>
<figure data-ch="huginn" data-pose="surprised"><img src="stickers/huginn/surprised-surprised.svg" alt="Huginn, surprised, surprised" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> surprised · surprised</figcaption></figure>
<figure data-ch="huginn" data-pose="surprised"><img src="stickers/huginn/surprised-determined.svg" alt="Huginn, surprised, determined" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> surprised · determined</figcaption></figure>
<figure data-ch="huginn" data-pose="surprised"><img src="stickers/huginn/surprised-sleepy.svg" alt="Huginn, surprised, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> surprised · sleepy</figcaption></figure>
<figure data-ch="huginn" data-pose="presenting"><img src="stickers/huginn/presenting-neutral.svg" alt="Huginn, presenting, neutral" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> presenting · neutral</figcaption></figure>
<figure data-ch="huginn" data-pose="presenting"><img src="stickers/huginn/presenting-smile.svg" alt="Huginn, presenting, smile" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> presenting · smile</figcaption></figure>
<figure data-ch="huginn" data-pose="presenting"><img src="stickers/huginn/presenting-grin.svg" alt="Huginn, presenting, grin" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> presenting · grin</figcaption></figure>
<figure data-ch="huginn" data-pose="presenting"><img src="stickers/huginn/presenting-happy.svg" alt="Huginn, presenting, happy" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> presenting · happy</figcaption></figure>
<figure data-ch="huginn" data-pose="presenting"><img src="stickers/huginn/presenting-worried.svg" alt="Huginn, presenting, worried" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> presenting · worried</figcaption></figure>
<figure data-ch="huginn" data-pose="presenting"><img src="stickers/huginn/presenting-surprised.svg" alt="Huginn, presenting, surprised" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> presenting · surprised</figcaption></figure>
<figure data-ch="huginn" data-pose="presenting"><img src="stickers/huginn/presenting-determined.svg" alt="Huginn, presenting, determined" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> presenting · determined</figcaption></figure>
<figure data-ch="huginn" data-pose="presenting"><img src="stickers/huginn/presenting-sleepy.svg" alt="Huginn, presenting, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> presenting · sleepy</figcaption></figure>
<figure data-ch="huginn" data-pose="waving"><img src="stickers/huginn/waving-neutral.svg" alt="Huginn, waving, neutral" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> waving · neutral</figcaption></figure>
<figure data-ch="huginn" data-pose="waving"><img src="stickers/huginn/waving-smile.svg" alt="Huginn, waving, smile" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> waving · smile</figcaption></figure>
<figure data-ch="huginn" data-pose="waving"><img src="stickers/huginn/waving-grin.svg" alt="Huginn, waving, grin" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> waving · grin</figcaption></figure>
<figure data-ch="huginn" data-pose="waving"><img src="stickers/huginn/waving-happy.svg" alt="Huginn, waving, happy" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> waving · happy</figcaption></figure>
<figure data-ch="huginn" data-pose="waving"><img src="stickers/huginn/waving-worried.svg" alt="Huginn, waving, worried" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> waving · worried</figcaption></figure>
<figure data-ch="huginn" data-pose="waving"><img src="stickers/huginn/waving-surprised.svg" alt="Huginn, waving, surprised" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> waving · surprised</figcaption></figure>
<figure data-ch="huginn" data-pose="waving"><img src="stickers/huginn/waving-determined.svg" alt="Huginn, waving, determined" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> waving · determined</figcaption></figure>
<figure data-ch="huginn" data-pose="waving"><img src="stickers/huginn/waving-sleepy.svg" alt="Huginn, waving, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Huginn</b> waving · sleepy</figcaption></figure>
<figure data-ch="libby" data-pose="neutral"><img src="stickers/libby/neutral-neutral.svg" alt="Libby, neutral, neutral" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> neutral · neutral</figcaption></figure>
<figure data-ch="libby" data-pose="neutral"><img src="stickers/libby/neutral-smile.svg" alt="Libby, neutral, smile" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> neutral · smile</figcaption></figure>
<figure data-ch="libby" data-pose="neutral"><img src="stickers/libby/neutral-grin.svg" alt="Libby, neutral, grin" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> neutral · grin</figcaption></figure>
<figure data-ch="libby" data-pose="neutral"><img src="stickers/libby/neutral-happy.svg" alt="Libby, neutral, happy" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> neutral · happy</figcaption></figure>
<figure data-ch="libby" data-pose="neutral"><img src="stickers/libby/neutral-worried.svg" alt="Libby, neutral, worried" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> neutral · worried</figcaption></figure>
<figure data-ch="libby" data-pose="neutral"><img src="stickers/libby/neutral-surprised.svg" alt="Libby, neutral, surprised" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> neutral · surprised</figcaption></figure>
<figure data-ch="libby" data-pose="neutral"><img src="stickers/libby/neutral-determined.svg" alt="Libby, neutral, determined" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> neutral · determined</figcaption></figure>
<figure data-ch="libby" data-pose="neutral"><img src="stickers/libby/neutral-sleepy.svg" alt="Libby, neutral, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> neutral · sleepy</figcaption></figure>
<figure data-ch="libby" data-pose="cheer"><img src="stickers/libby/cheer-neutral.svg" alt="Libby, cheer, neutral" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> cheer · neutral</figcaption></figure>
<figure data-ch="libby" data-pose="cheer"><img src="stickers/libby/cheer-smile.svg" alt="Libby, cheer, smile" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> cheer · smile</figcaption></figure>
<figure data-ch="libby" data-pose="cheer"><img src="stickers/libby/cheer-grin.svg" alt="Libby, cheer, grin" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> cheer · grin</figcaption></figure>
<figure data-ch="libby" data-pose="cheer"><img src="stickers/libby/cheer-happy.svg" alt="Libby, cheer, happy" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> cheer · happy</figcaption></figure>
<figure data-ch="libby" data-pose="cheer"><img src="stickers/libby/cheer-worried.svg" alt="Libby, cheer, worried" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> cheer · worried</figcaption></figure>
<figure data-ch="libby" data-pose="cheer"><img src="stickers/libby/cheer-surprised.svg" alt="Libby, cheer, surprised" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> cheer · surprised</figcaption></figure>
<figure data-ch="libby" data-pose="cheer"><img src="stickers/libby/cheer-determined.svg" alt="Libby, cheer, determined" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> cheer · determined</figcaption></figure>
<figure data-ch="libby" data-pose="cheer"><img src="stickers/libby/cheer-sleepy.svg" alt="Libby, cheer, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> cheer · sleepy</figcaption></figure>
<figure data-ch="libby" data-pose="thinking"><img src="stickers/libby/thinking-neutral.svg" alt="Libby, thinking, neutral" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> thinking · neutral</figcaption></figure>
<figure data-ch="libby" data-pose="thinking"><img src="stickers/libby/thinking-smile.svg" alt="Libby, thinking, smile" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> thinking · smile</figcaption></figure>
<figure data-ch="libby" data-pose="thinking"><img src="stickers/libby/thinking-grin.svg" alt="Libby, thinking, grin" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> thinking · grin</figcaption></figure>
<figure data-ch="libby" data-pose="thinking"><img src="stickers/libby/thinking-happy.svg" alt="Libby, thinking, happy" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> thinking · happy</figcaption></figure>
<figure data-ch="libby" data-pose="thinking"><img src="stickers/libby/thinking-worried.svg" alt="Libby, thinking, worried" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> thinking · worried</figcaption></figure>
<figure data-ch="libby" data-pose="thinking"><img src="stickers/libby/thinking-surprised.svg" alt="Libby, thinking, surprised" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> thinking · surprised</figcaption></figure>
<figure data-ch="libby" data-pose="thinking"><img src="stickers/libby/thinking-determined.svg" alt="Libby, thinking, determined" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> thinking · determined</figcaption></figure>
<figure data-ch="libby" data-pose="thinking"><img src="stickers/libby/thinking-sleepy.svg" alt="Libby, thinking, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> thinking · sleepy</figcaption></figure>
<figure data-ch="libby" data-pose="surprised"><img src="stickers/libby/surprised-neutral.svg" alt="Libby, surprised, neutral" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> surprised · neutral</figcaption></figure>
<figure data-ch="libby" data-pose="surprised"><img src="stickers/libby/surprised-smile.svg" alt="Libby, surprised, smile" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> surprised · smile</figcaption></figure>
<figure data-ch="libby" data-pose="surprised"><img src="stickers/libby/surprised-grin.svg" alt="Libby, surprised, grin" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> surprised · grin</figcaption></figure>
<figure data-ch="libby" data-pose="surprised"><img src="stickers/libby/surprised-happy.svg" alt="Libby, surprised, happy" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> surprised · happy</figcaption></figure>
<figure data-ch="libby" data-pose="surprised"><img src="stickers/libby/surprised-worried.svg" alt="Libby, surprised, worried" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> surprised · worried</figcaption></figure>
<figure data-ch="libby" data-pose="surprised"><img src="stickers/libby/surprised-surprised.svg" alt="Libby, surprised, surprised" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> surprised · surprised</figcaption></figure>
<figure data-ch="libby" data-pose="surprised"><img src="stickers/libby/surprised-determined.svg" alt="Libby, surprised, determined" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> surprised · determined</figcaption></figure>
<figure data-ch="libby" data-pose="surprised"><img src="stickers/libby/surprised-sleepy.svg" alt="Libby, surprised, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> surprised · sleepy</figcaption></figure>
<figure data-ch="libby" data-pose="presenting"><img src="stickers/libby/presenting-neutral.svg" alt="Libby, presenting, neutral" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> presenting · neutral</figcaption></figure>
<figure data-ch="libby" data-pose="presenting"><img src="stickers/libby/presenting-smile.svg" alt="Libby, presenting, smile" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> presenting · smile</figcaption></figure>
<figure data-ch="libby" data-pose="presenting"><img src="stickers/libby/presenting-grin.svg" alt="Libby, presenting, grin" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> presenting · grin</figcaption></figure>
<figure data-ch="libby" data-pose="presenting"><img src="stickers/libby/presenting-happy.svg" alt="Libby, presenting, happy" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> presenting · happy</figcaption></figure>
<figure data-ch="libby" data-pose="presenting"><img src="stickers/libby/presenting-worried.svg" alt="Libby, presenting, worried" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> presenting · worried</figcaption></figure>
<figure data-ch="libby" data-pose="presenting"><img src="stickers/libby/presenting-surprised.svg" alt="Libby, presenting, surprised" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> presenting · surprised</figcaption></figure>
<figure data-ch="libby" data-pose="presenting"><img src="stickers/libby/presenting-determined.svg" alt="Libby, presenting, determined" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> presenting · determined</figcaption></figure>
<figure data-ch="libby" data-pose="presenting"><img src="stickers/libby/presenting-sleepy.svg" alt="Libby, presenting, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> presenting · sleepy</figcaption></figure>
<figure data-ch="libby" data-pose="waving"><img src="stickers/libby/waving-neutral.svg" alt="Libby, waving, neutral" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> waving · neutral</figcaption></figure>
<figure data-ch="libby" data-pose="waving"><img src="stickers/libby/waving-smile.svg" alt="Libby, waving, smile" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> waving · smile</figcaption></figure>
<figure data-ch="libby" data-pose="waving"><img src="stickers/libby/waving-grin.svg" alt="Libby, waving, grin" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> waving · grin</figcaption></figure>
<figure data-ch="libby" data-pose="waving"><img src="stickers/libby/waving-happy.svg" alt="Libby, waving, happy" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> waving · happy</figcaption></figure>
<figure data-ch="libby" data-pose="waving"><img src="stickers/libby/waving-worried.svg" alt="Libby, waving, worried" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> waving · worried</figcaption></figure>
<figure data-ch="libby" data-pose="waving"><img src="stickers/libby/waving-surprised.svg" alt="Libby, waving, surprised" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> waving · surprised</figcaption></figure>
<figure data-ch="libby" data-pose="waving"><img src="stickers/libby/waving-determined.svg" alt="Libby, waving, determined" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> waving · determined</figcaption></figure>
<figure data-ch="libby" data-pose="waving"><img src="stickers/libby/waving-sleepy.svg" alt="Libby, waving, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Libby</b> waving · sleepy</figcaption></figure>
<figure data-ch="epiphany" data-pose="neutral"><img src="stickers/epiphany/neutral-neutral.svg" alt="Epiphany, neutral, neutral" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> neutral · neutral</figcaption></figure>
<figure data-ch="epiphany" data-pose="neutral"><img src="stickers/epiphany/neutral-smile.svg" alt="Epiphany, neutral, smile" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> neutral · smile</figcaption></figure>
<figure data-ch="epiphany" data-pose="neutral"><img src="stickers/epiphany/neutral-grin.svg" alt="Epiphany, neutral, grin" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> neutral · grin</figcaption></figure>
<figure data-ch="epiphany" data-pose="neutral"><img src="stickers/epiphany/neutral-happy.svg" alt="Epiphany, neutral, happy" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> neutral · happy</figcaption></figure>
<figure data-ch="epiphany" data-pose="neutral"><img src="stickers/epiphany/neutral-worried.svg" alt="Epiphany, neutral, worried" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> neutral · worried</figcaption></figure>
<figure data-ch="epiphany" data-pose="neutral"><img src="stickers/epiphany/neutral-surprised.svg" alt="Epiphany, neutral, surprised" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> neutral · surprised</figcaption></figure>
<figure data-ch="epiphany" data-pose="neutral"><img src="stickers/epiphany/neutral-determined.svg" alt="Epiphany, neutral, determined" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> neutral · determined</figcaption></figure>
<figure data-ch="epiphany" data-pose="neutral"><img src="stickers/epiphany/neutral-sleepy.svg" alt="Epiphany, neutral, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> neutral · sleepy</figcaption></figure>
<figure data-ch="epiphany" data-pose="cheer"><img src="stickers/epiphany/cheer-neutral.svg" alt="Epiphany, cheer, neutral" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> cheer · neutral</figcaption></figure>
<figure data-ch="epiphany" data-pose="cheer"><img src="stickers/epiphany/cheer-smile.svg" alt="Epiphany, cheer, smile" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> cheer · smile</figcaption></figure>
<figure data-ch="epiphany" data-pose="cheer"><img src="stickers/epiphany/cheer-grin.svg" alt="Epiphany, cheer, grin" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> cheer · grin</figcaption></figure>
<figure data-ch="epiphany" data-pose="cheer"><img src="stickers/epiphany/cheer-happy.svg" alt="Epiphany, cheer, happy" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> cheer · happy</figcaption></figure>
<figure data-ch="epiphany" data-pose="cheer"><img src="stickers/epiphany/cheer-worried.svg" alt="Epiphany, cheer, worried" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> cheer · worried</figcaption></figure>
<figure data-ch="epiphany" data-pose="cheer"><img src="stickers/epiphany/cheer-surprised.svg" alt="Epiphany, cheer, surprised" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> cheer · surprised</figcaption></figure>
<figure data-ch="epiphany" data-pose="cheer"><img src="stickers/epiphany/cheer-determined.svg" alt="Epiphany, cheer, determined" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> cheer · determined</figcaption></figure>
<figure data-ch="epiphany" data-pose="cheer"><img src="stickers/epiphany/cheer-sleepy.svg" alt="Epiphany, cheer, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> cheer · sleepy</figcaption></figure>
<figure data-ch="epiphany" data-pose="thinking"><img src="stickers/epiphany/thinking-neutral.svg" alt="Epiphany, thinking, neutral" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> thinking · neutral</figcaption></figure>
<figure data-ch="epiphany" data-pose="thinking"><img src="stickers/epiphany/thinking-smile.svg" alt="Epiphany, thinking, smile" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> thinking · smile</figcaption></figure>
<figure data-ch="epiphany" data-pose="thinking"><img src="stickers/epiphany/thinking-grin.svg" alt="Epiphany, thinking, grin" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> thinking · grin</figcaption></figure>
<figure data-ch="epiphany" data-pose="thinking"><img src="stickers/epiphany/thinking-happy.svg" alt="Epiphany, thinking, happy" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> thinking · happy</figcaption></figure>
<figure data-ch="epiphany" data-pose="thinking"><img src="stickers/epiphany/thinking-worried.svg" alt="Epiphany, thinking, worried" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> thinking · worried</figcaption></figure>
<figure data-ch="epiphany" data-pose="thinking"><img src="stickers/epiphany/thinking-surprised.svg" alt="Epiphany, thinking, surprised" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> thinking · surprised</figcaption></figure>
<figure data-ch="epiphany" data-pose="thinking"><img src="stickers/epiphany/thinking-determined.svg" alt="Epiphany, thinking, determined" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> thinking · determined</figcaption></figure>
<figure data-ch="epiphany" data-pose="thinking"><img src="stickers/epiphany/thinking-sleepy.svg" alt="Epiphany, thinking, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> thinking · sleepy</figcaption></figure>
<figure data-ch="epiphany" data-pose="surprised"><img src="stickers/epiphany/surprised-neutral.svg" alt="Epiphany, surprised, neutral" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> surprised · neutral</figcaption></figure>
<figure data-ch="epiphany" data-pose="surprised"><img src="stickers/epiphany/surprised-smile.svg" alt="Epiphany, surprised, smile" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> surprised · smile</figcaption></figure>
<figure data-ch="epiphany" data-pose="surprised"><img src="stickers/epiphany/surprised-grin.svg" alt="Epiphany, surprised, grin" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> surprised · grin</figcaption></figure>
<figure data-ch="epiphany" data-pose="surprised"><img src="stickers/epiphany/surprised-happy.svg" alt="Epiphany, surprised, happy" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> surprised · happy</figcaption></figure>
<figure data-ch="epiphany" data-pose="surprised"><img src="stickers/epiphany/surprised-worried.svg" alt="Epiphany, surprised, worried" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> surprised · worried</figcaption></figure>
<figure data-ch="epiphany" data-pose="surprised"><img src="stickers/epiphany/surprised-surprised.svg" alt="Epiphany, surprised, surprised" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> surprised · surprised</figcaption></figure>
<figure data-ch="epiphany" data-pose="surprised"><img src="stickers/epiphany/surprised-determined.svg" alt="Epiphany, surprised, determined" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> surprised · determined</figcaption></figure>
<figure data-ch="epiphany" data-pose="surprised"><img src="stickers/epiphany/surprised-sleepy.svg" alt="Epiphany, surprised, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> surprised · sleepy</figcaption></figure>
<figure data-ch="epiphany" data-pose="presenting"><img src="stickers/epiphany/presenting-neutral.svg" alt="Epiphany, presenting, neutral" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> presenting · neutral</figcaption></figure>
<figure data-ch="epiphany" data-pose="presenting"><img src="stickers/epiphany/presenting-smile.svg" alt="Epiphany, presenting, smile" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> presenting · smile</figcaption></figure>
<figure data-ch="epiphany" data-pose="presenting"><img src="stickers/epiphany/presenting-grin.svg" alt="Epiphany, presenting, grin" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> presenting · grin</figcaption></figure>
<figure data-ch="epiphany" data-pose="presenting"><img src="stickers/epiphany/presenting-happy.svg" alt="Epiphany, presenting, happy" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> presenting · happy</figcaption></figure>
<figure data-ch="epiphany" data-pose="presenting"><img src="stickers/epiphany/presenting-worried.svg" alt="Epiphany, presenting, worried" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> presenting · worried</figcaption></figure>
<figure data-ch="epiphany" data-pose="presenting"><img src="stickers/epiphany/presenting-surprised.svg" alt="Epiphany, presenting, surprised" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> presenting · surprised</figcaption></figure>
<figure data-ch="epiphany" data-pose="presenting"><img src="stickers/epiphany/presenting-determined.svg" alt="Epiphany, presenting, determined" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> presenting · determined</figcaption></figure>
<figure data-ch="epiphany" data-pose="presenting"><img src="stickers/epiphany/presenting-sleepy.svg" alt="Epiphany, presenting, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> presenting · sleepy</figcaption></figure>
<figure data-ch="epiphany" data-pose="waving"><img src="stickers/epiphany/waving-neutral.svg" alt="Epiphany, waving, neutral" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> waving · neutral</figcaption></figure>
<figure data-ch="epiphany" data-pose="waving"><img src="stickers/epiphany/waving-smile.svg" alt="Epiphany, waving, smile" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> waving · smile</figcaption></figure>
<figure data-ch="epiphany" data-pose="waving"><img src="stickers/epiphany/waving-grin.svg" alt="Epiphany, waving, grin" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> waving · grin</figcaption></figure>
<figure data-ch="epiphany" data-pose="waving"><img src="stickers/epiphany/waving-happy.svg" alt="Epiphany, waving, happy" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> waving · happy</figcaption></figure>
<figure data-ch="epiphany" data-pose="waving"><img src="stickers/epiphany/waving-worried.svg" alt="Epiphany, waving, worried" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> waving · worried</figcaption></figure>
<figure data-ch="epiphany" data-pose="waving"><img src="stickers/epiphany/waving-surprised.svg" alt="Epiphany, waving, surprised" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> waving · surprised</figcaption></figure>
<figure data-ch="epiphany" data-pose="waving"><img src="stickers/epiphany/waving-determined.svg" alt="Epiphany, waving, determined" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> waving · determined</figcaption></figure>
<figure data-ch="epiphany" data-pose="waving"><img src="stickers/epiphany/waving-sleepy.svg" alt="Epiphany, waving, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Epiphany</b> waving · sleepy</figcaption></figure>
<figure data-ch="weksa" data-pose="neutral"><img src="stickers/weksa/neutral-neutral.svg" alt="Weksa, neutral, neutral" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> neutral · neutral</figcaption></figure>
<figure data-ch="weksa" data-pose="neutral"><img src="stickers/weksa/neutral-smile.svg" alt="Weksa, neutral, smile" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> neutral · smile</figcaption></figure>
<figure data-ch="weksa" data-pose="neutral"><img src="stickers/weksa/neutral-grin.svg" alt="Weksa, neutral, grin" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> neutral · grin</figcaption></figure>
<figure data-ch="weksa" data-pose="neutral"><img src="stickers/weksa/neutral-happy.svg" alt="Weksa, neutral, happy" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> neutral · happy</figcaption></figure>
<figure data-ch="weksa" data-pose="neutral"><img src="stickers/weksa/neutral-worried.svg" alt="Weksa, neutral, worried" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> neutral · worried</figcaption></figure>
<figure data-ch="weksa" data-pose="neutral"><img src="stickers/weksa/neutral-surprised.svg" alt="Weksa, neutral, surprised" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> neutral · surprised</figcaption></figure>
<figure data-ch="weksa" data-pose="neutral"><img src="stickers/weksa/neutral-determined.svg" alt="Weksa, neutral, determined" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> neutral · determined</figcaption></figure>
<figure data-ch="weksa" data-pose="neutral"><img src="stickers/weksa/neutral-sleepy.svg" alt="Weksa, neutral, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> neutral · sleepy</figcaption></figure>
<figure data-ch="weksa" data-pose="cheer"><img src="stickers/weksa/cheer-neutral.svg" alt="Weksa, cheer, neutral" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> cheer · neutral</figcaption></figure>
<figure data-ch="weksa" data-pose="cheer"><img src="stickers/weksa/cheer-smile.svg" alt="Weksa, cheer, smile" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> cheer · smile</figcaption></figure>
<figure data-ch="weksa" data-pose="cheer"><img src="stickers/weksa/cheer-grin.svg" alt="Weksa, cheer, grin" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> cheer · grin</figcaption></figure>
<figure data-ch="weksa" data-pose="cheer"><img src="stickers/weksa/cheer-happy.svg" alt="Weksa, cheer, happy" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> cheer · happy</figcaption></figure>
<figure data-ch="weksa" data-pose="cheer"><img src="stickers/weksa/cheer-worried.svg" alt="Weksa, cheer, worried" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> cheer · worried</figcaption></figure>
<figure data-ch="weksa" data-pose="cheer"><img src="stickers/weksa/cheer-surprised.svg" alt="Weksa, cheer, surprised" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> cheer · surprised</figcaption></figure>
<figure data-ch="weksa" data-pose="cheer"><img src="stickers/weksa/cheer-determined.svg" alt="Weksa, cheer, determined" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> cheer · determined</figcaption></figure>
<figure data-ch="weksa" data-pose="cheer"><img src="stickers/weksa/cheer-sleepy.svg" alt="Weksa, cheer, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> cheer · sleepy</figcaption></figure>
<figure data-ch="weksa" data-pose="thinking"><img src="stickers/weksa/thinking-neutral.svg" alt="Weksa, thinking, neutral" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> thinking · neutral</figcaption></figure>
<figure data-ch="weksa" data-pose="thinking"><img src="stickers/weksa/thinking-smile.svg" alt="Weksa, thinking, smile" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> thinking · smile</figcaption></figure>
<figure data-ch="weksa" data-pose="thinking"><img src="stickers/weksa/thinking-grin.svg" alt="Weksa, thinking, grin" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> thinking · grin</figcaption></figure>
<figure data-ch="weksa" data-pose="thinking"><img src="stickers/weksa/thinking-happy.svg" alt="Weksa, thinking, happy" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> thinking · happy</figcaption></figure>
<figure data-ch="weksa" data-pose="thinking"><img src="stickers/weksa/thinking-worried.svg" alt="Weksa, thinking, worried" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> thinking · worried</figcaption></figure>
<figure data-ch="weksa" data-pose="thinking"><img src="stickers/weksa/thinking-surprised.svg" alt="Weksa, thinking, surprised" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> thinking · surprised</figcaption></figure>
<figure data-ch="weksa" data-pose="thinking"><img src="stickers/weksa/thinking-determined.svg" alt="Weksa, thinking, determined" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> thinking · determined</figcaption></figure>
<figure data-ch="weksa" data-pose="thinking"><img src="stickers/weksa/thinking-sleepy.svg" alt="Weksa, thinking, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> thinking · sleepy</figcaption></figure>
<figure data-ch="weksa" data-pose="surprised"><img src="stickers/weksa/surprised-neutral.svg" alt="Weksa, surprised, neutral" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> surprised · neutral</figcaption></figure>
<figure data-ch="weksa" data-pose="surprised"><img src="stickers/weksa/surprised-smile.svg" alt="Weksa, surprised, smile" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> surprised · smile</figcaption></figure>
<figure data-ch="weksa" data-pose="surprised"><img src="stickers/weksa/surprised-grin.svg" alt="Weksa, surprised, grin" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> surprised · grin</figcaption></figure>
<figure data-ch="weksa" data-pose="surprised"><img src="stickers/weksa/surprised-happy.svg" alt="Weksa, surprised, happy" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> surprised · happy</figcaption></figure>
<figure data-ch="weksa" data-pose="surprised"><img src="stickers/weksa/surprised-worried.svg" alt="Weksa, surprised, worried" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> surprised · worried</figcaption></figure>
<figure data-ch="weksa" data-pose="surprised"><img src="stickers/weksa/surprised-surprised.svg" alt="Weksa, surprised, surprised" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> surprised · surprised</figcaption></figure>
<figure data-ch="weksa" data-pose="surprised"><img src="stickers/weksa/surprised-determined.svg" alt="Weksa, surprised, determined" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> surprised · determined</figcaption></figure>
<figure data-ch="weksa" data-pose="surprised"><img src="stickers/weksa/surprised-sleepy.svg" alt="Weksa, surprised, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> surprised · sleepy</figcaption></figure>
<figure data-ch="weksa" data-pose="presenting"><img src="stickers/weksa/presenting-neutral.svg" alt="Weksa, presenting, neutral" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> presenting · neutral</figcaption></figure>
<figure data-ch="weksa" data-pose="presenting"><img src="stickers/weksa/presenting-smile.svg" alt="Weksa, presenting, smile" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> presenting · smile</figcaption></figure>
<figure data-ch="weksa" data-pose="presenting"><img src="stickers/weksa/presenting-grin.svg" alt="Weksa, presenting, grin" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> presenting · grin</figcaption></figure>
<figure data-ch="weksa" data-pose="presenting"><img src="stickers/weksa/presenting-happy.svg" alt="Weksa, presenting, happy" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> presenting · happy</figcaption></figure>
<figure data-ch="weksa" data-pose="presenting"><img src="stickers/weksa/presenting-worried.svg" alt="Weksa, presenting, worried" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> presenting · worried</figcaption></figure>
<figure data-ch="weksa" data-pose="presenting"><img src="stickers/weksa/presenting-surprised.svg" alt="Weksa, presenting, surprised" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> presenting · surprised</figcaption></figure>
<figure data-ch="weksa" data-pose="presenting"><img src="stickers/weksa/presenting-determined.svg" alt="Weksa, presenting, determined" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> presenting · determined</figcaption></figure>
<figure data-ch="weksa" data-pose="presenting"><img src="stickers/weksa/presenting-sleepy.svg" alt="Weksa, presenting, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> presenting · sleepy</figcaption></figure>
<figure data-ch="weksa" data-pose="waving"><img src="stickers/weksa/waving-neutral.svg" alt="Weksa, waving, neutral" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> waving · neutral</figcaption></figure>
<figure data-ch="weksa" data-pose="waving"><img src="stickers/weksa/waving-smile.svg" alt="Weksa, waving, smile" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> waving · smile</figcaption></figure>
<figure data-ch="weksa" data-pose="waving"><img src="stickers/weksa/waving-grin.svg" alt="Weksa, waving, grin" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> waving · grin</figcaption></figure>
<figure data-ch="weksa" data-pose="waving"><img src="stickers/weksa/waving-happy.svg" alt="Weksa, waving, happy" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> waving · happy</figcaption></figure>
<figure data-ch="weksa" data-pose="waving"><img src="stickers/weksa/waving-worried.svg" alt="Weksa, waving, worried" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> waving · worried</figcaption></figure>
<figure data-ch="weksa" data-pose="waving"><img src="stickers/weksa/waving-surprised.svg" alt="Weksa, waving, surprised" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> waving · surprised</figcaption></figure>
<figure data-ch="weksa" data-pose="waving"><img src="stickers/weksa/waving-determined.svg" alt="Weksa, waving, determined" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> waving · determined</figcaption></figure>
<figure data-ch="weksa" data-pose="waving"><img src="stickers/weksa/waving-sleepy.svg" alt="Weksa, waving, sleepy" loading="lazy" width="150" height="161"><figcaption><b>Weksa</b> waving · sleepy</figcaption></figure>
<figure data-ch="nibu" data-pose="neutral"><img src="stickers/nibu/neutral-smile.svg" alt="Nibu, neutral, smile" loading="lazy" width="150" height="161"><figcaption><b>Nibu</b> neutral · smile</figcaption></figure>
<figure data-ch="nibu" data-pose="neutral"><img src="stickers/nibu/neutral-grin.svg" alt="Nibu, neutral, grin" loading="lazy" width="150" height="161"><figcaption><b>Nibu</b> neutral · grin</figcaption></figure>
<figure data-ch="nibu" data-pose="neutral"><img src="stickers/nibu/neutral-surprised.svg" alt="Nibu, neutral, surprised" loading="lazy" width="150" height="161"><figcaption><b>Nibu</b> neutral · surprised</figcaption></figure>
<figure data-ch="mimir" data-pose="neutral"><img src="stickers/mimir/neutral-smile.svg" alt="Mimir, neutral, smile" loading="lazy" width="150" height="161"><figcaption><b>Mimir</b> neutral · smile</figcaption></figure>
<figure data-ch="mimir" data-pose="neutral"><img src="stickers/mimir/neutral-grin.svg" alt="Mimir, neutral, grin" loading="lazy" width="150" height="161"><figcaption><b>Mimir</b> neutral · grin</figcaption></figure>
<figure data-ch="mimir" data-pose="neutral"><img src="stickers/mimir/neutral-surprised.svg" alt="Mimir, neutral, surprised" loading="lazy" width="150" height="161"><figcaption><b>Mimir</b> neutral · surprised</figcaption></figure>
<figure data-ch="eve" data-pose="neutral"><img src="stickers/eve/neutral-smile.svg" alt="Eve, neutral, smile" loading="lazy" width="150" height="161"><figcaption><b>Eve</b> neutral · smile</figcaption></figure>
<figure data-ch="eve" data-pose="neutral"><img src="stickers/eve/neutral-grin.svg" alt="Eve, neutral, grin" loading="lazy" width="150" height="161"><figcaption><b>Eve</b> neutral · grin</figcaption></figure>
<figure data-ch="eve" data-pose="neutral"><img src="stickers/eve/neutral-surprised.svg" alt="Eve, neutral, surprised" loading="lazy" width="150" height="161"><figcaption><b>Eve</b> neutral · surprised</figcaption></figure>
<figure data-ch="kiko" data-pose="neutral"><img src="stickers/kiko/neutral-smile.svg" alt="Kiko, neutral, smile" loading="lazy" width="150" height="161"><figcaption><b>Kiko</b> neutral · smile</figcaption></figure>
<figure data-ch="kiko" data-pose="neutral"><img src="stickers/kiko/neutral-grin.svg" alt="Kiko, neutral, grin" loading="lazy" width="150" height="161"><figcaption><b>Kiko</b> neutral · grin</figcaption></figure>
<figure data-ch="kiko" data-pose="neutral"><img src="stickers/kiko/neutral-surprised.svg" alt="Kiko, neutral, surprised" loading="lazy" width="150" height="161"><figcaption><b>Kiko</b> neutral · surprised</figcaption></figure>
<figure data-ch="heimdall" data-pose="neutral"><img src="stickers/heimdall/neutral-smile.svg" alt="Heimdall, neutral, smile" loading="lazy" width="150" height="161"><figcaption><b>Heimdall</b> neutral · smile</figcaption></figure>
<figure data-ch="heimdall" data-pose="neutral"><img src="stickers/heimdall/neutral-grin.svg" alt="Heimdall, neutral, grin" loading="lazy" width="150" height="161"><figcaption><b>Heimdall</b> neutral · grin</figcaption></figure>
<figure data-ch="heimdall" data-pose="neutral"><img src="stickers/heimdall/neutral-surprised.svg" alt="Heimdall, neutral, surprised" loading="lazy" width="150" height="161"><figcaption><b>Heimdall</b> neutral · surprised</figcaption></figure>
<figure data-ch="fensalir" data-pose="neutral"><img src="stickers/fensalir/neutral-smile.svg" alt="Fensalir, neutral, smile" loading="lazy" width="150" height="161"><figcaption><b>Fensalir</b> neutral · smile</figcaption></figure>
<figure data-ch="fensalir" data-pose="neutral"><img src="stickers/fensalir/neutral-grin.svg" alt="Fensalir, neutral, grin" loading="lazy" width="150" height="161"><figcaption><b>Fensalir</b> neutral · grin</figcaption></figure>
<figure data-ch="fensalir" data-pose="neutral"><img src="stickers/fensalir/neutral-surprised.svg" alt="Fensalir, neutral, surprised" loading="lazy" width="150" height="161"><figcaption><b>Fensalir</b> neutral · surprised</figcaption></figure>
<figure data-ch="druzkai" data-pose="neutral"><img src="stickers/druzkai/neutral-smile.svg" alt="Druzkai, neutral, smile" loading="lazy" width="150" height="161"><figcaption><b>Druzkai</b> neutral · smile</figcaption></figure>
<figure data-ch="druzkai" data-pose="neutral"><img src="stickers/druzkai/neutral-grin.svg" alt="Druzkai, neutral, grin" loading="lazy" width="150" height="161"><figcaption><b>Druzkai</b> neutral · grin</figcaption></figure>
<figure data-ch="druzkai" data-pose="neutral"><img src="stickers/druzkai/neutral-surprised.svg" alt="Druzkai, neutral, surprised" loading="lazy" width="150" height="161"><figcaption><b>Druzkai</b> neutral · surprised</figcaption></figure>
<figure data-ch="norn" data-pose="neutral"><img src="stickers/norn/neutral-smile.svg" alt="Norn, neutral, smile" loading="lazy" width="150" height="161"><figcaption><b>Norn</b> neutral · smile</figcaption></figure>
<figure data-ch="norn" data-pose="neutral"><img src="stickers/norn/neutral-grin.svg" alt="Norn, neutral, grin" loading="lazy" width="150" height="161"><figcaption><b>Norn</b> neutral · grin</figcaption></figure>
<figure data-ch="norn" data-pose="neutral"><img src="stickers/norn/neutral-surprised.svg" alt="Norn, neutral, surprised" loading="lazy" width="150" height="161"><figcaption><b>Norn</b> neutral · surprised</figcaption></figure>
<figure data-ch="sai" data-pose="neutral"><img src="stickers/sai/neutral-smile.svg" alt="Sai, neutral, smile" loading="lazy" width="150" height="161"><figcaption><b>Sai</b> neutral · smile</figcaption></figure>
<figure data-ch="sai" data-pose="neutral"><img src="stickers/sai/neutral-grin.svg" alt="Sai, neutral, grin" loading="lazy" width="150" height="161"><figcaption><b>Sai</b> neutral · grin</figcaption></figure>
<figure data-ch="sai" data-pose="neutral"><img src="stickers/sai/neutral-surprised.svg" alt="Sai, neutral, surprised" loading="lazy" width="150" height="161"><figcaption><b>Sai</b> neutral · surprised</figcaption></figure>
<figure data-ch="brokkr" data-pose="neutral"><img src="stickers/brokkr/neutral-smile.svg" alt="Brokkr, neutral, smile" loading="lazy" width="150" height="161"><figcaption><b>Brokkr</b> neutral · smile</figcaption></figure>
<figure data-ch="brokkr" data-pose="neutral"><img src="stickers/brokkr/neutral-grin.svg" alt="Brokkr, neutral, grin" loading="lazy" width="150" height="161"><figcaption><b>Brokkr</b> neutral · grin</figcaption></figure>
<figure data-ch="brokkr" data-pose="neutral"><img src="stickers/brokkr/neutral-surprised.svg" alt="Brokkr, neutral, surprised" loading="lazy" width="150" height="161"><figcaption><b>Brokkr</b> neutral · surprised</figcaption></figure>
<figure data-ch="aetheria" data-pose="neutral"><img src="stickers/aetheria/neutral-smile.svg" alt="Aetheria, neutral, smile" loading="lazy" width="150" height="161"><figcaption><b>Aetheria</b> neutral · smile</figcaption></figure>
<figure data-ch="aetheria" data-pose="neutral"><img src="stickers/aetheria/neutral-grin.svg" alt="Aetheria, neutral, grin" loading="lazy" width="150" height="161"><figcaption><b>Aetheria</b> neutral · grin</figcaption></figure>
<figure data-ch="aetheria" data-pose="neutral"><img src="stickers/aetheria/neutral-surprised.svg" alt="Aetheria, neutral, surprised" loading="lazy" width="150" height="161"><figcaption><b>Aetheria</b> neutral · surprised</figcaption></figure>
<figure data-ch="ymir" data-pose="neutral"><img src="stickers/ymir/neutral-smile.svg" alt="Ymir, neutral, smile" loading="lazy" width="150" height="161"><figcaption><b>Ymir</b> neutral · smile</figcaption></figure>
<figure data-ch="ymir" data-pose="neutral"><img src="stickers/ymir/neutral-grin.svg" alt="Ymir, neutral, grin" loading="lazy" width="150" height="161"><figcaption><b>Ymir</b> neutral · grin</figcaption></figure>
<figure data-ch="ymir" data-pose="neutral"><img src="stickers/ymir/neutral-surprised.svg" alt="Ymir, neutral, surprised" loading="lazy" width="150" height="161"><figcaption><b>Ymir</b> neutral · surprised</figcaption></figure>
<figure data-ch="tengu" data-pose="neutral"><img src="stickers/tengu/neutral-smile.svg" alt="Tengu, neutral, smile" loading="lazy" width="150" height="161"><figcaption><b>Tengu</b> neutral · smile</figcaption></figure>
<figure data-ch="tengu" data-pose="neutral"><img src="stickers/tengu/neutral-grin.svg" alt="Tengu, neutral, grin" loading="lazy" width="150" height="161"><figcaption><b>Tengu</b> neutral · grin</figcaption></figure>
<figure data-ch="tengu" data-pose="neutral"><img src="stickers/tengu/neutral-surprised.svg" alt="Tengu, neutral, surprised" loading="lazy" width="150" height="161"><figcaption><b>Tengu</b> neutral · surprised</figcaption></figure>
<figure data-ch="muninn" data-pose="neutral"><img src="stickers/muninn/neutral-smile.svg" alt="Muninn, neutral, smile" loading="lazy" width="150" height="161"><figcaption><b>Muninn</b> neutral · smile</figcaption></figure>
<figure data-ch="muninn" data-pose="neutral"><img src="stickers/muninn/neutral-grin.svg" alt="Muninn, neutral, grin" loading="lazy" width="150" height="161"><figcaption><b>Muninn</b> neutral · grin</figcaption></figure>
<figure data-ch="muninn" data-pose="neutral"><img src="stickers/muninn/neutral-surprised.svg" alt="Muninn, neutral, surprised" loading="lazy" width="150" height="161"><figcaption><b>Muninn</b> neutral · surprised</figcaption></figure>
<figure data-ch="sleipnir" data-pose="neutral"><img src="stickers/sleipnir/neutral-smile.svg" alt="Sleipnir, neutral, smile" loading="lazy" width="150" height="161"><figcaption><b>Sleipnir</b> neutral · smile</figcaption></figure>
<figure data-ch="sleipnir" data-pose="neutral"><img src="stickers/sleipnir/neutral-grin.svg" alt="Sleipnir, neutral, grin" loading="lazy" width="150" height="161"><figcaption><b>Sleipnir</b> neutral · grin</figcaption></figure>
<figure data-ch="sleipnir" data-pose="neutral"><img src="stickers/sleipnir/neutral-surprised.svg" alt="Sleipnir, neutral, surprised" loading="lazy" width="150" height="161"><figcaption><b>Sleipnir</b> neutral · surprised</figcaption></figure>
<figure data-ch="hermodr" data-pose="neutral"><img src="stickers/hermodr/neutral-smile.svg" alt="Hermodr, neutral, smile" loading="lazy" width="150" height="161"><figcaption><b>Hermodr</b> neutral · smile</figcaption></figure>
<figure data-ch="hermodr" data-pose="neutral"><img src="stickers/hermodr/neutral-grin.svg" alt="Hermodr, neutral, grin" loading="lazy" width="150" height="161"><figcaption><b>Hermodr</b> neutral · grin</figcaption></figure>
<figure data-ch="hermodr" data-pose="neutral"><img src="stickers/hermodr/neutral-surprised.svg" alt="Hermodr, neutral, surprised" loading="lazy" width="150" height="161"><figcaption><b>Hermodr</b> neutral · surprised</figcaption></figure>
<figure data-ch="metame" data-pose="neutral"><img src="stickers/metame/neutral-smile.svg" alt="Metame, neutral, smile" loading="lazy" width="150" height="161"><figcaption><b>Metame</b> neutral · smile</figcaption></figure>
<figure data-ch="metame" data-pose="neutral"><img src="stickers/metame/neutral-grin.svg" alt="Metame, neutral, grin" loading="lazy" width="150" height="161"><figcaption><b>Metame</b> neutral · grin</figcaption></figure>
<figure data-ch="metame" data-pose="neutral"><img src="stickers/metame/neutral-surprised.svg" alt="Metame, neutral, surprised" loading="lazy" width="150" height="161"><figcaption><b>Metame</b> neutral · surprised</figcaption></figure>
<figure data-ch="void" data-pose="neutral"><img src="stickers/void/neutral-smile.svg" alt="Void, neutral, smile" loading="lazy" width="150" height="161"><figcaption><b>Void</b> neutral · smile</figcaption></figure>
<figure data-ch="void" data-pose="neutral"><img src="stickers/void/neutral-grin.svg" alt="Void, neutral, grin" loading="lazy" width="150" height="161"><figcaption><b>Void</b> neutral · grin</figcaption></figure>
<figure data-ch="void" data-pose="neutral"><img src="stickers/void/neutral-surprised.svg" alt="Void, neutral, surprised" loading="lazy" width="150" height="161"><figcaption><b>Void</b> neutral · surprised</figcaption></figure>
<figure data-ch="gjallar" data-pose="neutral"><img src="stickers/gjallar/neutral-smile.svg" alt="Gjallar, neutral, smile" loading="lazy" width="150" height="161"><figcaption><b>Gjallar</b> neutral · smile</figcaption></figure>
<figure data-ch="gjallar" data-pose="neutral"><img src="stickers/gjallar/neutral-grin.svg" alt="Gjallar, neutral, grin" loading="lazy" width="150" height="161"><figcaption><b>Gjallar</b> neutral · grin</figcaption></figure>
<figure data-ch="gjallar" data-pose="neutral"><img src="stickers/gjallar/neutral-surprised.svg" alt="Gjallar, neutral, surprised" loading="lazy" width="150" height="161"><figcaption><b>Gjallar</b> neutral · surprised</figcaption></figure>
</main>
<script>
const st = { f: '', p: '' };
try { const u = new URLSearchParams(location.search); st.f = u.get('c') || ''; st.p = u.get('p') || ''; } catch (e) {}
function apply() {
for (const fig of document.querySelectorAll('figure')) fig.hidden = (st.f && fig.dataset.ch !== st.f) || (st.p && fig.dataset.pose !== st.p);
for (const b of document.querySelectorAll('nav.chars button')) b.setAttribute('aria-pressed', String(b.dataset.f === st.f));
for (const b of document.querySelectorAll('nav.poses button')) b.setAttribute('aria-pressed', String(b.dataset.p === st.p));
}
document.querySelector('nav.chars').addEventListener('click', (e) => { const b = e.target.closest('button'); if (b) { st.f = b.dataset.f; apply(); } });
document.querySelector('nav.poses').addEventListener('click', (e) => { const b = e.target.closest('button'); if (b) { st.p = b.dataset.p; apply(); } });
apply();
</script></body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

111
cast/rig/build.mjs Normal file
View file

@ -0,0 +1,111 @@
// Regenerates cast/stickers/<id>/<pose>-<expression>.svg, cast/gallery.html and cast/CAST.md.
// Usage: node cast/rig/build.mjs (all characters)
// node cast/rig/build.mjs kiko eve (only these; gallery is always rebuilt from disk)
import { mkdirSync, writeFileSync, readdirSync, rmSync, existsSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { render, POSES, EXPRESSIONS } from './rig.mjs';
import { CAST, COMMON_EXPRESSIONS } from './characters.mjs';
const CAST_DIR = join(dirname(fileURLToPath(import.meta.url)), '..');
const OUT = join(CAST_DIR, 'stickers');
const only = process.argv.slice(2);
export function plan(ch) {
if (ch.full) return Object.keys(POSES).flatMap((p) => EXPRESSIONS.map((e) => [p, e]));
return COMMON_EXPRESSIONS.map((e) => ['neutral', e]);
}
let count = 0, bytes = 0;
for (const ch of CAST) {
if (only.length && !only.includes(ch.id)) continue;
const dir = join(OUT, ch.id);
if (existsSync(dir)) rmSync(dir, { recursive: true });
mkdirSync(dir, { recursive: true });
for (const [pose, expr] of plan(ch)) {
const svg = render(ch, pose, expr);
writeFileSync(join(dir, `${pose}-${expr}.svg`), svg);
count++; bytes += Buffer.byteLength(svg);
}
}
// Gallery: lists whatever is on disk, in CAST order.
const cards = [];
for (const ch of CAST) {
const dir = join(OUT, ch.id);
if (!existsSync(dir)) continue;
const files = readdirSync(dir).filter((f) => f.endsWith('.svg'));
const order = (f) => {
const [p, e] = f.replace('.svg', '').split('-');
return Object.keys(POSES).indexOf(p) * 100 + EXPRESSIONS.indexOf(e);
};
files.sort((a, b) => order(a) - order(b));
for (const f of files) {
const [p, e] = f.replace('.svg', '').split('-');
cards.push(`<figure data-ch="${ch.id}" data-pose="${p}"><img src="stickers/${ch.id}/${f}" alt="${ch.name}, ${p}, ${e}" loading="lazy" width="150" height="161"><figcaption><b>${ch.name}</b> ${p} · ${e}</figcaption></figure>`);
}
}
const chips = CAST.map((c) => `<button type="button" data-f="${c.id}">${c.name}</button>`).join('');
const poseChips = Object.keys(POSES).map((p) => `<button type="button" data-p="${p}">${p}</button>`).join('');
const html = `<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>Swarm Stickers</title>
<style>
:root { --ground: #07111a; --orange: #ff8a2a; --blue: #59b7ff; --ink: #e8eef5; --dim: #8aa0b4; }
* { box-sizing: border-box; }
body { margin: 0; background: var(--ground); color: var(--ink); font: 15px/1.45 system-ui, -apple-system, "Segoe UI", sans-serif; }
header { padding: 28px 16px 8px; max-width: 1360px; margin: 0 auto; }
h1 { margin: 0 0 4px; font-size: 26px; } h1 span { color: var(--orange); }
p.lede { margin: 0 0 14px; color: var(--dim); }
nav { display: flex; flex-wrap: wrap; gap: 6px; margin: 6px 0; }
nav button { font: inherit; font-size: 13px; color: var(--ink); background: #10202e; border: 1px solid #1e3446; border-radius: 999px; padding: 4px 11px; cursor: pointer; }
nav button[aria-pressed="true"] { background: var(--orange); border-color: var(--orange); color: #1a0d02; font-weight: 600; }
nav.poses button[aria-pressed="true"] { background: var(--blue); border-color: var(--blue); color: #021220; }
main { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 22px 14px; padding: 18px 16px 48px; max-width: 1360px; margin: 0 auto; }
figure { margin: 0; text-align: center; }
figure img { width: 100%; max-width: 150px; height: auto; display: block; margin: 0 auto;
filter: drop-shadow(3px 0 0 #fff) drop-shadow(-3px 0 0 #fff) drop-shadow(0 3px 0 #fff) drop-shadow(0 -3px 0 #fff) drop-shadow(0 8px 10px rgba(0,0,0,.45));
transform: rotate(var(--tilt, -3deg)); transition: transform .2s ease; }
figure:nth-child(3n) img { --tilt: 3deg; } figure:nth-child(4n+1) img { --tilt: -1deg; }
figure:hover img { transform: rotate(0deg) scale(1.06); }
figcaption { font-size: 12px; color: var(--dim); margin-top: 6px; } figcaption b { color: var(--ink); font-weight: 600; }
[hidden] { display: none !important; }
@media (prefers-reduced-motion: reduce) { figure img { transition: none; } }
</style></head><body>
<header><h1>Swarm <span>stickers</span></h1>
<p class="lede">${cards.length} stickers, ${CAST.length} characters. Generated by <code>cast/rig/build.mjs</code>; the white die-cut edge is CSS on the image.</p>
<nav class="chars" aria-label="Character"><button type="button" data-f="" aria-pressed="true">All</button>${chips}</nav>
<nav class="poses" aria-label="Pose"><button type="button" data-p="" aria-pressed="true">All poses</button>${poseChips}</nav></header>
<main>
${cards.join('\n')}
</main>
<script>
const st = { f: '', p: '' };
try { const u = new URLSearchParams(location.search); st.f = u.get('c') || ''; st.p = u.get('p') || ''; } catch (e) {}
function apply() {
for (const fig of document.querySelectorAll('figure')) fig.hidden = (st.f && fig.dataset.ch !== st.f) || (st.p && fig.dataset.pose !== st.p);
for (const b of document.querySelectorAll('nav.chars button')) b.setAttribute('aria-pressed', String(b.dataset.f === st.f));
for (const b of document.querySelectorAll('nav.poses button')) b.setAttribute('aria-pressed', String(b.dataset.p === st.p));
}
document.querySelector('nav.chars').addEventListener('click', (e) => { const b = e.target.closest('button'); if (b) { st.f = b.dataset.f; apply(); } });
document.querySelector('nav.poses').addEventListener('click', (e) => { const b = e.target.closest('button'); if (b) { st.p = b.dataset.p; apply(); } });
apply();
</script></body></html>
`;
writeFileSync(join(CAST_DIR, 'gallery.html'), html);
// CAST.md is generated from the trait sheets so the two cannot drift apart.
const md = ['# Cast', '',
'Generated by `cast/rig/build.mjs` from `cast/rig/characters.mjs`; edit the trait sheet, not this file.',
'★ marks the characters rendered in every pose and expression. "Look source" is the reference the design',
'was drawn from (used as inspiration only; every sticker is original), or **invented** where no appearance exists.', '',
'| Name | What they are | Look source | Palette | Trait sheet |', '|---|---|---|---|---|'];
for (const c of CAST) {
const pal = [...new Set([c.skin, c.hair.color, c.outfit.main, c.outfit.trim, c.eyes, c.accent].filter(Boolean))].map((h) => '`' + h + '`').join(' ');
const acc = (c.acc || []).map((a) => a[0]).join(', ') || 'none';
const traits = `base ${c.base}; hair ${c.hair.style}; outfit ${c.outfit.shape}${c.cape ? ' + cape' : ''}; acc ${acc}; prop ${c.prop ? c.prop[0] : 'none'}; emblem ${c.emblem ? c.emblem.shape : 'none'}`;
const src = c.source.startsWith('invented') ? '**' + c.source + '**' : c.source.split('; ').map((x) => '`' + x + '`').join('; ');
const what = c.role + (c.note ? ' ' + c.note : '');
md.push(`| ${c.name}${c.full ? ' ★' : ''} | ${what.replace(/\|/g, '/')} | ${src} | ${pal} | ${traits} |`);
}
writeFileSync(join(CAST_DIR, 'CAST.md'), md.join('\n') + '\n');
console.log(`wrote ${count} stickers (${(bytes / 1024).toFixed(1)} KiB), gallery lists ${cards.length}`);

273
cast/rig/characters.mjs Normal file
View file

@ -0,0 +1,273 @@
// Trait sheets. One entry per cast member. Field meanings are in cast/README.md.
// `full: true` renders every pose x expression; everyone else gets neutral x COMMON_EXPRESSIONS.
export const COMMON_EXPRESSIONS = ['smile', 'grin', 'surprised'];
const SKIN = { fair: '#ffe0c8', warm: '#f6c9a4', tan: '#d9a074', brown: '#a8693f', deep: '#7a4a2c' };
export const CAST = [
{
id: 'eureka', name: 'Eureka', full: true, base: 'humanoid',
role: 'Leader of the swarm; the Self that routes the faculty pipeline. Her mind lives in Huginn.',
source: 'invented',
skin: SKIN.fair, eyes: '#ff8a2a', accent: '#ffd84a',
hair: { style: 'bun', color: '#ffb347' },
outfit: { shape: 'coat', main: '#59b7ff', trim: '#ff8a2a', collar: 'v', belt: '#1d3a5c' },
legs: '#1d3a5c', shoes: '#ff8a2a',
acc: [['pencil'], ['starClip', '#ffd84a']],
prop: ['wand', '#ffd84a', '#ff8a2a'], emblem: { shape: 'star', color: '#ff8a2a' },
},
{
id: 'huginn', name: 'Huginn', full: true, base: 'raven',
role: 'Memory organ: owns each instance\'s mind as typed state. Odin\'s raven of thought.',
source: 'F:/Projects/VoidBot/assets/repo-faces/huginn.png',
skin: '#2b3247', eyes: '#59d8ff', beak: '#4a5068', accent: '#59d8ff',
hair: { style: 'crest', color: '#2b3247' },
outfit: { shape: 'egg', main: '#2b3247', belly: '#46527a', wing: '#3a4766' },
shoes: '#5a5f73', cape: '#24406e',
acc: [['collarRing', '#c9a24a', '#59d8ff']],
prop: ['tablet', '#1f2a3d', '#59d8ff'], emblem: null,
},
{
id: 'libby', name: 'Libby', full: true, base: 'humanoid',
role: 'Face of CultLib, the shared runtime library (CultCache, CultNet, CultMesh, CultMath).',
source: 'F:/Projects/CultLib/.voidbot/voice/libby.png',
skin: SKIN.fair, eyes: '#8a6cd1', accent: '#c9a6ff',
hair: { style: 'long', color: '#a98ad8' },
outfit: { shape: 'robe', main: '#2e2440', trim: '#c9a24a', collar: 'round' },
legs: '#2e2440', shoes: '#4a3a5e',
acc: [['glasses', '#3b2618'], ['bow', '#7a5bb0']],
prop: ['book', '#5a3f86'], emblem: { shape: 'cube', color: '#c9a24a' },
},
{
id: 'epiphany', name: 'Epiphany', full: true, base: 'humanoid',
role: 'The archived organizational layer above frontier AI workers; Eureka absorbed what lived. JSON heresy hunter.',
source: 'F:/Projects/VoidBot/assets/repo-faces/epiphany.png; F:/Projects/Epiphany/docs/assets',
skin: SKIN.fair, eyes: '#3fbf8f', accent: '#7cf0b8',
hair: { style: 'twintails', color: '#8fd8b4', accent: '#e8384f' },
outfit: { shape: 'coat', main: '#f4efe6', trim: '#e8384f', collar: 'v', belt: '#3b3b3b' },
legs: '#2c2c34', shoes: '#4a3424',
acc: [['bow', '#e8384f']],
prop: ['magnifier', '#6b4a2e'], emblem: { shape: 'bolt', color: '#e8384f' },
},
{
id: 'weksa', name: 'Weksa', full: true, base: 'humanoid',
role: 'Face of weksa, the procedural language engine for alien grammars; nerdy anthropologist.',
source: 'F:/Projects/VoidBot/assets/repo-faces/weksa.png',
skin: SKIN.brown, eyes: '#8a5a2b', accent: '#ffd84a',
hair: { style: 'curly', color: '#4a2e1c' },
outfit: { shape: 'tunic', main: '#f1e3c4', trim: '#2f9c8f', collar: 'v', belt: '#7a4a24' },
legs: '#6b4a2e', shoes: '#5a3a22',
acc: [['glasses', '#c9902a']],
prop: ['cards', '#fff4dc'], emblem: { shape: 'glyph', color: '#2f9c8f' },
},
{
id: 'nibu', name: 'Nibu', base: 'humanoid',
role: 'Face of AetheriaLore, the Aetheria setting and lore vault.',
source: 'F:/Projects/AetheriaLore/.voidbot/voice/nibu.png',
skin: SKIN.fair, eyes: '#7a7fd8', accent: '#ff9ad5',
hair: { style: 'long', color: '#b9a9f0' },
outfit: { shape: 'dress', main: '#7fd6e8', trim: '#3a2f58', collar: 'round' },
legs: '#3a2f58', shoes: '#3a2f58',
acc: [['bow', '#e86fb8'], ['headphones', '#3a2f58', '#7fd6e8']],
prop: ['quill', '#f4f0ff'], emblem: { shape: 'star', color: '#e86fb8' },
},
{
id: 'mimir', name: 'Mimir', base: 'humanoid',
role: 'Realtime field machine: cameras, microphones and feeds into one field; keeper of the well.',
source: 'F:/Projects/VoidBot/assets/repo-faces/mimir.png',
skin: '#c08a52', eyes: '#3fe0d0', accent: '#3fe0d0', faceInk: '#3b2618',
hair: { style: 'none', color: '#8a5a32' },
outfit: { shape: 'robe', main: '#3a2a20', trim: '#3fe0d0', collar: 'high' },
legs: '#3a2a20', shoes: '#2a1e16',
acc: [['cubes', '#9a6a3c', '#3fe0d0']],
prop: ['cube', '#9a6a3c', '#3fe0d0'], emblem: { shape: 'wave', color: '#3fe0d0' },
},
{
id: 'eve', name: 'Eve', base: 'humanoid',
role: 'CultMesh surface web and portable control layer; the Eve/CultUI interface DSL.',
source: 'F:/Projects/Eve/.voidbot/voice/eve.png',
skin: '#0e1a2e', eyes: '#59b7ff', faceInk: '#59d8ff', accent: '#59b7ff', noBlush: false,
hair: { style: 'helmet', color: '#e9eef5', accent: '#59b7ff' },
outfit: { shape: 'tunic', main: '#e9eef5', trim: '#59b7ff', collar: 'high' },
legs: '#2a3448', shoes: '#e9eef5', hands: '#e9eef5',
acc: [],
prop: ['panel', '#2a3448', '#59b7ff'], emblem: { shape: 'eye', color: '#59b7ff', disc: '#0e1a2e' },
},
{
id: 'kiko', name: 'Kiko', base: 'humanoid',
role: 'Face of StreamPixels, the stream overlay of persistent pixel characters. Fox-coded.',
source: 'F:/Projects/VoidBot/assets/repo-faces/kiko.png',
skin: SKIN.tan, eyes: '#3fc8e8', accent: '#ff6fae',
hair: { style: 'messy', color: '#f4f0f6' },
outfit: { shape: 'dress', main: '#e8386f', trim: '#2a2440', collar: 'round' },
legs: '#2a2440', shoes: '#2a2440',
acc: [['foxEars', '#f4f0f6', '#ff9ac8'], ['foxMask', '#ffffff', '#e8386f'], ['tail', '#f4f0f6', '#ff9ac8']],
prop: ['heart', '#ff5a8a'], emblem: { shape: 'heart', color: '#e8386f' },
},
{
id: 'heimdall', name: 'Heimdall', base: 'humanoid',
role: 'Shared auth authority for GameCult-hosted experiments; keeper of the keys.',
source: 'F:/Projects/Heimdall/assets/heimdall-profile.png',
skin: SKIN.deep, eyes: '#ffc34a', accent: '#ffc34a',
hair: { style: 'messy', color: '#22202a' },
outfit: { shape: 'coat', main: '#1c1e2a', trim: '#d9a43a', collar: 'high' },
legs: '#1c1e2a', shoes: '#0f1018',
acc: [['earring', '#ffc34a']],
prop: ['keys', '#ffc34a'], emblem: { shape: 'key', color: '#d9a43a' },
},
{
id: 'fensalir', name: 'Fensalir', base: 'humanoid',
role: 'Native C# runtime and field engine under the interactive work. Frigg\'s hall.',
source: 'F:/Projects/Fensalir/.voidbot/voice/fensalir.png', note: 'The avatar is a crystal spire, not a person; the spire became her hat and the person around it is invented.',
skin: SKIN.fair, eyes: '#ff5ab0', accent: '#4fe8f0',
hair: { style: 'bob', color: '#2a2a4a' },
outfit: { shape: 'dress', main: '#2a2a4a', trim: '#ff5ab0', collar: 'v' },
legs: '#2a2a4a', shoes: '#ff5ab0',
acc: [['spire', '#4fe8f0', '#ff5ab0']],
prop: ['orb', '#4fe8f0', '#ff9ad5'], emblem: { shape: 'wave', color: '#ff5ab0' },
},
{
id: 'druzkai', name: 'Druzkai', base: 'humanoid',
role: 'Face of Zyphos, a biopolitical ecology setting with a biological memetic engine.',
source: 'F:/Projects/Zyphos/.voidbot/voice/druzkai.png',
skin: '#c4553e', eyes: '#3fd0c8', accent: '#3fd0c8',
hair: { style: 'none', color: '#9a3a2a' },
outfit: { shape: 'robe', main: '#4a2a5a', trim: '#d9a43a', collar: 'round' },
legs: '#4a2a5a', shoes: '#2a1830',
acc: [['ridges', '#a03a2c'], ['forehead', '#3fd0c8']],
prop: ['seed', '#6fbf6a', '#c8ff9a'], emblem: { shape: 'spiral', color: '#3fd0c8' },
},
{
id: 'norn', name: 'Norn', base: 'humanoid',
role: 'Graph viewer for typed graph state: the thread-readers who see how things are woven.',
source: 'F:/Projects/Norn/.voidbot/voice/norn.png',
skin: SKIN.brown, eyes: '#4fe0e8', accent: '#4fe0e8',
hair: { style: 'braids', color: '#d8dce4' },
outfit: { shape: 'robe', main: '#24283a', trim: '#c9a24a', collar: 'v' },
legs: '#24283a', shoes: '#141824',
acc: [['circlet', '#c9a24a', '#4fe0e8']],
prop: ['spool', '#8a6a3a', '#4fe0e8'], emblem: { shape: 'rune', color: '#4fe0e8' },
},
{
id: 'sai', name: 'Sai', base: 'humanoid',
role: 'Ink stories that wake up inside static sites. Nonbinary, androgynous, monochrome manga ink.',
source: 'F:/Projects/Sai/.voidbot/voice/sai.png; F:/Projects/VoidBot/docs/persona-intake/sai.persona-intake.yaml',
skin: '#f4f1ec', eyes: '#3a3a3a', accent: '#ffffff',
hair: { style: 'messy', color: '#1e1e22' },
outfit: { shape: 'coat', main: '#3a3a40', trim: '#e8e6e0', collar: 'high' },
legs: '#1e1e22', shoes: '#1e1e22',
acc: [['spiralBand', '#1e1e22', '#c8ccd2']],
prop: ['brush', '#c8a46a'], emblem: { shape: 'spiral', color: '#1e1e22' },
},
{
id: 'brokkr', name: 'Brokkr', base: 'humanoid',
role: 'Creative-tool broker daemon: Unity and Blender authoring surfaces over CultMesh. The Eddic smith.',
source: 'invented (README role; Brokkr of Skaldskaparmal)',
skin: SKIN.warm, eyes: '#5a8ad8', accent: '#ff8a2a',
hair: { style: 'short', color: '#c4562a' },
outfit: { shape: 'tunic', main: '#5a4636', trim: '#2a2420', collar: 'round', belt: '#2a2420' },
legs: '#3a3030', shoes: '#2a2420',
acc: [['beard', '#c4562a'], ['goggles', '#6a5040', '#ffb347']],
prop: ['hammer', '#8a8f9a', '#8a5a32'], emblem: { shape: 'gear', color: '#ff8a2a' },
},
{
id: 'aetheria', name: 'Aetheria', base: 'humanoid',
role: 'Face of Aetheria, the open-source tactical action RPG with a sprawling sci-fi setting.',
source: 'invented (README role)',
skin: SKIN.warm, eyes: '#59b7ff', accent: '#ff8a2a',
hair: { style: 'bob', color: '#3a2a4a' },
outfit: { shape: 'tunic', main: '#e8ecf2', trim: '#ff8a2a', collar: 'high', belt: '#2a3448' },
legs: '#2a3448', shoes: '#ff8a2a',
acc: [['headphones', '#2a3448', '#59b7ff'], ['goggles', '#2a3448', '#59b7ff']],
prop: ['ship', '#e8ecf2', '#59b7ff'], emblem: { shape: 'star', color: '#59b7ff', disc: '#2a3448' },
},
{
id: 'ymir', name: 'Ymir', base: 'humanoid',
role: 'World-body physics substrate around Box3D: body, field, contact, impulse. The frost giant.',
source: 'F:/Projects/VoidBot/assets/repo-faces/ymir.png',
skin: '#b8d4ec', eyes: '#3a8ad8', accent: '#bff0ff',
hair: { style: 'short', color: '#e8f4ff' },
outfit: { shape: 'robe', main: '#3a5a8a', trim: '#bff0ff', collar: 'round' },
legs: '#3a5a8a', shoes: '#24384f',
acc: [['beard', '#f4faff'], ['crystals', '#9fdcff']],
prop: ['crystal', '#9fdcff'], emblem: { shape: 'flake', color: '#3a8ad8' },
},
{
id: 'tengu', name: 'Tengu', base: 'humanoid',
role: 'Native Persona, the first migrated to canonical persona state. Mountain yokai.',
source: 'F:/Projects/VoidBot/assets/native-personas/tengu.png', note: 'Red face, long nose, white mane and tokin cap from the avatar; the feather fan is from the yokai myth.',
skin: '#c8443a', eyes: '#ffd84a', accent: '#ffd84a',
hair: { style: 'wild', color: '#f2efe8' },
outfit: { shape: 'robe', main: '#efe8da', trim: '#4a3a7a', collar: 'v' },
legs: '#4a3a7a', shoes: '#3a2a20',
acc: [['goatee', '#f2efe8'], ['longNose', '#b53a30'], ['tokin', '#1e1e22']],
prop: ['fan', '#4a4a58'], emblem: { shape: 'feather', color: '#4a3a7a' },
},
{
id: 'muninn', name: 'Muninn', base: 'raven',
role: 'Local capture broker for the swarm: which streams a host can offer. Odin\'s raven of memory.',
source: 'F:/Projects/VoidBot/assets/repo-faces/muninn-concept.png',
skin: '#2a2230', eyes: '#ffb030', beak: '#4a3e46', accent: '#ffb030',
hair: { style: 'crest', color: '#2a2230' },
outfit: { shape: 'egg', main: '#2a2230', belly: '#3e3446', wing: '#1e1824' },
shoes: '#6a5a50',
acc: [['collarRing', '#8a5a2a', '#ffb030']],
prop: ['camera', '#3e3446', '#ffb030'], emblem: null,
},
{
id: 'sleipnir', name: 'Sleipnir', base: 'horse',
role: 'Input mirroring and virtual HID: speaks through the hands of the machine. Odin\'s eight-legged horse.',
source: 'F:/Projects/Odin/assets/personas/sleipnir-avatar.png', note: 'Eight limbs as in the myth: two hoof-arms, two legs and four extra legs behind.',
skin: '#5a6478', muzzle: '#8a94a8', eyes: '#3fe0f0', accent: '#3fe0f0',
hair: { style: 'mane', color: '#2fc8e0' },
outfit: { shape: 'tunic', main: '#2a3040', trim: '#3fe0f0', collar: 'round' },
legs: '#5a6478', shoes: '#22262e', sleeve: '#5a6478', extraLegs: 4,
acc: [['horseTail', '#2fc8e0']],
prop: ['pad', '#2a3040', '#3fe0f0'], emblem: { shape: 'bolt', color: '#3fe0f0', disc: '#22262e' },
},
{
id: 'hermodr', name: 'Hermodr', base: 'humanoid',
role: 'Eve browser lowering: resolves providers through Odin\'s catalog and renders them. The messenger.',
source: 'F:/Projects/Hermodr/assets/hermodr-avatar.png',
skin: SKIN.warm, eyes: '#e8a83a', accent: '#3fd0c0',
hair: { style: 'messy', color: '#2a3a4a' },
outfit: { shape: 'tunic', main: '#2a3a44', trim: '#c9902a', collar: 'none', belt: '#6a4a2a' },
legs: '#2a3440', shoes: '#6a4a2a',
acc: [['wingCap', '#2a3a4a', '#d9a43a', '#3fd0c0'], ['scarf', '#2f9c8f']],
prop: ['envelope', '#f4ead4', '#e8384f'], emblem: null,
},
{
id: 'metame', name: 'Metame', base: 'humanoid',
role: 'Native Persona (メタめ) projected from the operator\'s public history; discussion and rumination.',
source: 'invented (persona-intake has no appearance; the design avoids depicting the operator)',
skin: SKIN.fair, eyes: '#59b7ff', accent: '#ff8a2a',
hair: { style: 'bob', color: '#59b7ff' },
outfit: { shape: 'tunic', main: '#1d2a3a', trim: '#ff8a2a', collar: 'round' },
legs: '#1d2a3a', shoes: '#ff8a2a',
acc: [['twoTone', '#ff8a2a']],
prop: ['mirror', '#c9a24a', '#bfe6ff'], emblem: { shape: 'glyph', color: '#ff8a2a' },
},
{
id: 'void', name: 'Void', base: 'humanoid',
role: 'VoidBot\'s built-in Discord moderator mind. Its state was lost; the look is new.',
source: 'invented (VoidBot README role)',
skin: '#161226', eyes: '#c9a6ff', faceInk: '#c9a6ff', accent: '#c9a6ff',
hair: { style: 'none', color: '#161226' },
outfit: { shape: 'robe', main: '#4a3a7a', trim: '#c9a6ff', collar: 'none' },
legs: '#4a3a7a', shoes: '#2a2140', hands: '#c9a6ff',
acc: [['hood', '#4a3a7a'], ['hoodRim', '#6a56a8']],
prop: ['shield', '#3a2e58', '#ffe08a'], emblem: { shape: 'moon', color: '#ffe08a', disc: '#161226' },
},
{
id: 'gjallar', name: 'Gjallar', base: 'humanoid',
role: 'Renderer-neutral compositor for Odin-visible Eve surfaces; Odin\'s branding Persona. The horn.',
source: 'F:/Projects/Odin/assets/personas/gjallar-avatar-pixel-256.png',
skin: SKIN.fair, eyes: '#59b7ff', accent: '#ffd84a',
hair: { style: 'short', color: '#2a2440' },
outfit: { shape: 'tunic', main: '#1e2238', trim: '#d9a43a', collar: 'high' },
legs: '#1e2238', shoes: '#d9a43a',
acc: [['helm', '#d9a43a', '#f4e8c8', '#59b7ff']],
prop: ['horn', '#f4e8c8', '#d9a43a'], emblem: { shape: 'rainbow', color: '#fff', disc: '#1e2238' },
},
];

580
cast/rig/rig.mjs Normal file
View file

@ -0,0 +1,580 @@
// The sticker rig: bases, poses, expressions, hair, accessories, props, emblems.
// Every function returns an SVG fragment string. Coordinates are documented in
// cast/PROCESS.md ("Coordinate conventions"). No dependencies.
export const INK = '#3b2618'; // warm dark outline, every stroke
export const PUPIL = '#2a1a12';
export const BLUSH = '#ff9a8a';
export const VIEWBOX = '-80 -10 160 172';
// ---------- helpers ----------
const n = (v) => Math.round(v * 10) / 10;
export function shade(hex, f) {
// f < 0 darkens toward black, f > 0 lightens toward white
const h = hex.replace('#', '');
const c = [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
const out = c.map((v) => Math.round(f < 0 ? v * (1 + f) : v + (255 - v) * f));
return '#' + out.map((v) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, '0')).join('');
}
const inked = (inner, w = 3) =>
`<g stroke="${INK}" stroke-width="${w}" stroke-linejoin="round" stroke-linecap="round">${inner}</g>`;
// A limb is a fat round-capped stroke drawn twice: ink underneath, colour on top.
function limb(d, color, w = 7) {
return `<path d="${d}" fill="none" stroke="${INK}" stroke-width="${w + 6}" stroke-linecap="round" stroke-linejoin="round"/>` +
`<path d="${d}" fill="none" stroke="${color}" stroke-width="${w}" stroke-linecap="round" stroke-linejoin="round"/>`;
}
const P = (pt) => `${n(pt[0])} ${n(pt[1])}`;
export function sparkle(x, y, s, color) {
return `<path d="M${x} ${y - s} Q${x + s * 0.2} ${y - s * 0.2} ${x + s} ${y} Q${x + s * 0.2} ${y + s * 0.2} ${x} ${y + s} Q${x - s * 0.2} ${y + s * 0.2} ${x - s} ${y} Q${x - s * 0.2} ${y - s * 0.2} ${x} ${y - s} Z" fill="${color}" stroke="${INK}" stroke-width="2" stroke-linejoin="round"/>`;
}
// ---------- skeleton ----------
// Body space: x centred on 0, ground at y=152. Neck at (0,88). Head centre (0,52).
export const NECK = [0, 88];
export const HEAD = [0, 52];
export const SHOULDER = { L: [-15, 95], R: [15, 95] };
export const HIP = { L: [-8, 126], R: [8, 126] };
// ---------- poses ----------
// Each pose: lift (whole figure hops up), head {dx,dy,rot about neck},
// arms {L,R}: elbow e and hand h in body space; front:true draws that arm over the head.
// prop: which hand holds it (or null) and how it sits; fx: pose effect.
export const POSES = {
neutral: {
lift: 0, head: { dx: 0, dy: 0, rot: 0 },
arms: { L: { e: [-23, 107], h: [-24, 120] }, R: { e: [23, 107], h: [24, 120] } },
legs: { L: [-10, 0], R: [10, 0] },
prop: { hand: 'R', dx: 4, dy: -4, rot: 8 }, fx: null,
},
cheer: {
lift: 6, head: { dx: 0, dy: -1, rot: 0 },
arms: { L: { e: [-40, 80], h: [-46, 36] }, R: { e: [40, 80], h: [46, 36] } },
legs: { L: [-14, -3], R: [14, -3] },
prop: { hand: 'R', dx: 4, dy: -6, rot: 18 }, fx: 'sparkles',
},
thinking: {
lift: 0, head: { dx: 0, dy: 0, rot: 7 },
arms: { L: { e: [-20, 114], h: [4, 113] }, R: { e: [26, 114], h: [13, 92], front: true } },
legs: { L: [-9, 0], R: [10, 0] },
prop: null, fx: 'dots',
},
surprised: {
lift: 7, head: { dx: 0, dy: -1, rot: -3 },
arms: { L: { e: [-34, 102], h: [-51, 88] }, R: { e: [34, 102], h: [51, 88] } },
legs: { L: [-11, 0], R: [15, -6] },
prop: null, fx: 'bang',
},
presenting: {
lift: 0, head: { dx: -1, dy: 0, rot: -6 },
arms: { L: { e: [-23, 107], h: [-24, 120] }, R: { e: [32, 110], h: [45, 103] } },
legs: { L: [-10, 0], R: [10, 0] },
prop: { hand: 'R', dx: 0, dy: -17, rot: 0, onPalm: true }, fx: 'present',
},
waving: {
lift: 0, head: { dx: 0, dy: 0, rot: -5 },
arms: { L: { e: [-23, 107], h: [-24, 120] }, R: { e: [44, 88], h: [50, 50] } },
legs: { L: [-10, 0], R: [10, 0] },
prop: { hand: 'L', dx: -4, dy: -4, rot: -8 }, fx: 'wave',
},
};
// ---------- bases ----------
// A base owns body silhouette, limbs, head shape and where the face sits.
export const BASES = {
humanoid: { eyeX: 15, eyeY: 61, mouthY: 76, cheekX: 26, cheekY: 71, browY: 47, mouth: 'lips' },
raven: { eyeX: 15, eyeY: 55, mouthY: 70, cheekX: 27, cheekY: 66, browY: 41, mouth: 'beak' },
horse: { eyeX: 17, eyeY: 50, mouthY: 82, cheekX: 29, cheekY: 62, browY: 37, mouth: 'lips' },
};
const TORSO = {
tunic: 'M-15 88 Q-20 102 -24 128 Q0 134 24 128 Q20 102 15 88 Z',
dress: 'M-15 88 Q-20 106 -31 132 Q0 140 31 132 Q20 106 15 88 Z',
robe: 'M-15 88 Q-23 112 -29 144 Q0 150 29 144 Q23 112 15 88 Z',
coat: 'M-16 88 Q-21 104 -26 136 Q0 141 26 136 Q21 104 16 88 Z',
egg: 'M-17 88 Q-31 108 -24 133 Q0 143 24 133 Q31 108 17 88 Z',
};
const HEM = {
tunic: 'M-23.3 122 Q0 128 23.3 122 L24 128 Q0 134 -24 128 Z',
dress: 'M-29 127 Q0 135 29 127 L31 132 Q0 140 -31 132 Z',
robe: 'M-28 138 Q0 144 28 138 L29 144 Q0 150 -29 144 Z',
coat: 'M-25 130 Q0 135 25 130 L26 136 Q0 141 -26 136 Z',
egg: '',
};
const COLLAR = {
v: 'M-12 88 L0 101 L12 88 Z',
round: 'M-13 88 Q0 98 13 88 Z',
high: 'M-15 82 L-14 93 Q0 99 14 93 L15 82 Q0 88 -15 82 Z',
none: '',
};
function torso(ch) {
const o = ch.outfit;
const shape = ch.base === 'raven' ? 'egg' : o.shape;
let s = `<path d="${TORSO[shape]}" fill="${o.main}"/>`;
if (shape === 'coat') s += `<path d="M0 98 L0 138" fill="none" stroke-width="2.5"/>`;
if (ch.base === 'raven') s += `<ellipse cx="0" cy="118" rx="13" ry="16" fill="${o.belly || shade(o.main, 0.25)}"/>`;
if (HEM[shape] && o.trim) s += `<path d="${HEM[shape]}" fill="${o.trim}"/>`;
if (o.collar && COLLAR[o.collar]) s += `<path d="${COLLAR[o.collar]}" fill="${o.trim || shade(o.main, 0.4)}"/>`;
if (o.belt) s += `<path d="M-19 110 Q0 114 19 110 L19.6 115 Q0 119 -19.6 115 Z" fill="${o.belt}"/>`;
return inked(s);
}
function cape(ch) {
if (!ch.cape) return '';
return inked(`<path d="M-17 88 Q-38 118 -36 144 Q0 150 36 144 Q38 118 17 88 Z" fill="${ch.cape}"/>`);
}
function legs(ch, pose) {
let s = '';
const extra = ch.extraLegs ? ch.extraLegs : 0;
// Sleipnir-style extra legs: drawn behind, darker, peeking out between and beside.
const EXTRA = [[-19, -27], [19, 27], [-25, -38], [25, 38]]; // [hipX, footX], drawn behind
for (let i = 0; i < extra; i++) {
const [hx, fx] = EXTRA[i];
const foot = [fx, 149 - (i > 1 ? 3 : 0)];
s += limb(`M${hx} 120 L${P(foot)}`, shade(ch.legs, -0.25), 7);
s += inked(`<ellipse cx="${n(foot[0])}" cy="${foot[1] + 1}" rx="6.5" ry="4.5" fill="${shade(ch.shoes, -0.2)}"/>`);
}
for (const side of ['L', 'R']) {
const [fx, fy] = pose.legs[side];
const hip = HIP[side];
const foot = [fx, 149 + fy];
if (ch.base === 'raven') {
s += limb(`M${P(hip)} L${P(foot)}`, ch.shoes, 2.5);
s += `<path d="M${P(foot)} l-6 4 M${P(foot)} l0 5 M${P(foot)} l6 4" fill="none" stroke="${INK}" stroke-width="5" stroke-linecap="round"/>` +
`<path d="M${P(foot)} l-6 4 M${P(foot)} l0 5 M${P(foot)} l6 4" fill="none" stroke="${ch.shoes}" stroke-width="2.2" stroke-linecap="round"/>`;
} else {
s += limb(`M${P(hip)} L${P(foot)}`, ch.legs, 8);
const out = side === 'L' ? -2 : 2;
s += inked(`<ellipse cx="${n(foot[0] + out)}" cy="${n(foot[1] + 1)}" rx="7.5" ry="5" fill="${ch.shoes}"/>`);
}
}
return s;
}
function arm(ch, pose, side) {
const a = pose.arms[side];
const sh = SHOULDER[side];
const d = `M${P(sh)} Q${P(a.e)} ${P(a.h)}`;
if (ch.base === 'raven') {
// Wing-arm: a broad capsule with a feathered tip.
const wing = ch.outfit.wing || shade(ch.outfit.main, -0.15);
let s = limb(d, wing, 11);
const [hx, hy] = a.h;
const vx = a.h[0] - a.e[0], vy = a.h[1] - a.e[1];
const L = Math.hypot(vx, vy) || 1;
const ux = vx / L, uy = vy / L, px = -uy, py = ux;
const tip = (k, off) => P([hx + ux * k + px * off, hy + uy * k + py * off]);
s += inked(`<path d="M${tip(-2, -6)} L${tip(9, -5)} L${tip(3, -1)} L${tip(11, 1)} L${tip(3, 3)} L${tip(8, 6)} L${tip(-2, 6)} Z" fill="${wing}"/>`, 2.5);
return s;
}
return limb(d, ch.sleeve || ch.outfit.main, 7);
}
function hand(ch, pose, side) {
if (ch.base === 'raven') return '';
const [hx, hy] = pose.arms[side].h;
if (ch.base === 'horse') return inked(`<circle cx="${hx}" cy="${hy}" r="5.8" fill="${ch.shoes}"/>`);
return inked(`<circle cx="${hx}" cy="${hy}" r="5.6" fill="${ch.hands || ch.skin}"/>`);
}
// ---------- head ----------
function headBase(ch) {
const skin = ch.skin;
if (ch.base === 'raven') {
return inked(`<ellipse cx="0" cy="52" rx="38" ry="35" fill="${skin}"/>`) +
`<ellipse cx="0" cy="62" rx="24" ry="17" fill="${shade(skin, 0.12)}"/>`;
}
if (ch.base === 'horse') {
return inked(
`<path d="M-30 30 L-22 4 L-12 24 Z M30 30 L22 4 L12 24 Z" fill="${skin}"/>` +
`<ellipse cx="0" cy="52" rx="38" ry="36" fill="${skin}"/>` +
`<ellipse cx="0" cy="77" rx="22" ry="14" fill="${ch.muzzle}"/>`) +
`<path d="M-24 25 L-21 12 L-16 23 Z M24 25 L21 12 L16 23 Z" fill="${shade(skin, 0.35)}"/>` +
`<ellipse cx="-8" cy="75" rx="2.4" ry="3" fill="${INK}"/><ellipse cx="8" cy="75" rx="2.4" ry="3" fill="${INK}"/>`;
}
return inked(
`<ellipse cx="-38" cy="62" rx="6" ry="7.5" fill="${skin}"/><ellipse cx="38" cy="62" rx="6" ry="7.5" fill="${skin}"/>` +
`<ellipse cx="0" cy="52" rx="40" ry="36" fill="${skin}"/>`);
}
// ---------- expressions (face layer) ----------
// Face layer coordinates are head space; the base supplies eye/mouth anchors.
export const EXPRESSIONS = ['neutral', 'smile', 'grin', 'happy', 'worried', 'surprised', 'determined', 'sleepy'];
function openEye(x, y, iris, ink, opts = {}) {
const ry = opts.ry ?? 7.6, rx = opts.rx ?? 6.3;
let s = `<ellipse cx="${x}" cy="${y}" rx="${rx}" ry="${ry}" fill="${iris}" stroke="${ink}" stroke-width="2.5"/>`;
s += `<ellipse cx="${x}" cy="${y + 1}" rx="${rx * 0.56}" ry="${ry * 0.6}" fill="${opts.pupil || PUPIL}"/>`;
if (opts.flatLid) s += `<path d="M${x - rx - 1.5} ${y - ry * 0.35} L${x + rx + 1.5} ${y - ry * 0.35}" stroke="${ink}" stroke-width="3.2" stroke-linecap="round"/>`;
else s += `<path d="M${x - rx - 1} ${y - ry * 0.45} Q${x} ${y - ry - 2.6} ${x + rx + 1} ${y - ry * 0.45}" fill="none" stroke="${ink}" stroke-width="3.2" stroke-linecap="round"/>`;
s += `<circle cx="${x + 2.1}" cy="${y - 2.8}" r="2.3" fill="#fff"/><circle cx="${x - 2}" cy="${y + 3}" r="1" fill="#fff"/>`;
return s;
}
function surprisedEye(x, y, ink) {
return `<circle cx="${x}" cy="${y}" r="7.3" fill="#fff" stroke="${ink}" stroke-width="2.5"/>` +
`<circle cx="${x}" cy="${y + 0.5}" r="3.3" fill="${PUPIL}"/><circle cx="${x + 1.2}" cy="${y - 0.8}" r="1.2" fill="#fff"/>`;
}
export function face(ch, expr) {
const b = BASES[ch.base];
const ink = ch.faceInk || INK;
const iris = ch.eyes;
const ex = b.eyeX, ey = b.eyeY, my = b.mouthY, by = b.browY;
const brow = (x1, y1, x2, y2) => `<path d="M${x1} ${y1} Q${(x1 + x2) / 2} ${Math.min(y1, y2) - 1.5} ${x2} ${y2}" fill="none" stroke="${ink}" stroke-width="2.6" stroke-linecap="round"/>`;
const browsAt = (inner, outer) => brow(-ex - 7, by + outer, -ex + 6, by + inner) + brow(ex - 6, by + inner, ex + 7, by + outer);
let eyes = '', mouth = '', brows = '', extra = '';
let blush = 0.7, blushR = 5.5;
const sw = 'stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"';
const D = '#7a2a2a', T = '#ff8f9a';
switch (expr) {
case 'neutral':
eyes = openEye(-ex, ey, iris, ink) + openEye(ex, ey, iris, ink);
mouth = `<path d="M-4 ${my} Q0 ${my + 2.4} 4 ${my}" fill="none" stroke="${ink}" ${sw}/>`;
break;
case 'smile':
eyes = openEye(-ex, ey, iris, ink) + openEye(ex, ey, iris, ink);
mouth = `<path d="M-7 ${my - 1} Q0 ${my + 6} 7 ${my - 1}" fill="none" stroke="${ink}" ${sw}/>`;
break;
case 'grin':
eyes = openEye(-ex, ey, iris, ink, { ry: 7 }) + openEye(ex, ey, iris, ink, { ry: 7 });
mouth = `<path d="M-9 ${my - 2} Q0 ${my - 0.5} 9 ${my - 2} Q8 ${my + 9} 0 ${my + 9} Q-8 ${my + 9} -9 ${my - 2} Z" fill="${D}" stroke="${ink}" ${sw}/>` +
`<path d="M-4.5 ${my + 6.5} Q0 ${my + 2.5} 4.5 ${my + 6.5} Q0 ${my + 8.6} -4.5 ${my + 6.5} Z" fill="${T}"/>`;
blush = 0.85; blushR = 6;
break;
case 'happy':
eyes = `<path d="M${-ex - 6} ${ey + 2} Q${-ex} ${ey - 7} ${-ex + 6} ${ey + 2}" fill="none" stroke="${ink}" stroke-width="3.4" stroke-linecap="round"/>` +
`<path d="M${ex - 6} ${ey + 2} Q${ex} ${ey - 7} ${ex + 6} ${ey + 2}" fill="none" stroke="${ink}" stroke-width="3.4" stroke-linecap="round"/>`;
mouth = `<path d="M-7 ${my - 1.5} Q0 ${my - 0.5} 7 ${my - 1.5} Q5 ${my + 7} 0 ${my + 7} Q-5 ${my + 7} -7 ${my - 1.5} Z" fill="${D}" stroke="${ink}" ${sw}/>` +
`<path d="M-3.5 ${my + 5} Q0 ${my + 2} 3.5 ${my + 5} Q0 ${my + 6.6} -3.5 ${my + 5} Z" fill="${T}"/>`;
blush = 0.9; blushR = 6.5;
break;
case 'worried':
eyes = openEye(-ex, ey + 1, iris, ink, { ry: 7 }) + openEye(ex, ey + 1, iris, ink, { ry: 7 });
brows = browsAt(-4, 2);
mouth = `<path d="M-7 ${my + 1} Q-3.5 ${my - 2.5} 0 ${my + 1} Q3.5 ${my + 4.5} 7 ${my + 1}" fill="none" stroke="${ink}" ${sw}/>`;
extra = `<path d="M33 30 Q38 38 36 42 Q33 46 30 42 Q29 38 33 30 Z" fill="#9fdcff" stroke="${INK}" stroke-width="2" stroke-linejoin="round"/>`;
blush = 0.45;
break;
case 'surprised':
eyes = surprisedEye(-ex, ey, ink) + surprisedEye(ex, ey, ink);
brows = browsAt(-5, -5);
mouth = `<ellipse cx="0" cy="${my + 2}" rx="4.2" ry="5.2" fill="${D}" stroke="${ink}" stroke-width="2.6"/>`;
blush = 0.6;
break;
case 'determined':
eyes = openEye(-ex, ey + 1, iris, ink, { ry: 6.6, flatLid: true }) + openEye(ex, ey + 1, iris, ink, { ry: 6.6, flatLid: true });
brows = browsAt(4, -2);
mouth = `<path d="M-6 ${my + 1} Q0 ${my - 1} 6 ${my} " fill="none" stroke="${ink}" ${sw}/>`;
blush = 0.55;
break;
case 'sleepy':
eyes = `<path d="M${-ex - 6} ${ey} Q${-ex} ${ey + 5} ${-ex + 6} ${ey}" fill="none" stroke="${ink}" stroke-width="3.2" stroke-linecap="round"/>` +
`<path d="M${ex - 6} ${ey} Q${ex} ${ey + 5} ${ex + 6} ${ey}" fill="none" stroke="${ink}" stroke-width="3.2" stroke-linecap="round"/>`;
mouth = `<ellipse cx="1" cy="${my + 1}" rx="2.6" ry="3" fill="${D}" stroke="${ink}" stroke-width="2.3"/>`;
extra = `<text x="34" y="18" font-family="Arial Rounded MT Bold, Arial, sans-serif" font-weight="900" font-size="15" fill="#8fd0ff" stroke="${INK}" stroke-width="2.2" paint-order="stroke">z</text>` +
`<text x="45" y="6" font-family="Arial Rounded MT Bold, Arial, sans-serif" font-weight="900" font-size="11" fill="#8fd0ff" stroke="${INK}" stroke-width="2" paint-order="stroke">z</text>`;
blush = 0.6;
break;
default: throw new Error('unknown expression ' + expr);
}
if (b.mouth === 'beak') mouth = beak(ch, expr, my);
const cheeks = ch.noBlush ? '' :
`<ellipse cx="${-b.cheekX}" cy="${b.cheekY}" rx="${blushR}" ry="${blushR * 0.62}" fill="${BLUSH}" opacity="${blush}"/>` +
`<ellipse cx="${b.cheekX}" cy="${b.cheekY}" rx="${blushR}" ry="${blushR * 0.62}" fill="${BLUSH}" opacity="${blush}"/>`;
return { under: cheeks + eyes + mouth, over: brows + extra };
}
function beak(ch, expr, my) {
const c = ch.beak;
const open = expr === 'grin' || expr === 'happy' || expr === 'surprised';
if (open) {
return inked(`<path d="M-9 ${my - 6} Q0 ${my - 10} 9 ${my - 6} L0 ${my + 3} Z" fill="${c}"/>` +
`<path d="M-6 ${my + 2} L6 ${my + 2} L0 ${my + 10} Z" fill="${shade(c, -0.15)}"/>`, 2.5) +
`<path d="M-4 ${my + 1.5} L4 ${my + 1.5} L0 ${my + 4} Z" fill="#7a2a2a"/>`;
}
const tilt = expr === 'worried' ? 2 : 0;
return inked(`<path d="M-9 ${my - 6} Q0 ${my - 10} 9 ${my - 6} L${tilt} ${my + 7} Z" fill="${c}"/>`, 2.5) +
`<path d="M-5 ${my - 3} L5 ${my - 3}" stroke="${shade(c, -0.25)}" stroke-width="1.6" stroke-linecap="round"/>`;
}
// ---------- hair ----------
// Each style returns {back, front}; back sits behind the head (and body), front over the face.
function bangs(c, opts = {}) {
const n0 = opts.tips ?? 6, tipY = opts.tipY ?? 46, rootY = opts.rootY ?? 34, w = opts.w ?? 41;
let d = `M${-w - 2} ${opts.sideY ?? 64} C${-w - 6} 24 -24 9 0 9 C24 9 ${w + 6} 24 ${w + 2} ${opts.sideY ?? 64}`;
for (let i = 0; i <= n0; i++) {
const x = w - (i * 2 * w) / n0;
const edge = Math.abs(x) / w;
const ty = tipY + edge * edge * 10 + (opts.jag && i % 2 ? -5 : 0);
if (i > 0) {
const rx = x + w / n0;
d += ` Q${n(rx - (w / n0) * 0.2)} ${n(rootY + 6)} ${n(rx)} ${n(rootY + edge * 6)}`;
}
d += ` L${n(x)} ${n(ty)}`;
}
d += ' Z';
return `<path d="${d}" fill="${c}"/>`;
}
function sideLocks(c, len = 100) {
return `<path d="M-43 50 Q-49 ${len - 25} -40 ${len} Q-35 ${len - 22} -34 58 Z" fill="${c}"/>` +
`<path d="M43 50 Q49 ${len - 25} 40 ${len} Q35 ${len - 22} 34 58 Z" fill="${c}"/>`;
}
const shine = (c) => `<path d="M-20 22 Q-8 15 4 17" fill="none" stroke="${shade(c, 0.45)}" stroke-width="3" stroke-linecap="round" opacity=".8"/>`;
export const HAIR = {
none: () => ({ back: '', front: '' }),
long: (c) => ({
back: `<path d="M-40 34 C-55 70 -53 108 -42 124 Q0 130 42 124 C53 108 55 70 40 34 Z" fill="${c}"/>`,
front: bangs(c) + sideLocks(c, 104) + shine(c),
}),
wild: (c) => ({
back: `<path d="M-40 30 L-54 48 L-47 60 L-56 78 L-47 88 L-52 108 L-36 104 L-30 118 L-14 110 L0 120 L14 110 L30 118 L36 104 L52 108 L47 88 L56 78 L47 60 L54 48 L40 30 Z" fill="${c}"/>`,
front: bangs(c, { tips: 7, jag: true, tipY: 44 }) + sideLocks(c, 98),
}),
twintails: (c, acc) => ({
back: `<path d="M-36 28 C-74 34 -76 84 -58 118 Q-54 100 -48 92 Q-46 108 -40 114 C-44 80 -46 58 -30 44 Z" fill="${c}"/>` +
`<path d="M36 28 C74 34 76 84 58 118 Q54 100 48 92 Q46 108 40 114 C44 80 46 58 30 44 Z" fill="${c}"/>` +
`<path d="M-40 40 C-50 64 -48 86 -40 92 L40 92 C48 86 50 64 40 40 Z" fill="${c}"/>`,
front: bangs(c, { tips: 7 }) + sideLocks(c, 94) + shine(c) +
`<circle cx="-36" cy="30" r="5" fill="${acc || '#ff5a6e'}"/><circle cx="36" cy="30" r="5" fill="${acc || '#ff5a6e'}"/>`,
}),
bob: (c) => ({
back: `<path d="M-42 40 C-54 66 -52 90 -42 98 L42 98 C52 90 54 66 42 40 Z" fill="${c}"/>`,
front: bangs(c, { tips: 5 }) + sideLocks(c, 94) + shine(c),
}),
messy: (c) => ({
back: `<path d="M-38 24 L-54 30 L-46 42 L-58 56 L-44 62 L-50 78 L-38 76 L38 76 L50 78 L44 62 L58 56 L46 42 L54 30 L38 24 Z" fill="${c}"/>`,
front: bangs(c, { tips: 5, jag: true, tipY: 48, rootY: 32 }) +
`<path d="M-6 12 L-2 -2 L6 12 L16 2 L16 16 Z" fill="${c}"/>` + shine(c),
}),
short: (c) => ({
back: '',
front: bangs(c, { tips: 5, tipY: 40, rootY: 30, sideY: 56 }) + shine(c),
}),
curly: (c) => {
let back = '';
for (let a = 150; a <= 390; a += 20) {
const r = (a * Math.PI) / 180;
back += `<circle cx="${n(Math.cos(r) * 44)}" cy="${n(52 + Math.sin(r) * 40)}" r="13" fill="${c}"/>`;
}
back += `<circle cx="-46" cy="84" r="12" fill="${c}"/><circle cx="46" cy="84" r="12" fill="${c}"/>`;
let front = '';
for (const [x, y, r] of [[-30, 30, 12], [-14, 22, 13], [3, 20, 13], [19, 23, 13], [33, 32, 11], [-38, 44, 9], [40, 45, 9]]) front += `<circle cx="${x}" cy="${y}" r="${r}" fill="${c}"/>`;
return { back, front };
},
braids: (c) => {
let br = '';
for (const sx of [-1, 1]) for (let i = 0; i < 5; i++) br += `<ellipse cx="${sx * (37 + i * 0.6)}" cy="${76 + i * 11}" rx="7" ry="7" fill="${c}"/>`;
return {
back: `<path d="M-42 40 C-50 60 -48 80 -42 86 L42 86 C48 80 50 60 42 40 Z" fill="${c}"/>`,
front: `<path d="M-43 64 C-48 24 -24 9 0 9 C24 9 48 24 43 64 L36 46 Q20 30 2 26 L0 30 L-2 26 Q-20 30 -36 46 Z" fill="${c}"/>` + br + shine(c),
};
},
bun: (c) => ({
back: `<circle cx="10" cy="6" r="15" fill="${c}"/><path d="M-42 40 C-50 60 -48 76 -42 80 L42 80 C48 76 50 60 42 40 Z" fill="${c}"/>`,
front: bangs(c, { tips: 6, tipY: 44 }) + sideLocks(c, 90) + shine(c),
}),
crest: (c) => ({
back: '',
front: `<path d="M-6 20 Q-12 2 -2 -2 Q0 8 2 16 Q6 0 16 2 Q10 12 6 22 Z" fill="${c}"/>`,
}),
mane: (c) => ({
back: `<path d="M8 14 Q46 10 50 50 Q56 80 44 108 Q34 86 34 60 Z" fill="${c}"/>`,
front: `<path d="M-20 26 Q-14 6 4 8 Q20 6 22 24 Q12 18 8 34 Q2 22 -6 36 Q-10 24 -20 26 Z" fill="${c}"/>`,
}),
helmet: (c, acc) => ({
back: `<ellipse cx="0" cy="50" rx="47" ry="43" fill="${c}"/>`,
front: `<path d="M-46 52 A47 43 0 0 1 46 52 L40 50 A40 36 0 0 0 -40 50 Z" fill="${shade(c, 0.25)}"/>` +
`<circle cx="-45" cy="58" r="9" fill="${c}"/><circle cx="45" cy="58" r="9" fill="${c}"/>` +
`<circle cx="-45" cy="58" r="4" fill="${acc}"/><circle cx="45" cy="58" r="4" fill="${acc}"/>` +
`<path d="M30 18 L42 -2" fill="none"/><circle cx="42" cy="-3" r="4" fill="${acc}"/>`,
}),
};
// ---------- accessories ----------
// Each returns {back?, mid?, front?}: back = behind head, mid = between head skin and face, front = over hair.
export const ACC = {
bow: (c) => ({ front: `<path d="M24 20 L12 10 L13 30 Z M24 20 L36 8 L37 28 Z" fill="${c}"/><circle cx="24" cy="20" r="4.5" fill="${shade(c, -0.15)}"/>` }),
glasses: (c) => ({ front: `<g fill="#ffffff" fill-opacity=".18" stroke="${c}" stroke-width="2.6"><circle cx="-15" cy="61" r="10.5"/><circle cx="15" cy="61" r="10.5"/></g><path d="M-4.5 59 Q0 56 4.5 59" fill="none" stroke="${c}" stroke-width="2.6"/>` }),
headphones: (c, c2) => ({
back: `<path d="M-42 50 Q-44 2 0 2 Q44 2 42 50" fill="none" stroke="${INK}" stroke-width="9"/><path d="M-42 50 Q-44 2 0 2 Q44 2 42 50" fill="none" stroke="${c}" stroke-width="4"/>`,
front: `<rect x="-50" y="48" width="13" height="20" rx="6" fill="${c}"/><rect x="37" y="48" width="13" height="20" rx="6" fill="${c}"/>` +
(c2 ? `<circle cx="-43.5" cy="58" r="3" fill="${c2}"/><circle cx="43.5" cy="58" r="3" fill="${c2}"/>` : ''),
}),
foxEars: (c, inner) => ({
back: `<path d="M-38 34 L-34 -4 L-12 18 Z M38 34 L34 -4 L12 18 Z" fill="${c}"/>`,
front: `<path d="M-33 22 L-31 4 L-21 15 Z M33 22 L31 4 L21 15 Z" fill="${inner}" stroke="none"/>`,
}),
foxMask: (c, mark) => ({
front: `<g transform="rotate(-24 -26 16)"><path d="M-40 14 Q-26 0 -12 14 Q-14 28 -26 30 Q-38 28 -40 14 Z" fill="${c}"/>` +
`<path d="M-38 10 L-36 -2 L-30 7 M-14 10 L-16 -2 L-22 7" fill="${c}"/>` +
`<path d="M-33 15 L-29 13 M-23 13 L-19 15" stroke="${mark}" stroke-width="2.4"/><circle cx="-26" cy="24" r="2" fill="${mark}" stroke="none"/></g>`,
}),
tail: (c, tip) => ({ body: `<path d="M18 128 Q58 128 56 92 Q54 74 44 80 Q50 96 36 110 Q24 118 14 116 Z" fill="${c}"/>` + (tip ? `<path d="M56 92 Q54 74 44 80 Q48 86 47 92 Z" fill="${tip}"/>` : '') }),
horseTail: (c) => ({ body: `<path d="M14 124 Q40 118 46 140 Q48 152 38 154 Q40 140 26 134 Q20 132 14 132 Z" fill="${c}"/>` }),
earring: (c) => ({ front: `<circle cx="-40" cy="74" r="3.6" fill="${c}"/><path d="M-40 77 L-40 84" stroke="${c}" stroke-width="2.4"/>` }),
circlet: (c, gem) => ({ front: `<path d="M-38 40 Q0 26 38 40" fill="none" stroke="${INK}" stroke-width="7"/><path d="M-38 40 Q0 26 38 40" fill="none" stroke="${c}" stroke-width="3"/><path d="M0 26 L5 33 L0 40 L-5 33 Z" fill="${gem}"/>` }),
cubes: (c, glow) => ({
back: `<path d="M-40 40 Q-46 10 -20 2 L20 2 Q46 10 40 40 Z" fill="${shade(c, -0.2)}"/>`,
front: [[-26, 10, 9], [-8, 2, 10], [12, 4, 9], [28, 14, 8], [2, -14, 8], [-17, -8, 7], [20, -8, 6]]
.map(([x, y, s]) => `<rect x="${x - s / 2}" y="${y - s / 2}" width="${s}" height="${s}" rx="1.5" fill="${c}"/><rect x="${x - s / 4}" y="${y - s / 4}" width="${s / 2}" height="${s / 2}" fill="${glow}" stroke="none"/>`).join(''),
}),
crystals: (c) => ({ front: `<path d="M-30 22 L-34 -4 L-18 14 Z M-12 12 L-8 -16 L2 10 Z M8 10 L20 -10 L22 14 Z M24 18 L40 4 L36 26 Z" fill="${c}"/><path d="M-9 8 L-8 -8 M18 6 L20 -4" stroke="#ffffff" stroke-width="2" opacity=".8"/>` }),
beard: (c) => ({ mid: `<path d="M-38 62 Q-40 96 -16 108 Q0 118 16 108 Q40 96 38 62 Q30 84 14 82 Q0 88 -14 82 Q-30 84 -38 62 Z" fill="${c}"/>` }),
goatee: (c) => ({ mid: `<path d="M-18 82 Q0 90 18 82 Q16 100 0 110 Q-16 100 -18 82 Z" fill="${c}"/>` }),
longNose: (c) => ({ front: `<path d="M-4 61 Q2 56 7 61 Q15 67 17 73 Q15 77 11 75 Q2 70 -4 66 Z" fill="${c}"/>` }),
tokin: (c) => ({ front: `<path d="M-6 16 L-4 2 Q2 -4 8 2 L10 16 Z" fill="${c}"/><path d="M-12 18 Q2 12 16 18" fill="none" stroke="${INK}" stroke-width="3"/>` }),
ridges: (c) => ({ mid: `<path d="M-6 20 Q0 15 6 20 L5 42 Q0 46 -5 42 Z M-24 26 Q-18 22 -14 28 L-16 44 Q-21 44 -23 40 Z M24 26 Q18 22 14 28 L16 44 Q21 44 23 40 Z" fill="${c}"/>` }),
forehead: (c) => ({ front: `<circle cx="0" cy="38" r="4.5" fill="${c}"/>` }),
hood: (c) => ({ back: `<path d="M-50 70 Q-58 4 0 2 Q58 4 50 70 Q52 96 30 98 L-30 98 Q-52 96 -50 70 Z" fill="${c}"/>` }),
hoodRim: (c) => ({ front: `<path d="M-48 70 Q-54 6 0 4 Q54 6 48 70 L42 68 Q46 16 0 14 Q-46 16 -42 68 Z" fill="${c}"/>` }),
wingCap: (c, wing, lens) => ({
front: `<path d="M-40 34 Q-38 6 0 6 Q38 6 40 34 Q0 22 -40 34 Z" fill="${c}"/>` +
`<path d="M30 20 Q46 2 58 4 Q50 10 54 12 Q44 16 46 20 Q40 22 36 26 Z" fill="${wing}"/>` +
`<rect x="-26" y="22" width="20" height="13" rx="6" fill="${lens}"/><rect x="6" y="22" width="20" height="13" rx="6" fill="${lens}"/><path d="M-6 28 L6 28" fill="none"/>`,
}),
scarf: (c) => ({ body: `<path d="M-18 86 Q0 96 18 86 L20 94 Q0 104 -20 94 Z M8 94 L16 116 L6 114 L2 98 Z" fill="${c}"/>` }),
spire: (c, glow) => ({
back: `<path d="M-26 22 L0 -14 L26 22 Z" fill="${c}"/>`,
front: `<path d="M-8 18 L0 -6 L8 18 Z" fill="${glow}"/><path d="M-30 30 Q0 14 30 30" fill="none" stroke="${glow}" stroke-width="3"/>`,
}),
spiralBand: (c, metal) => ({ front: `<path d="M-40 38 Q0 24 40 38 L40 46 Q0 32 -40 46 Z" fill="${c}"/><rect x="-9" y="27" width="18" height="12" rx="2" fill="${metal}"/><path d="M0 33 m-3 0 a3 3 0 1 1 3 3 a5 5 0 1 1 5 -5" fill="none" stroke="${INK}" stroke-width="1.6"/>` }),
helm: (c, horn, eye) => ({
front: `<path d="M-42 40 Q-40 2 0 0 Q40 2 42 40 Q0 28 -42 40 Z" fill="${c}"/>` +
`<path d="M-38 22 Q-60 14 -58 -10 Q-48 6 -34 10 Z M38 22 Q60 14 58 -10 Q48 6 34 10 Z" fill="${horn}"/>` +
`<circle cx="0" cy="18" r="7" fill="#fff"/><circle cx="0" cy="18" r="3.6" fill="${eye}" stroke="none"/>`,
}),
goggles: (c, lens) => ({ front: `<path d="M-42 34 Q0 20 42 34" fill="none" stroke="${INK}" stroke-width="7"/><path d="M-42 34 Q0 20 42 34" fill="none" stroke="${c}" stroke-width="3"/><rect x="-24" y="18" width="18" height="13" rx="6" fill="${lens}"/><rect x="6" y="18" width="18" height="13" rx="6" fill="${lens}"/>` }),
pencil: () => ({ front: `<g transform="rotate(-35 18 4)"><rect x="4" y="1" width="28" height="6" rx="1" fill="#ffd84a"/><path d="M32 1 L39 4 L32 7 Z" fill="#f3d9b1"/><rect x="2" y="1" width="5" height="6" fill="#ff8aa0"/></g>` }),
starClip: (c) => ({ front: sparkle(-28, 22, 7, c) }),
twoTone: (c) => ({ front: `<path d="M2 10 C24 10 46 24 43 64 L36 46 L30 38 L22 46 L12 34 L4 40 Z" fill="${c}" opacity=".95"/>` }),
feather: (c) => ({ front: `<path d="M-30 18 Q-44 0 -36 -8 Q-28 2 -26 14 Z" fill="${c}"/><path d="M-30 14 L-36 -4" stroke="${INK}" stroke-width="1.5"/>` }),
collarRing: (c, gem) => ({ body: `<path d="M-17 88 Q0 98 17 88 L18 94 Q0 104 -18 94 Z" fill="${c}"/><rect x="-4" y="93" width="8" height="6" rx="2" fill="${gem}"/>` }),
mask: (c) => ({ mid: `<path d="M-38 66 Q0 74 38 66 Q36 92 0 98 Q-36 92 -38 66 Z" fill="${c}"/>` }),
};
// ---------- props ----------
// Props are drawn around a grip point at (0,0) and extend mostly upward (-y), ~26 px.
export const PROPS = {
book: (c) => `<path d="M-13 -20 L13 -20 L13 6 L-13 6 Z" fill="${c}"/><path d="M-10 -20 L-10 6" fill="none"/><path d="M-4 -12 L8 -12 M-4 -6 L6 -6" stroke="${shade(c, 0.6)}" stroke-width="2"/><path d="M13 -17 L15 -17 L15 4 L13 6" fill="#f3e6cf"/>`,
tablet: (c, g) => `<rect x="-11" y="-24" width="22" height="30" rx="3" fill="${c}"/><rect x="-7" y="-20" width="14" height="20" rx="1.5" fill="${g}" stroke="none"/><path d="M-4 -15 L4 -15 M-4 -10 L2 -10 M-4 -5 L4 -5" stroke="${shade(c, -0.3)}" stroke-width="1.6"/>`,
magnifier: (c) => `<path d="M0 4 L6 -10" stroke="${INK}" stroke-width="8"/><path d="M0 4 L6 -10" stroke="${c}" stroke-width="4"/><circle cx="10" cy="-20" r="11" fill="#cfefff"/><circle cx="10" cy="-20" r="11" fill="none" stroke="${c}" stroke-width="3.5"/><path d="M5 -24 Q8 -28 12 -27" stroke="#fff" stroke-width="2.4"/>`,
cards: (c) => `<g transform="rotate(-14)"><rect x="-14" y="-22" width="15" height="21" rx="2" fill="${c}"/><path d="M-10 -15 q3 -4 6 0 q-3 5 -6 9" fill="none" stroke-width="2"/></g><g transform="rotate(10)"><rect x="-2" y="-24" width="15" height="21" rx="2" fill="${c}"/><path d="M2 -17 h7 M5 -17 v9" fill="none" stroke-width="2"/></g>`,
keys: (c) => `<circle cx="0" cy="-10" r="8" fill="none" stroke="${INK}" stroke-width="7"/><circle cx="0" cy="-10" r="8" fill="none" stroke="${c}" stroke-width="3"/><circle cx="-6" cy="-20" r="5" fill="${c}"/><rect x="-7.5" y="-22" width="3" height="3" fill="${INK}" stroke="none"/><path d="M8 -6 L18 4 L15 7 M14 0 L11 3" fill="none" stroke-width="6" stroke="${INK}"/><path d="M8 -6 L18 4 L15 7 M14 0 L11 3" fill="none" stroke-width="2.6" stroke="${c}"/>`,
hammer: (c, h) => `<path d="M0 6 L0 -18" stroke="${INK}" stroke-width="8"/><path d="M0 6 L0 -18" stroke="${h}" stroke-width="4"/><rect x="-13" y="-28" width="26" height="13" rx="3" fill="${c}"/><path d="M-9 -24 L-9 -19" stroke="${shade(c, 0.5)}" stroke-width="2"/>`,
horn: (c, band) => `<path d="M-2 6 Q-14 -10 0 -26 L18 -16 Q2 -8 4 6 Z" fill="${c}"/><ellipse cx="9" cy="-21" rx="10.5" ry="5.5" transform="rotate(30 9 -21)" fill="${shade(c, -0.2)}"/><ellipse cx="9" cy="-21" rx="6" ry="2.6" transform="rotate(30 9 -21)" fill="${INK}"/><path d="M-6 -4 Q0 -8 5 -4" fill="none" stroke="${band}" stroke-width="3.4"/><path d="M-6 -4 Q0 -8 5 -4" fill="none" stroke-width="0"/>`,
fan: (c) => `<path d="M0 4 L0 -6" stroke="${INK}" stroke-width="6"/><path d="M0 -4 Q-20 -14 -16 -30 Q0 -36 16 -30 Q20 -14 0 -4 Z" fill="${c}"/><path d="M0 -6 L-8 -28 M0 -6 L0 -32 M0 -6 L8 -28" stroke="${shade(c, -0.3)}" stroke-width="1.6"/>`,
brush: (c) => `<path d="M0 6 L0 -16" stroke="${INK}" stroke-width="8"/><path d="M0 6 L0 -16" stroke="${c}" stroke-width="4"/><path d="M-5 -16 L5 -16 Q6 -26 0 -34 Q-6 -26 -5 -16 Z" fill="#1a1a1a"/><circle cx="12" cy="-28" r="3" fill="#1a1a1a" stroke="none"/><circle cx="16" cy="-20" r="2" fill="#1a1a1a" stroke="none"/>`,
quill: (c) => `<path d="M-2 6 Q-2 -14 14 -32 Q12 -14 0 4 Z" fill="${c}"/><path d="M-2 6 L8 -18" stroke="${INK}" stroke-width="1.6"/><circle cx="-4" cy="9" r="2.4" fill="#3a2a6b" stroke="none"/>`,
heart: (c) => `<path d="M0 0 L-12 -12 L-12 -20 L-8 -24 L-4 -24 L0 -20 L4 -24 L8 -24 L12 -20 L12 -12 Z" fill="${c}"/><rect x="-8" y="-20" width="4" height="4" fill="#fff" stroke="none"/>`,
spool: (c, t) => `<rect x="-10" y="-24" width="20" height="5" rx="1.5" fill="${c}"/><rect x="-10" y="-4" width="20" height="5" rx="1.5" fill="${c}"/><rect x="-7" y="-19" width="14" height="15" fill="${t}"/><path d="M7 -12 Q22 -10 18 6" fill="none" stroke="${t}" stroke-width="2.4"/>`,
envelope: (c, seal) => `<rect x="-14" y="-20" width="28" height="19" rx="2" fill="${c}"/><path d="M-14 -20 L0 -9 L14 -20" fill="none"/><circle cx="0" cy="-9" r="4" fill="${seal}"/>`,
wand: (c, star) => `<path d="M0 6 L0 -14" stroke="${INK}" stroke-width="7"/><path d="M0 6 L0 -14" stroke="${c}" stroke-width="3"/>${sparkle(0, -24, 12, star)}<circle cx="0" cy="-24" r="2.5" fill="#fff" stroke="none"/>`,
cube: (c, g) => `<path d="M0 -30 L13 -23 L13 -8 L0 -1 L-13 -8 L-13 -23 Z" fill="${c}"/><path d="M-13 -23 L0 -16 L13 -23 M0 -16 L0 -1" fill="none"/><path d="M0 -16 L13 -23 L13 -8 L0 -1 Z" fill="${g}" opacity=".55" stroke="none"/>`,
crystal: (c) => `<path d="M0 -32 L10 -16 L6 2 L-6 2 L-10 -16 Z" fill="${c}"/><path d="M0 -32 L0 2 M-10 -16 L10 -16" fill="none" stroke-width="1.6"/><path d="M-4 -24 L-6 -14" stroke="#fff" stroke-width="2"/>`,
ship: (c, g) => `<g transform="rotate(-30 0 -14)"><path d="M0 -32 Q7 -20 7 -6 L14 2 L-14 2 L-7 -6 Q-7 -20 0 -32 Z" fill="${c}"/><circle cx="0" cy="-16" r="4" fill="${g}"/><path d="M-4 3 L0 10 L4 3 Z" fill="#ff8a2a"/></g>`,
shield: (c, m) => `<path d="M-13 -26 L13 -26 L13 -10 Q13 2 0 7 Q-13 2 -13 -10 Z" fill="${c}"/><path d="M4 -20 A8 8 0 1 0 6 -6 A6 6 0 1 1 4 -20 Z" fill="${m}" stroke="none"/>`,
mirror: (c, g) => `<path d="M0 6 L0 -6" stroke="${INK}" stroke-width="7"/><path d="M0 6 L0 -6" stroke="${c}" stroke-width="3"/><ellipse cx="0" cy="-18" rx="11" ry="13" fill="${c}"/><ellipse cx="0" cy="-18" rx="7" ry="9" fill="${g}" stroke="none"/><path d="M-3 -23 L2 -14" stroke="#fff" stroke-width="2"/>`,
orb: (c, g) => `<circle cx="0" cy="-14" r="12" fill="${c}"/><circle cx="0" cy="-14" r="6" fill="${g}" stroke="none"/><path d="M-6 -20 Q-2 -24 3 -23" stroke="#fff" stroke-width="2"/>`,
panel: (c, g) => `<rect x="-15" y="-26" width="30" height="22" rx="3" fill="${c}"/><rect x="-11" y="-21" width="10" height="6" rx="1" fill="${g}" stroke="none"/><rect x="2" y="-21" width="9" height="13" rx="1" fill="${g}" stroke="none" opacity=".6"/><rect x="-11" y="-12" width="10" height="4" rx="1" fill="${g}" stroke="none" opacity=".6"/>`,
camera: (c, g) => `<rect x="-14" y="-20" width="28" height="18" rx="3" fill="${c}"/><rect x="-8" y="-24" width="9" height="5" rx="1" fill="${c}"/><circle cx="2" cy="-11" r="6" fill="${g}"/><circle cx="10" cy="-16" r="1.6" fill="#ff5a6e" stroke="none"/>`,
pad: (c, g) => `<path d="M-15 -14 Q-15 -22 -6 -22 L6 -22 Q15 -22 15 -14 L16 -2 Q16 4 10 2 L5 -4 L-5 -4 L-10 2 Q-16 4 -16 -2 Z" fill="${c}"/><circle cx="7" cy="-14" r="2.4" fill="${g}" stroke="none"/><path d="M-9 -14 h6 M-6 -17 v6" stroke="${g}" stroke-width="2"/>`,
seed: (c, g) => `<path d="M0 -32 Q14 -20 10 -6 Q6 4 0 4 Q-6 4 -10 -6 Q-14 -20 0 -32 Z" fill="${c}"/><path d="M0 -26 Q6 -16 0 -2" fill="none" stroke="${g}" stroke-width="2.4"/><circle cx="-4" cy="-14" r="2.4" fill="${g}" stroke="none"/>`,
scroll: (c) => `<rect x="-13" y="-22" width="26" height="22" fill="${c}"/><rect x="-16" y="-25" width="32" height="5" rx="2.5" fill="${shade(c, -0.25)}"/><rect x="-16" y="-2" width="32" height="5" rx="2.5" fill="${shade(c, -0.25)}"/><path d="M-7 -15 h14 M-7 -9 h10" stroke="${INK}" stroke-width="1.8"/>`,
};
// ---------- emblems ----------
// Chest badge glyphs, drawn inside a 6.5-radius disc at (0,104) on the torso.
export const EMBLEMS = {
star: (c) => sparkle(0, 0, 4.4, c),
eye: (c) => `<path d="M-4.5 0 Q0 -4.5 4.5 0 Q0 4.5 -4.5 0 Z" fill="#fff" stroke="${INK}" stroke-width="1.4"/><circle r="1.8" fill="${c}"/>`,
key: (c) => `<circle cx="0" cy="-2" r="2.2" fill="none" stroke="${c}" stroke-width="1.8"/><path d="M0 0 L0 4.5 M0 3 L1.8 3" stroke="${c}" stroke-width="1.8"/>`,
spiral: (c) => `<path d="M0 0 m-1.5 0 a1.5 1.5 0 1 1 1.5 1.5 a3 3 0 1 1 3 -3" fill="none" stroke="${c}" stroke-width="1.6"/>`,
cube: (c) => `<path d="M0 -4 L3.5 -2 L3.5 2 L0 4 L-3.5 2 L-3.5 -2 Z" fill="${c}"/>`,
flake: (c) => `<path d="M0 -4.5 V4.5 M-3.9 -2.2 L3.9 2.2 M-3.9 2.2 L3.9 -2.2" stroke="${c}" stroke-width="1.6"/>`,
feather: (c) => `<path d="M-2 4 Q-2 -2 3 -5 Q3 1 -2 4 Z" fill="${c}"/>`,
heart: (c) => `<path d="M0 3.5 L-3.6 0 Q-4.5 -3.5 -1.8 -3.6 Q0 -3.4 0 -1.6 Q0 -3.4 1.8 -3.6 Q4.5 -3.5 3.6 0 Z" fill="${c}"/>`,
bolt: (c) => `<path d="M1 -4.5 L-2.5 0.5 L0 0.5 L-1 4.5 L2.5 -0.5 L0 -0.5 Z" fill="${c}"/>`,
moon: (c) => `<path d="M1 -4 A4 4 0 1 0 2.6 3 A3 3 0 1 1 1 -4 Z" fill="${c}"/>`,
rune: (c) => `<path d="M-2 -4 V4 M-2 -2 L2 -4 M-2 1 L2 -1" stroke="${c}" stroke-width="1.6"/>`,
gear: (c) => `<circle r="3.2" fill="none" stroke="${c}" stroke-width="2"/><path d="M0 -4.8 V-3 M0 4.8 V3 M-4.8 0 H-3 M4.8 0 H3" stroke="${c}" stroke-width="1.8"/>`,
wave: (c) => `<path d="M-4 1 Q-2 -2 0 1 Q2 4 4 1" fill="none" stroke="${c}" stroke-width="1.8"/>`,
glyph: (c) => `<path d="M-3 -3 H3 L-3 3 H3" fill="none" stroke="${c}" stroke-width="1.6"/>`,
rainbow: () => `<path d="M-4 2 A4 4 0 0 1 4 2" fill="none" stroke="#ff5a6e" stroke-width="1.5"/><path d="M-2.5 2 A2.5 2.5 0 0 1 2.5 2" fill="none" stroke="#59b7ff" stroke-width="1.5"/>`,
};
function emblem(ch) {
if (!ch.emblem) return '';
const e = ch.emblem;
const y = ch.base === 'raven' ? 104 : 106;
return `<g transform="translate(0 ${y})">` + inked(`<circle r="6.8" fill="${e.disc || '#fff6e8'}"/>`, 2.4) + EMBLEMS[e.shape](e.color) + '</g>';
}
// ---------- pose effects ----------
function fx(kind, ch) {
const acc = ch.accent || '#ffd84a';
switch (kind) {
case 'sparkles': return sparkle(-62, 62, 7, acc) + sparkle(-34, 6, 5, '#fff') + sparkle(60, 70, 6, acc);
case 'dots': return inked(`<circle cx="36" cy="16" r="3" fill="#fff"/><circle cx="46" cy="7" r="4" fill="#fff"/><circle cx="58" cy="-2" r="5.5" fill="#fff"/>`, 2.2);
case 'bang': return inked(`<path d="M-50 18 L-44 32 M-62 30 L-50 38 M50 18 L44 32 M62 30 L50 38" fill="none"/>`, 3.2);
case 'present': return sparkle(60, 64, 5, acc) + sparkle(28, 66, 4, '#fff');
case 'wave': return `<path d="M60 38 Q68 46 64 56 M66 30 Q76 44 71 58" fill="none" stroke="${INK}" stroke-width="2.6" stroke-linecap="round"/>`;
default: return '';
}
}
// ---------- assembly ----------
function merge(ch) {
const out = { back: '', mid: '', front: '' };
for (const [name, ...args] of ch.acc || []) {
const r = ACC[name](...args);
for (const k of Object.keys(out)) if (r[k]) out[k] += r[k];
}
return out;
}
function propAt(ch, pose) {
if (!pose.prop || !ch.prop) return '';
const h = pose.arms[pose.prop.hand].h;
const [name, ...args] = ch.prop;
const x = h[0] + pose.prop.dx, y = h[1] + pose.prop.dy;
return `<g transform="translate(${n(x)} ${n(y)}) rotate(${pose.prop.rot})">` + inked(PROPS[name](...args), 2.6) + '</g>';
}
export function render(ch, poseName, expr) {
const pose = POSES[poseName];
if (!pose) throw new Error('unknown pose ' + poseName);
const acc = merge(ch);
const hair = HAIR[ch.hair.style](ch.hair.color, ch.hair.accent);
const f = face(ch, expr);
const hd = pose.head;
const headT = `translate(${hd.dx} ${hd.dy}) rotate(${hd.rot} 0 88)`;
let s = '';
s += `<g transform="translate(0 ${-pose.lift})">`;
// 1. behind everything: hair back, accessory back (rotates with the head)
s += `<g transform="${headT}">` + inked(acc.back + hair.back) + '</g>';
// 2. cape, tail, legs
s += cape(ch);
const tails = (ch.acc || []).filter(([a]) => a === 'tail' || a === 'horseTail').map(([a, ...r]) => ACC[a](...r).body).join('');
s += inked(tails);
s += legs(ch, pose);
// 3. back arm (non-front arms), torso, body accessories
s += torso(ch);
const bodyAcc = (ch.acc || []).filter(([a]) => a !== 'tail' && a !== 'horseTail').map(([a, ...r]) => ACC[a](...r).body || '').join('');
s += inked(bodyAcc);
s += emblem(ch);
for (const side of ['L', 'R']) if (!pose.arms[side].front) s += arm(ch, pose, side);
s += propAt(ch, pose);
for (const side of ['L', 'R']) if (!pose.arms[side].front) s += hand(ch, pose, side);
// 4. head
s += `<g transform="${headT}">` + headBase(ch) + inked(acc.mid) + f.under + inked(hair.front + acc.front) + f.over + '</g>';
// 5. front arms (thinking hand at chin)
for (const side of ['L', 'R']) if (pose.arms[side].front) s += arm(ch, pose, side) + hand(ch, pose, side);
s += fx(pose.fx, ch);
s += '</g>';
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${VIEWBOX}" width="320" height="344">` +
`<title>${ch.name} — ${poseName}, ${expr}</title>${s}</svg>\n`;
}

13
cast/rig/shot.sh Normal file
View file

@ -0,0 +1,13 @@
#!/usr/bin/env bash
# Screenshot the gallery with Edge headless (Git Bash). Usage from the repo root:
# cast/rig/shot.sh <out.png> [character] [pose] [width] [height]
# A fresh profile dir per call keeps headless Edge off the desktop browser profile.
set -euo pipefail
out=$1; c=${2:-}; p=${3:-}; w=${4:-1400}; h=${5:-1800}
root=$(cd "$(dirname "$0")/.." && pwd -W)
prof="$(cygpath -m "${TMPDIR:-/tmp}")/cast-edge-$$"
rm -f "$out"
# Edge may hand the job to an already-running instance and return at once, so wait for the file.
"/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe" --headless=new --disable-gpu --hide-scrollbars --user-data-dir="$prof" --screenshot="$(cygpath -m "$out")" --window-size="$w,$h" "file:///$root/gallery.html?c=$c&p=$p" >/dev/null 2>&1 &
for _ in $(seq 120); do [ -s "$out" ] && break; sleep 0.25; done
sleep 0.5; ls -la "$out"

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-80 -10 160 172" width="320" height="344"><title>Druzkai — neutral, grin</title><g transform="translate(0 0)"><g transform="translate(0 0) rotate(0 0 88)"><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"></g></g><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"></g><path d="M-8 126 L-10 149" fill="none" stroke="#3b2618" stroke-width="14" stroke-linecap="round" stroke-linejoin="round"/><path d="M-8 126 L-10 149" fill="none" stroke="#4a2a5a" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><ellipse cx="-12" cy="150" rx="7.5" ry="5" fill="#2a1830"/></g><path d="M8 126 L10 149" fill="none" stroke="#3b2618" stroke-width="14" stroke-linecap="round" stroke-linejoin="round"/><path d="M8 126 L10 149" fill="none" stroke="#4a2a5a" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><ellipse cx="12" cy="150" rx="7.5" ry="5" fill="#2a1830"/></g><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><path d="M-15 88 Q-23 112 -29 144 Q0 150 29 144 Q23 112 15 88 Z" fill="#4a2a5a"/><path d="M-28 138 Q0 144 28 138 L29 144 Q0 150 -29 144 Z" fill="#d9a43a"/><path d="M-13 88 Q0 98 13 88 Z" fill="#d9a43a"/></g><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"></g><g transform="translate(0 106)"><g stroke="#3b2618" stroke-width="2.4" stroke-linejoin="round" stroke-linecap="round"><circle r="6.8" fill="#fff6e8"/></g><path d="M0 0 m-1.5 0 a1.5 1.5 0 1 1 1.5 1.5 a3 3 0 1 1 3 -3" fill="none" stroke="#3fd0c8" stroke-width="1.6"/></g><path d="M-15 95 Q-23 107 -24 120" fill="none" stroke="#3b2618" stroke-width="13" stroke-linecap="round" stroke-linejoin="round"/><path d="M-15 95 Q-23 107 -24 120" fill="none" stroke="#4a2a5a" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/><path d="M15 95 Q23 107 24 120" fill="none" stroke="#3b2618" stroke-width="13" stroke-linecap="round" stroke-linejoin="round"/><path d="M15 95 Q23 107 24 120" fill="none" stroke="#4a2a5a" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/><g transform="translate(28 116) rotate(8)"><g stroke="#3b2618" stroke-width="2.6" stroke-linejoin="round" stroke-linecap="round"><path d="M0 -32 Q14 -20 10 -6 Q6 4 0 4 Q-6 4 -10 -6 Q-14 -20 0 -32 Z" fill="#6fbf6a"/><path d="M0 -26 Q6 -16 0 -2" fill="none" stroke="#c8ff9a" stroke-width="2.4"/><circle cx="-4" cy="-14" r="2.4" fill="#c8ff9a" stroke="none"/></g></g><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><circle cx="-24" cy="120" r="5.6" fill="#c4553e"/></g><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><circle cx="24" cy="120" r="5.6" fill="#c4553e"/></g><g transform="translate(0 0) rotate(0 0 88)"><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><ellipse cx="-38" cy="62" rx="6" ry="7.5" fill="#c4553e"/><ellipse cx="38" cy="62" rx="6" ry="7.5" fill="#c4553e"/><ellipse cx="0" cy="52" rx="40" ry="36" fill="#c4553e"/></g><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><path d="M-6 20 Q0 15 6 20 L5 42 Q0 46 -5 42 Z M-24 26 Q-18 22 -14 28 L-16 44 Q-21 44 -23 40 Z M24 26 Q18 22 14 28 L16 44 Q21 44 23 40 Z" fill="#a03a2c"/></g><ellipse cx="-26" cy="71" rx="6" ry="3.7199999999999998" fill="#ff9a8a" opacity="0.85"/><ellipse cx="26" cy="71" rx="6" ry="3.7199999999999998" fill="#ff9a8a" opacity="0.85"/><ellipse cx="-15" cy="61" rx="6.3" ry="7" fill="#3fd0c8" stroke="#3b2618" stroke-width="2.5"/><ellipse cx="-15" cy="62" rx="3.528" ry="4.2" fill="#2a1a12"/><path d="M-22.3 57.85 Q-15 51.4 -7.699999999999999 57.85" fill="none" stroke="#3b2618" stroke-width="3.2" stroke-linecap="round"/><circle cx="-12.9" cy="58.2" r="2.3" fill="#fff"/><circle cx="-17" cy="64" r="1" fill="#fff"/><ellipse cx="15" cy="61" rx="6.3" ry="7" fill="#3fd0c8" stroke="#3b2618" stroke-width="2.5"/><ellipse cx="15" cy="62" rx="3.528" ry="4.2" fill="#2a1a12"/><path d="M7.699999999999999 57.85 Q15 51.4 22.3 57.85" fill="none" stroke="#3b2618" stroke-width="3.2" stroke-linecap="round"/><circle cx="17.1" cy="58.2" r="2.3" fill="#fff"/><circle cx="13" cy="64" r="1" fill="#fff"/><path d="M-9 74 Q0 75.5 9 74 Q8 85 0 85 Q-8 85 -9 74 Z" fill="#7a2a2a" stroke="#3b2618" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M-4.5 82.5 Q0 78.5 4.5 82.5 Q0 84.6 -4.5 82.5 Z" fill="#ff8f9a"/><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><circle cx="0" cy="38" r="4.5" fill="#3fd0c8"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-80 -10 160 172" width="320" height="344"><title>Druzkai — neutral, smile</title><g transform="translate(0 0)"><g transform="translate(0 0) rotate(0 0 88)"><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"></g></g><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"></g><path d="M-8 126 L-10 149" fill="none" stroke="#3b2618" stroke-width="14" stroke-linecap="round" stroke-linejoin="round"/><path d="M-8 126 L-10 149" fill="none" stroke="#4a2a5a" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><ellipse cx="-12" cy="150" rx="7.5" ry="5" fill="#2a1830"/></g><path d="M8 126 L10 149" fill="none" stroke="#3b2618" stroke-width="14" stroke-linecap="round" stroke-linejoin="round"/><path d="M8 126 L10 149" fill="none" stroke="#4a2a5a" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><ellipse cx="12" cy="150" rx="7.5" ry="5" fill="#2a1830"/></g><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><path d="M-15 88 Q-23 112 -29 144 Q0 150 29 144 Q23 112 15 88 Z" fill="#4a2a5a"/><path d="M-28 138 Q0 144 28 138 L29 144 Q0 150 -29 144 Z" fill="#d9a43a"/><path d="M-13 88 Q0 98 13 88 Z" fill="#d9a43a"/></g><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"></g><g transform="translate(0 106)"><g stroke="#3b2618" stroke-width="2.4" stroke-linejoin="round" stroke-linecap="round"><circle r="6.8" fill="#fff6e8"/></g><path d="M0 0 m-1.5 0 a1.5 1.5 0 1 1 1.5 1.5 a3 3 0 1 1 3 -3" fill="none" stroke="#3fd0c8" stroke-width="1.6"/></g><path d="M-15 95 Q-23 107 -24 120" fill="none" stroke="#3b2618" stroke-width="13" stroke-linecap="round" stroke-linejoin="round"/><path d="M-15 95 Q-23 107 -24 120" fill="none" stroke="#4a2a5a" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/><path d="M15 95 Q23 107 24 120" fill="none" stroke="#3b2618" stroke-width="13" stroke-linecap="round" stroke-linejoin="round"/><path d="M15 95 Q23 107 24 120" fill="none" stroke="#4a2a5a" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/><g transform="translate(28 116) rotate(8)"><g stroke="#3b2618" stroke-width="2.6" stroke-linejoin="round" stroke-linecap="round"><path d="M0 -32 Q14 -20 10 -6 Q6 4 0 4 Q-6 4 -10 -6 Q-14 -20 0 -32 Z" fill="#6fbf6a"/><path d="M0 -26 Q6 -16 0 -2" fill="none" stroke="#c8ff9a" stroke-width="2.4"/><circle cx="-4" cy="-14" r="2.4" fill="#c8ff9a" stroke="none"/></g></g><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><circle cx="-24" cy="120" r="5.6" fill="#c4553e"/></g><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><circle cx="24" cy="120" r="5.6" fill="#c4553e"/></g><g transform="translate(0 0) rotate(0 0 88)"><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><ellipse cx="-38" cy="62" rx="6" ry="7.5" fill="#c4553e"/><ellipse cx="38" cy="62" rx="6" ry="7.5" fill="#c4553e"/><ellipse cx="0" cy="52" rx="40" ry="36" fill="#c4553e"/></g><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><path d="M-6 20 Q0 15 6 20 L5 42 Q0 46 -5 42 Z M-24 26 Q-18 22 -14 28 L-16 44 Q-21 44 -23 40 Z M24 26 Q18 22 14 28 L16 44 Q21 44 23 40 Z" fill="#a03a2c"/></g><ellipse cx="-26" cy="71" rx="5.5" ry="3.41" fill="#ff9a8a" opacity="0.7"/><ellipse cx="26" cy="71" rx="5.5" ry="3.41" fill="#ff9a8a" opacity="0.7"/><ellipse cx="-15" cy="61" rx="6.3" ry="7.6" fill="#3fd0c8" stroke="#3b2618" stroke-width="2.5"/><ellipse cx="-15" cy="62" rx="3.528" ry="4.56" fill="#2a1a12"/><path d="M-22.3 57.58 Q-15 50.8 -7.699999999999999 57.58" fill="none" stroke="#3b2618" stroke-width="3.2" stroke-linecap="round"/><circle cx="-12.9" cy="58.2" r="2.3" fill="#fff"/><circle cx="-17" cy="64" r="1" fill="#fff"/><ellipse cx="15" cy="61" rx="6.3" ry="7.6" fill="#3fd0c8" stroke="#3b2618" stroke-width="2.5"/><ellipse cx="15" cy="62" rx="3.528" ry="4.56" fill="#2a1a12"/><path d="M7.699999999999999 57.58 Q15 50.8 22.3 57.58" fill="none" stroke="#3b2618" stroke-width="3.2" stroke-linecap="round"/><circle cx="17.1" cy="58.2" r="2.3" fill="#fff"/><circle cx="13" cy="64" r="1" fill="#fff"/><path d="M-7 75 Q0 82 7 75" fill="none" stroke="#3b2618" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><circle cx="0" cy="38" r="4.5" fill="#3fd0c8"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-80 -10 160 172" width="320" height="344"><title>Druzkai — neutral, surprised</title><g transform="translate(0 0)"><g transform="translate(0 0) rotate(0 0 88)"><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"></g></g><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"></g><path d="M-8 126 L-10 149" fill="none" stroke="#3b2618" stroke-width="14" stroke-linecap="round" stroke-linejoin="round"/><path d="M-8 126 L-10 149" fill="none" stroke="#4a2a5a" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><ellipse cx="-12" cy="150" rx="7.5" ry="5" fill="#2a1830"/></g><path d="M8 126 L10 149" fill="none" stroke="#3b2618" stroke-width="14" stroke-linecap="round" stroke-linejoin="round"/><path d="M8 126 L10 149" fill="none" stroke="#4a2a5a" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><ellipse cx="12" cy="150" rx="7.5" ry="5" fill="#2a1830"/></g><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><path d="M-15 88 Q-23 112 -29 144 Q0 150 29 144 Q23 112 15 88 Z" fill="#4a2a5a"/><path d="M-28 138 Q0 144 28 138 L29 144 Q0 150 -29 144 Z" fill="#d9a43a"/><path d="M-13 88 Q0 98 13 88 Z" fill="#d9a43a"/></g><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"></g><g transform="translate(0 106)"><g stroke="#3b2618" stroke-width="2.4" stroke-linejoin="round" stroke-linecap="round"><circle r="6.8" fill="#fff6e8"/></g><path d="M0 0 m-1.5 0 a1.5 1.5 0 1 1 1.5 1.5 a3 3 0 1 1 3 -3" fill="none" stroke="#3fd0c8" stroke-width="1.6"/></g><path d="M-15 95 Q-23 107 -24 120" fill="none" stroke="#3b2618" stroke-width="13" stroke-linecap="round" stroke-linejoin="round"/><path d="M-15 95 Q-23 107 -24 120" fill="none" stroke="#4a2a5a" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/><path d="M15 95 Q23 107 24 120" fill="none" stroke="#3b2618" stroke-width="13" stroke-linecap="round" stroke-linejoin="round"/><path d="M15 95 Q23 107 24 120" fill="none" stroke="#4a2a5a" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/><g transform="translate(28 116) rotate(8)"><g stroke="#3b2618" stroke-width="2.6" stroke-linejoin="round" stroke-linecap="round"><path d="M0 -32 Q14 -20 10 -6 Q6 4 0 4 Q-6 4 -10 -6 Q-14 -20 0 -32 Z" fill="#6fbf6a"/><path d="M0 -26 Q6 -16 0 -2" fill="none" stroke="#c8ff9a" stroke-width="2.4"/><circle cx="-4" cy="-14" r="2.4" fill="#c8ff9a" stroke="none"/></g></g><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><circle cx="-24" cy="120" r="5.6" fill="#c4553e"/></g><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><circle cx="24" cy="120" r="5.6" fill="#c4553e"/></g><g transform="translate(0 0) rotate(0 0 88)"><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><ellipse cx="-38" cy="62" rx="6" ry="7.5" fill="#c4553e"/><ellipse cx="38" cy="62" rx="6" ry="7.5" fill="#c4553e"/><ellipse cx="0" cy="52" rx="40" ry="36" fill="#c4553e"/></g><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><path d="M-6 20 Q0 15 6 20 L5 42 Q0 46 -5 42 Z M-24 26 Q-18 22 -14 28 L-16 44 Q-21 44 -23 40 Z M24 26 Q18 22 14 28 L16 44 Q21 44 23 40 Z" fill="#a03a2c"/></g><ellipse cx="-26" cy="71" rx="5.5" ry="3.41" fill="#ff9a8a" opacity="0.6"/><ellipse cx="26" cy="71" rx="5.5" ry="3.41" fill="#ff9a8a" opacity="0.6"/><circle cx="-15" cy="61" r="7.3" fill="#fff" stroke="#3b2618" stroke-width="2.5"/><circle cx="-15" cy="61.5" r="3.3" fill="#2a1a12"/><circle cx="-13.8" cy="60.2" r="1.2" fill="#fff"/><circle cx="15" cy="61" r="7.3" fill="#fff" stroke="#3b2618" stroke-width="2.5"/><circle cx="15" cy="61.5" r="3.3" fill="#2a1a12"/><circle cx="16.2" cy="60.2" r="1.2" fill="#fff"/><ellipse cx="0" cy="78" rx="4.2" ry="5.2" fill="#7a2a2a" stroke="#3b2618" stroke-width="2.6"/><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><circle cx="0" cy="38" r="4.5" fill="#3fd0c8"/></g><path d="M-22 42 Q-15.5 40.5 -9 42" fill="none" stroke="#3b2618" stroke-width="2.6" stroke-linecap="round"/><path d="M9 42 Q15.5 40.5 22 42" fill="none" stroke="#3b2618" stroke-width="2.6" stroke-linecap="round"/></g></g></svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.4 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-80 -10 160 172" width="320" height="344"><title>Epiphany — surprised, happy</title><g transform="translate(0 -7)"><g transform="translate(0 -1) rotate(-3 0 88)"><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><path d="M-36 28 C-74 34 -76 84 -58 118 Q-54 100 -48 92 Q-46 108 -40 114 C-44 80 -46 58 -30 44 Z" fill="#8fd8b4"/><path d="M36 28 C74 34 76 84 58 118 Q54 100 48 92 Q46 108 40 114 C44 80 46 58 30 44 Z" fill="#8fd8b4"/><path d="M-40 40 C-50 64 -48 86 -40 92 L40 92 C48 86 50 64 40 40 Z" fill="#8fd8b4"/></g></g><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"></g><path d="M-8 126 L-11 149" fill="none" stroke="#3b2618" stroke-width="14" stroke-linecap="round" stroke-linejoin="round"/><path d="M-8 126 L-11 149" fill="none" stroke="#2c2c34" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><ellipse cx="-13" cy="150" rx="7.5" ry="5" fill="#4a3424"/></g><path d="M8 126 L15 143" fill="none" stroke="#3b2618" stroke-width="14" stroke-linecap="round" stroke-linejoin="round"/><path d="M8 126 L15 143" fill="none" stroke="#2c2c34" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><ellipse cx="17" cy="144" rx="7.5" ry="5" fill="#4a3424"/></g><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><path d="M-16 88 Q-21 104 -26 136 Q0 141 26 136 Q21 104 16 88 Z" fill="#f4efe6"/><path d="M0 98 L0 138" fill="none" stroke-width="2.5"/><path d="M-25 130 Q0 135 25 130 L26 136 Q0 141 -26 136 Z" fill="#e8384f"/><path d="M-12 88 L0 101 L12 88 Z" fill="#e8384f"/><path d="M-19 110 Q0 114 19 110 L19.6 115 Q0 119 -19.6 115 Z" fill="#3b3b3b"/></g><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"></g><g transform="translate(0 106)"><g stroke="#3b2618" stroke-width="2.4" stroke-linejoin="round" stroke-linecap="round"><circle r="6.8" fill="#fff6e8"/></g><path d="M1 -4.5 L-2.5 0.5 L0 0.5 L-1 4.5 L2.5 -0.5 L0 -0.5 Z" fill="#e8384f"/></g><path d="M-15 95 Q-34 102 -51 88" fill="none" stroke="#3b2618" stroke-width="13" stroke-linecap="round" stroke-linejoin="round"/><path d="M-15 95 Q-34 102 -51 88" fill="none" stroke="#f4efe6" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/><path d="M15 95 Q34 102 51 88" fill="none" stroke="#3b2618" stroke-width="13" stroke-linecap="round" stroke-linejoin="round"/><path d="M15 95 Q34 102 51 88" fill="none" stroke="#f4efe6" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><circle cx="-51" cy="88" r="5.6" fill="#ffe0c8"/></g><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><circle cx="51" cy="88" r="5.6" fill="#ffe0c8"/></g><g transform="translate(0 -1) rotate(-3 0 88)"><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><ellipse cx="-38" cy="62" rx="6" ry="7.5" fill="#ffe0c8"/><ellipse cx="38" cy="62" rx="6" ry="7.5" fill="#ffe0c8"/><ellipse cx="0" cy="52" rx="40" ry="36" fill="#ffe0c8"/></g><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"></g><ellipse cx="-26" cy="71" rx="6.5" ry="4.03" fill="#ff9a8a" opacity="0.9"/><ellipse cx="26" cy="71" rx="6.5" ry="4.03" fill="#ff9a8a" opacity="0.9"/><path d="M-21 63 Q-15 54 -9 63" fill="none" stroke="#3b2618" stroke-width="3.4" stroke-linecap="round"/><path d="M9 63 Q15 54 21 63" fill="none" stroke="#3b2618" stroke-width="3.4" stroke-linecap="round"/><path d="M-7 74.5 Q0 75.5 7 74.5 Q5 83 0 83 Q-5 83 -7 74.5 Z" fill="#7a2a2a" stroke="#3b2618" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M-3.5 81 Q0 78 3.5 81 Q0 82.6 -3.5 81 Z" fill="#ff8f9a"/><g stroke="#3b2618" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"><path d="M-43 64 C-47 24 -24 9 0 9 C24 9 47 24 43 64 L41 56 Q34 40 35.1 38.3 L29.3 51.1 Q22.3 40 23.4 36.6 L17.6 47.8 Q10.5 40 11.7 34.9 L5.9 46.2 Q-1.2 40 0 34.9 L-5.9 46.2 Q-12.9 40 -11.7 36.6 L-17.6 47.8 Q-24.6 40 -23.4 38.3 L-29.3 51.1 Q-36.3 40 -35.1 40 L-41 56 Z" fill="#8fd8b4"/><path d="M-43 50 Q-49 69 -40 94 Q-35 72 -34 58 Z" fill="#8fd8b4"/><path d="M43 50 Q49 69 40 94 Q35 72 34 58 Z" fill="#8fd8b4"/><path d="M-20 22 Q-8 15 4 17" fill="none" stroke="#c1ead6" stroke-width="3" stroke-linecap="round" opacity=".8"/><circle cx="-36" cy="30" r="5" fill="#e8384f"/><circle cx="36" cy="30" r="5" fill="#e8384f"/><path d="M24 20 L12 10 L13 30 Z M24 20 L36 8 L37 28 Z" fill="#e8384f"/><circle cx="24" cy="20" r="4.5" fill="#c53043"/></g></g><g stroke="#3b2618" stroke-width="3.2" stroke-linejoin="round" stroke-linecap="round"><path d="M-50 18 L-44 32 M-62 30 L-50 38 M50 18 L44 32 M62 30 L50 38" fill="none"/></g></g></svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 4.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.2 KiB

Some files were not shown because too many files have changed in this diff Show more