LotteryResultModal.tsx 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. import { AVPlaybackStatus, ResizeMode, Video } from 'expo-av';
  2. import { Image } from 'expo-image';
  3. import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
  4. import {
  5. ActivityIndicator,
  6. Animated,
  7. Dimensions,
  8. ImageBackground,
  9. Modal,
  10. ScrollView,
  11. StyleSheet,
  12. Text,
  13. TouchableOpacity,
  14. View
  15. } from 'react-native';
  16. import { useSafeAreaInsets } from 'react-native-safe-area-context';
  17. import { convertApply, getApplyResult } from '@/services/award';
  18. const { width: SCREEN_WIDTH } = Dimensions.get('window');
  19. const CARD_WIDTH = (SCREEN_WIDTH - 60) / 3;
  20. const CARD_HEIGHT = CARD_WIDTH * 1.5;
  21. const CDN_BASE = 'https://cdn.acetoys.cn';
  22. const imgUrl = `${CDN_BASE}/kai_xin_ma_te/supermart`;
  23. const imgUrlSupermart = `${CDN_BASE}/supermart`;
  24. const LEVEL_MAP: Record<string, { title: string; color: string; rgba: string; resultBg: string; borderImg: string }> = {
  25. A: {
  26. title: '超神款',
  27. color: '#F62C71',
  28. rgba: 'rgba(246, 44, 113, 1)',
  29. resultBg: `${imgUrlSupermart}/supermart/box/resultBgA.png`,
  30. borderImg: `${imgUrlSupermart}/supermart/box/borderImgA.png`
  31. },
  32. B: {
  33. title: '欧皇款',
  34. color: '#E9C525',
  35. rgba: 'rgba(233,197,37, 1)',
  36. resultBg: `${imgUrlSupermart}/supermart/box/resultBgB.png`,
  37. borderImg: `${imgUrlSupermart}/supermart/box/borderImgB.png`
  38. },
  39. C: {
  40. title: '隐藏款',
  41. color: '#A72CF6',
  42. rgba: 'rgba(167, 44, 246, 1)',
  43. resultBg: `${imgUrlSupermart}/supermart/box/resultBgC.png`,
  44. borderImg: `${imgUrlSupermart}/supermart/box/borderImgC.png`
  45. },
  46. D: {
  47. title: '普通款',
  48. color: '#40c9d7',
  49. rgba: 'rgba(64, 201, 215, 1)',
  50. resultBg: `${imgUrlSupermart}/supermart/box/resultBgD.png`,
  51. borderImg: `${imgUrlSupermart}/supermart/box/borderImgD.png`
  52. },
  53. };
  54. const LotteryImages = {
  55. lotteryBg: `${imgUrlSupermart}/supermart/box/sequence/sequence0.jpg`,
  56. cardBack: `${imgUrl}/box/back1.png`,
  57. halo: `${imgUrlSupermart}/supermart/box/halo.gif`,
  58. levelA_bg: `${imgUrlSupermart}/supermart/box/levelD.png`, // Using levelD as placeholder if specific not found, or specific bg
  59. levelA_title: `${imgUrlSupermart}/supermart/box/detail/levelTextA.png`,
  60. close: `${imgUrlSupermart}/supermart/box/qiji_close.png`,
  61. };
  62. const DEFAULT_JACKPOT_VIDEO = 'https://cdn.acetoys.cn/kai_xin_ma_te/supermart/box/lottery/jackpot.mp4';
  63. const KingModal = ({ visible, data, onClose }: { visible: boolean; data: LotteryItem | null; onClose: () => void }) => {
  64. if (!visible || !data) return null;
  65. return (
  66. <Modal visible={visible} transparent animationType="fade" onRequestClose={onClose}>
  67. <View style={styles.kingContainer}>
  68. <View style={styles.kingMask}>
  69. <TouchableOpacity style={StyleSheet.absoluteFill} onPress={onClose} />
  70. </View>
  71. <View style={styles.kingWrapper}>
  72. <Image
  73. source={{ uri: LEVEL_MAP[data.level]?.resultBg || LEVEL_MAP.A.resultBg }}
  74. style={styles.kingBg}
  75. contentFit="fill"
  76. />
  77. <Animated.Image
  78. source={{ uri: data.cover }}
  79. style={styles.kingProduct}
  80. resizeMode="contain"
  81. />
  82. <Image
  83. source={{ uri: LotteryImages.levelA_title }}
  84. style={styles.kingTitle}
  85. contentFit="contain"
  86. />
  87. </View>
  88. <TouchableOpacity style={styles.kingCloseBtn} onPress={onClose}>
  89. <Image source={{ uri: LotteryImages.close }} style={styles.kingCloseIcon} />
  90. </TouchableOpacity>
  91. </View>
  92. </Modal>
  93. );
  94. };
  95. interface LotteryItem {
  96. id: string;
  97. name: string;
  98. cover: string;
  99. level: string;
  100. magicAmount?: number;
  101. spu?: { marketPrice: number };
  102. }
  103. export interface LotteryResultModalRef {
  104. show: (tradeNo: string) => void;
  105. close: () => void;
  106. }
  107. interface LotteryResultModalProps {
  108. onClose?: () => void;
  109. onGoStore?: () => void;
  110. }
  111. export const LotteryResultModal = forwardRef<LotteryResultModalRef, LotteryResultModalProps>(
  112. ({ onClose, onGoStore }, ref) => {
  113. const insets = useSafeAreaInsets();
  114. const [visible, setVisible] = useState(false);
  115. const [loading, setLoading] = useState(true);
  116. const [tableData, setTableData] = useState<LotteryItem[]>([]);
  117. const [total, setTotal] = useState(0);
  118. const [showResult, setShowResult] = useState(false);
  119. const [showDh, setShowDh] = useState(false);
  120. const [haloShow, setHaloShow] = useState(false);
  121. const [rebateAmount, setRebateAmount] = useState(0);
  122. const [animationEnabled, setAnimationEnabled] = useState(true);
  123. const [isSkip, setIsSkip] = useState(true);
  124. const [tradeNo, setTradeNo] = useState('');
  125. const [kingVisible, setKingVisible] = useState(false);
  126. const [kingData, setKingData] = useState<LotteryItem | null>(null);
  127. const [videoVisible, setVideoVisible] = useState(false);
  128. const [videoUrl, setVideoUrl] = useState('');
  129. const flipAnims = useRef<Animated.Value[]>([]);
  130. const dataLoadedRef = useRef(false);
  131. useImperativeHandle(ref, () => ({
  132. show: (tNo: string) => {
  133. setTradeNo(tNo);
  134. setVisible(true);
  135. setLoading(true);
  136. setTableData([]);
  137. setTotal(0);
  138. setShowResult(false);
  139. setShowDh(false);
  140. setHaloShow(false);
  141. setRebateAmount(0);
  142. setIsSkip(true);
  143. setKingVisible(false);
  144. setKingData(null);
  145. setVideoVisible(false);
  146. setVideoUrl('');
  147. dataLoadedRef.current = false;
  148. flipAnims.current = [];
  149. },
  150. close: () => {
  151. setVisible(false);
  152. dataLoadedRef.current = false;
  153. },
  154. }));
  155. const flipCards = (data: LotteryItem[]) => {
  156. setIsSkip(false);
  157. setHaloShow(true);
  158. setTimeout(() => setHaloShow(false), 1000);
  159. const maxCards = Math.min(data.length, 9);
  160. const animations = flipAnims.current.slice(0, maxCards).map((anim, index) => {
  161. return Animated.sequence([
  162. Animated.delay(index * 200),
  163. Animated.timing(anim, { toValue: 1, duration: 200, useNativeDriver: true }),
  164. ]);
  165. });
  166. Animated.parallel(animations).start(() => {
  167. setTimeout(() => {
  168. setShowDh(data.every((item) => item.level !== 'B' && item.level !== 'A'));
  169. setShowResult(true);
  170. }, 900);
  171. });
  172. };
  173. useEffect(() => {
  174. if (!visible || !tradeNo || dataLoadedRef.current) return;
  175. let attempts = 0;
  176. const maxAttempts = 13;
  177. let timeoutId: ReturnType<typeof setTimeout>;
  178. let isMounted = true;
  179. const fetchData = async () => {
  180. if (!isMounted) return;
  181. try {
  182. const res = await getApplyResult(tradeNo);
  183. if (!isMounted) return;
  184. if (res?.inventoryList && res.inventoryList.length > 0) {
  185. dataLoadedRef.current = true;
  186. if (res.rebateAmount) setRebateAmount(res.rebateAmount);
  187. let array = res.inventoryList;
  188. if (res.magicFireworksList && res.magicFireworksList.length > 0) {
  189. array = [...res.magicFireworksList, ...res.inventoryList];
  190. }
  191. flipAnims.current = array.map(() => new Animated.Value(0));
  192. const sum = array.reduce((acc: number, item: LotteryItem) => acc + (item.magicAmount || 0), 0);
  193. setTotal(sum);
  194. setTableData(array);
  195. setLoading(false);
  196. setLoading(false);
  197. // Determine playing video
  198. let playVideoUrl = '';
  199. if (res.video) {
  200. playVideoUrl = res.video;
  201. } else {
  202. // Check if any Level A item exists
  203. const hasLevelA = array.some((item: LotteryItem) => item.level === 'A');
  204. if (hasLevelA) {
  205. playVideoUrl = DEFAULT_JACKPOT_VIDEO;
  206. }
  207. }
  208. // Check for Level A item for KingModal logic (which happens after video)
  209. const levelAItem = array.find((item: LotteryItem) => item.level === 'A');
  210. const startFlow = () => {
  211. setVideoVisible(false);
  212. if (levelAItem) {
  213. setKingData(levelAItem);
  214. setKingVisible(true);
  215. } else {
  216. setTimeout(() => flipCards(array), 500);
  217. }
  218. };
  219. if (playVideoUrl) {
  220. setVideoUrl(playVideoUrl);
  221. setVideoVisible(true);
  222. } else {
  223. startFlow();
  224. }
  225. } else if (attempts < maxAttempts) {
  226. attempts++;
  227. timeoutId = setTimeout(fetchData, 400);
  228. } else {
  229. setLoading(false);
  230. window.alert('获取结果超时,请在仓库中查看');
  231. }
  232. } catch (error) {
  233. if (attempts < maxAttempts) {
  234. attempts++;
  235. timeoutId = setTimeout(fetchData, 400);
  236. } else {
  237. setLoading(false);
  238. }
  239. }
  240. };
  241. fetchData();
  242. return () => {
  243. isMounted = false;
  244. if (timeoutId) clearTimeout(timeoutId);
  245. };
  246. }, [visible, tradeNo]);
  247. const handleSkip = () => {
  248. setIsSkip(false);
  249. flipAnims.current.forEach((anim) => anim.setValue(1));
  250. setShowDh(tableData.every((item) => item.level !== 'B' && item.level !== 'A'));
  251. setShowResult(true);
  252. };
  253. const handleDhAll = async () => {
  254. if (!total) return;
  255. try {
  256. const ids = tableData.filter((item) => item.magicAmount).map((item) => item.id);
  257. const res = await convertApply(ids);
  258. if (res) {
  259. setTableData((prev) => prev.map((item) => ({ ...item, magicAmount: 0 })));
  260. setTotal(0);
  261. window.alert('兑换成功');
  262. }
  263. } catch {
  264. window.alert('兑换失败,请重试');
  265. }
  266. };
  267. const handleBack = () => {
  268. setVisible(false);
  269. onClose?.();
  270. };
  271. const handleGoStore = () => {
  272. setVisible(false);
  273. onGoStore?.();
  274. };
  275. const renderCard = (item: LotteryItem, index: number) => {
  276. const levelConfig = LEVEL_MAP[item.level] || LEVEL_MAP.D;
  277. const flipAnim = flipAnims.current[index] || new Animated.Value(1);
  278. const isFirst9 = index < 9;
  279. // 前9张卡片有翻转动画
  280. if (isFirst9) {
  281. const backRotate = flipAnim.interpolate({
  282. inputRange: [0, 1],
  283. outputRange: ['0deg', '90deg']
  284. });
  285. const frontRotate = flipAnim.interpolate({
  286. inputRange: [0, 1],
  287. outputRange: ['-90deg', '0deg']
  288. });
  289. const backOpacity = flipAnim.interpolate({
  290. inputRange: [0, 0.5, 1],
  291. outputRange: [1, 0, 0]
  292. });
  293. const frontOpacity = flipAnim.interpolate({
  294. inputRange: [0, 0.5, 1],
  295. outputRange: [0, 0, 1]
  296. });
  297. return (
  298. <View key={item.id || index} style={styles.cardWrapper}>
  299. {/* 背面 - 卡牌背面 */}
  300. <Animated.View style={[styles.cardBack, { transform: [{ rotateY: backRotate }], opacity: backOpacity }]}>
  301. <Image source={{ uri: LotteryImages.cardBack }} style={styles.cardBackImage} contentFit="cover" />
  302. </Animated.View>
  303. {/* 正面 - 商品信息 */}
  304. <Animated.View style={[styles.cardFront, { transform: [{ rotateY: frontRotate }], opacity: frontOpacity }]}>
  305. <ImageBackground source={{ uri: levelConfig.resultBg }} style={styles.cardFrontBg} resizeMode="cover">
  306. <Image source={{ uri: item.cover }} style={styles.productImage} contentFit="contain" />
  307. <Image source={{ uri: levelConfig.borderImg }} style={styles.borderImage} contentFit="cover" />
  308. <View style={styles.cardInfo}>
  309. <View style={styles.infoRow}>
  310. <Text style={styles.levelText}>{levelConfig.title}</Text>
  311. <Text style={styles.priceText}>¥{item.spu?.marketPrice || 0}</Text>
  312. </View>
  313. <View style={styles.exchangeRow}>
  314. <Text style={styles.exchangeText}>价值:{item.magicAmount || 0}果实</Text>
  315. </View>
  316. <Text style={styles.nameText} numberOfLines={1}>{item.name}</Text>
  317. </View>
  318. </ImageBackground>
  319. </Animated.View>
  320. </View>
  321. );
  322. }
  323. // 9张以后的卡片直接显示正面
  324. return (
  325. <View key={item.id || index} style={styles.cardWrapper}>
  326. <View style={styles.cardFrontStatic}>
  327. <ImageBackground source={{ uri: levelConfig.resultBg }} style={styles.cardFrontBg} resizeMode="cover">
  328. <Image source={{ uri: item.cover }} style={styles.productImage} contentFit="contain" />
  329. <Image source={{ uri: levelConfig.borderImg }} style={styles.borderImage} contentFit="cover" />
  330. <View style={styles.cardInfo}>
  331. <View style={styles.infoRow}>
  332. <Text style={styles.levelText}>{levelConfig.title}</Text>
  333. <Text style={styles.priceText}>¥{item.spu?.marketPrice || 0}</Text>
  334. </View>
  335. <View style={styles.exchangeRow}>
  336. <Text style={styles.exchangeText}>价值:{item.magicAmount || 0}果实</Text>
  337. </View>
  338. <Text style={styles.nameText} numberOfLines={1}>{item.name}</Text>
  339. </View>
  340. </ImageBackground>
  341. </View>
  342. </View>
  343. );
  344. };
  345. return (
  346. <Modal visible={visible} transparent animationType="fade" onRequestClose={handleBack}>
  347. <View style={styles.container}>
  348. <ImageBackground source={{ uri: LotteryImages.lotteryBg }} style={styles.background} resizeMode="cover">
  349. {/* 光晕效果 */}
  350. {haloShow && (
  351. <View style={styles.haloSection}>
  352. <Image source={{ uri: LotteryImages.halo }} style={styles.halo} contentFit="cover" />
  353. </View>
  354. )}
  355. {/* 标题 */}
  356. <View style={[styles.titleSection, { marginTop: insets.top + 40 }]}>
  357. <Text style={styles.titleText}>恭喜您获得</Text>
  358. </View>
  359. {/* 主内容区 */}
  360. <View style={styles.mainSection}>
  361. {loading ? (
  362. <View style={styles.loadingBox}>
  363. <ActivityIndicator size="large" color="#fff" />
  364. <Text style={styles.loadingText}>正在开启宝箱...</Text>
  365. </View>
  366. ) : (
  367. <ScrollView style={styles.cardList} showsVerticalScrollIndicator={false}>
  368. <View style={styles.cardGrid}>
  369. {tableData.map((item, index) => renderCard(item, index))}
  370. </View>
  371. </ScrollView>
  372. )}
  373. </View>
  374. {/* 底部按钮区 */}
  375. {showResult && (
  376. <View style={[styles.bottomSection, { paddingBottom: insets.bottom + 20 }]}>
  377. <View style={styles.bottomBtns}>
  378. {total > 0 && showDh && (
  379. <TouchableOpacity style={styles.dhBtn} onPress={handleDhAll}>
  380. <Text style={styles.dhBtnText}>全部兑换</Text>
  381. <Text style={styles.dhBtnSubText}>共兑换果实 {total}</Text>
  382. </TouchableOpacity>
  383. )}
  384. <TouchableOpacity style={styles.againBtn} onPress={handleBack}>
  385. <Text style={styles.againBtnText}>再来一发</Text>
  386. </TouchableOpacity>
  387. </View>
  388. <TouchableOpacity style={styles.storeLink} onPress={handleGoStore}>
  389. <Text style={styles.storeLinkText}>前往 <Text style={styles.storeHighlight}>仓库</Text> 查看</Text>
  390. </TouchableOpacity>
  391. {rebateAmount > 0 && (
  392. <Text style={styles.rebateText}>本次支付返还果实 <Text style={styles.rebateAmount}>{rebateAmount}</Text> 枚</Text>
  393. )}
  394. <View style={styles.animationSwitch}>
  395. <Text style={styles.switchLabel}>是否开启动画</Text>
  396. <TouchableOpacity
  397. style={[styles.switchBtn, animationEnabled && styles.switchBtnActive]}
  398. onPress={() => setAnimationEnabled(!animationEnabled)}
  399. >
  400. <View style={[styles.switchThumb, animationEnabled && styles.switchThumbActive]} />
  401. </TouchableOpacity>
  402. </View>
  403. </View>
  404. )}
  405. {/* 跳过动画按钮 */}
  406. {isSkip && !loading && (
  407. <TouchableOpacity style={styles.skipBtn} onPress={handleSkip}>
  408. <Text style={styles.skipText}>跳过动画</Text>
  409. </TouchableOpacity>
  410. )}
  411. </ImageBackground>
  412. </View>
  413. <KingModal
  414. visible={kingVisible}
  415. data={kingData}
  416. onClose={() => {
  417. setKingVisible(false);
  418. setTimeout(() => flipCards(tableData), 300);
  419. }}
  420. />
  421. {videoVisible && videoUrl ? (
  422. <View style={styles.videoContainer}>
  423. <Video
  424. source={{ uri: videoUrl }}
  425. style={styles.video}
  426. resizeMode={ResizeMode.COVER}
  427. shouldPlay
  428. isLooping={false}
  429. onPlaybackStatusUpdate={(status: AVPlaybackStatus) => {
  430. if (status.isLoaded && status.didJustFinish) {
  431. // Video finished
  432. setVideoVisible(false);
  433. // Proceed to next step
  434. const levelAItem = tableData.find((item: LotteryItem) => item.level === 'A');
  435. if (levelAItem) {
  436. setKingData(levelAItem);
  437. setKingVisible(true);
  438. } else {
  439. setTimeout(() => flipCards(tableData), 300);
  440. }
  441. }
  442. }}
  443. />
  444. <TouchableOpacity
  445. style={styles.skipVideoBtn}
  446. onPress={() => {
  447. setVideoVisible(false);
  448. // Proceed to next step
  449. const levelAItem = tableData.find((item: LotteryItem) => item.level === 'A');
  450. if (levelAItem) {
  451. setKingData(levelAItem);
  452. setKingVisible(true);
  453. } else {
  454. setTimeout(() => flipCards(tableData), 300);
  455. }
  456. }}
  457. >
  458. <Text style={styles.skipText}>跳过</Text>
  459. </TouchableOpacity>
  460. </View>
  461. ) : null}
  462. </Modal>
  463. );
  464. }
  465. );
  466. const styles = StyleSheet.create({
  467. container: { flex: 1, backgroundColor: '#1a1a2e' },
  468. background: { flex: 1 },
  469. haloSection: { position: 'absolute', left: 0, top: 0, right: 0, bottom: 0, zIndex: 9999 },
  470. halo: { width: '100%', height: '100%' },
  471. titleSection: { alignItems: 'center', marginBottom: 15 },
  472. titleText: {
  473. fontSize: 31,
  474. fontWeight: 'bold',
  475. color: '#fffecc',
  476. textShadowColor: '#a06939',
  477. textShadowOffset: { width: 1, height: 1 },
  478. textShadowRadius: 2
  479. },
  480. mainSection: { flex: 1, paddingTop: 20 },
  481. loadingBox: { flex: 1, justifyContent: 'center', alignItems: 'center' },
  482. loadingText: { marginTop: 15, fontSize: 14, color: '#fff' },
  483. cardList: { flex: 1 },
  484. cardGrid: {
  485. flexDirection: 'row',
  486. flexWrap: 'wrap',
  487. paddingHorizontal: 15,
  488. justifyContent: 'flex-start'
  489. },
  490. cardWrapper: {
  491. width: CARD_WIDTH,
  492. height: CARD_HEIGHT,
  493. marginHorizontal: 5,
  494. marginBottom: 15,
  495. position: 'relative'
  496. },
  497. cardBack: {
  498. position: 'absolute',
  499. width: '100%',
  500. height: '100%',
  501. backfaceVisibility: 'hidden'
  502. },
  503. cardBackImage: { width: '100%', height: '100%', borderRadius: 10 },
  504. cardFront: {
  505. position: 'absolute',
  506. width: '100%',
  507. height: '100%',
  508. backfaceVisibility: 'hidden',
  509. borderRadius: 10,
  510. overflow: 'hidden'
  511. },
  512. cardFrontStatic: {
  513. width: '100%',
  514. height: '100%',
  515. borderRadius: 10,
  516. overflow: 'hidden'
  517. },
  518. cardFrontBg: {
  519. width: '100%',
  520. height: '100%',
  521. paddingTop: 15,
  522. borderRadius: 10,
  523. overflow: 'hidden'
  524. },
  525. productImage: { width: '85%', height: '55%', alignSelf: 'center' },
  526. borderImage: { position: 'absolute', left: 0, top: 0, width: '100%', height: '100%' },
  527. cardInfo: { position: 'absolute', left: 0, right: 0, bottom: 7, paddingHorizontal: 10 },
  528. infoRow: {
  529. flexDirection: 'row',
  530. justifyContent: 'space-between',
  531. alignItems: 'center',
  532. marginBottom: 2
  533. },
  534. levelText: { fontSize: 13, fontWeight: 'bold', color: '#fff' },
  535. priceText: { fontSize: 12, fontWeight: 'bold', color: '#fff' },
  536. exchangeRow: { marginBottom: 2 },
  537. exchangeText: { fontSize: 10, color: '#fff', fontWeight: 'bold' },
  538. nameText: { fontSize: 12, fontWeight: 'bold', color: '#fff' },
  539. bottomSection: { paddingHorizontal: 20, paddingTop: 20 },
  540. bottomBtns: { flexDirection: 'row', justifyContent: 'center', alignItems: 'center' },
  541. dhBtn: {
  542. backgroundColor: '#fff7e3',
  543. borderRadius: 20,
  544. paddingHorizontal: 18,
  545. paddingVertical: 8,
  546. marginRight: 10,
  547. alignItems: 'center'
  548. },
  549. dhBtnText: { fontSize: 14, fontWeight: '500', color: '#000' },
  550. dhBtnSubText: { fontSize: 10, color: '#735200' },
  551. againBtn: {
  552. backgroundColor: '#fec433',
  553. borderRadius: 20,
  554. paddingHorizontal: 25,
  555. paddingVertical: 12
  556. },
  557. againBtnText: { fontSize: 14, fontWeight: '600', color: '#000' },
  558. storeLink: { alignItems: 'center', marginTop: 12 },
  559. storeLinkText: { fontSize: 12, color: '#fff' },
  560. storeHighlight: { color: '#ff9600' },
  561. rebateText: { textAlign: 'center', marginTop: 13, fontSize: 13, color: '#fff' },
  562. rebateAmount: { color: '#ffeb3b', fontSize: 14, fontWeight: 'bold' },
  563. animationSwitch: {
  564. flexDirection: 'row',
  565. justifyContent: 'center',
  566. alignItems: 'center',
  567. marginTop: 8
  568. },
  569. switchLabel: { fontSize: 12, color: '#dedede', marginRight: 10 },
  570. switchBtn: {
  571. width: 44,
  572. height: 24,
  573. borderRadius: 12,
  574. backgroundColor: '#666',
  575. justifyContent: 'center',
  576. paddingHorizontal: 2
  577. },
  578. switchBtnActive: { backgroundColor: '#ff9600' },
  579. switchThumb: { width: 20, height: 20, borderRadius: 10, backgroundColor: '#fff' },
  580. switchThumbActive: { alignSelf: 'flex-end' },
  581. skipBtn: {
  582. position: 'absolute',
  583. bottom: '10%',
  584. alignSelf: 'center',
  585. backgroundColor: 'rgba(0,0,0,0.4)',
  586. paddingHorizontal: 15,
  587. paddingVertical: 7,
  588. borderRadius: 15
  589. },
  590. skipText: { fontSize: 14, color: '#fff' },
  591. kingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: 'transparent' },
  592. kingMask: { ...StyleSheet.absoluteFillObject, backgroundColor: 'rgba(0,0,0,0.7)' },
  593. kingWrapper: { width: 353 * 1.2, height: 545 * 1.2, alignItems: 'center', justifyContent: 'center' },
  594. kingBg: { position: 'absolute', width: '100%', height: '100%' },
  595. kingProduct: { width: 293 * 1.2, height: 370 * 1.2, marginTop: 33, borderRadius: 8 },
  596. kingTitle: { position: 'absolute', bottom: 40, width: 230 * 1.2, height: 102 * 1.2 },
  597. kingCloseBtn: { marginTop: 30 },
  598. kingCloseIcon: { width: 60, height: 60 },
  599. videoContainer: { ...StyleSheet.absoluteFillObject, zIndex: 10000, backgroundColor: 'black' },
  600. video: { width: '100%', height: '100%' },
  601. skipVideoBtn: { position: 'absolute', top: 60, right: 20, backgroundColor: 'rgba(0,0,0,0.5)', paddingHorizontal: 15, paddingVertical: 8, borderRadius: 20 },
  602. });