Static site that displays a gallery of before / after image pairs with an interactive draggable slider. Drop folders in pairs/, push, done. Deployable as-is on Vercel or Cloudflare Pages.
One click to clone the repo into your GitHub account and deploy it on either platform.
The deploy buttons above create a new, standalone repo on your GitHub account (not a GitHub fork — no upstream link, no "Sync fork" button). If you want to keep pulling future improvements from this repo, use the Fork button at the top of the GitHub page instead, then connect that fork to Vercel/Cloudflare manually.
- On every
git push, the platform (Vercel or Cloudflare) spins up a Linux container - The
build.shscript scans thepairs/directory, copies every image intopublic/pairs/, and generates apublic/pairs.jsonmanifest - The
public/directory (withindex.html+ the JSON + the images) is served on the CDN - The home page loads
pairs.jsonand renders one interactive before/after slider per pair
No framework, no bundler, no Node, no npm install — just bash, a static HTML file, and your images.
Create a sub-folder in pairs/ and drop two images named before.* and after.* (any of .jpg, .jpeg, .png, .webp, .avif, .gif):
pairs/
└── my-renovation/
├── before.jpg
└── after.jpg
Push the commit. That's it — the build picks it up automatically.
Add a meta.txt file in the folder to set a title, description, date, and custom labels:
title: Living room renovation
description: Repainted, refloored, and decluttered over a long weekend.
date: 2025-09-14
before_label: Before
after_label: After
All fields are optional. If title is missing, the folder name is used (de-slugified). Default labels are "Before" and "After". date is omitted from the manifest (and from the page) when not set.
Drop a pairs/intro.html file to display an introduction block at the top of the gallery (above the first pair). Its content is embedded as-is into pairs.json and rendered as raw HTML, so you can use links, paragraphs, lists, etc.:
<p>A small selection of recent restorations and reworks.</p>
<p>Drag the slider on each image to compare. <a href="https://example.com">More on my site →</a></p>Since the HTML is injected via innerHTML, only put content you trust there (it's your own build, so this is normally fine — just don't accept untrusted PRs that touch this file without reviewing).
Pairs are listed in alphabetical order of folder name. The simplest convention is a numeric prefix:
pairs/
├── 01-kitchen/
├── 02-bathroom/
└── 03-garden/
git clone <this-repo> my-gallery
cd my-gallery
# Drop your pairs in pairs/ — keep or delete the demo folders- Go to vercel.com/new
- Import the repo
- Vercel reads
vercel.jsonautomatically — nothing to configure - Click Deploy
- Go to dash.cloudflare.com → Workers & Pages → Create → Pages → Connect to Git
- Select the repo
- Configure:
- Build command:
bash build.sh - Build output directory:
public
- Build command:
- Click Save and Deploy
The same repo can be deployed to both platforms in parallel without conflict.
The build script only depends on bash, sed, grep, and cp — all standard. No installation needed:
bash build.sh
python3 -m http.server -d public 8000
# Open http://localhost:8000Or use the npm script if you prefer:
npm run build
npm run serve.
├── pairs/ # ← drop your pairs here (this is the only thing you edit)
│ ├── intro.html # optional site-level intro (HTML)
│ ├── 01-demo-paris/
│ │ ├── before.jpg
│ │ ├── after.jpg
│ │ └── meta.txt
│ └── 02-demo-portrait/
│ ├── before.jpg
│ ├── after.jpg
│ └── meta.txt
├── public/
│ ├── index.html # gallery page (loads pairs.json at runtime)
│ ├── _headers # Cloudflare cache rules
│ ├── pairs.json # ← generated by build.sh (gitignored)
│ └── pairs/ # ← generated by build.sh (gitignored)
├── build.sh # the whole build, in bash
├── vercel.json # Vercel config
├── wrangler.toml # Cloudflare config
├── package.json # convenience scripts
└── README.md
- Page styling: edit the
<style>block inpublic/index.html. The CSS uses custom properties at the top (:root) for colors, fonts, and spacing — tweak those first. - Slider behavior: the JS at the bottom of
index.htmlis ~120 lines of vanilla JS, no dependencies. Easy to fork. - Site title and brand: change the
<title>and the<h1 class="brand">inpublic/index.html. - Default labels: change the defaults in
build.sh(search forbefore_label="Before").
- Both images of a pair should have the same aspect ratio — the slider crops them to match the natural size of the
beforeimage. - Keep image dimensions reasonable: 1600–2400px on the long edge is plenty for the web.
- The build doesn't re-encode or resize images — what you commit is what gets served. Optimize beforehand if needed (e.g. with Squoosh or
cwebp).
The build output ships with cache headers configured for both platforms (vercel.json for Vercel, public/_headers for Cloudflare):
pairs/**(the actual images):max-age=31536000, immutable— they're served from the CDN foreverpairs.json:max-age=0, must-revalidate— always check for the latest manifest
This means new pairs appear immediately after a deploy, without waiting for cache TTLs.
The whole point is to stay close to typst2web in spirit: a single bash script, no node_modules, no framework lock-in, easy to read end-to-end in 10 minutes. If you need a richer site (pagination, filters, tags, search), it's still a good starting point — fork it and add what you need.
MIT.