@jjlmoya/utils-tabletop 1.4.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 (123) hide show
  1. package/package.json +1 -1
  2. package/src/entries.ts +11 -1
  3. package/src/index.ts +1 -0
  4. package/src/tests/locale_completeness.test.ts +1 -1
  5. package/src/tests/tool_validation.test.ts +1 -1
  6. package/src/tool/decision-wheel/audio.ts +78 -0
  7. package/src/tool/decision-wheel/bibliography.astro +16 -0
  8. package/src/tool/decision-wheel/bibliography.ts +8 -0
  9. package/src/tool/decision-wheel/client.ts +226 -0
  10. package/src/tool/decision-wheel/component.astro +48 -0
  11. package/src/tool/decision-wheel/components/SegmentEditor.astro +22 -0
  12. package/src/tool/decision-wheel/components/SpinHistory.astro +17 -0
  13. package/src/tool/decision-wheel/components/SpinResult.astro +13 -0
  14. package/src/tool/decision-wheel/components/WheelCanvas.astro +23 -0
  15. package/src/tool/decision-wheel/decision-wheel.css +632 -0
  16. package/src/tool/decision-wheel/drawing.ts +144 -0
  17. package/src/tool/decision-wheel/entry.ts +48 -0
  18. package/src/tool/decision-wheel/i18n/de.ts +97 -0
  19. package/src/tool/decision-wheel/i18n/en.ts +251 -0
  20. package/src/tool/decision-wheel/i18n/es.ts +93 -0
  21. package/src/tool/decision-wheel/i18n/fr.ts +93 -0
  22. package/src/tool/decision-wheel/i18n/id.ts +93 -0
  23. package/src/tool/decision-wheel/i18n/it.ts +93 -0
  24. package/src/tool/decision-wheel/i18n/ja.ts +93 -0
  25. package/src/tool/decision-wheel/i18n/ko.ts +93 -0
  26. package/src/tool/decision-wheel/i18n/nl.ts +93 -0
  27. package/src/tool/decision-wheel/i18n/pl.ts +93 -0
  28. package/src/tool/decision-wheel/i18n/pt.ts +93 -0
  29. package/src/tool/decision-wheel/i18n/ru.ts +75 -0
  30. package/src/tool/decision-wheel/i18n/sv.ts +93 -0
  31. package/src/tool/decision-wheel/i18n/tr.ts +93 -0
  32. package/src/tool/decision-wheel/i18n/zh.ts +93 -0
  33. package/src/tool/decision-wheel/index.ts +9 -0
  34. package/src/tool/decision-wheel/logic.test.ts +163 -0
  35. package/src/tool/decision-wheel/logic.ts +145 -0
  36. package/src/tool/decision-wheel/seo.astro +16 -0
  37. package/src/tool/decision-wheel/types.ts +14 -0
  38. package/src/tool/decision-wheel/ui.ts +121 -0
  39. package/src/tool/fantasy-runes-translator/bibliography.astro +16 -0
  40. package/src/tool/fantasy-runes-translator/bibliography.ts +16 -0
  41. package/src/tool/fantasy-runes-translator/client.ts +205 -0
  42. package/src/tool/fantasy-runes-translator/component.astro +95 -0
  43. package/src/tool/fantasy-runes-translator/entry.ts +56 -0
  44. package/src/tool/fantasy-runes-translator/fantasy-runes-translator.css +712 -0
  45. package/src/tool/fantasy-runes-translator/i18n/de.ts +139 -0
  46. package/src/tool/fantasy-runes-translator/i18n/en.ts +139 -0
  47. package/src/tool/fantasy-runes-translator/i18n/es.ts +139 -0
  48. package/src/tool/fantasy-runes-translator/i18n/fr.ts +139 -0
  49. package/src/tool/fantasy-runes-translator/i18n/id.ts +139 -0
  50. package/src/tool/fantasy-runes-translator/i18n/it.ts +139 -0
  51. package/src/tool/fantasy-runes-translator/i18n/ja.ts +139 -0
  52. package/src/tool/fantasy-runes-translator/i18n/ko.ts +139 -0
  53. package/src/tool/fantasy-runes-translator/i18n/nl.ts +139 -0
  54. package/src/tool/fantasy-runes-translator/i18n/pl.ts +139 -0
  55. package/src/tool/fantasy-runes-translator/i18n/pt.ts +139 -0
  56. package/src/tool/fantasy-runes-translator/i18n/ru.ts +139 -0
  57. package/src/tool/fantasy-runes-translator/i18n/sv.ts +139 -0
  58. package/src/tool/fantasy-runes-translator/i18n/tr.ts +139 -0
  59. package/src/tool/fantasy-runes-translator/i18n/zh.ts +139 -0
  60. package/src/tool/fantasy-runes-translator/index.ts +9 -0
  61. package/src/tool/fantasy-runes-translator/logic.ts +174 -0
  62. package/src/tool/fantasy-runes-translator/seo.astro +16 -0
  63. package/src/tool/fantasy-runes-translator/types.ts +26 -0
  64. package/src/tool/rpg-initiative-tracker/bibliography.astro +16 -0
  65. package/src/tool/rpg-initiative-tracker/bibliography.ts +12 -0
  66. package/src/tool/rpg-initiative-tracker/client.ts +201 -0
  67. package/src/tool/rpg-initiative-tracker/component.astro +23 -0
  68. package/src/tool/rpg-initiative-tracker/components/AddForm.astro +49 -0
  69. package/src/tool/rpg-initiative-tracker/components/CombatantList.astro +16 -0
  70. package/src/tool/rpg-initiative-tracker/components/TurnControls.astro +28 -0
  71. package/src/tool/rpg-initiative-tracker/entry.ts +50 -0
  72. package/src/tool/rpg-initiative-tracker/i18n/de.ts +266 -0
  73. package/src/tool/rpg-initiative-tracker/i18n/en.ts +263 -0
  74. package/src/tool/rpg-initiative-tracker/i18n/es.ts +264 -0
  75. package/src/tool/rpg-initiative-tracker/i18n/fr.ts +263 -0
  76. package/src/tool/rpg-initiative-tracker/i18n/id.ts +263 -0
  77. package/src/tool/rpg-initiative-tracker/i18n/it.ts +263 -0
  78. package/src/tool/rpg-initiative-tracker/i18n/ja.ts +263 -0
  79. package/src/tool/rpg-initiative-tracker/i18n/ko.ts +263 -0
  80. package/src/tool/rpg-initiative-tracker/i18n/nl.ts +263 -0
  81. package/src/tool/rpg-initiative-tracker/i18n/pl.ts +264 -0
  82. package/src/tool/rpg-initiative-tracker/i18n/pt.ts +263 -0
  83. package/src/tool/rpg-initiative-tracker/i18n/ru.ts +264 -0
  84. package/src/tool/rpg-initiative-tracker/i18n/sv.ts +263 -0
  85. package/src/tool/rpg-initiative-tracker/i18n/tr.ts +263 -0
  86. package/src/tool/rpg-initiative-tracker/i18n/zh.ts +263 -0
  87. package/src/tool/rpg-initiative-tracker/index.ts +9 -0
  88. package/src/tool/rpg-initiative-tracker/logic.test.ts +121 -0
  89. package/src/tool/rpg-initiative-tracker/logic.ts +122 -0
  90. package/src/tool/rpg-initiative-tracker/rpg-initiative-tracker.css +617 -0
  91. package/src/tool/rpg-initiative-tracker/seo.astro +16 -0
  92. package/src/tool/rpg-initiative-tracker/types.ts +15 -0
  93. package/src/tool/score-tracker/bibliography.astro +16 -0
  94. package/src/tool/score-tracker/bibliography.ts +12 -0
  95. package/src/tool/score-tracker/client.ts +248 -0
  96. package/src/tool/score-tracker/component.astro +34 -0
  97. package/src/tool/score-tracker/components/Leaderboard.astro +19 -0
  98. package/src/tool/score-tracker/components/PlayerSetup.astro +32 -0
  99. package/src/tool/score-tracker/components/RoundScoring.astro +22 -0
  100. package/src/tool/score-tracker/components/ScoreHistory.astro +25 -0
  101. package/src/tool/score-tracker/entry.ts +57 -0
  102. package/src/tool/score-tracker/i18n/de.ts +278 -0
  103. package/src/tool/score-tracker/i18n/en.ts +278 -0
  104. package/src/tool/score-tracker/i18n/es.ts +251 -0
  105. package/src/tool/score-tracker/i18n/fr.ts +278 -0
  106. package/src/tool/score-tracker/i18n/id.ts +278 -0
  107. package/src/tool/score-tracker/i18n/it.ts +278 -0
  108. package/src/tool/score-tracker/i18n/ja.ts +278 -0
  109. package/src/tool/score-tracker/i18n/ko.ts +278 -0
  110. package/src/tool/score-tracker/i18n/nl.ts +278 -0
  111. package/src/tool/score-tracker/i18n/pl.ts +278 -0
  112. package/src/tool/score-tracker/i18n/pt.ts +278 -0
  113. package/src/tool/score-tracker/i18n/ru.ts +278 -0
  114. package/src/tool/score-tracker/i18n/sv.ts +278 -0
  115. package/src/tool/score-tracker/i18n/tr.ts +278 -0
  116. package/src/tool/score-tracker/i18n/zh.ts +278 -0
  117. package/src/tool/score-tracker/index.ts +9 -0
  118. package/src/tool/score-tracker/logic.test.ts +83 -0
  119. package/src/tool/score-tracker/logic.ts +122 -0
  120. package/src/tool/score-tracker/score-tracker.css +1029 -0
  121. package/src/tool/score-tracker/seo.astro +16 -0
  122. package/src/tool/score-tracker/types.ts +17 -0
  123. package/src/tools.ts +8 -0
