@grindxp/cli 0.1.7 → 0.1.8
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/dist/web/client/assets/Copy.es-Bs4NgJu-.js +1 -0
- package/dist/web/client/assets/Sword.es-2Xm7T3t2.js +1 -0
- package/dist/web/client/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
- package/dist/web/client/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
- package/dist/web/client/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
- package/dist/web/client/assets/index-6XDcqRbL.js +42 -0
- package/dist/web/client/assets/index-BXM1N6tm.js +1 -0
- package/dist/web/client/assets/index-B_KMiE38.js +1 -0
- package/dist/web/client/assets/index-CGj2rOLm.js +1 -0
- package/dist/web/client/assets/index-CS5BuFbt.js +1 -0
- package/dist/web/client/assets/index-CYsASiu-.js +1 -0
- package/dist/web/client/assets/index-DAvwM0SX.js +1 -0
- package/dist/web/client/assets/index-DCBFp5DJ.js +1 -0
- package/dist/web/client/assets/index-DjKt1qNz.js +1 -0
- package/dist/web/client/assets/index-PIcFs1vr.js +1 -0
- package/dist/web/client/assets/instrument-serif-latin-400-italic-DKMiL14s.woff2 +0 -0
- package/dist/web/client/assets/instrument-serif-latin-400-italic-u__WvvIK.woff +0 -0
- package/dist/web/client/assets/instrument-serif-latin-400-normal-BVbkICAY.woff +0 -0
- package/dist/web/client/assets/instrument-serif-latin-400-normal-DnYpCC2O.woff2 +0 -0
- package/dist/web/client/assets/instrument-serif-latin-ext-400-italic-C9HzH3YL.woff2 +0 -0
- package/dist/web/client/assets/instrument-serif-latin-ext-400-italic-D7-lnxEk.woff +0 -0
- package/dist/web/client/assets/instrument-serif-latin-ext-400-normal-C2je3j2s.woff2 +0 -0
- package/dist/web/client/assets/instrument-serif-latin-ext-400-normal-CFCUzsTy.woff +0 -0
- package/dist/web/client/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/dist/web/client/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/dist/web/client/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/dist/web/client/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/dist/web/client/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/dist/web/client/assets/main-BI1EOhmt.js +18 -0
- package/dist/web/client/assets/styles-7TpWqjrh.css +1 -0
- package/dist/web/client/favicon.ico +0 -0
- package/dist/web/server/assets/_tanstack-start-manifest_v-B_rvI8DG.js +4 -0
- package/dist/web/server/assets/agent.functions-zpMkBrG3.js +19144 -0
- package/dist/web/server/assets/data.functions-9hSsMFx_.js +285 -0
- package/dist/web/server/assets/index-4SxmUYH6.js +14 -0
- package/dist/web/server/assets/index-BDL7hA7T.js +5924 -0
- package/dist/web/server/assets/index-BL8u2X7w.js +14 -0
- package/dist/web/server/assets/index-BRRsXrOi.js +14 -0
- package/dist/web/server/assets/index-BiD7uOOh.js +14 -0
- package/dist/web/server/assets/index-C09LXa7Z.js +4587 -0
- package/dist/web/server/assets/index-CJ_-TSqN.js +1426 -0
- package/dist/web/server/assets/index-D2fMUSdJ.js +477 -0
- package/dist/web/server/assets/index-D2yaimYL.js +14 -0
- package/dist/web/server/assets/index-D31yYLCV.js +2275 -0
- package/dist/web/server/assets/index-D3RUqTdb.js +14 -0
- package/dist/web/server/assets/index-D7z4dRpK.js +66 -0
- package/dist/web/server/assets/index-b30aLTKp.js +66 -0
- package/dist/web/server/assets/router-1koL9I3U.js +589 -0
- package/dist/web/server/assets/sessions-DOkG47Ex.js +403 -0
- package/dist/web/server/assets/start-HYkvq4Ni.js +4 -0
- package/dist/web/server/assets/token-W0NPKas8.js +86 -0
- package/dist/web/server/assets/token-util-1cB5CD6M.js +30 -0
- package/dist/web/server/assets/token-util-DA5xS0pj.js +451 -0
- package/dist/web/server/assets/vault.server-Ndu49yTf.js +19356 -0
- package/dist/web/server/server.js +4889 -0
- package/package.json +53 -51
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Link } from "@tanstack/react-router";
|
|
3
|
+
import { StarIcon, LightningIcon, FlameIcon, TrophyIcon, SwordIcon } from "@phosphor-icons/react";
|
|
4
|
+
import { c as cn, R as Route } from "./router-1koL9I3U.js";
|
|
5
|
+
import "@tanstack/react-query";
|
|
6
|
+
import "react";
|
|
7
|
+
import "clsx";
|
|
8
|
+
import "tailwind-merge";
|
|
9
|
+
import "../server.js";
|
|
10
|
+
import "node:async_hooks";
|
|
11
|
+
import "node:stream";
|
|
12
|
+
import "@tanstack/react-router/ssr/server";
|
|
13
|
+
import "./vault.server-Ndu49yTf.js";
|
|
14
|
+
import "node:fs";
|
|
15
|
+
import "node:os";
|
|
16
|
+
import "node:path";
|
|
17
|
+
import "path";
|
|
18
|
+
import "fs";
|
|
19
|
+
import "child_process";
|
|
20
|
+
import "node:buffer";
|
|
21
|
+
import "events";
|
|
22
|
+
import "https";
|
|
23
|
+
import "http";
|
|
24
|
+
import "net";
|
|
25
|
+
import "tls";
|
|
26
|
+
import "crypto";
|
|
27
|
+
import "stream";
|
|
28
|
+
import "url";
|
|
29
|
+
import "zlib";
|
|
30
|
+
import "buffer";
|
|
31
|
+
import "node:crypto";
|
|
32
|
+
const accentClasses = {
|
|
33
|
+
orange: "text-grind-orange",
|
|
34
|
+
green: "text-grind-xp",
|
|
35
|
+
purple: "text-[oklch(0.72_0.19_285)]",
|
|
36
|
+
default: "text-foreground"
|
|
37
|
+
};
|
|
38
|
+
function StatCard({
|
|
39
|
+
label,
|
|
40
|
+
value,
|
|
41
|
+
sub,
|
|
42
|
+
icon,
|
|
43
|
+
accent = "default",
|
|
44
|
+
className
|
|
45
|
+
}) {
|
|
46
|
+
const valueClass = accentClasses[accent] ?? accentClasses["default"];
|
|
47
|
+
return /* @__PURE__ */ jsxs(
|
|
48
|
+
"div",
|
|
49
|
+
{
|
|
50
|
+
className: cn(
|
|
51
|
+
"flex flex-col gap-1 rounded-xl border border-border bg-card px-5 py-4",
|
|
52
|
+
className
|
|
53
|
+
),
|
|
54
|
+
children: [
|
|
55
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
|
|
56
|
+
icon && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "shrink-0", children: icon }),
|
|
57
|
+
label
|
|
58
|
+
] }),
|
|
59
|
+
/* @__PURE__ */ jsx(
|
|
60
|
+
"div",
|
|
61
|
+
{
|
|
62
|
+
className: cn("font-mono text-2xl font-semibold leading-none tabular-nums", valueClass),
|
|
63
|
+
style: { fontVariantNumeric: "tabular-nums" },
|
|
64
|
+
children: value
|
|
65
|
+
}
|
|
66
|
+
),
|
|
67
|
+
sub && /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: sub })
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const DIFFICULTY_DOTS = {
|
|
73
|
+
easy: "◆◇◇",
|
|
74
|
+
medium: "◆◆◇",
|
|
75
|
+
hard: "◆◆◆",
|
|
76
|
+
epic: "◆◆◆★"
|
|
77
|
+
};
|
|
78
|
+
const DIFFICULTY_COLORS = {
|
|
79
|
+
easy: "text-grind-xp",
|
|
80
|
+
medium: "text-grind-orange",
|
|
81
|
+
hard: "text-[oklch(0.65_0.24_27)]",
|
|
82
|
+
epic: "text-[oklch(0.72_0.19_285)]"
|
|
83
|
+
};
|
|
84
|
+
const TYPE_LABELS = {
|
|
85
|
+
daily: "Daily",
|
|
86
|
+
weekly: "Weekly",
|
|
87
|
+
epic: "Epic",
|
|
88
|
+
bounty: "Bounty",
|
|
89
|
+
chain: "Chain",
|
|
90
|
+
ritual: "Ritual"
|
|
91
|
+
};
|
|
92
|
+
function ObjectiveCheck({ objective }) {
|
|
93
|
+
return /* @__PURE__ */ jsx(
|
|
94
|
+
"span",
|
|
95
|
+
{
|
|
96
|
+
"aria-label": objective.completed ? `${objective.label} — done` : `${objective.label} — pending`,
|
|
97
|
+
className: cn(
|
|
98
|
+
"inline-flex h-5 w-5 shrink-0 items-center justify-center rounded border text-[10px] font-bold",
|
|
99
|
+
objective.completed ? "border-grind-xp/40 bg-grind-xp/10 text-grind-xp" : "border-border bg-transparent text-transparent"
|
|
100
|
+
),
|
|
101
|
+
children: objective.completed ? "✓" : ""
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
function QuestRow({ quest, className }) {
|
|
106
|
+
const completedObjectives = quest.objectives.filter((o) => o.completed).length;
|
|
107
|
+
const totalObjectives = quest.objectives.length;
|
|
108
|
+
const difficultyDots = DIFFICULTY_DOTS[quest.difficulty] ?? "◆◇◇";
|
|
109
|
+
const difficultyColor = DIFFICULTY_COLORS[quest.difficulty] ?? "text-muted-foreground";
|
|
110
|
+
const typeLabel = TYPE_LABELS[quest.type] ?? quest.type;
|
|
111
|
+
return /* @__PURE__ */ jsxs(
|
|
112
|
+
"div",
|
|
113
|
+
{
|
|
114
|
+
className: cn(
|
|
115
|
+
"flex flex-col gap-2 rounded-lg border border-border bg-card px-4 py-3 transition-colors duration-150 hover:border-border/80 hover:bg-card/80",
|
|
116
|
+
className
|
|
117
|
+
),
|
|
118
|
+
children: [
|
|
119
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
|
|
120
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
|
|
121
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium text-foreground", children: quest.title }),
|
|
122
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
|
|
123
|
+
/* @__PURE__ */ jsx("span", { className: "rounded bg-secondary px-1.5 py-0.5 font-mono text-[10px]", children: typeLabel }),
|
|
124
|
+
quest.streakCount > 0 && /* @__PURE__ */ jsxs("span", { className: "text-grind-orange", "aria-label": `${quest.streakCount} day streak`, children: [
|
|
125
|
+
"🔥 ",
|
|
126
|
+
quest.streakCount,
|
|
127
|
+
"d"
|
|
128
|
+
] })
|
|
129
|
+
] })
|
|
130
|
+
] }),
|
|
131
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-col items-end gap-1", children: [
|
|
132
|
+
/* @__PURE__ */ jsx(
|
|
133
|
+
"span",
|
|
134
|
+
{
|
|
135
|
+
className: cn("font-mono text-sm font-semibold", difficultyColor),
|
|
136
|
+
"aria-label": `Difficulty: ${quest.difficulty}`,
|
|
137
|
+
children: difficultyDots
|
|
138
|
+
}
|
|
139
|
+
),
|
|
140
|
+
totalObjectives > 0 && /* @__PURE__ */ jsxs(
|
|
141
|
+
"span",
|
|
142
|
+
{
|
|
143
|
+
className: "text-xs text-muted-foreground",
|
|
144
|
+
"aria-label": `${completedObjectives} of ${totalObjectives} objectives complete`,
|
|
145
|
+
children: [
|
|
146
|
+
completedObjectives,
|
|
147
|
+
"/",
|
|
148
|
+
totalObjectives
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
] })
|
|
153
|
+
] }),
|
|
154
|
+
quest.objectives.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: quest.objectives.map((obj) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
155
|
+
/* @__PURE__ */ jsx(ObjectiveCheck, { objective: obj }),
|
|
156
|
+
/* @__PURE__ */ jsx(
|
|
157
|
+
"span",
|
|
158
|
+
{
|
|
159
|
+
className: cn(
|
|
160
|
+
"text-xs",
|
|
161
|
+
obj.completed ? "text-muted-foreground line-through" : "text-foreground"
|
|
162
|
+
),
|
|
163
|
+
children: obj.label
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
] }, obj.id)) }),
|
|
167
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
168
|
+
"+",
|
|
169
|
+
quest.baseXp,
|
|
170
|
+
" XP base"
|
|
171
|
+
] }) })
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
const SKILL_LEVEL_THRESHOLDS = [0, 100, 300, 600, 1e3, 1500];
|
|
177
|
+
const SKILL_LEVEL_NAMES = [
|
|
178
|
+
"Novice",
|
|
179
|
+
"Apprentice",
|
|
180
|
+
"Journeyman",
|
|
181
|
+
"Expert",
|
|
182
|
+
"Master",
|
|
183
|
+
"Grandmaster"
|
|
184
|
+
];
|
|
185
|
+
const CATEGORY_COLORS = {
|
|
186
|
+
fitness: "bg-grind-xp",
|
|
187
|
+
music: "bg-[oklch(0.72_0.19_285)]",
|
|
188
|
+
academics: "bg-[oklch(0.72_0.19_220)]",
|
|
189
|
+
discipline: "bg-grind-orange",
|
|
190
|
+
life: "bg-[oklch(0.72_0.19_180)]"
|
|
191
|
+
};
|
|
192
|
+
function getSkillProgress(skill) {
|
|
193
|
+
const level = Math.min(skill.level, 5);
|
|
194
|
+
const currentThreshold = SKILL_LEVEL_THRESHOLDS[level] ?? 0;
|
|
195
|
+
const nextThreshold = SKILL_LEVEL_THRESHOLDS[level + 1] ?? 1500;
|
|
196
|
+
if (level >= 5) {
|
|
197
|
+
return { progress: 1, xpInLevel: skill.xp - currentThreshold, xpForLevel: 1 };
|
|
198
|
+
}
|
|
199
|
+
const xpInLevel = Math.max(0, skill.xp - currentThreshold);
|
|
200
|
+
const xpForLevel = nextThreshold - currentThreshold;
|
|
201
|
+
const progress = Math.min(1, xpInLevel / xpForLevel);
|
|
202
|
+
return { progress, xpInLevel, xpForLevel };
|
|
203
|
+
}
|
|
204
|
+
function LevelDots({ level }) {
|
|
205
|
+
const capped = Math.min(level, 5);
|
|
206
|
+
return /* @__PURE__ */ jsx("span", { className: "flex items-center gap-0.5", "aria-label": `Level ${capped}`, children: Array.from({ length: 5 }, (_, i) => /* @__PURE__ */ jsx(
|
|
207
|
+
"span",
|
|
208
|
+
{
|
|
209
|
+
"aria-hidden": "true",
|
|
210
|
+
className: cn("h-1.5 w-1.5 rounded-full", i < capped ? "bg-grind-orange" : "bg-border")
|
|
211
|
+
},
|
|
212
|
+
i
|
|
213
|
+
)) });
|
|
214
|
+
}
|
|
215
|
+
function SkillBar({ skill, className }) {
|
|
216
|
+
const { progress } = getSkillProgress(skill);
|
|
217
|
+
const levelName = SKILL_LEVEL_NAMES[Math.min(skill.level, 5)] ?? "Grandmaster";
|
|
218
|
+
const barColor = CATEGORY_COLORS[skill.category] ?? "bg-primary";
|
|
219
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-1.5", className), children: [
|
|
220
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
221
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
222
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium text-foreground", children: skill.name }),
|
|
223
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: levelName })
|
|
224
|
+
] }),
|
|
225
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
|
|
226
|
+
/* @__PURE__ */ jsx(LevelDots, { level: skill.level }),
|
|
227
|
+
/* @__PURE__ */ jsxs(
|
|
228
|
+
"span",
|
|
229
|
+
{
|
|
230
|
+
className: "font-mono text-xs text-muted-foreground tabular-nums",
|
|
231
|
+
style: { fontVariantNumeric: "tabular-nums" },
|
|
232
|
+
"aria-label": `${skill.xp} XP`,
|
|
233
|
+
children: [
|
|
234
|
+
skill.xp.toLocaleString(),
|
|
235
|
+
" XP"
|
|
236
|
+
]
|
|
237
|
+
}
|
|
238
|
+
)
|
|
239
|
+
] })
|
|
240
|
+
] }),
|
|
241
|
+
/* @__PURE__ */ jsx(
|
|
242
|
+
"div",
|
|
243
|
+
{
|
|
244
|
+
className: "h-1 overflow-hidden rounded-full bg-secondary",
|
|
245
|
+
role: "progressbar",
|
|
246
|
+
"aria-valuenow": Math.round(progress * 100),
|
|
247
|
+
"aria-valuemin": 0,
|
|
248
|
+
"aria-valuemax": 100,
|
|
249
|
+
"aria-label": `${skill.name} progress: ${Math.round(progress * 100)}%`,
|
|
250
|
+
children: /* @__PURE__ */ jsx(
|
|
251
|
+
"div",
|
|
252
|
+
{
|
|
253
|
+
className: cn("h-full rounded-full transition-[width] duration-500", barColor),
|
|
254
|
+
style: { width: `${progress * 100}%` }
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
)
|
|
259
|
+
] });
|
|
260
|
+
}
|
|
261
|
+
const PROOF_LABELS = {
|
|
262
|
+
"self-report": "self",
|
|
263
|
+
timestamp: "timestamp",
|
|
264
|
+
duration: "timed",
|
|
265
|
+
screenshot: "screenshot",
|
|
266
|
+
"git-commit": "git",
|
|
267
|
+
"file-change": "file",
|
|
268
|
+
"process-check": "process",
|
|
269
|
+
"ai-verify": "AI verified",
|
|
270
|
+
"calendar-match": "calendar",
|
|
271
|
+
"multi-proof": "multi"
|
|
272
|
+
};
|
|
273
|
+
const PROOF_MULTIPLIERS = {
|
|
274
|
+
"self-report": 1,
|
|
275
|
+
timestamp: 1.1,
|
|
276
|
+
duration: 1.5,
|
|
277
|
+
screenshot: 1.25,
|
|
278
|
+
"git-commit": 1.5,
|
|
279
|
+
"file-change": 1.5,
|
|
280
|
+
"process-check": 1.5,
|
|
281
|
+
"ai-verify": 1.75,
|
|
282
|
+
"calendar-match": 1.1,
|
|
283
|
+
"multi-proof": 2
|
|
284
|
+
};
|
|
285
|
+
function formatRelativeTime(ts) {
|
|
286
|
+
const now = Date.now();
|
|
287
|
+
const diff = now - ts;
|
|
288
|
+
const mins = Math.floor(diff / 6e4);
|
|
289
|
+
const hours = Math.floor(diff / 36e5);
|
|
290
|
+
const days = Math.floor(diff / 864e5);
|
|
291
|
+
if (mins < 1) return "just now";
|
|
292
|
+
if (mins < 60) return `${mins}m ago`;
|
|
293
|
+
if (hours < 24) return `${hours}h ago`;
|
|
294
|
+
if (days === 1) return "yesterday";
|
|
295
|
+
return `${days}d ago`;
|
|
296
|
+
}
|
|
297
|
+
function ActivityItemRow({ item, className }) {
|
|
298
|
+
const proofLabel = PROOF_LABELS[item.proofType] ?? item.proofType;
|
|
299
|
+
const multiplier = PROOF_MULTIPLIERS[item.proofType] ?? 1;
|
|
300
|
+
const timeLabel = formatRelativeTime(item.completedAt);
|
|
301
|
+
const durationStr = item.durationMinutes != null ? `${item.durationMinutes}m` : null;
|
|
302
|
+
return /* @__PURE__ */ jsxs(
|
|
303
|
+
"div",
|
|
304
|
+
{
|
|
305
|
+
className: cn(
|
|
306
|
+
"flex items-center gap-3 rounded-lg px-3 py-2.5 transition-colors duration-150 hover:bg-secondary/40",
|
|
307
|
+
className
|
|
308
|
+
),
|
|
309
|
+
children: [
|
|
310
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "shrink-0 text-base", children: "✅" }),
|
|
311
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
|
|
312
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-sm text-foreground", children: item.questTitle }),
|
|
313
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
314
|
+
/* @__PURE__ */ jsx("time", { dateTime: new Date(item.completedAt).toISOString(), children: timeLabel }),
|
|
315
|
+
durationStr && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
316
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "·" }),
|
|
317
|
+
/* @__PURE__ */ jsx("span", { children: durationStr })
|
|
318
|
+
] }),
|
|
319
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "·" }),
|
|
320
|
+
/* @__PURE__ */ jsxs(
|
|
321
|
+
"span",
|
|
322
|
+
{
|
|
323
|
+
className: cn(
|
|
324
|
+
"rounded px-1 py-0.5 font-mono text-[10px]",
|
|
325
|
+
item.proofType === "ai-verify" ? "bg-[oklch(0.72_0.19_285)]/10 text-[oklch(0.72_0.19_285)]" : item.proofType === "duration" || item.proofType === "multi-proof" ? "bg-grind-xp/10 text-grind-xp" : "bg-secondary text-muted-foreground"
|
|
326
|
+
),
|
|
327
|
+
children: [
|
|
328
|
+
proofLabel,
|
|
329
|
+
multiplier > 1 && ` ${multiplier}×`
|
|
330
|
+
]
|
|
331
|
+
}
|
|
332
|
+
)
|
|
333
|
+
] })
|
|
334
|
+
] }),
|
|
335
|
+
/* @__PURE__ */ jsxs("div", { className: "shrink-0 text-right", children: [
|
|
336
|
+
/* @__PURE__ */ jsxs(
|
|
337
|
+
"span",
|
|
338
|
+
{
|
|
339
|
+
className: "font-mono text-sm font-semibold text-grind-xp tabular-nums",
|
|
340
|
+
style: { fontVariantNumeric: "tabular-nums" },
|
|
341
|
+
children: [
|
|
342
|
+
"+",
|
|
343
|
+
item.xpEarned
|
|
344
|
+
]
|
|
345
|
+
}
|
|
346
|
+
),
|
|
347
|
+
/* @__PURE__ */ jsx("span", { className: "ml-0.5 text-xs text-muted-foreground", children: "XP" })
|
|
348
|
+
] })
|
|
349
|
+
]
|
|
350
|
+
}
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
const LEVEL_TITLES = {
|
|
354
|
+
1: "Newcomer",
|
|
355
|
+
2: "Initiate",
|
|
356
|
+
3: "Apprentice",
|
|
357
|
+
4: "Journeyman",
|
|
358
|
+
5: "Adept",
|
|
359
|
+
6: "Expert",
|
|
360
|
+
7: "Veteran",
|
|
361
|
+
8: "Master",
|
|
362
|
+
9: "Grandmaster",
|
|
363
|
+
10: "Legend"
|
|
364
|
+
};
|
|
365
|
+
function xpForLevelThreshold(level) {
|
|
366
|
+
if (level <= 1) return 0;
|
|
367
|
+
return 50 * level * level + 50 * level;
|
|
368
|
+
}
|
|
369
|
+
function getLevelProgress(totalXp, level) {
|
|
370
|
+
const current = xpForLevelThreshold(level);
|
|
371
|
+
const next = xpForLevelThreshold(level + 1);
|
|
372
|
+
const xpInLevel = Math.max(0, totalXp - current);
|
|
373
|
+
const xpForLevel = next - current;
|
|
374
|
+
return {
|
|
375
|
+
progress: Math.min(1, xpInLevel / xpForLevel),
|
|
376
|
+
xpToNext: Math.max(0, next - totalXp)
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
function DashboardPage() {
|
|
380
|
+
const data = Route.useLoaderData();
|
|
381
|
+
const {
|
|
382
|
+
user,
|
|
383
|
+
xpToday,
|
|
384
|
+
bestStreak,
|
|
385
|
+
questsCompletedTotal,
|
|
386
|
+
activeQuests,
|
|
387
|
+
topSkills,
|
|
388
|
+
recentActivity
|
|
389
|
+
} = data;
|
|
390
|
+
const levelTitle = LEVEL_TITLES[user.level] ?? `Lv.${user.level}`;
|
|
391
|
+
const {
|
|
392
|
+
progress: levelProgress,
|
|
393
|
+
xpToNext
|
|
394
|
+
} = getLevelProgress(user.totalXp, user.level);
|
|
395
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col", children: [
|
|
396
|
+
/* @__PURE__ */ jsx("header", { className: "flex h-14 shrink-0 items-center border-b border-border bg-sidebar px-6", children: /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center justify-between gap-4", children: [
|
|
397
|
+
/* @__PURE__ */ jsxs("div", { className: "md:ml-8 flex items-center gap-2", children: [
|
|
398
|
+
/* @__PURE__ */ jsxs("span", { className: "font-mono text-xs text-muted-foreground", children: [
|
|
399
|
+
"Lv.",
|
|
400
|
+
user.level
|
|
401
|
+
] }),
|
|
402
|
+
/* @__PURE__ */ jsx("h1", { className: "font-display text-lg text-foreground", children: levelTitle }),
|
|
403
|
+
/* @__PURE__ */ jsxs("span", { className: "text-sm text-muted-foreground", children: [
|
|
404
|
+
"— ",
|
|
405
|
+
user.displayName
|
|
406
|
+
] })
|
|
407
|
+
] }),
|
|
408
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
409
|
+
xpToNext.toLocaleString(),
|
|
410
|
+
" XP to next level"
|
|
411
|
+
] }),
|
|
412
|
+
/* @__PURE__ */ jsx("div", { className: "hidden w-48 sm:block", children: /* @__PURE__ */ jsx("div", { className: "h-1.5 overflow-hidden rounded-full bg-secondary", role: "progressbar", "aria-valuenow": Math.round(levelProgress * 100), "aria-valuemin": 0, "aria-valuemax": 100, "aria-label": `Level progress: ${Math.round(levelProgress * 100)}%`, children: /* @__PURE__ */ jsx("div", { className: "h-full rounded-full bg-grind-orange transition-[width] duration-700", style: {
|
|
413
|
+
width: `${levelProgress * 100}%`
|
|
414
|
+
} }) }) })
|
|
415
|
+
] }) }),
|
|
416
|
+
/* @__PURE__ */ jsx("main", { className: "flex-1 overflow-y-auto p-6", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-6xl space-y-6", children: [
|
|
417
|
+
/* @__PURE__ */ jsx("section", { "aria-label": "Stats", children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-4", children: [
|
|
418
|
+
/* @__PURE__ */ jsx(StatCard, { label: "Total XP", value: user.totalXp.toLocaleString(), icon: /* @__PURE__ */ jsx(StarIcon, { size: 14, "aria-hidden": "true" }), accent: "orange" }),
|
|
419
|
+
/* @__PURE__ */ jsx(StatCard, { label: "XP Today", value: `+${xpToday.toLocaleString()}`, icon: /* @__PURE__ */ jsx(LightningIcon, { size: 14, "aria-hidden": "true" }), accent: "green" }),
|
|
420
|
+
/* @__PURE__ */ jsx(StatCard, { label: "Best Streak", value: bestStreak, sub: bestStreak > 0 ? `${bestStreak} day${bestStreak === 1 ? "" : "s"}` : "No active streak", icon: /* @__PURE__ */ jsx(FlameIcon, { size: 14, "aria-hidden": "true" }), accent: bestStreak > 0 ? "orange" : "default" }),
|
|
421
|
+
/* @__PURE__ */ jsx(StatCard, { label: "Completed", value: questsCompletedTotal.toLocaleString(), sub: "quest logs", icon: /* @__PURE__ */ jsx(TrophyIcon, { size: 14, "aria-hidden": "true" }), accent: "default" })
|
|
422
|
+
] }) }),
|
|
423
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-5 lg:grid-cols-2", children: [
|
|
424
|
+
/* @__PURE__ */ jsxs("section", { "aria-labelledby": "active-quests-heading", children: [
|
|
425
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 mb-3", children: [
|
|
426
|
+
/* @__PURE__ */ jsxs("h2", { id: "active-quests-heading", className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
|
|
427
|
+
/* @__PURE__ */ jsx(SwordIcon, { size: 16, weight: "fill", className: "text-grind-orange", "aria-hidden": "true" }),
|
|
428
|
+
"Active Quests"
|
|
429
|
+
] }),
|
|
430
|
+
activeQuests.length > 0 && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
431
|
+
activeQuests.length,
|
|
432
|
+
"/5"
|
|
433
|
+
] })
|
|
434
|
+
] }),
|
|
435
|
+
activeQuests.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3 rounded-xl border border-dashed border-border py-8 text-center", children: [
|
|
436
|
+
/* @__PURE__ */ jsx(SwordIcon, { size: 28, className: "text-muted-foreground/40", "aria-hidden": "true" }),
|
|
437
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
438
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "No active quests" }),
|
|
439
|
+
/* @__PURE__ */ jsxs("p", { className: "mt-0.5 text-xs text-muted-foreground/70", children: [
|
|
440
|
+
"Ask the",
|
|
441
|
+
" ",
|
|
442
|
+
/* @__PURE__ */ jsx(Link, { to: "/app/chat", search: {
|
|
443
|
+
c: void 0
|
|
444
|
+
}, className: "text-grind-orange underline-offset-2 hover:underline focus-visible:underline focus-visible:outline-none", children: "Companion" }),
|
|
445
|
+
" ",
|
|
446
|
+
"to create some"
|
|
447
|
+
] })
|
|
448
|
+
] })
|
|
449
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: activeQuests.map((quest) => /* @__PURE__ */ jsx(QuestRow, { quest }, quest.id)) })
|
|
450
|
+
] }),
|
|
451
|
+
/* @__PURE__ */ jsxs("section", { "aria-labelledby": "skills-heading", children: [
|
|
452
|
+
/* @__PURE__ */ jsxs("h2", { id: "skills-heading", className: "mb-3 flex items-center gap-2 text-sm font-medium text-foreground", children: [
|
|
453
|
+
/* @__PURE__ */ jsx(LightningIcon, { size: 16, weight: "fill", className: "text-grind-orange", "aria-hidden": "true" }),
|
|
454
|
+
"Skills"
|
|
455
|
+
] }),
|
|
456
|
+
topSkills.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3 rounded-xl border border-dashed border-border py-8 text-center", children: [
|
|
457
|
+
/* @__PURE__ */ jsx(LightningIcon, { size: 28, className: "text-muted-foreground/40", "aria-hidden": "true" }),
|
|
458
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Skills appear as you complete quests" })
|
|
459
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-4 rounded-xl border border-border bg-card p-4", children: topSkills.map((skill) => /* @__PURE__ */ jsx(SkillBar, { skill }, skill.id)) })
|
|
460
|
+
] })
|
|
461
|
+
] }),
|
|
462
|
+
/* @__PURE__ */ jsxs("section", { "aria-labelledby": "activity-heading", children: [
|
|
463
|
+
/* @__PURE__ */ jsxs("h2", { id: "activity-heading", className: "mb-3 flex items-center gap-2 text-sm font-medium text-foreground", children: [
|
|
464
|
+
/* @__PURE__ */ jsx(FlameIcon, { size: 16, weight: "fill", className: "text-grind-orange", "aria-hidden": "true" }),
|
|
465
|
+
"Recent Activity"
|
|
466
|
+
] }),
|
|
467
|
+
recentActivity.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3 rounded-xl border border-dashed border-border py-8 text-center", children: [
|
|
468
|
+
/* @__PURE__ */ jsx(FlameIcon, { size: 28, className: "text-muted-foreground/40", "aria-hidden": "true" }),
|
|
469
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Your completed quests will appear here" })
|
|
470
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "rounded-xl border border-border bg-card", children: recentActivity.map((item, idx) => /* @__PURE__ */ jsx("div", { className: idx < recentActivity.length - 1 ? "border-b border-border/50" : void 0, children: /* @__PURE__ */ jsx(ActivityItemRow, { item }) }, item.id)) })
|
|
471
|
+
] })
|
|
472
|
+
] }) })
|
|
473
|
+
] });
|
|
474
|
+
}
|
|
475
|
+
export {
|
|
476
|
+
DashboardPage as component
|
|
477
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { RobotIcon } from "@phosphor-icons/react";
|
|
3
|
+
function CompanionPage() {
|
|
4
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col items-center justify-center gap-4 p-8 text-center", children: [
|
|
5
|
+
/* @__PURE__ */ jsx("div", { className: "flex h-16 w-16 items-center justify-center rounded-full border border-border bg-card", children: /* @__PURE__ */ jsx(RobotIcon, { size: 28, className: "text-muted-foreground", "aria-hidden": "true" }) }),
|
|
6
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
7
|
+
/* @__PURE__ */ jsx("h1", { className: "text-lg font-semibold text-foreground", children: "Companion" }),
|
|
8
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: "Coming soon…" })
|
|
9
|
+
] })
|
|
10
|
+
] });
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
CompanionPage as component
|
|
14
|
+
};
|