Improve some animations.
This commit is contained in:
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import type { Card, ClientMessage, GameView, PlayerView } from '../types'
|
||||
import { CardView } from './CardView'
|
||||
import { useCardAnimations } from '../anim'
|
||||
|
||||
interface Flyer {
|
||||
key: string
|
||||
@@ -27,6 +28,12 @@ export function ShopPhase({ view, you, send }: Props) {
|
||||
const pending = view.pending
|
||||
const myPending = pending?.playerId === you.id
|
||||
|
||||
// Cards flip in off the deck, spring in when bought, and shrink away when
|
||||
// sold/bought; surviving cards glide to close the gap. Empty shop slots
|
||||
// (id '') carry no flip key, so they sit still.
|
||||
const shopAnim = useCardAnimations(view.shopRow, (c) => c.id || 'empty')
|
||||
const deckAnim = useCardAnimations(deck, (c) => c.id)
|
||||
|
||||
// Drop selections that no longer exist (bought/traded/discarded cards).
|
||||
useEffect(() => {
|
||||
setSelected((sel) => sel.filter((id) => deck.some((c) => c.id === id)))
|
||||
@@ -129,20 +136,26 @@ export function ShopPhase({ view, you, send }: Props) {
|
||||
Shop · Tier {view.round}
|
||||
<span className="muted"> · {view.deckCounts[view.round - 1]} left in deck</span>
|
||||
</div>
|
||||
<div className="shop-row">
|
||||
<div className="shop-row" ref={shopAnim.containerRef}>
|
||||
{view.shopRow.map((c, i) =>
|
||||
c.id ? (
|
||||
<CardView
|
||||
key={c.id}
|
||||
card={c}
|
||||
size="lg"
|
||||
disabled={!canBuy}
|
||||
onClick={canBuy ? () => act({ type: 'buy', row: i }) : undefined}
|
||||
/>
|
||||
<div key={c.id} className="card-cell" data-flip-key={c.id} data-enter="flip">
|
||||
<CardView
|
||||
card={c}
|
||||
size="lg"
|
||||
disabled={!canBuy}
|
||||
onClick={canBuy ? () => act({ type: 'buy', row: i }) : undefined}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div key={`empty-${i}`} className="card-slot-empty" />
|
||||
),
|
||||
)}
|
||||
{shopAnim.ghosts.map((g) => (
|
||||
<div key={g.key} className="card-cell card-ghost" style={g.style}>
|
||||
<CardView card={g.item} size="lg" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{myTurn && (
|
||||
<div className="hint">
|
||||
@@ -165,14 +178,20 @@ export function ShopPhase({ view, you, send }: Props) {
|
||||
{deck.length === 0 ? (
|
||||
<div className="muted deck-empty">No cards yet — buy something!</div>
|
||||
) : (
|
||||
<div className="deck-row">
|
||||
<div className="deck-row" ref={deckAnim.containerRef}>
|
||||
{deck.map((c) => (
|
||||
<CardView
|
||||
key={c.id}
|
||||
card={c}
|
||||
selected={selected.includes(c.id)}
|
||||
onClick={() => toggle(c.id)}
|
||||
/>
|
||||
<div key={c.id} className="card-cell" data-flip-key={c.id} data-enter="pop">
|
||||
<CardView
|
||||
card={c}
|
||||
selected={selected.includes(c.id)}
|
||||
onClick={() => toggle(c.id)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{deckAnim.ghosts.map((g) => (
|
||||
<div key={g.key} className="card-cell card-ghost" style={g.style}>
|
||||
<CardView card={g.item} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user