@jjlmoya/utils-chrono 1.11.0 → 1.17.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 (99) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +6 -0
  3. package/src/entries.ts +10 -1
  4. package/src/tests/locale_completeness.test.ts +1 -1
  5. package/src/tests/tool_validation.test.ts +1 -1
  6. package/src/tool/gear-train-explorer/i18n/de.ts +1 -1
  7. package/src/tool/gear-train-explorer/i18n/es.ts +1 -1
  8. package/src/tool/gear-train-explorer/i18n/fr.ts +1 -1
  9. package/src/tool/gear-train-explorer/i18n/id.ts +1 -1
  10. package/src/tool/gear-train-explorer/i18n/it.ts +1 -1
  11. package/src/tool/gear-train-explorer/i18n/nl.ts +1 -1
  12. package/src/tool/gear-train-explorer/i18n/pl.ts +1 -1
  13. package/src/tool/gear-train-explorer/i18n/pt.ts +1 -1
  14. package/src/tool/gear-train-explorer/i18n/ru.ts +1 -1
  15. package/src/tool/gear-train-explorer/i18n/sv.ts +1 -1
  16. package/src/tool/gear-train-explorer/i18n/tr.ts +1 -1
  17. package/src/tool/gmt-world-timer/bibliography.astro +11 -0
  18. package/src/tool/gmt-world-timer/bibliography.ts +7 -0
  19. package/src/tool/gmt-world-timer/client.ts +250 -0
  20. package/src/tool/gmt-world-timer/component.astro +13 -0
  21. package/src/tool/gmt-world-timer/components/GmtPanel.astro +18 -0
  22. package/src/tool/gmt-world-timer/entry.ts +34 -0
  23. package/src/tool/gmt-world-timer/gmt-world-timer.css +239 -0
  24. package/src/tool/gmt-world-timer/helpers.ts +28 -0
  25. package/src/tool/gmt-world-timer/i18n/de.ts +72 -0
  26. package/src/tool/gmt-world-timer/i18n/en.ts +72 -0
  27. package/src/tool/gmt-world-timer/i18n/es.ts +72 -0
  28. package/src/tool/gmt-world-timer/i18n/fr.ts +72 -0
  29. package/src/tool/gmt-world-timer/i18n/id.ts +72 -0
  30. package/src/tool/gmt-world-timer/i18n/it.ts +72 -0
  31. package/src/tool/gmt-world-timer/i18n/ja.ts +72 -0
  32. package/src/tool/gmt-world-timer/i18n/ko.ts +72 -0
  33. package/src/tool/gmt-world-timer/i18n/nl.ts +72 -0
  34. package/src/tool/gmt-world-timer/i18n/pl.ts +72 -0
  35. package/src/tool/gmt-world-timer/i18n/pt.ts +72 -0
  36. package/src/tool/gmt-world-timer/i18n/ru.ts +72 -0
  37. package/src/tool/gmt-world-timer/i18n/sv.ts +72 -0
  38. package/src/tool/gmt-world-timer/i18n/tr.ts +72 -0
  39. package/src/tool/gmt-world-timer/i18n/zh.ts +72 -0
  40. package/src/tool/gmt-world-timer/index.ts +11 -0
  41. package/src/tool/gmt-world-timer/seo.astro +11 -0
  42. package/src/tool/perpetual-calendar/bibliography.astro +16 -0
  43. package/src/tool/perpetual-calendar/bibliography.ts +16 -0
  44. package/src/tool/perpetual-calendar/calendar.ts +24 -0
  45. package/src/tool/perpetual-calendar/client.ts +98 -0
  46. package/src/tool/perpetual-calendar/component.astro +17 -0
  47. package/src/tool/perpetual-calendar/components/CalendarPanel.astro +49 -0
  48. package/src/tool/perpetual-calendar/dial.ts +176 -0
  49. package/src/tool/perpetual-calendar/entry.ts +48 -0
  50. package/src/tool/perpetual-calendar/helpers.ts +49 -0
  51. package/src/tool/perpetual-calendar/i18n/de.ts +85 -0
  52. package/src/tool/perpetual-calendar/i18n/en.ts +102 -0
  53. package/src/tool/perpetual-calendar/i18n/es.ts +85 -0
  54. package/src/tool/perpetual-calendar/i18n/fr.ts +85 -0
  55. package/src/tool/perpetual-calendar/i18n/id.ts +85 -0
  56. package/src/tool/perpetual-calendar/i18n/it.ts +85 -0
  57. package/src/tool/perpetual-calendar/i18n/ja.ts +85 -0
  58. package/src/tool/perpetual-calendar/i18n/ko.ts +85 -0
  59. package/src/tool/perpetual-calendar/i18n/nl.ts +85 -0
  60. package/src/tool/perpetual-calendar/i18n/pl.ts +85 -0
  61. package/src/tool/perpetual-calendar/i18n/pt.ts +85 -0
  62. package/src/tool/perpetual-calendar/i18n/ru.ts +85 -0
  63. package/src/tool/perpetual-calendar/i18n/sv.ts +85 -0
  64. package/src/tool/perpetual-calendar/i18n/tr.ts +85 -0
  65. package/src/tool/perpetual-calendar/i18n/zh.ts +85 -0
  66. package/src/tool/perpetual-calendar/index.ts +11 -0
  67. package/src/tool/perpetual-calendar/perpetual-calendar.css +181 -0
  68. package/src/tool/perpetual-calendar/seo.astro +16 -0
  69. package/src/tool/perpetual-calendar/state.ts +26 -0
  70. package/src/tool/tourbillon-visualizer/bibliography.astro +11 -0
  71. package/src/tool/tourbillon-visualizer/bibliography.ts +7 -0
  72. package/src/tool/tourbillon-visualizer/client.ts +122 -0
  73. package/src/tool/tourbillon-visualizer/component.astro +126 -0
  74. package/src/tool/tourbillon-visualizer/components/TourbillonPanel.astro +66 -0
  75. package/src/tool/tourbillon-visualizer/entry.ts +51 -0
  76. package/src/tool/tourbillon-visualizer/helpers.ts +35 -0
  77. package/src/tool/tourbillon-visualizer/i18n/de.ts +96 -0
  78. package/src/tool/tourbillon-visualizer/i18n/en.ts +96 -0
  79. package/src/tool/tourbillon-visualizer/i18n/es.ts +96 -0
  80. package/src/tool/tourbillon-visualizer/i18n/fr.ts +96 -0
  81. package/src/tool/tourbillon-visualizer/i18n/id.ts +96 -0
  82. package/src/tool/tourbillon-visualizer/i18n/it.ts +96 -0
  83. package/src/tool/tourbillon-visualizer/i18n/ja.ts +96 -0
  84. package/src/tool/tourbillon-visualizer/i18n/ko.ts +96 -0
  85. package/src/tool/tourbillon-visualizer/i18n/nl.ts +96 -0
  86. package/src/tool/tourbillon-visualizer/i18n/pl.ts +96 -0
  87. package/src/tool/tourbillon-visualizer/i18n/pt.ts +96 -0
  88. package/src/tool/tourbillon-visualizer/i18n/ru.ts +96 -0
  89. package/src/tool/tourbillon-visualizer/i18n/sv.ts +96 -0
  90. package/src/tool/tourbillon-visualizer/i18n/tr.ts +96 -0
  91. package/src/tool/tourbillon-visualizer/i18n/zh.ts +96 -0
  92. package/src/tool/tourbillon-visualizer/index.ts +11 -0
  93. package/src/tool/tourbillon-visualizer/renderer/base.ts +78 -0
  94. package/src/tool/tourbillon-visualizer/renderer/cage.ts +115 -0
  95. package/src/tool/tourbillon-visualizer/renderer/esc.ts +160 -0
  96. package/src/tool/tourbillon-visualizer/seo.astro +11 -0
  97. package/src/tool/tourbillon-visualizer/state.ts +21 -0
  98. package/src/tool/tourbillon-visualizer/tourbillon.ts +9 -0
  99. package/src/tools.ts +6 -0
