# Piece List (Moonarc)

The issues, episodes, lessons or videos of one person's work, as a list of rows in the order you give them (newest first). Each row has its number in the mono face, a picture, the title as the row's link, the date and the length. The picture is the piece's own: a waveform drawn from its title (look="wave", for audio), a drawing on a colour block (look="tile") or a frame with a play mark and the running time (look="frame", for video). A pointer on a row tints it, sets its waveform or its drawing moving and swells a frame's play mark, and with detail each row also carries its summary. A piece with audio gets a play button: one small script plays the file in the page, one piece at a time, and shows how much is played on its waveform. Without audio the script has nothing to bind and every row is a link.

- Pro block. Install: `npx shadcn@latest add @moonarc-pro/piece-list` (license key required)
- Tier B · category section · trigger hover, click
- Readout: `<PieceList look="wave" pieces={[{ title, href, number, date, audio }]} />`
- Browser support: widely (Chrome 105 · Firefox 121 · Safari 16); elsewhere: without :has() the row's link shows no focus ring; without container units the titles keep the page's text size and every row stays in its narrow layout, the date under the words
- Measured cost: 893 B raw JS · 510 B gzip · + runtime (with dependencies 2.6 kB raw; CSS 19.5 kB raw)
- Page: https://moonarc.dev/components/piece-list/

## Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `pieces` | `{ title, href, number?, date?, length?, summary?, id?, art?, block?, audio? }[]` | none | The pieces, in the order shown. href is where it is read, heard or watched. number is set in the mono face before the title ("No. 42", "Episode 18"); length is "8 min read" or "48 min". id is an anchor a link elsewhere can point at, and the row is tinted when it is the target. art is the drawing for look="tile" and "frame", picked from the title when unset; block is the picture's colour (the first four in turn when unset). audio is a file to play in the page, for look="wave". |
| `look` | `'wave' | 'tile' | 'frame'` | `'tile'` | The picture of each row: wave draws a waveform from the title, tile a Motif drawing on a colour block, frame a 16:10 video frame with a play mark and the running time. |
| `eyebrow` | `string` | none | A short line above the heading. Slot "eyebrow" wins. |
| `headline` | `string` | none | The heading. Slot "headline" wins and may hold an <em>, set in the emphasis face and colour. |
| `copy` | `string` | none | A line under the heading. |
| `more` | `{ href: string; label: string }` | none | A link beside the heading, e.g. to the whole archive. |
| `detail` | `boolean` | `false` | Show each piece's summary under its title. |
| `playLabel` | `string` | `'Play {title}'` | The play button's accessible name; {title} is the piece's title. |
| `level` | `1 | 2 | 3` | `2` | Heading level of the section's heading; the pieces' titles are one below. |
| `tone` | `'surface' | 'muted' | 'ink' | 'brand'` | `unset (paints nothing)` | The section's ground; the picture tiles keep their blocks on every tone. |
| `density` | `'compact' | 'cozy' | 'roomy'` | `'cozy'` | Block padding ×0.8 / ×1 / ×1.25 through --ma-density. |

## Usage

```astro
---
import PieceList from '@/components/moonarc/pro/PieceList.astro';
---
<PieceList
  look="wave"
  eyebrow="Episodes"
  detail
  more={{ href: '/episodes/', label: 'All episodes' }}
  pieces={[
    { number: 'Episode 18', title: 'Pricing a first course', date: '3 October 2026', length: '48 min', summary: 'What to charge before anyone has bought.', href: '/episodes/18/', audio: '/audio/episode-18.mp3' },
    { number: 'Episode 17', title: 'The newsletter that paid for itself', date: '19 September 2026', length: '41 min', summary: 'Six months of numbers, in the order they happened.', href: '/episodes/17/' },
  ]}
>
  <span slot="headline">Every episode, <em>newest first</em></span>
</PieceList>
```

## Reduced motion

The waveforms and drawings stay at rest on hover and while playing. A row changes its background and lifts, and a frame's play mark swells, at once, without a transition. Playing and the played share still work.

## With ClientRouter

The script binds the play buttons once per swap through the runtime's onMount; the audio stops when the page is swapped away. The rest is CSS.

## Craft

- The title link is stretched over the whole row, so a row is one target and the play button, raised above it, is the one other control. The keyboard focus ring goes round the row.
- One piece plays at a time. Starting another pauses the one playing, and the share played reaches the waveform as one custom property, so the bars colour themselves and no script touches each bar.
- No audio file is requested until its button is pressed, so a list of forty episodes costs nothing before anyone listens.
- The play button is named from the piece (playLabel carries {title}) and says whether it plays through aria-pressed, so a screen reader hears which piece a button plays and its state, never a name that changes under it.

## Replaces

- Podcast site episode lists (Transistor, Buzzsprout)
- Newsletter archive pages (Substack, Ghost themes)

## Source

Pro block. The source is served by the license-gated registry: `npx shadcn@latest add @moonarc-pro/piece-list` with a key in components.json (https://moonarc.dev/account/). Everything it composes is free: motif, waveform.
