palette-reel
A looping colour-palette reveal video — the moving counterpart of palette.
palette-reel is the moving version of palette. The colours
start as thin slivers showing only their name; one at a time a sliver expands into a band that
reveals its configured details (hex / oklch / rgb …), holds, then collapses before the next
opens — sweeping every colour and looping seamlessly. It's a local generator: no url, output
is mp4. Only colors is required.
{
name: "brand-colors-reel",
generator: "palette-reel",
options: {
colors: [
{ name: "Ink", hex: "#1a1714" },
{ name: "Camel", hex: "#b49a77" },
{ name: "Loden", hex: "#5c5e4c" },
],
details: ["hex", "oklch"],
},
}The available details field ids are name, hex, rgb, oklch, and hsl — the same set as
the still palette.
Config options
Only colors is required; everything else has a default. Reference is the interactive view;
TypeScript is the same shape in code, every option at its default.
colors{ name, hex }[]required
The colours to reveal (at least one).
namestringrequiredDisplay name shown on the colour's sliver / band.
hexstringrequiredColour value as a hex string like #D7DBDE.
details('name' | 'hex' | 'rgb' | 'oklch' | 'hsl')[]default ['hex', 'oklch', 'rgb']Fields revealed when a colour expands. The name is always shown, so it's ignored here.
outputobject
Output frame / encode settings.
widthnumberdefault 1920Output width in CSS px.
heightnumberdefault 1080Output height in CSS px.
deviceScaleFactornumberdefault 1Render scale, max 4 — higher captures crisper, then downscales into the video.
fpsnumberdefault 30Output frames per second (max 120).
crfnumberdefault 18x264 quality, 0–51 (lower = better quality / larger file).
fileNamestringOutput filename. Defaults to "<slug(asset name)>.mp4".
timingobject
Reveal timing (milliseconds).
holdMsnumberdefault 2000How long each colour stays fully open before handing off to the next (ms).
transitionMsnumberdefault 700Crossfade length from one open colour to the next (ms).
bouncebooleandefault truePing-pong the sweep (down the list then back up) so every handoff is between neighbouring bands, avoiding the last→first "pinch" at the loop seam. When false, it wraps directly (last→first): shorter, but crossfades non-adjacent bands at the seam.
easing'linear' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'ease-out-strong' | 'ease-in-out-strong'default 'ease-in-out'Easing applied to the crossfade ramp.
durationMsnumberClip length override (ms). Omit to derive (count × (hold + transition)) for a clean loop.
layoutobject
Sliver arrangement and sizing.
orientation'rows' | 'columns'default 'rows'Sliver arrangement: horizontal bands (names upright) or full-height vertical strips.
grownFlexnumberdefault 12How many times a sliver's share a fully-open band takes (a collapsed sliver is the baseline). Min 1.
minCrossPxnumberdefault 0Minimum cross-size of a sliver (px) so its name stays legible. 0 derives it from the height.
nameAlwaysVisiblebooleandefault trueKeep the name fully visible even in a collapsed sliver (else it fades with the band).
backgroundstringdefault '#ffffff'Backdrop behind the bands (shown in the gap between them).
gapnumberdefault 0Gap between bands (px). 0 means the bands abut.
cornerRadiusnumberdefault 0Band corner radius (px). 0 is square.
textobject
Label typography and formatting.
uppercasebooleandefault falseUppercase the colour names.
rgbStyle'labeled' | 'css' | 'plain'default 'labeled'RGB string style.
oklchStyle'css' | 'labeled'default 'css'OKLCH string style.
fontFilestringCustom font file (woff2 / woff / ttf / otf), served into the render. Omit for a system bold sans.
fontWeightnumberdefault 700Label font weight (1–1000).
fontSizenumberName font size in px. Omit to derive from the frame size.
detailFontScalenumberdefault 0.62Detail-line font size as a fraction of the name size.
contrastobject
Text-colour selection by band contrast.
textLightstringdefault '#ffffff'Light text colour, used on dark bands (picked by contrast).
textDarkstringdefault '#141414'Dark text colour, used on light bands (picked by contrast).
contrastThresholdnumberdefault 0.5Luminance above which the dark text is used (0..1).
{
name: "brand-colors-reel",
generator: "palette-reel",
options: {
// --- colours & reveal ---
colors: [
{ name: "Ink", hex: "#1a1714" },
{ name: "Camel", hex: "#b49a77" },
],
details: ["hex", "oklch", "rgb"],
// --- output ---
output: {
width: 1920,
height: 1080,
deviceScaleFactor: 1,
fps: 30,
crf: 18,
// fileName: "brand-colors-reel.mp4", // optional; defaults to <slug(name)>.mp4
},
// --- timing (ms) ---
timing: {
holdMs: 2000,
transitionMs: 700,
bounce: true,
easing: "ease-in-out",
// durationMs: 12000, // optional; derived from count × (hold + transition)
},
// --- layout & sizing ---
layout: {
orientation: "rows",
grownFlex: 12,
minCrossPx: 0,
nameAlwaysVisible: true,
background: "#ffffff",
gap: 0,
cornerRadius: 0,
},
// --- styling ---
text: {
uppercase: false,
rgbStyle: "labeled",
oklchStyle: "css",
// fontFile: "./fonts/Brand.woff2", // optional; falls back to a system bold sans
fontWeight: 700,
// fontSize: 64, // optional; derived from the frame size
detailFontScale: 0.62,
},
// --- contrast ---
contrast: {
textLight: "#ffffff",
textDark: "#141414",
contrastThreshold: 0.5,
},
},
}Every option also has hover docs in
pro-visu.config.ts— the authoring types are generated from the validation schema, so the editor always matches what the tool accepts.
Examples
Looping brand reveal
Feed it the palette and the details you want on each band; the reel expands one colour at a time,
holds, and collapses — looping seamlessly.
{name: "colors-reel",generator: "palette-reel",options: { colors: [ { name: "Ink", hex: "#1a1714" }, { name: "Paper", hex: "#f6f3ed" }, { name: "Camel", hex: "#b49a77" }, { name: "Loden", hex: "#5c5e4c" }, { name: "Cognac", hex: "#8a5a3c" }, ], details: ["hex", "oklch"], text: { uppercase: true }, layout: { background: "#1a1714" }, contrast: { textLight: "#f6f3ed" },},}