StimPack
Build log · Colophon

How StimPack was built.

A short build log on the design choices, the AI workflow, and the things I cared about while shipping an iPad simulation training companion for Laerdal SimMan trauma scenarios.

Mission

What StimPack is, and who it's for

StimPack is an iPad-first sidecar app for medics and instructors running Laerdal SimMan trauma simulations. It surfaces vitals, a body map with injury markers, the M.A.R.C.H. protocol checklist, a TCCC-style drug log, and a post-run debrief — built with the actual manikin’s capability matrix in mind, designed to be usable with gloved hands in a clinical or field setting.

It’s a self-directed project — no client, no template. The constraints are real (iPad, gloves, dark by default, WCAG AA) and the reference (Laerdal’s published spec) is real. The point is to ship something I’d actually trust running on a tablet at the casualty collection point.

Stack

Tools on the bench

Next.js 16TypeScriptTailwind 4Framer MotionZustandRadix UILucideVercelGitHubClaude Code
Operations log

What got built, in order

  1. 01

    Scaffold + design tokens

    Next.js 16 App Router, TypeScript, Tailwind 4 with @theme inline. CSS custom properties for the full palette so dark/light could share a single set of utilities. Hand-rolled shadcn-style primitives — Button, Sheet, Dialog, Badge, Progress, Checkbox — sized for iPad from the first commit.

  2. 02

    Five screens, end to end

    Profile select (Netflix-style), home with categories rail and recent, scenario list with filter pills, scenario brief with patient card and equipment checklist, active scenario runner. Each screen built complete before moving to the next so I could actually use the flow.

  3. 03

    Active scenario runner — the hard one

    2×2 grid: vitals monitor with live ECG trace synced to heart rate, body map with pulsing injury markers and tappable pulse-check points, MARCH protocol checklist with timestamps and out-of-sequence warnings, instructor notes. Vitals lerp from baseline to target weighted by completed checklist items.

  4. 04

    Light mode with no-flash hydration

    Inline script in <head> applies the theme synchronously before paint, useSyncExternalStore on the React side so SSR snapshot stays consistent. Every hard-coded hex got swept onto CSS variables so the body map SVG, ECG grid, and avatar gradients all reskin atomically.

  5. 05

    Ship: GitHub, Vercel, custom domain

    Private repo at FemtiGram/stimpack. Vercel auto-deploys main. Mapped to stimpack.andersgram.no via CNAME. Every push to main is a deploy.

  6. 06

    Read the actual Laerdal spec

    Pulled the SimMan Trauma capability matrix. Gap-analysed against the app, prioritized the high-leverage missing pieces. Added a TCCC drug log modeled after the manikin's Drug Recognition System, and a post-run debrief screen with timeline, vitals delta, and out-of-sequence stats.

  7. 07

    Accessibility + gloved iPad pass

    Computed contrast ratios in both themes, fixed three failing tokens, swept every interactive pill from 28–32px to 44px, added a .tap-pill utility with built-in focus rings, registered prefers-reduced-motion, widened the sidebar rail to 64px so the icon hits could grow to 56px for gloved use.

  8. 08

    Spatial cross-link + iPad-cap rule

    Tagged every MARCH item and injury with a body region; tapping a checklist item now flashes a glow on the matching anatomical zone of the body map, and tapping an injury rings + scrolls matching items into view. Injuries auto-render green when their region's MARCH items are completed. Swapped the simplified body SVG for a real manikin illustration. Codified the iPad width-cap as a single .viewport-cap utility so future pages inherit it.

  9. 09

    History, settings, and manikins

    Per-profile run archive — every End Scenario snapshots the run to localStorage and surfaces in /history; a single run renders at /history/[runId] using the same shared <RunReport> body as the live debrief. Built /settings (account, theme, run-history controls, about) and /categories (full grid). Added /manikins for device-wide hardware config — name, model, USB / Bluetooth / Wi-Fi connection, simulated handshake with a realistic 85% success rate, error → retry flow. Real hardware integration is a desktop-bridge problem; the UI is built so the swap is one function.

  10. 10

    Slot assignments + instructor reference

    Each scenario now declares manikin slots — single-casualty has one (Patient), MASCAL has six (one per casualty role). Instructor assigns a configured manikin to each slot on the brief page; tap once to assign and auto-connect. Active runner top bar shows aggregate connection status, click for per-slot detail. Connection state is global Zustand so the manikin you connected on /manikins is still connected when you navigate to the active runner. Added a TCCC quick-reference modal in the active top bar — pocket card view of MARCH thresholds, vital action ranges, and common drug doses for instructor teaching moments.

  11. 11

    Debrief lock + minimalist polish

    Debrief page now viewport-locked on lg+ — no full-page scroll, columns scroll internally so all stats stay in view. Fixed the Injuries Addressed stat to count region-matched MARCH completions (was only counting explicit Mark-Treated taps). Added an End Scenario button at the bottom of the MARCH checklist for instructors who scroll through everything. Avatars dropped the radial gradient: solid surface bg + 2px profile-color ring + foreground text — same identity, AA-compliant in both themes (was as low as 1.7:1 for green initials in light mode). Theme toggle moved from the sidebar drawer to the top-right of each page header, where it lives alongside other page actions.

  12. 12

    Historical casebook + readability pass

    Added an optional eraContext per scenario — era + a doctrinal note about what changed in medical practice because of that pattern. Five new scenarios across WWII Normandy, Korea (Chosin Reservoir), Vietnam (Bouncing Betty AP mines), Ukraine (drone-dropped munitions + prolonged field care), and modern urban disaster (crush syndrome). Three existing scenarios tagged Iraq / Afghanistan since they emerged from that doctrinal lineage. UI: era chip on the scenario card, full Historical Context card on the brief page. Readability: body prose bumped to 16px, foreground-dim and muted-2 brightened in dark mode (14.5:1 and 9.9:1 — both AAA), card grids forced to equal heights with auto-rows-fr + reserved era-line slots so a card without history doesn't shrink. Manikin icon swept HardDrive → Bot.

  13. 13

    Mobile pass

    Active scenario runner top bar restructured for narrow viewports: the left title block hides on mobile and a dedicated status strip below the header surfaces the title, callsign, and critical/stable state — the strip background tints red when vitals are deranged. Vitals cells go 2-cols on mobile so NIBP fits, number sizing scales 24/30/36 across breakpoints. Body map, MARCH, Notes/Drugs cells get larger mobile min-heights so figures aren't clipped and lists aren't cramped. The MARCH cross-link no longer page-jumps on mobile — custom scroll logic walks up to find the nearest scrollable ancestor and never the page itself. NIBP value row in the debrief wraps the unit cleanly instead of overflowing. Theme toggle pruned from 7 pages — kept on /, /home, /settings, /colophon — so per-page headers can reserve right-side space for actual page actions.

  14. 14

    Tactical theme + 3-way theme picker

    Third theme variant for night-vision-preserving operational use — all hues red so the screen emits as little blue/green light as possible, status differentiated by intensity + icon rather than hue. New [data-theme="red"] token block reskins every surface; a data-tactical-tint CSS filter shifts the patient PNG to red-on-black so raster images don't break the mode. Sweep: every hardcoded amber/red/green rgba() literal in className/style props moved to Tailwind slash-opacity utilities (border-accent/30, bg-success-soft, etc.) so they pick up the active theme. SVG strokes in Logo and the sidebar hamburger now reference var(--color-accent). The corner toggle was a 2-state cycle and hid the new mode; rebuilt as a popover with three explicit options (Dark / Light / Tactical) — same 44px trigger, drops a right-aligned menu with active-state check icon. Outside-click + Escape close.

