@jjlmoya/utils-forensic-science 1.1.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 (316) hide show
  1. package/package.json +69 -0
  2. package/scripts/crystal-lattice-catalog-translations.mjs +459 -0
  3. package/scripts/postinstall.mjs +27 -0
  4. package/scripts/sync-crystal-lattice-i18n.mjs +50 -0
  5. package/src/category/i18n/de.ts +55 -0
  6. package/src/category/i18n/en.ts +55 -0
  7. package/src/category/i18n/es.ts +55 -0
  8. package/src/category/i18n/fr.ts +55 -0
  9. package/src/category/i18n/id.ts +55 -0
  10. package/src/category/i18n/it.ts +55 -0
  11. package/src/category/i18n/ja.ts +56 -0
  12. package/src/category/i18n/ko.ts +56 -0
  13. package/src/category/i18n/nl.ts +55 -0
  14. package/src/category/i18n/pl.ts +55 -0
  15. package/src/category/i18n/pt.ts +55 -0
  16. package/src/category/i18n/ru.ts +55 -0
  17. package/src/category/i18n/sv.ts +55 -0
  18. package/src/category/i18n/tr.ts +55 -0
  19. package/src/category/i18n/zh.ts +56 -0
  20. package/src/category/index.ts +48 -0
  21. package/src/category/seo.astro +15 -0
  22. package/src/components/PreviewNavSidebar.astro +116 -0
  23. package/src/components/PreviewToolbar.astro +143 -0
  24. package/src/data.ts +10 -0
  25. package/src/entries.ts +48 -0
  26. package/src/env.d.ts +5 -0
  27. package/src/index.ts +29 -0
  28. package/src/layouts/PreviewLayout.astro +117 -0
  29. package/src/pages/[locale]/[slug].astro +162 -0
  30. package/src/pages/[locale].astro +261 -0
  31. package/src/pages/index.astro +3 -0
  32. package/src/tests/diacritics_density.test.ts +140 -0
  33. package/src/tests/faq_count.test.ts +19 -0
  34. package/src/tests/i18n_coverage.test.ts +36 -0
  35. package/src/tests/inverted_punctuation.test.ts +84 -0
  36. package/src/tests/locale_completeness.test.ts +24 -0
  37. package/src/tests/mocks/astro_mock.js +2 -0
  38. package/src/tests/no_en_dash.test.ts +70 -0
  39. package/src/tests/no_h1_in_components.test.ts +48 -0
  40. package/src/tests/schemas_fulfillment.test.ts +23 -0
  41. package/src/tests/script_density.test.ts +94 -0
  42. package/src/tests/seo_length.test.ts +46 -0
  43. package/src/tests/shared-test-helpers.ts +56 -0
  44. package/src/tests/slug_language_code_format.test.ts +23 -0
  45. package/src/tests/slug_uniqueness.test.ts +81 -0
  46. package/src/tests/title_quality.test.ts +55 -0
  47. package/src/tests/tool_exports.test.ts +34 -0
  48. package/src/tests/tool_validation.test.ts +16 -0
  49. package/src/tests/widmark.test.ts +28 -0
  50. package/src/tool/forensic-age-estimator/bibliography.astro +14 -0
  51. package/src/tool/forensic-age-estimator/bibliography.ts +16 -0
  52. package/src/tool/forensic-age-estimator/component.astro +321 -0
  53. package/src/tool/forensic-age-estimator/dental-skeletal-third-molar-age-estimator.css +634 -0
  54. package/src/tool/forensic-age-estimator/entry.ts +32 -0
  55. package/src/tool/forensic-age-estimator/i18n/de.ts +245 -0
  56. package/src/tool/forensic-age-estimator/i18n/en.ts +245 -0
  57. package/src/tool/forensic-age-estimator/i18n/es.ts +245 -0
  58. package/src/tool/forensic-age-estimator/i18n/fr.ts +245 -0
  59. package/src/tool/forensic-age-estimator/i18n/id.ts +245 -0
  60. package/src/tool/forensic-age-estimator/i18n/it.ts +245 -0
  61. package/src/tool/forensic-age-estimator/i18n/ja.ts +245 -0
  62. package/src/tool/forensic-age-estimator/i18n/ko.ts +245 -0
  63. package/src/tool/forensic-age-estimator/i18n/nl.ts +245 -0
  64. package/src/tool/forensic-age-estimator/i18n/pl.ts +245 -0
  65. package/src/tool/forensic-age-estimator/i18n/pt.ts +245 -0
  66. package/src/tool/forensic-age-estimator/i18n/ru.ts +245 -0
  67. package/src/tool/forensic-age-estimator/i18n/sv.ts +245 -0
  68. package/src/tool/forensic-age-estimator/i18n/tr.ts +245 -0
  69. package/src/tool/forensic-age-estimator/i18n/zh.ts +246 -0
  70. package/src/tool/forensic-age-estimator/index.ts +11 -0
  71. package/src/tool/forensic-age-estimator/logic.ts +81 -0
  72. package/src/tool/forensic-age-estimator/seo.astro +15 -0
  73. package/src/tool/forensic-blood-test-simulator/bibliography.astro +6 -0
  74. package/src/tool/forensic-blood-test-simulator/bibliography.ts +12 -0
  75. package/src/tool/forensic-blood-test-simulator/component.astro +168 -0
  76. package/src/tool/forensic-blood-test-simulator/dom-utils.ts +40 -0
  77. package/src/tool/forensic-blood-test-simulator/entry.ts +32 -0
  78. package/src/tool/forensic-blood-test-simulator/forensic-presumptive-blood-testing-luminol-kastle-meyer-simulator.css +532 -0
  79. package/src/tool/forensic-blood-test-simulator/helpers.ts +16 -0
  80. package/src/tool/forensic-blood-test-simulator/i18n/de.ts +185 -0
  81. package/src/tool/forensic-blood-test-simulator/i18n/en.ts +185 -0
  82. package/src/tool/forensic-blood-test-simulator/i18n/es.ts +185 -0
  83. package/src/tool/forensic-blood-test-simulator/i18n/fr.ts +220 -0
  84. package/src/tool/forensic-blood-test-simulator/i18n/id.ts +220 -0
  85. package/src/tool/forensic-blood-test-simulator/i18n/it.ts +220 -0
  86. package/src/tool/forensic-blood-test-simulator/i18n/ja.ts +220 -0
  87. package/src/tool/forensic-blood-test-simulator/i18n/ko.ts +220 -0
  88. package/src/tool/forensic-blood-test-simulator/i18n/nl.ts +220 -0
  89. package/src/tool/forensic-blood-test-simulator/i18n/pl.ts +220 -0
  90. package/src/tool/forensic-blood-test-simulator/i18n/pt.ts +220 -0
  91. package/src/tool/forensic-blood-test-simulator/i18n/ru.ts +220 -0
  92. package/src/tool/forensic-blood-test-simulator/i18n/sv.ts +220 -0
  93. package/src/tool/forensic-blood-test-simulator/i18n/tr.ts +220 -0
  94. package/src/tool/forensic-blood-test-simulator/i18n/zh.ts +220 -0
  95. package/src/tool/forensic-blood-test-simulator/index.ts +11 -0
  96. package/src/tool/forensic-blood-test-simulator/logic.ts +174 -0
  97. package/src/tool/forensic-blood-test-simulator/script.ts +192 -0
  98. package/src/tool/forensic-blood-test-simulator/seo.astro +15 -0
  99. package/src/tool/forensic-fiber-comparison-microscope/bibliography.astro +6 -0
  100. package/src/tool/forensic-fiber-comparison-microscope/bibliography.ts +16 -0
  101. package/src/tool/forensic-fiber-comparison-microscope/component.astro +146 -0
  102. package/src/tool/forensic-fiber-comparison-microscope/entry.ts +32 -0
  103. package/src/tool/forensic-fiber-comparison-microscope/forensic-fiber-comparison-microscope.css +629 -0
  104. package/src/tool/forensic-fiber-comparison-microscope/i18n/de.ts +250 -0
  105. package/src/tool/forensic-fiber-comparison-microscope/i18n/en.ts +246 -0
  106. package/src/tool/forensic-fiber-comparison-microscope/i18n/es.ts +246 -0
  107. package/src/tool/forensic-fiber-comparison-microscope/i18n/fr.ts +246 -0
  108. package/src/tool/forensic-fiber-comparison-microscope/i18n/id.ts +250 -0
  109. package/src/tool/forensic-fiber-comparison-microscope/i18n/it.ts +246 -0
  110. package/src/tool/forensic-fiber-comparison-microscope/i18n/ja.ts +250 -0
  111. package/src/tool/forensic-fiber-comparison-microscope/i18n/ko.ts +246 -0
  112. package/src/tool/forensic-fiber-comparison-microscope/i18n/nl.ts +246 -0
  113. package/src/tool/forensic-fiber-comparison-microscope/i18n/pl.ts +250 -0
  114. package/src/tool/forensic-fiber-comparison-microscope/i18n/pt.ts +250 -0
  115. package/src/tool/forensic-fiber-comparison-microscope/i18n/ru.ts +250 -0
  116. package/src/tool/forensic-fiber-comparison-microscope/i18n/sv.ts +250 -0
  117. package/src/tool/forensic-fiber-comparison-microscope/i18n/tr.ts +246 -0
  118. package/src/tool/forensic-fiber-comparison-microscope/i18n/zh.ts +250 -0
  119. package/src/tool/forensic-fiber-comparison-microscope/index.ts +11 -0
  120. package/src/tool/forensic-fiber-comparison-microscope/logic.ts +244 -0
  121. package/src/tool/forensic-fiber-comparison-microscope/render.ts +265 -0
  122. package/src/tool/forensic-fiber-comparison-microscope/seo.astro +15 -0
  123. package/src/tool/forensic-fiber-comparison-microscope/view.ts +267 -0
  124. package/src/tool/forensic-glass-becke-line-simulator/bibliography.astro +6 -0
  125. package/src/tool/forensic-glass-becke-line-simulator/bibliography.ts +16 -0
  126. package/src/tool/forensic-glass-becke-line-simulator/component.astro +81 -0
  127. package/src/tool/forensic-glass-becke-line-simulator/entry.ts +32 -0
  128. package/src/tool/forensic-glass-becke-line-simulator/forensic-glass-becke-line-simulator.css +392 -0
  129. package/src/tool/forensic-glass-becke-line-simulator/i18n/de.ts +231 -0
  130. package/src/tool/forensic-glass-becke-line-simulator/i18n/en.ts +231 -0
  131. package/src/tool/forensic-glass-becke-line-simulator/i18n/es.ts +231 -0
  132. package/src/tool/forensic-glass-becke-line-simulator/i18n/fr.ts +231 -0
  133. package/src/tool/forensic-glass-becke-line-simulator/i18n/id.ts +231 -0
  134. package/src/tool/forensic-glass-becke-line-simulator/i18n/it.ts +231 -0
  135. package/src/tool/forensic-glass-becke-line-simulator/i18n/ja.ts +231 -0
  136. package/src/tool/forensic-glass-becke-line-simulator/i18n/ko.ts +231 -0
  137. package/src/tool/forensic-glass-becke-line-simulator/i18n/nl.ts +231 -0
  138. package/src/tool/forensic-glass-becke-line-simulator/i18n/pl.ts +231 -0
  139. package/src/tool/forensic-glass-becke-line-simulator/i18n/pt.ts +231 -0
  140. package/src/tool/forensic-glass-becke-line-simulator/i18n/ru.ts +231 -0
  141. package/src/tool/forensic-glass-becke-line-simulator/i18n/sv.ts +231 -0
  142. package/src/tool/forensic-glass-becke-line-simulator/i18n/tr.ts +231 -0
  143. package/src/tool/forensic-glass-becke-line-simulator/i18n/zh.ts +231 -0
  144. package/src/tool/forensic-glass-becke-line-simulator/index.ts +11 -0
  145. package/src/tool/forensic-glass-becke-line-simulator/logic.ts +100 -0
  146. package/src/tool/forensic-glass-becke-line-simulator/seo.astro +15 -0
  147. package/src/tool/forensic-glass-becke-line-simulator/view.ts +281 -0
  148. package/src/tool/forensic-image-authenticity-analyzer/bibliography.astro +9 -0
  149. package/src/tool/forensic-image-authenticity-analyzer/bibliography.ts +7 -0
  150. package/src/tool/forensic-image-authenticity-analyzer/component.astro +250 -0
  151. package/src/tool/forensic-image-authenticity-analyzer/entry.ts +29 -0
  152. package/src/tool/forensic-image-authenticity-analyzer/forensic-image-metadata-authenticity-analyzer.css +679 -0
  153. package/src/tool/forensic-image-authenticity-analyzer/i18n/de.ts +105 -0
  154. package/src/tool/forensic-image-authenticity-analyzer/i18n/en.ts +105 -0
  155. package/src/tool/forensic-image-authenticity-analyzer/i18n/es.ts +105 -0
  156. package/src/tool/forensic-image-authenticity-analyzer/i18n/fr.ts +105 -0
  157. package/src/tool/forensic-image-authenticity-analyzer/i18n/id.ts +76 -0
  158. package/src/tool/forensic-image-authenticity-analyzer/i18n/it.ts +105 -0
  159. package/src/tool/forensic-image-authenticity-analyzer/i18n/ja.ts +72 -0
  160. package/src/tool/forensic-image-authenticity-analyzer/i18n/ko.ts +72 -0
  161. package/src/tool/forensic-image-authenticity-analyzer/i18n/nl.ts +75 -0
  162. package/src/tool/forensic-image-authenticity-analyzer/i18n/pl.ts +75 -0
  163. package/src/tool/forensic-image-authenticity-analyzer/i18n/pt.ts +105 -0
  164. package/src/tool/forensic-image-authenticity-analyzer/i18n/ru.ts +75 -0
  165. package/src/tool/forensic-image-authenticity-analyzer/i18n/sv.ts +76 -0
  166. package/src/tool/forensic-image-authenticity-analyzer/i18n/tr.ts +76 -0
  167. package/src/tool/forensic-image-authenticity-analyzer/i18n/zh.ts +71 -0
  168. package/src/tool/forensic-image-authenticity-analyzer/index.ts +11 -0
  169. package/src/tool/forensic-image-authenticity-analyzer/logic.ts +283 -0
  170. package/src/tool/forensic-image-authenticity-analyzer/seo.astro +10 -0
  171. package/src/tool/forensic-microcrystal-drug-simulator/bibliography.astro +6 -0
  172. package/src/tool/forensic-microcrystal-drug-simulator/bibliography.ts +12 -0
  173. package/src/tool/forensic-microcrystal-drug-simulator/component.astro +240 -0
  174. package/src/tool/forensic-microcrystal-drug-simulator/entry.ts +32 -0
  175. package/src/tool/forensic-microcrystal-drug-simulator/forensic-microcrystal-drug-simulator.css +430 -0
  176. package/src/tool/forensic-microcrystal-drug-simulator/i18n/de.ts +244 -0
  177. package/src/tool/forensic-microcrystal-drug-simulator/i18n/en.ts +244 -0
  178. package/src/tool/forensic-microcrystal-drug-simulator/i18n/es.ts +244 -0
  179. package/src/tool/forensic-microcrystal-drug-simulator/i18n/fr.ts +244 -0
  180. package/src/tool/forensic-microcrystal-drug-simulator/i18n/id.ts +244 -0
  181. package/src/tool/forensic-microcrystal-drug-simulator/i18n/it.ts +244 -0
  182. package/src/tool/forensic-microcrystal-drug-simulator/i18n/ja.ts +244 -0
  183. package/src/tool/forensic-microcrystal-drug-simulator/i18n/ko.ts +244 -0
  184. package/src/tool/forensic-microcrystal-drug-simulator/i18n/nl.ts +244 -0
  185. package/src/tool/forensic-microcrystal-drug-simulator/i18n/pl.ts +244 -0
  186. package/src/tool/forensic-microcrystal-drug-simulator/i18n/pt.ts +244 -0
  187. package/src/tool/forensic-microcrystal-drug-simulator/i18n/ru.ts +244 -0
  188. package/src/tool/forensic-microcrystal-drug-simulator/i18n/sv.ts +244 -0
  189. package/src/tool/forensic-microcrystal-drug-simulator/i18n/tr.ts +244 -0
  190. package/src/tool/forensic-microcrystal-drug-simulator/i18n/zh.ts +244 -0
  191. package/src/tool/forensic-microcrystal-drug-simulator/index.ts +11 -0
  192. package/src/tool/forensic-microcrystal-drug-simulator/logic.ts +189 -0
  193. package/src/tool/forensic-microcrystal-drug-simulator/seo.astro +15 -0
  194. package/src/tool/forensic-sex-determinator/bibliography.astro +14 -0
  195. package/src/tool/forensic-sex-determinator/bibliography.ts +12 -0
  196. package/src/tool/forensic-sex-determinator/component.astro +463 -0
  197. package/src/tool/forensic-sex-determinator/entry.ts +32 -0
  198. package/src/tool/forensic-sex-determinator/forensic-sex-determinator.css +413 -0
  199. package/src/tool/forensic-sex-determinator/i18n/de.ts +211 -0
  200. package/src/tool/forensic-sex-determinator/i18n/en.ts +211 -0
  201. package/src/tool/forensic-sex-determinator/i18n/es.ts +211 -0
  202. package/src/tool/forensic-sex-determinator/i18n/fr.ts +211 -0
  203. package/src/tool/forensic-sex-determinator/i18n/id.ts +211 -0
  204. package/src/tool/forensic-sex-determinator/i18n/it.ts +211 -0
  205. package/src/tool/forensic-sex-determinator/i18n/ja.ts +211 -0
  206. package/src/tool/forensic-sex-determinator/i18n/ko.ts +211 -0
  207. package/src/tool/forensic-sex-determinator/i18n/nl.ts +211 -0
  208. package/src/tool/forensic-sex-determinator/i18n/pl.ts +211 -0
  209. package/src/tool/forensic-sex-determinator/i18n/pt.ts +211 -0
  210. package/src/tool/forensic-sex-determinator/i18n/ru.ts +211 -0
  211. package/src/tool/forensic-sex-determinator/i18n/sv.ts +211 -0
  212. package/src/tool/forensic-sex-determinator/i18n/tr.ts +211 -0
  213. package/src/tool/forensic-sex-determinator/i18n/zh.ts +211 -0
  214. package/src/tool/forensic-sex-determinator/index.ts +11 -0
  215. package/src/tool/forensic-sex-determinator/logic.ts +89 -0
  216. package/src/tool/forensic-sex-determinator/seo.astro +15 -0
  217. package/src/tool/forensic-stature-estimator/bibliography.astro +14 -0
  218. package/src/tool/forensic-stature-estimator/bibliography.ts +12 -0
  219. package/src/tool/forensic-stature-estimator/component.astro +49 -0
  220. package/src/tool/forensic-stature-estimator/components/EstimationPanel.astro +65 -0
  221. package/src/tool/forensic-stature-estimator/components/OsteometricBoard.astro +39 -0
  222. package/src/tool/forensic-stature-estimator/components/OsteometricSelector.astro +109 -0
  223. package/src/tool/forensic-stature-estimator/dom-utils.ts +71 -0
  224. package/src/tool/forensic-stature-estimator/entry.ts +32 -0
  225. package/src/tool/forensic-stature-estimator/forensic-stature-estimator.css +689 -0
  226. package/src/tool/forensic-stature-estimator/helpers.ts +51 -0
  227. package/src/tool/forensic-stature-estimator/i18n/de.ts +196 -0
  228. package/src/tool/forensic-stature-estimator/i18n/en.ts +196 -0
  229. package/src/tool/forensic-stature-estimator/i18n/es.ts +196 -0
  230. package/src/tool/forensic-stature-estimator/i18n/fr.ts +196 -0
  231. package/src/tool/forensic-stature-estimator/i18n/id.ts +196 -0
  232. package/src/tool/forensic-stature-estimator/i18n/it.ts +196 -0
  233. package/src/tool/forensic-stature-estimator/i18n/ja.ts +196 -0
  234. package/src/tool/forensic-stature-estimator/i18n/ko.ts +196 -0
  235. package/src/tool/forensic-stature-estimator/i18n/nl.ts +196 -0
  236. package/src/tool/forensic-stature-estimator/i18n/pl.ts +196 -0
  237. package/src/tool/forensic-stature-estimator/i18n/pt.ts +196 -0
  238. package/src/tool/forensic-stature-estimator/i18n/ru.ts +196 -0
  239. package/src/tool/forensic-stature-estimator/i18n/sv.ts +196 -0
  240. package/src/tool/forensic-stature-estimator/i18n/tr.ts +196 -0
  241. package/src/tool/forensic-stature-estimator/i18n/zh.ts +196 -0
  242. package/src/tool/forensic-stature-estimator/index.ts +11 -0
  243. package/src/tool/forensic-stature-estimator/logic.ts +119 -0
  244. package/src/tool/forensic-stature-estimator/script.ts +288 -0
  245. package/src/tool/forensic-stature-estimator/seo.astro +15 -0
  246. package/src/tool/forensic-tlc-ink-simulator/bibliography.astro +6 -0
  247. package/src/tool/forensic-tlc-ink-simulator/bibliography.ts +16 -0
  248. package/src/tool/forensic-tlc-ink-simulator/component.astro +245 -0
  249. package/src/tool/forensic-tlc-ink-simulator/entry.ts +32 -0
  250. package/src/tool/forensic-tlc-ink-simulator/forensic-tlc-ink-simulator.css +462 -0
  251. package/src/tool/forensic-tlc-ink-simulator/i18n/de.ts +243 -0
  252. package/src/tool/forensic-tlc-ink-simulator/i18n/en.ts +243 -0
  253. package/src/tool/forensic-tlc-ink-simulator/i18n/es.ts +243 -0
  254. package/src/tool/forensic-tlc-ink-simulator/i18n/fr.ts +243 -0
  255. package/src/tool/forensic-tlc-ink-simulator/i18n/id.ts +243 -0
  256. package/src/tool/forensic-tlc-ink-simulator/i18n/it.ts +243 -0
  257. package/src/tool/forensic-tlc-ink-simulator/i18n/ja.ts +235 -0
  258. package/src/tool/forensic-tlc-ink-simulator/i18n/ko.ts +235 -0
  259. package/src/tool/forensic-tlc-ink-simulator/i18n/nl.ts +243 -0
  260. package/src/tool/forensic-tlc-ink-simulator/i18n/pl.ts +243 -0
  261. package/src/tool/forensic-tlc-ink-simulator/i18n/pt.ts +243 -0
  262. package/src/tool/forensic-tlc-ink-simulator/i18n/ru.ts +243 -0
  263. package/src/tool/forensic-tlc-ink-simulator/i18n/sv.ts +243 -0
  264. package/src/tool/forensic-tlc-ink-simulator/i18n/tr.ts +243 -0
  265. package/src/tool/forensic-tlc-ink-simulator/i18n/zh.ts +235 -0
  266. package/src/tool/forensic-tlc-ink-simulator/index.ts +11 -0
  267. package/src/tool/forensic-tlc-ink-simulator/logic.ts +152 -0
  268. package/src/tool/forensic-tlc-ink-simulator/seo.astro +15 -0
  269. package/src/tool/gsr-dispersion-calculator/bibliography.astro +6 -0
  270. package/src/tool/gsr-dispersion-calculator/bibliography.ts +16 -0
  271. package/src/tool/gsr-dispersion-calculator/component.astro +294 -0
  272. package/src/tool/gsr-dispersion-calculator/entry.ts +32 -0
  273. package/src/tool/gsr-dispersion-calculator/gsr-dispersion-calculator.css +305 -0
  274. package/src/tool/gsr-dispersion-calculator/i18n/de.ts +272 -0
  275. package/src/tool/gsr-dispersion-calculator/i18n/en.ts +272 -0
  276. package/src/tool/gsr-dispersion-calculator/i18n/es.ts +272 -0
  277. package/src/tool/gsr-dispersion-calculator/i18n/fr.ts +272 -0
  278. package/src/tool/gsr-dispersion-calculator/i18n/id.ts +272 -0
  279. package/src/tool/gsr-dispersion-calculator/i18n/it.ts +272 -0
  280. package/src/tool/gsr-dispersion-calculator/i18n/ja.ts +272 -0
  281. package/src/tool/gsr-dispersion-calculator/i18n/ko.ts +272 -0
  282. package/src/tool/gsr-dispersion-calculator/i18n/nl.ts +272 -0
  283. package/src/tool/gsr-dispersion-calculator/i18n/pl.ts +272 -0
  284. package/src/tool/gsr-dispersion-calculator/i18n/pt.ts +272 -0
  285. package/src/tool/gsr-dispersion-calculator/i18n/ru.ts +272 -0
  286. package/src/tool/gsr-dispersion-calculator/i18n/sv.ts +272 -0
  287. package/src/tool/gsr-dispersion-calculator/i18n/tr.ts +272 -0
  288. package/src/tool/gsr-dispersion-calculator/i18n/zh.ts +272 -0
  289. package/src/tool/gsr-dispersion-calculator/index.ts +11 -0
  290. package/src/tool/gsr-dispersion-calculator/logic.ts +148 -0
  291. package/src/tool/gsr-dispersion-calculator/seo.astro +15 -0
  292. package/src/tool/widmark-alcohol-simulator/bibliography.astro +14 -0
  293. package/src/tool/widmark-alcohol-simulator/bibliography.ts +12 -0
  294. package/src/tool/widmark-alcohol-simulator/component.astro +453 -0
  295. package/src/tool/widmark-alcohol-simulator/entry.ts +32 -0
  296. package/src/tool/widmark-alcohol-simulator/i18n/de.ts +193 -0
  297. package/src/tool/widmark-alcohol-simulator/i18n/en.ts +206 -0
  298. package/src/tool/widmark-alcohol-simulator/i18n/es.ts +193 -0
  299. package/src/tool/widmark-alcohol-simulator/i18n/fr.ts +193 -0
  300. package/src/tool/widmark-alcohol-simulator/i18n/id.ts +193 -0
  301. package/src/tool/widmark-alcohol-simulator/i18n/it.ts +193 -0
  302. package/src/tool/widmark-alcohol-simulator/i18n/ja.ts +193 -0
  303. package/src/tool/widmark-alcohol-simulator/i18n/ko.ts +193 -0
  304. package/src/tool/widmark-alcohol-simulator/i18n/nl.ts +193 -0
  305. package/src/tool/widmark-alcohol-simulator/i18n/pl.ts +193 -0
  306. package/src/tool/widmark-alcohol-simulator/i18n/pt.ts +193 -0
  307. package/src/tool/widmark-alcohol-simulator/i18n/ru.ts +193 -0
  308. package/src/tool/widmark-alcohol-simulator/i18n/sv.ts +193 -0
  309. package/src/tool/widmark-alcohol-simulator/i18n/tr.ts +193 -0
  310. package/src/tool/widmark-alcohol-simulator/i18n/zh.ts +193 -0
  311. package/src/tool/widmark-alcohol-simulator/index.ts +11 -0
  312. package/src/tool/widmark-alcohol-simulator/logic.ts +97 -0
  313. package/src/tool/widmark-alcohol-simulator/seo.astro +15 -0
  314. package/src/tool/widmark-alcohol-simulator/widmark-alcohol-simulator.css +386 -0
  315. package/src/tools.ts +27 -0
  316. package/src/types.ts +70 -0
