diff --git a/dotcom-rendering/src/components/ArticleHeadline.tsx b/dotcom-rendering/src/components/ArticleHeadline.tsx
index 67e3cc50970..e68f9c1874f 100644
--- a/dotcom-rendering/src/components/ArticleHeadline.tsx
+++ b/dotcom-rendering/src/components/ArticleHeadline.tsx
@@ -966,10 +966,13 @@ export const ArticleHeadline = ({
case ArticleDesign.Picture:
return (
);
-export const DecideLines = ({ format, color }: Props) => {
- const count = format.design === ArticleDesign.Comment ? 8 : 4;
+export const DecideLines = ({ format, color, displayingAvatar }: Props) => {
+ const count =
+ format.design === ArticleDesign.Comment || displayingAvatar ? 8 : 4;
switch (format.theme) {
case Pillar.Sport:
- return ;
+ if (format.design !== ArticleDesign.Picture) {
+ return ;
+ }
+ return (
+
+ );
default:
return (
{
return noGutters;
}
}
+ case ArticleDisplay.Showcase:
+ switch (format.design) {
+ case ArticleDesign.Picture:
+ return '';
+ default:
+ return noGutters;
+ }
default:
return noGutters;
}
diff --git a/dotcom-rendering/src/layouts/DecideLayout.tsx b/dotcom-rendering/src/layouts/DecideLayout.tsx
index fa41ad65ca6..f7a64d55a6f 100644
--- a/dotcom-rendering/src/layouts/DecideLayout.tsx
+++ b/dotcom-rendering/src/layouts/DecideLayout.tsx
@@ -17,7 +17,6 @@ import { ImmersiveLayout } from './ImmersiveLayout';
import { InteractiveLayout } from './InteractiveLayout';
import { LiveLayout } from './LiveLayout';
import { NewsletterSignupLayout } from './NewsletterSignupLayout';
-import { PictureLayout } from './PictureLayout';
import { StandardLayout } from './StandardLayout';
interface BaseProps {
@@ -101,15 +100,6 @@ const DecideLayoutApps = ({ article, renderingTarget }: AppProps) => {
serverTime={serverTime}
/>
);
- case ArticleDesign.Picture:
- return (
-
- );
default:
return (
{
serverTime={serverTime}
/>
);
- case ArticleDesign.Picture:
- return (
-
- );
default:
return (
(
-
- {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;
- }
-`;
-
-const mainMediaWrapper = (displayAvatarUrl: boolean) => css`
- position: relative;
- ${until.phablet} {
- margin-left: 20px;
- margin-right: 20px;
- }
- ${until.mobileLandscape} {
- margin-left: 10px;
- margin-right: 10px;
- }
- ${displayAvatarUrl
- ? css`
- margin-top: 8px;
- `
- : ``}
-`;
-
-const avatarHeadlineWrapper = css`
- display: flex;
- flex-direction: column;
- justify-content: space-between;
-`;
-
-// This styling taken from the similar approach in CommentLayout.tsx
-// If in mobile increase the margin top and margin right deficit
-const avatarPositionStyles = css`
- display: flex;
- justify-content: flex-end;
- position: relative;
- margin-bottom: -29px;
- pointer-events: none;
- ${from.desktop} {
- margin-top: -50px;
- }
- ${until.tablet} {
- overflow: hidden;
- }
-
- /* Why target img element?
-
- Because only in this context, where we have overflow: hidden
- and the margin-bottom and margin-top of avatarPositionStyles
- do we also want to apply our margin-right. These styles
- are tightly coupled in this context, and so it does not
- make sense to move them to the avatar component.
-
- It's imperfect from the perspective of DCR, the alternative is to bust
- the combined elements into a separate component (with the
- relevant stories) and couple them that way, which might be what
- you want to do if you find yourself adding more styles
- to this section. For now, this works without making me 🤢.
- */
-
- ${from.mobile} {
- img {
- margin-right: -1.85rem;
- }
- }
- ${from.mobileLandscape} {
- img {
- margin-right: -1.25rem;
- }
- }
-`;
-
-const LeftColLines = (displayAvatarUrl: boolean) => css`
- margin-bottom: 4px;
- ${displayAvatarUrl
- ? css`
- margin-top: -29px;
- `
- : ''}
-`;
-
-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 PictureLayout = (props: WebProps | AppsProps) => {
- const { article, format, renderingTarget, serverTime } = props;
-
- const {
- config: { isPaidContent, host, hasSurveyAd },
- } = article;
-
- const isWeb = renderingTarget === 'Web';
- const isApps = renderingTarget === 'Apps';
-
- // 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.
-
- const showComments = article.isCommentable && !isPaidContent;
-
- const { branding } = article.commercialProperties[article.editionId];
-
- const contributionsServiceUrl = getContributionsServiceUrl(article);
-
- const renderAds = canRenderAds(article);
-
- const isWorldCup2026 = article.tags.some((tag) => tag.id === worldCupTagId);
-
- const avatarUrl = getSoleContributor(
- article.tags,
- article.byline,
- )?.bylineLargeImageUrl;
-
- const displayAvatarUrl = avatarUrl ? true : false;
-
- return (
- <>
- {isWeb && (
-
- {renderAds && (
-
-
-
- )}
- tag.id)}
- sectionId={article.config.section}
- contentType={article.contentType}
- />
-
- )}
-
- {isWeb && renderAds && hasSurveyAd && (
-
- )}
-
-
- {isApps && renderAds && (
-
-
-
- )}
-
-
-
-
-
-
-
-
-
-
- {displayAvatarUrl ? (
-
-
-
-
-
- {!!avatarUrl && (
-
-
-
- )}
-
-
-
-
- ) : (
-
-
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {isApps ? (
- <>
-
-
-
-
-
-
- >
- ) : (
-
- )}
-
-
-
-
-
-
-
-
-
-
-
- {isWeb && renderAds && (
-
- )}
-
- {article.storyPackage && (
-
- )}
-
- {isWeb && (
-
-
-
- )}
- {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 cd5abdf5f3d..cb5aa7a660c 100644
--- a/dotcom-rendering/src/layouts/StandardLayout.tsx
+++ b/dotcom-rendering/src/layouts/StandardLayout.tsx
@@ -228,28 +228,32 @@ export const StandardLayout = (props: WebProps | AppProps) => {
)}
-
-
-
+ {!(format.design === ArticleDesign.Picture && isApps) && (
+
+
+
+ )}
{showComments && (
css`
+ margin-bottom: 4px;
+ ${showingAvatar
+ ? css`
+ margin-top: -29px;
+ `
+ : ''}
+ `;
+
return (
-
+ {displayAvatarUrl && isPicture ? (
+
+
+
+ {!!avatarUrl && isPicture && (
+ <>
+
+
+
+
+ >
+ )}
+
+ ) : (
+
+ )}
+
{isWeb &&
format.theme === ArticleSpecial.Labs &&
format.design !== ArticleDesign.Video ? (
@@ -491,6 +607,9 @@ export const StandardLayoutArticleGrid = ({
)}
@@ -697,30 +816,32 @@ export const StandardLayoutArticleGrid = ({
}
`}
>
-
-
-
-
-
+ {!isPicture && (
+
+
+
+
+
+ )}
);
diff --git a/dotcom-rendering/src/layouts/lib/articleArrangements.ts b/dotcom-rendering/src/layouts/lib/articleArrangements.ts
index 25481511bb6..a40b8a5a5ec 100644
--- a/dotcom-rendering/src/layouts/lib/articleArrangements.ts
+++ b/dotcom-rendering/src/layouts/lib/articleArrangements.ts
@@ -6,6 +6,7 @@ export type LayoutType =
| 'standard'
| 'showcase'
| 'media'
+ | 'picture'
| 'immersiveLandscapeDefault'
| 'immersiveLandscapeFeature'
| 'immersivePortraitDefault'
@@ -165,6 +166,42 @@ const mediaCss: LayoutCssMap = {
},
};
+const pictureCss: LayoutCssMap = {
+ title: {
+ mobile: 'grid-row: 1;',
+ tablet: 'grid-row: 1;',
+ leftCol: grid.column.left,
+ },
+ headline: {
+ mobile: 'grid-row: 2;',
+ tablet: 'grid-row: 2;',
+ desktop: grid.between('centre-column-start', 'right-column-end'),
+ leftCol: 'grid-row: 1;',
+ },
+ standfirst: {
+ mobile: 'grid-row: 4;',
+ tablet: 'grid-row: 4;',
+ leftCol: 'grid-row: 2;',
+ },
+ media: {
+ mobile: 'grid-row: 5;',
+ tablet: 'grid-row: 5;',
+ desktop: grid.between('centre-column-start', 'right-column-end'),
+ leftCol: `grid-row: 3;`,
+ },
+ meta: {
+ mobile: 'grid-row: 3;',
+ tablet: 'grid-row: 3;',
+ desktop: grid.between('centre-column-start', 'right-column-end'),
+ leftCol: `grid-row: 3 / span 2; ${grid.column.left};`,
+ },
+ body: {
+ tablet: 'grid-row: 6;',
+ desktop: grid.between('centre-column-start', 'right-column-end'),
+ leftCol: `grid-row: 5;`,
+ },
+};
+
const immersivePortraitDefaultCss: LayoutCssMap = {
title: {
belowDesktop: `${grid.column.all} grid-row: 2;`,
@@ -328,6 +365,7 @@ const layoutCssMaps: Record = {
standard: standardCss,
showcase: showcaseCss,
media: mediaCss,
+ picture: pictureCss,
immersiveLandscapeDefault: immersiveLandscapeDefaultCss,
immersiveLandscapeFeature: immersiveLandscapeFeatureCss,
immersivePortraitDefault: immersivePortraitDefaultCss,
@@ -383,12 +421,14 @@ export const getLayoutType = ({
isFeature,
orientation,
isMedia,
+ isPicture,
isShowcase,
}: {
isImmersive: boolean;
isFeature: boolean;
orientation: 'portrait' | 'landscape' | 'square';
isMedia: boolean;
+ isPicture: boolean;
isShowcase: boolean;
}): LayoutType => {
if (isImmersive) {
@@ -403,6 +443,7 @@ export const getLayoutType = ({
: 'immersiveLandscapeDefault';
}
if (isMedia) return 'media';
+ if (isPicture) return 'picture';
if (isShowcase) return 'showcase';
return 'standard';
};