Twenty One Media
webMay 13, 2026

The Scroll-Triggered Terminal We Use to Show Case Study Data

A static screenshot of a dashboard proves nothing. It could be a mockup. It could be a small sample with fake numbers. When we built the case study section for our homepage, we needed something that felt like real production data, not a marketing slide.

The answer: a scroll-triggered terminal component that boots a deploy sequence line by line as you scroll it into view.

What It Does

The Terminal component displays a list of typed lines, each with its own delay. When the element scrolls into the viewport, lines appear in sequence. The final line gets a character-by-character typewriter effect with a blinking block cursor. Everything before it appears in full.

The actual output we use for the Command Post case study looks like this:

$ npm run deploy:production

✓ Compiled in 2.1s
✓ Migrations applied (8/8)
✓ Edge functions deployed (12/12)
✓ Stripe webhook validated
✓ RLS policies verified

  Tenants:        47
  Active users:   312
  Missions:       1,847
  After actions:  2,103

  Status:         live
  Latency p99:    84ms

→ getcommandpost.org

The final line types out at 18ms per character. Every line before it appears instantly, staggered by its delay prop.

The Decisions That Matter

Typewriter only on the last line. Animating every line character by character would take 15 seconds and test patience. One line gets the effect. It carries the punch (the live URL, or the key result), and everything above it is just context that arrives quickly.

Fires once, on scroll. Using useInView with { once: true } means the animation plays when you first scroll to it and never replays. Case study data shouldn't re-animate on scroll-back. The results arrived. That's the metaphor.

Color-coded tones. Each line accepts an optional tone: ok renders green, accent renders in the brand blue, muted handles blank spacer lines. The tone system maps terminal semantics (success, info, warning) to a hierarchy that guides the eye without explanation.

Clean teardown. The scroll timers are stored in an array and cleared in the useEffect cleanup. The typewriter uses setInterval, which also clears on cleanup. Nothing leaks if the component unmounts mid-sequence.

Why Not a Screenshot

Screenshots age. If the real numbers change, you have a stale image and a maintenance task. The lines array is just data: change a number, rebuild, done.

It also looks different from a screenshot in a way that's hard to explain verbally but obvious in person. The sequence feels like watching something run. A screenshot feels like proof-of-concept. That's a real distinction when the ask is "should I hire these people to build something for me."

We're reusing this component for other client case studies where the result is best expressed as a system output rather than a vanity metric on a card.