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.
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.
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.
deploy.py) with SFTP backup and MITM verificationEach step was directed by Steven and executed by Claude Code. Key decision points are labelled.
main.js.
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.
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.
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.
https://www.emmason.co.uk/ and confirmed the live page contained
expected content markers.
test_site.py) and a static security scan
(security_scan.py) against the local WebSite/ folder.
Results are shown below.
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.
rel="preconnect" to reduce latency; subset to required weights only..htaccess file.
Run: python3 test_site.py from the project root.
Tests the local WebSite/ folder — not the live server.
| Test group | Scope | Result |
|---|---|---|
| File existence | 8 HTML pages + 4 assets | 12/12 PASS |
| HTML structure & meta tags | title, description (50–160 chars), viewport, charset, lang | 40/40 PASS |
| Internal link integrity | All href targets across 8 pages | PASS — no broken links |
| Image alt attributes | All <img> tags, all pages | PASS — all present |
| Navigation consistency | Header nav links on all 8 pages | 8/8 PASS |
| Footer structure | Footer present, colophon link, Claude Code notice | 24/24 PASS |
| .htaccess redirects | 6 WordPress → static URL redirects + DirectoryIndex | 8/8 PASS |
| JavaScript sanity | Year update, IntersectionObserver scroll reveal | 2/2 PASS |
| Total | 96/96 PASS · 0 warnings · 0 failures | |
Run python3 test_site.py from the project root to reproduce.
Run: python3 security_scan.py from the project root.
Static analysis of HTML, CSS and JS source files — no server required.
| Check | Finding | Result |
|---|---|---|
| target=_blank tab-napping | All external links have rel="noopener" | PASS — 27/27 |
| HTTP (non-HTTPS) resources | No insecure external URLs in any page | PASS |
| Inline event handlers | No onclick=, onload= etc. in markup | PASS |
| Dangerous JS patterns | No eval(), document.write(), or unsafe innerHTML= | PASS — 3/3 |
| Sensitive HTML comments | No passwords, tokens or secrets in source comments | PASS |
| X-Frame-Options | SAMEORIGIN set via mod_headers | PASS |
| X-Content-Type-Options | nosniff set via mod_headers | PASS |
| Referrer-Policy | strict-origin-when-cross-origin | PASS |
| Content-Security-Policy | Allowlist: self + fonts.googleapis.com + emmason.co.uk images | PASS |
| HSTS | max-age=31536000; includeSubDomains | PASS |
| Directory listing | Options -Indexes in .htaccess | PASS |
| CSRF exposure | No HTML forms — static contact page uses email links only | PASS |
| Google Fonts SRI | SRI hashes not applicable to dynamic Google Fonts CSS URLs | WARN (advisory) |
| Mailto harvesting | semmason@emmason.co.uk visible in source — low risk for a contact CV page | WARN (advisory) |
| Total | 40 PASS · 25 advisory warnings · 0 failures | |
Run python3 security_scan.py from the project root to reproduce. All failures are blocking; warnings are advisory.
Every run of deploy.py performs a man-in-the-middle (MITM) check
before sending any credentials or files. The sequence is:
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.
The original WordPress site is fully preserved and can be restored in two steps:
OldBackup/_htaccess.original to the remote root as .htaccess.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.