From f1d4c66c9f5bbd614057964f3fd2f4c138459610 Mon Sep 17 00:00:00 2001 From: Dominik Lander Date: Thu, 5 Mar 2026 16:56:09 +0000 Subject: [PATCH 1/3] Slimify all sections and reduce font size in AB test --- ab-testing/config/abTests.ts | 6 +- .../src/components/CardHeadline.tsx | 23 ++- .../src/components/DecideContainer.tsx | 17 ++ .../components/FlexibleGeneral.stories.tsx | 12 ++ .../src/components/FlexibleGeneral.tsx | 163 ++++++++++++------ .../components/FlexibleSpecial.stories.tsx | 11 ++ .../src/components/FlexibleSpecial.tsx | 44 ++++- .../src/components/FrontSection.tsx | 89 +++++----- .../ScrollableFeature.importable.tsx | 15 +- .../components/ScrollableFeature.stories.tsx | 10 ++ .../ScrollableMedium.importable.tsx | 13 +- .../components/ScrollableMedium.stories.tsx | 11 ++ .../components/StaticMediumFour.stories.tsx | 12 ++ .../src/components/StaticMediumFour.tsx | 12 +- dotcom-rendering/src/layouts/FrontLayout.tsx | 54 ++++-- .../src/lib/SlimHomepageAbTestHelpers.ts | 31 ++++ 16 files changed, 396 insertions(+), 127 deletions(-) create mode 100644 dotcom-rendering/src/lib/SlimHomepageAbTestHelpers.ts diff --git a/ab-testing/config/abTests.ts b/ab-testing/config/abTests.ts index 8074f1eadf3..77b8be05d73 100644 --- a/ab-testing/config/abTests.ts +++ b/ab-testing/config/abTests.ts @@ -109,14 +109,14 @@ const ABTests: ABTest[] = [ { name: "fronts-and-curation-slim-homepage", description: - "Test placing the Most Viewed and Deeply Read components in the right-hand column on the homepage.", + "Test slimming content and placing Most Popular components on the right-hand side on the UK front.", owners: ["fronts.and.curation@guardian.co.uk"], status: "ON", - expirationDate: `2026-04-28`, + expirationDate: "2026-04-28", type: "server", audienceSize: 0 / 100, audienceSpace: "A", - groups: ["control", "variant"], + groups: ["control", "variant-one", "variant-two"], shouldForceMetricsCollection: false, }, { diff --git a/dotcom-rendering/src/components/CardHeadline.tsx b/dotcom-rendering/src/components/CardHeadline.tsx index 43786c45804..00c8d482cc8 100644 --- a/dotcom-rendering/src/components/CardHeadline.tsx +++ b/dotcom-rendering/src/components/CardHeadline.tsx @@ -1,6 +1,7 @@ import { css } from '@emotion/react'; import { between, + from, headlineMedium14, headlineMedium15, headlineMedium17, @@ -142,6 +143,7 @@ export enum FontFamily { export type HeadlineSize = keyof typeof fontFamilies.headlineMedium; export type ResponsiveFontSize = { + wide?: HeadlineSize; desktop: HeadlineSize; tablet?: HeadlineSize; mobile?: HeadlineSize; @@ -151,7 +153,7 @@ export type ResponsiveFontSize = { const getFontSize = (sizes: ResponsiveFontSize, family: FontFamily) => { const font = fontFamilies[family]; - const { desktop, tablet, mobileMedium, mobile } = sizes; + const { wide, desktop, tablet, mobileMedium, mobile } = sizes; return css` ${font[desktop]}; @@ -176,6 +178,13 @@ const getFontSize = (sizes: ResponsiveFontSize, family: FontFamily) => { ${font[mobileMedium]}; } `} + + ${wide && + css` + ${from.wide} { + ${font[wide]}; + } + `} `; }; @@ -204,6 +213,15 @@ export const WithLink = ({ return <>{children}; }; +/** + * headline medium 20 on desktop and headline medium 17 on tablet and mobile + */ +export const defaultFontSizes: ResponsiveFontSize = { + desktop: 'xsmall', + tablet: 'xxsmall', + mobile: 'xxsmall', +}; + export const CardHeadline = ({ headlineText, format, @@ -211,8 +229,7 @@ export const CardHeadline = ({ kickerText, showPulsingDot, hasInlineKicker, - /** headline medium 20 on desktop and headline medium 17 on tablet and mobile */ - fontSizes = { desktop: 'xsmall', tablet: 'xxsmall', mobile: 'xxsmall' }, + fontSizes = defaultFontSizes, byline, showByline, linkTo, diff --git a/dotcom-rendering/src/components/DecideContainer.tsx b/dotcom-rendering/src/components/DecideContainer.tsx index 5023f2e4474..2fa9b20109b 100644 --- a/dotcom-rendering/src/components/DecideContainer.tsx +++ b/dotcom-rendering/src/components/DecideContainer.tsx @@ -47,6 +47,7 @@ type Props = { frontId?: string; collectionId: number; containerLevel?: DCRContainerLevel; + isInSlimHomepageAbTestVariant?: boolean; }; export const DecideContainer = ({ @@ -62,6 +63,7 @@ export const DecideContainer = ({ frontId, collectionId, containerLevel, + isInSlimHomepageAbTestVariant = false, }: Props) => { switch (containerType) { case 'dynamic/fast': @@ -244,6 +246,9 @@ export const DecideContainer = ({ imageLoading={imageLoading} aspectRatio={aspectRatio} collectionId={collectionId} + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> ); case 'flexible/general': @@ -257,6 +262,9 @@ export const DecideContainer = ({ aspectRatio={aspectRatio} containerLevel={containerLevel} collectionId={collectionId} + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> ); case 'scrollable/small': @@ -284,6 +292,9 @@ export const DecideContainer = ({ serverTime={serverTime} aspectRatio={aspectRatio} sectionId={sectionId} + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> ); @@ -296,6 +307,9 @@ export const DecideContainer = ({ serverTime={serverTime} imageLoading={imageLoading} aspectRatio={aspectRatio} + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> ); case 'scrollable/feature': @@ -308,6 +322,9 @@ export const DecideContainer = ({ serverTime={serverTime} aspectRatio={aspectRatio} collectionId={collectionId} + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> ); diff --git a/dotcom-rendering/src/components/FlexibleGeneral.stories.tsx b/dotcom-rendering/src/components/FlexibleGeneral.stories.tsx index 68d2f463cde..81dc250856c 100644 --- a/dotcom-rendering/src/components/FlexibleGeneral.stories.tsx +++ b/dotcom-rendering/src/components/FlexibleGeneral.stories.tsx @@ -160,6 +160,7 @@ const meta = { imageLoading: 'eager', aspectRatio: '5:4', collectionId: 1, + isInSlimHomepageAbTestVariant: false, }, render: ({ frontSectionTitle, ...args }) => ( @@ -193,6 +197,14 @@ export const SplashWithStandards: Story = { }, }; +export const SplashWithStandardsInSlimHomepageAbTest: Story = { + name: 'Splash with big and standard cards in the Slim Homepage AB Test', + args: { + ...SplashWithStandards.args, + isInSlimHomepageAbTestVariant: true, + }, +}; + export const SplashWithSublinks: Story = { name: 'Standard splash with sublinks', args: { diff --git a/dotcom-rendering/src/components/FlexibleGeneral.tsx b/dotcom-rendering/src/components/FlexibleGeneral.tsx index 9b09cae20d8..c4fcca3d6f9 100644 --- a/dotcom-rendering/src/components/FlexibleGeneral.tsx +++ b/dotcom-rendering/src/components/FlexibleGeneral.tsx @@ -17,7 +17,7 @@ import type { } from './Card/components/MediaWrapper'; import type { TrailTextSize } from './Card/components/TrailText'; import { UL } from './Card/components/UL'; -import type { ResponsiveFontSize } from './CardHeadline'; +import { defaultFontSizes, type ResponsiveFontSize } from './CardHeadline'; import type { Loading } from './CardPicture'; import { FeatureCard } from './FeatureCard'; import { FrontCard } from './FrontCard'; @@ -35,6 +35,7 @@ type Props = { collectionId: number; /** Passed through to cards to enable tag page storyline section specific rendering */ isStorylines?: boolean; + isInSlimHomepageAbTestVariant?: boolean; }; type RowLayout = 'oneCardHalfWidth' | 'oneCardFullWidth' | 'twoCard'; @@ -90,6 +91,7 @@ type ImmersiveCardLayoutProps = { imageLoading: Loading; collectionId: number; isStorylines?: boolean; + isInSlimHomepageAbTestVariant?: boolean; }; /** @@ -105,47 +107,57 @@ const ImmersiveCardLayout = ({ imageLoading, collectionId, isStorylines, -}: ImmersiveCardLayoutProps) => ( - -); + isInSlimHomepageAbTestVariant, +}: ImmersiveCardLayoutProps) => { + const headlineSizes = { desktop: 'medium', tablet: 'small' } as const; + + return ( + + ); +}; type BoostedSplashProperties = { headlineSizes: ResponsiveFontSize; @@ -170,13 +182,18 @@ const decideSplashCardProperties = ( useLargerHeadlineSizeDesktop: boolean, avatarUrl: boolean, isStorylines?: boolean, + isInSlimHomepageAbTestVariant?: boolean, ): BoostedSplashProperties => { switch (boostLevel) { // The default boost level is equal to no boost. It is the same as the default card layout. case 'default': return { headlineSizes: { - desktop: useLargerHeadlineSizeDesktop ? 'large' : 'medium', + desktop: + useLargerHeadlineSizeDesktop && + !isInSlimHomepageAbTestVariant + ? 'large' + : 'medium', tablet: 'medium', mobile: 'medium', }, @@ -190,13 +207,13 @@ const decideSplashCardProperties = ( subtitleSize: 'medium', }; case 'boost': - const boostSupportingContentAlignment: Alignment = - isStorylines || supportingContentLength < 4 - ? 'vertical' - : 'horizontal'; - return { headlineSizes: { + wide: + !isInSlimHomepageAbTestVariant && + useLargerHeadlineSizeDesktop + ? 'xlarge' + : 'large', desktop: useLargerHeadlineSizeDesktop ? 'xlarge' : 'large', tablet: 'large', mobile: 'large', @@ -204,7 +221,10 @@ const decideSplashCardProperties = ( mediaPositionOnDesktop: 'right', mediaPositionOnMobile: mediaCard ? 'top' : 'bottom', mediaSize: avatarUrl ? 'large' : 'xlarge', - supportingContentAlignment: boostSupportingContentAlignment, + supportingContentAlignment: + isStorylines || supportingContentLength < 4 + ? 'vertical' + : 'horizontal', liveUpdatesAlignment: 'vertical', trailTextSize: 'regular', subtitleSize: 'medium', @@ -212,6 +232,11 @@ const decideSplashCardProperties = ( case 'megaboost': return { headlineSizes: { + wide: + !isInSlimHomepageAbTestVariant && + useLargerHeadlineSizeDesktop + ? 'xxlarge' + : 'xlarge', desktop: useLargerHeadlineSizeDesktop ? 'xxlarge' : 'xlarge', @@ -229,6 +254,11 @@ const decideSplashCardProperties = ( case 'gigaboost': return { headlineSizes: { + wide: isInSlimHomepageAbTestVariant + ? 'xlarge' + : useLargerHeadlineSizeDesktop + ? 'xxxlarge' + : 'xxlarge', desktop: useLargerHeadlineSizeDesktop ? 'xxxlarge' : 'xxlarge', @@ -257,6 +287,7 @@ type SplashCardLayoutProps = { containerLevel: DCRContainerLevel; collectionId: number; isStorylines?: boolean; + isInSlimHomepageAbTestVariant?: boolean; }; const SplashCardLayout = ({ @@ -270,6 +301,7 @@ const SplashCardLayout = ({ containerLevel, collectionId, isStorylines, + isInSlimHomepageAbTestVariant, }: SplashCardLayoutProps) => { const card = cards[0]; if (!card) return null; @@ -283,6 +315,7 @@ const SplashCardLayout = ({ serverTime={serverTime} imageLoading={imageLoading} collectionId={collectionId} + isInSlimHomepageAbTestVariant={isInSlimHomepageAbTestVariant} /> ); } @@ -309,6 +342,7 @@ const SplashCardLayout = ({ useLargerHeadlineSizeDesktop, !!card.avatarUrl, isStorylines, + isInSlimHomepageAbTestVariant, ); return ( @@ -355,7 +389,7 @@ const SplashCardLayout = ({ subtitleSize={subtitleSize} headlinePosition={card.showLivePlayable ? 'outer' : 'inner'} isStorylines={isStorylines} - starRatingSize={'medium'} + starRatingSize="medium" /> @@ -368,6 +402,7 @@ type BoostedCardProperties = { liveUpdatesPosition: Position; supportingContentAlignment: Alignment; subtitleSize: SubtitleSize; + isInSlimHomepageAbTestVariant?: boolean; }; /** @@ -378,11 +413,13 @@ const decideCardProperties = ( supportingContentLength: number, boostLevel: Omit = 'boost', avatarUrl?: string, + isInSlimHomepageAbTestVariant = false, ): BoostedCardProperties => { switch (boostLevel) { case 'megaboost': return { headlineSizes: { + wide: isInSlimHomepageAbTestVariant ? 'small' : 'medium', desktop: 'medium', tablet: 'small', mobile: 'medium', @@ -422,6 +459,7 @@ type FullWidthCardLayoutProps = { containerLevel: DCRContainerLevel; collectionId: number; isStorylines?: boolean; + isInSlimHomepageAbTestVariant?: boolean; }; const FullWidthCardLayout = ({ @@ -436,6 +474,7 @@ const FullWidthCardLayout = ({ containerLevel, collectionId, isStorylines, + isInSlimHomepageAbTestVariant, }: FullWidthCardLayoutProps) => { const card = cards[0]; if (!card) return null; @@ -450,6 +489,7 @@ const FullWidthCardLayout = ({ card.supportingContent?.length ?? 0, card.boostLevel, card.avatarUrl, + isInSlimHomepageAbTestVariant, ); const shouldShowImmersive = card.isImmersive; @@ -512,7 +552,7 @@ const FullWidthCardLayout = ({ showKickerImage={card.format.design === ArticleDesign.Audio} subtitleSize={subtitleSize} isStorylines={isStorylines} - starRatingSize={'medium'} + starRatingSize="medium" /> @@ -531,6 +571,7 @@ type HalfWidthCardLayoutProps = { isLastRow: boolean; containerLevel: DCRContainerLevel; isStorylines?: boolean; + isInSlimHomepageAbTestVariant?: boolean; }; const HalfWidthCardLayout = ({ @@ -545,6 +586,7 @@ const HalfWidthCardLayout = ({ isLastRow, containerLevel, isStorylines, + isInSlimHomepageAbTestVariant, }: HalfWidthCardLayoutProps) => { if (cards.length === 0) return null; @@ -597,7 +639,14 @@ const HalfWidthCardLayout = ({ (containerLevel !== 'Primary' && cardIndex > 0) } trailText={undefined} - headlineSizes={undefined} + headlineSizes={ + isInSlimHomepageAbTestVariant + ? { + ...defaultFontSizes, + wide: defaultFontSizes.tablet, + } + : defaultFontSizes + } canPlayInline={false} isStorylines={isStorylines} /> @@ -618,6 +667,7 @@ export const FlexibleGeneral = ({ containerLevel = 'Primary', collectionId, isStorylines = false, + isInSlimHomepageAbTestVariant = false, }: Props) => { const splash = [...groupedTrails.splash].slice(0, 1).map((snap) => ({ ...snap, @@ -647,6 +697,9 @@ export const FlexibleGeneral = ({ containerLevel={containerLevel} collectionId={collectionId} isStorylines={isStorylines} + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> )} {groupedCards.map((row, i) => { @@ -666,6 +719,9 @@ export const FlexibleGeneral = ({ containerLevel={containerLevel} collectionId={collectionId} isStorylines={isStorylines} + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> ); @@ -686,6 +742,9 @@ export const FlexibleGeneral = ({ isLastRow={i === groupedCards.length - 1} containerLevel={containerLevel} isStorylines={isStorylines} + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> ); } diff --git a/dotcom-rendering/src/components/FlexibleSpecial.stories.tsx b/dotcom-rendering/src/components/FlexibleSpecial.stories.tsx index b69daa232d7..44530a868ef 100644 --- a/dotcom-rendering/src/components/FlexibleSpecial.stories.tsx +++ b/dotcom-rendering/src/components/FlexibleSpecial.stories.tsx @@ -88,6 +88,7 @@ const meta = { imageLoading: 'eager', aspectRatio: '5:4', frontSectionTitle: 'Flexible special', + isInSlimHomepageAbTestVariant: false, }, render: ({ frontSectionTitle, ...args }) => ( @@ -160,6 +164,13 @@ export const Five: Story = { collectionId: 1, }, }; +export const FiveSlimHomepageAbTest: Story = { + name: 'With one splash card and four standard cards in the Slim Homepage AB Test', + args: { + ...Five.args, + isInSlimHomepageAbTestVariant: true, + }, +}; export const OpinionStandardCards: Story = { name: 'With one splash card and two standard opinion cards', diff --git a/dotcom-rendering/src/components/FlexibleSpecial.tsx b/dotcom-rendering/src/components/FlexibleSpecial.tsx index c8be889350e..adc431514ed 100644 --- a/dotcom-rendering/src/components/FlexibleSpecial.tsx +++ b/dotcom-rendering/src/components/FlexibleSpecial.tsx @@ -16,7 +16,7 @@ import type { } from './Card/components/MediaWrapper'; import type { TrailTextSize } from './Card/components/TrailText'; import { UL } from './Card/components/UL'; -import type { ResponsiveFontSize } from './CardHeadline'; +import { defaultFontSizes, type ResponsiveFontSize } from './CardHeadline'; import type { Loading } from './CardPicture'; import { FrontCard } from './FrontCard'; import type { SubtitleSize } from './SelfHostedVideoPlayer'; @@ -31,6 +31,7 @@ type Props = { aspectRatio: AspectRatio; containerLevel?: DCRContainerLevel; collectionId: number; + isInSlimHomepageAbTestVariant?: boolean; }; type BoostProperties = { @@ -54,6 +55,7 @@ const determineCardProperties = ( mediaCard: boolean, imageSuppressed: boolean, hasLiveUpdates: boolean, + isInSlimHomepageAbTestVariant: boolean, ): BoostProperties => { const shouldDisplaySublinksHorizontally = supportingContentLength >= 3 || hasLiveUpdates; @@ -63,6 +65,11 @@ const determineCardProperties = ( case 'default': return { headlineSizes: { + wide: isInSlimHomepageAbTestVariant + ? 'large' + : imageSuppressed + ? 'xxlarge' + : 'xlarge', desktop: imageSuppressed ? 'xxlarge' : 'xlarge', tablet: 'large', mobile: 'medium', @@ -80,6 +87,11 @@ const determineCardProperties = ( case 'boost': return { headlineSizes: { + wide: isInSlimHomepageAbTestVariant + ? 'xlarge' + : imageSuppressed + ? 'xxxlarge' + : 'xxlarge', desktop: imageSuppressed ? 'xxxlarge' : 'xxlarge', tablet: 'xlarge', mobile: 'large', @@ -97,6 +109,11 @@ const determineCardProperties = ( case 'megaboost': return { headlineSizes: { + wide: isInSlimHomepageAbTestVariant + ? 'xlarge' + : imageSuppressed + ? 'xxxlarge' + : 'xxlarge', desktop: imageSuppressed ? 'xxxlarge' : 'xxlarge', tablet: 'xlarge', mobile: 'xlarge', @@ -112,6 +129,9 @@ const determineCardProperties = ( case 'gigaboost': return { headlineSizes: { + wide: isInSlimHomepageAbTestVariant + ? 'xxlarge' + : 'xxxlarge', desktop: 'xxxlarge', tablet: 'xxlarge', mobile: 'xxlarge', @@ -138,6 +158,7 @@ type OneCardLayoutProps = { isFirstRow: boolean; containerLevel: DCRContainerLevel; isSplashCard?: boolean; + isInSlimHomepageAbTestVariant?: boolean; }; export const OneCardLayout = ({ @@ -151,6 +172,7 @@ export const OneCardLayout = ({ isFirstRow, containerLevel, isSplashCard, + isInSlimHomepageAbTestVariant = false, }: OneCardLayoutProps) => { const card = cards[0]; if (!card) return null; @@ -170,6 +192,7 @@ export const OneCardLayout = ({ isMediaCard(card.format), !card.image, card.showLivePlayable, + isInSlimHomepageAbTestVariant, ); return ( @@ -233,6 +256,7 @@ type TwoOrFourCardLayoutProps = { aspectRatio: AspectRatio; isFirstRow: boolean; containerLevel: DCRContainerLevel; + isInSlimHomepageAbTestVariant?: boolean; }; const TwoOrFourCardLayout = ({ @@ -245,6 +269,7 @@ const TwoOrFourCardLayout = ({ aspectRatio, isFirstRow, containerLevel, + isInSlimHomepageAbTestVariant, }: TwoOrFourCardLayoutProps) => { if (cards.length === 0) return null; const hasTwoOrFewerCards = cards.length <= 2; @@ -273,7 +298,14 @@ const TwoOrFourCardLayout = ({ isMediaCard(card.format) || !!card.isNewsletter, )} mediaPositionOnMobile="left" - headlineSizes={undefined} + headlineSizes={ + isInSlimHomepageAbTestVariant + ? { + ...defaultFontSizes, + wide: defaultFontSizes.tablet, + } + : defaultFontSizes + } /* we don't want to support sublinks on standard cards here so we hard code to undefined */ supportingContent={undefined} mediaSize="small" @@ -304,6 +336,7 @@ export const FlexibleSpecial = ({ aspectRatio, containerLevel = 'Primary', collectionId, + isInSlimHomepageAbTestVariant = false, }: Props) => { const snaps = [...groupedTrails.snap].slice(0, 1).map((snap) => ({ ...snap, @@ -332,6 +365,9 @@ export const FlexibleSpecial = ({ isLastRow={splash.length === 0 && cards.length === 0} containerLevel={containerLevel} isSplashCard={false} + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> )} {isNonEmptyArray(splash) && ( @@ -346,6 +382,9 @@ export const FlexibleSpecial = ({ isFirstRow={!isNonEmptyArray(snaps)} containerLevel={containerLevel} isSplashCard={true} + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> )} @@ -358,6 +397,7 @@ export const FlexibleSpecial = ({ aspectRatio={aspectRatio} isFirstRow={!isNonEmptyArray(snaps) && !isNonEmptyArray(splash)} containerLevel={containerLevel} + isInSlimHomepageAbTestVariant={isInSlimHomepageAbTestVariant} /> ); diff --git a/dotcom-rendering/src/components/FrontSection.tsx b/dotcom-rendering/src/components/FrontSection.tsx index fcea812ff74..0b8ad72763a 100644 --- a/dotcom-rendering/src/components/FrontSection.tsx +++ b/dotcom-rendering/src/components/FrontSection.tsx @@ -97,7 +97,8 @@ type Props = { * The Slim Homepage AB test requires some sections to have reduced width so that * the Most Popular Front Right component can be placed on the right-hand side. */ - slimifySectionForAbTest?: boolean; + slimifySectionForSlimHomepageAbTest?: boolean; + showRightContentForSlimHomepageAbTest?: boolean; discussionApiUrl: string; collectionBranding?: CollectionBranding; isTagPage?: boolean; @@ -650,7 +651,8 @@ export const FrontSection = ({ isOnPaidContentFront, targetedTerritory, hasPageSkin = false, - slimifySectionForAbTest = false, + slimifySectionForSlimHomepageAbTest = false, + showRightContentForSlimHomepageAbTest = false, discussionApiUrl, collectionBranding, isTagPage = false, @@ -724,10 +726,8 @@ export const FrontSection = ({ title, showSectionColours, ), - // To reduce the width of the border line between Features - // and More features in the Slim Homepage AB test. - slimifySectionForAbTest && - sectionId === 'more-features' && + slimifySectionForSlimHomepageAbTest && + containerLevel === 'Secondary' && css` ${from.wide} { grid-column: 2 / 14; @@ -827,14 +827,15 @@ export const FrontSection = ({
- {slimifySectionForAbTest && sectionId === 'news' && ( -
- - - -
- )} + {showRightContentForSlimHomepageAbTest && + sectionId === 'news' && ( +
+ + + +
+ )} - {slimifySectionForAbTest && sectionId === 'features' && ( -
- - - -
- )} + {showRightContentForSlimHomepageAbTest && + sectionId === 'features' && ( +
+ + + +
+ )}
{ const isBelowTabletBreakpoint = useMatchMedia( removeMediaRulePrefix(until.tablet), ); + const headlineSizes = { + desktop: 'xsmall', + tablet: 'xxsmall', + mobile: 'xsmall', + } as const; + return ( ( @@ -100,6 +104,12 @@ type Story = StoryObj; export const Default = {}; +export const WithSlimHomepageAbTest = { + args: { + isInSlimHomepageAbTestVariant: true, + }, +} satisfies Story; + export const Media = { args: { trails: [galleryTrails[0], galleryTrails[1], audioTrails[0]], diff --git a/dotcom-rendering/src/components/ScrollableMedium.importable.tsx b/dotcom-rendering/src/components/ScrollableMedium.importable.tsx index 8946eb185f0..e26547e360c 100644 --- a/dotcom-rendering/src/components/ScrollableMedium.importable.tsx +++ b/dotcom-rendering/src/components/ScrollableMedium.importable.tsx @@ -17,6 +17,7 @@ type Props = { imageLoading: 'lazy' | 'eager'; aspectRatio: AspectRatio; sectionId: string; + isInSlimHomepageAbTestVariant?: boolean; }; /** @@ -34,6 +35,7 @@ export const ScrollableMedium = ({ showAge, aspectRatio, sectionId, + isInSlimHomepageAbTestVariant, }: Props) => { const isBelowTabletBreakpoint = useMatchMedia( removeMediaRulePrefix(until.tablet), @@ -52,6 +54,11 @@ export const ScrollableMedium = ({ ? 'top' : 'bottom'; + const headlineSizes = { + desktop: 'xsmall', + tablet: 'xxsmall', + } as const; + return ( ( @@ -55,6 +59,13 @@ export const WithFourCards = { }, } satisfies Story; +export const WithFourCardsInSlimHomepageAbTest = { + args: { + ...WithFourCards.args, + isInSlimHomepageAbTestVariant: true, + }, +} satisfies Story; + export const WithThreeCards = { args: { trails: trails.slice(0, 3), diff --git a/dotcom-rendering/src/components/StaticMediumFour.stories.tsx b/dotcom-rendering/src/components/StaticMediumFour.stories.tsx index da2d2636497..78beb99d01b 100644 --- a/dotcom-rendering/src/components/StaticMediumFour.stories.tsx +++ b/dotcom-rendering/src/components/StaticMediumFour.stories.tsx @@ -29,6 +29,7 @@ const meta = { showAge: true, imageLoading: 'eager', aspectRatio: '5:4', + isInSlimHomepageAbTestVariant: false, }, render: (args) => ( @@ -53,6 +57,14 @@ export const Four = { }, }; +export const FourSlimHomepageAbTest = { + name: 'With Four Cards in Slim Homepage AB Test', + args: { + trails: trails.slice(0, 4), + isInSlimHomepageAbTestVariant: true, + }, +}; + export const Three: Story = { name: 'With Three Cards', args: { diff --git a/dotcom-rendering/src/components/StaticMediumFour.tsx b/dotcom-rendering/src/components/StaticMediumFour.tsx index 470f607208d..600ec3b9696 100644 --- a/dotcom-rendering/src/components/StaticMediumFour.tsx +++ b/dotcom-rendering/src/components/StaticMediumFour.tsx @@ -9,6 +9,7 @@ import type { import { LI } from './Card/components/LI'; import type { MediaPositionType } from './Card/components/MediaWrapper'; import { UL } from './Card/components/UL'; +import { defaultFontSizes } from './CardHeadline'; import type { Loading } from './CardPicture'; import { FrontCard } from './FrontCard'; @@ -32,6 +33,7 @@ type Props = { showImage?: boolean; aspectRatio: AspectRatio; containerLevel?: DCRContainerLevel; + isInSlimHomepageAbTestVariant?: boolean; }; export const StaticMediumFour = ({ @@ -43,6 +45,7 @@ export const StaticMediumFour = ({ showImage = true, aspectRatio, containerLevel = 'Primary', + isInSlimHomepageAbTestVariant = false, }: Props) => { const cards = trails.slice(0, 4); @@ -70,7 +73,14 @@ export const StaticMediumFour = ({ !!card.isNewsletter, )} mediaPositionOnMobile="left" - headlineSizes={undefined} + headlineSizes={ + isInSlimHomepageAbTestVariant + ? { + ...defaultFontSizes, + wide: defaultFontSizes.tablet, + } + : defaultFontSizes + } /* we don't want to support sublinks on standard cards here so we hard code to undefined */ supportingContent={undefined} mediaSize="medium" diff --git a/dotcom-rendering/src/layouts/FrontLayout.tsx b/dotcom-rendering/src/layouts/FrontLayout.tsx index 7325e7e5bf1..fb8d851f238 100644 --- a/dotcom-rendering/src/layouts/FrontLayout.tsx +++ b/dotcom-rendering/src/layouts/FrontLayout.tsx @@ -40,6 +40,7 @@ import { } from '../lib/getFrontsAdPositions'; import { hideAge } from '../lib/hideAge'; import { ophanComponentId } from '../lib/ophan-helpers'; +import { doesPageQualifyForSlimHomepageAbTest } from '../lib/SlimHomepageAbTestHelpers'; import { useBetaAB } from '../lib/useAB'; import type { NavType } from '../model/extract-nav'; import { palette as schemePalette } from '../palette'; @@ -82,15 +83,16 @@ const isToggleable = ( * The show/hide button would be covered by the MostPopularFrontRight component * in the variant of the Slim Homepage AB test. */ - isTargetedContainerInSlimHomepageAbTest: boolean, + isShowingRightContentForSlimHomepageAbTest: boolean, ) => { + if (isShowingRightContentForSlimHomepageAbTest) return; + if (isNetworkFront) { return ( collection.displayName.toLowerCase() !== 'headlines' && !isNavList(collection) && !isHighlights(collection) && - !isLabs(collection) && - !isTargetedContainerInSlimHomepageAbTest + !isLabs(collection) ); } @@ -152,18 +154,33 @@ export const FrontLayout = ({ front, NAV }: Props) => { /** * The Slim Homepage AB test only runs on /uk and on screen widths >=1300px. - * In the variant of this test a Most Popular component is added to the right-hand side of the page. - * Page skins require slim content and is incompatible with this test. We do not run this test - * on pages where there is a page skin (a page skin takes precedence). + * In variant one and two of this test, the content is slimmed down. + * In variant two of this test, a Most Popular component is inserted into the right-hand side of the page. + * Page skins require slim content and is incompatible with this test, so we do not run + * this test on pages where there is a page skin (a page skin takes precedence). */ - const isInSlimHomepageAbTestVariant = - (pageId === 'uk' && - !hasPageSkin && + const pageQualifiesForSlimHomepageAbTest = + doesPageQualifyForSlimHomepageAbTest( + front.pressedPage.collections, + pageId, + hasPageSkin, + ); + const isInSlimHomepageAbTestVariantOne = + (pageQualifiesForSlimHomepageAbTest && abTests?.isUserInTestGroup( 'fronts-and-curation-slim-homepage', - 'variant', + 'variant-one', )) ?? false; + const isInSlimHomepageAbTestVariantTwo = + (pageQualifiesForSlimHomepageAbTest && + abTests?.isUserInTestGroup( + 'fronts-and-curation-slim-homepage', + 'variant-two', + )) ?? + false; + const isInEitherSlimHomepageAbTestVariant = + isInSlimHomepageAbTestVariantOne || isInSlimHomepageAbTestVariantTwo; const fallbackAspectRatio = (collectionType: DCRContainerType) => { switch (collectionType) { @@ -309,11 +326,6 @@ export const FrontLayout = ({ front, NAV }: Props) => { * We only shrink the content of certain sections to place the Most Popular * content on the right-hand side. Other sections remain full-width. */ - const isTargetedContainerInSlimHomepageAbTest = - isInSlimHomepageAbTestVariant && - (collection.displayName === 'News' || - collection.displayName === 'Features' || - collection.displayName === 'More features'); if (collection.collectionType === 'scrollable/highlights') { // Highlights are rendered in the Masthead component @@ -494,7 +506,7 @@ export const FrontLayout = ({ front, NAV }: Props) => { index, collection, front.isNetworkFront, - isTargetedContainerInSlimHomepageAbTest, + isInSlimHomepageAbTestVariantTwo, )} leftContent={decideLeftContent( front, @@ -534,8 +546,11 @@ export const FrontLayout = ({ front, NAV }: Props) => { index, )} isLabs={isLabs(collection)} - slimifySectionForAbTest={ - isTargetedContainerInSlimHomepageAbTest + slimifySectionForSlimHomepageAbTest={ + isInEitherSlimHomepageAbTestVariant + } + showRightContentForSlimHomepageAbTest={ + isInSlimHomepageAbTestVariantTwo } mostViewed={front.mostViewed} deeplyRead={front.deeplyRead} @@ -563,6 +578,9 @@ export const FrontLayout = ({ front, NAV }: Props) => { sectionId={ophanName} collectionId={index + 1} containerLevel={collection.containerLevel} + isInSlimHomepageAbTestVariant={ + isInEitherSlimHomepageAbTestVariant + } /> diff --git a/dotcom-rendering/src/lib/SlimHomepageAbTestHelpers.ts b/dotcom-rendering/src/lib/SlimHomepageAbTestHelpers.ts new file mode 100644 index 00000000000..9b057e6a2de --- /dev/null +++ b/dotcom-rendering/src/lib/SlimHomepageAbTestHelpers.ts @@ -0,0 +1,31 @@ +import type { DCRCollectionType } from '../types/front'; + +const requiredCollectionsForTest = ['news', 'features', 'more features']; + +const hasRequiredSlimHomepageAbTestCollections = ( + collections: DCRCollectionType[], +): boolean => { + return requiredCollectionsForTest.every((displayName) => + collections + .map((collection) => collection.displayName.toLowerCase()) + .includes(displayName), + ); +}; + +export const doesPageQualifyForSlimHomepageAbTest = ( + collections: DCRCollectionType[], + pageId: string, + hasPageSkin: boolean, +): boolean => { + const isFirstCollectionNews = + collections[0]?.displayName.toLowerCase() === 'news'; + const hasRequiredCollections = + hasRequiredSlimHomepageAbTestCollections(collections); + + return ( + pageId === 'uk' && + !hasPageSkin && + isFirstCollectionNews && + hasRequiredCollections + ); +}; From b84c8b8a325bdb4182e89226c8157a32221dc420 Mon Sep 17 00:00:00 2001 From: Dominik Lander Date: Thu, 5 Mar 2026 16:59:01 +0000 Subject: [PATCH 2/3] Temporarily comment out an AB test requirement for testing --- .../src/lib/SlimHomepageAbTestHelpers.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/dotcom-rendering/src/lib/SlimHomepageAbTestHelpers.ts b/dotcom-rendering/src/lib/SlimHomepageAbTestHelpers.ts index 9b057e6a2de..020f416dd05 100644 --- a/dotcom-rendering/src/lib/SlimHomepageAbTestHelpers.ts +++ b/dotcom-rendering/src/lib/SlimHomepageAbTestHelpers.ts @@ -17,15 +17,21 @@ export const doesPageQualifyForSlimHomepageAbTest = ( pageId: string, hasPageSkin: boolean, ): boolean => { - const isFirstCollectionNews = - collections[0]?.displayName.toLowerCase() === 'news'; + /** + * This is temporarily commmented out so that we can review this in a 0% test. + * There is currently a container above News on the UK front, which means the test won't run. + * We will only start this test once News is again the top container, but in the meantime, we + * would like to be able to review this test. + */ + // const isFirstCollectionNews = + // collections[0]?.displayName.toLowerCase() === 'news'; const hasRequiredCollections = hasRequiredSlimHomepageAbTestCollections(collections); return ( pageId === 'uk' && !hasPageSkin && - isFirstCollectionNews && + // isFirstCollectionNews && hasRequiredCollections ); }; From 8f2ad68d57d87b57c06dfa2cd649add90db3b5f0 Mon Sep 17 00:00:00 2001 From: Dominik Lander Date: Fri, 6 Mar 2026 11:28:31 +0000 Subject: [PATCH 3/3] Put sublinks in correct place for AB test --- dotcom-rendering/src/components/Card/Card.tsx | 22 ++++++++++++++++++- .../src/components/FlexibleGeneral.tsx | 9 ++++++++ .../src/components/FlexibleSpecial.tsx | 6 +++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/dotcom-rendering/src/components/Card/Card.tsx b/dotcom-rendering/src/components/Card/Card.tsx index 87b52c2ee8a..ae056cf6e9b 100644 --- a/dotcom-rendering/src/components/Card/Card.tsx +++ b/dotcom-rendering/src/components/Card/Card.tsx @@ -167,6 +167,7 @@ export type Props = { headlinePosition?: 'inner' | 'outer'; isStorylines?: boolean; starRatingSize?: RatingSizeType; + isInSlimHomepageAbTestVariant?: boolean; }; const waveformWrapper = ( @@ -406,6 +407,7 @@ export const Card = ({ isStorylines = false, starRatingSize = 'small', articleMedia, + isInSlimHomepageAbTestVariant, }: Props) => { const hasSublinks = supportingContent && supportingContent.length > 0; const sublinkPosition = decideSublinkPosition( @@ -701,6 +703,14 @@ export const Card = ({ return ; } + if (isInSlimHomepageAbTestVariant) { + return ( + + + + ); + } + return ( @@ -712,7 +722,7 @@ export const Card = ({ if (!hasSublinks) return null; if (sublinkPosition !== 'inner') return null; - return ( + const Sublinks = () => ( {isStorylines ? ( ); + + if (isInSlimHomepageAbTestVariant) { + return ( + + + + ); + } + + return ; }; const determinePadContent = ( diff --git a/dotcom-rendering/src/components/FlexibleGeneral.tsx b/dotcom-rendering/src/components/FlexibleGeneral.tsx index c4fcca3d6f9..2b292be87be 100644 --- a/dotcom-rendering/src/components/FlexibleGeneral.tsx +++ b/dotcom-rendering/src/components/FlexibleGeneral.tsx @@ -390,6 +390,9 @@ const SplashCardLayout = ({ headlinePosition={card.showLivePlayable ? 'outer' : 'inner'} isStorylines={isStorylines} starRatingSize="medium" + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> @@ -553,6 +556,9 @@ const FullWidthCardLayout = ({ subtitleSize={subtitleSize} isStorylines={isStorylines} starRatingSize="medium" + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> @@ -649,6 +655,9 @@ const HalfWidthCardLayout = ({ } canPlayInline={false} isStorylines={isStorylines} + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> ); diff --git a/dotcom-rendering/src/components/FlexibleSpecial.tsx b/dotcom-rendering/src/components/FlexibleSpecial.tsx index adc431514ed..0841527cc4f 100644 --- a/dotcom-rendering/src/components/FlexibleSpecial.tsx +++ b/dotcom-rendering/src/components/FlexibleSpecial.tsx @@ -229,6 +229,9 @@ export const OneCardLayout = ({ headlinePosition={isSplashCard ? 'outer' : 'inner'} subtitleSize={subtitleSize} starRatingSize="medium" + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> @@ -319,6 +322,9 @@ const TwoOrFourCardLayout = ({ !isMediaCard(card.format)) } canPlayInline={false} + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> );