← Work

07 / 07 · Studio · 2026

View
Source

The website you are reading, explained by the website you are reading.

Isometric illustration: a dark code editor and a browser window arranged on a beige stage, with a red sphere and a red cube as accents.

This is a project because the website is custom-coded, end to end. The website is the one you are reading. Nothing inside it came out of a template. I would not know how to use one.

The design system

Six colors. Two typefaces. One grid. A small, deliberately bounded vocabulary.

Color

--paper #fafaf7 page background
--paper-warm #f5f4ee about-card background
--ink #1a1a1a primary text
--ink-soft #555555 secondary text
--ink-faint #888888 meta + microcopy
--red #a01818 active state only

Red is reserved for the active state. Hover, scroll progress, the italic word in a project title, the current page in the nav. Nothing else. Its rarity is the point.

Type

Aa
EB Garamond Variable editorial · headlines, body, captions, drop caps 100 → 800 · italic
Aa
Hanken Grotesk Variable interface · nav, labels, buttons, meta 100 → 900

Self-hosted, variable. One file per family covers every weight and italic. The whole typography payload is about 200KB.

Grid

Mobile-first. The column is narrow by default and only earns more width when the viewport explicitly proves it can hold it.

Type scale

  1. 76 px
    Aa
    drop cap (Notes article opener)
  2. 48 px
    View Source
    project cover title
  3. 32 px
    The design system
    section heading
  4. 19 px
    Body text, set in Garamond.
    article body · notes
  5. 17 px
    Italic dek above the kicker.
    dek · tagline
  6. 13 px
    caption italic
    figcaption · photo numerals
  7. 10 px
    SECTION LABEL
    nav · meta · subheadings
Seven sizes. Two families. Every line of text in the site sits on this scale.

I am quietly proud of having built the whole thing myself. The advantage of building a website yourself is you get to show people exactly how it works. That is what the rest of this page is.

The stack

Built with Astro, a framework that turns the files I write into static HTML pages. Static pages load fast, do not require a server to be alive somewhere, and have the wonderful property of working when most other things on the internet do not.

There is no Tailwind. No styled-components. No CSS-in-JS. I wrote roughly 1,400 lines of plain CSS, the way people wrote CSS in 2008. Variables, media queries, the cascade. It is fewer lines of code than most React component libraries import before they render a button.

The fonts are EB Garamond for prose and Hanken Grotesk for the interface bits. Self-hosted, variable. The whole typography payload is about 200KB, which is the size of a single uncompressed phone photo.

The favicon is a red heart emoji. It is non-negotiable. I am not interested in feedback on the favicon.

9
pages
23
components
0
tracking scripts
0
cookie banners
0
subscribe popups
1
favicon (a heart)
The whole site, accounted for.

Trackers, compared

  1. This site 0
  2. Average personal blog 14
  3. Average news website 35
  4. Heavier ad-driven sites 80
The average website you opened today loaded thirty tracking scripts before you finished the first sentence. This one loaded none.

What's in the page

0
KB
  1. Fonts 200 KB 52%
  2. Images 140 KB 37%
  3. CSS 22 KB 6%
  4. HTML 12 KB 3%
  5. JS 8 KB 2%
A typical first visit. After this, the fonts come from cache.

Page weight, compared

  1. This site 382 KB
  2. Average personal blog 2.5 MB
  3. Average news article 6.0 MB
  4. Average e-commerce home 4.5 MB
About one phone photo. The other rows are mostly ads, trackers, and JavaScript loading more JavaScript.

How this page got here

  1. Markdown src/content/
  2. Astro build npm run build
  3. Static HTML CSS · JS · images
  4. Netlify CDN edge nodes
  5. Your browser this tab
Source on the left. You on the right. Everything in between happens once, automatically, in roughly ninety seconds.

Other things that take about 90 seconds

  1. One TikTok 60 s
  2. This site, rebuilt from source 90 s
  3. Brushing teeth (dentist-approved) 120 s
  4. Microwave popcorn 180 s
The whole site, rebuilt and uploaded, in less time than it takes to brush your teeth correctly.

The file tree

This is the source tree. Most production websites have more files in their cookie-consent dropdown than this site has in total.

