@grindxp/cli 0.1.7 → 0.1.9

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 (57) hide show
  1. package/dist/index.js +766 -134
  2. package/dist/web/client/assets/Copy.es-Bs4NgJu-.js +1 -0
  3. package/dist/web/client/assets/Sword.es-2Xm7T3t2.js +1 -0
  4. package/dist/web/client/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
  5. package/dist/web/client/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
  6. package/dist/web/client/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
  7. package/dist/web/client/assets/index-6XDcqRbL.js +42 -0
  8. package/dist/web/client/assets/index-BXM1N6tm.js +1 -0
  9. package/dist/web/client/assets/index-B_KMiE38.js +1 -0
  10. package/dist/web/client/assets/index-CGj2rOLm.js +1 -0
  11. package/dist/web/client/assets/index-CS5BuFbt.js +1 -0
  12. package/dist/web/client/assets/index-CYsASiu-.js +1 -0
  13. package/dist/web/client/assets/index-DAvwM0SX.js +1 -0
  14. package/dist/web/client/assets/index-DCBFp5DJ.js +1 -0
  15. package/dist/web/client/assets/index-DjKt1qNz.js +1 -0
  16. package/dist/web/client/assets/index-PIcFs1vr.js +1 -0
  17. package/dist/web/client/assets/instrument-serif-latin-400-italic-DKMiL14s.woff2 +0 -0
  18. package/dist/web/client/assets/instrument-serif-latin-400-italic-u__WvvIK.woff +0 -0
  19. package/dist/web/client/assets/instrument-serif-latin-400-normal-BVbkICAY.woff +0 -0
  20. package/dist/web/client/assets/instrument-serif-latin-400-normal-DnYpCC2O.woff2 +0 -0
  21. package/dist/web/client/assets/instrument-serif-latin-ext-400-italic-C9HzH3YL.woff2 +0 -0
  22. package/dist/web/client/assets/instrument-serif-latin-ext-400-italic-D7-lnxEk.woff +0 -0
  23. package/dist/web/client/assets/instrument-serif-latin-ext-400-normal-C2je3j2s.woff2 +0 -0
  24. package/dist/web/client/assets/instrument-serif-latin-ext-400-normal-CFCUzsTy.woff +0 -0
  25. package/dist/web/client/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  26. package/dist/web/client/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  27. package/dist/web/client/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  28. package/dist/web/client/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  29. package/dist/web/client/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  30. package/dist/web/client/assets/main-BI1EOhmt.js +18 -0
  31. package/dist/web/client/assets/styles-7TpWqjrh.css +1 -0
  32. package/dist/web/client/favicon.ico +0 -0
  33. package/dist/web/server/assets/_tanstack-start-manifest_v-B_rvI8DG.js +4 -0
  34. package/dist/web/server/assets/agent.functions-BL3upUNr.js +19541 -0
  35. package/dist/web/server/assets/data.functions-DZmdFOMQ.js +285 -0
  36. package/dist/web/server/assets/index-4SxmUYH6.js +14 -0
  37. package/dist/web/server/assets/index-B2ULpkv2.js +4587 -0
  38. package/dist/web/server/assets/index-BGBMycx-.js +2275 -0
  39. package/dist/web/server/assets/index-BL8u2X7w.js +14 -0
  40. package/dist/web/server/assets/index-BQUCDamI.js +5924 -0
  41. package/dist/web/server/assets/index-BRRsXrOi.js +14 -0
  42. package/dist/web/server/assets/index-BiD7uOOh.js +14 -0
  43. package/dist/web/server/assets/index-CB8UtTN8.js +66 -0
  44. package/dist/web/server/assets/index-D2yaimYL.js +14 -0
  45. package/dist/web/server/assets/index-D3RUqTdb.js +14 -0
  46. package/dist/web/server/assets/index-DTB2dYCz.js +1426 -0
  47. package/dist/web/server/assets/index-DfU25rnD.js +477 -0
  48. package/dist/web/server/assets/index-SHH7zSKt.js +66 -0
  49. package/dist/web/server/assets/router-CXyGzWDS.js +589 -0
  50. package/dist/web/server/assets/sessions-UCWtijHE.js +438 -0
  51. package/dist/web/server/assets/start-HYkvq4Ni.js +4 -0
  52. package/dist/web/server/assets/token-DGoahKjI.js +86 -0
  53. package/dist/web/server/assets/token-util-BopJPy-I.js +451 -0
  54. package/dist/web/server/assets/token-util-Bw35afYM.js +30 -0
  55. package/dist/web/server/assets/vault.server-CscY5Z8e.js +19357 -0
  56. package/dist/web/server/server.js +4889 -0
  57. 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-CXyGzWDS.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-CscY5Z8e.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,66 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useState, useCallback } from "react";
