About this animation
A scroll-driven typographic story with phrase-specific motion and configurable chapters, accent color, fonts, and motion intensity.
Why this one
Scattered letters settle into quiet, a sine wave becomes a signal line, and six typographic tabs collapse into one. Deep work stretches the actual glyphs; calm introduces a dark paper treatment; the closing signature writes letter by letter beside a kit mark revealed from a point. These are transformations of text and paths, with no effect blocks covering the words.
How it works
- Measure the container with
onLayout. Native text lays out each glyph once and reports its width; subsequent frames change transforms only. useAnimatedScrollHandlerrecords the actual scroll offset. A single chapter progressp = scrollY / heightdrives all narrative effects.- Chapters share a fixed absolute layer outside the transparent scroll view. The scroll content supplies six viewport heights of travel and holds the navigation buttons; no chapter translates to compensate for scrolling. Entry uses
(p - (c - 0.52)) / 0.4; exit uses(p - (c + 0.14)) / 0.28. The windows leave a gap between headlines. - Smoothstep settles each pose before the chapter center. The first phrase settles over
p = 0…0.12; other phrases settle fromc - 0.52toc - 0.20, then rest. The signal path uses 65 points on one canvas. - Native chapter snapping is optional; web scrolling stays continuous. The final CTA is a real child of the scroll content.
- The demo calls the imperative scroll API every 2.6 seconds, using a 2.1-second
(0.77, 0, 0.175, 1)transition. A black fade conceals the loop reset. Touch, wheel, or manual drag stops playback.
Install
npx expo install react-native-reanimated react-native-worklets expo-linear-gradient @shopify/react-native-skiaCopy this folder without demo/. Configure Reanimated through the Expo preset. On web, load CanvasKit before importing the component, using Skia's WithSkiaWeb or your application's existing lazy Skia loader. Provide loaded font family names; the component has no dependency on a shared theme. Icons: Lucide (ISC); paths are copied from the local kit in icons.ts.
Usage
import KineticManifestoScroll from './kinetic-manifesto-scroll';
<KineticManifestoScroll onComplete={() => openSignup()} />;const manifesto = useRef<KineticManifestoScrollRef>(null);
<KineticManifestoScroll
ref={manifesto}
brandName="Your workspace"
fontFamily="InstrumentSerif_400Regular"
italicFontFamily="InstrumentSerif_400Regular_Italic"
sansFontFamily="Geist_500Medium"
monoFontFamily="GeistMono_400Regular"
accentColor="#6366F1"
motionIntensity={0.7}
snapToChapters={false}
onChapterChange={trackChapter}
onComplete={openSignup}
/>;
manifesto.current?.scrollToChapter(3, 900);
manifesto.current?.reset();Import KineticManifestoScrollRef from the same entry point. Reserve a bounded container height; the root uses flex: 1.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| chapters | readonly ManifestoChapter[] | Six generic focus phrases | Exactly six chapters with two short lines, eyebrow, and copy. Other counts use the defaults. |
| brandName | string | Focus | Opening wordmark. |
| ctaLabel | string | Get started | Final action and accessibility label. |
| onComplete | () => void | — | Final action callback. |
| onInteraction | () => void | — | Interrupt external playback. |
| onChapterChange | (index: number) => void | — | Nearest chapter, zero based, on the JS thread. |
| paperColor | string | #F5F3EE | Initial paper. |
| darkColor | string | #0B0B0F | Closing background. |
| accentColor | string | #6366F1 | Italic text, line, and CTA. |
| colorScheme | light / dark / auto | light | Initial appearance; chapters five and six are dark. |
| fontFamily | string | System | Main headline. |
| italicFontFamily | string | fontFamily | Accent headline. |
| sansFontFamily | string | System | Wordmark, copy, and action. |
| monoFontFamily | string | System | Counter and labels. |
| motionIntensity | number | 1 | Displacement multiplier, clamped to 0–1.5. |
| snapToChapters | boolean | true | Native snapping only. |
| style | StyleProp<ViewStyle> | — | Root container. |
| testID | string | — | Root test identifier. |
Compatibility
Targets Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, Worklets 0.13.0, Skia 2.14.0, and Expo LinearGradient ~58.0.3. Uses bundled Expo Go modules. Supports iOS, Android, and web. No photos, network assets, or extra native dependencies.
Accessibility
Each chapter exposes a complete header label rather than separate letters. Inactive chapters are hidden from accessibility. The scroll view provides next/previous accessibility actions; the opening scroll cue is also a 48-point button, and chapters two through five have 44-point next-chapter buttons within the scroll content. The CTA is at least 54 points tall and presses to 0.97 with a near-critical spring. Reduced motion removes noise, wave displacement, stretching, breathing, and signature zoom; chapter changes use 200 ms fades and imperative scrolling takes 200 ms.
Layout and review notes
At 320×694 and 390×844, horizontal padding is 24 points; headline size adapts to line length. The depth chapter reserves two tall line boxes so stretched glyphs do not collide. Editorial content starts at 21% of the viewport, or 14% for depth. The final action sits at 81%; brand content stays at the top left, leaving the viewer's top-right close control clear. The demo's first scroll begins at 350 ms, placing the noise-to-calm moment within the first three seconds. This is a code review of the geometry; video review is still required.




