Files
FrankandClaude Sonnet 5 9052218927 Characters: build the page around Darragh Oakwanderer's sheet
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>
2026-09-22 08:55:28 +02:00

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 %}