Repository: guardian/dotcom-rendering
Pull request #13935: Use choiceCardsSettings in epic and banner
Author: login: tomrf1
Labels: name: dotcom-rendering
name: Seen-on-PROD
Created: 2025-05-13T08:43:18Z | Merged: 2025-05-20T08:49:12Z

The choice cards on the epic and banner display product information, and alter the CTA url based on the user's selection.

Until now the config for these choice cards has been hardcoded into DCR. This makes them inflexible and means developers need to make changes to DCR when MRR want to change the config.
We're now working on tooling this config instead.

The first part of this project is to move the config to the server (support-dotcom-components/SDC).
The server already returns a `choiceCardsSettings` field with the necessary config.
PR for epic: https://github.com/guardian/support-dotcom-components/pull/1349
PR for banner: https://github.com/guardian/support-dotcom-components/pull/1354

This PR updates the choice cards component to use the new model that is returned by SDC.
This involves removing some logic around setting amounts, currency, etc, since this is now driven by the server. It also removes some custom logic for an old promo.

Note - both epic + banner still have the legacy `choiceCardAmounts` field, which will be removed once we've completed the migration to the "tiered" choice cards.

## Screenshots:

Epic in UK + US:
<img width="562" alt="Screenshot 2025-05-14 at 09 43 38" src="https://github.com/user-attachments/assets/1b8d5039-a48b-474e-a3a7-f443c07733ad" />
<img width="562" alt="Screenshot 2025-05-14 at 09 44 03" src="https://github.com/user-attachments/assets/2bbbf4d1-d97b-4b7f-99e3-a0f23ac9cefd" />
<img width="322" alt="Screenshot 2025-05-14 at 09 44 31" src="https://github.com/user-attachments/assets/31788bfd-da1b-4a65-a5da-a389e0c069ee" />
<img width="322" alt="Screenshot 2025-05-14 at 09 44 45" src="https://github.com/user-attachments/assets/7ca8bdf2-8944-46b8-8cfb-7debfe006706" />

Banner in UK + US:

<img width="322" alt="Screenshot 2025-05-14 at 09 45 47" src="https://github.com/user-attachments/assets/4703f7b8-9ee4-4a99-8b14-408448d3bc44" />
<img width="322" alt="Screenshot 2025-05-14 at 09 45 54" src="https://github.com/user-attachments/assets/e63fa20f-9108-4d51-b4c3-a4e9fe406227" />
<img width="458" alt="Screenshot 2025-05-14 at 09 46 03" src="https://github.com/user-attachments/assets/bf5de046-9230-4bab-9fab-fd1af209641f" />
<img width="458" alt="Screenshot 2025-05-14 at 09 46 18" src="https://github.com/user-attachments/assets/7fae6ae9-e021-4aa4-81e0-ee2c7e957c6e" />




Diff:
diff --git a/dotcom-rendering/package.json b/dotcom-rendering/package.json
index 2e4ecf84238..41c94ad980b 100644
--- a/dotcom-rendering/package.json
+++ b/dotcom-rendering/package.json
@@ -43,7 +43,7 @@
 		"@guardian/shimport": "1.0.2",
 		"@guardian/source": "9.0.0",
 		"@guardian/source-development-kitchen": "18.1.0",
-		"@guardian/support-dotcom-components": "7.0.1",
+		"@guardian/support-dotcom-components": "7.2.0",
 		"@guardian/tsconfig": "0.2.0",
 		"@playwright/test": "1.45.3",
 		"@sentry/browser": "7.75.1",
diff --git a/dotcom-rendering/src/components/marketing/banners/common/BannerWrapper.tsx b/dotcom-rendering/src/components/marketing/banners/common/BannerWrapper.tsx
index 7f877780411..0802c66217f 100644
--- a/dotcom-rendering/src/components/marketing/banners/common/BannerWrapper.tsx
+++ b/dotcom-rendering/src/components/marketing/banners/common/BannerWrapper.tsx
@@ -87,6 +87,7 @@ const withBannerData =
 			separateArticleCount,
 			separateArticleCountSettings,
 			choiceCardAmounts,
+			choiceCardsSettings,
 			design,
 			bannerChannel,
 			abandonedBasket,
@@ -326,6 +327,7 @@ const withBannerData =
 					separateArticleCount,
 					separateArticleCountSettings,
 					choiceCardAmounts,
+					choiceCardsSettings,
 					tracking,
 					submitComponentEvent,
 					design,
diff --git a/dotcom-rendering/src/components/marketing/banners/common/types.tsx b/dotcom-rendering/src/components/marketing/banners/common/types.tsx
index 0c618bf153d..39dc04c69e1 100644
--- a/dotcom-rendering/src/components/marketing/banners/common/types.tsx
+++ b/dotcom-rendering/src/components/marketing/banners/common/types.tsx
@@ -14,6 +14,7 @@ import type {
 	SeparateArticleCount,
 	TickerSettings,
 } from '@guardian/support-dotcom-components/dist/shared/types';
+import type { ChoiceCardsSettings } from '@guardian/support-dotcom-components/dist/shared/types/props/choiceCards';
 import type { Tracking } from '@guardian/support-dotcom-components/dist/shared/types/props/shared';
 
 export type BannerId = 'designable-banner' | 'sign-in-prompt-banner';
