# Jack-o'-lantern

A carved gourd lantern: a lobed shell on a scalloped lid and stem, a face cut one feature at a time with the plugs pushing out of it, a candle inside whose light is paid for by the openings, and every part coming off in the reverse of the order it was fitted.

> 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/jack-o-lantern.json
```

Registry item: `jack-o-lantern` · [`https://robocn.dev/r/jack-o-lantern.json`](https://robocn.dev/r/jack-o-lantern.json)

## Notes

- Carving is not limited to the faces it ships with: with `control="cut"` a drag puts a knife on the shell wherever the pointer is. The pointer is put *back* on the skin — the projection run backwards by search rather than by formula — so a cut follows the surface it is over, round the curve and across the ribs, and the light comes out of whatever was cut. Backspace takes back the last cut and escape clears them.
- It turns all the way round in any direction. `view` still names the camera the machine is drawn from; `azimuth` and `elevation` move from there, and because it is one camera every hidden-line cull, depth sort and projected cut follows it — there is no second drawing for the back.
- The face is cut out of the shell rather than drawn on it. Each feature is an outline in shell coordinates — degrees of azimuth from the front, station up the profile — wrapped onto the lobed surface of revolution, so an eye rides the furrows it crosses and is the right size on a curved skin rather than on a flat drawing.
- Carving is progress along a perimeter: the knife goes in at one point and travels round, and until the loop closes the plug is still shell. That is why a half-carved eye is a scored arc with a tool at its end and not a faded triangle.
- The light is paid for by the holes. Open area over the area of the skin is the share of the candle that gets out at all, and each opening divides that light by its own area and throws the square root of what it gets — so the mouth reaches further than an eye without being brighter per square unit. An uncarved shell emits nothing however hard the candle burns.
- Taking the lid off is a draught: the flame leans, shortens and dims, and most of the light then goes up out of the neck instead of through the face.
- Solved: the shell, the wrapped cuts, the carve, the plug offsets, the escaping light and its reach, the flame, and the teardown schedule. Illustrated: the glow inside the shell, the bloom at each opening, the wax and the stem's curl. There is no combustion model and no collision model — a plug passes through whatever is in its way, as in any exploded drawing.

## Usage

```tsx
import { JackOLantern } from "@/components/ui/jack-o-lantern"

// Carved and burning, which is what it does with nobody driving it.
<JackOLantern />

// Or watch it cut its own face.
<JackOLantern behavior="carve" />

// Or drive it, which stops the loop.
<JackOLantern carve={0.6} onCarveChange={setCarve} interactive />

// Take it apart: the stem, then the lid, then the candle.
<JackOLantern carve={1} exploded={0.8} control="exploded" interactive />

// Hand somebody a blank gourd and a knife, and let them turn it round.
<JackOLantern face="blank" control="cut" interactive />
```

## Props

| name | type | default | description |
| --- | --- | --- | --- |
| `carve` | `number` | — | How much of the face is cut, 0 uncarved to 1 finished. Features are cut one after another, and a plug only comes free when its own loop closes. Supplying it stops the loop. |
| `onCarveChange` | `(carve: number) => void` | — | Fires while it is dragged or keyed, so interaction works in controlled mode too. |
| `exploded` | `number` | — | The teardown, 0 seated to 1 every part clear — stem, then lid, then candle, in the reverse of the order they were fitted. At 0 every offset is exactly zero. |
| `onExplodedChange` | `(exploded: number) => void` | — | Fires while the teardown is dragged or keyed. |
| `behavior` | `"carve" | "flicker" | "teardown" | "static"` | `"flicker"` | What it does with nobody driving it: cut the face, burn shut, come apart and back, or hold carved and still. The default is the finished machine burning, so a parked one is a carved lantern rather than a blank gourd. |
| `face` | `"classic" | "grin" | "scowl" | "sly" | "blank"` | `"classic"` | Which face is cut. Every feature is a generator call rather than a drawing, and `blank` is a gourd with nothing cut in it yet. |
| `strokes` | `ShellStroke[]` | — | Cuts made by hand, each the path a knife took across the skin in shell coordinates. Supplying it takes control of them; leave it off and the machine keeps its own. |
| `onStrokesChange` | `(strokes: ShellStroke[]) => void` | — | Fires when a hand cut is finished, taken back with backspace, or cleared with escape. |
| `nib` | `number` | `0.055` | How wide a cut the knife leaves, in stations. Clamped to 0.015..0.18. |
| `azimuth` | `number` | `0` | Degrees the camera swings round the machine, on top of `view`. Any angle at all, and it wraps. |
| `elevation` | `number` | `0` | Degrees the camera rises above the view's own elevation, clamped to ±88 — over the top, or up from under the floor. |
| `onOrbitChange` | `({ azimuth, elevation }) => void` | — | Fires while the machine is being turned, by drag or by arrow key. |
| `teeth` | `number` | `4` | Teeth left standing in the mouth, clamped to 1..9. |
| `lobes` | `number` | `9` | Ribs round the shell, clamped to 5..13. The cuts ride the furrows they cross. |
| `flame` | `number` | — | Controlled flame, 0 out to 1 full. Left off, the candle flickers on its own and leans when the lid comes off. |
| `lit` | `boolean` | `true` | Blow the candle out without taking it away. |
| `control` | `"cut" | "orbit" | "carve" | "exploded"` | `"cut"` | What a drag does. `cut` puts a knife on the shell and cuts wherever it is dragged; `orbit` turns the machine; the other two hold the preset carve or the teardown, the way the rest of the set's machines hold a channel. Holding shift swaps the knife for the turntable. |
| `interactive` | `boolean` | `false` | Hand it to a person: drag 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 pool of light the openings throw on it. |
| `label` | `string` | — | Optional technical caption under the drawing. |
| `view` | `"plan" | "front" | "profile" | "iso"` | `"front"` | Where the camera stands. One machine, four projections: straight down, straight on, side elevation, or three-quarter from above. |
| `speed` | `number` | `0.32` | Cycles per second: one carve, or one teardown. The candle flickers seven times faster. |
| `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/jack-o-lantern.tsx`
