feat: richer link preview cards and user local time display

P2-7: Domain-specific URL preview cards — YouTube shows mqdefault.jpg
thumbnail with ▶ play overlay + title; GitHub shows inline SVG icon +
owner/repo parsed from og:title + star/language info from og:description;
generic cards get a Google favicon when og:image is absent; empty cards
(no title or description) are suppressed entirely

P2-9: Live local time in user profiles — useLocalTime(timezone, hour12)
uses Intl.DateTimeFormat with the user's m.tz IANA zone; updates every
60s; shows clock icon + formatted time + timezone abbreviation (EST/JST
etc.) in dim text; respects existing hour24Clock setting

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 23:15:29 -04:00
co-authored by Claude Sonnet 4.6
parent 51a355fe77
commit 01ba24df12
4 changed files with 472 additions and 55 deletions
@@ -47,3 +47,94 @@ export const UrlPreviewDescription = style([
overflow: 'hidden',
},
]);
// YouTube card styles
export const YouTubeThumbnailWrapper = style([
DefaultReset,
{
position: 'relative',
flexShrink: 0,
width: toRem(160),
height: toRem(90),
overflow: 'hidden',
cursor: 'pointer',
backgroundColor: color.Surface.Container,
':hover': {
filter: 'brightness(0.85)',
},
},
]);
export const YouTubeThumbnailImg = style([
DefaultReset,
{
width: '100%',
height: '100%',
objectFit: 'cover',
objectPosition: 'center',
display: 'block',
},
]);
export const YouTubePlayOverlay = style([
DefaultReset,
{
position: 'absolute',
inset: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
pointerEvents: 'none',
},
]);
export const YouTubePlayButton = style([
DefaultReset,
{
width: toRem(44),
height: toRem(44),
borderRadius: '50%',
backgroundColor: 'rgba(0, 0, 0, 0.75)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#ffffff',
},
]);
// GitHub card styles
export const GitHubIconWrapper = style([
DefaultReset,
{
flexShrink: 0,
width: toRem(72),
minHeight: toRem(72),
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: color.SurfaceVariant.OnContainer,
opacity: 0.8,
backgroundColor: color.Surface.ContainerHover,
},
]);
// Generic card favicon styles
export const GenericFaviconWrapper = style([
DefaultReset,
{
display: 'inline-flex',
alignItems: 'center',
gap: config.space.S100,
verticalAlign: 'middle',
},
]);
export const GenericFaviconImg = style([
DefaultReset,
{
width: toRem(16),
height: toRem(16),
flexShrink: 0,
},
]);