class PoliticsSidebar extends Shadow { constructor(view, levelFilter, onViewChange, onLevelChange, jurisdiction, elections) { super() this.view = view this.levelFilter = levelFilter this.onViewChange = onViewChange this.onLevelChange = onLevelChange this.jurisdiction = jurisdiction this.elections = elections } render() { VStack(() => { // ── App title ───────────────────────────────────────────── VStack(() => { p("⚖️") .margin(0).fontSize(1.6, em).lineHeight("1") h2("Civics") .margin(0).marginTop(0.35, em).fontSize(1.05, em).fontWeight("700") .color("var(--headertext)") p("Your Political Dashboard") .margin(0).marginTop(0.12, em).fontSize(0.72, em) .color("var(--headertext)").opacity(0.38) }) .paddingHorizontal(1.1, em).paddingTop(1.2, em).paddingBottom(0.9, em) // ── Divider ─────────────────────────────────────────────── VStack(() => {}).height(1, px).background("var(--divider)").marginHorizontal(1, em) // ── Main nav ────────────────────────────────────────────── VStack(() => { this.sectionLabel("VIEWS") this.navItem("🏛", "Representatives", "representatives") this.navItem("🗳", "Elections", "elections") }) .paddingTop(0.75, em) VStack(() => {}).height(1, px).background("var(--divider)").marginHorizontal(1, em).marginVertical(0.5, em) // ── Level filter ────────────────────────────────────────── VStack(() => { this.sectionLabel("GOVERNMENT LEVEL") this.levelItem("all", "🇺🇸 + 🏠 All") this.levelItem("federal", "🇺🇸 Federal") this.levelItem("state", "🏠 State") this.levelItem("local", "📍 Local") }) VStack(() => {}).height(1, px).background("var(--divider)").marginHorizontal(1, em).marginVertical(0.5, em) // ── Jurisdiction card ───────────────────────────────────── VStack(() => { this.sectionLabel("YOUR DISTRICT") VStack(() => { this.jurisdictionRow("Federal", this.jurisdiction.federal) this.jurisdictionRow("State Senate", this.jurisdiction.stateSenate) this.jurisdictionRow("State House", this.jurisdiction.stateHouse) this.jurisdictionRow("County", this.jurisdiction.county) }) .padding(0.75, em) .background("var(--darkaccent)") .border("1px solid var(--divider)") .borderRadius(0.55, em) .marginHorizontal(0.75, em) .gap(0) }) .paddingTop(0) VStack(() => {}).flex(1) // ── Next election countdown ─────────────────────────────── VStack(() => { const next = this.elections .filter(e => new Date(e.date) > new Date()) .sort((a, b) => new Date(a.date) - new Date(b.date))[0] if (next) { const days = Math.ceil((new Date(next.date) - new Date()) / 86400000) VStack(() => { p("NEXT ELECTION") .margin(0).fontSize(0.6, em).fontWeight("700").letterSpacing("0.07em") .color("var(--headertext)").opacity(0.35) p(next.title) .margin(0).marginTop(0.35, em).fontSize(0.8, em).fontWeight("600") .color("var(--headertext)").lineHeight("1.3") HStack(() => { p(`${days}`) .margin(0).fontSize(1.6, em).fontWeight("800") .color("var(--quillred)").lineHeight("1") p("days away") .margin(0).marginLeft(0.35, em).fontSize(0.72, em) .color("var(--headertext)").opacity(0.45) .alignSelf("flex-end").paddingBottom(0.1, em) }) .alignItems("flex-end").marginTop(0.5, em) }) .padding(0.85, em) .background("var(--darkaccent)") .border("1px solid var(--divider)") .borderRadius(0.55, em) .cursor("pointer") .onClick((done) => {if(!done) return; this.onViewChange("elections")}) } }) .paddingHorizontal(0.75, em).paddingBottom(1.1, em).flexShrink(0) }) .height(100, pct).width(100, pct).boxSizing("border-box").overflowY("auto") } navItem(icon, label, viewId) { const isActive = this.view === viewId HStack(() => { p(icon).margin(0).fontSize(0.95, em).lineHeight("1").flexShrink(0) p(label).margin(0).fontSize(0.88, em).fontWeight(isActive ? "600" : "400") .color("var(--headertext)").opacity(isActive ? 1 : 0.65) }) .gap(0.62, em).paddingHorizontal(0.85, em).paddingVertical(0.45, em) .marginHorizontal(0.4, em) .borderRadius(0.45, em) .background(isActive ? "var(--app)" : "transparent") .cursor("pointer").alignItems("center") .width("calc(100% - 0.8em)").boxSizing("border-box") .onClick((done) => {if(!done) return; this.onViewChange(viewId)}) } levelItem(level, label) { const isActive = this.levelFilter === level HStack(() => { p(label).margin(0).fontSize(0.85, em).fontWeight(isActive ? "600" : "400") .color("var(--headertext)").opacity(isActive ? 1 : 0.58) }) .paddingHorizontal(0.85, em).paddingVertical(0.38, em) .marginHorizontal(0.4, em) .borderRadius(0.45, em) .background(isActive ? "var(--app)" : "transparent") .cursor("pointer") .width("calc(100% - 0.8em)").boxSizing("border-box") .onClick((done) => {if(!done) return; this.onLevelChange(level)}) } jurisdictionRow(label, value) { HStack(() => { p(label) .margin(0).fontSize(0.68, em).color("var(--headertext)").opacity(0.4) .width(5.5, em).flexShrink(0) p(value) .margin(0).fontSize(0.72, em).fontWeight("600").color("var(--headertext)") .flex(1).minWidth(0).overflow("hidden").whiteSpace("nowrap").textOverflow("ellipsis") }) .paddingVertical(0.32, em) .borderBottom("1px solid var(--divider)") .alignItems("flex-start").gap(0.4, em) } sectionLabel(text) { p(text) .margin(0).marginBottom(0.28, em).paddingHorizontal(1.1, em) .fontSize(0.62, em).fontWeight("700").letterSpacing("0.07em") .color("var(--headertext)").opacity(0.35) } } register(PoliticsSidebar)