# Rowing erg

An air flywheel on a one-way clutch: drag goes as the square of rim speed, the damper vent sets the drag factor, and the drive spins it up while the recovery coasts it down.

> For the complete index, see [llms.txt](https://robocn.dev/llms.txt). A Markdown version of any page is available by appending `.md` to its URL or by sending an `Accept: text/markdown` header.

## Install

```bash
bunx --bun shadcn@latest add https://robocn.dev/r/rowing-erg.json
```

Registry item: `rowing-erg` · [`https://robocn.dev/r/rowing-erg.json`](https://robocn.dev/r/rowing-erg.json)

## Notes

- Solved: the drag. A fan flywheel is retarded by k·ω², and the vent sets k, so the drag factor k / I — the number a rower reads off a monitor — is the vent's and nothing else's.
- Solved: the clutch. While the chain is faster than the rim the wheel is driven and the handle holds k·ω² / sprocket, which is why an erg gets harder the faster it is pulled rather than the further. Below rim speed the clutch lets go and the wheel coasts on dω/dt = −k·ω²/I.
- Solved: where the fan is. Its angle is the integral of its own speed, so it spins up through the drive and runs down through the recovery instead of turning at a rate somebody picked.
- Solved: the counter-travel. The slide is still coming forward at the catch when the chain has already gone taut, so the handle and the seat travel opposite ways for a few percent of the cycle. The solver measures that window off its own samples; the two arrows turn accent when it is open.
- `solveErgCycle` integrates one stroke repeatedly until the speed it starts at is the speed it ends at, so what is drawn is the machine's steady state and not a spin-up. The component memoises the cycle on the geometry and samples it by phase, which is what keeps every behaviour a pure function of the clock.
- Illustrated: there is no rower. The handle and seat schedules are chosen ramps with the sequencing a coach would recognise — legs, body, arms, and the reverse coming back — and everything downstream of them is integrated from those ramps. No bearing friction, no chain mass, no stretch.
- Design note: docs/gym-machines.md.

## Usage

```tsx
import { RowingErg } from "@/components/ui/rowing-erg"

// Runs its own stroke.
<RowingErg behavior="row" />

// Open the damper and the drag factor goes up with it.
<RowingErg vent={1} />

// Or drive the stroke, which stops the loop.
<RowingErg strokePhase={0.3} onStrokePhaseChange={setStrokePhase} interactive />
```

## Props

| name | type | default | description |
| --- | --- | --- | --- |
| `strokePhase` | `number` | — | Where in the stroke it is: 0 at the catch, round to 1 at the next. Supplying it stops the loop. |
| `onStrokePhaseChange` | `(strokePhase: number) => void` | — | Fires while it is dragged or keyed, so interaction works in controlled mode too. |
| `behavior` | `"row" | "sprint" | "paddle" | "static"` | `"row"` | What it does with nobody driving it. These differ by rate, which is a physical difference: the drag factor belongs to the vent, so rowing harder changes the speed and the force goes as the square of it. |
| `vent` | `number` | `0.5` | The damper vent, 0 shut to 1 wide open: how much air reaches the cage. It sets the drag coefficient, and so the drag factor. |
| `showChain` | `boolean` | `true` | Draw the chain, which is painted in the accent colour only while the clutch is actually driving the wheel. |
| `interactive` | `boolean` | `false` | Hand it to a person: drag across it, or focus it and use the arrow keys. It eases back into the behaviour on release. |
| `showGround` | `boolean` | `true` | Draw the contact shadow and the ground line beneath it. |
| `label` | `string` | — | Optional technical caption under the drawing. |
| `view` | `"plan" | "front" | "profile" | "iso"` | `"profile"` | Where the camera stands. One machine, four projections: straight down, straight on, side elevation, or three-quarter from above. |
| `speed` | `number` | `0.4` | Strokes per second, and a real rate: the dynamics are solved at it, so pulling the speed up makes the handle heavier. |
| `animate` | `boolean` | `true` | Off parks the machine at phase and stops rendering. A reduced-motion preference does the same. |
| `paused` | `boolean` | `false` | Freeze where it stands. |
| `phase` | `number` | `0` | Seconds of offset, so a row of machines breaks step. |
| `variant` | `"solid" | "outline" | "blueprint" | "wire"` | `"solid"` | How the machine is painted. Geometry never changes between variants. |
| `size` | `"xs" | "sm" | "md" | "lg" | "xl" | number` | `"md"` | Rendered width in pixels, or a step on the scale. |
| `color` | `string` | `var(--robot-shell)` | Body panels — the colour the machine reads as. |
| `accent` | `string` | `var(--robot-accent)` | Status colour: tip light, live tool, readouts. |
| `metal` | `string` | `var(--robot-metal)` | Bare machined parts: collars, bolts, tool bodies. |
| `dark` | `string` | `var(--robot-dark)` | Cast joints, base, shadow side. |
| `palette` | `Partial<RobotPalette>` | — | Override any subset of roles at once, including glow and grid. |

## Source

- `src/components/ui/rowing-erg.tsx`
