diff --git a/client/package.json b/client/package.json
index 174cd331..37451587 100644
--- a/client/package.json
+++ b/client/package.json
@@ -27,9 +27,11 @@
     "@ts-rest/react-query": "3.51.0",
     "class-variance-authority": "0.7.0",
     "clsx": "2.1.1",
+    "d3": "7.9.0",
     "framer-motion": "11.11.9",
     "jotai": "2.10.1",
     "lucide-react": "0.447.0",
+    "mapbox-expression": "0.0.3",
     "mapbox-gl": "3.7.0",
     "next": "14.2.10",
     "next-auth": "4.24.8",
@@ -42,6 +44,8 @@
     "zod": "catalog:"
   },
   "devDependencies": {
+    "@types/d3": "7.4.3",
+    "@types/geojson": "7946.0.14",
     "@types/mapbox-gl": "3.4.0",
     "@types/node": "catalog:",
     "@types/react": "^18",
diff --git a/client/src/app/(projects)/page.tsx b/client/src/app/(projects)/page.tsx
index 1e013437..b9eb26db 100644
--- a/client/src/app/(projects)/page.tsx
+++ b/client/src/app/(projects)/page.tsx
@@ -43,7 +43,7 @@ export default function Projects() {
       </motion.aside>
       <div className="flex flex-1 flex-col">
         <ProjectsHeader />
-        <div className="grid flex-grow grid-rows-2">
+        <div className="grid flex-grow grid-rows-2 gap-3">
           <section className="flex-1">
             <ProjectsMap />
           </section>
diff --git a/client/src/app/(projects)/store.ts b/client/src/app/(projects)/store.ts
index d5a3bf17..e03c9d06 100644
--- a/client/src/app/(projects)/store.ts
+++ b/client/src/app/(projects)/store.ts
@@ -12,6 +12,12 @@ export const projectsUIState = atom<{
   tableExpanded: "default",
 });
 
+export const projectsMapState = atom<{
+  legendOpen: boolean;
+}>({
+  legendOpen: true,
+});
+
 export const projectsFiltersState = atom<{
   keyword: string | undefined;
   projectSize: (typeof PROJECT_PARAMETERS)[0]["options"][number]["value"];
diff --git a/client/src/containers/projects/map/controls/index.tsx b/client/src/containers/projects/map/controls/index.tsx
new file mode 100644
index 00000000..00c05d33
--- /dev/null
+++ b/client/src/containers/projects/map/controls/index.tsx
@@ -0,0 +1,20 @@
+import { PropsWithChildren } from "react";
+
+import { cn } from "@/lib/utils";
+
+type ControlsProps = PropsWithChildren<{
+  className?: HTMLDivElement["className"];
+}>;
+
+export default function Controls({ className, children }: ControlsProps) {
+  return (
+    <div
+      className={cn(
+        "absolute right-4 top-4 flex flex-col items-center justify-center space-y-2",
+        className,
+      )}
+    >
+      {children}
+    </div>
+  );
+}
diff --git a/client/src/containers/projects/map/controls/legend/index.tsx b/client/src/containers/projects/map/controls/legend/index.tsx
new file mode 100644
index 00000000..d613699e
--- /dev/null
+++ b/client/src/containers/projects/map/controls/legend/index.tsx
@@ -0,0 +1,33 @@
+import { useSetAtom } from "jotai";
+import { Layers } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+
+import { projectsMapState } from "@/app/(projects)/store";
+
+const BUTTON_CLASSES = {
+  default:
+    "flex h-8 w-8 items-center justify-center rounded-full border border-white bg-white text-black shadow-md transition-colors",
+  hover: "hover:border-gray-400 active:border-gray-400",
+};
+
+export default function LegendControl() {
+  const setProjectsMapState = useSetAtom(projectsMapState);
+  const handleMapLegend = () => {
+    setProjectsMapState((prev) => ({
+      ...prev,
+      legendOpen: !prev.legendOpen,
+    }));
+  };
+
+  return (
+    <button
+      className={cn(BUTTON_CLASSES.default, BUTTON_CLASSES.hover)}
+      aria-label="Toggle legend"
+      type="button"
+      onClick={handleMapLegend}
+    >
+      <Layers className="h-4 w-4" />
+    </button>
+  );
+}
diff --git a/client/src/containers/projects/map/controls/zoom/index.tsx b/client/src/containers/projects/map/controls/zoom/index.tsx
new file mode 100644
index 00000000..daa450ed
--- /dev/null
+++ b/client/src/containers/projects/map/controls/zoom/index.tsx
@@ -0,0 +1,82 @@
+import { useCallback, MouseEvent } from "react";
+
+import { useMap, MapRef } from "react-map-gl";
+
+import { Plus, Minus } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+
+const BUTTON_CLASSES = {
+  default:
+    "flex h-8 w-8 items-center justify-center rounded-full border border-white bg-white text-black shadow-md transition-colors",
+  hover: "hover:border-gray-400 active:border-gray-400",
+  disabled: "opacity-50 cursor-default",
+};
+
+export default function ZoomControl({
+  id = "default",
+  className,
+}: {
+  id?: string;
+  className?: HTMLDivElement["className"];
+}) {
+  const { [id]: mapRef } = useMap();
+
+  const zoom = mapRef?.getZoom() as NonNullable<ReturnType<MapRef["getZoom"]>>;
+  const minZoom = mapRef?.getMinZoom() as NonNullable<
+    ReturnType<MapRef["getMinZoom"]>
+  >;
+  const maxZoom = mapRef?.getMaxZoom() as NonNullable<
+    ReturnType<MapRef["getMaxZoom"]>
+  >;
+
+  const increaseZoom = useCallback(
+    (e: MouseEvent<HTMLButtonElement>) => {
+      e.stopPropagation();
+      if (!mapRef) return null;
+
+      mapRef.zoomIn();
+    },
+    [mapRef],
+  );
+
+  const decreaseZoom = useCallback(
+    (e: MouseEvent<HTMLButtonElement>) => {
+      e.stopPropagation();
+      if (!mapRef) return null;
+
+      mapRef.zoomOut();
+    },
+    [mapRef],
+  );
+
+  return (
+    <div className={cn("inline-flex flex-col space-y-2", className)}>
+      <button
+        className={cn(BUTTON_CLASSES.default, {
+          [BUTTON_CLASSES.hover]: zoom < maxZoom,
+          [BUTTON_CLASSES.disabled]: zoom >= maxZoom,
+        })}
+        aria-label="Zoom in"
+        type="button"
+        disabled={zoom >= maxZoom}
+        onClick={increaseZoom}
+      >
+        <Plus className="h-6 w-6" />
+      </button>
+
+      <button
+        className={cn(BUTTON_CLASSES.default, {
+          [BUTTON_CLASSES.hover]: zoom > minZoom,
+          [BUTTON_CLASSES.disabled]: zoom <= minZoom,
+        })}
+        aria-label="Zoom out"
+        type="button"
+        disabled={zoom <= minZoom}
+        onClick={decreaseZoom}
+      >
+        <Minus className="h-6 w-6" />
+      </button>
+    </div>
+  );
+}
diff --git a/client/src/containers/projects/map/index.tsx b/client/src/containers/projects/map/index.tsx
index e4b70af8..b6b91c9a 100644
--- a/client/src/containers/projects/map/index.tsx
+++ b/client/src/containers/projects/map/index.tsx
@@ -1,22 +1,45 @@
 "use client";
 
-import { ExpandIcon } from "lucide-react";
+import { ComponentProps } from "react";
+
+import { useAtomValue } from "jotai";
+
+import { projectsMapState } from "@/app/(projects)/store";
+
+import Controls from "@/containers/projects/map/controls";
+import LegendControl from "@/containers/projects/map/controls/legend";
+import ZoomControl from "@/containers/projects/map/controls/zoom";
+import ProjectsLayer from "@/containers/projects/map/layers/projects";
+import { MATRIX_COLORS } from "@/containers/projects/map/layers/projects/utils";
+import Legend from "@/containers/projects/map/legend";
+import MatrixLegend from "@/containers/projects/map/legend/types/matrix";
 
 import Map from "@/components/map";
-import { Button } from "@/components/ui/button";
 
 export default function ProjectsMap() {
-  const onToggleExpand = () => {};
+  const { legendOpen } = useAtomValue(projectsMapState);
+
+  const matrixItems: ComponentProps<typeof MatrixLegend>["intersections"] =
+    Object.keys(MATRIX_COLORS).map((key, index) => ({
+      color: key,
+      id: index,
+    }));
 
   return (
     <div className="h-full overflow-hidden rounded-2xl">
       <Map>
-        <Button
-          onClick={onToggleExpand}
-          className="absolute right-2 top-2 z-50"
-        >
-          <ExpandIcon />
-        </Button>
+        <Controls>
+          <ZoomControl />
+        </Controls>
+        <Controls className="bottom-8 top-auto">
+          <LegendControl />
+        </Controls>
+        {legendOpen && (
+          <Legend>
+            <MatrixLegend items={[]} intersections={matrixItems} />
+          </Legend>
+        )}
+        <ProjectsLayer />
       </Map>
     </div>
   );
diff --git a/client/src/containers/projects/map/layers/projects/index.tsx b/client/src/containers/projects/map/layers/projects/index.tsx
new file mode 100644
index 00000000..d5da406f
--- /dev/null
+++ b/client/src/containers/projects/map/layers/projects/index.tsx
@@ -0,0 +1,329 @@
+import { Source, Layer } from "react-map-gl";
+
+import * as d3 from "d3";
+import { FeatureCollection, Geometry } from "geojson";
+import { FillLayerSpecification } from "mapbox-gl";
+
+import { generateColorRamp } from "@/containers/projects/map/layers/projects/utils";
+
+export default function ProjectsLayer() {
+  const _data: FeatureCollection<
+    Geometry,
+    {
+      cost: number;
+      abatement: number;
+    }
+  > = {
+    type: "FeatureCollection",
+    features: [
+      {
+        type: "Feature",
+        properties: {
+          cost: 10,
+          abatement: 10,
+        },
+        geometry: {
+          coordinates: [
+            [
+              [-1.0188997350152817, 19.339371896125982],
+              [-1.0188997350152817, 15.2827299773014],
+              [4.790209065497407, 15.2827299773014],
+              [4.790209065497407, 19.339371896125982],
+              [-1.0188997350152817, 19.339371896125982],
+            ],
+          ],
+          type: "Polygon",
+        },
+      },
+      {
+        type: "Feature",
+        properties: {
+          cost: 2,
+          abatement: 2,
+        },
+        geometry: {
+          coordinates: [
+            [
+              [8.553884254294076, 22.347045198308763],
+              [8.553884254294076, 18.98034659264529],
+              [13.93901438502084, 18.98034659264529],
+              [13.93901438502084, 22.347045198308763],
+              [8.553884254294076, 22.347045198308763],
+            ],
+          ],
+          type: "Polygon",
+        },
+      },
+      {
+        type: "Feature",
+        properties: {
+          cost: 3,
+          abatement: 3,
+        },
+        geometry: {
+          type: "Polygon",
+          coordinates: [
+            [
+              [10.266381087897429, 18.198922150882048],
+              [10.126800739719087, 18.192359148568762],
+              [9.988595064490216, 18.172734820447474],
+              [9.85312422606958, 18.14024251677739],
+              [9.721719567720436, 18.095202210312237],
+              [9.595669690810766, 18.038057113269076],
+              [9.476207106652101, 17.969369007114743],
+              [9.364495630484392, 17.889812349055255],
+              [9.261618669117883, 17.800167234433463],
+              [9.168568533490959, 17.701311307580816],
+              [9.086236885294658, 17.594210724810743],
+              [9.015406403773625, 17.479910282070346],
+              [8.956743735727128, 17.359522826272258],
+              [8.910793769403016, 17.23421807359045],
+              [8.877975252072886, 17.10521096018074],
+              [8.858577752113135, 16.973749651097236],
+              [8.85275994973774, 16.84110333187473],
+              [8.86054922630853, 16.708549904608123],
+              [8.8818425104034, 16.57736370665313],
+              [8.916408329433118, 16.448803365550514],
+              [8.963890008337529, 16.32409989865731],
+              [9.023809951462665, 16.204445160433327],
+              [9.095574939782063, 16.090980734512833],
+              [9.178482372830471, 15.984787361679347],
+              [9.27172738273566, 15.886874988704175],
+              [9.374410746275132, 15.79817351671959],
+              [9.485547519713867, 15.719524321363023],
+              [9.604076320125786, 15.65167261032016],
+              [9.728869175858854, 15.595260677076443],
+              [9.858741867725827, 15.550822102625256],
+              [9.992464681395326, 15.518776949553867],
+              [10.128773490365873, 15.49942798532747],
+              [10.266381087897429, 15.492957963729829],
+              [10.403988685428985, 15.49942798532747],
+              [10.540297494399534, 15.518776949553867],
+              [10.674020308069032, 15.550822102625256],
+              [10.803892999936004, 15.595260677076443],
+              [10.928685855669073, 15.65167261032016],
+              [11.047214656080993, 15.719524321363023],
+              [11.158351429519726, 15.79817351671959],
+              [11.261034793059197, 15.886874988704175],
+              [11.354279802964387, 15.984787361679347],
+              [11.437187236012795, 16.090980734512833],
+              [11.508952224332194, 16.204445160433327],
+              [11.56887216745733, 16.32409989865731],
+              [11.616353846361742, 16.448803365550514],
+              [11.65091966539146, 16.57736370665313],
+              [11.672212949486328, 16.708549904608123],
+              [11.68000222605712, 16.84110333187473],
+              [11.674184423681723, 16.973749651097236],
+              [11.654786923721971, 17.10521096018074],
+              [11.621968406391844, 17.23421807359045],
+              [11.576018440067731, 17.359522826272265],
+              [11.517355772021233, 17.479910282070346],
+              [11.4465252905002, 17.594210724810743],
+              [11.364193642303901, 17.701311307580816],
+              [11.271143506676976, 17.800167234433463],
+              [11.168266545310468, 17.889812349055255],
+              [11.056555069142757, 17.969369007114743],
+              [10.937092484984092, 18.03805711326907],
+              [10.811042608074422, 18.095202210312237],
+              [10.679637949725281, 18.14024251677739],
+              [10.544167111304644, 18.172734820447474],
+              [10.40596143607577, 18.192359148568762],
+              [10.266381087897429, 18.198922150882048],
+            ],
+          ],
+        },
+      },
+      {
+        type: "Feature",
+        properties: {
+          cost: 4,
+          abatement: 4,
+        },
+        geometry: {
+          coordinates: [
+            [
+              [-9.690278800554552, 26.97972035213317],
+              [-9.690278800554552, 24.396370522145148],
+              [-3.8527118185513416, 24.396370522145148],
+              [-3.8527118185513416, 26.97972035213317],
+              [-9.690278800554552, 26.97972035213317],
+            ],
+          ],
+          type: "Polygon",
+        },
+      },
+      {
+        type: "Feature",
+        properties: {
+          cost: 5,
+          abatement: 5,
+        },
+        geometry: {
+          coordinates: [
+            [
+              [-7.541068855664491, 41.43353951220527],
+              [-7.541068855664491, 38.528044523038034],
+              [-1.643841285199045, 38.528044523038034],
+              [-1.643841285199045, 41.43353951220527],
+              [-7.541068855664491, 41.43353951220527],
+            ],
+          ],
+          type: "Polygon",
+        },
+      },
+      {
+        type: "Feature",
+        properties: {
+          cost: 6,
+          abatement: 6,
+        },
+        geometry: {
+          coordinates: [
+            [
+              [0.7726470601811286, 48.46965178808685],
+              [0.7726470601811286, 45.718470124797705],
+              [6.157258642562226, 45.718470124797705],
+              [6.157258642562226, 48.46965178808685],
+              [0.7726470601811286, 48.46965178808685],
+            ],
+          ],
+          type: "Polygon",
+        },
+      },
+      {
+        type: "Feature",
+        properties: {
+          cost: 7,
+          abatement: 7,
+        },
+        geometry: {
+          coordinates: [
+            [
+              [18.520997774407675, 51.43117929808446],
+              [18.520997774407675, 47.08565668322254],
+              [25.385706207893833, 47.08565668322254],
+              [25.385706207893833, 51.43117929808446],
+              [18.520997774407675, 51.43117929808446],
+            ],
+          ],
+          type: "Polygon",
+        },
+      },
+      {
+        type: "Feature",
+        properties: {
+          cost: 8,
+          abatement: 8,
+        },
+        geometry: {
+          coordinates: [
+            [
+              [35.91663289081157, 51.36524697256908],
+              [35.91663289081157, 45.31005648052533],
+              [45.92258788209065, 45.31005648052533],
+              [45.92258788209065, 51.36524697256908],
+              [35.91663289081157, 51.36524697256908],
+            ],
+          ],
+          type: "Polygon",
+        },
+      },
+      {
+        type: "Feature",
+        properties: {
+          cost: 9,
+          abatement: 9,
+        },
+        geometry: {
+          coordinates: [
+            [
+              [-6.867226241868707, 54.62702931692684],
+              [-6.867226241868707, 51.419015757241226],
+              [2.883293358353882, 51.419015757241226],
+              [2.883293358353882, 54.62702931692684],
+              [-6.867226241868707, 54.62702931692684],
+            ],
+          ],
+          type: "Polygon",
+        },
+      },
+      {
+        type: "Feature",
+        properties: {
+          cost: 10,
+          abatement: 0,
+        },
+        geometry: {
+          coordinates: [
+            [
+              [38.47505991918618, 37.280028551805856],
+              [38.47505991918618, 29.10048451439505],
+              [50.31049906445517, 29.10048451439505],
+              [50.31049906445517, 37.280028551805856],
+              [38.47505991918618, 37.280028551805856],
+            ],
+          ],
+          type: "Polygon",
+        },
+      },
+    ],
+  };
+
+  const costAbatementSource = {
+    id: "cost-abatement-source",
+    type: "geojson",
+    data: _data,
+  };
+
+  const maxCost = d3.max(
+    costAbatementSource.data.features,
+    (d) => d.properties.cost,
+  );
+  const maxAbatement = d3.max(
+    costAbatementSource.data.features,
+    (d) => d.properties.abatement,
+  );
+
+  const COLOR_NUMBER = 2;
+  const colors = generateColorRamp(COLOR_NUMBER);
+
+  const costAbatementLayer: FillLayerSpecification = {
+    id: "cost-abatement-layer",
+    type: "fill",
+    source: costAbatementSource.id,
+    paint: {
+      "fill-color": [
+        "match",
+        [
+          "concat",
+          [
+            "ceil",
+            [
+              "/",
+              ["*", ["/", ["get", "cost"], maxCost], 100],
+              100 / COLOR_NUMBER,
+            ],
+          ],
+          [
+            "ceil",
+            [
+              "/",
+              ["*", ["/", ["get", "abatement"], maxAbatement], 100],
+              100 / COLOR_NUMBER,
+            ],
+          ],
+        ],
+        ...colors,
+        "#FFF",
+      ],
+      "fill-opacity": 1,
+    },
+  };
+
+  return (
+    <>
+      <Source {...costAbatementSource} />
+      <Layer {...costAbatementLayer} />
+    </>
+  );
+}
diff --git a/client/src/containers/projects/map/layers/projects/utils.ts b/client/src/containers/projects/map/layers/projects/utils.ts
new file mode 100644
index 00000000..8282d577
--- /dev/null
+++ b/client/src/containers/projects/map/layers/projects/utils.ts
@@ -0,0 +1,35 @@
+export const MATRIX_COLORS: Record<string, string[]> = {
+  "#e8e8e8": ["00"],
+  "#e4acac": ["01"],
+  "#c85a5a": ["02"],
+  "#b0d5df": ["10"],
+  "#ad9ea5": ["11"],
+  "#985356": ["12"],
+  "#64acbe": ["20"],
+  "#627f8c": ["21"],
+  "#574249": ["22"],
+};
+
+export const generateColorRamp = function (
+  COLOR_NUMBER: number = 3,
+  COLORS: Record<string, string[]> = MATRIX_COLORS,
+) {
+  const colors = [...Array((COLOR_NUMBER + 1) * (COLOR_NUMBER + 1)).keys()];
+
+  return colors
+    .map((c, i) => {
+      const position = `${Math.floor((i / (COLOR_NUMBER + 1)) % (COLOR_NUMBER + 1))}${
+        i % (COLOR_NUMBER + 1)
+      }`;
+      const color = Object.keys(COLORS).reduce((acc, k) => {
+        if (COLORS[k].includes(position) && !acc) {
+          return k;
+        }
+
+        return acc;
+      }, "");
+
+      return [position, color];
+    })
+    .flat();
+};
diff --git a/client/src/containers/projects/map/legend/index.tsx b/client/src/containers/projects/map/legend/index.tsx
new file mode 100644
index 00000000..964b1546
--- /dev/null
+++ b/client/src/containers/projects/map/legend/index.tsx
@@ -0,0 +1,21 @@
+import { PropsWithChildren } from "react";
+
+import { cn } from "@/lib/utils";
+
+export default function Legend({
+  children,
+  className,
+}: PropsWithChildren<{
+  className?: HTMLDivElement["className"];
+}>) {
+  return (
+    <div
+      className={cn(
+        "absolute bottom-8 right-16 rounded-2xl bg-blue-950 p-2 text-white",
+        className,
+      )}
+    >
+      {children}
+    </div>
+  );
+}
diff --git a/client/src/containers/projects/map/legend/types/matrix.tsx b/client/src/containers/projects/map/legend/types/matrix.tsx
new file mode 100644
index 00000000..9bbc1d88
--- /dev/null
+++ b/client/src/containers/projects/map/legend/types/matrix.tsx
@@ -0,0 +1,145 @@
+import React from "react";
+
+export interface LegendMatrixIntersectionsProps {
+  intersections: Array<{
+    id: number;
+    color: string;
+  }>;
+}
+
+export interface LegendTypeProps {
+  className?: HTMLDivElement["className"];
+}
+
+export default function MatrixLegend({
+  intersections = [],
+  colorNumber = 4,
+}: LegendTypeProps &
+  LegendMatrixIntersectionsProps & { colorNumber?: number }) {
+  return (
+    <div className="flex max-w-[250px] gap-3">
+      <div>
+        <span>Abatement potential and cost $/tc02</span>
+      </div>
+      <div className="flex items-center space-x-14">
+        <div className="relative w-20 flex-shrink-0 py-6 pl-5">
+          <p
+            className="font-heading absolute bottom-7 left-0 rotate-180 transform text-xs font-medium text-white"
+            style={{ writingMode: "vertical-rl" }}
+          >
+            Cost
+          </p>
+          <p className="font-heading absolute bottom-1 left-1/2 -translate-x-1/2 transform text-xs font-medium text-white">
+            Abatement
+          </p>
+          <div className="preserve-3d w-full transform">
+            <div className="w-full" style={{ paddingBottom: "100%" }}>
+              <div className="absolute left-0 top-0 flex h-full w-full flex-wrap">
+                {intersections.map((i) => (
+                  <div
+                    key={i.id}
+                    className="relative block"
+                    style={{
+                      background: `${i.color}`,
+                      width: `${100 / colorNumber}%`,
+                      height: `${100 / colorNumber}%`,
+                    }}
+                  />
+                ))}
+              </div>
+
+              {/*<div className="font-heading text-xxs absolute bottom-0 left-full z-10 h-full w-2 transform justify-between text-white">*/}
+              {/*  <div*/}
+              {/*    className="absolute flex h-px items-center space-x-1 leading-none"*/}
+              {/*    style={{ bottom: `${(100 / colorNumber) * 2}%` }}*/}
+              {/*  >*/}
+              {/*    <span className="relative top-px block h-px w-1 bg-gray-300" />*/}
+              {/*    <span className="relative block -rotate-45 transform">*/}
+              {/*      <span>10</span>*/}
+              {/*    </span>*/}
+              {/*  </div>*/}
+              {/*  <div*/}
+              {/*    className="absolute flex h-px items-center space-x-1 leading-none"*/}
+              {/*    style={{ bottom: `${(100 / colorNumber) * 6}%` }}*/}
+              {/*  >*/}
+              {/*    <span className="relative top-px block h-px w-1 bg-gray-300" />*/}
+              {/*    <span className="relative block -rotate-45 transform">*/}
+              {/*      <span>50</span>*/}
+              {/*    </span>*/}
+              {/*  </div>*/}
+              {/*  <div*/}
+              {/*    className="absolute flex h-px items-center space-x-1 leading-none"*/}
+              {/*    style={{ bottom: "100%" }}*/}
+              {/*  >*/}
+              {/*    <span className="relative top-px block h-px w-1 bg-gray-300" />*/}
+              {/*    <span className="relative block -rotate-45 transform">*/}
+              {/*      <span>100</span>*/}
+              {/*    </span>*/}
+              {/*  </div>*/}
+              {/*</div>*/}
+
+              {/*<div className="font-heading text-xxs absolute -bottom-1 -left-1 z-10 h-full w-2 origin-bottom rotate-90 transform justify-between text-white">*/}
+              {/*  <div*/}
+              {/*    className="absolute flex h-px transform items-center space-x-1 leading-none"*/}
+              {/*    style={{ bottom: `${100 - (100 / colorNumber) * 2}%` }}*/}
+              {/*  >*/}
+              {/*    <span className="relative top-px block h-px w-1 bg-gray-300" />*/}
+              {/*    <span className="relative block -rotate-180 transform">*/}
+              {/*      <span className="relative block rotate-45 transform">*/}
+              {/*        10*/}
+              {/*      </span>*/}
+              {/*    </span>*/}
+              {/*  </div>*/}
+              {/*  <div*/}
+              {/*    className="absolute flex h-px transform items-center space-x-1 leading-none"*/}
+              {/*    style={{ bottom: `${100 - (100 / colorNumber) * 6}%` }}*/}
+              {/*  >*/}
+              {/*    <span className="relative top-px block h-px w-1 bg-gray-300" />*/}
+              {/*    <span className="relative block -rotate-180 transform">*/}
+              {/*      <span className="relative block rotate-45 transform">*/}
+              {/*        50*/}
+              {/*      </span>*/}
+              {/*    </span>*/}
+              {/*  </div>*/}
+              {/*  <div*/}
+              {/*    className="absolute flex h-px transform items-center space-x-1 leading-none"*/}
+              {/*    style={{ bottom: "0%" }}*/}
+              {/*  >*/}
+              {/*    <span className="relative top-px block h-px w-1 bg-gray-300" />*/}
+              {/*    <span className="relative block -rotate-180 transform">*/}
+              {/*      <span className="relative block rotate-45 transform">*/}
+              {/*        100*/}
+              {/*      </span>*/}
+              {/*    </span>*/}
+              {/*  </div>*/}
+              {/*</div>*/}
+            </div>
+          </div>
+        </div>
+
+        {/*<div*/}
+        {/*  className={cn({*/}
+        {/*    [className]: !!className,*/}
+        {/*  })}*/}
+        {/*>*/}
+        {/*  <ul className="flex w-full flex-col space-y-2">*/}
+        {/*    {items.map(({ value, color }) => (*/}
+        {/*      <li*/}
+        {/*        key={`${value}`}*/}
+        {/*        className="font-heading flex items-center space-x-2 text-xs"*/}
+        {/*      >*/}
+        {/*        <div*/}
+        {/*          className="h-2 w-2 flex-shrink-0 rounded-sm"*/}
+        {/*          style={{*/}
+        {/*            backgroundColor: color,*/}
+        {/*          }}*/}
+        {/*        />*/}
+        {/*        <div className="clamp-2">{value}</div>*/}
+        {/*      </li>*/}
+        {/*    ))}*/}
+        {/*  </ul>*/}
+        {/*</div>*/}
+      </div>
+    </div>
+  );
+}
diff --git a/client/tsconfig.json b/client/tsconfig.json
index 83f6e899..4de67b8c 100644
--- a/client/tsconfig.json
+++ b/client/tsconfig.json
@@ -1,6 +1,7 @@
 {
   "extends": "../tsconfig.json",
   "compilerOptions": {
+    "target": "ES6",
     "lib": [
       "dom",
       "dom.iterable",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 39aff3f2..307585b7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -321,6 +321,9 @@ importers:
       clsx:
         specifier: 2.1.1
         version: 2.1.1
+      d3:
+        specifier: 7.9.0
+        version: 7.9.0
       framer-motion:
         specifier: 11.11.9
         version: 11.11.9(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -330,6 +333,9 @@ importers:
       lucide-react:
         specifier: 0.447.0
         version: 0.447.0(react@18.3.1)
+      mapbox-expression:
+        specifier: 0.0.3
+        version: 0.0.3(mapbox-gl@3.7.0)
       mapbox-gl:
         specifier: 3.7.0
         version: 3.7.0
@@ -361,6 +367,12 @@ importers:
         specifier: 'catalog:'
         version: 3.23.8
     devDependencies:
+      '@types/d3':
+        specifier: 7.4.3
+        version: 7.4.3
+      '@types/geojson':
+        specifier: 7946.0.14
+        version: 7946.0.14
       '@types/mapbox-gl':
         specifier: 3.4.0
         version: 3.4.0
@@ -3062,6 +3074,99 @@ packages:
   '@types/cookiejar@2.1.5':
     resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==}
 
+  '@types/d3-array@3.2.1':
+    resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==}
+
+  '@types/d3-axis@3.0.6':
+    resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==}
+
+  '@types/d3-brush@3.0.6':
+    resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==}
+
+  '@types/d3-chord@3.0.6':
+    resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==}
+
+  '@types/d3-color@3.1.3':
+    resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
+
+  '@types/d3-contour@3.0.6':
+    resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==}
+
+  '@types/d3-delaunay@6.0.4':
+    resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==}
+
+  '@types/d3-dispatch@3.0.6':
+    resolution: {integrity: sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==}
+
+  '@types/d3-drag@3.0.7':
+    resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==}
+
+  '@types/d3-dsv@3.0.7':
+    resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==}
+
+  '@types/d3-ease@3.0.2':
+    resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}
+
+  '@types/d3-fetch@3.0.7':
+    resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==}
+
+  '@types/d3-force@3.0.10':
+    resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==}
+
+  '@types/d3-format@3.0.4':
+    resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==}
+
+  '@types/d3-geo@3.1.0':
+    resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==}
+
+  '@types/d3-hierarchy@3.1.7':
+    resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==}
+
+  '@types/d3-interpolate@3.0.4':
+    resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
+
+  '@types/d3-path@3.1.0':
+    resolution: {integrity: sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==}
+
+  '@types/d3-polygon@3.0.2':
+    resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==}
+
+  '@types/d3-quadtree@3.0.6':
+    resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==}
+
+  '@types/d3-random@3.0.3':
+    resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==}
+
+  '@types/d3-scale-chromatic@3.0.3':
+    resolution: {integrity: sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==}
+
+  '@types/d3-scale@4.0.8':
+    resolution: {integrity: sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==}
+
+  '@types/d3-selection@3.0.11':
+    resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==}
+
+  '@types/d3-shape@3.1.6':
+    resolution: {integrity: sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==}
+
+  '@types/d3-time-format@4.0.3':
+    resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==}
+
+  '@types/d3-time@3.0.3':
+    resolution: {integrity: sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==}
+
+  '@types/d3-timer@3.0.2':
+    resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
+
+  '@types/d3-transition@3.0.9':
+    resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==}
+
+  '@types/d3-zoom@3.0.8':
+    resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==}
+
+  '@types/d3@7.4.3':
+    resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==}
+
   '@types/estree@1.0.5':
     resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
 
