Portfolio Website — Build Case Study

How I designed, built, and deployed my personal portfolio using an AI-assisted workflow — from structured brainstorming to a repeatable SSH deployment pipeline.

July 2026 Static HTML / CSS Single file · ~14 KB nginx · SSH · rsync
Overview

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.

The process
1

Brainstorm before building

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.
2

Iterate the design with live mockups

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.
3

Build as a single static file

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.

Why it matters: for a one-page site, a framework adds tooling debt without adding value. Simple loads fast and stays editable.
4

Deploy over SSH to the runtime

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.

Why it matters: separating source from published copy mirrors real deployment pipelines — you can stage, review, then publish.
5

Verify and comply

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.

Why it matters: evidence before assertions — "it works" should mean "I watched it work."
Tech stack

HTML5 + CSS3

Single file, CSS variables for theming, flex/grid layout, no framework.

Sora + Helvetica

Sora (Google Fonts) for display headings, Helvetica for body text.

YouTube thumbnail API

Video previews via img.youtube.com thumbnails linking out — more reliable than iframes.

nginx static hosting

Served from the runtime webroot with HTTPS on a Mainland China ICP-registered domain.

SSH + rsync

Key-based auth, incremental uploads, repeatable two-command deploys.

Claude Code + Superpowers

AI pair-worker for brainstorming, mockups, code, and deployment automation.

Deployment — how it works

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.

Challenges & fixes
YouTube iframe embeds rendered as a black screen when previewing the file locally.
Replaced iframes with clickable thumbnail images from img.youtube.com plus a play button overlay — loads instantly, works everywhere, and links out to YouTube.
The profile photo is a full-body portrait — cropping it into a small circle lost the suit and medal.
Redesigned the hero into two columns: text left, tall rounded portrait right with a coral accent block behind it. The photo became a feature instead of a compromise.
Converting light theme to dark broke elements with hardcoded white backgrounds.
Moved every color to CSS variables in :root, then swept for hardcoded values. Theme changes are now a ten-line edit.
The page passed local checks but compliance required the ICP备案 footer on every page under the domain.
Copied the exact footer markup from the runtime's main site and restyled it for the theme — verified live with grep against the served HTML.
What I learned
← Back to all evidence View the live site