@jjlmoya/utils-chrono 1.10.0 → 1.11.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/category/index.ts +2 -0
  3. package/src/entries.ts +4 -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/bibliography.astro +16 -0
  7. package/src/tool/gear-train-explorer/bibliography.ts +12 -0
  8. package/src/tool/gear-train-explorer/client.ts +146 -0
  9. package/src/tool/gear-train-explorer/component.astro +17 -0
  10. package/src/tool/gear-train-explorer/components/GearPanel.astro +102 -0
  11. package/src/tool/gear-train-explorer/entry.ts +53 -0
  12. package/src/tool/gear-train-explorer/gear-train-explorer.css +172 -0
  13. package/src/tool/gear-train-explorer/gears.ts +148 -0
  14. package/src/tool/gear-train-explorer/helpers.ts +49 -0
  15. package/src/tool/gear-train-explorer/i18n/de.ts +99 -0
  16. package/src/tool/gear-train-explorer/i18n/en.ts +98 -0
  17. package/src/tool/gear-train-explorer/i18n/es.ts +99 -0
  18. package/src/tool/gear-train-explorer/i18n/fr.ts +99 -0
  19. package/src/tool/gear-train-explorer/i18n/id.ts +98 -0
  20. package/src/tool/gear-train-explorer/i18n/it.ts +99 -0
  21. package/src/tool/gear-train-explorer/i18n/ja.ts +98 -0
  22. package/src/tool/gear-train-explorer/i18n/ko.ts +98 -0
  23. package/src/tool/gear-train-explorer/i18n/nl.ts +99 -0
  24. package/src/tool/gear-train-explorer/i18n/pl.ts +99 -0
  25. package/src/tool/gear-train-explorer/i18n/pt.ts +99 -0
  26. package/src/tool/gear-train-explorer/i18n/ru.ts +99 -0
  27. package/src/tool/gear-train-explorer/i18n/sv.ts +99 -0
  28. package/src/tool/gear-train-explorer/i18n/tr.ts +98 -0
  29. package/src/tool/gear-train-explorer/i18n/zh.ts +98 -0
  30. package/src/tool/gear-train-explorer/index.ts +11 -0
  31. package/src/tool/gear-train-explorer/movements.ts +61 -0
  32. package/src/tool/gear-train-explorer/scene.ts +120 -0
  33. package/src/tool/gear-train-explorer/seo.astro +16 -0
  34. package/src/tool/gear-train-explorer/state.ts +30 -0
  35. package/src/tools.ts +2 -0
