@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,225 @@
1
+ "use client";
2
+
3
+ import { useMemo, useState, useCallback } from "react";
4
+ import { FaWandSparkles, FaStar, FaFire, FaRocket } from "react-icons/fa6";
5
+ import { motion, AnimatePresence } from "framer-motion";
6
+ import type { DashboardData } from "../types";
7
+ import { getPlayedMatchRows } from "../lib/dashboardData";
8
+ import { getChartColor } from "../lib/utils/getChartColor";
9
+
10
+ type Particle = {
11
+ id: number;
12
+ x: number;
13
+ y: number;
14
+ color: string;
15
+ angle: number;
16
+ velocity: number;
17
+ size: number;
18
+ };
19
+
20
+ type BurstState = {
21
+ particles: Particle[];
22
+ teamName: string;
23
+ teamColor: string;
24
+ };
25
+
26
+ let particleId = 0;
27
+
28
+ function generateBurst(
29
+ cx: number,
30
+ cy: number,
31
+ color: string,
32
+ count: number = 24,
33
+ ): Particle[] {
34
+ const particles: Particle[] = [];
35
+ for (let i = 0; i < count; i++) {
36
+ const angle = (i / count) * 360 + (Math.random() - 0.5) * 20;
37
+ const velocity = 40 + Math.random() * 60;
38
+ particles.push({
39
+ id: ++particleId,
40
+ x: cx,
41
+ y: cy,
42
+ color,
43
+ angle: (angle * Math.PI) / 180,
44
+ velocity,
45
+ size: 3 + Math.random() * 5,
46
+ });
47
+ }
48
+ return particles;
49
+ }
50
+
51
+ export default function FireworksBurst({
52
+ data,
53
+ }: {
54
+ data: DashboardData | null;
55
+ }) {
56
+ const [bursts, setBursts] = useState<BurstState[]>([]);
57
+
58
+ const teams = useMemo(() => {
59
+ if (!data?.overall?.length) return [];
60
+ const daily = getPlayedMatchRows(data.daily);
61
+ return data.overall
62
+ .sort((a, b) => a.rank - b.rank)
63
+ .slice(0, 8)
64
+ .map((team, i) => {
65
+ const scores = daily
66
+ .map((r) => Number(r[team.name] ?? 0))
67
+ .filter((v) => v > 0);
68
+ const avg = scores.length
69
+ ? scores.reduce((a, b) => a + b, 0) / scores.length
70
+ : 0;
71
+ return {
72
+ name: team.name,
73
+ rank: team.rank,
74
+ points: team.points,
75
+ color: getChartColor(i, 8),
76
+ avg: Math.round(avg),
77
+ lastMatch: team.lastMatchPoints ?? scores.at(-1) ?? 0,
78
+ };
79
+ });
80
+ }, [data]);
81
+
82
+ const handleCelebrate = useCallback(
83
+ (e: React.MouseEvent<HTMLDivElement>, name: string, color: string) => {
84
+ const rect = e.currentTarget.getBoundingClientRect();
85
+ const cx = e.clientX - rect.left;
86
+ const cy = e.clientY - rect.top;
87
+ const particles = generateBurst(cx, cy, color);
88
+ setBursts((prev) => [
89
+ ...prev.slice(-3),
90
+ { particles, teamName: name, teamColor: color },
91
+ ]);
92
+ setTimeout(() => {
93
+ setBursts((prev) => prev.filter((b) => b.teamName !== name));
94
+ }, 1200);
95
+ },
96
+ [],
97
+ );
98
+
99
+ if (!teams.length) {
100
+ return <div className="ledger-note py-6 text-center">No data yet...</div>;
101
+ }
102
+
103
+ return (
104
+ <div
105
+ className="rounded-2xl border-2 p-4 wobbly overflow-hidden relative"
106
+ style={{
107
+ borderColor:
108
+ "color-mix(in srgb, var(--accent-magenta) 34%, var(--line))",
109
+ background:
110
+ "linear-gradient(180deg, color-mix(in srgb, var(--paper) 86%, var(--accent-magenta) 14%), color-mix(in srgb, var(--paper) 92%, var(--paper-2) 8%))",
111
+ }}
112
+ >
113
+ <div className="mb-3 flex items-center gap-2 border-b border-dashed border-(--border-wobbly)/40 pb-2">
114
+ <FaWandSparkles
115
+ className="text-lg"
116
+ style={{ color: "var(--accent-magenta)" }}
117
+ />
118
+ <span className="font-bold text-lg text-(--ink)">Fireworks Burst</span>
119
+ <span className="text-xs text-(--ink-faint) ml-auto">
120
+ click any team
121
+ </span>
122
+ </div>
123
+
124
+ <div className="relative">
125
+ <div className="grid gap-2 sm:grid-cols-1 md:grid-cols-3 relative z-10">
126
+ {teams.map((t, i) => (
127
+ <motion.div
128
+ key={t.name}
129
+ initial={{ opacity: 0, y: 12 }}
130
+ animate={{ opacity: 1, y: 0 }}
131
+ transition={{ delay: i * 0.05 }}
132
+ className="relative rounded-xl border-2 p-3 cursor-pointer select-none overflow-hidden"
133
+ style={{
134
+ borderColor: `color-mix(in srgb, ${t.color} 34%, var(--line))`,
135
+ background: `color-mix(in srgb, ${t.color} 7%, var(--paper) 93%)`,
136
+ }}
137
+ onClick={(e) => handleCelebrate(e, t.name, t.color)}
138
+ whileTap={{ scale: 0.95 }}
139
+ >
140
+ <div className="flex items-center gap-3">
141
+ <div
142
+ className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full text-lg"
143
+ style={{
144
+ background: `color-mix(in srgb, ${t.color} 24%, var(--paper) 76%)`,
145
+ color: t.color,
146
+ }}
147
+ >
148
+ {i === 0 ? <FaRocket /> : i < 3 ? <FaStar /> : <FaFire />}
149
+ </div>
150
+ <div className="min-w-0 flex-1">
151
+ <div className="flex items-center gap-1.5">
152
+ <span
153
+ className="rounded px-1.5 py-0.5 text-[10px] font-black"
154
+ style={{
155
+ background: `color-mix(in srgb, ${t.color} 26%, var(--panel) 74%)`,
156
+ color: "var(--ink)",
157
+ }}
158
+ >
159
+ #{t.rank}
160
+ </span>
161
+ <span className="truncate text-sm font-bold text-(--ink)">
162
+ {t.name}
163
+ </span>
164
+ </div>
165
+ <div className="flex items-center gap-2 mt-0.5">
166
+ <span
167
+ className="text-[15px] font-black"
168
+ style={{ color: t.color }}
169
+ >
170
+ {t.points.toLocaleString()}
171
+ </span>
172
+ <span className="text-[10px] text-(--ink-faint)">
173
+ last {t.lastMatch.toLocaleString()}
174
+ </span>
175
+ </div>
176
+ </div>
177
+ </div>
178
+
179
+ <motion.div
180
+ className="absolute -top-8 -right-8 text-[36px] opacity-10 pointer-events-none"
181
+ animate={{ rotate: [0, 15, -15, 0], scale: [1, 1.1, 1] }}
182
+ transition={{
183
+ repeat: Infinity,
184
+ duration: 4 + (i % 3),
185
+ ease: "easeInOut",
186
+ delay: i * 0.3,
187
+ }}
188
+ >
189
+ <FaStar style={{ color: t.color }} />
190
+ </motion.div>
191
+ </motion.div>
192
+ ))}
193
+ </div>
194
+
195
+ <AnimatePresence>
196
+ {bursts.map((burst) =>
197
+ burst.particles.map((p) => (
198
+ <motion.div
199
+ key={p.id}
200
+ className="absolute pointer-events-none z-20"
201
+ style={{
202
+ width: p.size,
203
+ height: p.size,
204
+ borderRadius: "50%",
205
+ background: p.color,
206
+ boxShadow: `0 0 6px ${p.color}`,
207
+ x: p.x - p.size / 2,
208
+ y: p.y - p.size / 2,
209
+ }}
210
+ initial={{ x: p.x, y: p.y, opacity: 1, scale: 1 }}
211
+ animate={{
212
+ x: p.x + Math.cos(p.angle) * p.velocity,
213
+ y: p.y + Math.sin(p.angle) * p.velocity + 60,
214
+ opacity: 0,
215
+ scale: 0,
216
+ }}
217
+ transition={{ duration: 0.8, ease: "easeOut" }}
218
+ />
219
+ )),
220
+ )}
221
+ </AnimatePresence>
222
+ </div>
223
+ </div>
224
+ );
225
+ }
@@ -0,0 +1,117 @@
1
+ "use client";
2
+
3
+ import { useEffect, useMemo, useState } from "react";
4
+ import { motion } from "framer-motion";
5
+ import { matches as fallbackMatches } from "../lib/matches";
6
+ import { getMatchStatus } from "../lib/matchStatus";
7
+ import type { UpcomingMatch } from "../lib/matches";
8
+
9
+ type UpcomingMatchesResponse = {
10
+ matches?: UpcomingMatch[];
11
+ source?: "remote" | "fallback";
12
+ };
13
+
14
+ export default function LiveMatchTicker() {
15
+ const [now, setNow] = useState(new Date());
16
+ const [upcomingMatches, setUpcomingMatches] =
17
+ useState<UpcomingMatch[]>(fallbackMatches);
18
+
19
+ useEffect(() => {
20
+ const interval = window.setInterval(() => {
21
+ setNow(new Date());
22
+ }, 1000);
23
+
24
+ return () => window.clearInterval(interval);
25
+ }, []);
26
+
27
+ useEffect(() => {
28
+ let active = true;
29
+
30
+ const loadSchedule = async () => {
31
+ try {
32
+ const response = await fetch(`/api/ipl/upcoming-matches?t=${Date.now()}`, {
33
+ cache: "no-store",
34
+ });
35
+
36
+ if (!response.ok) {
37
+ return;
38
+ }
39
+
40
+ const payload = (await response.json()) as UpcomingMatchesResponse;
41
+ if (!active) {
42
+ return;
43
+ }
44
+
45
+ if (Array.isArray(payload.matches) && payload.matches.length > 0) {
46
+ setUpcomingMatches(payload.matches);
47
+ }
48
+ } catch {
49
+ // Keep the fallback schedule when the live endpoint is unavailable.
50
+ }
51
+ };
52
+
53
+ void loadSchedule();
54
+ const refresh = window.setInterval(loadSchedule, 15 * 60 * 1000);
55
+
56
+ return () => {
57
+ active = false;
58
+ window.clearInterval(refresh);
59
+ };
60
+ }, []);
61
+
62
+ const relevantMatches = useMemo(
63
+ () =>
64
+ upcomingMatches
65
+ .map((match) => ({
66
+ ...match,
67
+ status: getMatchStatus(match.date),
68
+ }))
69
+ .filter((match) => match.status !== "ENDED")
70
+ .sort((a, b) => +new Date(a.date) - +new Date(b.date))
71
+ .slice(0, 5),
72
+ [upcomingMatches],
73
+ );
74
+
75
+ const tickerData =
76
+ relevantMatches.length > 0
77
+ ? [...relevantMatches, ...relevantMatches, ...relevantMatches]
78
+ : [];
79
+
80
+ return (
81
+ <div className="ticker-shell">
82
+ <span className="ticker-label">
83
+ <span className="ticker-label-date">
84
+ {now.toLocaleDateString("en-IN", { weekday: "short", day: "2-digit", month: "short" })}
85
+ </span>
86
+ <span className="ticker-label-time">
87
+ {now.toLocaleTimeString("en-IN", { hour: "2-digit", minute: "2-digit" })}
88
+ </span>
89
+ </span>
90
+
91
+ <div className="ticker-window">
92
+ <motion.div
93
+ className="ticker-track"
94
+ animate={{ x: ["0%", "-50%"] }}
95
+ transition={{ repeat: Infinity, duration: 34, ease: "linear" }}
96
+ >
97
+ {tickerData.map((match, index) => (
98
+ <span key={`${match.id}-${index}`} className="ticker-chip">
99
+ <span className="ticker-matchup">
100
+ {match.team1} <span className="ticker-vs">vs</span> {match.team2}
101
+ </span>
102
+ <span className="ticker-note">
103
+ {new Date(match.date).toLocaleDateString("en-IN", {
104
+ timeZone: "Asia/Kolkata", day: "2-digit", month: "short",
105
+ })}
106
+ ·
107
+ {new Date(match.date).toLocaleTimeString("en-IN", {
108
+ timeZone: "Asia/Kolkata", hour: "2-digit", minute: "2-digit",
109
+ })}
110
+ </span>
111
+ </span>
112
+ ))}
113
+ </motion.div>
114
+ </div>
115
+ </div>
116
+ );
117
+ }
@@ -0,0 +1,135 @@
1
+ "use client";
2
+
3
+ import { useMemo } from "react";
4
+ import { FaScroll } from "react-icons/fa6";
5
+ import { motion } from "framer-motion";
6
+ import type { DashboardData } from "../types";
7
+ import { getMatchViewState } from "../lib/dashboardView";
8
+ import { getDisplayLiveMatchId } from "../lib/dashboardData";
9
+
10
+ export default function MatchRecapScroll({
11
+ data,
12
+ }: {
13
+ data: DashboardData | null;
14
+ }) {
15
+ const recap = useMemo(() => {
16
+ if (!data?.daily?.length) return null;
17
+ const view = getMatchViewState(data.daily);
18
+ if (!view.latestRow) return null;
19
+ const entries = Object.entries(view.latestRow)
20
+ .filter(([k]) => k !== "day")
21
+ .map(([team, pts]) => ({ team, points: Number(pts) }))
22
+ .sort((a, b) => b.points - a.points);
23
+ return {
24
+ matchId: getDisplayLiveMatchId(data.daily),
25
+ isLive: view.isLive,
26
+ day: view.latestRow.day,
27
+ entries,
28
+ leader: entries[0],
29
+ };
30
+ }, [data]);
31
+
32
+ if (!recap) {
33
+ return <p className="ledger-note py-6">No match data to recap...</p>;
34
+ }
35
+
36
+ return (
37
+ <div
38
+ className="rounded-2xl border-2 p-4 wobbly"
39
+ style={{
40
+ borderColor: "color-mix(in srgb, #f59e0b 40%, var(--line))",
41
+ background:
42
+ "linear-gradient(180deg, color-mix(in srgb, var(--paper) 88%, var(--paper-2) 12%), color-mix(in srgb, var(--paper-2) 72%, var(--paper) 28%))",
43
+ }}
44
+ >
45
+ <div
46
+ className="mb-3 flex items-center gap-2 border-b border-dashed pb-2"
47
+ style={{ borderColor: "color-mix(in srgb, #f59e0b 20%, transparent)" }}
48
+ >
49
+ <FaScroll className="text-sm" style={{ color: "#f59e0b" }} />
50
+ <span className=" text-sm text-(--ink)">Match Recap Scroll</span>
51
+ <span
52
+ className="scribble-pill text-[13px] py-0.5 px-2 ml-auto"
53
+ style={{
54
+ background:
55
+ "color-mix(in srgb, #f59e0b 24%, var(--panel-strong) 76%)",
56
+ borderColor: "color-mix(in srgb, #f59e0b 40%, var(--line))",
57
+ }}
58
+ >
59
+ {recap.isLive ? "🔴 LIVE" : `Match ${recap.matchId}`}
60
+ </span>
61
+ </div>
62
+ <div className="relative">
63
+ {recap.entries.slice(0, 8).map((entry, i) => {
64
+ const barPct =
65
+ recap.leader.points > 0
66
+ ? (entry.points / recap.leader.points) * 100
67
+ : 0;
68
+ const brightColors = [
69
+ "#22d3ee",
70
+ "#f59e0b",
71
+ "#84cc16",
72
+ "#ec4899",
73
+ "#3b82f6",
74
+ "#f97316",
75
+ "#a855f7",
76
+ "#14b8a6",
77
+ ];
78
+ const barColor = brightColors[i % brightColors.length];
79
+ return (
80
+ <motion.div
81
+ key={entry.team}
82
+ initial={{ opacity: 0, x: -8 }}
83
+ animate={{ opacity: 1, x: 0 }}
84
+ transition={{ delay: i * 0.05 }}
85
+ className="flex items-center gap-2 py-1.5 border-b last:border-0"
86
+ style={{
87
+ borderColor: "color-mix(in srgb, var(--line) 20%, transparent)",
88
+ }}
89
+ >
90
+ <span className="w-6 text-[12px] font-bold text-(--ink-faint) text-right shrink-0">
91
+ #{i + 1}
92
+ </span>
93
+ <span className="w-24 truncate text-[13px] font-bold text-(--ink) shrink-0">
94
+ {entry.team}
95
+ </span>
96
+ <div
97
+ className="flex-1 h-3 rounded-sm relative overflow-hidden"
98
+ style={{
99
+ background:
100
+ "color-mix(in srgb, var(--panel) 88%, transparent)",
101
+ }}
102
+ >
103
+ <motion.div
104
+ initial={{ width: 0 }}
105
+ animate={{ width: `${Math.max(barPct, 4)}%` }}
106
+ transition={{ duration: 0.6, delay: i * 0.05 }}
107
+ className="h-full rounded-sm"
108
+ style={{ background: barColor }}
109
+ />
110
+ </div>
111
+ <span
112
+ className="w-16 text-right text-[13px] font-black shrink-0"
113
+ style={{ color: barColor }}
114
+ >
115
+ {entry.points.toLocaleString()}
116
+ </span>
117
+ {i === 0 && <span className="text-sm shrink-0">👑</span>}
118
+ </motion.div>
119
+ );
120
+ })}
121
+ </div>
122
+ <div className="mt-2 flex items-center gap-2">
123
+ <div
124
+ className="flex-1 h-px"
125
+ style={{ background: "color-mix(in srgb, #f59e0b 20%, transparent)" }}
126
+ />
127
+ <span className="text-[13px] text-(--ink-faint)">{recap.day}</span>
128
+ <div
129
+ className="flex-1 h-px"
130
+ style={{ background: "color-mix(in srgb, #f59e0b 20%, transparent)" }}
131
+ />
132
+ </div>
133
+ </div>
134
+ );
135
+ }