@devrongx/games 0.4.43 → 0.4.45

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.43",
3
+ "version": "0.4.45",
4
4
  "description": "Game UI components for sports prediction markets",
5
5
  "license": "MIT",
6
6
  "main": "./src/index.ts",
@@ -222,13 +222,15 @@ const deriveDemoData = (config: IChallengeConfig) => {
222
222
  const markets = config.markets;
223
223
  const len = markets.length;
224
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); }
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; }
229
230
 
230
231
  const marketA = markets[idxA];
231
232
  const marketB = markets[idxB];
233
+ const marketC = markets[idxC]; // unselected third question
232
234
 
233
235
  const optIdxA = Math.min(1, marketA.options.length - 1);
234
236
  const optIdxB = Math.min(2, marketB.options.length - 1);
@@ -247,9 +249,7 @@ const deriveDemoData = (config: IChallengeConfig) => {
247
249
  const combinedOdds = optionA.odds * optionB.odds;
248
250
  const combinedReward = Math.round(combinedStake * combinedOdds);
249
251
 
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 };
252
+ return { marketA, marketB, marketC, optIdxA, optIdxB, optionA, optionB, betA, betB, chips, rewardA, rewardB, combinedStake, combinedOdds, combinedReward };
253
253
  };
254
254
 
255
255
  // ── Slide 3: Markets demo — two questions with full bet flow ──
@@ -259,16 +259,16 @@ const MarketsSlide = ({ config }: SlideProps) => {
259
259
 
260
260
  useEffect(() => {
261
261
  const timers = [
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),
262
+ setTimeout(() => setDemoStep(1), 800), // Card 1 appears
263
+ setTimeout(() => setDemoStep(2), 2200), // Card 1 option selected
264
+ setTimeout(() => setDemoStep(3), 3200), // Card 1 chips appear (none selected)
265
+ setTimeout(() => setDemoStep(4), 4400), // Card 1 chip selected (1200ms gap)
266
+ setTimeout(() => setDemoStep(5), 5200), // Card 1 outcome, chips collapse
267
+ setTimeout(() => setDemoStep(6), 6200), // Card 2 appears
268
+ setTimeout(() => setDemoStep(7), 7400), // Card 2 option selected
269
+ setTimeout(() => setDemoStep(8), 8400), // Card 2 chips appear (none selected)
270
+ setTimeout(() => setDemoStep(9), 9600), // Card 2 chip selected (1200ms gap)
271
+ setTimeout(() => setDemoStep(10), 10400), // Card 2 outcome, chips collapse
272
272
  ];
273
273
  return () => timers.forEach(clearTimeout);
274
274
  }, []);
@@ -277,7 +277,95 @@ const MarketsSlide = ({ config }: SlideProps) => {
277
277
  const IconA = MARKET_ICONS[marketA.icon];
278
278
  const IconB = MARKET_ICONS[marketB.icon];
279
279
 
280
- const card1Compressed = demoStep >= 6;
280
+ // Shared option renderer
281
+ const renderOptionsGrid = (market: typeof marketA, selectedOptIdx: number | null) => (
282
+ <div className="grid grid-cols-2 gap-2">
283
+ {market.options.map((opt, j) => {
284
+ const isSelected = selectedOptIdx !== null && j === selectedOptIdx;
285
+ return (
286
+ <div
287
+ key={j}
288
+ className="flex items-center justify-between px-2 py-1.5 rounded-sm transition-all duration-500"
289
+ style={{
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)",
293
+ borderTop: "1px solid transparent",
294
+ borderRight: "1px solid transparent",
295
+ }}
296
+ >
297
+ <div className="flex items-center gap-1.5 min-w-0 flex-shrink">
298
+ {isSelected && (
299
+ <motion.svg
300
+ initial={{ scale: 0 }}
301
+ animate={{ scale: 1 }}
302
+ transition={{ type: "spring", stiffness: 400, damping: 15 }}
303
+ width="10" height="10" viewBox="0 0 16 16" fill="none"
304
+ >
305
+ <circle cx="8" cy="8" r="8" fill="white" />
306
+ <path d="M5 8l2 2 4-4" stroke="#9945FF" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
307
+ </motion.svg>
308
+ )}
309
+ <span className="text-[10px] text-white font-semibold truncate" style={OUTFIT}>{opt.label}</span>
310
+ </div>
311
+ <span className={`text-[10px] font-bold flex-shrink-0 ${isSelected ? "text-white" : "text-[#22E3E8]"}`} style={OUTFIT}>{opt.odds}x</span>
312
+ </div>
313
+ );
314
+ })}
315
+ </div>
316
+ );
317
+
318
+ // Chip carousel — selectedAmt of -1 means none selected
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 = selectedAmt > 0 && 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)",
342
+ }}
343
+ >
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
+ );
351
+
352
+ // Outcome line
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/50" 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
+ );
281
369
 
