Merge branch 'worktree-local-location-view'

This commit is contained in:
Bastian Wagner
2026-08-20 16:42:57 +02:00
60 changed files with 3744 additions and 170 deletions

View File

@@ -117,17 +117,37 @@
</section>
}
<button type="button" class="outcome__button" data-combat-to-hunt (click)="goToHunt()">
Zur Jagd
</button>
<div class="outcome__buttons">
<button type="button" class="outcome__button" data-combat-to-hunt (click)="goToHunt()">
Weiter jagen
</button>
<button
type="button"
class="outcome__button outcome__button--secondary"
data-combat-to-location
(click)="goToLocation()"
>
Zum Ort
</button>
</div>
</div>
} @else if (combat.status === 'LOST') {
<div class="outcome outcome--lost" data-combat-result="LOST">
<h2 class="outcome__title">Niederlage</h2>
<p>{{ combat.player.name }} wurde im Kampf besiegt.</p>
<button type="button" class="outcome__button" data-combat-to-hunt (click)="goToHunt()">
Zur Jagd
</button>
<div class="outcome__buttons">
<button type="button" class="outcome__button" data-combat-to-hunt (click)="goToHunt()">
Weiter jagen
</button>
<button
type="button"
class="outcome__button outcome__button--secondary"
data-combat-to-location
(click)="goToLocation()"
>
Zum Ort
</button>
</div>
</div>
}
</div>

View File

@@ -490,6 +490,18 @@
margin-block-start: var(--ar-space-2);
}
.outcome__buttons {
display: flex;
gap: var(--ar-space-3);
justify-content: center;
}
.outcome__button--secondary {
border-color: var(--ar-border);
color: var(--ar-text-muted);
background: var(--ar-panel);
}
.outcome__button:hover,
.combat__notice--error button:hover {
border-color: var(--ar-gold);

View File

@@ -266,7 +266,7 @@ describe('CombatPageComponent', () => {
expect(element.querySelector('[data-combat-attack]')).toBeNull();
});
it('navigates to /hunt from the victory screen', async () => {
it('keeps the one-click hunt loop from the victory screen', async () => {
const fixture = await setup({ ...activeCombat, status: 'WON' });
const element = fixture.nativeElement as HTMLElement;
@@ -275,6 +275,25 @@ describe('CombatPageComponent', () => {
expect(router.navigate).toHaveBeenCalledWith(['/hunt']);
});
it('also offers the way back to the location from the victory screen', async () => {
const fixture = await setup({ ...activeCombat, status: 'WON' });
const element = fixture.nativeElement as HTMLElement;
element.querySelector<HTMLButtonElement>('[data-combat-to-location]')?.click();
expect(router.navigate).toHaveBeenCalledWith(['/location']);
});
it('offers the same two ways out after a defeat', async () => {
const fixture = await setup({ ...activeCombat, status: 'LOST' });
const element = fixture.nativeElement as HTMLElement;
expect(element.querySelector('[data-combat-to-hunt]')).not.toBeNull();
element.querySelector<HTMLButtonElement>('[data-combat-to-location]')?.click();
expect(router.navigate).toHaveBeenCalledWith(['/location']);
});
it('shows an error and retries loading the combat', async () => {
const fixture = await setup(null);
combatStore.error.set('Dieser Kampf wurde nicht gefunden.');

View File

@@ -161,6 +161,13 @@ export class CombatPageComponent implements OnInit {
void this.router.navigate(['/hunt']);
}
// The location is the screen a fight resolves back into. It sits beside
// "Weiter jagen" rather than replacing it, so the hunt loop keeps its
// one-click rhythm.
protected goToLocation(): void {
void this.router.navigate(['/location']);
}
protected monsterSprite(monsterKey: string, artworkPath: string): string {
return monsterCutoutPath(monsterKey) ?? runtimeMonsterArtworkPath(artworkPath) ?? artworkPath;
}