Motion
Motion in SwiftAgency has a job: it sets reading order on load, confirms a hover or a filter, and shows that a page continues. It never decorates. Only transform, opacity and the font width axis animate, so nothing triggers layout, and there are no scroll listeners anywhere.
Patterns
Section titled “Patterns”| Pattern | How to use it | Where you see it |
|---|---|---|
| Load-in lines | .mask-line around .intro-line |
Page headlines rise out of a mask |
| Load-in fade | .intro-fade |
Intro copy and buttons after the headline |
| Curtain | .intro-curtain on a positioned wrapper |
The home hero image is uncovered |
| Scroll reveal | data-reveal, optionally in data-reveal-group |
Sections fade up once as they enter view |
| Width axis | .stretch-host and .stretch |
Links and titles widen on hover |
| Cursor preview | Built into <ProjectIndex> |
A frame follows the cursor over the list |
| Cursor-aware cards | Built into the work grid | Light and image drift follow the pointer |
| Scroll-driven hero | CSS animation-timeline: view() |
Case study hero images |
| Marquee | Built into <ClientMarquee> |
Client logos, pauses on hover |
Load-in
Section titled “Load-in”Stagger elements with the --i custom property. Each step adds 90ms.
<h1> <span class="mask-line"> <span class="intro-line" style="--i:0"> Work that </span> </span> <span class="mask-line"> <span class="intro-line" style="--i:1"> ships. </span> </span></h1><p class="intro-fade" style="--i:0"> Intro copy follows the headline.</p>Scroll reveal
Section titled “Scroll reveal”<ul data-reveal-group> <li data-reveal>One</li> <li data-reveal>Two</li> <li data-reveal>Three</li></ul>Children of a data-reveal-group are staggered automatically. Without JavaScript nothing is hidden: the hidden starting state only applies once the script has added the js class to <html>.
Reduced motion
Section titled “Reduced motion”When a visitor has asked their system for reduced motion (prefers-reduced-motion: reduce):
- Every animation and transition settles to its final state immediately.
- Scroll reveals, the curtain and load-in lines don’t play; content is simply there.
- The marquee becomes a static row, and the cursor effects stop moving.
- The width axis stays at rest on hover.
This is handled once in global.css, so your own components inherit it. If you write a new animation, wrap it in @media (prefers-reduced-motion: no-preference) the way the built-in ones are.
Timing
Section titled “Timing”Use the motion tokens: duration-fast for colour and hover, duration-base for reveals and menus, duration-slow for load-in. Things that arrive use ease-out; things that loop or cover use ease-in-out.