Analyze → Summarize → Act
Turn raw data into clear insights and actionable next steps.
Choose your reading theme.
A feature card where data sources stream through a funnel of light into a scanner and come out as folders of findings.
insight-card/insight-card.tsxTurn raw data into clear insights and actionable next steps.
The live component, numbered part by part. Every measurement is read from it as it is drawn. Point at a row to outline its part, or at a part to find its row.
When to use
colorScheme you give it.When not to use
autoplay={false}, or use still pictures.You can add this component with the shadcn CLI or by hand:
Into components/insight-card of any React project, with no setup:
npx shadcn@latest add https://brunodelorence.com/r/insight-card.jsonCopy these files into one folder of your project, keeping their relative imports together, or download them as a ZIP:
As shown under Usage, below.
Either way it needs React 19 and a bundler that supports CSS Modules, such as Next.js or Vite. It has a dark colour scheme: it follows your page’s color-scheme, light unless the page sets one, or its colorScheme prop, with nothing to configure. Browsers without light-dark() show it light.
import { InsightCard } from './insight-card';export function Features() { return ( <InsightCard autoplay={false} title="Analyze → Summarize → Act" text="Turn raw data into clear insights and actionable next steps." outputs={['Customer Feedback', 'Revenue Summary', 'Post-Sales Insight']} /> );}Each state below is a live instance, locked so you can study it.
The pieces it is built from, each with its measurements read live.
A feature card for a data product. Round badges of sources glide in from the left through a funnel of light and dotted particles, into a glowing scanner line. Folders of findings step out of it on the right, one every three sources. The loop runs only while the card is on screen, slows under the pointer, and can be paused.
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | 'Analyze → Summarize → Act' | The card’s title. |
text | string | 'Turn raw data into clear insights and actionable next steps.' | The line under the title. |
outputs | readonly [string, string, string] | ['Customer Feedback', 'Revenue Summary', 'Post-Sales Insight'] | The three folders that come out of the scanner, in turn. |
onPausedChange | (paused: boolean) => void | — | Called when the reader pauses (true) or plays (false) the flow with its button. |
autoplay | boolean | true | Starts the flow on its own when the card comes on screen. When false, it waits, still, for Play. |
colorScheme | 'light' | 'dark' | 'system' | — | Its colours: light, dark, or the reader’s system setting. Without it the component follows the page’s color-scheme, which is light unless the page sets one. |
className | string | — | Extra class for the root, a stage of its own. |
The complete source behind this demo. Open source under the MIT license, © 2026 Bruno Delorence: copy it, change it, ship it.
// Copyright (c) 2026 Bruno Delorence. MIT License.'use client';import { useEffect, useId, useRef, useState, type CSSProperties, type PointerEvent } from 'react';import { Backdrop, FolderDrawing, GLYPHS, SCANNER, funnelPolygon } from './drawings';import styles from './insight-card.module.css';/* * Insight card: a feature card whose picture shows raw data turned into insights. Round badges of * sources stream in from the left through a funnel of light, into a glowing scanner; folders of * findings slide out of it on the right. * * - Every size is a multiple of one unit, the card's width over 538, so the picture keeps its * proportions at any width. The funnel, its glow and the blue beyond are one static SVG. * - Three rows move, each a row of fixed drawings shifted by its transform with the Web * Animations API: the sources glide on steadily, one a second; the dotted particles in the * funnel drift with them; the folders step on by one, eased, every third source. Each row holds * two rounds of its drawings and loops by exactly one round, so the loop has no seam. * - The scanner hides what passes it: the sources' row is clipped at its line, and the folders' * row is clipped there too; a band of frost just past the line whitens them as they come out. * - It plays only while on screen in a visible tab. Hovering the card slows it to a third, the * speed eased in and out. A button pauses it; under reduced motion it starts still, as a frame. * - Light or dark: the stage, the card and its words are light-dark() tokens in the stylesheet, so/* Insight card (insight-card.tsx): a card on a stage of its own, its picture above its title. Every size is a multiple of --u, the card's width over 538, so the card scales as one drawing; the stage is a size container and the card's width follows it, up to 540px. The scene is one layout boundary of a size computed from that width, so nothing inside it lays out the page. Motion. Three rows move by their transform alone, each on a layer of its own: the sources, the particles and the folders. Their clips and the mask that fades the folders in are fixed boxes around them; the scanner and its glows are fixed gradients over them. Colours. The interface's are the tokens below, each light-dark(light, dark): the card takes the color-scheme it inherits from the page (light unless the page says otherwise), or the one its colorScheme prop sets (data-color-scheme). The picture (drawings.tsx, and the haze, particles, badges, frost and scanner here) is content: the same in both. --text and the rows' places, set on the scene and the picture, are sizes, so no token below takes those names. */.stage { /* The stage, and the card on it: white on white by day, the card a step lighter at night. */ --surface: #fff; --surface-2: #fff; /* The title, the line under it, and the stage's own text. */ --ink: #383838; --ink-muted: #6b6b6b; --ink-soft: #484848;/* * The insight card's drawings: the backdrop of its picture (the funnel on the left, the light * streaks on the right), the glyphs of the sources that flow in, and the folders that come out. * Every drawing is static; the card moves the boxes that hold them. *//** The picture's box, in the units every size of the card is a multiple of. */export const MEDIA = { width: 512, height: 277 };/** Where the scanner stands, across the picture. */export const SCANNER = 223;/** The funnel's upper and lower edges, from the left side to the scanner. */export const FUNNEL_TOP = 'M0 0C70 50 130 95 223 105';export const FUNNEL_BOTTOM = 'M0 277C60 240 120 195 223 191';/** A point of a cubic Bézier curve. */const bezier = (t: number, a: number, b: number, c: number, d: number) => (1 - t) ** 3 * a + 3 * (1 - t) ** 2 * t * b + 3 * (1 - t) * t ** 2 * c + t ** 3 * d;/** * The inside of the funnel, kept `inset` units clear of its edges (a negative inset reaches past * them), as a CSS polygon in shares of the left part of the picture (the scanner's width by the * picture's height). */