@@ -32,8 +32,10 @@ const shineCss = `
3232@keyframes op-shine-spin { to { --op-shine-ang: 360deg; } }
3333.op-content-inner { animation: op-inner-up 0.45s ease both; }
3434@keyframes op-inner-up { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
35+ @keyframes op-live-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
36+ .op-live { animation: op-live-pulse 1.4s ease-in-out infinite; }
3537@media (prefers-reduced-motion: reduce) {
36- .op-shine::before, .op-content-inner { animation: none; }
38+ .op-shine::before, .op-content-inner, .op-live { animation: none; }
3739}
3840`
3941
@@ -51,6 +53,25 @@ export type IntermissionScreenProps = {
5153
5254const isVideo = ( url : string ) => / \. ( m p 4 | w e b m | o g g | o g v | m o v | m 4 v ) ( \? .* ) ? $ / i. test ( url )
5355
56+ // Default accent (the same hot pink as the shine border) when an event has no category colour.
57+ const DEFAULT_ACCENT = '#ff4d6d'
58+
59+ // Two text sizes only: a label size (tag row) and a value size (time row). Emphasis comes from
60+ // weight/colour, not extra sizes, so each row baseline-aligns cleanly.
61+ const SIZE_LABEL = 'clamp(14px, 1.3vw, 21px)'
62+ const SIZE_VALUE = 'clamp(22px, 2.2vw, 34px)'
63+
64+ // Pick black or white text for a coloured background so the broadcast tag stays legible
65+ // whatever category colour the organiser chose.
66+ const readableOn = ( hex : string ) : string => {
67+ const h = hex . replace ( '#' , '' )
68+ if ( h . length < 6 ) return '#0b0b0c'
69+ const r = parseInt ( h . slice ( 0 , 2 ) , 16 )
70+ const g = parseInt ( h . slice ( 2 , 4 ) , 16 )
71+ const b = parseInt ( h . slice ( 4 , 6 ) , 16 )
72+ return ( 0.299 * r + 0.587 * g + 0.114 * b ) / 255 > 0.62 ? '#0b0b0c' : '#fff'
73+ }
74+
5475export const IntermissionScreen = ( {
5576 mediaUrl,
5677 nextTalk,
@@ -70,6 +91,7 @@ export const IntermissionScreen = ({
7091 . join ( ', ' )
7192 : ''
7293 const startTime = nextTalk ? DateTime . fromISO ( nextTalk . dateStart ) . toFormat ( 'HH:mm' ) : ''
94+ const accent = categoryColor || DEFAULT_ACCENT
7395
7496 // Persisted display options (cog menu, top-right)
7597 const [ settingsOpen , setSettingsOpen ] = useState ( false )
@@ -254,6 +276,9 @@ export const IntermissionScreen = ({
254276 ) }
255277 </ div >
256278
279+ { /* Bottom scrim so the lower-third stays legible over bright media */ }
280+ < div style = { styles . scrim } />
281+
257282 < style > { shineCss } </ style >
258283 < div style = { styles . contentWrap } >
259284 < div
@@ -265,42 +290,58 @@ export const IntermissionScreen = ({
265290 opacity : cardShown ? 1 : 0 ,
266291 transition : 'opacity 0.6s ease' ,
267292 } } >
268- < div className = { cardShown ? 'op-content-inner' : undefined } key = { cardShown ? 'shown' : 'hidden' } >
269- { nextTalk ? (
270- < >
271- < div style = { styles . eyebrow } >
272- < span style = { styles . dot } />
273- { isOngoing ? t . now : t . upNext }
274- { trackName ? ` · ${ trackName } ` : '' }
275- { categoryName && (
276- < span
277- style = { {
278- ...styles . categoryPill ,
279- background : categoryColor || 'rgba(255,255,255,0.18)' ,
280- } } >
281- { categoryName }
293+ < div style = { { ...styles . accentBar , background : accent } } />
294+ < div style = { styles . inner } >
295+ < div
296+ className = { cardShown ? 'op-content-inner' : undefined }
297+ key = { cardShown ? 'shown' : 'hidden' } >
298+ { nextTalk ? (
299+ < >
300+ < div style = { styles . tagRow } >
301+ < span style = { { ...styles . tag , background : accent , color : readableOn ( accent ) } } >
302+ { isOngoing && < span className = "op-live" style = { styles . liveDot } /> }
303+ { isOngoing ? t . now : t . upNext }
282304 </ span >
283- ) }
284- </ div >
285- < div style = { styles . title } > { nextTalk . title } </ div >
286- < div style = { styles . meta } >
287- { startTime && < span style = { styles . time } > { startTime } </ span > }
288- { relativeStart && < span style = { styles . relative } > { relativeStart } </ span > }
289- { speakerNames && < span style = { styles . speakers } > { speakerNames } </ span > }
290- </ div >
291- </ >
292- ) : (
293- < >
294- < div style = { styles . eyebrow } >
295- < span style = { styles . dot } />
296- { trackName || eventName || '' }
297- </ div >
298- < div style = { styles . title } > { t . wrapTitle } </ div >
299- < div style = { styles . meta } >
300- < span style = { styles . speakers } > { t . wrapSubtitle } </ span >
301- </ div >
302- </ >
303- ) }
305+ { relativeStart && < span style = { styles . relative } > { relativeStart } </ span > }
306+ { trackName && < span style = { styles . track } > { trackName } </ span > }
307+ { categoryName && (
308+ < span
309+ style = { {
310+ ...styles . categoryTag ,
311+ background : categoryColor || accent ,
312+ color : readableOn ( categoryColor || accent ) ,
313+ } } >
314+ { categoryName }
315+ </ span >
316+ ) }
317+ </ div >
318+ < div style = { styles . title } > { nextTalk . title } </ div >
319+ < div style = { styles . meta } >
320+ { startTime && (
321+ < span style = { { ...styles . time , color : accent } } > { startTime } </ span >
322+ ) }
323+ { speakerNames && (
324+ < >
325+ { startTime && < span style = { styles . sep } /> }
326+ < span style = { styles . speakers } > { speakerNames } </ span >
327+ </ >
328+ ) }
329+ </ div >
330+ </ >
331+ ) : (
332+ < >
333+ < div style = { styles . tagRow } >
334+ < span style = { { ...styles . tag , background : accent , color : readableOn ( accent ) } } >
335+ { trackName || eventName || '' }
336+ </ span >
337+ </ div >
338+ < div style = { styles . title } > { t . wrapTitle } </ div >
339+ < div style = { styles . meta } >
340+ < span style = { styles . speakers } > { t . wrapSubtitle } </ span >
341+ </ div >
342+ </ >
343+ ) }
344+ </ div >
304345 </ div >
305346 </ div >
306347 </ div >
@@ -398,83 +439,118 @@ const styles: { [key: string]: React.CSSProperties } = {
398439 fontSize : 14 ,
399440 cursor : 'pointer' ,
400441 } ,
442+ scrim : {
443+ position : 'absolute' ,
444+ left : 0 ,
445+ right : 0 ,
446+ bottom : 0 ,
447+ height : '55vh' ,
448+ background : 'linear-gradient(to top, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0.45) 45%, rgba(0,0,0,0) 100%)' ,
449+ pointerEvents : 'none' ,
450+ } ,
401451 contentWrap : {
402452 position : 'absolute' ,
403453 left : 0 ,
404454 right : 0 ,
405- bottom : '5vh' ,
455+ bottom : 'clamp(12px, 2. 5vh, 28px) ' ,
406456 display : 'flex' ,
407457 justifyContent : 'center' ,
458+ paddingLeft : 'clamp(20px, 4vw, 64px)' ,
459+ paddingRight : 'clamp(20px, 4vw, 64px)' ,
408460 pointerEvents : 'none' ,
409461 } ,
410462 content : {
411- maxWidth : '90vw' ,
412- minWidth : 'min(620px, 86vw)' ,
463+ position : 'relative' ,
464+ display : 'flex' ,
465+ alignItems : 'stretch' ,
466+ maxWidth : 'min(1100px, 92vw)' ,
413467 color : '#fff' ,
414- background : 'rgba(10,10,14,0.55)' ,
415- border : '1px solid rgba(255,255,255,0.14)' ,
416- borderRadius : 18 ,
417- padding : 'clamp(16px, 2.2vh, 28px) clamp(20px, 2.6vw, 36px)' ,
418- backdropFilter : 'blur(14px)' ,
419- boxShadow : '0 8px 40px rgba(0,0,0,0.45)' ,
468+ background : 'rgba(9,9,12,0.72)' ,
469+ borderRadius : 12 ,
470+ overflow : 'hidden' ,
471+ backdropFilter : 'blur(6px)' ,
420472 } ,
421- eyebrow : {
473+ accentBar : {
474+ flexShrink : 0 ,
475+ width : 'clamp(6px, 0.5vw, 10px)' ,
476+ } ,
477+ inner : {
478+ padding : 'clamp(18px, 2.6vh, 34px) clamp(24px, 3vw, 48px)' ,
479+ } ,
480+ tagRow : {
481+ display : 'flex' ,
482+ alignItems : 'baseline' ,
483+ gap : 'clamp(10px, 1vw, 16px)' ,
484+ marginBottom : 'clamp(10px, 1.4vh, 18px)' ,
485+ } ,
486+ tag : {
422487 display : 'inline-flex' ,
423488 alignItems : 'center' ,
424- gap : 10 ,
425- fontSize : 'clamp(13px, 1.1vw, 18px)' ,
426- fontWeight : 600 ,
427- letterSpacing : 2 ,
489+ gap : 8 ,
490+ padding : '5px 12px' ,
491+ borderRadius : 4 ,
492+ fontSize : SIZE_LABEL ,
493+ fontWeight : 800 ,
494+ letterSpacing : 1.5 ,
428495 textTransform : 'uppercase' ,
429- color : 'rgba(255,255,255,0.85)' ,
430- marginBottom : 'clamp(6px, 1vh, 12px)' ,
431496 } ,
432- dot : {
497+ liveDot : {
433498 width : 9 ,
434499 height : 9 ,
435500 borderRadius : '50%' ,
436- background : '#ff4d6d' ,
437- boxShadow : '0 0 12px 3px rgba(255,77,109,0.8)' ,
501+ background : 'currentColor' ,
438502 } ,
439- title : {
440- fontSize : 'clamp(26px, 3.2vw, 52px)' ,
503+ track : {
504+ fontSize : SIZE_LABEL ,
505+ fontWeight : 600 ,
506+ letterSpacing : 1 ,
507+ textTransform : 'uppercase' ,
508+ color : 'rgba(255,255,255,0.7)' ,
509+ } ,
510+ categoryTag : {
511+ display : 'inline-flex' ,
512+ alignItems : 'center' ,
513+ padding : '5px 12px' ,
514+ borderRadius : 4 ,
515+ fontSize : SIZE_LABEL ,
441516 fontWeight : 700 ,
442- lineHeight : 1.08 ,
443- letterSpacing : - 0.5 ,
517+ letterSpacing : 0.5 ,
444518 } ,
519+ title : {
520+ fontSize : 'clamp(30px, 4vw, 68px)' ,
521+ fontWeight : 800 ,
522+ lineHeight : 1.02 ,
523+ letterSpacing : - 1 ,
524+ textWrap : 'balance' ,
525+ } as React . CSSProperties ,
445526 meta : {
446527 display : 'flex' ,
447- alignItems : 'center ' ,
528+ alignItems : 'baseline ' ,
448529 gap : 'clamp(12px, 1.4vw, 22px)' ,
449- marginTop : 'clamp(10px , 1.4vh, 18px )' ,
530+ marginTop : 'clamp(12px , 1.8vh, 24px )' ,
450531 flexWrap : 'wrap' ,
451532 } ,
452533 time : {
453- fontSize : 'clamp(16px, 1.6vw, 26px)' ,
454- fontWeight : 700 ,
455- padding : '4px 14px' ,
456- borderRadius : 10 ,
457- background : 'rgba(255,255,255,0.14)' ,
458- border : '1px solid rgba(255,255,255,0.2)' ,
534+ fontSize : SIZE_VALUE ,
535+ fontWeight : 800 ,
536+ letterSpacing : - 0.5 ,
537+ fontVariantNumeric : 'tabular-nums' ,
459538 } ,
460539 relative : {
461- fontSize : 'clamp(15px, 1.4vw, 23px)' ,
540+ fontSize : SIZE_LABEL ,
462541 fontWeight : 600 ,
463- color : '#ff7591' ,
542+ color : 'rgba(255,255,255,0.78)' ,
543+ } ,
544+ sep : {
545+ width : 5 ,
546+ height : 5 ,
547+ borderRadius : '50%' ,
548+ background : 'rgba(255,255,255,0.4)' ,
549+ alignSelf : 'center' ,
464550 } ,
465551 speakers : {
466- fontSize : 'clamp(15px, 1.5vw, 24px)' ,
552+ fontSize : SIZE_VALUE ,
467553 fontWeight : 500 ,
468554 color : 'rgba(255,255,255,0.92)' ,
469555 } ,
470- categoryPill : {
471- marginLeft : 6 ,
472- padding : '2px 10px' ,
473- borderRadius : 999 ,
474- fontSize : 'clamp(11px, 0.9vw, 15px)' ,
475- fontWeight : 600 ,
476- letterSpacing : 1 ,
477- color : '#fff' ,
478- textShadow : '0 1px 4px rgba(0,0,0,0.5)' ,
479- } ,
480556}
0 commit comments