From 3edb69848d13b9d2f62b7d2148f7e1bb6fe9cd20 Mon Sep 17 00:00:00 2001
From: Joey Eamigh <55670930+JoeyEamigh@users.noreply.github.com>
Date: Wed, 11 Feb 2026 13:36:21 -0500
Subject: [PATCH] fix: region overlay visibility, ambient glow breathing,
pulsing threshold
- Fix fillColor/fillOpacity conflict: return separate RGB string and
opacity from priceToColor() instead of rgba with embedded alpha
- Implement ambient region glow via setInterval + overrideStyle with
sine-wave opacity oscillation (faster/brighter for higher prices)
- Lower pulsing marker threshold from 10% to 3% for demand-varied prices
---
src/components/map/energy-map.tsx | 2 +-
src/components/map/region-overlay.tsx | 90 ++++++++++++++++++++++++---
2 files changed, 81 insertions(+), 11 deletions(-)
diff --git a/src/components/map/energy-map.tsx b/src/components/map/energy-map.tsx
index 48cb5e8..b29204a 100644
--- a/src/components/map/energy-map.tsx
+++ b/src/components/map/energy-map.tsx
@@ -65,7 +65,7 @@ export function EnergyMap({ datacenters, regions }: EnergyMapProps) {
dcRegion.avgPrice !== null &&
dcRegion.maxPrice !== null &&
dcRegion.avgPrice > 0 &&
- dcRegion.maxPrice > dcRegion.avgPrice * 1.1;
+ dcRegion.maxPrice > dcRegion.avgPrice * 1.03;
return (
);
diff --git a/src/components/map/region-overlay.tsx b/src/components/map/region-overlay.tsx
index 49af3c4..77fb719 100644
--- a/src/components/map/region-overlay.tsx
+++ b/src/components/map/region-overlay.tsx
@@ -1,7 +1,7 @@
'use client';
import { useMap } from '@vis.gl/react-google-maps';
-import { useEffect, useRef } from 'react';
+import { useCallback, useEffect, useRef } from 'react';
export interface RegionHeatmapData {
code: string;
@@ -19,8 +19,15 @@ interface RegionOverlayProps {
onRegionClick: (regionCode: string) => void;
}
-function priceToColor(price: number | null): string {
- if (price === null || price <= 0) return 'rgba(100, 100, 100, 0.25)';
+interface PriceColorResult {
+ fillColor: string;
+ fillOpacity: number;
+}
+
+function priceToColor(price: number | null): PriceColorResult {
+ if (price === null || price <= 0) {
+ return { fillColor: 'rgb(100, 100, 100)', fillOpacity: 0.25 };
+ }
// Scale: 0-20 cool blue, 20-50 yellow/orange, 50+ red/magenta
const clamped = Math.min(price, 100);
@@ -29,18 +36,32 @@ function priceToColor(price: number | null): string {
if (ratio < 0.2) {
// Blue to cyan
const t = ratio / 0.2;
- return `rgba(${Math.round(30 + t * 30)}, ${Math.round(80 + t * 140)}, ${Math.round(220 - t * 20)}, 0.30)`;
+ return {
+ fillColor: `rgb(${Math.round(30 + t * 30)}, ${Math.round(80 + t * 140)}, ${Math.round(220 - t * 20)})`,
+ fillOpacity: 0.25 + ratio * 0.25,
+ };
} else if (ratio < 0.5) {
// Cyan to yellow/orange
const t = (ratio - 0.2) / 0.3;
- return `rgba(${Math.round(60 + t * 195)}, ${Math.round(220 - t * 60)}, ${Math.round(200 - t * 170)}, 0.35)`;
+ return {
+ fillColor: `rgb(${Math.round(60 + t * 195)}, ${Math.round(220 - t * 60)}, ${Math.round(200 - t * 170)})`,
+ fillOpacity: 0.3 + (ratio - 0.2) * 0.33,
+ };
} else {
// Orange to red/magenta
const t = (ratio - 0.5) / 0.5;
- return `rgba(${Math.round(255 - t * 35)}, ${Math.round(160 - t * 120)}, ${Math.round(30 + t * 80)}, 0.40)`;
+ return {
+ fillColor: `rgb(${Math.round(255 - t * 35)}, ${Math.round(160 - t * 120)}, ${Math.round(30 + t * 80)})`,
+ fillOpacity: 0.35 + (ratio - 0.5) * 0.1,
+ };
}
}
+/** Return the base fill opacity for a given price (used by breathing animation). */
+function priceToBaseOpacity(price: number | null): number {
+ return priceToColor(price).fillOpacity;
+}
+
function priceToBorderColor(price: number | null): string {
if (price === null || price <= 0) return 'rgba(150, 150, 150, 0.4)';
@@ -56,6 +77,34 @@ export function RegionOverlay({ regions, onRegionClick }: RegionOverlayProps) {
const map = useMap();
const dataLayerRef = useRef(null);
const listenersRef = useRef([]);
+ const priceMapRef = useRef