@@ -0,0 +1,160 @@
1
+ import { getC, cl, CX, CY } from '../state';
2
+
3
+ function balanceRim(br: number) {
4
+ const c = getC();
5
+ c.beginPath();
6
+ c.arc(0, 0, br, 0, Math.PI * 2);
7
+ c.fillStyle = cl('#2a3040', '#d8d0c0');
8
+ c.fill();
9
+ c.strokeStyle = cl('#d4af37', '#b09050');
10
+ c.lineWidth = 2;
11
+ c.stroke();
12
+ c.beginPath();
13
+ c.arc(0, 0, br * 0.85, 0, Math.PI * 2);
14
+ c.strokeStyle = cl('rgba(212,175,55,0.3)', 'rgba(176,144,80,0.3)');
15
+ c.lineWidth = 1;
16
+ c.stroke();
17
+ }
18
+
19
+ function balanceScrews(br: number) {
20
+ const c = getC();
21
+ for (let i = 0; i < 16; i++) {
22
+ const a = (i / 16) * Math.PI * 2;
23
+ const x = Math.cos(a) * br, y = Math.sin(a) * br;
24
+ c.beginPath();
25
+ c.arc(x, y, 3, 0, Math.PI * 2);
26
+ const g = c.createRadialGradient(x - 0.5, y - 0.5, 0, x, y, 3);
27
+ g.addColorStop(0, cl('#e8f0f8', '#d0c8b8'));
28
+ g.addColorStop(0.5, cl('#c0d0e0', '#b0a898'));
29
+ g.addColorStop(1, cl('#8098b0', '#807868'));
30
+ c.fillStyle = g;
31
+ c.fill();
32
+ }
33
+ }
34
+
35
+ export function drawBalance(angle: number, highlight: boolean) {
36
+ const c = getC();
37
+ const br = 55;
38
+ c.save();
39
+ c.translate(CX, CY);
40
+ c.rotate(angle);
41
+ balanceRim(br);
42
+ balanceScrews(br);
43
+ const cl2 = br * 0.6;
44
+ c.strokeStyle = cl('#c0d0e0', '#b0a898');
45
+ c.lineWidth = 2;
46
+ c.beginPath(); c.moveTo(-cl2, 0); c.lineTo(cl2, 0); c.stroke();
47
+ c.beginPath(); c.moveTo(0, -cl2); c.lineTo(0, cl2); c.stroke();
48
+ c.beginPath(); c.arc(0, 0, 4, 0, Math.PI * 2);
49
+ c.fillStyle = cl('#d4af37', '#b89850');
50
+ c.fill();
51
+ if (highlight) {
52
+ c.beginPath();
53
+ c.arc(0, 0, br + 4, 0, Math.PI * 2);
54
+ c.strokeStyle = 'rgba(212,175,55,0.6)';
55
+ c.lineWidth = 2;
56
+ c.setLineDash([3, 3]);
57
+ c.stroke();
58
+ c.setLineDash([]);
59
+ }
60
+ c.restore();
61
+ }
62
+
63
+ export function drawHairspring(phase: number, hz: number, highlight: boolean) {
64
+ const c = getC();
65
+ const expand = Math.sin(phase) * (0.08 + hz * 0.02);
66
+ c.save();
67
+ c.translate(CX, CY);
68
+ c.beginPath();
69
+ for (let i = 0; i < 200; i++) {
70
+ const t = i / 200;
71
+ const r = 8 + t * 32 * (1 + expand);
72
+ const a = t * Math.PI * 8 + Math.sin(phase) * 0.3;
73
+ if (i === 0) c.moveTo(Math.cos(a) * r, Math.sin(a) * r);
74
+ else c.lineTo(Math.cos(a) * r, Math.sin(a) * r);
75
+ }
76
+ c.strokeStyle = highlight ? 'rgba(255,215,0,0.8)' : cl('rgba(192,208,224,0.5)', 'rgba(144,136,120,0.4)');
77
+ c.lineWidth = highlight ? 1.2 : 0.7;
78
+ c.stroke();
79
+ c.restore();
80
+ }
81
+
82
+ export function drawPallet(phase: number, highlight: boolean) {
83
+ const c = getC();
84
+ c.save();
85
+ c.translate(CX, CY + 70);
86
+ const a = Math.sin(phase) * 0.35;
87
+ c.rotate(a);
88
+ c.strokeStyle = highlight ? '#ffd700' : cl('#a0b0c0', '#908878');
89
+ c.lineWidth = 2.5;
90
+ c.lineCap = 'round';
91
+ c.beginPath(); c.moveTo(-12, -18); c.lineTo(0, 0); c.stroke();
92
+ c.beginPath(); c.moveTo(0, 0); c.lineTo(10, -14); c.stroke();
93
+ c.beginPath(); c.moveTo(0, 0); c.lineTo(-10, -14); c.stroke();
94
+ c.fillStyle = '#c02030';
95
+ c.beginPath(); c.arc(10, -14, 2.5, 0, Math.PI * 2); c.fill();
96
+ c.beginPath(); c.arc(-10, -14, 2.5, 0, Math.PI * 2); c.fill();
97
+ c.fillStyle = cl('#a0b0c0', '#908878');
98
+ c.beginPath(); c.arc(0, 0, 3.5, 0, Math.PI * 2); c.fill();
99
+ c.beginPath(); c.arc(-12, -18, 2.5, 0, Math.PI * 2); c.fill();
100
+ if (highlight) {
101
+ c.beginPath();
102
+ c.arc(0, 0, 22, 0, Math.PI * 2);
103
+ c.strokeStyle = 'rgba(212,175,55,0.6)';
104
+ c.lineWidth = 1.5;
105
+ c.setLineDash([3, 3]);
106
+ c.stroke();
107
+ c.setLineDash([]);
108
+ }
109
+ c.restore();
110
+ }
111
+
112
+ function drawEscapeTeeth() {
113
+ const c = getC();
114
+ for (let i = 0; i < 15; i++) {
115
+ const a = (i / 15) * Math.PI * 2;
116
+ const nextA = ((i + 1) / 15) * Math.PI * 2;
117
+ const midA = (a + nextA) / 2;
118
+ c.beginPath();
119
+ c.moveTo(Math.cos(a) * 18, Math.sin(a) * 18);
120
+ c.lineTo(Math.cos(midA) * 26, Math.sin(midA) * 26);
121
+ c.lineTo(Math.cos(nextA) * 18, Math.sin(nextA) * 18);
122
+ c.closePath();
123
+ c.fillStyle = cl('#d4af37', '#b89850');
124
+ c.fill();
125
+ c.strokeStyle = cl('rgba(180,150,80,0.3)', 'rgba(120,100,60,0.3)');
126
+ c.lineWidth = 0.5;
127
+ c.stroke();
128
+ }
129
+ }
130
+
131
+ function drawEscapeCenter() {
132
+ const c = getC();
133
+ c.beginPath();
134
+ c.arc(0, 0, 16, 0, Math.PI * 2);
135
+ c.fillStyle = cl('#b89440', '#a08040');
136
+ c.fill();
137
+ c.beginPath();
138
+ c.arc(0, 0, 4, 0, Math.PI * 2);
139
+ c.fillStyle = cl('#2a3040', '#d0c8b8');
140
+ c.fill();
141
+ }
142
+
143
+ export function drawEscapeWheel(angle: number, highlight: boolean) {
144
+ const c = getC();
145
+ c.save();
146
+ c.translate(CX, CY + 130);
147
+ c.rotate(angle);
148
+ drawEscapeTeeth();
149
+ drawEscapeCenter();
150
+ if (highlight) {
151
+ c.beginPath();
152
+ c.arc(0, 0, 30, 0, Math.PI * 2);
153
+ c.strokeStyle = 'rgba(212,175,55,0.6)';
154
+ c.lineWidth = 1.5;
155
+ c.setLineDash([3, 3]);
156
+ c.stroke();
157
+ c.setLineDash([]);
158
+ }
159
+ c.restore();
160
+ }
@@ -0,0 +1,11 @@
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { tourbillonVisualizer } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+ interface Props { locale?: KnownLocale; }
6
+ const { locale = 'en' } = Astro.props;
7
+ const loader = tourbillonVisualizer.i18n[locale] || tourbillonVisualizer.i18n.en;
8
+ const content = await loader?.();
9
+ if (!content) return null;
10
+ ---
11
+ {content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
@@ -0,0 +1,21 @@
1
+ let _ctx: CanvasRenderingContext2D;
2
+ let _isDark = true;
3
+ let _ff = 'system-ui, sans-serif';
4
+
5
+ export function getC() { return _ctx; }
6
+ export function isD() { return _isDark; }
7
+ export function ff() { return _ff; }
8
+
9
+ export function setCtx(c: CanvasRenderingContext2D) {
10
+ _ctx = c;
11
+ _ff = getComputedStyle(document.body).fontFamily || _ff;
12
+ }
13
+
14
+ export function detT() {
15
+ const b = getComputedStyle(document.body).backgroundColor;
16
+ _isDark = !b || b === 'rgba(0, 0, 0, 0)' || b === 'transparent' ? true : parseInt(b.replace(/[^\d,]/g, '').split(',')[0]) < 128;
17
+ }
18
+
19
+ export function cl(h: string, l: string) { return _isDark ? h : l; }
20
+
21
+ export const W = 700, H = 700, CX = 350, CY = 350;
@@ -0,0 +1,9 @@
1
+ export const BEAT_RATES = {
2
+ 18000: { hz: 2.5, rpm: 20, bph: 18000 },
3
+ 28800: { hz: 4, rpm: 32, bph: 28800 },
4
+ 36000: { hz: 5, rpm: 40, bph: 36000 },
5
+ };
6
+
7
+ export function calc(b: number): { hz: number; rpm: number; bph: number } {
8
+ return BEAT_RATES[b as keyof typeof BEAT_RATES] || BEAT_RATES[28800];
9
+ }
package/src/tools.ts CHANGED
@@ -18,6 +18,9 @@ import { STRAP_LENGTH_CALCULATOR_TOOL } from './tool/strap-length-calculator';
18
18
  import { TELEMETER_CALCULATOR_TOOL } from './tool/telemeter-calculator';
19
19
  import { SIDEREAL_TIME_TRACKER_TOOL } from './tool/sidereal-time-tracker';
20
20
  import { GEAR_TRAIN_EXPLORER_TOOL } from './tool/gear-train-explorer';
21
+ import { PERPETUAL_CALENDAR_TOOL } from './tool/perpetual-calendar';
22
+ import { TOURBILLON_VISUALIZER_TOOL } from './tool/tourbillon-visualizer';
23
+ import { GMT_WORLD_TIMER_TOOL } from './tool/gmt-world-timer';
21
24
 
22
25
  export const ALL_TOOLS: ToolDefinition[] = [
23
26
  WATCH_ACCURACY_TRACKER_TOOL,
@@ -38,6 +41,9 @@ export const ALL_TOOLS: ToolDefinition[] = [
38
41
  TELEMETER_CALCULATOR_TOOL,
39
42
  SIDEREAL_TIME_TRACKER_TOOL,
40
43
  GEAR_TRAIN_EXPLORER_TOOL,
44
+ PERPETUAL_CALENDAR_TOOL,
45
+ TOURBILLON_VISUALIZER_TOOL,
46
+ GMT_WORLD_TIMER_TOOL,
41
47
  ];
42
48
 
43
49