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:
@@ -55,8 +55,9 @@ def test_invalid_action_is_400(client):
|
||||
def test_state_shape(client):
|
||||
state = client.post("/api/game", params={"seed": 5}).json()
|
||||
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 sum(state["shop_odds"]) == 100
|
||||
assert state["player"]["score"] == 0.0
|
||||
assert all(o["board"] is not None for o in state["opponents"])
|
||||
|
||||
|
||||
@@ -173,6 +173,7 @@ def serialize(game: Game) -> dict:
|
||||
for api in game.shop
|
||||
],
|
||||
"shop_locked": game.shop_locked,
|
||||
"shop_odds": game.cfg["shop"]["odds"][game.level - 1],
|
||||
"traits": traits_panel,
|
||||
"augment_offer": [
|
||||
{"api_name": a, "name": static["augments"].get(a, {}).get("name", a),
|
||||
|
||||
@@ -20,10 +20,6 @@ const kindLabel = { pvp: 'PvP', pve: 'PvE', carousel: 'Carousel' }
|
||||
<header>
|
||||
<span class="round">Runde {{ s.round.label }} · {{ kindLabel[s.round.kind] }}</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>
|
||||
<button class="fight" :disabled="s.over" @click="store.next()">
|
||||
{{ s.round.kind === 'pvp' ? 'Kampf' : 'Weiter' }} ▶
|
||||
|
||||
@@ -1,11 +1,29 @@
|
||||
<script setup>
|
||||
import { store } from '../store.js'
|
||||
const costColors = ['#8a8f9d', '#4caf6e', '#4a90d9', '#b153d8', '#e5a531']
|
||||
|
||||
function owned(api) {
|
||||
return [...store.state.board, ...store.state.bench].some((u) => u.api_name === api)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="shopwrap">
|
||||
<div class="topbar">
|
||||
<div class="tleft">
|
||||
<span class="lvl">Lvl. {{ store.state.player.level }}</span>
|
||||
<span v-if="store.state.player.xp_needed !== null" class="xp">
|
||||
{{ store.state.player.xp }}/{{ store.state.player.xp_needed }}
|
||||
</span>
|
||||
<span class="odds">
|
||||
<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"
|
||||
@@ -15,6 +33,17 @@ const costColors = ['#8a8f9d', '#4caf6e', '#4a90d9', '#b153d8', '#e5a531']
|
||||
<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 class="shoprow">
|
||||
<div class="controls">
|
||||
@@ -47,7 +76,7 @@ const costColors = ['#8a8f9d', '#4caf6e', '#4a90d9', '#b153d8', '#e5a531']
|
||||
>
|
||||
<template v-if="s">
|
||||
<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">
|
||||
<span v-for="n in s.upgrade" :key="n">★</span>
|
||||
</div>
|
||||
@@ -63,15 +92,6 @@ const costColors = ['#8a8f9d', '#4caf6e', '#4a90d9', '#b153d8', '#e5a531']
|
||||
</template>
|
||||
</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>
|
||||
</template>
|
||||
@@ -79,7 +99,13 @@ const costColors = ['#8a8f9d', '#4caf6e', '#4a90d9', '#b153d8', '#e5a531']
|
||||
<style scoped>
|
||||
.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 {
|
||||
background: #161b29;
|
||||
border: 1px solid #3a4460;
|
||||
@@ -175,8 +201,8 @@ const costColors = ['#8a8f9d', '#4caf6e', '#4a90d9', '#b153d8', '#e5a531']
|
||||
.ccost { color: #ffd75e; font-weight: 700; }
|
||||
|
||||
.lock {
|
||||
width: 44px;
|
||||
font-size: 17px;
|
||||
font-size: 15px;
|
||||
padding: 3px 10px;
|
||||
background: #141927;
|
||||
}
|
||||
.lock.locked { border-color: #ffd75e; background: #2a2618; }
|
||||
|
||||
Reference in New Issue
Block a user