|
|
|
@@ -1,12 +1,5 @@
|
|
|
|
|
/* eslint-disable react-hooks/exhaustive-deps */
|
|
|
|
|
import {
|
|
|
|
|
AnimatePresence,
|
|
|
|
|
motion,
|
|
|
|
|
animate,
|
|
|
|
|
AnimationPlaybackControlsWithThen,
|
|
|
|
|
useTransform,
|
|
|
|
|
useMotionValue,
|
|
|
|
|
} from "motion/react";
|
|
|
|
|
import { AnimatePresence, motion } from "motion/react";
|
|
|
|
|
import clsx from "clsx";
|
|
|
|
|
import { useRef, useState, useEffect } from "react";
|
|
|
|
|
import Marker from "./Marker";
|
|
|
|
@@ -80,7 +73,7 @@ const calculateMinZoom = (containerSize: Size, imageSize: Size): number => {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function Map({ maxZoom = 1 }: MapProps) {
|
|
|
|
|
const { height: windowHeight, width: windowWidth } = useWindowSize();
|
|
|
|
|
const { width: windowWidth } = useWindowSize();
|
|
|
|
|
|
|
|
|
|
const [isDragging, setIsDragging] = useState(false);
|
|
|
|
|
const [startPosition, setStartPosition] = useState<Position>({ x: 0, y: 0 });
|
|
|
|
@@ -99,7 +92,6 @@ function Map({ maxZoom = 1 }: MapProps) {
|
|
|
|
|
const markersContainerRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
const [hoveredMarker, setHoveredMarker] = useState<IMarker | null>(null);
|
|
|
|
|
const [isImageLoaded, setIsImageLoaded] = useState(false);
|
|
|
|
|
const animationRef = useRef<AnimationPlaybackControlsWithThen | null>(null);
|
|
|
|
|
const [lastClickTime, setLastClickTime] = useState(0);
|
|
|
|
|
const [isShowInstruction, setIsShowInstruction] = useState(true);
|
|
|
|
|
const [lastHoveredMarker, setLastHoveredMarker] = useState<IMarker | null>(
|
|
|
|
@@ -111,37 +103,7 @@ function Map({ maxZoom = 1 }: MapProps) {
|
|
|
|
|
const [windSpeed, setWindSpeed] = useState(0);
|
|
|
|
|
const [windDirection, setWindDirection] = useState(0);
|
|
|
|
|
|
|
|
|
|
// const motionZoom = useMotionValue(0);
|
|
|
|
|
// const x = useMotionValue(0);
|
|
|
|
|
// const y = useMotionValue(0);
|
|
|
|
|
|
|
|
|
|
// const motionZoomValue = useSpring(motionZoom, {
|
|
|
|
|
// bounce: 0,
|
|
|
|
|
// });
|
|
|
|
|
// const motionXValue = useSpring(x, { bounce: 0 });
|
|
|
|
|
// const motionYValue = useSpring(y, { bounce: 0 });
|
|
|
|
|
|
|
|
|
|
const cloudsRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
// const motionZoomValue = useTransform(motionZoom, [0, 1], [0, 1]);
|
|
|
|
|
// const motionXValue = useTransform(x.get);
|
|
|
|
|
// const motionYValue = useTransform(y.get);
|
|
|
|
|
// x,
|
|
|
|
|
// [0, containerSizeRef.current.width],
|
|
|
|
|
// [0, containerSizeRef.current.width]
|
|
|
|
|
|
|
|
|
|
// x,
|
|
|
|
|
// [0, containerSizeRef.current.height],
|
|
|
|
|
// [0, containerSizeRef.current.height]
|
|
|
|
|
// );
|
|
|
|
|
|
|
|
|
|
// useEffect(() => {
|
|
|
|
|
// motionZoom.set(zoom);
|
|
|
|
|
// }, [zoom]);
|
|
|
|
|
|
|
|
|
|
// useEffect(() => {
|
|
|
|
|
// x.set(position.x);
|
|
|
|
|
// y.set(position.y);
|
|
|
|
|
// }, [position]);
|
|
|
|
|
|
|
|
|
|
const handleHoverMarker = (marker: IMarker | null) => {
|
|
|
|
|
setHoveredMarker(marker);
|
|
|
|
@@ -149,57 +111,6 @@ function Map({ maxZoom = 1 }: MapProps) {
|
|
|
|
|
if (marker !== null) setLastHoveredMarker(marker);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const cloudsTranslateX = useMotionValue(0);
|
|
|
|
|
// const cloudsTranslateY = useMotionValue(0);
|
|
|
|
|
|
|
|
|
|
const cloudsX = useTransform(
|
|
|
|
|
() =>
|
|
|
|
|
cloudsTranslateX.get() * zoom +
|
|
|
|
|
// *
|
|
|
|
|
// Math.cos(((-90 - windDirection) / 180) * Math.PI)
|
|
|
|
|
position.x
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const cloudsY = useTransform(
|
|
|
|
|
() => position.y
|
|
|
|
|
// +
|
|
|
|
|
// cloudsTranslateY.get() *
|
|
|
|
|
// zoom *
|
|
|
|
|
// Math.sin(((-90 - windDirection) / 180) * Math.PI)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!cloudsRef.current) return;
|
|
|
|
|
const animation = animate(
|
|
|
|
|
cloudsTranslateX,
|
|
|
|
|
[-cloudsRef.current.clientWidth, 0],
|
|
|
|
|
{
|
|
|
|
|
duration: Math.round(3000 / windSpeed),
|
|
|
|
|
repeat: Infinity,
|
|
|
|
|
repeatDelay: 0,
|
|
|
|
|
ease: "linear",
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return animation.stop;
|
|
|
|
|
}, [cloudsTranslateX, windSpeed]);
|
|
|
|
|
|
|
|
|
|
// useEffect(() => {
|
|
|
|
|
// if (!cloudsRef.current) return;
|
|
|
|
|
// const animation = animate(
|
|
|
|
|
// cloudsTranslateY,
|
|
|
|
|
// [-cloudsRef.current.clientHeight, 0],
|
|
|
|
|
// {
|
|
|
|
|
// duration: Math.round(30 / windSpeed),
|
|
|
|
|
// repeat: Infinity,
|
|
|
|
|
// repeatDelay: 0,
|
|
|
|
|
// ease: "linear",
|
|
|
|
|
// }
|
|
|
|
|
// );
|
|
|
|
|
|
|
|
|
|
// return animation.stop;
|
|
|
|
|
// }, [cloudsTranslateY, windSpeed]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!containerRef.current || !isImageLoaded || originalSize.width === 0)
|
|
|
|
|
return;
|
|
|
|
@@ -226,7 +137,6 @@ function Map({ maxZoom = 1 }: MapProps) {
|
|
|
|
|
function handleLoad() {
|
|
|
|
|
if (!mapRef.current || !containerRef.current) return;
|
|
|
|
|
|
|
|
|
|
// Создаем временное изображение для гарантированного получения размеров
|
|
|
|
|
const img = new Image();
|
|
|
|
|
img.src = mapRef.current.src;
|
|
|
|
|
|
|
|
|
@@ -238,7 +148,6 @@ function Map({ maxZoom = 1 }: MapProps) {
|
|
|
|
|
|
|
|
|
|
setOriginalSize(newOriginalSize);
|
|
|
|
|
|
|
|
|
|
// Рассчитываем минимальный зум после получения размеров
|
|
|
|
|
const containerRect = containerRef.current!.getBoundingClientRect();
|
|
|
|
|
const minZoom = calculateMinZoom(
|
|
|
|
|
{
|
|
|
|
@@ -254,14 +163,12 @@ function Map({ maxZoom = 1 }: MapProps) {
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update container size and min zoom on resize
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const updateContainerSize = () => {
|
|
|
|
|
if (!containerRef.current) return;
|
|
|
|
|
|
|
|
|
|
const { width, height } = containerRef.current.getBoundingClientRect();
|
|
|
|
|
|
|
|
|
|
// Check if container size actually changed
|
|
|
|
|
if (
|
|
|
|
|
width === containerSizeRef.current.width &&
|
|
|
|
|
height === containerSizeRef.current.height
|
|
|
|
@@ -270,11 +177,9 @@ function Map({ maxZoom = 1 }: MapProps) {
|
|
|
|
|
|
|
|
|
|
containerSizeRef.current = { width, height };
|
|
|
|
|
|
|
|
|
|
// Recalculate min zoom when container size changes
|
|
|
|
|
const newMinZoom = calculateMinZoom({ width, height }, originalSize);
|
|
|
|
|
minZoomRef.current = newMinZoom;
|
|
|
|
|
|
|
|
|
|
// Reset zoom to new minimum zoom
|
|
|
|
|
setZoom(newMinZoom);
|
|
|
|
|
|
|
|
|
|
const scaledWidth = originalSize.width * zoom;
|
|
|
|
@@ -299,9 +204,7 @@ function Map({ maxZoom = 1 }: MapProps) {
|
|
|
|
|
|
|
|
|
|
window.addEventListener("resize", updateContainerSize);
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
window.removeEventListener("resize", updateContainerSize);
|
|
|
|
|
};
|
|
|
|
|
return () => window.removeEventListener("resize", updateContainerSize);
|
|
|
|
|
}, [originalSize, zoom]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
@@ -332,7 +235,6 @@ function Map({ maxZoom = 1 }: MapProps) {
|
|
|
|
|
const zoomToPoint = (point: Position, targetZoom: number) => {
|
|
|
|
|
if (!containerRef.current) return;
|
|
|
|
|
|
|
|
|
|
// Ensure zoom is within bounds
|
|
|
|
|
const boundedZoom = Math.min(
|
|
|
|
|
maxZoom,
|
|
|
|
|
Math.max(minZoomRef.current, targetZoom)
|
|
|
|
@@ -364,7 +266,6 @@ function Map({ maxZoom = 1 }: MapProps) {
|
|
|
|
|
if (isShowInstruction) setIsShowInstruction(false);
|
|
|
|
|
|
|
|
|
|
if (e.touches.length === 2) {
|
|
|
|
|
// Для щипка сразу сохраняем начальную дистанцию
|
|
|
|
|
const touch1 = e.touches[0];
|
|
|
|
|
const touch2 = e.touches[1];
|
|
|
|
|
const distance = Math.hypot(
|
|
|
|
@@ -383,7 +284,7 @@ function Map({ maxZoom = 1 }: MapProps) {
|
|
|
|
|
if (!containerRef.current) return;
|
|
|
|
|
|
|
|
|
|
if (e.touches.length === 2) {
|
|
|
|
|
e.preventDefault(); // Предотвращаем скролл страницы при щипке
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
const touch1 = e.touches[0];
|
|
|
|
|
const touch2 = e.touches[1];
|
|
|
|
|
const distance = Math.hypot(
|
|
|
|
@@ -402,13 +303,11 @@ function Map({ maxZoom = 1 }: MapProps) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Увеличиваем порог изменения для более плавного зума
|
|
|
|
|
const distanceChange = Math.abs(distance - previousTouchDistance.current);
|
|
|
|
|
const changePercentage =
|
|
|
|
|
(distanceChange / previousTouchDistance.current) * 100;
|
|
|
|
|
|
|
|
|
|
if (changePercentage >= 5) {
|
|
|
|
|
// Уменьшаем порог с 10 до 5 для более отзывчивого зума
|
|
|
|
|
const zoomFactor = distance > previousTouchDistance.current ? 1.1 : 0.9;
|
|
|
|
|
const centerX = (touch1.clientX + touch2.clientX) / 2;
|
|
|
|
|
const centerY = (touch1.clientY + touch2.clientY) / 2;
|
|
|
|
@@ -418,7 +317,6 @@ function Map({ maxZoom = 1 }: MapProps) {
|
|
|
|
|
Math.max(minZoomRef.current, zoom * zoomFactor)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Проверяем, действительно ли изменился зум
|
|
|
|
|
if (Math.abs(newZoom - zoom) > 0.001) {
|
|
|
|
|
setZoom(newZoom);
|
|
|
|
|
|
|
|
|
@@ -442,7 +340,6 @@ function Map({ maxZoom = 1 }: MapProps) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Обработка перетаскивания одним пальцем только если не в режиме щипка
|
|
|
|
|
if (isDragging && e.touches.length === 1) {
|
|
|
|
|
const { x, y } = getEventPosition(e);
|
|
|
|
|
const newPosition = {
|
|
|
|
@@ -531,6 +428,30 @@ function Map({ maxZoom = 1 }: MapProps) {
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function smoothZoomTo(targetZoom: number, point: Position) {
|
|
|
|
|
const duration = 400;
|
|
|
|
|
const startZoom = zoom;
|
|
|
|
|
const startTime = performance.now();
|
|
|
|
|
|
|
|
|
|
function animateZoom(now: number) {
|
|
|
|
|
const elapsed = now - startTime;
|
|
|
|
|
const t = Math.min(elapsed / duration, 1);
|
|
|
|
|
const ease = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
|
|
|
|
|
const currentZoom = startZoom + (targetZoom - startZoom) * ease;
|
|
|
|
|
|
|
|
|
|
zoomToPoint(point, currentZoom);
|
|
|
|
|
|
|
|
|
|
if (t < 1) {
|
|
|
|
|
requestAnimationFrame(animateZoom);
|
|
|
|
|
} else {
|
|
|
|
|
setZoom(targetZoom);
|
|
|
|
|
zoomToPoint(point, targetZoom);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
requestAnimationFrame(animateZoom);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleClick = (
|
|
|
|
|
e: React.MouseEvent<HTMLElement> | React.TouchEvent<HTMLElement>
|
|
|
|
|
) => {
|
|
|
|
@@ -543,49 +464,11 @@ function Map({ maxZoom = 1 }: MapProps) {
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (currentTime - lastClickTime < 200) {
|
|
|
|
|
if (animationRef.current) animationRef.current.stop();
|
|
|
|
|
|
|
|
|
|
const targetZoom =
|
|
|
|
|
Math.abs(zoom - maxZoom) < 0.01 ? minZoomRef.current : maxZoom;
|
|
|
|
|
const point = getEventPosition(e);
|
|
|
|
|
|
|
|
|
|
animationRef.current = animate(zoom, targetZoom, {
|
|
|
|
|
bounce: 0,
|
|
|
|
|
onUpdate(prev) {
|
|
|
|
|
zoomToPoint(point, prev);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// if (animationRef.current) {
|
|
|
|
|
// cancelAnimationFrame(animationRef.current);
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// const targetZoom =
|
|
|
|
|
// Math.abs(zoom - maxZoom) < 0.01 ? minZoomRef.current : maxZoom;
|
|
|
|
|
// const point = getEventPosition(e);
|
|
|
|
|
|
|
|
|
|
// const duration = 300; // длительность анимации в мс
|
|
|
|
|
// const startZoom = zoom;
|
|
|
|
|
// const startTime = performance.now();
|
|
|
|
|
|
|
|
|
|
// function animateZoom(currentTime: number) {
|
|
|
|
|
// const elapsed = currentTime - startTime;
|
|
|
|
|
// const t = Math.min(elapsed / duration, 1); // от 0 до 1
|
|
|
|
|
|
|
|
|
|
// // Можно использовать ease-функцию, например, easeInOutQuad
|
|
|
|
|
// const ease = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
|
|
|
|
|
|
|
|
|
|
// const currentZoom = startZoom + (targetZoom - startZoom) * ease;
|
|
|
|
|
// zoomToPoint(point, currentZoom);
|
|
|
|
|
|
|
|
|
|
// if (t < 1) {
|
|
|
|
|
// animationRef.current = requestAnimationFrame(animateZoom);
|
|
|
|
|
// } else {
|
|
|
|
|
// animationRef.current = null;
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// animationRef.current = requestAnimationFrame(animateZoom);
|
|
|
|
|
smoothZoomTo(targetZoom, point);
|
|
|
|
|
|
|
|
|
|
setLastClickTime(0);
|
|
|
|
|
} else {
|
|
|
|
@@ -598,24 +481,18 @@ function Map({ maxZoom = 1 }: MapProps) {
|
|
|
|
|
passive: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
return () =>
|
|
|
|
|
containerRef.current?.removeEventListener("wheel", handleWheel);
|
|
|
|
|
};
|
|
|
|
|
}, [isDragging, position]);
|
|
|
|
|
|
|
|
|
|
// Разделяем стили трансформации для изображения и контейнера маркеров
|
|
|
|
|
const imageStyle = {
|
|
|
|
|
transform: `translateX(${position.x}px) translateY(${position.y}px) scale(${zoom})`,
|
|
|
|
|
transform: `translateX(${position.x}px) translateY(${position.y}px) translateZ(0px) scale(${zoom})`,
|
|
|
|
|
...originalSize,
|
|
|
|
|
transformOrigin: "0 0",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const cloudStyle = {
|
|
|
|
|
x: cloudsX,
|
|
|
|
|
y: cloudsY,
|
|
|
|
|
scale: zoom,
|
|
|
|
|
...originalSize,
|
|
|
|
|
transformOrigin: "0 0",
|
|
|
|
|
...imageStyle,
|
|
|
|
|
opacity: opacityCloud,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@@ -623,19 +500,10 @@ function Map({ maxZoom = 1 }: MapProps) {
|
|
|
|
|
getWeather().then((data) => {
|
|
|
|
|
setTemperature(Math.round(data.temperature2m));
|
|
|
|
|
setWindSpeed(Math.round(data.windSpeed180m));
|
|
|
|
|
setWindDirection(45);
|
|
|
|
|
// setWindDirection(Math.round(data.windDirection180m));
|
|
|
|
|
setWindDirection(Math.round(data.windDirection180m));
|
|
|
|
|
});
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
console.log(position);
|
|
|
|
|
}, [position]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
console.log("windDirection", windDirection);
|
|
|
|
|
}, [windDirection]);
|
|
|
|
|
|
|
|
|
|
const [isFullScreen, setIsFullScreen] = useState(false);
|
|
|
|
|
|
|
|
|
|
function handleFullScreenClick() {
|
|
|
|
@@ -645,20 +513,50 @@ function Map({ maxZoom = 1 }: MapProps) {
|
|
|
|
|
|
|
|
|
|
containerSizeRef.current = { width, height };
|
|
|
|
|
|
|
|
|
|
// Recalculate min zoom when container size changes
|
|
|
|
|
const newMinZoom = calculateMinZoom({ width, height }, originalSize);
|
|
|
|
|
minZoomRef.current = newMinZoom;
|
|
|
|
|
|
|
|
|
|
// Reset zoom to new minimum zoom
|
|
|
|
|
setZoom(newMinZoom);
|
|
|
|
|
setIsFullScreen((prev) => !prev);
|
|
|
|
|
if (isFullScreen) {
|
|
|
|
|
document.exitFullscreen();
|
|
|
|
|
} else {
|
|
|
|
|
containerRef.current.requestFullscreen();
|
|
|
|
|
}
|
|
|
|
|
if (isFullScreen) document.exitFullscreen();
|
|
|
|
|
else containerRef.current.requestFullscreen();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cloudAnimationRef = useRef<number | null>(null);
|
|
|
|
|
const [cloudOffset, setCloudOffset] = useState(0);
|
|
|
|
|
const [cloudImageWidth, setCloudImageWidth] = useState(0);
|
|
|
|
|
const cloudImgRef = useRef<HTMLImageElement>(null);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (cloudImgRef.current && cloudImgRef.current.complete) {
|
|
|
|
|
setCloudImageWidth(cloudImgRef.current.naturalWidth);
|
|
|
|
|
setCloudOffset(-cloudImgRef.current.naturalWidth);
|
|
|
|
|
}
|
|
|
|
|
}, [windowWidth, originalSize]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!cloudImageWidth) return;
|
|
|
|
|
let lastTimestamp = performance.now();
|
|
|
|
|
|
|
|
|
|
function animateClouds(now: number) {
|
|
|
|
|
const delta = (now - lastTimestamp) / 100;
|
|
|
|
|
lastTimestamp = now;
|
|
|
|
|
setCloudOffset((prev) => {
|
|
|
|
|
let next = prev + (windSpeed * delta) / 10;
|
|
|
|
|
if (cloudImageWidth > 0 && next > 0) next -= cloudImageWidth;
|
|
|
|
|
return next;
|
|
|
|
|
});
|
|
|
|
|
cloudAnimationRef.current = requestAnimationFrame(animateClouds);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cloudAnimationRef.current = requestAnimationFrame(animateClouds);
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
if (cloudAnimationRef.current)
|
|
|
|
|
cancelAnimationFrame(cloudAnimationRef.current);
|
|
|
|
|
};
|
|
|
|
|
}, [windSpeed, cloudImageWidth]);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
ref={containerRef}
|
|
|
|
@@ -692,28 +590,6 @@ function Map({ maxZoom = 1 }: MapProps) {
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<motion.div
|
|
|
|
|
style={cloudStyle}
|
|
|
|
|
ref={cloudsRef}
|
|
|
|
|
className={clsx(
|
|
|
|
|
`absolute w-full h-full pointer-events-none flex transition-[opacity] will-change-[opacity,scale,translate,transform]`,
|
|
|
|
|
hoveredMarker && "opacity-80"
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
<img
|
|
|
|
|
src={`/images/map/clouds-${
|
|
|
|
|
windowWidth < 768 ? "mobile" : "desktop"
|
|
|
|
|
}.png`}
|
|
|
|
|
className="w-full"
|
|
|
|
|
/>
|
|
|
|
|
<img
|
|
|
|
|
src={`/images/map/clouds-${
|
|
|
|
|
windowWidth < 768 ? "mobile" : "desktop"
|
|
|
|
|
}.png`}
|
|
|
|
|
className="w-full"
|
|
|
|
|
/>
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
className={clsx(
|
|
|
|
|
"pointer-events-none absolute max-w-none transition-opacity duration-300 bg-black",
|
|
|
|
@@ -722,6 +598,37 @@ function Map({ maxZoom = 1 }: MapProps) {
|
|
|
|
|
style={imageStyle}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
style={cloudStyle}
|
|
|
|
|
ref={cloudsRef}
|
|
|
|
|
className={clsx(
|
|
|
|
|
`absolute w-full h-full pointer-events-none transition-opacity duration-300 will-change-[opacity,scale,translate,transform]`,
|
|
|
|
|
hoveredMarker && "opacity-80"
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
className="h-full flex"
|
|
|
|
|
style={{
|
|
|
|
|
rotate: `${90 + windDirection}deg`,
|
|
|
|
|
transform: `translateX(${Math.round(
|
|
|
|
|
cloudOffset
|
|
|
|
|
)}px) translateZ(0px)`,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<img
|
|
|
|
|
ref={cloudImgRef}
|
|
|
|
|
src={`/images/map/clouds-${
|
|
|
|
|
windowWidth < 768 ? "mobile" : "desktop"
|
|
|
|
|
}.png`}
|
|
|
|
|
/>
|
|
|
|
|
<img
|
|
|
|
|
src={`/images/map/clouds-${
|
|
|
|
|
windowWidth < 768 ? "mobile" : "desktop"
|
|
|
|
|
}.png`}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div ref={markersContainerRef} className="absolute" style={imageStyle}>
|
|
|
|
|
<div className="relative h-full">
|
|
|
|
|
{markers.map((marker) => (
|
|
|
|
@@ -746,7 +653,7 @@ function Map({ maxZoom = 1 }: MapProps) {
|
|
|
|
|
initial={{ opacity: 0 }}
|
|
|
|
|
animate={{ opacity: 1 }}
|
|
|
|
|
exit={{ opacity: 0 }}
|
|
|
|
|
className="absolute inset-0 z-30 flex items-center justify-center pointer-events-none"
|
|
|
|
|
className="absolute inset-0 flex items-center justify-center pointer-events-none"
|
|
|
|
|
>
|
|
|
|
|
<div className="w-fit bg-[#0D1922]/40 rounded-lg backdrop-blur-sm space-y-3 p-4 text-white">
|
|
|
|
|
<div className="flex items-center justify-center gap-4">
|
|
|
|
|