How this site
was built.
Everything you're looking at — the particle field, the drawn-by-code thumbnails, the type — compiles down to a static folder of plain HTML, CSS, and JavaScript. No site builder, no client framework, no stock assets. This page explains each piece briefly enough that you could build and host your own by tonight.
01What this is
A single-page portfolio plus this guide, themed as a verification dossier — a nod to the owner's background in wire-fraud prevention and settlement operations. The conceit drives everything: the fixed section index reads like a case file, the hero shows noise being swept into order, project cards open like records, and a telemetry ticker runs real numbers from shipped work.
Design goal: look like nothing template-shaped. There is no hero headshot, no purple-gradient SaaS glow, no card grid from a UI kit. Every visual on the page is generated by code that ships with the page.
02The stack
Deliberately small, so it can be understood in one sitting and hosted anywhere:
- Astro, as a static compiler. Components and content compile to plain
HTML at build time — the project data in
src/data/site.tsbecomes the cards, ticker, and badges you see, and zero client framework ships to the browser. The CSS and JavaScript are still hand-written, exactly as they were when this site was a single file. - Three.js (from npm, code-split into its own lazy chunk) for the WebGL
hero — the only runtime dependency — fetched and executed via dynamic
import()only if the browser can actually run WebGL. - Self-hosted fonts — Fraunces, Space Grotesk, JetBrains Mono as
latin-subset
woff2files. No Google Fonts request, no layout shift, works offline. - Static output.
astro buildwrites a plaindist/folder of HTML, CSS, and JS. No server runtime, no database — any web host that can serve files can serve this site.
Everything degrades on purpose: WebGL hero → Canvas-2D contour field → pure-CSS gradient. Reduced-motion users get a composed static frame. Print gets a clean light stylesheet.
03The design system
Palette — verification ink & telemetry amber
A deep blue-charcoal base (not pure black — it reads as material, not void), one hot signal color used sparingly, and teal reserved exclusively for "verified / positive" states, the way a status console would.
Type — a deliberate trio
Fraunces (a warm, sharp variable serif) carries the display voice; Space Grotesk handles UI and body; JetBrains Mono speaks for anything data-flavored — indices, tickers, labels, badges. The serif/mono tension is the whole personality of the page: human judgment on top of machine records.
Motion rules
One easing family, three durations, and a hard rule: motion must mean something. Reveals
confirm you've arrived at a section; the sweep line demonstrates the site's thesis; card
tilt (≤2.4°, pointer-fine devices only) makes the case files feel physical.
prefers-reduced-motion disables all of it and pre-settles every element.
04The hero, explained
The opening animation is a field of ~28,000 GPU particles arranged as a terrain. Layered sine "noise" keeps them drifting — and every eight seconds an amber scan line sweeps across, and particles it touches snap from noise into an ordered grid for a moment before relaxing. That's the site's one-sentence thesis, rendered: verification is turning noise into order.
All the movement lives in a small vertex shader, so the CPU cost is near zero:
// per-particle, on the GPU — the idea, condensed
float n = sin(pos.x*.8 + t) * sin(pos.z*.6 - t*.7); // drift
float d = abs(pos.x - sweepX); // distance to scan line
float snap = smoothstep(6.0, 0.0, d); // 1 near the beam
pos.y = mix(n * amp, gridY, snap); // noise → order Scrolling fades and lifts the field away; the mouse adds a few degrees of parallax; the tab going hidden pauses the render loop. If WebGL is unavailable, a Canvas-2D version draws 34 contour lines with the same sweep behavior — and if even that fails, a layered CSS gradient stands in. The page never shows a black hole where the hero should be.
05Generative sigils
Each project card's thumbnail is drawn at load time on a small canvas, seeded by the
project's own id and title through a tiny deterministic PRNG (xmur3 +
mulberry32). Same project, same sigil, every visit — but no two projects
share one, and adding a new project mints a new mark automatically.
Five motifs map to the nature of the work: lattice (structured systems),
scan (document verification), stream (intake & flow),
lap (the racing simulator), and pulse (reserved slots,
waiting). It's the same instinct as the hero: if an image is needed, generate it — don't
license it.
06Adding your projects
All portfolio content lives in one file — src/data/site.ts — and the two
dashed "reserved" cards are literal headroom. To add work: open that file, find the
PROJECTS array, and add an object. At the next build the card, sigil,
expandable case file, and tags render themselves.
{
id: "my-next-thing", // unique, used to seed the sigil
title: "My Next Thing",
line: "One sharp sentence.",
motif: "lattice", // lattice|scan|stream|lap|pulse
year: "2026", status: "Shipped", role: "Sole author",
stack: ["Tech", "Tech"],
repo: "https://github.com/you/repo",
repoPrivate: false, // true adds the "access on request" badge
desc: ["Paragraph one.", "Paragraph two."],
highlights: ["Bullet", "Bullet"]
}
The ticker works the same way — a TICKER array of
[number, label] pairs right below. Keep the numbers real; that's the point
of it.
07Hosting it
npm run build writes the finished site to dist/ — a static
folder, so nearly any host works. Three paths, each taking under five minutes:
Option A — Classic web hosting (Hostinger, cPanel, any Apache host)
- Run
npm run build(or use the ready-made deploy zip). -
In your hosting file manager, upload the contents of
dist/intopublic_html— most panels let you upload one zip and extract it there. -
Done. An
.htaccessships with the build to wire up the 404 page and long-cache the fonts and hashed assets.
Option B — Netlify Drop (fastest, no account needed to try)
- Go to app.netlify.com/drop.
- Drag the
dist/folder onto the page. -
You get a live
*.netlify.appURL immediately. Create a free account to keep it permanently and rename the subdomain.
Option C — GitHub Pages (free, custom-domain ready)
-
Create a public repository named
yourusername.github.io(that exact pattern gives the cleanest URL), or any repo name if you don't mind/repo-name/in the path. - Upload the contents of
dist/to the repository root — on github.com this is just Add file → Upload files, no Git required. -
Open Settings → Pages, set Source to Deploy from a branch, pick
mainand/ (root), save. - After a minute or two the site is live at
https://yourusername.github.io/.
Post-deploy checklist: replace the placeholder email and LinkedIn links in
src/data/site.ts, set SITE.url to your live address so social
link previews get an absolute og:image, and rebuild.
08The QA log
Every element on this site went through four inspection passes before sign-off — structure, behavior, design, and performance/accessibility. These are the actual findings and fixes from those passes, kept here as a record:
Pass 1 — Structure
The validator earned its keep: two navigation landmarks shared the same accessible
name, two <header> elements created duplicate unnamed banners, an
aria-label sat on a plain <div>, thirteen inline
styles had crept in, a <style> block was living in the body, and
the 404 page auto-redirected on a timer — a WCAG 2.2.1 violation. All fixed at the
source. A script then verified that every link, anchor, CSS url(), and
module import on both pages resolves to a real target.
RESULT · VALIDATOR CLEAN · ALL REFERENCES RESOLVE
Pass 2 — Behavior
The built page was booted in a DOM test runner with its scripts executing for real.
Fifteen assertions passed: four case files plus two reserved slots render, the
accordion's aria-expanded/aria-controls wiring toggles
correctly on click, the ticker populates, the footer year sets itself, and
file:// links self-repair. Because the runner has no WebGL, this also
proved the hero's fallback chain fails safe instead of throwing. Separately, the full
three.js import graph was evaluated in Node and the hero entry point confirmed
null-safe.
RESULT · 15/15 ASSERTIONS · FALLBACK CHAIN PROVEN
Pass 3 — Design
A computed contrast audit of seventeen color pairings caught the one that mattered:
the dim label color measured 3.5:1 on small mono text — below WCAG AA. It was
re-solved for the minimum passing value, keeping the hierarchy intact (now 4.6–5.4:1
on every surface). Also caught: card buttons' focus rings were clipped by
overflow:hidden (now inset), section flavor-notes overflowed narrow
phones (now hidden under 620px), and in the social-card art a vertical gradient line
rendered invisible — a zero-width bounding box makes objectBoundingBox
gradients undefined, fixed with userSpace coordinates. All six sigils and a mid-sweep
frame of the 2D hero were then rendered from the live page code and visually
inspected.
RESULT · 17/17 CONTRAST PAIRS PASS · VISUALS INSPECTED
Pass 4 — Performance & final read
A line-by-line read of the WebGL module verified the shader math, render-loop lifecycle, and disposal path, and turned up real issues elsewhere: the three.js chunk wasn't loading in parallel with the page (a serial import waterfall — fixed), the ticker's duplicated half was missing its trailing separator while flex gaps made the halves unequal (a ~26px visual jump every loop — rebuilt with exact two-unit duplication), and reduced-motion users kept a stretched hero frame after a resize (a re-render was added). Dead code was removed, this guide's claims were corrected against the code, and a final scan confirmed the shipped files contain no employer or colleague identifiers. Validator, behavior suite, and reference audit were then re-run clean.
RESULT · ALL SUITES RE-RUN GREEN · SIGN-OFF
09Built with Claude
This site was designed and written end-to-end by Claude (Anthropic's Fable model) in a single working session, from one detailed brief: build a unique, professional portfolio with real 3D, real animation, and zero template DNA — then document the process. Claude chose the dossier concept, wrote every line of the CSS, JavaScript, and shaders, generated the icon and social-card art as hand-authored SVG, ran the four QA passes above, and wrote this guide. It was later converted — same design system, same hand-written code, now organized as Astro components with the content pulled out into one data file — also by Claude.
To replicate: bring Claude a brief like that one, plus honest raw material about your own work — repos, project notes, numbers you can stand behind. Ask for a concept that comes from your domain rather than a trend, insist on a fallback chain and reduced-motion support, and make it show its QA work. Then build and drop the folder on a host, exactly as in section 07.