@jjlmoya/utils-civic 1.1.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 (119) hide show
  1. package/.github/workflows/npm-publish.yml +40 -0
  2. package/.gitignore +6 -0
  3. package/.stylelintrc.json +98 -0
  4. package/astro.config.mjs +19 -0
  5. package/eslint.config.js +201 -0
  6. package/package.json +79 -0
  7. package/prompts/create_tool.md +98 -0
  8. package/prompts/i18n/de.md +16 -0
  9. package/prompts/i18n/en.md +16 -0
  10. package/prompts/i18n/es.md +16 -0
  11. package/prompts/i18n/fr.md +16 -0
  12. package/prompts/i18n/id.md +16 -0
  13. package/prompts/i18n/it.md +16 -0
  14. package/prompts/i18n/ja.md +16 -0
  15. package/prompts/i18n/ko.md +16 -0
  16. package/prompts/i18n/nl.md +16 -0
  17. package/prompts/i18n/pl.md +16 -0
  18. package/prompts/i18n/pt.md +16 -0
  19. package/prompts/i18n/ru.md +16 -0
  20. package/prompts/i18n/sv.md +16 -0
  21. package/prompts/i18n/tr.md +16 -0
  22. package/prompts/i18n/zh.md +16 -0
  23. package/prompts/seo.md +58 -0
  24. package/prompts/translations/french.md +33 -0
  25. package/scripts/postinstall.mjs +27 -0
  26. package/src/category/CivicCategorySEO.astro +9 -0
  27. package/src/category/i18n/de.ts +21 -0
  28. package/src/category/i18n/en.ts +25 -0
  29. package/src/category/i18n/es.ts +21 -0
  30. package/src/category/i18n/fr.ts +21 -0
  31. package/src/category/i18n/id.ts +21 -0
  32. package/src/category/i18n/it.ts +21 -0
  33. package/src/category/i18n/ja.ts +21 -0
  34. package/src/category/i18n/ko.ts +21 -0
  35. package/src/category/i18n/nl.ts +21 -0
  36. package/src/category/i18n/pl.ts +21 -0
  37. package/src/category/i18n/pt.ts +21 -0
  38. package/src/category/i18n/ru.ts +21 -0
  39. package/src/category/i18n/sv.ts +21 -0
  40. package/src/category/i18n/tr.ts +21 -0
  41. package/src/category/i18n/zh.ts +21 -0
  42. package/src/category/index.ts +24 -0
  43. package/src/components/PreviewNavSidebar.astro +116 -0
  44. package/src/components/PreviewToolbar.astro +143 -0
  45. package/src/data.ts +10 -0
  46. package/src/entries.ts +9 -0
  47. package/src/env.d.ts +5 -0
  48. package/src/index.ts +20 -0
  49. package/src/layouts/PreviewLayout.astro +117 -0
  50. package/src/pages/[locale]/[slug].astro +163 -0
  51. package/src/pages/[locale].astro +253 -0
  52. package/src/pages/index.astro +4 -0
  53. package/src/tests/bibliography_wellformed_export.test.ts +46 -0
  54. package/src/tests/category_seo_quality.test.ts +79 -0
  55. package/src/tests/diacritics_density.test.ts +118 -0
  56. package/src/tests/faq_count.test.ts +18 -0
  57. package/src/tests/i18n_coverage.test.ts +34 -0
  58. package/src/tests/inverted_punctuation.test.ts +84 -0
  59. package/src/tests/locale_completeness.test.ts +23 -0
  60. package/src/tests/mocks/astro_mock.js +2 -0
  61. package/src/tests/no_em_dash.test.ts +47 -0
  62. package/src/tests/no_en_dash.test.ts +70 -0
  63. package/src/tests/no_h1_in_components.test.ts +48 -0
  64. package/src/tests/pagespeed_best_practices.test.ts +198 -0
  65. package/src/tests/qa-test-helpers.ts +31 -0
  66. package/src/tests/qa_bibliography_links.test.ts +53 -0
  67. package/src/tests/qa_claim_evidence.test.ts +68 -0
  68. package/src/tests/qa_logic_reference_coverage.test.ts +45 -0
  69. package/src/tests/qa_runtime_i18n.test.ts +100 -0
  70. package/src/tests/registry_contract.test.ts +67 -0
  71. package/src/tests/schemas_fulfillment.test.ts +23 -0
  72. package/src/tests/script_density.test.ts +94 -0
  73. package/src/tests/seo_length.test.ts +23 -0
  74. package/src/tests/seo_parity.test.ts +60 -0
  75. package/src/tests/seo_translation_completeness.test.ts +75 -0
  76. package/src/tests/seo_wellformed_export.test.ts +65 -0
  77. package/src/tests/shared-test-helpers.ts +56 -0
  78. package/src/tests/slug_language_code_format.test.ts +23 -0
  79. package/src/tests/slug_uniqueness.test.ts +81 -0
  80. package/src/tests/spanish_leakage.test.ts +175 -0
  81. package/src/tests/title_quality.test.ts +55 -0
  82. package/src/tests/tool_exports.test.ts +34 -0
  83. package/src/tests/tool_validation.test.ts +16 -0
  84. package/src/tests/translation_copy.test.ts +123 -0
  85. package/src/tool/election-seat-apportionment-calculator/bibliography.astro +6 -0
  86. package/src/tool/election-seat-apportionment-calculator/bibliography.ts +16 -0
  87. package/src/tool/election-seat-apportionment-calculator/component.astro +69 -0
  88. package/src/tool/election-seat-apportionment-calculator/controller.ts +181 -0
  89. package/src/tool/election-seat-apportionment-calculator/dom-views.ts +75 -0
  90. package/src/tool/election-seat-apportionment-calculator/election-seat-apportionment-calculator.css +585 -0
  91. package/src/tool/election-seat-apportionment-calculator/entry.ts +28 -0
  92. package/src/tool/election-seat-apportionment-calculator/evaluator.ts +20 -0
  93. package/src/tool/election-seat-apportionment-calculator/i18n/de.ts +55 -0
  94. package/src/tool/election-seat-apportionment-calculator/i18n/en.ts +108 -0
  95. package/src/tool/election-seat-apportionment-calculator/i18n/es.ts +50 -0
  96. package/src/tool/election-seat-apportionment-calculator/i18n/fr.ts +48 -0
  97. package/src/tool/election-seat-apportionment-calculator/i18n/id.ts +48 -0
  98. package/src/tool/election-seat-apportionment-calculator/i18n/it.ts +48 -0
  99. package/src/tool/election-seat-apportionment-calculator/i18n/ja.ts +48 -0
  100. package/src/tool/election-seat-apportionment-calculator/i18n/ko.ts +48 -0
  101. package/src/tool/election-seat-apportionment-calculator/i18n/nl.ts +48 -0
  102. package/src/tool/election-seat-apportionment-calculator/i18n/pl.ts +48 -0
  103. package/src/tool/election-seat-apportionment-calculator/i18n/pt.ts +48 -0
  104. package/src/tool/election-seat-apportionment-calculator/i18n/ru.ts +48 -0
  105. package/src/tool/election-seat-apportionment-calculator/i18n/sv.ts +48 -0
  106. package/src/tool/election-seat-apportionment-calculator/i18n/tr.ts +48 -0
  107. package/src/tool/election-seat-apportionment-calculator/i18n/zh.ts +48 -0
  108. package/src/tool/election-seat-apportionment-calculator/index.ts +11 -0
  109. package/src/tool/election-seat-apportionment-calculator/logic.test.ts +90 -0
  110. package/src/tool/election-seat-apportionment-calculator/logic.ts +268 -0
  111. package/src/tool/election-seat-apportionment-calculator/seo.astro +14 -0
  112. package/src/tool/election-seat-apportionment-calculator/sharing.test.ts +34 -0
  113. package/src/tool/election-seat-apportionment-calculator/sharing.ts +88 -0
  114. package/src/tool/election-seat-apportionment-calculator/storage.ts +28 -0
  115. package/src/tool/election-seat-apportionment-calculator/ui.ts +92 -0
  116. package/src/tools.ts +7 -0
  117. package/src/types.ts +69 -0
  118. package/tsconfig.json +15 -0
  119. package/vitest.config.ts +20 -0
