@devrongx/games 0.4.43 → 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.43",
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",
@@ -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,41 +249,127 @@ 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
- // ── Slide 3: Markets demo — two questions with full bet flow ──
255
+ // ── Slide 3: Markets demo — 3 questions, 2 selected one by one ──
256
256
  const MarketsSlide = ({ config }: SlideProps) => {
257
257
  const demo = useMemo(() => deriveDemoData(config), [config]);
258
258
  const [demoStep, setDemoStep] = useState(0);
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), 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
272
269
  ];
273
270
  return () => timers.forEach(clearTimeout);
274
271
  }, []);
275
272
 
276
- const { marketA, marketB, optIdxA, optIdxB, optionA, optionB, betA, betB, chips, rewardA, rewardB } = demo;
273
+ const { marketA, marketB, marketC, optIdxA, optIdxB, optionA, optionB, betA, betB, chips, rewardA, rewardB } = demo;
277
274
  const IconA = MARKET_ICONS[marketA.icon];
278
275
  const IconB = MARKET_ICONS[marketB.icon];
276
+ const IconC = MARKET_ICONS[marketC.icon];
277
+
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 (
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
+ );
279
317
 
280
- const card1Compressed = demoStep >= 6;
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)",
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
+ // 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
+ );
281
369
 
282
370
  return (
283
371
  <motion.div
284
- className="flex flex-col items-center gap-3 w-full max-w-[300px]"
372
+ className="flex flex-col items-center gap-2.5 w-full max-w-[300px]"
285
373
  initial="initial"
286
374
  animate="animate"
287
375
  exit="exit"
@@ -297,65 +385,38 @@ const MarketsSlide = ({ config }: SlideProps) => {
297
385
  {config.markets.length} questions to predict
298
386
  </motion.p>
299
387
 
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"
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"
308
399
  style={{
309
400
  background: "rgba(255,255,255,0.03)",
310
401
  border: "1px solid rgba(255,255,255,0.08)",
311
402
  }}
312
403
  >
313
- {/* Question header */}
314
- <div className="flex items-center gap-2 mb-2">
404
+ <div className="flex items-center gap-2 mb-1.5">
315
405
  {IconA && <IconA size={14} style={{ color: marketA.accent }} className="flex-shrink-0" />}
316
406
  <span className={`text-white font-semibold leading-tight ${card1Compressed ? "text-[11px]" : "text-[13px]"}`} style={OUTFIT}>
317
407
  {marketA.question}
318
408
  </span>
319
409
  </div>
320
410
 
321
- {/* Options grid or compressed selected tag */}
322
411
  {!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>
412
+ <>
413
+ {renderOptionsGrid(marketA, demoStep >= 2 ? optIdxA : null)}
414
+ <AnimatePresence>
415
+ {demoStep >= 3 && demoStep < 5 && renderChips(betA)}
416
+ </AnimatePresence>
417
+ </>
357
418
  ) : (
358
- <div className="flex items-center gap-1.5 mb-1">
419
+ <div className="flex items-center gap-1.5">
359
420
  <svg width="10" height="10" viewBox="0 0 16 16" fill="none">
360
421
  <circle cx="8" cy="8" r="8" fill="white" />
361
422
  <path d="M5 8l2 2 4-4" stroke="#9945FF" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
@@ -365,184 +426,53 @@ const MarketsSlide = ({ config }: SlideProps) => {
365
426
  </div>
366
427
  )}
367
428
 
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>
406
- )}
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
+ {demoStep >= 4 && renderOutcome(betA, optionA.odds, rewardA)}
430
+ </div>
429
431
 
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"
432
+ {/* Card 2 */}
433
+ <div
434
+ className="w-full rounded-lg px-3 py-2.5"
438
435
  style={{
439
436
  background: "rgba(255,255,255,0.03)",
440
437
  border: "1px solid rgba(255,255,255,0.08)",
441
438
  }}
442
439
  >
443
- <div className="flex items-center gap-2 mb-2">
440
+ <div className="flex items-center gap-2 mb-1.5">
444
441
  {IconB && <IconB size={14} style={{ color: marketB.accent }} className="flex-shrink-0" />}
445
442
  <span className="text-[13px] text-white font-semibold leading-tight" style={OUTFIT}>
446
443
  {marketB.question}
447
444
  </span>
448
445
  </div>
449
446
 
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>
447
+ {renderOptionsGrid(marketB, demoStep >= 5 ? optIdxB : null)}
484
448
 
485
- {/* Chip carousel — Card 2 */}
486
449
  <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
- )}
450
+ {demoStep >= 6 && renderChips(betB)}
524
451
  </AnimatePresence>