282
370
  return (
283
371
  <motion.div
@@ -297,7 +385,7 @@ const MarketsSlide = ({ config }: SlideProps) => {
297
385
  {config.markets.length} questions to predict
298
386
  </motion.p>
299
387
 
300
- {/* Card 1 */}
388
+ {/* Card 1 — always expanded */}
301
389
  <AnimatePresence>
302
390
  {demoStep >= 1 && (
303
391
  <motion.div
@@ -310,124 +398,25 @@ const MarketsSlide = ({ config }: SlideProps) => {
310
398
  border: "1px solid rgba(255,255,255,0.08)",
311
399
  }}
312
400
  >
313
- {/* Question header */}
314
401
  <div className="flex items-center gap-2 mb-2">
315
402
  {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}>
403
+ <span className="text-[13px] text-white font-semibold leading-tight" style={OUTFIT}>
317
404
  {marketA.question}
318
405
  </span>
319
406
  </div>
320
407
 
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">
360
- <circle cx="8" cy="8" r="8" fill="white" />
361
- <path d="M5 8l2 2 4-4" stroke="#9945FF" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
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
- )}
408
+ {renderOptionsGrid(marketA, demoStep >= 2 ? optIdxA : null)}
367
409
 
368
- {/* Chip carousel — Card 1 (hidden when compressed) */}
369
410
  <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>
406
- )}
411
+ {demoStep >= 3 && demoStep < 5 && renderChips(demoStep >= 4 ? betA : -1)}
407
412
  </AnimatePresence>
408
413
 
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
- )}
414
+ {demoStep >= 5 && renderOutcome(betA, optionA.odds, rewardA)}
426
415
  </motion.div>
427
416
  )}
428
417
  </AnimatePresence>
429
418
 
430
- {/* Card 2 */}
419
+ {/* Card 2 — always expanded */}
431
420
  <AnimatePresence>
432
421
  {demoStep >= 6 && (
433
422
  <motion.div
@@ -447,99 +436,13 @@ const MarketsSlide = ({ config }: SlideProps) => {
447
436
  </span>
448
437
  </div>
449
438
 
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>
439
+ {renderOptionsGrid(marketB, demoStep >= 7 ? optIdxB : null)}
484
440
 
485
- {/* Chip carousel — Card 2 */}
486
441
  <AnimatePresence>
487
- {demoStep >= 8 && (
488
- <motion.div
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"
494
- >
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
- })}
522
- </motion.div>
523
- )}
442
+ {demoStep >= 8 && demoStep < 10 && renderChips(demoStep >= 9 ? betB : -1)}
524
443
  </AnimatePresence>
525
444
 
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
- )}
445
+ {demoStep >= 10 && renderOutcome(betB, optionB.odds, rewardB)}
543
446
  </motion.div>
544
447
  )}
545
448
  </AnimatePresence>
