@jjlmoya/utils-sports 1.50.0 → 1.51.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 (33) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +2 -0
  3. package/src/entries.ts +2 -0
  4. package/src/tool/caloriesBurnedCalculator/bibliography.astro +6 -0
  5. package/src/tool/caloriesBurnedCalculator/bibliography.ts +18 -0
  6. package/src/tool/caloriesBurnedCalculator/calories-burned-by-exercise-calculator.css +458 -0
  7. package/src/tool/caloriesBurnedCalculator/component.astro +139 -0
  8. package/src/tool/caloriesBurnedCalculator/controller.ts +168 -0
  9. package/src/tool/caloriesBurnedCalculator/dom-views.ts +101 -0
  10. package/src/tool/caloriesBurnedCalculator/entry.ts +30 -0
  11. package/src/tool/caloriesBurnedCalculator/evaluator.ts +12 -0
  12. package/src/tool/caloriesBurnedCalculator/i18n/de.ts +222 -0
  13. package/src/tool/caloriesBurnedCalculator/i18n/en.ts +222 -0
  14. package/src/tool/caloriesBurnedCalculator/i18n/es.ts +222 -0
  15. package/src/tool/caloriesBurnedCalculator/i18n/fr.ts +222 -0
  16. package/src/tool/caloriesBurnedCalculator/i18n/id.ts +222 -0
  17. package/src/tool/caloriesBurnedCalculator/i18n/it.ts +222 -0
  18. package/src/tool/caloriesBurnedCalculator/i18n/ja.ts +222 -0
  19. package/src/tool/caloriesBurnedCalculator/i18n/ko.ts +222 -0
  20. package/src/tool/caloriesBurnedCalculator/i18n/nl.ts +222 -0
  21. package/src/tool/caloriesBurnedCalculator/i18n/pl.ts +222 -0
  22. package/src/tool/caloriesBurnedCalculator/i18n/pt.ts +222 -0
  23. package/src/tool/caloriesBurnedCalculator/i18n/ru.ts +222 -0
  24. package/src/tool/caloriesBurnedCalculator/i18n/sv.ts +222 -0
  25. package/src/tool/caloriesBurnedCalculator/i18n/tr.ts +222 -0
  26. package/src/tool/caloriesBurnedCalculator/i18n/zh.ts +222 -0
  27. package/src/tool/caloriesBurnedCalculator/index.ts +10 -0
  28. package/src/tool/caloriesBurnedCalculator/logic.test.ts +50 -0
  29. package/src/tool/caloriesBurnedCalculator/logic.ts +101 -0
  30. package/src/tool/caloriesBurnedCalculator/seo.astro +14 -0
  31. package/src/tool/caloriesBurnedCalculator/storage.ts +43 -0
  32. package/src/tool/caloriesBurnedCalculator/ui.ts +43 -0
  33. package/src/tools.ts +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-sports",
3
- "version": "1.50.0",
3
+ "version": "1.51.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -13,6 +13,7 @@ import { penaltyShootout } from '../tool/penaltyShootout/entry';
13
13
  import { swimCssCalculator } from '../tool/swimCssCalculator/entry';
14
14
  import { vo2MaxCalculator } from '../tool/vo2MaxCalculator/entry';
15
15
  import { roundRobinLeagueManager } from '../tool/roundRobinLeagueManager/entry';
16
+ import { caloriesBurnedCalculator } from '../tool/caloriesBurnedCalculator/entry';
16
17
 
