# Dynamization

> Dynamization Core

- Skill: `chanlik1208-dev/dynamization` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add chanlik1208-dev/dynamization`
- Raw SKILL.md: https://api.skillmd.com/api/skills/chanlik1208-dev/dynamization/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: chanLik1208-dev (https://skillmd.com/u/chanlik1208-dev)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/chanlik1208-dev/dynamization

---


# Dynamization Core

> Motion is the language of **time**: where a thing came from, where it went, whether you can touch it yet.
> Luminance is the language of **space**: what is on top, what is alive, where to look right now.
>
> Used separately, each does half a job. "Lifting" is not moving something up 4px — it is
> **displacement + a larger, softer shadow + a brighter surface, all at once.** Only then does the
> brain read it as *that came closer to me*.
>
> Interfaces that feel "stiff" or "flat" almost never suffer from an inelegant curve. They violate
> physical intuition, or they change only one property when they should change three.

**This pack owns no API.** Every judgment below is written as a number a human eye can verify —
a duration in seconds, a distance in pixels, a damping ratio, a luminance step. The runtime is a
detail you pick up in §2 and then stop thinking about.

## 0. Language

Every file in `references/` is English. Full translations of the judgment chapters
(this file, `feel`, `contrast`, `recipes`, `pitfalls`, `errata`) live in `i18n/<locale>/`:

| Locale | Path |
|---|---|
| 繁體中文 | `i18n/zh-TW/` |
| 日本語 | `i18n/ja/` |
| 한국어 | `i18n/ko/` |
| Español | `i18n/es/` |

**If you are answering the user in one of those languages, read that locale's files instead of the
English ones.** The adapters (`references/adapters/`) are English-only by design — they are mostly
code and API identifiers, where translation adds noise and drift.

## 1. The spec vocabulary

Everything in this pack is written in six terms. Learn them once; they are what gets ported.

| Term | Means | Written as |
|---|---|---|
| **dur** | how long, in seconds | `0.25s` |
| **curve** | `out` (fast then settling), `in` (slow then accelerating), `inout`, `linear` | `out` |
| **spring(Dv, b)** | a spring by **visual duration** `Dv` in seconds and **bounce** `b` in `0–1` | `spring(0.3, 0.15)` |
| **travel** | displacement, in px, from where the thing actually was | `y −4px` |
| **lumin** | a luminance step, in surface-value or shadow-tier terms | `surface +1 tier` |
| **stagger** | interval between siblings, in seconds | `0.04s` |

`spring(Dv, b)` is the load-bearing one. `Dv` is how long the movement **looks** like it takes,
excluding the settling tail — which is why a spring and a tween can be lined up against each other
at all. `b = 0` is no overshoot; `b = 1` is extremely bouncy. Never specify a spring in
stiffness/damping in a design conversation; nobody can picture those. Convert at the boundary →
`references/spring.md`.

## 2. Pick a runtime, then get out of its way

Read the adapter for the runtime you are in. **Read one. Not all of them.**

| Runtime | Adapter | Tier |
|---|---|---|
| CSS only — transitions, keyframes, `linear()` easing, view transitions | `references/adapters/css.md` | 3 |
| Web Animations API — `element.animate()`, `ScrollTimeline` | `references/adapters/waapi.md` | 2 |
| Luau / Roblox — `TweenService`, `TweenInfo`, `RunService` springs | `references/adapters/luau.md` | 2–1 |
| Anything else — a game engine, a native toolkit, an animation library | `references/adapters/porting.md` | — |

**Tier** is the one thing about a runtime that changes your *design*, not just your syntax:

| Tier | The runtime can | Consequence for what you design |
|---|---|---|
| **1** | interrupt an animation and carry **velocity** across the interruption | everything in this pack works as written |
| **2** | interrupt, but restarts from rest (velocity lost) | shorten reversals; prefer `b ≤ 0.15` so a restart is not visible as a hitch |
| **3** | only run to completion, or snap on interrupt | keep `dur ≤ 0.2s` on anything the user can re-trigger; fake springs with a sampled curve |

Most runtimes are Tier 2 by default and Tier 1 only if you hand-integrate the spring. The adapter
for your runtime says which, and how to climb a tier if the interaction needs it.

**Ask whether you need a library or a spring at all.** For one element, one state, and no
re-triggering, a plain tween is enough. Springs earn their complexity through interruptibility,
velocity handoff, and gesture handoff. If you use none of those, do not pay for them.

## 3. Five golden rules

Clear all five before writing any animation. Break one and the result feels *wrong* in a way people
usually cannot name.

### 1. Springs for position and size, tweens for opacity and colour

This is not taste, and it is not one library's opinion — it falls out of how the brain classifies
what it is looking at.

| What is animating | Use |
|---|---|
| position, rotation, skew — `x` `y` `rotate` and friends | `spring(0.28, 0.2)` — slight overshoot |
| the scale family | `spring(0.27, 0)` — **no overshoot, ever** |
| opacity, colour, blur, everything else | tween, `dur 0.3`, curve `out` |
| three or more keyframes | tween, `dur 0.8`, curve `inout` |

Why: things that occupy space (position, size) are read by the brain as **objects**, and objects
have mass and inertia. Opacity and colour are not objects — they are just *whether you can see it* —
so a spring there reads as a flicker.

**Corollary: never bounce a scale.** Bouncy growth reads as hitting glass.

Those four rows are a calibrated set. When you do not know what to use, use them unmodified.

### 2. Animations must be interruptible, and should carry velocity across the interruption

The single most important rule, and the one most often missed. Changing your mind mid-animation is
normal human behaviour.

- Use a spring where you can: a real spring integrator carries current velocity, so a reversal does
  not slam to a halt. **This — not bounciness — is the real reason springs beat tweens.**
- On a Tier 2/3 runtime, at minimum read the **current** value and animate from there, never from the
  nominal start. Snapping back to the start on every re-trigger is the classic stutter.
- Never chain animations with a timer that cannot be cancelled as a unit. Use a sequence or a
  parent-child orchestration that can be cancelled whole.
- Any "transition" mechanism that snaps to the end state when interrupted (many built-in
  page-transition APIs do) is unsuitable for anything the user can re-trigger quickly.

### 3. Entry and exit are not symmetric

Nobody should wait for something that is leaving.

```
enter:  opacity 0→1, y +8→0    dur 0.25   curve out
exit:   opacity 1→0, y 0→+4    dur 0.15   curve in
```

Exit runs at roughly **0.5–0.7×** the entry duration, over a **shorter distance**. A departing
element does not need to travel the full path — the eye only needs to know that it left.

### 4. Timing tiers: different jobs get different budgets

| Tier | dur | Where | Curve |
|---|---|---|---|
| Immediate feedback | `0.1–0.15s` | press scale, checkbox, focus ring | `out` or `spring(0.15, 0)` |
| Micro-interaction | `0.15–0.25s` | hover, tooltip, button colour | `out` |
| Component transition | `0.25–0.4s` | dropdown, modal, accordion, reflow | `spring(0.3, 0.15)` |
| Page / narrative | `0.4–0.8s` | route change, hero, multi-keyframe | `spring(0.5, 0.1)` + stagger |
| Over `1s` | almost certainly wrong | only loading, ambient, scroll-linked | — |

Hover animations **must not exceed 0.2s** — the cursor may already be gone.
Longer distances may run slightly longer, but **not linearly**: double the distance buys roughly
20–30% more time, not 100%.

> Full reasoning, the human semantics of spring parameters, orchestration rhythm, anti-patterns →
> `references/feel.md`

### 5. One event should change several properties in the same direction

A single property carries too little information. **Multiple properties moving together is what the
brain reads as one physical event.**

| To express | Change at least |
|---|---|
| Lift / approach (card hover) | `y` up + shadow larger and softer + surface brighter |
| Press / recess | scale down + shadow tightens + inner shadow + darker |
| Picked up (dragging) | scale up + wide shadow + raised depth order |
| Focus (modal opening) | content enters + **background dims** (the scrim must land first) |
| Disabled | a dedicated low-contrast colour token (**not** 50% opacity) |

Luminance is a *state signal*; displacement is a *process*. So **luminance changes faster than
movement** (roughly `0.12–0.15s` against `0.2–0.35s`).

In dark themes shadows are nearly invisible, so elevation has to be expressed by *brighter surfaces*
instead. Switching themes swaps the mechanism, not just the palette.

A third thing is sometimes on screen — **texture**: hatching, grain, a repeating rule. It is not a
channel and not a spec term. It is a modifier of `lumin` that reinforces the ground an object rests
on, and it must stay quieter than the smallest luminance step in your ladder, or it reads as noise
instead of material. It also always loses to text: nothing hatched sits behind running text, and in
any region with words the texture must not be what the eye reaches first. Reinforcement and accent
only → `contrast.md` §8.

> The optical model, light/dark token sets, the cost of animating each luminance property,
> accessibility floors → `references/contrast.md`

## 4. Routing table

Read the file for the job in front of you. **Do not read them all.**

| What you are doing | Read |
|---|---|
| Understanding what "natural" means; can't get the feel right; someone said it's "stiff" | `references/feel.md` ← **the time axis** |
| Elevation, shadow, dark mode, focus, scrim, contrast | `references/contrast.md` ← **the space axis** |
| Turning `spring(Dv, b)` into whatever numbers your runtime wants | `references/spring.md` |
| You want a finished effect to build from | `references/recipes.md` |
| Writing the actual calls in CSS / WAAPI / Luau | `references/adapters/<runtime>.md` |
| Your runtime has no adapter here | `references/adapters/porting.md` |
| Nothing animates, it janks, exit won't fire, the reversal stutters | `references/pitfalls.md` |
| **Nothing looks broken, and you are about to say you are done** | `references/errata.md` ← **read it before you ship** |

Non-English locales: substitute `i18n/<locale>/` for `feel`, `contrast`, `recipes`, `pitfalls`, `errata`.

## 5. Accessibility is not optional

Anything that **moves or scales a large element** must respect a reduced-motion preference. Every
platform exposes one; the adapter names it for your runtime.

The correct behaviour is not "turn animation off". It is: **disable displacement and scale, keep
opacity and colour.** The user still learns that the screen changed; it cross-fades instead of
sliding. Parallax, autoplaying video and infinite loops always need an explicit branch on top.

## 6. Performance red lines

The universal law: **compositing is cheap, painting is expensive, layout is ruinous.** Every runtime
has some version of these three tiers, even ones with no DOM.

- ✅ **Always safe**: transform (translate / scale / rotate) and opacity — they never reflow anything
- ⚠️ **Paint** (measure it): shadows, corner radius, background colour, blur — fine on small
  elements, dangerous on large ones or long lists
- ❌ **Layout** (avoid): width, height, top, left, margin, padding, border width — animating these
  re-solves the layout of everything around them, every frame

The standard escape hatch for an expensive property is to **pre-render both states and cross-fade
their opacity** rather than animating the expensive property itself. That trick appears in
`contrast.md` §6 for shadows and generalises to almost everything in the ⚠️ row.

## 7. Where the numbers come from

The parameter tables in this pack are a calibrated set, cross-checked against the defaults shipped by
widely used animation implementations and against the perceptual literature they were tuned on. They
are stated here as plain numbers precisely so that **you never have to fetch a vendor's
documentation to use this pack**, and so that nothing here rots when a library changes its API.

Where this pack and your runtime's own documentation disagree about *what an API does*, the
runtime's documentation wins — it is describing its own behaviour. Where they disagree about *what
feels right*, prefer this pack, then verify with your own eyes: a 0.3s spring either reads as an
object or it does not, and that is not a matter of opinion you need a citation for.

