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
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
/posterwith 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. ReadMODELS.mdbefore 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
#1a1a1aRemotion / 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_idas a pre-flight. - Sequential VO + SFX generation. ElevenLabs 429s on ≥3 concurrent. Always serial.
ralphy queuedaemon 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 grainCRF 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 graindrops 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 <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 |
| 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 -16on 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-frametranslate(${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#aaabackground underneath the icon layer.VcrTrackingCanvas— Mobius1-port algorithm: 20 noise points per frame, each spawns a tail ofgetRandomInt(1, 50)2×2 white rects walking ±1-4px horizontally.filter: blur(1px). Mounts as<canvas>withuseRef+useEffect([frame])so it redraws every frame.MobiusScanlines— repeating-linear-gradient scanline overlay onmixBlendMode: "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
multiplyoverlay. - 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-2with--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_v2viaralphy generate sfx. 4 variants per climax growl, layered. - Music:
ralphy ref pull <yt-music-url> --audio-onlyfor 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--reffrom 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.05toartifacts/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 grainpost-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 queuefor image batches → 9/10 jobs hit403 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.jsonwith 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
/evaluatorfor the post-render quality gate.
CLI cookbook
# 4. Prototype the locked style — gpt-image with genre-ref
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)"
# User says "go". Save winner as artifacts/refs/icon-master.png.
# 5. Batch 9 siblings — sequential, nano-banana, ref the approved master
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
# 6. Chroma-key to alpha
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
# 7. Voice pre-flight + sequential VO
xh GET "https://api.elevenlabs.io/v1/voices/<voice-id>" \
"xi-api-key:$ELEVENLABS_API_KEY" | jq .voice_id # must not 404
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
# AFTER all VO files exist, separate loudnorm pass:
for f in artifacts/voiceover/*.mp3; do
ralphy audio loudnorm --target -16 "$f"
done
# 8. SFX (sequential, 4 climax-growl variants for layering)
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
# 11. Render + optimize (mandatory -tune grain for noise-heavy comps)
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/viaralphy template extractonce the format is run end-to-end again. Move the noise-overlay components tosrc/lib/components/overlays/AnalogTV.tsx.