feat: move nav bar to the side (#6)

Co-authored-by: terraputix <terraputix@mailbox.org>
Reviewed-on: useweb/ombrella#6
This commit is contained in:
2026-02-16 01:23:52 +01:00
co-authored by terraputix
parent 53e5fb6538
commit 9a7e66d5d9
29 changed files with 831 additions and 975 deletions
+1 -268
View File
@@ -1,270 +1,3 @@
<script lang="ts">
import { onMount } from 'svelte';
import { get } from 'svelte/store';
import { fade, fly } from 'svelte/transition';
import { storedLocation } from '$lib/stores/settings';
import { Button } from '$lib/components/ui/button';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle
} from '$lib/components/ui/card';
let mounted = $state(false);
let location = get(storedLocation);
onMount(() => {
mounted = true;
});
const features = [
{
title: 'Week Prediction',
description:
'Get detailed hourly weather forecasts for the next 7 days with interactive charts and temperature gradients.',
href: `/weather`,
icon: 'calendar',
gradient: 'from-blue-500 to-cyan-500'
},
{
title: 'Model Comparison',
description:
'Compare multiple weather models side-by-side to understand forecast uncertainty and accuracy.',
href: '/weather/compare',
icon: 'trending-up',
gradient: 'from-purple-500 to-pink-500'
},
{
title: '14 Day Weather',
description:
'Extended forecast with ensemble model spreads showing temperature ranges and uncertainty.',
href: '/weather/14-day',
icon: 'cloud',
gradient: 'from-green-500 to-blue-500'
}
];
</script>
<svelte:head>
<title>Open-Meteo Weather - Advanced Weather Forecasting</title>
<meta
name="description"
content="Professional weather forecasting with multiple models, extended forecasts, and detailed comparisons. Powered by Open-Meteo API."
/>
<title>Open-Meteo Weather</title>
</svelte:head>
<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 Section -->
<div class="container mx-auto px-6 pt-20 pb-16">
{#if mounted}
<div class="mb-16 text-center" in:fade={{ duration: 800, delay: 200 }}>
<h1
class="mb-6 bg-gradient-to-r from-blue-600 via-purple-600 to-cyan-600 bg-clip-text text-5xl font-bold text-transparent md:text-7xl"
>
Open-Meteo Weather
</h1>
<p class="mx-auto mb-8 max-w-3xl text-xl text-gray-600 md:text-2xl dark:text-gray-300">
Professional weather forecasting with advanced models, detailed comparisons, and extended
predictions
</p>
<!-- Quick Access Button -->
<div in:fly={{ y: 20, duration: 600, delay: 600 }}>
<Button
href="/weather"
size="lg"
class="bg-gradient-to-r from-blue-600 to-purple-600 px-8 py-3 text-lg text-white shadow-lg transition-all duration-300 hover:from-blue-700 hover:to-purple-700 hover:shadow-xl"
>
View Current Weather
<svg class="ml-2 h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 7l5 5m0 0l-5 5m5-5H6"
/>
</svg>
</Button>
</div>
</div>
{/if}
<!-- Features Grid -->
<div class="mb-20 grid gap-8 md:grid-cols-3">
{#each features as feature, index (feature.title)}
{#if mounted}
<div in:fly={{ y: 30, duration: 600, delay: 300 + index * 150 }}>
<Card
class="h-full border-0 bg-white/80 shadow-lg backdrop-blur-sm transition-all duration-300 hover:-translate-y-1 hover:shadow-xl dark:bg-gray-800/80"
>
<CardHeader>
<div
class="h-12 w-12 rounded-xl bg-gradient-to-r {feature.gradient} mb-4 flex items-center justify-center"
>
{#if feature.icon === 'calendar'}
<svg
class="h-6 w-6 text-white"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
{:else if feature.icon === 'trending-up'}
<svg
class="h-6 w-6 text-white"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"
/>
</svg>
{:else if feature.icon === 'cloud'}
<svg
class="h-6 w-6 text-white"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z"
/>
</svg>
{/if}
</div>
<CardTitle class="mb-2 text-xl">{feature.title}</CardTitle>
<CardDescription class="text-gray-600 dark:text-gray-300">
{feature.description}
</CardDescription>
</CardHeader>
<CardContent class="pt-0">
<Button
href={feature.href}
variant="outline"
class="w-full hover:bg-gradient-to-r hover:{feature.gradient} transition-all duration-300 hover:border-transparent hover:text-white"
>
Explore {feature.title}
</Button>
</CardContent>
</Card>
</div>
{/if}
{/each}
</div>
<!-- Current Weather for Selected Location -->
{#if mounted && location}
<div class="mx-auto max-w-2xl" in:fade={{ duration: 600, delay: 800 }}>
<Card
class="border-blue-200 bg-gradient-to-r from-blue-500/10 to-purple-500/10 dark:border-blue-700"
>
<CardHeader>
<CardTitle class="text-center text-2xl">
Current Location: {location.name}
</CardTitle>
<CardDescription class="text-center">
{location.country}{location.latitude.toFixed(2)}°, {location.longitude.toFixed(2)}°
</CardDescription>
</CardHeader>
<CardContent class="text-center">
<Button href="/weather" variant="default" class="bg-blue-600 hover:bg-blue-700">
View Detailed Forecast
</Button>
</CardContent>
</Card>
</div>
{/if}
</div>
<!-- Features Overview Section -->
<div class="bg-white/50 py-20 dark:bg-gray-800/50">
<div class="container mx-auto px-6">
{#if mounted}
<div class="mb-16 text-center" in:fade={{ duration: 600, delay: 1000 }}>
<h2 class="mb-4 text-4xl font-bold text-gray-800 dark:text-white">
Why Choose Our Weather Service?
</h2>
<p class="mx-auto max-w-2xl text-xl text-gray-600 dark:text-gray-300">
Powered by Open-Meteo API with multiple weather models and advanced visualization
</p>
</div>
<div class="grid gap-8 md:grid-cols-2 lg:grid-cols-4">
{#each [{ title: 'Multiple Models', desc: 'Compare ECMWF, GFS, and more', icon: 'layers' }, { title: 'Extended Forecasts', desc: 'Up to 14 days ahead', icon: 'clock' }, { title: 'Interactive Charts', desc: 'Apache ECharts visualization', icon: 'bar-chart' }, { title: 'Real-time Data', desc: 'Always up-to-date', icon: 'refresh' }] as feature, index (feature.title)}
<div class="text-center" in:fly={{ y: 20, duration: 500, delay: 1200 + index * 100 }}>
<div
class="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-gradient-to-r from-blue-500 to-purple-500"
>
<svg
class="h-8 w-8 text-white"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
{#if feature.icon === 'layers'}
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
/>
{:else if feature.icon === 'clock'}
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
{:else if feature.icon === 'bar-chart'}
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
/>
{:else if feature.icon === 'refresh'}
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
/>
{/if}
</svg>
</div>
<h3 class="mb-2 text-lg font-semibold text-gray-800 dark:text-white">
{feature.title}
</h3>
<p class="text-gray-600 dark:text-gray-300">{feature.desc}</p>
</div>
{/each}
</div>
{/if}
</div>
</div>
</div>
<style>
:global(.container) {
max-width: 1200px;
}
</style>