@jjlmoya/utils-sports 1.50.0 → 1.52.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 (40) 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/tests/bibliography_wellformed_export.test.ts +46 -0
  5. package/src/tests/seo_wellformed_export.test.ts +65 -0
  6. package/src/tool/caloriesBurnedCalculator/bibliography.astro +6 -0
  7. package/src/tool/caloriesBurnedCalculator/bibliography.ts +18 -0
  8. package/src/tool/caloriesBurnedCalculator/calories-burned-by-exercise-calculator.css +458 -0
  9. package/src/tool/caloriesBurnedCalculator/component.astro +139 -0
  10. package/src/tool/caloriesBurnedCalculator/controller.ts +168 -0
  11. package/src/tool/caloriesBurnedCalculator/dom-views.ts +101 -0
  12. package/src/tool/caloriesBurnedCalculator/entry.ts +30 -0
  13. package/src/tool/caloriesBurnedCalculator/evaluator.ts +12 -0
  14. package/src/tool/caloriesBurnedCalculator/i18n/de.ts +222 -0
  15. package/src/tool/caloriesBurnedCalculator/i18n/en.ts +222 -0
  16. package/src/tool/caloriesBurnedCalculator/i18n/es.ts +222 -0
  17. package/src/tool/caloriesBurnedCalculator/i18n/fr.ts +222 -0
  18. package/src/tool/caloriesBurnedCalculator/i18n/id.ts +222 -0
  19. package/src/tool/caloriesBurnedCalculator/i18n/it.ts +222 -0
  20. package/src/tool/caloriesBurnedCalculator/i18n/ja.ts +222 -0
  21. package/src/tool/caloriesBurnedCalculator/i18n/ko.ts +222 -0
  22. package/src/tool/caloriesBurnedCalculator/i18n/nl.ts +222 -0
  23. package/src/tool/caloriesBurnedCalculator/i18n/pl.ts +222 -0
  24. package/src/tool/caloriesBurnedCalculator/i18n/pt.ts +222 -0
  25. package/src/tool/caloriesBurnedCalculator/i18n/ru.ts +222 -0
  26. package/src/tool/caloriesBurnedCalculator/i18n/sv.ts +222 -0
  27. package/src/tool/caloriesBurnedCalculator/i18n/tr.ts +222 -0
  28. package/src/tool/caloriesBurnedCalculator/i18n/zh.ts +222 -0
  29. package/src/tool/caloriesBurnedCalculator/index.ts +10 -0
  30. package/src/tool/caloriesBurnedCalculator/logic.test.ts +50 -0
  31. package/src/tool/caloriesBurnedCalculator/logic.ts +101 -0
  32. package/src/tool/caloriesBurnedCalculator/seo.astro +14 -0
  33. package/src/tool/caloriesBurnedCalculator/storage.ts +43 -0
  34. package/src/tool/caloriesBurnedCalculator/ui.ts +43 -0
  35. package/src/tool/dartsScoreKeeper/bibliography.astro +2 -11
  36. package/src/tool/dartsScoreKeeper/seo.astro +12 -1
  37. package/src/tool/penaltyShootout/bibliography.astro +2 -11
  38. package/src/tool/penaltyShootout/seo.astro +12 -2
  39. package/src/tool/volleyballScoreKeeper/seo.astro +12 -11
  40. 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.52.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,46 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { readdirSync, readFileSync, existsSync } from 'fs';
