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>
This commit is contained in:
Frank
2026-09-22 08:55:28 +02:00
co-authored by Claude Sonnet 5
parent bdf676f1dd
commit 9052218927
18 changed files with 1628 additions and 9 deletions
+48
View File
@@ -154,6 +154,54 @@ table.dataTable {
.art-reorder-status.is-visible { opacity: 0.92; }
.art-reorder-status.is-error { background: var(--bs-danger); color: #fff; }
/* --- Character sheets -------------------------------------------------- */
.min-w-0 { min-width: 0; }
.char-portrait {
display: flex;
align-items: center;
justify-content: center;
background: var(--bs-tertiary-bg);
border: 1px solid var(--bs-border-color);
border-radius: 0.5rem;
overflow: hidden;
}
.char-portrait img { width: 100%; height: 100%; object-fit: contain; }
.char-portrait--card { height: 150px; border-radius: 0.375rem 0.375rem 0 0; border-width: 0 0 1px; }
.char-portrait--hero { height: 220px; margin-bottom: 1rem; }
.char-meta {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
gap: 0.5rem 1.25rem;
margin-bottom: 1rem;
padding: 0.85rem 1rem;
background: var(--bs-tertiary-bg);
border: 1px solid var(--bs-border-color);
border-radius: 0.5rem;
}
.char-meta > div { display: flex; flex-direction: column; }
.char-meta dt {
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--bs-secondary-color);
}
.char-meta dd { margin: 0.05rem 0 0; font-weight: 500; }
.char-ability-table td:first-child,
.char-ability-table th:first-child,
.char-skill-table td:first-child,
.char-skill-table th:first-child { white-space: nowrap; }
.char-ability-table tr.is-proficient,
.char-skill-table tr.is-proficient { font-weight: 600; }
.char-ability-table tr.is-proficient td:first-child,
.char-skill-table tr.is-proficient td:first-child {
box-shadow: inset 2px 0 0 var(--bs-primary);
padding-left: 0.5rem;
}
.char-skill-table { font-size: 0.875rem; }
/* --- Tracker: unfoldable stat blocks --------------------------------- */
[data-bs-toggle="collapse"] .sb-caret {
display: inline-block;