| name | analog-horror-psa |
| namespace | user |
| description | How to make an analog-horror PSA short — the stenciled-pictogram / robo-broadcast / VHS-overlay format (faceless, 10 scenes × ~3s, 9:16, ~30s total). A domain overlay on the standard pipeline that supplies the IF / DO-NOT / BUT / AND scenario structure, the locked yellow-on-black 1970s-civil-defense pictogram visual STYLE, the chroma-key-to-alpha rule (icons must be transparent PNGs before composition), the robo-PSA voice profile (ElevenLabs "Alerter" community voice + ALL CAPS input + stability ~0.5 + style 0), the layered VHS-noise overlay stack (SnowCanvas + VcrTrackingCanvas + MobiusScanlines + MobiusWobble), the 5-layer RGB chromatic-split for icons + captions, the SMPTE colour-bars climax (3 wide vertical bars + filter blur(2.5px) mandatory), and the `-tune grain` CRF 30 final-encode rule for noise-heavy renders. Works for ANY PSA topic — "your fridge is not your fridge", "your phone is not your phone". USE WHEN the user asks for an analog horror PSA / "creepy PSA-style short" / fake-emergency-broadcast video / VHS-aesthetic warning / stenciled-pictogram horror short for TikTok / Reels / Shorts, with no specific existing video to reproduce. This is a niche SKILL (generalized), not a remix TEMPLATE. For "remix this exact PSA but swap the subject", use the remix path in docs/skills-vs-templates.md. |
Trigger
FIRES on a generic analog-horror PSA brief: "make an analog-horror PSA about ", "creepy emergency broadcast short", "VHS-style warning video", "PSA where is actually ", "fake civil-defense alert for ", "your is not your ", "stenciled-pictogram horror short". Any subject — the skill is subject-agnostic.
DO NOT FIRE when:
- The user points at one specific PSA video to reproduce → that is the remix path. See
docs/skills-vs-templates.md.
- The user wants a different horror register (cosmic-horror, found-footage, jumpscare reaction) → match a different niche skill or run freeform via scenarist.
- The user wants a still poster of the same vibe → use
/poster with an analog-horror aesthetic.
What this skill is
A generalized format overlay, not a finished PSA. It does not name a subject, a topic, or a voice line — it tells the pipeline HOW the stenciled-pictogram analog-horror PSA is built so a strong one comes out for whatever the user is warning about. It runs through the normal pipeline (scenarist → art-director → editor); it does not bypass intake gates or the quality gates.
Hard invariants
- All generation routes through
ralphy generate (image / sfx / voiceover) — no raw API. Read MODELS.md before naming any model id.
- Lock the visual STYLE on a single prototype icon BEFORE generating the other nine. The 10 pictograms must be siblings of one approved master. Iterating mid-batch costs $1.00+ on prototype gens (analog-horror-fridge-001 burned $1.00 of v3 → v10 iterations). Pass
--ref <approved-master> on every sibling gen.
- Icons must be chroma-keyed to alpha BEFORE composition. Otherwise the near-black icon bg shows as a visible rectangle on the
#1a1a1a Remotion / HyperFrames underlay. Two-pass ffmpeg colorkey: colorkey=0x000000:0.20:0.08,colorkey=0xFFFFFF:0.20:0.05 — second pass catches any icon that generates on a white outer panel.
- Verify ElevenLabs voice exists before bulk regen. Voice IDs (especially community / "Alerter" library voices) can disappear between sessions.
xh GET /v1/voices/<id> | jq .voice_id as a pre-flight.
- Sequential VO + SFX generation. ElevenLabs 429s on ≥3 concurrent. Always serial.
ralphy queue daemon trips an OpenRouter burst-cap on image gen. Use a sequential bash for-loop for the 10 sibling icons, not --queue.
- Loudnorm AFTER all clips are final, never in the same loop as gen. A failed regen mid-loop double-norms the surviving clips.
-tune grain CRF 30 on the final encode. Default x264 CRF 18 produces 190 MB / 30s renders because canvas-snow is max-entropy noise. ralphy video optimize --crf 30 --tune grain drops it ~12.8× with zero perceived loss.
- Append-only on regen. Re-rolling a voiceover saves the previous take as
<slot>-pre-<change>.mp3. Never overwrite.
The niche, in one paragraph
An analog-horror PSA lives or dies on broadcast-monotone voice + locked stencil pictogram visuals + the layered analog-tape decay overlay. The format mimics a 1970s-80s civil-defense emergency broadcast: flat yellow stenciled icons on pure black, a robotic announcer reading short directive lines, VHS hiss + scanlines + tracking glitches as the bed, and an SMPTE-colour-bars "signal lost" climax. The horror is in the register (cold, official, repeating) more than in the content of any single line.
The fixed 10-scene structure (do not restructure — only fill the slots)
Each scene is ~3 seconds, 9:16 portrait, total ~30s. The IF / DO-NOT / BUT / AND template gives the format its hypnotic cadence:
| Scene | Beat role | VO shape (ALL CAPS) |
|---|
| 1 | Title-card hook | "COMPLIANCE BULLETIN ." (EBS-style red <digit>-D icon) |
| 2 | Authority frame | "THIS IS A RESIDENTIAL BEHAVIORAL ALERT." |
| 3 | IF — observation | "IF YOUR ." |
| 4 | IF — escalation | "IF YOUR ." |
| 5 | DO-NOT — directive | "DO NOT LOOK IN ITS EYES." / "DO NOT ." |
| 6 | BUT — contradiction | "BUT IT WILL ." |
| 7 | AND — confirmation | "AND YOU WILL ." |
| 8 | Specifics | "YOUR IS NOT YOUR ." (the format's signature line) |
| 9 | Direct address | "." |
| 10 | Signal-lost climax | (no VO — SMPTE colour-bars + layered growl SFX) |
The IF / DO-NOT / BUT / AND structure is the format's recognizable cadence; deviating breaks the genre.
The locked pictogram visual STYLE
Every icon is a flat solid yellow #FFD400 shape on a pure black #000000 background, primitive / civil-defense register, no internal texture, no outline. Generate ONE prototype with --ref against the genre reference, get user approval, then batch all 9 siblings with the prototype as --ref.
Prompt scaffold (verbatim — drop in the subject):
Generate a pictogram icon that EXACTLY MATCHES the style of the reference image
(the yellow sitting-dog pictogram on pure black).
REQUIREMENTS:
- PURE BLACK BACKGROUND hex 000000 (perfectly flat solid black, NO texture,
NO grain, NO scanlines, NO noise on the background).
- FLAT SOLID YELLOW SHAPES hex FFD400 (completely uniform yellow fill, NO
internal texture, NO grain, NO gradient, NO highlights — just flat yellow).
- Slightly imperfect / hand-traced outer edges (subtle wobble like a stencil),
NO separate outline color — just flat yellow shapes directly on flat black.
- Style: simplified primitive pictogram, like a 1970s civil-defense PSA icon
or a road-safety warning sign — bold readable shapes, no fine detail,
no anatomical realism.
- Composition: subject occupies about 55 percent of frame, perfectly centered,
large solid-black margin all sides. Vertical 9:16.
- No text, no logos, no watermarks, no noise.
Subject for THIS icon: <SUBJECT SPECIFIC TO SCENE>
Negative: textured background, dark gray background, scanlines on bg, noise on bg, paper-grain, internal texture in shape, grain inside, spray paint inside, dotted fill, halftone fill, gradient fill, highlights inside, shadows inside, dark outline, black border, double outline, pixel art, 8-bit, blocky voxel, smooth perfect vector curves, 3d render, photorealism, depth of field, glow, neon, watermark, logo, signature, fur detail, eyes detail, mouth detail, realistic anatomy.
For scene-01, swap yellow for red #DD2020 to render an EBS-style <digit>-D title-card.
The robo-PSA voice profile
The voice carries the whole horror layer. Defaults that worked on analog-horror-fridge-001:
- Voice: ElevenLabs "Alerter" (community library, user-saved) is the validated winner. Default-library voices (Ava, Marcus) read too human even on a flat prompt. Verify the voice exists in the user's library via
xh GET /v1/voices/<id> BEFORE bulk regen — community voices disappear between sessions.
- Settings:
stability ~0.5, style 0. Strict broadcaster cadence with minimal inflection. The CLI does not yet expose these flags — fall back to the ElevenLabs UI when the keeper take is on the line.
- Input text: ALL CAPS. Forces flatter ElevenLabs delivery. Not a guarantee (the model still adds some emotion) but shifts the prior toward broadcast-monotone.
- Per-scene clips: Sequential generation. ElevenLabs 429s on ≥3 concurrent.
- Loudnorm:
ralphy audio loudnorm --target -16 on each clip AFTER all 10 are final. Never in the same loop as gen.
The VHS-noise overlay stack
Composed in HyperFrames (or Remotion). Four cooperating canvas / wrapper layers:
MobiusWobble — parent wrapper, applies tiny per-frame translate(${x}px, ${y}px) jitter (1px alternating every 2-3 frames) to the entire content tree. Cheap, dramatic, no per-component instrumentation.
SnowCanvas — pure white noise canvas (Uint32Array(255*Math.random()) per pixel per frame) at opacity 0.2 on #aaa background underneath the icon layer.
VcrTrackingCanvas — Mobius1-port algorithm: 20 noise points per frame, each spawns a tail of getRandomInt(1, 50) 2×2 white rects walking ±1-4px horizontally. filter: blur(1px). Mounts as <canvas> with useRef + useEffect([frame]) so it redraws every frame.
MobiusScanlines — repeating-linear-gradient scanline overlay on mixBlendMode: "multiply".
The reusable components belong at src/lib/components/overlays/AnalogTV.tsx (move + document them when the matching templates/entertainment-viral/analog-horror-psa/ scaffold gets extracted).
The 5-layer RGB chromatic split (icons + captions)
What sells "analog horror" in 2026, applied to both icon and caption on the same beat:
| Layer | Shift | Hue rotate | Blur | Opacity |
|---|
| Red ghost | -12px X | -40° | 4 | 1.0 |
| Green ghost | +12px X | +60° | 4 | 1.0 |
| Blue ghost (glitch) | random ±16px every 0.6s | 180° | 4 | 0.15-1.0 flicker (4-frame seed) |
| Wide soft white-halo | 0 | 0 | 22 | 0.55 |
| Sharp core | 0 | 0 | 0.9 | 1.0 |
All on mixBlendMode: "screen" except the sharp core. The blue ghost flickering down to 15% opacity on a 4-frame seed reads as "lost color channel" dropout.
Glitch beat synchronized across icon + caption — both use frame % 18 <= 1 ? 16 : 0 for the +X jump and frame % 18 >= 9 && <= 10 ? -16 : 0 for the −X jump. When the icon jerks, the caption jerks the same way — sells the "this is one analog signal" illusion.
The SMPTE colour-bars climax (scene-10)
Confetti dots look CSS-generated. The reference is real SMPTE bars:
- 3 wide vertical bars (cyan / green / magenta) top 78% + 5 narrower bars (multi-color) bottom 22%.
filter: blur(2.5px) — non-negotiable. Without the blur it reads as clean CSS, not a degraded analog channel.
- Horizontal sync-jitter offset (±9px X, ±4px Y).
- Rolling tracking band sweeping down.
- Chromatic-aberration ghost layer with hue-rotate.
- Scanline
multiply overlay.
- 28 dust specks + 5 white scratch lines.
No VO. Layer 4 climax-growl SFX variants at staggered offsets (1.5s, 1.8s, 1.9s, 2.1s) with volumes 0.55-0.60 — single growl sounds like "chihuahua yipping", layered 4 reads as "monster".
Audio bed levels
Validated on analog-horror-fridge-001:
- VHS-hiss bed: 0.22 (constant under everything)
- Low-drone bed: 0.12 (constant)
- Music bed (synth-tension track, yt-dlp from a YouTube reference): 0.35-0.40 — sits ABOVE the static-noise bed, just under the VO. Initial 0.16 was inaudible; the music has to do its dread-building job, not whisper underneath.
- Per-scene VO: 1.0
- Per-scene static-pop: 0.55
- Per-scene extra SFX: 0.4-0.7
Default model stack (verify against MODELS.md)
- Style-discovery prototype:
openai/gpt-5.4-image-2 with --ref <genre-frame>. Best at "match a reference image exactly" with verbose style prompts. One prototype, one yes/no, iterate ONLY this slot.
- Style-locked batch (after prototype approved):
google/gemini-3-pro-image-preview (nano-banana). With --ref <approved-prototype> + per-icon subject prompt, holds the locked style across all 9 siblings reliably. Cheaper + faster than gpt-5.4 for consistency-across-batch.
- VO: ElevenLabs
eleven_multilingual_v2, voice "Alerter" (or equivalent broadcaster-style community voice). Verify voice exists first.
- SFX: ElevenLabs
sound_generation_v2 via ralphy generate sfx. 4 variants per climax growl, layered.
- Music:
ralphy ref pull <yt-music-url> --audio-only for a synth-tension bed. Generated music (ElevenLabs Music) wasn't tried on the reference project; yt-dlp pull from a user-supplied YouTube reference is the validated 2-minute path.
- Render: HyperFrames composition →
ralphy render <id> → ralphy video optimize --crf 30 --tune grain --preset veryslow (mandatory for noise-heavy comps).
Failure modes
- Iterating prototypes without
--ref → 4-5 wrong styles burned ($0.80-$1.00). Prevention: pass a genre reference frame as --ref from the FIRST prototype.
- Forgetting the chroma-key step → visible rectangle around every icon on the dark Remotion bg. Prevention: two-pass
colorkey=0x000000:0.20:0.08,colorkey=0xFFFFFF:0.20:0.05 to artifacts/images-keyed/ before composition.
- Single-growl climax → reads as "chihuahua yipping". Prevention: 4 variants layered at staggered offsets.
- Confetti-dot climax → CSS-generated, not analog. Prevention: SMPTE bars +
filter: blur(2.5px) (non-negotiable).
- Loudnorm inside the gen loop → double-norms if any regen fails. Prevention: gen all → verify durations → loudnorm pass as a separate step.
- Default Remotion CRF 18 on noise-heavy comp → 190 MB / 30s output (unsharable to social). Prevention:
ralphy video optimize --crf 30 --tune grain post-render.
- Picking too-emotional default-library voice and iterating regens → wasted ~30 calls. Prevention: switch voice (community "Alerter") instead of fighting; fall back to ElevenLabs UI when CLI doesn't expose
stability / style.
ralphy queue for image batches → 9/10 jobs hit 403 Key limit exceeded. Prevention: sequential bash for-loop on the 10 sibling icons.
Workflow
- Intake. Run the standard intake (target language EN by default for PSA register, aspect 9:16 fixed, duration ~30s fixed, hard no's).
- Reference pull.
ralphy ref pull <reference-shorts-url> → frames → transcribe → analyze-video → blueprint. Save one canonical pictogram frame as the prototype --ref.
- Scenario lock. Write
scenario.json with 10 scenes × ~3s, IF / DO-NOT / BUT / AND structure, angle "storytime" (passes the virality rubric — "analog-horror-psa" doesn't). ralphy project score <id> --strict. Get explicit user "go" on the VO lines BEFORE any generation.
- Style lock. ONE prototype icon on
gpt-5.4-image-2 --ref <genre-frame>. Show user; get yes/no; iterate ONLY this slot until yes.
- Batch the 9 sibling icons via sequential bash for-loop on
gemini-3-pro-image-preview --ref <approved-prototype>.
- Chroma-key the 10 icons to
artifacts/images-keyed/.
- Voiceover. Verify voice exists. Sequential generation. ALL CAPS input. Loudnorm pass AFTER.
- SFX. Sequential. 4 climax-growl variants for layering.
- Music. yt-dlp pull from user-supplied YouTube synth-tension reference.
- Composition. HyperFrames comp with: keyed icons (transparency), MobiusWobble parent, SnowCanvas + VcrTrackingCanvas + MobiusScanlines bed, GlitchCaption (5-layer RGB-split), GlitchXIcon (5-layer RGB ghost), SignalLostColorBars climax.
- Render + optimize.
ralphy render <id> → ralphy video optimize --crf 30 --tune grain --preset veryslow.
- Hand off to
/evaluator for the post-render quality gate.
CLI cookbook
ralphy generate image --project <id> --slot scene-02-PROTO \
--model openai/gpt-5.4-image-2 --size 1080x1920 \
--ref artifacts/refs/genre-frame.jpg \
--prompt "$(cat prompts/style-lock.txt)"
for slot in scene-01 scene-03 scene-04 scene-05 scene-06 scene-07 scene-08 scene-09 scene-10; do
ralphy generate image --project <id> --slot "$slot" \
--model google/gemini-3-pro-image-preview --size 1080x1920 \
--ref artifacts/refs/icon-master.png \
--prompt "$(cat prompts/$slot.txt)"
sleep 2
done
mkdir -p artifacts/images-keyed
for f in artifacts/images/*.png; do
ffmpeg -y -i "$f" -vf "colorkey=0x000000:0.20:0.08,colorkey=0xFFFFFF:0.20:0.05,format=rgba" \
"artifacts/images-keyed/$(basename "$f")"
done
xh GET "https://api.elevenlabs.io/v1/voices/<voice-id>" \
"xi-api-key:$ELEVENLABS_API_KEY" | jq .voice_id
for slot in scene-01 scene-02 ... scene-09; do
ralphy generate voiceover --project <id> --slot "$slot" \
--voice <voice-id> --text "$(cat prompts/vo-$slot.txt)"
done
for f in artifacts/voiceover/*.mp3; do
ralphy audio loudnorm --target -16 "$f"
done
for s in vhs-hiss-bed static-pop ebs-alert-tone low-drone-bed mirror-shimmer \
signal-lost-tone rgb-static-burst climax-growl-v1 climax-growl-v2 \
climax-growl-v3 climax-growl-v4; do
ralphy generate sfx --project <id> --slot "$s" --prompt "$(cat prompts/sfx-$s.txt)"
done
ralphy render <id>
ralphy video optimize --project <id> --crf 30 --tune grain --preset veryslow
Minimum-viable budget: ~$1.65-2.50 / ~45-60 min (the analog-horror-fridge-001 reference ran $4.45 / 2.7× over because the style was iterated without --ref from prototype 1).
See also
docs/skills-vs-templates.md — why this is a skill and not a template.
docs/playbooks/scenarist.md — angle / virality-rubric notes (use "storytime", not "analog-horror-psa").
docs/playbooks/art-director.md — model picks + ref-anchor flow.
docs/playbooks/editor.md — HyperFrames composition for noise stacks.
MEMORY.md — append-only-on-generations, Kling no-music post-mix, 11labs geoblock fallback.
- Reference postmortem:
.ralphy/workspaces/<ws>/projects/analog-horror-fridge-001/POSTMORTEM.md — the 12 rules and full $ accounting this skill codifies.
- Follow-up: scaffold
templates/entertainment-viral/analog-horror-psa/ via ralphy template extract once the format is run end-to-end again. Move the noise-overlay components to src/lib/components/overlays/AnalogTV.tsx.