One brand. Five products.
Each with its own signal.
Every ExpiWell application shares one core — void slates, chalk text, cream intermissions, and the lime call-to-action. Each product leads with a single dominant accent: the signal color that identifies which app you're in. The other four accents stay in the room as sub-colors — for data categories, charts, and semantic states — so five apps always feel like one company.
● AA build — every text pairing ≥ 4.5:1 · UI boundaries ≥ 3:1 · WCAG 2.1
The constant layer
Shared by every application and the marketing site. These never change per product — they're what makes five apps feel like one company. Click any swatch to copy its hex.
Same tokens, both modes
Components never reference raw hexes — they reference the semantic layer, and the layer re-scopes per band. Dark bands map to the void slates; light bands map to cream and white. The descending ink hierarchy — ink → ink-2 → ink-3 → mute — creates visual weight without reaching for color.
● Edit one file → every app updates. Brand differences are a handful of CSS-variable overrides; everything here stays constant.
Every accent, five jobs
Each accent exists at five steps. Bright is the original app color — for dots, charts, and UI on dark. Hover is one notch of pressure. Muted is the globe-toned sibling for large fills on cream. Tint is the pale wash behind pills and banners. Deep passes AA contrast for text on light backgrounds and on its own tint. Each accent also names its solid — the button fill tuned to pass AA with that accent's ink: the light hues rest on the bright itself with void text; the saturated hues rest one step deeper so chalk text clears 4.5:1.
● Rule of thumb — dots & charts: bright. Buttons: solid + ink. Large fills on cream: muted. Pill & banner backgrounds: tint. Text on cream or tint: deep. Every pairing shown here measures ≥ 4.5:1.
One accent leads, four support
The dominant accent drives CTAs, focus, and identity. The remaining four accents become the app's ordered sub-colors — the palette for data categories, chart series, and secondary highlights. Components, spacing, and type never change between products.
Research Platform
Universities, labs & organizations running EMA and ESM studies.
ExpiWell Practice
Clinicians, therapists & coaches using EMA with clients.
Polling
In-the-moment public opinion, captured where it forms.
Pharmaceutical
Real-world evidence and patient-reported outcomes for trials.
Corporate Wellbeing
Employee wellbeing, measured in daily life — not annual surveys.
Capture experience as it actually happens.
Same components, same layout, same type — only --accent and the sub-color order change.
Light band — same tokens, inverted
Text stays readable because links use the deep step: an accent link
The palette does double duty
Status colors aren't a second palette — they're drawn from the family. Warn borrows the gold scale, info borrows the blue scale, success borrows the green scale. Only danger stands apart, reserved for destructive actions and errors. Each role ships its base (icons and dots), an AA solid for buttons, a tint, and a dark text step — and a tint background is always paired with its matching dark text.
Accent
The brand signal — switches with the app. Active states, live indicators, carets.
Info · from the blue scale
In-progress states, help banners, tip strips.
Warn · from the gold scale
Caution and past-due states — never errors.
Success · from the green scale
Saved, completed, and healthy states.
Danger · stands apart
Destructive only — delete buttons, error icons, failed saves.
Approved pairings. Always pair a tint background with its matching dark text variant. Mixing across roles breaks contrast and looks broken.
Three voices, one hierarchy
Fraunces 300–500
Capture experience as it happens.
Geist 400–600
High-fidelity data collection and analytics for scientific discovery — captured in the moment, on any device, anywhere in the world.
Geist Mono 400–500
The text atoms. Nine named variants cover every surface — the same names the <Text> atom exposes, so marketing and product share a vocabulary. Display sizes are fluid and always Fraunces; UI and body are Geist; anything uppercase, tabular, or data-flavored is Geist Mono.
● Emphasis in display copy = one italic Fraunces word in the accent's deep step. Bold is for UI, never headlines.
Sharp corners, soft light
3px is the default everywhere — almost square, but soft enough not to feel hostile. Two larger steps exist for grouped surfaces; pill (999) is reserved for toggle switches and status dots, never buttons. Depth comes from a quiet elevation scale tinted with void slate — never pure black — and motion uses three durations with one default ease.
● All shadow color = rgba(35,44,55) — the void, diluted. Never rgba(0,0,0). One ease for everything: cubic-bezier(.2,.8,.2,1).
Fields & actions
The canonical form controls and button variants shared by every app on marketing surfaces. Hover any button to feel the one-level lift; click into a field to see the neutral focus ring. Switch the brand in the lab below and the accent variants re-tone too.
Buttons
Primary is always lime — reserved for the one main action. Accent buttons rest on the app's solid step with its ink: void text on the light hues, chalk on the saturated ones. Every resting and hover state measures ≥ 4.5:1. Everything hovers with a one-level lift.
VariantsSizesInputs & selects
Label above, white field, 1px hairline outline. Focus is neutral in every app — the border darkens to ink with a faint slate halo — so a red glow can only ever mean an error. The brand lives in the caret and in toggles, never in the field glow: no accent can collide with danger, in any current or future app.
● Enter a valid email address
● Focus = ink outline + 3px slate halo at 12%, identical in all five apps. Caret-color carries the accent. The danger glow is reserved for errors — pink brands never collide with red.
Every state, both modes
Each panel forces its own color scheme so you can review parity without toggling anything. Both panels render with the currently selected brand — pick one and watch buttons, pills, banners, and the sub-color chart re-tone in light and dark at once. Click into any field: the focus ring stays neutral while the caret takes the brand color.
Light mode
● Enter a valid email address
Sample card title
Cards live on surface with a hairline border and resting e1. They adapt automatically across modes and brands — nothing here references a raw hex.
Dark mode
● Enter a valid email address
Sample card title
Cards live on surface with a hairline border and resting e1. They adapt automatically across modes and brands — nothing here references a raw hex.
● On dark, tints become the role color at 14–16% over the surface, and text lifts to the bright step — same tokens, re-scoped.
The habits that make it ours
3px corners, everywhere
Cards, buttons, inputs, badges. No pills, no soft rounds. Pill radius is reserved for toggle switches and signal dots.
The dot means live
A small colored circle prefixes mono labels — it marks real signals (responses, status, categories), never decoration.
Italic serif = emphasis
One italic Fraunces word per headline, colored in the accent's deep step. Never bold for emphasis in display copy.
Elevation is quiet
Resting surfaces sit at e1; hover lifts exactly one level; focus adds a quiet ink ring — never the accent, so the red error glow stays unmistakable. Shadows are slate-tinted, layered, never black.
Semantic tokens only
Components reference --bg, --surface, --ink, --line, --accent — never raw hexes. Modes and light bands re-scope the same tokens.
One accent leads, four support
Only the dominant accent drives CTAs, focus, and identity. The other accents work as the app's ordered sub-colors — data categories, chart series, semantic states — never competing for CTA duty.
Tint pairs with its own deep
A tint background always carries its matching dark text — accent-tint × accent-deep, warn-tint × warn-dark. Mixing across roles breaks contrast.
Lime is the ask
The lime CTA belongs to the company, not any one app. It marks the primary conversion action on every surface, in every product.
Every solid carries its ink
Buttons rest on the accent's solid step with its ink token — void on green, gold & lime, chalk on blue, pink & violet — tuned so every label measures ≥ 4.5:1 in any brand.
Drop-in CSS
What globals.css ships — core stays constant; products opt in with a single data-app attribute on the root. Switching brand changes the accent tokens and the sub-color order — nothing else.
:root {
/* core — constant across all apps */
--void: #232C37; --void-2: #2B3542; --panel: #2B3542;
--panel-2: #39434F; --hair: #414B58;
--chalk: #FBFCF8; --chalk-2: #C2C7CB;
--dust: #9AA3AD; --dust-d: #5B6572;
--paper: #F4F6EE; --cream: #F7F6EC;
--lime: #C3D93F; --lime-d: #51791B;
/* semantic layer — re-scoped per band (dark values shown) */
--bg: var(--void); --surface: var(--panel); --surface-2: var(--panel-2);
--ink: var(--chalk); --ink-2: var(--chalk-2); --ink-3: var(--dust);
--mute: #8A94A0; --line: var(--hair); --line-2: #7E8996;
/* accent scales — bright / hover / muted / tint / deep */
--green: #8DC63F; --green-h: #7CB32F; --green-m: #9FBF6B; --green-t: #EEF6DC; --green-d: #51791B;
--blue: #4285D6; --blue-h: #3572BC; --blue-m: #5F8FC0; --blue-t: #E7EFF9; --blue-d: #31659E;
--gold: #F2C230; --gold-h: #E0B01F; --gold-m: #E2B23F; --gold-t: #FBF1D3; --gold-d: #7E6110;
--pink: #E8578C; --pink-h: #D4467A; --pink-m: #C4789B; --pink-t: #FBE5EE; --pink-d: #A63A66;
--violet: #8A6FC5; --violet-h: #7A5DB6; --violet-m: #9C8DBE; --violet-t: #EFEBF8; --violet-d: #5F4699;
/* ink per accent — the text color for that accent's solid fills */
--green-ink: #232C37; --lime-ink: #232C37; --gold-ink: #232C37;
--blue-ink: #FFFFFF; --pink-ink: #FFFFFF; --violet-ink: #FFFFFF;
/* solid steps — AA button fills (light hues = bright; saturated rest deeper) */
--green-s: var(--green); --green-sh: var(--green-h);
--blue-s: var(--blue-h); --blue-sh: var(--blue-d);
--gold-s: var(--gold); --gold-sh: var(--gold-h);
--pink-s: #C63A6B; --pink-sh: var(--pink-d);
--violet-s: var(--violet-h); --violet-sh: var(--violet-d);
/* lifted steps — AA pill text over dark tints */
--green-lift: var(--green); --gold-lift: var(--gold);
--blue-lift: #9DBEE0; --pink-lift: #F2A9C4; --violet-lift: #BCA8E3;
/* roles — the palette doing double duty (danger stands apart) */
--danger: #E55448; --danger-h: #C9453A; --danger-t: #FBE7E4; --danger-d: #9A2F27;
--warn: var(--gold-m); --warn-h: #C99A28; --warn-t: var(--gold-t); --warn-d: var(--gold-d);
--info: var(--blue-m); --info-h: #4A7BAE; --info-t: var(--blue-t); --info-d: var(--blue-d);
--ok: var(--green); --ok-h: #679A23; --ok-t: var(--green-t); --ok-d: var(--green-d);
--danger-ink: #FFFFFF; --warn-ink: #232C37; --info-ink: #FFFFFF; --ok-ink: #232C37;
--danger-s: var(--danger-h); --danger-sh: #A83228;
--warn-s: var(--warn); --warn-sh: var(--warn-h);
--info-s: #4374A3; --info-sh: var(--info-d);
--ok-s: var(--ok); --ok-sh: var(--green-h);
/* focus — neutral in every app; accents never glow on text fields */
--focus-border: var(--ink);
--focus-ring: 0 0 0 3px color-mix(in srgb, var(--ink) 12%, transparent);
/* shape · elevation · motion */
--radius-sm: 3px; --radius-md: 4px; --radius-lg: 6px; --radius-pill: 999px;
--e1: 0 1px 2px rgba(35,44,55,.05), 0 1px 3px rgba(35,44,55,.08);
--e2: 0 2px 4px rgba(35,44,55,.05), 0 4px 10px rgba(35,44,55,.08);
--e3: 0 4px 10px rgba(35,44,55,.06), 0 10px 24px rgba(35,44,55,.10);
--e4: 0 8px 18px rgba(35,44,55,.08), 0 18px 40px rgba(35,44,55,.12);
--duration-fast: .12s; --duration-base: .15s; --duration-slow: .25s;
--ease: cubic-bezier(.2, .8, .2, 1);
}
/* light bands re-scope the semantic layer — components don't change */
.light-band {
--bg: var(--cream); --surface: #FFFFFF; --surface-2: #FAFAF3;
--ink: var(--void); --ink-2: var(--void-2); --ink-3: var(--dust-d);
--mute: #667079; --line: #E4E2D4; --line-2: #8B8672; /* line-2 = input borders, >=3:1 */
}
/* one dominant accent per application + ordered sub-colors */
[data-app='research'] { --accent: var(--green); --accent-hov: var(--green-h);
--accent-t: var(--green-t); --accent-deep: var(--green-d); --accent-contrast: var(--green-ink);
--accent-solid: var(--green-s); --accent-solid-h: var(--green-sh); --accent-lift: var(--green-lift);
--sub-1: var(--blue); --sub-2: var(--gold); --sub-3: var(--pink); --sub-4: var(--violet); }
[data-app='practice'] { --accent: var(--blue); --accent-hov: var(--blue-h);
--accent-t: var(--blue-t); --accent-deep: var(--blue-d); --accent-contrast: var(--blue-ink);
--accent-solid: var(--blue-s); --accent-solid-h: var(--blue-sh); --accent-lift: var(--blue-lift);
--sub-1: var(--green); --sub-2: var(--gold); --sub-3: var(--violet); --sub-4: var(--pink); }
[data-app='polling'] { --accent: var(--gold); --accent-hov: var(--gold-h);
--accent-t: var(--gold-t); --accent-deep: var(--gold-d); --accent-contrast: var(--gold-ink);
--accent-solid: var(--gold-s); --accent-solid-h: var(--gold-sh); --accent-lift: var(--gold-lift);
--sub-1: var(--blue); --sub-2: var(--green); --sub-3: var(--pink); --sub-4: var(--violet); }
[data-app='pharma'] { --accent: var(--pink); --accent-hov: var(--pink-h);
--accent-t: var(--pink-t); --accent-deep: var(--pink-d); --accent-contrast: var(--pink-ink);
--accent-solid: var(--pink-s); --accent-solid-h: var(--pink-sh); --accent-lift: var(--pink-lift);
--sub-1: var(--violet); --sub-2: var(--blue); --sub-3: var(--gold); --sub-4: var(--green); }
[data-app='wellbeing'] { --accent: var(--violet); --accent-hov: var(--violet-h);
--accent-t: var(--violet-t); --accent-deep: var(--violet-d); --accent-contrast: var(--violet-ink);
--accent-solid: var(--violet-s); --accent-solid-h: var(--violet-sh); --accent-lift: var(--violet-lift);
--sub-1: var(--pink); --sub-2: var(--blue); --sub-3: var(--green); --sub-4: var(--gold); }
/* dark-mode toggle — set data-theme='dark' on <html> (header ThemeToggle) to
force the cream light-bands back onto the dark semantic layer, so the whole
page renders uniformly dark. Only [data-theme='dark'] carries overrides; the
default (light) render is untouched. */
html[data-theme='dark'] .light-band {
--bg: var(--void); --surface: var(--panel); --surface-2: var(--panel-2);
--ink: var(--chalk); --ink-2: var(--chalk-2); --ink-3: var(--dust);
--line: var(--hair); --accent-ink: var(--accent); /* …the full :root dark scope */
}
/* .theme-lock opts a band out of the flip — this page keeps its cream
mode-parity panes (and their AA deep accents) even in dark mode. */
html[data-theme='dark'] .theme-lock .light-band { /* …the cream scope, re-asserted */ }Using tokens in code — the house patterns
// Typography — only via the <Text> atom (variants in typography-tokens.ts)
<Text variant="heading" as="h3">Event-triggering for EMA & ESM</Text>
// Buttons — only via <AppButton>: primary | cta | accent | ghost | danger | text
<AppButton variant="cta" withArrow>Get Started</AppButton>
// Direct CSS var in sx (layout + token references only — never raw hexes)
<Box sx={{ boxShadow: 'var(--e2)', borderRadius: 'var(--radius-sm)' }} />
// Status pill recipe — tint background × matching deep text
<Chip color="success" label="Completed" />
// Text field recipe — neutral focus, brand caret, danger owns the glow
// (already themed globally: MuiOutlinedInput in styles/theme/components.ts)
<FormControl>
<FormLabel>Email</FormLabel>
<OutlinedInput placeholder="you@university.edu" />
</FormControl>
// Chart series — dominant signal first, then the sub-color order
const SERIES = ['var(--accent)', 'var(--sub-1)', 'var(--sub-2)', 'var(--sub-3)', 'var(--sub-4)'];One wordmark, two inks
The ExpiWell wordmark ships in two ready-to-place variants — download the one built for your background and place it as-is: never recolor it, stretch it, or rebuild it from type.
Light mode
Near-black ink on transparent — for white, cream, and other light surfaces.

Dark mode
White on transparent — for the void slates and other dark surfaces.

● Both files serve same-origin from /brand/. The light variant doubles as the site's Organization JSON-LD logo — its path never changes.
Built once. Themed five ways.
● ExpiWell brand system · v3.1 · WCAG 2.1 AA · 2026