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