@jjlmoya/utils-sports 1.19.0 → 1.21.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.
- package/package.json +1 -1
- package/src/entries.ts +7 -1
- package/src/tests/locale_completeness.test.ts +1 -1
- package/src/tests/tool_validation.test.ts +2 -2
- package/src/tool/dartsScoreKeeper/bibliography.astro +15 -0
- package/src/tool/dartsScoreKeeper/bibliography.ts +8 -0
- package/src/tool/dartsScoreKeeper/component.astro +156 -0
- package/src/tool/dartsScoreKeeper/dartboard.ts +69 -0
- package/src/tool/dartsScoreKeeper/darts-scorekeeper.css +828 -0
- package/src/tool/dartsScoreKeeper/entry.ts +30 -0
- package/src/tool/dartsScoreKeeper/game-logic.ts +222 -0
- package/src/tool/dartsScoreKeeper/i18n/de.ts +198 -0
- package/src/tool/dartsScoreKeeper/i18n/en.ts +198 -0
- package/src/tool/dartsScoreKeeper/i18n/es.ts +198 -0
- package/src/tool/dartsScoreKeeper/i18n/fr.ts +198 -0
- package/src/tool/dartsScoreKeeper/i18n/id.ts +198 -0
- package/src/tool/dartsScoreKeeper/i18n/it.ts +198 -0
- package/src/tool/dartsScoreKeeper/i18n/ja.ts +198 -0
- package/src/tool/dartsScoreKeeper/i18n/ko.ts +198 -0
- package/src/tool/dartsScoreKeeper/i18n/nl.ts +198 -0
- package/src/tool/dartsScoreKeeper/i18n/pl.ts +198 -0
- package/src/tool/dartsScoreKeeper/i18n/pt.ts +198 -0
- package/src/tool/dartsScoreKeeper/i18n/ru.ts +198 -0
- package/src/tool/dartsScoreKeeper/i18n/sv.ts +198 -0
- package/src/tool/dartsScoreKeeper/i18n/tr.ts +198 -0
- package/src/tool/dartsScoreKeeper/i18n/zh.ts +198 -0
- package/src/tool/dartsScoreKeeper/index.ts +9 -0
- package/src/tool/dartsScoreKeeper/logic.ts +265 -0
- package/src/tool/dartsScoreKeeper/render.ts +167 -0
- package/src/tool/dartsScoreKeeper/seo.astro +4 -0
- package/src/tool/dartsScoreKeeper/ui.ts +20 -0
- package/src/tool/tennisScoreKeeper/bibliography.astro +6 -0
- package/src/tool/tennisScoreKeeper/bibliography.ts +8 -0
- package/src/tool/tennisScoreKeeper/component.astro +164 -0
- package/src/tool/tennisScoreKeeper/entry.ts +30 -0
- package/src/tool/tennisScoreKeeper/events.ts +136 -0
- package/src/tool/tennisScoreKeeper/game-logic.ts +248 -0
- package/src/tool/tennisScoreKeeper/i18n/de.ts +194 -0
- package/src/tool/tennisScoreKeeper/i18n/en.ts +194 -0
- package/src/tool/tennisScoreKeeper/i18n/es.ts +194 -0
- package/src/tool/tennisScoreKeeper/i18n/fr.ts +194 -0
- package/src/tool/tennisScoreKeeper/i18n/id.ts +194 -0
- package/src/tool/tennisScoreKeeper/i18n/it.ts +194 -0
- package/src/tool/tennisScoreKeeper/i18n/ja.ts +194 -0
- package/src/tool/tennisScoreKeeper/i18n/ko.ts +194 -0
- package/src/tool/tennisScoreKeeper/i18n/nl.ts +194 -0
- package/src/tool/tennisScoreKeeper/i18n/pl.ts +194 -0
- package/src/tool/tennisScoreKeeper/i18n/pt.ts +194 -0
- package/src/tool/tennisScoreKeeper/i18n/ru.ts +194 -0
- package/src/tool/tennisScoreKeeper/i18n/sv.ts +194 -0
- package/src/tool/tennisScoreKeeper/i18n/tr.ts +194 -0
- package/src/tool/tennisScoreKeeper/i18n/zh.ts +195 -0
- package/src/tool/tennisScoreKeeper/index.ts +9 -0
- package/src/tool/tennisScoreKeeper/logic.ts +205 -0
- package/src/tool/tennisScoreKeeper/render.ts +256 -0
- package/src/tool/tennisScoreKeeper/seo.astro +15 -0
- package/src/tool/tennisScoreKeeper/tennis-scorekeeper.css +827 -0
- package/src/tool/tennisScoreKeeper/ui.ts +27 -0
- package/src/tools.ts +4 -0
package/package.json
CHANGED
package/src/entries.ts
CHANGED
|
@@ -12,6 +12,10 @@ export { scoreKeeper } from './tool/scoreKeeper/entry';
|
|
|
12
12
|
export type { ScoreKeeperLocaleContent } from './tool/scoreKeeper/entry';
|
|
13
13
|
export { tournamentBracket } from './tool/tournamentBracket/entry';
|
|
14
14
|
export type { TournamentBracketLocaleContent } from './tool/tournamentBracket/entry';
|
|
15
|
+
export { tennisScoreKeeper } from './tool/tennisScoreKeeper/entry';
|
|
16
|
+
export type { TennisScoreKeeperLocaleContent } from './tool/tennisScoreKeeper/entry';
|
|
17
|
+
export { dartsScoreKeeper } from './tool/dartsScoreKeeper/entry';
|
|
18
|
+
export type { DartsScoreKeeperLocaleContent } from './tool/dartsScoreKeeper/entry';
|
|
15
19
|
export { sportsCategory } from './category';
|
|
16
20
|
import { basketScoreKeeper } from './tool/basketScoreKeeper/entry';
|
|
17
21
|
import { footballScoreKeeper } from './tool/footballScoreKeeper/entry';
|
|
@@ -20,4 +24,6 @@ import { pingPongScoreKeeper } from './tool/pingPongScoreKeeper/entry';
|
|
|
20
24
|
import { reactionTester } from './tool/reactionTester/entry';
|
|
21
25
|
import { scoreKeeper } from './tool/scoreKeeper/entry';
|
|
22
26
|
import { tournamentBracket } from './tool/tournamentBracket/entry';
|
|
23
|
-
|
|
27
|
+
import { tennisScoreKeeper } from './tool/tennisScoreKeeper/entry';
|
|
28
|
+
import { dartsScoreKeeper } from './tool/dartsScoreKeeper/entry';
|
|
29
|
+
export const ALL_ENTRIES = [basketScoreKeeper, footballScoreKeeper, gymTracker, pingPongScoreKeeper, reactionTester, scoreKeeper, tournamentBracket, tennisScoreKeeper, dartsScoreKeeper];
|
|
@@ -4,8 +4,8 @@ import { sportsCategory } from '../data';
|
|
|
4
4
|
|
|
5
5
|
describe('Tool Validation Suite', () => {
|
|
6
6
|
describe('Library Registration', () => {
|
|
7
|
-
it('should have
|
|
8
|
-
expect(ALL_TOOLS.length).toBe(
|
|
7
|
+
it('should have 9 tools in ALL_TOOLS', () => {
|
|
8
|
+
expect(ALL_TOOLS.length).toBe(9);
|
|
9
9
|
});
|
|
10
10
|
|
|
11
11
|
it('sportsCategory should be defined', () => {
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { KnownLocale } from '../../types';
|
|
3
|
+
import type { DartsScoreKeeperUI } from './ui';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
locale?: KnownLocale;
|
|
7
|
+
ui?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { ui } = Astro.props;
|
|
11
|
+
const t = (ui ?? {}) as DartsScoreKeeperUI;
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<div class="tn-console" id="tn-card" data-tn-ui={JSON.stringify(t)}>
|
|
15
|
+
<div class="tn-header-panel">
|
|
16
|
+
<div class="tn-header-actions">
|
|
17
|
+
<button class="tn-action-btn" id="tn-toggle-view-btn" title="Toggle View">
|
|
18
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
19
|
+
<circle cx="12" cy="12" r="10"/>
|
|
20
|
+
<circle cx="12" cy="12" r="6"/>
|
|
21
|
+
<circle cx="12" cy="12" r="2"/>
|
|
22
|
+
</svg>
|
|
23
|
+
</button>
|
|
24
|
+
<button class="tn-action-btn" data-tn-reset title={t.reset}>
|
|
25
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
26
|
+
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/>
|
|
27
|
+
<path d="M16 3h5v5M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/>
|
|
28
|
+
<path d="M8 21H3v-5"/>
|
|
29
|
+
</svg>
|
|
30
|
+
</button>
|
|
31
|
+
<button class="tn-action-btn" data-tn-fs title={t.fullscreen}>
|
|
32
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
33
|
+
<path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/>
|
|
34
|
+
</svg>
|
|
35
|
+
</button>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="tn-scoreboard">
|
|
40
|
+
<div class="tn-player-card tn-player-card-active" id="tn-card-a">
|
|
41
|
+
<div class="tn-player-name-section">
|
|
42
|
+
<input type="text" value={t.playerA} class="tn-player-input" id="tn-name-a" />
|
|
43
|
+
</div>
|
|
44
|
+
<div class="tn-big-score" id="tn-score-a">501</div>
|
|
45
|
+
<div class="tn-player-stats">
|
|
46
|
+
<div class="tn-legs-indicator" id="tn-legs-indicator-a">
|
|
47
|
+
<span class="tn-legs-dot"></span>
|
|
48
|
+
<span class="tn-legs-dot"></span>
|
|
49
|
+
<span class="tn-legs-dot"></span>
|
|
50
|
+
</div>
|
|
51
|
+
<span id="tn-avg-a">{t.average}: 0.00</span>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div class="tn-player-card" id="tn-card-b">
|
|
56
|
+
<div class="tn-player-name-section">
|
|
57
|
+
<input type="text" value={t.playerB} class="tn-player-input" id="tn-name-b" />
|
|
58
|
+
</div>
|
|
59
|
+
<div class="tn-big-score" id="tn-score-b">501</div>
|
|
60
|
+
<div class="tn-player-stats">
|
|
61
|
+
<div class="tn-legs-indicator" id="tn-legs-indicator-b">
|
|
62
|
+
<span class="tn-legs-dot"></span>
|
|
63
|
+
<span class="tn-legs-dot"></span>
|
|
64
|
+
<span class="tn-legs-dot"></span>
|
|
65
|
+
</div>
|
|
66
|
+
<span id="tn-avg-b">{t.average}: 0.00</span>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div class="tn-checkout-panel">
|
|
72
|
+
<div class="tn-checkout-title" id="tn-checkout-suggest"></div>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<div class="tn-dartboard-container" id="tn-dartboard-container">
|
|
76
|
+
<div class="tn-dartboard-wrapper">
|
|
77
|
+
<svg id="tn-dartboard-svg" viewBox="0 0 320 320"></svg>
|
|
78
|
+
</div>
|
|
79
|
+
<div class="tn-dartboard-actions">
|
|
80
|
+
<button class="tn-special-btn tn-special-btn-undo" data-dt-undo>Undo</button>
|
|
81
|
+
<button class="tn-special-btn tn-special-btn-miss" data-dt-val="0">Miss (0)</button>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div class="tn-turn-info">
|
|
86
|
+
<div class="tn-turn-title">{t.dart}</div>
|
|
87
|
+
<div class="tn-turn-darts" id="tn-turn-darts"></div>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<div class="tn-keypad">
|
|
91
|
+
<div class="tn-modifiers">
|
|
92
|
+
<button class="tn-mod-btn tn-mod-active" data-dt-mod="1">Single (x1)</button>
|
|
93
|
+
<button class="tn-mod-btn" data-dt-mod="2">Double (x2)</button>
|
|
94
|
+
<button class="tn-mod-btn" data-dt-mod="3">Triple (x3)</button>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<div class="tn-numbers-grid">
|
|
98
|
+
{Array.from({ length: 20 }, (_, i) => i + 1).map((n) => (
|
|
99
|
+
<button class="tn-val-btn" data-dt-val={n}>{n}</button>
|
|
100
|
+
))}
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<div class="tn-special-grid">
|
|
104
|
+
<button class="tn-special-btn" data-dt-val="25">OB (25)</button>
|
|
105
|
+
<button class="tn-special-btn" data-dt-val="50">DB (50)</button>
|
|
106
|
+
<button class="tn-special-btn tn-special-btn-miss" data-dt-val="0">Miss (0)</button>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<div class="tn-special-grid">
|
|
110
|
+
<button class="tn-special-btn tn-special-btn-undo" data-dt-undo>Undo</button>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<div class="tn-history-panel">
|
|
115
|
+
<div class="tn-history-grid" id="tn-history-list"></div>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<div class="tn-console-controls">
|
|
119
|
+
<div class="tn-control-group">
|
|
120
|
+
<button class="tn-control-btn tn-format-btn tn-format-active" data-dt-format="501">{t.score501}</button>
|
|
121
|
+
<button class="tn-control-btn tn-format-btn" data-dt-format="301">{t.score301}</button>
|
|
122
|
+
</div>
|
|
123
|
+
<div class="tn-control-group">
|
|
124
|
+
<button class="tn-control-btn tn-doubleout-active" data-dt-doubleout>{t.doubleOut}</button>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<div id="tn-winner" class="tn-celebration-screen">
|
|
129
|
+
<div class="tn-celebration-box">
|
|
130
|
+
<svg class="tn-celebration-cup" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
131
|
+
<path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6m12 5h1.5a2.5 2.5 0 0 0 0-5H18M6 4h12M12 18v3m-4 0h8m-4-17v14a4 4 0 0 1-4-4"/>
|
|
132
|
+
</svg>
|
|
133
|
+
<div class="tn-celebration-label">{t.winnerLabel}</div>
|
|
134
|
+
<div id="tn-winner-team" class="tn-celebration-name">Player 1</div>
|
|
135
|
+
<div id="tn-winner-score" class="tn-celebration-score">Set: 0 - 0</div>
|
|
136
|
+
<button class="tn-celebration-btn" data-close-winner>×</button>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<div id="tn-confetti" class="tn-confetti-field"></div>
|
|
141
|
+
|
|
142
|
+
<div id="tn-modal" class="tn-inline-modal">
|
|
143
|
+
<div class="tn-inline-modal-box">
|
|
144
|
+
<p class="tn-inline-modal-text">{t.resetConfirm}</p>
|
|
145
|
+
<div class="tn-inline-modal-btns">
|
|
146
|
+
<button id="tn-modal-cancel" class="tn-inline-modal-btn tn-inline-btn-cancel">{t.cancel}</button>
|
|
147
|
+
<button id="tn-modal-confirm" class="tn-inline-modal-btn tn-inline-btn-reset">{t.reset}</button>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<script>
|
|
154
|
+
import { initDartsScoreKeeper } from './logic';
|
|
155
|
+
initDartsScoreKeeper();
|
|
156
|
+
</script>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
interface RingParams {
|
|
2
|
+
cx: number;
|
|
3
|
+
cy: number;
|
|
4
|
+
rIn: number;
|
|
5
|
+
rOut: number;
|
|
6
|
+
startDeg: number;
|
|
7
|
+
endDeg: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const BOARD_NUMBERS = [20, 1, 18, 4, 13, 6, 10, 15, 2, 17, 3, 19, 7, 16, 8, 11, 14, 9, 12, 5];
|
|
11
|
+
|
|
12
|
+
function polarToCartesian(cx: number, cy: number, r: number, angleDeg: number): { x: number; y: number } {
|
|
13
|
+
const angleRad = ((angleDeg - 90) * Math.PI) / 180;
|
|
14
|
+
return {
|
|
15
|
+
x: cx + r * Math.cos(angleRad),
|
|
16
|
+
y: cy + r * Math.sin(angleRad),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getRingPath(params: RingParams): string {
|
|
21
|
+
const { cx, cy, rIn, rOut, startDeg, endDeg } = params;
|
|
22
|
+
const pInStart = polarToCartesian(cx, cy, rIn, startDeg);
|
|
23
|
+
const pInEnd = polarToCartesian(cx, cy, rIn, endDeg);
|
|
24
|
+
const pOutStart = polarToCartesian(cx, cy, rOut, startDeg);
|
|
25
|
+
const pOutEnd = polarToCartesian(cx, cy, rOut, endDeg);
|
|
26
|
+
return [
|
|
27
|
+
`M ${pInStart.x} ${pInStart.y}`,
|
|
28
|
+
`L ${pOutStart.x} ${pOutStart.y}`,
|
|
29
|
+
`A ${rOut} ${rOut} 0 0 1 ${pOutEnd.x} ${pOutEnd.y}`,
|
|
30
|
+
`L ${pInEnd.x} ${pInEnd.y}`,
|
|
31
|
+
`A ${rIn} ${rIn} 0 0 0 ${pInStart.x} ${pInStart.y}`,
|
|
32
|
+
'Z',
|
|
33
|
+
].join(' ');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function buildSector(cx: number, cy: number, idx: number, parts: string[]): void {
|
|
37
|
+
const num = BOARD_NUMBERS[idx];
|
|
38
|
+
const startDeg = idx * 18 - 9;
|
|
39
|
+
const endDeg = idx * 18 + 9;
|
|
40
|
+
const isEven = idx % 2 === 0;
|
|
41
|
+
const colorSingle = isEven ? '#1e2430' : '#e0e0e0';
|
|
42
|
+
const colorDoubleTriple = isEven ? '#ef4444' : '#2e7d32';
|
|
43
|
+
|
|
44
|
+
const pInnerSingle = getRingPath({ cx, cy, rIn: 18, rOut: 80, startDeg, endDeg });
|
|
45
|
+
parts.push(`<path d="${pInnerSingle}" fill="${colorSingle}" class="tn-board-sector" data-dt-val="${num}" data-dt-mod="1" />`);
|
|
46
|
+
|
|
47
|
+
const pTriple = getRingPath({ cx, cy, rIn: 80, rOut: 92, startDeg, endDeg });
|
|
48
|
+
parts.push(`<path d="${pTriple}" fill="${colorDoubleTriple}" class="tn-board-sector" data-dt-val="${num}" data-dt-mod="3" />`);
|
|
49
|
+
|
|
50
|
+
const pOuterSingle = getRingPath({ cx, cy, rIn: 92, rOut: 126, startDeg, endDeg });
|
|
51
|
+
parts.push(`<path d="${pOuterSingle}" fill="${colorSingle}" class="tn-board-sector" data-dt-val="${num}" data-dt-mod="1" />`);
|
|
52
|
+
|
|
53
|
+
const pDouble = getRingPath({ cx, cy, rIn: 126, rOut: 138, startDeg, endDeg });
|
|
54
|
+
parts.push(`<path d="${pDouble}" fill="${colorDoubleTriple}" class="tn-board-sector" data-dt-val="${num}" data-dt-mod="2" />`);
|
|
55
|
+
|
|
56
|
+
const pText = polarToCartesian(cx, cy, 148, idx * 18);
|
|
57
|
+
parts.push(`<text x="${pText.x}" y="${pText.y}" fill="#fff" font-size="10" font-weight="800" text-anchor="middle" dominant-baseline="central" transform="rotate(${idx * 18}, ${pText.x}, ${pText.y})">${num}</text>`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function buildDartboardSVG(cx: number, cy: number): string {
|
|
61
|
+
const parts: string[] = [];
|
|
62
|
+
parts.push(`<circle cx="${cx}" cy="${cy}" r="160" fill="#0d1118" />`);
|
|
63
|
+
parts.push(`<circle cx="${cx}" cy="${cy}" r="18" fill="#2e7d32" class="tn-board-sector" data-dt-val="25" data-dt-mod="1" />`);
|
|
64
|
+
parts.push(`<circle cx="${cx}" cy="${cy}" r="8" fill="#ef4444" class="tn-board-sector" data-dt-val="50" data-dt-mod="2" />`);
|
|
65
|
+
for (let i = 0; i < 20; i++) {
|
|
66
|
+
buildSector(cx, cy, i, parts);
|
|
67
|
+
}
|
|
68
|
+
return parts.join('\n');
|
|
69
|
+
}
|