Editorial Portfolio
A static React SPA personal portfolio in editorial bilingual style — dark mode, serif headings, mono accents, custom cursor ring (desktop), mobile bottom action bar, AI chat agent, scroll-driven reveals. Designed for fresh-grad / IC / founder profiles. Zero build step — Babel-standalone in the browser. Deploys to Vercel in 30 seconds.
Reference live site (built by 彭智炜 / @wilson534): https://pengzhiwei.vercel.app
When to use
- User asks "帮我做个人网站 / build my portfolio / 给我做个像彭智炜那样的站"
- User has a resume / GitHub profile / LinkedIn dump and wants a polished personal site
- User wants something deployable to Vercel today, not a Next.js project that takes a week
What you do
End-to-end ownership: codebase generation → local preview → deploy. The user should not have to learn React, CSS, or Vercel.
Step 1 — Gather information from context
Mine the conversation history, attached files, GitHub repo, prior resume for these fields. Most map 1:1 to data.js:
| Field | Where to find it | data.js key |
|---|---|---|
| Name (Zh + En) | first message, signature, GitHub | profile.nameZh / nameEn |
| Role | job title or self-described | profile.role / roleEn |
| Tagline | one-liner of what they ship | profile.tagline |
| Statement | 1-sentence philosophy | profile.statement |
| Location | city or "X → Y" if relocating | profile.location |
| Status | hiring / open-to-work / busy state | profile.status |
| Photo | portrait image path or URL | profile.photo (drop into assets/portrait.png) |
| Contact links | GitHub / 小红书 / Email / WeChat / Linkedin etc. | profile.links array |
| Hero figures | 3-4 numbers + labels (e.g. "12 awards · 7 hackathons") | profile.figures |
| Internships | dated work entries | timeline items with kind: "internship" |
| Projects | independent shipped projects | timeline items with kind: "project" |
| Hackathons | competition / showcase entries | timeline items with kind: "hackathon" or "showcase" |
| Awards | competition wins, scholarships | awards array |
| Press | interviews, articles, videos | press array |
| Skills | grouped chips: tools / stack / methods / domain | skills array of {group, items} |
| Inputs | what they're listening / reading / using right now | inputs |
If a section is genuinely missing from context, omit it cleanly:
- For top-level sections (press, inputs, agent chat) you can drop the entire
<section>block inapp.jsx - For timeline, just have fewer entries — the section auto-handles empty kinds
If critical fields are missing (name, no work history at all), ask once in a single bundled message.
Step 2 — Lay down the template
Copy the entire templates/ directory to a new working directory (e.g. user's ~/portfolio/ or wherever they want). The structure is:
portfolio/
├── index.html # root HTML — loads React + Babel from CDN
├── styles.css # all design tokens + responsive layout
├── app.jsx # React app (no build step needed)
├── tweaks-panel.jsx # dev-only edit-mode panel (safe to leave)
├── data.js # ← THE FILE YOU EDIT
├── .vercelignore # excludes dev artifacts from deploy
└── assets/
└── portrait.png # ← REPLACE with user's photo
Step 3 — Edit data.js only (DO NOT touch CSS or JSX)
data.js is the single source of truth for content. Replace every field under window.PZW_DATA = { ... } with the user's data. The existing values are 彭智炜's — use them as format reference only, then overwrite.
The structure with example shape:
window.PZW_DATA = {
profile: {
nameZh: "彭智炜", // → user's Chinese name
nameEn: "Musketeer", // → user's English name / nickname
role: "技术型 AI 产品工程师", // → user's role
roleEn: "Technical AI Product Engineer",
tagline: "...", // one-liner
statement: "...", // 1-2 sentence philosophy
location: "Shanghai → Beijing",
status: "summer 2026 · 开放合作 / 实习",
photo: "assets/portrait.png",
links: [ { label, handle, href }, ... ],
figures: [ { label, value }, ... ]
},
timeline: [ { kind, date, year, org, title, ... }, ... ],
awards: [ { year, name, prize, sub }, ... ],
press: [ { outlet, title, sub, kind, href }, ... ],
skills: [ { group, items: [...] }, ... ],
inputs: { listening: [...], reading: [...], using: [...] },
};
Step 4 — Replace placeholders in app.jsx
A few strings outside data.js use {{...}} placeholders. Replace these directly in app.jsx:
| Placeholder | Replace with |
|---|---|
{{NAME_ZH}} |
user's Chinese name (multiple occurrences) |
{{NAME_EN}} |
user's English name |
{{FIRST_NAME_ZH}} |
first name only (e.g. "智炜" → use last 2 chars of nameZh) |
{{GITHUB_USERNAME}} |
GitHub handle (no @) |
{{YEAR}} |
current year for footer copyright (e.g. 2026) |
Search-and-replace with sed -i '' 's/{{NAME_ZH}}/...//g' style.
Step 5 — Drop in the portrait photo
Replace assets/portrait.png with the user's photo. Recommended:
- portrait orientation (taller than wide)
- crop ratio approximately 3:4
- 1000px+ on the long edge
If the user hasn't provided one, leave a note in the README and skip — site will display a broken image but everything else works.
Step 6 — Local preview
Start a static server in the working directory and tell the user the URL:
cd portfolio/
python3 -m http.server 5173
# → open http://localhost:5173
Verify:
- Hero loads, name/role/tagline correct
- Timeline shows correct entries
- Mobile viewport (375px) works — no horizontal scroll, bottom action bar visible
- Chat agent responds (or doesn't crash if no API set)
Step 7 — Deploy to Vercel
cd portfolio/
vercel link --yes --project <user-handle> # one-time link, e.g. "wilson534"
vercel --prod --yes # deploy
Output: https://<user-handle>.vercel.app
If user doesn't have Vercel CLI: npm i -g vercel && vercel login.
Style rules baked in (don't alter)
- Editorial dark mode (default):
bg #0d0c0a+fg #ede7dc+ accentoklch(0.85 0.16 118)lime - Three direction modes user can toggle:
editorial / paper / terminal(the tweaks panel exposes this) - Bilingual: Chinese for sections + 大字, English for tech terms + small caps + mono labels
- Single-column mobile, max 1100px breakpoint folds nav into FAB stack
- Custom cursor ring on desktop, native cursor on touch
SUMMER 2026style chip in hero — auto-update season per user's launch context
Customization
Most users won't touch CSS. If they want to:
- Accent color:
--accent-huein:root(try 30 = orange, 280 = violet) - Direction default: change
tw.directioninitial in app.jsx - Font:
--serifand--monoin:root
Files
templates/— full deployable static site, drop in and gotemplates/data.js— single content source, contains 彭智炜's data as format referencetemplates/assets/— placeholder portrait. Replace before deploy.
Companion skill
Pair with editorial-resume — generates the matching single-page A4 PDF resume that the portfolio's mobile RESUME button links to.