@jjlmoya/utils-health 1.31.0 → 1.33.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 (56) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +3 -1
  3. package/src/entries.ts +5 -1
  4. package/src/index.ts +2 -0
  5. package/src/tests/locale_completeness.test.ts +1 -1
  6. package/src/tests/tool_validation.test.ts +1 -1
  7. package/src/tool/meanArterialPressureCalculator/bibliography.astro +14 -0
  8. package/src/tool/meanArterialPressureCalculator/bibliography.ts +24 -0
  9. package/src/tool/meanArterialPressureCalculator/component.astro +224 -0
  10. package/src/tool/meanArterialPressureCalculator/entry.ts +27 -0
  11. package/src/tool/meanArterialPressureCalculator/i18n/de.ts +330 -0
  12. package/src/tool/meanArterialPressureCalculator/i18n/en.ts +330 -0
  13. package/src/tool/meanArterialPressureCalculator/i18n/es.ts +330 -0
  14. package/src/tool/meanArterialPressureCalculator/i18n/fr.ts +330 -0
  15. package/src/tool/meanArterialPressureCalculator/i18n/id.ts +330 -0
  16. package/src/tool/meanArterialPressureCalculator/i18n/it.ts +330 -0
  17. package/src/tool/meanArterialPressureCalculator/i18n/ja.ts +330 -0
  18. package/src/tool/meanArterialPressureCalculator/i18n/ko.ts +330 -0
  19. package/src/tool/meanArterialPressureCalculator/i18n/nl.ts +330 -0
  20. package/src/tool/meanArterialPressureCalculator/i18n/pl.ts +330 -0
  21. package/src/tool/meanArterialPressureCalculator/i18n/pt.ts +330 -0
  22. package/src/tool/meanArterialPressureCalculator/i18n/ru.ts +330 -0
  23. package/src/tool/meanArterialPressureCalculator/i18n/sv.ts +330 -0
  24. package/src/tool/meanArterialPressureCalculator/i18n/tr.ts +330 -0
  25. package/src/tool/meanArterialPressureCalculator/i18n/zh.ts +330 -0
  26. package/src/tool/meanArterialPressureCalculator/index.ts +12 -0
  27. package/src/tool/meanArterialPressureCalculator/logic.ts +44 -0
  28. package/src/tool/meanArterialPressureCalculator/mean-arterial-pressure-calculator.css +437 -0
  29. package/src/tool/meanArterialPressureCalculator/seo.astro +15 -0
  30. package/src/tool/meanArterialPressureCalculator/ui.ts +36 -0
  31. package/src/tool/parametricNoiseSynthesizer/bibliography.astro +14 -0
  32. package/src/tool/parametricNoiseSynthesizer/bibliography.ts +24 -0
  33. package/src/tool/parametricNoiseSynthesizer/client.ts +342 -0
  34. package/src/tool/parametricNoiseSynthesizer/component.astro +77 -0
  35. package/src/tool/parametricNoiseSynthesizer/entry.ts +27 -0
  36. package/src/tool/parametricNoiseSynthesizer/i18n/de.ts +295 -0
  37. package/src/tool/parametricNoiseSynthesizer/i18n/en.ts +295 -0
  38. package/src/tool/parametricNoiseSynthesizer/i18n/es.ts +295 -0
  39. package/src/tool/parametricNoiseSynthesizer/i18n/fr.ts +295 -0
  40. package/src/tool/parametricNoiseSynthesizer/i18n/id.ts +295 -0
  41. package/src/tool/parametricNoiseSynthesizer/i18n/it.ts +295 -0
  42. package/src/tool/parametricNoiseSynthesizer/i18n/ja.ts +295 -0
  43. package/src/tool/parametricNoiseSynthesizer/i18n/ko.ts +295 -0
  44. package/src/tool/parametricNoiseSynthesizer/i18n/nl.ts +295 -0
  45. package/src/tool/parametricNoiseSynthesizer/i18n/pl.ts +295 -0
  46. package/src/tool/parametricNoiseSynthesizer/i18n/pt.ts +295 -0
  47. package/src/tool/parametricNoiseSynthesizer/i18n/ru.ts +295 -0
  48. package/src/tool/parametricNoiseSynthesizer/i18n/sv.ts +295 -0
  49. package/src/tool/parametricNoiseSynthesizer/i18n/tr.ts +295 -0
  50. package/src/tool/parametricNoiseSynthesizer/i18n/zh.ts +295 -0
  51. package/src/tool/parametricNoiseSynthesizer/index.ts +9 -0
  52. package/src/tool/parametricNoiseSynthesizer/logic.ts +12 -0
  53. package/src/tool/parametricNoiseSynthesizer/parametric-pink-brown-noise-synthesizer.css +300 -0
  54. package/src/tool/parametricNoiseSynthesizer/seo.astro +15 -0
  55. package/src/tool/parametricNoiseSynthesizer/ui.ts +21 -0
  56. package/src/tools.ts +3 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-health",
