-
+
+ {/* Sidebar */}
+
+
+ Map Layers
+
+
+ {/* Search Form */}
+
+
+ {/* Layer Toggle List */}
+
+
+ Available Layers ({layerConfigs.length})
+
+
+ {layerConfigs.map((layer) => (
+
+
+ {formatLayerName(layer.topic)}
+
+
+ {searchCompleted && (
+
+ )}
+
+ {!searchCompleted && (
+
+ Search first
+
+ )}
+
+ ))}
+
+
- {clickedCoordinate && (
-
- You clicked at: {clickedCoordinate[0]} /{' '} {clickedCoordinate[1]}
-
- )}
- >
+
+ {/* Map Container */}
+
+
+
+ {clickedCoordinate && (
+
+ Coordinates: {clickedCoordinate[0].toFixed(2)} / {clickedCoordinate[1].toFixed(2)}
+
+ )}
+
+
);
};
diff --git a/src/components/UI/Dock.css b/src/components/UI/Dock.css
new file mode 100644
index 0000000..d2c3bd7
--- /dev/null
+++ b/src/components/UI/Dock.css
@@ -0,0 +1,56 @@
+.dock-outer {
+ margin: 0 0.5rem;
+ display: flex;
+ max-width: 100%;
+ align-items: center;
+}
+
+.dock-panel {
+ position: absolute;
+ bottom: 0.5rem;
+ left: 50%;
+ transform: translateX(-50%);
+ display: flex;
+ align-items: flex-end;
+ width: fit-content;
+ gap: 1rem;
+ border-radius: 1rem;
+ background-color: #060010;
+ border: 1px solid #222;
+ padding: 0 0.5rem 0.5rem;
+}
+
+.dock-item {
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 10px;
+ background-color: #060010;
+ border: 1px solid #222;
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
+ 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+ cursor: pointer;
+ outline: none;
+}
+
+.dock-icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.dock-label {
+ position: absolute;
+ top: -1.5rem;
+ left: 50%;
+ width: fit-content;
+ white-space: pre;
+ border-radius: 0.375rem;
+ border: 1px solid #222;
+ background-color: #060010;
+ padding: 0.125rem 0.5rem;
+ font-size: 0.75rem;
+ color: #fff;
+ transform: translateX(-50%);
+}
\ No newline at end of file
diff --git a/src/components/UI/Dock.tsx b/src/components/UI/Dock.tsx
new file mode 100644
index 0000000..a16e807
--- /dev/null
+++ b/src/components/UI/Dock.tsx
@@ -0,0 +1,205 @@
+"use client";
+import "./Dock.css";
+
+import {
+ motion,
+ MotionValue,
+ useMotionValue,
+ useSpring,
+ useTransform,
+ type SpringOptions,
+ AnimatePresence,
+} from "framer-motion";
+import React, {
+ Children,
+ cloneElement,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from "react";
+
+
+export type DockItemData = {
+ icon: React.ReactNode;
+ label: React.ReactNode;
+ onClick: () => void;
+ className?: string;
+};
+
+export type DockProps = {
+ items: DockItemData[];
+ className?: string;
+ distance?: number;
+ panelHeight?: number;
+ baseItemSize?: number;
+ dockHeight?: number;
+ magnification?: number;
+ spring?: SpringOptions;
+};
+
+type DockItemProps = {
+ className?: string;
+ children: React.ReactNode;
+ onClick?: () => void;
+ mouseX: MotionValue;
+ spring: SpringOptions;
+ distance: number;
+ baseItemSize: number;
+ magnification: number;
+};
+
+function DockItem({
+ children,
+ className = "",
+ onClick,
+ mouseX,
+ spring,
+ distance,
+ magnification,
+ baseItemSize,
+}: DockItemProps) {
+ const ref = useRef
(null);
+ const isHovered = useMotionValue(0);
+
+ const mouseDistance = useTransform(mouseX, (val) => {
+ const rect = ref.current?.getBoundingClientRect() ?? {
+ x: 0,
+ width: baseItemSize,
+ };
+ return val - rect.x - baseItemSize / 2;
+ });
+
+ const targetSize = useTransform(
+ mouseDistance,
+ [-distance, 0, distance],
+ [baseItemSize, magnification, baseItemSize]
+ );
+ const size = useSpring(targetSize, spring);
+
+ return (
+ isHovered.set(1)}
+ onHoverEnd={() => isHovered.set(0)}
+ onFocus={() => isHovered.set(1)}
+ onBlur={() => isHovered.set(0)}
+ onClick={onClick}
+ className={`dock-item ${className}`}
+ tabIndex={0}
+ role="button"
+ aria-haspopup="true"
+ >
+ {Children.map(children, (child) =>
+ cloneElement(child as React.ReactElement, { })
+ )}
+
+ );
+}
+
+type DockLabelProps = {
+ className?: string;
+ children: React.ReactNode;
+};
+
+function DockLabel({ children, className = "", ...rest }: DockLabelProps) {
+ const { isHovered } = rest as { isHovered: MotionValue };
+ const [isVisible, setIsVisible] = useState(false);
+
+ useEffect(() => {
+ const unsubscribe = isHovered.on("change", (latest) => {
+ setIsVisible(latest === 1);
+ });
+ return () => unsubscribe();
+ }, [isHovered]);
+
+ return (
+
+ {isVisible && (
+
+ {children}
+
+ )}
+
+ );
+}
+
+type DockIconProps = {
+ className?: string;
+ children: React.ReactNode;
+};
+
+function DockIcon({ children, className = "" }: DockIconProps) {
+ return {children}
;
+}
+
+export default function Dock({
+ items,
+ className = "",
+ spring = { mass: 0.1, stiffness: 150, damping: 12 },
+ magnification = 70,
+ distance = 200,
+ panelHeight = 68,
+ dockHeight = 256,
+ baseItemSize = 50,
+}: DockProps) {
+ const mouseX = useMotionValue(Infinity);
+ const isHovered = useMotionValue(0);
+
+ const maxHeight = useMemo(
+ () => Math.max(dockHeight, magnification + magnification / 2 + 4),
+ [magnification, dockHeight]
+ );
+ const heightRow = useTransform(isHovered, [0, 1], [panelHeight, maxHeight]);
+ const height = useSpring(heightRow, spring);
+
+ return (
+
+ {
+ isHovered.set(1);
+ mouseX.set(pageX);
+ }}
+ onMouseLeave={() => {
+ isHovered.set(0);
+ mouseX.set(Infinity);
+ }}
+ className={`dock-panel ${className}`}
+ style={{ height: panelHeight }}
+ role="toolbar"
+ aria-label="Application dock"
+ >
+ {items.map((item, index) => (
+
+ {item.icon}
+ {item.label}
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/UI/Map_page.css b/src/components/UI/Map_page.css
new file mode 100644
index 0000000..ac68790
--- /dev/null
+++ b/src/components/UI/Map_page.css
@@ -0,0 +1,84 @@
+/* Full height setup */
+html, body, #root {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ width: 100%;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+ background-color: #f0f2f5;
+}
+
+/* Page container with space for navbar and sidebar */
+.container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: flex-start;
+ height: 100vh;
+ padding-top: 80px; /* Reserve space for a top bar */
+ padding-left: 240px; /* Reserve space for a sidebar if needed */
+ box-sizing: border-box;
+}
+
+/* Card-like map container */
+.map-container {
+ width: 700px;
+ height: 700px;
+ background-color: white;
+ border-radius: 20px;
+ box-shadow: 0 10px 20px rgba(0,0,0,0.05);
+ border: 1px solid #e0e0e0;
+ overflow: hidden;
+ position: relative;
+}
+
+/* The actual OpenLayers map inside the container */
+.map {
+ width: 700px;
+ height: 700px;
+
+}
+.submit {
+ background: linear-gradient(135deg, #007AFF, #0051D5); /* iOS blue gradient */
+ color: white;
+ padding: 12px 28px;
+ border: none;
+ border-radius: 12px;
+ font-size: 16px;
+ font-weight: 500;
+ cursor: pointer;
+ box-shadow: 0 6px 12px rgba(0, 122, 255, 0.3);
+ transition: all 0.2s ease-in-out;
+ outline: none;
+}
+
+.submit:hover {
+ background: linear-gradient(135deg, #005AE0, #0042B1);
+ transform: translateY(-2px);
+ box-shadow: 0 10px 18px rgba(0, 122, 255, 0.4);
+}
+
+.submit:active {
+ transform: translateY(1px);
+ box-shadow: 0 4px 10px rgba(0, 122, 255, 0.2);
+}
+
+.input-field {
+ width: 80%;
+ padding: 12px 16px;
+ font-size: 16px;
+ border: 1px solid #d1d1d6;
+ border-radius: 12px;
+ outline: none;
+ margin-bottom: 20px;
+ background-color: #f9f9f9;
+ color: #333;
+ transition: border 0.2s, box-shadow 0.2s;
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04);
+}
+
+.input-field:focus {
+ border-color: #007AFF;
+ box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.2);
+ background-color: #fff;
+}
diff --git a/src/components/UI/Map_page.tsx b/src/components/UI/Map_page.tsx
new file mode 100644
index 0000000..6741dd3
--- /dev/null
+++ b/src/components/UI/Map_page.tsx
@@ -0,0 +1,64 @@
+import { useEffect, useRef } from 'react';
+import { Map, View } from 'ol';
+import TileLayer from 'ol/layer/Tile';
+import { OSM } from 'ol/source';
+import { fromLonLat } from 'ol/proj';
+import './Map_page.css';
+import Dock from './Dock';
+// ✅ Make sure these are imported from 'react-icons/vsc'
+import { VscHome, VscArchive, VscAccount, VscSettingsGear } from 'react-icons/vsc';
+
+const Map_page = () => {
+ const mapRef = useRef