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.
- package/.claude/worktrees/competent-burnell-8d1330/README.md +138 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/package.json +35 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/scripts/copy-web-assets.mjs +12 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/logout.ts +12 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/open.ts +19 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/start.ts +97 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/status.ts +23 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/userinfo.ts +47 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/index.ts +23 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/lib/auth.ts +84 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/lib/browser.ts +37 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/lib/localState.ts +80 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/lib/runtime.ts +203 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/runtime/index.ts +36 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/types/inquirer.d.ts +9 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/tsconfig.json +19 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/index.html +15 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/package.json +27 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/postcss.config.cjs +7 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/src/App.tsx +57 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/src/components/CliAuthPage.tsx +385 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/src/components/ManifestoPage.tsx +946 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/src/components/TrackCard.tsx +19 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/src/lib/api.ts +58 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/src/main.tsx +11 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/src/styles/index.css +33 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/src/styles/manifesto.css +1398 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/tailwind.config.ts +36 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/tsconfig.json +25 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/vite.config.ts +20 -0
- package/.claude/worktrees/competent-burnell-8d1330/package-lock.json +5278 -0
- package/.claude/worktrees/competent-burnell-8d1330/package.json +24 -0
- package/.claude/worktrees/competent-burnell-8d1330/server/package.json +25 -0
- package/.claude/worktrees/competent-burnell-8d1330/server/src/app.ts +71 -0
- package/.claude/worktrees/competent-burnell-8d1330/server/src/config/env.ts +14 -0
- package/.claude/worktrees/competent-burnell-8d1330/server/src/features/auth/sessionStore.ts +74 -0
- package/.claude/worktrees/competent-burnell-8d1330/server/src/index.ts +8 -0
- package/.claude/worktrees/competent-burnell-8d1330/server/src/routes/auth.ts +112 -0
- package/.claude/worktrees/competent-burnell-8d1330/server/src/routes/health.ts +14 -0
- package/.claude/worktrees/competent-burnell-8d1330/server/tsconfig.json +19 -0
- package/.claude/worktrees/competent-burnell-8d1330/shared/package.json +24 -0
- package/.claude/worktrees/competent-burnell-8d1330/shared/src/index.ts +91 -0
- package/.claude/worktrees/competent-burnell-8d1330/shared/tsconfig.json +16 -0
- package/.claude/worktrees/competent-burnell-8d1330/tsconfig.base.json +12 -0
- package/.claude/worktrees/competent-burnell-8d1330/visionos-manifesto/index.html +392 -0
- package/.claude/worktrees/competent-burnell-8d1330/visionos-manifesto/script.js +146 -0
- package/.claude/worktrees/competent-burnell-8d1330/visionos-manifesto/styles.css +1082 -0
- package/.claude/worktrees/vigilant-napier-0de76f/README.md +138 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/package.json +35 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/scripts/copy-web-assets.mjs +12 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/logout.ts +12 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/open.ts +19 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/start.ts +97 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/status.ts +23 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/userinfo.ts +47 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/index.ts +23 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/lib/auth.ts +84 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/lib/browser.ts +37 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/lib/localState.ts +80 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/lib/runtime.ts +203 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/runtime/index.ts +36 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/types/inquirer.d.ts +9 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/tsconfig.json +19 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/index.html +15 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/package.json +27 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/postcss.config.cjs +7 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/src/App.tsx +57 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/src/components/CliAuthPage.tsx +385 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/src/components/ManifestoPage.tsx +946 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/src/components/TrackCard.tsx +19 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/src/lib/api.ts +58 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/src/main.tsx +11 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/src/styles/index.css +33 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/src/styles/manifesto.css +1398 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/tailwind.config.ts +36 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/tsconfig.json +25 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/vite.config.ts +20 -0
- package/.claude/worktrees/vigilant-napier-0de76f/package-lock.json +5278 -0
- package/.claude/worktrees/vigilant-napier-0de76f/package.json +24 -0
- package/.claude/worktrees/vigilant-napier-0de76f/server/package.json +25 -0
- package/.claude/worktrees/vigilant-napier-0de76f/server/src/app.ts +71 -0
- package/.claude/worktrees/vigilant-napier-0de76f/server/src/config/env.ts +14 -0
- package/.claude/worktrees/vigilant-napier-0de76f/server/src/features/auth/sessionStore.ts +74 -0
- package/.claude/worktrees/vigilant-napier-0de76f/server/src/index.ts +8 -0
- package/.claude/worktrees/vigilant-napier-0de76f/server/src/routes/auth.ts +112 -0
- package/.claude/worktrees/vigilant-napier-0de76f/server/src/routes/health.ts +14 -0
- package/.claude/worktrees/vigilant-napier-0de76f/server/tsconfig.json +19 -0
- package/.claude/worktrees/vigilant-napier-0de76f/shared/package.json +24 -0
- package/.claude/worktrees/vigilant-napier-0de76f/shared/src/index.ts +91 -0
- package/.claude/worktrees/vigilant-napier-0de76f/shared/tsconfig.json +16 -0
- package/.claude/worktrees/vigilant-napier-0de76f/tsconfig.base.json +12 -0
- package/.claude/worktrees/vigilant-napier-0de76f/visionos-manifesto/index.html +392 -0
- package/.claude/worktrees/vigilant-napier-0de76f/visionos-manifesto/script.js +146 -0
- package/.claude/worktrees/vigilant-napier-0de76f/visionos-manifesto/styles.css +1082 -0
- package/.github/workflows/publish.yml +30 -0
- package/README.md +175 -0
- package/cli/README.md +165 -0
- package/cli/package.json +36 -0
- package/cli/scripts/copy-web-assets.mjs +12 -0
- package/cli/src/commands/lessons.ts +68 -0
- package/cli/src/commands/login.ts +46 -0
- package/cli/src/commands/logout.ts +12 -0
- package/cli/src/commands/open.ts +29 -0
- package/cli/src/commands/start.ts +146 -0
- package/cli/src/commands/status.ts +28 -0
- package/cli/src/commands/userinfo.ts +59 -0
- package/cli/src/index.ts +109 -0
- package/cli/src/lib/auth.ts +84 -0
- package/cli/src/lib/browser.ts +37 -0
- package/cli/src/lib/content.ts +57 -0
- package/cli/src/lib/lessonPrinter.ts +38 -0
- package/cli/src/lib/lessonRunner.ts +381 -0
- package/cli/src/lib/localState.ts +114 -0
- package/cli/src/lib/loginFlow.ts +74 -0
- package/cli/src/lib/progress.ts +94 -0
- package/cli/src/lib/runtime.ts +220 -0
- package/cli/src/lib/validator.ts +401 -0
- package/cli/src/runtime/index.ts +108 -0
- package/cli/src/types/inquirer.d.ts +9 -0
- package/cli/tsconfig.json +19 -0
- package/client/index.html +15 -0
- package/client/package.json +27 -0
- package/client/postcss.config.cjs +7 -0
- package/client/src/App.tsx +102 -0
- package/client/src/components/AccountPage.tsx +79 -0
- package/client/src/components/AuthPanel.tsx +312 -0
- package/client/src/components/CliAuthPage.tsx +367 -0
- package/client/src/components/CreatorPortal.tsx +885 -0
- package/client/src/components/ErrorBoundary.tsx +92 -0
- package/client/src/components/ManifestoPage.tsx +1126 -0
- package/client/src/components/TrackCard.tsx +19 -0
- package/client/src/lib/api.ts +215 -0
- package/client/src/main.tsx +14 -0
- package/client/src/styles/index.css +33 -0
- package/client/src/styles/manifesto.css +1828 -0
- package/client/tailwind.config.ts +36 -0
- package/client/tsconfig.json +25 -0
- package/client/vercel.json +8 -0
- package/client/vite.config.ts +33 -0
- package/package.json +27 -0
- package/server/package.json +26 -0
- package/server/src/app.ts +132 -0
- package/server/src/config/env.ts +135 -0
- package/server/src/features/accounts/accountStore.ts +359 -0
- package/server/src/features/accounts/contentStore.ts +264 -0
- package/server/src/features/accounts/password.ts +26 -0
- package/server/src/features/auth/sessionStore.ts +79 -0
- package/server/src/index.ts +8 -0
- package/server/src/routes/auth.ts +328 -0
- package/server/src/routes/content.ts +174 -0
- package/server/src/routes/health.ts +14 -0
- package/server/src/routes/progress.ts +105 -0
- package/server/tsconfig.json +19 -0
- package/shared/package.json +24 -0
- package/shared/src/index.ts +455 -0
- package/shared/tsconfig.json +16 -0
- package/tsconfig.base.json +12 -0
- package/visionos-manifesto/index.html +392 -0
- package/visionos-manifesto/script.js +146 -0
- 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}
|
|
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> </TerminalLine>
|
|
523
|
+
<TerminalLine type="prompt">
|
|
524
|
+
echo "hello, terminal"
|
|
525
|
+
</TerminalLine>
|
|
526
|
+
<TerminalLine type="success">hello, terminal</TerminalLine>
|
|
527
|
+
<TerminalLine> </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> </TerminalLine>
|
|
622
|
+
<TerminalLine type="prompt">visionos start</TerminalLine>
|
|
623
|
+
<TerminalLine type="success">
|
|
624
|
+
✓ VisionOS runtime started
|
|
625
|
+
</TerminalLine>
|
|
626
|
+
<TerminalLine type="success">
|
|
627
|
+
✓ Server running at 127.0.0.1:5173
|
|
628
|
+
</TerminalLine>
|
|
629
|
+
<TerminalLine type="output">
|
|
630
|
+
→ 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> </TerminalLine>
|
|
962
|
+
<TerminalLine type="prompt">visionos start</TerminalLine>
|
|
963
|
+
<TerminalLine type="success">✓ Runtime active</TerminalLine>
|
|
964
|
+
<TerminalLine type="output">
|
|
965
|
+
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
|
+
}
|