"""Clay-pin map markers — the designed 14-piece marker family. SVGs live in ``ui/icons/`` (source of truth: the design handoff's ``icons/markers-spec.md``). Each marker is inlined into a Leaflet ``DivIcon`` (no extra HTTP requests inside the map iframe), sized per spec with the pin tip anchored on the coordinate, plus the spec's cast shadow, springy hover, and staggered pop-in — all gated behind ``prefers-reduced-motion``. """ from __future__ import annotations import functools from pathlib import Path import folium _ICON_DIR = Path(__file__).resolve().parent / "icons" # Our 17 OSM categories -> the 14 designed marker kinds (color-by-meaning: # cobalt water/wayfinding · grass green space · coral culture · sun cozy stops). CATEGORY_TO_KIND = { "park_garden": "park", "water_feature": "fountain", "viewpoint": "viewpoint", "monument_historic": "museum", "museum_gallery": "museum", "artwork": "star", # public art = a highlight find "place_of_worship": "museum", # columns glyph reads classical/temple "library": "library", "bookshop": "bookshop", "theatre_cinema": "museum", "cafe": "cafe", "bakery_food_shop": "bakery", "restaurant": "cafe", "bar_pub": "cafe", "market": "market", "specialty_shop": "market", "attraction": "star", } _W = 40 # marker width px (spec: 28 / 40 / 56) _H = round(_W * 84 / 64) # 52 — height keeps the 64x84 viewBox ratio @functools.lru_cache(maxsize=32) def _svg(kind: str) -> str: path = _ICON_DIR / f"marker-{kind}.svg" try: return path.read_text() except OSError: return "" def marker_icon(kind: str, index: int = 0, width: int = _W) -> folium.DivIcon | None: """A DivIcon for one pin; ``index`` staggers the pop-in (~150 ms per spec).""" svg = _svg(kind) if not svg: return None h = round(width * 84 / 64) html = (f'
{svg}
') return folium.DivIcon(html=html, icon_size=(width, h), icon_anchor=(width // 2, h), class_name="dr-pin-wrap") def poi_icon(category: str, index: int = 0) -> folium.DivIcon | None: return marker_icon(CATEGORY_TO_KIND.get(category, "star"), index) def endpoint_icon(which: str) -> folium.DivIcon | None: """'start' (cobalt arrow) or 'dest' (coral flag), slightly larger.""" return marker_icon(which, index=-2, width=46) # Injected once per map (ui/map.py): shadow + hover spring + pop-in, per spec. MARKER_CSS = """ """