Configuration (specreel.yml)
A specreel.yml in your working directory (or the traces directory, or passed
via --config) configures a gallery build. It's parsed by a tiny built-in YAML
subset — no PyYAML needed. Generate a starter with specreel init <traces>.
title: Kumkuat — Product Flows # gallery heading / kicker
product_name: Polarys # AI narration says this instead of localhost URLs
quality: high # motion: high | medium | low (file size tradeoff)
video_width: 1280 # --mp4 width in px (default: the source's native width)
pace: normal # slow | normal | fast | a number multiplying each step's hold
theme: dark # dark | light
ai: false # true (+ ANTHROPIC_API_KEY) to narrate captions
ai_model: claude-opus-5-5 # model for narration (haiku-5-5 is cheaper)
bundle: false # true to also emit the single-file gallery.html
branding: full # minimal = white-label: no specreel wordmark, no "— Specreel" title suffix
fonts: google # system = no Google Fonts <link> tags (offline, strict CSP, GDPR)
showcase: false # true to also emit showcase/ — the curated,
# customer-facing gallery (public & passing flows only)
showcase_title: Acme in action # showcase headline (default "<product_name> in action")
showcase_tagline: See the product do the work. # sub-line under the headline
showcase_accent: "#7c5cff" # accent color (hex also re-tints the glows)
showcase_logo: assets/logo.svg # logo file, embedded into the page (svg/png/jpg/webp)
showcase_css: showcase.css # raw CSS appended to the page — full styling control
showcase_cta_label: Start free trial # call-to-action button on every showcase player's outro
showcase_cta_url: https://acme.com/signup # http(s) only
public_url: https://acme.github.io/app/ # where it's served: og:image (link previews) + notification links
notify_webhook: https://hooks.slack.com/… # Slack incoming webhook (or --notify)
analytics: '<script defer data-domain="x" src="https://plausible.io/js/script.js"></script>'
# Leading navigations to drop from every demo (e.g. an auth-login redirect)
setup_urls:
- /test-api-key
# setup_once: true # trim only the first matching block (a sign-in prelude a flow may revisit)
flows: # keyed by flow slug (matches the gallery output)
signup:
title: Sign up
public: true
communications:
title: Browse the document library
public: true
internal-admin:
hidden: true # exclude from the gallery
some-flow:
healed: true # force the amber "updated" badge (e.g. a CI healer)
Top-level keys
| Key | Type | Meaning |
|---|---|---|
title |
string | Gallery heading (the green kicker on the index). |
product_name |
string | AI narration uses this instead of raw localhost URLs. |
theme |
dark | light |
Color theme. CLI --theme overrides. |
video_width |
int | --mp4 width in px. Default: the picture source's native width — the recording's when video.webm sits next to the trace, else the trace's 800px-capped frames (a bigger value then enlarges, softly). CLI --video-width overrides. |
quality |
high | medium | low |
Motion quality: how many screencast frames each step embeds (file-size knob). CLI --quality overrides. |
pace |
slow | normal | fast | number |
How long each step lingers. Steps already hold long enough to read their caption (narration included); this multiplies every hold — slow = 1.5×, fast = 0.7×, 2 = twice as long. CLI --pace overrides. Viewers also get a 1× / 1.5× / 2× / 0.75× speed control in the player. |
ai |
bool | Enable AI narration (still needs a key). CLI --ai also enables. |
ai_model |
string | Narration model. |
voice |
string | Studio voiceover voice (e.g. nova) — enables pre-rendered TTS narration (BYO OPENAI_API_KEY). CLI --voice also enables. |
tts_model |
string | TTS model for voice (default gpt-4o-mini-tts). |
tts_instructions |
string | Delivery notes for the narrator (gpt-* TTS models only). |
bundle |
bool | Also emit gallery.html. CLI --bundle also enables. |
branding |
full | minimal |
minimal drops the specreel wordmark and the "— Specreel" <title> suffix from every page — gallery, players, bundle and showcase — and keeps the provenance line. The white-label knob: White-label builds. |
fonts |
google | system |
system omits the Google Fonts <link> tags from every page; the built-in font stacks fall back to local fonts. Offline and CSP builds. |
showcase |
bool | Also emit showcase/, the curated customer-facing gallery — only flows with public: true that are passing. CLI --showcase also enables. |
showcase_title |
string | Showcase headline (default <product_name> in action). |
showcase_tagline |
string | Sub-line under the showcase headline. |
showcase_accent |
string | Accent color for the showcase — its index and every showcase player (a hex value also re-tints the glows, the cursor and the progress bar). |
showcase_logo |
path | Logo embedded, as a data URI, into the showcase header and each showcase player in place of the wordmark (svg/png/jpg/gif/webp; relative to this file). |
showcase_css |
path | Raw CSS appended to the showcase index and every showcase player — full styling control (relative to this file). |
showcase_cta_label |
string | Label of the call-to-action button on each showcase player's outro (default Learn more when only the URL is set). |
showcase_cta_url |
string | Where that button links. http(s):// only — anything else is ignored with a warning. Opens in a new tab; "Replay" becomes the secondary link. |
public_url |
string | Where the gallery is served. Sets the absolute og:image on every page so a pasted link unfurls with its poster (sharing & embedding), and links notifications. CLI --url overrides. |
notify_webhook |
string | Slack incoming-webhook URL. |
analytics |
string | Raw HTML injected into every page's <head> (BYO Plausible/GA/…). |
setup_urls |
list | goto steps to these URLs are trimmed from every demo, with the actions on that page. |
setup_once |
bool | Trim only the first matching block per demo — a sign-in prelude when the flow then opens the same URL (the hosted runner sets this). Default false. |
flows |
map | Per-flow overrides, keyed by slug. |
Showcase players
With showcase: true, the players under showcase/<flow>/ are rendered for
customers, not engineers — from the same player template as the internal gallery,
switched by a data-mode="showcase" attribute on the page, so the two can't drift:
- your product_name and showcase_logo sit in the header in place of the specreel
wordmark, showcase_accent and showcase_css apply, and the <title> reads
"Sign up — Acme" rather than "Sign up — Specreel";
- step kickers read "Step 3 of 10" instead of CHECK / ACTION / NAVIGATE, the step
list loses its kind pills, the header loses "regenerated from trace", and the intro
says "Watch the demo";
- the outro says "Demo complete" with your call to action (showcase_cta_label +
showcase_cta_url) as the primary button and "Replay" as the secondary link;
- one engineering fact stays on purpose: "verified
The internal gallery's players are untouched by every showcase_* key. Without a
product_name or showcase_logo the showcase falls back to the specreel wordmark;
branding: minimal drops it entirely.
White-label builds
branding: minimal is the knob a white-label customer asks for: no specreel wordmark
on the gallery, its players, the bundle or the showcase, and no "— Specreel" suffix in
any <title>. With product_name set, the product's name takes the wordmark's place.
The provenance line stays — "Generated from a passing test · date" on the internal
players, "verified · date" on the showcase — because that is the trust mark, not
branding. (Specreel Cloud's per-project logo and accent still apply on top at serve
time; they anchor on the same header slot.)
Offline and CSP builds
fonts: system omits the Google Fonts <link> tags (and the preconnect) from every
page: players, gallery, bundle and showcase. The pages fall back to the local stacks
already in their CSS — JetBrains Mono → ui-monospace → monospace and
Instrument Serif → Georgia → serif — so nothing breaks; the type is just the viewer's
own. Reach for it when:
- viewers are offline or air-gapped (a bundle attached to an email, an internal
network without egress): otherwise every page waits on a font request that never
answers before it settles on the fallback;
- the pages are served under a strict Content-Security-Policy that doesn't allow
fonts.googleapis.com in style-src and fonts.gstatic.com in font-src;
- a customer-facing page must not make third-party requests: every view otherwise
sends the viewer's IP address to Google, which under the GDPR needs a legal basis
(a Munich court ruled in 2022 that embedding Google Fonts without consent breached it).
Per-flow keys (flows.<slug>)
| Key | Type | Meaning |
|---|---|---|
title |
string | Friendly title (overrides the path-derived one). |
public |
bool | Tags the card public, marks it public in the manifest, and includes it in the showcase when passing. |
hidden |
bool | Excludes the flow from the gallery entirely. |
healed |
bool | Forces the amber ⟳ updated state (for a CI self-healing step). |
Slugs come from the trace's directory name (Playwright names result dirs after the test).
specreel initwrites the exact slugs for you.