# Extracting a style

Draft a style from stills, clips, live web pages, front-end code, or Figma with rapid style extract, then finish it with the extractor agent.

`rapid style extract` measures stills and clips, reads live pages and front-end code, and writes a draft style; the `rapid-extractor` agent finishes it. Values read from a source (tokens, type, faces, easing) win over values measured from pixels, so give it the page or the repository when there is one.

Extraction takes the feel of a look, never its identity: no logos, no marks, no faces you aren't licensed to use.

## Run it

```sh
rapid style extract refs/*.jpg refs/clip.mp4 --name warm-field --out ~/styles/warm-field
```

- `<sources...>`: any mix of stills and clips to measure, `https://` pages to read, and folders of front-end code. A folder that holds only stills and clips is taken as references.
- `--name <name>`: the draft's name (lowercase, `a-z`, `0-9`, `-`).
- `--out <dir>`: an empty or new folder for the draft. A folder that already holds files is refused.
- `--frames <n>`: frames sampled from each clip, 1 to 120 (default 12).
- `--timeout <seconds>`: how long to wait for each web page at each width (default 30).
- `--assets <dir>`: keep what a web page shows, in a new or empty folder; see [keeping a page's assets](#keeping-a-pages-assets).

```sh
rapid style extract https://example.test/ ./checkouts/site refs/clip.mp4 --name warm-field --out ~/styles/warm-field
```

It needs `ffmpeg` and `ffprobe` on your `PATH`. Run on a two-scene render and one still, it prints:

```text
drafted /Users/you/styles/warm-field
palette
  field      #14171a   50.4%
  ink        #fefefe    4.2%
  paper      #f0ede4   45.4%
shots     median 3.1s over 2 (even, steady)
tempo     200.3 BPM (confidence 0.389)
margins   top 26.8%  right 22.0%  bottom 43.8%  left 6.3%
motion    0.204/s (moderate)
formats   reel, square
```

Few references give rough numbers, as the low tempo confidence says. Give it several clips and stills of the look.

## What it measures

| What | How |
|---|---|
| palette | weighted k-means in OKLab over every sampled pixel, each frame weighing the same, then named as tokens |
| pacing | ffmpeg's scene-cut detection on clips: shot lengths, their median and mean, rhythm, and trend |
| music | tempo from the onset envelope of a clip's audio, with a confidence, and loudness per second |
| layout | where content stands apart from each frame's background colour, as insets from the edges |
| motion | how much the picture changes per second: a calm-to-kinetic number |
| formats | the built-in format nearest each reference's aspect ratio: `reel`, `square`, or `wide` |

## From a live page

An `https://` address opens in the headless Chrome rapid renders with, in its own browser context, closed after, with a time limit. The page's own scripts run; this is extraction, not rendering, so the render page's "nothing outside the page" rule does not apply. It loads the page at a phone width (390 px) and a desktop width (1440 px) and reads, from the page's computed styles:

| What | How |
|---|---|
| tokens | every custom property on `:root`, with its colour as hex where it is one |
| type | headings `h1`–`h3`, the body text (the style most text is set in), buttons, links, and small text: font-family stack, weight, style, size, line height, letter spacing, case, colour, and background |
| faces | the faces the page loaded (`document.fonts`): family, weight, style, and their `url()` and `local()` sources. Only names and addresses are recorded; no font file is downloaded or copied |
| colours | box backgrounds by the share of the screen they cover, and text colours by how much text is set in them |
| shapes | the commonest button and card: corner radius, border, and shadow |
| motion | computed transitions and animations (durations and timing functions), and `@keyframes` from the page's own stylesheets |
| margins | where the text starts in the first screen at each width: the container's real padding |

Before it reads, rapid waits out the page's loader and intro animations, then scrolls it a screen at a time so lazy pictures load and scroll-triggered sections play, and shoots every screen. Each stop waits briefly for the network and for motion to settle, so a long page takes a while.

Screenshots at both widths join the references, so the pixel analysis and the extractor agent see them; they land in `refs/`. A page that answers with an error, shows a sign-in form, or does not finish loading is reported plainly, and rapid continues with whatever did load.

## Keeping a page's assets

With `--assets <dir>`, rapid also keeps what the page showed, for a video about the site. The files are the site's own: use them only for a video about that site.

```sh
rapid style extract https://example.test/ --name site --out ~/styles/site --assets ~/rapid/site-video/assets
```

| Kept | Notes |
|---|---|
| pictures | at the largest size their `srcset` offers |
| video clips and posters | the clip files and their poster pictures |
| backgrounds | CSS background images |
| inline vectors | saved with their drawn size and inherited colour, so each stands alone |
| icons and the share image | the page's icons and its `og:image` |

Each file is named for its alt text, else for the heading of its section, else for its address. The folder holds:

```text
assets.json     every kept file: what it is, where it came from, and its section
screens/        every screen of the scrolled page
contact.png     every kept picture, clip frame, and vector in one labelled grid
```

`contact.png` shows the set at a glance, which is how a storyboarder chooses. Pointing `--assets` at a project's `assets/` folder puts the files where [scenes](/docs/writing-scenes#video-clips) reach them.

## From front-end code

A folder that is not a folder of stills is read as code, statically. Nothing in it runs. rapid skips dependencies and build output and reads:

| Format | What it reads |
|---|---|
| CSS, SCSS, Less | custom properties (`--name: value`, `var()` resolved within the folder) and `@font-face` families with their sources |
| Tailwind config (`tailwind.config.{js,ts,cjs,mjs}`) | `theme` and `theme.extend`: `colors`, `fontFamily`, `fontSize`, `borderRadius`, `transitionTimingFunction`, `transitionDuration`; values the config computes in code are skipped |
| design tokens JSON | W3C design tokens (`$value`, `$type`, aliases such as `{color.white}`), and Style Dictionary `value` trees in files named for tokens or a theme |
| Swift | `Color(red:green:blue:)`, `Color(hex:)`, `UIColor(red:…)`, and `Font.custom` / `UIFont(name:)` faces with their sizes |

Every value in `analysis.json` keeps the file it came from.

## From Figma

rapid holds no Figma keys. When Figma's MCP server is connected in your Claude Code, ask the extractor agent to read a file or a frame: it uses that server's tools for the variables (colours, type, radii) and screenshots of the frames, saves the screenshots into the draft's `refs/`, and fills the same fields a page or a repository does, noting the source in `taste.md`. Run `rapid style extract` on the screenshots (or on the matching page or repository) first, so the draft has its measurements.

## How exact values are used

The draft takes exact values over pixel guesses wherever a source gives one:

- **palette:** `field` is the background covering the most screen, `ink` the colour most text is set in, `accent` the buttons' background (else the links' colour), `paper` the cards' ground; a theme's colours are matched to those roles by their names (`background`, `text`, `primary`, `card`). Other token colours follow under their own names, then pixel colours none of them has.
- **type scale:** `hero`, `title`, `body`, and `small` from the real sizes of `h1`, `h2`, the body text, and the small text, at the width nearest the format, scaled so the page's width fills the frame (390 CSS pixels to a reel's 1080). A theme's sizes are read as a phone's.
- **leading:** from the headings' and the body's line heights.
- **faces:** each role (`display` from `h1`, `voice` from the body text, `ui` from buttons) names the source's face in `analysis.json`; the draft uses it when it is installed here, else the first installed family of its stack, else a generic family.
- **margins:** from the container's padding, as a share of the frame.
- **easing:** the commonest timing functions, as `motion.easing.in` and `out`.
- **components:** the card's and the button's radius, border, and shadow.

