diff --git a/public/docnest-icon-32.png b/public/docnest-icon-32.png
new file mode 100644
index 0000000..3d736b0
Binary files /dev/null and b/public/docnest-icon-32.png differ
diff --git a/public/pgp.asc b/public/pgp.asc
new file mode 100644
index 0000000..7a3b4d7
--- /dev/null
+++ b/public/pgp.asc
@@ -0,0 +1,26 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mDMEafxj9hYJKwYBBAHaRw8BAQdAuem8vfNFdu8MdEgkufBSWXIyYZofTxx/hNqM
+WKAoi3O0JE1pbmdkYSBYaWUgPHhpZW1pbmdkYTIwMjBAZ21haWwuY29tPoi1BBMW
+CgBdFiEEjFCtXc4OlJ45+vGdYJxwTlRKC/0FAmn8Y/YbFIAAAAAABAAObWFudTIs
+Mi41KzEuMTIsMCwzAhsBBQkJZgGABQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheA
+AAoJEGCccE5USgv9iugA/2BRU4ooaWF89QigzIyCK0g//qzw//0ZqT0dsKsYhYwX
+AQCv+9lJnAJSpV+CsypiKtAWp7Rf3ryim2ZnSE0Jxiy8C7gzBGn8aooWCSsGAQQB
+2kcPAQEHQJej8vLd6Zp/iC6j9rd/2jVPqDvS3HjH8eqNHYUpOVruiQERBBgWCgBC
+FiEEjFCtXc4OlJ45+vGdYJxwTlRKC/0FAmn8aoobFIAAAAAABAAObWFudTIsMi41
+KzEuMTIsMCwzAhsCBQkDwmcAAIEJEGCccE5USgv9diAEGRYKAB0WIQSNntaQmSEM
+uam1aJhRySHSOW66vwUCafxqigAKCRBRySHSOW66v/LdAP9co9+y961vHGuVuif4
+V5/Ngj9f3zGPJCS2726Dt7HH3gEAhUMV6kIUW0Ii9UXcTi54m+ldHEHcXVyOAlA4
+GYtlLwkKDAD7B3FSt615NnFWE522VrmcA/UyrcvvyGMBJTIISuWz5X4A/3msKBYh
+V3Ov5ir30JsHdq/34yKCKFDTKdqt3lXjf34MuDgEafxq7xIKKwYBBAGXVQEFAQEH
+QHYOZ8u8g9QGrTkj9Hd4/A5JKHCLQb/72PiKrnaTmRB0AwEIB4iaBBgWCgBCFiEE
+jFCtXc4OlJ45+vGdYJxwTlRKC/0FAmn8au8bFIAAAAAABAAObWFudTIsMi41KzEu
+MTIsMCwzAhsMBQkDwmcAAAoJEGCccE5USgv9gbAA/AvmH45NgIyD8T1qQ+kEtRSv
+O+CQLHbOt44R5wOuGMdmAQD/Elb2BuIHeGnFL/Q5x8+irKX59cJhxVWgRxPL0DW3
+CLgzBGn8aygWCSsGAQQB2kcPAQEHQH3HCFluWlBCg+wE2z7IlCueCMOLK+ghOtIY
+sBzwdVxIiJoEGBYKAEIWIQSMUK1dzg6Unjn68Z1gnHBOVEoL/QUCafxrKBsUgAAA
+AAAEAA5tYW51MiwyLjUrMS4xMiwwLDMCGyAFCQPCZwAACgkQYJxwTlRKC/2PwAD+
+KVRABLUlz27lVoY27Y54wyDIssIXYvw//2LU92a0SzsBAJLa2F1uihpBO6C5PgoL
+F7lvu2C0si9LdM0JclC2UvUD
+=opD7
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/App.css b/src/App.css
index 4834b10..ecaa212 100644
--- a/src/App.css
+++ b/src/App.css
@@ -37,3 +37,43 @@ html, body, #root {
font-smooth: never;
image-rendering: pixelated;
}
+
+/* CRT scanlines + faint phosphor glow over the entire desktop */
+#root::after {
+ content: '';
+ position: fixed;
+ inset: 0;
+ pointer-events: none;
+ z-index: 9998;
+ background:
+ repeating-linear-gradient(
+ 0deg,
+ rgba(0, 255, 255, 0.025) 0px,
+ rgba(0, 255, 255, 0.025) 1px,
+ transparent 1px,
+ transparent 3px
+ );
+ mix-blend-mode: screen;
+ animation: crt-flicker 4.2s steps(60) infinite;
+}
+
+@keyframes crt-flicker {
+ 0%, 100% { opacity: 0.85; }
+ 47% { opacity: 0.92; }
+ 50% { opacity: 0.6; }
+ 53% { opacity: 0.9; }
+}
+
+@keyframes blink-caret {
+ 0%, 49% { opacity: 1; }
+ 50%, 100% { opacity: 0; }
+}
+
+.cypher-caret {
+ display: inline-block;
+ width: 0.55em;
+ margin-left: 2px;
+ background: var(--pixel-cyan);
+ color: transparent;
+ animation: blink-caret 1s steps(1) infinite;
+}
diff --git a/src/apps/AboutMe.tsx b/src/apps/AboutMe.tsx
index e1a769f..608e7c4 100644
--- a/src/apps/AboutMe.tsx
+++ b/src/apps/AboutMe.tsx
@@ -1,35 +1,177 @@
export default function AboutMe() {
return (
-
-
-
👤
+
+
+ mingda@doitou-sv:~$
+ whoami█
+
+
+
+ 👤
+
-
Mingda Xie 解明达
-
Backend Developer · CS Master's @ Northeastern University
+
+ Mingda Xie{" "}
+
+ 解明达
+
+
+
+ Backend Developer · CS Master's @ Northeastern University
+
-
- {['Java/Spring Cloud', 'Go', 'TypeScript/Bun', 'Redis', 'k3s', 'Microservices'].map(tag => (
-
{tag}
+
+ {[
+ "Java/Spring Cloud",
+ "Go",
+ "TypeScript/Bun",
+ "Redis",
+ "k3s",
+ "Microservices",
+ ].map((tag) => (
+
+ [{tag}]
+
))}
-
- Backend engineer who runs a personal k3s cluster on Vultr, ships microservices, and builds things that occasionally work in production.
- Pixel art enjoyer. Stardew Valley veteran. Huge fan of 土屋アンナ.
+
+ //
+ Backend engineer. Distributed systems, infra, and the long tail of
+ things that go wrong at 3am. Trust the math, not the institution.
-
+ {/*
"I am the dragon scroll, bitch." — Genuine mastery over shortcuts.
-
+ */}
+
+
+
{`-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA512
+
+identity: Mingda Xie
+
+email: xiemingda2020@gmail.com
+pgp: 8C50 AD5D CE0E 949E 39FA F19D 609C 704E 544A 0BFD
+-----BEGIN PGP SIGNATURE-----
+
+iJEEARYKADkWIQSNntaQmSEMuam1aJhRySHSOW66vwUCafyKpRsUgAAAAAAEAA5t
+YW51MiwyLjUrMS4xMiwwLDMACgkQUckh0jluur8brQD/RzlXBuVuQnGioNsibNDm
+Rm85/5ed2BROS5gaH4FCBrQBALjMapIlyCYBNvIZNQX1eFCY/WNfZQXe3woeZYlP
+E/IF
+=k1lO
+-----END PGP SIGNATURE-----`}
+
);
}
diff --git a/src/apps/Projects.tsx b/src/apps/Projects.tsx
index 57bb691..bf08a6b 100644
--- a/src/apps/Projects.tsx
+++ b/src/apps/Projects.tsx
@@ -1,7 +1,6 @@
const PROJECTS = [
{ name: 'CampusNest', desc: 'Campus housing platform with Spring Cloud microservices, Redis caching, and k3s deployment.', tags: ['Java', 'Spring Cloud', 'Redis', 'k3s'], color: '#0a84ff' },
{ name: 'Alfred Bot', desc: 'Personal AI agent — task management, reminders, home automation bridge.', tags: ['Go', 'TypeScript', 'Bun'], color: '#bf5af2' },
- { name: 'Warden / Bastion', desc: 'Self-hosted auth and proxy layer running on k3s with Let\'s Encrypt.', tags: ['Nginx', 'k3s', 'VLESS'], color: '#30d158' },
{ name: 'Collab Platform', desc: 'Real-time collaborative workspace with WebSocket rooms and conflict resolution.', tags: ['TypeScript', 'Bun', 'WebSocket'], color: '#ffd60a' },
{ name: '成语填空', desc: 'Daily Chinese idiom guessing game — Wordle-style, runs at chengyu.m1ngdaxie.com.', tags: ['React', 'TypeScript'], color: '#ff453a' },
];
diff --git a/src/apps/Terminal.tsx b/src/apps/Terminal.tsx
index fe2e900..ff4782d 100644
--- a/src/apps/Terminal.tsx
+++ b/src/apps/Terminal.tsx
@@ -1,5 +1,5 @@
-import { useState, useRef, useEffect } from 'react';
import type { KeyboardEvent } from 'react';
+import { useEffect, useRef, useState } from 'react';
interface Line { type: 'input' | 'output'; text: string; key: number }
@@ -8,12 +8,34 @@ const COMMANDS: Record
= {
'Available commands:',
' help — show this list',
' about — who is mingda',
+ ' whoami — current identity',
' projects — list projects',
' skills — tech stack',
' contact — contact info',
+ ' pgp — public key fingerprint',
+ ' motd — message of the day',
' neofetch — system info',
' clear — clear terminal',
],
+ whoami: [
+ 'mingda',
+ 'uid=1000 groups=(wheel,crypto,sudoers)',
+ 'shell=/bin/zsh tty=/dev/pts/0',
+ ],
+ pgp: [
+ 'PGP fingerprint:',
+ ' 8C50 AD5D CE0E 949E 39FA F19D 609C 704E 544A 0BFD',
+ '',
+ 'Public key: /pgp.asc',
+ 'Verify before you trust.',
+ ],
+ motd: [
+ 'cypherpunks write code.',
+ 'privacy is necessary for an open society in the electronic age.',
+ 'we cannot expect governments, corporations, or other large,',
+ 'faceless organizations to grant us privacy out of their beneficence.',
+ ' — eric hughes, 1993',
+ ],
about: [
'Mingda Xie (解明达)',
'Backend Developer · CS Master\'s @ Northeastern University',
@@ -28,7 +50,6 @@ const COMMANDS: Record = {
'Projects:',
' CampusNest — Spring Cloud housing platform',
' Alfred Bot — Self-hosted AI agent',
- ' Warden/Bastion — k3s auth + proxy layer',
' Collab Platform— Real-time collaborative workspace',
' 成语填空 — Daily Chinese idiom game',
],
@@ -55,7 +76,7 @@ const COMMANDS: Record = {
' .::!!!::. Kernel: Ubuntu 24.04',
' .:::!!!:::. RAM: 4GB',
' .::::!!!::::. Shell: zsh',
- ' .:::::!!!:::::. Services: Alfred, Chengyu, k3s, VLESS',
+ ' .:::::!!!:::::. Services: Alfred, Chengyu, k3s',
'',
' nginx + k3s + let\'s encrypt',
],
@@ -68,8 +89,9 @@ function nextKey() { return ++lineCounter; }
export default function Terminal() {
const [lines, setLines] = useState([
- { type: 'output', text: 'MingdaOS Terminal v1.0', key: nextKey() },
- { type: 'output', text: 'Type "help" to see available commands.', key: nextKey() },
+ { type: 'output', text: 'MingdaOS Terminal v1.0 [secure tty]', key: nextKey() },
+ { type: 'output', text: 'kernel: linux 6.x | tls: 1.3 | pgp: 8C50AD5D...0BFD', key: nextKey() },
+ { type: 'output', text: 'cypherpunks write code. type "help" for commands.', key: nextKey() },
{ type: 'output', text: '', key: nextKey() },
]);
const [input, setInput] = useState('');
diff --git a/src/components/Desktop/Desktop.tsx b/src/components/Desktop/Desktop.tsx
index e9ccbed..bc69de4 100644
--- a/src/components/Desktop/Desktop.tsx
+++ b/src/components/Desktop/Desktop.tsx
@@ -50,12 +50,19 @@ export default function Desktop() {
gridRow: app.desktopPosition.row + 1,
}}
>
- openWindow(app.id)} />
+
+ app.externalUrl
+ ? window.open(app.externalUrl, '_blank', 'noopener,noreferrer')
+ : openWindow(app.id)
+ }
+ />
))}
- {APPS.map(app => (
+ {APPS.filter(app => !app.externalUrl).map(app => (
))}
diff --git a/src/components/DesktopIcon/DesktopIcon.tsx b/src/components/DesktopIcon/DesktopIcon.tsx
index 8330f8d..33b40fb 100644
--- a/src/components/DesktopIcon/DesktopIcon.tsx
+++ b/src/components/DesktopIcon/DesktopIcon.tsx
@@ -10,7 +10,9 @@ export default function DesktopIcon({ app, onOpen }: Props) {
return (
-
{app.emoji}
+ {app.iconImage
+ ?

+ :
{app.emoji}}
{app.title}
diff --git a/src/components/MenuBar/MenuBar.tsx b/src/components/MenuBar/MenuBar.tsx
index aa3c37f..fa7e9e4 100644
--- a/src/components/MenuBar/MenuBar.tsx
+++ b/src/components/MenuBar/MenuBar.tsx
@@ -1,31 +1,52 @@
import { useEffect, useState } from 'react';
import './MenuBar.css';
-const MENU_ITEMS = ['File', 'Edit', 'View', 'Go', 'Help'];
+const MENU_ITEMS = ['encrypt', 'sign', 'verify', 'wipe', 'about'];
+const BOOT_AT = Date.now();
export default function MenuBar() {
const [time, setTime] = useState(() => formatTime(new Date()));
+ const [uptime, setUptime] = useState(() => formatUptime(0));
useEffect(() => {
- const id = setInterval(() => setTime(formatTime(new Date())), 1000);
+ const id = setInterval(() => {
+ setTime(formatTime(new Date()));
+ setUptime(formatUptime(Date.now() - BOOT_AT));
+ }, 1000);
return () => clearInterval(id);
}, []);
return (
-
+ [ MingdaOS ]
{MENU_ITEMS.map(item => (
{item}
))}
- {time}
+
+ up {uptime}
+
+
+ {time}█
+
);
}
function formatTime(d: Date) {
- return d.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true });
+ const hh = String(d.getUTCHours()).padStart(2, '0');
+ const mm = String(d.getUTCMinutes()).padStart(2, '0');
+ const ss = String(d.getUTCSeconds()).padStart(2, '0');
+ return `${hh}:${mm}:${ss} UTC`;
+}
+
+function formatUptime(ms: number) {
+ const s = Math.floor(ms / 1000);
+ const h = Math.floor(s / 3600);
+ const m = Math.floor((s % 3600) / 60);
+ const sec = s % 60;
+ return `${String(h).padStart(2, '0')}h${String(m).padStart(2, '0')}m${String(sec).padStart(2, '0')}s`;
}
diff --git a/src/config/apps.ts b/src/config/apps.ts
index 680ade4..d30588b 100644
--- a/src/config/apps.ts
+++ b/src/config/apps.ts
@@ -89,6 +89,18 @@ export const APPS: AppConfig[] = [
component: Trash,
desktopPosition: { col: 1, row: 3 },
},
+ {
+ id: 'collab',
+ title: 'Collab',
+ emoji: '',
+ iconGradient: 'linear-gradient(135deg, rgba(255,255,255,0.06), rgba(255,255,255,0.02))',
+ iconImage: '/docnest-icon-32.png',
+ defaultSize: { width: 400, height: 360 },
+ defaultPosition: { x: 0, y: 0 },
+ component: Links,
+ desktopPosition: { col: 0, row: 4 },
+ externalUrl: 'https://collab.m1ngdaxie.com/',
+ },
];
export const WALLPAPERS = [
diff --git a/src/types/index.ts b/src/types/index.ts
index 78044ba..b586db3 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -21,6 +21,8 @@ export interface AppConfig {
defaultPosition: { x: number; y: number };
component: ComponentType;
desktopPosition: { col: number; row: number };
+ externalUrl?: string;
+ iconImage?: string;
}
export interface OSState {