Add park and ride card components with advanced search functionality

- Implemented park card component with image, status badge, favorite button, and quick stats overlay.
- Developed ride card component featuring thrill level badge, status badge, favorite button, and detailed stats.
- Created advanced search page with filters for parks and rides, including location, type, status, and thrill level.
- Added dynamic quick search functionality with results display.
- Enhanced user experience with JavaScript for filter toggling, range slider updates, and view switching.
- Included custom CSS for improved styling of checkboxes and search results layout.
This commit is contained in:
pacnpal
2025-09-24 23:10:48 -04:00
parent 4373d18176
commit b1c369c1bb
39 changed files with 5202 additions and 824 deletions

View File

@@ -1,169 +1,379 @@
{% extends 'base/base.html' %}
{% load static %}
{% load cotton %}
{% block title %}ThrillWiki - Theme Parks & Attractions Guide{% endblock %}
{% block title %}ThrillWiki - Your Ultimate Theme Park Adventure Guide{% endblock %}
{% block meta_description %}Discover the world's best theme parks and thrilling rides. Explore amazing parks, find detailed ride information, and share your adventures with fellow theme park enthusiasts.{% endblock %}
{% block meta_keywords %}theme parks, roller coasters, attractions, rides, amusement parks, Disney World, Universal Studios, Cedar Point, Six Flags, thrill rides{% endblock %}
{% block og_title %}ThrillWiki - Your Ultimate Theme Park & Attractions Guide{% endblock %}
{% block og_description %}Discover the world's best theme parks and thrilling rides. Explore amazing parks, find detailed ride information, and share your adventures with fellow theme park enthusiasts.{% endblock %}
{% block og_type %}website{% endblock %}
{% block twitter_title %}ThrillWiki - Your Ultimate Theme Park & Attractions Guide{% endblock %}
{% block twitter_description %}Discover the world's best theme parks and thrilling rides. Explore amazing parks, find detailed ride information, and share your adventures.{% endblock %}
{% block structured_data %}
<script type="application/ld+json" nonce="{{ request.csp_nonce }}">
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "ThrillWiki",
"description": "Your ultimate guide to theme parks and attractions worldwide",
"url": "{{ request.scheme }}://{{ request.get_host }}",
"potentialAction": {
"@type": "SearchAction",
"target": {
"@type": "EntryPoint",
"urlTemplate": "{{ request.scheme }}://{{ request.get_host }}/search/?q={search_term_string}"
},
"query-input": "required name=search_term_string"
},
"author": {
"@type": "Organization",
"name": "ThrillWiki",
"description": "The ultimate theme park and attractions database"
},
"mainEntity": {
"@type": "ItemList",
"name": "Featured Theme Parks and Attractions",
"description": "Top-rated theme parks and thrilling rides from around the world"
}
}
</script>
{% endblock %}
{% block meta_description %}Discover the world's most thrilling theme parks and attractions. Explore detailed guides, stunning photos, and insider tips for your next adventure at Disney, Universal, Cedar Point, and beyond.{% endblock %}
{% block content %}
<!-- Hero Section -->
<div class="mb-12 bg-white border border-gray-200 rounded-lg shadow-lg dark:bg-gray-800 dark:border-gray-700">
<div class="px-4 py-12 text-center">
<h1 class="mb-6 text-4xl font-bold text-gray-900 md:text-5xl lg:text-6xl dark:text-white">
Welcome to ThrillWiki
</h1>
<p class="max-w-3xl mx-auto mb-8 text-xl text-gray-600 md:text-2xl dark:text-gray-300">
Your ultimate guide to theme parks and attractions worldwide
<!-- Hero Section - Absolutely Stunning -->
<section class="hero relative min-h-screen flex items-center justify-center overflow-hidden">
<!-- Animated Background Elements -->
<div class="absolute inset-0 opacity-10 dark:opacity-5">
<div class="absolute top-20 left-10 w-32 h-32 bg-thrill-primary rounded-full blur-3xl animate-pulse"></div>
<div class="absolute top-40 right-20 w-48 h-48 bg-thrill-secondary rounded-full blur-3xl animate-pulse" style="animation-delay: 1s;"></div>
<div class="absolute bottom-32 left-1/4 w-40 h-40 bg-purple-500 rounded-full blur-3xl animate-pulse" style="animation-delay: 2s;"></div>
<div class="absolute bottom-20 right-1/3 w-36 h-36 bg-pink-500 rounded-full blur-3xl animate-pulse" style="animation-delay: 0.5s;"></div>
</div>
<!-- Main Hero Content -->
<div class="hero-content container mx-auto px-6 relative z-10">
<!-- Hero Badge -->
<div class="flex justify-center mb-8">
<div class="badge badge-info badge-lg pulse-glow">
<i class="fas fa-rocket mr-2"></i>
World's #1 Theme Park Guide
</div>
</div>
<!-- Hero Title with Stunning Typography -->
<h1 class="hero-title slide-in-up">
Discover Your Next
<span class="block">Thrilling Adventure</span>
</h1>
<!-- Hero Subtitle -->
<p class="hero-subtitle slide-in-up" style="animation-delay: 0.2s;">
Explore the world's most incredible theme parks, from heart-pounding roller coasters to magical experiences.
Your ultimate adventure starts here with insider guides, stunning visuals, and expert recommendations.
</p>
<!-- Hero CTA Buttons -->
<div class="hero-cta slide-in-up" style="animation-delay: 0.4s;">
<button class="btn-primary btn-lg hover-lift"
hx-get="/parks/"
hx-target="#main-content"
hx-swap="innerHTML transition:true">
<i class="fas fa-map-marked-alt mr-3"></i>
Explore Parks
</button>
<button class="btn-secondary btn-lg hover-lift">
<i class="fas fa-play mr-3"></i>
Watch Adventure
</button>
</div>
<!-- Hero Stats -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-8 mt-16 slide-in-up" style="animation-delay: 0.6s;">
<div class="text-center">
<div class="text-3xl md:text-4xl font-bold text-thrill-primary mb-2">500+</div>
<div class="text-neutral-600 dark:text-neutral-400">Theme Parks</div>
</div>
<div class="text-center">
<div class="text-3xl md:text-4xl font-bold text-thrill-secondary mb-2">2,000+</div>
<div class="text-neutral-600 dark:text-neutral-400">Thrilling Rides</div>
</div>
<div class="text-center">
<div class="text-3xl md:text-4xl font-bold text-thrill-success mb-2">50K+</div>
<div class="text-neutral-600 dark:text-neutral-400">Happy Visitors</div>
</div>
<div class="text-center">
<div class="text-3xl md:text-4xl font-bold text-purple-500 mb-2">100+</div>
<div class="text-neutral-600 dark:text-neutral-400">Countries</div>
</div>
</div>
</div>
<!-- Scroll Indicator -->
<div class="absolute bottom-8 left-1/2 transform -translate-x-1/2 animate-bounce">
<div class="w-6 h-10 border-2 border-white/30 rounded-full flex justify-center">
<div class="w-1 h-3 bg-white/50 rounded-full mt-2 animate-pulse"></div>
</div>
</div>
</section>
<!-- Featured Parks Section -->
<section class="py-24 bg-white/50 dark:bg-neutral-900/50 backdrop-blur-sm">
<div class="container mx-auto px-6">
<!-- Section Header -->
<div class="text-center mb-16">
<h2 class="text-4xl md:text-5xl font-bold mb-6">
<span class="bg-gradient-to-r from-thrill-primary to-thrill-secondary bg-clip-text text-transparent">
Featured Destinations
</span>
</h2>
<p class="text-xl text-neutral-600 dark:text-neutral-400 max-w-3xl mx-auto">
Discover the world's most incredible theme parks, each offering unique thrills and unforgettable memories
</p>
</div>
<!-- Featured Parks Grid -->
<div class="grid-auto-fit-lg"
hx-get="/api/parks/featured/"
hx-trigger="revealed"
hx-swap="innerHTML">
<!-- Loading Skeletons -->
<div class="card hover-lift">
<div class="loading-skeleton aspect-video rounded-t-2xl"></div>
<div class="p-6 space-y-4">
<div class="loading-skeleton h-6 w-3/4 rounded"></div>
<div class="loading-skeleton h-4 w-full rounded"></div>
<div class="loading-skeleton h-4 w-2/3 rounded"></div>
<div class="flex justify-between items-center">
<div class="loading-skeleton h-6 w-20 rounded-full"></div>
<div class="loading-skeleton h-8 w-24 rounded-lg"></div>
</div>
</div>
</div>
<div class="card hover-lift">
<div class="loading-skeleton aspect-video rounded-t-2xl"></div>
<div class="p-6 space-y-4">
<div class="loading-skeleton h-6 w-3/4 rounded"></div>
<div class="loading-skeleton h-4 w-full rounded"></div>
<div class="loading-skeleton h-4 w-2/3 rounded"></div>
<div class="flex justify-between items-center">
<div class="loading-skeleton h-6 w-20 rounded-full"></div>
<div class="loading-skeleton h-8 w-24 rounded-lg"></div>
</div>
</div>
</div>
<div class="card hover-lift">
<div class="loading-skeleton aspect-video rounded-t-2xl"></div>
<div class="p-6 space-y-4">
<div class="loading-skeleton h-6 w-3/4 rounded"></div>
<div class="loading-skeleton h-4 w-full rounded"></div>
<div class="loading-skeleton h-4 w-2/3 rounded"></div>
<div class="flex justify-between items-center">
<div class="loading-skeleton h-6 w-20 rounded-full"></div>
<div class="loading-skeleton h-8 w-24 rounded-lg"></div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Trending Rides Section -->
<section class="py-24">
<div class="container mx-auto px-6">
<!-- Section Header -->
<div class="text-center mb-16">
<h2 class="text-4xl md:text-5xl font-bold mb-6">
<span class="bg-gradient-to-r from-thrill-secondary to-red-500 bg-clip-text text-transparent">
Trending Thrills
</span>
</h2>
<p class="text-xl text-neutral-600 dark:text-neutral-400 max-w-3xl mx-auto">
The hottest rides everyone's talking about - from record-breaking coasters to innovative attractions
</p>
</div>
<!-- Trending Rides Carousel -->
<div class="relative">
<div class="grid-auto-fit-md"
hx-get="/api/rides/trending/"
hx-trigger="revealed"
hx-swap="innerHTML">
<!-- Loading Skeletons for Rides -->
<div class="card-ride hover-lift">
<div class="loading-skeleton aspect-square rounded-t-2xl"></div>
<div class="p-4 space-y-3">
<div class="loading-skeleton h-5 w-3/4 rounded"></div>
<div class="loading-skeleton h-4 w-full rounded"></div>
<div class="flex justify-between items-center">
<div class="loading-skeleton h-5 w-16 rounded-full"></div>
<div class="loading-skeleton h-6 w-20 rounded-lg"></div>
</div>
</div>
</div>
<div class="card-ride hover-lift">
<div class="loading-skeleton aspect-square rounded-t-2xl"></div>
<div class="p-4 space-y-3">
<div class="loading-skeleton h-5 w-3/4 rounded"></div>
<div class="loading-skeleton h-4 w-full rounded"></div>
<div class="flex justify-between items-center">
<div class="loading-skeleton h-5 w-16 rounded-full"></div>
<div class="loading-skeleton h-6 w-20 rounded-lg"></div>
</div>
</div>
</div>
<div class="card-ride hover-lift">
<div class="loading-skeleton aspect-square rounded-t-2xl"></div>
<div class="p-4 space-y-3">
<div class="loading-skeleton h-5 w-3/4 rounded"></div>
<div class="loading-skeleton h-4 w-full rounded"></div>
<div class="flex justify-between items-center">
<div class="loading-skeleton h-5 w-16 rounded-full"></div>
<div class="loading-skeleton h-6 w-20 rounded-lg"></div>
</div>
</div>
</div>
<div class="card-ride hover-lift">
<div class="loading-skeleton aspect-square rounded-t-2xl"></div>
<div class="p-4 space-y-3">
<div class="loading-skeleton h-5 w-3/4 rounded"></div>
<div class="loading-skeleton h-4 w-full rounded"></div>
<div class="flex justify-between items-center">
<div class="loading-skeleton h-5 w-16 rounded-full"></div>
<div class="loading-skeleton h-6 w-20 rounded-lg"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Features Section -->
<section class="py-24 bg-white/50 dark:bg-neutral-900/50 backdrop-blur-sm">
<div class="container mx-auto px-6">
<!-- Section Header -->
<div class="text-center mb-16">
<h2 class="text-4xl md:text-5xl font-bold mb-6">
<span class="bg-gradient-to-r from-purple-500 to-pink-500 bg-clip-text text-transparent">
Why Choose ThrillWiki?
</span>
</h2>
<p class="text-xl text-neutral-600 dark:text-neutral-400 max-w-3xl mx-auto">
We're more than just a guide - we're your adventure companion
</p>
</div>
<!-- Features Grid -->
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
<!-- Feature 1 -->
<div class="card-feature hover-lift text-center">
<div class="w-16 h-16 bg-gradient-to-r from-thrill-primary to-purple-500 rounded-2xl flex items-center justify-center mx-auto mb-6">
<i class="fas fa-map-marked-alt text-2xl text-white"></i>
</div>
<h3 class="text-2xl font-bold mb-4">Comprehensive Guides</h3>
<p class="text-neutral-600 dark:text-neutral-400">
Detailed information on every park, ride, and attraction with insider tips and expert recommendations.
</p>
<div class="flex flex-wrap justify-center gap-4">
<a href="{% url 'parks:park_list' %}"
class="px-8 py-3 text-lg btn-primary">
Explore Parks
</a>
<a href="{% url 'rides:global_ride_list' %}"
class="px-8 py-3 text-lg btn-secondary">
View Rides
</a>
</div>
<!-- Feature 2 -->
<div class="card-feature hover-lift text-center">
<div class="w-16 h-16 bg-gradient-to-r from-thrill-secondary to-red-500 rounded-2xl flex items-center justify-center mx-auto mb-6">
<i class="fas fa-camera text-2xl text-white"></i>
</div>
<h3 class="text-2xl font-bold mb-4">Stunning Visuals</h3>
<p class="text-neutral-600 dark:text-neutral-400">
High-quality photos and videos that bring the magic to life before you even arrive.
</p>
</div>
<!-- Feature 3 -->
<div class="card-feature hover-lift text-center">
<div class="w-16 h-16 bg-gradient-to-r from-thrill-success to-teal-500 rounded-2xl flex items-center justify-center mx-auto mb-6">
<i class="fas fa-users text-2xl text-white"></i>
</div>
<h3 class="text-2xl font-bold mb-4">Community Driven</h3>
<p class="text-neutral-600 dark:text-neutral-400">
Real reviews and experiences from fellow thrill-seekers and theme park enthusiasts.
</p>
</div>
<!-- Feature 4 -->
<div class="card-feature hover-lift text-center">
<div class="w-16 h-16 bg-gradient-to-r from-pink-500 to-rose-500 rounded-2xl flex items-center justify-center mx-auto mb-6">
<i class="fas fa-mobile-alt text-2xl text-white"></i>
</div>
<h3 class="text-2xl font-bold mb-4">Mobile Optimized</h3>
<p class="text-neutral-600 dark:text-neutral-400">
Perfect experience on any device, from planning at home to navigating in the park.
</p>
</div>
<!-- Feature 5 -->
<div class="card-feature hover-lift text-center">
<div class="w-16 h-16 bg-gradient-to-r from-indigo-500 to-blue-500 rounded-2xl flex items-center justify-center mx-auto mb-6">
<i class="fas fa-clock text-2xl text-white"></i>
</div>
<h3 class="text-2xl font-bold mb-4">Real-Time Updates</h3>
<p class="text-neutral-600 dark:text-neutral-400">
Live wait times, operating hours, and park status to help you make the most of your visit.
</p>
</div>
<!-- Feature 6 -->
<div class="card-feature hover-lift text-center">
<div class="w-16 h-16 bg-gradient-to-r from-yellow-500 to-orange-500 rounded-2xl flex items-center justify-center mx-auto mb-6">
<i class="fas fa-star text-2xl text-white"></i>
</div>
<h3 class="text-2xl font-bold mb-4">Expert Reviews</h3>
<p class="text-neutral-600 dark:text-neutral-400">
Professional insights and ratings from theme park experts and industry professionals.
</p>
</div>
</div>
</div>
</div>
</section>
<!-- Stats Section -->
<div class="grid-adaptive-sm mb-12">
<!-- Total Parks -->
<a href="{% url 'parks:park_list' %}"
class="flex flex-col items-center justify-center p-6 text-center transition-transform transform bg-white rounded-lg shadow-lg dark:bg-gray-800 hover:-translate-y-1 hover:shadow-xl">
<div class="mb-2 text-4xl font-bold text-blue-600 dark:text-blue-400">
{{ stats.total_parks }}
</div>
<div class="text-xl text-gray-600 dark:text-gray-300">
Theme Parks
</div>
</a>
<!-- Total Attractions -->
<a href="{% url 'rides:global_ride_list' %}"
class="flex flex-col items-center justify-center p-6 text-center transition-transform transform bg-white rounded-lg shadow-lg dark:bg-gray-800 hover:-translate-y-1 hover:shadow-xl">
<div class="mb-2 text-4xl font-bold text-blue-600 dark:text-blue-400">
{{ stats.ride_count }}
</div>
<div class="text-xl text-gray-600 dark:text-gray-300">
Attractions
</div>
</a>
<!-- Total Roller Coasters -->
<a href="{% url 'rides:global_roller_coasters' %}"
class="flex flex-col items-center justify-center p-6 text-center transition-transform transform bg-white rounded-lg shadow-lg dark:bg-gray-800 hover:-translate-y-1 hover:shadow-xl">
<div class="mb-2 text-4xl font-bold text-blue-600 dark:text-blue-400">
{{ stats.coaster_count }}
</div>
<div class="text-xl text-gray-600 dark:text-gray-300">
Roller Coasters
</div>
</a>
</div>
<!-- Featured Content -->
<div class="grid-adaptive">
<!-- Trending Parks -->
<div class="p-6 bg-white rounded-lg shadow-lg dark:bg-gray-800">
<h2 class="mb-6 text-2xl font-bold text-gray-900 dark:text-white">
Trending Parks
</h2>
<div class="space-y-4">
{% for park in popular_parks %}
<c-park_card :park="park" view_mode="grid" />
{% empty %}
<div class="flex flex-col items-center justify-center h-48 p-8 text-center bg-gray-50 rounded-lg dark:bg-gray-800/50">
<div class="mb-4 text-4xl">🎢</div>
<div class="text-lg font-medium text-gray-900 dark:text-white">No Parks Yet</div>
<div class="text-sm text-gray-600 dark:text-gray-400">Parks will appear here once they're added to the database</div>
</div>
{% endfor %}
</div>
<!-- Call to Action Section -->
<section class="py-24 relative overflow-hidden">
<!-- Background Gradient -->
<div class="absolute inset-0 bg-gradient-to-r from-thrill-primary via-purple-600 to-pink-600"></div>
<div class="absolute inset-0 bg-black/20"></div>
<!-- Content -->
<div class="container mx-auto px-6 relative z-10 text-center text-white">
<h2 class="text-4xl md:text-6xl font-bold mb-6">
Ready for Your Next Adventure?
</h2>
<p class="text-xl md:text-2xl mb-12 max-w-3xl mx-auto opacity-90">
Join thousands of thrill-seekers who trust ThrillWiki to plan their perfect theme park adventures
</p>
<div class="flex flex-col sm:flex-row gap-6 justify-center items-center">
<button class="btn-secondary btn-lg bg-white text-thrill-primary hover:bg-white/90 hover-lift">
<i class="fas fa-user-plus mr-3"></i>
Join the Community
</button>
<button class="btn-ghost btn-lg text-white border-white/30 hover:bg-white/10 hover-lift">
<i class="fas fa-compass mr-3"></i>
Start Exploring
</button>
</div>
</div>
</section>
<!-- Trending Rides -->
<div class="p-6 bg-white rounded-lg shadow-lg dark:bg-gray-800">
<h2 class="mb-6 text-2xl font-bold text-gray-900 dark:text-white">
Trending Rides
</h2>
<div class="space-y-4">
{% for ride in popular_rides %}
<c-ride_card :ride="ride" url_variant="park" />
{% empty %}
<div class="flex flex-col items-center justify-center h-48 p-8 text-center bg-gray-50 rounded-lg dark:bg-gray-800/50">
<div class="mb-4 text-4xl">🎠</div>
<div class="text-lg font-medium text-gray-900 dark:text-white">No Rides Yet</div>
<div class="text-sm text-gray-600 dark:text-gray-400">Rides will appear here once they're added to the database</div>
</div>
{% endfor %}
</div>
</div>
<!-- Highest Rated -->
<div class="p-6 bg-white rounded-lg shadow-lg dark:bg-gray-800">
<h2 class="mb-6 text-2xl font-bold text-gray-900 dark:text-white">
Highest Rated
</h2>
<div class="space-y-4">
{% for item in highest_rated %}
{% if item.park %}
<!-- This is a ride -->
<c-ride_card :ride="item" url_variant="park" />
{% else %}
<!-- This is a park -->
<c-park_card :park="item" view_mode="grid" />
{% endif %}
{% empty %}
<div class="flex flex-col items-center justify-center h-48 p-8 text-center bg-gray-50 rounded-lg dark:bg-gray-800/50">
<div class="mb-4 text-4xl"></div>
<div class="text-lg font-medium text-gray-900 dark:text-white">No Ratings Yet</div>
<div class="text-sm text-gray-600 dark:text-gray-400">Highest rated content will appear here once users start rating</div>
</div>
{% endfor %}
</div>
</div>
</div>
<!-- Enhanced JavaScript for Interactions -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Enable HTMX view transitions globally
htmx.config.globalViewTransitions = true;
// Add staggered animations to elements
const animatedElements = document.querySelectorAll('.slide-in-up');
animatedElements.forEach((el, index) => {
el.style.animationDelay = `${index * 0.1}s`;
});
// Parallax effect for hero background elements
window.addEventListener('scroll', () => {
const scrolled = window.pageYOffset;
const parallaxElements = document.querySelectorAll('.hero .absolute');
parallaxElements.forEach((el, index) => {
const speed = 0.5 + (index * 0.1);
el.style.transform = `translateY(${scrolled * speed}px)`;
});
});
// Intersection Observer for reveal animations
const observerOptions = {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('fade-in');
}
});
}, observerOptions);
// Observe all cards for reveal animations
document.querySelectorAll('.card, .card-feature, .card-park, .card-ride').forEach(card => {
observer.observe(card);
});
});
</script>
{% endblock %}