670 lines
18 KiB
HTML
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;">← 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 %}
|