init
This commit is contained in:
163
politics/desktop/PoliticsSidebar.js
Normal file
163
politics/desktop/PoliticsSidebar.js
Normal file
@@ -0,0 +1,163 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user