Instruments

Per-device controls mounted into the cream-paper widget bar. Each render below is the actual <lab-widget-*> custom element from js/widgets/ — refactor a widget and this page updates automatically. Every device widget is grouped by control archetype. Use the index below to jump straight to one.

Electronic scale

<lab-widget-scale> · js/widgets/scale/ScaleWidget.js

The minimum viable widget: a label, a value with unit, and one CTA. Establishes the cadence used by every readout-style widget — Space Grotesk 22 px value, mono Plex 11 px unit hugging the descender.

Empty (0 g)
142.5 g

Spring scale

<lab-widget-spring-scale> · js/widgets/spring-scale/SpringScaleWidget.js

Adds a horizontal pill-shaped track with green fill and per-newton ticks alongside the readout. The tick rule (filled ticks turn semi-transparent white as the fill passes them) is shared with several other gauges.

0 N · empty
2.5 N · mid
Maxed

Hot plate

<lab-widget-hot-plate> · js/widgets/hot-plate/HotPlateWidget.js

Combines a binary on/off button (left) with a segmented intensity selector (Low / Med / High) and a power-state indicator dot. The active state is conveyed two ways — black-stamp button + glowing red dot. Canonical pattern for "device with intensity levels."

Off
On · Low
On · Medium
On · High

Colored flashlight

<lab-widget-colored-flashlight> · js/widgets/colored-flashlight/ColoredFlashlightWidget.js

Three independently-toggleable channels (R, G, B) plus a master ON/OFF and live status dots. Demonstrates non-mutually-exclusive toggle buttons inside the widget bar. The neighbouring color-dot indicators glow when the corresponding channel is lit.

Off · all dim
Red + Blue (magenta)
All three (white)

Gauge

<lab-widget-gauge> · js/widgets/gauge/GaugeWidget.js

Continuous value with a draggable thumb on a 5 px track. A green "sweet spot" region marks the target band — the student aims to land the thumb inside it. Pointer events are captured directly by the widget; no separate confirm.

At min · 0.0 °C
In sweet spot
At max · 100 °C

Slider (generic)

<lab-widget-slider> · js/widgets/slider/SliderWidget.js

Native <input type="range"> styled with the system thumb (green disk + 3 px hard shadow), plus a Space Grotesk readout and an OK button to commit. Used for any continuous parameter where the student should preview before submitting.

Volume 65 %
Freq 1000 Hz

Microscope

<lab-widget-microscope> · js/widgets/microscope/MicroscopeWidget.js

The most control-dense widget. Three independent axes: objective lens (mutually exclusive 4x / 10x / 40x), focus pair (Coarse + Fine, both toggleable), and Diaphragm (single toggle). Demonstrates how to fit several toggle groups + standalone toggles into one bar without crowding.

Initial · 4x · all toggles inactive
10x · coarse + diaphragm set
40x · fine + diaphragm

Cart launcher

<lab-widget-cart-launcher> · js/widgets/cart-launcher/CartLauncherWidget.js

Three-state machine (placed → armed → fired) with a state readout in the bar. Buttons disable when their action is invalid for the current state. Same pattern other state-machine widgets follow (microwave, stopwatch, generators).

Placed
Armed
Fired

Pour

<lab-widget-pour> · js/widgets/pour/PourWidget.js

The system's exception: a free-floating, vertical column rather than a yellow widget bar. Pinned in screen-space above the active liquid container, it shows a fill-track (color-coded by liquid temperature: blue / red / cyan / done-green), volume in mL above, and a status string below. Sets the visual vocabulary for the next round of "ambient" widgets.

Idle
Pouring · room-temp
Pouring · hot
Pouring · cold
Full · done

Thermometer

<lab-widget-thermometer> · js/widgets/thermometer/ThermometerWidget.js

ON/OFF probe with a Space Grotesk °C readout. The pilot of the readout-style family; every "value + unit" widget below shares its display rules.

Off
Room · 22 °C
Hot · 85.3 °C

Ammeter

<lab-widget-ammeter> · js/widgets/ammeter/AmmeterWidget.js

ON/OFF + segmented mA / A unit selector. Establishes the "readout with a unit toggle" pattern shared by graduated cylinder, ruler, and others.

Off
42.5 mA
1.235 A

Light meter

<lab-widget-light-meter> · js/widgets/light-meter/LightMeterWidget.js

Lux readout with a cyclic range button (auto / 2K / 20K / 200K). Same chrome as the ammeter but with a step-through control instead of a binary toggle.

Off
450 lux · auto
15K lux · 20K range

