# Shutter type

Two lines of type through a rolling shutter: whatever moves tears into bands, whatever is still stays whole. Live at https://design.how/vault#shutter-type.

Install the skill, which carries the template, the brief and a check script:

```sh
npx skills add https://design.how/vault/shutter-type
```

## What it is

Two phrases share a 600 by 337.5 scene, 16:9, scaled to the box as one unit, over a 68-frame loop at 25 frames a second, 2.72 seconds. "Design is thinking" jitters in, settles, the ground inverts from paper to sky blue, the line swells a little and lifts away. A hard cut, and a stack of "made visual" scrolls up one row per beat. The type is a grotesque at medium weight in two colours, paper #f6f3ea and sky #00afca, swapped whole on frames 10 to 28 and 54 to the end.

Every slice, ghost and torn edge comes from one post-effect. A fixed map of horizontal stripes, 14 to 60 scene pixels tall, gives each stripe a lag of up to 2.4 frames, quantised to five taps, and no two neighbours share a tap. The engine draws the clean scene five times a frame, once per tap, into offscreen canvases, then copies each stripe from the canvas of its own lag. Where nothing moves the taps agree and the copy is seamless; where a word moves the stripes disagree and it tears. A stripe's lag never reaches back past the start of its phrase, so the cut stays hard, and the ground is drawn once from the present, so the inversion lands as one slap.

The first phrase arrives by jitter, not by easing. For seven frames each word sits at a fresh height within about 15px of home, then takes one larger kick and decays home by 0.62 a frame, from a stepped table. The shutter sees only that jitter at rest scale. The push-in, 0.26 percent a frame about the line's centre, and the 97px lift from frame 22 on cubic-bezier(0.85, 0, 0.15, 1) move the finished, shuttered picture, so the line leaves whole.

The second phrase is stacked on a 126px pitch, and a scroll of exactly one pitch on cubic-bezier(0.8, 0, 0.2, 1) starts every 14 frames and lasts 12. A full pitch is invisible at rest, so the eye only ever sees the tearing mid-move, and "visual" trails "made" by 0.4 of a frame to split the two words on the fast frames. Ink boxes are measured from the live face off an offscreen raster, and each word is squeezed to its set width so the spacing holds whichever grotesque draws it.

## How it works

- Build a fixed stripe map from a seeded walk down the scene: heights between STRIPE_MIN and STRIPE_MAX, each on one of STRIPE_TAPS lag taps, never the same tap as its neighbour.
- Each frame, draw the clean ink-only scene once per tap at frame f minus that tap's lag, clamped to the phrase's first frame, into its own offscreen canvas.
- Fill the ground from the present frame, then copy every stripe from its tap's canvas.
- For the first phrase, shutter the words at rest scale with only their jitter, then draw that layer scaled about the line's centre and lifted.
- Measure every word's ink box from the live face at boot and again when the face loads, and squeeze each word horizontally to its set width.
- Read the clock as elapsed / 40ms at a fractional frame every animation frame.
- Under reduced motion, draw frame 40 once: the stack at rest on paper.

## What you can change

- `PAPER` (#f6f3ea): One of the two colours, the ground for most of the loop.
- `SKY` (#00afca): The other colour, the ground on the inverted beats.
- `SKY_SPANS` ([[10, 28], [54, 67]]): The frames on which the colours invert.
- `STRIPE_LAG_MAX` (2.4): The longest lag a stripe carries, in frames. Higher tears further.
- `STRIPE_SEED` (0x51ab7e): Seed for the stripe map. A new seed moves the cuts and leaves the choreography alone.
- `FONT_VAR` (--visual-sans): The CSS variable holding the font family. Any grotesque works; the words are squeezed to their set widths.

Last updated: 2026-11-02
