@jjlmoya/utils-tabletop 1.11.0 → 1.13.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 (35) hide show
  1. package/package.json +1 -1
  2. package/src/entries.ts +4 -0
  3. package/src/tests/locale_completeness.test.ts +1 -1
  4. package/src/tests/tool_validation.test.ts +1 -1
  5. package/src/tool/dice-roller-simulator/component.astro +116 -26
  6. package/src/tool/scatter-direction-selector/animation.ts +278 -0
  7. package/src/tool/scatter-direction-selector/bibliography.astro +16 -0
  8. package/src/tool/scatter-direction-selector/bibliography.ts +12 -0
  9. package/src/tool/scatter-direction-selector/client.ts +293 -0
  10. package/src/tool/scatter-direction-selector/component.astro +183 -0
  11. package/src/tool/scatter-direction-selector/entry.ts +59 -0
  12. package/src/tool/scatter-direction-selector/history.ts +71 -0
  13. package/src/tool/scatter-direction-selector/i18n/de.ts +143 -0
  14. package/src/tool/scatter-direction-selector/i18n/en.ts +231 -0
  15. package/src/tool/scatter-direction-selector/i18n/es.ts +143 -0
  16. package/src/tool/scatter-direction-selector/i18n/fr.ts +143 -0
  17. package/src/tool/scatter-direction-selector/i18n/id.ts +143 -0
  18. package/src/tool/scatter-direction-selector/i18n/it.ts +143 -0
  19. package/src/tool/scatter-direction-selector/i18n/ja.ts +143 -0
  20. package/src/tool/scatter-direction-selector/i18n/ko.ts +143 -0
  21. package/src/tool/scatter-direction-selector/i18n/nl.ts +143 -0
  22. package/src/tool/scatter-direction-selector/i18n/pl.ts +143 -0
  23. package/src/tool/scatter-direction-selector/i18n/pt.ts +143 -0
  24. package/src/tool/scatter-direction-selector/i18n/ru.ts +143 -0
  25. package/src/tool/scatter-direction-selector/i18n/sv.ts +143 -0
  26. package/src/tool/scatter-direction-selector/i18n/tr.ts +143 -0
  27. package/src/tool/scatter-direction-selector/i18n/zh.ts +143 -0
  28. package/src/tool/scatter-direction-selector/index.ts +10 -0
  29. package/src/tool/scatter-direction-selector/logic.test.ts +62 -0
  30. package/src/tool/scatter-direction-selector/logic.ts +52 -0
  31. package/src/tool/scatter-direction-selector/scatter-direction-selector.css +809 -0
  32. package/src/tool/scatter-direction-selector/seo.astro +16 -0
  33. package/src/tool/scatter-direction-selector/settings.ts +81 -0
  34. package/src/tool/scatter-direction-selector/sound.ts +22 -0
  35. package/src/tools.ts +2 -0