@@ -547,34 +450,36 @@ const MarketsSlide = ({ config }: SlideProps) => {
547
450
  );
548
451
  };
549
452
 
550
- // ── Slide 4: Compound bet demo ──
453
+ // ── Slide 4: Compound bet demo — 3 full cards, 2 combined ──
551
454
  const MultiplierSlide = ({ config }: SlideProps) => {
552
455
  const demo = useMemo(() => deriveDemoData(config), [config]);
553
456
  const [step, setStep] = useState(0);
554
457
 
555
458
  useEffect(() => {
556
459
  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),
460
+ setTimeout(() => setStep(1), 300), // Headline
461
+ setTimeout(() => setStep(2), 1200), // 3 full cards appear
462
+ setTimeout(() => setStep(3), 3000), // Card 1 highlights, outcome fades out
463
+ setTimeout(() => setStep(4), 4200), // Card 2 highlights, outcome fades out
464
+ setTimeout(() => setStep(5), 5200), // Combined outcome line
465
+ setTimeout(() => setStep(6), 6500), // Payoff text
563
466
  ];
564
467
  return () => timers.forEach(clearTimeout);
565
468
  }, []);
566
469
 
567
- const { marketA, marketB, optionA, optionB, betA, betB, rewardA, rewardB, combinedStake, combinedOdds, combinedReward, maxMult } = demo;
470
+ const { marketA, marketB, marketC, optionA, optionB, betA, betB, rewardA, rewardB, combinedStake, combinedReward } = demo;
568
471
  const IconA = MARKET_ICONS[marketA.icon];
569
472
  const IconB = MARKET_ICONS[marketB.icon];
570
- const combinedOddsDisplay = Math.round(combinedOdds * 100) / 100;
473
+ const IconC = MARKET_ICONS[marketC.icon];
571
474
 
572
- const renderCompactCard = (
475
+ // Full expanded card with options grid — selected option highlighted
476
+ const renderFullCard = (
573
477
  market: typeof marketA,
574
478
  Icon: typeof IconA,
575
- option: typeof optionA,
576
- betAmount: number,
577
- reward: number,
479
+ selectedOptIdx: number | null,
480
+ betAmount: number | null,
481
+ odds: number | null,
482
+ reward: number | null,
578
483
  highlighted: boolean,
579
484
  hideOutcome: boolean,
580
485
  ) => (
@@ -590,38 +495,60 @@ const MultiplierSlide = ({ config }: SlideProps) => {
590
495
  border: "1px solid rgba(255,255,255,0.08)",
591
496
  }}
592
497
  >
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>
498
+ <div className="flex items-center gap-2 mb-1.5">
499
+ {Icon && <Icon size={14} style={{ color: market.accent }} className="flex-shrink-0" />}
500
+ <span className="text-[12px] text-white font-semibold leading-tight" style={OUTFIT}>{market.question}</span>
596
501
  </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>
502
+ <div className="grid grid-cols-2 gap-1.5">
503
+ {market.options.map((opt, j) => {
504
+ const isSelected = selectedOptIdx !== null && j === selectedOptIdx;
505
+ return (
506
+ <div
507
+ key={j}
508
+ className="flex items-center justify-between px-2 py-1 rounded-sm"
509
+ style={{
510
+ background: isSelected ? "linear-gradient(135deg, #22E3E8, #9945FF)" : "transparent",
511
+ borderLeft: isSelected ? "1px solid transparent" : "1px solid rgba(255,255,255,0.12)",
512
+ borderBottom: isSelected ? "1px solid transparent" : "1px solid rgba(255,255,255,0.06)",
513
+ borderTop: "1px solid transparent",
514
+ borderRight: "1px solid transparent",
515
+ }}
516
+ >
517
+ <div className="flex items-center gap-1 min-w-0 flex-shrink">
518
+ {isSelected && (
519
+ <svg width="8" height="8" viewBox="0 0 16 16" fill="none">
520
+ <circle cx="8" cy="8" r="8" fill="white" />
521
+ <path d="M5 8l2 2 4-4" stroke="#9945FF" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
522
+ </svg>
523
+ )}
524
+ <span className="text-[9px] text-white font-semibold truncate" style={OUTFIT}>{opt.label}</span>
525
+ </div>
526
+ <span className={`text-[9px] font-bold flex-shrink-0 ${isSelected ? "text-white" : "text-[#22E3E8]"}`} style={OUTFIT}>{opt.odds}x</span>
527
+ </div>
528
+ );
529
+ })}
605
530
  </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 }}