@@ -0,0 +1,16 @@
1
+ # Prompts-어 (ko)
2
+
3
+ ## 번역
4
+
5
+ 다음 내용을 한국어로 현지화하세요.
6
+
7
+ 번역하지 말고, 현지화하세요. 목표는 단어를 한 언어에서 다른 언어로 옮기는 것이 아니라, 실제 사람이 처음부터 한국어로 직접 쓴 것처럼 읽히는 텍스트를 만드는 것입니다. 잘 현지화된 텍스트에는 원본 언어의 흔적이 남지 않습니다.
8
+
9
+ 인간의 목소리로 쓰세요: 친근하고, 명확하며, 전문적으로. 기계적인 표현, 어색한 문장 구조, 사용 설명서처럼 들리는 표현은 피하세요. 한국어로 자연스럽게 읽히지 않는 부분이 있다면, 자연스러워질 때까지 다시 표현하세요.
10
+
11
+ 준수해야 할 지침:
12
+
13
+ - 표현, 비유, 문화적 참조를 한국어 사용 환경에서 자연스럽게 공감될 수 있도록 조정하세요.
14
+ - 기술 용어는 한국어권에서 가장 널리 통용되는 형태로 유지하고, 명확한 대응어가 없으면 원문 용어를 그대로 사용하세요.
15
+ - 원본의 형식(마크다운, 목록, 제목)을 유지하세요.
16
+ - 원문에서 정보를 추가하거나 생략하지 마세요.
@@ -0,0 +1,16 @@
1
+ # Prompts-derlands (nl)
2
+
3
+ ## Vertaling
4
+
5
+ Lokaliseer de volgende inhoud naar het Nederlands.
6
+
7
+ Vertaal niet: lokaliseer. Het doel is niet om woorden van de ene taal naar de andere over te zetten, maar om een tekst te produceren die klinkt alsof hij vanaf het begin door een echte persoon in het Nederlands is geschreven. Een goed gelokaliseerde tekst laat geen spoor van de brontaal achter.
8
+
9
+ Schrijf met een menselijke stem: warm, helder en professioneel. Vermijd machinale zinnen, geforceerde constructies en alles wat klinkt als een handleiding. Als iets niet natuurlijk stroomt in het Nederlands, herschrijf het dan totdat het dat wel doet.
10
+
11
+ Richtlijnen om te volgen:
12
+
13
+ - Pas uitdrukkingen, metaforen en culturele verwijzingen aan zodat ze natuurlijk resoneren in een Nederlandstalige context.
14
+ - Gebruik technische termen in hun meest gangbare vorm in de Nederlandstalige wereld; als er geen duidelijk equivalent bestaat, behoud dan de originele term.
15
+ - Respecteer de opmaak van het origineel (markdown, lijsten, koppen).
16
+ - Voeg geen informatie toe en laat geen informatie weg ten opzichte van het origineel.
@@ -0,0 +1,16 @@
1
+ # Prompts-lski (pl)
2
+
3
+ ## Tłumaczenie
4
+
5
+ Zlokalizuj poniższą treść na język polski.
6
+
7
+ Nie tłumacz-kalizuj. Celem nie jest przeniesienie słów z jednego języka do drugiego, lecz stworzenie tekstu, który brzmi tak, jakby od początku napisała go prawdziwa osoba po polsku. Dobrze zlokalizowany tekst nie zdradza języka źródłowego.
8
+
9
+ Pisz ludzkim głosem: bliskim, jasnym i profesjonalnym. Unikaj maszynowego języka, wymuszonych zwrotów i zdań brzmiących jak instrukcja obsługi. Jeśli coś nie brzmi naturalnie po polsku, przeredaguj to tak, żeby brzmiało.
10
+
11
+ Wytyczne do przestrzegania:
12
+
13
+ - Dostosuj wyrażenia, metafory i odniesienia kulturowe do tego, co naturalnie rezonuje w polskojęzycznym kontekście.
14
+ - Zachowaj terminy techniczne w ich najbardziej rozpoznawalnej formie w polskim środowisku; jeśli nie istnieje wyraźny odpowiednik, zachowaj oryginalny termin.
15
+ - Zachowaj formatowanie oryginału (markdown, listy, nagłówki).
16
+ - Nie dodawaj ani nie pomijaj żadnych informacji z oryginału.
@@ -0,0 +1,16 @@
1
+ # Prompts-rtuguês (pt)
2
+
3
+ ## Tradução
4
+
5
+ Localize o seguinte conteúdo para o português.
6
+
7
+ Não traduza: localize. O objetivo não é transferir palavras de um idioma para outro, mas produzir um texto que pareça ter sido escrito originalmente em português por uma pessoa real. Um texto bem localizado não traz nenhum rastro do idioma de origem.
8
+
9
+ Escreva com voz humana: próxima, clara e profissional. Evite linguagem de máquina, construções forçadas e frases que soam como manual técnico. Se algo não fluir de forma natural em português, reformule até que flua.
10
+
11
+ Critérios a respeitar:
12
+
13
+ - Adapte expressões, metáforas e referências culturais ao que ressoaria de forma natural num contexto lusófono.
14
+ - Mantenha os termos técnicos na forma mais reconhecida no mundo lusófono; se não existir um equivalente claro, conserve o termo original.
15
+ - Respeite a formatação do original (markdown, listas, cabeçalhos).
16
+ - Não acrescente nem omita informação em relação ao original.
@@ -0,0 +1,16 @@
1
+ # Prompts-сский (ru)
2
+
3
+ ## Перевод
4
+
5
+ Локализуйте следующий контент на русский язык.
6
+
7
+ Не переводите-кализуйте. Цель не в том, чтобы перенести слова из одного языка в другой, а в том, чтобы создать текст, который звучит так, словно его с самого начала написал живой человек по-русски. В хорошо локализованном тексте не остаётся следов языка оригинала.
8
+
9
+ Пишите человеческим голосом: тёплым, ясным и профессиональным. Избегайте машинного языка, вынужденных конструкций и фраз, которые звучат как инструкция по эксплуатации. Если что-то не звучит по-русски естественно, перефразируйте это так, чтобы звучало.
10
+
11
+ Принципы, которых следует придерживаться:
12
+
13
+ - Адаптируйте выражения, метафоры и культурные отсылки к тому, что естественно воспринимается в русскоязычном контексте.
14
+ - Используйте технические термины в их наиболее распространённой форме в русскоязычном мире; если чёткого эквивалента нет, сохраните оригинальный термин.
15
+ - Соблюдайте форматирование оригинала (markdown, списки, заголовки).
16
+ - Не добавляйте и не опускайте никакой информации по сравнению с оригиналом.
@@ -0,0 +1,16 @@
1
+ # Prompts-enska (sv)
2
+
3
+ ## Översättning
4
+
5
+ Lokalisera följande innehåll till svenska.
6
+
7
+ Översätt inte: lokalisera. Målet är inte att överföra ord från ett språk till ett annat, utan att producera en text som låter som om den skrevs direkt på svenska av en verklig person. En välokaliserad text lämnar inga spår av källspråket.
8
+
9
+ Skriv med en mänsklig röst: varm, tydlig och professionell. Undvik maskinspråk, krystade konstruktioner och meningar som låter som en instruktionsmanual. Om något inte flödar naturligt på svenska, skriv om det tills det gör det.
10
+
11
+ Riktlinjer att följa:
12
+
13
+ - Anpassa uttryck, metaforer och kulturella referenser till vad som skulle resonera naturligt i ett svenskspråkigt sammanhang.
14
+ - Använd tekniska termer i deras mest vedertagna form i den svenskspråkiga världen; om det inte finns något tydligt motsvarande, behåll den ursprungliga termen.
15
+ - Respektera originalets formatering (markdown, listor, rubriker).
16
+ - Lägg inte till eller utelämna någon information jämfört med originalet.
@@ -0,0 +1,16 @@
1
+ # Prompts-rkçe (tr)
2
+
3
+ ## Çeviri
4
+
5
+ Aşağıdaki içeriği Türkçeye yerelleştirin.
6
+
7
+ Çeviri yapmayın: yerelleştirin. Amaç, kelimeleri bir dilden diğerine aktarmak değil; sanki başından beri gerçek bir kişi tarafından Türkçe yazılmış gibi okunan bir metin üretmektir. İyi yerelleştirilmiş bir metinde kaynak dilin izi kalmaz.
8
+
9
+ İnsan sesiyle yazın: sıcak, açık ve profesyonel. Makine diline, zoraki ifadelere ve kullanım kılavuzu gibi ses çıkaran cümlelere yer vermeyin. Türkçede doğal akmayan bir şey varsa, akana kadar yeniden ifade edin.
10
+
11
+ Uyulması gereken yönergeler:
12
+
13
+ - İfadeleri, metaforları ve kültürel referansları Türkçe konuşan bir bağlamda doğal yankı uyandıracak şekilde uyarlayın.
14
+ - Teknik terimleri Türkçe konuşan dünyada en yaygın bilinen biçimleriyle kullanın; net bir karşılık yoksa orijinal terimi koruyun.
15
+ - Orijinalin biçimlendirmesine (markdown, listeler, başlıklar) uyun.
16
+ - Orijinale göre bilgi eklemeyin veya çıkarmayın.
@@ -0,0 +1,16 @@
1
+ # Prompts- (zh)
2
+
3
+ ## 翻译
4
+
5
+ 请将以下内容本地化为中文。
6
+
7
+ 不要翻译,要本地化。目标不是将词语从一种语言转移到另一种语言,而是产出一段读起来像是由真实的人直接用中文写成的文字。本地化质量上乘的文本不会留下任何源语言的痕迹。
8
+
9
+ 以人的声音书写:亲切、清晰、专业。避免机器腔、生硬的表达方式,以及任何听起来像操作手册的句子。如果某些内容用中文读起来不自然,就重新表达,直到自然为止。
10
+
11
+ 请遵循以下准则:
12
+
13
+ - 将表达方式、比喻和文化参照调整为在中文语境中能够自然共鸣的内容。
14
+ - 技术术语请使用在中文世界最广为人知的形式;如果没有明确的对应词,保留原文术语。
15
+ - 保留原文的格式(markdown、列表、标题)。
16
+ - 不得增加或删减原文中的任何信息。
package/prompts/seo.md ADDED
@@ -0,0 +1,58 @@
1
+ Aquí tienes la Mega-Prompt Final Evolucionada. Está diseñada para que sea un estándar universal: funciona para calculadoras, conversores, generadores o cualquier utilidad técnica.
2
+
3
+ El secreto de esta prompt es que no pide "un texto", sino una arquitectura de información que Google interpreta como contenido de alta calidad (E-E-A-T).
4
+
5
+ La Prompt: El Arquitecto de Utilidades SEO
6
+ Copia y pega lo siguiente:
7
+
8
+ Actúa como un Especialista SEO Senior experto en Programmatic SEO y Herramientas Online para el sector veterinario y de mascotas. Tu misión es redactar el contenido de apoyo para una página cuya función principal es una utilidad interactiva (calculadora, conversor o generador).
9
+
10
+ Objetivo: Crear un texto semántico de entre 400 y 600 palabras que aporte autoridad y contexto técnico sin distraer al usuario de la herramienta principal.
11
+
12
+ Tu "Caja de Herramientas" de componentes Astro:
13
+
14
+ SEOArticle: Contenedor raíz.
15
+
16
+ SEOTitle: Encabezados (level 2 para secciones, level 3 para subsecciones).
17
+
18
+ SEOList: Pasos de uso o beneficios.
19
+
20
+ SEOTable: Datos de referencia, rangos o comparativas.
21
+
22
+ SEOTip: El "Consejo del experto" (ideal para recomendaciones veterinarias).
23
+
24
+ SEOCard: Resaltar conceptos clave o fórmulas (ej: fórmula RER, etapas de vida).
25
+
26
+ SEOStats: Datos numéricos o estadísticas del sector (ej: % de mascotas con sobrepeso).
27
+
28
+ SEOGlossary: Definiciones técnicas rápidas (RER, DER, kcal/kg, etc.).
29
+
30
+ SEOProsCons: Ventajas y limitaciones de la herramienta.
31
+
32
+ SEOSummary: Resumen ejecutivo (TL;DR) para situar justo debajo del título.
33
+
34
+ SEODiagnostic: Checklist de validación o "Cuándo usar esta herramienta".
35
+
36
+ Reglas de Oro:
37
+
38
+ Prioriza la Escaneabilidad: El usuario quiere respuestas rápidas. Usa frases cortas y muchos componentes visuales.
39
+
40
+ Contexto Semántico: Incluye palabras clave LSI (latentes) y responde a la intención de búsqueda secundaria (el "qué pasa después" de usar la herramienta).
41
+
42
+ Autoridad Veterinaria: Cita fuentes reales (WSAVA, FEDIAF, AAHA) cuando sea relevante. El tono debe ser de experto en nutrición o medicina animal, no genérico.
43
+
44
+ Estructura Obligatoria:
45
+
46
+ Un <SEOSummary> al inicio con los puntos clave.
47
+
48
+ Una sección de "Cómo funciona" usando <SEOList> o <SEOCard>.
49
+
50
+ Una <SEOTable> con datos de referencia (ej: factores RER/DER, equivalencias de edad, valores calóricos).
51
+
52
+ Un <SEOTip> con valor añadido real (consejo del veterinario).
53
+
54
+ Un <SEODiagnostic> para ayudar al usuario a interpretar sus resultados.
55
+
56
+ Formato de Salida:
57
+ Devuelve exclusivamente el código para un archivo .astro. No incluyas explicaciones innecesarias fuera del código. Usa las props correctamente (ej: <SEOTitle level={2}>).
58
+
@@ -0,0 +1,33 @@
1
+ # Guide de Traduction Technique Élite (Français)
2
+
3
+ Tu es un expert natif français, spécialiste en médecine vétérinaire et passionné de bien-être animal. Ton objectif est de traduire les utilitaires de @jjlmoya/utils-pets avec une précision chirurgicale tout en dominant les résultats de recherche (SEO long-tail).
4
+
5
+ ## Ta Mission
6
+ Transformer un fichier technique TypeScript (EN/ES) en une version française qui semble avoir été écrite par un vétérinaire praticien né en France. Pas de traduction littérale, mais une adaptation naturelle et fluide du domaine vétérinaire.
7
+
8
+ ## Principes de Fer (Règles Strictes)
9
+ - **ZÉRO Commentaires**: Aucun commentaire dans le code (`//` ou `/* */`). Ton code doit être pur.
10
+ - **Intégrité TypeScript**: Préserve exactement la structure des objets et les types (ex: `PetAgeLocaleContent`, `PetRationLocaleContent`, imports, etc.).
11
+ - **SEO Long-Tail Dominant**: Les slugs doivent être riches en mots-clés stratégiques pour le marché français.
12
+ - Âge: `calculateur-age-chien-chat-annees-humaines`
13
+ - Ration: `calculateur-ration-quotidienne-chien-chat-nutrition`
14
+ - **Langage Naturel & Expert**: Utilise le "Vous" (formel). Emploie des termes techniques premium : *RER*, *DER*, *ration journalière*, *stade de vie*, *besoins énergétiques*, *condition corporelle*, *métabolisme basal*.
15
+
16
+ ## Vocabulaire Vétérinaire de Référence
17
+ - RER (Resting Energy Requirement) → Besoins Énergétiques au Repos (BER)
18
+ - DER (Daily Energy Requirement) → Besoins Énergétiques Journaliers (BEJ)
19
+ - Life stage → Stade de vie
20
+ - Puppy / Kitten → Chiot / Chaton
21
+ - Senior → Senior (ou Âgé)
22
+ - Sterilised → Stérilisé(e)
23
+ - Dry food → Croquettes / Aliment sec
24
+ - Wet food → Pâtée / Aliment humide
25
+ - Body weight → Poids corporel
26
+ - Caloric density → Densité calorique (kcal/kg)
27
+
28
+ ## Structure de la SEO
29
+ Dans la section `seo`, assure-toi que le contenu est riche (400-600 mots), structuré avec des balises HTML (`<strong>`, `<h3>`) et des listes qui apportent une réelle valeur ajoutée au propriétaire d'animal. Cite des sources reconnues : WSAVA, FEDIAF, AAHA.
30
+
31
+ ## Fichier Source à Traduire
32
+ [Colle ici le contenu source]
33
+
@@ -0,0 +1,27 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, readdirSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const libDir = dirname(fileURLToPath(import.meta.url));
6
+ const toolsDir = join(libDir, '../src/tool');
7
+
8
+ const inNodeModules = libDir.includes('node_modules');
9
+ if (!inNodeModules) process.exit(0);
10
+
11
+ const projectRoot = join(libDir, '../../../..');
12
+ const categoryKey = JSON.parse(readFileSync(join(libDir, '../package.json'), 'utf8')).name.replace('@jjlmoya/utils-', '');
13
+ const destDir = join(projectRoot, `public/styles/lib/${categoryKey}`);
14
+
15
+ mkdirSync(destDir, { recursive: true });
16
+
17
+ const tools = readdirSync(toolsDir, { withFileTypes: true }).filter(d => d.isDirectory());
18
+ for (const tool of tools) {
19
+ const toolDir = join(toolsDir, tool.name);
20
+ let files;
21
+ try { files = readdirSync(toolDir).filter(f => f.endsWith('.css')); }
22
+ catch { continue; }
23
+ for (const file of files) {
24
+ writeFileSync(join(destDir, file), readFileSync(join(toolDir, file)));
25
+ console.log(`[@jjlmoya/utils-${categoryKey}] copied ${file}`);
26
+ }
27
+ }
@@ -0,0 +1,9 @@
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { civicCategory } from './index';
4
+
5
+ const locale: string = Astro.props.locale ?? 'es';
6
+ const categoryContent = await civicCategory.i18n[locale as keyof typeof civicCategory.i18n]?.();
7
+ ---
8
+
9
+ {categoryContent && <SEORenderer content={{ locale, sections: categoryContent.seo }} />}
@@ -0,0 +1,21 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'oeffentliche-entscheidungen',
5
+ title: 'Werkzeuge für öffentliche Entscheidungen',
6
+ description: 'Offline-Werkzeuge, um öffentliche Entscheidungssysteme mit transparenten Annahmen zu untersuchen, beginnend mit einer Sitzverteilung bei Wahlen.',
7
+ seo: [
8
+ { type: 'title', text: 'Vertretung aus Stimmen modellieren', level: 2 },
9
+ { type: 'paragraph', html: 'Das erste bürgerschaftliche Werkzeug verwandelt Stimmen von Parteien oder Listen in eine nachvollziehbare Sitzverteilung. Methode, Sperrklausel, Quotientenfolge, Reststimmen und der Unterschied zwischen Stimmen und Sitzen bleiben sichtbar.' },
10
+ { type: 'title', text: 'Methoden und Annahmen vergleichen', level: 2 },
11
+ { type: 'paragraph', html: 'Civic-Modelle sind nützlich, wenn ihre Annahmen sichtbar bleiben. Vergleiche D\'Hondt, Sainte-Laguë und Hare mit denselben Eingaben und prüfe anschließend den Einfluss von Sperrklausel oder Wahlkreisgröße. Das Ergebnis ersetzt weder eine amtliche Auszählung noch das Recht eines bestimmten Landes.' },
12
+ { type: 'title', text: 'Privat im Browser', level: 2 },
13
+ { type: 'paragraph', html: 'Die Eingaben werden im Browser verarbeitet. Kein Konto ist erforderlich. Das Modell eignet sich für Unterricht, öffentliche Erklärungen und vorsichtige Was-wäre-wenn-Szenarien, ohne die eingegebenen Stimmen an einen Dienst zu senden.' },
14
+ { type: 'stats', items: [
15
+ { label: 'Werkzeuge', value: '1', icon: 'mdi:tools' },
16
+ { label: 'Methoden', value: '3', icon: 'mdi:scale-balance' },
17
+ { label: 'Modus', value: 'Offline', icon: 'mdi:lock-outline' },
18
+ { label: 'Status', value: 'Mehrsprachig', icon: 'mdi:translate' },
19
+ ] },
20
+ ],
21
+ };
@@ -0,0 +1,25 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ const slug = 'civic';
4
+ const title = 'Civic Decision Utilities';
5
+ const description = 'Offline tools for exploring public decision systems with transparent assumptions, starting with an election seat apportionment model.';
6
+
7
+ export const content: CategoryLocaleContent = {
8
+ slug,
9
+ title,
10
+ description,
11
+ seo: [
12
+ { type: 'title', text: 'Model Representation from Vote Totals', level: 2 },
13
+ { type: 'paragraph', html: 'The first civic utility turns party or list vote totals into an auditable seat allocation. It exposes the method, eligibility threshold, quotient order, remainder order, and the difference between votes and seats so a reader can follow the model step by step.' },
14
+ { type: 'title', text: 'Compare Methods and Assumptions', level: 2 },
15
+ { type: 'paragraph', html: 'Civic models are useful when their assumptions stay visible. Compare D\'Hondt, Sainte-Lague, and Hare largest remainder with the same inputs, then test how a threshold or district magnitude changes the result. The output is educational and cannot replace an official count or jurisdiction specific legal rule.' },
16
+ { type: 'title', text: 'Private by Default', level: 2 },
17
+ { type: 'paragraph', html: 'Inputs are processed in the browser. No account is needed, and the model is designed for classroom exercises, public explanation, and careful what if analysis without sending vote scenarios to a service.' },
18
+ { type: 'stats', items: [
19
+ { label: 'Tools', value: '1', icon: 'mdi:tools' },
20
+ { label: 'Methods', value: '3', icon: 'mdi:scale-balance' },
21
+ { label: 'Mode', value: 'Offline', icon: 'mdi:lock-outline' },
22
+ { label: 'Status', value: 'Multilingual', icon: 'mdi:translate' },
23
+ ] },
24
+ ],
25
+ };
@@ -0,0 +1,21 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'civico',
5
+ title: 'Herramientas para decisiones públicas',
6
+ description: 'Herramientas offline para explorar sistemas de decisión pública con supuestos transparentes, empezando por un modelo de reparto de escaños electorales.',
7
+ seo: [
8
+ { type: 'title', text: 'Modelar la representación desde los votos', level: 2 },
9
+ { type: 'paragraph', html: 'La primera utilidad cívica convierte los votos de partidos o listas en un reparto de escaños auditable. Expone el método, el umbral, el orden de cocientes, el orden de restos y la diferencia entre votos y escaños para seguir el modelo paso a paso.' },
10
+ { type: 'title', text: 'Comparar métodos y supuestos', level: 2 },
11
+ { type: 'paragraph', html: 'Los modelos cívicos son útiles cuando sus supuestos permanecen visibles. Compara D\'Hondt, Sainte-Laguë y resto mayor de Hare con las mismas entradas y prueba después cómo cambian los resultados con un umbral o una magnitud de distrito. La salida no sustituye un escrutinio oficial ni la norma electoral de una jurisdicción.' },
12
+ { type: 'title', text: 'Privacidad por defecto', level: 2 },
13
+ { type: 'paragraph', html: 'Las entradas se procesan en el navegador. No hace falta crear una cuenta y el modelo está pensado para clases, explicaciones públicas y análisis de escenarios sin enviar los supuestos electorales a un servicio.' },
14
+ { type: 'stats', items: [
15
+ { label: 'Herramientas', value: '1', icon: 'mdi:tools' },
16
+ { label: 'Métodos', value: '3', icon: 'mdi:scale-balance' },
17
+ { label: 'Modo', value: 'Offline', icon: 'mdi:lock-outline' },
18
+ { label: 'Estado', value: 'Multilingüe', icon: 'mdi:translate' },
19
+ ] },
20
+ ],
21
+ };
@@ -0,0 +1,21 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'civique',
5
+ title: 'Outils de décision publique',
6
+ description: 'Des outils hors ligne pour explorer les systèmes de décision publique avec des hypothèses transparentes, en commençant par un modèle de répartition des sièges électoraux.',
7
+ seo: [
8
+ { type: 'title', text: 'Modéliser la représentation à partir des voix', level: 2 },
9
+ { type: 'paragraph', html: 'Le premier outil civique transforme les voix des partis ou des listes en une répartition des sièges vérifiable. La méthode, le seuil, l\'ordre des quotients, l\'ordre des restes et l\'écart entre voix et sièges restent visibles à chaque étape.' },
10
+ { type: 'title', text: 'Comparer les méthodes et les hypothèses', level: 2 },
11
+ { type: 'paragraph', html: 'Les modèles civiques sont utiles lorsque leurs hypothèses restent lisibles. Comparez D\'Hondt, Sainte-Laguë et le quotient de Hare avec les mêmes données, puis observez l\'effet d\'un seuil ou de la magnitude des circonscriptions. Le résultat ne remplace ni un dépouillement officiel ni le droit électoral applicable.' },
12
+ { type: 'title', text: 'Privé par défaut', level: 2 },
13
+ { type: 'paragraph', html: 'Les données sont traitées dans le navigateur. Aucun compte n\'est nécessaire. Le modèle convient aux cours, aux explications publiques et aux scénarios "et si ?" sans transmettre les hypothèses de vote à un service.' },
14
+ { type: 'stats', items: [
15
+ { label: 'Outils', value: '1', icon: 'mdi:tools' },
16
+ { label: 'Méthodes', value: '3', icon: 'mdi:scale-balance' },
17
+ { label: 'Mode', value: 'Hors ligne', icon: 'mdi:lock-outline' },
18
+ { label: 'État', value: 'Multilingue', icon: 'mdi:translate' },
19
+ ] },
20
+ ],
21
+ };
@@ -0,0 +1,21 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'sipil',
5
+ title: 'Alat untuk keputusan publik',
6
+ description: 'Alat offline untuk mengeksplorasi sistem keputusan publik dengan asumsi yang transparan, dimulai dari model pembagian kursi pemilu.',
7
+ seo: [
8
+ { type: 'title', text: 'Memodelkan perwakilan dari jumlah suara', level: 2 },
9
+ { type: 'paragraph', html: 'Alat sipil pertama mengubah jumlah suara partai atau daftar menjadi pembagian kursi yang dapat ditelusuri. Metode, ambang batas, urutan hasil bagi, urutan sisa, serta perbedaan antara suara dan kursi terlihat langkah demi langkah.' },
10
+ { type: 'title', text: 'Bandingkan metode dan asumsi', level: 2 },
11
+ { type: 'paragraph', html: 'Model sipil berguna jika asumsinya tetap terlihat. Bandingkan D\'Hondt, Sainte-Laguë, dan sisa terbesar Hare dengan masukan yang sama, lalu uji pengaruh ambang batas atau besarnya daerah pemilihan. Hasilnya bersifat edukatif dan bukan pengganti penghitungan resmi atau aturan hukum setempat.' },
12
+ { type: 'title', text: 'Privat secara bawaan', level: 2 },
13
+ { type: 'paragraph', html: 'Masukan diproses di peramban. Tidak diperlukan akun. Model ini cocok untuk pembelajaran, penjelasan publik, dan analisis skenario tanpa mengirimkan asumsi suara ke layanan lain.' },
14
+ { type: 'stats', items: [
15
+ { label: 'Alat', value: '1', icon: 'mdi:tools' },
16
+ { label: 'Metode', value: '3', icon: 'mdi:scale-balance' },
17
+ { label: 'Mode', value: 'Offline', icon: 'mdi:lock-outline' },
18
+ { label: 'Status', value: 'Multibahasa', icon: 'mdi:translate' },
19
+ ] },
20
+ ],
21
+ };
@@ -0,0 +1,21 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'civico',
5
+ title: 'Strumenti per le decisioni pubbliche',
6
+ description: 'Strumenti offline per esplorare i sistemi decisionali pubblici con ipotesi trasparenti, a partire da un modello di ripartizione dei seggi elettorali.',
7
+ seo: [
8
+ { type: 'title', text: 'Modellare la rappresentanza dai voti', level: 2 },
9
+ { type: 'paragraph', html: 'Il primo strumento civico trasforma i voti dei partiti o delle liste in una ripartizione dei seggi verificabile. Metodo, soglia, ordine dei quozienti, ordine dei resti e differenza tra voti e seggi restano visibili durante tutto il calcolo.' },
10
+ { type: 'title', text: 'Confrontare metodi e ipotesi', level: 2 },
11
+ { type: 'paragraph', html: 'I modelli civici sono utili quando le loro ipotesi restano visibili. Confronta D\'Hondt, Sainte-Laguë e quoziente Hare con gli stessi dati, poi prova l\'effetto di una soglia o dell\'ampiezza dei collegi. Il risultato è didattico e non sostituisce uno scrutinio ufficiale né la legge elettorale applicabile.' },
12
+ { type: 'title', text: 'Privato per impostazione predefinita', level: 2 },
13
+ { type: 'paragraph', html: 'I dati vengono elaborati nel browser. Non serve un account. Il modello è pensato per lezioni, spiegazioni pubbliche e scenari ipotetici senza inviare le ipotesi di voto a un servizio.' },
14
+ { type: 'stats', items: [
15
+ { label: 'Strumenti', value: '1', icon: 'mdi:tools' },
16
+ { label: 'Metodi', value: '3', icon: 'mdi:scale-balance' },
17
+ { label: 'Modalità', value: 'Offline', icon: 'mdi:lock-outline' },
18
+ { label: 'Stato', value: 'Multilingue', icon: 'mdi:translate' },
19
+ ] },
20
+ ],
21
+ };
@@ -0,0 +1,21 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'civic',
5
+ title: '公共意思決定ツール',
6
+ description: '透明な前提で公共の意思決定制度を調べるオフラインツールです。まずは選挙の議席配分モデルから始めます。',
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: '入力はブラウザー内で処理されます。アカウントは必要ありません。授業や公開説明、得票シナリオの慎重な比較に使え、入力した前提を外部サービスへ送信しません。' },
14
+ { type: 'stats', items: [
15
+ { label: 'ツール', value: '1', icon: 'mdi:tools' },
16
+ { label: '方式', value: '3', icon: 'mdi:scale-balance' },
17
+ { label: 'モード', value: 'オフライン', icon: 'mdi:lock-outline' },
18
+ { label: '状態', value: '多言語対応', icon: 'mdi:translate' },
19
+ ] },
20
+ ],
21
+ };
@@ -0,0 +1,21 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'civic',
5
+ title: '공공 의사결정 도구',
6
+ description: '투명한 가정을 바탕으로 공공 의사결정 제도를 살펴보는 오프라인 도구입니다. 선거 의석 배분 모델부터 시작합니다.',
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: '입력은 브라우저에서 처리됩니다. 계정이 필요하지 않습니다. 수업, 공공 설명, 득표 시나리오의 신중한 비교에 사용할 수 있으며 입력한 가정을 외부 서비스로 보내지 않습니다.' },
14
+ { type: 'stats', items: [
15
+ { label: '도구', value: '1', icon: 'mdi:tools' },
16
+ { label: '방식', value: '3', icon: 'mdi:scale-balance' },
17
+ { label: '모드', value: '오프라인', icon: 'mdi:lock-outline' },
18
+ { label: '상태', value: '다국어', icon: 'mdi:translate' },
19
+ ] },
20
+ ],
21
+ };
@@ -0,0 +1,21 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'civiel',
5
+ title: 'Hulpmiddelen voor publieke besluitvorming',
6
+ description: 'Offline hulpmiddelen om publieke besluitvormingssystemen met transparante aannames te onderzoeken, te beginnen met een model voor de verdeling van verkiezingszetels.',
7
+ seo: [
8
+ { type: 'title', text: 'Vertegenwoordiging modelleren vanuit stemmen', level: 2 },
9
+ { type: 'paragraph', html: 'De eerste civiele tool zet stemmen van partijen of lijsten om in een controleerbare zetelverdeling. Methode, kiesdrempel, quotiëntenvolgorde, restvolgorde en het verschil tussen stemmen en zetels blijven stap voor stap zichtbaar.' },
10
+ { type: 'title', text: 'Methoden en aannames vergelijken', level: 2 },
11
+ { type: 'paragraph', html: 'Civiele modellen zijn nuttig wanneer hun aannames zichtbaar blijven. Vergelijk D\'Hondt, Sainte-Laguë en Hare met dezelfde invoer en test daarna het effect van een drempel of districtsgrootte. De uitkomst is educatief en vervangt geen officiële telling of specifieke kieswet.' },
12
+ { type: 'title', text: 'Standaard privé', level: 2 },
13
+ { type: 'paragraph', html: 'Invoer wordt in de browser verwerkt. Een account is niet nodig. Het model is bedoeld voor lessen, publieke uitleg en voorzichtige wat-als-scenario\'s zonder stemsituaties naar een dienst te sturen.' },
14
+ { type: 'stats', items: [
15
+ { label: 'Tools', value: '1', icon: 'mdi:tools' },
16
+ { label: 'Methoden', value: '3', icon: 'mdi:scale-balance' },
17
+ { label: 'Modus', value: 'Offline', icon: 'mdi:lock-outline' },
18
+ { label: 'Status', value: 'Meertalig', icon: 'mdi:translate' },
19
+ ] },
20
+ ],
21
+ };
@@ -0,0 +1,21 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'obywatelskie',
5
+ title: 'Narzędzia do decyzji publicznych',
6
+ description: 'Narzędzia offline do badania publicznych systemów decyzyjnych przy przejrzystych założeniach, zaczynając od modelu podziału mandatów wyborczych.',
7
+ seo: [
8
+ { type: 'title', text: 'Modelowanie reprezentacji na podstawie głosów', level: 2 },
9
+ { type: 'paragraph', html: 'Pierwsze narzędzie obywatelskie zamienia głosy oddane na partie lub listy w możliwy do prześledzenia podział mandatów. Metoda, próg, kolejność ilorazów, kolejność reszt oraz różnica między głosami i mandatami są widoczne na każdym etapie.' },
10
+ { type: 'title', text: 'Porównywanie metod i założeń', level: 2 },
11
+ { type: 'paragraph', html: 'Modele obywatelskie są przydatne, gdy ich założenia pozostają jawne. Porównaj D\'Hondta, Sainte-Laguë i metodę największych reszt Hare dla tych samych danych, a następnie sprawdź wpływ progu lub wielkości okręgu. Wynik ma charakter edukacyjny i nie zastępuje oficjalnego liczenia ani konkretnego prawa wyborczego.' },
12
+ { type: 'title', text: 'Prywatność domyślnie', level: 2 },
13
+ { type: 'paragraph', html: 'Dane wejściowe są przetwarzane w przeglądarce. Konto nie jest potrzebne. Model sprawdzi się na zajęciach, w wyjaśnieniach publicznych i w ostrożnych scenariuszach "co jeśli?", bez wysyłania założeń do usługi.' },
14
+ { type: 'stats', items: [
15
+ { label: 'Narzędzia', value: '1', icon: 'mdi:tools' },
16
+ { label: 'Metody', value: '3', icon: 'mdi:scale-balance' },
17
+ { label: 'Tryb', value: 'Offline', icon: 'mdi:lock-outline' },
18
+ { label: 'Status', value: 'Wielojęzyczne', icon: 'mdi:translate' },
19
+ ] },
20
+ ],
21
+ };
@@ -0,0 +1,21 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'civico',
5
+ title: 'Ferramentas para decisões públicas',
6
+ description: 'Ferramentas offline para explorar sistemas de decisão pública com pressupostos transparentes, começando por um modelo de distribuição de lugares eleitorais.',
7
+ seo: [
8
+ { type: 'title', text: 'Modelar a representação a partir dos votos', level: 2 },
9
+ { type: 'paragraph', html: 'A primeira ferramenta cívica transforma os votos de partidos ou listas numa distribuição de lugares que pode ser auditada. O método, o limiar, a ordem dos quocientes, a ordem dos restos e a diferença entre votos e lugares ficam visíveis passo a passo.' },
10
+ { type: 'title', text: 'Comparar métodos e pressupostos', level: 2 },
11
+ { type: 'paragraph', html: 'Os modelos cívicos são úteis quando os seus pressupostos permanecem claros. Compare D\'Hondt, Sainte-Laguë e o maior resto de Hare com os mesmos dados e teste depois o efeito de um limiar ou da dimensão do círculo. O resultado é educativo e não substitui uma contagem oficial nem a lei eleitoral aplicável.' },
12
+ { type: 'title', text: 'Privado por defeito', level: 2 },
13
+ { type: 'paragraph', html: 'Os dados são processados no navegador. Não é necessária uma conta. O modelo serve para aulas, explicações públicas e cenários hipotéticos sem enviar os pressupostos de votação para um serviço.' },
14
+ { type: 'stats', items: [
15
+ { label: 'Ferramentas', value: '1', icon: 'mdi:tools' },
16
+ { label: 'Métodos', value: '3', icon: 'mdi:scale-balance' },
17
+ { label: 'Modo', value: 'Offline', icon: 'mdi:lock-outline' },
18
+ { label: 'Estado', value: 'Multilingue', icon: 'mdi:translate' },
19
+ ] },
20
+ ],
21
+ };
@@ -0,0 +1,21 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'obshchestvennye',
5
+ title: 'Инструменты для общественных решений',
6
+ description: 'Офлайн-инструменты для изучения систем общественных решений с прозрачными допущениями, начиная с модели распределения мест на выборах.',
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: 'Вводимые данные обрабатываются в браузере. Учётная запись не нужна. Модель подходит для занятий, публичных объяснений и осторожного анализа сценариев без отправки предположений о голосах в сторонний сервис.' },
14
+ { type: 'stats', items: [
15
+ { label: 'Инструменты', value: '1', icon: 'mdi:tools' },
16
+ { label: 'Методы', value: '3', icon: 'mdi:scale-balance' },
17
+ { label: 'Режим', value: 'Офлайн', icon: 'mdi:lock-outline' },
18
+ { label: 'Статус', value: 'Мультиязычный', icon: 'mdi:translate' },
19
+ ] },
20
+ ],
21
+ };
@@ -0,0 +1,21 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'samhall',
5
+ title: 'Verktyg för offentliga beslut',
6
+ description: 'Offlineverktyg för att utforska offentliga beslutssystem med tydliga antaganden, med början i en modell för fördelning av valmandat.',
7
+ seo: [
8
+ { type: 'title', text: 'Modellera representation från röster', level: 2 },
9
+ { type: 'paragraph', html: 'Det första samhällsverktyget omvandlar röster på partier eller listor till en fördelning av mandat som går att följa. Metod, spärr, kvotordning, restordning och skillnaden mellan röster och mandat visas steg för steg.' },
10
+ { type: 'title', text: 'Jämför metoder och antaganden', level: 2 },
11
+ { type: 'paragraph', html: 'Samhällsmodeller är användbara när antagandena är synliga. Jämför D\'Hondt, Sainte-Laguë och Hares största rest med samma indata och testa sedan hur en spärr eller valkretsens storlek påverkar resultatet. Utfallet är pedagogiskt och ersätter inte en officiell räkning eller en viss vallag.' },
12
+ { type: 'title', text: 'Privat som standard', level: 2 },
13
+ { type: 'paragraph', html: 'Indata behandlas i webbläsaren. Inget konto behövs. Modellen passar undervisning, offentlig förklaring och försiktiga tänk om-scenarier utan att skicka röstantaganden till en tjänst.' },
14
+ { type: 'stats', items: [
15
+ { label: 'Verktyg', value: '1', icon: 'mdi:tools' },
16
+ { label: 'Metoder', value: '3', icon: 'mdi:scale-balance' },
17
+ { label: 'Läge', value: 'Offline', icon: 'mdi:lock-outline' },
18
+ { label: 'Status', value: 'Flerspråkigt', icon: 'mdi:translate' },
19
+ ] },
20
+ ],
21
+ };
@@ -0,0 +1,21 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
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.',
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.' },
14
+ { type: 'stats', items: [
15
+ { label: 'Araç', value: '1', icon: 'mdi:tools' },
16
+ { label: 'Yöntem', value: '3', icon: 'mdi:scale-balance' },
17
+ { label: 'Mod', value: 'Çevrimdışı', icon: 'mdi:lock-outline' },
18
+ { label: 'Durum', value: 'Çok dilli', icon: 'mdi:translate' },
19
+ ] },
20
+ ],
21
+ };