mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 02:31:08 -05:00
Enhance website accessibility and improve user interface elements
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
This commit is contained in:
8
.replit
8
.replit
@@ -54,14 +54,14 @@ outputType = "webview"
|
||||
localPort = 5000
|
||||
externalPort = 80
|
||||
|
||||
[[ports]]
|
||||
localPort = 33323
|
||||
externalPort = 3002
|
||||
|
||||
[[ports]]
|
||||
localPort = 41923
|
||||
externalPort = 3000
|
||||
|
||||
[[ports]]
|
||||
localPort = 44757
|
||||
externalPort = 3002
|
||||
|
||||
[[ports]]
|
||||
localPort = 45245
|
||||
externalPort = 3001
|
||||
|
||||
@@ -809,9 +809,6 @@
|
||||
.min-h-20 {
|
||||
min-height: calc(var(--spacing) * 20);
|
||||
}
|
||||
.min-h-\[24px\] {
|
||||
min-height: 24px;
|
||||
}
|
||||
.min-h-\[44px\] {
|
||||
min-height: 44px;
|
||||
}
|
||||
@@ -947,9 +944,6 @@
|
||||
.min-w-16 {
|
||||
min-width: calc(var(--spacing) * 16);
|
||||
}
|
||||
.min-w-\[24px\] {
|
||||
min-width: 24px;
|
||||
}
|
||||
.min-w-\[44px\] {
|
||||
min-width: 44px;
|
||||
}
|
||||
@@ -3091,6 +3085,11 @@
|
||||
left: calc(var(--spacing) * 4);
|
||||
}
|
||||
}
|
||||
.focus\:left-32 {
|
||||
&:focus {
|
||||
left: calc(var(--spacing) * 32);
|
||||
}
|
||||
}
|
||||
.focus\:z-50 {
|
||||
&:focus {
|
||||
z-index: 50;
|
||||
@@ -3162,6 +3161,11 @@
|
||||
--tw-ring-color: var(--color-blue-500);
|
||||
}
|
||||
}
|
||||
.focus\:ring-gray-500 {
|
||||
&:focus {
|
||||
--tw-ring-color: var(--color-gray-500);
|
||||
}
|
||||
}
|
||||
.focus\:ring-green-500 {
|
||||
&:focus {
|
||||
--tw-ring-color: var(--color-green-500);
|
||||
@@ -3193,6 +3197,12 @@
|
||||
--tw-ring-color: var(--color-yellow-500);
|
||||
}
|
||||
}
|
||||
.focus\:ring-offset-1 {
|
||||
&:focus {
|
||||
--tw-ring-offset-width: 1px;
|
||||
--tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||
}
|
||||
}
|
||||
.focus\:ring-offset-2 {
|
||||
&:focus {
|
||||
--tw-ring-offset-width: 2px;
|
||||
@@ -3215,6 +3225,11 @@
|
||||
outline-style: none;
|
||||
}
|
||||
}
|
||||
.focus\:ring-inset {
|
||||
&:focus {
|
||||
--tw-ring-inset: inset;
|
||||
}
|
||||
}
|
||||
.focus-visible\:ring-2 {
|
||||
&:focus-visible {
|
||||
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
||||
@@ -3371,6 +3386,11 @@
|
||||
min-height: calc(var(--spacing) * 0);
|
||||
}
|
||||
}
|
||||
.sm\:min-h-\[32px\] {
|
||||
@media (width >= 40rem) {
|
||||
min-height: 32px;
|
||||
}
|
||||
}
|
||||
.sm\:min-h-\[400px\] {
|
||||
@media (width >= 40rem) {
|
||||
min-height: 400px;
|
||||
@@ -3406,9 +3426,9 @@
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
.sm\:min-w-0 {
|
||||
.sm\:min-w-\[32px\] {
|
||||
@media (width >= 40rem) {
|
||||
min-width: calc(var(--spacing) * 0);
|
||||
min-width: 32px;
|
||||
}
|
||||
}
|
||||
.sm\:flex-1 {
|
||||
|
||||
@@ -90,16 +90,17 @@ Features:
|
||||
@click.away="open = false">
|
||||
|
||||
<div class="relative">
|
||||
<!-- Search Icon -->
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg class="h-5 w-5 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<!-- Search Icon with ARIA -->
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none" aria-hidden="true">
|
||||
<svg class="h-5 w-5 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Search Input -->
|
||||
<!-- Search Input with Enhanced Accessibility -->
|
||||
<input
|
||||
x-ref="searchInput"
|
||||
id="park-search"
|
||||
type="text"
|
||||
name="search"
|
||||
x-model="search"
|
||||
@@ -136,32 +137,40 @@ Features:
|
||||
}
|
||||
"
|
||||
autocomplete="off"
|
||||
role="combobox"
|
||||
aria-expanded="false"
|
||||
:aria-expanded="open"
|
||||
aria-autocomplete="list"
|
||||
aria-controls="search-suggestions"
|
||||
aria-describedby="search-help-text search-live-region"
|
||||
:aria-activedescendant="selectedIndex >= 0 ? `suggestion-${selectedIndex}` : null"
|
||||
/>
|
||||
|
||||
<!-- Loading Spinner -->
|
||||
<div id="search-spinner" class="absolute inset-y-0 right-0 pr-3 flex items-center htmx-indicator">
|
||||
<svg class="animate-spin h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24">
|
||||
<!-- Loading Spinner with ARIA -->
|
||||
<div id="search-spinner" class="absolute inset-y-0 right-0 pr-3 flex items-center htmx-indicator" aria-hidden="true">
|
||||
<svg class="animate-spin h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Clear Button -->
|
||||
<!-- Clear Button with Enhanced Accessibility -->
|
||||
<button
|
||||
x-show="search.length > 0"
|
||||
@click="clearSearch()"
|
||||
type="button"
|
||||
class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors htmx-indicator:hidden"
|
||||
aria-label="Clear search"
|
||||
class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors htmx-indicator:hidden min-w-[44px] min-h-[44px] justify-center"
|
||||
aria-label="Clear search input"
|
||||
title="Clear search"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Autocomplete Dropdown -->
|
||||
<!-- Autocomplete Dropdown with ARIA -->
|
||||
<div
|
||||
x-show="open && suggestions.length > 0"
|
||||
x-transition:enter="transition ease-out duration-100"
|
||||
@@ -172,15 +181,22 @@ Features:
|
||||
x-transition:leave-end="transform opacity-0 scale-95"
|
||||
class="absolute z-50 w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-lg dark:bg-gray-800 dark:border-gray-700 max-h-60 overflow-y-auto"
|
||||
style="display: none;"
|
||||
role="listbox"
|
||||
aria-label="Search suggestions"
|
||||
id="search-suggestions"
|
||||
>
|
||||
<div class="py-1">
|
||||
<template x-for="(suggestion, index) in suggestions" :key="index">
|
||||
<button
|
||||
type="button"
|
||||
class="w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700 flex items-center justify-between"
|
||||
class="w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700 flex items-center justify-between min-h-[44px] focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
:class="{ 'bg-gray-100 dark:bg-gray-700': selectedIndex === index }"
|
||||
@click="selectSuggestion(suggestion)"
|
||||
@mouseenter="selectedIndex = index"
|
||||
role="option"
|
||||
:id="`suggestion-${index}`"
|
||||
:aria-selected="selectedIndex === index"
|
||||
:aria-label="`Select ${suggestion.name || suggestion}${suggestion.type ? ' - ' + suggestion.type : ''}`"
|
||||
>
|
||||
<span x-text="suggestion.name || suggestion"></span>
|
||||
<template x-if="suggestion.type">
|
||||
@@ -229,4 +245,19 @@ Features:
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Screen Reader Support Elements -->
|
||||
<div id="search-help-text" class="sr-only">
|
||||
Type to search parks. Use arrow keys to navigate suggestions, Enter to select, or Escape to close.
|
||||
</div>
|
||||
|
||||
<!-- Live Region for Screen Reader Announcements -->
|
||||
<div id="search-live-region"
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
class="sr-only"
|
||||
x-text="open && suggestions.length > 0 ?
|
||||
`${suggestions.length} suggestion${suggestions.length !== 1 ? 's' : ''} available. Use arrow keys to navigate.` :
|
||||
(search.length >= 2 && !loading && suggestions.length === 0 ? 'No suggestions found.' : '')">
|
||||
</div>
|
||||
</div>
|
||||
@@ -28,10 +28,10 @@ Features:
|
||||
/>
|
||||
|
||||
{% if filters %}
|
||||
<div class="flex flex-wrap gap-2 {{ class }}">
|
||||
<div class="flex flex-wrap gap-2 {{ class }}" role="group" aria-label="Active filters">
|
||||
{% for filter_name, filter_value in filters.items %}
|
||||
{% if filter_value and filter_name != 'page' and filter_name != 'view_mode' %}
|
||||
<div class="inline-flex items-center gap-2 px-3 py-1.5 sm:py-1 text-sm font-medium text-blue-700 dark:text-blue-300 bg-blue-50 dark:bg-blue-900/30 rounded-full border border-blue-200 dark:border-blue-700/50">
|
||||
<div class="inline-flex items-center gap-2 px-3 py-1.5 sm:py-1 text-sm font-medium text-blue-700 dark:text-blue-300 bg-blue-50 dark:bg-blue-900/30 rounded-full border border-blue-200 dark:border-blue-700/50" role="group" aria-label="{{ filter_name|title }} filter: {{ filter_value }}">
|
||||
<span class="capitalize text-xs sm:text-sm">{{ filter_name|title }}:</span>
|
||||
<span class="font-semibold text-xs sm:text-sm">
|
||||
{% if filter_value == 'True' %}
|
||||
@@ -44,15 +44,15 @@ Features:
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-1 p-1 sm:p-0.5 text-blue-600 dark:text-blue-300 hover:text-blue-800 dark:hover:text-blue-200 hover:bg-blue-100 dark:hover:bg-blue-800/50 rounded-full transition-all duration-200 min-w-[24px] min-h-[24px] sm:min-w-0 sm:min-h-0 flex items-center justify-center"
|
||||
class="ml-1 p-1 sm:p-0.5 text-blue-600 dark:text-blue-300 hover:text-blue-800 dark:hover:text-blue-200 hover:bg-blue-100 dark:hover:bg-blue-800/50 rounded-full transition-all duration-200 min-w-[44px] min-h-[44px] sm:min-w-[32px] sm:min-h-[32px] flex items-center justify-center focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1"
|
||||
hx-get="{% if base_url %}{{ base_url }}{% else %}{{ request.path }}{% endif %}?{% for name, value in request.GET.items %}{% if name != filter_name and name != 'page' %}{{ name }}={{ value }}&{% endif %}{% endfor %}"
|
||||
hx-target="#park-results"
|
||||
hx-push-url="true"
|
||||
hx-indicator="#search-spinner"
|
||||
aria-label="Remove {{ filter_name }} filter"
|
||||
title="Remove filter"
|
||||
aria-label="Remove {{ filter_name|title }} filter with value {{ filter_value }}"
|
||||
title="Remove {{ filter_name|title }} filter"
|
||||
>
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
@@ -63,15 +63,15 @@ Features:
|
||||
{% if filters|length > 1 %}
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center gap-1 px-3 py-1 text-sm font-medium text-gray-600 bg-gray-100 rounded-full border border-gray-200 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-600 transition-colors"
|
||||
class="inline-flex items-center gap-1 px-3 py-2 text-sm font-medium text-gray-600 bg-gray-100 rounded-full border border-gray-200 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-600 transition-colors min-h-[44px] focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2"
|
||||
hx-get="{% if base_url %}{{ base_url }}{% else %}{{ request.path }}{% endif %}"
|
||||
hx-target="#park-results"
|
||||
hx-push-url="true"
|
||||
hx-indicator="#search-spinner"
|
||||
aria-label="Clear all filters"
|
||||
aria-label="Clear all active filters"
|
||||
title="Clear all filters"
|
||||
>
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
Clear all
|
||||
|
||||
@@ -48,8 +48,8 @@ Features:
|
||||
|
||||
{% if park %}
|
||||
{% if view_mode == 'list' %}
|
||||
{# Enhanced List View Item with CloudFlare Images #}
|
||||
<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 }}">
|
||||
{# 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 #}
|
||||
@@ -92,7 +92,7 @@ Features:
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# List View Status Badge Overlay #}
|
||||
{# 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
|
||||
@@ -102,9 +102,12 @@ Features:
|
||||
{% 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 %}">
|
||||
<span class="hidden sm:inline">{{ park.get_status_display }}</span>
|
||||
<span class="sm:hidden">{{ park.get_status_display|truncatechars:3 }}</span>
|
||||
{% 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>
|
||||
@@ -113,9 +116,9 @@ Features:
|
||||
{# 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 #}
|
||||
{# Enhanced Title with Better Mobile Typography and Accessibility #}
|
||||
<div class="flex items-start justify-between">
|
||||
<h2 class="text-lg sm:text-xl lg:text-2xl font-bold line-clamp-2 leading-tight">
|
||||
<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"
|
||||
@@ -127,7 +130,7 @@ Features:
|
||||
{{ park.name }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</h2>
|
||||
</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">
|
||||
@@ -147,9 +150,9 @@ Features:
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Enhanced Description #}
|
||||
{# Enhanced Description with Accessibility #}
|
||||
{% if park.description %}
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 line-clamp-2 leading-relaxed">
|
||||
<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 %}
|
||||
@@ -160,21 +163,27 @@ Features:
|
||||
<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" 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">
|
||||
<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">{{ park.ride_count }}</span>
|
||||
<span class="text-blue-600 dark:text-blue-400 hidden sm:inline">rides</span>
|
||||
<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" 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">
|
||||
<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">{{ park.coaster_count }}</span>
|
||||
<span class="text-purple-600 dark:text-purple-400 hidden sm:inline">coasters</span>
|
||||
<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>
|
||||
@@ -201,8 +210,8 @@ Features:
|
||||
</div>
|
||||
</article>
|
||||
{% else %}
|
||||
{# Enhanced Grid View Item #}
|
||||
<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 }}">
|
||||
{# 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 %}
|
||||
@@ -258,7 +267,7 @@ Features:
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Enhanced Status Badge Overlay with Better Mobile Touch Targets #}
|
||||
{# 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
|
||||
@@ -268,8 +277,11 @@ Features:
|
||||
{% 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 %}">
|
||||
{{ park.get_status_display }}
|
||||
{% 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>
|
||||
|
||||
@@ -280,8 +292,8 @@ Features:
|
||||
{# 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 #}
|
||||
<h2 class="text-lg sm:text-xl font-bold line-clamp-2 mb-2 leading-tight">
|
||||
{# 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"
|
||||
@@ -293,7 +305,7 @@ Features:
|
||||
{{ park.name }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</h2>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{# Enhanced Operator Display with Better Mobile Layout #}
|
||||
@@ -306,9 +318,9 @@ Features:
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Enhanced Description with Better Mobile Readability #}
|
||||
{# Enhanced Description with Better Mobile Readability and Accessibility #}
|
||||
{% if park.description %}
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 line-clamp-3 mb-4 leading-relaxed">
|
||||
<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 %}
|
||||
@@ -318,21 +330,27 @@ Features:
|
||||
<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" 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">
|
||||
<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">{{ park.ride_count }}</span>
|
||||
<span class="hidden sm:inline text-xs opacity-75">rides</span>
|
||||
<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" 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">
|
||||
<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">{{ park.coaster_count }}</span>
|
||||
<span class="hidden sm:inline text-xs opacity-75">coasters</span>
|
||||
<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>
|
||||
|
||||
@@ -44,7 +44,7 @@ Features:
|
||||
class=""
|
||||
/>
|
||||
|
||||
<div class="flex items-center justify-between text-sm text-gray-600 dark:text-gray-400 {{ class }}">
|
||||
<div class="flex items-center justify-between text-sm text-gray-600 dark:text-gray-400 {{ class }}" role="status" aria-live="polite">
|
||||
<div class="flex items-center gap-4">
|
||||
<!-- Result Count -->
|
||||
<div class="flex items-center gap-1">
|
||||
@@ -74,11 +74,11 @@ Features:
|
||||
|
||||
<!-- Filter Indicator -->
|
||||
{% if filter_count and filter_count > 0 %}
|
||||
<div class="flex items-center gap-1 text-blue-600 dark:text-blue-400">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<div class="flex items-center gap-1 text-blue-600 dark:text-blue-400" role="img" aria-label="{{ filter_count }} active filter{{ filter_count|pluralize }}">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
|
||||
</svg>
|
||||
<span>{{ filter_count }} filter{{ filter_count|pluralize }} active</span>
|
||||
<span aria-hidden="true">{{ filter_count }} filter{{ filter_count|pluralize }} active</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -103,7 +103,7 @@ Features:
|
||||
{% if total_results == 0 and is_search %}
|
||||
<div class="mt-3 p-3 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg">
|
||||
<div class="flex items-start gap-2">
|
||||
<svg class="w-5 h-5 mt-0.5 text-yellow-600 dark:text-yellow-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-5 h-5 mt-0.5 text-yellow-600 dark:text-yellow-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 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<div class="text-sm">
|
||||
|
||||
@@ -39,11 +39,12 @@ Features:
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center w-full px-3 sm:px-4 py-2.5 sm:py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200 min-h-[44px] sm:min-h-0"
|
||||
@click="open = !open"
|
||||
aria-expanded="true"
|
||||
:aria-expanded="open"
|
||||
aria-haspopup="true"
|
||||
aria-label="Sort options"
|
||||
aria-label="Sort options menu"
|
||||
id="sort-menu-button"
|
||||
>
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4h13M3 8h9m-9 4h6m4 0l4-4m0 0l4 4m-4-4v12" />
|
||||
</svg>
|
||||
Sort by
|
||||
@@ -72,7 +73,7 @@ Features:
|
||||
<span class="ml-1">: {{ current_sort }}</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<svg class="w-5 h-5 ml-2 -mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<svg class="w-5 h-5 ml-2 -mr-1" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
@@ -90,18 +91,19 @@ Features:
|
||||
@click.away="open = false"
|
||||
style="display: none;"
|
||||
>
|
||||
<div class="py-1" role="menu" aria-orientation="vertical" aria-labelledby="options-menu">
|
||||
<div class="py-1" role="menu" aria-orientation="vertical" aria-labelledby="sort-menu-button">
|
||||
{% if options %}
|
||||
{% for option in options %}
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center w-full px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700 {% if current_sort == option.value %}bg-blue-50 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300{% endif %}"
|
||||
class="flex items-center w-full px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700 {% if current_sort == option.value %}bg-blue-50 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300{% endif %} focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-inset"
|
||||
hx-get="{% url 'parks:park_list' %}?{% for name, value in request.GET.items %}{% if name != 'ordering' and name != 'page' %}{{ name }}={{ value }}&{% endif %}{% endfor %}ordering={{ option.value }}"
|
||||
hx-target="#park-results"
|
||||
hx-push-url="true"
|
||||
hx-indicator="#search-spinner"
|
||||
@click="open = false"
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
{% if current_sort == option.value %}
|
||||
<svg class="w-4 h-4 mr-2 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
|
||||
|
||||
@@ -30,7 +30,7 @@ Features:
|
||||
class=""
|
||||
/>
|
||||
|
||||
<div class="inline-flex rounded-lg border border-gray-200 dark:border-gray-700 {{ class }}" role="group" aria-label="View toggle">
|
||||
<div class="inline-flex rounded-lg border border-gray-200 dark:border-gray-700 {{ class }}" role="group" aria-label="Toggle between grid and list view modes">
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center px-3 py-2.5 sm:py-2 text-sm font-medium rounded-l-lg transition-all duration-200 min-h-[44px] sm:min-h-0 {% if current_view == 'grid' %}bg-blue-50 text-blue-700 border-blue-200 dark:bg-blue-900/30 dark:text-blue-300 dark:border-blue-700{% else %}bg-white text-gray-700 hover:bg-gray-50 focus:bg-gray-50 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700 dark:focus:bg-gray-700{% endif %} focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
||||
@@ -42,7 +42,7 @@ Features:
|
||||
aria-pressed="{% if current_view == 'grid' %}true{% else %}false{% endif %}"
|
||||
title="Grid view"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
|
||||
</svg>
|
||||
<span class="ml-1 hidden sm:inline">Grid</span>
|
||||
@@ -59,7 +59,7 @@ Features:
|
||||
aria-pressed="{% if current_view == 'list' %}true{% else %}false{% endif %}"
|
||||
title="List view"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" />
|
||||
</svg>
|
||||
<span class="ml-1 hidden sm:inline">List</span>
|
||||
|
||||
@@ -5,46 +5,57 @@
|
||||
{% block title %}Parks{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{# Enhanced Mobile-First Container with Better Spacing #}
|
||||
{# Skip Navigation Links for Keyboard Users #}
|
||||
<a href="#main-content" class="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 z-50 px-4 py-2 bg-blue-600 text-white font-semibold rounded-lg shadow-lg focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200">
|
||||
Skip to main content
|
||||
</a>
|
||||
<a href="#search-form" class="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-32 z-50 px-4 py-2 bg-blue-600 text-white font-semibold rounded-lg shadow-lg focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200">
|
||||
Skip to search
|
||||
</a>
|
||||
|
||||
{# Enhanced Mobile-First Container with Better Spacing and Landmarks #}
|
||||
<div class="container mx-auto px-3 sm:px-4 lg:px-6 py-4 sm:py-6" x-data="parkListState()">
|
||||
{# Enhanced Mobile-First Header Section #}
|
||||
<div class="mb-6 sm:mb-8">
|
||||
<header class="mb-6 sm:mb-8" aria-labelledby="page-title">
|
||||
<div class="flex flex-col gap-4 sm:gap-6">
|
||||
{# Enhanced Mobile-First Title Section #}
|
||||
{# Enhanced Mobile-First Title Section with Proper Heading #}
|
||||
<div class="text-center sm:text-left">
|
||||
<h1 class="text-2xl sm:text-3xl lg:text-4xl font-bold text-gray-900 dark:text-white leading-tight">
|
||||
<h1 id="page-title" class="text-2xl sm:text-3xl lg:text-4xl font-bold text-gray-900 dark:text-white leading-tight">
|
||||
Theme Parks
|
||||
</h1>
|
||||
<p class="mt-1 sm:mt-2 text-base sm:text-lg text-gray-600 dark:text-gray-400">
|
||||
<p class="mt-1 sm:mt-2 text-base sm:text-lg text-gray-600 dark:text-gray-400" id="page-description">
|
||||
Discover amazing theme parks around the world
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{# Enhanced Mobile-First Quick Stats with Better Touch Targets #}
|
||||
<div class="grid grid-cols-3 gap-3 sm:gap-4 lg:gap-6">
|
||||
<div class="text-center p-3 sm:p-4 bg-white/50 dark:bg-gray-800/50 rounded-lg border border-gray-200/50 dark:border-gray-700/50">
|
||||
<div class="font-bold text-lg sm:text-xl lg:text-2xl text-gray-900 dark:text-white">{{ filter_counts.total_parks|default:0 }}</div>
|
||||
{# Enhanced Mobile-First Quick Stats with Better Touch Targets and Landmarks #}
|
||||
<section aria-labelledby="park-statistics" class="grid grid-cols-3 gap-3 sm:gap-4 lg:gap-6">
|
||||
<h2 id="park-statistics" class="sr-only">Park Statistics Summary</h2>
|
||||
<div class="text-center p-3 sm:p-4 bg-white/50 dark:bg-gray-800/50 rounded-lg border border-gray-200/50 dark:border-gray-700/50" role="img" aria-labelledby="total-parks-stat" tabindex="0">
|
||||
<div id="total-parks-stat" class="font-bold text-lg sm:text-xl lg:text-2xl text-gray-900 dark:text-white" aria-label="{{ filter_counts.total_parks|default:0 }} total parks in database">{{ filter_counts.total_parks|default:0 }}</div>
|
||||
<div class="text-xs sm:text-sm text-gray-600 dark:text-gray-400 mt-1">Total Parks</div>
|
||||
</div>
|
||||
<div class="text-center p-3 sm:p-4 bg-white/50 dark:bg-gray-800/50 rounded-lg border border-gray-200/50 dark:border-gray-700/50">
|
||||
<div class="font-bold text-lg sm:text-xl lg:text-2xl text-gray-900 dark:text-white">{{ filter_counts.operating_parks|default:0 }}</div>
|
||||
<div class="text-center p-3 sm:p-4 bg-white/50 dark:bg-gray-800/50 rounded-lg border border-gray-200/50 dark:border-gray-700/50" role="img" aria-labelledby="operating-parks-stat" tabindex="0">
|
||||
<div id="operating-parks-stat" class="font-bold text-lg sm:text-xl lg:text-2xl text-gray-900 dark:text-white" aria-label="{{ filter_counts.operating_parks|default:0 }} currently operating parks">{{ filter_counts.operating_parks|default:0 }}</div>
|
||||
<div class="text-xs sm:text-sm text-gray-600 dark:text-gray-400 mt-1">Operating</div>
|
||||
</div>
|
||||
<div class="text-center p-3 sm:p-4 bg-white/50 dark:bg-gray-800/50 rounded-lg border border-gray-200/50 dark:border-gray-700/50">
|
||||
<div class="font-bold text-lg sm:text-xl lg:text-2xl text-gray-900 dark:text-white">{{ filter_counts.parks_with_coasters|default:0 }}</div>
|
||||
<div class="text-center p-3 sm:p-4 bg-white/50 dark:bg-gray-800/50 rounded-lg border border-gray-200/50 dark:border-gray-700/50" role="img" aria-labelledby="coaster-parks-stat" tabindex="0">
|
||||
<div id="coaster-parks-stat" class="font-bold text-lg sm:text-xl lg:text-2xl text-gray-900 dark:text-white" aria-label="{{ filter_counts.parks_with_coasters|default:0 }} parks with roller coasters">{{ filter_counts.parks_with_coasters|default:0 }}</div>
|
||||
<div class="text-xs sm:text-sm text-gray-600 dark:text-gray-400 mt-1">With Coasters</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{# Enhanced Mobile-First Search and Filter Bar #}
|
||||
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg border border-gray-200 dark:border-gray-700 p-4 sm:p-6 mb-6 sm:mb-8">
|
||||
{# Enhanced Mobile-First Search and Filter Bar with Proper Landmarks #}
|
||||
<section class="bg-white dark:bg-gray-800 rounded-xl shadow-lg border border-gray-200 dark:border-gray-700 p-4 sm:p-6 mb-6 sm:mb-8" aria-labelledby="search-filters-heading" role="search">
|
||||
<h2 id="search-filters-heading" class="sr-only">Search and Filter Parks</h2>
|
||||
<div class="space-y-4 sm:space-y-6">
|
||||
{# Enhanced Mobile-First Main Search Row #}
|
||||
<div class="space-y-3 sm:space-y-0 sm:flex sm:flex-col lg:flex-row gap-4">
|
||||
{# Enhanced Search Input with Better Mobile UX #}
|
||||
<div class="flex-1">
|
||||
{# Enhanced Search Input with Better Mobile UX and Form Landmark #}
|
||||
<div class="flex-1" id="search-form">
|
||||
<label for="park-search" class="sr-only">Search parks by name, location, or features</label>
|
||||
<c-enhanced_search
|
||||
placeholder="Search parks by name, location, or features..."
|
||||
current_value="{{ search_query }}"
|
||||
@@ -53,8 +64,8 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
{# Enhanced Mobile-First Controls Row with Better Touch Targets #}
|
||||
<div class="flex items-center justify-between sm:justify-start gap-2 sm:gap-3">
|
||||
{# Enhanced Mobile-First Controls Row with Better Touch Targets and Navigation #}
|
||||
<nav class="flex items-center justify-between sm:justify-start gap-2 sm:gap-3" aria-label="View and sort controls">
|
||||
{# Sort Controls with Mobile Optimization #}
|
||||
<div class="flex-1 sm:flex-none min-w-0">
|
||||
<c-sort_controls
|
||||
@@ -88,7 +99,7 @@
|
||||
<span class="ml-1 sm:ml-2 hidden sm:inline">Filters</span>
|
||||
<span class="sr-only sm:hidden" x-text="showFilters ? 'Hide filters' : 'Show filters'"></span>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{# Enhanced Mobile-First Advanced Filters with Better Touch Interaction #}
|
||||
|
||||
Reference in New Issue
Block a user