Use new Leaflet map

This commit is contained in:
Gus Ruben 2025-07-22 15:32:56 -04:00
parent 8fff005ae5
commit 82fd1aaa6d
6 changed files with 156 additions and 6 deletions

View file

@ -7,8 +7,10 @@
"@fontsource/atkinson-hyperlegible": "^5.2.6",
"@sveltejs/adapter-node": "^5.2.13",
"@sveltejs/adapter-static": "^3.0.8",
"@types/leaflet": "^1.9.20",
"airtable": "^0.12.2",
"gsap": "^3.13.0",
"leaflet": "^1.9.4",
"lenis": "^1.3.4",
"winston": "^3.17.0",
},
@ -196,6 +198,10 @@
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="],
"@types/leaflet": ["@types/leaflet@1.9.20", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-rooalPMlk61LCaLOvBF2VIf9M47HgMQqi5xQ9QRi7c8PkdIe0WrIi5IxXUXQjAdL0c+vcQ01mYWbthzmp9GHWw=="],
"@types/node": ["@types/node@14.18.63", "", {}, "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="],
"@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="],
@ -292,6 +298,8 @@
"kuler": ["kuler@2.0.0", "", {}, "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="],
"leaflet": ["leaflet@1.9.4", "", {}, "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="],
"lenis": ["lenis@1.3.4", "", { "peerDependencies": { "@nuxt/kit": ">=3.0.0", "react": ">=17.0.0", "vue": ">=3.0.0" }, "optionalPeers": ["@nuxt/kit", "react", "vue"] }, "sha512-WIGk8wiV2ABm/T7M+NC+tAV8fjzNJD1J4z11aZ3mTtx7WAZX/4QdCNhBO0g/TqXISA+/3hTbzrPC4FW1nhoNMQ=="],
"lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="],

View file

@ -30,8 +30,10 @@
"@fontsource/atkinson-hyperlegible": "^5.2.6",
"@sveltejs/adapter-node": "^5.2.13",
"@sveltejs/adapter-static": "^3.0.8",
"@types/leaflet": "^1.9.20",
"airtable": "^0.12.2",
"gsap": "^3.13.0",
"leaflet": "^1.9.4",
"lenis": "^1.3.4",
"winston": "^3.17.0"
}

View file

@ -0,0 +1,77 @@
<script lang="ts">
import { onMount } from 'svelte';
import { browser } from '$app/environment';
let mapContainer: HTMLElement;
let map: any;
onMount(async () => {
if (browser) {
// Dynamically import Leaflet to avoid SSR issues
const L = await import('leaflet');
// Load CSS
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css';
document.head.appendChild(link);
// Add custom popup styles
const style = document.createElement('style');
style.textContent = `
.leaflet-tooltip.custom-tooltip {
background: #FFFBDF !important;
border: 1px solid #D3B180 !important;
box-shadow: 0 3px 14px rgba(211, 177, 128, 0.15) !important;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
color: #78531D !important;
}
.leaflet-tooltip.custom-tooltip:before {
border-top-color: #D3B180 !important;
}
`;
document.head.appendChild(style);
// Initialize map
map = L.map(mapContainer).setView([20, 0], 2);
// Add tile layer
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
// Fetch and add event locations
try {
const response = await fetch('/api/map-data');
if (response.ok) {
const locations = await response.json();
// Create custom icon
const customIcon = L.icon({
iconUrl: '/map-flag.png',
iconSize: [32, 41],
iconAnchor: [16, 41],
popupAnchor: [1, -34]
});
// Add markers for each location
locations.forEach((location: any) => {
if (location.lat && location.lng) {
L.marker([location.lat, location.lng], { icon: customIcon })
.bindTooltip(`<strong>${location.event_name}</strong>`, {
permanent: false,
direction: 'top',
className: 'custom-tooltip'
})
.addTo(map);
}
});
}
} catch (error) {
console.error('Failed to load map data:', error);
}
}
});
</script>
<div bind:this={mapContainer} class="w-full h-full"></div>

View file

