@jjlmoya/utils-tabletop 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 (121) hide show
  1. package/package.json +65 -0
  2. package/scripts/postinstall.mjs +27 -0
  3. package/src/category/TabletopCategorySEO.astro +14 -0
  4. package/src/category/i18n/de.ts +27 -0
  5. package/src/category/i18n/en.ts +27 -0
  6. package/src/category/i18n/es.ts +27 -0
  7. package/src/category/i18n/fr.ts +27 -0
  8. package/src/category/i18n/id.ts +27 -0
  9. package/src/category/i18n/it.ts +27 -0
  10. package/src/category/i18n/ja.ts +27 -0
  11. package/src/category/i18n/ko.ts +27 -0
  12. package/src/category/i18n/nl.ts +27 -0
  13. package/src/category/i18n/pl.ts +27 -0
  14. package/src/category/i18n/pt.ts +27 -0
  15. package/src/category/i18n/ru.ts +27 -0
  16. package/src/category/i18n/sv.ts +27 -0
  17. package/src/category/i18n/tr.ts +27 -0
  18. package/src/category/i18n/zh.ts +27 -0
  19. package/src/category/index.ts +27 -0
  20. package/src/components/PreviewNavSidebar.astro +116 -0
  21. package/src/components/PreviewToolbar.astro +143 -0
  22. package/src/data.ts +10 -0
  23. package/src/entries.ts +10 -0
  24. package/src/env.d.ts +5 -0
  25. package/src/index.ts +21 -0
  26. package/src/layouts/PreviewLayout.astro +117 -0
  27. package/src/pages/[locale]/[slug].astro +161 -0
  28. package/src/pages/[locale].astro +251 -0
  29. package/src/pages/index.astro +4 -0
  30. package/src/tests/faq_count.test.ts +18 -0
  31. package/src/tests/i18n_coverage.test.ts +36 -0
  32. package/src/tests/locale_completeness.test.ts +29 -0
  33. package/src/tests/mocks/astro_mock.js +2 -0
  34. package/src/tests/no_en_dash.test.ts +41 -0
  35. package/src/tests/no_h1_in_components.test.ts +48 -0
  36. package/src/tests/schemas_fulfillment.test.ts +23 -0
  37. package/src/tests/seo_length.test.ts +22 -0
  38. package/src/tests/shared-test-helpers.ts +56 -0
  39. package/src/tests/slug_language_code_format.test.ts +23 -0
  40. package/src/tests/slug_uniqueness.test.ts +81 -0
  41. package/src/tests/title_quality.test.ts +55 -0
  42. package/src/tests/tool_exports.test.ts +34 -0
  43. package/src/tests/tool_validation.test.ts +16 -0
  44. package/src/tool/board-game-timer/bibliography.astro +16 -0
  45. package/src/tool/board-game-timer/bibliography.ts +12 -0
  46. package/src/tool/board-game-timer/board-game-timer.css +1615 -0
  47. package/src/tool/board-game-timer/client.ts +17 -0
  48. package/src/tool/board-game-timer/component.astro +22 -0
  49. package/src/tool/board-game-timer/components/DuelTimer.astro +75 -0
  50. package/src/tool/board-game-timer/components/MultiplayerTimer.astro +58 -0
  51. package/src/tool/board-game-timer/components/SetupPanel.astro +155 -0
  52. package/src/tool/board-game-timer/components/StatsModal.astro +52 -0
  53. package/src/tool/board-game-timer/entry.ts +76 -0
  54. package/src/tool/board-game-timer/i18n/de.ts +303 -0
  55. package/src/tool/board-game-timer/i18n/en.ts +304 -0
  56. package/src/tool/board-game-timer/i18n/es.ts +303 -0
  57. package/src/tool/board-game-timer/i18n/fr.ts +303 -0
  58. package/src/tool/board-game-timer/i18n/id.ts +303 -0
  59. package/src/tool/board-game-timer/i18n/it.ts +303 -0
  60. package/src/tool/board-game-timer/i18n/ja.ts +306 -0
  61. package/src/tool/board-game-timer/i18n/ko.ts +303 -0
  62. package/src/tool/board-game-timer/i18n/nl.ts +303 -0
  63. package/src/tool/board-game-timer/i18n/pl.ts +303 -0
  64. package/src/tool/board-game-timer/i18n/pt.ts +303 -0
  65. package/src/tool/board-game-timer/i18n/ru.ts +303 -0
  66. package/src/tool/board-game-timer/i18n/sv.ts +303 -0
  67. package/src/tool/board-game-timer/i18n/tr.ts +303 -0
  68. package/src/tool/board-game-timer/i18n/zh.ts +303 -0
  69. package/src/tool/board-game-timer/index.ts +9 -0
  70. package/src/tool/board-game-timer/logic.test.ts +282 -0
  71. package/src/tool/board-game-timer/logic.ts +6 -0
  72. package/src/tool/board-game-timer/modules/ColorHelper.ts +26 -0
  73. package/src/tool/board-game-timer/modules/DuelViewManager.ts +120 -0
  74. package/src/tool/board-game-timer/modules/FormatHelper.ts +12 -0
  75. package/src/tool/board-game-timer/modules/GameRunner.ts +136 -0
  76. package/src/tool/board-game-timer/modules/GameState.ts +48 -0
  77. package/src/tool/board-game-timer/modules/MultiplayerViewManager.ts +163 -0
  78. package/src/tool/board-game-timer/modules/SetupPanelManager.ts +209 -0
  79. package/src/tool/board-game-timer/modules/SoundManager.ts +109 -0
  80. package/src/tool/board-game-timer/modules/SpeechManager.ts +1 -0
  81. package/src/tool/board-game-timer/modules/StatsManager.ts +87 -0
  82. package/src/tool/board-game-timer/modules/StatsModalManager.ts +74 -0
  83. package/src/tool/board-game-timer/modules/TimerEngine.ts +175 -0
  84. package/src/tool/board-game-timer/modules/TimerEngineBase.ts +49 -0
  85. package/src/tool/board-game-timer/modules/TimerEngineClock.ts +127 -0
  86. package/src/tool/board-game-timer/modules/TimerStateHelper.ts +55 -0
  87. package/src/tool/board-game-timer/modules/particles.ts +108 -0
  88. package/src/tool/board-game-timer/seo.astro +16 -0
  89. package/src/tool/board-game-timer/types.ts +97 -0
  90. package/src/tool/dice-roller-simulator/bibliography.astro +16 -0
  91. package/src/tool/dice-roller-simulator/bibliography.ts +12 -0
  92. package/src/tool/dice-roller-simulator/client.ts +273 -0
  93. package/src/tool/dice-roller-simulator/component.astro +75 -0
  94. package/src/tool/dice-roller-simulator/components/DiceGrid.astro +55 -0
  95. package/src/tool/dice-roller-simulator/components/RollHistorySection.astro +26 -0
  96. package/src/tool/dice-roller-simulator/components/RollResultBanner.astro +10 -0
  97. package/src/tool/dice-roller-simulator/components/StatsPanel.astro +36 -0
  98. package/src/tool/dice-roller-simulator/dice-roller-simulator.css +1319 -0
  99. package/src/tool/dice-roller-simulator/entry.ts +39 -0
  100. package/src/tool/dice-roller-simulator/i18n/de.ts +305 -0
  101. package/src/tool/dice-roller-simulator/i18n/en.ts +305 -0
  102. package/src/tool/dice-roller-simulator/i18n/es.ts +305 -0
  103. package/src/tool/dice-roller-simulator/i18n/fr.ts +305 -0
  104. package/src/tool/dice-roller-simulator/i18n/id.ts +305 -0
  105. package/src/tool/dice-roller-simulator/i18n/it.ts +305 -0
  106. package/src/tool/dice-roller-simulator/i18n/ja.ts +305 -0
  107. package/src/tool/dice-roller-simulator/i18n/ko.ts +305 -0
  108. package/src/tool/dice-roller-simulator/i18n/nl.ts +305 -0
  109. package/src/tool/dice-roller-simulator/i18n/pl.ts +305 -0
  110. package/src/tool/dice-roller-simulator/i18n/pt.ts +305 -0
  111. package/src/tool/dice-roller-simulator/i18n/ru.ts +305 -0
  112. package/src/tool/dice-roller-simulator/i18n/sv.ts +305 -0
  113. package/src/tool/dice-roller-simulator/i18n/tr.ts +305 -0
  114. package/src/tool/dice-roller-simulator/i18n/zh.ts +305 -0
  115. package/src/tool/dice-roller-simulator/index.ts +9 -0
  116. package/src/tool/dice-roller-simulator/modules/chart.ts +32 -0
  117. package/src/tool/dice-roller-simulator/modules/history.ts +87 -0
  118. package/src/tool/dice-roller-simulator/modules/particles.ts +22 -0
  119. package/src/tool/dice-roller-simulator/seo.astro +16 -0
  120. package/src/tools.ts +9 -0
  121. package/src/types.ts +70 -0
