# Repo Visuals

> Create hero visuals — animated GIF, static PNG, or animated SVG — for GitHub repositories. Runs a structured discovery conversation (scan repo → recommend format → propose creative scenarios → agree on a brief), then designs bespoke HTML/SVG, previews it in the browser, and exports. Use when the user asks for a README hero, repo banner, README image, GitHub header, social preview card, repo demo GIF, hero image, OG image, project screenshot, repository showcase, or any "image at the top of the README".

- Skill: `livlign/repo-visuals` (Agent Skill, multi-file: 18 files)
- Install (CLI): `npx skillmds@latest add livlign/repo-visuals`
- Raw SKILL.md: https://api.skillmd.com/api/skills/livlign/repo-visuals/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Product & Planning
- Author: livlign (https://skillmd.com/u/livlign)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/livlign/repo-visuals

---


# repo-visuals

Turn a repo (GitHub URL, local folder, or free-text brief) into a hero visual that a viewer sees at the top of the README and instantly understands *what this project does and why it's interesting*. The hero may be an **animated GIF**, a **static PNG**, or an **animated SVG** — the skill recommends one based on the repo's identity, the user picks.

The skill's quality comes from the **discovery dialog**, not from templates. Every hero is bespoke.

## Phases

1. **Discovery** — pick operating mode (Auto / Semi-auto / Manual, §1.1a), scan the repo, summarize findings, recommend a format (animated vs static), ask about vibe/audience/hero moment, propose 2–3 scenarios, converge on a brief. Then **Gate A — brief vs README** (§1.8): narrow repo-context check on the brief before any pixels exist. The mode controls *how many* of these the user answers vs. Claude decides silently — it does not skip any of the craft checks.
2. **Build** — write HTML/CSS/JS for the chosen scenario. For static, design one decisive frame; for animated, a loop. No storyboard step. Then **Gate B — rendered HTML vs README** (§2.6): sample keyframes from the HTML, check what's on screen against the repo's own positioning, allow 1 auto-iteration if it fails. Both gates are deliberately narrow — only repo-context fidelity, not aesthetics or craft.
3. **Preview & iterate** — open the HTML in the user's default browser, iterate in text until the user says ship.
4. **Export** — animated GIF → Puppeteer screencast + ffmpeg palette pipeline. Static PNG → Puppeteer `page.screenshot` (with `deviceScaleFactor: 2` for retina crispness). Animated SVG → author the SVG directly (CSS `@keyframes` or SMIL `<animate>` inside the file); no rasterization step — the source *is* the artifact.
5. **Output** — place the hero in the target repo; optionally open a PR that embeds it in the README.

**Dev mode (author-only):** Phase 6 — Evaluate exists but **only runs in dev mode**. Dev mode is for the skill's author iterating on the skill itself; it collects scorecard data and writes run logs under `./evaluations/` in the user's current working directory. Enable it only when the user explicitly says "dev mode" (or sets `REPO_VISUALS_DEV=1`). In every normal run — including Manual, Semi-auto, and Auto — the skill ends after Phase 5. Do not mention Phase 6 to end users.

---

## Phase 1 — Discovery

Discovery runs **before any HTML is written**. Its job: go from a vague ask ("make a hero GIF for my repo") to a specific, committed creative brief.

### 1.1 Activation

Enter discovery mode whenever the user invokes the skill, **unless** they explicitly say "skip discovery, I have the brief ready."

### 1.1a Operating mode (ask first, once per run)

Before anything else — before the scan summary, before any direction questions — ask the user which operating mode they want for this run. Use the `AskUserQuestion` tool so the choice is structured and visible, not buried in free text. Offer three modes; describe each in concrete terms (what gets asked, what gets decided silently) and include pros/cons so the user can pick with open eyes. Default recommendation: **Semi-auto**.

> Note: this "operating mode" is a skill-level concept about how many questions the skill asks. It is distinct from the Claude Code harness's own auto-mode; the two do not replace each other.

Verbatim `AskUserQuestion` shape (Run mode header, three labeled options with pros/cons): `craft/snippets/operating-mode-question.md`. Paste as-is; option text is load-bearing.

Rules that apply to **every** mode regardless of choice (craft non-negotiables):

- **§1.3 inventory count is always collected.** Auto/semi don't skip it.
- **§4.0 scope-match rule is always enforced.** If the hero says "all" or shows a grid, on-screen reality must match the real inventory — regardless of mode.
- **§1.8 Gate A and §1.8b Gate B are always run on the brief.** Gate A covers repo-fidelity, Gate B covers aesthetic-identity match and the wow check. Auto/semi don't skip them before writing HTML.
- **§3.0a / §4.4a render self-critique is always run.** Every rendered HTML preview screenshot and every exported PNG/GIF gets read and bullet-critiqued before being shown to the user. The user is not the QA pass.
- **Any mode can be upgraded mid-run.** If Auto drifts, user can say "stop, switch to semi" and we resume from the nearest decision point. Do not silently re-ask everything; pick up at the next unanswered question.

After the user picks a mode, commit it to memory for the run (e.g. "Mode: Semi-auto") and reference it when deciding whether to ask or decide silently at each subsequent step. In Auto and Semi-auto, make decisions with a brief one-line note ("going with the Product-UI marketing archetype per §1.4c — amplication-shape repo") so the user can redirect if they disagree.

### 1.2 Input triage

User may provide:

- **GitHub URL** → clone shallow, plus use `gh repo view` for stars/topics/languages/releases.
- **Local path** → scan the tree directly.
- **Free-text brief only** → proceed to direction questions immediately.
- **Nothing** → ask first for one of the above.

### 1.3 Scan checklist (collect a lot)

Gather as much as possible before asking the user anything. Goal: Claude should already have an opinion about what this repo is before the vibe conversation starts.

- README (full text) — extract tagline from first paragraph.
- Manifest(s): `package.json`, `Cargo.toml`, `pyproject.toml`, `go.mod`, `*.csproj`, etc. — description, keywords, dependencies, entry points.
- File tree (depth 3) + top 10 files by size.
- Language breakdown (LOC per language).
- Existing visuals in repo: `assets/`, `docs/`, `.github/`, `public/`, any `*.png` / `*.gif` / `*.svg` near the root.
- **Brand logo — find it, don't invent it.** Look in this order, stop at the first hit: (1) repo files: `public/`, `assets/`, `.github/`, `docs/` for `logo.*`, `favicon.*`, `icon.*`, `brand.*`; (2) README `<img>` / `srcset` entries near the top (often hosted on the project's domain or GitHub CDN — try `curl` with a browser User-Agent); (3) homepage URL from `package.json` / GitHub repo metadata — fetch `/logo.svg`, `/logo-dark.svg`, `/favicon.svg`, `/favicon.ico`, or the og:image; (4) GitHub social-preview image. Save whatever you find to the working dir alongside `index.html`. If nothing turns up, **do not generate or stylize a substitute mark in any mode.** See §2.4 logo rule for what to do next.
- `LICENSE`, `CHANGELOG.md` — maturity signal.
- Recent git log (last 10 commits) — what's active.
- If GitHub: stars, topics, releases, open issues count.
- If `ast-graph` is available locally and the repo has supported languages: run `scan` + `hotspots` + `symbol` on top-level exports for structural inventory.
- **Count the real inventory** — if the repo exposes a collection (tools, commands, pages, themes, plugins, routes), count them from source (route files, `src/tools/*`, CLI command registrations, etc.). Do not estimate from the README. This number is used later: any hero that says "all", "every", "the whole", or shows a grid meant to represent scope must match this count — or explicitly frame itself as a sample ("10 of 30 shown", scroll affordance, "30+"). An undercount reads as a broken promise.
- **If the repo's product has a visible runtime UI** (CLI tool, TUI, terminal app, IDE plugin, desktop app), collect the *real* UI vocabulary before drafting any hero with that UI in it. Look for: screenshots in `assets/`/`docs/`, GIFs already in the README, source-level UI strings (banner generators, status string constants, hook output messages, prompt formats). **Proactively ask the user for a short screen recording** (`.mov` / `.mp4`) if none are in the repo — extract frames with `ffmpeg -i recording.mov -vf "fps=0.5,scale=1400:-1" frames/f%02d.png` and `Read` multiple keyframes for grounding. A generic terminal mock with invented `npm run typecheck`-style lines is a craft failure even when it looks polished — it doesn't match the real app and viewers who use the tool will spot it. This rule is hard, not soft: if the real UI vocabulary isn't accessible, **flag the gap to the user before drafting** rather than inventing.