src/├── assets/│   └── about/jacob-portrait.jpg├── components/│   ├── AboutCard.astro│   ├── AboutExpanded.astro│   ├── Article.astro│   ├── CardStack.astro│   ├── CodeBlock.astro│   ├── FileTreeBlock.astro│   ├── MiniDeck.astro│   ├── Nav.astro│   ├── ProjectCoverCard.astro│   ├── ProjectHeader.astro│   ├── StatsBlock.astro│   ├── TextBlock.astro│   └── TypewriterText.astro├── content/│   ├── about/index.md│   ├── notes/│   └── work/├── layouts/Base.astro├── pages/│   ├── index.astro│   ├── notes/│   └── work/├── scripts/stack.ts└── styles/    ├── components.css    └── global.css

A small thing I am proud of

Here is the code that runs when you tap a project on the homepage. The card you tap zooms gently toward you. The other cards fly off in random directions. The page navigates after half a second.

It is eighteen lines of JavaScript. It took longer than the entire About page.

function playDispersal(selectedCard) {
  const all = stackEl.querySelectorAll('.card');
  all.forEach((c) => {
    if (c === selectedCard) {
      c.style.transform =
        'translateY(0) scale(1.15) rotate(0deg)';
    } else {
      const sign = Math.random() < 0.5 ? -1 : 1;
      const dx = sign * (380 + Math.random() * 260);
      const dy = (Math.random() < 0.5 ? -1 : 1)
        * (160 + Math.random() * 220);
      const rot = sign * (14 + Math.random() * 22);
      c.style.transform =
        `translate(${dx}px, ${dy}px) rotate(${rot}deg)`;
      c.style.opacity = '0';
    }
  });
}
src/components/CardStack.astro

That little animation also broke, briefly, when users hit the back button on iOS Safari. Safari caches the page in the exact state you left it, which includes the inline transforms from the dispersal that was in progress when you navigated away. I discovered this approximately five minutes after a friend posted the site on Instagram.

I am not going to talk any further about iOS Safari.

The bug, the fix

  1. Tap project dispersal plays
  2. Project page user reads, scrolls
  3. bfcache restore deck stuck mid-flight
  4. pageshow handler reset, ready again
A friend texted me my site was broken. Two hours to find. Twelve lines to fix.
window.addEventListener('pageshow', (event) => {
  if (!event.persisted) return;
  isExiting = false;
  const allCards = stackEl.querySelectorAll('.card');
  allCards.forEach((c) => {
    c.style.transition = 'none';
  });
  stack.update();
  requestAnimationFrame(() => {
    allCards.forEach((c) => {
      c.style.transition = '';
    });
  });
});
src/components/CardStack.astro — the twelve lines

Lines of code, compared

  1. This fix 12 lines
  2. A typical UI component 80 lines
  3. A typical CMS plugin 800 lines
  4. A typical app privacy policy 1,500 lines
The fix is shorter than the legal text most apps make you agree to before opening them.
The same dispersal, here, on loop, until you scroll past.

The publishing workflow

When I push code to GitHub, Netlify notices, runs npm run build, and uploads the result to a content-delivery network. The whole process takes about 90 seconds. I have watched it happen approximately 400 times.

There is no content management system. The blog posts are markdown files in the same repository as the code. To publish a Note I write a file and push it. To edit one I edit the file. There is no admin panel and no draft state and no preview link. The trade-off is that I can only post when I am at a computer with the repository cloned. I have decided I am okay with this trade-off.

The shape of the site

Every page on this site is a real URL. They each emit their own title, meta description, Open Graph image, and canonical link. Nothing is rendered client-side. Nothing requires JavaScript to read. The sitemap below is the entire site, drawn out.

The sitemap

jacobsarasohn.com /
Work /work
Notes /notes
Rampart Medical /work/rampart-medical
Lights Out /work/lights-out
Life on the Frontline /work/life-on-the-frontline
On the Bike /work/on-the-bike
Turbo S /work/turbo-s
View Source /work/view-source
The Fifty-Fifty Problem /notes/the-fifty-fifty-problem
Microdosing Creativity /notes/microdosing-creativity
11 pages · 100% indexed · 0 broken links
Ten URLs. Each one indexable. Each one cacheable. Each one survives a refresh.
0
Performance
0
Accessibility
0
Best Practices
0
SEO
Lighthouse, mobile. The trick is to have a small site.

The honest part

I have now spent more time writing about this website than you will spend on it. This is a known issue.

The honest version of this page is that making a website by hand is one of the only ways left to fully control a small piece of the internet. The tools are free. The fonts are free. The hosting is roughly free. The whole thing fits inside one repository on one developer's laptop and yet is, for the moment, on every computer on earth that wants to look at it.

Right-click anywhere on this page and select View Source. That is the website. The whole thing is right there.