Add new Luck stat to the character stats system with class-specific values: - Assassin: 12 (highest - critical specialists) - Luminary: 11 (divine favor) - Wildstrider/Lorekeeper: 10 (average) - Arcanist/Oathkeeper: 9 (modest) - Vanguard: 8 (default - relies on strength) - Necromancer: 7 (lowest - dark arts cost) Changes: - Add luck field to Stats dataclass with default of 8 - Add LUCK to StatType enum - Update all 8 class YAML files with luck values - Display LUK in character panel (play page) and detail page - Update DATA_MODELS.md documentation Backward compatible: existing characters without luck default to 8.
194 lines
9.1 KiB
HTML
194 lines
9.1 KiB
HTML
{#
|
|
Character Panel - Left sidebar
|
|
Displays character stats, resource bars, and action buttons
|
|
#}
|
|
<div class="character-panel">
|
|
{# Character Header #}
|
|
<div class="character-header">
|
|
<div class="character-name">{{ character.name }}</div>
|
|
<div class="character-info">
|
|
<span class="character-class">{{ character.class_name }}</span>
|
|
<span class="character-level">Level {{ character.level }}</span>
|
|
</div>
|
|
<div class="character-usage">
|
|
{% include 'components/usage_display.html' %}
|
|
</div>
|
|
</div>
|
|
|
|
{# Resource Bars #}
|
|
<div class="resource-bars">
|
|
{# HP Bar #}
|
|
<div class="resource-bar resource-bar--hp">
|
|
<div class="resource-bar-label">
|
|
<span class="resource-bar-name">HP</span>
|
|
<span class="resource-bar-value">{{ character.current_hp }} / {{ character.max_hp }}</span>
|
|
</div>
|
|
<div class="resource-bar-track">
|
|
{% set hp_percent = (character.current_hp / character.max_hp * 100)|int %}
|
|
<div class="resource-bar-fill" style="width: {{ hp_percent }}%"></div>
|
|
</div>
|
|
</div>
|
|
|
|
{# MP Bar #}
|
|
<div class="resource-bar resource-bar--mp">
|
|
<div class="resource-bar-label">
|
|
<span class="resource-bar-name">MP</span>
|
|
<span class="resource-bar-value">{{ character.current_mp }} / {{ character.max_mp }}</span>
|
|
</div>
|
|
<div class="resource-bar-track">
|
|
{% set mp_percent = (character.current_mp / character.max_mp * 100)|int %}
|
|
<div class="resource-bar-fill" style="width: {{ mp_percent }}%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# Stats Accordion (Collapsed by default) #}
|
|
<div class="panel-accordion collapsed" data-panel-accordion="stats">
|
|
<button class="panel-accordion-header" onclick="togglePanelAccordion(this)">
|
|
<span>Stats</span>
|
|
<span class="panel-accordion-icon">▼</span>
|
|
</button>
|
|
<div class="panel-accordion-content">
|
|
<div class="stats-grid">
|
|
<div class="stat-item">
|
|
<div class="stat-abbr">STR</div>
|
|
<div class="stat-value">{{ character.stats.strength }}</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-abbr">DEX</div>
|
|
<div class="stat-value">{{ character.stats.dexterity }}</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-abbr">CON</div>
|
|
<div class="stat-value">{{ character.stats.constitution }}</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-abbr">INT</div>
|
|
<div class="stat-value">{{ character.stats.intelligence }}</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-abbr">WIS</div>
|
|
<div class="stat-value">{{ character.stats.wisdom }}</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-abbr">CHA</div>
|
|
<div class="stat-value">{{ character.stats.charisma }}</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-abbr">LUK</div>
|
|
<div class="stat-value">{{ character.stats.luck }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# Quick Actions (Equipment, NPC, Travel) #}
|
|
<div class="quick-actions">
|
|
{# Equipment & Gear - Opens modal #}
|
|
<button class="action-btn action-btn--special"
|
|
hx-get="{{ url_for('game.equipment_modal', session_id=session_id) }}"
|
|
hx-target="#modal-container"
|
|
hx-swap="innerHTML">
|
|
⚔️ Equipment & Gear
|
|
</button>
|
|
|
|
{# Talk to NPC - Opens NPC accordion #}
|
|
<button class="action-btn action-btn--special"
|
|
hx-get="{{ url_for('game.npcs_accordion', session_id=session_id) }}"
|
|
hx-target="#accordion-npcs"
|
|
hx-swap="innerHTML"
|
|
onclick="document.querySelector('[data-accordion=npcs]').classList.remove('collapsed')">
|
|
💬 Talk to NPC...
|
|
</button>
|
|
|
|
{# Travel - Opens modal #}
|
|
<button class="action-btn action-btn--special"
|
|
hx-get="{{ url_for('game.travel_modal', session_id=session_id) }}"
|
|
hx-target="#modal-container"
|
|
hx-swap="innerHTML">
|
|
🗺️ Travel to...
|
|
</button>
|
|
</div>
|
|
|
|
{# Actions Section #}
|
|
<div class="actions-section">
|
|
<div class="actions-title">Actions</div>
|
|
|
|
{# Free Tier Actions #}
|
|
<div class="actions-tier">
|
|
<div class="actions-tier-label">Free Actions</div>
|
|
<div class="actions-list">
|
|
{% for action in actions.free %}
|
|
{% set available = action.context == ['any'] or location.location_type in action.context %}
|
|
<button class="action-btn action-btn--free"
|
|
hx-post="{{ url_for('game.take_action', session_id=session_id) }}"
|
|
hx-vals='{"action_type": "button", "prompt_id": "{{ action.prompt_id }}"}'
|
|
hx-target="#narrative-content"
|
|
hx-swap="innerHTML"
|
|
hx-disabled-elt="this"
|
|
{% if not available %}disabled title="Not available in this location"{% endif %}>
|
|
<span class="action-btn-text">{{ action.display_text }}</span>
|
|
{% if action.cooldown %}
|
|
<span class="action-btn-cooldown" title="{{ action.cooldown }} turn cooldown">⏱</span>
|
|
{% endif %}
|
|
</button>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
{# Premium Tier Actions #}
|
|
<div class="actions-tier">
|
|
<div class="actions-tier-label">Premium Actions</div>
|
|
<div class="actions-list">
|
|
{% for action in actions.premium %}
|
|
{% set available = action.context == ['any'] or location.location_type in action.context %}
|
|
{% set locked = user_tier not in ['premium', 'elite'] %}
|
|
<button class="action-btn {% if locked %}action-btn--locked{% else %}action-btn--premium{% endif %}"
|
|
{% if not locked %}
|
|
hx-post="{{ url_for('game.take_action', session_id=session_id) }}"
|
|
hx-vals='{"action_type": "button", "prompt_id": "{{ action.prompt_id }}"}'
|
|
hx-target="#narrative-content"
|
|
hx-swap="innerHTML"
|
|
hx-disabled-elt="this"
|
|
{% endif %}
|
|
{% if locked %}disabled title="Requires Premium tier"{% elif not available %}disabled title="Not available in this location"{% endif %}>
|
|
<span class="action-btn-text">{{ action.display_text }}</span>
|
|
{% if locked %}
|
|
<span class="action-btn-lock">🔒</span>
|
|
{% elif action.cooldown %}
|
|
<span class="action-btn-cooldown" title="{{ action.cooldown }} turn cooldown">⏱</span>
|
|
{% endif %}
|
|
</button>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
{# Elite Tier Actions #}
|
|
<div class="actions-tier">
|
|
<div class="actions-tier-label">Elite Actions</div>
|
|
<div class="actions-list">
|
|
{% for action in actions.elite %}
|
|
{% set available = action.context == ['any'] or location.location_type in action.context %}
|
|
{% set locked = user_tier != 'elite' %}
|
|
<button class="action-btn {% if locked %}action-btn--locked{% else %}action-btn--elite{% endif %}"
|
|
{% if not locked %}
|
|
hx-post="{{ url_for('game.take_action', session_id=session_id) }}"
|
|
hx-vals='{"action_type": "button", "prompt_id": "{{ action.prompt_id }}"}'
|
|
hx-target="#narrative-content"
|
|
hx-swap="innerHTML"
|
|
hx-disabled-elt="this"
|
|
{% endif %}
|
|
{% if locked %}disabled title="Requires Elite tier"{% elif not available %}disabled title="Not available in this location"{% endif %}>
|
|
<span class="action-btn-text">{{ action.display_text }}</span>
|
|
{% if locked %}
|
|
<span class="action-btn-lock">🔒</span>
|
|
{% elif action.cooldown %}
|
|
<span class="action-btn-cooldown" title="{{ action.cooldown }} turn cooldown">⏱</span>
|
|
{% endif %}
|
|
</button>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|