From ff824d24266846ea05638e32abb174a252e28a15 Mon Sep 17 00:00:00 2001 From: Frederick O'Brien Date: Fri, 9 Oct 2026 10:48:14 +0100 Subject: [PATCH 1/4] Weekend reads immersive styles --- .../src/components/ArticleHeadline.tsx | 39 +++- .../src/components/ArticleMeta.apps.tsx | 11 +- .../src/components/ArticleMeta.web.tsx | 56 +++--- .../src/components/ArticleTitle.tsx | 26 ++- dotcom-rendering/src/components/Figure.tsx | 39 +++- .../src/components/HeadlineByline.tsx | 45 +++++ dotcom-rendering/src/components/MainMedia.tsx | 4 + .../src/layouts/StandardLayoutArticleGrid.tsx | 167 +++++++++++------- .../src/layouts/lib/articleArrangements.ts | 18 +- dotcom-rendering/src/lib/articleMeta.ts | 6 +- dotcom-rendering/src/lib/renderElement.tsx | 4 + 11 files changed, 314 insertions(+), 101 deletions(-) diff --git a/dotcom-rendering/src/components/ArticleHeadline.tsx b/dotcom-rendering/src/components/ArticleHeadline.tsx index c2334546810..cecf895a534 100644 --- a/dotcom-rendering/src/components/ArticleHeadline.tsx +++ b/dotcom-rendering/src/components/ArticleHeadline.tsx @@ -18,7 +18,10 @@ import { until, } from '@guardian/source/foundations'; import { grid } from '../../src/grid'; -import { type LayoutType } from '../layouts/lib/articleArrangements'; +import { + isWeekendRead, + type LayoutType, +} from '../layouts/lib/articleArrangements'; import { interactiveLegacyClasses } from '../layouts/lib/interactiveLegacyStyling'; import { getAgeWarning } from '../lib/age-warning'; import { @@ -532,6 +535,40 @@ export const ArticleHeadline = ({ }: Props) => { const isInverted = layoutType === 'immersiveLandscape'; const isLegacyImmersive = layoutType == null; + if (isWeekendRead(tags)) { + return ( +
+ +

+ {headlineString} +

