@jjlmoya/utils-chrono 1.2.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 (277) hide show
  1. package/package.json +65 -0
  2. package/scripts/postinstall.mjs +27 -0
  3. package/src/category/ChronoCategorySEO.astro +8 -0
  4. package/src/category/i18n/de.ts +23 -0
  5. package/src/category/i18n/en.ts +23 -0
  6. package/src/category/i18n/es.ts +23 -0
  7. package/src/category/i18n/fr.ts +23 -0
  8. package/src/category/i18n/id.ts +23 -0
  9. package/src/category/i18n/it.ts +23 -0
  10. package/src/category/i18n/ja.ts +23 -0
  11. package/src/category/i18n/ko.ts +23 -0
  12. package/src/category/i18n/nl.ts +23 -0
  13. package/src/category/i18n/pl.ts +23 -0
  14. package/src/category/i18n/pt.ts +23 -0
  15. package/src/category/i18n/ru.ts +23 -0
  16. package/src/category/i18n/sv.ts +23 -0
  17. package/src/category/i18n/tr.ts +23 -0
  18. package/src/category/i18n/zh.ts +23 -0
  19. package/src/category/index.ts +42 -0
  20. package/src/components/PreviewNavSidebar.astro +116 -0
  21. package/src/components/PreviewToolbar.astro +143 -0
  22. package/src/data.ts +11 -0
  23. package/src/entries.ts +32 -0
  24. package/src/env.d.ts +5 -0
  25. package/src/index.ts +28 -0
  26. package/src/layouts/PreviewLayout.astro +117 -0
  27. package/src/pages/[locale]/[slug].astro +161 -0
  28. package/src/pages/[locale].astro +251 -0
  29. package/src/pages/index.astro +4 -0
  30. package/src/tests/faq_count.test.ts +19 -0
  31. package/src/tests/i18n_coverage.test.ts +36 -0
  32. package/src/tests/locale_completeness.test.ts +29 -0
  33. package/src/tests/mocks/astro_mock.js +2 -0
  34. package/src/tests/no_h1_in_components.test.ts +48 -0
  35. package/src/tests/schemas_fulfillment.test.ts +23 -0
  36. package/src/tests/seo_length.test.ts +22 -0
  37. package/src/tests/shared-test-helpers.ts +56 -0
  38. package/src/tests/slug_language_code_format.test.ts +23 -0
  39. package/src/tests/slug_uniqueness.test.ts +81 -0
  40. package/src/tests/title_quality.test.ts +55 -0
  41. package/src/tests/tool_exports.test.ts +34 -0
  42. package/src/tests/tool_validation.test.ts +18 -0
  43. package/src/tool/beat-rate-converter/beat-rate-converter.css +301 -0
  44. package/src/tool/beat-rate-converter/bibliography.astro +16 -0
  45. package/src/tool/beat-rate-converter/bibliography.ts +12 -0
  46. package/src/tool/beat-rate-converter/client.ts +46 -0
  47. package/src/tool/beat-rate-converter/component.astro +15 -0
  48. package/src/tool/beat-rate-converter/components/ConverterPanel.astro +87 -0
  49. package/src/tool/beat-rate-converter/entry.ts +42 -0
  50. package/src/tool/beat-rate-converter/i18n/de.ts +138 -0
  51. package/src/tool/beat-rate-converter/i18n/en.ts +138 -0
  52. package/src/tool/beat-rate-converter/i18n/es.ts +138 -0
  53. package/src/tool/beat-rate-converter/i18n/fr.ts +138 -0
  54. package/src/tool/beat-rate-converter/i18n/id.ts +138 -0
  55. package/src/tool/beat-rate-converter/i18n/it.ts +138 -0
  56. package/src/tool/beat-rate-converter/i18n/ja.ts +138 -0
  57. package/src/tool/beat-rate-converter/i18n/ko.ts +138 -0
  58. package/src/tool/beat-rate-converter/i18n/nl.ts +138 -0
  59. package/src/tool/beat-rate-converter/i18n/pl.ts +138 -0
  60. package/src/tool/beat-rate-converter/i18n/pt.ts +138 -0
  61. package/src/tool/beat-rate-converter/i18n/ru.ts +138 -0
  62. package/src/tool/beat-rate-converter/i18n/sv.ts +138 -0
  63. package/src/tool/beat-rate-converter/i18n/tr.ts +138 -0
  64. package/src/tool/beat-rate-converter/i18n/zh.ts +138 -0
  65. package/src/tool/beat-rate-converter/index.ts +11 -0
  66. package/src/tool/beat-rate-converter/seo.astro +16 -0
  67. package/src/tool/crown-reference-guide/bibliography.astro +16 -0
  68. package/src/tool/crown-reference-guide/bibliography.ts +20 -0
  69. package/src/tool/crown-reference-guide/client.ts +193 -0
  70. package/src/tool/crown-reference-guide/component.astro +69 -0
  71. package/src/tool/crown-reference-guide/components/CrownView.astro +40 -0
  72. package/src/tool/crown-reference-guide/components/MovementSelector.astro +26 -0
  73. package/src/tool/crown-reference-guide/components/PositionPanel.astro +59 -0
  74. package/src/tool/crown-reference-guide/crown-reference-guide.css +383 -0
  75. package/src/tool/crown-reference-guide/entry.ts +60 -0
  76. package/src/tool/crown-reference-guide/i18n/de.ts +207 -0
  77. package/src/tool/crown-reference-guide/i18n/en.ts +207 -0
  78. package/src/tool/crown-reference-guide/i18n/es.ts +207 -0
  79. package/src/tool/crown-reference-guide/i18n/fr.ts +207 -0
  80. package/src/tool/crown-reference-guide/i18n/id.ts +207 -0
  81. package/src/tool/crown-reference-guide/i18n/it.ts +207 -0
  82. package/src/tool/crown-reference-guide/i18n/ja.ts +207 -0
  83. package/src/tool/crown-reference-guide/i18n/ko.ts +207 -0
  84. package/src/tool/crown-reference-guide/i18n/nl.ts +207 -0
  85. package/src/tool/crown-reference-guide/i18n/pl.ts +207 -0
  86. package/src/tool/crown-reference-guide/i18n/pt.ts +207 -0
  87. package/src/tool/crown-reference-guide/i18n/ru.ts +207 -0
  88. package/src/tool/crown-reference-guide/i18n/sv.ts +207 -0
  89. package/src/tool/crown-reference-guide/i18n/tr.ts +207 -0
  90. package/src/tool/crown-reference-guide/i18n/zh.ts +207 -0
  91. package/src/tool/crown-reference-guide/index.ts +11 -0
  92. package/src/tool/crown-reference-guide/seo.astro +16 -0
  93. package/src/tool/crown-reference-guide/utils.ts +0 -0
  94. package/src/tool/demagnetizing-timer/bibliography.astro +16 -0
  95. package/src/tool/demagnetizing-timer/bibliography.ts +12 -0
  96. package/src/tool/demagnetizing-timer/client.ts +221 -0
  97. package/src/tool/demagnetizing-timer/component.astro +15 -0
  98. package/src/tool/demagnetizing-timer/components/TimerPanel.astro +92 -0
  99. package/src/tool/demagnetizing-timer/demagnetizing-timer.css +389 -0
  100. package/src/tool/demagnetizing-timer/entry.ts +50 -0
  101. package/src/tool/demagnetizing-timer/helpers/field.ts +134 -0
  102. package/src/tool/demagnetizing-timer/i18n/de.ts +143 -0
  103. package/src/tool/demagnetizing-timer/i18n/en.ts +143 -0
  104. package/src/tool/demagnetizing-timer/i18n/es.ts +143 -0
  105. package/src/tool/demagnetizing-timer/i18n/fr.ts +143 -0
  106. package/src/tool/demagnetizing-timer/i18n/id.ts +143 -0
  107. package/src/tool/demagnetizing-timer/i18n/it.ts +143 -0
  108. package/src/tool/demagnetizing-timer/i18n/ja.ts +143 -0
  109. package/src/tool/demagnetizing-timer/i18n/ko.ts +143 -0
  110. package/src/tool/demagnetizing-timer/i18n/nl.ts +143 -0
  111. package/src/tool/demagnetizing-timer/i18n/pl.ts +143 -0
  112. package/src/tool/demagnetizing-timer/i18n/pt.ts +143 -0
  113. package/src/tool/demagnetizing-timer/i18n/ru.ts +143 -0
  114. package/src/tool/demagnetizing-timer/i18n/sv.ts +143 -0
  115. package/src/tool/demagnetizing-timer/i18n/tr.ts +143 -0
  116. package/src/tool/demagnetizing-timer/i18n/zh.ts +143 -0
  117. package/src/tool/demagnetizing-timer/index.ts +11 -0
  118. package/src/tool/demagnetizing-timer/seo.astro +16 -0
  119. package/src/tool/demagnetizing-timer/utils.ts +0 -0
  120. package/src/tool/power-reserve-estimator/bibliography.astro +16 -0
  121. package/src/tool/power-reserve-estimator/bibliography.ts +16 -0
  122. package/src/tool/power-reserve-estimator/client.ts +139 -0
  123. package/src/tool/power-reserve-estimator/component.astro +15 -0
  124. package/src/tool/power-reserve-estimator/components/EstimatorPanel.astro +150 -0
  125. package/src/tool/power-reserve-estimator/entry.ts +51 -0
  126. package/src/tool/power-reserve-estimator/i18n/de.ts +158 -0
  127. package/src/tool/power-reserve-estimator/i18n/en.ts +158 -0
  128. package/src/tool/power-reserve-estimator/i18n/es.ts +158 -0
  129. package/src/tool/power-reserve-estimator/i18n/fr.ts +158 -0
  130. package/src/tool/power-reserve-estimator/i18n/id.ts +158 -0
  131. package/src/tool/power-reserve-estimator/i18n/it.ts +158 -0
  132. package/src/tool/power-reserve-estimator/i18n/ja.ts +158 -0
  133. package/src/tool/power-reserve-estimator/i18n/ko.ts +158 -0
  134. package/src/tool/power-reserve-estimator/i18n/nl.ts +158 -0
  135. package/src/tool/power-reserve-estimator/i18n/pl.ts +158 -0
  136. package/src/tool/power-reserve-estimator/i18n/pt.ts +158 -0
  137. package/src/tool/power-reserve-estimator/i18n/ru.ts +158 -0
  138. package/src/tool/power-reserve-estimator/i18n/sv.ts +158 -0
  139. package/src/tool/power-reserve-estimator/i18n/tr.ts +158 -0
  140. package/src/tool/power-reserve-estimator/i18n/zh.ts +158 -0
  141. package/src/tool/power-reserve-estimator/index.ts +11 -0
  142. package/src/tool/power-reserve-estimator/power-reserve-estimator.css +402 -0
  143. package/src/tool/power-reserve-estimator/seo.astro +16 -0
  144. package/src/tool/strap-taper-calculator/bibliography.astro +16 -0
  145. package/src/tool/strap-taper-calculator/bibliography.ts +12 -0
  146. package/src/tool/strap-taper-calculator/client.ts +129 -0
  147. package/src/tool/strap-taper-calculator/component.astro +15 -0
  148. package/src/tool/strap-taper-calculator/components/CalculatorPanel.astro +114 -0
  149. package/src/tool/strap-taper-calculator/entry.ts +56 -0
  150. package/src/tool/strap-taper-calculator/i18n/de.ts +152 -0
  151. package/src/tool/strap-taper-calculator/i18n/en.ts +152 -0
  152. package/src/tool/strap-taper-calculator/i18n/es.ts +152 -0
  153. package/src/tool/strap-taper-calculator/i18n/fr.ts +152 -0
  154. package/src/tool/strap-taper-calculator/i18n/id.ts +152 -0
  155. package/src/tool/strap-taper-calculator/i18n/it.ts +152 -0
  156. package/src/tool/strap-taper-calculator/i18n/ja.ts +152 -0
  157. package/src/tool/strap-taper-calculator/i18n/ko.ts +152 -0
  158. package/src/tool/strap-taper-calculator/i18n/nl.ts +152 -0
  159. package/src/tool/strap-taper-calculator/i18n/pl.ts +152 -0
  160. package/src/tool/strap-taper-calculator/i18n/pt.ts +152 -0
  161. package/src/tool/strap-taper-calculator/i18n/ru.ts +152 -0
  162. package/src/tool/strap-taper-calculator/i18n/sv.ts +152 -0
  163. package/src/tool/strap-taper-calculator/i18n/tr.ts +152 -0
  164. package/src/tool/strap-taper-calculator/i18n/zh.ts +152 -0
  165. package/src/tool/strap-taper-calculator/index.ts +11 -0
  166. package/src/tool/strap-taper-calculator/seo.astro +16 -0
  167. package/src/tool/strap-taper-calculator/strap-taper-calculator.css +320 -0
  168. package/src/tool/watch-accuracy-tracker/bibliography.astro +16 -0
  169. package/src/tool/watch-accuracy-tracker/bibliography.ts +12 -0
  170. package/src/tool/watch-accuracy-tracker/chart.ts +126 -0
  171. package/src/tool/watch-accuracy-tracker/client.ts +287 -0
  172. package/src/tool/watch-accuracy-tracker/component.astro +35 -0
  173. package/src/tool/watch-accuracy-tracker/components/AccuracyTracker.astro +96 -0
  174. package/src/tool/watch-accuracy-tracker/components/DriftPredictor.astro +126 -0
  175. package/src/tool/watch-accuracy-tracker/components/LogHistory.astro +66 -0
  176. package/src/tool/watch-accuracy-tracker/entry.ts +94 -0
  177. package/src/tool/watch-accuracy-tracker/i18n/de.ts +167 -0
  178. package/src/tool/watch-accuracy-tracker/i18n/en.ts +167 -0
  179. package/src/tool/watch-accuracy-tracker/i18n/es.ts +167 -0
  180. package/src/tool/watch-accuracy-tracker/i18n/fr.ts +167 -0
  181. package/src/tool/watch-accuracy-tracker/i18n/id.ts +167 -0
  182. package/src/tool/watch-accuracy-tracker/i18n/it.ts +167 -0
  183. package/src/tool/watch-accuracy-tracker/i18n/ja.ts +167 -0
  184. package/src/tool/watch-accuracy-tracker/i18n/ko.ts +167 -0
  185. package/src/tool/watch-accuracy-tracker/i18n/nl.ts +167 -0
  186. package/src/tool/watch-accuracy-tracker/i18n/pl.ts +167 -0
  187. package/src/tool/watch-accuracy-tracker/i18n/pt.ts +167 -0
  188. package/src/tool/watch-accuracy-tracker/i18n/ru.ts +167 -0
  189. package/src/tool/watch-accuracy-tracker/i18n/sv.ts +167 -0
  190. package/src/tool/watch-accuracy-tracker/i18n/tr.ts +167 -0
  191. package/src/tool/watch-accuracy-tracker/i18n/zh.ts +167 -0
  192. package/src/tool/watch-accuracy-tracker/index.ts +9 -0
  193. package/src/tool/watch-accuracy-tracker/logger.ts +105 -0
  194. package/src/tool/watch-accuracy-tracker/seo.astro +16 -0
  195. package/src/tool/watch-accuracy-tracker/utils.ts +99 -0
  196. package/src/tool/watch-accuracy-tracker/watch-accuracy-tracker.css +564 -0
  197. package/src/tool/watch-savings-planner/bibliography.astro +16 -0
  198. package/src/tool/watch-savings-planner/bibliography.ts +8 -0
  199. package/src/tool/watch-savings-planner/client.ts +271 -0
  200. package/src/tool/watch-savings-planner/component.astro +33 -0
  201. package/src/tool/watch-savings-planner/components/AddGoalForm.astro +49 -0
  202. package/src/tool/watch-savings-planner/components/GoalCard.astro +58 -0
  203. package/src/tool/watch-savings-planner/entry.ts +62 -0
  204. package/src/tool/watch-savings-planner/i18n/de.ts +153 -0
  205. package/src/tool/watch-savings-planner/i18n/en.ts +155 -0
  206. package/src/tool/watch-savings-planner/i18n/es.ts +153 -0
  207. package/src/tool/watch-savings-planner/i18n/fr.ts +153 -0
  208. package/src/tool/watch-savings-planner/i18n/id.ts +153 -0
  209. package/src/tool/watch-savings-planner/i18n/it.ts +153 -0
  210. package/src/tool/watch-savings-planner/i18n/ja.ts +153 -0
  211. package/src/tool/watch-savings-planner/i18n/ko.ts +153 -0
  212. package/src/tool/watch-savings-planner/i18n/nl.ts +153 -0
  213. package/src/tool/watch-savings-planner/i18n/pl.ts +153 -0
  214. package/src/tool/watch-savings-planner/i18n/pt.ts +153 -0
  215. package/src/tool/watch-savings-planner/i18n/ru.ts +153 -0
  216. package/src/tool/watch-savings-planner/i18n/sv.ts +153 -0
  217. package/src/tool/watch-savings-planner/i18n/tr.ts +153 -0
  218. package/src/tool/watch-savings-planner/i18n/zh.ts +153 -0
  219. package/src/tool/watch-savings-planner/index.ts +11 -0
  220. package/src/tool/watch-savings-planner/seo.astro +16 -0
  221. package/src/tool/watch-savings-planner/utils.ts +0 -0
  222. package/src/tool/watch-savings-planner/watch-savings-planner.css +460 -0
  223. package/src/tool/water-resistance-converter/bibliography.astro +16 -0
  224. package/src/tool/water-resistance-converter/bibliography.ts +16 -0
  225. package/src/tool/water-resistance-converter/client.ts +56 -0
  226. package/src/tool/water-resistance-converter/component.astro +15 -0
  227. package/src/tool/water-resistance-converter/components/ConverterPanel.astro +113 -0
  228. package/src/tool/water-resistance-converter/entry.ts +52 -0
  229. package/src/tool/water-resistance-converter/i18n/de.ts +148 -0
  230. package/src/tool/water-resistance-converter/i18n/en.ts +148 -0
  231. package/src/tool/water-resistance-converter/i18n/es.ts +148 -0
  232. package/src/tool/water-resistance-converter/i18n/fr.ts +148 -0
  233. package/src/tool/water-resistance-converter/i18n/id.ts +148 -0
  234. package/src/tool/water-resistance-converter/i18n/it.ts +148 -0
  235. package/src/tool/water-resistance-converter/i18n/ja.ts +148 -0
  236. package/src/tool/water-resistance-converter/i18n/ko.ts +148 -0
  237. package/src/tool/water-resistance-converter/i18n/nl.ts +148 -0
  238. package/src/tool/water-resistance-converter/i18n/pl.ts +148 -0
  239. package/src/tool/water-resistance-converter/i18n/pt.ts +148 -0
  240. package/src/tool/water-resistance-converter/i18n/ru.ts +148 -0
  241. package/src/tool/water-resistance-converter/i18n/sv.ts +148 -0
  242. package/src/tool/water-resistance-converter/i18n/tr.ts +148 -0
  243. package/src/tool/water-resistance-converter/i18n/zh.ts +148 -0
  244. package/src/tool/water-resistance-converter/index.ts +11 -0
  245. package/src/tool/water-resistance-converter/seo.astro +16 -0
  246. package/src/tool/water-resistance-converter/water-resistance-converter.css +254 -0
  247. package/src/tool/wrist-presence-calculator/bibliography.astro +16 -0
  248. package/src/tool/wrist-presence-calculator/bibliography.ts +12 -0
  249. package/src/tool/wrist-presence-calculator/client.ts +180 -0
  250. package/src/tool/wrist-presence-calculator/component.astro +23 -0
  251. package/src/tool/wrist-presence-calculator/components/CalculatorInputs.astro +97 -0
  252. package/src/tool/wrist-presence-calculator/components/FitResult.astro +68 -0
  253. package/src/tool/wrist-presence-calculator/components/Visualizer.astro +16 -0
  254. package/src/tool/wrist-presence-calculator/entry.ts +59 -0
  255. package/src/tool/wrist-presence-calculator/helpers/canvas.ts +189 -0
  256. package/src/tool/wrist-presence-calculator/helpers/results.ts +78 -0
  257. package/src/tool/wrist-presence-calculator/i18n/de.ts +139 -0
  258. package/src/tool/wrist-presence-calculator/i18n/en.ts +155 -0
  259. package/src/tool/wrist-presence-calculator/i18n/es.ts +139 -0
  260. package/src/tool/wrist-presence-calculator/i18n/fr.ts +139 -0
  261. package/src/tool/wrist-presence-calculator/i18n/id.ts +139 -0
  262. package/src/tool/wrist-presence-calculator/i18n/it.ts +139 -0
  263. package/src/tool/wrist-presence-calculator/i18n/ja.ts +139 -0
  264. package/src/tool/wrist-presence-calculator/i18n/ko.ts +139 -0
  265. package/src/tool/wrist-presence-calculator/i18n/nl.ts +139 -0
  266. package/src/tool/wrist-presence-calculator/i18n/pl.ts +139 -0
  267. package/src/tool/wrist-presence-calculator/i18n/pt.ts +139 -0
  268. package/src/tool/wrist-presence-calculator/i18n/ru.ts +139 -0
  269. package/src/tool/wrist-presence-calculator/i18n/sv.ts +139 -0
  270. package/src/tool/wrist-presence-calculator/i18n/tr.ts +139 -0
  271. package/src/tool/wrist-presence-calculator/i18n/zh.ts +155 -0
  272. package/src/tool/wrist-presence-calculator/index.ts +11 -0
  273. package/src/tool/wrist-presence-calculator/seo.astro +16 -0
  274. package/src/tool/wrist-presence-calculator/utils.ts +30 -0
  275. package/src/tool/wrist-presence-calculator/wrist-presence-calculator.css +372 -0
  276. package/src/tools.ts +26 -0
  277. package/src/types.ts +70 -0
