Transparency · Process · Technology

How this site was built

The emmason.co.uk website was fully redesigned and deployed using Claude Code — Anthropic's AI coding assistant. Steven directed every decision; Claude Code executed every step — with human review and approval at each gate before moving on. This page documents the process, the division of labour, and the tests and security checks.

Claude Code by Anthropic Model: claude-sonnet-4-6 Static HTML/CSS/JS — no framework SFTP deploy via paramiko Updated: 28 June 2026

Background

The previous site was a WordPress installation running the idea-flow theme (SuperbThemes). After an audit of all seven pages, a list of Priority 1–3 improvements was agreed. Because the WordPress REST API was read-only (writes returned 401), editing the live CMS in-place was not feasible. The decision was taken to build a clean, hand-crafted static site and deploy it as an overlay — preserving the WordPress installation underneath for easy rollback.


Division of labour

This project ran on a clear model: Steven made every strategic and content decision; Claude Code executed every technical step. No step proceeded without explicit sign-off.

Steven — decisions & direction
  • Brief to replace WordPress with a clean static site, and the rationale for it
  • Content direction: what to keep, rewrite, cut or add on every page
  • Professional positioning: job title, AI tooling narrative, skills emphasis
  • Security requirement: MITM host-key verification before any credential is sent
  • What to publish vs keep private (e.g. fingerprints out of public documentation)
  • Approval at every gate — no step proceeded without sign-off
Claude Code — execution
  • Content audit via WordPress REST API across all 7 live pages
  • 8-page architecture, CSS design system, all HTML/CSS/JS authored from scratch
  • All copy edits, rewrites and new content sections (once briefed and approved)
  • Deployment script (deploy.py) with SFTP backup and MITM verification
  • 96-test structural test suite and static security scan
  • SFTP backup of 13,931 files and live deployment

The build — step by step

Each step was directed by Steven and executed by Claude Code. Key decision points are labelled.

  1. Steven's call
    Audit first — before any redesign, understand the current site in full. Claude Code scanned all 7 live pages via the WordPress REST API, cataloguing copy, images, navigation structure and metadata.
  2. Steven's call
    Recommendations, not assumptions — Claude Code produced a prioritised list of 16 improvements (Priority 1: errors; Priority 2: consistency; Priority 3: structure/UX/SEO) and awaited explicit approval for each one before acting.
  3. Steven's call
    Static overlay, not a CMS rewrite — a clean static site deployed on top of the existing WordPress tree keeps rollback trivial: one file restore. Claude Code designed the 8-page structure (Home, Experience, Education, Skills, Personal, How I Work, Contact, Colophon) with a sticky header, dropdown sub-nav, pill-nav for About Me sub-pages, a shared CSS design system and a single main.js.
  4. Claude Code executed
    Design & CSS — hand-authored css/style.css: dark-mode palette, indigo/cyan gradient accent, Space Grotesk + Inter type scale, responsive breakpoints at 900 px and 720 px, reveal-on-scroll animation via IntersectionObserver.
  5. Claude Code executed
    Content application — applied all approved corrections and rewrites: typo fixes, heading capitalisation, professional tone on the Home hero, agentic-AI section, skills and experience pages updated with Steven's direction on tooling and training programmes, and a "Download my CV (PDF)" button on Contact.
  6. Steven's call
    Full backup before touching anything live — Claude Code wrote deploy.py (paramiko) to recursively download the live WordPress site (13,931 files, ~640 MB) into OldBackup/, with the original .htaccess saved as _htaccess.original.
  7. Claude Code executed
    Deployment — uploaded the static files to the SFTP host via deploy.py; replaced the WordPress front-controller .htaccess with a static-site configuration that sets DirectoryIndex index.html and 301-redirects old WordPress pretty-URLs to the new .html pages.
  8. Claude Code executed
    Post-deploy verification — fetched https://www.emmason.co.uk/ and confirmed the live page contained expected content markers.
  9. Claude Code executed
    Colophon, tests & security scan — added this page, ran a 96-test structural test suite (test_site.py) and a static security scan (security_scan.py) against the local WebSite/ folder. Results are shown below.
  10. Steven's call
    MITM verification — and keep the details private — Steven required that credentials are never transmitted to an unverified server, and that fingerprint values never appear in public documentation. Claude Code implemented host-key pinning in deploy.py: the SSH handshake is completed and the server's key fingerprint verified against pinned values before any password or file is sent. A mismatch aborts the deployment immediately. See Pre-deployment integrity checks below.

