diff --git a/src/components/CouncilMark.astro b/src/components/CouncilMark.astro new file mode 100644 index 0000000..17e1c68 --- /dev/null +++ b/src/components/CouncilMark.astro @@ -0,0 +1,145 @@ +--- +import type { UserPublic } from '../lib/db'; +import { getLitQuarters } from '../lib/db'; + +interface Props { + member: UserPublic; + size?: 'sm' | 'md' | 'lg'; +} + +const { member, size = 'md' } = Astro.props; + +const SIZES = { sm: 48, md: 100, lg: 200 } as const; +const px = SIZES[size]; + +// 12 positions on a circle, i=0 = current quarter at 12 o'clock, going clockwise +// into the past. A position is "lit" iff the member has a roadmap_attribution +// to an item with shipped_at falling in that quarter. Pulse votes do NOT light. +const lit = getLitQuarters(member.id); +const litArr = [...lit].sort((a, b) => a - b); + +const CENTER = 60; +const RADIUS = 42; +function pointAt(i: number) { + const angle = -Math.PI / 2 + (i * Math.PI * 2) / 12; + return { x: CENTER + RADIUS * Math.cos(angle), y: CENTER + RADIUS * Math.sin(angle) }; +} + +const litPoints = litArr.map(pointAt); +const polygonPoints = litPoints.length >= 3 + ? litPoints.map(p => `${p.x.toFixed(2)},${p.y.toFixed(2)}`).join(' ') + : null; + +function initials(name: string): string { + const parts = name.trim().split(/\s+/); + if (parts.length === 1) return (parts[0][0] ?? '').toUpperCase(); + return ((parts[0][0] ?? '') + (parts[parts.length - 1][0] ?? '')).toUpperCase(); +} +--- + + + + + + {Array.from({ length: 12 }).map((_, i) => { + if (lit.has(i)) return null; + const p = pointAt(i); + return ; + })} + + {polygonPoints && ( + + )} + + {litArr.map((i, idx) => { + const p = pointAt(i); + return ( + + ); + })} + + {initials(member.name)} + + +