diff --git a/README.md b/README.md
index e0f619793f..7bfc56b28a 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@
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.
-
Learn more about why we built Twenty
+
Learn more about why we built Twenty
diff --git a/packages/twenty-website-new/next.config.ts b/packages/twenty-website-new/next.config.ts
index 1ab5ac8f15..fd7b50c4a4 100644
--- a/packages/twenty-website-new/next.config.ts
+++ b/packages/twenty-website-new/next.config.ts
@@ -114,9 +114,14 @@ const nextConfig: LinariaConfig = {
destination: 'https://docs.twenty.com/twenty-ui/:slug',
permanent: true,
},
+ {
+ source: '/why-twenty',
+ destination: '/resources/why-twenty',
+ permanent: true,
+ },
{
source: '/story',
- destination: '/why-twenty',
+ destination: '/resources/why-twenty',
permanent: true,
},
{
diff --git a/packages/twenty-website-new/src/app/[locale]/product/demo.data.ts b/packages/twenty-website-new/src/app/[locale]/product/demo.data.ts
new file mode 100644
index 0000000000..053650215d
--- /dev/null
+++ b/packages/twenty-website-new/src/app/[locale]/product/demo.data.ts
@@ -0,0 +1,8 @@
+import type { ImageType } from '@/design-system/components/Image';
+
+export const DEMO_DATA: { image: ImageType } = {
+ image: {
+ src: '/images/product/demo/kanban.webp',
+ alt: '',
+ },
+};
diff --git a/packages/twenty-website-new/src/app/[locale]/product/page.tsx b/packages/twenty-website-new/src/app/[locale]/product/page.tsx
index 864850bf81..357dbb2994 100644
--- a/packages/twenty-website-new/src/app/[locale]/product/page.tsx
+++ b/packages/twenty-website-new/src/app/[locale]/product/page.tsx
@@ -3,10 +3,11 @@ import { FAQ_DATA } from '@/sections/Faq/data';
import { MENU_DATA } from '@/sections/Menu/data';
import { TRUSTED_BY_DATA } from '@/sections/TrustedBy/data';
import { TalkToUsButton } from '@/lib/contact-cal';
+import { DEMO_DATA } from '@/app/[locale]/product/demo.data';
import { FEATURE_DATA } from '@/app/[locale]/product/feature.data';
import { HERO_COPY } from '@/app/[locale]/product/hero.data';
-import { SIGNOFF_COPY } from '@/app/[locale]/product/signoff.data';
import { STEPPER_DATA } from '@/app/[locale]/product/stepper.data';
+import { TABS_DATA } from '@/app/[locale]/product/tabs.data';
import { THREE_CARDS_ILLUSTRATION_DATA } from '@/app/[locale]/product/three-cards.data';
import {
Body,
@@ -27,8 +28,9 @@ import { Faq } from '@/sections/Faq/components';
import { Feature } from '@/sections/Feature/components';
import { Hero } from '@/sections/Hero/components';
import { Menu } from '@/sections/Menu/components';
+import { Demo } from '@/sections/Demo/components';
import { ProductStepper } from '@/sections/ProductStepper/components';
-import { Signoff } from '@/sections/Signoff/components';
+import { Tabs } from '@/sections/Tabs/components';
import { ThreeCards } from '@/sections/ThreeCards/components';
import { TrustedBy } from '@/sections/TrustedBy/components';
import { theme } from '@/theme';
@@ -113,6 +115,32 @@ export default async function ProductPage({ params }: ProductPageProps) {
/>
+
+
+
+
+ {renderText(msg`AI that actually`)}
+
+
+
+ {renderText(msg`helps you`)}
+ {' '}
+
+ {renderText(msg`work faster`)}
+
+
+
+
+
+
-
-
-
- {renderText(msg`Ready to grow`)}
-
-
-
- {renderText(msg`with`)}
- {' '}
-
- {renderText(msg`Twenty?`)}
-
-
-
+
-
+
+
+ {renderText(msg`A demo worth a`)}
+
+
+
+ {renderText(msg`thousand words`)}
+
+
+
-
-
-
+
+
+
diff --git a/packages/twenty-website-new/src/app/[locale]/product/tabs.data.ts b/packages/twenty-website-new/src/app/[locale]/product/tabs.data.ts
new file mode 100644
index 0000000000..3f56751a5a
--- /dev/null
+++ b/packages/twenty-website-new/src/app/[locale]/product/tabs.data.ts
@@ -0,0 +1,49 @@
+import { msg } from '@lingui/core/macro';
+
+import type { TabType } from '@/sections/Tabs/types';
+
+export const TABS_DATA: {
+ eyebrow: { heading: { text: ReturnType; fontFamily: 'sans' } };
+ tabs: TabType[];
+} = {
+ eyebrow: {
+ heading: {
+ text: msg`AI & Automation`,
+ fontFamily: 'sans',
+ },
+ },
+ tabs: [
+ {
+ body: msg`Show me all deals closing this month`,
+ icon: 'search',
+ image: {
+ src: '/images/product/tabs/deals.webp',
+ alt: 'Deals view',
+ },
+ },
+ {
+ body: msg`Create follow-up tasks for my top 10 accounts`,
+ icon: 'eye',
+ image: {
+ src: '/images/product/tabs/tasks.webp',
+ alt: 'Tasks view',
+ },
+ },
+ {
+ body: msg`Summarize this customer's history`,
+ icon: 'edit',
+ image: {
+ src: '/images/product/tabs/history.webp',
+ alt: 'History view',
+ },
+ },
+ {
+ body: msg`Create a workflow that send an email sequence`,
+ icon: 'check',
+ image: {
+ src: '/images/product/tabs/workflow.webp',
+ alt: 'Workflow view',
+ },
+ },
+ ],
+};
diff --git a/packages/twenty-website-new/src/app/[locale]/why-twenty/editorial-four.data.ts b/packages/twenty-website-new/src/app/[locale]/resources/why-twenty/editorial-four.data.ts
similarity index 100%
rename from packages/twenty-website-new/src/app/[locale]/why-twenty/editorial-four.data.ts
rename to packages/twenty-website-new/src/app/[locale]/resources/why-twenty/editorial-four.data.ts
diff --git a/packages/twenty-website-new/src/app/[locale]/why-twenty/editorial-one.data.ts b/packages/twenty-website-new/src/app/[locale]/resources/why-twenty/editorial-one.data.ts
similarity index 100%
rename from packages/twenty-website-new/src/app/[locale]/why-twenty/editorial-one.data.ts
rename to packages/twenty-website-new/src/app/[locale]/resources/why-twenty/editorial-one.data.ts
diff --git a/packages/twenty-website-new/src/app/[locale]/why-twenty/editorial-three.data.ts b/packages/twenty-website-new/src/app/[locale]/resources/why-twenty/editorial-three.data.ts
similarity index 100%
rename from packages/twenty-website-new/src/app/[locale]/why-twenty/editorial-three.data.ts
rename to packages/twenty-website-new/src/app/[locale]/resources/why-twenty/editorial-three.data.ts
diff --git a/packages/twenty-website-new/src/app/[locale]/why-twenty/hero.data.ts b/packages/twenty-website-new/src/app/[locale]/resources/why-twenty/hero.data.ts
similarity index 100%
rename from packages/twenty-website-new/src/app/[locale]/why-twenty/hero.data.ts
rename to packages/twenty-website-new/src/app/[locale]/resources/why-twenty/hero.data.ts
diff --git a/packages/twenty-website-new/src/app/[locale]/why-twenty/marquee.data.ts b/packages/twenty-website-new/src/app/[locale]/resources/why-twenty/marquee.data.ts
similarity index 100%
rename from packages/twenty-website-new/src/app/[locale]/why-twenty/marquee.data.ts
rename to packages/twenty-website-new/src/app/[locale]/resources/why-twenty/marquee.data.ts
diff --git a/packages/twenty-website-new/src/app/[locale]/why-twenty/page.tsx b/packages/twenty-website-new/src/app/[locale]/resources/why-twenty/page.tsx
similarity index 94%
rename from packages/twenty-website-new/src/app/[locale]/why-twenty/page.tsx
rename to packages/twenty-website-new/src/app/[locale]/resources/why-twenty/page.tsx
index e46aaa4cd8..a725ab11c4 100644
--- a/packages/twenty-website-new/src/app/[locale]/why-twenty/page.tsx
+++ b/packages/twenty-website-new/src/app/[locale]/resources/why-twenty/page.tsx
@@ -1,11 +1,11 @@
import { msg } from '@lingui/core/macro';
import { MENU_DATA } from '@/sections/Menu/data';
-import { EDITORIAL_FOUR } from '@/app/[locale]/why-twenty/editorial-four.data';
-import { EDITORIAL_ONE } from '@/app/[locale]/why-twenty/editorial-one.data';
-import { EDITORIAL_THREE } from '@/app/[locale]/why-twenty/editorial-three.data';
-import { HERO_COPY } from '@/app/[locale]/why-twenty/hero.data';
-import { MARQUEE_DATA } from '@/app/[locale]/why-twenty/marquee.data';
-import { SIGNOFF_COPY } from '@/app/[locale]/why-twenty/signoff.data';
+import { EDITORIAL_FOUR } from '@/app/[locale]/resources/why-twenty/editorial-four.data';
+import { EDITORIAL_ONE } from '@/app/[locale]/resources/why-twenty/editorial-one.data';
+import { EDITORIAL_THREE } from '@/app/[locale]/resources/why-twenty/editorial-three.data';
+import { HERO_COPY } from '@/app/[locale]/resources/why-twenty/hero.data';
+import { MARQUEE_DATA } from '@/app/[locale]/resources/why-twenty/marquee.data';
+import { SIGNOFF_COPY } from '@/app/[locale]/resources/why-twenty/signoff.data';
import { HeadingPart, LinkButton } from '@/design-system/components';
import { fetchCommunityStats } from '@/lib/community/fetch-community-stats';
import { createMessageDescriptorRenderer } from '@/lib/i18n/create-message-descriptor-renderer';
diff --git a/packages/twenty-website-new/src/app/[locale]/why-twenty/signoff.data.ts b/packages/twenty-website-new/src/app/[locale]/resources/why-twenty/signoff.data.ts
similarity index 100%
rename from packages/twenty-website-new/src/app/[locale]/why-twenty/signoff.data.ts
rename to packages/twenty-website-new/src/app/[locale]/resources/why-twenty/signoff.data.ts
diff --git a/packages/twenty-website-new/src/lib/i18n/__tests__/localize-href.test.ts b/packages/twenty-website-new/src/lib/i18n/__tests__/localize-href.test.ts
index 34e916b19c..3dcbd49a2d 100644
--- a/packages/twenty-website-new/src/lib/i18n/__tests__/localize-href.test.ts
+++ b/packages/twenty-website-new/src/lib/i18n/__tests__/localize-href.test.ts
@@ -34,12 +34,16 @@ describe('localizeHref', () => {
});
it('strips a redundant /en prefix when targeting the default locale', () => {
- expect(localizeHref('en', '/en/why-twenty')).toBe('/why-twenty');
+ expect(localizeHref('en', '/en/resources/why-twenty')).toBe(
+ '/resources/why-twenty',
+ );
expect(localizeHref('en', '/en')).toBe('/');
});
it('strips an /en-prefixed path when targeting an unpublished locale', () => {
- expect(localizeHref('de-DE', '/en/why-twenty')).toBe('/why-twenty');
+ expect(localizeHref('de-DE', '/en/resources/why-twenty')).toBe(
+ '/resources/why-twenty',
+ );
expect(localizeHref('de-DE', '/en')).toBe('/');
});
@@ -81,7 +85,9 @@ describe('localizeHref', () => {
describe('stripLocale', () => {
it('removes a published-locale URL-segment prefix from the pathname', () => {
- expect(stripLocale('/en/why-twenty')).toBe('/why-twenty');
+ expect(stripLocale('/en/resources/why-twenty')).toBe(
+ '/resources/why-twenty',
+ );
expect(stripLocale('/fr/customers/9dots')).toBe('/customers/9dots');
});
@@ -91,7 +97,7 @@ describe('stripLocale', () => {
});
it('returns the pathname unchanged when no published locale prefix is present', () => {
- expect(stripLocale('/why-twenty')).toBe('/why-twenty');
+ expect(stripLocale('/resources/why-twenty')).toBe('/resources/why-twenty');
expect(stripLocale('/')).toBe('/');
expect(stripLocale('/fr-FR/foo')).toBe('/fr-FR/foo');
expect(stripLocale('/zh-CN')).toBe('/zh-CN');
diff --git a/packages/twenty-website-new/src/lib/website-routing/static-website-routes.ts b/packages/twenty-website-new/src/lib/website-routing/static-website-routes.ts
index be73fe438c..17547491ed 100644
--- a/packages/twenty-website-new/src/lib/website-routing/static-website-routes.ts
+++ b/packages/twenty-website-new/src/lib/website-routing/static-website-routes.ts
@@ -14,7 +14,7 @@ export const STATIC_WEBSITE_ROUTES = [
},
{
id: 'whyTwenty',
- path: '/why-twenty',
+ path: '/resources/why-twenty',
title: msg`Why Twenty — The Open Source CRM Built to Be Customised`,
description: msg`Packaged CRMs make every company look the same. Twenty is the open source CRM teams shape around their workflow, with a modern UI and a developer-first platform.`,
changeFrequency: 'monthly',
diff --git a/packages/twenty-website-new/src/sections/Demo/components/Cta.tsx b/packages/twenty-website-new/src/sections/Demo/components/Cta.tsx
new file mode 100644
index 0000000000..09c2942cd7
--- /dev/null
+++ b/packages/twenty-website-new/src/sections/Demo/components/Cta.tsx
@@ -0,0 +1,16 @@
+import { theme } from '@/theme';
+import { styled } from '@linaria/react';
+import type { ReactNode } from 'react';
+
+const CTAsContainer = styled.div`
+ display: flex;
+ flex-wrap: wrap;
+ gap: ${theme.spacing(4)};
+ justify-content: center;
+`;
+
+type CtaProps = { children: ReactNode };
+
+export function Cta({ children }: CtaProps) {
+ return {children};
+}
diff --git a/packages/twenty-website-new/src/sections/Demo/components/Root.tsx b/packages/twenty-website-new/src/sections/Demo/components/Root.tsx
new file mode 100644
index 0000000000..61abc60c16
--- /dev/null
+++ b/packages/twenty-website-new/src/sections/Demo/components/Root.tsx
@@ -0,0 +1,89 @@
+import { Container } from '@/design-system/components';
+import { theme } from '@/theme';
+import { css } from '@linaria/core';
+import { styled } from '@linaria/react';
+import NextImage from 'next/image';
+import type { ReactNode } from 'react';
+
+const RootWrapper = styled.div`
+ display: grid;
+ grid-template-columns: 1fr;
+ position: relative;
+ width: 100%;
+`;
+
+const BackgroundLayer = styled.div`
+ align-self: stretch;
+ grid-column: 1;
+ grid-row: 1;
+ opacity: 0.6;
+ pointer-events: none;
+ position: relative;
+ width: 100%;
+ z-index: 0;
+`;
+
+const PatternLayer = styled.div`
+ bottom: 0;
+ height: 60%;
+ left: 50%;
+ position: absolute;
+ transform: translateX(-50%);
+ width: 100%;
+`;
+
+const patternImageClassName = css`
+ object-fit: cover;
+ object-position: center top;
+`;
+
+const StyledSection = styled.section`
+ grid-column: 1;
+ grid-row: 1;
+ position: relative;
+ width: 100%;
+ z-index: 1;
+`;
+
+const StyledContainer = styled(Container)`
+ display: grid;
+ grid-template-columns: 1fr;
+ justify-items: center;
+ padding-bottom: ${theme.spacing(16)};
+ padding-left: ${theme.spacing(4)};
+ padding-right: ${theme.spacing(4)};
+ padding-top: ${theme.spacing(16)};
+ row-gap: ${theme.spacing(6)};
+ text-align: center;
+ width: 100%;
+
+ @media (min-width: ${theme.breakpoints.md}px) {
+ padding-bottom: ${theme.spacing(24)};
+ padding-left: ${theme.spacing(10)};
+ padding-right: ${theme.spacing(10)};
+ padding-top: ${theme.spacing(24)};
+ }
+`;
+
+type RootProps = { children: ReactNode };
+
+export function Root({ children }: RootProps) {
+ return (
+
+
+
+
+
+
+
+ {children}
+
+
+ );
+}
diff --git a/packages/twenty-website-new/src/sections/Demo/components/Screenshot.tsx b/packages/twenty-website-new/src/sections/Demo/components/Screenshot.tsx
new file mode 100644
index 0000000000..38292aec8c
--- /dev/null
+++ b/packages/twenty-website-new/src/sections/Demo/components/Screenshot.tsx
@@ -0,0 +1,41 @@
+import type { ImageType } from '@/design-system/components/Image';
+import { theme } from '@/theme';
+import { css } from '@linaria/core';
+import { styled } from '@linaria/react';
+import NextImage from 'next/image';
+
+const ScreenshotRoot = styled.div`
+ aspect-ratio: 136 / 69;
+ border-radius: ${theme.radius(4)};
+ margin-bottom: ${theme.spacing(11)};
+ margin-top: ${theme.spacing(12)};
+ max-width: 800px;
+ overflow: hidden;
+ position: relative;
+ width: 100%;
+
+ @media (min-width: ${theme.breakpoints.md}px) {
+ border-radius: ${theme.radius(6)};
+ margin-top: ${theme.spacing(19)};
+ }
+`;
+
+const screenshotImageClassName = css`
+ object-fit: cover;
+`;
+
+type ScreenshotProps = { image: ImageType };
+
+export function Screenshot({ image }: ScreenshotProps) {
+ return (
+
+
+
+ );
+}
diff --git a/packages/twenty-website-new/src/sections/Demo/components/index.ts b/packages/twenty-website-new/src/sections/Demo/components/index.ts
new file mode 100644
index 0000000000..15b48888ee
--- /dev/null
+++ b/packages/twenty-website-new/src/sections/Demo/components/index.ts
@@ -0,0 +1,5 @@
+import { Cta } from './Cta';
+import { Root } from './Root';
+import { Screenshot } from './Screenshot';
+
+export const Demo = { Cta, Root, Screenshot };
diff --git a/packages/twenty-website-new/src/sections/Footer/data.ts b/packages/twenty-website-new/src/sections/Footer/data.ts
index 4532d2b48c..17fe8c8be9 100644
--- a/packages/twenty-website-new/src/sections/Footer/data.ts
+++ b/packages/twenty-website-new/src/sections/Footer/data.ts
@@ -12,9 +12,14 @@ export const FOOTER_DATA: FooterDataType = {
ctas: [],
links: [
{ label: msg`Home`, href: '/', external: false },
+ { label: msg`Product`, href: '/product', external: false },
{ label: msg`Pricing`, href: '/pricing', external: false },
{ label: msg`Partners`, href: '/partners', external: false },
- { label: msg`Why Twenty`, href: '/why-twenty', external: false },
+ {
+ label: msg`Why Twenty`,
+ href: '/resources/why-twenty',
+ external: false,
+ },
{ label: msg`Articles`, href: '/articles', external: false },
],
},
diff --git a/packages/twenty-website-new/src/sections/Menu/data.ts b/packages/twenty-website-new/src/sections/Menu/data.ts
index f42764e184..922f2b307d 100644
--- a/packages/twenty-website-new/src/sections/Menu/data.ts
+++ b/packages/twenty-website-new/src/sections/Menu/data.ts
@@ -23,7 +23,7 @@ function buildNavItems(): MenuNavItemType[] {
getLatestReleasePreview() ?? FALLBACK_RELEASES_PREVIEW;
return [
- { label: msg`Why`, href: '/why-twenty' },
+ { label: msg`Product`, href: '/product' },
{
label: msg`Resources`,
children: [
@@ -55,6 +55,17 @@ function buildNavItems(): MenuNavItemType[] {
description: msg`APIs, SDKs and webhooks to extend Twenty and ship apps on top of your CRM data.`,
},
},
+ {
+ label: msg`Why Twenty`,
+ description: msg`The open source CRM built to be customised`,
+ href: '/resources/why-twenty',
+ preview: {
+ image: '/images/why-twenty/hero/background.webp',
+ imageAlt: 'Why Twenty',
+ title: msg`Why Twenty`,
+ description: msg`Our story: building a CRM teams can truly own.`,
+ },
+ },
{
label: msg`Partners`,
description: msg`Find a Twenty partner`,
diff --git a/packages/twenty-website-new/src/sections/Tabs/components/Root.tsx b/packages/twenty-website-new/src/sections/Tabs/components/Root.tsx
new file mode 100644
index 0000000000..cee2170e9b
--- /dev/null
+++ b/packages/twenty-website-new/src/sections/Tabs/components/Root.tsx
@@ -0,0 +1,131 @@
+import { Container } from '@/design-system/components';
+import { theme } from '@/theme';
+import { css } from '@linaria/core';
+import { styled } from '@linaria/react';
+import NextImage from 'next/image';
+import type { ReactNode } from 'react';
+
+const RootWrapper = styled.div`
+ display: grid;
+ grid-template-columns: 1fr;
+ position: relative;
+ width: 100%;
+`;
+
+const TabBackgrounds = styled.div`
+ align-self: stretch;
+ grid-column: 1;
+ grid-row: 1;
+ min-height: 0;
+ pointer-events: none;
+ position: relative;
+ width: 100%;
+ z-index: 0;
+`;
+
+const BackgroundShape = styled.div`
+ bottom: 0;
+ left: 0;
+ pointer-events: none;
+ position: absolute;
+ right: 0;
+ top: 0;
+`;
+
+const ShapeClip = styled.div`
+ bottom: 0;
+ left: 0;
+ overflow: hidden;
+ pointer-events: none;
+ position: absolute;
+ right: 0;
+ top: 0;
+ z-index: 0;
+`;
+
+const shapeFillClassName = css`
+ object-fit: cover;
+ object-position: center top;
+`;
+
+const PatternLayer = styled.div`
+ bottom: -81px;
+ height: 575px;
+ left: 50%;
+ opacity: 0.4;
+ pointer-events: none;
+ position: absolute;
+ transform: translateX(-50%);
+ width: 100%;
+ z-index: 1;
+`;
+
+const patternImageClassName = css`
+ object-fit: cover;
+`;
+
+const StyledSection = styled.section`
+ align-self: start;
+ grid-column: 1;
+ grid-row: 1;
+ position: relative;
+ width: 100%;
+ z-index: 2;
+`;
+
+const StyledContainer = styled(Container)`
+ color: ${theme.colors.secondary.text[100]};
+ display: grid;
+ grid-template-columns: 1fr;
+ justify-items: center;
+ padding-bottom: ${theme.spacing(16)};
+ padding-left: ${theme.spacing(4)};
+ padding-right: ${theme.spacing(4)};
+ padding-top: ${theme.spacing(16)};
+ row-gap: ${theme.spacing(6)};
+ text-align: center;
+ width: 100%;
+
+ @media (min-width: ${theme.breakpoints.md}px) {
+ padding-bottom: ${theme.spacing(40)};
+ padding-left: ${theme.spacing(10)};
+ padding-right: ${theme.spacing(10)};
+ padding-top: ${theme.spacing(40)};
+ row-gap: ${theme.spacing(6)};
+ }
+`;
+
+type RootProps = { children: ReactNode };
+
+export function Root({ children }: RootProps) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+ );
+}
diff --git a/packages/twenty-website-new/src/sections/Tabs/components/TabButton.tsx b/packages/twenty-website-new/src/sections/Tabs/components/TabButton.tsx
new file mode 100644
index 0000000000..af63537efc
--- /dev/null
+++ b/packages/twenty-website-new/src/sections/Tabs/components/TabButton.tsx
@@ -0,0 +1,123 @@
+'use client';
+
+import { useRenderMessage } from '@/lib/i18n/use-render-message';
+import { INFORMATIVE_ICONS } from '@/icons';
+import type { TabType } from '@/sections/Tabs/types';
+import { theme } from '@/theme';
+import { styled } from '@linaria/react';
+
+const Label = styled.span`
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+`;
+
+const StyledButton = styled.button`
+ align-items: center;
+ border-radius: ${theme.radius(2)};
+ box-sizing: border-box;
+ cursor: pointer;
+ display: grid;
+ font-family: ${theme.font.family.sans};
+ font-size: ${theme.font.size(3.5)};
+ font-weight: ${theme.font.weight.regular};
+ gap: ${theme.spacing(2)};
+ grid-template-columns: minmax(0, 1fr) auto;
+ line-height: ${theme.lineHeight(3.5)};
+ max-width: 100%;
+ min-width: 0;
+ padding: ${theme.spacing(2)};
+ text-align: left;
+ transition:
+ background-color 0.2s ease,
+ background-image 0.2s ease,
+ border-color 0.2s ease,
+ color 0.2s ease;
+
+ &[data-active='true'] {
+ background-color: ${theme.colors.primary.background[100]};
+ border: 1px solid transparent;
+ color: ${theme.colors.primary.text[100]};
+ }
+
+ &[data-active='false'] {
+ background-color: ${theme.colors.secondary.background[100]};
+ background-image: linear-gradient(
+ 90deg,
+ rgba(255, 255, 255, 0.1) 0%,
+ rgba(255, 255, 255, 0.1) 100%
+ );
+ border: 1px solid ${theme.colors.secondary.border[10]};
+ color: ${theme.colors.secondary.text[100]};
+ }
+
+ &:focus-visible {
+ outline: 1px solid ${theme.colors.highlight[100]};
+ outline-offset: 1px;
+ }
+`;
+
+const TabIconBox = styled.span`
+ border-radius: ${theme.radius(1)};
+ display: grid;
+ height: ${theme.spacing(6)};
+ overflow: hidden;
+ place-items: center;
+ width: ${theme.spacing(6)};
+
+ & svg {
+ display: block;
+ max-height: 100%;
+ max-width: 100%;
+ }
+
+ &[data-active='true'] {
+ background-color: ${theme.colors.primary.text[10]};
+ }
+
+ &[data-active='false'] {
+ background-color: ${theme.colors.highlight[100]};
+ }
+`;
+
+type TabButtonProps = {
+ controls: string;
+ id: string;
+ isActive: boolean;
+ onSelect: () => void;
+ tab: TabType;
+};
+
+export function TabButton({
+ controls,
+ id,
+ isActive,
+ onSelect,
+ tab,
+}: TabButtonProps) {
+ const renderText = useRenderMessage();
+
+ const iconColor = isActive
+ ? theme.colors.highlight[100]
+ : theme.colors.secondary.text[100];
+
+ const Icon = INFORMATIVE_ICONS[tab.icon as keyof typeof INFORMATIVE_ICONS];
+
+ return (
+
+
+
+ {Icon ? : null}
+
+
+ );
+}
diff --git a/packages/twenty-website-new/src/sections/Tabs/components/TabButtons.tsx b/packages/twenty-website-new/src/sections/Tabs/components/TabButtons.tsx
new file mode 100644
index 0000000000..7dea65d671
--- /dev/null
+++ b/packages/twenty-website-new/src/sections/Tabs/components/TabButtons.tsx
@@ -0,0 +1,52 @@
+'use client';
+
+import type { TabType } from '@/sections/Tabs/types';
+import { theme } from '@/theme';
+import { styled } from '@linaria/react';
+
+import { TabButton } from './TabButton';
+
+const TabButtonsGrid = styled.div`
+ display: grid;
+ gap: ${theme.spacing(2)};
+ grid-template-columns: minmax(0, max-content);
+ justify-content: center;
+ width: 100%;
+
+ @media (min-width: ${theme.breakpoints.md}px) {
+ grid-template-columns: repeat(2, minmax(0, max-content));
+ }
+
+ @media (min-width: ${theme.breakpoints.lg}px) {
+ grid-template-columns: repeat(4, minmax(0, max-content));
+ }
+`;
+
+type TabButtonsProps = {
+ activeIndex: number;
+ idPrefix: string;
+ onSelect: (index: number) => void;
+ tabs: TabType[];
+};
+
+export function TabButtons({
+ activeIndex,
+ idPrefix,
+ onSelect,
+ tabs,
+}: TabButtonsProps) {
+ return (
+
+ {tabs.map((tab, index) => (
+ onSelect(index)}
+ tab={tab}
+ />
+ ))}
+
+ );
+}
diff --git a/packages/twenty-website-new/src/sections/Tabs/components/TabGroup.tsx b/packages/twenty-website-new/src/sections/Tabs/components/TabGroup.tsx
new file mode 100644
index 0000000000..1de2429852
--- /dev/null
+++ b/packages/twenty-website-new/src/sections/Tabs/components/TabGroup.tsx
@@ -0,0 +1,44 @@
+'use client';
+
+import { useId, useState } from 'react';
+
+import { theme } from '@/theme';
+import { styled } from '@linaria/react';
+
+import type { TabType } from '@/sections/Tabs/types';
+
+import { TabButtons } from './TabButtons';
+import { TabVisuals } from './TabVisuals';
+
+const StyledTabGroup = styled.div`
+ display: grid;
+ grid-template-columns: 1fr;
+ row-gap: ${theme.spacing(18)};
+ width: 100%;
+`;
+
+type TabGroupProps = {
+ tabs: TabType[];
+};
+
+export function TabGroup({ tabs }: TabGroupProps) {
+ const [activeIndex, setActiveIndex] = useState(0);
+ const idPrefix = useId();
+
+ return (
+
+
+
+
+ );
+}
diff --git a/packages/twenty-website-new/src/sections/Tabs/components/TabVisuals.tsx b/packages/twenty-website-new/src/sections/Tabs/components/TabVisuals.tsx
new file mode 100644
index 0000000000..b18612cf3f
--- /dev/null
+++ b/packages/twenty-website-new/src/sections/Tabs/components/TabVisuals.tsx
@@ -0,0 +1,46 @@
+'use client';
+
+import { Image } from '@/design-system/components';
+import type { TabType } from '@/sections/Tabs/types';
+import { theme } from '@/theme';
+import { styled } from '@linaria/react';
+
+const VisualRoot = styled.div`
+ width: 100%;
+
+ @media (min-width: ${theme.breakpoints.md}px) {
+ margin-left: auto;
+ margin-right: auto;
+ max-width: 995px;
+ }
+`;
+
+const VisualImage = styled(Image)`
+ border-radius: ${theme.radius(1)};
+`;
+
+type TabVisualsProps = {
+ activeIndex: number;
+ panelId: string;
+ tabId: string;
+ tabs: TabType[];
+};
+
+export function TabVisuals({
+ activeIndex,
+ panelId,
+ tabId,
+ tabs,
+}: TabVisualsProps) {
+ const tab = tabs[activeIndex];
+
+ if (!tab) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/packages/twenty-website-new/src/sections/Tabs/components/index.ts b/packages/twenty-website-new/src/sections/Tabs/components/index.ts
new file mode 100644
index 0000000000..8dabb1afc6
--- /dev/null
+++ b/packages/twenty-website-new/src/sections/Tabs/components/index.ts
@@ -0,0 +1,7 @@
+import { Root } from './Root';
+import { TabGroup } from './TabGroup';
+
+export const Tabs = {
+ Root,
+ TabGroup,
+};
diff --git a/packages/twenty-website-new/src/sections/Tabs/types/index.ts b/packages/twenty-website-new/src/sections/Tabs/types/index.ts
new file mode 100644
index 0000000000..cff9dc7f44
--- /dev/null
+++ b/packages/twenty-website-new/src/sections/Tabs/types/index.ts
@@ -0,0 +1,9 @@
+import type { MessageDescriptor } from '@lingui/core';
+
+import type { ImageType } from '@/design-system/components/Image';
+
+export type TabType = {
+ body: MessageDescriptor;
+ icon: string;
+ image: ImageType;
+};