Motion
Motion is as quiet as the rest of the system. Colour and opacity change; things rarely move, and nothing scales, bounces or springs.
Durations and curves
Section titled “Durations and curves”Four durations: --motion-fast for a tint change on a control, --motion-base for a surface change, --motion-slow for geometry such as a section folding, and --motion-entrance, which belongs to the dialog’s entrance and a change of theme and nothing else. Three curves: --ease-enter for things arriving, --ease-exit for things leaving, --ease-move for things changing size in place.
motion.css combines them into four transitions, --transition-tint, -surface, -fade and -collapse. Use those before writing a duration by hand.
| Token | Value | Description |
|---|---|---|
--motion-fastmotion.fast | 120ms | Tint on a control: press, tab select, link. |
--motion-basemotion.base | 160ms | Surfaces: nav solidify, dismiss, list item enter. |
--motion-slowmotion.slow | 240ms | Geometry: height collapse, rail reflow. |
--motion-entrancemotion.entrance | 350ms | The dialog entrance and the colour fade of a theme change, nothing else. |
--motion-shiftmotion.shift | 8px | The only translate distance in the system. |
--ease-enterease.enter | cubic-bezier(0, 0, 0.2, 1) | Arriving or settling. |
--ease-exitease.exit | cubic-bezier(0.4, 0, 1, 1) | Leaving. |
--ease-moveease.move | cubic-bezier(0.4, 0, 0.2, 1) | Changing size or position in place. |
Three primitives
Section titled “Three primitives”Every set that changes uses three classes: .ds-reveal-in (fade in from 8px below), .ds-fade-out (opacity only) and .ds-collapse (height). A list that gains and loses members runs them in one order: leaving items fade out, the rest move, arriving items reveal in. --motion-shift is the only distance anything travels, and reveal-in is the only place it is used.
There is no scroll-triggered reveal, no parallax and no skeleton shimmer. A change of page fades with page transitions, which a site imports on its own. The one looping animation is the indeterminate progress bar, which always has a label.
Changing the theme
Section titled “Changing the theme”The theme is one store per page, themeStore() from @rottensea/ds-personal-website/scripts/theme, and only the store changes it. It holds the reader’s choice (light, dark or system) and the theme in use, and it writes data-theme, the choice in localStorage, and the fade. It follows the OS while the choice is system, and other tabs of the site as they change the choice. ThemeToggle and SettingsMenu ask for a change with set(); SettingsMenu, Mermaid and SharePoster hear of one with subscribe(), so the panel always shows the current choice and a diagram draws again once for each real change.
import { themeStore } from "@rottensea/ds-personal-website/scripts/theme";
const theme = themeStore();theme.set("dark");theme.subscribe((state, previous) => { if (state.theme !== previous.theme) redraw();});Every change fades every colour on the page together, over --motion-entrance with --ease-move. The store marks <html> with data-theme-fade for that time; while the mark is there, base.css gives every element the same colour transition in place of its own, so a button’s hover tint does not run at its own pace. What a script draws is not a CSS colour: Mermaid diagrams and SharePoster draw again in the new theme and fade the new drawing in over the old one with fadeIn, from the same module.
A script that writes data-theme itself is still taken as a change, faded and passed to the subscribers, with a warning in development. Its fade can come too late if it reads styles straight after writing, so set() is the way.
Reduced motion
Section titled “Reduced motion”Motion drops to 1ms at two levels: the system’s prefers-reduced-motion setting, and a switch a site sets as data-reduce-motion="true" on <html>. Because either can be on at any time, motion only confirms an action; it never carries information of its own.
Two backgrounds keep moving under both: Starfield and DotGlobe. Their drift is slow and even, with nothing that flashes or jumps, and the site wants them alive.