add world map @ home page

This commit is contained in:
adeliptr 2025-05-29 14:58:46 +09:00
parent 595e984b12
commit 21ff12f6b8
6 changed files with 502 additions and 1016 deletions

1361
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,9 @@
"@sveltejs/adapter-auto": "^6.0.0",
"@sveltejs/kit": "^2.16.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@types/d3": "^7.4.3",
"@types/topojson-client": "^3.1.5",
"@types/topojson-server": "^3.0.4",
"nodemon": "^3.1.10",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
@ -23,6 +26,7 @@
},
"dependencies": {
"d3": "^7.9.0",
"world-map": "^0.0.9"
"topojson-client": "^3.1.0",
"topojson-server": "^3.0.1"
}
}

View File

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<link rel="stylesheet" href="./app.css" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>

View File

@ -0,0 +1,114 @@
<script lang="ts">
import { onMount } from 'svelte';
import * as d3 from 'd3';
import { feature } from 'topojson-client';
import { Colors } from '../constants/Colors';
import '../../app.css';
let mapContainer: HTMLDivElement;
onMount(() => {
const width = mapContainer.clientWidth;
const height = mapContainer.clientHeight;
// Create SVG
const svg = d3.select(mapContainer)
.append('svg')
.attr('width', '100%')
.attr('height', '100%')
.attr('viewBox', `0 0 ${width} ${height}`) // make a coordinate from (0, 0) to (width, height)
.attr('preserveAspectRatio', 'xMidYMid meet') as d3.Selection<SVGSVGElement, unknown, null, undefined>; // center the map
// Add a group for all map elements that will be transformed
const g = svg.append('g');
// Create projection
const projection = d3.geoMercator()
.scale(width / (2 * Math.PI))
.translate([width / 2, height / 1.6]); // position the map, horizontally centered but is slighty upward
const path = d3.geoPath().projection(projection);
// Tokyo coordinates [longitude, latitude]
const tokyo: [number, number] = [139.6917, 35.6895];
const initMap = async () => {
try {
// Load world map data
const response = await fetch('https://unpkg.com/world-atlas@2/countries-110m.json');
const world = await response.json();
// Convert TopoJSON to GeoJSON
const countries = feature(world, world.objects.countries) as any;
// Draw the map
g.append('g')
.selectAll('path')
.data(countries.features)
.enter()
.append('path')
.attr('d', path as any)
.attr('class', 'country')
.attr('fill', Colors.gray.light200)
.attr('stroke', Colors.gray.light50)
.attr('stroke-width', '0.5');
// Add Tokyo marker
g.append('circle')
.attr('cx', projection(tokyo)![0])
.attr('cy', projection(tokyo)![1])
.attr('r', 5)
.attr('class', 'marker')
.attr('fill', Colors.planner.med400);
// Add zoom behavior
const zoom = d3.zoom<SVGSVGElement, unknown>()
.scaleExtent([1, 8])
.on('zoom', (event) => {
g.attr('transform', event.transform);
});
svg.call(zoom)
.call(zoom.transform, d3.zoomIdentity);
} catch (error) {
console.error('Error loading map:', error);
}
};
initMap();
return () => {
d3.select(mapContainer).selectAll('*').remove();
};
});
</script>
<div bind:this={mapContainer} class="map-wrapper"></div>
<style>
.map-wrapper {
width: 100%;
height: 100%;
background-color: var(--gray-50);
overflow: hidden;
touch-action: none;
}
:global(.country) {
transition: fill 0.2s ease;
}
:global(.country:hover) {
fill: #a1cdd2;
}
:global(.marker) {
transition: all 0.2s ease;
pointer-events: all;
}
:global(.marker:hover) {
r: 8;
cursor: pointer;
}
</style>

View File

@ -1,6 +1,7 @@
<script>
// import WorldMap from '$lib/components/WorldMap.svelte';
import WorldMap from '$lib/components/WorldMap.svelte';
import { goto } from '$app/navigation';
import '../app.css';
let title = "Travel App";
let activeTab = "Planner";
@ -27,13 +28,15 @@
</button>
</div>
<div class="profile">
<button class="profile-btn">👤</button>
<button class="profile-btn">
<img src="/user.png" alt="" class="profile-pic"/>
</button>
</div>
</div>
</nav>
<div class="map-container">
<!-- <WorldMap /> -->
<WorldMap />
</div>
<div class="bottom-bar">
@ -50,7 +53,7 @@
height: 100vh;
display: flex;
flex-direction: column;
background-color: #F0F0F0;
background-color: var(--gray-50);
font-family: 'Inter';
}
@ -71,7 +74,7 @@
.right-nav {
display: flex;
align-items: center;
gap: 2rem;
gap: 1.5rem;
}
.menu {
@ -85,7 +88,7 @@
font-size: 1rem;
cursor: pointer;
padding: 0.5rem 1rem;
color: #999;
color:var(--gray-400);
transition: all 0.2s ease;
}
@ -101,7 +104,9 @@
.profile-btn {
background: none;
border: none;
font-size: 1.5rem;
width: 2.5rem;
height: 2.5rem;
opacity: 0.3;
cursor: pointer;
padding: 0.5rem;
border-radius: 50%;
@ -109,13 +114,18 @@
}
.profile-btn:hover {
background-color: #f5f5f5;
background-color: var(--gray-100);
opacity: 1;
}
.profile-pic {
height: 100%;
}
.map-container {
flex: 1;
position: relative;
background-color: #F0F0F0;
background-color: var(--gray-50);
/* overflow: hidden; */
}
@ -124,10 +134,9 @@
justify-content: space-between;
align-items: center;
padding: 1.5rem 2rem;
margin-top: 10px;
background-color: white;
border-radius: 20px 20px 0 0;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.1);
}
.past-trips {
@ -148,11 +157,11 @@
.hint {
margin: 0.2rem 0 0 0;
font-size: 0.8rem;
color: #666;
color: var(--gray-400);
}
.new-trip-btn {
background-color: #38C1D0;
background-color: var(--planner-300);
color: white;
border: none;
padding: 0.8rem 1.5rem;

BIN
static/user.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB