@@ -24,6 +24,7 @@ import type {
2424 DCRFrontImage ,
2525 DCRSupportingContent ,
2626} from '../types/front' ;
27+ import type { CardMediaType } from '../types/layout' ;
2728import type { MainMedia } from '../types/mainMedia' ;
2829import { BrandingLabel } from './BrandingLabel' ;
2930import { CardFooter } from './Card/components/CardFooter' ;
@@ -40,6 +41,7 @@ import { FormatBoundary } from './FormatBoundary';
4041import { Island } from './Island' ;
4142import { MediaDuration } from './MediaDuration' ;
4243import { Pill } from './Pill' ;
44+ import { SelfHostedVideo } from './SelfHostedVideo.importable' ;
4345import { StarRating } from './StarRating/StarRating' ;
4446import { SupportingContent } from './SupportingContent' ;
4547import { WaveForm } from './WaveForm' ;
@@ -125,7 +127,7 @@ const immersiveOverlayContainerStyles = css`
125127 * 48px is to ensure the gradient does not render the content inaccessible.
126128 */
127129 width : 268px ;
128- z-index : 1 ;
130+ z-index : ${ getZIndex ( 'feature-card-overlay' ) } ;
129131 }
130132` ;
131133
@@ -152,11 +154,14 @@ const overlayMaskGradientStyles = (angle: string) => css`
152154 );
153155` ;
154156const overlayStyles = css `
157+ position : relative;
155158 display : flex;
156159 flex-direction : column;
157160 text-align : start;
158161 gap : ${ space [ 1 ] } px;
159162 padding : 64px ${ space [ 2 ] } px ${ space [ 2 ] } px;
163+ /* Needs to be above self-hosted video */
164+ z-index : ${ getZIndex ( 'feature-card-overlay' ) } ;
160165 backdrop-filter : blur (12px ) brightness (0.5 );
161166 @supports not (backdrop-filter : blur (12px )) {
162167 background-color : ${ transparentColour ( sourcePalette . neutral [ 10 ] , 0.7 ) } ;
@@ -239,16 +244,35 @@ const getMedia = ({
239244 imageUrl,
240245 imageAltText,
241246 mainMedia,
242- canPlayInline ,
247+ showVideo ,
243248} : {
244249 imageUrl ?: string ;
245250 imageAltText ?: string ;
246251 mainMedia ?: MainMedia ;
247- canPlayInline ?: boolean ;
252+ showVideo ?: boolean ;
248253} ) => {
249- if ( mainMedia && mainMedia . type === 'YoutubeVideo' && canPlayInline ) {
254+ if ( mainMedia ?. type === 'SelfHostedVideo' && showVideo ) {
255+ let type : CardMediaType ;
256+ switch ( mainMedia . videoStyle ) {
257+ case 'Loop' :
258+ type = 'loop-video' ;
259+ break ;
260+ case 'Cinemagraph' :
261+ type = 'cinemagraph' ;
262+ break ;
263+ default :
264+ type = 'default-video' ;
265+ }
266+
267+ return {
268+ type,
269+ mainMedia,
270+ } as const ;
271+ }
272+
273+ if ( mainMedia ?. type === 'YoutubeVideo' && showVideo ) {
250274 return {
251- type : 'video' ,
275+ type : 'youtube- video' ,
252276 mainMedia,
253277 } as const ;
254278 }
@@ -305,12 +329,6 @@ export type Props = {
305329 showClock ?: boolean ;
306330 mainMedia ?: MainMedia ;
307331 trailText ?: string ;
308- /**
309- * Note YouTube recommends a minimum width of 480px @see https://developers.google.com/youtube/terms/required-minimum-functionality#embedded-youtube-player-size
310- * At 300px or below, the player will begin to lose functionality e.g. volume controls being omitted.
311- * Youtube requires a minimum width 200px.
312- */
313- canPlayInline ?: boolean ;
314332 kickerText ?: string ;
315333 showPulsingDot ?: boolean ;
316334 starRating ?: Rating ;
@@ -335,6 +353,7 @@ export type Props = {
335353 * The highlights container above the header is 0, the first container below the header is 1, etc.
336354 */
337355 collectionId : number ;
356+ uniqueId : string ;
338357 isNewsletter ?: boolean ;
339358 /**
340359 * An immersive feature card variant. It dictates that the card has a full width background image on
@@ -360,7 +379,6 @@ export const FeatureCard = ({
360379 imageLoading,
361380 showClock,
362381 mainMedia,
363- canPlayInline,
364382 kickerText,
365383 showPulsingDot,
366384 dataLinkName,
@@ -376,34 +394,37 @@ export const FeatureCard = ({
376394 starRating,
377395 showQuotes,
378396 collectionId,
397+ uniqueId,
379398 isNewsletter = false ,
380399 isImmersive = false ,
381400 showVideo = false ,
382401 showLabsRedesign = false ,
383402} : Props ) => {
384403 const hasSublinks = supportingContent && supportingContent . length > 0 ;
385404
386- const isVideoMainMedia = mainMedia ?. type === 'YoutubeVideo' ;
387405 const isVideoArticle = format . design === ArticleDesign . Video ;
388406
389- const videoDuration =
390- mainMedia ?. type === 'YoutubeVideo' ? mainMedia . duration : undefined ;
391-
407+ /**
408+ * Determine which type of media to use for the card.
409+ * For example, a video might be available, but if we don't want to show it, use an image instead.
410+ */
392411 const media = getMedia ( {
393412 imageUrl : image ?. src ,
394413 imageAltText : image ?. altText ,
395414 mainMedia,
396- canPlayInline ,
415+ showVideo ,
397416 } ) ;
398417
399- const showYoutubeVideo =
400- canPlayInline && showVideo && mainMedia ?. type === 'YoutubeVideo' ;
401-
402418 const showCardAge =
403419 webPublicationDate !== undefined && showClock !== undefined ;
404420
405421 const showCommentCount = discussionId !== undefined ;
406422
423+ const isSelfHostedVideo =
424+ media ?. type === 'loop-video' ||
425+ media ?. type === 'default-video' ||
426+ media ?. type === 'cinemagraph' ;
427+
407428 const labsDataAttributes = branding
408429 ? getOphanComponents ( {
409430 branding,
@@ -417,16 +438,16 @@ export const FeatureCard = ({
417438 < FormatBoundary format = { format } >
418439 < ContainerOverrides containerPalette = { containerPalette } >
419440 < div css = { [ baseCardStyles , hoverStyles , sublinkHoverStyles ] } >
420- { ! showYoutubeVideo && (
441+ { media ?. type !== 'youtube-video' && (
421442 < CardLink
422443 linkTo = { linkTo }
423444 headlineText = { headlineText }
424445 dataLinkName = { dataLinkName }
425446 isExternalLink = { isExternalLink }
426447 />
427448 ) }
428- < div css = { contentStyles } >
429- { showYoutubeVideo && (
449+ < div className = "contentStyles" css = { contentStyles } >
450+ { media ?. type === 'youtube-video' && (
430451 < div
431452 data-chromatic = "ignore"
432453 data-component = "youtube-atom"
@@ -441,15 +462,15 @@ export const FeatureCard = ({
441462 defer = { { until : 'visible' } }
442463 >
443464 < YoutubeBlockComponent
444- id = { mainMedia . id }
445- assetId = { mainMedia . videoId }
465+ id = { media . mainMedia . id }
466+ assetId = { media . mainMedia . videoId }
446467 index = { collectionId }
447- expired = { mainMedia . expired }
468+ expired = { media . mainMedia . expired }
448469 format = { format }
449470 stickyVideos = { false }
450471 enableAds = { false }
451- duration = { mainMedia . duration }
452- posterImage = { mainMedia . image }
472+ duration = { media . mainMedia . duration }
473+ posterImage = { media . mainMedia . image }
453474 width = { 300 }
454475 height = { 375 }
455476 origin = "The Guardian"
@@ -481,7 +502,7 @@ export const FeatureCard = ({
481502 </ Island >
482503 </ div >
483504 ) }
484- { ! showYoutubeVideo && media && (
505+ { media ?. type !== 'youtube-video' && (
485506 < div
486507 css = { css `
487508 position : relative;
@@ -490,24 +511,41 @@ export const FeatureCard = ({
490511 ) } ;
491512 ` }
492513 >
493- { media . type === 'video' && (
494- < div >
495- < CardPicture
496- mainImage = {
514+ { isSelfHostedVideo && (
515+ < Island
516+ priority = "critical"
517+ defer = { { until : 'visible' } }
518+ >
519+ < SelfHostedVideo
520+ sources = { media . mainMedia . sources }
521+ atomId = { media . mainMedia . atomId }
522+ uniqueId = { uniqueId }
523+ height = { media . mainMedia . height }
524+ width = { media . mainMedia . width }
525+ // Only cinemagraphs are currently supported in feature cards
526+ videoStyle = "Cinemagraph"
527+ posterImage = {
497528 media . mainMedia . image ?? ''
498529 }
499- imageSize = { imageSize }
500- alt = { headlineText }
501- loading = { imageLoading }
502- aspectRatio = { aspectRatio }
503- mobileAspectRatio = {
504- mobileAspectRatio
530+ fallbackImage = {
531+ media . mainMedia . image ?? ''
505532 }
533+ fallbackImageSize = { imageSize }
534+ fallbackImageLoading = { imageLoading }
535+ fallbackImageAlt = {
536+ media . imageAltText
537+ }
538+ fallbackImageAspectRatio = "5:4"
539+ linkTo = { linkTo }
540+ subtitleSource = {
541+ media . mainMedia . subtitleSource
542+ }
543+ subtitleSize = "large"
506544 />
507- </ div >
545+ </ Island >
508546 ) }
509547
510- { media . type === 'picture' && (
548+ { media ? .type === 'picture' && (
511549 < >
512550 < CardPicture
513551 mainImage = { media . imageUrl }
@@ -519,7 +557,7 @@ export const FeatureCard = ({
519557 mobileAspectRatio
520558 }
521559 />
522- { isVideoMainMedia &&
560+ { mainMedia ?. type === 'YoutubeVideo' &&
523561 mainMedia . duration > 0 && (
524562 < MediaDuration
525563 mediaDuration = {
@@ -697,14 +735,14 @@ export const FeatureCard = ({
697735 </ div >
698736 { /* On video article cards, the duration is displayed in the footer */ }
699737 { ! isVideoArticle &&
700- isVideoMainMedia &&
701- videoDuration !== undefined ? (
738+ mainMedia ?. type === 'YoutubeVideo' &&
739+ ! ! Number ( mainMedia . duration ) ? (
702740 < div css = { videoPillStyles } >
703741 < Pill
704742 content = {
705743 < time >
706744 { secondsToDuration (
707- videoDuration ,
745+ mainMedia . duration ,
708746 ) }
709747 </ time >
710748 }
0 commit comments