@@ -0,0 +1,120 @@
1
+ import type { MovementDef } from './movements';
2
+ import { getCtx, getMov, getHovered, getFontFam, setMov, setHovered, detectTheme, c, W, H } from './state';
3
+ import { drawGear, drawPallet, drawBalance, drawConnection } from './gears';
4
+
5
+ export function drawBackground() {
6
+ const ctx = getCtx();
7
+ const bg0 = c('#1e1e3a', '#f5f0e8');
8
+ const bg1 = c('#16162e', '#eae4d8');
9
+ const bg2 = c('#0c0c18', '#ddd6c8');
10
+ const grad = ctx.createRadialGradient(W / 2, H / 2, 0, W / 2, H / 2, 400);
11
+ grad.addColorStop(0, bg0);
12
+ grad.addColorStop(0.5, bg1);
13
+ grad.addColorStop(1, bg2);
14
+ ctx.fillStyle = grad;
15
+ ctx.fillRect(0, 0, W, H);
16
+ }
17
+
18
+ export function drawTitleBar(label: string) {
19
+ const ctx = getCtx();
20
+ ctx.fillStyle = c('rgba(212,175,55,0.7)', 'rgba(120,80,10,0.85)');
21
+ ctx.font = '600 14px ' + getFontFam();
22
+ ctx.textAlign = 'left';
23
+ ctx.fillText(label + ' — Gear Train', 16, 24);
24
+ ctx.fillStyle = c('rgba(160,160,184,0.55)', 'rgba(60,50,30,0.7)');
25
+ ctx.font = '11px ' + getFontFam();
26
+ ctx.fillText('Mainspring to Balance Wheel', 16, 40);
27
+ }
28
+
29
+ export function drawLabels() {
30
+ const ctx = getCtx();
31
+ const mov = getMov();
32
+ const hover = getHovered();
33
+ ctx.font = '10px ' + getFontFam();
34
+ ctx.textAlign = 'center';
35
+ for (let i = 0; i < mov.gears.length; i++) {
36
+ const g = mov.gears[i];
37
+ ctx.fillStyle = hover === i ? c('#ffd700', '#7a5a00') : c('rgba(200,200,220,0.8)', 'rgba(40,30,15,0.85)');
38
+ ctx.fillText(g.label, g.x, g.y + g.r + 14);
39
+ const rpmTxt = g.rpm < 1 ? (g.rpm * 60).toFixed(1) + '/h' : g.rpm.toFixed(1) + ' rpm';
40
+ ctx.fillStyle = hover === i ? c('rgba(255,215,0,0.6)', 'rgba(100,70,10,0.7)') : c('rgba(160,160,184,0.5)', 'rgba(60,50,30,0.65)');
41
+ ctx.font = '9px ' + getFontFam();
42
+ ctx.fillText(rpmTxt, g.x, g.y + g.r + 24);
43
+ ctx.font = '10px ' + getFontFam();
44
+ }
45
+ }
46
+
47
+ export function drawExtraLabels() {
48
+ const ctx = getCtx();
49
+ const mov = getMov();
50
+ const p = mov.pallet;
51
+ const b = mov.balance;
52
+ ctx.font = '10px ' + getFontFam();
53
+ ctx.textAlign = 'center';
54
+ ctx.fillStyle = c('rgba(200,200,220,0.8)', 'rgba(40,30,15,0.85)');
55
+ ctx.fillText('Pallet Fork', p.x, p.y + 22);
56
+ ctx.fillStyle = c('rgba(160,160,184,0.5)', 'rgba(60,50,30,0.65)');
57
+ ctx.font = '9px ' + getFontFam();
58
+ ctx.fillText(p.bph + ' bph', p.x, p.y + 32);
59
+ ctx.font = '10px ' + getFontFam();
60
+ ctx.fillStyle = c('rgba(200,200,220,0.8)', 'rgba(40,30,15,0.85)');
61
+ ctx.fillText('Balance Wheel', b.x, b.y + b.r + 16);
62
+ ctx.fillStyle = c('rgba(160,160,184,0.5)', 'rgba(60,50,30,0.65)');
63
+ ctx.font = '9px ' + getFontFam();
64
+ ctx.fillText(b.hz + ' Hz / ' + b.vph + ' vph', b.x, b.y + b.r + 26);
65
+ }
66
+
67
+ export function drawPowerFlowLine() {
68
+ const ctx = getCtx();
69
+ const mov = getMov();
70
+ const gears = mov.gears;
71
+ ctx.beginPath();
72
+ ctx.moveTo(gears[0].x + gears[0].r + 5, gears[0].y);
73
+ for (let i = 1; i < gears.length; i++) {
74
+ ctx.lineTo(gears[i].x - gears[i].r - 5, gears[i].y);
75
+ }
76
+ const eg = gears[gears.length - 1];
77
+ ctx.lineTo(eg.x + eg.r + 15, eg.y);
78
+ ctx.lineTo(mov.pallet.x - 15, mov.pallet.y);
79
+ ctx.lineTo(mov.pallet.x, mov.pallet.y);
80
+ ctx.strokeStyle = c('rgba(212,175,55,0.08)', 'rgba(139,105,20,0.12)');
81
+ ctx.lineWidth = 60; ctx.stroke();
82
+ ctx.strokeStyle = c('rgba(212,175,55,0.12)', 'rgba(139,105,20,0.15)');
83
+ ctx.lineWidth = 2;
84
+ ctx.setLineDash([4, 6]); ctx.stroke();
85
+ ctx.setLineDash([]);
86
+ }
87
+
88
+ function drawConnections(mov: MovementDef, highlight: number | null) {
89
+ const gears = mov.gears;
90
+ for (let i = 0; i < gears.length; i++) {
91
+ if (i < gears.length - 1) {
92
+ const g1 = gears[i], g2 = gears[i + 1];
93
+ drawConnection({ x1: g1.x + g1.r * 0.7, y1: g1.y, x2: g2.x - g2.r * 0.7, y2: g2.y, active: highlight === i || highlight === i + 1 });
94
+ }
95
+ }
96
+ const lg = gears[gears.length - 1];
97
+ drawConnection({ x1: lg.x + lg.r * 0.7, y1: lg.y, x2: mov.pallet.x - 10, y2: mov.pallet.y, active: highlight === gears.length - 1 });
98
+ drawConnection({ x1: mov.pallet.x, y1: mov.pallet.y, x2: mov.balance.x, y2: mov.balance.y, active: highlight === gears.length - 1 });
99
+ }
100
+
101
+ export function drawScene(mov: MovementDef, opts: { angles: number[]; palletPhase: number; balancePhase: number; highlight: number | null; hover: number | null }) {
102
+ const ctx = getCtx();
103
+ setMov(mov);
104
+ setHovered(opts.hover);
105
+ detectTheme();
106
+ ctx.save();
107
+ ctx.clearRect(0, 0, W, H);
108
+ drawBackground();
109
+ drawTitleBar(mov.label);
110
+ drawConnections(mov, opts.highlight);
111
+ for (let i = 0; i < mov.gears.length; i++) {
112
+ const g = mov.gears[i];
113
+ drawGear({ x: g.x, y: g.y, r: g.r, teeth: g.teeth, angle: opts.angles[i], color: g.color, highlight: opts.hover === i });
114
+ }
115
+ drawPallet(mov.pallet.x, mov.pallet.y, opts.palletPhase);
116
+ drawBalance(mov.balance.x, mov.balance.y, mov.balance.r, opts.balancePhase);
117
+ drawLabels();
118
+ drawExtraLabels();
119
+ ctx.restore();
120
+ }
@@ -0,0 +1,16 @@
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { gearTrainExplorer } 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 = gearTrainExplorer.i18n[locale] || gearTrainExplorer.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,30 @@
1
+ import type { MovementDef } from './movements';
2
+
3
+ let _ctx: CanvasRenderingContext2D;
4
+ let _mov: MovementDef;
5
+ let _hovered: number | null = null;
6
+ let _isDark = true;
7
+ let _fontFam = 'system-ui, sans-serif';
8
+
9
+ export function getCtx() { return _ctx; }
10
+ export function getMov() { return _mov; }
11
+ export function getHovered() { return _hovered; }
12
+ export function isDark() { return _isDark; }
13
+ export function getFontFam() { return _fontFam; }
14
+ export function setMov(m: MovementDef) { _mov = m; }
15
+ export function setHovered(h: number | null) { _hovered = h; }
16
+
17
+ export function setCtx(ctx: CanvasRenderingContext2D) {
18
+ _ctx = ctx;
19
+ _fontFam = getComputedStyle(document.body).fontFamily || _fontFam;
20
+ }
21
+
22
+ export function detectTheme() {
23
+ const bg = window.getComputedStyle(document.body).backgroundColor;
24
+ _isDark = !bg || bg === 'rgba(0, 0, 0, 0)' || bg === 'transparent' ? true : parseInt(bg.replace(/[^\d,]/g, '').split(',')[0]) < 128;
25
+ }
26
+
27
+ export function c(h: string, l: string): string { return _isDark ? h : l; }
28
+
29
+ export const W = 900;
30
+ export const H = 520;
package/src/tools.ts CHANGED
@@ -17,6 +17,7 @@ import { SERVICE_INTERVAL_TRACKER_TOOL } from './tool/service-interval-tracker';
17
17
  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
+ import { GEAR_TRAIN_EXPLORER_TOOL } from './tool/gear-train-explorer';
20
21
 
21
22
  export const ALL_TOOLS: ToolDefinition[] = [
22
23
  WATCH_ACCURACY_TRACKER_TOOL,
@@ -36,6 +37,7 @@ export const ALL_TOOLS: ToolDefinition[] = [
36
37
  STRAP_LENGTH_CALCULATOR_TOOL,
37
38
  TELEMETER_CALCULATOR_TOOL,
38
39
  SIDEREAL_TIME_TRACKER_TOOL,
40
+ GEAR_TRAIN_EXPLORER_TOOL,
39
41
  ];
40
42
 
41
43