Files
voyage/frontend/src/routes/+page.svelte
Sean Morley 151c76dbd1 Enhance user profile and world travel pages with improved UI and functionality
- Updated user profile page to include achievement calculations and enhanced styling for user information and statistics.
- Added icons for better visual representation of user stats and achievements.
- Improved layout for displaying adventures and collections with conditional rendering for empty states.
- Refactored world travel page to include search and filter functionality for cities, with a sidebar for progress and stats.
- Implemented completion percentage and progress bars for visited cities.
- Enhanced map integration with markers for visited and not visited cities, including toggle options for map labels.
2025-06-14 11:10:59 -04:00

403 lines
12 KiB
Svelte

<script lang="ts">
import { goto } from '$app/navigation';
import { t } from 'svelte-i18n';
import { onMount } from 'svelte';
import MapWithPins from '$lib/assets/MapWithPins.webp';
import type { Background } from '$lib/types.js';
// Icons
import MapIcon from '~icons/mdi/map-outline';
import CameraIcon from '~icons/mdi/camera-outline';
import CalendarIcon from '~icons/mdi/calendar-outline';
import TrophyIcon from '~icons/mdi/trophy-outline';
import ChevronRight from '~icons/mdi/chevron-right';
import PlayIcon from '~icons/mdi/play';
import CheckIcon from '~icons/mdi/check-circle';
import StarIcon from '~icons/mdi/star';
import GlobeIcon from '~icons/mdi/earth';
import LightningIcon from '~icons/mdi/lightning-bolt';
export let data;
let background: Background = data.props?.background ?? { url: '' };
let isVisible = false;
onMount(() => {
setTimeout(() => (isVisible = true), 100);
});
const features = [
{
icon: MapIcon,
title: $t('home.feature_1'),
description: $t('home.feature_1_desc'),
color: 'text-blue-500',
bgColor: 'bg-blue-50 dark:bg-blue-900/20'
},
{
icon: CameraIcon,
title: $t('home.feature_2'),
description: $t('home.feature_2_desc'),
color: 'text-green-500',
bgColor: 'bg-green-50 dark:bg-green-900/20'
},
{
icon: TrophyIcon,
title: $t('home.feature_3'),
description: $t('home.feature_3_desc'),
color: 'text-yellow-500',
bgColor: 'bg-yellow-50 dark:bg-yellow-900/20'
}
];
const stats = [
{ label: 'Countries Tracked', value: '195+', icon: GlobeIcon },
{ label: 'Adventures Logged', value: '10K+', icon: CalendarIcon },
{ label: 'Active Travelers', value: '5K+', icon: StarIcon }
];
</script>
<div class="min-h-screen bg-gradient-to-br from-base-200 via-base-100 to-base-200">
<!-- Hero Section -->
<section class="relative min-h-screen flex items-center justify-center overflow-hidden">
<!-- Background Pattern -->
<div class="absolute inset-0 opacity-5">
<div class="absolute inset-0 bg-gradient-to-br from-primary/20 to-secondary/20"></div>
<svg class="absolute inset-0 w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
<defs>
<pattern id="grid" width="10" height="10" patternUnits="userSpaceOnUse">
<path d="M 10 0 L 0 0 0 10" fill="none" stroke="currentColor" stroke-width="0.5" />
</pattern>
</defs>
<rect width="100" height="100" fill="url(#grid)" />
</svg>
</div>
<div class="container mx-auto px-6 py-20 relative z-10">
<div class="grid lg:grid-cols-2 gap-12 items-center">
<!-- Left Content -->
<div class="space-y-8 {isVisible ? 'animate-fade-in-up' : 'opacity-0'}">
{#if data.user}
{#if data.user.first_name && data.user.first_name !== null}
<div class="space-y-4">
<div
class="inline-flex items-center gap-2 px-4 py-2 bg-primary/10 text-primary rounded-full border border-primary/20"
>
<LightningIcon class="w-4 h-4" />
<span class="text-sm font-medium">Welcome back!</span>
</div>
<h1 class="text-5xl lg:text-7xl font-black leading-tight">
<span
class="bg-gradient-to-r from-primary via-secondary to-accent bg-clip-text text-transparent"
>
{data.user.first_name.charAt(0).toUpperCase() + data.user.first_name.slice(1)},
</span>
<br />
<span class="text-base-content/90">
{$t('home.hero_1')}
</span>
</h1>
</div>
{:else}
<div class="space-y-4">
<div
class="inline-flex items-center gap-2 px-4 py-2 bg-primary/10 text-primary rounded-full border border-primary/20"
>
<LightningIcon class="w-4 h-4" />
<span class="text-sm font-medium">Ready to explore?</span>
</div>
<h1 class="text-5xl lg:text-7xl font-black leading-tight">
<span
class="bg-gradient-to-r from-primary via-secondary to-accent bg-clip-text text-transparent"
>
{$t('home.hero_1')}
</span>
</h1>
</div>
{/if}
{:else}
<div class="space-y-4">
<div
class="inline-flex items-center gap-2 px-4 py-2 bg-primary/10 text-primary rounded-full border border-primary/20"
>
<LightningIcon class="w-4 h-4" />
<span class="text-sm font-medium">Start your journey</span>
</div>
<h1 class="text-5xl lg:text-7xl font-black leading-tight">
<span
class="bg-gradient-to-r from-primary via-secondary to-accent bg-clip-text text-transparent"
>
{$t('home.hero_1')}
</span>
</h1>
</div>
{/if}
<p class="text-xl lg:text-2xl text-base-content/70 leading-relaxed font-light max-w-2xl">
{$t('home.hero_2')}
</p>
<!-- CTA Buttons -->
<div class="flex flex-col sm:flex-row gap-4 pt-4">
{#if data.user}
<button
on:click={() => goto('/adventures')}
class="btn btn-primary btn-lg gap-3 shadow-lg hover:shadow-xl transition-all duration-300 group"
>
<PlayIcon class="w-5 h-5 group-hover:scale-110 transition-transform" />
{$t('home.go_to')}
<ChevronRight class="w-4 h-4 group-hover:translate-x-1 transition-transform" />
</button>
{:else}
<button
on:click={() => goto('/login')}
class="btn btn-primary btn-lg gap-3 shadow-lg hover:shadow-xl transition-all duration-300 group"
>
{$t('auth.login')}
<ChevronRight class="w-4 h-4 group-hover:translate-x-1 transition-transform" />
</button>
<button
on:click={() => goto('/signup')}
class="btn btn-outline btn-lg gap-3 hover:shadow-lg transition-all duration-300"
>
{$t('auth.signup')}
</button>
{/if}
</div>
<!-- Stats -->
<div class="grid grid-cols-3 gap-6 pt-8 border-t border-base-300">
{#each stats as stat}
<div class="text-center">
<div class="flex justify-center mb-2">
<div class="p-2 bg-primary/10 rounded-lg">
<svelte:component this={stat.icon} class="w-5 h-5 text-primary" />
</div>
</div>
<div class="text-2xl font-bold text-base-content">{stat.value}</div>
<div class="text-sm text-base-content/60">{stat.label}</div>
</div>
{/each}
</div>
</div>
<!-- Right Content - Hero Image -->
<div class="relative {isVisible ? 'animate-fade-in-right' : 'opacity-0'}">
<div class="relative">
<!-- Decorative Elements -->
<div class="absolute -top-4 -left-4 w-24 h-24 bg-primary/10 rounded-2xl rotate-6"></div>
<div
class="absolute -bottom-4 -right-4 w-32 h-32 bg-secondary/10 rounded-2xl -rotate-6"
></div>
<!-- Main Image -->
<div class="relative bg-base-100 p-4 rounded-3xl shadow-2xl">
<img
src={background.url}
alt={background.location}
class="rounded-2xl object-cover w-full h-[500px] shadow-lg"
/>
<!-- Floating Badge -->
<div
class="absolute top-8 left-8 bg-base-100/90 backdrop-blur-sm px-4 py-2 rounded-full shadow-lg border"
>
<div class="flex items-center gap-2">
<div class="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
<span class="text-sm font-medium"
>{background.location || 'Adventure Awaits'}</span
>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Scroll Indicator -->
<div class="absolute bottom-8 left-1/2 -translate-x-1/2 animate-bounce">
<div class="w-6 h-10 border-2 border-base-content/30 rounded-full flex justify-center">
<div class="w-1 h-3 bg-base-content/30 rounded-full mt-2 animate-pulse"></div>
</div>
</div>
</section>
<!-- Features Section -->
<section id="features" class="py-24 bg-base-100">
<div class="container mx-auto px-6">
<!-- Section Header -->
<div class="text-center mb-16 space-y-4">
<div
class="inline-flex items-center gap-2 px-4 py-2 bg-neutral/10 text-neutral rounded-full border border-neutral/20"
>
<StarIcon class="w-4 h-4" />
<span class="text-sm font-medium">{$t('home.key_features')}</span>
</div>
<h2 class="text-4xl lg:text-5xl font-bold">
<span class="bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
{$t('home.desc_1')}
</span>
</h2>
<p class="text-xl text-base-content/70 max-w-3xl mx-auto leading-relaxed">
{$t('home.desc_2')}
</p>
</div>
<div class="grid lg:grid-cols-2 gap-16 items-center">
<!-- Features List -->
<div class="space-y-8">
{#each features as feature, index}
<div
class="group hover:bg-base-200/50 p-6 rounded-2xl transition-all duration-300 hover:shadow-lg"
>
<div class="flex items-start gap-4">
<div class="flex-shrink-0 p-3 {feature.bgColor} rounded-xl">
<svelte:component this={feature.icon} class="w-6 h-6 {feature.color}" />
</div>
<div class="space-y-2">
<h3
class="text-xl font-bold text-base-content group-hover:text-primary transition-colors"
>
{feature.title}
</h3>
<p class="text-base-content/70 leading-relaxed">
{feature.description}
</p>
</div>
</div>
</div>
{/each}
</div>
<!-- Feature Image -->
<div class="relative">
<div class="relative bg-gradient-to-br from-primary/5 to-secondary/5 p-8 rounded-3xl">
<img
src={MapWithPins}
alt="World map with pins"
class="rounded-2xl shadow-2xl object-cover w-full"
/>
<!-- Floating Elements -->
<div class="absolute top-4 right-4 bg-base-100 p-3 rounded-xl shadow-lg animate-float">
<div class="flex items-center gap-2">
<CheckIcon class="w-4 h-4 text-success" />
<span class="text-sm font-medium">25 Countries</span>
</div>
</div>
<div
class="absolute bottom-4 left-4 bg-base-100 p-3 rounded-xl shadow-lg animate-float-delayed"
>
<div class="flex items-center gap-2">
<TrophyIcon class="w-4 h-4 text-warning" />
<span class="text-sm font-medium">Explorer Level</span>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Call to Action Section -->
{#if !data.user}
<section class="py-24 bg-gradient-to-r from-primary to-secondary">
<div class="container mx-auto px-6 text-center">
<div class="max-w-3xl mx-auto space-y-8">
<h2 class="text-4xl lg:text-5xl font-bold text-white">Ready to Start Your Adventure?</h2>
<p class="text-xl text-white/90 leading-relaxed">
Join thousands of travelers already using AdventureLog to document their journeys and
discover new destinations.
</p>
<div class="flex flex-col sm:flex-row gap-4 justify-center pt-4">
<button
on:click={() => goto('/signup')}
class="btn btn-lg bg-white text-primary hover:bg-white/90 gap-3 shadow-lg group"
>
Get Started Free
<ChevronRight class="w-4 h-4 group-hover:translate-x-1 transition-transform" />
</button>
<button
on:click={() => goto('/login')}
class="btn btn-lg btn-outline text-white border-white hover:bg-white hover:text-primary gap-3"
>
Sign In
</button>
</div>
</div>
</div>
</section>
{/if}
</div>
<svelte:head>
<title>Home | AdventureLog</title>
<meta
name="description"
content="AdventureLog is a platform to log your adventures and plan your travel."
/>
</svelte:head>
<style>
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fade-in-right {
from {
opacity: 0;
transform: translateX(30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes float {
0%,
100% {
transform: translateY(0px);
}
50% {
transform: translateY(-10px);
}
}
@keyframes float-delayed {
0%,
100% {
transform: translateY(0px);
}
50% {
transform: translateY(-8px);
}
}
.animate-fade-in-up {
animation: fade-in-up 0.8s ease-out;
}
.animate-fade-in-right {
animation: fade-in-right 0.8s ease-out 0.2s both;
}
.animate-float {
animation: float 3s ease-in-out infinite;
}
.animate-float-delayed {
animation: float-delayed 3s ease-in-out infinite 1.5s;
}
</style>