@devrongx/games 0.4.42 → 0.4.44

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.42",
3
+ "version": "0.4.44",
4
4
  "description": "Game UI components for sports prediction markets",
5
5
  "license": "MIT",
6
6
  "main": "./src/index.ts",
@@ -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,64 +217,85 @@ 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, idxC: number;
226
+ if (len > 12) { idxA = 8; idxB = 12; idxC = 4; }
227
+ else if (len >= 3) { idxA = len - 3; idxB = len - 2; idxC = len - 1; }
228
+ else if (len >= 2) { idxA = 0; idxB = 1; idxC = 0; }
229
+ else { idxA = 0; idxB = 0; idxC = 0; }
230
+
231
+ const marketA = markets[idxA];
232
+ const marketB = markets[idxB];
233
+ const marketC = markets[idxC]; // unselected third question
234
+
235
+ const optIdxA = Math.min(1, marketA.options.length - 1);
236
+ const optIdxB = Math.min(2, marketB.options.length - 1);
237
+ const optionA = marketA.options[optIdxA];
238
+ const optionB = marketB.options[optIdxB];
239
+
240
+ const inc = config.parlayConfig.stakeIncrements;
241
+ const betA = inc * 2;
242
+ const betB = inc * 3;
243
+ const chips = [inc, inc * 2, inc * 3, inc * 4];
244
+
245
+ const rewardA = Math.round(betA * optionA.odds);
246
+ const rewardB = Math.round(betB * optionB.odds);
247
+
248
+ const combinedStake = betA + betB;
249
+ const combinedOdds = optionA.odds * optionB.odds;
250
+ const combinedReward = Math.round(combinedStake * combinedOdds);
251
+
252
+ return { marketA, marketB, marketC, optIdxA, optIdxB, optionA, optionB, betA, betB, chips, rewardA, rewardB, combinedStake, combinedOdds, combinedReward };
253
+ };
254
+
255
+ // ── Slide 3: Markets demo — 3 questions, 2 selected one by one ──
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), 600), // All 3 cards appear
263
+ setTimeout(() => setDemoStep(2), 2000), // Card 1 option selected
264
+ setTimeout(() => setDemoStep(3), 3000), // Card 1 chips + auto-select
265
+ setTimeout(() => setDemoStep(4), 3800), // Card 1 outcome line
266
+ setTimeout(() => setDemoStep(5), 5000), // Card 1 compresses, Card 2 option selected
267
+ setTimeout(() => setDemoStep(6), 6000), // Card 2 chips + auto-select
268
+ setTimeout(() => setDemoStep(7), 6800), // Card 2 outcome line
227
269
  ];
228
270
  return () => timers.forEach(clearTimeout);
229
271
  }, []);
230
272
 
