feat: enhance Card, CasinoButton, Chip, DealerArea, and TableScreen components; add animation, size adjustments, and improved UI elements for better user experience
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { StyleSheet, Text, View } from 'react-native';
|
import { useEffect, useRef } from 'react';
|
||||||
|
import { Animated, StyleSheet, Text, View } from 'react-native';
|
||||||
import { colors, fonts } from '../theme';
|
import { colors, fonts } from '../theme';
|
||||||
|
|
||||||
const suitSymbols = {
|
const suitSymbols = {
|
||||||
@@ -15,12 +16,70 @@ const suitColors = {
|
|||||||
D: colors.red
|
D: colors.red
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Card({ rank, suit, hidden }) {
|
export default function Card({ rank, suit, hidden, size = 'normal', animate = true, delay = 0 }) {
|
||||||
|
const scaleMap = { tiny: 0.78, small: 0.92, normal: 1.06, large: 1.18 };
|
||||||
|
const scale = scaleMap[size] ?? 1;
|
||||||
|
const appear = useRef(new Animated.Value(animate ? 0 : 1)).current;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!animate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Animated.timing(appear, {
|
||||||
|
toValue: 1,
|
||||||
|
duration: 260,
|
||||||
|
delay,
|
||||||
|
useNativeDriver: true
|
||||||
|
}).start();
|
||||||
|
}, [animate, appear, delay]);
|
||||||
|
|
||||||
|
const animatedStyle = animate
|
||||||
|
? {
|
||||||
|
opacity: appear,
|
||||||
|
transform: [
|
||||||
|
{
|
||||||
|
translateY: appear.interpolate({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: [-12 * scale, 0]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scale: appear.interpolate({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: [0.96, 1]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const cardStyle = [
|
||||||
|
styles.card,
|
||||||
|
{
|
||||||
|
width: 54 * scale,
|
||||||
|
height: 78 * scale,
|
||||||
|
borderRadius: 8 * scale,
|
||||||
|
padding: 6 * scale,
|
||||||
|
marginRight: 6 * scale
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const cornerStyle = [styles.corner, { fontSize: 12 * scale, lineHeight: 13 * scale }];
|
||||||
|
const centerStyle = [styles.center, { fontSize: 24 * scale, lineHeight: 26 * scale }];
|
||||||
|
const backPatternStyle = [
|
||||||
|
styles.backPattern,
|
||||||
|
{
|
||||||
|
width: 32 * scale,
|
||||||
|
height: 46 * scale,
|
||||||
|
borderRadius: 6 * scale,
|
||||||
|
borderWidth: Math.max(1, Math.round(2 * scale))
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
if (hidden || rank === 'X') {
|
if (hidden || rank === 'X') {
|
||||||
return (
|
return (
|
||||||
<View style={[styles.card, styles.cardBack]}>
|
<Animated.View style={[cardStyle, styles.cardBack, animatedStyle]}>
|
||||||
<View style={styles.backPattern} />
|
<View style={backPatternStyle} />
|
||||||
</View>
|
</Animated.View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,29 +87,29 @@ export default function Card({ rank, suit, hidden }) {
|
|||||||
const color = suitColors[suit] || colors.text;
|
const color = suitColors[suit] || colors.text;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.card}>
|
<Animated.View style={[cardStyle, animatedStyle]}>
|
||||||
<Text style={[styles.corner, { color }]}>{rank}</Text>
|
<Text style={[cornerStyle, { color }]}>{rank}</Text>
|
||||||
<Text style={[styles.corner, { color }]}>{symbol}</Text>
|
<Text style={[cornerStyle, { color }]}>{symbol}</Text>
|
||||||
<Text style={[styles.center, { color }]}>{symbol}</Text>
|
<Text style={[centerStyle, { color }]}>{symbol}</Text>
|
||||||
<View style={styles.cornerBottom}>
|
<View style={styles.cornerBottom}>
|
||||||
<Text style={[styles.corner, { color }]}>{rank}</Text>
|
<Text style={[cornerStyle, { color }]}>{rank}</Text>
|
||||||
<Text style={[styles.corner, { color }]}>{symbol}</Text>
|
<Text style={[cornerStyle, { color }]}>{symbol}</Text>
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
|
</Animated.View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
card: {
|
card: {
|
||||||
width: 54,
|
|
||||||
height: 78,
|
|
||||||
borderRadius: 8,
|
|
||||||
backgroundColor: '#fdf8f0',
|
backgroundColor: '#fdf8f0',
|
||||||
padding: 6,
|
|
||||||
marginRight: 6,
|
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: '#d2c1a4',
|
borderColor: '#d2c1a4',
|
||||||
justifyContent: 'space-between'
|
justifyContent: 'space-between',
|
||||||
|
overflow: 'hidden',
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOpacity: 0.18,
|
||||||
|
shadowRadius: 6,
|
||||||
|
shadowOffset: { width: 0, height: 3 }
|
||||||
},
|
},
|
||||||
cardBack: {
|
cardBack: {
|
||||||
backgroundColor: '#152d52',
|
backgroundColor: '#152d52',
|
||||||
@@ -59,21 +118,17 @@ const styles = StyleSheet.create({
|
|||||||
justifyContent: 'center'
|
justifyContent: 'center'
|
||||||
},
|
},
|
||||||
backPattern: {
|
backPattern: {
|
||||||
width: 32,
|
|
||||||
height: 46,
|
|
||||||
borderRadius: 6,
|
|
||||||
borderWidth: 2,
|
|
||||||
borderColor: 'rgba(255,255,255,0.4)'
|
borderColor: 'rgba(255,255,255,0.4)'
|
||||||
},
|
},
|
||||||
corner: {
|
corner: {
|
||||||
fontSize: 12,
|
|
||||||
fontFamily: fonts.body,
|
fontFamily: fonts.body,
|
||||||
fontWeight: '700'
|
fontWeight: '700',
|
||||||
|
includeFontPadding: false
|
||||||
},
|
},
|
||||||
center: {
|
center: {
|
||||||
fontSize: 24,
|
|
||||||
fontFamily: fonts.display,
|
fontFamily: fonts.display,
|
||||||
textAlign: 'center'
|
textAlign: 'center',
|
||||||
|
includeFontPadding: false
|
||||||
},
|
},
|
||||||
cornerBottom: {
|
cornerBottom: {
|
||||||
transform: [{ rotate: '180deg' }]
|
transform: [{ rotate: '180deg' }]
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ const styles = StyleSheet.create({
|
|||||||
borderColor: 'rgba(255,255,255,0.2)'
|
borderColor: 'rgba(255,255,255,0.2)'
|
||||||
},
|
},
|
||||||
buttonSmall: {
|
buttonSmall: {
|
||||||
paddingVertical: 8,
|
paddingVertical: 9,
|
||||||
paddingHorizontal: 16
|
paddingHorizontal: 18
|
||||||
},
|
},
|
||||||
inner: {
|
inner: {
|
||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
@@ -56,7 +56,7 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
textSmall: {
|
textSmall: {
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
letterSpacing: 0.8
|
letterSpacing: 0.7
|
||||||
},
|
},
|
||||||
disabled: {
|
disabled: {
|
||||||
opacity: 0.5
|
opacity: 0.5
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export default function Chip({ label, color = 'blue', size = 'default' }) {
|
|||||||
const isSmall = size === 'small';
|
const isSmall = size === 'small';
|
||||||
return (
|
return (
|
||||||
<View style={[styles.chip, isSmall && styles.chipSmall, { backgroundColor: chipColors[color] || colors.chipBlue }]}>
|
<View style={[styles.chip, isSmall && styles.chipSmall, { backgroundColor: chipColors[color] || colors.chipBlue }]}>
|
||||||
<View style={styles.inner}>
|
<View style={[styles.inner, isSmall && styles.innerSmall]}>
|
||||||
<Text style={[styles.text, isSmall && styles.textSmall]}>{label}</Text>
|
<Text style={[styles.text, isSmall && styles.textSmall]}>{label}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -20,9 +20,9 @@ export default function Chip({ label, color = 'blue', size = 'default' }) {
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
chip: {
|
chip: {
|
||||||
width: 48,
|
width: 56,
|
||||||
height: 48,
|
height: 56,
|
||||||
borderRadius: 24,
|
borderRadius: 28,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
borderWidth: 3,
|
borderWidth: 3,
|
||||||
@@ -34,21 +34,27 @@ const styles = StyleSheet.create({
|
|||||||
borderRadius: 20
|
borderRadius: 20
|
||||||
},
|
},
|
||||||
inner: {
|
inner: {
|
||||||
width: 28,
|
width: 32,
|
||||||
height: 28,
|
height: 32,
|
||||||
borderRadius: 14,
|
borderRadius: 16,
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
borderColor: 'rgba(255,255,255,0.7)',
|
borderColor: 'rgba(255,255,255,0.7)',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center'
|
justifyContent: 'center'
|
||||||
},
|
},
|
||||||
|
innerSmall: {
|
||||||
|
width: 22,
|
||||||
|
height: 22,
|
||||||
|
borderRadius: 11,
|
||||||
|
borderWidth: 1.5
|
||||||
|
},
|
||||||
text: {
|
text: {
|
||||||
color: '#f7f2e6',
|
color: '#f7f2e6',
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
fontSize: 12,
|
fontSize: 13,
|
||||||
fontFamily: fonts.mono
|
fontFamily: fonts.mono
|
||||||
},
|
},
|
||||||
textSmall: {
|
textSmall: {
|
||||||
fontSize: 11
|
fontSize: 10
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,13 +2,20 @@ import { StyleSheet, Text, View } from 'react-native';
|
|||||||
import Card from './Card';
|
import Card from './Card';
|
||||||
import { colors, fonts } from '../theme';
|
import { colors, fonts } from '../theme';
|
||||||
|
|
||||||
export default function DealerArea({ hand }) {
|
export default function DealerArea({ hand, cardSize = 'small', delayBase = 0 }) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.wrapper}>
|
<View style={styles.wrapper}>
|
||||||
<Text style={styles.label}>Osztó</Text>
|
<Text style={styles.label}>Osztó</Text>
|
||||||
<View style={styles.hand}>
|
<View style={styles.hand}>
|
||||||
{hand.map((card, idx) => (
|
{hand.map((card, idx) => (
|
||||||
<Card key={`${card.rank}-${card.suit}-${idx}`} rank={card.rank} suit={card.suit} hidden={card.hidden} />
|
<Card
|
||||||
|
key={`${card.rank}-${card.suit}-${idx}`}
|
||||||
|
rank={card.rank}
|
||||||
|
suit={card.suit}
|
||||||
|
hidden={card.hidden}
|
||||||
|
size={cardSize}
|
||||||
|
delay={delayBase + idx * 120}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -23,7 +30,7 @@ const styles = StyleSheet.create({
|
|||||||
color: colors.goldBright,
|
color: colors.goldBright,
|
||||||
letterSpacing: 2,
|
letterSpacing: 2,
|
||||||
textTransform: 'uppercase',
|
textTransform: 'uppercase',
|
||||||
fontSize: 12,
|
fontSize: 13,
|
||||||
fontFamily: fonts.body
|
fontFamily: fonts.body
|
||||||
},
|
},
|
||||||
hand: {
|
hand: {
|
||||||
|
|||||||
@@ -5,12 +5,46 @@ import { BlurView } from 'expo-blur';
|
|||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import { WS_URL } from '../api';
|
import { WS_URL } from '../api';
|
||||||
import { colors, fonts } from '../theme';
|
import { colors, fonts } from '../theme';
|
||||||
|
import Card from '../components/Card';
|
||||||
import CasinoButton from '../components/CasinoButton';
|
import CasinoButton from '../components/CasinoButton';
|
||||||
import Chip from '../components/Chip';
|
import Chip from '../components/Chip';
|
||||||
import DealerArea from '../components/DealerArea';
|
import DealerArea from '../components/DealerArea';
|
||||||
import Seat from '../components/Seat';
|
|
||||||
import TableBackground from '../components/TableBackground';
|
const emptySeat = (index) => ({
|
||||||
import TableMarkings from '../components/TableMarkings';
|
index,
|
||||||
|
userId: null,
|
||||||
|
username: '',
|
||||||
|
bet: 0,
|
||||||
|
hand: [],
|
||||||
|
result: null,
|
||||||
|
isYou: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const getHandTotal = (hand) => {
|
||||||
|
if (!hand || hand.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let total = 0;
|
||||||
|
let aces = 0;
|
||||||
|
for (const card of hand) {
|
||||||
|
if (!card || card.rank === 'X') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (card.rank === 'A') {
|
||||||
|
total += 11;
|
||||||
|
aces += 1;
|
||||||
|
} else if (['J', 'Q', 'K'].includes(card.rank)) {
|
||||||
|
total += 10;
|
||||||
|
} else {
|
||||||
|
total += Number(card.rank);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (total > 21 && aces > 0) {
|
||||||
|
total -= 10;
|
||||||
|
aces -= 1;
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
};
|
||||||
|
|
||||||
export default function TableScreen({ token, tableId, user, onLeave }) {
|
export default function TableScreen({ token, tableId, user, onLeave }) {
|
||||||
const [table, setTable] = useState(null);
|
const [table, setTable] = useState(null);
|
||||||
@@ -18,9 +52,14 @@ export default function TableScreen({ token, tableId, user, onLeave }) {
|
|||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
const [betAmount, setBetAmount] = useState(10);
|
const [betAmount, setBetAmount] = useState(10);
|
||||||
const [turnSeconds, setTurnSeconds] = useState(null);
|
const [turnSeconds, setTurnSeconds] = useState(null);
|
||||||
|
const [roundSeconds, setRoundSeconds] = useState(null);
|
||||||
const [controlsVisible, setControlsVisible] = useState(true);
|
const [controlsVisible, setControlsVisible] = useState(true);
|
||||||
|
const [seatLayout, setSeatLayout] = useState(null);
|
||||||
|
const [winOverlay, setWinOverlay] = useState(null);
|
||||||
|
const [confettiKey, setConfettiKey] = useState(0);
|
||||||
const wsRef = useRef(null);
|
const wsRef = useRef(null);
|
||||||
const hideTimerRef = useRef(null);
|
const hideTimerRef = useRef(null);
|
||||||
|
const lastWinRoundRef = useRef(null);
|
||||||
const pulse = useRef(new Animated.Value(0)).current;
|
const pulse = useRef(new Animated.Value(0)).current;
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const { width, height } = useWindowDimensions();
|
const { width, height } = useWindowDimensions();
|
||||||
@@ -70,6 +109,8 @@ export default function TableScreen({ token, tableId, user, onLeave }) {
|
|||||||
const mySeat = useMemo(() => table?.seats?.find((seat) => seat.isYou), [table]);
|
const mySeat = useMemo(() => table?.seats?.find((seat) => seat.isYou), [table]);
|
||||||
const isMyTurn = table?.phase === 'playing' && table?.currentSeatIndex === mySeat?.index;
|
const isMyTurn = table?.phase === 'playing' && table?.currentSeatIndex === mySeat?.index;
|
||||||
const bettingLocked = ['playing', 'dealer', 'payout'].includes(table?.phase);
|
const bettingLocked = ['playing', 'dealer', 'payout'].includes(table?.phase);
|
||||||
|
const showBetControls = !bettingLocked;
|
||||||
|
const showActionControls = table?.phase === 'playing';
|
||||||
|
|
||||||
const scheduleHide = useCallback(() => {
|
const scheduleHide = useCallback(() => {
|
||||||
if (hideTimerRef.current) {
|
if (hideTimerRef.current) {
|
||||||
@@ -113,6 +154,22 @@ export default function TableScreen({ token, tableId, user, onLeave }) {
|
|||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [table?.turnEndsAt, isMyTurn]);
|
}, [table?.turnEndsAt, isMyTurn]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!table?.roundStartsAt || table?.phase !== 'betting') {
|
||||||
|
setRoundSeconds(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const update = () => {
|
||||||
|
const msLeft = Math.max(0, table.roundStartsAt - Date.now());
|
||||||
|
setRoundSeconds(Math.ceil(msLeft / 1000));
|
||||||
|
};
|
||||||
|
|
||||||
|
update();
|
||||||
|
const interval = setInterval(update, 250);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [table?.roundStartsAt, table?.phase]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isMyTurn) {
|
if (!isMyTurn) {
|
||||||
pulse.stopAnimation();
|
pulse.stopAnimation();
|
||||||
@@ -128,6 +185,24 @@ export default function TableScreen({ token, tableId, user, onLeave }) {
|
|||||||
).start();
|
).start();
|
||||||
}, [isMyTurn, pulse]);
|
}, [isMyTurn, pulse]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (table?.phase !== 'payout' || !mySeat?.result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastWinRoundRef.current === table.roundId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['win', 'blackjack'].includes(mySeat.result.outcome)) {
|
||||||
|
lastWinRoundRef.current = table.roundId;
|
||||||
|
setWinOverlay({ payout: mySeat.result.payout, outcome: mySeat.result.outcome });
|
||||||
|
setConfettiKey((prev) => prev + 1);
|
||||||
|
const timer = setTimeout(() => setWinOverlay(null), 2600);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}, [table?.phase, table?.roundId, mySeat?.result]);
|
||||||
|
|
||||||
const pulseStyle = {
|
const pulseStyle = {
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
@@ -159,29 +234,65 @@ export default function TableScreen({ token, tableId, user, onLeave }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const seats = table?.seats || [];
|
const seats = table?.seats || [];
|
||||||
|
const activePlayers = seats.filter((seat) => seat.userId).length;
|
||||||
|
const showRoundTimer = roundSeconds !== null && table?.phase === 'betting' && activePlayers > 1;
|
||||||
|
|
||||||
|
const uiScale = useMemo(() => {
|
||||||
|
const base = isPortrait ? Math.min(width / 390, height / 844) : Math.min(width / 844, height / 390);
|
||||||
|
return Math.max(0.88, Math.min(base, 1.15));
|
||||||
|
}, [width, height, isPortrait]);
|
||||||
|
|
||||||
|
const seatMetrics = useMemo(() => {
|
||||||
|
if (!seatLayout) {
|
||||||
|
return {
|
||||||
|
width: Math.round(150 * uiScale),
|
||||||
|
height: Math.round(110 * uiScale)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const baseWidth = seatLayout.width * (isPortrait ? 0.24 : 0.18);
|
||||||
|
const clamped = Math.max(120, Math.min(baseWidth, 190));
|
||||||
|
const widthValue = Math.round(clamped * uiScale);
|
||||||
|
return {
|
||||||
|
width: widthValue,
|
||||||
|
height: Math.round(widthValue * 0.78)
|
||||||
|
};
|
||||||
|
}, [seatLayout, isPortrait, uiScale]);
|
||||||
|
|
||||||
|
const seatAnchors = useMemo(() => {
|
||||||
if (isPortrait) {
|
if (isPortrait) {
|
||||||
return (
|
return [
|
||||||
<LinearGradient
|
{ x: 0.12, y: 0.32 },
|
||||||
colors={[colors.backgroundTop, colors.backgroundBottom]}
|
{ x: 0.88, y: 0.32 },
|
||||||
style={[
|
{ x: 0.12, y: 0.58 },
|
||||||
styles.container,
|
{ x: 0.88, y: 0.58 }
|
||||||
{
|
];
|
||||||
paddingTop: insets.top + 12,
|
|
||||||
paddingBottom: insets.bottom + 12
|
|
||||||
}
|
}
|
||||||
]}
|
return [
|
||||||
>
|
{ x: 0.2, y: 0.26 },
|
||||||
<View style={styles.rotateWrap}>
|
{ x: 0.8, y: 0.26 },
|
||||||
<Text style={styles.rotateTitle}>Fordítsd el a telefont</Text>
|
{ x: 0.2, y: 0.64 },
|
||||||
<Text style={styles.rotateSubtitle}>A blackjack asztal fekvő nézetben működik jól.</Text>
|
{ x: 0.8, y: 0.64 }
|
||||||
<View style={styles.rotateActions}>
|
];
|
||||||
<CasinoButton label="Vissza" onPress={onLeave} variant="red" />
|
}, [isPortrait]);
|
||||||
</View>
|
|
||||||
</View>
|
const seatSlots = useMemo(() => {
|
||||||
</LinearGradient>
|
return seatAnchors.map((_, idx) => seats[idx] ?? emptySeat(idx));
|
||||||
);
|
}, [seatAnchors, seats]);
|
||||||
|
|
||||||
|
const seatPositions = useMemo(() => {
|
||||||
|
if (!seatLayout) {
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
return seatAnchors.map((anchor) => ({
|
||||||
|
left: seatLayout.width * anchor.x - seatMetrics.width / 2,
|
||||||
|
top: seatLayout.height * anchor.y - seatMetrics.height / 2
|
||||||
|
}));
|
||||||
|
}, [seatLayout, seatAnchors, seatMetrics]);
|
||||||
|
|
||||||
|
const seatCardSize = uiScale > 1.02 ? 'normal' : 'small';
|
||||||
|
const playerCardSize = uiScale > 1 ? 'large' : 'normal';
|
||||||
|
const myTotal = getHandTotal(mySeat?.hand);
|
||||||
|
const dealerDelayBase = seatSlots.length * 140;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
@@ -189,47 +300,111 @@ export default function TableScreen({ token, tableId, user, onLeave }) {
|
|||||||
style={[
|
style={[
|
||||||
styles.container,
|
styles.container,
|
||||||
{
|
{
|
||||||
paddingTop: insets.top + 12,
|
paddingTop: insets.top + 10,
|
||||||
paddingBottom: insets.bottom + 12
|
paddingBottom: insets.bottom + 10
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<View style={styles.topBar}>
|
<View style={styles.topBar}>
|
||||||
<View>
|
<View>
|
||||||
<Text style={styles.tableTitle}>Asztal {tableId}</Text>
|
<Text style={styles.tableTitle}>Asztal {tableId}</Text>
|
||||||
<Text style={styles.balance}>Egyenleg: {balance} Ft</Text>
|
<Text style={styles.balance}>{balance} Ft</Text>
|
||||||
</View>
|
</View>
|
||||||
<CasinoButton label="Kilépek" onPress={onLeave} variant="red" />
|
<CasinoButton label="Kilép" onPress={onLeave} variant="red" size="small" />
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.tableWrap}>
|
<View style={styles.tableWrap}>
|
||||||
<TableBackground style={styles.tableSurface} showRing={false}>
|
<View style={styles.tableSurface} onTouchStart={showControls}>
|
||||||
<View style={styles.tableInner}>
|
<LinearGradient colors={[colors.tableFeltDark, colors.tableFelt]} style={styles.tableGradient}>
|
||||||
<TableMarkings />
|
<View style={styles.tableGlow} />
|
||||||
<View style={styles.tableContent}>
|
<View style={[styles.tableArc, styles.tableArcTop, { top: 20 * uiScale, height: 200 * uiScale }]} />
|
||||||
|
<View style={[styles.tableArc, styles.tableArcMid, { top: 140 * uiScale, height: 240 * uiScale }]} />
|
||||||
|
<View style={[styles.tableArc, styles.tableArcBottom, { bottom: 220 * uiScale, height: 260 * uiScale }]} />
|
||||||
|
|
||||||
|
<View style={[styles.tableContent, { paddingTop: 6 * uiScale, paddingBottom: 10 * uiScale }]}
|
||||||
|
>
|
||||||
<View style={styles.dealerArea}>
|
<View style={styles.dealerArea}>
|
||||||
<DealerArea hand={table?.dealerHand || []} />
|
<DealerArea hand={table?.dealerHand || []} cardSize={seatCardSize} delayBase={dealerDelayBase} />
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.seatLayer}>
|
|
||||||
{seats.map((seat) => (
|
<View style={styles.seatLayer} onLayout={(event) => setSeatLayout(event.nativeEvent.layout)}>
|
||||||
<View key={seat.index} style={[styles.seatSpot, seatPositions[seat.index]]}>
|
{seatSlots.map((seat, idx) => {
|
||||||
<Seat seat={seat} highlight={table.currentSeatIndex === seat.index} />
|
const pos = seatPositions[idx];
|
||||||
|
if (!pos) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const highlight = seat.userId && table?.currentSeatIndex === seat.index;
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
key={seat.index}
|
||||||
|
style={[
|
||||||
|
styles.seatSpot,
|
||||||
|
{
|
||||||
|
width: seatMetrics.width,
|
||||||
|
minHeight: seatMetrics.height
|
||||||
|
},
|
||||||
|
pos
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<PortraitSeat
|
||||||
|
seat={seat}
|
||||||
|
highlight={highlight}
|
||||||
|
cardSize={seatCardSize}
|
||||||
|
scale={uiScale}
|
||||||
|
dealIndex={idx}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.playerArea}>
|
||||||
|
<View style={styles.playerHand}>
|
||||||
|
{(mySeat?.hand || []).map((card, idx) => (
|
||||||
|
<View key={`${card.rank}-${card.suit}-${idx}`} style={[styles.playerCard, { marginLeft: idx === 0 ? 0 : -12 * uiScale }]}
|
||||||
|
>
|
||||||
|
<Card
|
||||||
|
rank={card.rank}
|
||||||
|
suit={card.suit}
|
||||||
|
hidden={card.hidden}
|
||||||
|
size={playerCardSize}
|
||||||
|
delay={(mySeat?.index ?? 0) * 140 + idx * 120}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
{myTotal !== null ? (
|
||||||
|
<Text style={styles.playerTotal}>Összeg: {myTotal}</Text>
|
||||||
|
) : null}
|
||||||
|
{mySeat ? (
|
||||||
|
<Text style={styles.playerBet}>Tét: {mySeat.bet || betAmount} Ft</Text>
|
||||||
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{controlsVisible ? (
|
{controlsVisible ? (
|
||||||
<View style={styles.controlsOverlay}>
|
<View style={styles.controlsArea}>
|
||||||
<BlurView intensity={28} tint="dark" style={styles.blurPanel}>
|
{showRoundTimer ? (
|
||||||
|
<Text style={styles.roundTimer}>Kezdés: {roundSeconds} mp</Text>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{showBetControls ? (
|
||||||
|
<BlurView intensity={22} tint="dark" style={styles.controlsPanel}>
|
||||||
<View style={styles.betRow}>
|
<View style={styles.betRow}>
|
||||||
<Text style={styles.sectionLabel}>Tét</Text>
|
<Text style={styles.sectionLabel}>Tét</Text>
|
||||||
<View style={styles.betControls}>
|
<View style={styles.betControls}>
|
||||||
<Pressable onPress={() => adjustBet(-10)} style={[styles.betAdjust, bettingLocked && styles.betAdjustDisabled]} disabled={bettingLocked}>
|
<Pressable
|
||||||
|
onPress={() => adjustBet(-10)}
|
||||||
|
style={[styles.betAdjust, bettingLocked && styles.betAdjustDisabled]}
|
||||||
|
disabled={bettingLocked}
|
||||||
|
>
|
||||||
<Text style={styles.betAdjustText}>-</Text>
|
<Text style={styles.betAdjustText}>-</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
<Chip label={`${betAmount}`} color="red" size="small" />
|
<Chip label={`${betAmount}`} color="red" />
|
||||||
<Pressable onPress={() => adjustBet(10)} style={[styles.betAdjust, bettingLocked && styles.betAdjustDisabled]} disabled={bettingLocked}>
|
<Pressable
|
||||||
|
onPress={() => adjustBet(10)}
|
||||||
|
style={[styles.betAdjust, bettingLocked && styles.betAdjustDisabled]}
|
||||||
|
disabled={bettingLocked}
|
||||||
|
>
|
||||||
<Text style={styles.betAdjustText}>+</Text>
|
<Text style={styles.betAdjustText}>+</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
@@ -237,17 +412,23 @@ export default function TableScreen({ token, tableId, user, onLeave }) {
|
|||||||
<CasinoButton label="Kész" onPress={() => send({ type: 'ready' })} variant="green" size="small" disabled={bettingLocked} />
|
<CasinoButton label="Kész" onPress={() => send({ type: 'ready' })} variant="green" size="small" disabled={bettingLocked} />
|
||||||
</View>
|
</View>
|
||||||
</BlurView>
|
</BlurView>
|
||||||
|
|
||||||
<BlurView intensity={28} tint="dark" style={styles.blurPanel}>
|
|
||||||
{isMyTurn && turnSeconds !== null ? (
|
|
||||||
<Text style={styles.timer}>Idő: {turnSeconds} mp</Text>
|
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
{showActionControls ? (
|
||||||
|
<View style={styles.actionWrap}>
|
||||||
|
<View style={[styles.actionHint, isMyTurn && styles.actionHintActive]}>
|
||||||
|
<Text style={styles.actionHintText}>{isMyTurn ? 'HIT' : 'Várakozás'}</Text>
|
||||||
|
{isMyTurn && turnSeconds !== null ? (
|
||||||
|
<Text style={styles.turnTimer}>{turnSeconds} mp</Text>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
<Animated.View style={[styles.actionRow, isMyTurn && pulseStyle]}>
|
<Animated.View style={[styles.actionRow, isMyTurn && pulseStyle]}>
|
||||||
<CasinoButton label="Hit" onPress={() => send({ type: 'action', action: 'hit' })} variant="gold" size="small" disabled={!isMyTurn} />
|
<CasinoButton label="Hit" onPress={() => send({ type: 'action', action: 'hit' })} variant="gold" size="small" disabled={!isMyTurn} />
|
||||||
<CasinoButton label="Stand" onPress={() => send({ type: 'action', action: 'stand' })} variant="gold" size="small" disabled={!isMyTurn} />
|
<CasinoButton label="Stand" onPress={() => send({ type: 'action', action: 'stand' })} variant="gold" size="small" disabled={!isMyTurn} />
|
||||||
<CasinoButton label="Double" onPress={() => send({ type: 'action', action: 'double' })} variant="gold" size="small" disabled={!isMyTurn} />
|
<CasinoButton label="Double" onPress={() => send({ type: 'action', action: 'double' })} variant="gold" size="small" disabled={!isMyTurn} />
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</BlurView>
|
</View>
|
||||||
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<Pressable style={styles.controlsReveal} onPress={showControls} />
|
<Pressable style={styles.controlsReveal} onPress={showControls} />
|
||||||
@@ -255,9 +436,144 @@ export default function TableScreen({ token, tableId, user, onLeave }) {
|
|||||||
|
|
||||||
{message ? <Text style={styles.message}>{message}</Text> : null}
|
{message ? <Text style={styles.message}>{message}</Text> : null}
|
||||||
</View>
|
</View>
|
||||||
</TableBackground>
|
|
||||||
</View>
|
|
||||||
</LinearGradient>
|
</LinearGradient>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{winOverlay ? (
|
||||||
|
<View pointerEvents="none" style={styles.winOverlay}>
|
||||||
|
<ConfettiBurst trigger={confettiKey} width={width} height={height} />
|
||||||
|
<View style={styles.winCard}>
|
||||||
|
<Text style={styles.winTitle}>Nyertél!</Text>
|
||||||
|
<Text style={styles.winAmount}>+{winOverlay.payout} Ft</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
</LinearGradient>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PortraitSeat({ seat, highlight, cardSize, scale, dealIndex }) {
|
||||||
|
const isEmpty = !seat.userId;
|
||||||
|
const hand = seat.hand || [];
|
||||||
|
const total = getHandTotal(hand);
|
||||||
|
const label = seat.userId ? (seat.isYou ? 'ÉN' : seat.username || 'Játékos') : 'Üres hely';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[styles.seatCard, highlight && styles.seatCardActive]}>
|
||||||
|
{highlight ? <View style={styles.seatGlow} /> : null}
|
||||||
|
<View style={styles.avatarWrap}>
|
||||||
|
<View style={styles.avatarRing}>
|
||||||
|
<View style={styles.avatarHead} />
|
||||||
|
<View style={styles.avatarBody} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<Text style={[styles.seatName, seat.isYou && styles.seatNameYou]} numberOfLines={1}>
|
||||||
|
{label}
|
||||||
|
</Text>
|
||||||
|
{hand.length > 0 ? (
|
||||||
|
<View style={styles.seatHand}>
|
||||||
|
{hand.map((card, idx) => (
|
||||||
|
<View
|
||||||
|
key={`${card.rank}-${card.suit}-${idx}`}
|
||||||
|
style={[
|
||||||
|
styles.seatCardStack,
|
||||||
|
{
|
||||||
|
marginLeft: idx === 0 ? 0 : -14 * scale,
|
||||||
|
transform: [{ rotate: `${idx === 0 ? 0 : idx * -3}deg` }]
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Card
|
||||||
|
rank={card.rank}
|
||||||
|
suit={card.suit}
|
||||||
|
hidden={card.hidden}
|
||||||
|
size={cardSize}
|
||||||
|
delay={dealIndex * 140 + idx * 120}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
{total !== null ? <Text style={styles.seatTotal}>Összeg: {total}</Text> : null}
|
||||||
|
<View style={[styles.betPill, isEmpty && styles.betPillEmpty]}>
|
||||||
|
<Text style={styles.betPillText}>{seat.bet > 0 ? `${seat.bet} Ft` : 'TÉT'}</Text>
|
||||||
|
</View>
|
||||||
|
{seat.result?.outcome === 'blackjack' ? (
|
||||||
|
<View style={styles.blackjackTag}>
|
||||||
|
<Text style={styles.blackjackText}>BLACKJACK!</Text>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ConfettiBurst({ trigger, width, height }) {
|
||||||
|
const pieces = useMemo(() => {
|
||||||
|
const palette = ['#f7d488', '#d94a3d', '#39c377', '#2f7dd3', '#f2f1e8'];
|
||||||
|
return Array.from({ length: 22 }, (_, idx) => {
|
||||||
|
return {
|
||||||
|
key: `${trigger}-${idx}`,
|
||||||
|
x: Math.random() * Math.max(0, width - 24),
|
||||||
|
delay: idx * 70,
|
||||||
|
rotate: Math.random() * 180,
|
||||||
|
drift: (Math.random() - 0.5) * 80,
|
||||||
|
size: 6 + Math.random() * 6,
|
||||||
|
color: palette[idx % palette.length],
|
||||||
|
anim: new Animated.Value(0)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [trigger, width]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const animations = pieces.map((piece) =>
|
||||||
|
Animated.timing(piece.anim, {
|
||||||
|
toValue: 1,
|
||||||
|
duration: 1600,
|
||||||
|
delay: piece.delay,
|
||||||
|
useNativeDriver: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
Animated.parallel(animations).start();
|
||||||
|
}, [pieces]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View pointerEvents="none" style={styles.confettiLayer}>
|
||||||
|
{pieces.map((piece) => (
|
||||||
|
<Animated.View
|
||||||
|
key={piece.key}
|
||||||
|
style={[
|
||||||
|
styles.confettiPiece,
|
||||||
|
{
|
||||||
|
backgroundColor: piece.color,
|
||||||
|
width: piece.size,
|
||||||
|
height: piece.size * 1.6,
|
||||||
|
left: piece.x,
|
||||||
|
transform: [
|
||||||
|
{
|
||||||
|
translateY: piece.anim.interpolate({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: [-20, height + 20]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
translateX: piece.anim.interpolate({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: [0, piece.drift]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rotate: piece.anim.interpolate({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: [`${piece.rotate}deg`, `${piece.rotate + 120}deg`]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,64 +590,215 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
tableTitle: {
|
tableTitle: {
|
||||||
color: colors.goldBright,
|
color: colors.goldBright,
|
||||||
fontSize: 20,
|
fontSize: 22,
|
||||||
fontFamily: fonts.display,
|
fontFamily: fonts.display,
|
||||||
letterSpacing: 2
|
letterSpacing: 1
|
||||||
},
|
},
|
||||||
balance: {
|
balance: {
|
||||||
color: colors.muted,
|
color: colors.muted,
|
||||||
marginTop: 4,
|
marginTop: 2,
|
||||||
fontFamily: fonts.mono
|
fontFamily: fonts.mono
|
||||||
},
|
},
|
||||||
tableWrap: {
|
tableWrap: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: 'flex-start',
|
marginTop: 10,
|
||||||
alignItems: 'center',
|
|
||||||
marginTop: 8,
|
|
||||||
marginBottom: 8,
|
marginBottom: 8,
|
||||||
width: '100%'
|
width: '100%'
|
||||||
},
|
},
|
||||||
tableSurface: {
|
tableSurface: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
padding: 0
|
borderRadius: 26,
|
||||||
|
overflow: 'hidden'
|
||||||
},
|
},
|
||||||
tableInner: {
|
tableGradient: {
|
||||||
flex: 1,
|
flex: 1
|
||||||
position: 'relative'
|
|
||||||
},
|
},
|
||||||
dealerArea: {
|
tableGlow: {
|
||||||
alignItems: 'center',
|
...StyleSheet.absoluteFillObject,
|
||||||
paddingTop: 8
|
backgroundColor: 'rgba(255,255,255,0.02)'
|
||||||
|
},
|
||||||
|
tableArc: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: '10%',
|
||||||
|
right: '10%',
|
||||||
|
borderRadius: 999,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(255,255,255,0.12)'
|
||||||
|
},
|
||||||
|
tableArcTop: {
|
||||||
|
top: 26
|
||||||
|
},
|
||||||
|
tableArcMid: {
|
||||||
|
top: 130,
|
||||||
|
borderColor: 'rgba(255,255,255,0.08)'
|
||||||
|
},
|
||||||
|
tableArcBottom: {
|
||||||
|
bottom: 200,
|
||||||
|
borderColor: 'rgba(255,255,255,0.1)'
|
||||||
},
|
},
|
||||||
tableContent: {
|
tableContent: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: 'space-between',
|
paddingHorizontal: 16
|
||||||
paddingBottom: 140
|
},
|
||||||
|
dealerArea: {
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 6
|
||||||
},
|
},
|
||||||
seatLayer: {
|
seatLayer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
position: 'relative'
|
position: 'relative',
|
||||||
|
marginTop: 6,
|
||||||
|
marginBottom: 6
|
||||||
},
|
},
|
||||||
seatSpot: {
|
seatSpot: {
|
||||||
|
position: 'absolute'
|
||||||
|
},
|
||||||
|
seatCard: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-start'
|
||||||
|
},
|
||||||
|
seatCardActive: {
|
||||||
|
shadowColor: colors.goldBright,
|
||||||
|
shadowOpacity: 0.6,
|
||||||
|
shadowRadius: 12
|
||||||
|
},
|
||||||
|
seatGlow: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
width: 90,
|
top: -8,
|
||||||
transform: [{ translateX: -45 }]
|
left: -8,
|
||||||
|
right: -8,
|
||||||
|
bottom: -8,
|
||||||
|
borderRadius: 999,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(255,214,138,0.6)'
|
||||||
},
|
},
|
||||||
controlsOverlay: {
|
avatarWrap: {
|
||||||
position: 'absolute',
|
marginTop: 2,
|
||||||
left: 20,
|
marginBottom: 4
|
||||||
right: 20,
|
|
||||||
bottom: 10,
|
|
||||||
gap: 10
|
|
||||||
},
|
},
|
||||||
controlsReveal: {
|
avatarRing: {
|
||||||
...StyleSheet.absoluteFillObject
|
width: 38,
|
||||||
|
height: 38,
|
||||||
|
borderRadius: 19,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(255,255,255,0.35)',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.25)'
|
||||||
},
|
},
|
||||||
blurPanel: {
|
avatarHead: {
|
||||||
borderRadius: 18,
|
width: 10,
|
||||||
paddingVertical: 8,
|
height: 10,
|
||||||
|
borderRadius: 5,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(255,255,255,0.3)',
|
||||||
|
marginBottom: 2
|
||||||
|
},
|
||||||
|
avatarBody: {
|
||||||
|
width: 18,
|
||||||
|
height: 10,
|
||||||
|
borderRadius: 6,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(255,255,255,0.25)'
|
||||||
|
},
|
||||||
|
seatName: {
|
||||||
|
color: colors.text,
|
||||||
|
fontFamily: fonts.body,
|
||||||
|
fontSize: 12,
|
||||||
|
marginBottom: 2
|
||||||
|
},
|
||||||
|
seatNameYou: {
|
||||||
|
fontWeight: '800',
|
||||||
|
color: colors.goldBright
|
||||||
|
},
|
||||||
|
seatHand: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 2
|
||||||
|
},
|
||||||
|
seatCardStack: {
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
seatTotal: {
|
||||||
|
marginTop: 3,
|
||||||
|
color: colors.muted,
|
||||||
|
fontSize: 11,
|
||||||
|
fontFamily: fonts.mono
|
||||||
|
},
|
||||||
|
betPill: {
|
||||||
|
marginTop: 4,
|
||||||
paddingHorizontal: 10,
|
paddingHorizontal: 10,
|
||||||
|
paddingVertical: 4,
|
||||||
|
borderRadius: 10,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.goldBright,
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.25)'
|
||||||
|
},
|
||||||
|
betPillEmpty: {
|
||||||
|
borderColor: 'rgba(255,255,255,0.18)'
|
||||||
|
},
|
||||||
|
betPillText: {
|
||||||
|
color: colors.goldBright,
|
||||||
|
fontFamily: fonts.mono,
|
||||||
|
fontSize: 12
|
||||||
|
},
|
||||||
|
blackjackTag: {
|
||||||
|
marginTop: 4,
|
||||||
|
paddingHorizontal: 8,
|
||||||
|
paddingVertical: 2,
|
||||||
|
borderRadius: 8,
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.45)',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.gold
|
||||||
|
},
|
||||||
|
blackjackText: {
|
||||||
|
color: colors.goldBright,
|
||||||
|
fontSize: 10,
|
||||||
|
fontFamily: fonts.body
|
||||||
|
},
|
||||||
|
playerArea: {
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 4,
|
||||||
|
marginBottom: 4
|
||||||
|
},
|
||||||
|
playerHand: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center'
|
||||||
|
},
|
||||||
|
playerCard: {
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
playerTotal: {
|
||||||
|
marginTop: 6,
|
||||||
|
color: colors.muted,
|
||||||
|
fontFamily: fonts.mono,
|
||||||
|
fontSize: 12
|
||||||
|
},
|
||||||
|
playerBet: {
|
||||||
|
marginTop: 2,
|
||||||
|
color: colors.goldBright,
|
||||||
|
fontFamily: fonts.body,
|
||||||
|
fontSize: 14
|
||||||
|
},
|
||||||
|
controlsArea: {
|
||||||
|
marginTop: 4,
|
||||||
|
gap: 12,
|
||||||
|
paddingBottom: 6
|
||||||
|
},
|
||||||
|
roundTimer: {
|
||||||
|
textAlign: 'center',
|
||||||
|
color: colors.goldBright,
|
||||||
|
fontFamily: fonts.mono,
|
||||||
|
fontSize: 12
|
||||||
|
},
|
||||||
|
controlsPanel: {
|
||||||
|
borderRadius: 18,
|
||||||
|
paddingVertical: 10,
|
||||||
|
paddingHorizontal: 12,
|
||||||
backgroundColor: 'rgba(10, 18, 14, 0.45)',
|
backgroundColor: 'rgba(10, 18, 14, 0.45)',
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: 'rgba(255,255,255,0.12)',
|
borderColor: 'rgba(255,255,255,0.12)',
|
||||||
@@ -341,17 +808,18 @@ const styles = StyleSheet.create({
|
|||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
gap: 8
|
gap: 10,
|
||||||
|
flexWrap: 'wrap'
|
||||||
},
|
},
|
||||||
betControls: {
|
betControls: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 6
|
gap: 8
|
||||||
},
|
},
|
||||||
betAdjust: {
|
betAdjust: {
|
||||||
width: 28,
|
width: 32,
|
||||||
height: 28,
|
height: 32,
|
||||||
borderRadius: 14,
|
borderRadius: 16,
|
||||||
backgroundColor: 'rgba(255,255,255,0.12)',
|
backgroundColor: 'rgba(255,255,255,0.12)',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center'
|
justifyContent: 'center'
|
||||||
@@ -364,6 +832,38 @@ const styles = StyleSheet.create({
|
|||||||
betAdjustDisabled: {
|
betAdjustDisabled: {
|
||||||
opacity: 0.4
|
opacity: 0.4
|
||||||
},
|
},
|
||||||
|
actionWrap: {
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 10
|
||||||
|
},
|
||||||
|
actionHint: {
|
||||||
|
minWidth: '80%',
|
||||||
|
borderRadius: 18,
|
||||||
|
paddingVertical: 10,
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(255,255,255,0.2)',
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.25)',
|
||||||
|
alignItems: 'center'
|
||||||
|
},
|
||||||
|
actionHintActive: {
|
||||||
|
borderColor: colors.goldBright,
|
||||||
|
shadowColor: colors.goldBright,
|
||||||
|
shadowOpacity: 0.4,
|
||||||
|
shadowRadius: 10
|
||||||
|
},
|
||||||
|
actionHintText: {
|
||||||
|
color: colors.goldBright,
|
||||||
|
fontFamily: fonts.display,
|
||||||
|
fontSize: 16,
|
||||||
|
letterSpacing: 2
|
||||||
|
},
|
||||||
|
turnTimer: {
|
||||||
|
marginTop: 4,
|
||||||
|
color: colors.muted,
|
||||||
|
fontFamily: fonts.mono,
|
||||||
|
fontSize: 12
|
||||||
|
},
|
||||||
actionRow: {
|
actionRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
@@ -377,46 +877,45 @@ const styles = StyleSheet.create({
|
|||||||
message: {
|
message: {
|
||||||
color: colors.goldBright,
|
color: colors.goldBright,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
marginTop: 8,
|
marginTop: 4,
|
||||||
fontFamily: fonts.body
|
fontFamily: fonts.body
|
||||||
},
|
},
|
||||||
timer: {
|
controlsReveal: {
|
||||||
color: colors.goldBright,
|
...StyleSheet.absoluteFillObject
|
||||||
textAlign: 'center',
|
|
||||||
marginBottom: 4,
|
|
||||||
fontFamily: fonts.mono
|
|
||||||
},
|
},
|
||||||
rotateWrap: {
|
winOverlay: {
|
||||||
flex: 1,
|
...StyleSheet.absoluteFillObject,
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
paddingHorizontal: 24
|
alignItems: 'center'
|
||||||
},
|
},
|
||||||
rotateTitle: {
|
winCard: {
|
||||||
|
paddingHorizontal: 24,
|
||||||
|
paddingVertical: 16,
|
||||||
|
borderRadius: 18,
|
||||||
|
backgroundColor: 'rgba(8, 18, 12, 0.85)',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.goldBright
|
||||||
|
},
|
||||||
|
winTitle: {
|
||||||
color: colors.goldBright,
|
color: colors.goldBright,
|
||||||
fontFamily: fonts.display,
|
fontFamily: fonts.display,
|
||||||
fontSize: 24,
|
fontSize: 22,
|
||||||
letterSpacing: 2,
|
|
||||||
textAlign: 'center'
|
textAlign: 'center'
|
||||||
},
|
},
|
||||||
rotateSubtitle: {
|
winAmount: {
|
||||||
color: colors.muted,
|
marginTop: 6,
|
||||||
fontFamily: fonts.body,
|
color: colors.text,
|
||||||
fontSize: 14,
|
fontFamily: fonts.mono,
|
||||||
marginTop: 12,
|
fontSize: 18,
|
||||||
textAlign: 'center'
|
textAlign: 'center'
|
||||||
},
|
},
|
||||||
rotateActions: {
|
confettiLayer: {
|
||||||
marginTop: 20
|
...StyleSheet.absoluteFillObject
|
||||||
|
},
|
||||||
|
confettiPiece: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
borderRadius: 3,
|
||||||
|
opacity: 0.9
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const seatPositions = {
|
|
||||||
0: { left: '8%', top: '52%' },
|
|
||||||
1: { left: '22%', top: '45%' },
|
|
||||||
2: { left: '36%', top: '40%' },
|
|
||||||
3: { left: '50%', top: '38%' },
|
|
||||||
4: { left: '64%', top: '40%' },
|
|
||||||
5: { left: '78%', top: '45%' },
|
|
||||||
6: { left: '92%', top: '52%' }
|
|
||||||
};
|
|
||||||
|
|||||||
Reference in New Issue
Block a user