3
- "version": "1.31.0",
3
+ "version": "1.33.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -7,6 +7,7 @@ import { waistHipRatioCalculator } from '../tool/waistHipRatioCalculator/entry';
7
7
  import { waistHeightRatioCalculator } from '../tool/waistHeightRatioCalculator/entry';
8
8
  import { daltonismSimulator } from '../tool/daltonismSimulator/entry';
9
9
  import { tinnitusReliever } from '../tool/tinnitusReliever/entry';
10
+ import { parametricNoiseSynthesizer } from '../tool/parametricNoiseSynthesizer/entry';
10
11
  import { breathingVisualizer } from '../tool/breathingVisualizer/entry';
11
12
  import { caffeineTracker } from '../tool/caffeineTracker/entry';
12
13
  import { caloricDeficitCalculator } from '../tool/caloricDeficitCalculator/entry';
@@ -23,6 +24,7 @@ import { leanBodyMassCalculator } from '../tool/leanBodyMassCalculator/entry';
23
24
  import { metabolicAgeCalculator } from '../tool/metabolicAgeCalculator/entry';
24
25
  import { bunCreatinineCalculator } from '../tool/bunCreatinineCalculator/entry';
25
26
  import { serumOsmolalityCalculator } from '../tool/serumOsmolalityCalculator/entry';
27
+ import { meanArterialPressureCalculator } from '../tool/meanArterialPressureCalculator/entry';
26
28
  import { correctedSodiumHyperglycemiaCalculator } from '../tool/correctedSodiumHyperglycemiaCalculator/entry';
27
29
  import { fractionalExcretionSodiumCalculator } from '../tool/fractionalExcretionSodiumCalculator/entry';
28
30
  import { targetHeightEstimator } from '../tool/targetHeightEstimator/entry';
@@ -30,7 +32,7 @@ import { correctedReticulocyteIndex } from '../tool/correctedReticulocyteIndex/e
30
32
 