Summarize findings back to the user in ~6–10 bullets so they can correct misreadings early. Include the inventory count when one exists. **If the scan finds an existing hero**, branch into §1.3a before continuing to direction questions.

### 1.3a Redesign vs greenfield (only if an existing hero is found)

If the scan surfaced an existing hero, branch into the redesign protocol *before* §1.4. Surface the existing file (path, format, age, README placement), then `AskUserQuestion` the entry mode — **Redesign (carry forward)** / **Fresh (ignore existing)** / **Replace in place** — and follow the matching steps. Full protocol (entry-mode question, critique structure, carry-forward rules, scenario constraints) in `craft/redesign.md`.

### 1.4 Direction questions (after the scan summary)

Ask these as a structured batch, with suggested defaults based on what the scan found so the user can just accept or redirect:

1. **Where will this hero live?** — README top / project website / social posts (Twitter, LinkedIn) / conference slide / internal demo / elsewhere. *This drives dimensions and loop length.*
2. **Output format?** — **animated GIF** / **static image (PNG)** / **animated SVG** / **HTML only (no export)**. Not every repo wants motion; some strong identities read better as one sharp frame; some content (vector UI, type, simple geometry) renders sharper, smaller, and theme-aware as SVG. Scan the repo first and offer a recommendation (see §1.4c), then let the user override. Default: whichever §1.4c recommends. *(MP4/WebM and square social cards are listed in the future-formats appendix.)*
3. **Audience** — working devs / eng leaders / newcomers / prospective contributors / all
4. **Vibe** — minimal, playful, technical, cinematic, retro-terminal, brutalist, polished-SaaS
5. **Energy** — calm & meditative / steady / frenetic & showy
6. **Hero moment** — the single thing a viewer should take away
7. **What NOT to imply** — claims, vibes, or audiences this hero must *avoid* suggesting. A hero can look polished and still say the wrong thing (e.g. implying enterprise-readiness for a hobby project, or "fast" for a tool whose pitch is correctness). Capture explicit don'ts so Gate A (§1.8) has something to check against. Examples: *"don't imply this is production-ready,"* *"don't lead with speed — the pitch is correctness,"* *"avoid AI/ML aesthetics — this is a UI library."*
8. **Hard constraints** — brand colors, existing fonts, imagery to include/avoid, duration cap

