Basic settings
Animations
This page explains the animation stack for maintainers: library choice, system wiring, day-to-day usage, and where to extend it. All global toggles live in src/config/animations.ts.
Which library we use
We use Motion (npm package motion) — the modern successor to Framer Motion. For marketing sections we use its DOM API (animate, inView), not React components. That keeps Astro .astro files as static HTML and avoids hydrating every block.
Dropdowns and modals still use tw-animate-css in src/components/ui/ — that is UI micro-animation, separate from page scroll/hero Motion.
How it is wired in the project
Animation behaviour is split across a few files. You rarely edit the engine; you change config and add HTML attributes in section files.
- src/config/animations.ts — enabled, durations, scroll distance, reduced motion
- src/lib/motion-presets.ts — fade-up, fade-in, expand-x keyframes
- src/scripts/motion-init.ts — initMotion(): hero on load + scroll inView
- src/layouts/BaseLayout.astro — calls initMotion on every page
- src/styles/core.css — hides [data-motion] until .motion-done (FOUC prevention)
Config: src/config/animations.ts
Open this file first. Set enabled: false to turn off all Motion animations site-wide (content stays visible). Other fields tune default timing; individual elements can override duration via data-motion-duration.
export const animations = {
enabled: true,
respectReducedMotion: true,
hero: { duration: 1, distance: 40, initialDelayMs: 300 },
scroll: {
duration: 0.7,
distance: 24,
once: true,
margin: "0px 0px -15% 0px",
},
} as const;
- enabled — turn all Motion animations on/off
- respectReducedMotion — skip animations if the visitor prefers reduced motion
- Timing (speed) — see the next section
Animation speed
Hero and scroll are tuned separately. You only touch src/config/animations.ts and, for hero order, HeroSection-01.astro.
- Hero speed — animations.ts: hero.duration (seconds per element), hero.initialDelayMs (ms before start)
- Hero order — HeroSection-01.astro: data-motion-delay on each line (seconds, e.g. 0.3, 0.6, 0.9)
- Scroll speed — animations.ts: scroll.duration, scroll.distance
- One card slower? Add data-motion-duration="1" on that element only
- Too fast? Raise hero.duration (e.g. 1.2) or increase data-motion-delay values
How to use it in sections
Add attributes to the HTML element you want to animate. Do not convert the section to React. Two modes:
- Page load (hero): data-motion-hero="fade-up|fade-in|expand-x"
- On scroll: data-motion="fade-up|fade-in|expand-x"
- Optional: data-motion-delay="0.2" (seconds, stagger cards)
- Optional: data-motion-duration="1" (seconds, overrides config)
- fade-in — opacity only; fade-up — fade + move up; expand-x — horizontal line
Example: hero (already in the project)
See src/components/sections/hero/HeroSection-01.astro — badge, title, divider, subtitle, and CTAs each have data-motion-hero and staggered data-motion-delay values.
<span data-motion-hero="fade-in" data-motion-delay="0.3">Badge</span>
<h1 data-motion-hero="fade-up" data-motion-delay="0.6">Title</h1>
<div data-motion-hero="expand-x" data-motion-delay="0.9" class="h-px w-20" />
Example: scroll reveal (already in the project)
See src/components/sections/services/ServicesSection.astro — each card has data-motion="fade-up" and delay index * 0.1 for a stagger effect.
{list.map((item, index) => (
<div
data-motion="fade-up"
data-motion-delay={(index * 0.1).toFixed(1)}
class="rounded-xl border p-6"
>
...
</div>
))}
Where else you can use it
Any .astro section can use the same attributes. Good candidates on the home page: StatsSection (each stat), FaqSection-01 (heading or each details row), CtaSection-06 (inner card), TrustBadgesSection, HowItWorksSection (steps).
Service detail pages, About, Contact: add data-motion on the main heading or content blocks. Keep hero delays short on LCP-critical pages.
For React islands (e.g. TestimonialsSectionClient.tsx) use motion/react only if you need interaction inside the island — do not wrap whole Astro sections in React for scroll fade.
- New preset? Add a case in src/lib/motion-presets.ts
- Docs pages (/docs) usually stay static — skip heavy motion unless requested
Tips and troubleshooting
- Content invisible? Hard-refresh; check enabled: true and browser console for errors
- Animating too much hurts performance — prefer headings and cards, not every line
- Test with prefers-reduced-motion enabled in OS settings