# Choose how your game sits on the screen

Decide two things before you build: where your game will be opened, and how it
occupies the screen there. They change how the game is built, from how big the
text can be to what happens to a long list, and they are expensive to change
later.

```sh
npx gamestage create --name "Kick Off" --format hunt --host app-webview
```

`create` records both in `gamestage.yaml`, and `verify` holds the game to them.

## Where it runs

| `--host` | Where the game is opened | Default layout |
| --- | --- | --- |
| `fullscreen` | Full screen, from a carousel, a Hub or a link. The default | `screen` (`page` for predict) |
| `article-launch` | A card in an article that opens the game full screen over it | `launch` |
| `article-embed` | A box inside an article, played where it sits | `card` |
| `app-webview` | Inside a club's or publisher's own app, in a web view | `screen` (`page` for predict) |

## How it sits on the screen

| `--layout` | What the player gets | Use it for |
| --- | --- | --- |
| `screen` | Fills the phone, nothing scrolls, and the layout adapts to the phone's size | Almost every game |
| `canvas` | Drawn at one size and scaled to fit, with bars where the shape differs | Games that need exact positions |
| `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 |
| `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 |
| `page` | Scrolls like a web page | Long question lists and results tables |
| `sheet` | Slides up over part of a host app | One quick question inside an app |

**Choose full screen.** Players come back to a game they can play properly. Full
screen, a game feels like an app and gets played; a widget in an article reads
as throwaway and gets scrolled past. In an article, use `launch`, and keep
`card` for a host that can't allow anything else: a box in an article is small,
easy to scroll past, and the page's own scrolling fights swipes inside the game.

Gamestage UI's `Stage` takes the same words as `fit`, so a game built with it
says this once.

## Load smoothly

A fan should see your game's colour from the first frame, then a loading mark,
then the game in one step. Never a white page, bare "Loading…" text, or a board
that jumps in.

Scaffolds from `create` do all of this already. If you wrote your own page:

1. **Paint the game's colour before any script.** In the head, before the first
   `<script>`:

   ```html
   <meta name="color-scheme" content="dark" />
   <meta name="theme-color" content="#0B1B2B" />
   <style>html, body { background: #0B1B2B; margin: 0; }</style>
   ```

2. **Draw a loading state, never text.** A centred spinner, or a progress bar
   when you know how far through you are, in a box the size of the game.
   Gamestage UI's `Loading` is exactly this.
3. **Say when the game is playable.** When the round is drawn, or has failed,
   call `window.gamestageReady()` (scaffolds define it), or set the mark
   yourself: `performance.mark("gamestage:playable")`. Remove the loading state
   then, not before.

## Intro screen

Every game `create` scaffolds opens on an intro screen: a full-screen colour
gradient, your game's name in its own font, a progress bar while it loads, then
a Play button. The tap on Play is what starts the game, which is also the
gesture a browser needs before it will let a page play sound. A game
scaffolded before this feature shipped keeps its old start.

The `window.gamestageReady()` call from the previous section is also what
drives the intro screen. Call it with an argument to say more than "ready":

| Call | What the fan sees |
| --- | --- |
| `window.gamestageReady()` | The round is drawn; the button reads "Play" |
| `window.gamestageReady({ resume: true })` | The fan has a round already in progress; the button reads "Continue" |
| `window.gamestageReady({ failed: true })` | The game could not load; the screen shows its error text and a retry button |

**Wait for the tap before you open anything that asks the fan something**, such
as How to play. `window.gamestageStart` is a promise that resolves on Play, and
`gamestage:start` is the matching event; opening a dialog before then would be
asking the fan before they have agreed to start.

The colours and words are Studio settings, under Brand and Wording. Set them
there, or seed them in `gamestage.settings.json` before your first deploy the
same as any other field (see [Beside the manifest:
`gamestage.settings.json`](/docs/app-manifest#beside-the-manifest-gamestage-settings-json));
`create` already seeds the four button and error labels for you.

| Setting | Field key | What it changes |
| --- | --- | --- |
| 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 |
| Intro screen colour, bottom | `splash_gradient_end` | Bottom of the gradient. Empty uses your background container colour |
| Intro screen button | `splash_play_label` | What Play says. Empty says "Play" |
| Intro screen button, part way through | `splash_continue_label` | What the button says when the fan has an unfinished round. Empty says "Continue" |
| Intro screen, when loading fails | `splash_error_text` | Empty says "The game didn't load. Check your connection and try again." |
| Intro screen, try again button | `splash_retry_label` | Empty says "Try again" |

The name is drawn in its own typeface here too, from the same Brand settings
that style it everywhere else on the page: see [Theming: the title's own
typeface](/docs/theming#the-title-s-own-typeface) for the typeface, weight,
letter spacing, case, colour, and the separate logo text and accent colour
settings.

### Inside a native app

Tell the app's team two things, because they are in the app rather than your
page:

- **Set the web view's own background to your game's background colour.** A
  web view is white until the page paints, so without this the player sees a
  white flash first.
- **Create the web view before the player taps**, and load the game into it
  then. Starting it on the tap adds the whole start-up to the wait.

## Load fast

`gamestage verify` opens your game on a mid-range phone over 4G (the processor
slowed four times, 9 Mbit/s down, 60 ms round trips, nothing cached) and
reports what it downloaded and how long it took. Over a budget, it warns and
names the heaviest files. It never fails a game for being slow.

| Layout | JavaScript | CSS | Fonts | Everything | First render | Playable |
| --- | --- | --- | --- | --- | --- | --- |
| `screen` | 350 KB | 60 KB | 150 KB | 900 KB | 1.8 s | 3.5 s |
| `canvas` | 500 KB | 60 KB | 150 KB | 1,500 KB | 1.8 s | 4.5 s |
| `launch` | 300 KB | 50 KB | 120 KB | 700 KB | 1.5 s | 3.0 s |
| `card` | 300 KB | 50 KB | 120 KB | 700 KB | 1.5 s | 3.0 s |
| `page` | 400 KB | 80 KB | 150 KB | 1,100 KB | 1.8 s | 4.0 s |
| `sheet` | 250 KB | 40 KB | 100 KB | 600 KB | 1.2 s | 2.5 s |

Sizes are what crossed the network, compressed. First render is when the fan
first sees something that isn't blank, so painting your colour and a loading
mark from the HTML counts. Playable is your `gamestage:playable` mark, or when
`verify` first finds the board if you set none.

What usually makes a game heavy, and the fix:

- **Fonts**: load one family in two weights, not five, and subset it to the
  characters the game uses.
- **Components you don't use**: install only the Gamestage UI components the
  game draws.
- **Images**: size them for a phone and use WebP.

`verify` also checks a `screen`, `canvas`, `card` or `sheet` game fits at
360×640, 390×844 and landscape without the page scrolling. If a list must
scroll, let it scroll inside the game, never the page.