Humidity sensor

<lab-widget-humidity-sensor> · js/widgets/humidity-sensor/HumiditySensorWidget.js

Two stacked readouts in one bar: %RH primary, °C secondary. Pattern for any device that reports two correlated values.

Off
45 % @ 22 °C
78.3 % @ 19.5 °C

CO₂ sensor

<lab-widget-co2-sensor> · js/widgets/co2-sensor/Co2SensorWidget.js

ppm readout with a stabilizing-state visual: the value blinks while warming up, settles when stable. First widget to model a transient sub-state inside an "on" mode.

Off
Stabilizing
Stable · 1250 ppm

Graduated cylinder

<lab-widget-graduated-cylinder> · js/widgets/graduated-cylinder/GraduatedCylinderWidget.js

Volume in mL or L, toggleable. Always-on (no power button) — the cylinder is a passive measurement device.

25.5 mL
1250 mL → L

Ruler

<lab-widget-ruler> · js/widgets/ruler/RulerWidget.js

Distance with cm / mm toggle. Shares unit-cycle chrome with graduated cylinder — same family, different domain.

12.5 cm
4.8 cm → mm

Force sensor

<lab-widget-force-sensor> · js/widgets/force-sensor/ForceSensorWidget.js

The control-rich corner of the readout family: live N value, peak captured below it, plus Zero / Start-Stop recording buttons. Pattern for any tool that records over time.

Idle
Recording · 12.4 N (peak 18.7)

Crater depth

<lab-widget-crater-depth> · js/widgets/crater-depth/CraterDepthWidget.js

The measure_depth readout: a label and a centimetre value, no button — depth is geometric, there is no sensor to arm. The value arrives as depth_m (metres) on the telemetry bus keyed to the depth stick; the element renders cm to one decimal, and "--" before a crater exists.

Empty (no crater)
3.3 cm

Flashlight

<lab-widget-flashlight> · js/widgets/flashlight/FlashlightWidget.js

Single-channel light: on/off + a wide / narrow beam selector. The "binary toggle plus modal sub-control" template every directional emitter follows.

Off
Wide beam
Narrow beam

Laser pointer

<lab-widget-laser-pointer> · js/widgets/laser-pointer/LaserPointerWidget.js

The minimal toggle widget: one button, two states. Reference for every device whose only choice is on or off.

Off
On

Kitchen lighter

<lab-widget-kitchen-lighter> · js/widgets/kitchen-lighter/KitchenLighterWidget.js

Strike → lit → extinguish. Looks like a toggle but the third state (extinguished) is reachable only from "lit", giving a small directional state graph in a single button group.

Idle
Lit
Extinguished

Fan

<lab-widget-fan> · js/widgets/fan/FanWidget.js

Same shape as hot plate: power button + a Low / High segmented selector. Different domain, identical chrome.

Off
Low
High

Hair dryer

<lab-widget-hair-dryer> · js/widgets/hair-dryer/HairDryerWidget.js

Variant of the fan: same controls, different label. Confirms that copies of an archetype don't need a new visual primitive.

Off
Low
High

Toggle

<lab-widget-toggle> · js/widgets/toggle/ToggleWidget.js

Generic discrete on/off control: a two-sided switch showing both positions at once — circle for off on the left, line for on at the right (IEC 60417) — with a highlight that travels between them and changes colour as it goes: dark ink on the off side, green on the on side, following the base sheet's .off / .active stamp semantics, so the colour reports the device state rather than marking which side is selected. Both marks are drawn as inline SVG on a shared grid, not set as text: as characters the O reads as a digit zero and the control as a numeric readout. It is one button rather than two halves, so clicking anywhere flips it and there is no dead side to press; the rocker switch below keeps two targets because it authors a named state rather than flipping. Bound to a boolean object property, e.g. a circuit switch's closed state.

The flip is usually the last thing a step asks for, and the handler unmounts the control as soon as it commits — so the element moves the highlight first and emits toggle only once the slide has landed (240 ms travel, 20 ms settle). The step advances after the student has seen the new position, not before it renders. Presses during that window are ignored, but the control is never disabled. Under prefers-reduced-motion the travel is dropped and the dwell kept.

Off — seeded open
On — seeded closed
Click to watch the travel

Rocker switch

<lab-widget-rocker-switch> · js/widgets/rocker-switch/RockerSwitchWidget.js

Domain-specific binary toggle for circuit work — labelled "open" / "closed" instead of "on" / "off". Preserves the pattern but speaks the right vocabulary.

