mirror of
https://github.com/LukeHagar/website.git
synced 2025-12-06 21:07:44 +00:00
update the map
This commit is contained in:
40
src/lib/actions/mouse-position.svelte.ts
Normal file
40
src/lib/actions/mouse-position.svelte.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { hover } from 'motion';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export interface Position {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export const useMousePosition = () => {
|
||||
let position = $state<Position>({
|
||||
x: 0,
|
||||
y: 0
|
||||
});
|
||||
|
||||
const handleMouseMove = (event: MouseEvent) => {
|
||||
position = {
|
||||
x: event.offsetX,
|
||||
y: event.offsetY
|
||||
};
|
||||
};
|
||||
|
||||
const action = (node: HTMLElement | SVGSVGElement) => {
|
||||
hover(node, () => {
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
});
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
action,
|
||||
position: () => {
|
||||
return position;
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,34 +0,0 @@
|
||||
import { hover } from 'motion';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const useMousePosition = () => {
|
||||
let position = writable<{ x: number; y: number }>({
|
||||
x: 0,
|
||||
y: 0
|
||||
});
|
||||
|
||||
const action = (node: HTMLElement) => {
|
||||
const handleMouseMove = (event: MouseEvent) => {
|
||||
// Get the bounding rectangle of the element
|
||||
const rect = node.getBoundingClientRect();
|
||||
|
||||
// Calculate position relative to the element
|
||||
position.set({
|
||||
x: event.clientX - rect.left,
|
||||
y: event.clientY - rect.top
|
||||
});
|
||||
};
|
||||
|
||||
hover(node, () => {
|
||||
node.addEventListener('mousemove', handleMouseMove);
|
||||
});
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
node.removeEventListener('mousemove', handleMouseMove);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
return { action, position };
|
||||
};
|
||||
@@ -68,18 +68,20 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="pointer-events-none absolute z-10 hidden md:block">
|
||||
<div class="pointer-events-none absolute z-100 hidden md:block">
|
||||
{#if tooltipData.city}
|
||||
<div
|
||||
class={classNames(
|
||||
'border-gradient relative z-100 flex w-[190px] flex-col gap-2 rounded-[10px] p-2 backdrop-blur-lg before:rounded-[10px] after:rounded-[10px]'
|
||||
)}
|
||||
style:transform={`translateX(${x + 125}px) translateY(${y + 200}px)`}
|
||||
style:transform={`translateX(${x + 250}px) translateY(${y - 620}px)`}
|
||||
>
|
||||
<span class="text-primary text-caption w-fit">
|
||||
{#key tooltipData.city}
|
||||
<span class="text-primary text-caption w-fit" bind:this={city}>
|
||||
{tooltipData.city}
|
||||
({tooltipData.code})
|
||||
</span>
|
||||
{/key}
|
||||
{#if tooltipData.available}
|
||||
<div
|
||||
class="text-caption flex h-5 items-center justify-center place-self-start rounded-md bg-[#10B981]/24 p-1 text-center text-[#B4F8E2]"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { slugify } from '$lib/utils/slugify';
|
||||
import { classNames } from '$lib/utils/classnames';
|
||||
import MapNav from './map-nav.svelte';
|
||||
import { useMousePosition } from '$lib/actions/mouse-position';
|
||||
import { useMousePosition } from '$lib/actions/mouse-position.svelte';
|
||||
import { useAnimateInView } from '$lib/actions/animate-in-view';
|
||||
import { pins, type PinSegment } from './data/pins';
|
||||
import MapTooltip, {
|
||||
@@ -75,17 +75,13 @@
|
||||
height,
|
||||
markers: getMarkers(),
|
||||
skew: 1,
|
||||
baseColor: '#dadadd',
|
||||
baseColor: 'rgba(255,255,255,.1)',
|
||||
markerColor: 'var(--color-accent)'
|
||||
});
|
||||
});
|
||||
|
||||
type Props = { theme: 'light' | 'dark' };
|
||||
|
||||
const { theme = 'dark' }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="-mt-8 w-full overflow-x-scroll [scrollbar-width:none] md:overflow-x-hidden">
|
||||
<div class="relative -mt-8 w-full overflow-x-scroll [scrollbar-width:none]">
|
||||
<div
|
||||
class="sticky left-0 mx-auto block max-w-[calc(100vw_-_calc(var(--spacing)_*-2))] md:hidden"
|
||||
>
|
||||
@@ -99,19 +95,15 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="relative mx-auto h-full w-[250vw] [scrollbar-width:none] md:w-full" use:inView>
|
||||
<div
|
||||
class="relative mx-auto h-full w-[250vw] [scrollbar-width:none] md:w-full"
|
||||
use:inView
|
||||
use:mousePosition
|
||||
class="relative mx-auto h-fit w-full max-w-5xl origin-bottom transform-[perspective(25px)_rotateX(1deg)_scale3d(1.4,_1.4,_1)] transition-all [scrollbar-width:none]"
|
||||
>
|
||||
<div
|
||||
class="relative w-full origin-bottom transform-[perspective(25px)_rotateX(1deg)_scale3d(1.4,_1.4,_1)] transition-all [scrollbar-width:none]"
|
||||
>
|
||||
<svg viewBox={`0 0 ${height * 2} ${height}`}>
|
||||
<svg viewBox={`0 0 ${height * 2} ${height}`} use:mousePosition>
|
||||
{#each map.points as point}
|
||||
<circle cx={point.x} cy={point.y} r={point.size} fill={point.color} />
|
||||
{/each}
|
||||
<!-- {#each map.markers as marker}
|
||||
{#each map.markers as marker}
|
||||
<g
|
||||
role="tooltip"
|
||||
class="animate-fade-in outline-none"
|
||||
@@ -123,15 +115,25 @@
|
||||
onmouseout={() => handleResetActiveTooltip(250)}
|
||||
data-region={slugify(marker.city)}
|
||||
>
|
||||
<circle cx={marker.x} cy={marker.y} r={marker.size} fill={marker.color} />
|
||||
<circle cx={marker.x} cy={marker.y} fill="white" />
|
||||
<circle
|
||||
cx={marker.x}
|
||||
cy={marker.y}
|
||||
r={marker.size * 1.5}
|
||||
fill={marker.color}
|
||||
/>
|
||||
<circle cx={marker.x} cy={marker.y} r={marker.size * 0.5} fill="white" />
|
||||
<circle
|
||||
cx={marker.x}
|
||||
cy={marker.y}
|
||||
r={marker.size * 4}
|
||||
fill="transparent"
|
||||
/>
|
||||
</g>
|
||||
{/each} -->
|
||||
{/each}
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<MapTooltip {...position()} />
|
||||
</div>
|
||||
|
||||
<MapTooltip {...$position} />
|
||||
|
||||
<MapNav {theme} onValueChange={(value) => (activeSegment = value)} />
|
||||
<MapNav onValueChange={(value) => (activeSegment = value)} />
|
||||
|
||||
@@ -24,31 +24,39 @@ export const createMap = ({
|
||||
|
||||
const defaultRadius = 0.35;
|
||||
|
||||
// Use the same exact constants as getMapPoints
|
||||
const TARGET_POINTS = 6000;
|
||||
const aspect = width / height;
|
||||
const NUM_ROWS = Math.round(Math.sqrt(TARGET_POINTS / aspect));
|
||||
const NUM_COLS = Math.round(NUM_ROWS * aspect);
|
||||
|
||||
const markerPoints = markers.map((marker) => {
|
||||
const { lat, lng, size, ...markerData } = marker;
|
||||
const [googleX, googleY] = proj4('GOOGLE', [lng, lat]);
|
||||
const rawY = (mapHeight * (Y_MAX - googleY)) / Y_RANGE;
|
||||
const rawX = (mapWidth * (googleX - X_MIN)) / X_RANGE;
|
||||
const y = Math.round(rawY / ystep);
|
||||
const x = Math.round(rawX);
|
||||
const localy = Math.round(y) * ystep;
|
||||
const localx = x;
|
||||
|
||||
const key = [localx, localy].join(';');
|
||||
if (!points[key]) {
|
||||
const [localLng, localLat] = proj4('GOOGLE', 'WGS84', [
|
||||
(localx * X_RANGE) / mapWidth + X_MIN,
|
||||
Y_MAX - (localy * Y_RANGE) / mapHeight
|
||||
]);
|
||||
points[key] = {
|
||||
x: localx,
|
||||
y: localy
|
||||
// Map projected coordinates to pixel space (same as getMapPoints)
|
||||
const localx = ((googleX - X_MIN) / X_RANGE) * width;
|
||||
const localy = ((Y_MAX - googleY) / Y_RANGE) * height;
|
||||
|
||||
// Round exactly as getMapPoints does
|
||||
const roundedX = Math.round(localx);
|
||||
const roundedY = Math.round(localy);
|
||||
|
||||
// Create a key for this point
|
||||
const exactKey = `${roundedX};${roundedY}`;
|
||||
|
||||
// Create the point if it doesn't exist
|
||||
if (!points[exactKey]) {
|
||||
points[exactKey] = {
|
||||
x: roundedX,
|
||||
y: roundedY
|
||||
};
|
||||
}
|
||||
|
||||
// Use these exact coordinates directly
|
||||
return {
|
||||
x: localx,
|
||||
y: localy,
|
||||
x: roundedX,
|
||||
y: roundedY,
|
||||
color: markerColor,
|
||||
size: size ?? defaultRadius,
|
||||
...markerData
|
||||
|
||||
Reference in New Issue
Block a user