# Screens Scroll (Moonarc)

An app's features told down the page while one phone stays in view beside them and its screen changes with each feature. Every feature names its own view timeline; each screen fades in on its feature's timeline over the one before it, so between two features the phone is never empty and scrolling back turns the screens back. A line beside each feature fills as it is read. Where view timelines are missing, below 64 rem, or with reduced motion, each feature shows its own phone under its text. The screens are drawn from rows (AppScreen). Zero JavaScript.

- Pro block. Install: `npx shadcn@latest add @moonarc-pro/screens-scroll` (license key required)
- Tier B · category section · trigger scroll
- Readout: `<ScreensScroll features={[{ title, text, screen }]} />`
- Browser support: limited (Chrome 116 · not Firefox · Safari 26); elsewhere: each feature shows its own phone under its text (Firefox, and Safari before 26)
- Measured cost: 0 B JS of its own · uses Reveal + runtime (with dependencies 3.0 kB raw; CSS 29.5 kB raw)
- Page: https://moonarc.dev/components/screens-scroll/

## Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `features` | `{ eyebrow?; title; text; points?; screen }[]` | none | Two to six features, top to bottom. screen is AppScreen's props; give it a label, which is what a screen reader hears after the feature. |
| `headline` | `string` | none | The section's heading. Slot "headline" wins and may hold an <em>. |
| `eyebrow` | `string` | none | A short line above the heading. |
| `copy` | `string` | none | A line under the heading. |
| `block` | `1 | 2 | 3 | 4 | 5` | `1` | The colour block of the disc behind the held phone. |
| `model` | `'island' | 'notch' | 'punch'` | `'island'` | The phone's cut-out. |
| `level` | `2 | 3` | `2` | Heading level of the heading; each feature's title is one below. |
| `media` | `'right' | 'left'` | `'right'` | Side of the held phone. |
| `view` | `string` | `'100vh'` | The height of what scrolls the section: the viewport, unless it scrolls inside a box (pass 100cqh and make the box a size container), so the features and the held phone are sized by what is seen. |
| `tone` | `'surface' | 'muted' | 'ink' | 'brand'` | `unset (paints nothing)` | The section's ground. |
| `density` | `'compact' | 'cozy' | 'roomy'` | `'cozy'` | Block padding ×0.8 / ×1 / ×1.25 through --ma-density. |

## Usage

```astro
---
import ScreensScroll from '@/components/moonarc/pro/ScreensScroll.astro';
---
<ScreensScroll
  eyebrow="How it works"
  headline="One tap a day"
  features={[
    { eyebrow: 'Today', title: 'Your habits, one list', text: 'Tick each one off as it is done.', screen: { title: 'Today', label: 'The today list', rows: [{ kind: 'checks', items: [{ text: 'Walk', done: true }, { text: 'Read' }] }] } },
    { eyebrow: 'Streaks', title: 'Days in a row', text: 'A week you keep turns solid.', screen: { title: 'Streak', label: 'A six-day streak', rows: [{ kind: 'days', days: 'xxxxxx.' }] } },
    { eyebrow: 'Insights', title: 'What sticks', text: 'Your best days, by the hour.', screen: { title: 'Insights', label: 'A week of completed habits', rows: [{ kind: 'bars', values: [3, 4, 2, 4, 4, 3, 1] }] } },
  ]}
/>
```

## Reduced motion

Each feature shows its own phone under its text; nothing is bound to the scroll.

## With ClientRouter

CSS only.

## Craft

- The screens are stacked in one cell and each only ever fades in, over the one before, on its feature's timeline: the phone shows the latest feature reached, and there is no gap between two features in which no screen is lit.
- The timelines are named per instance (timeline-scope on the section, each feature's name written into its style), so two of these on one page never read each other's scroll.
- The held phone is a picture of the same screens and is hidden from a screen reader; each feature keeps its own phone in the page for it, out of sight, so the screen is heard right after the feature it belongs to.
- The held phone is as tall as the viewport allows (its width is worked out from the viewport's height), so it never runs under the browser's edge.

## Replaces

- Sticky phone feature sections built with GSAP ScrollTrigger
- Scrollytelling app tours

## Source

Pro block. The source is served by the license-gated registry: `npx shadcn@latest add @moonarc-pro/screens-scroll` with a key in components.json (https://moonarc.dev/account/). Everything it composes is free: phone-frame, app-screen, reveal.
