---
title: The blog, taken apart
description: How the blog changed, and how the redesign brought its remaining components into the same system.
url: https://www.felixsanz.dev/articles/the-blog-taken-apart
language: en
published: 2026-07-28
updated: 2026-08-08
tags:
  - development
  - ux
alternate-es: https://www.felixsanz.dev/es/articulos/el-blog-por-dentro
---

# The blog, taken apart

## Introduction

In 2024, I stopped writing on the blog. Not because I had run out of ideas, but because I had started working on Runware's documentation and **it took up all the time I had**. This place sat idle and, before I knew it, a couple of years had passed.

Once I felt like experimenting and writing for myself again, I picked the project back up to launch the Lab, a new section for small experiments. One thing led to another, and I ended up redesigning the entire blog.

The old design worked, but I wanted to apply everything I had learned over the previous two years to the site. Some components still sat outside the system, and too many values had been chosen by eye. The goal was not just to change how it looked. I wanted **a more coherent system that was easier to modify**.

In this article, we are going to take it apart. First, we will look at what changed on the surface. Then we will get into the theme system and the color work required to replace violet with cyan and amber.

## The visual direction

I wanted a colder, more restrained aesthetic, with a very dark background and only a few color signals. During the process, I kept returning to the inside of the ship in *Interstellar*. It is my favorite movie, so I won't pretend to be objective about it. I didn't want to copy it or fill the site with spaceships, only to borrow that feeling of **a machine built to work**.

That is where the blog's two accents came from. Cyan follows everything you read or can click. It is the color that marks the path. Amber is reserved for specific moments where it can draw attention without competing with the content.

The home page is where the change is most obvious. The old version was friendly and colorful, with words highlighted in several tones and shapes floating around the photograph. Articles were laid out as cards. The new one is more restrained. The content is the focus again, and the articles form a single chronological list.

