diff --git a/src/apps/AboutMe.tsx b/src/apps/AboutMe.tsx new file mode 100644 index 0000000..542fe0e --- /dev/null +++ b/src/apps/AboutMe.tsx @@ -0,0 +1,36 @@ +export default function AboutMe() { + return ( +
+
+
👤
+
+

Mingda Xie 解明达

+

Backend Developer · CS Master's @ Northeastern University

+

Shenzhen-bound · Class of 2026

+
+
+
+ {['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. + LeetCode 400+. Pixel art enjoyer. Stardew Valley veteran. Huge fan of 土屋アンナ. +

+

+ "I am the dragon scroll, bitch." — Genuine mastery over shortcuts. +

+
+ ); +} diff --git a/src/apps/Alfred.tsx b/src/apps/Alfred.tsx new file mode 100644 index 0000000..0a52e9e --- /dev/null +++ b/src/apps/Alfred.tsx @@ -0,0 +1,28 @@ +export default function Alfred() { + return ( +
+
+
☠️
+
+

Alfred

+

Personal AI Agent

+
+
+

+ Alfred is my self-hosted AI agent running on k3s. It handles task management, reminders, + Telegram notifications, and acts as a personal ops layer for my homelab stack. +

+
+ {['Task & reminder management', 'Telegram bot interface', 'Home automation bridge', 'LLM-powered command routing'].map(f => ( +
+ {f} +
+ ))} +
+
+ ); +} diff --git a/src/apps/Chengyu.tsx b/src/apps/Chengyu.tsx new file mode 100644 index 0000000..078bd68 --- /dev/null +++ b/src/apps/Chengyu.tsx @@ -0,0 +1,32 @@ +export default function Chengyu() { + return ( +
+
🀄
+

成语填空

+

Chinese Idiom Guessing Game

+

Wordle-style · Daily puzzle · 4-character idioms

+

+ Guess the 4-character Chinese idiom (成语) in 6 tries. + Correct characters turn green, misplaced turn yellow. + A new puzzle drops every day at midnight. +

+ + Launch Game → + +
+ ); +} diff --git a/src/apps/Links.tsx b/src/apps/Links.tsx new file mode 100644 index 0000000..0a20638 --- /dev/null +++ b/src/apps/Links.tsx @@ -0,0 +1,41 @@ +const LINKS = [ + { label: 'GitHub', sub: 'github.com/m1ngdaxie', emoji: '🐙', url: 'https://github.com/m1ngdaxie', color: '#30d158' }, + { label: 'Homepage', sub: 'm1ngdaxie.com', emoji: '🌐', url: 'https://m1ngdaxie.com', color: '#0a84ff' }, + { label: '成语填空', sub: 'chengyu.m1ngdaxie.com', emoji: '🀄', url: 'https://chengyu.m1ngdaxie.com', color: '#ff453a' }, + { label: 'Email', sub: 'mingda@m1ngdaxie.com', emoji: '✉️', url: 'mailto:mingda@m1ngdaxie.com', color: '#bf5af2' }, +]; + +export default function Links() { + return ( +
+ {LINKS.map(l => ( + (e.currentTarget.style.background = 'rgba(255,255,255,0.08)')} + onMouseLeave={e => (e.currentTarget.style.background = 'rgba(255,255,255,0.04)')} + > +
{l.emoji}
+
+
{l.label}
+
{l.sub}
+
+
+
+ ))} +
+ ); +} diff --git a/src/apps/Poker.tsx b/src/apps/Poker.tsx new file mode 100644 index 0000000..a15f00d --- /dev/null +++ b/src/apps/Poker.tsx @@ -0,0 +1,16 @@ +export default function Poker() { + return ( +
+
🎲
+

Poker

+
🚧 Under Construction
+

+ A Texas Hold'em practice tool is in the works.
Check back later. +

+
+ ); +} diff --git a/src/apps/Projects.tsx b/src/apps/Projects.tsx new file mode 100644 index 0000000..57bb691 --- /dev/null +++ b/src/apps/Projects.tsx @@ -0,0 +1,38 @@ +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' }, +]; + +export default function Projects() { + return ( +
+ {PROJECTS.map(p => ( +
+
+
+ {p.name} +
+

{p.desc}

+
+ {p.tags.map(t => ( + {t} + ))} +
+
+ ))} +
+ ); +} diff --git a/src/apps/Terminal.tsx b/src/apps/Terminal.tsx new file mode 100644 index 0000000..6593421 --- /dev/null +++ b/src/apps/Terminal.tsx @@ -0,0 +1,178 @@ +import { useState, useRef, useEffect } from 'react'; +import type { KeyboardEvent } from 'react'; + +interface Line { type: 'input' | 'output'; text: string; key: number } + +const COMMANDS: Record = { + help: [ + 'Available commands:', + ' help — show this list', + ' about — who is mingda', + ' projects — list projects', + ' skills — tech stack', + ' contact — contact info', + ' neofetch — system info', + ' clear — clear terminal', + ], + about: [ + 'Mingda Xie (解明达)', + 'Backend Developer · CS Master\'s @ Northeastern University', + 'Shenzhen-bound · Class of 2026', + '', + 'Runs a personal k3s cluster. Writes Go and Java at 2am.', + 'LeetCode 400+. Pixel art. Stardew Valley. 土屋アンナ.', + '', + '"I am the dragon scroll, bitch."', + ], + projects: [ + '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', + ], + skills: [ + 'Tech Stack:', + ' Languages : Java, Go, TypeScript, Python', + ' Frameworks : Spring Cloud, Bun, React', + ' Infra : k3s, Nginx, Redis, Docker', + ' Cloud : Vultr, Let\'s Encrypt', + ' Other : Microservices, WebSocket, LLM APIs', + ], + contact: [ + 'Contact:', + ' GitHub : github.com/m1ngdaxie', + ' Web : m1ngdaxie.com', + ' Game : chengyu.m1ngdaxie.com', + ' Email : mingda@m1ngdaxie.com', + ], + neofetch: [ + ' . mingda@doitou-sv', + ' .:. ----------------', + ' .:!:. OS: MingdaOS 1.0', + ' .:!!!:. Host: Vultr VPS', + ' .::!!!::. Kernel: Ubuntu 24.04', + ' .:::!!!:::. RAM: 4GB', + ' .::::!!!::::. Shell: zsh', + ' .:::::!!!:::::. Services: Alfred, Chengyu, k3s, VLESS', + '', + ' nginx + k3s + let\'s encrypt', + ], +}; + +const PROMPT = 'mingda@doitou-sv ~ % '; + +let lineCounter = 0; // module-level counter for stable unique keys +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: '', key: nextKey() }, + ]); + const [input, setInput] = useState(''); + const [history, setHistory] = useState([]); + const [histIdx, setHistIdx] = useState(-1); + const bottomRef = useRef(null); + const inputRef = useRef(null); + + useEffect(() => { + bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [lines]); + + useEffect(() => { + inputRef.current?.focus(); + }, []); + + function handleSubmit() { + const cmd = input.trim().toLowerCase(); + const newLines: Line[] = [...lines, { type: 'input', text: PROMPT + input, key: nextKey() }]; + + if (cmd === '') { + setLines([...newLines, { type: 'output', text: '', key: nextKey() }]); + } else if (cmd === 'clear') { + setLines([]); + } else if (COMMANDS[cmd]) { + COMMANDS[cmd].forEach(l => newLines.push({ type: 'output', text: l, key: nextKey() })); + newLines.push({ type: 'output', text: '', key: nextKey() }); + setLines(newLines); + } else { + newLines.push({ type: 'output', text: `zsh: command not found: ${cmd}`, key: nextKey() }); + newLines.push({ type: 'output', text: '', key: nextKey() }); + setLines(newLines); + } + + if (cmd) setHistory(h => [cmd, ...h].slice(0, 50)); + setHistIdx(-1); + setInput(''); + } + + function handleKeyDown(e: KeyboardEvent) { + if (e.key === 'Enter') { + handleSubmit(); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + const next = Math.min(histIdx + 1, history.length - 1); + setHistIdx(next); + if (history[next] !== undefined) setInput(history[next]); + } else if (e.key === 'ArrowDown') { + e.preventDefault(); + const next = Math.max(histIdx - 1, -1); + setHistIdx(next); + setInput(next === -1 ? '' : history[next]); + } + } + + return ( +
inputRef.current?.focus()} + > +
+ {lines.map((line) => ( +
+ {line.text} +
+ ))} +
+
+
+ {PROMPT} + setInput(e.target.value)} + onKeyDown={handleKeyDown} + style={{ + flex: 1, + background: 'transparent', + border: 'none', + outline: 'none', + color: '#e2e8f0', + fontFamily: 'var(--font-mono)', + fontSize: 13, + caretColor: '#64d2ff', + }} + spellCheck={false} + autoComplete="off" + /> +
+
+ ); +} diff --git a/src/apps/Trash.tsx b/src/apps/Trash.tsx new file mode 100644 index 0000000..8b37383 --- /dev/null +++ b/src/apps/Trash.tsx @@ -0,0 +1,12 @@ +export default function Trash() { + return ( +
+
🗑️
+

Trash

+

The trash is empty.

+

+ just like my social life +

+
+ ); +}