A simple, clean, fast browser for the AtmosphereConf(2026) VODs
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: polish scroll header and harden HLS playback (AI-assisted)

j4ckxyz befacc57 7f7cf3c4

+83 -19
+48 -2
src/components/layout/app-shell.tsx
··· 1 1 import { Film, Info, Search } from 'lucide-react' 2 2 import { NavLink, type NavLinkProps } from 'react-router-dom' 3 - import { type PropsWithChildren } from 'react' 3 + import { type PropsWithChildren, useEffect, useRef, useState } from 'react' 4 4 5 5 import { cn } from '@/lib/utils' 6 6 ··· 44 44 } 45 45 46 46 export function AppShell({ children }: PropsWithChildren) { 47 + const [isHeaderHidden, setIsHeaderHidden] = useState(false) 48 + const lastScrollYRef = useRef(0) 49 + 50 + useEffect(() => { 51 + const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches 52 + if (prefersReducedMotion) { 53 + return 54 + } 55 + 56 + let ticking = false 57 + 58 + const onScroll = () => { 59 + if (ticking) { 60 + return 61 + } 62 + 63 + ticking = true 64 + window.requestAnimationFrame(() => { 65 + const currentY = window.scrollY 66 + const delta = currentY - lastScrollYRef.current 67 + 68 + if (currentY <= 8) { 69 + setIsHeaderHidden(false) 70 + } else if (delta > 10 && currentY > 72) { 71 + setIsHeaderHidden(true) 72 + } else if (delta < -10) { 73 + setIsHeaderHidden(false) 74 + } 75 + 76 + lastScrollYRef.current = currentY 77 + ticking = false 78 + }) 79 + } 80 + 81 + window.addEventListener('scroll', onScroll, { passive: true }) 82 + return () => { 83 + window.removeEventListener('scroll', onScroll) 84 + } 85 + }, []) 86 + 47 87 return ( 48 88 <div className="relative isolate min-h-svh bg-bg"> 49 - <header className="sticky top-0 z-10 border-b border-line/45 bg-surface/80 supports-[backdrop-filter]:backdrop-blur-md"> 89 + <header 90 + className={cn( 91 + 'sticky top-0 z-10 border-b border-line/45 bg-surface/80 transition-transform duration-300 ease-out supports-[backdrop-filter]:backdrop-blur-md', 92 + isHeaderHidden ? '-translate-y-full' : 'translate-y-0', 93 + )} 94 + onFocusCapture={() => setIsHeaderHidden(false)} 95 + > 50 96 <div className="mx-auto flex w-full max-w-5xl items-center justify-between gap-3 px-3 py-2.5 sm:px-4 md:px-6 md:py-3"> 51 97 <p className="text-base font-bold tracking-[0.01em] text-text md:text-lg">Atmosphere VODs</p> 52 98
+11 -10
src/components/talk-card.tsx
··· 18 18 const cardRef = useRef<HTMLAnchorElement | null>(null) 19 19 const [thumbnail, setThumbnail] = useState<string | null>(() => getCachedThumbnail(talk.uri)) 20 20 const [hasEnteredView, setHasEnteredView] = useState<boolean>(false) 21 - const featuredThumbnail = featured ? thumbnail : null 22 21 23 22 useEffect(() => { 24 - if (!featured || thumbnail || hasEnteredView || !cardRef.current) { 23 + if (thumbnail || hasEnteredView || !cardRef.current) { 25 24 return 26 25 } 27 26 ··· 46 45 return () => { 47 46 observer.disconnect() 48 47 } 49 - }, [featured, thumbnail, hasEnteredView]) 48 + }, [thumbnail, hasEnteredView]) 50 49 51 50 useEffect(() => { 52 - if (!featured || !hasEnteredView || thumbnail) { 51 + if (!hasEnteredView || thumbnail) { 53 52 return 54 53 } 55 54 ··· 65 64 return () => { 66 65 active = false 67 66 } 68 - }, [featured, hasEnteredView, thumbnail, talk.uri]) 67 + }, [hasEnteredView, thumbnail, talk.uri]) 69 68 70 69 return ( 71 70 <Link ··· 81 80 featured ? 'p-5 md:p-6' : 'p-4', 82 81 )} 83 82 > 84 - {featuredThumbnail ? ( 83 + {thumbnail ? ( 85 84 <div className="pointer-events-none absolute -inset-px"> 86 85 <img 87 - src={featuredThumbnail} 86 + src={thumbnail} 88 87 alt="" 89 88 aria-hidden="true" 90 89 loading="lazy" 91 90 decoding="async" 92 - className="block h-full w-full object-cover" 91 + className="block h-full w-full object-cover grayscale" 93 92 /> 94 93 <div 95 94 className="absolute inset-0" 96 95 style={{ 97 96 backgroundImage: 98 - 'linear-gradient(165deg, oklch(0 0 0 / 0.84), oklch(0.12 0 0 / 0.66))', 97 + featured 98 + ? 'linear-gradient(165deg, oklch(0 0 0 / 0.8), oklch(0.12 0 0 / 0.58))' 99 + : 'linear-gradient(165deg, oklch(0 0 0 / 0.9), oklch(0.12 0 0 / 0.72))', 99 100 }} 100 101 /> 101 102 </div> 102 103 ) : null} 103 104 104 - <div className="relative z-10"> 105 + <div className="relative z-10 min-h-[7rem]"> 105 106 <h3 106 107 className={cn( 107 108 'line-clamp-2 font-semibold leading-tight text-text',
+24 -7
src/pages/video-page.tsx
··· 66 66 setStatus('loading') 67 67 setError(null) 68 68 setCurrentTime(0) 69 + setPlaylistUrl(null) 69 70 70 71 let cancelled = false 71 72 73 + const onVideoError = () => { 74 + if (cancelled) { 75 + return 76 + } 77 + 78 + setError('Video playback failed in this browser. Please retry.') 79 + setStatus('error') 80 + } 81 + 82 + video.addEventListener('error', onVideoError) 83 + 72 84 async function load() { 73 85 try { 74 86 const playlistUrl = await fetchVideoPlaylist(uri) ··· 81 93 hlsRef.current = null 82 94 } 83 95 84 - if (video.canPlayType('application/vnd.apple.mpegurl')) { 85 - video.src = playlistUrl 86 - } else { 87 - const { default: Hls } = await import('hls.js/light') 88 - if (!Hls.isSupported()) { 89 - throw new Error('This browser does not support HLS playback.') 90 - } 96 + const { default: Hls } = await import('hls.js/light') 97 + if (cancelled) { 98 + return 99 + } 91 100 101 + if (Hls.isSupported()) { 92 102 const hls = new Hls({ 93 103 maxBufferLength: 30, 94 104 lowLatencyMode: true, ··· 96 106 hls.loadSource(playlistUrl) 97 107 hls.attachMedia(video) 98 108 hlsRef.current = hls 109 + } else if (video.canPlayType('application/vnd.apple.mpegurl')) { 110 + video.src = playlistUrl 111 + video.load() 112 + } else { 113 + throw new Error('This browser does not support HLS playback.') 99 114 } 100 115 101 116 setPlaylistUrl(playlistUrl) 117 + setError(null) 102 118 setStatus('ready') 103 119 } catch (loadError) { 104 120 if (cancelled) { ··· 116 132 117 133 return () => { 118 134 cancelled = true 135 + video.removeEventListener('error', onVideoError) 119 136 if (hlsRef.current) { 120 137 hlsRef.current.destroy() 121 138 hlsRef.current = null