+
+ {!!byline && ( + + )} +
+ ); + } switch (format.display) { case ArticleDisplay.Immersive: { switch (format.design) { diff --git a/dotcom-rendering/src/components/ArticleMeta.apps.tsx b/dotcom-rendering/src/components/ArticleMeta.apps.tsx index 0a6fc60bbf9..e5710484253 100644 --- a/dotcom-rendering/src/components/ArticleMeta.apps.tsx +++ b/dotcom-rendering/src/components/ArticleMeta.apps.tsx @@ -4,7 +4,10 @@ import { from, space, until } from '@guardian/source/foundations'; import { StraightLines } from '@guardian/source-development-kitchen/react-components'; import type { ReactNode } from 'react'; import { grid } from '../grid'; -import type { LayoutType } from '../layouts/lib/articleArrangements'; +import { + isWeekendRead, + type LayoutType, +} from '../layouts/lib/articleArrangements'; import { interactiveLegacyClasses } from '../layouts/lib/interactiveLegacyStyling'; import { ArticleDesign, @@ -332,7 +335,11 @@ export const ArticleMetaApps = ({ )} - {shouldShowContributor(format, isImmersiveGrid) && + {shouldShowContributor( + format, + isImmersiveGrid, + isWeekendRead(tags), + ) && !!byline && ( )} - {shouldShowContributor(format, isImmersiveGrid) && ( + {shouldShowContributor( + format, + isImmersiveGrid, + isWeekendRead(tags), + ) && ( ) : null} - {isImmersive && mainMediaElements?.[0] && ( - -
- -
-
- )} + {layoutType === 'immersiveLandscape' && + mainMediaElements?.[0] && ( + +
+ +
+
+ )} ); diff --git a/dotcom-rendering/src/components/ArticleTitle.tsx b/dotcom-rendering/src/components/ArticleTitle.tsx index 7786b099158..beacfc776b2 100644 --- a/dotcom-rendering/src/components/ArticleTitle.tsx +++ b/dotcom-rendering/src/components/ArticleTitle.tsx @@ -1,7 +1,10 @@ import { css } from '@emotion/react'; import { from } from '@guardian/source/foundations'; import { grid } from '../../src/grid'; -import type { LayoutType } from '../layouts/lib/articleArrangements'; +import { + isWeekendRead, + type LayoutType, +} from '../layouts/lib/articleArrangements'; import { ArticleDesign, ArticleDisplay, @@ -79,6 +82,10 @@ export const ArticleTitle = ({ format.design, ) && galleryStyles, sectionStyles, + css` + position: relative; + min-height: 71px; + `, ]} >
+ {isWeekendRead(tags) && ( + Book + )} ); diff --git a/dotcom-rendering/src/components/Figure.tsx b/dotcom-rendering/src/components/Figure.tsx index 8aa12616efa..ebc473cc0f9 100644 --- a/dotcom-rendering/src/components/Figure.tsx +++ b/dotcom-rendering/src/components/Figure.tsx @@ -1,6 +1,9 @@ import { css } from '@emotion/react'; import { breakpoints, from, space, until } from '@guardian/source/foundations'; -import type { LayoutType } from '../layouts/lib/articleArrangements'; +import { + isWeekendRead, + type LayoutType, +} from '../layouts/lib/articleArrangements'; import { ArticleDesign, type ArticleFormat, @@ -8,6 +11,7 @@ import { } from '../lib/articleFormat'; import { LABS_HEADER_HEIGHT } from '../lib/labs-constants'; import type { FEElement, RoleType } from '../types/content'; +import type { TagType } from '../types/tag'; import { minHeaderHeightPx } from './Masthead/Titlepiece/constants'; type Props = { @@ -21,6 +25,7 @@ type Props = { isTimeline?: boolean; isInDeprecatedInteractiveLayout?: boolean; articleArrangement?: LayoutType; + tags?: TagType[]; }; const roleCss = { @@ -306,6 +311,7 @@ export const Figure = ({ isTimeline = false, isInDeprecatedInteractiveLayout = false, articleArrangement, + tags, }: Props) => { if (isMainMedia && !isTimeline) { // Don't add in-body styles for main media elements @@ -317,10 +323,33 @@ export const Figure = ({
{children}
diff --git a/dotcom-rendering/src/components/HeadlineByline.tsx b/dotcom-rendering/src/components/HeadlineByline.tsx index 8a727337f3c..482edd6c3f9 100644 --- a/dotcom-rendering/src/components/HeadlineByline.tsx +++ b/dotcom-rendering/src/components/HeadlineByline.tsx @@ -5,6 +5,8 @@ import { headlineLight34, headlineMedium20, headlineMedium24, + headlineMedium34, + headlineMedium50, palette, space, textSans20, @@ -12,6 +14,7 @@ import { textSans34, until, } from '@guardian/source/foundations'; +import { isWeekendRead } from '../layouts/lib/articleArrangements'; import { ArticleDesign, ArticleDisplay, @@ -74,6 +77,28 @@ const opinionStyles = (format: ArticleFormat) => css` } `; +const weekendReadStyles = css` + ${headlineMedium34} + line-height: 38px; + color: ${schemedPalette('--byline-anchor')}; + font-style: italic; + + ${from.leftCol} { + ${headlineMedium50} + line-height: 54px; + font-style: italic; + } + + a { + color: inherit; + text-decoration: none; + font-style: normal; + :hover { + text-decoration: underline; + } + } +`; + const analysisStyles = css` ${headlineLight34}; line-height: 38px; @@ -145,6 +170,26 @@ export const HeadlineByline = ({ format, byline, tags }: Props) => { const hasSingleContributor = !!getSoleContributor(tags, byline); + if (isWeekendRead(tags)) { + return ( +
+
+ +
+
+ ); + } + switch (format.display) { case ArticleDisplay.Immersive: return ( diff --git a/dotcom-rendering/src/components/MainMedia.tsx b/dotcom-rendering/src/components/MainMedia.tsx index 153ab6563dd..d159d9e0d64 100644 --- a/dotcom-rendering/src/components/MainMedia.tsx +++ b/dotcom-rendering/src/components/MainMedia.tsx @@ -11,6 +11,7 @@ import { getZIndex } from '../lib/getZIndex'; import { RenderArticleElement } from '../lib/renderElement'; import type { Switches } from '../types/config'; import type { FEElement } from '../types/content'; +import type { TagType } from '../types/tag'; const mainMedia = css` position: relative; @@ -105,6 +106,7 @@ type Props = { contentType?: string; contentLayout?: string; articleArrangement?: LayoutType; + tags?: TagType[]; }; export const MainMedia = ({ @@ -123,6 +125,7 @@ export const MainMedia = ({ contentType, contentLayout, articleArrangement, + tags, }: Props) => { return (
@@ -146,6 +149,7 @@ export const MainMedia = ({ contentType={contentType} contentLayout={contentLayout} articleArrangement={articleArrangement} + tags={tags} /> ))}
diff --git a/dotcom-rendering/src/layouts/StandardLayoutArticleGrid.tsx b/dotcom-rendering/src/layouts/StandardLayoutArticleGrid.tsx index 6fed5f71084..d6994be50f9 100644 --- a/dotcom-rendering/src/layouts/StandardLayoutArticleGrid.tsx +++ b/dotcom-rendering/src/layouts/StandardLayoutArticleGrid.tsx @@ -46,6 +46,7 @@ import { type Area, getLayoutType, gridItemCss, + isWeekendRead, type LayoutType, } from './lib/articleArrangements'; @@ -64,6 +65,7 @@ const immersiveMediaBelowDesktop = ( headlineBackground: string, isMainMediaImage: boolean, hasMinimumImageHeight: boolean, + isLandscape: boolean, ) => css` ${until.tablet} { aspect-ratio: 4 / 5; @@ -77,31 +79,38 @@ const immersiveMediaBelowDesktop = ( ${!isMainMediaImage && 'overflow: hidden;'} - &::after { - content: ''; - position: absolute; - left: 0; - right: 0; - bottom: ${hasMinimumImageHeight ? '-1px' : '0'}; - height: ${hasMinimumImageHeight - ? '180px' - : isMainMediaImage - ? 'min(20%, calc(200% - 120vw + 30px))' - : 'min(60%, 144px)'}; - z-index: ${getZIndex('mediaOverlay')}; - background: linear-gradient( - to bottom, - rgba(0, 0, 0, ${hasMinimumImageHeight ? '0' : '0.08'}), - ${headlineBackground} ${hasMinimumImageHeight ? '100%' : '72%'} - ); - backdrop-filter: blur(12px); - mask-image: linear-gradient( - to bottom, - transparent ${hasMinimumImageHeight ? '0%' : '40%'}, - black 60% - ); - pointer-events: none; - } + ${isLandscape && + css` + ${until.tablet} { + aspect-ratio: 4 / 5; + } + &::after { + content: ''; + position: absolute; + left: 0; + right: 0; + bottom: ${hasMinimumImageHeight ? '-1px' : '0'}; + height: ${hasMinimumImageHeight + ? '180px' + : isMainMediaImage + ? 'min(60%, calc(200% - 120vw + 30px))' + : 'min(60%, 144px)'}; + z-index: ${getZIndex('mediaOverlay')}; + background: linear-gradient( + to bottom, + rgba(0, 0, 0, ${hasMinimumImageHeight ? '0' : '0.08'}), + ${headlineBackground} + ${hasMinimumImageHeight ? '100%' : '72%'} + ); + backdrop-filter: blur(12px); + mask-image: linear-gradient( + to bottom, + transparent ${hasMinimumImageHeight ? '0%' : '40%'}, + black 60% + ); + pointer-events: none; + } + `} } `; @@ -204,7 +213,6 @@ export const StandardLayoutArticleGrid = ({ : undefined; const isLabs = format.theme === ArticleSpecial.Labs; - const isImmersive = format.display === ArticleDisplay.Immersive; const headlineBackgroundImmersive = themePalette( '--headline-background-immersive', @@ -224,8 +232,6 @@ export const StandardLayoutArticleGrid = ({ mainMediaType === 'model.dotcomrendering.pageElements.MediaAtomBlockElement'; - const hasMinimumImageHeight = isLabs && isImmersive && isMainMediaImage; - const mainMediaAspectRatio = isMainMediaImage ? mainMedia.media.allImages[0]?.fields.aspectRatio : isMainMediaAtom @@ -242,9 +248,21 @@ export const StandardLayoutArticleGrid = ({ ? 'auto' : `max(calc(80vh - ${immersiveHeaderHeight}px), calc(25rem - ${immersiveHeaderHeight}px))`; - const layoutType = getLayoutType(format, mainMediaOrientation); + // If tags includes news/series/the-sunday-read then use immersivePortrait layout, else + // use getLayoutType function to determine the layout type based on the format and main media orientation. + const layoutType = isWeekendRead(article.tags) + ? 'immersivePortrait' + : getLayoutType(format, mainMediaOrientation); + + getLayoutType(format, mainMediaOrientation); const contentLayoutName = `${ArticleDisplay[format.display]}Layout`; + const isImmersive = + layoutType === 'immersivePortrait' || + layoutType === 'immersiveLandscape'; + + const hasMinimumImageHeight = isLabs && isImmersive && isMainMediaImage; + const ageWarning = getAgeWarning( article.tags, article.webPublicationDateDeprecated, @@ -266,11 +284,18 @@ export const StandardLayoutArticleGrid = ({ } `; + const hideCaptionBeneathMainMedia = + layoutType === 'media' || + layoutType === 'immersiveLandscape' || + layoutType === 'immersivePortrait'; + return (
-
- + {article.affiliateLinksDisclaimerRequired && ( + - {article.affiliateLinksDisclaimerRequired && ( - - )} -
+ )} - + {layoutType === 'immersivePortrait' ? ( - + ) : ( + + + + )} )} + tags.some((tag) => + [ + 'news/series/the-saturday-read', + 'news/series/the-sunday-read', + ].includes(tag.id), + ); diff --git a/dotcom-rendering/src/lib/articleMeta.ts b/dotcom-rendering/src/lib/articleMeta.ts index 54c8494ffab..31336d3c5a5 100644 --- a/dotcom-rendering/src/lib/articleMeta.ts +++ b/dotcom-rendering/src/lib/articleMeta.ts @@ -36,8 +36,12 @@ export const shouldShowAvatar = ( export const shouldShowContributor = ( format: ArticleFormat, isImmersiveGrid = false, + isWeekendRead = false, ): boolean => { - if (format.display === ArticleDisplay.Immersive && !isImmersiveGrid) { + if ( + (format.display === ArticleDisplay.Immersive && !isImmersiveGrid) || + isWeekendRead + ) { return false; } diff --git a/dotcom-rendering/src/lib/renderElement.tsx b/dotcom-rendering/src/lib/renderElement.tsx index 7b5a7447f87..198e95d85c3 100644 --- a/dotcom-rendering/src/lib/renderElement.tsx +++ b/dotcom-rendering/src/lib/renderElement.tsx @@ -75,6 +75,7 @@ import { } from '../layouts/lib/interactiveLegacyStyling'; import type { Switches } from '../types/config'; import type { FEElement, RoleType, StarRating } from '../types/content'; +import type { TagType } from '../types/tag'; import { ArticleDesign, type ArticleFormat, @@ -110,6 +111,7 @@ type Props = { articleArrangement?: LayoutType; idApiUrl?: string; isInDeprecatedInteractiveLayout?: boolean; + tags?: TagType[]; }; // updateRole modifies the role of an element in a way appropriate for most @@ -1057,6 +1059,7 @@ export const RenderArticleElement = ({ articleArrangement, idApiUrl, isInDeprecatedInteractiveLayout, + tags, }: Props) => { const withUpdatedRole = updateRole(element, format); @@ -1112,6 +1115,7 @@ export const RenderArticleElement = ({ isTimeline={isTimeline} isInDeprecatedInteractiveLayout={isInDeprecatedInteractiveLayout} articleArrangement={articleArrangement} + tags={tags} > {el} From 624d7bef3542736c9910582337cc633c0e990b7f Mon Sep 17 00:00:00 2001 From: Frederick O'Brien Date: Fri, 9 Oct 2026 11:28:01 +0100 Subject: [PATCH 2/4] Tidying --- .../src/components/ArticleHeadline.tsx | 25 ++++------ .../src/components/HeadlineByline.tsx | 1 - .../src/layouts/StandardLayoutArticleGrid.tsx | 50 ++++++++++--------- 3 files changed, 35 insertions(+), 41 deletions(-) diff --git a/dotcom-rendering/src/components/ArticleHeadline.tsx b/dotcom-rendering/src/components/ArticleHeadline.tsx index cecf895a534..ab539388bb1 100644 --- a/dotcom-rendering/src/components/ArticleHeadline.tsx +++ b/dotcom-rendering/src/components/ArticleHeadline.tsx @@ -143,6 +143,13 @@ const headlineFont = (format: ArticleFormat) => { `; }; +const weekendReadFont = css` + ${headlineMedium34} + ${from.leftCol} { + ${headlineMedium50} + } +`; + const invertedFontLineHeight = css` line-height: 2.1875rem; @@ -537,27 +544,13 @@ export const ArticleHeadline = ({ const isLegacyImmersive = layoutType == null; if (isWeekendRead(tags)) { return ( -
+
-

- {headlineString} -

+

{headlineString}

{!!byline && ( - - {article.affiliateLinksDisclaimerRequired && ( - + - )} + {article.affiliateLinksDisclaimerRequired && ( + + )} +
Date: Fri, 9 Oct 2026 12:14:33 +0100 Subject: [PATCH 3/4] Update ArticleTitle.tsx --- dotcom-rendering/src/components/ArticleTitle.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dotcom-rendering/src/components/ArticleTitle.tsx b/dotcom-rendering/src/components/ArticleTitle.tsx index beacfc776b2..fe4c01aef43 100644 --- a/dotcom-rendering/src/components/ArticleTitle.tsx +++ b/dotcom-rendering/src/components/ArticleTitle.tsx @@ -82,10 +82,12 @@ export const ArticleTitle = ({ format.design, ) && galleryStyles, sectionStyles, - css` - position: relative; - min-height: 71px; - `, + layoutType === 'immersivePortrait' && + isWeekendRead(tags) && + css` + position: relative; + min-height: 71px; + `, ]} >
Date: Fri, 9 Oct 2026 12:33:33 +0100 Subject: [PATCH 4/4] Tighten up immersive weekend read checks --- dotcom-rendering/src/components/ArticleHeadline.tsx | 3 ++- dotcom-rendering/src/components/ArticleMeta.apps.tsx | 3 ++- dotcom-rendering/src/components/ArticleMeta.web.tsx | 3 ++- dotcom-rendering/src/components/ArticleTitle.tsx | 2 +- dotcom-rendering/src/components/HeadlineByline.tsx | 10 +++++++--- dotcom-rendering/src/lib/articleMeta.ts | 2 +- 6 files changed, 15 insertions(+), 8 deletions(-) diff --git a/dotcom-rendering/src/components/ArticleHeadline.tsx b/dotcom-rendering/src/components/ArticleHeadline.tsx index ab539388bb1..33701f17ef2 100644 --- a/dotcom-rendering/src/components/ArticleHeadline.tsx +++ b/dotcom-rendering/src/components/ArticleHeadline.tsx @@ -542,7 +542,7 @@ export const ArticleHeadline = ({ }: Props) => { const isInverted = layoutType === 'immersiveLandscape'; const isLegacyImmersive = layoutType == null; - if (isWeekendRead(tags)) { + if (layoutType === 'immersivePortrait' && isWeekendRead(tags)) { return (
)}
diff --git a/dotcom-rendering/src/components/ArticleMeta.apps.tsx b/dotcom-rendering/src/components/ArticleMeta.apps.tsx index e5710484253..05bc7a4249a 100644 --- a/dotcom-rendering/src/components/ArticleMeta.apps.tsx +++ b/dotcom-rendering/src/components/ArticleMeta.apps.tsx @@ -338,7 +338,8 @@ export const ArticleMetaApps = ({ {shouldShowContributor( format, isImmersiveGrid, - isWeekendRead(tags), + layoutType === 'immersivePortrait' && + isWeekendRead(tags), ) && !!byline && (
- {isWeekendRead(tags) && ( + {layoutType === 'immersivePortrait' && isWeekendRead(tags) && ( Book { +export const HeadlineByline = ({ format, byline, tags, layoutType }: Props) => { if (byline === '') { return null; } const hasSingleContributor = !!getSoleContributor(tags, byline); - if (isWeekendRead(tags)) { + if (layoutType === 'immersivePortrait' && isWeekendRead(tags)) { return (
{ if ( (format.display === ArticleDisplay.Immersive && !isImmersiveGrid) || - isWeekendRead + (isWeekendRead && isImmersiveGrid) ) { return false; }