@empoweredvote/ev-ui 0.7.1 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,7 +1,6 @@
1
1
  // src/RadarChartCore.jsx
2
- import React from "react";
2
+ import React, { useState, useRef, useEffect } from "react";
3
3
  import { animated, useSpring } from "@react-spring/web";
4
- import { useRef, useEffect } from "react";
5
4
  function RadarChartCore({
6
5
  topics,
7
6
  data,
@@ -13,14 +12,17 @@ function RadarChartCore({
13
12
  onReplaceTopic = () => {
14
13
  },
15
14
  size = 400,
16
- labelFontSize = 18,
17
15
  padding = 80,
18
- labelOffset = 20,
19
- tightFit = false
16
+ tightFit = false,
17
+ activeSpoke = null,
18
+ writeIns = {}
20
19
  }) {
20
+ var _a;
21
21
  const radius = size / 2 - 40;
22
+ const extRadius = radius * 1.15;
22
23
  const centerX = size / 2;
23
24
  const centerY = size / 2;
25
+ const [tooltip, setTooltip] = useState(null);
24
26
  const spokes = Object.entries(data);
25
27
  const numSpokes = Math.max(spokes.length, 1);
26
28
  const prevCountRef = useRef(numSpokes);
@@ -28,42 +30,40 @@ function RadarChartCore({
28
30
  useEffect(() => {
29
31
  prevCountRef.current = numSpokes;
30
32
  }, [numSpokes]);
31
- const pointsArr = spokes.map(([shortTitle, value], index) => {
32
- var _a;
33
+ function calcAdjusted(value, shortTitle) {
34
+ var _a2;
35
+ if (!value || Number(value) === 0) return 0;
33
36
  const topic = topics.find((t) => t.short_title === shortTitle);
34
- const max = ((_a = topic == null ? void 0 : topic.stances) == null ? void 0 : _a.length) || 10;
35
- const pct = Math.min(value / max * 10, 10);
36
- const angle = 2 * Math.PI * index / numSpokes;
37
- const adjusted = value === 0 ? 0 : invertedSpokes[shortTitle] ? 11 - pct : pct;
37
+ const max = ((_a2 = topic == null ? void 0 : topic.stances) == null ? void 0 : _a2.length) || 10;
38
+ const pct = Number(value) / max * 10;
39
+ return invertedSpokes[shortTitle] ? (max + 1) * 10 / max - pct : pct;
40
+ }
41
+ const pointsArr = spokes.map(([shortTitle, value], index) => {
42
+ const adjusted = calcAdjusted(value, shortTitle);
38
43
  const r = adjusted / 10 * radius;
39
- const x = centerX + r * Math.sin(angle);
40
- const y = centerY - r * Math.cos(angle);
41
- return [x, y];
44
+ const angle = 2 * Math.PI * index / numSpokes;
45
+ return [centerX + r * Math.sin(angle), centerY - r * Math.cos(angle)];
42
46
  });
43
47
  const targetPoints = pointsArr.map((p) => p.join(",")).join(" ");
44
- const spring = useSpring({
48
+ const springBase = {
45
49
  to: { points: targetPoints },
46
50
  immediate: countChanged,
47
- reset: countChanged,
48
51
  config: { tension: 300, friction: 30 }
49
- });
52
+ };
53
+ if (countChanged) springBase.from = { points: targetPoints };
54
+ const spring = useSpring(springBase);
50
55
  const hasCompareData = Object.keys(compareData).length > 0;
51
56
  const centerPoints = spokes.map(() => `${centerX},${centerY}`).join(" ");
52
57
  let comparePoints = null;
53
58
  let compareCoords = null;
54
59
  if (hasCompareData) {
55
60
  const ccoords = spokes.map(([shortTitle], index) => {
56
- var _a, _b;
57
- const value = (_a = compareData[shortTitle]) != null ? _a : 0;
58
- const topic = topics.find((t) => t.short_title === shortTitle);
59
- const max = ((_b = topic == null ? void 0 : topic.stances) == null ? void 0 : _b.length) || 10;
60
- const pct = Math.min(value / max * 10, 10);
61
- const angle = 2 * Math.PI * index / numSpokes;
62
- const adjusted = value === 0 ? 0 : invertedSpokes[shortTitle] ? 11 - pct : pct;
61
+ var _a2;
62
+ const value = (_a2 = compareData[shortTitle]) != null ? _a2 : 0;
63
+ const adjusted = calcAdjusted(value, shortTitle);
63
64
  const r = adjusted / 10 * radius;
64
- const x = centerX + r * Math.sin(angle);
65
- const y = centerY - r * Math.cos(angle);
66
- return [x, y];
65
+ const angle = 2 * Math.PI * index / numSpokes;
66
+ return [centerX + r * Math.sin(angle), centerY - r * Math.cos(angle)];
67
67
  });
68
68
  compareCoords = ccoords;
69
69
  comparePoints = ccoords.map((p) => p.join(",")).join(" ");
@@ -73,61 +73,33 @@ function RadarChartCore({
73
73
  useEffect(() => {
74
74
  hadCompareDataRef.current = hasCompareData;
75
75
  }, [hasCompareData]);
76
- const compareSpring = useSpring({
76
+ const compareSpringBase = {
77
77
  to: { points: comparePoints || centerPoints || `${centerX},${centerY}` },
78
78
  immediate: countChanged || compareJustAppeared,
79
- reset: countChanged || compareJustAppeared,
80
79
  config: { tension: 300, friction: 30 }
81
- });
82
- const labelMeta = spokes.map(([shortTitle], i) => {
83
- const angle = 2 * Math.PI * i / numSpokes;
84
- const baseFSize = labelFontSize;
85
- const lines = wrapLabel(shortTitle, 12);
86
- const longestLineLen = lines.reduce(
87
- (max, ln) => ln.length > max ? ln.length : max,
88
- 0
89
- );
90
- const fSize = adaptiveFontSize(longestLineLen, baseFSize);
91
- const estimatedWidth = longestLineLen * fSize * 0.6;
92
- const sinA = Math.sin(angle);
93
- const side = sinA < -0.1 ? "left" : sinA > 0.1 ? "right" : "center";
94
- return { estimatedWidth, side };
95
- });
96
- const leftLabelWidths = labelMeta.filter((m) => m.side === "left").map((m) => m.estimatedWidth);
97
- const rightLabelWidths = labelMeta.filter((m) => m.side === "right").map((m) => m.estimatedWidth);
98
- const minPadding = 40;
99
- const maxPadding = padding * 2;
100
- const rawLeftPadding = Math.min(
101
- Math.max(
102
- leftLabelWidths.length ? Math.max(...leftLabelWidths) + labelOffset : 0,
103
- minPadding
104
- ),
105
- maxPadding
106
- );
107
- const rawRightPadding = Math.min(
108
- Math.max(
109
- rightLabelWidths.length ? Math.max(...rightLabelWidths) + labelOffset : 0,
110
- minPadding
111
- ),
112
- maxPadding
113
- );
114
- const symmetricPadding = Math.max(rawLeftPadding, rawRightPadding);
115
- const leftPadding = symmetricPadding;
116
- const rightPadding = symmetricPadding;
117
- const verticalPadding = padding;
118
- let tightTop = -verticalPadding;
119
- let tightHeight = size + verticalPadding * 2;
80
+ };
81
+ if (countChanged || compareJustAppeared) compareSpringBase.from = compareSpringBase.to;
82
+ const compareSpring = useSpring(compareSpringBase);
83
+ const spokeExtension = spokes.reduce((acc, [shortTitle]) => {
84
+ const userAdj = calcAdjusted(data[shortTitle], shortTitle);
85
+ const cmpAdj = hasCompareData && compareData[shortTitle] ? calcAdjusted(compareData[shortTitle], shortTitle) : 0;
86
+ acc[shortTitle] = userAdj > 10 || cmpAdj > 10;
87
+ return acc;
88
+ }, {});
89
+ const sidePadding = 20;
90
+ let tightTop = -sidePadding;
91
+ let tightHeight = size + sidePadding * 2;
120
92
  if (tightFit && numSpokes > 0) {
121
93
  let yMin = Infinity, yMax = -Infinity;
122
94
  for (let i = 0; i < numSpokes; i++) {
123
95
  const angle = 2 * Math.PI * i / numSpokes;
124
- const y = centerY - radius * Math.cos(angle);
96
+ const effectiveR = spokeExtension[(_a = spokes[i]) == null ? void 0 : _a[0]] ? extRadius : radius;
97
+ const y = centerY - effectiveR * Math.cos(angle);
125
98
  if (y < yMin) yMin = y;
126
99
  if (y > yMax) yMax = y;
127
100
  }
128
- const labelMargin = labelOffset + labelFontSize * 1.4 + labelFontSize * 1.1;
129
- tightTop = yMin - labelMargin;
130
- tightHeight = yMax + labelMargin - tightTop;
101
+ tightTop = yMin - sidePadding;
102
+ tightHeight = yMax + sidePadding - tightTop;
131
103
  }
132
104
  const guidePolygons = [];
133
105
  for (let level = 1; level <= 5; level++) {
@@ -135,101 +107,76 @@ function RadarChartCore({
135
107
  const ring = [];
136
108
  for (let i = 0; i < numSpokes; i++) {
137
109
  const angle = 2 * Math.PI * i / numSpokes;
138
- const x = centerX + radius * scale * Math.sin(angle);
139
- const y = centerY - radius * scale * Math.cos(angle);
140
- ring.push(`${x},${y}`);
110
+ ring.push(`${centerX + radius * scale * Math.sin(angle)},${centerY - radius * scale * Math.cos(angle)}`);
141
111
  }
142
112
  guidePolygons.push(
143
113
  /* @__PURE__ */ React.createElement("polygon", { key: level, points: ring.join(" "), fill: "none", stroke: "#ccc" })
144
114
  );
145
115
  }
116
+ const getStanceText = (shortTitle, value) => {
117
+ if (writeIns[shortTitle]) return writeIns[shortTitle];
118
+ const topic = topics.find((t) => t.short_title === shortTitle);
119
+ const stances = (topic == null ? void 0 : topic.stances) || [];
120
+ const idx = Math.round(Number(value)) - 1;
121
+ if (idx >= 0 && idx < stances.length) {
122
+ return stances[idx].text || shortTitle;
123
+ }
124
+ return shortTitle;
125
+ };
146
126
  return /* @__PURE__ */ React.createElement(
147
127
  "svg",
148
128
  {
149
129
  className: "w-full h-auto max-h-full",
150
- viewBox: `-${leftPadding} ${tightTop} ${size + leftPadding + rightPadding} ${tightHeight}`,
151
- preserveAspectRatio: "xMidYMid meet"
130
+ viewBox: `-${sidePadding} ${tightTop} ${size + sidePadding * 2} ${tightHeight}`,
131
+ preserveAspectRatio: "xMidYMid meet",
132
+ onMouseLeave: () => setTooltip(null)
152
133
  },
153
134
  guidePolygons,
154
135
  spokes.map(([shortTitle], i) => {
155
136
  const angle = 2 * Math.PI * i / numSpokes;
156
- const x = centerX + radius * Math.sin(angle);
157
- const y = centerY - radius * Math.cos(angle);
137
+ const endX = centerX + radius * Math.sin(angle);
138
+ const endY = centerY - radius * Math.cos(angle);
158
139
  const isUnanswered = !!unansweredSpokes[shortTitle];
159
- return /* @__PURE__ */ React.createElement(
140
+ const needsExt = spokeExtension[shortTitle];
141
+ const extX = centerX + extRadius * Math.sin(angle);
142
+ const extY = centerY - extRadius * Math.cos(angle);
143
+ return /* @__PURE__ */ React.createElement("g", { key: `spoke-${shortTitle}` }, /* @__PURE__ */ React.createElement(
160
144
  "line",
161
145
  {
162
- key: `line-${shortTitle}`,
163
146
  x1: centerX,
164
147
  y1: centerY,
165
- x2: x,
166
- y2: y,
167
- stroke: isUnanswered ? "#9ca3af" : "black",
148
+ x2: endX,
149
+ y2: endY,
150
+ stroke: isUnanswered ? "#9ca3af" : "#aaa",
168
151
  strokeDasharray: isUnanswered ? "4 3" : void 0,
169
152
  opacity: isUnanswered ? 0.6 : 1
170
153
  }
171
- );
172
- }),
173
- spokes.map(([shortTitle], i) => {
174
- const angle = 2 * Math.PI * i / numSpokes;
175
- const anchor = angle > Math.PI ? "end" : angle < Math.PI && angle > 0 ? "start" : "middle";
176
- const cosA = Math.cos(angle);
177
- const isTop = cosA > 0.5;
178
- const isBottom = cosA < -0.5;
179
- const baseFSize = labelFontSize;
180
- const lines = wrapLabel(shortTitle, 12);
181
- const longestLine = lines.reduce(
182
- (max, ln) => ln.length > max ? ln.length : max,
183
- 0
184
- );
185
- const fSize = adaptiveFontSize(longestLine, baseFSize);
186
- const lineHeight = fSize * 1.1;
187
- const dynamicLabelOffset = lines.length > 1 ? labelOffset + 8 : labelOffset;
188
- const offset2 = radius + dynamicLabelOffset;
189
- const labelX = centerX + offset2 * Math.sin(angle);
190
- let labelY = centerY - offset2 * Math.cos(angle);
191
- if (isTop && lines.length > 1) {
192
- labelY -= (lines.length - 1) * lineHeight;
193
- }
194
- const isUnansweredLabel = !!unansweredSpokes[shortTitle];
195
- return /* @__PURE__ */ React.createElement(
196
- "text",
154
+ ), needsExt && /* @__PURE__ */ React.createElement(
155
+ "line",
197
156
  {
198
- key: `label-${shortTitle}`,
199
- x: labelX,
200
- y: labelY,
201
- textAnchor: anchor,
202
- dominantBaseline: isBottom ? "hanging" : "auto",
203
- onClick: () => onReplaceTopic(shortTitle),
204
- className: "text-xl font-medium mb-1 md:text-base md:font-normal",
205
- fill: isUnansweredLabel ? "#9ca3af" : void 0,
206
- style: { cursor: "pointer", userSelect: "none", fontSize: fSize }
207
- },
208
- isUnansweredLabel && /* @__PURE__ */ React.createElement("title", null, "No stance on file for this topic."),
209
- lines.map((ln, idx) => /* @__PURE__ */ React.createElement("tspan", { key: idx, x: labelX, dy: idx === 0 ? "0" : `${lineHeight}` }, ln))
210
- );
157
+ x1: endX,
158
+ y1: endY,
159
+ x2: extX,
160
+ y2: extY,
161
+ stroke: "#aaa",
162
+ strokeDasharray: "4 4",
163
+ opacity: 0.4
164
+ }
165
+ ));
211
166
  }),
212
167
  countChanged ? /* @__PURE__ */ React.createElement(
213
168
  "polygon",
214
169
  {
215
170
  key: "user-static",
216
171
  points: targetPoints,
217
- style: {
218
- fill: "rgba(124, 107, 158, 0.4)",
219
- stroke: "#7C6B9E",
220
- strokeWidth: 3
221
- }
172
+ style: { fill: "rgba(124, 107, 158, 0.4)", stroke: "#7C6B9E", strokeWidth: 3 }
222
173
  }
223
174
  ) : /* @__PURE__ */ React.createElement(
224
175
  animated.polygon,
225
176
  {
226
177
  key: "user-animated",
227
178
  points: spring.points,
228
- style: {
229
- fill: "rgba(124, 107, 158, 0.4)",
230
- stroke: "#7C6B9E",
231
- strokeWidth: 3
232
- }
179
+ style: { fill: "rgba(124, 107, 158, 0.4)", stroke: "#7C6B9E", strokeWidth: 3 }
233
180
  }
234
181
  ),
235
182
  pointsArr.map(([cx, cy], i) => {
@@ -247,7 +194,10 @@ function RadarChartCore({
247
194
  r: matches ? 8 : 7,
248
195
  fill: matches ? "#fed12e" : "#7C6B9E",
249
196
  stroke: "#FFFFFF",
250
- strokeWidth: 3
197
+ strokeWidth: 3,
198
+ style: { cursor: "default" },
199
+ onMouseEnter: () => setTooltip({ x: cx, y: cy, text: getStanceText(shortTitle, userVal) }),
200
+ onMouseLeave: () => setTooltip(null)
251
201
  }
252
202
  );
253
203
  }),
@@ -256,22 +206,14 @@ function RadarChartCore({
256
206
  {
257
207
  key: "compare-static",
258
208
  points: comparePoints,
259
- style: {
260
- fill: "rgba(90, 154, 110, 0.3)",
261
- stroke: "#5A9A6E",
262
- strokeWidth: 2
263
- }
209
+ style: { fill: "rgba(90, 154, 110, 0.3)", stroke: "#5A9A6E", strokeWidth: 2 }
264
210
  }
265
211
  ) : /* @__PURE__ */ React.createElement(
266
212
  animated.polygon,
267
213
  {
268
214
  key: "compare-animated",
269
215
  points: compareSpring.points,
270
- style: {
271
- fill: "rgba(90, 154, 110, 0.3)",
272
- stroke: "#5A9A6E",
273
- strokeWidth: 2
274
- }
216
+ style: { fill: "rgba(90, 154, 110, 0.3)", stroke: "#5A9A6E", strokeWidth: 2 }
275
217
  }
276
218
  ) : null,
277
219
  hasCompareData && compareCoords && compareCoords.map(([cx, cy], i) => {
@@ -279,7 +221,7 @@ function RadarChartCore({
279
221
  const userVal = spokes[i][1];
280
222
  const compareVal = compareData[shortTitle];
281
223
  if (!compareVal || Number(compareVal) === 0) return null;
282
- const matches = userVal !== 0 && compareVal !== 0 && compareVal != null && Number(userVal) === Number(compareVal);
224
+ const matches = userVal !== 0 && compareVal != null && Number(userVal) === Number(compareVal);
283
225
  return /* @__PURE__ */ React.createElement(
284
226
  "circle",
285
227
  {
@@ -289,14 +231,17 @@ function RadarChartCore({
289
231
  r: matches ? 8 : 7,
290
232
  fill: matches ? "#fed12e" : "#5A9A6E",
291
233
  stroke: "#FFFFFF",
292
- strokeWidth: 3
234
+ strokeWidth: 3,
235
+ style: { cursor: "default" },
236
+ onMouseEnter: () => setTooltip({ x: cx, y: cy, text: getStanceText(shortTitle, compareVal) }),
237
+ onMouseLeave: () => setTooltip(null)
293
238
  }
294
239
  );
295
240
  }),
296
241
  spokes.map(([shortTitle], i) => {
297
242
  const angle = 2 * Math.PI * i / numSpokes;
298
- const x = centerX + radius * Math.sin(angle);
299
- const y = centerY - radius * Math.cos(angle);
243
+ const endX = centerX + (spokeExtension[shortTitle] ? extRadius : radius) * Math.sin(angle);
244
+ const endY = centerY - (spokeExtension[shortTitle] ? extRadius : radius) * Math.cos(angle);
300
245
  const isUnansweredHitbox = !!unansweredSpokes[shortTitle];
301
246
  return /* @__PURE__ */ React.createElement(
302
247
  "line",
@@ -304,54 +249,56 @@ function RadarChartCore({
304
249
  key: `hitbox-${shortTitle}`,
305
250
  x1: centerX,
306
251
  y1: centerY,
307
- x2: x,
308
- y2: y,
252
+ x2: endX,
253
+ y2: endY,
309
254
  stroke: "transparent",
310
255
  strokeWidth: 14,
311
256
  onClick: () => isUnansweredHitbox ? onReplaceTopic(shortTitle) : onToggleInversion(shortTitle),
312
257
  style: { cursor: "pointer" }
313
258
  }
314
259
  );
315
- })
260
+ }),
261
+ tooltip && (() => {
262
+ const tipW = 190;
263
+ const tipH = 70;
264
+ const svgW = size + sidePadding * 2;
265
+ const tx = tooltip.x + sidePadding + 12 > svgW - tipW ? tooltip.x - tipW - 12 : tooltip.x + 12;
266
+ const ty = tooltip.y - 10;
267
+ return /* @__PURE__ */ React.createElement(
268
+ "foreignObject",
269
+ {
270
+ x: tx,
271
+ y: ty,
272
+ width: tipW,
273
+ height: tipH,
274
+ style: { overflow: "visible", pointerEvents: "none" }
275
+ },
276
+ /* @__PURE__ */ React.createElement(
277
+ "div",
278
+ {
279
+ xmlns: "http://www.w3.org/1999/xhtml",
280
+ style: {
281
+ background: "white",
282
+ border: "1px solid #d1d5db",
283
+ borderRadius: 6,
284
+ padding: "6px 10px",
285
+ fontSize: 11,
286
+ lineHeight: 1.4,
287
+ color: "#111",
288
+ boxShadow: "0 2px 8px rgba(0,0,0,0.12)",
289
+ width: tipW + "px",
290
+ wordWrap: "break-word"
291
+ }
292
+ },
293
+ tooltip.text
294
+ )
295
+ );
296
+ })()
316
297
  );
317
298
  }
318
- function adaptiveFontSize(longestLineLen, baseFSize) {
319
- return Math.max(baseFSize, 10);
320
- }
321
- function wrapLabel(label, maxChars = 12) {
322
- const str = String(label);
323
- let rawWords;
324
- if (str.includes("/")) {
325
- rawWords = str.split("/").reduce((acc, segment, idx, arr) => {
326
- const part = idx < arr.length - 1 ? segment + "/" : segment;
327
- if (part.trim()) acc.push(part.trim());
328
- return acc;
329
- }, []);
330
- } else {
331
- rawWords = str.split(/\s+/);
332
- }
333
- const lines = [];
334
- let line = "";
335
- for (const word of rawWords) {
336
- if (line === "") {
337
- line = word;
338
- } else if ((line + " " + word).length <= maxChars) {
339
- line += " " + word;
340
- } else {
341
- lines.push(line);
342
- line = word;
343
- }
344
- }
345
- if (line) lines.push(line);
346
- if (lines.length > 2) {
347
- const overflow = lines.splice(2).join(" ");
348
- lines[1] = lines[1] + " " + overflow;
349
- }
350
- return lines;
351
- }
352
299
 
353
300
  // src/Header.jsx
354
- import React2, { useState as useState2, useRef as useRef2, useEffect as useEffect3 } from "react";
301
+ import React2, { useState as useState3, useRef as useRef2, useEffect as useEffect3 } from "react";
355
302
 
356
303
  // src/tokens.js
357
304
  var colorScales = {
@@ -747,9 +694,9 @@ var breakpoints = {
747
694
  };
748
695
 
749
696
  // src/useMediaQuery.js
750
- import { useState, useEffect as useEffect2 } from "react";
697
+ import { useState as useState2, useEffect as useEffect2 } from "react";
751
698
  function useMediaQuery(query) {
752
- const [matches, setMatches] = useState(() => {
699
+ const [matches, setMatches] = useState2(() => {
753
700
  if (typeof window === "undefined") return false;
754
701
  return window.matchMedia(query).matches;
755
702
  });
@@ -768,6 +715,7 @@ function useMediaQuery(query) {
768
715
  function Header({
769
716
  logoSrc,
770
717
  logoAlt = "Empowered Vote",
718
+ logoHref = "https://login.empowered.vote/profile",
771
719
  navItems = [],
772
720
  ctaButton,
773
721
  secondaryAction,
@@ -776,9 +724,9 @@ function Header({
776
724
  profileMenu,
777
725
  style = {}
778
726
  }) {
779
- const [mobileMenuOpen, setMobileMenuOpen] = useState2(false);
780
- const [openDropdown, setOpenDropdown] = useState2(null);
781
- const [profileOpen, setProfileOpen] = useState2(false);
727
+ const [mobileMenuOpen, setMobileMenuOpen] = useState3(false);
728
+ const [openDropdown, setOpenDropdown] = useState3(null);
729
+ const [profileOpen, setProfileOpen] = useState3(false);
782
730
  const isMobile = useMediaQuery("(max-width: 768px)");
783
731
  const profileRef = useRef2(null);
784
732
  useEffect3(() => {
@@ -1051,8 +999,8 @@ function Header({
1051
999
  return /* @__PURE__ */ React2.createElement("header", { style: styles2.header }, /* @__PURE__ */ React2.createElement("div", { style: styles2.container }, /* @__PURE__ */ React2.createElement(
1052
1000
  "a",
1053
1001
  {
1054
- href: "/",
1055
- onClick: (e) => handleNavClick(e, "/"),
1002
+ href: logoHref,
1003
+ onClick: (e) => handleNavClick(e, logoHref),
1056
1004
  style: { display: "flex", alignItems: "center" }
1057
1005
  },
1058
1006
  logoSrc ? /* @__PURE__ */ React2.createElement("img", { src: logoSrc, alt: logoAlt, style: styles2.logo }) : /* @__PURE__ */ React2.createElement(
@@ -1692,7 +1640,7 @@ function FilterSidebar({
1692
1640
  }
1693
1641
 
1694
1642
  // src/PoliticianCard.jsx
1695
- import React6, { useState as useState3, useEffect as useEffect4 } from "react";
1643
+ import React6, { useState as useState4, useEffect as useEffect4 } from "react";
1696
1644
  function PoliticianCard({
1697
1645
  id,
1698
1646
  imageSrc,
@@ -1887,7 +1835,7 @@ function PoliticianCard({
1887
1835
  }
1888
1836
  )
1889
1837
  );
1890
- const [imgError, setImgError] = useState3(false);
1838
+ const [imgError, setImgError] = useState4(false);
1891
1839
  useEffect4(() => {
1892
1840
  setImgError(false);
1893
1841
  }, [imageSrc]);
@@ -1954,7 +1902,7 @@ function PoliticianCard({
1954
1902
  }
1955
1903
 
1956
1904
  // src/CompassCardHorizontal.jsx
1957
- import React11, { useState as useState5, useEffect as useEffect5 } from "react";
1905
+ import React11, { useState as useState6, useEffect as useEffect5 } from "react";
1958
1906
 
1959
1907
  // src/PlaceholderRadar.jsx
1960
1908
  import React7 from "react";
@@ -2037,7 +1985,7 @@ function PlaceholderRadar({ size = 250, name = "" }) {
2037
1985
  import React10 from "react";
2038
1986
 
2039
1987
  // src/IconOverlay.jsx
2040
- import React9, { useState as useState4 } from "react";
1988
+ import React9, { useState as useState5 } from "react";
2041
1989
  import {
2042
1990
  useFloating,
2043
1991
  useHover,
@@ -2126,7 +2074,7 @@ function BranchIcon({ size = 16, color = "currentColor", branch }) {
2126
2074
 
2127
2075
  // src/IconOverlay.jsx
2128
2076
  function IconWithTooltip({ IconComponent, color, tooltip, size = 14, extraProps = {}, onClick }) {
2129
- const [isOpen, setIsOpen] = useState4(false);
2077
+ const [isOpen, setIsOpen] = useState5(false);
2130
2078
  const { refs, floatingStyles, context } = useFloating({
2131
2079
  open: isOpen,
2132
2080
  onOpenChange: setIsOpen,
@@ -2316,10 +2264,10 @@ function CompassCardHorizontal({
2316
2264
  onClick
2317
2265
  }) {
2318
2266
  var _a;
2319
- const [hovered, setHovered] = useState5(false);
2320
- const [focused, setFocused] = useState5(false);
2321
- const [imgError, setImgError] = useState5(false);
2322
- const [emptyCtaHovered, setEmptyCtaHovered] = useState5(false);
2267
+ const [hovered, setHovered] = useState6(false);
2268
+ const [focused, setFocused] = useState6(false);
2269
+ const [imgError, setImgError] = useState6(false);
2270
+ const [emptyCtaHovered, setEmptyCtaHovered] = useState6(false);
2323
2271
  useEffect5(() => {
2324
2272
  setImgError(false);
2325
2273
  }, [politician == null ? void 0 : politician.photo_origin_url]);
@@ -2569,7 +2517,7 @@ function CompassCardHorizontal({
2569
2517
  }
2570
2518
 
2571
2519
  // src/CompassCardVertical.jsx
2572
- import React12, { useState as useState6, useEffect as useEffect6 } from "react";
2520
+ import React12, { useState as useState7, useEffect as useEffect6 } from "react";
2573
2521
  var RADAR_SIZE2 = 400;
2574
2522
  function CompassCardVertical({
2575
2523
  politician,
@@ -2583,10 +2531,10 @@ function CompassCardVertical({
2583
2531
  onClick
2584
2532
  }) {
2585
2533
  var _a;
2586
- const [hovered, setHovered] = useState6(false);
2587
- const [focused, setFocused] = useState6(false);
2588
- const [imgError, setImgError] = useState6(false);
2589
- const [emptyCtaHovered, setEmptyCtaHovered] = useState6(false);
2534
+ const [hovered, setHovered] = useState7(false);
2535
+ const [focused, setFocused] = useState7(false);
2536
+ const [imgError, setImgError] = useState7(false);
2537
+ const [emptyCtaHovered, setEmptyCtaHovered] = useState7(false);
2590
2538
  useEffect6(() => {
2591
2539
  setImgError(false);
2592
2540
  }, [politician == null ? void 0 : politician.photo_origin_url]);
@@ -2921,7 +2869,7 @@ function CompassCardVertical({
2921
2869
  }
2922
2870
 
2923
2871
  // src/CompassKey.jsx
2924
- import React13, { useEffect as useEffect7, useState as useState7 } from "react";
2872
+ import React13, { useEffect as useEffect7, useState as useState8 } from "react";
2925
2873
 
2926
2874
  // src/evContext.js
2927
2875
  var DEFAULT_BROKER_URL = "https://ev-context.empowered.vote/";
@@ -3166,8 +3114,8 @@ var evContext = {
3166
3114
 
3167
3115
  // src/CompassKey.jsx
3168
3116
  function CompassKey({ compact = false } = {}) {
3169
- const [dismissed, setDismissed] = useState7(false);
3170
- const [loaded, setLoaded] = useState7(false);
3117
+ const [dismissed, setDismissed] = useState8(false);
3118
+ const [loaded, setLoaded] = useState8(false);
3171
3119
  useEffect7(() => {
3172
3120
  let cancelled = false;
3173
3121
  evContext.get().then((shared) => {
@@ -3294,7 +3242,7 @@ function CompassKey({ compact = false } = {}) {
3294
3242
  }
3295
3243
 
3296
3244
  // src/CategorySection.jsx
3297
- import React14, { useState as useState8 } from "react";
3245
+ import React14, { useState as useState9 } from "react";
3298
3246
  function CategorySection({
3299
3247
  title,
3300
3248
  infoTooltip,
@@ -3304,7 +3252,7 @@ function CategorySection({
3304
3252
  tier
3305
3253
  }) {
3306
3254
  var _a, _b;
3307
- const [showTooltip, setShowTooltip] = useState8(false);
3255
+ const [showTooltip, setShowTooltip] = useState9(false);
3308
3256
  const tierStyle = tier ? tierColors[tier] : null;
3309
3257
  const styles2 = {
3310
3258
  section: {
@@ -3465,7 +3413,7 @@ function SubGroupSection({ title, websiteUrl, children, gridTemplateColumns, gap
3465
3413
  }
3466
3414
 
3467
3415
  // src/GovernmentBodySection.jsx
3468
- import React16, { useState as useState9 } from "react";
3416
+ import React16, { useState as useState10 } from "react";
3469
3417
  function GovernmentBodySection({
3470
3418
  title,
3471
3419
  websiteUrl,
@@ -3474,7 +3422,7 @@ function GovernmentBodySection({
3474
3422
  children
3475
3423
  }) {
3476
3424
  var _a;
3477
- const [expanded, setExpanded] = useState9(defaultExpanded);
3425
+ const [expanded, setExpanded] = useState10(defaultExpanded);
3478
3426
  const tierStyle = tier ? tierColors[tier] : null;
3479
3427
  const accentColor = (_a = tierStyle == null ? void 0 : tierStyle.accent) != null ? _a : colors.borderMedium;
3480
3428
  const styles2 = {
@@ -3803,7 +3751,7 @@ function IssueTags({
3803
3751
  }
3804
3752
 
3805
3753
  // src/CommitteeTable.jsx
3806
- import React19, { useState as useState10 } from "react";
3754
+ import React19, { useState as useState11 } from "react";
3807
3755
  var ROLE_ORDER = {
3808
3756
  "chair": 0,
3809
3757
  "co-chair": 1,
@@ -3830,7 +3778,7 @@ function CommitteeTable({
3830
3778
  maxVisible = 6,
3831
3779
  style = {}
3832
3780
  }) {
3833
- const [showAll, setShowAll] = useState10(false);
3781
+ const [showAll, setShowAll] = useState11(false);
3834
3782
  if (committees.length === 0) {
3835
3783
  return null;
3836
3784
  }
@@ -3934,7 +3882,7 @@ function CommitteeTable({
3934
3882
  }
3935
3883
 
3936
3884
  // src/PoliticianProfile.jsx
3937
- import React22, { useState as useState11, useEffect as useEffect8 } from "react";
3885
+ import React22, { useState as useState12, useEffect as useEffect8 } from "react";
3938
3886
 
3939
3887
  // src/LegislativeInlineSummary.jsx
3940
3888
  import React20 from "react";
@@ -4377,7 +4325,7 @@ function PoliticianProfile({
4377
4325
  return pol.photo_origin_url;
4378
4326
  })();
4379
4327
  const initials = [pol.first_name, pol.last_name].filter(Boolean).map((n) => n[0]).join("");
4380
- const [imgError, setImgError] = useState11(false);
4328
+ const [imgError, setImgError] = useState12(false);
4381
4329
  useEffect8(() => {
4382
4330
  setImgError(false);
4383
4331
  }, [profileImageUrl]);
@@ -4787,7 +4735,7 @@ function PoliticianProfile({
4787
4735
  }
4788
4736
 
4789
4737
  // src/LegislativeRecord.jsx
4790
- import React23, { useState as useState12 } from "react";
4738
+ import React23, { useState as useState13 } from "react";
4791
4739
  var DEFAULT_LIMIT = 25;
4792
4740
  function normalizeText(str) {
4793
4741
  if (!str) return "";
@@ -4966,7 +4914,7 @@ function Spinner() {
4966
4914
  } }), /* @__PURE__ */ React23.createElement("style", null, `@keyframes ev-spin { to { transform: rotate(360deg); } }`));
4967
4915
  }
4968
4916
  function CommitteesSection({ committees, leadership }) {
4969
- const [showAll, setShowAll] = useState12(false);
4917
+ const [showAll, setShowAll] = useState13(false);
4970
4918
  const visible = showAll ? committees : committees.slice(0, DEFAULT_LIMIT);
4971
4919
  return /* @__PURE__ */ React23.createElement("div", { style: { marginBottom: spacing[8] } }, /* @__PURE__ */ React23.createElement(SectionHeader, { title: "Committees & Leadership" }), leadership && leadership.length > 0 && /* @__PURE__ */ React23.createElement("div", { style: { display: "flex", flexWrap: "wrap", gap: spacing[2], marginBottom: spacing[4] } }, leadership.map((role, i) => /* @__PURE__ */ React23.createElement("div", { key: i, style: {
4972
4920
  display: "inline-flex",
@@ -5006,8 +4954,8 @@ function CommitteesSection({ committees, leadership }) {
5006
4954
  }
5007
4955
  function LegislationSection({ bills, isMobile }) {
5008
4956
  const years = getYears(bills, "introduced_at");
5009
- const [yearFilter, setYearFilter] = useState12("All");
5010
- const [showAll, setShowAll] = useState12(false);
4957
+ const [yearFilter, setYearFilter] = useState13("All");
4958
+ const [showAll, setShowAll] = useState13(false);
5011
4959
  const handleYearChange = (y) => {
5012
4960
  setYearFilter(y);
5013
4961
  setShowAll(false);
@@ -5068,8 +5016,8 @@ function LegislationSection({ bills, isMobile }) {
5068
5016
  }
5069
5017
  function VotingSection({ votes, isMobile }) {
5070
5018
  const years = getYears(votes, "vote_date");
5071
- const [yearFilter, setYearFilter] = useState12("All");
5072
- const [showAll, setShowAll] = useState12(false);
5019
+ const [yearFilter, setYearFilter] = useState13("All");
5020
+ const [showAll, setShowAll] = useState13(false);
5073
5021
  const handleYearChange = (y) => {
5074
5022
  setYearFilter(y);
5075
5023
  setShowAll(false);
@@ -5281,7 +5229,7 @@ function JudicialRecordDetail({ politician = {}, judicialRecord, onBack }) {
5281
5229
  }
5282
5230
 
5283
5231
  // src/AuthForm.jsx
5284
- import React25, { useState as useState13 } from "react";
5232
+ import React25, { useState as useState14 } from "react";
5285
5233
  function AuthForm({
5286
5234
  logoSrc = "/EVLogo.svg",
5287
5235
  appName = "Empowered Vote",
@@ -5292,11 +5240,11 @@ function AuthForm({
5292
5240
  error = null,
5293
5241
  submitting = false
5294
5242
  }) {
5295
- const [username, setUsername] = useState13("");
5296
- const [password, setPassword] = useState13("");
5297
- const [confirmPassword, setConfirmPassword] = useState13("");
5298
- const [showPasswords, setShowPasswords] = useState13(false);
5299
- const [passwordMismatch, setPasswordMismatch] = useState13(false);
5243
+ const [username, setUsername] = useState14("");
5244
+ const [password, setPassword] = useState14("");
5245
+ const [confirmPassword, setConfirmPassword] = useState14("");
5246
+ const [showPasswords, setShowPasswords] = useState14(false);
5247
+ const [passwordMismatch, setPasswordMismatch] = useState14(false);
5300
5248
  const isLogin = mode === "login";
5301
5249
  const handleSubmit = (e) => {
5302
5250
  e.preventDefault();
@@ -5744,7 +5692,7 @@ function TopicTierBadge({
5744
5692
  }
5745
5693
 
5746
5694
  // src/CompassCoverageCallout.jsx
5747
- import React27, { useState as useState14 } from "react";
5695
+ import React27, { useState as useState15 } from "react";
5748
5696
  var TIER_LABELS = {
5749
5697
  federal: "federal",
5750
5698
  state: "state",
@@ -5757,7 +5705,7 @@ function CompassCoverageCallout({
5757
5705
  compassUrl,
5758
5706
  onDismiss
5759
5707
  }) {
5760
- const [dismissed, setDismissed] = useState14(false);
5708
+ const [dismissed, setDismissed] = useState15(false);
5761
5709
  if (dismissed) return null;
5762
5710
  const tierLabel = TIER_LABELS[tier] || tier;
5763
5711
  const tierStyle = tierColors[tier] || tierColors.federal;
@@ -5897,7 +5845,7 @@ function computeTierCoverage(topics) {
5897
5845
  }
5898
5846
 
5899
5847
  // src/useEvContextPromotion.js
5900
- import { useEffect as useEffect9, useState as useState15, useCallback, useRef as useRef3 } from "react";
5848
+ import { useEffect as useEffect9, useState as useState16, useCallback, useRef as useRef3 } from "react";
5901
5849
  function useEvContextPromotion({
5902
5850
  domain,
5903
5851
  isLoggedIn,
@@ -5906,10 +5854,10 @@ function useEvContextPromotion({
5906
5854
  apiWriter,
5907
5855
  enabled = true
5908
5856
  }) {
5909
- const [shouldPrompt, setShouldPrompt] = useState15(false);
5910
- const [payload, setPayload] = useState15(null);
5911
- const [status, setStatus] = useState15("idle");
5912
- const [error, setError] = useState15(null);
5857
+ const [shouldPrompt, setShouldPrompt] = useState16(false);
5858
+ const [payload, setPayload] = useState16(null);
5859
+ const [status, setStatus] = useState16("idle");
5860
+ const [error, setError] = useState16(null);
5913
5861
  const apiDataRef = useRef3(apiData);
5914
5862
  apiDataRef.current = apiData;
5915
5863
  const userIdRef = useRef3(userId);
@@ -6056,7 +6004,7 @@ function isGuestPopulated(domain, fullEvContext) {
6056
6004
  }
6057
6005
 
6058
6006
  // src/StanceAccordion.jsx
6059
- import React30, { useState as useState16, useRef as useRef4, useCallback as useCallback2, useEffect as useEffect10 } from "react";
6007
+ import React30, { useState as useState17, useRef as useRef4, useCallback as useCallback2, useEffect as useEffect10 } from "react";
6060
6008
 
6061
6009
  // src/Favicon.jsx
6062
6010
  import React29 from "react";
@@ -6117,9 +6065,9 @@ function StanceAccordion({
6117
6065
  apiUrl = "https://api.empowered.vote"
6118
6066
  }) {
6119
6067
  var _a;
6120
- const [expandedTopicId, setExpandedTopicId] = useState16(null);
6121
- const [loadingId, setLoadingId] = useState16(null);
6122
- const [showAll, setShowAll] = useState16(false);
6068
+ const [expandedTopicId, setExpandedTopicId] = useState17(null);
6069
+ const [loadingId, setLoadingId] = useState17(null);
6070
+ const [showAll, setShowAll] = useState17(false);
6123
6071
  const contextCache = useRef4(/* @__PURE__ */ new Map());
6124
6072
  const quotesCache = useRef4(null);
6125
6073
  const polAnswerMap = {};