specimen
A looping type-specimen video from a font file.
specimen renders a looping clip of a typeface set as a fixed number of left-aligned lines
of glyphs whose letters and colours change over a composed sequence of pulses, then captures
it. The glyph size is derived from lines (the rows fill the top 80% of the frame); the bottom
20% shows the background and the font name. Point it at a font file and give it a name;
everything else has a sensible default. No url required.
{
name: "brand-type",
generator: "specimen",
options: {
font: "public/fonts/YourFont.woff2",
name: "Your Font",
template: "sweep",
},
}Templates
template loads a named preset; your explicit options still override what it sets.
"sweep"— a seamless-looping showcase of even per-character colour sweeps (muted → accent → foreground) on a dark palette chosen so the accent reads."demo"— a labelled walkthrough of every pulse behaviour (each easing curve, a colour sweep, a mingle) with demo mode on; runs once, no mirror.
Pulses (the storyboard)
A pulse is one beat of the animation: a named span of time during which some fraction of the
glyph cells change letter and/or colour. An empty pulse is a hold. The clip length is the sum of
the pulse durations (doubled when mirror is on). Compose a sequence to author the whole clip.
| Pulse field | Type | Meaning |
|---|---|---|
name | string | Human label for the beat (readability only). Default "". |
duration | number | Length of the beat, in seconds. Required, must be positive. |
chars | number (0–1) | Fraction of cells whose glyph changes during the beat (1 = every cell once). Default 0. |
colors | number (0–1) | Fraction of cells whose colour changes during the beat (1 = every cell once). Default 0. |
color | "foreground" | "muted" | "accent" | Target every colour change at this token (a deliberate sweep) instead of a weighted-random pick. Omit for the default scattered recolour. |
pacing | "even" | "linear" | "ease-in" | "ease-out" | "ease-in-out" | "random" | How changes are distributed in time across the beat. Default "even". "linear"/"even" are uniform; "ease-in" front-loads; "ease-out" back-loads; "ease-in-out" bunches at both ends; "random" scatters. |
pulses must contain at least one beat. Omit it entirely and a built-in default storyboard
(DEFAULT_PULSES — a lively ~10-second outward half: intro hold → first letters → settle →
ripple → colour sweep → rest → quick burst → drift → finale → outro hold) is used, which
mirrors into a ~20-second seamless loop.
options: {
font: "public/fonts/YourFont.woff2",
name: "Your Font",
pulses: [
{ name: "hold", duration: 0.8 },
{ name: "to accent", duration: 2.2, colors: 1, color: "accent", pacing: "ease-in-out" },
{ name: "glyph drift", duration: 1.4, chars: 0.5 },
],
}Key options
| Option | Type | Default | Meaning |
|---|---|---|---|
font | string | — | Required. Font file (relative or absolute). |
name | string | "" | Display name shown bottom-left. |
template | "demo" | "sweep" | — | Load a preset (overridable). |
width / height | number | 1920 / 1080 | Output frame size. |
fps | number (≤120) | 30 | Frames per second. |
durationSeconds | number | derived | Override the (mirrored) sum of pulse durations. |
deviceScaleFactor | number (≤4) | 1 | Render scale. |
weight | number (1–1000) | 820 | Glyph weight on the variable-font axis. |
lines | number (1–40) | 3 | Number of glyph rows. The glyph size is derived so the rows fill the top 80% of the frame. |
leading | number | 0.78 | Line-height of the rows. |
blacklist | string | "" | Glyphs to exclude (case-insensitive). |
characterPool | string | A–Z 0–9 + symbols | Override the glyph pool (≥2 distinct). |
colors | { background, foreground, muted, accent?, label? } | see below | Colour tokens the glyphs cycle through (override any subset). |
colorWeights | { foreground, muted, accent } | 2 / 2 / 1 | Relative likelihood of each token on a random recolour. |
characterIntensity / colorIntensity | number | 1 | Multiply every pulse's change fractions. |
maxLineDrift | number (0–0.5) | 0.05 | Max fraction a line's width may drift as glyphs change — keeps the right edge stable. |
mirror | boolean | true | Play the pulses out and back for a seamless loop. |
demo | boolean | false | Overlay the active pulse's name (to see which beat is playing). |
seed | number | 1 | Schedule seed — same seed ⇒ identical animation. |
crf | number (0–51) | 18 | x264 quality (lower = better/larger). |
fileName | string | <name>.mp4 | Output filename. |
seedmakes the otherwise-scattered animation deterministic: the same seed always produces the same take. Change it for a different (still reproducible) result.
Colours
colors is the palette the glyphs cycle through (any CSS colour strings). Override any subset;
each key falls back independently.
| Key | Default | Meaning |
|---|---|---|
background | #eceef1 | Backdrop behind the glyphs (and the bottom 20% band). |
foreground | #16181d | Primary glyph colour — the resting majority. |
muted | #a7adb6 | Muted/secondary glyph colour. |
accent | falls back to background | Accent colour for occasional pops. Left unset, accent glyphs blend into the backdrop — so set it (or a template) to make accent reads visible. |
label | falls back to foreground | Colour of the font-name label in the bottom corner. |
colorWeights sets the relative likelihood of each token on a random (non-targeted) recolour
— defaults foreground: 2, muted: 2, accent: 1, so accent is a rare pop. A weight of 0
excludes that token from random recoloring entirely (an explicit pulse color can still target
it).
options: {
font: "public/fonts/YourFont.woff2",
name: "Your Font",
colors: {
background: "#1a1714",
foreground: "#f6f3ed",
muted: "#8a7e6f",
accent: "#b49a77", // set accent — left unset, accent glyphs blend into the background
},
colorWeights: { foreground: 3, muted: 2, accent: 1 }, // keep accent a rare pop
}Behaviours
- Width-stable layout. Glyphs are packed into
linesleft-aligned rows, each filled to a target width using the font's real advance widths (measured in-browser), so every row is about the same length. As glyphs change, swaps are width-compensated — adjacent glyphs change as cancelling pairs, and a per-line width budget caps any residual — so each line's total width, and thus its right edge, stays withinmaxLineDrift(default 5%) of its initial. The wall shifts pleasantly without the right edge or the line count ever jumping. (A very smallcharacterPoolleaves little room to compensate; the layout stays locked but the right edge may breathe a touch more.) - Even-coverage picker. Within a pulse, changes are distributed across cells so every glyph is
touched once before any repeats. To wash the whole specimen to one colour evenly, set a pulse's
colors: 1withpacing: "even"(and acolortarget) — that lands exactly one change on each glyph. A fraction below1touches that fraction of the cells. - Default glyph pool. The specimen draws from
ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789$%&@#*+=(uppercase A–Z, digits 0–9, and the symbols$ % & @ # * + =). Override it withcharacterPool(needs ≥2 distinct characters). - Blacklist.
blacklistremoves glyphs from the pool case-insensitively (e.g."qxz"and"QXZ"both drop Q, X, Z). A blacklist that would remove every glyph is ignored — the pool is never left empty. - Mirror. With
mirroron (default), the pulses play out and back as a palindrome that ends exactly on the opening frame, so the clip loops seamlessly. It doubles the clip length. Setmirror: falsefor a one-shot that ends on the last state.