@@ -0,0 +1,305 @@
1
+ import { bibliography } from '../bibliography';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+
4
+ export const content: ToolLocaleContent = {
5
+ slug: 'dice-roller-simulator',
6
+ title: '在线骰子模拟器与概率计算器',
7
+ description: '在线投掷虚拟多面体骰子(D4, D6, D8, D10, D12, D20, D100),添加修正值,并查看桌游与跑团的实时骰子统计数据和出现概率分析。',
8
+ ui: {
9
+ title: '在线骰子模拟器与概率计算器',
10
+ rollButton: '投掷骰子',
11
+ clearButton: '清除历史记录',
12
+ historyTitle: '投掷历史',
13
+ noHistory: '暂无投掷记录。请添加骰子并点击“投掷骰子”开始。',
14
+ totalRolls: '总投掷次数',
15
+ averageRoll: '平均投掷结果',
16
+ lastRoll: '最近投掷总和',
17
+ modifierLabel: '修正值 (+/-)',
18
+ probabilityTitle: '概率分布分析',
19
+ selectDice: '选择要投掷的骰子',
20
+ },
21
+ seo: [
22
+ { type: 'title', text: '掌握桌游胜率:多面体骰子的概率如何塑造您的游戏进程', level: 2 },
23
+ { type: 'paragraph', html: '多面体骰子是桌面角色扮演游戏(TRPG跑团)和现代棋盘游戏的核心。无论您是在《地下城与守卫》中追求高难度的豁免判定,还是在为资源产出进行投掷,所有结果都受到数学概率的支配。了解不同骰子类型的组合如何影响您的胜率,对于游戏过程中的战术决策至关重要。' },
24
+ {
25
+ type: 'stats',
26
+ items: [
27
+ { value: '5.0%', label: '自然20概率' },
28
+ { value: '10.5', label: '3d6投掷平均值' },
29
+ { value: '9.75%', label: '优势判定下自然20' },
30
+ ],
31
+ columns: 3,
32
+ },
33
+ { type: 'title', text: '多个骰子的数学逻辑:为什么3d6比单个D20更稳健', level: 2 },
34
+ { type: 'paragraph', html: '投掷单个D20骰子时,1到20之间的每个结果都有相同的5%概率出现,从而形成平坦的概率分布。然而,当投掷像3d6这样的多个骰子时,结果会聚集在平均值10.5附近。这种钟形曲线(正规分布)意味着,投出中等结果的可能性远大于极端值,这为游戏带来了一致性,减少了单颗骰子判定时的不稳定性和运气成分。' },
35
+ { type: 'title', text: 'D20概率详解:大成功、大失败与修正值的作用', level: 2 },
36
+ { type: 'paragraph', html: '仅加2的微小修正值就能大幅改变成功的概率,将困难的任务变成可以达成的挑战。在许多游戏规则中,自然20代表自动大成功(5%概率),而自然1则是大失败。通过将这些概率结果可视化,可以帮助玩家理性看待随机性,并根据统计数据支持来规划您的战术行动。' },
37
+ {
38
+ type: 'title',
39
+ text: '标准多面体骰子早见参考表',
40
+ level: 3,
41
+ },
42
+ {
43
+ type: 'table',
44
+ headers: ['骰子类型', '最小投掷值', '最大投掷值', '平均结果', 'TRPG跑团中的主要作用'],
45
+ rows: [
46
+ ['D4', '1', '4', '2.5', '魔法飞弹、匕首伤害、微小陷阱与障碍'],
47
+ ['D6', '1', '6', '3.5', '火球术、标准长剑、属性判定'],
48
+ ['D8', '1', '8', '4.5', '长剑伤害、治疗法术、中型武器判定'],
49
+ ['D10', '1', '10', '5.5', '长戟伤害、戏法伤害、百分位数(十位数)'],
50
+ ['D12', '1', '12', '6.5', '巨斧伤害、野蛮人生命值判定'],
51
+ ['D20', '1', '20', '10.5', '豁免判定、攻击判定、技能判定'],
52
+ ['D100', '1', '100', '50.5', '百分类判定表、狂野魔法暴走判定'],
53
+ ],
54
+ },
55
+ {
56
+ type: 'tip',
57
+ title: '如何在脑海中快速计算骰子平均值',
58
+ html: '要计算任何单颗骰子的平均值,只需将最小值(1)和最大值相加,然后除以2。例如,一颗D6的平均值是 (1 + 6) / 2 = 3.5。如果是多个骰子,只需将单颗骰子的平均值乘以骰子数量即可(例如,3d6的平均值是 3 * 3.5 = 10.5)。如果带有固定的修正值,直接加上该值即可(例如,3d6 + 4 的平均值是 14.5)。',
59
+ },
60
+ {
61
+ type: 'title',
62
+ text: '平坦几率判定系统与曲线分布判定系统的对比',
63
+ level: 3,
64
+ },
65
+ {
66
+ type: 'proscons',
67
+ title: 'D20系统与3d6钟形曲线系统对比',
68
+ items: [
69
+ {
70
+ pro: 'D20判定系统计算非常迅速,大成功和大失败的概率固定为相eqn的5%。',
71
+ con: 'D20的判定结果起伏极大,使得角色本身的技能熟练度往往不及骰子本身的运气。',
72
+ },
73
+ {
74
+ pro: '3d6钟形曲线系统倾向于投出中间值,确保高技能角色极少在简单的任务中失败。',
75
+ con: '3d6系统判定时需要相加三颗骰子的出目,且投出完美18的概率极低(仅0.46%)。',
76
+ },
77
+ ],
78
+ },
79
+ {
80
+ type: 'title',
81
+ text: '了解概率判定机制',
82
+ level: 3,
83
+ },
84
+ {
85
+ type: 'comparative',
86
+ items: [
87
+ {
88
+ title: 'D20判定系统(线形分布)',
89
+ description: '动作判定使用单颗20面骰子,每个结果的几率均等为5%。高波动性带来了紧张刺激的体验,但缺乏稳定性。',
90
+ icon: 'mdi:dice-d20',
91
+ highlight: false,
92
+ points: [
93
+ '每个结果出目概率固定为5%',
94
+ '出目分散,随机波动极大',
95
+ '修正值以线性的方式提升成功率',
96
+ ],
97
+ },
98
+ {
99
+ title: '3d6判定系统(钟形曲线)',
100
+ description: '相加三颗6面骰子,创建以10.5为中心的标准正态分布。结果高度一致稳定。',
101
+ icon: 'mdi:chart-bell-curve',
102
+ highlight: true,
103
+ points: [
104
+ '投出中间结果的概率非常高',
105
+ '投出极端结果的情况极其罕见',
106
+ '修正值对成功率的影响呈非线性',
107
+ ],
108
+ },
109
+ {
110
+ title: '百分比判定系统(投掷出目小于目标值)',
111
+ description: '使用两颗10面骰子组合生成1到100之间的数字。出目小于等于角色的属性值即为成功,成功概率极其直观。',
112
+ icon: 'mdi:percent-box-outline',
113
+ highlight: false,
114
+ points: [
115
+ '成功概率直接与百分比挂钩',
116
+ '直观易懂,一目了然',
117
+ '角色属性和技能成长在数学上非常清晰',
118
+ ],
119
+ },
120
+ ],
121
+ columns: 3,
122
+ },
123
+ {
124
+ type: 'diagnostic',
125
+ variant: 'info',
126
+ title: '赌徒谬误:骰子本身没有记忆',
127
+ icon: 'mdi:information-outline',
128
+ badge: 'PROBABILITY TIP',
129
+ html: 'TRPG跑团和桌游中常见的误区是认为之前的投掷结果会影响接下来的出目。如果在D20判定中连续投出了三次“1”,玩家往往会觉得下一次应该“出大数”了。然而实际上,每一次投掷都是独立事件。下一次投出20的概率依然是精确的5%。理性认识这一点能帮助玩家做出更客观的战术决策。',
130
+ },
131
+ {
132
+ type: 'title',
133
+ text: '多面体骰子与桌游概率专业术语表',
134
+ level: 3,
135
+ },
136
+ {
137
+ type: 'glossary',
138
+ items: [
139
+ {
140
+ term: '优势与劣势判定',
141
+ definition: '玩家投掷两颗D20骰子,优势下取较大值,劣势下取较小值。优势判定将出目的期望值从10.5提升至13.8。',
142
+ },
143
+ {
144
+ term: '钟形曲线(正态分布)',
145
+ definition: '出目聚集在中间平均值附近的概率分布。投掷并相加多颗骰子(如3d6或2d10)时产生。',
146
+ },
147
+ {
148
+ term: '大成功',
149
+ definition: '骰子投出最大值(如D20的20)时的自动成功判定,通常会带来额外伤害或特殊收益效果。',
150
+ },
151
+ {
152
+ term: '大失败',
153
+ definition: '骰子投出最小值1时的自动失败判定,往往会伴随滑稽或灾难性的后果。',
154
+ },
155
+ {
156
+ term: '期望值',
157
+ definition: '投掷骰子长期以来的平均出目。单颗D6的期望值是3.5;单颗D20是10.5。',
158
+ },
159
+ {
160
+ term: '百分比骰子',
161
+ definition: '同时投掷两颗D10骰子(一颗代表十位数,一颗代表个位数)以获取1到100之间的数值判定。',
162
+ },
163
+ ],
164
+ },
165
+ ],
166
+ faq: [
167
+ {
168
+ question: '概率模拟器是如何运作的?',
169
+ answer: '它利用排列组合数学方法计算出选定骰子和修正值组合的精确概率分布,从而让您能直观查看骰出高于或低于目标数值的几率。',
170
+ },
171
+ {
172
+ question: '支持哪些类型的骰子?',
173
+ answer: '支持TRPG跑团和各类桌游中常用的所有标准多面体骰子,包括D4, D6, D8, D10, D12, D20以及D100。',
174
+ },
175
+ {
176
+ question: '修正值是什么?如何起作用?',
177
+ answer: '修正值是代表角色技能熟练度、外部增益或减益效果的固定数值(如+1、-2等)。它会直接加到或减去您的骰子投掷出目总和。',
178
+ },
179
+ {
180
+ question: '带有优势或劣势判定投掷是什么意思?',
181
+ answer: '优势投掷指同时投掷两颗D20骰子并保留较大的那颗出目;劣势投掷指保留较小的那颗出目。这是跑团游戏中的经典判定机制。',
182
+ },
183
+ {
184
+ question: '大成功与大失败分别指什么?',
185
+ answer: '大成功通常指骰子投出了其出目上限(如D20的20),表示绝对的成功或双倍的伤害判定。大失败指骰子投出了出目下限1,表示绝对的失败且往往伴随不利影响。',
186
+ },
187
+ {
188
+ question: '模拟器是如何计算投掷概率的?',
189
+ answer: '模拟器在数学上穷举计算了选定骰子池和修正值所有出目的排列组合,进而展示每种可能出目总和的精确占比百分比。',
190
+ },
191
+ {
192
+ question: '为什么投掷3d6比投掷单颗D20更为稳定?',
193
+ answer: '投掷单颗D20时,每个出目出现的概率都是均等的5%。而投掷3d6时,三个出目相加会使得结果形成钟形曲线,概率集中在平均值10.5附近,使极端的高低数值极其罕见。',
194
+ },
195
+ ],
196
+ bibliography,
197
+ howTo: [
198
+ {
199
+ name: '添加骰子',
200
+ text: '点击骰子图标,将它们加入到您当前准备投掷的骰子池中。',
201
+ },
202
+ {
203
+ name: '添加修正值',
204
+ text: '输入正数或负数修正值,以便应用到最后的总和计算中。',
205
+ },
206
+ {
207
+ name: '投掷并查看统计',
208
+ text: '点击“投掷骰子”按钮以触发投掷动画、投掷历史记录和实时的概率分布图。',
209
+ },
210
+ ],
211
+ schemas: [
212
+ {
213
+ '@context': 'https://schema.org',
214
+ '@type': 'SoftwareApplication',
215
+ 'name': '在线骰子模拟器与概率计算器',
216
+ 'operatingSystem': 'All',
217
+ 'applicationCategory': 'UtilitiesApplication',
218
+ 'browserRequirements': 'Requires HTML5. Requires JavaScript.'
219
+ },
220
+ {
221
+ '@context': 'https://schema.org',
222
+ '@type': 'FAQPage',
223
+ 'mainEntity': [
224
+ {
225
+ '@type': 'Question',
226
+ 'name': '概率模拟器是如何运作的?',
227
+ 'acceptedAnswer': {
228
+ '@type': 'Answer',
229
+ 'text': '它利用排列组合数学方法计算出选定骰子和修正值组合的精确概率分布,从而让您能直观查看骰出高于或低于目标数值的几率。'
230
+ }
231
+ },
232
+ {
233
+ '@type': 'Question',
234
+ 'name': '支持哪些类型的骰子?',
235
+ 'acceptedAnswer': {
236
+ '@type': 'Answer',
237
+ 'text': '支持TRPG跑团和各类桌游中常用的所有标准多面体骰子,包括D4, D6, D8, D10, D12, D20以及D100。'
238
+ }
239
+ },
240
+ {
241
+ '@type': 'Question',
242
+ 'name': '修正值是什么?如何起作用?',
243
+ 'acceptedAnswer': {
244
+ '@type': 'Answer',
245
+ 'text': '修正值是代表角色技能熟练度、外部增益或减益效果的固定数值(如+1、-2等)。它会直接加到或减去您的骰子投掷出目总和。'
246
+ }
247
+ },
248
+ {
249
+ '@type': 'Question',
250
+ 'name': '带有优势或劣势判定投掷是什么意思?',
251
+ 'acceptedAnswer': {
252
+ '@type': 'Answer',
253
+ 'text': '优势投掷指同时投掷两颗D20骰子并保留较大的那颗出目;劣势投掷指保留较小的那颗出目。这是跑团游戏中的经典判定机制。'
254
+ }
255
+ },
256
+ {
257
+ '@type': 'Question',
258
+ 'name': '大成功与大失败分别指什么?',
259
+ 'acceptedAnswer': {
260
+ '@type': 'Answer',
261
+ 'text': '大成功通常指骰子投出了其出目上限(如D20的20),表示绝对的成功或双倍的伤害判定。大失败指骰子投出了出目下限1,表示绝对的失败且往往伴随不利影响。'
262
+ }
263
+ },
264
+ {
265
+ '@type': 'Question',
266
+ 'name': '模拟器是如何计算投掷概率的?',
267
+ 'acceptedAnswer': {
268
+ '@type': 'Answer',
269
+ 'text': '模拟器在数学上穷举计算了选定骰子池和修正值所有出目的排列组合,进而展示每种可能出目总和的精确占比百分比 。'
270
+ }
271
+ },
272
+ {
273
+ '@type': 'Question',
274
+ 'name': '为什么投掷3d6比投掷单颗D20更为稳定?',
275
+ 'acceptedAnswer': {
276
+ '@type': 'Answer',
277
+ 'text': '投掷单颗D20时,每个出目出现的概率都是均等的5%。而投掷3d6时,三个出目相加会使得结果形成钟形曲线,概率集中在平均值10.5附近,使极端的高低数值极其罕见。'
278
+ }
279
+ }
280
+ ]
281
+ },
282
+ {
283
+ '@context': 'https://schema.org',
284
+ '@type': 'HowTo',
285
+ 'name': 'How to Use the Dice Roller Simulator',
286
+ 'step': [
287
+ {
288
+ '@type': 'HowToStep',
289
+ 'name': '添加骰子',
290
+ 'text': '点击骰子图标,将它们加入到您当前准备投掷的骰子池中。'
291
+ },
292
+ {
293
+ '@type': 'HowToStep',
294
+ 'name': '添加修正值',
295
+ 'text': '输入正数或负数修正值,以便应用到最后的总和计算中。'
296
+ },
297
+ {
298
+ '@type': 'HowToStep',
299
+ 'name': '投掷并查看统计',
300
+ 'text': '点击“投掷骰子”按钮以触发投掷动画、投掷历史记录和实时的概率分布图。'
301
+ }
302
+ ]
303
+ }
304
+ ],
305
+ };
@@ -0,0 +1,9 @@
1
+ import type { ToolDefinition } from '../../types';
2
+ import { diceRollerSimulator } from './entry';
3
+ export * from './entry';
4
+ export const DICE_ROLLER_SIMULATOR_TOOL: ToolDefinition = {
5
+ entry: diceRollerSimulator,
6
+ Component: () => import('./component.astro'),
7
+ SEOComponent: () => import('./seo.astro'),
8
+ BibliographyComponent: () => import('./bibliography.astro'),
9
+ };
@@ -0,0 +1,32 @@
1
+ export function drawDistributionChart(container: HTMLElement | null, totals: number[]) {
2
+ if (!container) {
3
+ return;
4
+ }
5
+ container.innerHTML = '';
6
+
7
+ if (totals.length === 0) {
8
+ container.innerHTML = '<div class="chart-empty-state">No rolls recorded yet</div>';
9
+ return;
10
+ }
11
+
12
+ const counts = new Map<number, number>();
13
+ for (const val of totals) {
14
+ counts.set(val, (counts.get(val) || 0) + 1);
15
+ }
16
+
17
+ const uniqueTotals = Array.from(counts.keys()).sort((a, b) => a - b);
18
+ const maxCount = Math.max(...Array.from(counts.values()));
19
+
20
+ for (const total of uniqueTotals) {
21
+ const count = counts.get(total) || 0;
22
+ const heightPercent = (count / maxCount) * 100;
23
+
24
+ const barCol = document.createElement('div');
25
+ barCol.className = 'chart-bar-column';
26
+ barCol.innerHTML = `
27
+ <div class="chart-bar-fill" style="height: ${heightPercent}%" data-count="${count}"></div>
28
+ <span class="chart-bar-label">${total}</span>
29
+ `;
30
+ container.appendChild(barCol);
31
+ }
32
+ }
@@ -0,0 +1,87 @@
1
+ export interface RollLog {
2
+ id: string;
3
+ timestamp: string;
4
+ dice: string[];
5
+ modifier: number;
6
+ total: number;
7
+ }
8
+
9
+ const STORAGE_KEY = 'jjlmoya_tabletop_roll_history';
10
+
11
+ export function getHistory(): RollLog[] {
12
+ const data = localStorage.getItem(STORAGE_KEY);
13
+ return data ? JSON.parse(data) : [];
14
+ }
15
+
16
+ export function saveHistory(history: RollLog[]) {
17
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(history));
18
+ }
19
+
20
+ function createHistoryCard(log: RollLog): HTMLDivElement {
21
+ const item = document.createElement('div');
22
+ item.className = 'history-card-item';
23
+ const diceTagsHtml = log.dice.map(d => {
24
+ const parts = d.split(':');
25
+ return `<span class="breakdown-die-tag" data-die-type="${parts[0]}">${parts[1]}</span>`;
26
+ }).join(' ');
27
+
28
+ item.innerHTML = `
29
+ <div class="history-card-header">
30
+ <span>${new Date(log.timestamp).toLocaleTimeString()}</span>
31
+ <span>Mod: ${log.modifier >= 0 ? '+' : ''}${log.modifier}</span>
32
+ </div>
33
+ <div class="history-card-body">
34
+ <div class="history-dice-tags">${diceTagsHtml}</div>
35
+ <span class="history-grand-total">${log.total}</span>
36
+ </div>
37
+ `;
38
+ return item;
39
+ }
40
+
41
+ interface HistoryUIRefs {
42
+ list: HTMLElement | null;
43
+ emptyMsg: HTMLElement | null;
44
+ totalVal: HTMLElement | null;
45
+ averageVal: HTMLElement | null;
46
+ lastVal: HTMLElement | null;
47
+ badge: HTMLElement | null;
48
+ }
49
+
50
+ function setText(el: HTMLElement | null, text: string) {
51
+ if (el) {
52
+ el.textContent = text;
53
+ }
54
+ }
55
+
56
+ export function renderHistory(refs: HistoryUIRefs, onDrawChart: (totals: number[]) => void) {
57
+ const history = getHistory();
58
+ if (!refs.list || !refs.emptyMsg) return;
59
+
60
+ if (history.length === 0) {
61
+ refs.emptyMsg.style.display = 'block';
62
+ refs.list.innerHTML = '';
63
+ setText(refs.totalVal, '0');
64
+ setText(refs.averageVal, '--');
65
+ setText(refs.lastVal, '--');
66
+ setText(refs.badge, '0');
67
+ onDrawChart([]);
68
+ return;
69
+ }
70
+
71
+ refs.emptyMsg.style.display = 'none';
72
+ refs.list.innerHTML = '';
73
+
74
+ const totals = history.map(h => h.total);
75
+ const sum = totals.reduce((a, b) => a + b, 0);
76
+
77
+ for (let i = history.length - 1; i >= 0; i--) {
78
+ refs.list.appendChild(createHistoryCard(history[i]));
79
+ }
80
+
81
+ setText(refs.totalVal, history.length.toString());
82
+ setText(refs.badge, history.length.toString());
83
+ setText(refs.averageVal, (sum / history.length).toFixed(1));
84
+ setText(refs.lastVal, history[history.length - 1].total.toString());
85
+
86
+ onDrawChart(totals);
87
+ }
@@ -0,0 +1,22 @@
1
+ export function spawnParticle(container: HTMLElement, text: string, x?: number, y?: number) {
2
+ const el = document.createElement('span');
3
+ el.className = 'floating-magic-particle';
4
+ el.textContent = text;
5
+ if (x !== undefined && y !== undefined) {
6
+ el.style.left = `${x}px`;
7
+ el.style.top = `${y}px`;
8
+ } else {
9
+ el.style.left = '50%';
10
+ el.style.top = '20%';
11
+ }
12
+ container.appendChild(el);
13
+ setTimeout(() => el.remove(), 600);
14
+ }
15
+
16
+ export function spawnRollPop(container: HTMLElement, total: number) {
17
+ const el = document.createElement('div');
18
+ el.className = 'floating-roll-pop';
19
+ el.textContent = `${total >= 0 ? '+' : ''}${total}`;
20
+ container.appendChild(el);
21
+ setTimeout(() => el.remove(), 1000);
22
+ }
@@ -0,0 +1,16 @@
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { diceRollerSimulator } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'en' } = Astro.props;
11
+ const loader = diceRollerSimulator.i18n[locale] || diceRollerSimulator.i18n.en;
12
+ const content = await loader?.();
13
+ if (!content) return null;
14
+ ---
15
+
16
+ {content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
package/src/tools.ts ADDED
@@ -0,0 +1,9 @@
1
+ export { ALL_ENTRIES } from './entries';
2
+ import type { ToolDefinition } from './types';
3
+ import { DICE_ROLLER_SIMULATOR_TOOL } from './tool/dice-roller-simulator';
4
+ import { BOARD_GAME_TIMER_TOOL } from './tool/board-game-timer';
5
+
6
+ export const ALL_TOOLS: ToolDefinition[] = [
7
+ DICE_ROLLER_SIMULATOR_TOOL,
8
+ BOARD_GAME_TIMER_TOOL,
9
+ ];
package/src/types.ts ADDED
@@ -0,0 +1,70 @@
1
+ import type { SEOSection } from '@jjlmoya/utils-shared';
2
+ import type { WithContext, Thing } from 'schema-dts';
3
+
4
+ export type { SEOSection };
5
+
6
+ export type KnownLocale =
7
+ | 'ar' | 'da' | 'de' | 'en' | 'es' | 'fi'
8
+ | 'fr' | 'id' | 'it' | 'ja' | 'ko' | 'nb' | 'nl'
9
+ | 'pl' | 'pt' | 'ru' | 'sv' | 'tr' | 'zh';
10
+
11
+ export interface FAQItem {
12
+ question: string;
13
+ answer: string;
14
+ }
15
+
16
+ export interface BibliographyEntry {
17
+ name: string;
18
+ url: string;
19
+ }
20
+
21
+ export interface HowToStep {
22
+ name: string;
23
+ text: string;
24
+ }
25
+
26
+ export interface ToolLocaleContent<TUI extends Record<string, string> = Record<string, string>> {
27
+ slug: string;
28
+ title: string;
29
+ description: string;
30
+ ui: TUI;
31
+ seo: SEOSection[];
32
+ faq: FAQItem[];
33
+ bibliography: BibliographyEntry[];
34
+ howTo: HowToStep[];
35
+ schemas: WithContext<Thing>[];
36
+ }
37
+
38
+ export interface CategoryLocaleContent {
39
+ slug: string;
40
+ title: string;
41
+ description: string;
42
+ seo: SEOSection[];
43
+ }
44
+
45
+ export type LocaleLoader<T> = () => Promise<T>;
46
+
47
+ export type LocaleMap<T> = Partial<Record<KnownLocale, LocaleLoader<T>>>;
48
+
49
+ export interface TabletopToolEntry<TUI extends Record<string, string> = Record<string, string>> {
50
+ id: string;
51
+ icons: {
52
+ bg: string;
53
+ fg: string;
54
+ };
55
+ i18n: LocaleMap<ToolLocaleContent<TUI>>;
56
+ }
57
+
58
+ export interface TabletopCategoryEntry {
59
+ icon: string;
60
+ tools: TabletopToolEntry[];
61
+ i18n: LocaleMap<CategoryLocaleContent>;
62
+ }
63
+
64
+ export interface ToolDefinition {
65
+ entry: TabletopToolEntry;
66
+ Component: unknown;
67
+ SEOComponent: unknown;
68
+ BibliographyComponent: unknown;
69
+ }
70
+