610
- >
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>
531
+ {betAmount !== null && odds !== null && reward !== null && (
532
+ <motion.div
533
+ className="flex items-center gap-[4px] mt-1.5 overflow-hidden"
534
+ animate={{ opacity: hideOutcome ? 0 : 1, height: hideOutcome ? 0 : "auto" }}
535
+ transition={{ duration: 0.3 }}
536
+ >
537
+ <PointsIcon size={8} />
538
+ <span className="text-[10px] text-white font-semibold" style={OUTFIT}>{betAmount}</span>
539
+ <span className="text-[9px] text-white" style={OUTFIT}>&times;</span>
540
+ <span className="text-[10px] text-white font-bold" style={OUTFIT}>{odds}</span>
541
+ <span className="text-[9px] text-white/50" style={OUTFIT}>{"\u2192"}</span>
542
+ <PointsIcon size={8} />
543
+ <span className="text-[10px] text-[#22E3E8] font-bold" style={OUTFIT}>{reward}</span>
544
+ </motion.div>
545
+ )}
619
546
  </motion.div>
620
547
  );
621
548
 
622
549
  return (
623
550
  <motion.div
624
- className="flex flex-col items-center gap-3 w-full max-w-[300px]"
551
+ className="flex flex-col items-center gap-2.5 w-full max-w-[300px]"
625
552
  initial="initial"
626
553
  animate="animate"
627
554
  exit="exit"
@@ -632,14 +559,14 @@ const MultiplierSlide = ({ config }: SlideProps) => {
632
559
  initial={{ opacity: 0, y: 16 }}
633
560
  animate={{ opacity: 1, y: 0 }}
634
561
  transition={{ duration: 0.4 }}
635
- className="text-[15px] text-white font-medium"
562
+ className="text-[15px] text-white font-semibold"
636
563
  style={OUTFIT}
637
564
  >
638
- What if you combine them?
565
+ Stack predictions. Multiply returns.
639
566
  </motion.p>
640
567
  )}
641
568
 
