A standalone site for sharing HTML presentation decks with external parties. Each recipient gets a unique link. You control who can access what, and you get engagement analytics (how far they read, time per slide, clicks) without the recipient knowing the tracking level.
Deck HTML files are stored in Cloudflare R2 (object storage). Metadata, shares, and analytics are in Cloudflare D1 (SQLite). Both are in our Cloudflare account under the valinor-decks project. Nothing touches valinor-intra or the office server.
The database (shares, analytics, versions) is exported and committed to our private GitHub repo automatically every week, so there's an off-Cloudflare backup. Deck HTML files in R2 rely on Cloudflare's built-in redundancy and aren't separately backed up, so keep the original HTML you exported if you want your own copy.
For a deck that looks right on web, mobile, and PDF with no tuning, build from templates/deck-template.html in the repo. It's the tri-mode Valinor deck harness: a fixed 16:9 canvas on desktop, reflowed cards on mobile, and one clean slide per page in print. It ships with patterns for stat tiles, a bar chart, a roadmap, and a data table. Keep everything self-contained (no external fonts, scripts, or remote images) so it loads fast and isn't blocked. Keep the whole file under 1.5 MB (compress embedded images to webp) so it opens fast on a phone on cell data.
Before uploading a deck you authored, run the gate in tools/, in order:
python3 tools/check-deck.py your-deck.html — static checks: structure, print blank-page geometry, slide-count mismatches.node tools/render-deck.mjs your-deck.html --out /tmp/deck-out — opens the deck in a real browser exactly as recipients see it (desktop, phone, and the Download-PDF path) and fails on JS errors, mobile overflow, and broken structure. Writes screenshots and the PDF for review.python3 tools/check-deck-pdf.py /tmp/deck-out/deck.pdf — renders every PDF page to an image and fails on blank pages; flags unreadably small text.The easiest way to run all of this is to ask Claude to build or update the deck: the build-html-deck skill in the valinor repo walks the whole workflow, including the visual review. Commit the deck's HTML to the decks/ folder in this repo when you upload it — git is the version history for deck content; the upload is just a copy. See tools/README.md for one-time setup.
To do it yourself, the human guide is docs/authoring-decks.md in the repo: the design rules for a deck that works on web, mobile, and PDF, and why each one matters.
Every deck we serve is wrapped in a light reader layer, whatever tool made it:
One thing the reader layer can't fix: content hidden behind tabs or accordions prints only the panel that's open. If the content matters, lay it out flat in the deck.
When you update a deck, click New version on the deck row and upload the revised HTML. It becomes v2, v3, etc. For decks we author ourselves, also commit the revised HTML to decks/ in the repo, so the git history shows what changed between versions.
All existing share links automatically serve the latest version. If someone opened your link on Monday and you upload v2 on Tuesday, they'll see v2 on Wednesday.
If you need a specific share to stay on an older version (e.g., a signed-off version for a specific counterparty), pin it when creating the share using the "Pin to version" dropdown. Pinned shares never auto-update.
When creating a share, choose the access level:
jane@acmecorp.com): Only that person can access the link. They authenticate via a one-time PIN sent to their email (Cloudflare Access handles this).acmecorp.com): Anyone with an @acmecorp.com email can access the link. Useful when you don't know exactly who at the company will view it, or when multiple people need access.Each share link is unique regardless. Two shares to the same person for different decks produce two different URLs.
When someone opens a link, they enter their email and get a one-time PIN to confirm they own it (Cloudflare handles this). Then:
hello@valinordigital.com to request access. No broken-looking dead end.The email and PIN step happens once per browser session, so opening a second deck you sent them doesn't ask again.
Set per share, invisible to the recipient:
Click Revoke on any share in the Shares tab. The link immediately stops working: the next time anyone opens it, they get a "link is no longer active" page, even if they'd viewed the deck before.
Go to the Analytics tab, select a share, and click Load. You'll see total opens, deepest slide reached, time per slide, individual sessions (with version viewed), and click/hover interactions. Data updates in real time as recipients view decks.