This site is deliberately small. A handful of static pages, one stylesheet, nothing shipped to the browser to run. Here is how it fits together, and the design system it runs on, shown with real values.

The stack

Static HTML and CSS, and nothing client-side to run. I write the pages in Eleventy, a static site generator, and it produces plain HTML at build time. The build runs on my machine and I commit the output, so the deploy step only copies files. No build server, no surprises in CI.

A few choices keep it quick:

  • One stylesheet, inlined into the head of every page. The browser never blocks on a separate CSS request.
  • Fonts self-hosted as woff2: Poppins, two subsets, three weights.
  • The hero image gets a high fetch priority, so the biggest thing on the screen starts loading first.

That is most of the performance story. The pages hit 100 on mobile Lighthouse, and they get there by not loading things they don't need, not by anything clever.

Why tokens

Colour and corner radius spread fast, even on a site this small. Every new element is a chance to invent one more value that's almost like the last. So they all live in one place. Every colour, every radius, every shadow is a CSS variable, defined once at the top of the stylesheet, and nothing else in the file hardcodes a value.

Change the accent in one place and the whole site follows. Building a new component becomes a pick from a fixed menu. What comes next is that menu, pulled straight from the live variables.

Colours

The accent is a cyan pair. Cyan-700 does the work that needs contrast, links, buttons, the highlighted words in headings. Cyan-600 is a step lighter and only shows up in decoration, the slashes in the logo, the shape behind the photo, the focus ring.

  • --accent
    #0891b2
    Decorative: logo, blob, focus ring
  • --accent-dark
    #0e7490
    Links, buttons, eyebrows

The neutrals are a slate ramp, cool greys that sit next to the cyan without fighting it. Ink for headings and the dark bands, a slightly lighter slate for body copy, and a mid grey for secondary text. All of them clear AA contrast on white.

  • --ink
    #0f172a
    Headings, dark bands
  • --ink-2
    #1e293b
    Body copy
  • --muted
    #64748b
    Secondary text

Surfaces stay quiet. White for the page and the cards, a barely-there slate tint for sections and chips, a soft cyan only behind the photo, and one line colour for every border.

  • --white
    #ffffff
    Page and cards
  • --surface
    #f8fafc
    Tinted sections, chips
  • --soft
    #ecfeff
    Hero photo tint
  • --line
    #e2e8f0
    Borders

The dark sections (the stats band, the final call to action, the footer) get their own small set, so text on dark stays readable and borders stay subtle.

  • --ink-deep
    #020617
    Footer background
  • --text-on-dark
    #cbd5e1
    Body text on dark
  • --text-on-dark-muted
    #94a3b8
    Muted text on dark
  • --line-on-dark
    white 18%
    Borders on dark
  • --overlay-on-dark
    white 8%
    Hover fill on dark

Radius

Two working sizes and a pill. Small for buttons and chips, large for cards, the pill for tags. One-off signature shapes get their own tokens, marked do not reuse, so they stay out of the working scale.

  • --radius-sm
    Buttons, chips, nav, faq
  • --radius-lg
    Cards, large surfaces
  • --radius-pill
    Tags, fully rounded

Shadows

Two elevations and the button glow. A soft one for cards at rest, a deeper one for hover and anything that floats. Tinting shadows with the ink colour, rather than pure black, keeps them feeling part of the page.

  • --shadow-sm
    Resting cards
  • --shadow
    Hover, floating
  • --shadow-btn
    Primary button glow

Type

One typeface, Poppins, self-hosted. Headings at 700, body at 400, the occasional label at 600. Sizes scale with the viewport using CSS clamp, so the big headline shrinks on a phone without a stack of breakpoints behind it.

Headings, Poppins 700 Labels and eyebrows, Poppins 600 Body copy, Poppins 400, the weight you're reading now.

The card

The card is the workhorse block, one component reused wherever a piece of content needs a frame. White surface, a one pixel line, the large radius, the resting shadow, and 1.8rem of padding inside. On hover it lifts a little and the shadow deepens. Try it.

Component

This is a card

Built only from tokens: --white surface, a --line border, --radius-lg corners, --shadow-sm at rest, deepening to --shadow on hover.

An example link

Nothing in it is bespoke. Because the card is made from tokens, it matches everything else for free, and a new one never needs a fresh colour or a new kind of corner. That's the payoff of keeping the menu small.

Spacing and the box model

A strict spacing scale earns its keep on a bigger product. On a small site a handful of consistent values do most of the work, and reaching for one of them beats inventing a new number each time.

  • 24px Page gutter, the side breathing room on every section, set on .container.
  • clamp(3.25rem, 7vw, 6rem) Space above and below each section, smaller on a phone.
  • 1.8rem Padding inside a card.
  • ~1.4rem Gap between cards and grid items.

Two habits keep this predictable. Everything uses border-box sizing, set once in the reset, so padding and border count inside an element's width. A full width box with padding stays full width, it just gains room inside.

The reset also zeroes every default margin, then space goes back on in one direction. Headings and paragraphs push room below themselves, not above. So when two elements meet, their margins don't double up, the gap is whatever the lower element asked for.

Breakpoints

The layout is mostly fluid. Type and spacing scale with the viewport using CSS clamp, so there are only two hard breakpoints where the structure rearranges.

  • ≤ 860px The hero drops to one column with the photo on top, and the nav collapses into a burger menu.
  • ≤ 540px The base font eases down a touch, the stats and cards go to one column, and the hero buttons stretch to full width.

Paired buttons don't need a breakpoint to behave. Split the row into equal halves so they sit side by side while they fit, and let them wrap to full width once the row is too narrow. They stay equal at every size.

The one rule

Reuse a token, don't invent. If a value genuinely isn't there yet, add it to the top of the stylesheet first, then reference it. One small set, used everywhere. That's the whole idea.

This is the same habit I bring to other people's code. Most apps don't get messy on purpose, they grow without a system until small decisions pile up. If yours is at that point, that's the kind of thing I help untangle, with my team at Fingoweb behind the fixes.

Book a 30-minute call Other ways to reach me

FAQ

What generator does the site use?
Eleventy. It turns templates and markdown into static HTML at build time. I build locally and commit the output, so deploys are a plain file copy with no build server in the loop.
Why not Tailwind or a UI kit?
For a site this size it would be more bytes and more abstraction than the job needs. A dozen CSS variables and one stylesheet cover everything here, and they ship less to the browser.
Can I reuse this design system?
The tokens aren't a product, but the approach copies well. Pick one neutral ramp, one accent, two or three radii, two shadows, define them once at the top of your stylesheet, and refuse to hardcode anything after that.