@jjlmoya/utils-health 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/package.json +60 -0
  2. package/src/category/i18n/en.ts +60 -0
  3. package/src/category/i18n/es.ts +60 -0
  4. package/src/category/i18n/fr.ts +60 -0
  5. package/src/category/index.ts +22 -0
  6. package/src/category/seo.astro +15 -0
  7. package/src/components/PreviewNavSidebar.astro +116 -0
  8. package/src/components/PreviewToolbar.astro +143 -0
  9. package/src/data.ts +28 -0
  10. package/src/env.d.ts +5 -0
  11. package/src/index.ts +36 -0
  12. package/src/layouts/PreviewLayout.astro +117 -0
  13. package/src/pages/[locale]/[slug].astro +146 -0
  14. package/src/pages/[locale].astro +251 -0
  15. package/src/pages/index.astro +4 -0
  16. package/src/tests/faq_count.test.ts +19 -0
  17. package/src/tests/locale_completeness.test.ts +42 -0
  18. package/src/tests/mocks/astro_mock.js +2 -0
  19. package/src/tests/no_h1_in_components.test.ts +48 -0
  20. package/src/tests/schemas_fulfillment.test.ts +23 -0
  21. package/src/tests/seo_length.test.ts +22 -0
  22. package/src/tests/title_quality.test.ts +55 -0
  23. package/src/tests/tool_validation.test.ts +17 -0
  24. package/src/tool/binauralTuner/bibliography.astro +14 -0
  25. package/src/tool/binauralTuner/component.astro +687 -0
  26. package/src/tool/binauralTuner/i18n/en.ts +187 -0
  27. package/src/tool/binauralTuner/i18n/es.ts +187 -0
  28. package/src/tool/binauralTuner/i18n/fr.ts +187 -0
  29. package/src/tool/binauralTuner/index.ts +27 -0
  30. package/src/tool/binauralTuner/seo.astro +14 -0
  31. package/src/tool/binauralTuner/ui.ts +18 -0
  32. package/src/tool/bloodUnitConverter/bibliography.astro +14 -0
  33. package/src/tool/bloodUnitConverter/component.astro +915 -0
  34. package/src/tool/bloodUnitConverter/i18n/en.ts +227 -0
  35. package/src/tool/bloodUnitConverter/i18n/es.ts +250 -0
  36. package/src/tool/bloodUnitConverter/i18n/fr.ts +218 -0
  37. package/src/tool/bloodUnitConverter/index.ts +27 -0
  38. package/src/tool/bloodUnitConverter/seo.astro +14 -0
  39. package/src/tool/bloodUnitConverter/ui.ts +38 -0
  40. package/src/tool/bmiCalculator/bibliography.astro +14 -0
  41. package/src/tool/bmiCalculator/component.astro +415 -0
  42. package/src/tool/bmiCalculator/i18n/en.ts +217 -0
  43. package/src/tool/bmiCalculator/i18n/es.ts +221 -0
  44. package/src/tool/bmiCalculator/i18n/fr.ts +217 -0
  45. package/src/tool/bmiCalculator/index.ts +27 -0
  46. package/src/tool/bmiCalculator/seo.astro +14 -0
  47. package/src/tool/bmiCalculator/ui.ts +21 -0
  48. package/src/tool/breathingVisualizer/bibliography.astro +14 -0
  49. package/src/tool/breathingVisualizer/component.astro +636 -0
  50. package/src/tool/breathingVisualizer/i18n/en.ts +206 -0
  51. package/src/tool/breathingVisualizer/i18n/es.ts +206 -0
  52. package/src/tool/breathingVisualizer/i18n/fr.ts +206 -0
  53. package/src/tool/breathingVisualizer/index.ts +27 -0
  54. package/src/tool/breathingVisualizer/seo.astro +14 -0
  55. package/src/tool/breathingVisualizer/ui.ts +31 -0
  56. package/src/tool/caffeineTracker/bibliography.astro +14 -0
  57. package/src/tool/caffeineTracker/component.astro +1210 -0
  58. package/src/tool/caffeineTracker/i18n/en.ts +198 -0
  59. package/src/tool/caffeineTracker/i18n/es.ts +198 -0
  60. package/src/tool/caffeineTracker/i18n/fr.ts +198 -0
  61. package/src/tool/caffeineTracker/index.ts +27 -0
  62. package/src/tool/caffeineTracker/logic.ts +31 -0
  63. package/src/tool/caffeineTracker/seo.astro +14 -0
  64. package/src/tool/caffeineTracker/ui.ts +36 -0
  65. package/src/tool/daltonismSimulator/bibliography.astro +14 -0
  66. package/src/tool/daltonismSimulator/component.astro +383 -0
  67. package/src/tool/daltonismSimulator/i18n/en.ts +188 -0
  68. package/src/tool/daltonismSimulator/i18n/es.ts +218 -0
  69. package/src/tool/daltonismSimulator/i18n/fr.ts +168 -0
  70. package/src/tool/daltonismSimulator/index.ts +27 -0
  71. package/src/tool/daltonismSimulator/seo.astro +14 -0
  72. package/src/tool/daltonismSimulator/ui.ts +20 -0
  73. package/src/tool/digestionStopwatch/bibliography.astro +14 -0
  74. package/src/tool/digestionStopwatch/component.astro +627 -0
  75. package/src/tool/digestionStopwatch/i18n/en.ts +173 -0
  76. package/src/tool/digestionStopwatch/i18n/es.ts +173 -0
  77. package/src/tool/digestionStopwatch/i18n/fr.ts +173 -0
  78. package/src/tool/digestionStopwatch/index.ts +27 -0
  79. package/src/tool/digestionStopwatch/logic.ts +63 -0
  80. package/src/tool/digestionStopwatch/seo.astro +14 -0
  81. package/src/tool/digestionStopwatch/ui.ts +20 -0
  82. package/src/tool/epworthSleepinessScale/bibliography.astro +14 -0
  83. package/src/tool/epworthSleepinessScale/component.astro +528 -0
  84. package/src/tool/epworthSleepinessScale/i18n/en.ts +217 -0
  85. package/src/tool/epworthSleepinessScale/i18n/es.ts +217 -0
  86. package/src/tool/epworthSleepinessScale/i18n/fr.ts +217 -0
  87. package/src/tool/epworthSleepinessScale/index.ts +27 -0
  88. package/src/tool/epworthSleepinessScale/seo.astro +14 -0
  89. package/src/tool/epworthSleepinessScale/ui.ts +27 -0
  90. package/src/tool/hydrationCalculator/bibliography.astro +14 -0
  91. package/src/tool/hydrationCalculator/component.astro +694 -0
  92. package/src/tool/hydrationCalculator/i18n/en.ts +217 -0
  93. package/src/tool/hydrationCalculator/i18n/es.ts +222 -0
  94. package/src/tool/hydrationCalculator/i18n/fr.ts +199 -0
  95. package/src/tool/hydrationCalculator/index.ts +27 -0
  96. package/src/tool/hydrationCalculator/seo.astro +14 -0
  97. package/src/tool/hydrationCalculator/ui.ts +28 -0
  98. package/src/tool/pelliRobsonTest/bibliography.astro +14 -0
  99. package/src/tool/pelliRobsonTest/component.astro +653 -0
  100. package/src/tool/pelliRobsonTest/i18n/en.ts +205 -0
  101. package/src/tool/pelliRobsonTest/i18n/es.ts +205 -0
  102. package/src/tool/pelliRobsonTest/i18n/fr.ts +205 -0
  103. package/src/tool/pelliRobsonTest/index.ts +27 -0
  104. package/src/tool/pelliRobsonTest/seo.astro +14 -0
  105. package/src/tool/pelliRobsonTest/ui.ts +21 -0
  106. package/src/tool/peripheralVisionTrainer/bibliography.astro +14 -0
  107. package/src/tool/peripheralVisionTrainer/component.astro +678 -0
  108. package/src/tool/peripheralVisionTrainer/i18n/en.ts +224 -0
  109. package/src/tool/peripheralVisionTrainer/i18n/es.ts +224 -0
  110. package/src/tool/peripheralVisionTrainer/i18n/fr.ts +211 -0
  111. package/src/tool/peripheralVisionTrainer/index.ts +27 -0
  112. package/src/tool/peripheralVisionTrainer/seo.astro +14 -0
  113. package/src/tool/peripheralVisionTrainer/ui.ts +26 -0
  114. package/src/tool/readingDistanceCalculator/bibliography.astro +14 -0
  115. package/src/tool/readingDistanceCalculator/component.astro +588 -0
  116. package/src/tool/readingDistanceCalculator/i18n/en.ts +202 -0
  117. package/src/tool/readingDistanceCalculator/i18n/es.ts +215 -0
  118. package/src/tool/readingDistanceCalculator/i18n/fr.ts +193 -0
  119. package/src/tool/readingDistanceCalculator/index.ts +31 -0
  120. package/src/tool/readingDistanceCalculator/seo.astro +14 -0
  121. package/src/tool/readingDistanceCalculator/ui.ts +18 -0
  122. package/src/tool/screenDecompressionTime/bibliography.astro +14 -0
  123. package/src/tool/screenDecompressionTime/component.astro +671 -0
  124. package/src/tool/screenDecompressionTime/i18n/en.ts +225 -0
  125. package/src/tool/screenDecompressionTime/i18n/es.ts +247 -0
  126. package/src/tool/screenDecompressionTime/i18n/fr.ts +225 -0
  127. package/src/tool/screenDecompressionTime/index.ts +27 -0
  128. package/src/tool/screenDecompressionTime/seo.astro +14 -0
  129. package/src/tool/screenDecompressionTime/ui.ts +32 -0
  130. package/src/tool/tinnitusReliever/bibliography.astro +14 -0
  131. package/src/tool/tinnitusReliever/component.astro +581 -0
  132. package/src/tool/tinnitusReliever/i18n/en.ts +161 -0
  133. package/src/tool/tinnitusReliever/i18n/es.ts +161 -0
  134. package/src/tool/tinnitusReliever/i18n/fr.ts +161 -0
  135. package/src/tool/tinnitusReliever/index.ts +27 -0
  136. package/src/tool/tinnitusReliever/seo.astro +14 -0
  137. package/src/tool/tinnitusReliever/ui.ts +9 -0
  138. package/src/tool/ubeCalculator/bibliography.astro +14 -0
  139. package/src/tool/ubeCalculator/component.astro +683 -0
  140. package/src/tool/ubeCalculator/i18n/en.ts +200 -0
  141. package/src/tool/ubeCalculator/i18n/es.ts +200 -0
  142. package/src/tool/ubeCalculator/i18n/fr.ts +196 -0
  143. package/src/tool/ubeCalculator/index.ts +27 -0
  144. package/src/tool/ubeCalculator/seo.astro +14 -0
  145. package/src/tool/ubeCalculator/ui.ts +26 -0
  146. package/src/tool/waterPurifier/bibliography.astro +14 -0
  147. package/src/tool/waterPurifier/component.astro +628 -0
  148. package/src/tool/waterPurifier/i18n/en.ts +167 -0
  149. package/src/tool/waterPurifier/i18n/es.ts +167 -0
  150. package/src/tool/waterPurifier/i18n/fr.ts +167 -0
  151. package/src/tool/waterPurifier/index.ts +27 -0
  152. package/src/tool/waterPurifier/seo.astro +14 -0
  153. package/src/tool/waterPurifier/ui.ts +18 -0
  154. package/src/tools.ts +19 -0
  155. package/src/types.ts +72 -0
