Foundation

Colour

A neutral grey and one flat magenta, in semantic tokens with a light and a dark value each.

Concept · live ReactSource file: tokens.json

Ramps

Two ramps. Magenta 500 is the brand on every ground and 600 carries white text; the neutral ramp holds everything else.

50#FFF0F7
100#FFDDEE
200#FFB8DC
300#FF8AC4
400#FF56AA
500#FF2D95Brand
600#CA0E6FButtons, light
700#A6085B
800#930A52
900#6B0B3E
950#42062A
0#FFFFFF
50#F6F6F7Canvas
100#ECECEE
200#D9D9DD
300#BDBDC3
400#9A9AA2
500#77777F
600#5A5A62Secondary text
700#404047
800#2A2A30
850#1F1F24
900#16161A
950#0E0E10Overlay ground

Semantic tokens

What a colour is for, with its light and dark value. Components read these, never a ramp step.

TokenValueWhere it lands
--cm-bg-canvas#f6f6f7 / #0e0e10The page behind everything
--cm-bg-surface#ffffff / #16161aCards and panels
--cm-bg-raised#ffffff / #1f1f24Menus and sheets above a surface
--cm-bg-sunken#ececee / #1f1f24Chips, keycaps, tracks
--cm-border-subtle#d9d9dd / #2a2a30Dividers and resting edges
--cm-border-strong#77777fInput and control edges, 3:1 or better
--cm-text-primary#0e0e10 / #f6f6f7Body and headings
--cm-text-secondary#5a5a62 / #9a9aa2Supporting text, 6.9:1 on its ground
--cm-text-on-accent#ffffff / #0e0e10Text on an accent fill
--cm-accent-fill#ca0e6f / #ff2d95Primary buttons, selected controls
--cm-accent-fill-hover#a6085b / #ff56aaHover on an accent fill
--cm-accent-text#ca0e6f / #ff56aaLinks and accent words
--cm-accent-subtle#fff0f7 / #42062aSelected backgrounds
--cm-focus-ring#ca0e6f / #ff56aaThe 2px focus ring
--cm-brand-dot#ff2d95The ping and the logo's o, on every ground
--cm-status-urgent#ca0e6f / #ff2d95Urgent messages
--cm-elevation-flat0 1px 2px rgba(14, 14, 16, 0.04) / 0 0 0 1px rgba(255, 255, 255, 0.06)Rows and small surfaces resting on a card
--cm-elevation-card0 1px 2px rgba(14, 14, 16, 0.03), 0 6px 20px -6px rgba(14, 14, 16, 0.07) / 0 0 0 1px rgba(255, 255, 255, 0.06)Major containers: cards, panels, the tab bar. Replaces the grey stroke
--cm-elevation-raised0 2px 4px rgba(14, 14, 16, 0.04), 0 16px 40px -12px rgba(14, 14, 16, 0.14) / 0 0 0 1px rgba(255, 255, 255, 0.08), 0 24px 60px -20px rgba(0, 0, 0, 0.6)Menus, popovers, the floating tab bar
--cm-elevation-overlay0 4px 8px rgba(14, 14, 16, 0.05), 0 32px 72px -20px rgba(14, 14, 16, 0.28) / 0 0 0 1px rgba(255, 255, 255, 0.08), 0 24px 60px -20px rgba(0, 0, 0, 0.6)Dialogs and sheets over a scrim

Overlay

The overlay sits over a game, not over the app’s theme, so it carries its own colours. Dark glass is the default; he can switch the card to light in Overlay settings.

TokenValueWhere it lands
--cm-overlay-glassrgba(20, 20, 23, 0.82)The notification's ground
--cm-overlay-edgergba(255, 255, 255, 0.08)Its hairline
--cm-overlay-text#f6f6f7Name and message
--cm-overlay-text-2#9a9aa2Time and context
--cm-overlay-chiprgba(255, 255, 255, 0.07)The mood chip on glass

Mood colours

Each mood has one fill. They appear only inside a mood glyph, always beside the mood’s word.

TokenValueWhere it lands
--cm-p-mood-lonely#a78bfaLonely
--cm-p-mood-hungry#ff8a3dHungry
--cm-p-mood-bored#b7b7bfBored
--cm-p-mood-proud#ffd23fProud of you
--cm-p-mood-comehere#ff2d95Come here
--cm-p-mood-missyou#ff8ac4Missing you

Contrast

PairWhere it’s usedContrast
Magenta 500 on Neutral 950The ping and the fuse on the overlay5.6 : 1
White on Magenta 600Primary buttons and her bubbles, light5.4 : 1
Neutral 600 on whiteSecondary text, light6.9 : 1
Neutral 400 on Neutral 950Secondary text on the overlay6.9 : 1
Neutral 500 on whiteControl edges: switch, input4.4 : 1
  • Put magenta where someone at home is present: the ping, the fuse, her messages, Send.
  • Use Magenta 600 under white text; 500 under ink.
Don’t use it
  • A glow, gradient or second accent. Pink is flat and alone.
  • Neutral 500 for small text. At 4.4:1 it’s for control edges only.

Typography

Clash Display for the brand’s voice, Neue Montreal for everything a person reads quickly.

Concept · live ReactSource file: tokens.json

Faces

Clash Display 600 for the wordmark, headings and big numbers. Neue Montreal 400 and 500 for every message, label and setting.

