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 " in the header, the intro and the outro — that the demo is re-verified on every release is the pitch.

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 init writes the exact slugs for you.