mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 10:11:09 -05:00
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:
166
templates/components/cards/park_card.html
Normal file
166
templates/components/cards/park_card.html
Normal file
@@ -0,0 +1,166 @@
|
||||
<!-- Park Card Component -->
|
||||
<div class="card-park hover-lift group"
|
||||
hx-get="{% url 'parks:detail' park.slug %}"
|
||||
hx-target="#main-content"
|
||||
hx-swap="innerHTML transition:true">
|
||||
|
||||
<!-- Park Image with Overlay -->
|
||||
<div class="relative overflow-hidden rounded-t-2xl">
|
||||
{% if park.featured_image %}
|
||||
<img src="{{ park.featured_image.url }}"
|
||||
alt="{{ park.name }}"
|
||||
class="card-park-image"
|
||||
loading="lazy">
|
||||
{% else %}
|
||||
<div class="card-park-image bg-gradient-to-br from-thrill-primary/20 to-thrill-secondary/20 flex items-center justify-center">
|
||||
<i class="fas fa-map-marked-alt text-6xl text-thrill-primary/40"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Status Badge -->
|
||||
<div class="absolute top-4 left-4">
|
||||
{% if park.status == 'OPERATING' %}
|
||||
<span class="badge-operating">
|
||||
<i class="fas fa-check-circle mr-1"></i>
|
||||
Operating
|
||||
</span>
|
||||
{% elif park.status == 'CONSTRUCTION' %}
|
||||
<span class="badge-construction">
|
||||
<i class="fas fa-hard-hat mr-1"></i>
|
||||
Under Construction
|
||||
</span>
|
||||
{% elif park.status == 'CLOSED_PERMANENTLY' %}
|
||||
<span class="badge-closed">
|
||||
<i class="fas fa-times-circle mr-1"></i>
|
||||
Closed
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Favorite Button -->
|
||||
<div class="absolute top-4 right-4">
|
||||
<button class="w-10 h-10 bg-white/90 dark:bg-neutral-800/90 backdrop-blur-sm rounded-full flex items-center justify-center hover:bg-white dark:hover:bg-neutral-800 transition-all duration-200 hover:scale-110"
|
||||
hx-post="{% url 'parks:toggle_favorite' park.slug %}"
|
||||
hx-swap="outerHTML"
|
||||
onclick="event.stopPropagation()">
|
||||
{% if park.is_favorited %}
|
||||
<i class="fas fa-heart text-red-500"></i>
|
||||
{% else %}
|
||||
<i class="far fa-heart text-neutral-600 dark:text-neutral-400"></i>
|
||||
{% endif %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Gradient Overlay -->
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
|
||||
<!-- Quick Stats Overlay -->
|
||||
<div class="absolute bottom-4 left-4 right-4 transform translate-y-4 opacity-0 group-hover:translate-y-0 group-hover:opacity-100 transition-all duration-300">
|
||||
<div class="flex items-center justify-between text-white text-sm">
|
||||
<div class="flex items-center space-x-4">
|
||||
{% if park.ride_count %}
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-rocket mr-1"></i>
|
||||
{{ park.ride_count }} rides
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if park.area %}
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-expand-arrows-alt mr-1"></i>
|
||||
{{ park.area }} acres
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if park.rating %}
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-star text-yellow-400 mr-1"></i>
|
||||
{{ park.rating|floatformat:1 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Park Content -->
|
||||
<div class="card-park-content">
|
||||
<!-- Park Name and Location -->
|
||||
<div class="space-y-2">
|
||||
<h3 class="text-xl font-bold text-neutral-900 dark:text-neutral-100 group-hover:text-thrill-primary transition-colors duration-200">
|
||||
{{ park.name }}
|
||||
</h3>
|
||||
|
||||
{% if park.location %}
|
||||
<div class="flex items-center text-neutral-600 dark:text-neutral-400 text-sm">
|
||||
<i class="fas fa-map-marker-alt mr-2 text-thrill-primary"></i>
|
||||
{{ park.location.city }}{% if park.location.region %}, {{ park.location.region }}{% endif %}
|
||||
{% if park.location.country %}, {{ park.location.country }}{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Park Description -->
|
||||
{% if park.description %}
|
||||
<p class="text-neutral-600 dark:text-neutral-400 text-sm line-clamp-2">
|
||||
{{ park.description|truncatewords:20 }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<!-- Park Features/Tags -->
|
||||
{% if park.features.all %}
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{% for feature in park.features.all|slice:":3" %}
|
||||
<span class="px-2 py-1 bg-thrill-primary/10 text-thrill-primary text-xs rounded-full">
|
||||
{{ feature.name }}
|
||||
</span>
|
||||
{% endfor %}
|
||||
{% if park.features.count > 3 %}
|
||||
<span class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-600 dark:text-neutral-400 text-xs rounded-full">
|
||||
+{{ park.features.count|add:"-3" }} more
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Park Stats -->
|
||||
<div class="flex items-center justify-between pt-4 border-t border-neutral-200/50 dark:border-neutral-700/50">
|
||||
<div class="flex items-center space-x-4 text-sm text-neutral-600 dark:text-neutral-400">
|
||||
{% if park.opened_date %}
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-calendar mr-1"></i>
|
||||
{{ park.opened_date.year }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if park.park_type %}
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-tag mr-1"></i>
|
||||
{{ park.get_park_type_display }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Action Button -->
|
||||
<button class="btn-primary btn-sm opacity-0 group-hover:opacity-100 transition-all duration-200 transform translate-x-2 group-hover:translate-x-0">
|
||||
<i class="fas fa-arrow-right mr-2"></i>
|
||||
Explore
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading State Overlay -->
|
||||
<div class="absolute inset-0 bg-white/80 dark:bg-neutral-800/80 backdrop-blur-sm rounded-2xl flex items-center justify-center opacity-0 htmx-request:opacity-100 transition-opacity duration-200 pointer-events-none">
|
||||
<div class="loading-spinner opacity-100">
|
||||
<i class="fas fa-spinner text-2xl text-thrill-primary"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CSS for line-clamp utility -->
|
||||
<style>
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
271
templates/components/cards/ride_card.html
Normal file
271
templates/components/cards/ride_card.html
Normal file
@@ -0,0 +1,271 @@
|
||||
<!-- Ride Card Component -->
|
||||
<div class="card-ride hover-lift group"
|
||||
hx-get="{% url 'rides:detail' ride.slug %}"
|
||||
hx-target="#main-content"
|
||||
hx-swap="innerHTML transition:true">
|
||||
|
||||
<!-- Ride Image with Overlay -->
|
||||
<div class="relative overflow-hidden rounded-t-2xl">
|
||||
{% if ride.featured_image %}
|
||||
<img src="{{ ride.featured_image.url }}"
|
||||
alt="{{ ride.name }}"
|
||||
class="card-ride-image"
|
||||
loading="lazy">
|
||||
{% else %}
|
||||
<div class="card-ride-image bg-gradient-to-br from-thrill-secondary/20 to-red-500/20 flex items-center justify-center">
|
||||
<i class="fas fa-rocket text-6xl text-thrill-secondary/40"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Thrill Level Badge -->
|
||||
<div class="absolute top-4 left-4">
|
||||
{% if ride.thrill_level == 'EXTREME' %}
|
||||
<span class="badge bg-red-500/90 text-white border-red-500/20 backdrop-blur-sm">
|
||||
<i class="fas fa-fire mr-1"></i>
|
||||
Extreme
|
||||
</span>
|
||||
{% elif ride.thrill_level == 'HIGH' %}
|
||||
<span class="badge bg-orange-500/90 text-white border-orange-500/20 backdrop-blur-sm">
|
||||
<i class="fas fa-bolt mr-1"></i>
|
||||
High Thrill
|
||||
</span>
|
||||
{% elif ride.thrill_level == 'MODERATE' %}
|
||||
<span class="badge bg-yellow-500/90 text-white border-yellow-500/20 backdrop-blur-sm">
|
||||
<i class="fas fa-star mr-1"></i>
|
||||
Moderate
|
||||
</span>
|
||||
{% elif ride.thrill_level == 'MILD' %}
|
||||
<span class="badge bg-green-500/90 text-white border-green-500/20 backdrop-blur-sm">
|
||||
<i class="fas fa-leaf mr-1"></i>
|
||||
Family
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Status Badge -->
|
||||
{% if ride.status != 'OPERATING' %}
|
||||
<div class="absolute top-4 right-4">
|
||||
{% if ride.status == 'CONSTRUCTION' %}
|
||||
<span class="badge-construction backdrop-blur-sm">
|
||||
<i class="fas fa-hard-hat mr-1"></i>
|
||||
Coming Soon
|
||||
</span>
|
||||
{% elif ride.status == 'CLOSED_PERMANENTLY' %}
|
||||
<span class="badge-closed backdrop-blur-sm">
|
||||
<i class="fas fa-times-circle mr-1"></i>
|
||||
Closed
|
||||
</span>
|
||||
{% elif ride.status == 'CLOSED_TEMPORARILY' %}
|
||||
<span class="badge bg-yellow-500/90 text-white border-yellow-500/20 backdrop-blur-sm">
|
||||
<i class="fas fa-pause-circle mr-1"></i>
|
||||
Maintenance
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Favorite Button -->
|
||||
<div class="absolute {% if ride.status != 'OPERATING' %}top-16{% else %}top-4{% endif %} right-4">
|
||||
<button class="w-10 h-10 bg-white/90 dark:bg-neutral-800/90 backdrop-blur-sm rounded-full flex items-center justify-center hover:bg-white dark:hover:bg-neutral-800 transition-all duration-200 hover:scale-110"
|
||||
hx-post="{% url 'rides:toggle_favorite' ride.slug %}"
|
||||
hx-swap="outerHTML"
|
||||
onclick="event.stopPropagation()">
|
||||
{% if ride.is_favorited %}
|
||||
<i class="fas fa-heart text-red-500"></i>
|
||||
{% else %}
|
||||
<i class="far fa-heart text-neutral-600 dark:text-neutral-400"></i>
|
||||
{% endif %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Gradient Overlay -->
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
|
||||
<!-- Quick Stats Overlay -->
|
||||
<div class="absolute bottom-4 left-4 right-4 transform translate-y-4 opacity-0 group-hover:translate-y-0 group-hover:opacity-100 transition-all duration-300">
|
||||
<div class="grid grid-cols-2 gap-4 text-white text-sm">
|
||||
{% if ride.height %}
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-arrows-alt-v mr-2 text-thrill-secondary"></i>
|
||||
<div>
|
||||
<div class="font-semibold">{{ ride.height }}ft</div>
|
||||
<div class="text-xs opacity-75">Height</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if ride.speed %}
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-tachometer-alt mr-2 text-thrill-secondary"></i>
|
||||
<div>
|
||||
<div class="font-semibold">{{ ride.speed }}mph</div>
|
||||
<div class="text-xs opacity-75">Speed</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if ride.duration %}
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-clock mr-2 text-thrill-secondary"></i>
|
||||
<div>
|
||||
<div class="font-semibold">{{ ride.duration }}s</div>
|
||||
<div class="text-xs opacity-75">Duration</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if ride.inversions %}
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-sync-alt mr-2 text-thrill-secondary"></i>
|
||||
<div>
|
||||
<div class="font-semibold">{{ ride.inversions }}</div>
|
||||
<div class="text-xs opacity-75">Inversions</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Trending Badge -->
|
||||
{% if ride.is_trending %}
|
||||
<div class="absolute bottom-4 left-4 opacity-100 group-hover:opacity-0 transition-opacity duration-300">
|
||||
<span class="badge bg-gradient-to-r from-pink-500 to-red-500 text-white border-0 pulse-glow">
|
||||
<i class="fas fa-fire mr-1"></i>
|
||||
Trending
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Ride Content -->
|
||||
<div class="card-ride-content">
|
||||
<!-- Ride Name and Park -->
|
||||
<div class="space-y-2">
|
||||
<h3 class="text-lg font-bold text-neutral-900 dark:text-neutral-100 group-hover:text-thrill-secondary transition-colors duration-200">
|
||||
{{ ride.name }}
|
||||
</h3>
|
||||
|
||||
{% if ride.park %}
|
||||
<div class="flex items-center text-neutral-600 dark:text-neutral-400 text-sm">
|
||||
<i class="fas fa-map-marked-alt mr-2 text-thrill-secondary"></i>
|
||||
{{ ride.park.name }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Ride Type and Category -->
|
||||
<div class="flex items-center space-x-3">
|
||||
{% if ride.category %}
|
||||
<span class="px-2 py-1 bg-thrill-secondary/10 text-thrill-secondary text-xs rounded-full font-medium">
|
||||
{{ ride.get_category_display }}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if ride.coaster_type %}
|
||||
<span class="px-2 py-1 bg-neutral-100 dark:bg-neutral-700 text-neutral-600 dark:text-neutral-400 text-xs rounded-full">
|
||||
{{ ride.get_coaster_type_display }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Ride Description -->
|
||||
{% if ride.description %}
|
||||
<p class="text-neutral-600 dark:text-neutral-400 text-sm line-clamp-2">
|
||||
{{ ride.description|truncatewords:15 }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<!-- Ride Stats -->
|
||||
<div class="flex items-center justify-between pt-3 border-t border-neutral-200/50 dark:border-neutral-700/50">
|
||||
<div class="flex items-center space-x-4 text-sm text-neutral-600 dark:text-neutral-400">
|
||||
{% if ride.opened_date %}
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-calendar mr-1"></i>
|
||||
{{ ride.opened_date.year }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if ride.manufacturer %}
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-industry mr-1"></i>
|
||||
{{ ride.manufacturer.name }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Rating -->
|
||||
{% if ride.rating %}
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center mr-2">
|
||||
{% for i in "12345" %}
|
||||
{% if forloop.counter <= ride.rating %}
|
||||
<i class="fas fa-star text-yellow-400 text-xs"></i>
|
||||
{% else %}
|
||||
<i class="far fa-star text-neutral-300 dark:text-neutral-600 text-xs"></i>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<span class="text-sm font-semibold text-neutral-700 dark:text-neutral-300">
|
||||
{{ ride.rating|floatformat:1 }}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Action Button -->
|
||||
<div class="pt-3">
|
||||
<button class="btn-secondary btn-sm w-full opacity-0 group-hover:opacity-100 transition-all duration-200 transform translate-y-2 group-hover:translate-y-0">
|
||||
<i class="fas fa-info-circle mr-2"></i>
|
||||
View Details
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading State Overlay -->
|
||||
<div class="absolute inset-0 bg-white/80 dark:bg-neutral-800/80 backdrop-blur-sm rounded-2xl flex items-center justify-center opacity-0 htmx-request:opacity-100 transition-opacity duration-200 pointer-events-none">
|
||||
<div class="loading-spinner opacity-100">
|
||||
<i class="fas fa-spinner text-2xl text-thrill-secondary"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Additional CSS for enhanced styling -->
|
||||
<style>
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Enhanced pulse animation for trending badge */
|
||||
@keyframes pulse-glow {
|
||||
0%, 100% {
|
||||
box-shadow: 0 0 0 0 rgba(236, 72, 153, 0.4);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 0 8px rgba(236, 72, 153, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.pulse-glow {
|
||||
animation: pulse-glow 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
|
||||
/* Smooth hover transitions for stats */
|
||||
.card-ride:hover .grid > div {
|
||||
animation: slideInUp 0.3s ease-out forwards;
|
||||
}
|
||||
|
||||
.card-ride:hover .grid > div:nth-child(2) {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
|
||||
.card-ride:hover .grid > div:nth-child(3) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.card-ride:hover .grid > div:nth-child(4) {
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user