diff --git a/client/config/growthbook-features-default.json b/client/config/growthbook-features-default.json index 23971b6e27a..0af2d8d8bc4 100644 --- a/client/config/growthbook-features-default.json +++ b/client/config/growthbook-features-default.json @@ -5,17 +5,17 @@ "aa-test-in-component": { "defaultValue": false }, - "landing-page-redesign": { + "landing-top-skill-focused": { "defaultValue": false, "rules": [ { "coverage": 1, "hashAttribute": "id", - "seed": "landing-page-redesign", + "seed": "landing-top-skill-focused", "hashVersion": 2, "variations": [false, true], "weights": [0.5, 0.5], - "key": "landing-page-redesign", + "key": "landing-top-skill-focused", "meta": [ { "key": "0", diff --git a/client/i18n/locales/english/translations.json b/client/i18n/locales/english/translations.json index 5d289062c55..ab52e0cea29 100644 --- a/client/i18n/locales/english/translations.json +++ b/client/i18n/locales/english/translations.json @@ -114,12 +114,12 @@ }, "landing": { "big-heading-1": "Learn to code — for free.", - "big-heading-1-b": "Learn to code.", "big-heading-2": "Build projects.", + "big-heading-1-b": "Build Your Skills for Free.", "big-heading-3": "Earn certifications.", - "big-heading-4": "All for free.", - "h2-heading": "Since 2014, more than 40,000 freeCodeCamp.org graduates have gotten jobs at tech companies including:", - "h2-heading-b": "More than 100,000 freeCodeCamp.org graduates have gotten jobs at tech companies including:", + "advance-career": "Advance your career by learning in-demand skills in Programming, DevOps, Cybersecurity, AI Engineering, and English for Developers.", + "h2-heading": "More than 100,000 freeCodeCamp.org graduates have gotten jobs at tech companies including:", + "graduates-work": "More than 100,000 freeCodeCamp graduates work in companies such as", "hero-img-description": "freeCodeCamp students at a local study group in South Korea.", "hero-img-alt": "A group of people, including a White man, a Black woman, and an Asian woman, gathered around a laptop.", "hero-img-uis": "A group of screenshots showing the freeCodeCamp editor interface on both a mobile and desktop device and a certification.", diff --git a/client/src/components/landing/components/landing-top-b.tsx b/client/src/components/landing/components/landing-top-b.tsx index 2f0a3817dcb..7324d54dd75 100644 --- a/client/src/components/landing/components/landing-top-b.tsx +++ b/client/src/components/landing/components/landing-top-b.tsx @@ -14,79 +14,69 @@ import { import BigCallToAction from './big-call-to-action'; import CampersImage from './campers-image'; -const LogoRow = (): JSX.Element => { +function LandingTop(): JSX.Element { + const { t } = useTranslation(); const showChineseLogos = ['chinese', 'chinese-tradition'].includes( clientLocale ); - return ( - <> -

- landing.h2-heading-b -

-
- - - - {showChineseLogos ? ( - <> - - - - ) : ( - <> - - - - )} -
- - ); -}; - -function LandingTop(): JSX.Element { - const { t } = useTranslation(); - - return ( - - - - - + + + + +

- {t('landing.big-heading-1')} + {t('landing.big-heading-1-b')}

-

- {t('landing.big-heading-2')} +

+ {t('landing.advance-career')}

-

- {t('landing.big-heading-3')} -

- + + + + + +
+
+ - - + + +

+ landing.graduates-work +

+ +
+ + + + {showChineseLogos ? ( + <> + + + + ) : ( + <> + + + + )} +
diff --git a/client/src/components/landing/components/landing-top.tsx b/client/src/components/landing/components/landing-top.tsx index a47ee5df7aa..c9aadefb5de 100644 --- a/client/src/components/landing/components/landing-top.tsx +++ b/client/src/components/landing/components/landing-top.tsx @@ -1,7 +1,6 @@ import React from 'react'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import { Container, Col, Row, Spacer } from '@freecodecamp/ui'; - import { clientLocale } from '../../../../config/env.json'; import { AmazonLogo, @@ -15,64 +14,80 @@ import { import BigCallToAction from './big-call-to-action'; import CampersImage from './campers-image'; -function LandingTop(): JSX.Element { - const { t } = useTranslation(); +const LogoRow = (): JSX.Element => { const showChineseLogos = ['chinese', 'chinese-tradition'].includes( clientLocale ); + return ( - - + <> +

+ landing.h2-heading +

+
+ + + + {showChineseLogos ? ( + <> + + + + ) : ( + <> + + + + )} +
+ + ); +}; + +function LandingTop(): JSX.Element { + const { t } = useTranslation(); + + return ( + + - -

- {t('landing.big-heading-1')} -

-

- {t('landing.big-heading-2')} -

-

- {t('landing.big-heading-3')} -

-

- {t('landing.h2-heading')} -

-
- - - - {showChineseLogos ? ( - <> - - - - ) : ( - <> - - - - )} -
- - - - - -
+ + +

+ {t('landing.big-heading-1')} +

+

+ {t('landing.big-heading-2')} +

+

+ {t('landing.big-heading-3')} +

+ + + + +
+ + + + + + +
); } diff --git a/client/src/components/landing/components/ui-images.tsx b/client/src/components/landing/components/ui-images.tsx deleted file mode 100644 index d362c4b3fbf..00000000000 --- a/client/src/components/landing/components/ui-images.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { Spacer } from '@freecodecamp/ui'; -import Media from 'react-responsive'; -import landingPageb from '../../../assets/images/landing/landing-page-b.svg'; -import { LazyImage } from '../../helpers'; - -const LARGE_SCREEN_SIZE = 1200; - -function UIImages(): JSX.Element { - const { t } = useTranslation(); - - return ( - -
- -
- -
- ); -} - -UIImages.displayName = 'UIImages'; -export default UIImages; diff --git a/client/src/components/landing/landing.css b/client/src/components/landing/landing.css index 5bb8e611932..423f53732d3 100644 --- a/client/src/components/landing/landing.css +++ b/client/src/components/landing/landing.css @@ -98,7 +98,6 @@ figcaption.caption { .landing-top p { font-size: 1.2rem; - font-weight: bold; margin: 0.6rem 0; } @@ -218,33 +217,33 @@ figcaption.caption { } /* AB testing styles */ -.landing-page-b .mega-heading { +.landing-top .mega-heading { font-size: 2.2rem; margin: 0px 0px 2rem; font-weight: 700; line-height: 2rem; } -.landing-page-b .landing-top .btn-cta-big { +.landing-top .btn-cta-big { max-width: fit-content; padding: 12px 20px; } @media (min-width: 500px) { - .landing-page-b .mega-heading { + .landing-top .mega-heading { font-size: 3rem; line-height: 2.5rem; } - .landing-page-b .landing-top .btn-cta-big { + .landing-top .btn-cta-big { padding: 12px 40px; } } -.landing-page-b .logo-row-title { +.logo-row-title { font-weight: normal; } -.landing-page-b { +.landing-page { overflow-x: hidden; } @@ -297,3 +296,62 @@ figure.ui-images img { .benefits-container p { color: var(--gray-15); } + +/* AB testing styles */ +.landing-top-two-column { + display: flex; + flex-direction: column; + justify-content: center; + width: 100%; + gap: 20px; +} + +.landing-top-two-column p { + font-size: 1.3rem; + font-weight: normal; + line-height: 1.5; +} + +@media (min-width: 1200px) { + .landing-top-two-column { + flex-direction: row; + align-items: center; + } + + .landing-top-two-column .landing-top-left { + flex: 0 0 50%; + max-width: 50%; + z-index: 1; + } + + .landing-top-two-column .landing-top-right { + flex: 0 1 auto; + min-width: 0; + overflow: visible; + } +} + +.landing-top .landing-top-two-column figure, +.landing-top .landing-top-two-column figure img { + margin-left: -100px; + margin-top: 0px; + height: 550px; + width: 850px; + max-width: none; + -webkit-mask-image: linear-gradient(to right, transparent 0%, black 20%); + mask-image: linear-gradient(to right, transparent 0%, black 20%); +} + +.landing-top .ultra-heading { + font-size: 4rem; +} + +.landing-top-right figcaption { + display: none; +} + +.landing-top .brands-container p { + color: var(--quaternary-color); + font-weight: normal; + text-align: center; +} diff --git a/client/src/pages/index.tsx b/client/src/pages/index.tsx index caa07a8cb58..65e5a63d976 100644 --- a/client/src/pages/index.tsx +++ b/client/src/pages/index.tsx @@ -3,9 +3,8 @@ import { useTranslation } from 'react-i18next'; import { useGrowthBook } from '@growthbook/growthbook-react'; import SEO from '../components/seo'; import { Loader } from '../components/helpers'; -import LandingTop from '../components/landing/components/landing-top'; import LandingTopB from '../components/landing/components/landing-top-b'; -import AsSeenIn from '../components/landing/components/as-seen-in'; +import LandingTop from '../components/landing/components/landing-top'; import Testimonials from '../components/landing/components/testimonials'; import Certifications from '../components/landing/components/certifications'; import Faq from '../components/landing/components/faq'; @@ -14,19 +13,12 @@ import '../components/landing/landing.css'; type LandingProps = { showLandingPageRedesign: boolean; - showBenefitsSection: boolean; }; -const Landing = ({ - showLandingPageRedesign, - showBenefitsSection -}: LandingProps) => ( -
+const Landing = ({ showLandingPageRedesign }: LandingProps) => ( +
{showLandingPageRedesign ? : } - {showBenefitsSection ? : } - + @@ -38,21 +30,14 @@ function IndexPage(): JSX.Element { const growthbook = useGrowthBook(); if (growthbook && growthbook.ready) { const showLandingPageRedesign = growthbook.getFeatureValue( - 'landing-page-redesign', - false - ); - const showBenefitsSection = growthbook.getFeatureValue( - 'show-benefits', + 'landing-top-skill-focused', false ); growthbook.getFeatureValue('landing-aa-test', false); return ( <> - + ); } else { diff --git a/e2e/landing.spec.ts b/e2e/landing.spec.ts index a301dc635f3..355632a163a 100644 --- a/e2e/landing.spec.ts +++ b/e2e/landing.spec.ts @@ -51,70 +51,32 @@ test.describe('Landing Top - Variation B', () => { await goToLandingPage(page); }); - test('The supporting copy renders correctly', async ({ page }) => { - const landingH2Heading = page.getByTestId('landing-h2-heading-b'); - await expect(landingH2Heading).toHaveText( - translations.landing['h2-heading-b'].replace(/<\/?strong>/g, '') + test('Main heading copy renders correctly', async ({ page }) => { + const bigHeading = page.getByTestId('big-heading-1-b'); + await expect(bigHeading).toHaveText( + translations.landing['big-heading-1-b'] ); }); -}); -test.describe('Second section - Variation B', () => { - test('The component Why learn with freeCodeCamp renders correctly', async ({ - context, - page - }) => { - await addGrowthbookCookie({ context, variation: 'C' }); - await goToLandingPage(page); - const h2Element = page.locator( - `h2:has-text("${translations.landing.benefits['heading']}")` + test('Supporting copy renders correctly', async ({ page }) => { + const bigHeading = page.getByTestId('advance-career'); + await expect(bigHeading).toHaveText(translations.landing['advance-career']); + }); + + test('Logo row copy renders correctly', async ({ page }) => { + const landingH2Heading = page.getByTestId('graduates-work'); + await expect(landingH2Heading).toHaveText( + translations.landing['graduates-work'].replace(/<\/?strong>/g, '') ); - - await expect(h2Element).toBeVisible(); }); }); test.describe('Landing Top - Variation A', () => { test.beforeEach(async ({ context, page }) => { - await addGrowthbookCookie({ context, variation: 'A' }); + await addGrowthbookCookie({ context, variation: 'newA' }); await goToLandingPage(page); }); - test('The supporting copy renders correctly', async ({ page }) => { - const landingH2Heading = page.getByTestId('landing-h2-heading'); - await expect(landingH2Heading).toHaveText( - translations.landing['h2-heading'] - ); - }); -}); - -test.describe('Second section - Variation A', () => { - test('The component As Seen renders correctly', async ({ context, page }) => { - await addGrowthbookCookie({ context, variation: 'E' }); - await goToLandingPage(page); - const h2Element = page.locator( - `h2:has-text("${translations.landing['as-seen-in']}")` - ); - - await expect(h2Element).toBeVisible(); - }); -}); - -test.describe('Landing Page', () => { - test.beforeEach(async ({ page }) => { - await goToLandingPage(page); - }); - - test('Call to action buttons should render correctly', async ({ page }) => { - const ctas = page.getByRole('link', { - name: translations.buttons['logged-in-cta-btn'] - }); - await expect(ctas).toHaveCount(4); - for (const cta of await ctas.all()) { - await expect(cta).toBeVisible(); - } - }); - test('The headline renders correctly', async ({ page }) => { const landingHeading1 = page.getByTestId('landing-big-heading-1'); await expect(landingHeading1).toHaveText( @@ -132,6 +94,42 @@ test.describe('Landing Page', () => { ); }); + test('Logo row copy renders correctly', async ({ page }) => { + const landingH2Heading = page.getByTestId('h2-heading'); + await expect(landingH2Heading).toHaveText( + translations.landing['h2-heading'].replace(/<\/?strong>/g, '') + ); + }); +}); + +test.describe('Landing Page', () => { + test.beforeEach(async ({ page }) => { + await goToLandingPage(page); + }); + + test('The component Why learn with freeCodeCamp renders correctly', async ({ + context, + page + }) => { + await addGrowthbookCookie({ context, variation: 'C' }); + await goToLandingPage(page); + const h2Element = page.locator( + `h2:has-text("${translations.landing.benefits['heading']}")` + ); + + await expect(h2Element).toBeVisible(); + }); + + test('Call to action buttons should render correctly', async ({ page }) => { + const ctas = page.getByRole('link', { + name: translations.buttons['logged-in-cta-btn'] + }); + await expect(ctas).toHaveCount(4); + for (const cta of await ctas.all()) { + await expect(cta).toBeVisible(); + } + }); + test('Hero image should have an alt and a description', async ({ isMobile, page