diff --git a/docs/plans/2026-02-21-card-carousel-tap-detail-plan.md b/docs/plans/2026-02-21-card-carousel-tap-detail-plan.md
new file mode 100644
index 0000000..e68d349
--- /dev/null
+++ b/docs/plans/2026-02-21-card-carousel-tap-detail-plan.md
@@ -0,0 +1,260 @@
+# Card Carousel and Tap-to-Detail Implementation Plan
+
+> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
+
+**Goal:** Replace single thumbnail with 5-photo carousel on listing cards and fix tap behavior to open the detail bottom sheet.
+
+**Architecture:** Backend adds `additional_info` to streaming columns, extracts first 5 photo URLs into a `photos` property in GeoJSON output. Frontend adds a compact embla-carousel to PropertyCard and changes click handler to open the detail sheet instead of navigating to Rightmove.
+
+**Tech Stack:** Python (backend GeoJSON export), React + TypeScript + embla-carousel-react (frontend)
+
+---
+
+### Task 1: Backend — Add photos to GeoJSON streaming
+
+**Files:**
+- Modify: `repositories/listing_repository.py:19-25` (STREAMING_COLUMNS)
+- Modify: `ui_exporter.py:12-85` (convert_row_to_geojson)
+- Modify: `ui_exporter.py:88-134` (convert_to_geojson_feature)
+
+**Step 1: Add `additional_info` to STREAMING_COLUMNS**
+
+In `repositories/listing_repository.py`, add `'additional_info'` to the list:
+
+```python
+STREAMING_COLUMNS = [
+ 'id', 'price', 'number_of_bedrooms', 'square_meters',
+ 'longitude', 'latitude', 'photo_thumbnail', 'last_seen',
+ 'agency', 'price_history_json', 'available_from',
+ 'service_charge', 'lease_left', 'additional_info',
+]
+```
+
+**Step 2: Extract photos in `convert_row_to_geojson`**
+
+In `ui_exporter.py`, after the `available_from` handling block (after line 47), add photo extraction:
+
+```python
+# Extract first 5 photo URLs from additional_info
+photos: list[str] = []
+additional_info = row.get('additional_info')
+if additional_info:
+ if isinstance(additional_info, str):
+ import json as _json
+ additional_info = _json.loads(additional_info)
+ images = additional_info.get('property', {}).get('images', [])
+ photos = [img['url'] for img in images[:5] if isinstance(img, dict) and 'url' in img]
+if not photos and row.get('photo_thumbnail'):
+ photos = [row['photo_thumbnail']]
+```
+
+Then add `"photos": photos,` to the properties dict (after line 66, the `photo_thumbnail` line).
+
+**Step 3: Extract photos in `convert_to_geojson_feature`**
+
+In `ui_exporter.py`, in `convert_to_geojson_feature`, `property_info` is already extracted at line 98. After line 98, add:
+
+```python
+images = property_info.get('images', [])
+photos = [img['url'] for img in images[:5] if isinstance(img, dict) and 'url' in img]
+if not photos and listing.photo_thumbnail:
+ photos = [listing.photo_thumbnail]
+```
+
+Then add `"photos": photos,` to the properties dict (after the `photo_thumbnail` line).
+
+**Step 4: Run backend tests**
+
+Run: `pytest tests/ -v --tb=short -k "export or exporter or geojson"`
+Expected: All pass (no tests directly assert the exact properties shape for photos)
+
+**Step 5: Commit**
+
+```bash
+git add repositories/listing_repository.py ui_exporter.py
+git commit -m "feat: include first 5 photo URLs in GeoJSON listing properties"
+```
+
+---
+
+### Task 2: Frontend — Add photos to type definition
+
+**Files:**
+- Modify: `frontend/src/types/index.ts:10-26` (PropertyProperties)
+
+**Step 1: Add `photos` field**
+
+In `PropertyProperties` interface, add after line 22 (`photo_thumbnail`):
+
+```typescript
+ photos?: string[];
+```
+
+**Step 2: Commit**
+
+```bash
+git add frontend/src/types/index.ts
+git commit -m "feat: add photos array to PropertyProperties type"
+```
+
+---
+
+### Task 3: Frontend — Add compact carousel to PropertyCard
+
+**Files:**
+- Modify: `frontend/src/components/PropertyCard.tsx:1-3` (imports)
+- Modify: `frontend/src/components/PropertyCard.tsx:117-120` (handleClick)
+- Modify: `frontend/src/components/PropertyCard.tsx:130-139` (compact thumbnail)
+
+**Step 1: Add embla imports**
+
+Add at top of PropertyCard.tsx, after the existing imports:
+
+```typescript
+import { useState, useCallback, useEffect } from 'react';
+import useEmblaCarousel from 'embla-carousel-react';
+```
+
+**Step 2: Fix handleClick — remove window.open**
+
+Replace lines 117-120:
+
+```typescript
+const handleClick = () => {
+ window.open(property.url, '_blank', 'noopener,noreferrer');
+ onClick?.();
+};
+```
+
+With:
+
+```typescript
+const handleClick = () => {
+ onClick?.();
+};
+```
+
+**Step 3: Add CardCarousel component**
+
+Add a small inline component before the `PropertyCard` function (after the `AllPOIDistances` component, before the `PropertyCardProps` interface). This keeps carousel logic isolated:
+
+```typescript
+function CardCarousel({ photos }: { photos: string[] }) {
+ const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true, dragFree: false });
+ const [selectedIndex, setSelectedIndex] = useState(0);
+
+ const onSelect = useCallback(() => {
+ if (!emblaApi) return;
+ setSelectedIndex(emblaApi.selectedScrollSnap());
+ }, [emblaApi]);
+
+ useEffect(() => {
+ if (!emblaApi) return;
+ emblaApi.on('select', onSelect);
+ return () => { emblaApi.off('select', onSelect); };
+ }, [emblaApi, onSelect]);
+
+ if (photos.length <= 1) {
+ return (
+
+ );
+ }
+
+ return (
+