How I designed, built, and deployed my personal portfolio using an AI-assisted workflow — from structured brainstorming to a repeatable SSH deployment pipeline.
Goal: a simple but attractive personal-brand website — one page I can link from LinkedIn, GitHub, and my CV — hosted on my Innospire Runtime under Mainland China ICP compliance.
Result: a fully responsive single-page portfolio at nicholas90.homelinked.tech/portfolio with a dark warm theme, my experience and projects, embedded video work, and a two-command update pipeline. No frameworks, no build step — one HTML file plus one image.
Method: I worked with Claude Code (Anthropic's AI coding agent) using the Superpowers plugin's structured brainstorming workflow: requirements first, then design options, then approval gates before any code was written.
Instead of jumping into code, the first session was questions: What is the site for? Personal brand, not job-hunting. What sections? Hero, about, experience, projects, media, skills, contact. The AI read my CV (PDF) and my updated LinkedIn experience to ground every decision in real content.
Why it matters: unexamined assumptions are where projects waste the most time — even "simple" ones.Three visual directions were tested as real HTML mockups opened in the browser: clean-minimal (too plain), warm coral with personality (better), and finally a full dark-mode conversion. Each round changed real things: full name instead of a nickname greeting, no emoji, a two-column hero to feature a full-body portrait photo.
Why it matters: reacting to something you can see beats describing what you imagine.The production site is one index.html (~14 KB) plus a profile photo. CSS custom properties define the theme, so the light-to-dark conversion touched one :root block instead of hundreds of rules. Sora (Google Fonts) provides the display headings and Helvetica handles body text; there is no JavaScript framework, no build step.
The site lives in two places on the server: the source of truth in my workspace (/opt/workspaces/nicholas90/app/portfolio/) and the published copy in the nginx webroot (/var/www/nicholas90/portfolio/). Uploads use rsync over SSH-key authentication — no passwords. Full commands in the deployment section below.
Every deploy ends with verification: curl checks that the page returns HTTP 200 and that the change is actually in the served HTML. The required ICP备案 compliance footer was added per the Innospire student guide, and a copy of the deliverable was placed in exports/ for admin review.
Single file, CSS variables for theming, flex/grid layout, no framework.
Sora (Google Fonts) for display headings, Helvetica for body text.
Video previews via img.youtube.com thumbnails linking out — more reliable than iframes.
Served from the runtime webroot with HTTPS on a Mainland China ICP-registered domain.
Key-based auth, incremental uploads, repeatable two-command deploys.
AI pair-worker for brainstorming, mockups, code, and deployment automation.
Anyone with a runtime like this can reuse the pattern. The webroot is what nginx serves; the workspace is where source lives. Deploying is two commands from the local machine:
# 1. Upload local files to the workspace (source of truth) rsync -avz --delete ./portfolio/ \ nicholas90@nicholas90.homelinked.tech:/opt/workspaces/nicholas90/app/portfolio/ # 2. Publish the workspace copy into the nginx webroot ssh nicholas90@nicholas90.homelinked.tech \ 'rsync -a --delete /opt/workspaces/nicholas90/app/portfolio/ /var/www/nicholas90/portfolio/ \ && chmod 644 /var/www/nicholas90/portfolio/*'
Then verify the site is actually live and serving the new content:
# HTTP status + confirm the change is in the served HTML
curl -s -o /dev/null -w "HTTP %{http_code}\n" https://nicholas90.homelinked.tech/portfolio/
curl -s https://nicholas90.homelinked.tech/portfolio/ | grep "something-you-changed"
Authentication uses an ed25519 SSH key (generated with ssh-keygen -t ed25519, public key registered by the admin) — no passwords ever travel over the network, and the deploy can run unattended.
img.youtube.com plus a play button overlay — loads instantly, works everywhere, and links out to YouTube.:root, then swept for hardcoded values. Theme changes are now a ten-line edit.grep against the served HTML.