Parse storyDesign system
VisualSite

Parse story

Dot animation of a PDF converted to Markdown through OCR.

npx context-ds add story-parse
On the site

Usage

Use it for

File parsing on a site card or product section. Plays only on screen; shows the Markdown with reduced motion.

Avoid

  • Stories as the main content of a page

Dependencies: dot-engine

Example

examples/story-parse.tsx
import { Card } from "@/components/ui/card";import { ParseStory } from "@/components/visuals/stories/parse-story";export function Site() {  return (    <Card variant="ring" className="min-h-[22rem] w-full max-w-xl items-center justify-center">      <div className="relative w-full max-w-[26rem]">        <ParseStory />      </div>    </Card>  );}

Source

components/visuals/stories/parse-story.tsx
'use client';import { PARSE_RESTING, PARSE_STORY_GRID } from '@/components/visuals/stories/parse-story-scenes';import { DotStory, type LoadDotStory } from '@/components/visuals/dot-engine/dot-story';const loadParseStory: LoadDotStory = async (stage) => {	const { createParseStoryPlayer } = await import('@/components/visuals/stories/parse-story-player');	return createParseStoryPlayer(stage);};/** * The Parse File card's picture: an uploaded PDF is read by OCR and becomes a Markdown document. With reduced * motion it shows the Markdown. */export function ParseStory({ className }: { className?: string }) {	return <DotStory grid={PARSE_STORY_GRID} resting={PARSE_RESTING} load={loadParseStory} className={className} />;}
components/visuals/stories/parse-story-player.ts
import { CAPTIONS, MARKDOWN_FILE, PARSE_STORY_GRID, PDF, paintOcr } from '@/components/visuals/stories/parse-story-scenes';import { captionedBeat } from '@/components/visuals/dot-engine/story-caption';import { createBeatStoryPlayer } from '@/components/visuals/dot-engine/dot-story-beats';import type { DotStoryPlayer, DotStoryStage } from '@/components/visuals/dot-engine/dot-story-player';/** One parse, played as a loop: a bar of OCR reads down an uploaded PDF twice, and the PDF melts into Markdown. */const OCR = { startsAt: 0.5, seconds: 1.1, times: 2 };export function createParseStoryPlayer(stage: DotStoryStage): DotStoryPlayer {	return createBeatStoryPlayer(stage, {		grid: PARSE_STORY_GRID,		resting: { beat: 1, heldSeconds: 2 },		beats: [			captionedBeat({				picture: PDF,				caption: CAPTIONS.upload,				holdSeconds: OCR.startsAt + OCR.seconds * OCR.times + 0.5,				animate(states, heldSeconds) {					const reads = (heldSeconds - OCR.startsAt) / OCR.seconds;					if (reads >= 0 && reads < OCR.times) paintOcr(states, reads % 1);				},			}),			captionedBeat({ picture: MARKDOWN_FILE, caption: CAPTIONS.parsed, holdSeconds: 3.2, isTyped: true }),		],	});}
components/visuals/stories/parse-story-scenes.ts
import { storyCaption } from '@/components/visuals/dot-engine/story-caption';import { DOT_ABSENT, DOT_FILLED, DOT_HOLLOW, type DotGrid } from '@/components/visuals/dot-engine/dot-morph-dots';import { pictureOf, type StoryPicture } from '@/components/visuals/dot-engine/dot-story-beats';import { polygon, solidWithSpeckle, stack, type DotPainter } from '@/components/visuals/dot-engine/dot-story-cells';/** * The Parse File card's pictures, in the brand dot grid: an uploaded PDF, its scanned page read by a bar of OCR, and * the Markdown document it is converted into. */// The ring pattern behind the picture already keeps clear of it, so the dots need no halo of their own.export const PARSE_STORY_GRID: DotGrid = { columns: 58, rows: 38, pitch: 10, dotRadius: 3.2, ringStroke: 1, haloRadius: 0 };const { columns: COLUMNS } = PARSE_STORY_GRID;const between = (value: number, from: number, to: number) => value >= from && value <= to;const SHEET = { left: 5, right: 27, top: 3, bottom: 34, fold: 4 };const FOLD = { column: SHEET.right - SHEET.fold, row: SHEET.top + SHEET.fold };// A sheet of paper with its top corner folded over.const sheet = (content: DotPainter): DotPainter =>	stack(		(column, row) => ((column === FOLD.column && between(row, SHEET.top, FOLD.row)) || (row === FOLD.row && between(column, FOLD.column, SHEET.right)) ? DOT_FILLED : DOT_ABSENT),		polygon(			[				{ column: SHEET.left, row: SHEET.top },				{ column: FOLD.column, row: SHEET.top },				{ column: SHEET.right, row: FOLD.row },				{ column: SHEET.right, row: SHEET.bottom },				{ column: SHEET.left, row: SHEET.bottom },			],			content		)	);const SCAN = { left: 8, right: 24, top: 9, bottom: 23 };const BADGE = { left: 8, top: 26, columns: 13, rows: 7 };// The file type, cut out of its solid badge in rings: type set over the dots would only show between them.const BADGE_LETTERS = ['oo  oo  ooo', 'o o o o o  ', 'oo  o o oo ', 'o   o o o  ', 'o   oo  o  '];// A scanned page: a block of uneven ink where its text is, and the badge that says what kind of file it is.const scannedPage: DotPainter = (column, row) => {	if (between(column, SCAN.left, SCAN.right) && between(row, SCAN.top, SCAN.bottom)) return (row - SCAN.top) % 3 === 2 ? DOT_ABSENT : solidWithSpeckle(column, row);	if (!between(column, BADGE.left, BADGE.left + BADGE.columns - 1) || !between(row, BADGE.top, BADGE.top + BADGE.rows - 1)) return DOT_ABSENT;	return BADGE_LETTERS[row - BADGE.top - 1]?.[column - BADGE.left - 1] === 'o' ? DOT_HOLLOW : DOT_FILLED;};export const PDF: StoryPicture = pictureOf(PARSE_STORY_GRID, sheet(scannedPage));/** The PDF with a bar of OCR `read` of the way down its scanned page (none at 0 or 1). */export function paintOcr(states: Uint8Array, read: number) {	if (read <= 0 || read >= 1) return states;	const bar = SCAN.top - 1 + Math.round(read * (SCAN.bottom + 2 - SCAN.top));	for (let column = SHEET.left + 1; column < SHEET.right; column++) states[bar * COLUMNS + column] = DOT_FILLED;	return states;}// Markdown, drawn one string per row inside the sheet: `#` a solid dot, `o` a ring.const MARKDOWN_ROWS = ['', '', '', '', '', '   # #', '  #####', '   # #', '  #####', '   # #', '', '', '  ############', '', '  oooooooooooooooo', '  oooooooooooo', '', '  ## ooooooo', '', '  # oooooooooooo', '  # ooooooooo', '  # ooooooooooo', '', '  ooooooooooooooo', '  ooooooooo'];const markdown: DotPainter = (column, row) => {	const mark = MARKDOWN_ROWS[row - SHEET.top - 1]?.[column - SHEET.left - 1];	return mark === '#' ? DOT_FILLED : mark === 'o' ? DOT_HOLLOW : DOT_ABSENT;};export const MARKDOWN_FILE: StoryPicture = pictureOf(PARSE_STORY_GRID, sheet(markdown));const caption = (label: string, value: string, detail: string) => storyCaption(PARSE_STORY_GRID, 34, label, value, detail);export const CAPTIONS = {	upload: caption('upload', 'PDF', 'scanned pages, read by OCR'),	parsed: caption('parsed to', 'Markdown', 'ready for your index'),};/** The Markdown file: what the picture shows before it plays, and instead of playing with reduced motion. */export const PARSE_RESTING = { ...MARKDOWN_FILE, texts: CAPTIONS.parsed };

Reference: site src/components/sections/products/parse-story.tsx.