The Workshop Kit

The widgets the Muntin Method is built with.

Each widget is a small, composable, learn-by-doing primitive — a palette picker, a drag-rank list, a live preview frame. Every Method lesson is a composition of these. Open one to see it run; lift it into a new product when you build the next one.

Widgets in the kit
19
Shipped so far
19
First product
Open the Doors bootcamp

Every widget

Shipped live-preview-frame

Sandboxed iframe rendering a miniature, live preview of the operator's site as it stands right now. The foundation for the "Your site so far" rail.

Open the demo →
Shipped text-input

Parameterized prose-capture widget — single line or multi-line. Lesson author picks the context field via data-context-key. Autosaves with debounce; status announces via polite live region.

Open the demo →
Shipped palette-picker

3-swatch picker with cuisine-aware starting palettes and a live WCAG contrast guard. Writes to palette[]; every other widget on the page repaints.

Open the demo →
Shipped voice-slider

Three sliders — formal↔casual, classic↔modern, terse↔warm — with pre-authored sample copy at each extreme so the operator hears what the position sounds like. Writes voice: {formality, era, warmth} to context.

Open the demo →
Shipped drag-rank

Keyboard-first force-ranker (up/down buttons, not HTML5 drag) with optional severity scoring. Used in L2 to rank what a site is for. Up next: L6b leaks, L8 dish priority, L11b GBP fixes.

Open the demo →
Shipped before-after-slider

Single-handle reveal wipe over image or DOM pairs. Used for "what good looks like" alongside "what bad looks like." Horizontal or vertical axis, full keyboard control, role="slider" with valuetext narration.

Open the demo →
Shipped tab-flip

WAI-ARIA tabs pattern (arrow keys, Home/End, focus mode = manual) for inline rhetorical examples. Used in L3 to compare three promise styles, L13 to compare three response styles to a bad review.

Open the demo →
Shipped persona-card-builder

Structured persona capture — name + age + role + up to 6 trait chips + narrative paragraph — with a live "customer card" preview alongside the form. Initials-based avatar tinted by palette accent; dual-writes structured customerCard AND prose customerParagraph so downstream lessons + the L14 generator keep working. Used in L4.

Open the demo →
Shipped positioning-plotter

2D grid with draggable + keyboard-controllable dot (arrow keys, Shift for big jumps, Home/End/PageUp/Down for edges). Configurable axes via data-* attributes; plain-language live readout. Used in L6a for cuisine breadth × price tier.

Open the demo →
Shipped font-pair-picker

Six curated heading + body type pairings (editorial-modern, diner-classic, trattoria, taqueria, minimal-tasting, corner-store) rendered with system-font CSS stacks — no remote font fetch. Radio-group with full arrow-key navigation. Used in L7 alongside palette-picker + voice-slider.

Open the demo →
Shipped menu-builder

Drag-to-reorder dish list with inline name + price editing. 3-to-12 cap, keyboard-first reordering (up/down buttons), polite-region announcements on add/remove/reorder. Used in L8 for the menu shortlist.

Open the demo →
Shipped shot-list-grid

Curated visual reference grid — 8 universal restaurant shots + 2 cuisine-specific shots when restaurantProfile.cuisine is detected (covers 12 cuisines). Each card is an inline SVG composition sketch tinted with the operator's accent color (no remote photo fetch). Selection order matters — first three become the home-page set. Used in L9a.

Open the demo →
Shipped weekly-hours-grid

Seven-day grid (Mon-Sun) with native <input type="time"> pickers + closed checkbox per day. Copy-Mon-to-Fri and copy-first-open-day shortcuts. Validates close > open. Used in L10 to capture structured hours for the generator.

Open the demo →
Shipped gbp-card-preview

Live mock of a Google Business Profile card that builds from the operator's MuntinContext — name, category, address, today's open/closed status, photo count from shotList, description, accent color from palette. Captures the one GBP-specific field not collected elsewhere (primary category, with 27 restaurant categories baked in + "Other"). Readiness checklist underneath flags missing fields. Used in L11a + L11b.

Open the demo →
Shipped map-radius

Radius slider around the operator's address, rendered today as an SVG street-grid placeholder (seeded from the address hash so the same address shows the same neighborhood layout). Real static map tiles (pre-baked metro PNGs at /brand/maps/) can swap in without changing the widget contract. Shows car-minutes + walk-minutes equivalents. Used in L4 (who's close enough to come on a weeknight) and L12 (what radius are you trying to rank in).

Open the demo →
Shipped keyword-builder

Seeds 8 local-SEO keyword slots from cuisine + neighborhood (extracted from address); operator edits each row. Four formulas (cuisine+nbhd / dish+nbhd / occasion+nbhd / intent+cuisine+nbhd) × 2 slots each. Writes localKeywords.

Open the demo →
Shipped deploy-stepper

Seven-step deploy tracker for the host-agnostic deploy flow (unzip → sign up → upload → name → live URL → custom domain → DNS wait). Operator picks Cloudflare Pages / Netlify / Vercel from a dropdown; step descriptions interpolate the host's domain + the operator's restaurant slug. Mark-done buttons persist; cross-device sync means "I started the deploy on laptop, finishing on phone" works. Celebration card at step 7. Used in L15.

Open the demo →
Shipped rhythm-calendar

Month-view calendar with recurring task pins for hours / reviews / regen / SEO — operator picks a cadence (off / weekly / biweekly / monthly / quarterly) per task and sees the current month auto-populate with pins on the right days. Real .ics export downloads a VCALENDAR file with RRULE'd recurring events, importable into Google Calendar, Apple Calendar, and Outlook directly. Used in L16 — the last widget in the bootcamp.

Open the demo →

For Method authors

Every widget exports a tiny module at /tools/_shared/workshop/<tag>.js. The contract is small — mount(rootEl, state, deps), optional serialize, optional validate. The widget engine at /assets/js/workshop-widget.js discovers, hydrates, debounces, and broadcasts. To compose a new lesson, drop a <section class="course-widget" data-widget="..."> for each widget you want on the page. The engine does the rest.

The widget renderer is responsible for one thing: turning a state object into UI and committing changes back via deps.commit(patch). State lives in MuntinContext (a localStorage namespace shared with every Muntin tool), so any widget's output is automatically available to any other widget on the page — or on any other page in the suite.

A scaffolder ships at scripts/new-workshop-widget.mjs — run it to stub a new widget, its demo page, and a parity check entry. The Method's success depends on widgets staying small, composable, and consistent.