Hold tightHi there!

Recently, I have been playing around with grids, gutters, and margins. I had a lot of fun trying out different compositions and seeing how a grid affects elements and the overall feel of a page. For this portfolio, I went with an 8-column layout with adjacent columns and no gutter.

A portfolio built strictly on a rigid grid often feels cold, more like a spreadsheet than a personal space. On the other hand, if you throw structure out the window completely, things get messy fast. My goal was to find a middle ground: use the grid as an invisible baseline for order, while giving elements enough freedom to shift, stagger, and feel alive.

Starting with one single unit

Instead of grabbing a standard 12-column framework with random rem margins, I wanted the entire layout to grow out of one single number. Every measurement on desktop starts with this CSS variable:

scss
:root {
  --container-spacing: 2.5rem;
  --box-size: calc((100vw - var(--container-spacing) * 2) / 8);
  --font-s-h1: var(--box-size);
}

The desktop screen splits into an 8-column grid (.main-grid-8) with zero gap between columns. Because my main display font size (--font-s-h1) is tied directly to --box-size, the height of a headline matches the exact width of a single column. The type doesn't just float on top of the layout—it shares the exact same proportions as the grid underneath it.

8-column grid blueprint with h1 matching the height of an individual cell
8-column grid blueprint with h1 matching the height of an individual cell

Bending the vertical line

Once the horizontal lanes were locked in, I started bending the rules on the vertical axis. Instead of lining project cards up in neat, predictable rows, I pushed them up and down using fractions of that same --box-size unit:

scss
.collection-card {
  grid-column: span 2;

  &:nth-child(6n - 5) { grid-column: 1 / span 2; }
  &:nth-child(6n - 4) { grid-column: 4 / span 2; margin-top: calc(var(--box-size) * 1.8); }
  &:nth-child(6n - 3) { grid-column: 7 / span 2; margin-top: calc(var(--box-size) * 0.7); }
  &:nth-child(6n - 2) { grid-column: 1 / span 2; margin-top: var(--box-size); }
  &:nth-child(6n - 1) { grid-column: 4 / span 2; margin-top: calc(var(--box-size) * 2.4); }
  &:nth-child(6n)     { grid-column: 7 / span 2; margin-top: 0; }
}

Staggering the top margins immediately breaks that flat horizon line. Still, static offsets were only half of what I wanted; the layout needed to move.

Collection page cards
Collection page cards

By giving neighbouring columns different parallax speeds (data-parallax-speed="0.8" vs 0.2), cards slide past each other at different paces as you scroll. They stay strictly inside their horizontal lanes, even as their vertical positions shift around you.

Turning the Blueprint into a Playground

On most pages, the grid just does its job quietly in the background. On the About page, though, I wanted to pull the curtain back and put the grid front and center.

scss
.hero-about-contain {
  --about-box: calc(var(--box-size) * 0.5);
  display: grid;
  grid-template-columns: repeat(16, 1fr);
  grid-template-rows: repeat(8, 1fr);
}
The 16×8 matrix on the About page with interactive flip cards
The 16×8 matrix on the About page with interactive flip cards

Here, the hidden framework becomes a visible 16×8 grid of square tiles. Rather than leaving it as a static wireframe, I turned a few of those tiles into 3D flip cards with subtle sound effects. The layout skeleton stops being just a structural tool and turns into something you can actually click and play with.

Wrapping up

A grid shouldn't force every piece of work into identical boxes. When you treat it like a quiet rhythm in the background instead of a strict rulebook, you can let things drift, speed up, or sit in empty space, knowing the whole page will still hold together.