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