Repository: bbc/simorgh
Pull request #12494: WORLDSERVICE 408 - Implement Lite site text CTA under h1 on article page (Gahuza)
Author: login: Louis-Matsika
Labels: name: enhancement
description: New feature or request
Created: 2025-03-11T09:46:51Z | Merged: 2025-03-25T11:43:44Z

Resolves JIRA [WORLDSERVICE-408](https://jira.dev.bbc.co.uk/browse/WORLDSERVICE-408)

Overall changes
======
Created a new component `CallToActionLinkWithChevron` within `#components`

This new component uses the existing `CallToActionLink` however incorporates `<RightChevron>` and `<LeftChevron>` (to support both LTR, and RTL languages)

Created `ArticleHeadline` co-located within `src/app/pages/ArticlePage`. this component handels rendering heading on article pages and decides if a headline should include a LiteSiteCTA based on the toggles set in iSite


Notes
======
**Refactoring**
There are now a few CTAs within Simorgh that are logically and visually similar. A ticket will be made to address refactoring

**insuring there isn’t multiple LiteSiteCTAs on an article page**
Currently these changes affect every heading on an article page (this should be fine as there shouldn't be more than 1 `h1` heading on an article page).

if work _is_ needed to only apply the CTA under the first heading(as there may be more headings on the page), then work in the `BFF` would need to be done. perhaps adding a custom block that apply's `CallToActionLink` only to the first heading in the data that gets sent off to Simorgh


Testing
======
### **Canonical Articles that should display the liteSiteCTA**
<img width="658" alt="Screenshot 2025-03-24 at 3 34 19 PM" src="https://github.com/user-attachments/assets/6a0e93e8-9535-4980-98b5-1a8060230fa1" />


1. Visit any article page on a service with the liteSiteCTA toggle enabled
  	- e.g. `http://localhost:7080/gahuza/articles/c5y51yxeg53o`
 	 
2. Check if the liteSite CTA is visible under the Article heading

3. Click the CTA

	_The CTA should take you to the same article however the lite version_
	- e.g. `http://localhost:7080/gahuza/articles/c5y51yxeg53o.ltie`


### **Canonical Articles that should not display the liteSiteCTA**
<img width="534" alt="Screenshot 2025-03-24 at 3 37 42 PM" src="https://github.com/user-attachments/assets/f5720bf1-bb31-400f-bf60-7994d880a36c" />


1. Visit any article page on a service with the liteSiteCTA toggle disabled
   - e.g. `http://localhost:7080/gahuza/articles/c5y51yxeg53o`
   
 The liteSite CTA should not be visible under the Article heading


### **The LiteSite**
_The LiteSite itself should not render the liteSiteCTA under any conditions_
<img width="575" alt="Screenshot 2025-03-19 at 2 26 24 PM" src="https://github.com/user-attachments/assets/c60d181e-3f8c-4c18-93dc-b024889a567b" />


1. Visit any article page on the LiteSite
	- e.g. `http://localhost:7080/gahuza/articles/c5y51yxeg53o`
	
The liteSite CTA should not be visible under the Article heading


Helpful Links
======

[Coding Standards](https://github.com/bbc/simorgh/blob/latest/docs/Coding-Standards/README.md)

[Repository use guidelines](https://github.com/bbc/simorgh-infrastructure/blob/latest/documentation/repository-guidelines.md)




Diff:
diff --git a/.yarn/cache/@esbuild-darwin-x64-npm-0.25.0-615d56df7b-10.zip b/.yarn/cache/@esbuild-darwin-x64-npm-0.25.0-615d56df7b-10.zip
new file mode 100644
index 00000000000..02afc474131
Binary files /dev/null and b/.yarn/cache/@esbuild-darwin-x64-npm-0.25.0-615d56df7b-10.zip differ
diff --git a/.yarn/cache/@esbuild-linux-x64-npm-0.25.0-cd9596465b-10.zip b/.yarn/cache/@esbuild-linux-x64-npm-0.25.0-cd9596465b-10.zip
deleted file mode 100644
index bb37681fa5a..00000000000
Binary files a/.yarn/cache/@esbuild-linux-x64-npm-0.25.0-cd9596465b-10.zip and /dev/null differ
diff --git a/.yarn/cache/@img-sharp-darwin-x64-npm-0.33.5-785c54564a-10.zip b/.yarn/cache/@img-sharp-darwin-x64-npm-0.33.5-785c54564a-10.zip
new file mode 100644
index 00000000000..67656fdc4d9
Binary files /dev/null and b/.yarn/cache/@img-sharp-darwin-x64-npm-0.33.5-785c54564a-10.zip differ
diff --git a/.yarn/cache/@img-sharp-libvips-darwin-x64-npm-1.0.4-6fde8e50e0-10.zip b/.yarn/cache/@img-sharp-libvips-darwin-x64-npm-1.0.4-6fde8e50e0-10.zip
new file mode 100644
index 00000000000..9891ae0c12e
Binary files /dev/null and b/.yarn/cache/@img-sharp-libvips-darwin-x64-npm-1.0.4-6fde8e50e0-10.zip differ
diff --git a/.yarn/cache/@img-sharp-libvips-linux-x64-npm-1.0.4-0974f077b7-10.zip b/.yarn/cache/@img-sharp-libvips-linux-x64-npm-1.0.4-0974f077b7-10.zip
deleted file mode 100644
index a4a5824aa57..00000000000
Binary files a/.yarn/cache/@img-sharp-libvips-linux-x64-npm-1.0.4-0974f077b7-10.zip and /dev/null differ
diff --git a/.yarn/cache/@img-sharp-linux-x64-npm-0.33.5-1b6c430eb4-10.zip b/.yarn/cache/@img-sharp-linux-x64-npm-0.33.5-1b6c430eb4-10.zip
deleted file mode 100644
index fd9356ce5d3..00000000000
Binary files a/.yarn/cache/@img-sharp-linux-x64-npm-0.33.5-1b6c430eb4-10.zip and /dev/null differ
diff --git a/.yarn/cache/fsevents-patch-6b67494872-10.zip b/.yarn/cache/fsevents-patch-6b67494872-10.zip
new file mode 100644
index 00000000000..9887ada72d9
Binary files /dev/null and b/.yarn/cache/fsevents-patch-6b67494872-10.zip differ
diff --git a/cypress/e2e/pages/articles/testsForCanonicalOnly.js b/cypress/e2e/pages/articles/testsForCanonicalOnly.js
index 66b9d7b64f7..3cf1261194b 100644
--- a/cypress/e2e/pages/articles/testsForCanonicalOnly.js
+++ b/cypress/e2e/pages/articles/testsForCanonicalOnly.js
@@ -1,4 +1,5 @@
 /* eslint-disable import/prefer-default-export */
+import { liteEnabledServices } from '#app/components/LiteSiteCta/liteSiteConfig';
 import appConfig from '../../../../src/server/utilities/serviceConfigs';
 import envConfig from '../../../support/config/envs';
 import appToggles from '../../../support/helpers/useAppToggles';
@@ -72,6 +73,22 @@ export default ({ service, pageType, variant = 'default' }) =>
       });
     }
 
+    if (liteEnabledServices.includes(service)) {
+      describe('Canonical Lite Site CTA', () => {
+        it('should have a lite site CTA', () => {
+          cy.get('[data-e2e="to-lite-site"]').within(() => {
+            cy.get('a')
+              .should('have.attr', 'href')
+              .then($href => {
+                cy.get('a').click();
+                cy.url().should('contain', $href).should('contain', '.lite');
+              });
+          });
+          cy.go('back');
+        });
+      });
+    }
+
     describe('Media Player: Canonical', () => {
       it('should render a visible placeholder image', () => {
         cy.window().then(win => {
diff --git a/cypress/e2e/specialFeatures/atiAnalytics/assertions/canonicalLiteSiteCta.js b/cypress/e2e/specialFeatures/atiAnalytics/assertions/canonicalLiteSiteCta.js
new file mode 100644
index 00000000000..c3aa3865fd9
--- /dev/null
+++ b/cypress/e2e/specialFeatures/atiAnalytics/assertions/canonicalLiteSiteCta.js
@@ -0,0 +1,51 @@
+import { COMPONENTS, interceptATIAnalyticsBeacons } from '../helpers';
+import { assertATIComponentClickEvent, assertATIComponentViewEvent } from '.';
+
+const { CANONICAL_LITE_CTA } = COMPONENTS;
+
+export const assertCanonicalToLiteSiteCTAComponentView = ({
+  pageIdentifier,
+  contentType,
+  useReverb,
+  path,
+}) => {
+  it('should send a view event for the Canonical to Lite Site CTA component', () => {
+    interceptATIAnalyticsBeacons();
+    cy.visit(path);
+
+    cy.get('[data-e2e="to-lite-site"]').scrollIntoView({ duration: 1000 });
+
+    assertATIComponentViewEvent({
+      component: CANONICAL_LITE_CTA,
+      pageIdentifier,
+      contentType,
+      useReverb,
+    });
+  });
+};
+
+export const assertCanonicalToLiteSiteCTAComponentClick = ({
+  pageIdentifier,
+  contentType,
+  useReverb,
+  path,
+}) => {
+  it('should send a click event for the Canonical to Lite Site CTA component', () => {
+    interceptATIAnalyticsBeacons();
+    cy.visit(path);
+
+    cy.get('[data-e2e="to-lite-site"]').scrollIntoView({
+      duration: 1000,
+    });
+
+    // Click on first item
+    cy.get('[data-e2e="to-lite-site"]').find('a').first().click();
+
+    assertATIComponentClickEvent({
+      component: CANONICAL_LITE_CTA,
+      pageIdentifier,
+      contentType,
+      useReverb,
+    });
+  });
+};
diff --git a/cypress/e2e/specialFeatures/atiAnalytics/helpers/index.js b/cypress/e2e/specialFeatures/atiAnalytics/helpers/index.js
index 627ff2d0306..e976d5286a8 100644
--- a/cypress/e2e/specialFeatures/atiAnalytics/helpers/index.js
+++ b/cypress/e2e/specialFeatures/atiAnalytics/helpers/index.js
@@ -30,6 +30,7 @@ const RELATED_CONTENT = 'related-content';
 const RELATED_TOPICS = 'topics';
 const PODCAST_PROMO = 'promo-podcast';
 const LITE_SITE_CTA = 'lite-site-cta';
+const CANONICAL_LITE_CTA = 'canonical-lite-cta';
 const RECENT_AUDIO_EPISODES = 'episodes-audio';
 const PODCAST_LINKS = 'third-party';
 const LATEST_MEDIA = 'latest';
@@ -49,6 +50,7 @@ export const COMPONENTS = {
   RELATED_TOPICS,
   PODCAST_PROMO,
   LITE_SITE_CTA,
+  CANONICAL_LITE_CTA,
   RECENT_AUDIO_EPISODES,
   PODCAST_LINKS,
   LATEST_MEDIA,
diff --git a/cypress/e2e/specialFeatures/atiAnalytics/index.cy.js b/cypress/e2e/specialFeatures/atiAnalytics/index.cy.js
index 2f5478c0300..3c6048d079f 100644
--- a/cypress/e2e/specialFeatures/atiAnalytics/index.cy.js
+++ b/cypress/e2e/specialFeatures/atiAnalytics/index.cy.js
@@ -14,6 +14,10 @@ import {
   assertLatestMediaComponentView,
 } from './assertions/latestMedia';
 import { assertLiteSiteCTAComponentClick } from './assertions/liteSiteCta';
+import {
+  assertCanonicalToLiteSiteCTAComponentClick,
+  assertCanonicalToLiteSiteCTAComponentView,
+} from './assertions/canonicalLiteSiteCta';
 import {
   assertMessageBannerComponentClick,
   assertMessageBannerComponentView,
@@ -144,6 +148,8 @@ const canonicalTestSuites = [
       assertRelatedContentComponentClick,
       assertMostReadComponentView,
       assertMostReadComponentClick,
+      assertCanonicalToLiteSiteCTAComponentView,
+      assertCanonicalToLiteSiteCTAComponentClick,
     ],
   },
   {
diff --git a/src/app/components/CallToActionLinkWithChevron/index.stories.tsx b/src/app/components/CallToActionLinkWithChevron/index.stories.tsx
new file mode 100644
index 00000000000..05dd82ff22f
--- /dev/null
+++ b/src/app/components/CallToActionLinkWithChevron/index.stories.tsx
@@ -0,0 +1,29 @@
+import React, { PropsWithChildren } from 'react';
+
+import { SerializedStyles } from '@emotion/react';
+import CallToActionLinkWithChevron from '.';
+import { CallToActionLinkProps } from '../CallToActionLink/types';
+
+const Component = ({
+  href,
+  children,
+}: PropsWithChildren<
+  CallToActionLinkProps & { chevronStyles?: () => SerializedStyles }
+>) => (
+  <CallToActionLinkWithChevron href={href}>
+    {children}
+  </CallToActionLinkWithChevron>
+);
+
+export default {
+  title: 'Components/Call To Action Link With Chevron',
+  Component,
+};
+
+export const Example = () => {
+  return (
+    <Component href="https://www.bbc.com/ws/languages">
+      Call To Action
+    </Component>
+  );
+};
diff --git a/src/app/components/CallToActionLinkWithChevron/index.styles.ts b/src/app/components/CallToActionLinkWithChevron/index.styles.ts
new file mode 100644
index 00000000000..153180956e2
--- /dev/null
+++ b/src/app/components/CallToActionLinkWithChevron/index.styles.ts
@@ -0,0 +1,50 @@
+import { css, Theme } from '@emotion/react';
+import pixelsToRem from '../../utilities/pixelsToRem';
+
+export default {
+  linkText: ({ palette }: Theme) =>
+    css({
+      color: 'inherit',
+      borderBottom: `${pixelsToRem(1)}rem solid ${palette.GREY_10}`,
+      textDecoration: 'none',
+      'a:visited &': {
+        color: palette.METAL,
+        borderBottom: `${pixelsToRem(1)}rem solid ${palette.METAL}`,
+      },
+      'a:focus &, a:hover &': {
+        borderBottom: `${pixelsToRem(2)}rem solid ${palette.POSTBOX}`,
+        color: palette.POSTBOX,
+      },
+    }),
+  link: ({ palette }: Theme) =>
+    css({
+      color: palette.GREY_10,
+      display: 'inline-block',
+      textDecoration: 'none',
+      padding: `${pixelsToRem(12)}rem 0 ${pixelsToRem(12)}rem`,
+      '&:focus, &:hover': {
+        textDecoration: 'none',
+      },
+    }),
+  chevron: ({ palette, spacings, mq }: Theme) =>
+    css({
+      color: palette.GREY_10,
+      fill: 'currentColor',
+      marginInlineStart: `${spacings.FULL}rem`,
+      verticalAlign: 'middle',
+      width: `${pixelsToRem(14)}rem`,
+      height: `${pixelsToRem(14)}rem`,
+      'a:visited &': {
+        color: palette.METAL,
+      },
+      'a:focus &, a:hover &': {
+        color: palette.POSTBOX,
+      },
+      [mq.FORCED_COLOURS]: {
+        fill: 'linkText',
+        'a:visited &': {
+          fill: 'visitedText',
+        },
+      },
+    }),
+};
diff --git a/src/app/components/CallToActionLinkWithChevron/index.test.tsx b/src/app/components/CallToActionLinkWithChevron/index.test.tsx
new file mode 100644
index 00000000000..233d02933ec
--- /dev/null
+++ b/src/app/components/CallToActionLinkWithChevron/index.test.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import { render, screen } from '../react-testing-library-with-providers';
+import CallToActionLinkWithChevron from '.';
+
+describe('Call To Action Link With Chevron', () => {
+  it('should render children as link text', () => {
+    render(
+      <CallToActionLinkWithChevron href="https://www.bbc.com/ws/languages">
+        My Link Text
+      </CallToActionLinkWithChevron>,
+    );
+    const link = screen.getByText('My Link Text');
+    expect(link).toBeInTheDocument();
+  });
+
+  it('should render a link with the correct href', () => {
+    render(
+      <CallToActionLinkWithChevron href="https://www.bbc.com/ws/languages">
+        My Link Text
+      </CallToActionLinkWithChevron>,
+    );
+    const link = screen.getByRole('link');
+    expect(link.getAttribute('href')).toEqual(
+      'https://www.bbc.com/ws/languages',
+    );
+  });
+});
diff --git a/src/app/components/CallToActionLinkWithChevron/index.tsx b/src/app/components/CallToActionLinkWithChevron/index.tsx
new file mode 100644
index 00000000000..19fa0d99098
--- /dev/null
+++ b/src/app/components/CallToActionLinkWithChevron/index.tsx
@@ -0,0 +1,39 @@
+/** @jsx jsx */
+import { PropsWithChildren, useContext } from 'react';
+import { jsx } from '@emotion/react';
+import { ServiceContext } from '#app/contexts/ServiceContext';
+import { CallToActionLinkProps } from '../CallToActionLink/types';
+import { LeftChevron, RightChevron } from '../icons';
+import CallToActionLink from '../CallToActionLink';
+import Text from '../Text';
+import styles from './index.styles';
+
+const CallToActionLinkWithChevron = ({
+  href,
+  className,
+  children,
+  eventTrackingData,
+}: PropsWithChildren<CallToActionLinkProps>) => {
+  const { dir } = useContext(ServiceContext);
+  const isRtl = dir === 'rtl';
+
+  return (
+    <CallToActionLink
+      href={href}
+      className={className}
+      eventTrackingData={eventTrackingData}
+      css={styles.link}
+    >
+      <Text size="brevier" fontVariant="sansBold" css={styles.linkText}>
+        {children}
+      </Text>
+      {isRtl ? (
+        <LeftChevron css={styles.chevron} />
+      ) : (
+        <RightChevron css={styles.chevron} />
+      )}
+    </CallToActionLink>
+  );
+};
+
+export default CallToActionLinkWithChevron;
diff --git a/src/app/legacy/containers/Headings/index.jsx b/src/app/legacy/containers/Headings/index.jsx
index ca466648a3b..75ed4acddcf 100644
--- a/src/app/legacy/containers/Headings/index.jsx
+++ b/src/app/legacy/containers/Headings/index.jsx
@@ -46,6 +46,7 @@ const HeadingsContainer = ({
     },
   ],
   type,
+  className,
 }) => {
   const { script, service } = useContext(ServiceContext);
   const Heading = Headings[type];
@@ -71,6 +72,7 @@ const HeadingsContainer = ({
     id: isHeading ? headingId : subHeadingId,
     as: isHeading && !isFirstBlock ? 'strong' : null,
     tabIndex: isHeading && !isFirstBlock ? null : '-1',
+    className,
   };
 
   return (
diff --git a/src/app/pages/ArticlePage/ArticleHeadline/index.styles.ts b/src/app/pages/ArticlePage/ArticleHeadline/index.styles.ts
new file mode 100644
index 00000000000..c05d4f1f265
--- /dev/null
+++ b/src/app/pages/ArticlePage/ArticleHeadline/index.styles.ts
@@ -0,0 +1,31 @@
+import { css, Theme } from '@emotion/react';
+import pixelsToRem from '#app/utilities/pixelsToRem';
+
+export default {
+  liteCTAContainer: ({ mq }: Theme) =>
+    css({
+      marginBottom: `${pixelsToRem(20)}rem`,
+      [mq.GROUP_3_MIN_WIDTH]: {
+        marginBottom: `${pixelsToRem(28)}rem`,
+      },
+    }),
+  liteCTA: ({ spacings, mq }: Theme) =>
+    css({
+      display: 'inline-block',
+      marginInlineStart: `${spacings.FULL}rem`,
+
+      [mq.GROUP_2_MIN_WIDTH]: {
+        marginInlineStart: `${spacings.DOUBLE}rem`,
+      },
+      [mq.GROUP_4_MIN_WIDTH]: {
+        marginInlineStart: 0,
+      },
+    }),
+  headlineStylesOverride: ({ spacings, mq }: Theme) =>
+    css({
+      paddingBottom: `${spacings.HALF}rem`,
+      [mq.GROUP_3_MIN_WIDTH]: {
+        paddingBottom: `${pixelsToRem(12)}rem`,
+      },
+    }),
+};
diff --git a/src/app/pages/ArticlePage/ArticleHeadline/index.tsx b/src/app/pages/ArticlePage/ArticleHeadline/index.tsx
new file mode 100644
index 00000000000..99f0f42c6c3
--- /dev/null
+++ b/src/app/pages/ArticlePage/ArticleHeadline/index.tsx
@@ -0,0 +1,54 @@
+/** @jsx jsx */
+/* @jsxFrag React.Fragment */
+import { jsx } from '@emotion/react';
+import React, { useContext } from 'react';
+import { RequestContext } from '#contexts/RequestContext';
+import useViewTracker from '#app/hooks/useViewTracker';
+import useToggle from '#hooks/useToggle';
+import CallToActionLinkWithChevron from '#app/components/CallToActionLinkWithChevron';
+import { ServiceContext } from '#contexts/ServiceContext';
+import Headings from '#containers/Headings';
+import { ComponentToRenderProps } from '../types';
+import styles from './index.styles';
+
+const ArticleHeadline = (props: ComponentToRenderProps) => {
+  const { pathname, isLite } = useContext(RequestContext);
+  const { translations } = useContext(ServiceContext);
+  const eventTrackingData = { componentName: 'canonical-lite-cta' };
+  const { enabled: showCTA } = useToggle('liteSiteCTA');
+  const viewRef = useViewTracker(eventTrackingData);
+
+  const articleDataSavingLinkText =
+    translations?.liteSite?.articleDataSavingLinkText ?? 'Data saving version';
+
+  const showLiteCTAOnCanonical: boolean = !isLite && showCTA;
+
+  return (
+    <>
+      <Headings
+        className="article-heading"
+        {...props}
+        {...(showLiteCTAOnCanonical && {
+          css: styles.headlineStylesOverride,
+        })}
+      />
+      {showLiteCTAOnCanonical && (
+        <div
+          css={styles.liteCTAContainer}
+          ref={viewRef}
+          data-e2e="to-lite-site"
+        >
+          <CallToActionLinkWithChevron
+            eventTrackingData={eventTrackingData}
+            href={`${pathname}.lite`}
+            css={styles.liteCTA}
+          >
+            {articleDataSavingLinkText}
+          </CallToActionLinkWithChevron>
+        </div>
+      )}
+    </>
+  );
+};
+
+export default ArticleHeadline;
diff --git a/src/app/pages/ArticlePage/ArticlePage.tsx b/src/app/pages/ArticlePage/ArticlePage.tsx
index 42a89ad2842..682df78d4ac 100644
--- a/src/app/pages/ArticlePage/ArticlePage.tsx
+++ b/src/app/pages/ArticlePage/ArticlePage.tsx
@@ -9,7 +9,7 @@ import OptimizelyArticleCompleteTracking from '#app/legacy/containers/Optimizely
 import OptimizelyPageViewTracking from '#app/legacy/containers/OptimizelyPageViewTracking';
 import ArticleMetadata from '#containers/ArticleMetadata';
 import { RequestContext } from '#contexts/RequestContext';
-import headings from '#containers/Headings';
+import Headings from '#containers/Headings';
 import visuallyHiddenHeadline from '#containers/VisuallyHiddenHeadline';
 import gist from '#containers/Gist';
 import text from '#containers/Text';
@@ -68,6 +68,7 @@ import Disclaimer from '../../components/Disclaimer';
 import SecondaryColumn from './SecondaryColumn';
 import styles from './ArticlePage.styles';
 import { ComponentToRenderProps, TimeStampProps } from './types';
+import ArticleHeadline from './ArticleHeadline';
 
 const getImageComponent =
   (preloadLeadImageToggle: boolean) => (props: ComponentToRenderProps) => (
@@ -115,6 +116,10 @@ const DisclaimerWithPaddingOverride = (props: ComponentToRenderProps) => (
 const getPodcastPromoComponent = (podcastPromoEnabled: boolean) => () =>
   podcastPromoEnabled ? <InlinePodcastPromo /> : null;
 
+const getHeadlineComponent = (props: ComponentToRenderProps) => (
+  <ArticleHeadline {...props} />
+);
+
 const ArticlePage = ({ pageData }: { pageData: Article }) => {
   const { isApp } = useContext(RequestContext);
 
@@ -187,8 +192,8 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => {
 
   const componentsToRender = {
     visuallyHiddenHeadline,
-    headline: headings,
-    subheadline: headings,
+    headline: getHeadlineComponent,
+    subheadline: Headings,
     audio: MediaLoader,
     video: MediaLoader,
     text,
diff --git a/src/app/pages/ArticlePage/__snapshots__/index.test.tsx.snap b/src/app/pages/ArticlePage/__snapshots__/index.test.tsx.snap
index 3cad7115055..bff0e4395a7 100644
--- a/src/app/pages/ArticlePage/__snapshots__/index.test.tsx.snap
+++ b/src/app/pages/ArticlePage/__snapshots__/index.test.tsx.snap
@@ -845,7 +845,7 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
             dir="ltr"
           >
             <h1
-              class="emotion-6 emotion-7"
+              class="article-heading emotion-6 emotion-7"
               id="content"
               tabindex="-1"
             >
@@ -2238,7 +2238,7 @@ exports[`Article Page should render a news article correctly 1`] = `
             dir="ltr"
           >
             <h1
-              class="emotion-6 emotion-7"
+              class="article-heading emotion-6 emotion-7"
               id="content"
               tabindex="-1"
             >
@@ -3111,7 +3111,7 @@ exports[`Article Page should render a news article with headline in the middle c
             dir="ltr"
           >
             <strong
-              class="emotion-11 emotion-12"
+              class="article-heading emotion-11 emotion-12"
             >
               Article Headline
             </strong>
@@ -4419,7 +4419,7 @@ exports[`Article Page should render a rtl article (persian) with most read corre
             dir="rtl"
           >
             <h1
-              class="emotion-6 emotion-7"
+              class="article-heading emotion-6 emotion-7"
               id="content"
               tabindex="-1"
             >
diff --git a/src/app/pages/ArticlePage/index.stories.tsx b/src/app/pages/ArticlePage/index.stories.tsx
index 1a6dad4eb81..148bb8315f5 100644
--- a/src/app/pages/ArticlePage/index.stories.tsx
+++ b/src/app/pages/ArticlePage/index.stories.tsx
@@ -10,6 +10,8 @@ import { RequestContextProvider } from '#app/contexts/RequestContext';
 import { ARTICLE_PAGE } from '#app/routes/utils/pageTypes';
 import articleData from '#data/news/articles/c0g992jmmkko.json';
 import articleDataBurmese from '#data/burmese/articles/cn0exdy1jzvo.json';
+import articleDataGahuza from '#data/gahuza/articles/c5y51yxeg53o.json';
+import articleDataArabic from '#data/arabic/articles/cn0m90verwvo.json';
 import articleDataWithRelatedContent from '#data/afrique/articles/c7yn6nznljdo.json';
 import articleDataWithSingleRelatedContent from '#data/afrique/articles/cz216x22106o.json';
 import articleDataWithPodcastPromo from '#data/russian/articles/c61q94n3rm3o.json';
@@ -61,6 +63,7 @@ type Props = {
   service?: Services;
   podcastEnabled?: boolean;
   electionBanner?: boolean;
+  liteSiteCTAEnabled?: boolean;
 };
 
 const ComponentWithContext = ({
@@ -68,6 +71,7 @@ const ComponentWithContext = ({
   service = 'news',
   podcastEnabled = false,
   electionBanner = false,
+  liteSiteCTAEnabled = false,
 }: Props) => {
   return (
     <ToggleContextProvider
@@ -77,6 +81,7 @@ const ComponentWithContext = ({
         frostedPromo: { enabled: true, value: 1 },
         podcastPromo: { enabled: podcastEnabled },
         electionBanner: { enabled: electionBanner },
+        liteSiteCTA: { enabled: liteSiteCTAEnabled },
       }}
     >
       {/* Service set to news to enable most read. Article data is in english */}
@@ -110,6 +115,7 @@ const ComponentWithServiceContext = ({
   service = 'news',
   podcastEnabled = false,
   electionBanner = false,
+  liteSiteCTAEnabled = false,
 }: Props) => {
   const memoisedServiceContext = useMemo(
     () => ({ ...serviceContextMock, service }),
@@ -124,6 +130,7 @@ const ComponentWithServiceContext = ({
         frostedPromo: { enabled: true, value: 1 },
         podcastPromo: { enabled: podcastEnabled },
         electionBanner: { enabled: electionBanner },
+        liteSiteCTA: { enabled: liteSiteCTAEnabled },
       }}
     >
       {/* Service set to news to enable most read. Article data is in english */}
@@ -230,3 +237,38 @@ export const ArticlePageWithElectionBanner = {
     chromatic: { disableSnapshot: true },
   },
 };
+
+export const ArticlePageWithLiteSiteCTA = {
+  render: (_: StoryArgs, { service }: StoryProps) => (
+    <ComponentWithContext
+      data={articleData}
+      service={service}
+      liteSiteCTAEnabled
+    />
+  ),
+  parameters: {
+    chromatic: { disableSnapshot: true },
+  },
+};
+
+export const TestArticlePageWithLiteSiteCTA = {
+  render: () => (
+    <ComponentWithContext
+      data={articleDataGahuza}
+      service="gahuza"
+      liteSiteCTAEnabled
+    />
+  ),
+  tags: ['!dev'],
+};
+
+export const TestArticlePageWithLiteSiteCTARightToLeft = {
+  render: () => (
+    <ComponentWithContext
+      data={articleDataArabic}
+      service="arabic"
+      liteSiteCTAEnabled
+    />
+  ),
+  tags: ['!dev'],
+};
diff --git a/src/app/pages/ArticlePage/index.test.tsx b/src/app/pages/ArticlePage/index.test.tsx
index 53292b1a1db..9fccaa571e5 100644
--- a/src/app/pages/ArticlePage/index.test.tsx
+++ b/src/app/pages/ArticlePage/index.test.tsx
@@ -29,6 +29,8 @@ import { suppressPropWarnings } from '#app/legacy/psammead/psammead-test-helpers
 import { Services } from '#app/models/types/global';
 
 import { Article } from '#app/models/types/optimo';
+import * as clickTracking from '#app/hooks/useClickTrackerHandler';
+import * as viewTracking from '#app/hooks/useViewTracker';
 import {
   render,
   screen,
@@ -141,6 +143,59 @@ afterAll(() => {
 });
 
 describe('Article Page', () => {
+  it.each([
+    {
+      testScenario:
+        'should show the CTA on non Lite Site pages, when the toggle is enabled',
+      isLite: false,
+      toggleEnabled: true,
+      shouldBeDisplayed: true,
+    },
+    {
+      testScenario:
+        'should not show the CTA on non Lite Site pages, when the toggle is false',
+      isLite: false,
+      toggleEnabled: false,
+      shouldBeDisplayed: false,
+    },
+    {
+      testScenario:
+        'should not show the CTA on Lite Site pages, regardless of the toggle',
+      isLite: true,
+      toggleEnabled: true,
+      shouldBeDisplayed: false,
+    },
+  ])('$testScenario', ({ isLite, toggleEnabled, shouldBeDisplayed }) => {
+    render(<ArticlePage pageData={articleDataPersian} />, {
+      service: 'gahuza',
+      isLite,
+      toggles: { liteSiteCTA: { enabled: toggleEnabled } },
+    });
+
+    const liteCTA = screen.queryByRole('link', { name: /Nyandiko gusa/i });
+
+    if (shouldBeDisplayed) {
+      expect(liteCTA).toBeInTheDocument();
+    } else {
+      expect(liteCTA).not.toBeInTheDocument();
+    }
+  });
+
+  it('should apply click and view tracking data on lite site cta link', () => {
+    const eventTrackingData = { componentName: 'canonical-lite-cta' };
+    const clickTrackerSpy = jest.spyOn(clickTracking, 'default');
+    const viewTrackerSpy = jest.spyOn(viewTracking, 'default');
+
+    render(<ArticlePage pageData={articleDataPersian} />, {
+      service: 'gahuza',
+      isLite: false,
+      toggles: { liteSiteCTA: { enabled: true } },
+    });
+
+    expect(clickTrackerSpy).toHaveBeenCalledWith(eventTrackingData);
+    expect(viewTrackerSpy).toHaveBeenCalledWith(eventTrackingData);
+  });
+
   it('should use headline for meta description if summary does not exist', async () => {
     const articleDataNewsWithSummary = mergeDeepLeft(
       {
diff --git a/src/app/pages/ArticlePage/types.ts b/src/app/pages/ArticlePage/types.ts
index eab423df77e..f8ef5c59914 100644
--- a/src/app/pages/ArticlePage/types.ts
+++ b/src/app/pages/ArticlePage/types.ts
@@ -1,7 +1,12 @@
 import { OptimoBlock } from '#app/models/types/optimo';
 
+export type Block = Omit<OptimoBlock, 'model'> & {
+  model: { blocks: OptimoBlock[] };
+};
+
 export type ComponentToRenderProps = {
-  blocks: OptimoBlock[];
+  type: string;
+  blocks: Block[];
 };
 
 export type TimeStampProps = {
diff --git a/src/app/pages/HomePage/HomePage.tsx b/src/app/pages/HomePage/HomePage.tsx
index 1bdac025a6e..bca73a49115 100644
--- a/src/app/pages/HomePage/HomePage.tsx
+++ b/src/app/pages/HomePage/HomePage.tsx
@@ -122,6 +122,7 @@ const HomePage = ({ pageData }: HomePageProps) => {
                       }
                       embed={embed}
                     />
+
                     {index === indexOfFirstNonBanner && <MPU />}
                   </React.Fragment>
                 );
diff --git a/src/integration/pages/articles/canonicalLiteSiteCTA.js b/src/integration/pages/articles/canonicalLiteSiteCTA.js
new file mode 100644
index 00000000000..86cf1ba5b94
--- /dev/null
+++ b/src/integration/pages/articles/canonicalLiteSiteCTA.js
@@ -0,0 +1,11 @@
+export default () => {
+  describe('canonical Lite Site CTA', () => {
+    const liteSiteCTA = document.querySelector("[data-e2e='to-lite-site']");
+    it('should be in the document', () => {
+      expect(liteSiteCTA).toBeInTheDocument();
+    });
+    it('should match snapshot', () => {
+      expect(liteSiteCTA).toMatchSnapshot();
+    });
+  });
+};
diff --git a/src/integration/pages/articles/gahuza/__snapshots__/canonical.test.js.snap b/src/integration/pages/articles/gahuza/__snapshots__/canonical.test.js.snap
new file mode 100644
index 00000000000..c5b94f0d5fe
--- /dev/null
+++ b/src/integration/pages/articles/gahuza/__snapshots__/canonical.test.js.snap
@@ -0,0 +1,461 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Canonical Articles A11y I can see the skip to content link 1`] = `"Simbira ku birimwo"`;
+
+exports[`Canonical Articles Analytics ATI tracking pixel excluding src 1`] = `
+<img
+  alt=""
+  height="1px"
+  style="position:absolute"
+  width="1px"
+/>
+`;
+
+exports[`Canonical Articles Analytics ATI tracking pixel hostname 1`] = `"https://logws1363.ati-host.net/"`;
+
+exports[`Canonical Articles Analytics ATI tracking pixel search params 1`] = `
+[
+  {
+    "s": "598343",
+  },
+  {
+    "s2": "40",
+  },
+  {
+    "p": "gahuza.articles.c5y51yxeg53o.page",
+  },
+  {
+    "x1": "[urn:bbc:optimo:asset:c5y51yxeg53o]",
+  },
+  {
+    "x2": "[responsive]",
+  },
+  {
+    "x3": "[news-gahuza]",
+  },
+  {
+    "x4": "[rw]",
+  },
+  {
+    "x7": "[article]",
+  },
+  {
+    "x8": "[simorgh-nojs]",
+  },
+  {
+    "x9": "[Russia:%20Yulia%20Navalnaya%20yabwiye%20BBC%20ko%20afite%20intego%20yo%20kuziyamamariza%20kuba%20Perezida]",
+  },
+  {
+    "x11": "[2024-10-21T09:23:49.476Z]",
+  },
+  {
+    "x12": "[2024-10-21T09:23:49.476Z]",
+  },
+  {
+    "x13": "[Russia~Politics]",
+  },
+  {
+    "x14": "[39267b85-1784-4f4b-80ed-f8cb4a35f337~75612fa6-147c-4a43-97fa-fcf70d9cced3]",
+  },
+  {
+    "x17": "[Russia~Politics]",
+  },
+]
+`;
+
+exports[`Canonical Articles Footer Anchors should match text and url 1`] = `
+{
+  "text": "BBC News, Gahuza",
+  "url": "/gahuza",
+}
+`;
+
+exports[`Canonical Articles Footer Anchors should match text and url 2`] = `
+{
+  "text": "Igituma ushobora kwizera BBC News",
+  "url": "https://www.bbc.com/gahuza/institutional-49283343",
+}
+`;
+
+exports[`Canonical Articles Footer Anchors should match text and url 3`] = `
+{
+  "text": "Ingingo zo gukoresha urubuga",
+  "url": "https://www.bbc.com/gahuza/institutional-35754053",
+}
+`;
+
+exports[`Canonical Articles Footer Anchors should match text and url 4`] = `
+{
+  "text": "Ibijanye na BBC",
+  "url": "https://www.bbc.com/gahuza/institutional-35754055",
+}
+`;
+
+exports[`Canonical Articles Footer Anchors should match text and url 5`] = `
+{
+  "text": "Ibigenga n'ubuzima bwite",
+  "url": "https://www.bbc.com/gahuza/institutional-35754059",
+}
+`;
+
+exports[`Canonical Articles Footer Anchors should match text and url 6`] = `
+{
+  "text": "Cookies",
+  "url": "https://www.bbc.com/usingthebbc/cookies/",
+}
+`;
+
+exports[`Canonical Articles Footer Anchors should match text and url 7`] = `
+{
+  "text": "Vugana na BBC",
+  "url": "https://www.bbc.co.uk/gahuza/send/u50853291",
+}
+`;
+
+exports[`Canonical Articles Footer Anchors should match text and url 8`] = `
+{
+  "text": "Bona amakuru mu zindi ndimi",
+  "url": "https://www.bbc.com/ws/languages?xtor=CS1-13-[wsgahuza~N~A39~MBC]-[Owned]&utm_source=mktg",
+}
+`;
+
+exports[`Canonical Articles Footer Anchors should match text and url 9`] = `
+{
+  "text": "Soma ibijanye n'aho duhagaze ku mihora ijana ahandi",
+  "url": "https://www.bbc.co.uk/editorialguidelines/guidance/feeds-and-links",
+}
+`;
+
+exports[`Canonical Articles Footer I can click on the BBC branding and it would take me to the homepage 1`] = `"/gahuza"`;
+
+exports[`Canonical Articles Footer Paragraphs should match text 1`] = `"© 2025 BBC. BBC ntibazwa ibivuye ku zindi mbuga. Soma ibijanye n'aho duhagaze ku mihora ijana ahandi"`;
+
+exports[`Canonical Articles Header I can see the branding 1`] = `"BBC News, Gahuza"`;
+
+exports[`Canonical Articles Header Navigation link should match text and url 1`] = `
+{
+  "text": "Urupapuro rw'itangiriro",
+  "url": "/gahuza",
+}
+`;
+
+exports[`Canonical Articles Header Navigation link should match text and url 2`] = `
+{
+  "text": "Ibitero bya M23 muri Congo",
+  "url": "/gahuza/topics/cx2qn9pqx4yt",
+}
+`;
+
+exports[`Canonical Articles Header Navigation link should match text and url 3`] = `
+{
+  "text": "Ibiyaga binini",
+  "url": "/gahuza/topics/c06gq67y3w5t",
+}
+`;
+
+exports[`Canonical Articles Header Navigation link should match text and url 4`] = `
+{
+  "text": "Afrika",
+  "url": "/gahuza/topics/crvnv566zx9t",
+}
+`;
+
+exports[`Canonical Articles Header Navigation link should match text and url 5`] = `
+{
+  "text": "Mpuzamahanga",
+  "url": "/gahuza/topics/c9dvd93jjkkt",
+}
+`;
+
+exports[`Canonical Articles Header Navigation link should match text and url 6`] = `
+{
+  "text": "Ubuzima",
+  "url": "/gahuza/topics/cnq68qp4kxjt",
+}
+`;
+
+exports[`Canonical Articles Header Navigation link should match text and url 7`] = `
+{
+  "text": "Imikino",
+  "url": "/gahuza/topics/c5qvpq0jzy7t",
+}
+`;
+
+exports[`Canonical Articles Header Navigation link should match text and url 8`] = `
+{
+  "text": "Amajwi n’amashusho",
+  "url": "/gahuza/topics/crldzm936jmt",
+}
+`;
+
+exports[`Canonical Articles Image Caption should match text 1`] = `"Insiguro y'isanamu, Alexei Navalny na Yulia Navalnaya bari i Moscow mu 2013 muri metingi yo kwiyamamaza kw'abakuru b'imijyi"`;
+
+exports[`Canonical Articles Image should match snapshot 1`] = `
+<img
+  alt="Alexei Navalny na Yulia Navalnaya bari i Moscow mu 2013 muri metingi yo kwiyamamaza kw'abakuru b'imijyi"
+  class="bbc-139onq"
+  height="864"
+  sizes="(min-width: 1008px) 760px, 100vw"
+  src="https://ichef.bbci.co.uk/ace/ws/640/cpsprodpb/8654/live/6fd0abe0-8f8a-11ef-8e6d-e3e64e16c628.jpg.webp"
+  srcset="https://ichef.bbci.co.uk/ace/ws/240/cpsprodpb/8654/live/6fd0abe0-8f8a-11ef-8e6d-e3e64e16c628.jpg.webp 240w, https://ichef.bbci.co.uk/ace/ws/320/cpsprodpb/8654/live/6fd0abe0-8f8a-11ef-8e6d-e3e64e16c628.jpg.webp 320w, https://ichef.bbci.co.uk/ace/ws/480/cpsprodpb/8654/live/6fd0abe0-8f8a-11ef-8e6d-e3e64e16c628.jpg.webp 480w, https://ichef.bbci.co.uk/ace/ws/624/cpsprodpb/8654/live/6fd0abe0-8f8a-11ef-8e6d-e3e64e16c628.jpg.webp 624w, https://ichef.bbci.co.uk/ace/ws/800/cpsprodpb/8654/live/6fd0abe0-8f8a-11ef-8e6d-e3e64e16c628.jpg.webp 800w"
+  style="aspect-ratio:1536 / 864"
+  width="1536"
+/>
+`;
+
+exports[`Canonical Articles Main heading should match text 1`] = `"‘Nziyamamariza kuba perezida w’Uburusiya Putin navaho’ - Umupfakazi wa Navalny"`;
+
+exports[`Canonical Articles Most Read has item with rank 1`] = `
+{
+  "text": "M23 'yinjiye muri Teritwari ya Walikale'",
+  "url": "https://www.bbc.com/gahuza/articles/cn03d60pp08o",
+}
+`;
+
+exports[`Canonical Articles Most Read has item with rank 2`] = `
+{
+  "text": "‘Nziyamamariza kuba perezida w’Uburusiya Putin navaho’ - Umupfakazi wa Navalny",
+  "url": "https://www.bbc.com/gahuza/articles/c5y51yxeg53o",
+}
+`;
+
+exports[`Canonical Articles Most Read has item with rank 3`] = `
+{
+  "text": "Amerika iri gukora iperereza ku itangazwa rya gahunda y'ibanga ya Israel yo gutera Iran ",
+  "url": "https://www.bbc.com/gahuza/articles/c4gln7xlrdxo",
+}
+`;
+
+exports[`Canonical Articles Most Read has item with rank 4`] = `
+{
+  "text": "Misiri yatangajwe ko nta malaria ikiyirangwamo nyuma y'umuhate umaze imyaka 100 ",
+  "url": "https://www.bbc.com/gahuza/articles/cn4ze4382jzo",
+}
+`;
+
+exports[`Canonical Articles Most Read has item with rank 5`] = `
+{
+  "text": "Hamas: Ni ibiki byabaye ku bayobozi bakuru bayo?",
+  "url": "https://www.bbc.com/gahuza/articles/cgxqxnxylgzo",
+}
+`;
+
+exports[`Canonical Articles SEO Apple Touch Icon should match attributes 1`] = `
+{
+  "sizes": null,
+  "url": "http://localhost:7080/gahuza/images/icons/icon-192x192.png",
+}
+`;
+
+exports[`Canonical Articles SEO Apple Touch Icon should match attributes 2`] = `
+{
+  "sizes": "72x72",
+  "url": "https://static.files.bbci.co.uk/ws/simorgh-assets/public/gahuza/images/icons/icon-72x72.png",
+}
+`;
+
+exports[`Canonical Articles SEO Apple Touch Icon should match attributes 3`] = `
+{
+  "sizes": "96x96",
+  "url": "https://static.files.bbci.co.uk/ws/simorgh-assets/public/gahuza/images/icons/icon-96x96.png",
+}
+`;
+
+exports[`Canonical Articles SEO Apple Touch Icon should match attributes 4`] = `
+{
+  "sizes": "128x128",
+  "url": "https://static.files.bbci.co.uk/ws/simorgh-assets/public/gahuza/images/icons/icon-128x128.png",
+}
+`;
+
+exports[`Canonical Articles SEO Apple Touch Icon should match attributes 5`] = `
+{
+  "sizes": "144x144",
+  "url": "https://static.files.bbci.co.uk/ws/simorgh-assets/public/gahuza/images/icons/icon-144x144.png",
+}
+`;
+
+exports[`Canonical Articles SEO Apple Touch Icon should match attributes 6`] = `
+{
+  "sizes": "152x152",
+  "url": "https://static.files.bbci.co.uk/ws/simorgh-assets/public/gahuza/images/icons/icon-152x152.png",
+}
+`;
+
+exports[`Canonical Articles SEO Apple Touch Icon should match attributes 7`] = `
+{
+  "sizes": "180x180",
+  "url": "https://static.files.bbci.co.uk/ws/simorgh-assets/public/gahuza/images/icons/icon-180x180.png",
+}
+`;
+
+exports[`Canonical Articles SEO Apple Touch Icon should match attributes 8`] = `
+{
+  "sizes": "192x192",
+  "url": "https://static.files.bbci.co.uk/ws/simorgh-assets/public/gahuza/images/icons/icon-192x192.png",
+}
+`;
+
+exports[`Canonical Articles SEO Apple Touch Icon should match attributes 9`] = `
+{
+  "sizes": "384x384",
+  "url": "https://static.files.bbci.co.uk/ws/simorgh-assets/public/gahuza/images/icons/icon-384x384.png",
+}
+`;
+
+exports[`Canonical Articles SEO Apple Touch Icon should match attributes 10`] = `
+{
+  "sizes": "512x512",
+  "url": "https://static.files.bbci.co.uk/ws/simorgh-assets/public/gahuza/images/icons/icon-512x512.png",
+}
+`;
+
+exports[`Canonical Articles SEO Canonical link 1`] = `"http://localhost:7080/gahuza/articles/c5y51yxeg53o"`;
+
+exports[`Canonical Articles SEO Dir attribute 1`] = `"ltr"`;
+
+exports[`Canonical Articles SEO FB app ID 1`] = `"1609039196070050"`;
+
+exports[`Canonical Articles SEO Heading level 1 1`] = `"‘Nziyamamariza kuba perezida w’Uburusiya Putin navaho’ - Umupfakazi wa Navalny"`;
+
+exports[`Canonical Articles SEO Lang attribute 1`] = `"rw"`;
+
+exports[`Canonical Articles SEO Linked data should match text 1`] = `
+{
+  "@context": "http://schema.org",
+  "@graph": [
+    {
+      "@type": "ReportageNewsArticle",
+      "about": [
+        {
+          "@type": "Place",
+          "name": "Uburusiya",
+        },
+        {
+          "@type": "Thing",
+          "name": "Politike",
+          "sameAs": [
+            "http://dbpedia.org/resource/Politics",
+          ],
+        },
+      ],
+      "author": {
+        "@type": "Person",
+        "name": "Katie Razzall",
+      },
+      "dateModified": "2024-10-21T09:23:49.476Z",
+      "datePublished": "2024-10-21T09:23:49.476Z",
+      "description": "Yulia Navalnaya yavuze ibi mu kumurika igitabo umugabo we Alexei Navalny yandikaga ari muri gereza mbere y'uko apfa. ",
+      "headline": "Russia: Yulia Navalnaya yabwiye BBC ko afite intego yo kuziyamamariza kuba Perezida",
+      "image": {
+        "@type": "ImageObject",
+        "height": 576,
+        "url": "https://ichef.bbci.co.uk/news/1024/branded_gahuza/8654/live/6fd0abe0-8f8a-11ef-8e6d-e3e64e16c628.jpg",
+        "width": 1024,
+      },
+      "inLanguage": {
+        "@type": "Language",
+        "alternateName": "rw",
+        "name": "Kinyarwanda",
+      },
+      "mainEntityOfPage": {
+        "@id": "http://localhost:7080/gahuza/articles/c5y51yxeg53o",
+        "@type": "WebPage",
+        "name": "Russia: Yulia Navalnaya yabwiye BBC ko afite intego yo kuziyamamariza kuba Perezida",
+      },
+      "publisher": {
+        "@type": "NewsMediaOrganization",
+        "logo": {
+          "@type": "ImageObject",
+          "height": 576,
+          "url": "https://news.files.bbci.co.uk/ws/img/logos/og/gahuza.png",
+          "width": 1024,
+        },
+        "name": "BBC News Gahuza",
+        "publishingPrinciples": "https://www.bbc.com/gahuza/institutional-49283343",
+      },
+      "thumbnailUrl": "https://ichef.bbci.co.uk/news/1024/branded_gahuza/8654/live/6fd0abe0-8f8a-11ef-8e6d-e3e64e16c628.jpg",
+      "url": "http://localhost:7080/gahuza/articles/c5y51yxeg53o",
+    },
+  ],
+}
+`;
+
+exports[`Canonical Articles SEO OG description 1`] = `"Yulia Navalnaya yavuze ibi mu kumurika igitabo umugabo we Alexei Navalny yandikaga ari muri gereza mbere y'uko apfa. "`;
+
+exports[`Canonical Articles SEO OG image 1`] = `"https://ichef.bbci.co.uk/news/1024/branded_gahuza/8654/live/6fd0abe0-8f8a-11ef-8e6d-e3e64e16c628.jpg"`;
+
+exports[`Canonical Articles SEO OG image alt 1`] = `"Alexei Navalny na Yulia Navalnaya bari i Moscow mu 2013 muri metingi yo kwiyamamaza kw'abakuru b'imijyi"`;
+
+exports[`Canonical Articles SEO OG locale 1`] = `"rw-RW"`;
+
+exports[`Canonical Articles SEO OG site name 1`] = `"BBC News Gahuza"`;
+
+exports[`Canonical Articles SEO OG title 1`] = `"Russia: Yulia Navalnaya yabwiye BBC ko afite intego yo kuziyamamariza kuba Perezida - BBC News Gahuza"`;
+
+exports[`Canonical Articles SEO OG type 1`] = `"article"`;
+
+exports[`Canonical Articles SEO OG url 1`] = `"http://localhost:7080/gahuza/articles/c5y51yxeg53o"`;
+
+exports[`Canonical Articles SEO Page title 1`] = `"Russia: Yulia Navalnaya yabwiye BBC ko afite intego yo kuziyamamariza kuba Perezida - BBC News Gahuza"`;
+
+exports[`Canonical Articles SEO Robots meta tag 1`] = `"noodp, noydir, max-image-preview:large"`;
+
+exports[`Canonical Articles SEO Twitter card 1`] = `"summary_large_image"`;
+
+exports[`Canonical Articles SEO Twitter creator 1`] = `"@bbcgahuza"`;
+
+exports[`Canonical Articles SEO Twitter description 1`] = `"Yulia Navalnaya yavuze ibi mu kumurika igitabo umugabo we Alexei Navalny yandikaga ari muri gereza mbere y'uko apfa. "`;
+
+exports[`Canonical Articles SEO Twitter image alt 1`] = `"Alexei Navalny na Yulia Navalnaya bari i Moscow mu 2013 muri metingi yo kwiyamamaza kw'abakuru b'imijyi"`;
+
+exports[`Canonical Articles SEO Twitter image src 1`] = `"https://ichef.bbci.co.uk/news/1024/branded_gahuza/8654/live/6fd0abe0-8f8a-11ef-8e6d-e3e64e16c628.jpg"`;
+
+exports[`Canonical Articles SEO Twitter site 1`] = `"@bbcgahuza"`;
+
+exports[`Canonical Articles SEO Twitter title 1`] = `"Russia: Yulia Navalnaya yabwiye BBC ko afite intego yo kuziyamamariza kuba Perezida - BBC News Gahuza"`;
+
+exports[`Canonical Articles Timestamp should match text and date 1`] = `
+{
+  "date": "2024-10-21",
+  "text": "21 Ukw'icumi 2024",
+}
+`;
+
+exports[`Canonical Articles canonical Lite Site CTA should match snapshot 1`] = `
+<div
+  class="bbc-1xr610f"
+  data-e2e="to-lite-site"
+>
+  <a
+    class="bbc-ellgq8"
+    href="/gahuza/articles/c5y51yxeg53o.lite"
+  >
+    <div
+      class="bbc-1vp8jwk"
+    >
+      <span
+        class="bbc-kjero8"
+      >
+        <span
+          class="bbc-jqq4rs"
+        >
+          Nyandiko gusa
+        </span>
+        <svg
+          aria-hidden="true"
+          class="bbc-1rykoo5"
+          focusable="false"
+          height="12"
+          viewBox="0 0 32 32"
+          width="12"
+        >
+          <path
+            d="M21.6 14.3L5.5 31h6.4l14.6-15L11.9 1H5.5l16.1 16.7v-3.4z"
+          />
+        </svg>
+      </span>
+    </div>
+  </a>
+</div>
+`;
diff --git a/src/integration/pages/articles/gahuza/canonical.test.js b/src/integration/pages/articles/gahuza/canonical.test.js
new file mode 100644
index 00000000000..0a696308079
--- /dev/null
+++ b/src/integration/pages/articles/gahuza/canonical.test.js
@@ -0,0 +1,14 @@
+/**
+ * @service gahuza
+ * @pathname /gahuza/articles/c5y51yxeg53o
+ */
+
+import canonicalLiteSiteCTA from '../canonicalLiteSiteCTA';
+import runCanonicalTests from '../canonicalTests';
+
+describe('Canonical', () => {
+  describe(pageType, () => {
+    canonicalLiteSiteCTA();
+    runCanonicalTests(service);
+  });
+});


Reviews:
user: login: karinathomasbbc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T14:20:38Z
user: login: Louis-Matsika
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T14:21:40Z
user: login: amoore108
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T14:25:01Z
user: login: Louis-Matsika
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T15:54:35Z
user: login: karinathomasbbc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T15:57:11Z
user: login: Louis-Matsika
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T09:05:02Z
user: login: Louis-Matsika
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T10:00:56Z
user: login: greenc05
body: When I inspect the component (in isolation) in the browser, I see flex CSS, do we need this? Also in this state the focus indicator is full width of the window, should it be?

Have we also created this component for this work? If so, this appears to have some styling but not all of it, please could you explain what this component is doing? https://444-lite-site-cta--5d28eb3fe163f6002046d6fa.chromatic.com/?path=/story/components-call-to-action-link--example
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-19T13:54:33Z
user: login: Louis-Matsika
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T15:08:22Z
user: login: karinathomasbbc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T17:37:35Z
user: login: Isabella-Mitchell
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T08:48:16Z
user: login: Isabella-Mitchell
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T08:48:40Z
user: login: Isabella-Mitchell
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T08:48:47Z
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-20T08:50:47Z
user: login: karinathomasbbc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T09:09:36Z
user: login: karinathomasbbc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T09:19:52Z
user: login: karinathomasbbc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T10:48:30Z
user: login: shayneahchoon
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T11:27:05Z
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-20T16:32:13Z
user: login: karinathomasbbc
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T17:16:26Z
user: login: karinathomasbbc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T09:20:57Z
user: login: shayneahchoon
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T09:52:03Z
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-21T09:54:26Z
user: login: Louis-Matsika
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T14:22:48Z

Review comments:
diff_hunk: @@ -8,3 +8,13 @@ export type TimeStampProps = {
   firstPublished: number;
   lastPublished: number;
 };
+
+export type getHeadlineComponentProps = {
path: src/app/pages/ArticlePage/types.ts
user: login: karinathomasbbc
body: Type naming convention
```suggestion
export type HeadlineComponentProps = {
```
created_at: 2025-03-11T17:21:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -8,3 +8,13 @@ export type TimeStampProps = {
   firstPublished: number;
   lastPublished: number;
 };
+
+export type getHeadlineComponentProps = {
+  pathname: string; 
+  isLite: boolean;
+  translations?: {
+    liteSite?: {
+      articleDataSavingLinkText?: string;
+    };
path: src/app/pages/ArticlePage/types.ts
user: login: karinathomasbbc
body: Rather than duplicating the type definition, could we consider this?

Translations type from https://github.com/bbc/simorgh/blob/3ff04cd8478850a064582f134a7636f2e74ee6f1/src/app/models/types/translations.ts#L2

```suggestion
  translations? Translations
```
created_at: 2025-03-11T17:25:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -141,6 +141,44 @@ afterAll(() => {
 });
 
 describe('Article Page', () => {
+  it.each([
+    {
+      title:
+        'should show the CTA on non Lite Site pages, when the toggle is enabled',
+      isLite: false,
+      liteSiteCTA: true,
+      expected: true,
path: src/app/pages/ArticlePage/index.test.tsx
user: login: karinathomasbbc
body: Can we rename these variables to be more descriptive of what they would do?

```suggestion
      toggleEnabled: true,
      shouldBeDisplayed: true,
```
created_at: 2025-03-13T17:27:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -141,6 +141,44 @@ afterAll(() => {
 });
 
 describe('Article Page', () => {
+  it.each([
+    {
+      title:
path: src/app/pages/ArticlePage/index.test.tsx
user: login: karinathomasbbc
body: **nitpick**: rename to something like testTitle / scenario / testScenario - as title could mean something else
```suggestion
      title:
```
created_at: 2025-03-13T17:28:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,12 @@
+/**
+ * @service gahuza
+ * @pathname /gahuza/articles/c5y51yxeg53o
+ */
+
+import canonicalLiteSiteCTA from '../canonicalLiteSiteCTA';
+
+describe('Canonical', () => {
+  describe(pageType, () => {
+    canonicalLiteSiteCTA('/gahuza/articles/c5y51yxeg53o');
path: src/integration/pages/articles/gahuza/canonical.test.js
user: login: karinathomasbbc
body: While were in here, should we run the other canonical tests for this page? 
created_at: 2025-03-13T17:29:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,8 @@
+export default pathname => {
+  describe('canonical Lite Site CTA', () => {
+    it('should have an inline link', () => {
+      const liteSiteCTA = document.querySelector(`a[href="${pathname}.lite"]`);
+      expect(liteSiteCTA).toBeInTheDocument();
+    });
path: src/integration/pages/articles/canonicalLiteSiteCTA.js
user: login: karinathomasbbc
body: ```suggestion
    it('should be in the document', () => {
      const liteSiteCTA = document.querySelector(`a[href="${pathname}.lite"]`);
      expect(liteSiteCTA).toBeInTheDocument();
    });
```
created_at: 2025-03-17T18:51:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,21 @@
+import React, { PropsWithChildren } from 'react';
+
+import { SerializedStyles } from '@emotion/react';
+import CallToActionLink from '.';
+import { CallToActionLinkProps } from '../CallToActionLink/types';
+
+const Component = ({
+  href,
+  children,
+}: PropsWithChildren<
+  CallToActionLinkProps & { chevronStyles?: () => SerializedStyles }
+>) => <CallToActionLink href={href}>{children}</CallToActionLink>;
+
+export default {
+  title: 'Components/Call To Action Link With Chevron',
+  Component,
+};
+
+export const Example = () => {
+  return <Component href="www.bbc.com/afrique">Call To Action</Component>;
path: src/app/components/CallToActionLinkWithChevron/index.stories.tsx
user: login: karinathomasbbc
body: I try to use this generic link in all stories
```suggestion
  return <Component href="https://www.bbc.com/ws/languages">Call To Action</Component>;
```
created_at: 2025-03-18T14:08:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,27 @@
+import React from 'react';
+import { render, screen } from '../react-testing-library-with-providers';
+import CallToActionLinkWithChevron from '.';
+
+describe('Call To Action Link With Chevron', () => {
+  it('should render children as link text', () => {
+    render(
+      <CallToActionLinkWithChevron href="https://www.bbc.com/send/u94753086">
path: src/app/components/CallToActionLinkWithChevron/index.test.tsx
user: login: karinathomasbbc
body: **nitpick** Use generic link for testing

```suggestion
      <CallToActionLinkWithChevron href="https://www.bbc.com/ws/languages">
```
created_at: 2025-03-18T14:09:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,41 @@
+/** @jsx jsx */
+import { PropsWithChildren, useContext } from 'react';
+import { jsx } from '@emotion/react';
+import { ServiceContext } from '#app/contexts/ServiceContext';
+import { CallToActionLinkProps } from '../CallToActionLink/types';
+import { LeftChevron, RightChevron } from '../icons';
+import CallToActionLink from '../CallToActionLink';
+import Text from '../Text';
+import styles from './index.styles';
+
+const CallToActionLinkWithChevron = ({
+  href,
+  className,
+  children,
+  eventTrackingData,
+  download = false,
+}: PropsWithChildren<CallToActionLinkProps>) => {
+  const { dir } = useContext(ServiceContext);
+  const isRtl = dir === 'rtl';
+
+  return (
+    <CallToActionLink
+      href={href}
+      className={className}
+      eventTrackingData={eventTrackingData}
+      download={download}
path: src/app/components/CallToActionLinkWithChevron/index.tsx
user: login: karinathomasbbc
body: Is `download` here because it is used for another component?
created_at: 2025-03-18T14:10:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,11 +4,11 @@ import path from 'ramda/src/path';
 import pathOr from 'ramda/src/pathOr';
 import { jsx } from '@emotion/react';
 import { ServiceContext } from '../../../contexts/ServiceContext';
+import { LeftChevron, RightChevron } from '../../icons';
+import CallToActionLink from '../../CallToActionLink';
path: src/app/components/Embeds/Uploader/index.tsx
user: login: karinathomasbbc
body: We should probably list the components we need to regression test as part of this refactor - shall we add something to either the JIRA ticket or the PR description?
created_at: 2025-03-18T14:11:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -99,4 +99,30 @@ export default {
         padding: `${spacings.DOUBLE}rem`,
       },
     }),
+  liteCtaContainer: ({ mq }: Theme) =>
path: src/app/pages/ArticlePage/ArticlePage.styles.ts
user: login: karinathomasbbc
body: **nitpick:** For consistency with the names of the other styles in this file
```suggestion
  liteCTAContainer: ({ mq }: Theme) =>
```
created_at: 2025-03-18T14:12:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -119,10 +125,52 @@ const DisclaimerWithPaddingOverride = (props: ComponentToRenderProps) => (
 const getPodcastPromoComponent = (podcastPromoEnabled: boolean) => () =>
   podcastPromoEnabled ? <InlinePodcastPromo /> : null;
 
+const getHeadlineComponent =
path: src/app/pages/ArticlePage/ArticlePage.tsx
user: login: karinathomasbbc
body: Should we consider moving this into its own file next to the Article Page? We do this for the election banner, experiments & page promo sections...
created_at: 2025-03-18T14:14:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,8 @@
+export default pathname => {
+  describe('canonical Lite Site CTA', () => {
+    it('should have an inline link', () => {
+      const liteSiteCTA = document.querySelector(`a[href="${pathname}.lite"]`);
+      expect(liteSiteCTA).toBeInTheDocument();
+    });
path: src/integration/pages/articles/canonicalLiteSiteCTA.js
user: login: karinathomasbbc
body: I wonder if we could make this similar to https://github.com/bbc/simorgh/blob/d97369af82c7fcb92fd51a4ec9fd7730c8d222c7/src/integration/common/liteSite.lite.js#L14 - for integration tests, we usually follow the pattern of: check if it's in the document, snapshot the component

Also, the way in which this test has been written is slightly different to other integration tests, in that we are passing in the `pathname` as a param. We try to use `data-e2e` or `data-testid` attributes (or some other way in which to identify the component), so that we're not having to pass props around. I don't think it's documented anywhere, so it's OK to leave as is for now. Just to make you aware, and perhaps if we added this component to more integration tests, we could refactor then?


created_at: 2025-03-18T14:20:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -119,10 +125,52 @@ const DisclaimerWithPaddingOverride = (props: ComponentToRenderProps) => (
 const getPodcastPromoComponent = (podcastPromoEnabled: boolean) => () =>
   podcastPromoEnabled ? <InlinePodcastPromo /> : null;
 
+const getHeadlineComponent =
path: src/app/pages/ArticlePage/ArticlePage.tsx
user: login: Louis-Matsika
body: I think so, `getHeadlineComponent` is now quite big
created_at: 2025-03-18T14:21:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -99,4 +99,30 @@ export default {
         padding: `${spacings.DOUBLE}rem`,
       },
     }),
+  liteCtaContainer: ({ mq }: Theme) =>
+    css({
+      marginBottom: `${pixelsToRem(20)}rem`,
+      [mq.GROUP_3_MIN_WIDTH]: {
+        marginBottom: `${pixelsToRem(28)}rem`,
+      },
+    }),
+  liteCTA: ({ spacings, mq }: Theme) =>
+    css({
+      display: 'inline-block',
+      marginInlineStart: `${spacings.FULL}rem`,
+
+      [mq.GROUP_2_MIN_WIDTH]: {
+        marginInlineStart: `${spacings.DOUBLE}rem`,
+      },
+      [mq.GROUP_4_MIN_WIDTH]: {
+        marginInlineStart: 0,
+      },
+    }),
+  headlineWithLiteSiteCTA: ({ spacings, mq }: Theme) =>
+    css({
+      paddingBottom: `${spacings.HALF}rem`,
+      [mq.GROUP_3_MIN_WIDTH]: {
+        paddingBottom: `${pixelsToRem(12)}rem`,
+      },
+    }),
path: src/app/pages/ArticlePage/ArticlePage.styles.ts
user: login: amoore108
body: Do we need this class?

Could we not add `marginTop` to the `liteCtaContainer` and have all the spacing logic tied directly to the CTA component?
created_at: 2025-03-18T14:25:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,11 +4,11 @@ import path from 'ramda/src/path';
 import pathOr from 'ramda/src/pathOr';
 import { jsx } from '@emotion/react';
 import { ServiceContext } from '../../../contexts/ServiceContext';
+import { LeftChevron, RightChevron } from '../../icons';
+import CallToActionLink from '../../CallToActionLink';
path: src/app/components/Embeds/Uploader/index.tsx
user: login: Louis-Matsika
body: since we'll be refactoring the `liteSiteCTA` afterwards, for now there are no changes to any other components.

this change was caused by the imports being reordered
created_at: 2025-03-18T15:54:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,11 +4,11 @@ import path from 'ramda/src/path';
 import pathOr from 'ramda/src/pathOr';
 import { jsx } from '@emotion/react';
 import { ServiceContext } from '../../../contexts/ServiceContext';
+import { LeftChevron, RightChevron } from '../../icons';
+import CallToActionLink from '../../CallToActionLink';
path: src/app/components/Embeds/Uploader/index.tsx
user: login: karinathomasbbc
body: Ah my apologies. Makes sense. Do we have a ticket for the refactor? If not, can we create one, and add a link to this thread so we don't forget?
created_at: 2025-03-18T15:57:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,11 +4,11 @@ import path from 'ramda/src/path';
 import pathOr from 'ramda/src/pathOr';
 import { jsx } from '@emotion/react';
 import { ServiceContext } from '../../../contexts/ServiceContext';
+import { LeftChevron, RightChevron } from '../../icons';
+import CallToActionLink from '../../CallToActionLink';
path: src/app/components/Embeds/Uploader/index.tsx
user: login: Louis-Matsika
body: yeah i'll write up a ticket, probably by this Friday. and link this PR and thread 
created_at: 2025-03-19T09:05:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -99,4 +99,30 @@ export default {
         padding: `${spacings.DOUBLE}rem`,
       },
     }),
+  liteCtaContainer: ({ mq }: Theme) =>
+    css({
+      marginBottom: `${pixelsToRem(20)}rem`,
+      [mq.GROUP_3_MIN_WIDTH]: {
+        marginBottom: `${pixelsToRem(28)}rem`,
+      },
+    }),
+  liteCTA: ({ spacings, mq }: Theme) =>
+    css({
+      display: 'inline-block',
+      marginInlineStart: `${spacings.FULL}rem`,
+
+      [mq.GROUP_2_MIN_WIDTH]: {
+        marginInlineStart: `${spacings.DOUBLE}rem`,
+      },
+      [mq.GROUP_4_MIN_WIDTH]: {
+        marginInlineStart: 0,
+      },
+    }),
+  headlineWithLiteSiteCTA: ({ spacings, mq }: Theme) =>
+    css({
+      paddingBottom: `${spacings.HALF}rem`,
+      [mq.GROUP_3_MIN_WIDTH]: {
+        paddingBottom: `${pixelsToRem(12)}rem`,
+      },
+    }),
path: src/app/pages/ArticlePage/ArticlePage.styles.ts
user: login: Louis-Matsika
body: refactoring the `liteCtaContainer` to have `marginTop`does work:

```
liteCTAContainer: ({ spacings, mq }: Theme) =>
    css({
      marginTop: `${spacings.HALF}rem`,
      marginBottom: `${pixelsToRem(20)}rem`,

      [mq.GROUP_3_MIN_WIDTH]: {
        marginTop: `${pixelsToRem(12)}rem`,
        marginBottom: `${pixelsToRem(28)}rem`,
      },
    }),
```

however due to [UX designs](https://www.figma.com/design/SIiOr2YJEa9HAModL7kwzF/Lite-site-initiatives---handoff?node-id=3546-22138&t=sqQQ7KeptASFcVsm-0) its necessary to over ride the styles being applied to the heading.

trying the opposite and reducing spacing on the CTA itself would begin to interfere with the tap target of the button. I'm not too sure what can be done without overriding some heading styles?


I've at least renamed `liteCtaContainer` to `headlineStylesOverride` for now to better describe what these styles do
created_at: 2025-03-19T10:00:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,52 @@
+import { css, Theme } from '@emotion/react';
+import pixelsToRem from '../../utilities/pixelsToRem';
+
+export default {
+  linkText: ({ palette }: Theme) =>
+    css({
+      color: 'inherit',
+      borderBottom: `${pixelsToRem(1)}rem solid ${palette.GREY_10}`,
+      textDecoration: 'none',
+      'a:visited &': {
+        color: palette.METAL,
+        borderBottom: `${pixelsToRem(1)}rem solid ${palette.METAL}`,
path: src/app/components/CallToActionLinkWithChevron/index.styles.ts
user: login: greenc05
body: Is this needed?
created_at: 2025-03-19T13:45:50Z
author_association: NONE
diff_hunk: @@ -0,0 +1,52 @@
+import { css, Theme } from '@emotion/react';
+import pixelsToRem from '../../utilities/pixelsToRem';
+
+export default {
+  linkText: ({ palette }: Theme) =>
+    css({
+      color: 'inherit',
+      borderBottom: `${pixelsToRem(1)}rem solid ${palette.GREY_10}`,
+      textDecoration: 'none',
+      'a:visited &': {
+        color: palette.METAL,
+        borderBottom: `${pixelsToRem(1)}rem solid ${palette.METAL}`,
+      },
+      'a:focus &, a:hover &': {
+        borderBottom: `${pixelsToRem(2)}rem solid ${palette.POSTBOX}`,
+        color: palette.POSTBOX,
+      },
+    }),
+  link: ({ palette }: Theme) =>
+    css({
+      color: palette.GREY_10,
+      textDecoration: 'none',
+      padding: `${pixelsToRem(12)}rem 0 ${pixelsToRem(12)}rem`,
+      '&:focus, &:hover': {
+        textDecoration: 'none',
+      },
+    }),
+  chevron: ({ palette, spacings, mq }: Theme) =>
+    css({
+      color: palette.GREY_10,
+      fill: 'currentColor',
+      marginInlineStart: `${spacings.FULL}rem`,
+      verticalAlign: 'middle',
+      width: `${pixelsToRem(14)}rem`,
+      height: `${pixelsToRem(14)}rem`,
+      'a:visited &': {
+        color: palette.METAL,
+      },
+      'a:focus &, a:hover &': {
+        color: palette.POSTBOX,
+      },
+      [mq.FORCED_COLOURS]: {
+        fill: 'linkText',
+        'a:visited &': {
+          fill: 'visitedText',
+        },
+        'a:active &': {
path: src/app/components/CallToActionLinkWithChevron/index.styles.ts
user: login: greenc05
body: We don't usally do active?
created_at: 2025-03-19T13:47:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,52 @@
+import { css, Theme } from '@emotion/react';
+import pixelsToRem from '../../utilities/pixelsToRem';
+
+export default {
+  linkText: ({ palette }: Theme) =>
+    css({
+      color: 'inherit',
+      borderBottom: `${pixelsToRem(1)}rem solid ${palette.GREY_10}`,
+      textDecoration: 'none',
+      'a:visited &': {
+        color: palette.METAL,
+        borderBottom: `${pixelsToRem(1)}rem solid ${palette.METAL}`,
+      },
+      'a:focus &, a:hover &': {
+        borderBottom: `${pixelsToRem(2)}rem solid ${palette.POSTBOX}`,
+        color: palette.POSTBOX,
+      },
+    }),
+  link: ({ palette }: Theme) =>
+    css({
+      color: palette.GREY_10,
+      textDecoration: 'none',
+      padding: `${pixelsToRem(12)}rem 0 ${pixelsToRem(12)}rem`,
+      '&:focus, &:hover': {
+        textDecoration: 'none',
+      },
+    }),
+  chevron: ({ palette, spacings, mq }: Theme) =>
+    css({
+      color: palette.GREY_10,
+      fill: 'currentColor',
+      marginInlineStart: `${spacings.FULL}rem`,
+      verticalAlign: 'middle',
+      width: `${pixelsToRem(14)}rem`,
+      height: `${pixelsToRem(14)}rem`,
+      'a:visited &': {
+        color: palette.METAL,
+      },
+      'a:focus &, a:hover &': {
+        color: palette.POSTBOX,
+      },
+      [mq.FORCED_COLOURS]: {
+        fill: 'linkText',
+        'a:visited &': {
+          fill: 'visitedText',
+        },
+        'a:active &': {
path: src/app/components/CallToActionLinkWithChevron/index.styles.ts
user: login: Louis-Matsika
body: `a:active &` was being used to get the chevron to match with the text:

<img width="128" alt="Screenshot 2025-03-19 at 2 54 11 PM" src="https://github.com/user-attachments/assets/8f027606-2a86-4845-b3a7-8885d574a496" />
<img width="132" alt="Screenshot 2025-03-19 at 2 56 36 PM" src="https://github.com/user-attachments/assets/f37d7d9f-1341-4880-9441-3e70e7aac968" />

`a:active &` was previously used in another PR [here](https://github.com/bbc/simorgh/blob/5f9419d36ec68205cefa5ec486992d213290c876/src/app/components/LiteSiteCta/index.styles.tsx#L52) where I borrowed styles from.

`a:active &` has been removed now tho


created_at: 2025-03-19T15:08:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,27 @@
+import React from 'react';
+import { render, screen } from '../react-testing-library-with-providers';
+import CallToActionLinkWithChevron from '.';
+
+describe('Call To Action Link With Chevron', () => {
+  it('should render children as link text', () => {
+    render(
+      <CallToActionLinkWithChevron href="https://www.bbc.com/ws/languages">
+        My Link Text
+      </CallToActionLinkWithChevron>,
+    );
+    const link = screen.getByText('My Link Text');
+    expect(link).toBeInTheDocument();
+  });
+
+  it('should render a link with the correct href', () => {
+    render(
+      <CallToActionLinkWithChevron href="https://www.bbc.com/send/u94753086">
+        My Link Text
+      </CallToActionLinkWithChevron>,
+    );
+    const link = screen.getByRole('link');
+    expect(link.getAttribute('href')).toEqual(
+      'https://www.bbc.com/send/u94753086',
path: src/app/components/CallToActionLinkWithChevron/index.test.tsx
user: login: karinathomasbbc
body: **nitpick** (only if you have time): 
```suggestion
      <CallToActionLinkWithChevron href="https://www.bbc.com/ws/languages">
        My Link Text
      </CallToActionLinkWithChevron>,
    );
    const link = screen.getByRole('link');
    expect(link.getAttribute('href')).toEqual(
      'https://www.bbc.com/ws/languages',
```
created_at: 2025-03-19T17:26:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,56 @@
+import React from 'react';
+import { ServiceContextProvider } from '#contexts/ServiceContext';
+import { ToggleContextProvider } from '#contexts/ToggleContext';
+import { RequestContextProvider } from '#contexts/RequestContext';
+import { Services } from '#app/models/types/global';
+import { ARTICLE_PAGE } from '../../../routes/utils/pageTypes';
+import ArticleHeadline from '.';
+import fixture from './fixture';
+
+type Props = {
+  service: Services;
+  isLite?: boolean;
+  toggleEnabled?: boolean;
+};
+
+const ArticleHeadlineComponent = ({
+  service,
+  isLite,
+  toggleEnabled = false,
+}: Props) => (
+  <ToggleContextProvider
+    toggles={{
+      liteSiteCTA: {
+        enabled: toggleEnabled,
+      },
+    }}
+  >
+    <RequestContextProvider
+      isLite={isLite}
+      pageType={ARTICLE_PAGE}
+      pathname="/pathname"
+      service={service}
+    >
+      <ServiceContextProvider service={service}>
+        <ArticleHeadline {...fixture} />
+      </ServiceContextProvider>
+    </RequestContextProvider>
+  </ToggleContextProvider>
+);
+
+export default {
+  title: 'components/ArticleHeadline',
path: src/app/pages/ArticlePage/ArticleHeadline/index.stories.tsx
user: login: karinathomasbbc
body: Can we use the same case as other stories for consistency
```suggestion
  title: 'Components/ArticleHeadline',
```
created_at: 2025-03-19T17:33:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,56 @@
+import React from 'react';
+import { ServiceContextProvider } from '#contexts/ServiceContext';
+import { ToggleContextProvider } from '#contexts/ToggleContext';
+import { RequestContextProvider } from '#contexts/RequestContext';
+import { Services } from '#app/models/types/global';
+import { ARTICLE_PAGE } from '../../../routes/utils/pageTypes';
+import ArticleHeadline from '.';
+import fixture from './fixture';
+
+type Props = {
+  service: Services;
+  isLite?: boolean;
+  toggleEnabled?: boolean;
+};
+
+const ArticleHeadlineComponent = ({
+  service,
+  isLite,
+  toggleEnabled = false,
+}: Props) => (
+  <ToggleContextProvider
+    toggles={{
+      liteSiteCTA: {
+        enabled: toggleEnabled,
+      },
+    }}
+  >
+    <RequestContextProvider
+      isLite={isLite}
+      pageType={ARTICLE_PAGE}
+      pathname="/pathname"
+      service={service}
+    >
+      <ServiceContextProvider service={service}>
+        <ArticleHeadline {...fixture} />
+      </ServiceContextProvider>
+    </RequestContextProvider>
+  </ToggleContextProvider>
+);
+
+export default {
+  title: 'components/ArticleHeadline',
+  component: ArticleHeadline,
+};
+
+export const ArticleHeadlineWithLiteCTA = () => (
+  <ArticleHeadlineComponent service="gahuza" toggleEnabled />
+);
+
+export const ArticleHeadlineWithLiteCTARightToLeft = () => (
+  <ArticleHeadlineComponent service="arabic" toggleEnabled />
+);
+
+export const ArticleHeadlineDefault = () => (
path: src/app/pages/ArticlePage/ArticleHeadline/index.stories.tsx
user: login: karinathomasbbc
body: ```suggestion
export const Example = () => (
```
created_at: 2025-03-19T17:34:31Z
author_association: CONTRIBUTOR
path: src/app/pages/ArticlePage/ArticleHeadline/index.stories.tsx
user: login: karinathomasbbc
body: Would it be overkill to update the ArticlePage stories to include an example with liteSiteCTA enabled? This is probably better for being able to see the component in context. We could even consider removing this story, as we don't really need to see the article headline in isolation...
created_at: 2025-03-19T17:37:31Z
author_association: CONTRIBUTOR
path: src/app/pages/ArticlePage/ArticleHeadline/index.stories.tsx
user: login: Isabella-Mitchell
body: Okies, that's my bad. I thought it would be helpful to have co-located stories for reviewing. But agree the best reviewing experience would be to see it on the Article Page. And then we don't need any new fixture data to do this.
created_at: 2025-03-20T08:48:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,56 @@
+import React from 'react';
+import { ServiceContextProvider } from '#contexts/ServiceContext';
+import { ToggleContextProvider } from '#contexts/ToggleContext';
+import { RequestContextProvider } from '#contexts/RequestContext';
+import { Services } from '#app/models/types/global';
+import { ARTICLE_PAGE } from '../../../routes/utils/pageTypes';
+import ArticleHeadline from '.';
+import fixture from './fixture';
+
+type Props = {
+  service: Services;
+  isLite?: boolean;
+  toggleEnabled?: boolean;
+};
+
+const ArticleHeadlineComponent = ({
+  service,
+  isLite,
+  toggleEnabled = false,
+}: Props) => (
+  <ToggleContextProvider
+    toggles={{
+      liteSiteCTA: {
+        enabled: toggleEnabled,
+      },
+    }}
+  >
+    <RequestContextProvider
+      isLite={isLite}
+      pageType={ARTICLE_PAGE}
+      pathname="/pathname"
+      service={service}
+    >
+      <ServiceContextProvider service={service}>
+        <ArticleHeadline {...fixture} />
+      </ServiceContextProvider>
+    </RequestContextProvider>
+  </ToggleContextProvider>
+);
+
+export default {
+  title: 'components/ArticleHeadline',
+  component: ArticleHeadline,
+};
+
+export const ArticleHeadlineWithLiteCTA = () => (
+  <ArticleHeadlineComponent service="gahuza" toggleEnabled />
+);
+
+export const ArticleHeadlineWithLiteCTARightToLeft = () => (
+  <ArticleHeadlineComponent service="arabic" toggleEnabled />
+);
+
+export const ArticleHeadlineDefault = () => (
path: src/app/pages/ArticlePage/ArticleHeadline/index.stories.tsx
user: login: Isabella-Mitchell
body: Have deleted file - No longer required
created_at: 2025-03-20T08:48:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,56 @@
+import React from 'react';
+import { ServiceContextProvider } from '#contexts/ServiceContext';
+import { ToggleContextProvider } from '#contexts/ToggleContext';
+import { RequestContextProvider } from '#contexts/RequestContext';
+import { Services } from '#app/models/types/global';
+import { ARTICLE_PAGE } from '../../../routes/utils/pageTypes';
+import ArticleHeadline from '.';
+import fixture from './fixture';
+
+type Props = {
+  service: Services;
+  isLite?: boolean;
+  toggleEnabled?: boolean;
+};
+
+const ArticleHeadlineComponent = ({
+  service,
+  isLite,
+  toggleEnabled = false,
+}: Props) => (
+  <ToggleContextProvider
+    toggles={{
+      liteSiteCTA: {
+        enabled: toggleEnabled,
+      },
+    }}
+  >
+    <RequestContextProvider
+      isLite={isLite}
+      pageType={ARTICLE_PAGE}
+      pathname="/pathname"
+      service={service}
+    >
+      <ServiceContextProvider service={service}>
+        <ArticleHeadline {...fixture} />
+      </ServiceContextProvider>
+    </RequestContextProvider>
+  </ToggleContextProvider>
+);
+
+export default {
+  title: 'components/ArticleHeadline',
path: src/app/pages/ArticlePage/ArticleHeadline/index.stories.tsx
user: login: Isabella-Mitchell
body: Have deleted file - No longer required
created_at: 2025-03-20T08:48:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -230,3 +236,19 @@
     chromatic: { disableSnapshot: true },
   },
 };
+
+export const ArticlePageWithLiteSiteCTA = () => (
+  <ComponentWithServiceContext
+    data={articleDataGahuza}
+    service="gahuza"
path: src/app/pages/ArticlePage/index.stories.tsx
user: login: github-advanced-security[bot]
body: ## Hard-coded credentials

The hard-coded value "gahuza" is used as [authorization header](1).

[Show more details](https://github.com/bbc/simorgh/security/code-scanning/200)
created_at: 2025-03-20T08:50:46Z
author_association: NONE
path: src/app/pages/ArticlePage/ArticleHeadline/index.stories.tsx
user: login: karinathomasbbc
body: No worries, it was useful to have initially, I just thought it was best to view the component in context & it made sense on the Article Page
created_at: 2025-03-20T09:09:36Z
author_association: CONTRIBUTOR
path: src/app/pages/ArticlePage/index.stories.tsx
user: login: karinathomasbbc
body: Can we make a couple of changes to the stories? 

Here's what I would like: 
1. A generic story (similar to the example) where I can select the service and the page furniture will automatically change (even though the article content will remain the same, we should still get translations for the selected service). This should be named `ArticlePageWithLiteSiteCTA` but allow service to be passed in as a prop. This should also have chromatic snapshots disabled
2. A story for chromatic (which could be the current `ArticlePageWithLiteSiteCTA` story with gahuza pre-selected, but renamed to `TestArticlePageWithLiteSiteCTA`), which is actually hidden from view in Storybook - here's how we've done this elsewhere: https://github.com/bbc/simorgh/blob/87cffdfb0d417a78b9da91fc26a22baab8e141f8/src/app/pages/HomePage/index.stories.tsx#L92-L99
3. Delete `ArticlePageWithLiteSiteCTARightToLeft` because this will be superseded by 1.

Happy to hop on a call to discuss.
created_at: 2025-03-20T09:19:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,54 @@
+/** @jsx jsx */
+/* @jsxFrag React.Fragment */
+import { jsx } from '@emotion/react';
+import React, { useContext } from 'react';
+import { RequestContext } from '#contexts/RequestContext';
+import useViewTracker from '#app/hooks/useViewTracker';
+import useToggle from '#hooks/useToggle';
+import CallToActionLinkWithChevron from '#app/components/CallToActionLinkWithChevron';
+import { ServiceContext } from '#contexts/ServiceContext';
+import Headings from '#containers/Headings';
+import { ComponentToRenderProps } from '../types';
+import styles from './index.styles';
+
+const ArticleHeadline = (props: ComponentToRenderProps) => {
+  const { pathname, isLite } = useContext(RequestContext);
+  const { translations } = useContext(ServiceContext);
+  const eventTrackingData = { componentName: 'canonical-lite-cta' };
+  const { enabled: showCTA } = useToggle('liteSiteCTA');
+  const viewRef = useViewTracker(eventTrackingData);
+
+  const articleDataSavingLinkText =
+    translations?.liteSite?.articleDataSavingLinkText ?? 'Data-saving Version';
path: src/app/pages/ArticlePage/ArticleHeadline/index.tsx
user: login: karinathomasbbc
body: Feedback from demo: update text
```suggestion
    translations?.liteSite?.articleDataSavingLinkText ?? 'Data saving version';
```
created_at: 2025-03-20T10:48:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,52 @@
+import { css, Theme } from '@emotion/react';
+import pixelsToRem from '../../utilities/pixelsToRem';
+
+export default {
+  linkText: ({ palette }: Theme) =>
+    css({
+      color: 'inherit',
+      borderBottom: `${pixelsToRem(1)}rem solid ${palette.GREY_10}`,
+      textDecoration: 'none',
+      'a:visited &': {
+        color: palette.METAL,
+        borderBottom: `${pixelsToRem(1)}rem solid ${palette.METAL}`,
path: src/app/components/CallToActionLinkWithChevron/index.styles.ts
user: login: shayneahchoon
body: Hello, we need this one to apply the visited styles, inline with the figma docs: https://www.figma.com/design/SIiOr2YJEa9HAModL7kwzF/Lite-site-initiatives---handoff?node-id=3314-760&t=4AsAJHdlUsEQB2Q4-0 
created_at: 2025-03-20T11:27:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -230,3 +236,25 @@
     chromatic: { disableSnapshot: true },
   },
 };
+
+export const ArticlePageWithLiteSiteCTA = (
+  _: StoryArgs,
+  { service }: StoryProps,
+) => (
+  <ComponentWithContext
+    data={articleData}
+    service={service}
+    liteSiteCTAEnabled
+  />
+);
+
+export const TestArticlePageWithLiteSiteCTA = {
+  render: () => (
+    <ComponentWithServiceContext
+      data={articleDataGahuza}
+      service="gahuza"
path: src/app/pages/ArticlePage/index.stories.tsx
user: login: github-advanced-security[bot]
body: ## Hard-coded credentials

The hard-coded value "gahuza" is used as [authorization header](1).

[Show more details](https://github.com/bbc/simorgh/security/code-scanning/201)
created_at: 2025-03-20T16:32:13Z
author_association: NONE
diff_hunk: @@ -230,3 +236,25 @@ export const ArticlePageWithElectionBanner = {
     chromatic: { disableSnapshot: true },
   },
 };
+
+export const ArticlePageWithLiteSiteCTA = (
path: src/app/pages/ArticlePage/index.stories.tsx
user: login: karinathomasbbc
body: Let's disable snapshots, as this is already captured by `TestArticlePageWithLiteSiteCTA`
created_at: 2025-03-20T17:15:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -230,3 +236,25 @@ export const ArticlePageWithElectionBanner = {
     chromatic: { disableSnapshot: true },
   },
 };
+
+export const ArticlePageWithLiteSiteCTA = (
+  _: StoryArgs,
+  { service }: StoryProps,
+) => (
+  <ComponentWithContext
+    data={articleData}
+    service={service}
+    liteSiteCTAEnabled
+  />
+);
+
+export const TestArticlePageWithLiteSiteCTA = {
path: src/app/pages/ArticlePage/index.stories.tsx
user: login: karinathomasbbc
body: One last one - should we have a second snapshot test for RTL with arabic article data? (Optional - we can leave this until we roll out the Lite Site CTA to more services)
created_at: 2025-03-20T17:16:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -230,3 +236,25 @@ export const ArticlePageWithElectionBanner = {
     chromatic: { disableSnapshot: true },
   },
 };
+
+export const ArticlePageWithLiteSiteCTA = (
path: src/app/pages/ArticlePage/index.stories.tsx
user: login: karinathomasbbc
body: cc @Isabella-Mitchell can we do this before we merge, because the UI Tests snapshots are a bit messed up at the moment
created_at: 2025-03-21T09:20:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -230,3 +236,38 @@
     chromatic: { disableSnapshot: true },
   },
 };
+
+export const ArticlePageWithLiteSiteCTA = {
+  render: (_: StoryArgs, { service }: StoryProps) => (
+    <ComponentWithContext
+      data={articleData}
+      service={service}
+      liteSiteCTAEnabled
+    />
+  ),
+  parameters: {
+    chromatic: { disableSnapshot: true },
+  },
+};
+
+export const TestArticlePageWithLiteSiteCTA = {
+  render: () => (
+    <ComponentWithServiceContext
+      data={articleDataGahuza}
+      service="gahuza"
path: src/app/pages/ArticlePage/index.stories.tsx
user: login: github-advanced-security[bot]
body: ## Hard-coded credentials

The hard-coded value "gahuza" is used as [authorization header](1).

[Show more details](https://github.com/bbc/simorgh/security/code-scanning/202)
created_at: 2025-03-21T09:54:26Z
author_association: NONE
diff_hunk: @@ -230,3 +236,38 @@
     chromatic: { disableSnapshot: true },
   },
 };
+
+export const ArticlePageWithLiteSiteCTA = {
+  render: (_: StoryArgs, { service }: StoryProps) => (
+    <ComponentWithContext
+      data={articleData}
+      service={service}
+      liteSiteCTAEnabled
+    />
+  ),
+  parameters: {
+    chromatic: { disableSnapshot: true },
+  },
+};
+
+export const TestArticlePageWithLiteSiteCTA = {
+  render: () => (
+    <ComponentWithServiceContext
+      data={articleDataGahuza}
+      service="gahuza"
+      liteSiteCTAEnabled
+    />
+  ),
+  tags: ['!dev'],
+};
+
+export const TestArticlePageWithLiteSiteCTARightToLeft = {
+  render: () => (
+    <ComponentWithServiceContext
+      data={articleDataGahuza}
+      service="arabic"
path: src/app/pages/ArticlePage/index.stories.tsx
user: login: github-advanced-security[bot]
body: ## Hard-coded credentials

The hard-coded value "arabic" is used as [authorization header](1).

[Show more details](https://github.com/bbc/simorgh/security/code-scanning/203)
created_at: 2025-03-21T09:54:26Z
author_association: NONE
diff_hunk: @@ -4,11 +4,11 @@ import path from 'ramda/src/path';
 import pathOr from 'ramda/src/pathOr';
 import { jsx } from '@emotion/react';
 import { ServiceContext } from '../../../contexts/ServiceContext';
+import { LeftChevron, RightChevron } from '../../icons';
+import CallToActionLink from '../../CallToActionLink';
path: src/app/components/Embeds/Uploader/index.tsx
user: login: Louis-Matsika
body: @karinathomasbbc ticket has been made now https://jira.dev.bbc.co.uk/browse/WORLDSERVICE-543
created_at: 2025-03-21T14:22:48Z
author_association: CONTRIBUTOR

PR comments:
user: login: greenc05
created_at: 2025-03-19T14:01:26Z
body: @Louis-Matsika friendly reminder to read the accessiblity acceptance criteria please, see we are missing the touch target height for starters...
author_association: NONE
user: login: greenc05
created_at: 2025-03-19T14:10:18Z
body: Ok so now I've seen the component on the page and see the touch target height is good. Wondering why though that isn't in the component when it's viewed in isolatoin in storybook?

Would be great if we could start some best practice for PRs (@Isabella-Mitchell does an outstanding job of this), in which we can include links to components in isolatoin and in page please @karinathomasbbc @amoore108  @HarveyPeachey 
author_association: NONE
user: login: amoore108
created_at: 2025-03-19T14:23:26Z
body: > Ok so now I've seen the component on the page and see the touch target height is good. Wondering why though that isn't in the component when it's viewed in isolatoin in storybook?
> 
> Would be great if we could start some best practice for PRs (@Isabella-Mitchell does an outstanding job of this), in which we can include links to components in isolatoin and in page please @karinathomasbbc @amoore108  @HarveyPeachey

Touch target could be related to this comment: https://github.com/bbc/simorgh/pull/12494#discussion_r2001182590

Seems like it needs the header in place for the touch target to be correct, whereas I think we should have all the styling encapsulated within the CTA component itself.
author_association: CONTRIBUTOR
user: login: Isabella-Mitchell
created_at: 2025-03-19T14:39:26Z
body: > When I inspect the component in the browser, I see flex CSS, do we need this?
> 
> Have we also created this component for this work? If so, this appears to have some styling but not all of it, please could you explain what this component is doing? https://444-lite-site-cta--5d28eb3fe163f6002046d6fa.chromatic.com/?path=/story/components-call-to-action-link--example

The answer is a bit confusing since we have some refactoring to do:

For this work we have built the 'Call to Action Link with Chevron' component.

This component is very similar to the link we built for the Lite Site CTA - however this wasn't in a format to be reused.

Now this 'Call to Action Link with Chevron' can be reused, and we will refactor the Lite Site CTA to use this component. (Plus other components hopefully)

This 'Call to Action Link with Chevron' component uses the 'Call to Action Link' component. This is used in the Message Banner and Embed Uploader. In these components it requires Flex.

We intend to do a follow up piece of work with a refactor to reduce duplication across these components.

Furthermore, I don't think the Storybook link is the best way to view this component (something we should look at during refactoring). That's why we've put it on Preview environment. - https://simorgh1.belfrage-preview.test.api.bbc.com/gahuza/articles/cn52pd0q47no?renderer_env=live

author_association: CONTRIBUTOR
user: login: greenc05
created_at: 2025-03-19T14:55:57Z
body: Thanks @Isabella-Mitchell So we also have future work to refactor the call to action link (without chevron)? (Not sure we have any other components in this state)
author_association: NONE
user: login: Isabella-Mitchell
created_at: 2025-03-19T16:07:29Z
body: > > Ok so now I've seen the component on the page and see the touch target height is good. Wondering why though that isn't in the component when it's viewed in isolatoin in storybook?
> > Would be great if we could start some best practice for PRs (@Isabella-Mitchell does an outstanding job of this), in which we can include links to components in isolatoin and in page please @karinathomasbbc @amoore108  @HarveyPeachey
> 
> Touch target could be related to this comment: [#12494 (comment)](https://github.com/bbc/simorgh/pull/12494#discussion_r2001182590)
> 
> Seems like it needs the header in place for the touch target to be correct, whereas I think we should have all the styling encapsulated within the CTA component itself.

We've added display:inline-block to the link styles. This means the <a> is not treated as inline, and so the touch target spacing is shown on Storybook on the[ link in isolation.](https://444-lite-site-cta--5d28eb3fe163f6002046d6fa.chromatic.com/iframe.html?globals=&args=&id=components-call-to-action-link-with-chevron--example). Agree it's something we could look more into during refactoring.

We've added some more stories to help us see the component in context
[LTR](https://444-lite-site-cta--5d28eb3fe163f6002046d6fa.chromatic.com/iframe.html?globals=&args=&id=components-articleheadline--article-headline-with-lite-cta&viewMode=story)
[RTL](https://444-lite-site-cta--5d28eb3fe163f6002046d6fa.chromatic.com/?path=/story/components-articleheadline--article-headline-with-lite-cta-right-to-left&globals=service.service:arabic)
[Heading with no Lite Site CTA](https://444-lite-site-cta--5d28eb3fe163f6002046d6fa.chromatic.com/?path=/story/components-articleheadline--article-headline-default)
author_association: CONTRIBUTOR
user: login: Isabella-Mitchell
created_at: 2025-03-19T16:08:37Z
body: > Thanks @Isabella-Mitchell So we also have future work to refactor the call to action link (without chevron)? (Not sure we have any other components in this state)

We'll put together a ticket for this
author_association: CONTRIBUTOR