# react-kino 0.6.0 Document schema version 1. Pure data: react-kino/document and react-kino/recipes. React client entries: react-kino/story, react-kino/studio, react-kino/recipe-kit. CLI: @react-kino/cli 0.2.0. MIT licensed. --- # Storyboard and portable stories (https://www.react-kino.dev/docs/authoring) Edit real React scroll stories visually, export a versioned JSON document, and keep your components in your codebase. Kino Storyboard is an optional, local visual editor. Open [the workspace](/studio), choose a recipe, and select a layer in the preview or timeline. Drag its start/end handles or use keyboard arrows and numeric fields. Adjust its transform, reorder layers or scenes, and compare the desktop animation with the mobile reading layout. This authoring API requires `react-kino@0.6.0` or later. ## Render the document ```tsx "use client"; import { Story } from "react-kino/story"; import { parseStory } from "react-kino/document"; import { createRecipeComponents } from "react-kino/recipe-kit"; import rawStory from "./story.kino.json"; const document = parseStory(JSON.stringify(rawStory)); export function MyPage() { return ( , }} /> ); } ``` A component layer references a registered name. The document never contains executable React or JavaScript. Your application supplies links, forms, media, and rich UI through `components`; imported documents can only select those names. Missing registrations remain visible and produce a diagnostic. ## Embed the editor locally ```tsx "use client"; import { Storyboard } from "react-kino/studio"; import { createStoryRecipe } from "react-kino/recipes"; import { createRecipeComponents } from "react-kino/recipe-kit"; export function Authoring() { return ( , }} onChange={(document) => console.log(document)} /> ); } ``` Import the editor only in your authoring route or development UI. The default runtime does not import it. The editor provides its own scoped styles. Undo/redo is bounded to 80 document states. Drafts are saved locally after edits; a restored draft requires your explicit choice. **Clear saved draft** removes the stored copy, keeping the current open document available for export. Download JSON to share or commit. Import files or paste JSON in the inspector. Imports are limited to 2 MB and validated before replacing the current document; you can undo a successful import. Storage and clipboard failures are shown in the workspace, with download as the alternative. No account, hosted data store, telemetry, or payment is required. ## Mobile is a reading policy A story narrower than 640px switches to normal document flow, one column, and visible untransformed layers. Reduced-motion users receive the same reading path. This is a deliberate responsive policy, not a smaller copy of the desktop animation. A tall desktop scene also unpins and reveals its content. Use `reading` to enable this mode explicitly. ## Validate and inspect ```bash npx @react-kino/cli recipe launch story.kino.json npx @react-kino/cli validate story.kino.json --json npx @react-kino/cli doctor story.kino.json --components Product --json ``` The CLI checks the document, not the layout of your deployed application. Browser testing is still necessary for media, registered components, overflow, focus, and performance. The [JSON Schema](/schema/story-v1.json) helps editors; `validateStory` also checks unique IDs and start-before-end constraints. ## Support independent maintenance Tourlight, Kino, Clickmap, and Redact share one independent maintainer. Voluntary support helps fund fixes, compatibility, documentation, and development; every feature remains free and MIT licensed. [Support this project](https://react-tourlight.vercel.app/support) through our shared support page. No sponsorship is needed to use or export your work. --- # Getting Started (https://www.react-kino.dev/docs) Install react-kino and build your first scroll experience in minutes. ## Installation ```bash npm install react-kino ``` ```bash pnpm add react-kino ``` ```bash yarn add react-kino ``` **Requirements:** React 18+ ## Quick Start Wrap your app with ``, add a ``, and you have a pinned scroll section: ```tsx import { Kino, Scene, Reveal, Counter } from "react-kino"; function App() { return ( {(progress) => (

Welcome

Scroll-driven storytelling, made simple.

`${n.toLocaleString()}+ users`} />
)}
); } ``` That is a complete scroll experience: the section pins in place, content fades in at different scroll points, and a number counts up -- all in ~20 lines. ## Why react-kino - **Separate tools** -- runtime, pure documents, recipes and optional Storyboard have explicit imports. - **Declarative** -- compose ``, ``, ``, ``, and `` like regular React components. No imperative timelines. - **Lightweight runtime** -- `react-kino` uses a tiny internal engine package (`@kino/core`) plus React peers. - **SSR-safe** -- every component renders children on the server and animates on the client. ## SSR / Next.js react-kino is SSR-safe and defers scroll logic to `useEffect`. ```tsx // app/page.tsx "use client"; import { Kino, Scene, Reveal } from "react-kino"; export default function Page() { return (

