# Imgcli > Imgcli is a shadcn-style registry of Remotion components for product demos, launch videos and social clips. Install free components with `npx shadcn@latest add https://imgcli.com/r/.json`. The CLI copies the source and its dependencies into your project. To use `@imgcli/`, configure the mapping in the [installation guide](https://imgcli.com/docs/getting-started/installation). Set up the Remotion project's `components.json` and `@/` alias using the installation guide. The free component source is MIT licensed. The [Video Editor](https://imgcli.com/docs/video-editor) exports MP4 without a Remotion project. Sign in to save and reopen projects and share an exported video by link. New Pro purchases are paused. See the [Roadmap](https://imgcli.com/docs/roadmap) for what's available and what's next. Operated by Liqi Labs. The MIT-licensed source is based on snapcn by Sri Nath. Last updated: 2026-11-12 (newest component release) Building a video? Start at https://imgcli.com/llms-components.txt — every installable component in one table with what it is for, how many frames it runs and what it pulls in, linking to each component's full page. Any docs URL also serves raw markdown: append `.md`. For agent setup, see https://imgcli.com/docs/getting-started/agent-skill. ## Every installable component These 65 items are the complete set available from the free Imgcli registry. Use the names below and do not infer, pluralise or invent entries. - `npx shadcn@latest add https://imgcli.com/r/answer-highlight.json` - `npx shadcn@latest add https://imgcli.com/r/agent-steps.json` - `npx shadcn@latest add https://imgcli.com/r/announce-title.json` - `npx shadcn@latest add https://imgcli.com/r/answer-stream.json` - `npx shadcn@latest add https://imgcli.com/r/text-reveal.json` - `npx shadcn@latest add https://imgcli.com/r/text-highlight.json` - `npx shadcn@latest add https://imgcli.com/r/text-rewrite.json` - `npx shadcn@latest add https://imgcli.com/r/text-select.json` - `npx shadcn@latest add https://imgcli.com/r/text-swap.json` - `npx shadcn@latest add https://imgcli.com/r/text-swell.json` - `npx shadcn@latest add https://imgcli.com/r/text-build.json` - `npx shadcn@latest add https://imgcli.com/r/word-flip.json` - `npx shadcn@latest add https://imgcli.com/r/word-captions.json` - `npx shadcn@latest add https://imgcli.com/r/karaoke-captions.json` - `npx shadcn@latest add https://imgcli.com/r/logo-drift.json` - `npx shadcn@latest add https://imgcli.com/r/logo-assemble.json` - `npx shadcn@latest add https://imgcli.com/r/logo-flicker.json` - `npx shadcn@latest add https://imgcli.com/r/block-wordmark.json` - `npx shadcn@latest add https://imgcli.com/r/phone-frame.json` - `npx shadcn@latest add https://imgcli.com/r/laptop-frame.json` - `npx shadcn@latest add https://imgcli.com/r/terminal-simulator.json` - `npx shadcn@latest add https://imgcli.com/r/screen-recording.json` - `npx shadcn@latest add https://imgcli.com/r/count-grid.json` - `npx shadcn@latest add https://imgcli.com/r/roster-grant.json` - `npx shadcn@latest add https://imgcli.com/r/wordmark-cut.json` - `npx shadcn@latest add https://imgcli.com/r/cursor-track.json` - `npx shadcn@latest add https://imgcli.com/r/pulsing-border.json` - `npx shadcn@latest add https://imgcli.com/r/follower-rush.json` - `npx shadcn@latest add https://imgcli.com/r/prompt-send.json` - `npx shadcn@latest add https://imgcli.com/r/prompt-zoom.json` - `npx shadcn@latest add https://imgcli.com/r/punch-lines.json` - `npx shadcn@latest add https://imgcli.com/r/orbit-gallery.json` - `npx shadcn@latest add https://imgcli.com/r/hero-launch.json` - `npx shadcn@latest add https://imgcli.com/r/moodboard-reveal.json` - `npx shadcn@latest add https://imgcli.com/r/reel-collage.json` - `npx shadcn@latest add https://imgcli.com/r/reel-wall.json` - `npx shadcn@latest add https://imgcli.com/r/phone-stack.json` - `npx shadcn@latest add https://imgcli.com/r/portal-intro.json` - `npx shadcn@latest add https://imgcli.com/r/dot-bloom.json` - `npx shadcn@latest add https://imgcli.com/r/hello-zoom.json` - `npx shadcn@latest add https://imgcli.com/r/spec-sweep.json` - `npx shadcn@latest add https://imgcli.com/r/drop-line.json` - `npx shadcn@latest add https://imgcli.com/r/title-sweep.json` - `npx shadcn@latest add https://imgcli.com/r/cut-lockup.json` - `npx shadcn@latest add https://imgcli.com/r/bloom-lockup.json` - `npx shadcn@latest add https://imgcli.com/r/gather-lockup.json` - `npx shadcn@latest add https://imgcli.com/r/sky-lockup.json` - `npx shadcn@latest add https://imgcli.com/r/search-typing.json` - `npx shadcn@latest add https://imgcli.com/r/status-cycle.json` - `npx shadcn@latest add https://imgcli.com/r/type-morph.json` - `npx shadcn@latest add https://imgcli.com/r/orb-swarm.json` - `npx shadcn@latest add https://imgcli.com/r/word-gather.json` - `npx shadcn@latest add https://imgcli.com/r/word-wheel.json` - `npx shadcn@latest add https://imgcli.com/r/brand-wheel.json` - `npx shadcn@latest add https://imgcli.com/r/check-cycle.json` - `npx shadcn@latest add https://imgcli.com/r/font-shuffle.json` - `npx shadcn@latest add https://imgcli.com/r/button-press.json` - `npx shadcn@latest add https://imgcli.com/r/click-type.json` - `npx shadcn@latest add https://imgcli.com/r/capture-reveal.json` - `npx shadcn@latest add https://imgcli.com/r/channel-thread.json` - `npx shadcn@latest add https://imgcli.com/r/logo-collapse.json` - `npx shadcn@latest add https://imgcli.com/r/card-rail.json` - `npx shadcn@latest add https://imgcli.com/r/snap-cn-ui.json` - `npx shadcn@latest add https://imgcli.com/r/caret.json` - `npx shadcn@latest add https://imgcli.com/r/input.json` # Use Imgcli with an AI agent URL: https://imgcli.com/docs/getting-started/agent-skill Install Imgcli Skills and plugins from the public repository to build Remotion videos with a coding agent. ## Install the Skills Run this from your Remotion project. The `imgcli` Skill helps your agent choose components, compose scenes, and check the render. The [Skills repository](https://github.com/imgcli/skills) is public. ```bash npx skills add imgcli/skills --skill imgcli ``` Add another `--skill` flag to install a workflow along with the main Skill. ```bash npx skills add imgcli/skills --skill imgcli --skill remotion-product-demo ``` | Skill | Use it for | | --- | --- | | `remotion-launch-video` | Launch films and feature announcements | | `remotion-product-demo` | Product walkthroughs from screenshots or recordings | | `remotion-motion-graphics` | Motion graphics and short openers | | `remotion-kinetic-typography` | Animated text and title cards | | `remotion-logo-animation` | Logo reveals and end cards | | `remotion-explainer-video` | Narrated explainers with captions | ## Claude Code plugin Run these commands in a Claude Code session. ```text /plugin marketplace add imgcli/skills /plugin install imgcli@imgcli ``` ## Cursor plugin In **Customize**, choose **From GitHub Repository** and enter `https://github.com/imgcli/skills`. Select **Imgcli** and click **Install**. For a team rollout, import the same URL as a [team marketplace](https://cursor.com/docs/plugins#add-a-team-marketplace). To install the main Skill directly for Cursor, run `npx skills add imgcli/skills --skill imgcli -a cursor`. ## Use the component index Give your agent the [component index](/llms-components.txt) to find components, durations, and dependencies. Each entry links to its props and examples. The [installation guide](/docs/getting-started/installation) shows how to add components from the public Imgcli Registry. ```text Use https://imgcli.com/llms-components.txt to choose a text animation for my Remotion project. Install it from the Imgcli registry, add it to a composition, and render the composition to check that it works. ``` The [documentation index](/llms.txt) and [full documentation](/llms-full.txt) are available as plain text. --- # Installation URL: https://imgcli.com/docs/getting-started/installation Set up your Remotion project's aliases and install a free Imgcli component with the shadcn CLI. Start with a Remotion project. Run `npx create-video@latest` if you need one. ## 1. Teach your project the `@/` alias Components are copied in importing `@/components/imgcli/…` and `@/lib/snap-cn-ui`. A `create-video` project has no `@` alias, and Remotion's bundler does not read `tsconfig` paths — so set it in both places, or the import resolves in your editor and fails at render. ```json title="tsconfig.json" { "compilerOptions": { "paths": { "@/*": ["./src/*"] } } } ``` On TypeScript 5 you also need `"baseUrl": "."` alongside it. **TypeScript 7 removed `baseUrl`** and errors on it — `paths` are resolved relative to the `tsconfig.json` now, so leave it out. `create-video` scaffolds neither. ```ts title="remotion.config.ts" import path from "node:path"; import { Config } from "@remotion/cli/config"; import { enableTailwind } from "@remotion/tailwind-v4"; Config.overrideBundlerConfig((config) => { const c = enableTailwind(config); return { ...c, resolve: { ...c.resolve, alias: { ...c.resolve?.alias, "@": path.join(process.cwd(), "src") }, }, }; }); ``` ## 2. Add a `components.json` The shadcn CLI reads this to know where files go. `npx shadcn@latest init` writes one for you — but only in a framework it recognises, and Remotion is not one of them, so write it yourself: ```json title="components.json" { "$schema": "https://ui.shadcn.com/schema.json", "style": "new-york", "rsc": false, "tsx": true, "tailwind": { "config": "", "css": "src/index.css", "baseColor": "neutral", "cssVariables": true }, "aliases": { "components": "@/components", "utils": "@/lib/utils", "ui": "@/components/ui", "lib": "@/lib", "hooks": "@/hooks" }, "iconLibrary": "lucide" } ``` ## 3. Add a component ```bash npx shadcn@latest add https://imgcli.com/r/text-reveal.json ``` The URL points to Imgcli's registry. The CLI copies `text-reveal.tsx` to `src/components/imgcli/` and adds the shared files under `src/lib/snap-cn-ui/`. You can install the free components without an account or API key. For shorter commands, add this mapping to your `components.json`. Keep any registry mappings you already use. ```json title="components.json" { "registries": { "@imgcli": "https://imgcli.com/r/{name}.json" } } ``` ```bash npx shadcn@latest add @imgcli/text-reveal ``` ## Component code The exact file `shadcn add` copies into your project. ## Install everything Want the whole library at once? ## 4. Use it in a Remotion composition ```tsx import { Composition } from "remotion"; import { TextReveal } from "@/components/imgcli/text-reveal"; export const RemotionRoot = () => ( ); ``` Components take shadcn's light palette by default, and a Remotion render with no background of its own is black — so pass `mode="dark"` (or your own `theme`) when you render onto a dark frame, or near-black text lands on a near-black background. That's it. Render with `npx remotion render HelloWorld out.mp4`. For Claude Code, Cursor, or another coding agent, see the [Imgcli Skills and plugin instructions](/docs/getting-started/agent-skill). --- # Introduction URL: https://imgcli.com/docs/getting-started/introduction What Imgcli is and who it's for — Remotion components you install with the shadcn CLI and own as code in your project. Imgcli is built for solo builders and small teams who need to ship product demo videos without rebuilding the same animation primitives every time. If you've used [shadcn/ui](https://ui.shadcn.com), you'll feel at home: the workflow is identical, just for video instead of UI. ## Why Imgcli - **The type is measured, not eyeballed.** Scaled text judders because the glyph rasteriser has no vertical sub-pixel positioning, so a moving baseline climbs the pixel grid in whole-pixel jumps. Every text component here pivots its scale on the measured baseline and renders with `text-rendering: geometricPrecision`, which stops the stems re-hinting mid-animation. Both were found on rendered frames rather than in a browser preview, because you cannot see a sub-pixel bug. - **What the preview shows is what the file renders.** Where a live `@remotion/player` misrepresents a scene, because frame pacing on a 120Hz display makes smooth motion look stuck, the gallery plays the actual rendered mp4 instead. - **One design system, and it is shadcn's.** Components take `theme` and `mode` props resolved from the same token set as the site's `Button` and `Input`, so a scene dropped into your app does not arrive with its own palette. `pnpm check:tokens` fails the build if the two ever drift. - **The file is yours the moment it lands.** The CLI copies the source and installs its declared dependencies. Your installed source changes when you edit or update it. ## Prerequisites You should already have a Remotion project set up. If you don't: ```bash npx create-video@latest ``` ## What's next - [Installation](/docs/getting-started/installation): add Imgcli to an existing Remotion project --- # License URL: https://imgcli.com/docs/getting-started/license MIT licensing for Imgcli's free Remotion component source and support for existing licenses. ## Free components The free component source distributed through the Imgcli registry is under the [MIT license](https://opensource.org/license/mit). Keep the copyright and license notice when you redistribute the source. The library is based on [snapcn by Sri Nath](https://github.com/shadcn-labs/snapcn). For questions about an existing license, contact [hello@imgcli.com](mailto:hello@imgcli.com). --- # Components URL: https://imgcli.com/docs/components Imgcli components, grouped by category ## Text & Titles - [Hello Zoom](https://imgcli.com/docs/text/hello-zoom): Words flicker in place, one line types on, then the camera dives through its own letters into the next card - [Glow Pullback](https://imgcli.com/docs/text/glow-pullback) (Pro access required): A line that opens in close and pulls back to the whole sentence. - [Spec Sweep](https://imgcli.com/docs/text/spec-sweep): A one-line spec slides in word by word while a highlight runs along it and an icon charges up - [Drop Line](https://imgcli.com/docs/text/drop-line): Words cut past one card at a time, then the last word sinks out from under the dot of its own i - [Title Sweep](https://imgcli.com/docs/text/title-sweep): An end title lit by a gradient that travels through the letters, cuts in the dark, and a second title takes over - [Cross Out](https://imgcli.com/docs/text/cross-out) (Pro access required): Cross out the advice, then name the thing it was standing in for. - [Check Cycle](https://imgcli.com/docs/text/check-cycle): A sentence that finishes itself over and over — the word beside a ticked checkbox rolls up out of a slot, Intros, Demos, Launches, Changelogs, Tutorials, And more — then the line steps back a little and rests - [Focus Pull](https://imgcli.com/docs/text/focus-pull) (Pro access required): A phrase at a time, resolving out of a moving colour field. - [Word Wheel](https://imgcli.com/docs/text/word-wheel): A column of words that spins like a reel and stops on one of them — the lead-in holds the left of the line while the answer rolls into the slot beside it, decelerating over a second and three quarters - [Brand Wheel](https://imgcli.com/docs/text/brand-wheel): A lead word and a wheel of brand pills rolling through where you ship — X, YouTube, LinkedIn, Product Hunt — until a cursor clicks the last one - [Word Gather](https://imgcli.com/docs/text/word-gather): A sentence that arrives one word at a time, out of order, and gathers — the first word holds the frame alone, then the rest drop in around it in the accent colour and slide the last of the way home - [Font Shuffle](https://imgcli.com/docs/text/font-shuffle): A launch title that decodes out of scrambled glyphs, flashes the accent, then the product name tries on eleven typefaces while the line steps closer - [Type Morph](https://imgcli.com/docs/text/type-morph): A headline that types itself under a glowing caret, sheds its lead, morphs letter by letter, and ends under a colour flood - [Text Reveal](https://imgcli.com/docs/text/text-reveal): A cinematic zoom-out title reveal — the lead word appears large, then scales down and slides into place as the sentence sweeps in and settles - [Manifesto](https://imgcli.com/docs/text/manifesto) (Pro access required): Three statements, three different arrivals, hard cuts between them. - [Punch Lines](https://imgcli.com/docs/text/punch-lines): Full-frame title cards that cut hard between beats — a statement flicked in line by line and rushed past the camera, then a hero line whose words punch up from a third of their size - [Text Swell](https://imgcli.com/docs/text/text-swell): The lead word floats toward you and hangs there while the sentence assembles around it, letters bouncing up off the baseline — then the whole line falls back - [Pack Up](https://imgcli.com/docs/text/pack-up) (Pro access required): A line types itself, catches the light, and its last word is packed into a box. - [Text Highlight](https://imgcli.com/docs/text/text-highlight): Animated emphasis on one span inside a static sentence — marker, color, underline, strikethrough, or shimmer - [Phrase Swarm](https://imgcli.com/docs/text/phrase-swarm) (Pro access required): One phrase comes apart and another gathers out of the pieces. - [Text Rewrite](https://imgcli.com/docs/text/text-rewrite): A line writes itself, is selected, then has its opening swept away by a clip that cuts letters in half — and a new ending written on while it slides to its new centre - [Text Select](https://imgcli.com/docs/text/text-select): A line that writes itself a word at a time — each arriving in the accent and cooling a beat later, so one word is ever coloured — then pushes forward to full size and gets selected under a shining gradient - [Text Swap](https://imgcli.com/docs/text/text-swap): Replace one line of text with another using exit-then-enter scheduling and five transition presets - [Read Through](https://imgcli.com/docs/text/read-through) (Pro access required): A sentence set enormous and read a word at a time. - [Text Build](https://imgcli.com/docs/text/text-build): Words enter one at a time while the already-placed words reflow to stay centered — as a line or a stack - [Word Flip](https://imgcli.com/docs/text/word-flip): A headline types itself out, then one word cycles on a 3D flip — anticipation dip, motion-blurred throw, zero reflow - [Say It](https://imgcli.com/docs/text/say-it) (Pro access required): A word that comes apart, leaves a control behind, and gets pressed. - [Sentence Set](https://imgcli.com/docs/text/sentence-set) (Pro access required): A sentence setting itself, a word at a time. - [Stretch Word](https://imgcli.com/docs/text/stretch-word) (Pro access required): A word that will not stop, and the line that follows it. - [Wall Cut](https://imgcli.com/docs/text/wall-cut) (Pro access required): A sentence read out on an LED wall, cut from the pixels to the room. - [Word Rush](https://imgcli.com/docs/text/word-rush) (Pro access required): A field of words the camera flies through, one line of it that runs at the lens, and the line that answers on the other side. - [Word Settle](https://imgcli.com/docs/text/word-settle) (Pro access required): A sentence that arrives a word at a time, hot, and cools as it lands. - [Trend Type](https://imgcli.com/docs/text/trend-type) (Pro access required): A headline types itself while a trend line draws underneath it. - [Ember Reveal](https://imgcli.com/docs/text/ember-reveal) (Pro access required): A soft blue brush sweeps across a white frame and blooms into a glow, and the line writes itself out of the glow behind it, settling with a faint halo. - [Spark Question](https://imgcli.com/docs/text/spark-question) (Pro access required): A question asked one phrase at a time: each phrase lands huge and settles while the camera keeps pulling back, its words blurring up into place one after another as small four-point sparkles dart past, and a hard cut swaps in the next. - [Word Insert](https://imgcli.com/docs/text/word-insert) (Pro access required): A line that grows from its last word, in 12fps stop motion: each new word is slipped in ahead of it, landing bold and dark and settling to the line's ink while the words to its left glide aside, the words to its right step over and the underline stretches to fit, every word shaking on its own each pose. - [Echo Stack](https://imgcli.com/docs/text/echo-stack) (Pro access required): One word with copies of itself stacked above and below in horizontal windows, fading as they go. - [Find Yours](https://imgcli.com/docs/text/find-yours) (Pro access required): "Find yours" rises in word by word while a list of what you could make rolls up beside it through a soft fade: slow, then a motion-blurred rush, then a long brake onto one answer. - [Count Portal](https://imgcli.com/docs/text/count-portal) (Pro access required): A lead word assembles out of order, words cut past and the last one slams and inverts. - [Slam Type](https://imgcli.com/docs/text/slam-type) (Pro access required): Three hard cuts of one ultra-wide black word, each larger than the last, with one hot-metal gradient travelling through all three while the type pushes in. - [Spec Wall](https://imgcli.com/docs/text/spec-wall) (Pro access required): A heading arrives word by word, spec lines type on underneath and the block scrolls, faster and faster, until the card inverts and the rest flies past too quickly to read. ## Captions - [Word Captions](https://imgcli.com/docs/captions/word-captions): Burned-in captions in the styles big channels use — the YouTube box by default (white Roboto on a per-line black box), plus outlined and accent presets - [Karaoke Captions](https://imgcli.com/docs/captions/karaoke-captions): A caption line over any footage — the YouTube per-line black box by default, with karaoke fill, highlight-bar, and pill presets ## Logos - [Logo Collapse](https://imgcli.com/docs/logos/logo-collapse): A stack of shots flicked through, collapsing into your mark, and the wordmark landing beside it — each card smaller and held for less time than the last, cutting rather than tweening, until the stack resolves into a logo - [Lockup Reveal](https://imgcli.com/docs/logos/lockup-reveal) (Pro access required): A logo lockup opening on a construction grid, then turning out to be a window. - [Logo Drift](https://imgcli.com/docs/logos/logo-drift): A field of stack tiles drifting past a headline while the camera pulls steadily back, so the wall of things-it-works-with keeps arriving from every edge - [Logo Assemble](https://imgcli.com/docs/logos/logo-assemble): A ring of image cards revolves and drains to the centre, giving birth to a simple logo that slides left as the brand name reveals to its right - [Logo Flicker](https://imgcli.com/docs/logos/logo-flicker): Images flip across the screen very fast, the flicker decelerates and fades, and the logo and brand name resolve underneath - [Block Wordmark](https://imgcli.com/docs/logos/block-wordmark): A square scales in, a deck of coloured cards fans out around it and winds back, then the stack stretches into one block per letter and each block flashes a colour and swaps for its real letterform ## Screens & Devices - [Phone Stack](https://imgcli.com/docs/screens/phone-stack): Phone screens playing clips rise out of a blur one after another, each pushing the stack back so the older ones peek out on top - [App Reveal](https://imgcli.com/docs/screens/app-reveal) (Pro access required): A wordmark that folds into an app, and a camera that flies around it. - [Brief Send](https://imgcli.com/docs/screens/brief-send) (Pro access required): An empty project page pulls away, and the brief that fills it gets sent. - [Build Out](https://imgcli.com/docs/screens/build-out) (Pro access required): A send button that fills the frame, and the app that comes out of it. - [Checkout Push](https://imgcli.com/docs/screens/checkout-push) (Pro access required): A phone paying for something, pushed through the camera. - [Gallery Push](https://imgcli.com/docs/screens/gallery-push) (Pro access required): A wall of screenshots that opens one column at a time. - [Laptop Open](https://imgcli.com/docs/screens/laptop-open) (Pro access required): A laptop opening out of focus, holding, and then swallowing the lens. - [LCD Type](https://imgcli.com/docs/screens/lcd-type) (Pro access required): A type specimen on a screen you can see the pixels of. - [Phone Pitch](https://imgcli.com/docs/screens/phone-pitch) (Pro access required): A headline and a phone: the pitch shot. - [Picker Commit](https://imgcli.com/docs/screens/picker-commit) (Pro access required): A picker wheel that flings, settles on one row, and commits it into a button. - [Screen Wall](https://imgcli.com/docs/screens/screen-wall) (Pro access required): Four phones down a tunnel around a sentence, and the shot backing out of them. - [String Hero](https://imgcli.com/docs/screens/string-hero) (Pro access required): A line of code being typed, and then the string inside it becomes the headline. - [Phone Frame](https://imgcli.com/docs/screens/phone-frame): iPhone-style device frame with a dynamic island — sways in 3D showing off a glowing ride-summary map that draws itself - [Tap Through](https://imgcli.com/docs/screens/tap-through) (Pro access required): Somebody's feed, the button they press, and the line that press was for. - [Laptop Frame](https://imgcli.com/docs/screens/laptop-frame): MacBook that opens, runs a notch notification, then dives into the screen until an image or video fills the frame - [Terminal Simulator](https://imgcli.com/docs/screens/terminal-simulator): Terminal window with chunked command playback, freeze-frame pauses, step scrolling, and an optional cursor-pinned zoom - [Screen Recording](https://imgcli.com/docs/screens/screen-recording): Crops the browser and OS chrome off a raw screen capture, fits what is left to the frame, and runs a keyframed camera that pushes in on whatever matters - [Cursor Track](https://imgcli.com/docs/screens/cursor-track): A synthetic cursor that walks a path of waypoints over any children and pulses a click ring on arrival — for sims, and for recordings whose cursor never made the file - [Capture Reveal](https://imgcli.com/docs/screens/capture-reveal): A toast counts a screenshot up to 100% over a browser window, the window drops away, "Copied to clipboard." pops and a headline rises out from behind it ## Cursor & Clicks - [Click Type](https://imgcli.com/docs/cursor/click-type): A prompt composer writes its own placeholder while a cursor flies in and clicks the field, then a hard cut into the field where the prompt types at the caret - [Button Press](https://imgcli.com/docs/cursor/button-press): A cursor walks onto a split action button: hover, press, a spinner while it works, then a drawn tick. The deploy beat of every launch video ## Transitions - [Pixel Peak](https://imgcli.com/docs/transitions/pixel-peak) (Pro access required): Transition for @remotion/transitions: the next shot climbs up through the last one as a stepped peak of blocks. - [Dot Dissolve](https://imgcli.com/docs/transitions/dot-dissolve) (Pro access required): LED dot-matrix transition: dots pop in across the frame in clumps until the matrix covers it, hold, then break up in clumps and let go, the last stragglers out bottom left. - [Dot Bloom](https://imgcli.com/docs/transitions/dot-bloom): A halftone transition — the outgoing shot washes to cream, breaks into warm drifting dots, and the dots thin out onto the incoming shot ## Charts & Stats - [Hex Tally](https://imgcli.com/docs/charts/hex-tally) (Pro access required): A metric card that lands while a hex unit chart fills in under a running total. - [Metric Morph](https://imgcli.com/docs/charts/metric-morph) (Pro access required): The control becomes the number it produces. - [Scatter Bloom](https://imgcli.com/docs/charts/scatter-bloom) (Pro access required): A scatter plot that blooms onto a tilted plane while the camera drifts along it. - [Tally Rise](https://imgcli.com/docs/charts/tally-rise) (Pro access required): A currency figure climbing on a bloom that dies under it. - [Ticker Climb](https://imgcli.com/docs/charts/ticker-climb) (Pro access required): A claim, and then the live number that proves it. - [Vault Count](https://imgcli.com/docs/charts/vault-count) (Pro access required): A balance ticking up while the payments that fed it drift past. ## Social Proof - [Chat Thread](https://imgcli.com/docs/social/chat-thread) (Pro access required): A chat thread sending itself — the moment a demo video actually gets shared. - [Comment Storm](https://imgcli.com/docs/social/comment-storm) (Pro access required): The comment section under a launch everyone is waiting for, burying the frame. - [Channel Thread](https://imgcli.com/docs/social/channel-thread): A work chat filling itself in — faces, a name in bold, a quiet timestamp and messages as plain lines on a dark stage; three dots hold a line until the words arrive, and the transcript scrolls once per arrival and is perfectly still in between - [Thank You Swarm](https://imgcli.com/docs/social/thank-you-swarm) (Pro access required): A notification row of supporters that tears off under its own motion blur and comes back as a wall of them. - [Follower Rush](https://imgcli.com/docs/social/follower-rush): An X-style follower notification that piles up — avatars stack in and the count explodes, then the row bends into an undulating wave of faces ## Scenes - [Hype Reel](https://imgcli.com/docs/scenes/hype-reel) (Pro access required): A vertical video post going viral in three seconds: a close-up on its view count racing up, a cut out to the whole card under its caption, then avatars, chat bubbles and reactions spring in around it while the count tops out at 999k. - [Depth Swap](https://imgcli.com/docs/scenes/depth-swap) (Pro access required): A headline swaps inside a wall of pictures with real depth. - [Reel Wall](https://imgcli.com/docs/scenes/reel-wall): A curved wall of vertical clips turns past in the dark — it eases in from rest, then keeps turning - [Column Cut](https://imgcli.com/docs/scenes/column-cut) (Pro access required): A four-line cascade with a square cursor beside its third line, a blue highlight running across that line letter by letter while the type drifts up — then a hard cut to a column of pictures that jumps one picture per beat, the beats tightening from five frames to four, while a sentence lands either side of it and the whole frame pans away left, faster and faster. - [Bloom Lockup](https://imgcli.com/docs/scenes/bloom-lockup): A wall of finished work blooms outward from your logo, cuts in tight on it, then bends away through a lens and a flash while the logo rises to take its wordmark - [Gather Lockup](https://imgcli.com/docs/scenes/gather-lockup): A wall of your work sits under a line of type. The line falls apart word by word, the wall cuts in to a row of four, and each slot swaps its picture and hands over to your mark and wordmark - [Sky Lockup](https://imgcli.com/docs/scenes/sky-lockup): A one-line promise takes itself apart word by word, the page washes off to a soft sky, and the mark in the middle of the sentence slides over and becomes your logo - [Cut Lockup](https://imgcli.com/docs/scenes/cut-lockup): A logo reveal in hard cuts — huge on grid paper, three cuts down, a push to night where its construction lines draw themselves, then paper wipes up onto the lockup - [Orb Swarm](https://imgcli.com/docs/scenes/orb-swarm): A sentence told one beat at a time while glass orbs burst out of it, split into eight and sixteen, wind into a vortex, pay out as a chain and loop around the last words - [Card Rail](https://imgcli.com/docs/scenes/card-rail): A rail of cards flicked sideways across a tilted plane — pushed three times, each flick leaving at speed and decelerating for over a second into the next, while the plane's foot runs a fifth wider than its head - [Library Flight](https://imgcli.com/docs/scenes/library-flight) (Pro access required): A title, a fly-through of the whole library, and the line it adds up to. - [Orbit Flow](https://imgcli.com/docs/scenes/orbit-flow) (Pro access required): Three dashed orbits round a line of type, with three labelled stops riding the inner one. - [Proof Line](https://imgcli.com/docs/scenes/proof-line) (Pro access required): Five pieces of shipped work collapse, one at a time, into the sentence that describes them. - [Render Wall](https://imgcli.com/docs/scenes/render-wall) (Pro access required): A field of loose code that resolves into a wall of finished work, and then the wall backs away. - [Showcase Drift](https://imgcli.com/docs/scenes/showcase-drift) (Pro access required): A wall of work sliding to a stop behind a headline. - [Version Drop](https://imgcli.com/docs/scenes/version-drop) (Pro access required): A name typing itself across two shots, and the number it was all for. - [Wire Feed](https://imgcli.com/docs/scenes/wire-feed) (Pro access required): A live feed writing its newest item while the camera pulls back. - [Word Montage](https://imgcli.com/docs/scenes/word-montage) (Pro access required): Photos gathering round a line that keeps changing its mind, then collapsing into the sentence it was building to. - [Count Grid](https://imgcli.com/docs/scenes/count-grid): A grid of cards rushes in under a horizontal smear and settles on five, then the camera pulls back a fraction while every empty cell fills in from the middle out and the count jumps from five to five hundred - [Roster Grant](https://imgcli.com/docs/scenes/roster-grant): Three rows of roster cards stream past a call to action, a pointer walks in and clicks it, and the monograms start turning to the accent one every seven frames — top, middle, bottom, top, bottom, middle - [Blueprint Collage](https://imgcli.com/docs/scenes/blueprint-collage) (Pro access required): A headline cycles through three phrases while five pictures and a disc hop around it in 15fps stop motion, the camera eases back, and the pictures clear away one by one to leave the last phrase fading from the accent to ink. - [Wordmark Cut](https://imgcli.com/docs/scenes/wordmark-cut): A wordmark twice the width of the frame, cooling from a lit gradient to ink as it settles, then a hard cut onto its last letters and the dot — nearer, shallower, and pushing in - [Flood Outro](https://imgcli.com/docs/scenes/flood-outro) (Pro access required): An end card: your mark and wordmark write in left to right over a warm wave, the wave sinks away, then a blue-to-orange gradient floods down the frame and the lockup turns white letter by letter. - [Announce Title](https://imgcli.com/docs/scenes/announce-title): A four-shot launch title — the eyebrow rushes past the camera on a receding type plane, the name assembles on paper, and a macro pan cuts wide as the tagline builds itself last word first - [Status Cycle](https://imgcli.com/docs/scenes/status-cycle): A status pill whose label rolls behind a hard clip while its width springs past the target and back, then the field crossfades to a column of chips stepping up from below - [Mark Grid](https://imgcli.com/docs/scenes/mark-grid) (Pro access required): The last scene wipes away and a line types itself in around your mark, lifts off word by word, and a staggered wall of tiles opens round the mark as the camera eases in. - [Product Hero](https://imgcli.com/docs/scenes/hero-launch): Cinematic product-launch hero — two cards slide into formation as the headline reveals above - [Lens Wall](https://imgcli.com/docs/scenes/lens-wall) (Pro access required): A wall of 18 pictures fades in tile by tile on a slow zoom-out, then a lens bends it: the edges stretch, the middle pulls back, flashes and cuts to the bare wall. - [Orbit Gallery](https://imgcli.com/docs/scenes/orbit-gallery): A ring of feature cards orbits a central product mark, each rotating upright as it swings to the front - [Moodboard Reveal](https://imgcli.com/docs/scenes/moodboard-reveal): A kinetic headline with a swapping inline image, then a scattered photo gallery flies in and the camera pushes through it — dark to light — onto a hero image - [Mood Board](https://imgcli.com/docs/scenes/mood-board) (Pro access required): A collage builds itself under a two-line headline. - [Reel Collage](https://imgcli.com/docs/scenes/reel-collage): A photo reel between two captions shuts like an eyelid, the captions fold into one line, then split as a collage blooms in on a peach flush - [Launch Video Template](https://imgcli.com/docs/scenes/template-launch) (Pro access required): A finished product-launch video in five beats — hook, product, proof, numbers, wordmark — on one paper with a dissolve between each. - [Portal Intro](https://imgcli.com/docs/scenes/portal-intro): A sentence with a picture chip in it leans in until the chip takes the frame, then a title types over the picture and the mark and name arrive - [Glow Map](https://imgcli.com/docs/scenes/glow-map) (Pro access required): A light sweeps off the frame and a dotted world map wipes in west to east as it glides into place; points light up across it one after another and rings pulse out of them. - [Glow Type](https://imgcli.com/docs/scenes/glow-type) (Pro access required): Dark type lights up word by word as the camera eases back, then slides off left; the camera dives into the second line, glides along it letter-size while each letter lights as it passes, and pulls back out. - [Prompt Poster](https://imgcli.com/docs/scenes/prompt-poster) (Pro access required): A dark composer with an image attached finishes typing its request, a pointer glides in turning as it travels, presses send, and the page scrolls: the composer rides up and off while the generated poster rises in and settles. - [Pain Point](https://imgcli.com/docs/scenes/pain-point) (Pro access required): The opening beat of a launch video: the viewer's pain, typed as a question, while the camera pulls back through the tools they are stuck in. - [Glow Frame](https://imgcli.com/docs/scenes/glow-frame) (Pro access required): A line of type scales up while a thin light enters at the edge of the screen, runs round all four sides and fades to a hairline, its colours drifting round the frame. - [Metal Chip](https://imgcli.com/docs/scenes/metal-chip) (Pro access required): A brushed-metal chip with your mark embossed on its face pushes in and un-tilts in perspective while a soft light rolls over the surface. ## AI Chat Input - [Prompt Roll](https://imgcli.com/docs/ai-input/prompt-roll) (Pro access required): A prompt card is pushed in on while "Make your own …" types, cuts closer, and the last words wrap into a pill that rolls through what you can make, each in its own tint. - [Agent Chat](https://imgcli.com/docs/ai-input/agent-chat) (Pro access required): An agent conversation, from the empty field to the answer. - [Agent Open](https://imgcli.com/docs/ai-input/agent-open) (Pro access required): A line assembles a word at a time, resolves to your product's name, and becomes a prompt somebody types. - [Agent Compose](https://imgcli.com/docs/ai-input/agent-compose) (Pro access required): An agent's composer building itself around one chip, then sending a request. - [Agent Result](https://imgcli.com/docs/ai-input/agent-result) (Pro access required): An agent asked for something, and what it hands back. - [Agent Run](https://imgcli.com/docs/ai-input/agent-run) (Pro access required): A prompt typed, sent, and answered by an agent that shows its work. - [Agent Tools](https://imgcli.com/docs/ai-input/agent-tools) (Pro access required): The moment somebody hands an agent their tools. - [Files Drop](https://imgcli.com/docs/ai-input/files-drop) (Pro access required): A handful of frames is dragged into a prompt and becomes five attachments. - [Glass Prompt](https://imgcli.com/docs/ai-input/glass-prompt) (Pro access required): A prompt typed into a pane of glass, and a dive through its send button into the ring of pictures it asked for. - [Prompt Dive](https://imgcli.com/docs/ai-input/prompt-dive) (Pro access required): A prompt typed into an assistant, and then a dive into the send button. - [Search Retype](https://imgcli.com/docs/ai-input/search-retype) (Pro access required): A search field types its first query, the camera pushes in, the query deletes back to nothing and a new one types in. - [Task Stream](https://imgcli.com/docs/ai-input/task-stream) (Pro access required): An agent's task list, streaming in and ticking off. - [Search Typing](https://imgcli.com/docs/ai-input/search-typing): A search field wider than the shot — it comes forward, types across its left half, then pages to its right half - [Prompt Send](https://imgcli.com/docs/ai-input/prompt-send): A composer that unrolls from a hairline, writes a brief while the camera cuts in and rides the caret, then cuts back out as a pointer arrives and sends it — with typing that eases in and out instead of ticking - [Prompt Zoom](https://imgcli.com/docs/ai-input/prompt-zoom): An assistant landing screen that offers its suggestions, then cuts hard into the caret — a measured 2.547× push anchored on the text insertion point — where the prompt types itself - [Answer Stream](https://imgcli.com/docs/ai-input/answer-stream): The beat after send — a macro shot on the button cuts hard to the answer building itself, while the camera pulls back about a focal point above the frame to keep up with it - [Answer Highlight](https://imgcli.com/docs/ai-input/answer-highlight): A question, an answer that writes itself a word at a time, then a caret lands mid-sentence and drags a selection across the one statement that matters — and settles onto one word inside it - [Agent Steps](https://imgcli.com/docs/ai-input/agent-steps): An agent narrating its own work — a log that writes itself one line at a time, always on the centre line, then swells and clears until only the answer is left --- # Text & Titles URL: https://imgcli.com/docs/text Kinetic headlines, reveals, swaps, highlights and lower thirds — the decorative title layer of a video. Reach for this category whenever text is the shot: headlines, section titles, feature callouts, name bars. These components handle the enter/exit choreography — stagger, blur, slide, swap — so a title lands with intent instead of just appearing. For transcript-timed subtitles, use [Captions](/docs/captions) instead. --- # Brand Wheel URL: https://imgcli.com/docs/text/brand-wheel A lead word and a wheel of brand pills rolling through where you ship, until a cursor clicks the last one *(interactive preview: https://imgcli.com/docs/text/brand-wheel)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/brand-wheel.json ``` ## Component code The exact files `shadcn add` copies into your project. ## Usage Six items run 139 frames at 30fps; each extra item adds 20 (`brandWheelDuration(n)`). The wheel comes to rest on the last item. Known brands get their mark, anything else its initial. ```tsx import { BrandWheel } from "@/components/imgcli/brand-wheel"; export const Beat = () => ( ); ``` --- # Check Cycle URL: https://imgcli.com/docs/text/check-cycle A sentence that finishes itself over and over — the word beside a ticked checkbox rolls up out of a slot, Intros, Demos, Launches, Changelogs, Tutorials, And more — then the line steps back a little and rests *(interactive preview: https://imgcli.com/docs/text/check-cycle)* A sentence with a checkbox in the middle of it. `Create your own` sits to the left of a ticked box; the word to its right rolls up out of a slot and is replaced — Intros, Demos, Launches, Changelogs, Tutorials, And more — then the whole line steps back to 80% of its size and rests. ## How it moves **The two words in the slot are one strip.** The word leaving and the word arriving keep the same distance on every frame of a roll, so it is one curve, not an ease-in exit and an ease-out entrance: the in-out cubic, `cubic-bezier(0.65, 0, 0.35, 1)`, over exactly half a second, a roll every twenty frames. **The type moves by fractions of a pixel.** A browser rounds a line of type's baseline to a whole pixel, so a word lifting off slowly holds still for four frames and then jumps. Each line is painted twice, one pixel apart, and cross-faded by the fraction between — which is exactly how a video of this line would look. ## Notes Every number was measured off a recording, frame by frame, and verified by rendering this component at the recording's own timestamps and diffing them in Y′CbCr: 0.24/255 mean error across all 65 frames, which is the size of the recording's own compression noise. The checkbox never moves. The lead-in is right-aligned to it and the word left-aligned from it, so new copy changes the words' widths, not where the sentence sits. The type is Inter 3.19, loaded from fontsource; the recording was set in Inter 3, not the Inter 4 Google serves today. If it cannot load, Google's Inter paints. ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/check-cycle.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition } from "remotion"; import { CheckCycle } from "@/components/imgcli/check-cycle"; const Hook = () => ( ); export const RemotionRoot = () => ( ); ``` Each extra word adds one roll — 20 frames. ## Props ", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: '"light" | "dark"', description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Drop Line URL: https://imgcli.com/docs/text/drop-line Words cut past one card at a time, then the last word sinks out from under the dot of its own i *(interactive preview: https://imgcli.com/docs/text/drop-line)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/drop-line.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage Drop Line paints its own card and runs 38 frames at 30fps. Timing is measured frame by frame off a reference; every word, colour and font is a prop. ```tsx import { DropLine } from "@/components/imgcli/drop-line"; export const Beat = () => ; ``` --- # Font Shuffle URL: https://imgcli.com/docs/text/font-shuffle A launch title that decodes out of scrambled glyphs, flashes the accent, settles to ink, then the product name tries on eleven typefaces while the line steps closer *(interactive preview: https://imgcli.com/docs/text/font-shuffle)* A launch line, `Introducing snapcn Pro`. It decodes out of scrambled glyphs set in the wrong typeface, lands in the accent, settles to ink and holds. Then the product name tries on eleven typefaces: mono, light, humanist, pixel, script, slab, condensed, italic, blackletter, wide and a bold grotesk. The whole line steps closer on every swap. ## How it moves **Nothing tweens. Every beat is a held state.** The decode, the accent flash and the swaps are cuts on the 30fps frame grid, the way the original was made: two frames per decode beat, one for the full accent line, three or four per typeface. **The decode is the real line, half-hidden.** It is set in a Baskerville with a per-character mask, plus a handful of oversized foreign glyphs (`b`, `e`, `a`) that hang on from one beat to the next. Longer or shorter copy samples the same mask proportionally, so the density stays the same. **Each swap is also a zoom.** The line stays centred and steps from ×1.03 to ×1.25 about the middle of the frame. The lead keeps its serif. Only the subject changes face. ## Notes Every beat was measured off a 2s screen recording, frame by frame. Each was fitted in BT.709 Y′CbCr through the capture's own blur, then verified by rendering this component at the recording's size and timestamps and diffing the two. Timing, layout, zoom and colour are the recording's. The recording's typefaces are not free, so each one was fingerprinted against every Google family in its category and the closest taken: Source Serif 4 (text cut, pinned), Libre Baskerville, Fragment Mono, Hind, a pixel face and Mona Sans. They load from fontsource; if one cannot, the next face in its stack paints. The accent is the title's own colour, not a theme token. Pass `accent` to change it. ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/font-shuffle.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition } from "remotion"; import { FontShuffle } from "@/components/imgcli/font-shuffle"; const Launch = () => ; export const RemotionRoot = () => ( ); ``` ## Props ", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: '"light" | "dark"', description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Hello Zoom URL: https://imgcli.com/docs/text/hello-zoom Words flicker in place, one line types on, then the camera dives through its own letters into the next card *(interactive preview: https://imgcli.com/docs/text/hello-zoom)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/hello-zoom.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage Hello Zoom paints its own card and runs 75 frames at 30fps. Timing is measured frame by frame off a reference; every word, colour and font is a prop. ```tsx import { HelloZoom } from "@/components/imgcli/hello-zoom"; export const Beat = () => ; ``` --- # Punch Lines URL: https://imgcli.com/docs/text/punch-lines Full-frame title cards that cut hard between beats — a statement flicked in line by line and rushed past the camera, then a hero line whose words punch up from a third of their size and push slowly forward until it cuts *(interactive preview: https://imgcli.com/docs/text/punch-lines)* A whole sequence in one component: you write the script, it cuts itself. Two card looks, and the sequence alternates them. **`slide` — the statement card.** Its lines are already at full size the moment the card opens, sitting left of centre. Each one is flicked right, sails past its mark and settles home, one line behind the last. It leaves by **rushing past the camera** — a scale about the middle of the frame, so the top line climbs out of shot while the bottom line dives out of the bottom. **`punch` — the hero line.** Every word starts at a third of its size and swells into place in a wave, left to right. Once they have all landed the whole line keeps **pushing slowly forward** for as long as it is on screen. It leaves on a hard cut. ```tsx ``` `|` starts a card, `/` starts a line inside one. The three lists are parallel — one entry per card, and the **last entry repeats**, so `"slide,punch"` means "the first card is a statement and everything after it is a hero line". ## The cadence, and why a card is already moving when you first see it `holds` is time **on screen**, one entry per card. So `holds="48"` is three cards of 48 frames each, back to back, and no card can bleed over the one in front of it whichever way that one leaves. The whole run is `durationInFrames` = the sum of `holds`, and nothing else. The one thing that happens off screen is `lead`: a card's clock starts a few frames before its cut, so it is caught mid-entrance rather than starting from nothing. That is the reference's own trick — its third card is already at 87% of its size on the very first frame you can see it, and there is no trace of it, not one coloured pixel, on the frame before. It is most of what makes a cut read as expensive rather than abrupt. Set `lead={0}` and every entrance plays in full. ## The slide is not a spring It looks like one: the line comes in from the left, sails past the middle and eases back. But a damped sinusoid fits the overshoot and then insists on an undershoot that never happens, and a spring released from rest cannot overshoot 0.71em without starting forty times that far out. What fits — to **0.42px rms across 37 frames, both lines at once** — is a **critically damped** system given an initial kick: ``` x(t) = (A + B·t) · e^(−λ·t) ``` `slideFrom` is `A`, `slideKick` is `B`, `slideSettle` is `λ`. The line starts 0.74em left of centre, leaves at speed, reaches 0.71em past centre just under half a second in, and comes home without ever crossing back. Raise `slideSettle` and the whole move tightens up; drop `slideKick` to nothing and it just eases in from the left. ## The smear A browser animating `translate` draws every frame perfectly sharp. Film does not, and neither does anything rendered through a shutter — and the absence of that is most of what makes a fast move read as computer graphics rather than footage. So a sliding line is blurred **horizontally by the distance it covered in that frame**, and by nothing at all once it has stopped. The number is not a taste call: a box blur one frame wide has a Gaussian equivalent of `width / √12`, and the reference's first frame measures at exactly that. `motionBlur` is the shutter angle as a fraction — `1` is fully open, `0` turns it off. Under a fifth of a pixel the filter is dropped entirely, so a still frame is never paying for a blur it does not need. ## Sizing a card `fontSize` sets a `size = 1` card; `sizes` scales each card from there. They are not all the same for a reason — a one-word payoff wants to be three times the sentence that set it up, and that jump *is* the beat. A card is centred on its **ink**, not on its line boxes: the top of the first line's tallest letter down to the bottom of the last line's lowest one, measured per card from the copy you actually passed. A face's cap height is the wrong number twice over — some faces draw their ascenders taller than their caps, so a line with an ascender in it sits high; and a word with no ascender but a descender, a lowercase wordmark for instance, is not a cap band at all, and centring it as one drops it half a descender low. Both are small, and both are exactly the sort of thing that reads as "not quite right" without ever being nameable. Long copy is caught by `fit`: if a card's widest line would cross that fraction of the frame, the **whole card** comes down a size together, so the block never changes shape because one line happened to be the long one. It is fitted against the widest that card ever gets, not the width it is laid out at — a punch card keeps growing for its whole life, and a slide card swings most of an em either side of centre. A fit that ignores both passes on frame one and runs off the stage later. (The rush is deliberately exempt: leaving the frame is what it is for.) ## Colour A card is a full-bleed page rather than a control sitting on one, so it takes the design system's two page colours — `background` and `foreground` — instead of inventing a pair. `mode="dark"` inverts the whole run; `ground` and `ink` override either end. The accent is the exception. `accentColor` is a bare prop and deliberately *not* `theme.primary`: an accent that is supposed to shout is the design, not a token, and a brand palette swapped in underneath it should not be able to turn the payoff card into the same grey as everything else. `accentBeat` picks which card gets it, counting from 1. ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/punch-lines.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition } from "remotion"; import { PunchLines } from "@/components/imgcli/punch-lines"; const Titles = () => ( ); export const RemotionRoot = () => ( ); ``` `durationInFrames` is simply the sum of `holds` — 48 × 3 here. `punchLinesDuration(script, holds)` is exported so a script you change does not leave a composition length behind. ## Props ", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: '"light" | "dark"', description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Spec Sweep URL: https://imgcli.com/docs/text/spec-sweep A one-line spec slides in word by word while a highlight runs along it and an icon charges up *(interactive preview: https://imgcli.com/docs/text/spec-sweep)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/spec-sweep.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage Spec Sweep paints its own card and runs 75 frames at 30fps. Timing is measured frame by frame off a reference; every word, colour and font is a prop. ```tsx import { SpecSweep } from "@/components/imgcli/spec-sweep"; export const Beat = () => ; ``` --- # Text Build URL: https://imgcli.com/docs/text/text-build Words enter one at a time while the already-placed words physically reflow to stay centered — on the x axis as a growing line or the y axis as a growing stack *(interactive preview: https://imgcli.com/docs/text/text-build)* Unlike a plain text reveal, every word that is already on screen moves to make room: each new word enters (from the right on `axis="x"`, from above on `axis="y"`) and pushes the placed words through a re-centered layout, with a soft blur pulse while they travel. ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/text-build.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition } from "remotion"; import { TextBuild } from "@/components/imgcli/text-build"; const TextBuildScene = () => ( ); export const RemotionRoot = () => ( ); ``` Set `axis="y"` to build a centered stack of rows instead of a single line: ```tsx ``` ## Props ", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: "\"light\" | \"dark\"", description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Text Highlight URL: https://imgcli.com/docs/text/text-highlight A mark wipes a wordmark open, spins into its lockup, then swings wide and comes round past the lens while its ink floods the frame — plus five span-emphasis presets *(interactive preview: https://imgcli.com/docs/text/text-highlight)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/text-highlight.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition } from "remotion"; import { TextHighlight } from "@/components/imgcli/text-highlight"; const TextHighlightScene = () => ( ); export const RemotionRoot = () => ( ); ``` Unlike text-reveal components, the copy is fully visible from frame one — only the emphasis on the highlighted span animates in. ## Presets - `marker` — a translucent accent marker block (16% tint of `accentColor`) sweeps behind the span with a high-damping spring, so the text underneath stays readable. - `color` — the span's text color crossfades from `baseColor` to the accent (or `highlightedTextColor`). - `underline` — an accent underline of `thickness` px draws left-to-right beneath the span. - `strikethrough` — an accent line draws across the span, then it crossfades into `replaceWith` and holds. - `shimmer` — a `shineColor` gradient sweeps across the span via background-clip text. ## How `logo-wipe` works **The wipe is a clip, not a fade.** The mark rides an edge across the word, and everything to the right of that edge exists. So the letters arrive already solid, the way a lid sliding off a thing reveals it — and they appear right-to-left, because the edge is travelling left. The whole reveal is expressed in percentages of the word's own box (`clip-path` for the letters, `left: %` for the mark), so nothing has to be measured and it works at any font size or string length. **The rush is perspective, not easing.** The mark does not grow on a ramp — it *travels toward the eye*, and its apparent size goes as `1 / (1 - travel)`. It creeps for most of the trip and then blows up at the end, which is the whole character of a thing coming at your face and is not something a curve can fake. **The rush is a swing, and the swing is built in world space.** The mark does not come down the lens in a straight line — it leaves the lockup, arcs wide to the left, comes round, and passes you on the right. A semicircle from the screen to your eye. The part that matters is *where the curve is authored*. The lateral path is a world-space crossing with a bow pulled out of it, and the projection multiplies it by the apparent scale. That single multiply is the whole effect: the same world path is a nudge while the mark is far away and a whip across the frame once it is at the lens, because that is what an arc coming at your face does — it opens up as it arrives. Author the offset in screen pixels instead and no curve you pick will fix it; you get a shape sliding around on glass, in front of the scene rather than through it. It turns, and turning is not doubling back. A pendulum at the end of its swing has not changed its mind — it is still going, and the instant its sideways speed passes through nought it is coming at you faster than ever. What you must never do is take the mark off one way and then send something else back the other. **The colour comes out of the mark.** Not in from an edge. That distinction is the whole ending, and it is forced by geometry rather than taste: the frame has to end flat, so the colour has to close over the *left* of the frame while the mark is travelling *right*, and there are exactly two ways to get it there. Something sweeps leftwards across the screen — a wall, a wipe, a roller — which is a second momentum pulling against the first, and it reads as paint being rolled on by an off-stage hand. Or the mark is already lying over that ground when its ink spreads. This is the second job the bow does, and the reason it is not decoration. Because the mark swings *left* first, it is still left of centre at the moment it is big enough to go off — lying right across that half of the frame. So the ink is born on the left and opens out to the **right, following the mark out**. Take the bow away, send the mark straight down the lens, and the ink has to close leftward against the direction of everything else in the shot. Which is what it did, and it looked like paint. The size is the other half of it: at 42× the mark's arms are past all four corners by the time the ink moves, so what closes the frame is the gaps between them filling in. So the last beat is the mark's ink **flooding out of it** — a hard-edged disc of `coverColor`, born inside the mark's own centre knot where it cannot be seen, that then outruns it. Three things about it are load-bearing: - **It is a shape, not an opacity.** Fading a full-frame rectangle up to opaque leaves a half-transparent wash lying over the backdrop for several frames, and there is no reading of that except "the screen faded to blue". - **It is keyed to the mark's size, never to the clock.** It cannot begin until the mark is already frame-sized. On a timer it starts while the mark is small, and you get a blue screen with a little logo on it. - **It lags the mark.** Ink is emitted, not carried: the mark punches ahead and its colour is left in the wake. Pin the disc *to* the mark and it flies out to the side with it, which drags the burst off-centre — and then the furthest corner of the frame is the one behind the mark, so the last thing on screen is a colour edge closing backwards, against the direction the whole shot is travelling. **A logo cannot cover the frame by growing**, and that is a shape fact, not a tuning problem: scaling a mark scales its *holes* by exactly as much. Measured on the bundled clover, filling the frame with solid ink by scaling would take **396×**. The ink is what closes them. **The wordmark does not fade out.** The mark blows up straight through it and the ink takes what is left, so the word is *erased* rather than merely absent — and the frame is never empty, which is what fading it used to cost. Set `coverColor` to `"transparent"` to skip the ink and end on the giant mark instead, with the backdrop still showing through it. ### If your logo is an image, it needs `max-width: none` Tailwind's preflight sets `img { max-width: 100% }`, and it applies to `img` but **not** to `svg`. The mark is placed in an absolutely-positioned box at `left: 100%` (the word's right edge), where the width available to an auto-width box is *(containing block − left) = zero*. Shrink-to-fit then asks the content for its minimum width: an `` says "58" and holds the box open, but an image capped at `max-width: 100%` says "I can be zero" — so the box collapses and the mark renders **zero pixels wide**. A blank frame. The component already opts out (`max-width: none` on the mark, `width: max-content` on its box). It is called out here because it will not reproduce in a Remotion render — a bundle has no preflight — so a component can be verified frame by frame in an mp4 and still be invisible in the app that installs it. ### Your own logo Pass `logoSrc` (a URL — use `staticFile()` for your own `public/`), or `logo` (any node; an SVG is better, because it scales forever). It goes through Remotion's ``, so the frame waits for the image instead of rendering a hole. Give it resolution. The rush blows the mark up to about **42×** — a 300px PNG arrives as mush. And set `coverColor` to your mark's colour if it is not `accentColor`, or the ink will not match the mark it is supposed to be coming out of. `before` and `after` are ignored by this preset: it is a lockup, not a sentence. ## Props so the frame waits for it. Use staticFile() for your own public/", }, { name: "logoScale", type: "number", default: "1.05", description: "logo-wipe only: mark size as a multiple of fontSize", }, { name: "spinTurns", type: "number", default: "1", description: "logo-wipe only: turns the mark spins while it wipes the word open", }, { name: "holdDuration", type: "number", default: "12", description: "logo-wipe only: frames the finished lockup rests before the rush", }, { name: "rushDuration", type: "number", default: "24", description: "logo-wipe only: frames the rush at the camera takes. A perspective rush is back-loaded by nature — too few and the growth, the swing and the ink all land in the last three frames", }, { name: "rushScale", type: "number", default: "42", description: "logo-wipe only: the mark's size at the end of the rush, as a multiple of its resting size. Big enough that its own body overruns the frame, which is what lets the ink close the frame with nothing sweeping across it", }, { name: "rushDrift", type: "number", default: "0.55", description: "logo-wipe only: where the swing ends up, as a fraction of the width. Positive is right — across the frame and out past your shoulder. This is the crossing; swingOut is the arc", }, { name: "swingOut", type: "number", default: "0.25", description: "logo-wipe only: about how far the mark bows out to the left on the way, as a fraction of the width. This is what makes the rush a swing rather than a slide — and it is what leaves the mark lying over the left of the frame when its ink goes off, so the colour opens rightward behind it instead of closing backwards", }, { name: "coverColor", type: "string", default: "accentColor", description: "logo-wipe only: the colour the mark's ink floods out in, and the colour the frame ends on. Set it to your mark's own colour if it is not the accent, or \"transparent\" to end on the giant mark instead", }, { name: "springConfig", type: "{ damping?: number; mass?: number; stiffness?: number }", default: "{ damping: 18 }", description: "Spring for the marker and underline sweeps — high damping, no bounce", }, { name: "thickness", type: "number", default: "round(fontSize * 0.08)", description: "Underline/strikethrough line thickness in pixels", }, { name: "shineColor", type: "string", default: "lighter of theme.card / theme.foreground", description: "Shimmer only: color of the sweeping shine", }, { name: "fontSize", type: "number", default: "56", description: "Font size in pixels", }, { name: "fontWeight", type: "number", default: "600", description: "CSS font-weight", }, { name: "speed", type: "number", default: "1", description: "Playback speed multiplier", }, { name: "className", type: "string", description: "Optional className passed to the sentence span", }, { name: "theme", type: "Partial", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: "\"light\" | \"dark\"", description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Text Reveal URL: https://imgcli.com/docs/text/text-reveal A cinematic zoom-out title reveal — the lead word floats up close to the viewer, falls back to size, then the sentence pushes in word by word and slides into place *(interactive preview: https://imgcli.com/docs/text/text-reveal)* The reveal plays in four beats: 1. The lead word fades in large and centred — right up against the viewer. 2. It drifts a little **closer** still, and hangs there. 3. It **falls back** to its final size, roughly in place. 4. Only once it has landed does the line travel left, while the trailing words **push in** one by one from the right and settle. Beats 3 and 4 begin on the same frame but run on very different clocks: the recede is short, the slide is long and heavily eased in. That gap is what makes the word read as going *back* first and moving left second, rather than doing both at once — and the trailing words stay hidden until `wordDelay` frames into the recede, so none of them appear while the lead word is still travelling. Only `scale`, `translate` and `opacity` animate, so the baseline stays fixed and nothing reflows. ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/text-reveal.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition } from "remotion"; import { TextReveal } from "@/components/imgcli/text-reveal"; const TitleScene = () => ; export const RemotionRoot = () => ( ); ``` Tune the feel with a few props — a bigger `initialScale` for a more dramatic zoom, a longer `assembleDuration` for a slower settle: ```tsx ``` The lead word's centre is measured once on mount so it stays perfectly centred while large and lands exactly in the line — the type, spacing and baseline never shift. `recedeDuration` and `assembleDuration` are the two knobs that shape the reveal. Keep the assemble comfortably longer than the recede (the default 30 vs 14 is a good ratio); shrink the gap and the line starts sliding left while the lead word is still falling back, which reads as one muddled move instead of two. ## Props ", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: "\"light\" | \"dark\"", description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Text Rewrite URL: https://imgcli.com/docs/text/text-rewrite A line writes itself, is selected, then has its opening swept away by a clip that cuts letters in half — and a new ending written on while it slides to its new centre *(interactive preview: https://imgcli.com/docs/text/text-rewrite)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/text-rewrite.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition } from "remotion"; import { TextRewrite } from "@/components/imgcli/text-rewrite"; const Claim = () => ( ); export const RemotionRoot = () => ( ); ``` The whole gesture is three props. `keep` counts words off the **end** — everything before them is what gets swept away: ```tsx ``` ## The erase is a clip, not a deletion This is the measurement everything else follows from. As the opening of the line is taken away, **letters are cut in half by a vertical edge** — "Or" becomes ")r" becomes "r" — and the words that survive never move a pixel. A word-by-word delete would have re-laid the line out on every step, and across 183 frames the recording plainly does not. So there is no deletion here at all. There is a window with `overflow: hidden` whose left edge sweeps right over text that is standing still. ## The rewrite is that same edge, run the other way The window's **right** edge then sweeps right, revealing the new ending a fraction of a letter at a time, while the line slides to where its new content belongs. Those are two independent measurements — the clip edge, and the line's centre — and they land on one shared progress to within 0.001 of the travel. Fitted separately: | | fitted | rms | | --- | --- | --- | | erase (the clip edge, 29 frames) | `cubic-bezier(0.419, −0.050, 0.184, 1.044)` | `0.0086` | | rewrite (the line's centre, 31 frames) | `cubic-bezier(0.427, 0.021, 0.193, 0.999)` | `0.0059` | Two different signals, two different halves of the gesture, the same curve. It is `cubic-bezier(0.42, 0, 0.19, 1)`, and it is an **ease-in-out** — which is the whole character of the move. The edge creeps for four frames, crosses the sentence in twelve, and settles for twelve more. An ease-out would have read as a deletion. This reads as something being read, and then taken away. Every curve here was fitted on the recording's **timestamps**, not its frame numbers. It is a 60fps capture with eight dropped frames, so a frame index is not a clock — fitting on frame index makes the erase 0.467s when it is really 0.517s, and everything downstream lands early. ## The mark rides the edge The trailing mark sits `0.4em` clear of the last **visible** letter, not of the end of the line — through the typing, through the erase, and through the rewrite, where the last visible letter is often only half a letter. Measured, it is a constant 54px from the window's right edge on every frame of the gesture. That is what keeps the whole thing reading as one object rather than as a line with a decoration next to it. The default is the snapcn mark; `markPath` takes any path drawn in a `0 0 100 100` box, and `markSize={0}` leaves the line bare. ## The line opens, it does not reflow When a word lands, both ends of the line move outward by the same amount over the same five frames — **+18, +8, +4, +2, +1 px** on the right and the mirror of that on the left — while the centre never leaves 404 of 810. The word itself is drawn at full width from its first frame and *fades*: on the reference it lands whole and pale, overlapping the mark, and the mark slides clear over the next four frames. So the line's **width** is tweened around a fixed centre. Nothing is clipped while the line is being written, and nothing jumps. ## The size change is a cut 188px of ink on one frame and 453px on the next — **2.41×, with no intermediate frame anywhere in the recording.** `zoomDuration={0}` is that, and it is the default here because it is what the reference does. Give it a duration and it becomes a smooth push, and the pivot moves with it: a cut has no in-between frame to judder on, so it pivots on the line's optical centre (which is what the reference does — its small line and its full-size line share a centre at y 224 of 458, not a baseline). A ramp *does* have in-between frames, and there the pivot has to be the measured baseline, because the rasteriser gives glyph origins no vertical sub-pixel precision and any pivot that moves the baseline makes the type climb the grid in whole-pixel jumps. ```tsx // a push instead of a cut, pivot follows ``` ## The selection, and its sheen The drag is **28% across by 17ms, 54% by 33ms, 71% by 50ms** — an `easeOut` of degree 3.5 over 200ms, and it starts on the same frame the line comes forward. That was measured without touching the gradient at all: take nineteen fixed columns across the line and find the frame the warmth arrives in each. A quint, the obvious guess, is far too fast off the mark and misses the first two points by twenty points of travel. The fill is a **gradient**, and it is measurably the same one on every frame it appears: read column by column across eight frames spanning three box widths and two phases — 402px selected, 272px after the rewrite, 193px mid-erase — and it lands in the same place every time. **Deepest at 0.36, palest at 0.575, a 3.2× swing.** It is painted on the box rather than on the page, which is why it appears to travel as the box is dragged and then eaten. The deselect is nine frames and **linear to within 6% of the travel**. Nothing about it eases; the selection is simply dropped. | | measured | | --- | --- | | mark appears | `0.117s` | | word stagger | `189ms` | | line opens in | `85ms` | | forward move | `1.033s`, `2.41×`, one frame | | drag | `1.033s`, `200ms`, easeOut³·⁵ | | erase | `1.45s`, `517ms`, ease-in-out | | rewrite | `2.0s`, `533ms`, the same ease | | deselect | `2.85s`, `150ms`, linear | | selection box | `0.954em` tall, `0.083em` below the baseline | | sheen | deepest `0.36`, palest `0.575`, `3.2×` | Every one of those is a prop. ## Colour and wording The proportions and the timing are measured. The **colours and the wording are not** — the reference is a specific product with a specific terracotta and a specific claim, and neither belongs in a component that ships to strangers. The paint is the shadcn token set, the accent and the sheen both come from `theme.primary` unless you say otherwise, the mark is ours, and the line is a prop. ## Props ", description: "Token overrides — the page colour and the line's settled ink come from here", }, { name: "mode", type: '"light" | "dark"', default: "\"light\"", description: "Which token set to resolve against", }, { name: "speed", type: "number", default: "1", description: "Multiplies the frame clock. Every beat above scales with it", }, ]} /> The recording is 3.2s and holds the rewritten line for its last fifth of a second. 102 frames is that plus a beat, so the deselect lands and the new claim sits long enough to read before the composition ends. --- # Text Select URL: https://imgcli.com/docs/text/text-select A line that writes itself a word at a time, pushes forward to full size, and gets selected under a gradient with a glint travelling across it — the beat before somebody copies it *(interactive preview: https://imgcli.com/docs/text/text-select)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/text-select.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition } from "remotion"; import { TextSelect } from "@/components/imgcli/text-select"; const Claim = () => ; export const RemotionRoot = () => ( ); ``` Any line, any length — the stagger, the push and the drag all follow from it: ```tsx ``` ## The newest word is the accent Every word arrives in the accent colour, holds it for **exactly one beat — 208ms, the same as the stagger** — and cools to the foreground over the next. So there is always exactly one coloured word on the line, and it walks left to right ahead of the reader. Measured on the reference: the first word is still fully blue when the second lands, and fully dark by the time the third does. The last word has no next word to hand off to. On the reference it cools **as the selection is dragged over it**, in half the time the drag takes, so by the time the line is selected it is all one colour. That is the rule here too. ## The line comes forward, smoothly The reference gets to 2.39× between two adjacent frames — the ink is 215px wide on one and 516px on the next, with the single dropped frame between them the only place an in-between could have hidden. Set `zoomDuration={0}` and you get exactly that. It is **not** the default, because at 30fps a step does not read as *coming forward*; it reads as an edit. The default is a 420ms ramp on `cubic-bezier(0.2, 0.6, 0.35, 1)`, and three things make it a move rather than a shimmer: - **The scale is pivoted on the baseline**, and the baseline is *measured* — a zero-sized inline-block sitting on it, read back through `delayRender`. The rasteriser gives glyph origins no vertical sub-pixel precision at all, so any pivot that moves the baseline makes the type climb the pixel grid in whole-pixel jumps: sit still, jump, sit still. - **`text-rendering: geometricPrecision`**, because hinting bends each glyph so its stems land on whole pixels — and as the size slides, every stem re-snaps and the letterforms boil. - **`will-change` only while the push is running, and only in the Player.** In a render it hands the scale to the compositor, which resamples a bitmap instead of re-rasterising real type. Measured on the rendered frames: the ink width moves 91, 76, 61, 45, 34, 24, 19, 14, 11, 9, 5, 8 px per frame and then stops. One frame under a pixel, which is a settle. No direction reversals. ## The selection is a drag, and it shines It is **31% of the way across on its first frame** and spends the back half of its time covering the last 5% — `easeOutQuint` over 260ms. The right edge is a hard vertical line with no rounding, because that is what a caret dragging through text leaves behind. The fill is a **gradient**, not a flat wash, and that is measurable: read the reference's selection column by column, taking a value per column that no glyph can reach, and it runs 211 → **178 at 0.33** → **222 at 0.59** → 190. A **2.8× swing in strength** between its deepest point and its bright band. `selectionSheen` is that curve, twelve stops of `[position, multiple of selectionOpacity]`. The band is anchored to the **box**, not to the page: across the drag it sits at 0.58 of whatever width the box has reached that frame (0.584, 0.571, 0.580, 0.603, 0.585, 0.576, 0.589 …). A CSS gradient painted on a growing element does that for free — and it is why the shine appears to travel while the selection is being pulled across. ## …and then a glint crosses it The sheen above stops the instant the drag lands, and a highlight that stops moving stops shining. So a second, narrow band is swept across the finished box on its own clock — linearly, because a specular is a light source passing a surface, not something that settles. At the defaults it covers **78px a frame** from its first frame to its last, so there is no frame of it you cannot see. It is **not** just a light band, and this is the part worth knowing before you build one yourself. White over a light page has almost no room to work in: measured on the render, white at 0.34 over a fill sitting at R 230 lifts it *eight levels* — quieter than the static gradient it was supposed to be shining on. Going brighter does not fix it; there is only 25 levels of headroom above the fill in the first place. So the glint is shaped like a real specular on a coloured gel — a bright core with **deepened shoulders** either side, painted in the selection's own colour, where the light compresses the film. There is plenty of room downward. Measured on the rendered frames, isolated by differencing against a frame with the band off the edge: | | measured | | --- | --- | | travel | `78px` a frame, constant across the pass | | core | `+13` to `+26` levels | | shoulders | `−23` to `−41` levels | | against | the static sheen's `±15` | A 40-to-60 level band, moving. `shineOpacity={0}` turns it off and leaves you the reference's plain sheen. | | measured | | --- | --- | | word stagger | `208ms` | | word lands in | `130ms`, easeOutQuart — a fade, not a scale | | line arrives from | `0.475em` right, on **every** word | | accent holds / cools | `208ms` / `208ms` | | push forward | from `1.13s`, to `2.39×`, over `420ms` | | drag | from `1.4s`, `260ms`, easeOutQuint | | sheen | trough at `0.33`, band at `0.59`, `2.8×` swing | | glint | one pass from the drag landing, `620ms`, `78px` a frame | | selection box | `0.941em` tall, `0.12em` below the baseline | Every one of those is a prop. ## The line arrives from the right — all of it This is the measurement that took longest to see. When the second word lands, the first one moves — but so does the second, **by the same ten pixels**. A line that had merely grown a word would not do that: the old word would shift left by half the new word's width and the new word would not shift at all. So it is not the layout re-centring and it is not the new word widening into place. The entire line arrives half an em to the right on every word and settles back, which reads as the line being nudged along rather than stretched. `lineShift` is that, and it re-triggers on each word rather than only the first. ## It fits any line The selection is sized off a hidden copy of the line's `offsetWidth`, measured once behind `delayRender()`. Not `measureText` — a canvas ruler knows nothing about CSS tracking or word-spacing, and a box drawn to a width the line does not actually have is a visible five percent out. The words are laid out in normal flow rather than flex for a related reason: **a flex container drops whitespace-only text nodes**, so the spaces between words vanish and the line renders narrower than it measures. ## Colour and wording The proportions and the timing are measured. The **colours and the wording are not** — the reference is a specific product with a specific blue, and neither belongs in a component that ships to strangers. The paint is the shadcn token set, the accent and the selection both come from `theme.primary` unless you say otherwise, and the line is a prop. ## Props ", description: "Token overrides — the page colour and the line's settled ink come from here", }, { name: "mode", type: '"light" | "dark"', default: "\"light\"", description: "Which token set to resolve against", }, { name: "speed", type: "number", default: "1", description: "Multiplies the frame clock. Every beat above scales with it", }, ]} /> The recording is 1.88s. The push is a ramp here rather than a cut, which moves the drag later — 72 frames is that clip plus a beat, so the selection lands and sits long enough to read before the composition ends. --- # Text Swap URL: https://imgcli.com/docs/text/text-swap Replace one line of text with another — by default the old line rushes the camera and flies past you, and the new one is revealed behind it *(interactive preview: https://imgcli.com/docs/text/text-swap)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/text-swap.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition } from "remotion"; import { TextSwap } from "@/components/imgcli/text-swap"; const TextSwapScene = () => ( ); export const RemotionRoot = () => ( ); ``` Two stacked layers hold the outgoing and incoming lines. The incoming line is scheduled with `enterStart = max(0, exitTotal - overlap + microDelay)`, so the old text is (almost) gone before the new one lands — with a small beat between the two moments. ## Presets The `transition` prop selects one of six looks. Each preset carries its own default `unit`, `exitDuration`, and `enterDuration`; setting those props explicitly overrides the preset. | Preset | Description | | --- | --- | | `fly-through` | **Default.** The old line rushes the camera and passes your face; the new one is revealed behind it, arriving out of depth. | | `fade-through` | Material-style block fade: old line fades out with a small upward drift, new line fades in with a soft blur resolve. | | `crossfade` | Words gently crossfade one after another with a short vertical drift — a calm keynote rhythm. | | `shared-axis-y` | The whole line travels on the vertical axis: exits 24px up, enters from 24px below. | | `shared-axis-z` | Depth swap: the old line scales up and blurs away, the new one settles down from a larger scale. | | `cut` | Per-word hard cuts with stepped staircase timing for sharp editorial swaps. | ## How `fly-through` works, and why it is not just a big scale Three things, and each of them is load-bearing. **Perspective, not easing.** The line does not grow on a ramp — it *travels toward the eye*, and its apparent size goes as `1 / (1 - travel)`. So it creeps for most of the trip and then blows up right at the end, which is the whole character of something rushing past you. This cannot be faked with an easing curve. Fitted against the reference recording, a plain `interpolate` to a big scale misses by rmse 0.31 and can never reach the blowup at all; the perspective model lands at 10.09x where the reference measured 10.28x. The easing describes the *travel*, which is nearly linear — the drama is geometry, not curve-shaping. **A shutter, not a blur filter.** At the end of the rush the line more than doubles between one frame and the next. Drawn sharp, that does not read as speed — it reads as strobing. So the exit is sampled several times across the frame and the samples are averaged, which is exactly what a camera shutter does. Because the motion is a scale, the samples fan out radially: sharp at the centre of the rush, streaked at the edges. That radial smear is the thing that makes it feel fast, and you cannot get it from `filter: blur()`, which softens everything equally. **It stays solid until it is gone.** A line flying at your face does not dim on the way in. `exitFadeStart` holds the fade back until 72% of the travel has passed, and then it goes. The cost is real: the shutter draws the outgoing line 18 times per frame. That is free in a render and expensive in a live browser preview, which is why this component ships a **pre-rendered demo** on the docs site. ## Props ", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: "\"light\" | \"dark\"", description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Text Swell URL: https://imgcli.com/docs/text/text-swell The lead word rises into the middle and floats toward you — then the rest of the sentence pushes in from the right, bouncing its letters, and shoves the lead word aside before the whole line falls back *(interactive preview: https://imgcli.com/docs/text/text-swell)* A title reveal built around one idea: **the lead word is pushed aside by the words that follow it.** 1. The lead word **rises from below** and settles, centred, at its natural size. 2. The line **floats forward** — toward the viewer. The lead word is still alone, so it stays dead centre as it grows. 3. The second word cuts in from the right, its **letters bouncing** up off the baseline one after another, and **shoves the lead word left** to make room. Later words follow, pushing in **without a bounce**, each shoving the lead word further left. 4. Once everything has landed, the whole line **falls back** to its final size and settles into the sentence. ## The shove The shove is the whole point, so it is not on a clock of its own — that would just be a word arriving and a line sliding at the same time, and it would read as two things happening rather than one thing causing another. Instead, each trailing word **owns a share of the leftward travel** — its share of the width it adds to the line — and spends that share over exactly the frames it spends pushing into its slot. The lead word therefore cannot move until a word arrives to move it, and once every word has landed it sits exactly where the finished sentence needs it. Nothing is scheduled twice, so nothing can drift out of sync. ## The bounce `bounceWords` controls how many trailing words bounce their letters in. The default is `1`: only the second word bounces, and every word after it simply pushes in. The lead word never bounces. Each letter swells up **off its baseline** — not its centre — so the word grows upward and stays planted on the line. Both halves of the pulse use a curve that starts and ends at a standstill, so a letter never jerks off the baseline and the top of the bounce rounds over instead of cornering. `letterStagger` is the wave's speed; `letterHold` adds a flat top (leave it at `0` for a clean quick pop). ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/text-swell.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition } from "remotion"; import { TextSwell } from "@/components/imgcli/text-swell"; const TitleScene = () => ; export const RemotionRoot = () => ( ); ``` ## Why scaled text looks "stuck", and what actually fixes it If you have ever animated `scale` on a heading and watched the letters judder and stick instead of gliding, this is why — and it is worth knowing, because the obvious fixes are mostly wrong. **The cause is the type, not the animation.** Browsers do not scale text the way they scale an image. They re-shape and re-rasterise the glyphs at every new size, and the rasteriser snaps each glyph's origin to the pixel grid — in Skia, horizontal positions quantise to ¼px and **vertical positions have no sub-pixel precision at all**, they round to a whole pixel. So under a perfectly smooth scale ramp the letters sit on one pixel row for a few frames, snap to the next, and sit again. Measured on a linear 1.6× → 1× ramp, the line's vertical centroid reversed direction 29 times in 40 frames while its true motion was a straight line. **The fix is to lay the line out at the biggest size it will ever be shown at, and only ever scale it _down_** (`fontSize: fontSize * raster`, `scale: scale / raster`), with `will-change: transform` so the line is composited. Compositing hands the scale to the GPU, which resamples a texture instead of re-shaping type — the snapping cannot happen. Laying out big is what makes that safe: the transform never asks for more detail than the texture already holds, so the browser rasterises once and never revisits it. Same measurement after the change: **0.03px of quantisation instead of 0.30px**, and no loss of sharpness, because the texture was rasterised at the largest size in the first place. `will-change: transform` **on its own is not the fix** — scaling up past the texture's resolution forces the browser to re-rasterise mid-animation, which trades the jitter for blur and pops. The two halves only work together. Not fixes, despite being widely repeated: `translateZ(0)`, `backface-visibility: hidden`, `perspective(1px)`. They are all layer-promotion hacks with the same blur side effect. And `-webkit-font-smoothing: antialiased` does not affect glyph positioning at all. **One more, unrelated to rasterisation: no asymptotic easing on a frame clock.** Aggressive decelerating curves — quint-out, expo-out — cover 99% of their travel in the first third and then crawl. Over a 50px rise at 30fps that leaves five frames moving less than half a pixel each, which rasterise to *identical frames*: the word visibly stops dead partway and waits. Every curve here still arrives at a standstill, but none of them spend more than a frame or so on travel you cannot see. A settle worth one frame is a settle; a settle worth five frames is a freeze. ## frontScale is a ceiling, not a promise Floating forward blows the line up about its left edge, and until the last word has finished shoving, that edge still sits right of where it will rest. A word arriving into that gap is the widest the line ever gets — so `frontScale` is capped at whatever keeps even that moment inside the frame. That means a long sentence floats less far forward than you asked for. It is not a bug you can tune around; it is the only way to have every word on screen at once while the line is forward. If you want the full, dramatic push, **use fewer or shorter words** — the cap lifts as the line gets narrower. ## Props ", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: "\"light\" | \"dark\"", description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Title Sweep URL: https://imgcli.com/docs/text/title-sweep An end title lit by a gradient that travels through the letters, cuts in the dark, and a second title takes over *(interactive preview: https://imgcli.com/docs/text/title-sweep)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/title-sweep.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage Title Sweep paints its own card and runs 76 frames at 30fps. Timing is measured frame by frame off a reference; every word, colour and font is a prop. ```tsx import { TitleSweep } from "@/components/imgcli/title-sweep"; export const Beat = () => ; ``` --- # Type Morph URL: https://imgcli.com/docs/text/type-morph A headline that types itself under a glowing caret, sheds its lead, morphs letter by letter, and ends under a colour flood *(interactive preview: https://imgcli.com/docs/text/type-morph)* ## Overview Four mechanics on one timeline. **Typing.** The line is centre-anchored, so it grows in both directions rather than running to the right. A glyph lands in the accent colour and cools to ink over the next two glyphs — that moving warm edge is what reads as typing, more than the caret does. **The cut.** The lead is removed in a single frame. It is not animated out; what *is* animated is the slide back to centre that follows it. **The morph.** Glyphs shared between the two phrases travel from where they were to where they belong, and the rest cross-fade in place. Every position is measured in the real face at the real size behind a hidden probe, because a morph built on estimated advance widths lands each letter a pixel or two off and reads as a wobble. **The exit and the flood.** The line does not hold still and then cut — it slides left for six frames first, accelerating, and the cut lands on a line that was already leaving. Then two flat colours, four frames each, not a ramp: fading a full-frame rectangle up leaves a half-transparent wash over the type for several frames, and there is no reading of that except the screen dimming. `wordOnFlood` is off by default, so the flood ends clean. ## Notes `accent` defaults to the theme's accent, and `flood` to a violet pair. Both take your own colours; `flood={false}` ends the beat on paper instead. `background` defaults to the theme's page. The reference's is a plain near-white rather than the warm off-white the token carries, so the preview passes its own. There is no glow behind the caret — just the bar. An earlier version had one, and it is worth recording why it was hard to get right: it was built with `mixOklch` toward the page, and `mixOklch` walks the *hue* path. From a violet accent to a warm off-white that path goes through pink, so the halo rendered salmon. If you add a glow back, composite it with `withAlpha` over the page — alpha has no hue path. --- # Word Flip URL: https://imgcli.com/docs/text/word-flip A headline types itself out, then one word cycles on a 3D flip — sinking into an anticipation dip, throwing up and away about its baseline under motion blur, while the next word unfurls from below. *(interactive preview: https://imgcli.com/docs/text/word-flip)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/word-flip.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx import { WordFlip } from "@/components/imgcli/word-flip"; export function Hero() { return ( ); } ``` ## How it moves The sentence types itself a character at a time, holds for a beat, and then the word in the middle starts cycling. Four things happen in the nine frames of a flip, and they are all one gesture. ### The slot is reserved before the first word exists The whole point of this animation is that **nothing except the word ever moves** — no horizontal shift, no baseline shift, no reflow. That cannot be achieved by swapping text in and out of a line, because the words have different widths and the line would breathe every time one arrived. So the slot is an `inline-block` whose width is the widest word's, fixed from the first frame. While the sentence is still typing, the slot is simply *empty* — the gap you see between "A" and "Portfolio" is the word's seat, waiting for it. Every word is then uniformly scaled to fill that seat, which is why the widest word sits at scale 1 and the rest are scaled up to meet it, and why both edges of the slot are pinned no matter which word is showing. Measured across the whole clip: the left edge of "Portfolio" moves **1px**, and that 1px is the antialiasing on its own fade-in. ### The word sinks before it flies The flip does not start by lifting the word. It starts by dropping it about a tenth of an em — a backswing, the same thing a person does with their arm before they throw. It is what makes the flip read as weight instead of as a cut. That is not a separate keyframe. One `easeInBack` progress drives the translate *and* the rotation, and the curve's own overshoot is the dip: its minimum is `p = -0.100` at `t = 0.42`, so the dip is worth exactly a tenth of the exit travel and lands a little under halfway through it. ### The blur is a shutter, not a ramp Blur is proportional to **speed** — `easeInBackSpeed` is the analytic derivative of the curve above. It matters because the derivative is *zero at the turnaround*: at the bottom of the dip the word has stopped to change direction, and it is sharp there. Three frames later it is travelling as fast as it ever will, and it is smeared. A blur keyed to progress instead of speed would blur the word while it is standing still, which reads as a focus pull rather than motion. ### The two words overlap The incoming word starts before the outgoing one is gone (`overlap`, in frames), so the swap is continuous rather than sequential. It arrives rotated `-90°` about its baseline, a touch below where it belongs and a touch small, and unfurls into place on a moderate decelerate — never an expo-out, which on a frame clock spends most of its frames moving less than a pixel and reads as the word stopping dead partway up and waiting. Everything pivots on the **baseline**, and the baseline is measured, never derived from a line-height ratio. Pivot anywhere else and the glyph origins climb the pixel grid in whole-pixel jumps as the scale moves them — the word sits still, jumps, sits still. ## Props | Prop | Type | Default | Description | | --- | --- | --- | --- | | `prefix` | `string` | `"Looking For A"` | Text before the flipping word. | | `words` | `string[]` | `["Modern", "Stunning", "Minimal"]` | The words that cycle through the slot. | | `suffix` | `string` | `"Portfolio"` | Text after the flipping word. | | `gradient` | `string[]` | `["#2A8BF5", "#D382B0"]` | Gradient painted on the flipping word, left to right. | | `cps` | `number` | `9` | Typing speed, characters per second. | | `typeStart` | `number` | `4` | Frames before the first keystroke. | | `charFade` | `number` | `6` | Frames each character takes to fade in. | | `jitter` | `number` | `0.18` | Keystroke unevenness, as a fraction of one keystroke. `0` is a metronome. | | `pause` | `number` | `6` | Frames held after the sentence completes, before the first flip. 6 frames is the 200ms beat. | | `cycle` | `number` | `35` | Frames from one flip to the next. | | `exitDuration` | `number` | `9` | Frames the outgoing word takes to leave. | | `enterDuration` | `number` | `9` | Frames the incoming word takes to settle. | | `overlap` | `number` | `3` | Frames the incoming word starts before the outgoing one is gone. | | `caret` | `boolean` | `true` | Blinking caret while typing. | | `loop` | `boolean` | `true` | Keep cycling the words forever. | | `motion` | `Partial` | see below | Distances, in em. | | `perspective` | `number` | `6.5` | 3D depth, in em. Smaller is a stronger perspective. | | `fontFamily` | `string` | app sans stack | The typeface. | | `fontSize` | `number` | `72` | | | `color` | `string` | `theme.foreground` | The static part of the sentence. | | `theme` | `Partial` | — | Design-system token overrides. | | `mode` | `"light" \| "dark"` | `"light"` | Which end of the system to resolve against. | | `fontWeight` | `number` | `600` | | | `speed` | `number` | `1` | | ### `motion` Every distance is in **em**, so the gesture scales with the type instead of being tied to one font size. | Key | Default | Description | | --- | --- | --- | | `exitY` | `-1.29` | How far the outgoing word is thrown. Negative is up. Its first tenth, inverted, *is* the dip. | | `enterY` | `0.135` | How far below rest the incoming word starts. | | `rotate` | `90` | Degrees about the baseline. | | `scale` | `0.98` | What the outgoing word shrinks to, and the incoming grows from. | | `blur` | `0.085` | Peak blur, reached at peak speed. | ## Notes - The sentence is one line and never wraps. `fontSize` is the knob: at the default 72 the reference sentence fills about 83% of a 1280-wide composition. - `caret` has no effect on layout — it is a zero-width box with an absolutely positioned bar inside, so the sentence's geometry is identical with it and without it. - The words are measured once, behind `delayRender()`, so frame 0 is never captured against the wrong slot width. --- # Word Gather URL: https://imgcli.com/docs/text/word-gather A sentence that arrives one word at a time, out of order, and gathers — the first word holds the frame alone, then the rest drop in around it in the accent colour and slide the last of the way home, turning to ink as they settle *(interactive preview: https://imgcli.com/docs/text/word-gather)* The first word holds the middle of the frame on its own. Then the rest of the sentence drops in around it — not in reading order, and not where it belongs — each word landing somewhere near its slot in the accent colour and sliding the last of the way home, turning to ink as it settles. The line makes room for them as they come, and when the last one is down the whole block eases back to its real size. ## How it moves **Two layouts, and one word that moves between them.** The scene is only ever in one of two arrangements: the lead word alone, centred on the card, or the whole sentence wrapped and centred. Every other word appears in the second one and never sees the first. **The lead word runs on two clocks.** It drops onto its line in seven frames and slides left across the line over twenty-one — because one of those is a line changing and the other is a line reflowing, and they are not the same beat. **The layout is arithmetic, not the browser's.** Word advances come from `measureText`; the wrap, the centring and every slot are computed here. A word has to be transformed from somewhere else to its slot, and to know where its slot *is* you have to have laid the line out yourself. It also means the line break cannot move on you frame to frame — the layout is solved once, at scale 1, and the block's own scale is a transform over the top of it. ## Notes `accent` is the colour a word wears while it is still travelling; leave it blank and the theme's accent is used. It is a travelling state, not a highlight — every word ends in ink. `maxWidth` is where the line breaks, in card pixels. Because the layout is solved once rather than reflowed, changing it moves the break cleanly instead of making words jump between lines mid-animation. ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/word-gather.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition } from "remotion"; import { WordGather } from "@/components/imgcli/word-gather"; const Line = () => ; export const RemotionRoot = () => ( ); ``` ## Props ", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: '"light" | "dark"', description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Word Wheel URL: https://imgcli.com/docs/text/word-wheel A column of words that spins like a reel and stops on one of them — the lead-in holds the left of the line while the answer rolls into the slot beside it, decelerating over a second and three quarters *(interactive preview: https://imgcli.com/docs/text/word-wheel)* A sentence with a hole in it, and a reel that fills the hole. `snapcn for` holds the left of the line; the word that finishes it lives in a slot to its right, one row of a taller column whose other words are visible above and below, smaller and grey, fading out toward the top and bottom of the frame. The reel rolls, decelerates, and comes to rest with one word at full size and in ink. Left at the list's own length it turns exactly once — the scene shows its answer, spins it away, and brings it back. ## How it moves **The deceleration is a measurement, not a curve.** It ships as a fifty-one point table taken off a recording, because nothing standard fits: the closest cubic-bezier is five pixels out at its worst and the closest spring 4.3px RMS. A fifth of a row is a visibly different frame, so the measurement ships instead. **The selected word lags the slot.** A row takes the slot when it is nearest to it, but it does not arrive at full size on the frame it crosses — it grows into it over about seventy milliseconds. That lag is why the fast part of the roll never looks like a row of identical pops: each word is still growing when the next one takes over. **The scale pivots on the baseline.** Every row's baseline sits on a 34px grid, the oversized one included, which is the only pivot a browser will not round: it quantises a glyph's origin to a whole device pixel vertically, so a scale that moves the baseline makes the word climb the grid in jumps. ## Notes The reel is left-aligned twice over. The selected word steps left out of the column to finish the sentence and steps back when it loses the slot — that is measured off the reference and it is what makes the line read as a line rather than as a list with one row shouting. The gap between the lead-in and the word is a *gap*, not a position. Pin the word to an x and a shorter lead-in leaves a hole while a longer one runs the two together. ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/word-wheel.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition } from "remotion"; import { WordWheel } from "@/components/imgcli/word-wheel"; const Hook = () => ( ); export const RemotionRoot = () => ( ); ``` ## Props ", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: '"light" | "dark"', description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Captions URL: https://imgcli.com/docs/captions Burned-in, transcript-timed subtitles with word-pop and karaoke styles and safe-area presets for 16:9, 1:1 and 9:16. Use captions when someone is speaking and the words need to be on screen — talking-head clips, voiceover demos, anything watched muted on a feed. These components take a timed transcript and render frame-accurate, burned-in subtitles inside per-aspect safe areas. For decorative headlines and titles, use [Text & Titles](/docs/text) instead. --- # Karaoke Captions URL: https://imgcli.com/docs/captions/karaoke-captions Full-line captions with a progressive fill sweep across the spoken words and accent-colored keyword emphasis *(interactive preview: https://imgcli.com/docs/captions/karaoke-captions)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/karaoke-captions.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage The whole line stays on screen while a color fill sweeps left-to-right across the words to indicate speech progress — muted `#667085` ramps to ink `#101828` in the light theme, and a translucent-white ramp resolves to `#FAFAFA` in the dark theme. Emphasized words fill in the accent color and scale up subtly. Everything is driven by the current frame, so scrubbing and rendering are fully deterministic. ```tsx // src/Root.tsx import { Composition } from "remotion"; import { KaraokeCaptions } from "@/components/imgcli/karaoke-captions"; const CaptionScene = () => ( ); export const RemotionRoot = () => ( ); ``` Without `lines`, the component builds a single demo line from `text` and `emphasize`. For word-accurate karaoke timing (e.g. from a transcription API), pass `wordTimings` — the absolute frame each word starts filling; a word's fill ends when the next word begins, and the last word runs to `endFrame`: ```tsx ``` The component renders with a transparent canvas (plus the optional pill surface), so place it over any footage or backdrop. Unlike word-by-word caption styles, the full line is always visible — the fill is what indicates progress. ### Safe-area presets The `aspect` prop positions the caption block clear of platform UI: - `landscape` — 16:9 videos; sits 8% from the bottom with 10% side margins. - `portrait` — 9:16 (TikTok / Reels / Shorts); raised to 18% from the bottom to clear platform chrome, 7% side margins. - `square` — 1:1 feeds; 10% from the bottom, 8% side margins. ## Using a real transcript Nobody has frame numbers. Your transcript is in **milliseconds** — the shape Whisper, CapCut and Remotion's own `@remotion/captions` all speak: ```tsx ``` or paste an `.srt` straight in with `srt={...}`. Word-level output is collapsed into LINES (consecutive words become one line, a pause starts a new one) — which is exactly how a caption tool builds a line. Milliseconds are converted against `useVideoConfig().fps`, so one transcript is correct at any frame rate. ## What actually makes a caption look premium Three things, and the first one is most of it. ### 1. An OUTSIDE outline A caption has to stay legible over footage it has never seen — a face, a sky, a white desk. A heavy black outline is the only thing that does that, and it has to sit *outside* the letterform. `-webkit-text-stroke` **centres** the stroke on the glyph outline, so half of it eats inwards and the letters go thin and mushy. Measured on Montserrat 900 at 160px with a 14px stroke: | | white stem | | --- | --- | | no stroke | 38px | | `-webkit-text-stroke` alone | **22px** — the stroke eats 42% of the letterform | | `+ paint-order: stroke fill` | **38px** — identical to no stroke | ```tsx WebkitTextStrokeWidth: `${stroke}px`, WebkitTextStrokeColor: "#000", paintOrder: "stroke fill", // draw the stroke, THEN the fill over it ``` That one line is most of the difference between a caption and a cheap caption. ### 2. Weight and size Montserrat 800–900, at **11–13% of the frame's short side**. Not Inter at 700, and not 2.8% of the height — that is a subtitle. Load the face through `@remotion/google-fonts` so the Player, the mp4 and your own project all get the same one. ### 3. The spoken word lands A spring with overshoot, not an ease. And that scale is a scale **on text**, which is where captions usually fall apart: a browser gives glyph origins no vertical sub-pixel precision, so a scale that moves the baseline makes the word climb the pixel grid in whole-pixel jumps. The pop pivots on the **measured baseline** — verified on rendered frames, the baseline holds to **0px** while the word scales 1.16×, so it grows upward off its baseline exactly like real caption type. ## Props ", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "accentColor", type: "string", default: "theme.primary", description: "Fill color of emphasized words", }, { name: "fontSize", type: "number", default: "48", description: "Font size in pixels", }, { name: "fontWeight", type: "number", default: "600", description: "CSS font-weight", }, { name: "aspect", type: "\"landscape\" | \"portrait\" | \"square\"", default: "\"landscape\"", description: "Safe-area preset positioning the caption block", }, { name: "pill", type: "boolean", default: "true", description: "Rounded surface behind the line with a hairline border", }, { name: "emphasisScale", type: "number", default: "1.04", description: "Scale emphasized words grow to as their fill completes (ease-out, no bounce)", }, { name: "baseColor", type: "string", description: "Unfilled (not-yet-spoken) word color override", }, { name: "fillColor", type: "string", description: "Filled (spoken) word color override", }, { name: "speed", type: "number", default: "1", description: "Global playback multiplier", }, { name: "className", type: "string", description: "Optional className passed to the outer wrapper", }, ]} /> --- # Word Captions URL: https://imgcli.com/docs/captions/word-captions TikTok-style word-by-word burned-in captions from a timed transcript *(interactive preview: https://imgcli.com/docs/captions/word-captions)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/word-captions.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage Feed it a timed transcript — one `{ text, startFrame, endFrame? }` per word, e.g. mapped from Whisper word timestamps — and it burns captions into the frame: the current beat sits in a rounded backdrop pill inside the safe area of your aspect, and each word pops the moment it is spoken. The component has a transparent background, so overlay it on your screen recording or any scene. ```tsx // src/Root.tsx import { AbsoluteFill, Composition, OffthreadVideo, staticFile } from "remotion"; import { WordCaptions } from "@/components/imgcli/word-captions"; const CaptionedClip = () => ( ); export const RemotionRoot = () => ( ); ``` A word's `endFrame` defaults to the next word's `startFrame`, so contiguous transcripts only need start times. For quick drafts you can also pass `words` as a plain string — it is split on whitespace and paced evenly at `framesPerWord`. Set `groupSize={2}` or `{3}` to show short phrases per beat instead of single words. ## Active-word styles - `pop` — the spoken word scales to 1.06 with a fast 6-frame high-damping spring (no bounce). - `highlight` — the spoken word gets a rounded accent-colored chip behind it (CapCut-style). - `color` — the spoken word's text switches to the accent color; the rest stays white. ## Aspect presets - `9:16` — captions ride 20% up from the bottom, clear of feed UI (like/comment/share chrome). - `1:1` — captions sit 11% up from the bottom edge. - `16:9` — classic lower-third placement, 8% up from the bottom. ## Using a real transcript This is the part that matters. Nobody has frame numbers — you have a transcript, and it is in **milliseconds**. Whisper, whisper.cpp, CapCut, Submagic, Opus Clip, Descript and Remotion's own `@remotion/captions` all speak the same shape: ```ts type Caption = { text: string; startMs: number; endMs: number }; ``` So that is what this takes. Drop your Whisper output straight in: ```tsx import { WordCaptions } from "@/components/imgcli/word-captions"; // whatever your transcription tool gave you — word level or phrase level const captions = [ { text: "You", startMs: 1000, endMs: 1180 }, { text: "are", startMs: 1180, endMs: 1320 }, { text: "losing", startMs: 1320, endMs: 1700 }, { text: "three", startMs: 1700, endMs: 2050 }, { text: "hours", startMs: 2050, endMs: 2400 }, ]; ; ``` Or paste an `.srt` in as-is — cues get split into words for you, so the active-word highlight still tracks: ```tsx ``` The frame maths is the component's problem, not yours: milliseconds are converted against `useVideoConfig().fps`, so the same transcript is correct at 24, 30 or 60fps. ### Pages, not chunks Captions are grouped into **pages** the way real caption tools group them — by the shape of the speech. A page ends when it runs out of words, runs out of its character budget, or **the speaker pauses** (`pageBreakMs`). Chopping every N words regardless is how you end up with `STOP / LOSING / HOURS` stacked in a tower: three words that never belonged on a page together. `maxWords`, `maxChars` and `pageBreakMs` override the preset if you need to. ## What actually makes a caption look premium Three things, and the first one is most of it. ### 1. An OUTSIDE outline A caption has to stay legible over footage it has never seen — a face, a sky, a white desk. A heavy black outline is the only thing that does that, and it has to sit *outside* the letterform. `-webkit-text-stroke` **centres** the stroke on the glyph outline, so half of it eats inwards and the letters go thin and mushy. Measured on Montserrat 900 at 160px with a 14px stroke: | | white stem | | --- | --- | | no stroke | 38px | | `-webkit-text-stroke` alone | **22px** — the stroke eats 42% of the letterform | | `+ paint-order: stroke fill` | **38px** — identical to no stroke | ```tsx WebkitTextStrokeWidth: `${stroke}px`, WebkitTextStrokeColor: "#000", paintOrder: "stroke fill", // draw the stroke, THEN the fill over it ``` That one line is most of the difference between a caption and a cheap caption. ### 2. Weight and size Montserrat 800–900, at **11–13% of the frame's short side**. Not Inter at 700, and not 2.8% of the height — that is a subtitle. Load the face through `@remotion/google-fonts` so the Player, the mp4 and your own project all get the same one. ### 3. The spoken word lands A spring with overshoot, not an ease. And that scale is a scale **on text**, which is where captions usually fall apart: a browser gives glyph origins no vertical sub-pixel precision, so a scale that moves the baseline makes the word climb the pixel grid in whole-pixel jumps. The pop pivots on the **measured baseline** — verified on rendered frames, the baseline holds to **0px** while the word scales 1.16×, so it grows upward off its baseline exactly like real caption type. ## Props ", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: "\"light\" | \"dark\"", description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Logos URL: https://imgcli.com/docs/logos Logo stings and entrances for brand moments in launch videos. Reach for this category at the brand beats of a video: the opening sting, or an integrations moment mid-demo. Each component takes your mark (SVG or wordmark) and handles the entrance choreography and hold. --- # Block Wordmark URL: https://imgcli.com/docs/logos/block-wordmark A wordmark that builds itself out of solid blocks — a deck shuffles, collapses, splits one block per letter, and each block flashes and cuts to its real letterform *(interactive preview: https://imgcli.com/docs/logos/block-wordmark)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/block-wordmark.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage A wordmark build in six beats: 1. **Seed** — a single rounded square scales up off the baseline (`growDuration`). 2. **Shuffle** — coloured cards fan out from behind it and wind back in, one full turn each about a *shared* pivot on the baseline. The fan is one S-curve sampled at `cardStagger` offsets, so it opens and closes exactly once (`cards`, `spinTurns`, `spinDuration`, `liftRatio`). 3. **Collapse** — the deck lands back on the seed square and dips once, which is the only frame in the piece where the ink is a single block. 4. **Split** — the square stretches horizontally and splits into one block per letter, outermost blocks leading and the inner ones lagging by `splitStagger`. Any letter whose ink rises above the x-height grows a tab on the correct side — the "b" of `base` sprouts a left stem, a "d" a right one, an "A" an apex. 5. **Hold** — the block row sits and scales up slowly for `holdDuration` frames. This is the beat that reads as a logo rather than as an animation. 6. **Resolve** — each block flashes a colour from `colors`, returns to ink, and is **hard-cut** to its letterform `flashToSwap` frames later. The first letter goes last, so the word finishes where it started. Nothing crossfades and nothing morphs. At each swap frame the block is already within a few pixels of the settled letter, so the cut is invisible — and a spring or a crossfade "to improve it" is the one change that breaks the effect. ```tsx // src/Root.tsx import { Composition } from "remotion"; import { BlockWordmark } from "@/components/imgcli/block-wordmark"; const Sting = () => ( ); export const RemotionRoot = () => ( ); ``` ### Any word, not just this one Nothing about the geometry is hardcoded to a four-letter lowercase word. Every glyph's advance width, x-height and ink top are measured at runtime behind `delayRender()`, so the blocks are sized to the letters they become and the ascender tabs are found by measurement rather than a letter list. Emoji survive (the text is split by code point) and spaces get a gap, not a block. Two knobs decide how literal the morph is: - `blockSizing="square"` (default) makes every block a square the height of the x-height, so the row reads as an even rhythm and each block visibly *changes* into its letter. - `blockSizing="glyph"` sizes each block to its letter's advance width, which makes the resolve nearly in-place. Long words outrun the 150-frame default: the last letter resolves at roughly `94 + flashStagger × letters`. Drop `flashStagger` to 2 or raise `durationInFrames`. ### Colour The deck and the flash colours are a prop with our defaults — the first card follows your `primary` token, the rest are a decorative set. The **ink** defaults to your `foreground`, so an installed copy paints in whatever palette it lands in. The preview above passes `color="#0000FF"` because that is snapcn's own mark; yours is one prop away. A full brand look — ink, deck, and a display face's heavier ascender — is one line. `base` rather than `snapcn` here because the ascender props only show on a word that has one: ```tsx ``` `ascenderRatio` and `ascenderStemRatio` are the two places a display face differs most from the face in use: leave them out and they are measured per glyph, set them and you get the chunky flag a geometric sans draws. ## Props ", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: "\"light\" | \"dark\"", description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Logo Assemble URL: https://imgcli.com/docs/logos/logo-assemble A scatter of cards collapses into a brand card that is born tilted and settles — revealing the logo and brand name *(interactive preview: https://imgcli.com/docs/logos/logo-assemble)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/logo-assemble.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage A logo build in three beats: 1. **Orbit** — a single ring of image cards revolves around the centre, holding a line of `middleText` in the clear middle. 2. **Collapse** — the ring spins inward and drains to the centre, the cards vanishing. 3. **Reveal** — a simple, monochrome logo is born at the centre, then slides left, and the brand name reveals to its right. Pass your `logoSrc` (kept simple/monochrome), your `brandName`, the `middleText`, and the `images` that ride the ring. Everything is frame-driven, so scrubbing and rendering are deterministic. ```tsx // src/Root.tsx import { Composition } from "remotion"; import { LogoAssemble } from "@/components/imgcli/logo-assemble"; const Sting = () => ( ); export const RemotionRoot = () => ( ); ``` Assets take a root-relative `/…` path (served by Next in the Player, rewritten through `staticFile()` in a render) or an `http(s)`/data URL. This is a fast collapse and a scale-on-imagery landing, which a live `` mispaces on a high-refresh display — so the docs preview plays the **rendered mp4**. Re-render after any change: `pnpm run render:previews --only logo-assemble`. ## Props ", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: "\"light\" | \"dark\"", description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Logo Collapse URL: https://imgcli.com/docs/logos/logo-collapse A stack of shots flicked through, collapsing into your mark, and the wordmark landing beside it — each card smaller and held for less time than the last, cutting rather than tweening, until the stack resolves into a logo *(interactive preview: https://imgcli.com/docs/logos/logo-collapse)* One card holds the middle of the frame and cuts to the next shot, each one smaller and each held for less time than the last, so the sequence winds itself up rather than running at a constant clip. At the end of the run the card does not shrink away — it collapses onto a plain tile, the mark overshoots its resting size and settles, the tile goes, and the mark steps aside to make room for the word. ## The cuts are cuts Nothing cross-fades and nothing tweens between shots. The reference holds each card at a fixed size for its whole beat — measured, the box is constant to the pixel across every frame of a hold, and then a different size on the next one. A card that eased between sizes would read as a zoom; a card that cuts reads as a stack of prints being dealt, which is the whole idea. That is why `sizes` and `holds` are parallel lists of literals rather than a ratio and a duration. Neither series is geometric, and a formula fitted to one would miss the other. ## The collapse, frame by frame The beat that is easy to miss and impossible to unsee: the photo card does not shrink into the mark. It **cuts to a plain light tile** with the mark sitting on it, holds two frames, and only then does the tile go. In the reference that tile is luma 233 against a page of 247 — faint enough that nothing thresholded at 200 finds it, and it is what makes the collapse read as a stack resolving into a logo rather than as a card that got small. Then the mark lands: 32px, 25, 24, 23, on four consecutive frames. Two frames of overshoot is the difference between a mark that arrives and a mark that is simply smaller than the card was — and it is the only place in the scene where anything moves between frames instead of cutting. ## Room for the word The mark is centred alone, then steps to a waypoint for four frames, then to its place in the lockup as the word appears whole. Two steps, held, not an ease — the same grammar as the cards. Last, two brackets ping in above and below: each lands at 7px in full accent and settles to 4px and about a third of its ink over five frames, so by the time the lower one arrives the upper one has already quietened. ## Notes `images` and the two lists beside it are parallel — one entry each per shot. A size is a share of the frame height: `0.48` is a square card, `0.82x0.461` a 16:9 one. Real photo sets are not all one shape, and forcing a portrait print square crops it. Cards cut at their own aspect and fill their box, so pick shots that read at the smallest size in the run — the last card is on screen for a sixth of a second at a third of the frame. The default stack is the registry's own preview posters, so the sting says the story of the library: many scenes that share one mark. ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/logo-collapse.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition } from "remotion"; import { LogoCollapse } from "@/components/imgcli/logo-collapse"; const Sting = () => ( ); export const RemotionRoot = () => ( ); ``` ## Props ", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: '"light" | "dark"', description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Logo Drift URL: https://imgcli.com/docs/logos/logo-drift A field of tiles drifting past a headline while the camera pulls steadily back, so the wall of things-it-works-with keeps arriving from every edge *(interactive preview: https://imgcli.com/docs/logos/logo-drift)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/logo-drift.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition } from "remotion"; import { LogoDrift, SNAPCN_STACK } from "@/components/imgcli/logo-drift"; const IntegrationsScene = () => ( ); export const RemotionRoot = () => ( ); ``` A tile is a glyph, a colour and a world-line. Give it your own: ```tsx ``` ## The pull-back is one linear scale This is the measurement the whole component hangs off. Every tile in the reference shrinks at the same *relative* rate: fit its on-screen size against time and you get a straight line, and dividing that slope by the tile's own size gives **0.176 per second** for every tile on screen long enough to fit — seven of them, spread ±0.0016. It is **not** a perspective dolly. Under perspective it is `1/size` that runs straight, and it plainly does not: the size itself is the line. So the whole field lives on one `scale(1 − rate·t)` about the frame centre, which is why tiles keep arriving from every edge — the frame is not moving, it is covering more field. ## The field drifts under it Take the scale back out and each tile's path becomes a straight world-line to within a pixel: a constant velocity, around 80 stage px a second, in its own direction. That is the second half of the model and it is not optional — hold the field still and you are **32px out**; let it drift and six of the seven fitted tiles land within **0.3–1.0px per frame across their whole path**. | | measured | | --- | --- | | pull-back | `0.176` of scale per second, linear | | tile drift | constant velocity, ~80 stage px/sec, per tile | | tile fade | `1 − e^(−age/0.4s)` — 63% up in 400ms, still creeping at 1.2s | | word stagger | `150ms` | | word entrance | `1.216×` and `4.3px` of blur, resolved in `140ms` | | exit | from `3.13s`, to `0.80×` and `6.5px`, over `340ms` | | headline | `20px`, centre-set, the line re-centres as words land | Every one of those is a prop. ## The words do not fade in Measure the ink and it is conserved from the first frame a word exists. What resolves is **1.216× of scale and 4.3px of blur**, in 140ms — a word arrives close and soft and settles, it does not appear out of nothing. Words land every 150ms, so each one finishes exactly as the next begins, and because the line is centre-set the words already placed slide left to make room. The exit is the same move run backwards on the whole line at once: it recedes to 0.80×, blurs to 6.5px and goes. ## Everything is a prop, and it fits any field - **`tiles`** is the whole field: glyph, label, colour, world position, drift, size and entry time per tile. `SNAPCN_STACK` is the default and is exported, so you can spread it and change two entries. - **`pullback`** is the camera. Zero holds the field still; raise it and the wall recedes faster. - **`tileSpeed`** and **`tileScale`** multiply every tile's drift and size at once, so you can re-time a field you like the shape of without touching ten objects. - **`tileFade`** is the time constant of a tile's fade-up. It exists because not every tile can arrive from off-stage — the reference fades one up five pixels inside the right edge, and without the fade that reads as a pop. - **`glow`**, **`glowOpacity`** and **`accentColor`** paint the wash. It is static on the reference and static here: a glow that breathes under a headline reads as a mistake rather than as light. ## Colour and wording The proportions and the timing are measured. The **labels and the paint are not** — the reference is a specific product showing off a specific set of other people's logos, and none of that belongs in a component that ships to strangers. The default field is the stack a snapcn component actually drops into, drawn as wordmark tiles rather than borrowed marks, and the paint is ours. ## Props ", description: "Token overrides — the page colour and the headline's ink come from here", }, { name: "mode", type: '"light" | "dark"', default: "\"light\"", description: "Which token set to resolve against", }, { name: "speed", type: "number", default: "1", description: "Multiplies the frame clock. Every beat above scales with it", }, ]} /> The recording is 3.37s and cuts while the headline is still blurring out. 108 frames is that clip plus a quarter of a second, so the exit lands instead of being clipped mid-blur. --- # Logo Flicker URL: https://imgcli.com/docs/logos/logo-flicker Images flip across the screen very fast, the flicker decelerates and fades, and the logo and brand name resolve *(interactive preview: https://imgcli.com/docs/logos/logo-flicker)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/logo-flicker.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage A Marvel-Studios-style flicker reveal: 1. **Flicker** — the `images` flip **full-screen** at a **constant** fast rate (one image per `flipInterval` frames), right to the end — no slow-down. 2. **Cross-fade** — the flicker fades **out** while the logo and brand name fade **in**, on the exact same window — one move, not one after the other. Pass your `logoSrc` (simple/monochrome) and `brandName`, and the `images` the flicker flips through. Everything is frame-driven, so scrubbing and rendering are deterministic. ```tsx // src/Root.tsx import { Composition } from "remotion"; import { LogoFlicker } from "@/components/imgcli/logo-flicker"; const Sting = () => ( ); export const RemotionRoot = () => ( ); ``` The flicker swaps the full-screen image every couple of frames, which a live `` shows as harsh flashing before the browser has the images cached — so the docs preview plays the **rendered mp4**. Re-render after any change: `pnpm run render:previews --only logo-flicker`. Note: this is a fast full-screen image strobe. Raise `flipInterval` (slower flips) if you need it gentler for photosensitivity. ## Props ", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: "\"light\" | \"dark\"", description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Screens & Devices URL: https://imgcli.com/docs/screens Phone frames, terminals and AI-product simulations — the demo toolkit. This is the category for showing product. Wrap a recording or live UI in device chrome, or simulate a terminal or AI-tool session entirely in code. Reach for it in the demo section of a launch video — anywhere a screen is the subject of the shot. --- # Capture Reveal URL: https://imgcli.com/docs/screens/capture-reveal A toast counts "Capturing selection…" up to 100% over a browser window, the window drops away and shrinks, the toast pops to "Copied to clipboard." and a headline rises out from behind the window *(interactive preview: https://imgcli.com/docs/screens/capture-reveal)* A screenshot being taken. A toast counts `Capturing selection…` up to 100% over your product's window; the window drops away and shrinks, the toast pops to `Copied to clipboard.`, and a two-line headline rises out from behind the window while the cursor glides up to the toast. ## How it moves **The window is one transform.** A 108px drop and a 16% shrink ride the same curve, `cubic-bezier(0.549, 0.228, 0.136, 0.938)` over 0.8s, about a fixed origin — then a slow exponential shrink takes over the frame the drop lands, so the window never quite stops. **The toast presses and releases.** While the counter runs it sits pressed in to 88%; on the swap it pops from 0.9 to full size on `cubic-bezier(0.301, 1.056, 0.803, 1)`. The counter is linear: three percent a frame. **The headline rises from behind the window.** It does not scale and does not blur — it climbs 23px on a long ease-out and fades in on a faster curve of its own, so it is readable before it has finished arriving. ## Notes Every curve was measured off a 24fps screen recording, frame by frame, and verified by rendering this component at the recording's own size and timestamps and diffing the two in Y′CbCr. The window, toast and cursor land within a tenth of a pixel on every frame. The recording starts mid-count, so the first 1.3s — the toast arriving and pressing in — is built from the same curves. The type is DM Sans with its optical-size axis pinned per element — the display cut for the headline, the text cut for the toast — so a 1080p render draws the same letterforms as a 720p one. It loads from fontsource; if it cannot, Google's DM Sans paints. `src` is laid in at the window's full width, top first. A screenshot at least 3:4 tall keeps the window's bottom edge out of frame the whole time. ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/capture-reveal.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition, staticFile } from "remotion"; import { CaptureReveal } from "@/components/imgcli/capture-reveal"; const Hook = () => ( ); export const RemotionRoot = () => ( ); ``` ## Props ", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: '"light" | "dark"', description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Cursor Track URL: https://imgcli.com/docs/screens/cursor-track A synthetic cursor that moves through waypoints and pulses a ring on click, over any children *(interactive preview: https://imgcli.com/docs/screens/cursor-track)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/cursor-track.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage A wrapper. It renders `children` full-bleed and walks a cursor over them along a path of waypoints, pulsing a ring wherever you say there was a click. For UI sims, and for recordings whose real cursor never made it into the file — most capture tools drop it. ```tsx import { CursorTrack } from "@/components/imgcli/cursor-track"; import { ScreenRecording } from "@/components/imgcli/screen-recording"; ; ``` `at` is the frame the cursor starts travelling **towards** the point, absolute rather than relative to the previous waypoint. It then **rests there until the next waypoint's `at`** — there is no hold field, the hold is the gap. The first waypoint has nothing to travel from, so it *places* the cursor and its `duration` is the fade-up window instead; give it an off-frame `x` (values outside 0–1 are legal) to have the cursor walk in. `click: true` pulses the ring on **arrival**, at `at + duration`. To click twice without moving, add a second waypoint at the same point. `path` defaults to the demo track so the component is worth looking at with nothing passed; `path={[]}` renders no cursor at all, i.e. a no-op wrapper. ### Composing Put the recording inside this, not this inside the recording — the cursor has to be the top layer to read as a cursor: ```tsx ``` Waypoints are **frame** coordinates and do not ride an inner `` camera — a push-in moves the UI under the cursor but not the cursor. Keyframe both, or push in on a shot the cursor is already resting in. `speed` does not propagate either; pass the same value to every layer. ### The cursor itself `arrow` is a hand-drawn macOS-style pointer with its hotspot at the tip — an SVG, not an emoji and not a font glyph, because a glyph is whatever face the renderer happens to resolve. It stays readable over arbitrary footage through an outline stroked outside the ink rather than a drop shadow. `dot` is a touch/tap puck for mobile sims. `color` defaults to `theme.foreground` and `outlineColor` to `theme.background`, so passing `mode="dark"` over dark footage swaps both together. Omit `size` and the cursor is 3.9% of the frame height, which is what a real one looks like at any crop. ### When not to use this When the recording already has a cursor in it. Two pointers on screen is worse than none, and this component cannot remove the one in your file. Also skip it for anything that is not a click path — travel between waypoints is a straight line on an ease-in-out, which reads as deliberate for a demo and robotic for anything meant to look like a human wandering. ## Props ", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: "\"light\" | \"dark\"", description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Laptop Frame URL: https://imgcli.com/docs/screens/laptop-frame MacBook-style laptop that opens, runs a notch notification, then dives into the screen until it fills the frame *(interactive preview: https://imgcli.com/docs/screens/laptop-frame)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/laptop-frame.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage The lid **opens**, the notch runs its idle → connecting → connected notification, and then the camera **dives into the screen** — un-tilting it and scaling it up until the content fills the frame. Drop in a screenshot, a screen recording, or your own UI. Everything is driven by the current frame, so scrubbing and rendering are fully deterministic. The component ships with a transparent background; supply the backdrop from the scene. ```tsx // src/Root.tsx import { Composition, staticFile } from "remotion"; import { LaptopFrame } from "@/components/imgcli/laptop-frame"; const ProductScene = () => ( ); export const RemotionRoot = () => ( ); ``` `screenSrc` takes an **image or a video** — a `.mp4/.webm/.mov/.m4v` plays through ``, anything else through ``, both covering the screen and fading in. Pass a `staticFile()` path (or a root-relative `/…` one) for a bundled asset, or an `http(s)`/data URL. For full control, pass `children` instead. Omit both and you get a built-in product-hero placeholder. ### Entrances - `rise` — fades in while translating up 70px with a fast ease-out; the default launch look. - `open` — the lid swings up from the deck (82° → rest tilt) as the machine fades in. - `none` — appears already settled, for scenes that composite the laptop in some other way. ### The screen takeover (`finale`) `finale="zoom-to-screen"` is the payoff: from frame 140 the camera dollies into the screen and flattens the lid to head-on (rotateX → 0), so by frame 185 the screen content **covers the whole frame** and holds. The cover scale is derived from the composition size, so it lands filling the frame exactly — this assumes the laptop occupies the full composition. Set `finale="none"` to just leave the laptop sitting. This is a slow scale on the whole screen over many frames, which a live `` mispaces on a high-refresh display — so the docs preview here plays the **rendered mp4**. Re-render it after any change: `pnpm run render:previews --only laptop-frame`. ### The notch The notch is a dynamic-island-style pill that morphs through three states: an idle camera dot, three connecting dots, then the connected label with a battery. It lands "connected" at frame 120 (then fades as the dive begins) — set `showNotch={false}` for a plain lid, or change `notchLabel` and `batteryLevel` to suit the scene. ## Props ", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: "\"light\" | \"dark\"", description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Phone Frame URL: https://imgcli.com/docs/screens/phone-frame iPhone-style device frame with a dynamic island and a screen slot — flat, tilted, or a cinematic 3D showcase crane move *(interactive preview: https://imgcli.com/docs/screens/phone-frame)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/phone-frame.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage Drop any screen content into the frame — an app UI, an `` screen recording, or an `` — and the device rises in over your backdrop, then keeps a subtle float loop. Everything is driven by the current frame, so scrubbing and rendering are fully deterministic. The component ships with a transparent background; supply the backdrop from the scene (an ambient gradient works well). ```tsx // src/Root.tsx import { Composition, staticFile } from "remotion"; import { PhoneFrame } from "@/components/imgcli/phone-frame"; const AppLaunchScene = () => ( ); export const RemotionRoot = () => ( ); ``` `screenSrc` takes an **image or a video** — a `.mp4/.webm/.mov/.m4v` plays through `` (muted), anything else renders through ``, both cover-fit to the screen. Omit both `children` and `screenSrc` to get the built-in ride-summary placeholder — a dark map whose glowing GPS route draws itself while stat "insight" pills pop in along it. On light scenes, keep the silver `bezelColor`; on dark ones, pass `mode="dark"` and the shell follows the theme. Unlike a desktop browser window, this is a portrait device with a real dynamic-island cutout and a 3D tilt (`tilt`) or cinematic crane move (`showcase`) for hero shots. ### Variants - `flat` — faces the camera head-on. - `tilt` — a static perspective `rotateY` (see `tiltAngle`). - `showcase` — a cinematic crane move: the camera starts low at the bottom-left corner of the laid-back device, sweeps up the screen while zooming in, then pulls back and settles on a straight frontal view. Normalized to the composition length, so it always completes. ### Entrances - `rise` — fades in while translating up 70px with a fast ease-out; the default launch look. - `rotate-in` — rises with a slight -8° in-plane rotation and a 0.94 → 1 scale settle. - `float` — no travel: fades and grows from 0.96 in place, with the float loop running from frame 0. ## Props ", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: "\"light\" | \"dark\"", description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Phone Stack URL: https://imgcli.com/docs/screens/phone-stack Phone screens playing clips rise out of a blur one after another, each pushing the stack back so the older ones peek out on top *(interactive preview: https://imgcli.com/docs/screens/phone-stack)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/phone-stack.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage Each clip gets its own screen. Screens arrive `stagger` frames apart: the new one rises out of a blur into the front, and every screen already there steps back — smaller, and higher, so its top peeks out above the newest. Screens more than three steps back fade away, so any number of clips works. ```tsx import { Composition, staticFile } from "remotion"; import { PhoneStack } from "@/components/imgcli/phone-stack"; const Scene = () => ( ); export const RemotionRoot = () => ( ); ``` Clips take a root-relative `/…` path or an `http(s)` URL and play muted from the moment their screen arrives; images work too. Screen recordings at 9:19.5 fit exactly; any aspect is cropped to cover. ## Props --- # Screen Recording URL: https://imgcli.com/docs/screens/screen-recording Crops the chrome off a raw screen capture, fits it to the frame, and runs a keyframed camera that pushes in on what matters *(interactive preview: https://imgcli.com/docs/screens/screen-recording)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/screen-recording.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage A raw capture is not a shot. This is the treatment that makes it one: **crop** the browser and OS chrome off each edge, **fit** what is left to the frame, and run a **camera track** that pushes in on the thing you are talking about and pulls back when you are done. Everything is a pure function of the current frame, so scrubbing and rendering agree. ```tsx // src/Root.tsx import { Composition } from "remotion"; import { ScreenRecording } from "@/components/imgcli/screen-recording"; const Demo = () => ( ); export const RemotionRoot = () => ( ); ``` `src` takes an **image or a video** — a `.mp4/.webm/.mov/.m4v` plays through ``, anything else through ``, and both take the same camera track, so a single screenshot works. Root-relative paths are served by Next in the Player and rewritten through `staticFile()` in a render; `http(s)`/data URLs pass through. ### Cropping the chrome `crop` is **fractions of an edge, never pixels** — `{ top: 0.11 }` drops the top 11%, which is about what a tab strip and address bar come to. Measure once on a still and divide (`chromePixels / recordingHeight`); the same numbers then survive a re-record at a different resolution. Give `sourceAspect` (width ÷ height of the file, `1920x1080` → `1.778`) and the crop is exact on the raw file. **Omit it and the source is aspect-filled to the composition first**, so the fractions are measured on the shot as it appears rather than on the file. Identical whenever the two aspect ratios already match, which is the usual case — but if you are dropping a 4:3 capture into a 16:9 composition, pass it. ### The camera A move eases from wherever the camera actually is at `at` to the pose named, over `duration` frames, then **holds until the next move's `at`**. There is no `hold` field — the hold is the gap. `zoom` is absolute (`1` is the fitted shot), and omitting `zoom`, `x` or `y` carries the previous value, which is what makes a pure pan one line. Moves that overlap are truncated, never blended, so the camera stays continuous. Focal points are **clamped to what the current zoom can actually hold**, so a pull-back recentres itself instead of dragging a band of backdrop into frame. That clamp is visible in the numbers: at `zoom: 1.2`, `x: 0.95` resolves to `0.583` — the furthest right the shot can go without showing its own edge. Zoom in more to reach further out. Pass `camera={[]}` for a locked-off shot at zoom 1; a treated recording with no camera is still the point of this component. ### Composing Emphasis is this component's own job. `camera` is the way a shot points at something: a push to a focal point rides the footage by construction, because the transform *is* the footage. Nothing is layered over it, so nothing can drift off it. ```tsx ``` The camera transform lives entirely inside this component, so anything wrapping it — `cursor-track`, a caption — is unaffected and reads in plain **frame** coordinates. That is a feature and a trap at once: a wrapper does **not** ride the camera, so a cursor over a push-in visibly detaches from the UI it is clicking. Keyframe the wrapper too, or give that beat `camera={[]}`. Everything is laid out as a **fraction of the box the component is mounted in**, not of the composition, so a recording drops into `laptop-frame` or `phone-frame`'s `children` and fills the screen. The one exception is `sourceAspect`, which is compared against the composition's aspect — inside a device frame, omit it and let the source aspect-fill. `speed` does not propagate through a stack — pass the same value to every layer. ### When not to use this When the recording already is the shot. If your capture is a clean, cropped, well-paced window and the camera never moves, this component earns nothing over an `` of your own — and it will not fix a bad take. It also has no `chrome="browser"` preset, deliberately: a fraction that guesses an unknown recording's chrome is wrong more often than it is right, and a wrong crop is a shot with a sliver of tab bar in it. ## Props ", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: "\"light\" | \"dark\"", description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Terminal Simulator URL: https://imgcli.com/docs/screens/terminal-simulator Single-camera flythrough — one continuous camera whips from a headline to a package-manager command panel to a zoomed green terminal, all on one canvas *(interactive preview: https://imgcli.com/docs/screens/terminal-simulator)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/terminal-simulator.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage Everything lives on **one black canvas** and a **single camera** flies over it — nothing slides in from a screen edge. The camera holds on the **intro headline**, creeps left, then **suddenly accelerates** across to the **command panel** (package-manager tabs + a syntax-coloured install command with the package name highlighted), pushes in slowly while it types — leaving the trailing motion-blur copies — then pulls back, descends, and **punches in** on the green **focus terminal** as it types its output. Fast camera moves draw a velocity-driven ghost trail; holds are dead still. Output reveals in bursts (chunked, not char-by-char), and any line ending in `...` freezes the frame for 18 frames — or set `pause` explicitly. Everything is driven by the current frame, so scrubbing and rendering are fully deterministic. Pass `intro={null}` or `command={null}` to skip a station — the camera path closes the gap automatically. ```tsx // src/Root.tsx import { Composition } from "remotion"; import { TerminalSimulator } from "@/components/imgcli/terminal-simulator"; const InstallScene = () => ( ); export const RemotionRoot = () => ( ); ``` ### Intro headline `intro` is a single line rendered in the sans stack, centered. Wrap words in `*asterisks*` to paint them with the accent gradient (blue → violet); everything else stays white. Set `intro={null}` to skip straight to the command panel. ### Focus terminal lines The final station renders each line large and zoomed, right of the green accent bar. `command` lines are white and carry the blinking accent cursor (the last one keeps it after it finishes typing); `log` lines are dim, `success` lines take the accent green, `error` lines red. `zoom` sets the landing zoom of the final punch (1.5x by default, 2.4x with `zoom` on). ## Props ", description: "The focus-terminal output (final station). `delay` is the frame gap before a line starts typing (default 8). `pause` freezes the frame after the line finishes; lines ending in \"...\" auto-freeze for 18 frames when `pause` is omitted.", }, { name: "intro", type: "string | null", default: "\"*work* one step at a *time.*\"", description: "The first camera station. Wrap words in *asterisks* to tint them with the accent gradient. `null` or \"\" skips the station.", }, { name: "command", type: "{ managers?: string[]; text: string } | null", default: "{ managers: ['npm','pnpm','yarn','bun'], text: 'npm install One Tool' }", description: "The second camera station. `managers` are the tab labels (first is active); `text` is typed into the row — manager and subcommand dim, the package name highlighted. `null` skips the station.", }, { name: "background", type: "string", default: "theme.card", description: "Command-panel surface color", }, { name: "borderColor", type: "string", default: "theme.border", description: "Hairline color for the command-panel ring and divider", }, { name: "fontSize", type: "number", default: "18", description: "Base monospace size. The command panel and focus terminal scale up from it", }, { name: "charsPerFrame", type: "number", default: "2", description: "Reveal speed: how often the chunked reveal steps (a step every 1 / charsPerFrame frames)", }, { name: "chunkSize", type: "number", default: "3", description: "Characters revealed per step — larger chunks make output lurch in bursts like real command output", }, { name: "zoom", type: "boolean | { enabled: boolean; scale?: number; followCursor?: boolean }", default: "false", description: "Landing zoom of the final punch onto the terminal. Off = 1.5x; `true` or `{ scale }` overrides (default 2.4)", }, { name: "speed", type: "number", default: "1", description: "Global playback multiplier applied to the whole session", }, { name: "className", type: "string", description: "Optional className passed to the outer container", }, { name: "theme", type: "Partial", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: "\"light\" | \"dark\"", description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Charts & Stats URL: https://imgcli.com/docs/charts Animated scatter plots, line and bar charts, unit charts and count-up counters — the shot where a product video shows its numbers. Every launch video reaches the beat where a number has to land: installs this week, revenue, time saved. A screenshot of a dashboard reads as a still. These draw the chart on the frame clock instead, so the number arrives as part of the motion rather than sitting on top of it. They are plain Remotion components — `useCurrentFrame()` and `interpolate()`, no chart library — so they render frame-exact to MP4, and the labels, totals and series are props: the numbers on screen are yours. Everything in this category is part of [snapcn Pro](/docs/pricing?ref=charts) — and one Pro component is free with your email. Each card opens the component's own page, with the full demo, how it moves, and how it installs. --- # Social Proof URL: https://imgcli.com/docs/social Follower counts, notifications and community milestones — the beat of a video that says other people are already here. Reach for this category when the point of the shot is that other people showed up: the follower count climbing, the notifications stacking, the wall of faces. It is the beat that sits between the demo and the call to action — and the one that is hardest to fake convincingly with a screen recording. --- # Channel Thread URL: https://imgcli.com/docs/social/channel-thread A work chat filling itself in — an avatar, a name in bold, a quiet timestamp and messages as plain lines; three dots hold a line until the words arrive, and the transcript scrolls once per arrival and is perfectly still in between *(interactive preview: https://imgcli.com/docs/social/channel-thread)* Not bubbles: a transcript. An avatar, a name in bold, a quiet timestamp, and the messages under them as plain lines — the shape every team tool has settled on. Messages land one at a time. A second message from the same person joins the group without repeating the name. When somebody new starts typing, three dots hold their line until the words arrive. ## How it moves **The transcript never creeps.** Between arrivals it is byte-identical frame to frame — measured at 0.00px for twenty-nine frames at the head of the reference and another eight in the middle. Everything that looks like life comes from the arrivals themselves. **It scrolls only when it has to.** A line moves the transcript by however far it falls *past* the anchor, which is zero until the thread is tall enough to reach it. That is one constant instead of a per-message "does this one scroll" flag, and it is what reproduces the reference's second message landing with the stack measurably motionless. **A longer move takes less time.** Measured twice: the two-row scroll runs 1.60× faster in normalised time than the one-row scroll. That is a browser's native smooth-scroll heuristic, and the component reads one curve faster the further it has to go rather than pretending both moves are the same speed. **The room is made before the person arrives.** The scroll starts three frames before the avatar, the name and the first dot are painted. Three frames is nothing to watch and everything to match — and the ellipsis is timed from that moment, not from the scroll, or it sits a step out of phase for its whole life. ## The two clocks A message's `at` is the frame its words land on. Its `opens` is the frame its row appears. Splitting them is what lets a pause be a pause: a group's row shows typing dots until its words arrive, and a later message's row simply waits. ## Notes Messages are single lines — they do not wrap, because wrapping would break the measured baseline grid the whole scene is laid out on. Keep a line to about thirty characters at 16:9 and it will not reach the frame edge. The scene ships **dark**, alone in the registry. A transcript is read against the room it is in, and on the warm off-white page the scroll fade — which is the whole depth of the scene — turns the oldest line into a pale smudge that reads as a rendering fault rather than as distance. `mode="light"` is one prop away and still resolves from your tokens. Avatars default to two of the twenty-four photos the registry already ships in `public/avatars`. Faces are the difference between a chat that reads as a chat and one that reads as a wireframe. Each message carries its own author, time and avatar, and consecutive messages that share all three are one group under one name. Leave `avatar` out and that person gets their initial on a tile tinted with your accent. The avatar holds the render until the picture has actually loaded. Without that a frame can be captured before it arrives and the square comes out empty — invisible in the Player, where an image simply appears, and silent in an mp4 until somebody watches it. ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/channel-thread.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition } from "remotion"; import { ChannelThread } from "@/components/imgcli/channel-thread"; const rhea = { author: "rhea", time: "9:41 AM", avatar: "/avatars/07.jpg" }; const sam = { author: "sam", time: "9:42 AM", avatar: "/avatars/13.jpg" }; const Chat = () => ( ); export const RemotionRoot = () => ( ); ``` ## Props name time > text`, `|` between messages, a bare text continuing the speaker above", }, { name: "fontFamily", type: "string", default: '"Default"', description: "Barlow unless you name one of the loaded faces", }, { name: "theme", type: "Partial", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: '"light" | "dark"', default: '"dark"', description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Follower Rush URL: https://imgcli.com/docs/social/follower-rush An X-style follower notification that piles up — avatars stack in and the count explodes, then the row bends into an undulating wave of faces *(interactive preview: https://imgcli.com/docs/social/follower-rush)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/follower-rush.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage Built for the "we blew up" milestone beat. It opens as a single X-style notification — `Manon ✓ followed you` — then reorganizes into a stacked pile as more followers pour in: avatars pop on one by one and the count climbs slowly, then detonates from a couple dozen to the target. As it explodes the flat row bends into a travelling sine wave of faces that scrolls and undulates, with the lead name settling on the last follower and the count landing on the total. ```tsx // src/Root.tsx import { Composition } from "remotion"; import { FollowerRush } from "@/components/imgcli/follower-rush"; const Rush = () => ( ); export const RemotionRoot = () => ( ); ``` ## Props ", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "orientation", type: "\"horizontal\" | \"vertical\"", default: "\"horizontal\"", description: "Layout orientation — horizontal is 1280×720 (16:9), vertical is 720×1280 (9:16) letterboxed", }, { name: "speed", type: "number", default: "1", description: "Animation speed multiplier — minimum 1 so the timeline always reaches the wave", }, ]} /> ## Notes - **Exponential count:** The total holds at 1 through the inline intro, then grows on an eased exponential — a slow crawl to a couple dozen, then a blow-up that lands exactly on `totalFollowers`. - **Flat pile → wave:** Avatars overlap in a centred pile (leftmost on top, a page-coloured ring separating them), then bend into a travelling sine wave that scrolls; a single edge mask fades the wave's ends. - **Offline rendering:** Avatars are real low-id GitHub photos (CORS-safe), each backed by an initial-letter circle, so the composition renders with no network and MP4 export needs no CORS setup. - **Live data ready:** Pass your own crowd as `followers` in the `{ name, avatarId }` shape; the lead callout name cycles through it and freezes on the last one. --- # Scenes URL: https://imgcli.com/docs/scenes Full scene compositions for launch and demo videos Complete beats of a product video — openers, problem statements, pricing, and outros. Drop them into a `` or `TransitionSeries` and swap the copy for your own. --- # Announce Title URL: https://imgcli.com/docs/scenes/announce-title A four-shot announcement title — the eyebrow rushes past the camera on a receding type plane, the name assembles on paper, and a macro pan cuts wide as the tagline builds itself last word first *(interactive preview: https://imgcli.com/docs/scenes/announce-title)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/announce-title.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage Three strings and it is a launch film. Everything else has a measured default. ```tsx // src/Root.tsx import { Composition } from "remotion"; import { AnnounceTitle } from "@/components/imgcli/announce-title"; const Launch = () => ( ); export const RemotionRoot = () => ( ); ``` ## Four shots, three cuts Nothing here crossfades, and nothing is continuous across a cut. **The rush** (frames 0–17). The eyebrow is laid on a plane raked away from the viewer and thrown past the camera. It starts 53× its resting size with the plane's horizon *inside the frame* — one letter fills the screen — and lands at 4× as the horizon leaves and the plane flattens to face-on. Magnification and rake are both frame-by-frame tables rather than curves, because the move slows to a near-hold around frame 12 and then speeds up again into the cut, which no single easing does. **The field** (18–40). The same word, flat, finishing the move: 1.530× down to rest, its excess shrinking 24% per frame. Then it is torn off to the **left**, displacement tripling every frame, staggered about a fifth of a frame per letter — by frame 38 the first letter has travelled 56px and the last one five. **The paper** (41–79). Each word of the name slides in from the right into a slot that is already laid out. They never push each other around, they all close at the same 0.82 per frame, and the lead word travels 1.63× as far as the rest. Each pops in at partial opacity rather than fading up from nothing. **The macro pan and the close** (80–169). A macro shot of the middle of the tagline pans left — decelerating from 52px/frame to 9 at the hold, then accelerating out to 105 — with the symbol crossing the other way at the same speed. It cuts wide, and the line builds itself **last word first** while a white sweep crosses it left to right and drains the colour out of it. ## Making it yours ### The symbol The mark that leads the closing line is a path in a `0 0 100 100` box — it ships as the snapcn mark, and any other drops straight in: your logo, a bullet, a glyph you traced. ```tsx ``` `symbolColors` are gradient stops spaced evenly from the mark's first tip to the last — two give a plain ramp, one a flat fill, and leaving it out keeps the ten measured stops of the default spark. The customizer's text field takes the same thing comma-separated. `symbolPath=""` removes the mark entirely. ### Colour A title sequence is burned-in film type, not app chrome, so its palette is the design rather than the design system — every ground and ink is a prop: ```tsx ``` ### Length Type sizes are measured to the pixel, and a size fixed to one sentence clips the moment you write a longer one. So each line is drawn once into a hidden probe, its real width read off it, and its size scaled down only if it would overrun its share of the frame — a line that already fits renders at exactly the measured size. A character count cannot do this: the default tagline is one character longer than the one everything was measured against and **14% wider**, because `Ready-made` and `Remotion,` carry wider glyphs than `Perfect` and `massive`. ## Measured Every number below came off rendered frames, not taste. | | measured | | --- | --- | | cuts | frames `18`, `41`, `80`, `110` | | opening magnification | `53×` → `4×`, rake `81°` → `14°` | | eyebrow settle | `1.530×`, decaying `0.7605` per frame | | eyebrow exit | `×3.2` per frame, leftward, `0.215` frames per letter | | title words | `0.82` per frame; lead word travels `1.63×` the others | | macro pan | `52` → `9` → `105` px/frame | | closing slide | `0.792` per frame | | word reveal | one word every `2.833` frames, right to left | | white sweep | `31.7` px/frame, left to right, `0.21 W` soft edge | The settles are all the same curve — a first-order exponential lag, not a cubic-bezier — normalised over a fixed window so it *lands* instead of spending its last five frames moving less than half a pixel. ## Props This scene plays as a rendered mp4 rather than a live ``. Type is under a moving scale for most of the run — a receding 3D plane, a 1.5× settle, then a whole line panned at 14× — and the first two shots are each drawn up to 44 times a frame for the shutter. A browser has an 8ms budget for all of that; a render has none, and the difference is the whole read of the opening. Touch any control and the customizer hands you the live Player back. --- # Bloom Lockup URL: https://imgcli.com/docs/scenes/bloom-lockup A wall of finished work blooms outward from your logo, cuts in tight on it, then bends away through a lens and a flash while the logo rises to take its wordmark *(interactive preview: https://imgcli.com/docs/scenes/bloom-lockup)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/bloom-lockup.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage Bloom Lockup paints its own background and runs 120 frames at 30fps. Every curve is measured frame by frame off a reference recording. Fill the cards with your own screens, and pass your logo as SVG paths, the same shape Cut Lockup takes. ```tsx import { BloomLockup } from "@/components/imgcli/bloom-lockup"; export const Intro = () => ( ); ``` --- # Card Rail URL: https://imgcli.com/docs/scenes/card-rail A rail of cards flicked sideways across a tilted plane — the rail leaves at speed, decelerates for over a second and comes to rest about a card further along, while the plane's foot runs a fifth wider than its head *(interactive preview: https://imgcli.com/docs/scenes/card-rail)* The cards do not crawl past on a loop. They are **flicked**: the rail leaves at speed, decelerates for well over a second and comes to rest about a card further along, the way a carousel behaves under a thumb rather than under a timer. A title holds the top of the frame and does not move with them. ## The plane is tilted, and that is the whole look The rail is not flat to the camera. Measured on the card edges of a recording, the horizontal scale grows **0.000825 per pixel of height** about the frame's own centre line — the foot of a card is a fifth wider than its head — while horizontals stay horizontal and the type stays upright. That is a plane rotated about a horizontal axis under perspective, and it is why a row of plain rectangles reads as a surface receding into the frame rather than as a filmstrip. `DEPTH`, `TILT` and `HORIZON` are the three numbers that describe it, and they are deliberately separate from the card metrics: change the card size and the plane does not move. `HORIZON` is the height at which the plane is exactly life-size — above it the rail is smaller and further away, below it nearer and larger. It is solved, not chosen: the card's width at two heights puts it most of the way down the visible card rather than at the frame's middle. ## Flicks add up `flicks` is how many times the rail is pushed and `every` the frames between one leaving and the next. They are a **sum**, not a queue: each flick is the same measured curve started `every` frames after the last, so a flick begun while the one before it is still creeping carries that creep with it — which is what repeated swipes actually do. The default is three, thirty frames apart, which carries the rail about three cards along. That is also why the card lists are worth filling out. Three flicks show six cards; five entries would start repeating on screen. ## The flick is measured `FLICK` is the curve off the recording. It peaks at 29px a frame three frames in and is still moving at 2px a frame a second later. No standard ease has that tail — a cubic-out arrives four times too early and a spring overshoots something that never overshoots. And the travel is **not** one card pitch. A rail that advanced by exactly one pitch would be an assumption; measured, the recording's travels 333 where its pitch is 325. A flick ends where the momentum ran out, not on a snap, and those eight pixels are the difference between a carousel and a surface somebody pushed. ## Cards, and cards made elsewhere `cards` is one entry per card — `{ image, title, note, tag }` — and the rail repeats them rather than running out — though with three flicks you want at least six real ones, or the repeat shows. The rail has a beginning, though: nothing is drawn before the first card, because a loop that wraps round puts a card in the empty half of the frame the scene opens on. Leave the words out and the picture becomes the whole card. That is not a special case for its own sake — it is what lets a card designed somewhere else be handed in as one image. ## Notes `backdrop` takes two colour stops or one picture. A picture because a brand backdrop is often not a gradient at all: the recording this was measured from has corners that satisfy a linear fit to within five parts in 235, and a middle forty levels darker than that fit predicts — a mesh or a painted plate, not something two stops can be talked into. `from` may be negative, which opens the scene already in flight. That is how the recording starts, and it is often what you want at the head of a video. ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/card-rail.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition } from "remotion"; import { CardRail } from "@/components/imgcli/card-rail"; const Rail = () => ( ); export const RemotionRoot = () => ( ); ``` ## Props title > small print > tag`, `|` between cards", }, { name: "heading", type: "string", default: '"Browse scenes"', description: "The line that holds the top of the frame", }, { name: "flicks", type: "number", default: "3", description: "How many times the rail is pushed. Each one carries it about another card along", }, { name: "every", type: "number", default: "30", description: "Frames between one flick leaving and the next", }, { name: "from", type: "number", default: "0", description: "Frame the flick starts on. Negative opens the scene already moving", }, { name: "offset", type: "number", default: "0", description: "Where the rail sits before it moves, in layout pixels", }, { name: "backdrop", type: "string", description: "Two colour stops split by `|`, or one picture. Blank uses the theme's accent", }, { name: "fontFamily", type: "string", default: '"Default"', description: "Figtree unless you name one of the loaded faces", }, { name: "theme", type: "Partial", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: '"light" | "dark"', description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Count Grid URL: https://imgcli.com/docs/scenes/count-grid A grid of cards rushes in under a horizontal smear and settles on five, then the camera pulls back a fraction while every empty cell fills in from the middle out and the count jumps from five to five hundred *(interactive preview: https://imgcli.com/docs/scenes/count-grid)* The "and it scales" shot, without a cut. Five cards and a label arrive under a hard horizontal smear and settle. Then, on one frame, the camera pulls back a fraction and every empty cell around them fills in from the middle out while the number in the label jumps — five clips, then five hundred. The whole scene hangs on one value: the **cell pitch**. The card size, the gutter, the label and the grid's placement are all fractions of it, so the timing lives in a single curve and nothing can drift out of step with anything else. ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/count-grid.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition } from "remotion"; import { CountGrid } from "@/components/imgcli/count-grid"; const Scale = () => ( ); export const RemotionRoot = () => ( ); ``` Forty-seven frames — a second and a half. It is a beat inside a longer edit, not a standalone piece, and it is built to be cut into. ## The beats | beat | frames | what moves | | --- | --- | --- | | rush in | 0 – 7 | cell pitch 174 → 138.5, hard horizontal smear | | settle | 7 – 19 | 138.5 → 121.5, the blur spent by frame 5 | | label | 11 – 18 | the whole line fades up at once — it does not type | | drift | 19 – 36 | 121.5 → 118, still moving, never held | | fill | 37 – 46 | 118 → 96.5, every cell populates, the count changes | Two details are worth knowing, because they are what make it read as film rather than as a transition: **One card lands late.** Every other card is sharp and in place by frame 5; the one behind the label is still a horizontal streak at 7 and only settles around 9. It is the difference between five separate things arriving and one picture being pushed in. **The settle never settles.** The pitch is still shedding a fifth of a pixel per frame at frame 36, right up to the moment the grid starts filling. A held value there reads as a freeze-frame; a value that is still moving reads as a camera. ## How the fill works Cells are addressed by column and row from the middle, and each one's arrival is ranked by its **Chebyshev distance** to the nearest of the five that were already there. Ring one is the eight cells touching them, ring two the eight touching those, and so on. Each ring starts a frame after the one inside it and takes a frame and a half, so the grid grows outward from the label instead of appearing all at once. The ramp straddles the switch frame rather than starting on it — a ring that is still at zero on the frame the count changes arrives visibly late. ## Props - `from` / `to` — the count before and after the fill. Strings, so `"5"` and `"500"`, or `"1"` and `"1k"`. - `noun` — what is being counted. It does not change when the number does. - `cards` — image URLs, cycled across the grid. Neighbours never share one. The five that carry the first half of the shot take from the front of the list, so put your strongest images there. Defaults to snapcn's own showcase frames and one still per component in the registry. - `inkFrom` / `inkTo` — the label's ink, left end and right end of a horizontal gradient across the line. Default to the theme's `primary`, darkened toward `foreground` on the left. - `background` — the page. Defaults to the theme's `background`. - `theme` / `mode` — a partial `SnapCnTheme` and `"light"` or `"dark"`. Cards and the label pill paint from `card` and `border`. - `speed` — `1` is the measured speed; `0.5` plays it half as fast. ## Notes Images go through Remotion's ``, so the render blocks until they load and no frame is captured with a half-drawn card. Pass them through `staticFile()` if they live in your project's public directory — a bundle serves that folder from its own path, and a hard-coded `/shots/01.jpg` is a 404 in the render while looking perfect in the browser. The grid draws whatever reaches the frame at the current pitch and no more, so the composition can be any aspect ratio; a taller frame simply shows more rows. --- # Cut Lockup URL: https://imgcli.com/docs/scenes/cut-lockup A logo reveal in hard cuts — huge on grid paper, three cuts down, a push to night where its construction lines draw themselves, then paper wipes up onto the lockup *(interactive preview: https://imgcli.com/docs/scenes/cut-lockup)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/cut-lockup.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage Cut Lockup paints its own background and runs 108 frames at 30fps. Every cut, scale, push and the wipe are measured frame by frame off a reference recording; each one is a prop with that measured value as its default. ```tsx import { CutLockup } from "@/components/imgcli/cut-lockup"; export const Intro = () => ; ``` Your logo goes in as SVG paths — paste the `d` of each `` from your logo's SVG (outline text first in Figma: _Outline stroke_ / _Flatten_). Each subpath is drawn as its own construction line, with a dot on every anchor point. ```tsx ``` ## Props --- # Gather Lockup URL: https://imgcli.com/docs/scenes/gather-lockup A wall of your work sits under a line of type. The line falls apart word by word, the wall cuts in to a row of four, and each slot swaps its picture and hands over to your mark and wordmark *(interactive preview: https://imgcli.com/docs/scenes/gather-lockup)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/gather-lockup.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage Gather Lockup paints its own background and runs 60 frames at 24fps, every pose measured off a reference recording. It cuts between held poses instead of easing, so keep it at 24fps. Pass fifteen pictures: seven for the wall, four for the row, four for the swaps. Tiles crop to fit, so portrait pictures work best. The title reads best at five words on three lines. ```tsx import { GatherLockup } from "@/components/imgcli/gather-lockup"; export const Outro = () => ( ); ``` --- # Product Hero URL: https://imgcli.com/docs/scenes/hero-launch Cinematic product-launch hero — two cards slide into formation as the headline reveals above *(interactive preview: https://imgcli.com/docs/scenes/hero-launch)* ## Overview A launch-style hero on one shared timeline: the first card fades and scales into the centre, eases left until about half of it remains visible, and a second card slides in from the right to meet it. Both cards then drop together as a group while the headline reveals above — fading up from below with a blur that resolves as the cards settle. Every beat overlaps the next, using high-damping springs (no overshoot) for a premium, non-robotic feel. Pass your own `heading` to change the headline. The default headline is “Introducing Imgcli” and the sample cards load two MP4 clips. Pass an image or video URL as `image1` or `image2` to replace either clip. --- # Moodboard Reveal URL: https://imgcli.com/docs/scenes/moodboard-reveal A kinetic headline, a scattered photo gallery, a camera push from dark to light, landing on a hero image *(interactive preview: https://imgcli.com/docs/scenes/moodboard-reveal)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/moodboard-reveal.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage A four-beat, camera-drives-through montage: 1. **Headline** — `` · an inline image that swaps · **``** · ``, on a near-black dotted field. 2. **Gallery** — the image cards fly in and scatter into a moodboard. 3. **Push** — the camera dollies through the gallery (with motion blur) while the background lifts from near-black to off-white. 4. **Hero** — it lands on a single image, held to the end, with thin wireframe markers drifting. Pass your own `images` (used for both the intro swap and the gallery) and a `heroImage`. Everything is frame-driven, so scrubbing and rendering are deterministic. ```tsx // src/Root.tsx import { Composition, staticFile } from "remotion"; import { MoodboardReveal } from "@/components/imgcli/moodboard-reveal"; const Reel = () => ( ); export const RemotionRoot = () => ( ); ``` Images take a root-relative `/…` path (served by Next in the Player, rewritten through `staticFile()` in a render) or an `http(s)`/data URL. Eight portrait images fill the gallery cleanly; fewer are tiled across the slots. This is a slow camera scale over the whole gallery, which a live `` mispaces on a high-refresh display — so the docs preview plays the **rendered mp4**. Re-render after any change: `pnpm run render:previews --only moodboard-reveal`. ## Props ", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: "\"light\" | \"dark\"", description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Orb Swarm URL: https://imgcli.com/docs/scenes/orb-swarm A sentence told one beat at a time while glass orbs burst out of it, split into eight and sixteen, wind into a vortex, pay out as a chain and loop around the last words before shrinking away bead by bead *(interactive preview: https://imgcli.com/docs/scenes/orb-swarm)* Four glass orbs burst out of the first line. They split into eight, then sixteen, and drift out while the sentence builds word by word underneath them. Then the sixteen wind into a clockwise vortex, collapse into a knot, pay out as a chain, and the chain reaches round the closing card and closes on itself — before shrinking away from its tail, one bead a beat. ## Everything is on the reference's clock The scene was measured off a 24fps recording, beat by beat, so every table in the file is indexed in **beats** (1/24 s) and read at fractional beats with the same curve. A 30fps composition samples between them and gets the motion the recording could not show, rather than a pulldown judder it did. Each phase is the smallest description that reproduces the recording: - **Burst and splits.** Every orb moves on a straight line from where it is born. The eight share one ease; so do the sixteen. The first four each have their own, and they are stored as such rather than averaged into a curve none of them follows. - **The vortex.** The sixteen turn about one point, faster every beat, and shrink toward it — and the orbs themselves shrink with it, a fifth as fast, so they read as falling away as well as in. - **The chain.** From the collapse onward there is no rule the recording follows closely enough to be worth a formula, so it is the measurement itself: sixteen beads a beat, position and size. While the chain is still a stack its tail lies on top; once it opens out, the head does. ## The orbs are drawn, not styled Each orb carries a band texture that scrolls down its face once every fourteen beats, and the bands bow toward the rim like latitude lines on a sphere. A CSS gradient cannot bend, so the orbs are shaded per pixel into a canvas with the same function the reference was fitted with — the band table, the bow, and a two-pixel soft edge with a faint dark ring inside it that is what reads as glass rather than paint. The type is drawn into the same canvas, four times finer than the frame and then reduced. That is not decoration: a browser snaps every glyph's baseline to a whole pixel, on the page and in a canvas alike, and the reference's baselines sit between them. ## The script `script` is six cards separated by `|`. The first, second and last arrive whole; the three in the middle reveal word by word on the reference's beats, and the newest word wears a few indigos and a violet, letter by letter, until the next one lands and it turns to ink. The first card leaves on a wave — its letters thrown alternately up and down as the line swells — and the second arrives on one, rising into place from below. ```tsx ``` Cards can hold as many words as you like; a card with more words than the reference's spreads its colours across them. ## Notes `softness` is how soft the type is, in card pixels of blur. The reference is a screen capture and its type is this soft; `0` is crisp. The orbs keep their own soft edge either way — it is part of how they are shaded. `background` and `ink` are the page and the settled type. `orbColor` turns the whole glass — its bands, its rim, and the tints the newest words wear — to one hue in OKLCH, keeping every lightness as measured, so any colour still reads as the same glass. All three default to the theme — `background`, `foreground`, `primary` — and on a dark page the word tints mirror their lightness so the newest words stay legible. `#9f77f8` is the reference's own violet. The card is the reference's own 714 × 392 pixels, scaled to cover whatever composition it is placed in, so a 16:9 frame crops a sliver off the top and bottom rather than letterboxing. ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/orb-swarm.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition } from "remotion"; import { OrbSwarm } from "@/components/imgcli/orb-swarm"; const Scene = () => ( ); export const RemotionRoot = () => ( ); ``` ## Props ", default: "—", description: "Token overrides; page, ink and orbs read from it", }, { name: "mode", type: '"light" | "dark"', default: '"light"', description: "Which token set to resolve", }, ]} /> --- # Orbit Gallery URL: https://imgcli.com/docs/scenes/orbit-gallery An image-universe hero — a stream of photos flows along an Archimedean spiral vortex from the frame edges into the center, evenly arc-spaced, shrinking and rotating along the coil, with the title held in the clear middle *(interactive preview: https://imgcli.com/docs/scenes/orbit-gallery)* The editorial "universe" opener: a continuous stream of photos flows along an **Archimedean spiral** — a vortex seen from the top. Cards enter big at the frame edges (the outer coils overflow and clip), then wind inward, shrinking and rotating to follow the spiral's tangent until they vanish at the center. Because the spiral's radius falls off linearly, every coil is separated by the **same gap** — that even "circle to circle" spacing — and cards are placed by **arc length**, so they stay evenly spread instead of bunching near the middle. The center stays clear for the title. ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/orbit-gallery.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition } from "remotion"; import { OrbitGallery } from "@/components/imgcli/orbit-gallery"; const Opener = () => ( ); export const RemotionRoot = () => ( ); ``` Works in any aspect ratio — the spiral radius derives from `min(width, height)`, so the vortex scales cleanly across portrait, square and landscape. ## How the vortex is built `archimedeanPoint(n, R, turns)` walks the spiral from the outer edge (`n = 0`, radius `R`) to the center (`n = 1`, radius 0) — a linear radius, so the coils are evenly spaced. `buildArcTable` / `arcToN` reparameterize the path by arc length so cards sit at even visual intervals, and `sizeScale` shrinks each card toward the middle. The stream then advances by whole card-steps per loop, so the inward flow is seamless. Cards are sorted by depth (center on top) and rotated to the spiral's tangent. ## Props - `images` — artwork URLs, cycled along the stream. A URL that fails to load falls back to a gradient tile (playback never breaks). Pass `[]` to use the built-in editorial gradient tiles (no network requests). - `turns` — coils from edge to center (default 3.5). - `spacing` — arc gap between cards; smaller packs them tighter (default 5). - `spread` — radius scale; larger pushes the outer coils further off-frame while keeping the coil gaps even (default 6). - `imageSize` — card long-edge in px on the reference 600px canvas, scaled to your frame (default 200); `sizeAttenuation` controls the center shrink. - `cornerRadius` — 0–20 rounding (default 5; `0` for perfectly square cards). - `orbitSeconds` — seconds for one full inward turnover (default 40). - `title` / `subtitle` / `buttonLabel` — the centered lockup; empty `title` hides it for a pure vortex. - `footerLeft` / `footerCenter` / `footerRight` — tiny bottom captions. Pairs well with a title opener as the establishing beat. --- # Portal Intro URL: https://imgcli.com/docs/scenes/portal-intro A sentence with a picture chip in it leans in until the chip takes the frame, then a title types over the picture and the mark arrives *(interactive preview: https://imgcli.com/docs/scenes/portal-intro)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/portal-intro.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage A three-beat intro, 108 frames at 30fps: 1. **Line** — the `lead` words fade up one at a time while the line drifts in. 2. **Chip** — the line re-sets with a rounded picture chip and the `trail`, then leans in until the chip takes the frame. 3. **Title** — `title` types itself over the picture, lifts away, and the mark and `brand` arrive. ```tsx import { Composition, staticFile } from "remotion"; import { PortalIntro } from "@/components/imgcli/portal-intro"; const Scene = () => ( ); export const RemotionRoot = () => ( ); ``` ## Props ", description: "Design-system token overrides" }, { name: "mode", type: "\"light\" | \"dark\"", description: "Which end of the design system to resolve tokens against" }, ]} /> --- # Reel Collage URL: https://imgcli.com/docs/scenes/reel-collage A photo reel between two captions shuts like an eyelid, the copy swaps, and a collage blooms in around the lines *(interactive preview: https://imgcli.com/docs/scenes/reel-collage)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/reel-collage.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage A three-beat scene, 2.6 seconds of motion then a hold: 1. **Reel** — eight shots cut between `topLine` and `bottomLine` while everything slowly zooms. 2. **Eyelid** — the frame shuts to a hairline and the captions fold into one stacked pair. 3. **Collage** — the copy swaps to `swapTop` / `swapBottom`, the lines split apart, and six pictures bloom in around them on a peach flush. Every curve was fitted on a reference recording's timestamps, so the timing is the reference's, frame for frame. ```tsx // src/Root.tsx import { Composition, staticFile } from "remotion"; import { ReelCollage } from "@/components/imgcli/reel-collage"; const Scene = () => ( ); export const RemotionRoot = () => ( ); ``` Images take a root-relative `/…` path (served by Next in the Player, rewritten through `staticFile()` in a render) or an `http(s)`/data URL. Each is cropped to cover its frame, so any aspect works. Fewer than eight shots or six pictures are repeated. The reel cuts every three to six frames and the captions scale, which a live `` mispaces — so the docs preview plays the **rendered mp4**. Re-render after any change: `pnpm run render:previews --only reel-collage`. ## Props ", description: "Design-system token overrides" }, { name: "mode", type: "\"light\" | \"dark\"", description: "Which end of the design system to resolve tokens against" }, ]} /> --- # Reel Wall URL: https://imgcli.com/docs/scenes/reel-wall A curved wall of vertical clips turns past in the dark — it eases in from rest, then keeps turning *(interactive preview: https://imgcli.com/docs/scenes/reel-wall)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/reel-wall.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage A ring of vertical clips on a 3D wall. It eases in from rest, moving on twos like the reference it was fitted from, then keeps turning at an even speed for as long as the composition runs — make the composition longer for a longer turn. ```tsx // src/Root.tsx import { Composition, staticFile } from "remotion"; import { ReelWall } from "@/components/imgcli/reel-wall"; const Scene = () => ( ); export const RemotionRoot = () => ( ); ``` Clips take a root-relative `/…` path or an `http(s)` URL, play muted, and repeat around the ring. Vertical 9:16 clips fit best; any aspect is cropped to cover. Pass clips at least as long as the composition — a shorter clip stops on its last frame. ## Props --- # Roster Grant URL: https://imgcli.com/docs/scenes/roster-grant Three rows of roster cards stream past a call to action, a pointer walks in and clicks it, and the monograms start turning to the accent one every seven frames *(interactive preview: https://imgcli.com/docs/scenes/roster-grant)* The "and everyone gets it" shot. The outer rows stream in opposite directions, the middle row parts around a single button, a pointer arrives and presses it, the button flips to the accent, and a beat later the roster starts lighting up — top row, middle, bottom, top, bottom, middle, one every seven frames, so it reads as a system granting rather than a list iterating. The camera and the middle row run off **one clock**. Fitted separately off the reference, the pull-back wanted a 0.75s exponential and the gap the middle row leaves around the button wanted a 0.75s exponential; two eases that agree are one ease. The outer rows deliberately do **not** share it — the bottom row spends a quarter of its travel in the first 0.3s and the top row does not, so the two sides are never symmetric about the button and the frame never settles into a pattern. ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/roster-grant.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition } from "remotion"; import { RosterGrant } from "@/components/imgcli/roster-grant"; const Grant = () => ( ); export const RemotionRoot = () => ( ); ``` ## Props | prop | type | default | what it does | | --- | --- | --- | --- | | `rows` | `RosterEntry[][]` | the snapcn registry | Three rows of `{ name, role, initials? }`. Each row repeats across the frame; four entries per row is enough that no row visibly tiles. | | `label` | `string` | `"Install all"` | The button's copy. The button is content-sized, and the pointer's landing point moves with its trailing edge. | | `accentColor` | `string` | `theme.primary` | The colour the button and the monograms turn. | | `theme` / `mode` | `Partial` / `"light" \| "dark"` | design system | Token overrides. | | `fontFamily` | `string` | Inter | A face from the registry's font list, or one you have loaded. | | `speed` | `number` | `1` | 1 is the measured speed. | ## Timing Every number is read off the reference at 30fps. | beat | seconds | what moves | | --- | --- | --- | | settle | 0 – 3.0 | camera 1.184 → 1, middle row closing on the button | | approach | 0 – 1.32 | pointer arrives, in two moves rather than one | | press | 1.62 – 2.33 | button 1 → 0.931 → 1 — down in 0.18s, back in 0.53s | | flip | 1.69 | button goes accent, on one frame, no crossfade | | leave | 2.05 – 3.1 | pointer out to the bottom right | | grant | 2.33 + 0.233n | one monogram per step | | push | 3.0 – 4.0 | the whole scene pans left, accelerating | The press is three times as long coming up as going down. Symmetric, it stops feeling like a click and starts feeling like a bounce. --- # Sky Lockup URL: https://imgcli.com/docs/scenes/sky-lockup A one-line promise takes itself apart word by word, the page washes off to a soft sky, and the mark in the middle of the sentence slides over and becomes your logo *(interactive preview: https://imgcli.com/docs/scenes/sky-lockup)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/sky-lockup.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage Sky Lockup paints its own background and runs 57 frames at 30fps, under two seconds, so it closes a video without slowing it down. Every curve is measured frame by frame off a reference recording. The words, the wordmark, your mark and the sky are all props. ```tsx import { SkyLockup } from "@/components/imgcli/sky-lockup"; export const Outro = () => ( ); ``` Your logo goes in as SVG paths, the same shape Cut Lockup takes. Any image works as the sky, and `mode="dark"` swaps the white page for your dark card. ```tsx ``` --- # Status Cycle URL: https://imgcli.com/docs/scenes/status-cycle A status pill whose label rolls behind a hard clip while its width springs past the target and back, then the field crossfades to a column of chips stepping up from below *(interactive preview: https://imgcli.com/docs/scenes/status-cycle)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/status-cycle.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage A prefix, a list of statuses and a list of chips. Everything else has a measured default. ```tsx // src/Root.tsx import { Composition } from "remotion"; import { StatusCycle } from "@/components/imgcli/status-cycle"; const Cycle = () => ( ); export const RemotionRoot = () => ( ); ``` `durationInFrames` has to cover the whole arc: the intro, one `statusHold` per status, the crossfade, and one `chipStagger` per chip. The default 198 fits the default content with a hold at the end; add 30 frames per extra status and 8 per extra chip. ## How the swap works Three things happen on the same frame, and keeping them on the same frame is most of the effect. **The label rolls.** The outgoing label translates up and out through the pill's top edge while the incoming one arrives from below, both hard-clipped by the pill's rounded box. There is no crossfade — the label's ink never dims — and the travel is one pill height, over 0.5s on `cubic-bezier(0.45, 0, 0.55, 1)`: slower than the reference's 8-frame roll, whose middle frame jumps a third of the pill and reads as a flicker at 30fps. **The pill's width springs past its target.** Not a spring, though: the curve is `cubic-bezier(0.65, 0.2, 0.3, 1.5)`, and the `y2 > 1` control value *is* the overshoot. A real spring cannot make this shape. One frame into an eleven-frame rise a spring released from rest is already 4.1% of the way there; the reference is at 0.65%, six times flatter, and no damping/stiffness pair fixes that without losing the early peak. **The line re-centres.** The lockup is centred as a whole, so a widening pill pushes the prefix left and a narrowing one lets it back. The prefix does not re-animate; it only moves. The label is laid out at its full final width and centred, and the widening pill reveals it from the middle outward — so during a morph you see the middle of the new label with both ends clipped. That is why the width is its own animated quantity and is never derived from a padded text box. ## Content of any length Every width in the scene is measured at runtime behind `delayRender()` — the prefix's glyph advances, each status label, each chip — so a longer status or a longer chip simply makes a wider box. Nothing is tuned to one sentence. Every other proportion is a fraction of the composition rather than a pixel, so the scene holds its shape at 720p, 1080p or a vertical crop. ## Colour Leave the colour props off and the scene paints from the design system: `primary` for the first field, `background` for the second, `foreground` for the pill and the ink, and a pale tint of the field for the pill's label. Pass any of them to override. ```tsx ``` ## Overriding the motion Every curve and duration is a measured default on the exported `STATUS_CYCLE_MOTION`, and `motion` is merged over it — so you override one without restating the rest. ```tsx import { Easing } from "remotion"; ``` ## Props ", default: "STATUS_CYCLE_MOTION", description: "Curve and timing overrides: widthEase, widthSeconds, rollEase, rollSeconds, rollTravel, fadeSeconds, exitSeconds, scrollTau, letterSpring, letterStagger. Every default but the roll is fitted from frame data, so overriding one is a departure rather than a tweak", }, { name: "fontSize", type: "number", default: "8.7% of the composition height", description: "Act-1 type size in px", }, { name: "chipFontSize", type: "number", default: "22.2% of the composition height", description: "Act-2 type size in px", }, { name: "fontFamily", type: "string", default: "Inter", description: "Loaded through @remotion/google-fonts, so the Player and the render agree", }, { name: "introFrames", type: "number", default: "24", description: "Frames the per-letter intro cascade gets before the first status swap", }, { name: "statusHold", type: "number", default: "30", description: "Frames from one status swap to the next: the 15-frame roll, then 15 frames to read the label. Keep it above the roll, or every label is read mid-flight", }, { name: "chipStagger", type: "number", default: "8", description: "Frames from one chip's arrival to the next", }, { name: "startAt", type: "number", default: "0", description: "Frame the scene starts on", }, { name: "speed", type: "number", default: "1", description: "Playback rate. Below 1 the last chip never arrives", }, { name: "theme", type: "Partial", description: "Design-system token overrides", }, { name: "mode", type: '"light" | "dark"', description: "Which side of the token set to resolve", }, ]} /> --- # Wordmark Cut URL: https://imgcli.com/docs/scenes/wordmark-cut A wordmark twice the width of the frame, cooling from a lit gradient to ink, cut closer onto its last letters and the dot *(interactive preview: https://imgcli.com/docs/scenes/wordmark-cut)* Two shots and a hard cut between them. The wide one holds the front of the word, rides an exponential settle up into place while the light on the type cools out, and then starts drifting left — accelerating, so the cut lands on a frame that is already moving. The close one is 1.71× nearer, holds the **end** of the word, springs in, waits, and pushes. The dot never cools. It is the one thing in the piece that keeps the gradient at full strength, and the close shot is framed on it. ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/wordmark-cut.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition } from "remotion"; import { WordmarkCut } from "@/components/imgcli/wordmark-cut"; const Open = () => ; export const RemotionRoot = () => ( ); ``` ## Props | prop | type | default | what it does | | --- | --- | --- | --- | | `word` | `string` | `"snapcn."` | The wordmark. A trailing `.` is dropped — the dot is drawn, not typed. | | `dotFrom` / `dotTo` | `string` | `#3072db` / `#4fd8f5` | One ramp, painted at full strength on the dot and at `warm` on the type. Defaults to the design system's `primary` lifted to a cyan; the reference's own is `#ff6100 → #fff100`. | | `warm` | `number` | `1` | How much light the type carries at its hottest. `0` starts it at ink. | | `theme` / `mode` | `Partial` / `"light" \| "dark"` | design system | Token overrides — the page and the ink. | | `fontFamily` | `string` | Inter | A face from the registry's font list, or one you have loaded. | | `speed` | `number` | `1` | 1 is the measured speed. | ## Framing The two shots are anchored on different things, and neither has a fixed baseline: - The **wide** shot is framed on the *top of the ink*. A word with ascenders and a word without do not sit in the same place at the same point size — one of them floats 50px lower — so the ink's top edge is what stays put and the baseline follows. - The **close** shot is framed on the *dot*, because that is what it is a shot of. The baseline is then wherever the dot's own radius puts it. Set `word="Product."` and both come out at the reference's own numbers. ## Timing Every number is read off the reference at 30fps. | beat | seconds | what moves | | --- | --- | --- | | settle | 0 – 0.8 | the word rises 50px, exponentially | | cool | 0.36 – 1.03 | the light on the type goes to ink, linearly | | drift | 0.66 – 1.20 | the word slides 21px left, accelerating | | **cut** | 1.20 | 245px type → 419px type, front of the word → end of it | | spring | 1.20 – 1.45 | the close shot lands, overshooting 5px | | hold | 1.45 – 1.63 | nothing moves | | push | 1.63 – 2.20 | 1 → 1.30, still accelerating at the last frame | The wide shot is *accelerating* left when it ends and the close shot is *decelerating* when it begins. They are not one continuous move with a jump in it — they are the same camera on the same word, twice, and the cut is what makes the second one read as closer rather than as bigger. --- # Transitions URL: https://imgcli.com/docs/transitions Effects that carry one shot into the next — lay them over a cut. This is the category for the cut itself. A transition paints only its own effect, so it sits on top of both shots: cut between them underneath it, at the frame it covers the screen. --- # Dot Bloom URL: https://imgcli.com/docs/transitions/dot-bloom A halftone transition — the outgoing shot washes to cream, breaks into warm drifting dots, and the dots thin out onto the incoming shot *(interactive preview: https://imgcli.com/docs/transitions/dot-bloom)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/dot-bloom.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage Dot Bloom paints only the transition. Lay it over both shots and cut between them underneath it at `DOT_BLOOM_CUT`, the frame where the cream covers the whole screen. ```tsx import { AbsoluteFill, Sequence } from "remotion"; import { DOT_BLOOM_CUT, DotBloom } from "@/components/imgcli/dot-bloom"; const AT = 60; // where the transition starts export const Cut = () => ( ); ``` ## Props --- # Remotion Studio URL: https://imgcli.com/docs/remotion-studio Add an Imgcli component to your Remotion project in one click: Remotion Studio on your computer installs it and puts it on your timeline. No CLI needed. Supported components show an "Add to Remotion Studio" button. It sends the component over the Remotion Studio Protocol to the Studio running on the reader's computer (Remotion 4.0.524+, started with `npx remotion studio`, a composition open). Studio shows the source, and on confirmation writes `src/.element.tsx`, installs the packages it imports at exact versions, and places it in the open composition as one layer: transparent, sized to the component, text and colours in the Inspector. For automated installs, use `npx shadcn@latest add https://imgcli.com/r/.json`. Studio requires a person to confirm the installation. --- # Video Editor URL: https://imgcli.com/docs/video-editor Compose a video from Imgcli components — add clips, edit text and images, and export an MP4. A browser video editor at https://imgcli.com/docs/video-editor. Open the component catalog to find installable components for your own Remotion project. - Add clips from the registry to a timeline, edit their text, images and colours, reorder them and set each one's length. - Add a soundtrack, and trim where it starts. - Export an MP4, rendered server-side with Remotion: 1280×720 at 30fps, up to 60 clips and 180 seconds in total. - The web editor can apply a watermark to exports. Installed components render in your own Remotion project. - Sign in to save and reopen projects and to publish a share link for a video you export while signed in. Anyone with the link can watch the exported video. The project stays private. --- # Changelog URL: https://imgcli.com/docs/changelog Remotion component releases in Imgcli, including animations, captions, logo stings, and scenes. Every component, by the day it landed. Newest first. Also available as a feed: https://imgcli.com/docs/changelog/rss.xml - **2026-11-12** — Brand Wheel - **2026-11-05** — Bloom Lockup, Gather Lockup, Sky Lockup - **2026-11-04** — Hello Zoom, Spec Sweep, Drop Line, Title Sweep - **2026-11-03** — Cut Lockup - **2026-10-24** — Phone Stack, Reel Wall, Portal Intro, Dot Bloom - **2026-10-21** — Click Type, Button Press - **2026-10-08** — Font Shuffle - **2026-10-06** — Capture Reveal - **2026-09-30** — Check Cycle - **2026-09-25** — Orb Swarm - **2026-09-23** — Reel Collage - **2026-09-12** — Card Rail, Logo Collapse, Channel Thread, Word Wheel - **2026-09-11** — Word Gather - **2026-09-07** — Roster Grant, Wordmark Cut - **2026-09-06** — Count Grid - **2026-09-03** — Punch Lines - **2026-08-29** — Screen Recording, Cursor Track - **2026-08-28** — Answer Highlight, Agent Steps - **2026-08-26** — Text Rewrite, Text Select, Logo Drift, Prompt Send - **2026-08-25** — Type Morph - **2026-08-23** — Block Wordmark - **2026-08-20** — Announce Title, Status Cycle - **2026-07-28** — Answer Stream - **2026-07-26** — Prompt Zoom - **2026-07-24** — Text Reveal, Text Swell, Text Highlight, Text Swap, Text Build, Word Flip, Word Captions, Karaoke Captions, Logo Assemble, Logo Flicker, Phone Frame, Laptop Frame, Terminal Simulator, Follower Rush, Product Hero, Orbit Gallery, Moodboard Reveal, Search Typing --- # Roadmap URL: https://imgcli.com/docs/roadmap See what Imgcli supports today and what's next for the Remotion component library. See what Imgcli supports today and what's next for the Remotion component library. ## Shipped - **The component registry**: 61 free Remotion components for text, captions, logos, screens and complete scenes. Install from the Imgcli registry with npx shadcn@latest add https://imgcli.com/r/text-reveal.json. The @imgcli alias requires a registry mapping in components.json. https://imgcli.com/docs/getting-started/installation - **snap-cn-ui primitives**: Timeline-driven versions of the shadcn atoms — input, caret, and the theme and colour core the scenes paint from — so a component drops in next to your own Input and Button and belongs there. https://imgcli.com/docs/getting-started/installation - **Documentation for coding agents**: Use the component index to choose a scene, then follow its docs for the install command, props and examples. https://imgcli.com/docs/getting-started/agent-skill - **Template previews**: Preview complete Remotion videos for launches, product demos and changelogs. New purchases are paused. https://imgcli.com/docs/templates - **Video editor**: Build a timeline from Imgcli components, edit text and images, add audio and export an MP4 in the browser. Sign in to save and reopen projects and share an exported video. https://imgcli.com/docs/video-editor ## Next - **Component improvements**: Refine existing scenes and verify their defaults in standalone Remotion projects. The changelog tracks releases. https://imgcli.com/docs/changelog --- # How to make a demo video for an API or developer tool URL: https://imgcli.com/api-demo-video An API has no screen to record. Show the request and the result instead — a terminal, the call, the output, one number — built in React with Remotion so it stays sharp and editable. **Short answer:** an API has no UI to record, so show the **request and the result**. Four beats: the outcome in one line, the install or the call typed in a terminal, the output arriving, and one number or command to end on. Build it in React with [Remotion](https://www.remotion.dev/docs) so every character on screen is real, sharp text you can edit. This post is about the *developer-tool* version. For a product with a UI, see [how to make a product demo video in React](/product-demo-video-react). ## Why API demos are hard A dashboard demo shows itself: click, something changes. An API demo is a `curl` and a JSON blob, and a recording of that is: - **Too small.** A terminal at 13px on a 1080p capture is unreadable on a phone. - **Noisy.** Your prompt, your hostname, three warnings, a scroll. - **Frozen.** When the output format changes, you re-record. Rebuilt as components, the terminal types exactly the lines you give it, at a size you choose, and a new output is a new string. ## The four beats | Beat | Length | On screen | | --- | --- | --- | | Outcome | 2–3s | What the developer gets, in one line | | Call | 5–8s | The install or the request, typed | | Result | 5–10s | The output arriving — trimmed to what matters | | Close | 2–3s | One number, or the install command again | Trim the output hard. Nobody reads a 40-line JSON response in a video — show the three lines that prove it worked. ## The call and the result: a terminal [Terminal Simulator](/docs/screens/terminal-simulator) is one camera flying over one canvas: a headline, an install panel with package-manager tabs, then a focus terminal that types its output in bursts. Any line ending in `...` holds the frame, which is exactly the beat where a request is in flight: ```tsx import { TerminalSimulator } from "@/components/imgcli/terminal-simulator"; export const CallScene = () => ( ); ``` Pass `intro={null}` or `command={null}` to skip a station — the camera closes the gap. *(interactive preview: https://imgcli.com/api-demo-video)* ## For an agent or AI API: show the work If what you sell is an agent, the interesting part is not the request — it is what happens between the request and the answer. [Agent Steps](/docs/ai-input/agent-steps) writes that log one line at a time and ends on the result: ```tsx import { AgentSteps } from "@/components/imgcli/agent-steps"; export const WorkScene = () => ( ); ``` Write the last step in the present tense and put the number in `result` — the last step never finishes; it turns into the result chip. Then [Answer Stream](/docs/ai-input/answer-stream) for the reply itself, if your API answers in prose. ## Outcome and close: type, not logos Open and close with a line of text, not your logo. [Text Reveal](/docs/text/text-reveal) or [Punch Lines](/docs/text/punch-lines) carry a single sentence with enough motion to hold two seconds. More options in [how to animate text in Remotion](/remotion-text-animations). ## Put it together ```tsx import { AbsoluteFill, Series } from "remotion"; import { TextReveal } from "@/components/imgcli/text-reveal"; export const ApiDemo = () => ( ); ``` ```bash npx shadcn@latest add https://imgcli.com/r/terminal-simulator.json https://imgcli.com/r/agent-steps.json https://imgcli.com/r/text-reveal.json npx remotion render ApiDemo out/api-demo.mp4 ``` Posting it in a README too? Render the same composition as a GIF — see [how to make a GitHub README demo GIF](/github-readme-demo-gif). Next: [terminal and code-typing animation in Remotion](/remotion-terminal-animation). --- # App Store and Google Play preview videos, made in code URL: https://imgcli.com/app-store-preview-video Build App Store app previews and Google Play preview videos in React with Remotion — Apple's exact specs, what Apple allows on screen, what Google Play requires, and one composition that renders both. **Short answer:** for the **App Store**, render a **15–30 second** screen capture of the app at **886×1920** (iPhone) or **1200×1600** (iPad), **30fps** max, with text overlays — Apple does not allow device frames or footage that is not the app. For **Google Play**, upload to **YouTube** (ads off) and link it; any orientation works, and only the first 30 seconds autoplay. One [Remotion](https://www.remotion.dev/docs) composition can render both. Every number below is quoted from Apple's and Google's own pages as of September 2026. Check them again before you upload — both change. ## Apple's specs From App Store Connect's app preview specifications: | | | | --- | --- | | Length | 15–30 seconds | | Frame rate | Up to 30fps | | File size | Up to 500MB | | Formats | H.264 (.mov, .m4v, .mp4), 10–12 Mbps target; or ProRes 422 HQ (.mov) | | Audio | Stereo; 256kbps AAC at 44.1 or 48kHz for H.264 | | Per localization | Up to 3 previews | | iPhone 6.9" and 6.5" | 886×1920 portrait, 1920×886 landscape | | iPad 11" and 13" | 1200×1600 portrait, 1600×1200 landscape | ## What Apple allows on screen This is the rule that decides the design. App Review Guideline 2.3.4: > previews may only use video screen captures of the app itself. […] You can add > narration and video or textual overlays to help explain anything that isn't > clear from the video alone. So: **no phone mockup, no lifestyle footage, no logo animation that is not in the app.** A full-frame capture of the app, with text on top. What you *can* control is the camera and the words. ## Google Play's requirements From Play Console Help: use a **YouTube video URL** (not a playlist or channel), **turn ads off**, and don't age-restrict it. Landscape and portrait are both supported. Only the **first 30 seconds autoplay**, and Google advises that at least 80% of the video should show the actual user experience. Because it is a YouTube video rather than an App Store upload, Google Play is where a device-frame hero shot can go — see below. ## The App Store composition A capture from the simulator or a device, cropped to fit, with a camera that pushes in on each feature. [Screen Recording](/docs/screens/screen-recording) does the crop and the camera; the camera only moves *your app's pixels*, so it stays inside 2.3.4: ```tsx import { AbsoluteFill, Composition, Sequence } from "remotion"; import { ScreenRecording } from "@/components/imgcli/screen-recording"; const Caption = ({ text }: { text: string }) => (