@@ -0,0 +1,287 @@
1
+ import {
2
+ formatDrift,
3
+ getTernaryDriftClass,
4
+ calculateAverageRate,
5
+ getExplanationForRate
6
+ } from './utils';
7
+
8
+ import {
9
+ getLogs,
10
+ saveLogs,
11
+ renderLogsTable,
12
+ updateOverallStats
13
+ } from './logger';
14
+ import type { LogEntry } from './logger';
15
+
16
+ import { drawTrendChart } from './chart';
17
+
18
+ interface Watch {
19
+ id: string;
20
+ name: string;
21
+ }
22
+
23
+ const STORAGE_WATCHES = 'jjlmoya_chrono_watches';
24
+ const STORAGE_LOGS = 'jjlmoya_chrono_logs_';
25
+
26
+ const mainEl = document.querySelector('.tool-main-card') as HTMLElement;
27
+ const ui = mainEl ? JSON.parse(mainEl.dataset.ui || '{}') : {};
28
+ const statusPass = ui.statusPass || 'Pass';
29
+ const statusFail = ui.statusFail || 'Fail';
30
+
31
+ const tabButtons = document.querySelectorAll('.tab-btn');
32
+ const tabContents = document.querySelectorAll('.tab-content');
33
+
34
+ const driftRateInput = document.getElementById('drift-rate-input') as HTMLInputElement;
35
+ const presetBtns = document.querySelectorAll('.preset-btn');
36
+ const projectionTbody = document.getElementById('projection-tbody') as HTMLElement;
37
+ const standardCards = document.querySelectorAll('.standard-card');
38
+
39
+ const watchSelector = document.getElementById('watch-selector') as HTMLSelectElement;
40
+ const newWatchNameInput = document.getElementById('new-watch-name') as HTMLInputElement;
41
+ const addWatchBtn = document.getElementById('add-watch-btn') as HTMLElement;
42
+ const deleteWatchBtn = document.getElementById('delete-watch-btn') as HTMLElement;
43
+
44
+ const logFormSection = document.getElementById('log-form-section') as HTMLElement;
45
+ const offsetInput = document.getElementById('offset-input') as HTMLInputElement;
46
+ const positionSelector = document.getElementById('position-selector') as HTMLSelectElement;
47
+ const measurementDateInput = document.getElementById('measurement-date') as HTMLInputElement;
48
+ const useCurrentTimeBtn = document.getElementById('use-current-time-btn') as HTMLElement;
49
+ const saveLogBtn = document.getElementById('save-log-btn') as HTMLElement;
50
+
51
+ const clearLogsBtn = document.getElementById('clear-logs-btn') as HTMLElement;
52
+ const avgRateVal = document.getElementById('avg-rate-val') as HTMLElement;
53
+ const precisionStatusVal = document.getElementById('precision-status-val') as HTMLElement;
54
+ const totalLogsVal = document.getElementById('total-logs-val') as HTMLElement;
55
+ const logsTbody = document.getElementById('logs-tbody') as HTMLElement;
56
+ const noLogsMsg = document.getElementById('no-logs-msg') as HTMLElement;
57
+
58
+ const exportCsvBtn = document.getElementById('export-csv-btn') as HTMLElement;
59
+ const avgRateExplanationBox = document.getElementById('avg-rate-explanation-box') as HTMLElement;
60
+ const chartWrapper = document.getElementById('chart-wrapper') as HTMLElement;
61
+ const driftTrendChart = document.getElementById('drift-trend-chart') as HTMLCanvasElement;
62
+
63
+ let watches: Watch[] = [];
64
+ let currentWatchId = '';
65
+
66
+ tabButtons.forEach(btn => {
67
+ btn.addEventListener('click', () => {
68
+ tabButtons.forEach(b => b.classList.remove('active'));
69
+ tabContents.forEach(c => c.classList.remove('active'));
70
+ btn.classList.add('active');
71
+ const tabId = btn.getAttribute('data-tab');
72
+ if (tabId) {
73
+ const el = document.getElementById(tabId);
74
+ if (el) el.classList.add('active');
75
+ }
76
+ });
77
+ });
78
+
79
+ function updateDriftProjections(rate: number) {
80
+ if (!projectionTbody) return;
81
+ projectionTbody.querySelectorAll('tr').forEach(row => {
82
+ const days = parseFloat(row.getAttribute('data-days') || '0');
83
+ const valCell = row.querySelector('.drift-val');
84
+ if (valCell) {
85
+ valCell.textContent = formatDrift(rate * days);
86
+ valCell.className = getTernaryDriftClass(rate);
87
+ }
88
+ });
89
+
90
+ standardCards.forEach(card => {
91
+ const min = parseFloat(card.getAttribute('data-min') || '0');
92
+ const max = parseFloat(card.getAttribute('data-max') || '0');
93
+ const statusSpan = card.querySelector('.std-status');
94
+ const isPassed = rate >= min && rate <= max;
95
+ card.className = `standard-card ${isPassed ? 'passed' : 'failed'}`;
96
+ if (statusSpan) {
97
+ statusSpan.textContent = isPassed ? statusPass : statusFail;
98
+ }
99
+ });
100
+ }
101
+
102
+ driftRateInput.addEventListener('input', (e) => {
103
+ const target = e.target as HTMLInputElement;
104
+ updateDriftProjections(parseFloat(target.value) || 0);
105
+ });
106
+
107
+ presetBtns.forEach(btn => {
108
+ btn.addEventListener('click', () => {
109
+ const val = btn.getAttribute('data-value');
110
+ if (val) {
111
+ driftRateInput.value = val;
112
+ updateDriftProjections(parseFloat(val));
113
+ }
114
+ });
115
+ });
116
+
117
+ function deleteLog(logId: string) {
118
+ if (!currentWatchId) return;
119
+ const logs = getLogs(currentWatchId).filter(l => l.id !== logId);
120
+ saveLogs(currentWatchId, logs);
121
+ updateLogsDisplay();
122
+ }
123
+
124
+ function updateStatsAndChart(logs: LogEntry[]) {
125
+ if (logs.length >= 2) {
126
+ chartWrapper.style.display = 'block';
127
+ drawTrendChart(driftTrendChart, logs);
128
+ const first = logs[0];
129
+ const last = logs[logs.length - 1];
130
+ const avgRate = calculateAverageRate(first.offset, last.offset, first.date, last.date);
131
+ if (avgRate !== null) {
132
+ avgRateExplanationBox.textContent = getExplanationForRate(avgRate);
133
+ avgRateExplanationBox.style.display = 'block';
134
+ } else {
135
+ avgRateExplanationBox.style.display = 'none';
136
+ }
137
+ } else {
138
+ chartWrapper.style.display = 'none';
139
+ avgRateExplanationBox.style.display = 'none';
140
+ }
141
+ }
142
+
143
+ function updateLogsDisplay() {
144
+ if (!currentWatchId) return;
145
+ const logs = getLogs(currentWatchId).sort(
146
+ (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
147
+ );
148
+ totalLogsVal.textContent = logs.length.toString();
149
+
150
+ if (logs.length === 0) {
151
+ noLogsMsg.style.display = 'block';
152
+ logsTbody.innerHTML = '';
153
+ updateOverallStats([], avgRateVal, precisionStatusVal, ui);
154
+ updateStatsAndChart([]);
155
+ return;
156
+ }
157
+
158
+ noLogsMsg.style.display = 'none';
159
+ renderLogsTable(logs, logsTbody, deleteLog);
160
+ updateOverallStats(logs, avgRateVal, precisionStatusVal, ui);
161
+ updateStatsAndChart(logs);
162
+ }
163
+
164
+ function resetFields() {
165
+ avgRateVal.textContent = '--';
166
+ precisionStatusVal.textContent = '--';
167
+ precisionStatusVal.className = 'stat-value';
168
+ totalLogsVal.textContent = '0';
169
+ logsTbody.innerHTML = '';
170
+ }
171
+
172
+ function clearUIStats() {
173
+ logFormSection.classList.add('disabled-form');
174
+ resetFields();
175
+ noLogsMsg.style.display = 'block';
176
+ avgRateExplanationBox.style.display = 'none';
177
+ chartWrapper.style.display = 'none';
178
+ }
179
+
180
+ function selectWatch(id: string) {
181
+ currentWatchId = id;
182
+ watchSelector.value = id;
183
+ if (id) {
184
+ logFormSection.classList.remove('disabled-form');
185
+ updateLogsDisplay();
186
+ } else {
187
+ clearUIStats();
188
+ }
189
+ }
190
+
191
+ function updateWatchSelector() {
192
+ const prevVal = watchSelector.value;
193
+ watchSelector.innerHTML = `<option value="">-- ${ui.selectWatch || "Select Watch"} --</option>`;
194
+ watches.forEach(w => {
195
+ const opt = document.createElement('option');
196
+ opt.value = w.id;
197
+ opt.textContent = w.name;
198
+ watchSelector.appendChild(opt);
199
+ });
200
+ if (watches.some(w => w.id === prevVal)) {
201
+ watchSelector.value = prevVal;
202
+ } else {
203
+ watchSelector.value = '';
204
+ selectWatch('');
205
+ }
206
+ }
207
+
208
+ function saveWatches() {
209
+ localStorage.setItem(STORAGE_WATCHES, JSON.stringify(watches));
210
+ }
211
+
212
+ function loadWatches() {
213
+ const data = localStorage.getItem(STORAGE_WATCHES);
214
+ watches = data ? JSON.parse(data) : [];
215
+ updateWatchSelector();
216
+ }
217
+
218
+ watchSelector.addEventListener('change', (e) => {
219
+ const target = e.target as HTMLSelectElement;
220
+ selectWatch(target.value);
221
+ });
222
+
223
+ addWatchBtn.addEventListener('click', () => {
224
+ const name = newWatchNameInput.value.trim();
225
+ if (!name) return;
226
+ const id = 'watch_' + Date.now();
227
+ watches.push({ id, name });
228
+ saveWatches();
229
+ updateWatchSelector();
230
+ selectWatch(id);
231
+ newWatchNameInput.value = '';
232
+ });
233
+
234
+ deleteWatchBtn.addEventListener('click', () => {
235
+ if (!currentWatchId) return;
236
+ watches = watches.filter(w => w.id !== currentWatchId);
237
+ saveWatches();
238
+ localStorage.removeItem(STORAGE_LOGS + currentWatchId);
239
+ loadWatches();
240
+ });
241
+
242
+ function setTimeInputToNow() {
243
+ const now = new Date();
244
+ const tzOffset = now.getTimezoneOffset() * 60000;
245
+ const localISOTime = (new Date(now.getTime() - tzOffset)).toISOString().slice(0, 16);
246
+ measurementDateInput.value = localISOTime;
247
+ }
248
+
249
+ useCurrentTimeBtn.addEventListener('click', setTimeInputToNow);
250
+
251
+ function onSaveLog() {
252
+ if (!currentWatchId) return;
253
+ const offset = parseFloat(offsetInput.value);
254
+ const dateVal = measurementDateInput.value;
255
+ const posVal = positionSelector.value;
256
+ if (isNaN(offset) || !dateVal) return;
257
+
258
+ const logs = getLogs(currentWatchId);
259
+ logs.push({
260
+ id: 'log_' + Date.now(),
261
+ offset,
262
+ date: dateVal,
263
+ position: posVal || undefined
264
+ });
265
+ saveLogs(currentWatchId, logs);
266
+ updateLogsDisplay();
267
+
268
+ offsetInput.value = '';
269
+ positionSelector.value = '';
270
+ setTimeInputToNow();
271
+ }
272
+
273
+ saveLogBtn.addEventListener('click', onSaveLog);
274
+
275
+ clearLogsBtn.addEventListener('click', () => {
276
+ if (!currentWatchId) return;
277
+ saveLogs(currentWatchId, []);
278
+ updateLogsDisplay();
279
+ });
280
+
281
+ exportCsvBtn.addEventListener('click', () => {
282
+ if (currentWatchId) exportToCsv(currentWatchId);
283
+ });
284
+
285
+ setTimeInputToNow();
286
+ loadWatches();
287
+ updateDriftProjections(0);
@@ -0,0 +1,35 @@
1
+ ---
2
+ import DriftPredictor from './components/DriftPredictor.astro';
3
+ import AccuracyTracker from './components/AccuracyTracker.astro';
4
+ import LogHistory from './components/LogHistory.astro';
5
+
6
+ interface Props {
7
+ ui: Record<string, string>;
8
+ }
9
+
10
+ const { ui } = Astro.props;
11
+ ---
12
+
13
+ <div class="tool-main-card" data-ui={JSON.stringify(ui)}>
14
+ <div class="tabs-header">
15
+ <button type="button" class="tab-btn active" data-tab="drift-tab">
16
+ {ui.driftCalculatorTab || "Drift Predictor"}
17
+ </button>
18
+ <button type="button" class="tab-btn" data-tab="tracker-tab">
19
+ {ui.trackerTab || "Rate Logger"}
20
+ </button>
21
+ </div>
22
+
23
+ <div class="tab-content active" id="drift-tab">
24
+ <DriftPredictor labels={ui} />
25
+ </div>
26
+
27
+ <div class="tab-content" id="tracker-tab">
28
+ <div class="tracker-layout">
29
+ <AccuracyTracker labels={ui} />
30
+ <LogHistory labels={ui} />
31
+ </div>
32
+ </div>
33
+ </div>
34
+
35
+ <script src="./client.ts"></script>
@@ -0,0 +1,96 @@
1
+ ---
2
+ import { Icon } from 'astro-icon/components';
3
+
4
+ interface Props {
5
+ labels: Record<string, string>;
6
+ }
7
+
8
+ const { labels } = Astro.props;
9
+ ---
10
+
11
+ <div class="accuracy-tracker-section">
12
+ <div class="watch-selection-panel">
13
+ <div class="select-group">
14
+ <label class="control-label" for="watch-selector">
15
+ {labels.selectWatch || "Select Watch"}
16
+ </label>
17
+ <div class="watch-row-controls">
18
+ <select id="watch-selector" class="form-control">
19
+ <option value="">-- {labels.selectWatch || "Select Watch"} --</option>
20
+ </select>
21
+ <button type="button" id="delete-watch-btn" class="btn btn-danger" title={labels.deleteWatch || "Delete Watch"}>
22
+ <Icon name="mdi:trash-can-outline" width="18" height="18" />
23
+ </button>
24
+ </div>
25
+ </div>
26
+
27
+ <div class="add-watch-group">
28
+ <div class="input-with-button">
29
+ <input
30
+ type="text"
31
+ id="new-watch-name"
32
+ class="form-control"
33
+ placeholder={labels.watchPlaceholder || "e.g. Seiko SKX007"}
34
+ />
35
+ <button type="button" id="add-watch-btn" class="btn btn-primary">
36
+ <Icon name="mdi:plus" width="18" height="18" />
37
+ {labels.addWatch || "Add"}
38
+ </button>
39
+ </div>
40
+ </div>
41
+ </div>
42
+
43
+ <div id="log-form-section" class="log-form-section disabled-form">
44
+ <h4>{labels.addLog || "Log New Measurement"}</h4>
45
+ <div class="form-grid">
46
+ <div class="input-group">
47
+ <label class="control-label" for="offset-input">
48
+ {labels.offsetLabel || "Offset (seconds)"}
49
+ </label>
50
+ <input
51
+ type="number"
52
+ id="offset-input"
53
+ class="form-control"
54
+ placeholder={labels.offsetPlaceholder || "0"}
55
+ step="any"
56
+ />
57
+ </div>
58
+
59
+ <div class="input-group">
60
+ <label class="control-label" for="position-selector">
61
+ {labels.positionLabel || "Rest Position"}
62
+ </label>
63
+ <select id="position-selector" class="form-control">
64
+ <option value="">-- None --</option>
65
+ <option value="DU">Dial Up (DU)</option>
66
+ <option value="DD">Dial Down (DD)</option>
67
+ <option value="CU">Crown Up (CU)</option>
68
+ <option value="CD">Crown Down (CD)</option>
69
+ <option value="CL">Crown Left (CL)</option>
70
+ <option value="CR">Crown Right (CR)</option>
71
+ </select>
72
+ </div>
73
+
74
+ <div class="input-group">
75
+ <div class="label-row">
76
+ <label class="control-label" for="measurement-date">
77
+ {labels.dateLabel || "Measurement Time"}
78
+ </label>
79
+ <button type="button" id="use-current-time-btn" class="btn-text">
80
+ {labels.useCurrentTime || "Use Current Time"}
81
+ </button>
82
+ </div>
83
+ <input
84
+ type="datetime-local"
85
+ id="measurement-date"
86
+ class="form-control"
87
+ />
88
+ </div>
89
+ </div>
90
+
91
+ <button type="button" id="save-log-btn" class="btn btn-success btn-full">
92
+ <Icon name="mdi:content-save-outline" width="18" height="18" />
93
+ {labels.saveLog || "Save Log"}
94
+ </button>
95
+ </div>
96
+ </div>
@@ -0,0 +1,126 @@
1
+ ---
2
+ interface Props {
3
+ labels: Record<string, string>;
4
+ }
5
+
6
+ const { labels } = Astro.props;
7
+ ---
8
+
9
+ <div class="drift-predictor-section">
10
+ <div class="predictor-grid">
11
+ <div class="input-section">
12
+ <label class="control-label" for="drift-rate-input">
13
+ {labels.dailyRateInput || "Daily Rate Deviation (seconds/day)"}
14
+ </label>
15
+ <div class="input-with-unit">
16
+ <input
17
+ type="number"
18
+ id="drift-rate-input"
19
+ class="form-control"
20
+ placeholder={labels.dailyRatePlaceholder || "e.g. +4.5 or -3"}
21
+ step="any"
22
+ value="0"
23
+ />
24
+ <span class="unit-badge">{labels.secondsPerDay || "s/d"}</span>
25
+ </div>
26
+ <div class="presets-container">
27
+ <button type="button" class="preset-btn" data-value="2.0">{labels.presetRolex || "Rolex (+2 s/d)"}</button>
28
+ <button type="button" class="preset-btn" data-value="4.0">{labels.presetCosc || "COSC (+4 s/d)"}</button>
29
+ <button type="button" class="preset-btn" data-value="15.0">{labels.presetStdMech || "Standard (+15 s/d)"}</button>
30
+ <button type="button" class="preset-btn" data-value="0.5">{labels.presetQuartz || "Quartz (+0.5 s/d)"}</button>
31
+ </div>
32
+ </div>
33
+
34
+ <div class="projections-section">
35
+ <h4>{labels.driftHeading || "Projections"}</h4>
36
+ <div class="projection-table-wrapper">
37
+ <table class="projection-table">
38
+ <thead>
39
+ <tr>
40
+ <th>{labels.period || "Period"}</th>
41
+ <th>{labels.accumulatedDrift || "Projected Drift"}</th>
42
+ </tr>
43
+ </thead>
44
+ <tbody id="projection-tbody">
45
+ <tr data-days="1">
46
+ <td>{labels.day || "1 Day"}</td>
47
+ <td class="drift-val" data-val="0">0.0s</td>
48
+ </tr>
49
+ <tr data-days="7">
50
+ <td>{labels.week || "1 Week"}</td>
51
+ <td class="drift-val" data-val="0">0.0s</td>
52
+ </tr>
53
+ <tr data-days="30">
54
+ <td>{labels.month || "1 Month"}</td>
55
+ <td class="drift-val" data-val="0">0.0s</td>
56
+ </tr>
57
+ <tr data-days="90">
58
+ <td>{labels.months3 || "3 Months"}</td>
59
+ <td class="drift-val" data-val="0">0.0s</td>
60
+ </tr>
61
+ <tr data-days="180">
62
+ <td>{labels.months6 || "6 Months"}</td>
63
+ <td class="drift-val" data-val="0">0.0s</td>
64
+ </tr>
65
+ <tr data-days="365">
66
+ <td>{labels.year || "1 Year"}</td>
67
+ <td class="drift-val" data-val="0">0.0s</td>
68
+ </tr>
69
+ <tr data-days="1825">
70
+ <td>{labels.years5 || "5 Years"}</td>
71
+ <td class="drift-val" data-val="0">0.0s</td>
72
+ </tr>
73
+ </tbody>
74
+ </table>
75
+ </div>
76
+ </div>
77
+ </div>
78
+
79
+ <div class="standards-section">
80
+ <h4>{labels.watchStandardTitle || "Standards Certification"}</h4>
81
+ <div class="standards-grid">
82
+ <div class="standard-card" id-std="rolex" data-min="-2" data-max="2">
83
+ <div class="std-header">
84
+ <span class="std-name">{labels.stdRolex || "Rolex Superlative"}</span>
85
+ <span class="std-status"></span>
86
+ </div>
87
+ <span class="std-tolerance">{labels.toleranceRolex || "±2 s/d"}</span>
88
+ </div>
89
+ <div class="standard-card" id-std="cosc" data-min="-4" data-max="6">
90
+ <div class="std-header">
91
+ <span class="std-name">{labels.stdCosc || "COSC Chronometer"}</span>
92
+ <span class="std-status"></span>
93
+ </div>
94
+ <span class="std-tolerance">{labels.toleranceCosc || "-4 / +6 s/d"}</span>
95
+ </div>
96
+ <div class="standard-card" id-std="metas" data-min="0" data-max="5">
97
+ <div class="std-header">
98
+ <span class="std-name">{labels.stdMetas || "METAS Master"}</span>
99
+ <span class="std-status"></span>
100
+ </div>
101
+ <span class="std-tolerance">{labels.toleranceMetas || "0 / +5 s/d"}</span>
102
+ </div>
103
+ <div class="standard-card" id-std="standard-mech" data-min="-15" data-max="25">
104
+ <div class="std-header">
105
+ <span class="std-name">{labels.stdStdMech || "Standard Mechanical"}</span>
106
+ <span class="std-status"></span>
107
+ </div>
108
+ <span class="std-tolerance">{labels.toleranceStdMech || "±15 s/d"}</span>
109
+ </div>
110
+ <div class="standard-card" id-std="quartz" data-min="-0.5" data-max="0.5">
111
+ <div class="std-header">
112
+ <span class="std-name">{labels.stdQuartz || "Standard Quartz"}</span>
113
+ <span class="std-status"></span>
114
+ </div>
115
+ <span class="std-tolerance">{labels.toleranceQuartz || "±0.5 s/d"}</span>
116
+ </div>
117
+ <div class="standard-card" id-std="haq" data-min="-0.027" data-max="0.027">
118
+ <div class="std-header">
119
+ <span class="std-name">{labels.stdHaq || "High-Precision Quartz"}</span>
120
+ <span class="std-status"></span>
121
+ </div>
122
+ <span class="std-tolerance">{labels.toleranceHaq || "±10 s/year"}</span>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ </div>
@@ -0,0 +1,66 @@
1
+ ---
2
+ import { Icon } from 'astro-icon/components';
3
+
4
+ interface Props {
5
+ labels: Record<string, string>;
6
+ }
7
+
8
+ const { labels } = Astro.props;
9
+ ---
10
+
11
+ <div class="log-history-section">
12
+ <div class="history-header">
13
+ <h4>{labels.historyTitle || "Measurement History"}</h4>
14
+ <div class="header-actions-row">
15
+ <button type="button" id="export-csv-btn" class="btn btn-primary btn-sm">
16
+ <Icon name="mdi:download-outline" width="16" height="16" />
17
+ Export CSV
18
+ </button>
19
+ <button type="button" id="clear-logs-btn" class="btn btn-danger btn-sm">
20
+ <Icon name="mdi:history" width="16" height="16" />
21
+ Clear All
22
+ </button>
23
+ </div>
24
+ </div>
25
+
26
+ <div class="chart-card-wrapper" id="chart-wrapper" style="display: none;">
27
+ <canvas id="drift-trend-chart"></canvas>
28
+ </div>
29
+
30
+ <div class="stats-grid">
31
+ <div class="stat-box">
32
+ <span class="stat-label">{labels.avgRate || "Average Daily Rate"}</span>
33
+ <span id="avg-rate-val" class="stat-value">--</span>
34
+ </div>
35
+ <div class="stat-box">
36
+ <span class="stat-label">{labels.consistency || "Precision Status"}</span>
37
+ <span id="precision-status-val" class="stat-value">--</span>
38
+ </div>
39
+ <div class="stat-box">
40
+ <span class="stat-label">{labels.totalLogs || "Total Logs"}</span>
41
+ <span id="total-logs-val" class="stat-value">0</span>
42
+ </div>
43
+ </div>
44
+
45
+ <div id="avg-rate-explanation-box" class="explanation-box" style="display: none;">
46
+ </div>
47
+
48
+ <div class="table-wrapper">
49
+ <table class="history-table" id="logs-table">
50
+ <thead>
51
+ <tr>
52
+ <th>{labels.tableDate || "Date & Time"}</th>
53
+ <th>{labels.tableOffset || "Offset"}</th>
54
+ <th>{labels.tableRate || "Daily Rate"}</th>
55
+ <th>{labels.tableActions || "Actions"}</th>
56
+ </tr>
57
+ </thead>
58
+ <tbody id="logs-tbody">
59
+ </tbody>
60
+ </table>
61
+ </div>
62
+
63
+ <div id="no-logs-msg" class="no-logs-msg">
64
+ <p>{labels.noLogs || "No logs recorded for this watch yet. Add at least two logs to calculate accuracy."}</p>
65
+ </div>
66
+ </div>