Website last fixes (#19895)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Thomas des Francs 2026-04-21 07:43:06 +02:00 committed by GitHub
parent 6ef15713b1
commit 3ee1b528a1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
60 changed files with 383 additions and 184 deletions

203
README.md
View file

@ -1,126 +1,170 @@
<p align="center">
<a href="https://www.producthunt.com/products/twenty-crm?launch=twenty-2-0">
<img src="./packages/twenty-website/public/images/readme/product-hunt-banner.png" alt="We're live on Product Hunt — Support us" />
</a>
</p>
<p align="center">
<a href="https://www.twenty.com">
<img src="./packages/twenty-website/public/images/core/logo.svg" width="100px" alt="Twenty logo" />
</a>
</p>
<h2 align="center" >The #1 Open-Source CRM </h2>
<p align="center"><a href="https://twenty.com">🌐 Website</a> · <a href="https://docs.twenty.com">📚 Documentation</a> · <a href="https://github.com/orgs/twentyhq/projects/1"><img src="./packages/twenty-website/public/images/readme/planner-icon.svg" width="12" height="12"/> Roadmap </a> · <a href="https://discord.gg/cx5n4Jzs57"><img src="./packages/twenty-website/public/images/readme/discord-icon.svg" width="12" height="12"/> Discord</a> · <a href="https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty"><img src="./packages/twenty-website/public/images/readme/figma-icon.png" width="12" height="12"/> Figma</a></p>
<br />
<h2 align="center" >The #1 Open-Source CRM</h2>
<p align="center"><a href="https://twenty.com"><img src="./packages/twenty-website/public/images/readme/globe-icon.svg" width="12" height="12"/> Website</a> · <a href="https://docs.twenty.com"><img src="./packages/twenty-website/public/images/readme/book-icon.svg" width="12" height="12"/> Documentation</a> · <a href="https://github.com/orgs/twentyhq/projects/1"><img src="./packages/twenty-website/public/images/readme/map-icon.svg" width="12" height="12"/> Roadmap </a> · <a href="https://discord.gg/cx5n4Jzs57"><img src="./packages/twenty-website/public/images/readme/discord-icon.svg" width="12" height="12"/> Discord</a> · <a href="https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty"><img src="./packages/twenty-website/public/images/readme/figma-icon.png" width="12" height="12"/> Figma</a></p>
<p align="center">
<a href="https://www.twenty.com">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-website/public/images/readme/github-cover-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-website/public/images/readme/github-cover-light.png" />
<img src="./packages/twenty-website/public/images/readme/github-cover-light.png" alt="Cover" />
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website/public/images/readme/github-cover-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website/public/images/readme/github-cover-light.png" />
<img src="./packages/twenty-website/public/images/readme/github-cover-light.png" alt="Twenty banner" />
</picture>
</a>
</p>
<br />
# Installation
See:
🚀 [Self-hosting](https://docs.twenty.com/developers/self-host/capabilities/docker-compose)
🖥️ [Local Setup](https://docs.twenty.com/developers/contribute/capabilities/local-setup)
# Why Twenty
We built Twenty for three reasons:
Twenty gives technical teams the building blocks for a custom CRM that meets complex business needs and quickly adapts as the business evolves. Twenty is the CRM you build, ship, and version like the rest of your stack.
**CRMs are too expensive, and users are trapped.** Companies use locked-in customer data to hike prices. It shouldn't be that way.
**A fresh start is required to build a better experience.** We can learn from past mistakes and craft a cohesive experience inspired by new UX patterns from tools like Notion, Airtable or Linear.
**We believe in open-source and community.** Hundreds of developers are already building Twenty together. Once we have plugin capabilities, a whole ecosystem will grow around it.
<a href="https://twenty.com/why-twenty"><img src="./packages/twenty-website/public/images/readme/star-icon.svg" width="14" height="14"/> Learn more about why we built Twenty</a>
<br />
# What You Can Do With Twenty
# Installation
Please feel free to flag any specific needs you have by creating an issue.
### <img src="./packages/twenty-website/public/images/readme/globe-icon.svg" width="14" height="14"/> Cloud
Below are a few features we have implemented to date:
The fastest way to get started. Sign up at [twenty.com](https://twenty.com) and spin up a workspace in under a minute, with no infrastructure to manage and always up to date.
+ [Personalize layouts with filters, sort, group by, kanban and table views](#personalize-layouts-with-filters-sort-group-by-kanban-and-table-views)
+ [Customize your objects and fields](#customize-your-objects-and-fields)
+ [Create and manage permissions with custom roles](#create-and-manage-permissions-with-custom-roles)
+ [Automate workflow with triggers and actions](#automate-workflow-with-triggers-and-actions)
+ [Emails, calendar events, files, and more](#emails-calendar-events-files-and-more)
### <img src="./packages/twenty-website/public/images/readme/book-icon.svg" width="14" height="14"/> Build an app
Scaffold a new app with the Twenty CLI:
## Personalize layouts with filters, sort, group by, kanban and table views
```bash
npx create-twenty-app my-app
```
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-website/public/images/readme/views-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-website/public/images/readme/views-light.png" />
<img src="./packages/twenty-website/public/images/readme/views-light.png" alt="Companies Kanban Views" />
</picture>
</p>
Define objects, fields, and views as code:
## Customize your objects and fields
```ts
import { defineObject, FieldType } from 'twenty-sdk/define';
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-website/public/images/readme/data-model-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-website/public/images/readme/data-model-light.png" />
<img src="./packages/twenty-website/public/images/readme/data-model-light.png" alt="Setting Custom Objects" />
</picture>
</p>
export default defineObject({
nameSingular: 'deal',
namePlural: 'deals',
labelSingular: 'Deal',
labelPlural: 'Deals',
fields: [
{ name: 'name', label: 'Name', type: FieldType.TEXT },
{ name: 'amount', label: 'Amount', type: FieldType.CURRENCY },
{ name: 'closeDate', label: 'Close Date', type: FieldType.DATE_TIME },
],
});
```
## Create and manage permissions with custom roles
Then ship it to your workspace:
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-website/public/images/readme/permissions-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-website/public/images/readme/permissions-light.png" />
<img src="./packages/twenty-website/public/images/readme/permissions-light.png" alt="Permissions" />
</picture>
</p>
```bash
npx twenty deploy
```
## Automate workflow with triggers and actions
See the [app development guide](https://docs.twenty.com/developers/extend/apps/getting-started) for objects, views, agents, and logic functions.
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-website/public/images/readme/workflows-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-website/public/images/readme/workflows-light.png" />
<img src="./packages/twenty-website/public/images/readme/workflows-light.png" alt="Workflows" />
</picture>
</p>
### <img src="./packages/twenty-website/public/images/readme/rocket-icon.svg" width="14" height="14"/> Self-hosting
## Emails, calendar events, files, and more
Run Twenty on your own infrastructure with [Docker Compose](https://docs.twenty.com/developers/self-host/capabilities/docker-compose), or contribute locally via the [local setup guide](https://docs.twenty.com/developers/contribute/capabilities/local-setup).
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-website/public/images/readme/plus-other-features-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-website/public/images/readme/plus-other-features-light.png" />
<img src="./packages/twenty-website/public/images/readme/plus-other-features-light.png" alt="Other Features" />
</picture>
</p>
<br />
<br />
# Everything you need
Twenty gives you the building blocks of a modern CRM (objects, views, workflows, and agents) and lets you extend them as code. Here's a tour of what's in the box.
Want to go deeper? Read the <a href="https://docs.twenty.com/user-guide/introduction"><img src="./packages/twenty-website/public/images/readme/planner-icon.svg" width="14" height="14"/> User Guide</a> for product walkthroughs, or the <a href="https://docs.twenty.com"><img src="./packages/twenty-website/public/images/readme/book-icon.svg" width="14" height="14"/> Documentation</a> for developer reference.
<table align="center">
<tr>
<td width="50%">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website/public/images/readme/v2-build-apps-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website/public/images/readme/v2-build-apps-light.png" />
<img src="./packages/twenty-website/public/images/readme/v2-build-apps-light.png" alt="Create your apps" />
</picture>
<p align="center"><a href="https://docs.twenty.com/developers/extend/apps/getting-started"><img src="./packages/twenty-website/public/images/readme/code-icon.svg" width="16" height="16"/> Learn more about apps in doc</a></p>
</td>
<td width="50%">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website/public/images/readme/v2-version-control-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website/public/images/readme/v2-version-control-light.png" />
<img src="./packages/twenty-website/public/images/readme/v2-version-control-light.png" alt="Stay on top with version control" />
</picture>
<p align="center"><a href="https://docs.twenty.com/developers/extend/apps/publishing"><img src="./packages/twenty-website/public/images/readme/monitor-icon.svg" width="16" height="16"/> Learn more about version control in doc</a></p>
</td>
</tr>
<tr>
<td width="50%">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website/public/images/readme/v2-all-tools-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website/public/images/readme/v2-all-tools-light.png" />
<img src="./packages/twenty-website/public/images/readme/v2-all-tools-light.png" alt="All the tools you need to build anything" />
</picture>
<p align="center"><a href="https://docs.twenty.com/developers/extend/apps/building"><img src="./packages/twenty-website/public/images/readme/rocket-icon.svg" width="16" height="16"/> Learn more about primitives in doc</a></p>
</td>
<td width="50%">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website/public/images/readme/v2-tools-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website/public/images/readme/v2-tools-light.png" />
<img src="./packages/twenty-website/public/images/readme/v2-tools-light.png" alt="Customize your layouts" />
</picture>
<p align="center"><a href="https://docs.twenty.com/user-guide/views-pipelines/overview"><img src="./packages/twenty-website/public/images/readme/planner-icon.svg" width="16" height="16"/> Learn more about layouts in doc</a></p>
</td>
</tr>
<tr>
<td width="50%">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website/public/images/readme/v2-ai-agents-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website/public/images/readme/v2-ai-agents-light.png" />
<img src="./packages/twenty-website/public/images/readme/v2-ai-agents-light.png" alt="AI agents and chats" />
</picture>
<p align="center"><a href="https://docs.twenty.com/user-guide/ai/overview"><img src="./packages/twenty-website/public/images/readme/message-icon.svg" width="16" height="16"/> Learn more about AI in doc</a></p>
</td>
<td width="50%">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website/public/images/readme/v2-crm-tools-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website/public/images/readme/v2-crm-tools-light.png" />
<img src="./packages/twenty-website/public/images/readme/v2-crm-tools-light.png" alt="Plus all the tools of a good CRM" />
</picture>
<p align="center"><a href="https://docs.twenty.com/user-guide/introduction"><img src="./packages/twenty-website/public/images/readme/star-icon.svg" width="16" height="16"/> Learn more about CRM features in doc</a></p>
</td>
</tr>
</table>
<br />
# Stack
- [TypeScript](https://www.typescriptlang.org/)
- [Nx](https://nx.dev/)
- [NestJS](https://nestjs.com/), with [BullMQ](https://bullmq.io/), [PostgreSQL](https://www.postgresql.org/), [Redis](https://redis.io/)
- [React](https://reactjs.org/), with [Jotai](https://jotai.org/), [Linaria](https://linaria.dev/) and [Lingui](https://lingui.dev/)
- <a href="https://www.typescriptlang.org/"><img src="./packages/twenty-website/public/images/readme/stack-typescript.svg" width="14" height="14"/> TypeScript</a>
- <a href="https://nx.dev/"><img src="./packages/twenty-website/public/images/readme/stack-nx.svg" width="14" height="14"/> Nx</a>
- <a href="https://nestjs.com/"><img src="./packages/twenty-website/public/images/readme/stack-nestjs.svg" width="14" height="14"/> NestJS</a>, with <a href="https://bullmq.io/">BullMQ</a>, <a href="https://www.postgresql.org/"><img src="./packages/twenty-website/public/images/readme/stack-postgresql.svg" width="14" height="14"/> PostgreSQL</a>, <a href="https://redis.io/"><img src="./packages/twenty-website/public/images/readme/stack-redis.svg" width="14" height="14"/> Redis</a>
- <a href="https://reactjs.org/"><img src="./packages/twenty-website/public/images/readme/stack-react.svg" width="14" height="14"/> React</a>, with <a href="https://jotai.org/">Jotai</a>, <a href="https://linaria.dev/">Linaria</a> and <a href="https://lingui.dev/">Lingui</a>
# Thanks
<p align="center">
<a href="https://www.chromatic.com/"><img src="./packages/twenty-website/public/images/readme/chromatic.png" height="30" alt="Chromatic" /></a>
<a href="https://greptile.com"><img src="./packages/twenty-website/public/images/readme/greptile.png" height="30" alt="Greptile" /></a>
<a href="https://sentry.io/"><img src="./packages/twenty-website/public/images/readme/sentry.png" height="30" alt="Sentry" /></a>
<a href="https://crowdin.com/"><img src="./packages/twenty-website/public/images/readme/crowdin.png" height="30" alt="Crowdin" /></a>
<a href="https://e2b.dev/"><img src="./packages/twenty-website/public/images/readme/e2b.svg" height="30" alt="E2B" /></a>
<a href="https://www.chromatic.com/"><img src="./packages/twenty-website/public/images/readme/chromatic.png" height="28" alt="Chromatic" /></a>
&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://greptile.com"><img src="./packages/twenty-website/public/images/readme/greptile.png" height="28" alt="Greptile" /></a>
&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://sentry.io/"><img src="./packages/twenty-website/public/images/readme/sentry.png" height="28" alt="Sentry" /></a>
&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://crowdin.com/"><img src="./packages/twenty-website/public/images/readme/crowdin.png" height="28" alt="Crowdin" /></a>
</p>
Thanks to these amazing services that we use and recommend for UI testing (Chromatic), code review (Greptile), catching bugs (Sentry) and translating (Crowdin).
@ -128,9 +172,4 @@ Below are a few features we have implemented to date:
# Join the Community
- Star the repo
- Subscribe to releases (watch -> custom -> releases)
- Follow us on [Twitter](https://twitter.com/twentycrm) or [LinkedIn](https://www.linkedin.com/company/twenty/)
- Join our [Discord](https://discord.gg/cx5n4Jzs57)
- Improve translations on [Crowdin](https://twenty.crowdin.com/twenty)
- [Contributions](https://github.com/twentyhq/twenty/contribute) are, of course, most welcome!
<p><a href="https://github.com/twentyhq/twenty"><img src="./packages/twenty-website/public/images/readme/star-icon.svg" width="12" height="12"/> Star the repo</a> · <a href="https://discord.gg/cx5n4Jzs57"><img src="./packages/twenty-website/public/images/readme/discord-icon.svg" width="12" height="12"/> Discord</a> · <a href="https://github.com/twentyhq/twenty/discussions"><img src="./packages/twenty-website/public/images/readme/message-icon.svg" width="12" height="12"/> Feature requests</a> · <a href="https://github.com/orgs/twentyhq/projects/1/views/35"><img src="./packages/twenty-website/public/images/readme/rocket-icon.svg" width="12" height="12"/> Releases</a> · <a href="https://twitter.com/twentycrm"><img src="./packages/twenty-website/public/images/readme/x-icon.svg" width="12" height="12"/> X</a> · <a href="https://www.linkedin.com/company/twenty/"><img src="./packages/twenty-website/public/images/readme/linkedin-icon.svg" width="12" height="12"/> LinkedIn</a> · <a href="https://twenty.crowdin.com/twenty"><img src="./packages/twenty-website/public/images/readme/language-icon.svg" width="12" height="12"/> Crowdin</a> · <a href="https://github.com/twentyhq/twenty/contribute"><img src="./packages/twenty-website/public/images/readme/code-icon.svg" width="12" height="12"/> Contribute</a></p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View file

@ -11,6 +11,7 @@ import { TalkToUsButton } from '@/app/components/ContactCalModal';
import { FAQ_DATA, MENU_DATA, TRUSTED_BY_DATA } from '@/app/_constants';
import { Body, Eyebrow, Heading, LinkButton } from '@/design-system/components';
import { Pages } from '@/enums/pages';
import { ArrowRightUpIcon } from '@/icons';
import { fetchCommunityStats } from '@/lib/community/fetch-community-stats';
import { mergeSocialLinkLabels } from '@/lib/community/merge-social-link-labels';
import { Faq } from '@/sections/Faq/components';
@ -28,11 +29,15 @@ import { styled } from '@linaria/react';
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Twenty | Open Source CRM',
description: 'Modular, scalable open source CRM for modern teams.',
title: 'Twenty | #1 open source CRM',
description:
'The #1 open source CRM for modern teams. Modular, scalable, and built to fit your business.',
};
const HOME_TOP_BACKGROUND_COLOR = '#F4F4F4';
const PRODUCT_HUNT_LAUNCH_URL =
'https://www.producthunt.com/products/twenty-crm?launch=twenty-2-0';
const PRODUCT_HUNT_BRAND_COLOR = '#DA552F';
const HeroHeadingGroup = styled.div`
align-items: center;
@ -41,11 +46,63 @@ const HeroHeadingGroup = styled.div`
gap: ${theme.spacing(3)};
width: 100%;
> *:nth-child(2) {
> *:last-child {
margin-top: 0;
}
`;
const HeroLaunchChip = styled.a`
align-items: center;
background: ${theme.colors.primary.background[100]};
border: 1px solid ${theme.colors.primary.border[10]};
border-radius: 999px;
color: ${theme.colors.primary.text[100]};
display: inline-flex;
font-family: ${theme.font.family.mono};
font-size: ${theme.font.size(2.5)};
font-weight: ${theme.font.weight.medium};
gap: ${theme.spacing(2)};
line-height: ${theme.lineHeight(3)};
padding: ${theme.spacing(2)} ${theme.spacing(3)};
text-decoration: none;
text-transform: uppercase;
transition:
border-color 180ms ease,
color 180ms ease,
transform 180ms ease;
white-space: nowrap;
&:is(:hover, :focus-visible) {
border-color: ${PRODUCT_HUNT_BRAND_COLOR};
color: ${PRODUCT_HUNT_BRAND_COLOR};
transform: translateY(-1px);
}
&:focus-visible {
outline: 1px solid ${theme.colors.highlight[100]};
outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
transition: none;
}
`;
const HeroLaunchChipDot = styled.span`
background: ${PRODUCT_HUNT_BRAND_COLOR};
border-radius: 999px;
display: block;
flex-shrink: 0;
height: ${theme.spacing(2)};
width: ${theme.spacing(2)};
`;
const HeroLaunchChipLabel = styled.span`
align-items: center;
display: inline-flex;
gap: ${theme.spacing(1.5)};
`;
const HeroIntroGroup = styled.div`
align-items: center;
display: flex;
@ -109,6 +166,17 @@ export default async function HomePage() {
<Hero.Root backgroundColor={HOME_TOP_BACKGROUND_COLOR} showHomeBackground>
<HeroIntroGroup data-halftone-exclude>
<HeroHeadingGroup>
<HeroLaunchChip
href={PRODUCT_HUNT_LAUNCH_URL}
rel="noopener noreferrer"
target="_blank"
>
<HeroLaunchChipDot />
<HeroLaunchChipLabel>
Live on Product Hunt
<ArrowRightUpIcon size={8} strokeColor="currentColor" />
</HeroLaunchChipLabel>
</HeroLaunchChip>
<Hero.Heading page={Pages.Home} segments={HERO_DATA.heading} />
<Hero.Body page={Pages.Home} body={HERO_DATA.body} size="sm" />
</HeroHeadingGroup>

View file

@ -25,7 +25,7 @@ export const FAQ_DATA: FaqDataType = {
fontFamily: 'sans',
},
answer: {
text: "Yes. Twenty is the #1 open-source CRM on GitHub. Most teams run it on our managed cloud for zero-ops setup; self-hosting is always available if you'd rather own the infrastructure.",
text: "Yes. Twenty is the #1 open source CRM on GitHub. Most teams run it on our managed cloud for zero-ops setup; self-hosting is always available if you'd rather own the infrastructure.",
},
},
{

View file

@ -40,7 +40,7 @@ export const MENU_DATA: MenuDataType = {
image: '/images/shared/menu/developers-preview.png',
imageAlt: 'Blue developer illustration with branching arrows',
imagePosition: 'center',
imageScale: 1.4,
imageScale: 1.6,
title: 'Build on an open platform',
description:
'APIs, SDKs and webhooks to extend Twenty and ship apps on top of your CRM data.',

View file

@ -120,7 +120,7 @@ export const CASE_STUDY_CATALOG_ENTRIES: CaseStudyCatalogEntry[] = [
'NetZero uses Twenty as a modular CRM across product lines and countries, with a roadmap into AI-assisted workflows.',
date: '2025',
coverImageSrc:
'https://images.unsplash.com/photo-1510524474345-1c4bac68d1d0?w=1600&q=80',
'https://images.unsplash.com/photo-1744830343976-ce690ba2a67c?w=1600&q=80',
},
},
{

View file

@ -10,7 +10,7 @@ import { theme } from '@/theme';
import type { Metadata } from 'next';
const PLACEHOLDER_HERO =
'https://images.unsplash.com/photo-1510524474345-1c4bac68d1d0?w=1600&q=80';
'https://images.unsplash.com/photo-1744830343976-ce690ba2a67c?w=1600&q=80';
const CASE_STUDY: CaseStudyData = {
meta: {

View file

@ -66,8 +66,9 @@ const StyledMain = styled.main`
`;
export const metadata: Metadata = {
title: 'Twenty | Open Source CRM',
description: 'Modular, scalable open source CRM for modern teams.',
title: 'Twenty | #1 open source CRM',
description:
'The #1 open source CRM for modern teams. Modular, scalable, and built to fit your business.',
};
export default function RootLayout({

View file

@ -6,6 +6,6 @@ export const HERO_DATA = {
{ text: 'our partner', fontFamily: 'sans', newLine: true },
],
body: {
text: "We're building the #1 open-source CRM, but we can't do it alone. Join our partner ecosystem and grow with us.",
text: "We're building the #1 open source CRM, but we can't do it alone. Join our partner ecosystem and grow with us.",
},
} satisfies HeroBaseDataType;

View file

@ -34,7 +34,7 @@ export const THREE_CARDS_ILLUSTRATION_DATA: ThreeCardsIllustrationDataType = {
{
heading: { text: 'Content & Community Partners', fontFamily: 'sans' },
body: {
text: "Share Twenty with your audience and help shape the future of open-source CRM. We're looking for creators, educators, and community builders who want to showcase great software.",
text: "Share Twenty with your audience and help shape the future of the #1 open source CRM. We're looking for creators, educators, and community builders who want to showcase great software.",
},
benefits: [
{ text: 'Revenue share for referred customers', icon: 'tag' },

View file

@ -26,6 +26,7 @@ import { ThreeCards } from '@/sections/ThreeCards/components';
import { TrustedBy } from '@/sections/TrustedBy/components';
import type { ThreeCardsScrollLayoutOptions } from '@/sections/ThreeCards/utils/three-cards-scroll-layout';
import { theme } from '@/theme';
import { styled } from '@linaria/react';
import type { Metadata } from 'next';
const PARTNER_ILLUSTRATION_CARDS_SCROLL_LAYOUT_OPTIONS: ThreeCardsScrollLayoutOptions =
@ -37,10 +38,18 @@ const PARTNER_ILLUSTRATION_CARDS_SCROLL_LAYOUT_OPTIONS: ThreeCardsScrollLayoutOp
stagger: 0.16,
};
const PromoSpacing = styled.div`
margin-bottom: ${theme.spacing(8)};
@media (min-width: ${theme.breakpoints.md}px) {
margin-bottom: ${theme.spacing(12)};
}
`;
export const metadata: Metadata = {
title: 'Partners | Twenty',
description:
'Join our partner ecosystem and grow with us as we build the #1 open-source CRM.',
'Join our partner ecosystem and grow with us as we build the #1 open source CRM.',
};
export default async function PartnerPage() {
@ -72,13 +81,16 @@ export default async function PartnerPage() {
<TrustedBy.Root
backgroundColor={theme.colors.primary.background[100]}
compactBottom
>
<TrustedBy.Separator separator={TRUSTED_BY_DATA.separator} />
<TrustedBy.Logos logos={TRUSTED_BY_DATA.logos} />
<TrustedBy.ClientCount label={TRUSTED_BY_DATA.clientCountLabel.text} />
</TrustedBy.Root>
<CaseStudyCatalog.Promo entries={CASE_STUDY_CATALOG_ENTRIES} />
<PromoSpacing>
<CaseStudyCatalog.Promo compactTop entries={CASE_STUDY_CATALOG_ENTRIES} />
</PromoSpacing>
<ThreeCards.Root backgroundColor={theme.colors.secondary.background[5]}>
<ThreeCards.Intro page={Pages.Partners} align="left">

View file

@ -37,7 +37,7 @@ const PricingBannerContainer = styled.div`
export const metadata: Metadata = {
title: 'Pricing | Twenty',
description:
'Plans that scale with your team. Compare tiers of the #1 open-source CRM.',
'Plans that scale with your team. Compare tiers of the #1 open source CRM.',
};
export default async function PricingPage() {

View file

@ -7,5 +7,5 @@ export const RELEASE_NOTES_HERO_HEADING: HeadingType[] = [
];
export const RELEASE_NOTES_HERO_BODY: BodyType = {
text: 'Discover the newest features and improvements in Twenty,\nthe #1 open-source CRM.',
text: 'Discover the newest features and improvements in Twenty,\nthe #1 open source CRM.',
};

View file

@ -21,7 +21,7 @@ import { Fragment } from 'react';
export const metadata: Metadata = {
title: 'Releases | Twenty',
description:
'Discover the newest features and improvements in Twenty, the open-source CRM.',
'Discover the newest features and improvements in Twenty, the #1 open source CRM.',
};
export default async function ReleasesPage() {

View file

@ -11,7 +11,6 @@ import { LinkButton } from '@/design-system/components';
import { Pages } from '@/enums/pages';
import { fetchCommunityStats } from '@/lib/community/fetch-community-stats';
import { mergeSocialLinkLabels } from '@/lib/community/merge-social-link-labels';
import { OverflowProbe } from '@/lib/debug/overflow-probe';
import { Editorial } from '@/sections/Editorial/components';
import { Hero } from '@/sections/Hero/components';
import { Marquee } from '@/sections/Marquee/components';
@ -72,7 +71,6 @@ export default async function WhyTwentyPage() {
return (
<>
<OverflowProbe />
<Menu.Root
backgroundColor={theme.colors.secondary.background[100]}
scheme="secondary"

View file

@ -1,4 +1,4 @@
const PLUS_PATH = "M.5 7.25H14M7.25 14V.5";
const PLUS_PATH = 'M1.5 7.5H13.5M7.5 13.5V1.5';
interface PlusIconProps {
size: number;
@ -13,10 +13,13 @@ export function PlusIcon({ size, strokeColor }: PlusIconProps) {
viewBox="0 0 15 15"
fill="none"
xmlns="http://www.w3.org/2000/svg"
overflow="visible"
style={{ display: 'block' }}
>
<path
d={PLUS_PATH}
stroke={strokeColor}
strokeWidth={1.25}
strokeMiterlimit={10}
strokeLinecap="round"
strokeLinejoin="round"

View file

@ -32,7 +32,8 @@ const StyledContainer = styled(Container)<{ compactTop: boolean }>`
}
`;
const CORNER_OFFSET = '-6px';
const CORNER_SIZE = 14;
const CORNER_OFFSET = '-7px';
const FramedGrid = styled.div`
position: relative;
@ -89,11 +90,12 @@ const FrameRailBottom = styled.span`
const FrameCorner = styled.span`
align-items: center;
display: none;
height: 12px;
height: ${CORNER_SIZE}px;
justify-content: center;
line-height: 0;
pointer-events: none;
position: absolute;
width: 12px;
width: ${CORNER_SIZE}px;
@media (min-width: ${theme.breakpoints.md}px) {
display: flex;
@ -157,18 +159,30 @@ export function Grid({
{!compactTop && (
<>
<FrameCornerTopLeft aria-hidden>
<PlusIcon size={12} strokeColor={theme.colors.highlight[100]} />
<PlusIcon
size={CORNER_SIZE}
strokeColor={theme.colors.highlight[100]}
/>
</FrameCornerTopLeft>
<FrameCornerTopRight aria-hidden>
<PlusIcon size={12} strokeColor={theme.colors.highlight[100]} />
<PlusIcon
size={CORNER_SIZE}
strokeColor={theme.colors.highlight[100]}
/>
</FrameCornerTopRight>
</>
)}
<FrameCornerBottomLeft aria-hidden>
<PlusIcon size={12} strokeColor={theme.colors.highlight[100]} />
<PlusIcon
size={CORNER_SIZE}
strokeColor={theme.colors.highlight[100]}
/>
</FrameCornerBottomLeft>
<FrameCornerBottomRight aria-hidden>
<PlusIcon size={12} strokeColor={theme.colors.highlight[100]} />
<PlusIcon
size={CORNER_SIZE}
strokeColor={theme.colors.highlight[100]}
/>
</FrameCornerBottomRight>
<CardGrid>
{entries.map((entry, index) => {

View file

@ -11,7 +11,9 @@ import { PromoMic } from '@/illustrations/CaseStudyCatalog/PromoMic';
import { theme } from '@/theme';
import { styled } from '@linaria/react';
const CORNER_OFFSET = '-6px';
const CORNER_SIZE = 14;
const CORNER_OFFSET = '-7px';
const CONNECTED_TOP_OFFSET = '6px';
const LINE_INSET = '20px';
const TRUSTED_BY_BOTTOM_PADDING = 12;
const TRUSTED_BY_BOTTOM_PADDING_DESKTOP = 16;
@ -59,12 +61,13 @@ const FrameBoard = styled.div`
}
`;
const Frame = styled.div`
const Frame = styled.div<{ compactTop: boolean }>`
bottom: ${theme.spacing(12)};
left: ${theme.spacing(10)};
position: absolute;
right: ${theme.spacing(10)};
top: ${theme.spacing(12)};
top: ${({ compactTop }) =>
compactTop ? CONNECTED_TOP_OFFSET : theme.spacing(12)};
`;
const FrameLine = styled.span`
@ -86,28 +89,29 @@ const FrameLineBottom = styled(FrameLine)`
right: ${LINE_INSET};
`;
const FrameLineLeft = styled(FrameLine)`
const FrameLineLeft = styled(FrameLine)<{ compactTop: boolean }>`
bottom: ${LINE_INSET};
left: 0;
top: ${LINE_INSET};
top: ${({ compactTop }) => (compactTop ? '0' : LINE_INSET)};
width: 1px;
`;
const FrameLineRight = styled(FrameLine)`
const FrameLineRight = styled(FrameLine)<{ compactTop: boolean }>`
bottom: ${LINE_INSET};
right: 0;
top: ${LINE_INSET};
top: ${({ compactTop }) => (compactTop ? '0' : LINE_INSET)};
width: 1px;
`;
const FrameCorner = styled.span`
align-items: center;
display: flex;
height: 12px;
height: ${CORNER_SIZE}px;
justify-content: center;
line-height: 0;
pointer-events: none;
position: absolute;
width: 12px;
width: ${CORNER_SIZE}px;
`;
const FrameCornerTopLeft = styled(FrameCorner)`
@ -130,16 +134,20 @@ const FrameCornerBottomRight = styled(FrameCorner)`
right: ${CORNER_OFFSET};
`;
const StyledContainer = styled(Container)`
const StyledContainer = styled(Container)<{ compactTop: boolean }>`
align-items: center;
display: grid;
gap: ${theme.spacing(10)};
grid-template-columns: 1fr;
min-height: 520px;
padding-bottom: ${theme.spacing(24 + TRUSTED_BY_BOTTOM_PADDING)};
padding-bottom: ${({ compactTop }) =>
compactTop
? theme.spacing(16)
: theme.spacing(24 + TRUSTED_BY_BOTTOM_PADDING)};
padding-left: ${theme.spacing(4)};
padding-right: ${theme.spacing(4)};
padding-top: ${theme.spacing(20)};
padding-top: ${({ compactTop }) =>
compactTop ? theme.spacing(16) : theme.spacing(20)};
position: relative;
z-index: 1;
@ -147,12 +155,14 @@ const StyledContainer = styled(Container)`
column-gap: ${theme.spacing(16)};
grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
min-height: 500px;
padding-bottom: ${theme.spacing(
40 + TRUSTED_BY_BOTTOM_PADDING_DESKTOP,
)};
padding-bottom: ${({ compactTop }) =>
compactTop
? `calc(${theme.spacing(24)} + ${theme.spacing(12)} - ${CONNECTED_TOP_OFFSET})`
: theme.spacing(40 + TRUSTED_BY_BOTTOM_PADDING_DESKTOP)};
padding-left: ${theme.spacing(10)};
padding-right: ${theme.spacing(10)};
padding-top: ${theme.spacing(32)};
padding-top: ${({ compactTop }) =>
compactTop ? theme.spacing(24) : theme.spacing(32)};
}
`;
@ -250,41 +260,57 @@ type PromoProps = {
entries: readonly CaseStudyCatalogEntry[];
ctaHref?: string;
ctaLabel?: string;
compactTop?: boolean;
};
export function Promo({
entries,
ctaHref = '/customers',
ctaLabel = 'Explore customer stories',
compactTop = false,
}: PromoProps) {
const featured = entries[0];
return (
<Section aria-label="Customer stories preview">
<BackgroundLayer aria-hidden>
<FrameBoard>
<Frame>
<FrameLineTop />
<Frame compactTop={compactTop}>
{!compactTop && <FrameLineTop />}
<FrameLineBottom />
<FrameLineLeft />
<FrameLineRight />
<FrameCornerTopLeft>
<PlusIcon size={12} strokeColor={theme.colors.highlight[100]} />
</FrameCornerTopLeft>
<FrameCornerTopRight>
<PlusIcon size={12} strokeColor={theme.colors.highlight[100]} />
</FrameCornerTopRight>
<FrameLineLeft compactTop={compactTop} />
<FrameLineRight compactTop={compactTop} />
{!compactTop && (
<>
<FrameCornerTopLeft>
<PlusIcon
size={CORNER_SIZE}
strokeColor={theme.colors.highlight[100]}
/>
</FrameCornerTopLeft>
<FrameCornerTopRight>
<PlusIcon
size={CORNER_SIZE}
strokeColor={theme.colors.highlight[100]}
/>
</FrameCornerTopRight>
</>
)}
<FrameCornerBottomLeft>
<PlusIcon size={12} strokeColor={theme.colors.highlight[100]} />
<PlusIcon
size={CORNER_SIZE}
strokeColor={theme.colors.highlight[100]}
/>
</FrameCornerBottomLeft>
<FrameCornerBottomRight>
<PlusIcon size={12} strokeColor={theme.colors.highlight[100]} />
<PlusIcon
size={CORNER_SIZE}
strokeColor={theme.colors.highlight[100]}
/>
</FrameCornerBottomRight>
</Frame>
</FrameBoard>
</BackgroundLayer>
<StyledContainer>
<StyledContainer compactTop={compactTop}>
<VisualColumn>
<VisualStage>
<MicFrame aria-hidden>

View file

@ -8,10 +8,7 @@ import {
} from '@/icons';
import type { FaqQuestionType } from '@/sections/Faq/types/FaqQuestion';
import { theme } from '@/theme';
import {
Accordion as BaseAccordion,
type AccordionTriggerState,
} from '@base-ui/react/accordion';
import { Accordion as BaseAccordion } from '@base-ui/react/accordion';
import { styled } from '@linaria/react';
const QuestionText = styled.span`
@ -55,12 +52,29 @@ const ToggleVisual = styled.span`
display: inline-flex;
height: 36px;
justify-content: center;
position: relative;
transition:
border-color 0.2s ease,
transform 0.2s cubic-bezier(0.2, 0.8, 0.2, 1);
width: 36px;
`;
const ToggleIconLayer = styled.span`
align-items: center;
display: inline-flex;
inset: 0;
justify-content: center;
position: absolute;
transition:
opacity 0.2s ease,
transform 0.2s ease;
&[data-icon='minus'] {
opacity: 0;
transform: scale(0.9);
}
`;
const RowTrigger = styled.button`
background: transparent;
border: none;
@ -123,6 +137,16 @@ export const ItemRow = styled.div`
opacity: 1;
}
&[data-open] ${ToggleIconLayer}[data-icon='plus'] {
opacity: 0;
transform: scale(0.9);
}
&[data-open] ${ToggleIconLayer}[data-icon='minus'] {
opacity: 1;
transform: scale(1);
}
@media (min-width: ${theme.breakpoints.md}px) {
grid-template-columns: 14px 60px 1fr 80px 36px;
row-gap: ${theme.spacing(4)};
@ -186,42 +210,41 @@ export function Item({ question, value }: ItemProps) {
return (
<BaseAccordion.Item key={value} value={value} render={<ItemRow />}>
<BaseAccordion.Header render={<Header />}>
<BaseAccordion.Trigger
render={(props, state: AccordionTriggerState) => {
const isOpen = state.open;
const ToggleIcon = isOpen ? MinusIcon : PlusIcon;
return (
// oxlint-disable-next-line react/jsx-props-no-spreading -- Accordion.Trigger host props
<RowTrigger {...props} type="button">
<QuestionIconContainer aria-hidden>
<QuestionIconLayer data-layer="outline">
<RectangleOutlineIcon
size={14}
strokeColor={theme.colors.secondary.text[100]}
/>
</QuestionIconLayer>
<QuestionIconLayer data-layer="fill">
<RectangleFillIcon
size={14}
fillColor={theme.colors.secondary.text[100]}
/>
</QuestionIconLayer>
</QuestionIconContainer>
<BaseAccordion.Trigger render={<RowTrigger type="button" />}>
<QuestionIconContainer aria-hidden>
<QuestionIconLayer data-layer="outline">
<RectangleOutlineIcon
size={14}
strokeColor={theme.colors.secondary.text[100]}
/>
</QuestionIconLayer>
<QuestionIconLayer data-layer="fill">
<RectangleFillIcon
size={14}
fillColor={theme.colors.secondary.text[100]}
/>
</QuestionIconLayer>
</QuestionIconContainer>
<QuestionText>{question.question.text}</QuestionText>
<QuestionText>{question.question.text}</QuestionText>
<ToggleContainer>
<ToggleVisual aria-hidden>
<ToggleIcon
size={12}
strokeColor={theme.colors.secondary.border[80]}
/>
</ToggleVisual>
</ToggleContainer>
</RowTrigger>
);
}}
/>
<ToggleContainer>
<ToggleVisual aria-hidden>
<ToggleIconLayer data-icon="plus">
<PlusIcon
size={12}
strokeColor={theme.colors.secondary.border[80]}
/>
</ToggleIconLayer>
<ToggleIconLayer data-icon="minus">
<MinusIcon
size={12}
strokeColor={theme.colors.secondary.border[80]}
/>
</ToggleIconLayer>
</ToggleVisual>
</ToggleContainer>
</BaseAccordion.Trigger>
</BaseAccordion.Header>
<BaseAccordion.Panel render={<AnswerWrapper />} keepMounted>

View file

@ -4,7 +4,8 @@ import { theme } from '@/theme';
import { styled } from '@linaria/react';
import { Children, type ReactNode } from 'react';
const CORNER_OFFSET = '-6px';
const CORNER_SIZE = 14;
const CORNER_OFFSET = '-7px';
const StyledSection = styled.section<{
compactTop: boolean;
@ -18,6 +19,7 @@ const StyledSection = styled.section<{
compactTop ? theme.spacing(4) : theme.spacing(12)};
position: relative;
width: 100%;
z-index: ${({ compactBottom }) => (compactBottom ? 2 : 'auto')};
@media (min-width: ${theme.breakpoints.md}px) {
padding-bottom: ${({ compactBottom }) =>
@ -97,11 +99,12 @@ const StyledCountCell = styled(StyledCell)`
const CornerMarker = styled.span`
align-items: center;
display: flex;
height: 12px;
height: ${CORNER_SIZE}px;
justify-content: center;
line-height: 0;
pointer-events: none;
position: absolute;
width: 12px;
width: ${CORNER_SIZE}px;
`;
const CornerTopLeft = styled(CornerMarker)`
@ -150,16 +153,28 @@ export function Root({
<StyledContainer>
<StyledCard backgroundColor={cardBackgroundColor}>
<CornerTopLeft aria-hidden>
<PlusIcon size={12} strokeColor={theme.colors.highlight[100]} />
<PlusIcon
size={CORNER_SIZE}
strokeColor={theme.colors.highlight[100]}
/>
</CornerTopLeft>
<CornerTopRight aria-hidden>
<PlusIcon size={12} strokeColor={theme.colors.highlight[100]} />
<PlusIcon
size={CORNER_SIZE}
strokeColor={theme.colors.highlight[100]}
/>
</CornerTopRight>
<CornerBottomLeft aria-hidden>
<PlusIcon size={12} strokeColor={theme.colors.highlight[100]} />
<PlusIcon
size={CORNER_SIZE}
strokeColor={theme.colors.highlight[100]}
/>
</CornerBottomLeft>
<CornerBottomRight aria-hidden>
<PlusIcon size={12} strokeColor={theme.colors.highlight[100]} />
<PlusIcon
size={CORNER_SIZE}
strokeColor={theme.colors.highlight[100]}
/>
</CornerBottomRight>
<StyledLabelCell>{label}</StyledLabelCell>
<StyledLogosCell>{logos}</StyledLogosCell>

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 827 KiB

After

Width:  |  Height:  |  Size: 341 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 699 KiB

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4 KiB

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 776 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B