@jjlmoya/utils-civic 1.1.0 → 1.3.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 (83) 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 +3 -1
  18. package/src/entries.ts +6 -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/coalition-majority-calculator/bibliography.astro +6 -0
  24. package/src/tool/coalition-majority-calculator/bibliography.ts +12 -0
  25. package/src/tool/coalition-majority-calculator/coalition-majority-calculator.css +467 -0
  26. package/src/tool/coalition-majority-calculator/component.astro +62 -0
  27. package/src/tool/coalition-majority-calculator/contract.test.ts +14 -0
  28. package/src/tool/coalition-majority-calculator/controller.ts +89 -0
  29. package/src/tool/coalition-majority-calculator/dom-views.ts +145 -0
  30. package/src/tool/coalition-majority-calculator/entry.ts +28 -0
  31. package/src/tool/coalition-majority-calculator/evaluator.ts +13 -0
  32. package/src/tool/coalition-majority-calculator/i18n/de.ts +51 -0
  33. package/src/tool/coalition-majority-calculator/i18n/en.ts +74 -0
  34. package/src/tool/coalition-majority-calculator/i18n/es.ts +51 -0
  35. package/src/tool/coalition-majority-calculator/i18n/fr.ts +12 -0
  36. package/src/tool/coalition-majority-calculator/i18n/id.ts +45 -0
  37. package/src/tool/coalition-majority-calculator/i18n/it.ts +12 -0
  38. package/src/tool/coalition-majority-calculator/i18n/ja.ts +21 -0
  39. package/src/tool/coalition-majority-calculator/i18n/ko.ts +21 -0
  40. package/src/tool/coalition-majority-calculator/i18n/nl.ts +12 -0
  41. package/src/tool/coalition-majority-calculator/i18n/pl.ts +12 -0
  42. package/src/tool/coalition-majority-calculator/i18n/pt.ts +12 -0
  43. package/src/tool/coalition-majority-calculator/i18n/ru.ts +12 -0
  44. package/src/tool/coalition-majority-calculator/i18n/sv.ts +12 -0
  45. package/src/tool/coalition-majority-calculator/i18n/tr.ts +12 -0
  46. package/src/tool/coalition-majority-calculator/i18n/zh.ts +12 -0
  47. package/src/tool/coalition-majority-calculator/index.ts +11 -0
  48. package/src/tool/coalition-majority-calculator/logic.test.ts +60 -0
  49. package/src/tool/coalition-majority-calculator/logic.ts +146 -0
  50. package/src/tool/coalition-majority-calculator/seo.astro +12 -0
  51. package/src/tool/coalition-majority-calculator/storage.ts +32 -0
  52. package/src/tool/coalition-majority-calculator/ui.ts +80 -0
  53. package/src/tool/vote-seat-disproportionality-analyzer/bibliography.astro +6 -0
  54. package/src/tool/vote-seat-disproportionality-analyzer/bibliography.ts +16 -0
  55. package/src/tool/vote-seat-disproportionality-analyzer/component.astro +48 -0
  56. package/src/tool/vote-seat-disproportionality-analyzer/controller.ts +165 -0
  57. package/src/tool/vote-seat-disproportionality-analyzer/dom-views.ts +123 -0
  58. package/src/tool/vote-seat-disproportionality-analyzer/entry.ts +29 -0
  59. package/src/tool/vote-seat-disproportionality-analyzer/evaluator.ts +15 -0
  60. package/src/tool/vote-seat-disproportionality-analyzer/i18n/de.ts +44 -0
  61. package/src/tool/vote-seat-disproportionality-analyzer/i18n/en.ts +110 -0
  62. package/src/tool/vote-seat-disproportionality-analyzer/i18n/es.ts +42 -0
  63. package/src/tool/vote-seat-disproportionality-analyzer/i18n/fr.ts +37 -0
  64. package/src/tool/vote-seat-disproportionality-analyzer/i18n/id.ts +15 -0
  65. package/src/tool/vote-seat-disproportionality-analyzer/i18n/it.ts +10 -0
  66. package/src/tool/vote-seat-disproportionality-analyzer/i18n/ja.ts +10 -0
  67. package/src/tool/vote-seat-disproportionality-analyzer/i18n/ko.ts +10 -0
  68. package/src/tool/vote-seat-disproportionality-analyzer/i18n/nl.ts +10 -0
  69. package/src/tool/vote-seat-disproportionality-analyzer/i18n/pl.ts +10 -0
  70. package/src/tool/vote-seat-disproportionality-analyzer/i18n/pt.ts +10 -0
  71. package/src/tool/vote-seat-disproportionality-analyzer/i18n/ru.ts +10 -0
  72. package/src/tool/vote-seat-disproportionality-analyzer/i18n/sv.ts +10 -0
  73. package/src/tool/vote-seat-disproportionality-analyzer/i18n/tr.ts +10 -0
  74. package/src/tool/vote-seat-disproportionality-analyzer/i18n/zh.ts +10 -0
  75. package/src/tool/vote-seat-disproportionality-analyzer/index.ts +11 -0
  76. package/src/tool/vote-seat-disproportionality-analyzer/logic.test.ts +61 -0
  77. package/src/tool/vote-seat-disproportionality-analyzer/logic.ts +119 -0
  78. package/src/tool/vote-seat-disproportionality-analyzer/seo.astro +14 -0
  79. package/src/tool/vote-seat-disproportionality-analyzer/storage.ts +28 -0
  80. package/src/tool/vote-seat-disproportionality-analyzer/ui.ts +70 -0
  81. package/src/tool/vote-seat-disproportionality-analyzer/vote-seat-disproportionality-analyzer.css +467 -0
  82. package/src/tools.ts +4 -0
  83. package/src/types.ts +1 -1
