# BW Map Magnet — Specification

**Version:** 0.3.0 | **Last Updated:** 2026-05-13

## Purpose

Embed a categorised list of locations alongside an interactive map. Hovering a list item smoothly zooms the map to that location; idling on no item zooms back out to fit the full set. Designed for "things to do near our resort" style pages — activities, restaurants, attractions.

Inspired by the activities/map layout at biggameclubbimini.com/activities-maps/, but with the explicit improvement of zooming **back out** to show the whole area when the user is not hovering a specific item.

## Requirements

### Functional

- **R1 — Backend data entry.** Site authors enter map items through a standard WordPress custom post type (`bw_map_item`) with title, rich description, featured image, excerpt, and lat/lng coordinates. Items are grouped via a hierarchical taxonomy (`bw_map_category`).
- **R2 — Coordinate picker.** Authors set coordinates by clicking on a Leaflet map in the editor, dragging the marker, typing numeric values, or searching a place name (Nominatim geocoding).
- **R3 — Shortcode rendering.** `[bw_map_magnet]` renders a two-column layout: scrollable categorised list on one side, sticky map on the other. Attributes: `category`, `height`, `zoom`, `focus_zoom`, `limit`.
- **R4 — Hover-to-fly.** Hovering an item flies the map to that marker at `focus_zoom`.
- **R5 — Idle fit-bounds.** When no item is hovered (and none pinned), the map smoothly fits bounds to all visible items.
- **R6 — Click-to-pin.** Clicking an item opens its popup and pins the focus; clicking it again unpins and resets.
- **R7 — Responsive.** On screens ≤ 768px the layout stacks: map on top (relative, 360px tall), list below.
- **R8 — Keyboard accessible.** List items receive focus, can be activated via Enter/Space, and focus triggers the same fly-to behaviour as hover.
- **R9 — Demo data.** A one-click seeder under Map Magnet → Help & Demo Data creates 12 Vancouver Island sample items across two categories.
- **R10 — Category icons.** Each map category has a pickable icon from a bundled library (~25 resort-themed glyphs). The icon shows on map markers, popups, filter chips, and the term list table.
- **R11 — Frontend category filter.** A chip bar above the list toggles which category is visible. "All" shows everything; clicking a category chip shows only that category. The map re-fits bounds to whichever items are visible.
- **R12 — Image URL field.** Each item can have an external image URL that overrides the featured image (useful for stock photos without uploading).
- **R13 — Gutenberg block.** A dynamic `bw-map-magnet/map` block delivers the same render as the shortcode, with inspector controls for category filter, height, focus zoom, and the filter-chips toggle.
- **R14 — Per-category color.** Each category can have a custom color (WP color picker). The color applies to map markers, active filter chips, category title underline, and accent borders.
- **R15 — CSV import / export.** Site admins can download every map item as a CSV and upload a CSV to bulk-create or upsert items. Missing categories auto-created. Validation errors reported per-row.

### Non-functional

- Must work on WordPress 6.0+ with PHP 7.4+
- Must follow WordPress security best practices (nonces, escaped output, sanitised input, capability checks)
- Must be translatable (i18n) — all user-facing strings wrapped in `__()` / `esc_html__()`
- Must not interfere with other plugins (all globals/hooks/options prefixed `bw_map_magnet_`)
- Frontend assets are only enqueued on pages where the shortcode actually runs

## Acceptance Criteria

| Requirement | Acceptance | Status |
|---|---|---|
| R1 | `Map Magnet → Add New` produces a publishable post with all fields. | ☑ |
| R2 | Click/drag/search all update the lat/lng inputs in sync. | ☑ |
| R3 | `[bw_map_magnet]` renders without PHP errors on a published page. | ☑ |
| R4 | Hovering an item triggers a `flyTo` to its coordinates. | ☑ |
| R5 | Leaving the list (or unpinning) returns to fit-bounds. | ☑ |
| R6 | Clicking an item toggles pin state and opens/closes popup. | ☑ |
| R7 | Layout stacks vertically below 768px viewport. | ☑ |
| R8 | Tab focuses items; Enter activates. | ☑ |
| R9 | Seeder creates 12 items + 2 categories on first run; skips duplicates on re-run. | ☑ |
| R10 | Picker on taxonomy edit screen saves icon key; renders on markers/popups/chips. | ☑ |
| R11 | Clicking a chip filters list + map, re-fits bounds. | ☑ |
| R12 | URL set on a post becomes the thumbnail in list and popup. | ☑ |
| R13 | Block is selectable in the inserter and renders identically to the shortcode. | ☑ |
| R14 | Color saved on term; markers/chips/titles use it on the frontend. | ☑ |
| R15 | Export downloads a valid CSV; importing a modified copy round-trips cleanly. | ☑ |

## Deferred

- Vendoring Leaflet (currently from unpkg CDN).
- Alternate tile providers (Mapbox/Stadia) for high-traffic deployments.
- Per-item icon override (currently icon is inherited from category).
- Multi-select category filter (currently single-select).
- Sideloading external image URLs into the media library on seed.
- Import: GeoJSON support in addition to CSV.
- Import: setting category icon/color via additional CSV columns.