@@ -0,0 +1,26 @@
1
+ export interface RuneCharacter {
2
+ symbol: string;
3
+ name: string;
4
+ meaning: string;
5
+ phonetic: string;
6
+ }
7
+
8
+ export type RuneAlphabetKey = 'elder-futhark' | 'anglo-saxon' | 'fantasy' | 'theban' | 'enochian' | 'daemonic' | 'ogham';
9
+
10
+ export interface RuneAlphabet {
11
+ key: RuneAlphabetKey;
12
+ name: string;
13
+ description: string;
14
+ mapping: Record<string, RuneCharacter>;
15
+ }
16
+
17
+ export interface RuneCharacterResult {
18
+ input: string;
19
+ char: RuneCharacter | null;
20
+ }
21
+
22
+ export interface RuneTranslationResult {
23
+ alphabetKey: RuneAlphabetKey;
24
+ characters: RuneCharacterResult[];
25
+ raw: string;
26
+ }
@@ -0,0 +1,16 @@
1
+ ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import { initiativeTracker } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'en' } = Astro.props as Props;
11
+ const loader = initiativeTracker.i18n[locale] || initiativeTracker.i18n.en;
12
+ const content = await loader?.();
13
+ if (!content) return null;
14
+ ---
15
+
16
+ {content && <SharedBibliography links={content.bibliography} />}
@@ -0,0 +1,12 @@
1
+ import type { BibliographyEntry } from '../../types';
2
+
3
+ export const bibliography: BibliographyEntry[] = [
4
+ {
5
+ name: 'Dungeons & Dragons Basic Rules',
6
+ url: 'https://media.wizards.com/2018/dnd/downloads/DnD_BasicRules_2018.pdf',
7
+ },
8
+ {
9
+ name: 'Pathfinder Initiative Rules',
10
+ url: 'https://2e.aonprd.com/Rules.aspx?ID=2539',
11
+ },
12
+ ];
@@ -0,0 +1,201 @@
1
+ import { addCombatant, removeCombatant, toggleStatus, startCombat, nextTurn, prevTurn, endCombat, createBlankState } from './logic';
2
+ import type { InitiativeState } from './types';
3
+
4
+ const STORAGE_KEY = 'initiative-tracker-state';
5
+
6
+ const wrapper = document.querySelector('.init-main-wrapper') as HTMLElement;
7
+ if (!wrapper) throw new Error('Initiative tracker wrapper not found');
8
+
9
+ const ui: Record<string, string> = JSON.parse(wrapper.dataset.ui || '{}');
10
+
11
+ function saveState(): void {
12
+ try { localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); } catch { }
13
+ }
14
+
15
+ function loadState(): InitiativeState {
16
+ try {
17
+ const s = localStorage.getItem(STORAGE_KEY);
18
+ if (s) return JSON.parse(s) as InitiativeState;
19
+ } catch { }
20
+ return createBlankState();
21
+ }
22
+
23
+ let state: InitiativeState = loadState();
24
+
25
+ const $ = (id: string) => document.getElementById(id) as HTMLElement;
26
+ const combatantList = $('init-combatant-list');
27
+ const emptyState = $('init-empty-state');
28
+ const nameInput = $('init-name-input') as HTMLInputElement;
29
+ const initInput = $('init-value-input') as HTMLInputElement;
30
+ const modInput = $('init-mod-input') as HTMLInputElement;
31
+ const addBtn = $('init-add-btn');
32
+ const startBtn = $('init-start-btn');
33
+ const endBtn = $('init-end-btn');
34
+ const nextBtn = $('init-next-btn');
35
+ const prevBtn = $('init-prev-btn');
36
+ const roundLabel = $('init-round-label');
37
+
38
+ let currentType: 'player' | 'npc' = 'player';
39
+
40
+ function renderCombatantList(): void {
41
+ if (state.combatants.length === 0) {
42
+ combatantList.innerHTML = '';
43
+ combatantList.appendChild(emptyState);
44
+ return;
45
+ }
46
+ const items = state.combatants.map((c, i) => {
47
+ const isActive = state.isCombatActive && i === state.currentIndex;
48
+ const typeClass = c.isPlayer ? 'player-order' : 'npc-order';
49
+ const activeClass = isActive ? ' active' : '';
50
+ const npcClass = c.isPlayer ? '' : ' npc';
51
+ const orderClass = isActive ? ' active-order' : ' ' + typeClass;
52
+ const statusHtml = c.statuses.map(s => {
53
+ return `<span class="init-status-tag active" data-status="${s}">${s}</span>`;
54
+ }).join('');
55
+ return `<div class="init-combatant-item${activeClass}${npcClass}" data-cbt-id="${c.id}"><span class="init-cbt-order${orderClass}">${i + 1}</span><span class="init-cbt-name">${c.name}</span><span class="init-cbt-init">${c.initiative}</span><div class="init-cbt-statuses">${statusHtml}</div><div class="init-cbt-actions"><button type="button" class="init-btn-icon remove" data-action="remove" title="${ui.removeCombatant}"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button></div></div>`;
56
+ }).join('');
57
+ combatantList.innerHTML = items;
58
+ combatantList.querySelectorAll('[data-action="remove"]').forEach(b => b.addEventListener('click', onRemove));
59
+ combatantList.querySelectorAll('.init-status-tag').forEach(t => {
60
+ t.addEventListener('click', onToggleStatus);
61
+ });
62
+ }
63
+
64
+ function onRemove(e: Event): void {
65
+ const item = (e.currentTarget as HTMLElement).closest('.init-combatant-item') as HTMLElement;
66
+ const id = item?.dataset.cbtId;
67
+ if (id) {
68
+ state = removeCombatant(state, id);
69
+ renderAll();
70
+ }
71
+ }
72
+
73
+ function onToggleStatus(e: Event): void {
74
+ const tag = e.currentTarget as HTMLElement;
75
+ const item = tag.closest('.init-combatant-item') as HTMLElement;
76
+ const id = item?.dataset.cbtId;
77
+ const status = tag.dataset.status;
78
+ if (id && status) {
79
+ state = toggleStatus(state, id, status);
80
+ renderAll();
81
+ }
82
+ }
83
+
84
+ function spawnTurnParticle(text: string): void {
85
+ const el = document.createElement('div');
86
+ el.className = 'init-turn-particle';
87
+ el.textContent = text;
88
+ el.style.cssText = 'left:50%;top:50%;position:fixed';
89
+ document.body.appendChild(el);
90
+ setTimeout(() => el.remove(), 1000);
91
+ }
92
+
93
+ function longPress(el: HTMLElement, duration: number, cb: () => void): void {
94
+ let timer: number | null = null;
95
+ const start = (): void => {
96
+ el.style.setProperty('--lp-dur', duration + 'ms');
97
+ el.classList.add('lp-active');
98
+ timer = window.setTimeout(() => {
99
+ el.classList.remove('lp-active');
100
+ cb();
101
+ }, duration);
102
+ };
103
+ const cancel = (): void => {
104
+ if (timer !== null) {
105
+ clearTimeout(timer);
106
+ timer = null;
107
+ }
108
+ el.classList.remove('lp-active');
109
+ };
110
+ el.addEventListener('mousedown', start);
111
+ el.addEventListener('mouseup', cancel);
112
+ el.addEventListener('mouseleave', cancel);
113
+ el.addEventListener('touchstart', start);
114
+ el.addEventListener('touchend', cancel);
115
+ el.addEventListener('touchcancel', cancel);
116
+ }
117
+
118
+ function getActiveCombatantName(): string {
119
+ const c = state.combatants[state.currentIndex];
120
+ return c ? c.name : '';
121
+ }
122
+
123
+ function renderAll(): void {
124
+ renderCombatantList();
125
+ const hasCombatants = state.combatants.length > 0;
126
+ const inCombat = state.isCombatActive;
127
+
128
+ if (inCombat && hasCombatants) {
129
+ roundLabel.textContent = ui.roundLabel + ' ' + state.round + ' - ' + ui.turnLabel + ' ' + (state.currentIndex + 1);
130
+ } else {
131
+ roundLabel.textContent = ui.roundLabel + ': --';
132
+ }
133
+
134
+ startBtn.style.display = inCombat ? 'none' : 'inline-block';
135
+ startBtn.disabled = !hasCombatants;
136
+ endBtn.style.display = inCombat ? 'inline-block' : 'none';
137
+ endBtn.disabled = !inCombat;
138
+ nextBtn.disabled = !inCombat;
139
+ prevBtn.disabled = !inCombat;
140
+
141
+ saveState();
142
+ }
143
+
144
+ addBtn.addEventListener('click', () => {
145
+ const name = nameInput.value.trim();
146
+ const initiative = parseInt(initInput.value, 10);
147
+ const modifier = parseInt(modInput.value, 10) || 0;
148
+ if (!name || isNaN(initiative)) return;
149
+ state = addCombatant(state, name, initiative, { modifier, isPlayer: currentType === 'player' });
150
+ nameInput.value = '';
151
+ initInput.value = '';
152
+ modInput.value = '';
153
+ nameInput.focus();
154
+ renderAll();
155
+ });
156
+
157
+ initInput.addEventListener('keydown', (e) => {
158
+ if (e.key === 'Enter') addBtn.click();
159
+ });
160
+
161
+ nameInput.addEventListener('keydown', (e) => {
162
+ if (e.key === 'Enter') initInput.focus();
163
+ });
164
+
165
+ const toggleBtns = document.querySelectorAll('.init-toggle-btn');
166
+ toggleBtns.forEach(btn => {
167
+ btn.addEventListener('click', () => {
168
+ toggleBtns.forEach(b => b.classList.remove('active'));
169
+ btn.classList.add('active');
170
+ currentType = btn.dataset.type as 'player' | 'npc';
171
+ });
172
+ });
173
+
174
+ startBtn.addEventListener('click', () => {
175
+ state = startCombat(state);
176
+ const name = getActiveCombatantName();
177
+ if (name) spawnTurnParticle(name + ' ' + ui.yourTurn);
178
+ renderAll();
179
+ });
180
+
181
+ nextBtn.addEventListener('click', () => {
182
+ state = nextTurn(state);
183
+ const name = getActiveCombatantName();
184
+ if (name) spawnTurnParticle(name + ' ' + ui.yourTurn);
185
+ renderAll();
186
+ });
187
+
188
+ prevBtn.addEventListener('click', () => {
189
+ state = prevTurn(state);
190
+ const name = getActiveCombatantName();
191
+ if (name) spawnTurnParticle(name + ' ' + ui.yourTurn);
192
+ renderAll();
193
+ });
194
+
195
+ longPress(endBtn, 1000, () => {
196
+ state = endCombat(state);
197
+ spawnTurnParticle(ui.combatEnded);
198
+ renderAll();
199
+ });
200
+
201
+ renderAll();
@@ -0,0 +1,23 @@
1
+ ---
2
+ import './rpg-initiative-tracker.css';
3
+ import AddForm from './components/AddForm.astro';
4
+ import CombatantList from './components/CombatantList.astro';
5
+ import TurnControls from './components/TurnControls.astro';
6
+
7
+ interface Props {
8
+ ui: Record<string, string>;
9
+ }
10
+
11
+ const { ui } = Astro.props;
12
+ ---
13
+
14
+ <div class="init-main-wrapper" data-ui={JSON.stringify(ui)}>
15
+ <div class="init-header">
16
+ <h2>{ui.title}</h2>
17
+ </div>
18
+ <AddForm labels={ui} />
19
+ <CombatantList labels={ui} />
20
+ <TurnControls labels={ui} />
21
+ </div>
22
+
23
+ <script src="./client.ts"></script>
@@ -0,0 +1,49 @@
1
+ ---
2
+ interface Props {
3
+ labels: Record<string, string>;
4
+ }
5
+
6
+ const { labels } = Astro.props;
7
+ ---
8
+
9
+ <div class="init-add-form" id="init-add-form">
10
+ <div class="init-form-group">
11
+ <label for="init-name-input">{labels.namePlaceholder}</label>
12
+ <input
13
+ type="text"
14
+ class="init-input"
15
+ id="init-name-input"
16
+ placeholder={labels.namePlaceholder}
17
+ autocomplete="off"
18
+ />
19
+ </div>
20
+ <div class="init-form-group">
21
+ <label for="init-value-input">{labels.initiativeLabel}</label>
22
+ <input
23
+ type="number"
24
+ class="init-input"
25
+ id="init-value-input"
26
+ placeholder={labels.initiativePlaceholder}
27
+ />
28
+ </div>
29
+ <div class="init-form-group">
30
+ <label for="init-mod-input">{labels.modifierLabel}</label>
31
+ <input
32
+ type="number"
33
+ class="init-input"
34
+ id="init-mod-input"
35
+ placeholder={labels.modifierPlaceholder}
36
+ />
37
+ </div>
38
+ <div class="init-toggle-group" id="init-type-toggle">
39
+ <button type="button" class="init-toggle-btn player active" data-type="player">
40
+ {labels.playerLabel}
41
+ </button>
42
+ <button type="button" class="init-toggle-btn npc" data-type="npc">
43
+ {labels.npcLabel}
44
+ </button>
45
+ </div>
46
+ <button type="button" class="init-btn-add" id="init-add-btn">
47
+ {labels.addButton}
48
+ </button>
49
+ </div>
@@ -0,0 +1,16 @@
1
+ ---
2
+ interface Props {
3
+ labels: Record<string, string>;
4
+ }
5
+
6
+ const { labels } = Astro.props;
7
+ ---
8
+
9
+ <div id="init-combatant-list" class="init-combatant-list">
10
+ <div id="init-empty-state" class="init-empty-state">
11
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
12
+ <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
13
+ </svg>
14
+ <span>{labels.noCombatants}</span>
15
+ </div>
16
+ </div>
@@ -0,0 +1,28 @@
1
+ ---
2
+ interface Props {
3
+ labels: Record<string, string>;
4
+ }
5
+
6
+ const { labels } = Astro.props;
7
+ ---
8
+
9
+ <div class="init-round-display" id="init-round-display">
10
+ <span class="init-round-number" id="init-round-label">
11
+ {labels.roundLabel}: --
12
+ </span>
13
+ </div>
14
+
15
+ <div class="init-turn-controls" id="init-turn-controls">
16
+ <button type="button" class="init-turn-btn primary" id="init-start-btn">
17
+ {labels.startCombat}
18
+ </button>
19
+ <button type="button" class="init-turn-btn" id="init-prev-btn" disabled>
20
+ {labels.prevTurn}
21
+ </button>
22
+ <button type="button" class="init-turn-btn" id="init-next-btn" disabled>
23
+ {labels.nextTurn}
24
+ </button>
25
+ <button type="button" class="init-turn-btn danger" id="init-end-btn" disabled>
26
+ {labels.endCombat}
27
+ </button>
28
+ </div>
@@ -0,0 +1,50 @@
1
+ import type { TabletopToolEntry, ToolLocaleContent } from '../../types';
2
+
3
+ export type InitiativeTrackerUI = {
4
+ title: string;
5
+ addCombatant: string;
6
+ removeCombatant: string;
7
+ playerLabel: string;
8
+ npcLabel: string;
9
+ initiativeLabel: string;
10
+ modifierLabel: string;
11
+ namePlaceholder: string;
12
+ initiativePlaceholder: string;
13
+ modifierPlaceholder: string;
14
+ addButton: string;
15
+ startCombat: string;
16
+ endCombat: string;
17
+ nextTurn: string;
18
+ prevTurn: string;
19
+ roundLabel: string;
20
+ turnLabel: string;
21
+ noCombatants: string;
22
+ statusInput: string;
23
+ combatStarted: string;
24
+ combatEnded: string;
25
+ yourTurn: string;
26
+ };
27
+
28
+ export type InitiativeTrackerLocaleContent = ToolLocaleContent<InitiativeTrackerUI>;
29
+
30
+ export const initiativeTracker: TabletopToolEntry<InitiativeTrackerUI> = {
31
+ id: 'rpg-initiative-tracker',
32
+ icons: { bg: 'mdi:sword-cross', fg: 'mdi:sword-cross' },
33
+ i18n: {
34
+ de: () => import('./i18n/de').then((m) => m.content),
35
+ en: () => import('./i18n/en').then((m) => m.content),
36
+ es: () => import('./i18n/es').then((m) => m.content),
37
+ fr: () => import('./i18n/fr').then((m) => m.content),
38
+ id: () => import('./i18n/id').then((m) => m.content),
39
+ it: () => import('./i18n/it').then((m) => m.content),
40
+ ja: () => import('./i18n/ja').then((m) => m.content),
41
+ ko: () => import('./i18n/ko').then((m) => m.content),
42
+ nl: () => import('./i18n/nl').then((m) => m.content),
43
+ pl: () => import('./i18n/pl').then((m) => m.content),
44
+ pt: () => import('./i18n/pt').then((m) => m.content),
45
+ ru: () => import('./i18n/ru').then((m) => m.content),
46
+ sv: () => import('./i18n/sv').then((m) => m.content),
47
+ tr: () => import('./i18n/tr').then((m) => m.content),
48
+ zh: () => import('./i18n/zh').then((m) => m.content),
49
+ },
50
+ };