Design system
Walk the map. Everything below is read from @shlak/design-tokens, the one place a brand value is written. The written source is brand/DESIGN-SYSTEM.md; the tokens are transcribed from it by hand and a drift test fails if the two disagree.
The theme control in the header applies to this page only. It overrides the semantic tokens for this subtree, so you can read either scheme without changing your OS.
Logo
DESIGN-SYSTEM §5
Variants
Three variants, each drawn from the same 64-unit geometry. The mono variant carries no rut, because a single-colour mark cannot express one without breaking currentColor.
dark
Duotone, for dark surfaces
light
Duotone, for light surfaces
mono
Single colour, currentColor
Clear space
Clear space on all four sides equals x, where x is a quarter of the mark's height. Nothing enters it: no type, no page edge, no imagery.
mark 128 px · x = 32 px
Minimum sizes
Both are rendered here at their true minimum. Below them the mark stops being legible and the lockup's wordmark stops being readable, so the lockup gives way to the mark alone.
mark, 16 px
lockup, 88 px wide
The rut
The accent leg carries a flat band across its top edge: the walked part of a trail is worn into the ground, not raised off it. It is a clipped rect, never a filter, so react-native-svg renders it and so it falls under a pixel below about 24 px instead of smearing.
- leg
- pill at x 9.5, y 47.5, w 37, h 9, rx 4.5
- rut
- flat 2.4-unit band, clipped to the leg
- colour
- one step down the Lime ramp, never black
The same 2.4-unit band appears on progress bars and stage segments, so the logo and the progress UI read as one object at two scales. It is drawn again in Motion, below.
Do not
- Re-colour the accent to anything outside the Lime ramp.
- Apply a gradient, outer glow, or drop shadow to the mark.
- Rotate, skew, or stretch. The lockup ratio is fixed at 212 : 64.
- Place the duotone mark on a mid-tone that is within 3 : 1 of the base stroke.
- Re-set the wordmark in another family, or letter-space it by hand.
Colour
DESIGN-SYSTEM §1
Ramps
Three ramps and one flat ink. Ramp steps are theme-invariant: bg-trail-900 is the same colour in both schemes, which is why they are the escape hatch rather than the default.
Trail
The green-black neutral. Every dark-theme surface.
trail-50
#F1F7F1
trail-100
#DCE9DD
trail-200
#BCD3C0
trail-300
#8AAF93
trail-400
#5C8869
trail-500
#3F664E
trail-600
#2C4A38
trail-700
#1E3428
trail-800
#15251C
trail-900
#0E1A14
trail-950
#0A130E
Lime
The accent. Progress, achievement, the primary action.
lime-100
#EEFCCB
lime-200
#DBF894
lime-300
#C6F24E
lime-400
#A9D634
lime-500
#8CB520
lime-600
#6F9013
lime-700
#52690E
Stone
The warm neutral. Every light-theme surface.
stone-50
#FAF8F4
stone-100
#F2EFE8
stone-200
#E8E3D9
stone-300
#D6CFC1
stone-400
#B3AA99
stone-500
#8B8272
stone-600
#6B6355
stone-700
#4C463C
Ink and Signal
Signal is semantic only, never decorative.
ink
#121512
danger
#D4553D
warning
#E0A32E
success
#8CB520
info
#4E8FA8
Semantic tokens
Every token carries both schemes, so a dark: pairing on one of these utilities is redundant. Reach for these first; reach for a ramp step only when you want one fixed colour in both schemes.
bg.canvas
bg-canvas
#FAF8F4 / #0A130E
bg.surface
bg-surface
#F2EFE8 / #0E1A14
bg.raised
bg-raised
#FFFFFF / #15251C
fg.default
text-fg
#121512 / #E8E3D9
fg.muted
text-fg-muted
#6B6355 / #8AAF93
fg.accent
text-accent
#6F9013 / #C6F24E
border.default
border-border
#D6CFC1 / #1E3428
track.unfilled
bg-track
#B3AA99 / #2C4A38
track.filled
bg-track-filled
#6F9013 / #C6F24E
track.rut
bg-track-rut
#52690E / #A9D634
action.bg
bg-action
#C6F24E / #C6F24E
action.fg
text-on-action
#121512 / #121512
The one colour rule that matters
lime-300 is a surface colour, never a text colour on light. On stone-50 it measures 1.24 : 1, which is invisible. When lime has to appear as a colour on a light ground it steps down to lime-600.
Two thousand steps
lime-300 on stone-50 · 1.2 : 1 · never
Two thousand steps
lime-600 on stone-50 · 3.5 : 1 · clears the non-text minimum
The primary action is the other half of the rule: a lime-300 fill with ink text in both themes. The brand's most recognisable element is identical light and dark.
light
dark
Verified contrast
Measured, not assumed.
- ink on stone-50
- 17.5 : 1AAA
- stone-600 on stone-50
- 5.6 : 1AA
- stone-200 on trail-900
- 13.9 : 1AAA
- trail-300 on trail-900
- 7.2 : 1AAA
- ink on lime-300
- 14.3 : 1AAA
- lime-300 on trail-900
- 13.7 : 1AAA
- lime-600 on stone-50
- 3.5 : 1non-text only
- lime-300 on stone-50
- 1.2 : 1never
Typography
DESIGN-SYSTEM §2
Families
All three are vendored into the repository and loaded from pinned binaries. Neither app fetches a typeface over the network at build or run time, so web and mobile render identically.
Space Grotesk
Display and wordmark · 600, 700
ABCDEFGHIJKLMNOPRSTUWYZ ąćęłńóśźż 0123456789
Latin Extended-A, so Polish diacritics render.
Inter
UI and body · 400, 500, 600
ABCDEFGHIJKLMNOPRSTUWYZ ąćęłńóśźż 0123456789
Tabular numerals are on, so a changing step count does not re-flow its neighbours.
JetBrains Mono
Data and labels · 400, 500
ABCDEFGHIJKLMNOPRSTUWYZ ąćęłńóśźż 0123456789
Distances, step counts, and every small-caps label.
Scale
A 1.200 minor third off a 16 px base. Each specimen below is rendered through its Tailwind utility; the metrics beside it are read from the TypeScript constants.
display
38 / 42 · -0.02em
Wiślana Trasa Rowerowa
h1
30 / 36 · -0.02em
Today's walk
h2
24 / 30 · -0.02em
Stages and elevation
h3
20 / 26 · 0em
Vistula bend, kilometre 214
body-lg
18 / 28 · 0em
Your feet move the pin. Every step you take counts toward somewhere real.
body
16 / 24 · 0em
The route ahead splits at the old ferry crossing and rejoins after the treeline.
body-sm
14 / 20 · 0.01em
Synced 4 minutes ago from Health.
caption
12 / 16 · 0.01em
Updated 09:41
label
11 / 14 · 0.12em
Stage 4 of 10
caption is metadata only. The smallest step allowed for running body text is body-sm; never ship 12 px as running text on mobile.
Space, radius, size
DESIGN-SYSTEM §3
Space
A 4 px base, so Tailwind's own multiplier generates every step from one variable. Screen gutters are 20 px on mobile, 24 px on tablet and 32 px on web at 768 px and up.
Radius
xs · 4
chip
sm · 8
input, small button
md · 12
card
lg · 20
sheet, large button
xl · 28
modal
full · 9999
pill, avatar
Icon sizes
Icons are drawn on the same 24-unit grid as the mark, with round caps and joins, so they sit beside it without a weight mismatch. Stroke width is paired to size and never passed separately.
16 · stroke 1.5
20 · stroke 1.5
24 · stroke 1.5
32 · stroke 2
Hit targets
Never smaller, whatever the icon inside it measures.
44 × 44
everywhere
48 × 48
bottom bar
Elevation
Light theme uses shadow. Dark theme uses surface tint instead, which is why there is deliberately no dark counterpart to the three shadows.
light · shadow
raised
cards on canvas
overlay
menus, popovers
sheet
bottom sheets
dark · surface tint
trail-900
base surface
trail-800
raised surface, card
trail-700
border, divider
Motion
DESIGN-SYSTEM §4
Durations
Progress advances are the one place to spend the motion budget. Everything else is 200 ms or less. Press play to run all four at once; the difference between 120 ms and 900 ms is the whole point of the scale.
instant
120 ms · cubic-bezier(.2,0,0,1)
state flips, hovers, toggles
default
200 ms · cubic-bezier(.2,0,0,1)
everything not listed elsewhere
emphasis
320 ms · cubic-bezier(.2,0,0,1)
sheets, modals, screen transitions
progress
900 ms · cubic-bezier(.4,0,.2,1)
an accent fill advancing
The track and the rut
The progress track carries the same flat band across the top of its filled portion as the mark's accent leg, at the same share of the height. The logo and the progress UI are one object at two scales.
track · track-filled · track-rut at 26.7% of the height
28%
Reduced motion
Every transition on this page is gated behind Tailwind's motion-reduce variant. With Reduce Motion on, the fills and the dots jump straight to their end state instead of travelling, and nothing on the page loops.