perf: serve optimized JPEG derivatives for monster artwork

encounter-card.component rendered the raw 2.39MB ash-rat.png and
2.00MB road-bandit.png directly, up to 3 cards per hunt (~7MB/load,
re-rendered on "Neu suchen"). Add 560px-wide JPEG runtime derivatives
(generated via PowerShell System.Drawing, HighQualityBicubic, quality
82) and switch to the <picture>/<source srcset> pattern already used
by context-panel for location backgrounds, keeping the original PNGs
as the <img> fallback. Also add loading="lazy" decoding="async".

ash-rat.png: 2,506,677 B -> ash-rat-560.jpg: 35,896 B
road-bandit.png: 2,097,049 B -> road-bandit-560.jpg: 50,797 B
This commit is contained in:
Bastian Wagner
2026-08-19 14:33:32 +02:00
parent 26909d7e2a
commit 833fa52d8d
4 changed files with 21 additions and 5 deletions

View File

@@ -1,10 +1,17 @@
<article class="encounter-card">
<div class="encounter-card__artwork-frame">
<img
class="encounter-card__artwork"
[src]="encounter.monster.artworkPath"
[alt]="encounter.monster.name"
/>
<picture>
@if (runtimeArtworkPath(encounter.monster.artworkPath); as runtimeArtwork) {
<source [srcset]="runtimeArtwork" type="image/jpeg" />
}
<img
class="encounter-card__artwork"
[src]="encounter.monster.artworkPath"
[alt]="encounter.monster.name"
loading="lazy"
decoding="async"
/>
</picture>
</div>
<div class="encounter-card__body">
<h3 class="encounter-card__name">{{ encounter.monster.name }}</h3>

View File

@@ -2,6 +2,11 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
import { HuntEncounter } from '../../../core/api/game-api.models';
import { DangerBadgeComponent } from '../../../shared/danger-badge/danger-badge.component';
const runtimeArtworkPaths: Readonly<Record<string, string>> = {
'/images/monsters/ash-rat.png': '/images/monsters/runtime/ash-rat-560.jpg',
'/images/monsters/road-bandit.png': '/images/monsters/runtime/road-bandit-560.jpg',
};
@Component({
selector: 'app-encounter-card',
imports: [DangerBadgeComponent],
@@ -15,4 +20,8 @@ export class EncounterCardComponent {
protected onAttack(): void {
this.attack.emit(this.encounter.id);
}
protected runtimeArtworkPath(artworkPath: string): string | undefined {
return runtimeArtworkPaths[artworkPath];
}
}