Compare commits

...

17 Commits

Author SHA1 Message Date
M1ngdaXie
c8a6fc2871 feat: add 'go-local-rag-email' project to the Projects list and update terminal commands
All checks were successful
Deploy / deploy (push) Successful in 12s
2026-05-08 09:12:09 +08:00
M1ngdaXie
01a429f8e1 feat: add versioning support for assets and update version to 1.0.0
All checks were successful
Deploy / deploy (push) Successful in 12s
2026-05-07 21:46:53 +08:00
M1ngdaXie
74554210dd feat: enhance user interface with CRT effects, update AboutMe and Projects sections, and add new Collab app
All checks were successful
Deploy / deploy (push) Successful in 14s
2026-05-07 21:42:06 +08:00
M1ngdaXie
38bc80c566 feat(wallpaper): add pixel city wallpaper to the wallpaper collection
All checks were successful
Deploy / deploy (push) Successful in 12s
2026-03-29 17:03:50 -07:00
M1ngdaXie
c0dfdb1e54 fix(deploy): ensure target directory is created before deployment
All checks were successful
Deploy / deploy (push) Successful in 15s
2026-03-26 23:11:15 -07:00
M1ngdaXie
326c4b7a67 refactor(deploy): streamline deployment workflow by consolidating steps
Some checks failed
Deploy / deploy (push) Failing after 19s
2026-03-26 23:00:24 -07:00
M1ngdaXie
dddbc26725 fix(deploy): update branch name from 'main' to 'master' in deployment workflow
Some checks failed
Deploy / deploy (push) Has been cancelled
2026-03-26 22:46:17 -07:00
M1ngdaXie
e5251cd0c7 feat(deploy): add GitHub Actions workflow for deployment to server 2026-03-26 22:32:37 -07:00
M1ngdaXie
c603dc6745 feat(pixel): update font styles to use 'Inter' for UI and adjust .gitignore 2026-03-26 15:54:05 -07:00
M1ngdaXie
91378c9704 feat(pixel): replace smooth gradient wallpapers with pixel-style CSS patterns 2026-03-26 15:22:56 -07:00
M1ngdaXie
f432ed3538 feat(pixel): cyan boot screen and terminal prompt color 2026-03-26 15:22:29 -07:00
M1ngdaXie
a2cde36f6b feat(pixel): restyle desktop icons and context menu 2026-03-26 15:20:20 -07:00
M1ngdaXie
45891cec8b feat(pixel): restyle dock — sharp corners, hard shadow, cyan dot, steps() hover 2026-03-26 15:20:05 -07:00
M1ngdaXie
50cb8d50ac feat(pixel): restyle window — sharp corners, opaque bg, cyan title, hard shadow 2026-03-26 15:18:48 -07:00
M1ngdaXie
f70c258d98 feat(pixel): restyle menubar — opaque bg, cyan text, pixel border 2026-03-26 15:18:21 -07:00
M1ngdaXie
c374acc223 feat(pixel): swap fonts to Press Start 2P and overhaul CSS variables 2026-03-26 15:16:51 -07:00
M1ngdaXie
8b6cb0e039 docs: add pixel hybrid reskin spec and implementation plan 2026-03-26 15:13:39 -07:00
26 changed files with 1243 additions and 121 deletions

View File

