ATProto PDX stream scenes#
One procedural background for the whole meetup stream: a Truchet tile board of winding
paths that quietly rearranges itself, one snappy rose tile-flip at a time. A single
self-contained page (index.html, fonts embedded, no network needed) watches
OBS state through the injected window.obsstudio API and recasts itself per scene:
- Starting Soon — copy block with date/time, lively cadence (a flip every second or so)
- Thank You — copy block with a sign-off note, the board winding down
- Background — no text, full-frame board at a middle cadence, for layering slides or screen shares on top
When the stream goes live (obsStreamingStarted), the board plays a wavefront of flips
radiating from center and the text replays its entrance. The text entrance also plays on
scene changes — but not on page load, so the animation is saved for moments viewers
actually see, and the source can stay loaded all stream without cold canvas starts.
OBS setup#
Add one Browser Source and reuse it in every scene that wants the board:
- Leave Local file unchecked and put the file path in URL so you can pass
parameters, e.g.
file:///home/graham/atproto-pdx-scenes/index.html?when=Thursday, July 16 · 6:30 PM(file:///C:/Users/graha/...on Windows) - Width 1920, Height 1080
- FPS 30 (check "Use custom frame rate") — the page also caps itself at 30
- Leave Shutdown source when not visible and Refresh browser when scene becomes active unchecked — the page is designed to stay resident and react to events
- Set Page permissions to at least "Read access to OBS status information" so the page receives scene and streaming events
Scene name mapping#
On every obsSceneChanged, the page matches the new scene's name (case-insensitive
substring) to pick its mode:
| Mode | Default keywords | Override param |
|---|---|---|
| Starting Soon | soon, intro, preshow, pre-show |
?soonMatch=a,b,c |
| Thank You | thank, outro, end, bye |
?thanksMatch=a,b,c |
| Background | anything else | — |
So scenes named "Starting Soon", "Intro", "Thanks + Outro" just work; if your scene names don't fit, pass your own keyword lists.
Text parameters#
All text is set via URL query parameters; no code edits on event night.
Pass an empty value (e.g. &handle=) to hide that line.
| Param | Default | Used in |
|---|---|---|
title |
ATProto PDX |
both text modes |
handle |
@pdx.atproto.camp |
both text modes |
status |
Starting soon |
Starting Soon |
when |
(hidden) | Starting Soon — pass explicitly, e.g. ?when=Thursday, July 16 · 6:30 PM |
thanksStatus |
Thanks for watching |
Thank You |
note |
See you at the next one. |
Thank You |
mode |
soon |
initial mode before any OBS event, and the mode used outside OBS (soon/thanks/bg) |
Live chat#
In the Starting Soon and Thank You modes, the page shows the stream.place chat for the
channel in a column at bottom-right. It subscribes to a
Jetstream firehose for
place.stream.chat.message records and keeps the ones whose streamer DID matches the
channel. Author DIDs resolve through their DID document (PLC directory or did:web) to
get the handle plus PDS, then the chatter's place.stream.chat.profile is fetched for
their chosen name color (dark colors are lifted to a legibility floor for the slate
background; chatters without one get the house rose). Everything is cached per DID.
Record deletions on the firehose remove the message from the overlay, so moderation
actions are honored. The last 8 messages show, oldest fading out through a mask.
Live-only: messages sent before the page loaded don't appear.
Chat requires network access from the browser source; if offline, it reconnects with backoff and the rest of the scene is unaffected.
| Param | Default | |
|---|---|---|
channel |
pdx.atproto.camp |
the streamer whose chat to show (handle or DID) |
jetstream |
wss://jetstream2.us-east.bsky.network/subscribe |
alternate Jetstream instance |
chat |
on |
pass chat=off to disable entirely |
Previewing outside OBS#
Open the page in any browser (python -m http.server 8143, then
http://localhost:8143/). Without window.obsstudio the entrance plays on
load, and keyboard controls stand in for OBS events:
| Key | Action |
|---|---|
1 / 2 / 0 |
switch to Starting Soon / Thank You / Background |
r |
small ripple |
l |
simulate going live: board-wide ripple + text entrance |
c |
inject a demo chat message |
window.__spChat in the console shows chat connection state (connected, resolved
channel DID, messages shown/dropped) for debugging.
If the host machine struggles#
The scene is light (144 arc tiles redrawn at a capped 30fps, most frames identical), but if rehearsal shows the encoder fighting for headroom, screen-record 60–90 seconds and swap the Browser Source for a looping Media Source — same look, decode-only cost (you lose the OBS-event reactivity).
Design notes#
Palette, typography, and motion rules live in DESIGN.md; strategic context in PRODUCT.md.