展示HN:选择你自己的冒险风格演示文稿
Show HN: Choose your own adventure style Presentation

原始链接: https://github.com/Skarlso/adventure-voter

## 冒险投票:互动演示 冒险投票是一个系统,用于创建引人入胜的“选择你的冒险”风格的演示文稿,适用于技术演讲和研讨会。它使用 Go、WebSocket 和 Alpine.js 构建,允许观众实时投票决定演示文稿的方向。 演示文稿以定义了决策点的 Markdown 文件编写。当到达一个选择时,观众通过手机上的网页界面投票,演示文稿会动态地跟随获胜的路径。该系统设计为易于使用 Docker 部署或直接从源代码构建。 目前处于深度 Alpha 阶段,旨在通过注入桌面角色扮演游戏风格的互动性来对抗枯燥的幻灯片。它具有可选的演示者身份验证功能,并设计为在反向代理后安全运行。虽然优先考虑简单性,但它包括基本的安全措施,例如线程安全的状态和文件路径清理。 **开始使用:** 从 GitHub 下载最新版本,构建你的内容(Markdown 章节和 `story.yaml` 索引),然后运行二进制文件。访问演示者界面,并与你的观众分享投票者 URL!

## 冒险投票:互动演示 一位开发者对枯燥的演示感到沮丧,因此创建了“冒险投票”——一种工具,允许观众*选择*演示的方向,就像“选择你自己的冒险”书籍一样。该系统使用markdown格式进行演示内容,并采用简单的后端/前端设置,观众通过`/voter`链接投票选择选项,演示者通过`/presenter`控制流程。 目标是提高参与度和记忆力,尤其适用于像Kubernetes这样复杂的主题(开发者计划在KubeCon上使用它)。虽然需要更多前期工作来构建结构,但它旨在创造比传统演示更愉快和难忘的体验。 然而,评论者很快指出该工具的名称侵犯了商标,并建议立即更名。讨论还探讨了该工具的适用性——质疑它对信息型或顺序构建的演示的用处——而其他人则赞扬了这个概念,并建议将其与AI集成,以实现内容的个性化探索。
相关文章

原文

Hand-drawn with Procreate.

An interactive presentation system where your audience votes on what happens next. Think "Choose Your Own Adventure" meets live polling for tech talks and workshops.

You write your presentation 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 appear instantly.

screen1 screen2 screen3 screen4

Because presentations should be fun! I sat through way too many boring presentations which could have a lot better given a tiny bit of adventure. Don't get me wrong, I like a good technical presentation that shares a lot of interesting details about the latest™ tech. But after a long day at KubeCon you don't want to sit through yet another slideshow.

This frustration and my love for TTRPGs made this... thing, happen.

This is in heavy alpha. While I tested it, I'm pretty sure it's not without its faults. Especially since I'm terrible at frontend development. The code there is pieced together from forums, books, and bits of code from various repos that do some websocket handling. It works quite well, and I'm reasonably sure it does not farm coins...

I used the bare minimum I could find, 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's embed package, so you only need the binary and your content files for distribution.

Once done, run docker-compose down to shut it down.

Download from latest release:

Go to the GitHub release page, for example: https://github.com/user/adventure-voter/releases/tag/v0.0.1

Find your binary, download 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

Navigate to http://localhost:8080 and you should be greeted with the choice of being a presenter or a voter.

Content lives in markdown files with YAML front-matter. Here's a basic 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 the audience 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.

Start in content/story.yaml:

# Adventure Voter Story Index
start: intro

And go from there by building up the chain through next sections in the markdown files.

Open the presenter view on your screen and start sharing the voter URL. As you navigate through your story, decision points will automatically trigger voting sessions. Results appear in real-time, and when voting closes, click continue to follow the winning path.

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

The backend is a Go server handling WebSocket connections and vote aggregation. The frontend uses Alpine.js for reactive UI without heavy frameworks. Voters connect via WebSocket to submit votes, and the presenter view shows real-time results as they come in.

┌──────────────┐
│    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. See Reverse Proxy Setup for a 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)
  • -story: Path to story.yaml (default: content/story.yaml)
  • -presenter-secret: Authentication password (optional; disables auth if empty)

The presenter secret is optional. If set, presenter control endpoints require authentication. This prevents audience members from advancing slides. 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 auth for presenter endpoints, and proper file path sanitization.

Other than that, the bare minimum has been done to achieve security, this isn't a mission-critical application. It is meant to be short-lived.

If WebSocket connections fail, check that your reverse proxy passes upgrade headers correctly and that port 8080 is accessible. Browser developer tools will show WebSocket connection status in the Network tab.

If votes aren't updating, verify the WebSocket connection is established and check the server logs for errors.

If markdown isn't rendering, validate your YAML front-matter syntax and ensure file paths in story.yaml match your actual files.

联系我们 contact @ memedata.com