# Companion

A face with eyes that morph between expressions, blink and look around.

## Usage

It runs on its own. Give it a state and it picks the expression and blinks at
its own pace.

```tsx
import { Companion } from "@oscarkalid/ui";

<Companion state="idle" size={140} />
```

The eyes are cut out of the body, so whatever the face sits on shows through
them. Colour comes from the text colour, so `text-accent` or any other token
sets it.

## States

A state carries a pool of expressions, how often to move between them, and how
often to blink. Left alone it cycles the pool on its own.

`sleeping`, `waking`, `idle`, `listening`, `thinking`, `searching`,
`working`, `excited`, `curious`, `happy`, `sad`, `angry`, `surprised`,
`confused`.

`sleeping` does not blink.

## Expressions

Pass an expression to hold one and stop the pool cycling. Changing it morphs
point by point rather than cutting.

Twenty five are hand drawn and stored as coordinates. The rest are generated by
deforming an ellipse. `EXPRESSION_NAMES` lists them all and
`EXPRESSION_NOTES` describes each one.

```tsx
<Companion expression="pondering" />
```

The drawn expressions sit higher and further right on the face than the
generated ones, so moving between the two sets shifts the eyes as well as
reshaping them. That is how the original drew them.

## Colour

A token rather than a class, because `cn` joins class names rather than
merging them: a text colour passed in would sit alongside the default and let
the stylesheet's order decide which wins.

`accent`, `ink`, `ok`, `warn`, `danger`, `info`, `current`.

`current` inherits the text colour instead, for the cases a token does not
cover.

```tsx
<Companion tone="ok" />
```

## Shapes

The silhouette the eyes are cut out of. Each one scales and drops the face to
keep the eyes inside it.

`blob`, `heptagon`, `hexagon`, `pentagon`, `triangle`.

```tsx
<Companion shape="hexagon" />
```

## Props

| Prop | Type | Default |
|---|---|---|
| `state` | `CompanionState` | `"idle"` |
| `expression` | `CompanionExpression` | picked from the state's pool |
| `size` | `number` | `96` |
| `tone` | `CompanionTone` | `"accent"` |
| `shape` | `CompanionShape` | `"blob"` |
| `label` | `string` | none, and the face is hidden from screen readers |

Leave `label` off where the face is decoration. Set it where the face is the
only thing carrying the meaning.

## Reduced motion

Under `prefers-reduced-motion` the eyes stop blinking and drifting, and an
expression change arrives rather than travels.

## Source

Every eye is a ring of 48 points in a fixed order, starting at the same angle
and winding the same way. That is what lets any expression morph into any
other, and a ring built any other way folds through itself halfway.

```sh
packages/ui/src/components/Companion.tsx
```

https://github.com/yajesta/ui/blob/main/packages/ui/src/components/Companion.tsx
