class PoliticsRepresentatives extends Shadow { constructor(reps, levelFilter) { super() this.reps = reps this.levelFilter = levelFilter } get visible() { if (this.levelFilter === "all") return this.reps return this.reps.filter(r => r.level === this.levelFilter) } get grouped() { const order = ["federal", "state", "local"] const map = {} order.forEach(l => { map[l] = [] }) this.visible.forEach(r => { if (map[r.level]) map[r.level].push(r) }) return order.map(l => ({ level: l, reps: map[l] })).filter(g => g.reps.length) } render() { VStack(() => { // Header VStack(() => { HStack(() => { VStack(() => { h2("Your Representatives") .margin(0).fontSize(1.1, em).fontWeight("700").color("var(--headertext)") p(`${this.visible.length} officials across your districts`) .margin(0).marginTop(0.2, em).fontSize(0.78, em) .color("var(--headertext)").opacity(0.42) }) // Party breakdown pill HStack(() => { const counts = { R: 0, D: 0, I: 0 } this.visible.forEach(r => { counts[r.party] = (counts[r.party] || 0) + 1 }) if (counts.R) this.partyCount(counts.R, "R") if (counts.D) this.partyCount(counts.D, "D") if (counts.I) this.partyCount(counts.I, "I") }) .gap(0.5, em).alignItems("center") }) .justifyContent("space-between").alignItems("flex-start") }) .paddingHorizontal(1.75, em).paddingVertical(1.25, em) .borderBottom("1px solid var(--divider)").flexShrink(0) // Groups VStack(() => { this.grouped.forEach(group => { VStack(() => { // Level header HStack(() => { HStack(() => {}) .flex(1).height(1, px).background("var(--divider)") p(this.levelLabel(group.level)) .margin(0).marginHorizontal(0.9, em) .fontSize(0.68, em).fontWeight("700").letterSpacing("0.08em") .color("var(--headertext)").opacity(0.35) .whiteSpace("nowrap") HStack(() => {}) .flex(1).height(1, px).background("var(--divider)") }) .alignItems("center").marginBottom(1, em) // Cards HStack(() => { group.reps.forEach(rep => this.renderCard(rep)) }) .gap(0.85, em).flexWrap("wrap") }) .paddingTop(1.35, em).paddingBottom(0.5, em) }) }) .paddingHorizontal(1.75, em) .overflowY("auto").flex(1) }) .height(100, pct).width(100, pct).overflow("hidden") } renderCard(rep) { const partyColor = this.partyColor(rep.party) const partyBg = this.partyBg(rep.party) VStack(() => { // Party accent bar VStack(() => {}) .height(4, px).width(100, pct) .background(partyColor).flexShrink(0) VStack(() => { // Avatar + status ZStack(() => { // Avatar circle VStack(() => { p(rep.initials) .margin(0).fontSize(1.15, em).fontWeight("800") .color("white").lineHeight("1") }) .width(3.8, em).height(3.8, em).borderRadius(50, pct) .background(partyColor) .justifyContent("center").alignItems("center") .flexShrink(0) // Party badge bottom-right VStack(() => { p(rep.party) .margin(0).fontSize(0.52, em).fontWeight("800") .color("white").lineHeight("1") }) .position("absolute").bottom(0).right(0) .width(1.3, em).height(1.3, em).borderRadius(50, pct) .background(partyColor) .boxSizing("border-box") .justifyContent("center").alignItems("center") }) .position("relative") .width(3.8, em).height(3.8, em).marginBottom(0.85, em) // Name p(rep.name) .margin(0).fontSize(0.92, em).fontWeight("700") .color("var(--headertext)").lineHeight("1.25").textAlign("center") // Title p(rep.title) .margin(0).marginTop(0.22, em).fontSize(0.72, em) .color("var(--headertext)").opacity(0.55) .textAlign("center").lineHeight("1.35") // District chip if (rep.district) { p(rep.district) .margin(0).marginTop(0.55, em) .paddingHorizontal(0.6, em).paddingVertical(0.18, em) .background(partyBg).border(`1px solid ${partyColor}33`) .borderRadius(100, px) .fontSize(0.68, em).fontWeight("600") .color(partyColor) } // Since if (rep.since) { p(`Since ${rep.since}`) .margin(0).marginTop(0.42, em) .fontSize(0.68, em).color("var(--headertext)").opacity(0.32) } // Contact row HStack(() => { if (rep.phone) { this.contactBtn("📞", rep.phone) } if (rep.website) { this.contactBtn("🌐", rep.website) } }) .gap(0.4, em).marginTop(0.75, em) }) .padding(1.1, em).alignItems("center") }) .width(200, px).minWidth(180, px) .background("var(--main)") .border("1px solid var(--divider)") .borderRadius(0.65, em) .overflow("hidden") .boxSizing("border-box") .flexShrink(0) } contactBtn(icon, value) { button(icon) .padding(0.35, em) .background("var(--darkaccent)") .border("1px solid var(--divider)") .borderRadius(0.4, em) .fontSize(0.8, em) .cursor("pointer") .attr({ title: value }) } partyCount(count, party) { HStack(() => { VStack(() => {}) .width(0.5, em).height(0.5, em).borderRadius(50, pct) .background(this.partyColor(party)).flexShrink(0) p(`${count} ${party}`) .margin(0).fontSize(0.75, em).fontWeight("600") .color(this.partyColor(party)) }) .gap(0.3, em).alignItems("center") .paddingHorizontal(0.6, em).paddingVertical(0.22, em) .background(this.partyBg(party)) .border(`1px solid ${this.partyColor(party)}33`) .borderRadius(100, px) } levelLabel(level) { return { federal: "FEDERAL DELEGATION", state: "STATE DELEGATION", local: "LOCAL OFFICIALS" }[level] || level.toUpperCase() } partyColor(party) { return { R: "#ef4444", D: "#3b82f6", I: "#8b5cf6", L: "#f59e0b", G: "#22c55e" }[party] || "#6b7280" } partyBg(party) { return { R: "rgba(239,68,68,0.08)", D: "rgba(59,130,246,0.08)", I: "rgba(139,92,246,0.08)", L: "rgba(245,158,11,0.08)", G: "rgba(34,197,94,0.08)" }[party] || "var(--darkaccent)" } } register(PoliticsRepresentatives)