The system, in a paragraph

Deep navy display type on white, with a single bright teal used for fills and never for text. Large soft-cornered cards on a pale blue band, hard-edged square controls with one angled corner. Space Grotesk set tight and large for anything that speaks, Noto Sans for anything that explains. Motion is directional and driven by scroll position rather than by hover. The tone is measured and honest: the product's whole argument is that it tells you when it does not know, so the interface never fakes confidence it does not have.

The one rule people break first. Teal is a fill. It is 2.5:1 on white and fails as text at every size. When teal has to be a word, it is --ws-teal-deep.

This system is

Square controls against round containers. One accent. Uppercase rationed to labels. Type that carries the personality and colour that stays out of the way. Motion that moves in a direction.

This system is not

Gradients, a second accent colour, rounded buttons, emoji, drop shadows used for decoration, or any interface element that shows a number without being able to say how much it trusts it.

Colour

Contrast figures are measured against white. Each swatch states whether it may carry text at that ratio, because the brand's most attractive colour is also its least legible.

Ink--ws-ink · #15344B 12.93:1 · AA
Display type and dark surfaces. The colour the brand speaks in.
Teal--ws-teal · #03B8A5 2.5:1 · text: never
The brand. Fills and shapes only. Never a word.
Teal deep--ws-teal-deep · #00786C 5.38:1 · AA
The text-safe teal. Use wherever teal has to be read.
Sky--ws-sky · #E2F5FF 1.12:1 · text: never
The pale band that separates one section from the next.
Paper--ws-paper · #FFFFFF 1.0:1 · text: never
The default surface.
Body--ws-body · #000000 21.0:1 · AA
Body copy is true black, not ink.
Duo--ws-duo · #1C5F91 6.78:1 · AA
The single blue every duotone photograph prints in.
Muted--ws-muted · #6A7F8E 4.17:1 · AA large only
Secondary and axis text. Solid, so it survives on any surface.

Where each surface goes

Paper

The default. Prose, panels, tables, forms.

Sky

Section separation and quiet emphasis. Never a full page.

Ink

Footers, sidebars, closing bands. Teal becomes legible here.

Never. Teal text on any surface. A gradient anywhere. A second accent colour. A status colour reused as a brand colour.

Type

Two families. Space Grotesk at −0.05em tracking does the talking, Noto Sans at +0.02em does the explaining. The display face is never used below about 20px, and the body face is never used above the lede size.

Watching the drift, not the damage
A plan written in January knows nothing about your performance this week.

Tissue takes load, adapts, takes more load, adapts again. When the load arrives faster than the adaptation can keep pace, the tissue does not fail on the spot.

Read the note

The scale

RoleTokenFaceWhere
Megaclamp(3.6rem,17.6vw,261px)DisplayClosing bands only
H1clamp(2.9rem,8.1vw,120px)DisplayPage titles
H2clamp(2.2rem,4.05vw,60px)DisplaySection heads, article titles
H3 / H4clamp(1.9rem,3.1vw,46px) / 2.16vw,32pxDisplayCard and block heads
Ledeclamp(1.1rem,1.82vw,27px)BodyOne paragraph under a title
Bodyclamp(1rem,1.21vw,18px)BodyEverything else
Proseclamp(1.05rem,1.32vw,20px)BodyLong-form, capped at 68ch
UI / micro15px / 13pxBodyTools, labels, table heads

The highlighter

One plan that builds around your body
One span per visual line. A marker across a wrapping headline loses its block on every line after the first. This bug appeared four times during the site build.

Space and geometry

The look comes from the contrast between soft containers and hard controls. Do not soften the controls and do not sharpen the cards.

32px · cards, dialogs
8px · tool panels
0px · every control
TokenValueNote
--ws-maxw1416pxContent cap on every page
--ws-gutter20px, 32px above 1024
--ws-header-h104px, 88px below 1024Responsive. Offset against the token, never a number that matched it once.
Spacing4 8 12 16 24 32 48 64 96Sections size by clamp, tools by this scale

Buttons

A chamfer at the top left running the full height, so the control leans in the direction everything else travels. Hover deepens the notch rather than swapping a colour.

On the sky band
The angle is the constant, not the inset. 12px on a 38px button and 18px on a 57px one are both about 17.5°. At any other height the cut is height × 0.315.
/* the fill is a layer, so the focus ring survives */ .ws-btn::before{ content:""; position:absolute; inset:0; z-index:-1; background:var(--ws-teal); clip-path:polygon(var(--ws-cut) 0, 100% 0, 100% 100%, 0 100%); } .ws-btn:hover{ --ws-cut:var(--ws-cut-hover); }
Never clip the interactive element itself. clip-path clips everything an element paints, its outline included, so a clipped button loses its focus ring entirely.

Forms

Square fields, hairline borders, a 3px teal focus ring at 1px offset. Labels are uppercase micro type; hints and errors sit under the field, never beside it.

Surfaces

