This commit is contained in:
Bastian Wagner
2026-08-22 16:41:47 +02:00
parent dfa62fd152
commit 081c9f83f9
137 changed files with 11594 additions and 1302 deletions

View File

@@ -0,0 +1,265 @@
@if (store.loading()) {
<p class="merchant__status">Approaching…</p>
} @else if (store.error()) {
<div class="merchant__status merchant__status--error" role="alert">
<p>{{ store.error() }}</p>
<button type="button" class="merchant__button" (click)="leave()">
Back to the gate
</button>
</div>
} @else if (store.interaction(); as interaction) {
<article class="merchant">
<header class="merchant__identity">
<img
class="merchant__portrait"
[src]="interaction.npc.portraitPath"
[alt]="interaction.npc.name"
(error)="($any($event.target).style.visibility = 'hidden')"
/>
<div class="merchant__naming">
<h1 class="merchant__name">{{ interaction.npc.name }}</h1>
@if (interaction.npc.title) {
<p class="merchant__title">{{ interaction.npc.title }}</p>
}
@if (interaction.npc.description) {
<p class="merchant__description">{{ interaction.npc.description }}</p>
}
</div>
</header>
@if (interaction.dialogue) {
<blockquote class="merchant__dialogue" data-dialogue>
{{ interaction.dialogue.text }}
</blockquote>
}
<nav class="merchant__actions" aria-label="Interactions">
@for (action of interaction.availableActions; track action.type) {
<button
type="button"
class="merchant__button"
[class.merchant__button--active]="
(action.type === 'OPEN_EXCHANGE' && isPanel('EXCHANGE')) ||
(action.type === 'OPEN_SHOP' && isPanel('SHOP')) ||
(action.type === 'TALK' && isPanel('DIALOGUE'))
"
[attr.data-action]="action.type"
(click)="activate(action.type, action.key)"
>
{{ action.label }}
</button>
}
<button type="button" class="merchant__button" (click)="leave()">
Leave
</button>
</nav>
@if (store.actionError()) {
<p class="merchant__error" role="alert">{{ store.actionError() }}</p>
}
@if (isPanel('EXCHANGE') && store.exchange(); as exchange) {
<section class="merchant__panel" aria-label="Trade in goods">
<h2 class="merchant__panel-title">{{ exchange.profileName }}</h2>
<app-loot-capacity-strip [capacities]="exchange.capacities" />
@if (exchange.offers.length === 0) {
<p class="merchant__empty">There is nothing here he will take.</p>
} @else {
<ul class="trade-list">
@for (offer of exchange.offers; track offer.itemKey) {
<li
class="trade-row"
[class.trade-row--empty]="offer.quantityCarried === 0"
[attr.data-trade-item]="offer.itemKey"
>
<img
class="trade-row__icon"
[src]="offer.iconPath"
[alt]=""
aria-hidden="true"
/>
<div class="trade-row__naming">
<span class="trade-row__name">{{ offer.itemName }}</span>
<span class="trade-row__carried" data-carried>
Carrying {{ offer.quantityCarried }}
</span>
</div>
<div class="trade-row__value" data-value>
<span class="trade-row__silver"
>{{ offer.silverPerStep }} Silver</span
>
<span class="trade-row__rep"
>+{{ offer.reputationPerStep }}
{{ offer.factionName }}</span
>
</div>
<div class="trade-row__picker">
<button
type="button"
class="trade-row__step"
aria-label="Trade fewer"
[disabled]="
offer.quantityCarried === 0 || store.pending() !== null
"
(click)="step(offer.itemKey, -1, offer.inputQuantity)"
>
</button>
<input
class="trade-row__quantity"
type="number"
min="0"
[max]="offer.quantityCarried"
[step]="offer.inputQuantity"
[value]="quantityFor(offer.itemKey)"
[disabled]="
offer.quantityCarried === 0 || store.pending() !== null
"
[attr.aria-label]="'Quantity of ' + offer.itemName"
(input)="
onQuantityInput(offer.itemKey, $any($event.target).value)
"
/>
<button
type="button"
class="trade-row__step"
aria-label="Trade more"
[disabled]="
offer.quantityCarried === 0 || store.pending() !== null
"
(click)="step(offer.itemKey, 1, offer.inputQuantity)"
>
+
</button>
</div>
</li>
}
</ul>
<footer class="trade-footer">
<p class="trade-footer__preview" data-preview>
Selected:
<strong>{{ store.preview().silver }} Silver</strong>
and
<strong>{{ store.preview().reputation }} Reputation</strong>
</p>
<div class="trade-footer__buttons">
<button
type="button"
class="merchant__button"
[disabled]="store.pending() !== null"
(click)="store.selectAll()"
>
Select All
</button>
<button
type="button"
class="merchant__button merchant__button--primary"
data-trade-confirm
[disabled]="!store.hasSelection() || store.pending() !== null"
(click)="store.tradeSelected()"
>
{{ store.pending() === 'trade' ? 'Handing over…' : 'Trade Selected' }}
</button>
</div>
</footer>
}
@if (store.lastTrade(); as trade) {
<section class="trade-summary" data-trade-summary aria-live="polite">
<h3 class="trade-summary__title">Trade Complete</h3>
<ul class="trade-summary__consumed">
@for (entry of trade.consumed; track entry.itemKey) {
<li>{{ entry.quantity }} × {{ entry.itemName }} handed in</li>
}
</ul>
<ul class="trade-summary__rewards">
<li>+{{ trade.rewards.silver }} Silver</li>
<li>+{{ trade.rewards.regionalReputation }} Border Watch Reputation</li>
@if (trade.rewards.worldRenown > 0) {
<li class="trade-summary__renown" data-renown>
+{{ trade.rewards.worldRenown }} World Renown
</li>
}
</ul>
@if (trade.reputationRankChanged) {
<p class="trade-summary__rank" data-rank-changed>
The Border Watch now regards you differently.
</p>
}
<button
type="button"
class="merchant__button"
(click)="store.dismissTradeSummary()"
>
Done
</button>
</section>
}
</section>
}
@if (isPanel('SHOP') && store.shop(); as shop) {
<section class="merchant__panel" aria-label="Wares for sale">
<h2 class="merchant__panel-title">{{ shop.shopName }}</h2>
<p class="merchant__purse" data-purse>{{ shop.silver }} Silver</p>
<ul class="shop-list">
@for (offer of shop.offers; track offer.itemKey) {
<li class="shop-row" [attr.data-shop-item]="offer.itemKey">
<img
class="shop-row__icon"
[src]="offer.iconPath"
[alt]=""
aria-hidden="true"
/>
<div class="shop-row__naming">
<span class="shop-row__name">{{ offer.itemName }}</span>
<span class="shop-row__description">{{
offer.itemDescription
}}</span>
</div>
<span class="shop-row__price">{{ offer.price }} Silver</span>
<button
type="button"
class="merchant__button"
[disabled]="
!offer.unlocked ||
!offer.affordable ||
store.pending() !== null
"
(click)="store.buy(offer.itemKey)"
>
@if (!offer.unlocked) {
Locked
} @else if (!offer.affordable) {
Too costly
} @else {
Buy
}
</button>
</li>
}
</ul>
@if (store.lastPurchase(); as purchase) {
<p class="shop-receipt" data-purchase aria-live="polite">
Bought {{ purchase.quantity }} × {{ purchase.itemName }} for
{{ purchase.silverSpent }} Silver.
<button
type="button"
class="merchant__link"
(click)="store.dismissPurchase()"
>
Dismiss
</button>
</p>
}
</section>
}
</article>
}