Color
Interface colors describe purpose rather than a fixed appearance.
--color-canvas--color-ink--color-ink-muted--color-stage--color-stage-hover--color-image-surface--color-callout-bg--color-focus
jackosborne.com
The foundations, elements and interface patterns used across this site. This page is generated from the same tokens and components as the rest of the site, so it evolves with the work.
Semantic tokens keep the visual hierarchy consistent across light and dark appearances.
Interface colors describe purpose rather than a fixed appearance.
--color-canvas--color-ink--color-ink-muted--color-stage--color-stage-hover--color-image-surface--color-callout-bg--color-focusThe system stack carries the interface; mono is reserved for numbers, tokens and compact metadata.
Body copy supports sustained reading with a restrained size, comfortable measure and generous line height.
01 — 2025–Present
An 8px foundation scales from compact controls to broad composition.
8162432487296Twenty-four implementation tracks resolve into four conceptual rails.
Editorial content typically occupies tracks 07–18.
grid-column: 1 / 25Full grid
grid-column: 7 / 19Editorial rail
grid-column: 4 / 16Left push
grid-column: 10 / 22Right push
.full-grid { grid-column: 1 / 25; }
.editorial-rail { grid-column: 7 / 19; }
.push-left { grid-column: 4 / 16; }
.push-right { grid-column: 10 / 22; }Radius communicates object type: restrained for utility, generous for project stages.
12px · Small24px · Mobile card32px · Card999px · Pill.utility { border-radius: var(--radius-small); }
.mobile-card { border-radius: var(--radius-card-mobile); }
.card { border-radius: var(--radius-card); }
.pill { border-radius: var(--radius-pill); }Native HTML carries the hierarchy before components add any behavior.
Good interface writing is direct, useful and human. It explains enough to create confidence without competing with the work.
An inline link remains visibly identifiable, while strong text provides semantic emphasis rather than decoration.
Design systems are most useful when they capture decisions, not merely inventory.
<p>Good interface writing is direct and useful.</p>
<blockquote>Design systems capture decisions.</blockquote>Use unordered lists for related items and ordered lists when sequence matters.
<ul>
<li>Prefer semantic HTML.</li>
<li>Reuse established patterns.</li>
</ul>
<ol>
<li>Frame the problem.</li>
<li>Test the assumption.</li>
</ol><a href="/about/">Editorial text link</a>
<a class="button-link" href="/work/">Compact action</a>
<button type="button">Native button</button>| Role | Token | Use |
|---|---|---|
| Body | --font-size-base | Descriptions and details |
| Utility | --font-size-sm | Labels and metadata |
| Divider | --color-divider | Quiet structural separation |
| Stage | --color-stage | Image and component surfaces |
| Focus | --color-focus | Keyboard focus indication |
<div class="breakout">
<table class="data-table">
<thead>
<tr><th>Role</th><th>Token</th><th>Use</th></tr>
</thead>
<tbody>
<tr><td>Body</td><td>--font-size-base</td><td>Descriptions</td></tr>
</tbody>
</table>
</div>Figures can follow the editorial rail or use the grid-width breakout, with captions kept close to their image. CSS controls the displayed dimensions; the responsive-image helper supplies suitable source files for that layout.


<figure class="img">
<div class="bg">
{% responsiveImage
"/_/images/about/profile/featured.png",
"Jack Osborne on Edinburgh's Royal Mile",
"(min-width: 1140px) min(34vw, 494px), (min-width: 1024px) calc(100vw - 192px), calc(100vw - 64px)",
"about"
%}
</div>
<figcaption>
Figure title
<span>Supporting context.</span>
</figcaption>
</figure>
<div class="breakout">
<figure class="img">
<!-- Use a sizes value that describes the wider layout. -->
…
</figure>
</div>Small, composable patterns support the content without becoming a visual system of their own.
Eyebrow, title and optional description form one compact hierarchy.
Selected work
A focused explanation gives the page enough context without delaying the work.
<header class="page-intro grid">
<div class="page-intro__inner row-start-7 row-end-19">
<p class="page-intro__eyebrow">Selected work</p>
<h1>Saved Places</h1>
<p class="page-intro__description">A focused explanation gives the page enough context.</p>
</div>
</header>Supporting context remains available without competing with the primary narrative.
{% call accordion("project-context", "Context") %}
<ul>
<li><span class="heading">Role:</span> Product design</li>
<li><span class="heading">Year:</span> 2026</li>
</ul>
{% endcall %}A short contextual notice uses warmth sparingly.
<div class="callout" role="note">
<svg width="24" height="24" viewBox="0 0 24 24" aria-hidden="true">…</svg>
<div class="callout__text">
This is supporting information, not the main event.
</div>
</div>Imagery sits on a neutral gallery-like surface with generous inset space.
<div class="work-card__stage single-phone">…</div>
<div class="work-card__stage wide-browser">…</div>
<div class="work-card__stage split-stage">…</div>Interaction states, native semantics and user preferences are part of the component contract.
Every interactive control should communicate availability, history and keyboard focus without relying on motion alone.
a:link {
color: var(--color-link);
text-decoration-color: var(--color-ink);
}
a:visited {
color: var(--color-link-visited);
text-decoration-color: var(--color-link-visited);
}
a:hover,
a:focus { color: var(--color-link-hover); }
a:focus-visible,
button:focus-visible,
summary:focus-visible {
outline: var(--focus-ring-width) solid var(--color-focus);
outline-offset: var(--focus-ring-offset);
}
:is(button, input, textarea, select):disabled {
cursor: not-allowed;
opacity: 0.45;
}These native controls work without custom keyboard scripting. Use Tab to move through them, then Enter or Space to activate the focused control.
Disclosure content remains available to keyboard and assistive-technology users.
<a href="/about/">Focusable link</a>
<button type="button">Native button</button>
<details>
<summary>Native disclosure</summary>
<p>Disclosure content.</p>
</details>The site follows the operating-system preference and removes non-essential animation and transition duration globally.
Your current preference: Checking…
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0s !important;
animation-iteration-count: 1 !important;
transition-duration: 0s !important;
scroll-behavior: auto !important;
}
}