Personal website for Zhiyuan Wang, live at https://juanerw.github.io/personalWeb/.
personalWeb/
├── home.html ← Resume homepage (deployed as site root index.html)
├── index.html ← Meta-refresh fallback redirect → home.html
├── my-site/ ← React SPA: games & tools playground
│ ├── src/
│ │ ├── main.jsx
│ │ ├── App.jsx ← HashRouter + all routes
│ │ ├── LangContext.jsx ← React context for EN/ZH
│ │ ├── lang.js ← All bilingual strings (single source of truth)
│ │ ├── components/
│ │ │ ├── AppCard/ ← Card used on the home grid
│ │ │ └── Navbar/
│ │ ├── pages/
│ │ │ ├── Home/ ← Grid of all games & tools
│ │ │ ├── games/
│ │ │ │ ├── Game2048/
│ │ │ │ └── Snake/
│ │ │ └── tools/
│ │ │ ├── Notepad/
│ │ │ ├── Calendar/
│ │ │ ├── Calculator/
│ │ │ └── Spinner/
│ │ └── styles/
│ │ └── global.css ← Design tokens + shared body styles
│ └── vite.config.js ← base: '/personalWeb/my-site/'
├── TBA.io-main/ ← Static HTML task-management app (no build step)
└── .github/
└── workflows/
└── deploy.yml ← Builds React, assembles _site/, deploys to Pages
| Path | Source |
|---|---|
/personalWeb/ |
home.html |
/personalWeb/my-site/ |
my-site/dist/ (Vite build) |
/personalWeb/TBA/ |
TBA.io-main/ (copied as-is) |
Push to main → GitHub Actions builds → deploys to GitHub Pages.
The workflow (deploy.yml) does three things:
- Runs
npm ci && npm run buildinsidemy-site/ - Assembles
_site/: copieshome.html→_site/index.html, React build →_site/my-site/, static folders →_site/<name>/ - Uploads
_site/as a Pages artifact and deploys
Settings → Pages → Source must be "GitHub Actions" (set once, never needs to change).
Language is stored in localStorage under the key lang ('en' or 'zh'). Both parts of the site read and write the same key so switching language on one page persists to the other.
translationsobject at bottom of file holds every EN/ZH stringapplyLang(lang)sets all element text viagetElementById- Add a new translatable element: (1) give it an
id, (2) add the string to bothenandzhobjects, (3) add onegetElementByIdline inapplyLang
lang.jsexports{ en: {...}, zh: {...} }— single file, single source of truthLangContext.jsxprovides{ lang, T }(whereT = t[lang]) via React context- Every component reads strings with
const { T } = useLang() App.jsxwraps everything in<LangProvider>
Defined in my-site/src/styles/global.css and mirrored inline in home.html:
| Token | Value | Usage |
|---|---|---|
--bg |
#f4f1ea |
Page background |
--bg-card |
#fbf9f4 |
Card surface |
--ink |
#20211c |
Primary text |
--ink-soft |
#54564d |
Secondary text |
--ink-faint |
#8a8b80 |
Captions, labels |
--accent |
#1d6b5f |
Teal — links, borders, buttons |
--accent-deep |
#134a41 |
Hover state |
--line |
#ddd8cb |
Borders, dividers |
--display |
Fraunces | Headings |
--body |
Newsreader | Body text |
--mono |
JetBrains Mono | Labels, buttons, code |
my-site/src/pages/games/MyGame/
├── MyGame.jsx
└── MyGame.module.css
Minimal page structure:
import { Link } from 'react-router-dom'
import { useLang } from '../../../LangContext'
import styles from './MyGame.module.css'
export default function MyGame() {
const { T } = useLang()
return (
<div className={styles.page}>
<Link to="/">{T.back}</Link>
{/* game content */}
</div>
)
}import MyGame from './pages/games/MyGame/MyGame'
// inside <Routes>:
<Route path="/games/mygame" element={<MyGame />} />// in both en.apps and zh.apps:
mygame: { title: 'My Game', desc: 'One-line description.' },const games = [
...
{ key: 'mygame', path: '/games/mygame', emoji: '🎯' },
]In the play-grid div:
<a class="play-card" href="./my-site/#/games/mygame">
<div class="play-card-icon">🎯</div>
<div class="play-card-type" id="card-type-game3">Game</div>
<div class="play-card-title" id="card-title-mygame">My Game</div>
<div class="play-card-desc" id="card-desc-mygame">One-line description.</div>
</a>Then add to translations.en and translations.zh, and two getElementById lines in applyLang.
Static projects are standalone HTML apps that live outside the React SPA.
personalWeb/
└── MyProject/
├── index.html
└── ...
- name: Assemble site
run: |
mkdir -p _site/my-site _site/TBA _site/MyProject
cp home.html _site/index.html
cp -r my-site/dist/. _site/my-site/
cp -r TBA.io-main/. _site/TBA/
cp -r MyProject/. _site/MyProject/ # ← add thisUse href instead of path — this bypasses React Router and navigates directly:
const tools = [
...
{ key: 'myproject', href: '/personalWeb/MyProject/', emoji: '🔧' },
]Same as step 3–5 above, with href="./MyProject/" for the <a> tag in home.html.
| Prop | Type | Notes |
|---|---|---|
emoji |
string | Shown large at card top |
type |
string | Small uppercase label (T.games or T.tools) |
title |
string | Card heading (Fraunces font) |
description |
string | Body text |
path |
string | React Router path — use for in-app pages |
href |
string | Plain <a> href — use for external/static pages |
Providing href takes priority over path.