RestrictedScene
The page you land on when something says no.
Part of
@aora-bot/surfaces, not@aora-bot/react:pnpm add @aora-bot/surfaces @aora-bot/react, and import@aora-bot/surfaces/styles.cssafter the React stylesheet.
Held back
This action tried to reach something it is not allowed to touch.
Where a character costs least
A dead end is where a character is worth the most and costs the least: nobody is trying to get work done on this screen, and a face that is clearly declining rather than broken is the difference between "I am not allowed to do that" and "this product is failing". The default state is refusing.
The shape changes every rotateMs (12 s by default) because these screens get stared at. The interval is long enough to read as the character shifting position rather than as an animation demanding attention, and it stops entirely under reduced motion — a silhouette morph is motion even when nothing crosses the screen. shapePool is the set it rotates through (AUTO_SHAPE_POOL by default); rotateMs={0} holds the first.
Tones
tone picks the copy and the bezel light: restricted (alert light), offline (light off) and unauthorised (alert light). title and body override the copy for a specific message; action takes the way out — a button, a link, whatever applies.
DeviceBezel
The frame around the character: a rounded shell, an inset screen and a status light, all procedural. At these sizes a screenshot of a window would need three variants — dark mode, high density, a narrow column — and would pin the surface to whatever operating system was on screen when it was taken.
aspect is the screen's height as a fraction of its width, 0.74 by default — just short of 3:4, like a small display. Children are drawn on the screen and clipped to it. The same frame is what BootLoop draws around its character.
API
| Prop | Type | Default | Description |
|---|---|---|---|
action | string | number | bigint | boolean | ReactElement<unknown, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | Promise<AwaitedReactNode> | null | — | Buttons, links — whatever the way out of here is. |
body | string | — | — |
className | string | — | — |
color | "black" | "brown" | "red" | "orange" | "yellow" | "green" | "cyan" | "blue" | "violet" | "magenta" | "pink" | "gray" | — | — |
face | "cute" | "eyes" | "line" | — | — |
lineWeight | number | — | — |
nose | "slant" | "angular" | "hook" | "long" | "curve" | "bow" | null | — | — |
rotateMs | number | 12_000 | Time on each shape. 0 holds the first one. |
shape | "mochi" | "circle" | "blob" | "pebble" | "egg" | "bun" | "tofu" | "pill" | "hex" | "onigiri" | "gem" | "cloud" | "drop" | "leaf" | "flower" | "clover" | "sprout" | "mushroom" | "acorn" | "cactus" | "pear" | "apple" | "strawberry" | "cherry" | "star" | "heart" | "flame" | "bell" | "ghost" | "bot" | "crescent" | "boba" | "fish" | "octopus" | "cat" | "bunny" | "bear" | "chick" | "penguin" | "pig" | "fox" | "bob" | "topknot" | "spiky" | "curly" | "twinbuns" | — | One fixed shape. Wins over `shapePool` and stops the rotation, for a host that wants its own character on the screen rather than a parade. |
shapePool | readonly ("mochi" | "circle" | "blob" | "pebble" | "egg" | "bun" | "tofu" | "pill" | "hex" | "onigiri" | "gem" | "cloud" | "drop" | "leaf" | "flower" | "clover" | "sprout" | "mushroom" | "acorn" | "cactus" | "pear" | "apple" | "strawberry" | "cherry" | "star" | "heart" | "flame" | "bell" | "ghost" | "bot" | "crescent" | "boba" | "fish" | "octopus" | "cat" | "bunny" | "bear" | "chick" | "penguin" | "pig" | "fox" | "bob" | "topknot" | "spiky" | "curly" | "twinbuns")[] | AUTO_SHAPE_POOL | Shapes the character rotates through, when `shape` is not given. |
sizePx | number | — | — |
state | "idle" | "sleeping" | "drowsy" | "bored" | "waking" | "powering-down" | "listening" | "thinking" | "searching" | "reading" | "writing" | "working" | "loading" | "progress" | "radar" | "orbit" | "dictating" | "uploading" | "sending" | "receiving" | "messaging" | "waiting" | "spawning" | "composing" | "dragging" | "happy" | "excited" | "laughing" | "playful" | "proud" | "celebrate" | "bouncing" | "shy" | "sad" | "angry" | "confused" | "suspicious" | "surprised" | "scared" | "curious" | "humming" | "alerting" | "notifying" | "refusing" | 'refusing' | — |
style | CSSProperties | — | — |
title | string | — | Override the copy the tone would supply. |
tone | RestrictedTone | 'restricted' | — |
widthPx | number | 280 | — |
DeviceBezel
| Prop | Type | Default | Description |
|---|---|---|---|
aspect | number | 0.74 | Height as a fraction of the width. Just short of 3:4, like a small display. |
children | string | number | bigint | boolean | ReactElement<unknown, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | Promise<AwaitedReactNode> | null | — | Drawn on the screen, clipped to it. |
className | string | — | — |
light | BezelLight | 'on' | — |
style | CSSProperties | — | — |
widthPx | number | 280 | Outer width. Defaults to 280, which is what both scenes in the kit use. |
Next
- Refusals and limits — declining without looking broken
- States — every animation state, grouped