### 1.4a Probing when the user doesn't know

Many users will answer "I don't know" to vibe / hero moment / audience. That's normal. Don't force them to guess — extract intent sideways:

- **"Show me a repo whose hero you liked"** — reverse-engineer what they respond to.
- **"What do you wish someone understood about this project in 10 seconds?"** — surfaces the hero moment without asking for it directly.
- **"Who's the person whose opinion about this repo matters most to you?"** — surfaces the audience.
- **"Is this repo a calm/serious tool or a fun/exciting one?"** — a binary that usually works for vibe.

If they still can't articulate, offer Claude's best guess based on the scan and ask them to confirm or push back. Be willing to **commit first, confirm second** — a concrete wrong guess triggers better reactions than another open-ended question.

### 1.4c Format recommendation (animated vs static)

Before asking Q2, form an opinion from the scan. **First consult `craft/reference-gallery.md`** — try to place the target repo in one of the catalogued archetypes (Terminal-demo GIF / Product-UI marketing / Brand-first logo / Banner/promo graphic / Diagram-as-hero) and cite the match by name when recommending. If no clean match, fall back to the heuristics below.

The repo's identity tells you which format fits:

**Lean static (PNG) when:**
- The repo's pitch is a **surface**, not a process — a landing page, a typography choice, an API shape, a single striking screenshot.
- Existing visuals in `assets/` / `docs/` are already static and strong.
- The vibe is brutalist, editorial, documentation-first, or "the screenshot is the product" (design systems, component libs shown in a grid, CLI themes whose appeal is one frame).
- Audience skews toward skim-then-click (social posts shared as preview cards, directory listings).
- README opens with one crisp hero sentence and a screenshot — mirror that, don't animate it.

**Lean animated (GIF) when:**
- There's a **before→after transformation**, a step sequence, or a multi-stage pipeline — the motion *is* the pitch.
- The repo is a tool with visible runtime behavior (CLI output, live reload, computation unfolding).
- The hero moment is "watch this happen," not "see this exists."
- The scan surfaces multiple distinct capabilities worth walking through (then: animated; or: static grid).

**Lean animated SVG when:**
- The hero is **vector-friendly**: typography-led, UI chrome, simple geometric motion, line-art mascots, terminal/code-style heroes without raster screenshots or photographs.
- File-size matters and the content is mostly shapes + text — animated SVG often lands 5–50× smaller than the equivalent GIF and stays crisp at any zoom (no palette banding, no sub-pixel text smear).
- Theme-aware rendering is wanted (light/dark via `<picture>` swap or `prefers-color-scheme` media queries inside the SVG).
- Motion is expressible as property animation (transform, opacity, stroke-dashoffset, path morphs, simple keyframe sequences) — *not* per-frame raster effects, particle systems, blend-mode-heavy compositing, or anything that needed JS timing in the HTML version.
- Hard constraints from GitHub: SVG must be loaded via `<img src="...">` (inline `<svg>` is sanitized out of READMEs); **no `<script>`** survives the sanitizer, so motion is CSS `@keyframes` or SMIL `<animate>` only; no pointer/hover interaction (image embed gets no events); external resources (fonts, fetched images) are blocked, so embed everything.
- **Skip SVG if** the hero needs a real screenshot, photograph, dense gradients (size win evaporates once raster is embedded), or JS-driven scene sequencing.

**Lean HTML-only when:**
- The user will embed on a site they control and wants the live animation.
- The user explicitly wants to iterate further with their own tools.

