X Reveal Maker
press and hold to reveal images, made in your browser
P Live preview
Timeline
what scrollers see
Press and hold
the full screen viewer, always black
Load two pictures to begin
Base is what people see while scrolling. Reveal is what pops up when they press and hold.
Export 0 by 0Average cover 0%Separation 0%Last file 0
Aim for this: the left panel looks like your base picture, the right panel looks like your reveal.
If the reveal ghosts through on the left, lower Reveal brightness or raise Base lift.
How to make one that actually works
The idea in one line
A PNG can carry see through pixels. X paints the timeline card on your app background but always paints the
full screen viewer on black. One picture with a cleverly woven transparency pattern therefore reads as two
different pictures depending on what sits behind it.
Step by step
Load two pictures. Base is the boring one people scroll past. Reveal is the payoff. Bold shapes and big text beat busy photos every time.
Press Auto tune. It reads both pictures and sets sensible brightness levels for you.
Check the two panels. Left should look like your base. Right should look like your reveal. Nudge the sliders until both read cleanly.
Download the PNG. Leave it at 2432 on the long edge and PNG 8 unless you have a reason not to.
Open x.com in a desktop browser and attach the file. Do not use the iPhone or Android app to post it.
Post it alone. One image per post. Adding a second image changes how X processes both.
Tell people what to do: press and hold on mobile, or click to open on desktop.
Things that will ruin it
Posting from a phone app. The single most common failure.
Screenshotting the PNG. Screenshots throw away the transparency.
Editing it in another app first. Most editors flatten the see through pixels onto white.
Files over five megabytes. X converts those and the trick dies. Keep PNG 8 selected.
Sending it through chat apps before posting. They recompress.
The honest limitation
This trick reads the background behind your image. Someone browsing X in dark mode already has a dark
background in the timeline, so they may spot the reveal without pressing anything. Use the theme switcher
above the previews to see exactly what each audience gets, and tune for the one you care about.
What the sliders really do
Base lift squeezes the base picture into the bright half of the range. The trick needs the base to be brighter than the reveal at every single pixel.
Reveal brightness squeezes the reveal into the dark half.
Colour safety drains colour from both pictures. Every pixel carries three colours but only one transparency value, so the colour with the widest gap between your two pictures decides the transparency and the other two spill over. Draining colour closes that gap. This is why nearly every example of this trick you have seen looks close to black and white.
Pattern density floor stops the see through pattern from getting so sparse that colours blow out.
View bias decides which of the two views absorbs the leftover error, because no pattern can serve both perfectly.
Reveal punch overdrives the solid pixels for drama at the cost of a faint stain on the timeline.
A proxy you can deploy in two minutes
Replicate refuses calls made straight from a web page. Deploy this on Cloudflare Workers,
then paste the resulting address into the proxy box. Your token then only passes through
infrastructure you own.
export default {
async fetch(req) {
const cors = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "*",
"Access-Control-Allow-Methods": "GET,POST,OPTIONS"
};
if (req.method === "OPTIONS") return new Response(null, { headers: cors });
const target = new URL(req.url).searchParams.get("url");
if (!target) return new Response("missing url", { status: 400, headers: cors });
const allowed = ["api.replicate.com", "replicate.delivery", "pbxt.replicate.delivery"];
const host = new URL(target).hostname;
if (!allowed.some(a => host === a || host.endsWith("." + a)))
return new Response("host not allowed", { status: 403, headers: cors });
const headers = new Headers(req.headers);
headers.delete("host");
headers.delete("origin");
headers.delete("referer");
const res = await fetch(target, {
method: req.method,
headers,
body: ["GET", "HEAD"].includes(req.method) ? undefined : await req.arrayBuffer()
});
const out = new Headers(res.headers);
for (const [k, v] of Object.entries(cors)) out.set(k, v);
return new Response(res.body, { status: res.status, headers: out });
}
};
Save as worker.js, run npx wrangler deploy worker.js, then use
https://yourname.workers.dev/?url={url} in the proxy box.
The {url} part is replaced automatically.