@jjlmoya/utils-tabletop 1.3.0 → 1.5.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 (171) hide show
  1. package/package.json +1 -1
  2. package/src/category/TabletopCategorySEO.astro +2 -7
  3. package/src/category/i18n/en.ts +4 -4
  4. package/src/category/i18n/ru.ts +2 -2
  5. package/src/category/i18n/zh.ts +8 -8
  6. package/src/category/index.ts +2 -1
  7. package/src/entries.ts +14 -1
  8. package/src/index.ts +1 -0
  9. package/src/pages/[locale]/[slug].astro +7 -6
  10. package/src/tests/locale_completeness.test.ts +1 -1
  11. package/src/tests/no_em_dash.test.ts +47 -0
  12. package/src/tests/seo_length.test.ts +1 -0
  13. package/src/tests/title_quality.test.ts +1 -1
  14. package/src/tests/tool_validation.test.ts +1 -1
  15. package/src/tool/board-game-timer/modules/ColorHelper.ts +2 -2
  16. package/src/tool/board-game-timer/modules/DuelViewManager.ts +4 -4
  17. package/src/tool/board-game-timer/modules/GameRunner.ts +1 -1
  18. package/src/tool/board-game-timer/modules/MultiplayerViewManager.ts +2 -2
  19. package/src/tool/board-game-timer/modules/SetupPanelManager.ts +1 -1
  20. package/src/tool/board-game-timer/modules/TimerEngine.ts +1 -1
  21. package/src/tool/board-game-timer/types.ts +1 -1
  22. package/src/tool/decision-wheel/audio.ts +78 -0
  23. package/src/tool/decision-wheel/bibliography.astro +16 -0
  24. package/src/tool/decision-wheel/bibliography.ts +8 -0
  25. package/src/tool/decision-wheel/client.ts +226 -0
  26. package/src/tool/decision-wheel/component.astro +48 -0
  27. package/src/tool/decision-wheel/components/SegmentEditor.astro +22 -0
  28. package/src/tool/decision-wheel/components/SpinHistory.astro +17 -0
  29. package/src/tool/decision-wheel/components/SpinResult.astro +13 -0
  30. package/src/tool/decision-wheel/components/WheelCanvas.astro +23 -0
  31. package/src/tool/decision-wheel/decision-wheel.css +632 -0
  32. package/src/tool/decision-wheel/drawing.ts +144 -0
  33. package/src/tool/decision-wheel/entry.ts +48 -0
  34. package/src/tool/decision-wheel/i18n/de.ts +97 -0
  35. package/src/tool/decision-wheel/i18n/en.ts +251 -0
  36. package/src/tool/decision-wheel/i18n/es.ts +93 -0
  37. package/src/tool/decision-wheel/i18n/fr.ts +93 -0
  38. package/src/tool/decision-wheel/i18n/id.ts +93 -0
  39. package/src/tool/decision-wheel/i18n/it.ts +93 -0
  40. package/src/tool/decision-wheel/i18n/ja.ts +93 -0
  41. package/src/tool/decision-wheel/i18n/ko.ts +93 -0
  42. package/src/tool/decision-wheel/i18n/nl.ts +93 -0
  43. package/src/tool/decision-wheel/i18n/pl.ts +93 -0
  44. package/src/tool/decision-wheel/i18n/pt.ts +93 -0
  45. package/src/tool/decision-wheel/i18n/ru.ts +75 -0
  46. package/src/tool/decision-wheel/i18n/sv.ts +93 -0
  47. package/src/tool/decision-wheel/i18n/tr.ts +93 -0
  48. package/src/tool/decision-wheel/i18n/zh.ts +93 -0
  49. package/src/tool/decision-wheel/index.ts +9 -0
  50. package/src/tool/decision-wheel/logic.test.ts +163 -0
  51. package/src/tool/decision-wheel/logic.ts +145 -0
  52. package/src/tool/decision-wheel/seo.astro +16 -0
  53. package/src/tool/decision-wheel/types.ts +14 -0
  54. package/src/tool/decision-wheel/ui.ts +121 -0
  55. package/src/tool/dice-roller-simulator/client.ts +2 -2
  56. package/src/tool/dice-roller-simulator/i18n/ru.ts +8 -8
  57. package/src/tool/dice-roller-simulator/modules/history.ts +1 -1
  58. package/src/tool/fantasy-runes-translator/bibliography.astro +16 -0
  59. package/src/tool/fantasy-runes-translator/bibliography.ts +16 -0
  60. package/src/tool/fantasy-runes-translator/client.ts +205 -0
  61. package/src/tool/fantasy-runes-translator/component.astro +95 -0
  62. package/src/tool/fantasy-runes-translator/entry.ts +56 -0
  63. package/src/tool/fantasy-runes-translator/fantasy-runes-translator.css +712 -0
  64. package/src/tool/fantasy-runes-translator/i18n/de.ts +139 -0
  65. package/src/tool/fantasy-runes-translator/i18n/en.ts +139 -0
  66. package/src/tool/fantasy-runes-translator/i18n/es.ts +139 -0
  67. package/src/tool/fantasy-runes-translator/i18n/fr.ts +139 -0
  68. package/src/tool/fantasy-runes-translator/i18n/id.ts +139 -0
  69. package/src/tool/fantasy-runes-translator/i18n/it.ts +139 -0
  70. package/src/tool/fantasy-runes-translator/i18n/ja.ts +139 -0
  71. package/src/tool/fantasy-runes-translator/i18n/ko.ts +139 -0
  72. package/src/tool/fantasy-runes-translator/i18n/nl.ts +139 -0
  73. package/src/tool/fantasy-runes-translator/i18n/pl.ts +139 -0
  74. package/src/tool/fantasy-runes-translator/i18n/pt.ts +139 -0
  75. package/src/tool/fantasy-runes-translator/i18n/ru.ts +139 -0
  76. package/src/tool/fantasy-runes-translator/i18n/sv.ts +139 -0
  77. package/src/tool/fantasy-runes-translator/i18n/tr.ts +139 -0
  78. package/src/tool/fantasy-runes-translator/i18n/zh.ts +139 -0
  79. package/src/tool/fantasy-runes-translator/index.ts +9 -0
  80. package/src/tool/fantasy-runes-translator/logic.ts +174 -0
  81. package/src/tool/fantasy-runes-translator/seo.astro +16 -0
  82. package/src/tool/fantasy-runes-translator/types.ts +26 -0
  83. package/src/tool/first-player-selector/bibliography.astro +16 -0
  84. package/src/tool/first-player-selector/bibliography.ts +12 -0
  85. package/src/tool/first-player-selector/client.ts +197 -0
  86. package/src/tool/first-player-selector/component.astro +77 -0
  87. package/src/tool/first-player-selector/entry.ts +42 -0
  88. package/src/tool/first-player-selector/first-player-selector.css +303 -0
  89. package/src/tool/first-player-selector/i18n/de.ts +226 -0
  90. package/src/tool/first-player-selector/i18n/en.ts +226 -0
  91. package/src/tool/first-player-selector/i18n/es.ts +226 -0
  92. package/src/tool/first-player-selector/i18n/fr.ts +227 -0
  93. package/src/tool/first-player-selector/i18n/id.ts +226 -0
  94. package/src/tool/first-player-selector/i18n/it.ts +227 -0
  95. package/src/tool/first-player-selector/i18n/ja.ts +227 -0
  96. package/src/tool/first-player-selector/i18n/ko.ts +227 -0
  97. package/src/tool/first-player-selector/i18n/nl.ts +226 -0
  98. package/src/tool/first-player-selector/i18n/pl.ts +227 -0
  99. package/src/tool/first-player-selector/i18n/pt.ts +226 -0
  100. package/src/tool/first-player-selector/i18n/ru.ts +227 -0
  101. package/src/tool/first-player-selector/i18n/sv.ts +226 -0
  102. package/src/tool/first-player-selector/i18n/tr.ts +226 -0
  103. package/src/tool/first-player-selector/i18n/zh.ts +227 -0
  104. package/src/tool/first-player-selector/index.ts +9 -0
  105. package/src/tool/first-player-selector/modules/AnimationEffects.ts +256 -0
  106. package/src/tool/first-player-selector/modules/SelectionEngine.ts +249 -0
  107. package/src/tool/first-player-selector/modules/SoundEffects.ts +87 -0
  108. package/src/tool/first-player-selector/modules/TouchTracker.ts +148 -0
  109. package/src/tool/first-player-selector/seo.astro +16 -0
  110. package/src/tool/first-player-selector/types.ts +21 -0
  111. package/src/tool/rpg-initiative-tracker/bibliography.astro +16 -0
  112. package/src/tool/rpg-initiative-tracker/bibliography.ts +12 -0
  113. package/src/tool/rpg-initiative-tracker/client.ts +201 -0
  114. package/src/tool/rpg-initiative-tracker/component.astro +23 -0
  115. package/src/tool/rpg-initiative-tracker/components/AddForm.astro +49 -0
  116. package/src/tool/rpg-initiative-tracker/components/CombatantList.astro +16 -0
  117. package/src/tool/rpg-initiative-tracker/components/TurnControls.astro +28 -0
  118. package/src/tool/rpg-initiative-tracker/entry.ts +50 -0
  119. package/src/tool/rpg-initiative-tracker/i18n/de.ts +266 -0
  120. package/src/tool/rpg-initiative-tracker/i18n/en.ts +263 -0
  121. package/src/tool/rpg-initiative-tracker/i18n/es.ts +264 -0
  122. package/src/tool/rpg-initiative-tracker/i18n/fr.ts +263 -0
  123. package/src/tool/rpg-initiative-tracker/i18n/id.ts +263 -0
  124. package/src/tool/rpg-initiative-tracker/i18n/it.ts +263 -0
  125. package/src/tool/rpg-initiative-tracker/i18n/ja.ts +263 -0
  126. package/src/tool/rpg-initiative-tracker/i18n/ko.ts +263 -0
  127. package/src/tool/rpg-initiative-tracker/i18n/nl.ts +263 -0
  128. package/src/tool/rpg-initiative-tracker/i18n/pl.ts +264 -0
  129. package/src/tool/rpg-initiative-tracker/i18n/pt.ts +263 -0
  130. package/src/tool/rpg-initiative-tracker/i18n/ru.ts +264 -0
  131. package/src/tool/rpg-initiative-tracker/i18n/sv.ts +263 -0
  132. package/src/tool/rpg-initiative-tracker/i18n/tr.ts +263 -0
  133. package/src/tool/rpg-initiative-tracker/i18n/zh.ts +263 -0
  134. package/src/tool/rpg-initiative-tracker/index.ts +9 -0
  135. package/src/tool/rpg-initiative-tracker/logic.test.ts +121 -0
  136. package/src/tool/rpg-initiative-tracker/logic.ts +122 -0
  137. package/src/tool/rpg-initiative-tracker/rpg-initiative-tracker.css +617 -0
  138. package/src/tool/rpg-initiative-tracker/seo.astro +16 -0
  139. package/src/tool/rpg-initiative-tracker/types.ts +15 -0
  140. package/src/tool/score-tracker/bibliography.astro +16 -0
  141. package/src/tool/score-tracker/bibliography.ts +12 -0
  142. package/src/tool/score-tracker/client.ts +248 -0
  143. package/src/tool/score-tracker/component.astro +34 -0
  144. package/src/tool/score-tracker/components/Leaderboard.astro +19 -0
  145. package/src/tool/score-tracker/components/PlayerSetup.astro +32 -0
  146. package/src/tool/score-tracker/components/RoundScoring.astro +22 -0
  147. package/src/tool/score-tracker/components/ScoreHistory.astro +25 -0
  148. package/src/tool/score-tracker/entry.ts +57 -0
  149. package/src/tool/score-tracker/i18n/de.ts +278 -0
  150. package/src/tool/score-tracker/i18n/en.ts +278 -0
  151. package/src/tool/score-tracker/i18n/es.ts +251 -0
  152. package/src/tool/score-tracker/i18n/fr.ts +278 -0
  153. package/src/tool/score-tracker/i18n/id.ts +278 -0
  154. package/src/tool/score-tracker/i18n/it.ts +278 -0
  155. package/src/tool/score-tracker/i18n/ja.ts +278 -0
  156. package/src/tool/score-tracker/i18n/ko.ts +278 -0
  157. package/src/tool/score-tracker/i18n/nl.ts +278 -0
  158. package/src/tool/score-tracker/i18n/pl.ts +278 -0
  159. package/src/tool/score-tracker/i18n/pt.ts +278 -0
  160. package/src/tool/score-tracker/i18n/ru.ts +278 -0
  161. package/src/tool/score-tracker/i18n/sv.ts +278 -0
  162. package/src/tool/score-tracker/i18n/tr.ts +278 -0
  163. package/src/tool/score-tracker/i18n/zh.ts +278 -0
  164. package/src/tool/score-tracker/index.ts +9 -0
  165. package/src/tool/score-tracker/logic.test.ts +83 -0
  166. package/src/tool/score-tracker/logic.ts +122 -0
  167. package/src/tool/score-tracker/score-tracker.css +1029 -0
  168. package/src/tool/score-tracker/seo.astro +16 -0
  169. package/src/tool/score-tracker/types.ts +17 -0
  170. package/src/tools.ts +10 -0
  171. package/src/types.ts +1 -2
