11import { css } from '@emotion/react' ;
22import {
3+ type Breakpoint ,
4+ breakpoints ,
35 from ,
46 headlineBold15Object ,
57 headlineBold17Object ,
@@ -10,6 +12,7 @@ import {
1012 palette ,
1113} from '@guardian/source/foundations' ;
1214import { grid } from '../grid' ;
15+ import { generateImageURL } from '../lib/image' ;
1316import type { TagType } from '../types/tag' ;
1417
1518type Props = {
@@ -43,7 +46,7 @@ const configs = [
4346 ] ,
4447 tagIds : [ ] ,
4548 textColor : palette . neutral [ 7 ] ,
46- backgroundColor : '#CCCCCC ' ,
49+ backgroundColor : '#22B24B ' ,
4750 title : {
4851 label : 'Winter Olympics 2026' ,
4952 id : 'sport/winter-olympics-2026' ,
@@ -79,31 +82,6 @@ const configs = [
7982 } ,
8083] satisfies DirectoryPageNavConfig [ ] ;
8184
82- const backgroundImageStyles = (
83- images ?: DirectoryPageNavConfig [ 'backgroundImages' ] ,
84- ) => {
85- if ( ! images ) return { } ;
86-
87- return {
88- backgroundImage : `url(${ images . mobile } )` ,
89- backgroundSize : 'cover' ,
90- backgroundPosition : 'top center' ,
91-
92- [ from . mobileLandscape ] : {
93- backgroundImage : `url(${ images . mobileLandscape } )` ,
94- } ,
95- [ from . phablet ] : {
96- backgroundImage : `url(${ images . phablet } )` ,
97- } ,
98- [ from . tablet ] : {
99- backgroundImage : `url(${ images . tablet } )` ,
100- } ,
101- [ from . desktop ] : {
102- backgroundImage : `url(${ images . desktop } )` ,
103- } ,
104- } ;
105- } ;
106-
10785export const DirectoryPageNav = ( { pageId, pageTags } : Props ) => {
10886 const config = configs . find (
10987 ( cfg ) =>
@@ -123,21 +101,14 @@ export const DirectoryPageNav = ({ pageId, pageTags }: Props) => {
123101 backgroundColor,
124102 '&' : css ( grid . paddedContainer ) ,
125103 alignContent : 'space-between' ,
126- height : 116 ,
127- [ from . tablet ] : {
128- height : 140 ,
129- } ,
130- [ from . desktop ] : {
131- height : 150 ,
132- } ,
133- ...backgroundImageStyles ( config . backgroundImages ) ,
134104 } ) ;
135105
136106 const largeLinkStyles = css ( {
137107 ...headlineBold24Object ,
138108 color : textColor ,
139109 textDecoration : 'none' ,
140110 '&' : css ( grid . column . centre ) ,
111+ gridRow : 1 ,
141112 [ from . tablet ] : headlineBold42Object ,
142113 [ from . leftCol ] : css (
143114 grid . between ( 'left-column-start' , 'right-column-end' ) ,
@@ -148,6 +119,8 @@ export const DirectoryPageNav = ({ pageId, pageTags }: Props) => {
148119 display : 'flex' ,
149120 flexWrap : 'wrap' ,
150121 '&' : css ( grid . column . all ) ,
122+ gridRow : 2 ,
123+ alignSelf : 'end' ,
151124 position : 'relative' ,
152125 '--top-border-gap' : '1.55rem' ,
153126 [ from . mobileLandscape ] : {
@@ -225,7 +198,8 @@ export const DirectoryPageNav = ({ pageId, pageTags }: Props) => {
225198 } ) ;
226199
227200 return (
228- < nav css = { nav } >
201+ < nav css = { [ nav , heightStyles ] } >
202+ < BackgroundImage images = { config . backgroundImages } />
229203 < a href = { `/${ config . title . id } ` } css = { largeLinkStyles } >
230204 { config . title . label }
231205 </ a >
@@ -252,3 +226,96 @@ export const DirectoryPageNav = ({ pageId, pageTags }: Props) => {
252226 </ nav >
253227 ) ;
254228} ;
229+
230+ const heightStyles = css ( {
231+ height : 116 ,
232+ [ from . tablet ] : {
233+ height : 140 ,
234+ } ,
235+ [ from . desktop ] : {
236+ height : 150 ,
237+ } ,
238+ } ) ;
239+
240+ const BackgroundImage = ( props : {
241+ images : DirectoryPageNavConfig [ 'backgroundImages' ] ;
242+ } ) => {
243+ if ( props . images === undefined ) {
244+ return null ;
245+ }
246+
247+ return (
248+ < picture
249+ css = { [
250+ {
251+ '&' : css ( grid . column . all ) ,
252+ gridRow : '1/3' ,
253+ } ,
254+ heightStyles ,
255+ ] }
256+ >
257+ < Source images = { props . images } breakpoint = "wide" />
258+ < Source images = { props . images } breakpoint = "leftCol" />
259+ < Source images = { props . images } breakpoint = "desktop" />
260+ < Source images = { props . images } breakpoint = "tablet" />
261+ < Source images = { props . images } breakpoint = "phablet" />
262+ < Source images = { props . images } breakpoint = "mobileLandscape" />
263+ < Source images = { props . images } breakpoint = "mobileMedium" />
264+ < Source images = { props . images } breakpoint = "mobile" />
265+ < img
266+ src = { generateImageURL ( {
267+ mainImage : props . images . mobile ,
268+ imageWidth : breakpoints . mobileMedium ,
269+ resolution : 'low' ,
270+ } ) }
271+ alt = "Winter Olympics background graphic"
272+ css = { {
273+ width : '100%' ,
274+ height : '100%' ,
275+ objectFit : 'cover' ,
276+ objectPosition : 'top' ,
277+ } }
278+ />
279+ </ picture >
280+ ) ;
281+ } ;
282+
283+ const Source = ( props : { images : Images ; breakpoint : Breakpoint } ) => (
284+ < source
285+ media = { `(min-width: ${ breakpoints [ props . breakpoint ] } px)` }
286+ srcSet = { `${ generateImageURL ( {
287+ mainImage : props . images [ breakpointToImageSize ( props . breakpoint ) ] ,
288+ imageWidth : breakpoints [ props . breakpoint ] ,
289+ resolution : 'low' ,
290+ } ) } , ${ generateImageURL ( {
291+ mainImage : props . images [ breakpointToImageSize ( props . breakpoint ) ] ,
292+ imageWidth : breakpoints [ props . breakpoint ] ,
293+ resolution : 'high' ,
294+ } ) } 2x`}
295+ />
296+ ) ;
297+
298+ /**
299+ * We don't have an image for every breakpoint, so this picks an appropriate
300+ * image size in each case.
301+ */
302+ const breakpointToImageSize = ( breakpoint : Breakpoint ) : ImageSize => {
303+ switch ( breakpoint ) {
304+ case 'mobile' :
305+ case 'mobileMedium' :
306+ return 'mobile' ;
307+ case 'mobileLandscape' :
308+ return 'mobileLandscape' ;
309+ case 'phablet' :
310+ return 'phablet' ;
311+ case 'tablet' :
312+ return 'tablet' ;
313+ case 'desktop' :
314+ case 'leftCol' :
315+ case 'wide' :
316+ return 'desktop' ;
317+ }
318+ } ;
319+
320+ type Images = Exclude < DirectoryPageNavConfig [ 'backgroundImages' ] , undefined > ;
321+ type ImageSize = keyof Images ;
0 commit comments