# Cursor Gravity
URL: https://remocn.dev/docs/effects/cursor-gravity

A rounded triangular cursor tugs a button out of the screen edge with pulsed elastic resistance

- Install: `npx shadcn@latest add @remocn/cursor-gravity`
- Vibe: playful
- Natural length: 151f @ 30fps

## Installation

```bash
npx shadcn@latest add @remocn/cursor-gravity
```

## Usage

An empty scene. A soft triangular cursor arrives from below, approaches the
right edge and catches a hidden button. Two quick tugs pull its leading edge
into view, pause under load, and recoil. The button's back remains outside the
frame, stretching the surface into a narrow neck. A final pull frees the whole
button, which settles at center as the cursor exits below.

```tsx
import { CursorGravity } from "@/components/remocn/cursor-gravity";

export const Scene = () => <CursorGravity label="Create something" />;
```

`Tension` controls the recoil and surface deformation. The cursor’s rear bows backward under each tug while its tip stays at the
attachment point, then springs back as the effort releases. The pulse is in the
movement itself: a fast pull, a short hold, a small release, then a stronger
pull. All motion is frame-driven, including the spring and cursor shape.

## Custom content

The button has a 300 × 88 reference-pixel content slot. Supply `children` to
replace its label and arrow with your own content. The surrounding surface is
still the elastic button; text remains unstretched as it travels with the
material. Use compact content that fits the slot.

```tsx
<CursorGravity color="#e8ef9b" backgroundColor="#201426" tension={1.2}>
  <span style={{ fontSize: 26 }}>Start creating ↗</span>
</CursorGravity>
```

The composition's actual right edge is always the extraction boundary,
including in portrait and square renders. Element sizes scale with the smaller
of `width / 1280` and `height / 720`. A transparent background lets the button
appear over an existing scene.

## Timing

Reference frames at 30 fps and `speed={1}`:

| Frames | Motion |
| --- | --- |
| 0–10 | Empty scene |
| 10–39 | Triangular cursor enters from below |
| 39–48 | Cursor approaches and catches the right edge |
| 48–70 | First tug, hold, and recoil |
| 70–94 | Stronger second tug, hold, and recoil |
| 94–132 | Final pull; the trailing edge releases and springs inward |
| 132–151 | Button settles; cursor finishes exiting below |
| 151–180 | Centered button holds |

`cursorGravityLength` is 151, when the reveal and cursor exit have finished.
`getCursorGravityDuration({ speed })` includes the final hold and returns
frames at 30 fps. Multiply by `fps / 30` and round up for another frame rate.
Playback duration in seconds is consistent across frame rates. The final
button stays in place after the sequence ends.

## Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `children` | `ReactNode` | — | Replace the label and arrow; fits a 300 × 88 content slot |
| `label` | `string` | `"Create something"` | Default button label; long text truncates; ignored with children |
| `tension` | `number` | `1` | Elastic neck deformation and recoil, clamped to 0–1.5 |
| `cursorColor` | `string` | `"#fffaf0"` | Rounded triangular cursor and short attachment thread |
| `color` | `string` | `"#fffaf0"` | Elastic button surface |
| `textColor` | `string` | `"#32153c"` | Button label and arrow |
| `backgroundColor` | `string` | `"#a800b7"` | Full-frame background; accepts transparent |
| `speed` | `number` | `1` | Time multiplier; zero or negative freezes the empty initial frame |
| `className` | `string` | — | Class on the full-frame root |

## Use when
- An empty scene should reveal a call to action through a physical pulling gesture.
- A button needs to feel extracted from beyond the frame with tension, recoil, and release.

## Don't use when
- A cursor must accurately demonstrate an existing interface — use simulated-cursor.
- Two complete scenes need to swap — this reveals one element rather than replacing the whole frame.