Skip to content

Commit 2e56467

Browse files
authored
Render immersive feature media cards (#13675)
* Pass through mobile aspect ratio to youtube component * Add immersive styling to overlay * fix comment * linting * Move podcast image inside overlay of feature card * PR review comments
1 parent a04af74 commit 2e56467

File tree

7 files changed

+146
-69
lines changed

7 files changed

+146
-69
lines changed

dotcom-rendering/src/components/FeatureCard.tsx

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,6 @@ const podcastImageContainerStyles = css`
157157
const podcastImageStyles = css`
158158
height: 80px;
159159
width: 80px;
160-
position: absolute;
161-
/**
162-
* Displays 8px above the text.
163-
* desired space above text (8px) - padding-top of text container (64px) = -56px
164-
*/
165-
bottom: -${space[14]}px;
166-
left: ${space[2]}px;
167160
`;
168161

169162
const starRatingWrapper = css`
@@ -382,6 +375,7 @@ export const FeatureCard = ({
382375
hideCaption={true}
383376
pauseOffscreenVideo={true}
384377
aspectRatio={aspectRatio}
378+
mobileAspectRatio={mobileAspectRatio}
385379
altText={headlineText}
386380
kickerText={kickerText}
387381
trailText={
@@ -402,6 +396,7 @@ export const FeatureCard = ({
402396
discussionId={discussionId}
403397
discussionApiUrl={discussionApiUrl}
404398
isFeatureCard={true}
399+
isImmersive={isImmersive}
405400
/>
406401
</Island>
407402
</div>
@@ -481,40 +476,45 @@ export const FeatureCard = ({
481476
immersiveOverlayContainerStyles,
482477
]}
483478
>
484-
{mainMedia?.type === 'Audio' &&
485-
!!mainMedia.podcastImage?.src && (
486-
<div
487-
css={
488-
podcastImageContainerStyles
489-
}
490-
>
491-
<div css={podcastImageStyles}>
492-
<CardPicture
493-
mainImage={
494-
mainMedia
495-
.podcastImage
496-
.src
497-
}
498-
imageSize="podcast"
499-
alt={
500-
mainMedia
501-
.podcastImage
502-
.altText ?? ''
503-
}
504-
loading="lazy"
505-
roundedCorners={false}
506-
aspectRatio="1:1"
507-
/>
508-
</div>
509-
</div>
510-
)}
511479
<div
512480
css={[
513481
overlayStyles,
514482
isImmersive &&
515483
immersiveOverlayStyles,
516484
]}
517485
>
486+
{mainMedia?.type === 'Audio' &&
487+
!!mainMedia.podcastImage?.src && (
488+
<div
489+
css={
490+
podcastImageContainerStyles
491+
}
492+
>
493+
<div
494+
css={podcastImageStyles}
495+
>
496+
<CardPicture
497+
mainImage={
498+
mainMedia
499+
.podcastImage
500+
.src
501+
}
502+
imageSize="podcast"
503+
alt={
504+
mainMedia
505+
.podcastImage
506+
.altText ??
507+
''
508+
}
509+
loading="lazy"
510+
roundedCorners={
511+
false
512+
}
513+
aspectRatio="1:1"
514+
/>
515+
</div>
516+
</div>
517+
)}
518518
{/**
519519
* Without the wrapping div the headline and byline would have space
520520
* inserted between them due to being direct children of the flex container

dotcom-rendering/src/components/FlexibleGeneral.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,8 @@ const SplashCardLayout = ({
196196
const card = cards[0];
197197
if (!card) return null;
198198

199-
const isImmersive = false; // TODO: replace with live data from fronts tool - used for testing
200-
const shouldShowImmersive = isImmersive && !isMediaCard(card.format);
199+
// TODO: replace with live data from fronts tool - used for testing
200+
const shouldShowImmersive = false;
201201

202202
if (shouldShowImmersive) {
203203
return (

dotcom-rendering/src/components/MaintainAspectRatio.tsx

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,65 @@
11
import { css } from '@emotion/react';
2+
import { until } from '@guardian/source/foundations';
23
import type { AspectRatio } from '../types/front';
34

45
type Props = {
56
height: number;
67
width: number;
78
aspectRatio?: AspectRatio;
9+
mobileAspectRatio?: AspectRatio;
810
children: React.ReactNode;
911
};
1012

13+
const getAspectRatioPadding = (aspectRatio: AspectRatio): string => {
14+
switch (aspectRatio) {
15+
case '5:4':
16+
return '80%';
17+
case '4:5':
18+
return '125%';
19+
case '1:1':
20+
return '100%';
21+
case '5:3':
22+
return '60%';
23+
}
24+
};
25+
26+
const mobileAspectRatioStyles = (aspectRatio: AspectRatio) => {
27+
return css`
28+
${until.tablet} {
29+
padding-bottom: ${getAspectRatioPadding(aspectRatio)};
30+
}
31+
`;
32+
};
33+
1134
export const MaintainAspectRatio = ({
1235
height,
1336
width,
1437
aspectRatio,
38+
mobileAspectRatio,
1539
children,
1640
}: Props) => {
1741
/* https://css-tricks.com/aspect-ratio-boxes/ */
18-
const paddingBottom =
19-
aspectRatio === '5:4'
20-
? `${(4 / 5) * 100}%`
21-
: `${(height / width) * 100}%`;
22-
42+
const paddingBottom = aspectRatio
43+
? getAspectRatioPadding(aspectRatio)
44+
: `${(height / width) * 100}%`;
2345
return (
2446
<div
25-
css={css`
26-
/* position relative to contain the absolutely positioned iframe plus any Overlay image */
27-
position: relative;
28-
padding-bottom: ${paddingBottom};
29-
& > iframe,
30-
& > video {
31-
width: 100%;
32-
height: 100%;
33-
position: absolute;
34-
top: 0;
35-
left: 0;
36-
}
37-
`}
47+
css={[
48+
css`
49+
/* position relative to contain the absolutely positioned iframe plus any Overlay image */
50+
position: relative;
51+
padding-bottom: ${paddingBottom};
52+
& > iframe,
53+
& > video {
54+
width: 100%;
55+
height: 100%;
56+
position: absolute;
57+
top: 0;
58+
left: 0;
59+
}
60+
`,
61+
mobileAspectRatio && mobileAspectRatioStyles(mobileAspectRatio),
62+
]}
3863
>
3964
{children}
4065
</div>

dotcom-rendering/src/components/YoutubeAtom/YoutubeAtom.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export type Props = {
5151
hidePillOnMobile: boolean;
5252
renderingTarget: RenderingTarget;
5353
aspectRatio?: AspectRatio;
54+
mobileAspectRatio?: AspectRatio;
5455
trailText?: string;
5556
headlineSizes?: ResponsiveFontSize;
5657
isVideoArticle?: boolean;
@@ -61,6 +62,7 @@ export type Props = {
6162
discussionApiUrl?: string;
6263
discussionId?: string;
6364
isFeatureCard?: boolean;
65+
isImmersive?: boolean;
6466
};
6567

6668
/**
@@ -103,6 +105,7 @@ export const YoutubeAtom = ({
103105
hidePillOnMobile,
104106
renderingTarget,
105107
aspectRatio,
108+
mobileAspectRatio,
106109
trailText,
107110
headlineSizes,
108111
isVideoArticle,
@@ -113,6 +116,7 @@ export const YoutubeAtom = ({
113116
discussionApiUrl,
114117
discussionId,
115118
isFeatureCard,
119+
isImmersive,
116120
}: Props): JSX.Element => {
117121
const [overlayClicked, setOverlayClicked] = useState<boolean>(false);
118122
const [playerReady, setPlayerReady] = useState<boolean>(false);
@@ -200,6 +204,7 @@ export const YoutubeAtom = ({
200204
height={height}
201205
width={width}
202206
aspectRatio={aspectRatio}
207+
mobileAspectRatio={mobileAspectRatio}
203208
>
204209
{
205210
/**
@@ -248,6 +253,7 @@ export const YoutubeAtom = ({
248253
duration={duration}
249254
kicker={kicker}
250255
aspectRatio={aspectRatio}
256+
mobileAspectRatio={mobileAspectRatio}
251257
trailText={trailText}
252258
isVideoArticle={isVideoArticle}
253259
webPublicationDate={webPublicationDate}
@@ -256,6 +262,7 @@ export const YoutubeAtom = ({
256262
linkTo={linkTo}
257263
discussionId={discussionId}
258264
discussionApiUrl={discussionApiUrl}
265+
isImmersive={isImmersive}
259266
/>
260267
) : (
261268
<YoutubeAtomOverlay

dotcom-rendering/src/components/YoutubeAtom/YoutubeAtomFeatureCardOverlay.tsx

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { css } from '@emotion/react';
22
import { isUndefined } from '@guardian/libs';
3-
import { space } from '@guardian/source/foundations';
3+
import { from, space } from '@guardian/source/foundations';
44
import type { ArticleFormat } from '../../lib/articleFormat';
55
import { secondsToDuration } from '../../lib/formatTime';
66
import { palette } from '../../palette';
@@ -49,17 +49,9 @@ const hoverStyles = css`
4949
}
5050
`;
5151

52-
const textOverlayStyles = css`
53-
width: 100%;
54-
position: absolute;
55-
bottom: 0;
56-
display: flex;
57-
flex-direction: column;
58-
text-align: start;
59-
gap: ${space[1]}px;
60-
padding: 64px ${space[2]}px ${space[2]}px;
52+
const overlayMaskGradientStyles = (angle: string) => css`
6153
mask-image: linear-gradient(
62-
180deg,
54+
${angle},
6355
transparent 0px,
6456
rgba(0, 0, 0, 0.0381) 8px,
6557
rgba(0, 0, 0, 0.1464) 16px,
@@ -70,9 +62,31 @@ const textOverlayStyles = css`
7062
rgba(0, 0, 0, 0.9619) 56px,
7163
rgb(0, 0, 0) 64px
7264
);
65+
`;
66+
const textOverlayStyles = css`
67+
width: 100%;
68+
position: absolute;
69+
bottom: 0;
70+
display: flex;
71+
flex-direction: column;
72+
text-align: start;
73+
gap: ${space[1]}px;
74+
padding: 64px ${space[2]}px ${space[2]}px;
75+
${overlayMaskGradientStyles('180deg')};
76+
7377
backdrop-filter: blur(12px) brightness(0.5);
7478
`;
7579

80+
const immersiveOverlayStyles = css`
81+
${from.tablet} {
82+
height: 100%;
83+
width: 25%;
84+
padding: ${space[2]}px 64px ${space[2]}px ${space[2]}px;
85+
backdrop-filter: blur(12px) brightness(0.5);
86+
${overlayMaskGradientStyles('270deg')}
87+
}
88+
`;
89+
7690
const videoPillStyles = css`
7791
position: absolute;
7892
top: ${space[2]}px;
@@ -92,6 +106,7 @@ type Props = {
92106
duration?: number; // in seconds
93107
kicker?: string;
94108
aspectRatio?: AspectRatio;
109+
mobileAspectRatio?: AspectRatio;
95110
trailText?: string;
96111
isVideoArticle?: boolean;
97112
webPublicationDate?: string;
@@ -100,6 +115,7 @@ type Props = {
100115
linkTo?: string;
101116
discussionApiUrl?: string;
102117
discussionId?: string;
118+
isImmersive?: boolean;
103119
};
104120

105121
export const YoutubeAtomFeatureCardOverlay = ({
@@ -115,6 +131,7 @@ export const YoutubeAtomFeatureCardOverlay = ({
115131
kicker,
116132
format,
117133
aspectRatio,
134+
mobileAspectRatio,
118135
trailText,
119136
isVideoArticle,
120137
webPublicationDate,
@@ -123,6 +140,7 @@ export const YoutubeAtomFeatureCardOverlay = ({
123140
linkTo,
124141
discussionId,
125142
discussionApiUrl,
143+
isImmersive,
126144
}: Props) => {
127145
const id = `youtube-overlay-${uniqueId}`;
128146
const hasDuration = !isUndefined(duration) && duration > 0;
@@ -153,6 +171,7 @@ export const YoutubeAtomFeatureCardOverlay = ({
153171
height={height}
154172
width={width}
155173
aspectRatio={aspectRatio}
174+
mobileAspectRatio={mobileAspectRatio}
156175
/>
157176
)}
158177
{hasDuration && !isVideoArticle ? (
@@ -165,7 +184,12 @@ export const YoutubeAtomFeatureCardOverlay = ({
165184
) : null}
166185
<div className="image-overlay" />
167186
<PlayIcon iconWidth="narrow" />
168-
<div css={[textOverlayStyles]}>
187+
<div
188+
css={[
189+
textOverlayStyles,
190+
isImmersive && immersiveOverlayStyles,
191+
]}
192+
>
169193
{!!kicker && (
170194
<Kicker
171195
text={kicker}

0 commit comments

Comments
 (0)