Works with App Router

); } ``` **What happens on the server:** Components render their children immediately with no animation styles. Scroll tracking starts after hydration on the client. ## Accessibility react-kino respects the `prefers-reduced-motion` media query: - `` -- content renders immediately in its visible state - `` -- parallax offset is disabled - `` -- jumps to the `to` state immediately, no interpolation - `` -- displays the final value immediately No additional configuration is required. ## Animation Engine react-kino uses a lightweight JavaScript scroll engine with CSS transforms and opacity transitions: 1. **Shared scroll tracker** -- one provider can drive all scroll subscribers. 2. **requestAnimationFrame batching** -- updates are coalesced for smoother rendering. 3. **GPU-friendly styles** -- reveal/parallax use transform and opacity. ## Browser Support | Feature | Chrome | Firefox | Safari | Edge | | ------------------------ | ------ | ------- | ------ | ---- | | Core scroll tracking | 64+ | 60+ | 13+ | 79+ | | `position: sticky` | 56+ | 59+ | 13+ | 79+ | | `prefers-reduced-motion` | 74+ | 63+ | 10.1+ | 79+ | ## Author a complete story Open [Storyboard](/studio) to edit a recipe in the actual renderer, or start with [portable documents](/docs/authoring). Read the [responsive layout policy](/docs/responsive-stories) before building pinned content. --- # Responsive scroll stories that keep their content (https://www.react-kino.dev/docs/responsive-stories) Handle tall pinned sections, nested scroll roots, reduced motion, local parallax, and changing content in React. A pinned scene is a layout constraint: its content must fit the available viewport. Kino measures the scene content and falls back to natural flow when it is too tall. Late images, font changes, container resizing, and window resizing can change that decision. ```tsx ``` The default `overflow="auto"` policy preserves the whole article. Natural reading fallback advances the scene to its final progress, so late reveals remain visible. `overflow="clip"` opts into the older deliberately clipped cinematic treatment. In Story documents, reading fallback additionally neutralizes layer transforms and opacity, including exit animations. Reduced-motion users receive an unpinned scene without scroll-distance padding. Horizontal galleries become a vertical list. Do not put essential content behind animation-only callbacks. ## A nested scroll area ```tsx const root = useRef(null); return (
); ``` Keep the root ref attached for the provider lifetime. Scroll coordinates, viewport height, intersection gating, and resize invalidation use that root. `tracker.refresh()` is available through `useKino()` after application-specific layout changes. ## Text entering from outside the viewport ```tsx

This paragraph moves relative to its own passage through the viewport.

``` Both range values are pixels. Existing `speed` and `direction` props remain available, but speed now uses a local element origin rather than page-top displacement. This intentionally fixes accumulating offsets far down a page. Test existing compositions if they compensated for the old global behavior. ## Verify your story Check 320px and 390px mobile layouts, keyboard traversal, 200% zoom, reduced motion, browser back/scroll restoration, and delayed media. Test tall content with a reveal near the end of the scene, not just the first heading. Browser engines and actual video encodings can behave differently; a jsdom test does not establish visual correctness. --- # CompareSlider (https://www.react-kino.dev/docs/components/compare-slider) Before/after comparison slider with drag and scroll-driven modes. ## Overview `` creates a before/after comparison. Supports both interactive drag mode and scroll-driven mode when nested inside a ``. ## Usage ```tsx import { CompareSlider } from "react-kino"; {/* Interactive drag mode */} } after={After} /> {/* Scroll-driven mode inside a Scene */} } after={After} /> ``` ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `before` | `ReactNode` | -- | Content shown on the "before" side (always visible underneath) | | `after` | `ReactNode` | -- | Content shown on the "after" side (revealed via clip) | | `scrollDriven` | `boolean` | `false` | If `true`, slider position follows scroll progress instead of drag | | `progress` | `number` | -- | Progress override (0-1). When `scrollDriven`, defaults to parent `` context | | `initialPosition` | `number` | `0.5` | Initial slider position (0-1) in drag mode | | `ariaLabel` | `string` | `"Comparison slider"` | Accessible label for the drag handle (`role="slider"`) | | `className` | `string` | -- | CSS class for the container | The handle exposes `role="slider"` with `aria-valuenow`/`aria-valuemin`/`aria-valuemax`, and supports keyboard control (`ArrowLeft`/`ArrowRight` to nudge, `Home`/`End` to jump to the ends) when not `scrollDriven`. ## Example: Scroll-driven comparison ```tsx
} after={ After } />
``` --- # Counter (https://www.react-kino.dev/docs/components/counter) Animated number that counts between two values as the user scrolls. ## Overview `` animates a number between two values as the user scrolls. Automatically reads progress from a parent ``, or accepts a `progress` prop directly. ## Usage ```tsx import { Scene, Counter } from "react-kino"; `${n.toFixed(1)}%`} easing="ease-in-out" /> ``` ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `from` | `number` | -- | Starting value | | `to` | `number` | -- | Ending value | | `at` | `number` | `0` | Progress value (0-1) when counting begins | | `span` | `number` | `0.3` | How much of the progress range (0-1) the count spans | | `format` | `(value: number) => string` | `toLocaleString` | Formatting function for the displayed value | | `easing` | `string \| (t: number) => number` | `"ease-out"` | Easing preset name or custom easing function | | `progress` | `number` | -- | Direct progress override (0-1). If omitted, reads from parent `` context | | `className` | `string` | -- | CSS class for the `` element | When both `from` and `to` are integers, the displayed value is automatically rounded. ## Accessibility When `prefers-reduced-motion` is enabled, the counter displays the final `to` value immediately once progress reaches the `at` threshold. ## Example: Stat counter section ```tsx