31
33
  export const healthCategory: HealthCategoryEntry = {
32
34
  icon: 'mdi:heart-pulse',
33
- tools: [bmiCalculator, amputationAdjustedBmiCalculator, bodyAdiposityIndexCalculator, bodyFatPercentageCalculator, waistHipRatioCalculator, waistHeightRatioCalculator, leanBodyMassCalculator, metabolicAgeCalculator, caloricDeficitCalculator, dailyProteinCalculator, macronutrientCalculator, tdeeCalculator, bunCreatinineCalculator, serumOsmolalityCalculator, correctedSodiumHyperglycemiaCalculator, fractionalExcretionSodiumCalculator, correctedReticulocyteIndex, targetHeightEstimator, daltonismSimulator, tinnitusReliever, breathingVisualizer, caffeineTracker, waterPurifier, digestionStopwatch, binauralTuner, pelliRobsonTest, peripheralVisionTrainer, epworthSleepinessScale],
35
+ tools: [bmiCalculator, amputationAdjustedBmiCalculator, bodyAdiposityIndexCalculator, bodyFatPercentageCalculator, waistHipRatioCalculator, waistHeightRatioCalculator, leanBodyMassCalculator, metabolicAgeCalculator, caloricDeficitCalculator, dailyProteinCalculator, macronutrientCalculator, tdeeCalculator, bunCreatinineCalculator, serumOsmolalityCalculator, meanArterialPressureCalculator, correctedSodiumHyperglycemiaCalculator, fractionalExcretionSodiumCalculator, correctedReticulocyteIndex, targetHeightEstimator, daltonismSimulator, tinnitusReliever, parametricNoiseSynthesizer, breathingVisualizer, caffeineTracker, waterPurifier, digestionStopwatch, binauralTuner, pelliRobsonTest, peripheralVisionTrainer, epworthSleepinessScale],
34
36
  i18n: {
35
37
  en: () => import('./i18n/en').then((m) => m.content),
36
38
  es: () => import('./i18n/es').then((m) => m.content),
package/src/entries.ts CHANGED
@@ -20,6 +20,7 @@ export { macronutrientCalculator } from './tool/macronutrientCalculator/entry';
20
20
  export { tdeeCalculator } from './tool/tdeeCalculator/entry';
21
21
  export { bunCreatinineCalculator } from './tool/bunCreatinineCalculator/entry';
22
22
  export { serumOsmolalityCalculator } from './tool/serumOsmolalityCalculator/entry';
23
+ export { meanArterialPressureCalculator } from './tool/meanArterialPressureCalculator/entry';
23
24
  export { correctedSodiumHyperglycemiaCalculator } from './tool/correctedSodiumHyperglycemiaCalculator/entry';
24
25
  export { fractionalExcretionSodiumCalculator } from './tool/fractionalExcretionSodiumCalculator/entry';
25
26
  export { targetHeightEstimator } from './tool/targetHeightEstimator/entry';
@@ -29,6 +30,7 @@ export { peripheralVisionTrainer } from './tool/peripheralVisionTrainer/entry';
29
30
  export { readingDistanceCalculator } from './tool/readingDistanceCalculator/entry';
30
31
  export { screenDecompressionTime } from './tool/screenDecompressionTime/entry';
31
32
  export { tinnitusReliever } from './tool/tinnitusReliever/entry';
33
+ export { parametricNoiseSynthesizer } from './tool/parametricNoiseSynthesizer/entry';
32
34
  export { ubeCalculator } from './tool/ubeCalculator/entry';
33
35
  export { waterPurifier } from './tool/waterPurifier/entry';
34
36
  export { healthCategory } from './category';
@@ -54,6 +56,7 @@ import { macronutrientCalculator } from './tool/macronutrientCalculator/entry';
54
56
  import { tdeeCalculator } from './tool/tdeeCalculator/entry';
55
57
  import { bunCreatinineCalculator } from './tool/bunCreatinineCalculator/entry';
56
58
  import { serumOsmolalityCalculator } from './tool/serumOsmolalityCalculator/entry';
59
+ import { meanArterialPressureCalculator } from './tool/meanArterialPressureCalculator/entry';
57
60
  import { correctedSodiumHyperglycemiaCalculator } from './tool/correctedSodiumHyperglycemiaCalculator/entry';
58
61
  import { fractionalExcretionSodiumCalculator } from './tool/fractionalExcretionSodiumCalculator/entry';
59
62
  import { targetHeightEstimator } from './tool/targetHeightEstimator/entry';
@@ -63,6 +66,7 @@ import { peripheralVisionTrainer } from './tool/peripheralVisionTrainer/entry';
63
66
  import { readingDistanceCalculator } from './tool/readingDistanceCalculator/entry';
64
67
  import { screenDecompressionTime } from './tool/screenDecompressionTime/entry';
65
68
  import { tinnitusReliever } from './tool/tinnitusReliever/entry';
69
+ import { parametricNoiseSynthesizer } from './tool/parametricNoiseSynthesizer/entry';
66
70
  import { ubeCalculator } from './tool/ubeCalculator/entry';
67
71
  import { waterPurifier } from './tool/waterPurifier/entry';
68
- export const ALL_ENTRIES = [binauralTuner, bloodUnitConverter, bmiCalculator, amputationAdjustedBmiCalculator, bodyAdiposityIndexCalculator, bodyFatPercentageCalculator, waistHipRatioCalculator, waistHeightRatioCalculator, breathingVisualizer, caffeineTracker, caloricDeficitCalculator, dailyProteinCalculator, macronutrientCalculator, tdeeCalculator, bunCreatinineCalculator, serumOsmolalityCalculator, correctedSodiumHyperglycemiaCalculator, fractionalExcretionSodiumCalculator, correctedReticulocyteIndex, targetHeightEstimator, daltonismSimulator, digestionStopwatch, epworthSleepinessScale, hydrationCalculator, leanBodyMassCalculator, metabolicAgeCalculator, pelliRobsonTest, peripheralVisionTrainer, readingDistanceCalculator, screenDecompressionTime, tinnitusReliever, ubeCalculator, waterPurifier];
72
+ export const ALL_ENTRIES = [binauralTuner, bloodUnitConverter, bmiCalculator, amputationAdjustedBmiCalculator, bodyAdiposityIndexCalculator, bodyFatPercentageCalculator, waistHipRatioCalculator, waistHeightRatioCalculator, breathingVisualizer, caffeineTracker, caloricDeficitCalculator, dailyProteinCalculator, macronutrientCalculator, tdeeCalculator, bunCreatinineCalculator, serumOsmolalityCalculator, meanArterialPressureCalculator, correctedSodiumHyperglycemiaCalculator, fractionalExcretionSodiumCalculator, correctedReticulocyteIndex, targetHeightEstimator, daltonismSimulator, digestionStopwatch, epworthSleepinessScale, hydrationCalculator, leanBodyMassCalculator, metabolicAgeCalculator, pelliRobsonTest, peripheralVisionTrainer, readingDistanceCalculator, screenDecompressionTime, tinnitusReliever, parametricNoiseSynthesizer, ubeCalculator, waterPurifier];
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ export * from './tool/bmiCalculator';
2
2
  export * from './tool/amputationAdjustedBmiCalculator';
3
3
  export * from './tool/daltonismSimulator';
4
4
  export * from './tool/tinnitusReliever';
5
+ export * from './tool/parametricNoiseSynthesizer';
5
6
  export * from './tool/breathingVisualizer';
6
7
  export * from './tool/caffeineTracker';
7
8
  export * from './tool/caloricDeficitCalculator';
@@ -27,6 +28,7 @@ export * from './tool/macronutrientCalculator';
27
28
  export * from './tool/tdeeCalculator';
28
29
  export * from './tool/bunCreatinineCalculator';
29
30
  export * from './tool/serumOsmolalityCalculator';
31
+ export * from './tool/meanArterialPressureCalculator';
30
32
  export * from './tool/correctedSodiumHyperglycemiaCalculator';
31
33
  export * from './tool/fractionalExcretionSodiumCalculator';
32
34
  export * from './tool/targetHeightEstimator';
@@ -36,6 +36,6 @@ describe('Locale Completeness Validation', () => {
36
36
  });
37
37
 
38
38
  it('all tools registered', () => {
39
- expect(ALL_TOOLS.length).toBe(33);
39
+ expect(ALL_TOOLS.length).toBe(35);
40
40
  });
41
41
  });
