Shop-Leiste im TFT-Stil: Cards mit Splash, Trait-Pills, Shine, Upgrade-Sterne, Lock, Gold/Streak-Pills

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
team3
2026-07-23 05:47:52 +02:00
parent d191af34ca
commit 50c3f2857e
6 changed files with 214 additions and 45 deletions

View File

@@ -20,12 +20,10 @@ 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">🪙 {{ s.player.gold }}</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="stat">Streak {{ s.player.streak }}</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' }}

View File

@@ -4,57 +4,180 @@ const costColors = ['#8a8f9d', '#4caf6e', '#4a90d9', '#b153d8', '#e5a531']
</script>
<template>
<div class="shopbar">
<div class="controls">
<button
:disabled="store.state.player.xp_needed === null || store.state.player.gold < store.state.xp_cost"
@click="store.buyXp()"
<div class="shopwrap">
<div class="topbar">
<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'"
>
XP kaufen ({{ store.state.xp_cost }}g)
</button>
<button :disabled="store.state.player.gold < store.state.reroll_cost" @click="store.reroll()">
Reroll ({{ store.state.reroll_cost }}g)
</button>
<span class="flame">🔥</span> {{ Math.abs(store.state.player.streak) }}
</span>
</div>
<div class="slots">
<div
v-for="(s, i) in store.state.shop"
:key="i"
class="slot"
:class="{ empty: !s, unaffordable: s && s.cost > store.state.player.gold }"
:style="s ? { borderColor: costColors[s.cost - 1] } : {}"
@click="s && store.buy(i)"
>
<template v-if="s">
<img :src="s.icon" :alt="s.name" />
<div class="sname">{{ s.name }}</div>
<div class="straits">{{ s.traits.join(' · ') }}</div>
<div class="scost">{{ s.cost }}g</div>
</template>
<div class="shoprow">
<div class="controls">
<button
class="ctrl"
:disabled="store.state.player.gold < store.state.reroll_cost"
@click="store.reroll()"
>
<span class="ctrl-label">Refresh</span>
<span class="ctrl-cost">🪙 {{ store.state.reroll_cost }} </span>
</button>
<button
class="ctrl"
:disabled="store.state.player.xp_needed === null || store.state.player.gold < store.state.xp_cost"
@click="store.buyXp()"
>
<span class="ctrl-label">XP kaufen</span>
<span class="ctrl-cost">🪙 {{ store.state.xp_cost }} </span>
</button>
</div>
<div class="cards">
<div
v-for="(s, i) in store.state.shop"
:key="i"
class="card"
:class="{ empty: !s, unaffordable: s && s.cost > store.state.player.gold, upgrade: s && s.upgrade }"
:style="s ? { borderColor: s.upgrade ? '#ffd75e' : costColors[s.cost - 1] } : {}"
@click="s && store.buy(i)"
>
<template v-if="s">
<div class="art" :style="{ backgroundImage: `url(${s.icon})` }" />
<div class="shine" />
<div v-if="s.upgrade" class="starhint">
<span v-for="n in s.upgrade" :key="n"></span>
</div>
<div class="traitlist">
<span v-for="t in s.traits" :key="t.name" class="traitpill">
<img v-if="t.icon" :src="t.icon" :alt="t.name" />{{ t.name }}
</span>
</div>
<div class="cardbottom">
<span class="cname">{{ s.name }}</span>
<span class="ccost">🪙 {{ s.cost }}</span>
</div>
</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>
<style scoped>
.shopbar { display: flex; gap: 8px; }
.controls { display: flex; flex-direction: column; gap: 6px; justify-content: center; }
.slots { display: flex; gap: 6px; flex: 1; }
.slot {
.shopwrap { display: flex; flex-direction: column; gap: 6px; }
.topbar { display: flex; justify-content: center; gap: 10px; }
.pill {
background: #161b29;
border: 1px solid #3a4460;
border-radius: 14px;
padding: 3px 14px;
font-weight: 700;
}
.pill.gold { color: #ffd75e; }
.streak.hot { border-color: #b3543a; }
.streak.cold { border-color: #4a7fd9; }
.streak.cold .flame { filter: hue-rotate(190deg) saturate(2); }
.shoprow { display: flex; gap: 8px; align-items: stretch; }
.controls { display: flex; flex-direction: column; gap: 6px; width: 120px; }
.ctrl {
flex: 1;
display: flex; flex-direction: column;
align-items: flex-start; justify-content: center; gap: 2px;
background: linear-gradient(180deg, #1d2438, #141927);
border: 1px solid #4a4f30;
}
.ctrl:hover:not(:disabled) { border-color: #8a8340; }
.ctrl-label { font-weight: 700; font-size: 13px; }
.ctrl-cost { font-size: 11px; color: #c9b96a; }
.cards { display: flex; gap: 6px; flex: 1; }
.card {
flex: 1;
position: relative;
border: 2px solid #2a3145;
border-radius: 8px;
background: #1a2030;
padding: 4px;
background: #12141d;
height: 130px;
cursor: pointer;
text-align: center;
min-height: 96px;
overflow: hidden;
}
.slot:hover:not(.empty) { filter: brightness(1.2); }
.slot.empty { cursor: default; background: #12141d; }
.slot.unaffordable { opacity: 0.5; }
.slot img { width: 100%; height: 52px; object-fit: cover; border-radius: 5px; }
.sname { font-size: 12px; font-weight: 600; }
.straits { font-size: 10px; color: #7d86a0; }
.scost { font-size: 11px; color: #ffd75e; }
.card:hover:not(.empty) { filter: brightness(1.15); }
.card.empty { cursor: default; }
.card.unaffordable { opacity: 0.5; }
.card.upgrade { box-shadow: 0 0 10px #ffd75e88; }
.art { position: absolute; inset: 0; background-size: cover; background-position: center 20%; }
.shine { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
.shine::after {
content: '';
position: absolute;
top: 0; bottom: 0; width: 45%;
background: linear-gradient(105deg, transparent, rgba(255, 255, 255, 0.16), transparent);
animation: sweep 3s ease-in-out infinite;
}
@keyframes sweep {
0% { left: -50%; }
55% { left: 110%; }
100% { left: 110%; }
}
.starhint {
position: absolute;
top: 3px; left: 0; right: 0;
text-align: center;
color: #ffd75e;
text-shadow: 0 0 4px #000;
font-size: 15px;
letter-spacing: 2px;
}
.traitlist {
position: absolute;
top: 4px; left: 4px;
display: flex; flex-direction: column; gap: 3px;
}
.traitpill {
display: inline-flex; align-items: center; gap: 4px;
background: rgba(8, 10, 18, 0.72);
border-radius: 9px;
padding: 1px 7px 1px 3px;
font-size: 10px;
width: fit-content;
}
.traitpill img { width: 13px; height: 13px; filter: brightness(0) invert(1); }
.cardbottom {
position: absolute;
left: 0; right: 0; bottom: 0;
display: flex; justify-content: space-between; align-items: center;
padding: 14px 7px 4px;
background: linear-gradient(transparent, rgba(5, 7, 12, 0.92) 55%);
font-size: 12px;
}
.cname { font-weight: 700; }
.ccost { color: #ffd75e; font-weight: 700; }
.lock {
width: 44px;
font-size: 17px;
background: #141927;
}
.lock.locked { border-color: #ffd75e; background: #2a2618; }
</style>