@jjlmoya/utils-babies 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 +69 -0
- package/src/category/i18n/en.ts +48 -0
- package/src/category/i18n/es.ts +48 -0
- package/src/category/i18n/fr.ts +48 -0
- package/src/category/index.ts +24 -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 +30 -0
- package/src/env.d.ts +5 -0
- package/src/index.ts +19 -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/seo_length.test.ts +23 -0
- package/src/tests/tool_validation.test.ts +17 -0
- package/src/tool/baby-feeding-calculator/bibliography.astro +7 -0
- package/src/tool/baby-feeding-calculator/component.astro +210 -0
- package/src/tool/baby-feeding-calculator/i18n/en.ts +162 -0
- package/src/tool/baby-feeding-calculator/i18n/es.ts +162 -0
- package/src/tool/baby-feeding-calculator/i18n/fr.ts +162 -0
- package/src/tool/baby-feeding-calculator/index.ts +47 -0
- package/src/tool/baby-feeding-calculator/logic.ts +85 -0
- package/src/tool/baby-feeding-calculator/seo.astro +58 -0
- package/src/tool/baby-feeding-calculator/style.css +329 -0
- package/src/tool/baby-percentile-calculator/bibliography.astro +7 -0
- package/src/tool/baby-percentile-calculator/component.astro +388 -0
- package/src/tool/baby-percentile-calculator/i18n/en.ts +244 -0
- package/src/tool/baby-percentile-calculator/i18n/es.ts +244 -0
- package/src/tool/baby-percentile-calculator/i18n/fr.ts +244 -0
- package/src/tool/baby-percentile-calculator/index.ts +54 -0
- package/src/tool/baby-percentile-calculator/lmsData.ts +80 -0
- package/src/tool/baby-percentile-calculator/logic.ts +85 -0
- package/src/tool/baby-percentile-calculator/seo.astro +36 -0
- package/src/tool/baby-percentile-calculator/style.css +393 -0
- package/src/tool/baby-size-converter/bibliography.astro +7 -0
- package/src/tool/baby-size-converter/component.astro +289 -0
- package/src/tool/baby-size-converter/data.json +11 -0
- package/src/tool/baby-size-converter/i18n/en.ts +203 -0
- package/src/tool/baby-size-converter/i18n/es.ts +203 -0
- package/src/tool/baby-size-converter/i18n/fr.ts +203 -0
- package/src/tool/baby-size-converter/index.ts +53 -0
- package/src/tool/baby-size-converter/logic.ts +44 -0
- package/src/tool/baby-size-converter/seo.astro +36 -0
- package/src/tool/baby-size-converter/style.css +394 -0
- package/src/tool/fertile-days-estimator/bibliography.astro +7 -0
- package/src/tool/fertile-days-estimator/component.astro +265 -0
- package/src/tool/fertile-days-estimator/i18n/en.ts +258 -0
- package/src/tool/fertile-days-estimator/i18n/es.ts +262 -0
- package/src/tool/fertile-days-estimator/i18n/fr.ts +258 -0
- package/src/tool/fertile-days-estimator/index.ts +47 -0
- package/src/tool/fertile-days-estimator/logic.ts +58 -0
- package/src/tool/fertile-days-estimator/seo.astro +36 -0
- package/src/tool/fertile-days-estimator/style.css +419 -0
- package/src/tool/pregnancy-calculator/bibliography.astro +7 -0
- package/src/tool/pregnancy-calculator/calculator.ts +41 -0
- package/src/tool/pregnancy-calculator/component.astro +432 -0
- package/src/tool/pregnancy-calculator/i18n/en.ts +315 -0
- package/src/tool/pregnancy-calculator/i18n/es.ts +319 -0
- package/src/tool/pregnancy-calculator/i18n/fr.ts +315 -0
- package/src/tool/pregnancy-calculator/index.ts +55 -0
- package/src/tool/pregnancy-calculator/milestones.ts +153 -0
- package/src/tool/pregnancy-calculator/seo.astro +36 -0
- package/src/tool/pregnancy-calculator/store.ts +60 -0
- package/src/tool/pregnancy-calculator/style.css +807 -0
- package/src/tool/vaccination-calendar/bibliography.astro +7 -0
- package/src/tool/vaccination-calendar/component.astro +286 -0
- package/src/tool/vaccination-calendar/i18n/en.ts +170 -0
- package/src/tool/vaccination-calendar/i18n/es.ts +174 -0
- package/src/tool/vaccination-calendar/i18n/fr.ts +170 -0
- package/src/tool/vaccination-calendar/index.ts +47 -0
- package/src/tool/vaccination-calendar/logic.ts +59 -0
- package/src/tool/vaccination-calendar/seo.astro +36 -0
- package/src/tool/vaccination-calendar/style.css +316 -0
- package/src/tool/vaccination-calendar/vaccinationData.ts +21 -0
- package/src/tools.ts +17 -0
- package/src/types.ts +72 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
---
|
|
2
|
+
import './style.css';
|
|
3
|
+
import type { BabyPercentileCalculatorUI } from './index';
|
|
4
|
+
interface Props { ui: BabyPercentileCalculatorUI; }
|
|
5
|
+
const { ui } = Astro.props;
|
|
6
|
+
---
|
|
7
|
+
<div id="baby-percentile-calculator-root" class="baby-percentile-calculator baby-percentile-calculator-boy" data-ui={JSON.stringify(ui)}>
|
|
8
|
+
<div class="baby-percentile-calculator-main">
|
|
9
|
+
<div class="baby-percentile-calculator-left">
|
|
10
|
+
<span class="baby-percentile-calculator-section-marker">{ui.labelMeasurements}</span>
|
|
11
|
+
|
|
12
|
+
<div class="baby-percentile-calculator-input-group">
|
|
13
|
+
<label class="baby-percentile-calculator-input-label">{ui.labelSex}</label>
|
|
14
|
+
<div class="baby-percentile-calculator-sex-selector">
|
|
15
|
+
<button
|
|
16
|
+
id="bpc-btn-boy"
|
|
17
|
+
class="baby-percentile-calculator-sex-btn"
|
|
18
|
+
data-sex="boy"
|
|
19
|
+
data-active="true"
|
|
20
|
+
type="button"
|
|
21
|
+
>{ui.sexBoy}</button>
|
|
22
|
+
<button
|
|
23
|
+
id="bpc-btn-girl"
|
|
24
|
+
class="baby-percentile-calculator-sex-btn"
|
|
25
|
+
data-sex="girl"
|
|
26
|
+
data-active="false"
|
|
27
|
+
type="button"
|
|
28
|
+
>{ui.sexGirl}</button>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div class="baby-percentile-calculator-input-group">
|
|
33
|
+
<div class="baby-percentile-calculator-unit-nav">
|
|
34
|
+
<button
|
|
35
|
+
id="bpc-unit-months"
|
|
36
|
+
class="baby-percentile-calculator-unit-tab"
|
|
37
|
+
data-unit="months"
|
|
38
|
+
data-active="true"
|
|
39
|
+
type="button"
|
|
40
|
+
>{ui.unitMonths}</button>
|
|
41
|
+
<button
|
|
42
|
+
id="bpc-unit-years"
|
|
43
|
+
class="baby-percentile-calculator-unit-tab"
|
|
44
|
+
data-unit="yearsMonths"
|
|
45
|
+
data-active="false"
|
|
46
|
+
type="button"
|
|
47
|
+
>{ui.unitYearsMonths}</button>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div class="baby-percentile-calculator-input-group">
|
|
52
|
+
<div class="baby-percentile-calculator-stepper-box">
|
|
53
|
+
<button id="bpc-step-minus-years" class="baby-percentile-calculator-btn-step" type="button" data-hidden="true" style="display:none;">-</button>
|
|
54
|
+
<div id="bpc-years-view" class="baby-percentile-calculator-val-view" style="display:none;">
|
|
55
|
+
<span id="bpc-years-val" class="baby-percentile-calculator-val-big">0</span>
|
|
56
|
+
<span class="baby-percentile-calculator-val-sub">años</span>
|
|
57
|
+
</div>
|
|
58
|
+
<button id="bpc-step-plus-years" class="baby-percentile-calculator-btn-step" type="button" data-hidden="true" style="display:none;">+</button>
|
|
59
|
+
|
|
60
|
+
<button id="bpc-step-minus" class="baby-percentile-calculator-btn-step" type="button">-</button>
|
|
61
|
+
<div class="baby-percentile-calculator-val-view">
|
|
62
|
+
<span id="bpc-age-val" class="baby-percentile-calculator-val-big">0</span>
|
|
63
|
+
<span class="baby-percentile-calculator-val-sub">{ui.labelMonths}</span>
|
|
64
|
+
</div>
|
|
65
|
+
<button id="bpc-step-plus" class="baby-percentile-calculator-btn-step" type="button">+</button>
|
|
66
|
+
</div>
|
|
67
|
+
<input
|
|
68
|
+
id="bpc-slider"
|
|
69
|
+
class="baby-percentile-calculator-slider-line"
|
|
70
|
+
type="range"
|
|
71
|
+
min="0"
|
|
72
|
+
max="60"
|
|
73
|
+
value="0"
|
|
74
|
+
step="1"
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div class="baby-percentile-calculator-grid-2">
|
|
79
|
+
<div class="baby-percentile-calculator-input-group">
|
|
80
|
+
<label for="bpc-weight" class="baby-percentile-calculator-input-label">{ui.labelWeight}</label>
|
|
81
|
+
<input
|
|
82
|
+
id="bpc-weight"
|
|
83
|
+
class="baby-percentile-calculator-num-input"
|
|
84
|
+
type="number"
|
|
85
|
+
min="1"
|
|
86
|
+
max="30"
|
|
87
|
+
step="0.1"
|
|
88
|
+
placeholder="3.5"
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
<div class="baby-percentile-calculator-input-group">
|
|
92
|
+
<label for="bpc-height" class="baby-percentile-calculator-input-label">{ui.labelHeight}</label>
|
|
93
|
+
<input
|
|
94
|
+
id="bpc-height"
|
|
95
|
+
class="baby-percentile-calculator-num-input"
|
|
96
|
+
type="number"
|
|
97
|
+
min="30"
|
|
98
|
+
max="130"
|
|
99
|
+
step="0.1"
|
|
100
|
+
placeholder="50"
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div class="baby-percentile-calculator-history-actions">
|
|
106
|
+
<button id="bpc-btn-save" class="baby-percentile-calculator-btn-secondary" type="button">{ui.btnAddHistory}</button>
|
|
107
|
+
<button id="bpc-btn-clear" class="baby-percentile-calculator-btn-clear" type="button">{ui.btnClearHistory}</button>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div class="baby-percentile-calculator-right">
|
|
112
|
+
<span class="baby-percentile-calculator-section-marker">{ui.labelDashboard}</span>
|
|
113
|
+
|
|
114
|
+
<div class="baby-percentile-calculator-res-grid">
|
|
115
|
+
<div class="baby-percentile-calculator-res-item">
|
|
116
|
+
<span class="baby-percentile-calculator-res-title">{ui.labelWeight2}</span>
|
|
117
|
+
<span id="bpc-res-weight" class="baby-percentile-calculator-percent-val">—</span>
|
|
118
|
+
<span id="bpc-res-weight-desc" class="baby-percentile-calculator-res-desc">{ui.labelCalculating}</span>
|
|
119
|
+
</div>
|
|
120
|
+
<div class="baby-percentile-calculator-res-item">
|
|
121
|
+
<span class="baby-percentile-calculator-res-title">{ui.labelHeight2}</span>
|
|
122
|
+
<span id="bpc-res-height" class="baby-percentile-calculator-percent-val">—</span>
|
|
123
|
+
<span id="bpc-res-height-desc" class="baby-percentile-calculator-res-desc">{ui.labelCalculating}</span>
|
|
124
|
+
</div>
|
|
125
|
+
<div class="baby-percentile-calculator-res-item">
|
|
126
|
+
<span class="baby-percentile-calculator-res-title">{ui.labelBMI}</span>
|
|
127
|
+
<span id="bpc-res-bmi" class="baby-percentile-calculator-percent-val">—</span>
|
|
128
|
+
<span id="bpc-res-bmi-desc" class="baby-percentile-calculator-res-desc">{ui.labelCalculating}</span>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<div id="bpc-alert" class="baby-percentile-calculator-alert">{ui.alertOutOfRange}</div>
|
|
133
|
+
|
|
134
|
+
<div class="baby-percentile-calculator-chart-box">
|
|
135
|
+
<canvas id="bpc-chart"></canvas>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<p class="baby-percentile-calculator-disclaimer">{ui.disclaimer}</p>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<script>
|
|
144
|
+
import { calculatePercentile, getDescriptionKey, getDataSet, calcBMI, buildChartDatasets } from './logic';
|
|
145
|
+
import { interpolateLMS } from './lmsData';
|
|
146
|
+
|
|
147
|
+
const root = document.getElementById('baby-percentile-calculator-root') as HTMLElement;
|
|
148
|
+
const ui = JSON.parse(root.dataset.ui as string) as Record<string, string>;
|
|
149
|
+
|
|
150
|
+
interface HistoryEntry {
|
|
151
|
+
sex: string;
|
|
152
|
+
age: number;
|
|
153
|
+
weight: number;
|
|
154
|
+
height: number;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
interface State {
|
|
158
|
+
sex: string;
|
|
159
|
+
age: number;
|
|
160
|
+
ageUnit: string;
|
|
161
|
+
weight: number;
|
|
162
|
+
height: number;
|
|
163
|
+
history: HistoryEntry[];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const LS_KEY = 'jjlmoya-babies-percentile';
|
|
167
|
+
|
|
168
|
+
const state: State = {
|
|
169
|
+
sex: 'boy',
|
|
170
|
+
age: 0,
|
|
171
|
+
ageUnit: 'months',
|
|
172
|
+
weight: 0,
|
|
173
|
+
height: 0,
|
|
174
|
+
history: [],
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
let chartInstance: { destroy: () => void; data: { datasets: object[] }; update: () => void } | null = null;
|
|
178
|
+
|
|
179
|
+
function getEl(id: string): HTMLElement {
|
|
180
|
+
return document.getElementById(id) as HTMLElement;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function getInput(id: string): HTMLInputElement {
|
|
184
|
+
return document.getElementById(id) as HTMLInputElement;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function renderPercentiles(): void {
|
|
188
|
+
const dataSet = getDataSet(state.sex);
|
|
189
|
+
const resWeight = getEl('bpc-res-weight');
|
|
190
|
+
const resHeight = getEl('bpc-res-height');
|
|
191
|
+
const resBmi = getEl('bpc-res-bmi');
|
|
192
|
+
const resWeightDesc = getEl('bpc-res-weight-desc');
|
|
193
|
+
const resHeightDesc = getEl('bpc-res-height-desc');
|
|
194
|
+
const resBmiDesc = getEl('bpc-res-bmi-desc');
|
|
195
|
+
const alertEl = getEl('bpc-alert');
|
|
196
|
+
|
|
197
|
+
if (state.weight <= 0 || state.height <= 0) {
|
|
198
|
+
resWeight.textContent = '—';
|
|
199
|
+
resHeight.textContent = '—';
|
|
200
|
+
resBmi.textContent = '—';
|
|
201
|
+
resWeightDesc.textContent = ui['labelCalculating'] ?? null;
|
|
202
|
+
resHeightDesc.textContent = ui['labelCalculating'] ?? null;
|
|
203
|
+
resBmiDesc.textContent = ui['labelCalculating'] ?? null;
|
|
204
|
+
alertEl.removeAttribute('data-visible');
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const wLms = interpolateLMS(state.age, dataSet.weight);
|
|
209
|
+
const hLms = interpolateLMS(state.age, dataSet.height);
|
|
210
|
+
const bmi = calcBMI(state.weight, state.height);
|
|
211
|
+
const bmiLms = interpolateLMS(state.age, dataSet.bmi);
|
|
212
|
+
|
|
213
|
+
const pW = calculatePercentile(state.weight, wLms);
|
|
214
|
+
const pH = calculatePercentile(state.height, hLms);
|
|
215
|
+
const pBmi = calculatePercentile(bmi, bmiLms);
|
|
216
|
+
|
|
217
|
+
resWeight.textContent = `P${Math.round(pW)}`;
|
|
218
|
+
resHeight.textContent = `P${Math.round(pH)}`;
|
|
219
|
+
resBmi.textContent = `P${Math.round(pBmi)}`;
|
|
220
|
+
resWeightDesc.textContent = ui[getDescriptionKey(pW)] ?? '';
|
|
221
|
+
resHeightDesc.textContent = ui[getDescriptionKey(pH)] ?? '';
|
|
222
|
+
resBmiDesc.textContent = ui[getDescriptionKey(pBmi)] ?? '';
|
|
223
|
+
|
|
224
|
+
const outOfRange = pW < 3 || pW > 97 || pH < 3 || pH > 97;
|
|
225
|
+
if (outOfRange) {
|
|
226
|
+
alertEl.setAttribute('data-visible', 'true');
|
|
227
|
+
} else {
|
|
228
|
+
alertEl.removeAttribute('data-visible');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function initChart(): Promise<void> {
|
|
233
|
+
const { Chart, registerables } = await import('chart.js/auto');
|
|
234
|
+
Chart.register(...registerables);
|
|
235
|
+
const canvas = document.getElementById('bpc-chart') as HTMLCanvasElement;
|
|
236
|
+
chartInstance = new Chart(canvas, {
|
|
237
|
+
type: 'line',
|
|
238
|
+
data: { datasets: buildChartDatasets(state.sex) as never[] },
|
|
239
|
+
options: {
|
|
240
|
+
responsive: true,
|
|
241
|
+
maintainAspectRatio: true,
|
|
242
|
+
plugins: { legend: { display: false } },
|
|
243
|
+
scales: {
|
|
244
|
+
x: { type: 'linear', min: 0, max: 60 },
|
|
245
|
+
y: { type: 'linear' },
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function updateChart(): void {
|
|
252
|
+
if (!chartInstance) return;
|
|
253
|
+
chartInstance.data.datasets = buildChartDatasets(state.sex) as never[];
|
|
254
|
+
chartInstance.update();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function syncAgeDisplay(): void {
|
|
258
|
+
const slider = getInput('bpc-slider');
|
|
259
|
+
slider.value = String(state.age);
|
|
260
|
+
getEl('bpc-age-val').textContent = String(state.age % 12);
|
|
261
|
+
|
|
262
|
+
if (state.ageUnit === 'yearsMonths') {
|
|
263
|
+
getEl('bpc-years-val').textContent = String(Math.floor(state.age / 12));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function setAge(val: number): void {
|
|
268
|
+
state.age = Math.max(0, Math.min(60, val));
|
|
269
|
+
syncAgeDisplay();
|
|
270
|
+
renderPercentiles();
|
|
271
|
+
updateChart();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function setSex(sex: string): void {
|
|
275
|
+
state.sex = sex;
|
|
276
|
+
const btnBoy = getEl('bpc-btn-boy');
|
|
277
|
+
const btnGirl = getEl('bpc-btn-girl');
|
|
278
|
+
btnBoy.setAttribute('data-active', sex === 'boy' ? 'true' : 'false');
|
|
279
|
+
btnGirl.setAttribute('data-active', sex === 'girl' ? 'true' : 'false');
|
|
280
|
+
|
|
281
|
+
if (sex === 'boy') {
|
|
282
|
+
root.classList.add('baby-percentile-calculator-boy');
|
|
283
|
+
} else {
|
|
284
|
+
root.classList.remove('baby-percentile-calculator-boy');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
renderPercentiles();
|
|
288
|
+
updateChart();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function setUnit(unit: string): void {
|
|
292
|
+
state.ageUnit = unit;
|
|
293
|
+
const tabMonths = getEl('bpc-unit-months');
|
|
294
|
+
const tabYears = getEl('bpc-unit-years');
|
|
295
|
+
tabMonths.setAttribute('data-active', unit === 'months' ? 'true' : 'false');
|
|
296
|
+
tabYears.setAttribute('data-active', unit === 'yearsMonths' ? 'true' : 'false');
|
|
297
|
+
|
|
298
|
+
const yearsControls = [
|
|
299
|
+
getEl('bpc-step-minus-years'),
|
|
300
|
+
getEl('bpc-step-plus-years'),
|
|
301
|
+
getEl('bpc-years-view'),
|
|
302
|
+
];
|
|
303
|
+
|
|
304
|
+
yearsControls.forEach(el => {
|
|
305
|
+
if (unit === 'yearsMonths') {
|
|
306
|
+
el.style.display = '';
|
|
307
|
+
} else {
|
|
308
|
+
el.style.display = 'none';
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
const monthLabel = getEl('bpc-age-val').nextElementSibling as HTMLElement;
|
|
313
|
+
if (monthLabel) {
|
|
314
|
+
monthLabel.textContent = (unit === 'yearsMonths' ? ui['labelMonths'] : ui['labelMonths']) ?? null;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
syncAgeDisplay();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function loadHistory(): void {
|
|
321
|
+
try {
|
|
322
|
+
const raw = localStorage.getItem(LS_KEY);
|
|
323
|
+
if (raw) {
|
|
324
|
+
const parsed = JSON.parse(raw) as HistoryEntry[];
|
|
325
|
+
state.history = Array.isArray(parsed) ? parsed : [];
|
|
326
|
+
}
|
|
327
|
+
} catch {
|
|
328
|
+
state.history = [];
|
|
329
|
+
}
|
|
330
|
+
renderPercentiles();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function save(): void {
|
|
334
|
+
const entry: HistoryEntry = {
|
|
335
|
+
sex: state.sex,
|
|
336
|
+
age: state.age,
|
|
337
|
+
weight: state.weight,
|
|
338
|
+
height: state.height,
|
|
339
|
+
};
|
|
340
|
+
state.history.push(entry);
|
|
341
|
+
try {
|
|
342
|
+
localStorage.setItem(LS_KEY, JSON.stringify(state.history));
|
|
343
|
+
} catch { }
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function clearHistory(): void {
|
|
347
|
+
state.history = [];
|
|
348
|
+
try {
|
|
349
|
+
localStorage.removeItem(LS_KEY);
|
|
350
|
+
} catch { }
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function bindEvents(): void {
|
|
354
|
+
getEl('bpc-btn-boy').addEventListener('click', () => setSex('boy'));
|
|
355
|
+
getEl('bpc-btn-girl').addEventListener('click', () => setSex('girl'));
|
|
356
|
+
|
|
357
|
+
getEl('bpc-unit-months').addEventListener('click', () => setUnit('months'));
|
|
358
|
+
getEl('bpc-unit-years').addEventListener('click', () => setUnit('yearsMonths'));
|
|
359
|
+
|
|
360
|
+
getEl('bpc-step-minus').addEventListener('click', () => setAge(state.age - 1));
|
|
361
|
+
getEl('bpc-step-plus').addEventListener('click', () => setAge(state.age + 1));
|
|
362
|
+
|
|
363
|
+
getEl('bpc-step-minus-years').addEventListener('click', () => setAge(state.age - 12));
|
|
364
|
+
getEl('bpc-step-plus-years').addEventListener('click', () => setAge(state.age + 12));
|
|
365
|
+
|
|
366
|
+
getInput('bpc-slider').addEventListener('input', (e) => {
|
|
367
|
+
const val = parseInt((e.target as HTMLInputElement).value, 10);
|
|
368
|
+
setAge(val);
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
getInput('bpc-weight').addEventListener('input', (e) => {
|
|
372
|
+
state.weight = parseFloat((e.target as HTMLInputElement).value) || 0;
|
|
373
|
+
renderPercentiles();
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
getInput('bpc-height').addEventListener('input', (e) => {
|
|
377
|
+
state.height = parseFloat((e.target as HTMLInputElement).value) || 0;
|
|
378
|
+
renderPercentiles();
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
getEl('bpc-btn-save').addEventListener('click', () => save());
|
|
382
|
+
getEl('bpc-btn-clear').addEventListener('click', () => clearHistory());
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
loadHistory();
|
|
386
|
+
bindEvents();
|
|
387
|
+
initChart();
|
|
388
|
+
</script>
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import type { BabyPercentileCalculatorLocaleContent } from '../index';
|
|
2
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
3
|
+
|
|
4
|
+
const slug = 'baby-weight-height-percentile';
|
|
5
|
+
const title = 'WHO Percentile Calculator for Babies';
|
|
6
|
+
const description = 'Calculate your baby\'s weight, height and BMI percentile using WHO growth charts (0-5 years).';
|
|
7
|
+
const faq = [
|
|
8
|
+
{
|
|
9
|
+
question: 'What does the 50th percentile mean?',
|
|
10
|
+
answer: 'The 50th percentile means the baby is exactly at the median: half of babies the same age and sex weigh or measure more, and half weigh or measure less. It does not mean it is the ideal value, just the central value of the distribution.',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
question: 'Is a low percentile a sign of a problem?',
|
|
14
|
+
answer: 'Not necessarily. A low percentile (for example P10) can be perfectly normal if it remains stable over time. The growth trend is what matters, not an isolated value. Always consult your pediatrician.',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
question: 'Which growth charts does this calculator use?',
|
|
18
|
+
answer: 'This calculator uses the WHO Child Growth Standards for children aged 0 to 5 years, based on the Multicentre Growth Reference Study (MGRS).',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
question: 'What is BMI in babies?',
|
|
22
|
+
answer: 'Body Mass Index (BMI) in babies is the ratio of weight to height squared. Baby BMI percentiles differ from adult ones and must be interpreted using age- and sex-specific charts.',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
question: 'How often should I measure my baby?',
|
|
26
|
+
answer: 'In the first months, the pediatrician usually monitors growth at each visit. At home you can record weight and height monthly to observe the growth trend.',
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
const howTo = [
|
|
30
|
+
{
|
|
31
|
+
name: 'Select the baby\'s sex',
|
|
32
|
+
text: 'Choose boy or girl to apply the corresponding WHO charts.',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'Enter the age',
|
|
36
|
+
text: 'Use the slider or arrows to set the age in months (0-60 months).',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'Enter weight and height',
|
|
40
|
+
text: 'Type the weight in kilograms and height in centimetres.',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'Check the percentiles',
|
|
44
|
+
text: 'Weight, height and BMI percentiles are calculated automatically using WHO charts.',
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const faqSchema: WithContext<FAQPage> = {
|
|
49
|
+
'@context': 'https://schema.org',
|
|
50
|
+
'@type': 'FAQPage',
|
|
51
|
+
mainEntity: faq.map((item) => ({
|
|
52
|
+
'@type': 'Question',
|
|
53
|
+
name: item.question,
|
|
54
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
|
|
55
|
+
})),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const howToSchema: WithContext<HowTo> = {
|
|
59
|
+
'@context': 'https://schema.org',
|
|
60
|
+
'@type': 'HowTo',
|
|
61
|
+
name: title,
|
|
62
|
+
description,
|
|
63
|
+
step: howTo.map((step) => ({
|
|
64
|
+
'@type': 'HowToStep',
|
|
65
|
+
name: step.name,
|
|
66
|
+
text: step.text,
|
|
67
|
+
})),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const appSchema: WithContext<SoftwareApplication> = {
|
|
71
|
+
'@context': 'https://schema.org',
|
|
72
|
+
'@type': 'SoftwareApplication',
|
|
73
|
+
name: title,
|
|
74
|
+
description,
|
|
75
|
+
applicationCategory: 'UtilitiesApplication',
|
|
76
|
+
operatingSystem: 'Web',
|
|
77
|
+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
|
|
78
|
+
inLanguage: 'en',
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const content: BabyPercentileCalculatorLocaleContent = {
|
|
82
|
+
slug,
|
|
83
|
+
title,
|
|
84
|
+
description,
|
|
85
|
+
ui: {
|
|
86
|
+
labelMeasurements: 'Measurement Data',
|
|
87
|
+
labelSex: 'Baby\'s sex',
|
|
88
|
+
sexBoy: 'Boy',
|
|
89
|
+
sexGirl: 'Girl',
|
|
90
|
+
unitMonths: 'Months only',
|
|
91
|
+
unitYearsMonths: 'Years + months',
|
|
92
|
+
labelWeight: 'Weight (kg)',
|
|
93
|
+
labelHeight: 'Height (cm)',
|
|
94
|
+
btnAddHistory: 'Add to history',
|
|
95
|
+
btnClearHistory: 'Clear data',
|
|
96
|
+
labelDashboard: 'Growth Dashboard',
|
|
97
|
+
labelWeight2: 'Weight',
|
|
98
|
+
labelHeight2: 'Height',
|
|
99
|
+
labelBMI: 'BMI',
|
|
100
|
+
labelCalculating: 'Calculating...',
|
|
101
|
+
disclaimer: 'Percentiles are a statistical tool only. Always consult your pediatrician to interpret results.',
|
|
102
|
+
labelLowRange: 'Low range',
|
|
103
|
+
labelLowNormal: 'Low normal',
|
|
104
|
+
labelNormal: 'Normal',
|
|
105
|
+
labelHighNormal: 'High normal',
|
|
106
|
+
labelHighRange: 'High range',
|
|
107
|
+
alertOutOfRange: 'Value outside P3-P97 range. Please consult your pediatrician.',
|
|
108
|
+
labelMonths: 'Months',
|
|
109
|
+
faqTitle: 'Frequently asked questions',
|
|
110
|
+
bibliographyTitle: 'References',
|
|
111
|
+
},
|
|
112
|
+
seo: [
|
|
113
|
+
{
|
|
114
|
+
type: 'title',
|
|
115
|
+
text: 'WHO Percentile Calculator: Guide to Understanding Your Baby\'s Growth',
|
|
116
|
+
level: 2,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
type: 'summary',
|
|
120
|
+
title: '5 key points for interpreting your baby\'s percentiles',
|
|
121
|
+
items: [
|
|
122
|
+
'No single percentile defines your baby\'s health: the growth trend over time is what matters.',
|
|
123
|
+
'Percentiles between P3 and P97 are considered within the statistical normal range.',
|
|
124
|
+
'BMI in babies follows its own curves and should not be compared with adult reference values.',
|
|
125
|
+
'WHO charts are based on babies raised in optimal conditions, including exclusive breastfeeding in the first months.',
|
|
126
|
+
'Always share growth records with your paediatrician for proper clinical interpretation.',
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
type: 'title',
|
|
131
|
+
text: 'How to measure your baby correctly',
|
|
132
|
+
level: 3,
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
type: 'list',
|
|
136
|
+
items: [
|
|
137
|
+
'Weigh the baby without clothes and on the same scale whenever possible.',
|
|
138
|
+
'Measure length lying down (recumbent) until 24 months.',
|
|
139
|
+
'From 2 years of age, measure height in a standing position.',
|
|
140
|
+
'Take measurements at the same time of day for greater consistency.',
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
type: 'tip',
|
|
145
|
+
html: 'A percentile below P3 does not automatically indicate a health problem. The most important thing is to observe whether the growth curve maintains its trend over time.',
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
type: 'title',
|
|
149
|
+
text: 'Interpreting percentiles',
|
|
150
|
+
level: 3,
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
type: 'table',
|
|
154
|
+
headers: ['Percentile', 'Interpretation', 'Recommended action'],
|
|
155
|
+
rows: [
|
|
156
|
+
['P97 or above', 'High range', 'Discuss with the paediatrician at the next visit'],
|
|
157
|
+
['P85 to P97', 'High normal', 'Routine follow-up'],
|
|
158
|
+
['P15 to P85', 'Normal', 'No special action required'],
|
|
159
|
+
['P3 to P15', 'Low normal', 'Routine follow-up'],
|
|
160
|
+
['P3 or below', 'Low range', 'Consult the paediatrician'],
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
type: 'title',
|
|
165
|
+
text: 'WHO vs Orbegozo Charts',
|
|
166
|
+
level: 3,
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
type: 'comparative',
|
|
170
|
+
columns: 2,
|
|
171
|
+
items: [
|
|
172
|
+
{
|
|
173
|
+
title: 'WHO Charts',
|
|
174
|
+
description: 'International standard based on babies from 6 countries raised in optimal conditions.',
|
|
175
|
+
points: [
|
|
176
|
+
'Validated international standard',
|
|
177
|
+
'Based on babies from 6 countries in optimal conditions',
|
|
178
|
+
'Up to date with current scientific support',
|
|
179
|
+
'May not reflect local genetic variations',
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
title: 'Orbegozo Charts',
|
|
184
|
+
description: 'Charts adapted to the Spanish population, widely used in previous decades.',
|
|
185
|
+
points: [
|
|
186
|
+
'Adapted to the Spanish population',
|
|
187
|
+
'Widely used in Spain in previous decades',
|
|
188
|
+
'Based on older reference population',
|
|
189
|
+
'Lower international adoption',
|
|
190
|
+
],
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
type: 'stats',
|
|
196
|
+
columns: 3,
|
|
197
|
+
items: [
|
|
198
|
+
{ value: 'WHO 0-5 years', label: 'Reference standard' },
|
|
199
|
+
{ value: 'Growth trend', label: 'Key variable' },
|
|
200
|
+
{ value: 'LMS parameters', label: 'Technical precision' },
|
|
201
|
+
],
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
type: 'glossary',
|
|
205
|
+
items: [
|
|
206
|
+
{
|
|
207
|
+
term: 'Z-Score',
|
|
208
|
+
definition: 'Number of standard deviations a value is from the mean of the reference population. A Z-Score of 0 is equivalent to the 50th percentile.',
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
term: 'Harmony',
|
|
212
|
+
definition: 'Appropriate proportion between weight and height. A baby may have a low weight percentile but still be harmonious if their height percentile is similarly low.',
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
term: 'Anthropometry',
|
|
216
|
+
definition: 'Set of body measurements (weight, height, head circumference, BMI) used to evaluate growth and nutritional status.',
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
term: 'LMS parameters',
|
|
220
|
+
definition: 'Statistical method (L=Box-Cox, M=median, S=coefficient of variation) used by the WHO to build percentile tables and convert any measurement into a Z-Score.',
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
faqTitle: "Frequently asked questions",
|
|
226
|
+
faq,
|
|
227
|
+
bibliographyTitle: "References",
|
|
228
|
+
bibliography: [
|
|
229
|
+
{
|
|
230
|
+
name: 'WHO Child Growth Standards: Methods and development',
|
|
231
|
+
url: 'https://www.who.int/publications/i/item/924154693X',
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
name: 'WHO Child Growth Standards: Length/height-for-age, weight-for-age, weight-for-length, weight-for-height and body mass index-for-age',
|
|
235
|
+
url: 'https://www.who.int/tools/child-growth-standards/standards',
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
name: 'Comparison of the WHO Child Growth Standards and the CDC 2000 Growth Charts - de Onis M et al.',
|
|
239
|
+
url: 'https://doi.org/10.1093/jn/137.1.144S',
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
howTo,
|
|
243
|
+
schemas: [faqSchema as any, howToSchema as any, appSchema],
|
|
244
|
+
};
|