`taste.md` gets an **Exact** section that says where each of these came from.

## What it writes

```text
warm-field/
  manifest.json    kind style, version 0.1.0, tagged "draft", its file list filled in
  style.json       the measured palette, margins per format, shot length, and tempo
  style.css        the kit styled from the tokens alone, to make the style's own
  taste.md         the measurements as facts, with Motion, Type, and Composition left to write
  analysis.json    everything measured, by reference, and everything read from pages and code, by source
  refs/            stills taken at the references' largest scene changes, and page screenshots
```

What code can't measure stays a placeholder: with no page or theme to read, every type role's face is `sans-serif`; and `taste.md` has no rules yet. The draft has no proof, and its `style.css` is the default kit sheet, which looks the same in every draft.

## Finish it with the extractor agent

A video in the look finishes it itself: start it with `rapid video <project> --look <draft>` (or `new_video` with `look`), and the job runs the extractor, holds the draft to `rapid style check`, and pins it in the project's `rapid.lock` before the storyboard. To finish a draft on its own, the plugin's `rapid-extractor` agent does it. In Claude Code, ask for it:

```text
Use the rapid-extractor agent to finish the draft style in ~/styles/warm-field.
```

It reads `analysis.json` and the refs, keeps every exact value over its own impression and notes where each came from, names the class of each type role, picks the closest installed face that is licensed for rendered videos, writes `taste.md`'s rules (checkable ones with numbers from the analysis, and judgment ones in plain words), tunes `style.json`, and runs `rapid style check --write` until it passes. It keeps measured values unless the refs plainly contradict them, and records its reasoning in `analysis.json`.

Proving the draft against its references (rendering a sample in the new style beside them and tuning it until a judge says it reads right) is planned, not built. `rapid style proof` proves a style against itself, once you have written its sample.

## Then

Review the faces and rules yourself, make `style.css` the style's own, render a scene in the style, and follow [making a style](/docs/making-a-style#8-prove-it) from step 8 to prove it and pack it.
