Skip to content
URIAH CLEMMER
$ cat blog/welcome-to-the-log.md

Welcome to the log

SOFTWARE DESIGN 6 min read

This is the first entry in the log, which makes it the post that has to explain itself. The site is live: seven pages, real data behind all of them, and a look somewhere between a dark code editor and an arcade cabinet from 1985. Here is what it runs on and why it looks like that.

What this is

A personal site. Projects I have built, small tools I keep online because I got tired of rewriting them, a blog for everything that happens after work, and a contact form that actually reaches me. Nothing novel — the interesting part is that none of it is static markup.

Posts, projects, tools and blog topics are Eloquent models with migrations, factories and seeders behind them. The topic filter on the blog is a real query against a real query string, not a set of hardcoded links that pretend to filter. That decision cost an afternoon up front and has already paid for itself twice.

The stack

  • Laravel 13 on PHP 8.5, SQLite locally, served by Herd.
  • Blade for the views — components for the chrome, one page view per screen.
  • Tailwind CSS v4 for styling, with Flowbite 3 wired in for the interactive components.
  • Vite 8, with the three fonts self-hosted at build time rather than pulled from Google on every visit.
  • Pest 5 for tests, Pint for formatting, Larastan for static analysis.
  • Postmark for email and Twilio for SMS, both hanging off the contact form.

The one piece worth calling out: Tailwind v4 has no tailwind.config.js. Tokens are CSS custom properties declared in an @theme block, and Tailwind derives the utility names from them. Every guide written for v3 tells you to open a config file that does not exist. That was the single biggest surprise of the build.

Flowbite is in for what comes later — dropdowns, modals, drawers. As drawn, none of the seven screens needs a JavaScript component: the nav wraps instead of collapsing to a drawer, and the form controls are plain inputs restyled to the tokens. Anything added later inherits the theme for free.

Design rules that live in code

The tools table shows three statuses, each with its own badge treatment. Rather than scatter those classes across the template, the enum owns them:

return match ($this) {
    self::Live => 'bg-green px-2 py-[3px] text-green-deep',
    self::Beta => 'border border-red px-[7px] py-[2px] text-red',
    self::Soon => 'border border-ink/30 px-[7px] py-[2px] text-ink/45',
};

One place to change, no chance of the badge and the label drifting apart. Most of the design system ended up encoded this way somewhere.

The design

The brief was "code IDE crossed with retro arcade", built on a Modernist base: Archivo, flush-left, a modular grid, strong 2px rules and — this is the load-bearing constraint — zero border radius, everywhere. Not "mostly zero". There is a base rule forcing it on inputs and buttons so the browser cannot sneak rounding back in.

The palette is small on purpose. A near-black canvas, a slightly lighter surface for the 1180px column, off-white text, and exactly two accents: a green for anything you can act on and a red for anything demanding attention. Secondary text is not a separate colour, just the same off-white at a lower opacity. Fewer tokens, fewer arguments.

Three typefaces, each with a job. Archivo for everything you read. JetBrains Mono for meta lines, tables and code. Press Start 2P — the arcade face — is restricted to arcade labels only: LEVEL SELECT, LV.01, INSERT COIN, the pagination. Used anywhere else it stops being a joke and starts being a legibility problem.

The mark is a reveal, not a flourish. Knock back RIAH and you are left with U…CLEMMER, which is the domain.

That reveal only works while the ghosted letters stay quieter than the rest, which is why the hierarchy is carried by opacity and never by hue. Setting the ghost in red was tempting and would have been wrong twice over: red is the system's attention colour, so it pulls the eye to exactly the letters meant to recede — and under deuteranopia that red renders brighter than the green, reversing the reveal outright. Opacity survives every form of colour vision deficiency. Hue does not.

The seven screens

These are the design references the site was built from — the handoff prototypes, not captures of the running app. The built pages track them closely; where they differ, the copy is real now and the placeholder names are gone.

Landing page: the name URIAH CLEMMER set in large green type with the RIAH letters ghosted back
// landing — the mark at 148px, ghost letters faded rather than dropped
About page with the heading About Uriah and three paragraphs of biography
// about — the one screen that builds the mark inline, in sentence case
Projects page: filter chips above a three-column grid of project cards
// projects — filter chips, then cards with the stack line pinned to the bottom
Tools page: a four-column table of tools with green LIVE status badges
// tools — the table whose three status badges the enum above owns
Blog index headed The log, with a row of topic filter chips below it
// the log — LEVEL SELECT doubles as the topic filter
Blog post layout showing a terminal-style kicker, large headline and article metadata row
// a post — the layout you are reading; the sample article shown is placeholder copy
Contact page headed Start something, with a form on the left and direct channels on the right
// contact — the form opens a ticket and sends me an email and a text

What happens next

The blog shipped with sixteen convincing placeholder posts so the pagination had something to paginate. They are gone as of this week, which is the honest reason this post exists: a demo blog that reads convincingly is worse than an empty one, because you stop noticing it is not yours.

So the log starts here, at one entry. Bees, brews, bonsai, machine vision and whatever is on the bench — that is the plan. If something here is useful, or wrong, the contact form works.

#laravel #tailwind #flowbite #design-system
CONTINUE? Questions about this one? Say hello →