---
title: "Rebuilding this site around one background"
description: "How the portfolio got faster by deleting almost everything: one canvas for the whole site, one design system, and a GitHub calendar that ships no JavaScript."
canonical: "https://parthkapoor.me/blog/building-this-site"
last-updated: "2026-09-12"
author: "Parth Kapoor"
---

# Rebuilding this site around one background

The previous version of this site had twenty-one dependencies, three webfont
families, two full copies of the home page layout in the DOM, and a particle
canvas that was torn down and re-seeded on every navigation. It looked fine.
It did not feel fine.

This is what changed, and why.

## One background, mounted once

The flow field was the best thing about the old site and the most expensive.
It lived inside the page component, which meant React unmounted it on every
route change and a fresh canvas started from an empty buffer — so the trails
you had just been watching vanished, and the whole field re-seeded.

It lives in the root layout now:

```tsx
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Backdrop />
        {children}
        <Mascot />
        <Dock />
      </body>
    </html>
  )
}
```

The layout survives client-side navigation, so the field is seeded exactly
once per session. Nothing about the background changes when you move between
pages — which is precisely why moving between pages now feels like moving
inside one site instead of loading four.

A few other things came out of that rewrite:

- It does not start until the browser is idle after first paint, so it never
  competes with LCP.
- Device pixel ratio is capped at 1.25. It is an out-of-focus haze; a 3x
  retina buffer is 5.7x the fill cost for nothing you can see.
- Trails are erased with `destination-out` rather than repainted with the
  page colour, so the canvas stays transparent and the CSS glow underneath
  is not slowly painted over.
- It parks itself after a few seconds of nobody touching anything, and wakes
  on pointer, scroll or resize.

## Page transitions are a CSS file

App Router has a `template.tsx` that, unlike `layout.tsx`, remounts on every
navigation. That is the entire transition:

```tsx
export default function Template({ children }) {
  return <div className="page-in">{children}</div>
}
```

```css
@keyframes page-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
```

No animation library, no route-change listener, no JavaScript. It is
enter-only on purpose: an exit animation has to block the navigation in order
to play, which makes a site feel slower even as it looks smoother.

## The contribution graph ships no JavaScript

The old calendar used a client-side library. That means a bundle, a request
on every single visit, a spinner, and a layout shift when the data lands.

The replacement fetches on the server and lets Next cache it for an hour:

```ts
const res = await fetch(`${CALENDAR_API}/${login}?y=last`, {
  next: { revalidate: 3600 },
})
```

The grid is then 371 `<i>` elements with two custom properties each, coloured
by `color-mix` and staggered by week:

```css
.calendar > i {
  background: color-mix(
    in srgb,
    var(--color-accent) calc(var(--l) * 24%),
    var(--color-surface-2)
  );
  animation: tile-in 420ms var(--ease-out) both;
  animation-delay: calc(var(--w) * 8ms);
}
```

It is in the HTML of the first paint. There is nothing to hydrate.

## Deleting the design system that wasn't one

The real problem was never any single component — it was that there were
eleven ways to draw a card. The fix was a `@theme` block: four surfaces, four
inks, one accent pair, one type scale, two easings. Every border on the site
is `--color-rule`. Every card is `.panel`. Every technology icon resolves
through one registry, so "Go" is the same glyph on the stack card, the
timeline and a project tile.

That is not a visual change so much as a structural one, and it is the reason
the whole thing now fits in about a third of the source it used to need.
