# Project Index (Moonarc)

The work as a numbered index: one line per project with the title at display size and the client, the discipline and the year in mono, the whole line a link. Over a line the project's cover floats by the pointer and the pointer becomes a label (View); the other titles step back. Each cover can carry the project's view transition name, so with Astro's router the cover a reader chose grows into the case study (CaseStudyHero, same name). On a touch screen each line shows a thumbnail instead, and keyboard focus shows the cover at a fixed spot. The float is HoverImageLinks, the label Cursor; zero JavaScript of its own.

- Pro block. Install: `npx shadcn@latest add @moonarc-pro/project-index` (license key required)
- Tier B · category section · trigger hover, pointer, click
- Readout: `<ProjectIndex projects={[{ href, title, client, discipline, year, cover, name }]}>`
- Browser support: widely (Chrome 111 · Firefox 144 · Safari 18); elsewhere: without :has() the cover does not float; without view transitions (or Astro's router) the case study opens as a new page, with the router's own fade where it runs
- Measured cost: 0 B JS of its own · uses HoverImageLinks + runtime + Cursor (with dependencies 3.3 kB raw; CSS 14.9 kB raw)
- Page: https://moonarc.dev/components/project-index/

## Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `projects` | `{ href: string; title: string; client?: string; discipline?: string; year?: string | number; cover: string | { src, srcset?, width?, height? }; thumb?: string | { src, srcset?, width?, height? }; name?: string }[]` | none | The projects in order, numbered 01, 02 …. cover floats by the pointer (alt empty: the title names the link); thumb is the line's picture on a touch screen (default the cover); name is the cover's view transition name, written as Astro's transition:name writes it, the same name CaseStudyHero carries on the project's page. |
| `headline` | `string` | none | The section's heading. Slot "headline" wins. |
| `eyebrow` | `string` | none | A short line above the headline, in mono. Slot "eyebrow" wins. |
| `copy` | `string` | none | A paragraph under the headline. Slot "copy" wins. |
| `level` | `1 | 2` | `2` | Heading level of the headline: 2 under a page's h1, 1 when the index opens its page. |
| `label` | `string` | `'Projects'` | Accessible name of the list. |
| `cursorLabel` | `string` | `'View'` | The pointer's label over a line. |
| `cursor` | `boolean` | `true` | The label cursor (Cursor in label mode, confined to the section, the native pointer hidden inside it). Never shown on touch screens or under reduced motion; false keeps the plain pointer everywhere. |
| `head` | `[string, string, string, string]` | none | Column heads over the lines, in mono (project, client, discipline, year), shown from 48 rem of the section's width. aria-hidden: each line already reads in that order. |
| `more` | `{ href: string; label: string }` | none | A link after the list, such as every project. Slot "actions" wins. |
| `size` | `number` | `380` | Width of the floating cover in px. |
| `ratio` | `string` | `'16 / 10'` | Aspect ratio of the floating cover and the thumbnails. |
| `tone` | `'surface' | 'muted' | 'ink' | 'brand'` | `unset (paints nothing)` | The ground: the page surface, a muted band, an always-dark ink panel, or a deep brand panel. The label cursor is the theme's primary pair, and the tone's own text pair on ink and brand. |
| `density` | `'compact' | 'cozy' | 'roomy'` | `'cozy'` | Block padding and the lines' height ×0.8 / ×1 / ×1.25 through --ma-density. |

## Usage

```astro
---
import ProjectIndex from '@/components/moonarc/pro/ProjectIndex.astro';
import { ClientRouter } from 'astro:transitions';
import { getImage } from 'astro:assets';
import cover from '../assets/work/harbour/cover.webp';

// the float loads the width it shows; the same name on the case study's CaseStudyHero pairs the two
const img = await getImage({ src: cover, widths: [400, 800], width: 800 });
---
<ClientRouter />
<ProjectIndex
  headline="Selected work"
  head={['Project', 'Client', 'Discipline', 'Year']}
  projects={[
    { href: '/work/harbour/', title: 'Harbour Lines', client: 'Harbour Transit', discipline: 'Identity, wayfinding', year: 2026, cover: { src: img.src, srcset: img.srcSet.attribute }, name: 'work-harbour' },
    { href: '/work/field-notes/', title: 'Field Notes', client: 'Field Press', discipline: 'Editorial', year: 2025, cover: '/work/field-notes.webp', name: 'work-field-notes' },
  ]}
  more={{ href: '/work/', label: 'Every project' }}
/>
```

## Reduced motion

The cover appears at a fixed spot on the right while a line is hovered or focused, with no follow and no lean; the label cursor is not installed and the native pointer stays; the lines do not fade. Astro's router skips the view transition, so the case study simply opens.

## With ClientRouter

Made for it: with <ClientRouter /> and the same name on the case study's hero, the chosen cover grows into it. HoverImageLinks and Cursor bind through the shared runtime and are released before the swap.

## Craft

- One named element per project on either kind of screen: the floating cover with a pointer, the line's thumbnail on a touch screen, each display: none where the other shows. Two elements with one name would cancel the whole transition.
- The names are written inline, encoded the way Astro's transition:name encodes them, so a name given here pairs with the same name given through the directive on the case study, without the directive's keyframes on this page.
- Title in the display face at up to 4.25 rem, the facts in mono at the secondary strength: the eye runs down the titles, and the facts are there when it stops.
- The label is the theme's primary pair: the one accent on an index of black and white is the thing that says the line is a link.
- Under 48 rem of the section's width the facts share one line under the title, a middle dot between them, so a long title keeps the whole width of a phone.

## Replaces

- Project lists (Framer studio templates)
- HoverImageLinks project index (Hover.dev)
- Work index (Awwwards studio sites)

## Source

Pro block. The source is served by the license-gated registry: `npx shadcn@latest add @moonarc-pro/project-index` with a key in components.json (https://moonarc.dev/account/). Everything it composes is free: hover-image-links, cursor, underline-hover.
