minimal streamplace frontend
at main 55 lines 2.0 kB view raw
1import { A } from "@solidjs/router"; 2import { createSignal, onCleanup } from "solid-js"; 3 4export interface StreamCardProps { 5 handle: string; 6 did: string; 7 title: string; 8 viewerCount: number; 9 avatarUrl?: string; 10 thumbRef?: string; 11} 12 13export function StreamCard(props: StreamCardProps) { 14 const [ts, setTs] = createSignal(Date.now()); 15 const interval = setInterval(() => setTs(Date.now()), 15_000); 16 onCleanup(() => clearInterval(interval)); 17 18 const thumbUrl = () => 19 `https://stream.place/api/playback/${encodeURIComponent(props.handle)}/stream.jpg?ts=${ts()}`; 20 21 return ( 22 <A 23 href={`/${props.handle}`} 24 class="group border-sp-border bg-sp-surface hover:border-sp-accent overflow-hidden rounded-lg border transition-colors" 25 > 26 <div class="bg-sp-bg relative aspect-video w-full overflow-hidden"> 27 {thumbUrl() ? ( 28 <img src={thumbUrl()} alt="" class="h-full w-full object-cover" loading="lazy" /> 29 ) : ( 30 <div class="flex h-full items-center justify-center"> 31 <img src="/favicon.svg" alt="" class="h-10 w-10 opacity-50" /> 32 </div> 33 )} 34 <div class="absolute bottom-3 left-3 flex items-center gap-1.5 rounded bg-black/70 px-2 py-1 text-xs text-white backdrop-blur-sm"> 35 <span 36 class="bg-sp-accent inline-block h-2 w-2 rounded-full" 37 style={{ animation: "pulse-live 3s ease-in-out infinite" }} 38 /> 39 {props.viewerCount} 40 </div> 41 </div> 42 <div class="group-hover:bg-sp-accent/10 flex gap-3 p-3 transition-colors"> 43 {props.avatarUrl ? ( 44 <img src={props.avatarUrl} alt="" class="h-9 w-9 shrink-0 rounded-full" loading="lazy" /> 45 ) : ( 46 <div class="bg-sp-border h-9 w-9 shrink-0 rounded-full" /> 47 )} 48 <div class="min-w-0 flex-1"> 49 <div class="truncate text-sm font-medium">{props.title}</div> 50 <div class="text-sp-dim truncate text-xs">@{props.handle}</div> 51 </div> 52 </div> 53 </A> 54 ); 55}