@@ -3938,6 +4043,10 @@ packages:
     resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
     engines: {node: '>= 6'}
 
+  commander@7.2.0:
+    resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
+    engines: {node: '>= 10'}
+
   commander@9.5.0:
     resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==}
     engines: {node: ^12.20.0 || >=14}
@@ -4066,6 +4175,133 @@ packages:
   csstype@3.1.3:
     resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
 
+  d3-array@3.2.4:
+    resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
+    engines: {node: '>=12'}
+
+  d3-axis@3.0.0:
+    resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==}
+    engines: {node: '>=12'}
+
+  d3-brush@3.0.0:
+    resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==}
+    engines: {node: '>=12'}
+
+  d3-chord@3.0.1:
+    resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==}
+    engines: {node: '>=12'}
+
+  d3-color@3.1.0:
+    resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
+    engines: {node: '>=12'}
+
+  d3-contour@4.0.2:
+    resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==}
+    engines: {node: '>=12'}
+
+  d3-delaunay@6.0.4:
+    resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==}
+    engines: {node: '>=12'}
+
+  d3-dispatch@3.0.1:
+    resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
+    engines: {node: '>=12'}
+
+  d3-drag@3.0.0:
+    resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
+    engines: {node: '>=12'}
+
+  d3-dsv@3.0.1:
+    resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==}
+    engines: {node: '>=12'}
+    hasBin: true
+
+  d3-ease@3.0.1:
+    resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
+    engines: {node: '>=12'}
+
+  d3-fetch@3.0.1:
+    resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==}
+    engines: {node: '>=12'}
+
+  d3-force@3.0.0:
+    resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==}
+    engines: {node: '>=12'}
+
+  d3-format@3.1.0:
+    resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
+    engines: {node: '>=12'}
+
+  d3-geo@3.1.1:
+    resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==}
+    engines: {node: '>=12'}
+
+  d3-hierarchy@3.1.2:
+    resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==}
+    engines: {node: '>=12'}
+
+  d3-interpolate@3.0.1:
+    resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
+    engines: {node: '>=12'}
+
+  d3-path@3.1.0:
+    resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
+    engines: {node: '>=12'}
+
+  d3-polygon@3.0.1:
+    resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==}
+    engines: {node: '>=12'}
+
+  d3-quadtree@3.0.1:
+    resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==}
+    engines: {node: '>=12'}
+
+  d3-random@3.0.1:
+    resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==}
+    engines: {node: '>=12'}
+
+  d3-scale-chromatic@3.1.0:
+    resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==}
+    engines: {node: '>=12'}
+
+  d3-scale@4.0.2:
+    resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
+    engines: {node: '>=12'}
+
+  d3-selection@3.0.0:
+    resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
+    engines: {node: '>=12'}
+
+  d3-shape@3.2.0:
+    resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
+    engines: {node: '>=12'}
+
+  d3-time-format@4.1.0:
+    resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
+    engines: {node: '>=12'}
+
+  d3-time@3.1.0:
+    resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
+    engines: {node: '>=12'}
+
+  d3-timer@3.0.1:
+    resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
+    engines: {node: '>=12'}
+
+  d3-transition@3.0.1:
+    resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
+    engines: {node: '>=12'}
+    peerDependencies:
+      d3-selection: 2 - 3
+
+  d3-zoom@3.0.0:
+    resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
+    engines: {node: '>=12'}
+
+  d3@7.9.0:
+    resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==}
+    engines: {node: '>=12'}
+
   damerau-levenshtein@1.0.8:
     resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
 
