@devrongx/games 0.4.41 → 0.4.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devrongx/games",
3
- "version": "0.4.41",
3
+ "version": "0.4.43",
4
4
  "description": "Game UI components for sports prediction markets",
5
5
  "license": "MIT",
6
6
  "main": "./src/index.ts",
@@ -585,7 +585,7 @@ export const PreMatchGame = ({
585
585
 
586
586
  <div className="flex items-stretch justify-between gap-3">
587
587
  {/* Left — text area */}
588
- <div className="flex flex-col gap-2 flex-1 min-w-0">
588
+ <div className="flex flex-col gap-2 flex-1 min-w-0 overflow-hidden">
589
589
  {/* Remaining balance */}
590
590
  <AnimatePresence>
591
591
  {remainingBalance > 0 && (
@@ -711,8 +711,8 @@ export const PreMatchGame = ({
711
711
  )}
712
712
  </AnimatePresence>
713
713
 
714
- {/* Combined Bets (Parlays) */}
715
- <div className="mt-4">
714
+ {/* Combined Bets (Parlays) — hidden until v2 */}
715
+ {false && <div className="mt-4">
716
716
  {hasAnyParlays ? (
717
717
  <>
718
718
  <div className="flex items-center gap-2 mb-1">
@@ -835,7 +835,7 @@ export const PreMatchGame = ({
835
835
  </button>
836
836
  </div>
837
837
  )}
838
- </div>
838
+ </div>}
839
839
  </div>
840
840
 
841
841
  {/* Leaderboard & Potential Payouts */}
@@ -4,8 +4,9 @@
4
4
  import { useState, useEffect, useCallback, useRef, useMemo } from "react";
5
5
  import Image from "next/image";
6
6
  import { motion, AnimatePresence } from "framer-motion";
7
- import { Coins, Trophy, Pause, Rewind, FastForward, RotateCcw } from "lucide-react";
7
+ import { Trophy, Pause, Rewind, FastForward, RotateCcw } from "lucide-react";
8
8
  import { IChallengeConfig } from "./config";
9
+ import { MARKET_ICONS, PointsIcon } from "./constants";
9
10
 
10
11
  const OUTFIT = { fontFamily: "Outfit, sans-serif" };
11
12
 
@@ -216,199 +217,488 @@ const PointsSlide = ({ config }: SlideProps) => (
216
217
  </motion.div>
217
218
  );
218
219
 
219
- // ── Slide 3: Markets demo ──
220
+ // ── Shared helper: derive demo data for MarketsSlide + MultiplierSlide ──
221
+ const deriveDemoData = (config: IChallengeConfig) => {
222
+ const markets = config.markets;
223
+ const len = markets.length;
224
+
225
+ let idxA: number, idxB: number;
226
+ if (len > 12) { idxA = 8; idxB = 12; }
227
+ else if (len >= 2) { idxA = len - 2; idxB = len - 1; }
228
+ else { idxA = 0; idxB = Math.min(1, len - 1); }
229
+
230
+ const marketA = markets[idxA];
231
+ const marketB = markets[idxB];
232
+
233
+ const optIdxA = Math.min(1, marketA.options.length - 1);
234
+ const optIdxB = Math.min(2, marketB.options.length - 1);
235
+ const optionA = marketA.options[optIdxA];
236
+ const optionB = marketB.options[optIdxB];
237
+
238
+ const inc = config.parlayConfig.stakeIncrements;
239
+ const betA = inc * 2;
240
+ const betB = inc * 3;
241
+ const chips = [inc, inc * 2, inc * 3, inc * 4];
242
+
243
+ const rewardA = Math.round(betA * optionA.odds);
244
+ const rewardB = Math.round(betB * optionB.odds);
245
+
246
+ const combinedStake = betA + betB;
247
+ const combinedOdds = optionA.odds * optionB.odds;
248
+ const combinedReward = Math.round(combinedStake * combinedOdds);
249
+
250
+ const maxMult = config.compoundMultipliers[config.compoundMultipliers.length - 1];
251
+
252
+ return { marketA, marketB, optIdxA, optIdxB, optionA, optionB, betA, betB, chips, rewardA, rewardB, combinedStake, combinedOdds, combinedReward, maxMult };
253
+ };
254
+
255
+ // ── Slide 3: Markets demo — two questions with full bet flow ──
220
256
  const MarketsSlide = ({ config }: SlideProps) => {
257
+ const demo = useMemo(() => deriveDemoData(config), [config]);
221
258
  const [demoStep, setDemoStep] = useState(0);
222
259
 
223
260
  useEffect(() => {
224
261
  const timers = [
225
- setTimeout(() => setDemoStep(1), 2800),
226
- setTimeout(() => setDemoStep(2), 4200),
262
+ setTimeout(() => setDemoStep(1), 800),
263
+ setTimeout(() => setDemoStep(2), 2200),
264
+ setTimeout(() => setDemoStep(3), 3200),
265
+ setTimeout(() => setDemoStep(4), 3900),
266
+ setTimeout(() => setDemoStep(5), 4500),
267
+ setTimeout(() => setDemoStep(6), 5500),
268
+ setTimeout(() => setDemoStep(7), 6800),
269
+ setTimeout(() => setDemoStep(8), 7800),
270
+ setTimeout(() => setDemoStep(9), 8500),
271
+ setTimeout(() => setDemoStep(10), 9100),
227
272
  ];
228
273
  return () => timers.forEach(clearTimeout);
229
274
  }, []);
230
275
 
276
+ const { marketA, marketB, optIdxA, optIdxB, optionA, optionB, betA, betB, chips, rewardA, rewardB } = demo;
277
+ const IconA = MARKET_ICONS[marketA.icon];
278
+ const IconB = MARKET_ICONS[marketB.icon];
279
+
280
+ const card1Compressed = demoStep >= 6;
281
+
231
282
  return (
232
283
  <motion.div
233
- className="flex flex-col items-center gap-5 w-full max-w-[300px]"
284
+ className="flex flex-col items-center gap-3 w-full max-w-[300px]"
234
285
  initial="initial"
235
286
  animate="animate"
236
287
  exit="exit"
237
288
  >
289
+ {/* Header */}
238
290
  <motion.p
239
291
  initial={{ opacity: 0, y: 20 }}
240
292
  animate={{ opacity: 1, y: 0 }}
241
- transition={{ duration: 0.5, delay: 0.2 }}
293
+ transition={{ duration: 0.5 }}
242
294
  className="text-[14px] text-white font-medium text-center"
243
295
  style={OUTFIT}
244
296
  >
245
- {config.markets.length} markets to predict
297
+ {config.markets.length} questions to predict
246
298
  </motion.p>
247
299
 
248
- <motion.div
249
- initial={{ opacity: 0, y: 20 }}
250
- animate={{ opacity: 1, y: 0 }}
251
- transition={{ duration: 0.6, delay: 1.2 }}
252
- className="w-full rounded-lg px-3 py-3"
253
- style={{
254
- background: "rgba(255,255,255,0.03)",
255
- border: "1px solid rgba(255,255,255,0.08)",
256
- }}
257
- >
258
- <div className="flex items-center gap-2 mb-2.5">
259
- <Coins size={14} className="text-[#22E3E8]/50 flex-shrink-0" />
260
- <span className="text-[13px] text-white font-semibold" style={OUTFIT}>
261
- Who wins the toss?
262
- </span>
263
- </div>
264
- <div className="grid grid-cols-2 gap-2">
265
- <div
266
- className="flex items-center justify-between px-2.5 py-2 rounded-sm transition-all duration-500"
300
+ {/* Card 1 */}
301
+ <AnimatePresence>
302
+ {demoStep >= 1 && (
303
+ <motion.div
304
+ initial={{ opacity: 0, y: 20 }}
305
+ animate={{ opacity: 1, y: 0 }}
306
+ transition={{ duration: 0.5 }}
307
+ className="w-full rounded-lg px-3 py-3"
267
308
  style={{
268
- background: demoStep >= 1 ? "linear-gradient(135deg, #22E3E8, #9945FF)" : "transparent",
269
- borderLeft: demoStep >= 1 ? "1px solid transparent" : "1px solid rgba(255,255,255,0.12)",
270
- borderBottom: demoStep >= 1 ? "1px solid transparent" : "1px solid rgba(255,255,255,0.06)",
271
- borderTop: "1px solid transparent",
272
- borderRight: "1px solid transparent",
309
+ background: "rgba(255,255,255,0.03)",
310
+ border: "1px solid rgba(255,255,255,0.08)",
273
311
  }}
274
312
  >
275
- <div className="flex items-center gap-1.5">
276
- {demoStep >= 1 && (
277
- <motion.svg
278
- initial={{ scale: 0 }}
279
- animate={{ scale: 1 }}
280
- transition={{ type: "spring", stiffness: 400, damping: 15 }}
281
- width="10" height="10" viewBox="0 0 16 16" fill="none"
282
- >
313
+ {/* Question header */}
314
+ <div className="flex items-center gap-2 mb-2">
315
+ {IconA && <IconA size={14} style={{ color: marketA.accent }} className="flex-shrink-0" />}
316
+ <span className={`text-white font-semibold leading-tight ${card1Compressed ? "text-[11px]" : "text-[13px]"}`} style={OUTFIT}>
317
+ {marketA.question}
318
+ </span>
319
+ </div>
320
+
321
+ {/* Options grid or compressed selected tag */}
322
+ {!card1Compressed ? (
323
+ <div className="grid grid-cols-2 gap-2">
324
+ {marketA.options.map((opt, j) => {
325
+ const isSelected = demoStep >= 2 && j === optIdxA;
326
+ return (
327
+ <div
328
+ key={j}
329
+ className="flex items-center justify-between px-2 py-1.5 rounded-sm transition-all duration-500"
330
+ style={{
331
+ background: isSelected ? "linear-gradient(135deg, #22E3E8, #9945FF)" : "transparent",
332
+ borderLeft: isSelected ? "1px solid transparent" : "1px solid rgba(255,255,255,0.12)",
333
+ borderBottom: isSelected ? "1px solid transparent" : "1px solid rgba(255,255,255,0.06)",
334
+ borderTop: "1px solid transparent",
335
+ borderRight: "1px solid transparent",
336
+ }}
337
+ >
338
+ <div className="flex items-center gap-1.5">
339
+ {isSelected && (
340
+ <motion.svg
341
+ initial={{ scale: 0 }}
342
+ animate={{ scale: 1 }}
343
+ transition={{ type: "spring", stiffness: 400, damping: 15 }}
344
+ width="10" height="10" viewBox="0 0 16 16" fill="none"
345
+ >
346
+ <circle cx="8" cy="8" r="8" fill="white" />
347
+ <path d="M5 8l2 2 4-4" stroke="#9945FF" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
348
+ </motion.svg>
349
+ )}
350
+ <span className="text-[10px] text-white font-semibold truncate" style={OUTFIT}>{opt.label}</span>
351
+ </div>
352
+ <span className={`text-[10px] font-bold ${isSelected ? "text-white" : "text-[#22E3E8]"}`} style={OUTFIT}>{opt.odds}x</span>
353
+ </div>
354
+ );
355
+ })}
356
+ </div>
357
+ ) : (
358
+ <div className="flex items-center gap-1.5 mb-1">
359
+ <svg width="10" height="10" viewBox="0 0 16 16" fill="none">
283
360
  <circle cx="8" cy="8" r="8" fill="white" />
284
361
  <path d="M5 8l2 2 4-4" stroke="#9945FF" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
285
- </motion.svg>
362
+ </svg>
363
+ <span className="text-[10px] text-white font-semibold" style={OUTFIT}>{optionA.label}</span>
364
+ <span className="text-[9px] text-white/40 font-semibold" style={OUTFIT}>{optionA.odds}x</span>
365
+ </div>
366
+ )}
367
+
368
+ {/* Chip carousel — Card 1 (hidden when compressed) */}
369
+ <AnimatePresence>
370
+ {demoStep >= 3 && !card1Compressed && (
371
+ <motion.div
372
+ initial={{ opacity: 0, height: 0 }}
373
+ animate={{ opacity: 1, height: "auto" }}
374
+ exit={{ opacity: 0, height: 0 }}
375
+ transition={{ duration: 0.3 }}
376
+ className="flex items-center gap-1.5 overflow-hidden mt-2"
377
+ >
378
+ <span className="text-[8px] text-white/60 font-semibold flex-shrink-0" style={OUTFIT}>Bet:</span>
379
+ {chips.map((amt, i) => {
380
+ const isChipSelected = demoStep >= 4 && amt === betA;
381
+ return (
382
+ <motion.div
383
+ key={amt}
384
+ initial={{ opacity: 0, scale: 0.8 }}
385
+ animate={{ opacity: 1, scale: 1 }}
386
+ transition={{ delay: i * 0.08 }}
387
+ className="flex items-center gap-0.5 px-2 py-[3px] rounded relative overflow-hidden"
388
+ style={isChipSelected ? {
389
+ background: "linear-gradient(135deg, #22E3E8, #9945FF, #f83cc5)",
390
+ } : {
391
+ background: "rgba(34,227,232,0.08)",
392
+ border: "1px solid rgba(34,227,232,0.2)",
393
+ }}
394
+ >
395
+ <PointsIcon size={7} />
396
+ <span
397
+ className={`text-[9px] font-bold relative z-[1] ${isChipSelected ? "text-black" : "text-[#22E3E8]"}`}
398
+ style={OUTFIT}
399
+ >
400
+ {amt}
401
+ </span>
402
+ </motion.div>
403
+ );
404
+ })}
405
+ </motion.div>
286
406
  )}
287
- <span className="text-[11px] text-white font-semibold" style={OUTFIT}>{config.teamA.name}</span>
288
- </div>
289
- <span className={`text-[10px] font-bold ${demoStep >= 1 ? "text-white" : "text-[#22E3E8]"}`} style={OUTFIT}>
290
- 1.4x
291
- </span>
292
- </div>
293
- <div
294
- className="flex items-center justify-between px-2.5 py-2 rounded-sm"
407
+ </AnimatePresence>
408
+
409
+ {/* Outcome line Card 1 */}
410
+ {demoStep >= 5 && (
411
+ <motion.div
412
+ initial={{ opacity: 0, y: 4 }}
413
+ animate={{ opacity: 1, y: 0 }}
414
+ transition={{ duration: 0.3 }}
415
+ className="flex items-center gap-[3px] mt-1.5"
416
+ >
417
+ <PointsIcon size={7} />
418
+ <span className="text-[9px] text-white font-semibold" style={OUTFIT}>{betA}</span>
419
+ <span className="text-[8px] text-white/40" style={OUTFIT}>&times;</span>
420
+ <span className="text-[9px] text-white/60 font-semibold" style={OUTFIT}>{optionA.odds}</span>
421
+ <span className="text-[8px] text-white/40" style={OUTFIT}>=</span>
422
+ <PointsIcon size={7} />
423
+ <span className="text-[9px] text-[#22E3E8] font-bold" style={OUTFIT}>{rewardA}</span>
424
+ </motion.div>
425
+ )}
426
+ </motion.div>
427
+ )}
428
+ </AnimatePresence>
429
+
430
+ {/* Card 2 */}
431
+ <AnimatePresence>
432
+ {demoStep >= 6 && (
433
+ <motion.div
434
+ initial={{ opacity: 0, y: 20 }}
435
+ animate={{ opacity: 1, y: 0 }}
436
+ transition={{ duration: 0.5 }}
437
+ className="w-full rounded-lg px-3 py-3"
295
438
  style={{
296
- borderLeft: "1px solid rgba(255,255,255,0.12)",
297
- borderBottom: "1px solid rgba(255,255,255,0.06)",
298
- borderTop: "1px solid transparent",
299
- borderRight: "1px solid transparent",
439
+ background: "rgba(255,255,255,0.03)",
440
+ border: "1px solid rgba(255,255,255,0.08)",
300
441
  }}
301
442
  >
302
- <span className="text-[11px] text-white font-semibold" style={OUTFIT}>{config.teamB.name}</span>
303
- <span className="text-[10px] font-bold text-[#22E3E8]" style={OUTFIT}>1.4x</span>
304
- </div>
305
- </div>
306
- <AnimatePresence>
307
- {demoStep >= 2 && (
308
- <motion.div
309
- initial={{ opacity: 0, height: 0 }}
310
- animate={{ opacity: 1, height: "auto" }}
311
- exit={{ opacity: 0, height: 0 }}
312
- transition={{ duration: 0.3 }}
313
- className="mt-2 flex items-center gap-1.5 overflow-hidden"
314
- >
315
- <span className="text-[8px] text-white/60 font-semibold flex-shrink-0" style={OUTFIT}>Bet:</span>
316
- {[50, 100, 150, 200].map((amt, i) => (
443
+ <div className="flex items-center gap-2 mb-2">
444
+ {IconB && <IconB size={14} style={{ color: marketB.accent }} className="flex-shrink-0" />}
445
+ <span className="text-[13px] text-white font-semibold leading-tight" style={OUTFIT}>
446
+ {marketB.question}
447
+ </span>
448
+ </div>
449
+
450
+ <div className="grid grid-cols-2 gap-2">
451
+ {marketB.options.map((opt, j) => {
452
+ const isSelected = demoStep >= 7 && j === optIdxB;
453
+ return (
454
+ <div
455
+ key={j}
456
+ className="flex items-center justify-between px-2 py-1.5 rounded-sm transition-all duration-500"
457
+ style={{
458
+ background: isSelected ? "linear-gradient(135deg, #22E3E8, #9945FF)" : "transparent",
459
+ borderLeft: isSelected ? "1px solid transparent" : "1px solid rgba(255,255,255,0.12)",
460
+ borderBottom: isSelected ? "1px solid transparent" : "1px solid rgba(255,255,255,0.06)",
461
+ borderTop: "1px solid transparent",
462
+ borderRight: "1px solid transparent",
463
+ }}
464
+ >
465
+ <div className="flex items-center gap-1.5">
466
+ {isSelected && (
467
+ <motion.svg
468
+ initial={{ scale: 0 }}
469
+ animate={{ scale: 1 }}
470
+ transition={{ type: "spring", stiffness: 400, damping: 15 }}
471
+ width="10" height="10" viewBox="0 0 16 16" fill="none"
472
+ >
473
+ <circle cx="8" cy="8" r="8" fill="white" />
474
+ <path d="M5 8l2 2 4-4" stroke="#9945FF" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
475
+ </motion.svg>
476
+ )}
477
+ <span className="text-[10px] text-white font-semibold truncate" style={OUTFIT}>{opt.label}</span>
478
+ </div>
479
+ <span className={`text-[10px] font-bold ${isSelected ? "text-white" : "text-[#22E3E8]"}`} style={OUTFIT}>{opt.odds}x</span>
480
+ </div>
481
+ );
482
+ })}
483
+ </div>
484
+
485
+ {/* Chip carousel — Card 2 */}
486
+ <AnimatePresence>
487
+ {demoStep >= 8 && (
317
488
  <motion.div
318
- key={amt}
319
- initial={{ opacity: 0, scale: 0.8 }}
320
- animate={{ opacity: 1, scale: 1 }}
321
- transition={{ delay: i * 0.08 }}
322
- className="flex items-center gap-0.5 px-2 py-[3px] rounded relative overflow-hidden"
323
- style={amt === 100 ? {
324
- background: "linear-gradient(135deg, #22E3E8, #9945FF, #f83cc5)",
325
- } : {
326
- background: "rgba(34,227,232,0.08)",
327
- border: "1px solid rgba(34,227,232,0.2)",
328
- }}
489
+ initial={{ opacity: 0, height: 0 }}
490
+ animate={{ opacity: 1, height: "auto" }}
491
+ exit={{ opacity: 0, height: 0 }}
492
+ transition={{ duration: 0.3 }}
493
+ className="flex items-center gap-1.5 overflow-hidden mt-2"
329
494
  >
330
- <Image src="/iamgame_square_logo.jpg" alt="" width={7} height={7} className="rounded-[1px] relative z-[1]" />
331
- <span
332
- className={`text-[9px] font-bold relative z-[1] ${amt === 100 ? "text-black" : "text-[#22E3E8]"}`}
333
- style={OUTFIT}
334
- >
335
- {amt}
336
- </span>
495
+ <span className="text-[8px] text-white/60 font-semibold flex-shrink-0" style={OUTFIT}>Bet:</span>
496
+ {chips.map((amt, i) => {
497
+ const isChipSelected = demoStep >= 9 && amt === betB;
498
+ return (
499
+ <motion.div
500
+ key={amt}
501
+ initial={{ opacity: 0, scale: 0.8 }}
502
+ animate={{ opacity: 1, scale: 1 }}
503
+ transition={{ delay: i * 0.08 }}
504
+ className="flex items-center gap-0.5 px-2 py-[3px] rounded relative overflow-hidden"
505
+ style={isChipSelected ? {
506
+ background: "linear-gradient(135deg, #22E3E8, #9945FF, #f83cc5)",
507
+ } : {
508
+ background: "rgba(34,227,232,0.08)",
509
+ border: "1px solid rgba(34,227,232,0.2)",
510
+ }}
511
+ >
512
+ <PointsIcon size={7} />
513
+ <span
514
+ className={`text-[9px] font-bold relative z-[1] ${isChipSelected ? "text-black" : "text-[#22E3E8]"}`}
515
+ style={OUTFIT}
516
+ >
517
+ {amt}
518
+ </span>
519
+ </motion.div>
520
+ );
521
+ })}
337
522
  </motion.div>
338
- ))}
339
- </motion.div>
340
- )}
341
- </AnimatePresence>
342
- </motion.div>
523
+ )}
524
+ </AnimatePresence>
343
525
 
344
- <motion.p
345
- initial={{ opacity: 0, y: 16 }}
346
- animate={{ opacity: 1, y: 0 }}
347
- transition={{ duration: 0.5, delay: 5.0 }}
348
- className="text-[12px] text-white/40 font-medium text-center"
349
- style={OUTFIT}
350
- >
351
- Toss, winner, top scorers, boundaries, wickets & more
352
- </motion.p>
526
+ {/* Outcome line — Card 2 */}
527
+ {demoStep >= 10 && (
528
+ <motion.div
529
+ initial={{ opacity: 0, y: 4 }}
530
+ animate={{ opacity: 1, y: 0 }}
531
+ transition={{ duration: 0.3 }}
532
+ className="flex items-center gap-[3px] mt-1.5"
533
+ >
534
+ <PointsIcon size={7} />
535
+ <span className="text-[9px] text-white font-semibold" style={OUTFIT}>{betB}</span>
536
+ <span className="text-[8px] text-white/40" style={OUTFIT}>&times;</span>
537
+ <span className="text-[9px] text-white/60 font-semibold" style={OUTFIT}>{optionB.odds}</span>
538
+ <span className="text-[8px] text-white/40" style={OUTFIT}>=</span>
539
+ <PointsIcon size={7} />
540
+ <span className="text-[9px] text-[#22E3E8] font-bold" style={OUTFIT}>{rewardB}</span>
541
+ </motion.div>
542
+ )}
543
+ </motion.div>
544
+ )}
545
+ </AnimatePresence>
353
546
  </motion.div>
354
547
  );
355
548
  };
356
549
 
357
- // ── Slide 4: Compound multiplier ──
358
- const MultiplierSlide = ({ config }: SlideProps) => (
359
- <motion.div
360
- className="flex flex-col items-center gap-4"
361
- initial="initial"
362
- animate="animate"
363
- exit="exit"
364
- >
365
- <motion.p
366
- initial={{ opacity: 0, y: 20 }}
367
- animate={{ opacity: 1, y: 0 }}
368
- transition={{ duration: 0.5, delay: 0.2 }}
369
- className="text-[15px] text-white font-semibold"
370
- style={OUTFIT}
371
- >
372
- Stack predictions.
373
- </motion.p>
374
- <motion.p
375
- initial={{ opacity: 0, y: 20 }}
376
- animate={{ opacity: 1, y: 0 }}
377
- transition={{ duration: 0.5, delay: 1.0 }}
378
- className="text-[15px] text-white/70 font-medium"
379
- style={OUTFIT}
380
- >
381
- Multiply your odds.
382
- </motion.p>
550
+ // ── Slide 4: Compound bet demo ──
551
+ const MultiplierSlide = ({ config }: SlideProps) => {
552
+ const demo = useMemo(() => deriveDemoData(config), [config]);
553
+ const [step, setStep] = useState(0);
554
+
555
+ useEffect(() => {
556
+ const timers = [
557
+ setTimeout(() => setStep(1), 300),
558
+ setTimeout(() => setStep(2), 1200),
559
+ setTimeout(() => setStep(3), 3000),
560
+ setTimeout(() => setStep(4), 4200),
561
+ setTimeout(() => setStep(5), 5200),
562
+ setTimeout(() => setStep(6), 6500),
563
+ ];
564
+ return () => timers.forEach(clearTimeout);
565
+ }, []);
566
+
567
+ const { marketA, marketB, optionA, optionB, betA, betB, rewardA, rewardB, combinedStake, combinedOdds, combinedReward, maxMult } = demo;
568
+ const IconA = MARKET_ICONS[marketA.icon];
569
+ const IconB = MARKET_ICONS[marketB.icon];
570
+ const combinedOddsDisplay = Math.round(combinedOdds * 100) / 100;
571
+
572
+ const renderCompactCard = (
573
+ market: typeof marketA,
574
+ Icon: typeof IconA,
575
+ option: typeof optionA,
576
+ betAmount: number,
577
+ reward: number,
578
+ highlighted: boolean,
579
+ hideOutcome: boolean,
580
+ ) => (
383
581
  <motion.div
384
- initial={{ opacity: 0, scale: 0.8 }}
385
- animate={{ opacity: 1, scale: 1 }}
386
- transition={{ duration: 0.7, delay: 2.0, ease: "easeOut" }}
582
+ className="w-full rounded-lg px-3 py-2.5"
583
+ animate={{
584
+ borderColor: highlighted ? "rgba(34,227,232,0.4)" : "rgba(255,255,255,0.08)",
585
+ boxShadow: highlighted ? "0 0 12px rgba(34,227,232,0.15)" : "0 0 0px transparent",
586
+ }}
587
+ transition={{ duration: 0.4 }}
588
+ style={{
589
+ background: "rgba(255,255,255,0.03)",
590
+ border: "1px solid rgba(255,255,255,0.08)",
591
+ }}
387
592
  >
388
- <span
389
- className="text-[72px] font-bold leading-none"
390
- style={{
391
- ...OUTFIT,
392
- background: "linear-gradient(135deg, #22E3E8, #9945FF, #f83cc5)",
393
- WebkitBackgroundClip: "text",
394
- WebkitTextFillColor: "transparent",
395
- backgroundClip: "text",
396
- }}
593
+ <div className="flex items-center gap-1.5 mb-1.5">
594
+ {Icon && <Icon size={12} style={{ color: market.accent }} className="flex-shrink-0" />}
595
+ <span className="text-[11px] text-white font-semibold leading-tight truncate" style={OUTFIT}>{market.question}</span>
596
+ </div>
597
+ <div className="flex items-center gap-1.5 mb-1">
598
+ <div
599
+ className="flex items-center gap-1 px-2 py-[2px] rounded-sm"
600
+ style={{ background: "linear-gradient(135deg, #22E3E8, #9945FF)" }}
601
+ >
602
+ <span className="text-[9px] text-white font-semibold" style={OUTFIT}>{option.label}</span>
603
+ </div>
604
+ <span className="text-[9px] text-white/50 font-semibold" style={OUTFIT}>at {option.odds}x</span>
605
+ </div>
606
+ <motion.div
607
+ className="flex items-center gap-[3px] overflow-hidden"
608
+ animate={{ opacity: hideOutcome ? 0 : 1, height: hideOutcome ? 0 : "auto" }}
609
+ transition={{ duration: 0.3 }}
397
610
  >
398
- {config.compoundMultipliers[config.compoundMultipliers.length - 1]}x
399
- </span>
611
+ <PointsIcon size={7} />
612
+ <span className="text-[9px] text-white font-semibold" style={OUTFIT}>{betAmount}</span>
613
+ <span className="text-[8px] text-white/40" style={OUTFIT}>&times;</span>
614
+ <span className="text-[9px] text-white/60 font-semibold" style={OUTFIT}>{option.odds}</span>
615
+ <span className="text-[8px] text-white/40" style={OUTFIT}>=</span>
616
+ <PointsIcon size={7} />
617
+ <span className="text-[9px] text-[#22E3E8] font-bold" style={OUTFIT}>{reward}</span>
618
+ </motion.div>
400
619
  </motion.div>
401
- <motion.p
402
- initial={{ opacity: 0, y: 16 }}
403
- animate={{ opacity: 1, y: 0 }}
404
- transition={{ duration: 0.5, delay: 3.0 }}
405
- className="text-[12px] text-white/40 font-medium"
406
- style={OUTFIT}
620
+ );
621
+
622
+ return (
623
+ <motion.div
624
+ className="flex flex-col items-center gap-3 w-full max-w-[300px]"
625
+ initial="initial"
626
+ animate="animate"
627
+ exit="exit"
407
628
  >
408
- max compound multiplier
409
- </motion.p>
410
- </motion.div>
411
- );
629
+ {/* Headline */}
630
+ {step >= 1 && (
631
+ <motion.p
632
+ initial={{ opacity: 0, y: 16 }}
633
+ animate={{ opacity: 1, y: 0 }}
634
+ transition={{ duration: 0.4 }}
635
+ className="text-[15px] text-white font-medium"
636
+ style={OUTFIT}
637
+ >
638
+ What if you combine them?
639
+ </motion.p>
640
+ )}
641
+
642
+ {/* Two compact result cards */}
643
+ {step >= 2 && (
644
+ <motion.div
645
+ initial={{ opacity: 0, y: 16 }}
646
+ animate={{ opacity: 1, y: 0 }}
647
+ transition={{ duration: 0.5 }}
648
+ className="w-full flex flex-col gap-2"
649
+ >
650
+ {renderCompactCard(marketA, IconA, optionA, betA, rewardA, step >= 3, step >= 3)}
651
+ {renderCompactCard(marketB, IconB, optionB, betB, rewardB, step >= 4, step >= 4)}
652
+ </motion.div>
653
+ )}
654
+
655
+ {/* Combined outcome */}
656
+ {step >= 5 && (
657
+ <motion.div
658
+ initial={{ opacity: 0, y: 10 }}
659
+ animate={{ opacity: 1, y: 0 }}
660
+ transition={{ duration: 0.4 }}
661
+ className="w-full flex flex-col items-center gap-1 pt-1"
662
+ >
663
+ <span className="text-[9px] text-white/40 uppercase tracking-widest font-semibold" style={OUTFIT}>Combined multiplier</span>
664
+ <div className="flex items-center gap-[4px]">
665
+ <PointsIcon size={8} />
666
+ <span className="text-[10px] text-white font-semibold" style={OUTFIT}>{combinedStake}</span>
667
+ <span className="text-[9px] text-white/40" style={OUTFIT}>&times;</span>
668
+ <motion.span
669
+ className="text-[10px] text-white font-bold"
670
+ style={OUTFIT}
671
+ animate={{ scale: [1, 1.25, 1] }}
672
+ transition={{ duration: 0.5, delay: 0.2 }}
673
+ >
674
+ {combinedOddsDisplay}
675
+ </motion.span>
676
+ <span className="text-[9px] text-white/40" style={OUTFIT}>=</span>
677
+ <PointsIcon size={8} />
678
+ <span className="text-[10px] text-[#22E3E8] font-bold" style={OUTFIT}>{combinedReward}</span>
679
+ </div>
680
+ </motion.div>
681
+ )}
682
+
683
+ {/* Payoff text */}
684
+ {step >= 6 && (
685
+ <motion.div
686
+ initial={{ opacity: 0, y: 10 }}
687
+ animate={{ opacity: 1, y: 0 }}
688
+ transition={{ duration: 0.4 }}
689
+ className="flex flex-col items-center gap-1 pt-1"
690
+ >
691
+ <p className="text-[14px] text-white/70 font-medium text-center" style={OUTFIT}>
692
+ Combine more predictions. Multiply further.
693
+ </p>
694
+ <p className="text-[12px] text-white/40 font-medium text-center" style={OUTFIT}>
695
+ Up to {maxMult}x with all {config.markets.length} questions
696
+ </p>
697
+ </motion.div>
698
+ )}
699
+ </motion.div>
700
+ );
701
+ };
412
702
 
413
703
  // Free leaderboard payouts: ranks 1-10, $25 down to $2.5 in $2.5 steps
414
704
  const FREE_PAYOUTS = Array.from({ length: 10 }, (_, i) => ({
@@ -543,8 +833,8 @@ export const PreMatchIntro = ({ config, onComplete }: { config: IChallengeConfig
543
833
  { component: LogoSlide, duration: 5500 },
544
834
  { component: EntrySlide, duration: 5000 },
545
835
  { component: PointsSlide, duration: 5500 },
546
- { component: MarketsSlide, duration: 7000 },
547
- { component: MultiplierSlide, duration: 5500 },
836
+ { component: MarketsSlide, duration: 12000 },
837
+ { component: MultiplierSlide, duration: 10000 },
548
838
  { component: WinSlide, duration: 5500 },
549
839
  ];
550
840
  return config.entryFee === 0 ? all.filter((_, i) => i !== 1) : all;