231
- return (
232
- <motion.div
233
- className="flex flex-col items-center gap-5 w-full max-w-[300px]"
234
- initial="initial"
235
- animate="animate"
236
- exit="exit"
237
- >
238
- <motion.p
239
- initial={{ opacity: 0, y: 20 }}
240
- animate={{ opacity: 1, y: 0 }}
241
- transition={{ duration: 0.5, delay: 0.2 }}
242
- className="text-[14px] text-white font-medium text-center"
243
- style={OUTFIT}
244
- >
245
- {config.markets.length} markets to predict
246
- </motion.p>
273
+ const { marketA, marketB, marketC, optIdxA, optIdxB, optionA, optionB, betA, betB, chips, rewardA, rewardB } = demo;
274
+ const IconA = MARKET_ICONS[marketA.icon];
275
+ const IconB = MARKET_ICONS[marketB.icon];
276
+ const IconC = MARKET_ICONS[marketC.icon];
247
277
 
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">
278
+ const card1Compressed = demoStep >= 5;
279
+
280
+ // Shared option renderer for a full options grid
281
+ const renderOptionsGrid = (market: typeof marketA, selectedOptIdx: number | null) => (
282
+ <div className="grid grid-cols-2 gap-1.5">
283
+ {market.options.map((opt, j) => {
284
+ const isSelected = selectedOptIdx !== null && j === selectedOptIdx;
285
+ return (
265
286
  <div
266
- className="flex items-center justify-between px-2.5 py-2 rounded-sm transition-all duration-500"
287
+ key={j}
288
+ className="flex items-center justify-between px-2 py-1.5 rounded-sm transition-all duration-500"
267
289
  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)",
290
+ background: isSelected ? "linear-gradient(135deg, #22E3E8, #9945FF)" : "transparent",
291
+ borderLeft: isSelected ? "1px solid transparent" : "1px solid rgba(255,255,255,0.12)",
292
+ borderBottom: isSelected ? "1px solid transparent" : "1px solid rgba(255,255,255,0.06)",
271
293
  borderTop: "1px solid transparent",
272
294
  borderRight: "1px solid transparent",
273
295
  }}
274
296
  >
275
- <div className="flex items-center gap-1.5">
276
- {demoStep >= 1 && (
297
+ <div className="flex items-center gap-1.5 min-w-0 flex-shrink">
298
+ {isSelected && (
277
299
  <motion.svg
278
300
  initial={{ scale: 0 }}
279
301
  animate={{ scale: 1 }}
@@ -284,131 +306,345 @@ const MarketsSlide = ({ config }: SlideProps) => {
284
306
  <path d="M5 8l2 2 4-4" stroke="#9945FF" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
285
307
  </motion.svg>
286
308
  )}
287
- <span className="text-[11px] text-white font-semibold" style={OUTFIT}>{config.teamA.name}</span>
309
+ <span className="text-[10px] text-white font-semibold truncate" style={OUTFIT}>{opt.label}</span>
288
310
  </div>
289
- <span className={`text-[10px] font-bold ${demoStep >= 1 ? "text-white" : "text-[#22E3E8]"}`} style={OUTFIT}>
290
- 1.4x
291
- </span>
311
+ <span className={`text-[10px] font-bold flex-shrink-0 ${isSelected ? "text-white" : "text-[#22E3E8]"}`} style={OUTFIT}>{opt.odds}x</span>
292
312
  </div>
293
- <div
294
- className="flex items-center justify-between px-2.5 py-2 rounded-sm"
295
- 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",
313
+ );
314
+ })}
315
+ </div>
316
+ );
317
+
318
+ // Shared chip carousel renderer
319
+ const renderChips = (selectedAmt: number) => (
320
+ <motion.div
321
+ initial={{ opacity: 0, height: 0 }}
322
+ animate={{ opacity: 1, height: "auto" }}
323
+ exit={{ opacity: 0, height: 0 }}
324
+ transition={{ duration: 0.3 }}
325
+ className="flex items-center gap-1.5 overflow-hidden mt-2"
326
+ >
327
+ <span className="text-[8px] text-white/60 font-semibold flex-shrink-0" style={OUTFIT}>Bet:</span>
328
+ {chips.map((amt, i) => {
329
+ const isChipSelected = amt === selectedAmt;
330
+ return (
331
+ <motion.div
332
+ key={amt}
333
+ initial={{ opacity: 0, scale: 0.8 }}
334
+ animate={{ opacity: 1, scale: 1 }}
335
+ transition={{ delay: i * 0.08 }}
336
+ className="flex items-center gap-0.5 px-2 py-[3px] rounded relative overflow-hidden"
337
+ style={isChipSelected ? {
338
+ background: "linear-gradient(135deg, #22E3E8, #9945FF, #f83cc5)",
339
+ } : {
340
+ background: "rgba(34,227,232,0.08)",
341
+ border: "1px solid rgba(34,227,232,0.2)",
300
342
  }}
301
343
  >
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) => (
317
- <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
- }}
329
- >
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>
337
- </motion.div>
338
- ))}
339
- </motion.div>
340
- )}
341
- </AnimatePresence>
342
- </motion.div>
344
+ <PointsIcon size={7} />
345
+ <span className={`text-[9px] font-bold relative z-[1] ${isChipSelected ? "text-black" : "text-[#22E3E8]"}`} style={OUTFIT}>{amt}</span>
346
+ </motion.div>
347
+ );
348
+ })}
349
+ </motion.div>
350
+ );
343
351
 
