#programming-ai

I didn't write this website


The homepage figure in its five scenes: waving in a cape, running, cycling on a race bike, typing at a desk, and reading a book.

I didn’t write this blog either.

This site is a staticA site made of ready-made files. Nothing runs on a server when you visit; the files are just sent to you. blog with an animated homepage, and Claude Code (Anthropic’s coding agent, which works in the terminal) wrote all of its code. My part was deciding what it should be, and saying yes, no, or “the motion is not smooth”. Claude Code also drafted this post; I chose what went in. All of the code is on GitHub.

Why build it? Mostly to learn AI by making something with it. Building something real and watching it work gives me the same eureka feeling as the first HTML file I ever wrote. I also wanted a home for my side quests: writing things down gives what I learn some structure, and it gives friends and family an easy way to keep up with what I’m doing.

first chat to this post
11 days
commits before this post
47
dependency added
1
homepage size
97 KB
Lighthouse scores
97–100
requests to Claude
~480
input tokens (read)
~122M
output tokens (written)
~370k

Dotted words have a definition: tap or click them. Code and longer explanations are folded away; open what interests you.

The anatomy of this website

A website is a few layers, and most of them are invisible. Hover over a part, or tap it on a phone, to see what it does, what I picked and why.

What you see

Behind it

Address

The name you type to reach a site. It points your browser to the servers that hold it.

Picked
shrey.cloud, bought through Vercel. Until then it was a free my-website-phi-topaz.vercel.app address.
Why
Short, and .cloud leaves room for other projects on subdomains later (like quant.shrey.cloud). Buying it through Vercel meant no DNS settings to touch.
Alternatives
Cloudflare or Namecheap. shreyans.page cost half as much per year; shrey.gg was $130 a year.

Look and feel

CSS: the rules for colours, fonts, spacing and layout.

Picked
Plain CSS. Dark background, one green accent for everything you can click, and the JetBrains Mono font.
Why
Nothing extra to learn or download. The font file is served from this site, so visiting doesn’t ping Google.
Alternatives
Tailwind (utility classes), a ready-made theme, Google Fonts.
show the colours
/* src/styles/global.css: colours are named by role, not shade */
:root {
	--bg: #0a0b0d;
	--text: #c9d1d9;
	--accent: #39f58c;
}

Homepage figure

The animation on the homepage. Your scroll position decides the figure’s pose.

Picked
A body made of CSS shapes, props drawn in SVG, moved by GSAP ScrollTrigger.
Why
The whole homepage is 97 KB, and shapes stay sharp at any size. GSAP handles the scroll timing and is free.
Alternatives
A 3D figure in Three.js (built, then dropped: about 600 KB and I liked the 2D one more), a video, Lottie.
show the code
// src/scripts/homepage.ts (simplified): one pose per scene,
// angles in degrees. Scrolling blends from one pose to the next.
const poses = {
	run:  { torso: 4,  armL: 40,  armR: -45, legL: 30, legR: -35 },
	bike: { torso: 72, armL: -32, armR: -34, bike: 1 },
};

Posts

The writing itself.

Picked
Markdown files in the code, one per post, checked by a schema.
Why
Posts are plain text with full version history. A typo in a topic name stops the build instead of publishing a broken page.
Alternatives
A CMS such as WordPress or Sanity, or a database.
show a post file
---
title: 'Hello, world'
description: 'Why I started this site'
pubDate: '2026-10-07'
topic: 'programming-ai'
---

The post, written in Markdown.

Hosting: Vercel

Keeps the site’s files on servers around the world (a CDN) and sends them to each visitor from the nearest one.

Picked
Vercel.
Why
It rebuilds the site on every push. Every branch gets its own preview link, so changes can be checked before they go live. HTTPS included, free.
Alternatives
Netlify, Cloudflare Pages, GitHub Pages.

Build: Astro

Turns posts and components into plain HTML, CSS and JavaScript files, once, before anyone visits.

Picked
Astro 7, starting from its official blog template.
Why
Made for content sites. It sends almost no JavaScript, and RSS, a sitemap and post pages come built in.
Alternatives
Next.js, Hugo, WordPress.
show the command
npm run build   # writes the finished site to dist/

Code: GitHub

Stores the code and every version of it.

Picked
GitHub, public repository.
Why
Every change can be undone. Vercel watches it, and each roadmap step got its own branch.
Alternatives
GitLab, Bitbucket.
show the command
git push   # Vercel sees this and deploys

