Five landing pages, one family: making a studio's sites look related
- design
- css
- landing-pages
- design-systems
- astro
Every one of my apps has a small “by Karakabakov” link in its footer. Until this week, clicking it on hourslip.app took you from a black page with a violet glow to a white page with soft grey tiles and a different opinion about what a button is. Same person, same typeface. It looked as if the apps had been made by a studio and the studio’s website had been made by its landlord.
So I moved my site onto the system the product sites use. This is how that went, and what I would do sooner if I started again.
How did they drift apart?
One site at a time, between the 7th of September and the 1st of October. Cleat’s site went up first, then Retriever’s, then HourSlip’s, and SnagFrog’s landing page arrived this week inside a SvelteKit app, in rem units, where every other site counts in pixels. Each one was built from whichever of the others I had open at the time.
Meanwhile my own site had just been redone in white, because I liked the look I had made for Capta. Capta’s site is still white. It is the cousin who missed the memo, and I wrote the memo.
When I finally read the four product sites’ CSS side by side, they agreed on more than I
expected. Every nav bar is rgba(0, 0, 0, 0.72) over a blur, 64px tall. The main button
is a white pill everywhere, and three of the four have a secondary button that says “See
how it works”. Underneath it’s all pure black, with panels around #0b0b0d and 1px
hairlines.
They disagreed on the details nobody decides on purpose. SnagFrog blurs its nav at 14px and the others at 18px, which no human will ever notice. SnagFrog also sets its headings at weight 650 where the other three use 500, and that one you notice straight away when you flick between tabs. Flicking between tabs is precisely what someone following a footer link does.
Read the CSS, not the screenshots
The most useful hour of the job was the boring one: opening each stylesheet and writing the values down. Background, panel, two border colours, three text greys, the accent, heading weight and tracking, content width, section spacing, button padding, and the radius of everything.
Screenshots are no help here. Two greys four steps apart look identical in a screenshot and different on a real screen at 2am. A 14px blur and an 18px blur look the same anywhere. Once the numbers are in a list the decision gets small: pick one site’s values and call it the reference.
I picked HourSlip’s, because Retriever’s and Cleat’s already matched it almost value for value. Two out of three agreeing is as close to a design committee as a one-person studio gets.
Tokens, and keeping the old names
The reference values went into one block of custom properties: --bg, --panel,
--line, --ink and two lighter inks, --accent, a radius or two. Everything else reads
those.
My site had a complication. About forty files were written against the previous look’s
vocabulary: .panel, .block-card, .btn-solid, var(--paper), var(--mute). Renaming
them would have touched every one of those files and changed nothing a visitor can see. So
the names stayed and the values changed, which is how --paper came to be #000000. I
know. There is a comment above it saying the names are history, which is the comment you
write when you have decided to live with something.
That is why the move took one evening, with Claude Code doing most of the typing, and not a week. The inner pages kept their markup and simply started reading different values.
What each product keeps
The goal was never five identical sites. You should be able to tell HourSlip from Retriever at a glance, and also tell they come from the same place.
So the structure, the type, the greys and the buttons are shared, and the accent is the
one thing each product keeps. HourSlip is violet (#8b7cff), Retriever is lime
(#c6f03a), SnagFrog is azure (#3e8bff) and Cleat is orange (#ff8a3d). My own site
went with a softer blue, #6e8bff, chosen to sit next to SnagFrog’s without looking like a
typo of it.
On my site, each product page writes its accent onto :root from the product’s data. The
glow behind the screenshot, the gradient word in a heading and the focus ring all pick it
up, so /retriever glows lime and /hourslip glows violet. It is one line of data per
product and no per-page CSS.
The accent stays small on purpose: a glow, a label above a heading, a focus ring. It is never the button.
Why the buttons are white
On a black page, white is already the loudest thing available. A white pill doesn’t need a colour to be found, and it looks the same on every product’s site, which is the thing a returning visitor’s hand learns first.
Across the five sites there are now exactly two buttons. The primary is a white pill with black text, and the secondary is a dark panel with a hairline border. Each view gets one primary. If a section seems to need two, it has a decision to make.
The labels follow one rule as well: a verb, then what you get. “Download the beta”, “Start free trial”, “See how it works”. Never “Learn more”, which in the history of the web has not once told anyone what they would learn.
One hero
The product sites already opened the same way, so I wrote the order down and made my own site follow it:
- A small pill with a green dot saying where it runs, or what the trial is.
- The headline.
- One paragraph about what the thing does.
- Two buttons.
- A line of fine print.
- A real screenshot in a window frame, over a blurred glow in the accent colour.
My home page used to open with a MacBook drawn in CSS. Its lid opened as you scrolled, until the camera flew into the screen. I was fond of it. It was also 2,192 lines of CSS, and none of the product sites spends its first screen on a show. The home page’s stylesheet is 274 lines now. The MacBook is in git, where it can be fond of itself.
Deciding what gets smaller
A studio site has a problem the product sites don’t: everything on it is yours, and everything wants to be a big tile. I had nine app tiles and eleven browser games, all at the same weight, so none of them had any.
Five apps lead now, as large tiles each with a looping animation of what the app does. The other apps are one list with a line each, and the games are a row of chips. This was the hardest call of the whole redesign and it had nothing to do with CSS. A design system will make your pages consistent. It will not tell you which of your children goes on the poster.
Show what the app does
The featured tiles started out with screenshots. At tile size a screenshot is a picture of a window: a sidebar, some rows and a toolbar, all too small to read. I swapped them for small looping animations in HTML and SVG. HourSlip’s timer ticks to 1:30:00, someone presses Stop, and the time turns into a $180.00 invoice line. Retriever sends a request and the response comes back. A SnagFrog report arrives, gets fixed, and closes.
There is a rule for these, written next to the data: everything an animation prints is something the app really does, or a word its own page uses. Loops are very good at inventing features, and the rule is there to stop that.
The full screenshots stay on each product’s own page, where you are close enough to read them.
Motion that fails quietly
The scroll effects are CSS scroll-driven animations, with no JavaScript. The hero frame stands up from a tilt as you scroll, the frames behind it slide out, and the tiles rise one after another.
Two rules keep this from becoming a liability. Every animation runs towards the element’s
resting state, so a browser without scroll timelines just shows the finished page. And all
of it sits inside a prefers-reduced-motion: no-preference query, so anyone who asked
their system for less motion gets the still version. The page has to work before anything
on it moves.
Checking it
The greys were picked for contrast on black rather than by eye. Headings and strong text are 19.3:1, body copy 8.2:1, the dimmest grey allowed for small print 6.1:1, and the blue accent 6.8:1. The white look had the same fight from the other side: its lightest grey had to be darkened to pass. Small print always starts that argument.
Then I built the site, ran the tests and opened every kind of page at desktop and phone width. My site has tests for the markdown copies of each page, the structured data and the links, and they did not care in the slightest that everything had turned black. That is what you want from tests.
What I would do sooner
If you run more than one landing page, the cheapest time to unify them is before the second one exists. Copying the first site’s CSS is fine. Copying it and never putting the two side by side again is how you end up with two heading weights and a cousin in white.
In order:
- Write the shared values down once, as custom properties, before the second site.
- Decide what each product keeps for itself (for me, only the accent) and share the rest.
- Settle the buttons: one primary style, one secondary, verbs on both.
- Settle the order of the hero and stop reinventing the top of the page.
- Decide what is featured and make everything else smaller, out loud and on purpose.
- Put every site’s CSS side by side once a quarter. It takes an hour, which is less than a rebrand.
The obvious next step: each site still carries its own copy of those values, so the next drift is only a matter of time. Pulling them into one shared file that every site imports is on the list now.