[object Object]

← back to Contact Mailer Ideas

productized-skills-20260511 batch: 20 ideas (19/20 green) — each takes a Steve skill and turns it into a SaaS. Top: MockMaker/SiteScore/PitchVideo at 35/40

35d4a2c5bcab7f8e98ee6bf16cb67c92eaa4bf78 · 2026-05-11 16:49:47 -0700 · Steve Abrams

Files touched

Diff

commit 35d4a2c5bcab7f8e98ee6bf16cb67c92eaa4bf78
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon May 11 16:49:47 2026 -0700

    productized-skills-20260511 batch: 20 ideas (19/20 green) — each takes a Steve skill and turns it into a SaaS. Top: MockMaker/SiteScore/PitchVideo at 35/40
---
 batches/2026-05-11-productized-skills.json | 231 +++++++++++++++++++++++++++++
 build-pitch-tasks.py                       |  86 +++++++++++
 render-all-pitches.sh                      |  48 ++++++
 render-pitch-video.sh                      |  58 ++++++++
 render-slide.py                            |  71 +++++++++
 5 files changed, 494 insertions(+)

diff --git a/batches/2026-05-11-productized-skills.json b/batches/2026-05-11-productized-skills.json
new file mode 100644
index 0000000..93e3356
--- /dev/null
+++ b/batches/2026-05-11-productized-skills.json
@@ -0,0 +1,231 @@
+{
+  "batch": "productized-skills-20260511",
+  "batch_date": "2026-05-11",
+  "vertical": "Productized skills — each idea takes one of Steve's existing working skills and turns it into a standalone SaaS product",
+  "authority": "ideate-only",
+  "generated_at": "2026-05-11T17:30:00-07:00",
+  "generator": "claude direct (skill-by-skill productization analysis)",
+  "rationale": "Every idea here is grounded in a skill Steve has ALREADY built and tested. Feasibility floor is near-max because the engineering is done — productization is wrapping it as a hosted service + dashboard. The novelty + impact axes vary; some skills are venture-scale categories, others are clean indie SaaS plays.",
+  "ideas": [
+    {
+      "idx": 1, "name": "MockMaker",
+      "headline": "Ten homepages, every one different. In ten minutes.",
+      "angle": "Productize the mockups skill — N hand-built front-page concept variants per URL",
+      "target": "Agencies pitching redesigns + indie founders A/B testing landing pages",
+      "diff": "Each variant is a distinct aesthetic direction (debt-clock / story-wall / magazine / brutalist / bilingual) — not template reskins. The mockups skill already produces 10 working concepts; Productize = enter URL + count + theme → hosted gallery.",
+      "source_skill": "mockups",
+      "scores": { "novelty": 8, "fit": 9, "feas": 10, "impact": 8 },
+      "score_total": 35,
+      "monetization": "$49 per mockup pack (one-time) · $499/mo agency tier (unlimited)"
+    },
+    {
+      "idx": 2, "name": "SiteScore",
+      "headline": "Audit any website. Letter grades for every component.",
+      "angle": "Productize site-audit — synthesis-grade report with SEO + a11y + dead-link scan + component grades + 90-day roadmap",
+      "target": "Marketing directors + agency biz-dev + investors doing due diligence",
+      "diff": "Existing tools (Lighthouse, Ahrefs) score axes; site-audit synthesizes into letter-grade per component + ranked improvement roadmap. Reference impl already live at sdccanalysis.agentabrams.com.",
+      "source_skill": "site-audit",
+      "scores": { "novelty": 7, "fit": 9, "feas": 10, "impact": 9 },
+      "score_total": 35,
+      "monetization": "$29 per audit (one-time) · $199/mo unlimited audits for agencies"
+    },
+    {
+      "idx": 3, "name": "PeerSpy",
+      "headline": "Screenshot your competitors. Side by side. In one click.",
+      "angle": "Productize peer-survey — N comparable peers screencap'd with one UX move worth borrowing per peer",
+      "target": "Product managers + founders + agencies pitching against incumbents",
+      "diff": "Crunchbase-style competitor lookup + Playwright screencaps + Claude-graded 'one UX move to borrow' annotation. Reference impl: sdccanalysis.agentabrams.com/peers.html.",
+      "source_skill": "peer-survey",
+      "scores": { "novelty": 8, "fit": 8, "feas": 9, "impact": 7 },
+      "score_total": 32,
+      "monetization": "$19 per peer-survey · $99/mo unlimited"
+    },
+    {
+      "idx": 4, "name": "TrustedSources",
+      "headline": "A curated hub of authoritative sources for any vertical.",
+      "angle": "Productize info-hub — curated directory of N trusted external sources per vertical with MFA + retention + mobile-friendliness scoring",
+      "target": "Nonprofits + journalists + advocacy orgs needing 'where to look' references",
+      "diff": "Most directories are link lists; info-hub adds source-quality metadata (MFA, retention policy, mobile-friendly, sub-category). Reference: sdccanalysis.agentabrams.com/hub.html (30+ federal student-loan sources).",
+      "source_skill": "info-hub",
+      "scores": { "novelty": 8, "fit": 8, "feas": 9, "impact": 7 },
+      "score_total": 32,
+      "monetization": "$99 per hub (one-time setup) · $19/mo per hub for monthly refresh"
+    },
+    {
+      "idx": 5, "name": "WidgetPack",
+      "headline": "Six working calculators for your site. Live JavaScript.",
+      "angle": "Productize tools-pack — generate domain-specific browser-side widgets/calculators (all client-side, real working code)",
+      "target": "Industry sites needing engagement tools (mortgage calculators, ROI estimators, etc)",
+      "diff": "Real working JS — not mockups. tools-pack already shipped 6 SDCC borrower widgets at sdccanalysis.agentabrams.com/tools.html.",
+      "source_skill": "tools-pack",
+      "scores": { "novelty": 8, "fit": 8, "feas": 9, "impact": 7 },
+      "score_total": 32,
+      "monetization": "$199 per widget pack (one-time) · enterprise white-label $2k+"
+    },
+    {
+      "idx": 6, "name": "PrivacyExt",
+      "headline": "Privacy-first Chrome extension for any site. Scaffolded in 5 minutes.",
+      "angle": "Productize extension — MV3 Chrome extension scaffolder with 416-line legal/technical memo per scaffold",
+      "target": "Devs + agencies building privacy-respecting browser tools",
+      "diff": "Most boilerplates skip the privacy memo; extension generates data-flow + host permissions + retention policy + audit log alongside the code. Reference: sdccanalysis.agentabrams.com/extension.html.",
+      "source_skill": "extension",
+      "scores": { "novelty": 9, "fit": 7, "feas": 9, "impact": 7 },
+      "score_total": 32,
+      "monetization": "$99 per scaffold · $999/yr unlimited"
+    },
+    {
+      "idx": 7, "name": "FourHands",
+      "headline": "Your design brief, run through four design tools at once.",
+      "angle": "Productize four-horsemen — orchestrate Figma + 21st.dev + Paper.design + Canva in parallel for any UI brief",
+      "target": "Design agencies + product teams iterating on UI concepts",
+      "diff": "Tools each have an MCP; four-horsemen wires them into a single brief → 4 outputs flow. Currently running at 45.61.58.125:9801.",
+      "source_skill": "four-horsemen",
+      "scores": { "novelty": 9, "fit": 7, "feas": 8, "impact": 8 },
+      "score_total": 32,
+      "monetization": "$49 per brief run · $999/mo agency tier"
+    },
+    {
+      "idx": 8, "name": "AdSpy",
+      "headline": "Where does this domain advertise? Pixel-by-pixel.",
+      "angle": "Productize advertising-signals — input domain → detect Google Ads, Meta, TikTok, LinkedIn, etc. pixels + social profiles + Ads Transparency Center counts",
+      "target": "Competitive intelligence + marketing teams + investors",
+      "diff": "No paid APIs needed — pure pixel fingerprint analysis from public HTML. Other tools (SimilarWeb, SEMrush) cost $$$$ and don't go this deep on pixel-level detection.",
+      "source_skill": "advertising-signals",
+      "scores": { "novelty": 8, "fit": 8, "feas": 9, "impact": 8 },
+      "score_total": 33,
+      "monetization": "$9 per scan · $99/mo for 100 scans + watchlist alerts"
+    },
+    {
+      "idx": 9, "name": "DirSpend",
+      "headline": "Which businesses in this directory pay for ads?",
+      "angle": "Productize ad-social-tracker — scan an entire business directory, slice by ad-platform spend",
+      "target": "Lead-gen agencies + ad-platform sales reps + biz-dev teams",
+      "diff": "Existing competitive intel tools work per-domain; DirSpend works per-DIRECTORY (1,000+ businesses scanned in one go). Powered by Steve's existing tracker that works on lawyer/doctor/animals/lacountyeats/professional-directory.",
+      "source_skill": "ad-social-tracker",
+      "scores": { "novelty": 9, "fit": 7, "feas": 9, "impact": 8 },
+      "score_total": 33,
+      "monetization": "$499 per directory scan · $1,999/mo unlimited"
+    },
+    {
+      "idx": 10, "name": "LARecords",
+      "headline": "LA public records as a structured API.",
+      "angle": "Productize la-research-agent — assessor / recorder / LADBS / ZIMAS / courts / CPRA as one queryable API",
+      "target": "Real-estate professionals + journalists + legal teams + civic tech",
+      "diff": "LA records are fragmented across 50+ city + county sites. la-research-agent already aggregates them with verified dataset IDs. Hosted API = $$$.",
+      "source_skill": "la-research-agent",
+      "scores": { "novelty": 9, "fit": 8, "feas": 7, "impact": 9 },
+      "score_total": 33,
+      "monetization": "$99/mo per seat · $999/mo for 50-seat agency · pay-per-query API tier"
+    },
+    {
+      "idx": 11, "name": "AutoGA",
+      "headline": "GA4 property setup + injection across your entire fleet in one click.",
+      "angle": "Productize analytics skill — create GA4 accounts/properties/data streams/conversions, inject gtag across sites, run Data API reports",
+      "target": "Agencies managing 50+ client sites + multi-site SMB operators",
+      "diff": "GA4 setup is hated busywork; analytics skill automates the entire stack. Already running across Steve's site fleet.",
+      "source_skill": "analytics",
+      "scores": { "novelty": 8, "fit": 8, "feas": 9, "impact": 8 },
+      "score_total": 33,
+      "monetization": "$29/mo per site · $999/mo agency unlimited"
+    },
+    {
+      "idx": 12, "name": "DomainPilot",
+      "headline": "Every registrar in one dashboard. GoDaddy, CF, Namecheap, Porkbun.",
+      "angle": "Productize domain-name-agent + domain-suite — unified DNS / cert / WHOIS / email-auth across registrars",
+      "target": "Anyone managing 10+ domains across multiple registrars",
+      "diff": "Each registrar has a separate dashboard; DomainPilot is the cross-registrar dashboard most-needed-but-doesn't-exist. Steve's domain-suite MCP already handles all four registrars.",
+      "source_skill": "domain-name-agent",
+      "scores": { "novelty": 8, "fit": 9, "feas": 8, "impact": 8 },
+      "score_total": 33,
+      "monetization": "$19/mo for 10 domains · $99/mo for 100 · enterprise $999/mo"
+    },
+    {
+      "idx": 13, "name": "AutoFeature",
+      "headline": "Describe a feature. Wake up to it shipped.",
+      "angle": "Productize ralph — autonomous feature development as a service for any GitHub repo",
+      "target": "Solo devs + small teams who need feature velocity without hiring",
+      "diff": "Existing autonomous coding tools (Devin, Cursor) require constant nudging; ralph self-corrects via debate panels + ships overnight.",
+      "source_skill": "ralph",
+      "scores": { "novelty": 8, "fit": 7, "feas": 7, "impact": 9 },
+      "score_total": 31,
+      "monetization": "$99 per shipped feature · $1,999/mo unlimited (with codex/claude API passthrough)"
+    },
+    {
+      "idx": 14, "name": "VoiceVault",
+      "headline": "Clone your voice once. Use it everywhere.",
+      "angle": "Productize clone-voice — ElevenLabs + F5-TTS + macOS Personal Voice cross-platform voice manager",
+      "target": "Podcasters + course creators + brand spokespeople",
+      "diff": "Each voice-cloning vendor wants you to live in their walled garden; VoiceVault is the cross-vendor wrapper that exports clones to every TTS platform.",
+      "source_skill": "clone-voice",
+      "scores": { "novelty": 8, "fit": 7, "feas": 8, "impact": 7 },
+      "score_total": 30,
+      "monetization": "$99 one-time clone · $19/mo for ongoing TTS API access"
+    },
+    {
+      "idx": 15, "name": "ReelMaker",
+      "headline": "Product images. Polished reels. Five platforms. One click.",
+      "angle": "Productize reels-producer — generate IG/TikTok/LinkedIn/YouTube Shorts/GIFs from product images with transitions + captions + upload",
+      "target": "DTC brands + Etsy shops + design retailers",
+      "diff": "Most reel tools require you to manually upload to each platform; ReelMaker handles distribution. Captions auto-generated per platform tone (LinkedIn formal vs TikTok casual).",
+      "source_skill": "reels-producer",
+      "scores": { "novelty": 7, "fit": 8, "feas": 8, "impact": 7 },
+      "score_total": 30,
+      "monetization": "$49/mo for 30 reels · $199/mo for 300"
+    },
+    {
+      "idx": 16, "name": "RoomCanvas",
+      "headline": "Drop any wallpaper into any room. Photorealistic.",
+      "angle": "Productize room-setting-generator — bedroom / living / dining / office / bathroom visualizations at varied viewing distances",
+      "target": "Wallpaper / wallcovering / paint / fabric brands without a room visualizer",
+      "diff": "Most brands hire 3D artists $5k+ per scene; RoomCanvas generates photorealistic scenes for $2/render. Steve's room-setting-generator already handles 5 room types.",
+      "source_skill": "room-setting-generator",
+      "scores": { "novelty": 8, "fit": 8, "feas": 9, "impact": 8 },
+      "score_total": 33,
+      "monetization": "$2/render · $499/mo for 500 renders"
+    },
+    {
+      "idx": 17, "name": "PosterPress",
+      "headline": "Branded posters for any event, any topic. AI-designed.",
+      "angle": "Productize canvas-design — beautiful PNG/PDF posters using design philosophy (no copyright issues)",
+      "target": "Event planners + community organizers + small brands needing print marketing",
+      "diff": "Canva makes you start from a template; PosterPress generates original layouts from a brief. Avoids copyright pitfalls of using existing artists' work.",
+      "source_skill": "canvas-design",
+      "scores": { "novelty": 7, "fit": 7, "feas": 9, "impact": 7 },
+      "score_total": 30,
+      "monetization": "$9 per poster · $49/mo unlimited"
+    },
+    {
+      "idx": 18, "name": "TaskFanout",
+      "headline": "Your plan, fanned out to N parallel Claudes.",
+      "angle": "Productize abramstasks — take any numbered plan, spawn 1 iTerm tab per task with Claude pre-loaded",
+      "target": "Power Claude Code users + ML researchers running parallel experiments",
+      "diff": "Claude Code is single-threaded by default; abramstasks fans out plans across N tabs. Niche but loved by power users.",
+      "source_skill": "abramstasks",
+      "scores": { "novelty": 8, "fit": 6, "feas": 9, "impact": 6 },
+      "score_total": 29,
+      "monetization": "$29/mo individual · $99/mo team (with shared task tracking)"
+    },
+    {
+      "idx": 19, "name": "BriefRunner",
+      "headline": "One brief. Eleven AI stages. Reviewed output.",
+      "angle": "Productize visual-factory + ai-factory — multi-stage AI pipelines (brief → qwen3 → Playwright → llava → Claude critic)",
+      "target": "Marketing / content teams needing repeatable AI pipelines without DIY orchestration",
+      "diff": "Most AI tools are single-shot; BriefRunner is a multi-stage pipeline w/ self-critic at each stage. Already running as visual-factory + ai-factory.",
+      "source_skill": "visual-factory",
+      "scores": { "novelty": 8, "fit": 7, "feas": 8, "impact": 8 },
+      "score_total": 31,
+      "monetization": "$99 per pipeline run · $999/mo unlimited"
+    },
+    {
+      "idx": 20, "name": "PitchVideo",
+      "headline": "Any idea. 30-second VC pitch video. ElevenLabs voice.",
+      "angle": "Productize THE PIPELINE WE JUST BUILT — turn any text input (idea name + headline + scalability bullet) into a 30-sec slick MP4",
+      "target": "Founders + agencies + accelerator cohorts needing rapid pitch videos at scale",
+      "diff": "Most VC-pitch-video tools are template-bound; PitchVideo accepts any input + uses ElevenLabs Adam voice + ffmpeg-rendered slide. We just generated 88 in 8 minutes for $2 of API cost.",
+      "source_skill": "(this conversation's pipeline)",
+      "scores": { "novelty": 9, "fit": 8, "feas": 10, "impact": 8 },
+      "score_total": 35,
+      "monetization": "$5 per video · $49/mo for 30 videos · $499/mo for 500 (perfect for accelerator demo days)"
+    }
+  ]
+}
diff --git a/build-pitch-tasks.py b/build-pitch-tasks.py
new file mode 100755
index 0000000..0ef4273
--- /dev/null
+++ b/build-pitch-tasks.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python3
+"""Build TSV of pitch-render tasks across all batches.
+Output cols: batch \t idx \t name \t score \t headline \t script
+"""
+import json, os, glob
+from collections import defaultdict
+
+JSONL = os.path.expanduser("~/.claude/skills/idea-loop/data/ideas.jsonl")
+BATCHES_DIR = os.path.expanduser("~/Projects/contact-mailer-ideas/batches")
+
+ideas_by_batch = {}
+for f in sorted(glob.glob(os.path.join(BATCHES_DIR, "*.json"))):
+    d = json.load(open(f))
+    ideas_by_batch[d["batch"]] = d
+
+latest_scored = {}
+latest_marketing = {}
+for line in open(JSONL):
+    try:
+        t = json.loads(line)
+    except Exception:
+        continue
+    key = (t.get("batch"), t.get("idx"))
+    if t.get("scores"):
+        if key not in latest_scored or t.get("tick", 0) > latest_scored[key].get("tick", 0):
+            latest_scored[key] = t
+    if t.get("marketing"):
+        if key not in latest_marketing or t.get("tick", 0) > latest_marketing[key].get("tick", 0):
+            latest_marketing[key] = t
+
+def clean(s):
+    return (s or "").replace("\t", " ").replace("\n", " ").replace("\r", " ").strip()
+
+for batch_slug, doc in ideas_by_batch.items():
+    for idea in doc.get("ideas", []):
+        idx = idea["idx"]
+        name = idea["name"]
+        st = latest_scored.get((batch_slug, idx))
+        mkt = latest_marketing.get((batch_slug, idx))
+
+        headline = clean(idea.get("headline"))
+        if not headline and st:
+            headline = clean(st.get("new_headline"))
+        if not headline:
+            headline = clean(idea.get("angle", ""))
+
+        score = "—"
+        if st and st.get("scores"):
+            s = st["scores"]
+            score = str(s.get("novelty", 0) + s.get("fit", 0) + s.get("feas", 0) + s.get("impact", 0))
+
+        target = clean(idea.get("target"))
+        hook = ""
+        repeat = ""
+        if mkt and mkt.get("marketing"):
+            hook = clean(mkt["marketing"].get("hook"))
+            repeat = clean(mkt["marketing"].get("repeat"))
+
+        # Build ~70-80 word script
+        parts = []
+        if target:
+            parts.append(f"Here's the market: {target}.")
+        else:
+            parts.append("This is an underserved market.")
+        if hook:
+            h = hook.rstrip(".") + "."
+            # Strip surrounding quotes
+            h = h.strip("'").strip('"')
+            parts.append(h)
+        else:
+            parts.append(f"{name} is the answer.")
+        if repeat:
+            r = repeat[:240]
+            if len(repeat) > 240:
+                r = r.rsplit(" ", 1)[0] + "."
+            parts.append(r)
+        else:
+            parts.append("It scales structurally as the customer base grows.")
+        parts.append(f"Scored {score} out of 40 on the agentabrams opportunity scale.")
+
+        script = " ".join(parts)
+        # Replace any remaining quote chars that might bash-escape badly
+        script = script.replace("'", "").replace('"', "")
+        script = " ".join(script.split())  # collapse whitespace
+
+        print(f"{batch_slug}\t{idx}\t{name}\t{score}\t{headline}\t{script}")
diff --git a/render-all-pitches.sh b/render-all-pitches.sh
new file mode 100755
index 0000000..81cd107
--- /dev/null
+++ b/render-all-pitches.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+# Batch-render 30-sec VC pitch videos for ALL ideas. Concurrency=4.
+set -u
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+TASKS=$(mktemp)
+DONE_FILE=$(mktemp)
+trap "rm -f $TASKS $DONE_FILE" EXIT
+
+python3 "$SCRIPT_DIR/build-pitch-tasks.py" > "$TASKS"
+COUNT=$(wc -l < "$TASKS" | tr -d ' ')
+echo "[Render plan] $COUNT pitch videos, concurrency=4, est ~$((COUNT * 18 / 4 / 60)) min"
+echo
+
+MAX=4
+# Read all tasks into array (bash 3.2 compatible, no mapfile)
+LINES=()
+while IFS= read -r line; do
+  LINES+=("$line")
+done < "$TASKS"
+
+i=0
+for LINE in "${LINES[@]}"; do
+  IFS=$'\t' read -r BATCH IDX NAME SCORE HEADLINE SCRIPT <<< "$LINE"
+  i=$((i+1))
+  # Throttle: wait until job count drops
+  while [ "$(jobs -rp 2>/dev/null | wc -l | tr -d ' ')" -ge "$MAX" ]; do
+    sleep 0.3
+  done
+  (
+    if "$SCRIPT_DIR/render-pitch-video.sh" "$NAME" "$SCORE" "$HEADLINE" "$SCRIPT" > /dev/null 2>&1; then
+      printf "OK\t%s\n" "$NAME" >> "$DONE_FILE"
+    else
+      printf "FAIL\t%s\n" "$NAME" >> "$DONE_FILE"
+    fi
+  ) &
+done
+wait
+
+SUCCESS=$(grep -c '^OK' "$DONE_FILE" 2>/dev/null || echo 0)
+FAIL=$(grep -c '^FAIL' "$DONE_FILE" 2>/dev/null || echo 0)
+TOTAL=$(ls ~/Videos/shipped-apps/Pitch-*-$(date +%Y-%m-%d).mp4 2>/dev/null | wc -l | tr -d ' ')
+echo "[Done] ok=$SUCCESS fail=$FAIL files_on_disk=$TOTAL"
+echo "$(date -Iseconds)  units=$COUNT  workers=$MAX  ok=$SUCCESS  err=$FAIL  task=\"vc-pitch-videos-all-ideas\"" >> ~/.claude/skills/max-it/runs.log
+
+if [ "$FAIL" -gt 0 ]; then
+  echo "Fails:"
+  grep '^FAIL' "$DONE_FILE" | head -10
+fi
diff --git a/render-pitch-video.sh b/render-pitch-video.sh
new file mode 100755
index 0000000..111e60d
--- /dev/null
+++ b/render-pitch-video.sh
@@ -0,0 +1,58 @@
+#!/bin/bash
+# render-pitch-video.sh — generate a 30-sec scalability-focused VC pitch video for ONE idea
+# Usage: render-pitch-video.sh <name> <score> <headline> <pitch_script>
+# Output: ~/Videos/shipped-apps/Pitch-<Name>-2026-05-11.mp4
+set -euo pipefail
+
+NAME="${1:?idea name required}"
+SCORE="${2:?score required}"
+HEADLINE="${3:?headline required}"
+SCRIPT="${4:?pitch script required}"
+
+DATE=$(date +%Y-%m-%d)
+OUT_DIR="$HOME/Videos/shipped-apps"
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+TMP=$(mktemp -d)
+trap "rm -rf $TMP" EXIT
+
+mkdir -p "$OUT_DIR"
+
+SLUG=$(echo "$NAME" | tr -cd '[:alnum:]')
+OUT="$OUT_DIR/Pitch-$SLUG-$DATE.mp4"
+
+# ───── 1. Slide via PIL
+python3 "$SCRIPT_DIR/render-slide.py" "$NAME" "$HEADLINE" "$SCORE" "$TMP/slide.png" > /dev/null
+
+# ───── 2. TTS via ElevenLabs (Adam = stock male voice)
+ELEVENLABS_API_KEY=$(grep '^ELEVENLABS_API_KEY=' ~/Projects/secrets-manager/.env | cut -d= -f2)
+VOICE_ID="pNInz6obpgDQGcFmaJgB"  # Adam
+
+curl -sS -X POST "https://api.elevenlabs.io/v1/text-to-speech/$VOICE_ID" \
+  -H "xi-api-key: $ELEVENLABS_API_KEY" \
+  -H "Content-Type: application/json" \
+  -d "$(jq -n --arg text "$SCRIPT" '{
+    text: $text,
+    model_id: "eleven_turbo_v2_5",
+    voice_settings: {stability: 0.5, similarity_boost: 0.75, style: 0.3, use_speaker_boost: true}
+  }')" -o "$TMP/voice.mp3"
+
+# Check audio actually downloaded (not an error JSON)
+if [ "$(stat -f %z "$TMP/voice.mp3")" -lt 5000 ]; then
+  echo "ERROR: ElevenLabs returned tiny file (likely error). Raw response:" >&2
+  cat "$TMP/voice.mp3" >&2
+  exit 1
+fi
+
+# Pad/trim audio to exactly 30s
+ffmpeg -y -loglevel error -i "$TMP/voice.mp3" -af "apad=whole_dur=30" -t 30 "$TMP/voice30.mp3"
+
+# ───── 3. Combine static PNG slide + audio → 30s MP4
+ffmpeg -y -loglevel error \
+  -loop 1 -framerate 30 -i "$TMP/slide.png" \
+  -i "$TMP/voice30.mp3" \
+  -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 -tune stillimage \
+  -c:a aac -b:a 128k \
+  -shortest -movflags +faststart \
+  "$OUT" 2>&1 | tail -3
+
+echo "OK: $OUT ($(stat -f %z "$OUT") bytes)"
diff --git a/render-slide.py b/render-slide.py
new file mode 100755
index 0000000..907f9bc
--- /dev/null
+++ b/render-slide.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python3
+"""Render a single 1920x1080 PNG slide for a VC pitch video.
+Usage: render-slide.py <name> <headline> <score> <output.png>
+"""
+import sys
+from PIL import Image, ImageDraw, ImageFont
+
+if len(sys.argv) != 5:
+    print("Usage: render-slide.py <name> <headline> <score> <out.png>")
+    sys.exit(1)
+
+name, headline, score, out = sys.argv[1:5]
+
+W, H = 1920, 1080
+img = Image.new("RGB", (W, H), (11, 11, 13))
+d = ImageDraw.Draw(img)
+
+# Fonts (system Helvetica)
+def font(path, size):
+    try:
+        return ImageFont.truetype(path, size)
+    except Exception:
+        return ImageFont.load_default()
+
+NAME_FONT = font("/System/Library/Fonts/Supplemental/Arial Bold.ttf", 160)
+HEADLINE_FONT = font("/System/Library/Fonts/Supplemental/Arial.ttf", 56)
+LABEL_FONT = font("/System/Library/Fonts/Supplemental/Arial Bold.ttf", 28)
+FOOTER_FONT = font("/System/Library/Fonts/Supplemental/Arial.ttf", 26)
+
+def center(text, fnt, y, color):
+    bbox = d.textbbox((0, 0), text, font=fnt)
+    w = bbox[2] - bbox[0]
+    d.text(((W - w) // 2, y), text, font=fnt, fill=color)
+
+# Eyebrow label
+center("VC PITCH · SCALABILITY", LABEL_FONT, 220, (221, 171, 30))  # accent gold
+
+# Name (big)
+center(name, NAME_FONT, 290, (245, 243, 238))  # ink
+
+# Headline (medium gray)
+# Word-wrap headline if too wide
+def wrap(text, fnt, maxw):
+    words = text.split()
+    lines, cur = [], ""
+    for w in words:
+        test = (cur + " " + w).strip()
+        bbox = d.textbbox((0, 0), test, font=fnt)
+        if (bbox[2] - bbox[0]) > maxw and cur:
+            lines.append(cur)
+            cur = w
+        else:
+            cur = test
+    if cur:
+        lines.append(cur)
+    return lines
+
+headline_lines = wrap(headline, HEADLINE_FONT, W - 200)
+y = 510
+for line in headline_lines[:3]:
+    center(line, HEADLINE_FONT, y, (207, 203, 193))
+    y += 72
+
+# Score badge
+center(f"{score}/40 · OPPORTUNITY-GRADE", LABEL_FONT, 760, (34, 197, 94))
+
+# Footer
+center("agentabrams · ideas.agentabrams.com", FOOTER_FONT, 980, (90, 90, 98))
+
+img.save(out, "PNG", optimize=True)
+print(f"OK: {out}")

← b381acf VenturaEats (PULSE #21, 33/40): Ventura-corridor restaurant  ·  back to Contact Mailer Ideas  ·  auto-batch-generator: 2x-weekly Tue+Fri 6am theme-cycler (8 a484cab →