@@ -4143,6 +4379,9 @@ packages:
     resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
     engines: {node: '>= 0.4'}
 
+  delaunator@5.0.1:
+    resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==}
+
   delayed-stream@1.0.0:
     resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
     engines: {node: '>=0.4.0'}
@@ -4883,6 +5122,10 @@ packages:
     resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
     engines: {node: '>=0.10.0'}
 
+  iconv-lite@0.6.3:
+    resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
+    engines: {node: '>=0.10.0'}
+
   ieee754@1.2.1:
     resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
 
@@ -4931,6 +5174,10 @@ packages:
     resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==}
     engines: {node: '>= 0.4'}
 
+  internmap@2.0.3:
+    resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
+    engines: {node: '>=12'}
+
   invariant@2.2.4:
     resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
 
@@ -5552,6 +5799,11 @@ packages:
   makeerror@1.0.12:
     resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==}
 
+  mapbox-expression@0.0.3:
+    resolution: {integrity: sha512-P2hiPXPxXzmOTZUP2zLa+kjN42QbvAM125tH/akt+hdOOu4QgxAvXGcffiKMLVxOFE28vceD8Qc11gWUbnkc/A==}
+    peerDependencies:
+      mapbox-gl: ^1.0
+
   mapbox-gl@3.7.0:
     resolution: {integrity: sha512-dCbVyH1uGobwv6f4QKRv2Z2wuVT/RmspsudK3sTxGRFxZi6Pd2P9axdbVyZpmGddCAREy44pHhvzvO0qgpdKAg==}
 
