Tracker: unfold a combatant's stat block inline
- extract the 5e stat-block markup into enemy/_statblock.html.twig (sb.block(enemy) macro); the enemy detail page now uses it too - each tracker combatant gets a "stat block" toggle that expands the block in place (Bootstrap collapse), so you don't have to leave the encounter to check AC / attacks - open blocks are remembered in localStorage and re-opened after each damage/heal reload, and the caret rotates when expanded Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,29 +2,7 @@
|
||||
|
||||
{% block title %}{{ enemy.name }} · Enemies · dmtools{% endblock %}
|
||||
|
||||
{% macro mod(n) %}{{ n >= 0 ? '+' ~ n : n }}{% endmacro %}
|
||||
|
||||
{% macro stat(label, score, m) %}
|
||||
<div class="col text-center">
|
||||
<div class="text-uppercase small text-body-secondary">{{ label }}</div>
|
||||
<div class="fw-semibold">{{ score }} ({{ m }})</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro line(label, value) %}
|
||||
{% if value %}
|
||||
<div><span class="fw-semibold">{{ label }}.</span> {{ value }}</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro section(title, body) %}
|
||||
{% if body %}
|
||||
<h2 class="h6 border-bottom pb-1 mt-3">{{ title }}</h2>
|
||||
<div>{{ body|nl2br }}</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% import _self as sb %}
|
||||
{% import 'enemy/_statblock.html.twig' as sb %}
|
||||
|
||||
{% block body %}
|
||||
<div class="d-flex justify-content-between align-items-start mb-3 gap-3">
|
||||
@@ -40,40 +18,7 @@
|
||||
<div class="col-lg-7">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
{{ sb.line('Armor Class', enemy.armorClass ~ (enemy.armorClassNote ? ' (' ~ enemy.armorClassNote ~ ')' : '')) }}
|
||||
{{ sb.line('Hit Points', enemy.hitPoints ~ (enemy.hitDice ? ' (' ~ enemy.hitDice ~ ')' : '')) }}
|
||||
{{ sb.line('Speed', enemy.speed) }}
|
||||
|
||||
<div class="row row-cols-6 my-3 g-1">
|
||||
{{ sb.stat('Str', enemy.strength, sb.mod(enemy.abilityModifier(enemy.strength))) }}
|
||||
{{ sb.stat('Dex', enemy.dexterity, sb.mod(enemy.abilityModifier(enemy.dexterity))) }}
|
||||
{{ sb.stat('Con', enemy.constitution, sb.mod(enemy.abilityModifier(enemy.constitution))) }}
|
||||
{{ sb.stat('Int', enemy.intelligence, sb.mod(enemy.abilityModifier(enemy.intelligence))) }}
|
||||
{{ sb.stat('Wis', enemy.wisdom, sb.mod(enemy.abilityModifier(enemy.wisdom))) }}
|
||||
{{ sb.stat('Cha', enemy.charisma, sb.mod(enemy.abilityModifier(enemy.charisma))) }}
|
||||
</div>
|
||||
|
||||
{{ sb.line('Saving Throws', enemy.savingThrows) }}
|
||||
{{ sb.line('Skills', enemy.skills) }}
|
||||
{{ sb.line('Damage Vulnerabilities', enemy.damageVulnerabilities) }}
|
||||
{{ sb.line('Damage Resistances', enemy.damageResistances) }}
|
||||
{{ sb.line('Damage Immunities', enemy.damageImmunities) }}
|
||||
{{ sb.line('Condition Immunities', enemy.conditionImmunities) }}
|
||||
{{ sb.line('Senses', enemy.senses) }}
|
||||
{{ sb.line('Languages', enemy.languages ?? '—') }}
|
||||
{{ sb.line('Challenge', enemy.challengeRating ? enemy.challengeRating ~ (enemy.experiencePoints ? ' (' ~ enemy.experiencePoints ~ ' XP)' : '') : null) }}
|
||||
{{ sb.line('Proficiency Bonus', enemy.proficiencyBonus ? sb.mod(enemy.proficiencyBonus) : null) }}
|
||||
|
||||
{{ sb.section('Traits', enemy.traits) }}
|
||||
{{ sb.section('Actions', enemy.actions) }}
|
||||
{{ sb.section('Bonus Actions', enemy.bonusActions) }}
|
||||
{{ sb.section('Reactions', enemy.reactions) }}
|
||||
{{ sb.section('Legendary Actions', enemy.legendaryActions) }}
|
||||
{{ sb.section('Description', enemy.description) }}
|
||||
|
||||
{% if enemy.source %}
|
||||
<p class="text-body-secondary small mt-3 mb-0">Source: {{ enemy.source }}</p>
|
||||
{% endif %}
|
||||
{{ sb.block(enemy) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user