Files
Code_of_Conquest/public_web/templates/character/detail.html
Phillip Tarrant 30c3b800e6 feat(api): add luck (LUK) stat to character system
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.
2025-11-26 12:27:18 -06:00

490 lines
14 KiB
HTML

{% extends "base.html" %}
{% block title %}{{ character.name }} - Code of Conquest{% endblock %}
{% block content %}
<div class="character-detail-container">
<!-- Header -->
<div class="detail-header">
<div class="header-left">
<h1 class="character-name">{{ character.name }}</h1>
<p class="character-subtitle">
Level {{ character.level }} {{ character.player_class.name }}
</p>
</div>
<div class="header-right">
<a href="{{ url_for('character_views.list_characters') }}" class="btn btn-secondary">
← Back to Characters
</a>
</div>
</div>
<div class="decorative-line"></div>
<!-- Main Content Grid -->
<div class="detail-grid">
<!-- Left Column: Stats & Info -->
<div class="detail-section">
<h2 class="section-title">Character Information</h2>
<div class="info-card">
<div class="info-row">
<span class="info-label">Class:</span>
<span class="info-value">{{ character.player_class.name }}</span>
</div>
<div class="info-row">
<span class="info-label">Origin:</span>
<span class="info-value">{{ character.origin_name }}</span>
</div>
<div class="info-row">
<span class="info-label">Level:</span>
<span class="info-value">{{ character.level }}</span>
</div>
<div class="info-row">
<span class="info-label">Experience:</span>
<span class="info-value">{{ character.experience }} XP</span>
</div>
<div class="info-row">
<span class="info-label">Gold:</span>
<span class="info-value gold">{{ character.gold }} 💰</span>
</div>
<div class="info-row">
<span class="info-label">Skill Points:</span>
<span class="info-value">{{ character.available_skill_points }}</span>
</div>
</div>
<!-- Base Stats -->
<h3 class="subsection-title">Base Statistics</h3>
<div class="stats-grid">
<div class="stat-item">
<span class="stat-name">STR</span>
<span class="stat-value">{{ character.base_stats.strength }}</span>
</div>
<div class="stat-item">
<span class="stat-name">DEX</span>
<span class="stat-value">{{ character.base_stats.dexterity }}</span>
</div>
<div class="stat-item">
<span class="stat-name">CON</span>
<span class="stat-value">{{ character.base_stats.constitution }}</span>
</div>
<div class="stat-item">
<span class="stat-name">INT</span>
<span class="stat-value">{{ character.base_stats.intelligence }}</span>
</div>
<div class="stat-item">
<span class="stat-name">WIS</span>
<span class="stat-value">{{ character.base_stats.wisdom }}</span>
</div>
<div class="stat-item">
<span class="stat-name">CHA</span>
<span class="stat-value">{{ character.base_stats.charisma }}</span>
</div>
<div class="stat-item">
<span class="stat-name">LUK</span>
<span class="stat-value">{{ character.base_stats.luck }}</span>
</div>
</div>
<!-- Derived Stats -->
<h3 class="subsection-title">Derived Statistics</h3>
<div class="info-card">
<div class="info-row">
<span class="info-label">Hit Points:</span>
<span class="info-value">{{ character.current_hp }} / {{ character.max_hp }}</span>
</div>
<div class="info-row">
<span class="info-label">Mana Points:</span>
<span class="info-value">{{ character.base_stats.mana_points }}</span>
</div>
<div class="info-row">
<span class="info-label">Defense:</span>
<span class="info-value">{{ character.base_stats.defense }}</span>
</div>
<div class="info-row">
<span class="info-label">Resistance:</span>
<span class="info-value">{{ character.base_stats.resistance }}</span>
</div>
</div>
</div>
<!-- Right Column: Skills, Inventory, etc -->
<div class="detail-section">
<!-- Unlocked Skills -->
<h2 class="section-title">Unlocked Skills</h2>
{% if character.unlocked_skills %}
<div class="skills-list">
{% for skill_id in character.unlocked_skills %}
<div class="skill-badge">{{ skill_id }}</div>
{% endfor %}
</div>
{% else %}
<p class="empty-text">No skills unlocked yet.</p>
{% endif %}
<a href="{{ url_for('character_views.view_skills', character_id=character.character_id) }}" class="btn btn-primary btn-block">
Manage Skills
</a>
<!-- Equipment -->
<h2 class="section-title">Equipment</h2>
{% if character.equipped %}
<div class="equipment-list">
{% for slot, item in character.equipped.items() %}
<div class="equipment-item">
<span class="equipment-slot">{{ slot|title }}:</span>
<span class="equipment-name">{{ item.name }}</span>
</div>
{% endfor %}
</div>
{% else %}
<p class="empty-text">No equipment equipped.</p>
{% endif %}
<!-- Inventory -->
<h2 class="section-title">Inventory</h2>
{% if character.inventory %}
<div class="inventory-list">
{% for item in character.inventory %}
<div class="inventory-item">
<span class="item-name">{{ item.name }}</span>
<span class="item-type">{{ item.item_type }}</span>
</div>
{% endfor %}
</div>
{% else %}
<p class="empty-text">Inventory is empty.</p>
{% endif %}
<!-- Active Quests -->
<h2 class="section-title">Active Quests</h2>
{% if character.active_quests %}
<div class="quests-list">
{% for quest_id in character.active_quests %}
<div class="quest-item">{{ quest_id }}</div>
{% endfor %}
</div>
{% else %}
<p class="empty-text">No active quests.</p>
{% endif %}
</div>
</div>
<!-- Origin Story -->
<div class="origin-story-section">
<h2 class="section-title">Origin: {{ character.origin.name }}</h2>
<p class="origin-description">{{ character.origin.description }}</p>
<div class="starting-location">
<h3 class="subsection-title">Starting Location</h3>
<p><strong>{{ character.origin.starting_location.name }}</strong> ({{ character.origin.starting_location.region }})</p>
<p class="location-description">{{ character.origin.starting_location.description }}</p>
</div>
</div>
<!-- Actions -->
<div class="character-actions">
<form method="POST" action="{{ url_for('character_views.delete_character', character_id=character.character_id) }}"
onsubmit="return confirm('Are you sure you want to delete {{ character.name }}? This cannot be undone.');">
<button type="submit" class="btn btn-danger">
Delete Character
</button>
</form>
</div>
</div>
<style>
/* ===== CHARACTER DETAIL CONTAINER ===== */
.character-detail-container {
max-width: 1400px;
margin: 2rem auto;
padding: 2rem;
}
/* ===== HEADER ===== */
.detail-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
gap: 2rem;
}
.character-name {
font-family: var(--font-heading);
font-size: var(--text-3xl);
color: var(--accent-gold);
text-transform: uppercase;
letter-spacing: 2px;
margin: 0 0 0.5rem 0;
}
.character-subtitle {
font-size: var(--text-lg);
color: var(--text-secondary);
margin: 0;
}
/* ===== GRID LAYOUT ===== */
.detail-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
margin-bottom: 2rem;
}
.detail-section {
background: var(--bg-secondary);
border: 2px solid var(--border-primary);
border-radius: 8px;
padding: 1.5rem;
}
/* ===== SECTION TITLES ===== */
.section-title {
font-family: var(--font-heading);
font-size: var(--text-xl);
color: var(--accent-gold);
text-transform: uppercase;
letter-spacing: 1px;
margin: 0 0 1rem 0;
padding-bottom: 0.5rem;
border-bottom: 2px solid var(--border-primary);
}
.subsection-title {
font-family: var(--font-heading);
font-size: var(--text-lg);
color: var(--text-primary);
margin: 1.5rem 0 1rem 0;
text-transform: uppercase;
letter-spacing: 1px;
}
/* ===== INFO CARD ===== */
.info-card {
background: var(--bg-input);
border: 1px solid var(--border-primary);
border-radius: 4px;
padding: 1rem;
margin-bottom: 1.5rem;
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem 0;
border-bottom: 1px solid var(--bg-secondary);
}
.info-row:last-child {
border-bottom: none;
}
.info-label {
font-size: var(--text-sm);
color: var(--text-muted);
font-weight: 600;
}
.info-value {
font-size: var(--text-sm);
color: var(--text-primary);
}
.info-value.gold {
color: var(--accent-gold);
font-weight: 600;
}
/* ===== STATS GRID ===== */
.stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
margin-bottom: 1.5rem;
}
.stat-item {
background: var(--bg-input);
border: 1px solid var(--border-primary);
border-radius: 4px;
padding: 1rem;
text-align: center;
}
.stat-name {
display: block;
font-size: var(--text-xs);
color: var(--text-muted);
font-weight: 600;
margin-bottom: 0.5rem;
text-transform: uppercase;
}
.stat-value {
display: block;
font-size: var(--text-2xl);
color: var(--accent-gold);
font-weight: 700;
}
/* ===== SKILLS ===== */
.skills-list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 1rem;
}
.skill-badge {
background: var(--bg-input);
border: 1px solid var(--accent-gold);
border-radius: 12px;
padding: 0.25rem 0.75rem;
font-size: var(--text-xs);
color: var(--accent-gold);
text-transform: uppercase;
font-weight: 600;
}
/* ===== EQUIPMENT & INVENTORY ===== */
.equipment-list,
.inventory-list {
margin-bottom: 1rem;
}
.equipment-item,
.inventory-item {
background: var(--bg-input);
border: 1px solid var(--border-primary);
border-radius: 4px;
padding: 0.75rem;
margin-bottom: 0.5rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.equipment-slot,
.item-type {
font-size: var(--text-xs);
color: var(--text-muted);
text-transform: uppercase;
font-weight: 600;
}
.equipment-name,
.item-name {
color: var(--text-primary);
}
/* ===== QUESTS ===== */
.quests-list {
margin-bottom: 1rem;
}
.quest-item {
background: var(--bg-input);
border: 1px solid var(--border-primary);
border-radius: 4px;
padding: 0.75rem;
margin-bottom: 0.5rem;
color: var(--text-primary);
}
/* ===== ORIGIN STORY ===== */
.origin-story-section {
background: var(--bg-secondary);
border: 2px solid var(--border-primary);
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 2rem;
}
.origin-description {
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: 1.5rem;
}
.narrative-hooks {
list-style-type: none;
padding: 0;
margin-bottom: 1.5rem;
}
.narrative-hooks li {
background: var(--bg-input);
border-left: 3px solid var(--accent-gold);
padding: 0.75rem;
margin-bottom: 0.5rem;
color: var(--text-secondary);
}
.starting-location {
background: var(--bg-input);
border: 1px solid var(--border-primary);
border-radius: 4px;
padding: 1rem;
}
.location-description {
color: var(--text-secondary);
font-size: var(--text-sm);
margin: 0.5rem 0 0 0;
}
/* ===== EMPTY STATE ===== */
.empty-text {
color: var(--text-muted);
font-style: italic;
margin: 0.5rem 0 1rem 0;
}
/* ===== BUTTONS ===== */
.btn-block {
display: block;
width: 100%;
margin-bottom: 1.5rem;
}
.character-actions {
text-align: center;
}
.btn-danger {
background: transparent;
border: 2px solid var(--accent-red);
color: var(--accent-red);
}
.btn-danger:hover {
background: var(--accent-red);
color: var(--text-primary);
}
/* ===== RESPONSIVE ===== */
@media (max-width: 768px) {
.character-detail-container {
padding: 1rem;
}
.detail-header {
flex-direction: column;
align-items: flex-start;
}
.detail-grid {
grid-template-columns: 1fr;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
}
</style>
{% endblock %}