layer 1 — the scene
layer 2 — the broadcast
SIGNAL_04 / SCENE.RAW
STANDBY
AN OPEN WORKFLOW

The Decrypt
Reveal

One photo becomes a scene that hides a second, corrupted truth. This is a free, open workflow — generate your own images with any AI tool, drop in a zero-dependency script, and ship it. Everything below explains the trick; you're touching it right now.

MOVE TO SCAN · HOLD TO DECRYPT
01 — THE ILLUSION

Two images. One lie.

There is no shader magic in the reveal itself. It's two images of the same scene, generated to be pixel-aligned, stacked on top of each other. A CSS mask-image radial gradient cuts a soft hole in the top one, and JavaScript lerps that hole toward your cursor. Alignment is the entire illusion — drag the handle and watch every object hold its position while the world changes.

photoreal layer broadcast layer
LAYER 2 — BROADCAST
LAYER 1 — SCENE

Layer 2 is generated from layer 1 (image-to-image, strength ~0.6) with a prompt that opens with a "layout law": the output must be pixel-identical in composition. Every ordinary object was seeded into layer 1 on purpose, so it has a secret to become — notes turn to ciphertext, windows grow targeting brackets, the router becomes blinking data nodes.

02 — THE STACK

Nine layers, one frame

Everything above the base is compositor-cheap garnish: only opacity, transform, clip-path and the mask variables ever animate, so the whole thing runs smoothly on a phone. Hover the legend.

SUBJECT_01● RECX 0.482DECRYPTING ▓▓░ 64%

Explicit z-index on every layer is not optional — see bug 01 below.

03 — THE PIPELINE

Every asset is born from the last

The rule that holds the whole thing together: each derived asset is generated from the locked previous stage, never from the original photo. Regenerate a stage and everything downstream must be regenerated too — so iterate each stage until you love it before spending a single run on the next.

the source portrait

0 · Your photo

One portrait. Passed only as the identity reference for stage 1 — nothing downstream ever sees it again.

layer 1

1 · The scene

Text-to-image, ultra-wide lens, chiaroscuro in your site's palette — and deliberately seeded with "secret" objects. Demand grime: AI defaults to showroom polish.

layer 2

2 · The broadcast

Image-to-image from the locked scene + a style reference. Prompt opens with the layout law; bans glow; describes terminal structure, never skulls.

variant framedepth map

3 · Variants + depth

Re-dither the broadcast for shimmer frames (or animate it with video gen). Feed the scene to a depth estimator for the parallax map.

PHOTO  ──▸  SCENE  ──▸  BROADCAST  ──▸  VARIANTS      SCENE  ──▸  DEPTH MAP
04 — THE SHIMMER

The dots must live

A static glitch image reads as a filter. The fix costs two extra generations: ask the model to regenerate the broadcast "as the next frame of the same transmission" — identical composition, re-rolled dot pattern, new tear positions. Flipping opacity between the frames makes the dots crawl like a CRT re-dithering. This demo is doing it right now, from the portrait run of this workflow.

frame 0 frame 1 frame 2
FRAME 0 — BASE
FRAME 1 — RE-DITHERED
FRAME 2 — RE-DITHERED

Verify alignment before accepting a variant: blend it with the base at 50% opacity — you should see one clean image with only the tears ghosted. Double edges mean it drifted; re-roll. In the demo: slow flicks while idle, rapid ~60ms flips during glitch bursts, and the terminal text "scrolls" between frames so the flicker reads as a live session.

05 — FAKE 3D

Depth from a single photo

Feed the scene to a free depth estimator (Depth Anything, MiDaS), get a grayscale map — bright is near. A ~60-line WebGL shader offsets the photo's UVs by depth × cursor offset, and the subject genuinely shifts against the background. Move your cursor over this portrait.

portrait
photo
INPUT — PHOTO
depth map
OUTPUT — DEPTH (BRIGHT = NEAR)
float d = texture2D(uD, v).r; // depth float gx = dD/dx, gy = dD/dy; // depth gradient float edge = clamp(1. - 5.*length(vec2(gx,gy)), 0., 1.); vec2 off = uO * (d - .5) * edge; // near moves more… gl_FragColor = texture2D(uT, v + off); // …silhouettes stay pinned

The edge term is the difference between magic and mess: without it, depth boundaries smear background pixels across silhouettes in ugly streaks. It zeroes the offset wherever the depth gradient is steep.

06 — THE BUGS YOU WILL HIT

We hit them so you don't

01 · Compositor stacking

An accelerated WebGL canvas can paint above later masked siblings in Chromium, regardless of DOM order. Symptom: "the reveal stopped working" while the ring and particles still draw — it's rendering underneath the canvas. Fix: explicit z-index on every layer.

02 · Temporal dead zone

If your resize handler touches GL state, the initial resize() call must run after the let gl declarations execute — hoisted functions can reach a let before it initializes. Symptom: Cannot access 'gl' before initialization and a dead page.

03 · Opaque canvas flash

A WebGL canvas with alpha:false composites as solid black before anything is drawn — covering your fallback image, permanently if the shader fails. Keep it display:none until textures are loaded and the first frame is safe.

04 · GL killing the loop

Wrap the GL draw in try/catch inside your rAF. A lost context should quietly degrade the parallax — not freeze the lens, the particles, the HUD, and everything else living in the same loop.

07 — BUILD ORDER

Every step ships

Ordered by wow-per-effort. Stop wherever the ratio stops making sense — step 3 alone is already a hero people will screenshot.

01Lock layer 1iterate the scene until you love it — everything downstream inherits it
02Lock layer 2generate from layer 1 + style ref; fix screens with targeted single-edit prompts
03Static lens demotwo stacked images, the masked lens, hold-to-decrypt — ship-able on its own
04Garnishbursts, ghosts, tear, scanlines, grain, HUD, particle canvas
05Shimmervariant frames (or video gen → extracted frames), opacity-flipped
06Depth parallaxdepth map + the edge-aware shader on the base photo
07Guardrailsreduced-motion fallback, WebGL fallback, DPR cap, pause off-screen