@jjlmoya/utils-sports 1.49.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 (89) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +4 -0
  3. package/src/entries.ts +6 -0
  4. package/src/index.ts +3 -0
  5. package/src/tests/seo_translation_completeness.test.ts +69 -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/roundRobinLeagueManager/bibliography.astro +6 -0
  36. package/src/tool/roundRobinLeagueManager/bibliography.ts +12 -0
  37. package/src/tool/roundRobinLeagueManager/client-io.ts +35 -0
  38. package/src/tool/roundRobinLeagueManager/component.astro +195 -0
  39. package/src/tool/roundRobinLeagueManager/controller.ts +234 -0
  40. package/src/tool/roundRobinLeagueManager/dom-views.ts +246 -0
  41. package/src/tool/roundRobinLeagueManager/draft-form.ts +62 -0
  42. package/src/tool/roundRobinLeagueManager/entry.ts +30 -0
  43. package/src/tool/roundRobinLeagueManager/evaluator.ts +28 -0
  44. package/src/tool/roundRobinLeagueManager/i18n/de.ts +263 -0
  45. package/src/tool/roundRobinLeagueManager/i18n/en.ts +263 -0
  46. package/src/tool/roundRobinLeagueManager/i18n/es.ts +263 -0
  47. package/src/tool/roundRobinLeagueManager/i18n/fr.ts +263 -0
  48. package/src/tool/roundRobinLeagueManager/i18n/id.ts +263 -0
  49. package/src/tool/roundRobinLeagueManager/i18n/it.ts +263 -0
  50. package/src/tool/roundRobinLeagueManager/i18n/ja.ts +264 -0
  51. package/src/tool/roundRobinLeagueManager/i18n/ko.ts +263 -0
  52. package/src/tool/roundRobinLeagueManager/i18n/nl.ts +263 -0
  53. package/src/tool/roundRobinLeagueManager/i18n/pl.ts +263 -0
  54. package/src/tool/roundRobinLeagueManager/i18n/pt.ts +263 -0
  55. package/src/tool/roundRobinLeagueManager/i18n/ru.ts +263 -0
  56. package/src/tool/roundRobinLeagueManager/i18n/sv.ts +263 -0
  57. package/src/tool/roundRobinLeagueManager/i18n/tr.ts +263 -0
  58. package/src/tool/roundRobinLeagueManager/i18n/zh.ts +263 -0
  59. package/src/tool/roundRobinLeagueManager/index.ts +11 -0
  60. package/src/tool/roundRobinLeagueManager/logic.test.ts +152 -0
  61. package/src/tool/roundRobinLeagueManager/logic.ts +244 -0
  62. package/src/tool/roundRobinLeagueManager/round-robin-league-manager.css +1124 -0
  63. package/src/tool/roundRobinLeagueManager/seo.astro +15 -0
  64. package/src/tool/roundRobinLeagueManager/sharing.ts +45 -0
  65. package/src/tool/roundRobinLeagueManager/storage.ts +43 -0
  66. package/src/tool/roundRobinLeagueManager/ui.ts +90 -0
  67. package/src/tool/roundRobinLeagueManager/validation.ts +57 -0
  68. package/src/tool/runningPacePredictor/i18n/fr.ts +9 -9
  69. package/src/tool/runningPacePredictor/i18n/id.ts +9 -9
  70. package/src/tool/runningPacePredictor/i18n/it.ts +10 -10
  71. package/src/tool/runningPacePredictor/i18n/nl.ts +7 -7
  72. package/src/tool/runningPacePredictor/i18n/pl.ts +10 -10
  73. package/src/tool/runningPacePredictor/i18n/pt.ts +10 -10
  74. package/src/tool/runningPacePredictor/i18n/ru.ts +10 -10
  75. package/src/tool/runningPacePredictor/i18n/sv.ts +10 -10
  76. package/src/tool/runningPacePredictor/i18n/tr.ts +9 -9
  77. package/src/tool/swimCssCalculator/i18n/de.ts +7 -7
  78. package/src/tool/swimCssCalculator/i18n/es.ts +7 -7
  79. package/src/tool/swimCssCalculator/i18n/fr.ts +7 -7
  80. package/src/tool/swimCssCalculator/i18n/id.ts +7 -7
  81. package/src/tool/swimCssCalculator/i18n/it.ts +7 -7
  82. package/src/tool/swimCssCalculator/i18n/nl.ts +7 -7
  83. package/src/tool/swimCssCalculator/i18n/pl.ts +7 -7
  84. package/src/tool/swimCssCalculator/i18n/pt.ts +7 -7
  85. package/src/tool/swimCssCalculator/i18n/ru.ts +7 -7
  86. package/src/tool/swimCssCalculator/i18n/sv.ts +7 -7
  87. package/src/tool/swimCssCalculator/i18n/tr.ts +7 -7
  88. package/src/tool/swimCssCalculator/i18n/zh.ts +7 -7
  89. package/src/tools.ts +4 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-sports",
