@empoweredvote/ev-ui 0.7.0 → 0.8.0

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,
@@ -16,11 +15,16 @@ function RadarChartCore({
16
15
  labelFontSize = 18,
17
16
  padding = 80,
18
17
  labelOffset = 20,
19
- tightFit = false
18
+ tightFit = false,
19
+ activeSpoke = null,
20
+ writeIns = {}
20
21
  }) {
22
+ var _a;
21
23
  const radius = size / 2 - 40;
24
+ const extRadius = radius * 1.15;
22
25
  const centerX = size / 2;
23
26
  const centerY = size / 2;
27
+ const [tooltip, setTooltip] = useState(null);
24
28
  const spokes = Object.entries(data);
25
29
  const numSpokes = Math.max(spokes.length, 1);
26
30
  const prevCountRef = useRef(numSpokes);
@@ -28,42 +32,40 @@ function RadarChartCore({
28
32
  useEffect(() => {
29
33
  prevCountRef.current = numSpokes;
30
34
  }, [numSpokes]);
31
- const pointsArr = spokes.map(([shortTitle, value], index) => {
32
- var _a;
35
+ function calcAdjusted(value, shortTitle) {
36
+ var _a2;
37
+ if (!value || Number(value) === 0) return 0;
33
38
  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;
39
+ const max = ((_a2 = topic == null ? void 0 : topic.stances) == null ? void 0 : _a2.length) || 10;
40
+ const pct = Number(value) / max * 10;
41
+ return invertedSpokes[shortTitle] ? (max + 1) * 10 / max - pct : pct;
42
+ }
43
+ const pointsArr = spokes.map(([shortTitle, value], index) => {
44
+ const adjusted = calcAdjusted(value, shortTitle);
38
45
  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];
46
+ const angle = 2 * Math.PI * index / numSpokes;
47
+ return [centerX + r * Math.sin(angle), centerY - r * Math.cos(angle)];
42
48
  });
43
49
  const targetPoints = pointsArr.map((p) => p.join(",")).join(" ");
44
- const spring = useSpring({
50
+ const springBase = {
45
51
  to: { points: targetPoints },
46
52
  immediate: countChanged,
47
- reset: countChanged,
48
53
  config: { tension: 300, friction: 30 }
49
- });
54
+ };
55
+ if (countChanged) springBase.from = { points: targetPoints };
56
+ const spring = useSpring(springBase);
50
57
  const hasCompareData = Object.keys(compareData).length > 0;
51
58
  const centerPoints = spokes.map(() => `${centerX},${centerY}`).join(" ");
52
59
  let comparePoints = null;
53
60
  let compareCoords = null;
54
61
  if (hasCompareData) {
55
62
  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;
63
+ var _a2;
64
+ const value = (_a2 = compareData[shortTitle]) != null ? _a2 : 0;
65
+ const adjusted = calcAdjusted(value, shortTitle);
63
66
  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];
67
+ const angle = 2 * Math.PI * index / numSpokes;
68
+ return [centerX + r * Math.sin(angle), centerY - r * Math.cos(angle)];
67
69
  });
68
70
  compareCoords = ccoords;
69
71
  comparePoints = ccoords.map((p) => p.join(",")).join(" ");
@@ -73,21 +75,18 @@ function RadarChartCore({
73
75
  useEffect(() => {
74
76
  hadCompareDataRef.current = hasCompareData;
75
77
  }, [hasCompareData]);
76
- const compareSpring = useSpring({
78
+ const compareSpringBase = {
77
79
  to: { points: comparePoints || centerPoints || `${centerX},${centerY}` },
78
80
  immediate: countChanged || compareJustAppeared,
79
- reset: countChanged || compareJustAppeared,
80
81
  config: { tension: 300, friction: 30 }
81
- });
82
+ };
83
+ if (countChanged || compareJustAppeared) compareSpringBase.from = compareSpringBase.to;
84
+ const compareSpring = useSpring(compareSpringBase);
82
85
  const labelMeta = spokes.map(([shortTitle], i) => {
83
86
  const angle = 2 * Math.PI * i / numSpokes;
84
- const baseFSize = labelFontSize;
85
87
  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);
88
+ const longestLineLen = lines.reduce((m, ln) => ln.length > m ? ln.length : m, 0);
89
+ const fSize = adaptiveFontSize(longestLineLen, labelFontSize);
91
90
  const estimatedWidth = longestLineLen * fSize * 0.6;
92
91
  const sinA = Math.sin(angle);
93
92
  const side = sinA < -0.1 ? "left" : sinA > 0.1 ? "right" : "center";
@@ -98,30 +97,31 @@ function RadarChartCore({
98
97
  const minPadding = 40;
99
98
  const maxPadding = padding * 2;
100
99
  const rawLeftPadding = Math.min(
101
- Math.max(
102
- leftLabelWidths.length ? Math.max(...leftLabelWidths) + labelOffset : 0,
103
- minPadding
104
- ),
100
+ Math.max(leftLabelWidths.length ? Math.max(...leftLabelWidths) + labelOffset : 0, minPadding),
105
101
  maxPadding
106
102
  );
107
103
  const rawRightPadding = Math.min(
108
- Math.max(
109
- rightLabelWidths.length ? Math.max(...rightLabelWidths) + labelOffset : 0,
110
- minPadding
111
- ),
104
+ Math.max(rightLabelWidths.length ? Math.max(...rightLabelWidths) + labelOffset : 0, minPadding),
112
105
  maxPadding
113
106
  );
114
107
  const symmetricPadding = Math.max(rawLeftPadding, rawRightPadding);
115
108
  const leftPadding = symmetricPadding;
116
109
  const rightPadding = symmetricPadding;
117
110
  const verticalPadding = padding;
111
+ const spokeExtension = spokes.reduce((acc, [shortTitle]) => {
112
+ const userAdj = calcAdjusted(data[shortTitle], shortTitle);
113
+ const cmpAdj = hasCompareData && compareData[shortTitle] ? calcAdjusted(compareData[shortTitle], shortTitle) : 0;
114
+ acc[shortTitle] = userAdj > 10 || cmpAdj > 10;
115
+ return acc;
116
+ }, {});
118
117
  let tightTop = -verticalPadding;
119
118
  let tightHeight = size + verticalPadding * 2;
120
119
  if (tightFit && numSpokes > 0) {
121
120
  let yMin = Infinity, yMax = -Infinity;
122
121
  for (let i = 0; i < numSpokes; i++) {
123
122
  const angle = 2 * Math.PI * i / numSpokes;
124
- const y = centerY - radius * Math.cos(angle);
123
+ const effectiveR = spokeExtension[(_a = spokes[i]) == null ? void 0 : _a[0]] ? extRadius : radius;
124
+ const y = centerY - effectiveR * Math.cos(angle);
125
125
  if (y < yMin) yMin = y;
126
126
  if (y > yMax) yMax = y;
127
127
  }
@@ -135,75 +135,94 @@ function RadarChartCore({
135
135
  const ring = [];
136
136
  for (let i = 0; i < numSpokes; i++) {
137
137
  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}`);
138
+ ring.push(`${centerX + radius * scale * Math.sin(angle)},${centerY - radius * scale * Math.cos(angle)}`);
141
139
  }
142
140
  guidePolygons.push(
143
141
  /* @__PURE__ */ React.createElement("polygon", { key: level, points: ring.join(" "), fill: "none", stroke: "#ccc" })
144
142
  );
145
143
  }
144
+ const getStanceText = (shortTitle, value) => {
145
+ if (writeIns[shortTitle]) return writeIns[shortTitle];
146
+ const topic = topics.find((t) => t.short_title === shortTitle);
147
+ const stances = (topic == null ? void 0 : topic.stances) || [];
148
+ const idx = Math.round(Number(value)) - 1;
149
+ if (idx >= 0 && idx < stances.length) {
150
+ return stances[idx].text || shortTitle;
151
+ }
152
+ return shortTitle;
153
+ };
146
154
  return /* @__PURE__ */ React.createElement(
147
155
  "svg",
148
156
  {
149
157
  className: "w-full h-auto max-h-full",
150
158
  viewBox: `-${leftPadding} ${tightTop} ${size + leftPadding + rightPadding} ${tightHeight}`,
151
- preserveAspectRatio: "xMidYMid meet"
159
+ preserveAspectRatio: "xMidYMid meet",
160
+ onMouseLeave: () => setTooltip(null)
152
161
  },
153
162
  guidePolygons,
154
163
  spokes.map(([shortTitle], i) => {
155
164
  const angle = 2 * Math.PI * i / numSpokes;
156
- const x = centerX + radius * Math.sin(angle);
157
- const y = centerY - radius * Math.cos(angle);
165
+ const endX = centerX + radius * Math.sin(angle);
166
+ const endY = centerY - radius * Math.cos(angle);
158
167
  const isUnanswered = !!unansweredSpokes[shortTitle];
159
- return /* @__PURE__ */ React.createElement(
168
+ const needsExt = spokeExtension[shortTitle];
169
+ const extX = centerX + extRadius * Math.sin(angle);
170
+ const extY = centerY - extRadius * Math.cos(angle);
171
+ return /* @__PURE__ */ React.createElement("g", { key: `spoke-${shortTitle}` }, /* @__PURE__ */ React.createElement(
160
172
  "line",
161
173
  {
162
- key: `line-${shortTitle}`,
163
174
  x1: centerX,
164
175
  y1: centerY,
165
- x2: x,
166
- y2: y,
176
+ x2: endX,
177
+ y2: endY,
167
178
  stroke: isUnanswered ? "#9ca3af" : "black",
168
179
  strokeDasharray: isUnanswered ? "4 3" : void 0,
169
180
  opacity: isUnanswered ? 0.6 : 1
170
181
  }
171
- );
182
+ ), needsExt && /* @__PURE__ */ React.createElement(
183
+ "line",
184
+ {
185
+ x1: endX,
186
+ y1: endY,
187
+ x2: extX,
188
+ y2: extY,
189
+ stroke: "black",
190
+ strokeDasharray: "4 4",
191
+ opacity: 0.4
192
+ }
193
+ ));
172
194
  }),
173
195
  spokes.map(([shortTitle], i) => {
174
196
  const angle = 2 * Math.PI * i / numSpokes;
175
- const anchor = angle > Math.PI ? "end" : angle < Math.PI && angle > 0 ? "start" : "middle";
176
197
  const cosA = Math.cos(angle);
177
- const isTop = cosA > 0.5;
178
198
  const isBottom = cosA < -0.5;
179
- const baseFSize = labelFontSize;
199
+ const isTop = cosA > 0.5;
180
200
  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);
201
+ const longestLine = lines.reduce((m, ln) => ln.length > m ? ln.length : m, 0);
202
+ const fSize = adaptiveFontSize(longestLine, labelFontSize);
186
203
  const lineHeight = fSize * 1.1;
187
204
  const dynamicLabelOffset = lines.length > 1 ? labelOffset + 8 : labelOffset;
188
205
  const offset2 = radius + dynamicLabelOffset;
189
206
  const labelX = centerX + offset2 * Math.sin(angle);
190
207
  let labelY = centerY - offset2 * Math.cos(angle);
191
- if (isTop && lines.length > 1) {
192
- labelY -= (lines.length - 1) * lineHeight;
193
- }
208
+ if (isTop && lines.length > 1) labelY -= (lines.length - 1) * lineHeight;
209
+ const isActive = shortTitle === activeSpoke;
194
210
  const isUnansweredLabel = !!unansweredSpokes[shortTitle];
211
+ const activeFSize = fSize * 1.5;
212
+ const displayFSize = isActive ? activeFSize : fSize;
195
213
  return /* @__PURE__ */ React.createElement(
196
214
  "text",
197
215
  {
198
216
  key: `label-${shortTitle}`,
199
217
  x: labelX,
200
218
  y: labelY,
201
- textAnchor: anchor,
219
+ textAnchor: "middle",
202
220
  dominantBaseline: isBottom ? "hanging" : "auto",
203
221
  onClick: () => onReplaceTopic(shortTitle),
204
222
  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 }
223
+ fill: isActive ? "#E85D26" : isUnansweredLabel ? "#9ca3af" : void 0,
224
+ fontWeight: isActive ? "bold" : void 0,
225
+ style: { cursor: "pointer", userSelect: "none", fontSize: displayFSize }
207
226
  },
208
227
  isUnansweredLabel && /* @__PURE__ */ React.createElement("title", null, "No stance on file for this topic."),
209
228
  lines.map((ln, idx) => /* @__PURE__ */ React.createElement("tspan", { key: idx, x: labelX, dy: idx === 0 ? "0" : `${lineHeight}` }, ln))
@@ -214,22 +233,14 @@ function RadarChartCore({
214
233
  {
215
234
  key: "user-static",
216
235
  points: targetPoints,
217
- style: {
218
- fill: "rgba(124, 107, 158, 0.4)",
219
- stroke: "#7C6B9E",
220
- strokeWidth: 3
221
- }
236
+ style: { fill: "rgba(124, 107, 158, 0.4)", stroke: "#7C6B9E", strokeWidth: 3 }
222
237
  }
223
238
  ) : /* @__PURE__ */ React.createElement(
224
239
  animated.polygon,
225
240
  {
226
241
  key: "user-animated",
227
242
  points: spring.points,
228
- style: {
229
- fill: "rgba(124, 107, 158, 0.4)",
230
- stroke: "#7C6B9E",
231
- strokeWidth: 3
232
- }
243
+ style: { fill: "rgba(124, 107, 158, 0.4)", stroke: "#7C6B9E", strokeWidth: 3 }
233
244
  }
234
245
  ),
235
246
  pointsArr.map(([cx, cy], i) => {
@@ -247,7 +258,10 @@ function RadarChartCore({
247
258
  r: matches ? 8 : 7,
248
259
  fill: matches ? "#fed12e" : "#7C6B9E",
249
260
  stroke: "#FFFFFF",
250
- strokeWidth: 3
261
+ strokeWidth: 3,
262
+ style: { cursor: "default" },
263
+ onMouseEnter: () => setTooltip({ x: cx, y: cy, text: getStanceText(shortTitle, userVal) }),
264
+ onMouseLeave: () => setTooltip(null)
251
265
  }
252
266
  );
253
267
  }),
@@ -256,22 +270,14 @@ function RadarChartCore({
256
270
  {
257
271
  key: "compare-static",
258
272
  points: comparePoints,
259
- style: {
260
- fill: "rgba(90, 154, 110, 0.3)",
261
- stroke: "#5A9A6E",
262
- strokeWidth: 2
263
- }
273
+ style: { fill: "rgba(90, 154, 110, 0.3)", stroke: "#5A9A6E", strokeWidth: 2 }
264
274
  }
265
275
  ) : /* @__PURE__ */ React.createElement(
266
276
  animated.polygon,
267
277
  {
268
278
  key: "compare-animated",
269
279
  points: compareSpring.points,
270
- style: {
271
- fill: "rgba(90, 154, 110, 0.3)",
272
- stroke: "#5A9A6E",
273
- strokeWidth: 2
274
- }
280
+ style: { fill: "rgba(90, 154, 110, 0.3)", stroke: "#5A9A6E", strokeWidth: 2 }
275
281
  }
276
282
  ) : null,
277
283
  hasCompareData && compareCoords && compareCoords.map(([cx, cy], i) => {
@@ -279,7 +285,7 @@ function RadarChartCore({
279
285
  const userVal = spokes[i][1];
280
286
  const compareVal = compareData[shortTitle];
281
287
  if (!compareVal || Number(compareVal) === 0) return null;
282
- const matches = userVal !== 0 && compareVal !== 0 && compareVal != null && Number(userVal) === Number(compareVal);
288
+ const matches = userVal !== 0 && compareVal != null && Number(userVal) === Number(compareVal);
283
289
  return /* @__PURE__ */ React.createElement(
284
290
  "circle",
285
291
  {
@@ -289,14 +295,17 @@ function RadarChartCore({
289
295
  r: matches ? 8 : 7,
290
296
  fill: matches ? "#fed12e" : "#5A9A6E",
291
297
  stroke: "#FFFFFF",
292
- strokeWidth: 3
298
+ strokeWidth: 3,
299
+ style: { cursor: "default" },
300
+ onMouseEnter: () => setTooltip({ x: cx, y: cy, text: getStanceText(shortTitle, compareVal) }),
301
+ onMouseLeave: () => setTooltip(null)
293
302
  }
294
303
  );
295
304
  }),
296
305
  spokes.map(([shortTitle], i) => {
297
306
  const angle = 2 * Math.PI * i / numSpokes;
298
- const x = centerX + radius * Math.sin(angle);
299
- const y = centerY - radius * Math.cos(angle);
307
+ const endX = centerX + (spokeExtension[shortTitle] ? extRadius : radius) * Math.sin(angle);
308
+ const endY = centerY - (spokeExtension[shortTitle] ? extRadius : radius) * Math.cos(angle);
300
309
  const isUnansweredHitbox = !!unansweredSpokes[shortTitle];
301
310
  return /* @__PURE__ */ React.createElement(
302
311
  "line",
@@ -304,15 +313,51 @@ function RadarChartCore({
304
313
  key: `hitbox-${shortTitle}`,
305
314
  x1: centerX,
306
315
  y1: centerY,
307
- x2: x,
308
- y2: y,
316
+ x2: endX,
317
+ y2: endY,
309
318
  stroke: "transparent",
310
319
  strokeWidth: 14,
311
320
  onClick: () => isUnansweredHitbox ? onReplaceTopic(shortTitle) : onToggleInversion(shortTitle),
312
321
  style: { cursor: "pointer" }
313
322
  }
314
323
  );
315
- })
324
+ }),
325
+ tooltip && (() => {
326
+ const tipW = 190;
327
+ const tipH = 70;
328
+ const svgW = size + leftPadding + rightPadding;
329
+ const tx = tooltip.x + leftPadding + 12 > svgW - tipW ? tooltip.x - tipW - 12 : tooltip.x + 12;
330
+ const ty = tooltip.y - 10;
331
+ return /* @__PURE__ */ React.createElement(
332
+ "foreignObject",
333
+ {
334
+ x: tx,
335
+ y: ty,
336
+ width: tipW,
337
+ height: tipH,
338
+ style: { overflow: "visible", pointerEvents: "none" }
339
+ },
340
+ /* @__PURE__ */ React.createElement(
341
+ "div",
342
+ {
343
+ xmlns: "http://www.w3.org/1999/xhtml",
344
+ style: {
345
+ background: "white",
346
+ border: "1px solid #d1d5db",
347
+ borderRadius: 6,
348
+ padding: "6px 10px",
349
+ fontSize: 11,
350
+ lineHeight: 1.4,
351
+ color: "#111",
352
+ boxShadow: "0 2px 8px rgba(0,0,0,0.12)",
353
+ width: tipW + "px",
354
+ wordWrap: "break-word"
355
+ }
356
+ },
357
+ tooltip.text
358
+ )
359
+ );
360
+ })()
316
361
  );
317
362
  }
318
363
  function adaptiveFontSize(longestLineLen, baseFSize) {
@@ -351,7 +396,7 @@ function wrapLabel(label, maxChars = 12) {
351
396
  }
352
397
 
353
398
  // src/Header.jsx
354
- import React2, { useState as useState2, useRef as useRef2, useEffect as useEffect3 } from "react";
399
+ import React2, { useState as useState3, useRef as useRef2, useEffect as useEffect3 } from "react";
355
400
 
356
401
  // src/tokens.js
357
402
  var colorScales = {
@@ -747,9 +792,9 @@ var breakpoints = {
747
792
  };
748
793
 
749
794
  // src/useMediaQuery.js
750
- import { useState, useEffect as useEffect2 } from "react";
795
+ import { useState as useState2, useEffect as useEffect2 } from "react";
751
796
  function useMediaQuery(query) {
752
- const [matches, setMatches] = useState(() => {
797
+ const [matches, setMatches] = useState2(() => {
753
798
  if (typeof window === "undefined") return false;
754
799
  return window.matchMedia(query).matches;
755
800
  });
@@ -768,6 +813,7 @@ function useMediaQuery(query) {
768
813
  function Header({
769
814
  logoSrc,
770
815
  logoAlt = "Empowered Vote",
816
+ logoHref = "https://login.empowered.vote/profile",
771
817
  navItems = [],
772
818
  ctaButton,
773
819
  secondaryAction,
@@ -776,9 +822,9 @@ function Header({
776
822
  profileMenu,
777
823
  style = {}
778
824
  }) {
779
- const [mobileMenuOpen, setMobileMenuOpen] = useState2(false);
780
- const [openDropdown, setOpenDropdown] = useState2(null);
781
- const [profileOpen, setProfileOpen] = useState2(false);
825
+ const [mobileMenuOpen, setMobileMenuOpen] = useState3(false);
826
+ const [openDropdown, setOpenDropdown] = useState3(null);
827
+ const [profileOpen, setProfileOpen] = useState3(false);
782
828
  const isMobile = useMediaQuery("(max-width: 768px)");
783
829
  const profileRef = useRef2(null);
784
830
  useEffect3(() => {
@@ -1051,8 +1097,8 @@ function Header({
1051
1097
  return /* @__PURE__ */ React2.createElement("header", { style: styles2.header }, /* @__PURE__ */ React2.createElement("div", { style: styles2.container }, /* @__PURE__ */ React2.createElement(
1052
1098
  "a",
1053
1099
  {
1054
- href: "/",
1055
- onClick: (e) => handleNavClick(e, "/"),
1100
+ href: logoHref,
1101
+ onClick: (e) => handleNavClick(e, logoHref),
1056
1102
  style: { display: "flex", alignItems: "center" }
1057
1103
  },
1058
1104
  logoSrc ? /* @__PURE__ */ React2.createElement("img", { src: logoSrc, alt: logoAlt, style: styles2.logo }) : /* @__PURE__ */ React2.createElement(
@@ -1692,7 +1738,7 @@ function FilterSidebar({
1692
1738
  }
1693
1739
 
1694
1740
  // src/PoliticianCard.jsx
1695
- import React6, { useState as useState3, useEffect as useEffect4 } from "react";
1741
+ import React6, { useState as useState4, useEffect as useEffect4 } from "react";
1696
1742
  function PoliticianCard({
1697
1743
  id,
1698
1744
  imageSrc,
@@ -1887,7 +1933,7 @@ function PoliticianCard({
1887
1933
  }
1888
1934
  )
1889
1935
  );
1890
- const [imgError, setImgError] = useState3(false);
1936
+ const [imgError, setImgError] = useState4(false);
1891
1937
  useEffect4(() => {
1892
1938
  setImgError(false);
1893
1939
  }, [imageSrc]);
@@ -1954,7 +2000,7 @@ function PoliticianCard({
1954
2000
  }
1955
2001
 
1956
2002
  // src/CompassCardHorizontal.jsx
1957
- import React11, { useState as useState5, useEffect as useEffect5 } from "react";
2003
+ import React11, { useState as useState6, useEffect as useEffect5 } from "react";
1958
2004
 
1959
2005
  // src/PlaceholderRadar.jsx
1960
2006
  import React7 from "react";
@@ -2037,7 +2083,7 @@ function PlaceholderRadar({ size = 250, name = "" }) {
2037
2083
  import React10 from "react";
2038
2084
 
2039
2085
  // src/IconOverlay.jsx
2040
- import React9, { useState as useState4 } from "react";
2086
+ import React9, { useState as useState5 } from "react";
2041
2087
  import {
2042
2088
  useFloating,
2043
2089
  useHover,
@@ -2126,7 +2172,7 @@ function BranchIcon({ size = 16, color = "currentColor", branch }) {
2126
2172
 
2127
2173
  // src/IconOverlay.jsx
2128
2174
  function IconWithTooltip({ IconComponent, color, tooltip, size = 14, extraProps = {}, onClick }) {
2129
- const [isOpen, setIsOpen] = useState4(false);
2175
+ const [isOpen, setIsOpen] = useState5(false);
2130
2176
  const { refs, floatingStyles, context } = useFloating({
2131
2177
  open: isOpen,
2132
2178
  onOpenChange: setIsOpen,
@@ -2316,10 +2362,10 @@ function CompassCardHorizontal({
2316
2362
  onClick
2317
2363
  }) {
2318
2364
  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);
2365
+ const [hovered, setHovered] = useState6(false);
2366
+ const [focused, setFocused] = useState6(false);
2367
+ const [imgError, setImgError] = useState6(false);
2368
+ const [emptyCtaHovered, setEmptyCtaHovered] = useState6(false);
2323
2369
  useEffect5(() => {
2324
2370
  setImgError(false);
2325
2371
  }, [politician == null ? void 0 : politician.photo_origin_url]);
@@ -2569,7 +2615,7 @@ function CompassCardHorizontal({
2569
2615
  }
2570
2616
 
2571
2617
  // src/CompassCardVertical.jsx
2572
- import React12, { useState as useState6, useEffect as useEffect6 } from "react";
2618
+ import React12, { useState as useState7, useEffect as useEffect6 } from "react";
2573
2619
  var RADAR_SIZE2 = 400;
2574
2620
  function CompassCardVertical({
2575
2621
  politician,
@@ -2583,10 +2629,10 @@ function CompassCardVertical({
2583
2629
  onClick
2584
2630
  }) {
2585
2631
  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);
2632
+ const [hovered, setHovered] = useState7(false);
2633
+ const [focused, setFocused] = useState7(false);
2634
+ const [imgError, setImgError] = useState7(false);
2635
+ const [emptyCtaHovered, setEmptyCtaHovered] = useState7(false);
2590
2636
  useEffect6(() => {
2591
2637
  setImgError(false);
2592
2638
  }, [politician == null ? void 0 : politician.photo_origin_url]);
@@ -2921,7 +2967,7 @@ function CompassCardVertical({
2921
2967
  }
2922
2968
 
2923
2969
  // src/CompassKey.jsx
2924
- import React13, { useEffect as useEffect7, useState as useState7 } from "react";
2970
+ import React13, { useEffect as useEffect7, useState as useState8 } from "react";
2925
2971
 
2926
2972
  // src/evContext.js
2927
2973
  var DEFAULT_BROKER_URL = "https://ev-context.empowered.vote/";
@@ -3166,8 +3212,8 @@ var evContext = {
3166
3212
 
3167
3213
  // src/CompassKey.jsx
3168
3214
  function CompassKey({ compact = false } = {}) {
3169
- const [dismissed, setDismissed] = useState7(false);
3170
- const [loaded, setLoaded] = useState7(false);
3215
+ const [dismissed, setDismissed] = useState8(false);
3216
+ const [loaded, setLoaded] = useState8(false);
3171
3217
  useEffect7(() => {
3172
3218
  let cancelled = false;
3173
3219
  evContext.get().then((shared) => {
@@ -3294,7 +3340,7 @@ function CompassKey({ compact = false } = {}) {
3294
3340
  }
3295
3341
 
3296
3342
  // src/CategorySection.jsx
3297
- import React14, { useState as useState8 } from "react";
3343
+ import React14, { useState as useState9 } from "react";
3298
3344
  function CategorySection({
3299
3345
  title,
3300
3346
  infoTooltip,
@@ -3304,7 +3350,7 @@ function CategorySection({
3304
3350
  tier
3305
3351
  }) {
3306
3352
  var _a, _b;
3307
- const [showTooltip, setShowTooltip] = useState8(false);
3353
+ const [showTooltip, setShowTooltip] = useState9(false);
3308
3354
  const tierStyle = tier ? tierColors[tier] : null;
3309
3355
  const styles2 = {
3310
3356
  section: {
@@ -3465,7 +3511,7 @@ function SubGroupSection({ title, websiteUrl, children, gridTemplateColumns, gap
3465
3511
  }
3466
3512
 
3467
3513
  // src/GovernmentBodySection.jsx
3468
- import React16, { useState as useState9 } from "react";
3514
+ import React16, { useState as useState10 } from "react";
3469
3515
  function GovernmentBodySection({
3470
3516
  title,
3471
3517
  websiteUrl,
@@ -3474,7 +3520,7 @@ function GovernmentBodySection({
3474
3520
  children
3475
3521
  }) {
3476
3522
  var _a;
3477
- const [expanded, setExpanded] = useState9(defaultExpanded);
3523
+ const [expanded, setExpanded] = useState10(defaultExpanded);
3478
3524
  const tierStyle = tier ? tierColors[tier] : null;
3479
3525
  const accentColor = (_a = tierStyle == null ? void 0 : tierStyle.accent) != null ? _a : colors.borderMedium;
3480
3526
  const styles2 = {
@@ -3803,7 +3849,7 @@ function IssueTags({
3803
3849
  }
3804
3850
 
3805
3851
  // src/CommitteeTable.jsx
3806
- import React19, { useState as useState10 } from "react";
3852
+ import React19, { useState as useState11 } from "react";
3807
3853
  var ROLE_ORDER = {
3808
3854
  "chair": 0,
3809
3855
  "co-chair": 1,
@@ -3830,7 +3876,7 @@ function CommitteeTable({
3830
3876
  maxVisible = 6,
3831
3877
  style = {}
3832
3878
  }) {
3833
- const [showAll, setShowAll] = useState10(false);
3879
+ const [showAll, setShowAll] = useState11(false);
3834
3880
  if (committees.length === 0) {
3835
3881
  return null;
3836
3882
  }
@@ -3934,7 +3980,7 @@ function CommitteeTable({
3934
3980
  }
3935
3981
 
3936
3982
  // src/PoliticianProfile.jsx
3937
- import React22, { useState as useState11, useEffect as useEffect8 } from "react";
3983
+ import React22, { useState as useState12, useEffect as useEffect8 } from "react";
3938
3984
 
3939
3985
  // src/LegislativeInlineSummary.jsx
3940
3986
  import React20 from "react";
@@ -4377,7 +4423,7 @@ function PoliticianProfile({
4377
4423
  return pol.photo_origin_url;
4378
4424
  })();
4379
4425
  const initials = [pol.first_name, pol.last_name].filter(Boolean).map((n) => n[0]).join("");
4380
- const [imgError, setImgError] = useState11(false);
4426
+ const [imgError, setImgError] = useState12(false);
4381
4427
  useEffect8(() => {
4382
4428
  setImgError(false);
4383
4429
  }, [profileImageUrl]);
@@ -4787,7 +4833,7 @@ function PoliticianProfile({
4787
4833
  }
4788
4834
 
4789
4835
  // src/LegislativeRecord.jsx
4790
- import React23, { useState as useState12 } from "react";
4836
+ import React23, { useState as useState13 } from "react";
4791
4837
  var DEFAULT_LIMIT = 25;
4792
4838
  function normalizeText(str) {
4793
4839
  if (!str) return "";
@@ -4966,7 +5012,7 @@ function Spinner() {
4966
5012
  } }), /* @__PURE__ */ React23.createElement("style", null, `@keyframes ev-spin { to { transform: rotate(360deg); } }`));
4967
5013
  }
4968
5014
  function CommitteesSection({ committees, leadership }) {
4969
- const [showAll, setShowAll] = useState12(false);
5015
+ const [showAll, setShowAll] = useState13(false);
4970
5016
  const visible = showAll ? committees : committees.slice(0, DEFAULT_LIMIT);
4971
5017
  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
5018
  display: "inline-flex",
@@ -5006,8 +5052,8 @@ function CommitteesSection({ committees, leadership }) {
5006
5052
  }
5007
5053
  function LegislationSection({ bills, isMobile }) {
5008
5054
  const years = getYears(bills, "introduced_at");
5009
- const [yearFilter, setYearFilter] = useState12("All");
5010
- const [showAll, setShowAll] = useState12(false);
5055
+ const [yearFilter, setYearFilter] = useState13("All");
5056
+ const [showAll, setShowAll] = useState13(false);
5011
5057
  const handleYearChange = (y) => {
5012
5058
  setYearFilter(y);
5013
5059
  setShowAll(false);
@@ -5068,8 +5114,8 @@ function LegislationSection({ bills, isMobile }) {
5068
5114
  }
5069
5115
  function VotingSection({ votes, isMobile }) {
5070
5116
  const years = getYears(votes, "vote_date");
5071
- const [yearFilter, setYearFilter] = useState12("All");
5072
- const [showAll, setShowAll] = useState12(false);
5117
+ const [yearFilter, setYearFilter] = useState13("All");
5118
+ const [showAll, setShowAll] = useState13(false);
5073
5119
  const handleYearChange = (y) => {
5074
5120
  setYearFilter(y);
5075
5121
  setShowAll(false);
@@ -5281,7 +5327,7 @@ function JudicialRecordDetail({ politician = {}, judicialRecord, onBack }) {
5281
5327
  }
5282
5328
 
5283
5329
  // src/AuthForm.jsx
5284
- import React25, { useState as useState13 } from "react";
5330
+ import React25, { useState as useState14 } from "react";
5285
5331
  function AuthForm({
5286
5332
  logoSrc = "/EVLogo.svg",
5287
5333
  appName = "Empowered Vote",
@@ -5292,11 +5338,11 @@ function AuthForm({
5292
5338
  error = null,
5293
5339
  submitting = false
5294
5340
  }) {
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);
5341
+ const [username, setUsername] = useState14("");
5342
+ const [password, setPassword] = useState14("");
5343
+ const [confirmPassword, setConfirmPassword] = useState14("");
5344
+ const [showPasswords, setShowPasswords] = useState14(false);
5345
+ const [passwordMismatch, setPasswordMismatch] = useState14(false);
5300
5346
  const isLogin = mode === "login";
5301
5347
  const handleSubmit = (e) => {
5302
5348
  e.preventDefault();
@@ -5744,7 +5790,7 @@ function TopicTierBadge({
5744
5790
  }
5745
5791
 
5746
5792
  // src/CompassCoverageCallout.jsx
5747
- import React27, { useState as useState14 } from "react";
5793
+ import React27, { useState as useState15 } from "react";
5748
5794
  var TIER_LABELS = {
5749
5795
  federal: "federal",
5750
5796
  state: "state",
@@ -5757,7 +5803,7 @@ function CompassCoverageCallout({
5757
5803
  compassUrl,
5758
5804
  onDismiss
5759
5805
  }) {
5760
- const [dismissed, setDismissed] = useState14(false);
5806
+ const [dismissed, setDismissed] = useState15(false);
5761
5807
  if (dismissed) return null;
5762
5808
  const tierLabel = TIER_LABELS[tier] || tier;
5763
5809
  const tierStyle = tierColors[tier] || tierColors.federal;
@@ -5897,7 +5943,7 @@ function computeTierCoverage(topics) {
5897
5943
  }
5898
5944
 
5899
5945
  // src/useEvContextPromotion.js
5900
- import { useEffect as useEffect9, useState as useState15, useCallback, useRef as useRef3 } from "react";
5946
+ import { useEffect as useEffect9, useState as useState16, useCallback, useRef as useRef3 } from "react";
5901
5947
  function useEvContextPromotion({
5902
5948
  domain,
5903
5949
  isLoggedIn,
@@ -5906,10 +5952,10 @@ function useEvContextPromotion({
5906
5952
  apiWriter,
5907
5953
  enabled = true
5908
5954
  }) {
5909
- const [shouldPrompt, setShouldPrompt] = useState15(false);
5910
- const [payload, setPayload] = useState15(null);
5911
- const [status, setStatus] = useState15("idle");
5912
- const [error, setError] = useState15(null);
5955
+ const [shouldPrompt, setShouldPrompt] = useState16(false);
5956
+ const [payload, setPayload] = useState16(null);
5957
+ const [status, setStatus] = useState16("idle");
5958
+ const [error, setError] = useState16(null);
5913
5959
  const apiDataRef = useRef3(apiData);
5914
5960
  apiDataRef.current = apiData;
5915
5961
  const userIdRef = useRef3(userId);
@@ -6056,7 +6102,7 @@ function isGuestPopulated(domain, fullEvContext) {
6056
6102
  }
6057
6103
 
6058
6104
  // src/StanceAccordion.jsx
6059
- import React30, { useState as useState16, useRef as useRef4, useCallback as useCallback2, useEffect as useEffect10 } from "react";
6105
+ import React30, { useState as useState17, useRef as useRef4, useCallback as useCallback2, useEffect as useEffect10 } from "react";
6060
6106
 
6061
6107
  // src/Favicon.jsx
6062
6108
  import React29 from "react";
@@ -6117,9 +6163,9 @@ function StanceAccordion({
6117
6163
  apiUrl = "https://api.empowered.vote"
6118
6164
  }) {
6119
6165
  var _a;
6120
- const [expandedTopicId, setExpandedTopicId] = useState16(null);
6121
- const [loadingId, setLoadingId] = useState16(null);
6122
- const [showAll, setShowAll] = useState16(false);
6166
+ const [expandedTopicId, setExpandedTopicId] = useState17(null);
6167
+ const [loadingId, setLoadingId] = useState17(null);
6168
+ const [showAll, setShowAll] = useState17(false);
6123
6169
  const contextCache = useRef4(/* @__PURE__ */ new Map());
6124
6170
  const quotesCache = useRef4(null);
6125
6171
  const polAnswerMap = {};