@empoweredvote/ev-ui 0.8.13 → 0.8.15

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,34 +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 as useEffect2 } from "react";
5
-
6
- // src/useTheme.js
7
- import { useState, useEffect } from "react";
8
- function useTheme() {
9
- const read = () => {
10
- if (typeof document === "undefined") return false;
11
- const root = document.documentElement;
12
- const body = document.body;
13
- const hasDarkClass = (el) => el && el.classList && el.classList.contains("dark");
14
- const hasDarkAttr = (el) => el && el.getAttribute && el.getAttribute("data-theme") === "dark";
15
- return hasDarkClass(root) || hasDarkAttr(root) || hasDarkClass(body) || hasDarkAttr(body);
16
- };
17
- const [isDark, setIsDark] = useState(read);
18
- useEffect(() => {
19
- if (typeof document === "undefined" || typeof MutationObserver === "undefined") return;
20
- const update = () => setIsDark(read());
21
- update();
22
- const observer = new MutationObserver(update);
23
- const opts = { attributes: true, attributeFilter: ["class", "data-theme"] };
24
- observer.observe(document.documentElement, opts);
25
- if (document.body) observer.observe(document.body, opts);
26
- return () => observer.disconnect();
27
- }, []);
28
- return { isDark };
29
- }
30
-
31
- // src/RadarChartCore.jsx
32
4
  function RadarChartCore({
33
5
  topics,
34
6
  data,
@@ -40,83 +12,95 @@ function RadarChartCore({
40
12
  onReplaceTopic = () => {
41
13
  },
42
14
  size = 400,
43
- labelFontSize = 18,
15
+ labelFontSize = 13,
44
16
  padding = 80,
45
- labelOffset = 20,
46
- tightFit = false
17
+ labelOffset = 52,
18
+ tightFit = false,
19
+ activeSpoke = null,
20
+ writeIns = {},
21
+ darkMode = false,
22
+ lineColor,
23
+ ringColor,
24
+ // replacedSpokes: short_titles of substitute spokes (not the user's original defaults)
25
+ replacedSpokes = {},
26
+ // boldOriginalSpokes: when true, bold spokes that are NOT replacements
27
+ boldOriginalSpokes = false,
28
+ highlightedSpoke = null
47
29
  }) {
48
- const { isDark } = useTheme();
30
+ var _a;
49
31
  const radius = size / 2 - 40;
32
+ const extRadius = radius * 1.15;
50
33
  const centerX = size / 2;
51
34
  const centerY = size / 2;
35
+ const [tooltip, setTooltip] = useState(null);
52
36
  const spokes = Object.entries(data);
53
37
  const numSpokes = Math.max(spokes.length, 1);
54
38
  const prevCountRef = useRef(numSpokes);
55
39
  const countChanged = numSpokes !== prevCountRef.current;
56
- useEffect2(() => {
40
+ useEffect(() => {
57
41
  prevCountRef.current = numSpokes;
58
42
  }, [numSpokes]);
59
- const pointsArr = spokes.map(([shortTitle, value], index) => {
60
- var _a;
43
+ function calcAdjusted(value, shortTitle) {
44
+ var _a2;
45
+ if (!value || Number(value) === 0) return 0;
61
46
  const topic = topics.find((t) => t.short_title === shortTitle);
62
- const max = ((_a = topic == null ? void 0 : topic.stances) == null ? void 0 : _a.length) || 10;
63
- const pct = Math.min(value / max * 10, 10);
64
- const angle = 2 * Math.PI * index / numSpokes;
65
- const adjusted = value === 0 ? 0 : invertedSpokes[shortTitle] ? 11 - pct : pct;
47
+ const max = ((_a2 = topic == null ? void 0 : topic.stances) == null ? void 0 : _a2.length) || 10;
48
+ const pct = Number(value) / max * 10;
49
+ return invertedSpokes[shortTitle] ? (max + 1) * 10 / max - pct : pct;
50
+ }
51
+ const pointsArr = spokes.map(([shortTitle, value], index) => {
52
+ const adjusted = calcAdjusted(value, shortTitle);
66
53
  const r = adjusted / 10 * radius;
67
- const x = centerX + r * Math.sin(angle);
68
- const y = centerY - r * Math.cos(angle);
69
- return [x, y];
54
+ const angle = 2 * Math.PI * index / numSpokes;
55
+ return [centerX + r * Math.sin(angle), centerY - r * Math.cos(angle)];
70
56
  });
71
57
  const targetPoints = pointsArr.map((p) => p.join(",")).join(" ");
72
- const spring = useSpring({
58
+ const springBase = {
73
59
  to: { points: targetPoints },
74
60
  immediate: countChanged,
75
- reset: countChanged,
76
61
  config: { tension: 300, friction: 30 }
77
- });
62
+ };
63
+ if (countChanged) springBase.from = { points: targetPoints };
64
+ const spring = useSpring(springBase);
78
65
  const hasCompareData = Object.keys(compareData).length > 0;
79
66
  const centerPoints = spokes.map(() => `${centerX},${centerY}`).join(" ");
80
67
  let comparePoints = null;
81
68
  let compareCoords = null;
82
69
  if (hasCompareData) {
83
70
  const ccoords = spokes.map(([shortTitle], index) => {
84
- var _a, _b;
85
- const value = (_a = compareData[shortTitle]) != null ? _a : 0;
86
- const topic = topics.find((t) => t.short_title === shortTitle);
87
- const max = ((_b = topic == null ? void 0 : topic.stances) == null ? void 0 : _b.length) || 10;
88
- const pct = Math.min(value / max * 10, 10);
89
- const angle = 2 * Math.PI * index / numSpokes;
90
- const adjusted = value === 0 ? 0 : invertedSpokes[shortTitle] ? 11 - pct : pct;
71
+ var _a2;
72
+ const value = (_a2 = compareData[shortTitle]) != null ? _a2 : 0;
73
+ const adjusted = calcAdjusted(value, shortTitle);
91
74
  const r = adjusted / 10 * radius;
92
- const x = centerX + r * Math.sin(angle);
93
- const y = centerY - r * Math.cos(angle);
94
- return [x, y];
75
+ const angle = 2 * Math.PI * index / numSpokes;
76
+ return [centerX + r * Math.sin(angle), centerY - r * Math.cos(angle)];
95
77
  });
96
78
  compareCoords = ccoords;
97
79
  comparePoints = ccoords.map((p) => p.join(",")).join(" ");
98
80
  }
99
81
  const hadCompareDataRef = useRef(false);
100
82
  const compareJustAppeared = hasCompareData && !hadCompareDataRef.current;
101
- useEffect2(() => {
83
+ useEffect(() => {
102
84
  hadCompareDataRef.current = hasCompareData;
103
85
  }, [hasCompareData]);
104
- const compareSpring = useSpring({
86
+ const compareSpringBase = {
105
87
  to: { points: comparePoints || centerPoints || `${centerX},${centerY}` },
106
88
  immediate: countChanged || compareJustAppeared,
107
- reset: countChanged || compareJustAppeared,
108
89
  config: { tension: 300, friction: 30 }
109
- });
90
+ };
91
+ if (countChanged || compareJustAppeared) compareSpringBase.from = compareSpringBase.to;
92
+ const compareSpring = useSpring(compareSpringBase);
93
+ const spokeExtension = spokes.reduce((acc, [shortTitle]) => {
94
+ const userAdj = calcAdjusted(data[shortTitle], shortTitle);
95
+ const cmpAdj = hasCompareData && compareData[shortTitle] ? calcAdjusted(compareData[shortTitle], shortTitle) : 0;
96
+ acc[shortTitle] = userAdj > 10 || cmpAdj > 10;
97
+ return acc;
98
+ }, {});
110
99
  const labelMeta = spokes.map(([shortTitle], i) => {
111
100
  const angle = 2 * Math.PI * i / numSpokes;
112
- const baseFSize = labelFontSize;
113
101
  const lines = wrapLabel(shortTitle, 12);
114
- const longestLineLen = lines.reduce(
115
- (max, ln) => ln.length > max ? ln.length : max,
116
- 0
117
- );
118
- const fSize = adaptiveFontSize(longestLineLen, baseFSize);
119
- const estimatedWidth = longestLineLen * fSize * 0.6;
102
+ const longestLineLen = lines.reduce((m, ln) => ln.length > m ? ln.length : m, 0);
103
+ const estimatedWidth = longestLineLen * labelFontSize * 0.55;
120
104
  const sinA = Math.sin(angle);
121
105
  const side = sinA < -0.1 ? "left" : sinA > 0.1 ? "right" : "center";
122
106
  return { estimatedWidth, side };
@@ -125,23 +109,9 @@ function RadarChartCore({
125
109
  const rightLabelWidths = labelMeta.filter((m) => m.side === "right").map((m) => m.estimatedWidth);
126
110
  const minPadding = 40;
127
111
  const maxPadding = padding * 2;
128
- const rawLeftPadding = Math.min(
129
- Math.max(
130
- leftLabelWidths.length ? Math.max(...leftLabelWidths) + labelOffset : 0,
131
- minPadding
132
- ),
133
- maxPadding
134
- );
135
- const rawRightPadding = Math.min(
136
- Math.max(
137
- rightLabelWidths.length ? Math.max(...rightLabelWidths) + labelOffset : 0,
138
- minPadding
139
- ),
140
- maxPadding
141
- );
142
- const symmetricPadding = Math.max(rawLeftPadding, rawRightPadding);
143
- const leftPadding = symmetricPadding;
144
- const rightPadding = symmetricPadding;
112
+ const rawLeft = Math.min(Math.max(leftLabelWidths.length ? Math.max(...leftLabelWidths) + labelOffset : 0, minPadding), maxPadding);
113
+ const rawRight = Math.min(Math.max(rightLabelWidths.length ? Math.max(...rightLabelWidths) + labelOffset : 0, minPadding), maxPadding);
114
+ const sidePadding = Math.max(rawLeft, rawRight);
145
115
  const verticalPadding = padding;
146
116
  let tightTop = -verticalPadding;
147
117
  let tightHeight = size + verticalPadding * 2;
@@ -149,11 +119,12 @@ function RadarChartCore({
149
119
  let yMin = Infinity, yMax = -Infinity;
150
120
  for (let i = 0; i < numSpokes; i++) {
151
121
  const angle = 2 * Math.PI * i / numSpokes;
152
- const y = centerY - radius * Math.cos(angle);
122
+ const effectiveR = spokeExtension[(_a = spokes[i]) == null ? void 0 : _a[0]] ? extRadius : radius;
123
+ const y = centerY - effectiveR * Math.cos(angle);
153
124
  if (y < yMin) yMin = y;
154
125
  if (y > yMax) yMax = y;
155
126
  }
156
- const labelMargin = labelOffset + labelFontSize * 1.4 + labelFontSize * 1.1;
127
+ const labelMargin = labelOffset + labelFontSize * 2.5;
157
128
  tightTop = yMin - labelMargin;
158
129
  tightHeight = yMax + labelMargin - tightTop;
159
130
  }
@@ -163,75 +134,93 @@ function RadarChartCore({
163
134
  const ring = [];
164
135
  for (let i = 0; i < numSpokes; i++) {
165
136
  const angle = 2 * Math.PI * i / numSpokes;
166
- const x = centerX + radius * scale * Math.sin(angle);
167
- const y = centerY - radius * scale * Math.cos(angle);
168
- ring.push(`${x},${y}`);
137
+ ring.push(`${centerX + radius * scale * Math.sin(angle)},${centerY - radius * scale * Math.cos(angle)}`);
169
138
  }
170
139
  guidePolygons.push(
171
- /* @__PURE__ */ React.createElement("polygon", { key: level, points: ring.join(" "), fill: "none", stroke: isDark ? "#374151" : "#ccc" })
140
+ /* @__PURE__ */ React.createElement("polygon", { key: level, points: ring.join(" "), fill: "none", stroke: ringColor != null ? ringColor : darkMode ? "#555" : "#ccc" })
172
141
  );
173
142
  }
143
+ const getStanceText = (shortTitle, value) => {
144
+ if (writeIns[shortTitle]) return writeIns[shortTitle];
145
+ const topic = topics.find((t) => t.short_title === shortTitle);
146
+ const stances = (topic == null ? void 0 : topic.stances) || [];
147
+ const idx = Math.round(Number(value)) - 1;
148
+ if (idx >= 0 && idx < stances.length) {
149
+ return stances[idx].text || shortTitle;
150
+ }
151
+ return shortTitle;
152
+ };
174
153
  return /* @__PURE__ */ React.createElement(
175
154
  "svg",
176
155
  {
177
156
  className: "w-full h-auto max-h-full",
178
- viewBox: `-${leftPadding} ${tightTop} ${size + leftPadding + rightPadding} ${tightHeight}`,
179
- preserveAspectRatio: "xMidYMid meet"
157
+ viewBox: `-${sidePadding} ${tightTop} ${size + sidePadding * 2} ${tightHeight}`,
158
+ preserveAspectRatio: "xMidYMid meet",
159
+ onMouseLeave: () => setTooltip(null)
180
160
  },
181
161
  guidePolygons,
182
162
  spokes.map(([shortTitle], i) => {
183
163
  const angle = 2 * Math.PI * i / numSpokes;
184
- const x = centerX + radius * Math.sin(angle);
185
- const y = centerY - radius * Math.cos(angle);
164
+ const endX = centerX + radius * Math.sin(angle);
165
+ const endY = centerY - radius * Math.cos(angle);
186
166
  const isUnanswered = !!unansweredSpokes[shortTitle];
187
- return /* @__PURE__ */ React.createElement(
167
+ const needsExt = spokeExtension[shortTitle];
168
+ const extX = centerX + extRadius * Math.sin(angle);
169
+ const extY = centerY - extRadius * Math.cos(angle);
170
+ return /* @__PURE__ */ React.createElement("g", { key: `spoke-${shortTitle}` }, /* @__PURE__ */ React.createElement(
188
171
  "line",
189
172
  {
190
- key: `line-${shortTitle}`,
191
173
  x1: centerX,
192
174
  y1: centerY,
193
- x2: x,
194
- y2: y,
195
- stroke: isUnanswered ? isDark ? "#6b7280" : "#9ca3af" : isDark ? "#d1d5db" : "black",
175
+ x2: endX,
176
+ y2: endY,
177
+ stroke: isUnanswered ? "#9ca3af" : lineColor != null ? lineColor : darkMode ? "#bbb" : "#888",
196
178
  strokeDasharray: isUnanswered ? "4 3" : void 0,
197
179
  opacity: isUnanswered ? 0.6 : 1
198
180
  }
199
- );
181
+ ), needsExt && /* @__PURE__ */ React.createElement(
182
+ "line",
183
+ {
184
+ x1: endX,
185
+ y1: endY,
186
+ x2: extX,
187
+ y2: extY,
188
+ stroke: "#aaa",
189
+ strokeDasharray: "4 4",
190
+ opacity: 0.4
191
+ }
192
+ ));
200
193
  }),
201
194
  spokes.map(([shortTitle], i) => {
202
195
  const angle = 2 * Math.PI * i / numSpokes;
203
- const anchor = angle > Math.PI ? "end" : angle < Math.PI && angle > 0 ? "start" : "middle";
204
196
  const cosA = Math.cos(angle);
205
- const isTop = cosA > 0.5;
206
197
  const isBottom = cosA < -0.5;
207
- const baseFSize = labelFontSize;
198
+ const isTop = cosA > 0.5;
208
199
  const lines = wrapLabel(shortTitle, 12);
209
- const longestLine = lines.reduce(
210
- (max, ln) => ln.length > max ? ln.length : max,
211
- 0
212
- );
213
- const fSize = adaptiveFontSize(longestLine, baseFSize);
214
- const lineHeight = fSize * 1.1;
215
- const dynamicLabelOffset = lines.length > 1 ? labelOffset + 8 : labelOffset;
216
- const offset2 = radius + dynamicLabelOffset;
200
+ const longestLine = lines.reduce((m, ln) => ln.length > m ? ln.length : m, 0);
201
+ const lineHeight = labelFontSize * 1.2;
202
+ const dynamicOffset = lines.length > 1 ? labelOffset + 6 : labelOffset;
203
+ const offset2 = radius + dynamicOffset;
217
204
  const labelX = centerX + offset2 * Math.sin(angle);
218
205
  let labelY = centerY - offset2 * Math.cos(angle);
219
- if (isTop && lines.length > 1) {
220
- labelY -= (lines.length - 1) * lineHeight;
221
- }
206
+ if (isTop && lines.length > 1) labelY -= (lines.length - 1) * lineHeight;
207
+ const isActive = shortTitle === activeSpoke;
222
208
  const isUnansweredLabel = !!unansweredSpokes[shortTitle];
209
+ const isReplaced = !!replacedSpokes[shortTitle];
210
+ const shouldBold = boldOriginalSpokes && !isReplaced;
211
+ const fSize = labelFontSize;
223
212
  return /* @__PURE__ */ React.createElement(
224
213
  "text",
225
214
  {
226
215
  key: `label-${shortTitle}`,
227
216
  x: labelX,
228
217
  y: labelY,
229
- textAnchor: anchor,
218
+ textAnchor: "middle",
230
219
  dominantBaseline: isBottom ? "hanging" : "auto",
231
220
  onClick: () => onReplaceTopic(shortTitle),
232
- className: "text-xl font-medium mb-1 md:text-base md:font-normal",
233
- fill: isUnansweredLabel ? isDark ? "#6b7280" : "#9ca3af" : isDark ? "#d1d5db" : void 0,
234
- style: { cursor: "pointer", userSelect: "none", fontSize: fSize }
221
+ fill: isActive ? "#E85D26" : shortTitle === highlightedSpoke ? darkMode ? "#7DD4E8" : "#59B0C4" : isUnansweredLabel ? "#9ca3af" : darkMode ? "#D3D7DE" : "#555",
222
+ fontWeight: isActive || shouldBold ? "bold" : "normal",
223
+ style: { cursor: "pointer", userSelect: "none", fontSize: fSize, fontFamily: "sans-serif" }
235
224
  },
236
225
  isUnansweredLabel && /* @__PURE__ */ React.createElement("title", null, "No stance on file for this topic."),
237
226
  lines.map((ln, idx) => /* @__PURE__ */ React.createElement("tspan", { key: idx, x: labelX, dy: idx === 0 ? "0" : `${lineHeight}` }, ln))
@@ -242,22 +231,14 @@ function RadarChartCore({
242
231
  {
243
232
  key: "user-static",
244
233
  points: targetPoints,
245
- style: {
246
- fill: "rgba(124, 107, 158, 0.4)",
247
- stroke: "#7C6B9E",
248
- strokeWidth: 3
249
- }
234
+ style: { fill: "rgba(124, 107, 158, 0.4)", stroke: "#7C6B9E", strokeWidth: 3 }
250
235
  }
251
236
  ) : /* @__PURE__ */ React.createElement(
252
237
  animated.polygon,
253
238
  {
254
239
  key: "user-animated",
255
240
  points: spring.points,
256
- style: {
257
- fill: "rgba(124, 107, 158, 0.4)",
258
- stroke: "#7C6B9E",
259
- strokeWidth: 3
260
- }
241
+ style: { fill: "rgba(124, 107, 158, 0.4)", stroke: "#7C6B9E", strokeWidth: 3 }
261
242
  }
262
243
  ),
263
244
  pointsArr.map(([cx, cy], i) => {
@@ -272,10 +253,11 @@ function RadarChartCore({
272
253
  key: `user-dot-${i}`,
273
254
  cx,
274
255
  cy,
275
- r: matches ? 8 : 7,
256
+ r: matches ? 6 : 5,
276
257
  fill: matches ? "#fed12e" : "#7C6B9E",
277
258
  stroke: "#FFFFFF",
278
- strokeWidth: 3
259
+ strokeWidth: 2,
260
+ style: { pointerEvents: "none" }
279
261
  }
280
262
  );
281
263
  }),
@@ -285,9 +267,10 @@ function RadarChartCore({
285
267
  key: "compare-static",
286
268
  points: comparePoints,
287
269
  style: {
288
- fill: "rgba(90, 154, 110, 0.3)",
289
- stroke: "#5A9A6E",
290
- strokeWidth: 2
270
+ fill: darkMode ? "rgba(110, 210, 140, 0.55)" : "rgba(90, 154, 110, 0.45)",
271
+ stroke: darkMode ? "#6DD28C" : "#5A9A6E",
272
+ strokeWidth: 2,
273
+ mixBlendMode: darkMode ? "normal" : "multiply"
291
274
  }
292
275
  }
293
276
  ) : /* @__PURE__ */ React.createElement(
@@ -296,9 +279,10 @@ function RadarChartCore({
296
279
  key: "compare-animated",
297
280
  points: compareSpring.points,
298
281
  style: {
299
- fill: "rgba(90, 154, 110, 0.3)",
300
- stroke: "#5A9A6E",
301
- strokeWidth: 2
282
+ fill: darkMode ? "rgba(110, 210, 140, 0.55)" : "rgba(90, 154, 110, 0.45)",
283
+ stroke: darkMode ? "#6DD28C" : "#5A9A6E",
284
+ strokeWidth: 2,
285
+ mixBlendMode: darkMode ? "normal" : "multiply"
302
286
  }
303
287
  }
304
288
  ) : null,
@@ -307,24 +291,25 @@ function RadarChartCore({
307
291
  const userVal = spokes[i][1];
308
292
  const compareVal = compareData[shortTitle];
309
293
  if (!compareVal || Number(compareVal) === 0) return null;
310
- const matches = userVal !== 0 && compareVal !== 0 && compareVal != null && Number(userVal) === Number(compareVal);
294
+ const matches = userVal !== 0 && compareVal != null && Number(userVal) === Number(compareVal);
311
295
  return /* @__PURE__ */ React.createElement(
312
296
  "circle",
313
297
  {
314
298
  key: `compare-dot-${i}`,
315
299
  cx,
316
300
  cy,
317
- r: matches ? 8 : 7,
318
- fill: matches ? "#fed12e" : "#5A9A6E",
301
+ r: matches ? 6 : 5,
302
+ fill: matches ? "#fed12e" : darkMode ? "#6DD28C" : "#5A9A6E",
319
303
  stroke: "#FFFFFF",
320
- strokeWidth: 3
304
+ strokeWidth: 2,
305
+ style: { pointerEvents: "none" }
321
306
  }
322
307
  );
323
308
  }),
324
309
  spokes.map(([shortTitle], i) => {
325
310
  const angle = 2 * Math.PI * i / numSpokes;
326
- const x = centerX + radius * Math.sin(angle);
327
- const y = centerY - radius * Math.cos(angle);
311
+ const endX = centerX + (spokeExtension[shortTitle] ? extRadius : radius) * Math.sin(angle);
312
+ const endY = centerY - (spokeExtension[shortTitle] ? extRadius : radius) * Math.cos(angle);
328
313
  const isUnansweredHitbox = !!unansweredSpokes[shortTitle];
329
314
  return /* @__PURE__ */ React.createElement(
330
315
  "line",
@@ -332,21 +317,92 @@ function RadarChartCore({
332
317
  key: `hitbox-${shortTitle}`,
333
318
  x1: centerX,
334
319
  y1: centerY,
335
- x2: x,
336
- y2: y,
320
+ x2: endX,
321
+ y2: endY,
337
322
  stroke: "transparent",
338
323
  strokeWidth: 14,
339
324
  onClick: () => isUnansweredHitbox ? onReplaceTopic(shortTitle) : onToggleInversion(shortTitle),
340
325
  style: { cursor: "pointer" }
341
326
  }
342
327
  );
343
- })
328
+ }),
329
+ pointsArr.map(([cx, cy], i) => {
330
+ const shortTitle = spokes[i][0];
331
+ const userVal = spokes[i][1];
332
+ if (!userVal || Number(userVal) === 0) return null;
333
+ return /* @__PURE__ */ React.createElement(
334
+ "circle",
335
+ {
336
+ key: `user-sensor-${i}`,
337
+ cx,
338
+ cy,
339
+ r: 14,
340
+ fill: "transparent",
341
+ stroke: "none",
342
+ style: { cursor: "default" },
343
+ onMouseEnter: () => setTooltip({ x: cx, y: cy, text: getStanceText(shortTitle, userVal) }),
344
+ onMouseLeave: () => setTooltip(null)
345
+ }
346
+ );
347
+ }),
348
+ hasCompareData && compareCoords && compareCoords.map(([cx, cy], i) => {
349
+ const shortTitle = spokes[i][0];
350
+ const compareVal = compareData[shortTitle];
351
+ if (!compareVal || Number(compareVal) === 0) return null;
352
+ return /* @__PURE__ */ React.createElement(
353
+ "circle",
354
+ {
355
+ key: `compare-sensor-${i}`,
356
+ cx,
357
+ cy,
358
+ r: 14,
359
+ fill: "transparent",
360
+ stroke: "none",
361
+ style: { cursor: "default" },
362
+ onMouseEnter: () => setTooltip({ x: cx, y: cy, text: getStanceText(shortTitle, compareVal) }),
363
+ onMouseLeave: () => setTooltip(null)
364
+ }
365
+ );
366
+ }),
367
+ tooltip && (() => {
368
+ const tipW = 190;
369
+ const tipH = 70;
370
+ const svgW = size + sidePadding * 2;
371
+ const tx = tooltip.x + sidePadding + 12 > svgW - tipW ? tooltip.x - tipW - 12 : tooltip.x + 12;
372
+ const ty = tooltip.y - 10;
373
+ return /* @__PURE__ */ React.createElement(
374
+ "foreignObject",
375
+ {
376
+ x: tx,
377
+ y: ty,
378
+ width: tipW,
379
+ height: tipH,
380
+ style: { overflow: "visible", pointerEvents: "none" }
381
+ },
382
+ /* @__PURE__ */ React.createElement(
383
+ "div",
384
+ {
385
+ xmlns: "http://www.w3.org/1999/xhtml",
386
+ style: {
387
+ background: darkMode ? "#2F3237" : "#ffffff",
388
+ border: darkMode ? "1px solid #59B0C4" : "1px solid #d1d5db",
389
+ borderRadius: 8,
390
+ padding: "7px 11px",
391
+ fontSize: 12,
392
+ lineHeight: 1.4,
393
+ color: darkMode ? "#EBEDEF" : "#111",
394
+ boxShadow: darkMode ? "0 2px 12px rgba(0,0,0,0.4)" : "0 2px 8px rgba(0,0,0,0.12)",
395
+ width: tipW + "px",
396
+ wordWrap: "break-word"
397
+ }
398
+ },
399
+ tooltip.text
400
+ )
401
+ );
402
+ })()
344
403
  );
345
404
  }
346
- function adaptiveFontSize(longestLineLen, baseFSize) {
347
- return Math.max(baseFSize, 10);
348
- }
349
- function wrapLabel(label, maxChars = 12) {
405
+ function wrapLabel(label, maxChars = 12, maxLines = 3) {
350
406
  const str = String(label);
351
407
  let rawWords;
352
408
  if (str.includes("/")) {
@@ -371,15 +427,15 @@ function wrapLabel(label, maxChars = 12) {
371
427
  }
372
428
  }
373
429
  if (line) lines.push(line);
374
- if (lines.length > 2) {
375
- const overflow = lines.splice(2).join(" ");
376
- lines[1] = lines[1] + " " + overflow;
430
+ if (lines.length > maxLines) {
431
+ const overflow = lines.splice(maxLines).join(" ");
432
+ lines[maxLines - 1] = lines[maxLines - 1] + " " + overflow;
377
433
  }
378
434
  return lines;
379
435
  }
380
436
 
381
437
  // src/Header.jsx
382
- import React2, { useState as useState3, useRef as useRef2, useEffect as useEffect4 } from "react";
438
+ import React2, { useState as useState3, useRef as useRef2, useEffect as useEffect3 } from "react";
383
439
 
384
440
  // src/tokens.js
385
441
  var colorScales = {
@@ -775,13 +831,13 @@ var breakpoints = {
775
831
  };
776
832
 
777
833
  // src/useMediaQuery.js
778
- import { useState as useState2, useEffect as useEffect3 } from "react";
834
+ import { useState as useState2, useEffect as useEffect2 } from "react";
779
835
  function useMediaQuery(query) {
780
836
  const [matches, setMatches] = useState2(() => {
781
837
  if (typeof window === "undefined") return false;
782
838
  return window.matchMedia(query).matches;
783
839
  });
784
- useEffect3(() => {
840
+ useEffect2(() => {
785
841
  if (typeof window === "undefined") return;
786
842
  const mql = window.matchMedia(query);
787
843
  const handler = (e) => setMatches(e.matches);
@@ -793,6 +849,16 @@ function useMediaQuery(query) {
793
849
  }
794
850
 
795
851
  // src/Header.jsx
852
+ var dark = {
853
+ bg: "#1C1F23",
854
+ bgElevated: "#2F3237",
855
+ bgHover: "#41454E",
856
+ border: "#41454E",
857
+ link: "#59B0C4",
858
+ text: "#D3D7DE",
859
+ textMuted: "#9CA3AF",
860
+ shadow: "0 10px 40px rgba(0,0,0,0.4)"
861
+ };
796
862
  function Header({
797
863
  logoSrc,
798
864
  logoAlt = "Empowered Vote",
@@ -803,6 +869,7 @@ function Header({
803
869
  currentPath,
804
870
  onNavigate,
805
871
  profileMenu,
872
+ darkMode = false,
806
873
  style = {}
807
874
  }) {
808
875
  const [mobileMenuOpen, setMobileMenuOpen] = useState3(false);
@@ -810,7 +877,7 @@ function Header({
810
877
  const [profileOpen, setProfileOpen] = useState3(false);
811
878
  const isMobile = useMediaQuery("(max-width: 768px)");
812
879
  const profileRef = useRef2(null);
813
- useEffect4(() => {
880
+ useEffect3(() => {
814
881
  if (!profileOpen) return;
815
882
  const handleClickOutside = (e) => {
816
883
  if (profileRef.current && !profileRef.current.contains(e.target)) {
@@ -826,9 +893,16 @@ function Header({
826
893
  onNavigate(href);
827
894
  }
828
895
  };
896
+ const linkColor = darkMode ? dark.link : colors.evTeal;
897
+ const dropdownBg = darkMode ? dark.bgElevated : colors.bgWhite;
898
+ const dropdownShadow = darkMode ? dark.shadow : "0 10px 40px rgba(0,0,0,0.1)";
899
+ const hoverBg = darkMode ? dark.bgHover : colors.bgLight;
900
+ const borderColor = darkMode ? dark.border : colors.borderLight;
901
+ const mobileShadow = darkMode ? "0 4px 20px rgba(0,0,0,0.4)" : "0 4px 20px rgba(0,0,0,0.1)";
829
902
  const styles2 = {
830
903
  header: {
831
- backgroundColor: colors.bgWhite,
904
+ backgroundColor: darkMode ? dark.bg : colors.bgWhite,
905
+ borderBottom: darkMode ? `1px solid ${dark.border}` : "none",
832
906
  position: "sticky",
833
907
  top: 0,
834
908
  zIndex: 50,
@@ -856,7 +930,7 @@ function Header({
856
930
  fontFamily: fonts.primary,
857
931
  fontWeight: fontWeights.bold,
858
932
  fontSize: fontSizes.xl,
859
- color: colors.evTeal,
933
+ color: linkColor,
860
934
  textDecoration: "none",
861
935
  cursor: "pointer",
862
936
  display: "flex",
@@ -879,11 +953,12 @@ function Header({
879
953
  zIndex: 100
880
954
  },
881
955
  dropdownInner: {
882
- backgroundColor: colors.bgWhite,
956
+ backgroundColor: dropdownBg,
883
957
  borderRadius: borderRadius.lg,
884
- boxShadow: "0 10px 40px rgba(0,0,0,0.1)",
958
+ boxShadow: dropdownShadow,
885
959
  minWidth: "200px",
886
- padding: spacing[2]
960
+ padding: spacing[2],
961
+ border: darkMode ? `1px solid ${dark.border}` : "none"
887
962
  },
888
963
  dropdownItem: {
889
964
  display: "block",
@@ -891,7 +966,7 @@ function Header({
891
966
  fontFamily: fonts.primary,
892
967
  fontWeight: fontWeights.medium,
893
968
  fontSize: fontSizes.base,
894
- color: colors.evTeal,
969
+ color: linkColor,
895
970
  textDecoration: "none",
896
971
  borderRadius: borderRadius.md,
897
972
  cursor: "pointer"
@@ -913,13 +988,13 @@ function Header({
913
988
  display: "inline-flex",
914
989
  alignItems: "center",
915
990
  backgroundColor: "transparent",
916
- color: colors.evTeal,
991
+ color: linkColor,
917
992
  fontFamily: fonts.primary,
918
993
  fontWeight: fontWeights.bold,
919
994
  fontSize: fontSizes.base,
920
995
  padding: `${spacing[2]} ${spacing[5]}`,
921
996
  borderRadius: borderRadius.full || "999px",
922
- border: `1.5px solid ${colors.evTeal}`,
997
+ border: `1.5px solid ${linkColor}`,
923
998
  cursor: "pointer",
924
999
  textDecoration: "none",
925
1000
  lineHeight: 1.2,
@@ -935,7 +1010,7 @@ function Header({
935
1010
  hamburger: {
936
1011
  width: "24px",
937
1012
  height: "2px",
938
- backgroundColor: colors.evTeal,
1013
+ backgroundColor: linkColor,
939
1014
  position: "relative"
940
1015
  },
941
1016
  mobileMenu: {
@@ -945,25 +1020,26 @@ function Header({
945
1020
  top: "100%",
946
1021
  left: 0,
947
1022
  right: 0,
948
- backgroundColor: colors.bgWhite,
1023
+ backgroundColor: darkMode ? dark.bg : colors.bgWhite,
949
1024
  padding: spacing[4],
950
- boxShadow: "0 4px 20px rgba(0,0,0,0.1)"
1025
+ boxShadow: mobileShadow,
1026
+ borderTop: darkMode ? `1px solid ${dark.border}` : "none"
951
1027
  },
952
1028
  mobileNavItem: {
953
1029
  fontFamily: fonts.primary,
954
1030
  fontWeight: fontWeights.bold,
955
1031
  fontSize: fontSizes.lg,
956
- color: colors.evTeal,
1032
+ color: linkColor,
957
1033
  textDecoration: "none",
958
1034
  padding: `${spacing[3]} 0`,
959
- borderBottom: `1px solid ${colors.borderLight}`
1035
+ borderBottom: `1px solid ${borderColor}`
960
1036
  },
961
1037
  profileButton: {
962
1038
  width: "40px",
963
1039
  height: "40px",
964
1040
  borderRadius: borderRadius.full,
965
- border: `2px solid ${colors.evTeal}`,
966
- backgroundColor: colors.bgLight,
1041
+ border: `2px solid ${linkColor}`,
1042
+ backgroundColor: darkMode ? dark.bgElevated : colors.bgLight,
967
1043
  cursor: "pointer",
968
1044
  display: "flex",
969
1045
  alignItems: "center",
@@ -978,11 +1054,12 @@ function Header({
978
1054
  zIndex: 100
979
1055
  },
980
1056
  profileDropdownInner: {
981
- backgroundColor: colors.bgWhite,
1057
+ backgroundColor: dropdownBg,
982
1058
  borderRadius: borderRadius.lg,
983
- boxShadow: "0 10px 40px rgba(0,0,0,0.1)",
1059
+ boxShadow: dropdownShadow,
984
1060
  minWidth: "160px",
985
- padding: spacing[2]
1061
+ padding: spacing[2],
1062
+ border: darkMode ? `1px solid ${dark.border}` : "none"
986
1063
  },
987
1064
  profileDropdownItem: {
988
1065
  display: "block",
@@ -991,7 +1068,7 @@ function Header({
991
1068
  fontFamily: fonts.primary,
992
1069
  fontWeight: fontWeights.medium,
993
1070
  fontSize: fontSizes.base,
994
- color: colors.evTeal,
1071
+ color: linkColor,
995
1072
  textDecoration: "none",
996
1073
  borderRadius: borderRadius.md,
997
1074
  cursor: "pointer",
@@ -1001,7 +1078,7 @@ function Header({
1001
1078
  },
1002
1079
  mobileDivider: {
1003
1080
  height: "1px",
1004
- backgroundColor: colors.borderLight,
1081
+ backgroundColor: borderColor,
1005
1082
  margin: `${spacing[3]} 0`
1006
1083
  },
1007
1084
  profileLabel: {
@@ -1009,8 +1086,8 @@ function Header({
1009
1086
  fontFamily: fonts.primary,
1010
1087
  fontWeight: fontWeights.semibold,
1011
1088
  fontSize: fontSizes.sm,
1012
- color: colors.textMuted,
1013
- borderBottom: `1px solid ${colors.borderLight}`,
1089
+ color: darkMode ? dark.textMuted : colors.textMuted,
1090
+ borderBottom: `1px solid ${borderColor}`,
1014
1091
  marginBottom: spacing[1],
1015
1092
  overflow: "hidden",
1016
1093
  textOverflow: "ellipsis",
@@ -1051,7 +1128,7 @@ function Header({
1051
1128
  "path",
1052
1129
  {
1053
1130
  d: "M1 1L8 8L15 1",
1054
- stroke: colors.evTeal,
1131
+ stroke: linkColor,
1055
1132
  strokeWidth: "2",
1056
1133
  strokeLinecap: "round",
1057
1134
  strokeLinejoin: "round"
@@ -1067,7 +1144,7 @@ function Header({
1067
1144
  onClick: (e) => handleNavClick(e, dropdownItem.href),
1068
1145
  style: styles2.dropdownItem,
1069
1146
  onMouseEnter: (e) => {
1070
- e.target.style.backgroundColor = colors.bgLight;
1147
+ e.target.style.backgroundColor = hoverBg;
1071
1148
  },
1072
1149
  onMouseLeave: (e) => {
1073
1150
  e.target.style.backgroundColor = "transparent";
@@ -1091,7 +1168,7 @@ function Header({
1091
1168
  fontFamily: fonts.primary,
1092
1169
  fontWeight: fontWeights.bold,
1093
1170
  fontSize: fontSizes["2xl"],
1094
- color: colors.evTeal
1171
+ color: linkColor
1095
1172
  }
1096
1173
  },
1097
1174
  "empowered.vote"
@@ -1110,12 +1187,12 @@ function Header({
1110
1187
  style: styles2.secondaryAction,
1111
1188
  className: "ev-header-secondary",
1112
1189
  onMouseEnter: (e) => {
1113
- e.currentTarget.style.backgroundColor = colors.evTeal;
1190
+ e.currentTarget.style.backgroundColor = linkColor;
1114
1191
  e.currentTarget.style.color = colors.textWhite;
1115
1192
  },
1116
1193
  onMouseLeave: (e) => {
1117
1194
  e.currentTarget.style.backgroundColor = "transparent";
1118
- e.currentTarget.style.color = colors.evTeal;
1195
+ e.currentTarget.style.color = linkColor;
1119
1196
  }
1120
1197
  },
1121
1198
  secondaryAction.label
@@ -1149,7 +1226,7 @@ function Header({
1149
1226
  height: "20",
1150
1227
  viewBox: "0 0 24 24",
1151
1228
  fill: "none",
1152
- stroke: colors.evTeal,
1229
+ stroke: linkColor,
1153
1230
  strokeWidth: "2",
1154
1231
  strokeLinecap: "round",
1155
1232
  strokeLinejoin: "round"
@@ -1173,7 +1250,7 @@ function Header({
1173
1250
  },
1174
1251
  style: styles2.profileDropdownItem,
1175
1252
  onMouseEnter: (e) => {
1176
- e.target.style.backgroundColor = colors.bgLight;
1253
+ e.target.style.backgroundColor = hoverBg;
1177
1254
  },
1178
1255
  onMouseLeave: (e) => {
1179
1256
  e.target.style.backgroundColor = "transparent";
@@ -1193,7 +1270,7 @@ function Header({
1193
1270
  },
1194
1271
  style: styles2.profileDropdownItem,
1195
1272
  onMouseEnter: (e) => {
1196
- e.target.style.backgroundColor = colors.bgLight;
1273
+ e.target.style.backgroundColor = hoverBg;
1197
1274
  },
1198
1275
  onMouseLeave: (e) => {
1199
1276
  e.target.style.backgroundColor = "transparent";
@@ -1219,7 +1296,7 @@ function Header({
1219
1296
  left: 0,
1220
1297
  width: "100%",
1221
1298
  height: "2px",
1222
- backgroundColor: colors.evTeal
1299
+ backgroundColor: linkColor
1223
1300
  }
1224
1301
  }
1225
1302
  ), /* @__PURE__ */ React2.createElement(
@@ -1231,7 +1308,7 @@ function Header({
1231
1308
  left: 0,
1232
1309
  width: "100%",
1233
1310
  height: "2px",
1234
- backgroundColor: colors.evTeal
1311
+ backgroundColor: linkColor
1235
1312
  }
1236
1313
  }
1237
1314
  ))
@@ -1484,6 +1561,7 @@ function SiteHeader({
1484
1561
  profileMenu,
1485
1562
  secondaryAction,
1486
1563
  feedbackFeature,
1564
+ darkMode = false,
1487
1565
  style = {}
1488
1566
  }) {
1489
1567
  const handleNavigate = (href) => {
@@ -1512,6 +1590,7 @@ function SiteHeader({
1512
1590
  currentPath,
1513
1591
  onNavigate: handleNavigate,
1514
1592
  profileMenu,
1593
+ darkMode,
1515
1594
  style
1516
1595
  }
1517
1596
  );
@@ -1721,7 +1800,7 @@ function FilterSidebar({
1721
1800
  }
1722
1801
 
1723
1802
  // src/PoliticianCard.jsx
1724
- import React6, { useState as useState4, useEffect as useEffect5 } from "react";
1803
+ import React6, { useState as useState4, useEffect as useEffect4 } from "react";
1725
1804
  function PoliticianCard({
1726
1805
  id,
1727
1806
  imageSrc,
@@ -1917,7 +1996,7 @@ function PoliticianCard({
1917
1996
  )
1918
1997
  );
1919
1998
  const [imgError, setImgError] = useState4(false);
1920
- useEffect5(() => {
1999
+ useEffect4(() => {
1921
2000
  setImgError(false);
1922
2001
  }, [imageSrc]);
1923
2002
  const getInitials = (n) => {
@@ -1983,7 +2062,7 @@ function PoliticianCard({
1983
2062
  }
1984
2063
 
1985
2064
  // src/CompassCardHorizontal.jsx
1986
- import React11, { useState as useState6, useEffect as useEffect6 } from "react";
2065
+ import React11, { useState as useState6, useEffect as useEffect5 } from "react";
1987
2066
 
1988
2067
  // src/PlaceholderRadar.jsx
1989
2068
  import React7 from "react";
@@ -2349,7 +2428,7 @@ function CompassCardHorizontal({
2349
2428
  const [focused, setFocused] = useState6(false);
2350
2429
  const [imgError, setImgError] = useState6(false);
2351
2430
  const [emptyCtaHovered, setEmptyCtaHovered] = useState6(false);
2352
- useEffect6(() => {
2431
+ useEffect5(() => {
2353
2432
  setImgError(false);
2354
2433
  }, [politician == null ? void 0 : politician.photo_origin_url]);
2355
2434
  const cardStyle = {
@@ -2598,7 +2677,7 @@ function CompassCardHorizontal({
2598
2677
  }
2599
2678
 
2600
2679
  // src/CompassCardVertical.jsx
2601
- import React12, { useState as useState7, useEffect as useEffect7 } from "react";
2680
+ import React12, { useState as useState7, useEffect as useEffect6 } from "react";
2602
2681
  var RADAR_SIZE2 = 400;
2603
2682
  function CompassCardVertical({
2604
2683
  politician,
@@ -2616,7 +2695,7 @@ function CompassCardVertical({
2616
2695
  const [focused, setFocused] = useState7(false);
2617
2696
  const [imgError, setImgError] = useState7(false);
2618
2697
  const [emptyCtaHovered, setEmptyCtaHovered] = useState7(false);
2619
- useEffect7(() => {
2698
+ useEffect6(() => {
2620
2699
  setImgError(false);
2621
2700
  }, [politician == null ? void 0 : politician.photo_origin_url]);
2622
2701
  const cardStyle = {
@@ -2950,7 +3029,7 @@ function CompassCardVertical({
2950
3029
  }
2951
3030
 
2952
3031
  // src/CompassKey.jsx
2953
- import React13, { useEffect as useEffect8, useState as useState8 } from "react";
3032
+ import React13, { useEffect as useEffect7, useState as useState8 } from "react";
2954
3033
 
2955
3034
  // src/evContext.js
2956
3035
  var DEFAULT_BROKER_URL = "https://ev-context.empowered.vote/";
@@ -3197,7 +3276,7 @@ var evContext = {
3197
3276
  function CompassKey({ compact = false } = {}) {
3198
3277
  const [dismissed, setDismissed] = useState8(false);
3199
3278
  const [loaded, setLoaded] = useState8(false);
3200
- useEffect8(() => {
3279
+ useEffect7(() => {
3201
3280
  let cancelled = false;
3202
3281
  evContext.get().then((shared) => {
3203
3282
  if (cancelled) return;
@@ -3858,7 +3937,8 @@ function CommitteeTable({
3858
3937
  committees = [],
3859
3938
  title = "Committee Memberships",
3860
3939
  maxVisible = 6,
3861
- style = {}
3940
+ style = {},
3941
+ darkMode = false
3862
3942
  }) {
3863
3943
  const [showAll, setShowAll] = useState11(false);
3864
3944
  if (committees.length === 0) {
@@ -3878,7 +3958,7 @@ function CommitteeTable({
3878
3958
  fontFamily: fonts.primary,
3879
3959
  fontWeight: fontWeights.semibold,
3880
3960
  fontSize: "18px",
3881
- color: "#101828",
3961
+ color: darkMode ? "#f3f4f6" : "#101828",
3882
3962
  margin: 0,
3883
3963
  marginBottom: spacing[3]
3884
3964
  },
@@ -3887,7 +3967,7 @@ function CommitteeTable({
3887
3967
  borderCollapse: "collapse"
3888
3968
  },
3889
3969
  row: {
3890
- borderBottom: "1px solid #F3F4F6"
3970
+ borderBottom: `1px solid ${darkMode ? "#374151" : "#F3F4F6"}`
3891
3971
  },
3892
3972
  cell: {
3893
3973
  padding: `${spacing[2]} 0`,
@@ -3896,16 +3976,16 @@ function CommitteeTable({
3896
3976
  verticalAlign: "top"
3897
3977
  },
3898
3978
  nameCell: {
3899
- color: "#364153",
3979
+ color: darkMode ? "#d1d5db" : "#364153",
3900
3980
  paddingRight: spacing[4]
3901
3981
  },
3902
3982
  positionCell: {
3903
- color: "#6A7282",
3983
+ color: darkMode ? "#9ca3af" : "#6A7282",
3904
3984
  textAlign: "right",
3905
3985
  whiteSpace: "nowrap"
3906
3986
  },
3907
3987
  link: {
3908
- color: "#364153",
3988
+ color: darkMode ? "#d1d5db" : "#364153",
3909
3989
  textDecoration: "none"
3910
3990
  },
3911
3991
  toggle: {
@@ -3915,7 +3995,7 @@ function CommitteeTable({
3915
3995
  fontFamily: fonts.primary,
3916
3996
  fontSize: "14px",
3917
3997
  fontWeight: fontWeights.medium,
3918
- color: "#6A7282",
3998
+ color: darkMode ? "#9ca3af" : "#6A7282",
3919
3999
  background: "none",
3920
4000
  border: "none",
3921
4001
  padding: `${spacing[2]} 0`,
@@ -3964,7 +4044,7 @@ function CommitteeTable({
3964
4044
  }
3965
4045
 
3966
4046
  // src/PoliticianProfile.jsx
3967
- import React22, { useState as useState12, useEffect as useEffect9 } from "react";
4047
+ import React22, { useState as useState12, useEffect as useEffect8 } from "react";
3968
4048
 
3969
4049
  // src/LegislativeInlineSummary.jsx
3970
4050
  import React20 from "react";
@@ -4249,12 +4329,10 @@ function stripRetain(s) {
4249
4329
  return (s || "").replace(/\s*\(Retain\s+.+?\?\)/, "");
4250
4330
  }
4251
4331
  function qualifyLocalTitle(baseTitle, pol) {
4252
- if (!baseTitle) return baseTitle;
4332
+ if (!pol.government_name || !baseTitle) return baseTitle;
4253
4333
  const dt = pol.district_type || "";
4254
4334
  if (!dt.startsWith("LOCAL") && dt !== "COUNTY") return baseTitle;
4255
- const govRaw = pol.government_name || pol.representing_city || "";
4256
- if (!govRaw) return baseTitle;
4257
- const gov = govRaw.split(",")[0].trim();
4335
+ const gov = pol.government_name.split(",")[0].trim();
4258
4336
  const govCore = gov.replace(/^City of\s+/i, "").replace(/\s+County$/i, "").trim();
4259
4337
  if (govCore && baseTitle.toLowerCase().includes(govCore.toLowerCase())) return baseTitle;
4260
4338
  let prefix = dt === "COUNTY" ? gov : gov.replace(/^City of\s+/i, "");
@@ -4421,7 +4499,7 @@ function PoliticianProfile({
4421
4499
  })();
4422
4500
  const initials = [pol.first_name, pol.last_name].filter(Boolean).map((n) => n[0]).join("");
4423
4501
  const [imgError, setImgError] = useState12(false);
4424
- useEffect9(() => {
4502
+ useEffect8(() => {
4425
4503
  setImgError(false);
4426
4504
  }, [profileImageUrl]);
4427
4505
  const getSocialHandle = (type) => {
@@ -4813,7 +4891,7 @@ function PoliticianProfile({
4813
4891
  darkMode,
4814
4892
  style: { marginTop: allWebsites.length > 0 ? "8px" : 0 }
4815
4893
  }
4816
- )))), committees.length > 0 && /* @__PURE__ */ React22.createElement(React22.Fragment, null, /* @__PURE__ */ React22.createElement("div", { style: divider }), /* @__PURE__ */ React22.createElement(CommitteeTable, { committees })), pol.is_judicial ? /* @__PURE__ */ React22.createElement(
4894
+ )))), committees.length > 0 && /* @__PURE__ */ React22.createElement(React22.Fragment, null, /* @__PURE__ */ React22.createElement("div", { style: divider }), /* @__PURE__ */ React22.createElement(CommitteeTable, { committees, darkMode })), pol.is_judicial ? /* @__PURE__ */ React22.createElement(
4817
4895
  JudicialScorecard,
4818
4896
  {
4819
4897
  judicialRecord,
@@ -5941,7 +6019,7 @@ function computeTierCoverage(topics) {
5941
6019
  }
5942
6020
 
5943
6021
  // src/useEvContextPromotion.js
5944
- import { useEffect as useEffect10, useState as useState16, useCallback, useRef as useRef3 } from "react";
6022
+ import { useEffect as useEffect9, useState as useState16, useCallback, useRef as useRef3 } from "react";
5945
6023
  function useEvContextPromotion({
5946
6024
  domain,
5947
6025
  isLoggedIn,
@@ -6005,10 +6083,10 @@ function useEvContextPromotion({
6005
6083
  setPayload(null);
6006
6084
  }
6007
6085
  }, []);
6008
- useEffect10(() => {
6086
+ useEffect9(() => {
6009
6087
  detect();
6010
6088
  }, [detect, isLoggedIn, userId, apiData, enabled, domain]);
6011
- useEffect10(() => {
6089
+ useEffect9(() => {
6012
6090
  const unsub = evContext.subscribe(() => {
6013
6091
  detect();
6014
6092
  });
@@ -6100,7 +6178,7 @@ function isGuestPopulated(domain, fullEvContext) {
6100
6178
  }
6101
6179
 
6102
6180
  // src/StanceAccordion.jsx
6103
- import React30, { useState as useState17, useRef as useRef4, useCallback as useCallback2, useEffect as useEffect11 } from "react";
6181
+ import React30, { useState as useState17, useRef as useRef4, useCallback as useCallback2, useEffect as useEffect10 } from "react";
6104
6182
 
6105
6183
  // src/Favicon.jsx
6106
6184
  import React29 from "react";
@@ -6241,7 +6319,7 @@ function StanceAccordion({
6241
6319
  },
6242
6320
  [expandedTopicId, politicianId, apiUrl]
6243
6321
  );
6244
- useEffect11(() => {
6322
+ useEffect10(() => {
6245
6323
  if (initialExpandedTopicId && topics && topics.length > 0) {
6246
6324
  handleToggle(String(initialExpandedTopicId));
6247
6325
  }