Analytics

Counts visits: which pages, which countries, which devices.

Picked
Vercel Web Analytics.
Why
No cookies, so no cookie banner. One script tag.
Alternatives
Plausible, Umami, Google Analytics.
show the code
<script defer src="/_vercel/insights/script.js"></script>

Backend: none

A server that runs code on every visit, usually with a database.

Picked
Nothing. This site has no backend.
Why
A blog doesn’t need one. Nothing to run, pay for, or get hacked.
Alternatives
A Node server, Supabase, Firebase.

The short version: posts are MarkdownPlain text with a few symbols for formatting, like # for a heading and * for a list. files, Astro buildsA step that runs once, before anyone visits, and turns your files into plain HTML, CSS and JavaScript. them into plain files, GitHub stores the code, and Vercel sends the files to visitors from a CDNContent delivery network: copies of your site on servers around the world, so each visitor gets it from one nearby.. There’s no server running code and no database, so there’s very little to keep running or patch.

The figure

The homepage figure is made of plain shapes: rounded rectangles for the body, SVGAn image format made of shapes described in code, so it stays sharp at any size. drawings for the bike, desk, book and brain. A library called GSAP watches how far down the page you’ve scrolled and sets the figure’s pose to match.

The core idea fits in one sentence: scroll position is one number between 0 and 1, and every joint angle is worked out from it. Try it:

t = 0.00
Drag the slider. Every joint angle is start + (end − start) × t: standing at t = 0, running at t = 1.

The real figure does this for about twenty values at once (every joint, plus where it stands and which props are visible), across five scenes.

Two layers per joint1 minHow the scroll pose and the running motion avoid fighting each other.

Scroll sets the pose. But in some scenes the figure also moves on its own: legs stride while running, feet pedal on the bike, hands type at the desk. Those loops play on a timer, not with your scroll.

If both wrote to the same joint, one would overwrite the other. So every joint is two nested elements. The outer one gets the pose from scroll; the inner one gets the loop’s small extra swing on top. A loop only plays while its scene is on screen, and resets its inner layer to zero when it stops.

The bug that led to this: GSAP applies an animation’s starting values the moment it’s created, unless you tell it not to (immediateRender: false). For a while every scene had bent knees, because the running loop had quietly set them on page load.

Feet that stay on the pedals1 minInverse kinematics, in two lines of trigonometry.

When cycling, the pedal goes round in a circle and the foot has to stay on it. Instead of setting hip and knee angles by hand, the code knows where the foot must be and works backwards to the angles. That’s called inverse kinematicsInverse kinematics: working out the joint angles from where the end should be, like bending a knee so the foot lands on the pedal..

With two bones (thigh and shin) and a known distance from hip to pedal, the knee angle comes from the law of cosines. Then the hip angle is the direction to the pedal, corrected for the bent knee. That runs every frame, about sixty times a second.

The plan was a 3D figure, built with Three.js. Claude Code built it in one evening on a separate branchA separate copy of the code where you can try a change without touching the live site. (still on GitHub), with all five scenes, lighting and a real turn of the body. I looked at it next to the 2D one and kept the 2D one.

Left: the 3D figure from the Three.js experiment. Right: the 2D figure that’s on the homepage.

Building something just to throw it away sounds wasteful. Here it was a cheap way to decide: one evening of the agent’s time, and then I could compare the two side by side. The 3D version would also have added about 600 KB to a homepage that’s 97 KB now.

What I planned vs what shipped

Most of the plan changed once I saw it running.

planned or first built → shipped

Planned: 3D figure in Three.js

Shipped: 2D figure: CSS shapes and SVG

Why: built the 3D one, preferred the 2D one

Planned: no idle animation

Shipped: running, pedalling, typing loops

Why: I wanted it to move

Planned: keyboard shortcuts to open a topic

Shipped: buttons only

Why: I asked for buttons only

Planned: topics in the header

Shipped: topic chips on the blog page

Why: topics are part of the blog

Planned: a smiley face

Shipped: a superhero cape

Why: I didn’t like the face

Planned: SPEC.md, HANDOFF.md and hooks

Shipped: one file: docs/plan.md

Why: one file both Claudes read

How I worked with Claude Code

