# BloomBase UI Redesign — Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Replace Bootstrap 5 with Tailwind CSS, add a 5-tab bottom navigation, extend `Location` with a cover photo field, restyle all existing screens, and add a new Area Detail page — producing the vibrant organic aesthetic from Naomi's mockup.
**Architecture:** New `bloombase.css` holds fonts + CSS blob/flower decorations; `base.html` is fully rewritten with Tailwind CDN + bottom nav; all child templates are rewritten to use Tailwind utilities; one new view + template (`location_detail`) handles the Area Detail screen; `Location` model gains a nullable `cover_photo` ImageField with its own migration.
**Tech Stack:** Django 5.x, HTMX 1.9, Tailwind CSS Play CDN (development), Google Fonts (Great Vibes + Nunito + Pacifico), SQLite
---
## File Map
| File | Action |
|---|---|
| `plants/static/plants/css/bloombase.css` | **Create** — fonts, blob shapes, canvas flowers |
| `plants/templates/plants/base.html` | **Rewrite** — Tailwind, bottom nav, decorations |
| `plants/models.py` | **Modify** — add `cover_photo` to `Location` |
| `plants/migrations/XXXX_location_cover_photo.py` | **Create** via `makemigrations` |
| `plants/forms.py` | **Modify** — add `LocationForm` with `cover_photo` |
| `plants/views/locations.py` | **Modify** — use `LocationForm`; add `location_detail` view |
| `plants/urls.py` | **Modify** — add `location_detail` URL |
| `plants/templates/plants/locations.html` | **Rewrite** — My Garden screen |
| `plants/templates/plants/location_detail.html` | **Create** — Area Detail screen |
| `plants/templates/plants/location_edit.html` | **Rewrite** — add cover photo field |
| `plants/templates/plants/dashboard.html` | **Rewrite** — camera-first Home screen |
| `plants/templates/plants/plant_list.html` | **Rewrite** — search + filter chips |
| `plants/templates/plants/plant_detail.html` | **Rewrite** — hero + care + CTA |
| `plants/templates/plants/identify_upload.html` | **Rewrite** — viewfinder camera UI |
| `plants/templates/plants/identify_confirm.html` | **Rewrite** — match list restyle |
| `plants/tests/test_location_redesign.py` | **Create** — model + view tests |
---
## Task 1: Static CSS — bloombase.css
Creates the custom stylesheet: Google Fonts import, CSS blob background shapes, and fixed-position SVG flower decorations.
**Files:**
- Create: `plants/static/plants/css/bloombase.css`
- [ ] **Step 1.1: Create the static directory and file**
```bash
mkdir -p plants/static/plants/css
```
- [ ] **Step 1.2: Write bloombase.css**
```css
/* plants/static/plants/css/bloombase.css */
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700;800;900&family=Great+Vibes&family=Pacifico&display=swap');
/* ── Body canvas ── */
body {
background-color: #FFF8F2;
font-family: 'Nunito', sans-serif;
min-height: 100vh;
position: relative;
overflow-x: hidden;
}
/* ── Background blob shapes ── */
.bb-blob-tl {
position: fixed; top: -100px; left: -80px;
width: 380px; height: 380px;
background: radial-gradient(ellipse at 45% 45%, #F9C090 0%, #F07040 55%, #E05020 100%);
border-radius: 62% 38% 54% 46% / 58% 44% 56% 42%;
opacity: 0.18;
pointer-events: none; z-index: 0;
}
.bb-blob-br {
position: fixed; bottom: -80px; right: -60px;
width: 340px; height: 340px;
background: radial-gradient(ellipse at 55% 55%, #FFD080 0%, #F8A040 60%, #F07040 100%);
border-radius: 44% 56% 38% 62% / 52% 48% 52% 48%;
opacity: 0.16;
pointer-events: none; z-index: 0;
}
/* ── Canvas SVG flowers ── */
.bb-flower { position: fixed; pointer-events: none; z-index: 0; }
.bb-flower-tr { top: 16px; right: 24px; }
.bb-flower-ml { top: 38%; left: -4px; }
.bb-flower-br { bottom: 72px; right: 48px; }
.bb-flower-bl { bottom: 88px; left: 32px; }
/* ── Brand fonts ── */
.font-script { font-family: 'Great Vibes', cursive; }
.font-pacifico { font-family: 'Pacifico', cursive; }
.font-nunito { font-family: 'Nunito', sans-serif; }
/* ── Bottom nav stays above content ── */
#bottom-nav {
position: fixed; bottom: 0; left: 0; right: 0;
background: #fff;
border-top: 1px solid #F0E8DF;
height: 62px;
display: flex; align-items: center; justify-content: space-around;
z-index: 50;
padding-bottom: env(safe-area-inset-bottom, 0);
}
/* ── FAB inside nav ── */
.bb-fab {
width: 44px; height: 44px; border-radius: 50%;
background: linear-gradient(145deg, #F58040, #E84020);
box-shadow: 0 4px 16px rgba(240, 80, 40, 0.5);
display: flex; align-items: center; justify-content: center;
color: #fff; font-size: 24px; line-height: 1; font-weight: 300;
flex-shrink: 0;
}
/* ── Gradient CTA button ── */
.btn-bloom {
display: block; width: 100%;
background: linear-gradient(135deg, #F58040, #E84020);
color: #fff; border: none; border-radius: 50px;
padding: 12px 24px; font-family: 'Nunito', sans-serif;
font-weight: 800; font-size: 15px; text-align: center;
box-shadow: 0 4px 14px rgba(240,80,40,0.4); cursor: pointer;
text-decoration: none;
}
.btn-bloom-outline {
display: block; width: 100%;
background: transparent; color: #1A1208;
border: 1.5px solid #E0D4C8; border-radius: 50px;
padding: 11px 24px; font-family: 'Nunito', sans-serif;
font-weight: 700; font-size: 15px; text-align: center;
cursor: pointer; text-decoration: none;
}
/* ── White pill button (hero overlays) ── */
.btn-pill {
background: rgba(255,255,255,0.88);
border: none; border-radius: 50px;
padding: 6px 14px; font-size: 13px; font-weight: 700;
color: #1A1208; backdrop-filter: blur(4px); cursor: pointer;
text-decoration: none;
}
/* ── Confidence bar ── */
.conf-track { height: 5px; background: #F0E4D8; border-radius: 3px; overflow: hidden; }
.conf-fill { height: 100%; background: linear-gradient(90deg, #F8A060, #E84020); border-radius: 3px; }
/* ── Area thumbnail gradient placeholders ── */
.at-placeholder-1 { background: linear-gradient(135deg, #88C860, #E85878, #A0D060); }
.at-placeholder-2 { background: linear-gradient(150deg, #B8E060, #F0A840, #E87830); }
.at-placeholder-3 { background: linear-gradient(120deg, #F8B840, #88C060, #F0C860); }
.at-placeholder-4 { background: linear-gradient(140deg, #D0A8E8, #68B858, #C888E0); }
.at-placeholder-5 { background: linear-gradient(130deg, #78D8C8, #E8F0A0, #60C8A8); }
.at-placeholder-6 { background: linear-gradient(125deg, #F8A0B0, #B8E060, #F09060); }
/* ── Viewfinder corners ── */
.vf-corner { position: absolute; width: 18px; height: 18px; border-color: rgba(255,255,255,0.9); border-style: solid; }
.vf-tl { top: 10px; left: 10px; border-width: 2.5px 0 0 2.5px; border-radius: 2px 0 0 0; }
.vf-tr { top: 10px; right: 10px; border-width: 2.5px 2.5px 0 0; border-radius: 0 2px 0 0; }
.vf-bl { bottom: 10px; left: 10px; border-width: 0 0 2.5px 2.5px; border-radius: 0 0 0 2px; }
.vf-br { bottom: 10px; right: 10px; border-width: 0 2.5px 2.5px 0; border-radius: 0 0 2px 0; }
```
- [ ] **Step 1.3: Commit**
```bash
git add plants/static/plants/css/bloombase.css
git commit -m "feat: add bloombase.css — fonts, blobs, canvas flowers, shared components"
```
---
## Task 2: Rewrite base.html
Full replacement of the Bootstrap top-nav shell with Tailwind CDN + 5-tab bottom nav + background canvas decorations.
**Files:**
- Modify: `plants/templates/plants/base.html`
- [ ] **Step 2.1: Replace base.html**
```html
{# plants/templates/plants/base.html #}
{% block title %}BloomBase{% endblock %}
{% load static %}
{% block extra_head %}{% endblock %}
{% block content %}{% endblock %}
```
- [ ] **Step 2.2: Verify the app still loads (no crash)**
```bash
cd /home/stephan/devel/python/plantdb
python manage.py check
```
Expected: `System check identified no issues (0 silenced).`
- [ ] **Step 2.3: Commit**
```bash
git add plants/templates/plants/base.html
git commit -m "feat: rewrite base.html — Tailwind CDN, 5-tab bottom nav, canvas decorations"
```
---
## Task 3: Location model — add cover_photo + migration
**Files:**
- Modify: `plants/models.py`
- Create: migration (via `makemigrations`)
- Create: `plants/tests/test_location_redesign.py`
- [ ] **Step 3.1: Write failing test first**
```python
# plants/tests/test_location_redesign.py
import pytest
from django.core.files.uploadedfile import SimpleUploadedFile
from plants.models import Location
@pytest.mark.django_db
def test_location_cover_photo_field_exists():
"""Location can be saved with and without a cover_photo."""
loc = Location.objects.create(name='Test Garden')
assert loc.cover_photo.name == '' or loc.cover_photo.name is None
fake_img = SimpleUploadedFile('cover.jpg', b'fake-image-data', content_type='image/jpeg')
loc.cover_photo = fake_img
loc.save()
loc.refresh_from_db()
assert loc.cover_photo.name.startswith('locations/')
```
- [ ] **Step 3.2: Run test — expect failure**
```bash
python -m pytest plants/tests/test_location_redesign.py::test_location_cover_photo_field_exists -v
```
Expected: `FAILED` — `AttributeError: 'Location' object has no attribute 'cover_photo'`
- [ ] **Step 3.3: Add cover_photo to Location model**
In `plants/models.py`, change:
```python
class Location(models.Model):
name = models.CharField(max_length=200, unique=True)
```
to:
```python
class Location(models.Model):
name = models.CharField(max_length=200, unique=True)
cover_photo = models.ImageField(upload_to='locations/', blank=True, null=True)
```
- [ ] **Step 3.4: Create migration**
```bash
python manage.py makemigrations plants --name location_cover_photo
```
Expected output: `Migrations for 'plants': plants/migrations/XXXX_location_cover_photo.py`
- [ ] **Step 3.5: Apply migration**
```bash
python manage.py migrate
```
Expected: `Applying plants.XXXX_location_cover_photo... OK`
- [ ] **Step 3.6: Run test — expect pass**
```bash
python -m pytest plants/tests/test_location_redesign.py::test_location_cover_photo_field_exists -v
```
Expected: `PASSED`
- [ ] **Step 3.7: Commit**
```bash
git add plants/models.py plants/migrations/ plants/tests/test_location_redesign.py
git commit -m "feat: add Location.cover_photo ImageField + migration + test"
```
---
## Task 4: LocationForm + updated views + new location_detail + URL
**Files:**
- Modify: `plants/forms.py`
- Modify: `plants/views/locations.py`
- Modify: `plants/urls.py`
- Modify: `plants/tests/test_location_redesign.py`
- [ ] **Step 4.1: Write failing tests**
Append to `plants/tests/test_location_redesign.py`:
```python
from django.test import Client
from django.urls import reverse
@pytest.fixture
def client():
return Client()
@pytest.mark.django_db
def test_location_detail_view_returns_200(client):
loc = Location.objects.create(name='My Terrace')
resp = client.get(reverse('location_detail', kwargs={'pk': loc.pk}))
assert resp.status_code == 200
assert b'My Terrace' in resp.content
@pytest.mark.django_db
def test_location_edit_with_cover_photo(client):
loc = Location.objects.create(name='Balcony')
fake_img = SimpleUploadedFile('cover.jpg', b'data', content_type='image/jpeg')
resp = client.post(
reverse('location_edit', kwargs={'pk': loc.pk}),
{'name': 'Balcony Updated', 'cover_photo': fake_img},
)
assert resp.status_code == 302
loc.refresh_from_db()
assert loc.name == 'Balcony Updated'
assert loc.cover_photo.name.startswith('locations/')
```
- [ ] **Step 4.2: Run tests — expect failure**
```bash
python -m pytest plants/tests/test_location_redesign.py -v -k "detail or edit_with_cover"
```
Expected: `FAILED` — `NoReverseMatch: location_detail` and related errors.
- [ ] **Step 4.3: Add LocationForm to forms.py**
Append to `plants/forms.py`:
```python
class LocationForm(forms.ModelForm):
class Meta:
model = Location
fields = ['name', 'cover_photo']
```
- [ ] **Step 4.4: Rewrite views/locations.py**
```python
# plants/views/locations.py
from django.shortcuts import render, get_object_or_404, redirect
from django.views.decorators.http import require_POST
from django.db.models import Count
from plants.models import Location, Plant
from plants.forms import LocationForm
def location_list(request):
if request.method == 'POST':
name = request.POST.get('name', '').strip()
if name:
Location.objects.get_or_create(name=name)
return redirect('location_list')
locations = Location.objects.annotate(plant_count=Count('plants'))
return render(request, 'plants/locations.html', {
'locations': locations,
'active_tab': 'garden',
})
def location_detail(request, pk):
location = get_object_or_404(Location, pk=pk)
plants = location.plants.select_related('species').prefetch_related('photos').all()
return render(request, 'plants/location_detail.html', {
'location': location,
'plants': plants,
'active_tab': 'garden',
})
def location_edit(request, pk):
location = get_object_or_404(Location, pk=pk)
if request.method == 'POST':
form = LocationForm(request.POST, request.FILES, instance=location)
if form.is_valid():
form.save()
return redirect('location_list')
else:
form = LocationForm(instance=location)
return render(request, 'plants/location_edit.html', {
'location': location,
'form': form,
'active_tab': 'garden',
})
@require_POST
def location_delete(request, pk):
location = get_object_or_404(Location, pk=pk)
Plant.objects.filter(location=location).update(location=None)
location.delete()
return redirect('location_list')
```
- [ ] **Step 4.5: Add location_detail URL**
In `plants/urls.py`, add after the existing `locations/` URL:
```python
path('locations//', locations.location_detail, name='location_detail'),
```
The full relevant section of urlpatterns should look like:
```python
path('locations/', locations.location_list, name='location_list'),
path('locations//', locations.location_detail, name='location_detail'),
path('locations//edit/', locations.location_edit, name='location_edit'),
path('locations//delete/', locations.location_delete, name='location_delete'),
```
- [ ] **Step 4.6: Run tests — expect pass**
```bash
python -m pytest plants/tests/test_location_redesign.py -v
```
Expected: all `PASSED`
- [ ] **Step 4.7: Commit**
```bash
git add plants/forms.py plants/views/locations.py plants/urls.py plants/tests/test_location_redesign.py
git commit -m "feat: add LocationForm with cover_photo, location_detail view, update location_edit"
```
---
## Task 5: locations.html — My Garden screen
**Files:**
- Modify: `plants/templates/plants/locations.html`
- [ ] **Step 5.1: Rewrite locations.html**
```html
{# plants/templates/plants/locations.html #}
{% extends "plants/base.html" %}
{% block title %}My Garden — BloomBase{% endblock %}
{% block content %}
{% load static %}
{% endblock %}
```
- [ ] **Step 5.2: Verify page renders**
```bash
python manage.py runserver 0.0.0.0:8000
```
Open http://localhost:8000/locations/ — expect My Garden screen with area rows and + form.
- [ ] **Step 5.3: Commit**
```bash
git add plants/templates/plants/locations.html
git commit -m "feat: restyle locations.html as My Garden screen with cover photo thumbnails"
```
---
## Task 6: location_detail.html — Area Detail screen
**Files:**
- Create: `plants/templates/plants/location_detail.html`
- [ ] **Step 6.1: Create location_detail.html**
```html
{# plants/templates/plants/location_detail.html #}
{% extends "plants/base.html" %}
{% block title %}{{ location.name }} — BloomBase{% endblock %}
{% block content %}
{% if location.cover_photo %}

{% else %}
{% endif %}
← Back
Edit
{{ location.name }}
{{ plants.count }} plant{{ plants.count|pluralize }}
{% if plants %}
{% else %}
🪴
No plants here yet.
Identify a plant
{% endif %}
{% endblock %}
```
- [ ] **Step 6.2: Verify page renders**
Open http://localhost:8000/locations/1/ (replace 1 with a real pk) — expect hero + plant grid.
- [ ] **Step 6.3: Commit**
```bash
git add plants/templates/plants/location_detail.html
git commit -m "feat: add location_detail.html — area detail with cover photo header + plant grid"
```
---
## Task 7: location_edit.html — add cover photo upload
**Files:**
- Modify: `plants/templates/plants/location_edit.html`
- [ ] **Step 7.1: Rewrite location_edit.html**
```html
{# plants/templates/plants/location_edit.html #}
{% extends "plants/base.html" %}
{% block title %}Edit Area — BloomBase{% endblock %}
{% block content %}
{% endblock %}
```
- [ ] **Step 7.2: Verify form submits with photo**
Open http://localhost:8000/locations/1/edit/ — fill name, attach image, save. Confirm redirect to /locations/ with updated thumbnail.
- [ ] **Step 7.3: Commit**
```bash
git add plants/templates/plants/location_edit.html
git commit -m "feat: restyle location_edit.html with cover photo upload field"
```
---
## Task 8: dashboard.html — camera-first Home screen
Also update `dashboard` view to pass `active_tab`.
**Files:**
- Modify: `plants/views/dashboard.py`
- Modify: `plants/templates/plants/dashboard.html`
- [ ] **Step 8.1: Update dashboard view to pass active_tab and location count**
```python
# plants/views/dashboard.py
from datetime import date
from django.shortcuts import render
from plants.models import Plant, Location
def dashboard(request):
today = date.today()
all_plants = list(Plant.objects.select_related('species').all())
blooming_now = [p for p in all_plants if today.month in (p.bloom_months or [])]
latest_plants = Plant.objects.select_related('species', 'location').prefetch_related('photos').order_by('-pk')[:6]
location_count = Location.objects.count()
return render(request, 'plants/dashboard.html', {
'total_plants': len(all_plants),
'blooming_now': blooming_now,
'blooming_now_count': len(blooming_now),
'today': today,
'latest_plants': latest_plants,
'location_count': location_count,
'active_tab': 'home',
})
```
- [ ] **Step 8.2: Rewrite dashboard.html**
```html
{# plants/templates/plants/dashboard.html #}
{% extends "plants/base.html" %}
{% load plant_extras %}
{% block title %}BloomBase{% endblock %}
{% block content %}
{% endblock %}
```
- [ ] **Step 8.3: Verify home screen**
Open http://localhost:8000/ — expect camera viewfinder + My Garden pill + recent plants.
- [ ] **Step 8.4: Commit**
```bash
git add plants/views/dashboard.py plants/templates/plants/dashboard.html
git commit -m "feat: restyle dashboard as camera-first Home screen"
```
---
## Task 9: plant_list.html — search + location filter chips
Also update the `plant_list` view to pass `active_tab`.
**Files:**
- Modify: `plants/views/plants.py` (just add `active_tab` to plant_list context)
- Modify: `plants/templates/plants/plant_list.html`
- [ ] **Step 9.1: Add active_tab to plant_list view**
In `plants/views/plants.py`, find the `plant_list` view and add `'active_tab': 'garden'` to the context dict it passes to `render`. (Check the current return statement and add the key alongside existing context keys.)
- [ ] **Step 9.2: Rewrite plant_list.html**
```html
{# plants/templates/plants/plant_list.html #}
{% extends "plants/base.html" %}
{% block title %}Plants — BloomBase{% endblock %}
{% block content %}
{% include "plants/partials/plant_list_results.html" %}
{% endblock %}
```
- [ ] **Step 9.3: Update plant_list_results partial**
Open `plants/templates/plants/partials/plant_list_results.html` and check its current content. Update it to use Tailwind card style:
```html
{# plants/templates/plants/partials/plant_list_results.html #}
{% for plant in plants %}
{% if plant.species and plant.species.api_image_url %}
{% elif plant.thumbnail_url %}
{% else %}
🌿
{% endif %}
{% if plant.location %}
{{ plant.location.name }}
{% endif %}
›
{% empty %}
No plants found.
{% endfor %}
```
- [ ] **Step 9.4: Verify plant list**
Open http://localhost:8000/plants/ — expect search + chips + card list.
- [ ] **Step 9.5: Commit**
```bash
git add plants/views/plants.py plants/templates/plants/plant_list.html plants/templates/plants/partials/plant_list_results.html
git commit -m "feat: restyle plant_list with Tailwind — search, filter chips, plant cards"
```
---
## Task 10: plant_detail.html — hero + care + CTA
Also add `active_tab` to the `plant_detail` view.
**Files:**
- Modify: `plants/views/plants.py` (add `active_tab` to plant_detail context)
- Modify: `plants/templates/plants/plant_detail.html`
- [ ] **Step 10.1: Add active_tab to plant_detail view context**
In `plants/views/plants.py`, find the `plant_detail` view and add `'active_tab': 'garden'` to the context.
- [ ] **Step 10.2: Rewrite plant_detail.html**
```html
{# plants/templates/plants/plant_detail.html #}
{% extends "plants/base.html" %}
{% load plant_extras %}
{% block title %}{{ plant.name }} — BloomBase{% endblock %}
{% block content %}
{% if plant.species and plant.species.api_image_url %}

{% elif plant.thumbnail_url %}

{% else %}
🌿
{% endif %}
← Back
Edit
{{ plant.name }}
{% if plant.species %}
{{ plant.species.scientific_name }}
{% endif %}
{% if plant.location %}
📍 {{ plant.location.name }}
{% endif %}
{% if plant.watering or plant.sunlight or plant.species %}
{% if plant.watering %}
💧
{{ plant.get_watering_display }}
{% elif plant.species and plant.species.watering %}
💧
{{ plant.species.watering }}
{% endif %}
{% if plant.sunlight %}
☀️
{{ plant.get_sunlight_display }}
{% elif plant.species and plant.species.sunlight %}
☀️
{{ plant.species.sunlight }}
{% endif %}
{% if plant.species and plant.species.frost_hardiness_c is not None %}
🌡️
{{ plant.species.frost_hardiness_c }}°C min
{% endif %}
{% if plant.species and plant.species.max_height_cm %}
📏
{{ plant.species.max_height_cm }} cm
{% endif %}
{% endif %}
{% if plant.bloom_months %}
🌸 Bloom months
{% month_strip plant.bloom_months %}
{% endif %}
{% if plant.notes %}
{{ plant.notes|linebreaksbr }}
{% endif %}
{% if plant.pruning_months %}
{% include "plants/partials/pruning_strip.html" %}
{% endif %}
{% include "plants/partials/photo_gallery.html" %}
{% endblock %}
```
- [ ] **Step 10.3: Verify plant detail**
Open http://localhost:8000/plants/1/ — expect hero + care row + CTA buttons.
- [ ] **Step 10.4: Commit**
```bash
git add plants/views/plants.py plants/templates/plants/plant_detail.html
git commit -m "feat: restyle plant_detail — hero, care row, gradient CTA buttons"
```
---
## Task 11: identify_upload.html — viewfinder camera UI
Also add `active_tab` to `identify_upload` view.
**Files:**
- Modify: `plants/views/identify.py` (add `active_tab`)
- Modify: `plants/templates/plants/identify_upload.html`
- [ ] **Step 11.1: Add active_tab to identify_upload view**
In `plants/views/identify.py`, find the `identify_upload` view. Add `'active_tab': 'home'` to the context dict passed to render (for the GET response).
- [ ] **Step 11.2: Rewrite identify_upload.html**
```html
{# plants/templates/plants/identify_upload.html #}
{% extends "plants/base.html" %}
{% block title %}Identify plant — BloomBase{% endblock %}
{% block content %}
Add one or more photos, tag what each shows, then tap Identify.
{% if error %}
{{ error }}
{% endif %}
{% endblock %}
```
- [ ] **Step 11.3: Verify identify screen**
Open http://localhost:8000/identify/ — expect viewfinder box + shutter button + organ tags.
- [ ] **Step 11.4: Commit**
```bash
git add plants/views/identify.py plants/templates/plants/identify_upload.html
git commit -m "feat: restyle identify_upload as camera-first viewfinder screen"
```
---
## Task 12: identify_confirm.html — match list restyle
**Files:**
- Modify: `plants/templates/plants/identify_confirm.html`
- [ ] **Step 12.1: Rewrite identify_confirm.html**
```html
{# plants/templates/plants/identify_confirm.html #}
{% extends "plants/base.html" %}
{% block title %}Confirm species — BloomBase{% endblock %}
{% block content %}
Tap the correct species, then confirm.
{% endblock %}
```
- [ ] **Step 12.2: Verify identify confirm**
Run a real identification or view the confirm page directly — expect match cards with confidence badges and gradient confirm button.
- [ ] **Step 12.3: Commit**
```bash
git add plants/templates/plants/identify_confirm.html
git commit -m "feat: restyle identify_confirm — match cards with confidence badges"
```
---
## Self-Review
**Spec coverage check:**
| Spec item | Task |
|---|---|
| Replace Bootstrap 5 with Tailwind CSS | Task 2 (base.html) |
| 5-tab bottom nav (Home, Garden, +, Care, Profile) | Task 2 |
| `Location.cover_photo` + migration | Task 3 |
| `LocationForm` with cover_photo | Task 4 |
| `location_detail` view + URL | Task 4 |
| locations.html My Garden screen | Task 5 |
| location_detail.html Area Detail | Task 6 |
| location_edit.html with cover photo | Task 7 |
| dashboard.html camera-first | Task 8 |
| plant_list.html | Task 9 |
| plant_detail.html | Task 10 |
| identify_upload.html | Task 11 |
| identify_confirm.html | Task 12 |
| Canvas blob decorations + SVG flowers | Task 1 + Task 2 |
| Great Vibes + Nunito + Pacifico fonts | Task 1 |
| Gradient CTA buttons | Task 1 (.btn-bloom) |
| `active_tab` nav active state | Tasks 4, 8, 9, 10, 11 |
| Care + Profile stubs | Task 2 (href="#") |
**Gaps identified and resolved:**
- `plant_list_results.html` partial was missing — added in Task 9.3
- `plant_list` and `plant_detail` views need `active_tab` in context — noted in Tasks 9 and 10
- `identify_upload` view needs `active_tab` — noted in Task 11
**Type/name consistency:**
- `active_tab` values: `'home'` (dashboard, identify), `'garden'` (locations, plant_list, plant_detail, location_detail, location_edit)
- CSS class names used consistently: `.btn-bloom`, `.btn-bloom-outline`, `.btn-pill`, `.bb-fab`, `.vf-corner .vf-tl/tr/bl/br`
- `at-placeholder-N` classes 1–6 defined in bloombase.css and used in locations.html + location_detail.html
**No placeholders:** All template code is complete. All CSS is complete.