@@ -73,6 +74,7 @@ export interface BannerRenderProps {
 	separateArticleCount?: boolean;
 	separateArticleCountSettings?: SeparateArticleCount;
 	choiceCardAmounts?: SelectedAmountsVariant;
+	choiceCardsSettings?: ChoiceCardsSettings;
 	tracking: Tracking;
 	submitComponentEvent?: (componentEvent: ComponentEvent) => void;
 	design?: ConfigurableDesign;
diff --git a/dotcom-rendering/src/components/marketing/banners/designableBanner/DesignableBannerV2.tsx b/dotcom-rendering/src/components/marketing/banners/designableBanner/DesignableBannerV2.tsx
index 25ea0fba3ef..93d4b4c710a 100644
--- a/dotcom-rendering/src/components/marketing/banners/designableBanner/DesignableBannerV2.tsx
+++ b/dotcom-rendering/src/components/marketing/banners/designableBanner/DesignableBannerV2.tsx
@@ -24,14 +24,14 @@ import type {
 	Image,
 	Tracking,
 } from '@guardian/support-dotcom-components/dist/shared/types';
+import type { ChoiceCard } from '@guardian/support-dotcom-components/dist/shared/types/props/choiceCards';
 import { useEffect, useState } from 'react';
 import {
 	removeMediaRulePrefix,
 	useMatchMedia,
 } from '../../../../lib/useMatchMedia';
 import { ThreeTierChoiceCards } from '../../epics/ThreeTierChoiceCards';
-import type { SupportTier } from '../../epics/utils/threeTierChoiceCardAmounts';
-import { getChoiceCardData } from '../../lib/choiceCards';
+import { getChoiceCards } from '../../lib/choiceCards';
 import type { ReactComponent } from '../../lib/ReactComponent';
 import {
 	addChoiceCardsProductParams,
@@ -116,14 +116,18 @@ const buildChoiceCardSettings = (
 
 const buildUrlForThreeTierChoiceCards = (
 	tracking: Tracking,
-	selectedProduct: SupportTier,
+	selectedProduct: ChoiceCard['product'],
 	countryCode?: string,
 ) => {
 	const baseUrl = 'https://support.theguardian.com/contribute';
 	const urlWithProduct =
-		selectedProduct === 'OneOff'
+		selectedProduct.supportTier === 'OneOff'
 			? baseUrl
-			: addChoiceCardsProductParams(baseUrl, selectedProduct, 'Monthly');
+			: addChoiceCardsProductParams(
+					baseUrl,
+					selectedProduct.supportTier,
+					selectedProduct.ratePlan,
+			  );
 
 	return addRegionIdAndTrackingParamsToSupportUrl(
 		urlWithProduct,
@@ -144,7 +148,7 @@ const DesignableBannerV2: ReactComponent<BannerRenderProps> = ({
 	separateArticleCount, // legacy field
 	separateArticleCountSettings,
 	tickerSettings,
-	choiceCardAmounts,
+	choiceCardsSettings,
 	countryCode,
 	submitComponentEvent,
 	design,
@@ -175,13 +179,12 @@ const DesignableBannerV2: ReactComponent<BannerRenderProps> = ({
 		}
 	}, [iosAppBannerPresent, submitComponentEvent]);
 
-	/**
-	 * V2 choice cards state
-	 */
+	const choiceCards = getChoiceCards(isTabletOrAbove, choiceCardsSettings);
+	const defaultProduct = choiceCards?.find((cc) => cc.isDefault)?.product;
 	const [
 		threeTierChoiceCardSelectedProduct,
 		setThreeTierChoiceCardSelectedProduct,
-	] = useState<SupportTier>('SupporterPlus');
+	] = useState<ChoiceCard['product'] | undefined>(defaultProduct);
 
 	// We can't render anything without a design
 	if (!design) {
@@ -294,15 +297,6 @@ const DesignableBannerV2: ReactComponent<BannerRenderProps> = ({
 		? content.mainContent
 		: content.mobileContent;
 
-	const isVatCompliantCountry =
-		choiceCardAmounts?.testName !== 'VAT_COMPLIANCE';
-
-	const showChoiceCards = !!(
-		templateSettings.choiceCardSettings &&
-		choiceCardAmounts?.amountsCardData &&
-		isVatCompliantCountry
-	);
-
 	const getHeaderContainerCss = () => {
 		if (templateSettings.headerSettings?.headerImage) {
 			return styles.headerWithImageContainer(
@@ -396,7 +390,7 @@ const DesignableBannerV2: ReactComponent<BannerRenderProps> = ({
 					</div>
 				</div>
 
-				{!showChoiceCards && (
+				{!threeTierChoiceCardSelectedProduct && (
 					<div css={styles.ctaContentContainer}>
 						<DesignableBannerCtas
 							mainOrMobileContent={mainOrMobileContent}
@@ -435,21 +429,15 @@ const DesignableBannerV2: ReactComponent<BannerRenderProps> = ({
 					/>
 				)}
 
-				{showChoiceCards && (
+				{choiceCards && threeTierChoiceCardSelectedProduct && (
 					<div css={styles.threeTierChoiceCardsContainer}>
 						<ThreeTierChoiceCards
-							countryCode={countryCode}
 							selectedProduct={threeTierChoiceCardSelectedProduct}
 							setSelectedProduct={
 								setThreeTierChoiceCardSelectedProduct
 							}
-							choices={getChoiceCardData(
-								false,
-								false,
-								countryCode,
-							)}
+							choices={choiceCards}
 							id={'banner'}
-							isDiscountActive={false}
 						/>
 
 						<div css={styles.ctaContainer}>
diff --git a/dotcom-rendering/src/components/marketing/banners/designableBanner/stories/DesignableBannerV2.stories.tsx b/dotcom-rendering/src/components/marketing/banners/designableBanner/stories/DesignableBannerV2.stories.tsx
index c3b43b9c529..966f5947078 100644
--- a/dotcom-rendering/src/components/marketing/banners/designableBanner/stories/DesignableBannerV2.stories.tsx
+++ b/dotcom-rendering/src/components/marketing/banners/designableBanner/stories/DesignableBannerV2.stories.tsx
@@ -6,6 +6,7 @@ import type {
 import type { Meta, StoryObj } from '@storybook/react';
 import lzstring from 'lz-string';
 import { DesignableBannerUnvalidatedV2 as DesignableBannerV2 } from '../../../banners/designableBanner/DesignableBannerV2';
+import { choiceCardsSettings } from '../../../lib/storybook';
 import {
 	contentNoHeading,
 	design,
@@ -67,7 +68,6 @@ const regularChoiceCardAmounts: SelectedAmountsVariant = {
 		},
 	},
 };
-
 const headerImage: BannerDesignImage = {
 	kind: 'Image',
 	mobileUrl:
@@ -119,6 +119,7 @@ export const WithThreeTierChoiceCards: Story = {
 			abTestVariant: 'THREE_TIER_CHOICE_CARDS',
 		},
 		choiceCardAmounts: regularChoiceCardAmounts,
+		choiceCardsSettings,
 	},
 };
 
@@ -140,6 +141,7 @@ export const ThreeTierChoiceCardsWithHeaderImageAndCopy: Story = {
 			abTestVariant: 'THREE_TIER_CHOICE_CARDS',
 		},
 		choiceCardAmounts: regularChoiceCardAmounts,
+		choiceCardsSettings,
 	},
 };
 
@@ -169,6 +171,7 @@ export const HeaderImageOnly: Story = {
 			abTestVariant: 'THREE_TIER_CHOICE_CARDS',
 		},
 		choiceCardAmounts: regularChoiceCardAmounts,
+		choiceCardsSettings,
 	},
 };
 
@@ -205,6 +208,7 @@ export const WithTickerAndThreeTierChoiceCards: Story = {
 			abTestVariant: 'THREE_TIER_CHOICE_CARDS',
 		},
 		choiceCardAmounts: regularChoiceCardAmounts,
+		choiceCardsSettings,
 	},
 };
 
@@ -227,6 +231,7 @@ export const WithThreeTierChoiceCardsAndArticleCount: Story = {
 			abTestVariant: 'THREE_TIER_CHOICE_CARDS',
 		},
 		choiceCardAmounts: regularChoiceCardAmounts,
+		choiceCardsSettings,
 	},
 };
 
diff --git a/dotcom-rendering/src/components/marketing/epics/ContributionsEpic.stories.tsx b/dotcom-rendering/src/components/marketing/epics/ContributionsEpic.stories.tsx
index d11f57f95c7..741a7e42463 100644
--- a/dotcom-rendering/src/components/marketing/epics/ContributionsEpic.stories.tsx
+++ b/dotcom-rendering/src/components/marketing/epics/ContributionsEpic.stories.tsx
@@ -8,6 +8,7 @@ import { SecondaryCtaType } from '@guardian/support-dotcom-components';
 import type { Meta, StoryObj } from '@storybook/react';
 import lzstring from 'lz-string';
 import React from 'react';
+import { choiceCardsSettings } from '../lib/storybook';
 import { ContributionsEpicUnvalidated as ContributionsEpic } from './ContributionsEpic';
 import { props } from './utils/storybook';
 
@@ -302,58 +303,7 @@ export const WithThreeTierChoiceCards: Story = {
 			name: 'THREE_TIER_CHOICE_CARDS',
 			secondaryCta: undefined,
 			showChoiceCards: true,
-		},
-	},
-};
-
-export const WithThreeTierChoiceCardsForUS: Story = {
-	name: 'ContributionsEpic with three tier choice cards for US',
-	args: {
-		...meta.args,
-		countryCode: 'US',
-		variant: {
-			...props.variant,
-			name: 'THREE_TIER_CHOICE_CARDS',
-			secondaryCta: undefined,
-			showChoiceCards: true,
-		},
-	},
-};
-
-export const WithThreeTierDiscountChoiceCards: Story = {
-	name: 'ContributionsEpic with discounted three tier choice cards',
-	args: {
-		...meta.args,
-		countryCode: 'GB',
-		variant: {
-			...props.variant,
-			name: 'THREE_TIER_CHOICE_CARDS',
-			secondaryCta: undefined,
-			showChoiceCards: true,
-			cta: {
-				text: 'Support the Guardian',
-				baseUrl:
-					'https://support.theguardian.com/uk/contribute?promoCode=BLACK_FRIDAY_DISCOUNT_2024',
-			},
-		},
-	},
-};
-
-export const WithThreeTierDiscountChoiceCardsNonUS: Story = {
-	name: 'ContributionsEpic with discounted three tier choice cards For Non US',
-	args: {
-		...meta.args,
-		countryCode: 'GB',
-		variant: {
-			...props.variant,
-			name: 'THREE_TIER_CHOICE_CARDS',
-			secondaryCta: undefined,
-			showChoiceCards: true,
-			cta: {
-				text: 'Support the Guardian',
-				baseUrl:
-					'https://support.theguardian.com/uk/contribute?promoCode=30OFFAPRIL',
-			},
+			choiceCardsSettings,
 		},
 	},
 };
@@ -367,29 +317,7 @@ export const WithChoiceCardsAndSignInLink: Story = {
 			name: 'V1_SIGN_IN',
 			showSignInLink: true,
 			showChoiceCards: true,
-			choiceCardAmounts: {
-				testName: 'Storybook_test',
-				variantName: 'Control',
-				defaultContributionType: 'MONTHLY',
-				displayContributionType: ['ONE_OFF', 'MONTHLY', 'ANNUAL'],
-				amountsCardData: {
-					ONE_OFF: {
-						amounts: [5, 10, 15, 20],
-						defaultAmount: 5,
-						hideChooseYourAmount: false,
-					},
-					MONTHLY: {
-						amounts: [6, 12],
-						defaultAmount: 12,
-						hideChooseYourAmount: true,
-					},
-					ANNUAL: {
-						amounts: [50, 100, 150, 200],
-						defaultAmount: 100,
-						hideChooseYourAmount: true,
-					},
-				},
-			},
+			choiceCardsSettings,
 		},
 	},
 };
diff --git a/dotcom-rendering/src/components/marketing/epics/ContributionsEpic.test.tsx b/dotcom-rendering/src/components/marketing/epics/ContributionsEpic.test.tsx
index 05278401f20..10cb12d5bf3 100644
--- a/dotcom-rendering/src/components/marketing/epics/ContributionsEpic.test.tsx
+++ b/dotcom-rendering/src/components/marketing/epics/ContributionsEpic.test.tsx
@@ -11,6 +11,11 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
 import { ContributionsEpicUnvalidated as ContributionsEpic } from './ContributionsEpic';
 import { props as baseProps } from './utils/storybook';
 
+jest.mock('../../../lib/useMatchMedia', () => ({
+	useMatchMedia: jest.fn(() => true),
+	removeMediaRulePrefix: jest.fn(() => ''),
+}));
+
 describe('ContributionsEpic', () => {
 	it('renders the epic', () => {
 		const heading = 'Epic';
diff --git a/dotcom-rendering/src/components/marketing/epics/ContributionsLiveblogEpic.stories.tsx b/dotcom-rendering/src/components/marketing/epics/ContributionsLiveblogEpic.stories.tsx
index f3ea857d502..c317334cae9 100644
--- a/dotcom-rendering/src/components/marketing/epics/ContributionsLiveblogEpic.stories.tsx
+++ b/dotcom-rendering/src/components/marketing/epics/ContributionsLiveblogEpic.stories.tsx
@@ -7,6 +7,7 @@ import { SecondaryCtaType } from '@guardian/support-dotcom-components';
 import type { Meta, StoryObj } from '@storybook/react';
 import lzstring from 'lz-string';
 import React from 'react';
+import { choiceCardsSettings } from '../lib/storybook';
 import { ContributionsLiveblogEpic } from './ContributionsLiveblogEpic';
 import { props } from './utils/storybook';
 
@@ -83,20 +84,7 @@ export const WithThreeTierChoiceCards: Story = {
 			name: 'THREE_TIER_CHOICE_CARDS',
 			secondaryCta: undefined,
 			showChoiceCards: true,
-		},
-	},
-};
-
-export const WithThreeTierChoiceCardsForUS: Story = {
-	name: 'Contributions Liveblog Epic with Three Tier Choice Cards for US',
-	args: {
-		...meta.args,
-		countryCode: 'US',
-		variant: {
-			...props.variant,
-			name: 'US_CHECKOUT_PAGE',
-			secondaryCta: undefined,
-			showChoiceCards: true,
+			choiceCardsSettings,
 		},
 	},
 };
@@ -109,6 +97,7 @@ export const WithTicker: Story = {
 			...props.variant,
 			secondaryCta: undefined,
 			showChoiceCards: true,
+			choiceCardsSettings,
 			tickerSettings: {
 				currencySymbol: '£',
 				copy: {
diff --git a/dotcom-rendering/src/components/marketing/epics/ContributionsLiveblogEpic.tsx b/dotcom-rendering/src/components/marketing/epics/ContributionsLiveblogEpic.tsx
index c3d32042124..9e635794faa 100644
--- a/dotcom-rendering/src/components/marketing/epics/ContributionsLiveblogEpic.tsx
+++ b/dotcom-rendering/src/components/marketing/epics/ContributionsLiveblogEpic.tsx
@@ -49,6 +49,11 @@ const container = css`
 		padding-left: 60px;
 		padding-right: 20px;
 	}
+
+	b,
+	strong {
+		font-weight: bold;
+	}
 `;
 
 const textContainer = css`
diff --git a/dotcom-rendering/src/components/marketing/epics/ThreeTierChoiceCardData.tsx b/dotcom-rendering/src/components/marketing/epics/ThreeTierChoiceCardData.tsx
deleted file mode 100644
index a162bfd1285..00000000000
--- a/dotcom-rendering/src/components/marketing/epics/ThreeTierChoiceCardData.tsx
+++ /dev/null
@@ -1,122 +0,0 @@
-import { isUndefined } from '@guardian/libs';
-import type { ChoiceInfo } from './ThreeTierChoiceCards';
-
-export const ChoiceCardTestData_REGULAR = (
-	longerBenefits: boolean,
-	isDiscountActive: boolean,
-): ChoiceInfo[] => [
-	{
-		supportTier: 'Contribution',
-		label: (amount: number, currencySymbol: string): string =>
-			isDiscountActive
-				? `Support ${currencySymbol}${amount}/year`
-				: `Support ${currencySymbol}${amount}/month`,
-		benefits: () => ['Give to the Guardian every month with Support'],
-		recommended: false,
-	},
-	{
-		supportTier: 'SupporterPlus',
-		label: (
-			amount: number,
-			currencySymbol: string,
-			discount?: number,
-		): JSX.Element | string => {
-			if (!isUndefined(discount)) {
-				return (
-					<>
-						Support{' '}
-						

-							{currencySymbol}
-							{amount}
-						
{' '}
-						{currencySymbol}
-						{amount * (1 - discount)}/year{' '}
-					</>
-				);
-			} else {
-				return `Support ${currencySymbol}${amount}/month`;
-			}
-		},
-		benefitsLabel: longerBenefits ? 'All-access digital' : undefined,
-		benefits: () =>
-			longerBenefits
-				? fullSupporterPlusBenefits
-				: shorterSupporterPlusBenefits,
-		recommended: true,
-	},
-	{
-		supportTier: 'OneOff',
-		label: (): string => 'Support with another amount',
-		benefitsLabel: undefined,
-		benefits: () => ['We welcome support of any size, any time'],
-		recommended: false,
-	},
-];
-
-export const ChoiceCardTestData_US = (
-	longerBenefits: boolean,
-	isDiscountActive: boolean,
-): ChoiceInfo[] => [
-	{
-		supportTier: 'Contribution',
-		label: (amount: number, currencySymbol: string): string =>
-			isDiscountActive
-				? `Support ${currencySymbol}${amount}/year`
-				: `Support ${currencySymbol}${amount}/month`,
-		benefits: () => ['Give to the Guardian every month with Support'],
-		recommended: false,
-	},
-	{
-		supportTier: 'SupporterPlus',
-		label: (
-			amount: number,
-			currencySymbol: string,
-			discount?: number,
-		): JSX.Element | string => {
-			if (!isUndefined(discount)) {
-				return (
-					<>
-						Support{' '}
-						

-							{currencySymbol}
-							{amount}
-						
{' '}
-						{currencySymbol}
-						{amount * (1 - discount)}/year{' '}
-					</>
-				);
-			} else {
-				return `Support ${currencySymbol}${amount}/month`;
-			}
-		},
-		benefitsLabel: longerBenefits ? 'All-access digital' : undefined,
-		benefits: () =>
-			longerBenefits
-				? fullSupporterPlusBenefits
-				: shorterSupporterPlusBenefits,
-		recommended: true,
-	},
-	{
-		supportTier: 'OneOff',
-		label: (amount: number, currencySymbol: string): string =>
-			`Support once from just ${currencySymbol}1`,
-		benefitsLabel: undefined,
-		benefits: (currencySymbol: string) => [
-			`We welcome support of any size, any time - whether you choose to give ${currencySymbol}1 or more`,
-		],
-		recommended: false,
-	},
-];
-
-const fullSupporterPlusBenefits = [
-	'Unlimited access to the Guardian app',
-	'Unlimited access to our new Feast App',
-	'Ad-free reading on all your devices',
-	'Exclusive newsletter for supporters, sent every week from the Guardian newsroom',
-	'Far fewer asks for support',
-];
-
-const shorterSupporterPlusBenefits = [
-	'Unlimited access to the Guardian app and Feast app',
-	'Ad-free reading on all your devices',
-];
diff --git a/dotcom-rendering/src/components/marketing/epics/ThreeTierChoiceCards.tsx b/dotcom-rendering/src/components/marketing/epics/ThreeTierChoiceCards.tsx
index 19408926391..d53e2ec48e8 100644
--- a/dotcom-rendering/src/components/marketing/epics/ThreeTierChoiceCards.tsx
+++ b/dotcom-rendering/src/components/marketing/epics/ThreeTierChoiceCards.tsx
@@ -1,5 +1,4 @@
 import { css } from '@emotion/react';
-import { isUndefined } from '@guardian/libs';
 import {
 	palette,
 	space,
@@ -16,17 +15,9 @@ import {
 	type ThemeRadio,
 	themeRadio,
 } from '@guardian/source/react-components';
-import type { CountryGroupId } from '@guardian/support-dotcom-components';
-import {
-	countryCodeToCountryGroupId,
-	getLocalCurrencySymbol,
-} from '@guardian/support-dotcom-components';
+import type { ChoiceCard } from '@guardian/support-dotcom-components/dist/shared/types/props/choiceCards';
 import type { Dispatch, SetStateAction } from 'react';
-import type {
-	SupportRatePlan,
-	SupportTier,
-} from './utils/threeTierChoiceCardAmounts';
-import { threeTierChoiceCardAmounts } from './utils/threeTierChoiceCardAmounts';
+import sanitise from 'sanitize-html';
 
 const supportTierChoiceCardStyles = (selected: boolean) => css`
 	display: block;
@@ -100,20 +91,6 @@ const recommendedPillStyles = css`
 	right: ${space[5]}px;
 `;
 
-const discountedPillStyles = css`
-	border-radius: 4px;
-	padding: ${space[1]}px ${space[2]}px;
-	background-color: ${palette.error[400]};
-	${textSansBold14};
-	color: ${palette.neutral[100]};
-	position: absolute;
-	top: -${space[2]}px;
-	${until.phablet} {
-		right: ${space[3]}px;
-	}
-	right: ${space[5]}px;
-`;
-
 const customRadioTheme: ThemeRadio = {
 	...themeRadio,
 	borderSelected: palette.brandAlt[400],
@@ -122,46 +99,33 @@ const customRadioTheme: ThemeRadio = {
 	fillSelected: palette.brand[400],
 };
 
-export type ChoiceInfo = {
-	supportTier: SupportTier;
-	label: (
-		amount: number,
-		currencySymbol: string,
-		discount?: number,
-	) => JSX.Element | string;
-	benefitsLabel?: string;
-	benefits: (currencySymbol: string) => string[];
-	recommended: boolean;
-};
-
-function getChoiceAmount(
-	supportTier: SupportTier,
-	ratePlan: SupportRatePlan,
-	countryGroupId: CountryGroupId,
-): number {
-	return threeTierChoiceCardAmounts[ratePlan][countryGroupId][supportTier];
-}
-
 const SupportingBenefits = ({
 	benefitsLabel,
 	benefits,
 }: {
-	benefitsLabel: string | undefined;
-	benefits: string[];
+	benefitsLabel?: string;
+	benefits: ChoiceCard['benefits'];
 }) => {
 	const showTicks = benefits.length > 1;
 	return (
 		<div css={supportingTextStyles}>
 			{!!benefitsLabel && (
-				<span css={benefitsLabelStyles}>
-					Unlock <strong>{benefitsLabel}</strong> benefits:
-				</span>
+				<span
+					css={benefitsLabelStyles}
+					dangerouslySetInnerHTML={{
+						__html: sanitise(benefitsLabel),
+					}}
+				/>
 			)}
 			<ul css={benefitsStyles}>
 				{benefits.map((benefit) => (
-					<li key={benefit}>
+					<li key={benefit.copy}>
 						{showTicks && <SvgTickRound size="xsmall" />}
-						{benefit}
+						<span
+							dangerouslySetInnerHTML={{
+								__html: sanitise(benefit.copy),
+							}}
+						/>
 					</li>
 				))}
 			</ul>
@@ -169,36 +133,25 @@ const SupportingBenefits = ({
 	);
 };
 
-const RecommendedPill = () => {
-	return <div css={recommendedPillStyles}>Recommended</div>;
-};
-
-const DiscountedPill = ({ discount }: { discount: number }) => {
-	return <div css={discountedPillStyles}>{discount}% off</div>;
+const ChoiceCardPill = ({ copy }: { copy: string }) => {
+	return <div css={recommendedPillStyles}>{copy}</div>;
 };
 
 type ThreeTierChoiceCardsProps = {
-	selectedProduct: SupportTier;
-	setSelectedProduct: Dispatch<SetStateAction<SupportTier>>;
-	countryCode?: string;
-	choices: ChoiceInfo[];
-	supporterPlusDiscount?: number;
+	selectedProduct: ChoiceCard['product'];
+	setSelectedProduct: Dispatch<
+		SetStateAction<ChoiceCard['product'] | undefined>
+	>;
+	choices: ChoiceCard[];
 	id: string; // uniquely identify this choice cards component to avoid conflicting with others
-	isDiscountActive: boolean;
 };
 
 export const ThreeTierChoiceCards = ({
-	countryCode,
 	selectedProduct,
 	setSelectedProduct,
 	choices,
-	supporterPlusDiscount,
 	id,
-	isDiscountActive = false,
 }: ThreeTierChoiceCardsProps) => {
-	const currencySymbol = getLocalCurrencySymbol(countryCode);
-	const countryGroupId = countryCodeToCountryGroupId(countryCode);
-
 	return (
 		<RadioGroup
 			cssOverrides={css`
@@ -207,36 +160,14 @@ export const ThreeTierChoiceCards = ({
 		>
 			<Stack space={3}>
 				{choices.map(
-					({
-						supportTier,
-						label,
-						benefitsLabel,
-						benefits,
-						recommended,
-					}) => {
-						const choiceAmount = getChoiceAmount(
-							supportTier,
-							'Monthly',
-							countryGroupId,
-						);
-						const choiceAmountYearly = getChoiceAmount(
-							supportTier,
-							'Annual',
-							countryGroupId,
-						);
-
-						const selected = selectedProduct === supportTier;
+					({ product, label, benefitsLabel, benefits, pill }) => {
+						const { supportTier } = product;
 
-						const hasDiscount =
-							!isUndefined(supporterPlusDiscount) &&
-							supportTier === 'SupporterPlus';
+						const selected =
+							selectedProduct.supportTier === supportTier;
 
 						const radioId = `choicecard-${id}-${supportTier}`;
 
-						const finalChoiceAmount = isDiscountActive
-							? choiceAmountYearly
-							: choiceAmount;
-
 						return (
 							<div
 								key={supportTier}
@@ -245,24 +176,19 @@ export const ThreeTierChoiceCards = ({
 									background-color: inherit;
 								`}
 							>
-								{hasDiscount && (
-									<DiscountedPill
-										discount={supporterPlusDiscount * 100}
-									/>
-								)}
-								{recommended && !hasDiscount && (
-									<RecommendedPill />
-								)}
+								{pill && <ChoiceCardPill copy={pill.copy} />}
 								<label
 									css={supportTierChoiceCardStyles(selected)}
 									htmlFor={radioId}
 								>
 									<Radio
-										label={label(
-											finalChoiceAmount,
-											currencySymbol,
-											supporterPlusDiscount,
-										)}
+										label={
+											<span
+												dangerouslySetInnerHTML={{
+													__html: sanitise(label),
+												}}
+											/>
+										}
 										id={radioId}
 										value={supportTier}
 										cssOverrides={labelOverrideStyles(
@@ -272,17 +198,17 @@ export const ThreeTierChoiceCards = ({
 											selected ? (
 												<SupportingBenefits
 													benefitsLabel={
-														benefitsLabel
+														benefitsLabel as
+															| string
+															| undefined
 													}
-													benefits={benefits(
-														currencySymbol,
-													)}
+													benefits={benefits}
 												/>
 											) : undefined
 										}
 										checked={selected}
 										onChange={() => {
-											setSelectedProduct(supportTier);
+											setSelectedProduct(product);
 										}}
 										theme={customRadioTheme}
 									/>
diff --git a/dotcom-rendering/src/components/marketing/epics/ctas/ContributionsEpicButtons.tsx b/dotcom-rendering/src/components/marketing/epics/ctas/ContributionsEpicButtons.tsx
index c71676263a7..dc4ccda1939 100644
--- a/dotcom-rendering/src/components/marketing/epics/ctas/ContributionsEpicButtons.tsx
+++ b/dotcom-rendering/src/components/marketing/epics/ctas/ContributionsEpicButtons.tsx
@@ -7,11 +7,9 @@ import type { SerializedStyles } from '@emotion/react';
 import { css } from '@emotion/react';
 import type { ComponentEvent } from '@guardian/ophan-tracker-js';
 import { space } from '@guardian/source/foundations';
-import {
-	countryCodeToCountryGroupId,
-	SecondaryCtaType,
-} from '@guardian/support-dotcom-components';
+import { SecondaryCtaType } from '@guardian/support-dotcom-components';
 import type { EpicVariant } from '@guardian/support-dotcom-components/dist/shared/types/abTests/epic';
+import type { ChoiceCard } from '@guardian/support-dotcom-components/dist/shared/types/props/choiceCards';
 import type {
 	Cta,
 	Tracking,
@@ -30,10 +28,6 @@ import {
 	OPHAN_COMPONENT_EVENT_CTAS_VIEW,
 	OPHAN_COMPONENT_EVENT_REMINDER_OPEN,
 } from '../utils/ophan';
-import {
-	type SupportTier,
-	threeTierChoiceCardAmounts,
-} from '../utils/threeTierChoiceCardAmounts';
 import { EpicButton } from './EpicButton';
 
 const paymentImageStyles = css`
@@ -145,12 +139,10 @@ interface ContributionsEpicButtonsProps {
 	submitComponentEvent?: (event: ComponentEvent) => void;
 	isReminderActive: boolean;
 	isSignedIn: boolean;
-	threeTierChoiceCardSelectedProduct: SupportTier;
-	showChoiceCards?: boolean;
+	threeTierChoiceCardSelectedProduct?: ChoiceCard['product'];
 	amountsTestName?: string;
 	amountsVariantName?: string;
 	numArticles: number;
-	isDiscountActive?: boolean;
 }
 
 export const ContributionsEpicButtons = ({
@@ -161,12 +153,10 @@ export const ContributionsEpicButtons = ({
 	submitComponentEvent,
 	isReminderActive,
 	isSignedIn,
-	showChoiceCards,
 	threeTierChoiceCardSelectedProduct,
 	amountsTestName,
 	amountsVariantName,
 	numArticles,
-	isDiscountActive,
 }: ContributionsEpicButtonsProps): JSX.Element | null => {
 	const [hasBeenSeen, setNode] = useIsInView({
 		debounce: true,
@@ -188,47 +178,21 @@ export const ContributionsEpicButtons = ({
 		return null;
 	}
 
-	const getChoiceCardCta = (cta: Cta): Cta => {
-		const countryGroupId = countryCodeToCountryGroupId(countryCode);
-		if (showChoiceCards) {
-			if (threeTierChoiceCardSelectedProduct === 'OneOff') {
+	const getCta = (cta: Cta): Cta => {
+		if (threeTierChoiceCardSelectedProduct) {
+			if (threeTierChoiceCardSelectedProduct.supportTier === 'OneOff') {
 				return {
 					text: cta.text,
 					baseUrl: addChoiceCardsOneTimeParams(cta.baseUrl),
 				};
 			}
-			if (isDiscountActive) {
-				const contributionAmount =
-					threeTierChoiceCardSelectedProduct === 'SupporterPlus'
-						? threeTierChoiceCardAmounts['Annual'][countryGroupId]
-								.SupporterPlus
-						: undefined;
-
-				return {
-					text: cta.text,
-					baseUrl: addChoiceCardsProductParams(
-						cta.baseUrl,
-						threeTierChoiceCardSelectedProduct,
-						'Annual',
-						contributionAmount,
-					),
-				};
-			}
-
-			/** Contribution amount is variable, unlike the SupporterPlus amount which is fixed */
-			const contributionAmount =
-				threeTierChoiceCardSelectedProduct === 'Contribution'
-					? threeTierChoiceCardAmounts['Monthly'][countryGroupId]
-							.Contribution
-					: undefined;
 
 			return {
 				text: cta.text,
 				baseUrl: addChoiceCardsProductParams(
 					cta.baseUrl,
-					threeTierChoiceCardSelectedProduct,
-					'Monthly',
-					contributionAmount,
+					threeTierChoiceCardSelectedProduct.supportTier,
+					threeTierChoiceCardSelectedProduct.ratePlan,
 				),
 			};
 		}
@@ -236,9 +200,6 @@ export const ContributionsEpicButtons = ({
 		return cta;
 	};
 
-	const getCta = (cta: Cta): Cta =>
-		showChoiceCards ? getChoiceCardCta(cta) : cta;
-
 	const openReminder = () => {
 		if (submitComponentEvent) {
 			submitComponentEvent(OPHAN_COMPONENT_EVENT_REMINDER_OPEN);
diff --git a/dotcom-rendering/src/components/marketing/epics/ctas/ContributionsEpicCtasContainer.tsx b/dotcom-rendering/src/components/marketing/epics/ctas/ContributionsEpicCtasContainer.tsx
index dffa15b9989..b439469bf68 100644
--- a/dotcom-rendering/src/components/marketing/epics/ctas/ContributionsEpicCtasContainer.tsx
+++ b/dotcom-rendering/src/components/marketing/epics/ctas/ContributionsEpicCtasContainer.tsx
@@ -1,16 +1,20 @@
+import { from } from '@guardian/source/foundations';
+import type { ChoiceCard } from '@guardian/support-dotcom-components/dist/shared/types/props/choiceCards';
 import type { EpicProps } from '@guardian/support-dotcom-components/dist/shared/types/props/epic';
 import { useState } from 'react';
-import { getChoiceCardData } from '../../lib/choiceCards';
+import {
+	removeMediaRulePrefix,
+	useMatchMedia,
+} from '../../../../lib/useMatchMedia';
+import { getChoiceCards } from '../../lib/choiceCards';
 import type { ReactComponent } from '../../lib/ReactComponent';
 import { ThreeTierChoiceCards } from '../ThreeTierChoiceCards';
-import type { SupportTier } from '../utils/threeTierChoiceCardAmounts';
 import { ContributionsEpicButtons } from './ContributionsEpicButtons';
 import { ContributionsEpicReminder } from './ContributionsEpicReminder';
 
 type Props = EpicProps & {
 	amountsTestName?: string;
 	amountsVariantName?: string;
-	now?: Date;
 };
 
 export const ContributionsEpicCtasContainer: ReactComponent<Props> = ({
@@ -22,7 +26,6 @@ export const ContributionsEpicCtasContainer: ReactComponent<Props> = ({
 	fetchEmail,
 	amountsTestName,
 	amountsVariantName,
-	now = new Date(),
 }: Props): JSX.Element => {
 	// reminders
 	const [fetchedEmail, setFetchedEmail] = useState<string | undefined>(
@@ -33,48 +36,26 @@ export const ContributionsEpicCtasContainer: ReactComponent<Props> = ({
 	const onCloseReminderClick = () => {
 		setIsReminderActive(false);
 	};
+	const isTabletOrAbove = useMatchMedia(removeMediaRulePrefix(from.tablet));
 
-	// choice cards
-	const isNonVatCompliantCountry =
-		variant.choiceCardAmounts?.testName === 'VAT_COMPLIANCE';
-
-	const showChoiceCards =
-		variant.showChoiceCards && !isNonVatCompliantCountry;
-
-	/**
-	 * This corresponds to the products in the Product API
-	 * @see https://product-catalog.guardianapis.com/product-catalog.json
-	 */
+	const choiceCards = getChoiceCards(
+		isTabletOrAbove,
+		variant.choiceCardsSettings,
+	);
+	const defaultProduct = choiceCards?.find((cc) => cc.isDefault)?.product;
 	const [
 		threeTierChoiceCardSelectedProduct,
 		setThreeTierChoiceCardSelectedProduct,
-	] = useState<SupportTier>('SupporterPlus');
-
-	const hasPromoCodeInUrl =
-		variant.cta?.baseUrl.includes('30OFFAPRIL') ?? false;
-
-	// Check the dates
-	const isDiscountActive =
-		(hasPromoCodeInUrl &&
-			now >= new Date('2025-04-15T00:00:01') &&
-			now < new Date('2025-04-21T23:59:59')) ??
-		false;
+	] = useState<ChoiceCard['product'] | undefined>(defaultProduct);
 
 	return (
 		<>
-			{showChoiceCards && (
+			{choiceCards && threeTierChoiceCardSelectedProduct && (
 				<ThreeTierChoiceCards
-					countryCode={countryCode}
 					selectedProduct={threeTierChoiceCardSelectedProduct}
 					setSelectedProduct={setThreeTierChoiceCardSelectedProduct}
-					choices={getChoiceCardData(
-						true,
-						isDiscountActive,
-						countryCode,
-					)}
-					supporterPlusDiscount={isDiscountActive ? 0.3 : undefined}
+					choices={choiceCards}
 					id={'epic'}
-					isDiscountActive={isDiscountActive}
 				/>
 			)}
 			<ContributionsEpicButtons
@@ -96,14 +77,12 @@ export const ContributionsEpicCtasContainer: ReactComponent<Props> = ({
 				submitComponentEvent={submitComponentEvent}
 				isReminderActive={isReminderActive}
 				isSignedIn={Boolean(fetchedEmail)}
-				showChoiceCards={showChoiceCards}
 				threeTierChoiceCardSelectedProduct={
 					threeTierChoiceCardSelectedProduct
 				}
 				amountsTestName={amountsTestName}
 				amountsVariantName={amountsVariantName}
 				numArticles={articleCounts.for52Weeks}
-				isDiscountActive={isDiscountActive}
 			/>
 			{isReminderActive && showReminderFields && (
 				<ContributionsEpicReminder
diff --git a/dotcom-rendering/src/components/marketing/lib/choiceCards.ts b/dotcom-rendering/src/components/marketing/lib/choiceCards.ts
index f2aecda50a3..1ad5ee3d618 100644
--- a/dotcom-rendering/src/components/marketing/lib/choiceCards.ts
+++ b/dotcom-rendering/src/components/marketing/lib/choiceCards.ts
@@ -7,11 +7,10 @@ import type {
 	ContributionFrequency,
 	ContributionType,
 } from '@guardian/support-dotcom-components/dist/shared/types';
-import {
-	ChoiceCardTestData_REGULAR,
-	ChoiceCardTestData_US,
-} from '../epics/ThreeTierChoiceCardData';
-import type { ChoiceInfo } from '../epics/ThreeTierChoiceCards';
+import type {
+	ChoiceCard,
+	ChoiceCardsSettings,
+} from '@guardian/support-dotcom-components/dist/shared/types/props/choiceCards';
 
 export interface ChoiceCardSelection {
 	frequency: ContributionFrequency;
@@ -33,12 +32,15 @@ export const contributionType: ContributionType = {
 	},
 };
 
-export const getChoiceCardData = (
-	longerBenefits: boolean,
-	isDiscountActive: boolean,
-	countryCode?: string,
-): ChoiceInfo[] => {
-	return countryCode === 'US'
-		? ChoiceCardTestData_US(longerBenefits, isDiscountActive)
-		: ChoiceCardTestData_REGULAR(longerBenefits, isDiscountActive);
+export const getChoiceCards = (
+	isTabletOrAbove: boolean,
+	choiceCardsSettings?: ChoiceCardsSettings | null,
+): ChoiceCard[] | undefined => {
+	if (choiceCardsSettings) {
+		if (!isTabletOrAbove && choiceCardsSettings.mobileChoiceCards) {
+			return choiceCardsSettings.mobileChoiceCards;
+		}
+		return choiceCardsSettings.choiceCards;
+	}
+	return;
 };
diff --git a/dotcom-rendering/src/components/marketing/lib/storybook.ts b/dotcom-rendering/src/components/marketing/lib/storybook.ts
new file mode 100644
index 00000000000..924f964b80c
--- /dev/null
+++ b/dotcom-rendering/src/components/marketing/lib/storybook.ts
@@ -0,0 +1,98 @@
+import type { ChoiceCardsSettings } from '@guardian/support-dotcom-components/dist/shared/types/props/choiceCards';
+
+export const choiceCardsSettings: ChoiceCardsSettings = {
+	choiceCards: [
+		{
+			product: {
+				supportTier: 'Contribution',
+				ratePlan: 'Monthly',
+			},
+			label: 'Support £5/month',
+			isDefault: false,
+			benefits: [
+				{
+					copy: 'Give to the Guardian every month with Support',
+				},
+			],
+		},
+		{
+			product: {
+				supportTier: 'SupporterPlus',
+				ratePlan: 'Monthly',
+			},
+			label: 'Support £12/month',
+			isDefault: true,
+			benefitsLabel:
+				'Unlock <strong>All-access digital</strong> benefits:',
+			benefits: [
+				{
+					copy: '<strong>Unlimited</strong> access to the Guardian app',
+				},
+				{ copy: 'Unlimited access to our new Feast App' },
+				{ copy: 'Ad-free reading on all your devices' },
+				{
+					copy: 'Exclusive newsletter for supporters, sent every week from the Guardian newsroom',
+				},
+				{ copy: 'Far fewer asks for support' },
+			],
+			pill: {
+				copy: 'Recommended',
+			},
+		},
+		{
+			product: {
+				supportTier: 'OneOff',
+			},
+			label: `Support with another amount`,
+			isDefault: false,
+			benefits: [
+				{
+					copy: 'We welcome support of any size, any time',
+				},
+			],
+		},
+	],
+	mobileChoiceCards: [
+		{
+			product: {
+				supportTier: 'Contribution',
+				ratePlan: 'Monthly',
+			},
+			label: 'Support £5/month',
+			isDefault: false,
+			benefits: [
+				{
+					copy: 'Give to the Guardian every month with Support',
+				},
+			],
+		},
+		{
+			product: {
+				supportTier: 'SupporterPlus',
+				ratePlan: 'Monthly',
+			},
+			label: 'Support £12/month',
+			isDefault: true,
+			benefitsLabel: 'Unlock All-access digital benefits:',
+			benefits: [
+				{ copy: 'Unlimited access to the Guardian app' },
+				{ copy: 'Unlimited access to our new Feast App' },
+			],
+			pill: {
+				copy: 'Recommended',
+			},
+		},
+		{
+			product: {
+				supportTier: 'OneOff',
+			},
+			label: `Support with another amount`,
+			isDefault: false,
+			benefits: [
+				{
+					copy: 'We welcome support of any size, any time',
+				},
+			],
+		},
+	],
+};
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 078b7a553a5..ba0a60e0560 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -344,8 +344,8 @@ importers:
         specifier: 18.1.0
         version: 18.1.0(@emotion/react@11.14.0)(@guardian/libs@22.4.0)(@guardian/source@9.0.0)(@types/react@18.3.1)(react@18.3.1)(tslib@2.6.2)(typescript@5.5.3)
       '@guardian/support-dotcom-components':
-        specifier: 7.0.1
-        version: 7.0.1(@guardian/libs@22.4.0)(zod@3.22.4)
+        specifier: 7.2.0
+        version: 7.2.0(@guardian/libs@22.4.0)(zod@3.22.4)
       '@guardian/tsconfig':
         specifier: 0.2.0
         version: 0.2.0
@@ -4294,8 +4294,8 @@ packages:
       - utf-8-validate
     dev: false
 
-  /@guardian/support-dotcom-components@7.0.1(@guardian/libs@22.4.0)(zod@3.22.4):
-    resolution: {integrity: sha512-adPhnw6oJy9l8VumAxSGRKkY07wKG2tSuE3y7jk/JsUFxg/HceVczBTEgovUHebAH5PyoLdNaz1P2UnrKB6EGg==}
+  /@guardian/support-dotcom-components@7.2.0(@guardian/libs@22.4.0)(zod@3.22.4):
+    resolution: {integrity: sha512-/BridQv4d3v2TuklBz6cwsr+7DUmycA/gNSEe14a5f8Bie0JxpyP0dw1vHbgYAZOuXpDVbaj6YBNag1mOtgY/A==}
     peerDependencies:
       '@guardian/libs': ^22.0.0
       zod: ^3.22.4


Reviews:
user: login: tomrf1
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T08:44:20Z
user: login: tomrf1
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T08:45:58Z
user: login: tomrf1
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T10:52:31Z
user: login: LAKSHMIRPILLAI
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T14:02:26Z
user: login: tomrf1
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T10:33:32Z
user: login: LAKSHMIRPILLAI
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T13:36:23Z

Review comments:
diff_hunk: @@ -309,15 +312,6 @@ const DesignableBannerV2: ReactComponent<BannerRenderProps> = ({
 		? content.mainContent
 		: content.mobileContent;
 
-	const isVatCompliantCountry =
-		choiceCardAmounts?.testName !== 'VAT_COMPLIANCE';
path: dotcom-rendering/src/components/marketing/banners/designableBanner/DesignableBannerV2.tsx
user: login: tomrf1
body: now done server-side: https://github.com/guardian/support-dotcom-components/blob/5cac843ca137dccbb1f04c84c60f4119ea3387d2/src/server/api/bannerRouter.ts#L114
created_at: 2025-05-13T08:44:20Z
author_association: MEMBER
diff_hunk: @@ -33,48 +36,26 @@ export const ContributionsEpicCtasContainer: ReactComponent<Props> = ({
 	const onCloseReminderClick = () => {
 		setIsReminderActive(false);
 	};
+	const isTabletOrAbove = useMatchMedia(removeMediaRulePrefix(from.tablet));
 
-	// choice cards
-	const isNonVatCompliantCountry =
-		variant.choiceCardAmounts?.testName === 'VAT_COMPLIANCE';
path: dotcom-rendering/src/components/marketing/epics/ctas/ContributionsEpicCtasContainer.tsx
user: login: tomrf1
body: now done server-side: https://github.com/guardian/support-dotcom-components/blob/5cac843ca137dccbb1f04c84c60f4119ea3387d2/src/server/api/epicRouter.ts#L141
created_at: 2025-05-13T08:45:57Z
author_association: MEMBER
diff_hunk: @@ -100,20 +91,6 @@ const recommendedPillStyles = css`
 	right: ${space[5]}px;
 `;
 
-const discountedPillStyles = css`
path: dotcom-rendering/src/components/marketing/epics/ThreeTierChoiceCards.tsx
user: login: tomrf1
body: discount pill colours will come from the server when we get round to implementing proper support for discounts
created_at: 2025-05-13T10:52:31Z
author_association: MEMBER
diff_hunk: @@ -119,6 +119,7 @@ export const WithThreeTierChoiceCards: Story = {
 			abTestVariant: 'THREE_TIER_CHOICE_CARDS',
 		},
 		choiceCardAmounts: regularChoiceCardAmounts,
path: dotcom-rendering/src/components/marketing/banners/designableBanner/stories/DesignableBannerV2.stories.tsx
user: login: LAKSHMIRPILLAI
body: Do we need these choiceCardAmounts?
created_at: 2025-05-14T14:02:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -119,6 +119,7 @@ export const WithThreeTierChoiceCards: Story = {
 			abTestVariant: 'THREE_TIER_CHOICE_CARDS',
 		},
 		choiceCardAmounts: regularChoiceCardAmounts,
path: dotcom-rendering/src/components/marketing/banners/designableBanner/stories/DesignableBannerV2.stories.tsx
user: login: tomrf1
body: Good question. The V2 banner does not use this field.
However, the old banner does still use it, and because both banner components share the `BannerRenderProps` props type, we still need it in both! This is something we can tidy up when removing the old banner
created_at: 2025-05-19T10:33:32Z
author_association: MEMBER

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-13T08:45:50Z
body: ### [Deploy build 42806 of `Mobile::mobile-apps-rendering` to CODE](https://riffraff.gutools.co.uk/deployment/deployAgain?project=Mobile%3A%3Amobile-apps-rendering&build=42806&stage=CODE&updateStrategy=MostlyHarmless&action=deploy)
<details>
<summary>All deployment options</summary>

- [Deploy build 42806 of `Mobile::mobile-apps-rendering` to CODE](https://riffraff.gutools.co.uk/deployment/deployAgain?project=Mobile%3A%3Amobile-apps-rendering&build=42806&stage=CODE&updateStrategy=MostlyHarmless&action=deploy)
- [Deploy parts of build 42806 to CODE by previewing it first](https://riffraff.gutools.co.uk/preview/yaml?project=Mobile%3A%3Amobile-apps-rendering&build=42806&stage=CODE&updateStrategy=MostlyHarmless)
- [What's on CODE right now?](https://riffraff.gutools.co.uk/deployment/history?projectName=Mobile%3A%3Amobile-apps-rendering&stage=CODE)
</details>

---
_From [guardian/actions-riff-raff](https://github.com/guardian/actions-riff-raff)._
<!-- guardian/actions-riff-raff for Mobile::mobile-apps-rendering -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-13T08:47:51Z
body: **Size Change:** +64.6 kB (+6.63%) 🔍

**Total Size:** 1.04 MB

| Filename | Size | Change |  |
| :--- | :---: | :---: | :---: |
| `dotcom-rendering/dist/302.client.web.********************.js` | 0 B | -11.3 kB (removed) | 🏆 |
| `dotcom-rendering/dist/356.client.web.********************.js` | 0 B | -6.4 kB (removed) | 🏆 |
| `dotcom-rendering/dist/4776.client.web.********************.js` | 0 B | -11.6 kB (removed) | 🏆 |
| `dotcom-rendering/dist/7725.client.web.********************.js` | 0 B | -5.62 kB (removed) | 🏆 |
| `dotcom-rendering/dist/8709.client.web.********************.js` | 0 B | -20.3 kB (removed) | 🏆 |
| `dotcom-rendering/dist/4063.client.web.********************.js` | 5.48 kB | +5.48 kB (new file) | 🆕 |
| `dotcom-rendering/dist/5118.client.web.********************.js` | 5.82 kB | +5.82 kB (new file) | 🆕 |
| `dotcom-rendering/dist/6262.client.web.********************.js` | 11.8 kB | +11.8 kB (new file) | 🆕 |
| `dotcom-rendering/dist/6501.client.web.********************.js` | 65.6 kB | +65.6 kB (new file) | 🆕 |
| `dotcom-rendering/dist/6750.client.web.********************.js` | 10.6 kB | +10.6 kB (new file) | 🆕 |
| `dotcom-rendering/dist/8348.client.web.********************.js` | 20.5 kB | +20.5 kB (new file) | 🆕 |

<details><summary>ℹ️ <strong>View Unchanged</strong></summary>

| Filename | Size | Change |
| :--- | :---: | :---: |
| `dotcom-rendering/dist/1032.client.web.********************.js` | 3.31 kB | 0 B |
| `dotcom-rendering/dist/117.client.web.********************.js` | 2.88 kB | +5 B (+0.17%) |
| `dotcom-rendering/dist/1209.client.web.********************.js` | 3.13 kB | 0 B |
| `dotcom-rendering/dist/1656.client.web.********************.js` | 49.5 kB | 0 B |
| `dotcom-rendering/dist/1662.client.web.********************.js` | 7.97 kB | 0 B |
| `dotcom-rendering/dist/2045.client.web.********************.js` | 2.63 kB | 0 B |
| `dotcom-rendering/dist/2161.client.web.********************.js` | 3.9 kB | 0 B |
| `dotcom-rendering/dist/223.client.web.********************.js` | 527 B | 0 B |
| `dotcom-rendering/dist/2270.client.web.********************.js` | 3.65 kB | 0 B |
| `dotcom-rendering/dist/2309.client.web.********************.js` | 3.27 kB | +2 B (+0.06%) |
| `dotcom-rendering/dist/2360.client.web.********************.js` | 20 kB | 0 B |
| `dotcom-rendering/dist/3031.client.web.********************.js` | 2.77 kB | 0 B |
| `dotcom-rendering/dist/3292.client.web.********************.js` | 2.11 kB | 0 B |
| `dotcom-rendering/dist/3384.client.web.********************.js` | 3.86 kB | 0 B |
| `dotcom-rendering/dist/3473.client.web.********************.js` | 3.67 kB | 0 B |
| `dotcom-rendering/dist/3485.client.web.********************.js` | 4.67 kB | +2 B (+0.04%) |
| `dotcom-rendering/dist/3598.client.web.********************.js` | 3.65 kB | 0 B |
| `dotcom-rendering/dist/3605.client.web.********************.js` | 15.1 kB | 0 B |
| `dotcom-rendering/dist/3619.client.web.********************.js` | 4.46 kB | +2 B (+0.04%) |
| `dotcom-rendering/dist/3929.client.web.********************.js` | 3.45 kB | 0 B |
| `dotcom-rendering/dist/4221.client.web.********************.js` | 2.63 kB | 0 B |
| `dotcom-rendering/dist/4258.client.web.********************.js` | 4.08 kB | 0 B |
| `dotcom-rendering/dist/4306.client.web.********************.js` | 22.7 kB | 0 B |
| `dotcom-rendering/dist/4308.client.web.********************.js` | 3.57 kB | 0 B |
| `dotcom-rendering/dist/4324.client.web.********************.js` | 619 B | 0 B |
| `dotcom-rendering/dist/4457.client.web.********************.js` | 6.26 kB | 0 B |
| `dotcom-rendering/dist/4501.client.web.********************.js` | 4.29 kB | 0 B |
| `dotcom-rendering/dist/5140.client.web.********************.js` | 5.42 kB | 0 B |
| `dotcom-rendering/dist/5144.client.web.********************.js` | 2.55 kB | 0 B |
| `dotcom-rendering/dist/5448.client.web.********************.js` | 4.37 kB | 0 B |
| `dotcom-rendering/dist/545.client.web.********************.js` | 531 B | 0 B |
| `dotcom-rendering/dist/562.client.web.********************.js` | 11.5 kB | 0 B |
| `dotcom-rendering/dist/5715.client.web.********************.js` | 3.48 kB | 0 B |
| `dotcom-rendering/dist/5880.client.web.********************.js` | 3.13 kB | 0 B |
| `dotcom-rendering/dist/5923.client.web.********************.js` | 2.68 kB | 0 B |
| `dotcom-rendering/dist/6038.client.web.********************.js` | 2.76 kB | 0 B |
| `dotcom-rendering/dist/6217.client.web.********************.js` | 3.75 kB | 0 B |
| `dotcom-rendering/dist/6238.client.web.********************.js` | 3.1 kB | 0 B |
| `dotcom-rendering/dist/6555.client.web.********************.js` | 5.45 kB | 0 B |
| `dotcom-rendering/dist/6562.client.web.********************.js` | 439 B | 0 B |
| `dotcom-rendering/dist/6722.client.web.********************.js` | 3.06 kB | +21 B (+0.69%) |
| `dotcom-rendering/dist/6873.client.web.********************.js` | 4.13 kB | +1 B (+0.02%) |
| `dotcom-rendering/dist/695.client.web.********************.js` | 4.51 kB | 0 B |
| `dotcom-rendering/dist/7017.client.web.********************.js` | 157 B | 0 B |
| `dotcom-rendering/dist/7116.client.web.********************.js` | 23.2 kB | 0 B |
| `dotcom-rendering/dist/732.client.web.********************.js` | 3.09 kB | 0 B |
| `dotcom-rendering/dist/7398.client.web.********************.js` | 3.57 kB | 0 B |
| `dotcom-rendering/dist/7708.client.web.********************.js` | 5.05 kB | 0 B |
| `dotcom-rendering/dist/7735.client.web.********************.js` | 16.5 kB | 0 B |
| `dotcom-rendering/dist/7927.client.web.********************.js` | 2.76 kB | 0 B |
| `dotcom-rendering/dist/8068.client.web.********************.js` | 2.88 kB | 0 B |
| `dotcom-rendering/dist/808.client.web.********************.js` | 3.28 kB | 0 B |
| `dotcom-rendering/dist/8151.client.web.********************.js` | 4.17 kB | 0 B |
| `dotcom-rendering/dist/8212.client.web.********************.js` | 5.04 kB | 0 B |
| `dotcom-rendering/dist/8385.client.web.********************.js` | 3.88 kB | 0 B |
| `dotcom-rendering/dist/8436.client.web.********************.js` | 3.63 kB | 0 B |
| `dotcom-rendering/dist/8602.client.web.********************.js` | 3.39 kB | 0 B |
| `dotcom-rendering/dist/8610.client.web.********************.js` | 3.9 kB | 0 B |
| `dotcom-rendering/dist/8619.client.web.********************.js` | 4.44 kB | 0 B |
| `dotcom-rendering/dist/864.client.web.********************.js` | 3.17 kB | 0 B |
| `dotcom-rendering/dist/8942.client.web.********************.js` | 4.43 kB | +3 B (+0.07%) |
| `dotcom-rendering/dist/9120.client.web.********************.js` | 2.89 kB | 0 B |
| `dotcom-rendering/dist/9135.client.web.********************.js` | 3.54 kB | 0 B |
| `dotcom-rendering/dist/9376.client.web.********************.js` | 2.52 kB | 0 B |
| `dotcom-rendering/dist/967.client.web.********************.js` | 2.87 kB | 0 B |
| `dotcom-rendering/dist/9822.client.web.********************.js` | 7.67 kB | 0 B |
| `dotcom-rendering/dist/9870.client.web.********************.js` | 4.77 kB | 0 B |
| `dotcom-rendering/dist/Accessibility-importable.client.web.********************.js` | 8.42 kB | 0 B |
| `dotcom-rendering/dist/AdBlockAsk-importable.client.web.********************.js` | 2.99 kB | 0 B |
| `dotcom-rendering/dist/AdPortals-importable.client.web.********************.js` | 4.76 kB | 0 B |
| `dotcom-rendering/dist/AlreadyVisited-importable.client.web.********************.js` | 424 B | 0 B |
| `dotcom-rendering/dist/AppsEpic-importable.client.web.********************.js` | 3.61 kB | 0 B |
| `dotcom-rendering/dist/AppsFooter-importable.client.web.********************.js` | 2.69 kB | 0 B |
| `dotcom-rendering/dist/AppsLightboxImage-importable.client.web.********************.js` | 2.66 kB | 0 B |
| `dotcom-rendering/dist/AppsLightboxImageStore-importable.client.web.********************.js` | 2.6 kB | 0 B |
| `dotcom-rendering/dist/AudioAtomWrapper-importable.client.web.********************.js` | 2.76 kB | 0 B |
| `dotcom-rendering/dist/AudioPlayerWrapper-importable.client.web.********************.js` | 6.61 kB | 0 B |
| `dotcom-rendering/dist/AustralianTerritorySwitcher-importable.client.web.********************.js` | 4.61 kB | 0 B |
| `dotcom-rendering/dist/Branding-importable.client.web.********************.js` | 2.89 kB | 0 B |
| `dotcom-rendering/dist/braze-web-sdk-core.client.web.********************.js` | 67.7 kB | 0 B |
| `dotcom-rendering/dist/BrazeMessaging-importable.client.web.********************.js` | 1.68 kB | 0 B |
| `dotcom-rendering/dist/CalloutBlockComponent-importable.client.web.********************.js` | 6.73 kB | 0 B |
| `dotcom-rendering/dist/CalloutEmbedBlockComponent-importable.client.web.********************.js` | 5.77 kB | 0 B |
| `dotcom-rendering/dist/CardCommentCount-importable.client.web.********************.js` | 2.67 kB | 0 B |
| `dotcom-rendering/dist/Carousel-importable.client.web.********************.js` | 6.67 kB | 0 B |
| `dotcom-rendering/dist/CarouselForNewsletters-importable.client.web.********************.js` | 4.57 kB | 0 B |
| `dotcom-rendering/dist/ChartAtom-importable.client.web.********************.js` | 539 B | 0 B |
| `dotcom-rendering/dist/CommentCount-importable.client.web.********************.js` | 2.3 kB | 0 B |
| `dotcom-rendering/dist/CrosswordComponent-importable.client.web.********************.js` | 2.87 kB | 0 B |
| `dotcom-rendering/dist/DiscussionApps-importable.client.web.********************.js` | 1.07 kB | 0 B |
| `dotcom-rendering/dist/DiscussionMeta-importable.client.web.********************.js` | 2.4 kB | 0 B |
| `dotcom-rendering/dist/DiscussionWeb-importable.client.web.********************.js` | 3.46 kB | 0 B |
| `dotcom-rendering/dist/DocumentBlockComponent-importable.client.web.********************.js` | 2.85 kB | 0 B |
| `dotcom-rendering/dist/Dropdown-importable.client.web.********************.js` | 1.72 kB | 0 B |
| `dotcom-rendering/dist/EditionSwitcherBanner-importable.client.web.********************.js` | 4.45 kB | 0 B |
| `dotcom-rendering/dist/EmbedBlockComponent-importable.client.web.********************.js` | 3.98 kB | 0 B |
| `dotcom-rendering/dist/EnhancePinnedPost-importable.client.web.********************.js` | 2.02 kB | 0 B |
| `dotcom-rendering/dist/FetchOnwardsData-importable.client.web.********************.js` | 1.94 kB | 0 B |
| `dotcom-rendering/dist/FilterKeyEventsToggle-importable.client.web.********************.js` | 3.72 kB | 0 B |
| `dotcom-rendering/dist/FocusStyles-importable.client.web.********************.js` | 618 B | 0 B |
| `dotcom-rendering/dist/FollowWrapper-importable.client.web.********************.js` | 2.53 kB | 0 B |
| `dotcom-rendering/dist/FootballMatchesPageWrapper-importable.client.web.********************.js` | 7.31 kB | 0 B |
| `dotcom-rendering/dist/FootballTablesCompetitionSelect-importable.client.web.********************.js` | 3.26 kB | 0 B |
| `dotcom-rendering/dist/FooterLabel-importable.client.web.********************.js` | 364 B | 0 B |
| `dotcom-rendering/dist/FooterReaderRevenueLinks-importable.client.web.********************.js` | 3.43 kB | 0 B |
| `dotcom-rendering/dist/frameworks.client.web.********************.js` | 20.9 kB | 0 B |
| `dotcom-rendering/dist/FrontSubNav-importable.client.web.********************.js` | 7.51 kB | 0 B |
| `dotcom-rendering/dist/GetCricketScoreboard-importable.client.web.********************.js` | 6.29 kB | 0 B |
| `dotcom-rendering/dist/GetMatchNav-importable.client.web.********************.js` | 11.5 kB | 0 B |
| `dotcom-rendering/dist/GetMatchStats-importable.client.web.********************.js` | 8.12 kB | 0 B |
| `dotcom-rendering/dist/GetMatchTabs-importable.client.web.********************.js` | 2.58 kB | 0 B |
| `dotcom-rendering/dist/guardian-braze-components-banner.client.web.********************.js` | 15.9 kB | 0 B |
| `dotcom-rendering/dist/guardian-braze-components-end-of-article.client.web.********************.js` | 10.2 kB | 0 B |
| `dotcom-rendering/dist/GuideAtomWrapper-importable.client.web.********************.js` | 782 B | 0 B |
| `dotcom-rendering/dist/index.client.web.********************.js` | 46.4 kB | +18 B (+0.04%) |
| `dotcom-rendering/dist/InstagramBlockComponent-importable.client.web.********************.js` | 2.9 kB | 0 B |
| `dotcom-rendering/dist/InteractiveAtomMessenger-importable.client.web.********************.js` | 852 B | 0 B |
| `dotcom-rendering/dist/InteractiveBlockComponent-importable.client.web.********************.js` | 8.82 kB | 0 B |
| `dotcom-rendering/dist/InteractiveContentsBlockComponent-importable.client.web.********************.js` | 3.77 kB | 0 B |
| `dotcom-rendering/dist/KeyEventsCarousel-importable.client.web.********************.js` | 5.69 kB | 0 B |
| `dotcom-rendering/dist/KnowledgeQuizAtom-importable.client.web.********************.js` | 3.19 kB | 0 B |
| `dotcom-rendering/dist/LatestLinks-importable.client.web.********************.js` | 7.97 kB | 0 B |
| `dotcom-rendering/dist/LightboxHash-importable.client.web.********************.js` | 436 B | 0 B |
| `dotcom-rendering/dist/LightboxLayout-importable.client.web.********************.js` | 6.58 kB | 0 B |
| `dotcom-rendering/dist/LiveBlogEpic-importable.client.web.********************.js` | 3.56 kB | +9 B (+0.25%) |
| `dotcom-rendering/dist/LiveblogGutterAskWrapper-importable.client.web.********************.js` | 2.49 kB | 0 B |
| `dotcom-rendering/dist/LiveblogNotifications-importable.client.web.********************.js` | 4.84 kB | 0 B |
| `dotcom-rendering/dist/Liveness-importable.client.web.********************.js` | 4.7 kB | 0 B |
| `dotcom-rendering/dist/LoopVideo-importable.client.web.********************.js` | 3.67 kB | 0 B |
| `dotcom-rendering/dist/ManyNewsletterSignUp-importable.client.web.********************.js` | 7.68 kB | 0 B |
| `dotcom-rendering/dist/MapEmbedBlockComponent-importable.client.web.********************.js` | 6.04 kB | 0 B |
| `dotcom-rendering/dist/Metrics-importable.client.web.********************.js` | 2.7 kB | 0 B |
| `dotcom-rendering/dist/MostViewedFooter-importable.client.web.********************.js` | 3.85 kB | 0 B |
| `dotcom-rendering/dist/MostViewedFooterData-importable.client.web.********************.js` | 5.97 kB | 0 B |
| `dotcom-rendering/dist/MostViewedRightWithAd-importable.client.web.********************.js` | 5.24 kB | 0 B |
| `dotcom-rendering/dist/OnwardsUpper-importable.client.web.********************.js` | 5.32 kB | 0 B |
| `dotcom-rendering/dist/PersonalityQuizAtom-importable.client.web.********************.js` | 3.35 kB | 0 B |
| `dotcom-rendering/dist/ProfileAtom-importable.client.web.********************.js` | 545 B | 0 B |
| `dotcom-rendering/dist/ProfileAtomWrapper-importable.client.web.********************.js` | 805 B | 0 B |
| `dotcom-rendering/dist/PulsingDot-importable.client.web.********************.js` | 750 B | 0 B |
| `dotcom-rendering/dist/QandaAtom-importable.client.web.********************.js` | 542 B | 0 B |
| `dotcom-rendering/dist/ReaderRevenueDev-importable.client.web.********************.js` | 470 B | 0 B |
| `dotcom-rendering/dist/readerRevenueDevUtils.client.web.********************.js` | 1.69 kB | 0 B |
| `dotcom-rendering/dist/RelativeTime-importable.client.web.********************.js` | 2.56 kB | 0 B |
| `dotcom-rendering/dist/RichLinkComponent-importable.client.web.********************.js` | 6.14 kB | 0 B |
| `dotcom-rendering/dist/ScrollableFeature-importable.client.web.********************.js` | 6.63 kB | 0 B |
| `dotcom-rendering/dist/ScrollableHighlights-importable.client.web.********************.js` | 7.29 kB | 0 B |
| `dotcom-rendering/dist/ScrollableMedium-importable.client.web.********************.js` | 2.1 kB | 0 B |
| `dotcom-rendering/dist/ScrollableSmall-importable.client.web.********************.js` | 2.17 kB | 0 B |
| `dotcom-rendering/dist/SecureSignup-importable.client.web.********************.js` | 4.21 kB | 0 B |
| `dotcom-rendering/dist/SendTargetingParams-importable.client.web.********************.js` | 2.21 kB | 0 B |
| `dotcom-rendering/dist/sentry.client.web.********************.js` | 802 B | 0 B |
| `dotcom-rendering/dist/SetABTests-importable.client.web.********************.js` | 3.91 kB | 0 B |
| `dotcom-rendering/dist/SetAdTargeting-importable.client.web.********************.js` | 487 B | 0 B |
| `dotcom-rendering/dist/ShareButton-importable.client.web.********************.js` | 2.18 kB | 0 B |
| `dotcom-rendering/dist/shimport.client.web.********************.js` | 2.8 kB | 0 B |
| `dotcom-rendering/dist/ShowHideContainers-importable.client.web.********************.js` | 897 B | 0 B |
| `dotcom-rendering/dist/ShowMore-importable.client.web.********************.js` | 919 B | 0 B |
| `dotcom-rendering/dist/SignInGateMain.client.web.********************.js` | 1.11 kB | 0 B |
| `dotcom-rendering/dist/SignInGateMainCheckoutComplete.client.web.********************.js` | 2.6 kB | 0 B |
| `dotcom-rendering/dist/SignInGateSelector-importable.client.web.********************.js` | 4.97 kB | 0 B |
| `dotcom-rendering/dist/SlideshowCarousel-importable.client.web.********************.js` | 4.56 kB | 0 B |
| `dotcom-rendering/dist/SlotBodyEnd-importable.client.web.********************.js` | 4.91 kB | +10 B (+0.2%) |
| `dotcom-rendering/dist/SpotifyBlockComponent-importable.client.web.********************.js` | 5.8 kB | 0 B |
| `dotcom-rendering/dist/StickyBottomBanner-importable.client.web.********************.js` | 6.14 kB | +9 B (+0.15%) |
| `dotcom-rendering/dist/SubNav-importable.client.web.********************.js` | 2.44 kB | 0 B |
| `dotcom-rendering/dist/TableOfContents-importable.client.web.********************.js` | 3.66 kB | 0 B |
| `dotcom-rendering/dist/TimelineAtom-importable.client.web.********************.js` | 1.23 kB | 0 B |
| `dotcom-rendering/dist/Titlepiece-importable.client.web.********************.js` | 13.6 kB | 0 B |
| `dotcom-rendering/dist/TopBar-importable.client.web.********************.js` | 8.85 kB | 0 B |
| `dotcom-rendering/dist/TopBarSupport-importable.client.web.********************.js` | 2.5 kB | +2 B (+0.08%) |
| `dotcom-rendering/dist/TweetBlockComponent-importable.client.web.********************.js` | 1.13 kB | 0 B |
| `dotcom-rendering/dist/UnsafeEmbedBlockComponent-importable.client.web.********************.js` | 2.92 kB | 0 B |
| `dotcom-rendering/dist/VideoFacebookBlockComponent-importable.client.web.********************.js` | 6.06 kB | 0 B |
| `dotcom-rendering/dist/VineBlockComponent-importable.client.web.********************.js` | 3.32 kB | 0 B |
| `dotcom-rendering/dist/YoutubeBlockComponent-importable.client.web.********************.js` | 843 B | 0 B |

</details>



<a href="https://github.com/preactjs/compressed-size-action"><sub>compressed-size-action</sub></a>
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-13T08:59:06Z
body: ### [Deploy build 18600 of `dotcom:rendering-all` to CODE](https://riffraff.gutools.co.uk/deployment/deployAgain?project=dotcom%3Arendering-all&build=18600&stage=CODE&updateStrategy=MostlyHarmless&action=deploy)
<details>
<summary>All deployment options</summary>

- [Deploy build 18600 of `dotcom:rendering-all` to CODE](https://riffraff.gutools.co.uk/deployment/deployAgain?project=dotcom%3Arendering-all&build=18600&stage=CODE&updateStrategy=MostlyHarmless&action=deploy)
- [Deploy parts of build 18600 to CODE by previewing it first](https://riffraff.gutools.co.uk/preview/yaml?project=dotcom%3Arendering-all&build=18600&stage=CODE&updateStrategy=MostlyHarmless)
- [What's on CODE right now?](https://riffraff.gutools.co.uk/deployment/history?projectName=dotcom%3Arendering-all&stage=CODE)
</details>

---
_From [guardian/actions-riff-raff](https://github.com/guardian/actions-riff-raff)._
<!-- guardian/actions-riff-raff for dotcom:rendering-all -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-14T08:49:19Z
body: Hello :wave:! When you're ready to run Chromatic, please apply the `run_chromatic` label to this PR.

You will need to reapply the label each time you want to run Chromatic.

[Click here to see the Chromatic project.](https://www.chromatic.com/builds?appId=63e251470cfbe61776b0ef19)
author_association: NONE
user: login: prout-bot
created_at: 2025-05-20T08:58:06Z
body: Seen on [PROD](https://www.theguardian.com/uk) (merged by @tomrf1 8 minutes and 53 seconds ago) Please check your changes!
author_association: NONE