@vatvaghool/create-ipl-dashboard 0.1.0

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.
Files changed (108) hide show
  1. package/README.md +75 -0
  2. package/package.json +27 -0
  3. package/src/generate-template.mjs +73 -0
  4. package/src/index.mjs +98 -0
  5. package/src/prompts.mjs +78 -0
  6. package/src/scaffold.mjs +129 -0
  7. package/src/scraper.mjs +79 -0
  8. package/template/.dockerignore +13 -0
  9. package/template/AGENTS.md +5 -0
  10. package/template/Dockerfile.sync +14 -0
  11. package/template/README.md +160 -0
  12. package/template/app/api/ipl/data.ts +24 -0
  13. package/template/app/api/ipl/route.ts +505 -0
  14. package/template/app/api/ipl/transfers/route.ts +261 -0
  15. package/template/app/api/ipl/transfers/transform.ts +156 -0
  16. package/template/app/api/ipl/transform.ts +20 -0
  17. package/template/app/api/ipl/upcoming-matches/route.ts +18 -0
  18. package/template/app/api/ops/status/route.ts +225 -0
  19. package/template/app/components/AIRoasting.tsx +278 -0
  20. package/template/app/components/ColorWave.tsx +193 -0
  21. package/template/app/components/CrownBattle.tsx +207 -0
  22. package/template/app/components/DashboardContent.tsx +377 -0
  23. package/template/app/components/FantasyStockTicker.tsx +192 -0
  24. package/template/app/components/FireworksBurst.tsx +225 -0
  25. package/template/app/components/LiveMatchTicker.tsx +117 -0
  26. package/template/app/components/MatchRecapScroll.tsx +135 -0
  27. package/template/app/components/MatchStoryScrubber.tsx +274 -0
  28. package/template/app/components/PerformanceTracker.tsx +132 -0
  29. package/template/app/components/ProgressGlowRings.tsx +157 -0
  30. package/template/app/components/TeamDNAScanner.tsx +238 -0
  31. package/template/app/components/ThemeToggle.tsx +74 -0
  32. package/template/app/components/dashboard/CaptainBoard.tsx +138 -0
  33. package/template/app/components/dashboard/ChartBoard.tsx +162 -0
  34. package/template/app/components/dashboard/LatestBadge.tsx +23 -0
  35. package/template/app/components/dashboard/LedgerTable.tsx +385 -0
  36. package/template/app/components/dashboard/SectionCard.tsx +59 -0
  37. package/template/app/components/dashboard/StickyMini.tsx +20 -0
  38. package/template/app/components/dashboard/index.ts +6 -0
  39. package/template/app/components/ui/DashboardChartFrame.tsx +74 -0
  40. package/template/app/components/ui/DoodleSpinner.tsx +15 -0
  41. package/template/app/components/ui/TeamPills.tsx +41 -0
  42. package/template/app/data/match-points.ts +3 -0
  43. package/template/app/data/teams.ts +32 -0
  44. package/template/app/globals.css +1267 -0
  45. package/template/app/hooks/dashboard/index.ts +1 -0
  46. package/template/app/hooks/dashboard/useDashboardModel.ts +25 -0
  47. package/template/app/hooks/dashboardCache.ts +53 -0
  48. package/template/app/hooks/dashboardPolling.ts +53 -0
  49. package/template/app/hooks/snapshotCache.ts +47 -0
  50. package/template/app/hooks/useDashboardData.ts +28 -0
  51. package/template/app/layout.tsx +75 -0
  52. package/template/app/lib/aiAgent.ts +444 -0
  53. package/template/app/lib/config.ts +29 -0
  54. package/template/app/lib/dashboard/index.ts +1 -0
  55. package/template/app/lib/dashboard/model.ts +257 -0
  56. package/template/app/lib/dashboardData.ts +50 -0
  57. package/template/app/lib/dashboardView.ts +22 -0
  58. package/template/app/lib/detailedData.ts +112 -0
  59. package/template/app/lib/matchStatus.ts +28 -0
  60. package/template/app/lib/matches.ts +131 -0
  61. package/template/app/lib/teamBadges.ts +223 -0
  62. package/template/app/lib/upcomingMatches.ts +154 -0
  63. package/template/app/lib/useDb.ts +29 -0
  64. package/template/app/lib/utils/diff.ts +24 -0
  65. package/template/app/lib/utils/getChartColor.ts +17 -0
  66. package/template/app/lib/utils/getStdDeviation.ts +6 -0
  67. package/template/app/lib/utils/time.ts +40 -0
  68. package/template/app/lib/utils.ts +70 -0
  69. package/template/app/page.tsx +15 -0
  70. package/template/app/store/dashboardStore.ts +85 -0
  71. package/template/app/types/dashboard.ts +75 -0
  72. package/template/app/types.ts +130 -0
  73. package/template/app/utils/dashboard/index.ts +72 -0
  74. package/template/eslint.config.mjs +18 -0
  75. package/template/infra/cloud-run/README.md +68 -0
  76. package/template/infra/cloud-run/sync-job.yaml +32 -0
  77. package/template/infra/cutover/README.md +84 -0
  78. package/template/infra/vercel/README.md +57 -0
  79. package/template/next.config.ts +7 -0
  80. package/template/package-lock.json +7330 -0
  81. package/template/package.json +47 -0
  82. package/template/packages/ipl-dashboard-utils/README.md +316 -0
  83. package/template/packages/ipl-dashboard-utils/package.json +34 -0
  84. package/template/packages/ipl-dashboard-utils/src/index.ts +22 -0
  85. package/template/packages/ipl-dashboard-utils/src/transform.ts +687 -0
  86. package/template/packages/ipl-dashboard-utils/src/types.ts +88 -0
  87. package/template/packages/ipl-dashboard-utils/tsconfig.build.json +17 -0
  88. package/template/postcss.config.mjs +7 -0
  89. package/template/scripts/capture-ipl-auth.mjs +54 -0
  90. package/template/scripts/deploy-cloud-run-sync.sh +48 -0
  91. package/template/scripts/deploy-cloud-scheduler.sh +42 -0
  92. package/template/scripts/dev-simple.js +31 -0
  93. package/template/scripts/dev-welcome.mjs +38 -0
  94. package/template/scripts/monitor-ops-status.sh +50 -0
  95. package/template/scripts/seed-mongodb.ts +115 -0
  96. package/template/scripts/sync-cloud.mjs +50 -0
  97. package/template/scripts/sync-ipl.mjs +238 -0
  98. package/template/scripts/sync-transfers-daily.mjs +175 -0
  99. package/template/scripts/verify-production.mjs +108 -0
  100. package/template/tests/coverage-gaps.test.ts +290 -0
  101. package/template/tests/dashboard-polling.test.ts +96 -0
  102. package/template/tests/detailed-data.test.ts +60 -0
  103. package/template/tests/ipl-transform.test.ts +590 -0
  104. package/template/tests/transfers-route.test.ts +109 -0
  105. package/template/tests/upcoming-matches.test.ts +34 -0
  106. package/template/tests/utils-and-cache.test.ts +267 -0
  107. package/template/tsconfig.json +35 -0
  108. package/template/vercel.json +7 -0