@@ -6655,6 +6907,9 @@ packages:
     deprecated: Rimraf versions prior to v4 are no longer supported
     hasBin: true
 
+  robust-predicates@3.0.2:
+    resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==}
+
   rollup-plugin-esbuild-minify@1.1.2:
     resolution: {integrity: sha512-l8s3ggesd6WVpi7GPQG/X5MMhwg9thEQLRrsjO6X7xQMkKMxmmjm1NTunQid4pcIKcPYJdAkPwSo2vbI2Itf8Q==}
     engines: {node: '>= 14.18'}
@@ -10893,6 +11148,123 @@ snapshots:
 
   '@types/cookiejar@2.1.5': {}
 
+  '@types/d3-array@3.2.1': {}
+
+  '@types/d3-axis@3.0.6':
+    dependencies:
+      '@types/d3-selection': 3.0.11
+
+  '@types/d3-brush@3.0.6':
+    dependencies:
+      '@types/d3-selection': 3.0.11
+
+  '@types/d3-chord@3.0.6': {}
+
+  '@types/d3-color@3.1.3': {}
+
+  '@types/d3-contour@3.0.6':
+    dependencies:
+      '@types/d3-array': 3.2.1
+      '@types/geojson': 7946.0.14
+
+  '@types/d3-delaunay@6.0.4': {}
+
+  '@types/d3-dispatch@3.0.6': {}
+
+  '@types/d3-drag@3.0.7':
+    dependencies:
+      '@types/d3-selection': 3.0.11
+
+  '@types/d3-dsv@3.0.7': {}
+
+  '@types/d3-ease@3.0.2': {}
+
+  '@types/d3-fetch@3.0.7':
+    dependencies:
+      '@types/d3-dsv': 3.0.7
+
+  '@types/d3-force@3.0.10': {}
+
+  '@types/d3-format@3.0.4': {}
+
+  '@types/d3-geo@3.1.0':
+    dependencies:
+      '@types/geojson': 7946.0.14
+
+  '@types/d3-hierarchy@3.1.7': {}
+
+  '@types/d3-interpolate@3.0.4':
+    dependencies:
+      '@types/d3-color': 3.1.3
+
+  '@types/d3-path@3.1.0': {}
+
+  '@types/d3-polygon@3.0.2': {}
+
+  '@types/d3-quadtree@3.0.6': {}
+
+  '@types/d3-random@3.0.3': {}
+
+  '@types/d3-scale-chromatic@3.0.3': {}
+
+  '@types/d3-scale@4.0.8':
+    dependencies:
+      '@types/d3-time': 3.0.3
+
+  '@types/d3-selection@3.0.11': {}
+
+  '@types/d3-shape@3.1.6':
+    dependencies:
+      '@types/d3-path': 3.1.0
+
+  '@types/d3-time-format@4.0.3': {}
+
+  '@types/d3-time@3.0.3': {}
+
+  '@types/d3-timer@3.0.2': {}
+
+  '@types/d3-transition@3.0.9':
+    dependencies:
+      '@types/d3-selection': 3.0.11
+
+  '@types/d3-zoom@3.0.8':
+    dependencies:
+      '@types/d3-interpolate': 3.0.4
+      '@types/d3-selection': 3.0.11
+
+  '@types/d3@7.4.3':
+    dependencies:
+      '@types/d3-array': 3.2.1
+      '@types/d3-axis': 3.0.6
+      '@types/d3-brush': 3.0.6
+      '@types/d3-chord': 3.0.6
+      '@types/d3-color': 3.1.3
+      '@types/d3-contour': 3.0.6
+      '@types/d3-delaunay': 6.0.4
+      '@types/d3-dispatch': 3.0.6
+      '@types/d3-drag': 3.0.7
+      '@types/d3-dsv': 3.0.7
+      '@types/d3-ease': 3.0.2
+      '@types/d3-fetch': 3.0.7
+      '@types/d3-force': 3.0.10
+      '@types/d3-format': 3.0.4
+      '@types/d3-geo': 3.1.0
+      '@types/d3-hierarchy': 3.1.7
+      '@types/d3-interpolate': 3.0.4
+      '@types/d3-path': 3.1.0
+      '@types/d3-polygon': 3.0.2
+      '@types/d3-quadtree': 3.0.6
+      '@types/d3-random': 3.0.3
+      '@types/d3-scale': 4.0.8
+      '@types/d3-scale-chromatic': 3.0.3
+      '@types/d3-selection': 3.0.11
+      '@types/d3-shape': 3.1.6
+      '@types/d3-time': 3.0.3
+      '@types/d3-time-format': 4.0.3
+      '@types/d3-timer': 3.0.2
+      '@types/d3-transition': 3.0.9
+      '@types/d3-zoom': 3.0.8
+
   '@types/estree@1.0.5': {}
 
   '@types/estree@1.0.6': {}
