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>
|
</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}
|
{#if tooltipData.city}
|
||||||
<div
|
<div
|
||||||
class={classNames(
|
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]'
|
'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}
|
||||||
{tooltipData.city}
|
<span class="text-primary text-caption w-fit" bind:this={city}>
|
||||||
({tooltipData.code})
|
{tooltipData.city}
|
||||||
</span>
|
({tooltipData.code})
|
||||||
|
</span>
|
||||||
|
{/key}
|
||||||
{#if tooltipData.available}
|
{#if tooltipData.available}
|
||||||
<div
|
<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]"
|
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 { slugify } from '$lib/utils/slugify';
|
||||||
import { classNames } from '$lib/utils/classnames';
|
import { classNames } from '$lib/utils/classnames';
|
||||||
import MapNav from './map-nav.svelte';
|
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 { useAnimateInView } from '$lib/actions/animate-in-view';
|
||||||
import { pins, type PinSegment } from './data/pins';
|
import { pins, type PinSegment } from './data/pins';
|
||||||
import MapTooltip, {
|
import MapTooltip, {
|
||||||
@@ -75,17 +75,13 @@
|
|||||||
height,
|
height,
|
||||||
markers: getMarkers(),
|
markers: getMarkers(),
|
||||||
skew: 1,
|
skew: 1,
|
||||||
baseColor: '#dadadd',
|
baseColor: 'rgba(255,255,255,.1)',
|
||||||
markerColor: 'var(--color-accent)'
|
markerColor: 'var(--color-accent)'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
type Props = { theme: 'light' | 'dark' };
|
|
||||||
|
|
||||||
const { theme = 'dark' }: Props = $props();
|
|
||||||
</script>
|
</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
|
<div
|
||||||
class="sticky left-0 mx-auto block max-w-[calc(100vw_-_calc(var(--spacing)_*-2))] md:hidden"
|
class="sticky left-0 mx-auto block max-w-[calc(100vw_-_calc(var(--spacing)_*-2))] md:hidden"
|
||||||
>
|
>
|
||||||
@@ -99,19 +95,15 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div class="relative mx-auto h-full w-[250vw] [scrollbar-width:none] md:w-full" use:inView>
|
||||||
class="relative mx-auto h-full w-[250vw] [scrollbar-width:none] md:w-full"
|
|
||||||
use:inView
|
|
||||||
use:mousePosition
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
class="relative w-full origin-bottom transform-[perspective(25px)_rotateX(1deg)_scale3d(1.4,_1.4,_1)] transition-all [scrollbar-width:none]"
|
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]"
|
||||||
>
|
>
|
||||||
<svg viewBox={`0 0 ${height * 2} ${height}`}>
|
<svg viewBox={`0 0 ${height * 2} ${height}`} use:mousePosition>
|
||||||
{#each map.points as point}
|
{#each map.points as point}
|
||||||
<circle cx={point.x} cy={point.y} r={point.size} fill={point.color} />
|
<circle cx={point.x} cy={point.y} r={point.size} fill={point.color} />
|
||||||
{/each}
|
{/each}
|
||||||
<!-- {#each map.markers as marker}
|
{#each map.markers as marker}
|
||||||
<g
|
<g
|
||||||
role="tooltip"
|
role="tooltip"
|
||||||
class="animate-fade-in outline-none"
|
class="animate-fade-in outline-none"
|
||||||
@@ -123,15 +115,25 @@
|
|||||||
onmouseout={() => handleResetActiveTooltip(250)}
|
onmouseout={() => handleResetActiveTooltip(250)}
|
||||||
data-region={slugify(marker.city)}
|
data-region={slugify(marker.city)}
|
||||||
>
|
>
|
||||||
<circle cx={marker.x} cy={marker.y} r={marker.size} fill={marker.color} />
|
<circle
|
||||||
<circle cx={marker.x} cy={marker.y} fill="white" />
|
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>
|
</g>
|
||||||
{/each} -->
|
{/each}
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<MapTooltip {...position()} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MapTooltip {...$position} />
|
<MapNav onValueChange={(value) => (activeSegment = value)} />
|
||||||
|
|
||||||
<MapNav {theme} onValueChange={(value) => (activeSegment = value)} />
|
|
||||||
|
|||||||
@@ -24,31 +24,39 @@ export const createMap = ({
|
|||||||
|
|
||||||
const defaultRadius = 0.35;
|
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 markerPoints = markers.map((marker) => {
|
||||||
const { lat, lng, size, ...markerData } = marker;
|
const { lat, lng, size, ...markerData } = marker;
|
||||||
const [googleX, googleY] = proj4('GOOGLE', [lng, lat]);
|
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(';');
|
// Map projected coordinates to pixel space (same as getMapPoints)
|
||||||
if (!points[key]) {
|
const localx = ((googleX - X_MIN) / X_RANGE) * width;
|
||||||
const [localLng, localLat] = proj4('GOOGLE', 'WGS84', [
|
const localy = ((Y_MAX - googleY) / Y_RANGE) * height;
|
||||||
(localx * X_RANGE) / mapWidth + X_MIN,
|
|
||||||
Y_MAX - (localy * Y_RANGE) / mapHeight
|
// Round exactly as getMapPoints does
|
||||||
]);
|
const roundedX = Math.round(localx);
|
||||||
points[key] = {
|
const roundedY = Math.round(localy);
|
||||||
x: localx,
|
|
||||||
y: 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 {
|
return {
|
||||||
x: localx,
|
x: roundedX,
|
||||||
y: localy,
|
y: roundedY,
|
||||||
color: markerColor,
|
color: markerColor,
|
||||||
size: size ?? defaultRadius,
|
size: size ?? defaultRadius,
|
||||||
...markerData
|
...markerData
|
||||||
|
|||||||
Reference in New Issue
Block a user