Phase 54 — Map Bubbles / Markers + Popup Redesign

A visual repaint of the map pins, urgency styling, clusters, and tap popups — judged by eye. Each panel shows today next to the proposed treatment. Emoji glyphs stay (founder is fine with them); category colors stay (good map legibility). The scope is to harmonize the clashes, make sizes consistent, unify the popup card, and add one urgency system — not to brand-ify the markers. Everything here is adjustable on review; nothing is locked.

In-scope mockup (B8) — no approval gate Feeds build plans 54-07 / 54-08 Shares the chip-mockup visual language
B1

Marker icons — emoji vs lucide

keep emoji · swap lucide only where it's a clean upgrade
Today

Emoji glyphs across the board

Markers use emoji (🚨 📅 🏪 🔑 etc). They render fine and read clearly at a glance — the founder is happy keeping them. Developments already use an inlined lucide HardHat as precedent.

🚨
Safety
📅
Events
🏪
Market
🔑
Rental
Food
Dev (lucide today)

Verdict: keep emoji as the default. The DESIGN_STANDARDS "lucide-only, no emoji" rule is relaxed for map markers per founder direction. The only question is whether any individual glyph reads better as a clean lucide line-icon.

★ Proposed — per-marker, pick by eye

Emoji default + a few lucide candidates

NOT a sweep. We swap a glyph to a clean inlined lucide icon only where it's a clear upgrade. Below: emoji on the left of each pair, the lucide candidate on the right — you choose which (if any) to swap.

💡
Tips → bulb
📦
Package → box
🆘
Help → life-ring

Lucide icons are inlined as raw SVG strings (the HardHat precedent), stroke="currentColor" on a white-stroke wrapper — no React inside the Leaflet DivIcon. Default = keep emoji; swap only the glyphs you prefer in lucide.

Founder picks by eye. Whatever you don't like, we change. None of these swaps are mandatory.
B2

Marker colors — harmonize the clashes

stay category-coded · NOT brand-ified
Today — 3 documented clashes

Same color, different meaning

Colors are category-coded (good for legibility), but three pairs use an identical hex for unrelated categories — so two different things look the same on the map.

Package#F97316
Contractor#F97316
Safety#EF4444
Fitness biz#EF4444
For-sale#DC2626
Expired permit#DC2626

⚠ 3 collisions: orange Package = orange Contractor · red Safety = red Fitness · crimson For-sale = crimson Expired-permit.

★ Proposed — reconciled palette

One hue per meaning

Keep the dominant meaning on its color; nudge the colliding sibling to an adjacent, still-sensible hue. Colors remain category-coded, not brand colors — map legibility wins here.

Package#F97316
Contractor#B45309
Safety#EF4444
Fitness biz#DB2777
For-sale#DC2626
Expired permit#78716C

