diff --git a/dotcom-rendering/src/components/ArticleBody.tsx b/dotcom-rendering/src/components/ArticleBody.tsx
index 233adc190f7..12be4083bf3 100644
--- a/dotcom-rendering/src/components/ArticleBody.tsx
+++ b/dotcom-rendering/src/components/ArticleBody.tsx
@@ -56,6 +56,7 @@ type Props = {
shouldHideAds: boolean;
serverTime?: number;
idApiUrl?: string;
+ isOldInteractive?: boolean;
};
const globalOlStyles = () => css`
@@ -147,6 +148,7 @@ export const ArticleBody = ({
shouldHideAds,
serverTime,
idApiUrl,
+ isOldInteractive = false,
}: Props) => {
const isInteractiveContent =
format.design === ArticleDesign.Interactive ||
@@ -270,6 +272,7 @@ export const ArticleBody = ({
contributionsServiceUrl={contributionsServiceUrl}
shouldHideAds={shouldHideAds}
idApiUrl={idApiUrl}
+ isOldInteractive={isOldInteractive}
/>
{hasObserverPublicationTag && }
diff --git a/dotcom-rendering/src/components/Figure.tsx b/dotcom-rendering/src/components/Figure.tsx
index ccb113fb4b1..d69c3414b1b 100644
--- a/dotcom-rendering/src/components/Figure.tsx
+++ b/dotcom-rendering/src/components/Figure.tsx
@@ -1,5 +1,5 @@
import { css } from '@emotion/react';
-import { breakpoints, from, space, until } from '@guardian/source/foundations';
+import { from, space, until } from '@guardian/source/foundations';
import { ArticleDesign, type ArticleFormat } from '../lib/articleFormat';
import type { FEElement, RoleType } from '../types/content';
@@ -74,47 +74,7 @@ const roleCss = {
margin-top: ${space[3]}px;
margin-bottom: ${space[3]}px;
- ${until.tablet} {
- margin-left: -20px;
- margin-right: -20px;
- }
- ${until.mobileLandscape} {
- margin-left: -10px;
- margin-right: -10px;
- }
- ${from.tablet} {
- --scrollbar-width-fallback: 15px;
- --half-scrollbar-width-fallback: 7.5px;
-
- width: calc(
- 100vw - var(--scrollbar-width, var(--scrollbar-width-fallback))
- );
- max-width: calc(
- 100vw - var(--scrollbar-width, var(--scrollbar-width-fallback))
- );
-
- --grid-container-max-width: 740px;
- --grid-container-left-margin: calc(
- ((-100vw + (var(--grid-container-max-width) - 42px)) / 2) +
- var(
- --half-scrollbar-width,
- var(--half-scrollbar-width-fallback)
- )
- );
-
- margin-left: var(--grid-container-left-margin);
- }
- ${from.desktop} {
- --grid-container-max-width: ${breakpoints.desktop}px;
- }
- ${from.leftCol} {
- --grid-container-max-width: ${breakpoints.leftCol}px;
- --grid-left-col-width: 140px;
- }
- ${from.wide} {
- --grid-container-max-width: ${breakpoints.wide}px;
- --grid-left-col-width: 219px;
- }
+ grid-column: 1 / -1;
`,
showcase: css`
diff --git a/dotcom-rendering/src/components/SubMeta.tsx b/dotcom-rendering/src/components/SubMeta.tsx
index 7be376741d0..c7f116e5cf4 100644
--- a/dotcom-rendering/src/components/SubMeta.tsx
+++ b/dotcom-rendering/src/components/SubMeta.tsx
@@ -130,6 +130,7 @@ type Props = {
webUrl: string;
webTitle: string;
showBottomSocialButtons: boolean;
+ isDeprecatedInteractiveLayout?: boolean;
};
const syndicationButtonOverrides = css`
@@ -205,6 +206,7 @@ export const SubMeta = ({
webUrl,
webTitle,
showBottomSocialButtons,
+ isDeprecatedInteractiveLayout = false,
}: Props) => {
const createLinks = () => {
const links: BaseLinkType[] = [];
@@ -229,9 +231,7 @@ export const SubMeta = ({
{
switch (article.design) {
case ArticleDesign.Interactive:
return (
-
);
-
case ArticleDesign.FullPageInteractive: {
return (
{
switch (article.design) {
case ArticleDesign.Interactive:
return (
-
);
case ArticleDesign.FullPageInteractive: {
diff --git a/dotcom-rendering/src/layouts/FullPageInteractiveLayout.tsx b/dotcom-rendering/src/layouts/FullPageInteractiveLayout.tsx
index 2322ffb5168..3ea39231429 100644
--- a/dotcom-rendering/src/layouts/FullPageInteractiveLayout.tsx
+++ b/dotcom-rendering/src/layouts/FullPageInteractiveLayout.tsx
@@ -27,7 +27,7 @@ import type { ArticleDeprecated } from '../types/article';
import type { ServerSideTests, Switches } from '../types/config';
import type { FEElement } from '../types/content';
import type { RenderingTarget } from '../types/renderingTarget';
-import { temporaryBodyCopyColourOverride } from './InteractiveLayout';
+import { temporaryBodyCopyColourOverride } from './interactives/InteractiveArticleGridDeprecated';
import { interactiveGlobalStyles } from './lib/interactiveLegacyStyling';
import { BannerWrapper, Stuck } from './lib/stickiness';
diff --git a/dotcom-rendering/src/layouts/InteractiveLayout.tsx b/dotcom-rendering/src/layouts/InteractiveLayout.tsx
deleted file mode 100644
index 442be07969b..00000000000
--- a/dotcom-rendering/src/layouts/InteractiveLayout.tsx
+++ /dev/null
@@ -1,834 +0,0 @@
-import { css, Global } from '@emotion/react';
-import {
- from,
- palette as sourcePalette,
- until,
-} from '@guardian/source/foundations';
-import { Hide } from '@guardian/source/react-components';
-import { StraightLines } from '@guardian/source-development-kitchen/react-components';
-import type React from 'react';
-import { AdSlot, MobileStickyContainer } from '../components/AdSlot.web';
-import { AppsFooter } from '../components/AppsFooter.island';
-import { ArticleBody } from '../components/ArticleBody';
-import { ArticleContainer } from '../components/ArticleContainer';
-import { ArticleHeadline } from '../components/ArticleHeadline';
-import { ArticleMetaApps } from '../components/ArticleMeta.apps';
-import { ArticleMeta } from '../components/ArticleMeta.web';
-import { ArticleTitle } from '../components/ArticleTitle';
-import { Border } from '../components/Border';
-import { Carousel } from '../components/Carousel.island';
-import { DecideLines } from '../components/DecideLines';
-import { DirectoryPageNavIsland } from '../components/DirectoryPageNavIsland';
-import { DiscussionLayout } from '../components/DiscussionLayout';
-import { Footer } from '../components/Footer';
-import { GridItem } from '../components/GridItem';
-import { HeaderAdSlot } from '../components/HeaderAdSlot';
-import { InteractivesDisableArticleSwipe } from '../components/InteractivesDisableArticleSwipe.island';
-import { InteractivesNativePlatformWrapper } from '../components/InteractivesNativePlatformWrapper.island';
-import { InteractivesScrollbarWidth } from '../components/InteractivesScrollbarWidth.island';
-import { Island } from '../components/Island';
-import { LabsHeader } from '../components/LabsHeader';
-import { MainMedia } from '../components/MainMedia';
-import { Masthead } from '../components/Masthead/Masthead';
-import { MostViewedFooterData } from '../components/MostViewedFooterData.island';
-import { MostViewedFooterLayout } from '../components/MostViewedFooterLayout';
-import { OnwardsUpper } from '../components/OnwardsUpper.island';
-import { Section } from '../components/Section';
-import { SlotBodyEnd } from '../components/SlotBodyEnd.island';
-import { Standfirst } from '../components/Standfirst';
-import { StickyBottomBanner } from '../components/StickyBottomBanner.island';
-import { SubMeta } from '../components/SubMeta';
-import { SubNav } from '../components/SubNav.island';
-import { type ArticleFormat, ArticleSpecial } from '../lib/articleFormat';
-import { canRenderAds } from '../lib/canRenderAds';
-import { getContributionsServiceUrl } from '../lib/contributions';
-import { decideStoryPackageTrails } from '../lib/decideTrail';
-import type { NavType } from '../model/extract-nav';
-import { palette as themePalette } from '../palette';
-import type { ArticleDeprecated } from '../types/article';
-import type { RoleType } from '../types/content';
-import type { RenderingTarget } from '../types/renderingTarget';
-import {
- interactiveGlobalStyles,
- interactiveLegacyClasses,
-} from './lib/interactiveLegacyStyling';
-import { BannerWrapper, Stuck } from './lib/stickiness';
-
-const InteractiveGrid = ({ children }: { children: React.ReactNode }) => (
-
- {children}
-
-);
-
-const maxWidth = css`
- ${from.desktop} {
- max-width: 620px;
- }
-`;
-
-const stretchLines = css`
- ${until.phablet} {
- margin-left: -20px;
- margin-right: -20px;
- }
- ${until.mobileLandscape} {
- margin-left: -10px;
- margin-right: -10px;
- }
-`;
-
-export const temporaryBodyCopyColourOverride = css`
- .content__main-column--interactive p {
- /* stylelint-disable-next-line declaration-no-important */
- color: ${themePalette('--article-text')} !important;
- }
-`;
-
-interface CommonProps {
- article: ArticleDeprecated;
- format: ArticleFormat;
- renderingTarget: RenderingTarget;
- serverTime?: number;
-}
-
-interface WebProps extends CommonProps {
- NAV: NavType;
- renderingTarget: 'Web';
-}
-
-interface AppsProps extends CommonProps {
- renderingTarget: 'Apps';
-}
-
-export const InteractiveLayout = (props: WebProps | AppsProps) => {
- const { article, format, renderingTarget, serverTime } = props;
- const {
- config: { isPaidContent, host, hasSurveyAd },
- editionId,
- } = article;
-
- const isApps = renderingTarget === 'Apps';
- const isWeb = renderingTarget === 'Web';
-
- const showComments = article.isCommentable && !isPaidContent;
-
- const { branding } = article.commercialProperties[article.editionId];
-
- const contributionsServiceUrl = getContributionsServiceUrl(article);
-
- const renderAds = canRenderAds(article);
-
- const includesFullWidthElement = article.blocks.some((block) =>
- block.elements.some((element) => {
- const role =
- 'role' in element
- ? (element.role as RoleType | 'fullWidth' | undefined)
- : undefined;
- return role === 'fullWidth';
- }),
- );
-
- return (
- <>
- {includesFullWidthElement && (
-
-
-
- )}
- {isApps && (
- <>
-
-
-
-
-
-
-
- >
- )}
- {article.isLegacyInteractive && (
-
- )}
- {isWeb && (
- <>
-
- {renderAds && (
-
-
-
-
-
- )}
-
-
tag.id)}
- sectionId={article.config.section}
- contentType={article.contentType}
- />
-
-
- {format.theme === ArticleSpecial.Labs && (
-
-
-
- )}
-
- {renderAds && hasSurveyAd && (
-
- )}
- >
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {format.theme === ArticleSpecial.Labs ? (
- <>>
- ) : (
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
- {isApps ? (
- <>
-
-
-
-
-
-
- >
- ) : (
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {isWeb && renderAds && (
-
- )}
-
- {article.storyPackage && (
-
- )}
-
-
-
-
-
- {showComments && (
-
- )}
-
- {!isPaidContent && (
-
- )}
-
- {isWeb && renderAds && (
-
- )}
-
-
- {isWeb && props.NAV.subNavSections && (
-
- )}
-
- {isWeb && (
- <>
-
-
-
-
-
-
-
- {renderAds && (
-
- )}
- >
- )}
- {isApps && (
-
- )}
- >
- );
-};
diff --git a/dotcom-rendering/src/layouts/StandardLayout.tsx b/dotcom-rendering/src/layouts/StandardLayout.tsx
index 8d24f6d3b8f..296439d7be9 100644
--- a/dotcom-rendering/src/layouts/StandardLayout.tsx
+++ b/dotcom-rendering/src/layouts/StandardLayout.tsx
@@ -1,51 +1,24 @@
-import { css } from '@emotion/react';
-import { log } from '@guardian/libs';
-import {
- from,
- palette as sourcePalette,
- space,
- until,
-} from '@guardian/source/foundations';
-import { Hide } from '@guardian/source/react-components';
-import { StraightLines } from '@guardian/source-development-kitchen/react-components';
+import { palette as sourcePalette } from '@guardian/source/foundations';
import { AdPortals } from '../components/AdPortals.island';
import { AdSlot, MobileStickyContainer } from '../components/AdSlot.web';
-import { AffiliateDisclaimer } from '../components/AffiliateDisclaimer';
-import { AppsEpic } from '../components/AppsEpic.island';
import { AppsFooter } from '../components/AppsFooter.island';
-import { ArticleBody } from '../components/ArticleBody';
-import { ArticleContainer } from '../components/ArticleContainer';
-import { ArticleHeadline } from '../components/ArticleHeadline';
-import { ArticleMetaApps } from '../components/ArticleMeta.apps';
-import { ArticleMeta } from '../components/ArticleMeta.web';
-import { ArticleTitle } from '../components/ArticleTitle';
import { Carousel } from '../components/Carousel.island';
import { CricketMatchHeaderWrapper } from '../components/CricketMatchHeaderWrapper.island';
-import { DecideLines } from '../components/DecideLines';
import { DirectoryPageNavIsland } from '../components/DirectoryPageNavIsland';
import { DiscussionLayout } from '../components/DiscussionLayout';
import { FootballMatchHeaderWrapper } from '../components/FootballMatchHeaderWrapper.island';
-import { FootballMatchInfoWrapper } from '../components/FootballMatchInfoWrapper.island';
import { Footer } from '../components/Footer';
-import { GuardianLabsLines } from '../components/GuardianLabsLines';
import { HeaderAdSlot } from '../components/HeaderAdSlot';
import { Island } from '../components/Island';
import { LabsHeader } from '../components/LabsHeader';
-import { ListenToArticle } from '../components/ListenToArticle.island';
-import { MainMedia } from '../components/MainMedia';
import { Masthead } from '../components/Masthead/Masthead';
import { MatchHeaderFallback } from '../components/MatchHeaderFallback';
import { MostViewedFooterData } from '../components/MostViewedFooterData.island';
import { MostViewedFooterLayout } from '../components/MostViewedFooterLayout';
-import { MostViewedRightWithAd } from '../components/MostViewedRightWithAd.island';
import { OnwardsUpper } from '../components/OnwardsUpper.island';
import { Section } from '../components/Section';
-import { SlotBodyEnd } from '../components/SlotBodyEnd.island';
-import { Standfirst } from '../components/Standfirst';
import { StickyBottomBanner } from '../components/StickyBottomBanner.island';
-import { SubMeta } from '../components/SubMeta';
import { SubNav } from '../components/SubNav.island';
-import { grid } from '../grid';
import {
ArticleDesign,
ArticleDisplay,
@@ -55,55 +28,16 @@ import {
import { canRenderAds } from '../lib/canRenderAds';
import { getContributionsServiceUrl } from '../lib/contributions';
import { decideStoryPackageTrails } from '../lib/decideTrail';
-import { safeParseURL } from '../lib/parse';
-import { parse } from '../lib/slot-machine-flags';
import { useAB } from '../lib/useAB';
import { worldCupTagId } from '../lib/worldCup2026';
import type { NavType } from '../model/extract-nav';
import { palette as themePalette } from '../palette';
import type { ArticleDeprecated } from '../types/article';
import type { RenderingTarget } from '../types/renderingTarget';
-import {
- type Area,
- gridItemCss,
- type LayoutType,
-} from './lib/articleArrangements';
+import { InteractiveArticleGridDeprecated } from './interactives/InteractiveArticleGridDeprecated';
+import { type LayoutType } from './lib/articleArrangements';
import { BannerWrapper, Stuck } from './lib/stickiness';
-
-const stretchLines = css`
- ${until.phablet} {
- margin-left: -20px;
- margin-right: -20px;
- }
- ${until.mobileLandscape} {
- margin-left: -10px;
- margin-right: -10px;
- }
-`;
-
-interface GridItemProps {
- area: Area;
- layoutType: LayoutType;
- element?: 'div' | 'aside';
- className?: string;
- children: React.ReactNode;
-}
-
-const GridItem = ({
- area,
- layoutType,
- element: Element = 'div',
- className,
- children,
-}: GridItemProps) => (
-
- {children}
-
-);
+import { StandardLayoutContentGrid } from './StandardLayoutContentGrid';
interface Props {
article: ArticleDeprecated;
@@ -112,12 +46,12 @@ interface Props {
serverTime?: number;
}
-interface WebProps extends Props {
+export interface WebProps extends Props {
NAV: NavType;
renderingTarget: 'Web';
}
-interface AppProps extends Props {
+export interface AppProps extends Props {
renderingTarget: 'Apps';
}
@@ -131,11 +65,6 @@ export const StandardLayout = (props: WebProps | AppProps) => {
const isWeb = renderingTarget === 'Web';
const isApps = renderingTarget === 'Apps';
- const showBodyEndSlot =
- isWeb &&
- (parse(article.slotMachineFlags ?? '').showBodyEnd ||
- article.config.switches.slotBodyEnd);
-
// TODO:
// 1) Read 'forceEpic' value from URL parameter and use it to force the slot to render
// 2) Otherwise, ensure slot only renders if `article.config.shouldHideReaderRevenue` equals false.
@@ -145,11 +74,6 @@ export const StandardLayout = (props: WebProps | AppProps) => {
? article.matchUrl
: undefined;
- const footballMatchStatsUrl =
- article.matchType === 'FootballMatchType'
- ? article.matchStatsUrl
- : undefined;
-
const isFootballMatchReport =
format.design === ArticleDesign.MatchReport && !!footballMatchUrl;
@@ -164,14 +88,15 @@ export const StandardLayout = (props: WebProps | AppProps) => {
const isMedia =
format.design === ArticleDesign.Video ||
format.design === ArticleDesign.Audio;
-
- const isVideo = format.design === ArticleDesign.Video;
-
const isShowcase = format.display === ArticleDisplay.Showcase;
+ const isInteractive = format.design === ArticleDesign.Interactive;
- const showComments = article.isCommentable && !isPaidContent;
+ const interactiveLayoutSwitchoverDate = new Date('2024-06-01T00:00:00Z');
+ const publicationDate = new Date(article.webPublicationDate);
+ const isLegacyInteractive =
+ publicationDate < interactiveLayoutSwitchoverDate;
- const { branding } = article.commercialProperties[article.editionId];
+ const showComments = article.isCommentable && !isPaidContent;
const contributionsServiceUrl = getContributionsServiceUrl(article);
@@ -185,7 +110,9 @@ export const StandardLayout = (props: WebProps | AppProps) => {
? 'media'
: isShowcase
? 'showcase'
- : 'standard';
+ : isInteractive
+ ? 'interactive'
+ : 'standard';
return (
<>
@@ -265,362 +192,14 @@ export const StandardLayout = (props: WebProps | AppProps) => {
See furnitureArrangements.ts if reordering. */}
{/* This element is used to replace the article with the scorecard when the scorecard tab is clicked */}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ ) : (
+
-
- {isWeb &&
- format.theme === ArticleSpecial.Labs &&
- format.design !== ArticleDesign.Video ? (
-
- ) : (
-
- )}
-
- {isApps ? (
- <>
-
-
-
-
-
- {!!article.affiliateLinksDisclaimer && (
-
- )}
-
- >
- ) : (
- <>
-
- {!!article.affiliateLinksDisclaimer && (
-
- )}
- >
- )}
-
-
- {/* Only show Listen to Article button on App landscape views */}
- {isApps && (
-
- {!isVideo && (
-
-
-
-
-
- )}
-
- )}
-
-
-
-
- {isApps && (
-
-
-
- )}
-
- {showBodyEndSlot && (
-
-
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
+ />
+ )}
{isWeb && renderAds && !isLabs && (
{
- if (isMatchReport && !!footballMatchStatsUrl) {
- const parsedUrl = safeParseURL(footballMatchStatsUrl);
- if (!parsedUrl.ok) {
- log(
- 'dotcom',
- new Error(
- `Failed to parse match stats URL: ${footballMatchStatsUrl}`,
- ),
- );
-
- return null;
- }
- return (
-
-
-
- );
- }
-
- return null;
-};
diff --git a/dotcom-rendering/src/layouts/StandardLayoutContentGrid.tsx b/dotcom-rendering/src/layouts/StandardLayoutContentGrid.tsx
new file mode 100644
index 00000000000..6051a0097c7
--- /dev/null
+++ b/dotcom-rendering/src/layouts/StandardLayoutContentGrid.tsx
@@ -0,0 +1,453 @@
+import { css } from '@emotion/react';
+import { log } from '@guardian/libs';
+import { from, space, until } from '@guardian/source/foundations';
+import { Hide } from '@guardian/source/react-components';
+import { StraightLines } from '@guardian/source-development-kitchen/react-components';
+import { AffiliateDisclaimer } from '../components/AffiliateDisclaimer';
+import { AppsEpic } from '../components/AppsEpic.island';
+import { ArticleBody } from '../components/ArticleBody';
+import { ArticleContainer } from '../components/ArticleContainer';
+import { ArticleHeadline } from '../components/ArticleHeadline';
+import { ArticleMetaApps } from '../components/ArticleMeta.apps';
+import { ArticleMeta } from '../components/ArticleMeta.web';
+import { ArticleTitle } from '../components/ArticleTitle';
+import { DecideLines } from '../components/DecideLines';
+import { FootballMatchInfoWrapper } from '../components/FootballMatchInfoWrapper.island';
+import { GuardianLabsLines } from '../components/GuardianLabsLines';
+import { Island } from '../components/Island';
+import { ListenToArticle } from '../components/ListenToArticle.island';
+import { MainMedia } from '../components/MainMedia';
+import { MostViewedRightWithAd } from '../components/MostViewedRightWithAd.island';
+import { SlotBodyEnd } from '../components/SlotBodyEnd.island';
+import { Standfirst } from '../components/Standfirst';
+import { SubMeta } from '../components/SubMeta';
+import { grid } from '../grid';
+import {
+ ArticleDesign,
+ ArticleDisplay,
+ ArticleSpecial,
+} from '../lib/articleFormat';
+import { getContributionsServiceUrl } from '../lib/contributions';
+import { safeParseURL } from '../lib/parse';
+import { parse } from '../lib/slot-machine-flags';
+import { palette as themePalette } from '../palette';
+import {
+ type Area,
+ gridItemCss,
+ type LayoutType,
+} from './lib/articleArrangements';
+import type { AppProps, WebProps } from './StandardLayout';
+
+const stretchLines = css`
+ ${until.phablet} {
+ margin-left: -20px;
+ margin-right: -20px;
+ }
+ ${until.mobileLandscape} {
+ margin-left: -10px;
+ margin-right: -10px;
+ }
+`;
+
+interface GridItemProps {
+ area: Area;
+ layoutType: LayoutType;
+ element?: 'div' | 'aside';
+ className?: string;
+ children: React.ReactNode;
+}
+
+const GridItem = ({
+ area,
+ layoutType,
+ element: Element = 'div',
+ className,
+ children,
+}: GridItemProps) => (
+
+ {children}
+
+);
+
+export const StandardLayoutContentGrid = ({
+ props,
+ layoutType,
+}: {
+ props: WebProps | AppProps;
+ layoutType: LayoutType;
+}) => {
+ const { article, format, renderingTarget } = props;
+ const {
+ config: { host },
+ } = article;
+ const isWeb = renderingTarget === 'Web';
+ const isApps = renderingTarget === 'Apps';
+ const renderAds = isWeb && !article.shouldHideAds;
+
+ const contributionsServiceUrl = getContributionsServiceUrl(article);
+
+ const showBodyEndSlot =
+ isWeb &&
+ (parse(article.slotMachineFlags ?? '').showBodyEnd ||
+ article.config.switches.slotBodyEnd);
+
+ const { branding } = article.commercialProperties[article.editionId];
+
+ const footballMatchStatsUrl =
+ article.matchType === 'FootballMatchType'
+ ? article.matchStatsUrl
+ : undefined;
+
+ const isLabs = format.theme === ArticleSpecial.Labs;
+ const isMedia =
+ format.design === ArticleDesign.Video ||
+ format.design === ArticleDesign.Audio;
+
+ const isVideo = format.design === ArticleDesign.Video;
+
+ const footballMatchUrl =
+ article.matchType === 'FootballMatchType'
+ ? article.matchUrl
+ : undefined;
+
+ const isFootballMatchReport =
+ format.design === ArticleDesign.MatchReport && !!footballMatchUrl;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {isWeb &&
+ format.theme === ArticleSpecial.Labs &&
+ format.design !== ArticleDesign.Video ? (
+
+ ) : (
+
+ )}
+
+ {isApps ? (
+ <>
+
+
+
+
+
+ {!!article.affiliateLinksDisclaimer && (
+
+ )}
+
+ >
+ ) : (
+ <>
+
+ {!!article.affiliateLinksDisclaimer && (
+
+ )}
+ >
+ )}
+
+
+ {/* Only show Listen to Article button on App landscape views */}
+ {isApps && (
+
+ {!isVideo && (
+
+
+
+
+
+ )}
+
+ )}
+
+
+
+
+ {isApps && (
+
+
+
+ )}
+
+ {showBodyEndSlot && (
+
+
+
+ )}
+
+
+
+
+ {layoutType !== 'interactive' && (
+
+
+
+
+
+
+
+ )}
+
+ );
+};
+
+const MatchInfoContainer = ({
+ isMatchReport,
+ footballMatchStatsUrl,
+}: {
+ isMatchReport: boolean;
+ footballMatchStatsUrl: string | undefined;
+}) => {
+ if (isMatchReport && !!footballMatchStatsUrl) {
+ const parsedUrl = safeParseURL(footballMatchStatsUrl);
+ if (!parsedUrl.ok) {
+ log(
+ 'dotcom',
+ new Error(
+ `Failed to parse match stats URL: ${footballMatchStatsUrl}`,
+ ),
+ );
+
+ return null;
+ }
+ return (
+
+
+
+ );
+ }
+
+ return null;
+};
diff --git a/dotcom-rendering/src/layouts/interactives/InteractiveArticleGridDeprecated.tsx b/dotcom-rendering/src/layouts/interactives/InteractiveArticleGridDeprecated.tsx
new file mode 100644
index 00000000000..3f2635c63d9
--- /dev/null
+++ b/dotcom-rendering/src/layouts/interactives/InteractiveArticleGridDeprecated.tsx
@@ -0,0 +1,384 @@
+import { css } from '@emotion/react';
+import { from, until } from '@guardian/source/foundations';
+import { Hide } from '@guardian/source/react-components';
+import { ArticleBody } from '../../components/ArticleBody';
+import { ArticleContainer } from '../../components/ArticleContainer';
+import { ArticleHeadline } from '../../components/ArticleHeadline';
+import { ArticleMetaApps } from '../../components/ArticleMeta.apps';
+import { ArticleMeta } from '../../components/ArticleMeta.web';
+import { ArticleTitle } from '../../components/ArticleTitle';
+import { Border } from '../../components/Border';
+import { DecideLines } from '../../components/DecideLines';
+import { GridItem } from '../../components/GridItem';
+import { MainMedia } from '../../components/MainMedia';
+import { Section } from '../../components/Section';
+import { Standfirst } from '../../components/Standfirst';
+import { type ArticleFormat, ArticleSpecial } from '../../lib/articleFormat';
+import { getContributionsServiceUrl } from '../../lib/contributions';
+import type { NavType } from '../../model/extract-nav';
+import { palette as themePalette } from '../../palette';
+import type { ArticleDeprecated } from '../../types/article';
+import type { RenderingTarget } from '../../types/renderingTarget';
+import { interactiveLegacyClasses } from '../lib/interactiveLegacyStyling';
+
+const InteractiveGrid = ({ children }: { children: React.ReactNode }) => (
+
+ {children}
+
+);
+
+const maxWidth = css`
+ ${from.desktop} {
+ max-width: 620px;
+ }
+`;
+
+const stretchLines = css`
+ ${until.phablet} {
+ margin-left: -20px;
+ margin-right: -20px;
+ }
+ ${until.mobileLandscape} {
+ margin-left: -10px;
+ margin-right: -10px;
+ }
+`;
+
+interface Props {
+ article: ArticleDeprecated;
+ format: ArticleFormat;
+ renderingTarget: RenderingTarget;
+ serverTime?: number;
+}
+
+interface WebProps extends Props {
+ NAV: NavType;
+ renderingTarget: 'Web';
+}
+
+interface AppProps extends Props {
+ renderingTarget: 'Apps';
+}
+
+export const temporaryBodyCopyColourOverride = css`
+ .content__main-column--interactive p {
+ /* stylelint-disable-next-line declaration-no-important */
+ color: ${themePalette('--article-text')} !important;
+ }
+`;
+
+export const InteractiveArticleGridDeprecated = (
+ props: WebProps | AppProps,
+) => {
+ const { article, format, renderingTarget } = props;
+ const {
+ config: { host },
+ } = article;
+ const isApps = renderingTarget === 'Apps';
+
+ const contributionsServiceUrl = getContributionsServiceUrl(article);
+
+ const { branding } = article.commercialProperties[article.editionId];
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ {format.theme === ArticleSpecial.Labs ? (
+ <>>
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+ {isApps ? (
+ <>
+
+
+
+
+
+
+ >
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/dotcom-rendering/src/layouts/lib/articleArrangements.ts b/dotcom-rendering/src/layouts/lib/articleArrangements.ts
index 2cf5fb6843f..2e4323eb67e 100644
--- a/dotcom-rendering/src/layouts/lib/articleArrangements.ts
+++ b/dotcom-rendering/src/layouts/lib/articleArrangements.ts
@@ -2,7 +2,7 @@ import { css, type SerializedStyles } from '@emotion/react';
import { from, until } from '@guardian/source/foundations';
import { grid } from '../../grid';
-export type LayoutType = 'standard' | 'showcase' | 'media';
+export type LayoutType = 'standard' | 'showcase' | 'media' | 'interactive';
export type Area =
| 'title'
@@ -149,10 +149,42 @@ const mediaCss: LayoutCssMap = {
},
};
+const interactiveCss: LayoutCssMap = {
+ title: {
+ tablet: 'grid-row: 1;',
+ leftCol: `grid-row: 1; ${grid.column.left}`,
+ },
+ headline: {
+ tablet: 'grid-row: 2;',
+ leftCol: 'grid-row: 1;',
+ },
+ standfirst: {
+ tablet: 'grid-row: 3;',
+ leftCol: 'grid-row: 2;',
+ },
+ media: {
+ tablet: 'grid-row: 4;',
+ leftCol: 'grid-row: 3;',
+ },
+ meta: {
+ tablet: 'grid-row: 5;',
+ leftCol: `grid-row: 3 / span 2; ${grid.column.left};`,
+ },
+ body: {
+ tablet: `grid-row: 6; ${grid.column.all};`,
+ leftCol: 'grid-row: 4;',
+ },
+ 'right-column': {
+ desktop: `grid-row: 1 / span 6; ${grid.column.right};`,
+ leftCol: `grid-row: 1 / span 4; ${grid.column.right};`,
+ },
+};
+
const layoutCssMaps: Record = {
standard: standardCss,
showcase: showcaseCss,
media: mediaCss,
+ interactive: interactiveCss,
};
/**
diff --git a/dotcom-rendering/src/lib/ArticleRenderer.tsx b/dotcom-rendering/src/lib/ArticleRenderer.tsx
index c5878173c14..13cc697e423 100644
--- a/dotcom-rendering/src/lib/ArticleRenderer.tsx
+++ b/dotcom-rendering/src/lib/ArticleRenderer.tsx
@@ -3,6 +3,7 @@ import { Fragment } from 'react';
import { useConfig } from '../components/ConfigContext';
import { FeastContextualNudge } from '../components/FeastContextualNudge.island';
import { Island } from '../components/Island';
+import { grid } from '../grid';
import { interactiveLegacyClasses } from '../layouts/lib/interactiveLegacyStyling';
import type { ServerSideTests, Switches } from '../types/config';
import type { FEElement, RecipeBlockElement } from '../types/content';
@@ -40,6 +41,7 @@ type Props = {
contributionsServiceUrl: string;
shouldHideAds: boolean;
idApiUrl?: string;
+ isOldInteractive?: boolean;
};
export const ArticleRenderer = ({
@@ -64,6 +66,7 @@ export const ArticleRenderer = ({
contributionsServiceUrl,
shouldHideAds,
idApiUrl,
+ isOldInteractive = false,
}: Props) => {
const isSectionedMiniProfilesArticle =
elements.filter(
@@ -193,6 +196,13 @@ export const ArticleRenderer = ({
// ^^ Until we decide where to do the "isomorphism split" in this this code is not safe here.
// But should be soon.
+ const interactiveLayoutCSS = css`
+ ${grid.container}
+ > * {
+ ${grid.column.centre}
+ }
+ `;
+
return (
{renderingTarget === 'Apps'
? augmentedElements