Workshop Kit · widget reference
Shippedmap-radius
Radius slider around the operator's address, with a visual neighborhood placeholder. The plan calls for pre-baked static map tiles at /brand/maps/ keyed by metro area; that tile-bake is a separate infrastructure task, so today the widget renders an SVG street-grid (seeded from a hash of the operator's address, so the same address always shows the same neighborhood layout). When real tiles arrive, the SVG can be swapped for a CSS background-image of the metro PNG without changing the widget's external contract.
Live demo
Drag the slider from 0.5 to 5 miles. The circle resizes; the summary text updates with car-minutes + walk-minutes equivalents (15 mph urban arterial average, 3 mph walking). If you've set a restaurant address in any other lesson, it shows below the map; otherwise a prompt deep-links to L10.
No config block — slider range fixed at 0.5–5 miles in 0.25 increments
Contract
| Behavior | Notes |
|---|---|
Reads restaurantProfile.address |
Display only — the widget never geocodes (no fetch). The address is hashed to seed the SVG street-grid layout so a given address always shows the same neighborhood pattern. |
Reads deliveryRadius |
Initial slider position. Defaults to 1.5 miles. |
Reads palette[0] |
Tints the radius circle + the address pin marker. |
Writes deliveryRadius |
Number, miles, 0.5..5 in 0.25 increments. Used in L12 keyword phrasing and (eventually) in the L14 generator's schema markup. |
| Address arrives later | If the operator finishes L10 (sets their address) after mounting this widget on L4, the widget re-renders the street grid + display address via the mtn:context-change subscription. No remount needed. |
| Time-equivalents | Pure arithmetic — 15 mph for car (urban arterial average at dinner), 3 mph for walking. Reasonable rough orders of magnitude; not turn-by-turn estimates. |
Markup
No config block. The widget reads restaurantProfile.address + deliveryRadius from MuntinContext.
Accessibility
<input type="range">witharia-labelledbypointing at the widget heading andaria-valuetextset to the same sentence the visible summary shows ("2 miles — about 8 min by car or 40 min on foot"). Screen readers get the meaning, not just the raw number.- SVG visualization is
aria-hidden="true"; the address + summary carry the meaning. - Sliding announces "Radius set to … miles — about … minutes by car" via a polite live region. Debounced 250ms so dragging doesn't spam the announcer.
- The address row falls back to an italic prompt deep-linking to L10 when no address is set, so the widget never renders ambiguously.
Why a street-grid placeholder, not a real map
Real map tiles need either a tile-server fetch (violates "no fetch") or pre-baked PNGs for every metro the operator could possibly be in (a real infrastructure task — picking a tile provider, building the asset pipeline, choosing which metros to cover, refreshing them periodically as cities change). Until that infra ships, the SVG street-grid conveys the only signal the lesson actually needs: scale. A diner glancing at "0.5 miles" vs "5 miles" sees the difference; they don't need to recognize their own street. The address-hash seeding means the placeholder is at least consistent — the same address always shows the same neighborhood pattern, which makes it feel intentional rather than random.
When the tile pipeline ships, the widget swap is one CSS rule: change .mrw-map to background-image: url("/brand/maps/" + metro + ".webp") and the placeholder SVG goes away. The radius circle, slider, address row, and context wire all keep working.
Where it ships
Source
/tools/_shared/workshop/map-radius.js — ~210 LOC. Exports { tag, contextKeys, mount, serialize }. The SVG street-grid is built by buildStreetGrid(addrHash, accent) at the top of the file; a future tile-based replacement would just swap that one function out. Time-equivalent helpers carMinutes() and walkMinutes() use simple constants documented in comments.