Files
Code_of_Conquest/public_web/templates/dev/story_session.html
2025-11-24 23:10:55 -06:00

670 lines
18 KiB
HTML

{% extends "base.html" %}
{% block title %}Story Session - Dev Tools{% endblock %}
{% block extra_head %}
<style>
.dev-banner {
background: #dc2626;
color: white;
padding: 0.5rem 1rem;
text-align: center;
font-weight: bold;
position: sticky;
top: 0;
z-index: 100;
}
.session-container {
max-width: 1200px;
margin: 1rem auto;
padding: 0 1rem;
display: grid;
grid-template-columns: 250px 1fr 300px;
gap: 1rem;
}
@media (max-width: 1024px) {
.session-container {
grid-template-columns: 1fr;
}
}
.panel {
background: rgba(30, 30, 40, 0.9);
border: 1px solid #4a4a5a;
border-radius: 8px;
padding: 1rem;
}
.panel h3 {
color: #f59e0b;
margin-top: 0;
margin-bottom: 1rem;
font-size: 1rem;
border-bottom: 1px solid #4a4a5a;
padding-bottom: 0.5rem;
}
/* Left sidebar - State */
.state-panel {
font-size: 0.85rem;
}
.state-item {
margin-bottom: 0.75rem;
}
.state-label {
color: #9ca3af;
font-size: 0.75rem;
text-transform: uppercase;
}
.state-value {
color: white;
font-weight: 500;
}
/* Main area */
.main-panel {
min-height: 500px;
}
#dm-response {
background: #1a1a2a;
border-radius: 6px;
padding: 1.5rem;
min-height: 200px;
line-height: 1.6;
white-space: pre-wrap;
margin-bottom: 1rem;
}
.actions-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 0.5rem;
margin-bottom: 1rem;
}
.action-btn {
background: #3b82f6;
color: white;
padding: 0.75rem 1rem;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 0.9rem;
text-align: left;
transition: background 0.2s;
}
.action-btn:hover {
background: #2563eb;
}
.action-btn:disabled {
background: #4a4a5a;
cursor: wait;
}
.action-btn.action-premium {
background: #8b5cf6;
}
.action-btn.action-premium:hover {
background: #7c3aed;
}
.action-btn.action-elite {
background: #f59e0b;
}
.action-btn.action-elite:hover {
background: #d97706;
}
/* Right sidebar - History */
.history-panel {
max-height: 600px;
overflow-y: auto;
}
.history-entry {
padding: 0.75rem;
background: #2a2a3a;
border-radius: 6px;
margin-bottom: 0.5rem;
font-size: 0.85rem;
}
.history-turn {
color: #f59e0b;
font-weight: bold;
margin-bottom: 0.25rem;
}
.history-action {
color: #60a5fa;
margin-bottom: 0.5rem;
}
.history-response {
color: #d1d5db;
white-space: pre-wrap;
max-height: 100px;
overflow: hidden;
text-overflow: ellipsis;
}
/* Debug panel */
.debug-panel {
margin-top: 1rem;
padding: 1rem;
background: #1a1a2a;
border-radius: 6px;
font-family: monospace;
font-size: 0.75rem;
}
.debug-toggle {
color: #9ca3af;
cursor: pointer;
user-select: none;
}
.debug-content {
margin-top: 0.5rem;
max-height: 200px;
overflow: auto;
white-space: pre;
color: #a3e635;
}
.loading {
text-align: center;
padding: 1rem;
color: #60a5fa;
}
.error {
background: #7f1d1d;
color: #fecaca;
padding: 0.75rem;
border-radius: 6px;
}
.btn-refresh {
background: #6366f1;
color: white;
padding: 0.25rem 0.5rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.75rem;
float: right;
}
/* NPC Sidebar */
.npc-section {
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid #4a4a5a;
}
.npc-section h4 {
color: #f59e0b;
font-size: 0.85rem;
margin: 0 0 0.5rem 0;
}
.npc-card {
cursor: pointer;
padding: 0.5rem;
margin: 0.25rem 0;
background: #2a2a3a;
border-radius: 4px;
border: 1px solid transparent;
transition: all 0.2s;
}
.npc-card:hover {
background: #3a3a4a;
border-color: #5a5a6a;
}
.npc-name {
font-weight: 500;
color: #e5e7eb;
font-size: 0.9rem;
}
.npc-role {
font-size: 0.75rem;
color: #9ca3af;
}
.npc-empty {
color: #6b7280;
font-size: 0.8rem;
font-style: italic;
}
/* Travel Section */
.travel-section {
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid #4a4a5a;
}
.btn-travel {
width: 100%;
background: #059669;
color: white;
padding: 0.75rem 1rem;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 0.9rem;
transition: background 0.2s;
}
.btn-travel:hover {
background: #047857;
}
/* Modal Styles */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background: #1a1a2a;
border: 1px solid #4a4a5a;
border-radius: 8px;
padding: 1.5rem;
max-width: 500px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
}
.modal-content h3 {
color: #f59e0b;
margin-top: 0;
margin-bottom: 1rem;
}
.location-btn {
width: 100%;
padding: 0.75rem;
margin: 0.5rem 0;
background: #2a2a3a;
border: 1px solid #4a4a5a;
border-radius: 6px;
color: white;
cursor: pointer;
text-align: left;
transition: all 0.2s;
}
.location-btn:hover {
background: #3a3a4a;
border-color: #5a5a6a;
}
.location-name {
font-weight: 500;
color: #e5e7eb;
}
.location-type {
font-size: 0.8rem;
color: #9ca3af;
}
.modal-close {
margin-top: 1rem;
padding: 0.5rem 1rem;
background: #6b7280;
border: none;
border-radius: 4px;
color: white;
cursor: pointer;
}
.modal-close:hover {
background: #4b5563;
}
/* NPC Dialogue Result */
.npc-dialogue {
background: #2a2a3a;
border-left: 3px solid #f59e0b;
padding: 1rem;
margin-bottom: 1rem;
border-radius: 0 6px 6px 0;
}
.npc-dialogue-header {
color: #f59e0b;
font-weight: 500;
margin-bottom: 0.5rem;
}
/* NPC Chat Form Styles */
.npc-card-wrapper {
margin: 0.25rem 0;
background: #2a2a3a;
border-radius: 4px;
border: 1px solid transparent;
transition: all 0.2s;
}
.npc-card-wrapper:hover {
background: #3a3a4a;
border-color: #5a5a6a;
}
.npc-card-header {
cursor: pointer;
padding: 0.5rem;
}
.npc-chat-form {
padding: 0.5rem;
padding-top: 0;
border-top: 1px solid #4a4a5a;
}
.npc-chat-input {
width: 100%;
padding: 0.5rem;
margin-bottom: 0.5rem;
background: #1a1a2a;
border: 1px solid #4a4a5a;
border-radius: 4px;
color: #e5e7eb;
font-size: 0.85rem;
}
.npc-chat-input:focus {
outline: none;
border-color: #f59e0b;
}
.npc-chat-input::placeholder {
color: #6b7280;
}
.npc-chat-buttons {
display: flex;
gap: 0.5rem;
}
.btn-npc-send {
flex: 1;
padding: 0.4rem 0.75rem;
background: #f59e0b;
border: none;
border-radius: 4px;
color: #1a1a2a;
font-weight: 500;
font-size: 0.8rem;
cursor: pointer;
transition: background 0.2s;
}
.btn-npc-send:hover {
background: #d97706;
}
.btn-npc-send:disabled {
background: #4a4a5a;
color: #9ca3af;
cursor: wait;
}
.btn-npc-greet {
padding: 0.4rem 0.75rem;
background: #3b3b4b;
border: 1px solid #5a5a6a;
border-radius: 4px;
color: #e5e7eb;
font-size: 0.8rem;
cursor: pointer;
transition: all 0.2s;
}
.btn-npc-greet:hover {
background: #4b4b5b;
border-color: #6a6a7a;
}
</style>
{% endblock %}
{% block content %}
<div class="dev-banner">
DEV MODE - Session {{ session_id[:8] }}...
</div>
<div class="session-container">
<!-- Left sidebar: State -->
<div class="panel state-panel">
<h3>
Session State
<button class="btn-refresh"
hx-get="{{ url_for('dev.get_state', session_id=session_id) }}"
hx-target="#state-content"
hx-swap="innerHTML">
Refresh
</button>
</h3>
<div id="state-content">
<div class="state-item">
<div class="state-label">Session ID</div>
<div class="state-value">{{ session_id[:12] }}...</div>
</div>
<div class="state-item">
<div class="state-label">Turn</div>
<div class="state-value">{{ session.turn_number }}</div>
</div>
<div class="state-item">
<div class="state-label">Location</div>
<div class="state-value">{{ session.game_state.current_location }}</div>
</div>
<div class="state-item">
<div class="state-label">Type</div>
<div class="state-value">{{ session.game_state.location_type }}</div>
</div>
<div class="state-item">
<div class="state-label">Active Quests</div>
<div class="state-value">{{ session.game_state.active_quests|length }}</div>
</div>
</div>
<!-- NPC Section -->
<div class="npc-section">
<h4>NPCs Here</h4>
<div id="npc-list">
{% if npcs_present %}
{% for npc in npcs_present %}
<div class="npc-card-wrapper">
<div class="npc-card-header"
onclick="toggleNpcChat('{{ npc.npc_id }}')">
<div class="npc-name">{{ npc.name }}</div>
<div class="npc-role">{{ npc.role }}</div>
</div>
<div class="npc-chat-form" id="npc-chat-{{ npc.npc_id }}" style="display: none;">
<form hx-post="{{ url_for('dev.talk_to_npc', session_id=session_id, npc_id=npc.npc_id) }}"
hx-target="#dm-response"
hx-swap="innerHTML">
<input type="text"
name="player_response"
placeholder="Say something..."
class="npc-chat-input"
maxlength="500"
autocomplete="off">
<div class="npc-chat-buttons">
<button type="submit" class="btn-npc-send" hx-disabled-elt="this">Send</button>
<button type="button"
class="btn-npc-greet"
hx-post="{{ url_for('dev.talk_to_npc', session_id=session_id, npc_id=npc.npc_id) }}"
hx-vals='{"topic": "greeting"}'
hx-target="#dm-response"
hx-swap="innerHTML"
hx-disabled-elt="this">Greet</button>
</div>
</form>
</div>
</div>
{% endfor %}
{% else %}
<p class="npc-empty">No one here to talk to.</p>
{% endif %}
</div>
</div>
<!-- Travel Section -->
<div class="travel-section">
<button class="btn-travel"
hx-get="{{ url_for('dev.travel_modal', session_id=session_id) }}"
hx-target="#modal-container"
hx-swap="innerHTML">
Travel to...
</button>
</div>
<div style="margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #4a4a5a;">
<a href="{{ url_for('dev.story_hub') }}" style="color: #60a5fa; font-size: 0.85rem;">&larr; Back to Sessions</a>
</div>
</div>
<!-- Main area: DM Response & Actions -->
<div class="panel main-panel">
<h3>Story Gameplay</h3>
<!-- DM Response Area -->
<div id="dm-response">
{% if history %}
{{ history[-1].dm_response if history else 'Take an action to begin your adventure...' }}
{% else %}
Take an action to begin your adventure...
{% endif %}
</div>
<!-- Action Buttons -->
<div class="actions-grid" id="action-buttons">
{% if session.available_actions %}
{% for action in session.available_actions %}
<button class="action-btn {% if action.category == 'special' %}action-elite{% elif action.category in ['gather_info', 'travel'] and action.prompt_id in ['investigate_suspicious', 'follow_lead', 'make_camp'] %}action-premium{% endif %}"
hx-post="{{ url_for('dev.take_action', session_id=session_id) }}"
hx-vals='{"action_type": "button", "prompt_id": "{{ action.prompt_id }}"}'
hx-target="#dm-response"
hx-swap="innerHTML"
hx-disabled-elt="this"
title="{{ action.description }}">
{{ action.display_text }}
</button>
{% endfor %}
{% else %}
<p style="color: #9ca3af; text-align: center; grid-column: 1 / -1;">
No actions available for your tier/location. Try changing location or upgrading tier.
</p>
{% endif %}
</div>
<!-- Debug Panel -->
<div class="debug-panel">
<div class="debug-toggle" onclick="this.nextElementSibling.style.display = this.nextElementSibling.style.display === 'none' ? 'block' : 'none'">
[+] Debug Info (click to toggle)
</div>
<div class="debug-content" style="display: none;">
Session ID: {{ session_id }}
Character ID: {{ session.character_id }}
Turn: {{ session.turn_number }}
Game State: {{ session.game_state | tojson }}
</div>
</div>
</div>
<!-- Right sidebar: History -->
<div class="panel history-panel">
<h3>
History
<button class="btn-refresh"
hx-get="{{ url_for('dev.get_history', session_id=session_id) }}"
hx-target="#history-content"
hx-swap="innerHTML">
Refresh
</button>
</h3>
<div id="history-content">
{% if history %}
{% for entry in history|reverse %}
<div class="history-entry">
<div class="history-turn">Turn {{ entry.turn }}</div>
<div class="history-action">{{ entry.action }}</div>
<div class="history-response">{{ entry.dm_response[:150] }}{% if entry.dm_response|length > 150 %}...{% endif %}</div>
</div>
{% endfor %}
{% else %}
<p style="color: #9ca3af; text-align: center; font-size: 0.85rem;">No history yet.</p>
{% endif %}
</div>
</div>
</div>
<!-- Modal Container for Travel/NPC dialogs -->
<div id="modal-container"></div>
<script>
// Toggle NPC chat form visibility
function toggleNpcChat(npcId) {
const chatForm = document.getElementById('npc-chat-' + npcId);
if (!chatForm) return;
// Close all other NPC chat forms
document.querySelectorAll('.npc-chat-form').forEach(form => {
if (form.id !== 'npc-chat-' + npcId) {
form.style.display = 'none';
}
});
// Toggle the clicked NPC's chat form
if (chatForm.style.display === 'none') {
chatForm.style.display = 'block';
// Focus the input field
const input = chatForm.querySelector('.npc-chat-input');
if (input) {
input.focus();
}
} else {
chatForm.style.display = 'none';
}
}
// Clear input after successful form submission
document.body.addEventListener('htmx:afterRequest', function(event) {
// Check if this was an NPC chat form submission
const form = event.detail.elt.closest('.npc-chat-form form');
if (form && event.detail.successful) {
const input = form.querySelector('.npc-chat-input');
if (input) {
input.value = '';
}
}
});
</script>
{% endblock %}