
Your game is listed on a portal. The page gets visits. The play count does not move. It is tempting to read that as a verdict on the game — but nobody who left has played it yet. Whatever they rejected, they rejected before the game started. This article measures that window on a live portal, in numbers, and the numbers are worse than we expected.
The short version. On a portal, the first ten seconds of your game are not yours. The host’s CSS decides how large your game is. The host’s page decides how much of it is on screen. Your own first frame decides whether either of those mattered. We measured all three on a live site — our own — and found a game box 330 by 205 pixels on a phone, several seconds of black rectangle before anything was drawn, and a start button that landed at the bottom edge of a laptop viewport.
Everything below was measured on 3 August 2026, in Chrome on Windows, against live pages on slowden.com. We are the portal in this article, which is the point: this is data a developer cannot normally get, because it belongs to the host, and it is easier to publish honestly when the embarrassing parts are your own.
Three games, none of them made by SlowDen:
Layout was measured by loading each live page into a same-origin frame set to an exact viewport size and reading the real bounding boxes, rather than by eyeballing a browser window. Timings come from the Navigation Timing and Resource Timing APIs on the live pages. Byte sizes were re-fetched with cache: 'no-store' and measured from the response body, because a cached resource reports zero bytes in Resource Timing and would have made the totals look far better than they are.
The container is one line of CSS: a full-width box with a fixed 16:10 aspect ratio. That means the height of your game is decided entirely by how wide the page happens to be. Here is the same game page at seven viewport sizes.
| Viewport (CSS px) | Game box | Box height vs viewport | Visible without scrolling | Page above the game |
|---|---|---|---|---|
| 360 × 740 | 299 × 186 | 25% | 100% | 284 px |
| 390 × 844 | 330 × 205 | 24% | 100% | 260 px |
| 412 × 915 | 351 × 219 | 24% | 100% | 260 px |
| 768 × 1024 | 707 × 441 | 43% | 100% | 238 px |
| 1366 × 768 | 1054 × 658 | 86% | 79% | 251 px |
| 1536 × 695 | 1054 × 658 | 95% | 67% | 251 px |
| 1920 × 1080 | 1054 × 658 | 61% | 100% | 251 px |
Two things fall out of that table, and neither is obvious.
On a phone your game is a postage stamp. 330 by 205 CSS pixels is about a quarter of the screen. If your first frame carries a logo, a tagline, a difficulty selector and a start button, none of it is legible. Developers routinely build a title screen in a 1280 by 720 canvas and never look at it at 330 by 205 — but that is the size a large share of players will meet it at.
The worst clipping is not on mobile, it is on a laptop. At 1536 by 695 — an extremely common real-world viewport once browser chrome is subtracted — the box is 658 pixels tall and sits 251 pixels down the page, so a third of your game is below the fold on arrival. The phone cases were not clipped at all, because the box was too small to be clipped.
The design rule this implies. Your first frame has to work in a landscape box roughly 300 pixels wide, and it has to put anything important in the upper middle, because the bottom third may not be on screen. That is a much tighter brief than the one most title screens are designed to.
Here is the load sequence for one game page, taken from Navigation and Resource Timing on the live site:
| Moment | Time from navigation start |
|---|---|
| Host page HTML finished arriving (13,371 bytes decoded) | 154 ms |
| Web fonts finished (5 requests, 98 KB) | 323 ms |
| Host page DOM complete | 323 ms |
| Game frame’s document finished arriving | 1,260 ms |
| Host page load event | 1,264 ms |
On a second game in the same container, the host page fired its load event at 726 ms while the game frame’s own resources were still landing at roughly 4.5 seconds.
So the host page is finished and painted in about a third of a second, and the game is not. What fills the gap is a black box. We reloaded both pages repeatedly and screenshotted: immediately after load, the game area was a solid black rectangle with no play control anywhere on the page, and the partner player’s own splash screen appeared only several seconds later.
This is the same failure we wrote about in the black-screen teardown, seen from the other side. There it was one game’s preload queue. Here it is structural: every embedded game has a window in which the page is done and the game is not, and by default that window is painted black.
You cannot remove that window. You can decide what is in it. A first frame that draws a background colour, the game’s name and a progress indicator within a few hundred milliseconds is doing something no host can do for you, because the host cannot see inside your frame.
You choose where the start control sits inside your frame. The host chooses where the frame sits on the page. Nobody reconciles those two decisions, and the result is measurable.
Two games, same container, same portal, same viewport of 1568 by 688:
Neither studio did anything wrong. Both start screens are perfectly sensible in isolation. The failure only exists in combination — and the combination is the only thing the player ever experiences.
What we changed on our side. Publishing this without saying what we did with it would be cheap. Our game pages now paint their own thumbnail and a real play button immediately, and create the game frame only when someone clicks it, so the page can never again show a black box with no control on it. That change is in our source and is not deployed as of this article’s publication date — the pages measured above are still live in the state described. We will not pretend otherwise.
On the page measured above, the host’s own HTML was 13,371 bytes. Its web fonts were 98 KB across five requests. Those fonts were fully downloaded at 323 ms; your game’s frame document did not finish until 1,260 ms.
You do not control that and you should not try to. But it is worth knowing, because it changes what your own budget is for. The player’s patience is being spent on the portal’s furniture before your first byte arrives, and whatever you were planning to spend on a splash animation is coming out of what is left.
The Cube is not embedded — it is self-hosted on the same domain — so it is not a like-for-like comparison, and it is included for one specific reason: it shows what a fast first frame is actually made of.
| File | Bytes (decoded, re-fetched with no-store) |
|---|---|
| index.html | 7,144 |
| style.css | 35,932 |
| js.js (the whole game) | 101,851 |
| three.min.js (from a CDN) | 646,271 |
| Total | 791,198 ≈ 773 KB |
DOM complete at 292 ms, load event at 303 ms. What lands on screen is a finished, readable frame: the title, a rendered 3D cube, and the words Double tap to start. Four files, no loading bar, nothing to wait for — and note that 82 percent of those bytes are the 3D engine, not the game. The game itself is 102 KB.
The lesson is not “be under 800 KB”. It is that a first frame worth showing can be very cheap, and that shipping one is a decision rather than a budget.
Open the portal page that hosts your game, open the console, and paste this. It measures the page rather than guessing at it.
// Paste into the console on the page that EMBEDS your game.
const f = document.querySelector('iframe');
const r = f.getBoundingClientRect();
const nav = performance.getEntriesByType('navigation')[0];
console.table({
'viewport' : innerWidth + ' x ' + innerHeight,
'your game box' : Math.round(r.width) + ' x ' + Math.round(r.height),
'page above game' : Math.round(r.top) + ' px',
'visible on arrival': Math.round(100 * Math.max(0,
Math.min(r.bottom, innerHeight) - Math.max(r.top, 0)) / r.height) + '%',
'scroll to see all' : Math.max(0, Math.round(r.bottom - innerHeight)) + ' px',
'host DOM done' : Math.round(nav.domContentLoadedEventEnd) + ' ms',
'host load event' : Math.round(nav.loadEventEnd) + ' ms',
'lazy loaded' : f.getAttribute('loading') || 'no'
});
// When did YOUR frame's document actually arrive?
performance.getEntriesByType('resource')
.filter(x => x.name.includes(new URL(f.src).host))
.forEach(x => console.log(Math.round(x.responseEnd) + ' ms', x.name.split('?')[0]));
Run it at a few widths — drag the window narrow, then wide — because the layout changes and the clipping is worst at sizes you would not think to check. If visible on arrival is under 100%, work out what is in the missing part.
cache: 'no-store' and measured from the response body, so they are decoded sizes. On the wire they are compressed and smaller.SlowDen lists browser games and takes submissions from developers. If you have a playable web build, you can send it to us.
Slow Cook is our creative challenge for developers: pick a challenge, build a small game around it, submit it before the round closes on 31 August 2026. Selected entries may receive a shoutout on SlowDen’s social accounts and a chance to be featured — nothing is guaranteed, and every entry is read by a person.