Skarlso/adventure-voter: Choose your own adventure style Presentation framework.

adventuring gopher

Made by hand with Procreate.

An interactive presentation system where your audience votes on what happens next. Think “choose your own adventure” meets live polling for technical talks and workshops.

You write your presentations as Markdown files with decision points. When you reach a choice, your audience votes on their phones, and the presentation follows the path in real time. All communication happens over WebSockets, so votes are visible immediately.

screen1
screen2
screen3
screen4

because presentations must be funI sat through a lot of boring presentations that could have been much better with a little excitement. Don’t get me wrong, I love a good technical presentation that shares a lot of interesting information about the latest™ technology. But after a long day at KubeCon you won’t want to sit through another slideshow.

This desperation and my love for TTRPGs made it… possible.

It is in heavy alpha. While I tested it, I’m pretty sure it doesn’t have flaws. Especially because I’m very bad at frontend development. The code there is cobbled together from bits of code from forums, books, and various repos that do some WebSocket handling. It works quite well, and I’m pretty sure it doesn’t farm coins…

I used the minimum I found, which is alpine.js.

Using Docker:

docker-compose up --build

Then open http://localhost:8080/presenter for your presentation screen and share http://localhost:8080/voter with your audience.

Or build from source:

make build
# Or: go build -o adventure .
./adventure

The frontend is embedded in the binary at compile time using Go embed package, so you only need the binary and your content files for distribution.

Once completed, run docker-compose down To turn it off.

Download latest releases from:

For example, visit the GitHub releases page: https://github.com/user/adventure-voter/releases/tag/v0.0.1

Find your binary, download it and extract it. From there, simply create a structure like this:

➜ tree
.
├── adventure
└── content
    ├── chapters
    │   ├── 01-intro.md
    │   ├── 02-certificate-choice.md
    │   ├── 03a-cfssl-success.md
    │   ├── 03b-openssl-fail.md
    │   ├── 03c-self-signed-disaster.md
    │   ├── 04-etcd-choice.md
    │   ├── 05a-etcd-success.md
    │   ├── 05b-etcd-warning.md
    │   ├── 05c-etcd-disaster.md
    │   ├── 06-apiserver-choice.md
    │   ├── 07a-apiserver-success.md
    │   ├── 07b-apiserver-insecure.md
    │   ├── 07c-apiserver-broken.md
    │   ├── 08-network-choice.md
    │   ├── 09a-network-success.md
    │   ├── 09b-network-mess.md
    │   ├── 09c-network-broken.md
    │   └── 10-final-success.md
    └── story.yaml

3 directories, 20 files

And run the binary like this:

➜ ./adventure
2025/11/21 08:16:47 Adventure server starting...
2025/11/21 08:16:47 Content: /Users/user/goprojects/presentation/content/chapters
2025/11/21 08:16:47 Story: /Users/user/goprojects/presentation/content/story.yaml
2025/11/21 08:16:47 Static: /Users/user/goprojects/presentation/frontend
2025/11/21 08:16:47 Server: http://localhost:8080
2025/11/21 08:16:47 Voter: http://localhost:8080/voter
2025/11/21 08:16:47 Presenter: http://localhost:8080/presenter
2025/11/21 08:16:47 Presenter authentication: DISABLED
2025/11/21 08:16:47 Starting server on :8080
2025/11/21 08:16:47 Content directory: /Users/user/goprojects/presentation/content
2025/11/21 08:16:47 Static directory: /Users/user/goprojects/presentation/frontend

Visit http://localhost:8080 and you will be greeted with the option to become a presenter or voter.

Content resides in Markdown files with YAML front-matter. Here is an original story chapter:

---
id: intro
type: story
next: next-id
---

# Welcome to the Adventure

This is your presentation content. Use regular markdown syntax.

Decision points let viewers vote:

---
id: first-choice
type: decision
timer: 60
choices:
  - id: option-a
    label: Try the risky approach
    next: risk-path
  - id: option-b
    label: Play it safe
    next: safe-path
---

# What Should We Do?

The audience will vote on the next step.

in the beginning content/story.yaml,

# Adventure Voter Story Index
start: intro

and proceed from there, forming a chain next Sections in markdown files.

Open presenter view on your screen and start sharing the voter URL. As you progress on your story, Decision Points will automatically start a polling session. The results appear in real time, and when the voting closes, click Continue to get on your way to victory.

You can generate a QR code for the voter URL to make it easier for your audience to opt in.

The backend is a Go server that handles WebSocket connections and vote aggregation. The frontend uses Alpine.js for a responsive UI without heavy frameworks. Voters connect via WebSocket to submit votes, and presenter views show the results in real time.

┌──────────────┐
│    Voters    │  WebSocket connections
│  (phones)    │
└──────┬───────┘
       │
       ↓
┌──────────────┐
│  Go Backend  │  Vote counting and state
└──────┬───────┘
       │
       ↓
┌──────────────┐
│  Presenter   │  Main display
│  (screen)    │
└──────────────┘

The server is designed to run behind a reverse proxy like Nginx or Traefik. Look reverse proxy setup For configuration examples.

For quick deployment on a cloud server:

git clone <your-repo>
cd adventure-voter
docker-compose up -d

Then configure your reverse proxy to handle TLS and forward requests to port 8080.

The server accepts several flags:

./adventure \
  -addr=:8080 \
  -content=content/chapters \
  -story=content/story.yaml \
  -presenter-secret=your-password

Configuration flags:

  • -addr:server address (default: :8080,
  • -content:Path to chapter markdown files (default: content/chapters,
  • -storyPath to :story.yaml (default: content/story.yaml,
  • -presenter-secret:authentication password (optional; disables authentication if empty)

Presenter secret is optional. If set, presenter control endpoints require authentication. This prevents the audience from advancing the slide. Public endpoints (viewing chapters, voting) remain open.

The application includes optional presenter authentication and is designed for deployment behind a reverse proxy.

Key security features include thread-safe state management, optional bearer token authentication for presenter endpoints, and proper file path sanitization.

Furthermore, minimal effort has been made to achieve security given this is not a mission-critical application. It is meant to be short-lived.

If the WebSocket connection fails, check that your reverse proxy passes the upgrade header correctly and that port 8080 is reachable. Browser developer tools will show WebSocket connection status in the Network tab.

If votes are not updating, verify that a WebSocket connection is established and check the server logs for errors.

If Markdown is not rendering, validate your YAML front-matter syntax and ensure file path story.yaml Match your actual files.



<a href

3 thoughts on “Skarlso/adventure-voter: Choose your own adventure style Presentation framework.”

Leave a Comment