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