525
452
 
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>
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
+ )}
546
476
  </motion.div>
547
477
  );
548
478
  };
@@ -564,10 +494,33 @@ const MultiplierSlide = ({ config }: SlideProps) => {
564
494
  return () => timers.forEach(clearTimeout);
565
495
  }, []);
566
496
 
567
- const { marketA, marketB, optionA, optionB, betA, betB, rewardA, rewardB, combinedStake, combinedOdds, combinedReward, maxMult } = demo;
497
+ const { marketA, marketB, optionA, optionB, betA, betB, rewardA, rewardB, combinedStake, combinedOdds, combinedReward } = demo;
568
498
  const IconA = MARKET_ICONS[marketA.icon];
569
499
  const IconB = MARKET_ICONS[marketB.icon];
570
- const combinedOddsDisplay = Math.round(combinedOdds * 100) / 100;
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
+ );
571
524
 
572
525
  const renderCompactCard = (
573
526
  market: typeof marketA,
@@ -594,7 +547,7 @@ const MultiplierSlide = ({ config }: SlideProps) => {
594
547
  {Icon && <Icon size={12} style={{ color: market.accent }} className="flex-shrink-0" />}
595
548
  <span className="text-[11px] text-white font-semibold leading-tight truncate" style={OUTFIT}>{market.question}</span>
596
549
  </div>
597
- <div className="flex items-center gap-1.5 mb-1">
550
+ <div className="flex items-center gap-1.5 mb-1.5">
598
551
  <div
599
552
  className="flex items-center gap-1 px-2 py-[2px] rounded-sm"
600
553
  style={{ background: "linear-gradient(135deg, #22E3E8, #9945FF)" }}
@@ -604,17 +557,11 @@ const MultiplierSlide = ({ config }: SlideProps) => {
604
557
  <span className="text-[9px] text-white/50 font-semibold" style={OUTFIT}>at {option.odds}x</span>
605
558
  </div>
606
559
  <motion.div
607
- className="flex items-center gap-[3px] overflow-hidden"
560
+ className="overflow-hidden"
608
561
  animate={{ opacity: hideOutcome ? 0 : 1, height: hideOutcome ? 0 : "auto" }}
609
562
  transition={{ duration: 0.3 }}
610
563
  >
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>
564
+ {renderOutcomeLine(betAmount, String(option.odds), reward)}
618
565
  </motion.div>
619
566
  </motion.div>
620
567
  );
@@ -632,10 +579,10 @@ const MultiplierSlide = ({ config }: SlideProps) => {
632
579
  initial={{ opacity: 0, y: 16 }}
633
580
  animate={{ opacity: 1, y: 0 }}
634
581
  transition={{ duration: 0.4 }}
635
- className="text-[15px] text-white font-medium"
582
+ className="text-[15px] text-white font-semibold"
636
583
  style={OUTFIT}
637
584
  >
638
- What if you combine them?
585
+ Stack predictions. Multiply returns.
639
586
  </motion.p>
640
587
  )}
641
588
 
@@ -658,24 +605,26 @@ const MultiplierSlide = ({ config }: SlideProps) => {
658
605
  initial={{ opacity: 0, y: 10 }}
659
606
  animate={{ opacity: 1, y: 0 }}
660
607
  transition={{ duration: 0.4 }}
661
- className="w-full flex flex-col items-center gap-1 pt-1"
608
+ className="w-full flex flex-col items-center gap-2 pt-2"
662
609
  >
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>
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>
668
615
  <motion.span
669
- className="text-[10px] text-white font-bold"
616
+ className="text-[15px] text-white font-bold"
670
617
  style={OUTFIT}
671
618
  animate={{ scale: [1, 1.25, 1] }}
672
619
  transition={{ duration: 0.5, delay: 0.2 }}
673
620
  >
674
- {combinedOddsDisplay}
621
+ {optionA.odds}
675
622
  </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>
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>
679
628
  </div>
680
629
  </motion.div>
681
630
  )}
@@ -686,14 +635,11 @@ const MultiplierSlide = ({ config }: SlideProps) => {
686
635
  initial={{ opacity: 0, y: 10 }}
687
636
  animate={{ opacity: 1, y: 0 }}
688
637
  transition={{ duration: 0.4 }}
689
- className="flex flex-col items-center gap-1 pt-1"
638
+ className="pt-1"
690
639
  >
691
640
  <p className="text-[14px] text-white/70 font-medium text-center" style={OUTFIT}>
692
641
  Combine more predictions. Multiply further.
693
642
  </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
643
  </motion.div>
698
644
  )}
699
645
  </motion.div>