Shop-Topbar: Level/XP/Odds links, Lock rechts; Shine nur bei eigenem Champion

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
team3
2026-07-23 05:58:07 +02:00
parent 50c3f2857e
commit 885bf28fd4
4 changed files with 50 additions and 26 deletions

View File

@@ -55,8 +55,9 @@ def test_invalid_action_is_400(client):
def test_state_shape(client): def test_state_shape(client):
state = client.post("/api/game", params={"seed": 5}).json() state = client.post("/api/game", params={"seed": 5}).json()
for key in ("round", "player", "board", "bench", "shop", "traits", for key in ("round", "player", "board", "bench", "shop", "traits",
"opponents", "items", "over", "log"): "opponents", "items", "over", "log", "shop_odds", "shop_locked"):
assert key in state assert key in state
assert sum(state["shop_odds"]) == 100
assert state["player"]["score"] == 0.0 assert state["player"]["score"] == 0.0
assert all(o["board"] is not None for o in state["opponents"]) assert all(o["board"] is not None for o in state["opponents"])

View File

@@ -173,6 +173,7 @@ def serialize(game: Game) -> dict:
for api in game.shop for api in game.shop
], ],
"shop_locked": game.shop_locked, "shop_locked": game.shop_locked,
"shop_odds": game.cfg["shop"]["odds"][game.level - 1],
"traits": traits_panel, "traits": traits_panel,
"augment_offer": [ "augment_offer": [
{"api_name": a, "name": static["augments"].get(a, {}).get("name", a), {"api_name": a, "name": static["augments"].get(a, {}).get("name", a),

View File

@@ -20,10 +20,6 @@ const kindLabel = { pvp: 'PvP', pve: 'PvE', carousel: 'Carousel' }
<header> <header>
<span class="round">Runde {{ s.round.label }} · {{ kindLabel[s.round.kind] }}</span> <span class="round">Runde {{ s.round.label }} · {{ kindLabel[s.round.kind] }}</span>
<span class="stat"> {{ s.player.hp }}</span> <span class="stat"> {{ s.player.hp }}</span>
<span class="stat">
Lvl {{ s.player.level }}
<template v-if="s.player.xp_needed !== null">({{ s.player.xp }}/{{ s.player.xp_needed }})</template>
</span>
<span class="score">Boardstärke {{ s.player.score }}</span> <span class="score">Boardstärke {{ s.player.score }}</span>
<button class="fight" :disabled="s.over" @click="store.next()"> <button class="fight" :disabled="s.over" @click="store.next()">
{{ s.round.kind === 'pvp' ? 'Kampf' : 'Weiter' }} {{ s.round.kind === 'pvp' ? 'Kampf' : 'Weiter' }}

View File

@@ -1,19 +1,48 @@
<script setup> <script setup>
import { store } from '../store.js' import { store } from '../store.js'
const costColors = ['#8a8f9d', '#4caf6e', '#4a90d9', '#b153d8', '#e5a531'] const costColors = ['#8a8f9d', '#4caf6e', '#4a90d9', '#b153d8', '#e5a531']
function owned(api) {
return [...store.state.board, ...store.state.bench].some((u) => u.api_name === api)
}
</script> </script>
<template> <template>
<div class="shopwrap"> <div class="shopwrap">
<div class="topbar"> <div class="topbar">
<span class="pill gold">🪙 {{ store.state.player.gold }}</span> <div class="tleft">
<span <span class="lvl">Lvl. {{ store.state.player.level }}</span>
v-if="store.state.player.streak !== 0" <span v-if="store.state.player.xp_needed !== null" class="xp">
class="pill streak" {{ store.state.player.xp }}/{{ store.state.player.xp_needed }}
:class="store.state.player.streak > 0 ? 'hot' : 'cold'" </span>
> <span class="odds">
<span class="flame">🔥</span> {{ Math.abs(store.state.player.streak) }} <span
</span> v-for="(p, i) in store.state.shop_odds"
:key="i"
:style="{ color: costColors[i] }"
> {{ p }}%</span>
</span>
</div>
<div class="tcenter">
<span class="pill gold">🪙 {{ store.state.player.gold }}</span>
<span
v-if="store.state.player.streak !== 0"
class="pill streak"
:class="store.state.player.streak > 0 ? 'hot' : 'cold'"
>
<span class="flame">🔥</span> {{ Math.abs(store.state.player.streak) }}
</span>
</div>
<div class="tright">
<button
class="lock"
:class="{ locked: store.state.shop_locked }"
:title="store.state.shop_locked ? 'Shop entsperren' : 'Shop sperren'"
@click="store.act({ action: 'lock' })"
>
{{ store.state.shop_locked ? '🔒' : '🔓' }}
</button>
</div>
</div> </div>
<div class="shoprow"> <div class="shoprow">
@@ -47,7 +76,7 @@ const costColors = ['#8a8f9d', '#4caf6e', '#4a90d9', '#b153d8', '#e5a531']
> >
<template v-if="s"> <template v-if="s">
<div class="art" :style="{ backgroundImage: `url(${s.icon})` }" /> <div class="art" :style="{ backgroundImage: `url(${s.icon})` }" />
<div class="shine" /> <div v-if="owned(s.api_name)" class="shine" />
<div v-if="s.upgrade" class="starhint"> <div v-if="s.upgrade" class="starhint">
<span v-for="n in s.upgrade" :key="n"></span> <span v-for="n in s.upgrade" :key="n"></span>
</div> </div>
@@ -63,15 +92,6 @@ const costColors = ['#8a8f9d', '#4caf6e', '#4a90d9', '#b153d8', '#e5a531']
</template> </template>
</div> </div>
</div> </div>
<button
class="lock"
:class="{ locked: store.state.shop_locked }"
:title="store.state.shop_locked ? 'Shop entsperren' : 'Shop sperren'"
@click="store.act({ action: 'lock' })"
>
{{ store.state.shop_locked ? '🔒' : '🔓' }}
</button>
</div> </div>
</div> </div>
</template> </template>
@@ -79,7 +99,13 @@ const costColors = ['#8a8f9d', '#4caf6e', '#4a90d9', '#b153d8', '#e5a531']
<style scoped> <style scoped>
.shopwrap { display: flex; flex-direction: column; gap: 6px; } .shopwrap { display: flex; flex-direction: column; gap: 6px; }
.topbar { display: flex; justify-content: center; gap: 10px; } .topbar { display: flex; align-items: center; gap: 10px; }
.tleft { flex: 1; display: flex; align-items: baseline; gap: 8px; }
.tcenter { display: flex; gap: 10px; }
.tright { flex: 1; display: flex; justify-content: flex-end; }
.lvl { font-weight: 800; font-size: 15px; }
.xp { color: #8a92a8; font-size: 12px; }
.odds { display: flex; gap: 8px; font-size: 12px; font-weight: 600; margin-left: 8px; }
.pill { .pill {
background: #161b29; background: #161b29;
border: 1px solid #3a4460; border: 1px solid #3a4460;
@@ -175,8 +201,8 @@ const costColors = ['#8a8f9d', '#4caf6e', '#4a90d9', '#b153d8', '#e5a531']
.ccost { color: #ffd75e; font-weight: 700; } .ccost { color: #ffd75e; font-weight: 700; }
.lock { .lock {
width: 44px; font-size: 15px;
font-size: 17px; padding: 3px 10px;
background: #141927; background: #141927;
} }
.lock.locked { border-color: #ffd75e; background: #2a2618; } .lock.locked { border-color: #ffd75e; background: #2a2618; }