@jjlmoya/utils-chrono 1.4.0 → 1.5.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 (114) 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/index.ts +1 -0
  5. package/src/tests/locale_completeness.test.ts +1 -1
  6. package/src/tests/no_en_dash.test.ts +41 -0
  7. package/src/tests/tool_validation.test.ts +1 -1
  8. package/src/tool/beat-rate-converter/i18n/en.ts +1 -1
  9. package/src/tool/demagnetizing-timer/components/TimerPanel.astro +45 -17
  10. package/src/tool/demagnetizing-timer/i18n/de.ts +2 -2
  11. package/src/tool/demagnetizing-timer/i18n/en.ts +1 -1
  12. package/src/tool/demagnetizing-timer/i18n/es.ts +1 -1
  13. package/src/tool/demagnetizing-timer/i18n/id.ts +1 -1
  14. package/src/tool/demagnetizing-timer/i18n/it.ts +1 -1
  15. package/src/tool/demagnetizing-timer/i18n/nl.ts +1 -1
  16. package/src/tool/demagnetizing-timer/i18n/pl.ts +1 -1
  17. package/src/tool/demagnetizing-timer/i18n/pt.ts +1 -1
  18. package/src/tool/demagnetizing-timer/i18n/ru.ts +3 -3
  19. package/src/tool/demagnetizing-timer/i18n/sv.ts +1 -1
  20. package/src/tool/demagnetizing-timer/i18n/tr.ts +1 -1
  21. package/src/tool/lume-color-simulator/i18n/de.ts +13 -13
  22. package/src/tool/lume-color-simulator/i18n/en.ts +8 -8
  23. package/src/tool/lume-color-simulator/i18n/es.ts +7 -7
  24. package/src/tool/lume-color-simulator/i18n/fr.ts +4 -4
  25. package/src/tool/lume-color-simulator/i18n/id.ts +7 -7
  26. package/src/tool/lume-color-simulator/i18n/it.ts +7 -7
  27. package/src/tool/lume-color-simulator/i18n/ko.ts +3 -3
  28. package/src/tool/lume-color-simulator/i18n/nl.ts +8 -8
  29. package/src/tool/lume-color-simulator/i18n/pl.ts +13 -13
  30. package/src/tool/lume-color-simulator/i18n/pt.ts +4 -4
  31. package/src/tool/lume-color-simulator/i18n/ru.ts +8 -8
  32. package/src/tool/lume-color-simulator/i18n/sv.ts +16 -16
  33. package/src/tool/lume-color-simulator/i18n/tr.ts +8 -8
  34. package/src/tool/lume-color-simulator/i18n/zh.ts +7 -7
  35. package/src/tool/moon-phase-visualizer/i18n/de.ts +10 -10
  36. package/src/tool/moon-phase-visualizer/i18n/en.ts +6 -6
  37. package/src/tool/moon-phase-visualizer/i18n/es.ts +4 -4
  38. package/src/tool/moon-phase-visualizer/i18n/fr.ts +4 -4
  39. package/src/tool/moon-phase-visualizer/i18n/id.ts +4 -4
  40. package/src/tool/moon-phase-visualizer/i18n/it.ts +4 -4
  41. package/src/tool/moon-phase-visualizer/i18n/ko.ts +2 -2
  42. package/src/tool/moon-phase-visualizer/i18n/nl.ts +6 -6
  43. package/src/tool/moon-phase-visualizer/i18n/pl.ts +9 -9
  44. package/src/tool/moon-phase-visualizer/i18n/pt.ts +4 -4
  45. package/src/tool/moon-phase-visualizer/i18n/ru.ts +4 -4
  46. package/src/tool/moon-phase-visualizer/i18n/sv.ts +10 -10
  47. package/src/tool/moon-phase-visualizer/i18n/tr.ts +6 -6
  48. package/src/tool/moon-phase-visualizer/i18n/zh.ts +4 -4
  49. package/src/tool/power-reserve-estimator/i18n/de.ts +2 -2
  50. package/src/tool/power-reserve-estimator/i18n/es.ts +2 -2
  51. package/src/tool/power-reserve-estimator/i18n/fr.ts +2 -2
  52. package/src/tool/power-reserve-estimator/i18n/id.ts +2 -2
  53. package/src/tool/power-reserve-estimator/i18n/it.ts +2 -2
  54. package/src/tool/power-reserve-estimator/i18n/nl.ts +2 -2
  55. package/src/tool/power-reserve-estimator/i18n/pt.ts +2 -2
  56. package/src/tool/strap-taper-calculator/i18n/ru.ts +1 -1
  57. package/src/tool/tachymeter-calculator/bibliography.astro +16 -0
  58. package/src/tool/tachymeter-calculator/bibliography.ts +16 -0
  59. package/src/tool/tachymeter-calculator/client.ts +180 -0
  60. package/src/tool/tachymeter-calculator/component.astro +15 -0
  61. package/src/tool/tachymeter-calculator/components/CalculatorPanel.astro +121 -0
  62. package/src/tool/tachymeter-calculator/entry.ts +43 -0
  63. package/src/tool/tachymeter-calculator/i18n/de.ts +172 -0
  64. package/src/tool/tachymeter-calculator/i18n/en.ts +172 -0
  65. package/src/tool/tachymeter-calculator/i18n/es.ts +172 -0
  66. package/src/tool/tachymeter-calculator/i18n/fr.ts +172 -0
  67. package/src/tool/tachymeter-calculator/i18n/id.ts +172 -0
  68. package/src/tool/tachymeter-calculator/i18n/it.ts +172 -0
  69. package/src/tool/tachymeter-calculator/i18n/ja.ts +172 -0
  70. package/src/tool/tachymeter-calculator/i18n/ko.ts +172 -0
  71. package/src/tool/tachymeter-calculator/i18n/nl.ts +172 -0
  72. package/src/tool/tachymeter-calculator/i18n/pl.ts +172 -0
  73. package/src/tool/tachymeter-calculator/i18n/pt.ts +172 -0
  74. package/src/tool/tachymeter-calculator/i18n/ru.ts +172 -0
  75. package/src/tool/tachymeter-calculator/i18n/sv.ts +172 -0
  76. package/src/tool/tachymeter-calculator/i18n/tr.ts +172 -0
  77. package/src/tool/tachymeter-calculator/i18n/zh.ts +172 -0
  78. package/src/tool/tachymeter-calculator/index.ts +11 -0
  79. package/src/tool/tachymeter-calculator/seo.astro +16 -0
  80. package/src/tool/tachymeter-calculator/tachymeter-calculator.css +492 -0
  81. package/src/tool/tachymeter-calculator/utils.ts +10 -0
  82. package/src/tool/watch-accuracy-tracker/i18n/pl.ts +1 -1
  83. package/src/tool/watch-accuracy-tracker/i18n/ru.ts +4 -4
  84. package/src/tool/watch-savings-planner/i18n/en.ts +3 -3
  85. package/src/tool/watch-size-comparator/i18n/de.ts +30 -30
  86. package/src/tool/watch-size-comparator/i18n/en.ts +20 -20
  87. package/src/tool/watch-size-comparator/i18n/es.ts +16 -16
  88. package/src/tool/watch-size-comparator/i18n/fr.ts +18 -18
  89. package/src/tool/watch-size-comparator/i18n/id.ts +18 -18
  90. package/src/tool/watch-size-comparator/i18n/it.ts +18 -18
  91. package/src/tool/watch-size-comparator/i18n/ko.ts +11 -11
  92. package/src/tool/watch-size-comparator/i18n/nl.ts +20 -20
  93. package/src/tool/watch-size-comparator/i18n/pl.ts +27 -27
  94. package/src/tool/watch-size-comparator/i18n/pt.ts +17 -17
  95. package/src/tool/watch-size-comparator/i18n/ru.ts +18 -18
  96. package/src/tool/watch-size-comparator/i18n/sv.ts +29 -29
  97. package/src/tool/watch-size-comparator/i18n/tr.ts +20 -20
  98. package/src/tool/watch-size-comparator/i18n/zh.ts +17 -17
  99. package/src/tool/water-resistance-converter/i18n/de.ts +1 -1
  100. package/src/tool/water-resistance-converter/i18n/en.ts +1 -1
  101. package/src/tool/water-resistance-converter/i18n/es.ts +1 -1
  102. package/src/tool/water-resistance-converter/i18n/id.ts +1 -1
  103. package/src/tool/water-resistance-converter/i18n/ja.ts +1 -1
  104. package/src/tool/water-resistance-converter/i18n/ko.ts +1 -1
  105. package/src/tool/water-resistance-converter/i18n/nl.ts +1 -1
  106. package/src/tool/water-resistance-converter/i18n/pl.ts +1 -1
  107. package/src/tool/water-resistance-converter/i18n/pt.ts +1 -1
  108. package/src/tool/water-resistance-converter/i18n/ru.ts +1 -1
  109. package/src/tool/water-resistance-converter/i18n/sv.ts +1 -1
  110. package/src/tool/water-resistance-converter/i18n/tr.ts +1 -1
  111. package/src/tool/water-resistance-converter/i18n/zh.ts +1 -1
  112. package/src/tool/wrist-presence-calculator/i18n/de.ts +1 -1
  113. package/src/tool/wrist-presence-calculator/i18n/ru.ts +18 -18
  114. package/src/tools.ts +2 -0
