/** * PBL v2 — Star rating (two variants). * * One renderer, two presentation policies. Stars are always 0–5 in * 0.5 increments (clamped + rounded server-side by normalizeStars; * we render whatever number we get). * * - plain row, NO "n/5" tail. * used for milestone reflection * (the user explicitly asked for * icon vibes, not graded vibes). * - * row + "n / 5" trailing text in * monospace. used by task-eval * card where the score IS a * judgement. * * Half-star rendering: a CSS gradient mask paints the left half of * a star glyph gold and the right half neutral. No SVG paint-server * hacks, no duplicate glyphs. */ import { cn } from '@/lib/utils'; interface Props { /** 0-5 in 0.5 increments. Out-of-range values are clamped at * render time; non-finite values fall back to 0. */ value: number; showDenominator?: boolean; /** Override star size (px). Defaults to 16. */ size?: number; className?: string; } export function StarRating({ value, showDenominator, size = 16, className }: Props) { const safe = Number.isFinite(value) ? Math.max(0, Math.min(5, value)) : 0; return ( {[0, 1, 2, 3, 4].map((i) => { const filled = safe >= i + 1; const half = !filled && safe > i; return ( ); })} {showDenominator && ( {safe.toFixed(safe % 1 === 0 ? 0 : 1)} / 5 )} ); }