# Pull-cord Bulb

A pendant lamp that is a switch. Pull the acorn: the detent resists and snaps, and the filament warms from dull red to white while the lamp sways. It can switch the whole page’s finish.

Object · Free · https://tactileui.dev/objects/pull-cord

- Version: 0.1.0
- Size: 26.9 KB gzipped
- Access: Free for anyone, MIT, open source
- Needs: pull-cord.mjs + pull-cord.css
- Tests: 19 tests, 12 states
- Lab: https://tactileui.dev/objects/pull-cord

## Use it

A toggle people want to touch. Its first job is the finish switch of a Tactile UI site (lit is aluminium, dark is graphite, remembered like the site's own key) hanging from the nav. The same switch is dark mode for any app, "lights on" for a room in a smart-home or IoT panel, or a focus-mode toggle in an agent or devtools console. It is a real role="switch" with aria-checked, a label, Space/Enter and a change event.

Copy the `pull-cord` folder from its package next to your page, link its stylesheet and construct it on an element. Every option has a default; the lab’s Code tab writes the same example at any settings, and for React, Vue, Svelte or a custom element ([Using a framework](https://tactileui.dev/docs/frameworks)).

```html
<link rel="stylesheet" href="pull-cord/pull-cord.css">
<div id="pull-cord" style="width: min(92vw, 760px); height: min(82vh, 620px)"></div>

<script type="module">
  import PullCord from './pull-cord/pull-cord.mjs';

  const pullCord = new PullCord(document.getElementById('pull-cord'), {
    on: true,
    shade: 'dome',
    bulb: 'clear',
  });

  // pullCord.update({ … });   change any option in place
  // pullCord.destroy();        and take it away again
</script>
```

## Options

| Option | Type | Default | What it does |
| - | - | - | - |
| `on` | boolean | true | Where the switch starts. Pull the cord in the preview to change it. |
| `shade` | dome · tube · bare | 'dome' | A spun dome, a tube, or a bare lamp holder with its switch pod on an arm. |
| `bulb` | clear · frosted | 'clear' | Clear shows the filament; frosted glows all over. |
| `finish` | auto · aluminium · graphite | 'auto' | Auto follows the page: the lamp is made in its finish (white on a porcelain page, black on a black one); without the kit’s tokens, aluminium on a light page and graphite on a dark one. |
| `warmth` | number, 2200 to 3200 | 2700 | The filament’s colour temperature when fully lit. |
| `cord` | number, 0.6 to 1.5 | 1 | The pull cord, as a share of the lamp’s own. |
| `light` | number, 0 to 345 | 315 | 0 is from above, clockwise. Every highlight and shadow follows it. |
| `target` | none · html | null | html: pulling it sets the page’s data-finish (on = aluminium, off = graphite) and remembers the choice. |
| `label` | text | 'Light' | What a screen reader calls the switch. |
| `sound` | boolean | false | A synthesised snap as the switch trips. Starts only after you touch the page. |

## Methods

- `new PullCord(element, options)`: Builds it inside the element with these options.
- `checked`: Whether the lamp is lit.
- `state`: 
- `pull()`: Pull the cord: the same physical pull a hand makes, animated.
- `toggle()`: Pull the cord from outside, as a hand would.
- `set(on, { instant = false } = {})`: Set the lamp without a pull (no `change` event).
- `update(opts = {})`: Change options in place.
- `destroy()`: Stops it and cleans up after it.

Events it fires, which bubble from the element: `change`.

## States

1. **At rest**: The lamp hangs plumb from its ceiling cup, the cord straight, the switch seated; the loop is asleep and costs nothing.
2. **Lit**: The filament burns at its blackbody colour, the inside of the shade glows, the rim's lip catches it, and a soft pool of warm light falls on the wall below, screen-blended onto the page, fading out before the edge of the object's box.
3. **Dark**: The glass reads as glass: the room lamp's softbox in it, a smaller inverted reflection from the back of the envelope, the stem, leads and coil inside a clear bulb.
4. **Tug**: A pull that does not reach the trip point stretches the cord out of the switch and tilts the lamp, then springs back without switching.
5. **Pull**: Under the detent the acorn visibly lags the pointer; at the trip the force drops and the acorn catches up: the snap; the lamp toggles and fires change.
6. **Release**: The plunger returns and re-arms with a softer tick, the cord bounces slack and swings, the lamp sways, and all of it settles and sleeps within about three seconds.
7. **Warming and cooling**: Heating against T⁴ radiation: on in about 130 ms through red and orange; off, a fast fade and then a dull ember that lingers.
8. **Keyboard and click**: Space, Enter or a click anywhere run the same physical pull, animated; the state changes when the switch trips, not when the key goes down.
9. **Finish switch**: With a target it writes data-finish (on = aluminium, off = graphite), remembers the choice in localStorage['tui-finish'] on the page root, and follows a finish changed elsewhere.
10. **Header toggle**: In a 56–64 px host under a nav it hangs on a long cable with its cord and acorn drawn heavier, and keeps a 44 px grip.
11. **Off screen or hidden**: The loop pauses; a returning tab advances one clamped frame.
12. **Reduced motion**: The switch still works: the state flips at once, the glow steps instead of ramping, nothing swings, and no frame is requested.

## How it works

Made of: Mass spring rope, Detent plunger latch, Hand spring coupling, Damped pendulum, Filament thermal model, Blackbody colour, Per pixel turned shading, Room fixed sheen, Screen blended light, Rim cutoff wall light, Finish switch, Sleeping loop.

Original. Canvas 2D on four layers: room-lamp shadows and the bulb's light on the wall at half resolution, the lamp, and the filament's light; the two light layers are mix-blend-mode: screen, so light adds to the page instead of covering it. Physics in metres, seconds and newtons at 1/16000 s sub-steps; every turned part is shaded per pixel once per size and finish. The integrator assigns cut (0 is not a valid cut, so the field is left out).