@@ -0,0 +1,22 @@
name: Deploy
on:
push:
branches:
- master
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install & Build
run: npm ci && npm run build
- name: Deploy
run: |
mkdir -p /var/www/os.m1ngdaxie.com
rm -rf /var/www/os.m1ngdaxie.com/*
cp -r dist/* /var/www/os.m1ngdaxie.com/

3
.gitignore vendored
View File

@@ -22,3 +22,6 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
.superpowers
docs

View File

@@ -0,0 +1,662 @@
# MingdaOS Pixel Hybrid Reskin Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Restyle MingdaOS from glassmorphism macOS to a pixel hybrid aesthetic — Press Start 2P font, cyan `#00ffff` accent, sharp corners, hard shadows, opaque surfaces, no backdrop-filter blur.
**Architecture:** Pure CSS/style changes across 10 files. No logic, no new components, no new dependencies beyond adding Press Start 2P to Google Fonts. Each task is one file or one tightly related set of changes.
**Tech Stack:** CSS variables, Google Fonts (Press Start 2P), existing React + Vite stack
---
## File Map
```
Modified only — no new files:
├── index.html # Swap Google Fonts link
├── src/
│ ├── App.css # CSS variables overhaul
│ ├── config/apps.ts # WALLPAPERS → pixel CSS patterns
│ ├── components/Desktop/Desktop.css # background-size for dot grid wallpaper
│ ├── apps/Terminal.tsx # Prompt color #64d2ff → #00ffff
│ └── components/
│ ├── BootScreen/BootScreen.css # Cyan progress bar
│ ├── MenuBar/MenuBar.css # Opaque bg, cyan text, no blur
│ ├── Window/Window.css # Sharp corners, hard shadow, opaque
│ ├── Dock/Dock.css # Sharp corners, hard shadow, steps() hover
│ ├── DesktopIcon/DesktopIcon.css # Sharp corners, cyan label
│ └── ContextMenu/ContextMenu.css # Sharp corners, cyan hover, no blur
```
---
## Task 1: Google Fonts + CSS variables
**Files:**
- Modify: `index.html`
- Modify: `src/App.css`
- [ ] **Step 1: Update Google Fonts in `index.html`**
Find the existing fonts link (lines 8-10) and replace with:
```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Fira+Code:wght@400;500&display=swap" rel="stylesheet">
```
(Removed Outfit, added Press Start 2P, kept Fira Code)
- [ ] **Step 2: Overhaul CSS variables in `src/App.css`**
Replace the entire file with:
```css
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--font-ui: 'Press Start 2P', monospace;
--font-mono: 'Fira Code', 'Courier New', monospace;
--accent-red: #ff453a;
--accent-yellow: #ffd60a;
--accent-green: #30d158;
--accent-blue: #0a84ff;
--accent-purple: #bf5af2;
--accent-teal: #00ffff;
--accent-cyan: #00ffff;
--glass-bg: #0d0d1a;
--glass-border: #333333;
--glass-blur: none;
--glass-shadow: 4px 4px 0 #000000;
--radius-window: 0px;
--radius-icon: 0px;
--menubar-height: 28px;
--dock-height: 72px;
--titlebar-height: 36px;
--pixel-border: 2px solid #333333;
--pixel-shadow: 4px 4px 0 #000000;
--pixel-cyan: #00ffff;
}
html, body, #root {
width: 100%;
height: 100%;
overflow: hidden;
font-family: var(--font-ui);
-webkit-font-smoothing: none;
font-smooth: never;
image-rendering: pixelated;
}
```
- [ ] **Step 3: Verify build**
```bash
cd /Users/xiemingda/mingda-os && npm run build 2>&1 | tail -5
```
Expected: `✓ built` with no errors.
- [ ] **Step 4: Commit**
```bash
git add index.html src/App.css
git commit -m "feat(pixel): swap fonts to Press Start 2P and overhaul CSS variables"
```
---
## Task 2: MenuBar styles
**Files:**
- Modify: `src/components/MenuBar/MenuBar.css`
- [ ] **Step 1: Replace `MenuBar.css` entirely**
```css
.menubar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: var(--menubar-height);
background: #080816;
border-bottom: 2px solid rgba(0, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 12px;
z-index: 1000;
user-select: none;
}
.menubar-left {
display: flex;
align-items: center;
gap: 4px;
}
.menubar-logo {
font-size: 14px;
margin-right: 8px;
cursor: default;
color: var(--pixel-cyan);
}
.menubar-item {
font-size: 8px;
font-weight: 400;
color: var(--pixel-cyan);
padding: 2px 8px;
cursor: default;
letter-spacing: 0.5px;
}
.menubar-item:hover {
background: rgba(0, 255, 255, 0.15);
}
.menubar-right {
display: flex;
align-items: center;
}
.menubar-clock {
font-size: 8px;
color: var(--pixel-cyan);
letter-spacing: 0.5px;
}
```
- [ ] **Step 2: Verify build**
```bash
npm run build 2>&1 | tail -5
```
- [ ] **Step 3: Commit**
```bash
git add src/components/MenuBar/MenuBar.css
git commit -m "feat(pixel): restyle menubar — opaque bg, cyan text, pixel border"
```
---
## Task 3: Window styles
**Files:**
- Modify: `src/components/Window/Window.css`
- [ ] **Step 1: Replace `Window.css` entirely**
```css
.window {
background: #0d0d1a;
border: 2px solid #333333;
border-radius: 0;
box-shadow: var(--pixel-shadow);
display: flex;
flex-direction: column;
overflow: hidden;
transition: opacity 0.15s;
}
.window--focused {
border-color: rgba(0, 255, 255, 0.4);
}
.window--unfocused {
opacity: 0.72;
}
.window-titlebar {
height: var(--titlebar-height);
background: #111122;
display: flex;
align-items: center;
padding: 0 12px;
cursor: move;
border-bottom: 2px solid #333333;
flex-shrink: 0;
gap: 8px;
position: relative;
}
.traffic-lights {
display: flex;
gap: 6px;
flex-shrink: 0;
}
.traffic-light {
width: 12px;
height: 12px;
border-radius: 0;
border: 1px solid rgba(0,0,0,0.5);
cursor: pointer;
position: relative;
transition: filter 0.1s;
}
.window--unfocused .traffic-light {
background: #444 !important;
}
.traffic-light--close { background: var(--accent-red); }
.traffic-light--minimize { background: var(--accent-yellow); }
.traffic-light--maximize { background: var(--accent-green); }
.traffic-lights:hover .traffic-light--close::after { content: '✕'; }
.traffic-lights:hover .traffic-light--minimize::after { content: ''; }
.traffic-lights:hover .traffic-light--maximize::after { content: '+'; }
.traffic-light::after {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 8px;
font-weight: 700;
color: rgba(0,0,0,0.7);
line-height: 1;
}
.window-title {
flex: 1;
text-align: center;
font-size: 9px;
font-weight: 400;
color: var(--pixel-cyan);
pointer-events: none;
position: absolute;
left: 50%;
transform: translateX(-50%);
letter-spacing: 1px;
white-space: nowrap;
}
.window-content {
flex: 1;
overflow: auto;
color: #fff;
}
```
- [ ] **Step 2: Verify build**
```bash
npm run build 2>&1 | tail -5
```
- [ ] **Step 3: Commit**
```bash
git add src/components/Window/Window.css
git commit -m "feat(pixel): restyle window — sharp corners, opaque bg, cyan title, hard shadow"
```
---
## Task 4: Dock styles
**Files:**
- Modify: `src/components/Dock/Dock.css`
- [ ] **Step 1: Replace `Dock.css` entirely**
```css
.dock-container {
position: fixed;
bottom: 8px;
left: 50%;
transform: translateX(-50%);
z-index: 500;
}
.dock {
display: flex;
align-items: flex-end;
gap: 6px;
background: #0d0d1a;
border: 2px solid #333333;
border-radius: 0;
padding: 8px 12px;
box-shadow: 3px 3px 0 #000000;
}
.dock-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 3px;
cursor: pointer;
position: relative;
}
.dock-item:hover .dock-icon {
transform: scale(1.18) translateY(-6px);
}
.dock-icon {
width: 48px;
height: 48px;
border-radius: 0;
border: 2px solid #000;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
transition: transform 0.1s steps(2);
box-shadow: 2px 2px 0 #000;
}
.dock-dot {
width: 4px;
height: 4px;
border-radius: 0;
background: var(--pixel-cyan);
}
```
- [ ] **Step 2: Verify build**
```bash
npm run build 2>&1 | tail -5
```
- [ ] **Step 3: Commit**
```bash
git add src/components/Dock/Dock.css
git commit -m "feat(pixel): restyle dock — sharp corners, hard shadow, cyan dot, steps() hover"
```
---
## Task 5: DesktopIcon + ContextMenu styles
**Files:**
- Modify: `src/components/DesktopIcon/DesktopIcon.css`
- Modify: `src/components/ContextMenu/ContextMenu.css`
- [ ] **Step 1: Replace `DesktopIcon.css` entirely**
```css
.desktop-icon {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
cursor: default;
width: 72px;
padding: 6px;
border-radius: 0;
transition: background 0.1s;
}
.desktop-icon:hover {
background: rgba(0, 255, 255, 0.08);
}
.desktop-icon-img {
width: 56px;
height: 56px;
border-radius: 0;
border: 2px solid #000;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
box-shadow: 3px 3px 0 #000;
}
.desktop-icon-label {
font-size: 8px;
color: var(--pixel-cyan);
text-align: center;
text-shadow: 1px 1px 0 #000;
max-width: 72px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
letter-spacing: 0.5px;
}
```
- [ ] **Step 2: Replace `ContextMenu.css` entirely**
```css
.context-menu {
position: fixed;
background: #0d0d1a;
border: 2px solid #333333;
border-radius: 0;
box-shadow: 3px 3px 0 #000000;
padding: 4px;
min-width: 200px;
z-index: 9000;
}
.context-menu-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 10px;
border-radius: 0;
font-size: 8px;
color: rgba(255, 255, 255, 0.9);
cursor: default;
letter-spacing: 0.5px;
}
.context-menu-item:hover {
background: var(--pixel-cyan);
color: #000;
}
.context-menu-separator {
height: 2px;
background: #333333;
margin: 3px 6px;
}
```
- [ ] **Step 3: Verify build**
```bash
npm run build 2>&1 | tail -5
```
- [ ] **Step 4: Commit**
```bash
git add src/components/DesktopIcon/DesktopIcon.css src/components/ContextMenu/ContextMenu.css
git commit -m "feat(pixel): restyle desktop icons and context menu"
```
---
## Task 6: BootScreen styles + Terminal color
**Files:**
- Modify: `src/components/BootScreen/BootScreen.css`
- Modify: `src/apps/Terminal.tsx`
- [ ] **Step 1: Replace `BootScreen.css` entirely**
```css
.boot-screen {
position: fixed;
inset: 0;
background: #000;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 48px;
z-index: 9999;
}
.boot-logo {
display: flex;
align-items: center;
justify-content: center;
}
.boot-apple {
width: 80px;
height: 80px;
fill: #00ffff;
image-rendering: pixelated;
}
.boot-progress-track {
width: 200px;
height: 4px;
background: rgba(0, 255, 255, 0.15);
border: 1px solid rgba(0, 255, 255, 0.3);
overflow: hidden;
}
.boot-progress-bar {
height: 100%;
background: #00ffff;
transition: width 0.05s linear;
box-shadow: 0 0 8px #00ffff;
}
```
- [ ] **Step 2: Update Terminal prompt color**
In `src/apps/Terminal.tsx`, find the two places where `#64d2ff` is used for the prompt color and update them to `#00ffff`:
Find:
```tsx
<span style={{ color: '#64d2ff', whiteSpace: 'nowrap' }}>{PROMPT}</span>
```
Replace with:
```tsx
<span style={{ color: '#00ffff', whiteSpace: 'nowrap' }}>{PROMPT}</span>
```
Find:
```tsx
caretColor: '#64d2ff',
```
Replace with:
```tsx
caretColor: '#00ffff',
```
Also update the input color line styling in the `lines.map`:
Find:
```tsx
color: line.type === 'input' ? '#64d2ff' : '#e2e8f0',
```
Replace with:
```tsx
color: line.type === 'input' ? '#00ffff' : '#e2e8f0',
```
- [ ] **Step 3: Verify build**
```bash
npm run build 2>&1 | tail -5
```
- [ ] **Step 4: Commit**
```bash
git add src/components/BootScreen/BootScreen.css src/apps/Terminal.tsx
git commit -m "feat(pixel): cyan boot screen and terminal prompt color"
```
---
## Task 7: Pixel wallpapers
**Files:**
- Modify: `src/config/apps.ts`
- [ ] **Step 1: Replace the WALLPAPERS array in `src/config/apps.ts`**
Find the `export const WALLPAPERS` array (near the bottom of the file) and replace it entirely with:
```typescript
export const WALLPAPERS = [
// Dot grid — dark with subtle cyan dot pattern
`radial-gradient(circle, rgba(0,255,255,0.07) 1px, transparent 1px),
radial-gradient(circle, rgba(0,255,255,0.04) 1px, transparent 1px),
linear-gradient(135deg, #050510 0%, #080818 100%)`,
// Scanlines — horizontal line texture
`repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0,255,255,0.03) 2px,
rgba(0,255,255,0.03) 4px
),
linear-gradient(135deg, #050510 0%, #06060f 100%)`,
// Solid dark — flat and minimal
`linear-gradient(135deg, #050510 0%, #080818 100%)`,
];
```
Also update the Desktop.tsx background-size to make the dot grid work. Find `src/components/Desktop/Desktop.css` and update `.desktop`:
```css
.desktop {
position: fixed;
inset: 0;
background-size: 24px 24px, 12px 12px, cover;
overflow: hidden;
}
```
- [ ] **Step 2: Verify build**
```bash
npm run build 2>&1 | tail -5
```
- [ ] **Step 3: Commit**
```bash
git add src/config/apps.ts src/components/Desktop/Desktop.css
git commit -m "feat(pixel): replace smooth gradient wallpapers with pixel-style CSS patterns"
```
---
## Task 8: Final verification
- [ ] **Step 1: Full production build**
```bash
npm run build
```
Expected: no errors, `dist/` updated.
- [ ] **Step 2: Run dev server and manually verify**
```bash
npm run dev
```
Open http://localhost:5173 and check:
- Boot screen: cyan Apple logo + cyan progress bar fills
- Desktop: dot grid or scanline wallpaper visible
- MenuBar: cyan text, opaque dark background, no glass blur
- Desktop icons: sharp corners, cyan labels, hard shadows
- Double-click icon → window opens with sharp corners, cyan title, `#111122` titlebar
- Traffic lights: square (not round), red/yellow/green still colored
- Focused window has subtle cyan border, unfocused dims to 72%
- Right-click → context menu: sharp corners, cyan hover
- Dock: square icons, hard shadow, cyan dot under open apps, snap hover animation
- Terminal: `help`, `about`, `neofetch` — cyan prompt `mingda@doitou-sv ~ %`
- Press Start 2P font visible throughout UI
- [ ] **Step 3: Final commit**
```bash
git add -A
git commit -m "chore: pixel hybrid reskin complete"
```
---
## Verification Checklist
- [ ] Press Start 2P font loaded and rendering (check browser DevTools Network tab)
- [ ] No rounded corners anywhere on windows, dock, icons, context menu
- [ ] No backdrop-filter blur on any element
- [ ] All chrome backgrounds are opaque (no rgba semi-transparent blur)
- [ ] Hard `4px 4px 0 #000` shadows on windows and dock
- [ ] Cyan `#00ffff` on: menubar text, window titles, dock dots, desktop icon labels, terminal prompt, boot progress bar, boot Apple logo
- [ ] Wallpaper is a pixel-style CSS pattern (dot grid / scanlines / solid)
- [ ] `npm run build` clean with no errors

View File

@@ -0,0 +1,119 @@
# MingdaOS Pixel Hybrid Reskin — Design Spec
## Goal
Restyle MingdaOS from a smooth glassmorphism macOS aesthetic to a pixel hybrid aesthetic: retro game OS vibes (Celeste/Shovel Knight), sharp edges, flat opaque surfaces, hard pixel shadows, Press Start 2P font, cyan accent color. No new functionality — visual layer changes only.
## Design Decisions
| Decision | Choice | Rationale |
|---|---|---|
| Font | Press Start 2P | Bold, chunky, unmistakably pixel |
| Accent color | `#00ffff` (cyan) | Cyberpunk/sci-fi energy, high contrast on dark |
| Style level | Pixel Hybrid (B) | Keep glassmorphism window system, replace chrome styling |
| Border radius | 0 everywhere | Sharp corners = pixel aesthetic |
| Backdrop filter | Removed | Glassmorphism blur is anti-pixel |
| Shadows | Hard offset `4px 4px 0 #000` | Pixel-style depth instead of soft glow |
## Typography
- **Replace** `Outfit` with `Press Start 2P` for all UI chrome
- **Keep** `Fira Code` for Terminal app only
- **Remove** `-webkit-font-smoothing: antialiased` — pixel fonts render crisp, not smoothed
- Font sizes: 8px labels, 10px titles (Press Start 2P reads large)
- Update Google Fonts link in `index.html`
## Color System
| Variable | Old | New |
|---|---|---|
| `--accent-teal` / primary accent | `#64d2ff` | `#00ffff` |
| `--glass-bg` | `rgba(30,30,40,0.55)` | `#0d0d1a` (opaque) |
| `--glass-border` | `rgba(255,255,255,0.08)` | `#333333` |
| `--glass-blur` | `blur(24px) saturate(1.4)` | `none` |
| `--glass-shadow` | `0 8px 40px rgba(0,0,0,0.35)` | `4px 4px 0 #000000` |
| Window bg | `rgba(30,30,40,0.55)` | `#0d0d1a` |
| Titlebar bg | (same as window) | `#111122` |
| Menubar bg | `rgba(20,20,28,0.72)` | `#080816` |
| Dock bg | `rgba(30,30,40,0.52)` | `#0d0d1a` |
## Components
### MenuBar
- Background: `#080816` opaque, no backdrop-filter
- Border-bottom: `2px solid rgba(0,255,255,0.2)`
- Text (logo + items + clock): `#00ffff`
- Font: Press Start 2P, 8px
### Window
- Background: `#0d0d1a`, no backdrop-filter
- Titlebar: `#111122` background
- Border: `2px solid #333`
- Focused border: `2px solid rgba(0,255,255,0.4)`
- Shadow: `4px 4px 0 #000`
- Border-radius: `0`
- Title text: `#00ffff`, Press Start 2P 9px
- Traffic lights: keep as colored squares (not circles — `border-radius: 0`)
### Dock
- Background: `#0d0d1a`, no backdrop-filter
- Border: `2px solid #333`, `box-shadow: 3px 3px 0 #000`
- Border-radius: `0`
- Icon containers: `border-radius: 0`, `border: 2px solid #000`
- Open indicator dot: `#00ffff`
- Hover: `transition: transform 0.1s steps(2)`
### DesktopIcon
- Icon container: `border-radius: 0`
- Label color: `#00ffff` (was white)
- Hover background: `rgba(0,255,255,0.08)`
### ContextMenu
- Background: `#0d0d1a`, no backdrop-filter
- Border: `2px solid #333`, `box-shadow: 3px 3px 0 #000`
- Border-radius: `0`
- Hover: `#00ffff` background, `#000` text
- Font: Press Start 2P 9px
### BootScreen
- Keep existing structure (black bg, Apple logo, progress bar)
- Progress bar: `#00ffff` fill (was white)
- Font (if any text added): Press Start 2P
### Terminal
- Prompt color: `#00ffff` (already `#64d2ff`, update to `#00ffff`)
- Keep Fira Code font
## Wallpaper
Replace smooth radial gradient wallpapers with 3 CSS pixel-style variants:
1. **Dot grid** — dark base `#050510` with a subtle repeating dot pattern
2. **Scanlines** — dark base with horizontal line texture
3. **Solid dark** — flat `#050510` (clean, minimal)
## Animations
- Window open/close: change framer-motion `ease: 'easeOut'``ease: [1,0,1,0]` (step-like) or keep easeOut (subtle)
- Dock hover: `transition: transform 0.1s steps(2)`
- Boot progress: keep existing linear fill
## Files Changed
| File | Change |
|---|---|
| `index.html` | Swap Google Fonts: remove Outfit, add Press Start 2P |
| `src/App.css` | Update all CSS variables, remove font-smoothing, add pixel rendering |
| `src/components/MenuBar/MenuBar.css` | Opaque bg, cyan text, no backdrop-filter |
| `src/components/Window/Window.css` | Sharp corners, opaque bg, hard shadow, pixel titlebar |
| `src/components/Dock/Dock.css` | Sharp corners, opaque bg, hard shadow, steps() hover |
| `src/components/DesktopIcon/DesktopIcon.css` | Sharp corners, cyan label |
| `src/components/ContextMenu/ContextMenu.css` | Sharp corners, opaque bg, cyan hover |
| `src/components/BootScreen/BootScreen.css` | Cyan progress bar |
| `src/apps/Terminal.tsx` | Update prompt color to `#00ffff` |
| `src/config/apps.ts` | Update WALLPAPERS to pixel-style CSS patterns |
## Out of Scope
- No sprite/image assets (emoji icons stay as-is for now)
- No new functionality
- No changes to WindowContext, types, or component logic

View File

@@ -2,12 +2,14 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg?v=1.0.0" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="version" content="1.0.0" />
<meta http-equiv="Cache-Control" content="no-cache, must-revalidate" />
<title>MingdaOS</title> <title>MingdaOS</title>
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600&family=Fira+Code:wght@400;500&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Fira+Code:wght@400;500&display=swap" rel="stylesheet">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

@@ -1,7 +1,7 @@
{ {
"name": "mingda-os", "name": "mingda-os",
"private": true, "private": true,
"version": "0.0.0", "version": "1.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

BIN
public/docnest-icon-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 B

26
public/pgp.asc Normal file
View File

@@ -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-----

BIN
public/wallpaper-city.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 749 KiB

View File

@@ -1,7 +1,7 @@
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root { :root {
--font-ui: 'Outfit', system-ui, sans-serif; --font-ui: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--font-mono: 'Fira Code', 'Courier New', monospace; --font-mono: 'Fira Code', 'Courier New', monospace;
--accent-red: #ff453a; --accent-red: #ff453a;
@@ -9,18 +9,23 @@
--accent-green: #30d158; --accent-green: #30d158;
--accent-blue: #0a84ff; --accent-blue: #0a84ff;
--accent-purple: #bf5af2; --accent-purple: #bf5af2;
--accent-teal: #64d2ff; --accent-teal: #00ffff;
--accent-cyan: #00ffff;
--glass-bg: rgba(30, 30, 40, 0.55); --glass-bg: #0d0d1a;
--glass-border: rgba(255, 255, 255, 0.08); --glass-border: #333333;
--glass-blur: blur(24px) saturate(1.4); --glass-blur: none;
--glass-shadow: 0 8px 40px rgba(0, 0, 0, 0.35); --glass-shadow: 4px 4px 0 #000000;
--radius-window: 12px; --radius-window: 0px;
--radius-icon: 14px; --radius-icon: 0px;
--menubar-height: 28px; --menubar-height: 28px;
--dock-height: 72px; --dock-height: 72px;
--titlebar-height: 36px; --titlebar-height: 36px;
--pixel-border: 2px solid #333333;
--pixel-shadow: 4px 4px 0 #000000;
--pixel-cyan: #00ffff;
} }
html, body, #root { html, body, #root {
@@ -28,5 +33,47 @@ html, body, #root {
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
font-family: var(--font-ui); font-family: var(--font-ui);
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: none;
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;
} }

View File

@@ -1,36 +1,179 @@
import { APP_VERSION } from '../config/version';
export default function AboutMe() { export default function AboutMe() {
return ( return (
<div style={{ padding: '24px', color: '#fff', fontFamily: 'var(--font-ui)' }}> <div
<div style={{ display: 'flex', gap: 20, alignItems: 'flex-start', marginBottom: 20 }}> style={{ padding: "24px", color: "#fff", fontFamily: "var(--font-ui)" }}
<div style={{ >
width: 72, height: 72, borderRadius: '50%', <div
background: 'linear-gradient(135deg, #0a84ff, #bf5af2)', style={{
display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: "var(--font-mono)",
fontSize: 36, flexShrink: 0 fontSize: 12,
}}>👤</div> color: "var(--pixel-cyan)",
marginBottom: 16,
letterSpacing: 0.5,
}}
>
<span style={{ opacity: 0.5 }}>mingda@doitou-sv:~$ </span>
whoami<span className="cypher-caret"></span>
</div>
<div
style={{
display: "flex",
gap: 20,
alignItems: "flex-start",
marginBottom: 20,
}}
>
<div
style={{
width: 72,
height: 72,
borderRadius: "50%",
background: "linear-gradient(135deg, #0a84ff, #bf5af2)",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 36,
flexShrink: 0,
}}
>
👤
</div>
<div> <div>
<h2 style={{ fontSize: 20, fontWeight: 600, marginBottom: 4 }}>Mingda Xie <span style={{ color: 'rgba(255,255,255,0.4)', fontWeight: 400, fontSize: 14 }}></span></h2> <h2 style={{ fontSize: 20, fontWeight: 600, marginBottom: 4 }}>
<p style={{ fontSize: 13, color: 'rgba(255,255,255,0.6)', lineHeight: 1.5 }}>Backend Developer · CS Master's @ Northeastern University</p> Mingda Xie{" "}
<p style={{ fontSize: 13, color: 'rgba(255,255,255,0.5)' }}>Shenzhen-bound · Class of 2026</p> <span
style={{
color: "rgba(255,255,255,0.4)",
fontWeight: 400,
fontSize: 14,
}}
>
</span>
</h2>
<p
style={{
fontSize: 13,
color: "rgba(255,255,255,0.6)",
lineHeight: 1.5,
}}
>
Backend Developer · CS Master's @ Northeastern University
</p>
</div> </div>
</div> </div>
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 20 }}> <div
{['Java/Spring Cloud', 'Go', 'TypeScript/Bun', 'Redis', 'k3s', 'Microservices'].map(tag => ( style={{ display: "flex", gap: 8, flexWrap: "wrap", marginBottom: 20 }}
<span key={tag} style={{ >
padding: '3px 10px', borderRadius: 20, {[
background: 'rgba(10, 132, 255, 0.15)', "Java/Spring Cloud",
border: '1px solid rgba(10,132,255,0.3)', "Go",
fontSize: 12, color: '#64d2ff' "TypeScript/Bun",
}}>{tag}</span> "Redis",
"k3s",
"Microservices",
].map((tag) => (
<span
key={tag}
style={{
padding: "2px 8px",
borderRadius: 0,
background: "rgba(0,255,255,0.06)",
border: "1px solid rgba(0,255,255,0.25)",
fontSize: 11,
fontFamily: "var(--font-mono)",
color: "var(--pixel-cyan)",
letterSpacing: 0.3,
}}
>
[{tag}]
</span>
))} ))}
</div> </div>
<p style={{ fontSize: 13, color: 'rgba(255,255,255,0.7)', lineHeight: 1.7, marginBottom: 16 }}> <p
Backend engineer who runs a personal k3s cluster on Vultr, ships microservices, and builds things that occasionally work in production. style={{
LeetCode 400+. Pixel art enjoyer. Stardew Valley veteran. Huge fan of 土屋アンナ. fontSize: 13,
fontFamily: "var(--font-mono)",
color: "rgba(255,255,255,0.7)",
lineHeight: 1.7,
marginBottom: 16,
}}
>
<span style={{ color: "rgba(0,255,255,0.45)" }}>// </span>
Backend engineer. Distributed systems, infra, and the long tail of
things that go wrong at 3am. Trust the math, not the institution.
</p> </p>
<p style={{ fontSize: 12, color: 'rgba(255,255,255,0.3)', fontStyle: 'italic' }}> {/* <p style={{ fontSize: 12, color: 'rgba(255,255,255,0.3)', fontStyle: 'italic' }}>
"I am the dragon scroll, bitch." Genuine mastery over shortcuts. "I am the dragon scroll, bitch." Genuine mastery over shortcuts.
</p> </p> */}
<div
style={{
marginTop: 8,
padding: "12px 14px",
background: "rgba(0,0,0,0.25)",
border: "1px solid rgba(255,255,255,0.08)",
borderRadius: 8,
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 8,
}}
>
<span
style={{
fontSize: 11,
color: "rgba(255,255,255,0.4)",
letterSpacing: 0.5,
textTransform: "uppercase",
}}
>
Signed Identity
</span>
<a
href={`/pgp.asc?v=${APP_VERSION}`}
target="_blank"
rel="noopener noreferrer"
style={{
fontSize: 11,
color: "#64d2ff",
textDecoration: "none",
}}
>
pgp.asc
</a>
</div>
<pre
style={{
margin: 0,
fontFamily: "var(--font-mono)",
fontSize: 11,
lineHeight: 1.5,
color: "rgba(255,255,255,0.65)",
whiteSpace: "pre-wrap",
wordBreak: "break-all",
}}
>{`-----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-----`}</pre>
</div>
</div> </div>
); );
} }

