@jjlmoya/utils-civic 1.1.0 → 1.2.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.
Files changed (53) hide show
  1. package/package.json +1 -1
  2. package/src/category/i18n/de.ts +12 -9
  3. package/src/category/i18n/en.ts +12 -9
  4. package/src/category/i18n/es.ts +12 -9
  5. package/src/category/i18n/fr.ts +12 -9
  6. package/src/category/i18n/id.ts +12 -9
  7. package/src/category/i18n/it.ts +12 -9
  8. package/src/category/i18n/ja.ts +12 -9
  9. package/src/category/i18n/ko.ts +12 -9
  10. package/src/category/i18n/nl.ts +12 -9
  11. package/src/category/i18n/pl.ts +12 -9
  12. package/src/category/i18n/pt.ts +12 -9
  13. package/src/category/i18n/ru.ts +12 -9
  14. package/src/category/i18n/sv.ts +12 -9
  15. package/src/category/i18n/tr.ts +12 -9
  16. package/src/category/i18n/zh.ts +12 -8
  17. package/src/category/index.ts +2 -1
  18. package/src/entries.ts +4 -0
  19. package/src/index.ts +1 -0
  20. package/src/tests/i18n_coverage.test.ts +8 -0
  21. package/src/tests/locale_completeness.test.ts +1 -1
  22. package/src/tests/tool_validation.test.ts +1 -1
  23. package/src/tool/vote-seat-disproportionality-analyzer/bibliography.astro +6 -0
  24. package/src/tool/vote-seat-disproportionality-analyzer/bibliography.ts +16 -0
  25. package/src/tool/vote-seat-disproportionality-analyzer/component.astro +48 -0
  26. package/src/tool/vote-seat-disproportionality-analyzer/controller.ts +165 -0
  27. package/src/tool/vote-seat-disproportionality-analyzer/dom-views.ts +123 -0
  28. package/src/tool/vote-seat-disproportionality-analyzer/entry.ts +29 -0
  29. package/src/tool/vote-seat-disproportionality-analyzer/evaluator.ts +15 -0
  30. package/src/tool/vote-seat-disproportionality-analyzer/i18n/de.ts +44 -0
  31. package/src/tool/vote-seat-disproportionality-analyzer/i18n/en.ts +110 -0
  32. package/src/tool/vote-seat-disproportionality-analyzer/i18n/es.ts +42 -0
  33. package/src/tool/vote-seat-disproportionality-analyzer/i18n/fr.ts +37 -0
  34. package/src/tool/vote-seat-disproportionality-analyzer/i18n/id.ts +15 -0
  35. package/src/tool/vote-seat-disproportionality-analyzer/i18n/it.ts +10 -0
  36. package/src/tool/vote-seat-disproportionality-analyzer/i18n/ja.ts +10 -0
  37. package/src/tool/vote-seat-disproportionality-analyzer/i18n/ko.ts +10 -0
  38. package/src/tool/vote-seat-disproportionality-analyzer/i18n/nl.ts +10 -0
  39. package/src/tool/vote-seat-disproportionality-analyzer/i18n/pl.ts +10 -0
  40. package/src/tool/vote-seat-disproportionality-analyzer/i18n/pt.ts +10 -0
  41. package/src/tool/vote-seat-disproportionality-analyzer/i18n/ru.ts +10 -0
  42. package/src/tool/vote-seat-disproportionality-analyzer/i18n/sv.ts +10 -0
  43. package/src/tool/vote-seat-disproportionality-analyzer/i18n/tr.ts +10 -0
  44. package/src/tool/vote-seat-disproportionality-analyzer/i18n/zh.ts +10 -0
  45. package/src/tool/vote-seat-disproportionality-analyzer/index.ts +11 -0
  46. package/src/tool/vote-seat-disproportionality-analyzer/logic.test.ts +61 -0
  47. package/src/tool/vote-seat-disproportionality-analyzer/logic.ts +119 -0
  48. package/src/tool/vote-seat-disproportionality-analyzer/seo.astro +14 -0
  49. package/src/tool/vote-seat-disproportionality-analyzer/storage.ts +28 -0
  50. package/src/tool/vote-seat-disproportionality-analyzer/ui.ts +70 -0
  51. package/src/tool/vote-seat-disproportionality-analyzer/vote-seat-disproportionality-analyzer.css +467 -0
  52. package/src/tools.ts +2 -0
  53. package/src/types.ts +1 -1
