164 lines
7.7 KiB
JavaScript
164 lines
7.7 KiB
JavaScript
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)
|