visionos-monorepo 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 (160) hide show
  1. package/.claude/worktrees/competent-burnell-8d1330/README.md +138 -0
  2. package/.claude/worktrees/competent-burnell-8d1330/cli/package.json +35 -0
  3. package/.claude/worktrees/competent-burnell-8d1330/cli/scripts/copy-web-assets.mjs +12 -0
  4. package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/logout.ts +12 -0
  5. package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/open.ts +19 -0
  6. package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/start.ts +97 -0
  7. package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/status.ts +23 -0
  8. package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/userinfo.ts +47 -0
  9. package/.claude/worktrees/competent-burnell-8d1330/cli/src/index.ts +23 -0
  10. package/.claude/worktrees/competent-burnell-8d1330/cli/src/lib/auth.ts +84 -0
  11. package/.claude/worktrees/competent-burnell-8d1330/cli/src/lib/browser.ts +37 -0
  12. package/.claude/worktrees/competent-burnell-8d1330/cli/src/lib/localState.ts +80 -0
  13. package/.claude/worktrees/competent-burnell-8d1330/cli/src/lib/runtime.ts +203 -0
  14. package/.claude/worktrees/competent-burnell-8d1330/cli/src/runtime/index.ts +36 -0
  15. package/.claude/worktrees/competent-burnell-8d1330/cli/src/types/inquirer.d.ts +9 -0
  16. package/.claude/worktrees/competent-burnell-8d1330/cli/tsconfig.json +19 -0
  17. package/.claude/worktrees/competent-burnell-8d1330/client/index.html +15 -0
  18. package/.claude/worktrees/competent-burnell-8d1330/client/package.json +27 -0
  19. package/.claude/worktrees/competent-burnell-8d1330/client/postcss.config.cjs +7 -0
  20. package/.claude/worktrees/competent-burnell-8d1330/client/src/App.tsx +57 -0
  21. package/.claude/worktrees/competent-burnell-8d1330/client/src/components/CliAuthPage.tsx +385 -0
  22. package/.claude/worktrees/competent-burnell-8d1330/client/src/components/ManifestoPage.tsx +946 -0
  23. package/.claude/worktrees/competent-burnell-8d1330/client/src/components/TrackCard.tsx +19 -0
  24. package/.claude/worktrees/competent-burnell-8d1330/client/src/lib/api.ts +58 -0
  25. package/.claude/worktrees/competent-burnell-8d1330/client/src/main.tsx +11 -0
  26. package/.claude/worktrees/competent-burnell-8d1330/client/src/styles/index.css +33 -0
  27. package/.claude/worktrees/competent-burnell-8d1330/client/src/styles/manifesto.css +1398 -0
  28. package/.claude/worktrees/competent-burnell-8d1330/client/tailwind.config.ts +36 -0
  29. package/.claude/worktrees/competent-burnell-8d1330/client/tsconfig.json +25 -0
  30. package/.claude/worktrees/competent-burnell-8d1330/client/vite.config.ts +20 -0
  31. package/.claude/worktrees/competent-burnell-8d1330/package-lock.json +5278 -0
  32. package/.claude/worktrees/competent-burnell-8d1330/package.json +24 -0
  33. package/.claude/worktrees/competent-burnell-8d1330/server/package.json +25 -0
  34. package/.claude/worktrees/competent-burnell-8d1330/server/src/app.ts +71 -0
  35. package/.claude/worktrees/competent-burnell-8d1330/server/src/config/env.ts +14 -0
  36. package/.claude/worktrees/competent-burnell-8d1330/server/src/features/auth/sessionStore.ts +74 -0
  37. package/.claude/worktrees/competent-burnell-8d1330/server/src/index.ts +8 -0
  38. package/.claude/worktrees/competent-burnell-8d1330/server/src/routes/auth.ts +112 -0
  39. package/.claude/worktrees/competent-burnell-8d1330/server/src/routes/health.ts +14 -0
  40. package/.claude/worktrees/competent-burnell-8d1330/server/tsconfig.json +19 -0
  41. package/.claude/worktrees/competent-burnell-8d1330/shared/package.json +24 -0
  42. package/.claude/worktrees/competent-burnell-8d1330/shared/src/index.ts +91 -0
  43. package/.claude/worktrees/competent-burnell-8d1330/shared/tsconfig.json +16 -0
  44. package/.claude/worktrees/competent-burnell-8d1330/tsconfig.base.json +12 -0
  45. package/.claude/worktrees/competent-burnell-8d1330/visionos-manifesto/index.html +392 -0
  46. package/.claude/worktrees/competent-burnell-8d1330/visionos-manifesto/script.js +146 -0
  47. package/.claude/worktrees/competent-burnell-8d1330/visionos-manifesto/styles.css +1082 -0
  48. package/.claude/worktrees/vigilant-napier-0de76f/README.md +138 -0
  49. package/.claude/worktrees/vigilant-napier-0de76f/cli/package.json +35 -0
  50. package/.claude/worktrees/vigilant-napier-0de76f/cli/scripts/copy-web-assets.mjs +12 -0
  51. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/logout.ts +12 -0
  52. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/open.ts +19 -0
  53. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/start.ts +97 -0
  54. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/status.ts +23 -0
  55. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/userinfo.ts +47 -0
  56. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/index.ts +23 -0
  57. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/lib/auth.ts +84 -0
  58. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/lib/browser.ts +37 -0
  59. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/lib/localState.ts +80 -0
  60. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/lib/runtime.ts +203 -0
  61. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/runtime/index.ts +36 -0
  62. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/types/inquirer.d.ts +9 -0
  63. package/.claude/worktrees/vigilant-napier-0de76f/cli/tsconfig.json +19 -0
  64. package/.claude/worktrees/vigilant-napier-0de76f/client/index.html +15 -0
  65. package/.claude/worktrees/vigilant-napier-0de76f/client/package.json +27 -0
  66. package/.claude/worktrees/vigilant-napier-0de76f/client/postcss.config.cjs +7 -0
  67. package/.claude/worktrees/vigilant-napier-0de76f/client/src/App.tsx +57 -0
  68. package/.claude/worktrees/vigilant-napier-0de76f/client/src/components/CliAuthPage.tsx +385 -0
  69. package/.claude/worktrees/vigilant-napier-0de76f/client/src/components/ManifestoPage.tsx +946 -0
  70. package/.claude/worktrees/vigilant-napier-0de76f/client/src/components/TrackCard.tsx +19 -0
  71. package/.claude/worktrees/vigilant-napier-0de76f/client/src/lib/api.ts +58 -0
  72. package/.claude/worktrees/vigilant-napier-0de76f/client/src/main.tsx +11 -0
  73. package/.claude/worktrees/vigilant-napier-0de76f/client/src/styles/index.css +33 -0
  74. package/.claude/worktrees/vigilant-napier-0de76f/client/src/styles/manifesto.css +1398 -0
  75. package/.claude/worktrees/vigilant-napier-0de76f/client/tailwind.config.ts +36 -0
  76. package/.claude/worktrees/vigilant-napier-0de76f/client/tsconfig.json +25 -0
  77. package/.claude/worktrees/vigilant-napier-0de76f/client/vite.config.ts +20 -0
  78. package/.claude/worktrees/vigilant-napier-0de76f/package-lock.json +5278 -0
  79. package/.claude/worktrees/vigilant-napier-0de76f/package.json +24 -0
  80. package/.claude/worktrees/vigilant-napier-0de76f/server/package.json +25 -0
  81. package/.claude/worktrees/vigilant-napier-0de76f/server/src/app.ts +71 -0
  82. package/.claude/worktrees/vigilant-napier-0de76f/server/src/config/env.ts +14 -0
  83. package/.claude/worktrees/vigilant-napier-0de76f/server/src/features/auth/sessionStore.ts +74 -0
  84. package/.claude/worktrees/vigilant-napier-0de76f/server/src/index.ts +8 -0
  85. package/.claude/worktrees/vigilant-napier-0de76f/server/src/routes/auth.ts +112 -0
  86. package/.claude/worktrees/vigilant-napier-0de76f/server/src/routes/health.ts +14 -0
  87. package/.claude/worktrees/vigilant-napier-0de76f/server/tsconfig.json +19 -0
  88. package/.claude/worktrees/vigilant-napier-0de76f/shared/package.json +24 -0
  89. package/.claude/worktrees/vigilant-napier-0de76f/shared/src/index.ts +91 -0
  90. package/.claude/worktrees/vigilant-napier-0de76f/shared/tsconfig.json +16 -0
  91. package/.claude/worktrees/vigilant-napier-0de76f/tsconfig.base.json +12 -0
  92. package/.claude/worktrees/vigilant-napier-0de76f/visionos-manifesto/index.html +392 -0
  93. package/.claude/worktrees/vigilant-napier-0de76f/visionos-manifesto/script.js +146 -0
  94. package/.claude/worktrees/vigilant-napier-0de76f/visionos-manifesto/styles.css +1082 -0
  95. package/.github/workflows/publish.yml +30 -0
  96. package/README.md +175 -0
  97. package/cli/README.md +165 -0
  98. package/cli/package.json +36 -0
  99. package/cli/scripts/copy-web-assets.mjs +12 -0
  100. package/cli/src/commands/lessons.ts +68 -0
  101. package/cli/src/commands/login.ts +46 -0
  102. package/cli/src/commands/logout.ts +12 -0
  103. package/cli/src/commands/open.ts +29 -0
  104. package/cli/src/commands/start.ts +146 -0
  105. package/cli/src/commands/status.ts +28 -0
  106. package/cli/src/commands/userinfo.ts +59 -0
  107. package/cli/src/index.ts +109 -0
  108. package/cli/src/lib/auth.ts +84 -0
  109. package/cli/src/lib/browser.ts +37 -0
  110. package/cli/src/lib/content.ts +57 -0
  111. package/cli/src/lib/lessonPrinter.ts +38 -0
  112. package/cli/src/lib/lessonRunner.ts +381 -0
  113. package/cli/src/lib/localState.ts +114 -0
  114. package/cli/src/lib/loginFlow.ts +74 -0
  115. package/cli/src/lib/progress.ts +94 -0
  116. package/cli/src/lib/runtime.ts +220 -0
  117. package/cli/src/lib/validator.ts +401 -0
  118. package/cli/src/runtime/index.ts +108 -0
  119. package/cli/src/types/inquirer.d.ts +9 -0
  120. package/cli/tsconfig.json +19 -0
  121. package/client/index.html +15 -0
  122. package/client/package.json +27 -0
  123. package/client/postcss.config.cjs +7 -0
  124. package/client/src/App.tsx +102 -0
  125. package/client/src/components/AccountPage.tsx +79 -0
  126. package/client/src/components/AuthPanel.tsx +312 -0
  127. package/client/src/components/CliAuthPage.tsx +367 -0
  128. package/client/src/components/CreatorPortal.tsx +885 -0
  129. package/client/src/components/ErrorBoundary.tsx +92 -0
  130. package/client/src/components/ManifestoPage.tsx +1126 -0
  131. package/client/src/components/TrackCard.tsx +19 -0
  132. package/client/src/lib/api.ts +215 -0
  133. package/client/src/main.tsx +14 -0
  134. package/client/src/styles/index.css +33 -0
  135. package/client/src/styles/manifesto.css +1828 -0
  136. package/client/tailwind.config.ts +36 -0
  137. package/client/tsconfig.json +25 -0
  138. package/client/vercel.json +8 -0
  139. package/client/vite.config.ts +33 -0
  140. package/package.json +27 -0
  141. package/server/package.json +26 -0
  142. package/server/src/app.ts +132 -0
  143. package/server/src/config/env.ts +135 -0
  144. package/server/src/features/accounts/accountStore.ts +359 -0
  145. package/server/src/features/accounts/contentStore.ts +264 -0
  146. package/server/src/features/accounts/password.ts +26 -0
  147. package/server/src/features/auth/sessionStore.ts +79 -0
  148. package/server/src/index.ts +8 -0
  149. package/server/src/routes/auth.ts +328 -0
  150. package/server/src/routes/content.ts +174 -0
  151. package/server/src/routes/health.ts +14 -0
  152. package/server/src/routes/progress.ts +105 -0
  153. package/server/tsconfig.json +19 -0
  154. package/shared/package.json +24 -0
  155. package/shared/src/index.ts +455 -0
  156. package/shared/tsconfig.json +16 -0
  157. package/tsconfig.base.json +12 -0
  158. package/visionos-manifesto/index.html +392 -0
  159. package/visionos-manifesto/script.js +146 -0
  160. package/visionos-manifesto/styles.css +1082 -0