I used two Claudes. In the claude.ai app I talked through ideas and made decisions. Claude Code, in the terminalThe text window where you type commands instead of clicking., did the building: it reads the code, edits files, runs the build and pushes to GitHub. The two can’t see each other. The only link is a file in the repoRepository: the folder of code plus its full history of changes..

claude.ai chatDesignideas, decisions, questions
in the repodocs/plan.mdroadmap, decisions, build notes
Claude CodeBuildingcode, previews, commits
Two Claudes and a text file. Neither can see the other, only the file.

docs/plan.md holds the roadmap, the decisions and a log of every time the build departed from the plan. Claude Code updates it after each step. One lesson: when I asked the claude.ai project to review it while writing this post, it was reading a copy from before most of the build, with seven steps and none ticked. A shared file only works if both sides see the latest copy.

What made the biggest difference:

  • Standing rules. A CLAUDE.md file is read at the start of every session. Mine says: explain every change and the reasoning, ask before adding a dependency, one branch per roadmap step, never push without being asked, and open preview links in my browser.
  • A previewA private copy of the site, with its own link, built from a branch so you can check it before it goes live. for every step. Each step got a branch; Vercel built a private copy of the site for it; I looked; then it was merged.
  • Checking its own work. Before showing me a layout, Claude Code took screenshots with a headless browser (Chrome without a window) at phone, tablet and desktop sizes. The images in this post were made the same way.
  • A second agent in parallel. Analytics was built by another agent in its own copy of the repo while the first one was still polishing the homepage.
  • A second model as reviewer. When I said the bike proportions looked weird, a different Claude model (Fable) reviewed them: road-bike wheel size, a head tube, a fork that leans forward. The bike looks like a bike now. Fable also reviewed this post, and drew the map on the About page.
  • Permissions. A settings file lists what it can do without asking: build, run the dev server, read the git history, open links, and push. Anything else needs a yes.
show the permissions file
{
  "permissions": {
    "allow": [
      "Bash(npm run build)",
      "Bash(npm run preview)",
      "Bash(npx astro dev *)",
      "Bash(git status *)",
      "Bash(git diff *)",
      "Bash(git log *)",
      "Bash(open https://*)",
      "Bash(git push *)"
    ]
  }
}

Advice I got and didn’t use: plan mode, hooks, custom skills, and a cheaper model for routine edits.

The numbers

6272891011121314153416917
Commits per day, October 2026, before this post

Eleven days from my first question to this post, but most of the building happened in one evening: 34 of the 47 commits are from 16 October.

Claude Code works in tokensThe unit AI models read and write in. Roughly ¾ of a word.. It ran on Claude Opus 5.5 (with a few requests to Fable, the reviewer) under a Claude Max subscription. Over about 480 requests it wrote roughly 370,000 tokens: code, explanations, commands. Every request also sends the whole conversation and the relevant code again, which added up to about 122 million input tokens: roughly 330 read for every one written. Nearly all of that came from a cache, which makes re-sending it cheap.

Google’s Lighthouse scores the homepage 97 for performance on a simulated slow phone, and 100 for accessibility, best practices and SEO.

From a blank MacBook to a live site

Seven steps, plus an optional eighth. Each has the exact commands folded underneath.

  1. Install Homebrew, which installs other software for you. It also installs Apple’s command line tools, which include git.
show the command
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
  1. Install Node and the GitHub command line. Node runs Astro’s build tools; it never runs on the live site.
show the command
brew install node gh
node -v   # should be 22 or newer
  1. Make a GitHub account at github.com, then log in from the terminal.
show the command
gh auth login
  1. Start from Astro’s blog template and run it on your Mac.
show the command
mkdir -p ~/dev && cd ~/dev
npm create astro@latest my-website -- --template blog
cd my-website
npm run dev   # open http://localhost:4321
  1. Put it on GitHub.
show the command
git init -b main   # skip if the installer already made a repo
git add -A
git commit -m "Start from the Astro blog template"
gh repo create my-website --public --source=. --push
  1. Connect Vercel. Sign up at vercel.com with your GitHub account, choose Add New → Project, import the repo and press Deploy. A minute later you have a live link, and every push deploysPutting a new version of the site online. again.

  2. Install Claude Code and let it learn your project.

show the command
curl -fsSL https://claude.ai/install.sh | bash
cd ~/dev/my-website
claude   # then type /init inside it
  1. Optional: buy a domain. In Vercel, open Domains, search for a name and buy it; it connects to your project without touching any settings. Look at the renewal price, not the first-year price: some endings cost $2 the first year and $27 every year after. I went with shrey.cloud: $7.99 for the first year, about $24 a year after. .cloud leaves room to host other projects on subdomains later.