Countries

Users

`${n.toFixed(1)}%`} />

Uptime

``` --- # HorizontalScroll (https://www.react-kino.dev/docs/components/horizontal-scroll) Converts vertical scroll into horizontal movement with full-viewport panels. ## Overview `` converts vertical scroll into horizontal movement. Wrap `` components inside it to create horizontally-scrolling sections. ## Usage ```tsx import { HorizontalScroll, Panel } from "react-kino";

Panel One

Panel Two

Panel Three

``` ## `` Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `children` | `ReactNode` | -- | `` components | | `className` | `string` | -- | CSS class for the outer spacer | | `panelHeight` | `string` | `"100vh"` | Height of each panel as a CSS string | ## `` Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `children` | `ReactNode` | -- | Panel content | | `className` | `string` | -- | CSS class | | `style` | `CSSProperties` | -- | Inline styles (merged with default `100vw x 100vh` sizing) | ## How it works The spacer height is automatically set to `childCount * 100vh`, giving each panel a full viewport of scroll distance. Vertical scroll is translated into a horizontal `translateX` on the inner container. ## Example: Feature showcase ```tsx {features.map((f) => (

{f.title}

{f.description}

))}
``` --- # Kino (https://www.react-kino.dev/docs/components/kino) Root provider that initializes the scroll tracking engine. ## Overview `` is the root provider that initializes the scroll tracking engine. Wrap your app or page layout with it to enable scroll-driven animations. ## Usage ```tsx import { Kino } from "react-kino"; {/* your scenes and content */} ``` All scroll-driven components (``, ``, ``, etc.) must be descendants of ``. ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `children` | `ReactNode` | -- | Child elements | ## Example ```tsx import { Kino, Scene, Reveal } from "react-kino"; function App() { return (

Hello, scroll world

); } ``` --- # Marquee (https://www.react-kino.dev/docs/components/marquee) Infinite scrolling marquee that loops content horizontally. ## Overview `` creates an infinitely scrolling horizontal marquee. Content loops seamlessly. Supports configurable speed, direction, and hover-to-pause behavior. ## Usage ```tsx import { Marquee } from "react-kino"; react-kino Cinematic scroll Core engine <1 KB ``` ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `speed` | `number` | `40` | Scroll speed in pixels per second | | `direction` | `"left" \| "right"` | `"left"` | Direction of scrolling | | `pauseOnHover` | `boolean` | `false` | Whether to pause the animation when hovered | | `gap` | `number` | `40` | Gap between repeated items in pixels | | `children` | `ReactNode` | -- | Content to scroll | | `className` | `string` | -- | CSS class for the container | ## Example: Logo parade ```tsx Company 1 Company 2 Company 3 Company 4 ``` ## Example: Reverse direction ```tsx Fast Declarative Accessible ``` --- # Parallax (https://www.react-kino.dev/docs/components/parallax) A layer that scrolls at a different speed, creating depth. ## Overview `` moves its children at a different scroll speed than the page, creating a sense of depth. Use `speed < 1` for background layers and `speed > 1` for foreground layers. ## Usage ```tsx import { Parallax } from "react-kino"; {/* Background image scrolls at half speed */} {/* Foreground element scrolls faster */}
New
``` ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `speed` | `number` | `0.5` | Speed multiplier. `1` = normal scroll, `< 1` = slower (background feel), `> 1` = faster (foreground feel) | | `direction` | `"vertical" \| "horizontal"` | `"vertical"` | Scroll direction for the parallax offset | | `children` | `ReactNode` | -- | Content to apply parallax to | | `className` | `string` | -- | CSS class | | `style` | `CSSProperties` | -- | Inline styles (merged with transform) | ## How it works The parallax offset is calculated as: ``` offset = scrollPosition * (1 - speed) ``` The transform is applied using `translateY` (or `translateX` for horizontal) for GPU-accelerated performance. ## Accessibility When `prefers-reduced-motion` is enabled, the parallax offset is disabled and content scrolls normally. ## Example: Layered depth effect ```tsx
{/* Slow background */}
{/* Mid layer */}
{/* Fast foreground */}

Create depth.

