# 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 #}
``` ### 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//feature/') @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//feature//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 %} {% endblock %} {% block content %}
{# Page Header with Back Button #}
Back

{{ feature.name }}

{# Include shared content #} {% include 'game/partials/feature_content.html' %}
{% endblock %} {% block scripts %} {% 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 #} ``` ### 5. Add Responsive Navigation Update the trigger element to use responsive navigation JavaScript. **File**: `templates/game/partials/sidebar_features.html` ```html
{{ feature.name }}
``` ### 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 %} {% 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//npc/` (public_web/app/views/game_views.py:695) - Modal: `/game/session//npc//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