Valinor Decks

Back to dashboard

What this is

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.

Where files live

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.

Uploading a deck

  1. Export your presentation as a single HTML file (Gamma, Google Slides, Reveal.js, etc. all support this).
  2. Go to the Decks tab, give it a title, pick the file, and click Upload.
  3. The system counts slides automatically and stores the file. It becomes v1.

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:

  1. python3 tools/check-deck.py your-deck.html — static checks: structure, print blank-page geometry, slide-count mismatches.
  2. 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.
  3. 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.
  4. Look at the screenshots and page images it produced. The scripts prove the deck isn't broken; your eyes prove it's good.

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.

Mobile, print, and PDF

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.

Versioning

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.

Sharing with individuals vs. domains vs. open

When creating a share, choose the access level:

Each share link is unique regardless. Two shares to the same person for different decks produce two different URLs.

What a recipient experiences

When someone opens a link, they enter their email and get a one-time PIN to confirm they own it (Cloudflare handles this). Then:

The email and PIN step happens once per browser session, so opening a second deck you sent them doesn't ask again.

Tracking levels

Set per share, invisible to the recipient:

Revoking access

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.

Analytics

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.