@@ -44,7 +44,7 @@ export const content: ToolLocaleContent<StrapTaperCalculatorUI> = {
44
44
  { type: 'title', text: 'Золотое сечение для пропорций ремешка', level: 3 },
45
45
  { type: 'paragraph', html: 'Энтузиасты часов в целом сходятся во мнении, что ширина ушек от 45% до 55% диаметра корпуса выглядит лучше всего. Что касается сужения, ширина пряжки от 75% до 85% ширины ушек создаёт сбалансированный, элегантный силуэт. Соотношения за пределами этого диапазона могут сделать часы визуально тяжелыми или слишком хрупкими.' },
46
46
  { type: 'title', text: 'Сужение по стилю часов', level: 3 },
47
- { type: 'paragraph', html: 'Классические часы: сужение на 4 мм (с 20 до 16 мм). Спортивные часы: сужение на 23 мм. Дайверские часы: сужение на 02 мм. Военные или полевые часы: прямое сужение для прочного вида. Пилотские часы: минимальное сужение в соответствии с эстетикой приборов.' },
47
+ { type: 'paragraph', html: 'Классические часы: сужение на 4 мм (с 20 до 16 мм). Спортивные часы: сужение на 2-3 мм. Дайверские часы: сужение на 0-2 мм. Военные или полевые часы: прямое сужение для прочного вида. Пилотские часы: минимальное сужение в соответствии с эстетикой приборов.' },
48
48
  ],