@@ -0,0 +1,467 @@
1
+ :root {
2
+ --n-paper: #f4f0e7;
3
+ --n-surface: #fffdf8;
4
+ --n-ink: #20211d;
5
+ --n-muted: #6b6d62;
6
+ --n-line: #d9d3c6;
7
+ --n-accent: #b2472f;
8
+ --n-accent-soft: #f1d8ca;
9
+ --n-blue: #285c73;
10
+ --n-blue-soft: #d6e6e9;
11
+ --n-gold: #b6812b;
12
+ --n-success: #3c7654;
13
+ --n-caution: #a44d31;
14
+ --n-shadow: rgba(35, 31, 24, .12);
15
+ }
16
+
17
+ .theme-dark {
18
+ --n-paper: #171817;
19
+ --n-surface: #222422;
20
+ --n-ink: #f4f0e7;
21
+ --n-muted: #b3b4a9;
22
+ --n-line: #4a4e48;
23
+ --n-accent: #ef8868;
24
+ --n-accent-soft: #4c2c25;
25
+ --n-blue: #83c5d4;
26
+ --n-blue-soft: #203a41;
27
+ --n-gold: #e0b15a;
28
+ --n-success: #8bc096;
29
+ --n-caution: #f09a72;
30
+ --n-shadow: rgba(0, 0, 0, .28);
31
+ }
32
+
33
+ .vote-seat-tool {
34
+ --n-paper: #f4f0e7;
35
+ --n-surface: #fffdf8;
36
+ --n-ink: #20211d;
37
+ --n-muted: #6b6d62;
38
+ --n-line: #d9d3c6;
39
+ --n-accent: #b2472f;
40
+ --n-accent-soft: #f1d8ca;
41
+ --n-blue: #285c73;
42
+ --n-blue-soft: #d6e6e9;
43
+ --n-gold: #b6812b;
44
+ --n-success: #3c7654;
45
+ --n-caution: #a44d31;
46
+ --n-shadow: rgba(35, 31, 24, .12);
47
+
48
+ color: var(--n-ink);
49
+ background: var(--n-paper);
50
+ padding: clamp(1rem, 3vw, 2.5rem);
51
+ font: inherit;
52
+ }
53
+
54
+ .theme-dark .vote-seat-tool {
55
+ --n-paper: #171817;
56
+ --n-surface: #222422;
57
+ --n-ink: #f4f0e7;
58
+ --n-muted: #b3b4a9;
59
+ --n-line: #4a4e48;
60
+ --n-accent: #ef8868;
61
+ --n-accent-soft: #4c2c25;
62
+ --n-blue: #83c5d4;
63
+ --n-blue-soft: #203a41;
64
+ --n-gold: #e0b15a;
65
+ --n-success: #8bc096;
66
+ --n-caution: #f09a72;
67
+ --n-shadow: rgba(0, 0, 0, .28);
68
+ }
69
+
70
+ .comparison-surface {
71
+ display: grid;
72
+ grid-template-columns: minmax(16rem, .8fr) minmax(24rem, 1.4fr);
73
+ max-width: 76rem;
74
+ margin: 0 auto;
75
+ overflow: hidden;
76
+ background: var(--n-surface);
77
+ border: 1px solid var(--n-line);
78
+ box-shadow: 0 .8rem 2rem var(--n-shadow);
79
+ }
80
+
81
+ .input-surface,
82
+ .result-surface {
83
+ min-width: 0;
84
+ padding: clamp(1.2rem, 3vw, 2.4rem);
85
+ }
86
+
87
+ .input-surface {
88
+ border-right: 1px solid var(--n-line);
89
+ }
90
+
91
+ .section-label,
92
+ .result-heading {
93
+ display: flex;
94
+ align-items: baseline;
95
+ gap: .75rem;
96
+ margin-bottom: 1rem;
97
+ }
98
+
99
+ .section-label h2,
100
+ .result-heading h2,
101
+ .detail-heading {
102
+ margin: 0;
103
+ font-size: clamp(1.1rem, 2vw, 1.45rem);
104
+ line-height: 1.1;
105
+ font-weight: 600;
106
+ }
107
+
108
+ .section-glyph {
109
+ color: var(--n-accent);
110
+ font-size: .72rem;
111
+ font-weight: 700;
112
+ letter-spacing: .12em;
113
+ }
114
+
115
+ .field-help,
116
+ .model-notice,
117
+ .result-guide,
118
+ .model-note,
119
+ .totals-line {
120
+ color: var(--n-muted);
121
+ font-size: .88rem;
122
+ line-height: 1.5;
123
+ }
124
+
125
+ .field-help {
126
+ margin: 0 0 1.25rem;
127
+ }
128
+
129
+ .model-notice {
130
+ max-width: 38rem;
131
+ margin: 0 0 1.2rem;
132
+ }
133
+
134
+ .party-input-list {
135
+ display: grid;
136
+ gap: .7rem;
137
+ }
138
+
139
+ .party-input-row {
140
+ display: grid;
141
+ grid-template-columns: 1.8rem minmax(7rem, 1fr) minmax(5.5rem, .7fr) minmax(4.25rem, .5fr) 2rem;
142
+ align-items: end;
143
+ gap: .45rem;
144
+ padding-bottom: .7rem;
145
+ border-bottom: 1px solid var(--n-line);
146
+ }
147
+
148
+ .row-number {
149
+ padding-bottom: .7rem;
150
+ color: var(--n-accent);
151
+ font-size: .75rem;
152
+ font-weight: 700;
153
+ }
154
+
155
+ .input-label {
156
+ display: grid;
157
+ gap: .25rem;
158
+ min-width: 0;
159
+ }
160
+
161
+ .input-label span {
162
+ color: var(--n-muted);
163
+ font-size: .68rem;
164
+ letter-spacing: .04em;
165
+ text-transform: uppercase;
166
+ }
167
+
168
+ .input-label input {
169
+ box-sizing: border-box;
170
+ width: 100%;
171
+ min-height: 2.45rem;
172
+ padding: .45rem .55rem;
173
+ border: 1px solid var(--n-line);
174
+ border-radius: .15rem;
175
+ background: var(--n-paper);
176
+ color: var(--n-ink);
177
+ font: inherit;
178
+ font-size: .95rem;
179
+ }
180
+
181
+ .input-label input:focus {
182
+ outline: 2px solid var(--n-accent);
183
+ outline-offset: 1px;
184
+ }
185
+
186
+ .remove-row {
187
+ min-height: 2.45rem;
188
+ border: 0;
189
+ background: transparent;
190
+ color: var(--n-muted);
191
+ cursor: pointer;
192
+ font-size: 1.35rem;
193
+ line-height: 1;
194
+ }
195
+
196
+ .remove-row:hover,
197
+ .remove-row:focus-visible {
198
+ color: var(--n-accent);
199
+ }
200
+
201
+ .add-row,
202
+ .secondary-action,
203
+ .primary-action {
204
+ border-radius: .15rem;
205
+ cursor: pointer;
206
+ font: inherit;
207
+ }
208
+
209
+ .add-row {
210
+ margin-top: 1rem;
211
+ padding: .45rem 0;
212
+ border: 0;
213
+ background: transparent;
214
+ color: var(--n-blue);
215
+ font-size: .9rem;
216
+ font-weight: 700;
217
+ }
218
+
219
+ .form-actions {
220
+ display: flex;
221
+ flex-wrap: wrap;
222
+ gap: .65rem;
223
+ margin-top: 1.5rem;
224
+ }
225
+
226
+ .primary-action,
227
+ .secondary-action {
228
+ min-height: 2.7rem;
229
+ padding: .6rem .85rem;
230
+ }
231
+
232
+ .primary-action {
233
+ border: 1px solid var(--n-accent);
234
+ background: var(--n-accent);
235
+ color: var(--n-surface);
236
+ font-weight: 700;
237
+ }
238
+
239
+ .secondary-action {
240
+ border: 1px solid var(--n-line);
241
+ background: transparent;
242
+ color: var(--n-ink);
243
+ }
244
+
245
+ .primary-action:focus-visible,
246
+ .secondary-action:focus-visible,
247
+ .add-row:focus-visible {
248
+ outline: 2px solid var(--n-blue);
249
+ outline-offset: 2px;
250
+ }
251
+
252
+ .tool-status {
253
+ min-height: 1.4rem;
254
+ margin: .9rem 0 0;
255
+ color: var(--n-muted);
256
+ font-size: .82rem;
257
+ line-height: 1.4;
258
+ }
259
+
260
+ .status-positive {
261
+ color: var(--n-success);
262
+ }
263
+
264
+ .status-caution,
265
+ .status-error {
266
+ color: var(--n-caution);
267
+ }
268
+
269
+ .empty-result {
270
+ display: grid;
271
+ min-height: 18rem;
272
+ place-items: center;
273
+ padding: 2rem;
274
+ border: 1px dashed var(--n-line);
275
+ color: var(--n-muted);
276
+ text-align: center;
277
+ }
278
+
279
+ .result-stack {
280
+ display: grid;
281
+ gap: 1rem;
282
+ }
283
+
284
+ .metrics-grid {
285
+ display: grid;
286
+ grid-template-columns: repeat(4, minmax(0, 1fr));
287
+ gap: .55rem;
288
+ }
289
+
290
+ .metric {
291
+ display: grid;
292
+ gap: .35rem;
293
+ min-width: 0;
294
+ padding: .75rem;
295
+ border-top: 3px solid var(--n-line);
296
+ background: var(--n-paper);
297
+ }
298
+
299
+ .metric-gallagher {
300
+ border-color: var(--n-accent);
301
+ }
302
+
303
+ .metric-loosemore {
304
+ border-color: var(--n-blue);
305
+ }
306
+
307
+ .metric-sainte {
308
+ border-color: var(--n-gold);
309
+ }
310
+
311
+ .metric-gap {
312
+ border-color: var(--n-success);
313
+ }
314
+
315
+ .metric-label {
316
+ color: var(--n-muted);
317
+ font-size: .7rem;
318
+ line-height: 1.2;
319
+ }
320
+
321
+ .metric-value {
322
+ overflow-wrap: anywhere;
323
+ font-size: clamp(1.05rem, 2.2vw, 1.55rem);
324
+ line-height: 1;
325
+ }
326
+
327
+ .totals-line,
328
+ .result-guide,
329
+ .model-note {
330
+ margin: 0;
331
+ }
332
+
333
+ .press-scene {
334
+ display: grid;
335
+ gap: .75rem;
336
+ padding: 1rem;
337
+ border: 1px solid var(--n-line);
338
+ background: var(--n-paper);
339
+ }
340
+
341
+ .scene-legend {
342
+ color: var(--n-muted);
343
+ font-size: .72rem;
344
+ letter-spacing: .08em;
345
+ text-transform: uppercase;
346
+ }
347
+
348
+ .share-strip {
349
+ display: grid;
350
+ grid-template-columns: minmax(5rem, .6fr) minmax(10rem, 2fr) minmax(4rem, .5fr);
351
+ align-items: center;
352
+ gap: .7rem;
353
+ }
354
+
355
+ .share-name,
356
+ .share-gap {
357
+ overflow-wrap: anywhere;
358
+ font-size: .82rem;
359
+ }
360
+
361
+ .share-gap {
362
+ color: var(--n-accent);
363
+ text-align: right;
364
+ }
365
+
366
+ .share-ribbons {
367
+ position: relative;
368
+ display: grid;
369
+ gap: .3rem;
370
+ min-height: 1.45rem;
371
+ padding: .2rem 0;
372
+ background: repeating-linear-gradient(90deg, transparent 0, transparent calc(20% - 1px), var(--n-line) 20%);
373
+ }
374
+
375
+ .share-ribbon {
376
+ display: block;
377
+ min-width: 2px;
378
+ height: .35rem;
379
+ transform-origin: left center;
380
+ }
381
+
382
+ .vote-ribbon {
383
+ background: var(--n-blue);
384
+ }
385
+
386
+ .seat-ribbon {
387
+ background: var(--n-accent);
388
+ }
389
+
390
+ .model-note-caution {
391
+ color: var(--n-caution);
392
+ }
393
+
394
+ .detail-section {
395
+ overflow-x: auto;
396
+ }
397
+
398
+ .detail-heading {
399
+ margin-bottom: .7rem;
400
+ font-size: 1rem;
401
+ }
402
+
403
+ .detail-table {
404
+ width: 100%;
405
+ border-collapse: collapse;
406
+ font-size: .78rem;
407
+ }
408
+
409
+ .detail-table th,
410
+ .detail-table td {
411
+ padding: .55rem .45rem;
412
+ border-bottom: 1px solid var(--n-line);
413
+ text-align: left;
414
+ vertical-align: top;
415
+ }
416
+
417
+ .detail-table th {
418
+ color: var(--n-muted);
419
+ font-size: .68rem;
420
+ font-weight: 700;
421
+ letter-spacing: .04em;
422
+ text-transform: uppercase;
423
+ }
424
+
425
+ .detail-table td:nth-child(n + 2) {
426
+ white-space: nowrap;
427
+ }
428
+
429
+ [hidden] {
430
+ display: none;
431
+ }
432
+
433
+ @media (max-width: 900px) {
434
+ .comparison-surface {
435
+ grid-template-columns: 1fr;
436
+ }
437
+
438
+ .input-surface {
439
+ border-right: 0;
440
+ border-bottom: 1px solid var(--n-line);
441
+ }
442
+ }
443
+
444
+ @media (max-width: 620px) {
445
+ .vote-seat-tool {
446
+ padding: .7rem;
447
+ }
448
+
449
+ .input-surface,
450
+ .result-surface {
451
+ padding: 1rem;
452
+ }
453
+
454
+ .party-input-row {
455
+ grid-template-columns: 1.5rem minmax(6rem, 1fr) minmax(4.9rem, .65fr) minmax(3.7rem, .5fr) 1.7rem;
456
+ gap: .3rem;
457
+ }
458
+
459
+ .metrics-grid {
460
+ grid-template-columns: repeat(2, minmax(0, 1fr));
461
+ }
462
+
463
+ .share-strip {
464
+ grid-template-columns: minmax(4rem, .7fr) minmax(7rem, 1.7fr) minmax(3.5rem, .5fr);
465
+ gap: .45rem;
466
+ }
467
+ }
package/src/tools.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  export { ALL_ENTRIES } from './entries';
2
2
  import type { ToolDefinition } from './types';
3
3
  import { ELECTION_SEAT_APPORTIONMENT_CALCULATOR_TOOL } from './tool/election-seat-apportionment-calculator';
4
+ import { VOTE_SEAT_DISPROPORTIONALITY_ANALYZER_TOOL } from './tool/vote-seat-disproportionality-analyzer';
4
5
 
5
6
  export const ALL_TOOLS: ToolDefinition[] = [
6
7
  ELECTION_SEAT_APPORTIONMENT_CALCULATOR_TOOL,
8
+ VOTE_SEAT_DISPROPORTIONALITY_ANALYZER_TOOL,
7
9
  ];
package/src/types.ts CHANGED
@@ -47,6 +47,7 @@ export type LocaleMap<T> = Partial<Record<KnownLocale, LocaleLoader<T>>>;
47
47
 
48
48
  export interface CivicToolEntry<TUI extends Record<string, string> = Record<string, string>> {
49
49
  id: string;
50
+ phase?: 'english-first' | 'localized';
50
51
  icons: {
51
52
  bg: string;
52
53
  fg: string;
@@ -66,4 +67,3 @@ export interface ToolDefinition {
66
67
  SEOComponent: unknown;
67
68
  BibliographyComponent: unknown;
68
69
  }
69
-