# Gamestage UI

Gamestage UI is a component library for the interface a live game wears: the
choices, the score, the timer, the rounds, the streaks, the rewards and the
leaderboard. It renders the game around the action rather than owning the
action, so the video, the 3D scene or the canvas stays yours.

Components are copied into your project rather than installed as a dependency.
You get the source, you edit it, and nothing upgrades under you.

## Install a component

Gamestage UI serves from a private registry, so it needs a token. The token is a
GitHub token with read access to `gamestageai/gamestage-ui`, and `gh auth token`
prints one.

```sh
export GAMESTAGE_REGISTRY_TOKEN=$(gh auth token)
npx gamestage-ui add choice
```

That writes the component and its stylesheet into `components/gamestage/`,
brings in whatever it depends on, and installs any npm packages it needs with
the package manager your lockfile names. A file that is already there is left
alone unless you pass `--overwrite`, and the command prints every path it wrote.

**If you get "not found", check the token before you hunt the component.** A
request without one returns 404 rather than 403, so a missing token looks
exactly like a missing component.

| Command | What it does |
| --- | --- |
| `npx gamestage-ui add <name...>` | Copies components in, with what they depend on |
| `npx gamestage-ui list` | Prints every component the registry serves |
| `npx gamestage-ui init` | Writes `gamestage.json`, which pins the registry URL |

`init` is optional. The registry URL is built in, and `gamestage.json` matters
only when you want to point at a different one. The token is never written to a
file.

## A question on screen

```tsx
import { useState } from 'react'
import { Choice, type ChoiceResult } from '@/components/gamestage/choice'

export function Question() {
  const [result, setResult] = useState<ChoiceResult>()

  return (
    <Choice
      label="Which driver led the most laps?"
      presentation="buttons"
      commitOnSelect
      result={result}
      options={[
        { id: 'ver', label: 'Verstappen' },
        { id: 'nor', label: 'Norris' },
        { id: 'lec', label: 'Leclerc' },
      ]}
      onCommitted={(ids) => setResult({ [ids[0]]: ids[0] === 'ver' ? 'correct' : 'wrong' })}
    />
  )
}
```

That renders three answers, records the pick and shows the outcome you hand
back. The same component renders as cards, a list, a wheel or an overlay over a
live scene by changing `presentation`, because a quiz grid and a card picker are
one choice wearing different clothes.

## What it will not do

This is the part that saves time later. Gamestage UI performs none of these and
will not start.

| It does not | You do |
| --- | --- |
| Sign anyone in | Pass the player you already have |
| Store progress | Persist it wherever you keep state |
| Call your API | Call it from the callbacks |
| Decide who won | Settle the outcome and pass it back |
| Ask for consent | Render your own consent surface |
| Send analytics | Forward the callbacks to your own tracking |

The boundary is enforced rather than promised: no component imports anything but
React, and a gate check fails the build if one reaches for `fetch`, storage, a
socket or a cookie.

**This is why it sits beside the Engine rather than inside it.** Gamestage's
Engine decides whether a play was any good and holds the score. Gamestage UI
draws what the Engine decided. Neither knows about the other, and you can use
either on its own.

## Theming

Every colour, size, duration and typeface is a token. Components read tokens and
never define their own, so changing a token changes every surface at once.

```html
<html data-gs-theme="arcade" data-gs-context="light">
```

A theme changes the palette, the typefaces, the corner radius, the edge
treatment, and what a control does when you press it: travel on an offset base,
scale, or hold still and shift colour instead. Three themes ship today: arcade,
paper and stadium.

## Selecting on it from outside

Components describe themselves with `data-gs-*` attributes, so your code and
your tests find things without depending on class names or on the words on
screen. A theme changes the first and a translation changes the second.

```css
[data-gs-part="label"]                       /* every label, everywhere */
[data-gs-scope="stat"][data-gs-part="label"] /* only a stat's */
[data-gs-state="wrong"]                      /* a settled wrong answer */
```

Those attributes are public API. One is documented before it is emitted, and a
gate check fails the build when the documentation and the components disagree.

## What the registry serves

**Grouped by what it's for.** The groups are the library's own: each item
declares its category, and the Gallery at
[ui.gamestage.ai](https://ui.gamestage.ai/) reads the same field, so what you
see there and what you read here cannot disagree. New items ship often; treat
this as a sample rather than a count.

| Group | Items |
| --- | --- |
| Controls | `button`, `choice`, `selector`, `slider`, `toggle` |
| Player | `identity`, `player-card`, `presence`, `profile` |
| Game state | `hud`, `lives`, `multiplier`, `round`, `score`, `stat`, `streak`, `timer` |
| Progress | `level-ring`, `milestones`, `progress-bar`, `quest`, `segments` |
| Rewards | `inventory`, `reward` |
| Competition | `leaderboard`, `podium`, `versus` |
| Charts | `chart` (bar, line, area, radial) |
| Flow and guidance | `coach-mark`, `onboarding`, `tip`, `tutorial-step` |
| Media | `media-frame`, `story` |
| Game feel | `feedback` |
| Composition | `nav`, `stage` |
| Application surfaces | `code-entry`, `consent`, `discovery`, `empty-state`, `error-state`, `field`, `form`, `login`, `prize-entry`, `recovery-code`, `sponsor` |
| Foundations | `adapter`, `cues`, `fonts`, `fonts-arcade`, `fonts-paper`, `theme-arcade`, `theme-paper`, `theme-stadium`, `tokens` |

`glyphs` is in the registry, ungrouped.

You rarely name the last two rows. Application surfaces are whole screens rather
than parts of one, and Foundations are the tokens, typefaces, themes and the
host adapter that other items pull in for themselves, so asking for `choice`
brings what it needs without you listing it.

`npx gamestage-ui list` prints what the registry holds today, which is the list
to trust over this one.

Three things arrived in the registry on 28 September. `nav` takes
`presentation="glass"` and `labels="hidden"` for an icon-only bar. `story` takes
`gestures` (tap, swipe across, swipe down to close) and `fullscreen`. And every
pressable control presses down under a thumb on an iPhone and never selects its
words.
