Foundations
Colors
The warm Peri palette is canonical: creams, beiges, warm browns, muted oranges, soft golds and earthy neutrals — with green reserved exclusively for data. Click any swatch to copy its hex.
Palette law
Surfaces
The canvas-to-card ladder. Cards are pure white on a cool-tinted canvas; the warm surface (#F7F4F2) marks interactive insets.
Text — warm brown ramp
The strongest brand signal in the system: deep warm brown instead of grey-black. Grey text is off-system.
Primary — orange
Orange means interaction and identity, exclusively. It is never used as a data color.
Semantic
Three feedback hues, used sparingly so they keep their meaning.
Chart — green family
Green is reserved for data and positivity so orange keeps exclusive meaning as interaction. The two never compete on one element.
Cycle-phase pastels (retained)
Inherited from the earlier product generation; survive only in calendar/phase marking. Flagged for a future warm-harmonisation pass — the luteal pink is the furthest from the warm palette without being purple.
Gradients — the only four
Gradients are sanctioned in exactly four places. New gradients are off-system; the portal itself uses none beyond these.
gradient.button
Primary CTA, selected chips, selected scale cells
gradient.fab
Floating + button, active day card
gradient.header
App top bar (Figma intent), demo banner
gradient.splash
Splash screen only — warm blush, one-off
Accessibility checker
Live WCAG contrast ratios for the system's real text/surface pairs. Muted text intentionally trades contrast for hierarchy and is restricted to non-essential captions.
| Sample | Pair | Ratio | WCAG |
|---|---|---|---|
| Aa | Primary text on card#673228 on #FFFFFF · body | 10.18 | AAA |
| Aa | Secondary text on card#7A5D53 on #FFFFFF · body | 5.97 | AA |
| Aa | Muted text on card#B59386 on #FFFFFF · caption | 2.81 | Fails |
| Aa | Primary text on warm surface#673228 on #F7F4F2 · body | 9.29 | AAA |
| Aa | White on gradient end#FFFFFF on #C94800 · 15px/600 | 4.76 | AA |
| Aa | White on accent (gradient top)#FFFFFF on #F39021 · 15px/600 | 2.38 | Fails |
| Aa | Active tab on pill#C2581D on #F7F4F2 · 11px/600 | 4.07 | AA Large |
| Aa | Accent-dark on tint#C94800 on #F1E3D6 · body | 3.79 | Large only |
| Aa | Alert on card#D1495B on #FFFFFF · body | 4.36 | Large only |
| Aa | Chart green on card#0E9455 on #FFFFFF · body | 3.90 | Large only |
Known tradeoffs
text.muted(#B59386) fails AA on white — by design it is limited to captions, placeholders and timestamps that are never the only carrier of information.- White on
#F39021passes only at large/bold sizes — which is why CTA labels are 15px/600 and the gradient darkens toward#C94800. - The 11px active-tab label uses
#C2581D(not #F39021) precisely to hold contrast at small sizes — this is the documented reason two “primary dark” tokens exist.
Token structure
Shipped code uses flat CSS custom properties. For scaling, tokens map into three tiers without renaming the shipped vars:
| Tier | Example | Notes |
|---|---|---|
| 1 · Primitive | peri.palette.orange.500 = #F39021 | Raw values; never referenced by components directly |
| 2 · Semantic | peri.color.action.primary → orange.500 | Meaning, not appearance; what this portal documents |
| 3 · Component | peri.button.primary.bg → color.action.primary | Per-component overrides only where needed |