``` --- # Progress (https://www.react-kino.dev/docs/components/progress) Fixed scroll progress indicator with bar, dots, and ring styles. ## Overview `` is a fixed scroll progress indicator. It supports bar, dots, and ring visual styles and can be positioned on any edge of the viewport. ## Usage ```tsx import { Progress } from "react-kino"; {/* Simple top bar */} {/* Ring indicator in the corner */} {/* Dot pagination on the right */} ``` ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `type` | `"bar" \| "dots" \| "ring"` | `"bar"` | Visual style of the indicator | | `position` | `"top" \| "bottom" \| "left" \| "right"` | `"top"` | Fixed position on screen | | `color` | `string` | `"#3b82f6"` | Color of the progress fill / active dots / ring stroke | | `trackColor` | `string` | `"transparent"` | Background / inactive color | | `progress` | `number` | -- | Progress override (0-1). If omitted, reads page scroll progress | | `dotCount` | `number` | `5` | Number of dots (only for `"dots"` type) | | `ringSize` | `number` | `48` | Diameter in pixels (only for `"ring"` type) | | `className` | `string` | -- | CSS class for the wrapper | ## Example: Red progress bar ```tsx {/* rest of your page */} ``` --- # Reveal (https://www.react-kino.dev/docs/components/reveal) Scroll-triggered entrance animation with five built-in presets. ## Overview `` triggers an entrance animation when scroll progress reaches a threshold. Place inside a `` or provide a `progress` prop directly. ## Usage ```tsx import { Scene, Reveal } from "react-kino";

Appears at 20% scroll

Blurs in at 50% with a delay

``` ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `at` | `number` | `0` | Progress value (0-1) when animation triggers | | `animation` | `RevealAnimation` | `"fade"` | Animation preset (see below) | | `duration` | `number` | `600` | Animation duration in milliseconds | | `delay` | `number` | `0` | Delay before animation starts in milliseconds | | `progress` | `number` | -- | Direct progress override (0-1). If omitted, reads from parent `` context | | `children` | `ReactNode` | -- | Content to reveal | | `className` | `string` | -- | CSS class for the wrapper div | ## Animation presets | Preset | Effect | |--------|--------| | `"fade"` | Opacity 0 to 1 | | `"fade-up"` | Fade in + slide up 40px | | `"fade-down"` | Fade in + slide down 40px | | `"scale"` | Fade in + scale from 0.9 to 1 | | `"blur"` | Fade in + unblur from 12px | ## Accessibility When `prefers-reduced-motion` is enabled, content renders immediately in its visible state with no animation. ## Example: Staggered reveals ```tsx {(progress) => (
)}
``` --- # Scene (https://www.react-kino.dev/docs/components/scene) A pinned scroll section. Content stays fixed while the user scrolls through the scene's duration. ## Overview `` is the core building block of react-kino. It creates a tall spacer element and pins its inner content using `position: sticky`. Progress goes from `0` to `1` as the user scrolls through the scene. ## Usage ```tsx import { Kino, Scene } from "react-kino"; {/* Static children -- use child components that read progress from context */} {/* Render prop -- get progress directly */} {(progress) => (
{Math.round(progress * 100)}% scrolled
)}
``` ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `duration` | `string` | -- | Scroll distance the scene spans. Supports `vh` and `px` units (e.g. `"200vh"`, `"1500px"`) | | `pin` | `boolean` | `true` | Whether to pin (sticky) the inner content during scroll | | `children` | `ReactNode \| (progress: number) => ReactNode` | -- | Static content or render function receiving progress (0-1) | | `className` | `string` | -- | CSS class for the outer spacer element | | `style` | `CSSProperties` | -- | Inline styles for the sticky inner container | ## How it works `` uses CSS `position: sticky` with a tall spacer `div` whose height matches the `duration` prop. As the user scrolls through the spacer, progress is calculated as: ``` progress = scrollPositionThroughSpacer / spacerHeight ``` This is the same technique as GSAP ScrollTrigger, without the dependency. ## Context `` provides a `SceneContext` that child components (``, ``, ``) automatically read from. You do not need to pass progress manually when nesting components inside a ``. ## Examples ### Progress percentage display ```tsx {(progress) => (

{Math.round(progress * 100)}%

)}
``` ### Multiple scenes in sequence ```tsx {(progress) => } {(progress) => } {(progress) => } ``` --- # ScrollTransform (https://www.react-kino.dev/docs/components/scroll-transform) Transforms an element from one visual state to another as the user scrolls. ## Overview `` interpolates between two visual states (`from` and `to`) as scroll progress advances. Place it inside a `` to automatically drive the transition, or provide a `progress` prop directly. Useful for 3D tilt effects, slide-in animations, scale reveals, and any scroll-driven transform. ## Usage ```tsx import { Scene, ScrollTransform } from "react-kino";
Your content here
``` ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `from` | `TransformState` | -- | Starting visual state at progress `0` | | `to` | `TransformState` | -- | Ending visual state at progress `1` | | `perspective` | `number` | -- | CSS perspective value in pixels applied to the wrapper. Required for 3D rotation effects | | `easing` | `string \| (t: number) => number` | `"linear"` | Easing preset name or custom easing function | | `progress` | `number` | -- | Direct progress override (0-1). If omitted, reads from parent `` context | | `children` | `ReactNode` | -- | Content to transform | | `className` | `string` | -- | CSS class for the wrapper element | | `style` | `CSSProperties` | -- | Inline styles (merged with computed transform) | ## TransformState properties All properties are optional. Only the properties you specify will be interpolated. | Property | Type | Unit | Description | |----------|------|------|-------------| | `x` | `number` | `px` | Horizontal translation | | `y` | `number` | `px` | Vertical translation | | `z` | `number` | `px` | Depth translation (3D) | | `rotateX` | `number` | `deg` | Rotation around X-axis | | `rotateY` | `number` | `deg` | Rotation around Y-axis | | `rotateZ` | `number` | `deg` | Rotation around Z-axis | | `scale` | `number` | -- | Uniform scale factor | | `scaleX` | `number` | -- | Horizontal scale factor | | `scaleY` | `number` | -- | Vertical scale factor | | `opacity` | `number` | -- | Opacity (0-1) | | `blur` | `number` | `px` | CSS blur filter | | `brightness` | `number` | -- | CSS brightness filter (1 = normal) | ## Available easing presets | Preset | Description | |--------|-------------| | `"linear"` | Constant speed, no acceleration | | `"ease-in"` | Quadratic ease in | | `"ease-out"` | Quadratic ease out | | `"ease-in-out"` | Quadratic ease in-out | | `"ease-in-cubic"` | Cubic ease in | | `"ease-out-cubic"` | Cubic ease out (great for scroll reveals) | | `"ease-in-out-cubic"` | Cubic ease in-out | | `"ease-in-quart"` | Quartic ease in | | `"ease-out-quart"` | Quartic ease out | | `"ease-in-out-quart"` | Quartic ease in-out | You can also pass a custom easing function `(t: number) => number` where `t` ranges from `0` to `1`. ## Accessibility When `prefers-reduced-motion` is enabled, the element renders in its `to` state immediately with no interpolation. This ensures all content is visible and readable without requiring scroll interaction. ## Example: Device tilt (3D rotation) ```tsx MacBook Pro ``` ## Example: Slide-in with rotation ```tsx