@@ -0,0 +1,627 @@
1
+ ---
2
+ import { Icon } from 'astro-icon/components';
3
+ import type { DigestionStopwatchUI } from './ui';
4
+
5
+ interface Props {
6
+ ui?: Record<string, unknown>;
7
+ }
8
+
9
+ const ui = (Astro.props.ui ?? {}) as DigestionStopwatchUI;
10
+
11
+ const macros = [
12
+ { type: 'liquid', icon: 'mdi:water', label: ui.macroLiquidLabel, colorClass: 'ds__macro-icon--liquid' },
13
+ { type: 'carbs', icon: 'mdi:bread-slice', label: ui.macroCarbsLabel, colorClass: 'ds__macro-icon--carbs' },
14
+ { type: 'protein', icon: 'mdi:food-steak', label: ui.macroProteinLabel, colorClass: 'ds__macro-icon--protein' },
15
+ { type: 'fat', icon: 'mdi:cheese', label: ui.macroFatLabel, colorClass: 'ds__macro-icon--fat' },
16
+ ];
17
+
18
+ const stages = [
19
+ { id: 'stomach', name: ui.stageStomach, ph: 'pH 2.0', dotClass: 'ds__stage-dot--rose', barClass: 'ds__stage-bar--rose' },
20
+ { id: 'small_intestine', name: ui.stageSmallIntestine, ph: 'pH 6.5', dotClass: 'ds__stage-dot--cyan', barClass: 'ds__stage-bar--cyan' },
21
+ { id: 'large_intestine', name: ui.stageLargeIntestine, ph: 'pH 6.0', dotClass: 'ds__stage-dot--emerald', barClass: 'ds__stage-bar--emerald' },
22
+ ];
23
+ ---
24
+
25
+ <div class="ds" id="ds-container" data-ui={JSON.stringify(ui)}>
26
+ <div class="ds__glow ds__glow--tl"></div>
27
+ <div class="ds__glow ds__glow--br"></div>
28
+
29
+ <div class="ds__grid">
30
+
31
+
32
+ <div class="ds__controls">
33
+
34
+ <div class="ds__row">
35
+ <div class="ds__field">
36
+ <span class="ds__field-label">{ui.labelIngestionDay}</span>
37
+ <div class="ds__day-selector">
38
+ {[
39
+ { offset: 0, label: ui.dayToday },
40
+ { offset: 1, label: ui.dayYesterday },
41
+ ].map((d, i) => (
42
+ <button
43
+ class={`ds__day-btn${i === 0 ? ' ds__day-btn--active' : ''}`}
44
+ data-offset={d.offset}
45
+ >
46
+ {d.label}
47
+ </button>
48
+ ))}
49
+ </div>
50
+ </div>
51
+
52
+ <div class="ds__field">
53
+ <label for="ds-hour" class="ds__field-label">{ui.labelIngestionHour}</label>
54
+ <div class="ds__time-wrap">
55
+ <input type="time" id="ds-hour" class="ds__time-input" />
56
+ <Icon name="mdi:clock-outline" class="ds__time-icon" />
57
+ </div>
58
+ </div>
59
+ </div>
60
+
61
+ <div class="ds__field">
62
+ <span class="ds__field-label">{ui.labelNutritionalComposition}</span>
63
+ <div class="ds__macro-grid">
64
+ {macros.map((m) => (
65
+ <button class="ds__macro-btn" data-type={m.type}>
66
+ <div class="ds__macro-icon-wrap">
67
+ <Icon name={m.icon} class={`ds__macro-icon ${m.colorClass}`} />
68
+ </div>
69
+ <span class="ds__macro-label">{m.label}</span>
70
+ </button>
71
+ ))}
72
+ </div>
73
+ </div>
74
+ </div>
75
+
76
+
77
+ <div class="ds__panel">
78
+ <h3 class="ds__panel-title">
79
+ <Icon name="mdi:pulse" class="ds__pulse-icon" />
80
+ {ui.statusTitle}
81
+ </h3>
82
+
83
+ <div class="ds__stages">
84
+ {stages.map((stage) => (
85
+ <div class="ds__stage ds__stage--inactive" data-stage={stage.id}>
86
+ <div class="ds__stage-header">
87
+ <div class="ds__stage-name-row">
88
+ <div class={`ds__stage-dot ${stage.dotClass}`}></div>
89
+ <span class="ds__stage-name">{stage.name}</span>
90
+ </div>
91
+ <span class="ds__stage-ph">{stage.ph}</span>
92
+ </div>
93
+ <div class="ds__stage-track">
94
+ <div class={`ds__stage-bar ${stage.barClass}`} style="width: 0%"></div>
95
+ </div>
96
+ <p class="ds__stage-status"></p>
97
+ </div>
98
+ ))}
99
+ </div>
100
+ </div>
101
+
102
+ </div>
103
+ </div>
104
+
105
+ <script>
106
+ import { calculateStages } from './logic';
107
+ import type { MacronutrientType, StageDescriptions } from './logic';
108
+ import type { DigestionStopwatchUI } from './ui';
109
+
110
+ const container = document.getElementById('ds-container');
111
+ const ui = JSON.parse(container?.dataset.ui ?? '{}') as DigestionStopwatchUI;
112
+
113
+ const hourInput = container?.querySelector<HTMLInputElement>('#ds-hour');
114
+ const stageEls = container?.querySelectorAll<HTMLElement>('.ds__stage');
115
+
116
+ let selectedDayOffset = 0;
117
+ let selectedMacros: MacronutrientType[] = [];
118
+
119
+ function initTime() {
120
+ if (!hourInput) return;
121
+ const now = new Date();
122
+ const hh = String(now.getHours()).padStart(2, '0');
123
+ const mm = String(now.getMinutes()).padStart(2, '0');
124
+ hourInput.value = `${hh}:${mm}`;
125
+ }
126
+
127
+ function buildIngestionDate(): Date {
128
+ const [hours, minutes] = (hourInput?.value ?? '00:00').split(':').map(Number);
129
+ const d = new Date();
130
+ d.setDate(d.getDate() - selectedDayOffset);
131
+ d.setHours(hours, minutes, 0, 0);
132
+ return d;
133
+ }
134
+
135
+ function getDescriptions(): StageDescriptions {
136
+ return {
137
+ waiting: ui.descWaiting,
138
+ processed: ui.descProcessed,
139
+ stomach: ui.descStomach,
140
+ smallIntestine: ui.descSmallIntestine,
141
+ largeIntestine: ui.descLargeIntestine,
142
+ };
143
+ }
144
+
145
+ function applyStageClass(entry: HTMLElement, pct: number) {
146
+ const isActive = pct > 0 && pct < 100;
147
+ const isDone = pct >= 100;
148
+ entry.classList.toggle('ds__stage--active', isActive);
149
+ entry.classList.toggle('ds__stage--done', isDone);
150
+ entry.classList.toggle('ds__stage--inactive', !isActive && !isDone);
151
+ }
152
+
153
+ function updateStage(entry: HTMLElement, pct: number, desc: string) {
154
+ const bar = entry.querySelector<HTMLElement>('.ds__stage-bar');
155
+ const label = entry.querySelector<HTMLElement>('.ds__stage-status');
156
+ if (bar) bar.style.width = `${Math.min(100, pct)}%`;
157
+ if (label) label.textContent = desc;
158
+ applyStageClass(entry, pct);
159
+ }
160
+
161
+ function updateUI() {
162
+ const ingestionDate = buildIngestionDate();
163
+ if (isNaN(ingestionDate.getTime())) return;
164
+ const stages = calculateStages(ingestionDate, selectedMacros, getDescriptions());
165
+ stageEls?.forEach((entry, i) => {
166
+ const stage = stages[i];
167
+ if (stage) updateStage(entry, stage.percentage, stage.description);
168
+ });
169
+ }
170
+
171
+ function handleDayBtn(btn: HTMLButtonElement) {
172
+ container?.querySelectorAll<HTMLButtonElement>('.ds__day-btn').forEach((b) => {
173
+ b.classList.remove('ds__day-btn--active');
174
+ });
175
+ btn.classList.add('ds__day-btn--active');
176
+ selectedDayOffset = parseInt(btn.dataset.offset ?? '0', 10);
177
+ updateUI();
178
+ }
179
+
180
+ function handleMacroBtn(btn: HTMLButtonElement) {
181
+ const type = (btn.dataset.type ?? '') as MacronutrientType;
182
+ if (btn.classList.contains('ds__macro-btn--active')) {
183
+ btn.classList.remove('ds__macro-btn--active');
184
+ selectedMacros = selectedMacros.filter((m) => m !== type);
185
+ } else {
186
+ btn.classList.add('ds__macro-btn--active');
187
+ selectedMacros.push(type);
188
+ }
189
+ updateUI();
190
+ }
191
+
192
+ initTime();
193
+ updateUI();
194
+
195
+ container?.querySelectorAll<HTMLButtonElement>('.ds__day-btn').forEach((btn) => {
196
+ btn.addEventListener('click', () => handleDayBtn(btn));
197
+ });
198
+ container?.querySelectorAll<HTMLButtonElement>('.ds__macro-btn').forEach((btn) => {
199
+ btn.addEventListener('click', () => handleMacroBtn(btn));
200
+ });
201
+ hourInput?.addEventListener('input', updateUI);
202
+ setInterval(updateUI, 60000);
203
+ </script>
204
+
205
+ <style>
206
+ .ds {
207
+ --ds-bg: #f4f4f5;
208
+ --ds-panel-bg: #fff;
209
+ --ds-border: #e4e4e7;
210
+ --ds-text: #18181b;
211
+ --ds-text-muted: #71717a;
212
+ --ds-text-dim: #d4d4d8;
213
+ --ds-input-bg: #fff;
214
+ --ds-day-active-bg: #18181b;
215
+ --ds-day-active-text: #fff;
216
+ --ds-macro-active-bg: rgba(16, 185, 129, 0.07);
217
+ --ds-macro-active-border: rgba(16, 185, 129, 0.3);
218
+ --ds-macro-active-icon-bg: rgb(52, 211, 153);
219
+ --ds-emerald: #059669;
220
+ --ds-cyan: #0891b2;
221
+ --ds-rose: #e11d48;
222
+ --ds-amber: #d97706;
223
+ --ds-yellow: #ca8a04;
224
+ --ds-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
225
+
226
+ background: var(--ds-bg);
227
+ border-radius: 3rem;
228
+ border: 1px solid var(--ds-border);
229
+ box-shadow: var(--ds-shadow);
230
+ overflow: hidden;
231
+ position: relative;
232
+ padding: 2rem 1.5rem;
233
+ }
234
+
235
+ :global(.theme-dark) .ds {
236
+ --ds-bg: #09090b;
237
+ --ds-panel-bg: rgba(24, 24, 27, 0.6);
238
+ --ds-border: rgba(255, 255, 255, 0.06);
239
+ --ds-text: #fff;
240
+ --ds-text-muted: #71717a;
241
+ --ds-text-dim: #3f3f46;
242
+ --ds-input-bg: #18181b;
243
+ --ds-day-active-bg: #fff;
244
+ --ds-day-active-text: #000;
245
+ --ds-macro-active-bg: rgba(255, 255, 255, 0.05);
246
+ --ds-macro-active-border: rgba(255, 255, 255, 0.2);
247
+ --ds-macro-active-icon-bg: rgb(52, 211, 153);
248
+ --ds-emerald: #10b981;
249
+ --ds-cyan: #06b6d4;
250
+ --ds-rose: #f43f5e;
251
+ --ds-amber: #f59e0b;
252
+ --ds-yellow: #eab308;
253
+ --ds-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
254
+ }
255
+
256
+ @media (min-width: 768px) {
257
+ .ds {
258
+ padding: 3rem;
259
+ }
260
+ }
261
+
262
+
263
+ .ds__glow {
264
+ position: absolute;
265
+ width: 16rem;
266
+ height: 16rem;
267
+ border-radius: 50%;
268
+ pointer-events: none;
269
+ filter: blur(100px);
270
+ }
271
+
272
+ .ds__glow--tl {
273
+ top: -6rem;
274
+ left: -6rem;
275
+ background: rgba(16, 185, 129, 0.12);
276
+ }
277
+
278
+ .ds__glow--br {
279
+ bottom: -6rem;
280
+ right: -6rem;
281
+ background: rgba(6, 182, 212, 0.12);
282
+ }
283
+
284
+ :global(.theme-dark) .ds__glow--tl {
285
+ background: rgba(16, 185, 129, 0.05);
286
+ }
287
+
288
+ :global(.theme-dark) .ds__glow--br {
289
+ background: rgba(6, 182, 212, 0.05);
290
+ }
291
+
292
+
293
+ .ds__grid {
294
+ display: grid;
295
+ gap: 3rem;
296
+ position: relative;
297
+ z-index: 1;
298
+ }
299
+
300
+ @media (min-width: 1024px) {
301
+ .ds__grid {
302
+ grid-template-columns: 1fr 1fr;
303
+ gap: 4rem;
304
+ }
305
+ }
306
+
307
+
308
+ .ds__controls {
309
+ display: flex;
310
+ flex-direction: column;
311
+ gap: 2.5rem;
312
+ }
313
+
314
+ .ds__row {
315
+ display: grid;
316
+ grid-template-columns: 1fr 1fr;
317
+ gap: 1.5rem;
318
+ }
319
+
320
+ .ds__field {
321
+ display: flex;
322
+ flex-direction: column;
323
+ gap: 1rem;
324
+ }
325
+
326
+ .ds__field-label {
327
+ font-size: 0.625rem;
328
+ font-weight: 900;
329
+ text-transform: uppercase;
330
+ letter-spacing: 0.2em;
331
+ color: var(--ds-text-muted);
332
+ }
333
+
334
+
335
+ .ds__day-selector {
336
+ display: flex;
337
+ padding: 0.25rem;
338
+ background: var(--ds-input-bg);
339
+ border-radius: 1rem;
340
+ border: 1px solid var(--ds-border);
341
+ }
342
+
343
+ .ds__day-btn {
344
+ flex: 1;
345
+ padding: 0.625rem;
346
+ border-radius: 0.75rem;
347
+ font-size: 0.875rem;
348
+ font-weight: 700;
349
+ border: none;
350
+ background: transparent;
351
+ color: var(--ds-text-muted);
352
+ cursor: pointer;
353
+ transition: background 0.2s, color 0.2s, box-shadow 0.2s;
354
+ }
355
+
356
+ .ds__day-btn--active {
357
+ background: var(--ds-day-active-bg);
358
+ color: var(--ds-day-active-text);
359
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
360
+ }
361
+
362
+ :global(.theme-dark) .ds__day-btn--active {
363
+ box-shadow: 0 4px 12px rgba(255, 255, 255, 0.1);
364
+ }
365
+
366
+
367
+ .ds__time-wrap {
368
+ position: relative;
369
+ }
370
+
371
+ .ds__time-input {
372
+ width: 100%;
373
+ background: var(--ds-input-bg);
374
+ border: 1px solid var(--ds-border);
375
+ border-radius: 1rem;
376
+ padding: 0.75rem 1.25rem;
377
+ font-size: 1.125rem;
378
+ color: var(--ds-text);
379
+ box-sizing: border-box;
380
+ transition: border-color 0.2s;
381
+ }
382
+
383
+ .ds__time-input:focus {
384
+ outline: none;
385
+ border-color: rgba(16, 185, 129, 0.4);
386
+ }
387
+
388
+ .ds__time-input::-webkit-calendar-picker-indicator {
389
+ cursor: pointer;
390
+ opacity: 0;
391
+ position: absolute;
392
+ inset: 0;
393
+ width: 100%;
394
+ height: 100%;
395
+ }
396
+
397
+ .ds__time-icon {
398
+ position: absolute;
399
+ right: 1.25rem;
400
+ top: 50%;
401
+ transform: translateY(-50%);
402
+ width: 1.25rem;
403
+ height: 1.25rem;
404
+ color: var(--ds-text-dim);
405
+ pointer-events: none;
406
+ }
407
+
408
+
409
+ .ds__macro-grid {
410
+ display: grid;
411
+ grid-template-columns: repeat(4, 1fr);
412
+ gap: 0.75rem;
413
+ }
414
+
415
+ .ds__macro-btn {
416
+ display: flex;
417
+ flex-direction: column;
418
+ align-items: center;
419
+ gap: 0.5rem;
420
+ padding: 0.75rem 0.5rem;
421
+ border: 1px solid transparent;
422
+ border-radius: 1rem;
423
+ background: transparent;
424
+ cursor: pointer;
425
+ transition: background 0.2s, border-color 0.2s, box-shadow 0.2s;
426
+ }
427
+
428
+ .ds__macro-btn:hover .ds__macro-icon-wrap {
429
+ transform: scale(1.1);
430
+ }
431
+
432
+ .ds__macro-btn--active {
433
+ background: var(--ds-macro-active-bg);
434
+ border-color: var(--ds-macro-active-border);
435
+ box-shadow: 0 0 30px rgba(52, 211, 153, 0.1);
436
+ }
437
+
438
+ .ds__macro-icon-wrap {
439
+ width: 2.5rem;
440
+ height: 2.5rem;
441
+ border-radius: 50%;
442
+ background: var(--ds-input-bg);
443
+ border: 1px solid var(--ds-border);
444
+ display: flex;
445
+ align-items: center;
446
+ justify-content: center;
447
+ transition: background 0.2s, border-color 0.2s, box-shadow 0.2s, transform 0.2s;
448
+ }
449
+
450
+ .ds__macro-btn--active .ds__macro-icon-wrap {
451
+ background: var(--ds-macro-active-icon-bg);
452
+ border-color: var(--ds-macro-active-icon-bg);
453
+ box-shadow: 0 0 20px rgba(52, 211, 153, 0.4);
454
+ transform: scale(1.15);
455
+ }
456
+
457
+ .ds__macro-icon {
458
+ width: 1.25rem;
459
+ height: 1.25rem;
460
+ transition: color 0.2s;
461
+ }
462
+
463
+ .ds__macro-icon--liquid { color: var(--ds-cyan); }
464
+ .ds__macro-icon--carbs { color: var(--ds-amber); }
465
+ .ds__macro-icon--protein { color: var(--ds-rose); }
466
+ .ds__macro-icon--fat { color: var(--ds-yellow); }
467
+
468
+ .ds__macro-btn--active .ds__macro-icon {
469
+ color: #000;
470
+ }
471
+
472
+ .ds__macro-label {
473
+ font-size: 0.7rem;
474
+ font-weight: 700;
475
+ color: var(--ds-text-muted);
476
+ transition: color 0.2s, font-weight 0.2s;
477
+ }
478
+
479
+ .ds__macro-btn--active .ds__macro-label {
480
+ color: var(--ds-text);
481
+ font-weight: 900;
482
+ }
483
+
484
+
485
+ .ds__panel {
486
+ background: var(--ds-panel-bg);
487
+ border-radius: 2.5rem;
488
+ border: 1px solid var(--ds-border);
489
+ padding: 2rem;
490
+ display: flex;
491
+ flex-direction: column;
492
+ gap: 2.5rem;
493
+ }
494
+
495
+ @media (min-width: 768px) {
496
+ .ds__panel {
497
+ padding: 3rem;
498
+ }
499
+ }
500
+
501
+ .ds__panel-title {
502
+ font-size: 0.75rem;
503
+ font-weight: 900;
504
+ text-transform: uppercase;
505
+ letter-spacing: 0.15em;
506
+ color: var(--ds-text-muted);
507
+ display: flex;
508
+ align-items: center;
509
+ gap: 0.75rem;
510
+ margin: 0;
511
+ }
512
+
513
+ .ds__pulse-icon {
514
+ color: var(--ds-emerald);
515
+ width: 1rem;
516
+ height: 1rem;
517
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
518
+ }
519
+
520
+ @keyframes pulse {
521
+ 0%, 100% { opacity: 1; }
522
+ 50% { opacity: 0.4; }
523
+ }
524
+
525
+
526
+ .ds__stages {
527
+ display: flex;
528
+ flex-direction: column;
529
+ gap: 2.5rem;
530
+ }
531
+
532
+ .ds__stage {
533
+ transition: opacity 0.7s, transform 0.7s;
534
+ }
535
+
536
+ .ds__stage--inactive {
537
+ opacity: 0.35;
538
+ filter: grayscale(1);
539
+ }
540
+
541
+ .ds__stage--active {
542
+ opacity: 1;
543
+ transform: scale(1.02);
544
+ }
545
+
546
+ .ds__stage--done {
547
+ opacity: 1;
548
+ filter: grayscale(1);
549
+ }
550
+
551
+ .ds__stage-header {
552
+ display: flex;
553
+ justify-content: space-between;
554
+ align-items: center;
555
+ margin-bottom: 1rem;
556
+ }
557
+
558
+ .ds__stage-name-row {
559
+ display: flex;
560
+ align-items: center;
561
+ gap: 0.75rem;
562
+ }
563
+
564
+ .ds__stage-dot {
565
+ width: 0.5rem;
566
+ height: 0.5rem;
567
+ border-radius: 50%;
568
+ }
569
+
570
+ .ds__stage-dot--rose { background: var(--ds-rose); }
571
+ .ds__stage-dot--cyan { background: var(--ds-cyan); }
572
+ .ds__stage-dot--emerald { background: var(--ds-emerald); }
573
+
574
+ .ds__stage-name {
575
+ font-size: 0.875rem;
576
+ font-weight: 700;
577
+ color: var(--ds-text);
578
+ text-transform: uppercase;
579
+ letter-spacing: 0.05em;
580
+ }
581
+
582
+ .ds__stage-ph {
583
+ font-size: 0.625rem;
584
+ color: var(--ds-text-muted);
585
+ background: rgba(255, 255, 255, 0.05);
586
+ padding: 0.125rem 0.5rem;
587
+ border-radius: 0.25rem;
588
+ }
589
+
590
+ .ds__stage-track {
591
+ width: 100%;
592
+ height: 0.375rem;
593
+ background: rgba(255, 255, 255, 0.05);
594
+ border-radius: 9999px;
595
+ overflow: hidden;
596
+ }
597
+
598
+ .ds__stage-bar {
599
+ height: 100%;
600
+ border-radius: 9999px;
601
+ transition: width 0.6s ease;
602
+ width: 0%;
603
+ }
604
+
605
+ .ds__stage-bar--rose {
606
+ background: var(--ds-rose);
607
+ box-shadow: 0 0 10px rgba(244, 63, 94, 0.4);
608
+ }
609
+
610
+ .ds__stage-bar--cyan {
611
+ background: var(--ds-cyan);
612
+ box-shadow: 0 0 10px rgba(6, 182, 212, 0.4);
613
+ }
614
+
615
+ .ds__stage-bar--emerald {
616
+ background: var(--ds-emerald);
617
+ box-shadow: 0 0 10px rgba(16, 185, 129, 0.4);
618
+ }
619
+
620
+ .ds__stage-status {
621
+ font-size: 0.6875rem;
622
+ color: var(--ds-text-muted);
623
+ margin: 0.75rem 0 0;
624
+ font-style: italic;
625
+ font-weight: 500;
626
+ }
627
+ </style>