# Concept A, SKYLINE: 21st.dev components ported

Both pages are single-file vanilla HTML. Each pattern below was fetched from 21st.dev
with `mcp__21st__get_component`, read in full, and ported by hand to CSS and plain JS with
the same behaviour. The install commands pull the React originals when the tailabsv2
implementation wants the real component. `$API_KEY_21ST` is the paid account key.

| 21st id | Component (author) | Install | Used where |
|---|---|---|---|
| 20033 | Pixelated Image Reveal (soralabs) | `npx shadcn@latest add "https://21st.dev/r/soralabs/pixelated-image-reveal?api_key=$API_KEY_21ST"` | Landing: city film cards, hover swap |
| 21470 | Logo Cloud Marquee (7ovr) | `npx shadcn@latest add "https://21st.dev/r/7ovr/logo-cloud-3?api_key=$API_KEY_21ST"` | Landing: both logo rows |
| 18898 | Feature Bento (uilayout) | `npx shadcn@latest add "https://21st.dev/r/uilayout.contact/feature-bento?api_key=$API_KEY_21ST"` | Landing: case-study grid; Dashboard: For you today |
| 23545 | Presence Avatars (ddoemonn) | `npx shadcn@latest add "https://21st.dev/r/ddoemonn/presence-avatars?api_key=$API_KEY_21ST"` | Hero meta, posts panel, dashboard rooms and threads |
| 22085 | Testimonials 13 (shadcnui-blocks) | `npx shadcn@latest add "https://21st.dev/r/shadcnui-blocks/testimonials-13?api_key=$API_KEY_21ST"` | Landing: testimonial marquee |
| 20459 | Number Ticker (danielpetho) | `npx shadcn@latest add "https://21st.dev/r/danielpetho/basic-number-ticker?api_key=$API_KEY_21ST"` | Landing: the four headline stats |
| 19070 | Advanced Stats (uilayout) | `npx shadcn@latest add "https://21st.dev/r/uilayout.contact/advanced-stats?api_key=$API_KEY_21ST"` | Dashboard team lead: KPI row, clipped area chart |
| 23552 | Segmented Control (ddoemonn) | `npx shadcn@latest add "https://21st.dev/r/ddoemonn/segmented-control?api_key=$API_KEY_21ST"` | Landing audience switch; dashboard persona switch |

## What changed in the port, and why

### 20033 Pixelated Image Reveal
Kept: the N by N grid of cells (7 by 7), the randomised stagger (`shuffle` then
`position * (stepDuration / count)`), the two-phase sequence (cells fade in over one step,
the layer swaps, cells fade out over a fresh random order), the generation counter that
cancels a stale run when the pointer leaves mid-reveal, and the coarse-pointer fallback
(tap toggles instead of hover, gated with `matchMedia('(hover: hover) and (pointer: fine)')`).
Changed: the layers are two `<video>` elements with poster images rather than two
`next/image`s, so the swap is London street to Tokyo, SF skyline to night aerial, NYC crosswalk
to Times Square. Cells are ember (`#E0693C`) instead of the demo red. Motion's `motion.div`
is replaced by `setTimeout` per cell setting `style.opacity`, which is what the original does
under the hood (`duration: 0` with a delay). Under `prefers-reduced-motion` the layers swap
with no cells, as the original does.

The scroll-in resolve on the same cards is the brief's canvas mosaic resolve, not this
component: `pixelate()` shrinks the poster to `w/block` and blows it back up with
`imageSmoothingEnabled = false`, block tweened 64 to 1 over 900ms on the `--ease-out` curve,
then the canvas fades and the video plays underneath. The hero runs the same resolve on load
over 1100ms. Both source the poster image, so they work where H.264 cannot decode.

### 21470 Logo Cloud Marquee
Kept: duplicated track, `translateX(-50%)` keyframe on `linear` (constant motion), mask
fade at both edges (`linear-gradient(to right, transparent, #000 12%, #000 88%, transparent)`),
pause on hover, `animation: none` under reduced motion (the track then wraps and centres).
Changed: hover pause is gated behind `(hover: hover) and (pointer: fine)`; the second row runs
in reverse at a different speed; PNG logos are `grayscale(1)` with `mix-blend-mode: multiply`
on cream; walmart.png, tiktok.png and wireapps.png are treated like google.png; typeb.jpeg is
opaque and sits on its own white plate, never tinted.

