@jjlmoya/utils-cooking 1.2.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 (130) hide show
  1. package/package.json +60 -0
  2. package/src/category/i18n/en.ts +24 -0
  3. package/src/category/i18n/es.ts +208 -0
  4. package/src/category/i18n/fr.ts +24 -0
  5. package/src/category/index.ts +37 -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 +11 -0
  10. package/src/env.d.ts +5 -0
  11. package/src/index.ts +32 -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/i18n-titles.test.ts +66 -0
  18. package/src/tests/locale_completeness.test.ts +42 -0
  19. package/src/tests/mocks/astro_mock.js +2 -0
  20. package/src/tests/no_h1_in_components.test.ts +48 -0
  21. package/src/tests/seo_length.test.ts +22 -0
  22. package/src/tests/tool_validation.test.ts +17 -0
  23. package/src/tool/american-kitchen-converter/AmericanKitchenEngine.ts +259 -0
  24. package/src/tool/american-kitchen-converter/bibliography.astro +6 -0
  25. package/src/tool/american-kitchen-converter/component.astro +838 -0
  26. package/src/tool/american-kitchen-converter/i18n/en.ts +282 -0
  27. package/src/tool/american-kitchen-converter/i18n/es.ts +281 -0
  28. package/src/tool/american-kitchen-converter/i18n/fr.ts +292 -0
  29. package/src/tool/american-kitchen-converter/index.ts +24 -0
  30. package/src/tool/american-kitchen-converter/seo.astro +8 -0
  31. package/src/tool/banana-ripeness/BananaCare.css +587 -0
  32. package/src/tool/banana-ripeness/BananaEngine.ts +79 -0
  33. package/src/tool/banana-ripeness/bibliography.astro +6 -0
  34. package/src/tool/banana-ripeness/component.astro +285 -0
  35. package/src/tool/banana-ripeness/i18n/en.ts +177 -0
  36. package/src/tool/banana-ripeness/i18n/es.ts +177 -0
  37. package/src/tool/banana-ripeness/i18n/fr.ts +177 -0
  38. package/src/tool/banana-ripeness/index.ts +24 -0
  39. package/src/tool/banana-ripeness/seo.astro +8 -0
  40. package/src/tool/brine/bibliography.astro +6 -0
  41. package/src/tool/brine/component.astro +884 -0
  42. package/src/tool/brine/i18n/en.ts +221 -0
  43. package/src/tool/brine/i18n/es.ts +222 -0
  44. package/src/tool/brine/i18n/fr.ts +221 -0
  45. package/src/tool/brine/index.ts +26 -0
  46. package/src/tool/brine/seo.astro +8 -0
  47. package/src/tool/cookware-guide/CookwareGuide.css +487 -0
  48. package/src/tool/cookware-guide/bibliography.astro +6 -0
  49. package/src/tool/cookware-guide/component.astro +164 -0
  50. package/src/tool/cookware-guide/i18n/en.ts +163 -0
  51. package/src/tool/cookware-guide/i18n/es.ts +163 -0
  52. package/src/tool/cookware-guide/i18n/fr.ts +164 -0
  53. package/src/tool/cookware-guide/index.ts +24 -0
  54. package/src/tool/cookware-guide/init.ts +174 -0
  55. package/src/tool/cookware-guide/seo.astro +8 -0
  56. package/src/tool/egg-timer/EggTimer.css +503 -0
  57. package/src/tool/egg-timer/bibliography.astro +14 -0
  58. package/src/tool/egg-timer/component.astro +281 -0
  59. package/src/tool/egg-timer/i18n/en.ts +230 -0
  60. package/src/tool/egg-timer/i18n/es.ts +222 -0
  61. package/src/tool/egg-timer/i18n/fr.ts +121 -0
  62. package/src/tool/egg-timer/index.ts +27 -0
  63. package/src/tool/egg-timer/seo.astro +39 -0
  64. package/src/tool/ingredient-rescaler/IngredientRescaler.css +308 -0
  65. package/src/tool/ingredient-rescaler/bibliography.astro +6 -0
  66. package/src/tool/ingredient-rescaler/component.astro +107 -0
  67. package/src/tool/ingredient-rescaler/i18n/en.ts +265 -0
  68. package/src/tool/ingredient-rescaler/i18n/es.ts +268 -0
  69. package/src/tool/ingredient-rescaler/i18n/fr.ts +207 -0
  70. package/src/tool/ingredient-rescaler/index.ts +24 -0
  71. package/src/tool/ingredient-rescaler/init.ts +200 -0
  72. package/src/tool/ingredient-rescaler/seo.astro +8 -0
  73. package/src/tool/kitchen-timer/KitchenTimer.css +325 -0
  74. package/src/tool/kitchen-timer/bibliography.astro +6 -0
  75. package/src/tool/kitchen-timer/component.astro +341 -0
  76. package/src/tool/kitchen-timer/i18n/en.ts +154 -0
  77. package/src/tool/kitchen-timer/i18n/es.ts +154 -0
  78. package/src/tool/kitchen-timer/i18n/fr.ts +154 -0
  79. package/src/tool/kitchen-timer/index.ts +26 -0
  80. package/src/tool/kitchen-timer/init.ts +55 -0
  81. package/src/tool/kitchen-timer/lib/AudioHelper.ts +27 -0
  82. package/src/tool/kitchen-timer/lib/DockManager.ts +97 -0
  83. package/src/tool/kitchen-timer/lib/KitchenTimer.ts +264 -0
  84. package/src/tool/kitchen-timer/seo.astro +8 -0
  85. package/src/tool/meringue-peak/MeringueCalculator.css +298 -0
  86. package/src/tool/meringue-peak/bibliography.astro +6 -0
  87. package/src/tool/meringue-peak/component.astro +169 -0
  88. package/src/tool/meringue-peak/i18n/en.ts +257 -0
  89. package/src/tool/meringue-peak/i18n/es.ts +234 -0
  90. package/src/tool/meringue-peak/i18n/fr.ts +234 -0
  91. package/src/tool/meringue-peak/index.ts +24 -0
  92. package/src/tool/meringue-peak/seo.astro +8 -0
  93. package/src/tool/mold-scaler/MoldScaler.css +406 -0
  94. package/src/tool/mold-scaler/bibliography.astro +6 -0
  95. package/src/tool/mold-scaler/component.astro +126 -0
  96. package/src/tool/mold-scaler/i18n/en.ts +268 -0
  97. package/src/tool/mold-scaler/i18n/es.ts +269 -0
  98. package/src/tool/mold-scaler/i18n/fr.ts +276 -0
  99. package/src/tool/mold-scaler/index.ts +26 -0
  100. package/src/tool/mold-scaler/init.ts +264 -0
  101. package/src/tool/mold-scaler/seo.astro +8 -0
  102. package/src/tool/pizza/Pizza.css +569 -0
  103. package/src/tool/pizza/bibliography.astro +6 -0
  104. package/src/tool/pizza/calculator.ts +143 -0
  105. package/src/tool/pizza/component.astro +237 -0
  106. package/src/tool/pizza/i18n/en.ts +288 -0
  107. package/src/tool/pizza/i18n/es.ts +289 -0
  108. package/src/tool/pizza/i18n/fr.ts +288 -0
  109. package/src/tool/pizza/index.ts +27 -0
  110. package/src/tool/pizza/seo.astro +8 -0
  111. package/src/tool/roux-guide/RouxGuide.css +483 -0
  112. package/src/tool/roux-guide/bibliography.astro +6 -0
  113. package/src/tool/roux-guide/component.astro +194 -0
  114. package/src/tool/roux-guide/i18n/en.ts +233 -0
  115. package/src/tool/roux-guide/i18n/es.ts +225 -0
  116. package/src/tool/roux-guide/i18n/fr.ts +225 -0
  117. package/src/tool/roux-guide/index.ts +24 -0
  118. package/src/tool/roux-guide/init.ts +187 -0
  119. package/src/tool/roux-guide/seo.astro +8 -0
  120. package/src/tool/sourdough-calculator/SourdoughCalculator.css +369 -0
  121. package/src/tool/sourdough-calculator/bibliography.astro +6 -0
  122. package/src/tool/sourdough-calculator/component.astro +198 -0
  123. package/src/tool/sourdough-calculator/i18n/en.ts +242 -0
  124. package/src/tool/sourdough-calculator/i18n/es.ts +243 -0
  125. package/src/tool/sourdough-calculator/i18n/fr.ts +248 -0
  126. package/src/tool/sourdough-calculator/index.ts +24 -0
  127. package/src/tool/sourdough-calculator/init.ts +131 -0
  128. package/src/tool/sourdough-calculator/seo.astro +8 -0
  129. package/src/tools.ts +29 -0
  130. package/src/types.ts +73 -0
