How to build a screenshot frame tool
How to build a screenshot frame tool with a paste, a gradient, and a PNG at the exact social size. Follow the prompts, check each step, then skip the gallery.
You can learn how to build a screenshot frame tool as a paste, a gradient, and a PNG at a size you pick. Shots.so is that product. The catalog line is “Your screenshot, but dressed for the timeline.”
You pay $10–$20 a month. That is $120–$240 a year. The verdict is “Gradient, padding, done”. The first estimate is 2 hours: gradient background, padding, device frame, export.
I already wrote the teardown, Vibe code your own Shots.so. This guide is the walk. Prompts in order, then what to look at.
The audience is anyone posting product screenshots to social media. If you need a template marketplace, 3D mockups, or video exports, keep paying. The same v1 also replaces Xnapper and Pika.
TinyPNG Pro is the compressor, $25–$39 a month, about 60 minutes. I walked that as How to build a TinyPNG alternative. Crop and annotate is Photoshop, $9.99–$22.99 a month, about 12 hours. Capture on a Mac is CleanShot X, $8–$10 a month, about 10 hours.
How to build a screenshot frame tool without a gallery
The Shots.so page splits the work into three rows.
The first row is the one you want. Paste a shot. Sit it on a gradient. Add padding and a light browser chrome. Download a PNG that measures the size you asked for. That is the 2-hour v1.
The second row is 6 hours. Named style presets, batch export, arrows, more social sizes. Do that after one real tweet already used the file.
The third row is 2 weeks and up. A template gallery, 3D mockups, video exports, accounts. Skip it.
If you tell an agent to “clone Shots.so”, it aims at the third row. Pin every prompt to the first one.
v1 does this. Flavio hits the screenshot key, pastes into Shinyshot, nudges padding, and downloads hero-home.shot.png at 1600 by 900. The file never left the tab. No login, no upload, no saved gallery. Capture, compression, and arrows stay on those other cards.
The parts and where a click goes
Every product prompt ships in two flavors: Node.js plus Astro, and TanStack Start. The capability line for Shots.so is Astro, Alpine, and the browser. This site is an Astro app on Cloudflare, so that is the path I default to. Use TanStack Start if React is your weekday language. The paint still runs in the tab. Pick one stack and stay on it.
The stack post is which tab you copy. The prompt post is how you pin an agent to one row.
Open Build your own Shots.so. The guardrail is the useful sentence: render and export entirely in the browser so screenshots never leave the device. The non-goal sits next to it. No accounts, cloud storage, a template marketplace, video mockups, or team libraries.
A first visit fetches the page. After that, paste, sliders, and export are browser work. There is no Worker that accepts multipart/form-data. There is no cron. If the plan invents a shots table so the look survives a refresh, send it back.
The data the frame carries
The card lists local Composition, StylePreset, and ExportJob objects only. Tonight you need Composition and a short-lived ExportJob. StylePreset is the 6-hour row.
A composition is one loaded shot plus the current look plus the current output size. The shot is a bitmap with a filename and its natural width and height. The look is background, padding, corner radius, shadow, and frame. The size tonight is 1600 by 900, plus one other preset you can switch.
A new paste replaces the shot and keeps the look. A slider change rewrites the look and keeps the shot. An export reads both, paints a fresh canvas at the preset size, and holds a blob until you download it or copy it. Refreshing throws the composition away, and that is the product.
There is no owner row and no uniqueness constraint. The screenshot never becomes a network payload.
The screens you ship
Three surfaces on one page. Keep them boxes and labels. If the agent starts matching Shots.so’s live UI, send it back.
The empty canvas is the first thing you see. A paste hint, a drop target, a file picker, and a line that says the file stays local.
The workspace is a preview plus a style panel. The preview shows the framed shot on its background. The panel is swatches, padding, radius, shadow, and a frame picker. Sliders repaint live.
The export bar is size, download, and copy. Two social sizes are enough tonight. The download writes a new name. Copy puts the PNG on the clipboard, or shows a fallback if the browser refuses images.
Paste, compose, then export
Flavio copies hero-home.png from the screenshot key and pastes. The tab decodes the bitmap. A PDF or a renamed text file is refused with a sentence on the canvas. The current look stays.
A valid shot is scaled to the largest size that fits the padded box, never larger than its natural pixels. A tall phone capture and a wide desktop capture both sit in the middle. The frame header, if present, is part of that box, or a tall shot overflows.
Layer order is background, shadow, frame, then the shot clipped to the corner radius. When a frame is on, the radius lives on the frame’s outer edge. The shot’s own corners stay square inside it.
Export does not photograph the preview. It paints the same layers on an offscreen canvas at 1600 by 900. Download or copy uses that blob.
A smaller file is a different job. That walkthrough is How to build a TinyPNG alternative.
Prompt 1. Scaffold Shinyshot and stop
Copy the Astro prompt from the product page. Paste it into your coding tool. Put this block above it.
Ship the 2-hour row only.
Gradient, padding, frame, export.
Project name: Shinyshot.
Public URL is https://shots.flaviocopes.com.
There is no owner account.
There is no database.
The composition lives in page state.
Refreshing discards it.
Seed shot: /Users/flavio/hero-home.png
at 2400 by 1350.
Do not add style presets, batch
export, annotations, or extra
social sizes beyond two presets.
Do not add accounts, cloud
storage, a template gallery,
3D mockups, video, or a CDN.
Do not POST image bytes to a
Worker, R2, or any server.
When the plan is done, stop.
Do not write implementation
code until I say go.
The official prompt already asks for a short technical plan first: routes, tables, authorization, jobs. Let it produce that plan. Read it.
You want / for the workspace: empty canvas, style panel, export bar. Drop /api/render, a compositions table, and a sign-in screen.
If the plan includes D1, R2, a users table, or an upload endpoint, send it back. Paste the non-goal again.
When the plan is a static page plus Alpine or client state, say go. Let it scaffold the app and empty screens.
What to check after the scaffold
Start the app. You should see a paste hint, a drop target, and a line that says the files stay local. There is no login.
Open the repo. There is no migration folder inventing compositions. If the README is a placeholder, stop. It has to say refresh discards the shot, paste is the first path, and export paints a second canvas.
Prompt 2. Paste, drop, or reject
The mini-prompt on the card is “Paste, drop, or reject”. Agents hide paste behind a file picker because a picker is easier to demo. The screenshot key plus paste is the product.
Build intake for Shinyshot on
https://shots.flaviocopes.com.
Three ways in, in this order:
1. Paste from the clipboard.
Listen for paste. Pull image
data from clipboard items.
2. Drag and drop on the canvas.
Show a drop target on dragover.
3. A file picker as fallback.
Validate by decoding, not by
the extension or the OS MIME
type. Accept PNG, JPEG, and
WebP that become a bitmap.
Reject a PDF, a text file, or
a corrupt dump with a sentence
on the canvas. Leave the current
composition untouched.
A new valid image replaces the
shot and keeps the current look.
Cap huge inputs. A 6K capture
is downscaled with a notice so
the tab stays usable.
Never send image bytes to a
server or to analytics.
Seed these checks in the README:
- paste /Users/flavio/hero-home.png
- drop a text file named notes.jpg
- drop a second valid PNG
Done when a paste lands on the
canvas, notes.jpg shows a reason
and the first shot is still there,
and the Network panel shows no
image upload.
What to check after intake works
Copy hero-home.png and paste. The canvas shows the shot. Filename and 2400 by 1350 stay after a re-render.
Drop a .txt file renamed to notes.jpg. A sentence appears. hero-home.png is still the composition.
Drop about-flavio.png. The look you already set does not reset.
Open DevTools. The Network panel has no POST of image bytes. If you see an upload to R2 “so the shot survives a refresh”, stop. Paste the non-goal again.
If paste does nothing and only the picker works, send prompt 2 back. That is the whole product missing.
Prompt 3. Fit the shot and paint the preview
The mini-prompt is “The layout solver behind the pretty padding”. A tall phone shot and a wide desktop shot both have to sit in 1600 by 900. Agents scale the preview with CSS and call it done. The export then crops the shadow or smears the text.
Build the live workspace for
Shinyshot.
The page is a preview plus a
style panel. The panel has:
- gradient and solid swatches
- padding as a percent
- corner radius
- shadow strength
- frame: none, browser-light,
browser-dark
Every slider and swatch
repaints the preview.
Fit the shot in the padded box.
Keep aspect ratio. Center it.
Never upscale a small capture.
The background owns the extra
room. If a frame is on, the
header bar is part of the box
so a tall shot does not overflow.
Layer order: background, shadow,
frame, then the shot. Radius sits
on the frame's outer edge when a
frame is on. The shot's corners
stay square inside the chrome.
The preview canvas is sized for
the viewport. Label it as a
preview. It is not the export.
Done when hero-home.png sits
inside 12 percent padding on a
lime-to-blue gradient, a phone
screenshot stays sharp and
centered, toggling browser-light
does not clip the shot, and
sliders update without a reload.
What to check after the preview paints
Paste hero-home.png. Default look is a gradient, about 12 percent padding, a visible radius, a light shadow.
Turn padding up. The shot shrinks. Turn it down. The shot grows. It never exceeds 2400 by 1350.
Paste a tall phone capture. It stays in the box. Turn the frame on. It still stays in the box. The radius is on the chrome, not on the pixels inside it.
Resize the window. The preview scales. That is fine. Do not measure this canvas and call it 1600 by 900.
Prompt 4. Export at exactly 1600 by 900
The mini-prompt is “Export at exactly 1600 by 900”. The preview follows the window. The file has to measure the preset.
Build export for Shinyshot.
Two size presets on the bar.
The first is 1600 by 900.
The second is another size you
pick and label. Switching
presets recomputes the fit.
Download and copy do not use
the preview canvas. Create an
offscreen canvas at the preset
size. Paint the same layers
with measurements for that
size. Then make a PNG blob.
Download writes a new name:
hero-home.png becomes
hero-home.shot.png.
Never overwrite the original.
Copy writes that blob to the
clipboard. If the browser
refuses images, show a
fallback and keep download.
Shadows stay inside the
bounds. Radius antialiases.
The shot resamples cleanly
when it shrinks.
Do not add batch export.
Do not add named style
presets yet.
Done when a 1600 by 900
export measures exactly
1600 by 900 in any window
size, copy pastes into a
chat, and the original
hero-home.png is still on
disk.
Open the downloaded file and read the pixels. Window size and display density do not change the number.
What to check after the first export
Download hero-home.shot.png. Open it. 1600 by 900. Gradient, padding, optional chrome. The folder still has hero-home.png.
Resize the browser to a narrow pane. Export again. Still 1600 by 900. Switch the second preset. The new file matches that size.
Copy the PNG into a chat you already use. If copy fails, the fallback sentence is visible and download still works.
Clear the page with a refresh. The workspace is empty, which is what you asked for.
If the download is 1200 by something, or matches the preview element’s CSS size, the agent photographed the preview. Send prompt 4 back.
Failures the agent ships anyway
An upload “for later”
If the composition survives a refresh, the screenshot left the device. The README already says refreshing discards local work. Keep it that way.
Trusting the extension
notes.jpg can be a text file. Decode it. A bad drop does not wipe a look you already tuned.
Exporting the preview canvas
The on-screen bitmap follows the viewport and the display density. You notice this when the file matches the preview element’s size, or doubles it on a retina laptop. The offscreen canvas is the fix.
Upscaling a small capture
A 800 by 500 window dump blown up to fill 1600 by 900 turns into mush. Cap at natural size. Let the gradient own the rest.
Matching Shots.so’s chrome
A 3D laptop, a video spin, a marketplace of templates. That is the 2-week row. A flat browser bar is enough for a tweet.
Deploy the static page
Shinyshot wants Astro, Alpine, and the browser. Put it on a subdomain you already own, shots.flaviocopes.com, or a path next to a site you already deploy. There is no database to back up.
The product pages put hosting this way: a focused replacement costs your build time plus close-to-zero hosting. The hosting post is still the path for the custom domain, even when the Worker has no bindings.
This site ships to Cloudflare. A static Astro page on Pages is enough. Attach the domain. The public URL is the product, so shots.flaviocopes.com has to resolve on the public internet, not only on localhost.
Ship the empty workspace. Paste hero-home.png from a phone on cellular, not the same Wi-Fi as your laptop. Confirm the Network panel is still quiet. Confirm the download is 1600 by 900 and named hero-home.shot.png.
Use it as the real framer for a week. If you miss named looks or a second batch of sizes, those are the 6-hour row and the other mini-prompts on the card.
Then cancel Shots.so, or Xnapper, or Pika, whichever one you pay for.
What to skip on purpose
Leave out accounts, cloud storage, a template marketplace, video mockups, and team libraries.
Style presets and batch export wait until you have posted one file. The preset mini-prompt is “Style presets that live in localStorage”. Save the look under a name. Applying it never touches the loaded shot.
A printed code is a different job. That is the QR code guide. $15–$49 a month, about 90 minutes.
A one-page site for the tool’s own marketing blurb is Carrd, $2–$4 a month, about 2 hours. The Carrd walkthrough is that post. The Carrd guide is the walk.
More writeups land on the guides index. The audit is the catalog if you want a smaller first ship.
Start now
Go to Build your own Shots.so. Copy the Astro prompt, or TanStack Start if that is what you write.
Tell the agent the first estimate only: gradient background, padding, device frame, export. Two hours.
Paste one real screenshot. Put the PNG on a tweet tonight.