Design reference
Moving parts — all of them, in one place.
Part one renders the shipped motion behaviors live, from the real primitives in
public/motion.css and public/app.js. Two shipped behaviors are
deliberately not demoed here: [data-parallax] needs real scroll depth to read
as anything (it ships on the home hero), and the carousel's lc-coverflow /
lc-scroll-progress are demoed on carousels rather than
duplicated. Part two maps the design_system/motion system catalog to what we
could still adopt. Sibling of the hero sections,
section gallery, backgrounds, and
icons references.
- Intent first. Every move has one of six jobs. Show where you are, confirm, guide, focus, wait, or delight. Its speed and feel come from that job.
- Hard duration caps. Small moves stay under 240ms. Screen changes under 360ms. Scroll bits under 700ms. Those come from our ease-of-use rules. Only the slow background loops run longer, and they are just for looks.
- Opacity and transform only. Nothing moves the layout itself. No top, left, width, or height. Only paint tricks, like a color sweep or a shine, are allowed on top.
- Tokens, not magic numbers. Durations, easings, distances, and staggers come from the
--motion-*custom properties vendored from the design system intomotion.css. - Reduced motion stills everything.
prefers-reduced-motionputs every part in its resting spot, both in CSS and in the script. Movement is never the only sign, and the main headline never waits on a fade. Turn on your OS "reduce motion" setting and every demo on this page goes still.
- Shipped on this site
- 01 Reveal on scroll Guide
- 02 Stagger groups Guide
- 03 Ambient hero loops Delight / Wait
- 04 Pointer glow Focus
- 05 Header elevation Orient
- 06 Hover & press microinteractions Focus / Confirm
- 07 Link underline wipe Focus
- 08 Accordion disclosure Orient
- 09 Pop-up entrances Orient
- 10 Checkout loading shimmer Wait
- 11 Chat panel entrance Orient
- 12 Process-rail scroll choreography Guide
- 13 Cross-document route fade Orient
- 14 Hero 3D canvas backdrop Delight
- 15 The safety nets Governance
- Design-system possibilities
- P1 Microinteraction recipes Not adopted
- P2 UI Motion variants & unused primitives Partially adopted
- P3 Complex scroll choreography Partially adopted
- P4 Illustration animations Not adopted
- P5 3D product stage Partially adopted
- P6 Marketing video loops Not adopted
- P7 Heavy physics / particle fields Available, likely overkill
Reveal on scroll Guide
The one we lean on most. Things opt in with data-reveal and start hidden and shifted. A watcher in app.js adds .is-revealed as they scroll into view. The way they move is set by data-reveal="up|down|left|right" (default up). The vanilla equivalent of the design-system RevealOnScroll primitive.
data-revealup (default)data-reveal="down"downdata-reveal="left"from the rightdata-reveal="right"from the leftStagger groups Guide
A data-reveal-group makes its children come in one after another. app.js sets a delay of 50ms per item, to match --motion-stagger-normal. We use it on card grids, check lists, and top panel rows.
Ambient hero loops Delight / Wait
The soft moving layer behind the top blocks. Five moves, all set inside the no-preference rule, so they do not exist at all if you ask for less motion: lc-drift, lc-aurora, lc-pulse, lc-sweep, and lc-gradient-pan.
lc-drift · 6–7s float · used by .hero-proof-mark and hero visual cardslc-aurora · 16s glow mass · behind .interior-hero-panel, capture/checklist/proof cardslc-pulse · 2.4s opacity pulse · the .hero-frame-chip--live status dotlc-sweep · 5s accent-line sweep · the .hero-compact-band top edgelc-gradient-pan · 8s paint-only pan · the .hero-highlight headline phrasePointer glow Focus
A soft light that follows your mouse across a panel. Opt in with data-pointer-glow; app.js tracks the pointer into --lc-pointer-x/y and gates the whole behavior off for reduced motion and non-hover devices. The glow layer is pointer-events: none — light, never a blocker.
Move your cursor across this panel.
On a phone, or with less motion turned on, this panel stays still on purpose. The glow is a nice extra for a mouse, never a must.
Header elevation Orient
The header starts clear, then turns solid and lifts once you scroll down — app.js toggles .is-scrolled on [data-elevate] past an 8px threshold, throttled through requestAnimationFrame. You're looking at it: scroll this page.
Hover & press microinteractions Focus / Confirm
Buttons lift and change color. Cards rise. Links get a line. Boxes get a ring when you tab to them. All plain CSS, and all under the 240ms cap our ease-of-use rules set.
Link underline wipe Focus
The footer's own motion language, and the closest thing the site ships to the catalog's selection-underline recipe: the label nudges 3px along its track while a teal hairline wipes in beneath it, scaleX(0→1) from the left. Two properties, one direction, no layout work — hover and :focus-visible fire identically, so it reads the same by keyboard.
Accordion disclosure Orient
Every FAQ on the site is a native <details>. It used to snap open; now the panel's own auto height interpolates via ::details-content + interpolate-size, with opacity following at 180ms — no wrapper element, no JS, and the native disclosure semantics untouched. Browsers without ::details-content keep the instant open, which is a correct resting state.
Open and close me
The panel's own height is what moves. No wrapper, no measured size, no JavaScript. The arrow spin was already there at 160ms. The height and fade are the new bit.
And me — they're independent
The built-in <details> keeps its keyboard and screen-reader behavior just as it was. This only adds movement when it opens.
Pop-up entrances Orient
Every pop-up on /pop-ups comes in the same way. The back layer fades in over 250ms while the card or bar rises 0.5rem over 300ms. Both stop dead if you ask for less motion.
Checkout loading shimmer Wait
While the card box loads, the empty space shows a slanted shine that sweeps across. Nothing shifts or resizes, so the wait reads as "loading", not "broken". Paired with status text, never spinner-only.
Chat panel entrance Orient
The env-gated chat widget's panel pops in from its launcher corner — 8px rise, 0.98→1 scale, transform-origin bottom-right. The demo below mirrors the real keyframe (the widget's own styles only load when the widget is enabled).
Process-rail scroll choreography Guide
The numbered process rails on /services, /tools, /launch-kit, and every niche page now carry a progress spine that draws as you read them: a container opts in with data-rail, app.js observes its direct children, and each step that scrolls in advances --rail-progress a fraction toward 1 while CSS scales the line. Scroll choreography from the catalog's blueprints, built on the observer we already ship rather than a scroll library — no scroll hijack, and the steps read fine with the line at zero. See it on /services →
- 1
Audit
Each step that scrolls in moves the line along.
- 2
Build
Progress is steps-seen ÷ steps-total.
- 3
Launch
One transition per segment, 420ms.
- 4
Tune
Never scrubbed to raw scroll position.
Cross-document route fade Orient
Page-to-page navigation cross-fades instead of flashing white. This is the catalog's route-fade variant in its CSS multi-page form — deliberately NOT Astro's <ClientRouter />, which would swap documents without re-running the deferred app.js and would silently break the reveal observer, theme toggle, header elevation, pointer glow, and the form engine. Browsers without view transitions navigate exactly as before. It can't be demoed in place — it only exists between documents. Navigate to /section-gallery and come back to see it.
Hero 3D canvas backdrop Delight
The home hero renders a vanilla Three.js scene behind the copy, authored against the design-system three-system contracts: balanced preset, mounts only when WebGL is available, idles only while visible, and holds a static pose under reduced motion. The DOM hero is the fallback, so the headline never waits on it. Not re-mounted here — see it on the home page. View it on the home page →
The safety nets Governance
The layer that keeps all of the above honest: a no-JS failsafe (lc-reveal-failsafe) reveals everything at 1.2s if app.js never runs; prefers-reduced-motion stills every animation via CSS and short-circuits the observer and pointer glow in JS; hover effects are gated to hover-capable devices; and JS-driven smooth scrolling (the site-map search jump) falls back to instant under reduced motion.
What we could adopt next
The motion set, matched to this site.
design_system/motion system/ is a catalog of governed motion collections. Its
React and runtime packs can't run on this vanilla Astro site — anything adopted gets vendored
as a vanilla equivalent, the way the reveal layer already was. Each card names the collection,
its adoption status here, where it would land, and the governance constraint that comes with it.
Cards with a live demo build it from the vendored tokens.
Microinteraction recipes Not adopted
The site's forms confirm success and flag errors with text alone — correct, but the catalog's confirm and focus recipes would let a saved state pop a small check and an invalid field nudge sideways, with the text staying the primary cue. All four recipes in the pack are demoed below: button-confirm-pop, field-error-nudge, selection-underline (a selected tab's rule wiping in from 72% — the selection sibling of the shipped link underline wipe, which is the only form of it the site has today), and saving-shimmer (a background save, distinct from the shipped checkout skeleton: it tints a control that already has content rather than standing in for content that isn't there yet). All token-built and under the 240ms cap.
Where it lands: The contact, class, and booking forms. Quiz and tool sends. Checkout box checks. And the step pickers on /tools and the quizzes.
Constraint: Confirms stay under 220ms and focus nudges under 240ms. Movement is never the only sign. The words and icons still say what happened.
UI Motion variants & unused primitives Partially adopted
Two of the pack's four variants are now live. route-fade shipped as a cross-document view transition (see the shipped specimen above), and the orphaned data-reveal="scale" primitive — vendored into motion.css but with zero usages on any page — now drives the /pricing tier cards, so the plans scale up into place instead of sliding. What's still open is panel-enter and dialog-scale, demoed below: the overlay layer uses its own popup-rise pair rather than these, and no panel or tab surface consumes them yet. The spring tokens (--motion-spring-*) are also vendored but unconsumable in pure CSS; they'd matter only if a JS motion engine is ever adopted. Two more primitives sit in the same orphaned state data-reveal="scale" was in, and are named here rather than left for the next sweep to rediscover: .page-progress (motion.css, geometry in styles.css) and [data-ticker-row]/.ticker-row (same pair) are both fully built and consumed by nothing anywhere in src/ — they arrived under the HOME SCROLL STORY banner and no page ever opted in.
Where it lands: Panel and tab changes on /tools and the quizzes, plus loading screens outside checkout.
Constraint: Screen changes stay under 360ms, and only fade or shift.
data-reveal="scale"no longer unused. It now drives the /pricing plan cardspanel-enter 220ms · opacity + 12px rise. Where it would land: the /tools panel switcher and the quiz step body. dialog-scale 180ms · opacity + 0.98→1 scale. Tighter and more centred than the popup-rise pair the overlays use today. Complex scroll choreography Partially adopted
The step-by-step explainer half of this is now live — see the process-rail specimen above. The GSAP runtime still can't ship here (flat nginx root), so the choreography rides the IntersectionObserver we already have, exposed as a generic [data-rail] primitive any container can opt into. The demo below is that same line-draw. What's still unadopted is the comparison-reveal blueprint (two columns resolving against each other as you scroll) and anything scrubbed continuously to scroll position rather than stepped.
Where it lands: Already live on the step rails (/services, /tools, /launch-kit, trade pages). Still to do: price plan reveals and long explainer runs.
Constraint: Scroll bits stay under 700ms. We never take over your scrolling. And nothing is hidden from the page's real order.
Illustration animations Not adopted
The set has a step-by-step Lottie for showing how a flow works. A Lottie player is a real weight on a small site. So the plain step stack is also the cheap first take. Steps that show up one by one read like a moving explainer and cost nothing. See it below.
Where it lands: Getting started, empty /docs screens, and the what-happens-next on the class page.
Constraint: Any Lottie needs a still picture for less-motion mode. Load the player late, and keep it below the top of the page.
3D product stage Partially adopted
The home top block already uses the 3D set. The next step is the slow-spin product stage: a gentle turning showcase for the product page, with a plain fallback for slower devices.
Where it lands: /products gallery hero; the Launch Kit offer page.
Constraint: A still picture if you ask for less motion. A plain fallback when 3D is not supported. And never above the main headline.
Marketing video loops Not adopted
The video and class paths are built but still show stand-in videos. The set spells out the sizes and formats for launch loops and explainers. A quiet, poster-backed loop is the one move these paths still lack.
Where it lands: /vsl (the sales video itself), /webinar room, a muted launch loop on the home proof band.
Constraint: poster-fallback is mandatory; autoplay only muted; loops pause when off-screen; route large renders through the media pipeline, not the repo.
Heavy physics / particle fields Available, likely overkill
The set has a heavy 3D dust-cloud scene for fancy moments. For a lead site, it costs more than it gives. We list it so you know it exists, not because we should use it.
Where it lands: Nowhere yet. A push or a launch moment at most.
Constraint: static-particle-poster fallback mandatory; WebGPU support is still uneven; never beside task UI.