diff --git a/frontend/src/style.css b/frontend/src/style.css index 45fcdfe..d64d97d 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -85,7 +85,11 @@ h1, h2, .font-display { .regress-field-green { filter: drop-shadow(0 0 6px #39ff14); } /* Reskin leaflet.markercluster's default light-green bubble look to match - the HUD theme — overrides MarkerCluster.Default.css. */ + the HUD theme — overrides MarkerCluster.Default.css. Color reflects what's + actually inside: neutral cyan if nothing in the cluster is claimed, team + color if it's only one team, a split orange/green "contested" look if + both teams have a claim inside — otherwise a cluster full of claimed + nodes looks identical to an empty one, which was the whole complaint. */ .regress-cluster div { width: 100%; height: 100%; @@ -93,11 +97,32 @@ h1, h2, .font-display { display: flex; align-items: center; justify-content: center; - background: radial-gradient(circle at 35% 35%, rgba(0, 255, 242, 0.35), rgba(0, 255, 242, 0.12) 70%); - border: 2px solid #00fff2; color: #d8fbff; font-family: 'Share Tech Mono', monospace; font-weight: bold; font-size: 12px; +} + +.regress-cluster--neutral div { + background: radial-gradient(circle at 35% 35%, rgba(0, 255, 242, 0.35), rgba(0, 255, 242, 0.12) 70%); + border: 2px solid #00fff2; box-shadow: 0 0 10px rgba(0, 255, 242, 0.5); } + +.regress-cluster--orange div { + background: radial-gradient(circle at 35% 35%, rgba(255, 95, 31, 0.45), rgba(255, 95, 31, 0.15) 70%); + border: 2px solid #ff5f1f; + box-shadow: 0 0 12px rgba(255, 95, 31, 0.65); +} + +.regress-cluster--green div { + background: radial-gradient(circle at 35% 35%, rgba(57, 255, 20, 0.45), rgba(57, 255, 20, 0.15) 70%); + border: 2px solid #39ff14; + box-shadow: 0 0 12px rgba(57, 255, 20, 0.65); +} + +.regress-cluster--contested div { + background: linear-gradient(135deg, rgba(255, 95, 31, 0.45) 50%, rgba(57, 255, 20, 0.45) 50%); + border: 2px solid #ffffff; + box-shadow: 0 0 12px rgba(255, 0, 229, 0.55); +} diff --git a/frontend/src/views/MapView.vue b/frontend/src/views/MapView.vue index 7f6c946..550cb4d 100644 --- a/frontend/src/views/MapView.vue +++ b/frontend/src/views/MapView.vue @@ -5,6 +5,7 @@ import 'leaflet.markercluster'; import { useAuthStore } from '../stores/auth'; import { useGameStore } from '../stores/game'; import type { Place } from '../stores/game'; +import type { Team } from '../stores/auth'; const auth = useAuthStore(); const game = useGameStore(); @@ -19,6 +20,11 @@ let map: L.Map | null = null; let markersLayer: L.MarkerClusterGroup | null = null; let linksLayer: L.LayerGroup | null = null; let fieldsLayer: L.LayerGroup | null = null; +// Cluster icons need to know if anything inside is claimed (and by whom) — +// leaflet.markercluster only gives back the child L.Marker instances, not +// our Place data, so track the mapping on the side (WeakMap so old markers +// from a previous redraw() just fall out rather than needing manual cleanup). +const markerTeam = new WeakMap(); const selectedPlaceId = ref(null); const linkSourceId = ref(null); @@ -68,6 +74,7 @@ function redraw() { const marker = L.marker([place.lat, place.lon], { icon: portalIcon(place, isSource) }); marker.on('click', () => onMarkerClick(place)); marker.bindTooltip(place.name || `Place ${place.id}`, { direction: 'top' }); + markerTeam.set(marker, place.claim_team); return marker; }); markersLayer.addLayers(markers); @@ -184,8 +191,19 @@ onMounted(async () => { iconCreateFunction: (cluster) => { const count = cluster.getChildCount(); const size = count < 100 ? 34 : count < 1000 ? 42 : 50; + + let hasOrange = false; + let hasGreen = false; + for (const marker of cluster.getAllChildMarkers()) { + const team = markerTeam.get(marker as L.Marker); + if (team === 'orange') hasOrange = true; + else if (team === 'green') hasGreen = true; + if (hasOrange && hasGreen) break; + } + const modifier = hasOrange && hasGreen ? 'contested' : hasOrange ? 'orange' : hasGreen ? 'green' : 'neutral'; + return L.divIcon({ - className: 'regress-cluster', + className: `regress-cluster regress-cluster--${modifier}`, html: `
${count}
`, iconSize: [size, size], }); @@ -272,7 +290,7 @@ watch(linkSourceId, redraw); class="font-bold uppercase" :class="selectedPlace.claim_team === 'orange' ? 'text-orange-neon text-glow-orange' : 'text-green-neon text-glow-green'" > - held since block {{ selectedPlace.claimed_at_block ?? '?' }} in {{ selectedPlace.claim_team }} + held since block {{ selectedPlace.claimed_at_block ?? '?' }}