@ -2,6 +2,7 @@
import { onMount } from "svelte";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import MapComponent from "../components/MapComponent.svelte";
/** @type {import('./$types').PageData} */
export let data;
@ -883,13 +884,11 @@ Mumbai`.split("\n")
<div class="relative w-full max-w-3xl mx-auto min-w-72 max-md:mx-0">
<img src="banner.png" alt="100 Cities Worldwide" class="absolute top-0 left-1/2 -translate-x-1/2 max-md:-translate-y-1/2 max-sm:translate-y-[calc(-50%-4rem)] h-48 w-auto z-100 scale-150 saturate-70 brightness-110 object-contain px-4 pointer-events-none">
<img src="hole.png" alt="" class="w-full h-full max-w-3xl max-sm:scale-200 pointer-events-none">
<iframe
src="https://felt.com/embed/map/Daydream-Events-pPFQnT34SOq6tYlb2S1IdC?loc=0%2C-73.3%2C1.7z&legend=0&cooperativeGestures=1&link=0&geolocation=0&zoomControls=1&scaleBar=0"
<div
class="absolute top-0 left-0 w-full h-full border-0 max-sm:scale-200"
style="mask: url('hole.png') no-repeat center; -webkit-mask: url('hole.png') no-repeat center; mask-size: contain; -webkit-mask-size: contain;"
title="Felt Map"
referrerpolicy="strict-origin-when-cross-origin">
</iframe>
style="mask: url('hole.png') no-repeat center; -webkit-mask: url('hole.png') no-repeat center; mask-size: contain; -webkit-mask-size: contain;">
<MapComponent />
</div>
<p class="absolute left-1/2 -translate-x-1/2 font-sans text-center text-2xl pt-12 max-sm:pt-40 max-sm:text-xl w-max max-w-[80vh] max-md:max-w-full md:px-12 text-[#60574b] z-10000 ">All daydream events are organized by high school students like yourself! <br> <span class="font-bold"><a class="underline hover:text-pink" href="https://forms.hackclub.com/daydream">Sign up</a> to organize now!</span></p>
</div>
</div>

View file

@ -0,0 +1,64 @@
import { AIRTABLE_API_KEY, AIRTABLE_BASE_ID, GEOCODER_API_KEY } from '$env/static/private';
import { json } from '@sveltejs/kit';
export const prerender = true;
/** @type {import('./$types').RequestHandler} */
export async function GET() {
if (!AIRTABLE_API_KEY || !AIRTABLE_BASE_ID || !GEOCODER_API_KEY) {
return json({ error: 'Missing required environment variables' }, { status: 500 });
}
try {
// Fetch approved events from Airtable
const airtableUrl = `https://api.airtable.com/v0/${AIRTABLE_BASE_ID}/events?filterByFormula={triage_status}="Approved"`;
const airtableResponse = await fetch(airtableUrl, {
headers: {
'Authorization': `Bearer ${AIRTABLE_API_KEY}`
}
});
if (!airtableResponse.ok) {
throw new Error(`Airtable API error: ${airtableResponse.status}`);
}
const airtableData = await airtableResponse.json();
const events = airtableData.records;
// Geocode each event location
const locations = [];
for (const event of events) {
const { city, state, country, event_name } = event.fields;
if (!city || !event_name) continue;
// Build address string
const addressParts = [city, state, country].filter(Boolean);
const address = addressParts.join(', ');
try {
const geocodeUrl = `https://geocoder.hackclub.com/v1/geocode?address=${encodeURIComponent(address)}&key=${GEOCODER_API_KEY}`;
const geocodeResponse = await fetch(geocodeUrl);
if (geocodeResponse.ok) {
const geocodeData = await geocodeResponse.json();
locations.push({
lat: geocodeData.lat,
lng: geocodeData.lng,
event_name,
city,
state,
country
});
}
} catch (error) {
console.error(`Failed to geocode ${address}:`, error);
}
}
return json(locations);
} catch (error) {
console.error('Failed to fetch map data:', error);
return json({ error: 'Failed to fetch map data' }, { status: 500 });
}
}

BIN
static/map-flag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B