# Responsive Modal Pattern

## Overview

This pattern provides an optimal UX for modal-like content by adapting to screen size:
- **Desktop (>1024px)**: Displays content in modal overlays
- **Mobile (≤1024px)**: Navigates to dedicated full pages

This addresses common mobile modal UX issues: cramped space, nested scrolling, keyboard conflicts, and lack of native back button support.

---

## When to Use This Pattern

Use this pattern when:
- Content requires significant interaction (forms, chat, complex data)
- Mobile users need better scroll/keyboard handling
- The interaction benefits from full-screen on mobile
- Desktop users benefit from keeping context visible

**Don't use** for:
- Simple confirmations/alerts (use standard modals)
- Very brief interactions (dropdowns, tooltips)
- Content that must overlay the game state

---

## Implementation Steps

### 1. Create Shared Content Partial

Extract the actual content into a reusable partial template that both the modal and page can use.

**File**: `templates/game/partials/[feature]_content.html`

```html
{# Shared content partial #}
<div class="feature-container">
    <!-- Your content here -->
    <!-- This will be used by both modal and page -->
</div>
```

### 2. Create Two Routes

Create both a modal route and a page route in your view:

**File**: `app/views/game_views.py`

```python
@game_bp.route('/session/<session_id>/feature/<feature_id>')
@require_auth
def feature_page(session_id: str, feature_id: str):
    """
    Dedicated page view (mobile-friendly).
    Used on mobile devices for better UX.
    """
    # Fetch data
    data = get_feature_data(session_id, feature_id)

    return render_template(
        'game/feature_page.html',
        session_id=session_id,
        feature=data
    )


@game_bp.route('/session/<session_id>/feature/<feature_id>/modal')
@require_auth
def feature_modal(session_id: str, feature_id: str):
    """
    Modal view (desktop overlay).
    Used on desktop for quick interactions.
    """
    # Fetch same data
    data = get_feature_data(session_id, feature_id)

    return render_template(
        'game/partials/feature_modal.html',
        session_id=session_id,
        feature=data
    )
```

### 3. Create Page Template

Create a dedicated page template with header, back button, and shared content.

**File**: `templates/game/feature_page.html`

```html
{% extends "base.html" %}

{% block title %}{{ feature.name }} - Code of Conquest{% endblock %}

{% block extra_head %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/play.css') }}">
{% endblock %}

{% block content %}
<div class="feature-page">
    {# Page Header with Back Button #}
    <div class="feature-header">
        <a href="{{ url_for('game.play_session', session_id=session_id) }}"
           class="feature-back-btn">
            <svg><!-- Back arrow icon --></svg>
            Back
        </a>
        <h1 class="feature-title">{{ feature.name }}</h1>
    </div>

    {# Include shared content #}
    {% include 'game/partials/feature_content.html' %}
</div>
{% endblock %}

{% block scripts %}
<script>
// Add any feature-specific JavaScript here
</script>
{% endblock %}
```

### 4. Update Modal Template

Update your modal template to use the shared content partial.

**File**: `templates/game/partials/feature_modal.html`

```html
{# Modal wrapper #}
<div class="modal-overlay" onclick="if(event.target === this) closeModal()">
    <div class="modal-content modal-content--xl">
        {# Modal Header #}
        <div class="modal-header">
            <h3 class="modal-title">{{ feature.name }}</h3>
            <button class="modal-close" onclick="closeModal()">&times;</button>
        </div>

        {# Modal Body - Uses shared content #}
        <div class="modal-body">
            {% include 'game/partials/feature_content.html' %}
        </div>

        {# Modal Footer (optional) #}
        <div class="modal-footer">
            <button class="btn btn--secondary" onclick="closeModal()">
                Close
            </button>
        </div>
    </div>
</div>
```

### 5. Add Responsive Navigation

Update the trigger element to use responsive navigation JavaScript.

**File**: `templates/game/partials/sidebar_features.html`

```html
<div class="feature-item"
     data-feature-id="{{ feature.id }}"
     onclick="navigateResponsive(
         event,
         '{{ url_for('game.feature_page', session_id=session_id, feature_id=feature.id) }}',
         '{{ url_for('game.feature_modal', session_id=session_id, feature_id=feature.id) }}'
     )"
     style="cursor: pointer;">
    <div class="feature-name">{{ feature.name }}</div>
</div>
```

### 6. Add Mobile-Friendly CSS

Add CSS for the dedicated page with mobile optimizations.

**File**: `static/css/play.css`