Things that bit us1 minThree of mine, two of Claude Code's, and one I avoided.
  • Pasting a long list of commands at once (me). One of them deleted some files, and zsh asked “are you sure?”. That question swallowed the rest of the paste.
  • Resetting git history (me). During setup I deleted the git history and started again. That’s only safe before you’ve pushed anywhere.
  • A minifier rewrote the CSS (Claude Code). The tool that shrinks CSS for the live site turned a short animation rule into animation: none, and the typing animation vanished. Writing the rule out in full fixed it.
  • SVG ignores hidden (Claude Code). The hover line on the About page’s chart stayed on screen, because the HTML hidden attribute does nothing inside an SVG drawing. One line of CSS fixed it.
  • The domain payment failed (me). There was no card on my Vercel account, so the first purchase of shrey.cloud didn’t go through. Adding one fixed it; nothing was charged twice.
  • Projects in ~/Documents (avoided, thanks to the chat). iCloud tries to sync that folder, including the thousands of files in node_modules. Use a folder like ~/dev.

Questions I asked along the way

I’m new to this. These are real questions from my chats, lightly tidied.

Should I move from the web chat to the terminal?

For building, yes. Claude Code can read and edit the files, run the build and see the errors itself. I kept the web chat for design discussions.

What is Vercel for?

It takes the code from GitHub, builds the site every time you push, and serves it from servers around the world. It also gives every branch its own preview link.

Is Node a backend?

Not here. Node runs once on my Mac or on Vercel to build the site. Visitors only ever get the finished files.

Wouldn't making my email address public cause spam?

Every git commit carries an email address, and in a public repo anyone can see it. GitHub gives you a private no-reply address for commits.

Why is deleting branches necessary?

It isn’t. Once a branch is merged, it’s just a label; deleting it loses nothing. It keeps the list short.

Is GSAP an alternative to Three.js, or do we use both?

They do different jobs. Three.js draws 3D scenes; GSAP changes values over time or with scroll. You can use them together. This site ended up needing only GSAP.

What are hooks?

Scripts Claude Code runs automatically at set moments, like before a command or when a session starts. Rules in CLAUDE.md are advice the model follows; hooks always run.

What is Lighthouse?

A tool built into Chrome that scores a page on speed, accessibility, good practice and search-engine friendliness, and lists what to fix.

Can we read visitors' cookies to see which other sites they visited?

No. A site can only read its own cookies; browsers block everything else on purpose. This site uses no cookies at all.

Since I first published this

I kept going the same day. What changed:

  • A domain. The site moved to shrey.cloud (step 8 above).
  • A review of this post. Fable and the claude.ai chat both read it. They caught a quote I never said, two plan-vs-shipped rows the record didn’t support, a link preview that showed the template’s stock picture, and a copy button covering code on phones. The chat also got one thing wrong: it said it had made up “seven steps, none ticked”, but the git history shows the 8 October plan had exactly seven unticked steps.
  • An About page with an interactive terminal (type help), a map from Mumbai to Amsterdam whose coastlines Fable drew by hand in SVG, and a chart of the Dutch and Indian stock indexes since I moved, fed by a small script that downloads monthly closes.
  • Smaller things: a green >_ tab icon, a button on the homepage that points here, empty topic pages that suggest the latest post, a 404 page, and a percentage on the reading progress bar.
  • Better scores. After fixing a colour-contrast problem in the anatomy diagram, Lighthouse gives the homepage, this post and the About page 100 in all four categories.

The counter kept running too: 69 commits (from 47), about 795 requests to Claude (from 480), roughly 198 million input tokens and 584,000 output tokens.

What surprised me

How easy it was, mostly. Claude’s taste was better than I expected, and Claude Code was more self-sufficient: it ran the build, checked its own work with screenshots, and opened previews in my browser. These agents can now drive a good part of a Mac on their own. And the animations on the homepage and the interactive parts of this post came out better than I’d hoped.

What’s next

Maybe a newsletter, other projects on shrey.cloud, and posts on the other topics: running, biking, psychology, quant trading and finance.


I chose what went into this post. Claude Code drafted it from notes the two Claudes wrote about the project, the git history and my chats with Claude.