642
- {/* Two compact result cards */}
569
+ {/* 3 full expanded cards */}
643
570
  {step >= 2 && (
644
571
  <motion.div
645
572
  initial={{ opacity: 0, y: 16 }}
@@ -647,8 +574,39 @@ const MultiplierSlide = ({ config }: SlideProps) => {
647
574
  transition={{ duration: 0.5 }}
648
575
  className="w-full flex flex-col gap-2"
649
576
  >
650
- {renderCompactCard(marketA, IconA, optionA, betA, rewardA, step >= 3, step >= 3)}
651
- {renderCompactCard(marketB, IconB, optionB, betB, rewardB, step >= 4, step >= 4)}
577
+ {renderFullCard(marketA, IconA, demo.optIdxA, betA, optionA.odds, rewardA, step >= 3, step >= 3)}
578
+ {renderFullCard(marketB, IconB, demo.optIdxB, betB, optionB.odds, rewardB, step >= 4, step >= 4)}
579
+ {/* Card 3 — unselected, no bet */}
580
+ <div
581
+ className="w-full rounded-lg px-3 py-2.5"
582
+ style={{
583
+ background: "rgba(255,255,255,0.03)",
584
+ border: "1px solid rgba(255,255,255,0.08)",
585
+ opacity: 0.5,
586
+ }}
587
+ >
588
+ <div className="flex items-center gap-2 mb-1.5">
589
+ {IconC && <IconC size={14} style={{ color: marketC.accent }} className="flex-shrink-0" />}
590
+ <span className="text-[12px] text-white font-semibold leading-tight" style={OUTFIT}>{marketC.question}</span>
591
+ </div>
592
+ <div className="grid grid-cols-2 gap-1.5">
593
+ {marketC.options.map((opt, j) => (
594
+ <div
595
+ key={j}
596
+ className="flex items-center justify-between px-2 py-1 rounded-sm"
597
+ style={{
598
+ borderLeft: "1px solid rgba(255,255,255,0.12)",
599
+ borderBottom: "1px solid rgba(255,255,255,0.06)",
600
+ borderTop: "1px solid transparent",
601
+ borderRight: "1px solid transparent",
602
+ }}
603
+ >
604
+ <span className="text-[9px] text-white font-semibold truncate" style={OUTFIT}>{opt.label}</span>
605
+ <span className="text-[9px] font-bold text-[#22E3E8] flex-shrink-0" style={OUTFIT}>{opt.odds}x</span>
606
+ </div>
607
+ ))}
608
+ </div>
609
+ </div>
652
610
  </motion.div>
653
611
  )}
654
612
 
@@ -658,24 +616,26 @@ const MultiplierSlide = ({ config }: SlideProps) => {
658
616
  initial={{ opacity: 0, y: 10 }}
659
617
  animate={{ opacity: 1, y: 0 }}
660
618
  transition={{ duration: 0.4 }}
661
- className="w-full flex flex-col items-center gap-1 pt-1"
619
+ className="w-full flex flex-col items-center gap-2 pt-2"
662
620
  >
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>
621
+ <span className="text-[10px] text-white/40 uppercase tracking-widest font-semibold" style={OUTFIT}>Combined multiplier</span>
622
+ <div className="flex items-center gap-[6px]">
623
+ <PointsIcon size={12} />
624
+ <span className="text-[15px] text-white font-semibold" style={OUTFIT}>{combinedStake}</span>
625
+ <span className="text-[13px] text-white font-medium" style={OUTFIT}>&times;</span>
668
626
  <motion.span
669
- className="text-[10px] text-white font-bold"
627
+ className="text-[15px] text-white font-bold"
670
628
  style={OUTFIT}
671
629
  animate={{ scale: [1, 1.25, 1] }}
672
630
  transition={{ duration: 0.5, delay: 0.2 }}
673
631
  >
674
- {combinedOddsDisplay}
632
+ {optionA.odds}
675
633
  </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>
634
+ <span className="text-[13px] text-white font-medium" style={OUTFIT}>&times;</span>
635
+ <span className="text-[15px] text-white font-bold" style={OUTFIT}>{optionB.odds}</span>
636
+ <span className="text-[13px] text-white/50" style={OUTFIT}>{"\u2192"}</span>
637
+ <PointsIcon size={12} />
638
+ <span className="text-[15px] text-[#22E3E8] font-bold" style={OUTFIT}>{combinedReward}</span>
679
639
  </div>
680
640
  </motion.div>
681
641
  )}
@@ -686,14 +646,11 @@ const MultiplierSlide = ({ config }: SlideProps) => {
686
646
  initial={{ opacity: 0, y: 10 }}
687
647
  animate={{ opacity: 1, y: 0 }}
688
648
  transition={{ duration: 0.4 }}
689
- className="flex flex-col items-center gap-1 pt-1"
649
+ className="pt-1"
690
650
  >
691
651
  <p className="text-[14px] text-white/70 font-medium text-center" style={OUTFIT}>
692
652
  Combine more predictions. Multiply further.
693
653
  </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
654
  </motion.div>
698
655
  )}
699
656
  </motion.div>