Skip to content

Commit 4c3bbe1

Browse files
HugoGresseclaude
andauthored
Redesign the intermission lower-third (keep the shine border) (#268)
* feat(intermission): redesign the lower-third (less generic, keep shine) Move away from the generic translucent-glass card look toward a broadcast-style lower-third: - anchored bottom-left over a full-width scrim (legible on any media) - category-coloured accent bar down the left edge - filled "UP NEXT"/"NOW" tag (auto-contrast text, pulsing live dot) instead of dot + uppercase eyebrow - larger, tighter editorial title - time as a large accent number with separator + speakers (no chip) The animated conic-gradient shine border is kept unchanged. Accent colour derives from the talk's category, falling back to the shine pink. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(intermission): countdown beside the tag, filled category block - move the relative "in 8 min" up into the tag row, right after the UP NEXT / NOW tag (removed from the time/meta row) - render the category as a fully filled colour block (categoryColor background + auto-contrast text) instead of a swatch + muted label Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * style(intermission): baseline-align rows, two text sizes only - tag row (UP NEXT, countdown, track, category) all share SIZE_LABEL and baseline-align - time row (start time, speakers) shares SIZE_VALUE and baseline-aligns - emphasis now comes from weight/colour, not size — two sizes total Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * style(intermission): center the lower-third and tighten bottom margin Center the card horizontally (justify center + bottom-center transform origin) and reduce the gap to the bottom of the screen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 569f2ad commit 4c3bbe1

1 file changed

Lines changed: 156 additions & 80 deletions

File tree

src/public/intermission/IntermissionScreen.tsx

Lines changed: 156 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -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

5254
const isVideo = (url: string) => /\.(mp4|webm|ogg|ogv|mov|m4v)(\?.*)?$/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+
5475
export 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

Comments
 (0)