@jjlmoya/utils-health 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.
- package/package.json +60 -0
- package/src/category/i18n/en.ts +60 -0
- package/src/category/i18n/es.ts +60 -0
- package/src/category/i18n/fr.ts +60 -0
- package/src/category/index.ts +22 -0
- package/src/category/seo.astro +15 -0
- package/src/components/PreviewNavSidebar.astro +116 -0
- package/src/components/PreviewToolbar.astro +143 -0
- package/src/data.ts +28 -0
- package/src/env.d.ts +5 -0
- package/src/index.ts +36 -0
- package/src/layouts/PreviewLayout.astro +117 -0
- package/src/pages/[locale]/[slug].astro +146 -0
- package/src/pages/[locale].astro +251 -0
- package/src/pages/index.astro +4 -0
- package/src/tests/faq_count.test.ts +19 -0
- package/src/tests/locale_completeness.test.ts +42 -0
- package/src/tests/mocks/astro_mock.js +2 -0
- package/src/tests/no_h1_in_components.test.ts +48 -0
- package/src/tests/schemas_fulfillment.test.ts +23 -0
- package/src/tests/seo_length.test.ts +22 -0
- package/src/tests/title_quality.test.ts +55 -0
- package/src/tests/tool_validation.test.ts +17 -0
- package/src/tool/binauralTuner/bibliography.astro +14 -0
- package/src/tool/binauralTuner/component.astro +687 -0
- package/src/tool/binauralTuner/i18n/en.ts +187 -0
- package/src/tool/binauralTuner/i18n/es.ts +187 -0
- package/src/tool/binauralTuner/i18n/fr.ts +187 -0
- package/src/tool/binauralTuner/index.ts +27 -0
- package/src/tool/binauralTuner/seo.astro +14 -0
- package/src/tool/binauralTuner/ui.ts +18 -0
- package/src/tool/bloodUnitConverter/bibliography.astro +14 -0
- package/src/tool/bloodUnitConverter/component.astro +915 -0
- package/src/tool/bloodUnitConverter/i18n/en.ts +227 -0
- package/src/tool/bloodUnitConverter/i18n/es.ts +250 -0
- package/src/tool/bloodUnitConverter/i18n/fr.ts +218 -0
- package/src/tool/bloodUnitConverter/index.ts +27 -0
- package/src/tool/bloodUnitConverter/seo.astro +14 -0
- package/src/tool/bloodUnitConverter/ui.ts +38 -0
- package/src/tool/bmiCalculator/bibliography.astro +14 -0
- package/src/tool/bmiCalculator/component.astro +415 -0
- package/src/tool/bmiCalculator/i18n/en.ts +217 -0
- package/src/tool/bmiCalculator/i18n/es.ts +221 -0
- package/src/tool/bmiCalculator/i18n/fr.ts +217 -0
- package/src/tool/bmiCalculator/index.ts +27 -0
- package/src/tool/bmiCalculator/seo.astro +14 -0
- package/src/tool/bmiCalculator/ui.ts +21 -0
- package/src/tool/breathingVisualizer/bibliography.astro +14 -0
- package/src/tool/breathingVisualizer/component.astro +636 -0
- package/src/tool/breathingVisualizer/i18n/en.ts +206 -0
- package/src/tool/breathingVisualizer/i18n/es.ts +206 -0
- package/src/tool/breathingVisualizer/i18n/fr.ts +206 -0
- package/src/tool/breathingVisualizer/index.ts +27 -0
- package/src/tool/breathingVisualizer/seo.astro +14 -0
- package/src/tool/breathingVisualizer/ui.ts +31 -0
- package/src/tool/caffeineTracker/bibliography.astro +14 -0
- package/src/tool/caffeineTracker/component.astro +1210 -0
- package/src/tool/caffeineTracker/i18n/en.ts +198 -0
- package/src/tool/caffeineTracker/i18n/es.ts +198 -0
- package/src/tool/caffeineTracker/i18n/fr.ts +198 -0
- package/src/tool/caffeineTracker/index.ts +27 -0
- package/src/tool/caffeineTracker/logic.ts +31 -0
- package/src/tool/caffeineTracker/seo.astro +14 -0
- package/src/tool/caffeineTracker/ui.ts +36 -0
- package/src/tool/daltonismSimulator/bibliography.astro +14 -0
- package/src/tool/daltonismSimulator/component.astro +383 -0
- package/src/tool/daltonismSimulator/i18n/en.ts +188 -0
- package/src/tool/daltonismSimulator/i18n/es.ts +218 -0
- package/src/tool/daltonismSimulator/i18n/fr.ts +168 -0
- package/src/tool/daltonismSimulator/index.ts +27 -0
- package/src/tool/daltonismSimulator/seo.astro +14 -0
- package/src/tool/daltonismSimulator/ui.ts +20 -0
- package/src/tool/digestionStopwatch/bibliography.astro +14 -0
- package/src/tool/digestionStopwatch/component.astro +627 -0
- package/src/tool/digestionStopwatch/i18n/en.ts +173 -0
- package/src/tool/digestionStopwatch/i18n/es.ts +173 -0
- package/src/tool/digestionStopwatch/i18n/fr.ts +173 -0
- package/src/tool/digestionStopwatch/index.ts +27 -0
- package/src/tool/digestionStopwatch/logic.ts +63 -0
- package/src/tool/digestionStopwatch/seo.astro +14 -0
- package/src/tool/digestionStopwatch/ui.ts +20 -0
- package/src/tool/epworthSleepinessScale/bibliography.astro +14 -0
- package/src/tool/epworthSleepinessScale/component.astro +528 -0
- package/src/tool/epworthSleepinessScale/i18n/en.ts +217 -0
- package/src/tool/epworthSleepinessScale/i18n/es.ts +217 -0
- package/src/tool/epworthSleepinessScale/i18n/fr.ts +217 -0
- package/src/tool/epworthSleepinessScale/index.ts +27 -0
- package/src/tool/epworthSleepinessScale/seo.astro +14 -0
- package/src/tool/epworthSleepinessScale/ui.ts +27 -0
- package/src/tool/hydrationCalculator/bibliography.astro +14 -0
- package/src/tool/hydrationCalculator/component.astro +694 -0
- package/src/tool/hydrationCalculator/i18n/en.ts +217 -0
- package/src/tool/hydrationCalculator/i18n/es.ts +222 -0
- package/src/tool/hydrationCalculator/i18n/fr.ts +199 -0
- package/src/tool/hydrationCalculator/index.ts +27 -0
- package/src/tool/hydrationCalculator/seo.astro +14 -0
- package/src/tool/hydrationCalculator/ui.ts +28 -0
- package/src/tool/pelliRobsonTest/bibliography.astro +14 -0
- package/src/tool/pelliRobsonTest/component.astro +653 -0
- package/src/tool/pelliRobsonTest/i18n/en.ts +205 -0
- package/src/tool/pelliRobsonTest/i18n/es.ts +205 -0
- package/src/tool/pelliRobsonTest/i18n/fr.ts +205 -0
- package/src/tool/pelliRobsonTest/index.ts +27 -0
- package/src/tool/pelliRobsonTest/seo.astro +14 -0
- package/src/tool/pelliRobsonTest/ui.ts +21 -0
- package/src/tool/peripheralVisionTrainer/bibliography.astro +14 -0
- package/src/tool/peripheralVisionTrainer/component.astro +678 -0
- package/src/tool/peripheralVisionTrainer/i18n/en.ts +224 -0
- package/src/tool/peripheralVisionTrainer/i18n/es.ts +224 -0
- package/src/tool/peripheralVisionTrainer/i18n/fr.ts +211 -0
- package/src/tool/peripheralVisionTrainer/index.ts +27 -0
- package/src/tool/peripheralVisionTrainer/seo.astro +14 -0
- package/src/tool/peripheralVisionTrainer/ui.ts +26 -0
- package/src/tool/readingDistanceCalculator/bibliography.astro +14 -0
- package/src/tool/readingDistanceCalculator/component.astro +588 -0
- package/src/tool/readingDistanceCalculator/i18n/en.ts +202 -0
- package/src/tool/readingDistanceCalculator/i18n/es.ts +215 -0
- package/src/tool/readingDistanceCalculator/i18n/fr.ts +193 -0
- package/src/tool/readingDistanceCalculator/index.ts +31 -0
- package/src/tool/readingDistanceCalculator/seo.astro +14 -0
- package/src/tool/readingDistanceCalculator/ui.ts +18 -0
- package/src/tool/screenDecompressionTime/bibliography.astro +14 -0
- package/src/tool/screenDecompressionTime/component.astro +671 -0
- package/src/tool/screenDecompressionTime/i18n/en.ts +225 -0
- package/src/tool/screenDecompressionTime/i18n/es.ts +247 -0
- package/src/tool/screenDecompressionTime/i18n/fr.ts +225 -0
- package/src/tool/screenDecompressionTime/index.ts +27 -0
- package/src/tool/screenDecompressionTime/seo.astro +14 -0
- package/src/tool/screenDecompressionTime/ui.ts +32 -0
- package/src/tool/tinnitusReliever/bibliography.astro +14 -0
- package/src/tool/tinnitusReliever/component.astro +581 -0
- package/src/tool/tinnitusReliever/i18n/en.ts +161 -0
- package/src/tool/tinnitusReliever/i18n/es.ts +161 -0
- package/src/tool/tinnitusReliever/i18n/fr.ts +161 -0
- package/src/tool/tinnitusReliever/index.ts +27 -0
- package/src/tool/tinnitusReliever/seo.astro +14 -0
- package/src/tool/tinnitusReliever/ui.ts +9 -0
- package/src/tool/ubeCalculator/bibliography.astro +14 -0
- package/src/tool/ubeCalculator/component.astro +683 -0
- package/src/tool/ubeCalculator/i18n/en.ts +200 -0
- package/src/tool/ubeCalculator/i18n/es.ts +200 -0
- package/src/tool/ubeCalculator/i18n/fr.ts +196 -0
- package/src/tool/ubeCalculator/index.ts +27 -0
- package/src/tool/ubeCalculator/seo.astro +14 -0
- package/src/tool/ubeCalculator/ui.ts +26 -0
- package/src/tool/waterPurifier/bibliography.astro +14 -0
- package/src/tool/waterPurifier/component.astro +628 -0
- package/src/tool/waterPurifier/i18n/en.ts +167 -0
- package/src/tool/waterPurifier/i18n/es.ts +167 -0
- package/src/tool/waterPurifier/i18n/fr.ts +167 -0
- package/src/tool/waterPurifier/index.ts +27 -0
- package/src/tool/waterPurifier/seo.astro +14 -0
- package/src/tool/waterPurifier/ui.ts +18 -0
- package/src/tools.ts +19 -0
- package/src/types.ts +72 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { KnownLocale } from '../../types';
|
|
3
|
+
import type { BMICalculatorUI } from './ui';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
locale?: KnownLocale;
|
|
7
|
+
ui?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const t = (Astro.props.ui ?? {}) as BMICalculatorUI;
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<div
|
|
14
|
+
class="bmi"
|
|
15
|
+
id="bmi-root"
|
|
16
|
+
data-category="normal"
|
|
17
|
+
data-lbl-underweight={t.lblUnderweight ?? 'Underweight'}
|
|
18
|
+
data-lbl-normal={t.lblNormal ?? 'Normal Weight'}
|
|
19
|
+
data-lbl-overweight={t.lblOverweight ?? 'Overweight'}
|
|
20
|
+
data-lbl-ob1={t.lblOb1 ?? 'Obesity I'}
|
|
21
|
+
data-lbl-ob2={t.lblOb2 ?? 'Obesity II'}
|
|
22
|
+
data-lbl-ob3={t.lblOb3 ?? 'Obesity III'}
|
|
23
|
+
data-desc-underweight={t.descUnderweight ?? 'May indicate health problems. Consult a doctor.'}
|
|
24
|
+
data-desc-normal={t.descNormal ?? 'Well done. Maintain a balanced diet and regular exercise.'}
|
|
25
|
+
data-desc-overweight={t.descOverweight ?? 'Slightly excess weight. Small changes in diet can help.'}
|
|
26
|
+
data-desc-ob1={t.descOb1 ?? 'Moderate cardiovascular risk. Medical supervision recommended.'}
|
|
27
|
+
data-desc-ob2={t.descOb2 ?? 'High risk of hypertension and diabetes. Consult a specialist.'}
|
|
28
|
+
data-desc-ob3={t.descOb3 ?? 'Very high risk. Seek professional medical help.'}
|
|
29
|
+
data-unit-cm={t.unitCm ?? 'cm'}
|
|
30
|
+
data-unit-kg={t.unitKg ?? 'kg'}
|
|
31
|
+
>
|
|
32
|
+
<div class="bmi__left">
|
|
33
|
+
<div class="bmi__field">
|
|
34
|
+
<div class="bmi__field-header">
|
|
35
|
+
<span class="bmi__field-label">{t.labelHeight ?? 'Height'}</span>
|
|
36
|
+
<span class="bmi__field-value" id="bmi-height-val">175 <span class="bmi__unit">{t.unitCm ?? 'cm'}</span></span>
|
|
37
|
+
</div>
|
|
38
|
+
<input class="bmi__slider" type="range" id="bmi-height" min="100" max="230" value="175" />
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div class="bmi__field">
|
|
42
|
+
<div class="bmi__field-header">
|
|
43
|
+
<span class="bmi__field-label">{t.labelWeight ?? 'Weight'}</span>
|
|
44
|
+
<span class="bmi__field-value" id="bmi-weight-val">70 <span class="bmi__unit">{t.unitKg ?? 'kg'}</span></span>
|
|
45
|
+
</div>
|
|
46
|
+
<input class="bmi__slider" type="range" id="bmi-weight" min="30" max="180" value="70" />
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div class="bmi__step-btns">
|
|
50
|
+
<button class="bmi__step" id="bmi-minus" aria-label="Decrease weight">−</button>
|
|
51
|
+
<button class="bmi__step" id="bmi-plus" aria-label="Increase weight">+</button>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div class="bmi__right">
|
|
56
|
+
<span class="bmi__your-bmi">{t.yourBmi ?? 'Your BMI is'}</span>
|
|
57
|
+
<div class="bmi__value" id="bmi-value">22.9</div>
|
|
58
|
+
|
|
59
|
+
<div class="bmi__category" id="bmi-category">{t.lblNormal ?? 'Normal Weight'}</div>
|
|
60
|
+
<p class="bmi__desc" id="bmi-desc">{t.descNormal ?? 'Well done. Maintain a balanced diet and regular exercise.'}</p>
|
|
61
|
+
|
|
62
|
+
<div class="bmi__scale">
|
|
63
|
+
<div class="bmi__scale-bar">
|
|
64
|
+
<div class="bmi__scale-seg bmi__scale-seg--uw"></div>
|
|
65
|
+
<div class="bmi__scale-seg bmi__scale-seg--nw"></div>
|
|
66
|
+
<div class="bmi__scale-seg bmi__scale-seg--ow"></div>
|
|
67
|
+
<div class="bmi__scale-seg bmi__scale-seg--ob"></div>
|
|
68
|
+
<div class="bmi__scale-marker" id="bmi-marker"></div>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="bmi__scale-labels">
|
|
71
|
+
<span>15</span><span>18.5</span><span>25</span><span>30</span><span>40+</span>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<style>
|
|
78
|
+
.bmi {
|
|
79
|
+
--bmi-accent: #10b981;
|
|
80
|
+
--bmi-bg: rgba(255, 255, 255, 0.9);
|
|
81
|
+
--bmi-bg-left: rgba(248, 250, 252, 0.6);
|
|
82
|
+
--bmi-border: rgba(226, 232, 240, 0.8);
|
|
83
|
+
--bmi-text: #1e293b;
|
|
84
|
+
--bmi-muted: #64748b;
|
|
85
|
+
--bmi-slider-track: #e2e8f0;
|
|
86
|
+
--bmi-step-bg: #fff;
|
|
87
|
+
--bmi-step-border: #e2e8f0;
|
|
88
|
+
|
|
89
|
+
display: grid;
|
|
90
|
+
grid-template-columns: 1fr 1fr;
|
|
91
|
+
background: var(--bmi-bg);
|
|
92
|
+
border: 1px solid var(--bmi-border);
|
|
93
|
+
border-radius: 2rem;
|
|
94
|
+
overflow: hidden;
|
|
95
|
+
max-width: 900px;
|
|
96
|
+
margin: 2rem auto;
|
|
97
|
+
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
:global(.theme-dark) .bmi {
|
|
101
|
+
--bmi-bg: rgba(15, 23, 42, 0.85);
|
|
102
|
+
--bmi-bg-left: rgba(30, 41, 59, 0.5);
|
|
103
|
+
--bmi-border: rgba(51, 65, 85, 0.8);
|
|
104
|
+
--bmi-text: #f8fafc;
|
|
105
|
+
--bmi-muted: #94a3b8;
|
|
106
|
+
--bmi-slider-track: #334155;
|
|
107
|
+
--bmi-step-bg: #1e293b;
|
|
108
|
+
--bmi-step-border: #334155;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.bmi[data-category="underweight"] { --bmi-accent: #3b82f6; }
|
|
112
|
+
.bmi[data-category="normal"] { --bmi-accent: #10b981; }
|
|
113
|
+
.bmi[data-category="overweight"] { --bmi-accent: #f59e0b; }
|
|
114
|
+
.bmi[data-category="ob1"] { --bmi-accent: #f97316; }
|
|
115
|
+
.bmi[data-category="ob2"] { --bmi-accent: #f43f5e; }
|
|
116
|
+
.bmi[data-category="ob3"] { --bmi-accent: #dc2626; }
|
|
117
|
+
|
|
118
|
+
.bmi__left {
|
|
119
|
+
padding: 2.5rem;
|
|
120
|
+
background: var(--bmi-bg-left);
|
|
121
|
+
border-right: 1px solid var(--bmi-border);
|
|
122
|
+
display: flex;
|
|
123
|
+
flex-direction: column;
|
|
124
|
+
justify-content: center;
|
|
125
|
+
gap: 2rem;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.bmi__field {
|
|
129
|
+
display: flex;
|
|
130
|
+
flex-direction: column;
|
|
131
|
+
gap: 0.75rem;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.bmi__field-header {
|
|
135
|
+
display: flex;
|
|
136
|
+
justify-content: space-between;
|
|
137
|
+
align-items: baseline;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.bmi__field-label {
|
|
141
|
+
font-size: 0.7rem;
|
|
142
|
+
font-weight: 800;
|
|
143
|
+
text-transform: uppercase;
|
|
144
|
+
letter-spacing: 0.1em;
|
|
145
|
+
color: var(--bmi-muted);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.bmi__field-value {
|
|
149
|
+
font-size: 1.75rem;
|
|
150
|
+
font-weight: 900;
|
|
151
|
+
color: var(--bmi-text);
|
|
152
|
+
letter-spacing: -1px;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.bmi__unit {
|
|
156
|
+
font-size: 0.85rem;
|
|
157
|
+
font-weight: 500;
|
|
158
|
+
color: var(--bmi-muted);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.bmi__slider {
|
|
162
|
+
width: 100%;
|
|
163
|
+
height: 6px;
|
|
164
|
+
border-radius: 3px;
|
|
165
|
+
appearance: none;
|
|
166
|
+
background: var(--bmi-slider-track);
|
|
167
|
+
cursor: pointer;
|
|
168
|
+
accent-color: var(--bmi-accent);
|
|
169
|
+
outline: none;
|
|
170
|
+
border: none;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.bmi__step-btns {
|
|
174
|
+
display: flex;
|
|
175
|
+
gap: 0.75rem;
|
|
176
|
+
justify-content: center;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.bmi__step {
|
|
180
|
+
width: 2.75rem;
|
|
181
|
+
height: 2.75rem;
|
|
182
|
+
border-radius: 50%;
|
|
183
|
+
background: var(--bmi-step-bg);
|
|
184
|
+
border: 1px solid var(--bmi-step-border);
|
|
185
|
+
color: var(--bmi-muted);
|
|
186
|
+
font-size: 1.5rem;
|
|
187
|
+
font-weight: 300;
|
|
188
|
+
cursor: pointer;
|
|
189
|
+
display: flex;
|
|
190
|
+
align-items: center;
|
|
191
|
+
justify-content: center;
|
|
192
|
+
transition: all 0.15s;
|
|
193
|
+
line-height: 1;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.bmi__step:hover {
|
|
197
|
+
border-color: var(--bmi-accent);
|
|
198
|
+
color: var(--bmi-accent);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.bmi__right {
|
|
202
|
+
padding: 2.5rem;
|
|
203
|
+
display: flex;
|
|
204
|
+
flex-direction: column;
|
|
205
|
+
align-items: center;
|
|
206
|
+
justify-content: center;
|
|
207
|
+
text-align: center;
|
|
208
|
+
gap: 0.75rem;
|
|
209
|
+
position: relative;
|
|
210
|
+
overflow: hidden;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.bmi__right::before {
|
|
214
|
+
content: '';
|
|
215
|
+
position: absolute;
|
|
216
|
+
inset: 0;
|
|
217
|
+
background: radial-gradient(ellipse at 50% 0%, rgba(var(--bmi-accent-rgb, 16, 185, 129), 0.12) 0%, transparent 70%);
|
|
218
|
+
transition: background 0.4s;
|
|
219
|
+
pointer-events: none;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.bmi__your-bmi {
|
|
223
|
+
font-size: 0.7rem;
|
|
224
|
+
font-weight: 800;
|
|
225
|
+
text-transform: uppercase;
|
|
226
|
+
letter-spacing: 0.1em;
|
|
227
|
+
color: var(--bmi-muted);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.bmi__value {
|
|
231
|
+
font-size: 5rem;
|
|
232
|
+
font-weight: 950;
|
|
233
|
+
letter-spacing: -3px;
|
|
234
|
+
color: var(--bmi-text);
|
|
235
|
+
line-height: 1;
|
|
236
|
+
transition: color 0.3s;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.bmi__category {
|
|
240
|
+
display: inline-block;
|
|
241
|
+
padding: 0.4rem 1.25rem;
|
|
242
|
+
border-radius: 2rem;
|
|
243
|
+
background: rgba(var(--bmi-accent-rgb, 16, 185, 129), 0.12);
|
|
244
|
+
color: var(--bmi-accent);
|
|
245
|
+
border: 1px solid rgba(var(--bmi-accent-rgb, 16, 185, 129), 0.25);
|
|
246
|
+
font-weight: 800;
|
|
247
|
+
font-size: 1rem;
|
|
248
|
+
transition: all 0.3s;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.bmi__desc {
|
|
252
|
+
font-size: 0.875rem;
|
|
253
|
+
color: var(--bmi-muted);
|
|
254
|
+
max-width: 22ch;
|
|
255
|
+
line-height: 1.5;
|
|
256
|
+
margin: 0;
|
|
257
|
+
transition: color 0.3s;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.bmi__scale {
|
|
261
|
+
width: 100%;
|
|
262
|
+
margin-top: 0.5rem;
|
|
263
|
+
display: flex;
|
|
264
|
+
flex-direction: column;
|
|
265
|
+
gap: 0.35rem;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.bmi__scale-bar {
|
|
269
|
+
position: relative;
|
|
270
|
+
height: 10px;
|
|
271
|
+
border-radius: 5px;
|
|
272
|
+
overflow: visible;
|
|
273
|
+
display: flex;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.bmi__scale-seg {
|
|
277
|
+
height: 100%;
|
|
278
|
+
border-radius: 0;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.bmi__scale-seg--uw { width: 18.5%; background: #93c5fd; border-radius: 5px 0 0 5px; }
|
|
282
|
+
.bmi__scale-seg--nw { width: 16.5%; background: #6ee7b7; }
|
|
283
|
+
.bmi__scale-seg--ow { width: 12.5%; background: #fcd34d; }
|
|
284
|
+
.bmi__scale-seg--ob { flex: 1; background: linear-gradient(to right, #fb923c, #f43f5e, #dc2626); border-radius: 0 5px 5px 0; }
|
|
285
|
+
|
|
286
|
+
.bmi__scale-marker {
|
|
287
|
+
position: absolute;
|
|
288
|
+
top: -3px;
|
|
289
|
+
width: 4px;
|
|
290
|
+
height: 16px;
|
|
291
|
+
background: var(--bmi-text);
|
|
292
|
+
border-radius: 2px;
|
|
293
|
+
box-shadow: 0 0 8px rgba(0, 0, 0, 0.3);
|
|
294
|
+
transform: translateX(-50%);
|
|
295
|
+
transition: left 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
296
|
+
left: 50%;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.bmi__scale-labels {
|
|
300
|
+
display: flex;
|
|
301
|
+
justify-content: space-between;
|
|
302
|
+
font-size: 0.65rem;
|
|
303
|
+
font-weight: 700;
|
|
304
|
+
color: var(--bmi-muted);
|
|
305
|
+
padding: 0 2px;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
@media (max-width: 640px) {
|
|
309
|
+
.bmi {
|
|
310
|
+
grid-template-columns: 1fr;
|
|
311
|
+
border-radius: 1.5rem;
|
|
312
|
+
margin: 1rem 0;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.bmi__left {
|
|
316
|
+
border-right: none;
|
|
317
|
+
border-bottom: 1px solid var(--bmi-border);
|
|
318
|
+
padding: 2rem 1.5rem;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.bmi__right {
|
|
322
|
+
padding: 2rem 1.5rem;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.bmi__value {
|
|
326
|
+
font-size: 4rem;
|
|
327
|
+
letter-spacing: -2px;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
</style>
|
|
331
|
+
|
|
332
|
+
<script>
|
|
333
|
+
const root = document.getElementById('bmi-root') as HTMLElement;
|
|
334
|
+
const ds = root?.dataset ?? {};
|
|
335
|
+
|
|
336
|
+
const heightInput = document.getElementById('bmi-height') as HTMLInputElement;
|
|
337
|
+
const weightInput = document.getElementById('bmi-weight') as HTMLInputElement;
|
|
338
|
+
const heightVal = document.getElementById('bmi-height-val') as HTMLElement;
|
|
339
|
+
const weightVal = document.getElementById('bmi-weight-val') as HTMLElement;
|
|
340
|
+
const bmiValue = document.getElementById('bmi-value') as HTMLElement;
|
|
341
|
+
const bmiCategory = document.getElementById('bmi-category') as HTMLElement;
|
|
342
|
+
const bmiDesc = document.getElementById('bmi-desc') as HTMLElement;
|
|
343
|
+
const bmiMarker = document.getElementById('bmi-marker') as HTMLElement;
|
|
344
|
+
const btnPlus = document.getElementById('bmi-plus') as HTMLElement;
|
|
345
|
+
const btnMinus = document.getElementById('bmi-minus') as HTMLElement;
|
|
346
|
+
|
|
347
|
+
type Category = 'underweight' | 'normal' | 'overweight' | 'ob1' | 'ob2' | 'ob3';
|
|
348
|
+
|
|
349
|
+
const CATEGORY_DATA: Record<Category, { lbl: string; desc: string }> = {
|
|
350
|
+
underweight: { lbl: ds.lblUnderweight ?? 'Underweight', desc: ds.descUnderweight ?? '' },
|
|
351
|
+
normal: { lbl: ds.lblNormal ?? 'Normal Weight', desc: ds.descNormal ?? '' },
|
|
352
|
+
overweight: { lbl: ds.lblOverweight ?? 'Overweight', desc: ds.descOverweight ?? '' },
|
|
353
|
+
ob1: { lbl: ds.lblOb1 ?? 'Obesity I', desc: ds.descOb1 ?? '' },
|
|
354
|
+
ob2: { lbl: ds.lblOb2 ?? 'Obesity II', desc: ds.descOb2 ?? '' },
|
|
355
|
+
ob3: { lbl: ds.lblOb3 ?? 'Obesity III', desc: ds.descOb3 ?? '' },
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
function calcBmi(kg: number, cm: number): number {
|
|
359
|
+
const h = cm / 100;
|
|
360
|
+
return Math.round((kg / (h * h)) * 10) / 10;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function getCategory(bmi: number): Category {
|
|
364
|
+
if (bmi < 18.5) return 'underweight';
|
|
365
|
+
if (bmi < 25) return 'normal';
|
|
366
|
+
if (bmi < 30) return 'overweight';
|
|
367
|
+
if (bmi < 35) return 'ob1';
|
|
368
|
+
if (bmi < 40) return 'ob2';
|
|
369
|
+
return 'ob3';
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function markerPercent(bmi: number): number {
|
|
373
|
+
const pct = ((bmi - 15) / (40 - 15)) * 100;
|
|
374
|
+
return Math.max(0, Math.min(100, pct));
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function updateDisplays(h: number, w: number) {
|
|
378
|
+
const cm = ds.unitCm ?? 'cm';
|
|
379
|
+
const kg = ds.unitKg ?? 'kg';
|
|
380
|
+
heightVal.innerHTML = `${h} <span class="bmi__unit">${cm}</span>`;
|
|
381
|
+
weightVal.innerHTML = `${w} <span class="bmi__unit">${kg}</span>`;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function updateResult(bmi: number, cat: Category) {
|
|
385
|
+
const data = CATEGORY_DATA[cat];
|
|
386
|
+
bmiValue.textContent = bmi.toString();
|
|
387
|
+
bmiCategory.textContent = data.lbl;
|
|
388
|
+
bmiDesc.textContent = data.desc;
|
|
389
|
+
bmiMarker.style.left = `${markerPercent(bmi)}%`;
|
|
390
|
+
root.dataset.category = cat;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function update() {
|
|
394
|
+
const h = Number(heightInput.value);
|
|
395
|
+
const w = Number(weightInput.value);
|
|
396
|
+
updateDisplays(h, w);
|
|
397
|
+
const bmi = calcBmi(w, h);
|
|
398
|
+
updateResult(bmi, getCategory(bmi));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
heightInput.addEventListener('input', update);
|
|
402
|
+
weightInput.addEventListener('input', update);
|
|
403
|
+
|
|
404
|
+
btnPlus.addEventListener('click', () => {
|
|
405
|
+
weightInput.value = String(Math.min(180, Number(weightInput.value) + 1));
|
|
406
|
+
update();
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
btnMinus.addEventListener('click', () => {
|
|
410
|
+
weightInput.value = String(Math.max(30, Number(weightInput.value) - 1));
|
|
411
|
+
update();
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
update();
|
|
415
|
+
</script>
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import type { WithContext, FAQPage, HowToThing, SoftwareApplication } from 'schema-dts';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
import type { BMICalculatorUI } from '../ui';
|
|
4
|
+
|
|
5
|
+
const slug = 'bmi-calculator';
|
|
6
|
+
const title = 'BMI Calculator: Body Mass Index Online';
|
|
7
|
+
const description =
|
|
8
|
+
'Calculate your Body Mass Index (BMI) for free. Online tool to assess your nutritional status based on your weight and height, with visual scale and WHO categories.';
|
|
9
|
+
|
|
10
|
+
const faqData = [
|
|
11
|
+
{
|
|
12
|
+
question: 'Is BMI a reliable health indicator?',
|
|
13
|
+
answer:
|
|
14
|
+
'BMI is a useful screening tool for the general population, but it has limitations. It does not differentiate between muscle mass and body fat, so athletes with a lot of muscle may get incorrect overweight results without having excess body fat.',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
question: "What is the Quetelet Index?",
|
|
18
|
+
answer:
|
|
19
|
+
'It is the technical name for BMI. It was developed by statistician Adolphe Quetelet in the 19th century as a simple way to classify the nutritional status of people based solely on their weight and height.',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
question: 'How can I lower my BMI in a healthy way?',
|
|
23
|
+
answer:
|
|
24
|
+
'The key is not just to lower the number, but to improve body composition. Combine a balanced diet with a moderate caloric deficit and strength training to maintain muscle while losing fat.',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
question: 'When should I be concerned about my BMI result?',
|
|
28
|
+
answer:
|
|
29
|
+
'If your result is outside the normal range (18.5 to 24.9), it is advisable to consult a healthcare professional. A very low or very high BMI can increase the risk of cardiovascular problems, diabetes or nutritional deficiencies.',
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const howToData = [
|
|
34
|
+
{
|
|
35
|
+
name: 'Adjust your height',
|
|
36
|
+
text: 'Move the height slider to your height in centimeters (between 100 and 230 cm).',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'Adjust your weight',
|
|
40
|
+
text: 'Move the weight slider or use the plus/minus buttons to fine-tune the value in kilograms.',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'Read your result',
|
|
44
|
+
text: 'Look at the BMI value, the corresponding WHO category and the nutritional status description.',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'Interpret the visual scale',
|
|
48
|
+
text: 'The marker on the color bar visually shows which zone of the spectrum your BMI falls in.',
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
const faqSchema: WithContext<FAQPage> = {
|
|
53
|
+
'@context': 'https://schema.org',
|
|
54
|
+
'@type': 'FAQPage',
|
|
55
|
+
mainEntity: faqData.map((item) => ({
|
|
56
|
+
'@type': 'Question',
|
|
57
|
+
name: item.question,
|
|
58
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
|
|
59
|
+
})),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const howToSchema: WithContext<HowToThing> = {
|
|
63
|
+
'@context': 'https://schema.org',
|
|
64
|
+
'@type': 'HowTo',
|
|
65
|
+
name: title,
|
|
66
|
+
description,
|
|
67
|
+
step: howToData.map((step, i) => ({
|
|
68
|
+
'@type': 'HowToStep',
|
|
69
|
+
position: i + 1,
|
|
70
|
+
name: step.name,
|
|
71
|
+
text: step.text,
|
|
72
|
+
})),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const appSchema: WithContext<SoftwareApplication> = {
|
|
76
|
+
'@context': 'https://schema.org',
|
|
77
|
+
'@type': 'SoftwareApplication',
|
|
78
|
+
name: title,
|
|
79
|
+
description,
|
|
80
|
+
applicationCategory: 'HealthApplication',
|
|
81
|
+
operatingSystem: 'Web',
|
|
82
|
+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const content: ToolLocaleContent<BMICalculatorUI> = {
|
|
86
|
+
slug,
|
|
87
|
+
title,
|
|
88
|
+
description,
|
|
89
|
+
faqTitle: 'Frequently Asked Questions about BMI',
|
|
90
|
+
faq: faqData,
|
|
91
|
+
bibliographyTitle: 'Scientific references',
|
|
92
|
+
bibliography: [
|
|
93
|
+
{
|
|
94
|
+
name: 'WHO: Obesity and overweight — Fact sheet',
|
|
95
|
+
url: 'https://www.who.int/news-room/fact-sheets/detail/obesity-and-overweight',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: 'Quetelet LAJ. A Treatise on Man and the Development of his Faculties (1842)',
|
|
99
|
+
url: 'https://archive.org/details/treatiseonmandeve00quet',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'NIH: Classification of Overweight and Obesity by BMI',
|
|
103
|
+
url: 'https://www.nhlbi.nih.gov/health/educational/lose_wt/BMI/bmi_dis.htm',
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
howTo: howToData,
|
|
107
|
+
schemas: [faqSchema, howToSchema, appSchema],
|
|
108
|
+
ui: {
|
|
109
|
+
labelHeight: 'Height',
|
|
110
|
+
labelWeight: 'Weight',
|
|
111
|
+
unitCm: 'cm',
|
|
112
|
+
unitKg: 'kg',
|
|
113
|
+
yourBmi: 'Your BMI is',
|
|
114
|
+
waiting: 'Waiting for data...',
|
|
115
|
+
waitingDesc: 'Enter your weight and height.',
|
|
116
|
+
lblUnderweight: 'Underweight',
|
|
117
|
+
lblNormal: 'Normal Weight',
|
|
118
|
+
lblOverweight: 'Overweight',
|
|
119
|
+
lblOb1: 'Obesity I',
|
|
120
|
+
lblOb2: 'Obesity II',
|
|
121
|
+
lblOb3: 'Obesity III',
|
|
122
|
+
descUnderweight: 'May indicate malnutrition or health problems. Consult a doctor.',
|
|
123
|
+
descNormal: 'Well done. Maintain a balanced diet and regular exercise.',
|
|
124
|
+
descOverweight: 'Slightly excess weight. Small changes in diet can help.',
|
|
125
|
+
descOb1: 'Moderate risk of cardiovascular problems. Medical supervision recommended.',
|
|
126
|
+
descOb2: 'High risk of hypertension and diabetes. Consult a specialist.',
|
|
127
|
+
descOb3: 'Very high risk situation. It is crucial to seek professional medical help.',
|
|
128
|
+
},
|
|
129
|
+
seo: [
|
|
130
|
+
{
|
|
131
|
+
type: 'summary',
|
|
132
|
+
title: 'Online BMI Calculator',
|
|
133
|
+
items: [
|
|
134
|
+
'<strong>Instant calculation</strong>: Adjust the sliders and get your BMI in real time.',
|
|
135
|
+
'<strong>6 WHO categories</strong>: From Underweight to Obesity Type III with clinical description.',
|
|
136
|
+
'<strong>Visual scale</strong>: A marker shows you visually which zone of the spectrum you are in.',
|
|
137
|
+
'<strong>No registration</strong>: No cookies, no account, no data leaving your device.',
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
{ type: 'title', text: 'What is Body Mass Index?', level: 2 },
|
|
141
|
+
{
|
|
142
|
+
type: 'paragraph',
|
|
143
|
+
html: 'The <strong>Body Mass Index (BMI)</strong> is the most widely used nutritional health indicator in the world. It was developed in the 19th century by Belgian statistician and mathematician Adolphe Quetelet and is calculated by dividing body weight in kilograms by the square of height in meters: <strong>BMI = kg / m²</strong>.',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
type: 'paragraph',
|
|
147
|
+
html: 'The World Health Organization adopted BMI as a standard screening tool because it is simple to calculate, requires no specialized equipment, and provides a reasonable approximation of the nutritional status of most adults. However, it is important to understand what it measures and, above all, what it does <strong>not</strong> measure.',
|
|
148
|
+
},
|
|
149
|
+
{ type: 'title', text: 'How to interpret it: WHO ranges', level: 2 },
|
|
150
|
+
{
|
|
151
|
+
type: 'table',
|
|
152
|
+
headers: ['WHO Category', 'BMI Range', 'Health Risk'],
|
|
153
|
+
rows: [
|
|
154
|
+
['Underweight', 'Below 18.5', 'Risk of malnutrition and deficiencies'],
|
|
155
|
+
['Normal Weight', '18.5 to 24.9', 'Minimal risk (optimal range)'],
|
|
156
|
+
['Overweight', '25.0 to 29.9', 'Slightly increased risk'],
|
|
157
|
+
['Obesity Type I', '30.0 to 34.9', 'Moderate risk'],
|
|
158
|
+
['Obesity Type II', '35.0 to 39.9', 'High risk'],
|
|
159
|
+
['Obesity Type III', '40.0 or more', 'Very high risk (morbid obesity)'],
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
{ type: 'title', text: 'Limitations of BMI: when it is not enough', level: 2 },
|
|
163
|
+
{
|
|
164
|
+
type: 'list',
|
|
165
|
+
items: [
|
|
166
|
+
"<strong>Does not distinguish muscle from fat:</strong> A bodybuilder can have a BMI of 32 (obesity) with a healthy body fat percentage of 10%.",
|
|
167
|
+
'<strong>Does not consider fat distribution:</strong> Visceral fat (around organs) is more dangerous than subcutaneous fat, and BMI does not distinguish between them.',
|
|
168
|
+
'<strong>Varies by ethnicity:</strong> Cardiovascular risk thresholds differ between Asian, European and African American populations.',
|
|
169
|
+
'<strong>Not valid for children:</strong> In those under 18, age and sex percentiles are used, not adult ranges.',
|
|
170
|
+
'<strong>Does not apply during pregnancy:</strong> Gestational weight requires specific tables.',
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
type: 'tip',
|
|
175
|
+
title: 'Complement BMI with other metrics',
|
|
176
|
+
html: 'For a more complete assessment, healthcare professionals combine BMI with <strong>waist circumference</strong> (indicator of visceral fat), <strong>body fat percentage</strong> (measured with bioimpedance or DEXA) and blood tests (cholesterol, blood glucose, blood pressure).',
|
|
177
|
+
},
|
|
178
|
+
{ type: 'title', text: 'BMI and cardiovascular risk: what science says', level: 2 },
|
|
179
|
+
{
|
|
180
|
+
type: 'paragraph',
|
|
181
|
+
html: 'Multiple large-scale epidemiological studies have established a correlation between elevated BMI and the risk of chronic disease. A meta-analysis published in The Lancet with data from over 10 million participants demonstrated that a BMI above 25 kg/m² is associated with higher risk of type 2 diabetes, hypertension, cardiovascular disease and certain cancers.',
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
type: 'proscons',
|
|
185
|
+
items: [
|
|
186
|
+
{
|
|
187
|
+
pro: 'Simple and universal: you only need to know your weight and height to calculate it.',
|
|
188
|
+
con: 'Does not measure actual body composition or fat distribution.',
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
pro: 'Validated by WHO as a screening indicator for population studies.',
|
|
192
|
+
con: 'Can incorrectly classify high-performance athletes as obese.',
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
pro: 'Allows detection of severe malnutrition cases in clinical settings.',
|
|
196
|
+
con: 'Risk thresholds vary by ethnicity, reducing its universality.',
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
pro: 'Low-cost tool for monitoring public health trends.',
|
|
200
|
+
con: 'Does not replace a full medical evaluation or body composition analysis.',
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
},
|
|
204
|
+
{ type: 'title', text: 'How to improve your BMI in a healthy way', level: 2 },
|
|
205
|
+
{
|
|
206
|
+
type: 'list',
|
|
207
|
+
items: [
|
|
208
|
+
'Establish a moderate caloric deficit (300 to 500 kcal/day) to lose fat without sacrificing muscle.',
|
|
209
|
+
'Increase protein intake (1.6 to 2.2 g/kg body weight) to preserve muscle mass.',
|
|
210
|
+
'Incorporate strength training 2 or 3 times per week to improve body composition.',
|
|
211
|
+
'Prioritize restorative sleep (7 to 9 hours): lack of sleep increases hunger hormones.',
|
|
212
|
+
'Manage chronic stress: elevated cortisol promotes the accumulation of visceral fat.',
|
|
213
|
+
'Consult a registered dietitian for a personalized and sustainable long-term plan.',
|
|
214
|
+
],
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
};
|