mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 07:11:08 -05:00
Introduce ARIA attributes, improve focus management, and refine UI element styling for better accessibility and user experience across the application. Replit-Commit-Author: Agent Replit-Commit-Session-Id: c446bc9e-66df-438c-a86c-f53e6da13649 Replit-Commit-Checkpoint-Type: intermediate_checkpoint
378 lines
28 KiB
HTML
378 lines
28 KiB
HTML
{% comment %}
|
|
Park Card Component - Django Cotton Version
|
|
|
|
A reusable park card component that supports both list and grid view modes.
|
|
Includes status badges, operator information, description, and ride/coaster statistics.
|
|
|
|
Usage Examples:
|
|
|
|
List View:
|
|
<c-park_card
|
|
park=park
|
|
view_mode="list"
|
|
/>
|
|
|
|
Grid View:
|
|
<c-park_card
|
|
park=park
|
|
view_mode="grid"
|
|
/>
|
|
|
|
With custom CSS classes:
|
|
<c-park_card
|
|
park=park
|
|
view_mode="grid"
|
|
class="custom-class"
|
|
/>
|
|
|
|
Parameters:
|
|
- park: Park object (required)
|
|
- view_mode: "list" or "grid" (default: "grid")
|
|
- class: Additional CSS classes (optional)
|
|
|
|
Features:
|
|
- Responsive design with hover effects
|
|
- Status badge with proper color coding
|
|
- Operator information display
|
|
- Description with automatic truncation (30 words for list, 15 for grid)
|
|
- Ride and coaster count statistics with icons
|
|
- Gradient effects and modern styling
|
|
- Links to park detail pages
|
|
{% endcomment %}
|
|
|
|
<c-vars
|
|
park
|
|
view_mode="grid"
|
|
class=""
|
|
/>
|
|
|
|
{% if park %}
|
|
{% if view_mode == 'list' %}
|
|
{# Enhanced List View Item with CloudFlare Images and Accessibility #}
|
|
<article class="group bg-white/80 dark:bg-gray-800/80 backdrop-blur-sm border border-gray-200/50 dark:border-gray-700/50 rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 transform hover:scale-[1.02] overflow-hidden {{ class }}" role="article" aria-labelledby="park-title-{{ park.id }}" aria-describedby="park-description-{{ park.id }}">
|
|
<div class="p-4 sm:p-6">
|
|
<div class="flex flex-col sm:flex-row gap-4 sm:gap-6">
|
|
{# Enhanced List View Image Section #}
|
|
<div class="flex-shrink-0 w-full sm:w-32 md:w-40 lg:w-48">
|
|
<div class="relative aspect-[16/9] sm:aspect-[4/3] bg-gradient-to-br from-blue-500 via-purple-500 to-pink-500 rounded-lg overflow-hidden">
|
|
{% if park.card_image.image or park.photos.first.image %}
|
|
{% with image=park.card_image.image|default:park.photos.first.image %}
|
|
{# List View CloudFlare Images Optimization #}
|
|
<picture class="w-full h-full">
|
|
{# Mobile list view (full width, 16:9) #}
|
|
<source media="(max-width: 639px)"
|
|
srcset="
|
|
{{ image.public_url }} 1x,
|
|
{{ image.public_url }} 2x
|
|
"
|
|
type="image/webp">
|
|
|
|
{# Tablet/Desktop list view (smaller thumbnail) #}
|
|
<source media="(min-width: 640px)"
|
|
srcset="
|
|
{{ image.public_url }} 1x,
|
|
{{ image.public_url }} 2x
|
|
"
|
|
type="image/webp">
|
|
|
|
{# Fallback image #}
|
|
<img src="{{ image.public_url }}"
|
|
alt="{{ park.name }} - {% if park.card_image.alt_text %}{{ park.card_image.alt_text }}{% elif park.photos.first.alt_text %}{{ park.photos.first.alt_text }}{% else %}Theme park exterior view{% endif %}"
|
|
class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
|
|
loading="lazy"
|
|
decoding="async">
|
|
</picture>
|
|
{% endwith %}
|
|
{% else %}
|
|
{# Enhanced List View Fallback #}
|
|
<div class="flex items-center justify-center h-full text-white/70 bg-gradient-to-br from-gray-400 via-gray-500 to-gray-600 dark:from-gray-600 dark:via-gray-700 dark:to-gray-800">
|
|
<svg class="w-8 h-8 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path>
|
|
</svg>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# List View Status Badge Overlay with Accessibility #}
|
|
<div class="absolute top-1.5 right-1.5 sm:top-2 sm:right-2">
|
|
<span class="inline-flex items-center px-1.5 py-0.5 sm:px-2 sm:py-1 rounded-full text-xs font-semibold border shrink-0 bg-white/95 backdrop-blur-sm shadow-sm
|
|
{% if park.status == 'operating' or park.status == 'OPERATING' %}text-green-700 border-green-200
|
|
{% elif park.status == 'closed' or park.status == 'CLOSED_PERM' or park.status == 'closed_permanently' or park.status == 'closed_perm' %}text-red-700 border-red-200
|
|
{% elif park.status == 'seasonal' %}text-blue-700 border-blue-200
|
|
{% elif park.status == 'closed_temp' or park.status == 'CLOSED_TEMP' %}text-yellow-700 border-yellow-200
|
|
{% elif park.status == 'under_construction' or park.status == 'UNDER_CONSTRUCTION' %}text-blue-700 border-blue-200
|
|
{% elif park.status == 'demolished' or park.status == 'DEMOLISHED' %}text-gray-700 border-gray-200
|
|
{% elif park.status == 'relocated' or park.status == 'RELOCATED' %}text-purple-700 border-purple-200
|
|
{% else %}text-gray-700 border-gray-200{% endif %}"
|
|
role="img"
|
|
aria-label="Park status: {{ park.get_status_display }}"
|
|
title="Park status: {{ park.get_status_display }}">
|
|
<span class="hidden sm:inline" aria-hidden="true">{{ park.get_status_display }}</span>
|
|
<span class="sm:hidden" aria-hidden="true">{{ park.get_status_display|truncatechars:3 }}</span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# Enhanced Main Content Section with Better Mobile Layout #}
|
|
<div class="flex-1 min-w-0 flex flex-col justify-between">
|
|
<div class="space-y-2 sm:space-y-3">
|
|
{# Enhanced Title with Better Mobile Typography and Accessibility #}
|
|
<div class="flex items-start justify-between">
|
|
<h3 id="park-title-{{ park.id }}" class="text-lg sm:text-xl lg:text-2xl font-bold line-clamp-2 leading-tight">
|
|
{% if park.slug %}
|
|
<a href="{% url 'parks:park_detail' park.slug %}"
|
|
class="bg-gradient-to-r from-gray-900 to-gray-700 dark:from-white dark:to-gray-300 bg-clip-text text-transparent hover:from-blue-600 hover:to-purple-600 dark:hover:from-blue-400 dark:hover:to-purple-400 transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 rounded-sm"
|
|
aria-label="View details for {{ park.name }}">
|
|
{{ park.name }}
|
|
</a>
|
|
{% else %}
|
|
<span class="bg-gradient-to-r from-gray-900 to-gray-700 dark:from-white dark:to-gray-300 bg-clip-text text-transparent">
|
|
{{ park.name }}
|
|
</span>
|
|
{% endif %}
|
|
</h3>
|
|
|
|
{# View Details Arrow for Mobile #}
|
|
<div class="sm:hidden text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors duration-200 ml-2 flex-shrink-0">
|
|
<svg class="w-5 h-5 transform group-hover:translate-x-1 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
|
|
{# Enhanced Operator Display #}
|
|
{% if park.operator %}
|
|
<div class="text-sm sm:text-base font-medium text-gray-600 dark:text-gray-400 flex items-center">
|
|
<svg class="w-3 h-3 mr-1.5 flex-shrink-0 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/>
|
|
</svg>
|
|
<span class="truncate">{{ park.operator.name }}</span>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# Enhanced Description with Accessibility #}
|
|
{% if park.description %}
|
|
<p id="park-description-{{ park.id }}" class="text-sm text-gray-600 dark:text-gray-400 line-clamp-2 leading-relaxed">
|
|
{{ park.description|truncatewords:30 }}
|
|
</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{# Enhanced Stats Section with Better Mobile Layout #}
|
|
{% if park.ride_count or park.coaster_count %}
|
|
<div class="flex items-center justify-between pt-3 border-t border-gray-200/50 dark:border-gray-600/50 mt-3">
|
|
<div class="flex items-center space-x-3 sm:space-x-6 text-sm">
|
|
{% if park.ride_count %}
|
|
<div class="flex items-center space-x-1.5 sm:space-x-2 px-2 sm:px-4 py-1.5 sm:py-2 bg-gradient-to-r from-blue-50 to-purple-50 dark:from-blue-900/20 dark:to-purple-900/20 rounded-lg border border-blue-200/50 dark:border-blue-800/50"
|
|
role="img"
|
|
aria-label="{{ park.ride_count }} ride{{ park.ride_count|pluralize }} available"
|
|
title="{{ park.ride_count }} ride{{ park.ride_count|pluralize }}">
|
|
<svg class="w-4 h-4 sm:w-5 sm:h-5 text-blue-600 dark:text-blue-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
<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"/>
|
|
</svg>
|
|
<span class="font-semibold text-blue-700 dark:text-blue-300" aria-hidden="true">{{ park.ride_count }}</span>
|
|
<span class="text-blue-600 dark:text-blue-400 hidden sm:inline" aria-hidden="true">rides</span>
|
|
</div>
|
|
{% endif %}
|
|
{% if park.coaster_count %}
|
|
<div class="flex items-center space-x-1.5 sm:space-x-2 px-2 sm:px-4 py-1.5 sm:py-2 bg-gradient-to-r from-purple-50 to-pink-50 dark:from-purple-900/20 dark:to-pink-900/20 rounded-lg border border-purple-200/50 dark:border-purple-800/50"
|
|
role="img"
|
|
aria-label="{{ park.coaster_count }} roller coaster{{ park.coaster_count|pluralize }} available"
|
|
title="{{ park.coaster_count }} roller coaster{{ park.coaster_count|pluralize }}">
|
|
<svg class="w-4 h-4 sm:w-5 sm:h-5 text-purple-600 dark:text-purple-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
|
</svg>
|
|
<span class="font-semibold text-purple-700 dark:text-purple-300" aria-hidden="true">{{ park.coaster_count }}</span>
|
|
<span class="text-purple-600 dark:text-purple-400 hidden sm:inline" aria-hidden="true">coasters</span>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{# View Details Arrow for Desktop #}
|
|
<div class="hidden sm:block text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors duration-200">
|
|
<svg class="w-5 h-5 transform group-hover:translate-x-1 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
{# Show arrow even when no stats for consistent layout #}
|
|
<div class="hidden sm:flex justify-end pt-3 border-t border-gray-200/50 dark:border-gray-600/50 mt-3">
|
|
<div class="text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors duration-200">
|
|
<svg class="w-5 h-5 transform group-hover:translate-x-1 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
{% else %}
|
|
{# Enhanced Grid View Item with Accessibility #}
|
|
<article class="group bg-white/80 dark:bg-gray-800/80 backdrop-blur-sm border border-gray-200/50 dark:border-gray-700/50 rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 transform hover:scale-105 hover:-rotate-1 overflow-hidden {{ class }}" role="article" aria-labelledby="park-title-grid-{{ park.id }}" aria-describedby="park-description-grid-{{ park.id }}">
|
|
{# Enhanced Park Image with CloudFlare Images Integration #}
|
|
<div class="relative aspect-[4/3] bg-gradient-to-br from-blue-500 via-purple-500 to-pink-500 overflow-hidden">
|
|
{% if park.card_image.image or park.photos.first.image %}
|
|
{% with image=park.card_image.image|default:park.photos.first.image %}
|
|
{# CloudFlare Images Responsive Picture Element #}
|
|
<picture class="w-full h-full">
|
|
{# Mobile optimization (320-767px) - thumbnail variant with mobile-specific transformations #}
|
|
<source media="(max-width: 767px)"
|
|
srcset="
|
|
{{ image.public_url }} 1x,
|
|
{{ image.public_url }} 2x
|
|
"
|
|
type="image/webp">
|
|
|
|
{# Tablet optimization (768-1023px) - medium variant #}
|
|
<source media="(min-width: 768px) and (max-width: 1023px)"
|
|
srcset="
|
|
{{ image.public_url }} 1x,
|
|
{{ image.public_url }} 2x
|
|
"
|
|
type="image/webp">
|
|
|
|
{# Desktop optimization (1024px+) - large variant #}
|
|
<source media="(min-width: 1024px)"
|
|
srcset="
|
|
{{ image.public_url }} 1x,
|
|
{{ image.public_url }} 2x
|
|
"
|
|
type="image/webp">
|
|
|
|
{# Fallback image with progressive enhancement #}
|
|
<img src="{{ image.public_url }}"
|
|
alt="{{ park.name }} - {% if park.card_image.alt_text %}{{ park.card_image.alt_text }}{% elif park.photos.first.alt_text %}{{ park.photos.first.alt_text }}{% else %}Theme park exterior view{% endif %}"
|
|
class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
|
|
loading="lazy"
|
|
decoding="async"
|
|
style="aspect-ratio: 4/3; object-position: center;">
|
|
</picture>
|
|
|
|
{# Image Overlay Effects #}
|
|
<div class="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
|
{% endwith %}
|
|
{% else %}
|
|
{# Enhanced Fallback with Better UX #}
|
|
<div class="flex flex-col items-center justify-center h-full text-white/70 bg-gradient-to-br from-gray-400 via-gray-500 to-gray-600 dark:from-gray-600 dark:via-gray-700 dark:to-gray-800">
|
|
<div class="p-6 text-center">
|
|
<svg class="w-12 h-12 mx-auto mb-3 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path>
|
|
</svg>
|
|
<p class="text-sm font-medium opacity-80">No Image Available</p>
|
|
<p class="text-xs opacity-60 mt-1">{{ park.name }}</p>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# Enhanced Status Badge Overlay with Better Mobile Touch Targets and Accessibility #}
|
|
<div class="absolute top-2 right-2 sm:top-3 sm:right-3">
|
|
<span class="inline-flex items-center px-2 py-1 sm:px-2.5 sm:py-1 rounded-full text-xs font-semibold border shrink-0 bg-white/95 backdrop-blur-sm shadow-sm
|
|
{% if park.status == 'operating' or park.status == 'OPERATING' %}text-green-700 border-green-200
|
|
{% elif park.status == 'closed' or park.status == 'CLOSED_PERM' or park.status == 'closed_permanently' or park.status == 'closed_perm' %}text-red-700 border-red-200
|
|
{% elif park.status == 'seasonal' %}text-blue-700 border-blue-200
|
|
{% elif park.status == 'closed_temp' or park.status == 'CLOSED_TEMP' %}text-yellow-700 border-yellow-200
|
|
{% elif park.status == 'under_construction' or park.status == 'UNDER_CONSTRUCTION' %}text-blue-700 border-blue-200
|
|
{% elif park.status == 'demolished' or park.status == 'DEMOLISHED' %}text-gray-700 border-gray-200
|
|
{% elif park.status == 'relocated' or park.status == 'RELOCATED' %}text-purple-700 border-purple-200
|
|
{% else %}text-gray-700 border-gray-200{% endif %}"
|
|
role="img"
|
|
aria-label="Park status: {{ park.get_status_display }}"
|
|
title="Park status: {{ park.get_status_display }}">
|
|
<span aria-hidden="true">{{ park.get_status_display }}</span>
|
|
</span>
|
|
</div>
|
|
|
|
{# Loading Placeholder with Skeleton Effect #}
|
|
<div class="absolute inset-0 bg-gradient-to-r from-gray-200 via-gray-100 to-gray-200 dark:from-gray-700 dark:via-gray-600 dark:to-gray-700 animate-pulse opacity-0 transition-opacity duration-300" data-loading-placeholder></div>
|
|
</div>
|
|
|
|
{# Enhanced Content Area with Better Mobile Optimization #}
|
|
<div class="p-4 sm:p-6">
|
|
<div class="mb-3 sm:mb-4">
|
|
{# Enhanced Title with Better Mobile Typography and Accessibility #}
|
|
<h3 id="park-title-grid-{{ park.id }}" class="text-lg sm:text-xl font-bold line-clamp-2 mb-2 leading-tight">
|
|
{% if park.slug %}
|
|
<a href="{% url 'parks:park_detail' park.slug %}"
|
|
class="text-gray-900 dark:text-white hover:text-blue-600 dark:hover:text-blue-400 transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 rounded-sm"
|
|
aria-label="View details for {{ park.name }}">
|
|
{{ park.name }}
|
|
</a>
|
|
{% else %}
|
|
<span class="text-gray-900 dark:text-white">
|
|
{{ park.name }}
|
|
</span>
|
|
{% endif %}
|
|
</h3>
|
|
</div>
|
|
|
|
{# Enhanced Operator Display with Better Mobile Layout #}
|
|
{% if park.operator %}
|
|
<div class="text-sm font-medium text-gray-600 dark:text-gray-400 mb-3 truncate flex items-center">
|
|
<svg class="w-3 h-3 mr-1.5 flex-shrink-0 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/>
|
|
</svg>
|
|
<span class="truncate">{{ park.operator.name }}</span>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# Enhanced Description with Better Mobile Readability and Accessibility #}
|
|
{% if park.description %}
|
|
<p id="park-description-grid-{{ park.id }}" class="text-sm text-gray-600 dark:text-gray-400 line-clamp-3 mb-4 leading-relaxed">
|
|
{{ park.description|truncatewords:15 }}
|
|
</p>
|
|
{% endif %}
|
|
|
|
{# Enhanced Stats Footer with Better Mobile Layout #}
|
|
{% if park.ride_count or park.coaster_count %}
|
|
<div class="flex items-center justify-between pt-3 sm:pt-4 border-t border-gray-200/50 dark:border-gray-600/50">
|
|
<div class="flex items-center space-x-3 sm:space-x-4 text-sm">
|
|
{% if park.ride_count %}
|
|
<div class="flex items-center space-x-1.5 text-blue-600 dark:text-blue-400"
|
|
role="img"
|
|
aria-label="{{ park.ride_count }} ride{{ park.ride_count|pluralize }} available"
|
|
title="{{ park.ride_count }} ride{{ park.ride_count|pluralize }}">
|
|
<svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
<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"/>
|
|
</svg>
|
|
<span class="font-semibold" aria-hidden="true">{{ park.ride_count }}</span>
|
|
<span class="hidden sm:inline text-xs opacity-75" aria-hidden="true">rides</span>
|
|
</div>
|
|
{% endif %}
|
|
{% if park.coaster_count %}
|
|
<div class="flex items-center space-x-1.5 text-purple-600 dark:text-purple-400"
|
|
role="img"
|
|
aria-label="{{ park.coaster_count }} roller coaster{{ park.coaster_count|pluralize }} available"
|
|
title="{{ park.coaster_count }} roller coaster{{ park.coaster_count|pluralize }}">
|
|
<svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
|
</svg>
|
|
<span class="font-semibold" aria-hidden="true">{{ park.coaster_count }}</span>
|
|
<span class="hidden sm:inline text-xs opacity-75" aria-hidden="true">coasters</span>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{# Enhanced View Details Arrow with Better Mobile Touch Target #}
|
|
<div class="text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors duration-200 p-1 -m-1">
|
|
<svg class="w-4 h-4 sm:w-5 sm:h-5 transform group-hover:translate-x-1 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
{# Show arrow even when no stats for consistent layout #}
|
|
<div class="flex justify-end pt-3 sm:pt-4 border-t border-gray-200/50 dark:border-gray-600/50">
|
|
<div class="text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors duration-200 p-1 -m-1">
|
|
<svg class="w-4 h-4 sm:w-5 sm:h-5 transform group-hover:translate-x-1 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</article>
|
|
{% endif %}
|
|
{% endif %} |