Phase 47 · Wave 2 · Task 1 mockup · 2026-05-27

CityPillModal — replaces the dropdown

Tap the city pill in the header → this modal opens. Replaces today's flat dropdown (OQ-3 RESOLVED: REPLACE). Left = NYC-pilot active (Williamsburg). Right = non-NYC active (Tokyo, polygon-only per D-15). Both states visible on one page so the founder sees both in one tap.

Decisions reflected · D-01 modal location · D-15 NYC-only shading · OQ-3 REPLACE dropdown · "Pin demo cities" affordance is an in-modal toggle row (Variant A — see footer)
State A · NYC pilot active (Williamsburg)
Nabe
📍 Wburg ▾
Your nabes
Switch neighborhood
Currently Williamsburg · NYC
Saved nabes
3 saved
Other cities (tap to switch active)
What's here: Saved nabes (Active row highlighted + live counts) → Pin demo cities toggle (ON) → Browse all NYC CTA (only when active city is NYC-pilot) → Other cities grid → Add a nabe → Cancel. Live dots: pulsing fuchsia for hot, fuchsia for lively, indigo for moderate, gray for quiet.
State B · Non-NYC active (Tokyo) — polygon-only per D-15
Nabe
📍 Tokyo ▾
Your nabes
Switch neighborhood
Currently Tokyo · live heatmap not yet available outside NYC
Saved nabes
2 saved
Tokyo wards · tap to set active
Active: Shibuya
No liveness shading outside NYC (D-15)
Heatmap available in NYC only
Switch active to Williamsburg / Greenpoint / East Village to browse all 195 nabes by liveness.
Switch active city
What's different here: NO "Browse all NYC" CTA. Instead a static polygon outline of Tokyo wards (illustrative — production fetches from neighborhoodPolygons.ts) and an amber notice that heatmap shading is NYC-only per D-15. Saves the polygon-only-city promise from the data architecture.
📋 Edge state · Empty saved-nabes (first-time user)
Your nabes
Switch neighborhood
Currently Williamsburg · NYC
📍
No saved nabes yet
Save your block, your old neighborhood, places you visit — they'll show up here for one-tap switching.
Empty-state principles
  • Dashed empty box explains the value ("save your block") so the user understands what saved means before tapping anything.
  • Pin toggle defaults to OFF for new users (no opinion until they save a few nabes).
  • Browse all NYC stays prominent — it's the discovery path that produces the first save.
  • "Add a nabe" surfaces as the explicit alternative to Browse for users who already know the name of the nabe they want.
Founder choice point: Should the pitch demo build ship with Williamsburg + LA + Tokyo + Vesta pre-saved (so the modal is never empty during the pitch), or with empty state visible as part of the story? Default in this mockup: pre-saved with W'burg active + 2 others, so the founder demos the populated case. Confirm or override.
🧭 Design notes — what's intentional
Why a vaul bottom sheet, not a dropdown

The dropdown is being replaced (OQ-3). A bottom sheet gives room for the saved-nabes list + Browse CTA + Pin toggle + Add affordance — none of which fit in a 160px-wide dropdown menu. Matches existing project pattern (every drawer uses vaul; see AgentProfile, DEMO_SCRIPT overlay).

Why "Pin demo cities" is an in-modal toggle (Variant A)

An in-modal toggle row beats a sticky chip: (1) lives next to the saved-nabes list where pinning logically applies, (2) reuses standard row + iOS-switch pattern from elsewhere in the app, (3) doesn't compete visually with the Browse-NYC CTA. Founder can override to "sticky chip in header" but Variant A is the default recommended here.

Why the Browse-NYC CTA is the visual hero

The heatmap is the "wow" moment in Wave 2. The CTA pulls Variant C gradient + a mini-heatmap preview thumbnail so the user understands what they'll see before tapping. Pulls visual weight away from the saved-nabes list — the saved list is utility, the heatmap is discovery.

Why no "Browse all" in non-NYC modal

D-15 is a hard scope lock: liveness shading is NYC-only in Phase 47. Showing a "Browse all Tokyo" CTA that opens a non-heatmap polygon view would be a UX dead-end. Instead an amber notice + the static polygon SVG outline tells the founder/user where the boundary is, and the polygon outline still lets them tap a ward to switch active nabe.

Badge sizing compliance

"Active" pill = px-2.5 py-0.5 rounded-full text-xs font-medium per DESIGN_STANDARDS. Live dots are pure indicators (no text), so they're 2×2px circles with the colorway derived from liveness.level (hot=pulsing fuchsia / lively=fuchsia / moderate=violet / quiet=zinc).

What replaces the existing dropdown

Old NeighborhoodSelector dropdown variant's onClick switches from "open inline menu" to "call onOpenModal()". Inline menu is dead. The "Pitch demo / Other cities" divider semantics from the old menu re-appear as the Pin-demo-cities toggle + Other-cities grid in the modal.

📝 Deviations from PLAN line-item
Resolution applied (OQ-3 = REPLACE dropdown)
Reply "approved" to lock; describe changes if needed.
Reviewing on iPad? Tap to expand each device frame.
File: mockups/phase-47-pill-modal.html
Next: Task 2-5 build the TSX components against this approved shape.