Lightning fast

Built for performance from the ground up.

``` --- # StickyHeader (https://www.react-kino.dev/docs/components/sticky-header) A header that becomes sticky with a blurred background after scrolling past a threshold. ## Overview `` creates a header that sticks to the top of the viewport once the user scrolls past a configurable threshold. It applies a backdrop blur and background color for readability. ## Usage ```tsx import { StickyHeader } from "react-kino"; ``` ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `threshold` | `number` | `0` | Scroll distance in pixels before the header becomes sticky | | `background` | `string` | `"rgba(255, 255, 255, 0.8)"` | Background color when sticky | | `blur` | `number` | `8` | Backdrop blur amount in pixels | | `children` | `ReactNode` | -- | Header content | | `className` | `string` | -- | CSS class for the header element | | `style` | `CSSProperties` | -- | Inline styles | ## Example: Dark theme header ```tsx ``` --- # TextReveal (https://www.react-kino.dev/docs/components/text-reveal) Word-by-word, character-by-character, or line-by-line text reveal driven by scroll. ## Overview `` progressively reveals text as the user scrolls. Supports word, character, and line modes. ## Usage ```tsx import { Scene, TextReveal } from "react-kino"; {(progress) => ( Scroll-driven storytelling components for React. Build cinematic experiences without the complexity. )} ``` ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `children` | `string` | -- | The text to reveal | | `mode` | `"word" \| "char" \| "line"` | `"word"` | How to split the text into tokens | | `at` | `number` | `0` | Progress value (0-1) when reveal starts | | `span` | `number` | `0.8` | How much of the progress range the full reveal spans | | `color` | `string` | currentColor | Color of revealed tokens | | `dimColor` | `string` | -- | Color of unrevealed tokens (default: 15% opacity) | | `progress` | `number` | -- | Direct progress override. If omitted, reads from parent `` context | | `className` | `string` | -- | CSS class for the wrapper | ## Accessibility `prefers-reduced-motion`: all text renders immediately at full opacity. ## Modes | Mode | Behavior | |------|----------| | `"word"` | Splits on whitespace, reveals one word at a time | | `"char"` | Reveals one character at a time | | `"line"` | Splits on newlines, reveals one line at a time | ## Example: Character reveal ```tsx {(progress) => ( Every character matters. )} ``` --- # VideoScroll (https://www.react-kino.dev/docs/components/video-scroll) Scrubs through a video as the user scrolls, like Apple product pages. ## Overview `` scrubs through a video as the user scrolls -- like the AirPods Pro or iPhone product pages. Pair with overlay children for animated text on top of the video. ## Usage ```tsx import { VideoScroll } from "react-kino"; {(progress) => (

Scroll to reveal

)}
``` ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `src` | `string` | -- | URL of the video file (MP4 recommended, no audio needed) | | `duration` | `string` | `"300vh"` | Scroll distance the video scrubbing spans | | `pin` | `boolean` | `true` | Whether to pin the video while scrubbing | | `poster` | `string` | -- | Poster image shown before the video loads | | `children` | `ReactNode \| (progress: number) => ReactNode` | -- | Overlay content rendered on top of the video | | `className` | `string` | -- | CSS class for the outer spacer | ## Behavior - The video is `muted`, `playsInline`, and never autoplays - `currentTime` is set directly from scroll progress - `prefers-reduced-motion`: video stays on the poster frame ## Example: Product reveal ```tsx {(progress) => (

The future is here

)}
``` --- # useIsClient (https://www.react-kino.dev/docs/hooks/use-is-client) SSR guard that returns false on the server and true after mount. ## Overview `useIsClient()` is an SSR guard. Returns `false` on the server and during hydration, `true` after the component mounts on the client. ## Usage ```tsx import { useIsClient } from "react-kino"; function SafeComponent() { const isClient = useIsClient(); if (!isClient) return
Loading...
; return
Window width: {window.innerWidth}
; } ``` ## Returns | Type | Description | |------|-------------| | `boolean` | `false` on server / during hydration, `true` after client mount | ## When to use Use `useIsClient()` when you need to access browser-only APIs (`window`, `document`, `navigator`) that are not available during server-side rendering. react-kino's built-in components already handle SSR safety internally, so you typically only need this hook when building custom scroll-driven components. ## Example: Conditional rendering ```tsx import { useIsClient } from "react-kino"; function WindowSize() { const isClient = useIsClient(); if (!isClient) { return --; } return ( {window.innerWidth} x {window.innerHeight} ); } ``` --- # useKino (https://www.react-kino.dev/docs/hooks/use-kino) Access the root ScrollTracker instance from Kino. ## Overview `useKino()` accesses the root `ScrollTracker` instance from ``. For advanced use cases where you need direct access to the scroll engine. ## Usage ```tsx import { useKino } from "react-kino"; function AdvancedComponent() { const { tracker } = useKino(); // tracker.subscribe(), tracker.start(), tracker.stop() } ``` ## Returns | Property | Type | Description | |----------|------|-------------| | `tracker` | `ScrollTracker` | The root scroll tracker instance | Throws an error if used outside ``. ## ScrollTracker API The `ScrollTracker` instance provides low-level access to the scroll engine: - `tracker.subscribe(callback)` -- subscribe to scroll updates - `tracker.start()` -- start tracking (called automatically by ``) - `tracker.stop()` -- stop tracking - `tracker.progress` -- current page scroll progress (0-1) ## Example: Subscribe to scroll events ```tsx import { useEffect } from "react"; import { useKino } from "react-kino"; function ScrollLogger() { const { tracker } = useKino(); useEffect(() => { const unsubscribe = tracker.subscribe((progress) => { console.log("Scroll progress:", progress); }); return unsubscribe; }, [tracker]); return null; } ``` --- # useSceneContext (https://www.react-kino.dev/docs/hooks/use-scene-context) Access the progress value from a parent Scene component. ## Overview `useSceneContext()` accesses the progress value from a parent ``. Useful for building custom components that react to scene progress. ## Usage ```tsx import { useSceneContext } from "react-kino"; function CustomFadeIn() { const { progress } = useSceneContext(); return
I fade in as you scroll
; } ``` ## Returns | Property | Type | Description | |----------|------|-------------| | `progress` | `number` | Current scene progress from `0` to `1` | Throws an error if used outside a ``. ## Example: Custom scroll-driven component ```tsx import { useSceneContext } from "react-kino"; function RotatingElement() { const { progress } = useSceneContext(); return (
I rotate as you scroll
); } // Usage: ``` --- # useSceneProgress (https://www.react-kino.dev/docs/hooks/use-scene-progress) Returns scene-level scroll progress for a specific element. ## Overview `useSceneProgress(ref, durationPx)` returns scene-level scroll progress for a specific element. Useful when building custom scroll-driven components outside of ``. ## Usage ```tsx import { useRef } from "react"; import { useSceneProgress } from "react-kino"; function CustomScene() { const ref = useRef(null); const progress = useSceneProgress(ref, 1500); // 1500px scroll distance return (
Progress: {progress.toFixed(2)}
); } ``` ## Parameters | Param | Type | Description | |-------|------|-------------| | `spacerRef` | `RefObject` | Ref to the spacer/container element | | `durationPx` | `number` | Total scroll distance in pixels | ## Returns | Type | Description | |------|-------------| | `number` | Progress from `0` to `1` | ## Example: Custom parallax section ```tsx function CustomParallax() { const ref = useRef(null); const progress = useSceneProgress(ref, 2000); return (
Content moves as you scroll
); } ``` --- # useScrollProgress (https://www.react-kino.dev/docs/hooks/use-scroll-progress) Returns the page-level scroll progress as a number from 0 to 1. ## Overview `useScrollProgress()` returns the page-level scroll progress as a number from `0` (top of page) to `1` (bottom of page). ## Usage ```tsx import { useScrollProgress } from "react-kino"; function ScrollPercentage() { const progress = useScrollProgress(); return
{Math.round(progress * 100)}%
; } ``` ## Returns | Type | Description | |------|-------------| | `number` | Progress from `0` (top of page) to `1` (bottom of page) | ## Example: Custom progress bar ```tsx import { useScrollProgress } from "react-kino"; function CustomProgressBar() { const progress = useScrollProgress(); return (
); } ``` --- # Device Tilt Effect (https://www.react-kino.dev/docs/recipes/device-tilt) Create Apple-style 3D device reveals that rotate into view as the user scrolls. ## Overview The device tilt effect is a popular pattern seen on Apple product pages: a device (laptop, phone, or card) starts tilted away from the viewer and gradually rotates into a flat, front-facing position as the user scrolls. This creates a cinematic 3D reveal that draws attention to the product. ## Using ScrollTransform (recommended) The simplest approach is to use `` with 3D rotation properties and a `perspective` value. ```tsx import { Scene, ScrollTransform } from "react-kino"; function DeviceTilt() { return ( MacBook Pro ); } ``` This gives you the full effect in five lines of JSX. The `perspective` prop sets the 3D viewing distance, and `ease-out-cubic` gives a natural deceleration as the device settles into place. ## Manual approach For full control over the animation, use the `` render prop to access progress directly and compute your own transforms. ```tsx import { Scene } from "react-kino"; function DeviceTiltManual() { return ( {(progress) => { const t = Math.min(1, progress * 1.2); // complete slightly before end const rotateX = 40 * (1 - t); const rotateY = -12 * (1 - t); const scale = 0.82 + 0.18 * t; const opacity = 0.3 + 0.7 * t; return (
MacBook Pro
); }}
); } ``` ## Customization tips ### Transform origin The default `transform-origin` is `center center`. For a device lying on a desk that tilts up toward the viewer, use `center bottom`: ```tsx Laptop ``` ### Choosing perspective values - **800-1000px** -- Dramatic, exaggerated 3D effect - **1200-1600px** -- Natural, realistic perspective (recommended for most cases) - **2000px+** -- Subtle, almost flat feeling ### Easing choices - **`ease-out-cubic`** -- Best for reveals. Fast start, gentle landing. - **`ease-in-out`** -- Smooth throughout. Good for back-and-forth transitions. - **`ease-out-quart`** -- Even more dramatic deceleration for a "snap into place" feel. ### Adding a shadow animation Pair the tilt with a CSS shadow that grows as the device comes to rest: ```tsx {(progress) => ( Device )} ``` --- # Sticky Timeline (https://www.react-kino.dev/docs/recipes/sticky-timeline) Build a pinned section with step-by-step timeline indicators driven by scroll progress. ## Overview The sticky timeline pattern creates a pinned section with a vertical (or horizontal) timeline that fills as the user scrolls. Each step activates in sequence, with content crossfading to match the active step. This is a common pattern for feature walkthroughs, product tours, and process explainers. ## Implementation Use the `` render prop to access scroll progress, then derive the active step and fill percentage from it. ```tsx import { Scene } from "react-kino"; const steps = [ { title: "Connect", description: "Link your accounts in one click." }, { title: "Configure", description: "Set your preferences and rules." }, { title: "Launch", description: "Go live with a single command." }, { title: "Monitor", description: "Track performance in real time." }, ]; function StickyTimeline() { return ( {(progress) => { const activeStep = Math.min( steps.length - 1, Math.floor(progress * steps.length) ); const fillPercent = progress * 100; return (
{/* Timeline column */}
{/* Track */}
{/* Dots */} {steps.map((_, i) => (
))}
{/* Content column */}
{steps.map((step, i) => (

{step.title}

{step.description}

))}
); }} ); } ``` ## CSS Add these styles for the timeline track, fill bar, and dots. ```css .timeline-track { position: absolute; top: 0; bottom: 0; left: 50%; width: 2px; background: #e5e7eb; transform: translateX(-50%); overflow: hidden; } .timeline-fill { position: absolute; top: 0; left: 0; width: 100%; background: #3b82f6; transition: height 0.1s linear; } .timeline-dot { position: absolute; left: 50%; width: 16px; height: 16px; border-radius: 50%; border: 2px solid #e5e7eb; background: #fff; transform: translate(-50%, -50%); transition: border-color 0.3s ease, background-color 0.3s ease; z-index: 1; } .timeline-dot.active { border-color: #3b82f6; background: #3b82f6; } .timeline-content h2 { font-size: 2rem; margin-bottom: 0.5rem; } .timeline-content p { font-size: 1.125rem; color: #6b7280; max-width: 500px; } ``` ## Content crossfade The content switching uses CSS transitions for a smooth crossfade. The active step has `opacity: 1` and `translateY(0)`, while inactive steps have `opacity: 0` and `translateY(20px)`. The first step uses `position: relative` to maintain layout height, while the rest are `position: absolute` so they stack. If you need more control over the crossfade timing, you can use sub-progress values: ```tsx {(progress) => { const stepProgress = (progress * steps.length) % 1; // 0-1 within each step // Use stepProgress for per-step animations like // content slide-in, image transitions, etc. }} ``` ## Customization tips ### Horizontal timeline Rotate the timeline to horizontal by swapping axes. Use `width` instead of `height` for the fill, and lay out dots with `left` instead of `top`: ```tsx
{steps.map((_, i) => (
))} ``` ### Numbered steps Replace the dots with numbered indicators: ```tsx
{i + 1}
``` ### Custom dot styles Use a ring style for upcoming steps and a solid fill for completed ones: ```css .timeline-dot.upcoming { border-color: #d1d5db; background: transparent; } .timeline-dot.completed { border-color: #3b82f6; background: #3b82f6; } .timeline-dot.current { border-color: #3b82f6; background: #fff; box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.2); } ``` ### Adding icons Use step-specific icons inside each dot for a richer visual: ```tsx const stepIcons = ["🔗", "⚙️", "🚀", "📊"]; {steps.map((_, i) => (
{stepIcons[i]}
))} ``` --- # Complete story recipes (https://www.react-kino.dev/docs/recipes/story-recipes) Start a React product launch, editorial essay, case study, comparison, feature story, or portfolio from an editable document. Each recipe contains a complete sequence of scenes and layers, with timing, color, and reading fallback. The copy is a writing scaffold, not a claim about your product. Replace it with your own evidence and voice. | Recipe | Live example | CLI ID | | ------------------ | --------------------------- | ------------ | | Product launch | [Open](/recipes/launch) | `launch` | | Editorial essay | [Open](/recipes/editorial) | `editorial` | | Case study | [Open](/recipes/case-study) | `case-study` | | Before and after | [Open](/recipes/comparison) | `comparison` | | Feature chapters | [Open](/recipes/gallery) | `gallery` | | Personal portfolio | [Open](/recipes/portfolio) | `portfolio` | ```tsx "use client"; import { Story } from "react-kino/story"; import { createStoryRecipe } from "react-kino/recipes"; import { createRecipeComponents } from "react-kino/recipe-kit"; const document = createStoryRecipe("editorial"); export default function Page() { return ; } ``` Recipes are ordinary version 1 documents. `createStoryRecipe` returns a fresh copy for editing. The optional `react-kino/recipe-kit` entry provides original SVG art, an interactive comparison, project cards, a quotation and an honest evidence template. These are sample components you can replace with your own registrations. They are included automatically in Storyboard, but not in the default runtime. The comparison recipe expects `Before` and `After` component registrations; our live example uses original schematic layouts, not fabricated client results. ```tsx , After: }} /> ``` Use a component registration for meaningful links and media behavior. Set an image layer's `alt` description, use assets you have permission to publish, and check unavailable-image states. Source is not extracted from your React components; Storyboard adjusts their containing layer. ## Work with your coding agent The package includes `skills/kino/SKILL.md` with the current contract, safe editing workflow, and checks. Ask your agent to create a story document, run validation, and then preview it in the real renderer. A JSON document and explicit diagnostics are usually sufficient; this release does not require an MCP server. --- # Scroll-linked video with a readable fallback (https://www.react-kino.dev/docs/recipes/video-story) Prepare media for scroll seeking, avoid eager full downloads, and provide poster and error states. See the [live component lab](/playground) for a real four-second original sample and an intentional media-error example. The reproducible source is `scripts/generate-demo-video.sh`; the sample is MIT licensed with the repository. ```tsx The motion preview is unavailable. The hinge folds flat for storage.

} >

The same explanation remains part of the page.

``` The default is metadata preload. Set `preload="none"` if you want to defer loading further, or `auto` only when the media cost is justified. Reduced-motion mode does not attach the video source and keeps the poster plus a readable overlay. Media errors remove the pinned spacer and show the fallback. A visible loading status remains until playable data arrives. The seek loop waits for an in-flight seek and then applies the latest requested position. This prevents queuing every intermediate scroll frame, but it cannot make a poorly encoded or unavailable video scrub smoothly. Use a short muted video, sensible dimensions/bitrate, frequent keyframes, and an HTTP server that supports range requests. Test your actual file on Safari and mobile devices. Keep critical text outside the pixels of the video. A poster needs a useful description, and any spoken content needs an accessible text or caption alternative. For an editorial sequence with text entering over a fixed visual, combine ordinary semantic text with Scene and explicit transform ranges rather than baking the words into the video.