Everything that ships on this site — tokens, type, colour, patterns and components — rendered live from the same CSS the rest of the site uses. Written for humans and agents alike.
Colour
Every swatch below is painted with var(--token), so this page always shows the colour that is actually shipping. Source of truth: src/scss/utilities/_variables.scss.
Core palette
var(--color-highlight)
#7a9676
var(--color-text-default)
#fff
var(--color-text-alt)
#fff
var(--color-text-alt2)
#d9d7c9
var(--color-background)
#fff
var(--color-background-alt)
#30552f
var(--color-background-breakout)
#3b5e3a
var(--color-background-selection)
#f0f2f3
var(--color-base)
#f8f8f8
var(--color-border)
#4f694b
var(--color-light)
#fff
var(--color-mid)
#a3a3a3
var(--color-muted)
#9eb395
Aliases
Semantic tokens that point at the palette. The --color-text-link-* and two --color-button-* tokens resolve to --color-brand-*, which is not defined anywhere — they are inert. Unclassed links actually use --color-text-alt2 (see Prose).
Text set at this line height wraps like this. The body ships at 1.4 via _typography.scss; these tokens are applied per component. Resize to check the rhythm across a few lines of copy.
var(--lineheight-text)1.5
Text set at this line height wraps like this. The body ships at 1.4 via _typography.scss; these tokens are applied per component. Resize to check the rhythm across a few lines of copy.
var(--lineheight-heading)1.3
Spacing
Each bar is sized with width: var(--token).
var(--spacing-xx-small).125rem · 2px
var(--spacing-x-small).25rem · 4px
var(--spacing-small).5rem · 8px
var(--spacing).75rem · 12px
var(--spacing-medium)1rem · 16px
var(--spacing-large)1.5rem · 24px
var(--spacing-x-large)2rem · 32px
var(--spacing-xx-large)3rem · 48px
var(--spacing-xxx-large)4rem · 64px
var(--spacing-xxxx-large)5rem · 80px
var(--spacing-mega-large)7rem · 112px
Layout & breakpoints
Page scaffold
Pages are built from .wrapper (centred, max-width: var(--max-width), 130rem · 2080px) containing .grid-wrapper, which becomes a six-column grid from 600px. Content blocks are .grid-items placed with grid-column. Work case-study copy lives in .project__content, which spans four of the six columns on wide screens.
Breakpoints
37.5em · 600px — .grid-wrapper switches to six columns; wrapper padding steps up; work/skills section headings become sticky.
50em · 800px — .project-split forms two columns.
75em · 1200px — type scale steps up (:root media override); project meta moves to its own column; .project-split stacks again through this band.
100em · 1600px — .project-split returns to two columns; the availability seal moves to the second-last column.
Width and grid tokens
var(--width-600)37.5rem · 600px
var(--width-800)50rem · 800px
var(--width-1000)62.5rem · 1000px
var(--width-1200)75rem · 1200px
var(--max-width)130rem · 2080px
var(--grid-column-gap)2vw
var(--grid-row-gap)2vh
Motion
Duration tokens power transitions (link colour, image zooms, the availability seal spin). Hover or focus a chip to feel each duration — the background fade uses that token.
Prose
How markdown renders inside a work case study. This demo sits inside real .project .project__content wrappers, so what you see is exactly what ships.
Section heading (h2, serif)
Body copy at --font-size-medium with an unclassed link — colour --color-text-alt2, underline --color-highlight, and a highlight-block hover. Emphasis renders as italic and semibold-bold strong.
Subheading (h3)
Lists keep browser defaults inside prose:
First item
Second item
Blockquotes get a 5px --color-highlight left border and generous vertical margin.
Inline code like var(--color-highlight) renders as a bordered chip; fenced blocks share the same surface:
A fenced code block. Same chip surface, block-level, scrolls if it must.
Figure captions are set in the mono stack at --font-size-default.
Markdown snippet
## Section heading
Body copy with an [unclassed link](/) and `inline code`.
> A pull quote or aside.
```text
A fenced code block. The language tag must be one Prism knows — use text as the fallback.
```
Components
Status badge
Pill for experimental or in-progress items. Rendered via the renderStatusBadge macro, which prepends screen-reader context. Compact variant is used on list cards.
Experimental or in-progress: Work in progress
Experimental or in-progress: Work in progress
Nunjucks snippet
{% from "partials/components/status-badge.njk" import renderStatusBadge %}
{{ renderStatusBadge({ text: "Work in progress" }) }}
{{ renderStatusBadge({ text: "Work in progress", compact: true }) }}
Project summary panel
Breakout panel on --color-background-breakout used for case-study intros and outros.
The gist
A short, self-contained summary of the case study — what the problem was, what shipped, and what changed for users.
HTML snippet
<section class="project-summary">
<h3>The gist</h3>
<p>One-paragraph summary of the case study.</p>
</section>
Project split
Two-column case-study block: media about two thirds, copy one third. Two columns from 800px, stacks through the 1200–1600px band while the main column is narrow, then splits again. --image-right flips the order. No blank lines inside the wrapper when used in markdown.
Optional caption
Image left. Media takes roughly two thirds of the row, copy one third.
Optional caption
Image right. Same ratio with the columns swapped.
HTML snippet
<div class="project-split project-split--image-left">
<figure class="project-split__media">
<img src="/images/example.png" alt="Describe the image.">
<figcaption>Optional caption</figcaption>
</figure>
<div class="project-split__copy">
<p>Copy for the narrow column.</p>
</div>
</div>
Prompt compare
Side-by-side comparison table for prompts or copy variants. Section labels sit above each cell (no left-hand row column). When embedded in markdown, keep the whole block free of blank lines or markdown-it closes the HTML block early and re-parses the contents.
Example comparison: initial versus refined prompt.
Initial prompt
Refined prompt
Prompt
You are a helpful assistant.
Write a job listing.
Prompt
You are an expert coordinator.
Write a job listing in Australian English.
Use plain text. No em dashes.
Spinning starburst pinned to an intro grid cell on the about page. The seal rotates over 60 seconds, spins up on hover or focus, and honours prefers-reduced-motion. The demo box below stands in for the grid cell it normally pins to.
Faux browser chrome — traffic lights + selectable address bar — for showing a screenshot or scene as if it were a live tab. renderMiniBrowser is the chrome on its own; renderMiniBrowserCard wraps it in a fixed-aspect-ratio card, and adds a bottom-up gradient wash that brightens on hover/focus when it sits inside a link (pass linked: true). Both are self-contained — styled with local custom properties rather than the page's colour tokens, since the chrome is standing in for a real OS window and shouldn't shift with the theme.
The navigation, page intro and footer aren’t duplicated here — the live versions frame this very page. The intro pattern (partials/components/intro.njk) is the serif heading and blurb at the top; the footer shows the contact list and acknowledgement styles.
Notes for agents
Tokens are defined once, as CSS custom properties, in src/scss/utilities/_variables.scss. This page reads that file at build time (src/_data/designTokens.js) and paints with var(), so it cannot drift from shipped CSS.
There is no root font-size override: 1rem = 16px everywhere.
Styles compile from src/scss/global.scss only. Partials in src/scss/components/ that are not in its import list (for example _form.scss, _button.scss, _pagination.scss) do not ship — check the import list before documenting or reusing a class.
Markdown (markdown-it) runs with html: true and typographer: true, and indented code blocks are disabled — always use fenced blocks. Fence language tags must be languages Prism recognises; use text as the fallback or the build fails.
Raw HTML blocks in markdown (project split, prompt compare) must not contain blank lines — markdown-it ends the HTML block at the first empty line and re-parses what follows.
Prose components (project-split, prompt-compare, code chips) are scoped to .project__content and expect that ancestor.
The --color-text-link-*, --color-button and --color-button-hover tokens reference undefined --color-brand-* values and are inert; don’t build on them.
All examples on this page render inline in the document — no iframes — so component queries against this URL see real, final markup.