@@ -12015,6 +12387,8 @@ snapshots:
 
   commander@4.1.1: {}
 
+  commander@7.2.0: {}
+
   commander@9.5.0: {}
 
   comment-json@4.2.3:
@@ -12143,6 +12517,158 @@ snapshots:
 
   csstype@3.1.3: {}
 
+  d3-array@3.2.4:
+    dependencies:
+      internmap: 2.0.3
+
+  d3-axis@3.0.0: {}
+
+  d3-brush@3.0.0:
+    dependencies:
+      d3-dispatch: 3.0.1
+      d3-drag: 3.0.0
+      d3-interpolate: 3.0.1
+      d3-selection: 3.0.0
+      d3-transition: 3.0.1(d3-selection@3.0.0)
+
+  d3-chord@3.0.1:
+    dependencies:
+      d3-path: 3.1.0
+
+  d3-color@3.1.0: {}
+
+  d3-contour@4.0.2:
+    dependencies:
+      d3-array: 3.2.4
+
+  d3-delaunay@6.0.4:
+    dependencies:
+      delaunator: 5.0.1
+
+  d3-dispatch@3.0.1: {}
+
+  d3-drag@3.0.0:
+    dependencies:
+      d3-dispatch: 3.0.1
+      d3-selection: 3.0.0
+
+  d3-dsv@3.0.1:
+    dependencies:
+      commander: 7.2.0
+      iconv-lite: 0.6.3
+      rw: 1.3.3
+
+  d3-ease@3.0.1: {}
+
+  d3-fetch@3.0.1:
+    dependencies:
+      d3-dsv: 3.0.1
+
+  d3-force@3.0.0:
+    dependencies:
+      d3-dispatch: 3.0.1
+      d3-quadtree: 3.0.1
+      d3-timer: 3.0.1
+
+  d3-format@3.1.0: {}
+
+  d3-geo@3.1.1:
+    dependencies:
+      d3-array: 3.2.4
+
+  d3-hierarchy@3.1.2: {}
+
+  d3-interpolate@3.0.1:
+    dependencies:
+      d3-color: 3.1.0
+
+  d3-path@3.1.0: {}
+
+  d3-polygon@3.0.1: {}
+
+  d3-quadtree@3.0.1: {}
+
+  d3-random@3.0.1: {}
+
+  d3-scale-chromatic@3.1.0:
+    dependencies:
+      d3-color: 3.1.0
+      d3-interpolate: 3.0.1
+
+  d3-scale@4.0.2:
+    dependencies:
+      d3-array: 3.2.4
+      d3-format: 3.1.0
+      d3-interpolate: 3.0.1
+      d3-time: 3.1.0
+      d3-time-format: 4.1.0
+
+  d3-selection@3.0.0: {}
+
+  d3-shape@3.2.0:
+    dependencies:
+      d3-path: 3.1.0
+
+  d3-time-format@4.1.0:
+    dependencies:
+      d3-time: 3.1.0
+
+  d3-time@3.1.0:
+    dependencies:
+      d3-array: 3.2.4
+
+  d3-timer@3.0.1: {}
+
+  d3-transition@3.0.1(d3-selection@3.0.0):
+    dependencies:
+      d3-color: 3.1.0
+      d3-dispatch: 3.0.1
+      d3-ease: 3.0.1
+      d3-interpolate: 3.0.1
+      d3-selection: 3.0.0
+      d3-timer: 3.0.1
+
+  d3-zoom@3.0.0:
+    dependencies:
+      d3-dispatch: 3.0.1
+      d3-drag: 3.0.0
+      d3-interpolate: 3.0.1
+      d3-selection: 3.0.0
+      d3-transition: 3.0.1(d3-selection@3.0.0)
+
+  d3@7.9.0:
+    dependencies:
+      d3-array: 3.2.4
+      d3-axis: 3.0.0
+      d3-brush: 3.0.0
+      d3-chord: 3.0.1
+      d3-color: 3.1.0
+      d3-contour: 4.0.2
+      d3-delaunay: 6.0.4
+      d3-dispatch: 3.0.1
+      d3-drag: 3.0.0
+      d3-dsv: 3.0.1
+      d3-ease: 3.0.1
+      d3-fetch: 3.0.1
+      d3-force: 3.0.0
+      d3-format: 3.1.0
+      d3-geo: 3.1.1
+      d3-hierarchy: 3.1.2
+      d3-interpolate: 3.0.1
+      d3-path: 3.1.0
+      d3-polygon: 3.0.1
+      d3-quadtree: 3.0.1
+      d3-random: 3.0.1
+      d3-scale: 4.0.2
+      d3-scale-chromatic: 3.1.0
+      d3-selection: 3.0.0
+      d3-shape: 3.2.0
+      d3-time: 3.1.0
+      d3-time-format: 4.1.0
+      d3-timer: 3.0.1
+      d3-transition: 3.0.1(d3-selection@3.0.0)
+      d3-zoom: 3.0.0
+
   damerau-levenshtein@1.0.8: {}
 
   data-view-buffer@1.0.1:
