← Back to the dossier

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.ts becomes 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 woff2 files. No Google Fonts request, no layout shift, works offline.
  • Static output. astro build writes a plain dist/ 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.

ink #0b1116
ink-2 #101a21
line #22303a
fog #93a7b1
paper #e9edea
amber #e8a33d
amber-hi #f6c065
teal #3fbfa8

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)

  1. Run npm run build (or use the ready-made deploy zip).
  2. In your hosting file manager, upload the contents of dist/ into public_html — most panels let you upload one zip and extract it there.
  3. Done. An .htaccess ships 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)

  1. Go to app.netlify.com/drop.
  2. Drag the dist/ folder onto the page.
  3. You get a live *.netlify.app URL immediately. Create a free account to keep it permanently and rename the subdomain.

Option C — GitHub Pages (free, custom-domain ready)

  1. 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.
  2. Upload the contents of dist/ to the repository root — on github.com this is just Add file → Upload files, no Git required.
  3. Open Settings → Pages, set Source to Deploy from a branch, pick main and / (root), save.
  4. 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.