Replaces the Characters placeholder with a real 5e character sheet feature, following the same read-only-detail/edit-split/game-notes pattern as NPCs and Enemies: - Character entity: identity, combat vitals, six ability scores, saving throw and skill proficiencies (computed modifiers, not stored), a small attacks table, and the sheet's freeform boxes (features, equipment, personality, appearance, backstory, allies, treasure). - Detail page shows the portrait uncropped, then the sheet - genuine <table>s for ability scores, skills and attacks, since that's the one part of a character sheet that actually is a table; everything else reuses the prose helpers from the enemy stat block. Game notes are the only editable field here, same as Enemy/NPC. - Edit page covers every field via CharacterType; the attack rows are six plain inputs handled by hand (not a Symfony collection field), since it's a short fixed-shape table, not a dynamic list. - Portraits follow the NPC convention: assets/image/characters/<NameNo Spaces>.png, computed path, not a DB column. - Imported the example sheet from assets/characters/ (Darragh Oakwanderer, halfling ranger) directly into the database. Tests: entity math (ability/save/skill modifiers, passive perception, portrait path), template rendering for show/edit/index (catches broken Twig/macro/path()/asset() calls), and the login-gate redirect. 21/21 passing; schema validate clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
38 lines
1.7 KiB
Twig
38 lines
1.7 KiB
Twig
{% extends 'base.html.twig' %}
|
|
|
|
{% block title %}Characters · dmtools{% endblock %}
|
|
|
|
{% block body %}
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<h1 class="h3 mb-0">Characters</h1>
|
|
<a href="{{ path('app_characters_new') }}" class="btn btn-primary btn-sm">Add character</a>
|
|
</div>
|
|
|
|
{% if characters is empty %}
|
|
<div class="card shadow-sm">
|
|
<div class="card-body text-center text-body-secondary py-5">
|
|
No party members yet. Use <strong>Add character</strong> to bring in a sheet.
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="row row-cols-1 row-cols-sm-2 row-cols-lg-3 g-3">
|
|
{% for c in characters %}
|
|
<div class="col">
|
|
<a href="{{ path('app_characters_show', {id: c.id}) }}" class="card shadow-sm h-100 text-decoration-none text-body">
|
|
<div class="char-portrait char-portrait--card">
|
|
<img src="{{ asset(c.portraitPath) }}" alt="{{ c.name }}" loading="lazy" onerror="this.parentElement.remove()">
|
|
</div>
|
|
<div class="card-body py-2">
|
|
<div class="fw-semibold text-truncate" title="{{ c.name }}">{{ c.name }}</div>
|
|
<div class="text-body-secondary small">{{ c.metaLine ?: '—' }}</div>
|
|
{% if c.playerName %}
|
|
<div class="text-body-secondary small">Played by {{ c.playerName }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</a>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
{% endblock %}
|