From 316117f0de609417f0f9acd6c3f1e8119811b174 Mon Sep 17 00:00:00 2001 From: Hunter Garrett Date: Sat, 10 Oct 2026 12:48:11 -0400 Subject: [PATCH 1/8] feat(docs): build the Performant card's still frame in code Replace the exported image with two gray chips drawn from the mock, with no work flowing to either. The story hands the work to the GPU from here and winds it down back to this frame. --- apps/docs-tests/docs/homepage.spec.ts | 27 ++--- apps/docs/public/features/performant.png | Bin 123871 -> 0 bytes .../docs/src/components/features/features.tsx | 11 ++- .../performant-illustration.module.css | 92 ++++++++++++++++++ .../features/performant-illustration.tsx | 51 ++++++++++ 5 files changed, 163 insertions(+), 18 deletions(-) delete mode 100644 apps/docs/public/features/performant.png create mode 100644 apps/docs/src/components/features/performant-illustration.module.css create mode 100644 apps/docs/src/components/features/performant-illustration.tsx diff --git a/apps/docs-tests/docs/homepage.spec.ts b/apps/docs-tests/docs/homepage.spec.ts index 52afa0c9..29dd0544 100644 --- a/apps/docs-tests/docs/homepage.spec.ts +++ b/apps/docs-tests/docs/homepage.spec.ts @@ -18,34 +18,35 @@ const FAVORITES = [ 'led-wall', ]; -// Each feature card's title, its description, and its illustration's URL, -// or null for an illustration built in code. +// Each feature card's title, its description, and its illustration: the +// URL of an image exported from the mock, or, for an illustration built in +// code, a piece of text it shows. interface FeatureCard { title: string; description: string; - illustration: string | null; + illustration: { src: string } | { text: string }; } const FEATURE_CARDS: FeatureCard[] = [ { title: 'Composable', description: 'Stack shaders in a single scene to build the exact effect you want.', - illustration: '/features/composable.png', + illustration: { src: '/features/composable.png' }, }, { title: 'Performant', description: 'Reduce browser overhead for your users with WebGPU rendering.', - illustration: '/features/performant.png', + illustration: { text: 'GPU' }, }, { title: 'Reactive', description: 'Bring shaders to life with animation, cursor, and scroll inputs.', - illustration: '/features/reactive.png', + illustration: { src: '/features/reactive.png' }, }, { title: 'Extensible', description: 'Write your own shaders with the same TSL primitives our components use.', - illustration: null, + illustration: { text: '' }, }, ]; @@ -269,12 +270,12 @@ test('each feature card shows its illustration, hidden from screen readers', asy await expect(item.getByRole('img')).toHaveCount(0); - // Extensible's code window: its header shows, inside a hidden subtree. - if (card.illustration === null) { - const header = item.getByText('', { exact: true }); + // An illustration built in code: its text shows, inside a hidden subtree. + if ('text' in card.illustration) { + const text = item.getByText(card.illustration.text, { exact: true }); - await expect(header).toBeVisible(); - expect(await header.evaluate((element) => !!element.closest('[aria-hidden="true"]'))).toBe( + await expect(text).toBeVisible(); + expect(await text.evaluate((element) => !!element.closest('[aria-hidden="true"]'))).toBe( true, ); continue; @@ -283,7 +284,7 @@ test('each feature card shows its illustration, hidden from screen readers', asy const image = item.locator('img'); await expect(image).toHaveAttribute('aria-hidden', 'true'); - await expect(image).toHaveAttribute('src', card.illustration); + await expect(image).toHaveAttribute('src', card.illustration.src); await expect .poll(() => image.evaluate((element: HTMLImageElement) => element.complete && element.naturalWidth > 0), diff --git a/apps/docs/public/features/performant.png b/apps/docs/public/features/performant.png deleted file mode 100644 index 92c7161df37d66297cbbc11cf7135e2a0d96d448..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 123871 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 =JH!|5n*N>H( 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 = { composable: , - performant: , + performant: , reactive: , extensible: , }; diff --git a/apps/docs/src/components/features/performant-illustration.module.css b/apps/docs/src/components/features/performant-illustration.module.css new file mode 100644 index 00000000..c81e6640 --- /dev/null +++ b/apps/docs/src/components/features/performant-illustration.module.css @@ -0,0 +1,92 @@ +/* The Performant illustration, after the Figma mock's node 939:962. Every + length is in the mock's pixels times --mock-px, one mock pixel at the + card window's current width. The window is 312 mock pixels wide, and + features.module.css makes it the container that `cqw` measures, so the + illustration scales like the image it replaced at every column width. */ +.illustration { + --mock-px: calc(100cqw / 312); + + display: grid; + place-items: center; + aspect-ratio: 312 / 309; +} + +/* The two chips side by side, 24px apart, centered in the window. */ +.chips { + display: flex; + gap: calc(24 * var(--mock-px)); +} + +/* ---- A chip ---- + 100px wide: a 100px body with a row of pins above and below it. */ +.chip { + display: flex; + flex-direction: column; + align-items: center; + width: calc(100 * var(--mock-px)); +} + +/* Four pins, 6.67 by 8.89 with 8.89 between them, their outer ends + rounded. The bottom row turns over, so its rounded ends point down. */ +.pins { + display: flex; + gap: calc(8.889 * var(--mock-px)); +} + +.pins[data-edge='bottom'] { + rotate: 180deg; +} + +.pin { + width: calc(6.667 * var(--mock-px)); + height: calc(8.889 * var(--mock-px)); + border-radius: calc(8.889 * var(--mock-px)) calc(8.889 * var(--mock-px)) 0 0; + background: var(--white-a70); +} + +/* The body's outer square. Its color is the mock's: 90% white laid over + the window's gray with an overlay blend, as the Extensible code window's + frame is. The blend layer sits under the inner squares, which are + positioned so they paint over it. */ +.body { + position: relative; + width: 100%; + aspect-ratio: 1; + padding: calc(3.333 * var(--mock-px)); +} + +.body::before { + position: absolute; + inset: 0; + border-radius: calc(8.889 * var(--mock-px)); + background: var(--white-a90); + content: ''; + mix-blend-mode: overlay; +} + +/* The middle square, 3.33px inside the outer one, at 20% white. */ +.ring { + position: relative; + height: 100%; + padding: calc(3.333 * var(--mock-px)); + border-radius: calc(5.556 * var(--mock-px)); + background: var(--white-a20); +} + +/* The center square, 3.33px inside the middle one, at 30% white, with the + chip's name centered in it. */ +.core { + display: grid; + place-items: center; + height: 100%; + border-radius: calc(2.222 * var(--mock-px)); + background: var(--white-a30); +} + +.name { + color: var(--black-a60); + font-family: var(--font-mono); + font-size: calc(21.756 * var(--mock-px)); + font-weight: var(--font-weight-semibold); + line-height: 1; +} diff --git a/apps/docs/src/components/features/performant-illustration.tsx b/apps/docs/src/components/features/performant-illustration.tsx new file mode 100644 index 00000000..5610dfec --- /dev/null +++ b/apps/docs/src/components/features/performant-illustration.tsx @@ -0,0 +1,51 @@ +/** + * The Performant feature card's illustration, after the Figma mock: a CPU + * chip beside a GPU chip. The still frame (SHA-215) shows both chips idle + * and gray, with no work flowing to either, so its story can hand the work + * to the GPU and wind it down there. + */ +import styles from './performant-illustration.module.css'; + +// The four pins along a chip's top or bottom edge. The bottom row is the +// top row turned over, so its rounded ends point down. +function Pins({ edge }: { edge: 'top' | 'bottom' }) { + return ( +
+ + + + +
+ ); +} + +// A chip: its body, three nested squares that step lighter toward the +// center, with its name in the middle and four pins above and below. +function Chip({ name }: { name: string }) { + return ( +
+ +
+
+
+ {name} +
+
+
+ +
+ ); +} + +// Hidden from screen readers: the card's title and description carry its +// message. +export function PerformantIllustration() { + return ( +
+
+ + +
+
+ ); +} From 95bd061b229d85134e7e7fb366c008bd976d0807 Mon Sep 17 00:00:00 2001 From: Hunter Garrett Date: Sat, 10 Oct 2026 14:35:10 -0400 Subject: [PATCH 2/8] feat(docs): let a feature card's story hold while the pointer stays Add useHeldStory beside useStory: a hover plays the story's opening, the story holds while the pointer stays, and its ending plays once the pointer leaves. Each part finishes once started. Both shapes share one hover gate, so the Extensible card's repeating story behaves as before. --- apps/docs/src/components/features/story.tsx | 177 ++++++++++++++++---- 1 file changed, 145 insertions(+), 32 deletions(-) diff --git a/apps/docs/src/components/features/story.tsx b/apps/docs/src/components/features/story.tsx index b08ec519..aac6111a 100644 --- a/apps/docs/src/components/features/story.tsx +++ b/apps/docs/src/components/features/story.tsx @@ -2,14 +2,17 @@ /** * The lifecycle every feature card's story shares (SHA-178): a hover plays - * the story, which keeps going while the pointer stays and finishes the play - * under way once it leaves. Touch, keyboard, and reduced motion leave the card - * on its still frame. StoryCard is the card, and useStory the illustration's side. + * the story, and a story under way always finishes after the pointer leaves. + * Touch, keyboard, and reduced motion leave the card on its still frame. + * StoryCard is the card. An illustration takes one of two story shapes: + * useStory repeats a play while the pointer stays, and useHeldStory opens, + * holds while the pointer stays, then ends. */ import { createContext, type ReactNode, type RefObject, + useCallback, useContext, useEffect, useRef, @@ -44,37 +47,29 @@ export function StoryCard({ className, children }: { className?: string; childre } // --------------------------------------------- -// The story +// The pointer // --------------------------------------------- -/** Where an illustration's story is: playing or not, which play, and how to end one. */ -export interface Story { - /** True from the hover that starts the story until a play ends with the pointer off the card. */ - playing: boolean; - /** - * Counts the story's plays: one more each time a play starts, including a - * play that follows another because the pointer stayed. An illustration - * keys its animation by it, so every play starts from the still frame. - */ - plays: number; - /** Called by the illustration each time a play lands back on the still frame. */ - onStoryEnd: () => void; -} - /** - * The story of the illustration that calls it, played by a hover on its - * StoryCard. Only a pointer that passes the site's hover gate starts it, - * and a touch never does, even on a touch-screen laptop that passes the - * gate. Under reduced motion nothing starts it. + * Follows the pointer on the illustration's StoryCard, behind the site's + * hover gate, and calls `onEnter` and any `onLeave` as it comes and goes. Only a + * pointer that passes the gate counts, and a touch never does, even on a + * touch-screen laptop that passes the gate. Under reduced motion nothing + * counts. Returns whether the pointer is on the card now, which only the + * ends of a story's parts read, so it lives in a ref rather than in state. */ -export function useStory(): Story { +function useCardPointer(onEnter: () => void, onLeave?: () => void): RefObject { const cardRef = useContext(CardContext); const canHover = useCanHover(); const reducedMotion = useReducedMotion() === true; - const [story, setStory] = useState({ playing: false, plays: 0 }); - // Whether the pointer is on the card. Only the end of a play reads it, so - // it lives in a ref rather than in state. const pointerOnCardRef = useRef(false); + // The latest callbacks, so the listeners below call the current ones + // without being added again on every render. + const callbacksRef = useRef({ onEnter, onLeave }); + + useEffect(() => { + callbacksRef.current = { onEnter, onLeave }; + }); // Listens to the card itself, so the whole card is the hover target, not // just the illustration. @@ -86,25 +81,66 @@ export function useStory(): Story { const onPointerEnter = (event: PointerEvent) => { if (event.pointerType === 'touch') return; pointerOnCardRef.current = true; - // A play under way carries on, so a hover during it doesn't restart it. - setStory((current) => - current.playing ? current : { playing: true, plays: current.plays + 1 }, - ); + callbacksRef.current.onEnter(); }; const onPointerLeave = (event: PointerEvent) => { - if (event.pointerType !== 'touch') pointerOnCardRef.current = false; + if (event.pointerType === 'touch') return; + pointerOnCardRef.current = false; + callbacksRef.current.onLeave?.(); }; card.addEventListener('pointerenter', onPointerEnter); card.addEventListener('pointerleave', onPointerLeave); + // The gate closing counts as the pointer leaving, so a held story ends + // rather than holding with no pointer to release it. return () => { - pointerOnCardRef.current = false; card.removeEventListener('pointerenter', onPointerEnter); card.removeEventListener('pointerleave', onPointerLeave); + + if (pointerOnCardRef.current) { + pointerOnCardRef.current = false; + callbacksRef.current.onLeave?.(); + } }; }, [cardRef, canHover, reducedMotion]); + return pointerOnCardRef; +} + +// --------------------------------------------- +// A story that repeats +// --------------------------------------------- + +/** Where an illustration's story is: playing or not, which play, and how to end one. */ +export interface Story { + /** True from the hover that starts the story until a play ends with the pointer off the card. */ + playing: boolean; + /** + * Counts the story's plays: one more each time a play starts, including a + * play that follows another because the pointer stayed. An illustration + * keys its animation by it, so every play starts from the still frame. + */ + plays: number; + /** Called by the illustration each time a play lands back on the still frame. */ + onStoryEnd: () => void; +} + +/** + * The story of the illustration that calls it, played by a hover on its + * StoryCard. A play that ends with the pointer still on the card starts the + * next one, so the story keeps going while the pointer stays. + */ +export function useStory(): Story { + const [story, setStory] = useState({ playing: false, plays: 0 }); + const pointerOnCardRef = useCardPointer( + // A play under way carries on, so a hover during it doesn't restart it. + () => + setStory((current) => + current.playing ? current : { playing: true, plays: current.plays + 1 }, + ), + ); + // A play that ends with the pointer still on the card starts the next one. // Otherwise the card rests on its still frame. const onStoryEnd = () => { @@ -117,3 +153,80 @@ export function useStory(): Story { return { ...story, onStoryEnd }; } + +// --------------------------------------------- +// A story that holds +// --------------------------------------------- + +/** + * Where a held story is. It rests on the still frame, plays its opening on a + * hover, holds while the pointer stays, and plays its ending back to the + * still frame once the pointer leaves. + */ +export type HeldStage = 'still' | 'opening' | 'holding' | 'ending'; + +/** Where an illustration's held story is, which play, and how to end each part. */ +export interface HeldStory { + stage: HeldStage; + /** + * Counts the story's openings. An illustration keys its animation by it, + * so every play starts from the still frame. + */ + plays: number; + /** Called by the illustration when its opening lands on the hold. */ + onOpeningEnd: () => void; + /** Called by the illustration when its ending lands on the still frame. */ + onStoryEnd: () => void; +} + +/** + * The held story of the illustration that calls it, played by a hover on + * its StoryCard. Each part runs to its end once it starts. A pointer that + * leaves during the opening lets it land, then the ending plays with no + * hold. A pointer that comes back during the ending lets it land on the + * still frame, then a fresh opening plays. + */ +export function useHeldStory(): HeldStory { + const [story, setStory] = useState<{ stage: HeldStage; plays: number }>({ + stage: 'still', + plays: 0, + }); + const pointerOnCardRef = useCardPointer( + // Only the still frame opens the story. A hover during the opening or + // the ending waits for that part to land. + () => + setStory((current) => + current.stage === 'still' ? { stage: 'opening', plays: current.plays + 1 } : current, + ), + // Leaving during the hold ends the story. Leaving during the opening + // waits for it to land. + () => + setStory((current) => + current.stage === 'holding' ? { ...current, stage: 'ending' } : current, + ), + ); + + // Stable, so an illustration can list them in an effect's deps without + // restarting the part under way on every render. + const onOpeningEnd = useCallback(() => { + const holding = pointerOnCardRef.current; + + setStory((current) => + current.stage === 'opening' ? { ...current, stage: holding ? 'holding' : 'ending' } : current, + ); + }, [pointerOnCardRef]); + + const onStoryEnd = useCallback(() => { + const reopening = pointerOnCardRef.current; + + setStory((current) => { + if (current.stage !== 'ending') return current; + + return reopening + ? { stage: 'opening', plays: current.plays + 1 } + : { ...current, stage: 'still' }; + }); + }, [pointerOnCardRef]); + + return { ...story, onOpeningEnd, onStoryEnd }; +} From 3c1774031b69357c43d047c8e801954c19718c1e Mon Sep 17 00:00:00 2001 From: Hunter Garrett Date: Sat, 10 Oct 2026 14:35:10 -0400 Subject: [PATCH 3/8] feat(docs): play the Performant card's story on hover Draw the mock's GPU chip alone at 1.5 times its size. A hover warms the chip and starts a band of work flowing into and out of its pins, which runs while the pointer stays. Leaving cuts back to the still frame until the ending lands. --- .../performant-illustration.module.css | 184 +++++++++-- .../features/performant-illustration.tsx | 308 +++++++++++++++++- 2 files changed, 444 insertions(+), 48 deletions(-) diff --git a/apps/docs/src/components/features/performant-illustration.module.css b/apps/docs/src/components/features/performant-illustration.module.css index c81e6640..1db8d699 100644 --- a/apps/docs/src/components/features/performant-illustration.module.css +++ b/apps/docs/src/components/features/performant-illustration.module.css @@ -1,92 +1,212 @@ -/* The Performant illustration, after the Figma mock's node 939:962. Every +/* The Performant illustration, after the Figma mock's node 939:962: its + GPU chip, alone and half again its size, centered in the window. Every length is in the mock's pixels times --mock-px, one mock pixel at the card window's current width. The window is 312 mock pixels wide, and features.module.css makes it the container that `cqw` measures, so the - illustration scales like the image it replaced at every column width. */ + illustration scales like the image it replaced at every column width. + The chip's own lengths are the mock's chip pixels times --chip-px, 1.5 + mock pixels, so the mock's 100px chip draws 150px wide. + + The pinks are the mock's: --glow lights the chip and the large + particles, and --glow-text, a deeper pink, the chip's name and the small + particles. */ .illustration { --mock-px: calc(100cqw / 312); + --chip-px: calc(1.5 * var(--mock-px)); + --glow: #fd73d5; + --glow-text: #fc40c6; + position: relative; display: grid; place-items: center; aspect-ratio: 312 / 309; } -/* The two chips side by side, 24px apart, centered in the window. */ -.chips { - display: flex; - gap: calc(24 * var(--mock-px)); -} +/* ---- The chip ---- + 100 chip pixels wide: a 100px body with a row of pins above and below + it. Its --lit, which the story sets, runs from 0, the mock's gray GPU + chip, to 1, the mock's glowing CPU chip, and every layer below mixes by + it. A layer of the gray look fades out as its lit counterpart fades in. -/* ---- A chip ---- - 100px wide: a 100px body with a row of pins above and below it. */ + Several layers blend with what lies beneath them, so nothing from the + chip up to the card's window may set opacity, a transform, or anything + else that would isolate them from the window's gray. */ .chip { + position: relative; display: flex; flex-direction: column; align-items: center; - width: calc(100 * var(--mock-px)); + width: calc(100 * var(--chip-px)); } /* Four pins, 6.67 by 8.89 with 8.89 between them, their outer ends - rounded. The bottom row turns over, so its rounded ends point down. */ + rounded. Gray pins are 70% white. Lit pins are full white laid over the + glow with an overlay blend, which picks up its pink. */ .pins { display: flex; - gap: calc(8.889 * var(--mock-px)); + gap: calc(8.889 * var(--chip-px)); +} + +.pin { + --pin-radius: calc(8.889 * var(--chip-px)) calc(8.889 * var(--chip-px)) 0 0; + + position: relative; + width: calc(6.667 * var(--chip-px)); + height: calc(8.889 * var(--chip-px)); } -.pins[data-edge='bottom'] { - rotate: 180deg; +.pins[data-edge='bottom'] .pin { + --pin-radius: 0 0 calc(8.889 * var(--chip-px)) calc(8.889 * var(--chip-px)); } -.pin { - width: calc(6.667 * var(--mock-px)); - height: calc(8.889 * var(--mock-px)); - border-radius: calc(8.889 * var(--mock-px)) calc(8.889 * var(--mock-px)) 0 0; +.pin::before, +.pin::after { + position: absolute; + inset: 0; + border-radius: var(--pin-radius); + content: ''; +} + +.pin::before { background: var(--white-a70); + opacity: calc(1 - var(--lit, 0)); +} + +.pin::after { + background: var(--white); + mix-blend-mode: overlay; + opacity: var(--lit, 0); } -/* The body's outer square. Its color is the mock's: 90% white laid over - the window's gray with an overlay blend, as the Extensible code window's - frame is. The blend layer sits under the inner squares, which are - positioned so they paint over it. */ +/* The body's outer square. Gray, it is 90% white laid over the window's + gray with an overlay blend, as the Extensible code window's frame is. + The blend layer sits under the inner squares, which are positioned so + they paint over it. */ .body { position: relative; width: 100%; aspect-ratio: 1; - padding: calc(3.333 * var(--mock-px)); + padding: calc(3.333 * var(--chip-px)); } .body::before { position: absolute; inset: 0; - border-radius: calc(8.889 * var(--mock-px)); + border-radius: calc(8.889 * var(--chip-px)); background: var(--white-a90); content: ''; mix-blend-mode: overlay; + opacity: calc(1 - var(--lit, 0)); +} + +/* Lit, the outer square glows: the mock's pink halo, white close to the + edge, and pink under the body, with the outer square's 20% white over + it. It sits on the body's square, under the top pins' 8.89px, and paints + first, under the pins and the body. */ +.glow { + position: absolute; + top: calc(8.889 * var(--chip-px)); + left: 0; + width: 100%; + aspect-ratio: 1; + border-radius: calc(8.889 * var(--chip-px)); + background: color-mix(in srgb, var(--glow), var(--white) 20%); + box-shadow: + 0 0 calc(50.4 * var(--chip-px)) color-mix(in srgb, var(--glow) 50%, transparent), + 0 0 calc(28.8 * var(--chip-px)) var(--glow), + 0 0 calc(16.8 * var(--chip-px)) var(--glow), + 0 0 calc(8.4 * var(--chip-px)) var(--white), + 0 0 calc(2.4 * var(--chip-px)) var(--white), + 0 0 calc(1.2 * var(--chip-px)) var(--white); + opacity: var(--lit, 0); } -/* The middle square, 3.33px inside the outer one, at 20% white. */ +/* The middle square, 3.33px inside the outer one, at 20% white in both + looks. */ .ring { position: relative; height: 100%; - padding: calc(3.333 * var(--mock-px)); - border-radius: calc(5.556 * var(--mock-px)); + padding: calc(3.333 * var(--chip-px)); + border-radius: calc(5.556 * var(--chip-px)); background: var(--white-a20); } -/* The center square, 3.33px inside the middle one, at 30% white, with the - chip's name centered in it. */ +/* The center square, 3.33px inside the middle one, with the chip's name + centered in it. Gray, it is 30% white. Lit, it is 40% white with a + screen blend, which lightens the pink under it toward white. */ .core { + position: relative; display: grid; place-items: center; height: 100%; - border-radius: calc(2.222 * var(--mock-px)); +} + +.core::before, +.coreLit { + position: absolute; + inset: 0; + border-radius: calc(2.222 * var(--chip-px)); +} + +.core::before { background: var(--white-a30); + content: ''; + opacity: calc(1 - var(--lit, 0)); +} + +.coreLit { + background: var(--white-a40); + mix-blend-mode: screen; + opacity: var(--lit, 0); } +/* Positioned, so it paints over the center square's layers. */ .name { - color: var(--black-a60); + position: relative; + color: color-mix(in srgb, var(--black-a60), var(--glow-text) calc(var(--lit, 0) * 100%)); font-family: var(--font-mono); - font-size: calc(21.756 * var(--mock-px)); + font-size: calc(21.756 * var(--chip-px)); font-weight: var(--font-weight-semibold); line-height: 1; } + +/* ---- The band of work ---- + A column of particles under the chip, as wide as the chip's 56px middle, + from the tips of its bottom pins to 8px past the window's bottom edge, + where the window clips it. The chip sits centered, so its pins end half + its 117.78px height below the window's middle. The mask fades a particle + out over the 16px under the pins, so it dissolves into the chip rather + than stopping at a hard edge. */ +.band { + position: absolute; + top: calc(50% + 58.889 * var(--chip-px)); + bottom: calc(-8 * var(--mock-px)); + left: calc(50% - 28 * var(--chip-px)); + width: calc(56 * var(--chip-px)); + mask-image: linear-gradient(to bottom, transparent, black calc(16 * var(--mock-px))); +} + +/* A particle's lane: a strip the band's full height, starting a particle's + height above it, so a particle at the top of its trip sits wholly above + the band. The story moves it down by a share of its own height. */ +.lane { + position: absolute; + top: calc(-8 * var(--mock-px)); + height: 100%; +} + +.particle { + display: block; +} + +.particle[data-size='large'] { + width: calc(6 * var(--mock-px)); + height: calc(8 * var(--mock-px)); + background: var(--glow); +} + +.particle[data-size='small'] { + width: calc(4 * var(--mock-px)); + height: calc(5 * var(--mock-px)); + background: var(--glow-text); +} diff --git a/apps/docs/src/components/features/performant-illustration.tsx b/apps/docs/src/components/features/performant-illustration.tsx index 5610dfec..3e6e9a8e 100644 --- a/apps/docs/src/components/features/performant-illustration.tsx +++ b/apps/docs/src/components/features/performant-illustration.tsx @@ -1,13 +1,42 @@ +'use client'; + /** - * The Performant feature card's illustration, after the Figma mock: a CPU - * chip beside a GPU chip. The still frame (SHA-215) shows both chips idle - * and gray, with no work flowing to either, so its story can hand the work - * to the GPU and wind it down there. + * The Performant feature card's illustration: the Figma mock's GPU chip, + * alone and half again its size, as the author settled during SHA-215. The + * still frame shows the chip idle and gray. Its story warms the chip and + * starts a band of work flowing under it, which runs while the pointer + * stays. story.tsx says when each part plays. */ +import { type CSSProperties, useEffect } from 'react'; + +import { + animate, + type AnimationSequence, + domMin, + LazyMotion, + m, + type MotionStyle, + type MotionValue, + useAnimationFrame, + useMotionValue, + useTransform, +} from 'motion/react'; + +import { EASE_OUT } from '@/lib/easing'; + import styles from './performant-illustration.module.css'; +import { type HeldStage, useHeldStory } from './story'; + +// --------------------------------------------- +// The chip +// --------------------------------------------- -// The four pins along a chip's top or bottom edge. The bottom row is the -// top row turned over, so its rounded ends point down. +// How lit the chip is, from 0, the mock's gray chip, to 1, the mock's +// glowing one. performant-illustration.module.css mixes every layer of the +// chip by it. +type LitStyle = MotionStyle & { '--lit': MotionValue }; + +// The four pins along the chip's top or bottom edge. function Pins({ edge }: { edge: 'top' | 'bottom' }) { return (
@@ -19,33 +48,280 @@ function Pins({ edge }: { edge: 'top' | 'bottom' }) { ); } -// A chip: its body, three nested squares that step lighter toward the -// center, with its name in the middle and four pins above and below. -function Chip({ name }: { name: string }) { +// The chip: its body, three nested squares that step lighter toward the +// center, with its name in the middle and four pins above and below. The +// glow and the lit center are separate layers, because a lit chip blends +// its layers differently from a gray one. The glow comes first, so both +// rows of pins paint over its halo and pick up its color. +function Chip({ lit }: { lit: MotionValue }) { + const style: LitStyle = { '--lit': lit }; + return ( -
+ +
- {name} + + GPU
+
+ ); +} + +// --------------------------------------------- +// The work +// --------------------------------------------- + +/** A particle in the band of work under the chip. */ +interface Particle { + /** Names the particle: its place in the band's fill order. */ + id: number; + /** The particle's left edge, in percent of the band's width. */ + left: number; + /** + * A large particle is the mock's 6 by 8 in the glow's pink, and a small + * one the mock's 4 by 5 in the deeper pink. + */ + size: 'large' | 'small'; + /** How strongly the particle shows once it has faded in, from 0 to 1. */ + opacity: number; + /** An `up` particle rises into the chip, and a `down` one falls out of it. */ + direction: 'up' | 'down'; + /** Trips across the band per second at the band's full pace. */ + speed: number; + /** Where in its trip the particle is at the start of a play, from 0 to 1. */ + phase: number; +} + +// The share of particles that rise into the chip. The rest fall out of it. +const RISING_SHARE = 0.6; + +// The share of particles that are the mock's large ones. +const LARGE_SHARE = 0.4; + +// The strengths a particle shows at, picked at random, so the band layers +// like the reference: some bright, some faint. +const OPACITIES = [1, 0.6, 0.35] as const; + +// The range of particle speeds, in trips across the band per second at +// full pace. A trip is the band's height, about 75 mock pixels. +const SLOWEST_SPEED = 0.9; +const FASTEST_SPEED = 1.8; + +// The band is 84 mock pixels wide and a particle at most 6, so a particle's +// left edge stays within the first 78. +const LANE_PERCENT = (78 / 84) * 100; + +/** + * A seeded random number generator (mulberry32), so the particles land in + * the same places on every render, on the server and in the browser. + */ +function seededRandom(seed: number): () => number { + let state = seed; + + return () => { + state = (state + 0x6d2b79f5) | 0; + let mixed = Math.imul(state ^ (state >>> 15), 1 | state); + + mixed = (mixed + Math.imul(mixed ^ (mixed >>> 7), 61 | mixed)) ^ mixed; + + return ((mixed ^ (mixed >>> 14)) >>> 0) / 4294967296; + }; +} + +/** The band's particles, scattered at random from `seed`. */ +function scatterParticles(seed: number, count: number): readonly Particle[] { + const random = seededRandom(seed); + + return Array.from({ length: count }, (_, id) => ({ + id, + left: random() * LANE_PERCENT, + size: random() < LARGE_SHARE ? 'large' : 'small', + opacity: OPACITIES[Math.floor(random() * OPACITIES.length)] ?? 1, + direction: random() < RISING_SHARE ? 'up' : 'down', + speed: SLOWEST_SPEED + random() * (FASTEST_SPEED - SLOWEST_SPEED), + phase: random(), + })); +} + +// How many particles the band holds. More crowd it. +const PARTICLE_COUNT = 30; + +const PARTICLES = scatterParticles(215, PARTICLE_COUNT); + +/** + * Where a particle is down the band, from 0 at the chip's pins to 1 past + * the window's bottom edge, once the band's flow has run `trips` trips at + * full pace. A trip that runs off one end starts again at the other. + */ +function bandPosition(particle: Particle, trips: number): number { + const progress = (particle.phase + particle.speed * trips) % 1; + + return particle.direction === 'up' ? 1 - progress : progress; +} + +function clampUnit(value: number): number { + return Math.min(1, Math.max(0, value)); +} + +/** + * A particle on its lane: a strip the band's full height, which the flow + * moves down by a share of its own height, so a step is the same share of + * the band at every card width. It fades in as the band's count of showing + * particles passes its place in the band, so the band fills in one particle + * after another. + */ +function WorkParticle({ + particle, + flow, + shown, +}: { + particle: Particle; + flow: MotionValue; + shown: MotionValue; +}) { + const y = useTransform(flow, (trips) => `${bandPosition(particle, trips) * 100}%`); + const opacity = useTransform(shown, (count) => particle.opacity * clampUnit(count - particle.id)); + const laneStyle: CSSProperties = { left: `${particle.left}%` }; + + return ( + + + + ); +} + +/** + * The band of work under the chip. Its flow runs on a clock of its own, + * which advances by the band's pace every frame, so the pace can ease up + * without the particles jumping. + */ +function WorkBand({ pace, shown }: { pace: MotionValue; shown: MotionValue }) { + // How many trips the band's flow has run at full pace. + const flow = useMotionValue(0); + + useAnimationFrame((_, deltaMs) => { + flow.set(flow.get() + (pace.get() * deltaMs) / 1000); + }); + + return ( +
+ {PARTICLES.map((particle) => ( + + ))}
); } +// --------------------------------------------- +// The story's timing +// --------------------------------------------- + +// From the motion brief on SHA-215, as the author tuned it in the dev +// server. The chip warms while the band fills in and gets up to pace. +// Everything here enters, so it all eases out. Durations are in seconds, +// as Motion takes them. + +// How long the chip takes to warm and the band to fill in and get up to +// pace. +const WARM_SECONDS = 0.3; + +// --------------------------------------------- +// The story +// --------------------------------------------- + +/** + * One play of the story: the band of work, mounted fresh for each play and + * gone on the still frame, so nothing runs a frame loop at rest. It warms + * the chip, which stays mounted, through its --lit. + */ +function Play({ + stage, + lit, + onOpeningEnd, + onStoryEnd, +}: { + stage: HeldStage; + lit: MotionValue; + onOpeningEnd: () => void; + onStoryEnd: () => void; +}) { + // The band's pace, as a share of full pace: 0 stopped. + const pace = useMotionValue(0); + // How many of the band's particles show, counted in order. + const shown = useMotionValue(0); + + // The opening, as one timeline: the chip warms, and the band fills in and + // gets up to pace. A fresh play mounts for each opening, and the motion + // values and callbacks never change, so this runs once per play. + useEffect(() => { + if (stage !== 'opening') return undefined; + + const warm = { duration: WARM_SECONDS, ease: EASE_OUT, at: 0 }; + const opening: AnimationSequence = [ + [lit, 1, warm], + [shown, PARTICLE_COUNT, warm], + [pace, 1, warm], + ]; + const controls = animate(opening); + let stopped = false; + + void controls.then(() => { + if (!stopped) onOpeningEnd(); + }); + + return () => { + stopped = true; + controls.stop(); + }; + }, [stage, lit, pace, shown, onOpeningEnd]); + + // For now the ending cuts straight back to the still frame. The drain + // replaces it in the next phase. + useEffect(() => { + if (stage !== 'ending') return; + + lit.set(0); + onStoryEnd(); + }, [stage, lit, onStoryEnd]); + + return ; +} + +// --------------------------------------------- +// The illustration +// --------------------------------------------- + // Hidden from screen readers: the card's title and description carry its -// message. +// message. The chip stays mounted and takes its glow from the play, which +// mounts fresh for each opening, keyed by the play's count. +// +// `m` under LazyMotion rather than `motion`, as in the Extensible card: the +// chip and the particles only bind motion values to style, which domMin +// covers. export function PerformantIllustration() { + const { stage, plays, onOpeningEnd, onStoryEnd } = useHeldStory(); + const lit = useMotionValue(0); + return (
-
- - -
+ + {stage !== 'still' && ( + + )} + +
); } From 934b1b8acdb2b7c03056775e925df31f7da1dc7f Mon Sep 17 00:00:00 2001 From: Hunter Garrett Date: Sat, 10 Oct 2026 18:54:45 -0400 Subject: [PATCH 4/8] feat(docs): light the Performant card's chip and drain its band on leave The still frame now shows the chip softly lit with no work under it. A hover streams particles in from the band's ends and brings the halo up to the mock's full glow. Once the pointer leaves, nothing new enters, each particle finishes its trip, and the halo settles as the last one leaves. Particles waiting at either end of the band are transparent, and the band clips itself, so none shows at the window's edge in any browser. The band streams in from the hover, so the held story drops its opening stage: a hover goes straight to the hold. --- .../performant-illustration.module.css | 139 +++---- .../features/performant-illustration.tsx | 358 ++++++++++-------- apps/docs/src/components/features/story.tsx | 56 ++- 3 files changed, 275 insertions(+), 278 deletions(-) diff --git a/apps/docs/src/components/features/performant-illustration.module.css b/apps/docs/src/components/features/performant-illustration.module.css index 1db8d699..f2b6970e 100644 --- a/apps/docs/src/components/features/performant-illustration.module.css +++ b/apps/docs/src/components/features/performant-illustration.module.css @@ -24,13 +24,12 @@ /* ---- The chip ---- 100 chip pixels wide: a 100px body with a row of pins above and below - it. Its --lit, which the story sets, runs from 0, the mock's gray GPU - chip, to 1, the mock's glowing CPU chip, and every layer below mixes by - it. A layer of the gray look fades out as its lit counterpart fades in. + it. The mock draws its CPU chip lit and its GPU chip gray, and this GPU + chip takes the lit look. Several layers blend with what lies beneath them, so nothing from the chip up to the card's window may set opacity, a transform, or anything - else that would isolate them from the window's gray. */ + else that would isolate them from the glow and the window's gray. */ .chip { position: relative; display: flex; @@ -40,77 +39,46 @@ } /* Four pins, 6.67 by 8.89 with 8.89 between them, their outer ends - rounded. Gray pins are 70% white. Lit pins are full white laid over the - glow with an overlay blend, which picks up its pink. */ + rounded: full white laid over the halo with an overlay blend, which + picks up its pink. */ .pins { display: flex; gap: calc(8.889 * var(--chip-px)); } .pin { - --pin-radius: calc(8.889 * var(--chip-px)) calc(8.889 * var(--chip-px)) 0 0; - - position: relative; width: calc(6.667 * var(--chip-px)); height: calc(8.889 * var(--chip-px)); -} - -.pins[data-edge='bottom'] .pin { - --pin-radius: 0 0 calc(8.889 * var(--chip-px)) calc(8.889 * var(--chip-px)); -} - -.pin::before, -.pin::after { - position: absolute; - inset: 0; - border-radius: var(--pin-radius); - content: ''; -} - -.pin::before { - background: var(--white-a70); - opacity: calc(1 - var(--lit, 0)); -} - -.pin::after { + border-radius: calc(8.889 * var(--chip-px)) calc(8.889 * var(--chip-px)) 0 0; background: var(--white); mix-blend-mode: overlay; - opacity: var(--lit, 0); -} - -/* The body's outer square. Gray, it is 90% white laid over the window's - gray with an overlay blend, as the Extensible code window's frame is. - The blend layer sits under the inner squares, which are positioned so - they paint over it. */ -.body { - position: relative; - width: 100%; - aspect-ratio: 1; - padding: calc(3.333 * var(--chip-px)); } -.body::before { - position: absolute; - inset: 0; - border-radius: calc(8.889 * var(--chip-px)); - background: var(--white-a90); - content: ''; - mix-blend-mode: overlay; - opacity: calc(1 - var(--lit, 0)); +.pins[data-edge='bottom'] .pin { + border-radius: 0 0 calc(8.889 * var(--chip-px)) calc(8.889 * var(--chip-px)); } -/* Lit, the outer square glows: the mock's pink halo, white close to the - edge, and pink under the body, with the outer square's 20% white over - it. It sits on the body's square, under the top pins' 8.89px, and paints - first, under the pins and the body. */ -.glow { +/* The glow, in two layers on the body's square, under the top pins' + 8.89px. Both paint first, under the pins and the body. The fill is the + pink under the body, with the body's outer square, 20% white, over it. + The halo is the mock's pink glow around the chip, white close to the + edge. Its --halo, which the story sets, runs from a soft glow on the + still frame to the mock's full glow while the band of work flows. */ +.fill, +.halo { position: absolute; top: calc(8.889 * var(--chip-px)); left: 0; width: 100%; aspect-ratio: 1; border-radius: calc(8.889 * var(--chip-px)); +} + +.fill { background: color-mix(in srgb, var(--glow), var(--white) 20%); +} + +.halo { box-shadow: 0 0 calc(50.4 * var(--chip-px)) color-mix(in srgb, var(--glow) 50%, transparent), 0 0 calc(28.8 * var(--chip-px)) var(--glow), @@ -118,13 +86,19 @@ 0 0 calc(8.4 * var(--chip-px)) var(--white), 0 0 calc(2.4 * var(--chip-px)) var(--white), 0 0 calc(1.2 * var(--chip-px)) var(--white); - opacity: var(--lit, 0); + opacity: var(--halo, 1); } -/* The middle square, 3.33px inside the outer one, at 20% white in both - looks. */ -.ring { +/* The body, its outer square drawn by the fill beneath it. */ +.body { position: relative; + width: 100%; + aspect-ratio: 1; + padding: calc(3.333 * var(--chip-px)); +} + +/* The middle square, 3.33px inside the outer one, at 20% white. */ +.ring { height: 100%; padding: calc(3.333 * var(--chip-px)); border-radius: calc(5.556 * var(--chip-px)); @@ -132,8 +106,9 @@ } /* The center square, 3.33px inside the middle one, with the chip's name - centered in it. Gray, it is 30% white. Lit, it is 40% white with a - screen blend, which lightens the pink under it toward white. */ + centered in it: 40% white with a screen blend, which lightens the pink + under it toward white. The blend is a layer of its own, so it leaves the + name alone. */ .core { position: relative; display: grid; @@ -141,29 +116,19 @@ height: 100%; } -.core::before, -.coreLit { +.core::before { position: absolute; inset: 0; border-radius: calc(2.222 * var(--chip-px)); -} - -.core::before { - background: var(--white-a30); - content: ''; - opacity: calc(1 - var(--lit, 0)); -} - -.coreLit { background: var(--white-a40); + content: ''; mix-blend-mode: screen; - opacity: var(--lit, 0); } -/* Positioned, so it paints over the center square's layers. */ +/* Positioned, so it paints over the center square's blend layer. */ .name { position: relative; - color: color-mix(in srgb, var(--black-a60), var(--glow-text) calc(var(--lit, 0) * 100%)); + color: var(--glow-text); font-family: var(--font-mono); font-size: calc(21.756 * var(--chip-px)); font-weight: var(--font-weight-semibold); @@ -171,28 +136,32 @@ } /* ---- The band of work ---- - A column of particles under the chip, as wide as the chip's 56px middle, - from the tips of its bottom pins to 8px past the window's bottom edge, - where the window clips it. The chip sits centered, so its pins end half - its 117.78px height below the window's middle. The mask fades a particle - out over the 16px under the pins, so it dissolves into the chip rather - than stopping at a hard edge. */ + A column of particles under the chip, as wide as the chip's middle + square, 56 chip pixels, from the tips of its bottom pins to the window's + bottom edge. The chip sits centered, so its pins end half its 117.78 + chip pixels of height below the window's middle. The band clips its own + particles, so one waiting at either end stays hidden without leaning on + the window's rounded clip, which some browsers skip for moving content. + The mask fades a particle out over the 16 mock pixels under the pins, so + it dissolves into the chip rather than stopping at a hard edge. */ .band { position: absolute; top: calc(50% + 58.889 * var(--chip-px)); - bottom: calc(-8 * var(--mock-px)); + bottom: 0; left: calc(50% - 28 * var(--chip-px)); width: calc(56 * var(--chip-px)); + overflow: clip; mask-image: linear-gradient(to bottom, transparent, black calc(16 * var(--mock-px))); } -/* A particle's lane: a strip the band's full height, starting a particle's - height above it, so a particle at the top of its trip sits wholly above - the band. The story moves it down by a share of its own height. */ +/* A particle's lane: a strip a particle's height taller than the band, + starting that height above it, so a particle at either end of its trip + sits wholly outside the band. The story moves it down by a share of the + lane's height. */ .lane { position: absolute; top: calc(-8 * var(--mock-px)); - height: 100%; + height: calc(100% + 8 * var(--mock-px)); } .particle { diff --git a/apps/docs/src/components/features/performant-illustration.tsx b/apps/docs/src/components/features/performant-illustration.tsx index 3e6e9a8e..f770a052 100644 --- a/apps/docs/src/components/features/performant-illustration.tsx +++ b/apps/docs/src/components/features/performant-illustration.tsx @@ -2,22 +2,24 @@ /** * The Performant feature card's illustration: the Figma mock's GPU chip, - * alone and half again its size, as the author settled during SHA-215. The - * still frame shows the chip idle and gray. Its story warms the chip and - * starts a band of work flowing under it, which runs while the pointer - * stays. story.tsx says when each part plays. + * alone, half again its size, and lit. The still frame shows the chip + * softly lit, with no work under it. Its story (SHA-215) streams a band of + * work into and out of the chip's pins and brings the glow up while the + * pointer stays, then lets the band run dry and the glow settle. story.tsx + * says when each part plays. */ -import { type CSSProperties, useEffect } from 'react'; +import { type CSSProperties, useEffect, useRef, useState } from 'react'; import { - animate, - type AnimationSequence, + cancelFrame, + cubicBezier, domMin, + frame, LazyMotion, m, type MotionStyle, type MotionValue, - useAnimationFrame, + motionValue, useMotionValue, useTransform, } from 'motion/react'; @@ -31,11 +33,6 @@ import { type HeldStage, useHeldStory } from './story'; // The chip // --------------------------------------------- -// How lit the chip is, from 0, the mock's gray chip, to 1, the mock's -// glowing one. performant-illustration.module.css mixes every layer of the -// chip by it. -type LitStyle = MotionStyle & { '--lit': MotionValue }; - // The four pins along the chip's top or bottom edge. function Pins({ edge }: { edge: 'top' | 'bottom' }) { return ( @@ -48,22 +45,25 @@ function Pins({ edge }: { edge: 'top' | 'bottom' }) { ); } +// The halo's strength, from 0, none, to 1, the mock's full glow. +// performant-illustration.module.css fades the halo by it. +type HaloStyle = MotionStyle & { '--halo': MotionValue }; + // The chip: its body, three nested squares that step lighter toward the // center, with its name in the middle and four pins above and below. The -// glow and the lit center are separate layers, because a lit chip blends -// its layers differently from a gray one. The glow comes first, so both -// rows of pins paint over its halo and pick up its color. -function Chip({ lit }: { lit: MotionValue }) { - const style: LitStyle = { '--lit': lit }; +// glow's two layers come first, so both rows of pins paint over the halo +// and pick up its color. +function Chip({ halo }: { halo: MotionValue }) { + const style: HaloStyle = { '--halo': halo }; return ( - + +
- GPU
@@ -79,7 +79,7 @@ function Chip({ lit }: { lit: MotionValue }) { /** A particle in the band of work under the chip. */ interface Particle { - /** Names the particle: its place in the band's fill order. */ + /** Names the particle, for its React key. */ id: number; /** The particle's left edge, in percent of the band's width. */ left: number; @@ -88,28 +88,39 @@ interface Particle { * one the mock's 4 by 5 in the deeper pink. */ size: 'large' | 'small'; - /** How strongly the particle shows once it has faded in, from 0 to 1. */ + /** How strongly the particle shows, from 0 to 1. */ opacity: number; /** An `up` particle rises into the chip, and a `down` one falls out of it. */ direction: 'up' | 'down'; - /** Trips across the band per second at the band's full pace. */ + /** Trips across the band per second. */ speed: number; - /** Where in its trip the particle is at the start of a play, from 0 to 1. */ - phase: number; + /** Seconds from the hover until the particle first enters the band. */ + delay: number; } +// How many particles the band holds once it has filled. More crowd it. +const PARTICLE_COUNT = 30; + +// How long the band takes to fill, in seconds: the last particle enters by +// then. Longer streams the work in more gradually. +const FILL_SECONDS = 0.8; + // The share of particles that rise into the chip. The rest fall out of it. const RISING_SHARE = 0.6; -// The share of particles that are the mock's large ones. +// The share of particles that are the mock's large ones, in the paler pink. +// Higher makes the band heavier and paler, lower finer and deeper pink. const LARGE_SHARE = 0.4; // The strengths a particle shows at, picked at random, so the band layers -// like the reference: some bright, some faint. +// like the reference: some bright, some faint. Fainter strengths give the +// band more depth and less punch. const OPACITIES = [1, 0.6, 0.35] as const; -// The range of particle speeds, in trips across the band per second at -// full pace. A trip is the band's height, about 75 mock pixels. +// The range of particle speeds, in trips per second. A trip runs the +// particle's lane, from one end to the other: the band's height plus a +// particle's, about 74 mock pixels. Faster speeds make the band busier and +// shorten the drain, which takes one trip of the slowest particle at most. const SLOWEST_SPEED = 0.9; const FASTEST_SPEED = 1.8; @@ -134,59 +145,55 @@ function seededRandom(seed: number): () => number { }; } -/** The band's particles, scattered at random from `seed`. */ -function scatterParticles(seed: number, count: number): readonly Particle[] { +/** + * The band's particles, scattered at random from `seed`. Their entries + * spread evenly across the fill, each at a random moment within its share, + * so the band fills steadily rather than in bursts. + */ +function scatterParticles(seed: number): readonly Particle[] { const random = seededRandom(seed); - return Array.from({ length: count }, (_, id) => ({ + return Array.from({ length: PARTICLE_COUNT }, (_, id) => ({ id, left: random() * LANE_PERCENT, size: random() < LARGE_SHARE ? 'large' : 'small', opacity: OPACITIES[Math.floor(random() * OPACITIES.length)] ?? 1, direction: random() < RISING_SHARE ? 'up' : 'down', speed: SLOWEST_SPEED + random() * (FASTEST_SPEED - SLOWEST_SPEED), - phase: random(), + delay: (FILL_SECONDS * (id + random())) / PARTICLE_COUNT, })); } -// How many particles the band holds. More crowd it. -const PARTICLE_COUNT = 30; - -const PARTICLES = scatterParticles(215, PARTICLE_COUNT); +// Any fixed seed works, and 215, the ticket's number, is as good as any. +// Another seed reshuffles the band. +const PARTICLES = scatterParticles(215); /** - * Where a particle is down the band, from 0 at the chip's pins to 1 past - * the window's bottom edge, once the band's flow has run `trips` trips at - * full pace. A trip that runs off one end starts again at the other. + * Where a particle is down its lane, from 0 at the top to 1 at the bottom, + * once it has run `trips` trips. A trip starts at the particle's entry end, + * just outside the band: under the window's edge for a particle that rises, + * and above the band, under the chip's pins, for one that falls. A trip + * that runs off one end starts again at the other, so a whole number of + * trips puts the particle back at its entry end. */ -function bandPosition(particle: Particle, trips: number): number { - const progress = (particle.phase + particle.speed * trips) % 1; - - return particle.direction === 'up' ? 1 - progress : progress; -} +function bandPosition(direction: Particle['direction'], trips: number): number { + const progress = trips - Math.floor(trips); -function clampUnit(value: number): number { - return Math.min(1, Math.max(0, value)); + return direction === 'up' ? 1 - progress : progress; } /** - * A particle on its lane: a strip the band's full height, which the flow - * moves down by a share of its own height, so a step is the same share of - * the band at every card width. It fades in as the band's count of showing - * particles passes its place in the band, so the band fills in one particle - * after another. + * A particle on its lane: a strip a little taller than the band, which the + * particle's trips move down by a share of its own height, so a step is the + * same share of the band at every card width. The particle shows only while + * it is on a trip. Before its first, and once the ending stops it at the end + * of one, it waits at its entry end: just outside the band, and transparent + * besides, because a clip at the band's edge can let a pixel's sliver + * through. */ -function WorkParticle({ - particle, - flow, - shown, -}: { - particle: Particle; - flow: MotionValue; - shown: MotionValue; -}) { - const y = useTransform(flow, (trips) => `${bandPosition(particle, trips) * 100}%`); - const opacity = useTransform(shown, (count) => particle.opacity * clampUnit(count - particle.id)); +function WorkParticle({ particle, trips }: { particle: Particle; trips: MotionValue }) { + const y = useTransform(trips, (run) => `${bandPosition(particle.direction, run) * 100}%`); + const opacity = useTransform(trips, (run) => (run > 0 && run % 1 !== 0 ? particle.opacity : 0)); const laneStyle: CSSProperties = { left: `${particle.left}%` }; return ( @@ -196,101 +203,131 @@ function WorkParticle({ ); } -/** - * The band of work under the chip. Its flow runs on a clock of its own, - * which advances by the band's pace every frame, so the pace can ease up - * without the particles jumping. - */ -function WorkBand({ pace, shown }: { pace: MotionValue; shown: MotionValue }) { - // How many trips the band's flow has run at full pace. - const flow = useMotionValue(0); - - useAnimationFrame((_, deltaMs) => { - flow.set(flow.get() + (pace.get() * deltaMs) / 1000); - }); - - return ( -
- {PARTICLES.map((particle) => ( - - ))} -
- ); -} - // --------------------------------------------- -// The story's timing +// The story // --------------------------------------------- -// From the motion brief on SHA-215, as the author tuned it in the dev -// server. The chip warms while the band fills in and gets up to pace. -// Everything here enters, so it all eases out. Durations are in seconds, -// as Motion takes them. +// From the motion brief on SHA-215. Every particle moves at a constant +// speed, so only the glow eases: with the site's --ease-out, as anything +// warming or cooling does. -// How long the chip takes to warm and the band to fill in and get up to -// pace. -const WARM_SECONDS = 0.3; +// The halo's strength on the still frame, from 0, none, to 1, the mock's +// full glow, which it rises to as the band fills. Lower makes the hover's +// lift bigger. +const STILL_FRAME_HALO = 0.4; -// --------------------------------------------- -// The story -// --------------------------------------------- +// The shortest the halo takes to settle back once the pointer leaves, in +// seconds. It settles over the band's drain, so it lands as the last +// particle leaves, but takes at least this long when the drain is quicker. +const SHORTEST_SETTLE_SECONDS = 0.4; + +const easeOut = cubicBezier(...EASE_OUT); + +/** A particle and how many trips it has run this play. */ +interface BandParticle { + particle: Particle; + trips: MotionValue; +} /** - * One play of the story: the band of work, mounted fresh for each play and - * gone on the still frame, so nothing runs a frame loop at rest. It warms - * the chip, which stays mounted, through its --lit. + * Runs the band's flow and the glow for one play, from the hover until the + * band runs dry. Each particle enters after its delay and loops at its own + * speed, while the halo rises to full over the band's fill. Once the + * story's ending starts, nothing new enters: each particle in the band + * finishes the trip it is on, one still waiting never enters, and the halo + * settles back over the time the last trip takes. When both are done, the + * band and the glow are back on the still frame and the story ends. */ -function Play({ - stage, - lit, - onOpeningEnd, - onStoryEnd, -}: { - stage: HeldStage; - lit: MotionValue; - onOpeningEnd: () => void; - onStoryEnd: () => void; -}) { - // The band's pace, as a share of full pace: 0 stopped. - const pace = useMotionValue(0); - // How many of the band's particles show, counted in order. - const shown = useMotionValue(0); - - // The opening, as one timeline: the chip warms, and the band fills in and - // gets up to pace. A fresh play mounts for each opening, and the motion - // values and callbacks never change, so this runs once per play. - useEffect(() => { - if (stage !== 'opening') return undefined; - - const warm = { duration: WARM_SECONDS, ease: EASE_OUT, at: 0 }; - const opening: AnimationSequence = [ - [lit, 1, warm], - [shown, PARTICLE_COUNT, warm], - [pace, 1, warm], - ]; - const controls = animate(opening); - let stopped = false; +function useBandFlow( + band: readonly BandParticle[], + halo: MotionValue, + stage: HeldStage, + plays: number, + onStoryEnd: () => void, +) { + // The stage as the frame loop sees it, kept current without restarting + // the loop. + const stageRef = useRef(stage); - void controls.then(() => { - if (!stopped) onOpeningEnd(); - }); + useEffect(() => { + stageRef.current = stage; + }); - return () => { - stopped = true; - controls.stop(); - }; - }, [stage, lit, pace, shown, onOpeningEnd]); + const playing = stage !== 'still'; - // For now the ending cuts straight back to the still frame. The drain - // replaces it in the next phase. + // One loop per play, keyed by the play's count, so a play that follows + // straight on from another's ending starts the band from empty again. useEffect(() => { - if (stage !== 'ending') return; + if (!playing) return undefined; + + let seconds = 0; + // Set once the ending starts: where each particle stops, in trips, and + // how the halo settles from where it was. + let ending: + | { stops: number[]; startSeconds: number; fromHalo: number; settleSeconds: number } + | undefined; + const advance = ({ delta }: { delta: number }) => { + seconds += delta / 1000; + + const runs = band.map(({ particle }) => + Math.max(0, particle.speed * (seconds - particle.delay)), + ); + + if (stageRef.current === 'ending' && !ending) { + const stops = runs.map((run) => (run > 0 ? Math.floor(run) + 1 : 0)); + const drainSeconds = Math.max( + 0, + ...band.map( + ({ particle }, index) => ((stops[index] ?? 0) - (runs[index] ?? 0)) / particle.speed, + ), + ); + + ending = { + stops, + startSeconds: seconds, + fromHalo: halo.get(), + settleSeconds: Math.max(drainSeconds, SHORTEST_SETTLE_SECONDS), + }; + } + + // Whether any particle is still on the move, which only the ending's + // stops can end. + let moving = false; + + for (const [index, { trips }] of band.entries()) { + const run = runs[index] ?? 0; + const stop = ending?.stops[index] ?? Infinity; + + trips.set(Math.min(run, stop)); + if (run < stop) moving = true; + } + + if (!ending) { + halo.set( + STILL_FRAME_HALO + (1 - STILL_FRAME_HALO) * easeOut(Math.min(1, seconds / FILL_SECONDS)), + ); + + return; + } + + const settled = Math.min(1, (seconds - ending.startSeconds) / ending.settleSeconds); + + halo.set(ending.fromHalo + (STILL_FRAME_HALO - ending.fromHalo) * easeOut(settled)); + + if (!moving && settled === 1) { + cancelFrame(advance); + onStoryEnd(); + } + }; - lit.set(0); - onStoryEnd(); - }, [stage, lit, onStoryEnd]); + frame.update(advance, true); - return ; + return () => { + cancelFrame(advance); + band.forEach(({ trips }) => trips.set(0)); + halo.set(STILL_FRAME_HALO); + }; + }, [playing, plays, band, halo, onStoryEnd]); } // --------------------------------------------- @@ -298,29 +335,32 @@ function Play({ // --------------------------------------------- // Hidden from screen readers: the card's title and description carry its -// message. The chip stays mounted and takes its glow from the play, which -// mounts fresh for each opening, keyed by the play's count. +// message. // // `m` under LazyMotion rather than `motion`, as in the Extensible card: the -// chip and the particles only bind motion values to style, which domMin +// particles and the chip only bind motion values to style, which domMin // covers. export function PerformantIllustration() { - const { stage, plays, onOpeningEnd, onStoryEnd } = useHeldStory(); - const lit = useMotionValue(0); + const { stage, plays, onStoryEnd } = useHeldStory(); + // How many trips each particle has run this play. At 0 it waits at its + // entry end, out of sight, which is where every particle sits on the + // still frame. + const [band] = useState(() => + PARTICLES.map((particle) => ({ particle, trips: motionValue(0) })), + ); + const halo = useMotionValue(STILL_FRAME_HALO); + + useBandFlow(band, halo, stage, plays, onStoryEnd); return (
- {stage !== 'still' && ( - - )} - +
+ {band.map(({ particle, trips }) => ( + + ))} +
+
); diff --git a/apps/docs/src/components/features/story.tsx b/apps/docs/src/components/features/story.tsx index aac6111a..fa713741 100644 --- a/apps/docs/src/components/features/story.tsx +++ b/apps/docs/src/components/features/story.tsx @@ -5,8 +5,8 @@ * the story, and a story under way always finishes after the pointer leaves. * Touch, keyboard, and reduced motion leave the card on its still frame. * StoryCard is the card. An illustration takes one of two story shapes: - * useStory repeats a play while the pointer stays, and useHeldStory opens, - * holds while the pointer stays, then ends. + * useStory repeats a play while the pointer stays, and useHeldStory holds + * while the pointer stays, then plays its ending. */ import { createContext, @@ -159,32 +159,29 @@ export function useStory(): Story { // --------------------------------------------- /** - * Where a held story is. It rests on the still frame, plays its opening on a - * hover, holds while the pointer stays, and plays its ending back to the - * still frame once the pointer leaves. + * Where a held story is. It rests on the still frame, holds from a hover + * for as long as the pointer stays, and plays its ending back to the still + * frame once the pointer leaves. */ -export type HeldStage = 'still' | 'opening' | 'holding' | 'ending'; +export type HeldStage = 'still' | 'holding' | 'ending'; -/** Where an illustration's held story is, which play, and how to end each part. */ +/** Where an illustration's held story is, which play, and how to end it. */ export interface HeldStory { stage: HeldStage; /** - * Counts the story's openings. An illustration keys its animation by it, - * so every play starts from the still frame. + * Counts the story's plays. An illustration keys its animation by it, so + * every play starts from the still frame. */ plays: number; - /** Called by the illustration when its opening lands on the hold. */ - onOpeningEnd: () => void; /** Called by the illustration when its ending lands on the still frame. */ onStoryEnd: () => void; } /** * The held story of the illustration that calls it, played by a hover on - * its StoryCard. Each part runs to its end once it starts. A pointer that - * leaves during the opening lets it land, then the ending plays with no - * hold. A pointer that comes back during the ending lets it land on the - * still frame, then a fresh opening plays. + * its StoryCard. Its ending runs to the still frame once it starts. A + * pointer that comes back during the ending lets it land, then a fresh play + * starts. */ export function useHeldStory(): HeldStory { const [story, setStory] = useState<{ stage: HeldStage; plays: number }>({ @@ -192,41 +189,32 @@ export function useHeldStory(): HeldStory { plays: 0, }); const pointerOnCardRef = useCardPointer( - // Only the still frame opens the story. A hover during the opening or - // the ending waits for that part to land. + // Only the still frame starts the story. A hover during the ending waits + // for it to land. () => setStory((current) => - current.stage === 'still' ? { stage: 'opening', plays: current.plays + 1 } : current, + current.stage === 'still' ? { stage: 'holding', plays: current.plays + 1 } : current, ), - // Leaving during the hold ends the story. Leaving during the opening - // waits for it to land. + // Leaving during the hold ends the story. () => setStory((current) => current.stage === 'holding' ? { ...current, stage: 'ending' } : current, ), ); - // Stable, so an illustration can list them in an effect's deps without - // restarting the part under way on every render. - const onOpeningEnd = useCallback(() => { - const holding = pointerOnCardRef.current; - - setStory((current) => - current.stage === 'opening' ? { ...current, stage: holding ? 'holding' : 'ending' } : current, - ); - }, [pointerOnCardRef]); - + // Stable, so an illustration can list it in an effect's deps without + // restarting the play under way on every render. const onStoryEnd = useCallback(() => { - const reopening = pointerOnCardRef.current; + const replaying = pointerOnCardRef.current; setStory((current) => { if (current.stage !== 'ending') return current; - return reopening - ? { stage: 'opening', plays: current.plays + 1 } + return replaying + ? { stage: 'holding', plays: current.plays + 1 } : { ...current, stage: 'still' }; }); }, [pointerOnCardRef]); - return { ...story, onOpeningEnd, onStoryEnd }; + return { ...story, onStoryEnd }; } From c8d07aab69ade411d1dc547bad9375e3689e0bb1 Mon Sep 17 00:00:00 2001 From: Hunter Garrett Date: Sat, 10 Oct 2026 19:02:22 -0400 Subject: [PATCH 5/8] test(docs-tests): cover the Performant card's story Check that a hover streams the band in and that it runs dry after the pointer leaves, back to the still frame. A hover during that ending must not cut it short, and touch and reduced motion must leave the still frame. The band carries a data-band attribute for the spec to find it. --- .../docs/features-performant.spec.ts | 287 ++++++++++++++++++ .../features/performant-illustration.tsx | 4 +- 2 files changed, 289 insertions(+), 2 deletions(-) create mode 100644 apps/docs-tests/docs/features-performant.spec.ts diff --git a/apps/docs-tests/docs/features-performant.spec.ts b/apps/docs-tests/docs/features-performant.spec.ts new file mode 100644 index 00000000..001e093e --- /dev/null +++ b/apps/docs-tests/docs/features-performant.spec.ts @@ -0,0 +1,287 @@ +import { expect, test } from '@playwright/test'; +import type { Locator, Page } from '@playwright/test'; + +/** + * The Performant feature card's story (SHA-215): a hover streams a band of + * work in under the chip, which keeps flowing while the pointer stays and + * runs dry once it leaves, back to the still frame with no work showing. A + * hover during that ending doesn't cut it short. Touch and reduced motion + * leave the still frame. Checks count the particles showing and watch them + * move, never how long anything takes or its curve, so tuning the motion + * cannot break this. + */ + +// No check here needs a scene, so this file turns WebGL off, as +// favorites-card-tab.spec.ts does. The hero keeps its poster rather than +// drawing full-width Aurora on CI's CPU renderer. A launch option forces a +// new browser, so it has to be set for the whole file. +test.use({ launchOptions: { args: ['--disable-webgl'] } }); + +const featuresList = (page: Page) => + page.getByRole('list', { name: 'We’ll handle the complex stuff' }); + +const performantCard = (page: Page) => + featuresList(page) + .getByRole('listitem') + .filter({ has: page.getByRole('heading', { name: 'Performant' }) }); + +// The illustration around the band of work the story streams. +const illustration = (card: Locator) => + card.locator('[aria-hidden="true"]').filter({ has: card.page().locator('[data-band]') }); + +// Scrolls the features section to the middle of the viewport, twice, for the +// reason favorites-hover.spec.ts gives: the section rides in the hero's +// sticky pin, and the hero finishes its change a frame after the first +// scroll, which moves the section again. +async function open(page: Page): Promise { + await page.goto('/'); + await page.waitForLoadState('networkidle'); + await featuresList(page).evaluate(async (list) => { + const section = list.closest('section')!; + const nextFrames = () => + new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve))); + + section.scrollIntoView({ block: 'center' }); + await nextFrames(); + section.scrollIntoView({ block: 'center' }); + }); +} + +/** Moves the pointer off the card, onto the site header. */ +async function leave(page: Page): Promise { + await page.mouse.move(0, 0); +} + +/** The band on one frame. */ +interface BandFrame { + /** How many particles show. */ + showing: number; + /** The band's particles' places, summed, which changes whenever one moves. */ + places: number; +} + +interface BandRecord { + /** Every frame so far, oldest first. */ + all: () => Promise; + /** The latest frame. */ + now: () => Promise; +} + +// Records the band on every frame from the moment this is called: how many +// of its particles show, and where they all are. +async function recordBand(card: Locator): Promise { + await card.evaluate((cardElement) => { + const frames: BandFrame[] = []; + const lanes = [...cardElement.querySelector('[data-band]')!.children]; + const measure = () => { + const styles = lanes.map((lane) => getComputedStyle(lane)); + + frames.push({ + showing: styles.filter((style) => Number(style.opacity) > 0).length, + places: styles.reduce((sum, style) => sum + new DOMMatrix(style.transform).m42, 0), + }); + requestAnimationFrame(measure); + }; + + (window as unknown as { bandFrames: BandFrame[] }).bandFrames = frames; + measure(); + }); + + const all = () => + card.page().evaluate(() => (window as unknown as { bandFrames: BandFrame[] }).bandFrames); + + return { all, now: async () => (await all()).at(-1)! }; +} + +/** How many particles the band holds once it has filled. */ +const bandSize = (card: Locator) => card.locator('[data-band] > *').count(); + +/** + * A picture of the illustration, a pixel inside its box, so a box that falls + * between device pixels never pulls in the card around the window. + */ +async function screenshotIllustration(card: Locator): Promise { + const box = (await illustration(card).boundingBox())!; + + return card.page().screenshot({ + clip: { x: box.x + 1, y: box.y + 1, width: box.width - 2, height: box.height - 2 }, + }); +} + +/** Expects no particle to have shown the whole time. */ +function expectStillThroughout(frames: BandFrame[]): void { + expect(frames.length).toBeGreaterThan(0); + expect(frames.filter((frame) => frame.showing > 0)).toEqual([]); +} + +// --------------------------------------------- +// Hover +// --------------------------------------------- + +test('a hover plays the story, which runs dry after the pointer leaves', async ({ page }) => { + await open(page); + + const card = performantCard(page); + const stillFrame = await screenshotIllustration(card); + const band = await recordBand(card); + + expect((await band.now()).showing).toBe(0); + + await card.hover(); + await expect.poll(async () => (await band.now()).showing).toBeGreaterThan(0); + + // The band carries on after the pointer leaves, its particles still + // moving, then runs dry and stays empty. + const framesBeforeLeaving = (await band.all()).length; + + await leave(page); + await expect.poll(async () => (await band.now()).showing).toBe(0); + + const ending = (await band.all()).slice(framesBeforeLeaving); + const stillShowing = ending.filter((frame) => frame.showing > 0); + + expect(new Set(stillShowing.map((frame) => frame.places)).size).toBeGreaterThan(1); + + const afterTheStory = await recordBand(card); + + await page.waitForTimeout(1_000); + expectStillThroughout(await afterTheStory.all()); + expect(await screenshotIllustration(card)).toEqual(stillFrame); +}); + +// A restart would cut the drain short, emptying the band at once, or bring +// particles back into it while it drains. The check fills the band, leaves +// and comes straight back, then reads every frame from the leave: the band +// empties a few particles at a time and never refills until it has run +// dry, and the story plays again after that, because the pointer is on the +// card. +test('a hover during the ending lets the band run dry before it plays again', async ({ page }) => { + await open(page); + + const card = performantCard(page); + const band = await recordBand(card); + const filled = await bandSize(card); + + await card.hover(); + await expect.poll(async () => (await band.now()).showing).toBe(filled); + + const framesBeforeLeaving = (await band.all()).length; + + await leave(page); + await card.hover(); + // The pointer is back while the band still drains. + expect((await band.now()).showing).toBeGreaterThan(0); + + const ranDryThenPlayed = async () => { + const since = (await band.all()).slice(framesBeforeLeaving); + const dry = since.findIndex((frame) => frame.showing === 0); + + return dry >= 0 && since.slice(dry).some((frame) => frame.showing > 0); + }; + + await expect.poll(ranDryThenPlayed).toBe(true); + + const since = (await band.all()).slice(framesBeforeLeaving); + const draining = since.slice( + 0, + since.findIndex((frame) => frame.showing === 0), + ); + + expect(draining.some((frame) => frame.showing > 0 && frame.showing < filled)).toBe(true); + draining.forEach((frame, index) => { + expect(frame.showing).toBeLessThanOrEqual(draining[index - 1]?.showing ?? filled); + }); +}); + +test('the story keeps going while the pointer stays', async ({ page }) => { + await open(page); + + const card = performantCard(page); + const band = await recordBand(card); + + await card.hover(); + await expect.poll(async () => (await band.now()).showing).toBeGreaterThan(0); + await page.waitForTimeout(3_000); + + const before = await band.now(); + + await expect.poll(async () => (await band.now()).places).not.toBe(before.places); + expect((await band.now()).showing).toBeGreaterThan(0); +}); + +// --------------------------------------------- +// Touch +// --------------------------------------------- + +// A touch browser still fires mouse events around a tap, so the spec tries +// a hover as well as the tap itself. Nothing should happen, so there is +// nothing to wait on, and the spec gives the page a second, as +// favorites-card-tab.spec.ts does. +test.describe('on a touch device', () => { + test.use({ hasTouch: true, isMobile: true }); + + test('a hover or a tap leaves the card on its still frame', async ({ page }) => { + await open(page); + + const card = performantCard(page); + const stillFrame = await screenshotIllustration(card); + const band = await recordBand(card); + + await card.hover(); + await card.tap(); + await page.waitForTimeout(1_000); + + expectStillThroughout(await band.all()); + expect(await screenshotIllustration(card)).toEqual(stillFrame); + }); +}); + +// A touch-screen laptop passes the hover gate, because its primary pointer +// is the trackpad, so only the story's own check keeps a tap from playing +// it. Chromium's touch emulation fails the gate's query, so the spec answers +// it for the page. A mouse hover at the end proves the gate was open all +// along. +test.describe('on a touch-screen laptop', () => { + test.use({ hasTouch: true }); + + test('a tap leaves the card on its still frame, and a mouse hover plays it', async ({ page }) => { + await page.addInitScript(() => { + const matchMedia = window.matchMedia.bind(window); + + window.matchMedia = (query) => + matchMedia(query === '(hover: hover) and (pointer: fine)' ? 'all' : query); + }); + await open(page); + + const card = performantCard(page); + const band = await recordBand(card); + + await card.tap(); + await page.waitForTimeout(1_000); + expectStillThroughout(await band.all()); + + await card.hover(); + await expect.poll(async () => (await band.now()).showing).toBeGreaterThan(0); + }); +}); + +// --------------------------------------------- +// Reduced motion +// --------------------------------------------- + +test.describe('under reduced motion', () => { + test('a hover leaves the card on its still frame', async ({ page }) => { + await page.emulateMedia({ reducedMotion: 'reduce' }); + await open(page); + + const card = performantCard(page); + const stillFrame = await screenshotIllustration(card); + const band = await recordBand(card); + + await card.hover(); + await page.waitForTimeout(1_000); + + expectStillThroughout(await band.all()); + expect(await screenshotIllustration(card)).toEqual(stillFrame); + }); +}); diff --git a/apps/docs/src/components/features/performant-illustration.tsx b/apps/docs/src/components/features/performant-illustration.tsx index f770a052..dde3ed55 100644 --- a/apps/docs/src/components/features/performant-illustration.tsx +++ b/apps/docs/src/components/features/performant-illustration.tsx @@ -335,7 +335,7 @@ function useBandFlow( // --------------------------------------------- // Hidden from screen readers: the card's title and description carry its -// message. +// message. `data-band` marks the band of work, for the Playwright spec. // // `m` under LazyMotion rather than `motion`, as in the Extensible card: the // particles and the chip only bind motion values to style, which domMin @@ -355,7 +355,7 @@ export function PerformantIllustration() { return (
-
+
{band.map(({ particle, trips }) => ( ))} From b419e20a0da059cd3c5e0322ae49e80d5dec2edc Mon Sep 17 00:00:00 2001 From: Hunter Garrett Date: Sat, 10 Oct 2026 20:44:25 -0400 Subject: [PATCH 6/8] fix(docs): keep waiting particles out of the Performant card's drain --- .../src/components/features/performant-illustration.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/docs/src/components/features/performant-illustration.tsx b/apps/docs/src/components/features/performant-illustration.tsx index dde3ed55..f5b1e376 100644 --- a/apps/docs/src/components/features/performant-illustration.tsx +++ b/apps/docs/src/components/features/performant-illustration.tsx @@ -274,7 +274,13 @@ function useBandFlow( ); if (stageRef.current === 'ending' && !ending) { - const stops = runs.map((run) => (run > 0 ? Math.floor(run) + 1 : 0)); + // Each stop comes from where its particle last showed, before this + // frame's advance, so one whose delay ran out since then never enters. + const stops = band.map(({ trips }) => { + const shown = trips.get(); + + return shown > 0 ? Math.floor(shown) + 1 : 0; + }); const drainSeconds = Math.max( 0, ...band.map( From ea40f4cf169c734fa83a93bf6c0f65b000efaccc Mon Sep 17 00:00:00 2001 From: Hunter Garrett Date: Sat, 10 Oct 2026 20:55:40 -0400 Subject: [PATCH 7/8] fix(docs): sync the Performant card's stage before the next frame --- .../src/components/features/performant-illustration.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/docs/src/components/features/performant-illustration.tsx b/apps/docs/src/components/features/performant-illustration.tsx index f5b1e376..cd0d2aa5 100644 --- a/apps/docs/src/components/features/performant-illustration.tsx +++ b/apps/docs/src/components/features/performant-illustration.tsx @@ -8,7 +8,7 @@ * pointer stays, then lets the band run dry and the glow settle. story.tsx * says when each part plays. */ -import { type CSSProperties, useEffect, useRef, useState } from 'react'; +import { type CSSProperties, useEffect, useLayoutEffect, useRef, useState } from 'react'; import { cancelFrame, @@ -246,10 +246,12 @@ function useBandFlow( onStoryEnd: () => void, ) { // The stage as the frame loop sees it, kept current without restarting - // the loop. + // the loop. A layout effect updates it before the next frame runs, so the + // ending starts on the first frame after the leave renders, before a + // particle still waiting can enter. const stageRef = useRef(stage); - useEffect(() => { + useLayoutEffect(() => { stageRef.current = stage; }); From 53f166e2be7a8b264959ed85ee4ac3c3c546c449 Mon Sep 17 00:00:00 2001 From: Hunter Garrett Date: Sat, 10 Oct 2026 20:55:46 -0400 Subject: [PATCH 8/8] test(docs-tests): check the Performant card's glow comes up on hover --- .../docs/features-performant.spec.ts | 30 +++++++++++++++---- .../features/performant-illustration.tsx | 5 ++-- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/apps/docs-tests/docs/features-performant.spec.ts b/apps/docs-tests/docs/features-performant.spec.ts index 001e093e..b34d9d04 100644 --- a/apps/docs-tests/docs/features-performant.spec.ts +++ b/apps/docs-tests/docs/features-performant.spec.ts @@ -3,12 +3,13 @@ import type { Locator, Page } from '@playwright/test'; /** * The Performant feature card's story (SHA-215): a hover streams a band of - * work in under the chip, which keeps flowing while the pointer stays and - * runs dry once it leaves, back to the still frame with no work showing. A - * hover during that ending doesn't cut it short. Touch and reduced motion - * leave the still frame. Checks count the particles showing and watch them - * move, never how long anything takes or its curve, so tuning the motion - * cannot break this. + * work in under the chip and brings its glow up to full. The band keeps + * flowing while the pointer stays and runs dry once it leaves, back to the + * still frame with no work showing. A hover during that ending doesn't cut + * it short. Touch and reduced motion leave the still frame. Checks count the + * particles showing, watch them move, and read where the glow ends up, never + * how long anything takes or its curve, so tuning the motion cannot break + * this. */ // No check here needs a scene, so this file turns WebGL off, as @@ -96,6 +97,10 @@ async function recordBand(card: Locator): Promise { /** How many particles the band holds once it has filled. */ const bandSize = (card: Locator) => card.locator('[data-band] > *').count(); +/** The chip's glow's strength, from 0, none, to 1, the mock's full glow. */ +const glowStrength = (card: Locator) => + card.locator('[data-halo]').evaluate((halo) => Number(getComputedStyle(halo).opacity)); + /** * A picture of the illustration, a pixel inside its box, so a box that falls * between device pixels never pulls in the card around the window. @@ -209,6 +214,19 @@ test('the story keeps going while the pointer stays', async ({ page }) => { expect((await band.now()).showing).toBeGreaterThan(0); }); +// The first test's final screenshot covers the glow settling back to the +// still frame's, so this one checks only that it comes up. +test('a hover brings the glow up to full while the pointer stays', async ({ page }) => { + await open(page); + + const card = performantCard(page); + + expect(await glowStrength(card)).toBeLessThan(1); + + await card.hover(); + await expect.poll(() => glowStrength(card)).toBe(1); +}); + // --------------------------------------------- // Touch // --------------------------------------------- diff --git a/apps/docs/src/components/features/performant-illustration.tsx b/apps/docs/src/components/features/performant-illustration.tsx index cd0d2aa5..09f063f3 100644 --- a/apps/docs/src/components/features/performant-illustration.tsx +++ b/apps/docs/src/components/features/performant-illustration.tsx @@ -58,7 +58,7 @@ function Chip({ halo }: { halo: MotionValue }) { return ( - +
@@ -343,7 +343,8 @@ function useBandFlow( // --------------------------------------------- // Hidden from screen readers: the card's title and description carry its -// message. `data-band` marks the band of work, for the Playwright spec. +// message. `data-band` marks the band of work, and `data-halo` the chip's +// glow, for the Playwright spec. // // `m` under LazyMotion rather than `motion`, as in the Extensible card: the // particles and the chip only bind motion values to style, which domMin