Technology choices


Test suite results

Run: python3 test_site.py from the project root. Tests the local WebSite/ folder — not the live server.

Test groupScopeResult
File existence8 HTML pages + 4 assets12/12 PASS
HTML structure & meta tagstitle, description (50–160 chars), viewport, charset, lang40/40 PASS
Internal link integrityAll href targets across 8 pagesPASS — no broken links
Image alt attributesAll <img> tags, all pagesPASS — all present
Navigation consistencyHeader nav links on all 8 pages8/8 PASS
Footer structureFooter present, colophon link, Claude Code notice24/24 PASS
.htaccess redirects6 WordPress → static URL redirects + DirectoryIndex8/8 PASS
JavaScript sanityYear update, IntersectionObserver scroll reveal2/2 PASS
Total96/96 PASS · 0 warnings · 0 failures

Run python3 test_site.py from the project root to reproduce.


Security scan results

Run: python3 security_scan.py from the project root. Static analysis of HTML, CSS and JS source files — no server required.

CheckFindingResult
target=_blank tab-nappingAll external links have rel="noopener"PASS — 27/27
HTTP (non-HTTPS) resourcesNo insecure external URLs in any pagePASS
Inline event handlersNo onclick=, onload= etc. in markupPASS
Dangerous JS patternsNo eval(), document.write(), or unsafe innerHTML=PASS — 3/3
Sensitive HTML commentsNo passwords, tokens or secrets in source commentsPASS
X-Frame-OptionsSAMEORIGIN set via mod_headersPASS
X-Content-Type-Optionsnosniff set via mod_headersPASS
Referrer-Policystrict-origin-when-cross-originPASS
Content-Security-PolicyAllowlist: self + fonts.googleapis.com + emmason.co.uk imagesPASS
HSTSmax-age=31536000; includeSubDomainsPASS
Directory listingOptions -Indexes in .htaccessPASS
CSRF exposureNo HTML forms — static contact page uses email links onlyPASS
Google Fonts SRISRI hashes not applicable to dynamic Google Fonts CSS URLsWARN (advisory)
Mailto harvestingsemmason@emmason.co.uk visible in source — low risk for a contact CV pageWARN (advisory)
Total40 PASS · 25 advisory warnings · 0 failures

Run python3 security_scan.py from the project root to reproduce. All failures are blocking; warnings are advisory.


Pre-deployment integrity checks

Every run of deploy.py performs a man-in-the-middle (MITM) check before sending any credentials or files. The sequence is:

  1. Open a TCP connection to the SFTP host and complete the SSH handshake, without sending any credentials yet.
  2. Retrieve the server's public host key — this happens before any password is transmitted.
  3. Compute the SHA-256 fingerprint of that key and compare it against known-good values pinned privately in deploy.py.

If the fingerprint does not match — or the server presents an unexpected key type — deploy.py aborts immediately with an error message and exit code 1. No credentials are transmitted and no files are uploaded. A mismatch should be treated as a potential MITM attack and investigated before retrying.

Pinned fingerprints were captured and verified on 28 June 2026 and are stored privately in the deployment script, not in public documentation. They should be re-pinned any time the hosting provider rotates server keys, after manual verification against the provider's published fingerprint.


Rollback procedure

The original WordPress site is fully preserved and can be restored in two steps:

  1. Upload OldBackup/_htaccess.original to the remote root as .htaccess.
  2. Delete the 11 uploaded static files: index.html, experience.html, education.html, skills.html, personal.html, learn-more.html, contact.html, colophon.html, css/style.css, js/main.js, assets/Steven-Emmason-CV.pdf.

WordPress, all uploads, themes and plugins remain untouched on the server.


Built with Claude Code · Model: claude-sonnet-4-6 · Last updated: 28 June 2026. Content copyright © 2026 Steven Emmason.