49
49
  faq: [
50
50
  {
@@ -0,0 +1,16 @@
1
+ ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import { tachymeterCalculator } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'en' } = Astro.props as Props;
11
+ const loader = tachymeterCalculator.i18n[locale] || tachymeterCalculator.i18n.en;
12
+ const content = await loader?.();
13
+ if (!content) return null;
14
+ ---
15
+
16
+ {content && <SharedBibliography links={content.bibliography} />}
@@ -0,0 +1,16 @@
1
+ import type { BibliographyEntry } from '../../types';
2
+
3
+ export const bibliography: BibliographyEntry[] = [
4
+ {
5
+ name: 'Tachymeter-Wikipedia',
6
+ url: 'https://en.wikipedia.org/wiki/Tachymeter_(watch)',
7
+ },
8
+ {
9
+ name: 'How to Use a Tachymeter Bezel',
10
+ url: 'https://www.truefacet.com/guide/understanding-tachymeter-bezel/',
11
+ },
12
+ {
13
+ name: 'Tachymeter Explained: Formula & Examples',
14
+ url: 'https://www.citizenwatch-global.com/support/exterior/tachymeter.html',
15
+ },
16
+ ];
@@ -0,0 +1,180 @@
1
+ const presetSelect = document.getElementById('preset-select') as HTMLSelectElement;
2
+ const timeInput = document.getElementById('time-input') as HTMLInputElement;
3
+ const customSection = document.getElementById('custom-section') as HTMLElement;
4
+ const speedValue = document.getElementById('speed-value') as HTMLElement;
5
+ const bezelValue = document.getElementById('bezel-value') as HTMLElement;
6
+ const handGroup = document.getElementById('tach-hand-group') as SVGGElement;
7
+ const ticksG = document.getElementById('tach-ticks') as SVGElement;
8
+ const highlightG = document.getElementById('tach-highlight') as SVGElement;
9
+ const sweepEl = document.getElementById('tach-sweep') as SVGPathElement;
10
+ const timeLabel = document.getElementById('tach-time-label') as SVGTextElement;
11
+ const speedLabel = document.getElementById('tach-speed-label') as SVGTextElement;
12
+ const visualEl = document.getElementById('tach-visual') as HTMLElement;
13
+
14
+ const CX = 110;
15
+ const CY = 110;
16
+ const ARC_R = 84;
17
+ const TICK_R1 = 79;
18
+ const TICK_R2 = 85;
19
+ const T_MIN = 7.2;
20
+ const T_MAX = 60;
21
+ const A_MIN = 12;
22
+
23
+ interface Preset { label: string; seconds: number; }
24
+
25
+ const presets: Record<string, Preset> = {
26
+ car: { label: 'Car (1 km)', seconds: 36 },
27
+ bike: { label: 'Cycling (1 km)', seconds: 90 },
28
+ run: { label: 'Running (1 km)', seconds: 240 },
29
+ walk: { label: 'Walking (1 km)', seconds: 480 },
30
+ f1: { label: 'F1 Pit Stop', seconds: 2.5 },
31
+ plane: { label: 'Private Jet (1 km)', seconds: 12 },
32
+ train: { label: 'Bullet Train (1 km)', seconds: 10 },
33
+ };
34
+
35
+ const labelSpeeds = [60, 70, 80, 90, 100, 120, 150, 200, 300, 400, 500];
36
+ const majorSpeeds = [65, 75, 85, 95, 130, 140, 160, 170, 220, 240, 260, 280, 350, 450];
37
+
38
+ function speedToTime(s: number): number {
39
+ return 3600 / s;
40
+ }
41
+
42
+ function timeToAngle(t: number): number {
43
+ const ratio = Math.log(t / T_MIN) / Math.log(T_MAX / T_MIN);
44
+ return 12 + ratio * 228;
45
+ }
46
+
47
+ function pos(r: number, a: number): { x: number; y: number } {
48
+ const rad = a * Math.PI / 180;
49
+ return { x: CX + r * Math.sin(rad), y: CY - r * Math.cos(rad) };
50
+ }
51
+
52
+ function addTick(spd: number, cls: string, label?: string) {
53
+ const t = speedToTime(spd);
54
+ if (t < T_MIN || t > T_MAX) return;
55
+ const a = timeToAngle(t);
56
+ const o = pos(TICK_R2, a);
57
+ const i = pos(TICK_R1, a);
58
+ const ln = document.createElementNS('http://www.w3.org/2000/svg', 'line');
59
+ ln.setAttribute('x1', String(i.x));
60
+ ln.setAttribute('y1', String(i.y));
61
+ ln.setAttribute('x2', String(o.x));
62
+ ln.setAttribute('y2', String(o.y));
63
+ ln.classList.add(cls);
64
+ ticksG.appendChild(ln);
65
+ if (label) {
66
+ const lp = pos(93, a);
67
+ const tx = document.createElementNS('http://www.w3.org/2000/svg', 'text');
68
+ tx.setAttribute('x', String(lp.x));
69
+ tx.setAttribute('y', String(lp.y));
70
+ tx.setAttribute('text-anchor', 'middle');
71
+ tx.setAttribute('dominant-baseline', 'central');
72
+ tx.classList.add('tick-label');
73
+ tx.textContent = label;
74
+ ticksG.appendChild(tx);
75
+ }
76
+ }
77
+
78
+ function stepSize(s: number): number {
79
+ if (s < 100) return 2;
80
+ if (s < 200) return 5;
81
+ if (s < 300) return 10;
82
+ return 20;
83
+ }
84
+
85
+ function buildBezel() {
86
+ for (let s = 60; s <= 500; s += stepSize(s)) {
87
+ if (labelSpeeds.includes(s) || majorSpeeds.includes(s)) continue;
88
+ addTick(s, 'tick-minor');
89
+ }
90
+ majorSpeeds.forEach((s) => addTick(s, 'tick-major'));
91
+ labelSpeeds.forEach((s) => addTick(s, 'tick-major', String(s)));
92
+ }
93
+
94
+ function getTime(): number {
95
+ if (presetSelect.value === 'custom') {
96
+ return parseFloat(timeInput.value) || 36;
97
+ }
98
+ return presets[presetSelect.value]?.seconds || 36;
99
+ }
100
+
101
+ function arcPath(r: number, a: number): string {
102
+ const s = pos(r, 12);
103
+ const e = pos(r, a);
104
+ const large = a > 192 ? 1 : 0;
105
+ return `M ${s.x} ${s.y} A ${r} ${r} 0 ${large} 1 ${e.x} ${e.y}`;
106
+ }
107
+
108
+ function updateHighlight(angle: number) {
109
+ highlightG.innerHTML = '';
110
+ const p = pos(ARC_R, angle);
111
+ const g = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
112
+ g.setAttribute('cx', String(p.x));
113
+ g.setAttribute('cy', String(p.y));
114
+ g.setAttribute('r', '6');
115
+ g.classList.add('tach-glow');
116
+ highlightG.appendChild(g);
117
+ const rg = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
118
+ rg.setAttribute('cx', String(p.x));
119
+ rg.setAttribute('cy', String(p.y));
120
+ rg.setAttribute('r', '10');
121
+ rg.classList.add('tach-glow-ring');
122
+ highlightG.appendChild(rg);
123
+ }
124
+
125
+ function calc() {
126
+ const time = getTime();
127
+ const speed = 3600 / time;
128
+ const clampedTime = Math.max(T_MIN, Math.min(T_MAX, time));
129
+ const angle = timeToAngle(clampedTime);
130
+ const display = speed >= 100 ? speed.toFixed(0) : speed.toFixed(1);
131
+
132
+ speedValue.textContent = display;
133
+ bezelValue.textContent = display;
134
+ timeLabel.textContent = time < 60 ? `${time.toFixed(1)}s` : `${Math.floor(time / 60)}m ${Math.round(time % 60)}s`;
135
+ speedLabel.textContent = display;
136
+
137
+ handGroup.style.transform = `rotate(${angle}deg)`;
138
+ sweepEl.setAttribute('d', arcPath(ARC_R, angle));
139
+ updateHighlight(angle);
140
+
141
+ visualEl.classList.remove('speed-slow', 'speed-mid', 'speed-fast');
142
+ if (speed >= 200) visualEl.classList.add('speed-fast');
143
+ else if (speed >= 100) visualEl.classList.add('speed-mid');
144
+ else visualEl.classList.add('speed-slow');
145
+ }
146
+
147
+ function toggleCustom() {
148
+ const show = presetSelect.value === 'custom';
149
+ customSection.style.display = show ? 'flex' : 'none';
150
+ if (!show) {
151
+ timeInput.value = String(presets[presetSelect.value]?.seconds || 36);
152
+ }
153
+ calc();
154
+ }
155
+
156
+ presetSelect.addEventListener('change', () => {
157
+ handGroup.style.transition = 'none';
158
+ handGroup.style.transform = `rotate(${A_MIN}deg)`;
159
+ requestAnimationFrame(() => {
160
+ handGroup.style.transition = '';
161
+ toggleCustom();
162
+ });
163
+ });
164
+
165
+ timeInput.addEventListener('input', calc);
166
+
167
+ document.querySelectorAll('.stepper-btn').forEach((btn) => {
168
+ btn.addEventListener('click', () => {
169
+ presetSelect.value = 'custom';
170
+ toggleCustom();
171
+ const step = parseFloat(btn.getAttribute('data-step') || '1');
172
+ const dir = btn.textContent?.includes('+') ? 1 : -1;
173
+ const val = parseFloat(timeInput.value) || 36;
174
+ timeInput.value = String(Math.max(1, Math.min(600, val + dir * step)));
175
+ calc();
176
+ });
177
+ });
178
+
179
+ buildBezel();
180
+ toggleCustom();
@@ -0,0 +1,15 @@
1
+ ---
2
+ import CalculatorPanel from './components/CalculatorPanel.astro';
3
+
4
+ interface Props {
5
+ ui: Record<string, string>;
6
+ }
7
+
8
+ const { ui } = Astro.props;
9
+ ---
10
+
11
+ <div class="tool-main-card" data-ui={JSON.stringify(ui)}>
12
+ <CalculatorPanel labels={ui} />
13
+ </div>
14
+
15
+ <script src="./client.ts"></script>
@@ -0,0 +1,121 @@
1
+ ---
2
+ interface Props {
3
+ labels: Record<string, string>;
4
+ }
5
+
6
+ const { labels } = Astro.props;
7
+ ---
8
+
9
+ <div class="tach-panel">
10
+ <div class="panel-section">
11
+ <div class="section-label">{labels.presetLabel || "Preset"}</div>
12
+ <select class="preset-select" id="preset-select">
13
+ <option value="car">Car (36s per km)</option>
14
+ <option value="bike">Cycling (90s per km)</option>
15
+ <option value="run">Running (240s per km)</option>
16
+ <option value="walk">Walking (480s per km)</option>
17
+ <option value="f1">F1 Pit Stop (2.5s)</option>
18
+ <option value="plane">Private Jet (12s per km)</option>
19
+ <option value="train">Bullet Train (10s per km)</option>
20
+ <option value="custom">{labels.customPreset || "Custom"}</option>
21
+ </select>
22
+ </div>
23
+
24
+ <div class="panel-section" id="custom-section" style="display: none;">
25
+ <div class="section-label">{labels.timeLabel || "Elapsed Time"}</div>
26
+ <div class="stepper-wrap">
27
+ <button type="button" class="stepper-btn" data-step="1">−1</button>
28
+ <input
29
+ type="number"
30
+ class="stepper-input"
31
+ id="time-input"
32
+ min="1"
33
+ max="600"
34
+ step="0.1"
35
+ value="36"
36
+ />
37
+ <button type="button" class="stepper-btn" data-step="1">+1</button>
38
+ </div>
39
+ </div>
40
+
41
+ <div class="tach-visual" id="tach-visual">
42
+ <svg class="tach-svg" viewBox="0 0 220 220" id="tach-svg">
43
+ <defs>
44
+ <linearGradient id="bezel-grad" x1="0%" y1="0%" x2="100%" y2="100%">
45
+ <stop offset="0%" stop-color="var(--text-base)" stop-opacity="0.12" />
46
+ <stop offset="100%" stop-color="var(--text-base)" stop-opacity="0.04" />
47
+ </linearGradient>
48
+ <radialGradient id="dial-grad" cx="50%" cy="50%" r="50%">
49
+ <stop offset="0%" stop-color="var(--bg-surface)" stop-opacity="1" />
50
+ <stop offset="100%" stop-color="var(--bg-page)" stop-opacity="0.6" />
51
+ </radialGradient>
52
+ <filter id="glow">
53
+ <feGaussianBlur stdDeviation="2.5" result="blur" />
54
+ <feMerge>
55
+ <feMergeNode in="blur" />
56
+ <feMergeNode in="SourceGraphic" />
57
+ </feMerge>
58
+ </filter>
59
+ <filter id="glow-strong">
60
+ <feGaussianBlur stdDeviation="4" result="blur" />
61
+ <feMerge>
62
+ <feMergeNode in="blur" />
63
+ <feMergeNode in="SourceGraphic" />
64
+ </feMerge>
65
+ </filter>
66
+ </defs>
67
+
68
+ <circle class="tach-bezel-bg" cx="110" cy="110" r="106" />
69
+ <circle class="tach-bezel-outer" cx="110" cy="110" r="103" />
70
+ <circle class="tach-bezel-inner" cx="110" cy="110" r="76" />
71
+
72
+ <g id="tach-ticks"></g>
73
+ <path class="tach-sweep" id="tach-sweep" />
74
+ <g id="tach-highlight"></g>
75
+
76
+ <circle class="tach-dial" cx="110" cy="110" r="74" />
77
+
78
+ <g class="tach-hand-group" id="tach-hand-group">
79
+ <line class="tach-hand" id="tach-hand" x1="110" y1="110" x2="110" y2="30" />
80
+ <line class="tach-hand-tail" x1="110" y1="110" x2="110" y2="118" />
81
+ </g>
82
+
83
+ <circle class="tach-pip" cx="110" cy="110" r="4" />
84
+ <circle class="tach-pip-ring" cx="110" cy="110" r="7" />
85
+
86
+ <text class="tach-time-label" id="tach-time-label" x="110" y="106" text-anchor="middle">36.0s</text>
87
+ <text class="tach-speed-label" id="tach-speed-label" x="110" y="134" text-anchor="middle">100</text>
88
+ <text class="tach-unit-label" x="110" y="149" text-anchor="middle">km/h</text>
89
+ </svg>
90
+ </div>
91
+
92
+ <div class="tach-readout" id="tach-readout">
93
+ <div class="readout-block">
94
+ <span class="readout-value" id="speed-value">100</span>
95
+ <span class="readout-unit">km/h</span>
96
+ </div>
97
+ <div class="readout-divider"></div>
98
+ <div class="readout-block">
99
+ <span class="readout-label">{labels.bezelReading || "Bezel"}</span>
100
+ <span class="readout-bezel" id="bezel-value">100</span>
101
+ </div>
102
+ </div>
103
+
104
+ <div class="steps-section">
105
+ <div class="step-row">
106
+ <div class="step-marker">1</div>
107
+ <span class="step-text">{labels.exampleStep1 || "Start the chronograph when the object passes the starting point."}</span>
108
+ </div>
109
+ <div class="step-row">
110
+ <div class="step-marker">2</div>
111
+ <span class="step-text">{labels.exampleStep2 || "Stop it after 1 km. The tachymeter scale shows the speed."}</span>
112
+ </div>
113
+ </div>
114
+
115
+ <div class="tip-row">
116
+ <svg class="tip-icon" viewBox="0 0 24 24" width="16" height="16">
117
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" fill="currentColor"></path>
118
+ </svg>
119
+ <span class="tip-text">{labels.tipContent || "At 36 seconds the bezel reads 100. Faster times (shorter elapsed) point to higher speeds on the tachymeter scale."}</span>
120
+ </div>
121
+ </div>
@@ -0,0 +1,43 @@
1
+ import type { ChronoToolEntry, ToolLocaleContent } from '../../types';
2
+
3
+ export type TachymeterCalculatorUI = {
4
+ title: string;
5
+ timeLabel: string;
6
+ timeHelp: string;
7
+ speedResult: string;
8
+ bezelReading: string;
9
+ presetLabel: string;
10
+ selectPreset: string;
11
+ customPreset: string;
12
+ seconds: string;
13
+ kmh: string;
14
+ mph: string;
15
+ exampleStep1: string;
16
+ exampleStep2: string;
17
+ tipTitle: string;
18
+ tipContent: string;
19
+ };
20
+
21
+ export type TachymeterCalculatorLocaleContent = ToolLocaleContent<TachymeterCalculatorUI>;
22
+
23
+ export const tachymeterCalculator: ChronoToolEntry<TachymeterCalculatorUI> = {
24
+ id: 'tachymeter-calculator',
25
+ icons: { bg: 'mdi:speedometer', fg: 'mdi:clock-outline' },
26
+ i18n: {
27
+ de: () => import('./i18n/de').then((m) => m.content),
28
+ en: () => import('./i18n/en').then((m) => m.content),
29
+ es: () => import('./i18n/es').then((m) => m.content),
30
+ fr: () => import('./i18n/fr').then((m) => m.content),
31
+ id: () => import('./i18n/id').then((m) => m.content),
32
+ it: () => import('./i18n/it').then((m) => m.content),
33
+ ja: () => import('./i18n/ja').then((m) => m.content),
34
+ ko: () => import('./i18n/ko').then((m) => m.content),
35
+ nl: () => import('./i18n/nl').then((m) => m.content),
36
+ pl: () => import('./i18n/pl').then((m) => m.content),
37
+ pt: () => import('./i18n/pt').then((m) => m.content),
38
+ ru: () => import('./i18n/ru').then((m) => m.content),
39
+ sv: () => import('./i18n/sv').then((m) => m.content),
40
+ tr: () => import('./i18n/tr').then((m) => m.content),
41
+ zh: () => import('./i18n/zh').then((m) => m.content),
42
+ },
43
+ };
@@ -0,0 +1,172 @@
1
+ import type { ToolLocaleContent } from '../../../types';
2
+ import type { TachymeterCalculatorUI } from '../entry';
3
+ import { bibliography } from '../bibliography';
4
+
5
+ export const content: ToolLocaleContent<TachymeterCalculatorUI> = {
6
+ slug: 'tachymeter-rechner',
7
+ title: 'Tachymeter Rechner: Geschwindigkeit aus verstrichener Zeit',
8
+ description: 'Berechnen Sie die Geschwindigkeit aus der verstrichenen Zeit mit der Tachymeter-Formel. Funktioniert mit jeder Chronographenuhr mit Tachymeterskala.',
9
+ ui: {
10
+ title: 'Tachymeter Rechner',
11
+ timeLabel: 'Verstrichene Zeit (Sekunden)',
12
+ timeHelp: 'Gemessene Zeit über 1 km',
13
+ speedResult: 'Geschwindigkeit',
14
+ bezelReading: 'Lünettenanzeige',
15
+ presetLabel: 'Szenario',
16
+ selectPreset: 'Szenario auswählen',
17
+ customPreset: 'Benutzerdefiniert',
18
+ seconds: 's',
19
+ kmh: 'km/h',
20
+ mph: 'mph',
21
+ exampleStep1: 'Starten Sie den Chronographen, wenn das Objekt den Startpunkt passiert.',
22
+ exampleStep2: 'Stoppen Sie ihn nach 1 km. Die Tachymeterskala zeigt die Geschwindigkeit.',
23
+ tipTitle: 'Hinweis',
24
+ tipContent: 'Bei 36 Sekunden zeigt die Lünette 100. Schnellere Zeiten (kürzere Dauer) zeigen höhere Geschwindigkeiten auf der Tachymeterskala an.',
25
+ },
26
+ seo: [
27
+ { type: 'title', text: 'Tachymeter Rechner: Geschwindigkeit aus verstrichener Zeit', level: 2 },
28
+ { type: 'paragraph', html: 'Ein Tachymeter ist eine Skala auf der Lünette oder dem Zifferblatt einer Uhr, die die verstrichene Zeit in Geschwindigkeit umrechnet. Sie wird typischerweise mit einem Chronographen verwendet: Starten Sie den Timer, wenn das Objekt einen Referenzpunkt passiert, stoppen Sie ihn nach 1 km und lesen Sie die Geschwindigkeit von der Skala ab. Dieser Rechner erledigt die Mathematik für Sie.' },
29
+ { type: 'title', text: 'Wie ein Tachymeter funktioniert', level: 3 },
30
+ {
31
+ type: 'glossary', items: [
32
+ { term: 'Formel', definition: 'Geschwindigkeit = 3.600 / verstrichene Zeit in Sekunden. Die Konstante 3.600 steht für die Sekunden pro Stunde.' },
33
+ { term: 'Die Skala', definition: 'Tachymeterlünetten sind von 60 (auf der 3-Uhr-Position) bis zu 400 oder 500 (nahe 12 Uhr) markiert. Höhere Geschwindigkeiten entsprechen kürzeren Zeiten.' },
34
+ { term: 'Basisentfernung', definition: 'Standard-Tachymeter sind auf 1 km oder 1 Meile kalibriert. Für andere Entfernungen teilen Sie den Wert entsprechend.' },
35
+ ]
36
+ },
37
+ { type: 'title', text: 'Übliche Tachymetermessungen', level: 3 },
38
+ {
39
+ type: 'table', headers: ['Verstrichene Zeit', 'Geschwindigkeit', 'Szenario'], rows: [
40
+ ['10 s', '360 km/h', 'Hochgeschwindigkeitszug / Rennwagen'],
41
+ ['12 s', '300 km/h', 'Privatjet Start'],
42
+ ['20 s', '180 km/h', 'Sportwagen auf Autobahn'],
43
+ ['30 s', '120 km/h', 'Auto auf Autobahn'],
44
+ ['36 s', '100 km/h', 'Auto auf Landstraße (Standardreferenz)'],
45
+ ['45 s', '80 km/h', 'Auto innerorts'],
46
+ ['60 s', '60 km/h', 'Stadtverkehr'],
47
+ ['90 s', '40 km/h', 'Radfahren'],
48
+ ['120 s', '30 km/h', 'Joggen'],
49
+ ['240 s', '15 km/h', 'Laufen'],
50
+ ['480 s', '7.5 km/h', 'Gehen'],
51
+ ]
52
+ },
53
+ { type: 'title', text: 'Über die Standardskala hinaus', level: 3 },
54
+ { type: 'paragraph', html: 'Wenn Ihre Tachymeterlünette nur bis 400 reicht, Ihre gemessene Zeit aber unter 9 Sekunden liegt, können Sie sie dennoch nutzen. Verwenden Sie einfach eine größere Basisentfernung. Wenn Sie zum Beispiel 2 km in 18 Sekunden zurücklegen, halbieren Sie den Lünettenwert. Umgekehrt: Für langsame Geschwindigkeiten (über 60 Sekunden) verwenden Sie eine kürzere Basisentfernung und multiplizieren Sie entsprechend.' },
55
+ { type: 'diagnostic', variant: 'info', title: 'Tachymeter Kompatibilität', icon: 'mdi:information', badge: 'HINWEIS', html: 'Nicht alle Chronographen haben eine Tachymeterlünette. Bei einigen Uhren befindet sich die Skala auf dem inneren Ring oder dem äußeren Kapitelring. Digitale Uhren können eine Tachymeterfunktion im Chronographenmodus bieten. Die Formel funktioniert immer, unabhängig von der physischen Skala.' },
56
+ ],
57
+ faq: [
58
+ {
59
+ question: 'Wie benutze ich eine Tachymeterlünette an meiner Uhr?',
60
+ answer: 'Starten Sie den Chronographen an einem Referenzpunkt (z.B. einem Kilometerstein). Stoppen Sie ihn nach genau 1 km. Der Sekundenzeiger zeigt auf die Geschwindigkeit auf der Lünette. Bei 36 Sekunden zeigt er zum Beispiel auf 100 km/h.',
61
+ },
62
+ {
63
+ question: 'Was ist, wenn meine gemessene Zeit kürzer ist als die Skala anzeigen kann?',
64
+ answer: 'Einige Tachymeter gehen nur bis 400 oder 500. Wenn Sie 1 km in unter 9 Sekunden zurücklegen, verwenden Sie eine größere Basisentfernung. Legen Sie 2 km zurück und teilen Sie den Wert durch 2.',
65
+ },
66
+ {
67
+ question: 'Was ist, wenn meine gemessene Zeit länger ist als die Skala anzeigen kann?',
68
+ answer: 'Verwenden Sie eine kürzere Basisentfernung. Bei 90 Sekunden für 1 km messen Sie stattdessen 0,5 km. Lesen Sie den Wert ab und multiplizieren Sie mit 2.',
69
+ },
70
+ {
71
+ question: 'Kann ich einen Tachymeter auch für andere Dinge als Geschwindigkeit verwenden?',
72
+ answer: 'Ja. Der Tachymeter misst jede Rate pro Stunde. Messen Sie die Zeit für eine Aufgabe und die Skala zeigt, wie viele Sie pro Stunde erledigen können. Ein 30-Sekunden-Vorgang bedeutet 120 Einheiten pro Stunde.',
73
+ },
74
+ {
75
+ question: 'Verwenden alle Tachymeter die gleiche Skala?',
76
+ answer: 'Die meisten verwenden die logarithmische Standard-Skala auf Basis der Formel 3.600 / Sekunden. Einige Vintage-Uhren oder Marken können jedoch abweichen.',
77
+ },
78
+ ],
79
+ bibliography,
80
+ howTo: [
81
+ {
82
+ name: 'Szenario auswählen oder eigene Zeit eingeben',
83
+ text: 'Wählen Sie aus Voreinstellungen wie Auto, Radfahren oder Laufen, oder wählen Sie Benutzerdefiniert für eine beliebige Zeit in Sekunden.',
84
+ },
85
+ {
86
+ name: 'Geschwindigkeit ablesen',
87
+ text: 'Der Rechner zeigt die Geschwindigkeit in km/h und den entsprechenden Tachymeterwert.',
88
+ },
89
+ {
90
+ name: 'Auf Ihre Uhr anwenden',
91
+ text: 'Nutzen Sie die verstrichene Zeit, um die passende Geschwindigkeit auf Ihrer Tachymeterlünette zu finden.',
92
+ },
93
+ ],
94
+ schemas: [
95
+ {
96
+ '@context': 'https://schema.org',
97
+ '@type': 'FAQPage',
98
+ 'mainEntity': [
99
+ {
100
+ '@type': 'Question',
101
+ 'name': 'Wie benutze ich eine Tachymeterlünette an meiner Uhr?',
102
+ 'acceptedAnswer': {
103
+ '@type': 'Answer',
104
+ 'text': 'Starten Sie den Chronographen an einem Referenzpunkt. Stoppen Sie ihn nach genau 1 km. Der Sekundenzeiger zeigt auf die Geschwindigkeit auf der Lünette.',
105
+ },
106
+ },
107
+ {
108
+ '@type': 'Question',
109
+ 'name': 'Was ist, wenn meine gemessene Zeit kürzer ist als die Skala anzeigen kann?',
110
+ 'acceptedAnswer': {
111
+ '@type': 'Answer',
112
+ 'text': 'Verwenden Sie eine größere Basisentfernung. Legen Sie 2 km zurück und teilen Sie den Wert durch 2.',
113
+ },
114
+ },
115
+ {
116
+ '@type': 'Question',
117
+ 'name': 'Was ist, wenn meine gemessene Zeit länger ist als die Skala anzeigen kann?',
118
+ 'acceptedAnswer': {
119
+ '@type': 'Answer',
120
+ 'text': 'Verwenden Sie eine kürzere Basisentfernung. Messen Sie 0,5 km und multiplizieren Sie mit 2.',
121
+ },
122
+ },
123
+ {
124
+ '@type': 'Question',
125
+ 'name': 'Kann ich einen Tachymeter auch für andere Dinge nutzen?',
126
+ 'acceptedAnswer': {
127
+ '@type': 'Answer',
128
+ 'text': 'Ja. Der Tachymeter misst jede Rate pro Stunde. Messen Sie die Zeit für eine Aufgabe, die Skala zeigt die Anzahl pro Stunde.',
129
+ },
130
+ },
131
+ {
132
+ '@type': 'Question',
133
+ 'name': 'Verwenden alle Tachymeter die gleiche Skala?',
134
+ 'acceptedAnswer': {
135
+ '@type': 'Answer',
136
+ 'text': 'Die meisten verwenden die Standard-Formel 3.600 / Sekunden. Einige Vintage-Uhren können abweichen.',
137
+ },
138
+ },
139
+ ],
140
+ },
141
+ {
142
+ '@context': 'https://schema.org',
143
+ '@type': 'SoftwareApplication',
144
+ 'name': 'Tachymeter Rechner',
145
+ 'operatingSystem': 'All',
146
+ 'applicationCategory': 'UtilitiesApplication',
147
+ 'browserRequirements': 'Requires HTML5. Requires JavaScript.',
148
+ },
149
+ {
150
+ '@context': 'https://schema.org',
151
+ '@type': 'HowTo',
152
+ 'name': 'Wie berechne ich Geschwindigkeit mit einem Tachymeter',
153
+ 'step': [
154
+ {
155
+ '@type': 'HowToStep',
156
+ 'name': 'Szenario auswählen oder eigene Zeit eingeben',
157
+ 'text': 'Wählen Sie aus Voreinstellungen wie Auto, Radfahren oder Laufen, oder wählen Sie Benutzerdefiniert für eine beliebige Zeit in Sekunden.',
158
+ },
159
+ {
160
+ '@type': 'HowToStep',
161
+ 'name': 'Geschwindigkeit ablesen',
162
+ 'text': 'Der Rechner zeigt die Geschwindigkeit in km/h und den entsprechenden Tachymeterwert.',
163
+ },
164
+ {
165
+ '@type': 'HowToStep',
166
+ 'name': 'Auf Ihre Uhr anwenden',
167
+ 'text': 'Nutzen Sie die verstrichene Zeit, um die passende Geschwindigkeit auf Ihrer Tachymeterlünette zu finden.',
168
+ },
169
+ ],
170
+ },
171
+ ],
172
+ };