View File

@@ -1,7 +1,7 @@
const PROJECTS = [ 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: '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: '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: 'go-local-rag-email', desc: 'Local-first Gmail assistant: semantic search and AI summaries via RAG, runs offline.', tags: ['Go', 'Qdrant', 'SQLite', 'RAG'], color: '#00ffff' },
{ name: 'Collab Platform', desc: 'Real-time collaborative workspace with WebSocket rooms and conflict resolution.', tags: ['TypeScript', 'Bun', 'WebSocket'], color: '#ffd60a' }, { 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' }, { name: '成语填空', desc: 'Daily Chinese idiom guessing game — Wordle-style, runs at chengyu.m1ngdaxie.com.', tags: ['React', 'TypeScript'], color: '#ff453a' },
]; ];

View File

@@ -1,5 +1,5 @@
import { useState, useRef, useEffect } from 'react';
import type { KeyboardEvent } from 'react'; import type { KeyboardEvent } from 'react';
import { useEffect, useRef, useState } from 'react';
interface Line { type: 'input' | 'output'; text: string; key: number } interface Line { type: 'input' | 'output'; text: string; key: number }
@@ -8,12 +8,34 @@ const COMMANDS: Record<string, string[]> = {
'Available commands:', 'Available commands:',
' help — show this list', ' help — show this list',
' about — who is mingda', ' about — who is mingda',
' whoami — current identity',
' projects — list projects', ' projects — list projects',
' skills — tech stack', ' skills — tech stack',
' contact — contact info', ' contact — contact info',
' pgp — public key fingerprint',
' motd — message of the day',
' neofetch — system info', ' neofetch — system info',
' clear — clear terminal', ' 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: [ about: [
'Mingda Xie (解明达)', 'Mingda Xie (解明达)',
'Backend Developer · CS Master\'s @ Northeastern University', 'Backend Developer · CS Master\'s @ Northeastern University',
@@ -28,7 +50,7 @@ const COMMANDS: Record<string, string[]> = {
'Projects:', 'Projects:',
' CampusNest — Spring Cloud housing platform', ' CampusNest — Spring Cloud housing platform',
' Alfred Bot — Self-hosted AI agent', ' Alfred Bot — Self-hosted AI agent',
' Warden/Bastion — k3s auth + proxy layer', ' rag-email — Local-first Gmail RAG assistant (Go + Qdrant)',
' Collab Platform— Real-time collaborative workspace', ' Collab Platform— Real-time collaborative workspace',
' 成语填空 — Daily Chinese idiom game', ' 成语填空 — Daily Chinese idiom game',
], ],
@@ -55,7 +77,7 @@ const COMMANDS: Record<string, string[]> = {
' .::!!!::. Kernel: Ubuntu 24.04', ' .::!!!::. Kernel: Ubuntu 24.04',
' .:::!!!:::. RAM: 4GB', ' .:::!!!:::. RAM: 4GB',
' .::::!!!::::. Shell: zsh', ' .::::!!!::::. Shell: zsh',
' .:::::!!!:::::. Services: Alfred, Chengyu, k3s, VLESS', ' .:::::!!!:::::. Services: Alfred, Chengyu, k3s',
'', '',
' nginx + k3s + let\'s encrypt', ' nginx + k3s + let\'s encrypt',
], ],
@@ -68,8 +90,9 @@ function nextKey() { return ++lineCounter; }
export default function Terminal() { export default function Terminal() {
const [lines, setLines] = useState<Line[]>([ const [lines, setLines] = useState<Line[]>([
{ type: 'output', text: 'MingdaOS Terminal v1.0', key: nextKey() }, { type: 'output', text: 'MingdaOS Terminal v1.0 [secure tty]', key: nextKey() },
{ type: 'output', text: 'Type "help" to see available commands.', 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() }, { type: 'output', text: '', key: nextKey() },
]); ]);
const [input, setInput] = useState(''); const [input, setInput] = useState('');
@@ -144,7 +167,7 @@ export default function Terminal() {
<div style={{ flex: 1, overflowY: 'auto' }}> <div style={{ flex: 1, overflowY: 'auto' }}>
{lines.map((line) => ( {lines.map((line) => (
<div key={line.key} style={{ <div key={line.key} style={{
color: line.type === 'input' ? '#64d2ff' : '#e2e8f0', color: line.type === 'input' ? '#00ffff' : '#e2e8f0',
whiteSpace: 'pre', whiteSpace: 'pre',
lineHeight: 1.6, lineHeight: 1.6,
}}> }}>
@@ -154,7 +177,7 @@ export default function Terminal() {
<div ref={bottomRef} /> <div ref={bottomRef} />
</div> </div>
<div style={{ display: 'flex', alignItems: 'center', marginTop: 4 }}> <div style={{ display: 'flex', alignItems: 'center', marginTop: 4 }}>
<span style={{ color: '#64d2ff', whiteSpace: 'nowrap' }}>{PROMPT}</span> <span style={{ color: '#00ffff', whiteSpace: 'nowrap' }}>{PROMPT}</span>
<input <input
ref={inputRef} ref={inputRef}
value={input} value={input}
@@ -168,7 +191,7 @@ export default function Terminal() {
color: '#e2e8f0', color: '#e2e8f0',
fontFamily: 'var(--font-mono)', fontFamily: 'var(--font-mono)',
fontSize: 13, fontSize: 13,
caretColor: '#64d2ff', caretColor: '#00ffff',
}} }}
spellCheck={false} spellCheck={false}
autoComplete="off" autoComplete="off"

View File

@@ -19,20 +19,21 @@
.boot-apple { .boot-apple {
width: 80px; width: 80px;
height: 80px; height: 80px;
fill: #fff; fill: #00ffff;
image-rendering: pixelated;
} }
.boot-progress-track { .boot-progress-track {
width: 200px; width: 200px;
height: 3px; height: 4px;
background: rgba(255,255,255,0.15); background: rgba(0, 255, 255, 0.15);
border-radius: 2px; border: 1px solid rgba(0, 255, 255, 0.3);
overflow: hidden; overflow: hidden;
} }
.boot-progress-bar { .boot-progress-bar {
height: 100%; height: 100%;
background: #fff; background: #00ffff;
border-radius: 2px;
transition: width 0.05s linear; transition: width 0.05s linear;
box-shadow: 0 0 8px #00ffff;
} }

View File

@@ -1,11 +1,9 @@
.context-menu { .context-menu {
position: fixed; position: fixed;
background: rgba(40, 40, 52, 0.82); background: #0d0d1a;
backdrop-filter: var(--glass-blur); border: 2px solid #333333;
-webkit-backdrop-filter: var(--glass-blur); border-radius: 0;
border: 1px solid var(--glass-border); box-shadow: 3px 3px 0 #000000;
border-radius: 8px;
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
padding: 4px; padding: 4px;
min-width: 200px; min-width: 200px;
z-index: 9000; z-index: 9000;
@@ -15,19 +13,21 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
padding: 6px 10px; padding: 8px 10px;
border-radius: 5px; border-radius: 0;
font-size: 13px; font-size: 8px;
color: rgba(255, 255, 255, 0.9); color: rgba(255, 255, 255, 0.9);
cursor: default; cursor: default;
letter-spacing: 0.5px;
} }
.context-menu-item:hover { .context-menu-item:hover {
background: var(--accent-blue); background: var(--pixel-cyan);
color: #000;
} }
.context-menu-separator { .context-menu-separator {
height: 1px; height: 2px;
background: var(--glass-border); background: #333333;
margin: 3px 6px; margin: 3px 6px;
} }

View File

@@ -1,7 +1,8 @@
.desktop { .desktop {
position: fixed; position: fixed;
inset: 0; inset: 0;
background-size: cover; background-size: 24px 24px, 12px 12px, cover;
background-position: center;
overflow: hidden; overflow: hidden;
} }

View File

@@ -31,7 +31,10 @@ export default function Desktop() {
return ( return (
<div <div
className="desktop" className="desktop"
style={{ background: WALLPAPERS[state.wallpaper] }} style={{
background: WALLPAPERS[state.wallpaper],
backgroundSize: WALLPAPERS[state.wallpaper].startsWith('url(') ? 'cover' : undefined,
}}
onContextMenu={handleContextMenu} onContextMenu={handleContextMenu}
onClick={() => setCtxMenu(null)} onClick={() => setCtxMenu(null)}
> >
@@ -47,12 +50,19 @@ export default function Desktop() {
gridRow: app.desktopPosition.row + 1, gridRow: app.desktopPosition.row + 1,
}} }}
> >
<DesktopIcon app={app} onOpen={() => openWindow(app.id)} /> <DesktopIcon
app={app}
onOpen={() =>
app.externalUrl
? window.open(app.externalUrl, '_blank', 'noopener,noreferrer')
: openWindow(app.id)
}
/>
</div> </div>
))} ))}
</div> </div>
{APPS.map(app => ( {APPS.filter(app => !app.externalUrl).map(app => (
<Window key={app.id} app={app} /> <Window key={app.id} app={app} />
))} ))}

View File

@@ -6,32 +6,34 @@
cursor: default; cursor: default;
width: 72px; width: 72px;
padding: 6px; padding: 6px;
border-radius: 8px; border-radius: 0;
transition: background 0.1s; transition: background 0.1s;
} }
.desktop-icon:hover { .desktop-icon:hover {
background: rgba(255,255,255,0.08); background: rgba(0, 255, 255, 0.08);
} }
.desktop-icon-img { .desktop-icon-img {
width: 56px; width: 56px;
height: 56px; height: 56px;
border-radius: var(--radius-icon); border-radius: 0;
border: 2px solid #000;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 28px; font-size: 28px;
box-shadow: 0 2px 12px rgba(0,0,0,0.3); box-shadow: 3px 3px 0 #000;
} }
.desktop-icon-label { .desktop-icon-label {
font-size: 11px; font-size: 8px;
color: #fff; color: var(--pixel-cyan);
text-align: center; text-align: center;
text-shadow: 0 1px 3px rgba(0,0,0,0.6); text-shadow: 1px 1px 0 #000;
max-width: 72px; max-width: 72px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
letter-spacing: 0.5px;
} }

