diff --git a/package.json b/package.json index 55aa2a8a25..c6233d6e40 100644 --- a/package.json +++ b/package.json @@ -203,6 +203,7 @@ "packages/twenty-utils", "packages/twenty-zapier", "packages/twenty-website", + "packages/twenty-website-new", "packages/twenty-docs", "packages/twenty-e2e-testing", "packages/twenty-shared", diff --git a/packages/twenty-website-new/.gitignore b/packages/twenty-website-new/.gitignore new file mode 100644 index 0000000000..301c67f0cd --- /dev/null +++ b/packages/twenty-website-new/.gitignore @@ -0,0 +1,39 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# DB +*.sqlite \ No newline at end of file diff --git a/packages/twenty-website-new/.oxlintrc.json b/packages/twenty-website-new/.oxlintrc.json new file mode 100644 index 0000000000..3402a729a8 --- /dev/null +++ b/packages/twenty-website-new/.oxlintrc.json @@ -0,0 +1,48 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["react", "typescript", "import", "unicorn"], + "categories": { + "correctness": "off" + }, + "ignorePatterns": ["node_modules"], + "rules": { + "func-style": ["error", "declaration", { "allowArrowFunctions": true }], + "no-console": "off", + "no-control-regex": "off", + "no-debugger": "error", + "no-duplicate-imports": "error", + "no-undef": "off", + "no-unused-vars": "off", + "no-redeclare": "off", + "import/no-duplicates": "error", + "typescript/no-redeclare": "error", + "typescript/ban-ts-comment": "error", + "typescript/consistent-type-imports": ["error", { + "prefer": "type-imports", + "fixStyle": "inline-type-imports" + }], + "typescript/explicit-function-return-type": "off", + "typescript/explicit-module-boundary-types": "off", + "typescript/no-empty-object-type": ["error", { + "allowInterfaces": "with-single-extends" + }], + "typescript/no-empty-function": "off", + "typescript/no-explicit-any": "off", + "typescript/no-unused-vars": ["warn", { + "vars": "all", + "varsIgnorePattern": "^_", + "args": "after-used", + "argsIgnorePattern": "^_" + }], + "react/no-unescaped-entities": "off", + "react/prop-types": "off", + "react/jsx-key": "off", + "react/display-name": "off", + "react/jsx-uses-react": "off", + "react/react-in-jsx-scope": "off", + "react/jsx-no-useless-fragment": "off", + "react/jsx-props-no-spreading": ["error", { "explicitSpread": "ignore" }], + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn" + } +} diff --git a/packages/twenty-website-new/README.md b/packages/twenty-website-new/README.md new file mode 100644 index 0000000000..663a198e11 --- /dev/null +++ b/packages/twenty-website-new/README.md @@ -0,0 +1,4 @@ +# Twenty-Website + +This is used for the marketing website (twenty.com). +This is not related in any way to the main app, which you can find in twenty-front and twenty-server. diff --git a/packages/twenty-website-new/next.config.ts b/packages/twenty-website-new/next.config.ts new file mode 100644 index 0000000000..14aee202e8 --- /dev/null +++ b/packages/twenty-website-new/next.config.ts @@ -0,0 +1,5 @@ +import withLinaria, { type LinariaConfig } from 'next-with-linaria'; + +const nextConfig: LinariaConfig = { reactCompiler: true }; + +module.exports = withLinaria(nextConfig); diff --git a/packages/twenty-website-new/package.json b/packages/twenty-website-new/package.json new file mode 100644 index 0000000000..c4ad1d9804 --- /dev/null +++ b/packages/twenty-website-new/package.json @@ -0,0 +1,27 @@ +{ + "name": "twenty-website-new", + "private": true, + "scripts": { + "nx": "NX_DEFAULT_PROJECT=twenty-website-new node ../../node_modules/nx/bin/nx.js", + "dev": "npx next dev", + "build": "npx next build", + "start": "npx next start" + }, + "dependencies": { + "@base-ui/react": "^1.3.0", + "@base-ui/utils": "^0.2.6", + "@linaria/core": "^7.0.0", + "@linaria/react": "^7.0.1", + "@wyw-in-js/babel-preset": "^0.8.1", + "next": "16.1.7", + "next-with-linaria": "^1.3.0", + "react": "19.2.3", + "react-dom": "19.2.3" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "babel-plugin-react-compiler": "1.0.0" + } +} diff --git a/packages/twenty-website-new/project.json b/packages/twenty-website-new/project.json new file mode 100644 index 0000000000..7af8727de3 --- /dev/null +++ b/packages/twenty-website-new/project.json @@ -0,0 +1,48 @@ +{ + "name": "twenty-website-new", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/twenty-website-new/src", + "projectType": "application", + "tags": ["scope:website"], + "targets": { + "build": { + "executor": "nx:run-commands", + "cache": true, + "inputs": ["production", "^production"], + "outputs": ["{projectRoot}/.next"], + "options": { + "cwd": "{projectRoot}", + "command": "npx next build" + }, + "dependsOn": ["^build"] + }, + "dev": { + "executor": "nx:run-commands", + "cache": false, + "options": { + "cwd": "{projectRoot}", + "command": "npx next dev" + } + }, + "start": { + "executor": "nx:run-commands", + "cache": false, + "dependsOn": ["build"], + "options": { + "cwd": "{projectRoot}", + "command": "npx next start" + } + }, + "lint": {}, + "lint:diff-with-main": {}, + "typecheck": {}, + "fmt": { + "options": { + "files": "." + }, + "configurations": { + "fix": {} + } + } + } +} diff --git a/packages/twenty-website-new/public/images/home/hero/background.png b/packages/twenty-website-new/public/images/home/hero/background.png new file mode 100644 index 0000000000..2d5203cdee Binary files /dev/null and b/packages/twenty-website-new/public/images/home/hero/background.png differ diff --git a/packages/twenty-website-new/public/images/home/hero/foreground.png b/packages/twenty-website-new/public/images/home/hero/foreground.png new file mode 100644 index 0000000000..3b9d55d84a Binary files /dev/null and b/packages/twenty-website-new/public/images/home/hero/foreground.png differ diff --git a/packages/twenty-website-new/public/images/home/problem/problem-visual.svg b/packages/twenty-website-new/public/images/home/problem/problem-visual.svg new file mode 100644 index 0000000000..608ba5ce1a --- /dev/null +++ b/packages/twenty-website-new/public/images/home/problem/problem-visual.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/twenty-website-new/public/images/home/three-cards-feature/familiar-interface.png b/packages/twenty-website-new/public/images/home/three-cards-feature/familiar-interface.png new file mode 100644 index 0000000000..00fb1b0231 Binary files /dev/null and b/packages/twenty-website-new/public/images/home/three-cards-feature/familiar-interface.png differ diff --git a/packages/twenty-website-new/public/images/home/three-cards-feature/fast-path.png b/packages/twenty-website-new/public/images/home/three-cards-feature/fast-path.png new file mode 100644 index 0000000000..00fb1b0231 Binary files /dev/null and b/packages/twenty-website-new/public/images/home/three-cards-feature/fast-path.png differ diff --git a/packages/twenty-website-new/public/images/home/three-cards-feature/live-data.png b/packages/twenty-website-new/public/images/home/three-cards-feature/live-data.png new file mode 100644 index 0000000000..00fb1b0231 Binary files /dev/null and b/packages/twenty-website-new/public/images/home/three-cards-feature/live-data.png differ diff --git a/packages/twenty-website-new/public/images/why-twenty/hero/background.png b/packages/twenty-website-new/public/images/why-twenty/hero/background.png new file mode 100644 index 0000000000..b0a7e6a575 Binary files /dev/null and b/packages/twenty-website-new/public/images/why-twenty/hero/background.png differ diff --git a/packages/twenty-website-new/src/app/(home)/constants/hero.ts b/packages/twenty-website-new/src/app/(home)/constants/hero.ts new file mode 100644 index 0000000000..95ed63264a --- /dev/null +++ b/packages/twenty-website-new/src/app/(home)/constants/hero.ts @@ -0,0 +1,12 @@ +import { HeroDataType } from '@/sections/Hero/types'; + +export const HERO_DATA: HeroDataType = { + heading: [ + { text: 'Build', fontFamily: 'sans' }, + { text: ' your Enterprise CRM ', fontFamily: 'serif' }, + { text: 'at AI Speed', fontFamily: 'sans' }, + ], + body: { + text: 'Twenty gives technical teams the building blocks for a custom CRM that meets complex business needs and quickly adapts as the business evolves.', + }, +}; diff --git a/packages/twenty-website-new/src/app/(home)/constants/problem.ts b/packages/twenty-website-new/src/app/(home)/constants/problem.ts new file mode 100644 index 0000000000..77d1d7d01b --- /dev/null +++ b/packages/twenty-website-new/src/app/(home)/constants/problem.ts @@ -0,0 +1,23 @@ +import { ProblemDataType } from '@/sections/Problem/types'; + +export const PROBLEM_DATA: ProblemDataType = { + eyebrow: { heading: { text: 'The Problem:', fontFamily: 'sans' } }, + heading: [ + { text: 'A custom CRM gives your org an edge, ', fontFamily: 'serif' }, + { text: 'but building one comes with tradeoffs', fontFamily: 'sans' }, + ], + points: [ + { + heading: { text: 'The Giant Monolith', fontFamily: 'sans' }, + body: { + text: 'Proprietary languages, slow deployment cycles, and "black box" logic.', + }, + }, + { + heading: { text: 'The In-house Burden', fontFamily: 'sans' }, + body: { + text: "It's fragile. V1 ships quickly, but maintaining and making changes is a long term burden.", + }, + }, + ], +}; diff --git a/packages/twenty-website-new/src/app/(home)/constants/three-cards-feature.ts b/packages/twenty-website-new/src/app/(home)/constants/three-cards-feature.ts new file mode 100644 index 0000000000..7ce6586fd4 --- /dev/null +++ b/packages/twenty-website-new/src/app/(home)/constants/three-cards-feature.ts @@ -0,0 +1,34 @@ +import { ThreeCardsFeatureCardsDataType } from '@/sections/ThreeCards/types'; + +export const THREE_CARDS_FEATURE_DATA: ThreeCardsFeatureCardsDataType = { + eyebrow: { + heading: { + text: 'Skip the clunky UX that always comes with custom.', + fontFamily: 'sans', + }, + }, + heading: [ + { text: 'Make your GTM team happy with ', fontFamily: 'serif' }, + { text: "a CRM they'll love", fontFamily: 'sans' }, + ], + featureCards: [ + { + heading: { text: 'Familiar, modern interface', fontFamily: 'sans' }, + body: { + text: "Twenty makes it simple. It's clean, intuitive, and built to feel like Notion.", + }, + }, + { + heading: { text: 'Fast path to action', fontFamily: 'sans' }, + body: { + text: 'Smart patterns, shortcuts, and layouts make everyday tasks faster and easier to execute.', + }, + }, + { + heading: { text: 'Live data and AI Built', fontFamily: 'sans' }, + body: { + text: 'Everything updates in real time, with AI chat always ready to help you move faster.', + }, + }, + ], +}; diff --git a/packages/twenty-website-new/src/app/(home)/constants/three-cards-illustration.ts b/packages/twenty-website-new/src/app/(home)/constants/three-cards-illustration.ts new file mode 100644 index 0000000000..8de854a9ed --- /dev/null +++ b/packages/twenty-website-new/src/app/(home)/constants/three-cards-illustration.ts @@ -0,0 +1,49 @@ +import { ThreeCardsIllustrationDataType } from '@/sections/ThreeCards/types'; + +export const THREE_CARDS_ILLUSTRATION_DATA: ThreeCardsIllustrationDataType = { + eyebrow: { + heading: { text: 'Stop settling for trade-offs.', fontFamily: 'sans' }, + }, + heading: [ + { text: 'Assemble, iterate and adapt a robust CRM,', fontFamily: 'serif' }, + { text: " that's quick to flex", fontFamily: 'sans' }, + ], + body: { + text: 'Compose your CRM and interal apps with a single exesibility toolkit.', + }, + illustrationCards: [ + { + heading: { text: 'Production grade quality', fontFamily: 'sans' }, + body: { + text: "What stood out with Twenty wasn't just flexibility it was the quality. The system feels stable, polished, and ready for real teams with real stakes.", + }, + benefits: undefined, + attribution: { + role: { text: 'Head of Engineering' }, + company: { text: 'Mid-Market Fintech' }, + }, + }, + { + heading: { text: 'AI for rapid iterations', fontFamily: 'sans' }, + body: { + text: 'Twenty removes friction from iteration and lets us adapt our CRM as the business changes.', + }, + benefits: undefined, + attribution: { + role: { text: 'Head of Engineering' }, + company: { text: 'Mid-Market Fintech' }, + }, + }, + { + heading: { text: 'Control without drag', fontFamily: 'sans' }, + body: { + text: "It's a modern, open-source alternative to Salesforce/Hubspot that lets you manage your customer relationships in a secure + privacy-first way.", + }, + benefits: undefined, + attribution: { + role: { text: 'Head of Engineering' }, + company: { text: 'Mid-Market Fintech' }, + }, + }, + ], +}; diff --git a/packages/twenty-website-new/src/app/(home)/constants/trusted-by.ts b/packages/twenty-website-new/src/app/(home)/constants/trusted-by.ts new file mode 100644 index 0000000000..bec3823871 --- /dev/null +++ b/packages/twenty-website-new/src/app/(home)/constants/trusted-by.ts @@ -0,0 +1,7 @@ +import { TrustedByDataType } from '@/sections/TrustedBy/types'; + +export const TRUSTED_BY_DATA: TrustedByDataType = { + separator: { text: 'trusted by' }, + clientCountLabel: { text: '+ 10 000 other clients' }, + logos: [{ icon: 'flexport' }, { icon: 'zapier' }], +}; diff --git a/packages/twenty-website-new/src/app/(home)/page.tsx b/packages/twenty-website-new/src/app/(home)/page.tsx new file mode 100644 index 0000000000..e598332292 --- /dev/null +++ b/packages/twenty-website-new/src/app/(home)/page.tsx @@ -0,0 +1,88 @@ +import { HERO_DATA } from '@/app/(home)/constants/hero'; +import { PROBLEM_DATA } from '@/app/(home)/constants/problem'; +import { THREE_CARDS_FEATURE_DATA } from '@/app/(home)/constants/three-cards-feature'; +import { THREE_CARDS_ILLUSTRATION_DATA } from '@/app/(home)/constants/three-cards-illustration'; +import { TRUSTED_BY_DATA } from '@/app/(home)/constants/trusted-by'; +import { Body, Eyebrow, Heading, LinkButton } from '@/design-system/components'; +import { Pages } from '@/enums/pages'; +import { Hero } from '@/sections/Hero/components'; +import { Problem } from '@/sections/Problem/components'; +import { ThreeCards } from '@/sections/ThreeCards/components'; +import { TrustedBy } from '@/sections/TrustedBy/components'; +import { theme } from '@/theme'; + +export default function HomePage() { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/packages/twenty-website-new/src/app/favicon.ico b/packages/twenty-website-new/src/app/favicon.ico new file mode 100644 index 0000000000..d611985bfd Binary files /dev/null and b/packages/twenty-website-new/src/app/favicon.ico differ diff --git a/packages/twenty-website-new/src/app/layout.tsx b/packages/twenty-website-new/src/app/layout.tsx new file mode 100644 index 0000000000..2ef8d8527b --- /dev/null +++ b/packages/twenty-website-new/src/app/layout.tsx @@ -0,0 +1,89 @@ +import { Footer } from '@/sections/Footer/components'; +import { Menu } from '@/sections/Menu/components'; +import { theme } from '@/theme'; +import { cssVariables } from '@/theme/css-variables'; +import { css } from '@linaria/core'; +import { styled } from '@linaria/react'; +import type { Metadata } from 'next'; +import { Aleo, Azeret_Mono, Host_Grotesk } from 'next/font/google'; + +const hostGrotesk = Host_Grotesk({ + subsets: ['latin'], + weight: ['300', '400', '500', '600'], + variable: '--font-sans', + display: 'swap', +}); + +const aleo = Aleo({ + subsets: ['latin'], + weight: ['300'], + variable: '--font-serif', + display: 'swap', +}); + +const azeretMono = Azeret_Mono({ + subsets: ['latin'], + weight: ['300', '500'], + variable: '--font-mono', + display: 'swap', +}); + +css` + :global(*), + :global(*::before), + :global(*::after) { + box-sizing: border-box; + margin: 0; + padding: 0; + } + + :global(html) { + background-color: ${theme.colors.primary.background[100]}; + } + + :global(body) { + color: ${theme.colors.primary.text[100]}; + display: flex; + font-family: ${theme.font.family.sans}; + flex-direction: column; + min-height: 100vh; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } +`; + +const StyledMain = styled.main` + flex-grow: 1; +`; + +export const metadata: Metadata = { + title: 'Twenty — Open Source CRM', + description: 'Modular, scalable open source CRM for modern teams.', +}; + +export default function RootLayout({ + children, +}: Readonly<{ children: React.ReactNode }>) { + return ( + + + + + + + + + {children} + + + + + + + + + + ); +} diff --git a/packages/twenty-website-new/src/app/partner/constants/hero.ts b/packages/twenty-website-new/src/app/partner/constants/hero.ts new file mode 100644 index 0000000000..7983f82b32 --- /dev/null +++ b/packages/twenty-website-new/src/app/partner/constants/hero.ts @@ -0,0 +1,11 @@ +import { HeroDataType } from '@/sections/Hero/types'; + +export const HERO_DATA: HeroDataType = { + heading: [ + { text: 'Become a ', fontFamily: 'serif' }, + { text: 'Twenty Partner', fontFamily: 'sans' }, + ], + 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.", + }, +}; diff --git a/packages/twenty-website-new/src/app/partner/constants/three-cards-illustration.ts b/packages/twenty-website-new/src/app/partner/constants/three-cards-illustration.ts new file mode 100644 index 0000000000..857319c75b --- /dev/null +++ b/packages/twenty-website-new/src/app/partner/constants/three-cards-illustration.ts @@ -0,0 +1,53 @@ +import { ThreeCardsIllustrationDataType } from '@/sections/ThreeCards/types'; + +export const THREE_CARDS_ILLUSTRATION_DATA: ThreeCardsIllustrationDataType = { + eyebrow: { + heading: { + text: 'Which partner program is right for you?', + fontFamily: 'sans', + }, + }, + heading: [ + { text: 'Find the program that fits your business ', fontFamily: 'serif' }, + { text: 'and unlock new opportunities with Twenty', fontFamily: 'sans' }, + ], + body: { text: '' }, + illustrationCards: [ + { + heading: { text: 'Technology Partners', fontFamily: 'sans' }, + body: { + text: 'Build integrations that connect Twenty with the tools your customers already use. Help us expand the Twenty ecosystem.', + }, + benefits: [ + { text: 'Co-marketing opportunities' }, + { text: 'Listing on Twenty integrations page' }, + { text: 'Soon: earn revenue' }, + ], + attribution: undefined, + }, + { + 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.", + }, + benefits: [ + { text: 'Revenue share for referred customers' }, + { text: 'Exclusive content collaboration opportunities' }, + { text: 'Marketing assets & brand resources' }, + ], + attribution: undefined, + }, + { + heading: { text: 'Solutions Partners', fontFamily: 'sans' }, + body: { + text: 'Help customers implement, customize, and succeed with Twenty. Combine sales and services to grow your business.', + }, + benefits: [ + { text: 'Resale discounts & revenue share' }, + { text: 'Marketplace listing' }, + { text: 'Dedicated partner support' }, + ], + attribution: undefined, + }, + ], +}; diff --git a/packages/twenty-website-new/src/app/partner/page.tsx b/packages/twenty-website-new/src/app/partner/page.tsx new file mode 100644 index 0000000000..1fe36e8668 --- /dev/null +++ b/packages/twenty-website-new/src/app/partner/page.tsx @@ -0,0 +1,68 @@ +import { TRUSTED_BY_DATA } from '@/app/(home)/constants/trusted-by'; +import { HERO_DATA } from '@/app/partner/constants/hero'; +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'; +import { Hero } from '@/sections/Hero/components'; +import { ThreeCards } from '@/sections/ThreeCards/components'; +import { TrustedBy } from '@/sections/TrustedBy/components'; +import { theme } from '@/theme'; + +export default function PartnerPage() { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/packages/twenty-website-new/src/app/pricing/constants/hero.ts b/packages/twenty-website-new/src/app/pricing/constants/hero.ts new file mode 100644 index 0000000000..7a8abeb9f3 --- /dev/null +++ b/packages/twenty-website-new/src/app/pricing/constants/hero.ts @@ -0,0 +1,11 @@ +import { HeroDataType } from '@/sections/Hero/types'; + +export const HERO_DATA: HeroDataType = { + heading: [ + { text: 'A pricing that scales ', fontFamily: 'serif' }, + { text: 'with your need', fontFamily: 'sans' }, + ], + 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.", + }, +}; diff --git a/packages/twenty-website-new/src/app/pricing/page.tsx b/packages/twenty-website-new/src/app/pricing/page.tsx new file mode 100644 index 0000000000..3c119728de --- /dev/null +++ b/packages/twenty-website-new/src/app/pricing/page.tsx @@ -0,0 +1,15 @@ +import { HERO_DATA } from '@/app/pricing/constants/hero'; +import { Pages } from '@/enums/pages'; +import { Hero } from '@/sections/Hero/components'; +import { theme } from '@/theme'; + +export default function PricingPage() { + return ( + <> + + + + + + ); +} diff --git a/packages/twenty-website-new/src/app/product/constants/hero.ts b/packages/twenty-website-new/src/app/product/constants/hero.ts new file mode 100644 index 0000000000..9e0eeec7c7 --- /dev/null +++ b/packages/twenty-website-new/src/app/product/constants/hero.ts @@ -0,0 +1,11 @@ +import { HeroDataType } from '@/sections/Hero/types'; + +export const HERO_DATA: HeroDataType = { + heading: [ + { text: 'The CRM that moves ', fontFamily: 'serif' }, + { text: 'as fast as you do', fontFamily: 'sans' }, + ], + body: { + text: 'Modern interface. AI assistance. All the features you need, ready from day one.', + }, +}; diff --git a/packages/twenty-website-new/src/app/product/constants/three-cards.ts b/packages/twenty-website-new/src/app/product/constants/three-cards.ts new file mode 100644 index 0000000000..57902ca01c --- /dev/null +++ b/packages/twenty-website-new/src/app/product/constants/three-cards.ts @@ -0,0 +1,38 @@ +import { ThreeCardsIllustrationDataType } from '@/sections/ThreeCards/types'; + +export const THREE_CARDS_ILLUSTRATION_DATA: ThreeCardsIllustrationDataType = { + eyebrow: { + heading: { text: 'Stop settling for trade-offs.', fontFamily: 'sans' }, + }, + heading: [ + { text: 'A modern CRM with ', fontFamily: 'serif' }, + { text: 'an intuitive interface', fontFamily: 'sans' }, + ], + body: { text: '' }, + illustrationCards: [ + { + heading: { text: 'Built for speed', fontFamily: 'sans' }, + body: { + text: 'Fly through your workspace with shortcuts and short load times.', + }, + benefits: undefined, + attribution: undefined, + }, + { + heading: { text: 'Real-time data', fontFamily: 'sans' }, + body: { + text: 'See updates as they happen. Work with your team and agents seamlessly.', + }, + benefits: undefined, + attribution: undefined, + }, + { + heading: { text: 'Stay in Flow', fontFamily: 'sans' }, + body: { + text: 'AI chat, settings, and records in a side panels for fast, single-screen access.', + }, + benefits: undefined, + attribution: undefined, + }, + ], +}; diff --git a/packages/twenty-website-new/src/app/product/page.tsx b/packages/twenty-website-new/src/app/product/page.tsx new file mode 100644 index 0000000000..681c9be58c --- /dev/null +++ b/packages/twenty-website-new/src/app/product/page.tsx @@ -0,0 +1,60 @@ +import { TRUSTED_BY_DATA } from '@/app/(home)/constants/trusted-by'; +import { HERO_DATA } from '@/app/product/constants/hero'; +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 { Hero } from '@/sections/Hero/components'; +import { ThreeCards } from '@/sections/ThreeCards/components'; +import { TrustedBy } from '@/sections/TrustedBy/components'; +import { theme } from '@/theme'; + +export default function ProductPage() { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/packages/twenty-website-new/src/app/why-twenty/constants/hero.ts b/packages/twenty-website-new/src/app/why-twenty/constants/hero.ts new file mode 100644 index 0000000000..7db806d429 --- /dev/null +++ b/packages/twenty-website-new/src/app/why-twenty/constants/hero.ts @@ -0,0 +1,11 @@ +import { HeroDataType } from '@/sections/Hero/types'; + +export const HERO_DATA: HeroDataType = { + heading: [ + { text: 'The future of CRM is built, ', fontFamily: 'serif' }, + { text: 'not bought.', fontFamily: 'sans' }, + ], + body: { + text: 'Most packaged software makes companies more similar. CRM is no exception. AI is turning software into a commodity. That does not make your go-to-market a commodity. It makes it the battleground.', + }, +}; diff --git a/packages/twenty-website-new/src/app/why-twenty/page.tsx b/packages/twenty-website-new/src/app/why-twenty/page.tsx new file mode 100644 index 0000000000..69a3b2550d --- /dev/null +++ b/packages/twenty-website-new/src/app/why-twenty/page.tsx @@ -0,0 +1,14 @@ +import { HERO_DATA } from '@/app/why-twenty/constants/hero'; +import { Pages } from '@/enums/pages'; +import { Hero } from '@/sections/Hero/components'; +import { theme } from '@/theme'; + +export default function WhyTwentyPage() { + return ( + + + + + + ); +} diff --git a/packages/twenty-website-new/src/design-system/components/Body/Body.tsx b/packages/twenty-website-new/src/design-system/components/Body/Body.tsx new file mode 100644 index 0000000000..d7183afa31 --- /dev/null +++ b/packages/twenty-website-new/src/design-system/components/Body/Body.tsx @@ -0,0 +1,99 @@ +import { theme } from '@/theme'; +import { css } from '@linaria/core'; +import { BodyType } from './types/Body'; + +const bodyClassName = css` + margin: 0; + + &[data-family='sans'] { + font-family: ${theme.font.family.sans}; + } + + &[data-family='serif'] { + font-family: ${theme.font.family.serif}; + } + + &[data-family='mono'] { + font-family: ${theme.font.family.mono}; + } + + &[data-weight='light'] { + font-weight: ${theme.font.weight.light}; + } + + &[data-weight='regular'] { + font-weight: ${theme.font.weight.regular}; + } + + &[data-weight='medium'] { + font-weight: ${theme.font.weight.medium}; + } + + &[data-size='md'] { + font-size: ${theme.font.size(4)}; + line-height: ${theme.lineHeight(5.5)}; + } + + &[data-size='sm'] { + font-size: ${theme.font.size(4)}; + line-height: ${theme.lineHeight(5.5)}; + } + + &[data-size='xs'] { + font-size: ${theme.font.size(3)}; + line-height: ${theme.lineHeight(3.5)}; + } + + @media (min-width: ${theme.breakpoints.md}px) { + &[data-size='md'] { + font-size: ${theme.font.size(4.5)}; + line-height: ${theme.lineHeight(6)}; + } + + &[data-size='sm'] { + font-size: ${theme.font.size(4)}; + line-height: ${theme.lineHeight(5.5)}; + } + + &[data-size='xs'] { + font-size: ${theme.font.size(3)}; + line-height: ${theme.lineHeight(3.5)}; + } + } +`; + +export type BodyAs = 'p' | 'span' | 'div'; +export type BodyFamily = 'sans' | 'serif' | 'mono'; +export type BodyWeight = 'light' | 'regular' | 'medium'; +export type BodySize = 'md' | 'sm' | 'xs'; + +export type BodyProps = { + as?: BodyAs; + body: BodyType; + family?: BodyFamily; + weight?: BodyWeight; + size?: BodySize; + className?: string; +}; + +export function Body({ + as: Tag = 'p', + body, + family = 'sans', + weight = 'regular', + size = 'md', + className, +}: BodyProps) { + const rootClassName = [bodyClassName, className].filter(Boolean).join(' '); + + return ( + + {body.text} + + ); +} diff --git a/packages/twenty-website-new/src/design-system/components/Body/types/Body.ts b/packages/twenty-website-new/src/design-system/components/Body/types/Body.ts new file mode 100644 index 0000000000..183f9de787 --- /dev/null +++ b/packages/twenty-website-new/src/design-system/components/Body/types/Body.ts @@ -0,0 +1,3 @@ +export type BodyType = { + text: string; +} diff --git a/packages/twenty-website-new/src/design-system/components/Button/BaseButton.tsx b/packages/twenty-website-new/src/design-system/components/Button/BaseButton.tsx new file mode 100644 index 0000000000..d21b1627a7 --- /dev/null +++ b/packages/twenty-website-new/src/design-system/components/Button/BaseButton.tsx @@ -0,0 +1,84 @@ +import { theme } from '@/theme'; +import { styled } from '@linaria/react'; +import { ButtonShape } from './ButtonShape'; + +export const buttonBaseStyles = ` + align-items: center; + background: transparent; + border: none; + border-radius: ${theme.radius(2)}; + cursor: pointer; + display: inline-flex; + font-family: ${theme.font.family.mono}; + font-size: ${theme.font.size(3)}; + font-weight: ${theme.font.weight.medium}; + height: ${theme.spacing(10)}; + justify-content: center; + letter-spacing: 0; + padding: 0 ${theme.spacing(5)}; + position: relative; + text-decoration: none; + text-transform: uppercase; + + &:focus-visible { + outline: 1px solid ${theme.colors.highlight[100]}; + outline-offset: 1px; + } +`; + +const Label = styled.span` + position: relative; + z-index: 1; + + &[data-color='primary'] { + color: ${theme.colors.primary.text[100]}; + } + + &[data-color='secondary'] { + color: ${theme.colors.secondary.text[100]}; + } +`; + +export type BaseButtonProps = { + color: 'primary' | 'secondary'; + label: string; + variant: 'contained' | 'outlined'; +}; + +export function BaseButton({ color, label, variant }: BaseButtonProps) { + let fillColor: string; + let strokeColor: string; + let labelColor: 'primary' | 'secondary'; + + switch (`${variant}.${color}`) { + case 'contained.primary': + fillColor = theme.colors.primary.background[100]; + strokeColor = 'none'; + labelColor = 'primary'; + break; + case 'contained.secondary': + fillColor = theme.colors.secondary.background[100]; + strokeColor = 'none'; + labelColor = 'secondary'; + break; + case 'outlined.primary': + fillColor = 'none'; + strokeColor = theme.colors.primary.background[100]; + labelColor = 'secondary'; + break; + case 'outlined.secondary': + fillColor = 'none'; + strokeColor = theme.colors.secondary.background[100]; + labelColor = 'primary'; + break; + default: + throw new Error(`Unhandled button appearance: ${variant} ${color}`); + } + + return ( + <> + + + + ); +} diff --git a/packages/twenty-website-new/src/design-system/components/Button/ButtonShape.tsx b/packages/twenty-website-new/src/design-system/components/Button/ButtonShape.tsx new file mode 100644 index 0000000000..1c5617f3c6 --- /dev/null +++ b/packages/twenty-website-new/src/design-system/components/Button/ButtonShape.tsx @@ -0,0 +1,74 @@ +import { styled } from "@linaria/react"; + +interface ButtonShapeProps { + fillColor: string; + strokeColor: string; +} + +const LEFT_FILL = "M4 0 A4 4 0 0 0 0 4 V36 A4 4 0 0 0 4 40 Z"; +const LEFT_OUTLINE = "M4 0.5 A3.5 3.5 0 0 0 0.5 4 V36 A3.5 3.5 0 0 0 4 39.5"; + +const RIGHT_FILL = "M0 0 h11 a4 4 0 0 1 4 4 v20.523 a6 6 0 0 1 -1.544 4.019 l-8.548 9.477 A6 6 0 0 1 0.453 40 H0 Z"; +const RIGHT_OUTLINE = "M0 0.5 h11 a3.5 3.5 0 0 1 3.5 3.5 v20.523 a5.5 5.5 0 0 1 -1.416 3.684 l-8.547 9.477 a5.5 5.5 0 0 1 -4.084 1.816 H0"; + +const ShapeContainer = styled.div` + display: flex; + height: 40px; + inset: 0; + pointer-events: none; + position: absolute; + width: 100%; + z-index: 0; +`; + +const LeftCap = styled.svg` + display: block; + flex-shrink: 0; +`; + +const MiddleSegment = styled.svg` + display: block; + flex-grow: 1; + margin: 0 -1px; + min-width: 0; +`; + +const RightCap = styled.svg` + display: block; + flex-shrink: 0; +`; + +export function ButtonShape({ fillColor, strokeColor }: ButtonShapeProps) { + const isOutline = fillColor === "none"; + + return ( + + + {isOutline ? ( + + ) : ( + + )} + + + + {isOutline ? ( + <> + + + + ) : ( + + )} + + + + {isOutline ? ( + + ) : ( + + )} + + + ); +} diff --git a/packages/twenty-website-new/src/design-system/components/Button/LinkButton.tsx b/packages/twenty-website-new/src/design-system/components/Button/LinkButton.tsx new file mode 100644 index 0000000000..9abccf4320 --- /dev/null +++ b/packages/twenty-website-new/src/design-system/components/Button/LinkButton.tsx @@ -0,0 +1,49 @@ +import type { LinkButtonType } from '@/design-system/components/Button/types/LinkButtonType'; +import { styled } from '@linaria/react'; +import Link from 'next/link'; +import { + BaseButton, + type BaseButtonProps, + buttonBaseStyles, +} from './BaseButton'; + +const StyledButtonAnchor = styled.a` + ${buttonBaseStyles} +`; + +const StyledButtonLink = styled(Link)` + ${buttonBaseStyles} +`; + +export type LinkButtonPresentation = 'anchor' | 'link'; + +export type LinkButtonProps = Omit & + LinkButtonType & { type: LinkButtonPresentation }; + +export function LinkButton({ + color, + href, + label, + type, + variant, +}: LinkButtonProps) { + const inner = ; + + if (type === 'anchor') { + return ( + + {inner} + + ); + } + + return ( + + {inner} + + ); +} diff --git a/packages/twenty-website-new/src/design-system/components/Button/SubmitButton.tsx b/packages/twenty-website-new/src/design-system/components/Button/SubmitButton.tsx new file mode 100644 index 0000000000..94bc6cda0b --- /dev/null +++ b/packages/twenty-website-new/src/design-system/components/Button/SubmitButton.tsx @@ -0,0 +1,27 @@ +import { styled } from '@linaria/react'; +import { + BaseButton, + type BaseButtonProps, + buttonBaseStyles, +} from './BaseButton'; +import type { SubmitButtonType } from './types/SubmitButtonType'; + +const StyledSubmitButton = styled.button` + ${buttonBaseStyles} +`; + +export type SubmitButtonProps = Omit & + SubmitButtonType & { onClick?: () => void }; + +export function SubmitButton({ + color, + label, + onClick, + variant, +}: SubmitButtonProps) { + return ( + + + + ); +} diff --git a/packages/twenty-website-new/src/design-system/components/Button/types/LinkButtonType.ts b/packages/twenty-website-new/src/design-system/components/Button/types/LinkButtonType.ts new file mode 100644 index 0000000000..e2d671cf60 --- /dev/null +++ b/packages/twenty-website-new/src/design-system/components/Button/types/LinkButtonType.ts @@ -0,0 +1 @@ +export type LinkButtonType = { href: string; label: string }; diff --git a/packages/twenty-website-new/src/design-system/components/Button/types/SubmitButtonType.ts b/packages/twenty-website-new/src/design-system/components/Button/types/SubmitButtonType.ts new file mode 100644 index 0000000000..4e98c8ecc2 --- /dev/null +++ b/packages/twenty-website-new/src/design-system/components/Button/types/SubmitButtonType.ts @@ -0,0 +1 @@ +export type SubmitButtonType = { label: string }; diff --git a/packages/twenty-website-new/src/design-system/components/Container/Container.tsx b/packages/twenty-website-new/src/design-system/components/Container/Container.tsx new file mode 100644 index 0000000000..28b17fcbf9 --- /dev/null +++ b/packages/twenty-website-new/src/design-system/components/Container/Container.tsx @@ -0,0 +1,15 @@ +import { theme } from '@/theme'; +import { styled } from '@linaria/react'; +import type { ReactNode } from 'react'; + +const ContainerRoot = styled.div` + margin: 0 auto; + max-width: ${theme.breakpoints.maxContent}px; + width: 100%; +`; + +export type ContainerProps = { children: ReactNode; className?: string }; + +export function Container({ children, className }: ContainerProps) { + return {children}; +} diff --git a/packages/twenty-website-new/src/design-system/components/Eyebrow/Eyebrow.tsx b/packages/twenty-website-new/src/design-system/components/Eyebrow/Eyebrow.tsx new file mode 100644 index 0000000000..172d995d91 --- /dev/null +++ b/packages/twenty-website-new/src/design-system/components/Eyebrow/Eyebrow.tsx @@ -0,0 +1,53 @@ +import { Heading } from '@/design-system/components/Heading/Heading'; +import { HeadingType } from '@/design-system/components/Heading/types/Heading'; +import { RectangleFillIcon } from '@/icons'; +import { theme } from '@/theme'; +import { css } from '@linaria/core'; +import { styled } from '@linaria/react'; + +const EyebrowRow = styled.div` + align-items: start; + display: grid; + gap: ${theme.spacing(2)}; + grid-template-columns: auto 1fr; + text-align: start; +`; + +const IconWrapper = styled.div` + display: flex; + align-items: center; + height: ${theme.lineHeight(6)}; +`; + +const eyebrowColorPrimary = css` + color: ${theme.colors.primary.text[60]}; +`; + +const eyebrowColorSecondary = css` + color: ${theme.colors.secondary.text[60]}; +`; + +type EyebrowProps = { + heading: HeadingType; + colorScheme: 'primary' | 'secondary'; +}; + +export function Eyebrow({ heading, colorScheme }: EyebrowProps) { + const colorClassName = + colorScheme === 'primary' ? eyebrowColorPrimary : eyebrowColorSecondary; + + return ( + + + + + + + ); +} diff --git a/packages/twenty-website-new/src/design-system/components/Eyebrow/types/Eyebrow.ts b/packages/twenty-website-new/src/design-system/components/Eyebrow/types/Eyebrow.ts new file mode 100644 index 0000000000..b13030e201 --- /dev/null +++ b/packages/twenty-website-new/src/design-system/components/Eyebrow/types/Eyebrow.ts @@ -0,0 +1,3 @@ +import { HeadingType } from '@/design-system/components/Heading/types/Heading'; + +export type EyebrowType = { heading: HeadingType }; diff --git a/packages/twenty-website-new/src/design-system/components/Heading/Heading.tsx b/packages/twenty-website-new/src/design-system/components/Heading/Heading.tsx new file mode 100644 index 0000000000..8e8a97bf46 --- /dev/null +++ b/packages/twenty-website-new/src/design-system/components/Heading/Heading.tsx @@ -0,0 +1,130 @@ +import { theme } from '@/theme'; +import { css } from '@linaria/core'; +import { styled } from '@linaria/react'; +import { HeadingType } from './types/Heading'; + +const headingRootClassName = css` + margin: 0; + + &[data-weight='light'] { + font-weight: ${theme.font.weight.light}; + } + + &[data-weight='regular'] { + font-weight: ${theme.font.weight.regular}; + } + + &[data-weight='medium'] { + font-weight: ${theme.font.weight.medium}; + } + + &[data-size='xl'] { + font-size: ${theme.font.size(15)}; + line-height: ${theme.lineHeight(16.5)}; + } + + &[data-size='lg'] { + font-size: ${theme.font.size(10)}; + line-height: ${theme.lineHeight(11.5)}; + } + + &[data-size='md'] { + font-size: ${theme.font.size(10)}; + line-height: ${theme.lineHeight(11.5)}; + } + + &[data-size='sm'] { + font-size: ${theme.font.size(8)}; + line-height: ${theme.lineHeight(10)}; + } + + &[data-size='xs'] { + font-size: ${theme.font.size(4.5)}; + line-height: ${theme.lineHeight(6)}; + } + + @media (min-width: ${theme.breakpoints.md}px) { + &[data-size='xl'] { + font-size: ${theme.font.size(20)}; + line-height: ${theme.lineHeight(21.5)}; + } + + &[data-size='lg'] { + font-size: ${theme.font.size(15)}; + line-height: ${theme.lineHeight(16.5)}; + } + + &[data-size='md'] { + font-size: ${theme.font.size(12)}; + line-height: ${theme.lineHeight(14)}; + } + + &[data-size='sm'] { + font-size: ${theme.font.size(8)}; + line-height: ${theme.lineHeight(10)}; + } + + &[data-size='xs'] { + font-size: ${theme.font.size(5.5)}; + line-height: ${theme.lineHeight(7)}; + } + } +`; + +const StyledSpan = styled.span` + &[data-family='sans'] { + font-family: ${theme.font.family.sans}; + letter-spacing: -0.02em; + } + + &[data-family='serif'] { + font-family: ${theme.font.family.serif}; + letter-spacing: 0; + } + + &[data-family='mono'] { + font-family: ${theme.font.family.mono}; + letter-spacing: -0.02em; + } +`; + +export type HeadingAs = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; +export type HeadingFamily = 'sans' | 'serif' | 'mono'; +export type HeadingWeight = 'light' | 'regular' | 'medium'; +export type HeadingSize = 'xl' | 'lg' | 'md' | 'sm' | 'xs'; + +export type HeadingProps = { + as?: HeadingAs; + segments: HeadingType | HeadingType[]; + weight?: HeadingWeight; + size?: HeadingSize; + className?: string; +}; + +export function Heading({ + as: Tag = 'h1', + segments, + weight = 'regular', + size = 'md', + className, +}: HeadingProps) { + const rootClassName = [headingRootClassName, className] + .filter(Boolean) + .join(' '); + + return ( + + {Array.isArray(segments) ? ( + segments.map((segment, index) => ( + + {segment.text} + + )) + ) : ( + + {segments.text} + + )} + + ); +} diff --git a/packages/twenty-website-new/src/design-system/components/Heading/types/Heading.ts b/packages/twenty-website-new/src/design-system/components/Heading/types/Heading.ts new file mode 100644 index 0000000000..ff36e8b77f --- /dev/null +++ b/packages/twenty-website-new/src/design-system/components/Heading/types/Heading.ts @@ -0,0 +1,4 @@ +export type HeadingType = { + fontFamily: 'sans' | 'serif' | 'mono'; + text: string; +} \ No newline at end of file diff --git a/packages/twenty-website-new/src/design-system/components/IconButton/IconButton.tsx b/packages/twenty-website-new/src/design-system/components/IconButton/IconButton.tsx new file mode 100644 index 0000000000..8d63e38869 --- /dev/null +++ b/packages/twenty-website-new/src/design-system/components/IconButton/IconButton.tsx @@ -0,0 +1,68 @@ +import type { ComponentType } from "react"; +import { theme } from "@/theme"; +import { styled } from "@linaria/react"; + +type IconComponent = + | ComponentType<{ size: number; fillColor: string }> + | ComponentType<{ size: number; strokeColor: string }>; + +interface IconButtonProps { + icon: IconComponent; + ariaLabel: string; + borderColor: string; + iconFillColor: string; + iconSize: number; + iconStrokeColor: string; + size: number; + onClick?: (e: React.MouseEvent) => void; + ariaExpanded?: boolean; +} + +const StyledButton = styled.button<{ size: number; borderColor: string }>` + align-items: center; + background: transparent; + border: 1px solid ${(p) => p.borderColor}; + border-radius: ${theme.radius(2)}; + cursor: pointer; + display: inline-flex; + height: ${(p) => p.size}px; + justify-content: center; + margin: 0; + padding: 0; + width: ${(p) => p.size}px; + + &:focus-visible { + outline: 1px solid ${theme.colors.highlight[100]}; + outline-offset: 1px; + } +`; + +export function IconButton({ + icon: Icon, + ariaLabel, + borderColor, + iconFillColor, + iconSize, + iconStrokeColor, + size, + onClick, + ariaExpanded, +}: IconButtonProps) { + return ( + + + ); +} diff --git a/packages/twenty-website-new/src/design-system/components/Illustration/types/Illustration.ts b/packages/twenty-website-new/src/design-system/components/Illustration/types/Illustration.ts new file mode 100644 index 0000000000..58ca3171a3 --- /dev/null +++ b/packages/twenty-website-new/src/design-system/components/Illustration/types/Illustration.ts @@ -0,0 +1,4 @@ +export type IllustrationType = { + src: string; + title: string; +}; diff --git a/packages/twenty-website-new/src/design-system/components/Image/Image.tsx b/packages/twenty-website-new/src/design-system/components/Image/Image.tsx new file mode 100644 index 0000000000..732836e86a --- /dev/null +++ b/packages/twenty-website-new/src/design-system/components/Image/Image.tsx @@ -0,0 +1,26 @@ +import NextImage from 'next/image'; +import { styled } from '@linaria/react'; +import type { ImageType } from './types/Image'; + +const ImageRoot = styled.div` + aspect-ratio: 16 / 9; + overflow: hidden; + position: relative; + width: 100%; +`; + +export type ImageComponentProps = ImageType & { className?: string }; + +export function Image({ alt, className, src }: ImageComponentProps) { + return ( + + + + ); +} diff --git a/packages/twenty-website-new/src/design-system/components/Image/types/Image.ts b/packages/twenty-website-new/src/design-system/components/Image/types/Image.ts new file mode 100644 index 0000000000..5403c61dbc --- /dev/null +++ b/packages/twenty-website-new/src/design-system/components/Image/types/Image.ts @@ -0,0 +1,4 @@ +export type ImageType = { + alt: string; + src: string; +}; diff --git a/packages/twenty-website-new/src/design-system/components/index.ts b/packages/twenty-website-new/src/design-system/components/index.ts new file mode 100644 index 0000000000..7a843ec2b3 --- /dev/null +++ b/packages/twenty-website-new/src/design-system/components/index.ts @@ -0,0 +1,8 @@ +export { Body } from './Body/Body'; +export { LinkButton } from './Button/LinkButton'; +export { SubmitButton } from './Button/SubmitButton'; +export { Container } from './Container/Container'; +export { Eyebrow } from './Eyebrow/Eyebrow'; +export { Heading } from './Heading/Heading'; +export { IconButton } from './IconButton/IconButton'; +export { Image } from './Image/Image'; diff --git a/packages/twenty-website-new/src/enums/pages.ts b/packages/twenty-website-new/src/enums/pages.ts new file mode 100644 index 0000000000..ba005c862e --- /dev/null +++ b/packages/twenty-website-new/src/enums/pages.ts @@ -0,0 +1,7 @@ +export enum Pages { + Home = "home", + Partner = "partner", + Pricing = "pricing", + Product = "product", + WhyTwenty = "whyTwenty", + } \ No newline at end of file diff --git a/packages/twenty-website-new/src/icons/clients/Flexport.tsx b/packages/twenty-website-new/src/icons/clients/Flexport.tsx new file mode 100644 index 0000000000..c122350b58 --- /dev/null +++ b/packages/twenty-website-new/src/icons/clients/Flexport.tsx @@ -0,0 +1,29 @@ +const FLEXPORT_VIEWBOX_WIDTH = 115; +const FLEXPORT_VIEWBOX_HEIGHT = 27; + +const FLEXPORT_PATH_1 = + "M13.462 16.78V.047h5.457v16.034c0 1.13.24 1.394 1.394 1.394h.457l.649 4.423a39 39 0 0 1-3.125.169c-2.981.024-4.832-1.034-4.832-5.289M55.241 6.562l4.23-.336.41 1.274c.84-.793 2.331-1.587 5.528-1.587 5.505 0 6.78 3.87 6.78 8.198 0 4.35-1.395 8.173-7.116 8.173-2.524 0-3.726-.577-4.4-1.202v5.505h-5.456zm11.418 7.765c0-1.851-.336-3.462-2.692-3.462-2.524 0-3.27 1.299-3.27 3.462s.554 3.365 3.27 3.365c2.308-.024 2.692-1.538 2.692-3.365m24.977-7.764 4.23-.337.553 1.659c.698-.938 2.597-1.731 4.856-1.827v5.24c-3.63.024-4.183 1.106-4.183 3.27v7.452h-5.456zm11.154-.505h2.812l.89-3.197 4.206-.914v4.11h3.438v5.241h-3.438v4.568c0 1.081.169 1.634 1.875 1.634h1.563v4.543c-.817.049-1.731.073-2.765.073-4.591 0-6.105-1.467-6.105-5.433v-5.36h-2.476zM0 6.25h2.98v-.312C2.98 1.97 4.472 0 9.592 0c1.347 0 1.587.024 2.26.072v4.423h-1.514c-1.563 0-1.9.385-1.9 1.394v.361h3.414v4.616H8.414V22.02H2.98V10.866H0zm33.294 11.058c-.914.529-1.37.649-3.173.649-2.14 0-2.957-.77-3.222-2.043H37.38c.192-5.553-.264-10.048-7.26-10.048s-8.485 3.87-8.485 8.149c0 4.399 1.466 8.293 8.485 8.293 2.765 0 4.496-.505 6.347-1.707zm-3.318-6.947c2.236 0 2.404 1.057 2.5 2.356h-5.6c.192-1.515.913-2.356 3.1-2.356"; +const FLEXPORT_PATH_2 = + "m48.198 22.043-2.669-3.822-2.668 3.822h-6.106l5.721-8.149-5.192-7.332h6.154l2.115 2.981 2.116-2.98h6.226l-5.289 7.307 5.722 8.173zm25.12-7.956c0-5.24 2.404-8.197 8.558-8.197 6.178 0 8.534 2.98 8.534 8.197 0 5.24-2.38 8.22-8.534 8.22-6.154.025-8.558-2.98-8.558-8.22m11.61.168c0-1.971-.504-3.414-3.052-3.414-2.572 0-3.053 1.443-3.053 3.414 0 1.947.505 3.414 3.053 3.414 2.572 0 3.053-1.443 3.053-3.414"; + +interface FlexportIconProps { + size: number; + fillColor: string; +} + +export function FlexportIcon({ size, fillColor }: FlexportIconProps) { + const height = size * (FLEXPORT_VIEWBOX_HEIGHT / FLEXPORT_VIEWBOX_WIDTH); + + return ( + + + + + ); +} diff --git a/packages/twenty-website-new/src/icons/clients/Zapier.tsx b/packages/twenty-website-new/src/icons/clients/Zapier.tsx new file mode 100644 index 0000000000..49511c64ea --- /dev/null +++ b/packages/twenty-website-new/src/icons/clients/Zapier.tsx @@ -0,0 +1,26 @@ +const ZAPIER_VIEWBOX_WIDTH = 112; +const ZAPIER_VIEWBOX_HEIGHT = 31; + +const ZAPIER_PATH = + "m26.105 20.644-.016-.05 9.885-9.05v-4.36H20.115v4.36h9.473l.015.043-9.788 9.057v4.36h16.318v-4.36zM45.87 6.77q-2.744 0-4.473.827a5.87 5.87 0 0 0-2.595 2.274 8.3 8.3 0 0 0-1.089 3.29l4.812.676q.261-1.538 1.044-2.18a3.2 3.2 0 0 1 2.068-.64q1.466 0 2.126.784c.436.522.658 1.245.658 2.198v.475h-4.627q-3.532 0-5.076 1.567t-1.54 4.136q0 2.631 1.54 3.916t3.875 1.277q2.895 0 4.454-1.992a7.4 7.4 0 0 0 1.288-2.744h.174l.664 4.36h4.36V13.952q0-3.423-1.804-5.303t-5.86-1.88m1.525 13.61c-.692.577-1.598.865-2.726.865q-1.395 0-1.938-.489a1.6 1.6 0 0 1-.545-1.24 1.63 1.63 0 0 1 .451-1.203 1.68 1.68 0 0 1 1.24-.452h4.552v.3a2.78 2.78 0 0 1-1.034 2.22M79.899 7.184h-5.151v17.823h5.15zm30.415 0q-2.183 0-3.441 1.541-.903 1.108-1.373 3.271h-.152l-.674-4.812h-4.399v17.82h5.152v-9.138q0-1.728.809-2.65t2.987-.922h1.955v-5.11zm-15.152.583q-1.804-1.074-4.55-1.07-4.324 0-6.768 2.461-2.445 2.463-2.444 6.789a10.8 10.8 0 0 0 1.11 5.037 8 8 0 0 0 3.16 3.329q2.046 1.182 4.869 1.185 2.44 0 4.078-.752a6.56 6.56 0 0 0 2.595-2.05 8.9 8.9 0 0 0 1.41-2.914l-4.438-1.242a4.1 4.1 0 0 1-1.045 1.806q-.752.753-2.52.753-2.1 0-3.133-1.204c-.499-.58-.812-1.408-.95-2.48H98.69q.076-.753.076-1.262v-1.11a9.9 9.9 0 0 0-.9-4.323 6.77 6.77 0 0 0-2.704-2.953m-4.7 3.177q3.135 0 3.554 3.084h-7.389a4.24 4.24 0 0 1 .828-1.883q1.016-1.203 3.008-1.2M77.38.002a2.9 2.9 0 0 0-2.108.81 2.8 2.8 0 0 0-.828 2.088 2.83 2.83 0 0 0 2.935 2.932 2.8 2.8 0 0 0 2.089-.828 2.87 2.87 0 0 0 .807-2.104A2.77 2.77 0 0 0 77.382.004zM66.737 6.696q-2.633 0-4.23 1.653-1.183 1.225-1.736 3.655h-.165l-.673-4.812h-4.4v22.935h5.151v-9.446h.189c.157.63.377 1.242.657 1.827a5.2 5.2 0 0 0 2.05 2.293 6 6 0 0 0 3.044.734q3.236 0 4.927-2.52 1.692-2.52 1.692-7.032 0-4.357-1.636-6.822-1.638-2.465-4.87-2.465m.522 12.914q-.9 1.306-2.82 1.306a3.32 3.32 0 0 1-2.762-1.261q-1-1.256-.998-3.44v-.338q0-2.217.998-3.395t2.762-1.164q1.953 0 2.838 1.24t.885 3.46q.006 2.295-.898 3.592zm-49.436 1.034H0v4.366h17.823z"; + +interface ZapierIconProps { + size: number; + fillColor: string; +} + +export function ZapierIcon({ size, fillColor }: ZapierIconProps) { + const height = size * (ZAPIER_VIEWBOX_HEIGHT / ZAPIER_VIEWBOX_WIDTH); + + return ( + + + + ); +} diff --git a/packages/twenty-website-new/src/icons/clients/index.ts b/packages/twenty-website-new/src/icons/clients/index.ts new file mode 100644 index 0000000000..59c58fb04c --- /dev/null +++ b/packages/twenty-website-new/src/icons/clients/index.ts @@ -0,0 +1,13 @@ +import type { ComponentType } from "react"; +import { FlexportIcon } from "./Flexport"; +import { ZapierIcon } from "./Zapier"; + +export { FlexportIcon } from "./Flexport"; +export { ZapierIcon } from "./Zapier"; + +export type ClientIconProps = { size: number; fillColor: string }; + +export const CLIENT_ICONS: Record> = { + flexport: FlexportIcon, + zapier: ZapierIcon, +}; diff --git a/packages/twenty-website-new/src/icons/functional/ArrowLeft.tsx b/packages/twenty-website-new/src/icons/functional/ArrowLeft.tsx new file mode 100644 index 0000000000..9f30d45cf0 --- /dev/null +++ b/packages/twenty-website-new/src/icons/functional/ArrowLeft.tsx @@ -0,0 +1,29 @@ +const ARROW_LEFT_PATH = + "M.5 5.75H14m-13.5 0L5.75 11M.5 5.75 5.75.5"; + +interface ArrowLeftIconProps { + size: number; + strokeColor: string; + fillColor?: string; +} + +export function ArrowLeftIcon({ size, strokeColor }: ArrowLeftIconProps) { + return ( + + + + ); +} diff --git a/packages/twenty-website-new/src/icons/functional/ArrowRight.tsx b/packages/twenty-website-new/src/icons/functional/ArrowRight.tsx new file mode 100644 index 0000000000..d19ec6d851 --- /dev/null +++ b/packages/twenty-website-new/src/icons/functional/ArrowRight.tsx @@ -0,0 +1,29 @@ +const ARROW_RIGHT_PATH = + "M14 5.75H.5m13.5 0L8.75.5M14 5.75 8.75 11"; + +interface ArrowRightIconProps { + size: number; + strokeColor: string; + fillColor?: string; +} + +export function ArrowRightIcon({ size, strokeColor }: ArrowRightIconProps) { + return ( + + + + ); +} diff --git a/packages/twenty-website-new/src/icons/functional/ArrowRightUp.tsx b/packages/twenty-website-new/src/icons/functional/ArrowRightUp.tsx new file mode 100644 index 0000000000..b46354651b --- /dev/null +++ b/packages/twenty-website-new/src/icons/functional/ArrowRightUp.tsx @@ -0,0 +1,30 @@ +const ARROW_RIGHT_UP_PATH = + "M6.069.5.5 6.068M6.069.5H1.738m4.33 0v4.331"; + +interface ArrowRightUpIconProps { + size: number; + strokeColor: string; +} + +export function ArrowRightUpIcon({ + size, + strokeColor, +}: ArrowRightUpIconProps) { + return ( + + + + ); +} diff --git a/packages/twenty-website-new/src/icons/functional/Close.tsx b/packages/twenty-website-new/src/icons/functional/Close.tsx new file mode 100644 index 0000000000..4e4b2300a4 --- /dev/null +++ b/packages/twenty-website-new/src/icons/functional/Close.tsx @@ -0,0 +1,26 @@ +const CLOSE_PATH = "m.5.5 9.546 9.546m-9.546 0L10.046.5"; + +interface CloseIconProps { + size: number; + strokeColor: string; +} + +export function CloseIcon({ size, strokeColor }: CloseIconProps) { + return ( + + + + ); +} diff --git a/packages/twenty-website-new/src/icons/functional/Menu.tsx b/packages/twenty-website-new/src/icons/functional/Menu.tsx new file mode 100644 index 0000000000..b0c37c966e --- /dev/null +++ b/packages/twenty-website-new/src/icons/functional/Menu.tsx @@ -0,0 +1,26 @@ +const MENU_PATH = "M.5.5H14M.5 5.75H14M.5 11H14"; + +interface MenuIconProps { + size: number; + strokeColor: string; +} + +export function MenuIcon({ size, strokeColor }: MenuIconProps) { + return ( + + + + ); +} diff --git a/packages/twenty-website-new/src/icons/functional/Minus.tsx b/packages/twenty-website-new/src/icons/functional/Minus.tsx new file mode 100644 index 0000000000..d1d5095e02 --- /dev/null +++ b/packages/twenty-website-new/src/icons/functional/Minus.tsx @@ -0,0 +1,26 @@ +const MINUS_PATH = "M.5 7.25H14.5"; + +interface MinusIconProps { + size: number; + strokeColor: string; +} + +export function MinusIcon({ size, strokeColor }: MinusIconProps) { + return ( + + + + ); +} diff --git a/packages/twenty-website-new/src/icons/functional/Plus.tsx b/packages/twenty-website-new/src/icons/functional/Plus.tsx new file mode 100644 index 0000000000..74be5f0ceb --- /dev/null +++ b/packages/twenty-website-new/src/icons/functional/Plus.tsx @@ -0,0 +1,26 @@ +const PLUS_PATH = "M.5 7.25H14M7.25 14V.5"; + +interface PlusIconProps { + size: number; + strokeColor: string; +} + +export function PlusIcon({ size, strokeColor }: PlusIconProps) { + return ( + + + + ); +} diff --git a/packages/twenty-website-new/src/icons/functional/RectangleFill.tsx b/packages/twenty-website-new/src/icons/functional/RectangleFill.tsx new file mode 100644 index 0000000000..5b27d805f2 --- /dev/null +++ b/packages/twenty-website-new/src/icons/functional/RectangleFill.tsx @@ -0,0 +1,18 @@ +interface RectangleFillIconProps { + size: number; + fillColor: string; +} + +export function RectangleFillIcon({ size, fillColor }: RectangleFillIconProps) { + return ( + + + + ); +} diff --git a/packages/twenty-website-new/src/icons/functional/RectangleOutline.tsx b/packages/twenty-website-new/src/icons/functional/RectangleOutline.tsx new file mode 100644 index 0000000000..288ebe0bc4 --- /dev/null +++ b/packages/twenty-website-new/src/icons/functional/RectangleOutline.tsx @@ -0,0 +1,29 @@ +interface RectangleOutlineIconProps { + size: number; + strokeColor: string; +} + +export function RectangleOutlineIcon({ + size, + strokeColor, +}: RectangleOutlineIconProps) { + return ( + + + + ); +} diff --git a/packages/twenty-website-new/src/icons/index.ts b/packages/twenty-website-new/src/icons/index.ts new file mode 100644 index 0000000000..0b42ff0411 --- /dev/null +++ b/packages/twenty-website-new/src/icons/index.ts @@ -0,0 +1,15 @@ +export { CLIENT_ICONS, FlexportIcon, ZapierIcon } from "./clients"; +export { ArrowLeftIcon } from "./functional/ArrowLeft"; +export { ArrowRightIcon } from "./functional/ArrowRight"; +export { ArrowRightUpIcon } from "./functional/ArrowRightUp"; +export { CloseIcon } from "./functional/Close"; +export { MenuIcon } from "./functional/Menu"; +export { MinusIcon } from "./functional/Minus"; +export { PlusIcon } from "./functional/Plus"; +export { RectangleFillIcon } from "./functional/RectangleFill"; +export { RectangleOutlineIcon } from "./functional/RectangleOutline"; +export { Logo } from "./logo/Logo"; +export { DiscordIcon } from "./social/Discord"; +export { GitHubIcon } from "./social/GitHub"; +export { LinkedInIcon } from "./social/LinkedIn"; +export { XIcon } from "./social/X"; diff --git a/packages/twenty-website-new/src/icons/informative/Users.tsx b/packages/twenty-website-new/src/icons/informative/Users.tsx new file mode 100644 index 0000000000..b9ea2ed901 --- /dev/null +++ b/packages/twenty-website-new/src/icons/informative/Users.tsx @@ -0,0 +1,29 @@ +const USERS_PATH = + "M5.499 9a7.52 7.52 0 0 1 4.492 1.502c.939.694 1.176 1.744.895 2.646h2.523c.28-.014.42-.06.48-.091a1 1 0 0 0-.11-.354 2.8 2.8 0 0 0-.709-.885 2 2 0 0 0-.396-.261.5.5 0 0 0-.175-.057v-2c.72 0 1.382.392 1.85.78.495.412.948.965 1.232 1.554.266.552.487 1.363.113 2.123-.405.824-1.277 1.145-2.2 1.19l-.024.001H8.427v-.153q-.06.004-.121.005H2.692a2.7 2.7 0 0 1-2.49-1.605c-.41-.952-.22-2.135.805-2.893A7.52 7.52 0 0 1 5.499 9m0 2c-1.213 0-2.347.404-3.303 1.11-.136.101-.175.19-.188.245a.4.4 0 0 0 .03.247c.09.205.32.398.654.398h5.614a.7.7 0 0 0 .654-.398.4.4 0 0 0 .03-.248c-.013-.054-.052-.143-.188-.244-.956-.706-2.09-1.11-3.303-1.11m.373-11a3.873 3.873 0 1 1 0 7.747 3.873 3.873 0 0 1 0-7.747m5.387.742a3.132 3.132 0 1 1-.648 6.195V4.799a1.133 1.133 0 0 0 1.78-.926 1.132 1.132 0 0 0-1.78-.928V.81q.315-.067.648-.068M5.872 2a1.873 1.873 0 1 0 0 3.746 1.873 1.873 0 0 0 0-3.746"; + +interface UsersIconProps { + size: number; + fillColor: string; +} + +export function UsersIcon({ + size, + fillColor, +}: UsersIconProps) { + return ( + + + + ); +} diff --git a/packages/twenty-website-new/src/icons/logo/Logo.tsx b/packages/twenty-website-new/src/icons/logo/Logo.tsx new file mode 100644 index 0000000000..736d781354 --- /dev/null +++ b/packages/twenty-website-new/src/icons/logo/Logo.tsx @@ -0,0 +1,27 @@ +const LOGO_PATH = + "M6.822 14.174c0-2.435 1.996-4.411 4.456-4.411h8.574c.125 0 .241.075.293.19a.31.31 0 0 1-.056.344l-1.88 2.023c-.326.35-.787.552-1.27.552H11.3c-.738 0-1.338.594-1.338 1.325v3.336a.78.78 0 0 1-.783.777H7.61a.78.78 0 0 1-.783-.777v-3.36zM33.5 25.553c0 2.434-1.996 4.411-4.456 4.411h-3.642c-2.46 0-4.454-1.977-4.454-4.411v-6.315c0-.43.16-.842.456-1.16l2.124-2.285a.33.33 0 0 1 .355-.081.32.32 0 0 1 .205.295v9.527c0 .73.598 1.322 1.337 1.322h3.6a1.33 1.33 0 0 0 1.337-1.322V14.197c0-.73-.599-1.325-1.337-1.325H24.84c-.481 0-.938.201-1.265.547L11.088 26.856h7.503a.78.78 0 0 1 .784.778v1.552a.78.78 0 0 1-.784.778H8.481a1.655 1.655 0 0 1-1.662-1.644v-.824c0-.412.156-.809.44-1.114l13.999-15.06a4.9 4.9 0 0 1 3.594-1.56h4.189c2.46 0 4.454 1.977 4.454 4.412v11.379z"; + +interface LogoProps { + size: number; + fillColor: string; + backgroundColor: string; +} + +export function Logo({ + size, + fillColor, + backgroundColor, +}: LogoProps) { + return ( + + + + + ); +} diff --git a/packages/twenty-website-new/src/icons/social/Discord.tsx b/packages/twenty-website-new/src/icons/social/Discord.tsx new file mode 100644 index 0000000000..73080bc656 --- /dev/null +++ b/packages/twenty-website-new/src/icons/social/Discord.tsx @@ -0,0 +1,21 @@ +const DISCORD_PATH = + "M10.518.785A10.3 10.3 0 0 0 7.99.001a.04.04 0 0 0-.04.019c-.11.194-.23.448-.315.647a9.5 9.5 0 0 0-2.84 0 7 7 0 0 0-.32-.647.04.04 0 0 0-.041-.02c-.888.153-1.737.421-2.53.785A.04.04 0 0 0 1.888.8C.277 3.206-.165 5.553.05 7.871a.04.04 0 0 0 .017.03A10.3 10.3 0 0 0 3.17 9.469a.04.04 0 0 0 .044-.014q.36-.49.634-1.033a.04.04 0 0 0-.021-.055 7 7 0 0 1-.97-.462.04.04 0 0 1-.003-.066q.098-.073.192-.15a.04.04 0 0 1 .04-.006c2.034.928 4.235.928 6.245 0a.04.04 0 0 1 .04.005q.095.078.193.151a.04.04 0 0 1-.003.066q-.463.27-.97.462a.04.04 0 0 0-.02.055q.28.543.633 1.032a.04.04 0 0 0 .044.015A10.3 10.3 0 0 0 12.355 7.9a.04.04 0 0 0 .016-.03c.26-2.68-.433-5.007-1.837-7.071a.03.03 0 0 0-.016-.015M4.152 6.46c-.612 0-1.117-.562-1.117-1.252s.495-1.253 1.117-1.253c.627 0 1.126.567 1.117 1.253 0 .69-.495 1.252-1.117 1.252m4.129 0c-.613 0-1.117-.562-1.117-1.252s.495-1.253 1.117-1.253c.627 0 1.126.567 1.116 1.253 0 .69-.49 1.252-1.116 1.252"; + +interface DiscordIconProps { + size: number; + fillColor: string; +} + +export function DiscordIcon({ size, fillColor }: DiscordIconProps) { + return ( + + + + ); +} diff --git a/packages/twenty-website-new/src/icons/social/GitHub.tsx b/packages/twenty-website-new/src/icons/social/GitHub.tsx new file mode 100644 index 0000000000..d50d61c4d5 --- /dev/null +++ b/packages/twenty-website-new/src/icons/social/GitHub.tsx @@ -0,0 +1,21 @@ +const GITHUB_PATH = + "M6.979 0C3.12 0 0 3.143 0 7.031a7.025 7.025 0 0 0 4.772 6.67c.347.07.474-.151.474-.337 0-.163-.012-.722-.012-1.304-1.941.419-2.345-.838-2.345-.838-.312-.815-.775-1.025-.775-1.025-.635-.43.047-.43.047-.43.705.046 1.074.721 1.074.721.624 1.071 1.63.769 2.034.582.058-.454.243-.768.439-.943-1.548-.163-3.177-.768-3.177-3.469 0-.768.277-1.396.716-1.885-.069-.175-.312-.897.069-1.863 0 0 .59-.186 1.918.722a6.7 6.7 0 0 1 1.745-.233c.589 0 1.19.082 1.745.233 1.329-.908 1.918-.722 1.918-.722.381.966.139 1.688.069 1.863.451.489.717 1.117.717 1.885 0 2.701-1.629 3.295-3.189 3.469.254.222.473.641.473 1.304 0 .943-.011 1.7-.011 1.932 0 .187.127.408.474.338a7.025 7.025 0 0 0 4.772-6.67C13.958 3.143 10.827 0 6.979 0"; + +interface GitHubIconProps { + size: number; + fillColor: string; +} + +export function GitHubIcon({ size, fillColor }: GitHubIconProps) { + return ( + + + + ); +} diff --git a/packages/twenty-website-new/src/icons/social/LinkedIn.tsx b/packages/twenty-website-new/src/icons/social/LinkedIn.tsx new file mode 100644 index 0000000000..8e775e8716 --- /dev/null +++ b/packages/twenty-website-new/src/icons/social/LinkedIn.tsx @@ -0,0 +1,21 @@ +const LINKEDIN_PATH = + "M.386 2.375A1.35 1.35 0 0 1 0 1.394c0-.39.129-.732.386-.997Q.772.001 1.38 0c.406 0 .725.133.982.397q.386.397.386.997c0 .399-.13.716-.386.98q-.386.398-.982.398c-.397 0-.738-.133-.995-.397m2.146 1.517V12H.216V3.892zm7.712.801Q11 5.602 11 7.19v4.667H8.8V7.519q0-.802-.375-1.245t-1.01-.444q-.633 0-1.009.444T6.031 7.52v4.338H3.818V3.869h2.213v1.06q.336-.53.906-.838t1.28-.307q1.27 0 2.026.908"; + +interface LinkedInIconProps { + size: number; + fillColor: string; +} + +export function LinkedInIcon({ size, fillColor }: LinkedInIconProps) { + return ( + + + + ); +} diff --git a/packages/twenty-website-new/src/icons/social/X.tsx b/packages/twenty-website-new/src/icons/social/X.tsx new file mode 100644 index 0000000000..f0eac68307 --- /dev/null +++ b/packages/twenty-website-new/src/icons/social/X.tsx @@ -0,0 +1,21 @@ +const X_PATH = + "m.03 0 4.83 6.46L0 11.713h1.094L5.35 7.115l3.439 4.598h3.723L7.41 4.89 11.935 0H10.84L6.921 4.235 3.754 0zm1.61.806h1.71l7.553 10.101h-1.71z"; + +interface XIconProps { + size: number; + fillColor: string; +} + +export function XIcon({ size, fillColor }: XIconProps) { + return ( + + + + ); +} diff --git a/packages/twenty-website-new/src/sections/Footer/FooterShape.tsx b/packages/twenty-website-new/src/sections/Footer/FooterShape.tsx new file mode 100644 index 0000000000..af2f31e0f4 --- /dev/null +++ b/packages/twenty-website-new/src/sections/Footer/FooterShape.tsx @@ -0,0 +1,46 @@ +const FOOTER_SHAPE_PATH = + "M0 4a4 4 0 0 1 4-4h344.32c4.197 0 8.369.66 12.361 1.958l49.5 16.084A40 40 0 0 0 422.542 20h517.7c4.293 0 8.559-.691 12.633-2.047l47.785-15.906A40 40 0 0 1 1013.29 0H1356a4 4 0 0 1 4 4v16H0z"; + +interface FooterShapeProps { + fillColor: string; +} + +export function FooterShape({ fillColor }: FooterShapeProps) { + return ( +
+ + + +
+
+ ); +} diff --git a/packages/twenty-website-new/src/sections/Footer/components/Bottom/Bottom.tsx b/packages/twenty-website-new/src/sections/Footer/components/Bottom/Bottom.tsx new file mode 100644 index 0000000000..8f9a4ca65e --- /dev/null +++ b/packages/twenty-website-new/src/sections/Footer/components/Bottom/Bottom.tsx @@ -0,0 +1,49 @@ +import { theme } from '@/theme'; +import { styled } from '@linaria/react'; +import type { ReactNode } from 'react'; + +const BottomGrid = styled.div` + display: grid; + font-size: ${theme.font.size(3)}; + gap: ${theme.spacing(6)}; + grid-template-columns: 1fr 1fr; + grid-template-rows: auto auto; +`; + +const Copyright = styled.div` + color: ${theme.colors.primary.text[100]}; + font-family: ${theme.font.family.mono}; + grid-column: 1; + grid-row: 2; + justify-self: start; + text-transform: uppercase; + + @media (min-width: ${theme.breakpoints.md}px) { + grid-row: 1; + } +`; + +const Credit = styled.div` + color: ${theme.colors.primary.text[100]}; + font-family: ${theme.font.family.mono}; + grid-column: 2; + grid-row: 2; + justify-self: end; + text-transform: uppercase; + + @media (min-width: ${theme.breakpoints.md}px) { + grid-column: 1 / -1; + } +`; + +type BottomProps = { children?: ReactNode }; + +export function Bottom({ children }: BottomProps) { + return ( + + © 2026 – Twenty + {children} + Design by Ardent. + + ); +} diff --git a/packages/twenty-website-new/src/sections/Footer/components/Logo/Logo.tsx b/packages/twenty-website-new/src/sections/Footer/components/Logo/Logo.tsx new file mode 100644 index 0000000000..a0f1252ff2 --- /dev/null +++ b/packages/twenty-website-new/src/sections/Footer/components/Logo/Logo.tsx @@ -0,0 +1,12 @@ +import { Logo as LogoIcon } from '@/icons'; +import { theme } from '@/theme'; + +export function Logo() { + return ( + + ); +} diff --git a/packages/twenty-website-new/src/sections/Footer/components/Nav/Nav.tsx b/packages/twenty-website-new/src/sections/Footer/components/Nav/Nav.tsx new file mode 100644 index 0000000000..6a21b727be --- /dev/null +++ b/packages/twenty-website-new/src/sections/Footer/components/Nav/Nav.tsx @@ -0,0 +1,198 @@ +import { LinkButton } from '@/design-system/components'; +import { PlusIcon, RectangleFillIcon } from '@/icons'; +import { FOOTER_NAV_GROUPS } from '@/sections/Footer/constants/footer-nav-groups'; +import { theme } from '@/theme'; +import { NavigationMenu } from '@base-ui/react/navigation-menu'; +import { styled } from '@linaria/react'; +import Link from 'next/link'; +import React from 'react'; + +const FooterNav = styled.nav` + margin-top: ${theme.spacing(10)}; + margin-bottom: ${theme.spacing(10)}; + + @media (min-width: ${theme.breakpoints.md}px) { + display: grid; + grid-template-columns: 1fr auto 1fr auto 1fr auto 1fr; + } +`; + +const NavDivider = styled.div` + align-items: center; + display: flex; + gap: ${theme.spacing(1.5)}; + width: 100%; + + @media (min-width: ${theme.breakpoints.md}px) { + flex-direction: column; + height: 100%; + width: auto; + margin-left: ${theme.spacing(7)}; + margin-right: ${theme.spacing(7)}; + } +`; + +const NavDividerLine = styled.div` + background-color: #1c1c1c1a; + flex: 1 1 0%; + height: 1px; + min-height: 1px; + min-width: 0; + + @media (min-width: ${theme.breakpoints.md}px) { + height: auto; + min-height: 0; + min-width: 1px; + width: 1px; + } +`; + +const NavGroup = styled.section` + margin-top: ${theme.spacing(4)}; + margin-bottom: ${theme.spacing(4)}; + + @media (min-width: ${theme.breakpoints.md}px) { + margin-top: 0; + margin-bottom: ${theme.spacing(7)}; + } +`; + +const NavGroupTitle = styled.h4` + font-size: ${theme.font.size(4)}; + font-weight: ${theme.font.weight.medium}; + margin-bottom: ${theme.spacing(2)}; + line-height: 1.35; +`; + +const NavMenuList = styled(NavigationMenu.List)` + display: flex; + flex-direction: column; + gap: ${theme.spacing(2)}; + list-style: none; + margin: 0; + padding: 0; +`; + +const NavLinkHoverIcon = styled.span` + display: inline-flex; + flex-shrink: 0; + min-width: 0; + opacity: 0; + overflow: hidden; + transition: + width 0.3s ease-out, + opacity 0.3s ease-out; + width: 0; +`; + +const NavLink = styled(NavigationMenu.Link)` + align-items: center; + color: ${theme.colors.primary.text[100]}; + display: flex; + font-size: ${theme.font.size(4)}; + gap: 0; + text-decoration: none; + transition: gap 0.3s ease-out; + + &:focus-visible { + outline: 1px solid ${theme.colors.highlight[100]}; + outline-offset: 1px; + } + + @media (min-width: ${theme.breakpoints.md}px) { + &:hover { + gap: ${theme.spacing(2)}; + } + + &:hover ${NavLinkHoverIcon} { + opacity: 1; + width: 14px; + } + } +`; + +const Actions = styled.div` + align-items: flex-start; + display: flex; + flex-direction: column; + gap: ${theme.spacing(2)}; + margin-top: ${theme.spacing(8)}; + + & > * { + width: 126px; + } +`; + +export function Nav() { + return ( + }> + {FOOTER_NAV_GROUPS.map((group, index) => ( + + {index > 0 && ( + + + + + + )} + + {group.title} + + {group.links.map((link) => ( + + + ) : ( + + ) + } + > + + + + {link.label} + + + ))} + + {group.showActions && ( + + + + + )} + + + ))} + + ); +} diff --git a/packages/twenty-website-new/src/sections/Footer/components/Root/Root.tsx b/packages/twenty-website-new/src/sections/Footer/components/Root/Root.tsx new file mode 100644 index 0000000000..a3beadcb71 --- /dev/null +++ b/packages/twenty-website-new/src/sections/Footer/components/Root/Root.tsx @@ -0,0 +1,68 @@ +import { Container } from '@/design-system/components'; +import { theme } from '@/theme'; +import { styled } from '@linaria/react'; +import type { ReactNode } from 'react'; +import { FooterShape } from '../../FooterShape'; + +const FooterRoot = styled.footer` + background-color: ${theme.colors.secondary.background[100]}; + width: 100%; +`; + +const FooterContainer = styled(Container)` + display: flex; + flex-direction: column; + height: 1280px; + position: relative; + padding-bottom: ${theme.spacing(4)}; + padding-left: ${theme.spacing(4)}; + padding-right: ${theme.spacing(4)}; + + @media (min-width: ${theme.breakpoints.lg}px) { + padding-bottom: ${theme.spacing(10)}; + padding-left: ${theme.spacing(10)}; + padding-right: ${theme.spacing(10)}; + } +`; + +const FooterBackground = styled.div` + position: absolute; + z-index: 0; +`; + +const FooterContent = styled.div` + margin-top: auto; + padding-bottom: ${theme.spacing(12)}; + padding-top: ${theme.spacing(12)}; + padding-left: ${theme.spacing(4)}; + padding-right: ${theme.spacing(4)}; + position: relative; + z-index: 1; + + @media (min-width: ${theme.breakpoints.md}px) { + padding-left: ${theme.spacing(20)}; + padding-right: ${theme.spacing(20)}; + padding-top: ${theme.spacing(20)}; + } + + @media (min-width: ${theme.breakpoints.lg}px) { + padding-left: ${theme.spacing(30)}; + padding-right: ${theme.spacing(30)}; + } +`; + +type RootProps = { children: ReactNode }; + +export function Root({ children }: RootProps) { + return ( + + + + + + {children} + + + + ); +} diff --git a/packages/twenty-website-new/src/sections/Footer/components/Social/Social.tsx b/packages/twenty-website-new/src/sections/Footer/components/Social/Social.tsx new file mode 100644 index 0000000000..558620aff7 --- /dev/null +++ b/packages/twenty-website-new/src/sections/Footer/components/Social/Social.tsx @@ -0,0 +1,74 @@ +import { ArrowRightUpIcon } from '@/icons'; +import { SOCIAL_LINKS } from '@/sections/Footer/constants/social-links'; +import { theme } from '@/theme'; +import { Separator } from '@base-ui/react/separator'; +import { styled } from '@linaria/react'; +import React from 'react'; + +const SocialContainer = styled.nav` + align-items: center; + display: flex; + gap: ${theme.spacing(6)}; + grid-column: 1 / -1; + grid-row: 1; + + @media (min-width: ${theme.breakpoints.md}px) { + grid-column: 2; + justify-self: end; + } +`; + +const SocialDivider = styled(Separator)` + background-color: ${theme.colors.primary.border[40]}; + border: none; + height: 10px; + width: 1px; +`; + +const SocialItem = styled.a` + align-items: center; + color: ${theme.colors.primary.text[100]}; + display: flex; + font-size: ${theme.font.size(3)}; + font-weight: ${theme.font.weight.medium}; + gap: ${theme.spacing(3)}; + line-height: 14px; + text-decoration: none; + + &:focus-visible { + outline: 1px solid ${theme.colors.highlight[100]}; + outline-offset: 1px; + } +`; + +export function Social() { + return ( + + {SOCIAL_LINKS.filter((item) => item.showInDrawer).map((item, index) => ( + + {index > 0 && } + + + {item.label} + {item.label != null && ( + + )} + + + ))} + + ); +} diff --git a/packages/twenty-website-new/src/sections/Footer/components/index.ts b/packages/twenty-website-new/src/sections/Footer/components/index.ts new file mode 100644 index 0000000000..d329377e0d --- /dev/null +++ b/packages/twenty-website-new/src/sections/Footer/components/index.ts @@ -0,0 +1,7 @@ +import { Bottom } from './Bottom/Bottom'; +import { Logo } from './Logo/Logo'; +import { Nav } from './Nav/Nav'; +import { Root } from './Root/Root'; +import { Social } from './Social/Social'; + +export const Footer = { Root, Logo, Nav, Social, Bottom }; diff --git a/packages/twenty-website-new/src/sections/Footer/constants/footer-nav-groups.ts b/packages/twenty-website-new/src/sections/Footer/constants/footer-nav-groups.ts new file mode 100644 index 0000000000..bedffb4990 --- /dev/null +++ b/packages/twenty-website-new/src/sections/Footer/constants/footer-nav-groups.ts @@ -0,0 +1,47 @@ +export const FOOTER_NAV_GROUPS = [ + { + id: "footer-sitemap", + title: "Sitemap", + showActions: false, + 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", + showActions: false, + 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", + showActions: false, + 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", + showActions: true, + links: [ + { + label: "LinkedIn", + href: "https://www.linkedin.com/company/twenty", + external: true, + }, + ], + }, +]; diff --git a/packages/twenty-website-new/src/sections/Footer/constants/nav-items.ts b/packages/twenty-website-new/src/sections/Footer/constants/nav-items.ts new file mode 100644 index 0000000000..99b8a94aad --- /dev/null +++ b/packages/twenty-website-new/src/sections/Footer/constants/nav-items.ts @@ -0,0 +1,6 @@ +export const NAV_ITEMS = [ + { label: 'Product', href: '/product' }, + { label: 'Pricing', href: '/pricing' }, + { label: 'Partners', href: '/partner' }, + { label: 'Why Twenty', href: '/why-twenty' }, +]; diff --git a/packages/twenty-website-new/src/sections/Footer/constants/social-links.ts b/packages/twenty-website-new/src/sections/Footer/constants/social-links.ts new file mode 100644 index 0000000000..beaf0ca099 --- /dev/null +++ b/packages/twenty-website-new/src/sections/Footer/constants/social-links.ts @@ -0,0 +1,37 @@ +import { DiscordIcon, GitHubIcon, LinkedInIcon, XIcon } from "@/icons" + +export const SOCIAL_LINKS = [ + { + href: "https://github.com/twentyhq/twenty", + icon: GitHubIcon, + label: "39.8K", + ariaLabel: "GitHub (opens in new tab)", + showInDesktop: true, + showInDrawer: true, + }, + { + href: "https://discord.gg/cx5n4Jzs57", + icon: DiscordIcon, + label: "5.6K", + ariaLabel: "Discord (opens in new tab)", + className: "discord-link", + showInDesktop: true, + showInDrawer: true, + }, + { + href: "https://www.linkedin.com/company/twenty", + icon: LinkedInIcon, + label: undefined, + ariaLabel: "LinkedIn (opens in new tab)", + showInDesktop: false, + showInDrawer: true, + }, + { + href: "https://x.com/twentycrm", + icon: XIcon, + label: undefined, + ariaLabel: "X (opens in new tab)", + showInDesktop: false, + showInDrawer: true, + }, +] diff --git a/packages/twenty-website-new/src/sections/Hero/components/Body/Body.tsx b/packages/twenty-website-new/src/sections/Hero/components/Body/Body.tsx new file mode 100644 index 0000000000..df7e8cc92b --- /dev/null +++ b/packages/twenty-website-new/src/sections/Hero/components/Body/Body.tsx @@ -0,0 +1,46 @@ +import { + Body as BaseBody, + type BodyProps, +} from '@/design-system/components/Body/Body'; +import { Pages } from '@/enums/pages'; +import { theme } from '@/theme'; +import { styled } from '@linaria/react'; + +const BodyContainer = styled.div` + color: ${theme.colors.primary.text[60]}; + margin-top: calc(${theme.spacing(2)} - ${theme.spacing(6)}); + width: 360px; + + @media (min-width: ${theme.breakpoints.md}px) { + &[data-page=${Pages.Home}] { + width: 591px; + } + + &[data-page=${Pages.Partner}] { + width: 500px; + } + + &[data-page=${Pages.Pricing}] { + width: 500px; + } + + &[data-page=${Pages.Product}] { + width: 591px; + } + + &[data-page=${Pages.WhyTwenty}] { + width: 443px; + color: ${theme.colors.secondary.text[100]}; + } + } +`; + +export type HeroBodyProps = BodyProps & { page: Pages }; + +export function Body({ page, ...bodyProps }: HeroBodyProps) { + return ( + + + + ); +} diff --git a/packages/twenty-website-new/src/sections/Hero/components/Cta/Cta.tsx b/packages/twenty-website-new/src/sections/Hero/components/Cta/Cta.tsx new file mode 100644 index 0000000000..d20d98b91a --- /dev/null +++ b/packages/twenty-website-new/src/sections/Hero/components/Cta/Cta.tsx @@ -0,0 +1,16 @@ +import { theme } from '@/theme'; +import { styled } from '@linaria/react'; +import { ReactNode } from 'react'; + +const CTAsContainer = styled.div` + display: flex; + flex-wrap: wrap; + gap: ${theme.spacing(4)}; + justify-content: center; +`; + +export type HeroCtaProps = { children: ReactNode }; + +export function Cta({ children }: HeroCtaProps) { + return {children}; +} diff --git a/packages/twenty-website-new/src/sections/Hero/components/Heading/Heading.tsx b/packages/twenty-website-new/src/sections/Hero/components/Heading/Heading.tsx new file mode 100644 index 0000000000..0f3d230580 --- /dev/null +++ b/packages/twenty-website-new/src/sections/Hero/components/Heading/Heading.tsx @@ -0,0 +1,35 @@ +import { + Heading as BaseHeading, + type HeadingProps, +} from '@/design-system/components/Heading/Heading'; +import { Pages } from '@/enums/pages'; +import { theme } from '@/theme'; +import { styled } from '@linaria/react'; + +const HeadingContainer = styled.div` + width: 360px; + word-wrap: break-word; + + @media (min-width: ${theme.breakpoints.md}px) { + width: 672px; + + &[data-page=${Pages.WhyTwenty}] { + color: ${theme.colors.secondary.text[100]}; + } + } +`; + +export type HeroHeadingProps = HeadingProps & { page: Pages }; + +export function Heading({ + page, + size = 'lg', + weight = 'light', + ...props +}: HeroHeadingProps) { + return ( + + + + ); +} diff --git a/packages/twenty-website-new/src/sections/Hero/components/HomeVisual/HomeVisual.tsx b/packages/twenty-website-new/src/sections/Hero/components/HomeVisual/HomeVisual.tsx new file mode 100644 index 0000000000..68c41fedcf --- /dev/null +++ b/packages/twenty-website-new/src/sections/Hero/components/HomeVisual/HomeVisual.tsx @@ -0,0 +1,55 @@ +import { Image } from '@/design-system/components'; +import { theme } from '@/theme'; +import { styled } from '@linaria/react'; + +const StyledHomeVisual = styled.div` + display: grid; + grid-template-columns: 1fr; + grid-template-rows: 1fr; + margin-top: ${theme.spacing(5)}; + position: relative; + right: calc(${theme.spacing(2)} - ${theme.spacing(6)}); + width: 100%; + + @media (min-width: ${theme.breakpoints.md}px) { + height: 500px; + margin-top: ${theme.spacing(17.5)}; + overflow: hidden; + right: 0; + } +`; + +const StyledBackgroundImage = styled(Image)` + grid-area: 1 / 1; + justify-self: end; + width: 98%; + + @media (min-width: ${theme.breakpoints.md}px) { + width: 850px; + } +`; + +const StyledForegroundImage = styled(Image)` + grid-area: 1 / 1; + justify-self: end; + margin-right: ${theme.spacing(6.5)}; + margin-top: ${theme.spacing(20)}; + width: 98%; + z-index: 1; + + @media (min-width: ${theme.breakpoints.md}px) { + justify-self: center; + margin-right: ${theme.spacing(40)}; + margin-top: ${theme.spacing(17.5)}; + width: 800px; + } +`; + +export function HomeVisual() { + return ( + + + + + ); +} diff --git a/packages/twenty-website-new/src/sections/Hero/components/Illustration/Illustration.tsx b/packages/twenty-website-new/src/sections/Hero/components/Illustration/Illustration.tsx new file mode 100644 index 0000000000..11eb01c4ea --- /dev/null +++ b/packages/twenty-website-new/src/sections/Hero/components/Illustration/Illustration.tsx @@ -0,0 +1,37 @@ +import { IllustrationType } from '@/design-system/components/Illustration/types/Illustration'; +import { theme } from '@/theme'; +import { styled } from '@linaria/react'; + +const StyledIllustrationContainer = styled.div` + margin-top: ${theme.spacing(6)}; + width: 100%; +`; + +type IllustrationProps = IllustrationType & { + backgroundColor: string; +}; + +export function Illustration({ + src, + title, + backgroundColor, +}: IllustrationProps) { + return ( + +