class PeopleCard extends Shadow { constructor(person) { super() this.person = person } render() { const p_ = this.person const initials = ((p_.first_name?.[0] ?? "") + (p_.last_name?.[0] ?? "")).toUpperCase() const roles = Array.isArray(p_.roles) ? p_.roles : [] const isOnline = p_._online HStack(() => { // Avatar + online dot ZStack(() => { VStack(() => { if (p_.image_path) { img(`${config.SERVER}${p_.image_path}`, "2.75em", "2.75em") .borderRadius(50, pct) .objectFit("cover") } else { p(initials) .margin(0) .fontSize(0.78, em) .fontWeight("700") .color("white") } }) .width(2.75, em) .height(2.75, em) .borderRadius(50, pct) .background(p_.image_path ? "transparent" : this.avatarColor(p_.email || p_.first_name)) .justifyContent("center") .alignItems("center") .overflow("hidden") .flexShrink(0) // Online indicator if (isOnline) { VStack(() => {}) .width(0.68, em) .height(0.68, em) .borderRadius(50, pct) .background("#10b981") .boxSizing("border-box") .position("absolute") .bottom(0).right(0) } }) .position("relative") .width(2.75, em) .height(2.75, em) .flexShrink(0) // Info VStack(() => { HStack(() => { p(`${p_.first_name} ${p_.last_name}`) .margin(0) .fontSize(0.92, em) .fontWeight("600") .color("var(--headertext)") .flex(1) .minWidth(0) .overflow("hidden") .whiteSpace("nowrap") .textOverflow("ellipsis") if (isOnline) { p("online") .margin(0) .fontSize(0.65, em) .fontWeight("600") .color("#10b981") .flexShrink(0) } }) .alignItems("center") .gap(0.5, em) HStack(() => { p(p_.email ?? "") .margin(0) .fontSize(0.72, em) .color("var(--headertext)") .opacity(0.42) .overflow("hidden") .whiteSpace("nowrap") .textOverflow("ellipsis") .flex(1) .minWidth(0) roles.slice(0, 2).forEach(role => { p(role) .margin(0) .fontSize(0.62, em) .fontWeight("600") .color("var(--quillred)") .background("rgba(159,28,41,0.1)") .paddingHorizontal(0.45, em) .paddingVertical(0.12, em) .borderRadius(100, px) .whiteSpace("nowrap") .flexShrink(0) }) }) .gap(0.35, em) .alignItems("center") .marginTop(0.2, em) }) .flex(1) .minWidth(0) .gap(0) }) .gap(0.75, em) .paddingHorizontal(1, em) .paddingVertical(0.85, em) .alignItems("center") .width(100, pct) .boxSizing("border-box") } avatarColor(seed) { const colors = ["#3b82f6", "#9E1C29", "#10b981", "#f59e0b", "#8b5cf6", "#ec4899", "#06b6d4", "#84cc16"] if (!seed) return colors[0] let hash = 0 for (let i = 0; i < seed.length; i++) hash = seed.charCodeAt(i) + ((hash << 5) - hash) return colors[Math.abs(hash) % colors.length] } } register(PeopleCard)