@@ -0,0 +1,385 @@
1
+ import { motion } from "framer-motion";
2
+ import {
3
+ FaArrowDown,
4
+ FaArrowTrendUp,
5
+ FaCircle,
6
+ FaMinus,
7
+ FaTrophy,
8
+ } from "react-icons/fa6";
9
+ import { buildDetailedTableRows } from "../../lib/detailedData";
10
+ import {
11
+ getTeamColor,
12
+ getBoosterCount,
13
+ formatLedgerNumber,
14
+ } from "../../utils/dashboard";
15
+ import { LatestBadge } from "./LatestBadge";
16
+
17
+ interface LedgerTableProps {
18
+ rows: ReturnType<typeof buildDetailedTableRows>;
19
+ }
20
+
21
+ export function LedgerTable({ rows }: LedgerTableProps) {
22
+ if (!rows.length) {
23
+ return <p className="ledger-note py-6">No table rows available yet.</p>;
24
+ }
25
+
26
+ const highestLatest = rows.reduce(
27
+ (max, row) => Math.max(max, row.latestPoints ?? 0),
28
+ 0,
29
+ );
30
+
31
+ return (
32
+ <div
33
+ className="ledger-shell"
34
+ style={{
35
+ borderColor: "color-mix(in srgb, var(--accent-cyan) 30%, var(--line))",
36
+ background:
37
+ "linear-gradient(180deg, color-mix(in srgb, var(--accent-cyan) 7%, var(--panel-strong) 93%), color-mix(in srgb, var(--panel) 92%, transparent))",
38
+ }}
39
+ >
40
+ <table className="ledger-table">
41
+ <thead>
42
+ <tr>
43
+ <th>Rank</th>
44
+ <th>Team</th>
45
+ <th>Overall</th>
46
+ <th>Gap</th>
47
+ <th>Latest</th>
48
+ <th>Moves</th>
49
+ <th>Transfers Left</th>
50
+ <th>Booster</th>
51
+ <th>Eff</th>
52
+ </tr>
53
+ </thead>
54
+ <tbody>
55
+ {rows.map((row, index) => (
56
+ <TableRow
57
+ key={row.name}
58
+ row={row}
59
+ index={index}
60
+ highestLatest={highestLatest}
61
+ />
62
+ ))}
63
+ </tbody>
64
+ </table>
65
+ </div>
66
+ );
67
+ }
68
+
69
+ interface TableRowProps {
70
+ row: ReturnType<typeof buildDetailedTableRows>[0];
71
+ index: number;
72
+ highestLatest: number;
73
+ }
74
+
75
+ function TableRow({ row, index, highestLatest }: TableRowProps) {
76
+ const teamColor = getTeamColor(index, 8); // Assuming 8 teams
77
+ const shift = getRankShift(row);
78
+ const shiftAmount = getShiftAmount(row);
79
+ const boosterCount = getBoosterCount(row.boostersUsed);
80
+ const isTopLatest =
81
+ row.latestPoints !== undefined &&
82
+ row.latestPoints === highestLatest &&
83
+ highestLatest > 0;
84
+
85
+ return (
86
+ <motion.tr
87
+ initial={{ opacity: 0, x: -8 }}
88
+ animate={{ opacity: 1, x: 0 }}
89
+ transition={{ delay: index * 0.04 }}
90
+ className="relative"
91
+ style={{
92
+ background:
93
+ "color-mix(in srgb, var(--paper) 76%, var(--panel-strong) 24%)",
94
+ }}
95
+ >
96
+ <RankCell row={row} teamColor={teamColor} index={index} />
97
+ <TeamCell row={row} />
98
+ <OverallCell row={row} />
99
+ <GapCell row={row} />
100
+ <LatestCell row={row} isTop={isTopLatest} />
101
+ <MovesCell shift={shift} shiftAmount={shiftAmount} />
102
+ <TransfersCell row={row} />
103
+ <BoosterCell boosterCount={boosterCount} />
104
+ <EfficiencyCell row={row} />
105
+ </motion.tr>
106
+ );
107
+ }
108
+
109
+ function RankCell({
110
+ row,
111
+ teamColor,
112
+ index,
113
+ }: {
114
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
115
+ row: any;
116
+ teamColor: string;
117
+ index: number;
118
+ }) {
119
+ return (
120
+ <td className="relative">
121
+ <motion.div
122
+ className="absolute inset-0 rounded-lg pointer-events-none"
123
+ style={{
124
+ background: `radial-gradient(circle at 50% 50%, color-mix(in srgb, ${teamColor} 14%, transparent) 0%, transparent 70%)`,
125
+ }}
126
+ animate={{ opacity: [0.2, 0.5, 0.2] }}
127
+ transition={{
128
+ repeat: Infinity,
129
+ duration: 2.5 + (index % 3),
130
+ ease: "easeInOut",
131
+ delay: index * 0.1,
132
+ }}
133
+ />
134
+ <motion.div
135
+ className="absolute -inset-px rounded-lg pointer-events-none"
136
+ style={{
137
+ border: `1.5px solid ${teamColor}`,
138
+ opacity: 0.3,
139
+ }}
140
+ animate={{
141
+ opacity: [0.15, 0.45, 0.15],
142
+ filter: ["blur(2px)", "blur(5px)", "blur(2px)"],
143
+ }}
144
+ transition={{
145
+ repeat: Infinity,
146
+ duration: 2,
147
+ ease: "easeInOut",
148
+ delay: index * 0.1,
149
+ }}
150
+ />
151
+ <span
152
+ className="relative z-10 inline-flex items-center gap-2 rounded-full border px-3 py-1 text-[13px] font-black"
153
+ style={{
154
+ borderColor: `color-mix(in srgb, ${teamColor} 38%, var(--line))`,
155
+ background: `color-mix(in srgb, ${teamColor} 16%, var(--panel-strong) 84%)`,
156
+ boxShadow: `0 0 12px color-mix(in srgb, ${teamColor} 20%, transparent)`,
157
+ }}
158
+ >
159
+ {row.rank === 1 ? (
160
+ <FaTrophy style={{ color: "var(--marker-yellow)" }} />
161
+ ) : row.rank === 2 ? (
162
+ <FaTrophy style={{ color: "var(--marker-blue)" }} />
163
+ ) : row.rank === 3 ? (
164
+ <FaTrophy style={{ color: "var(--marker-orange)" }} />
165
+ ) : null}
166
+ <span>#{row.rank}</span>
167
+ </span>
168
+ </td>
169
+ );
170
+ }
171
+
172
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
173
+ function TeamCell({ row }: { row: any }) {
174
+ return (
175
+ <td>
176
+ <div className="flex items-center gap-2">
177
+ <span className="font-semibold">{row.name}</span>
178
+ </div>
179
+ </td>
180
+ );
181
+ }
182
+
183
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
184
+ function OverallCell({ row }: { row: any }) {
185
+ return <td className="font-semibold">{formatLedgerNumber(row.points)}</td>;
186
+ }
187
+
188
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
189
+ function GapCell({ row }: { row: any }) {
190
+ return (
191
+ <td>
192
+ <span
193
+ className="inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-[13px] font-black"
194
+ style={{
195
+ background:
196
+ typeof row.gapToNext === "number" && row.gapToNext > 0
197
+ ? "color-mix(in srgb, var(--marker-yellow) 18%, var(--panel-strong))"
198
+ : "transparent",
199
+ color: "var(--ink)",
200
+ }}
201
+ >
202
+ {typeof row.gapToNext === "number" && row.gapToNext > 0
203
+ ? `+${formatLedgerNumber(row.gapToNext)}`
204
+ : "-"}
205
+ </span>
206
+ </td>
207
+ );
208
+ }
209
+
210
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
211
+ function LatestCell({ row, isTop }: { row: any; isTop: boolean }) {
212
+ return (
213
+ <td>
214
+ <LatestBadge pts={row.latestPoints ?? 0} isTop={isTop} />
215
+ </td>
216
+ );
217
+ }
218
+
219
+ function MovesCell({
220
+ shift,
221
+ shiftAmount,
222
+ }: {
223
+ shift: string;
224
+ shiftAmount: number;
225
+ }) {
226
+ return (
227
+ <td>
228
+ {shift === "up" ? (
229
+ <span
230
+ className="inline-flex items-center gap-1 rounded-full border px-2.5 py-1 text-[13px] font-black"
231
+ style={{
232
+ borderColor:
233
+ "color-mix(in srgb, var(--marker-green) 40%, var(--line))",
234
+ background:
235
+ "color-mix(in srgb, var(--marker-green) 16%, var(--panel-strong))",
236
+ color: "var(--ink)",
237
+ }}
238
+ >
239
+ <FaArrowTrendUp />
240
+ <span>+{shiftAmount}</span>
241
+ </span>
242
+ ) : shift === "down" ? (
243
+ <span
244
+ className="inline-flex items-center gap-1 rounded-full border px-2.5 py-1 text-[13px] font-black"
245
+ style={{
246
+ borderColor:
247
+ "color-mix(in srgb, var(--marker-pink) 40%, var(--line))",
248
+ background:
249
+ "color-mix(in srgb, var(--marker-pink) 16%, var(--panel-strong))",
250
+ color: "var(--ink)",
251
+ }}
252
+ >
253
+ <FaArrowDown />
254
+ <span>-{shiftAmount}</span>
255
+ </span>
256
+ ) : shift === "same" ? (
257
+ <span
258
+ className="inline-flex items-center gap-1 rounded-full border px-2.5 py-1 text-[13px] font-black"
259
+ style={{
260
+ borderColor:
261
+ "color-mix(in srgb, var(--marker-blue) 40%, var(--line))",
262
+ background:
263
+ "color-mix(in srgb, var(--marker-blue) 16%, var(--panel-strong))",
264
+ color: "var(--ink)",
265
+ }}
266
+ >
267
+ <FaMinus />
268
+ <span>same</span>
269
+ </span>
270
+ ) : (
271
+ <span className="text-(--ink-soft)">-</span>
272
+ )}
273
+ </td>
274
+ );
275
+ }
276
+
277
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
278
+ function TransfersCell({ row }: { row: any }) {
279
+ return (
280
+ <td>
281
+ {typeof row.transfersLeft === "number" ? (
282
+ <span
283
+ className="inline-flex items-center gap-1 rounded-full border px-2.5 py-1 text-[13px] font-bold"
284
+ style={{
285
+ borderColor:
286
+ "color-mix(in srgb, var(--accent-lime) 28%, var(--line))",
287
+ background:
288
+ "color-mix(in srgb, var(--accent-lime) 16%, var(--panel-strong))",
289
+ color: "var(--ink)",
290
+ }}
291
+ >
292
+ <FaCircle
293
+ className="text-[0.45rem]"
294
+ style={{ color: "var(--marker-green)" }}
295
+ />
296
+ <span>{row.transfersLeft}</span>
297
+ </span>
298
+ ) : (
299
+ "-"
300
+ )}
301
+ </td>
302
+ );
303
+ }
304
+
305
+ function BoosterCell({ boosterCount }: { boosterCount: number }) {
306
+ return (
307
+ <td>
308
+ {boosterCount > 0 ? (
309
+ <span
310
+ className="inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-[13px] font-bold text-(--ink)"
311
+ style={{
312
+ borderColor:
313
+ "color-mix(in srgb, var(--accent-magenta) 28%, var(--line))",
314
+ background:
315
+ "color-mix(in srgb, var(--accent-magenta) 16%, var(--panel-strong) 84%)",
316
+ }}
317
+ >
318
+ <span className="flex items-center">
319
+ {Array.from({ length: boosterCount }).map((_, dotIndex) => (
320
+ <FaCircle
321
+ key={`booster-${dotIndex}`}
322
+ className="text-[0.42rem] -ml-0.5 first:ml-0"
323
+ style={{
324
+ color: getTeamColor(dotIndex, 8),
325
+ }}
326
+ />
327
+ ))}
328
+ </span>
329
+ <span>{boosterCount}</span>
330
+ </span>
331
+ ) : (
332
+ <span className="inline-flex items-center text-(--ink-soft)">
333
+ <FaCircle className="text-[0.38rem] -ml-0.5 first:ml-0 text-slate-400" />
334
+ <FaCircle className="text-[0.38rem] -ml-0.5 text-slate-400" />
335
+ <FaCircle className="text-[0.38rem] -ml-0.5 text-slate-400" />
336
+ </span>
337
+ )}
338
+ </td>
339
+ );
340
+ }
341
+
342
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
343
+ function EfficiencyCell({ row }: { row: any }) {
344
+ return (
345
+ <td>
346
+ <span
347
+ className="inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-[13px] font-black"
348
+ style={{
349
+ background:
350
+ typeof row.efficiency === "number" && row.efficiency >= 8
351
+ ? "color-mix(in srgb, var(--marker-green) 20%, var(--panel-strong))"
352
+ : typeof row.efficiency === "number" && row.efficiency >= 6
353
+ ? "color-mix(in srgb, var(--marker-yellow) 20%, var(--panel-strong))"
354
+ : "color-mix(in srgb, var(--marker-pink) 20%, var(--panel-strong))",
355
+ color:
356
+ typeof row.efficiency === "number" && row.efficiency >= 8
357
+ ? "var(--ink)"
358
+ : typeof row.efficiency === "number" && row.efficiency >= 6
359
+ ? "var(--ink)"
360
+ : "var(--ink)",
361
+ }}
362
+ >
363
+ <FaCircle className="text-[0.38rem]" />
364
+ <span>
365
+ {typeof row.efficiency === "number" ? row.efficiency.toFixed(1) : "-"}
366
+ </span>
367
+ </span>
368
+ </td>
369
+ );
370
+ }
371
+
372
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
373
+ function getRankShift(row: any): string {
374
+ if (typeof row.previousRank !== "number") return "none";
375
+ if (row.previousRank > row.rank) return "up";
376
+ if (row.previousRank < row.rank) return "down";
377
+ return "same";
378
+ }
379
+
380
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
381
+ function getShiftAmount(row: any): number {
382
+ return typeof row.previousRank === "number"
383
+ ? Math.abs(row.previousRank - row.rank)
384
+ : 0;
385
+ }
@@ -0,0 +1,59 @@
1
+ import { motion } from "framer-motion";
2
+ import type { SectionCardProps } from "../../types/dashboard";
3
+
4
+ export function SectionCard({
5
+ title,
6
+ note,
7
+ children,
8
+ className = "",
9
+ index,
10
+ icon: Icon,
11
+ accent = "var(--accent-cyan)",
12
+ }: SectionCardProps) {
13
+ return (
14
+ <motion.section
15
+ className={`paper-card section-card ${className}`}
16
+ initial={{ opacity: 0, y: 22, rotate: index % 2 === 0 ? -0.5 : 0.7 }}
17
+ whileInView={{ opacity: 1, y: 0, rotate: 0 }}
18
+ viewport={{ once: true, margin: "-60px" }}
19
+ transition={{ duration: 0.45, delay: index * 0.04 }}
20
+ >
21
+ <div className="section-tape" aria-hidden="true" />
22
+ <div className="section-head">
23
+ <div className="flex items-start gap-3">
24
+ <span
25
+ className="inline-flex h-11 w-11 items-center justify-center rounded-full border shadow-[0_10px_20px_var(--shadow)]"
26
+ style={{
27
+ color: "var(--ink)",
28
+ borderColor: `color-mix(in srgb, ${accent} 34%, var(--border))`,
29
+ background: `radial-gradient(circle at 30% 30%, color-mix(in srgb, ${accent} 40%, var(--panel-strong) 60%), color-mix(in srgb, ${accent} 82%, transparent))`,
30
+ }}
31
+ >
32
+ <Icon className="text-lg" />
33
+ </span>
34
+ <div>
35
+ <h2 className="section-title">{title}</h2>
36
+ <p className="ledger-note">{note}</p>
37
+ </div>
38
+ </div>
39
+ <span
40
+ className="pencil-mark inline-flex items-center gap-1 rounded-full px-2 py-1"
41
+ style={{
42
+ background: `color-mix(in srgb, ${accent} 16%, transparent)`,
43
+ color: accent,
44
+ }}
45
+ aria-hidden="true"
46
+ >
47
+ <FaBolt className="text-[0.7rem]" />
48
+ <span className="text-[12px] font-black uppercase tracking-[0.2em]">
49
+ spotlight
50
+ </span>
51
+ </span>
52
+ </div>
53
+ {children}
54
+ </motion.section>
55
+ );
56
+ }
57
+
58
+ // Import required icon
59
+ import { FaBolt } from "react-icons/fa6";
@@ -0,0 +1,20 @@
1
+ import type { StickyMiniProps } from "../../types/dashboard";
2
+
3
+ export function StickyMini({
4
+ title,
5
+ value,
6
+ note,
7
+ variant,
8
+ icon: Icon,
9
+ }: StickyMiniProps) {
10
+ return (
11
+ <div className={`sticky-mini sticky-mini-${variant} hover-jitter`}>
12
+ <p className="sticky-mini-title flex items-center gap-2">
13
+ <Icon className="text-sm" />
14
+ <span>{title}</span>
15
+ </p>
16
+ <p className="sticky-mini-value">{value}</p>
17
+ <p className="sticky-mini-note">{note}</p>
18
+ </div>
19
+ );
20
+ }
@@ -0,0 +1,6 @@
1
+ export { SectionCard } from "./SectionCard";
2
+ export { StickyMini } from "./StickyMini";
3
+ export { ChartBoard } from "./ChartBoard";
4
+ export { CaptainBoard } from "./CaptainBoard";
5
+ export { LedgerTable } from "./LedgerTable";
6
+ export { LatestBadge } from "./LatestBadge";
@@ -0,0 +1,74 @@
1
+ "use client";
2
+
3
+ import type { ReactNode } from "react";
4
+
5
+ export function DashboardChartFrame({
6
+ children,
7
+ chartHeight,
8
+ }: {
9
+ children: ReactNode;
10
+ chartHeight: number;
11
+ isMobile: boolean;
12
+ }) {
13
+ return (
14
+ <div
15
+ className="min-w-0 p-2.5 sm:p-3 hover-jitter relative overflow-hidden"
16
+ style={{
17
+ height: chartHeight,
18
+ width: "100%",
19
+ margin: "0",
20
+ border:
21
+ "2px solid color-mix(in srgb, var(--accent-cyan) 24%, var(--line))",
22
+ borderRadius: "24px 26px 18px 22px",
23
+ background:
24
+ "linear-gradient(180deg, color-mix(in srgb, var(--paper) 92%, var(--panel-strong) 8%), color-mix(in srgb, var(--paper-2) 84%, var(--panel) 16%))",
25
+ boxShadow:
26
+ "inset 0 0 0 1px color-mix(in srgb, var(--chalk) 30%, transparent), 0 16px 30px color-mix(in srgb, var(--shadow) 20%, transparent)",
27
+ position: "relative",
28
+ }}
29
+ >
30
+ {/* Notebook margin line */}
31
+ <div
32
+ className="absolute left-3 top-0 bottom-0 pointer-events-none"
33
+ style={{
34
+ width: "1px",
35
+ background:
36
+ "linear-gradient(180deg, color-mix(in srgb, var(--marker-pink) 40%, transparent) 0%, transparent 100%)",
37
+ opacity: 0.3,
38
+ zIndex: 1,
39
+ }}
40
+ />
41
+
42
+ {/* Coffee stain doodle */}
43
+ <div
44
+ className="absolute pointer-events-none wobbly"
45
+ style={{
46
+ top: "6%",
47
+ right: "4%",
48
+ width: 28,
49
+ height: 16,
50
+ borderRadius: "50%",
51
+ background:
52
+ "color-mix(in srgb, var(--marker-yellow) 18%, transparent)",
53
+ zIndex: 1,
54
+ }}
55
+ />
56
+
57
+ {/* Corner fold doodle - top right */}
58
+ <div
59
+ className="absolute top-0 right-0 pointer-events-none wobbly"
60
+ style={{
61
+ width: 0,
62
+ height: 0,
63
+ borderStyle: "solid",
64
+ borderWidth: "0 16px 16px 0",
65
+ borderColor:
66
+ "transparent color-mix(in srgb, var(--marker-yellow) 30%, transparent) transparent transparent",
67
+ zIndex: 2,
68
+ }}
69
+ />
70
+
71
+ {children}
72
+ </div>
73
+ );
74
+ }
@@ -0,0 +1,15 @@
1
+ "use client";
2
+
3
+ export function DoodleSpinner() {
4
+ return (
5
+ <svg className="doodle-spinner" viewBox="0 0 48 48">
6
+ <circle cx="24" cy="24" r="20" />
7
+ <circle cx="24" cy="24" r="14" strokeDasharray="60" />
8
+ <circle cx="24" cy="24" r="8" strokeDasharray="30" />
9
+ {/* hand-drawn center dot */}
10
+ <circle cx="24" cy="24" r="3" fill="var(--ink-soft)" opacity="0.4" />
11
+ </svg>
12
+ );
13
+ }
14
+
15
+
@@ -0,0 +1,41 @@
1
+ "use client";
2
+
3
+ type TeamPill = {
4
+ label: string;
5
+ fill: string;
6
+ };
7
+
8
+ export default function TeamPills({
9
+ items,
10
+ max = 4,
11
+ }: {
12
+ items: TeamPill[];
13
+ max?: number;
14
+ }) {
15
+ return (
16
+ <div className="flex flex-wrap gap-2">
17
+ {items.slice(0, max).map((item, i) => (
18
+ <span
19
+ key={item.label}
20
+ className="inline-flex items-center gap-2 border px-3 py-1 text-[12px] font-black uppercase tracking-[0.16em]"
21
+ style={{
22
+ color: "var(--ink)",
23
+ border: "1.5px solid",
24
+ borderRadius: `999px ${920 + i * 28}px ${980 - i * 24}px 999px`,
25
+ borderColor: `color-mix(in srgb, ${item.fill} 40%, var(--line))`,
26
+ background: `color-mix(in srgb, ${item.fill} 18%, var(--panel-strong))`,
27
+ boxShadow: `2px 3px 0 color-mix(in srgb, ${item.fill} 28%, var(--shadow))`,
28
+ transform: `rotate(${i % 2 === 0 ? -0.8 + i * 0.4 : 0.6 - i * 0.3}deg)`,
29
+ fontFamily: "var(--font-note), cursive",
30
+ }}
31
+ >
32
+ <span
33
+ className="h-2.5 w-2.5 rounded-full"
34
+ style={{ background: item.fill }}
35
+ />
36
+ <span>{item.label}</span>
37
+ </span>
38
+ ))}
39
+ </div>
40
+ );
41
+ }
@@ -0,0 +1,3 @@
1
+ export const MATCH_POINTS: { teamId: number; matchId: number; points: number }[] = [
2
+ {"teamId":1,"matchId":1,"points":727},{"teamId":1,"matchId":2,"points":229},{"teamId":1,"matchId":3,"points":163.5},{"teamId":1,"matchId":4,"points":120.5},{"teamId":1,"matchId":5,"points":137.5},{"teamId":1,"matchId":6,"points":472},{"teamId":1,"matchId":7,"points":120},{"teamId":1,"matchId":8,"points":107},{"teamId":1,"matchId":9,"points":498},{"teamId":1,"matchId":10,"points":225.5},{"teamId":1,"matchId":11,"points":323.5},{"teamId":1,"matchId":12,"points":65},{"teamId":1,"matchId":13,"points":309.5},{"teamId":1,"matchId":14,"points":268},{"teamId":1,"matchId":15,"points":168},{"teamId":1,"matchId":16,"points":442},{"teamId":1,"matchId":17,"points":407},{"teamId":1,"matchId":18,"points":360},{"teamId":1,"matchId":19,"points":239},{"teamId":1,"matchId":20,"points":368.5},{"teamId":1,"matchId":21,"points":324.5},{"teamId":1,"matchId":22,"points":159},{"teamId":1,"matchId":23,"points":467},{"teamId":1,"matchId":24,"points":95},{"teamId":1,"matchId":25,"points":275},{"teamId":1,"matchId":26,"points":273.5},{"teamId":1,"matchId":27,"points":312.5},{"teamId":1,"matchId":28,"points":334},{"teamId":1,"matchId":29,"points":558.5},{"teamId":1,"matchId":30,"points":540},{"teamId":1,"matchId":31,"points":565},{"teamId":1,"matchId":32,"points":157},{"teamId":1,"matchId":33,"points":403.5},{"teamId":1,"matchId":34,"points":214.5},{"teamId":1,"matchId":35,"points":736.5},{"teamId":1,"matchId":36,"points":461},{"teamId":1,"matchId":37,"points":308.5},{"teamId":1,"matchId":38,"points":101.5},{"teamId":1,"matchId":39,"points":101},{"teamId":1,"matchId":40,"points":345},{"teamId":1,"matchId":41,"points":471.5},{"teamId":1,"matchId":42,"points":249},{"teamId":1,"matchId":43,"points":282.5},{"teamId":1,"matchId":44,"points":102.5},{"teamId":1,"matchId":45,"points":203},{"teamId":1,"matchId":46,"points":210},{"teamId":1,"matchId":47,"points":889},{"teamId":1,"matchId":48,"points":269},{"teamId":1,"matchId":49,"points":250.5},{"teamId":1,"matchId":50,"points":461},{"teamId":2,"matchId":1,"points":535},{"teamId":2,"matchId":2,"points":337.5},{"teamId":2,"matchId":3,"points":165.5},{"teamId":2,"matchId":4,"points":196.5},{"teamId":2,"matchId":5,"points":108.5},{"teamId":2,"matchId":6,"points":141},{"teamId":2,"matchId":7,"points":307},{"teamId":2,"matchId":8,"points":257.5},{"teamId":2,"matchId":9,"points":307},{"teamId":2,"matchId":10,"points":238.5},{"teamId":2,"matchId":11,"points":302.5},{"teamId":2,"matchId":12,"points":65},{"teamId":2,"matchId":13,"points":241},{"teamId":2,"matchId":14,"points":198.5},{"teamId":2,"matchId":15,"points":168},{"teamId":2,"matchId":16,"points":352.5},{"teamId":2,"matchId":17,"points":109},{"teamId":2,"matchId":18,"points":332},{"teamId":2,"matchId":19,"points":282.5},{"teamId":2,"matchId":20,"points":212.5},{"teamId":2,"matchId":21,"points":322.5},{"teamId":2,"matchId":22,"points":305},{"teamId":2,"matchId":23,"points":218},{"teamId":2,"matchId":24,"points":244.5},{"teamId":2,"matchId":25,"points":271.5},{"teamId":2,"matchId":26,"points":93},{"teamId":2,"matchId":27,"points":158.5},{"teamId":2,"matchId":28,"points":232.5},{"teamId":2,"matchId":29,"points":159},{"teamId":2,"matchId":30,"points":135},{"teamId":2,"matchId":31,"points":283},{"teamId":2,"matchId":32,"points":159},{"teamId":2,"matchId":33,"points":148},{"teamId":2,"matchId":34,"points":242},{"teamId":2,"matchId":35,"points":649},{"teamId":2,"matchId":36,"points":511},{"teamId":2,"matchId":37,"points":259},{"teamId":2,"matchId":38,"points":146.5},{"teamId":2,"matchId":39,"points":88.5},{"teamId":2,"matchId":40,"points":339},{"teamId":2,"matchId":41,"points":320.5},{"teamId":2,"matchId":42,"points":324.5},{"teamId":2,"matchId":43,"points":247.5},{"teamId":2,"matchId":44,"points":263.5},{"teamId":2,"matchId":45,"points":135},{"teamId":2,"matchId":46,"points":253},{"teamId":2,"matchId":47,"points":293},{"teamId":2,"matchId":48,"points":295},{"teamId":2,"matchId":49,"points":659},{"teamId":2,"matchId":50,"points":301},{"teamId":3,"matchId":1,"points":295},{"teamId":3,"matchId":2,"points":297},{"teamId":3,"matchId":3,"points":155},{"teamId":3,"matchId":4,"points":252.5},{"teamId":3,"matchId":5,"points":208},{"teamId":3,"matchId":6,"points":217},{"teamId":3,"matchId":7,"points":266},{"teamId":3,"matchId":8,"points":148.5},{"teamId":3,"matchId":9,"points":349},{"teamId":3,"matchId":10,"points":78},{"teamId":3,"matchId":11,"points":179.5},{"teamId":3,"matchId":12,"points":42.5},{"teamId":3,"matchId":13,"points":242},{"teamId":3,"matchId":14,"points":170.5},{"teamId":3,"matchId":15,"points":249},{"teamId":3,"matchId":16,"points":150},{"teamId":3,"matchId":17,"points":263},{"teamId":3,"matchId":18,"points":401},{"teamId":3,"matchId":19,"points":327},{"teamId":3,"matchId":20,"points":264},{"teamId":3,"matchId":21,"points":263.5},{"teamId":3,"matchId":22,"points":161},{"teamId":3,"matchId":23,"points":235},{"teamId":3,"matchId":24,"points":399.5},{"teamId":3,"matchId":25,"points":410.5},{"teamId":3,"matchId":26,"points":239},{"teamId":3,"matchId":27,"points":280.5},{"teamId":3,"matchId":28,"points":241.5},{"teamId":3,"matchId":29,"points":309},{"teamId":3,"matchId":30,"points":78},{"teamId":3,"matchId":31,"points":186.5},{"teamId":3,"matchId":32,"points":119},{"teamId":3,"matchId":33,"points":316.5},{"teamId":3,"matchId":34,"points":596},{"teamId":3,"matchId":35,"points":766.5},{"teamId":3,"matchId":36,"points":283},{"teamId":3,"matchId":37,"points":347},{"teamId":3,"matchId":38,"points":29},{"teamId":3,"matchId":39,"points":83.5},{"teamId":3,"matchId":40,"points":414},{"teamId":3,"matchId":41,"points":183.5},{"teamId":3,"matchId":42,"points":258.5},{"teamId":3,"matchId":43,"points":191.5},{"teamId":3,"matchId":44,"points":179.5},{"teamId":3,"matchId":45,"points":218},{"teamId":3,"matchId":46,"points":233.5},{"teamId":3,"matchId":47,"points":456.5},{"teamId":3,"matchId":48,"points":231.5},{"teamId":3,"matchId":49,"points":921},{"teamId":3,"matchId":50,"points":406},{"teamId":4,"matchId":1,"points":386},{"teamId":4,"matchId":2,"points":401.5},{"teamId":4,"matchId":3,"points":149},{"teamId":4,"matchId":4,"points":296},{"teamId":4,"matchId":5,"points":67.5},{"teamId":4,"matchId":6,"points":296},{"teamId":4,"matchId":7,"points":240.5},{"teamId":4,"matchId":8,"points":221.5},{"teamId":4,"matchId":9,"points":182.5},{"teamId":4,"matchId":10,"points":368.5},{"teamId":4,"matchId":11,"points":107},{"teamId":4,"matchId":12,"points":77},{"teamId":4,"matchId":13,"points":-1},{"teamId":4,"matchId":14,"points":226},{"teamId":4,"matchId":15,"points":294.5},{"teamId":4,"matchId":16,"points":319},{"teamId":4,"matchId":17,"points":205.5},{"teamId":4,"matchId":18,"points":572},{"teamId":4,"matchId":19,"points":178},{"teamId":4,"matchId":20,"points":161},{"teamId":4,"matchId":21,"points":48.5},{"teamId":4,"matchId":22,"points":255},{"teamId":4,"matchId":23,"points":279},{"teamId":4,"matchId":24,"points":167.5},{"teamId":4,"matchId":25,"points":189.5},{"teamId":4,"matchId":26,"points":408},{"teamId":4,"matchId":27,"points":357.5},{"teamId":4,"matchId":28,"points":248.5},{"teamId":4,"matchId":29,"points":172.5},{"teamId":4,"matchId":30,"points":73},{"teamId":4,"matchId":31,"points":411},{"teamId":4,"matchId":32,"points":135},{"teamId":4,"matchId":33,"points":345.5},{"teamId":4,"matchId":34,"points":113},{"teamId":4,"matchId":35,"points":678},{"teamId":4,"matchId":36,"points":507},{"teamId":4,"matchId":37,"points":170.5},{"teamId":4,"matchId":38,"points":185},{"teamId":4,"matchId":39,"points":99},{"teamId":4,"matchId":40,"points":368},{"teamId":4,"matchId":41,"points":517.5},{"teamId":4,"matchId":42,"points":260},{"teamId":4,"matchId":43,"points":321.5},{"teamId":4,"matchId":44,"points":175.5},{"teamId":4,"matchId":45,"points":319},{"teamId":4,"matchId":46,"points":327.5},{"teamId":4,"matchId":47,"points":299},{"teamId":4,"matchId":48,"points":393},{"teamId":4,"matchId":49,"points":819},{"teamId":4,"matchId":50,"points":167.5},{"teamId":5,"matchId":1,"points":510},{"teamId":5,"matchId":2,"points":376.5},{"teamId":5,"matchId":3,"points":108},{"teamId":5,"matchId":4,"points":174},{"teamId":5,"matchId":5,"points":162.5},{"teamId":5,"matchId":6,"points":317},{"teamId":5,"matchId":7,"points":203.5},{"teamId":5,"matchId":8,"points":159.5},{"teamId":5,"matchId":9,"points":403},{"teamId":5,"matchId":10,"points":65.5},{"teamId":5,"matchId":11,"points":188},{"teamId":5,"matchId":12,"points":32.5},{"teamId":5,"matchId":13,"points":166},{"teamId":5,"matchId":14,"points":130},{"teamId":5,"matchId":15,"points":181.5},{"teamId":5,"matchId":16,"points":322},{"teamId":5,"matchId":17,"points":219},{"teamId":5,"matchId":18,"points":172},{"teamId":5,"matchId":19,"points":242},{"teamId":5,"matchId":20,"points":328.5},{"teamId":5,"matchId":21,"points":28.5},{"teamId":5,"matchId":22,"points":232},{"teamId":5,"matchId":23,"points":316},{"teamId":5,"matchId":24,"points":258},{"teamId":5,"matchId":25,"points":294},{"teamId":5,"matchId":26,"points":192},{"teamId":5,"matchId":27,"points":136},{"teamId":5,"matchId":28,"points":78.5},{"teamId":5,"matchId":29,"points":319},{"teamId":5,"matchId":30,"points":127},{"teamId":5,"matchId":31,"points":551},{"teamId":5,"matchId":32,"points":290},{"teamId":5,"matchId":33,"points":455.5},{"teamId":5,"matchId":34,"points":452.5},{"teamId":5,"matchId":35,"points":662},{"teamId":5,"matchId":36,"points":465},{"teamId":5,"matchId":37,"points":350.5},{"teamId":5,"matchId":38,"points":51},{"teamId":5,"matchId":39,"points":258.5},{"teamId":5,"matchId":40,"points":367},{"teamId":5,"matchId":41,"points":254},{"teamId":5,"matchId":42,"points":344.5},{"teamId":5,"matchId":43,"points":245.5},{"teamId":5,"matchId":44,"points":276.5},{"teamId":5,"matchId":45,"points":135},{"teamId":5,"matchId":46,"points":73.5},{"teamId":5,"matchId":47,"points":287},{"teamId":5,"matchId":48,"points":287},{"teamId":5,"matchId":49,"points":186.5},{"teamId":5,"matchId":50,"points":174},{"teamId":6,"matchId":1,"points":530.5},{"teamId":6,"matchId":2,"points":415.5},{"teamId":6,"matchId":3,"points":241.5},{"teamId":6,"matchId":4,"points":169},{"teamId":6,"matchId":5,"points":47},{"teamId":6,"matchId":6,"points":331},{"teamId":6,"matchId":7,"points":197},{"teamId":6,"matchId":8,"points":99.5},{"teamId":6,"matchId":9,"points":471.5},{"teamId":6,"matchId":10,"points":240},{"teamId":6,"matchId":11,"points":146},{"teamId":6,"matchId":12,"points":35.5},{"teamId":6,"matchId":13,"points":265.5},{"teamId":6,"matchId":14,"points":82},{"teamId":6,"matchId":15,"points":217.5},{"teamId":6,"matchId":16,"points":135.5},{"teamId":6,"matchId":17,"points":188},{"teamId":6,"matchId":18,"points":101.5},{"teamId":6,"matchId":19,"points":155.5},{"teamId":6,"matchId":20,"points":226.5},{"teamId":6,"matchId":21,"points":46},{"teamId":6,"matchId":22,"points":115},{"teamId":6,"matchId":23,"points":297.5},{"teamId":6,"matchId":24,"points":169.5},{"teamId":6,"matchId":25,"points":275},{"teamId":6,"matchId":26,"points":325},{"teamId":6,"matchId":27,"points":245.5},{"teamId":6,"matchId":28,"points":280.5},{"teamId":6,"matchId":29,"points":402.5},{"teamId":6,"matchId":30,"points":75.5},{"teamId":6,"matchId":31,"points":322},{"teamId":6,"matchId":32,"points":303},{"teamId":6,"matchId":33,"points":308.5},{"teamId":6,"matchId":34,"points":322},{"teamId":6,"matchId":35,"points":455},{"teamId":6,"matchId":36,"points":326.5},{"teamId":6,"matchId":37,"points":524},{"teamId":6,"matchId":38,"points":131},{"teamId":6,"matchId":39,"points":152.5},{"teamId":6,"matchId":40,"points":100},{"teamId":6,"matchId":41,"points":497.5},{"teamId":6,"matchId":42,"points":313.5},{"teamId":6,"matchId":43,"points":197.5},{"teamId":6,"matchId":44,"points":224},{"teamId":6,"matchId":45,"points":181},{"teamId":6,"matchId":46,"points":259},{"teamId":6,"matchId":47,"points":827},{"teamId":6,"matchId":48,"points":144},{"teamId":6,"matchId":49,"points":210.5},{"teamId":6,"matchId":50,"points":387},{"teamId":7,"matchId":1,"points":343},{"teamId":7,"matchId":2,"points":374.5},{"teamId":7,"matchId":3,"points":140.5},{"teamId":7,"matchId":4,"points":242.5},{"teamId":7,"matchId":5,"points":0},{"teamId":7,"matchId":6,"points":175},{"teamId":7,"matchId":7,"points":239},{"teamId":7,"matchId":8,"points":10},{"teamId":7,"matchId":9,"points":277},{"teamId":7,"matchId":10,"points":76},{"teamId":7,"matchId":11,"points":75.5},{"teamId":7,"matchId":12,"points":41.5},{"teamId":7,"matchId":13,"points":114},{"teamId":7,"matchId":14,"points":149.5},{"teamId":7,"matchId":15,"points":78},{"teamId":7,"matchId":16,"points":124},{"teamId":7,"matchId":17,"points":314},{"teamId":7,"matchId":18,"points":40},{"teamId":7,"matchId":19,"points":12},{"teamId":7,"matchId":20,"points":246},{"teamId":7,"matchId":21,"points":206},{"teamId":7,"matchId":22,"points":104},{"teamId":7,"matchId":23,"points":111},{"teamId":7,"matchId":24,"points":460},{"teamId":7,"matchId":25,"points":53},{"teamId":7,"matchId":26,"points":16},{"teamId":7,"matchId":27,"points":58.5},{"teamId":7,"matchId":28,"points":105},{"teamId":7,"matchId":29,"points":103},{"teamId":7,"matchId":30,"points":184},{"teamId":7,"matchId":31,"points":175.5},{"teamId":7,"matchId":32,"points":130},{"teamId":7,"matchId":33,"points":136.5},{"teamId":7,"matchId":34,"points":85},{"teamId":7,"matchId":35,"points":246},{"teamId":7,"matchId":36,"points":279},{"teamId":7,"matchId":37,"points":125},{"teamId":7,"matchId":38,"points":0},{"teamId":7,"matchId":39,"points":86.5},{"teamId":7,"matchId":40,"points":203},{"teamId":7,"matchId":41,"points":84},{"teamId":7,"matchId":42,"points":296},{"teamId":7,"matchId":43,"points":220},{"teamId":7,"matchId":44,"points":57},{"teamId":7,"matchId":45,"points":60},{"teamId":7,"matchId":46,"points":132.5},{"teamId":7,"matchId":47,"points":0},{"teamId":7,"matchId":48,"points":315},{"teamId":7,"matchId":49,"points":261.5},{"teamId":7,"matchId":50,"points":384},{"teamId":8,"matchId":1,"points":667},{"teamId":8,"matchId":2,"points":257},{"teamId":8,"matchId":3,"points":0},{"teamId":8,"matchId":4,"points":0},{"teamId":8,"matchId":5,"points":0},{"teamId":8,"matchId":6,"points":259},{"teamId":8,"matchId":7,"points":0},{"teamId":8,"matchId":8,"points":206},{"teamId":8,"matchId":9,"points":0},{"teamId":8,"matchId":10,"points":209},{"teamId":8,"matchId":11,"points":268},{"teamId":8,"matchId":12,"points":0},{"teamId":8,"matchId":13,"points":42},{"teamId":8,"matchId":14,"points":0},{"teamId":8,"matchId":15,"points":0},{"teamId":8,"matchId":16,"points":168},{"teamId":8,"matchId":17,"points":66},{"teamId":8,"matchId":18,"points":0},{"teamId":8,"matchId":19,"points":0},{"teamId":8,"matchId":20,"points":289},{"teamId":8,"matchId":21,"points":175},{"teamId":8,"matchId":22,"points":0},{"teamId":8,"matchId":23,"points":207},{"teamId":8,"matchId":24,"points":5},{"teamId":8,"matchId":25,"points":0},{"teamId":8,"matchId":26,"points":155},{"teamId":8,"matchId":27,"points":260},{"teamId":8,"matchId":28,"points":0},{"teamId":8,"matchId":29,"points":0},{"teamId":8,"matchId":30,"points":75.5},{"teamId":8,"matchId":31,"points":186},{"teamId":8,"matchId":32,"points":0},{"teamId":8,"matchId":33,"points":107.5},{"teamId":8,"matchId":34,"points":75},{"teamId":8,"matchId":35,"points":0},{"teamId":8,"matchId":36,"points":197},{"teamId":8,"matchId":37,"points":0},{"teamId":8,"matchId":38,"points":0},{"teamId":8,"matchId":39,"points":144},{"teamId":8,"matchId":40,"points":0},{"teamId":8,"matchId":41,"points":205},{"teamId":8,"matchId":42,"points":144},{"teamId":8,"matchId":43,"points":0},{"teamId":8,"matchId":44,"points":82.5},{"teamId":8,"matchId":45,"points":51},{"teamId":8,"matchId":46,"points":0},{"teamId":8,"matchId":47,"points":270},{"teamId":8,"matchId":48,"points":0},{"teamId":8,"matchId":49,"points":219},{"teamId":8,"matchId":50,"points":183},
3
+ ];