sf-fleet-hud
uicrew-cardsource: ok · hit
Crew Card
Personnel card: portrait, name/callsign, rank ticks, assignment, status and vitals/duty telemetry. Portrait is supplied via the photo prop — no images ship with this item.
npx shadcn add "https://sf-fleet-hud.vercel.app/r/crew-card.json"free
Where it goes
stats
Interaction
animated-counter
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lightui-primitives@radix-ui/react-avatar
Registry dependencies
utilshttps://sf-fleet-hud.vercel.app/r/hud-avatar.jsonhttps://sf-fleet-hud.vercel.app/r/status-badge.jsonhttps://sf-fleet-hud.vercel.app/r/telemetry-bar.jsonhttps://sf-fleet-hud.vercel.app/r/hud-sparkline.json
Files1 · source cached 10/14/2026, 1:17:56 PM
registry/hud/crew-card.tsx
import * as React from "react"
import { cn } from "@/lib/utils"
import {
HudAvatar,
HudAvatarFallback,
HudAvatarImage,
HudAvatarRank,
HudAvatarStatus,
} from "@/registry/hud/hud-avatar"
import { StatusBadge } from "@/registry/hud/status-badge"
import { TelemetryBar } from "@/registry/hud/telemetry-bar"
import { HudSparkline } from "@/registry/hud/hud-sparkline"
type CrewStatus = "active" | "engaged" | "standby" | "critical" | "offline"
type CrewMember = {
/** Service ID rendered in the meta row, e.g. "ID-00417". */
id: string
/** Display name. Rendered uppercase; "SURNAME, GIVEN" reads best. */
name: string
/** Callsign, shown quoted under the name. */
callsign?: string
/** Rank label, e.g. "LT. CMDR". */
rank?: string
/** Rank tick bars under the photo, 0–5. */
rankTicks?: number
/** Assignment line, e.g. "OPS / NAV". */
unit?: string
status?: CrewStatus
/** Portrait URL. Falls back to initials when absent or on load failure. */
photo?: string
/** Vitals sparkline series. Hidden when omitted. */
vitals?: number[]
/** Duty load 0–100. Hidden when omitted. */
duty?: number
}
// Crew status drives both the badge treatment and the avatar corner dot.
const STATUS_MAP: Record<
CrewStatus,
{ label: string; badge: React.ComponentProps<typeof StatusBadge>["variant"]; dot: string }
> = {
active: { label: "Active", badge: "live", dot: "bg-primary" },
engaged: { label: "Engaged", badge: "engaged", dot: "bg-primary" },
standby: { label: "Standby", badge: "standby", dot: "bg-[#5A6065]" },
critical: { label: "Critical", badge: "critical", dot: "bg-destructive" },
offline: { label: "Offline", badge: "patrol", dot: "bg-[#3A3E42]" },
}
// Derives up to two initials for the photo-less fallback.
function initialsOf(name: string) {
const parts = name.replace(/,/g, " ").split(/\s+/).filter(Boolean)
if (parts.length === 0) return "??"
if (parts.length === 1) return parts[0].slice(0, 2)
return parts[0][0] + parts[1][0]
}
function CrewCard({
className,
crew,
showTelemetry = true,
...props
}: Omit<React.ComponentProps<"article">, "children"> & {
crew: CrewMember
showTelemetry?: boolean
}) {
const status = STATUS_MAP[crew.status ?? "active"]
const hasTelemetry = showTelemetry && (crew.vitals?.length || crew.duty != null)
return (
<article
className={cn("relative border border-border bg-card", className)}
{...props}
>
<div className="flex items-start gap-3.5 px-4 py-3.5">
<HudAvatar className="size-16" variant="strong">
{crew.photo && <HudAvatarImage src={crew.photo} alt="" />}
<HudAvatarFallback className="text-[15px] tracking-[0.05em]">
{initialsOf(crew.name)}
</HudAvatarFallback>
<HudAvatarStatus className={status.dot} />
{crew.rankTicks ? <HudAvatarRank count={crew.rankTicks} /> : null}
</HudAvatar>
<div className="min-w-0 flex-1">
<h3 className="m-0 truncate font-sans text-[15px] font-medium uppercase tracking-[0.12em] text-foreground">
{crew.name}
</h3>
{crew.callsign && (
<p className="mt-0.5 truncate font-mono text-[10px] uppercase tracking-[0.16em] text-primary">
“{crew.callsign}”
</p>
)}
{crew.rank && (
<p className="mt-1.5 truncate font-mono text-[10px] uppercase tracking-[0.16em] text-[#9AA0A4]">
{crew.rank}
</p>
)}
{crew.unit && (
<p className="mt-0.5 truncate font-mono text-[10px] uppercase tracking-[0.16em] text-[#5A6065]">
{crew.unit}
</p>
)}
</div>
</div>
<div className="flex items-center justify-between gap-2 border-t border-[#1D2023] px-4 py-2.5">
<StatusBadge variant={status.badge} dot={crew.status === "critical"}>
{status.label}
</StatusBadge>
<span className="font-mono text-[10px] uppercase tracking-[0.16em] text-[#5A6065]">
{crew.id}
</span>
</div>
{hasTelemetry ? (
<div className="flex flex-col gap-2.5 border-t border-[#1D2023] px-4 py-3">
{crew.vitals?.length ? (
<div className="flex items-center justify-between gap-3">
<span className="font-mono text-[10px] uppercase tracking-[0.14em] text-[#6E7478]">
Vitals
</span>
<HudSparkline
className="text-[#8A9094]"
values={crew.vitals}
width={110}
height={20}
/>
</div>
) : null}
{crew.duty != null && (
<TelemetryBar label="Duty Load" value={crew.duty} warn={crew.duty >= 85} />
)}
</div>
) : null}
</article>
)
}
export { CrewCard, type CrewMember, type CrewStatus }