Color providers
font_color accepts six forms: a [r, g, b] list (constant), the string shorthands "rainbow", "color_cycle", "shimmer", and "random", or an inline table for a gradient, a ranged color cycle, or a tuned shimmer. The constant, rainbow, and color_cycle forms cover most signs. The same field on top_color / bottom_color for two_row and image widgets behaves identically — pick once per widget, swap providers without changing anything else.

Shimmer has its own demo further down — see Shimmer.
The six providers
Section titled “The six providers”| Provider | Syntax | Per-char? | Animates? |
|---|---|---|---|
| Constant | [r, g, b] | no | no |
| Rainbow | "rainbow" | yes | yes — hue sweeps per frame |
| ColorCycle | "color_cycle" | no | yes — whole-string hue rotation |
| ColorCycle range | {style = "color_cycle", from = [...], to = [...], speed=N} | no | yes — restricted hue arc |
| Gradient | {style = "gradient", from = [...], to = [...]} | yes | no — frozen interpolation |
| Shimmer | "shimmer" or {style = "shimmer", shimmer = "gold", ...} | yes | yes — bright-spot sweep with pause |
| Random | "random" | no | no — picks once on visit |
Per-char providers (rainbow, gradient, shimmer) assign a unique color to each character position and call color_for once per character per draw. Whole-string providers (constant, color_cycle, random) compute one color per draw and apply it to the entire message.
Constant
Section titled “Constant”The simplest case — a fixed [r, g, b] triplet. Nothing animates; the color is stable across every frame and every loop.
[[playlist.section.widget]]type = "message"text = "Now Open"font_color = [255, 200, 0]Rainbow
Section titled “Rainbow”Per-character hue offset that advances each frame. The first character starts at a different hue than the second, and every character’s hue sweeps forward 8° per tick (default). rainbow is a continuous-phase provider — the sweep does not reset between loop_count repetitions, so the color keeps flowing rather than snapping back to the start on each pass.
[[playlist.section.widget]]type = "message"text = "Grand Opening"font_color = "rainbow"Fine-tune with an inline table if the defaults are too fast or too spread:
[[playlist.section.widget]]type = "message"text = "Grand Opening"font_color = {style = "rainbow", speed = 4, char_offset = 20}speed is hue degrees advanced per 50 ms tick (default 8). char_offset is the hue gap between consecutive characters (default 30).
ColorCycle
Section titled “ColorCycle”Whole-string hue rotation — every character shares one color, and that color cycles through the spectrum over time. Subtler than rainbow; useful when you want motion without the striped look. Also continuous-phase — the cycle does not reset between loops.
[[playlist.section.widget]]type = "message"text = "Now Playing"font_color = "color_cycle"Speed is configurable via {style = "color_cycle", speed = N} (degrees per tick, default 5).
Restricting the hue arc
Section titled “Restricting the hue arc”Add from and to RGB colors to restrict the cycle to the shorter arc between those two hues instead of sweeping the full wheel. The engine always takes the shorter path — from = [255, 0, 0] (red) and to = [0, 0, 255] (blue) sweeps through the 120° magenta/violet arc rather than the 240° yellow/green/cyan arc. Without from/to, full-wheel behavior is unchanged.
[[playlist.section.widget]]type = "message"text = "Now Playing"font_color = {style = "color_cycle", from = [225, 48, 108], to = [120, 230, 255], speed = 5}from and to must both be present or both absent. Achromatic colors (gray, white, black) are rejected — their hue is undefined. speed = 0 is also rejected; use a plain [r, g, b] constant instead.
Common patterns
Section titled “Common patterns”Brand palette sweep — limit the cycle to your two brand colors so the animation never wanders into off-brand hues:
font_color = {style = "color_cycle", from = [255, 92, 38], to = [255, 183, 3], speed = 3}# Flame → amber — stays in the phoenix-warm arcWarm-cool oscillation — a tight arc between two complementary hues creates a slow temperature-shift effect:
font_color = {style = "color_cycle", from = [255, 140, 0], to = [120, 230, 255], speed = 2}# Amber → cyan — warm/cool pulseGradient
Section titled “Gradient”A frozen left-to-right interpolation between two colors. char_index drives the interpolation; the frame counter is ignored — so the colors never shift, but each character gets its own tint. Unlike rainbow and color_cycle, gradient does not require the per-tick render loop; the engine can take a paint-once-and-sleep fast path for otherwise-static content.
[[playlist.section.widget]]type = "message"text = "Fade Effect"font_color = {style = "gradient", from = [255, 80, 80], to = [80, 80, 255]}from and to are [r, g, b] triplets. The first character gets from, the last gets to, and all others interpolate linearly between them.
Shimmer
Section titled “Shimmer”A bright spot glides left-to-right across the text — one shimmer_color highlight sweeping over a base_color rest state — then pauses, then repeats. The spot fades in and out with a cosine curve so it blends smoothly rather than clipping abruptly.
[[playlist.section.widget]]type = "message"text = "Now Hiring"font_color = "shimmer""shimmer" as a plain string uses the defaults: dim blue-gray base, white highlight, 14 chars/second, 8-char-wide spot, 0.5 s pause between sweeps. Tune with an inline table:
[[playlist.section.widget]]type = "message"text = "Grand Opening"font_color = {style = "shimmer", base = [40, 40, 60], shimmer = "gold", speed = 10, width = 6, pause = 1.0}| Field | Default | Description |
|---|---|---|
base | [60, 60, 80] | Rest color. [r, g, b] or a shorthand: "white", "gold", "blue", "cyan". |
shimmer | "white" | Highlight color. Same formats as base. |
speed | 14.0 | Chars per second the spot travels. Lower = slower, dreamier. |
width | 8.0 | Spot width in chars. Wider = broader glow; narrower = sharper. |
pause | 0.5 | Seconds of rest (base color only) between sweeps. |
Shimmer is a continuous-phase provider — it does not reset between loop_count repetitions, so the sweep keeps flowing across playlist loops rather than snapping back to the start.
When a section transition fires while a shimmer sweep is mid-glide, the engine waits up to roughly one second for the spot to reach its pause window before switching scenes, so the bright spot isn’t teleported by the cut. Sweeps that need longer than a second to reach their pause — or a pause shorter than a single frame — transition immediately, as before.
Shimmer with a dark base color: The base defaults to a dark blue-gray ([60, 60, 80]). If you want truly black resting characters, set base = [0, 0, 0]. High-contrast setups (dark base, bright shimmer) look best; a light base with a slightly-brighter shimmer produces a subtle, professional feel.
Common patterns
Section titled “Common patterns”
Frost — cool blue rest state, white highlight, slow sweep. The spot glides wide and lazy across the text, leaving it dim and icy at rest.
font_color = {style = "shimmer", base = "blue", shimmer = "white", speed = 5, width = 4, pause = 1.2}Gold coin — dark amber base, classic gold [255, 215, 0] (#FFD700) highlight. Narrow, fast sweep with a pause between cycles — the bright streak catches and disappears like light glancing off a spinning coin.
font_color = {style = "shimmer", base = [90, 55, 0], shimmer = [255, 215, 0], speed = 18, width = 3, pause = 0.8}Hot ember — near-black red base, orange highlight. Medium speed, narrow width — a glowing coal that briefly catches.
font_color = {style = "shimmer", base = [60, 10, 0], shimmer = [255, 120, 30], speed = 12, width = 4, pause = 0.6}Neon breathe — near-black base, bright cyan highlight, wide spot, no pause. The spot sweeps without stopping, so the text breathes continuously rather than flashing and going dark.
font_color = {style = "shimmer", base = [0, 5, 0], shimmer = [0, 230, 200], speed = 5, width = 10, pause = 0}Random
Section titled “Random”Picks a single random color at widget construction and holds it for the duration of the visit. The color does not change frame-to-frame or character-to-character; it just picks something different each time the section plays. Useful for adding variety to long-running displays without any visible animation.
[[playlist.section.widget]]type = "message"text = "Daily Special"font_color = "random"Which to use
Section titled “Which to use”- Constant — brand colors, anything that shouldn’t animate.
- Color_cycle — the whole message shifts hue together; subtler than rainbow. Add
from/toto keep the sweep inside your brand palette. - Rainbow — per-character hue sweep; use it when flair is the point (kids’ sections, grand openings, announcements).
- Gradient — frozen left-to-right interpolation; good when your brand has two anchor colors you want to bridge.
- Shimmer — a gliding bright-spot sweep; good for drawing attention to a single message, “now open” signage, or any case where you want motion without the full color-sweep effect.
- Random — picks a different color each time the section plays; no animation.
Inline emoji and per-char providers
Section titled “Inline emoji and per-char providers”Rainbow and gradient sweep continuously across :slug: emoji boundaries. The sprite still renders in its own native colors, and the surrounding text gets per-character colors with the index advancing across the emoji segments without resetting. This means the hue does not jump or restart when it crosses an emoji — the stripe flows through the whole message as if the emoji were transparent to the color math.
[[playlist.section.widget]]type = "message"text = ":star: Now Enrolling :star:"font_color = "rainbow"Colors and animations are independent
Section titled “Colors and animations are independent”font_color and animation are separate axes on a message widget. A widget can carry both simultaneously — for example font_color = "rainbow" and animation = "typewriter" — and each effect tracks its own frame counter independently. Characters type out in rainbow with no interference between the two effects. See animations for the full animation vocabulary.