Marketing uses the 32px card and its lift. Tools use the flat 8px panel, because a 32px radius reads as decorative at dashboard density.

Card · 32px

Coaching for the years it takes

Lifts 6px on hover with a deeper shadow. Used for anything a visitor is meant to consider rather than operate.

Panel · 8px Internal
Interactions this week83 of 125 · resets Monday
Ink veil at 55%, 3px blur, 32px radius

Status

Four reserved colours that never become chart series. A status colour never carries meaning on its own: it always ships with an icon or a label.

DefaultInk QuietSynced StaleDisconnected
Garmin connected. Last sync 4 minutes ago.
Polar has not synced in 6 days. Detail past 28 days cannot be recovered once the window closes.
We could not reach Strava. Reconnect to resume importing.

Confidence

The product's own idea, and the one piece of UI no other brand has. A signal shows how much it knows, and a signal running on thin data says so rather than producing a plausible number.

Readiness62 Good coverage
Resilience Two days of data

Tables

Ruled, never zebra. Headers are teal-deep micro caps and stick. Figures are tabular and right-aligned so columns compare down rather than across.

SourceIntradayRetentionLast syncState
Apple HealthPushUnlimited4 min Live
WithingsYes7 days2 h Live
PolarYes28 days6 d Stale
GarminNo11 min Live
StravaNo19 d Disconnected
OuraNo1 h Live

App shell

Ink sidebar, teal marking the current item, a hairline topbar. The marketing header does not belong in a tool and the tool shell does not belong on the site.

Athletes/M. Declerck

Marie Declerck

Weekly load412+8% on 4-week mean
Resting HR48up 3 for 9 days
Sleep6:42below own need

Charts

The palette was validated rather than chosen: lightness band, chroma floor, colour-vision separation, normal-vision separation and contrast against white. Substituting a colour by eye breaks it.

Categorical · assign in this order, never cycled
1
2
3
4
5
6
7
8
tealorangeblueambermagentagreenvioletred
Eight adjacent, three all-pairs. All eight are safe where series sit next to each other, as in lines, bars and stacks. In a scatter, bubble, choropleth or small multiple, any pair can end up side by side and only the first three validate. Past three, fold into Other or facet. A ninth series is never a generated hue.
Line · 2px, recessive grid, one direct label
2040608070
Load Recovery Sleep debt
Bar · 4px rounded ends anchored to the baseline
Sequential · one teal hue, light to dark

Full range for continuous magnitude. For discrete ordered steps start at the fourth swatch, so the lightest still separates from the surface.

Diverging · teal to orange through a neutral grey
Never. A dual axis. Colour assigned by rank, so that a filter repaints the survivors. A rainbow ramp. A hue at the diverging midpoint. A number printed on every point.

Motion

One easing curve, two durations. Scroll-driven motion writes a single custom property per frame to a stage element and lets CSS do the rest, never a listener per item.

TokenValueWhere
--ws-easecubic-bezier(.22,.61,.36,1)Everything. There is no second curve.
--ws-dur-micro220msHover, focus, colour
--ws-dur800msReveals on entry
Button notch260msThe cut deepening on hover
Dialog420msOpacity and a 20px rise
Anything driven by a scroll variable needs an answer for when the scroll code never runs. Under reduced motion, or below a breakpoint where the stage is disabled, the variable stays at its initial value and any calc() floor renders.

Voice

Applies to microcopy, empty states and error messages as much as to marketing. Direct second person, short sentences mixed with longer ones, concrete over abstract, and the limit stated rather than hidden.

Write

“We could not reach Garmin. Your last sync was 6 days ago.”

“Not enough data yet. This needs about two more weeks before it means anything.”

Do not write

“Oops! Something went wrong.”

“Unlock powerful insights with our comprehensive dashboard.”

Banned. Em dashes. “Seamless”, “robust”, “leverage”, “comprehensive”, “empower”, “unlock”. Three-part lists built for rhythm. Exclamation marks. A closing line that restates what was just said.

The standing disclaimer

Trailwise is not a medical device. Neither is it a replacement for a doctor or a physiotherapist. Trailwise flags what is worth a conversation. It does not diagnose, and it will tell you when it does not know.

Using this

Three files travel together. Hand the primer to a conversation, link the stylesheet in the build, feed the JSON to anything that generates a theme.

FileForHow
TRAILWISE-DESIGN-PRIMER.mdAny chat that will build a Trailwise interface Attach it and say: build X, follow the rules section exactly
trailwise.cssThe build itself <link rel="stylesheet" href="trailwise.css">, then use the ws- classes
trailwise-tokens.jsonTheme generators, Tailwind configs, Figma imports Read the color, type and charts keys
This bookHumans, and design reviewOpen it and point at the section
Build a <thing>. Use the attached Trailwise design primer and trailwise.css. Follow the rules section exactly.
What this replaces. The Field Guide system, forest and cream and alpenglow with Hanken Grotesk. The only thing that crossed over is the angled button geometry, re-cut against these tokens. If you find Field Guide colours in a project, they are the old system.