@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
|
@@ -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 >=
|
|
228
|
-
else { idxA = 0; idxB =
|
|
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
|
-
|
|
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),
|
|
266
|
-
setTimeout(() => setDemoStep(5),
|
|
267
|
-
setTimeout(() => setDemoStep(6),
|
|
268
|
-
setTimeout(() => setDemoStep(7),
|
|
269
|
-
setTimeout(() => setDemoStep(8),
|
|
270
|
-
setTimeout(() => setDemoStep(9),
|
|
271
|
-
setTimeout(() => setDemoStep(10),
|
|
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
|
-
|
|
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}>×</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=
|
|
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
|
-
{
|
|
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 &&
|
|
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
|
-
{
|
|
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}>×</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
|
-
|
|
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
|
-
{
|
|
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}>×</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,
|
|
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
|
|
473
|
+
const IconC = MARKET_ICONS[marketC.icon];
|
|
571
474
|
|
|
572
|
-
|
|
475
|
+
// Full expanded card with options grid — selected option highlighted
|
|
476
|
+
const renderFullCard = (
|
|
573
477
|
market: typeof marketA,
|
|
574
478
|
Icon: typeof IconA,
|
|
575
|
-
|
|
576
|
-
betAmount: number,
|
|
577
|
-
|
|
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-
|
|
594
|
-
{Icon && <Icon size={
|
|
595
|
-
<span className="text-[
|
|
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="
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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}>×</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-
|
|
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-
|
|
562
|
+
className="text-[15px] text-white font-semibold"
|
|
636
563
|
style={OUTFIT}
|
|
637
564
|
>
|
|
638
|
-
|
|
565
|
+
Stack predictions. Multiply returns.
|
|
639
566
|
</motion.p>
|
|
640
567
|
)}
|
|
641
568
|
|
|
642
|
-
{/*
|
|
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
|
-
{
|
|
651
|
-
{
|
|
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-
|
|
619
|
+
className="w-full flex flex-col items-center gap-2 pt-2"
|
|
662
620
|
>
|
|
663
|
-
<span className="text-[
|
|
664
|
-
<div className="flex items-center gap-[
|
|
665
|
-
<PointsIcon size={
|
|
666
|
-
<span className="text-[
|
|
667
|
-
<span className="text-[
|
|
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}>×</span>
|
|
668
626
|
<motion.span
|
|
669
|
-
className="text-[
|
|
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
|
-
{
|
|
632
|
+
{optionA.odds}
|
|
675
633
|
</motion.span>
|
|
676
|
-
<span className="text-[
|
|
677
|
-
<
|
|
678
|
-
<span className="text-[
|
|
634
|
+
<span className="text-[13px] text-white font-medium" style={OUTFIT}>×</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="
|
|
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>
|