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