352
+ // Shared outcome line renderer
353
+ const renderOutcome = (bet: number, odds: number, reward: number) => (
354
+ <motion.div
355
+ initial={{ opacity: 0, y: 4 }}
356
+ animate={{ opacity: 1, y: 0 }}
357
+ transition={{ duration: 0.3 }}
358
+ className="flex items-center gap-[3px] mt-1.5"
359
+ >
360
+ <PointsIcon size={7} />
361
+ <span className="text-[9px] text-white font-semibold" style={OUTFIT}>{bet}</span>
362
+ <span className="text-[8px] text-white" style={OUTFIT}>&times;</span>
363
+ <span className="text-[9px] text-white/60 font-semibold" style={OUTFIT}>{odds}</span>
364
+ <span className="text-[8px] text-white/40" style={OUTFIT}>{"\u2192"}</span>
365
+ <PointsIcon size={7} />
366
+ <span className="text-[9px] text-[#22E3E8] font-bold" style={OUTFIT}>{reward}</span>
367
+ </motion.div>
368
+ );
369
+
370
+ return (
371
+ <motion.div
372
+ className="flex flex-col items-center gap-2.5 w-full max-w-[300px]"
373
+ initial="initial"
374
+ animate="animate"
375
+ exit="exit"
376
+ >
377
+ {/* Header */}
344
378
  <motion.p
345
- initial={{ opacity: 0, y: 16 }}
379
+ initial={{ opacity: 0, y: 20 }}
346
380
  animate={{ opacity: 1, y: 0 }}
347
- transition={{ duration: 0.5, delay: 5.0 }}
348
- className="text-[12px] text-white/40 font-medium text-center"
381
+ transition={{ duration: 0.5 }}
382
+ className="text-[14px] text-white font-medium text-center"
349
383
  style={OUTFIT}
350
384
  >
351
- Toss, winner, top scorers, boundaries, wickets & more
385
+ {config.markets.length} questions to predict
352
386
  </motion.p>
387
+
388
+ {/* All 3 cards appear together */}
389
+ {demoStep >= 1 && (
390
+ <motion.div
391
+ initial={{ opacity: 0, y: 16 }}
392
+ animate={{ opacity: 1, y: 0 }}
393
+ transition={{ duration: 0.5 }}
394
+ className="w-full flex flex-col gap-2"
395
+ >
396
+ {/* Card 1 */}
397
+ <div
398
+ className="w-full rounded-lg px-3 py-2.5"
399
+ style={{
400
+ background: "rgba(255,255,255,0.03)",
401
+ border: "1px solid rgba(255,255,255,0.08)",
402
+ }}
403
+ >
404
+ <div className="flex items-center gap-2 mb-1.5">
405
+ {IconA && <IconA size={14} style={{ color: marketA.accent }} className="flex-shrink-0" />}
406
+ <span className={`text-white font-semibold leading-tight ${card1Compressed ? "text-[11px]" : "text-[13px]"}`} style={OUTFIT}>
407
+ {marketA.question}
408
+ </span>
409
+ </div>
410
+
411
+ {!card1Compressed ? (
412
+ <>
413
+ {renderOptionsGrid(marketA, demoStep >= 2 ? optIdxA : null)}
414
+ <AnimatePresence>
415
+ {demoStep >= 3 && demoStep < 5 && renderChips(betA)}
416
+ </AnimatePresence>
417
+ </>
418
+ ) : (
419
+ <div className="flex items-center gap-1.5">
420
+ <svg width="10" height="10" viewBox="0 0 16 16" fill="none">
421
+ <circle cx="8" cy="8" r="8" fill="white" />
422
+ <path d="M5 8l2 2 4-4" stroke="#9945FF" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
423
+ </svg>
424
+ <span className="text-[10px] text-white font-semibold" style={OUTFIT}>{optionA.label}</span>
425
+ <span className="text-[9px] text-white/40 font-semibold" style={OUTFIT}>{optionA.odds}x</span>
426
+ </div>
427
+ )}
428
+
429
+ {demoStep >= 4 && renderOutcome(betA, optionA.odds, rewardA)}
430
+ </div>
431
+
432
+ {/* Card 2 */}
433
+ <div
434
+ className="w-full rounded-lg px-3 py-2.5"
435
+ style={{
436
+ background: "rgba(255,255,255,0.03)",
437
+ border: "1px solid rgba(255,255,255,0.08)",
438
+ }}
439
+ >
440
+ <div className="flex items-center gap-2 mb-1.5">
441
+ {IconB && <IconB size={14} style={{ color: marketB.accent }} className="flex-shrink-0" />}
442
+ <span className="text-[13px] text-white font-semibold leading-tight" style={OUTFIT}>
443
+ {marketB.question}
444
+ </span>
445
+ </div>
446
+
447
+ {renderOptionsGrid(marketB, demoStep >= 5 ? optIdxB : null)}
448
+
449
+ <AnimatePresence>
450
+ {demoStep >= 6 && renderChips(betB)}
451
+ </AnimatePresence>
452
+
453
+ {demoStep >= 7 && renderOutcome(betB, optionB.odds, rewardB)}
454
+ </div>
455
+
456
+ {/* Card 3 — always unselected */}
457
+ <div
458
+ className="w-full rounded-lg px-3 py-2.5"
459
+ style={{
460
+ background: "rgba(255,255,255,0.03)",
461
+ border: "1px solid rgba(255,255,255,0.08)",
462
+ opacity: demoStep >= 2 ? 0.5 : 1,
463
+ transition: "opacity 0.5s",
464
+ }}
465
+ >
466
+ <div className="flex items-center gap-2 mb-1.5">
467
+ {IconC && <IconC size={14} style={{ color: marketC.accent }} className="flex-shrink-0" />}
468
+ <span className="text-[13px] text-white font-semibold leading-tight" style={OUTFIT}>
469
+ {marketC.question}
470
+ </span>
471
+ </div>
472
+ {renderOptionsGrid(marketC, null)}
473
+ </div>
474
+ </motion.div>
475
+ )}
353
476
  </motion.div>
354
477
  );
