Front page.What it is
Live
IAN
A live room that puts a finished deck on every client's phone, in step with the presenter.
In A Nutshell. It shows decks; it never makes them.

Set in type.The stack
What it runs on
A web app for the room, and a small sync server beside it.
Interface
- Next.js16.3the room, the console, the deck list
- React19.2
- uqr0.1QR codes for the room
Live
- Sync serverserver-sent events to every phone, on Railway
- Upstash Redisa 50-event history and the viewer count
Data
- Neon Postgresdecks and rooms
- Drizzle0.45queries
- Cloudflare R2slides, on pre-signed uploads
- Better Auth1.7magic-link sign-in
Tooling
- Bun1.4
- Biome2.5lint and format
- Vercelthe web app
On the press.How it works
5 steps, in order
From a deck made elsewhere to a room full of phones.
The deck arrives made
CCC builds the slides and reserves a deck. Each slide image goes straight to R2 on a pre-signed upload.
Every slide is checked
Completion reads the first 24 bytes of each PNG and checks its size. One wrong slide refuses the whole deck.
Open the room
The launcher shows a QR code. Clients scan it; nobody signs up.
Present
The console moves and every phone follows. A phone that drops reconnects and catches up from the last 50 events.
End the meeting
One button closes the room on every phone. Nothing stays behind for the client.
Pulled proof.The code
Refusing a bad slide
for (const s of manifest.slides) { const key = `decks/${id}/slides/${pad(s.n)}.png`; const header = await getObjectRange(key, 0, 23); if (!header) { return NextResponse.json( { error: `Missing object: ${key}` }, { status: 409 }, ); } const size = readPngSize(header); if (!size || size.width !== s.width || size.height !== s.height) { return NextResponse.json( { error: `PNG size mismatch: ${key}` }, { status: 422 }, ); } }24 bytes per slide is enough to read a PNG's size, so the check never downloads the deck.
The reel.The walkthrough
Watch it run
Corrections.What it fixes
The problems it solves
Decks emailed as PDFs nobody opens.The deck is on their phone while you talk.
Screen share: lag, and your desktop on show.Full-resolution slides on each phone. Nothing leaves your screen.
A shared link that changes when the deck changes.A pushed version never changes a link already given out.
Viewers asked to make an account.Scan and watch.
Editor's letter.Why I built it
Why it exists
My slide pipeline worked, and its output had nowhere to go but a file.
IAN is that endpoint. I built it as internal infrastructure first and a product second: the decks come from my own system, and the room only has to show them well.

Against the rack.Better than average
Against the usual tools
Against Zoom or Meet screen share for showing a deck, and DocSend, Pitch and Gamma for sharing one. Their side was read from each vendor's own pages on 2026-09-27.
| What | The market average | This app |
|---|---|---|
| Joining | A link sent ahead, often by email or calendar invite. | Scan a QR code on the presenter's screen.vendor's page |
| What viewers see on a call | A video feed of your screen, limited by bandwidth. | The slide itself, full resolution, on their own screen.vendor's page |
| After a dropped connection | Gamma's follow link: the viewer clicks to sync back up. | The phone catches up on its own from the last 50 events.vendor's page |
| After the meeting | A deck link stays open until you turn it off or it expires. | End meeting closes the room on every phone at once.vendor's page |
The imprint.How long it took
24 commits in 20 days
- commits
- 24
- days with a commit
- 6
- days, first commit to latest
- 20
- First commit
- All six features landed: the core took four days
- Latest commit
Measured: git log of the app's repo, 2026-09-27.
Colophon.Where it sits in the ledger
