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,1126 @@
1
+ import { useEffect, useRef, useState, useCallback } from "react";
2
+ import {
3
+ BEGINNER_LESSONS,
4
+ LEARNING_TRACKS,
5
+ type VisionOsUserProfile,
6
+ type LearningProgress,
7
+ } from "../../../shared/src/index.js";
8
+ import { getProgress, clearStoredAuthToken } from "../lib/api.js";
9
+
10
+ /* ────────────────────────────────────────────────────────────────
11
+ Sub-components
12
+ ──────────────────────────────────────────────────────────────── */
13
+
14
+ function ScanlineDivider({
15
+ variant = "both",
16
+ height = 120,
17
+ style,
18
+ }: {
19
+ variant?: "both" | "fade-top" | "fade-bottom";
20
+ height?: number;
21
+ style?: React.CSSProperties;
22
+ }) {
23
+ return (
24
+ <div
25
+ className={`scanline-divider scanline-divider--${variant} scanline-reveal`}
26
+ style={{ height, ...style }}
27
+ />
28
+ );
29
+ }
30
+
31
+ function TerminalWindow({
32
+ children,
33
+ style,
34
+ }: {
35
+ children: React.ReactNode;
36
+ style?: React.CSSProperties;
37
+ }) {
38
+ return (
39
+ <div className="terminal-window" style={style}>
40
+ <div className="terminal-titlebar">
41
+ <span className="terminal-dot terminal-dot--red" />
42
+ <span className="terminal-dot terminal-dot--yellow" />
43
+ <span className="terminal-dot terminal-dot--green" />
44
+ </div>
45
+ <div className="terminal-body">{children}</div>
46
+ </div>
47
+ );
48
+ }
49
+
50
+ function TerminalLine({
51
+ type,
52
+ children,
53
+ }: {
54
+ type?: "prompt" | "output" | "success";
55
+ children: React.ReactNode;
56
+ }) {
57
+ return (
58
+ <span className="terminal-line">
59
+ {type === "prompt" ? (
60
+ <>
61
+ <span className="t-prompt">~$</span>{" "}
62
+ <span className="command">{children}</span>
63
+ </>
64
+ ) : type === "output" ? (
65
+ <span className="output">{children}</span>
66
+ ) : type === "success" ? (
67
+ <span className="success">{children}</span>
68
+ ) : (
69
+ children
70
+ )}
71
+ </span>
72
+ );
73
+ }
74
+
75
+ /** Helper: wrap each word in a <span className="word"> for stagger animation */
76
+ function WordStagger({
77
+ text,
78
+ className = "",
79
+ tag: Tag = "p",
80
+ style,
81
+ }: {
82
+ text: string;
83
+ className?: string;
84
+ tag?: keyof JSX.IntrinsicElements;
85
+ style?: React.CSSProperties;
86
+ }) {
87
+ const TagComponent = Tag as any;
88
+ return (
89
+ <TagComponent className={`word-stagger ${className}`} style={style}>
90
+ {text.split(" ").map((word, i) => (
91
+ <span className="word" key={i}>
92
+ {word}&nbsp;
93
+ </span>
94
+ ))}
95
+ </TagComponent>
96
+ );
97
+ }
98
+
99
+ /* ────────────────────────────────────────────────────────────────
100
+ ManifestoPage
101
+ ──────────────────────────────────────────────────────────────── */
102
+
103
+ interface ManifestoPageProps {
104
+ user: VisionOsUserProfile | null;
105
+ }
106
+
107
+ export function ManifestoPage({ user }: ManifestoPageProps) {
108
+ const screenRef = useRef<HTMLDivElement>(null);
109
+ const [scrolled, setScrolled] = useState(false);
110
+ const [copied, setCopied] = useState(false);
111
+ const [scrollHidden, setScrollHidden] = useState(false);
112
+ const [progress, setProgress] = useState<LearningProgress[]>([]);
113
+
114
+ useEffect(() => {
115
+ if (!user) {
116
+ setProgress([]);
117
+ return;
118
+ }
119
+
120
+ let cancelled = false;
121
+
122
+ async function fetchProgress() {
123
+ try {
124
+ const res = await getProgress();
125
+ if (!cancelled) {
126
+ setProgress(res.progress);
127
+ }
128
+ } catch (err) {
129
+ console.error("Failed to load progress telemetry:", err);
130
+ }
131
+ }
132
+
133
+ // 1. Initial fetch
134
+ void fetchProgress();
135
+
136
+ // 2. Poll every 4 seconds for real-time terminal sync
137
+ const interval = setInterval(() => {
138
+ void fetchProgress();
139
+ }, 4000);
140
+
141
+ return () => {
142
+ cancelled = true;
143
+ clearInterval(interval);
144
+ };
145
+ }, [user]);
146
+
147
+ // ── Smooth scroll behavior ────────────────────────────────────
148
+ useEffect(() => {
149
+ document.documentElement.style.scrollBehavior = "smooth";
150
+ return () => {
151
+ document.documentElement.style.scrollBehavior = "";
152
+ };
153
+ }, []);
154
+
155
+ // ── Scroll Effects ────────────────────────────────────────────
156
+ useEffect(() => {
157
+ const handleScroll = () => {
158
+ setScrolled(window.scrollY > 50);
159
+ if (window.scrollY > 100) setScrollHidden(true);
160
+ };
161
+ window.addEventListener("scroll", handleScroll, { passive: true });
162
+ return () => window.removeEventListener("scroll", handleScroll);
163
+ }, []);
164
+
165
+ // ── IntersectionObserver for ALL reveal animations ────────────
166
+ useEffect(() => {
167
+ const els = screenRef.current?.querySelectorAll(
168
+ ".reveal, .reveal-left, .reveal-right, .reveal-scale, .reveal-blur, .word-stagger, .hr-reveal, .scanline-reveal, .animated-separator"
169
+ );
170
+ if (!els) return;
171
+
172
+ const obs = new IntersectionObserver(
173
+ (entries) => {
174
+ entries.forEach((e) => {
175
+ if (e.isIntersecting) {
176
+ e.target.classList.add("visible");
177
+ obs.unobserve(e.target);
178
+ }
179
+ });
180
+ },
181
+ { threshold: 0.12, rootMargin: "0px 0px -40px 0px" }
182
+ );
183
+ els.forEach((el) => obs.observe(el));
184
+
185
+ return () => obs.disconnect();
186
+ }, [user]);
187
+
188
+ // ── Terminal line re-trigger on scroll ────────────────────────
189
+ useEffect(() => {
190
+ const terminals = screenRef.current?.querySelectorAll(".terminal-window");
191
+ if (!terminals) return;
192
+
193
+ const obs = new IntersectionObserver(
194
+ (entries) => {
195
+ entries.forEach((e) => {
196
+ if (e.isIntersecting) {
197
+ const lines = e.target.querySelectorAll(".terminal-line");
198
+ lines.forEach((line) => {
199
+ (line as HTMLElement).style.animation = "none";
200
+ void (line as HTMLElement).offsetHeight;
201
+ (line as HTMLElement).style.animation = "";
202
+ });
203
+ obs.unobserve(e.target);
204
+ }
205
+ });
206
+ },
207
+ { threshold: 0.3 }
208
+ );
209
+ terminals.forEach((el) => obs.observe(el));
210
+ return () => obs.disconnect();
211
+ }, [user]);
212
+
213
+ // ── Copy handler ──────────────────────────────────────────────
214
+ const handleCopy = useCallback(async (text: string) => {
215
+ try {
216
+ await navigator.clipboard.writeText(text);
217
+ } catch {
218
+ const ta = document.createElement("textarea");
219
+ ta.value = text;
220
+ ta.style.position = "fixed";
221
+ ta.style.opacity = "0";
222
+ document.body.appendChild(ta);
223
+ ta.select();
224
+ document.execCommand("copy");
225
+ document.body.removeChild(ta);
226
+ }
227
+ setCopied(true);
228
+ setTimeout(() => setCopied(false), 1800);
229
+ }, []);
230
+
231
+ const today = new Date();
232
+ const formattedDate = `${String(today.getMonth() + 1).padStart(2, "0")}/${String(today.getDate()).padStart(2, "0")}/${String(today.getFullYear()).slice(-2)}`;
233
+
234
+ const trackIcons: Record<string, string> = {
235
+ git: "G",
236
+ linux: "L",
237
+ "react-cli": "R",
238
+ "os-commands": "OS",
239
+ };
240
+
241
+ return (
242
+ <div className="monitor-bezel">
243
+ <div className="monitor-screen" ref={screenRef}>
244
+ {/* ── STICKY HEADER ─────────────────────────────── */}
245
+ <header className={`m-site-header ${scrolled ? "scrolled" : ""}`}>
246
+ <div className="header-left">
247
+ <span className="header-brand">VisionOS</span>
248
+ <span className="header-meta">v0.1.0 — CLI-First Learning</span>
249
+ </div>
250
+ <nav className="header-nav">
251
+ <a href="#tracks">Tracks</a>
252
+ <a href="#commands">Commands</a>
253
+ <a href="/creator" className="creator-nav-link">Create Lessons</a>
254
+ {user ? (
255
+ <>
256
+ <span className="user-nav-profile" style={{ color: "var(--m-ink)", fontWeight: 700, fontSize: "0.85rem", marginRight: 8, textTransform: "uppercase", fontFamily: "var(--font-mono)", opacity: 0.8 }}>
257
+ Hi, {user.name}
258
+ </span>
259
+ <a
260
+ href="#"
261
+ onClick={(e) => {
262
+ e.preventDefault();
263
+ clearStoredAuthToken();
264
+ window.location.href = "/";
265
+ }}
266
+ className="logout-nav-link"
267
+ style={{ color: "var(--accent-red)" }}
268
+ >
269
+ Logout
270
+ </a>
271
+ </>
272
+ ) : (
273
+ <>
274
+ <a href="/login">Login</a>
275
+ <a href="/register">Register</a>
276
+ </>
277
+ )}
278
+ <a
279
+ href="https://github.com/darklegend91/VisionOS"
280
+ target="_blank"
281
+ rel="noopener noreferrer"
282
+ >
283
+ GitHub <span className="arrow">↗</span>
284
+ </a>
285
+ </nav>
286
+ </header>
287
+
288
+ {/* ── LOGGED-IN TERMINAL SECTION (Image 1) ─────── */}
289
+ {user && (
290
+ <section className="logged-in-terminal-section">
291
+ <div className="logged-in-header">
292
+ <div className="logged-in-header-left">
293
+ <span className="logged-in-header-brand">VisionOS, Inc.</span>
294
+ <span className="logged-in-header-copy">
295
+ Copyright 2025
296
+ <br />
297
+ All Rights Reserved
298
+ </span>
299
+ </div>
300
+ <div className="logged-in-header-nav">
301
+ <a href="#tracks">
302
+ tracks <span className="arrow">↗</span>
303
+ </a>
304
+ <a href="#commands">
305
+ commands <span className="arrow">↗</span>
306
+ </a>
307
+ <a href="/creator" className="creator-nav-link">
308
+ create lessons <span className="arrow">↗</span>
309
+ </a>
310
+ <a href="#hero">@visionos</a>
311
+ </div>
312
+ </div>
313
+
314
+ <div className="logged-in-quote reveal">
315
+ CD, RM, PING: COMMANDS WE MEMORIZED
316
+ <br />
317
+ TO TRANSLATE OUR DESIRES INTO ACTIONS...
318
+ <br />
319
+ IF WE CHOSE TO LEARN THEM.
320
+ <span className="typewriter-cursor typewriter-cursor--dark" />
321
+ </div>
322
+
323
+ <div className="logged-in-meta reveal delay-1">
324
+ Last login: {today.toDateString()}, {today.toLocaleTimeString()}
325
+ <br />
326
+ Welcome back, {user.name} · {user.email}
327
+ </div>
328
+
329
+ {/* ── User Log Details Panel ────────────────── */}
330
+ <div className="user-log-details reveal delay-2">
331
+ <div className="user-log-grid">
332
+ <div className="user-log-field">
333
+ <span className="user-log-label">Session ID</span>
334
+ <span className="user-log-value user-log-value--highlight">
335
+ {user.sessionId ? user.sessionId.slice(0, 12) + "..." : "ACTIVE"}
336
+ </span>
337
+ </div>
338
+ <div className="user-log-field">
339
+ <span className="user-log-label">User</span>
340
+ <span className="user-log-value">{user.name}</span>
341
+ </div>
342
+ <div className="user-log-field">
343
+ <span className="user-log-label">Email</span>
344
+ <span className="user-log-value">{user.email}</span>
345
+ </div>
346
+ <div className="user-log-field">
347
+ <span className="user-log-label">Status</span>
348
+ <span className="user-log-value user-log-value--green">
349
+ ● ONLINE
350
+ </span>
351
+ </div>
352
+
353
+ <div className="user-log-divider" />
354
+
355
+ <div className="user-log-field">
356
+ <span className="user-log-label">Runtime</span>
357
+ <span className="user-log-value">Node.js v20+</span>
358
+ </div>
359
+ <div className="user-log-field">
360
+ <span className="user-log-label">Port</span>
361
+ <span className="user-log-value user-log-value--highlight">
362
+ :5173
363
+ </span>
364
+ </div>
365
+ <div className="user-log-field">
366
+ <span className="user-log-label">Tracks Available</span>
367
+ <span className="user-log-value">{LEARNING_TRACKS.length}</span>
368
+ </div>
369
+ <div className="user-log-field">
370
+ <span className="user-log-label">Login Time</span>
371
+ <span className="user-log-value">
372
+ {today.toLocaleTimeString()}
373
+ </span>
374
+ </div>
375
+ </div>
376
+ </div>
377
+
378
+ {/* ── Progress Telemetry Dashboard ────────────────── */}
379
+ <div className="progress-telemetry reveal delay-3" style={{ marginTop: 40 }}>
380
+ <div className="telemetry-title">
381
+ <span className="telemetry-pulse">●</span>
382
+ // TRACK TELEMETRY & SYSTEM PROGRESS
383
+ </div>
384
+ <div className="telemetry-grid">
385
+ {LEARNING_TRACKS.map((track) => {
386
+ const trackLessons = BEGINNER_LESSONS.filter((l) => l.trackId === track.id);
387
+ const trackProgress = progress.find((p) => p.trackId === track.id);
388
+ const completedCount = trackProgress?.completedLessonIds?.length ?? 0;
389
+ const totalLessons = trackLessons.length;
390
+ const percentage = totalLessons > 0 ? Math.round((completedCount / totalLessons) * 100) : 0;
391
+
392
+ const isTrackCompleted = totalLessons > 0 && completedCount === totalLessons;
393
+ const activeLessonId = trackProgress?.currentLessonId;
394
+ const activeLesson = activeLessonId ? trackLessons.find((l) => l.id === activeLessonId) : null;
395
+
396
+ // Render ASCII Progress Bar
397
+ const totalBlocks = 20;
398
+ const filledBlocks = Math.round((percentage / 100) * totalBlocks);
399
+ const emptyBlocks = totalBlocks - filledBlocks;
400
+ const progressBar = `[${"█".repeat(filledBlocks)}${"░".repeat(emptyBlocks)}] ${percentage}%`;
401
+
402
+ return (
403
+ <div key={track.id} className="telemetry-card">
404
+ <div className="telemetry-track-header">
405
+ <span className="telemetry-track-name">{track.label}</span>
406
+ {isTrackCompleted ? (
407
+ <span className="telemetry-track-status telemetry-track-status--completed">
408
+ COMPLETED
409
+ </span>
410
+ ) : activeLesson ? (
411
+ <span className="telemetry-track-status telemetry-track-status--active">
412
+ IN PROGRESS
413
+ </span>
414
+ ) : (
415
+ <span className="telemetry-track-status telemetry-track-status--inactive">
416
+ NOT STARTED
417
+ </span>
418
+ )}
419
+ </div>
420
+
421
+ <div className="telemetry-bar">
422
+ {progressBar}
423
+ </div>
424
+
425
+ <div className="telemetry-stats">
426
+ <span>LESSONS COMPLETED</span>
427
+ <span>{completedCount} / {totalLessons}</span>
428
+ </div>
429
+
430
+ {activeLesson && (
431
+ <div className="telemetry-active-lesson">
432
+ <span style={{ display: "block", fontSize: "0.6rem", color: "rgba(255,255,255,0.4)", textTransform: "uppercase", marginBottom: 2 }}>
433
+ Current active lesson
434
+ </span>
435
+ <span style={{ fontWeight: 600 }}>{activeLesson.title}</span>
436
+ </div>
437
+ )}
438
+ </div>
439
+ );
440
+ })}
441
+ </div>
442
+ </div>
443
+ </section>
444
+ )}
445
+
446
+ {/* ── HERO ──────────────────────────────────────── */}
447
+ <section className="m-section m-section--hero" id="hero">
448
+ <div className="hero-content">
449
+ <p className="text-label reveal">
450
+ Once upon a time, we typed a command in
451
+ </p>
452
+ <div style={{ marginTop: 28, overflow: "hidden" }}>
453
+ <span className="scanline-text glitch-text reveal delay-1">
454
+ TERMIN<br className="mobile-break" />AL.
455
+ </span>
456
+ </div>
457
+ <p
458
+ className="text-hero-lead reveal-blur delay-3"
459
+ style={{ marginTop: 40 }}
460
+ >
461
+ The command line was the first interface.
462
+ <br />
463
+ It's time we <em>learned</em> it properly.
464
+ <span className="typewriter-cursor" />
465
+ </p>
466
+ </div>
467
+
468
+ <div className="hero-prompt-container reveal delay-4">
469
+ <div className="hero-prompt">
470
+ <span>▸</span>
471
+ <span className="hero-prompt-key">enter</span>
472
+ <span>to continue</span>
473
+ </div>
474
+ </div>
475
+
476
+ {/* Scroll Indicator */}
477
+ <div
478
+ className={`scroll-indicator ${scrollHidden ? "hidden" : ""}`}
479
+ >
480
+ <svg
481
+ viewBox="0 0 24 24"
482
+ fill="none"
483
+ stroke="currentColor"
484
+ strokeWidth="1.5"
485
+ strokeLinecap="round"
486
+ >
487
+ <rect x="7" y="2" width="10" height="16" rx="5" />
488
+ <line x1="12" y1="6" x2="12" y2="9" />
489
+ </svg>
490
+ <span className="arrow">↓</span>
491
+ </div>
492
+ </section>
493
+
494
+ {/* ── SCANLINE DIVIDER ──────────────────────────── */}
495
+ <ScanlineDivider />
496
+
497
+ {/* ── ANIMATED SEPARATOR ─────────────────────────── */}
498
+ <div className="animated-separator" />
499
+
500
+ {/* ── NARRATIVE 1 — THE ORIGIN ──────────────────── */}
501
+ <section className="m-section" id="origin">
502
+ <div className="split-layout split-layout--text-left split-layout--center">
503
+ <div className="reveal-left">
504
+ <WordStagger
505
+ text="The command line was the original interface."
506
+ className="text-narrative"
507
+ tag="p"
508
+ />
509
+ <p
510
+ className="text-narrative text-narrative--muted reveal-blur delay-2"
511
+ style={{ marginTop: 20 }}
512
+ >
513
+ Raw, powerful, unfiltered. Before every GUI, before every app —
514
+ there was a blinking cursor and an empty prompt.
515
+ </p>
516
+ </div>
517
+
518
+ <div className="reveal-right delay-2">
519
+ <TerminalWindow>
520
+ <TerminalLine type="prompt">whoami</TerminalLine>
521
+ <TerminalLine type="output">learner</TerminalLine>
522
+ <TerminalLine>&nbsp;</TerminalLine>
523
+ <TerminalLine type="prompt">
524
+ echo "hello, terminal"
525
+ </TerminalLine>
526
+ <TerminalLine type="success">hello, terminal</TerminalLine>
527
+ <TerminalLine>&nbsp;</TerminalLine>
528
+ <TerminalLine type="prompt">█</TerminalLine>
529
+ </TerminalWindow>
530
+ <div className="figure-caption" style={{ marginTop: 20 }}>
531
+ <span className="figure-label">Fig 1</span>
532
+ <span className="figure-desc">
533
+ The terminal — where computing began. A blinking cursor,
534
+ infinite possibility.
535
+ </span>
536
+ </div>
537
+ </div>
538
+ </div>
539
+ </section>
540
+
541
+ {/* ── HR animated divider ────────────────────────── */}
542
+ <hr className="hr-reveal" />
543
+
544
+ {/* ── TRANSITION — GIANT TEXT ───────────────────── */}
545
+ <section
546
+ className="m-section"
547
+ style={{ padding: "var(--space-xxl) 0" }}
548
+ >
549
+ <ScanlineDivider
550
+ variant="fade-bottom"
551
+ height={60}
552
+ style={{ marginBottom: 32 }}
553
+ />
554
+ <div className="scanline-band reveal-scale">
555
+ <span
556
+ className="scanline-text glitch-text"
557
+ style={{ textAlign: "center", display: "block" }}
558
+ >
559
+ PRACTICE
560
+ </span>
561
+ </div>
562
+ <ScanlineDivider
563
+ variant="fade-top"
564
+ height={60}
565
+ style={{ marginTop: 32 }}
566
+ />
567
+ <div
568
+ style={{
569
+ textAlign: "center",
570
+ padding: "0 60px",
571
+ marginTop: "var(--space-lg)",
572
+ }}
573
+ >
574
+ <WordStagger
575
+ text="Now, we believe it's time for a future where the terminal becomes your classroom."
576
+ className="text-narrative"
577
+ tag="p"
578
+ style={{ maxWidth: 600, margin: "0 auto" }}
579
+ />
580
+ </div>
581
+ </section>
582
+
583
+ {/* ── ANIMATED SEPARATOR ─────────────────────────── */}
584
+ <div className="animated-separator" />
585
+
586
+ {/* ── MEET VISIONOS ────────────────────────────── */}
587
+ <section className="m-section" id="about">
588
+ <div className="reveal">
589
+ <p className="text-label">Introducing</p>
590
+ <h2 className="text-section-title" style={{ marginTop: 8 }}>
591
+ Meet VisionOS
592
+ </h2>
593
+ <p className="text-section-subtitle" style={{ marginTop: 4 }}>
594
+ A CLI-first learning platform.
595
+ </p>
596
+ </div>
597
+
598
+ <div style={{ marginTop: "var(--space-lg)" }}>
599
+ <div className="split-layout split-layout--text-left split-layout--center">
600
+ <div className="reveal-left delay-1">
601
+ <WordStagger
602
+ text="The best way to learn terminal commands is by doing — not reading."
603
+ className="text-narrative"
604
+ tag="p"
605
+ />
606
+ <p className="m-text-body reveal-blur delay-3" style={{ marginTop: 24 }}>
607
+ VisionOS is a guided, interactive learning environment that
608
+ lives right inside your terminal. Install it, pick a track,
609
+ and start typing real commands — with real-time validation,
610
+ progress tracking, and structured lessons.
611
+ </p>
612
+ </div>
613
+ <div className="reveal-right delay-2">
614
+ <TerminalWindow>
615
+ <TerminalLine type="prompt">
616
+ npm install -g visionos-cli
617
+ </TerminalLine>
618
+ <TerminalLine type="output">
619
+ + visionos-cli@0.1.0 installed
620
+ </TerminalLine>
621
+ <TerminalLine>&nbsp;</TerminalLine>
622
+ <TerminalLine type="prompt">visionos start</TerminalLine>
623
+ <TerminalLine type="success">
624
+ &nbsp; ✓ VisionOS runtime started
625
+ </TerminalLine>
626
+ <TerminalLine type="success">
627
+ &nbsp; ✓ Server running at 127.0.0.1:5173
628
+ </TerminalLine>
629
+ <TerminalLine type="output">
630
+ &nbsp; → Opening dashboard...
631
+ </TerminalLine>
632
+ </TerminalWindow>
633
+ <div className="figure-caption" style={{ marginTop: 20 }}>
634
+ <span className="figure-label">Fig 3</span>
635
+ <span className="figure-desc">
636
+ Two commands. That's all it takes to enter a guided terminal
637
+ classroom.
638
+ </span>
639
+ </div>
640
+ </div>
641
+ </div>
642
+ </div>
643
+ </section>
644
+
645
+ {/* ── SCANLINE DIVIDER ──────────────────────────── */}
646
+ <ScanlineDivider height={60} />
647
+
648
+ {/* ── HOW IT WORKS ─────────────────────────────── */}
649
+ <section className="m-section" id="how">
650
+ <div className="reveal">
651
+ <p className="text-label">How it works</p>
652
+ <h2 className="text-section-title" style={{ marginTop: 8 }}>
653
+ Three steps to mastery
654
+ </h2>
655
+ </div>
656
+ <div className="steps-grid">
657
+ {[
658
+ {
659
+ num: "01",
660
+ title: "Install",
661
+ desc: "Install VisionOS globally with a single npm command. No config, no fuss.",
662
+ code: "npm i -g visionos-cli",
663
+ },
664
+ {
665
+ num: "02",
666
+ title: "Start",
667
+ desc: "Launch the runtime. VisionOS spins up a local server and opens your dashboard.",
668
+ code: "visionos start",
669
+ },
670
+ {
671
+ num: "03",
672
+ title: "Learn",
673
+ desc: "Pick a learning track, follow guided lessons, and practice real commands with instant feedback.",
674
+ code: "visionos open",
675
+ },
676
+ ].map((step, i) => (
677
+ <div key={step.num} className={`step-card magnetic-hover reveal delay-${i + 1}`}>
678
+ <span className="step-number">{step.num} —</span>
679
+ <h3 className="step-title">{step.title}</h3>
680
+ <p className="m-text-body" style={{ maxWidth: "none" }}>
681
+ {step.desc}
682
+ </p>
683
+ <code className="step-code">{step.code}</code>
684
+ </div>
685
+ ))}
686
+ </div>
687
+ </section>
688
+
689
+ {/* ── HR animated divider ────────────────────────── */}
690
+ <hr className="hr-reveal" />
691
+
692
+ {/* ── LEARNING TRACKS ──────────────────────────── */}
693
+ <section className="m-section" id="tracks">
694
+ <div className="reveal">
695
+ <p className="text-label">Learning Tracks</p>
696
+ <h2 className="text-section-title" style={{ marginTop: 8 }}>
697
+ Pick your path
698
+ </h2>
699
+ <p className="m-text-body reveal-blur delay-1" style={{ marginTop: 16 }}>
700
+ Four structured tracks, each with guided lessons that build on
701
+ each other. Start from basics, reach mastery.
702
+ </p>
703
+ </div>
704
+ <div className="m-tracks-grid">
705
+ {LEARNING_TRACKS.map((track, i) => (
706
+ <div
707
+ key={track.id}
708
+ className={`m-track-card magnetic-hover reveal delay-${i + 1}`}
709
+ >
710
+ <div className="m-track-card-icon">
711
+ {trackIcons[track.id] ?? track.label[0]}
712
+ </div>
713
+ <h3 className="m-track-card-title">{track.label}</h3>
714
+ <p className="m-track-card-desc">{track.description}</p>
715
+ <div style={{ marginTop: 18, display: "grid", gap: 8 }}>
716
+ {BEGINNER_LESSONS.filter((lesson) => lesson.trackId === track.id).map((lesson) => {
717
+ const trackProgress = progress.find((p) => p.trackId === track.id);
718
+ const isCompleted = trackProgress?.completedLessonIds?.includes(lesson.id) ?? false;
719
+ const isActive = trackProgress?.currentLessonId === lesson.id;
720
+
721
+ return (
722
+ <div
723
+ key={lesson.id}
724
+ style={{
725
+ padding: "10px 12px",
726
+ background: "rgba(250, 250, 248, 0.56)",
727
+ border: "1px solid rgba(0,0,0,0.06)",
728
+ borderRadius: "var(--radius-md)",
729
+ position: "relative"
730
+ }}
731
+ >
732
+ <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8 }}>
733
+ <span style={{ display: "block", color: "var(--m-ink)", fontSize: "0.76rem", fontWeight: 800 }}>
734
+ {lesson.title}
735
+ </span>
736
+ <div style={{ display: "flex", gap: 4, flexShrink: 0 }}>
737
+ {user && isCompleted && (
738
+ <span className="lesson-status-badge lesson-status-badge--completed">COMPLETED</span>
739
+ )}
740
+ {user && isActive && (
741
+ <span className="lesson-status-badge lesson-status-badge--active">ACTIVE</span>
742
+ )}
743
+ </div>
744
+ </div>
745
+ <span style={{ display: "block", marginTop: 4, color: "var(--ink-muted)", fontSize: "0.7rem", lineHeight: 1.45 }}>
746
+ {lesson.steps.length} steps · {lesson.estimatedMinutes} min
747
+ </span>
748
+ </div>
749
+ );
750
+ })}
751
+ </div>
752
+ </div>
753
+ ))}
754
+ </div>
755
+ </section>
756
+
757
+ {/* ── ANIMATED SEPARATOR ─────────────────────────── */}
758
+ <div className="animated-separator" />
759
+
760
+ {/* ── SCANLINE DIVIDER ──────────────────────────── */}
761
+ <ScanlineDivider height={60} />
762
+
763
+ {/* ── CLI COMMANDS REFERENCE ────────────────────── */}
764
+ <section className="m-section" id="commands">
765
+ <div className="split-layout split-layout--text-left">
766
+ <div>
767
+ <div className="reveal">
768
+ <p className="text-label">CLI Reference</p>
769
+ <h2 className="text-section-title" style={{ marginTop: 8 }}>
770
+ Built for
771
+ <br />
772
+ the terminal
773
+ </h2>
774
+ <p className="m-text-body reveal-blur delay-1" style={{ marginTop: 20 }}>
775
+ Every interaction happens through clean, memorable commands.
776
+ No GUI required.
777
+ </p>
778
+ </div>
779
+ <div
780
+ className="commands-list reveal delay-2"
781
+ style={{ marginTop: 36 }}
782
+ >
783
+ {[
784
+ {
785
+ cmd: "visionos start",
786
+ desc: "Login, choose a track, and preview a lesson",
787
+ },
788
+ {
789
+ cmd: "visionos login",
790
+ desc: "Sign in or create an account through the browser",
791
+ },
792
+ {
793
+ cmd: "visionos lessons",
794
+ desc: "Browse beginner lessons for every track",
795
+ },
796
+ {
797
+ cmd: "visionos open",
798
+ desc: "Open the learning dashboard in your browser",
799
+ },
800
+ {
801
+ cmd: "visionos userinfo",
802
+ desc: "Display your profile & learning progress",
803
+ },
804
+ {
805
+ cmd: "visionos status",
806
+ desc: "Check if the runtime server is running",
807
+ },
808
+ {
809
+ cmd: "visionos logout",
810
+ desc: "Sign out and clear session data",
811
+ },
812
+ ].map((row) => (
813
+ <div key={row.cmd} className="command-row">
814
+ <span className="command-name">{row.cmd}</span>
815
+ <span className="command-desc">{row.desc}</span>
816
+ </div>
817
+ ))}
818
+ </div>
819
+ </div>
820
+
821
+ <div
822
+ className="reveal-right delay-3"
823
+ style={{
824
+ display: "flex",
825
+ alignItems: "center",
826
+ justifyContent: "center",
827
+ }}
828
+ >
829
+ <div className="access-card">
830
+ <div className="access-card-stripe" />
831
+ <div className="access-card-content">
832
+ <div>
833
+ <span className="access-card-label">
834
+ Terminal Access Card
835
+ </span>
836
+ <span className="access-card-value">
837
+ VisionOS{" "}
838
+ <span style={{ color: "var(--accent-teal)" }}>CLI</span>
839
+ </span>
840
+ </div>
841
+ <div className="access-card-row">
842
+ <div className="access-card-field">
843
+ <span className="access-card-label">Version</span>
844
+ <span className="access-card-value access-card-mono">
845
+ 0.1.0
846
+ </span>
847
+ </div>
848
+ <div className="access-card-field">
849
+ <span className="access-card-label">Runtime</span>
850
+ <span className="access-card-value access-card-mono">
851
+ Node.js
852
+ </span>
853
+ </div>
854
+ </div>
855
+ <div className="access-card-row">
856
+ <div className="access-card-field">
857
+ <span className="access-card-label">Port</span>
858
+ <span className="access-card-value access-card-mono">
859
+ 4000
860
+ </span>
861
+ </div>
862
+ <div className="access-card-field">
863
+ <span className="access-card-label">Tracks</span>
864
+ <span className="access-card-value access-card-mono">
865
+ 4
866
+ </span>
867
+ </div>
868
+ </div>
869
+ </div>
870
+ </div>
871
+ </div>
872
+ </div>
873
+ </section>
874
+
875
+ {/* ── CTA — START YOUR JOURNEY ─────────────────── */}
876
+ <section
877
+ className="m-section"
878
+ style={{ paddingBottom: "var(--space-xxl)" }}
879
+ id="cta"
880
+ >
881
+ <div
882
+ className="scanline-band reveal-scale"
883
+ style={{ marginBottom: "var(--space-xl)" }}
884
+ >
885
+ <span
886
+ className="scanline-text glitch-text"
887
+ style={{
888
+ textAlign: "center",
889
+ display: "block",
890
+ fontSize: "clamp(3rem, 8vw, 7rem)",
891
+ }}
892
+ >
893
+ BEGIN.
894
+ </span>
895
+ </div>
896
+
897
+ <div className="split-layout split-layout--text-left split-layout--center">
898
+ <div className="reveal-left">
899
+ <p className="text-label">Get Started</p>
900
+ <h2 className="text-section-title" style={{ marginTop: 8 }}>
901
+ Start your
902
+ <br />
903
+ terminal journey
904
+ </h2>
905
+ <p className="m-text-body reveal-blur delay-1" style={{ marginTop: 20 }}>
906
+ One command to install. One command to learn. The terminal
907
+ awaits.
908
+ </p>
909
+
910
+ <div
911
+ className={`copy-command ${copied ? "copied" : ""}`}
912
+ role="button"
913
+ tabIndex={0}
914
+ aria-label="Copy install command"
915
+ onClick={() => void handleCopy("npm install -g visionos-cli")}
916
+ onKeyDown={(e) => {
917
+ if (e.key === "Enter" || e.key === " ")
918
+ void handleCopy("npm install -g visionos-cli");
919
+ }}
920
+ >
921
+ <span style={{ opacity: 0.4 }}>$</span>
922
+ <span>npm install -g visionos-cli</span>
923
+ <span className="copy-icon">
924
+ <svg
925
+ width="18"
926
+ height="18"
927
+ viewBox="0 0 24 24"
928
+ fill="none"
929
+ stroke="currentColor"
930
+ strokeWidth="2"
931
+ strokeLinecap="round"
932
+ >
933
+ <rect x="9" y="9" width="13" height="13" rx="2" />
934
+ <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" />
935
+ </svg>
936
+ </span>
937
+ <span className="copied-tooltip">Copied!</span>
938
+ </div>
939
+
940
+ <p className="text-label" style={{ marginTop: 24 }}>
941
+ Then run{" "}
942
+ <strong style={{ color: "var(--m-ink)", fontWeight: 700 }}>
943
+ visionos start
944
+ </strong>
945
+ </p>
946
+ </div>
947
+
948
+ <div
949
+ className="reveal-right delay-2"
950
+ style={{
951
+ display: "flex",
952
+ alignItems: "center",
953
+ justifyContent: "center",
954
+ }}
955
+ >
956
+ <TerminalWindow style={{ width: "100%", maxWidth: 400 }}>
957
+ <TerminalLine type="prompt">
958
+ npm i -g visionos-cli
959
+ </TerminalLine>
960
+ <TerminalLine type="success">✓ installed globally</TerminalLine>
961
+ <TerminalLine>&nbsp;</TerminalLine>
962
+ <TerminalLine type="prompt">visionos start</TerminalLine>
963
+ <TerminalLine type="success">✓ Runtime active</TerminalLine>
964
+ <TerminalLine type="output">
965
+ &nbsp; Dashboard → http://127.0.0.1:5173
966
+ </TerminalLine>
967
+ <TerminalLine type="prompt">█</TerminalLine>
968
+ </TerminalWindow>
969
+ </div>
970
+ </div>
971
+ </section>
972
+
973
+ {/* ── FOOTER SHOWCASE SECTION (Image 2) ────────── */}
974
+ <ScanlineDivider height={60} />
975
+ <section className="m-footer-showcase">
976
+ {/* Header row */}
977
+ <div className="m-footer-showcase-header">
978
+ <div className="header-left">
979
+ <span className="header-brand">VisionOS, Inc.</span>
980
+ <span className="header-meta">
981
+ Copyright 2025
982
+ <br />
983
+ All Rights Reserved
984
+ </span>
985
+ </div>
986
+ <nav className="header-nav">
987
+ <a href="#tracks">
988
+ tracks <span className="arrow">↗</span>
989
+ </a>
990
+ <a href="#commands">
991
+ commands <span className="arrow">↗</span>
992
+ </a>
993
+ <a href="/creator" className="creator-nav-link">
994
+ create lessons <span className="arrow">↗</span>
995
+ </a>
996
+ <a href="#hero">@visionos</a>
997
+ </nav>
998
+ </div>
999
+
1000
+
1001
+
1002
+ <div className="m-footer-showcase-content">
1003
+ {/* Left: Heading + CTA */}
1004
+ <div className="reveal-left">
1005
+ <h2 className="m-footer-heading">
1006
+ Meet VisionOS
1007
+ <span className="m-footer-heading-sub">
1008
+ Interfaces for the future.
1009
+ </span>
1010
+ </h2>
1011
+
1012
+ <div className="m-footer-cta-area">
1013
+ {/* Icon */}
1014
+ <svg
1015
+ className="m-footer-cta-icon float-gentle"
1016
+ viewBox="0 0 64 64"
1017
+ fill="none"
1018
+ >
1019
+ <rect
1020
+ x="8"
1021
+ y="14"
1022
+ width="48"
1023
+ height="36"
1024
+ rx="4"
1025
+ stroke="var(--m-ink)"
1026
+ strokeWidth="3"
1027
+ />
1028
+ <path
1029
+ d="M20 30L28 38L44 22"
1030
+ stroke="var(--m-ink)"
1031
+ strokeWidth="3"
1032
+ strokeLinecap="round"
1033
+ strokeLinejoin="round"
1034
+ />
1035
+ </svg>
1036
+ <p className="m-footer-cta-text">
1037
+ Claim your terminal access card.
1038
+ </p>
1039
+ <div className="m-footer-input-group">
1040
+ <input
1041
+ type="email"
1042
+ className="m-footer-input"
1043
+ placeholder="Enter your email address"
1044
+ aria-label="Email address"
1045
+ />
1046
+ <button className="m-footer-send-btn" aria-label="Submit">
1047
+ <svg
1048
+ width="20"
1049
+ height="20"
1050
+ viewBox="0 0 24 24"
1051
+ fill="none"
1052
+ stroke="currentColor"
1053
+ strokeWidth="2"
1054
+ strokeLinecap="round"
1055
+ strokeLinejoin="round"
1056
+ >
1057
+ <line x1="22" y1="2" x2="11" y2="13" />
1058
+ <polygon points="22 2 15 22 11 13 2 9 22 2" />
1059
+ </svg>
1060
+ </button>
1061
+ </div>
1062
+ </div>
1063
+ </div>
1064
+
1065
+ {/* Right: ID Card */}
1066
+ <div className="reveal-right delay-2">
1067
+ <div className="m-id-card">
1068
+ <div className="m-id-card-image">
1069
+ <div className="m-id-card-image-inner">
1070
+ {user ? user.name.charAt(0).toUpperCase() : "V"}
1071
+ </div>
1072
+ </div>
1073
+ <div className="m-id-card-info" style={{ position: "relative" }}>
1074
+ <div>
1075
+ <span className="m-id-card-name-label">Name</span>
1076
+ <span className="m-id-card-name-value">
1077
+ {user ? user.name : "VisionOS"}
1078
+ <br />
1079
+ {user ? "Learner" : "Terminal"}
1080
+ </span>
1081
+ </div>
1082
+ <div className="m-id-card-barcode">10. 0001</div>
1083
+ <div className="m-id-card-footer">
1084
+ <div className="m-id-card-footer-item">
1085
+ <span className="m-id-card-footer-label">Cohort</span>
1086
+ <span className="m-id-card-footer-value">000.1</span>
1087
+ </div>
1088
+ <div className="m-id-card-footer-item">
1089
+ <span className="m-id-card-footer-label">Date</span>
1090
+ <span className="m-id-card-footer-value">
1091
+ {formattedDate}
1092
+ </span>
1093
+ </div>
1094
+ </div>
1095
+ </div>
1096
+ </div>
1097
+ </div>
1098
+ </div>
1099
+ </section>
1100
+
1101
+ {/* ── FOOTER ────────────────────────────────────── */}
1102
+ <footer className="m-site-footer">
1103
+ <div className="header-left">
1104
+ <span className="header-brand">VisionOS</span>
1105
+ <span className="header-meta">© 2025 · CLI-First Learning</span>
1106
+ </div>
1107
+ <nav className="header-nav">
1108
+ <a href="#hero">Top ↑</a>
1109
+ <a href="/creator" className="creator-nav-link">Create Lessons</a>
1110
+ <a
1111
+ href="https://github.com/darklegend91/VisionOS"
1112
+ target="_blank"
1113
+ rel="noopener noreferrer"
1114
+ >
1115
+ GitHub <span className="arrow">↗</span>
1116
+ </a>
1117
+ </nav>
1118
+ </footer>
1119
+ </div>
1120
+
1121
+ <div className="bezel-notch">
1122
+ <div className="bezel-notch-inner" />
1123
+ </div>
1124
+ </div>
1125
+ );
1126
+ }