3
+ import { LightningIcon, CopyIcon, CheckIcon } from "@phosphor-icons/react";
4
+ import { c as cn } from "./router-CXyGzWDS.js";
5
+ import "@tanstack/react-router";
6
+ import "@tanstack/react-query";
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-CscY5Z8e.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
+ function CopyCommandBlock({
33
+ command
34
+ }) {
35
+ const [copied, setCopied] = useState(false);
36
+ const handleCopy = useCallback(() => {
37
+ navigator.clipboard.writeText(command).then(() => {
38
+ setCopied(true);
39
+ setTimeout(() => setCopied(false), 2e3);
40
+ });
41
+ }, [command]);
42
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-border bg-card pl-4 pr-3 py-3", children: [
43
+ /* @__PURE__ */ jsx("code", { className: "flex-1 min-w-0 break-all font-mono text-sm text-grind-orange", children: command }),
44
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: handleCopy, "aria-label": copied ? "Copied" : `Copy command: ${command}`, className: "flex shrink-0 items-center justify-center rounded-md p-1 text-muted-foreground [touch-action:manipulation] transition-[transform,background-color,color] duration-150 active:scale-[0.97] hover:bg-secondary hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: /* @__PURE__ */ jsxs("span", { className: "relative flex size-4 items-center justify-center", children: [
45
+ /* @__PURE__ */ jsx(CopyIcon, { size: 14, "aria-hidden": "true", className: cn("absolute motion-safe:transition-opacity duration-200", copied ? "opacity-0" : "opacity-100") }),
46
+ /* @__PURE__ */ jsx(CheckIcon, { size: 14, "aria-hidden": "true", className: cn("absolute text-grind-xp motion-safe:transition-opacity duration-200", copied ? "opacity-100" : "opacity-0") })
47
+ ] }) })
48
+ ] });
49
+ }
50
+ function ChatError({
51
+ error
52
+ }) {
53
+ const msg = error instanceof Error ? error.message : "Something went wrong";
54
+ const isNotInit = msg.includes("not initialized");
55
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col items-center justify-center gap-4 p-8 text-center", children: [
56
+ /* @__PURE__ */ jsx("div", { className: "flex h-16 w-16 items-center justify-center rounded-full border border-border bg-card", children: /* @__PURE__ */ jsx(LightningIcon, { size: 28, className: "text-muted-foreground", "aria-hidden": "true" }) }),
57
+ /* @__PURE__ */ jsxs("div", { children: [
58
+ /* @__PURE__ */ jsx("h1", { className: "text-lg font-semibold text-foreground", children: isNotInit ? "Grind Not Initialized" : "Failed to Load Chat" }),
59
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: isNotInit ? "Run the command below in your terminal to get started." : msg })
60
+ ] }),
61
+ isNotInit && /* @__PURE__ */ jsx(CopyCommandBlock, { command: "grindxp init" })
62
+ ] });
63
+ }
64
+ export {
65
+ ChatError as errorComponent
66
+ };