DKBezeler
Every framed iPhone screenshot and screen recording on this site goes through the same pipeline: I drop a capture in a folder and it comes out baked into a device bezel. DKBezeler is that pipeline, extracted into a CLI anyone can run. It looks at your capture’s resolution, picks the right device frame, and bakes one flat asset: a PNG that is transparent outside the phone, or a light/dark pair of videos with corners matched to your page. The DKprefix is of course an homage to NeXTSTEP’s NS prefixes.
See it
A raw iPhone screenshot from my Dot review and the PNG DKBezeler baked from it, exactly as the pipeline produced it:


And a baked screen recording. Toggle this site’s theme and watch the corners follow:
Start
npx @diklein/dkbezeler init
npx @diklein/dkbezeler "Screen Recording.mov" --out public/imginitfetches Apple’s official product bezels onto your machine. Apple publishes them as public downloads on their CDN, so DKBezeler never ships or redistributes any frame art. Without the automatic download you would need to fetch the assets by hand at developer.apple.com/design/resources. By default every device Apple publishes is fetched, and for a subset you can name your device: init iphone-17.
Stills need nothing beyond the package itself. Baking screen recordings needs ffmpeg installed, and the CLI tells you if it is missing.
How it picks the frame
Your capture’s pixel size is looked up in a device table. For example, 1206x2622 is an iPhone 17 Pro capture and 1320x2868 is a Pro Max. No match falls back to the frame whose screen cutout is closest in aspect ratio, and a fit more than 4% off is rejected instead of silently wrong.
Any frame works
A frame is any PNG of a device with a transparent screen cutout ringed by a solid bezel. DKBezeler measures everything it needs from the file itself, flood-filling the alpha channel to find the screen. Apple’s bezels, Android frames, your own exports, etc. are all the same to it:
npx @diklein/dkbezeler measure my-frame.png --name "iPhone 17 Pro"When a new iPhone ships, nothing here needs an update. Download the new bezel, measure it, done.
Light mode; dark mode
Framed videos come out as a pair: a light version and a dark version, with corners baked to match each theme’s page background because H.264 has no alpha. Tell DKBezeler your two backgrounds once and every bake in the project matches your site:
npx @diklein/dkbezeler theme --light "#ffffff" --dark "#0f1317"The display component renders both versions and CSS shows the right one. On this site the swap is instant when you toggle themes. One subtlety the tool handles for you: near-black colors decode about one unit darker through video encoding, so it nudges the dark value accordingly and tells you when your corners are baking on defaults instead of your own colors.
The details that took the evenings
Around a phone’s rounded corners, the frame’s outermost pixels are antialiased. A naive composite lets the bright screen show straight through them: invisible on white, a crusty dotted rim in dark mode. DKBezeler recomputes every one of those pixels exactly from the frame’s own alpha on every bake. Videos additionally keep their real frame rate (a default ffmpeg pipeline quietly resamples 60fps recordings to 25). And because iPhone recordings store color on a different numeric scale than web video players expect, a naive re-encode shifts every color slightly on playback, enough to un-match your corners from your page. DKBezeler converts each clip to the scale the web assumes, so the baked colors are the colors that appear.
Display components
npx shadcn@latest add https://diklein.com/r/dk-bezel.jsonThis installs DKBezelImage and DKBezelVideo. A framed clip is two files, so DKBezelVideoputs both on the page and a CSS rule hides the one that doesn’t match the current theme. Toggling your site’s theme swaps them instantly with no JavaScript. The hidden file costs your visitors almost nothing. Browsers fetch only a small header for a video that isn’t displayed, so the wrong-theme clip never downloads or plays.