{"doc":"layout-and-loading","title":"Choose how your game sits on the screen","markdown":"# Choose how your game sits on the screen\n\nDecide two things before you build: where your game will be opened, and how it\noccupies the screen there. They change how the game is built, from how big the\ntext can be to what happens to a long list, and they are expensive to change\nlater.\n\n```sh\nnpx gamestage create --name \"Kick Off\" --format hunt --host app-webview\n```\n\n`create` records both in `gamestage.yaml`, and `verify` holds the game to them.\n\n## Where it runs\n\n| `--host` | Where the game is opened | Default layout |\n| --- | --- | --- |\n| `fullscreen` | Full screen, from a carousel, a Hub or a link. The default | `screen` (`page` for predict) |\n| `article-launch` | A card in an article that opens the game full screen over it | `launch` |\n| `article-embed` | A box inside an article, played where it sits | `card` |\n| `app-webview` | Inside a club's or publisher's own app, in a web view | `screen` (`page` for predict) |\n\n## How it sits on the screen\n\n| `--layout` | What the player gets | Use it for |\n| --- | --- | --- |\n| `screen` | Fills the phone, nothing scrolls, and the layout adapts to the phone's size | Almost every game |\n| `canvas` | Drawn at one size and scaled to fit, with bars where the shape differs | Games that need exact positions |\n| `launch` | A preview in the host page; tapping it opens the game full screen, and closing it returns the reader to where they were | Any game shown in an article |\n| `card` | A fixed box inside a page that scrolls, with no scrolling inside it | Only where the host can't open a game full screen |\n| `page` | Scrolls like a web page | Long question lists and results tables |\n| `sheet` | Slides up over part of a host app | One quick question inside an app |\n\n**Choose full screen.** Players come back to a game they can play properly. Full\nscreen, a game feels like an app and gets played; a widget in an article reads\nas throwaway and gets scrolled past. In an article, use `launch`, and keep\n`card` for a host that can't allow anything else: a box in an article is small,\neasy to scroll past, and the page's own scrolling fights swipes inside the game.\n\nGamestage UI's `Stage` takes the same words as `fit`, so a game built with it\nsays this once.\n\n## Load smoothly\n\nA fan should see your game's colour from the first frame, then a loading mark,\nthen the game in one step. Never a white page, bare \"Loading…\" text, or a board\nthat jumps in.\n\nScaffolds from `create` do all of this already. If you wrote your own page:\n\n1. **Paint the game's colour before any script.** In the head, before the first\n   `<script>`:\n\n   ```html\n   <meta name=\"color-scheme\" content=\"dark\" />\n   <meta name=\"theme-color\" content=\"#0B1B2B\" />\n   <style>html, body { background: #0B1B2B; margin: 0; }</style>\n   ```\n\n2. **Draw a loading state, never text.** A centred spinner, or a progress bar\n   when you know how far through you are, in a box the size of the game.\n   Gamestage UI's `Loading` is exactly this.\n3. **Say when the game is playable.** When the round is drawn, or has failed,\n   call `window.gamestageReady()` (scaffolds define it), or set the mark\n   yourself: `performance.mark(\"gamestage:playable\")`. Remove the loading state\n   then, not before.\n\n## Intro screen\n\nEvery game `create` scaffolds opens on an intro screen: a full-screen colour\ngradient, your game's name in its own font, a progress bar while it loads, then\na Play button. The tap on Play is what starts the game, which is also the\ngesture a browser needs before it will let a page play sound. A game\nscaffolded before this feature shipped keeps its old start.\n\nThe `window.gamestageReady()` call from the previous section is also what\ndrives the intro screen. Call it with an argument to say more than \"ready\":\n\n| Call | What the fan sees |\n| --- | --- |\n| `window.gamestageReady()` | The round is drawn; the button reads \"Play\" |\n| `window.gamestageReady({ resume: true })` | The fan has a round already in progress; the button reads \"Continue\" |\n| `window.gamestageReady({ failed: true })` | The game could not load; the screen shows its error text and a retry button |\n\n**Wait for the tap before you open anything that asks the fan something**, such\nas How to play. `window.gamestageStart` is a promise that resolves on Play, and\n`gamestage:start` is the matching event; opening a dialog before then would be\nasking the fan before they have agreed to start.\n\nThe colours and words are Studio settings, under Brand and Wording. Set them\nthere, or seed them in `gamestage.settings.json` before your first deploy the\nsame as any other field (see [Beside the manifest:\n`gamestage.settings.json`](/docs/app-manifest#beside-the-manifest-gamestage-settings-json));\n`create` already seeds the four button and error labels for you.\n\n| Setting | Field key | What it changes |\n| --- | --- | --- |\n| Intro screen colour, top | `splash_gradient_start` | Top of the gradient, and the colour the page paints before anything else loads. Empty uses your background colour |\n| Intro screen colour, bottom | `splash_gradient_end` | Bottom of the gradient. Empty uses your background container colour |\n| Intro screen button | `splash_play_label` | What Play says. Empty says \"Play\" |\n| Intro screen button, part way through | `splash_continue_label` | What the button says when the fan has an unfinished round. Empty says \"Continue\" |\n| Intro screen, when loading fails | `splash_error_text` | Empty says \"The game didn't load. Check your connection and try again.\" |\n| Intro screen, try again button | `splash_retry_label` | Empty says \"Try again\" |\n\nThe name is drawn in its own typeface here too, from the same Brand settings\nthat style it everywhere else on the page: see [Theming: the title's own\ntypeface](/docs/theming#the-title-s-own-typeface) for the typeface, weight,\nletter spacing, case, colour, and the separate logo text and accent colour\nsettings.\n\n### Inside a native app\n\nTell the app's team two things, because they are in the app rather than your\npage:\n\n- **Set the web view's own background to your game's background colour.** A\n  web view is white until the page paints, so without this the player sees a\n  white flash first.\n- **Create the web view before the player taps**, and load the game into it\n  then. Starting it on the tap adds the whole start-up to the wait.\n\n## Load fast\n\n`gamestage verify` opens your game on a mid-range phone over 4G (the processor\nslowed four times, 9 Mbit/s down, 60 ms round trips, nothing cached) and\nreports what it downloaded and how long it took. Over a budget, it warns and\nnames the heaviest files. It never fails a game for being slow.\n\n| Layout | JavaScript | CSS | Fonts | Everything | First render | Playable |\n| --- | --- | --- | --- | --- | --- | --- |\n| `screen` | 350 KB | 60 KB | 150 KB | 900 KB | 1.8 s | 3.5 s |\n| `canvas` | 500 KB | 60 KB | 150 KB | 1,500 KB | 1.8 s | 4.5 s |\n| `launch` | 300 KB | 50 KB | 120 KB | 700 KB | 1.5 s | 3.0 s |\n| `card` | 300 KB | 50 KB | 120 KB | 700 KB | 1.5 s | 3.0 s |\n| `page` | 400 KB | 80 KB | 150 KB | 1,100 KB | 1.8 s | 4.0 s |\n| `sheet` | 250 KB | 40 KB | 100 KB | 600 KB | 1.2 s | 2.5 s |\n\nSizes are what crossed the network, compressed. First render is when the fan\nfirst sees something that isn't blank, so painting your colour and a loading\nmark from the HTML counts. Playable is your `gamestage:playable` mark, or when\n`verify` first finds the board if you set none.\n\nWhat usually makes a game heavy, and the fix:\n\n- **Fonts**: load one family in two weights, not five, and subset it to the\n  characters the game uses.\n- **Components you don't use**: install only the Gamestage UI components the\n  game draws.\n- **Images**: size them for a phone and use WebP.\n\n`verify` also checks a `screen`, `canvas`, `card` or `sheet` game fits at\n360×640, 390×844 and landscape without the page scrolling. If a list must\nscroll, let it scroll inside the game, never the page.\n"}