3
+ import { join, relative } from 'path';
4
+
5
+ function findBibliographyAstroFiles(dir: string): string[] {
6
+ const files: string[] = [];
7
+ if (!existsSync(dir)) return files;
8
+ const entries = readdirSync(dir, { withFileTypes: true });
9
+
10
+ for (const entry of entries) {
11
+ const fullPath = join(dir, entry.name);
12
+ if (entry.isDirectory()) {
13
+ files.push(...findBibliographyAstroFiles(fullPath));
14
+ } else if (entry.isFile() && entry.name === 'bibliography.astro') {
15
+ files.push(fullPath);
16
+ }
17
+ }
18
+
19
+ return files;
20
+ }
21
+
22
+ const toolDir = join(process.cwd(), 'src', 'tool');
23
+ const bibliographyFiles = findBibliographyAstroFiles(toolDir);
24
+
25
+ describe('Bibliography Component Wellformed Export', () => {
26
+ it('found tool bibliography.astro components', () => {
27
+ expect(bibliographyFiles.length).toBeGreaterThan(0);
28
+ });
29
+
30
+ bibliographyFiles.forEach((file) => {
31
+ const relativePath = relative(process.cwd(), file);
32
+
33
+ it(`${relativePath} should use SharedBibliography from @jjlmoya/utils-shared`, () => {
34
+ const content = readFileSync(file, 'utf-8');
35
+
36
+ const usesSharedComponent =
37
+ content.includes('@jjlmoya/utils-shared') &&
38
+ (content.includes('Bibliography') || content.includes('SharedBibliography'));
39
+
40
+ expect(
41
+ usesSharedComponent,
42
+ `File "${relativePath}" does not use the standard Bibliography component from @jjlmoya/utils-shared, resulting in unstyled or raw bibliography output.`,
43
+ ).toBe(true);
44
+ });
45
+ });
46
+ });
@@ -0,0 +1,65 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { readdirSync, readFileSync, existsSync } from 'fs';
3
+ import { join, relative } from 'path';
4
+
5
+ function findSeoAstroFiles(dir: string): string[] {
6
+ const files: string[] = [];
7
+ if (!existsSync(dir)) return files;
8
+ const entries = readdirSync(dir, { withFileTypes: true });
9
+
10
+ for (const entry of entries) {
11
+ const fullPath = join(dir, entry.name);
12
+ if (entry.isDirectory()) {
13
+ files.push(...findSeoAstroFiles(fullPath));
14
+ } else if (entry.isFile() && entry.name === 'seo.astro') {
15
+ files.push(fullPath);
16
+ }
17
+ }
18
+
19
+ return files;
20
+ }
21
+
22
+ const toolDir = join(process.cwd(), 'src', 'tool');
23
+ const seoFiles = findSeoAstroFiles(toolDir);
24
+
25
+ describe('SEO Component Wellformed Export', () => {
26
+ it('found tool seo.astro components', () => {
27
+ expect(seoFiles.length).toBeGreaterThan(0);
28
+ });
29
+
30
+ seoFiles.forEach((file) => {
31
+ const relativePath = relative(process.cwd(), file);
32
+
33
+ it(`${relativePath} should dynamically load SEO sections and use SEORenderer`, () => {
34
+ const content = readFileSync(file, 'utf-8');
35
+
36
+ const usesSeoRenderer =
37
+ content.includes('@jjlmoya/utils-shared') &&
38
+ (content.includes('SEORenderer') || content.includes('SEOArticle'));
39
+
40
+ const acquiresDynamicContent =
41
+ content.includes('.i18n') ||
42
+ content.includes('loader') ||
43
+ content.includes('await loader') ||
44
+ content.includes('.seo');
45
+
46
+ const isBrokenPattern =
47
+ /sections\s*=\s*\[\s*\]/.test(content) && !acquiresDynamicContent;
48
+
49
+ expect(
50
+ usesSeoRenderer,
51
+ `File "${relativePath}" does not import or use SEORenderer from @jjlmoya/utils-shared.`,
52
+ ).toBe(true);
53
+
54
+ expect(
55
+ isBrokenPattern,
56
+ `File "${relativePath}" relies on a static sections=[] prop default without fetching content from entry.i18n, resulting in empty SEO text on consumers.`,
57
+ ).toBe(false);
58
+
59
+ expect(
60
+ acquiresDynamicContent,
61
+ `File "${relativePath}" does not fetch dynamic i18n content for SEO rendering.`,
62
+ ).toBe(true);
63
+ });
64
+ });
65
+ });
@@ -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
+ }