@@ -5,7 +5,7 @@ import { healthCategory } from '../data';
5
5
  describe('Tool Validation Suite', () => {
6
6
  describe('Library Registration', () => {
7
7
  it('should have 33 tools in ALL_TOOLS', () => {
8
- expect(ALL_TOOLS.length).toBe(33);
8
+ expect(ALL_TOOLS.length).toBe(35);
9
9
  });
10
10
 
11
11
  it('healthCategory should be defined', () => {
@@ -0,0 +1,14 @@
1
+ ---
2
+ import { Bibliography } from '@jjlmoya/utils-shared';
3
+ import { meanArterialPressureCalculator } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'en' } = Astro.props;
11
+ const content = await meanArterialPressureCalculator.i18n[locale]?.();
12
+ ---
13
+
14
+ {content && <Bibliography links={content.bibliography} />}
@@ -0,0 +1,24 @@
1
+ import type { BibliographyEntry } from '../../types';
2
+
3
+ export const bibliography: BibliographyEntry[] = [
4
+ {
5
+ name: 'DeMers D, Wachs D. Physiology, Mean Arterial Pressure. StatPearls Publishing. 2020.',
6
+ url: 'https://www.ncbi.nlm.nih.gov/books/NBK538226/',
7
+ },
8
+ {
9
+ name: 'Moran D, et al. Calculation of mean arterial pressure. Journal of Clinical Monitoring. 1995.',
10
+ url: 'https://pubmed.ncbi.nlm.nih.gov/8530791/',
11
+ },
12
+ {
13
+ name: 'Magder S. The meaning of blood pressure. Critical Care. 2018.',
14
+ url: 'https://ccforum.biomedcentral.com/articles/10.1186/s13054-018-2171-1',
15
+ },
16
+ {
17
+ name: 'Surviving Sepsis Campaign: international guidelines for management of sepsis and septic shock 2021.',
18
+ url: 'https://pubmed.ncbi.nlm.nih.gov/34599691/',
19
+ },
20
+ {
21
+ name: 'Wikipedia: Mean arterial pressure',
22
+ url: 'https://en.wikipedia.org/wiki/Mean_arterial_pressure',
23
+ },
24
+ ];
@@ -0,0 +1,224 @@
1
+ ---
2
+ import './mean-arterial-pressure-calculator.css';
3
+ import type { MeanArterialPressureCalculatorUI } from './ui';
4
+
5
+ interface Props {
6
+ locale?: string;
7
+ ui?: Record<string, unknown>;
8
+ }
9
+
10
+ const t = (Astro.props.ui || {}) as MeanArterialPressureCalculatorUI;
11
+ ---
12
+
13
+ <div class="mapcalc" data-ui={JSON.stringify(t)} data-band="optimal">
14
+ <div class="mapcalc__safety" role="alert">
15
+ <strong>{t.educationWarningTitle}</strong>
16
+ <span>{t.educationWarningBody}</span>
17
+ </div>
18
+
19
+ <div class="mapcalc__grid">
20
+ <section class="mapcalc__dial-panel" aria-label={t.mapLabel}>
21
+ <div class="mapcalc__dial" aria-hidden="true">
22
+ <div class="mapcalc__dial-face"></div>
23
+ <div class="mapcalc__ticks">
24
+ <span class="mapcalc__tick mapcalc__tick--40">40</span>
25
+ <span class="mapcalc__tick mapcalc__tick--60">60</span>
26
+ <span class="mapcalc__tick mapcalc__tick--80">80</span>
27
+ <span class="mapcalc__tick mapcalc__tick--100">100</span>
28
+ <span class="mapcalc__tick mapcalc__tick--120">120</span>
29
+ <span class="mapcalc__tick mapcalc__tick--140">140</span>
30
+ </div>
31
+ <div class="mapcalc__dial-unit">{t.mmhg}</div>
32
+ <div id="map-needle" class="mapcalc__needle"></div>
33
+ <div class="mapcalc__hub"></div>
34
+ </div>
35
+
36
+ <div class="mapcalc__readout">
37
+ <span>{t.mapLabel}</span>
38
+ <strong id="map-value">{t.emptyValue}</strong>
39
+ <small>{t.mmhg}</small>
40
+ </div>
41
+
42
+ <div id="map-status" class="mapcalc__status">
43
+ <strong>{t.optimalTitle}</strong>
44
+ <span>{t.optimalBody}</span>
45
+ </div>
46
+
47
+ <div class="mapcalc__ranges" aria-label={t.mapLabel}>
48
+ <span class="mapcalc__range mapcalc__range--red">{t.rangeInadequate}</span>
49
+ <span class="mapcalc__range mapcalc__range--yellow">{t.rangeWarning}</span>
50
+ <span class="mapcalc__range mapcalc__range--green">{t.rangeOptimal}</span>
51
+ <span class="mapcalc__range mapcalc__range--orange">{t.rangeElevated}</span>
52
+ </div>
53
+ </section>
54
+
55
+ <section class="mapcalc__controls">
56
+ <label class="mapcalc__control">
57
+ <span><b>{t.systolicLabel}</b><small>{t.sbpShort}</small></span>
58
+ <input id="map-sbp-range" type="range" min="70" max="240" step="1" value="120" />
59
+ <div class="mapcalc__number">
60
+ <input id="map-sbp" type="number" min="70" max="240" step="1" value="120" />
61
+ <small>{t.mmhg}</small>
62
+ </div>
63
+ </label>
64
+
65
+ <label class="mapcalc__control">
66
+ <span><b>{t.diastolicLabel}</b><small>{t.dbpShort}</small></span>
67
+ <input id="map-dbp-range" type="range" min="30" max="160" step="1" value="80" />
68
+ <div class="mapcalc__number">
69
+ <input id="map-dbp" type="number" min="30" max="160" step="1" value="80" />
70
+ <small>{t.mmhg}</small>
71
+ </div>
72
+ </label>
73
+
74
+ <label class="mapcalc__control mapcalc__control--hr">
75
+ <span><b>{t.heartRateLabel}</b><small>{t.heartRateOptional}</small></span>
76
+ <input id="map-hr-range" type="range" min="30" max="180" step="1" value="75" />
77
+ <div class="mapcalc__number">
78
+ <input id="map-hr" type="number" min="30" max="180" step="1" value="75" />
79
+ <small>{t.bpm}</small>
80
+ </div>
81
+ </label>
82
+
83
+ <label class="mapcalc__switch">
84
+ <input id="map-use-hr" type="checkbox" />
85
+ <span>{t.heartRateToggle}</span>
86
+ </label>
87
+
88
+ <div class="mapcalc__metrics">
89
+ <div>
90
+ <span>{t.standardMapLabel}</span>
91
+ <strong id="map-standard">{t.emptyValue}</strong>
92
+ </div>
93
+ <div>
94
+ <span>{t.correctedMapLabel}</span>
95
+ <strong id="map-corrected">{t.emptyValue}</strong>
96
+ </div>
97
+ <div>
98
+ <span>{t.pulsePressureLabel}</span>
99
+ <strong id="map-pulse">{t.emptyValue}</strong>
100
+ </div>
101
+ </div>
102
+
103
+ <div class="mapcalc__formula">
104
+ <span id="map-mode">{t.activeStandard}</span>
105
+ <code id="map-formula">{t.standardFormula}</code>
106
+ </div>
107
+ </section>
108
+ </div>
109
+ </div>
110
+
111
+ <script>
112
+ type Band = 'inadequate' | 'warning' | 'optimal' | 'elevated';
113
+
114
+ interface MapCtx {
115
+ root: HTMLElement;
116
+ ui: Record<string, string>;
117
+ sbp: HTMLInputElement;
118
+ dbp: HTMLInputElement;
119
+ hr: HTMLInputElement;
120
+ sbpRange: HTMLInputElement;
121
+ dbpRange: HTMLInputElement;
122
+ hrRange: HTMLInputElement;
123
+ useHr: HTMLInputElement;
124
+ }
125
+
126
+ function mapQs<T extends HTMLElement>(root: Element, selector: string): T {
127
+ const el = root.querySelector<T>(selector);
128
+ if (!el) throw new Error(`Missing ${selector}`);
129
+ return el;
130
+ }
131
+
132
+ function fmt(value: number): string {
133
+ return String(Math.round(value * 10) / 10);
134
+ }
135
+
136
+ function clamp(value: number, min: number, max: number): number {
137
+ return Math.min(max, Math.max(min, value));
138
+ }
139
+
140
+ function bandFor(map: number): Band {
141
+ if (map < 60) return 'inadequate';
142
+ if (map < 70) return 'warning';
143
+ if (map <= 100) return 'optimal';
144
+ return 'elevated';
145
+ }
146
+
147
+ function statusFor(ctx: MapCtx, band: Band): [string, string] {
148
+ if (band === 'inadequate') return [ctx.ui.inadequateTitle, ctx.ui.inadequateBody];
149
+ if (band === 'warning') return [ctx.ui.warningTitle, ctx.ui.warningBody];
150
+ if (band === 'elevated') return [ctx.ui.elevatedTitle, ctx.ui.elevatedBody];
151
+ return [ctx.ui.optimalTitle, ctx.ui.optimalBody];
152
+ }
153
+
154
+ function readMapInputs(ctx: MapCtx) {
155
+ const sbp = clamp(parseFloat(ctx.sbp.value) || 0, 70, 240);
156
+ let dbp = clamp(parseFloat(ctx.dbp.value) || 0, 30, 160);
157
+ const hr = clamp(parseFloat(ctx.hr.value) || 75, 30, 180);
158
+ if (dbp >= sbp) dbp = Math.max(30, sbp - 1);
159
+
160
+ ctx.sbp.value = String(Math.round(sbp));
161
+ ctx.dbp.value = String(Math.round(dbp));
162
+ ctx.hr.value = String(Math.round(hr));
163
+ ctx.sbpRange.value = ctx.sbp.value;
164
+ ctx.dbpRange.value = ctx.dbp.value;
165
+ ctx.hrRange.value = ctx.hr.value;
166
+
167
+ return { sbp, dbp, hr };
168
+ }
169
+
170
+ function renderMap(ctx: MapCtx) {
171
+ const { sbp, dbp, hr } = readMapInputs(ctx);
172
+
173
+ const pulsePressure = sbp - dbp;
174
+ const standard = (sbp + 2 * dbp) / 3;
175
+ const corrected = dbp + (0.33 + 0.0012 * hr) * pulsePressure;
176
+ const displayed = ctx.useHr.checked ? corrected : standard;
177
+ const band = bandFor(displayed);
178
+ const angle = -122 + (clamp(displayed, 40, 140) - 40) * 2.44;
179
+ const [title, body] = statusFor(ctx, band);
180
+
181
+ ctx.root.dataset.band = band;
182
+ mapQs<HTMLElement>(ctx.root, '#map-needle').style.setProperty('--angle', `${angle}deg`);
183
+ mapQs<HTMLElement>(ctx.root, '#map-value').textContent = fmt(displayed);
184
+ mapQs<HTMLElement>(ctx.root, '#map-standard').textContent = fmt(standard);
185
+ mapQs<HTMLElement>(ctx.root, '#map-corrected').textContent = ctx.useHr.checked ? fmt(corrected) : ctx.ui.emptyValue;
186
+ mapQs<HTMLElement>(ctx.root, '#map-pulse').textContent = fmt(pulsePressure);
187
+ mapQs<HTMLElement>(ctx.root, '#map-mode').textContent = ctx.useHr.checked ? ctx.ui.activeCorrected : ctx.ui.activeStandard;
188
+ mapQs<HTMLElement>(ctx.root, '#map-formula').textContent = ctx.useHr.checked ? ctx.ui.correctedFormula : ctx.ui.standardFormula;
189
+
190
+ const status = mapQs<HTMLElement>(ctx.root, '#map-status');
191
+ mapQs<HTMLElement>(status, 'strong').textContent = title;
192
+ mapQs<HTMLElement>(status, 'span').textContent = body;
193
+ }
194
+
195
+ function bindPair(ctx: MapCtx, numberInput: HTMLInputElement, rangeInput: HTMLInputElement) {
196
+ numberInput.addEventListener('input', () => renderMap(ctx));
197
+ rangeInput.addEventListener('input', () => {
198
+ numberInput.value = rangeInput.value;
199
+ renderMap(ctx);
200
+ });
201
+ }
202
+
203
+ function initMapCalculator(root: Element) {
204
+ const ctx: MapCtx = {
205
+ root: root as HTMLElement,
206
+ ui: JSON.parse((root as HTMLElement).dataset.ui || '{}') as Record<string, string>,
207
+ sbp: mapQs<HTMLInputElement>(root, '#map-sbp'),
208
+ dbp: mapQs<HTMLInputElement>(root, '#map-dbp'),
209
+ hr: mapQs<HTMLInputElement>(root, '#map-hr'),
210
+ sbpRange: mapQs<HTMLInputElement>(root, '#map-sbp-range'),
211
+ dbpRange: mapQs<HTMLInputElement>(root, '#map-dbp-range'),
212
+ hrRange: mapQs<HTMLInputElement>(root, '#map-hr-range'),
213
+ useHr: mapQs<HTMLInputElement>(root, '#map-use-hr'),
214
+ };
215
+
216
+ bindPair(ctx, ctx.sbp, ctx.sbpRange);
217
+ bindPair(ctx, ctx.dbp, ctx.dbpRange);
218
+ bindPair(ctx, ctx.hr, ctx.hrRange);
219
+ ctx.useHr.addEventListener('change', () => renderMap(ctx));
220
+ renderMap(ctx);
221
+ }
222
+
223
+ document.querySelectorAll('.mapcalc').forEach(initMapCalculator);
224
+ </script>
@@ -0,0 +1,27 @@
1
+ import type { HealthToolEntry } from '../../types';
2
+ import type { MeanArterialPressureCalculatorUI } from './ui';
3
+
4
+ export const meanArterialPressureCalculator: HealthToolEntry<MeanArterialPressureCalculatorUI> = {
5
+ id: 'meanArterialPressureCalculator',
6
+ icons: {
7
+ bg: 'mdi:gauge',
8
+ fg: 'mdi:heart-pulse',
9
+ },
10
+ i18n: {
11
+ de: () => import('./i18n/de').then((m) => m.content),
12
+ en: () => import('./i18n/en').then((m) => m.content),
13
+ es: () => import('./i18n/es').then((m) => m.content),
14
+ fr: () => import('./i18n/fr').then((m) => m.content),
15
+ id: () => import('./i18n/id').then((m) => m.content),
16
+ it: () => import('./i18n/it').then((m) => m.content),
17
+ ja: () => import('./i18n/ja').then((m) => m.content),
18
+ ko: () => import('./i18n/ko').then((m) => m.content),
19
+ nl: () => import('./i18n/nl').then((m) => m.content),
20
+ pl: () => import('./i18n/pl').then((m) => m.content),
21
+ pt: () => import('./i18n/pt').then((m) => m.content),
22
+ ru: () => import('./i18n/ru').then((m) => m.content),
23
+ sv: () => import('./i18n/sv').then((m) => m.content),
24
+ tr: () => import('./i18n/tr').then((m) => m.content),
25
+ zh: () => import('./i18n/zh').then((m) => m.content),
26
+ },
27
+ };