# Sound

A CSS custom property's value as the text it names: quotes removed, or the inside of `url(…)`. Empty when unset.

- Registry name: `sound` (`@candor/sound`)
- Tier: Free (MIT). No licence key needed.
- Docs: https://candor.themethodic.com/docs/sound

## Install

```bash
npx candor-ui add sound
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/sound`.

## Functions

### soundString

```ts
soundString(raw: string)
```

### readSoundVolume

```ts
readSoundVolume(event: SoundEvent, element?: Element | undefined)
```

How loud `event` is, 0 to 1: the global `--cd-sound-volume` times the event's own, each 1 when unset. 1 on the server.

### readSoundToken

```ts
readSoundToken(event: SoundEvent, element?: Element | undefined)
```

The brand's sound for `event`, read from the computed style of `element` (the document's root when left out), or `undefined` when the brand names none, or on the server.

### mayPlay

```ts
mayPlay(event: SoundEvent, last: Partial<Record<SoundEvent, number>>, now: number, burstMs = 400)
```

Whether a play at `now` is allowed, given when each event last played and when anything last played: the same event waits `soundCoalesceMs` after it played, and any sound in the same instant (a burst) is the one already playing. Pure; the caller keeps the clock.

## Types

### SoundEvent

The five things the app may make a sound for. A closed list: `reply` and `silent` have no `AttentionKind` until 3.0.

```ts
"needs-you" | "finished" | "failed" | "reply" | "silent"
```

### SoundToken

A sound as the brand names it: where the file is, an optional Ogg alternative, and how loud (0 to 1).

```ts
{ src: string; ogg?: string | undefined; volume: number }
```

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider` (fixed strings) and `TermsProvider` (nouns); entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
