diff --git a/packages/twenty-website-new/public/illustrations/home/faq/faq.glb b/packages/twenty-website-new/public/illustrations/home/faq/faq.glb new file mode 100644 index 0000000000..8fddb27955 Binary files /dev/null and b/packages/twenty-website-new/public/illustrations/home/faq/faq.glb differ diff --git a/packages/twenty-website-new/public/illustrations/home/footer/footer.glb b/packages/twenty-website-new/public/illustrations/home/footer/footer.glb new file mode 100644 index 0000000000..c6c198c913 Binary files /dev/null and b/packages/twenty-website-new/public/illustrations/home/footer/footer.glb differ diff --git a/packages/twenty-website-new/public/illustrations/home/helped/one.glb b/packages/twenty-website-new/public/illustrations/home/helped/one.glb new file mode 100644 index 0000000000..1f1e4e9b9f Binary files /dev/null and b/packages/twenty-website-new/public/illustrations/home/helped/one.glb differ diff --git a/packages/twenty-website-new/public/illustrations/home/helped/three.glb b/packages/twenty-website-new/public/illustrations/home/helped/three.glb new file mode 100644 index 0000000000..eaa3db151d Binary files /dev/null and b/packages/twenty-website-new/public/illustrations/home/helped/three.glb differ diff --git a/packages/twenty-website-new/public/illustrations/home/helped/two.glb b/packages/twenty-website-new/public/illustrations/home/helped/two.glb new file mode 100644 index 0000000000..0bd7e77c66 Binary files /dev/null and b/packages/twenty-website-new/public/illustrations/home/helped/two.glb differ diff --git a/packages/twenty-website-new/public/illustrations/home/testimonials/testimonials.glb b/packages/twenty-website-new/public/illustrations/home/testimonials/testimonials.glb new file mode 100644 index 0000000000..8b6c7321c3 Binary files /dev/null and b/packages/twenty-website-new/public/illustrations/home/testimonials/testimonials.glb differ diff --git a/packages/twenty-website-new/public/illustrations/home/three-cards-illustration/one.glb b/packages/twenty-website-new/public/illustrations/home/three-cards-illustration/one.glb new file mode 100644 index 0000000000..9919100fd8 Binary files /dev/null and b/packages/twenty-website-new/public/illustrations/home/three-cards-illustration/one.glb differ diff --git a/packages/twenty-website-new/public/illustrations/home/three-cards-illustration/three.glb b/packages/twenty-website-new/public/illustrations/home/three-cards-illustration/three.glb new file mode 100644 index 0000000000..0466963315 Binary files /dev/null and b/packages/twenty-website-new/public/illustrations/home/three-cards-illustration/three.glb differ diff --git a/packages/twenty-website-new/public/illustrations/home/three-cards-illustration/two.glb b/packages/twenty-website-new/public/illustrations/home/three-cards-illustration/two.glb new file mode 100644 index 0000000000..9919100fd8 Binary files /dev/null and b/packages/twenty-website-new/public/illustrations/home/three-cards-illustration/two.glb differ diff --git a/packages/twenty-website-new/public/illustrations/partner/testimonials/testimonials.glb b/packages/twenty-website-new/public/illustrations/partner/testimonials/testimonials.glb new file mode 100644 index 0000000000..1da85b3cfb Binary files /dev/null and b/packages/twenty-website-new/public/illustrations/partner/testimonials/testimonials.glb differ diff --git a/packages/twenty-website-new/public/illustrations/partner/three-cards-illustration/one.glb b/packages/twenty-website-new/public/illustrations/partner/three-cards-illustration/one.glb new file mode 100644 index 0000000000..df5415035f Binary files /dev/null and b/packages/twenty-website-new/public/illustrations/partner/three-cards-illustration/one.glb differ diff --git a/packages/twenty-website-new/public/illustrations/partner/three-cards-illustration/three.glb b/packages/twenty-website-new/public/illustrations/partner/three-cards-illustration/three.glb new file mode 100644 index 0000000000..20569023f7 Binary files /dev/null and b/packages/twenty-website-new/public/illustrations/partner/three-cards-illustration/three.glb differ diff --git a/packages/twenty-website-new/public/illustrations/partner/three-cards-illustration/two.glb b/packages/twenty-website-new/public/illustrations/partner/three-cards-illustration/two.glb new file mode 100644 index 0000000000..b7351542af Binary files /dev/null and b/packages/twenty-website-new/public/illustrations/partner/three-cards-illustration/two.glb differ diff --git a/packages/twenty-website-new/public/illustrations/pricing/PlanTable/money.glb b/packages/twenty-website-new/public/illustrations/pricing/PlanTable/money.glb new file mode 100644 index 0000000000..eaa3db151d Binary files /dev/null and b/packages/twenty-website-new/public/illustrations/pricing/PlanTable/money.glb differ diff --git a/packages/twenty-website-new/public/illustrations/pricing/Price/organization.glb b/packages/twenty-website-new/public/illustrations/pricing/Price/organization.glb new file mode 100644 index 0000000000..f98649779e Binary files /dev/null and b/packages/twenty-website-new/public/illustrations/pricing/Price/organization.glb differ diff --git a/packages/twenty-website-new/public/illustrations/pricing/Price/pro.glb b/packages/twenty-website-new/public/illustrations/pricing/Price/pro.glb new file mode 100644 index 0000000000..9e9853b547 Binary files /dev/null and b/packages/twenty-website-new/public/illustrations/pricing/Price/pro.glb differ diff --git a/packages/twenty-website-new/public/illustrations/product/hero/hero.glb b/packages/twenty-website-new/public/illustrations/product/hero/hero.glb new file mode 100644 index 0000000000..6f39c4ed20 Binary files /dev/null and b/packages/twenty-website-new/public/illustrations/product/hero/hero.glb differ diff --git a/packages/twenty-website-new/public/illustrations/product/three-cards-illustration/one.glb b/packages/twenty-website-new/public/illustrations/product/three-cards-illustration/one.glb new file mode 100644 index 0000000000..cd043ca837 Binary files /dev/null and b/packages/twenty-website-new/public/illustrations/product/three-cards-illustration/one.glb differ diff --git a/packages/twenty-website-new/public/illustrations/product/three-cards-illustration/three.glb b/packages/twenty-website-new/public/illustrations/product/three-cards-illustration/three.glb new file mode 100644 index 0000000000..281546022a Binary files /dev/null and b/packages/twenty-website-new/public/illustrations/product/three-cards-illustration/three.glb differ diff --git a/packages/twenty-website-new/public/illustrations/product/three-cards-illustration/two.glb b/packages/twenty-website-new/public/illustrations/product/three-cards-illustration/two.glb new file mode 100644 index 0000000000..27a9aaf4cc Binary files /dev/null and b/packages/twenty-website-new/public/illustrations/product/three-cards-illustration/two.glb differ diff --git a/packages/twenty-website-new/public/illustrations/why-twenty/hero/logo.glb b/packages/twenty-website-new/public/illustrations/why-twenty/hero/logo.glb new file mode 100644 index 0000000000..43cc57570e Binary files /dev/null and b/packages/twenty-website-new/public/illustrations/why-twenty/hero/logo.glb differ diff --git a/packages/twenty-website-new/public/illustrations/why-twenty/quote/quote.glb b/packages/twenty-website-new/public/illustrations/why-twenty/quote/quote.glb new file mode 100644 index 0000000000..1da85b3cfb Binary files /dev/null and b/packages/twenty-website-new/public/illustrations/why-twenty/quote/quote.glb differ diff --git a/packages/twenty-website-new/public/illustrations/why-twenty/stepper/logo.glb b/packages/twenty-website-new/public/illustrations/why-twenty/stepper/logo.glb new file mode 100644 index 0000000000..43cc57570e Binary files /dev/null and b/packages/twenty-website-new/public/illustrations/why-twenty/stepper/logo.glb differ diff --git a/packages/twenty-website-new/public/images/product/feature/contacts.png b/packages/twenty-website-new/public/images/product/feature/contacts.png new file mode 100644 index 0000000000..aba4db30a9 Binary files /dev/null and b/packages/twenty-website-new/public/images/product/feature/contacts.png differ diff --git a/packages/twenty-website-new/public/images/product/feature/dashboards.png b/packages/twenty-website-new/public/images/product/feature/dashboards.png new file mode 100644 index 0000000000..ab2aa00921 Binary files /dev/null and b/packages/twenty-website-new/public/images/product/feature/dashboards.png differ diff --git a/packages/twenty-website-new/public/images/product/feature/data.png b/packages/twenty-website-new/public/images/product/feature/data.png new file mode 100644 index 0000000000..acd056e784 Binary files /dev/null and b/packages/twenty-website-new/public/images/product/feature/data.png differ diff --git a/packages/twenty-website-new/public/images/product/feature/emails.png b/packages/twenty-website-new/public/images/product/feature/emails.png new file mode 100644 index 0000000000..d22f8fb1d6 Binary files /dev/null and b/packages/twenty-website-new/public/images/product/feature/emails.png differ diff --git a/packages/twenty-website-new/public/images/product/feature/files.png b/packages/twenty-website-new/public/images/product/feature/files.png new file mode 100644 index 0000000000..1375b6ca13 Binary files /dev/null and b/packages/twenty-website-new/public/images/product/feature/files.png differ diff --git a/packages/twenty-website-new/public/images/product/feature/mask.png b/packages/twenty-website-new/public/images/product/feature/mask.png new file mode 100644 index 0000000000..e5686762f3 Binary files /dev/null and b/packages/twenty-website-new/public/images/product/feature/mask.png differ diff --git a/packages/twenty-website-new/public/images/product/feature/pipeline.png b/packages/twenty-website-new/public/images/product/feature/pipeline.png new file mode 100644 index 0000000000..9a46f57d47 Binary files /dev/null and b/packages/twenty-website-new/public/images/product/feature/pipeline.png differ diff --git a/packages/twenty-website-new/public/images/product/feature/tasks.png b/packages/twenty-website-new/public/images/product/feature/tasks.png new file mode 100644 index 0000000000..3e3c00cc0a Binary files /dev/null and b/packages/twenty-website-new/public/images/product/feature/tasks.png differ diff --git a/packages/twenty-website-new/src/app/(home)/constants/footer.ts b/packages/twenty-website-new/src/app/(home)/constants/footer.ts new file mode 100644 index 0000000000..f7874be96a --- /dev/null +++ b/packages/twenty-website-new/src/app/(home)/constants/footer.ts @@ -0,0 +1,107 @@ +import type { FooterDataType } from '@/sections/Footer/types'; + +export const FOOTER_DATA: FooterDataType = { + illustration: { + src: 'https://app.endlesstools.io/embed/73beb0a6-bcda-410f-8e4c-a3bcd70224b0', + title: 'Footer background', + }, + bottom: { + copyright: '© 2026 – Twenty', + credit: 'Design by Ardent.', + }, + navGroups: [ + { + id: 'footer-sitemap', + title: 'Sitemap', + ctas: [], + links: [ + { label: 'Home', href: '/', external: false }, + { label: 'Product', href: '/product', external: false }, + { label: 'Pricing', href: '/pricing', external: false }, + { label: 'Partners', href: '/partner', external: false }, + { label: 'Why Twenty', href: '/why-twenty', external: false }, + ], + }, + { + id: 'footer-help', + title: 'Help', + ctas: [], + links: [ + { label: 'Developers', href: '#', external: false }, + { label: 'FAQ', href: '#', external: false }, + { label: 'Support', href: '#', external: false }, + { label: 'Release Notes', href: '#', external: false }, + ], + }, + { + id: 'footer-legal', + title: 'Legal', + ctas: [], + links: [ + { label: 'Cookie Policy', href: '#', external: false }, + { label: 'Privacy Policy', href: '#', external: false }, + { label: 'Terms and Conditions', href: '#', external: false }, + ], + }, + { + id: 'footer-connect', + title: 'Connect', + ctas: [ + { + color: 'secondary', + href: 'https://app.twenty.com/welcome', + label: 'Talk to us', + type: 'anchor', + variant: 'contained', + }, + { + color: 'secondary', + href: 'https://app.twenty.com/welcome', + label: 'Get started', + type: 'anchor', + variant: 'outlined', + }, + ], + links: [ + { + label: 'LinkedIn', + href: 'https://www.linkedin.com/company/twenty', + external: true, + }, + ], + }, + ], + socialLinks: [ + { + href: 'https://github.com/twentyhq/twenty', + icon: 'github', + label: '39.8K', + ariaLabel: 'GitHub (opens in new tab)', + showInDesktop: true, + showInDrawer: true, + }, + { + href: 'https://discord.gg/cx5n4Jzs57', + icon: 'discord', + label: '5.6K', + ariaLabel: 'Discord (opens in new tab)', + className: 'discord-link', + showInDesktop: true, + showInDrawer: true, + }, + { + href: 'https://www.linkedin.com/company/twenty', + icon: 'linkedin', + ariaLabel: 'LinkedIn (opens in new tab)', + showInDesktop: false, + showInDrawer: true, + }, + { + href: 'https://x.com/twentycrm', + icon: 'x', + ariaLabel: 'X (opens in new tab)', + showInDesktop: false, + showInDrawer: true, + }, + ], +}; diff --git a/packages/twenty-website-new/src/app/layout.tsx b/packages/twenty-website-new/src/app/layout.tsx index c2300d7d59..d0d6e1a779 100644 --- a/packages/twenty-website-new/src/app/layout.tsx +++ b/packages/twenty-website-new/src/app/layout.tsx @@ -1,3 +1,4 @@ +import { FOOTER_DATA } from '@/app/(home)/constants/footer'; import { Footer } from '@/sections/Footer/components'; import { theme } from '@/theme'; import { cssVariables } from '@/theme/css-variables'; @@ -69,11 +70,14 @@ export default function RootLayout({ className={`${cssVariables} ${hostGrotesk.variable} ${aleo.variable} ${azeretMono.variable}`} > {children} - + - - - + + + diff --git a/packages/twenty-website-new/src/app/partner/constants/signoff.ts b/packages/twenty-website-new/src/app/partner/constants/signoff.ts new file mode 100644 index 0000000000..822f00b00c --- /dev/null +++ b/packages/twenty-website-new/src/app/partner/constants/signoff.ts @@ -0,0 +1,11 @@ +import { SignoffDataType } from '@/sections/Signoff/types'; + +export const SIGNOFF_DATA: SignoffDataType = { + heading: [ + { text: 'Ready to grow', fontFamily: 'serif' }, + { text: ' with Twenty?', fontFamily: 'sans' }, + ], + body: { + text: 'Join our partner ecosystem and help businesses take control of their CRM.', + }, +}; diff --git a/packages/twenty-website-new/src/app/partner/page.tsx b/packages/twenty-website-new/src/app/partner/page.tsx index 26ca2919a2..fe24dde99a 100644 --- a/packages/twenty-website-new/src/app/partner/page.tsx +++ b/packages/twenty-website-new/src/app/partner/page.tsx @@ -4,6 +4,7 @@ import { TRUSTED_BY_DATA } from '@/app/(home)/constants/trusted-by'; import { ENGAGEMENT_BAND_DATA } from '@/app/partner/constants/engagement-band'; import { HERO_DATA } from '@/app/partner/constants/hero'; import { TESTIMONIALS_DATA } from '@/app/partner/constants/testimonials'; +import { SIGNOFF_DATA } from '@/app/partner/constants/signoff'; import { THREE_CARDS_ILLUSTRATION_DATA } from '@/app/partner/constants/three-cards-illustration'; import { Body, Eyebrow, Heading, LinkButton } from '@/design-system/components'; import { Pages } from '@/enums/pages'; @@ -11,6 +12,7 @@ import { EngagementBand } from '@/sections/EngagementBand/components'; import { Faq } from '@/sections/Faq/components'; import { Hero } from '@/sections/Hero/components'; import { Menu } from '@/sections/Menu/components'; +import { Signoff } from '@/sections/Signoff/components'; import { Testimonials } from '@/sections/Testimonials/components'; import { ThreeCards } from '@/sections/ThreeCards/components'; import { TrustedBy } from '@/sections/TrustedBy/components'; @@ -117,6 +119,30 @@ export default function PartnerPage() { /> + + + + + + + + + diff --git a/packages/twenty-website-new/src/app/pricing/constants/plan-table.ts b/packages/twenty-website-new/src/app/pricing/constants/plan-table.ts new file mode 100644 index 0000000000..47439f3d7b --- /dev/null +++ b/packages/twenty-website-new/src/app/pricing/constants/plan-table.ts @@ -0,0 +1,164 @@ +import type { PlanTableDataType } from '@/sections/PlanTable/types'; + +const EMAIL_OUTREACH_TASK = { + label: 'Tasks', + options: ['email outreach'], + value: 'email outreach', +} as const; + +export const PLAN_TABLE_DATA: PlanTableDataType = { + featureColumnLabel: 'Feature', + rows: [ + { + featureLabel: 'Price', + tiers: { + organization: { kind: 'text', text: '$19' }, + pro: { kind: 'text', text: '$9' }, + }, + type: 'row', + }, + { + featureLabel: 'Self Hosting discount', + tiers: { + organization: { kind: 'text', text: 'Free for up to 20 seats' }, + pro: { kind: 'text', text: 'Free for up to 20 seats' }, + }, + type: 'row', + }, + { + featureLabel: 'Seats limit', + tiers: { + organization: { kind: 'text', text: 'Unlimited' }, + pro: { kind: 'text', text: 'Unlimited' }, + }, + type: 'row', + }, + { + title: 'Reports', + type: 'category', + }, + { + featureLabel: 'Number of dashboards', + tiers: { + organization: { kind: 'text', text: 'Unlimited' }, + pro: { kind: 'text', text: 'Unlimited' }, + }, + type: 'row', + }, + { + title: 'Automations', + type: 'category', + }, + { + featureLabel: 'Workflows', + tiers: { + organization: { kind: 'yes', label: 'Yes' }, + pro: { kind: 'yes', label: 'Yes' }, + }, + type: 'row', + }, + { + featureLabel: 'AI Agent', + tiers: { + organization: { kind: 'yes', label: 'Yes' }, + pro: { kind: 'yes', label: 'Yes' }, + }, + type: 'row', + }, + { + featureLabel: 'Automation credits', + tiers: { + organization: { kind: 'text', text: '1,000' }, + pro: { kind: 'text', text: '1,000' }, + }, + type: 'row', + }, + { + featureLabel: 'Extra credits', + tiers: { + organization: { kind: 'dash' }, + pro: { + kind: 'text', + text: '1,000 for $10 - 5,000 for $48', + }, + }, + type: 'row', + }, + { + calculator: { + priceLine: { + amount: '700€', + label: 'Price', + periodSuffix: '/month', + }, + sections: [ + { + id: 'intelligence-ai', + modelField: { + label: 'Model', + options: ['GPT 5'], + value: 'GPT 5', + }, + requestField: { label: 'Requests', value: 100 }, + tasksField: { + label: EMAIL_OUTREACH_TASK.label, + options: [...EMAIL_OUTREACH_TASK.options], + value: EMAIL_OUTREACH_TASK.value, + }, + title: 'Intelligence AI', + }, + { + id: 'compute', + requestField: { label: 'Requests', value: 100 }, + tasksField: { + label: EMAIL_OUTREACH_TASK.label, + options: [...EMAIL_OUTREACH_TASK.options], + value: EMAIL_OUTREACH_TASK.value, + }, + title: 'Compute', + }, + { + id: 'storage', + requestField: { label: 'Requests', value: 100 }, + tasksField: { + label: EMAIL_OUTREACH_TASK.label, + options: [...EMAIL_OUTREACH_TASK.options], + value: EMAIL_OUTREACH_TASK.value, + }, + title: 'Storage', + }, + ], + visual: { + body: { + text: 'Estimate your costs effortlessly with our price calculator customize your workspace in just a few clicks!', + }, + heading: [ + { fontFamily: 'serif', text: 'Get a glimpse of ' }, + { fontFamily: 'sans', text: "what it'll cost" }, + ], + }, + }, + type: 'calculator', + }, + { + title: 'Workspace', + type: 'category', + }, + { + featureLabel: 'Custom objects', + tiers: { + organization: { kind: 'text', text: 'Unlimited' }, + pro: { kind: 'text', text: 'Unlimited' }, + }, + type: 'row', + }, + ], + seeMoreFeaturesCta: { + href: '/pricing', + label: 'See more features', + }, + tierColumns: [ + { id: 'pro', label: 'Pro' }, + { id: 'organization', label: 'Organization' }, + ], +}; diff --git a/packages/twenty-website-new/src/app/pricing/constants/salesforce.ts b/packages/twenty-website-new/src/app/pricing/constants/salesforce.ts new file mode 100644 index 0000000000..c59585d475 --- /dev/null +++ b/packages/twenty-website-new/src/app/pricing/constants/salesforce.ts @@ -0,0 +1,66 @@ +import type { SalesforceDataType } from '@/sections/Salesforce/types'; + +export const SALESFORCE_DATA: SalesforceDataType = { + body: { + text: 'Aenean lacinia bibendum nulla sed consectetur. Integer posuere erat a ante venenatis dapibus.', + }, + heading: [ + { text: 'Trust the n°1 CRM,', fontFamily: 'serif' }, + { text: ' or not!', fontFamily: 'sans' }, + ], + pricing: { + addons: [ + { + id: 'api-access', + label: 'API access', + popupKey: 'wrongChoiceDefault', + rightLabel: '+$35/user per month', + }, + { + id: 'webhooks', + label: 'Webhooks (Change data capture)', + popupKey: 'wrongChoiceLorem', + rightLabel: '+$7000/org per month', + }, + { + disabled: true, + id: 'live-updates', + label: 'Live updates', + popupKey: 'wrongChoiceDefault', + rightLabel: 'Unavailable', + }, + { + defaultChecked: true, + id: 'ui-theme', + label: 'UI theme', + popupKey: 'wrongChoiceDefault', + rightLabel: 'Retro 2015', + }, + { + id: 'sso', + label: 'SSO', + popupKey: 'wrongChoiceDefault', + rightLabel: '+$5/user per month', + }, + { + id: 'permissions', + label: '11 permissions groups', + popupKey: 'wrongChoiceDefault', + rightLabel: '+$75/user per month (Switch to enterprise!)', + }, + { + id: 'maps', + label: 'Maps view', + popupKey: 'wrongChoiceDefault', + rightLabel: '+$105/user per month', + }, + ], + featureSectionHeading: 'Best for Salesforce', + priceAmount: '$100 USD', + priceSuffix: ' / seat / month - billed yearly', + primaryCtaLabel: 'Do you really want to click and ask your first quote ???', + productTitle: 'Salesforce Pro', + secondaryCtaLabel: "More options available, BUT DON'T CLICK !!", + windowTitle: 'Salesforce Pro pricing', + }, +}; diff --git a/packages/twenty-website-new/src/app/pricing/page.tsx b/packages/twenty-website-new/src/app/pricing/page.tsx index 2274edb056..b9211fb027 100644 --- a/packages/twenty-website-new/src/app/pricing/page.tsx +++ b/packages/twenty-website-new/src/app/pricing/page.tsx @@ -2,6 +2,8 @@ import { FAQ_DATA } from '@/app/(home)/constants/faq'; import { MENU_DATA } from '@/app/(home)/constants/menu'; import { ENGAGEMENT_BAND_DATA } from '@/app/partner/constants/engagement-band'; import { HERO_DATA } from '@/app/pricing/constants/hero'; +import { PLAN_TABLE_DATA } from '@/app/pricing/constants/plan-table'; +import { SALESFORCE_DATA } from '@/app/pricing/constants/salesforce'; import { Eyebrow, LinkButton } from '@/design-system/components'; import { Pages } from '@/enums/pages'; import { EngagementBand } from '@/sections/EngagementBand/components'; @@ -9,6 +11,8 @@ import { Faq } from '@/sections/Faq/components'; import { Hero } from '@/sections/Hero/components'; import { Menu } from '@/sections/Menu/components'; import { Plans } from '@/sections/Plans/components'; +import { PlanTable } from '@/sections/PlanTable/components'; +import { Salesforce } from '@/sections/Salesforce/components'; import { theme } from '@/theme'; export default function PricingPage() { @@ -58,6 +62,17 @@ export default function PricingPage() { + + + + + + diff --git a/packages/twenty-website-new/src/app/product/constants/feature.ts b/packages/twenty-website-new/src/app/product/constants/feature.ts new file mode 100644 index 0000000000..38dadf6a94 --- /dev/null +++ b/packages/twenty-website-new/src/app/product/constants/feature.ts @@ -0,0 +1,87 @@ +import type { FeatureDataType } from '@/sections/Feature/types'; + +export const FEATURE_DATA: FeatureDataType = { + eyebrow: { + heading: { + text: 'Core Features', + fontFamily: 'sans', + }, + }, + heading: [ + { text: 'Everything you need,', fontFamily: 'serif' }, + { text: ' out of the box', fontFamily: 'sans' }, + ], + mask: { src: '/images/product/feature/mask.png', alt: '' }, + tiles: [ + { + icon: 'check', + image: { src: '/images/product/feature/dashboards.png', alt: '' }, + heading: { text: 'Reports & Dashboards', fontFamily: 'sans' }, + bullets: [ + { text: 'Build custom dashboards' }, + { text: 'Aggregate, bar, line, pie, and gauge widgets' }, + { text: 'Filtered metrics from live CRM data' }, + ], + }, + { + icon: 'check', + image: { src: '/images/product/feature/tasks.png', alt: '' }, + heading: { text: 'Tasks & Activities', fontFamily: 'sans' }, + bullets: [ + { text: 'Create tasks from records' }, + { text: 'Assign owners and due dates' }, + { text: 'Rich notes attached to records' }, + ], + }, + { + icon: 'check', + image: { src: '/images/product/feature/emails.png', alt: '' }, + heading: { text: 'Email & Calendar', fontFamily: 'sans' }, + bullets: [ + { text: 'Connect Google or Microsoft accounts' }, + { text: 'Emails and events linked to CRM records' }, + { text: 'Full communication history in one place' }, + ], + }, + { + icon: 'check', + image: { src: '/images/product/feature/contacts.png', alt: '' }, + heading: { text: 'Contacts & Companies', fontFamily: 'sans' }, + bullets: [ + { text: 'Custom fields and relationships' }, + { text: 'Unified timeline (emails, events, tasks, notes, files)' }, + { text: 'Email/calendar activity on each record' }, + ], + }, + { + icon: 'check', + image: { src: '/images/product/feature/pipeline.png', alt: '' }, + heading: { text: 'Pipeline Management', fontFamily: 'sans' }, + bullets: [ + { text: 'Custom deal stages for your process' }, + { text: 'Drag-and-drop deals between stages' }, + { text: 'Track amount and close date' }, + ], + }, + { + icon: 'check', + image: { src: '/images/product/feature/files.png', alt: '' }, + heading: { text: 'Files', fontFamily: 'sans' }, + bullets: [ + { text: 'Multi-file upload on records' }, + { text: 'Rename, download, and delete attachments' }, + { text: 'In-app preview for supported file types (when enabled)' }, + ], + }, + { + icon: 'check', + image: { src: '/images/product/feature/data.png', alt: '' }, + heading: { text: 'Data import', fontFamily: 'sans' }, + bullets: [ + { text: 'CSV import flow' }, + { text: 'Column-to-field mapping (including relations)' }, + { text: 'CSV export anytime' }, + ], + }, + ], +}; diff --git a/packages/twenty-website-new/src/app/product/page.tsx b/packages/twenty-website-new/src/app/product/page.tsx index f44c91f59b..c792375fd1 100644 --- a/packages/twenty-website-new/src/app/product/page.tsx +++ b/packages/twenty-website-new/src/app/product/page.tsx @@ -2,15 +2,19 @@ import { FAQ_DATA } from '@/app/(home)/constants/faq'; import { MENU_DATA } from '@/app/(home)/constants/menu'; import { TRUSTED_BY_DATA } from '@/app/(home)/constants/trusted-by'; import { DEMO_DATA } from '@/app/product/constants/demo'; +import { FEATURE_DATA } from '@/app/product/constants/feature'; import { HERO_DATA } from '@/app/product/constants/hero'; +import { STEPPER_DATA } from '@/app/product/constants/stepper'; import { TABS_DATA } from '@/app/product/constants/tabs'; import { THREE_CARDS_ILLUSTRATION_DATA } from '@/app/product/constants/three-cards'; import { Body, Eyebrow, Heading, LinkButton } from '@/design-system/components'; import { Pages } from '@/enums/pages'; import { Demo } from '@/sections/Demo/components'; +import { Feature } from '@/sections/Feature/components'; import { Faq } from '@/sections/Faq/components'; import { Hero } from '@/sections/Hero/components'; import { Menu } from '@/sections/Menu/components'; +import { ProductStepper } from '@/sections/ProductStepper/components'; import { Tabs } from '@/sections/Tabs/components'; import { ThreeCards } from '@/sections/ThreeCards/components'; import { TrustedBy } from '@/sections/TrustedBy/components'; @@ -64,6 +68,17 @@ export default function ProductPage() { + + + + + + + + - {/* */} + /> diff --git a/packages/twenty-website-new/src/app/why-twenty/constants/editorial-four.ts b/packages/twenty-website-new/src/app/why-twenty/constants/editorial-four.ts new file mode 100644 index 0000000000..3c0e13d42a --- /dev/null +++ b/packages/twenty-website-new/src/app/why-twenty/constants/editorial-four.ts @@ -0,0 +1,22 @@ +import type { EditorialDataType } from '@/sections/Editorial/types/EditorialData'; + +export const EDITORIAL_FOUR: EditorialDataType = { + eyebrow: { + heading: { + fontFamily: 'sans', + text: 'What must our CRM do that nobody else can?', + }, + }, + heading: { + fontFamily: 'serif', + text: 'CRM becomes an execution system', + }, + body: [ + { + text: 'For years, CRM was treated like a ledger. A place to log what happened. AI flips that. When agents draft emails, qualify leads, propose next steps, update records, and trigger follow-ups, the CRM becomes where work begins.', + }, + { + text: 'It becomes the interface between humans, agents, and reality. The customer system is the map that directs action. If the map is generic, the actions will be generic. If the map is wrong, the actions will be wrong at scale. If they matter, you must own them.', + }, + ], +}; diff --git a/packages/twenty-website-new/src/app/why-twenty/constants/editorial-one.ts b/packages/twenty-website-new/src/app/why-twenty/constants/editorial-one.ts new file mode 100644 index 0000000000..9e13f97fa2 --- /dev/null +++ b/packages/twenty-website-new/src/app/why-twenty/constants/editorial-one.ts @@ -0,0 +1,22 @@ +import type { EditorialDataType } from '@/sections/Editorial/types/EditorialData'; + +export const EDITORIAL_ONE: EditorialDataType = { + eyebrow: { + heading: { + fontFamily: 'sans', + text: 'What must our CRM do that nobody else can?', + }, + }, + heading: [ + { fontFamily: 'serif', text: 'Packaged CRMs' }, + { fontFamily: 'sans', text: ' optimize for sameness' }, + ], + body: [ + { + text: 'Off-the-shelf CRMs are built for the median workflow. They standardize what an account is, what a pipeline looks like, what stages matter, and what "good" reporting means. They help you keep up with the market, this is beta. But when it is time to generate differentiated value, alpha, they fall short.', + }, + { + text: 'When every company runs the same objects, the same lifecycle stages, the same routing logic, and the same prepackaged "AI insights," the CRM stops being an advantage. It becomes a cost of entry. You do not just buy software. You inherit assumptions, assumptions that over time become constraints.', + }, + ], +}; diff --git a/packages/twenty-website-new/src/app/why-twenty/constants/editorial-three.ts b/packages/twenty-website-new/src/app/why-twenty/constants/editorial-three.ts new file mode 100644 index 0000000000..8fb7dc58c2 --- /dev/null +++ b/packages/twenty-website-new/src/app/why-twenty/constants/editorial-three.ts @@ -0,0 +1,14 @@ +import type { EditorialDataType } from '@/sections/Editorial/types/EditorialData'; + +export const EDITORIAL_THREE: EditorialDataType = { + eyebrow: { + heading: { fontFamily: 'sans', text: 'Lorem ipsum dolor' }, + }, + heading: [ + { fontFamily: 'serif', text: 'Lorem ipsum dolor sit amet,' }, + { fontFamily: 'sans', text: ' consectetur' }, + ], + body: { + text: 'AI compresses the time between idea and implementation. Feature parity arrives faster. "Best practices" expire faster. Competitive imitation accelerates because everyone has access to the same models, the same libraries, and the same distribution channels. When the surface area of software becomes cheap, differentiation moves underneath it.', + }, +}; diff --git a/packages/twenty-website-new/src/app/why-twenty/constants/editorial-two.ts b/packages/twenty-website-new/src/app/why-twenty/constants/editorial-two.ts new file mode 100644 index 0000000000..c3f90487d8 --- /dev/null +++ b/packages/twenty-website-new/src/app/why-twenty/constants/editorial-two.ts @@ -0,0 +1,7 @@ +import type { EditorialDataType } from '@/sections/Editorial/types/EditorialData'; + +export const EDITORIAL_TWO: EditorialDataType = { + body: { + text: 'AI compresses the time between idea and implementation. Feature parity arrives faster. "Best practices" expire faster. Competitive imitation accelerates because everyone has access to the same models, the same libraries, and the same distribution channels. When the surface area of software becomes cheap, differentiation moves underneath it.', + }, +}; diff --git a/packages/twenty-website-new/src/app/why-twenty/constants/marquee.ts b/packages/twenty-website-new/src/app/why-twenty/constants/marquee.ts new file mode 100644 index 0000000000..d203885d26 --- /dev/null +++ b/packages/twenty-website-new/src/app/why-twenty/constants/marquee.ts @@ -0,0 +1,9 @@ +import type { MarqueeDataType } from '@/sections/Marquee/types'; + +export const MARQUEE_DATA: MarqueeDataType = { + heading: [ + { fontFamily: 'serif', text: 'AI raises' }, + { fontFamily: 'sans', text: 'the cost of' }, + { fontFamily: 'sans', text: 'being wrong' }, + ], +}; diff --git a/packages/twenty-website-new/src/app/why-twenty/constants/signoff.ts b/packages/twenty-website-new/src/app/why-twenty/constants/signoff.ts new file mode 100644 index 0000000000..f55ea37b05 --- /dev/null +++ b/packages/twenty-website-new/src/app/why-twenty/constants/signoff.ts @@ -0,0 +1,11 @@ +import { SignoffDataType } from '@/sections/Signoff/types'; + +export const SIGNOFF_DATA: SignoffDataType = { + heading: [ + { text: 'The future of CRM is built,', fontFamily: 'serif' }, + { text: ' not bought.', fontFamily: 'sans' }, + ], + body: { + text: 'This is the world we are building toward at Twenty. ', + }, +}; diff --git a/packages/twenty-website-new/src/app/why-twenty/constants/stepper.ts b/packages/twenty-website-new/src/app/why-twenty/constants/stepper.ts new file mode 100644 index 0000000000..3520af7cdf --- /dev/null +++ b/packages/twenty-website-new/src/app/why-twenty/constants/stepper.ts @@ -0,0 +1,20 @@ +import type { WhyTwentyStepperDataType } from '@/sections/WhyTwentyStepper/types'; + +export const STEPPER_DATA: WhyTwentyStepperDataType = { + heading: { text: 'Our vision', fontFamily: 'serif' }, + body: [ + { + text: 'We believe every serious company will need a malleable system of record for customers. Not a collection of disconnected tools.', + }, + { + text: 'Not a frozen monolith that only specialists can change. A living system that can evolve as fast as strategy evolves, while remaining trustworthy enough for humans, agents, and regulators to rely on.', + }, + { + text: 'Because in the AI age, the winners will not be the companies with the most dashboards. They will be the companies whose systems turn information into decisions, and decisions into action, faster than everyone else.', + }, + ], + illustration: { + src: 'https://app.endlesstools.io/embed/ddc485f9-cc8e-422b-a076-5430eb84e21a', + title: 'Endless Tools Editor', + }, +}; diff --git a/packages/twenty-website-new/src/app/why-twenty/page.tsx b/packages/twenty-website-new/src/app/why-twenty/page.tsx index 72c61be270..05173b718a 100644 --- a/packages/twenty-website-new/src/app/why-twenty/page.tsx +++ b/packages/twenty-website-new/src/app/why-twenty/page.tsx @@ -1,13 +1,25 @@ import { MENU_DATA } from '@/app/(home)/constants/menu'; +import { EDITORIAL_FOUR } from '@/app/why-twenty/constants/editorial-four'; +import { EDITORIAL_ONE } from '@/app/why-twenty/constants/editorial-one'; +import { EDITORIAL_THREE } from '@/app/why-twenty/constants/editorial-three'; +import { EDITORIAL_TWO } from '@/app/why-twenty/constants/editorial-two'; import { HERO_DATA } from '@/app/why-twenty/constants/hero'; +import { MARQUEE_DATA } from '@/app/why-twenty/constants/marquee'; import { QUOTE_DATA } from '@/app/why-twenty/constants/quote'; +import { SIGNOFF_DATA } from '@/app/why-twenty/constants/signoff'; import { STATEMENT_ONE } from '@/app/why-twenty/constants/statement-one'; import { STATEMENT_TWO } from '@/app/why-twenty/constants/statement-two'; +import { STEPPER_DATA } from '@/app/why-twenty/constants/stepper'; +import { LinkButton } from '@/design-system/components'; import { Pages } from '@/enums/pages'; +import { Editorial } from '@/sections/Editorial/components'; import { Hero } from '@/sections/Hero/components'; +import { Marquee } from '@/sections/Marquee/components'; import { Menu } from '@/sections/Menu/components'; import { Quote } from '@/sections/Quote/components'; +import { Signoff } from '@/sections/Signoff/components'; import { Statement } from '@/sections/Statement/components'; +import { WhyTwentyStepper } from '@/sections/WhyTwentyStepper/components'; import { theme } from '@/theme'; export default function WhyTwentyPage() { @@ -38,11 +50,55 @@ export default function WhyTwentyPage() { /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); } diff --git a/packages/twenty-website-new/src/design-system/components/LazyEmbed/LazyEmbed.tsx b/packages/twenty-website-new/src/design-system/components/LazyEmbed/LazyEmbed.tsx new file mode 100644 index 0000000000..3debf4b12a --- /dev/null +++ b/packages/twenty-website-new/src/design-system/components/LazyEmbed/LazyEmbed.tsx @@ -0,0 +1,157 @@ +'use client'; + +import { + type ComponentPropsWithoutRef, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; + +const DEFAULT_LOAD_ROOT_MARGIN = '1200px 0px'; +const DEFAULT_UNLOAD_ROOT_MARGIN = '1800px 0px'; +const DEFAULT_UNLOAD_DELAY_MS = 1500; + +type LazyEmbedProps = Omit, 'loading' | 'src'> & { + eager?: boolean; + rootMargin?: string; + src: string; + unloadDelayMs?: number; + unloadWhenHidden?: boolean; + unloadRootMargin?: string; +}; + +export function LazyEmbed({ + eager = false, + rootMargin = DEFAULT_LOAD_ROOT_MARGIN, + unloadDelayMs = DEFAULT_UNLOAD_DELAY_MS, + unloadWhenHidden = true, + unloadRootMargin = DEFAULT_UNLOAD_ROOT_MARGIN, + src, + className, + style, + role, + tabIndex, + 'aria-hidden': ariaHidden, + ...iframeProps +}: LazyEmbedProps) { + const [hostNode, setHostNode] = useState(null); + const [shouldRender, setShouldRender] = useState(eager); + const unloadTimeoutRef = useRef(null); + + const wrapperProps = useMemo( + () => ({ + 'aria-hidden': ariaHidden, + className, + role, + style, + tabIndex, + }), + [ariaHidden, className, role, style, tabIndex], + ); + + useEffect(() => { + if (!hostNode) { + return; + } + + if (!('IntersectionObserver' in window)) { + setShouldRender(true); + return; + } + + const clearPendingUnload = () => { + if (unloadTimeoutRef.current === null) { + return; + } + + window.clearTimeout(unloadTimeoutRef.current); + unloadTimeoutRef.current = null; + }; + + const scheduleUnload = () => { + if (!unloadWhenHidden || unloadTimeoutRef.current !== null) { + return; + } + + unloadTimeoutRef.current = window.setTimeout(() => { + setShouldRender(false); + unloadTimeoutRef.current = null; + }, unloadDelayMs); + }; + + if (eager) { + setShouldRender(true); + } + + const loadObserver = new IntersectionObserver( + (entries) => { + const isNearViewport = entries.some((entry) => entry.isIntersecting); + + if (isNearViewport) { + clearPendingUnload(); + setShouldRender(true); + } + }, + { rootMargin }, + ); + + loadObserver.observe(hostNode); + + let unloadObserver: IntersectionObserver | null = null; + + if (unloadWhenHidden) { + unloadObserver = new IntersectionObserver( + (entries) => { + const isStillClose = entries.some((entry) => entry.isIntersecting); + + if (isStillClose) { + clearPendingUnload(); + return; + } + + scheduleUnload(); + }, + { rootMargin: unloadRootMargin }, + ); + + unloadObserver.observe(hostNode); + } + + return () => { + clearPendingUnload(); + loadObserver.disconnect(); + unloadObserver?.disconnect(); + }; + }, [ + eager, + hostNode, + rootMargin, + unloadDelayMs, + unloadWhenHidden, + unloadRootMargin, + ]); + + return ( +
+ {shouldRender ? ( +