Present the recommendation forcefully: *"Based on the scan, I'd go **static** — this repo's identity is [X], and motion would add noise not signal. Want me to proceed with static, or would you rather animated?"* Then honor whatever they pick.

**Mascot / character-driven repos** (pixel pet, animal, robot, bespoke creature) are a special case worth flagging at recommendation time:
- Size the mascot as the **visual anchor**: ≥30–35% of the stage's smaller dimension. Smaller, and it gets out-competed by surrounding chrome (terminal windows, taglines, icons) and the hero loses its identity. Real incident: clawd-on-desk landed at 184px-on-500px-tall (~37%) only after two iterations of bumping from an undersized initial 96px.
- Default to **always-present** layout: the mascot sits in its final position from frame 1. Story arc happens via the mascot's own animation/state changes, not via the mascot moving across the frame.
- **Skip cinematic entrances** — jump-in / zoom-in / slide-in / camera-dolly transitions almost always look bad in a small GIF and add no information. If you're tempted to write one, prefer holding the framing and animating the mascot's state instead. The same run had a "pet jumps into terminal + camera zooms" sequence that the user explicitly cut as *"look bad"*; removing it improved the hero.

### 1.5 Scenario proposal

Offer **2–3 concrete scenarios** (two if the viable directions are closely related, three if they diverge meaningfully), each with:

- **Concept** — one line
- **Scene beats** — 3–5 bullets describing what happens second-by-second
- **Vibe anchor** — a visual reference ("think Linear changelog", "think `htop`")
- **Why it suits this repo** — one paragraph grounding it in the scan findings
- **Risk** — one line on what could make this one weaker

Then **Claude's recommendation**: pick one and argue for it forcefully in 1–2 sentences. Frame it as something the user can still redirect.

### 1.6 Convergence signal — "enough info"

Move to the build phase when all six are settled:

- [ ] Chosen scenario (one of the 2–3 proposed, possibly with edits)
- [ ] Vibe locked
- [ ] Hero moment named in one sentence
- [ ] Audience named
- [ ] Hard constraints captured (or confirmed "none")
- [ ] "What NOT to imply" list captured (or confirmed "none") — used by Gate A (§1.8)
- [ ] Output format chosen (animated GIF / static PNG / animated SVG / HTML only)
- [ ] Placement agreed (README / website / social / slide / other)
- [ ] Duration chosen — animated only (typical: 15–25s loop; default 20s); skip for static
- [ ] Dimensions confirmed (see §1.7 — default 1200×675; tailor when the repo spirit calls for it)
- [ ] Real inventory count captured from §1.3 scan (if the repo has a countable collection)
- [ ] Operating mode recorded from §1.1a (Auto / Semi-auto / Manual)
- [ ] Paddings are symmetric — top = bottom, left = right — unless a deliberate asymmetry is named in the brief (§2.4 symmetry rule)
- [ ] Gate A (§1.8) and Gate B (§1.8b) both PASS — repo-fidelity *and* aesthetic-identity + wow have been checked

Write the brief back to the user in a compact block. Wait for **"go"** before writing any HTML — *unless* the mode is **Auto**, in which case proceed directly to Phase 2 with a brief summary line and no wait. In **Semi-auto**, show the brief but proceed after a brief pause unless the user interjects. In **Manual**, wait explicitly for "go".

### 1.7 Dimensions — default with room to tailor

**Default: 1200×675 (16:9).** It's the safe pick when the repo gives no strong signal — GitHub's ~1000px README column renders it at ~560h (comfortable but not dominant), and the skill's capture/eval scripts are calibrated around that width. Start here unless the repo *wants* something else.

**Tailor when the repo's spirit points elsewhere.** Don't force a 16:9 crop onto a project whose identity is clearly another shape. Signals to deviate:
- **Slim banner (e.g. 1200×300 / 1600×400):** one-line wordmark-led brands, docs sites with an existing slim header, terminal-themed repos where the pitch is a single ribbon of text. Banners read as "logo-bar," not "hero scene" — use shorter loops (10–15s) and fewer scenes.
- **Square / portrait (1:1, 4:5):** social-first repos, mobile SDKs, anything whose primary surface is a phone. Don't build these for a README header — they're a separate deliverable.
- **Wider than 16:9 (2:1, 21:9):** data-viz / dashboard / timeline repos whose pitch is lateral scan; spatial tools (maps, CAD, audio waveforms). Only go this wide if the content genuinely fills it — empty wide heroes look lonelier than cramped 16:9 ones.
- **Taller than 16:9 (4:3, 3:2):** repos with dense stacked panels (notebook screenshots, multi-pane IDEs, documentation browsers) where 675h would truncate the story.