@@ -12228,6 +12754,10 @@ snapshots:
       has-property-descriptors: 1.0.2
       object-keys: 1.1.1
 
+  delaunator@5.0.1:
+    dependencies:
+      robust-predicates: 3.0.2
+
   delayed-stream@1.0.0: {}
 
   delegates@1.0.0: {}
@@ -13243,6 +13773,10 @@ snapshots:
     dependencies:
       safer-buffer: 2.1.2
 
+  iconv-lite@0.6.3:
+    dependencies:
+      safer-buffer: 2.1.2
+
   ieee754@1.2.1: {}
 
   ignore-by-default@1.0.1: {}
@@ -13314,6 +13848,8 @@ snapshots:
       hasown: 2.0.2
       side-channel: 1.0.6
 
+  internmap@2.0.3: {}
+
   invariant@2.2.4:
     dependencies:
       loose-envify: 1.4.0
@@ -14110,6 +14646,10 @@ snapshots:
     dependencies:
       tmpl: 1.0.5
 
+  mapbox-expression@0.0.3(mapbox-gl@3.7.0):
+    dependencies:
+      mapbox-gl: 3.7.0
+
   mapbox-gl@3.7.0:
     dependencies:
       '@mapbox/jsonlint-lines-primitives': 2.0.2
@@ -15202,6 +15742,8 @@ snapshots:
     dependencies:
       glob: 7.2.3
 
+  robust-predicates@3.0.2: {}
+
   rollup-plugin-esbuild-minify@1.1.2(rollup@4.24.0):
     dependencies:
       esbuild: 0.23.1