@devrongx/games 0.4.3 → 0.4.4

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.3",
3
+ "version": "0.4.4",
4
4
  "description": "Game UI components for sports prediction markets",
5
5
  "license": "MIT",
6
6
  "main": "./src/index.ts",
@@ -2,7 +2,7 @@
2
2
  "use client";
3
3
 
4
4
  import { useRef, useMemo, useEffect } from "react";
5
- import { motion } from "framer-motion";
5
+ import { motion, AnimatePresence } from "framer-motion";
6
6
  import { Calendar, MapPin, Loader2, Trophy, Zap, Target, Users } from "lucide-react";
7
7
  import { useTDMatches } from "./useTDMatches";
8
8
  import { useTDPools } from "../pools/hooks";
@@ -114,26 +114,38 @@ const GAME_TYPE_STYLE: Record<number, { icon: typeof Target; color: string; bg:
114
114
 
115
115
  function PoolPill({ pool, onPress }: { pool: ITDPool; onPress: (pool: ITDPool) => void }) {
116
116
  const style = GAME_TYPE_STYLE[pool.game_type] ?? { icon: Target, color: "#22E3E8", bg: "rgba(34,227,232,0.06)" };
117
- const Icon = style.icon;
118
117
  const isClosed = pool.status === TDPoolStatus.CLOSED || pool.status === TDPoolStatus.RESOLVING || pool.status === TDPoolStatus.COMPLETE;
118
+ const isOpen = pool.status === TDPoolStatus.OPEN;
119
119
 
120
120
  return (
121
- <button
121
+ <motion.button
122
122
  onClick={() => onPress(pool)}
123
- className="flex items-center gap-1 px-1.5 py-0.5 rounded-md flex-shrink-0"
123
+ whileTap={{ scale: 0.93 }}
124
+ whileHover={{ scale: 1.04 }}
125
+ transition={{ type: "spring", stiffness: 500, damping: 25 }}
126
+ className="flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg flex-shrink-0"
124
127
  style={{
125
- background: style.bg,
126
- border: `1px solid ${style.color}20`,
127
- opacity: isClosed ? 0.5 : 1,
128
+ background: isOpen ? style.bg.replace("0.06", "0.10") : style.bg,
129
+ border: `1px solid ${style.color}${isOpen ? "50" : "25"}`,
130
+ opacity: isClosed ? 0.45 : 1,
131
+ boxShadow: isOpen ? `0 0 8px ${style.color}18` : undefined,
128
132
  }}
129
133
  >
130
- <span className="text-[7px] font-bold" style={{ ...OUTFIT, color: style.color }}>
134
+ <span className="text-[10px] font-bold" style={{ ...OUTFIT, color: "#fff" }}>
131
135
  {pool.display_price}
132
136
  </span>
133
137
  {pool.entry_count > 0 && (
134
- <span className="text-[6px] text-white/30" style={OUTFIT}>·{pool.entry_count > 999 ? `${(pool.entry_count / 1000).toFixed(1)}k` : pool.entry_count}</span>
138
+ <span className="text-[8px] font-medium" style={{ ...OUTFIT, color: "rgba(255,255,255,0.65)" }}>
139
+ {pool.entry_count > 999 ? `${(pool.entry_count / 1000).toFixed(1)}k` : pool.entry_count}
140
+ </span>
141
+ )}
142
+ {isOpen && (
143
+ <span className="relative flex h-1.5 w-1.5 shrink-0">
144
+ <span className="animate-ping absolute inline-flex h-full w-full rounded-full opacity-60" style={{ backgroundColor: style.color }} />
145
+ <span className="relative inline-flex rounded-full h-1.5 w-1.5" style={{ backgroundColor: style.color }} />
146
+ </span>
135
147
  )}
136
- </button>
148
+ </motion.button>
137
149
  );
138
150
  }
139
151
 
@@ -164,21 +176,28 @@ function MatchGameSection({
164
176
  if (gameTypes.length === 0) return null;
165
177
 
166
178
  return (
167
- <div className="flex flex-col gap-1 mt-1 pt-1.5" style={{ borderTop: "1px solid rgba(255,255,255,0.06)" }}>
179
+ <div className="flex flex-col gap-1.5 mt-1.5 pt-2" style={{ borderTop: "1px solid rgba(255,255,255,0.08)" }}>
168
180
  {gameTypes.map((gt) => {
169
181
  const style = GAME_TYPE_STYLE[gt];
170
182
  const Icon = style.icon;
171
183
  return (
172
- <div key={gt} className="flex flex-col gap-0.5">
173
- <div className="flex items-center gap-1 px-1">
174
- <Icon size={8} style={{ color: style.color }} />
175
- <span className="text-[7px] barlowcondensedBold tracking-wide text-white/60">
184
+ <div key={gt} className="flex flex-col gap-1">
185
+ <div className="flex items-center gap-1 px-0.5">
186
+ <Icon size={9} style={{ color: style.color }} />
187
+ <span className="text-[8px] barlowcondensedBold tracking-wide" style={{ color: "rgba(255,255,255,0.85)" }}>
176
188
  {style.label}
177
189
  </span>
178
190
  </div>
179
- <div className="flex gap-1 flex-wrap">
180
- {grouped[gt].map((pool) => (
181
- <PoolPill key={pool.id} pool={pool} onPress={onPoolPress ?? (() => {})} />
191
+ <div className="flex gap-1.5 flex-wrap">
192
+ {grouped[gt].map((pool, idx) => (
193
+ <motion.div
194
+ key={pool.id}
195
+ initial={{ opacity: 0, y: 6 }}
196
+ animate={{ opacity: 1, y: 0 }}
197
+ transition={{ delay: idx * 0.04, duration: 0.25, ease: "easeOut" }}
198
+ >
199
+ <PoolPill pool={pool} onPress={onPoolPress ?? (() => {})} />
200
+ </motion.div>
182
201
  ))}
183
202
  </div>
184
203
  </div>
@@ -225,19 +244,23 @@ function MatchCard({ match, onPoolPress, partnerSource }: { match: ITDMatch; onP
225
244
  />
226
245
  )}
227
246
 
228
- <div
229
- className="relative flex flex-col gap-1.5 rounded-xl px-3 py-2.5"
247
+ <motion.div
248
+ className="relative flex flex-col gap-2 rounded-xl px-3.5 py-3"
230
249
  style={{
231
- background: isLive ? "rgba(10,10,18,0.95)" : "rgba(10,10,18,0.98)",
250
+ background: isLive
251
+ ? "linear-gradient(160deg, rgba(14,10,24,0.98) 0%, rgba(10,8,20,0.98) 100%)"
252
+ : "rgba(10,10,18,0.98)",
232
253
  }}
254
+ whileHover={{ backgroundColor: "rgba(18,18,28,0.98)" }}
255
+ transition={{ duration: 0.2 }}
233
256
  >
234
257
  {/* Time + live indicator */}
235
- <div className="flex items-center justify-between">
258
+ <div className="flex items-center justify-between gap-1">
236
259
  <span
237
- className="text-[10px]"
260
+ className="text-[11px] font-semibold"
238
261
  style={{
239
262
  ...OUTFIT,
240
- color: isLive ? "#f83cc5" : isCompleted ? "rgba(255,255,255,0.35)" : "#fff",
263
+ color: isLive ? "#f83cc5" : isCompleted ? "rgba(255,255,255,0.45)" : "#fff",
241
264
  }}
242
265
  >
243
266
  {time}
@@ -254,26 +277,26 @@ function MatchCard({ match, onPoolPress, partnerSource }: { match: ITDMatch; onP
254
277
  </div>
255
278
  )}
256
279
  {isCompleted && match.winner && (
257
- <span className="text-[8px]" style={{ ...OUTFIT, color: "rgba(255,255,255,0.35)" }}>
280
+ <span className="text-[8px]" style={{ ...OUTFIT, color: "rgba(255,255,255,0.5)" }}>
258
281
  {match.winner.short_name || match.winner.name} won
259
282
  </span>
260
283
  )}
261
284
  </div>
262
285
 
263
286
  {/* Teams */}
264
- <div className="flex items-center gap-1.5 justify-center">
287
+ <div className="flex items-center gap-2 justify-center">
265
288
  <span
266
- className="barlowcondensedBold text-[20px] leading-none"
267
- style={{ color: isCompleted ? "rgba(255,255,255,0.35)" : "#fff" }}
289
+ className="barlowcondensedBold text-[24px] leading-none tracking-wide"
290
+ style={{ color: isCompleted ? "rgba(255,255,255,0.4)" : "#fff" }}
268
291
  >
269
292
  {getTeamShortName(match, "a")}
270
293
  </span>
271
- <span className="text-[9px]" style={{ color: "rgba(255,255,255,0.3)" }}>
272
- v
294
+ <span className="text-[10px] font-semibold" style={{ ...OUTFIT, color: "rgba(255,255,255,0.5)" }}>
295
+ vs
273
296
  </span>
274
297
  <span
275
- className="barlowcondensedBold text-[20px] leading-none"
276
- style={{ color: isCompleted ? "rgba(255,255,255,0.35)" : "#fff" }}
298
+ className="barlowcondensedBold text-[24px] leading-none tracking-wide"
299
+ style={{ color: isCompleted ? "rgba(255,255,255,0.4)" : "#fff" }}
277
300
  >
278
301
  {getTeamShortName(match, "b")}
279
302
  </span>
@@ -282,10 +305,10 @@ function MatchCard({ match, onPoolPress, partnerSource }: { match: ITDMatch; onP
282
305
  {/* Venue */}
283
306
  {venue && (
284
307
  <div className="flex items-center gap-1 justify-center">
285
- <MapPin size={8} style={{ color: "rgba(255,255,255,0.3)" }} />
308
+ <MapPin size={9} style={{ color: "rgba(255,255,255,0.55)" }} />
286
309
  <span
287
- className="text-[8px] truncate max-w-[100px]"
288
- style={{ ...OUTFIT, color: "rgba(255,255,255,0.3)" }}
310
+ className="text-[9px] truncate max-w-[130px]"
311
+ style={{ ...OUTFIT, color: "rgba(255,255,255,0.7)" }}
289
312
  >
290
313
  {venue}
291
314
  </span>
@@ -298,9 +321,9 @@ function MatchCard({ match, onPoolPress, partnerSource }: { match: ITDMatch; onP
298
321
  {Array.from({ length: Math.min(Math.ceil(match.rating_popularity / 2), 5) }).map((_, i) => (
299
322
  <Zap
300
323
  key={i}
301
- size={7}
324
+ size={8}
302
325
  fill={isLive ? "#f83cc5" : "#22E3E8"}
303
- style={{ color: isLive ? "#f83cc5" : "#22E3E8", opacity: 0.4 + i * 0.15 }}
326
+ style={{ color: isLive ? "#f83cc5" : "#22E3E8", opacity: 0.45 + i * 0.13 }}
304
327
  />
305
328
  ))}
306
329
  </div>
@@ -313,7 +336,7 @@ function MatchCard({ match, onPoolPress, partnerSource }: { match: ITDMatch; onP
313
336
  onPoolPress={onPoolPress}
314
337
  partnerSource={partnerSource}
315
338
  />
316
- </div>
339
+ </motion.div>
317
340
  </div>
318
341
  );
319
342
  }
@@ -329,32 +352,32 @@ function DayColumn({ day, index, onPoolPress, partnerSource }: { day: IMatchDay;
329
352
  <motion.div
330
353
  initial={{ opacity: 0, y: 10 }}
331
354
  animate={{ opacity: 1, y: 0 }}
332
- transition={{ duration: 0.3, delay: index * 0.03 }}
333
- className="shrink-0 flex flex-col gap-1.5"
334
- style={{ scrollSnapAlign: "start", width: "140px" }}
355
+ transition={{ duration: 0.35, delay: index * 0.04, ease: [0.25, 0.46, 0.45, 0.94] }}
356
+ className="shrink-0 flex flex-col gap-2"
357
+ style={{ scrollSnapAlign: "start", width: "180px" }}
335
358
  >
336
359
  {/* Day header */}
337
- <div className="flex flex-col items-center gap-0.5 pb-1">
360
+ <div className="flex flex-col items-center gap-0.5 pb-1.5">
338
361
  <span
339
- className="text-[10px] barlowcondensedBold tracking-widest"
362
+ className="text-[11px] barlowcondensedBold tracking-widest"
340
363
  style={{
341
- color: hasLive ? "#f83cc5" : hasToday ? "#22E3E8" : allCompleted ? "rgba(255,255,255,0.25)" : "#fff",
364
+ color: hasLive ? "#f83cc5" : hasToday ? "#22E3E8" : allCompleted ? "rgba(255,255,255,0.35)" : "rgba(255,255,255,0.9)",
342
365
  }}
343
366
  >
344
367
  {day.label}
345
368
  </span>
346
369
  <span
347
- className="text-[12px] font-semibold"
370
+ className="text-[13px] font-semibold"
348
371
  style={{
349
372
  ...OUTFIT,
350
- color: hasLive ? "#f83cc5" : hasToday ? "#22E3E8" : allCompleted ? "rgba(255,255,255,0.25)" : "#fff",
373
+ color: hasLive ? "#f83cc5" : hasToday ? "#22E3E8" : allCompleted ? "rgba(255,255,255,0.35)" : "#fff",
351
374
  }}
352
375
  >
353
376
  {day.dayMonth}
354
377
  </span>
355
378
  {(hasToday || hasLive) && (
356
379
  <div
357
- className="w-5 h-[2px] rounded-full mt-0.5"
380
+ className="w-6 h-[2px] rounded-full mt-0.5"
358
381
  style={{ background: hasLive ? "#f83cc5" : "#22E3E8" }}
359
382
  />
360
383
  )}
@@ -467,18 +490,18 @@ export function MatchCalendar({ tournamentId, onPoolPress, partnerSource }: Matc
467
490
  )}
468
491
 
469
492
  {/* Section header */}
470
- <div className="flex items-center gap-2 px-5 mb-2.5">
471
- <Calendar size={12} style={{ color: "#22E3E8" }} />
472
- <span className="barlowCondensedSemiBold text-[13px] tracking-wider text-white">
493
+ <div className="flex items-center gap-2 px-5 mb-3">
494
+ <Calendar size={13} style={{ color: "#22E3E8" }} />
495
+ <span className="barlowCondensedSemiBold text-[14px] tracking-wider text-white">
473
496
  SCHEDULE
474
497
  </span>
475
- <div className="flex-1 h-px" style={{ background: "rgba(255,255,255,0.06)" }} />
498
+ <div className="flex-1 h-px" style={{ background: "rgba(255,255,255,0.08)" }} />
476
499
  </div>
477
500
 
478
501
  {/* Scrollable day columns */}
479
502
  <div
480
503
  ref={scrollRef}
481
- className="flex gap-2.5 overflow-x-auto px-5 pb-3 scrollbar-hide"
504
+ className="flex gap-3 overflow-x-auto px-5 pb-4 scrollbar-hide"
482
505
  style={{ scrollSnapType: "x mandatory", WebkitOverflowScrolling: "touch" }}
483
506
  >
484
507
  {days.map((day, i) => (