**How to decide:** during the scan (§1.3), note the existing hero image's dimensions and the repo's format archetype (from `craft/reference-gallery.md`). If they point toward a non-16:9 shape *and* the brief's hero moment fits that shape, propose the tailored size in the direction-questions batch (§1.4) — show the user the default and the tailored option, cite the reason, let them pick. If nothing argues against 1200×675, don't introduce the choice; just use it.

**Craft rules scale with whatever dimensions are chosen** — the font-size floors, bottom-clearance minimums, and column-density rules later in this document are written around 1200×675 but are expressed as ratios (% of stage height, px relative to canvas width) so they port. When you deviate, recompute: e.g. at 1200×400 the body-text floor is still ~2.5% of stage height × retina headroom, not a fixed 17px.

**The capture pipeline already supports arbitrary dimensions** — `scripts/screenshot.js` and `capture.js` both take `--width` and `--height` flags. Pass whatever the brief locked in; don't hardcode 1200×675 in export commands.

### 1.8 Gate A — brief vs README (repo-context check, pre-render)

Run after the brief block is written, before saying "go" (Auto: silently; Semi-auto: silently, surface only if it triggers a revision; Manual: visible note the user can override).

**Scope of this gate is deliberately narrow: only repo-context fidelity.** A hero can look polished and still say the wrong thing about *this specific repo* — that's what Gate A catches. Aesthetic judgments, AI-design clichés, technical compliance, "is the design generic" — all out of scope. Those belong to taste and craft rules elsewhere in this document, not to a gate that triggers a revision. Keeping the scope narrow is what prevents the gate from drifting into "optimize for pretty" — the failure mode the original Reddit feedback warned about.

**Inputs** (assemble from §1.3 scan + the locked brief):

- README first 40 lines (verbatim).
- Real inventory count and any specific names from §1.3.
- Stated hero moment (one sentence).
- Audience.
- "What NOT to imply" list (§1.4 question 7).

**Critique prompt:**

> You are critiquing a draft creative brief for the repo `<owner/repo>`'s hero visual *before any HTML exists*. Your only job is to check whether the brief faithfully represents this repo's own positioning — not to judge aesthetics, not to suggest design improvements.
>
> **Brief:** `<paste the locked brief>`
>
> **Repo ground truth:** `<README excerpt, inventory count, stated hero moment, audience, "what NOT to imply">`
>
> Answer concretely (cite the brief and the README):
> 1. **Voice match.** Does the brief's on-screen copy use this repo's own README phrasing and terminology, or does it drift into generic marketing voice?
> 2. **Scope honesty.** If the brief implies "all" / "every" / a grid, does it match the real inventory count? If not, is it explicitly framed as a sample?
> 3. **What it implies that it shouldn't.** Walk the "what NOT to imply" list and the README's actual positioning — does the brief accidentally suggest something the repo isn't claiming?
>
> For each, output: `PASS` (with a one-line reason), or `FAIL` (with a one-line reason and a one-line concrete fix). Do not comment on visual style, color, layout, or design quality — those are not what this gate checks.

**What to do with the result:**