Workflow

How AI fits into this

I built StimPack with Claude Code as my pair-programmer. I drove the architecture, the design constraints, the gap-analysis against the Laerdal spec, and every call about what to ship vs. defer. Claude wrote a lot of the implementation — the five screens of consistent UI, the mechanical sweeps like the WCAG pass and the rail-widening pass, and the kind of grunt work that’s easy to half-do when you’re tired.

The pieces that needed me: reading the SimMan spec PDF and deciding what mapped to what, choosing the visual system, prioritizing accessibility, knowing when “done” was actually done. The pieces Claude was good at: producing 600 lines of correct Tailwind in one go, surfacing token-level issues before I ran the page through a contrast checker, and not getting bored.

The takeaway isn’t “AI built this for me”. It’s that a senior operator with a clear constraint set and a willingness to push back can ship a real iPad-class app on a personal timeline. The leverage is in the directing, not the typing.

Constraints

What I cared about

  • iPad-first, gloved-hands

    1024×768 landscape primary. Every tap target ≥44px, primary actions ≥48px. The operator may be wearing nitrile gloves.

  • WCAG 2.2 AA both themes

    Normal text contrast ≥4.5:1, UI ≥3:1. Verified in dark and light. Placeholders, primary buttons, and labels all checked against effective backgrounds.

  • Anchored to real hardware

    Capability map ties back to Laerdal SimMan Trauma — pulses, MARCH steps, drug recognition, IO access. The app teaches the manikin's workflow, not a synthetic one.

  • Clinical, not flashy

    No gradients, no purple AI clichés. Flat surfaces, amber accent, mono labels. Reference: medical monitoring software meets tactical ops.

Roadmap

What’s not built yet

Pulled from the SimMan Trauma capability gap-analysis. Each is a concrete, testable feature — none of them are the typical “AI wishlist” handwave.

  • Body-map auscultation actions (heart, lung, bowel sounds)
  • Head-injury secretions / CSF leak indicators
  • NBC scenarios (chemical, biological, nuclear)
  • CPR depth + rate feedback widget for V-Fib runs
  • Stomach auscultation as ETT confirmation step
  • More airway adjuncts as MARCH actions (Combitube, LMA, surgical cric)
Contact

Anders Gram

Product engineer · Norway. Open to interesting work.

© 2026 Anders Gram · Built with Claude Code