```css
/* Feature Page */
.feature-page {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    background: var(--bg-secondary);
}

/* Page Header with Back Button */
.feature-header {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 1rem;
    background: var(--bg-primary);
    border-bottom: 2px solid var(--play-border);
    position: sticky;
    top: 0;
    z-index: 100;
}

.feature-back-btn {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 1rem;
    background: var(--bg-input);
    color: var(--text-primary);
    text-decoration: none;
    border-radius: 4px;
    border: 1px solid var(--play-border);
    transition: all 0.2s ease;
}

.feature-back-btn:hover {
    background: var(--bg-tertiary);
    border-color: var(--accent-gold);
    color: var(--accent-gold);
}

.feature-title {
    flex: 1;
    margin: 0;
    font-family: var(--font-heading);
    font-size: 1.5rem;
    color: var(--accent-gold);
}

/* Responsive layout */
@media (min-width: 1025px) {
    .feature-page .feature-container {
        max-width: 1400px;
        margin: 0 auto;
        padding: 2rem;
    }
}

@media (max-width: 1024px) {
    .feature-page .feature-container {
        padding: 1rem;
        flex: 1;
    }
}
```

### 7. Include JavaScript

Ensure the responsive modals JavaScript is included in your play page template.

**File**: `templates/game/play.html`

```html
{% block scripts %}
<!-- Existing scripts -->
<script>
// Your existing JavaScript
</script>

<!-- Responsive Modal Navigation -->
<script src="{{ url_for('static', filename='js/responsive-modals.js') }}"></script>
{% endblock %}
```

---

## JavaScript API

The responsive navigation is handled by `responsive-modals.js`:

### Functions

#### `navigateResponsive(event, pageUrl, modalUrl)`

Navigates responsively based on screen size.

**Parameters:**
- `event` (Event): Click event (will be prevented)
- `pageUrl` (string): Full page URL for mobile navigation
- `modalUrl` (string): Modal content URL for desktop HTMX loading

**Example:**
```javascript
navigateResponsive(
    event,
    '/game/session/123/npc/456',      // Page route
    '/game/session/123/npc/456/chat'  // Modal route
)
```

#### `isMobile()`

Returns true if viewport is ≤1024px.

**Example:**
```javascript
if (isMobile()) {
    // Mobile-specific behavior
}
```

---

## Breakpoint

The mobile/desktop breakpoint is **1024px** to match the CSS media query:

```javascript
const MOBILE_BREAKPOINT = 1024;
```

This ensures consistent behavior between JavaScript navigation and CSS responsive layouts.

---

## Example: NPC Chat

See the NPC chat implementation for a complete working example:

**Routes:**
- Page: `/game/session/<session_id>/npc/<npc_id>` (public_web/app/views/game_views.py:695)
- Modal: `/game/session/<session_id>/npc/<npc_id>/chat` (public_web/app/views/game_views.py:746)

**Templates:**
- Shared content: `templates/game/partials/npc_chat_content.html`
- Page: `templates/game/npc_chat_page.html`
- Modal: `templates/game/partials/npc_chat_modal.html`

**Trigger:**
- `templates/game/partials/sidebar_npcs.html` (line 11)

**CSS:**
- Page styles: `static/css/play.css` (lines 1809-1956)

---

## Testing Checklist

When implementing this pattern, test:

- [ ] Desktop (>1024px): Modal opens correctly
- [ ] Desktop: Modal closes with X button, overlay click, and Escape key
- [ ] Desktop: Game screen remains visible behind modal
- [ ] Mobile (≤1024px): Navigates to dedicated page
- [ ] Mobile: Back button returns to game
- [ ] Mobile: Content scrolls naturally
- [ ] Mobile: Keyboard doesn't break layout
- [ ] Both: Shared content renders identically
- [ ] Both: HTMX interactions work correctly
- [ ] Resize: Behavior adapts when crossing breakpoint

---

## Best Practices

1. **Always share content**: Use a partial template to avoid duplication
2. **Keep routes parallel**: Use consistent URL patterns (`/feature` and `/feature/modal`)
3. **Test both views**: Ensure feature parity between modal and page
4. **Mobile-first CSS**: Design for mobile, enhance for desktop
5. **Consistent breakpoint**: Always use 1024px to match the JavaScript
6. **Document examples**: Link to working implementations in this doc

---

## Future Improvements

Potential enhancements to this pattern:

- [ ] Add transition animations for modal open/close
- [ ] Support deep linking to modals on desktop
- [ ] Add browser back button handling for desktop modals
- [ ] Support customizable breakpoints per feature
- [ ] Add analytics tracking for modal vs page usage

---

## Related Documentation

- **HTMX_PATTERNS.md** - HTMX integration patterns
- **TEMPLATES.md** - Template structure and conventions
- **TESTING.md** - Manual testing procedures
