your personal website on atproto - mirror
blento.app
1<script lang="ts">
2 import { onMount } from 'svelte';
3 import 'mapbox-gl/dist/mapbox-gl.css';
4 import mapboxgl from 'mapbox-gl';
5 import { env } from '$env/dynamic/public';
6 import type { Item } from '$lib/types';
7 import { getHexOfCardColor } from '../helper';
8
9 let { item = $bindable() }: { item: Item } = $props();
10
11 // $inspect(item);
12
13 let mapContainer: HTMLElement | undefined = $state();
14 let map: mapboxgl.Map | undefined = $state();
15
16 onMount(() => {
17 if (!mapContainer || !env.PUBLIC_MAPBOX_TOKEN) {
18 console.log('no map container or no mapbox token');
19 return;
20 }
21
22 try {
23 mapboxgl.accessToken = env.PUBLIC_MAPBOX_TOKEN;
24
25 const lat = parseFloat(item.cardData.lat);
26 const lon = parseFloat(item.cardData.lon);
27 const zoom = item.cardData.zoom ? parseFloat(item.cardData.zoom) : 0;
28 const lightPreset = item.cardData.lightPreset || 'day';
29
30 map = new mapboxgl.Map({
31 container: mapContainer,
32 style: 'mapbox://styles/mapbox/standard',
33 center: [lon, lat],
34 config: {
35 basemap: {
36 lightPreset: lightPreset,
37 showPointOfInterestLabels: false
38 }
39 },
40 zoom: zoom,
41 attributionControl: false,
42 dragPan: false,
43 dragRotate: false,
44 keyboard: false,
45 doubleClickZoom: true,
46 touchZoomRotate: true,
47 scrollZoom: true,
48 boxZoom: false,
49 pitchWithRotate: false,
50 touchPitch: false
51 });
52
53 // Keep location centered during zoom and save zoom level
54 map.on('zoom', () => {
55 if (map) {
56 map.setCenter([lon, lat]);
57 }
58 });
59
60 map.on('zoomend', () => {
61 if (map) {
62 item.cardData.zoom = map.getZoom().toString();
63 }
64 });
65
66 map.on('load', () => {
67 if (!map) return;
68
69 map.resize();
70 map.setCenter([lon, lat]);
71
72 const accentColor = getHexOfCardColor(item);
73
74 // Add location point source
75 map.addSource('location-point', {
76 type: 'geojson',
77 data: {
78 type: 'Feature',
79 geometry: {
80 type: 'Point',
81 coordinates: [lon, lat]
82 },
83 properties: {
84 name: item.cardData.name || ''
85 }
86 }
87 });
88
89 // Outer glow
90 map.addLayer({
91 id: 'location-glow-outer',
92 type: 'circle',
93 source: 'location-point',
94 paint: {
95 'circle-radius': 20,
96 'circle-color': accentColor,
97 'circle-opacity': 0.15,
98 'circle-blur': 1
99 }
100 });
101
102 // Middle glow
103 map.addLayer({
104 id: 'location-glow-middle',
105 type: 'circle',
106 source: 'location-point',
107 paint: {
108 'circle-radius': 12,
109 'circle-color': accentColor,
110 'circle-opacity': 0.3,
111 'circle-blur': 0.5
112 }
113 });
114
115 // White border
116 map.addLayer({
117 id: 'location-dot-border',
118 type: 'circle',
119 source: 'location-point',
120 paint: {
121 'circle-radius': 8,
122 'circle-color': '#ffffff',
123 'circle-opacity': 1
124 }
125 });
126
127 // Accent color center dot
128 map.addLayer({
129 id: 'location-dot',
130 type: 'circle',
131 source: 'location-point',
132 paint: {
133 'circle-radius': 6,
134 'circle-color': accentColor,
135 'circle-opacity': 1
136 }
137 });
138 });
139
140 // Handle container resize
141 const resizeObserver = new ResizeObserver(() => {
142 if (map) {
143 map.resize();
144 map.setCenter([lon, lat]);
145 }
146 });
147 if (mapContainer) resizeObserver.observe(mapContainer);
148
149 return () => {
150 resizeObserver.disconnect();
151 if (map) {
152 map.remove();
153 }
154 };
155 } catch (err) {
156 console.error(`Something went wrong trying to initialize the map`, err);
157 }
158 });
159</script>
160
161<div bind:this={mapContainer} class="absolute inset-0 isolate h-full w-full"></div>