@@ -0,0 +1,885 @@
1
+ import { useEffect, useState } from "react";
2
+ import {
3
+ type LearningTrack,
4
+ type LearningLesson,
5
+ type LessonStep,
6
+ type VisionOsUserProfile,
7
+ type LessonDifficulty
8
+ } from "../../../shared/src/index.js";
9
+ import {
10
+ getTracks,
11
+ getLessons,
12
+ createTrack,
13
+ updateTrack,
14
+ createLesson,
15
+ updateLesson
16
+ } from "../lib/api.js";
17
+
18
+ interface CreatorPortalProps {
19
+ user: VisionOsUserProfile | null;
20
+ }
21
+
22
+ export function CreatorPortal({ user }: CreatorPortalProps) {
23
+ const [tracks, setTracks] = useState<LearningTrack[]>([]);
24
+ const [selectedTrack, setSelectedTrack] = useState<LearningTrack | null>(null);
25
+ const [lessons, setLessons] = useState<LearningLesson[]>([]);
26
+ const [selectedLesson, setSelectedLesson] = useState<LearningLesson | null>(null);
27
+
28
+ // Forms
29
+ const [isCreatingTrack, setIsCreatingTrack] = useState(false);
30
+ const [trackForm, setTrackForm] = useState({ id: "", label: "", description: "" });
31
+ const [trackError, setTrackError] = useState("");
32
+
33
+ const [isEditingTrack, setIsEditingTrack] = useState(false);
34
+ const [editTrackForm, setEditTrackForm] = useState({ label: "", description: "" });
35
+
36
+ const [isEditingLesson, setIsEditingLesson] = useState(false);
37
+ const [lessonForm, setLessonForm] = useState<{
38
+ id: string;
39
+ title: string;
40
+ difficulty: LessonDifficulty;
41
+ summary: string;
42
+ estimatedMinutes: number;
43
+ steps: LessonStep[];
44
+ }>({
45
+ id: "",
46
+ title: "",
47
+ difficulty: "beginner",
48
+ summary: "",
49
+ estimatedMinutes: 10,
50
+ steps: []
51
+ });
52
+ const [lessonError, setLessonError] = useState("");
53
+
54
+ // Load tracks initially
55
+ useEffect(() => {
56
+ loadTracks();
57
+ }, []);
58
+
59
+ async function loadTracks() {
60
+ try {
61
+ const res = await getTracks();
62
+ setTracks(res.tracks);
63
+ } catch (err) {
64
+ console.error("Failed to load tracks:", err);
65
+ }
66
+ }
67
+
68
+ // Load lessons when selected track changes
69
+ useEffect(() => {
70
+ if (selectedTrack) {
71
+ loadLessons(selectedTrack.id);
72
+ setIsCreatingTrack(false);
73
+ setIsEditingTrack(false);
74
+ setIsEditingLesson(false);
75
+ setSelectedLesson(null);
76
+ } else {
77
+ setLessons([]);
78
+ }
79
+ }, [selectedTrack]);
80
+
81
+ async function loadLessons(trackId: string) {
82
+ try {
83
+ const res = await getLessons(trackId);
84
+ setLessons(res.lessons);
85
+ } catch (err) {
86
+ console.error("Failed to load lessons:", err);
87
+ }
88
+ }
89
+
90
+ async function handleCreateTrack(e: React.FormEvent) {
91
+ e.preventDefault();
92
+ setTrackError("");
93
+ try {
94
+ const cleanId = trackForm.id.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "");
95
+ if (!cleanId) {
96
+ setTrackError("Track ID cannot be empty and must be alphanumeric.");
97
+ return;
98
+ }
99
+ const res = await createTrack({
100
+ id: cleanId,
101
+ label: trackForm.label.trim(),
102
+ description: trackForm.description.trim()
103
+ });
104
+ await loadTracks();
105
+ setSelectedTrack(res.track);
106
+ setIsCreatingTrack(false);
107
+ setTrackForm({ id: "", label: "", description: "" });
108
+ } catch (err: any) {
109
+ setTrackError(err.message ?? "Failed to create track.");
110
+ }
111
+ }
112
+
113
+ async function handleUpdateTrack(e: React.FormEvent) {
114
+ e.preventDefault();
115
+ if (!selectedTrack) return;
116
+ try {
117
+ const res = await updateTrack(selectedTrack.id, {
118
+ label: editTrackForm.label.trim(),
119
+ description: editTrackForm.description.trim()
120
+ });
121
+ await loadTracks();
122
+ setSelectedTrack(res.track);
123
+ setIsEditingTrack(false);
124
+ } catch (err: any) {
125
+ alert(err.message ?? "Failed to update track.");
126
+ }
127
+ }
128
+
129
+ function startCreateLesson() {
130
+ setLessonError("");
131
+ setLessonForm({
132
+ id: "",
133
+ title: "",
134
+ difficulty: "beginner",
135
+ summary: "",
136
+ estimatedMinutes: 10,
137
+ steps: [
138
+ {
139
+ id: "",
140
+ title: "",
141
+ objective: "",
142
+ command: "",
143
+ explanation: "",
144
+ validationType: "syntax"
145
+ }
146
+ ]
147
+ });
148
+ setIsEditingLesson(true);
149
+ setSelectedLesson(null);
150
+ }
151
+
152
+ function startEditLesson(lesson: LearningLesson) {
153
+ setLessonError("");
154
+ setLessonForm({
155
+ id: lesson.id,
156
+ title: lesson.title,
157
+ difficulty: lesson.difficulty,
158
+ summary: lesson.summary,
159
+ estimatedMinutes: lesson.estimatedMinutes,
160
+ steps: lesson.steps.map(s => ({
161
+ id: s.id,
162
+ title: s.title,
163
+ objective: s.objective,
164
+ command: s.command,
165
+ explanation: s.explanation,
166
+ validationType: s.validationType || "syntax",
167
+ validationPath: s.validationPath || "",
168
+ validationContent: s.validationContent || ""
169
+ }))
170
+ });
171
+ setIsEditingLesson(true);
172
+ setSelectedLesson(lesson);
173
+ }
174
+
175
+ function handleAddStep() {
176
+ setLessonForm(prev => ({
177
+ ...prev,
178
+ steps: [
179
+ ...prev.steps,
180
+ {
181
+ id: "",
182
+ title: "",
183
+ objective: "",
184
+ command: "",
185
+ explanation: "",
186
+ validationType: "syntax"
187
+ }
188
+ ]
189
+ }));
190
+ }
191
+
192
+ function handleRemoveStep(index: number) {
193
+ setLessonForm(prev => {
194
+ const newSteps = [...prev.steps];
195
+ newSteps.splice(index, 1);
196
+ return { ...prev, steps: newSteps };
197
+ });
198
+ }
199
+
200
+ function handleStepChange(index: number, field: keyof LessonStep, value: any) {
201
+ setLessonForm(prev => {
202
+ const newSteps = [...prev.steps];
203
+ newSteps[index] = { ...newSteps[index], [field]: value };
204
+ return { ...prev, steps: newSteps };
205
+ });
206
+ }
207
+
208
+ async function handleSaveLesson(e: React.FormEvent) {
209
+ e.preventDefault();
210
+ if (!selectedTrack) return;
211
+ setLessonError("");
212
+
213
+ const cleanLessonId = lessonForm.id.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "");
214
+ if (!cleanLessonId) {
215
+ setLessonError("Lesson ID is required and must be alphanumeric.");
216
+ return;
217
+ }
218
+
219
+ const payload = {
220
+ id: cleanLessonId,
221
+ trackId: selectedTrack.id,
222
+ title: lessonForm.title.trim(),
223
+ difficulty: lessonForm.difficulty,
224
+ summary: lessonForm.summary.trim(),
225
+ estimatedMinutes: Number(lessonForm.estimatedMinutes) || 10,
226
+ steps: lessonForm.steps.map(s => ({
227
+ id: s.id.trim().toLowerCase().replace(/[^a-z0-9_-]/g, ""),
228
+ title: s.title.trim(),
229
+ objective: s.objective.trim(),
230
+ command: s.command.trim(),
231
+ explanation: s.explanation.trim(),
232
+ validationType: s.validationType || "syntax",
233
+ validationPath: s.validationPath ? s.validationPath.trim() : undefined,
234
+ validationContent: s.validationContent ? s.validationContent.trim() : undefined
235
+ }))
236
+ };
237
+
238
+ try {
239
+ if (selectedLesson) {
240
+ // Edit
241
+ await updateLesson(selectedLesson.id, payload);
242
+ } else {
243
+ // Create
244
+ await createLesson(selectedTrack.id, payload);
245
+ }
246
+ await loadLessons(selectedTrack.id);
247
+ setIsEditingLesson(false);
248
+ setSelectedLesson(null);
249
+ } catch (err: any) {
250
+ setLessonError(err.message ?? "Failed to save lesson.");
251
+ }
252
+ }
253
+
254
+ if (!user) {
255
+ return (
256
+ <div className="monitor-bezel">
257
+ <div className="monitor-screen" style={{ display: "flex", alignItems: "center", justifyContent: "center", minHeight: "100vh", padding: 24 }}>
258
+ <div style={{ color: "var(--m-ink)", fontFamily: "var(--font-mono)", textAlign: "center", maxWidth: 520, background: "rgba(255,255,255,0.68)", border: "1px solid rgba(10,10,10,0.14)", borderRadius: "var(--radius-md)", padding: "34px 38px", boxShadow: "0 20px 50px rgba(10,10,10,0.12)" }}>
259
+ <p style={{ color: "var(--accent-red)", fontSize: "0.8rem", fontWeight: 800, letterSpacing: "0.08em" }}>ACCESS REQUIRED</p>
260
+ <h1 style={{ marginTop: 10, fontFamily: "var(--font-sans)", fontSize: "2rem", lineHeight: 1.05 }}>Create your own lessons</h1>
261
+ <p style={{ marginTop: 12, color: "var(--ink-soft)", lineHeight: 1.6 }}>Sign in to open Creator Studio, build custom tracks, and publish terminal lesson steps.</p>
262
+ <div style={{ display: "flex", justifyContent: "center", gap: 12, marginTop: 24, flexWrap: "wrap" }}>
263
+ <a href="/login" className="creator-auth-link creator-auth-link--primary">Login</a>
264
+ <a href="/register" className="creator-auth-link">Register</a>
265
+ </div>
266
+ <a href="/" style={{ color: "var(--accent-teal)", display: "inline-block", marginTop: 22, fontWeight: 700 }}>Return to home</a>
267
+ </div>
268
+ </div>
269
+ </div>
270
+ );
271
+ }
272
+
273
+ const isSelectedTrackStatic = selectedTrack && !selectedTrack.creatorId;
274
+ const isSelectedTrackCreatedByMe = selectedTrack && selectedTrack.creatorId === user.userId;
275
+
276
+ return (
277
+ <div className="monitor-bezel">
278
+ <div className="monitor-screen" style={{ display: "flex", flexDirection: "column", minHeight: "100vh" }}>
279
+ {/* Header */}
280
+ <header className="m-site-header">
281
+ <div className="header-left">
282
+ <span className="header-brand">VisionOS</span>
283
+ <span className="header-meta" style={{ color: "#00ffcc" }}>CREATOR STUDIO</span>
284
+ </div>
285
+ <nav className="header-nav">
286
+ <a href="/">Home <span className="arrow">↗</span></a>
287
+ </nav>
288
+ </header>
289
+
290
+ {/* Dashboard Grid */}
291
+ <div style={{ display: "grid", gridTemplateColumns: "280px 1fr", gap: 24, padding: 24, flex: 1 }}>
292
+ {/* Sidebar */}
293
+ <aside style={{ borderRight: "1px solid rgba(255,255,255,0.08)", paddingRight: 24 }}>
294
+ <h2 className="text-label" style={{ fontFamily: "var(--font-mono)", marginBottom: 16 }}>Learning Tracks</h2>
295
+ <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
296
+ {tracks.map(track => {
297
+ const isMyTrack = track.creatorId === user.userId;
298
+ return (
299
+ <button
300
+ key={track.id}
301
+ onClick={() => {
302
+ setSelectedTrack(track);
303
+ setEditTrackForm({ label: track.label, description: track.description });
304
+ }}
305
+ style={{
306
+ background: selectedTrack?.id === track.id ? "#001f1a" : "rgba(255,255,255,0.55)",
307
+ border: selectedTrack?.id === track.id ? "1px solid #00ffcc" : "1px solid rgba(10,10,10,0.16)",
308
+ color: selectedTrack?.id === track.id ? "#00ffcc" : "var(--m-ink)",
309
+ borderRadius: "var(--radius-sm)",
310
+ padding: "10px 14px",
311
+ textAlign: "left",
312
+ cursor: "pointer",
313
+ fontFamily: "var(--font-mono)",
314
+ fontSize: "0.85rem",
315
+ display: "flex",
316
+ justifyContent: "space-between",
317
+ alignItems: "center",
318
+ transition: "all 0.2s ease",
319
+ boxShadow: selectedTrack?.id === track.id ? "0 0 12px rgba(0, 255, 204, 0.25)" : "none"
320
+ }}
321
+ >
322
+ <span>{track.label}</span>
323
+ {isMyTrack && <span style={{ color: "#00ffcc", fontSize: "0.65rem", border: "1px solid #00ffcc", padding: "1px 4px", borderRadius: 2 }}>MY CONTENT</span>}
324
+ {!track.creatorId && <span style={{ color: "var(--ink-muted)", fontSize: "0.65rem" }}>SYSTEM</span>}
325
+ </button>
326
+ );
327
+ })}
328
+ </div>
329
+
330
+ <button
331
+ onClick={() => {
332
+ setIsCreatingTrack(true);
333
+ setSelectedTrack(null);
334
+ setIsEditingTrack(false);
335
+ setIsEditingLesson(false);
336
+ }}
337
+ style={{
338
+ width: "100%",
339
+ marginTop: 24,
340
+ background: "#000",
341
+ border: "1px dashed #00ffcc",
342
+ color: "#00ffcc",
343
+ borderRadius: "var(--radius-sm)",
344
+ padding: "12px",
345
+ fontFamily: "var(--font-mono)",
346
+ cursor: "pointer",
347
+ fontSize: "0.85rem",
348
+ textAlign: "center",
349
+ boxShadow: "0 0 10px rgba(0, 255, 204, 0.15)"
350
+ }}
351
+ >
352
+ + Create New Track
353
+ </button>
354
+ </aside>
355
+
356
+ {/* Main workspace */}
357
+ <main style={{ paddingLeft: 12 }}>
358
+ {/* Create Track Form */}
359
+ {isCreatingTrack && (
360
+ <div style={{ maxWidth: 650 }}>
361
+ <h1 className="text-section-title" style={{ fontSize: "1.8rem" }}>Create Track</h1>
362
+ <p className="m-text-body" style={{ color: "var(--ink-muted)", marginTop: 6, fontSize: "0.85rem" }}>
363
+ Design a custom path with specialized terminal lessons.
364
+ </p>
365
+
366
+ {trackError && <div style={{ color: "var(--accent-red)", fontFamily: "var(--font-mono)", marginTop: 12, fontSize: "0.85rem" }}>ERROR: {trackError}</div>}
367
+
368
+ <form onSubmit={handleCreateTrack} style={{ marginTop: 24, display: "flex", flexDirection: "column", gap: 16 }}>
369
+ <div>
370
+ <label className="text-label" style={{ display: "block", marginBottom: 6 }}>Track ID (URL & CLI key)</label>
371
+ <input
372
+ type="text"
373
+ placeholder="e.g. advanced-git-flow"
374
+ value={trackForm.id}
375
+ onChange={e => setTrackForm(prev => ({ ...prev, id: e.target.value }))}
376
+ required
377
+ style={{
378
+ width: "100%",
379
+ padding: 10,
380
+ background: "rgba(255,255,255,0.03)",
381
+ border: "1px solid rgba(255,255,255,0.1)",
382
+ borderRadius: "var(--radius-sm)",
383
+ color: "var(--m-ink)",
384
+ fontFamily: "var(--font-mono)"
385
+ }}
386
+ />
387
+ </div>
388
+ <div>
389
+ <label className="text-label" style={{ display: "block", marginBottom: 6 }}>Display Label</label>
390
+ <input
391
+ type="text"
392
+ placeholder="e.g. Advanced Git"
393
+ value={trackForm.label}
394
+ onChange={e => setTrackForm(prev => ({ ...prev, label: e.target.value }))}
395
+ required
396
+ style={{
397
+ width: "100%",
398
+ padding: 10,
399
+ background: "rgba(255,255,255,0.03)",
400
+ border: "1px solid rgba(255,255,255,0.1)",
401
+ borderRadius: "var(--radius-sm)",
402
+ color: "var(--m-ink)",
403
+ fontFamily: "var(--font-mono)"
404
+ }}
405
+ />
406
+ </div>
407
+ <div>
408
+ <label className="text-label" style={{ display: "block", marginBottom: 6 }}>Description Summary</label>
409
+ <textarea
410
+ placeholder="Explain what learners will practice on their terminal..."
411
+ value={trackForm.description}
412
+ onChange={e => setTrackForm(prev => ({ ...prev, description: e.target.value }))}
413
+ required
414
+ rows={4}
415
+ style={{
416
+ width: "100%",
417
+ padding: 10,
418
+ background: "rgba(255,255,255,0.03)",
419
+ border: "1px solid rgba(255,255,255,0.1)",
420
+ borderRadius: "var(--radius-sm)",
421
+ color: "var(--m-ink)",
422
+ fontFamily: "var(--font-mono)"
423
+ }}
424
+ />
425
+ </div>
426
+
427
+ <div style={{ display: "flex", gap: 12, marginTop: 12 }}>
428
+ <button type="submit" className="copy-link-btn" style={{ background: "#000", border: "1px solid var(--bezel-outer)", color: "var(--bg-beige)", boxShadow: "0 0 12px rgba(168, 168, 160, 0.4)", fontWeight: 700 }}>Publish Track</button>
429
+ <button type="button" onClick={() => setIsCreatingTrack(false)} style={{ background: "#000", border: "1px solid rgba(255,255,255,0.25)", color: "var(--bg-beige)", padding: "10px 18px", borderRadius: "var(--radius-sm)", cursor: "pointer", boxShadow: "0 0 8px rgba(255,255,255,0.05)" }}>Cancel</button>
430
+ </div>
431
+ </form>
432
+ </div>
433
+ )}
434
+
435
+ {/* Selected Track Dashboard */}
436
+ {selectedTrack && !isCreatingTrack && !isEditingLesson && (
437
+ <div>
438
+ <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", borderBottom: "1px solid rgba(255,255,255,0.06)", paddingBottom: 16 }}>
439
+ <div>
440
+ <h1 className="text-section-title" style={{ fontSize: "2rem" }}>{selectedTrack.label}</h1>
441
+ <p className="m-text-body" style={{ color: "var(--ink-muted)", marginTop: 6, fontSize: "0.9rem" }}>{selectedTrack.description}</p>
442
+ <p style={{ fontFamily: "var(--font-mono)", color: "#00ffcc", fontSize: "0.75rem", marginTop: 8 }}>Track Key: {selectedTrack.id}</p>
443
+ </div>
444
+ {isSelectedTrackCreatedByMe && (
445
+ <button
446
+ onClick={() => setIsEditingTrack(true)}
447
+ style={{
448
+ background: "#000",
449
+ border: "1px solid #00ffcc",
450
+ color: "#00ffcc",
451
+ padding: "8px 16px",
452
+ borderRadius: "var(--radius-sm)",
453
+ cursor: "pointer",
454
+ fontFamily: "var(--font-mono)",
455
+ fontSize: "0.8rem",
456
+ boxShadow: "0 0 10px rgba(0, 255, 204, 0.3)"
457
+ }}
458
+ >
459
+ Edit Track Meta
460
+ </button>
461
+ )}
462
+ </div>
463
+
464
+ {/* Edit Track Meta Form */}
465
+ {isEditingTrack && (
466
+ <form onSubmit={handleUpdateTrack} style={{ marginTop: 20, display: "flex", flexDirection: "column", gap: 12, maxWidth: 600 }}>
467
+ <div>
468
+ <label className="text-label" style={{ display: "block", marginBottom: 4 }}>Label</label>
469
+ <input
470
+ type="text"
471
+ value={editTrackForm.label}
472
+ onChange={e => setEditTrackForm(prev => ({ ...prev, label: e.target.value }))}
473
+ required
474
+ style={{ width: "100%", padding: 8, background: "rgba(255,255,255,0.03)", border: "1px solid rgba(255,255,255,0.1)", color: "var(--m-ink)", fontFamily: "var(--font-mono)" }}
475
+ />
476
+ </div>
477
+ <div>
478
+ <label className="text-label" style={{ display: "block", marginBottom: 4 }}>Description</label>
479
+ <textarea
480
+ value={editTrackForm.description}
481
+ onChange={e => setEditTrackForm(prev => ({ ...prev, description: e.target.value }))}
482
+ required
483
+ rows={3}
484
+ style={{ width: "100%", padding: 8, background: "rgba(255,255,255,0.03)", border: "1px solid rgba(255,255,255,0.1)", color: "var(--m-ink)", fontFamily: "var(--font-mono)" }}
485
+ />
486
+ </div>
487
+ <div style={{ display: "flex", gap: 10 }}>
488
+ <button type="submit" style={{ background: "#000", border: "1px solid var(--bezel-outer)", color: "var(--bg-beige)", padding: "8px 16px", borderRadius: "var(--radius-sm)", cursor: "pointer", fontWeight: 700, boxShadow: "0 0 12px rgba(168, 168, 160, 0.4)" }}>Save Changes</button>
489
+ <button type="button" onClick={() => setIsEditingTrack(false)} style={{ background: "#000", border: "1px solid rgba(255,255,255,0.25)", color: "var(--bg-beige)", padding: "8px 16px", borderRadius: "var(--radius-sm)", cursor: "pointer", boxShadow: "0 0 8px rgba(255,255,255,0.05)" }}>Cancel</button>
490
+ </div>
491
+ </form>
492
+ )}
493
+
494
+ {/* Track Lessons Section */}
495
+ <div style={{ marginTop: 32 }}>
496
+ <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
497
+ <h2 className="text-label" style={{ fontSize: "1rem", fontFamily: "var(--font-mono)" }}>Track Lessons ({lessons.length})</h2>
498
+ {isSelectedTrackCreatedByMe && (
499
+ <button
500
+ onClick={startCreateLesson}
501
+ style={{
502
+ background: "#000",
503
+ border: "1px solid #00ffcc",
504
+ color: "#00ffcc",
505
+ padding: "8px 16px",
506
+ borderRadius: "var(--radius-sm)",
507
+ cursor: "pointer",
508
+ fontFamily: "var(--font-mono)",
509
+ fontSize: "0.8rem",
510
+ fontWeight: 700,
511
+ boxShadow: "0 0 12px rgba(0, 255, 204, 0.35)"
512
+ }}
513
+ >
514
+ + Add New Lesson
515
+ </button>
516
+ )}
517
+ </div>
518
+
519
+ {lessons.length === 0 ? (
520
+ <div style={{ border: "1px dashed rgba(255,255,255,0.06)", padding: "32px 0", textAlign: "center", borderRadius: "var(--radius-md)", color: "var(--ink-muted)" }}>
521
+ No lessons have been added to this track yet.
522
+ </div>
523
+ ) : (
524
+ <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}>
525
+ {lessons.map(lesson => {
526
+ const isMyLesson = lesson.creatorId === user.userId;
527
+ return (
528
+ <div
529
+ key={lesson.id}
530
+ style={{
531
+ background: "rgba(255,255,255,0.02)",
532
+ border: "1px solid rgba(255,255,255,0.05)",
533
+ borderRadius: "var(--radius-md)",
534
+ padding: 20,
535
+ display: "flex",
536
+ flexDirection: "column",
537
+ justifyContent: "space-between"
538
+ }}
539
+ >
540
+ <div>
541
+ <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
542
+ <span style={{ color: "#00ffcc", fontFamily: "var(--font-mono)", fontSize: "0.75rem", textTransform: "uppercase" }}>{lesson.difficulty} — {lesson.estimatedMinutes} mins</span>
543
+ {isMyLesson && <span style={{ color: "#00ffcc", fontSize: "0.6rem", border: "1px solid #00ffcc", padding: "1px 4px", borderRadius: 2 }}>MY CONTENT</span>}
544
+ </div>
545
+ <h3 style={{ marginTop: 8, fontSize: "1.1rem" }}>{lesson.title}</h3>
546
+ <p className="m-text-body" style={{ fontSize: "0.8rem", color: "var(--ink-muted)", marginTop: 6 }}>{lesson.summary}</p>
547
+ <p style={{ fontFamily: "var(--font-mono)", fontSize: "0.7rem", color: "var(--ink-muted)", marginTop: 8 }}>Steps: {lesson.steps.length}</p>
548
+ </div>
549
+
550
+ {isMyLesson && (
551
+ <button
552
+ onClick={() => startEditLesson(lesson)}
553
+ style={{
554
+ marginTop: 16,
555
+ alignSelf: "flex-start",
556
+ background: "#000",
557
+ border: "1px solid rgba(255,255,255,0.25)",
558
+ color: "var(--bg-beige)",
559
+ padding: "6px 12px",
560
+ borderRadius: "var(--radius-sm)",
561
+ cursor: "pointer",
562
+ fontSize: "0.75rem",
563
+ fontFamily: "var(--font-mono)",
564
+ boxShadow: "0 0 8px rgba(255, 255, 255, 0.05)"
565
+ }}
566
+ >
567
+ Edit Lesson & Steps
568
+ </button>
569
+ )}
570
+ </div>
571
+ );
572
+ })}
573
+ </div>
574
+ )}
575
+ </div>
576
+ </div>
577
+ )}
578
+
579
+ {/* Lesson & Steps Builder */}
580
+ {isEditingLesson && selectedTrack && (
581
+ <div style={{ maxWidth: 850, paddingBottom: 48 }}>
582
+ <h1 className="text-section-title" style={{ fontSize: "1.8rem" }}>
583
+ {selectedLesson ? `Edit Lesson: ${selectedLesson.title}` : "Add New Lesson"}
584
+ </h1>
585
+ <p className="m-text-body" style={{ color: "var(--ink-muted)", marginTop: 6, fontSize: "0.85rem" }}>
586
+ Define custom terminal steps and configure dynamic verification rules.
587
+ </p>
588
+
589
+ {lessonError && <div style={{ color: "var(--accent-red)", fontFamily: "var(--font-mono)", marginTop: 12, fontSize: "0.85rem" }}>ERROR: {lessonError}</div>}
590
+
591
+ <form onSubmit={handleSaveLesson} style={{ marginTop: 24, display: "flex", flexDirection: "column", gap: 16 }}>
592
+ <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}>
593
+ <div>
594
+ <label className="text-label" style={{ display: "block", marginBottom: 6 }}>Lesson ID</label>
595
+ <input
596
+ type="text"
597
+ placeholder="e.g. git-rebasing-basics"
598
+ value={lessonForm.id}
599
+ disabled={!!selectedLesson}
600
+ onChange={e => setLessonForm(prev => ({ ...prev, id: e.target.value }))}
601
+ required
602
+ style={{
603
+ width: "100%",
604
+ padding: 10,
605
+ background: "rgba(255,255,255,0.55)",
606
+ border: "1px solid rgba(10,10,10,0.16)",
607
+ borderRadius: "var(--radius-sm)",
608
+ color: "var(--m-ink)",
609
+ fontFamily: "var(--font-mono)",
610
+ opacity: selectedLesson ? 0.5 : 1
611
+ }}
612
+ />
613
+ </div>
614
+ <div>
615
+ <label className="text-label" style={{ display: "block", marginBottom: 6 }}>Title</label>
616
+ <input
617
+ type="text"
618
+ placeholder="e.g. Rebasing Basics"
619
+ value={lessonForm.title}
620
+ onChange={e => setLessonForm(prev => ({ ...prev, title: e.target.value }))}
621
+ required
622
+ style={{
623
+ width: "100%",
624
+ padding: 10,
625
+ background: "rgba(255,255,255,0.03)",
626
+ border: "1px solid rgba(255,255,255,0.1)",
627
+ borderRadius: "var(--radius-sm)",
628
+ color: "var(--m-ink)",
629
+ fontFamily: "var(--font-mono)"
630
+ }}
631
+ />
632
+ </div>
633
+ </div>
634
+
635
+ <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}>
636
+ <div>
637
+ <label className="text-label" style={{ display: "block", marginBottom: 6 }}>Difficulty</label>
638
+ <select
639
+ value={lessonForm.difficulty}
640
+ onChange={e => setLessonForm(prev => ({ ...prev, difficulty: e.target.value as any }))}
641
+ style={{
642
+ width: "100%",
643
+ padding: 10,
644
+ background: "var(--bg-beige-light)",
645
+ border: "1px solid rgba(10,10,10,0.18)",
646
+ borderRadius: "var(--radius-sm)",
647
+ color: "var(--m-ink)",
648
+ fontFamily: "var(--font-mono)"
649
+ }}
650
+ >
651
+ <option value="beginner">Beginner</option>
652
+ <option value="intermediate">Intermediate</option>
653
+ <option value="advanced">Advanced</option>
654
+ </select>
655
+ </div>
656
+ <div>
657
+ <label className="text-label" style={{ display: "block", marginBottom: 6 }}>Estimated Minutes</label>
658
+ <input
659
+ type="number"
660
+ placeholder="10"
661
+ value={lessonForm.estimatedMinutes}
662
+ onChange={e => setLessonForm(prev => ({ ...prev, estimatedMinutes: Number(e.target.value) }))}
663
+ required
664
+ style={{
665
+ width: "100%",
666
+ padding: 10,
667
+ background: "rgba(255,255,255,0.03)",
668
+ border: "1px solid rgba(255,255,255,0.1)",
669
+ borderRadius: "var(--radius-sm)",
670
+ color: "var(--m-ink)",
671
+ fontFamily: "var(--font-mono)"
672
+ }}
673
+ />
674
+ </div>
675
+ </div>
676
+
677
+ <div>
678
+ <label className="text-label" style={{ display: "block", marginBottom: 6 }}>Summary Description</label>
679
+ <input
680
+ type="text"
681
+ placeholder="Brief overview explaining what this lesson teaches..."
682
+ value={lessonForm.summary}
683
+ onChange={e => setLessonForm(prev => ({ ...prev, summary: e.target.value }))}
684
+ required
685
+ style={{
686
+ width: "100%",
687
+ padding: 10,
688
+ background: "rgba(255,255,255,0.03)",
689
+ border: "1px solid rgba(255,255,255,0.1)",
690
+ borderRadius: "var(--radius-sm)",
691
+ color: "var(--m-ink)",
692
+ fontFamily: "var(--font-mono)"
693
+ }}
694
+ />
695
+ </div>
696
+
697
+ {/* Step Editor List */}
698
+ <div style={{ marginTop: 24 }}>
699
+ <h3 className="text-label" style={{ fontSize: "0.95rem", marginBottom: 12, display: "flex", justifyContent: "space-between", alignItems: "center" }}>
700
+ <span>Lesson Steps ({lessonForm.steps.length})</span>
701
+ <button
702
+ type="button"
703
+ onClick={handleAddStep}
704
+ style={{
705
+ background: "#000",
706
+ border: "1px solid #00ffcc",
707
+ color: "#00ffcc",
708
+ padding: "4px 10px",
709
+ borderRadius: 4,
710
+ fontSize: "0.75rem",
711
+ cursor: "pointer",
712
+ fontFamily: "var(--font-mono)",
713
+ boxShadow: "0 0 10px rgba(0, 255, 204, 0.2)"
714
+ }}
715
+ >
716
+ + Add Step
717
+ </button>
718
+ </h3>
719
+
720
+ <div style={{ display: "flex", flexDirection: "column", gap: 20 }}>
721
+ {lessonForm.steps.map((step, idx) => (
722
+ <div
723
+ key={idx}
724
+ style={{
725
+ background: "rgba(255,255,255,0.01)",
726
+ border: "1px solid rgba(255,255,255,0.06)",
727
+ borderRadius: "var(--radius-md)",
728
+ padding: 20,
729
+ position: "relative"
730
+ }}
731
+ >
732
+ <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }}>
733
+ <span style={{ color: "#00ffcc", fontFamily: "var(--font-mono)", fontWeight: 700, fontSize: "0.8rem" }}>STEP {idx + 1}</span>
734
+ {lessonForm.steps.length > 1 && (
735
+ <button
736
+ type="button"
737
+ onClick={() => handleRemoveStep(idx)}
738
+ style={{
739
+ background: "#000",
740
+ border: "1px solid var(--accent-red)",
741
+ color: "var(--accent-red)",
742
+ padding: "4px 8px",
743
+ borderRadius: 4,
744
+ fontSize: "0.75rem",
745
+ cursor: "pointer",
746
+ fontFamily: "var(--font-mono)",
747
+ boxShadow: "0 0 8px rgba(255, 77, 77, 0.25)"
748
+ }}
749
+ >
750
+ [Remove Step]
751
+ </button>
752
+ )}
753
+ </div>
754
+
755
+ <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginBottom: 12 }}>
756
+ <div>
757
+ <label style={{ fontSize: "0.75rem", display: "block", color: "var(--ink-muted)", marginBottom: 4 }}>Step Key ID</label>
758
+ <input
759
+ type="text"
760
+ placeholder="e.g. git-rebase-interactive"
761
+ value={step.id}
762
+ onChange={e => handleStepChange(idx, "id", e.target.value)}
763
+ required
764
+ style={{ width: "100%", padding: 8, background: "rgba(255,255,255,0.03)", border: "1px solid rgba(255,255,255,0.08)", color: "var(--m-ink)", fontFamily: "var(--font-mono)", fontSize: "0.85rem" }}
765
+ />
766
+ </div>
767
+ <div>
768
+ <label style={{ fontSize: "0.75rem", display: "block", color: "var(--ink-muted)", marginBottom: 4 }}>Display Title</label>
769
+ <input
770
+ type="text"
771
+ placeholder="e.g. Start Interactive Rebase"
772
+ value={step.title}
773
+ onChange={e => handleStepChange(idx, "title", e.target.value)}
774
+ required
775
+ style={{ width: "100%", padding: 8, background: "rgba(255,255,255,0.03)", border: "1px solid rgba(255,255,255,0.08)", color: "var(--m-ink)", fontFamily: "var(--font-mono)", fontSize: "0.85rem" }}
776
+ />
777
+ </div>
778
+ </div>
779
+
780
+ <div style={{ display: "grid", gridTemplateColumns: "1.2fr 0.8fr", gap: 12, marginBottom: 12 }}>
781
+ <div>
782
+ <label style={{ fontSize: "0.75rem", display: "block", color: "var(--ink-muted)", marginBottom: 4 }}>Command Explanation</label>
783
+ <input
784
+ type="text"
785
+ placeholder="Explain what this command does and how it affects history..."
786
+ value={step.explanation}
787
+ onChange={e => handleStepChange(idx, "explanation", e.target.value)}
788
+ required
789
+ style={{ width: "100%", padding: 8, background: "rgba(255,255,255,0.03)", border: "1px solid rgba(255,255,255,0.08)", color: "var(--m-ink)", fontFamily: "var(--font-mono)", fontSize: "0.85rem" }}
790
+ />
791
+ </div>
792
+ <div>
793
+ <label style={{ fontSize: "0.75rem", display: "block", color: "var(--ink-muted)", marginBottom: 4 }}>Step Goal / Objective</label>
794
+ <input
795
+ type="text"
796
+ placeholder="e.g. Choose which commits to squash."
797
+ value={step.objective}
798
+ onChange={e => handleStepChange(idx, "objective", e.target.value)}
799
+ required
800
+ style={{ width: "100%", padding: 8, background: "rgba(255,255,255,0.03)", border: "1px solid rgba(255,255,255,0.08)", color: "var(--m-ink)", fontFamily: "var(--font-mono)", fontSize: "0.85rem" }}
801
+ />
802
+ </div>
803
+ </div>
804
+
805
+ <div style={{ display: "grid", gridTemplateColumns: "1.2fr 0.8fr", gap: 12, marginBottom: 12 }}>
806
+ <div>
807
+ <label style={{ fontSize: "0.75rem", display: "block", color: "var(--ink-muted)", marginBottom: 4 }}>Target Command</label>
808
+ <input
809
+ type="text"
810
+ placeholder="e.g. git rebase -i HEAD~3"
811
+ value={step.command}
812
+ onChange={e => handleStepChange(idx, "command", e.target.value)}
813
+ required
814
+ style={{ width: "100%", padding: 8, background: "rgba(255,255,255,0.03)", border: "1px solid rgba(255,255,255,0.08)", color: "var(--m-ink)", fontFamily: "var(--font-mono)", fontSize: "0.85rem" }}
815
+ />
816
+ </div>
817
+ <div>
818
+ <label style={{ fontSize: "0.75rem", display: "block", color: "var(--ink-muted)", marginBottom: 4 }}>Expected Result Verification</label>
819
+ <select
820
+ value={step.validationType || "syntax"}
821
+ onChange={e => handleStepChange(idx, "validationType", e.target.value)}
822
+ style={{ width: "100%", padding: 8, background: "var(--bg-beige-light)", border: "1px solid rgba(10,10,10,0.18)", color: "var(--m-ink)", fontFamily: "var(--font-mono)", fontSize: "0.85rem" }}
823
+ >
824
+ <option value="syntax">Syntax Success (runs with exit 0)</option>
825
+ <option value="file-exists">File/Folder Exists</option>
826
+ <option value="file-contains">File Exists and Contains Content</option>
827
+ </select>
828
+ </div>
829
+ </div>
830
+
831
+ {/* Dynamic validation parameters */}
832
+ {step.validationType !== "syntax" && (
833
+ <div style={{ display: "grid", gridTemplateColumns: step.validationType === "file-contains" ? "1fr 1fr" : "1fr", gap: 12, padding: "12px 16px", background: "rgba(0, 255, 204, 0.03)", border: "1px solid rgba(0, 255, 204, 0.08)", borderRadius: "var(--radius-sm)" }}>
834
+ <div>
835
+ <label style={{ fontSize: "0.7rem", display: "block", color: "#00ffcc", marginBottom: 4 }}>Relative File/Folder Path</label>
836
+ <input
837
+ type="text"
838
+ placeholder="e.g. practice/hello.txt"
839
+ value={step.validationPath || ""}
840
+ onChange={e => handleStepChange(idx, "validationPath", e.target.value)}
841
+ required
842
+ style={{ width: "100%", padding: 8, background: "rgba(255,255,255,0.03)", border: "1px solid rgba(0, 255, 204, 0.2)", color: "var(--m-ink)", fontFamily: "var(--font-mono)", fontSize: "0.8rem" }}
843
+ />
844
+ </div>
845
+ {step.validationType === "file-contains" && (
846
+ <div>
847
+ <label style={{ fontSize: "0.7rem", display: "block", color: "#00ffcc", marginBottom: 4 }}>Expected Text/String Content</label>
848
+ <input
849
+ type="text"
850
+ placeholder="e.g. hello terminal"
851
+ value={step.validationContent || ""}
852
+ onChange={e => handleStepChange(idx, "validationContent", e.target.value)}
853
+ required
854
+ style={{ width: "100%", padding: 8, background: "rgba(255,255,255,0.03)", border: "1px solid rgba(0, 255, 204, 0.2)", color: "var(--m-ink)", fontFamily: "var(--font-mono)", fontSize: "0.8rem" }}
855
+ />
856
+ </div>
857
+ )}
858
+ </div>
859
+ )}
860
+ </div>
861
+ ))}
862
+ </div>
863
+ </div>
864
+
865
+ <div style={{ display: "flex", gap: 12, marginTop: 24 }}>
866
+ <button type="submit" className="copy-link-btn" style={{ background: "#000", border: "1px solid var(--bezel-outer)", color: "var(--bg-beige)", boxShadow: "0 0 12px rgba(168, 168, 160, 0.4)", fontWeight: 700 }}>Publish Lesson</button>
867
+ <button type="button" onClick={() => setIsEditingLesson(false)} style={{ background: "#000", border: "1px solid rgba(255,255,255,0.25)", color: "var(--bg-beige)", padding: "10px 18px", borderRadius: "var(--radius-sm)", cursor: "pointer", boxShadow: "0 0 8px rgba(255,255,255,0.05)" }}>Cancel</button>
868
+ </div>
869
+ </form>
870
+ </div>
871
+ )}
872
+
873
+ {/* Empty State */}
874
+ {!selectedTrack && !isCreatingTrack && (
875
+ <div style={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", minHeight: "400px", color: "var(--ink-muted)" }}>
876
+ <h2>Creator Workspace</h2>
877
+ <p style={{ marginTop: 8, fontSize: "0.9rem" }}>Select a track from the sidebar to manage lessons or create a new track.</p>
878
+ </div>
879
+ )}
880
+ </main>
881
+ </div>
882
+ </div>
883
+ </div>
884
+ );
885
+ }