Open
Closed

Tumble buggy

<lab-widget-tumble-buggy> · js/widgets/tumble-buggy/TumbleBuggyWidget.js

Toggle plus a forward / reverse selector — the "device with a directional axis" variant of the fan/hot-plate shape.

Off
Forward
Reverse

Speaker

<lab-widget-speaker> · js/widgets/speaker/SpeakerWidget.js

Binary toggle decorated with animated wave bars while playing. Sets the visual rule for "audio currently emitting" used by the rest of the sound family.

Silent
Playing

Tuning fork

<lab-widget-tuning-fork> · js/widgets/tuning-fork/TuningForkWidget.js

Strike action + a "vibrating" indicator that decays over time. Same toggle shape as the speaker but with momentary semantics.

Still
Vibrating

Drum

<lab-widget-drum> · js/widgets/drum/DrumWidget.js

Same momentary-toggle shape as the tuning fork, with a "struck" indicator instead of "vibrating". Both widgets share their attack/decay rules.

Still
Struck

pH strip

<lab-widget-ph-strip> · js/widgets/ph-strip/PhStripWidget.js

No yellow chrome — just a coloured strip plus a numeric pH. The undipped state shows the strip blank; once dipped, the colour and number lock in.

Un-dipped
Acidic · pH 3
Neutral · pH 7
Basic · pH 11

BTB indicator

<lab-widget-btb-indicator> · js/widgets/btb-indicator/BtbIndicatorWidget.js

Coloured chip + categorical label (Acidic / Neutral / Basic). Demonstrates a colour-driven readout where the value is qualitative, not numeric.

Acidic
Neutral
Basic

LED (bi-color)

<lab-widget-led> · js/widgets/led/LedWidget.js

Three-state indicator (off / green / red). No chrome around it — drops directly onto the bench as a status light.

Off
Green
Red

Microwave

<lab-widget-microwave> · js/widgets/microwave/MicrowaveWidget.js

Four-state machine (door open → closed → running → stopped) with a digit timer. Adds a countdown readout to the cart-launcher pattern.

Door open
Door closed
Running
Stopped

Stopwatch

<lab-widget-stopwatch> · js/widgets/stopwatch/StopwatchWidget.js

Owns its own clock — increments live in the second tile. Same Space Grotesk tabular display rule as the time control bar, but mounted as a per-step measurement tool.

Idle
Running (live)

Hand-crank generator

<lab-widget-generator> · js/widgets/generator/GeneratorWidget.js

Stop / slow / fast / intermittent / reverse with a paired V + mA readout. The fullest example of how a state-machine widget adds quantitative outputs alongside its mode buttons.

Idle
Slow · 1.2 V · 0.15 A
Fast · 3.8 V · 0.45 A
Reverse · −3.8 V · −0.45 A

Homemade generator

<lab-widget-homemade-generator> · js/widgets/homemade-generator/HomemadeGeneratorWidget.js

Sister widget to the hand-crank generator: same state machine, but the readout is replaced by two indicator LEDs for a more qualitative student experience.

Idle
Slow (dim)
Fast (bright)
Reverse

Snapping track

<lab-widget-snapping-track> · js/widgets/snapping-track/SnappingTrackWidget.js

Continuous-input chrome that snaps to a fixed list of positions. Same shape as the gauge but the thumb can only land on authored angles.

0°
45°
90°

Smart-cart track

<lab-widget-smart-cart-track> · js/widgets/smart-cart-track/SmartCartTrackWidget.js

Pairs a numeric angle readout with a brake-release toggle: the cart only rolls when the brake is released. Demonstrates the "input + safety interlock" combination.

Flat · brake
15° · brake
25° · released

Preset

<lab-widget-preset> · js/widgets/preset/PresetWidget.js

Generic multi-choice selector for any ui: preset step. Wraps the segmented-toggle atom with the standard yellow chrome and emits a preset-select event with the picked value.

Three options
Five options

Text

<lab-widget-text> · js/widgets/text/TextWidget.js

A short authored line on the widget bar. First deployment is olfactory description on sniff steps — a step composes a phenomenon-framed close-in with widget: text + widget_text:, the binder forwards the copy and the eyebrow label ("Smell" for v1). Inherits the standard cream + ink + hard-shadow widget-bar chrome via widgetBaseStyles; local rule adds prose typography (Space Grotesk 14 px regular, 1.3 line-height). Fixed 56 px silhouette — copy stays within two lines under the 200-char schema cap (Phase B). Control entry in the telemetry registry; no events, no bus subscription.