More random UI fixes.
This commit is contained in:
@@ -723,7 +723,7 @@ func (g *Game) TradeStart(playerID string, cardIDs []string) error {
|
||||
ids[i] = c.ID
|
||||
}
|
||||
g.addLog(LogEntry{Seat: p.Seat, Icon: "🔄", Kind: LogTrade, Cards: ids,
|
||||
Text: fmt.Sprintf("%s traded in %s (%s) for a tier %d pick.",
|
||||
Text: fmt.Sprintf("%s tripled %s (%s) for a tier %d pick.",
|
||||
p.Name, strings.Join(names, ", "), suit, nextTier)})
|
||||
g.Pending = &PendingTrade{
|
||||
PlayerID: playerID,
|
||||
@@ -757,11 +757,11 @@ func (g *Game) TradeChoose(playerID string, pick int) error {
|
||||
// we have to reveal that pet (its effect log names it anyway).
|
||||
if hasBuyEffect(chosen) {
|
||||
g.addLog(LogEntry{Seat: p.Seat, Icon: "🔄", Kind: LogTradePick, CardName: chosen.Name,
|
||||
Text: fmt.Sprintf("%s's trade pick is %s %s — its buy ability triggers.",
|
||||
Text: fmt.Sprintf("%s's Triple pick is %s %s — its buy ability triggers.",
|
||||
p.Name, article(chosen.Name), chosen.Name)})
|
||||
} else {
|
||||
g.addLog(LogEntry{Seat: p.Seat, Icon: "🔄", Kind: LogTradePick,
|
||||
Text: fmt.Sprintf("%s keeps their trade pick hidden.", p.Name)})
|
||||
Text: fmt.Sprintf("%s keeps their Triple pick hidden.", p.Name)})
|
||||
}
|
||||
// Pets obtained via the Triple action trigger their Buy effects.
|
||||
g.applyShopTrigger(p, chosen, TriggerBuy)
|
||||
|
||||
@@ -537,13 +537,19 @@ export function BattlePhase({ view, send, step, setStep }: Props) {
|
||||
? { left: peek.rect.left }
|
||||
: { right: window.innerWidth - peek.rect.right }),
|
||||
}
|
||||
// Your deck sits on the left of the board and fights from its right
|
||||
// edge; the rival's sits on the right and fights from its left edge.
|
||||
// Order each peek to match, so "first" always points toward the clash.
|
||||
const mine = peek.seat === youSeat
|
||||
const firstSide = mine ? 'right' : 'left'
|
||||
return createPortal(
|
||||
<div className={`deck-peek deck-peek-${peek.dir}`} style={style}>
|
||||
<div className="deck-peek-label">
|
||||
{peek.seat === youSeat ? 'Your' : 'Opponent’s'} deck · {lineup.length} card
|
||||
{lineup.length !== 1 ? 's' : ''} (first on the right)
|
||||
{mine ? 'Your' : 'Opponent’s'} deck · {lineup.length} card
|
||||
{lineup.length !== 1 ? 's' : ''} (first on the {firstSide})
|
||||
</div>
|
||||
<div className="deck-peek-cards">
|
||||
<div className={`deck-peek-cards first-${firstSide}`}>
|
||||
<div className="deck-peek-first">⚔️ first</div>
|
||||
{lineup.map((c, i) => (
|
||||
<CardView key={c.id || i} card={c} size="sm" />
|
||||
))}
|
||||
|
||||
@@ -62,7 +62,7 @@ const TRIGGER_GLOSS: Record<string, string> = {
|
||||
Hurt: 'when it survives taking damage',
|
||||
Sell: 'when you sell it in the shop',
|
||||
Buy: 'when you buy it',
|
||||
Triple: 'when traded in as one of three',
|
||||
Triple: 'when tripled as one of three same-suit pets',
|
||||
'After Attacking': 'right after it attacks in a clash',
|
||||
'Battle Prep': 'as the battle is arranged',
|
||||
'Shop start': 'when the shop opens each round',
|
||||
|
||||
@@ -227,7 +227,7 @@ export function ShopPhase({ view, you, send }: Props) {
|
||||
<div className="shop-status">
|
||||
{pending && !myPending ? (
|
||||
<span className="muted">
|
||||
{opponent?.name ?? 'Opponent'} is trading up a tier…
|
||||
{opponent?.name ?? 'Opponent'} is tripling up a tier…
|
||||
</span>
|
||||
) : you.ready ? (
|
||||
<span className="muted">
|
||||
@@ -239,7 +239,7 @@ export function ShopPhase({ view, you, send }: Props) {
|
||||
Too many pets! Sell down to {view.maxPets} before you can pass 🍎
|
||||
</span>
|
||||
) : myTurn ? (
|
||||
<span className="status-hot">Your turn — buy, sell, trade, or pass</span>
|
||||
<span className="shop-status-pill">Your turn — buy, sell, triple, or pass</span>
|
||||
) : (
|
||||
<span className="muted">
|
||||
{view.players[view.turn]?.name ?? 'Opponent'}’s turn…
|
||||
@@ -305,7 +305,7 @@ export function ShopPhase({ view, you, send }: Props) {
|
||||
? 'Tap a card to buy it for 1 🪙 — selling and trading are free'
|
||||
: avocados > 0
|
||||
? 'No coins — tap a card to buy it by discarding an Avocado 🥑'
|
||||
: 'No coins left — you can still sell, trade, or pass'}
|
||||
: 'No coins left — you can still sell, triple, or pass'}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
@@ -398,9 +398,9 @@ export function ShopPhase({ view, you, send }: Props) {
|
||||
className="btn btn-secondary"
|
||||
disabled={!myTurn || !sameSuit || view.round >= view.maxRounds}
|
||||
onClick={() => act({ type: 'trade', cards: selected })}
|
||||
title="Trade 3 same-suit pets for a pick from the next tier — free"
|
||||
title="Triple 3 same-suit pets for a pick from the next tier — free"
|
||||
>
|
||||
Trade 3{' '}
|
||||
Triple 3{' '}
|
||||
{sameSuit && selectedCards[0].suit ? (
|
||||
<span className={`suit-dot suit-${selectedCards[0].suit}`} />
|
||||
) : (
|
||||
@@ -455,7 +455,7 @@ export function ShopPhase({ view, you, send }: Props) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Trade picker */}
|
||||
{/* Triple picker */}
|
||||
{myPending && pending && (
|
||||
<div className="modal-backdrop">
|
||||
<div className="modal">
|
||||
|
||||
@@ -36,8 +36,8 @@ export function Table({ session, onLeave }: { session: Session; onLeave: () => v
|
||||
})
|
||||
|
||||
// A shop-phase peek at an opponent's deck from the previous round's battle
|
||||
// (its arranged lineup is already public). Anchored under the clicked button.
|
||||
const [deckPeek, setDeckPeek] = useState<{ seat: number; rect: DOMRect } | null>(null)
|
||||
// (its arranged lineup is already public). Shown as a centered modal.
|
||||
const [deckPeek, setDeckPeek] = useState<{ seat: number } | null>(null)
|
||||
|
||||
// The battle outcome is known before the replay plays out, so we hold its
|
||||
// "result" log entry back: it's dropped from the persistent list during the
|
||||
@@ -158,10 +158,9 @@ export function Table({ session, onLeave }: { session: Session; onLeave: () => v
|
||||
<button
|
||||
className={`chip chip-btn ${deckPeek?.seat === p.seat ? 'is-active' : ''}`}
|
||||
title="See their deck from last round's battle"
|
||||
onClick={(e) => {
|
||||
const rect = e.currentTarget.getBoundingClientRect()
|
||||
setDeckPeek((cur) => (cur?.seat === p.seat ? null : { seat: p.seat, rect }))
|
||||
}}
|
||||
onClick={() =>
|
||||
setDeckPeek((cur) => (cur?.seat === p.seat ? null : { seat: p.seat }))
|
||||
}
|
||||
>
|
||||
👁 deck
|
||||
</button>
|
||||
@@ -214,25 +213,26 @@ export function Table({ session, onLeave }: { session: Session; onLeave: () => v
|
||||
const lineup = view.battle?.lineups?.[deckPeek.seat] ?? []
|
||||
if (!lineup.length) return null
|
||||
const oppName = view.players.find((p) => p.seat === deckPeek.seat)?.name ?? 'Opponent'
|
||||
const left = Math.max(8, Math.min(deckPeek.rect.left, window.innerWidth - 380))
|
||||
return createPortal(
|
||||
<>
|
||||
<div className="deck-peek-backdrop" onClick={() => setDeckPeek(null)} />
|
||||
<div className="modal-backdrop" onClick={() => setDeckPeek(null)}>
|
||||
<div
|
||||
className="deck-peek deck-peek-toolbar"
|
||||
style={{ top: deckPeek.rect.bottom + 8, left }}
|
||||
className="modal deck-peek-modal"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="deck-peek-label">
|
||||
{oppName}’s deck last round · {lineup.length} card
|
||||
{lineup.length !== 1 ? 's' : ''} (first on the right)
|
||||
{oppName}’s deck from last round’s battle · {lineup.length} card
|
||||
{lineup.length !== 1 ? 's' : ''} (first on the left)
|
||||
</div>
|
||||
<div className="deck-peek-cards">
|
||||
{/* The rival battles on your right, so their first pet faces you
|
||||
on the left — order the peek to match, first card on the left. */}
|
||||
<div className="deck-peek-cards first-left">
|
||||
<div className="deck-peek-first">⚔️ first</div>
|
||||
{lineup.map((c, i) => (
|
||||
<CardView key={c.id || i} card={c} size="sm" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>,
|
||||
</div>,
|
||||
document.body,
|
||||
)
|
||||
})()}
|
||||
|
||||
+58
-10
@@ -1246,6 +1246,24 @@ h3 {
|
||||
animation: pulse 1.6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* The "it's your turn" prompt as a solid golden badge with light text, so it
|
||||
reads as a call to action rather than bare pulsing text. */
|
||||
.shop-status-pill {
|
||||
display: inline-block;
|
||||
font-family: var(--font-display);
|
||||
font-size: 0.92em;
|
||||
color: #fff8e6;
|
||||
background: linear-gradient(180deg, var(--gold), var(--gold-deep));
|
||||
padding: 0.22em 0.85em;
|
||||
border-radius: 999px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.5);
|
||||
text-shadow: 0 1px 3px rgba(120, 78, 8, 0.5);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.5),
|
||||
inset 0 -4px 10px rgba(150, 100, 20, 0.3),
|
||||
0 4px 14px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
@@ -2601,22 +2619,52 @@ h3 {
|
||||
.deck-peek-cards {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
max-width: min(60vw, 560px);
|
||||
/* Read right-to-left like the arrangement screen: the first (top) card sits
|
||||
on the right and later pets flow left. */
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
/* Keep each card's own contents left-to-right despite the rtl row. */
|
||||
/* first-right: the first pet sits on the right (matches your own arrangement).
|
||||
first-left: the first pet sits on the left (a rival, who faces you from the
|
||||
right of the board). The "⚔️ first" marker is the first DOM child, so it
|
||||
lands next to the first card at whichever end. */
|
||||
.deck-peek-cards.first-right {
|
||||
direction: rtl;
|
||||
}
|
||||
.deck-peek-cards.first-left {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
/* Keep each card's own contents left-to-right regardless of row direction. */
|
||||
.deck-peek-cards > * {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
/* Backdrop behind the toolbar deck-peek: any click outside dismisses it. Sits
|
||||
below the popover (z 60) but above the board. */
|
||||
.deck-peek-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 55;
|
||||
/* Compact echo of the arrangement phase's "⚔️ first" indicator. */
|
||||
.deck-peek-first {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
font-family: var(--font-display);
|
||||
font-size: 0.68rem;
|
||||
color: var(--gold);
|
||||
white-space: nowrap;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
/* Deck peek shown as a centered modal (shop-phase rival deck): cap the width so
|
||||
a wide lineup wraps instead of running off-screen. */
|
||||
.deck-peek-modal {
|
||||
padding: 20px;
|
||||
gap: 12px;
|
||||
max-width: min(92vw, 720px);
|
||||
}
|
||||
|
||||
.deck-peek-modal .deck-peek-cards {
|
||||
max-width: 100%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.deck-peek-modal .deck-peek-label {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user