Virtual Lab Design System
The visual layer behind every Galileo investigation. A creamy lab-bench surface, hard-offset shadows, Space Grotesk display type for instructions, IBM Plex Sans for body, IBM Plex Mono for labels and numeric readouts. Every value on this page is read live from the kit — tokens from galileo_viewer_kit/css/tokens.css, atoms from css/atoms/*.css, handlers from js/handlers/**/*.css, and widgets directly from the <lab-widget-*> custom elements.
Explore the library
Where to start
Six entry points into the lab's UI vocabulary. The first three are what you'll touch every day; the last three back them with the typography, spacing, and palette they're built from.
Conventions
Three rules that explain most of the system
| Convention | What it does | Where you see it |
|---|---|---|
| Two type families | Plex Sans for chrome, Space Grotesk for narrative voice, Plex Mono for numeric readouts. Three voices kept strictly apart so each role reads at a glance. | Buttons · step instructions · value readouts |
| Hard offset shadows | No blur. Every elevated surface uses a 2–6 px solid offset (2px 2px 0, 5px 5px 0, 6px 6px 0) that reads as ink-print, not glass. |
Step bar · widget bar · completion screen |
| Tight rectangles | --radius-full: 2px is the default for buttons and bars; true circles use --radius-pill: 9999px. Chosen to look stamped, not glassy. |
Buttons · widget bar · step-bar phase chip |
Typography
Three families. IBM Plex Sans carries the chrome — buttons, counters, captions, body copy. Space Grotesk carries narrative voice — step instructions, modal titles, the value displays inside widget bars. IBM Plex Mono carries data — numeric readouts, unit labels, the phase chip.
Families
IBM Plex Sans — --font-family
UI default. All buttons, counters, labels, body copy. 400 / 500 / 600 / 700 / 800 weights are loaded.
Space Grotesk — --font-display
Display + readout. Step-bar instructions (24 / 700), value displays (38 / 600 with tabular numbers), pause-stamp pills (18 / 700). Optical-size axis tuned via opsz.
Scale
Six steps from caption to display
Used directly as var(--font-size-*). Below each row shows the resolved px value plus a sample at that size. Both --font-size-* values resolve from tokens.css at load time.
Weights
Spacing
The lab's rhythm. Six steps from 8 px to 32 px built on 0.5 rem increments. Use the tokens — never literal pixel values — so screen-density and accessibility tweaks ripple through the whole UI from one edit. Each row reads its width from var(--space-*) directly, so the bars stay accurate if the scale ever shifts.
Scale
Six tokens, one rhythm
The widget bar's internal gap is --space-md. Card padding is --space-xl. Step-bar gap between progress and header is --space-xs. Don't reach for literals — extend the scale instead.
Cheat sheet
Where each step lands in the wild
| Token | Typical use | Where you'll see it |
|---|---|---|
--space-xs | Inline, tight | Step-bar header gap, completed-pill badge gap |
--space-sm | Compact group | Toggle-group internal gap, range-input thumb gap |
--space-md | Default | Widget-bar inter-control gap, time-control gap |
--space-lg | Loose group | Scale-bar value-pill gap, completion-screen icon→title |
--space-xl | Card gutter | Modal padding, card margins, section breathing room |
--space-2xl | Page gutter | Catalog page padding, landing-screen vertical rhythm |
Density
Hand-sized chrome heights
A few non-token sizes are baked into the system because they're physical-feeling constants — students press them with fingers (eventually on tablet) and they need to feel uniform across handlers. These come from the actual atom + handler CSS, not from --space-*.
Color
A creamy lab bench, a sticky-note yellow chrome, near-black ink, and a single semantic green for "go." Every fill below is background: var(--token) — the displayed hex is read live from getComputedStyle at page load.
Surface
Lab bench & ink
Brand & chrome
Yellow stick & "go" green
Semantic
Status & alert
Neutral
Greys
Radii & shadows
Tight rectangles by default, true circles only when intended. Hard-offset shadows — never blurred — so every elevated surface reads as ink-stamped paper.
Radii
Five steps
--radius-full: 2 px is the system default. K12 uses "full" for a tight rectangle (rubber-stamp aesthetic) — true circles use the wrapper-only --radius-pill.
Shadows
Hard-offset library
No blur. Every shadow uses solid offsets in pixels — that's the look. The widget bar and step bar have the most pronounced ones (5–6 px) so they hover above the canvas.
Motion
Two transition tokens
--transition-fast
150ms ease-out
Hover, press, toggle. Buttons + indicator dots + step-bar segment fills.
--transition-normal
250ms ease-out
Larger surface entrances (modal in, completion fade), settle animations.
Atoms
Every button, pill, indicator, toggle, and range primitive applied here uses the same class names handlers and widgets reach for. Sources: galileo_viewer_kit/css/atoms/button.css + pill.css for the global stamps; js/widgets/_base/widget-base-styles.js and js/handlers/widget/widget-bar.css for the widget-button family that lives with its surface.
Buttons
.widget-btn — green stamped chip, the workhorse
The primary affordance inside every widget bar (Tare, Start, Ignite, Launch…). Green-success fill with a 2 px hard-offset shadow, scaled down on press. Four state modifiers cover the patterns: .active is an explicit alias of the default green for toggles that flip an engaged state; .inactive is the cream-ghost "not currently selected" option in a radio group; .off is the dark-ink "currently off" state of a binary on/off master toggle (pairs with .active as the on state — label text carries the literal "OFF" / "ON"); :disabled mutes to a faded outline. Sample below renders the live .wb-btn (light-DOM mirror); shadow-DOM .widget-btn is visually identical and used inside <lab-widget-*> custom elements.
.btn-primary-dark — Space Grotesk black stamp
Heavy primary CTA used for destructive confirms and the prominent "Unpause" action. Space Grotesk 700, 18 px, hard 3 px shadow.
.btn-step-continue — yellow Continue stamp
Surfaced by the step bar when a ui: button step needs an explicit Continue. The .unpause variant flips to a black stamp during settle pauses.
.btn-completion-* — completion screen
Primary green + white outlined secondary. Stacked horizontally inside the success card.
.zoom-in-btn — floating yellow stamp
Auto-appearing zoom-in trigger. Visible while a camera_preset step is loaded. .active flips to a black stamp once the student has zoomed in.
.modal-btn-* — modal actions
Primary green for affirmative confirms, .danger black stamp for destructive ones, white outline for cancel.
Pills & stamps
Static label primitives
Contents chips
Always-on labels for liquid containers
A container with a declared properties.contents renders a screen-space chip above it, displaying the authored identity (Cold water, HCl 1M, Mixture, …) prefixed by any hazard badges from a closed taxonomy. Identity only — temperature, pH and concentration stay on instruments. Empty containers carry no chip. Owned by ContentsChipHandler; design captured in journal/007/2026-05-13_16_contents_label_chips.md.
Identity, no hazard
Plain chip when the liquid is benign. Example: a freshly poured beaker of water.
Physical-state hazards — hot & cold
An orange disc on liquids above ~40 °C; a blue disc on those below ~10 °C. The qualifier in the identity string (Warm, Cold) is the author's initial condition only — it drops once the simulation perturbs the contents (heating, mixing), at which point the chip reverts to the base identity (Water).
Chemical hazards — corrosive
A red-bordered diamond with a black centre dot — categorical hazard mark, not strict GHS — for both acids and bases. Authoring vocabulary keeps corrosive_acid vs corrosive_base separate so sub-type lessons can elaborate later, but the chip collapses both to one mark; an acid + base mixture therefore shows a single diamond, not two. After cross-species pour the destination becomes Mixture and inherits the union of source hazards.
Focus dimming
A step's focus_set (default = union of object: / objects:) names the containers the student should attend to. Out-of-focus chips recede at chip level — the frame fades to a hairline, the drop-shadow drops, and the text greys. Hazard badges keep full strength so safety information is never lost. An explicit empty focus_set: [] turns dimming off everywhere.
UI components
The DOM surfaces handlers render over the Unity canvas. Each render below uses the same DOM structure and class names the handler emits at runtime — only positioning is unwrapped (handlers anchor with position: fixed; previews use position: relative) so multiple instances can stack inline.
Step bar · 5 states
Step bar
StepBarHandler · #step-bar · handlers/stepbar/step-bar.css
Top-center card. Phase progress, phase chip, step counter, Space Grotesk instruction (v1 chrome — 22 px / 1.3, instruction-dominant), optional Continue / Unpause / Restart action. Always visible during an investigation.
Info modal · 3 variants
Modal
ModalHandler · #modal-backdrop · handlers/modal/modal.css
Centered card with blurred backdrop. Top accent ribbon: green for Information / Destructive, yellow for Confirm — variant signal moves to the stamp + label below. Stamp icon (cyan i / yellow ? / ink !), mono-uppercase stamp label (INFORMATION / CONFIRM / CONFIRM), Space Grotesk 22 title, body, and a full-bottom action row (single button = full-width; paired buttons = equal 50/50 split) so the layout anchors horizontally instead of trailing into an empty bottom-right corner. Three callable shapes: info (single Done), confirm (cancel + green save), destructive confirm (cancel + black-stamp danger). Dismissal goes through the explicit action button — there's no separate close X.
Convection current
Save measurements?
Restart investigation?
Completion screen
Completion screen
CompletionHandler · #completion-backdrop · handlers/completion/completion.css
Full-viewport success card. Top accent ribbon (green = regular / table, yellow = all-complete), ink-stamped badge with Space Grotesk glyph, mono stamp label (COMPLETED / ALL DONE — tertiary ink for regular, amber on all-complete), title, subtitle, full-width action column. Three variants: regular, table-complete, all-investigations.
COMPLETED
Investigation completed
You can now turn off the computer.
ALL DONE
All investigations completed
Fantastic work — the whole lab is done.
Components from js/components/
Inspection modal
<lab-inspection-modal> · js/components/inspection-modal/
Post-measurement review modal. Green 4 px top stripe, ink-stamped i + REVIEW stamp label + Space Grotesk title + description + Continue (primary, full-width) and Restart Procedure (secondary, full-width). Focus-trapped; Escape resolves as Continue. Buttons share an identical width so the row never reflows when labels change.
Classroom nav
<lab-classroom-nav> · js/components/classroom-nav/
"Back to classroom" nav button for station-rotation investigations.
Floating overlays
Pause overlay
PauseOverlayHandler · 6 px green inset · handlers/overlay/pause-overlay.css
Non-interactive (pointer-events: none). Active whenever simulated time is paused.
Restart investigation pill
RestartLinkHandler · handlers/restart/restart-link.css
Persistent bottom-left pill, always visible. Click opens a confirmation modal; on confirm dispatches restart-investigation. The wrapper routes that to a parent-coordinated re-feed (embedded mode, via HostBridgeHandler's RestartRequested postMessage) or a page reload (standalone).
Orbit-trace readout
OrbitTraceHandler · handlers/orbit/orbit-trace.css
Yellow stamp pinned above the bottom of the canvas. Auto-updates per frame with current semi-major axis and eccentricity.
Loading screen
LoadingHandler · handlers/loading/loading-screen.css
Full-screen black overlay shown from first paint until Unity hands over control on the first real step. 500 ms fade-out.
Loading investigation…
Setting up your lab workspace
Loader curtain
LoaderCurtainHandler · SimulationBusyHandler · handlers/loader-curtain/
Mid-investigation "please wait" overlay shown when a /simulate request stays in flight longer than 2 s, kept on screen for at least 1.5 s once shown so a fast response doesn't cause a flash. Two siblings at separate z-indices: a faint 15 % cream wash sits below the lab chrome (z-index 55, dims the canvas only — step bar, widget bar, time control, and restart pill stay bright and clickable), and a small ink-bordered card with spinner + label sits above the chrome (z-index 95, the focal point). Reads as "calculating, hold on" rather than "loading, welcome". LoaderCurtainHandler is the visual primitive (show({label, sub}) / hide()); SimulationBusyHandler owns the threshold and min-visible timers and binds them to the simulate-request / simulate-response / simulate-fail DOM events bridged off the sealed engine in GalileoWrapper/js/main.js.
Lab screens
Two states of the lab — Frozen (action step, awaiting input) and Simulating (clip running). Composed live from the same chrome the kit ships: step-bar, restart-link, camera-controls, time-control, and a single device widget at bottom-centre. In Simulating the time-control bar takes the bottom-centre slot and the device widget stacks above it. Production CSS edits land here too.
Contents chips · five beakers
Frozen · five beakers, step-focus on three
Mockup of the always-on contents chip overlay on a five-beaker scene. Step prose names warm_beaker, cold_beaker and hcl_beaker, so those three chips render at full strength; naoh_beaker is outside focus_set and recedes at chip level (light frame, no shadow, grey label) while keeping its hazard badge bright. The empty leftmost beaker carries no chip (absence means empty). Beakers are CSS silhouettes — this is a layout sketch, not a 3D render. The chips themselves are the production .contents-chip components from Atoms. Blocking work to make this live: journal/007/2026-05-13_17_contents_chips_dev_team_request.md.
Lab states · 2 screens
Frozen
Action step · time bar absent · device widget sits at bottom-centre
Simulating
Clip running · step bar carries the SIM phase chip · time-control bar at bottom-centre with the device widget stacked above it
Simulating · speed popover open
Same as Simulating (device widget above the time-control bar); the speed dial is engaged (green) and the speed popover floats above the dial. Three forward speeds — 3x at the top, 1x at the bottom (active).
Widgets
Per-device controls mounted into the cream-paper widget bar. Each render below is the actual <lab-widget-*> custom element from galileo_viewer_kit/js/widgets/ — refactor a widget and this page updates automatically. Time Control sits at the top as the canonical example of the widget-bar pattern, followed by every device widget grouped by control archetype. Use the index below to jump straight to one.
1 · Time control · MANDATORY
Time control
TimeControlHandler · #time-control · handlers/time/time-control.css
Field Notebook v2 standard, current source of truth Figma node 110:421. Cream pill (530 × 48, 1.5 px ink border, 12 px corner, 3 px hard shadow). Layout left → right: speed dial · pause · scrubber · readout · joined "Previous-step / Skip-to-end" pair. Two visible states — collapsed default and engaged (green dial + a compact vertical speed popover anchored above the dial). Speed labels use the lowercase letter x (1x, 2x, 3x), not the multiplication sign. Tapping any popover button assigns the speed and dismisses the popover; tapping outside the popover also dismisses. The left button of the joined pair dispatches a step-previous event (it re-uses the skip-back glyph but navigates investigation steps, not the clip cursor); the right button still skips the cursor to the clip's end. Mounts whenever the active step has a duration: and auto-hides when the step ends.
Controls
step-previous, right scrubs to clip end)
Speeds: 1x · 2x · 3x (forward only — reverse playback retired May 2026)
States
2 · Electronic scale · simplest readout + button
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.
3 · Spring scale · horizontal segmented gauge
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.
4 · Hot plate · ON/OFF + segmented heat level
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."
5 · Colored flashlight · independent multi-toggle + status dots
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.
6 · Gauge · draggable thumb with sweet-spot region
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.
7 · Slider · range input + commit button
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.
8 · Microscope · multi-axis controls
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.
9 · Cart launcher · linear state machine
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).
10 · Pour · the only floating widget (vertical)
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.
11 · Thermometer · readout family
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.
12 · IR thermometer · readout + trigger
IR thermometer
<lab-widget-ir-thermometer> · js/widgets/ir-thermometer/IrThermometerWidget.js
Adds a Trigger button + green laser-dot indicator to the readout pattern: ON arms it; pulling the trigger captures the spot temperature.
13 · Ammeter · readout + unit toggle
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.
14 · Light meter · readout + range cycle
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.
15 · Humidity sensor · dual-readout
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.
16 · CO₂ sensor · readout + stabilizing pulse
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.
17 · Graduated cylinder · volume readout + unit
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.
18 · Ruler · distance readout + unit
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.
19 · Force sensor · readout + record + zero + peak
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.
20 · Flashlight · ON/OFF + beam-width toggle
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.
21 · Laser pointer · pure binary toggle
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.
22 · Kitchen lighter · momentary state group
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.
23 · Fan · ON/OFF + speed segmented
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.
24 · Hair dryer · ON/OFF + speed segmented
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.
25 · Power strip · pure binary toggle
Power strip
<lab-widget-power-strip> · js/widgets/power-strip/PowerStripWidget.js
Master switch for everything plugged in. Same chrome as the laser pointer; different mental model — students associate this with "all devices live".
26 · Rocker switch · open / closed circuit
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.
27 · Tumble buggy · ON/OFF + direction
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.
28 · Speaker · play / stop + animated wave
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.
29 · Tuning fork · strike / dampen + vibrate
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.
30 · Drum · strike / dampen
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.
31 · pH strip · color swatch + numeric
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.
32 · BTB indicator · color swatch + label
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.
33 · LED · bi-color status indicator
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.
34 · Microwave · door state machine + countdown
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.
35 · Stopwatch · self-ticking start / stop / reset
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.
36 · Hand-crank generator · multi-state + signed readouts
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.
37 · Homemade generator · slower variant + LEDs
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.
38 · Snapping track · discrete-snap rotary
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.
40 · Smart-cart track · brake state + angle readout
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.
41 · Preset · multi-choice button group
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.