Global WatchGlobal Watch Docs
Features

Alert Marker UX

Alert Marker UX

The Alert Marker UX system provides time-aware visual feedback for alert markers on the map. It combines three visual layers to communicate alert urgency, density, and recency at a glance.

Three Visual Layers

LayerPurposeTrigger
Temporal DecayOpacity, scale, border degrade as alerts ageAlways active for live alerts
Pulse AnimationSubtle scale pulse draws attentionAlerts less than 1 hour old
HeatmapDensity visualization at macro levelZoom level below 10

Temporal Decay

Alert markers visually age over time. Newer alerts are large and opaque; older alerts fade and shrink.

Decay Tiers

Alert AgeOpacityScaleBorder StylePulse
Less than 1 hour1.01.0SolidYes
1–6 hours0.90.95SolidNo
6–24 hours0.750.9SolidNo
1–3 days0.60.85DashedNo
3–7 days0.450.8DashedNo
More than 7 days0.30.75DottedNo

Special States

  • Selected marker: When a user clicks an alert marker, opacity overrides to 1.0 (fully visible) regardless of age. Deselecting restores the age-based opacity.
  • Hover override: When the user hovers over a marker with reduced opacity, it temporarily becomes fully visible (1.0). On mouse leave, it returns to its decay-based opacity. The transition uses a 200ms CSS ease-out for a smooth effect.
  • Historical mode: When viewing a past time range, all markers use a fixed decay of opacity: 0.7, scale: 0.9, solid border, no pulse.

Opacity Resolution

const resolvedOpacity = isSelected || isHovered ? 1 : (decayOpacity ?? (isHistorical ? 0.7 : 1));

Priority: Selected > Hovered > Decay > Historical default > Full opacity.

Pulse Animation

Alerts detected less than 1 hour ago display a subtle CSS animation that scales the marker from 1.0 → 1.15 → 1.0 on a 2-second loop. This draws attention to the most recent alerts without being distracting.

The animation is injected as a global @keyframes rule and applied conditionally.

Heatmap Layer

At low zoom levels (below zoom 10), a Mapbox GL heatmap layer shows alert density before individual markers are visible.

Weight Formula

Each alert's heatmap weight combines severity and recency:

weight = severityWeight × recency
  • Severity weight: low = 1, medium = 2, high = 3, critical = 4
  • Recency: Exponential decay with 24-hour half-life (new = 1.0, 24h = 0.5, 48h = 0.25)

Color Ramp

The heatmap uses an amber-to-red gradient:

DensityColor
0.0Transparent
0.2Light Yellow
0.4Amber
0.6Orange
0.8Deep Orange
1.0Red

Zoom Transition

The heatmap and cluster layers crossfade between zoom 7 and 10:

  • Zoom ≤ 7: Heatmap at full opacity, no clusters
  • Zoom 7–10: Heatmap fading out, clusters fading in
  • Zoom ≥ 10: Clusters and individual markers visible, heatmap invisible

Data Flow

Alerts from database

Time range filter → visible alerts

GeoJSON enrichment (adds heatWeight, detectedAt, severityWeight)

┌─────────────────────────────────────┐
│  Heatmap Source (zoom < 10)         │
│  Uses heatWeight for density colors │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│  Clustered Source (zoom ≥ 10)       │
│  Clusters → count labels            │
│  Unclustered → AlertMarker          │
│    → computeDecayFactor(detectedAt) │
│    → RichMapMarker with decay props │
└─────────────────────────────────────┘

Attention Badge

Alert markers display an AttentionBadge indicating their read/action status. The unread badge uses red (#EF4444) to match the alert notification badge in the map controls bar. On alert markers, badges render borderless for a cleaner look.

Implementation Files

FileRole
apps/map/src/components/map/alerts-layer.tsxMain orchestrator — decay helpers, heatmap, markers
apps/map/src/components/map/rich-map-marker.tsxGeneric marker with decay, pulse, hover, and selection
apps/map/src/config/map/constants.tsSource IDs, Layer IDs, zoom thresholds
packages/fw/attention-badges/src/components/attention-badge.tsxBadge component with borderless prop
packages/fw/attention-badges/src/constants.tsBadge color definitions

On this page