@@ -0,0 +1,93 @@
1
+ import { bibliography } from '../bibliography';
2
+ import type { DecisionWheelLocaleContent } from '../entry';
3
+
4
+ export const content: DecisionWheelLocaleContent = {
5
+ slug: 'roleta-de-decisoes',
6
+ title: 'Roleta de Decisões: Seletor Aleatório e Gerador de Opções',
7
+ description: 'Gire uma roleta de decisões personalizável para jogos de tabuleiro. Adicione e edite fatias com pesos para definir resultados.',
8
+ ui: {
9
+ title: 'Roleta de Decisões',
10
+ spinButton: 'Girar a Roleta',
11
+ clearHistory: 'Limpar Histórico',
12
+ resultHeading: 'Resultado',
13
+ historyTitle: 'Histórico de Giros',
14
+ noHistory: 'Nenhum giro. Clique em Girar a Roleta para começar.',
15
+ addSegmentLabel: 'Adicionar Fatia',
16
+ removeSegmentLabel: 'Remover',
17
+ segmentLabelPlaceholder: 'Rótulo',
18
+ presetLabel: 'Predefinições',
19
+ presetYesNo: 'Sim ou Não',
20
+ presetNumbers: 'Números 1-6',
21
+ presetActions: 'Ações',
22
+ presetCustom: 'Personalizado',
23
+ presetD20: 'D20',
24
+ presetAlignment: 'Alinhamento',
25
+ presetLoot: 'Raridade do Saque',
26
+ weightLabel: 'Peso',
27
+ spinAgain: 'Girar Novamente',
28
+ noSegments: 'Adicione fatias à roleta antes de girar.',
29
+ },
30
+ seo: [
31
+ { type: 'title', text: 'Roleta de Decisões para Jogos de Tabuleiro e Atividades', level: 2 },
32
+ { type: 'paragraph', html: 'A roleta de decisões é uma ferramenta versátil para jogos de mesa. Personalize as fatias com seus textos e cores e gire.' },
33
+ {
34
+ type: 'stats',
35
+ items: [
36
+ { value: '16', label: 'Adicionar Fatia' },
37
+ { value: '7', label: 'Predefinições' },
38
+ { value: '10', label: 'Histórico de Giros' },
39
+ ],
40
+ columns: 3,
41
+ },
42
+ { type: 'title', text: 'Como Usar a Roleta de Decisões', level: 2 },
43
+ { type: 'paragraph', html: 'Selecione uma configuração padrão ou crie fatias com pesos diferentes para alterar as chances de vitória.' },
44
+ ],
45
+ faq: [
46
+ {
47
+ question: 'Como funciona a roleta de decisões?',
48
+ answer: 'Insira suas opções, ajuste os pesos caso queira variar a probabilidade de sorteio e clique no botão Girar.',
49
+ },
50
+ {
51
+ question: 'Posso personalizar cores e nomes?',
52
+ answer: 'Sim, você pode adicionar fatias, mudar os nomes de exibição, escolher cores e definir pesos de 1 a 5.',
53
+ },
54
+ ],
55
+ bibliography,
56
+ howTo: [
57
+ {
58
+ name: 'Montar as Fatias',
59
+ text: 'Carregue uma predefinição rápida ou crie suas próprias fatias especificando rótulos, cores e pesos.',
60
+ },
61
+ {
62
+ name: 'Girar a Roleta',
63
+ text: 'Clique para acionar a rotação e aguarde a roleta parar por completo com física realista.',
64
+ },
65
+ ],
66
+ schemas: [
67
+ {
68
+ '@context': 'https://schema.org',
69
+ '@type': 'SoftwareApplication',
70
+ 'name': 'Roleta de Decisões',
71
+ 'operatingSystem': 'All',
72
+ 'applicationCategory': 'UtilitiesApplication',
73
+ 'browserRequirements': 'Requires HTML5 Canvas. Requires JavaScript.',
74
+ },
75
+ {
76
+ '@context': 'https://schema.org',
77
+ '@type': 'FAQPage',
78
+ 'mainEntity': [
79
+ { '@type': 'Question', 'name': 'Como funciona a roleta de decisões?', 'acceptedAnswer': { '@type': 'Answer', 'text': 'Insira suas opções, ajuste os pesos caso queira variar a probabilidade de sorteio e clique no botão Girar.' } },
80
+ { '@type': 'Question', 'name': 'Posso personalizar cores e nomes?', 'acceptedAnswer': { '@type': 'Answer', 'text': 'Sim, você pode adicionar fatias, mudar os nomes de exibição, escolher cores e definir pesos de 1 a 5.' } },
81
+ ],
82
+ },
83
+ {
84
+ '@context': 'https://schema.org',
85
+ '@type': 'HowTo',
86
+ 'name': 'How to Use the Decision Wheel Spinner',
87
+ 'step': [
88
+ { '@type': 'HowToStep', 'name': 'Montar as Fatias', 'text': 'Carregue uma predefinição rápida ou crie suas próprias fatias especificando rótulos, cores e pesos.' },
89
+ { '@type': 'HowToStep', 'name': 'Girar a Roleta', 'text': 'Clique para acionar a rotação e aguarde a roleta parar por completo com física realista.' },
90
+ ],
91
+ },
92
+ ],
93
+ };
@@ -0,0 +1,75 @@
1
+ import { bibliography } from '../bibliography';
2
+ import type { DecisionWheelLocaleContent } from '../entry';
3
+
4
+ export const content: DecisionWheelLocaleContent = {
5
+ slug: 'koleso-reshenij',
6
+ title: 'Koleso Reshenij: Generator Vyborov i Sluchajnyj Selektor',
7
+ description: 'Vrashhajte nastraivaemoe koleso reshenij dlya nastolnyh igr.',
8
+ ui: {
9
+ title: 'Koleso Reshenij',
10
+ spinButton: 'Zapustit Vrashenie',
11
+ clearHistory: 'Ochistit Istoriyu',
12
+ resultHeading: 'Rezultat',
13
+ historyTitle: 'Istoriya Vrashenij',
14
+ noHistory: 'Vrashenij eshe ne bylo. Nagmite Zapustit Vrashenie.',
15
+ addSegmentLabel: 'Dobavit Segment',
16
+ removeSegmentLabel: 'Udalit',
17
+ segmentLabelPlaceholder: 'Podpis',
18
+ presetLabel: 'Zagotovki',
19
+ presetYesNo: 'Da / Net',
20
+ presetNumbers: 'Chisla 1-6',
21
+ presetActions: 'Dejstviya',
22
+ presetCustom: 'Svoi',
23
+ presetD20: 'D20',
24
+ presetAlignment: 'Mirovozzrenie',
25
+ presetLoot: 'Redkost Dobychi',
26
+ weightLabel: 'Ves',
27
+ spinAgain: 'Vrashat Eshe',
28
+ noSegments: 'Dobavte segmenty na koleso pered vrasheniem.',
29
+ },
30
+ seo: [
31
+ { type: 'title', text: 'Koleso Reshenij dlya Nastolnyh Igr', level: 2 },
32
+ { type: 'paragraph', html: 'Koleso reshenij - eto universalnyj instrument dlya nastolnyh igr i gruppovyh meropriyatij. Nastrojte segmenty, zapustite vrashenie i poluchite sluchajnyj rezultat s realiznichnoj animaciej.' },
33
+ { type: 'stats', items: [{ value: '16', label: 'Maksimum Segmentov' }, { value: '4', label: 'Rezhima Zagotovok' }, { value: '10', label: 'Zapisej Istorii' }], columns: 3 },
34
+ ],
35
+ faq: [
36
+ { question: 'Kak rabotaet Koleso Reshenij?', answer: 'Vyberite ili sozdajte svoi segmenty, zatem nagmite Zapustit Vrashenie. Koleso ostanavlivaetsya na sluchajnom segmente.' },
37
+ { question: 'Mogu li ya dobavit svoi segmenty?', answer: 'Da, nagmite Dobavit Segment chtoby sozdat novye varianty.' },
38
+ { question: 'Kakie zagotovki dostupny?', answer: 'Dostupny zagotovki Da/Net, Chisla 1-6, Dejstviya i Svoi.' },
39
+ ],
40
+ bibliography,
41
+ howTo: [
42
+ { name: 'Vyberite ili Sozdajte Segmenty', text: 'Vyberite zagotovku ili dobavte svoi segmenty.' },
43
+ { name: 'Zapustite Vrashenie', text: 'Nagmite knopku, chtoby zapustit koleso.' },
44
+ { name: 'Posmotrite Rezultat', text: 'Rezultat podcherkivaetsya vizualnym effektom.' },
45
+ ],
46
+ schemas: [
47
+ {
48
+ '@context': 'https://schema.org',
49
+ '@type': 'SoftwareApplication',
50
+ 'name': 'Decision Wheel Spinner',
51
+ 'operatingSystem': 'All',
52
+ 'applicationCategory': 'UtilitiesApplication',
53
+ 'browserRequirements': 'Requires HTML5 Canvas. Requires JavaScript.',
54
+ },
55
+ {
56
+ '@context': 'https://schema.org',
57
+ '@type': 'FAQPage',
58
+ 'mainEntity': [
59
+ { '@type': 'Question', 'name': 'Kak rabotaet Koleso Reshenij?', 'acceptedAnswer': { '@type': 'Answer', 'text': 'Vyberite ili sozdajte svoi segmenty, zatem nagmite Zapustit Vrashenie.' } },
60
+ { '@type': 'Question', 'name': 'Mogu li ya dobavit svoi segmenty?', 'acceptedAnswer': { '@type': 'Answer', 'text': 'Da, nagmite Dobavit Segment chtoby sozdat novye varianty.' } },
61
+ { '@type': 'Question', 'name': 'Kakie zagotovki dostupny?', 'acceptedAnswer': { '@type': 'Answer', 'text': 'Dostupny zagotovki Da/Net, Chisla 1-6, Dejstviya i Svoi.' } },
62
+ ],
63
+ },
64
+ {
65
+ '@context': 'https://schema.org',
66
+ '@type': 'HowTo',
67
+ 'name': 'How to Use the Decision Wheel Spinner',
68
+ 'step': [
69
+ { '@type': 'HowToStep', 'name': 'Choose or Create Segments', 'text': 'Select a preset or add custom segments.' },
70
+ { '@type': 'HowToStep', 'name': 'Spin the Wheel', 'text': 'Click the spin button to start the wheel.' },
71
+ { '@type': 'HowToStep', 'name': 'View Result', 'text': 'The result is displayed with emphasis.' },
72
+ ],
73
+ },
74
+ ],
75
+ };
@@ -0,0 +1,93 @@
1
+ import { bibliography } from '../bibliography';
2
+ import type { DecisionWheelLocaleContent } from '../entry';
3
+
4
+ export const content: DecisionWheelLocaleContent = {
5
+ slug: 'beslutsjhjul',
6
+ title: 'Beslutshjul: Slumpmässig Väljare och Alternativgenerator',
7
+ description: 'Snurra ett anpassningsbart beslutshjul för brädspel. Lägg till segment med vikter för att slumpa fram resultat.',
8
+ ui: {
9
+ title: 'Beslutshjul',
10
+ spinButton: 'Snurra Hjulet',
11
+ clearHistory: 'Rensa Historik',
12
+ resultHeading: 'Resultat',
13
+ historyTitle: 'Snurrhistorik',
14
+ noHistory: 'Inga snurr än. Klicka på Snurra Hjulet för att starta.',
15
+ addSegmentLabel: 'Lägg Till Segment',
16
+ removeSegmentLabel: 'Ta bort',
17
+ segmentLabelPlaceholder: 'Etikett',
18
+ presetLabel: 'Mallar',
19
+ presetYesNo: 'Ja eller Nej',
20
+ presetNumbers: 'Siffror 1-6',
21
+ presetActions: 'Aktioner',
22
+ presetCustom: 'Anpassad',
23
+ presetD20: 'D20',
24
+ presetAlignment: 'Inriktning',
25
+ presetLoot: 'Byte Sällsynthet',
26
+ weightLabel: 'Vikt',
27
+ spinAgain: 'Snurra Igen',
28
+ noSegments: 'Lägg till segment på hjulet innan du snurrar.',
29
+ },
30
+ seo: [
31
+ { type: 'title', text: 'Beslutshjul för Brädspel och Gruppaktiviteter', level: 2 },
32
+ { type: 'paragraph', html: 'Beslutshjulet är ett mångsidigt verktyg för brädspel. Anpassa segmenten med dina egna texter och färger.' },
33
+ {
34
+ type: 'stats',
35
+ items: [
36
+ { value: '16', label: 'Lägg Till Segment' },
37
+ { value: '7', label: 'Mallar' },
38
+ { value: '10', label: 'Snurrhistorik' },
39
+ ],
40
+ columns: 3,
41
+ },
42
+ { type: 'title', text: 'Hur du Använder Beslutshjulet', level: 2 },
43
+ { type: 'paragraph', html: 'Välj en mall eller skapa dina egna segment med vikter för att justera chanserna för varje val.' },
44
+ ],
45
+ faq: [
46
+ {
47
+ question: 'Hur fungerar beslutshjulet?',
48
+ answer: 'Skapa dina val, ställ in vikter om du vill ändra chanserna och tryck på snurrknappen för resultat.',
49
+ },
50
+ {
51
+ question: 'Kan jag ändra färger och namn?',
52
+ answer: 'Ja, du kan lägga till segment, ändra namnen, välja egna färger och ställa in vikten från 1 till 5.',
53
+ },
54
+ ],
55
+ bibliography,
56
+ howTo: [
57
+ {
58
+ name: 'Skapa Segment',
59
+ text: 'Välj en snabbmall eller skapa dina egna segment med etiketter och färger.',
60
+ },
61
+ {
62
+ name: 'Snurra Hjulet',
63
+ text: 'Klicka på snurra-knappen för att starta hjulet med realistisk friktionsinbromsning.',
64
+ },
65
+ ],
66
+ schemas: [
67
+ {
68
+ '@context': 'https://schema.org',
69
+ '@type': 'SoftwareApplication',
70
+ 'name': 'Beslutshjul',
71
+ 'operatingSystem': 'All',
72
+ 'applicationCategory': 'UtilitiesApplication',
73
+ 'browserRequirements': 'Requires HTML5 Canvas. Requires JavaScript.',
74
+ },
75
+ {
76
+ '@context': 'https://schema.org',
77
+ '@type': 'FAQPage',
78
+ 'mainEntity': [
79
+ { '@type': 'Question', 'name': 'Hur fungerar beslutshjulet?', 'acceptedAnswer': { '@type': 'Answer', 'text': 'Skapa dina val, ställ in vikter om du vill ändra chanserna och tryck på snurrknappen för resultat.' } },
80
+ { '@type': 'Question', 'name': 'Kan jag ändra färger och namn?', 'acceptedAnswer': { '@type': 'Answer', 'text': 'Ja, du kan lägga till segment, ändra namnen, välja egna färger och ställa in vikten från 1 till 5.' } },
81
+ ],
82
+ },
83
+ {
84
+ '@context': 'https://schema.org',
85
+ '@type': 'HowTo',
86
+ 'name': 'How to Use the Decision Wheel Spinner',
87
+ 'step': [
88
+ { '@type': 'HowToStep', 'name': 'Skapa Segment', 'text': 'Välj en snabbmall eller skapa dina egna segment med etiketter och färger.' },
89
+ { '@type': 'HowToStep', 'name': 'Snurra Hjulet', 'text': 'Klicka på snurra-knappen för att starta hjulet med realistisk friktionsinbromsning.' },
90
+ ],
91
+ },
92
+ ],
93
+ };
@@ -0,0 +1,93 @@
1
+ import { bibliography } from '../bibliography';
2
+ import type { DecisionWheelLocaleContent } from '../entry';
3
+
4
+ export const content: DecisionWheelLocaleContent = {
5
+ slug: 'karar-carki',
6
+ title: 'Karar Çarkı: Rastgele Seçici ve Seçenek Çarkı',
7
+ description: 'Masa oyunları için özelleştirilebilir bir karar çarkı çevirin. Sonuçları rastgele belirlemek için ağırlıklı dilimler ekleyin.',
8
+ ui: {
9
+ title: 'Karar Çarkı',
10
+ spinButton: 'Çarkı Çevir',
11
+ clearHistory: 'Geçmişi Temizle',
12
+ resultHeading: 'Sonuç',
13
+ historyTitle: 'Çevirme Geçmişi',
14
+ noHistory: 'Henüz çevrilmedi. Başlamak için Çarkı Çevir butonuna tıklayın.',
15
+ addSegmentLabel: 'Dilim Ekle',
16
+ removeSegmentLabel: 'Kaldır',
17
+ segmentLabelPlaceholder: 'Etiket',
18
+ presetLabel: 'Hazır Ayarlar',
19
+ presetYesNo: 'Evet veya Hayır',
20
+ presetNumbers: 'Sayılar 1-6',
21
+ presetActions: 'Eylemler',
22
+ presetCustom: 'Özel',
23
+ presetD20: 'D20',
24
+ presetAlignment: 'Hizalanma',
25
+ presetLoot: 'Ganimet Nadirliği',
26
+ weightLabel: 'Ağırlık',
27
+ spinAgain: 'Tekrar Çevir',
28
+ noSegments: 'Çevirmeden önce çarka dilimler ekleyin.',
29
+ },
30
+ seo: [
31
+ { type: 'title', text: 'Masa Oyunları ve Grup Etkinlikleri İçin Karar Çarkı', level: 2 },
32
+ { type: 'paragraph', html: 'Karar çarkı, masa oyunları için çok yönlü bir rastgele seçim aracıdır. Dilimleri kendi etiketlerinizle özelleştirin.' },
33
+ {
34
+ type: 'stats',
35
+ items: [
36
+ { value: '16', label: 'Dilim Ekle' },
37
+ { value: '7', label: 'Hazır Ayarlar' },
38
+ { value: '10', label: 'Çevirme Geçmişi' },
39
+ ],
40
+ columns: 3,
41
+ },
42
+ { type: 'title', text: 'Karar Çarkı Nasıl Kullanılır', level: 2 },
43
+ { type: 'paragraph', html: 'Hazır bir şablon seçin veya olasılıkları ayarlamak için farklı ağırlıklara sahip kendi dilimlerinizi oluşturun.' },
44
+ ],
45
+ faq: [
46
+ {
47
+ question: 'Karar çarkı nasıl çalışır?',
48
+ answer: 'Seçeneklerinizi oluşturun, ağırlıklarını belirleyin ve rastgele bir sonuç elde etmek için Çarkı Çevir düğmesine basın.',
49
+ },
50
+ {
51
+ question: 'Renkleri ve adları özelleştirebilir miyim?',
52
+ answer: 'Evet, yeni dilim ekleyebilir, etiketleri düzenleyebilir, renk atayabilir ve ağırlıklarını 1 ila 5 arasında ayarlayabilirsiniz.',
53
+ },
54
+ ],
55
+ bibliography,
56
+ howTo: [
57
+ {
58
+ name: 'Dilimleri Oluşturun',
59
+ text: 'Hazır şablonlardan yükleyin veya etiket, renk ve ağırlık belirterek kendi dilimlerinizi ekleyin.',
60
+ },
61
+ {
62
+ name: 'Çarkı Çevirin',
63
+ text: 'Düğmeye tıklayarak gerçekçi yavaşlama efektine sahip çark dönme animasyonunu başlatın.',
64
+ },
65
+ ],
66
+ schemas: [
67
+ {
68
+ '@context': 'https://schema.org',
69
+ '@type': 'SoftwareApplication',
70
+ 'name': 'Karar Çarkı',
71
+ 'operatingSystem': 'All',
72
+ 'applicationCategory': 'UtilitiesApplication',
73
+ 'browserRequirements': 'Requires HTML5 Canvas. Requires JavaScript.',
74
+ },
75
+ {
76
+ '@context': 'https://schema.org',
77
+ '@type': 'FAQPage',
78
+ 'mainEntity': [
79
+ { '@type': 'Question', 'name': 'Karar çarkı nasıl çalışır?', 'acceptedAnswer': { '@type': 'Answer', 'text': 'Seçeneklerinizi oluşturun, ağırlıklarını belirleyin ve rastgele bir sonuç elde etmek için Çarkı Çevir düğmesine basın.' } },
80
+ { '@type': 'Question', 'name': 'Renkleri ve adları özelleştirebilir miyim?', 'acceptedAnswer': { '@type': 'Answer', 'text': 'Evet, yeni dilim ekleyebilir, etiketleri düzenleyebilir, renk atayabilir ve ağırlıklarını 1 ila 5 arasında ayarlayabilirsiniz.' } },
81
+ ],
82
+ },
83
+ {
84
+ '@context': 'https://schema.org',
85
+ '@type': 'HowTo',
86
+ 'name': 'How to Use the Decision Wheel Spinner',
87
+ 'step': [
88
+ { '@type': 'HowToStep', 'name': 'Dilimleri Oluşturun', 'text': 'Hazır şablonlardan yükleyin veya etiket, renk ve ağırlık belirterek kendi dilimlerinizi ekleyin.' },
89
+ { '@type': 'HowToStep', 'name': 'Çarkı Çevirin', 'text': 'Düğmeye tıklayarak gerçekçi yavaşlama efektine sahip çark dönme animasyonunu başlatın.' },
90
+ ],
91
+ },
92
+ ],
93
+ };
@@ -0,0 +1,93 @@
1
+ import { bibliography } from '../bibliography';
2
+ import type { DecisionWheelLocaleContent } from '../entry';
3
+
4
+ export const content: DecisionWheelLocaleContent = {
5
+ slug: 'decision-wheel',
6
+ title: '决策轮盘: 在线随机选择器与抽签转盘',
7
+ description: '为桌游设计的高级自定义决策轮盘。添加带有不同权重的分块,以随机确定游戏结果。',
8
+ ui: {
9
+ title: '决策轮盘',
10
+ spinButton: '转动轮盘',
11
+ clearHistory: '清除历史',
12
+ resultHeading: '结果',
13
+ historyTitle: '转动历史',
14
+ noHistory: '暂无历史记录。点击转动轮盘开始。',
15
+ addSegmentLabel: '添加选项',
16
+ removeSegmentLabel: '删除',
17
+ segmentLabelPlaceholder: '标签',
18
+ presetLabel: '预设',
19
+ presetYesNo: '是或否',
20
+ presetNumbers: '数字 1-6',
21
+ presetActions: '行动',
22
+ presetCustom: '自定义',
23
+ presetD20: 'D20',
24
+ presetAlignment: '阵营',
25
+ presetLoot: '战利品稀有度',
26
+ weightLabel: '权重',
27
+ spinAgain: '再次转动',
28
+ noSegments: '请在转动前为轮盘添加选项。',
29
+ },
30
+ seo: [
31
+ { type: 'title', text: '适用于桌游及团队聚会的随机决策轮盘', level: 2 },
32
+ { type: 'paragraph', html: '决策轮盘是一个用于桌游多用途的随机选择工具。可自由定制各分块的内容和颜色。' },
33
+ {
34
+ type: 'stats',
35
+ items: [
36
+ { value: '16', label: '添加选项' },
37
+ { value: '7', label: '预设' },
38
+ { value: '10', label: '转动历史' },
39
+ ],
40
+ columns: 3,
41
+ },
42
+ { type: 'title', text: '如何使用决策轮盘', level: 2 },
43
+ { type: 'paragraph', html: '选择预设方案,或创建自定义分块并设置权重,以按需调整各选项的被选中概率。' },
44
+ ],
45
+ faq: [
46
+ {
47
+ question: '决策轮盘是如何工作的?',
48
+ answer: '配置选项并设置用于调节概率的权重,点击转动轮盘按钮即可随机获得其中一个选项。',
49
+ },
50
+ {
51
+ question: '可以自定义颜色和文字吗?',
52
+ answer: '可以。您可以自由添加选项、更改文本标签、选择背景颜色并将权重在 1 至 5 之间进行调整。',
53
+ },
54
+ ],
55
+ bibliography,
56
+ howTo: [
57
+ {
58
+ name: '配置轮盘分块',
59
+ text: '选择一个快速预设方案,或者添加自定义的标签、颜色和权重。',
60
+ },
61
+ {
62
+ name: '转动决策轮盘',
63
+ text: '点击转动按钮,轮盘将伴随逼真的物理摩擦阻尼减速动画进行旋转。',
64
+ },
65
+ ],
66
+ schemas: [
67
+ {
68
+ '@context': 'https://schema.org',
69
+ '@type': 'SoftwareApplication',
70
+ 'name': '决策轮盘',
71
+ 'operatingSystem': 'All',
72
+ 'applicationCategory': 'UtilitiesApplication',
73
+ 'browserRequirements': 'Requires HTML5 Canvas. Requires JavaScript.',
74
+ },
75
+ {
76
+ '@context': 'https://schema.org',
77
+ '@type': 'FAQPage',
78
+ 'mainEntity': [
79
+ { '@type': 'Question', 'name': '决策轮盘是如何工作的?', 'acceptedAnswer': { '@type': 'Answer', 'text': '配置选项并设置用于调节概率的权重,点击转动轮盘按钮即可随机获得其中一个选项。' } },
80
+ { '@type': 'Question', 'name': '可以自定义颜色和文字吗?', 'acceptedAnswer': { '@type': 'Answer', 'text': '可以。您可以自由添加选项、更改文本标签、选择背景颜色并将权重在 1 至 5 之间进行调整。' } },
81
+ ],
82
+ },
83
+ {
84
+ '@context': 'https://schema.org',
85
+ '@type': 'HowTo',
86
+ 'name': 'How to Use the Decision Wheel Spinner',
87
+ 'step': [
88
+ { '@type': 'HowToStep', 'name': '配置轮盘分块', 'text': '选择一个快速预设方案,或者添加自定义的标签、颜色和权重。' },
89
+ { '@type': 'HowToStep', 'name': '转动决策轮盘', 'text': '点击转动按钮,轮盘将伴随逼真的物理摩擦阻尼减速动画进行旋转。' },
90
+ ],
91
+ },
92
+ ],
93
+ };
@@ -0,0 +1,9 @@
1
+ import type { ToolDefinition } from '../../types';
2
+ import { decisionWheel } from './entry';
3
+ export * from './entry';
4
+ export const DECISION_WHEEL_TOOL: ToolDefinition = {
5
+ entry: decisionWheel,
6
+ Component: () => import('./component.astro'),
7
+ SEOComponent: () => import('./seo.astro'),
8
+ BibliographyComponent: () => import('./bibliography.astro'),
9
+ };
@@ -0,0 +1,163 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ getPresetSegments,
4
+ addSegment,
5
+ removeSegment,
6
+ updateSegment,
7
+ selectRandomSegment,
8
+ getSelectedIndex,
9
+ addHistoryItem,
10
+ } from './logic';
11
+ import type { SpinResultEntry, WheelSegment } from './types';
12
+
13
+ describe('getPresetSegments', () => {
14
+ it('returns 2 segments for yesno', () => {
15
+ const segs = getPresetSegments('yesno');
16
+ expect(segs).toHaveLength(2);
17
+ expect(segs[0].label).toBe('Yes');
18
+ expect(segs[1].label).toBe('No');
19
+ });
20
+
21
+ it('returns 6 segments for numbers', () => {
22
+ const segs = getPresetSegments('numbers');
23
+ expect(segs).toHaveLength(6);
24
+ expect(segs[0].label).toBe('1');
25
+ expect(segs[5].label).toBe('6');
26
+ });
27
+
28
+ it('returns 5 segments for actions', () => {
29
+ const segs = getPresetSegments('actions');
30
+ expect(segs).toHaveLength(5);
31
+ expect(segs[0].label).toBe('Attack');
32
+ });
33
+
34
+ it('returns 20 segments for d20', () => {
35
+ const segs = getPresetSegments('d20');
36
+ expect(segs).toHaveLength(20);
37
+ expect(segs[0].label).toBe('1');
38
+ expect(segs[19].label).toBe('20');
39
+ });
40
+
41
+ it('returns 9 segments for alignment', () => {
42
+ const segs = getPresetSegments('alignment');
43
+ expect(segs).toHaveLength(9);
44
+ expect(segs[0].label).toBe('Lawful Good');
45
+ expect(segs[8].label).toBe('Chaotic Evil');
46
+ });
47
+
48
+ it('returns 5 segments with custom weights for loot', () => {
49
+ const segs = getPresetSegments('loot');
50
+ expect(segs).toHaveLength(5);
51
+ expect(segs[0].label).toBe('Common');
52
+ expect(segs[0].weight).toBe(5);
53
+ expect(segs[4].label).toBe('Legendary');
54
+ expect(segs[4].weight).toBe(1);
55
+ });
56
+
57
+ it('returns 3 segments for custom', () => {
58
+ const segs = getPresetSegments('custom');
59
+ expect(segs).toHaveLength(3);
60
+ expect(segs[0].label).toBe('Option A');
61
+ });
62
+ });
63
+
64
+ describe('addSegment', () => {
65
+ it('adds a new segment to the array', () => {
66
+ const segs = getPresetSegments('yesno');
67
+ const result = addSegment(segs);
68
+ expect(result).toHaveLength(3);
69
+ expect(result[2].label).toBe('Option C');
70
+ });
71
+ });
72
+
73
+ describe('removeSegment', () => {
74
+ it('removes a segment by id', () => {
75
+ const segs = getPresetSegments('numbers');
76
+ const result = removeSegment(segs, 'seg-0');
77
+ expect(result).toHaveLength(5);
78
+ expect(result.find((s) => s.id === 'seg-0')).toBeUndefined();
79
+ });
80
+
81
+ it('does not remove the last segment', () => {
82
+ const segs = getPresetSegments('yesno');
83
+ const result = removeSegment(removeSegment(segs, 'seg-0'), 'seg-1');
84
+ expect(result).toHaveLength(1);
85
+ expect(result[0].id).toBe('seg-1');
86
+ });
87
+ });
88
+
89
+ describe('updateSegment', () => {
90
+ it('updates the label of a segment', () => {
91
+ const segs = getPresetSegments('yesno');
92
+ const result = updateSegment(segs, 'seg-0', 'label', 'Maybe');
93
+ expect(result[0].label).toBe('Maybe');
94
+ expect(result[1].label).toBe('No');
95
+ });
96
+
97
+ it('updates the color of a segment', () => {
98
+ const segs = getPresetSegments('yesno');
99
+ const result = updateSegment(segs, 'seg-0', 'color', '#ff0000');
100
+ expect(result[0].color).toBe('#ff0000');
101
+ });
102
+
103
+ it('updates the weight of a segment', () => {
104
+ const segs = getPresetSegments('yesno');
105
+ const result = updateSegment(segs, 'seg-0', 'weight', '3');
106
+ expect(result[0].weight).toBe(3);
107
+ });
108
+ });
109
+
110
+ describe('selectRandomSegment', () => {
111
+ it('returns a valid index within range', () => {
112
+ const segs = getPresetSegments('numbers');
113
+ for (let i = 0; i < 100; i++) {
114
+ const idx = selectRandomSegment(segs);
115
+ expect(idx).toBeGreaterThanOrEqual(0);
116
+ expect(idx).toBeLessThan(6);
117
+ }
118
+ });
119
+ });
120
+
121
+ describe('getSelectedIndex', () => {
122
+ it('returns index 0 when angle aligns with first segment', () => {
123
+ const segs = getPresetSegments('yesno');
124
+ const idx = getSelectedIndex(segs, 0);
125
+ expect(idx).toBe(0);
126
+ });
127
+
128
+ it('returns index 1 for second segment', () => {
129
+ const segs = getPresetSegments('yesno');
130
+ const idx = getSelectedIndex(segs, 180);
131
+ expect(idx).toBe(1);
132
+ });
133
+
134
+ it('handles custom weights correctly for index selection', () => {
135
+ const segs: WheelSegment[] = [
136
+ { id: '1', label: 'A', color: 'red', weight: 3 },
137
+ { id: '2', label: 'B', color: 'blue', weight: 1 },
138
+ ];
139
+ expect(getSelectedIndex(segs, 0)).toBe(0);
140
+ expect(getSelectedIndex(segs, 90)).toBe(1);
141
+ expect(getSelectedIndex(segs, 180)).toBe(0);
142
+ expect(getSelectedIndex(segs, 270)).toBe(0);
143
+ });
144
+ });
145
+
146
+ describe('addHistoryItem', () => {
147
+ it('adds item to history', () => {
148
+ const segs = getPresetSegments('yesno');
149
+ const result: SpinResultEntry[] = [];
150
+ const updated = addHistoryItem(result, segs[0], 0);
151
+ expect(updated).toHaveLength(1);
152
+ expect(updated[0].segment.label).toBe('Yes');
153
+ });
154
+
155
+ it('limits history to 10 items', () => {
156
+ const segs = getPresetSegments('numbers');
157
+ let history: SpinResultEntry[] = [];
158
+ for (let i = 0; i < 15; i++) {
159
+ history = addHistoryItem(history, segs[i % 6], i % 6);
160
+ }
161
+ expect(history).toHaveLength(10);
162
+ });
163
+ });