![The 2024 home page has colorful keywords and geometric shapes floating around a photograph. Articles appear in a grid of cards.](https://www.felixsanz.dev/assets/home-before.BVloWoxt_Z1dYzy4.jpg)

*The old home page*

![The new home page: a dark cockpit-inspired design with the tagline, a cosmic avatar, a mission clock, and every article in a single chronological list.](https://www.felixsanz.dev/assets/home-after.DmvP_EEZ_2wTIkH.jpg)

*The new home page*

### A clock that does absolutely nothing

In one corner of the home page is a small mission clock. It displays `T+…s`, the number of seconds since January 1, 1999, roughly when I started programming. I don't remember the exact date, so New Year's Day will have to do.

It has no practical use. **It is completely unnecessary**, which is exactly why it works. Before you read a single line, it has already told you what kind of place this is.

*[Mission clock]*

## The color lesson

The blog already used OKLCH, so I did not have to switch color spaces. OKLCH describes each color with three values: lightness, chroma, and hue. Its main advantage is that **lightness tracks our perception of brightness much more closely** than in other common formats. That makes adjusting contrast and building scales far more predictable.

For years, I followed a rule that seemed logical: if every accent shares the same lightness and chroma, and only the hue changes, they should all look like part of the same family.

The first half was true. The second was the problem.

### All equal, all flat

**Hues do not have the same chroma headroom**. The amount of saturation we can ask for before leaving the displayable gamut depends on both hue and lightness. If we force every color to use the same value, the entire palette is limited by the color with the least room.

Violet appears in this comparison because it was the main accent in the previous design. It has plenty of headroom, which is why my rule of keeping chroma constant never caused much trouble. Replacing it with cyan and amber put me in a much narrower part of the gamut.

The chart checks the maximum chroma each hue can have before the browser has to clip it to represent it in sRGB. I use sRGB because it is the common color gamut I can safely rely on for the web. At a lightness of `0.70`, these are the approximate values for the hues we care about:

| Hue | Approx. max chroma | Relative headroom |
| --- | --- | --- |
| Violet · 295° | ~0.18 | Wide |
| Amber · 79° | ~0.15 | Moderate |
| Cyan · 200° | ~0.12 | Narrow |

These are not universal limits. They change with lightness and also depend on the color gamut we are working in. The important part is the difference between them. **Asking for the same chroma does not guarantee equally vivid colors**.

You can test it in the next example. Keep lightness at `0.70` and switch to cyan. Then raise chroma above `0.12`. The requested value exceeds what sRGB can represent, and the indicator marks it as clipped. Violet still has room. The chart below plots that ceiling for every hue and shows that it is not a straight line.

*[Interactive: OKLCH gamut explorer]*

The answer is not to push every color to its limit, but to **adjust its chroma separately**. Accents can share a lightness that relates them to one another while each hue uses the intensity it needs and can actually reproduce.

In this palette, amber has slightly more chroma than cyan. It also appears far less often, so it can be more intense without taking over the interface. Cyan guides. Amber interrupts when there is a reason.

### The amber problem

The light theme introduces another problem. Amber is very close to yellow, and yellow needs plenty of lightness to retain its character. If we darken it too much to gain contrast on white, it turns into a muted olive.

I could shift the hue toward orange, but then the same signal would use two different colors depending on the theme. I preferred to **keep the hue and change the role it plays**. In the dark theme, it can behave like a small light. On paper, I use it more sparingly, like an ink mark.

79°

**Dark theme**`oklch(70% 0.14 79)`

Amber can behave like a light.

79°

**The same color on white**`oklch(70% 0.14 79)`

It stays amber, but loses too much contrast.

79°

**Light theme**`oklch(63% 0.132 79)`

Its lightness drops just enough to gain contrast and still look amber.

## From palette to system

Picking four pleasant colors is not enough to build a design. The interesting work begins when we define **what each color is responsible for** and how it relates to the rest. Then we decide what should happen when the theme changes.

### The hull

The dark background is an almost black petrol blue. It has enough color not to look gray, but not enough to compete with the star field and the orbits on the home page. Panels and borders stay within the same family instead of introducing new hues. Text uses an off-white to avoid the harsh contrast of pure white.

*[Color palette]*

> [!WARNING]
> **Contrast is always relative**
> Darkening the background made an entire layer of subtle borders disappear. Their values had been chosen to sit just below the previous background. Against the new hull, they were darker than the page itself and almost invisible. I had to raise them so they stood out again. When the floor changes, **the relationship of everything resting on it changes too**.

### Two themes, not three palettes

The blog already had a light theme and a dark one. The problem was that it kept three versions of the palette: one for light mode, another for dark mode, and a third for the default mode when the operating system preferred dark. `default` was not a third theme, but the CSS treated it like another copy that had to stay in sync.

Now **each color is defined once**, with both values inside `light-dark()`. The `color-scheme` property decides which one to use. If you do not choose a theme, the browser follows the operating system preference. If you use the blog's selector, `color-scheme` is fixed to light or dark.

```css
/* Before */
[data-theme="light"] { --color-text: oklch(44% 0.01 285deg); }
[data-theme="dark"] { --color-text: oklch(79% 0.01 85deg); }

@media (prefers-color-scheme: dark) {
  [data-theme="default"] { --color-text: oklch(79% 0.01 85deg); }
}

/* Now */
:root {
  color-scheme: light dark;
  --color-text: light-dark(oklch(44% 0.01 285deg), oklch(79% 0.01 85deg));
}

[data-theme="light"] { color-scheme: light; }
[data-theme="dark"] { color-scheme: dark; }
```

To modify a color, I edit one line and see both versions together. The default mode still works, but it no longer contains its own palette. Only a few non-color variables remain duplicated because `light-dark()` cannot be used for them. **Most of the third block has disappeared**.

### One scale for intensity

Text and interface elements use the same conceptual scale: `prominent`, `strong`, `regular`, `quiet`, and `subtle`. There is one version for text and another for lines and surfaces, but both answer the same question: how much visual weight does this piece need?

Surface · element-\*

Text · text-\*

prominentstrongregularquietsubtle

The steps are not five colors picked by hand. They start from an anchor and are produced by changing its lightness. In the light theme, increasing intensity means going darker. In the dark theme, it means the opposite. The scale keeps its meaning even though the operation is reversed.

There used to be names like `hover` and `selected`. They seemed descriptive until the same value proved useful for an element that was neither selected nor under the pointer. Naming by intensity avoids that lie: **a color does not know which component uses it, only how much it should stand out**.

Semantic colors (`success`, `warning`, `error`, `info`, and `note`) sit outside this scale. They do not represent levels of intensity, but different meanings at the same hierarchical level.

> [!NOTE]
> **Meaning is not hierarchy**
> Semantic colors share the same treatment so none of them dominates by default. Green should not seem more important than red, nor should a note feel more urgent than a warning. The content decides the hierarchy. Color only explains **what kind of message we are looking at**.

### A token is a decision

The blog already had tokens and scales built on OKLCH. The problem was that not everything used that system. Some components had solved their needs separately and remained outside it.

Form controls were the clearest example. Each had ended up with its own spacing, border, `hover` state, and focus ring. None was disastrous on its own, but building the next control meant choosing between several almost identical values without knowing which one represented the right decision.

Field

Select

Option AOption B

Range40

OneTwo

RadioOption

CheckboxToggleButton

Now they all share the same geometry and states. This does more than make them look alike. It lets me change a decision from the place where that decision lives.

That is the important distinction. **A token is not a shared value but a shared decision**. Two elements may happen to have the same radius or spacing today. They should only use the same token if changing one tomorrow should also change the other.

A palette collects values. A system collects relationships and decisions. The redesign did more than change how the blog looks. It also reduced the number of places I need to touch to modify it and, in most cases, **that number is now one**.

## Less surface, more clarity

When I brought the system into the articles, I followed a simple rule: **if an element is already distinguished by its background, it does not also need a border that repeats the same separation**.

Code blocks use a filled surface, with the title presented as a `//` comment, and no frame. Callouts follow the same principle. The background color already shows where they begin and end. Their labels explain what they mean.

In dark mode, **the code background is slightly lighter than the page**. I wanted it to read like an illuminated panel on the hull, not like an even darker hole. It is important content, so it deserves more presence than the page background.

Code also **separates brand colors from colors that communicate meaning**. A highlighted line uses semantic blue, and diffs use green or red. Cyan remains reserved for reading and navigation. That gives each color a recognizable role instead of turning it into decoration available for anything.

## The details matter too

The system sets the direction, but the small details are what give it character.

When you hover over a link, a point of light travels along the underline like a small comet. Headings display their Markdown depth (`##`, `###`) in the margin, and horizontal rules become `//`. Ordered lists use monospaced numbers. In unordered lists, the marker stretches slightly as you hover over each item.

The title of each page does not appear all at once either. Its letters pass through a short sequence of glyphs before settling into the final text. To keep the animation from jumping twice, it waits for the font to be available before starting, with a timeout in case loading takes too long.

The blog, taken apart

None of these details is essential. Together, however, they **make the blog behave like the same object everywhere**. The more noticeable animations respect `prefers-reduced-motion`, because a visual identity should not come at the expense of a visitor's comfort.

## A place to experiment

The latest addition is the [Lab](https://www.felixsanz.dev/lab), a space for small experiments. It shares the same visual system as the rest of the blog, but changes the main activity. You do not go there only to read, but also to try things.

Right now, many of the experiments explore what AI models can and cannot do. I do not want to turn it into a section exclusively about artificial intelligence. Its purpose is precisely to leave room to build something out of curiosity and learn enough to explain it.

The experiments do not end with an isolated demo either. **I publish the code** and **pair each project with an article that takes the project apart**. In that way, the Lab feeds the blog, and the blog turns those tests into something another person can understand and reuse.

## Conclusion

The most visible part of this work is the new aesthetic: the dark hull, cyan, amber, and all the small cockpit details. But **the important change sits underneath**. Themes now share definitions, scales express intensity, controls start from the same geometry, and each token represents a decision that deserves to change as a unit.

Something else did not change: the typeface. It is still round and approachable against all that cold instrumentation. I like that contrast because it neatly captures what I am trying to do here: **explain how things work and still sound like a person**.

**A warm voice inside a cold machine.**