- **All PASS** → proceed to Phase 2.
- **Any FAIL** → revise the brief to apply the fixes, re-show the brief block (don't re-run Gate A — one pass is enough). In Manual mode, surface the FAILs to the user and let them decide whether to apply the fix.
- **Auto mode + FAIL** → apply fixes silently, note them in a one-line "tightened brief: [what changed]" before proceeding.

### 1.8b Gate B — aesthetic identity & wow (pre-render)

Run immediately after Gate A passes, on the same locked brief, before saying "go" (Auto: silently; Semi-auto: silently, surface only if it triggers a revision; Manual: visible note the user can override).

**Scope of this gate is deliberately narrow: only aesthetic identity match and wow.** Gate A already verifies that the brief says the right *facts* about the repo. Gate B verifies that the brief picks the right *visual language* for the repo and that the composition has a reason to exist. Anything past those two questions — pixel-level polish, type kerning, exact palette values — belongs to render-time critique (§3.0a / §4.4a), not here.

The two failure modes Gate B catches:

1. **Author's-personal-aesthetic ≠ product's-identity.** A repo by a designer has *two* visual languages around it — the maintainer's personal blog/portfolio aesthetic, and the *product*'s own identity. Heroes belong to the product. Defaulting to the maintainer's personal palette is a real, recurring failure.
   *Example incident:* a hero for `kepano/obsidian-skills` was first drafted in Flexoki cream — kepano's *personal blog* palette. The product is **Obsidian**, whose iconic visual is the dark-purple graph view. The cream draft drew "no wow, doesn't feel like the repo." Fix was to switch to dark + graph-view aesthetic.

2. **No wow anchor.** A brief like "tasteful grid of cards in a muted palette" is generic — there is no specific compositional move that would make a viewer stop scrolling. The brief needs to name *one* concrete visual move that anchors the hero (the product's iconic visual, an unexpected layout, a vivid contrast, a recognizable metaphor).

**Inputs** (assemble from §1.3 scan + the locked brief):

- Author identity *vs* product identity. Are they the same? (e.g., a solo dev's CLI = same; a designer's library for someone else's product = different — use the *product*'s identity.)
- Existing visuals in `assets/` / app screenshots / docs.site — what visual language does the *product* itself use?
- Stated vibe and palette in the brief.
- Stated hero moment and chosen scenario.

**Critique prompt:**

> You are critiquing a draft creative brief for the repo `<owner/repo>`'s hero visual *before any HTML exists*. Your job is to check two things only: aesthetic identity match, and wow anchor. Do not re-check facts, scope, or repo-fidelity — Gate A already did that.
>
> **Brief:** `<paste the locked brief>`
>
> **Identity context:**
> - Repo author / maintainer: `<name>`
> - Author's personal aesthetic (if known from scan — blog, portfolio, prior work): `<one line>`
> - The *product* this repo is for or about: `<name>`
> - Product's iconic visual identity (from app screenshots, docs site, brand): `<one line>`
> - Existing in-repo visuals: `<list or "none">`
>
> Answer concretely:
> 1. **Identity match.** Does the brief's chosen vibe/palette/composition match *the product's* visual identity, or has it drifted into the author's personal aesthetic (or generic AI-design defaults)? If the author and product are the same entity, this question collapses to "does the brief match the maintainer's identity."
> 2. **Wow anchor.** Name the *one specific compositional move* in the brief that would make a viewer stop scrolling. If you can't name one in a single sentence, the brief is generic — flag it.
>
> For each, output: `PASS` (with a one-line reason), or `FAIL` (with a one-line reason and a one-line concrete fix — e.g., "swap Flexoki cream for Obsidian's dark + purple graph-view aesthetic," or "anchor on the product's signature graph visualization on the right half of the canvas").

**What to do with the result:**

- **Both PASS** → proceed to Phase 2.
- **Any FAIL** → revise the brief, re-show the brief block (don't re-run Gate B — one pass is enough). In Manual mode, surface the FAILs to the user and let them decide whether to apply the fix. In Auto/Semi-auto, apply fixes silently with a one-line note ("tightened brief: switched palette from Flexoki cream to Obsidian dark-purple to match product identity").

---

## Phase 2 — Build

Once the brief is locked and the user says "go", write the HTML in **one file** (`index.html` in a working directory for this repo's hero).

### 2.1 Working directory

Default layout — always nest per-repo under a subdirectory named after the target repo (e.g. `last30days-skill`, `my-cli`):

```
<current-dir>/repo-visuals-work/<repo-name>/
  index.html        # the hero animation source (GIF / PNG / HTML formats)
  index.svg         # the hero source when format is animated SVG (single file, self-contained)
  assets/           # any images/SVGs the scenario needs
```

For **animated SVG**, author `index.svg` directly as a single self-contained file — `<style>` block with CSS `@keyframes` (or SMIL `<animate>` elements) inside the SVG, all fonts embedded as `@font-face` data URIs (Google Fonts loads at render time and won't survive GitHub's `<img>` sandbox), no external assets, no `<script>`. Open the file directly in a browser to preview — the same animation that plays locally will play when GitHub embeds it via `<img>`.

Use `repo-visuals-work/<repo-name>/` so the scratch files stay obviously separate from the target repo **and** runs for different repos don't clobber each other. Never write directly into `repo-visuals-work/` — always into the repo-named subfolder. If no obvious repo name, use a short slug derived from the brief.

### 2.2 Re-read the README before writing

Open the target repo's README once more right before writing HTML. Mirror its actual phrasing, headings, and technical terms in the animation's on-screen text. The hero should feel like it was written *by* the repo author, not *for* them.

### 2.2a Consult the craft library

Before writing any scene copy or layout, read:

- `craft/headlines.md` — headline patterns (imperative-plus-invariant, narrative arc), voice rules, anti-patterns.
- `craft/templates/*.html` — full working heroes from past runs, **all upstream-merged** (the maintainers of the target repos accepted them). Read end-to-end to see how a complete scene system is composed (stage + browser chrome + tool-body + rotating hero text + progress indicator + timeline scheduler). Don't copy verbatim — steal patterns. Match by archetype, not by preference ranking.

Every scene needs a headline. A demo without copy delivers no meaning.

### 2.3 Structure of `index.html`

Single self-contained file. No build step. Sections:

1. **Stage** — a fixed container sized to the chosen dimensions (§1.7). Everything absolutely positioned inside. Outside the stage: a subtle page background so the capture crop is unambiguous.
2. **Fonts & tokens** — import fonts at the top (Google Fonts `@import`), define CSS custom properties for the palette (`--bg`, `--accent`, `--text`, `--muted`, etc.) derived from the vibe/constraints.
3. **Scene DOM** — the HTML elements that each scene uses. Build all of them upfront; scenes show/hide via classes.
4. **Timeline object** — a JS `TIMELINE` with named keys (e.g. `scene2Start: 3500`) so pacing is tweakable in one spot. Include `loopPauseAt` so the animation has a clean loop boundary.
5. **Timer helpers** — `schedule(t, fn)` wrapping `setTimeout` into an `animTimers` array, plus a `runLoop()` that clears and restarts. This is what the export pipeline calls to reset to t=0 deterministically.
6. **Scenes** — each scene is a plain function that flips classes / starts tweens. Keep CSS transitions for simple motion; use small rAF-driven tweens for counters or typing effects.

### 2.4 Rules of thumb

- **Durations**: match the chosen loop length (default 20s). Each scene ~3–6s, never so fast the viewer can't read it. Shorter dimensions (banner / slim) naturally want shorter loops — 10–15s.
- **Motion**: use `transform` and `opacity` only — never `top/left/width/height` (janky, expensive).
- **Type**: scale the minimum body size to stage height — ~2.5% of stage height, bumped up for GIF legibility. At 675h that's ~17px; at 300h that's ~14px minimum. Never below 13px.
- **Palette**: pick 4–6 colors. More kills GIF compression *and* looks busy.
- **Loop seam**: the last frame should visually match the first (or transition back smoothly). `runLoop()` is the hard reset if needed.
- **Determinism**: no `Math.random()` without a seed, no `Date.now()`-based logic. Everything must replay identically each capture.
- **Avoid the NY Times / editorial-newspaper theme**: no serif-headline + rule-lines + dateline + column-grid pastiche. It's become a default AI-design shortcut and reads as generic. Pick a visual language that actually suits the repo's domain (terminal, data-viz, spatial, playful, brutalist, neon, etc.) — not a broadsheet aesthetic unless the repo is explicitly about journalism or publishing.
- **No drift-prone claims in on-screen copy.** No version numbers, star counts, download counts, contributor counts, language-floor versions, or release dates as exact values — they go stale fast. Use `<next lower round number>+` ("40+ rules", "6k+ stars") when a number helps the pitch. Full banned/allowed list and soft-cap reasoning in `craft/rules.md`.
- **NEVER invent a logo.** The logo is core brand identity — the maintainer's, not yours to design. The §1.3 scan must look for the real one. Behavior by mode:
  - **Auto mode** → if §1.3 found a logo, **use it.** If §1.3 found nothing, render the hero **wordmark-only** — no mark at all. Do not generate a substitute. Note in the run summary whether a logo was found and used, or skipped.
  - **Semi-auto and Manual modes** → if §1.3 found a logo, use it (you may still confirm with the user that it's the right one before locking the brief). If nothing was found, **STOP and ask the user** before drawing anything. Offer three options: (a) point me to a logo file or URL, (b) proceed wordmark-only, (c) you explicitly want me to create a placeholder mark — and only if (c), describe it first and get confirmation before rendering.
  - A geometric "V"/"X"/initial-letter shape, a gradient pill with the first character, a generic database/cube/lightning glyph — **all of these count as inventing a logo.** Past failure (VeloxDB run): shipped a custom purple "V lightning" mark for a repo whose real logo (a stacked database disc with speed lines) was sitting at `<homepage>/logo-dark.svg` the whole time. Required a follow-up commit and PR push. The fetch step in §1.3 exists to prevent exactly this.
- **No fake app UI.** If the repo's product has a visible runtime UI (CLI/TUI/IDE/desktop app) and the hero contains *any* depiction of it (terminal panel, app window chrome, status bars, banners), every visible UI string and shape must come from the real app — its actual banner, prompt format, status phrases, footer chrome, command output. Invented `npm run typecheck`-style lines, generic `$` shell prompts, made-up status indicators all read as a hollow simulation to anyone who actually uses the tool. Pull real strings from screenshots, repo source (status constants, hook output, banner generators), or — best of all — a screen recording the user provides. If you can't access the real vocabulary, *flag the gap and ask* before drafting; don't invent. This rule cost two prior runs (Terminal.Gui simulated UI, clawd-on-desk v1–v2 fake terminal) and the third (clawd-on-desk v3+) only became authentic after extracting frames from a user-supplied `.mov`. See also §1.3's screen-recording prompt.
- **README-voice over slogan-voice in hero copy.** Default rotating headlines, captions, and overlay text to README-style status-readout shape (colored pip + bold one-word state label + factual phrase, optionally a dim subtitle of concrete context) — not punchy two-line marketing slogans. Anti-patterns: imperative-then-twist ("Walk away. Come back."), "So you don't have to" / "So it just works" patterns, hero-text title pairs in big bold + accent-colored phrase. The user calls these "silly". Status-readout shape (`● thinking · reading your codebase`, `● juggling · 3 subagents in parallel`) feels like the repo's own activity log. See `craft/headlines.md`.
- **Layout discipline (split-stage, anchors, scoping).** The bullets that bite during build:
  - Don't pin a left-column's bottom row flush to the edge — center the column or leave ≥32 px breathing room.
  - Don't let one column run empty while the other fills — match density with secondary content right under the title block, not bottom-pinned 200 px below.
  - Bottom-anchored footer elements need ≥40 px clearance and ≥32 px gap above. If the math doesn't work, shrink stage height — don't just nudge.
  - Scope generic class selectors to a region (`.form .row`, not bare `.row`) so they don't leak into the rest of the stage.
  - On-screen labels must read cold — no cryptic abbreviations a first-time viewer can't decode in two seconds.
  - Symmetry is load-bearing — top-pad = bottom-pad, left-margin = right-margin. When shrinking a right-anchored element, preserve its left-edge position, not its right margin.
  - Full incident write-ups for each in `craft/rules.md`.
- **Preview-vs-export parity.** The HTML the user previews must equal the exported pixel grid: pin the stage at `(0, 0)`, treat `box-shadow` as preview-only, never let content exceed the stage, always pass an explicit `clip` to `page.screenshot()`, and visually diff the export against the HTML before declaring done. Detail in `craft/rules.md`.
- **Retina quality is mandatory.** Capture at 2× always (PNG via `deviceScaleFactor: 2`; GIF via the zoom-fallback in `craft/export.md`). Source font floor on a 1200-wide canvas is ~15.5 px body / ~12.5 px metadata after GitHub's ~0.83× downscale. The own benchmark is "looks like a native MacBook retina screenshot" sitting next to other chrome on the README.
- **Embedding a user- or reviewer-provided asset has its own rulebook.** Loop must equal or cleanly divide the outer loop; keep inner at native resolution; budget palette by keeping outer chrome low-chroma; never silently replace a reviewer's asset. Full version in `craft/rules.md`.

### 2.5 When to stop writing and preview

First preview as soon as:

- All scenes are implemented (even if polish is rough)
- Timing hits the full duration target
- No console errors

Don't polish before first preview. User's reaction on overall shape is more valuable than Claude's local polish loop.

### 2.6 Gate B — rendered HTML vs README (repo-context check, pre-export)

Once `index.html` is ready and §2.5's stop conditions are met, run a **vision-based check on the rendered HTML before any GIF/PNG export.** Same narrow scope as Gate A — only repo-context fidelity (does what's on screen match what the README says about this repo?). Aesthetic, technical, and craft compliance are explicitly out of scope; those are caught by taste and the dev-mode scorecard, not by an iteration-triggering gate.

This catches the brief→pixels gap: the right brief can still produce text that drifted from README phrasing, a scene that undercounts the repo's real inventory, or copy that quietly implies something the "what NOT to imply" list ruled out.

**Why on HTML, not the exported artifact:** an export cycle is expensive (GIF: capture + ffmpeg ~30s+, PNG: Puppeteer ~3s but still wasted if the critique fails). Running the check on Puppeteer screenshots of the HTML keeps it in the same cheap loop as build, so an iteration is "edit HTML, re-screenshot, re-critique" — no encoder in the inner loop.

**Mode interaction:**

| Mode | Gate B behavior |
|---|---|
| **Auto** | Run silently. If FAIL, apply 1 auto-iteration (edit HTML, re-screenshot, re-critique). After 1 retry, proceed regardless and note remaining issues in the hand-off. |
| **Semi-auto** | Run once silently before showing the artifact. If FAIL, apply 1 auto-iteration before the human preview. Surface a one-line "Gate B caught and fixed: [X]" so the user knows. |
| **Manual** | Skip — the human is the critic. Optional: offer to run it as a "second opinion" if the user asks. |

**Capture step** (no ffmpeg):

- Animated: call `scripts/screenshot.js` 4–5 times at evenly-spaced `--seek` values across the loop duration (the script uses `window.seekTo(t)` if exposed by the HTML; otherwise design the HTML to expose it — see §2.3 timeline helpers). Output to `repo-visuals-work/<repo>/critique-frames/`.
- Static: a single `scripts/screenshot.js` call at `deviceScaleFactor: 2`.

**Critique prompt** — narrowed version of §6.3, repo-context only:

> You are checking the rendered hero for `<owner/repo>` against the repo's own positioning. Only judge whether what's on screen matches the README — not whether it looks good, not whether the design is original, not whether type is well-set. Aesthetic an

…(truncated)
