diff --git a/src/App.tsx b/src/App.tsx
index d6084a5..6acdf8f 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -7,6 +7,7 @@ import { StatsBar } from './components/StatsBar'
import { FilterBar } from './components/FilterBar'
import { StoreCard } from './components/StoreCard'
import { StoreTable } from './components/StoreTable'
+import { MobileRolodex } from './components/MobileRolodex'
import { StoreModal } from './components/StoreModal'
import { DeleteConfirm } from './components/DeleteConfirm'
import { Pagination } from './components/Pagination'
@@ -185,11 +186,9 @@ export default function App() {
) : (
<>
- {/* Mobile: always cards */}
-
- {stores.map(s => (
-
- ))}
+ {/* Mobile: rolodex */}
+
+
{/* Desktop: toggle between table and grid */}
diff --git a/src/components/MobileRolodex.tsx b/src/components/MobileRolodex.tsx
new file mode 100644
index 0000000..98da624
--- /dev/null
+++ b/src/components/MobileRolodex.tsx
@@ -0,0 +1,186 @@
+import { useState, useRef } from 'react'
+import { Pencil, Trash2, ChevronLeft, ChevronRight } from 'lucide-react'
+import type { Store } from '../types'
+import { RouteBadge } from './RouteBadge'
+import { TagChip } from './TagChip'
+import { parseTags, formatDate } from '../utils'
+
+interface Props {
+ stores: Store[]
+ onEdit: (store: Store) => void
+ onDelete: (store: Store) => void
+}
+
+export function MobileRolodex({ stores, onEdit, onDelete }: Props) {
+ const [index, setIndex] = useState(0)
+ const [dragging, setDragging] = useState(false)
+ const [dragOffset, setDragOffset] = useState(0)
+ const touchStartX = useRef(0)
+ const touchStartY = useRef(0)
+ const isHorizontalDrag = useRef
(null)
+
+ const total = stores.length
+
+ function goTo(i: number) {
+ setIndex(Math.max(0, Math.min(total - 1, i)))
+ setDragOffset(0)
+ }
+
+ function onTouchStart(e: React.TouchEvent) {
+ touchStartX.current = e.touches[0].clientX
+ touchStartY.current = e.touches[0].clientY
+ isHorizontalDrag.current = null
+ setDragging(true)
+ }
+
+ function onTouchMove(e: React.TouchEvent) {
+ const dx = e.touches[0].clientX - touchStartX.current
+ const dy = e.touches[0].clientY - touchStartY.current
+
+ // Determine drag axis on first significant move
+ if (isHorizontalDrag.current === null && (Math.abs(dx) > 4 || Math.abs(dy) > 4)) {
+ isHorizontalDrag.current = Math.abs(dx) > Math.abs(dy)
+ }
+
+ if (isHorizontalDrag.current) {
+ e.preventDefault()
+ // Dampen drag at edges
+ const atStart = index === 0 && dx > 0
+ const atEnd = index === total - 1 && dx < 0
+ setDragOffset(atStart || atEnd ? dx * 0.2 : dx)
+ }
+ }
+
+ function onTouchEnd() {
+ setDragging(false)
+ if (isHorizontalDrag.current && Math.abs(dragOffset) > 60) {
+ dragOffset < 0 ? goTo(index + 1) : goTo(index - 1)
+ } else {
+ setDragOffset(0)
+ }
+ isHorizontalDrag.current = null
+ }
+
+ if (total === 0) return null
+
+ // Positions relative to current index
+ function getCardStyle(offset: number): React.CSSProperties {
+ const x = offset * 88 + dragOffset * (offset === 0 ? 1 : 0.15)
+ const scale = offset === 0 ? 1 : 0.88
+ const opacity = Math.abs(offset) > 1 ? 0 : offset === 0 ? 1 : 0.5
+ const zIndex = offset === 0 ? 10 : Math.abs(offset) === 1 ? 5 : 0
+ const rotateY = offset === 0 ? (dragOffset * -0.04) : (offset > 0 ? -6 : 6)
+
+ return {
+ transform: `translateX(${x}%) scale(${scale}) rotateY(${rotateY}deg)`,
+ opacity,
+ zIndex,
+ transition: dragging ? 'none' : 'all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)',
+ }
+ }
+
+ return (
+
+ {/* Card stack */}
+
+ {/* Render prev, current, next (and one more each side for depth) */}
+ {[-2, -1, 0, 1, 2].map(offset => {
+ const cardIndex = index + offset
+ if (cardIndex < 0 || cardIndex >= total) return null
+ const store = stores[cardIndex]
+ const storeTagList = parseTags(store.tags)
+ return (
+
+ {/* Card header */}
+
+
+
{store.store}
+
{store.category}
+
+
+
+
+ {/* Tags */}
+ {storeTagList.length > 0 && (
+
+ {storeTagList.map(t => )}
+
+ )}
+
+
+
{formatDate(store.created_at)}
+ {/* Actions only on current card */}
+ {offset === 0 && (
+
+
+
+
+ )}
+
+
+ )
+ })}
+
+
+ {/* Navigation */}
+
+
+
+ {/* Dot indicators — show up to 9, scroll window for larger sets */}
+
+ {total <= 9 ? (
+ Array.from({ length: total }, (_, i) => (
+
+
+
+
+
+ )
+}