Standardize park and ride cards with django-cotton component

Updates CSS with new Tailwind classes and refactors ride card template to use django-cotton, implementing park-specific URL generation and graceful handling of missing slugs.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 0bdea3fb-49ea-4863-b501-fa6f5af0cbf0
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
This commit is contained in:
pac7
2025-09-22 03:35:47 +00:00
parent 97bf980e45
commit ffebd5ce01
3 changed files with 107 additions and 8 deletions

View File

@@ -55,7 +55,7 @@ localPort = 5000
externalPort = 80 externalPort = 80
[[ports]] [[ports]]
localPort = 35685 localPort = 37689
externalPort = 3002 externalPort = 3002
[[ports]] [[ports]]

View File

@@ -410,6 +410,9 @@
.top-0 { .top-0 {
top: calc(var(--spacing) * 0); top: calc(var(--spacing) * 0);
} }
.top-1 {
top: calc(var(--spacing) * 1);
}
.top-1\/2 { .top-1\/2 {
top: calc(1/2 * 100%); top: calc(1/2 * 100%);
} }
@@ -449,6 +452,9 @@
.left-0 { .left-0 {
left: calc(var(--spacing) * 0); left: calc(var(--spacing) * 0);
} }
.left-1 {
left: calc(var(--spacing) * 1);
}
.left-1\/2 { .left-1\/2 {
left: calc(1/2 * 100%); left: calc(1/2 * 100%);
} }
@@ -527,6 +533,9 @@
max-width: 96rem; max-width: 96rem;
} }
} }
.-mx-1 {
margin-inline: calc(var(--spacing) * -1);
}
.-mx-1\.5 { .-mx-1\.5 {
margin-inline: calc(var(--spacing) * -1.5); margin-inline: calc(var(--spacing) * -1.5);
} }
@@ -545,6 +554,9 @@
.mx-auto { .mx-auto {
margin-inline: auto; margin-inline: auto;
} }
.-my-1 {
margin-block: calc(var(--spacing) * -1);
}
.-my-1\.5 { .-my-1\.5 {
margin-block: calc(var(--spacing) * -1.5); margin-block: calc(var(--spacing) * -1.5);
} }
@@ -554,6 +566,9 @@
.my-auto { .my-auto {
margin-block: auto; margin-block: auto;
} }
.mt-0 {
margin-top: calc(var(--spacing) * 0);
}
.mt-0\.5 { .mt-0\.5 {
margin-top: calc(var(--spacing) * 0.5); margin-top: calc(var(--spacing) * 0.5);
} }
@@ -626,6 +641,9 @@
.mb-12 { .mb-12 {
margin-bottom: calc(var(--spacing) * 12); margin-bottom: calc(var(--spacing) * 12);
} }
.-ml-0 {
margin-left: calc(var(--spacing) * -0);
}
.-ml-0\.5 { .-ml-0\.5 {
margin-left: calc(var(--spacing) * -0.5); margin-left: calc(var(--spacing) * -0.5);
} }
@@ -797,6 +815,9 @@
.min-h-screen { .min-h-screen {
min-height: 100vh; min-height: 100vh;
} }
.w-1 {
width: calc(var(--spacing) * 1);
}
.w-1\/2 { .w-1\/2 {
width: calc(1/2 * 100%); width: calc(1/2 * 100%);
} }
@@ -932,6 +953,13 @@
.grow { .grow {
flex-grow: 1; flex-grow: 1;
} }
.border-collapse {
border-collapse: collapse;
}
.-translate-x-1 {
--tw-translate-x: calc(var(--spacing) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.-translate-x-1\/2 { .-translate-x-1\/2 {
--tw-translate-x: calc(calc(1/2 * 100%) * -1); --tw-translate-x: calc(calc(1/2 * 100%) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y); translate: var(--tw-translate-x) var(--tw-translate-y);
@@ -948,6 +976,10 @@
--tw-translate-x: 100%; --tw-translate-x: 100%;
translate: var(--tw-translate-x) var(--tw-translate-y); translate: var(--tw-translate-x) var(--tw-translate-y);
} }
.-translate-y-1 {
--tw-translate-y: calc(var(--spacing) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.-translate-y-1\/2 { .-translate-y-1\/2 {
--tw-translate-y: calc(calc(1/2 * 100%) * -1); --tw-translate-y: calc(calc(1/2 * 100%) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y); translate: var(--tw-translate-x) var(--tw-translate-y);
@@ -1130,6 +1162,13 @@
margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse))); margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)));
} }
} }
.space-y-8 {
:where(& > :not(:last-child)) {
--tw-space-y-reverse: 0;
margin-block-start: calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse));
margin-block-end: calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)));
}
}
.-space-x-px { .-space-x-px {
:where(& > :not(:last-child)) { :where(& > :not(:last-child)) {
--tw-space-x-reverse: 0; --tw-space-x-reverse: 0;
@@ -1394,6 +1433,9 @@
.bg-\[\#5865F2\] { .bg-\[\#5865F2\] {
background-color: #5865F2; background-color: #5865F2;
} }
.bg-accent {
background-color: var(--color-accent);
}
.bg-accent\/10 { .bg-accent\/10 {
background-color: color-mix(in srgb, #8b5cf6 10%, transparent); background-color: color-mix(in srgb, #8b5cf6 10%, transparent);
@supports (color: color-mix(in lab, red, red)) { @supports (color: color-mix(in lab, red, red)) {
@@ -1442,6 +1484,9 @@
.bg-blue-600 { .bg-blue-600 {
background-color: var(--color-blue-600); background-color: var(--color-blue-600);
} }
.bg-blue-900 {
background-color: var(--color-blue-900);
}
.bg-blue-900\/40 { .bg-blue-900\/40 {
background-color: color-mix(in srgb, oklch(37.9% 0.146 265.522) 40%, transparent); background-color: color-mix(in srgb, oklch(37.9% 0.146 265.522) 40%, transparent);
@supports (color: color-mix(in lab, red, red)) { @supports (color: color-mix(in lab, red, red)) {
@@ -1496,6 +1541,9 @@
.bg-green-600 { .bg-green-600 {
background-color: var(--color-green-600); background-color: var(--color-green-600);
} }
.bg-green-900 {
background-color: var(--color-green-900);
}
.bg-green-900\/40 { .bg-green-900\/40 {
background-color: color-mix(in srgb, oklch(39.3% 0.095 152.535) 40%, transparent); background-color: color-mix(in srgb, oklch(39.3% 0.095 152.535) 40%, transparent);
@supports (color: color-mix(in lab, red, red)) { @supports (color: color-mix(in lab, red, red)) {
@@ -1550,6 +1598,9 @@
.bg-red-600 { .bg-red-600 {
background-color: var(--color-red-600); background-color: var(--color-red-600);
} }
.bg-red-900 {
background-color: var(--color-red-900);
}
.bg-red-900\/40 { .bg-red-900\/40 {
background-color: color-mix(in srgb, oklch(39.6% 0.141 25.723) 40%, transparent); background-color: color-mix(in srgb, oklch(39.6% 0.141 25.723) 40%, transparent);
@supports (color: color-mix(in lab, red, red)) { @supports (color: color-mix(in lab, red, red)) {
@@ -1607,6 +1658,9 @@
.bg-yellow-600 { .bg-yellow-600 {
background-color: var(--color-yellow-600); background-color: var(--color-yellow-600);
} }
.bg-yellow-900 {
background-color: var(--color-yellow-900);
}
.bg-yellow-900\/40 { .bg-yellow-900\/40 {
background-color: color-mix(in srgb, oklch(42.1% 0.095 57.708) 40%, transparent); background-color: color-mix(in srgb, oklch(42.1% 0.095 57.708) 40%, transparent);
@supports (color: color-mix(in lab, red, red)) { @supports (color: color-mix(in lab, red, red)) {
@@ -1625,6 +1679,10 @@
--tw-gradient-position: to top in oklab; --tw-gradient-position: to top in oklab;
background-image: linear-gradient(var(--tw-gradient-stops)); background-image: linear-gradient(var(--tw-gradient-stops));
} }
.from-black {
--tw-gradient-from: var(--color-black);
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.from-black\/60 { .from-black\/60 {
--tw-gradient-from: color-mix(in srgb, #000 60%, transparent); --tw-gradient-from: color-mix(in srgb, #000 60%, transparent);
@supports (color: color-mix(in lab, red, red)) { @supports (color: color-mix(in lab, red, red)) {
@@ -1685,6 +1743,10 @@
--tw-gradient-from: var(--color-red-500); --tw-gradient-from: var(--color-red-500);
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
} }
.from-secondary {
--tw-gradient-from: var(--color-secondary);
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.from-secondary\/20 { .from-secondary\/20 {
--tw-gradient-from: color-mix(in srgb, #e11d48 20%, transparent); --tw-gradient-from: color-mix(in srgb, #e11d48 20%, transparent);
@supports (color: color-mix(in lab, red, red)) { @supports (color: color-mix(in lab, red, red)) {
@@ -1720,6 +1782,10 @@
--tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-via-stops); --tw-gradient-stops: var(--tw-gradient-via-stops);
} }
.to-accent {
--tw-gradient-to: var(--color-accent);
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.to-accent\/20 { .to-accent\/20 {
--tw-gradient-to: color-mix(in srgb, #8b5cf6 20%, transparent); --tw-gradient-to: color-mix(in srgb, #8b5cf6 20%, transparent);
@supports (color: color-mix(in lab, red, red)) { @supports (color: color-mix(in lab, red, red)) {
@@ -1849,6 +1915,9 @@
.px-8 { .px-8 {
padding-inline: calc(var(--spacing) * 8); padding-inline: calc(var(--spacing) * 8);
} }
.py-0 {
padding-block: calc(var(--spacing) * 0);
}
.py-0\.5 { .py-0\.5 {
padding-block: calc(var(--spacing) * 0.5); padding-block: calc(var(--spacing) * 0.5);
} }
@@ -2185,6 +2254,9 @@
.italic { .italic {
font-style: italic; font-style: italic;
} }
.underline {
text-decoration-line: underline;
}
.underline-offset-4 { .underline-offset-4 {
text-underline-offset: 4px; text-underline-offset: 4px;
} }

View File

@@ -1,45 +1,61 @@
{% comment %} {% comment %}
Ride Card Component - Django Cotton Version Ride Card Component - Django Cotton Version
A comprehensive ride card component with image handling, status badges, and feature displays. A comprehensive ride card component with image handling, status badges, feature displays,
Includes all ride statistics, special features, and manufacturer information. and robust URL generation that supports both global and park-specific URL patterns.
Includes graceful handling of missing slugs to prevent 500 errors.
Usage Examples: Usage Examples:
Basic usage: Basic usage (default global URL pattern):
<c-ride_card ride=ride /> <c-ride_card ride=ride />
Park-specific URL pattern:
<c-ride_card ride=ride url_variant="park" />
With custom CSS classes: With custom CSS classes:
<c-ride_card <c-ride_card
ride=ride ride=ride
url_variant="global"
class="custom-class" class="custom-class"
/> />
With custom image fallback: With custom image fallback:
<c-ride_card <c-ride_card
ride=ride ride=ride
url_variant="park"
fallback_gradient="from-red-500 to-blue-600" fallback_gradient="from-red-500 to-blue-600"
/> />
Parameters: Parameters:
- ride: Ride object (required) - ride: Ride object (required)
- url_variant: URL pattern type - 'global' (default) or 'park' (optional)
- class: Additional CSS classes (optional) - class: Additional CSS classes (optional)
- fallback_gradient: Custom gradient for image fallback (default: "from-blue-500 to-purple-600") - fallback_gradient: Custom gradient for image fallback (default: "from-blue-500 to-purple-600")
URL Pattern Logic:
- If url_variant='global' and ride.slug exists: uses rides:ride_detail with ride.slug
- If url_variant='park' and both ride.park.slug and ride.slug exist: uses parks:rides:ride_detail with ride.park.slug, ride.slug
- If no valid URL can be generated: renders ride name as plain text (no link)
Features: Features:
- Graceful handling of missing slugs (prevents NoReverseMatch errors)
- Support for both global and park-specific URL patterns
- Image handling with gradient fallback backgrounds - Image handling with gradient fallback backgrounds
- Status badges with proper color coding (operating, closed_temporarily, closed_permanently, under_construction) - Status badges with proper color coding (operating, closed_temporarily, closed_permanently, under_construction)
- Ride name with link to detail page - Ride name with conditional linking based on slug availability
- Category and park information display - Category and park information display
- Statistics grid for height, speed, capacity, duration - Statistics grid for height, speed, capacity, duration
- Special features badges (inversions, launches, track_type) - Special features badges (inversions, launches, track_type)
- Opening date and manufacturer/designer information - Opening date and manufacturer/designer information
- Responsive design with hover effects - Responsive design with hover effects
- Modern Tailwind styling and animations - Modern Tailwind styling and animations
- Backwards compatibility with existing usage
{% endcomment %} {% endcomment %}
<c-vars <c-vars
ride="" ride=""
url_variant="global"
class="" class=""
fallback_gradient="from-blue-500 to-purple-600" fallback_gradient="from-blue-500 to-purple-600"
/> />
@@ -94,10 +110,21 @@ Features:
<!-- Name and category --> <!-- Name and category -->
<div class="mb-3"> <div class="mb-3">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-1"> <h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-1">
{% comment %}Robust URL generation with missing slug handling{% endcomment %}
{% if url_variant == 'park' and ride.park and ride.park.slug and ride.slug %}
<a href="{% url 'parks:rides:ride_detail' ride.park.slug ride.slug %}"
class="hover:text-blue-600 dark:hover:text-blue-400 transition-colors">
{{ ride.name }}
</a>
{% elif url_variant == 'global' and ride.slug %}
<a href="{% url 'rides:ride_detail' ride.slug %}" <a href="{% url 'rides:ride_detail' ride.slug %}"
class="hover:text-blue-600 dark:hover:text-blue-400 transition-colors"> class="hover:text-blue-600 dark:hover:text-blue-400 transition-colors">
{{ ride.name }} {{ ride.name }}
</a> </a>
{% else %}
{% comment %}No valid URL can be generated - render as plain text{% endcomment %}
{{ ride.name }}
{% endif %}
</h3> </h3>
<div class="flex items-center text-sm text-gray-600 dark:text-gray-400"> <div class="flex items-center text-sm text-gray-600 dark:text-gray-400">
<span class="inline-flex items-center px-2 py-1 rounded-md text-xs font-medium bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300 mr-2"> <span class="inline-flex items-center px-2 py-1 rounded-md text-xs font-medium bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300 mr-2">