355
478
  };
356
479
 
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>
480
+ // ── Slide 4: Compound bet demo ──
481
+ const MultiplierSlide = ({ config }: SlideProps) => {
482
+ const demo = useMemo(() => deriveDemoData(config), [config]);
483
+ const [step, setStep] = useState(0);
484
+
485
+ useEffect(() => {
486
+ const timers = [
487
+ setTimeout(() => setStep(1), 300),
488
+ setTimeout(() => setStep(2), 1200),
489
+ setTimeout(() => setStep(3), 3000),
490
+ setTimeout(() => setStep(4), 4200),
491
+ setTimeout(() => setStep(5), 5200),
492
+ setTimeout(() => setStep(6), 6500),
493
+ ];
494
+ return () => timers.forEach(clearTimeout);
495
+ }, []);
496
+
497
+ const { marketA, marketB, optionA, optionB, betA, betB, rewardA, rewardB, combinedStake, combinedOdds, combinedReward } = demo;
498
+ const IconA = MARKET_ICONS[marketA.icon];
499
+ const IconB = MARKET_ICONS[marketB.icon];
500
+
501
+ // Shared outcome line — identical style for individual and combined
502
+ const renderOutcomeLine = (bet: number, odds: string, reward: number, pulse?: boolean) => (
503
+ <div className="flex items-center gap-[5px]">
504
+ <PointsIcon size={10} />
505
+ <span className="text-[12px] text-white font-semibold" style={OUTFIT}>{bet}</span>
506
+ <span className="text-[11px] text-white font-medium" style={OUTFIT}>&times;</span>
507
+ {pulse ? (
508
+ <motion.span
509
+ className="text-[12px] text-white font-bold"
510
+ style={OUTFIT}
511
+ animate={{ scale: [1, 1.25, 1] }}
512
+ transition={{ duration: 0.5, delay: 0.2 }}
513
+ >
514
+ {odds}
515
+ </motion.span>
516
+ ) : (
517
+ <span className="text-[12px] text-white font-bold" style={OUTFIT}>{odds}</span>
518
+ )}
519
+ <span className="text-[11px] text-white/50" style={OUTFIT}>{"\u2192"}</span>
520
+ <PointsIcon size={10} />
521
+ <span className="text-[12px] text-[#22E3E8] font-bold" style={OUTFIT}>{reward}</span>
522
+ </div>
523
+ );
524
+
525
+ const renderCompactCard = (
526
+ market: typeof marketA,
527
+ Icon: typeof IconA,
528
+ option: typeof optionA,
529
+ betAmount: number,
530
+ reward: number,
531
+ highlighted: boolean,
532
+ hideOutcome: boolean,
533
+ ) => (
383
534
  <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" }}
535
+ className="w-full rounded-lg px-3 py-2.5"
536
+ animate={{
537
+ borderColor: highlighted ? "rgba(34,227,232,0.4)" : "rgba(255,255,255,0.08)",
538
+ boxShadow: highlighted ? "0 0 12px rgba(34,227,232,0.15)" : "0 0 0px transparent",
539
+ }}
540
+ transition={{ duration: 0.4 }}
541
+ style={{
542
+ background: "rgba(255,255,255,0.03)",
543
+ border: "1px solid rgba(255,255,255,0.08)",
544
+ }}
387
545
  >
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
- }}
546
+ <div className="flex items-center gap-1.5 mb-1.5">
547
+ {Icon && <Icon size={12} style={{ color: market.accent }} className="flex-shrink-0" />}
548
+ <span className="text-[11px] text-white font-semibold leading-tight truncate" style={OUTFIT}>{market.question}</span>
549
+ </div>
550
+ <div className="flex items-center gap-1.5 mb-1.5">
551
+ <div
552
+ className="flex items-center gap-1 px-2 py-[2px] rounded-sm"
553
+ style={{ background: "linear-gradient(135deg, #22E3E8, #9945FF)" }}
554
+ >
555
+ <span className="text-[9px] text-white font-semibold" style={OUTFIT}>{option.label}</span>
556
+ </div>
557
+ <span className="text-[9px] text-white/50 font-semibold" style={OUTFIT}>at {option.odds}x</span>
558
+ </div>
559
+ <motion.div
560
+ className="overflow-hidden"
561
+ animate={{ opacity: hideOutcome ? 0 : 1, height: hideOutcome ? 0 : "auto" }}
562
+ transition={{ duration: 0.3 }}
397
563
  >