@@ -0,0 +1,264 @@
1
+ import { getAudioContext, playBeep } from "./AudioHelper";
2
+
3
+ export class KitchenTimer extends EventTarget {
4
+ element: HTMLElement;
5
+ private inputs: { h: HTMLInputElement; m: HTMLInputElement; s: HTMLInputElement };
6
+ private btnToggle: HTMLButtonElement;
7
+ private btnReset: HTMLButtonElement;
8
+ private btnAdd1m: HTMLButtonElement;
9
+ private btnAdd5m: HTMLButtonElement;
10
+ private statusText: HTMLElement;
11
+ private timerNameInput: HTMLInputElement;
12
+
13
+ totalSeconds: number = 0;
14
+ remainingSeconds: number = 0;
15
+ private intervalId: number | null = null;
16
+ isRunning: boolean = false;
17
+
18
+ private audioContext: AudioContext | null = null;
19
+
20
+ constructor(element: HTMLElement) {
21
+ super();
22
+ this.element = element;
23
+
24
+ const hoursInput = element.querySelector(".hours") as HTMLInputElement | null;
25
+ const minutesInput = element.querySelector(".minutes") as HTMLInputElement | null;
26
+ const secondsInput = element.querySelector(".seconds") as HTMLInputElement | null;
27
+
28
+ if (!minutesInput || !secondsInput) {
29
+ throw new Error("Missing required time input elements");
30
+ }
31
+
32
+ this.inputs = {
33
+ h: hoursInput || document.createElement('input'),
34
+ m: minutesInput,
35
+ s: secondsInput,
36
+ };
37
+
38
+ this.btnToggle = element.querySelector(".btn-toggle") as HTMLButtonElement;
39
+ this.btnReset = element.querySelector(".btn-reset") as HTMLButtonElement;
40
+ this.btnAdd1m = element.querySelector(".btn-add-1m") as HTMLButtonElement;
41
+ this.btnAdd5m = element.querySelector(".btn-add-5m") as HTMLButtonElement;
42
+ this.statusText = element.querySelector(".status-text") as HTMLElement;
43
+ this.timerNameInput = element.querySelector(".timer-name") as HTMLInputElement;
44
+
45
+ if (!this.btnToggle || !this.btnReset || !this.statusText || !this.timerNameInput) {
46
+ throw new Error("Missing required control elements");
47
+ }
48
+
49
+ this.initEvents();
50
+ this.validateInputs();
51
+ }
52
+
53
+ private initEvents() {
54
+ this.btnToggle.addEventListener("click", () => this.toggle());
55
+ this.btnReset.addEventListener("click", () => this.reset());
56
+ this.btnAdd1m.addEventListener("click", () => this.addTime(60));
57
+ this.btnAdd5m.addEventListener("click", () => this.addTime(300));
58
+
59
+ Object.values(this.inputs).forEach((input) => {
60
+ input.addEventListener("change", () => this.validateInputs());
61
+ input.addEventListener("input", () => {
62
+ if (this.isRunning) this.pause();
63
+ this.checkStartButton();
64
+ });
65
+ });
66
+
67
+ this.timerNameInput.addEventListener("input", () => {
68
+ this.dispatchUpdate();
69
+ });
70
+ }
71
+
72
+ private getInputSeconds(): number {
73
+ const h = parseInt(this.inputs.h.value) || 0;
74
+ const m = parseInt(this.inputs.m.value) || 0;
75
+ const s = parseInt(this.inputs.s.value) || 0;
76
+ return h * 3600 + m * 60 + s;
77
+ }
78
+
79
+ private setDisplay(totalSec: number) {
80
+ const h = Math.floor(totalSec / 3600);
81
+ const m = Math.floor((totalSec % 3600) / 60);
82
+ const s = totalSec % 60;
83
+
84
+ if (this.element.querySelector(".hours")) {
85
+ this.inputs.h.value = h.toString().padStart(2, "0");
86
+ this.inputs.m.value = m.toString().padStart(2, "0");
87
+ } else {
88
+ this.inputs.m.value = Math.floor(totalSec / 60).toString().padStart(2, "0");
89
+ }
90
+ this.inputs.s.value = s.toString().padStart(2, "0");
91
+ }
92
+
93
+ private validateInputs() {
94
+ let s = this.getInputSeconds();
95
+ if (s < 0) s = 0;
96
+ this.setDisplay(s);
97
+ this.remainingSeconds = s;
98
+ this.totalSeconds = s;
99
+ this.checkStartButton();
100
+ this.dispatchUpdate();
101
+ }
102
+
103
+ private addTime(seconds: number) {
104
+ const current = this.getInputSeconds();
105
+ this.pause();
106
+ this.setDisplay(current + seconds);
107
+ this.validateInputs();
108
+ }
109
+
110
+ toggle() {
111
+ if (this.isRunning) {
112
+ this.pause();
113
+ } else {
114
+ this.start();
115
+ }
116
+ }
117
+
118
+ start() {
119
+ if (this.remainingSeconds <= 0) this.validateInputs();
120
+ if (this.remainingSeconds <= 0) return;
121
+
122
+ this.isRunning = true;
123
+ this.updateUIState();
124
+ this.dispatchUpdate();
125
+
126
+ if (!this.audioContext) {
127
+ this.audioContext = getAudioContext();
128
+ }
129
+
130
+ if (this.audioContext?.state === "suspended") {
131
+ this.audioContext.resume().catch(() => {
132
+ console.warn("Could not resume AudioContext");
133
+ });
134
+ }
135
+
136
+ this.intervalId = window.setInterval(() => {
137
+ this.tick();
138
+ }, 1000);
139
+ }
140
+
141
+ pause() {
142
+ this.isRunning = false;
143
+ if (this.intervalId) {
144
+ clearInterval(this.intervalId);
145
+ this.intervalId = null;
146
+ }
147
+ this.updateUIState();
148
+ this.dispatchUpdate();
149
+ }
150
+
151
+ reset() {
152
+ this.pause();
153
+ this.setDisplay(0);
154
+ this.remainingSeconds = 0;
155
+ this.totalSeconds = 0;
156
+ this.statusText.textContent = "Listo";
157
+ this.statusText.classList.remove("running", "finished");
158
+ this.element.classList.remove("finished");
159
+ this.dispatchUpdate();
160
+ }
161
+
162
+ tick() {
163
+ if (this.remainingSeconds > 0) {
164
+ this.remainingSeconds--;
165
+ this.setDisplay(this.remainingSeconds);
166
+ this.updateProgressBar();
167
+ this.dispatchUpdate();
168
+ } else {
169
+ this.timeUp();
170
+ }
171
+ }
172
+
173
+ private timeUp() {
174
+ this.pause();
175
+ this.playAlarm();
176
+ this.statusText.textContent = "¡TIEMPO!";
177
+ this.statusText.classList.add("finished");
178
+ this.element.classList.add("finished");
179
+
180
+ if ("Notification" in window && Notification.permission === "granted") {
181
+ new Notification(`Temporizador Terminado de ${this.getName()}`);
182
+ }
183
+ this.dispatchUpdate();
184
+ }
185
+
186
+ private playAlarm() {
187
+ if (!this.audioContext) return;
188
+
189
+ const playTone = () => {
190
+ try {
191
+ playBeep(this.audioContext!, 880, 0.1);
192
+ setTimeout(() => playBeep(this.audioContext!, 440, 0.1), 200);
193
+ setTimeout(() => playBeep(this.audioContext!, 880, 0.2), 400);
194
+ } catch {
195
+ console.warn("Could not play alarm tone");
196
+ }
197
+ };
198
+
199
+ playTone();
200
+ let count = 0;
201
+ const alarmInterval = setInterval(() => {
202
+ if (++count > 5) clearInterval(alarmInterval);
203
+ else playTone();
204
+ }, 1000);
205
+ }
206
+
207
+ private updateUIState() {
208
+ if (this.isRunning) {
209
+ this.updateUIRunning();
210
+ } else {
211
+ this.updateUIPaused();
212
+ }
213
+ this.checkStartButton();
214
+ }
215
+
216
+ private updateUIRunning() {
217
+ const btnText = this.element.querySelector(".btn-toggle .btn-text");
218
+ const iconPlay = this.element.querySelector(".icon-play");
219
+ const iconPause = this.element.querySelector(".icon-pause");
220
+ if (btnText) btnText.textContent = "Pausar";
221
+ iconPlay?.setAttribute("style", "display: none;");
222
+ iconPause?.removeAttribute("style");
223
+ this.element.classList.add("running");
224
+ this.statusText.classList.add("running");
225
+ this.statusText.textContent = "Corriendo";
226
+ Object.values(this.inputs).forEach((i) => (i.disabled = true));
227
+ }
228
+
229
+ private updateUIPaused() {
230
+ const btnText = this.element.querySelector(".btn-toggle .btn-text");
231
+ const iconPlay = this.element.querySelector(".icon-play");
232
+ const iconPause = this.element.querySelector(".icon-pause");
233
+ if (btnText) btnText.textContent = "Iniciar";
234
+ iconPlay?.removeAttribute("style");
235
+ iconPause?.setAttribute("style", "display: none;");
236
+ this.element.classList.remove("running");
237
+ this.statusText.classList.remove("running");
238
+ this.statusText.textContent = this.remainingSeconds > 0 && this.remainingSeconds < this.totalSeconds ? "Pausado" : "Listo";
239
+ Object.values(this.inputs).forEach((i) => (i.disabled = false));
240
+ }
241
+
242
+ private checkStartButton() {
243
+ const canStart = this.isRunning || this.getInputSeconds() > 0;
244
+ this.btnToggle.disabled = !canStart;
245
+ this.btnToggle.classList.toggle("disabled", !canStart);
246
+ }
247
+
248
+ private updateProgressBar() {
249
+ if (this.totalSeconds === 0) return;
250
+ const progress = 1 - this.remainingSeconds / this.totalSeconds;
251
+ const progressFill = this.element.querySelector(".progress-fill") as HTMLElement;
252
+ if (progressFill) {
253
+ progressFill.style.transform = `scaleX(${progress})`;
254
+ }
255
+ }
256
+
257
+ private dispatchUpdate() {
258
+ this.dispatchEvent(new CustomEvent("update", { detail: this }));
259
+ }
260
+
261
+ public getName(): string {
262
+ return this.timerNameInput.value;
263
+ }
264
+ }
@@ -0,0 +1,8 @@
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { content } from './i18n/es';
4
+
5
+ const locale = 'es';
6
+ ---
7
+
8
+ <SEORenderer content={{ sections: content.seo, locale }} />
@@ -0,0 +1,298 @@
1
+ .meringue-calculator-container {
2
+ --primary: #f472b6;
3
+ --primary-dark: #db2777;
4
+ --text-dark: #1e293b;
5
+ --text-muted: #64748b;
6
+ --text-light: #cbd5e1;
7
+ --bg-light: #f1f5f9;
8
+ --border-dark: #334155;
9
+ --border-muted: #e2e8f0;
10
+ --pink-light: #f472b6;
11
+ --card-bg: #fff;
12
+ --card-border: #e2e8f0;
13
+ --card-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.1);
14
+ --card-hover-shadow: 0 12px 40px 0 rgba(31, 38, 135, 0.15);
15
+ --input-bg: #fff;
16
+ --input-border: #e2e8f0;
17
+ --input-dark-bg: #334155;
18
+ --input-dark-border: #475569;
19
+ --time-bg: #f1f5f9;
20
+ --pro-tip-bg: #fdf2f8;
21
+ --pro-tip-border: #f472b6;
22
+
23
+ max-width: 800px;
24
+ margin: 2rem auto;
25
+ padding: 1rem;
26
+ display: flex;
27
+ flex-direction: column;
28
+ gap: 2rem;
29
+ }
30
+
31
+ .theme-dark .meringue-calculator-container {
32
+ --text-dark: #f1f5f9;
33
+ --text-muted: #94a3b8;
34
+ --text-light: #cbd5e1;
35
+ --bg-light: #1e293b;
36
+ --border-dark: #475569;
37
+ --border-muted: #334155;
38
+ --card-bg: #1e293b;
39
+ --card-border: #334155;
40
+ --card-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.5);
41
+ --card-hover-shadow: 0 12px 40px 0 rgba(0, 0, 0, 0.3);
42
+ --input-bg: #334155;
43
+ --input-border: #475569;
44
+ --input-dark-bg: #1e293b;
45
+ --input-dark-border: #475569;
46
+ --time-bg: #1e293b;
47
+ --pro-tip-bg: #4c1d3a;
48
+ --pro-tip-border: #db2777;
49
+ }
50
+
51
+ .meringue-card {
52
+ background: var(--card-bg);
53
+ backdrop-filter: blur(12px);
54
+ -webkit-backdrop-filter: blur(12px);
55
+ border: 1px solid var(--card-border);
56
+ border-radius: 24px;
57
+ padding: 2.5rem;
58
+ box-shadow: var(--card-shadow);
59
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
60
+ }
61
+
62
+ .meringue-card:hover {
63
+ transform: translateY(-4px);
64
+ box-shadow: var(--card-hover-shadow);
65
+ }
66
+
67
+ .meringue-input-section {
68
+ display: grid;
69
+ grid-template-columns: 1fr 1fr;
70
+ gap: 2rem;
71
+ margin-bottom: 2.5rem;
72
+ }
73
+
74
+ @media (max-width: 640px) {
75
+ .meringue-input-section {
76
+ grid-template-columns: 1fr;
77
+ }
78
+ }
79
+
80
+ .meringue-input-group {
81
+ display: flex;
82
+ flex-direction: column;
83
+ gap: 0.75rem;
84
+ }
85
+
86
+ .meringue-input-group label {
87
+ font-size: 0.875rem;
88
+ font-weight: 700;
89
+ color: var(--text-muted);
90
+ text-transform: uppercase;
91
+ letter-spacing: 0.05em;
92
+ }
93
+
94
+ .meringue-input-wrapper {
95
+ position: relative;
96
+ display: flex;
97
+ align-items: center;
98
+ }
99
+
100
+ .meringue-input-wrapper input {
101
+ width: 100%;
102
+ padding: 1rem 1.25rem;
103
+ padding-right: 3rem;
104
+ border-radius: 16px;
105
+ border: 2px solid var(--input-border);
106
+ background: var(--input-bg);
107
+ font-size: 1.125rem;
108
+ font-weight: 600;
109
+ color: var(--text-dark);
110
+ transition: all 0.2s ease;
111
+ outline: none;
112
+ }
113
+
114
+ .meringue-input-wrapper input:focus {
115
+ border-color: var(--primary);
116
+ box-shadow: 0 0 0 4px rgba(244, 114, 182, 0.1);
117
+ }
118
+
119
+ .theme-dark .meringue-input-wrapper input:focus {
120
+ box-shadow: 0 0 0 4px rgba(244, 114, 182, 0.2);
121
+ }
122
+
123
+ .meringue-input-unit {
124
+ position: absolute;
125
+ right: 1.25rem;
126
+ font-weight: 700;
127
+ color: var(--text-light);
128
+ pointer-events: none;
129
+ }
130
+
131
+ .meringue-type-selector {
132
+ display: flex;
133
+ background: var(--bg-light);
134
+ padding: 0.5rem;
135
+ border-radius: 16px;
136
+ gap: 0.25rem;
137
+ }
138
+
139
+ .meringue-type-btn {
140
+ flex: 1;
141
+ padding: 0.75rem;
142
+ border: none;
143
+ background: transparent;
144
+ border-radius: 12px;
145
+ font-size: 0.875rem;
146
+ font-weight: 600;
147
+ color: var(--text-muted);
148
+ cursor: pointer;
149
+ transition: all 0.2s ease;
150
+ }
151
+
152
+ .meringue-type-btn.active {
153
+ background: var(--card-bg);
154
+ color: var(--primary-dark);
155
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
156
+ }
157
+
158
+ .theme-dark .meringue-type-btn.active {
159
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
160
+ }
161
+
162
+ .meringue-results-grid {
163
+ display: grid;
164
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
165
+ gap: 1.5rem;
166
+ }
167
+
168
+ .meringue-result-item {
169
+ background: var(--time-bg);
170
+ border: 1px solid var(--card-border);
171
+ border-radius: 20px;
172
+ padding: 1.5rem;
173
+ display: flex;
174
+ flex-direction: column;
175
+ gap: 0.5rem;
176
+ transition: all 0.2s ease;
177
+ }
178
+
179
+ .meringue-result-item:hover {
180
+ background: var(--card-bg);
181
+ transform: scale(1.02);
182
+ }
183
+
184
+ .meringue-result-label {
185
+ font-size: 0.75rem;
186
+ font-weight: 700;
187
+ color: var(--text-light);
188
+ text-transform: uppercase;
189
+ }
190
+
191
+ .meringue-result-value {
192
+ font-size: 1.5rem;
193
+ font-weight: 800;
194
+ color: var(--text-dark);
195
+ display: flex;
196
+ align-items: baseline;
197
+ gap: 0.25rem;
198
+ }
199
+
200
+ .meringue-result-unit {
201
+ font-size: 0.875rem;
202
+ font-weight: 600;
203
+ color: var(--text-light);
204
+ }
205
+
206
+ .meringue-time-section {
207
+ margin-top: 2.5rem;
208
+ padding-top: 2.5rem;
209
+ border-top: 1px solid var(--card-border);
210
+ }
211
+
212
+ .meringue-time-grid {
213
+ display: flex;
214
+ flex-direction: column;
215
+ gap: 1rem;
216
+ }
217
+
218
+ .meringue-time-row {
219
+ display: flex;
220
+ align-items: center;
221
+ justify-content: space-between;
222
+ padding: 1rem 1.5rem;
223
+ border-radius: 16px;
224
+ background: var(--time-bg);
225
+ transition: all 0.2s ease;
226
+ }
227
+
228
+ .meringue-time-info {
229
+ display: flex;
230
+ flex-direction: column;
231
+ }
232
+
233
+ .meringue-time-stage {
234
+ font-weight: 700;
235
+ color: var(--border-dark);
236
+ }
237
+
238
+ .meringue-time-desc {
239
+ font-size: 0.75rem;
240
+ color: var(--text-muted);
241
+ }
242
+
243
+ .meringue-time-val {
244
+ font-weight: 800;
245
+ color: var(--primary-dark);
246
+ font-size: 1.125rem;
247
+ }
248
+
249
+ .theme-dark .meringue-time-val {
250
+ color: var(--primary);
251
+ }
252
+
253
+ .meringue-pro-tip {
254
+ margin-top: 2rem;
255
+ padding: 1.25rem;
256
+ border-radius: 16px;
257
+ background: var(--pro-tip-bg);
258
+ border: 1px dashed var(--pro-tip-border);
259
+ display: flex;
260
+ gap: 1rem;
261
+ align-items: center;
262
+ }
263
+
264
+ .meringue-pro-tip svg {
265
+ color: var(--primary-dark);
266
+ flex-shrink: 0;
267
+ }
268
+
269
+ .theme-dark .meringue-pro-tip svg {
270
+ color: var(--primary);
271
+ }
272
+
273
+ .meringue-pro-tip p {
274
+ font-size: 0.875rem;
275
+ color: var(--primary-dark);
276
+ line-height: 1.5;
277
+ margin: 0;
278
+ }
279
+
280
+ .theme-dark .meringue-pro-tip p {
281
+ color: var(--pink-light);
282
+ }
283
+
284
+ .meringue-fade-in {
285
+ animation: meringue-fade-in 0.5s ease forwards;
286
+ }
287
+
288
+ @keyframes meringue-fade-in {
289
+ from {
290
+ opacity: 0;
291
+ transform: translateY(10px);
292
+ }
293
+
294
+ to {
295
+ opacity: 1;
296
+ transform: translateY(0);
297
+ }
298
+ }
@@ -0,0 +1,6 @@
1
+ ---
2
+ import { Bibliography } from '@jjlmoya/utils-shared';
3
+ import { content } from './i18n/es';
4
+ ---
5
+
6
+ <Bibliography links={content.bibliography} />