diff --git a/dotcom-rendering/playwright/tests/commercial.e2e.spec.ts b/dotcom-rendering/playwright/tests/commercial.e2e.spec.ts
index 61c96351056..db823202659 100644
--- a/dotcom-rendering/playwright/tests/commercial.e2e.spec.ts
+++ b/dotcom-rendering/playwright/tests/commercial.e2e.spec.ts
@@ -19,7 +19,7 @@ test.describe('Commercial E2E tests', () => {
'merchandising',
];
- const totalSlotsExpected = 15; // All slots, even if unfilled ie. `display: none`
+ const totalSlotsExpected = 16; // All slots, even if unfilled ie. `display: none`
const inlineSlots = totalSlotsExpected - fixedSlots.length;
// We are excluding survey slot as they can be switched off
diff --git a/dotcom-rendering/playwright/tests/lightbox.e2e.spec.ts b/dotcom-rendering/playwright/tests/lightbox.e2e.spec.ts
index 5b902439b82..f5e3becad75 100644
--- a/dotcom-rendering/playwright/tests/lightbox.e2e.spec.ts
+++ b/dotcom-rendering/playwright/tests/lightbox.e2e.spec.ts
@@ -109,7 +109,7 @@ test.describe('Lightbox', () => {
// Open lightbox by clicking on the fourth image within the article body
// Lightbox should open at the fifth image as the first image is the main media image
- await page.locator('article img').nth(3).click({ force: true });
+ await page.locator('button.open-lightbox').nth(4).click();
await expectToBeVisible(page, '#gu-lightbox');
diff --git a/dotcom-rendering/src/components/ArticleHeadline.tsx b/dotcom-rendering/src/components/ArticleHeadline.tsx
index 3b0c141fc86..0d32869f847 100644
--- a/dotcom-rendering/src/components/ArticleHeadline.tsx
+++ b/dotcom-rendering/src/components/ArticleHeadline.tsx
@@ -18,6 +18,7 @@ import {
until,
} from '@guardian/source/foundations';
import { grid } from '../../src/grid';
+import { type LayoutType } from '../layouts/lib/articleArrangements';
import { interactiveLegacyClasses } from '../layouts/lib/interactiveLegacyStyling';
import { getAgeWarning } from '../lib/age-warning';
import {
@@ -27,7 +28,6 @@ import {
ArticleSpecial,
Pillar,
} from '../lib/articleFormat';
-import { getZIndex } from '../lib/getZIndex';
import { palette as themePalette } from '../palette';
import type { StarRating as Rating } from '../types/content';
import type { TagType } from '../types/tag';
@@ -39,6 +39,7 @@ import { StarRating } from './StarRating/StarRating';
type Props = {
headlineString: string;
format: ArticleFormat;
+ layoutType?: LayoutType;
byline?: string;
tags: TagType[];
webPublicationDateDeprecated: string;
@@ -214,30 +215,20 @@ const invertedStyles = css`
box-decoration-break: clone;
`;
-const immersiveStyles = css`
- min-height: 112px;
- padding-bottom: ${space[6]}px;
- padding-left: ${space[1]}px;
-
- ${from.mobileLandscape} {
- padding-left: ${space[3]}px;
- }
-
- ${from.tablet} {
- padding-left: ${space[1]}px;
- }
-
- margin-right: ${space[5]}px;
-`;
-
const darkBackground = css`
background-color: ${themePalette('--headline-background')};
`;
const invertedText = css`
- white-space: pre-wrap;
- padding-bottom: ${space[1]}px;
- padding-right: ${space[1]}px;
+ ${from.desktop} {
+ color: white;
+ background-color: black;
+ white-space: pre-wrap;
+ padding-bottom: ${space[1]}px;
+ padding-right: ${space[1]}px;
+ margin-left: -10px;
+ padding-left: 10px;
+ }
`;
const maxWidth = css`
@@ -255,35 +246,6 @@ const invertedWrapper = css`
margin-left: 6px;
`;
-const immersiveWrapper = css`
- /*
- Make sure we vertically align the headline font with the body font
- */
- margin-left: 6px;
-
- ${from.tablet} {
- margin-left: 16px;
- }
-
- ${from.leftCol} {
- margin-left: 25px;
- }
-
- /*
- We need this grow to ensure the headline fills the main content column
- */
- flex-grow: 1;
- /*
- This z-index is what ensures the headline text shows above the pseudo black
- box that extends the black background to the right
- */
- z-index: ${getZIndex('articleHeadline')};
-
- ${until.mobileLandscape} {
- margin-right: 40px;
- }
-`;
-
// Due to MainMedia using position: relative, this seems to effect the rendering order
// To mitigate we use z-index
// TODO: find a cleaner solution
@@ -292,59 +254,58 @@ const zIndex = css`
`;
const ageWarningMargins = (format: ArticleFormat) => {
- if (format.design === ArticleDesign.Gallery) {
+ if (
+ format.design === ArticleDesign.Gallery ||
+ format.display === ArticleDisplay.Immersive
+ ) {
return '';
}
- return format.display === ArticleDisplay.Immersive
- ? css`
- margin-left: 0px;
- margin-bottom: 0px;
-
- ${from.tablet} {
- margin-left: 10px;
- }
+ return css`
+ margin-top: 12px;
+ margin-left: -10px;
+ margin-bottom: 6px;
- ${from.leftCol} {
- margin-left: 20px;
- }
- `
- : css`
- margin-top: 12px;
- margin-left: -10px;
- margin-bottom: 6px;
-
- ${from.tablet} {
- margin-left: -20px;
- }
+ ${from.tablet} {
+ margin-left: -20px;
+ }
- ${from.leftCol} {
- margin-left: -10px;
- margin-top: 0;
- }
- `;
+ ${from.leftCol} {
+ margin-left: -10px;
+ margin-top: 0;
+ }
+ `;
};
-const backgroundStyles = css`
- background-color: ${themePalette('--age-warning-wrapper-background')};
-`;
-
const WithAgeWarning = ({
tags,
webPublicationDateDeprecated,
format,
children,
+ snapToInverted = false,
}: {
tags: TagType[];
webPublicationDateDeprecated: string;
format: ArticleFormat;
children: React.ReactNode;
+ snapToInverted?: boolean;
}) => {
const age = getAgeWarning(tags, webPublicationDateDeprecated);
if (age) {
return (
<>
-
+
{children}
@@ -433,6 +394,7 @@ const galleryStyles = css`
export const ArticleHeadline = ({
headlineString,
format,
+ layoutType,
tags,
byline,
webPublicationDateDeprecated,
@@ -440,6 +402,7 @@ export const ArticleHeadline = ({
isMatch,
starRating,
}: Props) => {
+ const isInverted = layoutType === 'immersiveLandscapeDefault';
switch (format.display) {
case ArticleDisplay.Immersive: {
switch (format.design) {
@@ -465,12 +428,13 @@ export const ArticleHeadline = ({
format.theme === ArticleSpecial.Labs
? labsFont
: headlineFont(format),
- invertedText,
- css`
- color: ${themePalette(
- '--headline-colour',
- )};
- `,
+ isInverted
+ ? [invertedText, darkBackground]
+ : css`
+ color: ${themePalette(
+ '--headline-colour',
+ )};
+ `,
]}
>
{headlineString}
@@ -496,16 +460,17 @@ export const ArticleHeadline = ({
webPublicationDateDeprecated
}
format={format}
+ snapToInverted={true}
>
diff --git a/dotcom-rendering/src/components/ArticleMeta.web.tsx b/dotcom-rendering/src/components/ArticleMeta.web.tsx
index 5abd00eede4..71013659d5a 100644
--- a/dotcom-rendering/src/components/ArticleMeta.web.tsx
+++ b/dotcom-rendering/src/components/ArticleMeta.web.tsx
@@ -1,9 +1,11 @@
import { css } from '@emotion/react';
import { isUndefined } from '@guardian/libs';
import { between, from, space, until } from '@guardian/source/foundations';
+import { Hide } from '@guardian/source/react-components';
import { StraightLines } from '@guardian/source-development-kitchen/react-components';
import type { CSSProperties } from 'react';
import type { FEArticle } from '../frontend/feArticle';
+import type { LayoutType } from '../layouts/lib/articleArrangements';
import { interactiveLegacyClasses } from '../layouts/lib/interactiveLegacyStyling';
import {
ArticleDesign,
@@ -20,6 +22,7 @@ import {
} from '../lib/articleMeta';
import { getAudioData } from '../lib/audio-data';
import { getSoleContributor } from '../lib/byline';
+import { decideMainMediaCaption } from '../lib/decide-caption';
import { palette as themePalette } from '../palette';
import { hasPreferredSourceButton } from '../preferredSource';
import type { Branding as BrandingType } from '../types/branding';
@@ -27,6 +30,7 @@ import type { FEElement } from '../types/content';
import type { TagType } from '../types/tag';
import { Avatar } from './Avatar';
import { Branding } from './Branding.island';
+import { Caption } from './Caption';
import { CommentCount } from './CommentCount.island';
import { useConfig } from './ConfigContext';
import { Contributor } from './Contributor';
@@ -40,6 +44,7 @@ import { TimeDateline } from './TimeDateline';
type Props = {
format: ArticleFormat;
+ layoutType?: LayoutType;
pageId: string;
webTitle: string;
byline?: string;
@@ -106,11 +111,7 @@ const metaFlex = css`
`;
const preferredSourceMetaFlex = (hasButton: boolean): CSSProperties =>
- hasButton
- ? {
- marginBottom: 8,
- }
- : {};
+ hasButton ? { marginBottom: 8 } : {};
const stretchLines = css`
display: block;
@@ -149,11 +150,7 @@ const metaExtras = (isPictureContent: boolean) => css`
`;
const preferredSourceMetaExtras = (hasButton: boolean): CSSProperties =>
- hasButton
- ? {
- paddingTop: 8,
- }
- : {};
+ hasButton ? { paddingTop: 8 } : {};
const metaNumbers = (isPictureContent: boolean) => css`
border-top: 1px solid ${themePalette('--article-border')};
@@ -246,6 +243,25 @@ const MetaAvatarContainer = ({ children }: { children: React.ReactNode }) => (
);
+const ImmersiveMetaAvatarContainer = ({
+ children,
+}: {
+ children: React.ReactNode;
+}) => (
+
+ {children}
+
+);
+
const RowBelowLeftCol = ({ children }: { children: React.ReactNode }) => (
- <>
+ {isImmersive && isPortraitOrLandscapeImmersive ? (
+
{!!avatarUrl && (
-
+
-
+
)}
{isAudio && podcast && seriesTag && (
@@ -400,9 +430,66 @@ export const ArticleMeta = ({
/>
)}
- >
-
+
+ ) : (
+
+ <>
+ {!!avatarUrl && (
+
+
+
+ )}
+
+ {isAudio && podcast && seriesTag && (
+
+ )}
+
+ {shouldShowContributor(format) && (
+
+ )}
+
+ {crossword?.creator && (
+
+ )}
+ {!isUndefined(webPublicationDate) &&
+ isFilterArticle ? (
+
+ ) : (
+
+ )}
+
+ >
+
+ )}
{showPreferredSource ?
: null}
+ {format.display === ArticleDisplay.Immersive &&
+ mainMediaElements?.[0] && (
+
+
+
+
+
+ )}
);
diff --git a/dotcom-rendering/src/components/ArticleTitle.tsx b/dotcom-rendering/src/components/ArticleTitle.tsx
index de44e1eb319..e78a65319ea 100644
--- a/dotcom-rendering/src/components/ArticleTitle.tsx
+++ b/dotcom-rendering/src/components/ArticleTitle.tsx
@@ -1,6 +1,7 @@
import { css } from '@emotion/react';
import { from } from '@guardian/source/foundations';
import { grid } from '../../src/grid';
+import type { LayoutType } from '../layouts/lib/articleArrangements';
import {
ArticleDesign,
ArticleDisplay,
@@ -11,8 +12,9 @@ import { SeriesSectionLink } from './SeriesSectionLink';
type Props = {
format: ArticleFormat;
- tags: TagType[];
+ layoutType?: LayoutType;
sectionLabel: string;
+ tags: TagType[];
sectionUrl: string;
guardianBaseURL: string;
isMatch?: boolean;
@@ -28,17 +30,11 @@ const sectionStyles = css`
`;
const immersiveMargins = css`
- max-width: 400px;
+ max-width: 500px;
min-width: 200px;
margin-bottom: 4px;
- /*
- Make sure we vertically align the title font with the body font
- */
${from.tablet} {
- margin-left: 16px;
- }
- ${from.leftCol} {
- margin-left: 25px;
+ margin-left: -4px;
}
`;
@@ -57,6 +53,7 @@ const galleryStyles = css`
export const ArticleTitle = ({
format,
+ layoutType,
tags,
sectionLabel,
sectionUrl,
@@ -80,6 +77,7 @@ export const ArticleTitle = ({
>
{
+const CameraIcon = ({ isMainMedia }: IconProps) => {
return (
-
+
);
};
-const VideoIcon = ({ format, isMainMedia }: IconProps) => {
+const VideoIcon = ({ isMainMedia }: IconProps) => {
return (
-
+
);
@@ -325,9 +305,9 @@ export const Caption = ({
data-spacefinder-role="inline"
>
{mediaType === 'YoutubeVideo' || mediaType === 'SelfHostedVideo' ? (
-
+
) : (
-
+
)}
{!!captionText && (
{
- const count = format.design === ArticleDesign.Comment ? 8 : 4;
+ const count =
+ format.design === ArticleDesign.Comment
+ ? 8
+ : format.display === ArticleDisplay.Immersive
+ ? 1
+ : 4;
switch (format.theme) {
case Pillar.Sport:
diff --git a/dotcom-rendering/src/components/Figure.tsx b/dotcom-rendering/src/components/Figure.tsx
index ccb113fb4b1..83a444ac608 100644
--- a/dotcom-rendering/src/components/Figure.tsx
+++ b/dotcom-rendering/src/components/Figure.tsx
@@ -1,6 +1,15 @@
import { css } from '@emotion/react';
-import { breakpoints, from, space, until } from '@guardian/source/foundations';
+import {
+ breakpoints,
+ from,
+ palette as sourcePalette,
+ space,
+ until,
+} from '@guardian/source/foundations';
+import type { LayoutType } from '../layouts/lib/articleArrangements';
import { ArticleDesign, type ArticleFormat } from '../lib/articleFormat';
+import { transparentColour } from '../lib/transparentColour';
+import { palette } from '../palette';
import type { FEElement, RoleType } from '../types/content';
type Props = {
@@ -12,8 +21,35 @@ type Props = {
className?: string;
type?: FEElement['_type'];
isTimeline?: boolean;
+ articleArrangement?: LayoutType;
};
+const overlayMaskGradientStyles = (angle: string) => css`
+ mask-image: linear-gradient(
+ ${angle},
+ rgb(0, 0, 0) 0%,
+ rgba(0, 0, 0, 0.9619) 12.5%,
+ rgba(0, 0, 0, 0.8536) 25%,
+ rgba(0, 0, 0, 0.6913) 37.5%,
+ rgba(0, 0, 0, 0.5) 50%,
+ rgba(0, 0, 0, 0.3087) 62.5%,
+ rgba(0, 0, 0, 0.1464) 75%,
+ rgba(0, 0, 0, 0.0381) 87.5%,
+ transparent 100%
+ );
+`;
+
+const blurStyles = css`
+ position: absolute;
+ inset: 0;
+ background-color: ${palette('--article-background')};
+ backdrop-filter: blur(12px) brightness(0.5);
+ @supports not (backdrop-filter: blur(12px)) {
+ background-color: ${transparentColour(sourcePalette.neutral[10], 0.7)};
+ }
+ ${overlayMaskGradientStyles('0deg')};
+`;
+
const roleCss = {
inline: css`
margin-top: ${space[3]}px;
@@ -263,6 +299,7 @@ export const Figure = ({
className = '',
type,
isTimeline = false,
+ articleArrangement = 'standard',
}: Props) => {
if (isMainMedia && !isTimeline) {
// Don't add in-body styles for main media elements
@@ -273,6 +310,17 @@ export const Figure = ({
return (
{children}
+ {articleArrangement === 'immersiveLandscapeFeature' && (
+
+ )}
);
}
diff --git a/dotcom-rendering/src/components/ImageBlockComponent.tsx b/dotcom-rendering/src/components/ImageBlockComponent.tsx
index 2171767212a..fab73cf3ccd 100644
--- a/dotcom-rendering/src/components/ImageBlockComponent.tsx
+++ b/dotcom-rendering/src/components/ImageBlockComponent.tsx
@@ -1,3 +1,4 @@
+import type { LayoutType } from '../layouts/lib/articleArrangements';
import type { ArticleFormat } from '../lib/articleFormat';
import type { ImageBlockElement } from '../types/content';
import { ImageComponent } from './ImageComponent';
@@ -10,6 +11,7 @@ type Props = {
isMainMedia?: boolean;
isAvatar?: boolean;
isTimeline?: boolean;
+ articleArrangement?: LayoutType;
};
export const ImageBlockComponent = ({
@@ -20,6 +22,7 @@ export const ImageBlockComponent = ({
isMainMedia,
isAvatar,
isTimeline = false,
+ articleArrangement,
}: Props) => {
const { role } = element;
return (
@@ -32,6 +35,7 @@ export const ImageBlockComponent = ({
title={title}
isAvatar={isAvatar}
isTimeline={isTimeline}
+ articleArrangement={articleArrangement}
/>
);
};
diff --git a/dotcom-rendering/src/components/ImageComponent.tsx b/dotcom-rendering/src/components/ImageComponent.tsx
index acf1d4678ab..d7ad02686a9 100644
--- a/dotcom-rendering/src/components/ImageComponent.tsx
+++ b/dotcom-rendering/src/components/ImageComponent.tsx
@@ -9,6 +9,7 @@ import {
palette as srcPalette,
until,
} from '@guardian/source/foundations';
+import type { LayoutType } from '../layouts/lib/articleArrangements';
import {
ArticleDesign,
ArticleDisplay,
@@ -34,6 +35,7 @@ type Props = {
title?: string;
isAvatar?: boolean;
isTimeline?: boolean;
+ articleArrangement?: LayoutType;
};
const timelineBulletStyles = css`
@@ -244,6 +246,7 @@ export const ImageComponent = ({
title,
isAvatar,
isTimeline = false,
+ articleArrangement,
}: Props) => {
const { renderingTarget } = useConfig();
// Its possible the tools wont send us any images urls
@@ -270,6 +273,8 @@ export const ImageComponent = ({
}
const isWeb = renderingTarget === 'Web';
+ const isGridImmersive =
+ articleArrangement?.startsWith('immersive') ?? false;
/**
* We use height and width for two things.
@@ -303,10 +308,10 @@ export const ImageComponent = ({
always be used if display === 'immersive' */
height: 100%;
width: 100%;
- min-height: 25rem;
+ min-height: ${isGridImmersive ? 0 : '25rem'};
${from.desktop} {
- min-height: 31.25rem;
+ min-height: ${isGridImmersive ? 0 : '31.25rem'};
}
img {
diff --git a/dotcom-rendering/src/components/MainMedia.tsx b/dotcom-rendering/src/components/MainMedia.tsx
index 871c4606715..3a2d30e23f5 100644
--- a/dotcom-rendering/src/components/MainMedia.tsx
+++ b/dotcom-rendering/src/components/MainMedia.tsx
@@ -1,5 +1,6 @@
import { css } from '@emotion/react';
import { breakpoints, space, until } from '@guardian/source/foundations';
+import type { LayoutType } from '../layouts/lib/articleArrangements';
import {
ArticleDesign,
ArticleDisplay,
@@ -12,6 +13,7 @@ import type { Switches } from '../types/config';
import type { FEElement } from '../types/content';
const mainMedia = css`
+ position: relative;
height: 100%;
${until.tablet} {
@@ -95,6 +97,7 @@ type Props = {
shouldHideAds: boolean;
contentType?: string;
contentLayout?: string;
+ articleArrangement?: LayoutType;
};
export const MainMedia = ({
@@ -112,6 +115,7 @@ export const MainMedia = ({
shouldHideAds,
contentType,
contentLayout,
+ articleArrangement,
}: Props) => {
return (
@@ -134,6 +138,7 @@ export const MainMedia = ({
shouldHideAds={shouldHideAds}
contentType={contentType}
contentLayout={contentLayout}
+ articleArrangement={articleArrangement}
/>
))}
diff --git a/dotcom-rendering/src/components/SeriesSectionLink.tsx b/dotcom-rendering/src/components/SeriesSectionLink.tsx
index 803a78ba186..c10fe3cdfd5 100644
--- a/dotcom-rendering/src/components/SeriesSectionLink.tsx
+++ b/dotcom-rendering/src/components/SeriesSectionLink.tsx
@@ -11,6 +11,7 @@ import {
textSansBold20,
until,
} from '@guardian/source/foundations';
+import type { LayoutType } from '../layouts/lib/articleArrangements';
import { interactiveLegacyClasses } from '../layouts/lib/interactiveLegacyStyling';
import {
ArticleDesign,
@@ -26,6 +27,7 @@ import { PulsingDot } from './PulsingDot.island';
type Props = {
format: ArticleFormat;
+ layoutType?: LayoutType;
tags: TagType[];
sectionLabel: string;
sectionUrl: string;
@@ -190,6 +192,7 @@ const sectionPadding = css`
export const SeriesSectionLink = ({
format,
+ layoutType,
tags,
sectionLabel,
sectionUrl,
@@ -376,7 +379,38 @@ export const SeriesSectionLink = ({
);
}
- // Immersives show nothing at all if there's no series tag
+ if (
+ layoutType === 'immersivePortraitDefault' ||
+ layoutType === 'immersivePortraitFeature' ||
+ layoutType === 'immersiveLandscapeDefault' ||
+ layoutType === 'immersiveLandscapeFeature'
+ ) {
+ return (
+ <>
+
+ {sectionLabel}
+
+ >
+ );
+ }
+ // Other types of immersives show nothing at all if there's no series tag
return null;
}
if (tag) {
diff --git a/dotcom-rendering/src/components/Standfirst.tsx b/dotcom-rendering/src/components/Standfirst.tsx
index 0567d1dbc99..c517894fcaa 100644
--- a/dotcom-rendering/src/components/Standfirst.tsx
+++ b/dotcom-rendering/src/components/Standfirst.tsx
@@ -213,7 +213,6 @@ const decidePadding = ({ display, design }: ArticleFormat) => {
case ArticleDisplay.Immersive:
return css`
padding-bottom: 0;
- padding-top: ${space[2]}px;
${from.tablet} {
padding-bottom: 0;
@@ -238,7 +237,6 @@ const decidePadding = ({ display, design }: ArticleFormat) => {
case ArticleDisplay.Immersive:
return css`
padding-bottom: 0;
- padding-top: ${space[2]}px;
${from.tablet} {
padding-bottom: 0;
@@ -268,7 +266,6 @@ const standfirstStyles = ({ display, design, theme }: ArticleFormat) => {
`;
default:
return css`
- max-width: 280px;
${from.tablet} {
max-width: 460px;
}
diff --git a/dotcom-rendering/src/grid.ts b/dotcom-rendering/src/grid.ts
index a02e5c3f861..5e7a0111455 100644
--- a/dotcom-rendering/src/grid.ts
+++ b/dotcom-rendering/src/grid.ts
@@ -159,6 +159,7 @@ const outerRules = (color?: string): string => `
&::before {
grid-column: centre-column-start;
transform: translateX(-${columnGap});
+ z-index: 10;
${fromBreakpoint.leftCol} {
grid-column: left-column-start;
@@ -169,6 +170,7 @@ const outerRules = (color?: string): string => `
&::after {
grid-column: right-column-end;
transform: translateX(-1px);
+ z-index: 10;
${betweenBreakpoint.tablet.and.desktop} {
grid-column: centre-column-end;
diff --git a/dotcom-rendering/src/layouts/DecideLayout.tsx b/dotcom-rendering/src/layouts/DecideLayout.tsx
index 77803138313..3d828bdc45b 100644
--- a/dotcom-rendering/src/layouts/DecideLayout.tsx
+++ b/dotcom-rendering/src/layouts/DecideLayout.tsx
@@ -9,7 +9,6 @@ import { GalleryLayout } from './GalleryLayout';
import { HostedArticleLayout } from './HostedArticleLayout';
import { HostedGalleryLayout } from './HostedGalleryLayout';
import { HostedVideoLayout } from './HostedVideoLayout';
-import { ImmersiveLayout } from './ImmersiveLayout';
import { InteractiveLayout } from './InteractiveLayout';
import { LiveLayout } from './LiveLayout';
import { NewsletterSignupLayout } from './NewsletterSignupLayout';
@@ -55,7 +54,7 @@ const DecideLayoutApps = ({ article, renderingTarget }: AppProps) => {
}
default: {
return (
- {
}
default: {
return (
- (
-
- {children}
-
-);
-
-const maxWidth = css`
- ${from.desktop} {
- max-width: 620px;
- }
-`;
-
-const linesMargin = css`
- ${from.leftCol} {
- margin-top: ${space[5]}px;
- }
-`;
-
-const stretchLines = css`
- ${until.phablet} {
- margin-left: -20px;
- margin-right: -20px;
- }
- ${until.mobileLandscape} {
- margin-left: -10px;
- margin-right: -10px;
- }
-`;
-
-interface CommonProps {
- article: ArticleDeprecated;
- format: ArticleFormat;
- serverTime?: number;
-}
-
-interface WebProps extends CommonProps {
- NAV: NavType;
- renderingTarget: 'Web';
-}
-
-interface AppProps extends CommonProps {
- renderingTarget: 'Apps';
-}
-
-const Box = ({ children }: { children: React.ReactNode }) => (
-
- {children}
-
-);
-
-export const ImmersiveLayout = (props: WebProps | AppProps) => {
- const { article, format, renderingTarget, serverTime } = props;
-
- const {
- config: { isPaidContent, host, hasSurveyAd },
- editionId,
- } = article;
- 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.
-
- const showComments = article.isCommentable && !isPaidContent;
-
- const mainMedia = article.mainMediaElements[0];
-
- const captionText = decideMainMediaCaption(mainMedia);
-
- const HEADLINE_OFFSET = mainMedia ? 120 : 0;
-
- const { branding } = article.commercialProperties[article.editionId];
-
- const contributionsServiceUrl = getContributionsServiceUrl(article);
-
- const isLabs = format.theme === ArticleSpecial.Labs;
-
- /**
- We need change the height values depending on whether the labs header is there or not to keep
- the headlines appearing at a consistent height between labs and non labs immersive articles.
- */
-
- const labsHeaderHeight = LABS_HEADER_HEIGHT;
- const combinedHeight = (minHeaderHeightPx + labsHeaderHeight).toString();
-
- const navAndLabsHeaderHeight = isLabs
- ? `${combinedHeight}px`
- : `${minHeaderHeightPx}px`;
-
- const hasMainMediaStyles = css`
- height: calc(80vh - ${navAndLabsHeaderHeight});
- /**
- 80vh is normally enough but don't let the content shrink vertically too
- much just in case
- */
- min-height: calc(25rem - ${navAndLabsHeaderHeight});
- ${from.desktop} {
- height: calc(100vh - ${navAndLabsHeaderHeight});
- min-height: calc(31.25rem - ${navAndLabsHeaderHeight});
- }
- ${from.wide} {
- min-height: calc(50rem - ${navAndLabsHeaderHeight});
- }
- `;
- const LeftColCaption = () => (
-
-
-
- );
-
- const renderAds = canRenderAds(article);
-
- return (
- <>
- {isWeb && (
- tag.id)}
- sectionId={article.config.section}
- contentType={article.contentType}
- />
- )}
-
-
-
- {format.theme === ArticleSpecial.Labs && (
-
-
-
- )}
-
-
-
-
-
- {mainMedia && (
- <>
-
- >
- )}
-
-
- {isWeb && renderAds && hasSurveyAd && (
-
- )}
-
-
- {isApps && renderAds && (
-
-
-
- )}
-
-
- {/* Above leftCol, the Caption is controlled by Section ^^ */}
-
-
-
-
-
-
- {format.design === ArticleDesign.PhotoEssay ? (
- <>>
- ) : (
-
- )}
-
-
- <>
- {!mainMedia && (
-
- )}
- >
-
-
- <>
- {!mainMedia && (
-
- )}
- >
-
-
-
-
-
- {!!article.byline && (
-
- )}
- {/* Only show Listen to Article button on App landscape views */}
- {isApps && (
-
-
-
-
-
-
-
- )}
-
-
- {format.design === ArticleDesign.PhotoEssay &&
- !isLabs ? (
- <>>
- ) : (
-
-
- {format.theme ===
- ArticleSpecial.Labs ? (
-
- ) : (
-
- )}
-
-
- )}
-
- {isApps ? (
- <>
-
-
-
-
-
- {!!article.affiliateLinksDisclaimer && (
-
- )}
-
- >
- ) : (
- <>
-
- {!!article.affiliateLinksDisclaimer && (
-
- )}
- >
- )}
-
-
-
-
-
- {showBodyEndSlot && (
-
-
-
- )}
-
-
-
-
-
-
-
- <>
- {mainMedia && isWeb && renderAds && (
-
- )}
- >
-
-
-
-
-
- {!isLabs && isWeb && renderAds && (
-
- )}
-
- {article.storyPackage && (
-
- )}
-
-
-
-
-
- {showComments && (
-
- )}
- {!isPaidContent && (
-
- )}
- {!isLabs && 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 8a855b830a5..b3536cefd1f 100644
--- a/dotcom-rendering/src/layouts/StandardLayout.tsx
+++ b/dotcom-rendering/src/layouts/StandardLayout.tsx
@@ -62,6 +62,8 @@ export const StandardLayout = (props: WebProps | AppProps) => {
const isWeb = renderingTarget === 'Web';
const isApps = renderingTarget === 'Apps';
+ const contentLayoutName = `${ArticleDisplay[format.display]}Layout`;
+
// 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.
@@ -116,7 +118,9 @@ export const StandardLayout = (props: WebProps | AppProps) => {
idApiUrl={article.config.idApiUrl}
contributionsServiceUrl={contributionsServiceUrl}
showSubNav={!isLabs && !isWorldCup2026}
- showSlimNav={false}
+ showSlimNav={
+ format.display === ArticleDisplay.Immersive
+ }
hasPageSkinContentSelfConstrain={true}
pageId={article.pageId}
tagIds={article.tags.map((tag) => tag.id)}
@@ -157,7 +161,7 @@ export const StandardLayout = (props: WebProps | AppProps) => {
)}
-
+
{isApps && renderAds && (
@@ -172,6 +176,7 @@ export const StandardLayout = (props: WebProps | AppProps) => {
renderingTarget={renderingTarget}
/>
+
{isWeb && renderAds && !isLabs && (
{
+ const match = url.match(/\/\d+_\d+_(\d+)_(\d+)\/\d+\.\w+$/);
+ if (!match) return 'landscape';
+ const [, width, height] = match.map(Number);
+ if (width == null || height == null) return 'landscape';
+ if (height > width) return 'portrait';
+ if (width > height) return 'landscape';
+ return 'square';
+};
+
const GridItem = ({
area,
layoutType,
@@ -110,6 +132,8 @@ export const StandardLayoutArticleGrid = ({
format.design === ArticleDesign.Video ||
format.design === ArticleDesign.Audio;
const isShowcase = format.display === ArticleDisplay.Showcase;
+ const isImmersive = format.display === ArticleDisplay.Immersive;
+ const isFeature = format.design === ArticleDesign.Feature;
const footballMatchUrl =
article.matchType === 'FootballMatchType'
@@ -119,11 +143,42 @@ export const StandardLayoutArticleGrid = ({
const isFootballMatchReport =
format.design === ArticleDesign.MatchReport && !!footballMatchUrl;
- const layoutType: LayoutType = isMedia
- ? 'media'
- : isShowcase
- ? 'showcase'
- : 'standard';
+ const mainMedia = article.mainMediaElements[0];
+ const captionText = decideMainMediaCaption(mainMedia);
+ const mainMediaUrl: string | undefined =
+ mainMedia?._type ===
+ 'model.dotcomrendering.pageElements.ImageBlockElement'
+ ? mainMedia.media.allImages[0]?.url
+ : undefined;
+ const mainMediaAspectRatio =
+ mainMedia?._type ===
+ 'model.dotcomrendering.pageElements.ImageBlockElement'
+ ? mainMedia.media.allImages[0]?.fields.aspectRatio
+ : undefined;
+
+ const mainMediaOrientation =
+ mainMediaUrl != null ? getImageOrientation(mainMediaUrl) : 'landscape';
+
+ const layoutType = getLayoutType({
+ isImmersive,
+ isFeature,
+ orientation: mainMediaOrientation,
+ isMedia,
+ isShowcase,
+ });
+ const contentLayoutName = `${ArticleDisplay[format.display]}Layout`;
+
+ const isImmersivePortrait =
+ layoutType === 'immersivePortraitDefault' ||
+ layoutType === 'immersivePortraitFeature';
+ const isImmersiveLandscape =
+ layoutType === 'immersiveLandscapeDefault' ||
+ layoutType === 'immersiveLandscapeFeature';
+
+ const ageWarning = getAgeWarning(
+ article.tags,
+ article.webPublicationDateDeprecated,
+ );
return (
-
+
-
+
-
+
-
+
-
- {format.design !== ArticleDesign.Audio && (
-
- {isWeb &&
- format.theme === ArticleSpecial.Labs &&
- format.design !== ArticleDesign.Video ? (
-
- ) : (
-
- )}
-
- )}
+ {isImmersive && (
+
+
+
+
+
+ )}
+
+ {format.display !== ArticleDisplay.Immersive &&
+ format.design !== ArticleDesign.Audio &&
+ layoutType !== 'immersivePortraitDefault' && (
+
+ {isWeb &&
+ format.theme === ArticleSpecial.Labs &&
+ format.design !== ArticleDesign.Video ? (
+
+ ) : (
+
+ )}
+
+ )}
{isApps ? (
<>
@@ -224,6 +405,7 @@ export const StandardLayoutArticleGrid = ({
{!!article.affiliateLinksDisclaimer && (
@@ -275,7 +458,13 @@ export const StandardLayoutArticleGrid = ({
>
)}
-
+
{/* Only show Listen to Article button on App landscape views */}
{isApps && (
diff --git a/dotcom-rendering/src/layouts/lib/articleArrangements.ts b/dotcom-rendering/src/layouts/lib/articleArrangements.ts
index 2cf5fb6843f..e7fee10daec 100644
--- a/dotcom-rendering/src/layouts/lib/articleArrangements.ts
+++ b/dotcom-rendering/src/layouts/lib/articleArrangements.ts
@@ -2,24 +2,33 @@ 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'
+ | 'immersiveLandscapeDefault'
+ | 'immersiveLandscapeFeature'
+ | 'immersivePortraitDefault'
+ | 'immersivePortraitFeature';
export type Area =
| 'title'
| 'headline'
| 'standfirst'
+ | 'caption'
| 'media'
| 'meta'
| 'body'
| 'right-column';
-type Breakpoint = 'mobile' | 'tablet' | 'desktop' | 'leftCol';
+type Breakpoint = 'mobile' | 'tablet' | 'desktop' | 'leftCol' | 'wide';
const breakpointQueries: Record = {
mobile: until.tablet,
tablet: from.tablet,
desktop: from.desktop,
leftCol: from.leftCol,
+ wide: from.wide,
};
// Raw CSS overrides per area per breakpoint. Entries are only needed when an area
@@ -149,10 +158,182 @@ const mediaCss: LayoutCssMap = {
},
};
+const immersivePortraitDefaultCss: LayoutCssMap = {
+ title: {
+ mobile: 'grid-row: 1;',
+ tablet: 'grid-row: 1;',
+ desktop: `grid-row: 1; ${grid.between('centre-column-start', 8)};`,
+ leftCol: `grid-row: 1; ${grid.between('left-column-start', 9)};`,
+ },
+ headline: {
+ mobile: 'grid-row: 2;',
+ tablet: 'grid-row: 2;',
+ desktop: `grid-row: 2; ${grid.between('centre-column-start', 8)};`,
+ leftCol: `grid-row: 2; ${grid.between('left-column-start', 9)};`,
+ wide: `grid-row: 2; ${grid.between('left-column-start', 10)};`,
+ },
+ media: {
+ mobile: 'grid-row: 3;',
+ tablet: 'grid-row: 3;',
+ desktop: `grid-row: 1 / span 4; ${grid.between(8, 'right-column-end')};`,
+ leftCol: `grid-row: 1 / span 3; ${grid.between(9, 'right-column-end')};`,
+ wide: `grid-row: 1 / span 3; ${grid.between(10, 'right-column-end')};`,
+ },
+ standfirst: {
+ mobile: 'grid-row: 4;',
+ tablet: 'grid-row: 4;',
+ desktop: `grid-row: 3; ${grid.between('centre-column-start', 7)};`,
+ leftCol: `grid-row: 3; ${grid.between('centre-column-start', 8)};`,
+ wide: `grid-row: 3; ${grid.between('centre-column-start', 9)};`,
+ },
+ caption: {
+ mobile: 'grid-row: 5;',
+ tablet: 'grid-row: 5;',
+ desktop: `grid-row: 5;`,
+ },
+ meta: {
+ mobile: 'grid-row: 6;',
+ tablet: 'grid-row: 6;',
+ desktop: `grid-row: 4; ${grid.between('centre-column-start', 8)};`,
+ leftCol: `grid-row: 3 / span 2; ${grid.column.left};`,
+ },
+ body: {
+ mobile: 'grid-row: 7;',
+ leftCol: 'grid-row: 4;',
+ },
+ 'right-column': {
+ desktop: `grid-row: 5 / span 2; ${grid.column.right};`,
+ leftCol: `grid-row: 4; ${grid.column.right};`,
+ },
+};
+
+const immersivePortraitFeatureCss: LayoutCssMap = {
+ title: {
+ mobile: 'grid-row: 2;',
+ tablet: 'grid-row: 2;',
+ desktop: `grid-row: 1; ${grid.between('centre-column-start', 8)};`,
+ leftCol: `grid-row: 1; ${grid.between('left-column-start', 9)};`,
+ },
+ headline: {
+ mobile: 'grid-row: 3;',
+ tablet: 'grid-row: 3;',
+ desktop: `grid-row: 2; ${grid.between('centre-column-start', 8)};`,
+ leftCol: `grid-row: 2; ${grid.between('left-column-start', 9)};`,
+ wide: `grid-row: 2; ${grid.between('left-column-start', 10)};`,
+ },
+ media: {
+ mobile: 'grid-row: 1;',
+ tablet: 'grid-row: 1;',
+ desktop: `grid-row: 1 / span 4; ${grid.between(8, 'right-column-end')};`,
+ leftCol: `grid-row: 1 / span 3; ${grid.between(9, 'right-column-end')};`,
+ wide: `grid-row: 1 / span 3; ${grid.between(10, 'right-column-end')};`,
+ },
+ standfirst: {
+ mobile: 'grid-row: 4;',
+ tablet: 'grid-row: 4;',
+ desktop: `grid-row: 3; ${grid.between('centre-column-start', 7)};`,
+ leftCol: `grid-row: 3; ${grid.between('centre-column-start', 8)};`,
+ wide: `grid-row: 3; ${grid.between('centre-column-start', 9)};`,
+ },
+ caption: {
+ mobile: 'grid-row: 5;',
+ tablet: 'grid-row: 5;',
+ desktop: `grid-row: 5;`,
+ },
+ meta: {
+ mobile: 'grid-row: 6;',
+ tablet: 'grid-row: 6;',
+ desktop: `grid-row: 4; ${grid.between('centre-column-start', 8)};`,
+ leftCol: `grid-row: 3 / span 2; ${grid.column.left};`,
+ },
+ body: {
+ mobile: 'grid-row: 7;',
+ leftCol: 'grid-row: 4;',
+ },
+ 'right-column': {
+ desktop: `grid-row: 5 / span 2; ${grid.column.right};`,
+ leftCol: `grid-row: 4; ${grid.column.right};`,
+ },
+};
+
+const immersiveLandscapeDefaultCss: LayoutCssMap = {
+ title: {
+ mobile: 'grid-row: 1;',
+ tablet: 'grid-row: 1;',
+ desktop: 'grid-row: 2;',
+ },
+ headline: {
+ mobile: 'grid-row: 2;',
+ tablet: 'grid-row: 2;',
+ desktop: 'grid-row: 3 / span 2;',
+ wide: `${grid.between('centre-column-start', 14)};`,
+ },
+ media: {
+ mobile: 'grid-row: 3;',
+ tablet: 'grid-row: 3;',
+ desktop: `grid-row: 1 / span 3; ${grid.between('centre-column-start', 'right-column-end')};`,
+ leftCol: `grid-row: 1 / span 3; ${grid.between('left-column-start', 'right-column-end')};`,
+ },
+ standfirst: {
+ mobile: 'grid-row: 4;',
+ tablet: 'grid-row: 4;',
+ desktop: 'grid-row: 5;',
+ },
+ caption: {
+ desktop: 'grid-row: 6;',
+ },
+ meta: {
+ mobile: 'grid-row: 5;',
+ tablet: 'grid-row: 5;',
+ desktop: `grid-row: 7;`,
+ leftCol: `grid-row: 5 / span 2; ${grid.column.left};`,
+ },
+ body: {
+ leftCol: 'grid-row: 6;',
+ },
+ 'right-column': {
+ desktop: `grid-row: 6 / span 3; ${grid.column.right};`,
+ },
+};
+
+const immersiveLandscapeFeatureCss: LayoutCssMap = {
+ title: {
+ desktop: 'grid-row: 2;',
+ },
+ headline: {
+ desktop: 'grid-row: 3 / span 2;',
+ },
+ media: {
+ mobile: `${grid.column.all}`,
+ desktop: `grid-row: 1 / span 3; ${grid.between('centre-column-start', 'right-column-end')};`,
+ leftCol: `grid-row: 1 / span 3; ${grid.between('left-column-start', 'right-column-end')};`,
+ },
+ standfirst: {
+ desktop: 'grid-row: 5;',
+ },
+ caption: {
+ desktop: 'grid-row: 6;',
+ },
+ meta: {
+ desktop: `grid-row: 7;`,
+ leftCol: `grid-row: 5 / span 2; ${grid.column.left};`,
+ },
+ body: {
+ leftCol: 'grid-row: 6;',
+ },
+ 'right-column': {
+ desktop: `grid-row: 6 / span 3; ${grid.column.right};`,
+ },
+};
+
const layoutCssMaps: Record = {
standard: standardCss,
showcase: showcaseCss,
media: mediaCss,
+ immersiveLandscapeDefault: immersiveLandscapeDefaultCss,
+ immersiveLandscapeFeature: immersiveLandscapeFeatureCss,
+ immersivePortraitDefault: immersivePortraitDefaultCss,
+ immersivePortraitFeature: immersivePortraitFeatureCss,
};
/**
@@ -192,3 +373,38 @@ export const gridItemCss = (
${breakpointCss}
`;
};
+
+/**
+ * Determines which {@link LayoutType} to render. Immersive layouts are
+ * split by orientation (portrait vs. landscape/square) and by whether the
+ * format is a Feature, since each combination has a distinct grid
+ * arrangement. Non-immersive formats fall back to media/showcase/standard.
+ */
+export const getLayoutType = ({
+ isImmersive,
+ isFeature,
+ orientation,
+ isMedia,
+ isShowcase,
+}: {
+ isImmersive: boolean;
+ isFeature: boolean;
+ orientation: 'portrait' | 'landscape' | 'square';
+ isMedia: boolean;
+ isShowcase: boolean;
+}): LayoutType => {
+ if (isImmersive) {
+ if (orientation === 'portrait') {
+ return isFeature
+ ? 'immersivePortraitFeature'
+ : 'immersivePortraitDefault';
+ }
+ // Square images are treated the same as landscape for immersive layouts.
+ return isFeature
+ ? 'immersiveLandscapeFeature'
+ : 'immersiveLandscapeDefault';
+ }
+ if (isMedia) return 'media';
+ if (isShowcase) return 'showcase';
+ return 'standard';
+};
diff --git a/dotcom-rendering/src/lib/articleMeta.test.ts b/dotcom-rendering/src/lib/articleMeta.test.ts
index dd7eebd8ebd..f064e36a97e 100644
--- a/dotcom-rendering/src/lib/articleMeta.test.ts
+++ b/dotcom-rendering/src/lib/articleMeta.test.ts
@@ -23,11 +23,29 @@ describe('shouldShowContributor', () => {
...standardFormat,
display: ArticleDisplay.NumberedList,
};
+
const immersive = {
...standardFormat,
display: ArticleDisplay.Immersive,
};
+ const immersivePortraitDefault = {
+ ...immersive,
+ layoutType: 'immersivePortraitDefault',
+ };
+ const immersivePortraitFeature = {
+ ...immersive,
+ layoutType: 'immersivePortraitFeature',
+ };
+ const immersiveLandscapeDefault = {
+ ...immersive,
+ layoutType: 'immersiveLandscapeDefault',
+ };
+ const immersiveLandscapeFeature = {
+ ...immersive,
+ layoutType: 'immersiveLandscapeFeature',
+ };
+
it('should return true if Standard display and Standard design', () => {
expect(shouldShowContributor(standardFormat)).toBe(true);
});
@@ -49,6 +67,22 @@ describe('shouldShowContributor', () => {
});
it('should return false if Immersive display', () => {
- expect(shouldShowContributor(immersive)).toBe(false);
+ expect(shouldShowContributor(immersive)).toBe(true);
+ });
+
+ it('should return true if Immersive display and immersivePortraitDefault layout', () => {
+ expect(shouldShowContributor(immersivePortraitDefault)).toBe(true);
+ });
+
+ it('should return true if Immersive display and immersivePortraitFeature layout', () => {
+ expect(shouldShowContributor(immersivePortraitFeature)).toBe(true);
+ });
+
+ it('should return true if Immersive display and immersiveLandscapeDefault layout', () => {
+ expect(shouldShowContributor(immersiveLandscapeDefault)).toBe(true);
+ });
+
+ it('should return true if Immersive display and immersiveLandscapeFeature layout', () => {
+ expect(shouldShowContributor(immersiveLandscapeFeature)).toBe(true);
});
});
diff --git a/dotcom-rendering/src/lib/articleMeta.ts b/dotcom-rendering/src/lib/articleMeta.ts
index 8b3543071f6..23b51b8a264 100644
--- a/dotcom-rendering/src/lib/articleMeta.ts
+++ b/dotcom-rendering/src/lib/articleMeta.ts
@@ -8,7 +8,6 @@ import {
export const shouldShowAvatar = (format: ArticleFormat): boolean => {
switch (format.display) {
case ArticleDisplay.Immersive:
- return false;
case ArticleDisplay.Showcase:
case ArticleDisplay.NumberedList:
case ArticleDisplay.Standard: {
@@ -32,7 +31,6 @@ export const shouldShowContributor = (format: ArticleFormat): boolean => {
case ArticleDisplay.NumberedList:
return true;
case ArticleDisplay.Immersive:
- return false;
case ArticleDisplay.Showcase:
case ArticleDisplay.Standard: {
switch (format.design) {
diff --git a/dotcom-rendering/src/lib/getZIndex.test.ts b/dotcom-rendering/src/lib/getZIndex.test.ts
index 19f82962d88..78bb441772c 100644
--- a/dotcom-rendering/src/lib/getZIndex.test.ts
+++ b/dotcom-rendering/src/lib/getZIndex.test.ts
@@ -14,6 +14,12 @@ describe('getZIndex', () => {
expect(getZIndex('tableOfContents')).toBeGreaterThan(
getZIndex('articleHeadline'),
);
+ expect(getZIndex('subNavBanner')).toBeGreaterThan(
+ getZIndex('articleHeadline'),
+ );
+ expect(getZIndex('subNavBanner')).toBeGreaterThan(
+ getZIndex('bodyArea'),
+ );
expect(getZIndex('card-nested-link')).toBeGreaterThan(
getZIndex('card-link'),
);
diff --git a/dotcom-rendering/src/lib/renderElement.tsx b/dotcom-rendering/src/lib/renderElement.tsx
index 03043d1bef0..8414852af5f 100644
--- a/dotcom-rendering/src/lib/renderElement.tsx
+++ b/dotcom-rendering/src/lib/renderElement.tsx
@@ -69,6 +69,7 @@ import {
} from '../components/WitnessBlockComponent';
import { YoutubeBlockComponent } from '../components/YoutubeBlockComponent.island';
import { YoutubeEmbedBlockComponent } from '../components/YoutubeEmbedBlockComponent';
+import type { LayoutType } from '../layouts/lib/articleArrangements';
import {
interactiveLegacyFigureClasses,
isInteractive,
@@ -107,6 +108,7 @@ type Props = {
shouldHideAds: boolean;
contentType?: string;
contentLayout?: string;
+ articleArrangement?: LayoutType;
idApiUrl?: string;
};
@@ -178,6 +180,7 @@ export const renderElement = ({
shouldHideAds,
contentType,
contentLayout,
+ articleArrangement,
idApiUrl,
}: Props) => {
const isBlog =
@@ -409,6 +412,7 @@ export const renderElement = ({
title={element.title}
isAvatar={element.isAvatar}
isTimeline={isTimeline}
+ articleArrangement={articleArrangement}
/>
);
case 'model.dotcomrendering.pageElements.InstagramBlockElement':
@@ -1050,6 +1054,7 @@ export const RenderArticleElement = ({
shouldHideAds,
contentType,
contentLayout,
+ articleArrangement,
idApiUrl,
}: Props) => {
const withUpdatedRole = updateRole(element, format);
@@ -1078,6 +1083,7 @@ export const RenderArticleElement = ({
shouldHideAds,
contentType,
contentLayout,
+ articleArrangement,
idApiUrl,
});
@@ -1102,6 +1108,7 @@ export const RenderArticleElement = ({
type={element._type}
format={format}
isTimeline={isTimeline}
+ articleArrangement={articleArrangement}
>
{el}
diff --git a/dotcom-rendering/src/paletteDeclarations.ts b/dotcom-rendering/src/paletteDeclarations.ts
index 3fb76dea59d..b0bf5c22d02 100644
--- a/dotcom-rendering/src/paletteDeclarations.ts
+++ b/dotcom-rendering/src/paletteDeclarations.ts
@@ -88,7 +88,7 @@ const textblockTextDark: PaletteFunction = () => 'inherit';
const headlineTextLight: PaletteFunction = ({ design, display, theme }) => {
switch (display) {
case ArticleDisplay.Immersive:
- return sourcePalette.neutral[97];
+ return sourcePalette.neutral[7];
default: {
switch (design) {
case ArticleDesign.Editorial:
@@ -195,19 +195,8 @@ const headlineMatchTextLight: PaletteFunction = (format) =>
const headlineMatchTextDark: PaletteFunction = (format) =>
seriesTitleMatchTextDark(format);
-const headlineBackgroundLight: PaletteFunction = ({
- display,
- design,
- theme,
-}) => {
+const headlineBackgroundLight: PaletteFunction = ({ display, design }) => {
switch (display) {
- case ArticleDisplay.Immersive:
- switch (theme) {
- case ArticleSpecial.SpecialReport:
- return sourcePalette.specialReport[300];
- default:
- return sourcePalette.neutral[7];
- }
case ArticleDisplay.Showcase:
case ArticleDisplay.NumberedList:
case ArticleDisplay.Standard: