@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,460 @@
1
+ .tool-main-card {
2
+ background: var(--bg-surface);
3
+ border: 1px solid var(--border-color);
4
+ border-radius: 1.25rem;
5
+ max-width: 600px;
6
+ margin: 0 auto;
7
+ padding: 1.75rem;
8
+ display: flex;
9
+ flex-direction: column;
10
+ gap: 1.5rem;
11
+ box-shadow: var(--shadow-base);
12
+
13
+ --color-white: #fff;
14
+ --color-success: #22c55e;
15
+ --color-danger: #ef4444;
16
+ --color-success-bg: rgba(34, 197, 94, 0.12);
17
+ --color-danger-bg: rgba(239, 68, 68, 0.08);
18
+ }
19
+
20
+ .savings-summary {
21
+ display: flex;
22
+ gap: 1rem;
23
+ justify-content: center;
24
+ }
25
+
26
+ .summary-stat {
27
+ flex: 1;
28
+ text-align: center;
29
+ padding: 1rem;
30
+ background: var(--bg-page);
31
+ border-radius: 0.875rem;
32
+ border: 1px solid var(--border-base);
33
+ }
34
+
35
+ .summary-value {
36
+ display: block;
37
+ font-size: 1.5rem;
38
+ font-weight: 800;
39
+ color: var(--accent);
40
+ font-variant-numeric: tabular-nums;
41
+ line-height: 1.2;
42
+ }
43
+
44
+ .summary-label {
45
+ display: block;
46
+ font-size: 0.75rem;
47
+ color: var(--text-dimmed);
48
+ margin-top: 0.2rem;
49
+ text-transform: uppercase;
50
+ letter-spacing: 0.06em;
51
+ font-weight: 600;
52
+ }
53
+
54
+ .add-goal-trigger {
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ gap: 0.5rem;
59
+ width: 100%;
60
+ padding: 0.75rem;
61
+ border: 2px dashed var(--border-base);
62
+ border-radius: 0.875rem;
63
+ background: transparent;
64
+ color: var(--text-dimmed);
65
+ font-size: 0.9375rem;
66
+ font-weight: 600;
67
+ cursor: pointer;
68
+ transition: all 0.2s ease;
69
+ }
70
+
71
+ .add-goal-trigger:hover {
72
+ border-color: var(--accent);
73
+ color: var(--accent);
74
+ background: rgba(99, 102, 241, 0.04);
75
+ }
76
+
77
+ .sim-month-btn {
78
+ display: flex;
79
+ align-items: center;
80
+ justify-content: center;
81
+ gap: 0.35rem;
82
+ padding: 0.35rem 0.75rem;
83
+ border: 1px solid var(--border-base);
84
+ border-radius: 2rem;
85
+ background: var(--bg-page);
86
+ color: var(--text-dimmed);
87
+ font-size: 0.75rem;
88
+ font-weight: 600;
89
+ cursor: pointer;
90
+ transition: all 0.2s ease;
91
+ align-self: flex-end;
92
+ }
93
+
94
+ .sim-month-btn:hover {
95
+ border-color: var(--accent);
96
+ color: var(--accent);
97
+ }
98
+
99
+ .add-goal-form {
100
+ background: var(--bg-page);
101
+ border: 1px solid var(--border-base);
102
+ border-radius: 0.875rem;
103
+ overflow: hidden;
104
+ }
105
+
106
+ .form-header {
107
+ display: flex;
108
+ align-items: center;
109
+ justify-content: space-between;
110
+ padding: 0.875rem 1.125rem;
111
+ border-bottom: 1px solid var(--border-base);
112
+ }
113
+
114
+ .form-header h3 {
115
+ margin: 0;
116
+ font-size: 0.9375rem;
117
+ font-weight: 700;
118
+ color: var(--text-base);
119
+ }
120
+
121
+ .form-close {
122
+ background: none;
123
+ border: none;
124
+ color: var(--text-dimmed);
125
+ cursor: pointer;
126
+ padding: 0.25rem;
127
+ border-radius: 0.375rem;
128
+ }
129
+
130
+ .form-close:hover {
131
+ color: var(--text-base);
132
+ background: var(--bg-surface);
133
+ }
134
+
135
+ .form-body {
136
+ padding: 1.125rem;
137
+ display: flex;
138
+ flex-direction: column;
139
+ gap: 0.875rem;
140
+ }
141
+
142
+ .form-group {
143
+ display: flex;
144
+ flex-direction: column;
145
+ gap: 0.375rem;
146
+ }
147
+
148
+ .form-label {
149
+ font-size: 0.8125rem;
150
+ font-weight: 600;
151
+ color: var(--text-dimmed);
152
+ }
153
+
154
+ .form-input {
155
+ padding: 0.5rem 0.75rem;
156
+ border: 1px solid var(--border-base);
157
+ border-radius: 0.5rem;
158
+ background: var(--bg-surface);
159
+ color: var(--text-base);
160
+ font-size: 0.9375rem;
161
+ outline: none;
162
+ transition: border-color 0.2s ease;
163
+ }
164
+
165
+ .form-input:focus {
166
+ border-color: var(--accent);
167
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
168
+ }
169
+
170
+ .form-row {
171
+ display: grid;
172
+ grid-template-columns: 1fr 1fr;
173
+ gap: 0.75rem;
174
+ }
175
+
176
+ .range-group {
177
+ display: flex;
178
+ align-items: center;
179
+ gap: 0.75rem;
180
+ }
181
+
182
+ .form-range {
183
+ flex: 1;
184
+ -webkit-appearance: none;
185
+ appearance: none;
186
+ height: 4px;
187
+ border-radius: 2px;
188
+ background: var(--border-base);
189
+ outline: none;
190
+ }
191
+
192
+ .form-range::-webkit-slider-thumb {
193
+ -webkit-appearance: none;
194
+ width: 18px;
195
+ height: 18px;
196
+ border-radius: 50%;
197
+ background: var(--accent);
198
+ cursor: pointer;
199
+ border: 2px solid var(--bg-surface);
200
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
201
+ }
202
+
203
+ .range-value {
204
+ font-size: 0.9375rem;
205
+ font-weight: 700;
206
+ color: var(--accent);
207
+ min-width: 4rem;
208
+ text-align: right;
209
+ font-variant-numeric: tabular-nums;
210
+ }
211
+
212
+ .form-submit {
213
+ padding: 0.625rem;
214
+ border: none;
215
+ border-radius: 0.5rem;
216
+ background: var(--accent);
217
+ color: var(--color-white);
218
+ font-size: 0.9375rem;
219
+ font-weight: 600;
220
+ cursor: pointer;
221
+ transition: all 0.2s ease;
222
+ }
223
+
224
+ .form-submit:hover {
225
+ opacity: 0.9;
226
+ transform: translateY(-1px);
227
+ }
228
+
229
+ .goals-list {
230
+ display: flex;
231
+ flex-direction: column;
232
+ gap: 1rem;
233
+ }
234
+
235
+ .goals-empty {
236
+ text-align: center;
237
+ padding: 2.5rem 1rem;
238
+ color: var(--text-dimmed);
239
+ }
240
+
241
+ .empty-title {
242
+ font-size: 1rem;
243
+ font-weight: 600;
244
+ margin: 0.75rem 0 0.25rem;
245
+ color: var(--text-base);
246
+ }
247
+
248
+ .empty-sub {
249
+ font-size: 0.8125rem;
250
+ margin: 0;
251
+ opacity: 0.7;
252
+ }
253
+
254
+ .goal-card {
255
+ background: var(--bg-page);
256
+ border: 1px solid var(--border-base);
257
+ border-radius: 0.875rem;
258
+ padding: 1.125rem;
259
+ transition: all 0.3s ease;
260
+ }
261
+
262
+ .goal-card:hover {
263
+ border-color: rgba(99, 102, 241, 0.2);
264
+ }
265
+
266
+ .goal-achieved {
267
+ border-color: rgba(34, 197, 94, 0.3);
268
+ background: linear-gradient(135deg, var(--bg-page) 0%, rgba(34, 197, 94, 0.04) 100%);
269
+ }
270
+
271
+ .goal-header {
272
+ display: flex;
273
+ align-items: center;
274
+ justify-content: space-between;
275
+ margin-bottom: 1rem;
276
+ }
277
+
278
+ .goal-name-wrap {
279
+ display: flex;
280
+ align-items: center;
281
+ gap: 0.5rem;
282
+ }
283
+
284
+ .goal-name {
285
+ font-size: 1rem;
286
+ font-weight: 700;
287
+ color: var(--text-base);
288
+ }
289
+
290
+ .goal-achieved-badge {
291
+ font-size: 0.6875rem;
292
+ font-weight: 600;
293
+ padding: 0.2rem 0.5rem;
294
+ border-radius: 1rem;
295
+ background: var(--color-success-bg);
296
+ color: var(--color-success);
297
+ text-transform: uppercase;
298
+ letter-spacing: 0.04em;
299
+ }
300
+
301
+ .goal-delete {
302
+ background: none;
303
+ border: none;
304
+ color: var(--text-dimmed);
305
+ cursor: pointer;
306
+ padding: 0.375rem;
307
+ border-radius: 0.375rem;
308
+ opacity: 0.5;
309
+ transition: all 0.2s ease;
310
+ }
311
+
312
+ .goal-delete:hover {
313
+ opacity: 1;
314
+ color: var(--color-danger);
315
+ background: var(--color-danger-bg);
316
+ }
317
+
318
+ .goal-ring-section {
319
+ display: flex;
320
+ gap: 1.25rem;
321
+ align-items: center;
322
+ }
323
+
324
+ .goal-ring {
325
+ position: relative;
326
+ width: 100px;
327
+ height: 100px;
328
+ flex-shrink: 0;
329
+ }
330
+
331
+ .goal-ring svg {
332
+ width: 100%;
333
+ height: 100%;
334
+ transform: rotate(-90deg);
335
+ }
336
+
337
+ .goal-ring-bg {
338
+ fill: none;
339
+ stroke: var(--border-base);
340
+ stroke-width: 6;
341
+ opacity: 0.4;
342
+ }
343
+
344
+ .goal-ring-fill {
345
+ fill: none;
346
+ stroke: var(--accent);
347
+ stroke-width: 6;
348
+ stroke-linecap: round;
349
+ transition: stroke-dashoffset 0.5s ease;
350
+ }
351
+
352
+ .goal-achieved .goal-ring-fill {
353
+ stroke: var(--color-success);
354
+ }
355
+
356
+ .goal-ring-text {
357
+ position: absolute;
358
+ top: 50%;
359
+ left: 50%;
360
+ transform: translate(-50%, -50%);
361
+ text-align: center;
362
+ }
363
+
364
+ .goal-percent {
365
+ display: block;
366
+ font-size: 1.25rem;
367
+ font-weight: 800;
368
+ color: var(--text-base);
369
+ line-height: 1;
370
+ }
371
+
372
+ .goal-ring-label {
373
+ display: block;
374
+ font-size: 0.625rem;
375
+ color: var(--text-dimmed);
376
+ text-transform: uppercase;
377
+ letter-spacing: 0.05em;
378
+ margin-top: 0.15rem;
379
+ }
380
+
381
+ .goal-meta {
382
+ flex: 1;
383
+ display: grid;
384
+ grid-template-columns: 1fr 1fr;
385
+ gap: 0.5rem;
386
+ }
387
+
388
+ .meta-row {
389
+ display: flex;
390
+ flex-direction: column;
391
+ gap: 0.1rem;
392
+ }
393
+
394
+ .meta-label {
395
+ font-size: 0.6875rem;
396
+ color: var(--text-dimmed);
397
+ text-transform: uppercase;
398
+ letter-spacing: 0.04em;
399
+ font-weight: 600;
400
+ }
401
+
402
+ .meta-value {
403
+ font-size: 0.9375rem;
404
+ font-weight: 700;
405
+ color: var(--text-base);
406
+ font-variant-numeric: tabular-nums;
407
+ }
408
+
409
+ .goal-monthly-adjust {
410
+ margin-top: 0.75rem;
411
+ padding-top: 0.75rem;
412
+ border-top: 1px solid var(--border-base);
413
+ display: flex;
414
+ align-items: center;
415
+ gap: 0.75rem;
416
+ }
417
+
418
+ .adjust-label {
419
+ font-size: 0.75rem;
420
+ color: var(--text-dimmed);
421
+ font-weight: 600;
422
+ white-space: nowrap;
423
+ }
424
+
425
+ .goal-slider {
426
+ flex: 1;
427
+ -webkit-appearance: none;
428
+ appearance: none;
429
+ height: 4px;
430
+ border-radius: 2px;
431
+ background: var(--border-base);
432
+ outline: none;
433
+ }
434
+
435
+ .goal-slider::-webkit-slider-thumb {
436
+ -webkit-appearance: none;
437
+ width: 16px;
438
+ height: 16px;
439
+ border-radius: 50%;
440
+ background: var(--accent);
441
+ cursor: pointer;
442
+ border: 2px solid var(--bg-surface);
443
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
444
+ }
445
+
446
+ @media (max-width: 480px) {
447
+ .goal-ring-section {
448
+ flex-direction: column;
449
+ text-align: center;
450
+ }
451
+
452
+ .goal-meta {
453
+ grid-template-columns: 1fr 1fr;
454
+ width: 100%;
455
+ }
456
+
457
+ .form-row {
458
+ grid-template-columns: 1fr;
459
+ }
460
+ }
@@ -0,0 +1,16 @@
1
+ ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import { waterResistanceConverter } 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 = waterResistanceConverter.i18n[locale] || waterResistanceConverter.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: 'Watch Water Resistance Ratings Explained — Crown & Caliber',
6
+ url: 'https://www.crownandcaliber.com/blogs/stories/watch-water-resistance-ratings',
7
+ },
8
+ {
9
+ name: 'Water Resistance in Watches — ISO 6425 Standard',
10
+ url: 'https://en.wikipedia.org/wiki/Water_Resistance_Watch_ISO_6425',
11
+ },
12
+ {
13
+ name: 'What Do Water Resistance Numbers Actually Mean?',
14
+ url: 'https://www.watchfinder.com/water-resistance-guide',
15
+ },
16
+ ];
@@ -0,0 +1,56 @@
1
+ const depthInput = document.getElementById('depth-input') as HTMLInputElement;
2
+ const unitSelect = document.getElementById('unit-select') as HTMLSelectElement;
3
+ const convM = document.getElementById('conv-m') as HTMLElement;
4
+ const convFt = document.getElementById('conv-ft') as HTMLElement;
5
+ const convAtm = document.getElementById('conv-atm') as HTMLElement;
6
+ const convBar = document.getElementById('conv-bar') as HTMLElement;
7
+ const ratingName = document.getElementById('rating-name') as HTMLElement;
8
+ const usageCards = document.querySelectorAll('.usage-card') as NodeListOf<HTMLElement>;
9
+
10
+ function toMeters(value: number, unit: string): number {
11
+ switch (unit) {
12
+ case 'ft': return value / 3.28084;
13
+ case 'atm': return value * 10;
14
+ case 'bar': return value * 10;
15
+ default: return value;
16
+ }
17
+ }
18
+
19
+ function round(v: number): number {
20
+ return v < 10 ? Math.round(v * 10) / 10 : Math.round(v);
21
+ }
22
+
23
+ function getRating(m: number): { name: string; active: string } {
24
+ if (m < 30) return { name: 'Not Water Resistant', active: 'none' };
25
+ if (m < 50) return { name: 'Splash Resistant', active: 'handwash' };
26
+ if (m < 100) return { name: 'Shower & Surface Swim', active: 'shower' };
27
+ if (m < 200) return { name: 'Swimming & Snorkeling', active: 'snorkel' };
28
+ if (m < 500) return { name: 'Recreational Scuba', active: 'scuba' };
29
+ return { name: 'Deep / Saturation Diving', active: 'extreme' };
30
+ }
31
+
32
+ function update() {
33
+ const val = parseFloat(depthInput.value) || 0;
34
+ const unit = unitSelect.value;
35
+ const m = toMeters(val, unit);
36
+
37
+ const atm = round(m / 10);
38
+ const bar = round(m / 10);
39
+ const ft = round(m * 3.28084);
40
+
41
+ convM.textContent = round(m) + ' m';
42
+ convFt.textContent = ft + ' ft';
43
+ convAtm.textContent = atm + ' ATM';
44
+ convBar.textContent = bar + ' bar';
45
+
46
+ const rating = getRating(m);
47
+ ratingName.textContent = rating.name;
48
+
49
+ usageCards.forEach((card) => {
50
+ card.classList.toggle('active', card.dataset.rating === rating.active);
51
+ });
52
+ }
53
+
54
+ depthInput.addEventListener('input', update);
55
+ unitSelect.addEventListener('change', update);
56
+ update();
@@ -0,0 +1,15 @@
1
+ ---
2
+ import ConverterPanel from './components/ConverterPanel.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
+ <ConverterPanel labels={ui} />
13
+ </div>
14
+
15
+ <script src="./client.ts"></script>
@@ -0,0 +1,113 @@
1
+ ---
2
+ interface Props {
3
+ labels: Record<string, string>;
4
+ }
5
+
6
+ const { labels } = Astro.props;
7
+ ---
8
+
9
+ <div class="converter-panel">
10
+ <div class="panel-section">
11
+ <div class="section-label">{labels.depthLabel || "Water Resistance"}</div>
12
+ <div class="input-unit-group">
13
+ <input type="number" class="depth-input" id="depth-input" min="0" max="10000" value="30" placeholder="30" />
14
+ <select class="unit-select" id="unit-select">
15
+ <option value="m">{labels.unitMeters || "Meters (m)"}</option>
16
+ <option value="ft">{labels.unitFeet || "Feet (ft)"}</option>
17
+ <option value="atm">{labels.unitATM || "Atmospheres (ATM)"}</option>
18
+ <option value="bar">{labels.unitBar || "Bar (bar)"}</option>
19
+ </select>
20
+ </div>
21
+ </div>
22
+
23
+ <div class="conversions-card" id="conversions-card">
24
+ <div class="conversions-header">{labels.convertedValues || "Equivalent Ratings"}</div>
25
+ <div class="conversions-grid">
26
+ <div class="conv-item">
27
+ <span class="conv-label">{labels.unitMeters || "Meters"}</span>
28
+ <span class="conv-value" id="conv-m">30 m</span>
29
+ </div>
30
+ <div class="conv-item">
31
+ <span class="conv-label">{labels.unitFeet || "Feet"}</span>
32
+ <span class="conv-value" id="conv-ft">100 ft</span>
33
+ </div>
34
+ <div class="conv-item">
35
+ <span class="conv-label">{labels.unitATM || "ATM"}</span>
36
+ <span class="conv-value" id="conv-atm">3 ATM</span>
37
+ </div>
38
+ <div class="conv-item">
39
+ <span class="conv-label">{labels.unitBar || "Bar"}</span>
40
+ <span class="conv-value" id="conv-bar">3 bar</span>
41
+ </div>
42
+ </div>
43
+ </div>
44
+
45
+ <div class="rating-badge" id="rating-badge">
46
+ <div class="rating-label">{labels.ratingLabel || "Rating"}</div>
47
+ <div class="rating-name" id="rating-name">{labels.handWash || "Splash Resistant"}</div>
48
+ </div>
49
+
50
+ <div class="usage-cards" id="usage-cards">
51
+ <div class="usage-card" data-rating="none">
52
+ <div class="usage-icon">
53
+ <svg viewBox="0 0 24 24" width="18" height="18"><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"/></svg>
54
+ </div>
55
+ <div>
56
+ <div class="usage-title">{labels.notWaterResistant || "Not Water Resistant"}</div>
57
+ <div class="usage-desc">{labels.notWaterResistantDesc || "No splashes. Keep away from water entirely."}</div>
58
+ </div>
59
+ </div>
60
+ <div class="usage-card" data-rating="handwash">
61
+ <div class="usage-icon">
62
+ <svg viewBox="0 0 24 24" width="18" height="18"><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"/></svg>
63
+ </div>
64
+ <div>
65
+ <div class="usage-title">{labels.handWash || "Splash Resistant"}</div>
66
+ <div class="usage-desc">{labels.handWashDesc || "Hand washing, rain, splashes. Not for swimming."}</div>
67
+ </div>
68
+ </div>
69
+ <div class="usage-card" data-rating="shower">
70
+ <div class="usage-icon">
71
+ <svg viewBox="0 0 24 24" width="18" height="18"><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"/></svg>
72
+ </div>
73
+ <div>
74
+ <div class="usage-title">{labels.showerSwim || "Shower & Light Swim"}</div>
75
+ <div class="usage-desc">{labels.showerSwimDesc || "Showering, light swimming. Not for diving."}</div>
76
+ </div>
77
+ </div>
78
+ <div class="usage-card" data-rating="snorkel">
79
+ <div class="usage-icon">
80
+ <svg viewBox="0 0 24 24" width="18" height="18"><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"/></svg>
81
+ </div>
82
+ <div>
83
+ <div class="usage-title">{labels.snorkeling || "Swimming & Snorkeling"}</div>
84
+ <div class="usage-desc">{labels.snorkelingDesc || "Pool swimming, snorkeling, water sports."}</div>
85
+ </div>
86
+ </div>
87
+ <div class="usage-card" data-rating="scuba">
88
+ <div class="usage-icon">
89
+ <svg viewBox="0 0 24 24" width="18" height="18"><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"/></svg>
90
+ </div>
91
+ <div>
92
+ <div class="usage-title">{labels.scubaDiving || "Recreational Scuba"}</div>
93
+ <div class="usage-desc">{labels.scubaDivingDesc || "Scuba diving, snorkeling, water sports."}</div>
94
+ </div>
95
+ </div>
96
+ <div class="usage-card" data-rating="extreme">
97
+ <div class="usage-icon">
98
+ <svg viewBox="0 0 24 24" width="18" height="18"><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"/></svg>
99
+ </div>
100
+ <div>
101
+ <div class="usage-title">{labels.saturationDiving || "Saturation Diving"}</div>
102
+ <div class="usage-desc">{labels.saturationDivingDesc || "Professional saturation diving. Extreme depths."}</div>
103
+ </div>
104
+ </div>
105
+ </div>
106
+
107
+ <div class="tip-row">
108
+ <svg class="tip-icon" viewBox="0 0 24 24" width="16" height="16">
109
+ <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" />
110
+ </svg>
111
+ <span class="tip-text">{labels.tipContent || "Water resistance degrades over time. Gaskets and seals should be tested yearly and replaced every 3\u20135 years."}</span>
112
+ </div>
113
+ </div>