@@ -0,0 +1,16 @@
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { scatterDirectionSelector } 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 = scatterDirectionSelector.i18n[locale] || scatterDirectionSelector.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 }} />}
@@ -0,0 +1,81 @@
1
+ export class SettingsBinder {
2
+ private sectorsCount: number = 8;
3
+ private diceType: string = 'd6';
4
+ private maxCustomDist: number = 12;
5
+ private hitChance: number = 33;
6
+ private windDriftMode: boolean = false;
7
+
8
+ onSectorsChange: ((count: number) => void) | null = null;
9
+ onModeChange: ((wind: boolean) => void) | null = null;
10
+ onDiceChange: ((dice: string) => void) | null = null;
11
+ onCustomMaxChange: ((max: number) => void) | null = null;
12
+ onHitChanceChange: ((chance: number) => void) | null = null;
13
+
14
+ getSectors(): number { return this.sectorsCount; }
15
+ getDice(): string { return this.diceType; }
16
+ getMode(): boolean { return this.windDriftMode; }
17
+ getMaxDist(): number { return this.maxCustomDist; }
18
+ getHitChance(): number { return this.hitChance; }
19
+
20
+ bind(root: HTMLElement): void {
21
+ this.bindSectorToggle(root);
22
+ this.bindModeToggle(root);
23
+ this.bindDiceSelect(root);
24
+ this.bindCustomMax(root);
25
+ this.bindHitChance(root);
26
+ }
27
+
28
+ private bindSectorToggle(root: HTMLElement): void {
29
+ const btns = root.querySelectorAll('[data-sectors]');
30
+ btns.forEach((btn) => {
31
+ btn.addEventListener('click', () => {
32
+ btns.forEach((b) => b.classList.remove('active'));
33
+ btn.classList.add('active');
34
+ this.sectorsCount = parseInt(btn.getAttribute('data-sectors') || '8', 10);
35
+ this.onSectorsChange?.(this.sectorsCount);
36
+ });
37
+ });
38
+ }
39
+
40
+ private bindModeToggle(root: HTMLElement): void {
41
+ const btns = root.querySelectorAll('[data-mode]');
42
+ const hint = root.querySelector('#sds-wind-hint') as HTMLElement;
43
+ btns.forEach((btn) => {
44
+ btn.addEventListener('click', () => {
45
+ btns.forEach((b) => b.classList.remove('active'));
46
+ btn.classList.add('active');
47
+ this.windDriftMode = btn.getAttribute('data-mode') === 'wind';
48
+ hint.style.display = this.windDriftMode ? 'block' : 'none';
49
+ this.onModeChange?.(this.windDriftMode);
50
+ });
51
+ });
52
+ }
53
+
54
+ private bindDiceSelect(root: HTMLElement): void {
55
+ const select = root.querySelector('#sds-dice-select') as HTMLSelectElement;
56
+ select.addEventListener('change', () => {
57
+ this.diceType = select.value;
58
+ const wrap = root.querySelector('#sds-custom-dist-wrapper') as HTMLElement;
59
+ wrap.style.display = this.diceType === 'custom' ? 'block' : 'none';
60
+ this.onDiceChange?.(this.diceType);
61
+ });
62
+ }
63
+
64
+ private bindCustomMax(root: HTMLElement): void {
65
+ const input = root.querySelector('#sds-custom-max') as HTMLInputElement;
66
+ input.addEventListener('input', () => {
67
+ this.maxCustomDist = Math.max(1, parseInt(input.value, 10) || 12);
68
+ this.onCustomMaxChange?.(this.maxCustomDist);
69
+ });
70
+ }
71
+
72
+ private bindHitChance(root: HTMLElement): void {
73
+ const input = root.querySelector('#sds-hit-chance') as HTMLInputElement;
74
+ const display = root.querySelector('#sds-hit-chance-val') as HTMLElement;
75
+ input.addEventListener('input', () => {
76
+ this.hitChance = parseInt(input.value, 10);
77
+ display.textContent = `${this.hitChance}%`;
78
+ this.onHitChanceChange?.(this.hitChance);
79
+ });
80
+ }
81
+ }
@@ -0,0 +1,22 @@
1
+ export class SoundManager {
2
+ private ctx: AudioContext | null = null;
3
+ enabled: boolean = true;
4
+
5
+ play(freq: number, type: OscillatorType, dur: number): void {
6
+ if (!this.enabled) return;
7
+ const ACtor = (window.AudioContext || (window as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext) as typeof AudioContext;
8
+ if (!this.ctx) this.ctx = new ACtor();
9
+ if (this.ctx.state === 'suspended') this.ctx.resume();
10
+
11
+ const osc = this.ctx.createOscillator();
12
+ const gain = this.ctx.createGain();
13
+ osc.connect(gain);
14
+ gain.connect(this.ctx.destination);
15
+ osc.type = type;
16
+ osc.frequency.setValueAtTime(freq, this.ctx.currentTime);
17
+ gain.gain.setValueAtTime(0.08, this.ctx.currentTime);
18
+ gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + dur);
19
+ osc.start();
20
+ osc.stop(this.ctx.currentTime + dur);
21
+ }
22
+ }
package/src/tools.ts CHANGED
@@ -10,6 +10,7 @@ import { DECISION_WHEEL_TOOL } from './tool/decision-wheel';
10
10
  import { INVESTIGATION_BOARD_TOOL } from './tool/investigation-board';
11
11
  import { LUNAR_TIDE_TRACKER_TOOL } from './tool/lunar-tide-tracker';
12
12
  import { HIDDEN_ROLE_DEALER_TOOL } from './tool/hidden-role-dealer';
13
+ import { SCATTER_DIRECTION_SELECTOR_TOOL } from './tool/scatter-direction-selector';
13
14
 
14
15
  export const ALL_TOOLS: ToolDefinition[] = [
15
16
  DICE_ROLLER_SIMULATOR_TOOL,
@@ -22,5 +23,6 @@ export const ALL_TOOLS: ToolDefinition[] = [
22
23
  INVESTIGATION_BOARD_TOOL,
23
24
  LUNAR_TIDE_TRACKER_TOOL,
24
25
  HIDDEN_ROLE_DEALER_TOOL,
26
+ SCATTER_DIRECTION_SELECTOR_TOOL,
25
27
  ];
26
28