View File

@@ -10,7 +10,9 @@ export default function DesktopIcon({ app, onOpen }: Props) {
return ( return (
<div className="desktop-icon" onDoubleClick={onOpen}> <div className="desktop-icon" onDoubleClick={onOpen}>
<div className="desktop-icon-img" style={{ background: app.iconGradient }}> <div className="desktop-icon-img" style={{ background: app.iconGradient }}>
<span>{app.emoji}</span> {app.iconImage
? <img src={app.iconImage} alt={app.title} style={{ width: '70%', height: '70%', objectFit: 'contain' }} />
: <span>{app.emoji}</span>}
</div> </div>
<span className="desktop-icon-label">{app.title}</span> <span className="desktop-icon-label">{app.title}</span>
</div> </div>

View File

@@ -10,13 +10,11 @@
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
gap: 6px; gap: 6px;
background: rgba(30, 30, 40, 0.52); background: #0d0d1a;
backdrop-filter: var(--glass-blur); border: 2px solid #333333;
-webkit-backdrop-filter: var(--glass-blur); border-radius: 0;
border: 1px solid var(--glass-border);
border-radius: 18px;
padding: 8px 12px; padding: 8px 12px;
box-shadow: 0 4px 28px rgba(0,0,0,0.4); box-shadow: 3px 3px 0 #000000;
} }
.dock-item { .dock-item {
@@ -35,18 +33,19 @@
.dock-icon { .dock-icon {
width: 48px; width: 48px;
height: 48px; height: 48px;
border-radius: 12px; border-radius: 0;
border: 2px solid #000;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 24px; font-size: 24px;
transition: transform 0.15s ease; transition: transform 0.1s steps(2);
box-shadow: 0 2px 8px rgba(0,0,0,0.3); box-shadow: 2px 2px 0 #000;
} }
.dock-dot { .dock-dot {
width: 4px; width: 4px;
height: 4px; height: 4px;
border-radius: 50%; border-radius: 0;
background: rgba(255,255,255,0.7); background: var(--pixel-cyan);
} }

View File

@@ -4,10 +4,8 @@
left: 0; left: 0;
right: 0; right: 0;
height: var(--menubar-height); height: var(--menubar-height);
background: rgba(20, 20, 28, 0.72); background: #080816;
backdrop-filter: var(--glass-blur); border-bottom: 2px solid rgba(0, 255, 255, 0.2);
-webkit-backdrop-filter: var(--glass-blur);
border-bottom: 1px solid var(--glass-border);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
@@ -26,19 +24,20 @@
font-size: 14px; font-size: 14px;
margin-right: 8px; margin-right: 8px;
cursor: default; cursor: default;
color: var(--pixel-cyan);
} }
.menubar-item { .menubar-item {
font-size: 12px; font-size: 8px;
font-weight: 500; font-weight: 400;
color: rgba(255,255,255,0.85); color: var(--pixel-cyan);
padding: 2px 8px; padding: 2px 8px;
border-radius: 4px;
cursor: default; cursor: default;
letter-spacing: 0.5px;
} }
.menubar-item:hover { .menubar-item:hover {
background: rgba(255,255,255,0.1); background: rgba(0, 255, 255, 0.15);
} }
.menubar-right { .menubar-right {
@@ -47,7 +46,7 @@
} }
.menubar-clock { .menubar-clock {
font-size: 12px; font-size: 8px;
font-weight: 500; color: var(--pixel-cyan);
color: rgba(255,255,255,0.85); letter-spacing: 0.5px;
} }

View File

@@ -1,31 +1,53 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import './MenuBar.css'; import './MenuBar.css';
import { APP_VERSION } from '../../config/version';
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() { export default function MenuBar() {
const [time, setTime] = useState(() => formatTime(new Date())); const [time, setTime] = useState(() => formatTime(new Date()));
const [uptime, setUptime] = useState(() => formatUptime(0));
useEffect(() => { 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 () => clearInterval(id);
}, []); }, []);
return ( return (
<div className="menubar"> <div className="menubar">
<div className="menubar-left"> <div className="menubar-left">
<span className="menubar-logo">&#63743;</span> <span className="menubar-logo">[ MingdaOS v{APP_VERSION} ]</span>
{MENU_ITEMS.map(item => ( {MENU_ITEMS.map(item => (
<span key={item} className="menubar-item">{item}</span> <span key={item} className="menubar-item">{item}</span>
))} ))}
</div> </div>
<div className="menubar-right"> <div className="menubar-right">
<span className="menubar-clock">{time}</span> <span className="menubar-clock" style={{ opacity: 0.55, marginRight: 12 }}>
up {uptime}
</span>
<span className="menubar-clock">
{time}<span className="cypher-caret"></span>
</span>
</div> </div>
</div> </div>
); );
} }
function formatTime(d: Date) { 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`;
} }

View File

@@ -1,27 +1,30 @@
.window { .window {
background: var(--glass-bg); background: #0d0d1a;
backdrop-filter: var(--glass-blur); border: 2px solid #333333;
-webkit-backdrop-filter: var(--glass-blur); border-radius: 0;
border: 1px solid var(--glass-border); box-shadow: var(--pixel-shadow);
border-radius: var(--radius-window);
box-shadow: var(--glass-shadow);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
transition: opacity 0.15s; transition: opacity 0.15s;
} }
.window--focused {
border-color: rgba(0, 255, 255, 0.4);
}
.window--unfocused { .window--unfocused {
opacity: 0.72; opacity: 0.72;
} }
.window-titlebar { .window-titlebar {
height: var(--titlebar-height); height: var(--titlebar-height);
background: #111122;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 0 12px; padding: 0 12px;
cursor: move; cursor: move;
border-bottom: 1px solid var(--glass-border); border-bottom: 2px solid #333333;
flex-shrink: 0; flex-shrink: 0;
gap: 8px; gap: 8px;
position: relative; position: relative;
@@ -36,15 +39,15 @@
.traffic-light { .traffic-light {
width: 12px; width: 12px;
height: 12px; height: 12px;
border-radius: 50%; border-radius: 0;
border: none; border: 1px solid rgba(0,0,0,0.5);
cursor: pointer; cursor: pointer;
position: relative; position: relative;
transition: filter 0.1s; transition: filter 0.1s;
} }
.window--unfocused .traffic-light { .window--unfocused .traffic-light {
background: #666 !important; background: #444 !important;
} }
.traffic-light--close { background: var(--accent-red); } .traffic-light--close { background: var(--accent-red); }
@@ -63,20 +66,22 @@
justify-content: center; justify-content: center;
font-size: 8px; font-size: 8px;
font-weight: 700; font-weight: 700;
color: rgba(0,0,0,0.6); color: rgba(0,0,0,0.7);
line-height: 1; line-height: 1;
} }
.window-title { .window-title {
flex: 1; flex: 1;
text-align: center; text-align: center;
font-size: 13px; font-size: 9px;
font-weight: 500; font-weight: 400;
color: rgba(255,255,255,0.85); color: var(--pixel-cyan);
pointer-events: none; pointer-events: none;
position: absolute; position: absolute;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
letter-spacing: 1px;
white-space: nowrap;
} }
.window-content { .window-content {

View File

@@ -7,6 +7,7 @@ import Alfred from '../apps/Alfred';
import Chengyu from '../apps/Chengyu'; import Chengyu from '../apps/Chengyu';
import Poker from '../apps/Poker'; import Poker from '../apps/Poker';
import Trash from '../apps/Trash'; import Trash from '../apps/Trash';
import { withVersion } from './version';
export const APPS: AppConfig[] = [ export const APPS: AppConfig[] = [
{ {
@@ -89,10 +90,36 @@ export const APPS: AppConfig[] = [
component: Trash, component: Trash,
desktopPosition: { col: 1, row: 3 }, 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: withVersion('/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 = [ export const WALLPAPERS = [
'radial-gradient(ellipse at 20% 50%, #1a0533 0%, transparent 60%), radial-gradient(ellipse at 80% 20%, #0d1f3c 0%, transparent 55%), radial-gradient(ellipse at 60% 80%, #0a1a0a 0%, transparent 50%), radial-gradient(ellipse at 40% 40%, #2d0a3e 0%, transparent 45%), linear-gradient(135deg, #0a0a12 0%, #12061e 50%, #060d1a 100%)', // Dot grid — dark with subtle cyan dot pattern
'radial-gradient(ellipse at 30% 60%, #1a0a00 0%, transparent 60%), radial-gradient(ellipse at 70% 30%, #001a2e 0%, transparent 55%), radial-gradient(ellipse at 50% 80%, #0d0020 0%, transparent 50%), linear-gradient(135deg, #080808 0%, #1a0d00 50%, #000d1a 100%)', `radial-gradient(circle, rgba(0,255,255,0.07) 1px, transparent 1px),
'radial-gradient(ellipse at 50% 50%, #001a1a 0%, transparent 70%), radial-gradient(ellipse at 20% 80%, #1a001a 0%, transparent 55%), linear-gradient(135deg, #060a0a 0%, #0a0620 100%)', radial-gradient(circle, rgba(0,255,255,0.04) 1px, transparent 1px),
linear-gradient(135deg, #050510 0%, #080818 100%)`,
// Scanlines — horizontal line texture
`repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0,255,255,0.03) 2px,
rgba(0,255,255,0.03) 4px
),
linear-gradient(135deg, #050510 0%, #06060f 100%)`,
// Solid dark — flat and minimal
`linear-gradient(135deg, #050510 0%, #080818 100%)`,
// Pixel city — neon cityscape
`url(${withVersion('/wallpaper-city.png')})`,
]; ];

5
src/config/version.ts Normal file
View File

@@ -0,0 +1,5 @@
export const APP_VERSION = '1.0.0';
export function withVersion(url: string): string {
return url.includes('?') ? `${url}&v=${APP_VERSION}` : `${url}?v=${APP_VERSION}`;
}

View File

@@ -21,6 +21,8 @@ export interface AppConfig {
defaultPosition: { x: number; y: number }; defaultPosition: { x: number; y: number };
component: ComponentType; component: ComponentType;
desktopPosition: { col: number; row: number }; desktopPosition: { col: number; row: number };
externalUrl?: string;
iconImage?: string;
} }
export interface OSState { export interface OSState {