kumiko-gen spec
Overview
kumiko-gen generates deterministic kumiko-style SVG thumbnails from article slug strings. Published as npm package @takazudo/kumiko-gen. Zero external dependencies.
Input: slug string. Output: SVG string. Same slug always produces the same SVG.
Generation pipeline
- FNV-1a hash: slug string → 32-bit unsigned integer
- Mulberry32 PRNG seeded with hash
- Seeded random determines: layer count, pattern selection, per-layer color, overlap count, stroke widths, grid divisions, layer transforms
- Generate equilateral triangle grid
- Render each layer with per-layer color, rotation, position offset, and per-overlap stroke width
- Assemble SVG with viewBox (zoom support)
All randomness comes from the seeded PRNG. No Math.random().
Options
interface KumikoOptions {
size?: number; // canvas size in px (default: 800)
divisions?: number; // grid columns (default: seeded from [6, 8, 10])
zoom?: number; // viewBox crop factor (default: 1)
fg?: string; // override stroke color for all layers
bg?: string; // background color (default: "#2d2d2d")
strokeWidth?: number; // override stroke width (default: seeded per overlap)
colorScheme?: string; // color scheme name or "random" for deterministic per-slug selection
}
Color scheme
36 built-in color schemes (Dracula, Nord, Catppuccin, Gruvbox, TokyoNight, etc.) are available. Each scheme has 1 background + 7 foreground colors.
The default scheme uses a dark background with 8 foreground colors:
| Index | Hex | Name |
|---|---|---|
| bg | #2d2d2d | dark background |
| 0 | #4a4a4a | dark gray |
| 1 | #b5524a | brick red |
| 2 | #5ea85e | green |
| 3 | #c8a64e | gold |
| 4 | #737d8e | slate blue |
| 5 | #a87a96 | mauve |
| 6 | #5a8a8e | teal |
| 7 | #d5d5d5 | light gray |
--color-scheme <name> selects a specific scheme. --color-scheme random picks a deterministic scheme per slug (derived from the hash, not the PRNG). --list-color-schemes prints all available names.
--fg overrides all layers to a single color. --bg overrides background. These take priority over the selected scheme.
Stroke style
Fixed for all layers:
stroke-linecap="square"stroke-linejoin="bevel"
Triangle grid
- Canvas tiled with equilateral triangles
divisionscontrols column count (6, 8, or 10)- Row height:
colWidth * (√3 / 2) - Each row alternates upward/downward triangles
- Grid extends slightly beyond canvas to fill edges
Triangle data:
vertices: [Point, Point, Point]centroid: Point(average of 3 vertices)isUpward: boolean
Patterns (9 types)
Basic (3 elements/triangle)
| Name | Description |
|---|---|
asanoha | Vertex → centroid lines. Star motif when mirrored. |
mitsukude | Edge midpoint → centroid lines. Y-shape. |
goma | Vertex → opposite edge midpoint lines. Inner subdivision. |
shippo | Arcs between vertex pairs. Radius = edge length * 0.5. |
Complex (6-12 elements/triangle)
| Name | Elements | Description |
|---|---|---|
yae-asanoha | 6 | All vertex + midpoint → centroid radials. |
kikko | 6 | Inner triangle (40% scale) + outer-to-inner vertex connections. |
sakura | 6 | Quadratic bezier petals at vertices + centroid-to-midpoint spokes. |
bishamon | 9 | Inner triangle (35%) + vertex→centroid + inner→opposite midpoint cross. |
izutsu | 12 | Two concentric triangles (65%, 30%) + radial connections between all 3 levels. |
Layers
Multiple distinct patterns overlaid in one SVG. Each layer gets its own color from the palette.
| Probability | Layer count |
|---|---|
| 40% | 2 |
| 40% | 3 |
| 20% | 4 |
Patterns selected by shuffling all 9, taking first N.
Per-layer transform
Each layer gets:
- Color: random from 8-color palette
- Rotation: 0–360 degrees around canvas center
- Position offset: ±20% of canvas size in X and Y
Overlap
Each layer independently rolls for overlap copies of the same pattern.
| Probability | Copies |
|---|---|
| 50% | 1 (no overlap) |
| 30% | 2 |
| 20% | 3 |
Additional copies add on top of layer transform:
- Extra offset: ±1.5% of canvas size
- Extra rotation: ±4 degrees
All overlaps within the same layer share the same color.
Stroke width
Each overlap copy gets its own random stroke width. Options are scaled by total density (sum of all overlap copies across all layers):
| Density | Stroke width options |
|---|---|
| 1 | 1, 1.5, 2, 3, 4 |
| 2 | 0.5, 1, 1.5, 2, 3 |
| 3 | 0.5, 1, 1.5, 2 |
| 4 | 0.5, 0.75, 1, 1.5 |
| 5+ | 0.5, 0.75, 1 |
--stroke-width CLI flag overrides all random widths with a single value.
Zoom
Crops SVG viewBox to center portion.
viewSize = size / zoom
viewOffset = (size - viewSize) / 2
Full geometry is still generated; only the visible area changes.
CLI
pnpm kumiko-gen <slug> [options]
| Flag | Description |
|---|---|
--size <n> | Canvas size (default: 800) |
--zoom <n> | Zoom factor (default: 1) |
--color-scheme <name> | Color scheme name or random |
--list-color-schemes | Print available color scheme names and exit |
--fg <color> | Override stroke color for all layers |
--bg <color> | Background color |
--stroke-width <n> | Override stroke width |
--divisions <n> | Grid column count |
--out <path> | Output file path |
--out-dir <dir> | Output directory ({dir}/{slug}.svg) |
Default output (standalone): <slug>.svg in current directory.
In zpaper, the root script passes --out-dir blog/public/thumbnails --color-scheme random --zoom 5 so pnpm kumiko-gen <slug> outputs a zoomed, uniquely colored SVG to blog/public/thumbnails/<slug>.svg.
Source
Source code: github.com/Takazudo/kumiko-gen