feat(combat): add CombatPageComponent and replace the combat/new placeholder route

Wires up the Slice 0.3 combat screen (player/monster HP bars, round
display, Angriff action, grouped German combat log, victory/defeat
panels) and replaces the Slice 0.2 combat/new placeholder route with
combat/:combatId loading CombatPageComponent.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Bastian Wagner
2026-08-19 16:53:59 +02:00
parent 4b1e7f034c
commit 1fd62cddde
6 changed files with 606 additions and 75 deletions

View File

@@ -0,0 +1,95 @@
<section class="combat-page" aria-label="Kampf">
@if (combatStore.combat(); as combat) {
<div class="combat-page__scene">
<div class="combat-page__combatant combat-page__combatant--player">
<img
class="combat-page__portrait"
src="/images/hud/runtime/CharacterIcon-128.png"
[alt]="combat.player.name"
/>
<div class="combat-page__info">
<span class="combat-page__name">{{ combat.player.name }}</span>
<div class="combat-page__hp" aria-label="Lebenspunkte">
<span>{{ combat.player.currentHp }} / {{ combat.player.maxHp }}</span>
<span class="combat-page__hp-track" aria-hidden="true">
<span class="combat-page__hp-value" [style.inline-size.%]="playerHpPercent()"></span>
</span>
</div>
</div>
</div>
<div class="combat-page__round" data-combat-round>Runde {{ combat.round }}</div>
<div class="combat-page__combatant combat-page__combatant--monster">
<picture>
@if (runtimeMonsterArtwork(combat.monster.artworkPath); as runtimeArtwork) {
<source [srcset]="runtimeArtwork" type="image/jpeg" />
}
<img
class="combat-page__portrait"
[src]="combat.monster.artworkPath"
[alt]="combat.monster.name"
/>
</picture>
<div class="combat-page__info">
<span class="combat-page__name">{{ combat.monster.name }}</span>
<span class="combat-page__level">Stufe {{ combat.monster.level }}</span>
<div class="combat-page__hp" aria-label="Lebenspunkte des Gegners">
<span>{{ combat.monster.currentHp }} / {{ combat.monster.maxHp }}</span>
<span class="combat-page__hp-track" aria-hidden="true">
<span
class="combat-page__hp-value combat-page__hp-value--monster"
[style.inline-size.%]="monsterHpPercent()"
></span>
</span>
</div>
</div>
</div>
</div>
<div class="combat-page__actions">
@if (combat.status === 'ACTIVE') {
<button
type="button"
class="combat-page__attack"
data-combat-attack
[disabled]="combatStore.actionPending()"
(click)="attack()"
>
Angriff
</button>
} @else if (combat.status === 'WON') {
<section class="combat-page__result combat-page__result--won" data-combat-result="WON">
<h2>Sieg</h2>
<p>{{ combat.monster.name }} wurde besiegt.</p>
<p class="combat-page__result-hint">Belohnungen werden im nächsten Schritt verarbeitet.</p>
<button type="button" data-combat-to-hunt (click)="goToHunt()">Zur Jagd</button>
</section>
} @else {
<section class="combat-page__result combat-page__result--lost" data-combat-result="LOST">
<h2>Niederlage</h2>
<p>{{ combat.player.name }} wurde im Kampf besiegt.</p>
<button type="button" data-combat-to-hunt (click)="goToHunt()">Zur Jagd</button>
</section>
}
</div>
<aside class="combat-page__log" aria-label="Kampfprotokoll">
@for (round of logRounds(); track round.round) {
<p class="combat-page__log-round">Runde {{ round.round }}</p>
@for (event of round.events; track event.sequence) {
<p class="combat-page__log-entry">{{ formatEvent(event) }}</p>
}
}
</aside>
} @else if (combatStore.loading()) {
<p class="combat-page__loading" role="status">Kampf wird geladen…</p>
}
@if (combatStore.error(); as error) {
<section class="combat-page__error" role="alert">
<p>{{ error }}</p>
<button type="button" data-combat-retry (click)="retry()">Erneut versuchen</button>
</section>
}
</section>