@@ -2,19 +2,22 @@ import type { CategoryLocaleContent } from '../../types';
2
2
 
3
3
  export const content: CategoryLocaleContent = {
4
4
  slug: 'kamusal',
5
- title: 'Kamusal karar araçları',
6
- description: 'Şeffaf varsayımlarla kamu karar sistemlerini incelemek için çevrimdışı araçlar; başlangıç noktası seçim sandalyesi dağılımı modelidir.',
5
+ title: 'Seçimler, bütçeler ve kamu verileri için araçlar',
6
+ description: 'Seçimleri, koalisyonları, referandumları, kamu bütçelerini, açık verileri, parlamento oylarını ve bilgiye erişimi açık varsayımlarla inceleyen yurttaşlık araçları.',
7
7
  seo: [
8
- { type: 'title', text: 'Oy toplamlarından temsili modellemek', level: 2 },
9
- { type: 'paragraph', html: 'İlk kamusal araç, parti veya liste oylarını izlenebilir bir sandalye dağılımına dönüştürür. Yöntem, seçim barajı, bölüm sırası, kalan oy sırası ve oylarla sandalyeler arasındaki fark adım adım görünür.' },
10
- { type: 'title', text: 'Yöntemleri ve varsayımları karşılaştırın', level: 2 },
11
- { type: 'paragraph', html: 'Kamusal modeller, varsayımları açık kaldığında işe yarar. Aynı girdilerle D\'Hondt, Sainte-Laguë ve Hare en büyük kalan yöntemlerini karşılaştırın; ardından barajın veya seçim bölgesi büyüklüğünün sonucu nasıl değiştirdiğini inceleyin. Çıktı eğitseldir ve resmî sayımın ya da belirli bir seçim hukukunun yerini tutmaz.' },
12
- { type: 'title', text: 'Varsayılan olarak özel', level: 2 },
13
- { type: 'paragraph', html: 'Girdiler tarayıcıda işlenir. Hesap açmak gerekmez. Model, dersler, kamusal açıklamalar ve oy senaryolarını bir hizmete göndermeden yapılan dikkatli varsayım çalışmaları için tasarlanmıştır.' },
8
+ { type: 'title', text: 'Seçimleri ve ortak kararları inceleyin', level: 2 },
9
+ { type: 'paragraph', html: 'Bu yurttaşlık araçları koleksiyonu, kamu karar sistemlerini anlamayı kolaylaştırır. Seçim sandalye dağılımıyla başlar ve koalisyon çoğunlukları, referandum eşikleri, parlamento oyları ve oyların karara dönüşme süreci için araçlar sunacak şekilde tasarlanmıştır.' },
10
+ { type: 'title', text: 'Kuralları gizlemeden temsili karşılaştırın', level: 2 },
11
+ { type: 'paragraph', html: 'Bir seçim senaryosu, varsayımları görünür olduğunda anlamlıdır. Sandalye dağılımı modeliyle D\'Hondt, Sainte-Laguë ve Hare en büyük kalan yöntemlerini karşılaştırın; ardından barajları, seçim bölgesi büyüklüğünü, oy paylarını, sandalye paylarını ve dağıtım sırasını inceleyin. Bunlar öğrenme ve açıklama senaryolarıdır, resmî sonuç değildir.' },
12
+ { type: 'title', text: 'Kamu önceliklerini bütçe senaryolarına dönüştürün', level: 2 },
13
+ { type: 'paragraph', html: 'Planlanan araçlar, öncelikleri anlaşılır bütçe senaryolarına dönüştürmeye de yardımcı olacaktır. Bir öncelik matrisinin kamu ihtiyaçlarını nasıl sıraladığını, katılımcı bütçenin kısıtlar altında nasıl dağıtılacağını ve kamu bütçesinin toplamlar, kategoriler ve tercihler üzerinden nasıl okunacağını inceleyin.' },
14
+ { type: 'title', text: 'Açık verileri ve kamu hesap verebilirliğini inceleyin', level: 2 },
15
+ { type: 'paragraph', html: 'Kamusal bir karar, arkasındaki bilginin kalitesine bağlıdır. Planlanan koleksiyon açık veri kalitesini profillemek, parlamento oylarını analiz etmek ve daha açık bilgi edinme başvuruları hazırlamak için araçlar içerir; böylece eksikler ve sorular daha kolay belgelenebilir.' },
16
+ { type: 'title', text: 'Şeffaf senaryolar, açık sınırlar ve tarayıcı gizliliği', level: 2 },
17
+ { type: 'paragraph', html: 'Her araç girdileri, yöntemi, varsayımları ve sınırları anlaşılır bir dille göstermeyi amaçlar. Koleksiyon derslerde, gazetecilikte, topluluk çalışmalarında ve dikkatli ne olurdu analizlerinde kullanılabilir. Tarayıcıdaki senaryolar resmî sayımın, belirli bir hukuk kuralının veya uzman görüşünün yerini tutmaz.' },
14
18
  { type: 'stats', items: [
15
19
  { label: 'Araç', value: '1', icon: 'mdi:tools' },
16
20
  { label: 'Yöntem', value: '3', icon: 'mdi:scale-balance' },
17
- { label: 'Mod', value: 'Çevrimdışı', icon: 'mdi:lock-outline' },
18
21
  { label: 'Durum', value: 'Çok dilli', icon: 'mdi:translate' },
19
22
  ] },
20
23
  ],
@@ -2,15 +2,19 @@ import type { CategoryLocaleContent } from '../../types';
2
2
 
3
3
  export const content: CategoryLocaleContent = {
4
4
  slug: 'civic',
5
- title: '公共决策工具',
6
- description: '用透明的假设探索公共决策制度的离线工具,首先提供选举席位分配模型。',
5
+ title: '选举、预算与公共数据的公民工具',
6
+ description: '用清晰的假设探索选举、联盟、公投、公共预算、开放数据、议会投票和信息公开的公民工具。',
7
7
  seo: [
8
- { type: 'title', text: '从得票数建立代表性模型', level: 2 },
9
- { type: 'paragraph', html: '第一个公共工具把政党或名单的得票数转换为可追溯的席位分配。用户可以逐步查看分配方法、门槛、商的顺序、余数的顺序,以及得票与席位之间的差异。' },
10
- { type: 'title', text: '比较方法与假设', level: 2 },
11
- { type: 'paragraph', html: '公共制度模型只有在假设清晰时才有帮助。用相同输入比较顿特法、圣拉古法和黑尔最大余数法,再测试门槛或选区席位数如何改变结果。输出用于学习和解释,不能替代官方计票或任何特定地区的选举法。' },
12
- { type: 'title', text: '默认在浏览器中处理', level: 2 },
13
- { type: 'paragraph', html: '输入在浏览器内处理,不需要账户。它适合课堂练习、公共说明和谨慎的假设分析,不会把投票情景发送给外部服务。' },
8
+ { type: 'title', text: '探索选举与集体决策', level: 2 },
9
+ { type: 'paragraph', html: '这组公民工具旨在让公共决策制度更容易理解。它从选举席位分配开始,并计划覆盖联盟多数、公投门槛、议会投票模式,以及得票如何转化为公共决策等问题。' },
10
+ { type: 'title', text: '不隐藏规则,比较代表性差异', level: 2 },
11
+ { type: 'paragraph', html: '选举情景只有在假设清晰时才有意义。使用席位分配模型比较顿特法、圣拉古法和黑尔最大余数法,再查看门槛、选区规模、得票率、席位率和席位分配顺序。这些情景用于学习和解释,不是官方结果。' },
12
+ { type: 'title', text: '把公共优先事项转化为预算情景', level: 2 },
13
+ { type: 'paragraph', html: '计划中的工具还将帮助把优先事项转化为易于理解的预算情景。探索优先级矩阵如何排列公共需求,参与式预算如何在限制条件下分配,以及如何通过总额、类别和权衡来阅读公共预算。' },
14
+ { type: 'title', text: '检查开放数据与公共问责', level: 2 },
15
+ { type: 'paragraph', html: '公共决策取决于支撑它的信息质量。计划中的工具包括开放数据质量分析、议会投票分析和信息公开申请构建,帮助记录信息缺口、提出清晰问题并追踪依据。' },
16
+ { type: 'title', text: '为公共决策提供清晰情景', level: 2 },
17
+ { type: 'paragraph', html: '每个工具都计划用易懂的语言展示输入、方法、假设和限制。它们适合课堂、新闻报道、社区工作和谨慎的假设分析。工具帮助清晰解释问题,但不能替代官方计票、具体法律规则或专业意见。' },
14
18
  { type: 'stats', items: [
15
19
  { label: '工具', value: '1', icon: 'mdi:tools' },
16
20
  { label: '方法', value: '3', icon: 'mdi:scale-balance' },
@@ -1,9 +1,11 @@
1
+ import { coalitionMajorityCalculator } from '../tool/coalition-majority-calculator/entry';
1
2
  import { electionSeatApportionmentCalculator } from '../tool/election-seat-apportionment-calculator/entry';
3
+ import { voteSeatDisproportionalityAnalyzer } from '../tool/vote-seat-disproportionality-analyzer/entry';
2
4
  import type { CategoryLocaleContent, KnownLocale } from '../types';
3
5
 
4
6
  export const civicCategory = {
5
7
  icon: 'mdi:bank-outline',
6
- tools: [electionSeatApportionmentCalculator],
8
+ tools: [coalitionMajorityCalculator, electionSeatApportionmentCalculator, voteSeatDisproportionalityAnalyzer],
7
9
  i18n: {
8
10
  en: () => import('./i18n/en').then((m) => m.content),
9
11
  de: () => import('./i18n/de').then((m) => m.content),
package/src/entries.ts CHANGED
@@ -1,9 +1,15 @@
1
1
  export { electionSeatApportionmentCalculator } from './tool/election-seat-apportionment-calculator/entry';
2
2
  export type { ElectionSeatUI, ElectionSeatLocaleContent } from './tool/election-seat-apportionment-calculator/entry';
3
+ export { voteSeatDisproportionalityAnalyzer } from './tool/vote-seat-disproportionality-analyzer/entry';
4
+ export type { VoteSeatUI, VoteSeatLocaleContent } from './tool/vote-seat-disproportionality-analyzer/entry';
3
5
  export { civicCategory } from './category';
4
6
 
7
+ import { coalitionMajorityCalculator } from './tool/coalition-majority-calculator/entry';
5
8
  import { electionSeatApportionmentCalculator } from './tool/election-seat-apportionment-calculator/entry';
9
+ import { voteSeatDisproportionalityAnalyzer } from './tool/vote-seat-disproportionality-analyzer/entry';
6
10
 
7
11
  export const ALL_ENTRIES = [
12
+ coalitionMajorityCalculator,
8
13
  electionSeatApportionmentCalculator,
14
+ voteSeatDisproportionalityAnalyzer,
9
15
  ];
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ export { civicCategory } from './category';
2
2
  export const civicCategorySEO = () => import('./category/CivicCategorySEO.astro').then((m) => m.default);
3
3
 
4
4
  export { electionSeatApportionmentCalculator, ELECTION_SEAT_APPORTIONMENT_CALCULATOR_TOOL } from './tool/election-seat-apportionment-calculator';
5
+ export { voteSeatDisproportionalityAnalyzer, VOTE_SEAT_DISPROPORTIONALITY_ANALYZER_TOOL } from './tool/vote-seat-disproportionality-analyzer';
5
6
 
6
7
  export type {
7
8
  KnownLocale,
@@ -11,6 +11,10 @@ describe('I18n Coverage Validation', () => {
11
11
  ALL_TOOLS.forEach(({ entry }: { entry: any }) => {
12
12
  describe(`Tool: ${entry.id}`, () => {
13
13
  it('should have all 15 required locales', () => {
14
+ if (entry.phase === 'english-first') {
15
+ expect(Object.keys(entry.i18n)).toEqual(['en']);
16
+ return;
17
+ }
14
18
  const registeredLocales = Object.keys(entry.i18n);
15
19
  EXPECTED_LOCALES.forEach((locale) => {
16
20
  expect(
@@ -21,6 +25,10 @@ describe('I18n Coverage Validation', () => {
21
25
  });
22
26
 
23
27
  it('all locale loaders should be functions', () => {
28
+ if (entry.phase === 'english-first') {
29
+ expect(typeof entry.i18n.en).toBe('function');
30
+ return;
31
+ }
24
32
  EXPECTED_LOCALES.forEach((locale) => {
25
33
  const loader = entry.i18n[locale as keyof typeof entry.i18n];
26
34
  expect(
@@ -18,6 +18,6 @@ describe('Locale Completeness Validation', () => {
18
18
  });
19
19
 
20
20
  it('all tools registered', () => {
21
- expect(ALL_TOOLS.length).toBe(1);
21
+ expect(ALL_TOOLS.length).toBe(3);
22
22
  });
23
23
  });
@@ -5,7 +5,7 @@ import { civicCategory } from '../data';
5
5
  describe('Tool Validation Suite', () => {
6
6
  describe('Library Registration', () => {
7
7
  it('should have tools in ALL_TOOLS', () => {
8
- expect(ALL_TOOLS.length).toBe(1);
8
+ expect(ALL_TOOLS.length).toBe(3);
9
9
  });
10
10
 
11
11
  it('civicCategory should be defined', () => {
@@ -0,0 +1,6 @@
1
+ ---
2
+ import { Bibliography } from '@jjlmoya/utils-shared';
3
+ import { bibliography } from './bibliography';
4
+ ---
5
+
6
+ <Bibliography links={bibliography} />
@@ -0,0 +1,12 @@
1
+ import type { BibliographyEntry } from '../../types';
2
+
3
+ export const bibliography: BibliographyEntry[] = [
4
+ {
5
+ name: 'Deutscher Bundestag, Koalition',
6
+ url: 'https://www.bundestag.de/services/glossar/glossar/K/koalition-245478',
7
+ },
8
+ {
9
+ name: 'Madeleine O. Hosli, The New Voting Weights in the Council of the European Union',
10
+ url: 'https://ciaotest.cc.columbia.edu/isa/hom01/',
11
+ },
12
+ ];
@@ -0,0 +1,467 @@
1
+ :root {
2
+ --n-paper: #f5f1e8;
3
+ --n-card: #fffdf8;
4
+ --n-ink: #17202a;
5
+ --n-muted: #5e6872;
6
+ --n-line: #d8d2c8;
7
+ --n-accent: #a44a2f;
8
+ --n-accent-soft: #f0d7c8;
9
+ --n-orbit: #d1a23a;
10
+ --n-good: #2f7759;
11
+ --n-warn: #a44a2f;
12
+ }
13
+
14
+ .theme-dark {
15
+ --n-paper: #161817;
16
+ --n-card: #202321;
17
+ --n-ink: #f1eee6;
18
+ --n-muted: #b5b9b2;
19
+ --n-line: #454945;
20
+ --n-accent: #ef8b63;
21
+ --n-accent-soft: #4b3028;
22
+ --n-orbit: #e0bd57;
23
+ --n-good: #79bd96;
24
+ --n-warn: #ef8b63;
25
+ }
26
+
27
+ .coalition-tool {
28
+ --n-surface: var(--n-card);
29
+ --n-field: var(--n-paper);
30
+
31
+ background: var(--n-paper);
32
+ border: 1px solid var(--n-line);
33
+ color: var(--n-ink);
34
+ margin: 0 auto;
35
+ max-width: 1120px;
36
+ overflow: hidden;
37
+ }
38
+
39
+ .coalition-workspace {
40
+ display: grid;
41
+ grid-template-columns: minmax(300px, .8fr) minmax(400px, 1.2fr);
42
+ }
43
+
44
+ .coalition-controls,
45
+ .coalition-result {
46
+ min-width: 0;
47
+ padding: 1.4rem;
48
+ }
49
+
50
+ .coalition-controls {
51
+ border-right: 1px solid var(--n-line);
52
+ }
53
+
54
+ .coalition-result {
55
+ background: var(--n-surface);
56
+ }
57
+
58
+ .section-kicker {
59
+ color: var(--n-accent);
60
+ font-size: .75rem;
61
+ font-weight: 800;
62
+ letter-spacing: .11em;
63
+ text-transform: uppercase;
64
+ }
65
+
66
+ .party-list {
67
+ display: grid;
68
+ gap: .7rem;
69
+ margin: 1rem 0;
70
+ }
71
+
72
+ .party-row {
73
+ align-items: end;
74
+ display: grid;
75
+ gap: .55rem;
76
+ grid-template-columns: minmax(0, 1fr) 6rem 2rem;
77
+ }
78
+
79
+ label {
80
+ color: var(--n-muted);
81
+ display: grid;
82
+ font-size: .76rem;
83
+ font-weight: 700;
84
+ gap: .3rem;
85
+ }
86
+
87
+ input {
88
+ background: var(--n-field);
89
+ border: 1px solid var(--n-line);
90
+ border-radius: .35rem;
91
+ box-sizing: border-box;
92
+ color: var(--n-ink);
93
+ font: inherit;
94
+ min-height: 2.55rem;
95
+ padding: .5rem .6rem;
96
+ width: 100%;
97
+ }
98
+
99
+ input:focus-visible,
100
+ button:focus-visible {
101
+ outline: 3px solid var(--n-orbit);
102
+ outline-offset: 2px;
103
+ }
104
+
105
+ .icon-button,
106
+ .secondary-button,
107
+ .text-button {
108
+ border: 0;
109
+ cursor: pointer;
110
+ font: inherit;
111
+ }
112
+
113
+ .icon-button {
114
+ background: transparent;
115
+ color: var(--n-muted);
116
+ font-size: 1.1rem;
117
+ min-height: 2.55rem;
118
+ }
119
+
120
+ .control-actions {
121
+ align-items: center;
122
+ display: flex;
123
+ gap: .8rem;
124
+ margin: .8rem 0 1.4rem;
125
+ }
126
+
127
+ .secondary-button {
128
+ background: var(--n-accent-soft);
129
+ border-radius: 999px;
130
+ color: var(--n-ink);
131
+ padding: .65rem .9rem;
132
+ }
133
+
134
+ .text-button {
135
+ background: transparent;
136
+ color: var(--n-accent);
137
+ padding: .5rem 0;
138
+ text-decoration: underline;
139
+ }
140
+
141
+ .threshold-control {
142
+ border-top: 1px solid var(--n-line);
143
+ padding-top: 1rem;
144
+ }
145
+
146
+ .threshold-help,
147
+ .threshold-control small,
148
+ .control-note,
149
+ .limit-note,
150
+ .method-note,
151
+ .scene-empty {
152
+ color: var(--n-muted);
153
+ }
154
+
155
+ .control-note,
156
+ .limit-note {
157
+ font-size: .88rem;
158
+ line-height: 1.5;
159
+ }
160
+
161
+ .limit-note {
162
+ border-left: 3px solid var(--n-orbit);
163
+ padding-left: .7rem;
164
+ }
165
+
166
+ .result-heading {
167
+ align-items: baseline;
168
+ display: flex;
169
+ justify-content: space-between;
170
+ gap: 1rem;
171
+ }
172
+
173
+ .result-heading p {
174
+ color: var(--n-good);
175
+ font-size: .86rem;
176
+ margin: 0;
177
+ text-align: right;
178
+ }
179
+
180
+ .result-heading p[data-tone="warn"] {
181
+ color: var(--n-warn);
182
+ }
183
+
184
+ .scene-wrap {
185
+ align-items: center;
186
+ border-bottom: 1px solid var(--n-line);
187
+ display: flex;
188
+ justify-content: center;
189
+ min-height: 300px;
190
+ }
191
+
192
+ .scene-empty {
193
+ font-size: .9rem;
194
+ line-height: 1.5;
195
+ max-width: 20rem;
196
+ text-align: center;
197
+ }
198
+
199
+ .coalition-orbit {
200
+ fill: none;
201
+ stroke: var(--n-orbit);
202
+ stroke-dasharray: 3 7;
203
+ stroke-width: 2;
204
+ }
205
+
206
+ .coalition-core {
207
+ fill: var(--n-accent-soft);
208
+ stroke: var(--n-accent);
209
+ stroke-width: 2;
210
+ }
211
+
212
+ .coalition-link {
213
+ stroke: var(--n-line);
214
+ stroke-width: 1.2;
215
+ }
216
+
217
+ .coalition-node circle {
218
+ fill: var(--n-field);
219
+ stroke: var(--n-muted);
220
+ stroke-width: 1.5;
221
+ }
222
+
223
+ .coalition-winning-link {
224
+ opacity: .42;
225
+ stroke: var(--n-accent);
226
+ stroke-width: 2.4;
227
+ }
228
+
229
+ .coalition-node.is-pivotal circle {
230
+ fill: var(--n-accent-soft);
231
+ stroke: var(--n-accent);
232
+ stroke-width: 3;
233
+ }
234
+
235
+ .coalition-core-label,
236
+ .coalition-node-name,
237
+ .coalition-node-seats,
238
+ .coalition-core-caption {
239
+ fill: var(--n-ink);
240
+ }
241
+
242
+ .coalition-core-label {
243
+ font-size: 1.25rem;
244
+ font-weight: 800;
245
+ }
246
+
247
+ .coalition-core-caption,
248
+ .coalition-node-seats {
249
+ fill: var(--n-muted);
250
+ font-size: .62rem;
251
+ }
252
+
253
+ .coalition-node-name {
254
+ font-size: .68rem;
255
+ font-weight: 800;
256
+ text-anchor: middle;
257
+ }
258
+
259
+ .coalition-node-seats {
260
+ text-anchor: middle;
261
+ }
262
+
263
+ .coalition-stats {
264
+ display: grid;
265
+ gap: .7rem;
266
+ grid-template-columns: repeat(2, 1fr);
267
+ margin: 1.2rem 0;
268
+ }
269
+
270
+ .coalition-stat {
271
+ border-left: 2px solid var(--n-orbit);
272
+ padding-left: .65rem;
273
+ }
274
+
275
+ .coalition-stat dt {
276
+ color: var(--n-muted);
277
+ font-size: .72rem;
278
+ }
279
+
280
+ .coalition-stat dd {
281
+ font-size: 1.1rem;
282
+ font-weight: 800;
283
+ margin: .15rem 0 0;
284
+ }
285
+
286
+ .pivots,
287
+ .coalition-table-wrap {
288
+ border-top: 1px solid var(--n-line);
289
+ padding-top: 1rem;
290
+ }
291
+
292
+ .pivots h3,
293
+ .coalition-table-wrap h3 {
294
+ font-size: .9rem;
295
+ margin: 0;
296
+ }
297
+
298
+ .table-heading {
299
+ align-items: end;
300
+ display: flex;
301
+ gap: 1rem;
302
+ justify-content: space-between;
303
+ margin-bottom: .65rem;
304
+ }
305
+
306
+ .table-filter {
307
+ align-items: center;
308
+ display: flex;
309
+ flex-direction: row;
310
+ font-size: .7rem;
311
+ gap: .4rem;
312
+ }
313
+
314
+ .table-filter select {
315
+ background: var(--n-field);
316
+ border: 1px solid var(--n-line);
317
+ border-radius: .35rem;
318
+ color: var(--n-ink);
319
+ font: inherit;
320
+ min-height: 2rem;
321
+ padding: .25rem .4rem;
322
+ }
323
+
324
+ .pivots ul {
325
+ display: grid;
326
+ gap: .45rem;
327
+ grid-template-columns: repeat(2, 1fr);
328
+ list-style: none;
329
+ margin: 0 0 1.2rem;
330
+ padding: 0;
331
+ }
332
+
333
+ .pivots li {
334
+ display: grid;
335
+ grid-template-columns: 1fr auto;
336
+ gap: .2rem .5rem;
337
+ }
338
+
339
+ .pivots small {
340
+ color: var(--n-muted);
341
+ font-size: .7rem;
342
+ grid-column: 1 / -1;
343
+ }
344
+
345
+ .table-scroll {
346
+ overflow-x: auto;
347
+ }
348
+
349
+ table {
350
+ border-collapse: collapse;
351
+ font-size: .78rem;
352
+ width: 100%;
353
+ }
354
+
355
+ th,
356
+ td {
357
+ border-bottom: 1px solid var(--n-line);
358
+ padding: .55rem .4rem;
359
+ text-align: left;
360
+ vertical-align: top;
361
+ }
362
+
363
+ thead th {
364
+ color: var(--n-muted);
365
+ font-size: .7rem;
366
+ position: sticky;
367
+ text-transform: uppercase;
368
+ top: 0;
369
+ }
370
+
371
+ .row-badge {
372
+ color: var(--n-muted);
373
+ display: block;
374
+ font-size: .65rem;
375
+ font-weight: 800;
376
+ margin-bottom: .15rem;
377
+ text-transform: uppercase;
378
+ }
379
+
380
+ .row-badge.is-minimal {
381
+ color: var(--n-accent);
382
+ }
383
+
384
+ .method-note {
385
+ border-top: 1px solid var(--n-line);
386
+ display: grid;
387
+ font-size: .8rem;
388
+ gap: 1rem;
389
+ grid-template-columns: repeat(3, 1fr);
390
+ line-height: 1.45;
391
+ padding: 1rem 1.4rem;
392
+ }
393
+
394
+ .method-note section {
395
+ display: grid;
396
+ gap: .35rem;
397
+ }
398
+
399
+ .method-note strong {
400
+ color: var(--n-ink);
401
+ }
402
+
403
+ .coalition-seo,
404
+ .coalition-bibliography {
405
+ color: var(--n-ink);
406
+ margin: 2rem auto;
407
+ max-width: 840px;
408
+ }
409
+
410
+ .coalition-seo h2,
411
+ .coalition-bibliography h2 {
412
+ margin-top: 2rem;
413
+ }
414
+
415
+ .coalition-seo p,
416
+ .coalition-seo li,
417
+ .coalition-bibliography li {
418
+ line-height: 1.65;
419
+ }
420
+
421
+ .coalition-seo aside {
422
+ border-left: 3px solid var(--n-orbit);
423
+ margin: 1.2rem 0;
424
+ padding: .2rem 1rem;
425
+ }
426
+
427
+ .coalition-seo table {
428
+ margin: 1rem 0;
429
+ }
430
+
431
+ .coalition-bibliography a {
432
+ color: var(--n-accent);
433
+ }
434
+
435
+ @media (max-width: 760px) {
436
+ .coalition-workspace {
437
+ grid-template-columns: 1fr;
438
+ }
439
+
440
+ .coalition-controls {
441
+ border-bottom: 1px solid var(--n-line);
442
+ border-right: 0;
443
+ }
444
+
445
+ .coalition-result {
446
+ padding-top: 1.1rem;
447
+ }
448
+ }
449
+
450
+ @media (max-width: 460px) {
451
+ .coalition-controls,
452
+ .coalition-result,
453
+ .method-note {
454
+ padding-left: .9rem;
455
+ padding-right: .9rem;
456
+ }
457
+
458
+ .party-row {
459
+ grid-template-columns: minmax(0, 1fr) 5rem 1.7rem;
460
+ }
461
+
462
+ .pivots ul {
463
+ grid-template-columns: 1fr;
464
+ }
465
+
466
+ .method-note { grid-template-columns: 1fr; }
467
+ }