adam-os 0.1.5 → 0.2.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/app/get/CopyButton.tsx +27 -0
- package/app/get/layout.tsx +33 -0
- package/app/get/page.tsx +332 -0
- package/bin/adam-os.js +10 -10
- package/components/Apps/ContactForm.tsx +6 -4
- package/components/Apps/Music.tsx +774 -0
- package/components/Apps/Welcome.tsx +2 -13
- package/components/Desktop/Desktop.tsx +18 -17
- package/components/Mobile/MobileLayout.tsx +153 -37
- package/components/Window/AppWindow.tsx +26 -0
- package/context/WindowManager.tsx +28 -4
- package/data/IMAGE_MAP.md +102 -0
- package/data/portfolio.json +97 -36
- package/hooks/useDraggable.ts +34 -5
- package/hooks/useResizable.ts +16 -1
- package/next.config.ts +12 -7
- package/package.json +2 -1
- package/public/icons/about-me-icon.png +0 -0
- package/public/icons/design-system.png +0 -0
- package/public/icons/music.webp +0 -0
- package/public/icons/welcome.webp +0 -0
- package/public/portfolio/parcl-design-system/hero.png +0 -0
- package/styles/win95.css +251 -20
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
|
|
5
|
+
export default function CopyButton({ text }: { text: string }) {
|
|
6
|
+
const [copied, setCopied] = useState(false);
|
|
7
|
+
|
|
8
|
+
async function handleCopy() {
|
|
9
|
+
try {
|
|
10
|
+
await navigator.clipboard.writeText(text);
|
|
11
|
+
setCopied(true);
|
|
12
|
+
setTimeout(() => setCopied(false), 2000);
|
|
13
|
+
} catch {
|
|
14
|
+
// fallback: select a hidden textarea
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<button
|
|
20
|
+
onClick={handleCopy}
|
|
21
|
+
className="win95-btn"
|
|
22
|
+
style={{ fontSize: 11, padding: "3px 10px", minHeight: 24 }}
|
|
23
|
+
>
|
|
24
|
+
{copied ? "Copied!" : "Copy"}
|
|
25
|
+
</button>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { Inter, JetBrains_Mono } from "next/font/google";
|
|
3
|
+
import "@/app/globals.css";
|
|
4
|
+
|
|
5
|
+
// Isolated layout — no WindowManagerProvider, no Desktop/Taskbar chrome.
|
|
6
|
+
// Must define its own <html>/<body> to opt out of the root layout tree.
|
|
7
|
+
|
|
8
|
+
const inter = Inter({
|
|
9
|
+
subsets: ["latin"],
|
|
10
|
+
variable: "--font-inter",
|
|
11
|
+
display: "swap",
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const jetbrainsMono = JetBrains_Mono({
|
|
15
|
+
subsets: ["latin"],
|
|
16
|
+
variable: "--font-jetbrains",
|
|
17
|
+
display: "swap",
|
|
18
|
+
weight: ["400", "500", "700"],
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const metadata: Metadata = {
|
|
22
|
+
title: "AdamOS — Get the App",
|
|
23
|
+
description:
|
|
24
|
+
"Open AdamOS in your browser or run it locally via the CLI. Windows 95-style portfolio by Adam Bush.",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default function GetLayout({ children }: { children: React.ReactNode }) {
|
|
28
|
+
return (
|
|
29
|
+
<html lang="en" className={`${inter.variable} ${jetbrainsMono.variable}`}>
|
|
30
|
+
<body>{children}</body>
|
|
31
|
+
</html>
|
|
32
|
+
);
|
|
33
|
+
}
|
package/app/get/page.tsx
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import CopyButton from "./CopyButton";
|
|
2
|
+
|
|
3
|
+
const LIVE_URL = "https://adam-bush.vercel.app/";
|
|
4
|
+
|
|
5
|
+
const COMMANDS = {
|
|
6
|
+
npxMac: "npx adam-os",
|
|
7
|
+
npxWin: "npx adam-os",
|
|
8
|
+
installMac:
|
|
9
|
+
"curl -fsSL https://raw.githubusercontent.com/iamwitness/portfolio-adam-bush-96/main/install.sh | bash",
|
|
10
|
+
installWin:
|
|
11
|
+
"irm https://raw.githubusercontent.com/iamwitness/portfolio-adam-bush-96/main/install.ps1 | iex",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// ── Sub-components ────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
function CodeBlock({
|
|
17
|
+
label,
|
|
18
|
+
icon,
|
|
19
|
+
command,
|
|
20
|
+
}: {
|
|
21
|
+
label: string;
|
|
22
|
+
icon: string;
|
|
23
|
+
command: string;
|
|
24
|
+
}) {
|
|
25
|
+
return (
|
|
26
|
+
<div style={{ marginBottom: 10 }}>
|
|
27
|
+
<div
|
|
28
|
+
style={{
|
|
29
|
+
display: "flex",
|
|
30
|
+
alignItems: "center",
|
|
31
|
+
gap: 6,
|
|
32
|
+
marginBottom: 4,
|
|
33
|
+
fontSize: 11,
|
|
34
|
+
color: "var(--win95-text-disabled)",
|
|
35
|
+
fontFamily: "var(--font-body)",
|
|
36
|
+
fontWeight: 600,
|
|
37
|
+
textTransform: "uppercase",
|
|
38
|
+
letterSpacing: "0.05em",
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
<span>{icon}</span>
|
|
42
|
+
{label}
|
|
43
|
+
</div>
|
|
44
|
+
<div
|
|
45
|
+
className="win95-inset"
|
|
46
|
+
style={{
|
|
47
|
+
display: "flex",
|
|
48
|
+
alignItems: "center",
|
|
49
|
+
justifyContent: "space-between",
|
|
50
|
+
gap: 8,
|
|
51
|
+
padding: "8px 10px",
|
|
52
|
+
background: "#1a1a1a",
|
|
53
|
+
borderColor: "#555",
|
|
54
|
+
}}
|
|
55
|
+
>
|
|
56
|
+
<code
|
|
57
|
+
style={{
|
|
58
|
+
fontFamily: "var(--font-mono)",
|
|
59
|
+
fontSize: 11,
|
|
60
|
+
color: "#d4f5a0",
|
|
61
|
+
wordBreak: "break-all",
|
|
62
|
+
flex: 1,
|
|
63
|
+
}}
|
|
64
|
+
>
|
|
65
|
+
{command}
|
|
66
|
+
</code>
|
|
67
|
+
<CopyButton text={command} />
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function SectionLabel({ children }: { children: React.ReactNode }) {
|
|
74
|
+
return (
|
|
75
|
+
<p
|
|
76
|
+
style={{
|
|
77
|
+
fontSize: 11,
|
|
78
|
+
fontWeight: 700,
|
|
79
|
+
color: "var(--win95-text)",
|
|
80
|
+
marginBottom: 8,
|
|
81
|
+
display: "flex",
|
|
82
|
+
alignItems: "center",
|
|
83
|
+
gap: 6,
|
|
84
|
+
}}
|
|
85
|
+
>
|
|
86
|
+
{children}
|
|
87
|
+
</p>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function Divider() {
|
|
92
|
+
return (
|
|
93
|
+
<hr
|
|
94
|
+
style={{
|
|
95
|
+
border: "none",
|
|
96
|
+
borderTop: "1px solid var(--win95-border-dark)",
|
|
97
|
+
margin: "14px 0",
|
|
98
|
+
opacity: 0.4,
|
|
99
|
+
}}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── Page ──────────────────────────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
export default function GetPage() {
|
|
107
|
+
return (
|
|
108
|
+
<div
|
|
109
|
+
style={{
|
|
110
|
+
width: "100vw",
|
|
111
|
+
height: "100vh",
|
|
112
|
+
display: "flex",
|
|
113
|
+
alignItems: "center",
|
|
114
|
+
justifyContent: "center",
|
|
115
|
+
background: "var(--win95-bg)",
|
|
116
|
+
padding: "16px",
|
|
117
|
+
overflow: "auto",
|
|
118
|
+
}}
|
|
119
|
+
>
|
|
120
|
+
{/* Window chrome */}
|
|
121
|
+
<div
|
|
122
|
+
style={{
|
|
123
|
+
background: "var(--win95-chrome)",
|
|
124
|
+
borderRadius: "var(--radius-md)",
|
|
125
|
+
boxShadow: "var(--shadow-window)",
|
|
126
|
+
width: "100%",
|
|
127
|
+
maxWidth: 860,
|
|
128
|
+
overflow: "hidden",
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
{/* Title bar */}
|
|
132
|
+
<div className="win95-titlebar">
|
|
133
|
+
<span style={{ fontSize: 16, lineHeight: 1 }}>🖥</span>
|
|
134
|
+
<span className="win95-titlebar-title">
|
|
135
|
+
AdamOS — Choose Your Experience
|
|
136
|
+
</span>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
{/* Body */}
|
|
140
|
+
<div style={{ padding: "20px 20px 16px" }}>
|
|
141
|
+
<p
|
|
142
|
+
style={{
|
|
143
|
+
fontSize: "var(--font-size-sm)",
|
|
144
|
+
marginBottom: 16,
|
|
145
|
+
color: "var(--win95-text-disabled)",
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
How would you like to run AdamOS?
|
|
149
|
+
</p>
|
|
150
|
+
|
|
151
|
+
{/* Two-panel layout */}
|
|
152
|
+
<div
|
|
153
|
+
style={{
|
|
154
|
+
display: "grid",
|
|
155
|
+
gridTemplateColumns: "1fr 1fr",
|
|
156
|
+
gap: 12,
|
|
157
|
+
}}
|
|
158
|
+
className="get-panels"
|
|
159
|
+
>
|
|
160
|
+
{/* ── Left: Open in Browser ── */}
|
|
161
|
+
<div
|
|
162
|
+
className="win95-raised"
|
|
163
|
+
style={{
|
|
164
|
+
padding: "20px",
|
|
165
|
+
display: "flex",
|
|
166
|
+
flexDirection: "column",
|
|
167
|
+
gap: 12,
|
|
168
|
+
background: "var(--win95-window-bg)",
|
|
169
|
+
}}
|
|
170
|
+
>
|
|
171
|
+
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
|
172
|
+
<span style={{ fontSize: 24 }}>🌐</span>
|
|
173
|
+
<div>
|
|
174
|
+
<p
|
|
175
|
+
style={{
|
|
176
|
+
fontWeight: 700,
|
|
177
|
+
fontSize: "var(--font-size-base)",
|
|
178
|
+
}}
|
|
179
|
+
>
|
|
180
|
+
Open in Browser
|
|
181
|
+
</p>
|
|
182
|
+
<p
|
|
183
|
+
style={{
|
|
184
|
+
fontSize: 11,
|
|
185
|
+
color: "var(--win95-text-disabled)",
|
|
186
|
+
marginTop: 2,
|
|
187
|
+
}}
|
|
188
|
+
>
|
|
189
|
+
No install required
|
|
190
|
+
</p>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<p
|
|
195
|
+
style={{
|
|
196
|
+
fontSize: "var(--font-size-sm)",
|
|
197
|
+
color: "var(--win95-text)",
|
|
198
|
+
lineHeight: 1.5,
|
|
199
|
+
}}
|
|
200
|
+
>
|
|
201
|
+
The full Win95 desktop experience, live in your browser.
|
|
202
|
+
Nothing to install.
|
|
203
|
+
</p>
|
|
204
|
+
|
|
205
|
+
<a
|
|
206
|
+
href={LIVE_URL}
|
|
207
|
+
target="_blank"
|
|
208
|
+
rel="noopener noreferrer"
|
|
209
|
+
className="win95-btn"
|
|
210
|
+
style={{
|
|
211
|
+
marginTop: "auto",
|
|
212
|
+
textDecoration: "none",
|
|
213
|
+
background: "var(--win95-highlight)",
|
|
214
|
+
color: "var(--win95-highlight-text)",
|
|
215
|
+
fontWeight: 600,
|
|
216
|
+
fontSize: "var(--font-size-sm)",
|
|
217
|
+
padding: "8px 20px",
|
|
218
|
+
justifyContent: "center",
|
|
219
|
+
}}
|
|
220
|
+
>
|
|
221
|
+
Launch AdamOS →
|
|
222
|
+
</a>
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
{/* ── Right: Run Locally ── */}
|
|
226
|
+
<div
|
|
227
|
+
className="win95-raised"
|
|
228
|
+
style={{
|
|
229
|
+
padding: "20px",
|
|
230
|
+
display: "flex",
|
|
231
|
+
flexDirection: "column",
|
|
232
|
+
gap: 0,
|
|
233
|
+
background: "var(--win95-window-bg)",
|
|
234
|
+
}}
|
|
235
|
+
>
|
|
236
|
+
<div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 14 }}>
|
|
237
|
+
<span style={{ fontSize: 24 }}>💻</span>
|
|
238
|
+
<div>
|
|
239
|
+
<p
|
|
240
|
+
style={{
|
|
241
|
+
fontWeight: 700,
|
|
242
|
+
fontSize: "var(--font-size-base)",
|
|
243
|
+
}}
|
|
244
|
+
>
|
|
245
|
+
Run Locally
|
|
246
|
+
</p>
|
|
247
|
+
<p
|
|
248
|
+
style={{
|
|
249
|
+
fontSize: 11,
|
|
250
|
+
color: "var(--win95-text-disabled)",
|
|
251
|
+
marginTop: 2,
|
|
252
|
+
}}
|
|
253
|
+
>
|
|
254
|
+
Runs on your machine
|
|
255
|
+
</p>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
|
|
259
|
+
{/* Section A: Have Node.js */}
|
|
260
|
+
<SectionLabel>
|
|
261
|
+
<span
|
|
262
|
+
style={{
|
|
263
|
+
display: "inline-block",
|
|
264
|
+
width: 6,
|
|
265
|
+
height: 6,
|
|
266
|
+
borderRadius: "50%",
|
|
267
|
+
background: "#4caf50",
|
|
268
|
+
flexShrink: 0,
|
|
269
|
+
}}
|
|
270
|
+
/>
|
|
271
|
+
Have Node.js ≥ 18? Run directly:
|
|
272
|
+
</SectionLabel>
|
|
273
|
+
|
|
274
|
+
<CodeBlock label="macOS · Terminal" icon="🍎" command={COMMANDS.npxMac} />
|
|
275
|
+
<CodeBlock label="Windows · PowerShell" icon="🪟" command={COMMANDS.npxWin} />
|
|
276
|
+
|
|
277
|
+
<Divider />
|
|
278
|
+
|
|
279
|
+
{/* Section B: Full installer */}
|
|
280
|
+
<SectionLabel>
|
|
281
|
+
<span
|
|
282
|
+
style={{
|
|
283
|
+
display: "inline-block",
|
|
284
|
+
width: 6,
|
|
285
|
+
height: 6,
|
|
286
|
+
borderRadius: "50%",
|
|
287
|
+
background: "#ff9800",
|
|
288
|
+
flexShrink: 0,
|
|
289
|
+
}}
|
|
290
|
+
/>
|
|
291
|
+
Don't have Node.js? Full install:
|
|
292
|
+
</SectionLabel>
|
|
293
|
+
|
|
294
|
+
<CodeBlock
|
|
295
|
+
label="macOS · Terminal"
|
|
296
|
+
icon="🍎"
|
|
297
|
+
command={COMMANDS.installMac}
|
|
298
|
+
/>
|
|
299
|
+
<CodeBlock
|
|
300
|
+
label="Windows · PowerShell"
|
|
301
|
+
icon="🪟"
|
|
302
|
+
command={COMMANDS.installWin}
|
|
303
|
+
/>
|
|
304
|
+
</div>
|
|
305
|
+
</div>
|
|
306
|
+
|
|
307
|
+
{/* Footer */}
|
|
308
|
+
<p
|
|
309
|
+
style={{
|
|
310
|
+
fontSize: 11,
|
|
311
|
+
color: "var(--win95-text-disabled)",
|
|
312
|
+
marginTop: 12,
|
|
313
|
+
textAlign: "center",
|
|
314
|
+
}}
|
|
315
|
+
>
|
|
316
|
+
CLI mode requires Node.js ≥ 18 · The full installer handles
|
|
317
|
+
everything automatically.
|
|
318
|
+
</p>
|
|
319
|
+
</div>
|
|
320
|
+
</div>
|
|
321
|
+
|
|
322
|
+
{/* Responsive: stack panels on small screens */}
|
|
323
|
+
<style>{`
|
|
324
|
+
@media (max-width: 600px) {
|
|
325
|
+
.get-panels {
|
|
326
|
+
grid-template-columns: 1fr !important;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
`}</style>
|
|
330
|
+
</div>
|
|
331
|
+
);
|
|
332
|
+
}
|
package/bin/adam-os.js
CHANGED
|
@@ -116,7 +116,7 @@ function printBanner(url, mode) {
|
|
|
116
116
|
async function main() {
|
|
117
117
|
checkNodeVersion();
|
|
118
118
|
|
|
119
|
-
const
|
|
119
|
+
const isDev = process.argv.includes("--dev");
|
|
120
120
|
const portArg = process.argv.find((a) => a.startsWith("--port="));
|
|
121
121
|
const basePort = portArg ? parseInt(portArg.split("=")[1], 10) : 3000;
|
|
122
122
|
|
|
@@ -137,12 +137,19 @@ async function main() {
|
|
|
137
137
|
process.on("SIGINT", () => process.exit(0));
|
|
138
138
|
process.on("SIGTERM", () => process.exit(0));
|
|
139
139
|
|
|
140
|
-
if (
|
|
140
|
+
if (isDev) {
|
|
141
|
+
printBanner(url, "Development (live reload)");
|
|
142
|
+
const server = spawn(nextBin, ["dev", "--port", String(port)], {
|
|
143
|
+
cwd: PKG_ROOT, stdio: "inherit", env,
|
|
144
|
+
});
|
|
145
|
+
setTimeout(() => openBrowser(url), 3500);
|
|
146
|
+
server.on("exit", (code) => process.exit(code ?? 0));
|
|
147
|
+
} else {
|
|
141
148
|
console.log("\n Building production bundle (this takes ~30 seconds)...\n");
|
|
142
149
|
try {
|
|
143
150
|
execSync(`"${nextBin}" build`, { cwd: PKG_ROOT, stdio: "inherit", env });
|
|
144
151
|
} catch (_) {
|
|
145
|
-
console.error("\n ✗ Build failed. Try dev mode: npx adam-os\n");
|
|
152
|
+
console.error("\n ✗ Build failed. Try dev mode: npx adam-os --dev\n");
|
|
146
153
|
process.exit(1);
|
|
147
154
|
}
|
|
148
155
|
printBanner(url, "Production");
|
|
@@ -151,13 +158,6 @@ async function main() {
|
|
|
151
158
|
});
|
|
152
159
|
setTimeout(() => openBrowser(url), 2000);
|
|
153
160
|
server.on("exit", (code) => process.exit(code ?? 0));
|
|
154
|
-
} else {
|
|
155
|
-
printBanner(url, "Development (live reload)");
|
|
156
|
-
const server = spawn(nextBin, ["dev", "--port", String(port)], {
|
|
157
|
-
cwd: PKG_ROOT, stdio: "inherit", env,
|
|
158
|
-
});
|
|
159
|
-
setTimeout(() => openBrowser(url), 3500);
|
|
160
|
-
server.on("exit", (code) => process.exit(code ?? 0));
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
|
|
@@ -29,16 +29,16 @@ export function ContactForm() {
|
|
|
29
29
|
</div>
|
|
30
30
|
) : (
|
|
31
31
|
<form
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}}
|
|
32
|
+
action={`mailto:${siteConfig.email}`}
|
|
33
|
+
method="get"
|
|
34
|
+
encType="text/plain"
|
|
36
35
|
style={{ display: "flex", flexDirection: "column", gap: 12 }}
|
|
37
36
|
>
|
|
38
37
|
<label style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
|
39
38
|
<span>Name</span>
|
|
40
39
|
<input
|
|
41
40
|
type="text"
|
|
41
|
+
name="Name"
|
|
42
42
|
className="win95-inset"
|
|
43
43
|
required
|
|
44
44
|
style={{
|
|
@@ -54,6 +54,7 @@ export function ContactForm() {
|
|
|
54
54
|
<span>Email</span>
|
|
55
55
|
<input
|
|
56
56
|
type="email"
|
|
57
|
+
name="Email"
|
|
57
58
|
className="win95-inset"
|
|
58
59
|
required
|
|
59
60
|
style={{
|
|
@@ -68,6 +69,7 @@ export function ContactForm() {
|
|
|
68
69
|
<label style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
|
69
70
|
<span>Message</span>
|
|
70
71
|
<textarea
|
|
72
|
+
name="Message"
|
|
71
73
|
className="win95-inset"
|
|
72
74
|
required
|
|
73
75
|
rows={5}
|