feat(frontend): add Tape presentational component

This commit is contained in:
devilreef 2026-04-30 09:34:49 +06:00
parent d16bc80ac8
commit f069eef8a8
Signed by: devilreef
SSH key fingerprint: SHA256:UZisRr4iuXx+IhkbZnR655L2RWAT6o2rgbGv5F/6m3Y
2 changed files with 53 additions and 0 deletions

View file

@ -0,0 +1,30 @@
import type { Station } from '@lib/types';
interface Props {
station: Station;
index: number;
active: boolean;
onSelect: (id: string) => void;
listeners?: number | null;
}
export function Tape({ station, index, active, onSelect, listeners }: Props) {
return (
<button
className={`tape ${active ? 'active' : ''}`}
onClick={() => onSelect(station.id)}
type="button"
>
<div className="tape-num">CH.{String(index + 1).padStart(2, '0')}</div>
<div className="tape-name">{station.name}</div>
{station.tags.length > 0 ? (
<div className="tape-jp">{station.tags.slice(0, 2).join(' · ')}</div>
) : null}
<div className="tape-holes"><span /><span /></div>
<div className="tape-meta">
<span>{station.mounts.mp3.replace(/^\//, '')}</span>
<span>{listeners == null ? '— ppl' : `${listeners} ppl`}</span>
</div>
</button>
);
}

View file

@ -0,0 +1,23 @@
.tape {
font-family: var(--f-mono);
background: var(--cream); color: var(--ink);
padding: 10px 12px 12px;
border: 2px solid var(--ink);
box-shadow: 3px 3px 0 var(--ink);
cursor: pointer; text-align: left;
transition: transform 120ms ease-out, box-shadow 120ms ease-out;
width: 100%;
}
.tape:hover { transform: translate(-2px,-2px); box-shadow: 5px 5px 0 var(--ink); }
.tape.active {
background: var(--pink); color: var(--cream);
transform: translate(-3px,-3px) rotate(-1deg);
box-shadow: 6px 6px 0 var(--lemon);
}
.tape-num { font-family: var(--f-mono); font-size: 10px; opacity: 0.65; letter-spacing: 1.5px; }
.tape-name { font-family: var(--f-pixel); font-size: 16px; line-height: 1.1; margin-top: 2px; }
.tape-jp { font-family: var(--f-pixel); font-size: 11px; opacity: 0.85; letter-spacing: 1.5px; margin-top: 2px; }
.tape-meta { font-family: var(--f-mono); font-size: 10px; margin-top: 6px; display: flex; justify-content: space-between; opacity: 0.75; }
.tape-holes { display: flex; gap: 4px; margin-top: 6px; }
.tape-holes span { width: 14px; height: 14px; border-radius: 50%; background: var(--ink); box-shadow: inset 0 0 0 3px var(--cream); }
.tape.active .tape-holes span { box-shadow: inset 0 0 0 3px var(--pink); }