@jjlmoya/utils-cooking 1.9.0 → 1.11.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.
@@ -26,47 +26,6 @@ const VISCOSITY_RATIOS: Record<TextureLevel, number> = {
26
26
  4: 185,
27
27
  };
28
28
 
29
- const LIQUID_CONFIG: Record<LiquidType, LiquidConfig> = {
30
- milk: {
31
- name: 'Béchamel',
32
- rouxType: 'white',
33
- tip: 'Usa leche fría. Añádela gradualmente al principio o de golpe si bates fuerte.',
34
- },
35
- lightStock: {
36
- name: 'Velouté',
37
- rouxType: 'blond',
38
- tip: 'Usa fondo de ave o pescado. Deja que el roux huela a galleta antes de ligar.',
39
- },
40
- darkStock: {
41
- name: 'Espagnole',
42
- rouxType: 'brown',
43
- tip: 'Para salsas oscuras potentes. El roux debe estar color chocolate, pero sin quemarse.',
44
- },
45
- tomato: {
46
- name: 'Salsa de Tomate',
47
- rouxType: 'blond',
48
- tip: 'El roux ayudará a dar cuerpo y suavidad a la textura final del tomate.',
49
- },
50
- };
51
-
52
- const ROUX_INFO: Record<'white' | 'blond' | 'brown', RouxInfo> = {
53
- white: {
54
- label: 'Blanco',
55
- time: '2-3 min',
56
- description: 'Cocina solo hasta perder el olor a harina cruda. Sin color.',
57
- },
58
- blond: {
59
- label: 'Rubio',
60
- time: '5-8 min',
61
- description: 'Busca un color de mantequilla tostada y un aroma a nueces.',
62
- },
63
- brown: {
64
- label: 'Oscuro',
65
- time: '15-20 min',
66
- description: 'Fuego muy suave. Color chocolate. Nota: requiere un 10% más de peso.',
67
- },
68
- };
69
-
70
29
  interface RouxElements {
71
30
  volumeInput: HTMLInputElement;
72
31
  liquidBtns: NodeListOf<Element>;
@@ -124,9 +83,9 @@ function setupLiquidButtons(els: RouxElements, state: State, updateCalculations:
124
83
  els.liquidBtns.forEach((btn) => {
125
84
  btn.addEventListener('click', () => {
126
85
  els.liquidBtns.forEach((b) => {
127
- b.classList.remove('active', 'bg-white', 'dark:bg-slate-700', 'shadow-md', 'ring-2', 'ring-indigo-500/20');
86
+ b.classList.remove('active');
128
87
  });
129
- btn.classList.add('active', 'bg-white', 'dark:bg-slate-700', 'shadow-md', 'ring-2', 'ring-indigo-500/20');
88
+ btn.classList.add('active');
130
89
  state.liquid = (btn as HTMLElement).dataset.type as LiquidType || 'milk';
131
90
  updateCalculations();
132
91
  });
@@ -137,19 +96,16 @@ function setupTextureButtons(els: RouxElements, state: State, updateCalculations
137
96
  els.textureBtns.forEach((btn) => {
138
97
  btn.addEventListener('click', () => {
139
98
  els.textureBtns.forEach((b) => {
140
- b.classList.remove('active', 'border-amber-500/50', 'bg-amber-50', 'dark:bg-amber-900/10');
141
- b.classList.add('border-slate-100', 'dark:border-slate-800', 'bg-white', 'dark:bg-slate-900');
99
+ b.classList.remove('active');
142
100
  });
143
-
144
- btn.classList.remove('border-slate-100', 'dark:border-slate-800', 'bg-white', 'dark:bg-slate-900');
145
- btn.classList.add('active', 'border-amber-500/50', 'bg-amber-50', 'dark:bg-amber-900/10');
101
+ btn.classList.add('active');
146
102
  state.level = parseInt((btn as HTMLElement).dataset.level || '2') as TextureLevel;
147
103
  updateCalculations();
148
104
  });
149
105
  });
150
106
  }
151
107
 
152
- function updateDisplay(els: RouxElements, state: State): void {
108
+ function updateDisplay(els: RouxElements, state: State, LIQUID_CONFIG: Record<LiquidType, LiquidConfig>, ROUX_INFO: Record<'white' | 'blond' | 'brown', RouxInfo>): void {
153
109
  const config = LIQUID_CONFIG[state.liquid];
154
110
  const baseRatio = VISCOSITY_RATIOS[state.level];
155
111
  const liters = state.volume / 1000;
@@ -169,14 +125,61 @@ function updateDisplay(els: RouxElements, state: State): void {
169
125
  els.progressBar.style.width = `${(state.level / 4) * 100}%`;
170
126
  }
171
127
 
172
- export function initRouxGuide(): void {
128
+ function getLiquidConfig(ui: Record<string, string>): Record<LiquidType, LiquidConfig> {
129
+ return {
130
+ milk: {
131
+ name: ui.recipeBechamel as string,
132
+ rouxType: 'white',
133
+ tip: ui.tipBechamel as string,
134
+ },
135
+ lightStock: {
136
+ name: ui.recipeVeloute as string,
137
+ rouxType: 'blond',
138
+ tip: ui.tipVeloute as string,
139
+ },
140
+ darkStock: {
141
+ name: ui.recipeEspagnole as string,
142
+ rouxType: 'brown',
143
+ tip: ui.tipEspagnole as string,
144
+ },
145
+ tomato: {
146
+ name: ui.recipeTomato as string,
147
+ rouxType: 'blond',
148
+ tip: ui.tipTomato as string,
149
+ },
150
+ };
151
+ }
152
+
153
+ function getRouxInfo(ui: Record<string, string>): Record<'white' | 'blond' | 'brown', RouxInfo> {
154
+ return {
155
+ white: {
156
+ label: ui.rouxWhiteLabel as string,
157
+ time: ui.timeWhite as string,
158
+ description: ui.descWhite as string,
159
+ },
160
+ blond: {
161
+ label: ui.rouxBlondLabel as string,
162
+ time: ui.timeBlond as string,
163
+ description: ui.descBlond as string,
164
+ },
165
+ brown: {
166
+ label: ui.rouxBrownLabel as string,
167
+ time: ui.timeBrown as string,
168
+ description: ui.descBrown as string,
169
+ },
170
+ };
171
+ }
172
+
173
+ export function initRouxGuide(ui: Record<string, string>): void {
173
174
  const els = setupElements();
174
175
  if (!els) return;
175
176
 
177
+ const LIQUID_CONFIG = getLiquidConfig(ui);
178
+ const ROUX_INFO = getRouxInfo(ui);
176
179
  const state: State = { volume: 1000, liquid: 'milk', level: 2 };
177
- const updateCalculations = () => updateDisplay(els, state);
180
+ const updateCalculations = () => updateDisplay(els, state, LIQUID_CONFIG, ROUX_INFO);
178
181
 
179
- els.volumeInput.addEventListener('input', (e) => {
182
+ els.volumeInput.addEventListener('input', (e: Event) => {
180
183
  state.volume = parseInt((e.target as HTMLInputElement).value) || 0;
181
184
  updateCalculations();
182
185
  });
@@ -1,587 +0,0 @@
1
- .banana-care-container {
2
- --bc-yellow: #fbbf24;
3
- --bc-yellow-dark: #eab308;
4
- --bc-orange: #b45309;
5
- --bc-text-dark: #18181b;
6
- --bc-text-light: #fff;
7
- --bc-text-muted: #a1a1aa;
8
- --bc-text-gray: #71717a;
9
- --bc-text-gray-dark: #52525b;
10
- --bc-blue-light: #4f8cee;
11
- --bc-blue: #3b82f6;
12
- --bc-blue-dark: #2563eb;
13
- --bc-blue-lighter: #60a5fa;
14
- --bc-green: #10b981;
15
- --bc-gray-light: #d4d4d8;
16
- --bc-slate-dark: #1e293b;
17
-
18
- max-width: 100%;
19
- margin: 2rem auto;
20
- padding: 1rem;
21
- display: flex;
22
- flex-direction: column;
23
- gap: 2rem;
24
- }
25
-
26
- .banana-card {
27
- background: #fafafa;
28
- backdrop-filter: blur(24px);
29
- border: 1px solid #e4e4e7;
30
- border-radius: 2.5rem;
31
- padding: 2.5rem;
32
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
33
- overflow: hidden;
34
- position: relative;
35
- }
36
-
37
- .theme-dark .banana-card {
38
- background: rgba(24, 24, 27, 0.4);
39
- border-color: rgba(113, 113, 122, 0.5);
40
- box-shadow: 0 32px 64px 0 rgba(0, 0, 0, 0.3);
41
- }
42
-
43
- .glow-bg {
44
- position: absolute;
45
- top: -96px;
46
- right: -96px;
47
- width: 256px;
48
- height: 256px;
49
- background: rgba(250, 204, 21, 0.2);
50
- border-radius: 9999px;
51
- filter: blur(96px);
52
- pointer-events: none;
53
- transition: all 700ms;
54
- }
55
-
56
- .glow-bg-2 {
57
- position: absolute;
58
- bottom: -96px;
59
- left: -96px;
60
- width: 256px;
61
- height: 256px;
62
- background: rgba(52, 211, 153, 0.1);
63
- border-radius: 9999px;
64
- filter: blur(96px);
65
- pointer-events: none;
66
- transition: all 700ms;
67
- }
68
-
69
- .banana-grid {
70
- position: relative;
71
- z-index: 10;
72
- max-width: 80rem;
73
- margin: 0 auto;
74
- display: grid;
75
- grid-template-columns: 1fr 1fr;
76
- gap: 2rem;
77
- align-items: center;
78
- }
79
-
80
- @media (max-width: 1024px) {
81
- .banana-grid {
82
- grid-template-columns: 1fr;
83
- }
84
- }
85
-
86
- .banana-visual-section {
87
- display: flex;
88
- flex-direction: column;
89
- align-items: center;
90
- justify-content: center;
91
- gap: 1rem;
92
- }
93
-
94
- .status-card {
95
- position: relative;
96
- width: 100%;
97
- max-width: 340px;
98
- aspect-ratio: 1;
99
- display: flex;
100
- align-items: center;
101
- justify-content: center;
102
- }
103
-
104
- .glow-indicator {
105
- position: absolute;
106
- inset: -100px;
107
- border-radius: 9999px;
108
- filter: blur(80px);
109
- opacity: 0.3;
110
- transition: all 1000ms;
111
- }
112
-
113
- .status-visual {
114
- position: relative;
115
- z-index: 10;
116
- transition: all 700ms ease-out;
117
- }
118
-
119
- .banana-svg {
120
- width: 100%;
121
- height: 100%;
122
- filter: drop-shadow(0 25px 50px rgba(0, 0, 0, 0.3));
123
- }
124
-
125
- .banana-group {
126
- transition: all 700ms;
127
- transform-origin: center;
128
- }
129
-
130
- .banana-body {
131
- transition: color 500ms;
132
- color: var(--bc-yellow);
133
- }
134
-
135
- .banana-dot {
136
- transition: opacity 500ms cubic-bezier(0.4, 0, 0.2, 1);
137
- opacity: 0;
138
- }
139
-
140
- .status-info {
141
- display: flex;
142
- flex-direction: column;
143
- align-items: center;
144
- gap: 1rem;
145
- }
146
-
147
- .status-badge {
148
- display: inline-flex;
149
- align-items: center;
150
- gap: 0.5rem;
151
- padding: 0.375rem 1rem;
152
- border-radius: 9999px;
153
- background: rgba(255, 255, 255, 0.1);
154
- border: 1px solid rgba(255, 255, 255, 0.2);
155
- backdrop-filter: blur(12px);
156
- }
157
-
158
- .theme-dark .status-badge {
159
- background: rgba(0, 0, 0, 0.2);
160
- border-color: rgba(255, 255, 255, 0.05);
161
- }
162
-
163
- .status-dot {
164
- width: 0.625rem;
165
- height: 0.625rem;
166
- border-radius: 50%;
167
- box-shadow: 0 0 15px currentcolor;
168
- }
169
-
170
- .status-name {
171
- font-size: 0.875rem;
172
- font-weight: 900;
173
- text-transform: uppercase;
174
- letter-spacing: 0.05em;
175
- color: var(--bc-text-dark);
176
- }
177
-
178
- .theme-dark .status-name {
179
- color: var(--bc-text-light);
180
- }
181
-
182
- .status-description {
183
- font-size: 0.75rem;
184
- color: var(--bc-text-gray);
185
- font-weight: 700;
186
- max-width: 280px;
187
- text-align: center;
188
- min-height: 3em;
189
- }
190
-
191
- .theme-dark .status-description {
192
- color: var(--bc-text-muted);
193
- }
194
-
195
- .banana-controls {
196
- display: flex;
197
- flex-direction: column;
198
- }
199
-
200
- .controls-panel {
201
- padding: 2rem;
202
- border-radius: 2rem;
203
- background: #fff;
204
- border: 1px solid #e4e4e7;
205
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
206
- backdrop-filter: blur(12px);
207
- display: flex;
208
- flex-direction: column;
209
- gap: 2rem;
210
- }
211
-
212
- .theme-dark .controls-panel {
213
- background: rgba(0, 0, 0, 0.2);
214
- border-color: rgba(255, 255, 255, 0.05);
215
- }
216
-
217
- .control-group {
218
- display: flex;
219
- flex-direction: column;
220
- gap: 1rem;
221
- }
222
-
223
- .ripeness-header {
224
- display: flex;
225
- justify-content: space-between;
226
- align-items: flex-end;
227
- }
228
-
229
- .ripeness-label {
230
- font-size: 0.625rem;
231
- font-weight: 900;
232
- text-transform: uppercase;
233
- letter-spacing: 0.075em;
234
- color: var(--bc-text-muted);
235
- }
236
-
237
- .level-value {
238
- font-size: 1.875rem;
239
- font-weight: 900;
240
- color: var(--bc-yellow-dark);
241
- font-variant-numeric: tabular-nums;
242
- }
243
-
244
- .ripeness-slider {
245
- width: 100%;
246
- height: 0.75rem;
247
- border-radius: 9999px;
248
- background: #e4e4e7;
249
- border: none;
250
- outline: none;
251
- cursor: pointer;
252
- accent-color: var(--bc-yellow-dark);
253
- appearance: none;
254
- }
255
-
256
- .theme-dark .ripeness-slider {
257
- background: #27272a;
258
- }
259
-
260
- .ripeness-slider::-webkit-slider-thumb {
261
- appearance: none;
262
- width: 36px;
263
- height: 36px;
264
- background: var(--bc-yellow);
265
- cursor: pointer;
266
- border-radius: 50%;
267
- border: 4px solid white;
268
- box-shadow: 0 10px 25px rgba(251, 191, 36, 0.4);
269
- transition: all 0.2s;
270
- }
271
-
272
- .theme-dark .ripeness-slider::-webkit-slider-thumb {
273
- border-color: var(--bc-text-dark);
274
- }
275
-
276
- .ripeness-slider:active::-webkit-slider-thumb {
277
- transform: scale(0.9);
278
- }
279
-
280
- .grid-2-cols {
281
- display: grid;
282
- grid-template-columns: 1fr 1fr;
283
- gap: 1.5rem;
284
- }
285
-
286
- @media (max-width: 768px) {
287
- .grid-2-cols {
288
- grid-template-columns: 1fr;
289
- }
290
- }
291
-
292
- .prediction-box {
293
- padding: 1.5rem;
294
- border-radius: 1.875rem;
295
- background: #fafafa;
296
- border: 1px solid #e4e4e7;
297
- text-align: center;
298
- display: flex;
299
- flex-direction: column;
300
- justify-content: center;
301
- }
302
-
303
- .prediction-label {
304
- font-size: 0.625rem;
305
- font-weight: 900;
306
- text-transform: uppercase;
307
- letter-spacing: 0.05em;
308
- color: var(--bc-orange);
309
- margin-bottom: 0.5rem;
310
- }
311
-
312
- :root[class~="dark"] .prediction-label {
313
- color: var(--bc-yellow);
314
- }
315
-
316
- .prediction-value {
317
- display: flex;
318
- align-items: baseline;
319
- justify-content: center;
320
- gap: 0.25rem;
321
- }
322
-
323
- .days-number {
324
- font-size: 3.75rem;
325
- font-weight: 900;
326
- color: var(--bc-text-dark);
327
- font-variant-numeric: tabular-nums;
328
- }
329
-
330
- :root[class~="dark"] .days-number {
331
- color: var(--bc-text-light);
332
- }
333
-
334
- .days-unit {
335
- font-size: 0.75rem;
336
- font-weight: 900;
337
- color: var(--bc-text-muted);
338
- text-transform: uppercase;
339
- }
340
-
341
- .settings-column {
342
- display: flex;
343
- flex-direction: column;
344
- gap: 1rem;
345
- }
346
-
347
- .setting-item {
348
- padding: 1rem;
349
- border-radius: 0.5rem;
350
- border: 1px solid transparent;
351
- transition: all 0.2s;
352
- }
353
-
354
- .setting-item.temp {
355
- background: rgba(79, 172, 254, 0.05);
356
- border-color: rgba(79, 172, 254, 0.1);
357
- }
358
-
359
- .setting-item.humidity {
360
- background: rgba(59, 130, 246, 0.05);
361
- border-color: rgba(59, 130, 246, 0.1);
362
- }
363
-
364
- .setting-item:hover {
365
- background-color: rgba(79, 172, 254, 0.1);
366
- }
367
-
368
- .setting-header {
369
- display: flex;
370
- justify-content: space-between;
371
- align-items: center;
372
- margin-bottom: 0.5rem;
373
- }
374
-
375
- .setting-label {
376
- font-size: 0.5625rem;
377
- font-weight: 900;
378
- text-transform: uppercase;
379
- color: var(--bc-blue-light);
380
- }
381
-
382
- .setting-item.humidity .setting-label {
383
- color: var(--bc-blue);
384
- }
385
-
386
- .setting-value {
387
- font-size: 0.75rem;
388
- font-weight: 700;
389
- color: var(--bc-blue-dark);
390
- }
391
-
392
- .theme-dark .setting-value {
393
- color: var(--bc-blue-lighter);
394
- }
395
-
396
- .setting-slider {
397
- width: 100%;
398
- height: 0.25rem;
399
- border-radius: 9999px;
400
- background: #dbeafe;
401
- border: none;
402
- outline: none;
403
- cursor: pointer;
404
- accent-color: var(--bc-blue-light);
405
- appearance: none;
406
- }
407
-
408
- .theme-dark .setting-slider {
409
- background: rgba(59, 130, 246, 0.5);
410
- }
411
-
412
- .partner-btn {
413
- width: 100%;
414
- padding: 1.25rem;
415
- border-radius: 0.5rem;
416
- border: 1px solid rgba(16, 185, 129, 0.2);
417
- background: rgba(16, 185, 129, 0.05);
418
- display: flex;
419
- align-items: center;
420
- justify-content: space-between;
421
- cursor: pointer;
422
- transition: all 0.2s;
423
- }
424
-
425
- .partner-btn:hover {
426
- background: rgba(16, 185, 129, 0.1);
427
- }
428
-
429
- .partner-btn.active {
430
- background: var(--bc-green);
431
- }
432
-
433
- .theme-dark .partner-btn.active {
434
- background: var(--bc-green);
435
- }
436
-
437
- .partner-content {
438
- display: flex;
439
- align-items: center;
440
- gap: 1rem;
441
- text-align: left;
442
- }
443
-
444
- .partner-icon {
445
- padding: 0.75rem;
446
- border-radius: 0.5rem;
447
- background: rgba(16, 185, 129, 0.2);
448
- width: 2.5rem;
449
- height: 2.5rem;
450
- display: flex;
451
- align-items: center;
452
- justify-content: center;
453
- color: var(--bc-green);
454
- transition: transform 0.2s;
455
- }
456
-
457
- .partner-btn.active .partner-icon {
458
- transform: scale(1.1);
459
- }
460
-
461
- .partner-title {
462
- font-size: 0.75rem;
463
- font-weight: 900;
464
- color: var(--bc-text-dark);
465
- text-transform: uppercase;
466
- }
467
-
468
- .theme-dark .partner-title {
469
- color: var(--bc-text-light);
470
- }
471
-
472
- .partner-btn.active .partner-title {
473
- color: var(--bc-text-light);
474
- }
475
-
476
- .partner-subtitle {
477
- font-size: 0.625rem;
478
- color: var(--bc-text-muted);
479
- font-weight: 700;
480
- text-transform: uppercase;
481
- letter-spacing: 0.05em;
482
- }
483
-
484
- .partner-btn.active .partner-subtitle {
485
- color: rgba(255, 255, 255, 0.8);
486
- }
487
-
488
- .toggle-switch {
489
- width: 3rem;
490
- height: 1.5rem;
491
- border-radius: 9999px;
492
- background: #e4e4e7;
493
- position: relative;
494
- border: 1px solid var(--bc-gray-light);
495
- }
496
-
497
- .theme-dark .toggle-switch {
498
- background: #27272a;
499
- border-color: #3f3f46;
500
- }
501
-
502
- .toggle-circle {
503
- position: absolute;
504
- left: 0.25rem;
505
- top: 0.25rem;
506
- width: 1rem;
507
- height: 1rem;
508
- border-radius: 50%;
509
- background: white;
510
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
511
- transition: transform 300ms;
512
- }
513
-
514
- .toggle-circle.active {
515
- transform: translateX(1.5rem);
516
- }
517
-
518
- .info-grid {
519
- gap: 1rem;
520
- }
521
-
522
- .info-box {
523
- padding: 1.5rem;
524
- border-radius: 1.875rem;
525
- border: 1px solid #e4e4e7;
526
- }
527
-
528
- .theme-dark .info-box {
529
- background: rgba(113, 113, 122, 0.4);
530
- border-color: rgba(113, 113, 122, 0.5);
531
- }
532
-
533
- .info-box.conservation {
534
- background: #fafafa;
535
- }
536
-
537
- .info-box.conservation .info-title {
538
- color: var(--bc-text-dark);
539
- }
540
-
541
- .info-box.conservation .info-text {
542
- color: var(--bc-slate-dark);
543
- }
544
-
545
- .info-box.acceleration {
546
- background: rgba(250, 204, 21, 0.05);
547
- border-color: rgba(250, 204, 21, 0.2);
548
- }
549
-
550
- .info-title {
551
- font-size: 0.5625rem;
552
- font-weight: 900;
553
- text-transform: uppercase;
554
- letter-spacing: 0.05em;
555
- color: var(--bc-text-gray-dark);
556
- margin-bottom: 0.5rem;
557
- }
558
-
559
- .info-text {
560
- font-size: 0.75rem;
561
- color: var(--bc-text-dark);
562
- line-height: 1.5;
563
- font-weight: 700;
564
- margin: 0;
565
- }
566
-
567
- .theme-dark .info-text {
568
- color: var(--bc-gray-light);
569
- }
570
-
571
- .info-box.acceleration .info-title {
572
- color: var(--bc-orange);
573
- }
574
-
575
- .theme-dark .info-box.acceleration .info-title {
576
- color: var(--bc-yellow);
577
- }
578
-
579
- .info-box.acceleration .info-text {
580
- font-style: italic;
581
- color: var(--bc-text-dark);
582
- font-weight: 900;
583
- }
584
-
585
- .theme-dark .info-box.acceleration .info-text {
586
- color: var(--bc-text-light);
587
- }