@@ -0,0 +1,453 @@
1
+ ---
2
+ import './widmark-alcohol-simulator.css';
3
+
4
+ interface Props {
5
+ ui: Record<string, string>;
6
+ }
7
+
8
+ const { ui } = Astro.props;
9
+ ---
10
+
11
+ <div class="widmark-simulator" id="widmark-simulator-root">
12
+ <aside class="widmark-panel">
13
+ <h3 class="widmark-section-title">
14
+ <span>{ui.subjectProfile}</span>
15
+ </h3>
16
+
17
+ <div class="widmark-field">
18
+ <span>{ui.weight}</span>
19
+ <input type="number" id="widmark-weight" class="widmark-input" min="30" max="250" value="80" />
20
+ </div>
21
+
22
+ <div class="widmark-field">
23
+ <span>{ui.sex}</span>
24
+ <div class="widmark-radio-group">
25
+ <label class="widmark-radio-card">
26
+ <input type="radio" name="widmark-sex" value="male" checked />
27
+ <span>{ui.male}</span>
28
+ </label>
29
+ <label class="widmark-radio-card">
30
+ <input type="radio" name="widmark-sex" value="female" />
31
+ <span>{ui.female}</span>
32
+ </label>
33
+ </div>
34
+ </div>
35
+
36
+ <div class="widmark-field">
37
+ <span>{ui.hydration}</span>
38
+ <select id="widmark-hydration" class="widmark-select">
39
+ <option value="low">{ui.hydrationLow}</option>
40
+ <option value="normal" selected>{ui.hydrationNormal}</option>
41
+ <option value="high">{ui.hydrationHigh}</option>
42
+ </select>
43
+ </div>
44
+
45
+ <div class="widmark-field">
46
+ <span>{ui.stomachState}</span>
47
+ <select id="widmark-stomach" class="widmark-select">
48
+ <option value="empty">{ui.stomachEmpty}</option>
49
+ <option value="light" selected>{ui.stomachLight}</option>
50
+ <option value="full">{ui.stomachFull}</option>
51
+ </select>
52
+ </div>
53
+
54
+ <h3 class="widmark-section-title">
55
+ <span>{ui.drinksTimeline}</span>
56
+ </h3>
57
+
58
+ <div class="widmark-quick-drinks">
59
+ <button type="button" class="widmark-quick-btn" data-preset="beer">{(ui.beer || '').split(' ')[0]}</button>
60
+ <button type="button" class="widmark-quick-btn" data-preset="wine">{(ui.wine || '').split(' ')[0]}</button>
61
+ <button type="button" class="widmark-quick-btn" data-preset="spirits">{(ui.spirits || '').split(' ')[0]}</button>
62
+ </div>
63
+
64
+ <div class="widmark-drink-form-grid">
65
+ <div class="widmark-field">
66
+ <span>{ui.drinkVolume}</span>
67
+ <input type="number" id="widmark-drink-vol" class="widmark-input" value="330" min="10" max="2000" />
68
+ </div>
69
+ <div class="widmark-field">
70
+ <span>{ui.drinkAbv}</span>
71
+ <input type="number" id="widmark-drink-abv" class="widmark-input" value="5" min="0.1" max="100" step="0.1" />
72
+ </div>
73
+ <div class="widmark-field">
74
+ <span>{ui.drinkTime}</span>
75
+ <input type="number" id="widmark-drink-time" class="widmark-input" value="0" min="0" max="11.9" step="0.1" />
76
+ </div>
77
+ <button type="button" id="widmark-add-drink" class="widmark-btn-primary">
78
+ <span>{ui.addDrink}</span>
79
+ </button>
80
+ </div>
81
+
82
+ <h3 class="widmark-section-title">
83
+ <span>{ui.drinksList}</span>
84
+ </h3>
85
+ <div id="widmark-drinks-container" class="widmark-drinks-list"></div>
86
+ </aside>
87
+
88
+ <main class="widmark-dashboard">
89
+ <div class="widmark-metrics-grid">
90
+ <div class="widmark-metric-card" id="card-current-bac">
91
+ <span class="widmark-metric-label">{ui.currentBac}</span>
92
+ <span class="widmark-metric-value" id="val-current-bac">0.000<span class="widmark-metric-unit">g/L</span></span>
93
+ </div>
94
+ <div class="widmark-metric-card">
95
+ <span class="widmark-metric-label">{ui.peakBac}</span>
96
+ <span class="widmark-metric-value" id="val-peak-bac">0.000<span class="widmark-metric-unit">g/L</span></span>
97
+ </div>
98
+ <div class="widmark-metric-card">
99
+ <span class="widmark-metric-label">{ui.timeToSober}</span>
100
+ <span class="widmark-metric-value" id="val-sober-time">-</span>
101
+ </div>
102
+ <div class="widmark-metric-card" id="card-driving-status">
103
+ <span class="widmark-metric-label">{ui.drivingLimitAlert}</span>
104
+ <span class="widmark-metric-value" id="val-driving-status" style="font-size: 1.15rem; font-weight: 700;">-</span>
105
+ </div>
106
+ </div>
107
+
108
+ <div class="widmark-visual-container">
109
+ <div class="widmark-chart-wrapper">
110
+ <svg class="widmark-chart-svg" viewBox="0 0 800 280" preserveAspectRatio="none">
111
+ <defs>
112
+ <linearGradient id="chart-gradient" x1="0" y1="0" x2="0" y2="1">
113
+ <stop offset="0%" stop-color="#38bdf8" />
114
+ <stop offset="100%" stop-color="#38bdf8" stop-opacity="0" />
115
+ </linearGradient>
116
+ </defs>
117
+
118
+ <line class="widmark-grid-line" x1="50" y1="20" x2="780" y2="20" />
119
+ <line class="widmark-grid-line" x1="50" y1="85" x2="780" y2="85" />
120
+ <line class="widmark-grid-line" x1="50" y1="150" x2="780" y2="150" />
121
+ <line class="widmark-grid-line" x1="50" y1="215" x2="780" y2="215" />
122
+ <line class="widmark-grid-line" x1="50" y1="240" x2="780" y2="240" />
123
+
124
+ <text class="widmark-grid-text" x="15" y="24">1.5</text>
125
+ <text class="widmark-grid-text" x="15" y="89">1.0</text>
126
+ <text class="widmark-grid-text" x="15" y="154">0.5</text>
127
+ <text class="widmark-grid-text" x="15" y="219">0.1</text>
128
+ <text class="widmark-grid-text" x="20" y="244">0.0</text>
129
+
130
+ <line class="widmark-limit-line" x1="50" y1="150" x2="780" y2="150" />
131
+
132
+ <line class="widmark-grid-line" x1="50" y1="20" x2="50" y2="240" />
133
+ <line class="widmark-grid-line" x1="171.6" y1="20" x2="171.6" y2="240" />
134
+ <line class="widmark-grid-line" x1="293.3" y1="20" x2="293.3" y2="240" />
135
+ <line class="widmark-grid-line" x1="415" y1="20" x2="415" y2="240" />
136
+ <line class="widmark-grid-line" x1="536.6" y1="20" x2="536.6" y2="240" />
137
+ <line class="widmark-grid-line" x1="658.3" y1="20" x2="658.3" y2="240" />
138
+ <line class="widmark-grid-line" x1="780" y1="20" x2="780" y2="240" />
139
+
140
+ <text class="widmark-grid-text" x="45" y="260">0h</text>
141
+ <text class="widmark-grid-text" x="166" y="260">2h</text>
142
+ <text class="widmark-grid-text" x="288" y="260">4h</text>
143
+ <text class="widmark-grid-text" x="410" y="260">6h</text>
144
+ <text class="widmark-grid-text" x="531" y="260">8h</text>
145
+ <text class="widmark-grid-text" x="653" y="260">10h</text>
146
+ <text class="widmark-grid-text" x="775" y="260">12h</text>
147
+
148
+ <path id="widmark-chart-path" class="widmark-curve-path" d="" />
149
+ <path id="widmark-chart-fill" class="widmark-curve-fill" d="" />
150
+ </svg>
151
+
152
+ <div id="widmark-chart-overlay" class="widmark-no-drinks">
153
+ <span>{ui.noDrinksText}</span>
154
+ </div>
155
+ </div>
156
+
157
+ <div class="widmark-disclaimer">
158
+ <strong>{ui.legalLimitText} (0.5 g/L)</strong>
159
+ <span>{ui.noteDisclaimer}</span>
160
+ <span style="font-size: 0.75rem; opacity: 0.8;">{ui.noteWidmark}</span>
161
+ </div>
162
+ </div>
163
+ </main>
164
+ </div>
165
+
166
+ <script id="widmark-ui-data" type="application/json" set:html={JSON.stringify(ui)}></script>
167
+
168
+ <script>
169
+ import { WidmarkEngine } from './logic';
170
+ import type { DrinkInput, SubjectProfile } from './logic';
171
+
172
+ const uiDataEl = document.getElementById('widmark-ui-data');
173
+ const ui = JSON.parse(uiDataEl?.textContent || '{}') as Record<string, string>;
174
+
175
+ const weightInput = document.getElementById('widmark-weight') as HTMLInputElement;
176
+ const hydrationInput = document.getElementById('widmark-hydration') as HTMLSelectElement;
177
+ const stomachInput = document.getElementById('widmark-stomach') as HTMLSelectElement;
178
+ const drinkVolInput = document.getElementById('widmark-drink-vol') as HTMLInputElement;
179
+ const drinkAbvInput = document.getElementById('widmark-drink-abv') as HTMLInputElement;
180
+ const drinkTimeInput = document.getElementById('widmark-drink-time') as HTMLInputElement;
181
+ const addDrinkBtn = document.getElementById('widmark-add-drink');
182
+ const drinksContainer = document.getElementById('widmark-drinks-container');
183
+
184
+ const valCurrentBac = document.getElementById('val-current-bac');
185
+ const valPeakBac = document.getElementById('val-peak-bac');
186
+ const valSoberTime = document.getElementById('val-sober-time');
187
+ const valDrivingStatus = document.getElementById('val-driving-status');
188
+
189
+ const cardCurrentBac = document.getElementById('card-current-bac');
190
+ const cardDrivingStatus = document.getElementById('card-driving-status');
191
+
192
+ const chartPath = document.getElementById('widmark-chart-path') as SVGPathElement | null;
193
+ const chartFill = document.getElementById('widmark-chart-fill') as SVGPathElement | null;
194
+ const chartOverlay = document.getElementById('widmark-chart-overlay');
195
+
196
+ const storageKeyProfile = 'widmark:profile';
197
+ const storageKeyDrinks = 'widmark:drinks';
198
+
199
+ let activeDrinks: DrinkInput[] = [];
200
+
201
+ function loadSavedState() {
202
+ try {
203
+ const savedProfile = localStorage.getItem(storageKeyProfile);
204
+ const savedDrinks = localStorage.getItem(storageKeyDrinks);
205
+
206
+ if (savedProfile) {
207
+ const profile = JSON.parse(savedProfile);
208
+ weightInput.value = String(profile.weight);
209
+ hydrationInput.value = profile.hydration;
210
+ stomachInput.value = profile.stomach;
211
+ const sexRadio = document.querySelector(`input[name="widmark-sex"][value="${profile.sex}"]`) as HTMLInputElement;
212
+ if (sexRadio) sexRadio.checked = true;
213
+ }
214
+
215
+ if (savedDrinks) {
216
+ activeDrinks = JSON.parse(savedDrinks);
217
+ }
218
+ } catch {
219
+ localStorage.removeItem(storageKeyProfile);
220
+ localStorage.removeItem(storageKeyDrinks);
221
+ }
222
+ }
223
+
224
+ function saveCurrentState() {
225
+ const profile: SubjectProfile = {
226
+ weight: Number(weightInput.value),
227
+ sex: (document.querySelector('input[name="widmark-sex"]:checked') as HTMLInputElement)?.value as 'male' | 'female',
228
+ hydration: hydrationInput.value as 'low' | 'normal' | 'high',
229
+ stomach: stomachInput.value as 'empty' | 'light' | 'full',
230
+ };
231
+ localStorage.setItem(storageKeyProfile, JSON.stringify(profile));
232
+ localStorage.setItem(storageKeyDrinks, JSON.stringify(activeDrinks));
233
+ }
234
+
235
+ function getProfile(): SubjectProfile {
236
+ return {
237
+ weight: Number(weightInput.value) || 80,
238
+ sex: (document.querySelector('input[name="widmark-sex"]:checked') as HTMLInputElement)?.value as 'male' | 'female',
239
+ hydration: hydrationInput.value as 'low' | 'normal' | 'high',
240
+ stomach: stomachInput.value as 'empty' | 'light' | 'full',
241
+ };
242
+ }
243
+
244
+ function setMetricCardClass(card: HTMLElement | null, status: 'safe' | 'warning' | 'danger') {
245
+ if (!card) return;
246
+ card.className = `widmark-metric-card alert-${status}`;
247
+ }
248
+
249
+ function clearBacValues() {
250
+ if (valCurrentBac) valCurrentBac.innerHTML = `0.000<span class="widmark-metric-unit">g/L</span>`;
251
+ if (valPeakBac) valPeakBac.innerHTML = `0.000<span class="widmark-metric-unit">g/L</span>`;
252
+ if (valSoberTime) valSoberTime.textContent = ui.sober ?? null;
253
+ if (valDrivingStatus) valDrivingStatus.textContent = ui.safeLimit ?? null;
254
+ }
255
+
256
+ function clearChartPaths() {
257
+ if (chartPath) chartPath.setAttribute('d', '');
258
+ if (chartFill) chartFill.setAttribute('d', '');
259
+ if (chartOverlay) chartOverlay.style.display = 'flex';
260
+ }
261
+
262
+ function renderEmptyState() {
263
+ clearBacValues();
264
+ setMetricCardClass(cardCurrentBac, 'safe');
265
+ setMetricCardClass(cardDrivingStatus, 'safe');
266
+ clearChartPaths();
267
+ }
268
+
269
+ function renderStatusText(currentBacVal: number) {
270
+ if (!valDrivingStatus) return;
271
+ if (currentBacVal >= 0.5) {
272
+ valDrivingStatus.textContent = ui.dangerLimit ?? null;
273
+ setMetricCardClass(cardCurrentBac, 'danger');
274
+ setMetricCardClass(cardDrivingStatus, 'danger');
275
+ } else if (currentBacVal >= 0.2) {
276
+ valDrivingStatus.textContent = ui.warningLimit ?? null;
277
+ setMetricCardClass(cardCurrentBac, 'warning');
278
+ setMetricCardClass(cardDrivingStatus, 'warning');
279
+ } else {
280
+ valDrivingStatus.textContent = ui.safeLimit ?? null;
281
+ setMetricCardClass(cardCurrentBac, 'safe');
282
+ setMetricCardClass(cardDrivingStatus, 'safe');
283
+ }
284
+ }
285
+
286
+ function renderSoberTime(soberTime: number) {
287
+ if (!valSoberTime) return;
288
+ if (soberTime === -1) {
289
+ valSoberTime.textContent = ui.sober ?? null;
290
+ } else if (soberTime === -2) {
291
+ valSoberTime.textContent = ui.neverSober ?? null;
292
+ } else {
293
+ valSoberTime.textContent = `${soberTime.toFixed(1)} h`;
294
+ }
295
+ }
296
+
297
+ function renderMetrics(result: ReturnType<typeof WidmarkEngine.simulate>) {
298
+ const currentBacVal = result.datapoints[0]?.bac || 0;
299
+ if (valCurrentBac) {
300
+ valCurrentBac.innerHTML = `${currentBacVal.toFixed(3)}<span class="widmark-metric-unit">g/L</span>`;
301
+ }
302
+ if (valPeakBac) {
303
+ valPeakBac.innerHTML = `${result.peakBac.toFixed(3)}<span class="widmark-metric-unit">g/L</span>`;
304
+ }
305
+ renderSoberTime(result.soberTime);
306
+ renderStatusText(currentBacVal);
307
+ }
308
+
309
+ function updateChartGrid(maxBacGraph: number, offsetY: number, height: number) {
310
+ const gridLinesY = [1.5, 1.0, 0.5, 0.1, 0.0];
311
+ const gridTexts = document.querySelectorAll('.widmark-grid-text');
312
+ gridLinesY.forEach((val, idx) => {
313
+ const y = offsetY + height - (val / maxBacGraph) * height;
314
+ const line = document.querySelectorAll('.widmark-grid-line')[idx];
315
+ if (line) {
316
+ line.setAttribute('y1', String(y));
317
+ line.setAttribute('y2', String(y));
318
+ }
319
+ const text = gridTexts[idx];
320
+ if (text) {
321
+ text.setAttribute('y', String(y + 4));
322
+ text.textContent = val.toFixed(1);
323
+ }
324
+ });
325
+ }
326
+
327
+ function updateChart(result: ReturnType<typeof WidmarkEngine.simulate>) {
328
+ const maxBacGraph = Math.max(1.6, result.peakBac * 1.15);
329
+ const width = 730;
330
+ const height = 220;
331
+ const offsetX = 50;
332
+ const offsetY = 20;
333
+ let pathD = '';
334
+ let fillD = `M ${offsetX} ${offsetY + height} `;
335
+
336
+ for (let i = 0; i < result.datapoints.length; i++) {
337
+ const dp = result.datapoints[i]!;
338
+ const x = offsetX + (dp.time / 12.0) * width;
339
+ const y = offsetY + height - (dp.bac / maxBacGraph) * height;
340
+ if (i === 0) {
341
+ pathD += `M ${x} ${y} `;
342
+ } else {
343
+ pathD += `L ${x} ${y} `;
344
+ }
345
+ fillD += `L ${x} ${y} `;
346
+ }
347
+
348
+ fillD += `L ${offsetX + width} ${offsetY + height} Z`;
349
+ if (chartPath) chartPath.setAttribute('d', pathD);
350
+ if (chartFill) chartFill.setAttribute('d', fillD);
351
+
352
+ const limitY = offsetY + height - (0.5 / maxBacGraph) * height;
353
+ const limitLine = document.querySelector('.widmark-limit-line');
354
+ if (limitLine) {
355
+ limitLine.setAttribute('y1', String(limitY));
356
+ limitLine.setAttribute('y2', String(limitY));
357
+ }
358
+ updateChartGrid(maxBacGraph, offsetY, height);
359
+ }
360
+
361
+ function updateSimulation() {
362
+ const profile = getProfile();
363
+ if (activeDrinks.length === 0) {
364
+ renderEmptyState();
365
+ renderDrinksList();
366
+ saveCurrentState();
367
+ return;
368
+ }
369
+
370
+ if (chartOverlay) chartOverlay.style.display = 'none';
371
+
372
+ const result = WidmarkEngine.simulate(profile, activeDrinks);
373
+ renderMetrics(result);
374
+ updateChart(result);
375
+ renderDrinksList();
376
+ saveCurrentState();
377
+ }
378
+
379
+ function renderDrinksList() {
380
+ if (!drinksContainer) return;
381
+ drinksContainer.innerHTML = '';
382
+ activeDrinks.forEach((drink, index) => {
383
+ const item = document.createElement('div');
384
+ item.className = 'widmark-drink-item';
385
+
386
+ const details = document.createElement('div');
387
+ details.className = 'widmark-drink-details';
388
+
389
+ const title = document.createElement('span');
390
+ title.className = 'widmark-drink-title';
391
+ title.textContent = `${drink.volume}ml @ ${drink.abv}%`;
392
+
393
+ const meta = document.createElement('span');
394
+ meta.className = 'widmark-drink-meta';
395
+ meta.textContent = `Hour ${drink.time.toFixed(1)}`;
396
+
397
+ details.appendChild(title);
398
+ details.appendChild(meta);
399
+
400
+ const removeBtn = document.createElement('button');
401
+ removeBtn.type = 'button';
402
+ removeBtn.className = 'widmark-btn-remove';
403
+ removeBtn.textContent = ui.remove ?? null;
404
+ removeBtn.addEventListener('click', () => {
405
+ activeDrinks.splice(index, 1);
406
+ updateSimulation();
407
+ });
408
+
409
+ item.appendChild(details);
410
+ item.appendChild(removeBtn);
411
+ drinksContainer.appendChild(item);
412
+ });
413
+ }
414
+
415
+ addDrinkBtn?.addEventListener('click', () => {
416
+ const volume = Number(drinkVolInput.value);
417
+ const abv = Number(drinkAbvInput.value);
418
+ const time = Number(drinkTimeInput.value);
419
+
420
+ if (volume > 0 && abv >= 0 && time >= 0) {
421
+ activeDrinks.push({ volume, abv, time });
422
+ activeDrinks.sort((a, b) => a.time - b.time);
423
+ updateSimulation();
424
+ }
425
+ });
426
+
427
+ document.querySelectorAll('[data-preset]').forEach((btn) => {
428
+ btn.addEventListener('click', () => {
429
+ const preset = btn.getAttribute('data-preset');
430
+ if (preset === 'beer') {
431
+ drinkVolInput.value = '330';
432
+ drinkAbvInput.value = '5.0';
433
+ } else if (preset === 'wine') {
434
+ drinkVolInput.value = '150';
435
+ drinkAbvInput.value = '12.0';
436
+ } else if (preset === 'spirits') {
437
+ drinkVolInput.value = '40';
438
+ drinkAbvInput.value = '40.0';
439
+ }
440
+ });
441
+ });
442
+
443
+ [weightInput, hydrationInput, stomachInput].forEach((input) => {
444
+ input.addEventListener('change', updateSimulation);
445
+ });
446
+
447
+ document.querySelectorAll('input[name="widmark-sex"]').forEach((radio) => {
448
+ radio.addEventListener('change', updateSimulation);
449
+ });
450
+
451
+ loadSavedState();
452
+ updateSimulation();
453
+ </script>
@@ -0,0 +1,32 @@
1
+ import type { ScienceToolEntry, ToolLocaleContent } from '../../types';
2
+
3
+ export interface WidmarkAlcoholSimulatorUI {
4
+ [key: string]: string;
5
+ }
6
+
7
+ export type WidmarkAlcoholSimulatorLocaleContent = ToolLocaleContent<WidmarkAlcoholSimulatorUI>;
8
+
9
+ export const widmarkAlcoholSimulator: ScienceToolEntry<WidmarkAlcoholSimulatorUI> = {
10
+ id: 'widmark-alcohol-simulator',
11
+ icons: {
12
+ bg: 'mdi:glass-cocktail',
13
+ fg: 'mdi:chart-bell-curve',
14
+ },
15
+ i18n: {
16
+ de: () => import('./i18n/de').then((m) => m.content),
17
+ en: () => import('./i18n/en').then((m) => m.content),
18
+ es: () => import('./i18n/es').then((m) => m.content),
19
+ fr: () => import('./i18n/fr').then((m) => m.content),
20
+ id: () => import('./i18n/id').then((m) => m.content),
21
+ it: () => import('./i18n/it').then((m) => m.content),
22
+ ja: () => import('./i18n/ja').then((m) => m.content),
23
+ ko: () => import('./i18n/ko').then((m) => m.content),
24
+ nl: () => import('./i18n/nl').then((m) => m.content),
25
+ pl: () => import('./i18n/pl').then((m) => m.content),
26
+ pt: () => import('./i18n/pt').then((m) => m.content),
27
+ ru: () => import('./i18n/ru').then((m) => m.content),
28
+ sv: () => import('./i18n/sv').then((m) => m.content),
29
+ tr: () => import('./i18n/tr').then((m) => m.content),
30
+ zh: () => import('./i18n/zh').then((m) => m.content),
31
+ },
32
+ };
@@ -0,0 +1,193 @@
1
+ import { bibliography } from '../bibliography';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+
4
+ const slug = 'widmark-blutalkohol-simulator';
5
+ const title = 'Widmark Blutalkohol Simulator';
6
+ const description = 'Simulieren Sie die Aufnahme, Verteilung und den Abbau von Alkohol im menschlichen Körper mithilfe der Widmark-Formel und Nullter-Ordnung-Abbaukinetik.';
7
+
8
+ const howTo = [
9
+ {
10
+ name: 'Körperliche Parameter einstellen',
11
+ text: 'Stellen Sie Körpergewicht, biologisches Geschlecht und Hydrationszustand ein. Diese Variablen bestimmen den Widmark-Faktor (r), der das Verteilungsvolumen bestimmt.',
12
+ },
13
+ {
14
+ name: 'Mageninhalt festlegen',
15
+ text: 'Wählen Sie zwischen leerem Magen, einer leichten Mahlzeit oder einer vollen Mahlzeit. Dies beeinflusst die Absorptionsgeschwindigkeit (ka) und flacht die Blutalkoholkurve ab.',
16
+ },
17
+ {
18
+ name: 'Getränke hinzufügen',
19
+ text: 'Geben Sie Volumen, Alkoholgehalt (Vol.-%) und die relative Zeit des Konsums ein, um den Kurvenverlauf zu simulieren.',
20
+ },
21
+ {
22
+ name: 'Verlauf analysieren',
23
+ text: 'Analysieren Sie die Kurve und den Abbau der Blutalkoholkonzentration (BAK) über einen Zeitraum von 12 Stunden.',
24
+ },
25
+ ];
26
+
27
+ const faq = [
28
+ {
29
+ key: 'faq-1',
30
+ question: 'Was ist die Widmark-Formel und wie wird sie in der Forensik angewendet?',
31
+ answer: 'Die Widmark-Formel ist ein mathematisches Modell, das 1932 von Erik M. P. Widmark entwickelt wurde. Sie schätzt die Blutalkoholkonzentration basierend auf der konsumierten Alkoholmasse, dem Körpergewicht, dem Verteilungsfaktor (r) und einer linearen Eliminierungsrate. Forensische Toxikologen nutzen diese Formel für Rückrechnungen, um die BAK zum Zeitpunkt eines Unfalls zu bestimmen.',
32
+ },
33
+ {
34
+ key: 'faq-2',
35
+ question: 'Wie wirkt sich Nahrung im Magen auf die BAK-Kurve aus?',
36
+ answer: 'Nahrung im Magen verzögert die Magenentleerung, wodurch der Alkohol langsamer in den Dünndarm gelangt. Da dort die Hauptaufnahme stattfindet, führt eine Verzögerung zu einer reduzierten Absorptionsrate (ka). Die Kurve flacht ab, und der maximale BAK-Wert sinkt deutlich.',
37
+ },
38
+ {
39
+ key: 'faq-3',
40
+ question: 'Warum erfolgt der Alkoholabbau nach einer Kinetik nullter Ordnung?',
41
+ answer: 'Im Gegensatz zu den meisten Medikamenten wird Alkohol nach einer Kinetik nullter Ordnung abgebaut, da das hauptsächliche Enzym (Alkoholdehydrogenase - ADH) bereits bei sehr niedrigen Werten (ca. 0,02 g/L) vollständig gesättigt ist. Die Leber baut daher eine konstante Menge Alkohol pro Stunde ab, unabhängig von der Konzentration im Blut.',
42
+ },
43
+ {
44
+ key: 'faq-4',
45
+ question: 'Welche biologischen Faktoren beeinflussen den Widmark-Faktor r?',
46
+ answer: 'Der Widmark-Faktor r beschreibt das Verhältnis von Körperwasser zum Gesamtgewicht. Da Ethanol hydrophil und lipophob ist, verteilt es sich nur im Körperwasser. Muskelgewebe enthält viel Wasser, Fettgewebe hingegen kaum. Menschen mit höherem Fettanteil oder Dehydratation haben daher ein geringeres Verteilungsvolumen, was zu einer höheren BAK führt.',
47
+ },
48
+ {
49
+ key: 'faq-5',
50
+ question: 'Kann ich diesen Simulator nutzen, um meine Fahrtauglichkeit rechtlich nachzuweisen?',
51
+ answer: 'Nein. Dieser Simulator dient ausschließlich Bildungszwecken. Reale Faktoren wie Enzymgenetik, Lebergesundheit, Medikamente und Mageninhalt variieren stark. Verlassen Sie sich niemals auf mathematische Modelle, um Ihre Fahrtauglichkeit zu bestimmen.',
52
+ },
53
+ ];
54
+
55
+ export const content: ToolLocaleContent = {
56
+ slug,
57
+ title,
58
+ description,
59
+ ui: {
60
+ subjectProfile: 'Profil',
61
+ weight: 'Gewicht (kg)',
62
+ sex: 'Biologisches Geschlecht',
63
+ male: 'Männlich',
64
+ female: 'Weiblich',
65
+ hydration: 'Hydrationszustand',
66
+ hydrationLow: 'Niedrig',
67
+ hydrationNormal: 'Normal',
68
+ hydrationHigh: 'Hoch',
69
+ stomachState: 'Mageninhalt',
70
+ stomachEmpty: 'Leer (Schnelle Aufnahme)',
71
+ stomachLight: 'Leichte Mahlzeit (Mittlere Aufnahme)',
72
+ stomachFull: 'Volle Mahlzeit (Langsame Aufnahme)',
73
+ drinksTimeline: 'Konsum-Verlauf',
74
+ addDrink: 'Getränk hinzufügen',
75
+ drinkVolume: 'Volumen (ml)',
76
+ drinkAbv: 'Alkoholgehalt (% Vol)',
77
+ drinkTime: 'Zeit (Stunde)',
78
+ drinksList: 'Konsumierte Getränke',
79
+ remove: 'Entfernen',
80
+ beer: 'Bier (330ml, 5%)',
81
+ wine: 'Wein (150ml, 12%)',
82
+ spirits: 'Spirituosen (40ml, 40%)',
83
+ peakBac: 'Max. BAK',
84
+ timeToSober: 'Zeit bis nüchtern',
85
+ sober: 'Nüchtern',
86
+ currentBac: 'Aktuelle BAK',
87
+ drivingLimitAlert: 'Fahrberechtigung',
88
+ safeLimit: 'Unter Grenzwert',
89
+ warningLimit: 'Warnung überschritten',
90
+ dangerLimit: 'Grenzwert überschritten',
91
+ graphTitle: 'Blutalkoholkonzentration über 12 Stunden',
92
+ timeAxisLabel: 'Zeit (Stunden)',
93
+ bacAxisLabel: 'Promille (g/L)',
94
+ legalLimitText: 'Standard-Grenzwert',
95
+ noDrinksText: 'Fügen Sie Getränke hinzu, um die Kurve zu simulieren.',
96
+ hours: 'Stunden',
97
+ soberStatus: 'Nüchtern in {time} Std.',
98
+ neverSober: 'Übersteigt 12 Std.',
99
+ noteDisclaimer: 'Nur für Bildungszwecke. Setzen Sie sich nach dem Konsum nicht ans Steuer.',
100
+ noteWidmark: 'Basiert auf der Widmark-Gleichung und konstantem Leberabbau.',
101
+ },
102
+ seo: [
103
+ {
104
+ type: 'title',
105
+ text: 'Forensische Toxikologie und Pharmakokinetik von Ethanol',
106
+ level: 2,
107
+ },
108
+ {
109
+ type: 'paragraph',
110
+ html: 'Das Verständnis von <strong>Aufnahme, Verteilung und Abbau von Ethanol</strong> ist eine grundlegende Anforderung in der Rechtsmedizin und der Rekonstruktion von Verkehrsunfällen. Nach dem Konsum gelangt der Alkohol über die Speiseröhre in den Magen, wo ein kleiner Teil direkt resorbiert wird. Der Großteil der Resorption findet jedoch im Dünndarm statt. Die Entleerungsgeschwindigkeit des Magens ist der limitierende Faktor für die Alkoholresorption, weshalb der Konsum auf vollen Magen den Anstieg der BAK-Kurve deutlich verzögert.',
111
+ },
112
+ {
113
+ type: 'paragraph',
114
+ html: 'Dieser interaktive Simulator implementiert die klassischen Berechnungen von Erik M. P. Widmark in Kombination mit einer kontinuierlichen Kompartmentsimulation über 12 Stunden. Durch die Anpassung physiologischer Parameter können Anwender die mathematische Akkumulation und den linearen Abbau im zeitlichen Verlauf nachvollziehen.',
115
+ },
116
+ {
117
+ type: 'title',
118
+ text: 'Mathematische Herleitung der Widmark-Formel',
119
+ level: 3,
120
+ },
121
+ {
122
+ type: 'paragraph',
123
+ html: 'Die fundamentale Widmark-Formel berechnet die theoretische Blutalkoholkonzentration unter Annahme einer instantanen Absorption: <strong>BAK = (A / (W * r)) - (&beta; * t)</strong>. Dabei steht <em>A</em> für die Masse an reinem Ethanol in Gramm, <em>W</em> für das Körpergewicht in Kilogramm und <em>r</em> für den Widmark-Verteilungsfaktor. Die Variable <em>&beta;</em> stellt die stündliche Abbaurate dar, während <em>t</em> die verstrichene Zeit beschreibt.',
124
+ },
125
+ {
126
+ type: 'paragraph',
127
+ html: 'Zur Umrechnung des Getränkevolumens in Gramm reinen Alkohols multiplizieren wir das Volumen (ml) mit dem Alkoholgehalt (Vol.-%) und der Dichte von Ethanol (ca. 0,8 g/ml). Ein 330-ml-Bier mit 5% enthält somit 13,2 g reines Ethanol. Dieser Wert wird durch das Gesamtkörperwasser geteilt, welches vom Geschlecht (Standardwert Männer: 0,68; Frauen: 0,55) und dem Hydrationsniveau beeinflusst wird.',
128
+ },
129
+ {
130
+ type: 'title',
131
+ text: 'Physiologische Variablen und das Verteilungsvolumen',
132
+ level: 3,
133
+ },
134
+ {
135
+ type: 'paragraph',
136
+ html: 'Das Verteilungsvolumen von Ethanol hängt maßgeblich von der Körperzusammensetzung ab. Da Ethanol hydrophil und lipophob ist, verteilt es sich ausschließlich im Körperwasser. Muskelgewebe besteht zu etwa 75% aus Wasser, wohingegen Fettgewebe kaum Wasser enthält. Ein muskulöser Mensch weist daher einen größeren Verteilungsfaktor r auf und erreicht bei gleicher Alkoholmenge eine niedrigere BAK als eine Person gleichen Gewichts mit höherem Körperfettanteil. Dehydration verringert das Körperwasser und steigert somit die maximale Konzentration.',
137
+ },
138
+ {
139
+ type: 'table',
140
+ headers: ['Physiologische Variable', 'Biologischer Mechanismus', 'Auswirkung auf Kinetik', 'Forensische Bedeutung'],
141
+ rows: [
142
+ ['Körpergewicht', 'Bestimmt das Gesamtvolumen des Körperwassers.', 'Umgekehrt proportional zur maximalen BAK.', 'Bildet die Basis für forensische Rückrechnungen.'],
143
+ ['Geschlecht', 'Beeinflusst das typische Muskel-Fett-Verhältnis.', 'Faktor r ist bei Frauen niedriger (0,55), was die BAK erhöht.', 'Erklärt die höhere biologische Empfindlichkeit von Frauen.'],
144
+ ['Hydrationszustand', 'Verändert das verfügbare freie Wasser im Körper.', 'Dehydration senkt r um 0,05, wodurch die Kurve steigt.', 'Erlaubt Korrekturen bei starker körperlicher Belastung.'],
145
+ ['Mageninhalt', 'Nahrung verzögert die Magenentleerung.', 'Senkt die Absorptionsrate ka, flacht die Kurve ab.', 'Erklärt unerwartet niedrige Werte nach Mahlzeiten.'],
146
+ ],
147
+ },
148
+ {
149
+ type: 'title',
150
+ text: 'Eliminationskinetik: Nullter Ordnung Abbau in der Leber',
151
+ level: 3,
152
+ },
153
+ {
154
+ type: 'paragraph',
155
+ html: 'Nach dem Eintritt in den Blutkreislauf wird Alkohol primär in der Leber durch das Enzym Alkoholdehydrogenase (ADH) abgebaut. Da dieses Enzym bereits bei sehr geringen Konzentrationen gesättigt ist, verläuft der Abbau nach einer Kinetik nullter Ordnung. Dies bedeutet, dass pro Zeiteinheit eine konstante Alkoholmenge eliminiert wird. Der forensisch etablierte Mittelwert (&beta;<sub>60</sub>) beträgt 0,15 g/L pro Stunde, was dem Abbau von etwa 10 g reinem Alkohol pro Stunde entspricht. Dieser lineare Abbau erzeugt den geradlinigen Abfall der Kurve.',
156
+ },
157
+ ],
158
+ faq,
159
+ bibliography,
160
+ howTo,
161
+ schemas: [
162
+ {
163
+ '@context': 'https://schema.org',
164
+ '@type': 'SoftwareApplication',
165
+ name: title,
166
+ description,
167
+ applicationCategory: 'ForensicApplication',
168
+ operatingSystem: 'Any',
169
+ },
170
+ {
171
+ '@context': 'https://schema.org',
172
+ '@type': 'FAQPage',
173
+ mainEntity: faq.map((item) => ({
174
+ '@type': 'Question',
175
+ name: item.question,
176
+ acceptedAnswer: {
177
+ '@type': 'Answer',
178
+ text: item.answer,
179
+ },
180
+ })),
181
+ },
182
+ {
183
+ '@context': 'https://schema.org',
184
+ '@type': 'HowTo',
185
+ name: title,
186
+ step: howTo.map((step) => ({
187
+ '@type': 'HowToStep',
188
+ name: step.name,
189
+ text: step.text,
190
+ })),
191
+ },
192
+ ],
193
+ };