# ThrillWiki Project Documentation

## Table of Contents

1. [Project Overview](#project-overview)
2. [Technical Stack and Architecture](#technical-stack-and-architecture)
3. [Database Models and Relationships](#database-models-and-relationships)
4. [Visual Theme and Design System](#visual-theme-and-design-system)
5. [Frontend Implementation Patterns](#frontend-implementation-patterns)
6. [User Experience and Key Features](#user-experience-and-key-features)
7. [Page Structure and Templates](#page-structure-and-templates)
8. [Services and Business Logic](#services-and-business-logic)
9. [Development Workflow](#development-workflow)
10. [API Endpoints and URL Structure](#api-endpoints-and-url-structure)

---

## Project Overview

ThrillWiki is a sophisticated Django-based web application designed for theme park and roller coaster enthusiasts. It provides comprehensive information management for parks, rides, companies, and user-generated content with advanced features including geographic mapping, moderation systems, and real-time interactions.

### Key Characteristics

- **Enterprise-Grade Architecture**: Service-oriented design with clear separation of concerns
- **Modern Frontend**: HTMX + Alpine.js for dynamic interactions without heavy JavaScript frameworks
- **Geographic Intelligence**: PostGIS integration for location-based features and mapping
- **Content Moderation**: Comprehensive workflow for user-generated content approval
- **Audit Trail**: Complete history tracking using django-pghistory
- **Responsive Design**: Mobile-first approach with sophisticated dark theme support

---

## Technical Stack and Architecture

### Core Technologies

| Component | Technology | Version | Purpose |
|-----------|------------|---------|---------|
| **Backend Framework** | Django | 5.0+ | Main web framework |
| **Database** | PostgreSQL + PostGIS | Latest | Relational database with geographic extension |
| **Frontend** | HTMX + Alpine.js | 1.9.6 + Latest | Dynamic interactions and client-side behavior |
| **Styling** | Tailwind CSS | Latest | Utility-first CSS framework |
| **Package Manager** | UV | Latest | Python dependency management |
| **Authentication** | Django Allauth | 0.60.1+ | OAuth and user management |
| **History Tracking** | django-pghistory | 3.5.2+ | Audit trails and versioning |
| **Testing** | Pytest + Playwright | Latest | Unit and E2E testing |

### Architecture Patterns

#### Service-Oriented Architecture

```
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Presentation  │    │    Business     │    │      Data       │
│     Layer       │    │     Logic       │    │     Layer       │
├─────────────────┤    ├─────────────────┤    ├─────────────────┤
│ • Templates     │◄──►│ • Services      │◄──►│ • Models        │
│ • Views         │    │ • Map Service   │    │ • Database      │
│ • HTMX/Alpine   │    │ • Search        │    │ • PostGIS       │
│ • Tailwind CSS  │    │ • Moderation    │    │ • Caching       │
└─────────────────┘    └─────────────────┘    └─────────────────┘
```

#### Django App Organization

The project follows a domain-driven design approach with clear app boundaries:

```
thrillwiki_django_no_react/
├── core/                   # Core business logic and shared services
│   ├── services/          # Unified map service, clustering, caching
│   ├── search/            # Search functionality
│   ├── mixins/            # Reusable view mixins
│   └── history/           # History tracking utilities
├── accounts/              # User management and authentication
├── parks/                 # Theme park entities
│   └── models/            # Park, Company, Location models
├── rides/                 # Ride entities and ride-specific logic
│   └── models/            # Ride, RideModel, Company models
├── location/              # Geographic location handling
├── media/                 # Media file management and photo handling
├── moderation/            # Content moderation workflow
├── email_service/         # Email handling and notifications
└── static/                # Frontend assets (CSS, JS, images)
```

### Package Management with UV

ThrillWiki exclusively uses UV for Python package management, providing:

- **Faster dependency resolution**: Significantly faster than pip
- **Lock file support**: Ensures reproducible environments
- **Virtual environment management**: Automatic environment handling
- **Cross-platform compatibility**: Consistent behavior across development environments

#### Critical Commands

```bash
# Add new dependencies
uv add <package>

# Django management (NEVER use python manage.py)
uv run manage.py <command>

# Development server startup
lsof -ti :8000 | xargs kill -9; find . -type d -name "__pycache__" -exec rm -r {} +; uv run manage.py tailwind runserver
```

---

## Database Models and Relationships

### Entity Relationship Architecture

ThrillWiki implements a sophisticated entity relationship model that enforces business rules at the database level:

#### Core Business Rules (from .clinerules)

1. **Park Relationships**
   - Parks MUST have an Operator (required relationship)
   - Parks MAY have a PropertyOwner (optional, usually same as Operator)
   - Parks CANNOT directly reference Company entities

2. **Ride Relationships**
   - Rides MUST belong to a Park (required relationship)
   - Rides MAY have a Manufacturer (optional relationship)
   - Rides MAY have a Designer (optional relationship)
   - Rides CANNOT directly reference Company entities

3. **Entity Definitions**
   - **Operators**: Companies that operate theme parks
   - **PropertyOwners**: Companies that own park property (new concept)
   - **Manufacturers**: Companies that manufacture rides
   - **Designers**: Companies/individuals that design rides

### Core Model Structure

#### Park Models (`parks/models/`)

```python
# Park Entity
class Park(TrackedModel):
    # Core identifiers
    name = CharField(max_length=255)
    slug = SlugField(max_length=255, unique=True)
    
    # Business relationships (enforced by .clinerules)
    operator = ForeignKey('Company', related_name='operated_parks')
    property_owner = ForeignKey('Company', related_name='owned_parks', null=True)
    
    # Operational data
    status = CharField(choices=STATUS_CHOICES, default="OPERATING")
    opening_date = DateField(null=True, blank=True)
    size_acres = DecimalField(max_digits=10, decimal_places=2)
    
    # Statistics
    average_rating = DecimalField(max_digits=3, decimal_places=2)
    ride_count = IntegerField(null=True, blank=True)
    coaster_count = IntegerField(null=True, blank=True)
```

#### Ride Models (`rides/models/`)

```python
# Ride Entity
class Ride(TrackedModel):
    # Core identifiers
    name = CharField(max_length=255)
    slug = SlugField(max_length=255)
    
    # Required relationships (enforced by .clinerules)
    park = ForeignKey('parks.Park', related_name='rides')
    
    # Optional business relationships
    manufacturer = ForeignKey('Company', related_name='manufactured_rides')
    designer = ForeignKey('Company', related_name='designed_rides')
    ride_model = ForeignKey('RideModel', related_name='rides')
    
    # Classification
    category = CharField(choices=CATEGORY_CHOICES)
    status = CharField(choices=STATUS_CHOICES, default='OPERATING')
```

#### Company Models (Shared across apps)

```python
# Company Entity (supports multiple roles)
class Company(TrackedModel):
    class CompanyRole(TextChoices):
        OPERATOR = 'OPERATOR', 'Park Operator'
        PROPERTY_OWNER = 'PROPERTY_OWNER', 'Property Owner'
        MANUFACTURER = 'MANUFACTURER', 'Ride Manufacturer'
        DESIGNER = 'DESIGNER', 'Ride Designer'
    
    name = CharField(max_length=255)
    slug = SlugField(max_length=255, unique=True)
    roles = ArrayField(CharField(choices=CompanyRole.choices))
```

### Geographic Models (`location/models/`)

```python
# Generic Location Model
class Location(TrackedModel):
    # Generic relationship (can attach to any model)
    content_type = ForeignKey(ContentType)
    object_id = PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
    
    # Geographic data (dual storage for compatibility)
    latitude = DecimalField(max_digits=9, decimal_places=6)
    longitude = DecimalField(max_digits=9, decimal_places=6)
    point = PointField(srid=4326)  # PostGIS geometry field
    
    # Address components
    street_address = CharField(max_length=255)
    city = CharField(max_length=100)
    state = CharField(max_length=100)
    country = CharField(max_length=100)
    postal_code = CharField(max_length=20)
```

### History Tracking with pghistory

Every critical model uses `@pghistory.track()` decoration for comprehensive audit trails:

```python
@pghistory.track()
class Park(TrackedModel):
    # All field changes are automatically tracked
    # Creates parallel history tables with full change logs
```

### Media and Content Models

```python
# Generic Photo Model
class Photo(TrackedModel):
    # Generic relationship support
    content_type = ForeignKey(ContentType)
    object_id = PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
    
    # Media handling
    image = ImageField(upload_to=photo_upload_path, storage=MediaStorage())
    is_primary = BooleanField(default=False)
    is_approved = BooleanField(default=False)
    
    # Metadata extraction
    date_taken = DateTimeField(null=True)  # Auto-extracted from EXIF
    uploaded_by = ForeignKey(User, related_name='uploaded_photos')
```

### User and Authentication Models

```python
# Extended User Model
class User(AbstractUser):
    class Roles(TextChoices):
        USER = 'USER', 'User'
        MODERATOR = 'MODERATOR', 'Moderator'
        ADMIN = 'ADMIN', 'Admin'
        SUPERUSER = 'SUPERUSER', 'Superuser'
    
    # Immutable identifier
    user_id = CharField(max_length=10, unique=True, editable=False)
    
    # Permission system
    role = CharField(choices=Roles.choices, default=Roles.USER)
    
    # User preferences
    theme_preference = CharField(choices=ThemePreference.choices)
```

---

## Visual Theme and Design System

### Design Philosophy

ThrillWiki implements a sophisticated **dark-first design system** with vibrant accent colors that reflect the excitement of theme parks and roller coasters.

#### Color Palette

```css
:root {
  --primary: #4f46e5;     /* Vibrant indigo */
  --secondary: #e11d48;   /* Vibrant rose */
  --accent: #8b5cf6;      /* Purple accent */
}
```

#### Background Gradients

```css
/* Light theme */
body {
  background: linear-gradient(to bottom right, 
    white, 
    rgb(239 246 255), /* blue-50 */
    rgb(238 242 255)  /* indigo-50 */
  );
}

/* Dark theme */
body.dark {
  background: linear-gradient(to bottom right,
    rgb(3 7 18),      /* gray-950 */
    rgb(49 46 129),   /* indigo-950 */
    rgb(59 7 100)     /* purple-950 */
  );
}
```

### Tailwind CSS Configuration

#### Custom Configuration (`tailwind.config.js`)

```javascript
module.exports = {
  darkMode: 'class',  // Class-based dark mode
  content: [
    './templates/**/*.html',
    './assets/css/src/**/*.css',
  ],
  theme: {
    extend: {
      colors: {
        primary: '#4f46e5',
        secondary: '#e11d48', 
        accent: '#8b5cf6'
      },
      fontFamily: {
        'sans': ['Poppins', 'sans-serif'],
      },
    },
  },
  plugins: [
    require("@tailwindcss/typography"),
    require("@tailwindcss/forms"),
    require("@tailwindcss/aspect-ratio"),
    require("@tailwindcss/container-queries"),
    // Custom HTMX variants
    plugin(function ({ addVariant }) {
      addVariant("htmx-settling", ["&.htmx-settling", ".htmx-settling &"]);
      addVariant("htmx-request", ["&.htmx-request", ".htmx-request &"]);
      addVariant("htmx-swapping", ["&.htmx-swapping", ".htmx-swapping &"]);
      addVariant("htmx-added", ["&.htmx-added", ".htmx-added &"]);
    }),
  ],
}
```

### Component System (`static/css/src/input.css`)

#### Button Components

```css
.btn-primary {
  @apply inline-flex items-center px-6 py-2.5 border border-transparent 
         rounded-full shadow-md text-sm font-medium text-white 
         bg-gradient-to-r from-primary to-secondary 
         hover:from-primary/90 hover:to-secondary/90 
         focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary/50 
         transform hover:scale-105 transition-all;
}

.btn-secondary {
  @apply inline-flex items-center px-6 py-2.5 border border-gray-200 
         dark:border-gray-700 rounded-full shadow-md text-sm font-medium 
         text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-800 
         hover:bg-gray-50 dark:hover:bg-gray-700 
         transform hover:scale-105 transition-all;
}
```

#### Navigation Components

```css
.nav-link {
  @apply flex items-center text-gray-700 dark:text-gray-200 
         px-6 py-2.5 rounded-lg font-medium border border-transparent 
         hover:border-primary/20 dark:hover:border-primary/30
         hover:text-primary dark:text-primary 
         hover:bg-primary/10 dark:bg-primary/20;
}
```

#### Card System

```css
.card {
  @apply p-6 bg-white dark:bg-gray-800 border rounded-lg shadow-lg 
         border-gray-200/50 dark:border-gray-700/50;
}

.card-hover {
  @apply transition-transform transform hover:-translate-y-1;
}
```

#### Responsive Grid System

```css
/* Adaptive grid with content-aware sizing */
.grid-adaptive {
  @apply grid gap-6;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}

/* Stats grid with even layouts */
.grid-stats {
  @apply grid gap-4;
  grid-template-columns: repeat(2, 1fr);  /* Mobile: 2 columns */
}

@media (min-width: 1024px) {
  .grid-stats {
    grid-template-columns: repeat(3, 1fr);  /* Desktop: 3 columns */
  }
}

@media (min-width: 1280px) {
  .grid-stats {
    grid-template-columns: repeat(5, 1fr);  /* Large: 5 columns */
  }
}
```

### Dark Mode Implementation

#### Theme Toggle System

The theme system provides:
- **Automatic detection** of system preference
- **Manual toggle** with persistent storage
- **Flash prevention** during page load
- **Smooth transitions** between themes

```javascript
// Theme initialization (prevents flash)
let theme = localStorage.getItem("theme");
if (!theme) {
  theme = window.matchMedia("(prefers-color-scheme: dark)").matches
    ? "dark" : "light";
  localStorage.setItem("theme", theme);
}
if (theme === "dark") {
  document.documentElement.classList.add("dark");
}
```

#### CSS Custom Properties for Theme Switching

```css
/* Theme-aware components */
.auth-card {
  @apply w-full max-w-md p-8 mx-auto border shadow-xl 
         bg-white/90 dark:bg-gray-800/90 rounded-2xl backdrop-blur-sm 
         border-gray-200/50 dark:border-gray-700/50;
}

/* Status badges with theme support */
.status-operating {
  @apply text-green-800 bg-green-100 dark:bg-green-700 dark:text-green-50;
}

.status-closed {
  @apply text-red-800 bg-red-100 dark:bg-red-700 dark:text-red-50;
}
```

---

## Frontend Implementation Patterns

### HTMX Integration Patterns

ThrillWiki leverages HTMX for dynamic interactions while maintaining server-side rendering benefits:

#### Dynamic Content Loading

```html
<!-- Search with live results -->
<form hx-get="{% url 'parks:search' %}" 
      hx-target="#search-results" 
      hx-trigger="input changed delay:300ms">
  <input type="text" name="q" placeholder="Search parks and rides...">
</form>

<div id="search-results" 
     hx-indicator="#loading-spinner">
  <!-- Results loaded dynamically -->
</div>
```

#### Modal Interactions

```html
<!-- HTMX-powered modals -->
<div hx-get="{% url 'account_login' %}"
     hx-target="body"
     hx-swap="beforeend"
     class="cursor-pointer menu-item">
  <span>Login</span>
</div>
```

#### Custom HTMX Variants

```css
/* Loading states */
.htmx-request .htmx-indicator {
  display: block;
}

/* Transition effects */
.htmx-settling {
  opacity: 0.7;
  transition: opacity 0.3s ease;
}

.htmx-swapping {
  transform: scale(0.98);
  transition: transform 0.2s ease;
}
```

### Alpine.js Patterns

Alpine.js handles client-side state management and interactions:

#### Dropdown Components

```html
<div x-data="{ open: false }" 
     @click.outside="open = false">
  <!-- Profile dropdown -->
  <img @click="open = !open" 
       src="{{ user.profile.avatar.url }}" 
       class="cursor-pointer">
  
  <div x-cloak
       x-show="open"
       x-transition:enter="transition ease-out duration-100"
       x-transition:enter-start="transform opacity-0 scale-95"
       x-transition:enter-end="transform opacity-100 scale-100">
    <!-- Dropdown content -->
  </div>
</div>
```

#### Modal Management

```html
<div x-data="{ 
       show: false, 
       editingPhoto: null,
       init() { this.editingPhoto = { caption: '' }; }
     }"
     @show-photo-upload.window="show = true; init()">
  <!-- Modal implementation -->
</div>
```

### JavaScript Architecture (`static/js/`)

#### Modular JavaScript Organization

```
static/js/
├── main.js              # Core functionality (theme, navigation)
├── alerts.js            # Alert system management
├── photo-gallery.js     # Photo gallery interactions
├── park-map.js          # Leaflet map integration
├── location-autocomplete.js  # Geographic search
└── alpine.min.js        # Alpine.js framework
```

#### Theme Management (`static/js/main.js`)

```javascript
// Theme handling with system preference detection
document.addEventListener('DOMContentLoaded', () => {
  const themeToggle = document.getElementById('theme-toggle');
  const html = document.documentElement;
  
  // Initialize toggle state
  if (themeToggle) {
    themeToggle.checked = html.classList.contains('dark');
    
    // Handle toggle changes
    themeToggle.addEventListener('change', function() {
      const isDark = this.checked;
      html.classList.toggle('dark', isDark);
      localStorage.setItem('theme', isDark ? 'dark' : 'light');
    });
    
    // Listen for system theme changes
    const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
    mediaQuery.addEventListener('change', (e) => {
      if (!localStorage.getItem('theme')) {
        const isDark = e.matches;
        html.classList.toggle('dark', isDark);
        themeToggle.checked = isDark;
      }
    });
  }
});
```

#### Mobile Navigation

```javascript
// Mobile menu with smooth transitions
const toggleMenu = () => {
  isMenuOpen = !isMenuOpen;
  mobileMenu.classList.toggle('show', isMenuOpen);
  mobileMenuBtn.setAttribute('aria-expanded', isMenuOpen.toString());
  
  // Update icon
  const icon = mobileMenuBtn.querySelector('i');
  icon.classList.remove(isMenuOpen ? 'fa-bars' : 'fa-times');
  icon.classList.add(isMenuOpen ? 'fa-times' : 'fa-bars');
};
```

### Template System Patterns

#### Component-Based Architecture

```
templates/
├── base/
│   └── base.html            # Base template with navigation
├── core/
│   ├── search/
│   │   ├── components/      # Search UI components
│   │   ├── layouts/         # Search layout templates
│   │   └── partials/        # Reusable search elements
├── parks/
│   ├── partials/
│   │   ├── park_list_item.html     # Reusable park card
│   │   ├── park_actions.html       # Action buttons
│   │   └── park_stats.html         # Statistics display
│   ├── park_detail.html            # Main park page
│   └── park_list.html              # Park listing page
└── media/
    └── partials/
        ├── photo_display.html      # Photo gallery component
        └── photo_upload.html       # Upload interface
```

#### Template Inheritance Pattern

```html
<!-- base/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <title>{% block title %}ThrillWiki{% endblock %}</title>
  {% block extra_head %}{% endblock %}
</head>
<body class="bg-gradient-to-br from-white via-blue-50 to-indigo-50 
             dark:from-gray-950 dark:via-indigo-950 dark:to-purple-950">
  
  {% include "base/navigation.html" %}
  
  <main class="container flex-grow px-6 py-8 mx-auto">
    {% block content %}{% endblock %}
  </main>
  
  {% block extra_js %}{% endblock %}
</body>
</html>
```

#### HTMX Template Patterns

```html
<!-- Responsive template selection -->
{% extends "base/base.html" %}

{% block content %}
<div hx-get="{% url 'parks:park_list' %}"
     hx-target="#park-grid"
     hx-swap="innerHTML"
     hx-trigger="load, view-change from:body">
  
  <div id="park-grid" class="grid-adaptive">
    {% include "parks/partials/park_list_item.html" %}
  </div>
</div>
{% endblock %}
```

---

## User Experience and Key Features

### Navigation and Information Architecture

#### Primary Navigation Structure

```
ThrillWiki Navigation
├── Home                    # Dashboard with featured content
├── Parks                   # Theme park directory
│   ├── Browse Parks       # Filterable park listings
│   ├── Add New Park       # User contribution form
│   └── [Park Detail]      # Individual park pages
├── Rides                   # Ride directory (global)
│   ├── Browse Rides       # Cross-park ride search
│   ├── Add New Ride       # User contribution form
│   └── [Ride Detail]      # Individual ride pages
├── Search                  # Universal search interface
└── User Account
    ├── Profile            # User profile and stats
    ├── Settings           # Preferences and account
    ├── Moderation         # Content review (if authorized)
    └── Admin              # System administration (if authorized)
```

#### Responsive Navigation Patterns

- **Desktop**: Full horizontal navigation with search bar
- **Tablet**: Collapsible navigation with maintained search functionality
- **Mobile**: Hamburger menu with slide-out panel

### Core Feature Set

#### 1. Park Management System

**Park Detail Pages** provide comprehensive information:

```
Park Information Hierarchy
├── Header Section
│   ├── Park name and location
│   ├── Status badge (Operating, Closed, etc.)
│   ├── Average rating display
│   └── Quick action buttons
├── Statistics Dashboard
│   ├── Operator information (priority display)
│   ├── Property owner (if different)
│   ├── Total ride count (linked)
│   ├── Roller coaster count
│   ├── Opening date
│   └── Website link
├── Content Sections
│   ├── Photo gallery (if photos exist)
│   ├── About section (description)
│   ├── Rides & Attractions (preview list)
│   └── Location map (if coordinates available)
└── Additional Information
    ├── History timeline
    ├── Related parks
    └── User contributions
```

**Key UX Features**:
- **Smart statistics layout**: Responsive grid that prevents awkward spacing
- **Priority content placement**: Operator information prominently featured
- **Contextual actions**: Edit/moderate buttons appear based on user permissions
- **Progressive disclosure**: Detailed information revealed as needed

#### 2. Advanced Search and Filtering

**Unified Search System** supports:

- **Cross-content search**: Parks, rides, companies in single interface
- **Geographic filtering**: Search within specific regions or distances
- **Attribute filtering**: Status, ride types, ratings, opening dates
- **Real-time results**: HTMX-powered instant search feedback

**Search Result Patterns**:
```html
<!-- Search results with context -->
<div class="search-result-item">
  <div class="result-type-badge">Park</div>
  <h3 class="result-title">{{ park.name }}</h3>
  <p class="result-location">{{ park.formatted_location }}</p>
  <div class="result-stats">
    <span>{{ park.ride_count }} rides</span>
    <span>{{ park.get_status_display }}</span>
  </div>
</div>
```

#### 3. Geographic and Mapping Features

**Unified Map Service** provides:

- **Multi-layer mapping**: Parks, rides, and companies on single map
- **Intelligent clustering**: Zoom-level appropriate point grouping
- **Performance optimization**: Smart caching and result limiting
- **Geographic bounds**: Efficient spatial queries using PostGIS

**Map Integration Pattern**:
```javascript
// Park detail map initialization
document.addEventListener('DOMContentLoaded', function() {
  {% with location=park.location.first %}
  initParkMap({{ location.latitude }}, {{ location.longitude }}, "{{ park.name }}");
  {% endwith %}
});
```

#### 4. Content Moderation Workflow

**Submission Process**:

```
User Contribution Flow
├── Content Creation
│   ├── Form submission (parks, rides, photos)
│   ├── Validation and sanitization
│   └── EditSubmission/PhotoSubmission creation
├── Review Process
│   ├── Moderator dashboard listing
│   ├── Side-by-side comparison view
│   ├── Edit capability before approval
│   └── Approval/rejection with notes
├── Publication
│   ├── Automatic publication for moderators
│   ├── Content integration into main database
│   └── User notification system
└── History Tracking
    ├── Complete audit trail
    ├── Revert capability
    └── Performance metrics
```

**Moderation Features**:
- **Auto-approval**: Moderators bypass review process
- **Edit before approval**: Moderators can refine submissions
- **Batch operations**: Efficient handling of multiple submissions
- **Escalation system**: Complex cases forwarded to administrators

#### 5. Photo and Media Management

**Photo System Features**:

- **Multi-format support**: JPEG, PNG with automatic optimization
- **EXIF extraction**: Automatic date/time capture from metadata
- **Approval workflow**: Moderation for user-uploaded content
- **Smart storage**: Organized directory structure by content type
- **Primary photo designation**: Featured image selection per entity

**Upload Interface**:
```html
<!-- Modal photo upload -->
<div x-data="photoUploadModal()" 
     @show-photo-upload.window="show = true">
  <form hx-post="{% url 'media:upload' %}" 
        hx-encoding="multipart/form-data">
    {% csrf_token %}
    <input type="file" multiple accept="image/*">
    <input type="text" placeholder="Caption (optional)">
  </form>
</div>
```

#### 6. User Authentication and Profiles

**Authentication Features**:
- **Social OAuth**: Google and Discord integration
- **Custom profiles**: Display names, avatars, bio information
- **Role-based permissions**: User, Moderator, Admin, Superuser levels
- **Theme preferences**: User-specific dark/light mode settings

**Profile Statistics**:
```html
<!-- User profile stats -->
<div class="profile-stats grid-stats">
  <div class="stat-card">
    <span class="stat-value">{{ profile.coaster_credits }}</span>
    <span class="stat-label">Coaster Credits</span>
  </div>
  <div class="stat-card">
    <span class="stat-value">{{ profile.dark_ride_credits }}</span>
    <span class="stat-label">Dark Rides</span>
  </div>
  <!-- Additional stat types... -->
</div>
```

#### 7. History and Audit System

**Change Tracking Features**:
- **Complete audit trails**: Every modification recorded
- **Diff visualization**: Before/after comparisons
- **User attribution**: Change tracking by user
- **Revert capability**: Rollback to previous versions
- **Performance monitoring**: Query and response time tracking

### Accessibility and Responsive Design

#### Mobile-First Approach

- **Responsive breakpoints**: 540px, 768px, 1024px, 1280px+
- **Touch-friendly interfaces**: Appropriate button sizes and spacing
- **Optimized content hierarchy**: Essential information prioritized on small screens

#### Accessibility Features

- **Semantic HTML**: Proper heading structure and landmarks
- **ARIA labels**: Screen reader support for interactive elements
- **Keyboard navigation**: Full keyboard accessibility
- **Color contrast**: WCAG AA compliant color schemes
- **Focus indicators**: Clear focus states for interactive elements

---

## Page Structure and Templates

### Template Hierarchy and Organization

#### Base Template Architecture

```html
<!-- templates/base/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="csrf-token" content="{{ csrf_token }}">
  <title>{% block title %}ThrillWiki{% endblock %}</title>

  <!-- Theme prevention script (prevents flash) -->
  <script>
    let theme = localStorage.getItem("theme");
    if (!theme) {
      theme = window.matchMedia("(prefers-color-scheme: dark)").matches
        ? "dark" : "light";
      localStorage.setItem("theme", theme);
    }
    if (theme === "dark") {
      document.documentElement.classList.add("dark");
    }
  </script>

  <!-- External dependencies -->
  <script src="https://unpkg.com/htmx.org@1.9.6"></script>
  <script defer src="{% static 'js/alpine.min.js' %}"></script>
  
  <!-- Styling -->
  <link href="{% static 'css/tailwind.css' %}" rel="stylesheet">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">

  {% block extra_head %}{% endblock %}
</head>
<body class="flex flex-col min-h-screen text-gray-900 
             bg-gradient-to-br from-white via-blue-50 to-indigo-50 
             dark:from-gray-950 dark:via-indigo-950 dark:to-purple-950 
             dark:text-white">
  
  {% include "base/header.html" %}
  
  {% include "base/flash_messages.html" %}
  
  <main class="container flex-grow px-6 py-8 mx-auto">
    {% block content %}{% endblock %}
  </main>
  
  {% include "base/footer.html" %}
  
  <script src="{% static 'js/main.js' %}"></script>
  {% block extra_js %}{% endblock %}
</body>
</html>
```

#### Component-Based Template System

##### Navigation Component (`templates/base/header.html`)

```html
<header class="sticky top-0 z-40 border-b shadow-lg 
               bg-white/90 dark:bg-gray-800/90 backdrop-blur-lg 
               border-gray-200/50 dark:border-gray-700/50">
  <nav class="container mx-auto nav-container">
    <div class="flex items-center justify-between">
      
      <!-- Logo -->
      <div class="flex items-center">
        <a href="{% url 'home' %}" 
           class="font-bold text-transparent transition-transform site-logo 
                  bg-gradient-to-r from-primary to-secondary bg-clip-text 
                  hover:scale-105">
          ThrillWiki
        </a>
      </div>

      <!-- Main Navigation Links -->
      <div class="flex items-center space-x-2 sm:space-x-4">
        <a href="{% url 'parks:park_list' %}" class="nav-link">
          <i class="fas fa-map-marker-alt"></i>
          <span>Parks</span>
        </a>
        <a href="{% url 'rides:global_ride_list' %}" class="nav-link">
          <i class="fas fa-rocket"></i>
          <span>Rides</span>
        </a>
      </div>

      <!-- Search Bar (Desktop) -->
      <div class="flex-1 hidden max-w-md mx-8 lg:flex">
        <form action="{% url 'search:search' %}" method="get" class="w-full">
          <div class="relative">
            <input type="text" name="q" 
                   placeholder="Search parks and rides..."
                   class="form-input">
          </div>
        </form>
      </div>

      <!-- User Menu and Theme Toggle -->
      <div class="flex items-center space-x-2 sm:space-x-6">
        {% include "base/theme_toggle.html" %}
        {% include "base/user_menu.html" %}
        {% include "base/mobile_menu_button.html" %}
      </div>
    </div>

    <!-- Mobile Menu -->
    {% include "base/mobile_menu.html" %}
  </nav>
</header>
```

##### Theme Toggle Component

```html
<!-- templates/base/theme_toggle.html -->
<label for="theme-toggle" class="cursor-pointer">
  <input type="checkbox" id="theme-toggle" class="hidden">
  <div class="inline-flex items-center justify-center p-2 
              text-gray-500 transition-colors hover:text-primary 
              dark:text-gray-400 dark:hover:text-primary theme-toggle-btn"
       role="button" aria-label="Toggle dark mode">
    <i class="text-xl fas"></i>
  </div>
</label>
```

### Page-Specific Template Patterns

#### Park Detail Page Structure

```html
<!-- templates/parks/park_detail.html -->
{% extends "base/base.html" %}
{% load static park_tags %}

{% block title %}{{ park.name }} - ThrillWiki{% endblock %}

{% block extra_head %}
{% if park.location.exists %}
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
{% endif %}
{% endblock %}

{% block content %}
<div class="container px-4 mx-auto sm:px-6 lg:px-8">
  
  <!-- Dynamic Action Buttons -->
  <div hx-get="{% url 'parks:park_actions' park.slug %}"
       hx-trigger="load, auth-changed from:body"
       hx-swap="innerHTML">
  </div>

  <!-- Park Header -->
  <div class="p-compact mb-6 bg-white rounded-lg shadow-lg dark:bg-gray-800">
    <div class="text-center">
      <h1 class="text-3xl font-bold text-gray-900 dark:text-white lg:text-4xl">
        {{ park.name }}
      </h1>
      {% if park.formatted_location %}
      <div class="flex items-center justify-center mt-2 text-sm text-gray-600 dark:text-gray-400">
        <i class="mr-1 fas fa-map-marker-alt"></i>
        <p>{{ park.formatted_location }}</p>
      </div>
      {% endif %}
    </div>
  </div>

  <!-- Statistics Grid -->
  <div class="grid-stats mb-6">
    {% include "parks/partials/park_stats.html" %}
  </div>

  <!-- Photo Gallery -->
  {% if park.photos.exists %}
  <div class="p-optimized mb-8 bg-white rounded-lg shadow dark:bg-gray-800">
    <h2 class="mb-4 text-xl font-semibold text-gray-900 dark:text-white">Photos</h2>
    {% include "media/partials/photo_display.html" %}
  </div>
  {% endif %}

  <!-- Main Content Grid -->
  <div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
    
    <!-- Left Column: Description and Rides -->
    <div class="lg:col-span-2">
      {% include "parks/partials/park_description.html" %}
      {% include "parks/partials/park_rides.html" %}
    </div>

    <!-- Right Column: Map and Additional Info -->
    <div class="lg:col-span-1">
      {% include "parks/partials/park_map.html" %}
      {% include "parks/partials/park_history.html" %}
    </div>
  </div>
</div>

<!-- Photo Upload Modal -->
{% include "media/partials/photo_upload_modal.html" %}
{% endblock %}

{% block extra_js %}
<script src="{% static 'js/photo-gallery.js' %}"></script>
{% if park.location.exists %}
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="{% static 'js/park-map.js' %}"></script>
{% endif %}
{% endblock %}
```

#### Reusable Partial Templates

##### Park Statistics Component

```html
<!-- templates/parks/partials/park_stats.html -->
<!-- Operator - Priority Card (First Position) -->
{% if park.operator %}
<div class="bg-white rounded-lg shadow-lg dark:bg-gray-800 p-compact card-stats card-stats-priority">
  <div class="text-center">
    <dt class="text-sm font-semibold text-gray-900 dark:text-white">Operator</dt>
    <dd class="mt-1">
      <a href="{% url 'operators:operator_detail' park.operator.slug %}"
         class="text-sm font-bold text-sky-900 dark:text-sky-400 hover:text-sky-800 dark:hover:text-sky-300">
        {{ park.operator.name }}
      </a>
    </dd>
  </div>
</div>
{% endif %}

<!-- Additional stats cards... -->
<a href="{% url 'parks:rides:ride_list' park.slug %}"
   class="bg-white rounded-lg shadow-lg dark:bg-gray-800 p-compact card-stats transition-transform hover:scale-[1.02]">
  <div class="text-center">
    <dt class="text-sm font-semibold text-gray-900 dark:text-white">Total Rides</dt>
    <dd class="mt-1 text-2xl font-bold text-sky-900 dark:text-sky-400">
      {{ park.ride_count|default:"N/A" }}
    </dd>
  </div>
</a>
```

##### Photo Display Component

```html
<!-- templates/media/partials/photo_display.html -->
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3" 
     x-data="photoGallery()">
  {% for photo in photos %}
  <div class="relative group">
    <img src="{{ photo.image.url }}" 
         alt="{{ photo.alt_text|default:photo.caption }}"
         class="w-full h-48 object-cover rounded-lg shadow-md 
                transition-transform group-hover:scale-105 cursor-pointer"
         @click="openModal('{{ photo.image.url }}', '{{ photo.caption }}')">
    
    {% if photo.caption %}
    <div class="absolute bottom-0 left-0 right-0 p-2 
                bg-gradient-to-t from-black/70 to-transparent rounded-b-lg">
      <p class="text-white text-sm">{{ photo.caption }}</p>
    </div>
    {% endif %}
  </div>
  {% endfor %}
</div>
```

### Form Templates and User Input

#### Dynamic Form Rendering

```html
<!-- templates/parks/park_form.html -->
{% extends "base/base.html" %}

{% block content %}
<div class="max-w-4xl mx-auto">
  <div class="auth-card">
    <h1 class="auth-title">
      {% if is_edit %}Edit Park{% else %}Add New Park{% endif %}
    </h1>
    
    <form method="post" enctype="multipart/form-data" 
          class="space-y-6" 
          hx-post="{% if is_edit %}{% url 'parks:park_edit' object.slug %}{% else %}{% url 'parks:park_create' %}{% endif %}"
          hx-target="#form-container"
          hx-swap="innerHTML">
      {% csrf_token %}
      
      <!-- Dynamic field rendering -->
      {% for field in form %}
      <div class="form-group">
        <label for="{{ field.id_for_label }}" class="form-label">
          {{ field.label }}
          {% if field.field.required %}<span class="text-red-500">*</span>{% endif %}
        </label>
        {{ field|add_class:"form-input" }}
        {% if field.help_text %}
        <div class="form-hint">{{ field.help_text }}</div>
        {% endif %}
        {% if field.errors %}
        <div class="form-error">{{ field.errors }}</div>
        {% endif %}
      </div>
      {% endfor %}
      
      <div class="flex justify-end space-x-4">
        <a href="{% if is_edit %}{{ object.get_absolute_url }}{% else %}{% url 'parks:park_list' %}{% endif %}" 
           class="btn-secondary">Cancel</a>
        <button type="submit" class="btn-primary">
          {% if is_edit %}Update Park{% else %}Add Park{% endif %}
        </button>
      </div>
    </form>
  </div>
</div>
{% endblock %}
```

### Static File Organization

#### Asset Structure

```
static/
├── css/
│   ├── src/
│   │   └── input.css          # Tailwind source
│   ├── tailwind.css           # Compiled Tailwind
│   └── alerts.css             # Custom alert styles
├── js/
│   ├── main.js                # Core functionality
│   ├── alerts.js              # Alert management
│   ├── photo-gallery.js       # Photo interactions
│   ├── park-map.js            # Map functionality
│   ├── location-autocomplete.js # Geographic search
│   └── alpine.min.js          # Alpine.js framework
└── images/
    ├── placeholders/          # Default images
    └── icons/                 # Custom icons
```

#### Template Tag Usage

Custom template tags enhance template functionality:

```html
<!-- Using custom park tags -->
{% load park_tags %}

<!-- Status badge with automatic styling -->
<span class="{% park_status_class park.status %}">
  {{ park.get_status_display }}
</span>

<!-- Rating display with stars -->
{% if park.average_rating %}
{% rating_stars park.average_rating %}
{% endif %}
```

---

## Services and Business Logic

### Unified Map Service Architecture

The `UnifiedMapService` provides the core geographic functionality for ThrillWiki, handling location data for parks, rides, and companies through a sophisticated service layer.

#### Service Architecture Overview

```
UnifiedMapService
├── LocationAbstractionLayer     # Data source abstraction
├── ClusteringService           # Point clustering for performance
├── MapCacheService            # Intelligent caching
└── Data Structures            # Type-safe data containers
```

#### Core Service Implementation

```python
# core/services/map_service.py
class UnifiedMapService:
    """
    Main service orchestrating map data retrieval, filtering, clustering, and caching.
    Provides a unified interface for all location types with performance optimization.
    """
    
    # Performance thresholds
    MAX_UNCLUSTERED_POINTS = 500
    MAX_CLUSTERED_POINTS = 2000
    DEFAULT_ZOOM_LEVEL = 10
    
    def __init__(self):
        self.location_layer = LocationAbstractionLayer()
        self.clustering_service = ClusteringService()
        self.cache_service = MapCacheService()
    
    def get_map_data(
        self,
        bounds: Optional[GeoBounds] = None,
        filters: Optional[MapFilters] = None,
        zoom_level: int = DEFAULT_ZOOM_LEVEL,
        cluster: bool = True,
        use_cache: bool = True
    ) -> MapResponse:
        """
        Primary method for retrieving unified map data with intelligent
        caching, clustering, and performance optimization.
        """
        # Implementation handles cache checking, database queries,
        # smart limiting, clustering decisions, and response caching
        pass
    
    def get_location_details(self, location_type: str, location_id: int) -> Optional[UnifiedLocation]:
        """Get detailed information for a specific location with caching."""
        pass
    
    def search_locations(
        self,
        query: str,
        bounds: Optional[GeoBounds] = None,
        location_types: Optional[Set[LocationType]] = None,
        limit: int = 50
    ) -> List[UnifiedLocation]:
        """Search locations with text query and geographic bounds."""
        pass
```

#### Data Structure System

The service uses type-safe data structures for all map operations:

```python
# core/services/data_structures.py

class LocationType(Enum):
    """Types of locations supported by the map service."""
    PARK = "park"
    RIDE = "ride"
    COMPANY = "company"
    GENERIC = "generic"

@dataclass
class GeoBounds:
    """Geographic boundary box for spatial queries."""
    north: float
    south: float
    east: float
    west: float
    
    def to_polygon(self) -> Polygon:
        """Convert bounds to PostGIS Polygon for database queries."""
        return Polygon.from_bbox((self.west, self.south, self.east, self.north))
    
    def expand(self, factor: float = 1.1) -> 'GeoBounds':
        """Expand bounds by factor for buffer queries."""
        pass

@dataclass
class MapFilters:
    """Filtering options for map queries."""
    location_types: Optional[Set[LocationType]] = None
    park_status: Optional[Set[str]] = None
    ride_types: Optional[Set[str]] = None
    search_query: Optional[str] = None
    min_rating: Optional[float] = None
    has_coordinates: bool = True
    country: Optional[str] = None
    state: Optional[str] = None
    city: Optional[str] = None

@dataclass
class UnifiedLocation:
    """Standardized location representation across all entity types."""
    id: int
    location_type: LocationType
    name: str
    coordinates: Point
    url: str
    additional_data: Dict[str, Any] = field(default_factory=dict)
```

### Moderation System

ThrillWiki implements a comprehensive moderation system for user-generated content edits:

#### Edit Submission Workflow

```python
# moderation/models.py
@pghistory.track()
class EditSubmission(TrackedModel):
    """Tracks all proposed changes to parks and rides."""
    
    STATUS_CHOICES = [
        ("PENDING", "Pending"),
        ("APPROVED", "Approved"),
        ("REJECTED", "Rejected"),
        ("ESCALATED", "Escalated"),
    ]
    
    SUBMISSION_TYPE_CHOICES = [
        ("EDIT", "Edit Existing"),
        ("CREATE", "Create New"),
    ]
    
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField(null=True, blank=True)
    content_object = GenericForeignKey("content_type", "object_id")
    
    submission_type = models.CharField(max_length=10, choices=SUBMISSION_TYPE_CHOICES)
    proposed_changes = models.JSONField()  # Stores field-level changes
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="PENDING")
    
    # Moderation tracking
    reviewed_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True, blank=True,
        related_name="reviewed_submissions"
    )
    reviewed_at = models.DateTimeField(null=True, blank=True)
    reviewer_notes = models.TextField(blank=True)
```

#### Moderation Features

- **Change Tracking**: Every edit submission tracked with `django-pghistory`
- **Field-Level Changes**: JSON storage of specific field modifications
- **Review Workflow**: Pending → Approved/Rejected/Escalated states
- **Reviewer Assignment**: Track who reviewed each submission
- **Audit Trail**: Complete history of all moderation decisions

### Search and Autocomplete

#### Location-Based Search

```python
# Autocomplete integration for geographic search
class LocationAutocompleteView(autocomplete.Select2QuerySetView):
    """AJAX autocomplete for geographic locations."""
    
    def get_queryset(self):
        if not self.request.user.is_authenticated:
            return Location.objects.none()
        
        qs = Location.objects.filter(is_active=True)
        
        if self.q:
            qs = qs.filter(
                Q(name__icontains=self.q) |
                Q(city__icontains=self.q) |
                Q(state__icontains=self.q) |
                Q(country__icontains=self.q)
            )
        
        return qs.select_related('country', 'state').order_by('name')[:20]
```

#### Search Integration

- **HTMX-Powered Search**: Real-time search suggestions without page reloads
- **Geographic Filtering**: Search within specific bounds or regions
- **Multi-Model Search**: Unified search across parks, rides, and companies
- **Performance Optimized**: Cached results and smart query limiting

---

## Development Workflow

### Required Development Environment

#### UV Package Manager Integration

ThrillWiki exclusively uses [UV](https://github.com/astral-sh/uv) for all Python package management and Django commands:

```bash
# CRITICAL: Always use these exact commands

# Package Installation
uv add <package>                    # Add new dependencies
uv add --dev <package>              # Add development dependencies

# Django Management Commands
uv run manage.py makemigrations     # Create migrations
uv run manage.py migrate            # Apply migrations
uv run manage.py createsuperuser    # Create admin user
uv run manage.py shell              # Start Django shell
uv run manage.py collectstatic      # Collect static files

# Development Server (CRITICAL - use exactly as shown)
lsof -ti :8000 | xargs kill -9; find . -type d -name "__pycache__" -exec rm -r {} +; uv run manage.py tailwind runserver
```

**IMPORTANT**: Never use `python manage.py` or `pip install`. The project is configured exclusively for UV.

#### Local Development Setup

```bash
# Initial setup
git clone <repository-url>
cd thrillwiki_django_no_react

# Install dependencies
uv sync

# Database setup (requires PostgreSQL with PostGIS)
uv run manage.py migrate

# Create superuser
uv run manage.py createsuperuser

# Install Tailwind CSS and build
uv run manage.py tailwind install
uv run manage.py tailwind build

# Start development server
lsof -ti :8000 | xargs kill -9; find . -type d -name "__pycache__" -exec rm -r {} +; uv run manage.py tailwind runserver
```

### Database Requirements

#### PostgreSQL with PostGIS

```sql
-- Required PostgreSQL extensions
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE EXTENSION IF NOT EXISTS postgis_topology;
CREATE EXTENSION IF NOT EXISTS postgis_raster;
```

#### Geographic Data

- **Coordinate System**: WGS84 (SRID: 4326) for all geographic data
- **Point Storage**: All locations stored as PostGIS Point geometry
- **Spatial Queries**: Optimized with GiST indexes for geographic searches
- **Distance Calculations**: Native PostGIS distance functions

### Frontend Development

#### Tailwind CSS Workflow

```bash
# Development mode (watches for changes)
uv run manage.py tailwind start

# Production build
uv run manage.py tailwind build

# Custom CSS location
static/css/src/input.css    # Source file
static/css/tailwind.css     # Compiled output
```

#### JavaScript Integration

- **Alpine.js**: Reactive components and state management
- **HTMX**: AJAX interactions and partial page updates
- **Custom Scripts**: Modular JavaScript in `static/js/` directory

---

## API Endpoints and URL Structure

### Primary URL Configuration

#### Main Application Routes

```python
# thrillwiki/urls.py
urlpatterns = [
    path("admin/", admin.site.urls),
    path("", HomeView.as_view(), name="home"),
    
    # Autocomplete URLs (must be before other URLs)
    path("ac/", autocomplete_urls),
    
    # Core functionality
    path("parks/", include("parks.urls", namespace="parks")),
    path("rides/", include("rides.urls", namespace="rides")),
    path("photos/", include("media.urls", namespace="photos")),
    
    # Search and API
    path("search/", include("core.urls.search", namespace="search")),
    path("api/map/", include("core.urls.map_urls", namespace="map_api")),
    
    # User management
    path("accounts/", include("accounts.urls")),
    path("accounts/", include("allauth.urls")),
    path("user/<str:username>/", ProfileView.as_view(), name="user_profile"),
    path("settings/", SettingsView.as_view(), name="settings"),
    
    # Moderation system
    path("moderation/", include("moderation.urls", namespace="moderation")),
    
    # Static pages
    path("terms/", TemplateView.as_view(template_name="pages/terms.html"), name="terms"),
    path("privacy/", TemplateView.as_view(template_name="pages/privacy.html"), name="privacy"),
]
```

#### Parks URL Structure

```python
# parks/urls.py
app_name = "parks"

urlpatterns = [
    # Main park views
    path("", ParkSearchView.as_view(), name="park_list"),
    path("create/", ParkCreateView.as_view(), name="park_create"),
    path("<slug:slug>/", ParkDetailView.as_view(), name="park_detail"),
    path("<slug:slug>/edit/", ParkUpdateView.as_view(), name="park_update"),
    
    # HTMX endpoints
    path("add-park-button/", add_park_button, name="add_park_button"),
    path("search/location/", location_search, name="location_search"),
    path("search/reverse-geocode/", reverse_geocode, name="reverse_geocode"),
    path("areas/", get_park_areas, name="get_park_areas"),
    path("suggest_parks/", suggest_parks, name="suggest_parks"),
    
    # Park areas
    path("<slug:park_slug>/areas/<slug:area_slug>/", ParkAreaDetailView.as_view(), name="area_detail"),
    
    # Category-specific rides within parks
    path("<slug:park_slug>/roller_coasters/", ParkSingleCategoryListView.as_view(),
         {'category': 'RC'}, name="park_roller_coasters"),
    path("<slug:park_slug>/dark_rides/", ParkSingleCategoryListView.as_view(),
         {'category': 'DR'}, name="park_dark_rides"),
    path("<slug:park_slug>/flat_rides/", ParkSingleCategoryListView.as_view(),
         {'category': 'FR'}, name="park_flat_rides"),
    path("<slug:park_slug>/water_rides/", ParkSingleCategoryListView.as_view(),
         {'category': 'WR'}, name="park_water_rides"),
    path("<slug:park_slug>/transports/", ParkSingleCategoryListView.as_view(),
         {'category': 'TR'}, name="park_transports"),
    path("<slug:park_slug>/others/", ParkSingleCategoryListView.as_view(),
         {'category': 'OT'}, name="park_others"),
    
    # Nested rides URLs
    path("<slug:park_slug>/rides/", include("rides.park_urls", namespace="rides")),
]
```

### API Endpoints

#### Map API

```
GET /api/map/data/
- bounds: Geographic bounds (north,south,east,west)
- zoom: Map zoom level
- filters: JSON-encoded filter parameters
- Returns: Unified location data with clustering

GET /api/map/location/<type>/<id>/
- Returns: Detailed location information

POST /api/map/search/
- query: Search text
- bounds: Optional geographic bounds
- types: Location types to search
- Returns: Matching locations
```

#### Search API

```
GET /search/
- q: Search query
- type: Entity type (park, ride, company)
- location: Geographic filter
- Returns: Search results with pagination

GET /search/suggest/
- q: Partial query for autocomplete
- Returns: Quick suggestions
```

#### HTMX Endpoints

All HTMX endpoints return HTML fragments for seamless page updates:

```
POST /parks/suggest_parks/          # Park suggestions for autocomplete
GET /parks/areas/                   # Dynamic area loading
POST /parks/search/location/        # Location search with coordinates
POST /parks/search/reverse-geocode/ # Address lookup from coordinates
```

---

## Conclusion

ThrillWiki represents a sophisticated Django application implementing modern web development practices with a focus on performance, user experience, and maintainability. The project successfully combines:

### Technical Excellence

- **Modern Django Patterns**: Service-oriented architecture with clear separation of concerns
- **Geographic Capabilities**: Full PostGIS integration for spatial data and mapping
- **Performance Optimization**: Intelligent caching, query optimization, and clustering
- **Type Safety**: Comprehensive use of dataclasses and enums for data integrity

### User Experience

- **Responsive Design**: Mobile-first approach with Tailwind CSS
- **Progressive Enhancement**: HTMX for seamless interactions without JavaScript complexity
- **Dark Mode Support**: Complete theming system with user preferences
- **Accessibility**: WCAG-compliant components and navigation

### Development Workflow

- **UV Integration**: Modern Python package management with reproducible builds
- **Comprehensive Testing**: Model validation, service testing, and frontend integration
- **Documentation**: Extensive inline documentation and architectural decisions
- **Moderation System**: Complete workflow for user-generated content management

### Architectural Strengths

1. **Scalability**: Service layer architecture supports growth and feature expansion
2. **Maintainability**: Clear code organization with consistent patterns
3. **Performance**: Optimized database queries and intelligent caching strategies
4. **Security**: Authentication, authorization, and input validation throughout
5. **Extensibility**: Plugin-ready architecture for additional features

The project demonstrates enterprise-level Django development practices while maintaining simplicity and developer experience. The combination of modern frontend techniques (HTMX, Alpine.js, Tailwind) with robust backend services creates a powerful platform for theme park and ride enthusiasts.

This documentation serves as both a technical reference and architectural guide for understanding and extending the ThrillWiki platform.
