207 lines
6.2 KiB
Svelte
207 lines
6.2 KiB
Svelte
<script lang="ts">
|
|
import { fly, fade } from 'svelte/transition';
|
|
import { onMount } from 'svelte';
|
|
import WeatherNav from '$lib/components/navigation/weather-nav.svelte';
|
|
import LocationSearch from '$lib/components/location/location-search.svelte';
|
|
import { storedLocation } from '$lib/stores/settings';
|
|
import { get } from 'svelte/store';
|
|
import { page } from '$app/stores';
|
|
|
|
interface Props {
|
|
children?: import('svelte').Snippet;
|
|
}
|
|
|
|
let { children }: Props = $props();
|
|
|
|
let location = $state(get(storedLocation));
|
|
let mounted = $state(false);
|
|
let currentWeather = $state(null);
|
|
|
|
// Subscribe to location changes
|
|
storedLocation.subscribe((value) => {
|
|
location = value;
|
|
if (mounted) {
|
|
loadCurrentWeather();
|
|
}
|
|
});
|
|
|
|
onMount(() => {
|
|
mounted = true;
|
|
loadCurrentWeather();
|
|
});
|
|
|
|
const loadCurrentWeather = async () => {
|
|
if (!location?.latitude) return;
|
|
|
|
try {
|
|
const response = await fetch(
|
|
`https://api.open-meteo.com/v1/forecast?latitude=${location.latitude}&longitude=${location.longitude}¤t=temperature_2m,weather_code&forecast_days=1`
|
|
);
|
|
const data = await response.json();
|
|
currentWeather = data;
|
|
} catch (error) {
|
|
console.error('Failed to load current weather:', error);
|
|
}
|
|
};
|
|
|
|
const getWeatherIcon = (code) => {
|
|
const iconMap = {
|
|
0: '☀️',
|
|
1: '🌤️',
|
|
2: '⛅',
|
|
3: '☁️',
|
|
45: '🌫️',
|
|
48: '🌫️',
|
|
51: '🌦️',
|
|
53: '🌦️',
|
|
55: '🌦️',
|
|
61: '🌧️',
|
|
63: '🌧️',
|
|
65: '🌧️',
|
|
71: '🌨️',
|
|
73: '🌨️',
|
|
75: '❄️',
|
|
95: '⛈️'
|
|
};
|
|
return iconMap[code] || '☁️';
|
|
};
|
|
|
|
const getPageTitle = () => {
|
|
const path = $page.url.pathname;
|
|
if (path.includes('/compare')) return 'Model Comparison';
|
|
if (path.includes('/14-day')) return '14 Day Forecast';
|
|
if (path.includes('/week')) return 'Week Prediction';
|
|
return 'Weather Forecast';
|
|
};
|
|
</script>
|
|
|
|
<WeatherNav />
|
|
|
|
<div
|
|
class="min-h-screen bg-gradient-to-br from-blue-50 via-white to-cyan-50 dark:from-gray-900 dark:via-gray-800 dark:to-blue-900"
|
|
>
|
|
<!-- Hero Header Section -->
|
|
<div class="relative overflow-hidden bg-gradient-to-r from-blue-600 via-purple-600 to-cyan-600">
|
|
<div class="absolute inset-0 bg-black/10"></div>
|
|
<div class="relative">
|
|
<div class="container mx-auto px-6 py-12">
|
|
{#if mounted}
|
|
<div class="flex flex-col items-center space-y-6" in:fade={{ duration: 800 }}>
|
|
<!-- Page Title -->
|
|
<div class="text-center" in:fly={{ y: -20, duration: 600, delay: 200 }}>
|
|
<h1 class="mb-2 text-3xl font-bold text-white md:text-4xl">
|
|
{getPageTitle()}
|
|
</h1>
|
|
</div>
|
|
|
|
<!-- Location Display & Search -->
|
|
<div class="w-full max-w-2xl" in:fly={{ y: 20, duration: 600, delay: 400 }}>
|
|
<div
|
|
class="rounded-2xl bg-white/95 p-6 shadow-2xl backdrop-blur-md dark:bg-gray-800/95"
|
|
>
|
|
<!-- Current Location Display -->
|
|
{#if location}
|
|
<div
|
|
class="flex flex-col items-center justify-between space-y-4 md:flex-row md:space-y-0 md:space-x-6"
|
|
>
|
|
<!-- Location Info -->
|
|
<div class="flex flex-1 items-center space-x-4">
|
|
<div class="flex-shrink-0">
|
|
<img
|
|
class="h-12 w-12 rounded-full shadow-lg"
|
|
src="/images/country-flags/{(
|
|
location.country_code || 'united_nations'
|
|
).toLowerCase()}.svg"
|
|
alt={location.country}
|
|
/>
|
|
</div>
|
|
<div class="flex-1 text-left">
|
|
<h2 class="text-2xl font-bold text-gray-900 dark:text-white">
|
|
{location.name}
|
|
</h2>
|
|
<p class="text-gray-600 dark:text-gray-300">
|
|
{#if location.admin1}{location.admin1},
|
|
{/if}{location.country}
|
|
</p>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
{location.latitude.toFixed(2)}°N, {location.longitude.toFixed(2)}°E
|
|
{#if location.elevation}• {location.elevation.toFixed(0)}m{/if}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Current Weather -->
|
|
{#if currentWeather}
|
|
<div class="flex items-center space-x-3" in:fade={{ delay: 800 }}>
|
|
<div class="text-center">
|
|
<div class="mb-1 text-3xl">
|
|
{getWeatherIcon(currentWeather.current.weather_code)}
|
|
</div>
|
|
<div class="text-2xl font-bold text-gray-900 dark:text-white">
|
|
{Math.round(currentWeather.current.temperature_2m)}°C
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Location Search -->
|
|
<div class="mt-6 border-t border-gray-200 pt-6 dark:border-gray-600">
|
|
<LocationSearch
|
|
label="🔍 Change location or search for a new city..."
|
|
on:location={(event) => {
|
|
storedLocation.set(event.detail);
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stats Row (if location has population) -->
|
|
{#if location?.population}
|
|
<div
|
|
class="flex justify-center space-x-8 text-white/90"
|
|
in:fly={{ y: 20, duration: 600, delay: 600 }}
|
|
>
|
|
<div class="text-center">
|
|
<div class="text-sm font-medium">Population</div>
|
|
<div class="text-lg font-bold">{location.population.toLocaleString()}</div>
|
|
</div>
|
|
{#if location.timezone}
|
|
<div class="text-center">
|
|
<div class="text-sm font-medium">Timezone</div>
|
|
<div class="text-lg font-bold">{location.timezone}</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Decorative elements -->
|
|
<div class="pointer-events-none absolute top-0 left-0 h-full w-full overflow-hidden">
|
|
<div class="absolute -top-4 -right-4 h-24 w-24 rounded-full bg-white/10"></div>
|
|
<div class="absolute top-1/3 -left-8 h-16 w-16 rounded-full bg-white/5"></div>
|
|
<div class="absolute bottom-8 left-1/4 h-12 w-12 rounded-full bg-white/10"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Content -->
|
|
<div class="container mx-auto px-6 py-8">
|
|
{#if mounted}
|
|
<div in:fade={{ duration: 600, delay: 400 }}>
|
|
{@render children?.()}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
:global(.container) {
|
|
max-width: 1200px;
|
|
}
|
|
</style>
|