guandan.dev

guandan.dev

https://git.tonybtw.com/guandan.dev.git git://git.tonybtw.com/guandan.dev.git
4,510 bytes raw
1
import {
2
    Card as Card_Type, get_suit_symbol, get_rank_symbol, is_red_suit, is_wild, Rank, Rank_Red_Joker, Suit_Joker
3
} from '../game/types'
4
5
type Card_Size = 'small' | 'normal'
6
7
interface Card_Props {
8
    card: Card_Type
9
    level: Rank
10
    selected: boolean
11
    on_click: () => void
12
    size?: Card_Size
13
}
14
15
const SIZE_CONFIG = {
16
    small: { width: 48, height: 67, rank_font: 15, suit_font: 13 },
17
    normal: { width: 60, height: 84, rank_font: 22, suit_font: 20 },
18
}
19
20
export function Card({ card, level, selected, on_click, size = 'normal' }: Card_Props) {
21
    const is_joker = card.Suit === Suit_Joker
22
    const is_red = is_joker ? card.Rank === Rank_Red_Joker : is_red_suit(card.Suit)
23
    const is_wild_card = is_wild(card, level)
24
    const cfg = SIZE_CONFIG[size]
25
26
    const rank_display = is_joker
27
        ? (card.Rank === Rank_Red_Joker ? 'R' : 'B')
28
        : get_rank_symbol(card.Rank)
29
30
    const suit_display = is_joker ? '🃏' : get_suit_symbol(card.Suit)
31
32
    return (
33
        <div
34
            onClick={on_click}
35
            style={{
36
                width: cfg.width,
37
                height: cfg.height,
38
                backgroundColor: is_wild_card ? '#fff3cd' : '#fff',
39
                border: is_wild_card ? '2px solid #ffc107' : '1px solid #ccc',
40
                borderRadius: 6,
41
                position: 'relative',
42
                cursor: 'pointer',
43
                userSelect: 'none',
44
                boxShadow: '0 1px 3px rgba(0,0,0,0.12)',
45
                overflow: 'hidden',
46
            }}
47
        >
48
            {/* Selection overlay */}
49
            {selected && (
50
                <div
51
                    style={{
52
                        position: 'absolute',
53
                        top: 0,
54
                        left: 0,
55
                        right: 0,
56
                        bottom: 0,
57
                        backgroundColor: 'rgba(156, 39, 176, 0.35)',
58
                        borderRadius: 5,
59
                        pointerEvents: 'none',
60
                        zIndex: 10,
61
                    }}
62
                />
63
            )}
64
65
            {/* Rank and suit at TOP of card so it's visible when stacked */}
66
            <div
67
                style={{
68
                    position: 'absolute',
69
                    top: 4,
70
                    left: 0,
71
                    right: 0,
72
                    display: 'flex',
73
                    flexDirection: 'row',
74
                    alignItems: 'center',
75
                    justifyContent: 'center',
76
                    gap: 1,
77
                }}
78
            >
79
                <span
80
                    style={{
81
                        fontSize: cfg.rank_font,
82
                        fontWeight: 'bold',
83
                        color: is_red ? '#dc3545' : '#000',
84
                        lineHeight: 1,
85
                    }}
86
                >
87
                    {rank_display}
88
                </span>
89
                <span
90
                    style={{
91
                        fontSize: cfg.suit_font,
92
                        color: is_red ? '#dc3545' : '#000',
93
                        lineHeight: 1,
94
                    }}
95
                >
96
                    {suit_display}
97
                </span>
98
            </div>
99
100
            {/* Big suit in center */}
101
            <div
102
                style={{
103
                    position: 'absolute',
104
                    top: '55%',
105
                    left: '50%',
106
                    transform: 'translate(-50%, -50%)',
107
                    fontSize: size === 'small' ? 18 : 32,
108
                    color: is_red ? '#dc3545' : '#000',
109
                    opacity: 0.9,
110
                }}
111
            >
112
                {suit_display}
113
            </div>
114
        </div>
115
    )
116
}
117
118
interface Card_Back_Props {
119
    size?: Card_Size
120
}
121
122
export function Card_Back({ size = 'normal' }: Card_Back_Props) {
123
    const cfg = SIZE_CONFIG[size]
124
125
    return (
126
        <div
127
            style={{
128
                width: cfg.width,
129
                height: cfg.height,
130
                backgroundColor: '#1e3a5f',
131
                border: '1px solid #0d1b2a',
132
                borderRadius: 6,
133
                display: 'flex',
134
                alignItems: 'center',
135
                justifyContent: 'center',
136
                backgroundImage: 'repeating-linear-gradient(45deg, transparent, transparent 5px, rgba(255,255,255,0.1) 5px, rgba(255,255,255,0.1) 10px)',
137
            }}
138
        >
139
            <div style={{ color: '#fff', fontSize: size === 'small' ? 14 : 22 }}>🀄</div>
140
        </div>
141
    )
142
}