@jjlmoya/utils-tabletop 1.7.0 → 1.9.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 (34) hide show
  1. package/package.json +1 -1
  2. package/src/entries.ts +14 -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/drawing.ts +1 -1
  7. package/src/tool/investigation-board/interactDrag.ts +2 -1
  8. package/src/tool/lunar-tide-tracker/bibliography.astro +16 -0
  9. package/src/tool/lunar-tide-tracker/bibliography.ts +8 -0
  10. package/src/tool/lunar-tide-tracker/client.ts +264 -0
  11. package/src/tool/lunar-tide-tracker/component.astro +103 -0
  12. package/src/tool/lunar-tide-tracker/entry.ts +26 -0
  13. package/src/tool/lunar-tide-tracker/i18n/de.ts +156 -0
  14. package/src/tool/lunar-tide-tracker/i18n/en.ts +214 -0
  15. package/src/tool/lunar-tide-tracker/i18n/es.ts +157 -0
  16. package/src/tool/lunar-tide-tracker/i18n/fr.ts +156 -0
  17. package/src/tool/lunar-tide-tracker/i18n/id.ts +156 -0
  18. package/src/tool/lunar-tide-tracker/i18n/it.ts +156 -0
  19. package/src/tool/lunar-tide-tracker/i18n/ja.ts +156 -0
  20. package/src/tool/lunar-tide-tracker/i18n/ko.ts +214 -0
  21. package/src/tool/lunar-tide-tracker/i18n/nl.ts +214 -0
  22. package/src/tool/lunar-tide-tracker/i18n/pl.ts +214 -0
  23. package/src/tool/lunar-tide-tracker/i18n/pt.ts +214 -0
  24. package/src/tool/lunar-tide-tracker/i18n/ru.ts +214 -0
  25. package/src/tool/lunar-tide-tracker/i18n/sv.ts +214 -0
  26. package/src/tool/lunar-tide-tracker/i18n/tr.ts +214 -0
  27. package/src/tool/lunar-tide-tracker/i18n/zh.ts +214 -0
  28. package/src/tool/lunar-tide-tracker/index.ts +9 -0
  29. package/src/tool/lunar-tide-tracker/logic.test.ts +56 -0
  30. package/src/tool/lunar-tide-tracker/logic.ts +76 -0
  31. package/src/tool/lunar-tide-tracker/lunar-tide-tracker.css +494 -0
  32. package/src/tool/lunar-tide-tracker/seo.astro +16 -0
  33. package/src/tool/lunar-tide-tracker/types.ts +70 -0
  34. package/src/tools.ts +2 -0
