Play liveChoose your opponent
Join
32fe633
81 lines · 3.4 KB Raw Permalink History
1// In place of the upstream's flying cards: its useCardMoveAnimation draws and gains with these, between the deck and the
2// seats, with 无名杀's cards and card back (a discard shows in the middle of the table instead, as a played card). And
3// in place of its createCardFlyAnimation (a card you play, from your hand): where the card left your hand, so the table
4// flies it to the middle from there.
5import { cardArt, CARD_BACK } from './art';
6import { isRed } from './Card';
7
8export interface FlyCardFace {
9 name: string;
10 suit: string;
11 rank: string;
12}
13export type AnchorTarget = { kind: 'deck' } | { kind: 'discard' } | { kind: 'seat'; index: number } | { kind: 'offscreen' };
14
15const DURATION = 620;
16
17function anchor(t: AnchorTarget): { x: number; y: number } {
18 const sel = t.kind === 'seat' ? `[data-seat-index="${t.index}"]` : t.kind === 'offscreen' ? '' : `[data-zone-anchor="${t.kind}"]`;
19 const el = sel ? document.querySelector(sel) : null;
20 const r = el?.getBoundingClientRect();
21 if (r && r.width) return { x: r.left + r.width / 2, y: r.top + r.height / 2 };
22 return { x: innerWidth / 2, y: t.kind === 'offscreen' ? -80 : innerHeight * 0.42 };
23}
24
25/** The table's scale (the canvas is drawn at a design size and scaled to the frame): flying cards match its cards. */
26export const scale = () => Number(getComputedStyle(document.documentElement).getPropertyValue('--nm-scale')) || 1;
27
28function face(f: FlyCardFace | null): HTMLElement {
29 const el = document.createElement('div');
30 if (!f) {
31 el.className = 'nm-card back';
32 el.style.backgroundImage = `url(${CARD_BACK})`;
33 return el;
34 }
35 el.className = `nm-card${isRed(f.suit) ? ' red' : ''}`;
36 const art = cardArt(f.name);
37 const a = document.createElement('div');
38 a.className = art ? 'nm-card-art' : 'nm-card-big';
39 if (art) a.style.backgroundImage = `url(${art})`;
40 else a.textContent = f.name.slice(0, 2);
41 const n = document.createElement('div');
42 n.className = `nm-card-name${f.name.length > 3 ? ' long' : ''}`;
43 n.textContent = f.name;
44 el.append(a, n);
45 if (f.suit) {
46 const i = document.createElement('div');
47 i.className = `nm-card-info${isRed(f.suit) ? ' red' : ''}`;
48 i.textContent = `${f.suit}${f.rank}`;
49 el.append(i);
50 }
51 return el;
52}
53
54/** Where each card you played last left your hand (by card id), in the frame's pixels. */
55export const handOrigin = new Map<string, { x: number; y: number }>();
56
57export function createCardFlyAnimation(cardEl: HTMLElement, card: { id: string }): void {
58 const r = cardEl.getBoundingClientRect();
59 handOrigin.set(card.id, { x: r.left + r.width / 2, y: r.top + r.height / 2 });
60 if (handOrigin.size > 50) handOrigin.delete(handOrigin.keys().next().value!);
61}
62
63export function flyCards(from: AnchorTarget, to: AnchorTarget, faces: (FlyCardFace | null)[], count?: number): void {
64 const total = Math.min(count ?? faces.length, 8);
65 if (total <= 0 || to.kind === 'discard') return;
66 const a = anchor(from);
67 const b = anchor(to);
68 const s = scale();
69 const box = document.createElement('div');
70 box.className = 'nm-fly';
71 box.style.cssText = `left:${a.x}px;top:${a.y}px;--dx:${b.x - a.x}px;--dy:${b.y - a.y}px;--s:${s * 0.8};--n:${total};animation-duration:${DURATION}ms`;
72 for (let i = 0; i < total; i++) {
73 const c = face(faces[i] ?? null);
74 c.style.setProperty('--i', String(i));
75 box.append(c);
76 }
77 document.body.append(box);
78 const done = () => box.remove();
79 box.addEventListener('animationend', done);
80 setTimeout(done, DURATION + 300);
81}