About
Before and after for an image upscale. Drag the divider, scroll or pinch to zoom both sides in sync, hold L for a loupe. Shows the factor, sizes and progress while the job runs.
Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Upscale Compare to your project and fits it to your theme.
Code
How Upscale Compare is used: the demo's code. The component's own source comes with a free account.
Usage.tsx
"use client"
import * as React from "react"
import { UpscaleCompare, UpscaleImage } from "@/components/ui/upscale-compare"
/**
* Builds the original for a demo from the result itself: drawn down to 1/`factor` and saved as
* a rough JPEG, so the before side has real low resolution pixels and compression blocks without
* a second network request.
*/
function useLowRes(image: UpscaleImage, factor: number): UpscaleImage | null {
const [low, setLow] = React.useState<UpscaleImage | null>(null)
React.useEffect(() => {
let alive = true
const img = new Image()
img.crossOrigin = "anonymous"
img.onload = () => {
const w = Math.round(image.width / factor)
const h = Math.round(image.height / factor)
const canvas = document.createElement("canvas")
canvas.width = w
canvas.height = h
const ctx = canvas.getContext("2d")
if (!ctx || !alive) return
ctx.imageSmoothingQuality = "low"
ctx.drawImage(img, 0, 0, w, h)
try {
setLow({ src: canvas.toDataURL("image/jpeg", 0.5), alt: image.alt, width: w, height: h })
} catch {
setLow({ ...image, width: w, height: h })
}
}
img.src = image.src
return () => {
alive = false
}
}, [image, factor])
return low
}
// picsum.photos id 659, 720 x 540, inline so the 21st preview needs no image host.
const husky = {
alt: "A husky in a harness at dusk, head turned toward the light",
width: 720,
height: 540,
src: "data:image/webp;base64,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",
}
const FIT_CSS = "html,body{overflow:hidden}[data-fit-small] [data-fit-hide]{display:none!important}"
/**
* 21st shows a demo in a small frame: about 674 by 421 on a component page, 358 by 224 on a
* phone and 245 by 139 on a profile card. A demo taller than its frame gets scrollbars, which
* render white on dark pages. FitFrame fills the frame exactly instead.
*
* - "scale": the content shrinks until it fits, centered. For visuals such as orbs.
* - "crop": the content stays at full size from the top and fades out at the bottom. For text
* that streams in, where a changing scale would make the page breathe.
*
* Anything marked `data-fit-hide` (state buttons, the chatcn.dev line) drops out in frames
* shorter than `hideBelow`, so a profile card shows the visual alone.
*/
function FitFrame({
children,
mode = "scale",
hideBelow = 360,
}: {
children: React.ReactNode
mode?: "scale" | "crop"
hideBelow?: number
}) {
const content = React.useRef<HTMLDivElement>(null)
const [cropped, setCropped] = React.useState(false)
React.useLayoutEffect(() => {
const el = content.current
if (!el) return
const measure = () => {
const vw = window.innerWidth
const vh = window.innerHeight
el.toggleAttribute("data-fit-small", vh < hideBelow)
// Layout sizes ignore the transform, so measuring never feeds back.
const w = Math.max(el.offsetWidth, el.scrollWidth)
const h = Math.max(el.offsetHeight, el.scrollHeight)
const scale = mode === "scale" ? Math.min(1, vw / w, vh / h) : 1
el.style.transform = scale < 1 ? `scale(${scale})` : ""
setCropped(mode === "crop" && h > vh + 1)
}
measure()
const observer = new ResizeObserver(measure)
observer.observe(el)
window.addEventListener("resize", measure)
return () => {
observer.disconnect()
window.removeEventListener("resize", measure)
}
}, [mode, hideBelow])
const fade = "linear-gradient(to bottom, #000 calc(100% - 40px), transparent)"
return (
<div
className="flex w-full justify-center overflow-hidden"
style={{
height: "100dvh",
alignItems: mode === "crop" ? "flex-start" : "center",
maskImage: cropped ? fade : undefined,
WebkitMaskImage: cropped ? fade : undefined,
}}
>
<style>{FIT_CSS}</style>
<div
ref={content}
className="w-full shrink-0"
style={{ transformOrigin: mode === "crop" ? "top center" : "center" }}
>
{children}
</div>
</div>
)
}
/** The line under each 21st demo that points back to the kit. */
function MoreOn({ slug, children }: { slug: string; children: React.ReactNode }) {
return (
<p data-fit-hide className="text-[0.75rem]/[1rem] text-(color:--chatcn-fg-faint) text-center text-balance">
{children}{" "}
<a
href={`https://chatcn.dev/docs/components/${slug}`}
target="_blank"
rel="noopener"
className="text-(color:--chatcn-fg-subtle) hover:text-(color:--chatcn-fg) underline underline-offset-2"
>
chatcn.dev
</a>
</p>
)
}
/**
* A 4x upscale against its original. The divider sweeps on its own until the viewer drags it;
* scroll or pinch zooms both sides, and L holds the lens.
*/
export default function UpscaleComparePreview() {
const before = useLowRes(husky, 4)
const [position, setPosition] = React.useState(46)
const [held, setHeld] = React.useState(false)
React.useEffect(() => {
if (held || !before) return
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return
let frame = 0
const start = performance.now()
const tick = (now: number) => {
// Eases between 28% and 72%, about seven seconds a round trip.
setPosition(50 - 22 * Math.cos(((now - start) / 7000) * Math.PI * 2))
frame = requestAnimationFrame(tick)
}
frame = requestAnimationFrame(tick)
return () => cancelAnimationFrame(frame)
}, [held, before])
return (
<FitFrame>
<div className="flex w-full flex-col items-center justify-center gap-6 p-6">
<div className="flex w-full max-w-2xl flex-col">
{before ? (
<UpscaleCompare
before={before}
after={husky}
position={position}
onPositionChange={(next) => {
setHeld(true)
setPosition(next)
}}
onPointerDown={() => setHeld(true)}
/>
) : (
<div className="bg-(color:--chatcn-fill) rounded-(--chatcn-radius-lg) aspect-[4/3] w-full" />
)}
</div>
<MoreOn slug="upscale-compare">Drag, scroll to zoom both sides, hold L for the lens. On</MoreOn>
</div>
</FitFrame>
)
}















