diff --git a/NAVI-DIRECTIONS-REDESIGN.md b/NAVI-DIRECTIONS-REDESIGN.md
index 0274b93..932b155 100644
--- a/NAVI-DIRECTIONS-REDESIGN.md
+++ b/NAVI-DIRECTIONS-REDESIGN.md
@@ -1,667 +1,842 @@
-# Navi Directions UX Redesign
-
-**Status:** Draft
-**Author:** Claude + Matt
-**Date:** 2026-04-26
-**Implementation:** Deferred to dedicated session
-
----
-
-## 1. Current State
-
-### Components
-
-| Component | File | Role |
-|-----------|------|------|
-| SearchBar | `SearchBar.jsx` | Overloaded: search, add stop, set origin (hidden modes) |
-| StopList | `StopList.jsx` | Drag-drop reordering of stops |
-| GpsOriginItem | `GpsOriginItem.jsx` | "Your location" row when GPS granted |
-| StopItem | `StopItem.jsx` | Individual stop with delete button |
-| ModeSelector | `ModeSelector.jsx` | auto/pedestrian/bicycle toggle |
-| ManeuverList | `ManeuverList.jsx` | Turn-by-turn directions display |
-| PlaceDetail | `PlaceDetail.jsx` | "Directions" button for selected place |
-
-### State Model
-
-```javascript
-stops: [] // Array of {id, lat, lon, name, source, matchCode}
-gpsOrigin: true // Use GPS as origin when available
-pendingDestination: null // Place waiting for origin (GPS-denied flow)
-route: null // Valhalla trip response
-routeLoading: false
-routeError: null
-```
-
-### Failure Modes
-
-1. **No visible from/to inputs** — Users cannot see or directly edit origin/destination
-2. **SearchBar hidden mode-switching** — Three different behaviors based on invisible state:
- - Normal: opens place detail
- - With `pendingDestination`: first result becomes origin
- - After adding stops: unclear which role next selection plays
-3. **GPS-denied flow uses ephemeral toast** — "Set a starting point" disappears, no persistent UI guidance
-4. **No swap button** — Cannot reverse route direction
-5. **No map context menu** — Right-click/long-press does nothing
-6. **No waypoint addition UI** — Only drag-drop reordering, no insert-between
-7. **Place panel "Directions" silently sets up route** — Based on hidden state, no confirmation
-
----
-
-## 2. Design Principles
-
-1. **Direct manipulation over hidden modes** — Every action should have visible UI
-2. **Two visible inputs always** — When in directions mode, From and To fields are always visible
-3. **Spatial interactions over linear** — Radial menu for map interactions, not dropdowns
-4. **Same gesture model everywhere** — Right-click (desktop) = long-press (mobile)
-5. **Preserve existing state model** — `stops[]` array stays, just better UI on top
-
----
-
-## 3. Visual Mockup — Directions Panel
-
-```
-┌─────────────────────────────────────┐
-│ DIRECTIONS │
-├─────────────────────────────────────┤
-│ │
-│ From: [📍 Your location ][×] │
-│ ──────────────────────── │
-│ [⇅] │ ← Swap button
-│ ──────────────────────── │
-│ To: [Coffee shop on Main St][×] │
-│ │
-│ [+ Add stop] │
-│ │
-├─────────────────────────────────────┤
-│ [🚗 Auto] [🚶 Walk] [🚲 Bike] │
-├─────────────────────────────────────┤
-│ ┌─────────────────────────────┐ │
-│ │ 12 min · 4.2 mi │ │
-│ │ via W Main St │ │
-│ └─────────────────────────────┘ │
-│ │
-│ ▼ Turn-by-turn (expandable) │
-│ → Head north on Oak Ave │
-│ ↱ Turn right onto Main St │
-│ ◉ Arrive at destination │
-│ │
-└─────────────────────────────────────┘
-```
-
-### Input States
-
-**From field:**
-- GPS granted: Shows "📍 Your location" pill with clear button
-- GPS denied/cleared: Empty, placeholder "Starting point..."
-- Filled: Shows place name with clear button
-
-**To field:**
-- Empty: Placeholder "Destination..."
-- Filled: Shows place name with clear button
-
-**Active input:**
-- Blue border highlight
-- Search dropdown appears on typing
-- Map click populates this field
-
----
-
-## 4. Visual Mockup — Radial Map Menu
-
-```
- Drop pin
- 🔴
- ╱ ╲
- ╱ ╲
- Directions ╱ ╲ Directions
- from here 🟢──────────────🔵 to here
- │ 43.6166 │
- │ -116.2008 │
- │ [loading…] │ ← Center disc with coords/label
- Add as 🟡──────────────🟣 Save place
- stop ╲ ╱
- ╲ ╱
- ╲ ╱
- 🟠
- What's here
-```
-
-### Wedge Layout (60° each)
-
-| Position | Action | Icon | Color |
-|----------|--------|------|-------|
-| Top | Drop pin | Pin | Red |
-| Top-right | Directions to here | Arrow-in | Blue |
-| Bottom-right | Save place | Star + 🔒 | Purple |
-| Bottom | What's here | Info | Orange |
-| Bottom-left | Add as stop | Plus | Yellow |
-| Top-left | Directions from here | Arrow-out | Green |
-
-### Behavior
-
-- **Trigger:** Right-click (desktop) or long-press 450ms (mobile)
-- **Center disc:** ~40px diameter, shows coordinates immediately, reverse-geocoded label async
-- **Wedge highlight:** On hover (desktop) or drag-over (mobile)
-- **Commit:** Release on wedge (mobile) or click wedge (desktop)
-- **Cancel:** Release outside, Escape key, tap elsewhere
-
----
-
-## 5. Component Breakdown
-
-### DirectionsPanel
-
-Replaces current Panel directions mode.
-
-```
-Props: none (reads from store)
-State: none (all in global store)
-Children:
- - LocationInput (from)
- - SwapButton
- - LocationInput (to)
- - WaypointList (if stops.length > 2)
- - AddStopButton
- - ModeSelector
- - RouteSummary
- - ManeuverList (collapsible)
-```
-
-### LocationInput
-
-Reusable component for from, to, and waypoint inputs.
-
-```
-Props:
- - slot: 'from' | 'to' | `waypoint:${index}`
- - value: { lat, lon, name, source } | null
- - placeholder: string
- - showGpsPill: boolean
- - onClear: () => void
-
-Features:
- - Search-as-you-type (Photon geocoder)
- - GPS pill state with clear button
- - Active-input visual state (blue border)
- - Reverse-geocoded labels for coord-only entries
- - Dropdown for search results
-```
-
-### SwapButton
-
-Simple button between From and To inputs.
-
-```
-Props: none
-Action: Swaps stops[0] and stops[stops.length - 1]
-Visual: ⇅ icon, hover highlight
-```
-
-### WaypointList
-
-Refactored from existing StopList, preserves drag-drop.
-
-```
-Props: none (reads stops from store)
-Features:
- - Only renders stops[1..n-1] (middle waypoints)
- - Drag-drop reordering via @dnd-kit
- - Delete button per waypoint
- - "Via" label prefix
-```
-
-### RadialMenu
-
-New general-purpose component.
-
-```
-Props:
- - open: boolean
- - x: number (screen X)
- - y: number (screen Y)
- - lat: number
- - lon: number
- - wedges: Array<{ id, icon, label, action: (lat, lon) => void }>
- - onClose: () => void
-
-Features:
- - Configurable wedge count and actions
- - Async center label (reverse geocode)
- - Keyboard dismissal (Escape)
- - Touch-friendly sizing on mobile
- - Fade in/out animations
-```
-
----
-
-## 6. State Model
-
-### Existing (unchanged)
-
-```javascript
-stops: [] // Origin = stops[0], destination = stops[last], waypoints in between
-gpsOrigin: boolean // Whether GPS should be used as origin
-route: object | null // Valhalla trip response
-routeLoading: boolean
-routeError: string | null
-```
-
-### New
-
-```javascript
-activeInputSlot: 'from' | 'to' | `waypoint:${N}` | null
-// Which input is currently focused/active for map-click-to-fill
-
-radialMenuState: {
- open: boolean,
- x: number, // Screen coordinates
- y: number,
- lat: number, // Map coordinates
- lon: number,
- label: string | null // Reverse-geocoded, async populated
-}
-
-// Pin persistence (three-tier)
-transientPins: [] // In-memory, lost on refresh
-// localStorage: 'navi_saved_pins' key for guest saves
-// Backend sync: future work for authed users
-```
-
-### Removed
-
-```javascript
-pendingDestination: null // No longer needed — explicit inputs replace hidden state
-```
-
----
-
-## 7. Interaction Flows
-
-### Open directions tab fresh
-
-1. From field shows GPS pill if `geoPermission === 'granted'`, else empty
-2. To field is empty, focused by default
-3. No route calculated yet
-
-### Click "Directions" from place panel
-
-1. Directions panel opens (if not already)
-2. To field auto-fills with selected place
-3. From field:
- - If GPS granted: shows GPS pill
- - Else: empty, receives focus
-4. Route calculates if both filled
-
-### Type in input
-
-1. Input receives focus, becomes `activeInputSlot`
-2. Photon search fires on debounce (300ms)
-3. Dropdown shows results
-4. Select result → populates input, clears dropdown
-5. Route recalculates
-
-### Right-click / long-press on map
-
-1. Radial menu appears centered on click point
-2. Center disc shows coordinates immediately
-3. Reverse geocode fires async, populates label
-4. User hovers/drags to wedge:
-
-| Wedge | Action |
-|-------|--------|
-| **Directions from here** | Opens directions if closed, fills From with coords, focuses To |
-| **Directions to here** | Opens directions if closed, fills To with coords, focuses From if empty |
-| **Add as stop** | Inserts new stop before destination |
-| **What's here** | Reverse geocode → opens place panel |
-| **Drop pin** | Creates transient marker (session-only by default) |
-| **Save place** | Guest: opens login flow; Authed: opens save dialog |
-
-5. Release outside or Escape → dismisses without action
-
-### Map click with active input
-
-When directions panel is open and an input is focused (`activeInputSlot !== null`):
-
-1. Single click on map
-2. Clicked coordinates populate the active input
-3. Reverse geocode fires to get display name
-4. Input loses focus, `activeInputSlot = null`
-5. Route recalculates
-
-### Map long-press / right-click with active input
-
-Even when an input is active:
-
-1. Long-press / right-click opens radial menu (overrides click-to-fill)
-2. User can select "Directions from here" or "Directions to here"
-3. Explicitly overrides the active input — intentional action takes priority
-
-### Map click with no active input
-
-1. No action (map interaction only)
-2. Future v2 consideration: reverse-geocode and show place panel
-
-### Swap button
-
-1. Click swap button
-2. `stops[0]` and `stops[stops.length - 1]` swap positions
-3. If GPS was origin, GPS pill moves to destination (unusual but allowed)
-4. Route recalculates
-
----
-
-## 8. Place Panel "Directions" Handoff
-
-**Current behavior:** Calls `startDirections(place)` with complex conditional logic, may show toast.
-
-**New behavior:**
-
-```javascript
-handleDirections = () => {
- // Always open directions panel
- setActiveTab('directions')
-
- // Fill destination
- setStop(stops.length, { // Appends or replaces last
- lat: place.lat,
- lon: place.lon,
- name: place.name,
- source: place.source
- })
-
- // Handle origin
- if (geoPermission === 'granted') {
- setGpsOrigin(true) // GPS pill in From
- } else if (stops.length === 0) {
- setActiveInputSlot('from') // Focus From input
- }
-
- // Close place panel
- clearSelectedPlace()
-}
-```
-
-**No toast needed** — UI is self-explanatory with visible From/To fields.
-
----
-
-## 9. Radial Menu Specifics
-
-### Trigger
-
-| Platform | Gesture | Duration |
-|----------|---------|----------|
-| Desktop | Right-click | Instant |
-| Mobile | Long-press | 450ms |
-
-### Conflict Avoidance
-
-Long-press must NOT fire during active pan:
-- Track touch start position
-- If touch moves >5-10px before timer fires, cancel long-press
-- Pan gesture takes priority
-- Matches iOS Safari default contextmenu synthesis behavior
-
-### Geometry
-
-```
-Outer radius: ~80px from center (desktop), ~100px (mobile)
-Inner radius: ~40px (center disc, desktop), ~50px (mobile)
-Wedge angle: 60° each (6 wedges)
-Gap between wedges: 2px
-Minimum touch target: 48px per wedge
-```
-
-### Visual States
-
-| Element | Default | Hover/Active | Selected |
-|---------|---------|--------------|----------|
-| Wedge background | `rgba(0,0,0,0.7)` | `rgba(0,0,0,0.85)` | Wedge accent color |
-| Wedge icon | White, 50% opacity | White, 100% opacity | White |
-| Wedge label | Hidden | Shown (tooltip) | Shown |
-| Center disc | Dark, coords visible | — | — |
-| Save place (guest) | Lock icon overlay | — | — |
-
-### Animation
-
-- **Fade in:** <100ms ease-out
-- **Fade out:** <150ms ease-in
-- **Wedge hover:** Instant background change
-- **Center label:** Fade in when reverse geocode completes
-
----
-
-## 10. Mobile Considerations
-
-### Panel Layout
-
-**Decision: Bottom sheet on mobile (<768px), side panel on desktop.**
-
-Bottom sheet states:
-- **Peek:** Route summary only (collapsed)
-- **Half:** Inputs + summary visible
-- **Full:** Inputs + turn-by-turn maneuvers
-
-Drag handle at top for resize between states.
-
-**Implementation notes:**
-- Use React state for sheet position: `sheetState: 'peek' | 'half' | 'full'`
-- CSS transforms for smooth transitions: `transform: translateY()`
-- Touch gesture detection for drag-to-resize
-
-### Long-press Timing
-
-**Decision: 450ms with 5-10px movement threshold.**
-
-If finger moves more than threshold during press window, abort long-press and treat as pan. This matches iOS Safari's default contextmenu synthesis behavior.
-
-**Implementation notes:**
-- Create reusable `useLongPress` hook
-- Track `touchstart` position
-- `setTimeout` for 450ms
-- Clear timeout on `touchmove` if delta > threshold
-- Fire callback on timeout completion
-
-### Radial Sizing
-
-Mobile radial larger for finger touch:
-- Outer radius: ~100px (vs 80px desktop)
-- Center disc: ~50px (vs 40px desktop)
-- Minimum wedge touch target: 48px
-
-### Compact Directions Mode
-
-When route is calculated and user is navigating:
-1. Collapse From/To inputs to single-line summary
-2. Show prominent next maneuver
-3. Expand on tap to edit inputs
-4. Maneuver list scrollable
-
-### Keyboard Awareness
-
-- Detect keyboard open via `visualViewport` API
-- Shift panel content up to keep active input visible
-- Don't let keyboard overlap input being typed in
-
----
-
-## 11. Place Panel Restructure
-
-**Out of scope for this document.**
-
-Separate session will address:
-- Cleaner info card layout (Google Maps style)
-- Better visual hierarchy
-- Action button placement
-- No new data sources, just CSS/JSX polish
-
----
-
-## 12. Out of Scope (Future Phases)
-
-| Feature | Notes |
-|---------|-------|
-| Saved routes | Auth required, dedicated work |
-| Route alternatives | Valhalla supports, surface in v2 |
-| Avoid tolls/highways | Valhalla supports via costing options |
-| Real-time rerouting | Requires location tracking loop |
-| Multi-modal | Drive + transit + walk hybrids |
-| Traffic-aware routing | Requires traffic data source |
-| Offline routing | Requires local Valhalla instance |
-
----
-
-## 13. Implementation Sequence
-
-| Phase | Task | Depends On |
-|-------|------|------------|
-| **a** | Build RadialMenu component (general-purpose, no actions wired) | — |
-| **b** | Wire "What's here" action to validate trigger + reverse-geocode flow | a |
-| **c** | Refactor SearchBar to single-mode (search-only, remove pending* logic) | — |
-| **d** | Build LocationInput component (reusable) | c |
-| **e** | Build DirectionsPanel layout with two LocationInputs | d |
-| **f** | Wire remaining radial actions to directions flow | b, e |
-| **g** | Wire place panel "Directions" handoff to new flow | e |
-| **h** | Add SwapButton | e |
-| **i** | Add map-click-to-fill-active-input | e |
-| **j** | Mobile polish (long-press timing, bottom sheet, keyboard) | a-i |
-
-**Estimated phases:** 10 discrete tasks, can be done incrementally.
-
----
-
-## 14. Resolved Decisions
-
-### 1. Mobile Layout
-
-**Decision:** Bottom sheet on mobile (<768px breakpoint), side panel on desktop.
-
-Three sheet states:
-- **Peek:** Summary only (route time/distance)
-- **Half:** Inputs + summary visible
-- **Full:** Inputs + turn-by-turn maneuvers
-
-Drag-to-resize between states via handle at top of sheet.
-
-**Implementation notes:**
-- `sheetState` in store: `'peek' | 'half' | 'full'`
-- CSS transforms (`translateY`) for smooth animation
-- Touch gesture handler for drag detection
-- Snap to nearest state on release
-
-### 2. Long-press Timing
-
-**Decision:** 450ms with 5-10px movement threshold.
-
-If finger moves more than threshold during the press window, abort long-press and treat as pan gesture. This matches iOS Safari's default contextmenu synthesis behavior.
-
-**Implementation notes:**
-- Create `useLongPress(callback, delay = 450)` hook
-- Track `touchstart` coordinates
-- On `touchmove`, check if `Math.hypot(dx, dy) > threshold`
-- If exceeded, clear timeout (abort)
-- If timeout fires without abort, invoke callback with coords
-
-### 3. Save Place for Guests
-
-**Decision:** Visible wedge with subtle lock icon overlay to indicate auth required.
-
-Tapping as guest opens login flow with return-to-action after auth completes.
-
-**Implementation notes:**
-- "Save place" wedge always visible
-- Lock icon (🔒) overlaid at 50% opacity on wedge icon
-- On tap: check auth state
- - Authed: open save dialog
- - Guest: trigger login modal with `returnAction: 'save-place'`
-- After successful auth, resume save flow
-
-### 4. Radial Ring Structure
-
-**Decision:** Single ring only. Six wedges maximum.
-
-Future expansion via contextual wedge sets (different actions based on what was clicked — e.g., clicking on a route segment could show "Avoid this road" instead of "Drop pin") rather than deeper nested rings.
-
-**Implementation notes:**
-- `wedges` prop on RadialMenu is array of 6 max
-- Context-aware wedge selection handled by parent component
-- No inner ring in v1; revisit if six actions prove insufficient
-
-### 5. Drop Pin Persistence
-
-**Decision:** Three-tier model.
-
-| Tier | Scope | Storage | Behavior |
-|------|-------|---------|----------|
-| Transient | Session | In-memory (`transientPins[]`) | Default. Lost on refresh. |
-| Guest save | Device | `localStorage` (`navi_saved_pins`) | Survives refresh, lost on cache clear or other device. |
-| Authed sync | Account | Backend API | Syncs across devices. Future work. |
-
-**Implementation notes:**
-- Transient pins: `transientPins: []` in store
-- Guest save: on "Save" tap, copy pin to localStorage
-- localStorage schema: `[{ id, lat, lon, name, createdAt }]`
-- Backend sync: TODO placeholder; initial implementation is localStorage only
-- Authed users see "Sync to account" option (greyed out with "Coming soon" for v1)
-
-### 6. Radial vs Click During Active Input
-
-**Decision:** Different gestures, different behaviors.
-
-| State | Gesture | Behavior |
-|-------|---------|----------|
-| Active input | Map click | Fills active input with click coords (reverse-geocoded) |
-| Active input | Map long-press / right-click | Opens radial (explicit override allowed) |
-| No active input | Map click | No action (v2: reverse-geocode + place panel) |
-| No active input | Map long-press / right-click | Opens radial (primary use case) |
-
-**Implementation notes:**
-- Click handler checks `activeInputSlot`
- - If set: fill input, reverse-geocode for label
- - If null: no-op (or v2 place panel)
-- Long-press / contextmenu handler always opens radial
-- Radial "Directions from/to here" explicitly sets input, overriding prior value
-
----
-
-## Appendix A: Current Code References
-
-| File | Lines | Relevance |
-|------|-------|-----------|
-| `store.js` | 72-86 | `startDirections()` logic to replace |
-| `store.js` | 16-34 | `stops[]` management to preserve |
-| `SearchBar.jsx` | 140-170 | `pendingDestination` logic to remove |
-| `PlaceDetail.jsx` | 574-579 | `handleDirections()` to rewrite |
-| `App.jsx` | 31-66 | Route fetch effect to preserve |
-| `api.js` | 29-56 | `requestRoute()` unchanged |
-
----
-
-## Appendix B: Radial Menu SVG Structure
-
-```svg
-
-```
-
----
-
-*Document created 2026-04-26. Updated 2026-04-26 with resolved decisions. Implementation to follow in dedicated session.*
+# Navi Directions UX Redesign
+
+**Status:** Draft
+**Author:** Claude + Matt
+**Date:** 2026-04-26
+**Implementation:** Deferred to dedicated session
+
+---
+
+## 1. Current State
+
+### Components
+
+| Component | File | Role |
+|-----------|------|------|
+| SearchBar | `SearchBar.jsx` | Overloaded: search, add stop, set origin (hidden modes) |
+| StopList | `StopList.jsx` | Drag-drop reordering of stops |
+| GpsOriginItem | `GpsOriginItem.jsx` | "Your location" row when GPS granted |
+| StopItem | `StopItem.jsx` | Individual stop with delete button |
+| ModeSelector | `ModeSelector.jsx` | auto/pedestrian/bicycle toggle |
+| ManeuverList | `ManeuverList.jsx` | Turn-by-turn directions display |
+| PlaceDetail | `PlaceDetail.jsx` | "Directions" button for selected place |
+
+### State Model
+
+```javascript
+stops: [] // Array of {id, lat, lon, name, source, matchCode}
+gpsOrigin: true // Use GPS as origin when available
+pendingDestination: null // Place waiting for origin (GPS-denied flow)
+route: null // Valhalla trip response
+routeLoading: false
+routeError: null
+```
+
+### Failure Modes
+
+1. **No visible from/to inputs** — Users cannot see or directly edit origin/destination
+2. **SearchBar hidden mode-switching** — Three different behaviors based on invisible state:
+ - Normal: opens place detail
+ - With `pendingDestination`: first result becomes origin
+ - After adding stops: unclear which role next selection plays
+3. **GPS-denied flow uses ephemeral toast** — "Set a starting point" disappears, no persistent UI guidance
+4. **No swap button** — Cannot reverse route direction
+5. **No map context menu** — Right-click/long-press does nothing
+6. **No waypoint addition UI** — Only drag-drop reordering, no insert-between
+7. **Place panel "Directions" silently sets up route** — Based on hidden state, no confirmation
+
+---
+
+## 2. Design Principles
+
+1. **Direct manipulation over hidden modes** — Every action should have visible UI
+2. **Two visible inputs always** — When in directions mode, From and To fields are always visible
+3. **Spatial interactions over linear** — Radial menu for map interactions, not dropdowns
+4. **Same gesture model everywhere** — Right-click (desktop) = long-press (mobile)
+5. **Preserve existing state model** — `stops[]` array stays, just better UI on top
+
+---
+
+## 3. Visual Mockup — Directions Panel
+
+```
+┌─────────────────────────────────────┐
+│ DIRECTIONS │
+├─────────────────────────────────────┤
+│ │
+│ From: [📍 Your location ][×] │
+│ ──────────────────────── │
+│ [⇅] │ ← Swap button
+│ ──────────────────────── │
+│ To: [Coffee shop on Main St][×] │
+│ │
+│ [+ Add stop] │
+│ │
+├─────────────────────────────────────┤
+│ [🚗 Auto] [🚶 Walk] [🚲 Bike] │
+├─────────────────────────────────────┤
+│ ┌─────────────────────────────┐ │
+│ │ 12 min · 4.2 mi │ │
+│ │ via W Main St │ │
+│ └─────────────────────────────┘ │
+│ │
+│ ▼ Turn-by-turn (expandable) │
+│ → Head north on Oak Ave │
+│ ↱ Turn right onto Main St │
+│ ◉ Arrive at destination │
+│ │
+└─────────────────────────────────────┘
+```
+
+### Input States
+
+**From field:**
+- GPS granted: Shows "📍 Your location" pill with clear button
+- GPS denied/cleared: Empty, placeholder "Starting point..."
+- Filled: Shows place name with clear button
+
+**To field:**
+- Empty: Placeholder "Destination..."
+- Filled: Shows place name with clear button
+
+**Active input:**
+- Blue border highlight
+- Search dropdown appears on typing
+- Map click populates this field
+
+---
+
+## 4. Visual Mockup — Radial Map Menu
+
+```
+ Drop pin
+ 🔴
+ ╱ ╲
+ ╱ ╲
+ Directions ╱ ╲ Directions
+ from here 🟢──────────────🔵 to here
+ │ 43.6166 │
+ │ -116.2008 │
+ │ [loading…] │ ← Center disc with coords/label
+ Add as 🟡──────────────🟣 Save place
+ stop ╲ ╱
+ ╲ ╱
+ ╲ ╱
+ 🟠
+ What's here
+```
+
+### Wedge Layout (60° each)
+
+| Position | Action | Icon | Color |
+|----------|--------|------|-------|
+| Top | Drop pin | Pin | Red |
+| Top-right | Directions to here | Arrow-in | Blue |
+| Bottom-right | Save place | Star + 🔒 | Purple |
+| Bottom | What's here | Info | Orange |
+| Bottom-left | Add as stop | Plus | Yellow |
+| Top-left | Directions from here | Arrow-out | Green |
+
+### Behavior
+
+- **Trigger:** Right-click (desktop) or long-press 450ms (mobile)
+- **Center disc:** ~40px diameter, shows coordinates immediately, reverse-geocoded label async
+- **Wedge highlight:** On hover (desktop) or drag-over (mobile)
+- **Commit:** Release on wedge (mobile) or click wedge (desktop)
+- **Cancel:** Release outside, Escape key, tap elsewhere
+
+---
+
+## 5. Component Breakdown
+
+### DirectionsPanel
+
+Replaces current Panel directions mode.
+
+```
+Props: none (reads from store)
+State: none (all in global store)
+Children:
+ - LocationInput (from)
+ - SwapButton
+ - LocationInput (to)
+ - WaypointList (if stops.length > 2)
+ - AddStopButton
+ - ModeSelector
+ - RouteSummary
+ - ManeuverList (collapsible)
+```
+
+### LocationInput
+
+Reusable component for from, to, and waypoint inputs.
+
+```
+Props:
+ - slot: 'from' | 'to' | `waypoint:${index}`
+ - value: { lat, lon, name, source } | null
+ - placeholder: string
+ - showGpsPill: boolean
+ - onClear: () => void
+
+Features:
+ - Search-as-you-type (Photon geocoder)
+ - GPS pill state with clear button
+ - Active-input visual state (blue border)
+ - Reverse-geocoded labels for coord-only entries
+ - Dropdown for search results
+```
+
+### SwapButton
+
+Simple button between From and To inputs.
+
+```
+Props: none
+Action: Swaps stops[0] and stops[stops.length - 1]
+Visual: ⇅ icon, hover highlight
+```
+
+### WaypointList
+
+Refactored from existing StopList, preserves drag-drop.
+
+```
+Props: none (reads stops from store)
+Features:
+ - Only renders stops[1..n-1] (middle waypoints)
+ - Drag-drop reordering via @dnd-kit
+ - Delete button per waypoint
+ - "Via" label prefix
+```
+
+### RadialMenu
+
+New general-purpose component.
+
+```
+Props:
+ - open: boolean
+ - x: number (screen X)
+ - y: number (screen Y)
+ - lat: number
+ - lon: number
+ - wedges: Array<{ id, icon, label, action: (lat, lon) => void }>
+ - onClose: () => void
+
+Features:
+ - Configurable wedge count and actions
+ - Async center label (reverse geocode)
+ - Keyboard dismissal (Escape)
+ - Touch-friendly sizing on mobile
+ - Fade in/out animations
+```
+
+---
+
+## 6. State Model
+
+### Existing (unchanged)
+
+```javascript
+stops: [] // Origin = stops[0], destination = stops[last], waypoints in between
+gpsOrigin: boolean // Whether GPS should be used as origin
+route: object | null // Valhalla trip response
+routeLoading: boolean
+routeError: string | null
+```
+
+### New
+
+```javascript
+activeInputSlot: 'from' | 'to' | `waypoint:${N}` | null
+// Which input is currently focused/active for map-click-to-fill
+
+radialMenuState: {
+ open: boolean,
+ x: number, // Screen coordinates
+ y: number,
+ lat: number, // Map coordinates
+ lon: number,
+ label: string | null // Reverse-geocoded, async populated
+}
+
+// Pin persistence (three-tier)
+transientPins: [] // In-memory, lost on refresh
+// localStorage: 'navi_saved_pins' key for guest saves
+// Backend sync: future work for authed users
+```
+
+### Removed
+
+```javascript
+pendingDestination: null // No longer needed — explicit inputs replace hidden state
+```
+
+---
+
+## 7. Interaction Flows
+
+### Open directions tab fresh
+
+1. From field shows GPS pill if `geoPermission === 'granted'`, else empty
+2. To field is empty, focused by default
+3. No route calculated yet
+
+### Click "Directions" from place panel
+
+1. Directions panel opens (if not already)
+2. To field auto-fills with selected place
+3. From field:
+ - If GPS granted: shows GPS pill
+ - Else: empty, receives focus
+4. Route calculates if both filled
+
+### Type in input
+
+1. Input receives focus, becomes `activeInputSlot`
+2. Photon search fires on debounce (300ms)
+3. Dropdown shows results
+4. Select result → populates input, clears dropdown
+5. Route recalculates
+
+### Right-click / long-press on map
+
+1. Radial menu appears centered on click point
+2. Center disc shows coordinates immediately
+3. Reverse geocode fires async, populates label
+4. User hovers/drags to wedge:
+
+| Wedge | Action |
+|-------|--------|
+| **Directions from here** | Opens directions if closed, fills From with coords, focuses To |
+| **Directions to here** | Opens directions if closed, fills To with coords, focuses From if empty |
+| **Add as stop** | Inserts new stop before destination |
+| **What's here** | Reverse geocode → opens place panel |
+| **Drop pin** | Creates transient marker (session-only by default) |
+| **Save place** | Guest: opens login flow; Authed: opens save dialog |
+
+5. Release outside or Escape → dismisses without action
+
+### Map click with active input
+
+When directions panel is open and an input is focused (`activeInputSlot !== null`):
+
+1. Single click on map
+2. Clicked coordinates populate the active input
+3. Reverse geocode fires to get display name
+4. Input loses focus, `activeInputSlot = null`
+5. Route recalculates
+
+### Map long-press / right-click with active input
+
+Even when an input is active:
+
+1. Long-press / right-click opens radial menu (overrides click-to-fill)
+2. User can select "Directions from here" or "Directions to here"
+3. Explicitly overrides the active input — intentional action takes priority
+
+### Map click with no active input
+
+1. No action (map interaction only)
+2. Future v2 consideration: reverse-geocode and show place panel
+
+### Swap button
+
+1. Click swap button
+2. `stops[0]` and `stops[stops.length - 1]` swap positions
+3. If GPS was origin, GPS pill moves to destination (unusual but allowed)
+4. Route recalculates
+
+---
+
+## 8. Place Panel "Directions" Handoff
+
+**Current behavior:** Calls `startDirections(place)` with complex conditional logic, may show toast.
+
+**New behavior:**
+
+```javascript
+handleDirections = () => {
+ // Always open directions panel
+ setActiveTab('directions')
+
+ // Fill destination
+ setStop(stops.length, { // Appends or replaces last
+ lat: place.lat,
+ lon: place.lon,
+ name: place.name,
+ source: place.source
+ })
+
+ // Handle origin
+ if (geoPermission === 'granted') {
+ setGpsOrigin(true) // GPS pill in From
+ } else if (stops.length === 0) {
+ setActiveInputSlot('from') // Focus From input
+ }
+
+ // Close place panel
+ clearSelectedPlace()
+}
+```
+
+**No toast needed** — UI is self-explanatory with visible From/To fields.
+
+---
+
+## 9. Radial Menu Specifics
+
+### Trigger
+
+| Platform | Gesture | Duration |
+|----------|---------|----------|
+| Desktop | Right-click | Instant |
+| Mobile | Long-press | 450ms |
+
+### Conflict Avoidance
+
+Long-press must NOT fire during active pan:
+- Track touch start position
+- If touch moves >5-10px before timer fires, cancel long-press
+- Pan gesture takes priority
+- Matches iOS Safari default contextmenu synthesis behavior
+
+### Geometry
+
+```
+Outer radius: ~80px from center (desktop), ~100px (mobile)
+Inner radius: ~40px (center disc, desktop), ~50px (mobile)
+Wedge angle: 60° each (6 wedges)
+Gap between wedges: 2px
+Minimum touch target: 48px per wedge
+```
+
+### Visual States
+
+| Element | Default | Hover/Active | Selected |
+|---------|---------|--------------|----------|
+| Wedge background | `rgba(0,0,0,0.7)` | `rgba(0,0,0,0.85)` | Wedge accent color |
+| Wedge icon | White, 50% opacity | White, 100% opacity | White |
+| Wedge label | Hidden | Shown (tooltip) | Shown |
+| Center disc | Dark, coords visible | — | — |
+| Save place (guest) | Lock icon overlay | — | — |
+
+### Animation
+
+- **Fade in:** <100ms ease-out
+- **Fade out:** <150ms ease-in
+- **Wedge hover:** Instant background change
+- **Center label:** Fade in when reverse geocode completes
+
+---
+
+## 10. Mobile Considerations
+
+### Panel Layout
+
+**Decision: Bottom sheet on mobile (<768px), side panel on desktop.**
+
+Bottom sheet states:
+- **Peek:** Route summary only (collapsed)
+- **Half:** Inputs + summary visible
+- **Full:** Inputs + turn-by-turn maneuvers
+
+Drag handle at top for resize between states.
+
+**Implementation notes:**
+- Use React state for sheet position: `sheetState: 'peek' | 'half' | 'full'`
+- CSS transforms for smooth transitions: `transform: translateY()`
+- Touch gesture detection for drag-to-resize
+
+### Long-press Timing
+
+**Decision: 450ms with 5-10px movement threshold.**
+
+If finger moves more than threshold during press window, abort long-press and treat as pan. This matches iOS Safari's default contextmenu synthesis behavior.
+
+**Implementation notes:**
+- Create reusable `useLongPress` hook
+- Track `touchstart` position
+- `setTimeout` for 450ms
+- Clear timeout on `touchmove` if delta > threshold
+- Fire callback on timeout completion
+
+### Radial Sizing
+
+Mobile radial larger for finger touch:
+- Outer radius: ~100px (vs 80px desktop)
+- Center disc: ~50px (vs 40px desktop)
+- Minimum wedge touch target: 48px
+
+### Compact Directions Mode
+
+When route is calculated and user is navigating:
+1. Collapse From/To inputs to single-line summary
+2. Show prominent next maneuver
+3. Expand on tap to edit inputs
+4. Maneuver list scrollable
+
+### Keyboard Awareness
+
+- Detect keyboard open via `visualViewport` API
+- Shift panel content up to keep active input visible
+- Don't let keyboard overlap input being typed in
+
+---
+
+## 11. Place Panel Restructure
+
+**Out of scope for this document.**
+
+Separate session will address:
+- Cleaner info card layout (Google Maps style)
+- Better visual hierarchy
+- Action button placement
+- No new data sources, just CSS/JSX polish
+
+---
+
+## 12. Out of Scope (Future Phases)
+
+| Feature | Notes |
+|---------|-------|
+| Saved routes | Auth required, dedicated work |
+| Route alternatives | Valhalla supports, surface in v2 |
+| Avoid tolls/highways | Valhalla supports via costing options |
+| Real-time rerouting | Requires location tracking loop |
+| Multi-modal | Drive + transit + walk hybrids |
+| Traffic-aware routing | Requires traffic data source |
+| Offline routing | Requires local Valhalla instance |
+| Search history backend persistence | Frontend localStorage first |
+| Saved places browsing UI | Future dedicated work |
+| Contacts view redesign | Separate from route-building |
+
+---
+
+## 13. Implementation Sequence
+
+| Phase | Task | Depends On |
+|-------|------|------------|
+| **a** | Build RadialMenu component (general-purpose, no actions wired) | — |
+| **b** | Wire "What's here" action to validate trigger + reverse-geocode flow | a |
+| **c** | Refactor SearchBar to single-mode (search-only, remove pending* logic) | — |
+| **d** | Build LocationInput component (reusable) | c |
+| **e** | Build DirectionsPanel layout with two LocationInputs | d |
+| **f** | Wire remaining radial actions to directions flow | b, e |
+| **g** | Wire place panel "Directions" handoff to new flow | e |
+| **h** | Add SwapButton | e |
+| **i** | Add map-click-to-fill-active-input | e |
+| **j** | Mobile polish (long-press timing, bottom sheet, keyboard) | a-i |
+
+### Phases k-o: Single-Panel Architecture
+
+| Phase | Task | Depends On |
+|-------|------|------------|
+| **k** | Refactor Panel.jsx to single-column state-driven content, remove right PlaceDetail panel | — |
+| **l** | Build shared place card component (preview + stop cards) | k |
+| **m** | Wire panel state transitions (IDLE → PREVIEW → ROUTING → ROUTE_CALCULATED) | l |
+| **n** | Mobile bottom sheet state mapping | m |
+| **o** | Search history (frontend localStorage scope first) | m |
+
+**Estimated phases:** 15 discrete tasks, can be done incrementally.
+
+---
+
+## 14. Resolved Decisions
+
+### 1. Mobile Layout
+
+**Decision:** Bottom sheet on mobile (<768px breakpoint), side panel on desktop.
+
+Three sheet states:
+- **Peek:** Summary only (route time/distance)
+- **Half:** Inputs + summary visible
+- **Full:** Inputs + turn-by-turn maneuvers
+
+Drag-to-resize between states via handle at top of sheet.
+
+**Implementation notes:**
+- `sheetState` in store: `'peek' | 'half' | 'full'`
+- CSS transforms (`translateY`) for smooth animation
+- Touch gesture handler for drag detection
+- Snap to nearest state on release
+
+### 2. Long-press Timing
+
+**Decision:** 450ms with 5-10px movement threshold.
+
+If finger moves more than threshold during the press window, abort long-press and treat as pan gesture. This matches iOS Safari's default contextmenu synthesis behavior.
+
+**Implementation notes:**
+- Create `useLongPress(callback, delay = 450)` hook
+- Track `touchstart` coordinates
+- On `touchmove`, check if `Math.hypot(dx, dy) > threshold`
+- If exceeded, clear timeout (abort)
+- If timeout fires without abort, invoke callback with coords
+
+### 3. Save Place for Guests
+
+**Decision:** Visible wedge with subtle lock icon overlay to indicate auth required.
+
+Tapping as guest opens login flow with return-to-action after auth completes.
+
+**Implementation notes:**
+- "Save place" wedge always visible
+- Lock icon (🔒) overlaid at 50% opacity on wedge icon
+- On tap: check auth state
+ - Authed: open save dialog
+ - Guest: trigger login modal with `returnAction: 'save-place'`
+- After successful auth, resume save flow
+
+### 4. Radial Ring Structure
+
+**Decision:** Single ring only. Six wedges maximum.
+
+Future expansion via contextual wedge sets (different actions based on what was clicked — e.g., clicking on a route segment could show "Avoid this road" instead of "Drop pin") rather than deeper nested rings.
+
+**Implementation notes:**
+- `wedges` prop on RadialMenu is array of 6 max
+- Context-aware wedge selection handled by parent component
+- No inner ring in v1; revisit if six actions prove insufficient
+
+### 5. Drop Pin Persistence
+
+**Decision:** Three-tier model.
+
+| Tier | Scope | Storage | Behavior |
+|------|-------|---------|----------|
+| Transient | Session | In-memory (`transientPins[]`) | Default. Lost on refresh. |
+| Guest save | Device | `localStorage` (`navi_saved_pins`) | Survives refresh, lost on cache clear or other device. |
+| Authed sync | Account | Backend API | Syncs across devices. Future work. |
+
+**Implementation notes:**
+- Transient pins: `transientPins: []` in store
+- Guest save: on "Save" tap, copy pin to localStorage
+- localStorage schema: `[{ id, lat, lon, name, createdAt }]`
+- Backend sync: TODO placeholder; initial implementation is localStorage only
+- Authed users see "Sync to account" option (greyed out with "Coming soon" for v1)
+
+### 6. Radial vs Click During Active Input
+
+**Decision:** Different gestures, different behaviors.
+
+| State | Gesture | Behavior |
+|-------|---------|----------|
+| Active input | Map click | Fills active input with click coords (reverse-geocoded) |
+| Active input | Map long-press / right-click | Opens radial (explicit override allowed) |
+| No active input | Map click | No action (v2: reverse-geocode + place panel) |
+| No active input | Map long-press / right-click | Opens radial (primary use case) |
+
+**Implementation notes:**
+- Click handler checks `activeInputSlot`
+ - If set: fill input, reverse-geocode for label
+ - If null: no-op (or v2 place panel)
+- Long-press / contextmenu handler always opens radial
+- Radial "Directions from/to here" explicitly sets input, overriding prior value
+
+---
+
+## 15. Panel Architecture (Single-Column Model)
+
+This section defines the broader panel design that consolidates today's
+two-panel layout (Routes/Contacts + floating PlaceDetail) into one
+always-visible left column with state-driven content.
+
+### Overview
+
+The left panel is one always-visible column with state-driven content.
+The right-side PlaceDetail panel is **removed**; its content moves into
+the left panel.
+
+**Width:** ~360px desktop. Mobile: panel becomes bottom sheet per
+Section 10.
+
+**Visual style:** Panel is always anchored at the left edge of the
+viewport with consistent panel background. When idle (no preview, no
+route), only the search bar is visible at the top — the rest of the
+panel is empty space. The panel never disappears; it just shows less
+or more content depending on state.
+
+### Panel States
+
+Panel states are mutually exclusive content modes:
+
+#### IDLE (no preview, no route)
+
+- Search bar at top
+- Empty body (or subtle empty-state prompt: "Search or click a place
+ to begin")
+- Recent activity / search history (when implemented)
+
+#### PREVIEW (place selected via map click or search, not committed to route)
+
+- Search bar at top
+- Preview card: full place detail (name, type, coords, elevation,
+ land class, about, contact, links)
+- Action buttons inside or below the card: [Directions] [Add stop]
+ [Save] [Share] [x]
+- Click another place: preview replaced (previous lost — search
+ history retains it)
+- Click x or Escape: preview dismissed, return to IDLE
+
+#### ROUTING (1+ stops, no preview)
+
+- Search bar at top
+- Section: "Route" with stop cards
+- Each stop card: collapsed by default (header: pin + name +
+ drag handle + remove); expandable to show full detail
+- Drag-to-reorder via card headers (preserves existing StopList)
+- Per-card actions: Save, Share, Remove
+- Bottom: [Get Directions] (when 2+ stops with valid mode), mode
+ selector (auto/walk/bike), trip summary placeholder
+
+#### PREVIEW + ROUTING (place clicked while route exists)
+
+- Search bar at top
+- Preview card directly below search
+- Route section below preview
+- Both visible simultaneously
+- Multiple cards can be expanded at once (preview + a stop)
+
+#### ROUTE_CALCULATED (after route fetch)
+
+- Search bar at top
+- Route summary (total time, distance)
+- Mode selector
+- Stop cards (collapsed; expand for detail)
+- Turn-by-turn maneuvers
+- Preview can appear on top if user clicks a place
+
+### Search Bar Behavior
+
+- Pinned to top of panel
+- Always for browsing (search -> preview, never auto-add as stop)
+
+#### Search History (aspirational — design now, implement later)
+
+- Empty search -> dropdown shows 5 most recent searches
+- Authed: persisted to backend
+- Guest: localStorage
+- Typing: recent matches sorted to top of search results
+
+### Card Pattern
+
+Shared between preview and stop cards. Both use the same component,
+parameterized by role.
+
+#### Header (always visible)
+
+- Pin/marker icon (color varies by role — preview vs stop number)
+- Place name
+- Expand/collapse chevron
+- For stops: drag handle, remove (x)
+
+#### Body (collapsed by default for stops, expanded by default for preview)
+
+- Type / category
+- Coordinates + elevation + land class
+- About (description, Wikipedia excerpt)
+- Contact (phone, website, hours)
+- Links (Wikipedia, OSM, Wikidata)
+- Action buttons:
+ - Preview: [Directions] [Add stop] [Save] [Share]
+ - Stop: [Save] [Share]
+
+### State Transitions
+
+```
+IDLE -> PREVIEW : click place / search-select
+PREVIEW -> IDLE : x button / Escape
+PREVIEW -> PREVIEW (different) : click another place
+PREVIEW -> ROUTING : "Add stop" on preview
+PREVIEW -> ROUTE_CALCULATED : "Directions" (becomes destination,
+ route auto-calculates if From available)
+ROUTING -> IDLE : remove all stops
+ROUTING -> PREVIEW + ROUTING : click place
+ROUTING -> ROUTE_CALCULATED : "Get Directions" / auto-route trigger
+PREVIEW + ROUTING -> ROUTING : dismiss preview
+PREVIEW + ROUTING -> ROUTING : "Add stop" on preview (adds new stop)
+ROUTE_CALCULATED -> ROUTING : edit route
+ROUTE_CALCULATED -> IDLE : clear route
+```
+
+### Routes / Contacts Tabs
+
+Tentative: Routes is default panel. Contacts is a separate view
+accessible via a tab or icon at the top. Contacts view replaces
+route/preview content with a contact list — does not combine with
+route-building.
+
+### Map Zoom-to-Feature
+
+With single panel, padding becomes simpler — just one panel width
+(~360px) on the left. Top/right/bottom can be small (~50px each).
+
+### Mobile Bottom Sheet State Mapping
+
+Same logic as desktop, layout rotated:
+
+| State | Sheet Position |
+|---------------------|----------------|
+| IDLE | peek (search bar visible) |
+| PREVIEW | half (preview card) |
+| ROUTING | half (stops list) |
+| PREVIEW + ROUTING | full (both, scrollable) |
+| ROUTE_CALCULATED | half/full (summary + maneuvers) |
+
+---
+
+## 16. Open Questions
+
+### From Single-Panel Architecture
+
+- Routes/Contacts tab location and behavior in single-panel model
+- Preview-of-already-routed-place: probably auto-expands the existing
+ stop card, no duplicate preview
+- Preview card position confirmed above route section (per mock)
+
+
+---
+
+## Appendix A: Current Code References
+
+| File | Lines | Relevance |
+|------|-------|-----------|
+| `store.js` | 72-86 | `startDirections()` logic to replace |
+| `store.js` | 16-34 | `stops[]` management to preserve |
+| `SearchBar.jsx` | 140-170 | `pendingDestination` logic to remove |
+| `PlaceDetail.jsx` | 574-579 | `handleDirections()` to rewrite |
+| `App.jsx` | 31-66 | Route fetch effect to preserve |
+| `api.js` | 29-56 | `requestRoute()` unchanged |
+
+---
+
+## Appendix B: Radial Menu SVG Structure
+
+```svg
+
+```
+
+---
+
+*Document created 2026-04-26. Updated with resolved decisions and single-panel architecture (Phases k-o). Implementation to follow in dedicated session.*