MINIGAMES · EMBED KIT
Minigames Lobby
A self-contained minigames lobby — game catalog plus a live leaderboard — that you drop into any site with a single <iframe>. No SDK, no build step. The UI adapts to the width of the frame you give it, so it looks right in a phone-width sidebar or a full-width page.
Container-aware
Lays out from the width of the iframe, not the visitor's screen. Single column when narrow, a two-column dashboard past ~790px — powered by CSS container queries.
Zero-config embed
One <iframe> pointed at the /lobby/ path. No SDK, no bundler, no build step on your side.
Static & portable
Prerendered static export — plain HTML/CSS/JS. Host it on S3 or any CDN. No server, no runtime dependencies.
Live preview
Both frames below are real embeds of the deployed /lobby/ route, each sized only by its wrapper. Resize your browser to watch the fluid one reflow — the layout follows the frame's width, not the screen.
In a narrow frame
Same component, same code — the layout responds to the frame's own width:
- Between 448px and 790px the leaderboard moves below the game catalog, so the two stack into a single column.
- Below 448px the phone layout takes over: the catalog becomes a one-card carousel.
LOBBYandLEADERBOARDtabs then switch between the two.- Nothing is cropped — the frame scrolls when the layout needs more height than you gave it.
Embed it
Point the iframe at your deployed lobby URL. That's the whole integration.
<iframe
src="https://your-host.example.com/lobby/"
title="Minigames Lobby"
width="100%"
height="720"
loading="lazy"
style="border: 0; display: block;"
></iframe>export const MinigamesLobby = () => (
<iframe
src="https://your-host.example.com/lobby/"
title="Minigames Lobby"
loading="lazy"
className="block h-[720px] w-full border-0"
/>
);<!-- Narrow slot? The lobby detects its own width and
switches to the phone layout — no config needed. -->
<div style="width: 380px">
<iframe
src="https://your-host.example.com/lobby/"
title="Minigames Lobby"
width="100%"
height="720"
loading="lazy"
style="border: 0"
></iframe>
</div>Events
You cannot see inside the frame, so the lobby tells you when a player starts a game and when they come back out. Two postMessage events on window.parent — no SDK, nothing to opt into.
- game-opened
- A player tapped PLAY NOW and the game is taking over the frame. Carries game.title.
- game-closed
- They left the game and the lobby is back. Fires however they got out — the close control, or Escape in the windowed layout.
window.addEventListener("message", (event) => {
// Only trust messages coming from the host you embedded.
if (event.origin !== "https://your-host.example.com") {
return;
}
const message = event.data;
if (message?.source !== "oddin-minigames") {
return;
}
if (message.type === "game-opened") {
// A player started message.game.title: pause your own carousel,
// hide a sticky header, hold off a live refresh …
return;
}
if (message.type === "game-closed") {
// They are back in the lobby — undo whatever you did above.
}
});Every message carries source: "oddin-minigames", so it is safe to filter on that and ignore the rest of your page's postMessage traffic. The target origin is "*" — the lobby is a static export and does not know who embeds it — so check event.origin against your lobby host, as the snippet does.
Sizing & behavior
- Width
- Set it to the width of your slot — width="100%" is usually what you want. From about 790px the catalog and the leaderboard sit side by side; below that they stack into one column, and below 448px you get the phone layout.
- Height
- An iframe can't grow to its content on its own, so give it a height: 720 is a good default, and the lobby fills whatever it gets. The two-column layout needs about 700px to fit; the stacked one about 1240px.
- Scrolling
- A frame shorter than the layout needs scrolls inside itself — your page never grows to fit the embed. Nothing else is required: the lobby has no chrome of its own, so no permissions to grant.
- Path
- Reference the deployed /lobby/ URL — mind the trailing slash: the static export serves /lobby/index.html.
- Language
- Add ?lang= after the trailing slash — /lobby/?lang=ru — and the lobby, its game tiles, the games' loading screens and the games themselves follow it. en (the default) and ru are supported; anything else falls back to en. Game titles are artwork and stay as they are.
- Session
- Put the player's session token in the URL's hash — /lobby/#session=<token> — and the lobby passes it on to the game it starts in that game's own hash. Keeping it out of the query means it is never sent to a server, so it stays out of access logs and referrers on both hops; combine it with ?lang= as /lobby/?lang=ru#session=<token>. Without it the lobby and the games still run, unauthenticated. The token is read once when the lobby loads, and changing only the hash does not reload a frame — to hand over a new one, reload the iframe itself (remount it, or call contentWindow.location.reload()).
- Accessibility
- Always set a title. Add loading="lazy" so the embed never blocks your page load.
Deployment. The lobby ships as a static export (plain HTML/CSS/JS). Host it on S3 static website hosting or any CDN — index document index.html, error document 404.html. Serve /lobby/ from the same host you embed from (or reference its absolute URL) and you're done.