At Creators we build the interactive games that run on top of live streams. A creator drops a browser source into OBS, the game appears on their broadcast, and their chat plays along. Technically it's just a web page. React, a websocket, a lot of SVG.
Between us we've shipped a lot of web apps. We still got most of this wrong the first time.
Nobody watching can click anything
This is the part that took us longest to actually internalise, as opposed to just knowing it.
If ten thousand people are watching a quiz on stream, there is exactly one browser rendering that quiz. It's the creator's. Everyone else is looking at a video of it, encoded and shipped to them somewhere between two and twenty seconds late depending on the platform and their connection.

The game renders once. Everyone else is watching a video of it.
So every reflex we'd built up over years of product work was pointed at a person who isn't there.
Optimistic updates, for one. The whole reason they exist is to hide latency from the person who just clicked. Nobody clicked. There's no perceived latency to paper over, and if the server later disagrees with you, you've just corrected yourself on someone's broadcast in front of their audience.
Spinners are worse. A loading state on a stream isn't "please wait", it's silence the creator has to fill by talking. We've watched that happen and it's genuinely uncomfortable.
And you can't show an error, because who's reading it? A red retry banner sitting on a live broadcast is worse than whatever it's reporting. Half the time the failure would have gone unnoticed. The banner never does.
One connection, games loaded late
We run nine of them now: a spin wheel, a quiz, truth or dare, draw and guess, number guess, fastest fingers, hangman, word scramble and tug of war. Every one is an overlay the creator embeds the same way.
The obvious structure is one overlay app per game. We went the other way. There's a single overlay entry point that opens one realtime connection, then lazily pulls in whichever game the session asked for from a small registry that maps a slug to a component.
Worth being clear about why, because the instinct to split by feature is usually right and this is one of the times it isn't.
Games are heavy and mutually exclusive. Canvas renderers, sound files, confetti, none of which you need until the creator picks that game, and you'll never need two at once. Shipping nine of them up front to run one would be indefensible; lazy loading them costs nothing. The connection is the opposite: small, slow to establish, and the one thing that absolutely cannot blink mid-session. So the socket opens first and the game arrives whenever it arrives.
Booleans didn't survive
The quiz walks through a sequence. Waiting to start, question live, answer reveal, leaderboard, a short countdown, then the next question or the final results.

One value the server owns, not three flags the client juggles.
We started with flags. Is a question showing, is the answer revealed, is the leaderboard up. Three booleans, some conditionals, done.
What killed it wasn't complexity. It was that the overlay can show up in the middle of a game. OBS gets restarted. The browser source reloads after a network hiccup. The page mounts fresh while question four is already counting down, and it has to draw the right thing from whatever the server says is happening at that exact moment.
Eight combinations of three booleans, four of which are nonsense. Versus one named phase that the server owns, where a page that just mounted renders identically to one that's been open for twenty minutes.
It also fixed a bug we'd been chasing where two flags disagreed for a single frame and the leaderboard flashed over a live question. That one showed up on a real broadcast, which is how we found it.
The animation is holding a secret
The spin wheel made this obvious in a way the other games didn't.
The server decides the winner. The wheel then spends a few seconds slowing down before it lands on them. That gap is the entire product. It's the suspense. It's also several seconds where the browser knows the answer and the audience doesn't.
تجربة تفاعلية
جرّبها: العجلة تعرف النتيجة سلفًا
اضغط «أدر». تُحسم النتيجة لحظة الضغط، لا لحظة توقّف العجلة. الثواني التي بينهما مجرّد تشويق.
Which means the animation isn't a transition sitting on top of the state. For as long as it runs, it is the state, and nothing else on screen is allowed to give it away early.
Truth or dare has a quieter version. The prompt card flips in on a spring rotation, and the new text is in the DOM before the card has turned far enough to hide it. Time it badly and the prompt is readable mid-flip, on stream, at whatever resolution the viewer picked.
The rule we ended up with: if an animation runs longer than about a third of a second, ask what someone could learn from the DOM before it finishes. On a normal site, nothing worth worrying about. Here it's the whole game.
The sound bug you will never see locally
The quiz ticks while the timer runs and the pitch climbs as it gets close. The ring goes yellow at five seconds and red at three. Confetti fires on the final results.
That isn't decoration. Audio is how the audience notices something changed while they were reading chat, and a rising pitch is a countdown you can follow without looking at it.
It's also the thing most likely to break in a way that's invisible to you. Browsers block audio until a user gesture, and a browser source in OBS has no user. Ever. Nobody clicks it, nobody ever will. If anything in your audio path assumes an interaction happened, it works perfectly on your machine and is silent on every real stream.
And nobody reports it, because a missing sound doesn't look broken. It just feels flat.
تجربة تفاعلية
اسمعها: العدّ التنازلي كما يسمعه الجمهور
نفس النغمة الموجودة في اللعبة: صافرة قصيرة كل ثانية، وتَرتفع حِدّتها عند آخر ثلاث ثوانٍ. الحلقة تتحوّل إلى الأصفر عند الخامسة والأحمر عند الثالثة.
سمعتَها لأنّك ضغطت. المتصفّح لا يسمح بالصوت قبل تفاعل المستخدم، ومصدر المتصفّح داخل OBS لا يتلقّى أيّ ضغطة أبدًا — ولهذا يعمل الصوت هنا ويصمت على البثّ الحقيقي.
Arabic, in a box that doesn't move
Creators is Arabic-first, so the games are. The quiz can generate its questions in Arabic or English by topic and difficulty.
RTL in an overlay is stricter than RTL on a page and it took us a while to work out why. It's that there's no scroll and no reflow to absorb your mistakes. The overlay is a fixed rectangle sitting on top of live video. A layout that just gets taller in Arabic doesn't push content harmlessly down the page. It pushes it over the creator's face.
Logical properties instead of left and right, obviously. Test at the longest string rather than a typical one, which we knew and still didn't do consistently.
The one that surprised us was leaderboards. Arabic display names sit next to Latin usernames and Western digits, so a single row can switch direction two or three times. The browser gets this right on its own. It stops getting it right the moment you start manually reversing things because a preview looked wrong to you.
What we'd tell someone starting this
Build reconnection before you build the game. A game that plays beautifully and drops once an hour is worse than a boring one that never does, because it fails live and the creator has to explain it to their audience.
Decide early who owns the clock. If it's the client, two overlays will disagree and the timer won't match the answer.
Watch a real broadcast, not your dev overlay on a white background. Thin fonts, subtle borders and low contrast all pass code review and all fall apart in the encoder.
And get comfortable with not being able to ask the user anything. No prompts, no confirmations, no apology toast. I expected that to be the annoying part of the job. It's turned out to be the most useful constraint I've worked under, because the only option left is getting it right before it goes on screen.
