Avatar Stack Flock
ns-ui/avatar-stack-flockFreeComponent
Avatar stack that mills as a live boids flock and resolves into the classic overlapping row on hover, with the +N badge appearing only once the formation settles.
Live preview
live · rendered by the registry
Rendered by ns-ui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://design.helpmarq.com/r/avatar-stack-flock.jsonSource
1"use client";23import { useEffect, useRef } from "react";45// ---------------------------------------------------------------------------6// FlockStack — team avatar stack that never sits still: at rest the avatars7// mill as a bounded boids flock (separation / alignment / cohesion + soft8// walls); hovering or keyboard-focusing the region ramps a seek force toward9// each avatar's slot in the classic overlapping row, SUMMED into the same10// three rules, so the tidy "group photo" is itself a settled flocking state.11// Pure DOM — per-frame transforms from a refs-only vector sim, no canvas, no12// React state on the hot path. Every color is a CSS token class (nothing is13// read via getComputedStyle, so there is nothing to re-derive on theme14// change — ring-background / border-border / bg-surface self-adapt). The +N15// badge fades in only once the formation resolves; once resolved AND settled16// the rAF loop genuinely sleeps until hover exit. Static resolved row under17// prefers-reduced-motion.18// ---------------------------------------------------------------------------1920export interface FlockMember {21 name: string;22 /** 1–2 chars; derived from name when omitted */23 initials?: string;24 /** optional avatar image url; initials shown otherwise */25 src?: string;26}2728const DEFAULT_MEMBERS: FlockMember[] = [29 { name: "Mara Chen" },30 { name: "Jonas Weber" },31 { name: "Aiko Tanaka" },32 { name: "Sam Okafor" },33 { name: "Lena Fischer" },34 { name: "Ravi Patel" },35 { name: "Nora Lindqvist" },36];3738function initialsOf(m: FlockMember) {39 if (m.initials) return m.initials.slice(0, 2).toUpperCase();40 return m.name41 .split(/\s+/)42 .map((w) => w[0] ?? "")43 .join("")44 .slice(0, 2)45 .toUpperCase();46}4748// deterministic PRNG for the initial scatter (stable across strict-mode runs)49function mulberry32(seed: number) {50 let a = seed >>> 0;51 return () => {52 a = (a + 0x6d2b79f5) >>> 0;53 let t = a;54 t = Math.imul(t ^ (t >>> 15), t | 1);55 t ^= t + Math.imul(t ^ (t >>> 7), t | 61);56 return ((t ^ (t >>> 14)) >>> 0) / 4294967296;57 };58}5960// sim constants — px, seconds61const SEP_R = 34;62const SEP_W = 1.4;63const ALI_R = 60;64const ALI_W = 0.6;65const COH_R = 90;66const COH_W = 0.5;67const MAX_SPEED = 60; // px/s while milling — keeps the ambient drift calm68const SEEK_MAX_SPEED = 480; // px/s ceiling while seeking, ramped in with seekT69const MAX_FORCE = 260; // steering clamp, px/s^270const WALL_M = 24; // soft-wall ramp distance71// … truncated
Files it writes
More from ns-ui
All 228 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Accordion Latchaccordion-latch | ns-ui | Components | Free | no deps | |
| Approval Inline Diffapproval-inline-diff | ns-ui | Components | Free | no deps | |
| ASCII Engraving Contourascii-engraving-contour | ns-ui | Components | Free | no deps | |
| ASCII Globe Spinascii-globe-spin | ns-ui | Components | Free | no deps | |
| ASCII Torus Donutascii-torus-donut | ns-ui | Components | Free | no deps | |
| Autosave Ratchetautosave-ratchet | ns-ui | Components | Free | no deps | |
| Background ASCII Ditherbackground-ascii-dither | ns-ui | Components | Free | no deps | |
| Background ASCII Plasmabackground-ascii-plasma | ns-ui | Components | Free | no deps |