17
18
  export const sportsCategory: SportsCategoryEntry = {
18
19
  icon: 'mdi:soccer',
@@ -31,6 +32,7 @@ export const sportsCategory: SportsCategoryEntry = {
31
32
  swimCssCalculator,
32
33
  vo2MaxCalculator,
33
34
  roundRobinLeagueManager,
35
+ caloriesBurnedCalculator,
34
36
  ] as unknown as SportsToolEntry<Record<string, string>>[],
35
37
  i18n: {
36
38
  es: () => import('./i18n/es').then((m) => m.content),
package/src/entries.ts CHANGED
@@ -67,6 +67,7 @@ import { swimCssCalculator } from './tool/swimCssCalculator/entry';
67
67
  import { volleyballScoreKeeper } from './tool/volleyballScoreKeeper/entry';
68
68
  import { vo2MaxCalculator } from './tool/vo2MaxCalculator/entry';
69
69
  import { roundRobinLeagueManager } from './tool/roundRobinLeagueManager/entry';
70
+ import { caloriesBurnedCalculator } from './tool/caloriesBurnedCalculator/entry';
70
71
 
71
72
  export const ALL_ENTRIES = [
72
73
  basketScoreKeeper,
@@ -91,4 +92,5 @@ export const ALL_ENTRIES = [
91
92
  volleyballScoreKeeper,
92
93
  vo2MaxCalculator,
93
94
  roundRobinLeagueManager,
95
+ caloriesBurnedCalculator,
94
96
  ];
@@ -0,0 +1,6 @@
1
+ ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import { bibliographyEntries } from './bibliography';
4
+ ---
5
+
6
+ <SharedBibliography links={bibliographyEntries} />
@@ -0,0 +1,18 @@
1
+ import type { BibliographyEntry } from '../../types';
2
+
3
+ export const bibliographyEntries: BibliographyEntry[] = [
4
+ {
5
+ name: '2024 Adult Compendium of Physical Activities: A third update of the energy costs of human activities',
6
+ url: 'https://pmc.ncbi.nlm.nih.gov/articles/PMC10818145/',
7
+ },
8
+ {
9
+ name: 'The Compendium of Physical Activities 2024 Adult Compendium',
10
+ url: 'https://pacompendium.com/adult-compendium/',
11
+ },
12
+ {
13
+ name: 'Metabolic equivalents in exercise testing and exercise prescription',
14
+ url: 'https://pubmed.ncbi.nlm.nih.gov/2204507/',
15
+ },
16
+ ];
17
+
18
+ export const bibliography = bibliographyEntries;
@@ -0,0 +1,458 @@
1
+ :root {
2
+ --n-ink: #18221e;
3
+ --n-muted: #66756e;
4
+ --n-paper: #f7f6f0;
5
+ --n-surface: #fff;
6
+ --n-surface-soft: #eef1e9;
7
+ --n-line: #d6ded5;
8
+ --n-accent: #d85e31;
9
+ --n-accent-strong: #a84321;
10
+ --n-accent-soft: #f4d4c7;
11
+ --n-teal: #27736d;
12
+ --n-teal-soft: #cbe3dc;
13
+ --n-shadow: 0 18px 50px rgba(25, 43, 33, 0.1);
14
+ }
15
+
16
+ .theme-dark {
17
+ --n-ink: #f7f3ea;
18
+ --n-muted: #b9b3ad;
19
+ --n-paper: #18191f;
20
+ --n-surface: #23242b;
21
+ --n-surface-soft: #2b2a33;
22
+ --n-line: #474650;
23
+ --n-accent: #ff8a5c;
24
+ --n-accent-strong: #ffc0a3;
25
+ --n-accent-soft: #4a2d2a;
26
+ --n-teal: #7ec7d0;
27
+ --n-teal-soft: #293f47;
28
+ --n-shadow: 0 18px 50px rgba(0, 0, 0, 0.28);
29
+ --n-orbit-label-shadow: 0 1px 3px rgba(0, 0, 0, 0.62);
30
+ }
31
+
32
+ .theme-dark .calories-burned-calculator {
33
+ --n-ink: #f7f3ea;
34
+ --n-muted: #b9b3ad;
35
+ --n-paper: #18191f;
36
+ --n-surface: #23242b;
37
+ --n-surface-soft: #2b2a33;
38
+ --n-line: #474650;
39
+ --n-accent: #ff8a5c;
40
+ --n-accent-strong: #ffc0a3;
41
+ --n-accent-soft: #4a2d2a;
42
+ --n-teal: #7ec7d0;
43
+ --n-teal-soft: #293f47;
44
+ --n-shadow: 0 18px 50px rgba(0, 0, 0, 0.28);
45
+ --n-orbit-label-shadow: 0 1px 3px rgba(0, 0, 0, 0.62);
46
+ }
47
+
48
+ .calories-burned-calculator {
49
+ color: var(--n-ink);
50
+ background: var(--n-paper);
51
+ border: 1px solid var(--n-line);
52
+ box-shadow: var(--n-shadow);
53
+ overflow: hidden;
54
+ }
55
+
56
+ .calories-tool-grid {
57
+ display: grid;
58
+ grid-template-columns: minmax(280px, 0.78fr) minmax(360px, 1.22fr);
59
+ min-height: 650px;
60
+ }
61
+
62
+ .calories-control-panel {
63
+ display: grid;
64
+ align-content: start;
65
+ gap: 1.25rem;
66
+ padding: clamp(1.5rem, 4vw, 3rem);
67
+ background: var(--n-surface-soft);
68
+ border-right: 1px solid var(--n-line);
69
+ }
70
+
71
+ .calories-panel-heading,
72
+ .calories-result-heading {
73
+ display: grid;
74
+ gap: 0.45rem;
75
+ }
76
+
77
+ .calories-kicker {
78
+ margin: 0;
79
+ color: var(--n-accent-strong);
80
+ font-size: 0.7rem;
81
+ font-weight: 800;
82
+ letter-spacing: 0.13em;
83
+ text-transform: uppercase;
84
+ }
85
+
86
+ .calories-flow {
87
+ max-width: 33rem;
88
+ margin: 0;
89
+ color: var(--n-muted);
90
+ font-size: 0.9rem;
91
+ line-height: 1.55;
92
+ }
93
+
94
+ .calories-field,
95
+ .calories-unit-block {
96
+ display: grid;
97
+ gap: 0.55rem;
98
+ }
99
+
100
+ .calories-field-wide {
101
+ grid-column: 1 / -1;
102
+ }
103
+
104
+ .calories-label {
105
+ color: var(--n-ink);
106
+ font-size: 0.78rem;
107
+ font-weight: 800;
108
+ letter-spacing: 0.02em;
109
+ }
110
+
111
+ .calories-select {
112
+ position: relative;
113
+ }
114
+
115
+ .calories-select-trigger,
116
+ .calories-number,
117
+ .calories-intensity-button,
118
+ .calories-unit-toggle button,
119
+ .calories-reset {
120
+ font: inherit;
121
+ }
122
+
123
+ .calories-select-trigger {
124
+ display: flex;
125
+ align-items: center;
126
+ justify-content: space-between;
127
+ width: 100%;
128
+ min-height: 3.1rem;
129
+ padding: 0.8rem 1rem;
130
+ color: var(--n-ink);
131
+ background: var(--n-surface);
132
+ border: 1px solid var(--n-line);
133
+ cursor: pointer;
134
+ text-align: left;
135
+ }
136
+
137
+ .calories-select-mark {
138
+ width: 0.55rem;
139
+ height: 0.55rem;
140
+ border-right: 2px solid var(--n-accent-strong);
141
+ border-bottom: 2px solid var(--n-accent-strong);
142
+ transform: rotate(45deg) translateY(-2px);
143
+ }
144
+
145
+ .calories-select-menu {
146
+ position: absolute;
147
+ z-index: 5;
148
+ top: calc(100% + 0.35rem);
149
+ width: 100%;
150
+ padding: 0.35rem;
151
+ background: var(--n-surface);
152
+ border: 1px solid var(--n-line);
153
+ box-shadow: var(--n-shadow);
154
+ }
155
+
156
+ .calories-select-options {
157
+ display: grid;
158
+ max-height: 17rem;
159
+ overflow: auto;
160
+ }
161
+
162
+ .calories-activity-option {
163
+ padding: 0.75rem 0.8rem;
164
+ color: var(--n-ink);
165
+ background: transparent;
166
+ border: 0;
167
+ cursor: pointer;
168
+ text-align: left;
169
+ }
170
+
171
+ .calories-activity-option:hover,
172
+ .calories-activity-option.is-selected {
173
+ color: var(--n-accent-strong);
174
+ background: var(--n-accent-soft);
175
+ }
176
+
177
+ .calories-intensity-row,
178
+ .calories-unit-toggle {
179
+ display: grid;
180
+ grid-template-columns: repeat(3, 1fr);
181
+ gap: 0.4rem;
182
+ }
183
+
184
+ .calories-intensity-button,
185
+ .calories-unit-toggle button {
186
+ min-height: 2.65rem;
187
+ padding: 0.55rem 0.4rem;
188
+ color: var(--n-muted);
189
+ background: var(--n-surface);
190
+ border: 1px solid var(--n-line);
191
+ cursor: pointer;
192
+ font-size: 0.76rem;
193
+ font-weight: 700;
194
+ }
195
+
196
+ .calories-intensity-button.is-active,
197
+ .calories-unit-toggle button.is-active {
198
+ color: var(--n-surface);
199
+ background: var(--n-teal);
200
+ border-color: var(--n-teal);
201
+ }
202
+
203
+ .calories-number-wrap {
204
+ position: relative;
205
+ }
206
+
207
+ .calories-number {
208
+ width: 100%;
209
+ min-height: 3rem;
210
+ padding: 0.65rem 3.25rem 0.65rem 0.9rem;
211
+ color: var(--n-ink);
212
+ background: var(--n-surface);
213
+ border: 1px solid var(--n-line);
214
+ font-size: 1rem;
215
+ font-weight: 750;
216
+ }
217
+
218
+ .calories-number-unit {
219
+ position: absolute;
220
+ top: 50%;
221
+ right: 0.9rem;
222
+ color: var(--n-muted);
223
+ font-size: 0.78rem;
224
+ font-weight: 800;
225
+ transform: translateY(-50%);
226
+ }
227
+
228
+ .calories-range {
229
+ width: 100%;
230
+ accent-color: var(--n-accent);
231
+ cursor: pointer;
232
+ }
233
+
234
+ .calories-hint,
235
+ .calories-method-note,
236
+ .calories-estimate-notice {
237
+ margin: 0;
238
+ color: var(--n-muted);
239
+ font-size: 0.75rem;
240
+ line-height: 1.5;
241
+ }
242
+
243
+ .calories-unit-block {
244
+ padding-top: 0.55rem;
245
+ }
246
+
247
+ .calories-unit-toggle {
248
+ grid-template-columns: repeat(2, 1fr);
249
+ }
250
+
251
+ .calories-reset {
252
+ justify-self: start;
253
+ padding: 0.3rem 0;
254
+ color: var(--n-accent-strong);
255
+ background: transparent;
256
+ border: 0;
257
+ border-bottom: 1px solid currentcolor;
258
+ cursor: pointer;
259
+ font-size: 0.78rem;
260
+ font-weight: 800;
261
+ }
262
+
263
+ .calories-result-panel {
264
+ display: grid;
265
+ align-content: start;
266
+ gap: 1.25rem;
267
+ padding: clamp(1.5rem, 5vw, 4rem);
268
+ background: var(--n-surface);
269
+ }
270
+
271
+ .calories-result-heading h2 {
272
+ max-width: 17rem;
273
+ margin: 0;
274
+ font-size: clamp(1.65rem, 4vw, 2.7rem);
275
+ line-height: 0.98;
276
+ }
277
+
278
+ .calories-orbit {
279
+ position: relative;
280
+ display: grid;
281
+ place-items: center;
282
+ width: min(100%, 300px);
283
+ aspect-ratio: 1;
284
+ margin: 0 auto;
285
+ }
286
+
287
+ .calories-orbit svg {
288
+ width: 100%;
289
+ height: 100%;
290
+ overflow: visible;
291
+ }
292
+
293
+ .calories-orbit-track,
294
+ .calories-orbit-arc {
295
+ fill: none;
296
+ stroke-width: 7;
297
+ }
298
+
299
+ .calories-orbit-track {
300
+ stroke: var(--n-surface-soft);
301
+ }
302
+
303
+ .calories-orbit-arc {
304
+ stroke: var(--n-accent);
305
+ stroke-dasharray: 628;
306
+ stroke-dashoffset: 280;
307
+ transform: rotate(-90deg);
308
+ transform-origin: center;
309
+ transition: stroke-dashoffset 350ms ease;
310
+ }
311
+
312
+ .calories-orbit-core {
313
+ fill: var(--n-accent-soft);
314
+ }
315
+
316
+ .calories-energy-flame {
317
+ fill: var(--n-accent);
318
+ transform-origin: 120px 120px;
319
+ transition: transform 350ms ease;
320
+ }
321
+
322
+ .calories-orbit-dot {
323
+ fill: var(--n-teal);
324
+ }
325
+
326
+ .calories-orbit-dot-alt {
327
+ fill: var(--n-accent-strong);
328
+ }
329
+
330
+ .calories-orbit-copy {
331
+ position: absolute;
332
+ display: grid;
333
+ place-items: center;
334
+ line-height: 1;
335
+ }
336
+
337
+ .calories-orbit-copy strong {
338
+ font-size: clamp(2.8rem, 7vw, 4.5rem);
339
+ letter-spacing: -0.08em;
340
+ }
341
+
342
+ .calories-orbit-copy span {
343
+ margin-top: 0.45rem;
344
+ color: var(--n-muted);
345
+ font-size: 0.78rem;
346
+ font-weight: 800;
347
+ letter-spacing: 0.16em;
348
+ text-transform: uppercase;
349
+ }
350
+
351
+ .theme-dark .calories-orbit-copy span {
352
+ color: var(--n-ink);
353
+ text-shadow: var(--n-orbit-label-shadow);
354
+ }
355
+
356
+ .calories-result-stats,
357
+ .calories-metric-strip {
358
+ display: grid;
359
+ grid-template-columns: 1.25fr 1fr 1fr;
360
+ gap: 0.7rem;
361
+ }
362
+
363
+ .calories-stat,
364
+ .calories-metric-strip > div {
365
+ display: grid;
366
+ gap: 0.3rem;
367
+ min-width: 0;
368
+ padding-top: 0.8rem;
369
+ border-top: 2px solid var(--n-line);
370
+ }
371
+
372
+ .calories-stat-primary {
373
+ border-color: var(--n-accent);
374
+ }
375
+
376
+ .calories-stat span,
377
+ .calories-metric-strip span {
378
+ color: var(--n-muted);
379
+ font-size: 0.7rem;
380
+ line-height: 1.35;
381
+ }
382
+
383
+ .calories-stat strong,
384
+ .calories-metric-strip strong {
385
+ font-size: 1rem;
386
+ line-height: 1.2;
387
+ }
388
+
389
+ .calories-metric-strip {
390
+ grid-template-columns: repeat(3, 1fr);
391
+ padding: 1rem 0;
392
+ border-block: 1px solid var(--n-line);
393
+ }
394
+
395
+ .calories-metric-strip > div {
396
+ padding: 0 0.7rem;
397
+ border-top: 0;
398
+ border-left: 1px solid var(--n-line);
399
+ }
400
+
401
+ .calories-metric-strip > div:first-child {
402
+ padding-left: 0;
403
+ border-left: 0;
404
+ }
405
+
406
+ .calories-method-note {
407
+ max-width: 42rem;
408
+ }
409
+
410
+ .calories-estimate-notice {
411
+ padding: 0.85rem 1rem;
412
+ color: var(--n-accent-strong);
413
+ background: var(--n-accent-soft);
414
+ border-left: 3px solid var(--n-accent);
415
+ }
416
+
417
+ [data-calories-tool] button:focus-visible,
418
+ [data-calories-tool] input:focus-visible {
419
+ outline: 3px solid var(--n-accent);
420
+ outline-offset: 3px;
421
+ }
422
+
423
+ @media (max-width: 800px) {
424
+ .calories-tool-grid {
425
+ grid-template-columns: 1fr;
426
+ }
427
+
428
+ .calories-control-panel {
429
+ border-right: 0;
430
+ border-bottom: 1px solid var(--n-line);
431
+ }
432
+ }
433
+
434
+ @media (max-width: 520px) {
435
+ .calories-control-panel,
436
+ .calories-result-panel {
437
+ padding: 1.25rem;
438
+ }
439
+
440
+ .calories-result-stats {
441
+ grid-template-columns: 1fr 1fr;
442
+ }
443
+
444
+ .calories-stat-primary {
445
+ grid-column: 1 / -1;
446
+ }
447
+
448
+ .calories-metric-strip {
449
+ grid-template-columns: 1fr;
450
+ gap: 0.8rem;
451
+ }
452
+
453
+ .calories-metric-strip > div,
454
+ .calories-metric-strip > div:first-child {
455
+ padding: 0;
456
+ border-left: 0;
457
+ }
458
+ }
@@ -0,0 +1,139 @@
1
+ ---
2
+ import { calculateCalories } from './logic';
3
+ import type { CaloriesBurnedCalculatorUI } from './ui';
4
+
5
+ interface Props {
6
+ ui: CaloriesBurnedCalculatorUI;
7
+ }
8
+
9
+ const { ui } = Astro.props;
10
+ const initialState = {
11
+ activityId: 'walking',
12
+ intensity: 'moderate',
13
+ weightKg: 70,
14
+ durationMinutes: 30,
15
+ unit: 'metric',
16
+ };
17
+ const initialResult = calculateCalories(initialState);
18
+ const initialGross = Math.round(initialResult?.grossKcal ?? 0);
19
+ const initialNet = Math.round(initialResult?.netKcal ?? 0);
20
+ const initialPerMinute = initialResult?.grossKcalPerMinute.toFixed(1) ?? '0.0';
21
+ ---
22
+
23
+ <div class="calories-burned-calculator" data-calories-tool>
24
+ <script is:inline type="application/json" data-calories-config set:html={JSON.stringify({ ui, initialState })}></script>
25
+
26
+ <div class="calories-tool-grid">
27
+ <section class="calories-control-panel" aria-label={ui.activityHint}>
28
+ <div class="calories-panel-heading">
29
+ <p class="calories-kicker">{ui.activityHint}</p>
30
+ <p class="calories-flow">Choose an activity, tune the effort, then make the result yours with body weight and time.</p>
31
+ </div>
32
+
33
+ <div class="calories-field calories-field-wide">
34
+ <label class="calories-label" for="calories-activity-trigger">{ui.activityLabel}</label>
35
+ <div class="calories-select" data-activity-menu-wrapper>
36
+ <button id="calories-activity-trigger" class="calories-select-trigger" type="button" data-activity-trigger aria-haspopup="listbox" aria-expanded="false">
37
+ <span data-activity-value>{ui.activityWalking}</span>
38
+ <span class="calories-select-mark" aria-hidden="true"></span>
39
+ </button>
40
+ <div class="calories-select-menu" data-activity-menu hidden>
41
+ <div class="calories-select-options" data-activity-options role="listbox" aria-label={ui.activityMenuLabel}></div>
42
+ </div>
43
+ </div>
44
+ </div>
45
+
46
+ <fieldset class="calories-field calories-field-wide">
47
+ <legend class="calories-label">{ui.intensityLabel}</legend>
48
+ <div class="calories-intensity-row">
49
+ <button type="button" class="calories-intensity-button" data-intensity="light" aria-pressed="false">{ui.intensityLight}</button>
50
+ <button type="button" class="calories-intensity-button is-active" data-intensity="moderate" aria-pressed="true">{ui.intensityModerate}</button>
51
+ <button type="button" class="calories-intensity-button" data-intensity="vigorous" aria-pressed="false">{ui.intensityVigorous}</button>
52
+ </div>
53
+ </fieldset>
54
+
55
+ <div class="calories-field">
56
+ <label class="calories-label" for="calories-weight-number">{ui.weightLabel} <span data-weight-unit>kg</span></label>
57
+ <div class="calories-number-wrap">
58
+ <input id="calories-weight-number" class="calories-number" type="number" min="25" max="300" step="0.5" value="70" data-weight-number inputmode="decimal" />
59
+ <span class="calories-number-unit" data-weight-unit>kg</span>
60
+ </div>
61
+ <input class="calories-range" type="range" min="25" max="300" step="0.5" value="70" data-weight-range aria-label={ui.weightLabel} />
62
+ <p class="calories-hint">{ui.weightHint}</p>
63
+ </div>
64
+
65
+ <div class="calories-field">
66
+ <label class="calories-label" for="calories-duration-number">{ui.durationLabel}</label>
67
+ <div class="calories-number-wrap">
68
+ <input id="calories-duration-number" class="calories-number" type="number" min="1" max="600" step="1" value="30" data-duration-number inputmode="numeric" />
69
+ <span class="calories-number-unit">min</span>
70
+ </div>
71
+ <input class="calories-range" type="range" min="1" max="600" step="1" value="30" data-duration-range aria-label={ui.durationLabel} />
72
+ <p class="calories-hint">{ui.durationHint}</p>
73
+ </div>
74
+
75
+ <div class="calories-unit-block">
76
+ <span class="calories-label">{ui.unitsLabel}</span>
77
+ <div class="calories-unit-toggle" role="group" aria-label={ui.unitsLabel}>
78
+ <button type="button" data-unit="metric" aria-pressed="true" class="is-active">{ui.metricUnit}</button>
79
+ <button type="button" data-unit="imperial" aria-pressed="false">{ui.imperialUnit}</button>
80
+ </div>
81
+ </div>
82
+
83
+ <button class="calories-reset" type="button" data-reset>{ui.resetButton}</button>
84
+ </section>
85
+
86
+ <section class="calories-result-panel" data-result-panel data-intensity="moderate" aria-live="polite">
87
+ <div class="calories-result-heading">
88
+ <p class="calories-kicker">{ui.resultEyebrow}</p>
89
+ <h2>{ui.resultTitle}</h2>
90
+ </div>
91
+ <div class="calories-orbit" data-energy-scene role="img" aria-label={ui.energySceneLabel}>
92
+ <svg viewBox="0 0 240 240" aria-hidden="true">
93
+ <circle class="calories-orbit-track" cx="120" cy="120" r="100"></circle>
94
+ <circle class="calories-orbit-arc" data-energy-arc cx="120" cy="120" r="100"></circle>
95
+ <circle class="calories-orbit-core" cx="120" cy="120" r="58"></circle>
96
+ <g class="calories-energy-flame" data-energy-flame>
97
+ <path d="M120 78c14 17 27 29 27 48a27 27 0 0 1-54 0c0-10 5-21 15-31 0 12 4 17 9 21 4-10 5-22 3-38Z"></path>
98
+ </g>
99
+ <circle class="calories-orbit-dot" cx="120" cy="20" r="6"></circle>
100
+ <circle class="calories-orbit-dot calories-orbit-dot-alt" cx="220" cy="120" r="4"></circle>
101
+ </svg>
102
+ <div class="calories-orbit-copy">
103
+ <strong data-output="gross">{initialGross}</strong>
104
+ <span>kcal</span>
105
+ </div>
106
+ </div>
107
+
108
+ <div class="calories-result-stats">
109
+ <div class="calories-stat calories-stat-primary">
110
+ <span>{ui.grossCaloriesLabel}</span>
111
+ <strong><span data-output="gross">{initialGross}</span> kcal</strong>
112
+ </div>
113
+ <div class="calories-stat">
114
+ <span>{ui.netCaloriesLabel}</span>
115
+ <strong><span data-output="net">{initialNet}</span> kcal</strong>
116
+ </div>
117
+ <div class="calories-stat">
118
+ <span>{ui.perMinuteLabel}</span>
119
+ <strong data-output="per-minute">{initialPerMinute} kcal/min</strong>
120
+ </div>
121
+ </div>
122
+
123
+ <div class="calories-metric-strip">
124
+ <div><span>{ui.metLabel}</span><strong data-output="met">3.8 MET</strong></div>
125
+ <div><span>{ui.intensityBandLabel}</span><strong data-output="band">{ui.intensityModerate}</strong></div>
126
+ <div><span>{ui.weightLabel}</span><strong data-output="weight">70 kg</strong></div>
127
+ </div>
128
+
129
+ <p class="calories-method-note">{ui.methodNote}</p>
130
+ <p class="calories-estimate-notice" data-empty-result>{ui.estimateNotice}</p>
131
+ </section>
132
+ </div>
133
+ </div>
134
+
135
+ <script>
136
+ import { mountCaloriesBurnedCalculator } from './controller';
137
+
138
+ mountCaloriesBurnedCalculator();
139
+ </script>