@@ -0,0 +1,494 @@
1
+ :root,
2
+ .theme-light {
3
+ --ltt-accent: #6d28d9;
4
+ --ltt-accent-2: #7c3aed;
5
+ --ltt-accent-3: #5b21b6;
6
+ --ltt-glow: rgba(109, 40, 217, 0.25);
7
+ --ltt-glow-soft: rgba(109, 40, 217, 0.12);
8
+ --ltt-label-color: #3b0764;
9
+ --ltt-moon-bg: #e9e0ff;
10
+ --ltt-moon-stroke: rgba(109, 40, 217, 0.2);
11
+ --ltt-on-accent: #fff;
12
+ --ltt-danger: #dc2626;
13
+ }
14
+
15
+ .theme-dark {
16
+ --ltt-accent: #8b5cf6;
17
+ --ltt-accent-2: #a78bfa;
18
+ --ltt-accent-3: #c4b5fd;
19
+ --ltt-glow: rgba(139, 92, 246, 0.35);
20
+ --ltt-glow-soft: rgba(139, 92, 246, 0.15);
21
+ --ltt-label-color: #e9d5ff;
22
+ --ltt-moon-bg: #0f172a;
23
+ --ltt-moon-stroke: rgba(255, 255, 255, 0.1);
24
+ --ltt-on-accent: #fff;
25
+ --ltt-danger: #f87171;
26
+ }
27
+
28
+ .ltt-root {
29
+ background: var(--bg-surface);
30
+ border: 1px solid var(--border-color);
31
+ border-radius: 2rem;
32
+ padding: 1.5rem;
33
+ color: var(--text-base);
34
+ display: flex;
35
+ flex-direction: column;
36
+ gap: 1.25rem;
37
+ box-shadow: 0 30px 80px rgba(0, 0, 0, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.07);
38
+ }
39
+
40
+ .ltt-presets {
41
+ display: flex;
42
+ gap: 0.6rem;
43
+ flex-wrap: wrap;
44
+ }
45
+
46
+ .ltt-preset-btn {
47
+ background: var(--bg-page);
48
+ border: 1px solid var(--border-color);
49
+ color: var(--text-muted);
50
+ padding: 0.45rem 1rem;
51
+ border-radius: 2rem;
52
+ font-size: 0.8rem;
53
+ font-weight: 500;
54
+ cursor: pointer;
55
+ transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);
56
+ letter-spacing: 0.03em;
57
+ }
58
+
59
+ .ltt-preset-btn:hover,
60
+ .ltt-preset-btn.active {
61
+ border-color: var(--ltt-accent);
62
+ color: var(--ltt-accent-3);
63
+ box-shadow: 0 0 14px var(--ltt-glow-soft);
64
+ transform: translateY(-1px);
65
+ }
66
+
67
+ .ltt-body {
68
+ display: grid;
69
+ grid-template-columns: auto 1fr;
70
+ gap: 1.5rem;
71
+ align-items: start;
72
+ }
73
+
74
+ @media (max-width: 700px) {
75
+ .ltt-body {
76
+ grid-template-columns: 1fr;
77
+ }
78
+ }
79
+
80
+ .ltt-portal-col {
81
+ display: flex;
82
+ flex-direction: column;
83
+ align-items: center;
84
+ gap: 0.75rem;
85
+ }
86
+
87
+ .ltt-portal-wrap {
88
+ position: relative;
89
+ width: 220px;
90
+ flex-shrink: 0;
91
+ }
92
+
93
+ .ltt-portal-svg {
94
+ width: 220px;
95
+ height: 220px;
96
+ display: block;
97
+ }
98
+
99
+ .ltt-ring {
100
+ transform-origin: 120px 120px;
101
+ }
102
+
103
+ .ltt-ring-a {
104
+ animation: ltt-spin-a 40s linear infinite;
105
+ }
106
+
107
+ .ltt-ring-b {
108
+ animation: ltt-spin-b 25s linear infinite reverse;
109
+ }
110
+
111
+ .ltt-ring-c {
112
+ animation: ltt-spin-a 18s linear infinite;
113
+ }
114
+
115
+ @keyframes ltt-spin-a {
116
+ to { transform: rotate(360deg); }
117
+ }
118
+
119
+ @keyframes ltt-spin-b {
120
+ to { transform: rotate(360deg); }
121
+ }
122
+
123
+ .ltt-tide-badge {
124
+ position: absolute;
125
+ bottom: -0.75rem;
126
+ left: 50%;
127
+ transform: translateX(-50%);
128
+ background: var(--bg-page);
129
+ border: 1px solid var(--border-color);
130
+ border-radius: 2rem;
131
+ padding: 0.3rem 0.9rem 0.4rem;
132
+ display: flex;
133
+ align-items: center;
134
+ gap: 0.5rem;
135
+ white-space: nowrap;
136
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
137
+ }
138
+
139
+ .ltt-tide-label {
140
+ font-size: 0.75rem;
141
+ font-weight: 700;
142
+ color: var(--ltt-label-color);
143
+ text-transform: uppercase;
144
+ letter-spacing: 0.06em;
145
+ }
146
+
147
+ .ltt-tide-bar-wrap {
148
+ width: 60px;
149
+ height: 4px;
150
+ background: var(--border-color);
151
+ border-radius: 2px;
152
+ overflow: hidden;
153
+ }
154
+
155
+ .ltt-tide-bar {
156
+ height: 100%;
157
+ background: linear-gradient(90deg, var(--ltt-accent), var(--ltt-accent-3));
158
+ width: 0%;
159
+ transition: width 0.6s cubic-bezier(0.16, 1, 0.3, 1);
160
+ box-shadow: 0 0 8px var(--ltt-glow);
161
+ }
162
+
163
+ .ltt-tide-pct {
164
+ font-size: 0.72rem;
165
+ color: var(--text-muted);
166
+ font-weight: 500;
167
+ }
168
+
169
+ .ltt-magic-strip {
170
+ font-size: 0.72rem;
171
+ text-align: center;
172
+ color: var(--text-muted);
173
+ font-style: italic;
174
+ max-width: 220px;
175
+ line-height: 1.4;
176
+ min-height: 2.4em;
177
+ }
178
+
179
+ .ltt-controls-col {
180
+ display: flex;
181
+ flex-direction: column;
182
+ gap: 1.25rem;
183
+ }
184
+
185
+ .ltt-label {
186
+ font-size: 0.72rem;
187
+ font-weight: 600;
188
+ text-transform: uppercase;
189
+ letter-spacing: 0.08em;
190
+ color: var(--text-muted);
191
+ }
192
+
193
+ .ltt-day-block {
194
+ background: var(--bg-page);
195
+ border: 1px solid var(--border-color);
196
+ border-radius: 1.25rem;
197
+ padding: 1rem 1.1rem;
198
+ display: flex;
199
+ flex-direction: column;
200
+ gap: 0.75rem;
201
+ }
202
+
203
+ .ltt-day-row {
204
+ display: flex;
205
+ align-items: baseline;
206
+ justify-content: space-between;
207
+ }
208
+
209
+ .ltt-day-val {
210
+ font-size: 2.2rem;
211
+ font-weight: 800;
212
+ color: var(--ltt-accent);
213
+ text-shadow: 0 0 18px var(--ltt-glow);
214
+ line-height: 1;
215
+ font-variant-numeric: tabular-nums;
216
+ }
217
+
218
+ .ltt-slider {
219
+ width: 100%;
220
+ cursor: pointer;
221
+ accent-color: var(--ltt-accent);
222
+ height: 3px;
223
+ }
224
+
225
+ .ltt-step-row {
226
+ display: flex;
227
+ gap: 0.4rem;
228
+ }
229
+
230
+ .ltt-step-btn {
231
+ flex: 1;
232
+ background: var(--bg-surface);
233
+ border: 1px solid var(--border-color);
234
+ color: var(--text-base);
235
+ padding: 0.5rem 0;
236
+ border-radius: 0.75rem;
237
+ font-weight: 700;
238
+ font-size: 0.85rem;
239
+ cursor: pointer;
240
+ transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
241
+ }
242
+
243
+ .ltt-step-btn:hover {
244
+ background: var(--ltt-accent);
245
+ border-color: var(--ltt-accent);
246
+ color: var(--ltt-on-accent);
247
+ transform: scale(1.06);
248
+ box-shadow: 0 0 12px var(--ltt-glow);
249
+ }
250
+
251
+ .ltt-step-pos {
252
+ color: var(--ltt-accent-3);
253
+ }
254
+
255
+ .ltt-reset-btn {
256
+ background: transparent;
257
+ border: 1px solid var(--border-color);
258
+ color: var(--text-muted);
259
+ padding: 0.5rem 0.7rem;
260
+ border-radius: 0.75rem;
261
+ font-size: 0.75rem;
262
+ font-weight: 500;
263
+ cursor: pointer;
264
+ transition: all 0.2s ease;
265
+ white-space: nowrap;
266
+ }
267
+
268
+ .ltt-reset-btn:hover {
269
+ border-color: var(--ltt-accent);
270
+ color: var(--ltt-accent-3);
271
+ }
272
+
273
+ .ltt-moons-block {
274
+ background: var(--bg-page);
275
+ border: 1px solid var(--border-color);
276
+ border-radius: 1.25rem;
277
+ padding: 1rem 1.1rem;
278
+ display: flex;
279
+ flex-direction: column;
280
+ gap: 0.75rem;
281
+ }
282
+
283
+ .ltt-moons-header {
284
+ display: flex;
285
+ align-items: center;
286
+ justify-content: space-between;
287
+ }
288
+
289
+ .ltt-add-btn {
290
+ background: var(--bg-surface);
291
+ border: 1px solid var(--ltt-accent);
292
+ color: var(--ltt-accent-3);
293
+ padding: 0.3rem 0.75rem;
294
+ border-radius: 2rem;
295
+ font-size: 0.75rem;
296
+ font-weight: 600;
297
+ cursor: pointer;
298
+ transition: all 0.2s ease;
299
+ }
300
+
301
+ .ltt-add-btn:hover {
302
+ background: var(--ltt-accent);
303
+ color: var(--ltt-on-accent);
304
+ box-shadow: 0 0 12px var(--ltt-glow);
305
+ }
306
+
307
+ .ltt-moons-list {
308
+ display: flex;
309
+ flex-direction: column;
310
+ gap: 0.6rem;
311
+ }
312
+
313
+ .ltt-moon-row {
314
+ display: grid;
315
+ grid-template-columns: 28px 1fr 60px 50px 28px 24px;
316
+ gap: 0.4rem;
317
+ align-items: center;
318
+ background: var(--bg-surface);
319
+ border: 1px solid var(--border-color);
320
+ border-radius: 1rem;
321
+ padding: 0.5rem 0.6rem;
322
+ transition: border-color 0.2s;
323
+ }
324
+
325
+ .ltt-moon-row:hover {
326
+ border-color: var(--ltt-accent);
327
+ }
328
+
329
+ .ltt-moon-vis-mini {
330
+ width: 24px;
331
+ height: 24px;
332
+ border-radius: 50%;
333
+ flex-shrink: 0;
334
+ }
335
+
336
+ .ltt-moon-input {
337
+ background: transparent;
338
+ border: none;
339
+ color: var(--text-base);
340
+ font-size: 0.82rem;
341
+ font-weight: 500;
342
+ width: 100%;
343
+ outline: none;
344
+ min-width: 0;
345
+ }
346
+
347
+ .ltt-moon-input::placeholder {
348
+ color: var(--text-muted);
349
+ font-size: 0.75rem;
350
+ }
351
+
352
+ .ltt-moon-num {
353
+ background: var(--bg-page);
354
+ border: 1px solid var(--border-color);
355
+ color: var(--text-base);
356
+ padding: 0.2rem 0.4rem;
357
+ border-radius: 0.5rem;
358
+ font-size: 0.75rem;
359
+ text-align: center;
360
+ width: 100%;
361
+ }
362
+
363
+ .ltt-color-swatch {
364
+ -webkit-appearance: none;
365
+ width: 24px;
366
+ height: 24px;
367
+ border-radius: 50%;
368
+ border: none;
369
+ cursor: pointer;
370
+ background: none;
371
+ }
372
+
373
+ .ltt-color-swatch::-webkit-color-swatch-wrapper {
374
+ padding: 0;
375
+ }
376
+
377
+ .ltt-color-swatch::-webkit-color-swatch {
378
+ border-radius: 50%;
379
+ border: 1px solid var(--border-color);
380
+ }
381
+
382
+ .ltt-remove-btn {
383
+ background: none;
384
+ border: none;
385
+ color: var(--text-muted);
386
+ font-size: 1rem;
387
+ cursor: pointer;
388
+ line-height: 1;
389
+ padding: 0;
390
+ transition: color 0.15s;
391
+ }
392
+
393
+ .ltt-remove-btn:hover {
394
+ color: var(--ltt-danger);
395
+ }
396
+
397
+ .ltt-phase-tag {
398
+ font-size: 0.68rem;
399
+ color: var(--text-muted);
400
+ white-space: nowrap;
401
+ overflow: hidden;
402
+ text-overflow: ellipsis;
403
+ display: flex;
404
+ align-items: center;
405
+ gap: 0.35rem;
406
+ padding-left: 0.5rem;
407
+ }
408
+
409
+ .ltt-phase-dot {
410
+ width: 7px;
411
+ height: 7px;
412
+ border-radius: 50%;
413
+ flex-shrink: 0;
414
+ border: 1px solid var(--border-color);
415
+ }
416
+
417
+ .ltt-forecast-wrap {
418
+ display: flex;
419
+ flex-direction: column;
420
+ gap: 0.5rem;
421
+ }
422
+
423
+ .ltt-forecast-label {
424
+ font-size: 0.72rem;
425
+ font-weight: 600;
426
+ text-transform: uppercase;
427
+ letter-spacing: 0.08em;
428
+ color: var(--text-muted);
429
+ }
430
+
431
+ .ltt-forecast {
432
+ display: grid;
433
+ grid-template-columns: repeat(10, 1fr);
434
+ gap: 0.5rem;
435
+ }
436
+
437
+ @media (max-width: 700px) {
438
+ .ltt-forecast {
439
+ grid-template-columns: repeat(5, 1fr);
440
+ }
441
+ }
442
+
443
+ .ltt-fc-cell {
444
+ background: var(--bg-page);
445
+ border: 1px solid var(--border-color);
446
+ border-radius: 0.85rem;
447
+ padding: 0.5rem 0.25rem;
448
+ display: flex;
449
+ flex-direction: column;
450
+ align-items: center;
451
+ gap: 0.3rem;
452
+ text-align: center;
453
+ transition: border-color 0.2s, transform 0.15s;
454
+ cursor: default;
455
+ }
456
+
457
+ .ltt-fc-cell:hover {
458
+ border-color: var(--ltt-accent);
459
+ transform: translateY(-2px);
460
+ }
461
+
462
+ .ltt-fc-day {
463
+ font-size: 0.65rem;
464
+ font-weight: 700;
465
+ color: var(--text-muted);
466
+ }
467
+
468
+ .ltt-fc-dots {
469
+ display: flex;
470
+ gap: 2px;
471
+ justify-content: center;
472
+ }
473
+
474
+ .ltt-fc-dot {
475
+ width: 8px;
476
+ height: 8px;
477
+ border-radius: 50%;
478
+ border: 1px solid rgba(255, 255, 255, 0.1);
479
+ flex-shrink: 0;
480
+ }
481
+
482
+ .ltt-fc-pct {
483
+ font-size: 0.6rem;
484
+ color: var(--text-muted);
485
+ }
486
+
487
+ .ltt-fc-cell.ltt-fc-peak {
488
+ border-color: var(--ltt-accent);
489
+ box-shadow: 0 0 10px var(--ltt-glow-soft);
490
+ }
491
+
492
+ .ltt-fc-cell.ltt-fc-peak .ltt-fc-day {
493
+ color: var(--ltt-accent-3);
494
+ }
@@ -0,0 +1,16 @@
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { lunarTideTracker } 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 = lunarTideTracker.i18n[locale] || lunarTideTracker.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,70 @@
1
+ export type MoonPhaseType =
2
+ | 'new'
3
+ | 'waxing_crescent'
4
+ | 'first_quarter'
5
+ | 'waxing_gibbous'
6
+ | 'full'
7
+ | 'waning_gibbous'
8
+ | 'last_quarter'
9
+ | 'waning_crescent';
10
+
11
+ export interface Moon {
12
+ id: string;
13
+ name: string;
14
+ period: number;
15
+ offset: number;
16
+ color: string;
17
+ }
18
+
19
+ export interface TideState {
20
+ level: number;
21
+ name: string;
22
+ modifier: string;
23
+ }
24
+
25
+ export interface MoonPhaseInfo {
26
+ phase: MoonPhaseType;
27
+ percentage: number;
28
+ angle: number;
29
+ }
30
+
31
+ export interface ForecastItem {
32
+ day: number;
33
+ moons: {
34
+ moonId: string;
35
+ phase: MoonPhaseType;
36
+ percentage: number;
37
+ }[];
38
+ tideLevel: number;
39
+ tideName: string;
40
+ }
41
+
42
+ export interface LunarTideUI {
43
+ title: string;
44
+ moonsTitle: string;
45
+ addMoon: string;
46
+ removeMoon: string;
47
+ moonName: string;
48
+ orbitalPeriod: string;
49
+ startingOffset: string;
50
+ timelineTitle: string;
51
+ currentDay: string;
52
+ tideTitle: string;
53
+ tideLevel: string;
54
+ magicModifier: string;
55
+ forecastTitle: string;
56
+ dayLabel: string;
57
+ resetButton: string;
58
+ presetTitle: string;
59
+ presetSingle: string;
60
+ presetDouble: string;
61
+ presetTriple: string;
62
+ newMoon: string;
63
+ waxingCrescent: string;
64
+ firstQuarter: string;
65
+ waxingGibbous: string;
66
+ fullMoon: string;
67
+ waningGibbous: string;
68
+ lastQuarter: string;
69
+ waningCrescent: string;
70
+ }
package/src/tools.ts CHANGED
@@ -8,6 +8,7 @@ import { INITIATIVE_TRACKER_TOOL } from './tool/rpg-initiative-tracker';
8
8
  import { FANTASY_RUNES_TRANSLATOR_TOOL } from './tool/fantasy-runes-translator';
9
9
  import { DECISION_WHEEL_TOOL } from './tool/decision-wheel';
10
10
  import { INVESTIGATION_BOARD_TOOL } from './tool/investigation-board';
11
+ import { LUNAR_TIDE_TRACKER_TOOL } from './tool/lunar-tide-tracker';
11
12
 
12
13
  export const ALL_TOOLS: ToolDefinition[] = [
13
14
  DICE_ROLLER_SIMULATOR_TOOL,
@@ -18,5 +19,6 @@ export const ALL_TOOLS: ToolDefinition[] = [
18
19
  FANTASY_RUNES_TRANSLATOR_TOOL,
19
20
  DECISION_WHEEL_TOOL,
20
21
  INVESTIGATION_BOARD_TOOL,
22
+ LUNAR_TIDE_TRACKER_TOOL,
21
23
  ];
22
24