← All studies MVS Solves  /  Study 03  /  SET IN MOTION
Live
Local --:--:-- — Viewport — Study 03

Type is not furniture.

Mask reveal · Decode · Difference blend · Weight ramp Four techniques · no WebGL · no library
01 — Mask reveal

It arrives from behind its own edge

Each word sits in an overflow:hidden box and slides up from 105%. The mask is the parent, not a gradient — the letters are genuinely clipped by the line above, the way a printed slug would be. Staggered by index on a long expo curve.

02 — Decode

Characters resolve out of noise, left to right, each settling at its own moment. One requestAnimationFrame loop over a string — no library, and no per-character DOM node churn.

It inverts

Paper
Red
Ink
Blue
Paper
03 — What that was

mix-blend-mode: difference on a sticky headline. Four panels scroll underneath and the type inverts against every one — black on paper, white on ink, the exact complement over red and blue. No colour swapping, no scrim, and not one line of JavaScript. Three declarations of CSS.

04 — Weight as motion
Hierarchyis notsize aloneit is weightand rhythm

Weight and colour move together as the line crosses the viewport centre. The eye reads the heaviest line first regardless of where it sits — which is why a weight ramp beats a size ramp when vertical space is tight.

Engineering note

Four type techniques, zero dependencies

The constraint

Kinetic typography that survives a real content workflow — copy that changes, translations that run longer, and a client who will paste in a sentence twice the length of the one it was designed around.

Why the obvious route fails

Most kinetic type splits a headline into one DOM node per character and animates each. That breaks text selection, confuses screen readers, churns hundreds of nodes on every resize, and shatters the moment the copy changes length.

What I did instead

Reveals use real overflow masks on word wrappers, so letters are genuinely clipped by the line above the way a printed slug would be. The decode runs one requestAnimationFrame loop over a string — no per-character nodes at all. The inversion is mix-blend-mode: difference on a sticky headline: three declarations, and not one line of JavaScript touches it.

The trade-off I accepted

Difference blending needs a two-colour ground. It is exact over flat panels and turns to mud over photography, so it is a deliberate choice for a controlled palette rather than a general-purpose technique. The text is also readable before any script runs — JS arms the hidden state, never the reverse.

Reveal
overflow mask per word, expo stagger
Decode
1 rAF loop, 0 per-character DOM nodes
Inversion
mix-blend-mode: difference, 3 declarations
Isolation
isolate — blend confined to its section
Libraries
None. No GSAP, no WebGL
Fail-safe
Readable with JS disabled entirely