# The Stage

Your cart gets the whole window. `useStage()` tells you how big it is, in logical pixels, and re-renders when that changes, so a layout built from flexbox and percentages reflows to fit.

```tsx
import { useStage } from "oj"

const { width, height } = useStage()
```

Pointer events, `input.mouse.position` and touch positions are all in those same window pixels, so a layout and a hit test agree without any conversion.

There is nothing to declare. A `stage` left in an older `oj.json` is not read.

## A world takes its size from the stage

Anything with a simulation in it (physics, a flock, a particle field) runs against bounds, and those bounds should be the stage rather than a number you wrote down. `usePhysics` builds its walls around the host element you hand it, so sizing that element from `useStage()` is the whole of it:

```tsx
const stage = useStage()
const box = useRef(null)
const world = usePhysics(box, { gravity: [0, 980], bounds: true, bodies })

return <View ref={box} style={{ width: stage.width, height: stage.height }}>...</View>
```

Place things as fractions of `stage.width` and `stage.height`, not in units of the size you happened to design at.

**The world is built once**, on mount, and is not rebuilt when the window changes, since rebuilding drops every body's velocity. To follow a resize, remount by keying on the size:

```tsx
<World key={`${stage.width}x${stage.height}`} />
```

## A fixed board

Some carts are not apps. A board, a puzzle, an arcade playfield: you lay it out once at a size that suits it and every coordinate means the same thing forever. Lay it out at that size and scale it to fit the window:

```tsx
import { useStage, View } from "oj"

const W = 960, H = 540   // the board, in board units

// A fixed W x H board, scaled to fit the window and centred.
function useBoard() {
    const { width, height } = useStage()
    const scale = Math.min(width / W, height / H)
    const left = (width - W * scale) / 2, top = (height - H * scale) / 2
    return {
        scale,
        style: { position: "absolute", left: (width - W) / 2, top: (height - H) / 2, width: W, height: H, scale } as const,
        toBoard: (p: { x: number; y: number }) => ({ x: (p.x - left) / scale, y: (p.y - top) / scale }),
    }
}

function Game() {
    const board = useBoard()
    return <View style={board.style}>{/* everything laid out in 960 by 540 units */}</View>
}
```

`left` and `top` use the unscaled size because a scale is applied around the element's centre.

Positions then need one step to reach board units:

- **A handler on an element inside the board** needs nothing. UI Toolkit picks through the scale.
- **`e.localX` and `e.localY`** are window pixels relative to the element. Divide by `board.scale`.
- **`e.x` and `e.y`, `input.mouse.position` and touch positions** are window pixels. Pass them through `board.toBoard(p)`.
- **Deltas**, such as a drag's movement, divide by `board.scale`. `useSwipe` thresholds stay in window pixels.

The space around the board shows whatever is behind it. For a colour there, put a full-window `View` with a `backgroundColor` behind the board.

## Fullscreen

Fullscreen gives your cart a bigger window and nothing else changes: `useStage()` reports the new size and the layout follows. The page around the cart owns the fullscreen request, because the browser requires a user gesture and the permission lives on that side of the frame boundary.