{text}

); const AppPreview = () => ( ); export const RemotionRoot = () => ( ); ``` ```bash npx remotion render AppPreview out/app-preview-iphone.mp4 ``` For iPad, register a second `` at `1200×1600` with the iPad capture. Same components, different `width`, `height` and `src`. If your capture already matches 886×1920 there is nothing to crop. If it is a different aspect, give Screen Recording `sourceAspect` (width ÷ height of the file) so the crop and fit are exact. ## The Google Play video: here a device frame fits On YouTube you are not bound by 2.3.4, so the opening shot can be the app inside a phone. [Phone Frame](/docs/screens/phone-frame) takes the same capture as `screenSrc` and brings the device in with a crane move: ```tsx import { staticFile } from "remotion"; import { PhoneFrame } from "@/components/imgcli/phone-frame"; export const PlayHero = () => ( ); ``` Open on that for a few seconds, then cut to the full-frame capture — Google's guidance is that most of the video should be the real experience. *(interactive preview: https://imgcli.com/app-store-preview-video)* ## Install ```bash npx shadcn@latest add https://imgcli.com/r/screen-recording.json https://imgcli.com/r/phone-frame.json ``` More on devices in [how to add an iPhone mockup to a Remotion video](/remotion-iphone-mockup). Next: [product tour and onboarding videos in React](/onboarding-video-react). --- # The best tools for making a product demo video in 2026 URL: https://imgcli.com/best-product-demo-video-tools Screen recorders, interactive demo builders and code-based video — Screen Studio, Tella, Loom, Cap, Arcade, Supademo, Storylane and Remotion compared on output, platform and price. **Short answer:** for a polished screen recording on a Mac, **Screen Studio**; on Mac or Windows, **Tella** or **Cap**; for a quick async walkthrough, **Loom**. If you want people to click through the product rather than watch it, use an interactive demo tool — **Arcade**, **Supademo** or **Storylane**. If you want the video to live in your repo and re-render when the product changes, build it in code with **Remotion**. Prices and platforms below were checked on each tool's own site in October 2026. They change often, so confirm on the pricing page before you buy. ## At a glance | Tool | Makes | Platform | Price | Best for | | --- | --- | --- | --- | --- | | [Screen Studio](https://screen.studio) | Screen recording with auto zooms | macOS only | $29/month, or $9/month billed yearly | Polished Mac recordings | | [Tella](https://www.tella.com) | Screen + camera recording, editor | Mac, Windows, browser, Chrome extension | From $13/user/month billed annually | Edited walkthroughs with a camera | | [Loom](https://www.loom.com) | Async video messages | Mac, Windows, iOS, Android | Free plan; Business $18/user/month | Fast, shareable explanations | | [Cap](https://cap.so) | Open-source screen recorder | macOS, Windows, Linux | Free plan; Desktop $29/year or $58 lifetime | A free or open-source recorder | | [Arcade](https://www.arcade.software) | Interactive demos (plus AI video) | Chrome extension, desktop app, Figma plugin | Free plan; Pro from $49/month billed annually | Clickable demos that also export MP4/GIF | | [Supademo](https://supademo.com) | Interactive demos | Chrome extension, Mac and Windows apps | Free plan; Scale $38/month billed annually | Self-serve demos for marketing and onboarding | | [Storylane](https://www.storylane.io) | Interactive and sandbox demos | Web | Free plan; Starter $50/month | Sales-led teams | | [Remotion](https://www.remotion.dev) | Videos rendered from React | Any OS with Node | Free for individuals and companies up to 3 employees | Videos that live in code | ## Screen recorders ### Screen Studio Screen Studio calls itself "the screen recorder for Mac that handles the hard parts" — zooms, motion, sound and framing — so a raw recording comes out looking edited. It is macOS only; its FAQ says there are no near-future plans for Windows. Pricing is $29 a month, or $9 a month billed yearly, and every feature is in every plan. **Best for:** a Mac user who wants a polished recording without opening an editor. If you are on Windows, see our [Screen Studio alternatives](/screen-studio-alternatives). ### Tella Tella records your screen, camera, microphone and system audio as separate tracks, so you can change the layout after recording, and its editor handles trimming, layouts, zooms, captions and sound cleanup. It runs on Mac and Windows, in the browser, and as a Chrome extension. The pricing page lists Pro at $13 per user per month and Premium at $19, both billed annually; there is no free plan, though the FAQ mentions a free trial. **Best for:** a walkthrough with you on camera that needs light editing. ### Loom Loom is for recording and sharing video messages with teammates and customers. It records your screen and camera, on Mac, Windows, iOS and Android. The free Starter plan allows 25 videos of up to five minutes each, at up to 720p. Business is $18 per user per month with unlimited videos and recording time, and Business + AI is $24. **Best for:** a quick explanation sent to one person or a team, not a launch video. ### Cap Cap is an open-source screen recorder. Most of the code is AGPLv3, according to its GitHub repository. The download page offers macOS (Apple Silicon and Intel), Windows and Linux builds. The free plan is for personal use; commercial use needs a paid plan. The Desktop License is $29 a year or $58 once for lifetime, and Cap Pro, which adds cloud sharing and AI titles and transcripts, is $12 per user per month or $8.16 billed annually. **Best for:** a low-cost or open-source recorder, including on Windows and Linux. ## Interactive demo tools These capture your product as clickable steps rather than a video. We cover when that is the right format in [interactive demo vs video demo](/interactive-demo-vs-video-demo). ### Arcade Arcade records with a Chrome extension, a desktop app or a Figma plugin, then lets you add hotspots, callouts, voiceover and branching. Demos embed on websites, help centres and inside your product, and any demo can be exported as an MP4, a GIF or a step-by-step guide. Arcade's homepage now leads with an AI video generator for marketing teams, and its pricing page lists credit-based plans: a free plan and Pro from $49 a month billed annually. **Best for:** a clickable demo that you also want as a video or GIF. ### Supademo Supademo captures demos with a Chrome extension or Mac and Windows desktop apps. Its site lists website, sales and onboarding use cases, and its FAQ says demos export as GIF, MP4 and PDF. The free plan includes five interactive demos for one creator. Scale is $50 a month, or $38 a month billed annually, per creator. **Best for:** a small team that wants interactive demos on a free or low tier. ### Storylane Storylane offers interactive demos, sandbox demos ("a pixel-perfect clone of the product") and Hubs, a digital sales room. The free plan allows one published demo at a time. Starter is listed at $50 a month, with a 20% discount for yearly billing; Growth and Premium are priced for sales teams. **Best for:** a sales-led company that wants demos inside a sales process. ## Code-based: Remotion and component libraries [Remotion](https://www.remotion.dev) describes itself as a way to "create videos and motion graphics with React". Each scene is a React component, you preview it in Remotion Studio, and `npx remotion render` writes the file (H.264 is the default codec). Remotion's license is free for individuals, for-profit companies with up to three employees, and non-profits; larger companies need a company license. What you get is a demo you can edit like code: change a string, re-render, and the video matches the new UI. What you pay is setup time and the motion design itself, which is where component libraries come in. We compare them in [the best Remotion component libraries](/best-remotion-component-libraries). [Imgcli](/docs/components) is ours, so weigh this paragraph accordingly. It is a shadcn registry of Remotion components built for software videos — a [screen recording with a camera move](/docs/screens/screen-recording), a [cursor track](/docs/screens/cursor-track), a [prompt being typed](/docs/ai-input/prompt-send), a [laptop frame](/docs/screens/laptop-frame). You install one with `npx shadcn@latest add https://imgcli.com/r/.json` and the source is copied into your project. The [free catalog](/docs/components) lists the MIT-licensed components. Use the [browser video editor](/docs/video-editor) to assemble scenes and export MP4 without a Remotion project. Sign in to save projects and share your exports. **Best for:** developers who want the demo video in their repo, re-rendered as the product changes. Walkthroughs: [product demo video in React](/product-demo-video-react) and [a SaaS demo video without After Effects](/saas-demo-video-without-after-effects). ## How to choose 1. **Do viewers need to click through the product?** Use an interactive demo tool. 2. **Are you recording real UI on a Mac and want it polished?** Screen Studio. 3. **On Windows or Linux, or want it free?** Cap; Tella if you want an editor with a camera. 4. **Is it a quick note to a teammate or customer?** Loom. 5. **Will the video be re-made every release, or does it need motion graphics?** Remotion, with prebuilt components. The categories mix well: record the product in Screen Studio or Cap, then bring the clip into a Remotion composition for the titles and the cut. For launch specifics see [the Product Hunt launch video](/product-hunt-launch-video), and for a README see [the GitHub demo GIF](/github-readme-demo-gif). ## Sources - https://screen.studio/ - https://www.tella.com/ - https://www.tella.com/pricing - https://www.loom.com/ - https://www.loom.com/pricing - https://cap.so/pricing - https://cap.so/download - https://github.com/CapSoftware/Cap - https://www.arcade.software/ - https://www.arcade.software/product/interactive-demo - https://www.arcade.software/pricing - https://supademo.com/ - https://supademo.com/pricing - https://www.storylane.io/ - https://www.storylane.io/pricing - https://www.remotion.dev/ - https://www.remotion.dev/docs/render - https://www.remotion.dev/docs/encoding - https://github.com/remotion-dev/remotion/blob/main/LICENSE.md --- # The best Remotion component libraries in 2026 URL: https://imgcli.com/best-remotion-component-libraries Every Remotion component library worth knowing in 2026 — Remotion Elements, remocn, RemotionUI, Imgcli and React Video Editor — what each is for, how it installs and what it costs. **Short answer:** start with **Remotion Elements** (official, drops into Studio), reach for **remocn** or **RemotionUI** when you need breadth, use **Imgcli** when the video is a product demo, and buy **React Video Editor** only if you are building an editor into your own app. All but the last copy source into your project, so you can mix them. Third-party catalog figures reflect the original September 2026 comparison. ## At a glance | Library | Size | Price | Installs with | Best for | | --- | --- | --- | --- | --- | | [Remotion Elements](https://www.remotion.dev/elements) | ~40 | See Remotion's license | Remotion Studio | Official building blocks: captions, charts, backgrounds, maps | | [remocn](https://remocn.dev) | ~308 | Free, MIT | shadcn CLI | Breadth: typography, shaders, transitions, icons | | [RemotionUI](https://remotionui.com) | ~210 | Free | `npx remotion-ui add` | Scenes, transitions, data, device frames | | [Imgcli](/docs/components) | See the catalog | MIT for free components | shadcn CLI | Product demo shots | | [React Video Editor](https://www.reactvideoeditor.com) | Full editor | From $149 one-time | Source template or SDK | Putting a video editor inside your app | ## Remotion Elements The official catalogue from the Remotion team, at remotion.dev/elements. Around forty elements across audio, backgrounds, captions, layouts, charts, maps, overlays, storytelling, text effects and YouTube. The distinguishing feature is the Studio integration: install an element and it appears on the canvas you have open. Start here — it is the set most likely to track Remotion's own API. ## remocn The widest free library: roughly 300 components, MIT, no paid tier. Categories include typography, shaders, transitions, animated icons, UI primitives, filters, effects and layout. It is the natural pick when you need a transition or a shader background. See the original comparison in [upstream snapcn vs remocn](/snapcn-vs-remocn). ## RemotionUI Around 210 components with its own CLI (`npx remotion-ui@latest init` to start, `npx remotion-ui add ` per component), copying source into your repo. Covers compositions, scenes, transitions, data and media, maps and devices, and shapes. ## Imgcli Our library, so read this one with that in mind. Imgcli is narrower on purpose: its components are the shots a software video is made of — a [prompt typed and sent](/docs/ai-input/prompt-send), an [answer streaming in](/docs/ai-input/answer-stream), a [terminal](/docs/screens/terminal-simulator), a [screen recording with a camera](/docs/screens/screen-recording), a [phone](/docs/screens/phone-frame) or [laptop](/docs/screens/laptop-frame), [captions](/docs/captions) and [logo stings](/docs/logos). Components take shadcn theme tokens, text is tuned against render judder, and there is an [MCP server](/docs/mcp) for coding agents. *(interactive preview: https://imgcli.com/best-remotion-component-libraries)* ## React Video Editor Not a component library: a complete video editor built on Remotion, sold as source code (one-time, from $149) or as a managed SDK. Timeline, captions, effects, uploads. Buy it if your product needs an editor; skip it if you are making one video. ## How to choose 1. **Is the video about your software?** Start with Imgcli's demo shots and fill gaps from the others. 2. **Do you need transitions, shaders or backgrounds?** remocn or RemotionUI. 3. **Do you edit in Remotion Studio?** Remotion Elements first. 4. **Are your users making videos?** React Video Editor. They do not conflict. Components that copy source into your project are just React files — install from two libraries and use them in one ``. Next: [how to make a product demo video in React](/product-demo-video-react). --- # Free and paid Remotion templates in 2026 URL: https://imgcli.com/best-remotion-templates Every official create-video template, what the Remotion Store sells and for how much, and the third-party template collections listed in Remotion's docs. **Short answer:** start with the free official templates from `npx create-video@latest`. Pick **Hello World** for a first project, **Next.js** or **React Router** for a video-generation SaaS, or **Render Server** / **Next.js (Vercel Sandbox)** if you need server rendering. Pay for a Remotion Store item only if you are building an editor (**Editor Starter**, **Timeline**). For ready-made scenes, Remotion's docs link free MIT collections (**SwiftClip**, **RenderComp Free**, **cliphou.se**) and a paid one (**Lexington Motion**). Everything below was checked in October 2026, on remotion.dev, the Remotion Store, the create-video source on GitHub and each third-party site. Prices are as published that day. ## At a glance | Source | What you get | Price (as published) | License | | --- | --- | --- | --- | | [create-video templates](https://www.remotion.dev/templates) | 20 official starters | Free | Remotion license | | [Remotion Store](https://www.remotion.pro/store) | Editor Starter, Timeline, Cube Transition, Colors and Shapes | $10 to $600 per item | Remotion terms | | [SwiftClip](https://github.com/zz41354899/SwiftClip) | 30+ composition templates | Free | MIT | | [RenderComp Free](https://rendercomp.com/free) | 50 templates | Free; full catalogue $149 | MIT | | [cliphou.se](https://cliphou.se/remotion-templates/) | Editable templates, source download | Free | MIT-0 | | [Lexington Motion](https://lexingtonthemes.com/remotion) | 415 templates | $99 one payment | Commercial license | Whichever you pick, Remotion's own license still applies to Remotion. It is free for individuals, for-profit organisations with up to 3 employees and non-profits. Bigger companies need a Company License (see [License & Pricing](https://www.remotion.dev/docs/license/pricing)). ## Official templates: `npx create-video@latest` The wizard asks for a package manager, a template, and whether to install Agent Skills. The docs recommend Hello World for a first project. To skip the questions: ```bash npx create-video@latest --yes --blank my-video ``` That command is from the getting-started page. It creates a blank project with Agent Skills and no Tailwind. The list below is the `FEATURED_TEMPLATES` array in `packages/create-video/src/templates.ts`, and it matches the "Free templates" section on remotion.dev/templates: | Template | Description from the source | | --- | --- | | Blank | Nothing except an empty canvas | | Hello World | A playground with a simple animation | | Next.js | SaaS template for video generation apps | | Next.js (Vercel Sandbox) | Render videos on-demand using Vercel Sandbox | | Recorder | A video production tool built entirely in JavaScript | | Prompt to Motion Graphics SaaS Starter Kit | SaaS template for AI-powered animation generation | | Render Server | An Express.js server for rendering videos with Remotion | | Electron | Render Remotion videos from a desktop app | | React Router | SaaS template for video generation apps | | Element Library | Build and publish your own Remotion Element library | | React Three Fiber | Remotion + React Three Fiber starter template | | Still images | Dynamic PNG/JPEG template with built-in server | | Audiogram | Text and waveform visualization for podcasts | | Music Visualization | Music visualization starter | | Prompt to Video | Create a story with images and voiceover from a prompt | | Skia | React Native Skia starter | | Overlay | Overlays for video editing software | | Code Hike | Beautiful code animations | | Stargazer | Celebrate your repo stars with a video | | TikTok | Generate animated word-by-word captions | Most are maintained under the `remotion-dev` GitHub organisation. Stargazer is the exception: its repository is `pomber/stargazer`. Blank, Hello World, Audiogram, Music Visualization, Prompt to Video, Overlay, Stargazer and TikTok let the CLI add Tailwind. (Music Visualization's description in the source reuses Audiogram's text, so the table describes it by name.) The same file has a separate `PAID_TEMPLATES` list. It holds one entry, Editor Starter, which links to remotion.pro. ## The Remotion Store The store at remotion.pro/store listed four products: | Item | Price | What it is | | --- | --- | --- | | Editor Starter | $600 | A starting point for building your own video editor. GitHub repository, copy-and-pasteable. Marked as an Enterprise license perk | | Timeline | $300 | A `` component that syncs with the Remotion Player, shipped in a Vite example project. Built with Tailwind CSS and React | | Cube Transition | $10 | A 3D cube presentation for `` from `@remotion/transitions`, also available as an npm package | | Colors and Shapes | $20 | Marked as a "License perk" on the store listing. Its product page did not load when we checked | The templates page also lists **Watercolor Map** under paid templates. Its store page now says "This product is no longer available for purchase." On "license perk": Remotion's pricing page lists "Editor Starter included" in the Enterprise License (starting at $500 per month). The Editor Starter page marks it as included with the Enterprise license, not the Company license. If your company is already on Enterprise, check your perks before you buy. ## Third-party template collections These are the template collections linked from Remotion's [List of resources](https://www.remotion.dev/docs/resources). We checked each one ourselves. **SwiftClip.** MIT, open source, 45 GitHub stars. The README describes it as "an open-source library of 30 copy-paste video composition templates". Its feature list says 32: Product Launch, SaaS Promo, Data Viz, Lower Third, Testimonial Card, End Screen and more. They are built on `useCurrentFrame()`, `interpolate()` and `spring()`, with Remotion, React and lucide-react as the only dependencies. **RenderComp Free.** 50 templates under MIT. The page says you can use them in commercial projects as long as you keep the MIT notice. Full source is on GitHub with no signup. RenderComp sells the full catalogue (1,000+) as "RenderComp Solo" for $149, "pay once, lifetime updates". **cliphou.se.** Templates under MIT-0. You can edit text and colours in the browser, download MP4s, or download a standalone Remotion source package (`npm ci`, then render with your props). The page says "100% free", with no watermark or attribution required. **Lexington Motion.** Paid: "415 video templates for reels, social, web, charts and more", $99 for all of them, one payment, with a commercial license and unlimited renders. The page notes that a Remotion license is not included. ## Templates vs components A template is a whole project you start from. If you already have a Remotion project and need the shots inside it, a component library fits better. Those are covered in [the best Remotion component libraries](/best-remotion-component-libraries). Imgcli is one of them (ours): you install components into an existing project with `npx shadcn@latest add https://imgcli.com/r/.json`, including full scenes like [Product Hero](/docs/scenes/hero-launch), and the source is copied into your repo. Browse the [free MIT-licensed components](/docs/components) for scenes you can add to your project. If you are rendering on a server, the next question is where to run it: [Lambda vs Cloud Run vs your own server](/remotion-lambda-vs-self-hosted-rendering). ## Sources - https://www.remotion.dev/docs (Creating a new project) - https://www.remotion.dev/templates - https://github.com/remotion-dev/remotion/blob/main/packages/create-video/src/templates.ts - https://www.remotion.dev/docs/vercel-sandbox - https://www.remotion.pro/store - https://www.remotion.pro/editor-starter - https://www.remotion.pro/timeline - https://www.remotion.pro/cube-transition - https://www.remotion.pro/watercolor-map - https://www.remotion.dev/docs/license/pricing - https://github.com/remotion-dev/remotion/blob/main/LICENSE.md - https://www.remotion.dev/docs/resources - https://github.com/zz41354899/SwiftClip - https://rendercomp.com/free - https://github.com/RenderComp/free-remotion-templates - https://cliphou.se/remotion-templates/ - https://lexingtonthemes.com/remotion --- # The best shadcn-compatible animation libraries in 2026 URL: https://imgcli.com/best-shadcn-animation-libraries Magic UI, Aceternity UI, Motion Primitives, Animate UI, React Bits, Kokonut UI and Cult UI compared on license, install method, price and animation engine. **Short answer:** for MIT code with a big catalogue, start with **Magic UI**. **Motion Primitives**, **Kokonut UI** and **Cult UI** are also MIT and free. **Aceternity UI** has a free tier and a paid all-access plan under its own license. **React Bits** and **Animate UI** are free to use in your product, but their MIT + Commons Clause license forbids reselling or redistributing the components. Every one of them has a namespace in the shadcn registry directory. Everything below was checked in October 2026, on each project's site, its GitHub repository and the shadcn registry directory. Star counts come from the GitHub API on the same day and will drift. ## At a glance | Library | License | Installs with | Price | GitHub stars | Animates with | | --- | --- | --- | --- | --- | --- | | [Magic UI](https://magicui.design) | MIT | shadcn CLI (`@magicui`) | Free; Pro $199 one-time | 22,775 | Motion on animated items (some are CSS-only) | | [Aceternity UI](https://ui.aceternity.com) | Aceternity's own license | shadcn CLI (`@aceternity`) | Free tier; paid plans from $169 one-time | No public repo found | Motion on most items | | [Motion Primitives](https://motion-primitives.com) | MIT | Own CLI, manual copy, `@motion-primitives` | Free; separate Pro site | 6,779 | Motion | | [Animate UI](https://animate-ui.com) | MIT + Commons Clause | npm + own CLI, `@animate-ui` | Free | 4,563 | Motion | | [React Bits](https://reactbits.dev) | MIT + Commons Clause | shadcn CLI (`@react-bits`) or copy-paste | Free; Pro from $129 one-time | 49,890 | GSAP, Motion, Three.js or OGL, per component | | [Kokonut UI](https://kokonutui.com) | MIT | shadcn CLI (`@kokonutui`) | Free; separate Pro site | 2,188 | Motion | | [Cult UI](https://www.cult-ui.com) | MIT | shadcn CLI (`@cult-ui`) | Free | 6,654 | Motion on most items | "Animates with" comes from the `dependencies` field of the registry items we fetched (for example Magic UI's `blur-fade` lists `motion` and its `marquee` lists none) and from each project's docs. Always check the component you actually install. ## Magic UI MIT-licensed, and the most-starred MIT library in this list. The docs install components with the shadcn CLI: ```bash pnpm dlx shadcn@latest add @magicui/globe ``` Animated items depend on `motion`. Some, like `marquee`, list no dependencies at all. Magic UI Pro is sold separately as templates plus premium components, for $199 one-time, according to the docs page. ## Aceternity UI Components install through the `@aceternity` namespace, for example `npx shadcn@latest add @aceternity/3d-card`. Most registry items we checked (`text-generate-effect`, `background-beams`, `hero-highlight`) depend on `motion`; `3d-card` lists no dependencies. Read the licence page before you ship. It is not MIT: one set of terms covers every item, free or paid. You can build unlimited end products for yourself or your clients, and those products may be sold. You cannot redistribute the source files, sell items on a marketplace, or build themes or templates to sell on a marketplace. The pricing page lists a free tier, a 1-Year License at $169, Lifetime Access at $199 and Team at $1,590, all one-time payments, each shown against a higher "regular price". ## Motion Primitives MIT. The repo describes it as motion components built with Motion and Tailwind CSS, and says the project is in beta. Install with its CLI (`npx motion-primitives@latest add text-effect`) or copy the code by hand. Manual setup needs `motion` (plus `lucide-react` for some components). It is also registered as `@motion-primitives`. A separate site, pro.motion-primitives.com, sells sections and templates; it showed no prices when we checked. ## Animate UI A different approach: animated drop-in versions of Radix UI, Base UI and React Aria primitives. The README says "Swap the import, keep your shadcn/ui components." The docs install `@animate-ui/react` and `motion`, then `npx @animate-ui/cli@latest animate all` animates every installed shadcn/ui component. Individual items are also served under the `@animate-ui` namespace. **License:** MIT + Commons Clause. You may use it in any application, website or product, commercial or not, "so long as you do not sell or redistribute the components themselves in their original form". ## React Bits The most-starred project in this list. The README shows effects such as Split Text, Liquid Chrome, Ballpit and Circular Gallery. Every component comes in four versions: JavaScript or TypeScript, styled with CSS or Tailwind. Install with the shadcn CLI, where the suffix picks the version: ```bash npx shadcn@latest add @react-bits/SplitText-TS-TW ``` Or copy the files from a component's Code tab. The engine varies by component. `SplitText-TS-TW` depends on `gsap` and `@gsap/react`, `BlurText-TS-TW` on `motion`, and the repo's package.json also includes `three` and `ogl`. **License, precisely:** the LICENSE file is "MIT + Commons Clause License Condition v1.0". It grants use, copying, modification and distribution "as part of an application, website, or product", including for commercial purposes, "so long as you do not sell, sublicense, or redistribute the components themselves-whether alone, in a bundle, or as a ported version." The same restriction covers the React Bits tools (Background Studio, Texture Lab and the others) and its sound library. What you export from the tools is yours to use in any product, but you may not sell the exports on their own as a pack. So using React Bits in your SaaS landing page is fine. Porting its components into another library you publish is not. React Bits Pro is a separate paid product. Its pricing page lists lifetime plans at $129 (Starter), $249 (Pro) and $349 (Ultimate), as one-time payments. ## Kokonut UI MIT. Built with Tailwind CSS, shadcn/ui and Motion, according to its README. Install with `npx shadcn@latest add @kokonutui/particle-button`. The CLI pulls in what a component needs, such as `lucide-react`, shadcn/ui primitives and `motion`. Kokonut UI Pro (kokonutui.pro) sells templates and premium sections; the main site shows no price. ## Cult UI MIT. The README says every component is a shadcn/ui registry item, uses Tailwind CSS v4 and shadcn theme tokens, and that "most animate with Motion". Install with either the URL or the namespace: ```bash npx shadcn@latest add @cult-ui/shift-card ``` The site calls the components "all free" and links a separate Cult Pro site. ## How to choose 1. **Need plain MIT?** Magic UI, Motion Primitives, Kokonut UI or Cult UI. 2. **Want your existing shadcn components to animate?** Animate UI's CLI does that in one command. 3. **Want showpiece effects (WebGL, 3D, text)?** React Bits, as long as you are not planning to redistribute the components. 4. **Building a client site from paid blocks?** Read Aceternity's licence first; the marketplace limits matter for agencies. ## What about video? Remotion's docs say that "all animations in Remotion must be driven by the value returned by the `useCurrentFrame()` hook". Anything else needs an integration that syncs timing with Remotion. For GSAP there is a `@remotion/gsap` package. For Framer Motion, the docs say there is no integration yet. So most of the libraries above will not render correctly in a Remotion video without rework. For video there are shadcn registries written for Remotion: - **[remocn](https://www.remocn.dev)**: MIT, 1,624 GitHub stars, installs as `@remocn/`. - **[Imgcli](/docs/getting-started/installation)**: our registry, so weigh that. It installs as `npx shadcn@latest add https://imgcli.com/r/.json` and copies the source into your project. The [free MIT-licensed catalog](/docs/components) includes [text-reveal](/docs/text/text-reveal) and [terminal-simulator](/docs/screens/terminal-simulator). The full rundown of Remotion libraries is in [the best Remotion component libraries](/best-remotion-component-libraries). ## Sources - https://ui.shadcn.com/r/registries.json - https://github.com/magicuidesign/magicui - https://magicui.design/docs/installation - https://magicui.design/r/blur-fade.json - https://magicui.design/r/marquee.json - https://ui.aceternity.com/docs - https://ui.aceternity.com/components/3d-card-effect - https://ui.aceternity.com/registry/text-generate-effect.json - https://ui.aceternity.com/licence - https://ui.aceternity.com/pricing - https://github.com/ibelick/motion-primitives - https://motion-primitives.com/docs/installation - https://pro.motion-primitives.com - https://github.com/imskyleen/animate-ui (README and LICENSE.md) - https://animate-ui.com/docs/installation - https://github.com/DavidHDev/react-bits (README, LICENSE, package.json) - https://reactbits.dev/get-started/installation - https://reactbits.dev/r/SplitText-TS-TW.json - https://pro.reactbits.dev - https://github.com/kokonut-labs/kokonutui - https://kokonutui.com/docs - https://kokonutui.com - https://github.com/nolly-studio/cult-ui - https://www.cult-ui.com - https://github.com/Remocn/remocn - https://motion.dev - https://www.remotion.dev/docs/third-party --- # How to make a changelog video in React URL: https://imgcli.com/changelog-video-react Turn every release into a changelog video with Remotion — one React composition, the release notes passed in as props, rendered from the CLI or CI with a single command. To make a changelog video in React, build **one** [Remotion](https://www.remotion.dev/docs) composition that takes the release as props — a version and a list of changes — and render it per release with `npx remotion render --props=release.json`. Let `calculateMetadata` size the video to the number of changes, and every release gets its video from a JSON file. *(interactive preview: https://imgcli.com/changelog-video-react)* ## 1. Describe the release as data ```json title="release.json" { "version": "v2.4", "tagline": "Faster renders, dark mode, and a new CLI.", "changes": ["Renders 2× faster", "Dark mode everywhere", "npx acme deploy"] } ``` This file is the only thing that changes between releases. Generate it from your changelog, your tags or your PR titles — it is just JSON. ## 2. One composition, driven by props A title card for the version, then one card per change. [Announce Title](/docs/scenes/announce-title) opens; [Punch Lines](/docs/text/punch-lines) cuts hard between the changes, one card each (`|` starts a new card): ```tsx // src/Changelog.tsx import { Series } from "remotion"; import { AnnounceTitle } from "@/components/imgcli/announce-title"; import { PunchLines, punchLinesDuration, } from "@/components/imgcli/punch-lines"; export type Release = { version: string; tagline: string; changes: string[]; }; export const TITLE_FRAMES = 170; export const HOLD = "48"; // frames per change card export const changesScript = (changes: string[]) => changes.join(" | "); export const Changelog = ({ version, tagline, changes }: Release) => ( ); ``` `punchLinesDuration()` is exported by the component for exactly this: change the script and the sequence length follows. ## 3. Let the video size itself Five changes need a longer video than three. `calculateMetadata` computes the duration from the props before anything renders: ```tsx // src/Root.tsx import { Composition } from "remotion"; import { Changelog, changesScript, HOLD, TITLE_FRAMES, type Release, } from "./Changelog"; import { punchLinesDuration } from "@/components/imgcli/punch-lines"; export const RemotionRoot = () => ( ({ durationInFrames: TITLE_FRAMES + punchLinesDuration(changesScript(props.changes), HOLD), })} /> ); ``` ## 4. Render it per release `--props` takes a JSON string or a path to a JSON file: ```bash npx remotion render Changelog out/v2.4.mp4 --props=./release.json ``` Put that in CI on every tag and the MP4 is waiting when you write the announcement. Render a square cut for social by changing `width` and `height`; the components size themselves to the frame. ## What makes a changelog video worth watching - **Three changes, not twelve.** A video is the trailer, not the notes. Link the full changelog. - **Name the outcome, not the ticket.** "Renders 2× faster" beats "Refactor render queue". - **Show one change working.** If a change has UI, cut to it — a [screen recording with a camera](/docs/screens/screen-recording) or a [terminal typing the new command](/docs/screens/terminal-simulator). - **End on how to get it.** A version number and an install command. *(interactive preview: https://imgcli.com/changelog-video-react)* Both components install as source you own: ```bash npx shadcn@latest add https://imgcli.com/r/announce-title.json https://imgcli.com/r/punch-lines.json ``` Next: [how to make a product demo video in React](/product-demo-video-react). --- # Making videos with Claude Code and Remotion URL: https://imgcli.com/claude-code-remotion-videos Claude Code can write a whole Remotion video from a brief. Give it Remotion's agent skills, a component library to compose instead of improvised motion, and a prompt with real copy — here is the setup and what agents still get wrong. **Short answer:** Claude Code makes videos by writing Remotion — React components rendered to MP4. Install [Remotion](https://www.remotion.dev/docs)'s agent skills so it knows the API, give it a component library to **compose** rather than animation to invent, and brief it with your real copy and a length. Then review the frames yourself. ## 1. Set up the project with skills ```bash npx create-video@latest ``` The Remotion scaffolder offers to add Remotion's agent skills — say yes. In an existing project: ```bash npx skills add remotion-dev/skills ``` These teach the agent Remotion's own best practices: `useCurrentFrame()`, `interpolate()`, ``, rendering. They work with Claude Code, Codex, Cursor and other agents. Remotion's docs also serve every page as markdown: append `.md` to a docs URL, and Claude Code fetches that version when you paste a link. ## 2. Give it components, not a blank canvas Give the agent the [Imgcli component index](/llms-components.txt) so it can choose a component for each beat and read its props, duration, and install command. *(interactive preview: https://imgcli.com/claude-code-remotion-videos)* ## 3. Write the brief like a shot list Vague prompts get vague videos. Name the length, the format, the beats and the exact words on screen: ```text Make a 20-second 1920x1080 launch video for Acme Billing in this Remotion project. Beats: 1. Title: "Invoices, sent before lunch" (3s) 2. A prompt being typed and sent: "Bill Acme for September's usage" (5s) 3. The answer streaming in (6s) 4. Screen recording public/recordings/checkout.mp4, push in on the Send button (6s) Use installed Imgcli components for every beat; do not write animation by hand. Transitions between beats: slide from the right, not fade. Render it to out/launch.mp4 when Studio looks right. ``` The last lines do the most work: "do not write animation by hand" keeps the agent composing, and naming the transition avoids the most common defect. ## 4. What agents still get wrong - **Crossfades that stack scenes.** Remotion's `fade()` keeps the outgoing scene at full opacity, so two headlines sit on top of each other mid-transition. Ask for `slide()` — the full reason is in [why your Remotion scene transitions look broken](/remotion-scene-transitions-overlap). - **Improvised easing.** Linear moves, springs that bounce type, quint ease-outs that look frozen on a 30fps clock. See [how to animate text in Remotion](/remotion-text-animations). - **Guessed props.** An agent that has not read a component's props invents plausible ones. Use the component index to confirm each install name. - **Invisible text.** A render with no background is black, and light-theme text disappears on it. Tell the agent the background colour. - **Never looking.** The agent cannot see motion. Scrub it in Studio yourself before you ship. *(interactive preview: https://imgcli.com/claude-code-remotion-videos)* Next: [how to make a product demo video in React](/product-demo-video-react) — the same video, step by step, if you would rather build it yourself. --- # Documentation URL: https://imgcli.com/docs Imgcli is a shadcn registry of Remotion components — install an animation into your own project and own the file. You have shipped the product and now you need the video. Imgcli is a [shadcn](https://ui.shadcn.com) registry of Remotion components — titles, captions, device frames, logo stings, whole scenes — that you install into your own project with the CLI you already use. The component lands in your repo as a file you own, written against the plain Remotion API, and you edit it like any other component you wrote yourself. ## How it works 1. **Have a Remotion project.** Imgcli does not bootstrap one — `npx create-video@latest` does. See [Installation](/docs/getting-started/installation). 2. **Add a component.** `npx shadcn@latest add https://imgcli.com/r/text-reveal.json` copies the source into `components/imgcli/`, with anything it depends on. No dependency on Imgcli is added to your `package.json`. 3. **Put it in a composition.** Pass the words you are animating; every other prop has a working default, so it renders the moment it is mounted. Change the copy, the colours, the timing — it is your file now. 4. **Render.** `npx remotion render`. The same component that played in the browser preview is the one that goes into the mp4. ## What is in here [Components](/docs/components) is the gallery: every component in Imgcli in one filterable grid, each card playing its own scene. The category pages below are the same components grouped by the job they do, with the installation and usage notes for that group. Free components are [MIT licensed](/docs/getting-started/license). Send component requests to [hello@imgcli.com](mailto:hello@imgcli.com). --- # AI Chat Input URL: https://imgcli.com/docs/ai-input Prompt bars, search fields and chat composers that type themselves — the shot where a product gets asked a question. Reach for this category when the input *is* the shot: the search bar at the top of a landing page, the prompt box in an AI product, the composer someone is about to hit enter on. These components animate the query being written — and the camera that leans in to watch it happen. The hard part is not the typing, it is the type. A field that resizes as it fills, or a line of text that re-kerns as it grows, or a scale that drags the baseline across the pixel grid, is what makes a demo look cheap. These components lay the sentence out once and reveal it by clipping, and they pivot every scale on the baseline — so the letters land, and nothing else moves. --- # Agent Steps URL: https://imgcli.com/docs/ai-input/agent-steps An agent narrating its own work — a log that writes itself one line at a time, always on the centre line, and then clears until only the answer is left *(interactive preview: https://imgcli.com/docs/ai-input/agent-steps)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/agent-steps.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage The prompt lands in a pill. Under it the log writes itself: each step arrives **running** — present tense, ellipsis, a faint globe turning beside it — and later flips to **past tense** behind a check, carrying whatever number it found. The whole column steps up one row every time a line is added, so the line that is still running is always the one on the centre of the frame. When the work is done the column swells and clears, the surviving line slides to the middle of the frame, and the answer takes its place in a chip of its own. ```tsx // src/Root.tsx import { Composition } from "remotion"; import { AgentSteps } from "@/components/imgcli/agent-steps"; const Working = () => ( ); export const RemotionRoot = () => ( ); ``` ### The last step never finishes That is the whole structure, not an oversight. Every step but the last one runs for its `hold` and then flips to `done`; the last one is **still running** when the column clears, and the result chip is what it turns into. Write it in the present tense (`"Rendering frames…"`) and put the number in `result` (`"Rendered 900 frames"`) — the payoff is the moment the sentence completes. There is no gap to tune between one step finishing and the next appearing: the next line **is** the acknowledgement that the last one finished, so it enters on the very next frame. That is what the reference does, frame for frame, and a pause between the two reads as a stall. ### The column is bottom-anchored, not top-anchored Rows stack downwards but the column hangs from its **last** row, which sits on `centerY`. So the list grows upward out of the frame rather than downward into empty space, and the live line never moves. The travel is one `ROW_PITCH` per step on a `cubic-bezier(.3,.7,.3,1)` over **0.308s**, and it is additive: if two steps land inside one travel, the column carries both moves at once instead of interpolating between row indices — which is the difference between a list that keeps up with a fast agent and one that stalls and then jumps. ### Proportions Every measurement is a fraction of the composition height, so the whole thing scales as one piece. They come off a 718×398 screen recording, and none of them are eyeballed. | | fraction of frame height | measured | | --- | --- | --- | | row pitch | **0.0738** | 29.39px, from six consecutive settle positions agreeing to 0.07px | | query pill height | 0.0553 | 22.0px, by the **mass** of its fill, not by its edges | | corner radius | 0.28 of pill height | not a pill; a stadium would be 0.500 and sits 3.5px wrong | | result pill height | 0.0691 | 27.5px — the answer gets a bigger chip than the question | | icon box | 0.0314 | 12.5px, derived from drawn ink, since a lucide glyph does not fill its box | | step type | 0.0254 | 10.11px | | prompt type | 0.878 × step type | the prompt is set *smaller* than the log it starts | The prompt being the smaller type is the one that looks like a mistake and is not. It falls out of a cross-correlation of the reference's column ink against this component's own render under a horizontal scale sweep: three separate step labels peak at 0.882, 0.868 and 0.887, and the prompt at 0.772. The same ratio shows up independently in the two strings' widths. A prompt has to survive one line in its pill; a step label never gets long enough to care. ### Why the measurements are mass, not edges A Gaussian blur moves an edge crossing but conserves an integral. The reference is a compressed recording of 10px type with a soft edge either side of every shape and a faint shadow under the pill, so every distance-between-two-edges came out wrong in a different direction each time. The pill height is therefore measured as the **mass** of its fill over a column band clear of both the type and the corner radius, and the icon strokes are fitted on **ink** — the one thing a blurred 12px glyph still reports honestly. Lucide's default stroke-width of 2 puts 34% more ink on the globe than the reference carries, which at this size is the difference between an icon and a dark blob. ### The running step's icon is nearly invisible `RUNNING_ICON` is **0.17**, and that is measured too: the reference's spinner carries 850 of ink where its finished globe carries 5110. A running step is a *place* in the list, not a thing to look at — the moment it finishes, its icon is what tells you. The globe turns while it runs, and only its meridian turns: spinning the whole glyph would spin the equator with it and read as a wheel rather than a sphere. The label, on the other hand, is **not** dimmed while it runs. The reference's running and done labels carry the same ink per glyph. ### The outro is two lengths, not one The column swells to **1.40×** and fades, and the surviving row slides to the middle. Both curves are `cubic-bezier(.3,0,.3,1)`, fitted separately at rms 0.017 and 0.015 — and they are deliberately not the same length. The swell finishes in **0.5s**, the slide in **0.6s**: the row is already at its final size while it is still travelling, so the last thing that moves is the thing you are meant to read. The slide needs no DOM measurement. The row is wrapped in an element as wide as the column and the pair carry `translateX(+50%)` and `translateX(-50%)` — percentages of two different widths — so the row's centre lands on the column's centre whatever the copy is. ### The surface is the design system's, not the reference's The proportions above are measured. The **colours are not** — they were the reference's brand. The page is `theme.background`, the pills are `theme.card`, the log is `theme.mutedForeground`, the checks and the glint are `theme.primary`, and the wash in the corner is `mixOklch(background, primary, 0.17)` — a tint of the page, never a second colour on it. Override any of them per-prop, or the lot through `theme`. ## Props done @globe; running > done\"", }, { name: "result", type: "string", default: '"Rendered 900 frames"', description: "The answer the last step turns into", }, { name: "queryHold", type: "number", default: "0.683", description: "Seconds the prompt holds alone before the first step", }, { name: "stepHold", type: "number", default: "0.45", description: "Default seconds a step runs, for steps that do not set their own hold", }, { name: "finalHold", type: "number", default: "0.283", description: "Seconds the last step runs before the column starts to clear. It never flips to its done label — the result replaces it", }, { name: "paperColor", type: "string", description: "Page behind the scene. Defaults to theme.background", }, { name: "glowColor", type: "string", description: "The wash in the corner. Defaults to mixOklch(paper, primary, 0.17)", }, { name: "glowX", type: "number", default: "0.858", description: "Wash centre, as a fraction of frame width", }, { name: "glowY", type: "number", default: "0.854", description: "Wash centre, as a fraction of frame height", }, { name: "glowRadius", type: "number", default: "0.465", description: "Wash radius, as a fraction of composition height. 0 turns it off", }, { name: "pillColor", type: "string", description: "Pill fill. Defaults to theme.card", }, { name: "inkColor", type: "string", description: "Prompt and result ink. Defaults to theme.foreground", }, { name: "stepColor", type: "string", description: "Step ink and the globe. Defaults to theme.mutedForeground", }, { name: "accentColor", type: "string", description: "The completed check and the glint that crosses the result. Defaults to theme.primary", }, { name: "centerY", type: "number", default: "0.5", description: "The line the running step sits on, as a fraction of frame height", }, { name: "fontFamily", type: "string", description: "Overrides Inter", }, { name: "theme", type: "Partial", description: "Design-system token overrides — the same shape every snap-cn-ui component takes", }, { name: "mode", type: '"light" | "dark"', default: '"light"', description: "Which default token set to resolve against", }, { name: "speed", type: "number", default: "1", description: "Global playback multiplier applied to the whole sequence", }, ]} /> --- # Answer Highlight URL: https://imgcli.com/docs/ai-input/answer-highlight A question, an answer that writes itself, and then a selection dragged across the one sentence that matters — and settled onto one word inside it *(interactive preview: https://imgcli.com/docs/ai-input/answer-highlight)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/answer-highlight.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage Four beats. The question lands in a pill. The answer writes itself a word at a time. A caret appears mid-sentence and a **selection band drags across one statement**, wrapping onto the next line the way a real one does. Then the band settles onto **one word** inside it, and that word takes the accent. The point of the shot is the third beat. The typing is the setup — it is what makes the highlight land on something the viewer has just read rather than on a paragraph that was always there. ```tsx // src/Root.tsx import { Composition } from "remotion"; import { AnswerHighlight } from "@/components/imgcli/answer-highlight"; const Ask = () => ( ); export const RemotionRoot = () => ( ); ``` ### You name the sentence, not its coordinates `statement` and `word` are **strings, matched against the answer** — not indices, not offsets, not a markup syntax inside the copy. Edit the answer and the highlight follows it; edit it so far that the sentence is gone and nothing highlights, which is the signal that the two have drifted apart rather than a band drawn over the wrong words. Punctuation does not have to line up. `statement="keep the timeline declarative"` finds the answer's `declarative,` — writing the comma into the prop to make the match land is the kind of thing nobody remembers and everybody debugs twice. `word` only counts if it falls **inside** `statement`; the same word appearing earlier in the answer is not the one you meant. ### The band is a background, not a rectangle Every word of the answer is its own inline span, and the selection is a `background` on the spans the drag has passed. That is the whole trick, and it is what buys: - **Real wrapping.** A selection that runs over a line break breaks square at the line end and picks up at the start of the next one — because the browser is doing the wrapping, not a measured rectangle trying to imitate it. - **No DOM measurement.** No refs, no `delayRender`, no `getBoundingClientRect` that reports device pixels and has to be divided back out by the current scale. Put any copy you like in it. The word gap is `padding`, not `margin`, so the band is drawn behind the spaces too — otherwise a selection over four words comes out as four chips with holes where the spaces were. ### Why the drag has a caret Because a selection without one is a highlight animating in, and a highlight animating in reads as decoration. The leading edge is drawn as a **hard stop inside a gradient**, with a sliver of the accent just past it — one `background` doing the band, its edge, and the caret at once. The stop moves *through* the word it is crossing rather than jumping over it: at 30fps a per-word step advances three whole words a second and the whole thing turns into four chips appearing in sequence. `dragStep` is seconds per word, default **0.11** — four words in a little under half a second, which is what a hand actually does. ### The selection is a tint, never the accent `accentColor` at 16% over the page for the statement, 30% for the picked word, and the picked word's ink mixed toward the accent. Primary at full strength behind body type puts white-on-blue in the middle of a paragraph and the line stops being readable — which is exactly why a browser's own selection is pale until the window loses focus. The tint is composited as **alpha, not `mixOklch`**. Mixing the warm off-white page toward a blue in oklch takes the short way round the hue wheel and passes through green: the first cut of this scene highlighted the answer in pale mint. ### Proportions The question pill is the one measured object here — its height, radius, padding and entrance are the numbers [Agent Steps](/docs/ai-input/agent-steps) carries, fitted off the same recording. Everything below it is chosen for this scene, and the constants in the file say what each one is for. | | value | | --- | --- | | answer type | 0.0385 of frame height | | answer measure | 0.6 of frame width — three or four lines of a real answer | | line height | **1.64**, looser than a paragraph wants, so the band on one line does not sit against the type on the next | | band inset | 0.18em above and below the type | | caret | 0.06em wide | ## Props ", description: "Design-system token overrides — the same shape every snap-cn-ui component takes", }, { name: "mode", type: '"light" | "dark"', default: '"light"', description: "Which default token set to resolve against", }, { name: "speed", type: "number", default: "1", description: "Global playback multiplier applied to the whole sequence", }, ]} /> --- # Answer Stream URL: https://imgcli.com/docs/ai-input/answer-stream The beat after send — a macro shot on the button that cuts hard to the answer building itself, while the camera pulls back to keep up with it *(interactive preview: https://imgcli.com/docs/ai-input/answer-stream)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/answer-stream.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage It opens where [Prompt Zoom](/docs/ai-input/prompt-zoom) leaves you — close on the composer — and carries through the send, the cut, and the answer. ```tsx // src/Root.tsx import { Composition } from "remotion"; import { AnswerStream } from "@/components/imgcli/answer-stream"; const ReplyScene = () => ( ); export const RemotionRoot = () => ( ); ``` ## Scene to scene, the frame cuts Read frame by frame, the reference has exactly one grammar for changing shot, and it is a **hard cut** — one frame wide, no blur, no ramp. What makes the cuts invisible is not a transition. It is what happens either side of them. **Every cut lands on motion.** The camera is eight frames into an accelerating push toward the send button when the cut fires. It is moving fastest at the moment it is replaced. **Every cut is followed by a glide, not a stop.** The answer page arrives 27px high and settles down over 15 frames on an ease-out. Cut to a static frame and the edit becomes the loudest thing in the shot. The only *moves* are inside a shot, and there are two kinds: a slow drift that never quite stops, and one fast blurred push onto whatever is about to happen. ## The pull-back is fitted to the content As the answer grows — paragraph, then headline, then four cards, then the composer sliding up — the camera pulls back and rises to keep the block in frame. Tracking the column's left and right edges across the move recovers a scale of **1.364× → 1.0** about a fixed point at **(0.5, −0.548)** of the frame: dead centre horizontally, and 273px *above the top edge*. Both edges independently agree on that point to within 1px, which is what makes it a measurement rather than a guess. The focal point being above the frame is the whole reason the page appears to **rise** as it shrinks, instead of collapsing toward its own middle. Peak velocity sits at 48–52% of the move, which is a cubic in-out and rules out a spring — a spring peaks at a third. Measured back off the rendered frames against the reference's, the two curves start together, end together and part by at most 0.048 of scale near the midpoint. `pullbackFrom` is expressed against where the shot **settles**, not where the pull-back bottoms out. It overshoots its mark by 2.8% and creeps back onto it, and normalising against the bottom of that move instead puts every constant 2.8% out. The move carries **motion blur** derived from the camera's own speed — a frame that carries a typical element `d` px smears it by `d/4` — rather than dialled in by eye. ## Words arrive hot Containers land empty and fill: the question pill and each of the four cards appear as a bare surface first, and their text streams in after. Each word lands at `accentColor` and cools to `foreground` on a fixed clock, so the lit band at the head of the stream is `wordsPerSecond × coolSeconds` words wide — six, on the reference — no matter how fast you run it. Words that have not landed are hidden, not absent. Removing them lets the line reflow underneath and every word already on screen shifts as the next one arrives; on the reference they do not move at all. ## Props | prop | default | | | --- | --- | --- | | `question` | — | fills the pill, and stays in the composer | | `answer` | — | `\n` is a hard break; everything else wraps | | `headline` | — | lands under the reply, introduces the cards | | `cards` | four | `{ title, body, icon? }` — `icon` is an SVG path on a 24×24 box | | `commitAt` | `1.0` | when the push into the button starts accelerating | | `cutAt` | `1.284` | when the shot cuts. One frame, never a ramp | | `pullbackAt` | `1.933` | when the camera starts keeping up | | `pullbackDuration` | `1.1` | | | `pullbackFrom` | `1.364` | relative to where the shot settles | | `pullbackUndershoot` | `0.028` | how far past its mark it goes first | | `focusX` / `focusY` | `0.5` / `-0.548` | above the frame, on purpose | | `macroZoom` | `2.36` | the opening shot on the send button | | `wordsPerSecond` | `25` | | | `coolSeconds` | `0.23` | how long a word stays lit | | `blur` | `3` | ceiling on the camera's motion blur | Plus `accentColor`, `theme`, `mode` and `speed`, like every other component. ## Notes The preview above plays a **rendered mp4**, not a live ``. The pull-back re-shapes every line of type on the page at a new size for 33 consecutive frames; a frame that misses its budget in the browser is shown for the wrong length of time, and the eye reads that as the answer sticking as it arrives. The reference has no button press — its cursor is already down when the shot opens, and the accelerating push does all the work. Synthesised, the cut needs a visible cause, so there is a three-frame dip on `commitAt`. The colours and the copy are **not** measured. The reference is a specific product with its own coral, its own four icon tints and its own marketing lines; none of that belongs in a component that lands next to somebody else's `Input`. The paint is the shadcn token set, the composer takes its surface from the `Input` primitive's own style context, and every string is a prop. --- # Prompt Send URL: https://imgcli.com/docs/ai-input/prompt-send A composer that unrolls from a hairline, writes a brief while the camera rides the caret, then cuts back out as a pointer arrives and sends it *(interactive preview: https://imgcli.com/docs/ai-input/prompt-send)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/prompt-send.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition } from "remotion"; import { PromptSend } from "@/components/imgcli/prompt-send"; const BriefScene = () => ( ); export const RemotionRoot = () => ( ); ``` Give it a longer sentence, more chips, a shorter field — it all still lands: ```tsx ``` ## The typing is eased, and that is the whole shot The surprise in the reference recording is that it does **not** type at a constant rate. It accelerates in and decelerates out: about 17 characters a second over the first word, 33 through the middle, and a visible settle on the last three. Fitting an **ease-in-out sine** to the character count read off twenty-four frames lands within **0.30 characters rms** across all 69 — no constant rate comes close. That matters beyond the typing, because the camera rides the same curve. Pin a camera to a character *count* and it lurches once per keystroke; pin it to the continuous curve underneath and it glides, while the caret hops forward and back onto its mark — which is exactly the ±8px wobble the reference shows. `typeStart` and `typeDuration` are the two props. Everything about the rate falls out of them, and any length of copy finishes in the same window. ## Two cuts, not two zooms At **1.975s** the view cuts to **2.327×** in a single frame, landing with the caret 57% across the frame, and then tracks left with the sentence. At **4.008s** it cuts again, out to **1.44×**, centred on the send button — which is where the pointer is already heading. Both are **steps**. `cameraFor` returns one of exactly three states and there is a test asserting that no frame of the clip sits anywhere else. Ramping a cut, even across two frames, turns it into a zoom and loses the snap. Both marks are *fractions of the frame*, never pixel offsets, so a wider panel or a bigger type size reframes instead of drifting off the edge. ## The unroll The panel does not fade in and it does not scale up from nothing. A 2px hairline appears at its top edge and the box grows **downward** out of it — top edge pinned, bottom edge travelling its full height in 445ms — while the width goes 0.93 → 1. The writing surface and the chip strip are two heights on the same curve with different durations, which is why the strip surfaces from under the field late instead of being there from the first frame. | | measured | | --- | --- | | unroll | `445ms`, `cubic-bezier(0.3, 0, 0.35, 1)` | | placeholder + button | `417ms`, over `300ms`, linear | | chips | one every `100ms`, each rising `11.4px` | | typing | `0.915s` → `3.94s`, ease-in-out sine | | cut in | `1.975s`, `2.327×`, caret at `(0.573, 0.488)` | | cut out | `4.008s`, `1.44×`, button at `(0.531, 0.526)` | | pointer | enters `4.04s`, halves its distance every `44ms` | | click | `4.658s` | ## Nothing is a constant tuned to one sentence Every offset above is either a fixed inset — a padding, which should not change when the type does — or a **ratio of a font size**, which must. `layoutFor` turns four numbers into the whole geometry, and the defaults are the recording's own measurements, so the shipped shot is pixel-identical to the one that was measured: - **Longer copy scrolls the field under the caret**, the way a real single-line input does, instead of running past its edge. It scrolls on the continuous curve, so the line glides while the caret hops. - **More chips wrap**, and the panel grows by exactly the rows they needed. The stagger keeps running in label order across them. - **No chips at all** and the panel hugs the field. - **A short `fieldHeight`** puts the text and the send button on the same row, and the line yields the button's gutter — but only then, so the reference's tall field still gets its full width. ## The pointer belongs to the screen It is drawn **outside** the camera. A pointer is a property of the display, not of the thing on it, so it must not grow when the shot pushes in — and the reference's does not. Set `cursor={false}` to drop it, and the click and the send bloom go with it. ## The caret blinks off the last keystroke Not off a wall clock. A real caret stays solid while you type and starts blinking a fixed interval after you stop, and anchoring it that way is what gets the reference's two measured blinks — dark at 4.267s, back at 4.741s — without a single extra number. ## Colour and wording The proportions and the timing are measured. The **colours and the labels are not** — the reference is a specific product, and its blue and its copy are its brand. The paint is the shadcn token set, the field takes its surface from the `Input` primitive's own style context so the two cannot drift, and the backdrop wash is layered from `accentColor` (which defaults to `theme.primary`), so it is the installer's accent. ## Props ", description: "Token overrides. The field's surface comes from the Input primitive's own style context, so the two cannot drift", }, { name: "mode", type: '"light" | "dark"', default: "\"light\"", description: "Which token set to resolve against", }, { name: "speed", type: "number", default: "1", description: "Multiplies the frame clock. Every beat above scales with it", }, ]} /> The recording is 5.02s and ends 140ms into the send bloom. The opening of that bloom is measured; the sweep that follows it is the component's own, which is why the default duration is 165 frames rather than 151. --- # Prompt Zoom URL: https://imgcli.com/docs/ai-input/prompt-zoom A prompt field that types a sentence into itself and, partway through, cuts — hard, in a single frame — into the caret and keeps going *(interactive preview: https://imgcli.com/docs/ai-input/prompt-zoom)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/prompt-zoom.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage The prompt starts typing in the wide shot. Partway through the sentence the view **cuts** to the caret — and the typing does not pause for it, it carries straight on at the new size. ```tsx // src/Root.tsx import { Composition } from "remotion"; import { PromptZoom } from "@/components/imgcli/prompt-zoom"; const AskScene = () => ( ); export const RemotionRoot = () => ( ); ``` ## The cut is the point It is a **cut, not a zoom**. On the reference the wide view is whole on one frame and fully pushed in on the very next — 803ms to 824ms, with nothing in between. Interpolating it, even across two or three frames, turns a cut into a move and loses the snap entirely. `isZoomed` is deliberately a boolean, and there is a test asserting no frame is ever partway. It fires **mid-sentence**, and that is what keeps it from reading as a scene change: the frame either side of the cut shows the same half-typed prompt, so there is one continuous action running through it and the cut simply gets you closer to it. Where it lands matters as much as how fast. The push is **2.547×**, anchored on a point **10% across and 41% down the input field** — the text insertion point. The shot doesn't just get bigger, it arrives on the thing that is about to happen. That focal point was fitted to the field's top and left edges, then checked against geometry it had not seen: it predicts the `+` glyph's centre to within **1.2px** and the field's bottom edge to **0.0px**. | | measured | | --- | --- | | typing starts | `0.35s` | | cut at | `1.0s` — mid-sentence | | zoom | `2.547×` | | focal point | `(0.270, 0.516)` of the frame | | typing | `18` chars/sec | Every one of those is a prop. ## Colour and wording The proportions and the timing are measured. The **colours and the labels are not** — the reference is a specific product, and its mark, model names and suggestion labels are its brand, not something to ship to strangers. The paint is the shadcn token set, the field takes its surface from the `Input` primitive's own style context so the two cannot drift, and `greeting`, `placeholder`, `model` and `effort` are all props. Suggestion pills are off by default — pass `chips={["Write", "Learn"]}` to show a static row under the field. `accentColor` paints the starburst and defaults to `theme.primary`. The reference clip is 1.078s and stops **mid-word** — it is still typing "Get…" when it ends. The default duration here is the choreography's own end plus a hold, not the recording's length. --- # Search Typing URL: https://imgcli.com/docs/ai-input/search-typing A search field wider than the shot — two thirds in frame, then it comes forward, types across its left half, pages to its right half, and goes back *(interactive preview: https://imgcli.com/docs/ai-input/search-typing)* ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/search-typing.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage Two thirds of the field are in frame to begin with, parked back. On the first keystroke it comes forward, and now only its **left half** fits. The sentence types across that half; when the caret runs out of frame the field slides, and its **right half** comes in. The sentence finishes there — and then the field goes back **further than it started**, far enough that the **whole** of it lands in frame, centred. That is the only moment in the clip you see the complete field and the finished sentence at once, so it is the shot the whole thing is built toward. Everything is driven by the current frame, so scrubbing and rendering are deterministic — including the keystroke jitter, which is seeded, not sampled from a clock. ```tsx // src/Root.tsx import { Composition } from "remotion"; import { SearchTyping } from "@/components/imgcli/search-typing"; const AskScene = () => ( ); export const RemotionRoot = () => ( ); ``` The component ships with a transparent background — place it over any backdrop, or over one of the [Backgrounds](/docs/backgrounds). ### The field is wider than the frame That is the whole idea. `fieldHeight` sets how tall the field reads on screen (0.27 of the frame, so it comes out long and slim), and `frontVisible` sets how much of it is in frame once it has come forward. The field is then **padded out to whatever length that framing needs**. That padding matters, and it is not a fudge. A field only as long as its sentence has a *fixed* aspect ratio — so demanding that half of it fill the frame would force its height, and it would come out as a chunky slab. Separating the two knobs is what lets it be both half-in-frame and slim. A real search field is longer than what you type into it anyway. `dolly` is the ratio between the two depths — **1.25**, which is what the reference measures. `frontVisible × dolly` is how much is in frame at rest: 0.56 × 1.25 ≈ **two thirds**. The **ending depth is not a third knob**: it falls out of the geometry. The field retreats until it is exactly as wide as the page, so it fits — and because `edgeInset` leaves an equal margin either side, anchoring the retreat on the field's right cap lands it centred with no special case. The push and the retreat are therefore two separate moves to two separate depths (`cameraPush`, `cameraRetreat`), not one move played backwards. Folding them together is what kept the field from ever pulling back far enough to be seen whole. The page across is not on a timer — it fires on the **character whose caret is about to run out of frame**, which falls near the middle of the sentence. It fires a little early on purpose: the pan eases in, so for its first frames the field has barely moved while the typing carries the caret on rightwards. Fire it *at* the edge and the caret spills over before the field catches up. Short sentences that fit in the first half never pan at all. ### Proportions Every measurement is a ratio of the field's height, so the whole thing scales as one piece. They are not invented: they come off the reference frames. | | ratio of field height | | --- | --- | | corner radius | **0.276** — not a pill; a pill would be 0.500 | | font size | 0.379 (cap height 0.265) | | left cap → first glyph | 1.024 | | left cap → icon | 0.357; icon 0.378 | | caret | 0.024 wide × 0.434 tall | The face is **Outfit Light (300)**, loaded through `@remotion/google-fonts` so the Player, the rendered mp4 and your own project all get the same one. It was chosen by measurement, not by eye: the reference's stem-to-cap ratio is 0.121, and Outfit 300 is the geometric face that lands on it. ### The surface is the design system's, not the reference's The proportions above are measured. The **colours are not** — they were the reference's brand. The field paints itself from `inputStyleContext()`, the very same style context the shadcn `` uses, so it is a card surface on a hairline border and it cannot drift away from the input sitting next to it in your app. That matters most for the shadow. The reference's field carries a heavy drop shadow and a grey-crown gradient. On its dark violet backdrop those read as depth and as glass; on a light page they read as a **grey smear** and an **inner shadow**. So the default carries **no shadow at all** — a flat `#FFFFFF` card and a `#E4E7EC` hairline border, which is how shadcn defines a control. `surface="glass"` keeps the measured reference field; use it only over something dark. ### Why the type doesn't judder as it scales A browser gives glyph origins **no vertical sub-pixel precision at all** — it rounds them to a whole device pixel. So a scale that *moves the baseline* makes the type climb the pixel grid in whole-pixel jumps: sit still, jump, sit still. So the scale **pivots on the baseline**, which is measured (a zero-sized inline-block sitting on it), never guessed from a line-height. On the rendered frames the baseline holds to a **single pixel row** across the full 1.25× sweep. Separately, hinting bends each glyph so its stems land on whole pixels, and under a sliding scale they re-snap every frame — the letterforms boil. `text-rendering: geometricPrecision` turns that off. `will-change: transform` is gated on `getRemotionEnvironment().isRendering`: right for the Player (one long-lived tab, a hard frame budget), wrong for a render (parallel tabs each inherit a stale raster). ### Why the text doesn't shift as it types The obvious way to type a string is `text.slice(0, n)`. That re-shapes the run every keystroke, and a run that re-shapes can re-kern — settled glyphs twitch as new ones land. Instead the sentence is laid out **once, in full**, and revealed by clipping to a measured character boundary. A run shaped once cannot move a glyph. Ligatures are off, so the clip can never bisect an "fi". The measurement runs once, behind `delayRender()`, after `document.fonts.ready` — and with the camera transform switched off, because `getBoundingClientRect()` reports device px and would otherwise hand back every advance multiplied by the current scale. ## Props ", description: "Design-system token overrides — the same shape every snap-cn-ui component takes", }, { name: "mode", type: '"light" | "dark"', default: '"light"', description: "Which default token set to resolve against", }, { name: "icon", type: '"search" | "sparkle" | "none"', default: '"search"', description: "Glyph in the left of the field", }, { name: "fontFamily", type: "string", description: "Overrides Outfit", }, { name: "fontWeight", type: "number", default: "300", description: "CSS font-weight. The reference measures as a Light", }, { name: "seed", type: "string", default: '"search-typing"', description: "Seeds humanize. Same seed, same performance, forever", }, { name: "speed", type: "number", default: "1", description: "Global playback multiplier applied to the whole sequence", }, { name: "className", type: "string", description: "Optional className passed to the field", }, ]} /> --- # Remotion cursor animations URL: https://imgcli.com/docs/collections/cursor-animations A synthetic mouse pointer that moves, hovers and clicks over any UI in a Remotion video — no screen recording, no real cursor to chase. A demo reads as a demo when something is being *used*. A cursor that walks to a button and presses it does that in about forty frames, and it is the one thing a static screenshot cannot fake. [Cursor Track](/docs/screens/cursor-track) is the general case: give it waypoints over any children and it moves, eases, and presses. The others are scenes with a click already choreographed into them — [Roster Grant](/docs/scenes/roster-grant) clicks a call to action and switches a team on, [Tap Through](/docs/screens/tap-through) is the phone version. Keep the path short. A pointer that wanders is a pointer the viewer is watching instead of the product. --- # Remotion device mockups URL: https://imgcli.com/docs/collections/device-mockups iPhone and MacBook frames for Remotion — put a screenshot or a screen recording inside a device that moves, without leaving React. A product shot needs the product in something. These put your screenshot, your `` or a live component inside a phone or a laptop and then move the camera, so the device is part of the shot rather than a PNG sitting on top of it. Reach for a **frame** ([Phone Frame](/docs/screens/phone-frame), [Laptop Frame](/docs/screens/laptop-frame)) when the device is the container and you are composing the rest of the scene around it. Reach for a **shot** ([Phone Pitch](/docs/screens/phone-pitch), [Laptop Open](/docs/screens/laptop-open)) when the device arriving *is* the beat — they carry their own camera move and their own timing. Every one takes its screen content as a child, so what goes inside can be an image, a recording cropped with [Screen Recording](/docs/screens/screen-recording), or another snapcn component. --- # Remotion image gallery animations URL: https://imgcli.com/docs/collections/image-galleries Photo walls, moodboards and 3D card rails for Remotion — the shot that shows a body of work rather than one screen. Some beats are not about one screen, they are about *how much* there is: a portfolio, a template library, a feed. These arrange your images in space and move the camera through them. [Orbit Gallery](/docs/scenes/orbit-gallery) rings them around a centre mark, [Moodboard Reveal](/docs/scenes/moodboard-reveal) flies a scatter in and lands on a hero, and [Card Rail](/docs/scenes/card-rail) flicks them sideways on a tilted plane. All of them take your own images — pass real ones, because a gallery running snapcn's demo pictures is a video about snapcn. --- # Remotion terminal animations URL: https://imgcli.com/docs/collections/terminal-animations A terminal that types its own commands and prints real output, plus the code and LED-screen shots that go with it — all in React, no screen recording. The install line is the product for a lot of developer tools, and filming a real terminal means a real cursor, a real font and a real mistake in take four. [Terminal Simulator](/docs/screens/terminal-simulator) types the command and prints the output you give it, deterministically, at whatever speed reads. Use it for the command itself. Use [String Hero](/docs/text/string-hero) when the shot is a line of code whose *content* is the headline, and [LCD Type](/docs/text/lcd-type) when you want the pixels of the screen to be visible — a type specimen rather than a shell. Because the output is data and not a recording, the same component renders a different transcript per video, which is the point of doing it in code. --- # Cursor & Clicks URL: https://imgcli.com/docs/cursor Cursors that click, pick and collaborate — the interaction beats of a product video. This is the category for the moment someone uses the product. A cursor presses the button, picks the option, or a team of cursors works on the same card. Every one is built on [Cursor Track](/docs/screens/cursor-track), so a pointer moves the same way in all of them. --- # Button Press URL: https://imgcli.com/docs/cursor/button-press A cursor walks onto a split action button: hover, press, a spinner while it works, then a drawn tick *(interactive preview: https://imgcli.com/docs/cursor/button-press)* A cursor walks in and lands on a split action button: the label, a hairline divider and a chevron, in your primary colour. The button darkens as the cursor reaches it, gives a little on the click and springs back. The label slides out for a spinner, then the spinner slides out for a tick that draws itself. It's the deploy, publish or send beat in a launch video. ## How it moves **Only the surface presses.** The fill gives by 3.5% and returns to exactly 1. The label never scales, so it never shimmers. **One width, three labels.** The button is sized once (`width`, at 720p) for the longest label, so it never reflows under the cursor sitting on it. ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/button-press.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition } from "remotion"; import { ButtonPress } from "@/components/imgcli/button-press"; const Scene = () => ; export const RemotionRoot = () => ( ); ``` ## Props ", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: '"light" | "dark"', description: "Which end of the design system to resolve tokens against", }, ]} /> --- # Click Type URL: https://imgcli.com/docs/cursor/click-type A prompt composer writes its own placeholder while a cursor flies in and clicks the field, then a hard cut into the field where the prompt types at the caret *(interactive preview: https://imgcli.com/docs/cursor/click-type)* A prompt composer in Prompt Send's style: a glass panel over a soft accent wash, a white writing surface, two icon buttons and a round send button. The placeholder writes itself while a cursor flies in from the edge and clicks the field. Then the shot cuts hard into the field, the placeholder is gone, and your prompt types at the caret. Use it for the "just describe it" beat of an AI product video. ## How it moves **It cuts with Prompt Send.** Same glass panel, same writing surface, same wash, all from your theme. Change the wash with `accentColor`. The send button arms to ink once the first letter lands. **Two shots, one card.** The close-up is the same card under a constant camera, pinned on the first letter. Nothing scales while it moves, so the type stays sharp. **The cursor stays in the wide shot.** It clicks, and the cut leaves it behind, the way a real screen recording would. **Typing is a keystroke a frame.** A space costs an extra beat. The caret holds solid while it types and blinks while it waits. The length depends on the prompt: `clickTypeFrames(prompt)` returns it. ## Installation ```bash npx shadcn@latest add https://imgcli.com/r/click-type.json ``` ## Component code The exact file `shadcn add` copies into your project. ## Usage ```tsx // src/Root.tsx import { Composition } from "remotion"; import { ClickType, clickTypeFrames } from "@/components/imgcli/click-type"; const prompt = "Build me a booking app"; const Scene = () => ; export const RemotionRoot = () => ( ); ``` ## Props ", description: "Design-system token overrides. Anything you leave out falls back to the shadcn defaults", }, { name: "mode", type: '"light" | "dark"', description: "Which end of the design system to resolve tokens against", }, ]} /> --- # How to make a GitHub README demo GIF with Remotion URL: https://imgcli.com/github-readme-demo-gif Render a crisp, looping demo GIF for your GitHub README with Remotion — the exact CLI flags, how to stay under GitHub's 10MB limit, and when an MP4 is the better choice. To make a README demo GIF with [Remotion](https://www.remotion.dev/docs), render your composition with `--codec=gif`, halve the frame rate with `--every-nth-frame=2`, and shrink it with `--scale`. Keep it short and loopable, and under GitHub's 10MB limit for images and GIFs. ```bash npx remotion render Demo out/demo.gif --codec=gif --every-nth-frame=2 --scale=0.5 ``` *(interactive preview: https://imgcli.com/github-readme-demo-gif)* ## 1. Build a short, loopable composition A README GIF is watched in a loop, at small size, with no sound. So: - **5–10 seconds.** One command, one result. - **Big type.** It will be shown at the README's width, often half your composition's. - **End where it starts.** A loop that snaps from the last frame to the first looks broken; fade both ends to the same background. For a CLI tool, a [terminal that types its own command](/docs/screens/terminal-simulator) is the whole video: ```tsx // src/Root.tsx import { Composition } from "remotion"; import { TerminalSimulator } from "@/components/imgcli/terminal-simulator"; const Demo = () => ( ); export const RemotionRoot = () => ( ); ``` For a UI, crop a recording and push in on the part that matters with [Screen Recording](/docs/screens/screen-recording), and add a pointer back with [Cursor Track](/docs/screens/cursor-track). ## 2. Render it as a GIF ```bash npx remotion render Demo out/demo.gif --codec=gif --every-nth-frame=2 --scale=0.5 ``` What each flag does: | Flag | Effect | | --- | --- | | `--codec=gif` | Output a GIF instead of an MP4 | | `--every-nth-frame=2` | Render every second frame: 30fps becomes 15fps. `3` gives 10fps. Only valid for GIFs | | `--scale=0.5` | Halve the output dimensions: 1280×720 becomes 640×360 | | `--number-of-gif-loops=0` | Play once. Omit it to loop forever — what a README wants | | `--frames=0-149` | Render only a range, to trim without touching the composition | 15fps is plenty for a README. Frame rate and dimensions are where the bytes are: half the frames at a quarter of the pixels is a far smaller file. ## 3. Get it under 10MB GitHub accepts images and GIFs up to **10MB** when you drag them into an issue, pull request or comment. For a README, aim far lower — a few megabytes — so the page does not stall. If the file is too big, in order: 1. **Shorten it.** Every second costs the same; cut the ones that are not the payoff. 2. **Drop the frame rate.** `--every-nth-frame=3`. 3. **Shrink it.** `--scale=0.4`. 4. **Simplify the frame.** GIF has 256 colours per frame. Gradients, blur and photos cost the most; flat backgrounds and text compress best. ## 4. Put it in the README Drag the GIF into the README editor on GitHub, or commit it and reference it: ```md ![acme deploy in one command](./docs/demo.gif) ``` Write the alt text as the sentence the GIF proves. It is what screen readers, search engines and anyone on a slow connection see. ## When to use an MP4 instead A GIF is huge for what it shows. An MP4 of the same clip is a fraction of the size and keeps full colour. GitHub plays `.mp4`, `.mov` and `.webm` uploads inline with a player — but they do not autoplay like a GIF, and a GIF also works where a README is mirrored, such as npm. Common split: a GIF in the README, the full-quality MP4 in the release notes. *(interactive preview: https://imgcli.com/github-readme-demo-gif)* Install the components: ```bash npx shadcn@latest add https://imgcli.com/r/terminal-simulator.json https://imgcli.com/r/screen-recording.json ``` Next: [how to make a changelog video in React](/changelog-video-react). --- # Interactive product demo vs video demo: which to make URL: https://imgcli.com/interactive-demo-vs-video-demo When a clickable interactive demo beats a product video and when it does not — landing pages, sales, onboarding and launch posts on X and Product Hunt — with the tools for each. **Short answer:** make a **video** when the viewer is passive — scrolling a feed, landing on your homepage, watching a launch. Make an **interactive demo** when the viewer is actively evaluating — a buyer who wants to try before a call, or a new user learning a workflow. Most products end up with both: a 30–60 second video at the top, and a clickable demo for the people who want more. Tool details below were checked on each vendor's site in October 2026. The advice sections are our own judgement, not survey data. ## At a glance | | Video demo | Interactive demo | | --- | --- | --- | | Viewer | Watches | Clicks through | | Pace | Yours | Theirs | | Works in a feed (X, LinkedIn) | Yes, as a video post | Needs a link out | | Product Hunt gallery | Yes, as a YouTube link | Yes, as a demo link | | Landing page | Top of the page | Lower down, or behind "try it" | | Sales | Before the first call | During and after evaluation | | Onboarding | Overview | Step-by-step guides | | Tools | Screen Studio, Tella, Loom, Cap, Remotion | Arcade, Supademo, Storylane | ## What each one is A **video demo** is a file — usually an MP4 — that plays the same way for everyone. You choose what is shown, in what order and how fast. It can be a cleaned-up screen recording, a camera walkthrough, or a motion-graphics cut built from scratch. An **interactive demo** is a captured copy of your product that the viewer steps through. Arcade describes recording with a Chrome extension, desktop app or Figma plugin and then adding hotspots, callouts, voiceover and branching. Storylane also offers "sandbox demos" that "let buyers and sellers navigate a pixel-perfect clone of the product." The viewer is driving, so the demo has to survive them clicking in an order you did not plan. ## When a video wins **Launch posts on X and LinkedIn.** A feed post carries a video that plays in place. A clickable demo has to be a link to somewhere else, and a link is a second decision the scroller has to make. For the launch moment, the video is the asset; see [how to make a Product Hunt launch video](/product-hunt-launch-video) for the structure and the aspect ratios. **Product Hunt.** Product Hunt's [posting guide](https://help.producthunt.com/en/articles/479557-how-to-post-a-product) says gallery videos must be YouTube links, and that it needs at least two gallery images. It also lists interactive demo tools — Arcade, Storylane, ScreenSpace, Hexus, Supademo and Layerpath — whose demos can be added to the gallery as a link. So you can ship both; the video is what plays first. **The top of a landing page.** A first-time visitor has not decided to invest any effort yet. A short video asks nothing of them — it can autoplay muted, it shows the outcome, and it is over in under a minute. An interactive demo asks them to start clicking before they know whether they care. **A README or docs page.** A looping GIF shows the product right where the reader already is, with no click. See [the GitHub README demo GIF](/github-readme-demo-gif). **When the product is hard to show by clicking.** Anything that happens over time — an AI answer streaming in, a build running in a terminal, a background job finishing — is easier to stage as a video than as a sequence of click targets. ## When an interactive demo wins **A buyer evaluating before a sales call.** Storylane's pitch is exactly this: "Interactive demos open your product before a form or a call." Supademo lists landing-page embeds so visitors "try the product before booking" a call, and personalised demos in outbound sequences. A buyer comparing three tools wants to poke at the real flow, not watch your edit of it. **Onboarding a specific workflow.** Supademo describes helping new customers "complete key workflows with interactive demos they can follow at their own pace." A step-by-step guide beats a video here because the user can stop on the step they are stuck on. **When sales wants to know who looked.** Interactive demo platforms build analytics in — Supademo lists tracking links and analytics on its paid plan, and Storylane's Starter plan includes HubSpot and Zapier integrations. If the demo is a sales-pipeline tool, that tracking is the point. ## The tradeoffs | | Video | Interactive demo | | --- | --- | --- | | Control of the story | Full | Shared with the viewer | | Effort for the viewer | None | They have to click | | Keeping it current | Re-record or re-render | Re-capture the changed steps | | Motion and titles | Yes | Mostly callouts and hotspots | | Where it plays | Anywhere a video plays | Where the embed or link works | Two practical points. First, a video goes stale when the UI changes; an interactive demo does too, step by step. If you ship UI every week, pick the format whose update path you will actually do. Second, many interactive tools also export video — Arcade says any demo can be exported "as an MP4 video, a GIF, or a step-by-step guide", and Supademo's FAQ says it "exports in GIF, MP4, and PDF formats" — so capturing once can cover both, at the cost of a video that looks like a click-through. ## Tools for each **Video:** - [Screen Studio](https://screen.studio) — macOS-only recorder with automatic zooms; $29 a month or $9 a month billed yearly. - [Tella](https://www.tella.com) — screen and camera on separate tracks, with an editor; Mac, Windows and browser. - [Loom](https://www.loom.com) — quick async video messages; free Starter plan. - [Cap](https://cap.so) — open-source recorder with a free personal-use plan. - [Remotion](https://www.remotion.dev) — videos written as React components, rendered to a file. Free for individuals and companies up to three employees. If you build the video in code, prebuilt scenes save you the motion design. Imgcli is our library of them, so read this with that in mind: shadcn-installed Remotion components for software videos such as a [prompt being typed and sent](/docs/ai-input/prompt-send), an [answer streaming in](/docs/ai-input/answer-stream), a [terminal](/docs/screens/terminal-simulator) and a [framed screen recording](/docs/screens/screen-recording). These are free under MIT. The [browser video editor](/docs/video-editor) can assemble and export an MP4 without a Remotion project. The step-by-step build is in [how to make a product demo video in React](/product-demo-video-react). **Interactive:** - [Arcade](https://www.arcade.software) — Chrome extension, desktop app or Figma plugin; exports MP4 and GIF; free plan, Pro from $49 a month billed annually. - [Supademo](https://supademo.com) — Chrome extension and desktop apps; free plan with five interactive demos; Scale from $38 a month billed annually. - [Storylane](https://www.storylane.io) — interactive and sandbox demos; free plan with one live demo; Starter $50 a month. The full comparison, with pricing for each, is in [the best product demo video tools](/best-product-demo-video-tools). ## A sensible default 1. Make one 30–60 second video of the core job. Put it at the top of the landing page, in the launch post and in the Product Hunt gallery. 2. If buyers evaluate before talking to you, or onboarding has a workflow people get stuck in, add an interactive demo for that one flow. 3. Update whichever one goes stale first; that is the one people are using. No motion designer? See [a SaaS demo video without After Effects](/saas-demo-video-without-after-effects). ## Sources - https://help.producthunt.com/en/articles/479557-how-to-post-a-product - https://www.arcade.software/product/interactive-demo - https://www.arcade.software/pricing - https://supademo.com/ - https://supademo.com/pricing - https://www.storylane.io/ - https://www.storylane.io/pricing - https://screen.studio/ - https://www.tella.com/ - https://www.loom.com/pricing - https://cap.so/pricing - https://www.remotion.dev/ - https://github.com/remotion-dev/remotion/blob/main/LICENSE.md --- # What we learned shipping llms.txt for a component registry URL: https://imgcli.com/llms-txt-component-registry How to install Imgcli components from the current registry, plus an upstream snapcn case study on llms.txt, Markdown docs, and missing component names. ## Install an Imgcli component today Choose a component from the [Imgcli component index](/llms-components.txt) and prepare your Remotion project using the [installation guide](/docs/getting-started/installation). For example, install Text Reveal with: ```bash npx shadcn@latest add https://imgcli.com/r/text-reveal.json ``` The short command `npx shadcn@latest add @imgcli/text-reveal` works after you add the `@imgcli` registry mapping to `components.json`. The [current llms.txt](/llms.txt) lists the exact install commands. ## Case study from upstream snapcn The examples and measurements below describe the [upstream snapcn registry](https://github.com/shadcn-labs/snapcn) at the time of the case study. **Short answer:** an `llms.txt` that lists pages is not enough for a component registry. Agents need the **complete list of installable names**, documentation as **markdown at `.md`**, and a registry **404 that answers in JSON** with the real names. Before snapcn made those changes, agents guessed nonexistent component names 208 times in thirty days. Upstream snapcn documented `npx shadcn@latest add @snapcn/` for its [Remotion](https://www.remotion.dev/docs) components. ## Agents guess names, and guess wrong Snapcn's first `llms.txt` said the install pattern was `@snapcn/` and then never named a single component. The gallery section listed *display* names behind links, so the slug had to be guessed. Agents guessed. In thirty days the registry served **208 requests for names that do not exist**, across 41 distinct spellings — `blur-out-up` (21), `dynamic-grid` (10), `soft-blur-in` (9), `number-wheel`, `line-by-line-slide`, `shader-warp`. None appeared in the upstream repository, roadmap or links. They were invented, and each one was someone who had already typed `npx shadcn add` — one failed install for every eight that worked. ## Fix 1: say the list is complete The fix is a section in `llms.txt` generated from the same list the "install everything" button uses, so a new component appears there by existing: ```ts const INSTALLABLE = `## Every installable component These ${INSTALL_ALL_NAMES.length} names are the complete set. \`@snapcn/\` resolves for these and for nothing else — any other name returns 404, so do not infer, pluralise or invent one. If what you want is not on this list, snapcn does not have it yet; say so rather than guessing a plausible name. ${INSTALL_ALL_NAMES.map((name) => `- \`npx shadcn@latest add @snapcn/${name}\``).join("\n")} `; ``` Two details matter. The list is **runnable commands**, not names, so there is nothing left to assemble. And it **tells the agent what to do on a miss**: say the component does not exist. Without that instruction, a model's default is to produce something plausible. The upstream header also recorded the August 2026 rename from `@snap-cn` to `@snapcn`. ## Fix 2: answer `.md` Appending `.md` to a docs URL is the convention coding agents try first. Snapcn initially answered with a 200 and forty-seven kilobytes of React shell. An agent that spends its context on `