sitereal
v0.1

Interaction

Motion

data-motion, springs, scroll links and the runtime that reads them.

An animation is an attribute on the element. data-motion="rise" is the whole of it, and everything else overrides what that name already means.

<section builder-data-element="section" data-motion="rise" data-motion-stagger="90">

Nothing that repeats a default is written, so the markup stays short and hand-editable — and an element pasted into another project animates there too.

The presets

PresetDoes
fadeopacity only — the quiet one
risefades in from below
sinkfades in from above
slide-rightenters from the left
slide-leftenters from the right
zoomgrows into place
popsmall, then overshoots
blurfocus pulls in
tiltrights itself as it arrives
customnothing preset — say it yourself

The overrides

AttributeMeans
data-motion-durationms
data-motion-delayms
data-motion-easesmooth out in-out bouncy linear, or any curve of your own
data-motion-springstiffness damping mass — the transition, instead of ease and duration
data-motion-opacity -x -y -scale -rotate -blurwhere it comes from
data-motion-exit-*the same six, for where it leaves to
data-motion-triggerwhat starts it
data-motion-starthow far into the viewport (% of its height) before it fires
data-motion-oncefalse = it plays again on the way back
data-motion-staggerms between children — the box does not animate, its children do, in turn

Springs

A spring is three numbers — stiffness, damping, mass — solved into a real CSS linear() easing, so the page needs no physics engine and the curve survives being pasted into a stylesheet.

Its duration is a result, not a fourth setting: 400 30 1 settles in 460ms, and the panel says so. The graph you look at while tuning and the animation that ships are the same spring by construction.

Scroll links

A reveal and a scroll link are different things, and they say so with different attributes:

<img builder-data-element="image" data-scroll-speed="0.15" />
AttributeMeans
data-scroll-speedhow much slower than the page — 0.15 is a sixth behind; negative leads
data-scroll-rotatedegrees across the whole range
data-scroll-scalescale added across the whole range

A reveal has a start, an end and a hidden state. A scroll link has none of those — it is only where the element sits given where the page is. One element can carry both, and they do not fight over CSS either: the reveal writes the individual translate / scale / rotate properties, the scrubber writes transform, and the browser composes the two.

The runtime

public/scripts/motion.js, emitted automatically for the pages that need it:

  • IntersectionObserver for reveals, a requestAnimationFrame scrubber for scroll links.
  • State in inline styles, so nothing fights your stylesheet.
  • prefers-reduced-motion honoured.
  • One run per document, however many copies of the script arrive.

It is JavaScript-first on purpose — never CSS view() / scroll() timelines, because an overflow: clip ancestor traps them and a pinned section freezes them.

No flash

A few hundred inline bytes in the head arm the document and write their own hide rule — not the design system's, because a project with the design system off must not flash. The runtime disarms them once it holds the hiding in inline styles, and they disarm themselves after 2.5 seconds if the runtime never arrives.

A page that fails to animate is a page. A page that stays invisible is nothing. That is why the guard has a timeout.

Tokens

Four values in Design → Motion say what animation is like on this site:

--motion-duration: 700ms;
--motion-ease: cubic-bezier(0.16, 1, 0.3, 1);
--motion-distance: 32px;
--motion-stagger: 90ms;

The runtime never reads them in JavaScript. They go into the inline transition / translate as var(--motion-…, fallback), so the module can be off, the design system can be off, and an element still animates — it just stops having an opinion.

On the canvas

On a canvas every element already sits where its animation ends, so a reveal is something you ask to see: press Play. It prunes detached nodes and re-scans first, because a canvas rebuilds its frames on every edit.