## What Rebinds the marketplace's expertise facet from `deploymentExpertise` (Cloud / Self-host) to **`partnerScope`** — the five partner Categories: Advisory & Discovery · Solutioning · Custom Development · Hosting & Infrastructure · Training & Adoption. Moves the card chip, the profile facts row, the dropdown filter, the `?categories=` URL param, and the API-boundary normalization onto `partnerScope`. The standalone Cloud/Self-host facet is **dropped** (hosting is now the `HOSTING` category), per the harmonization decision. ## Depends on - The app exposing `partnerScope` — companion app PR #21126. - The new `partnerScope` options + data migration — signup app PR #21040. ## Tests TDD red→green on: `filter-partners`, both API normalizers, `filter-url-helpers`, `PartnerCard`, `use-filter-state`. 53/53 pass; typecheck + lint + format clean. ## Merge order (we'll decide) Independent diff. Suggested last of the four, after the signup PRs (#21039 / #21040) and the app PR (#21126). Run `lingui:extract` once after #21039 merges so the `.po` files don't conflict twice. Deploy the app + migrate before the website ships.
7.1 KiB
Twenty Website — DESIGN.md
Visual system for the Twenty marketing site. Distilled from
packages/twenty-website/src/theme/. Loaded by everyimpeccableinvocation alongside PRODUCT.md.
Theme
Light by default. A founder browsing a partner profile in daylight on a 14–27 inch monitor is the default scene. The site does ship a data-scheme="dark" override (see css-variables.ts), but no current public page opts into it. Treat dark as a deferred surface.
Color
Palette is OKLCH-equivalent neutrals at the surface level. The brand accents (blue, pink, yellow, green) are present in the token system but used sparingly — none of them appear on the partner pages.
Strategy: Restrained
Tinted neutrals + one accent ≤10%. The accent for partner pages is the deep ink black (var(--color-black-100)) used in CTAs and hover states. Anything beyond a hairline border, an icon glyph, or a primary CTA should question whether it needs color at all.
Tokens (from src/theme/colors.ts + css-variables.ts)
Neutrals (the workhorses):
| Token | Hex (computed) | Role |
|---|---|---|
colors.primary.background[100] |
#ffffff |
Page + card surface |
colors.primary.text[100] |
#1c1c1c |
Headlines, primary text |
colors.primary.text[80] |
#1c1c1ccc |
Body text |
colors.primary.text[60] |
#1c1c1c99 |
Eyebrows, meta, captions |
colors.primary.text[40] |
#1c1c1c66 |
Disabled / placeholder |
colors.primary.text[20] |
#1c1c1c33 |
Subtle separators |
colors.primary.text[10] |
#1c1c1c1a |
Hairline borders |
colors.primary.text[5] |
#1c1c1c0d |
Subtle fills (rates panel, skill chips) |
colors.primary.border[10] |
#1c1c1c1a |
Default border |
colors.primary.border[20] |
#1c1c1c33 |
Hover border |
Reverse palette (for dark CTAs):
| Token | Role |
|---|---|
colors.secondary.background[100] |
Filled CTA background (deep ink) |
colors.secondary.text[100] |
Filled CTA text (white) |
Brand accents (currently absent from partner pages; available if needed):
colors.accent.blue—#4a38f5/#8174f8colors.accent.pink—#ed87fc/#f3abfdcolors.accent.yellow—#feffb7/#feffd9colors.accent.green—#89fc9a/#b0fdbecolors.highlight— same hue as blue accent
Do not introduce gradients, glass blurs, or saturated fills on partner pages. Color is conviction here, not decoration.
Typography
Three families, each load-balanced via CSS variables:
| Family | Var | Use |
|---|---|---|
theme.font.family.serif |
--font-serif |
Headlines, partner names, headline values |
theme.font.family.sans |
--font-sans |
Body, prose, interactive labels |
theme.font.family.mono |
--font-mono |
Eyebrows, meta, currency labels, tabular numerics |
theme.font.family.retro |
--font-retro |
Reserved (not used on partner pages) |
Weight + Size Contrast
Weights: light: 300, regular: 400, medium: 500. No bold. Hierarchy is driven by scale and family contrast, never by weight alone.
Scale (theme.font.size(n) → calc(var(--font-base) * n), where --font-base: 0.25rem ≈ 4px):
- Display / h1: size 9–12 (36–48px)
- h2 / section heads: size 7–8 (28–32px)
- h3 / card heads: size 5–6 (20–24px)
- Body / prose: size 4–5 (16–20px)
- Eyebrow / meta: size 3 (12px) with
letter-spacing: 0.06–0.08emandtext-transform: uppercase
Body line length: cap at 65–75ch (the existing PartnerProfileIntro uses max-width: 62ch — keep that order of magnitude).
Hierarchy contract
- A serif
<h1>at size 9 light reads as a partner's name on the detail page. - A mono eyebrow above or below it locates the partner (region · city · country).
- A serif size 6 light reads as a section head.
- Body prose is sans regular.
- Currency values are serif (they read as headline numbers, not stats).
- Currency labels and meta are mono.
Spacing & Layout
Base unit 4px. Spacing helper theme.spacing(n) returns n * 4px. Common rhythms on the partner pages:
- Inter-section gap on the detail page:
theme.spacing(10–14)— generous, editorial breathing room. - Inter-element gap inside a section:
theme.spacing(3–5). - Card padding:
theme.spacing(6). - Page horizontal padding:
theme.spacing(4)mobile,theme.spacing(10)≥ md breakpoint.
Radius
theme.radius(n) returns n * 2px. The default card radius is theme.radius(2) = 4px. Pills use 999px. No softer rounding than that.
Borders
Borders are hairline (1px solid theme.colors.primary.border[10]). They define edges quietly. On hover they step to border[20]. Never use a chunky border as decoration.
Components
Card (PartnerCard, RatesPanel)
White surface, hairline border, 4px radius, 24px padding, soft shadow on hover only:
background-color: ${theme.colors.primary.background[100]};
border: 1px solid ${theme.colors.primary.border[10]};
border-radius: ${theme.radius(2)};
padding: ${theme.spacing(6)};
&:hover {
border-color: ${theme.colors.primary.border[20]};
box-shadow: 0 12px 32px -16px rgba(0, 0, 0, 0.18);
transform: translateY(-2px);
}
Chip / Pill
Rounded 999px, 1px border, subtle background fill (primary.text[5] for filter pills, transparent for chip rows), text[80] color, mono or sans font.
Button / LinkButton
Lives in @/design-system/components. Two color modes: primary (deep ink fill, white text) and secondary (transparent fill, ink text + 1px border). variant="contained" is what partner pages use.
Avatar
PartnerAvatar is a deterministic generated mark from name + slug. Used as fallback when profilePictureUrl is missing. The real photo overlays it at 120px circle on the detail page, 56px on the list card.
Motion
- Hover transitions: 250ms, ease-out (cubic-bezier curve in
PartnerCard:0.25s ease). - Card entrance: 700ms cubic-bezier
0.22, 1, 0.36, 1(ease-out-quart), 90ms stagger per index. - All motion respects
@media (prefers-reduced-motion: reduce)— animations stop, hover translate disabled. - No bounce, no elastic, no parallax. Editorial restraint.
Iconography
@tabler/icons-react, 14–16px on body-level chips, 18–24px on buttons. Always aria-hidden="true" when decorative. Stroke width 2 (default).
Accessibility Defaults
- Focus ring:
outline: 2px solid theme.colors.primary.text[100]; outline-offset: 4px(already used on the card link). - Touch target ≥ 40×40px on mobile.
aria-labelon icon-only buttons,aria-labelledbyon sectioned regions.- All
<a target="_blank">includesrel="noopener noreferrer". - Color is never the sole carrier of meaning. The money pills carry both an icon and a text label.
Anti-patterns (project-specific)
In addition to the impeccable shared absolute bans:
- Do not use the brand accent colors (blue/pink/yellow/green) on partner pages unless we have a stronger reason than "to add color".
- No skeuomorphic shadows on cards. The hover shadow is
0 12px 32px -16px rgba(0,0,0,0.18)— that's the ceiling. - No gradients on anything. Including text, borders, and backgrounds.
- No floating "Trusted by" logo bars on partner pages.