Reasoning: Safety/emergency reds stay loud and reserved for urgency; commerce (contractor) shifts to a warm amber-brown; an expired permit reads as a muted, "dead" stone-gray (it's history, not an alert). Final hues are adjustable on review.

Touch-light. Three hex edits in the MARKER_STYLES table + the matching legend swatches. No structural change.
B3

Urgency tiers — one system, content + hotspot pins

calm · ring · pulse
Today — inconsistent & partly broken

Two different urgency models

Hotspot emergency pins pulse correctly (a real @keyframes hotspot-pulse-ring). But urgent content pins reference animation: pulse — and there is no @keyframes pulse defined anywhere, so it silently does nothing. Urgency loudness is uneven across the map.

🚨
Hotspot emergency — pulses ✓
🚨
Urgent content — pulse is a no-op 🐛

Latent bug to fix here: add a shared keyframe (or reuse hotspot-pulse-ring) so content urgency actually animates, and apply the SAME tier logic to both builders.

★ Proposed — 3 tiers, one rule

Calm → ring → pulse + size bump

One tier system applied to BOTH getMapContentIcon (urgent posts) and getHotspotIcon. The emergency pulse below is live CSS so you can judge the loudness.

CalmEveryday content — plain dot, no decoration.
📅
MediumA subtle static ring — noticeable, not loud.
🚨
EmergencyAnimated pulse + size bump + bold color. Reserved for safety / live emergencies.

One consistent system means a safety post and an emergency hotspot read with the same loudness — no more "the hotspot screams but the content pin whispers." Pulse speed/size adjustable on review.

Fixes the latent no-op pulse by defining the shared keyframe once and tiering both icon builders off the same urgency map.
B5

Pin sizes & shapes — make them consistent

round bubbles · keep thumb + price where useful · NOT teardrop
Today — a grab-bag of sizes

30 / 32 / 44px circles + 18px square + price tags

Five different pin treatments with no shared scale: standard 30px, development & property 32px, photo-post 44px rounded-square, permit 18px square with a floating label, plus price-tag labels.

🚨
30px
🏗️
32px
📷
44px sq
…4821
18px sq
🏡
$1.2M
+ price tag

No visual rhythm — pins jump in size for no user-facing reason, and the 18px permit square reads as a different language from the round bubbles.

★ Proposed — one round-bubble scale

30px base · 36px emphasis · keep thumb + price

One round-bubble family on a tidy scale. Photo-thumbnail and price-label survive only where they genuinely add value. Permit square becomes a small round bubble so it speaks the same language. Teardrop map-pins were considered and rejected — they fight our rounded card/chip look.

🚨
30 base
🏗️
36 emphasis
📷
thumb kept
📋
permit → round
🏡
$1.2M
price kept

Keep round/mixed, make consistent. Two sizes do almost all the work — 30px baseline, ~36px for items that deserve emphasis (developments, emergencies). Thumbnail + price are the only "special" shapes, and they earn it.

Mechanical. Normalize the size constants in markerStyles.ts + reconcile the two cluster size-tiers into one scale.
B6

Popups — one unified, on-brand card

replaces 3 inline-styled popups · brand-gradient CTA
Today — 3+ inconsistent popups

Each popup styled separately

Content/development, permit, and hotspot each have their own inline-styled markup. CTAs use different colors — content uses a hardcoded purple (#7c3aed), hotspot uses the per-category color. No shared card language.

Development

The Greenpoint Mews

42-unit residential · expected Q3 2027

Permit

BK-2026-4821

Facade work · filed 3d ago

no CTA
Emergency

Water main break — N 7th

Avoid the block, crews on site…

Three different headers, badge styles, and CTA colors. The hardcoded #7c3aed doesn't match our actual brand gradient.

★ Proposed — ONE card, two depths

Brand-gradient header + category accent

All popups fold into a single card: brand-gradient header/CTA (the real gradient, not the old hardcoded purple), category color as a small accent dot/label only, badge sizing per DESIGN_STANDARDS, image thumb, line-clamped description, footer meta. A light variant (no CTA) and a rich variant (with CTA) cover the B4 hybrid tap behavior.

Development

The Greenpoint Mews

42-unit residential with ground-floor retail. Expected completion Q3 2027 along the waterfront.

ResidentialExpected Q3 2027
Permit

BK-2026-4821

Facade restoration — filed at 88 Berry St. Tap-outside to dismiss.

Filed 3 days ago · no drawer (light tap)

Rich (listings, developments, businesses) = full card with a brand-gradient CTA. Light (stories, permits) = preview only, no CTA. The interactive CTA keeps the Leaflet ref-based click handler; the gradient header is a static div so a className gradient works fine.

Merge, don't multiply. The 3 popup branches collapse into one card driven by a discriminant. CTA uses --grad-brand-r / BRAND_GRADIENT — never the old hardcoded purple.
B7

Clusters — cleaner, consistent treatment

content + hotspot share one look
Today — two unrelated bubbles

Dark-navy vs red-tint, different sizes

Content clusters are a flat semi-transparent dark navy rgba(26,26,46,.7); hotspot clusters are a red tint rgba(220,38,38,.75). They also use different size tiers (content <10/<30, hotspot <5/<15) so the same count looks different.

12
Content cluster
7
Hotspot cluster

Flat, slightly muddy, and the two cluster types don't feel like they belong to the same map.

★ Proposed — one cluster family

Brand-gradient content · loud-red hotspot

Content clusters pick up the brand gradient (they're the "everything here" bubble — a frame surface, so gradient is on-brand). Hotspot clusters keep a strong red so live activity still reads as urgent, with a soft glow ring. Both share one size scale and a crisp white border.

12
Content cluster
7
Hotspot cluster

One reconciled size scale for both cluster builders, consistent white border + shadow. The hotspot glow keeps "live activity here" legible without the muddy flat fill.

Restyle only. Update the two L.divIcon cluster factories (MapMarkers + HotspotMarkers) and merge their divergent size tiers.
How to use this board: Everything visual is a proposal — react to it by eye and we adjust anything you don't like (colors, sizes, which icons go lucide, pulse loudness, cluster look). This mockup is the visual target for the build plans that follow (54-07 repaints markerStyles.ts + clusters; 54-08 builds the unified popup card). The hybrid tap behavior (B4) — light items show a preview, rich items tap straight to the drawer — is behavioral and best judged on the live build during UAT, not from this static page. Brand tokens (grad-brand, grad-brand-r, warm-card) match the chip mockup verbatim so both review pages share one language.