### 18898 Feature Bento
Kept: the grid idea of one hero card spanning two columns with photo, scrim and text pinned
to the bottom, plus tile cards. Changed: the case-study grid is 3 columns with Modern Health
wide, 28Digital wide, and a closing CTA tile so no row has a hole; the image hover is
`scale(1.05)` over 500ms `--ease-out` and gated to fine pointers; the demo's `animate-ping`
and gradient hover overlays were dropped (restraint is the point of this concept).

### 23545 Presence Avatars
Kept: rounded-square tiles with a 2px gap ring, 9px overlap, z-order front to back, overflow
chip with tabular mono count, initials fallback, `role="group"` with an `aria-label`.
Changed: Motion's spring (stiffness 520, damping 34, mass 0.45, effectively critically damped)
becomes a 240ms `--ease-out` transition from `scale(.86)` + `opacity: 0` with a 50ms
per-tile stagger, triggered by IntersectionObserver. The live `usePresence` hook (ordering by
first-seen, polite announcements) is not needed for a static prototype and was left out.

### 22085 Testimonials 13
Kept: auto-playing marquee of quote cards with avatar, name, role and an employer mark;
pause on hover; edge mask. Changed: the alternating logo tiles with the dotted grid were
replaced by a small employer mark inside each card, and each card carries a Team or Career
tag so the mix reads at a glance. The X link button was removed. The Marquee util is the same
keyframe as the logo cloud.

### 20459 Number Ticker
Kept: tween from 0 to target, rounded output, `tabular-nums`, started on scroll-in and only
once. Changed: Motion's `animate()` is a `requestAnimationFrame` loop over 1400ms with the
`--ease-out` cubic; formatting goes through `toLocaleString('en-GB')` with a decimals option
so "4.8" and "10,000+" both work. Reduced motion prints the final value.

### 19070 Advanced Stats
Kept: KPI cards with label, big number and delta; the clipped area chart reveal
(`ClippedAreaChart` in the original animates a clip; here a `<clipPath>` rect scales from
`scaleX(0)` to `scaleX(1)` over 900ms `--ease-in-out` when the team lead view enters).
Changed: Recharts is replaced by hand-written SVG (a 5-point line and area with an ember
gradient); the TimelineAnimation scroll reveals are the page's own reveal system.

### 23552 Segmented Control
Kept: `role="radiogroup"` with `role="radio"` buttons, `aria-checked`, roving tabindex,
ArrowLeft/Right/Up/Down, Home, End, a sliding thumb sized to the active option.
Changed: by the brief and the frequency rule the thumb does not animate (Motion's spring is
gone; the thumb is measured from the active button's `offsetLeft` and `offsetWidth` and set
instantly, re-measured on resize and after fonts load). The original's clip-masked duplicate
label layer (the Emil tab-colour trick) is replaced by a 120ms colour transition on the label,
since the thumb itself no longer moves. The content behind the switch cross-fades in 160 to
180ms on `--ease-out`.

## Searched but not ported
`mcp__21st__search "segmented control"` returned 23552 (used), 24931, 26923 and 7961. No
donut or command palette was needed once the insights answer used horizontal bars; a
command palette is out of scope for a static shell (the top bar shows a search affordance).

## The thirteen journey screens

The journey screens (`onboarding-*`, `member-*`, `business-*`, `learner-*`) share
`flow.css` and `flow.js` in this folder and reuse the same ported components as the
landing page and dashboard, so nothing new needs installing for them:

- **Segmented control** (21st `ddoemonn/segmented-control`) drives the plan tabs on
  `member-upgrade.html`. Thumb moves instantly, content cross-fades under 200ms.
- **Presence avatars** (21st `ddoemonn/presence-avatars`) appear on
  `onboarding-signup.html`, `business-scope.html` and `learner-day-one.html`.
- **Pixelated image reveal** (21st `soralabs/pixelated-image-reveal`, id 20033) is the
  canvas mosaic in `flow.js`. It carries a meaning on `onboarding-first-win.html`: an
  agent run that has not happened yet is an unresolved mosaic, a finished run resolves
  to the real frame, and a failed run never resolves.
- The step strip at the foot of every screen is generated from the single `STEPS`
  table in `flow.js`, so the thirteen file names exist in exactly one place.

Install commands for the React versions are in the sections above.
