feel
Back to list

diip3sh/feel

Feel

GitHub

Build UI interactions whose motion comes from a physical model instead of durations and easing curves. Turn a feel described in words ("a toggle that feels heavy", "a drawer with a magnetic snap", "a card you throw into a corner", "a knob with heavy detents", "a sticky slider") into a production component driven by mass, stiffness, damping and force fields (magnets, friction, walls), on a line, in a plane or around a dial, plus a test rig that plots position, velocity and the force field, overlays the model's prediction on the measured motion, and exports the same spring to Framer Motion, react-spring, SwiftUI, Compose or CSS linear(). Use this whenever someone asks for an animation, transition, spring, gesture, drag, flick, snap points, bottom sheet, toggle, slider, carousel or any micro-interaction that should "feel" a certain way, or wants to tune or explain a spring, even if they never say "physics".

MotionInteractionFrontendCraft0 demos

Real-world examples not captured yet

This skill shows a gallery of live HTML demos (CDN libraries welcome — GSAP, Three.js, etc.) rendered inline via srcdoc.

Skill markdown
# Feel

This skill makes interactions that move like things. A toggle's thumb is a body with mass on a spring. A drawer is a body coasting through a field of magnets. A drag is a force: the pointer pulls the body through a hand spring, so a heavy thing lags the finger and a light thing doesn't. Nothing in the result has a duration or an easing curve. How long a motion takes is a consequence of the model, and the rig can show you exactly why.

One complete page ships with the skill: `index.html`, with four components, each in its rig: a heavy toggle (a spring), a magnetic drawer (a field), a card thrown to a corner (a body in a plane) and a detented knob (a body in degrees). Open it before you start. Serve the folder (`python3 -m http.server`) and play with all four.

## The one rule

**Every motion comes from a physical model: mass, stiffness, damping and forces. No hand-picked durations.**

That means:

- No `transition: … 300ms`, no `animation-duration`, no `cubic-bezier`, no `ease-out`, no `duration:` options, no timers that move things. `scripts/audit.mjs` fails on any of them.
- A tap is a target change (springs) or an impulse (fields), never a timed slide.
- A drag sets a hand, not a position. Release keeps the body's real velocity.
- A decision on release (which side, which stop) is made from where the body would coast, `x + v·m/c`, or by simulating forward. It is never made from a threshold in milliseconds.
- If a library forces a duration (CSS), the duration is computed from the model, and the code comment says so. `Feel.handoff` does this for you.

It fails in two ways:

- **Tween in disguise.** The code uses a spring but snaps the target on a timer, or reads pointer velocity and throws it away on release. The motion looks springy and feels dead.
- **Untuned physics.** It's a real spring, but the numbers were typed until it "looked fine". Physics with arbitrary numbers is just a fancier easing curve. Start from words, check them against the rig, and write down why each number is what it is.

## Workflow

1. **Size it, before anything else.** Tell the user which size this is.
   - **Interaction**: one component, one body (toggle, button press, card lift, slider thumb). 20–45 minutes. Build it without asking.
   - **Gesture system**: several bodies or a field (bottom sheet with stops, carousel with momentum, reorderable list). 1–3 hours. Say so, then build it.
   - **Motion system**: a whole product's motion vocabulary (tokens, every component, docs). Half a day or more. Don't start it. Propose the 3 interactions that matter most, and list what the full system would add.
2. **Words to physics.** Write the feel as words and run `Feel.words("…")`. It returns mass, natural frequency and damping ratio, then stiffness and damping. `references/vocabulary.md` has every word and how words blend. If the user's word isn't there, map it to the nearest ones and say which you used. Then write one sentence per number:
   - *frequency 1.35 Hz:* slow enough to read as weight;
   - *ζ 0.74:* a 3% overshoot, the thump of something landing;
   - *mass 3:* lags a fast drag by a few px and coasts 80 px per 1000 px/s.
3. **Choose the model.** `references/models.md` covers them:
   - **Spring to a target** for anything with two or more discrete states chosen by the code (toggle, press, expand).
   - **A field** (magnets, friction, walls) for anything the user throws (sheets, carousels, pickers). A field has no target: the body ends up wherever the field holds it.
   - **Walls** at the ends of travel, for rubber-banding. Overshoot past a physical end is caught by the wall, not drawn through it.
   - **A plane** (`Feel.Body2D`) for anything thrown in two directions at once (a picture-in-picture card, a floating panel). Corners and snap points are `Feel.points`, measured in a straight line. Two 1D bodies can't do this, because which stop is nearest depends on both coordinates.
   - **An angle** (`Feel.Body` with `unit: "°"`) for knobs, dials and wheels. Detents are `Feel.magnets` in degrees; a wheel that turns forever adds `period: 360`.
4. **Build the component on a body.**
   - Write one `styles(b)` that returns `[[el, props], …]`: transform from position, anything else (fill, shadow, stretch) from position or speed. Paint it with `Feel.paint(styles(b))` in `onFrame`.
   - Bind input with `Feel.drag(el, body, { axis })`: `"x"`, `"y"`, `"xy"` for a plane, `"angle"` for a turn.
   - Keyboard reaches every state.
   - `prefers-reduced-motion` jumps to rest. The runtime does this for you.
   - To let the browser play the motion (CSS keyframes, off the main thread), call `Feel.handoff(body, styles)`. Register any custom property it animates with `@property`.
   - Framework-free: copy the closest of `components/heavy-toggle.js`, `magnetic-drawer.js`, `corner-card.js` or `rotary-knob.js`. React: `runtime/react/useFeel.ts`.
5. **Put it in the rig.** `Feel.rig(host, { body, stage, travel, stops, feel, actions, handoff })`. Check ev

…