forked from
juliet.paris/streamplace-spa
minimal streamplace frontend
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}