AaClash Display 600The wordmark, headings and big numbers.
AaNeue Montreal 400 and 500Every message, label and setting.
DisplayClash 600 · 64Only your people.
HeadingClash 600 · 24Who can reach you
BodyMontreal 400 · 16Messages wait for the buy phase in ranked, unless they’re urgent.
Overlay messageMontreal 400 · 15dinner’s ready, come eat with me
LabelMontreal 500 · 13Allowed to interrupt · Quiet moments · Heads down
Key capMontreal 500 · 11F7 On my way · F8 After this match · F9 Love you

Scale

TokenValueWhere it lands
displayClash 600 · 64 / −5%Hero numbers, the wordmark
headingClash 600 · 24 to 34Screen titles, her invite
bodyMontreal 400 · 16Running text
overlay-messageMontreal 400 · 15The message on his screen
labelMontreal 500 · 13 to 14Names, controls, chips
captionMontreal 400 · 12Meta and time
keycapMontreal 500 · 11Key names

Rules

  • Use tabular figures for anything that changes: score, time left, counts.
  • Keep overlay text at 12px or larger. It’s read in a glance over a moving scene.
Don’t use it
  • Clash for running text or anything under 18px.
  • A monospace face, even for keys.
  • Neue Montreal at weight 100 or 200.

Spacing, shape and motion

A 4px grid, five radii, and three durations: fast, base and the eight-second fuse.

Concept · live ReactSource file: tokens.json

Spacing

A 4px grid. Steps are named by multiples of four.

space.14px
space.28px
space.312px
space.416px
space.520px
space.624px
space.832px
space.1040px
space.1456px
space.1872px

Radius

sm · 6px
md · 10px
lg · 16px
xl · 24px
full · 999px
  • sm for keycaps, md for small panels, lg for the notification and cards, xl for her phone’s cards, full for buttons, chips and avatars.

Motion

Motion only explains a change: something arrived, something left, time is running out.

TokenValueWhere it lands
--cm-duration-fast160msExits, switch knobs, hovers
--cm-duration-base200msThe notification arriving
--cm-duration-fuse8000msHow long a notification stays
--cm-ease-outcubic-bezier(0.2, 0.8, 0.2, 1)Every arrival

Reduced motion

  • Replace every slide with a plain fade, and stop the fuse animating. Its time limit still applies.
Don’t use it
  • Bounce, overshoot or anything that loops while a game is on screen.

Elevation

Depth comes from soft shadows, never a grey stroke: four steps from a resting row to a dialog.

Concept · live ReactSource file: tokens.json

Scale

Four steps. A surface lifts as it gets closer to the person: a row barely, a card a little, a menu more, a dialog most. Shadows are tinted with the ink colour and kept low, so the canvas never looks dirty.

Flat
Card
Raised
Overlay

Tokens

Semantic elevation tokens point at a primitive shadow scale, as colour does. Components read elevation, never a shadow step.

TokenValueWhere it lands
--cm-elevation-flat0 1px 2px rgba(14, 14, 16, 0.04) / 0 0 0 1px rgba(255, 255, 255, 0.06)Rows and small surfaces resting on a card
--cm-elevation-card0 1px 2px rgba(14, 14, 16, 0.03), 0 6px 20px -6px rgba(14, 14, 16, 0.07) / 0 0 0 1px rgba(255, 255, 255, 0.06)Major containers: cards, panels, the tab bar. Replaces the grey stroke
--cm-elevation-raised0 2px 4px rgba(14, 14, 16, 0.04), 0 16px 40px -12px rgba(14, 14, 16, 0.14) / 0 0 0 1px rgba(255, 255, 255, 0.08), 0 24px 60px -20px rgba(0, 0, 0, 0.6)Menus, popovers, the floating tab bar
--cm-elevation-overlay0 4px 8px rgba(14, 14, 16, 0.05), 0 32px 72px -20px rgba(14, 14, 16, 0.28) / 0 0 0 1px rgba(255, 255, 255, 0.08), 0 24px 60px -20px rgba(0, 0, 0, 0.6)Dialogs and sheets over a scrim
TokenValueWhere it lands
--cm-shadow-xs0 1px 2px rgba(14, 14, 16, 0.04)
--cm-shadow-sm0 1px 2px rgba(14, 14, 16, 0.03), 0 6px 20px -6px rgba(14, 14, 16, 0.07)
--cm-shadow-md0 2px 4px rgba(14, 14, 16, 0.04), 0 16px 40px -12px rgba(14, 14, 16, 0.14)
--cm-shadow-lg0 4px 8px rgba(14, 14, 16, 0.05), 0 32px 72px -20px rgba(14, 14, 16, 0.28)
--cm-shadow-edge-dark0 0 0 1px rgba(255, 255, 255, 0.06)Dark mode only
--cm-shadow-raised-dark0 0 0 1px rgba(255, 255, 255, 0.08), 0 24px 60px -20px rgba(0, 0, 0, 0.6)Dark mode only

In dark mode

A shadow disappears on a dark ground, so dark mode swaps it for a faint light edge, and raised surfaces add a deep shadow under that.

Flat
Card
Raised
Overlay

Usage

  • Card for every major container on a page. One step is enough; hierarchy comes from what’s inside.
  • Raised for anything that floats over content: menus, the tab bar.
Don’t use it
  • A grey stroke around a card. If it needs separating, it needs a shadow.
  • Stacking shadows: a card inside a card stays flat.