3
- "version": "1.49.0",
3
+ "version": "1.51.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -12,6 +12,8 @@ import { baseballScoreKeeper } from '../tool/baseballScoreKeeper/entry';
12
12
  import { penaltyShootout } from '../tool/penaltyShootout/entry';
13
13
  import { swimCssCalculator } from '../tool/swimCssCalculator/entry';
14
14
  import { vo2MaxCalculator } from '../tool/vo2MaxCalculator/entry';
15
+ import { roundRobinLeagueManager } from '../tool/roundRobinLeagueManager/entry';
16
+ import { caloriesBurnedCalculator } from '../tool/caloriesBurnedCalculator/entry';
15
17
 
16
18
  export const sportsCategory: SportsCategoryEntry = {
17
19
  icon: 'mdi:soccer',
@@ -29,6 +31,8 @@ export const sportsCategory: SportsCategoryEntry = {
29
31
  penaltyShootout,
30
32
  swimCssCalculator,
31
33
  vo2MaxCalculator,
34
+ roundRobinLeagueManager,
35
+ caloriesBurnedCalculator,
32
36
  ] as unknown as SportsToolEntry<Record<string, string>>[],
33
37
  i18n: {
34
38
  es: () => import('./i18n/es').then((m) => m.content),
package/src/entries.ts CHANGED
@@ -40,6 +40,8 @@ export { volleyballScoreKeeper } from './tool/volleyballScoreKeeper/entry';
40
40
  export type { VolleyballScoreKeeperLocaleContent } from './tool/volleyballScoreKeeper/entry';
41
41
  export { vo2MaxCalculator } from './tool/vo2MaxCalculator/entry';
42
42
  export type { Vo2MaxCalculatorLocaleContent } from './tool/vo2MaxCalculator/entry';
43
+ export { roundRobinLeagueManager } from './tool/roundRobinLeagueManager/entry';
44
+ export type { RoundRobinLeagueManagerLocaleContent } from './tool/roundRobinLeagueManager/entry';
43
45
 
44
46
  export { sportsCategory } from './category';
45
47
 
@@ -64,6 +66,8 @@ import { runningPacePredictor } from './tool/runningPacePredictor/entry';
64
66
  import { swimCssCalculator } from './tool/swimCssCalculator/entry';
65
67
  import { volleyballScoreKeeper } from './tool/volleyballScoreKeeper/entry';
66
68
  import { vo2MaxCalculator } from './tool/vo2MaxCalculator/entry';
69
+ import { roundRobinLeagueManager } from './tool/roundRobinLeagueManager/entry';
70
+ import { caloriesBurnedCalculator } from './tool/caloriesBurnedCalculator/entry';
67
71
 
68
72
  export const ALL_ENTRIES = [
69
73
  basketScoreKeeper,
@@ -87,4 +91,6 @@ export const ALL_ENTRIES = [
87
91
  swimCssCalculator,
88
92
  volleyballScoreKeeper,
89
93
  vo2MaxCalculator,
94
+ roundRobinLeagueManager,
95
+ caloriesBurnedCalculator,
90
96
  ];
package/src/index.ts CHANGED
@@ -37,3 +37,6 @@ export type { PenaltyShootoutUI, PenaltyShootoutLocaleContent } from './tool/pen
37
37
 
38
38
  export { VOLLEYBALL_SCOREKEEPER_TOOL, volleyballScoreKeeper } from './tool/volleyballScoreKeeper/index';
39
39
  export type { VolleyballScoreKeeperUI, VolleyballScoreKeeperLocaleContent } from './tool/volleyballScoreKeeper/index';
40
+
41
+ export { ROUND_ROBIN_LEAGUE_MANAGER_TOOL, roundRobinLeagueManager } from './tool/roundRobinLeagueManager/index';
42
+ export type { RoundRobinLeagueManagerUI, RoundRobinLeagueManagerLocaleContent } from './tool/roundRobinLeagueManager/index';
@@ -0,0 +1,69 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ALL_TOOLS } from '../tools';
3
+
4
+ const ASIAN_LOCALES = ['ja', 'ko', 'zh'];
5
+
6
+ function getSimpleSectionLength(section: any): number {
7
+ if (section.type === 'title') return section.text ? section.text.length : 0;
8
+ if (section.type === 'paragraph') return section.html ? section.html.replace(/<[^>]*>/g, '').length : 0;
9
+ if (section.type === 'list') return section.items ? section.items.join('').length : 0;
10
+ return 0;
11
+ }
12
+
13
+ function getComplexSectionLength(section: any): number {
14
+ if (section.type === 'diagnostic' || section.type === 'tip') {
15
+ return (section.title ? section.title.length : 0) + (section.html ? section.html.replace(/<[^>]*>/g, '').length : 0);
16
+ }
17
+ if (section.type === 'table') {
18
+ return ((section.headers || []).join('').length) + ((section.rows || []).flat().join('').length);
19
+ }
20
+ return 0;
21
+ }
22
+
23
+ function getSectionLength(section: any): number {
24
+ return getSimpleSectionLength(section) || getComplexSectionLength(section);
25
+ }
26
+
27
+ function calculateSeoTextLength(seoSections: any[]): number {
28
+ return seoSections.reduce((acc, section) => acc + getSectionLength(section), 0);
29
+ }
30
+
31
+ function checkLocaleSeoLength(toolId: string, locale: string, localeLen: number, enLen: number): void {
32
+ const isAsian = ASIAN_LOCALES.includes(locale);
33
+ const minLength = Math.floor(enLen * (isAsian ? 0.25 : 0.70));
34
+ const msgType = isAsian ? 'suspiciously short' : 'truncated/lazy';
35
+
36
+ expect(
37
+ localeLen,
38
+ `[LAZY SEO TRANSLATION] Tool "${toolId}" locale "${locale}" SEO text is ${msgType} (${localeLen} chars vs EN ${enLen} chars, expected min ${minLength})`,
39
+ ).toBeGreaterThanOrEqual(minLength);
40
+ }
41
+
42
+ async function auditSingleLocale(toolId: string, locale: string, loader: any, enSeoLength: number): Promise<void> {
43
+ if (locale === 'en' || !loader) return;
44
+ const content = await loader();
45
+ if (!content.seo || !Array.isArray(content.seo)) return;
46
+
47
+ checkLocaleSeoLength(toolId, locale, calculateSeoTextLength(content.seo), enSeoLength);
48
+ }
49
+
50
+ describe('SEO Translation Completeness & Laziness Audit', () => {
51
+ ALL_TOOLS.forEach(({ entry }) => {
52
+ describe(`Tool: ${entry.id}`, () => {
53
+ it('should not have lazy or truncated SEO content compared to English reference', async () => {
54
+ const enLoader = entry.i18n['en' as keyof typeof entry.i18n];
55
+ if (!enLoader) return;
56
+
57
+ const enContent = await enLoader();
58
+ if (!enContent.seo || !Array.isArray(enContent.seo)) return;
59
+
60
+ const enSeoLength = calculateSeoTextLength(enContent.seo);
61
+
62
+ for (const [locale, loader] of Object.entries(entry.i18n)) {
63
+ await auditSingleLocale(entry.id, locale, loader, enSeoLength);
64
+ }
65
+ });
66
+ });
67
+ });
68
+ });
69
+
@@ -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
+ }