One stdlib Python server (server.py, 127.0.0.1:8080) behind a Cloudflare quick tunnel (URL in url.txt).
| URL | Who | What |
|---|---|---|
/ |
public | Live deck, backstage slide removed. No presenter script. |
/follow |
public (phones) | Chapters, Q1 to Q20 ticking live, customer use cases, Novotx links |
/?presenter=<key> |
presenter | Full deck plus the sync hook and the QR card. Key is in presenter.key (chmod 600). |
/follow-qr.svg, follow-qr.png |
anyone | QR for <url.txt>/follow. The PNG file is rewritten when the server starts or the URL changes. |
./serve.sh serverrestarts just the server../serve.sh(no argument) also starts a NEW tunnel, which changes the URL and the QR.- Present from
<url>/?presenter=$(cat presenter.key). The key is removed from the address bar straight away and an HttpOnly cookie keeps presenter mode on reload. - Press L (or the teal Phones button in the bottom bar) for the audience QR card. The
...on the card opens presenter controls: Show demo links on phones (off by default) and Reset all ticks. - Tick requirements with the deck's own "Mark shown" rows. Phones update in about 0.1s locally, 1.5s at worst.
- The hook wraps the deck's
refreshDemo()(called after every tick) and also checks thebis-demo-done-v1localStorage every second. Changes are POSTed to/api/statetogether with the current chapter slide (SCENES[cur].ch). - Phones listen on
/api/stream(SSE) and poll/api/stateevery 1.5s whenever SSE is not delivering (Cloudflare can buffer SSE). - State lives in memory and
state.json, so it survives a restart. Only the presenter can write:X-Presenter-Keyheader or the presenter cookie. Anything else gets 403. - Reset:
curl -X POST -d '{}' -H "X-Presenter-Key: $(cat presenter.key)" <url>/api/reset. Reset bumps anepoch; an open presenter tab sees it and clears its own ticks instead of pushing them back. Opening the presenter URL in a fresh browser merges with the server ticks, it never wipes the phones. - Ticks are stored per browser origin. Ticks made in the
file://deck do not sync; present from the tunnel URL.
/follow and /api/content parse CITY.chapters, the CITY.chapters[i].url/impact lines, CITY.outcomes and CITY.proof6 from the live deck file on every change (mtime). If a parse fails mid-edit, the last good copy (content.last-good.json) is served. Chapter links are only shown after the server has fetched them and got HTTP 200 (rechecked every 30 minutes). If the deck changes during the session, phones reload the content by themselves.
- The Q&A overlay's corner card (Q) and its full-screen QR are repointed to
/followin presenter mode, so there is one audience QR. /followshows an Ask us a question button only when the Q&A portal is publicly reachable: its/api/infoURL is https and not localhost, or you put the public URL inqa-url.txt.
- Repo:
github.com/nicoaccela/bismarck-deck(private).render.yaml= the Blueprint. Planstarterso it never sleeps mid-demo. - The server uses
~/Bismarck-RFP-Deckwhen it exists (your Mac) and the bundleddeck/copy otherwise (Render). - Env:
PRESENTER_KEY(generated by Render, see Environment tab),QA_URL(optional public Q&A portal link).RENDER_EXTERNAL_URLsets the QR target automatically. - Deck changed?
./publish.shcopies it intodeck/and pushes; Render redeploys. - State (ticks) lives on the instance disk and resets on redeploy. Do not redeploy mid-demo.
Every page needs an access code. Anyone without it sees a branded login screen.
- The QR code carries the code (
/follow?code=...), so phones that scan it skip the screen. People given the bare link type the code once; a cookie keeps them in for 30 days. - The presenter link (
/?presenter=<key>) gets in without the code. - Local code:
access-code.txt(currently BMARCK). If that file is missing it is derived from the presenter key.ACCESS_CODEoverrides both. On Render, setACCESS_CODEin the Environment tab so it matches what you hand out. - 10 wrong tries from one address locks it out for 10 minutes.
"Email it to me" on /follow asks for name, email, department (optional) and a follow-up opt-in, then opens an email to that person with their copy.
- Stored in
leads.jsonlinDATA_DIR(a 1GB persistent disk on Render at /var/data, so redeploys keep it). - Download as a spreadsheet (presenter only): open the presenter link once, then
<url>/leads.csvin that same browser. - Personal notes are never sent. Only people who tick the box agreed to a follow-up.
/start screen: pick Watch live or My checklist (the QR opens this)./watchWatch live: the deck follows the presenter's slide and ticks. Read-only. Browsing away pauses; one click jumps back./followMy checklist: personal check-offs, notes, customer stories, email/PDF copy, sign-up. Shows the presenter's ticks as "Shown" badges./?presenter=<key>presenter deck (drives both views).