398
- {config.compoundMultipliers[config.compoundMultipliers.length - 1]}x
399
- </span>
564
+ {renderOutcomeLine(betAmount, String(option.odds), reward)}
565
+ </motion.div>
400
566
  </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}
567
+ );
568
+
569
+ return (
570
+ <motion.div
571
+ className="flex flex-col items-center gap-3 w-full max-w-[300px]"
572
+ initial="initial"
573
+ animate="animate"
574
+ exit="exit"
407
575
  >
408
- max compound multiplier
409
- </motion.p>
410
- </motion.div>
411
- );
576
+ {/* Headline */}
577
+ {step >= 1 && (
578
+ <motion.p
579
+ initial={{ opacity: 0, y: 16 }}
580
+ animate={{ opacity: 1, y: 0 }}
581
+ transition={{ duration: 0.4 }}
582
+ className="text-[15px] text-white font-semibold"
583
+ style={OUTFIT}
584
+ >
585
+ Stack predictions. Multiply returns.
586
+ </motion.p>
587
+ )}
588
+
589
+ {/* Two compact result cards */}
590
+ {step >= 2 && (
591
+ <motion.div
592
+ initial={{ opacity: 0, y: 16 }}
593
+ animate={{ opacity: 1, y: 0 }}
594
+ transition={{ duration: 0.5 }}
595
+ className="w-full flex flex-col gap-2"
596
+ >
597
+ {renderCompactCard(marketA, IconA, optionA, betA, rewardA, step >= 3, step >= 3)}
598
+ {renderCompactCard(marketB, IconB, optionB, betB, rewardB, step >= 4, step >= 4)}
599
+ </motion.div>
600
+ )}
601
+
602
+ {/* Combined outcome */}
603
+ {step >= 5 && (
604
+ <motion.div
605
+ initial={{ opacity: 0, y: 10 }}
606
+ animate={{ opacity: 1, y: 0 }}
607
+ transition={{ duration: 0.4 }}
608
+ className="w-full flex flex-col items-center gap-2 pt-2"
609
+ >
610
+ <span className="text-[10px] text-white/40 uppercase tracking-widest font-semibold" style={OUTFIT}>Combined multiplier</span>
611
+ <div className="flex items-center gap-[6px]">
612
+ <PointsIcon size={12} />
613
+ <span className="text-[15px] text-white font-semibold" style={OUTFIT}>{combinedStake}</span>
614
+ <span className="text-[13px] text-white font-medium" style={OUTFIT}>&times;</span>
615
+ <motion.span
616
+ className="text-[15px] text-white font-bold"
617
+ style={OUTFIT}
618
+ animate={{ scale: [1, 1.25, 1] }}
619
+ transition={{ duration: 0.5, delay: 0.2 }}
620
+ >
621
+ {optionA.odds}
622
+ </motion.span>
623
+ <span className="text-[13px] text-white font-medium" style={OUTFIT}>&times;</span>
624
+ <span className="text-[15px] text-white font-bold" style={OUTFIT}>{optionB.odds}</span>
625
+ <span className="text-[13px] text-white/50" style={OUTFIT}>{"\u2192"}</span>
626
+ <PointsIcon size={12} />
627
+ <span className="text-[15px] text-[#22E3E8] font-bold" style={OUTFIT}>{combinedReward}</span>
628
+ </div>
629
+ </motion.div>
630
+ )}
631
+
632
+ {/* Payoff text */}
633
+ {step >= 6 && (
634
+ <motion.div
635
+ initial={{ opacity: 0, y: 10 }}
636
+ animate={{ opacity: 1, y: 0 }}
637
+ transition={{ duration: 0.4 }}
638
+ className="pt-1"
639
+ >
640
+ <p className="text-[14px] text-white/70 font-medium text-center" style={OUTFIT}>
641
+ Combine more predictions. Multiply further.
642
+ </p>
643
+ </motion.div>
644
+ )}
645
+ </motion.div>
646
+ );
647
+ };
412
648
 
413
649
  // Free leaderboard payouts: ranks 1-10, $25 down to $2.5 in $2.5 steps
414
650
  const FREE_PAYOUTS = Array.from({ length: 10 }, (_, i) => ({
@@ -543,8 +779,8 @@ export const PreMatchIntro = ({ config, onComplete }: { config: IChallengeConfig
543
779
  { component: LogoSlide, duration: 5500 },
544
780
  { component: EntrySlide, duration: 5000 },
545
781
  { component: PointsSlide, duration: 5500 },
546
- { component: MarketsSlide, duration: 7000 },
547
- { component: MultiplierSlide, duration: 5500 },
782
+ { component: MarketsSlide, duration: 12000 },
783
+ { component: MultiplierSlide, duration: 10000 },
548
784
  { component: WinSlide, duration: 5500 },
549
785
  ];
550
786
  return config.entryFee === 0 ? all.filter((_, i) => i !== 1) : all;