first commit
This commit is contained in:
669
public_web/templates/dev/story_session.html
Normal file
669
public_web/templates/dev/story_session.html
Normal file
@@ -0,0 +1,669 @@
|
||||
{% 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 %}
|
||||
Reference in New Issue
Block a user