@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,569 @@
1
+ .pizza-container {
2
+ --color-primary: #f97316;
3
+ --color-primary-dark: #ea580c;
4
+ --color-primary-light: #fed7aa;
5
+ --color-secondary: #3b82f6;
6
+ --color-secondary-dark: #1e40af;
7
+ --color-secondary-light: #bfdbfe;
8
+ --color-bg: #fff;
9
+ --color-border: #e2e8f0;
10
+ --color-text-primary: #1e293b;
11
+ --color-text-secondary: #64748b;
12
+ --color-text-tertiary: #94a3b8;
13
+ --color-white: #fff;
14
+ --color-blue-400: #60a5fa;
15
+ --color-blue-500: #3b82f6;
16
+ --color-blue-100: #dbeafe;
17
+ --color-blue-300: #93c5fd;
18
+ --color-orange-100: #fed7aa;
19
+ --color-orange-300: #fdba74;
20
+ --color-slate-200: #e2e8f0;
21
+ --color-slate-400: #cbd5e1;
22
+ --color-slate-800: #1e293b;
23
+ --color-slate-900: #0f172a;
24
+ --color-amber-100: #fef08a;
25
+ --color-orange-400: #fb923c;
26
+ --color-orange-600: #b45309;
27
+ --color-orange-700: #92400e;
28
+ --color-orange-900: #78350f;
29
+ --color-bg-dark: #0f172a;
30
+ --color-border-dark: #1e293b;
31
+
32
+ width: 100%;
33
+ }
34
+
35
+ .theme-dark .pizza-container {
36
+ --color-bg: #0f172a;
37
+ --color-border: #1e293b;
38
+ --color-text-primary: #f1f5f9;
39
+ --color-text-secondary: #cbd5e1;
40
+ --color-text-tertiary: #64748b;
41
+ }
42
+
43
+ .pizza-grid {
44
+ display: grid;
45
+ gap: 1.5rem;
46
+ grid-template-columns: 1fr;
47
+ }
48
+
49
+ @media (min-width: 1024px) {
50
+ .pizza-grid {
51
+ grid-template-columns: repeat(2, 1fr);
52
+ gap: 2rem;
53
+ }
54
+ }
55
+
56
+ .pizza-controls {
57
+ display: flex;
58
+ flex-direction: column;
59
+ gap: 1.5rem;
60
+ }
61
+
62
+ @media (min-width: 1024px) {
63
+ .pizza-controls {
64
+ grid-column: span 1;
65
+ grid-row: span 2;
66
+ }
67
+ }
68
+
69
+ .pizza-card {
70
+ background-color: var(--color-bg);
71
+ border-radius: 1.5rem;
72
+ padding: 1.5rem;
73
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
74
+ border: 1px solid var(--color-border);
75
+ }
76
+
77
+ @media (min-width: 768px) {
78
+ .pizza-card {
79
+ padding: 2rem;
80
+ }
81
+ }
82
+
83
+ .pizza-title {
84
+ font-size: 1.5rem;
85
+ font-weight: 700;
86
+ margin-bottom: 2rem;
87
+ display: flex;
88
+ align-items: center;
89
+ gap: 0.75rem;
90
+ color: var(--color-text-primary);
91
+ }
92
+
93
+ .pizza-icon {
94
+ width: 1.5rem;
95
+ height: 1.5rem;
96
+ color: var(--color-primary);
97
+ }
98
+
99
+ .pizza-controls-space {
100
+ display: flex;
101
+ flex-direction: column;
102
+ gap: 2.5rem;
103
+ }
104
+
105
+ .pizza-control-group {
106
+ display: flex;
107
+ flex-direction: column;
108
+ gap: 1rem;
109
+ }
110
+
111
+ .pizza-label-row {
112
+ display: flex;
113
+ justify-content: space-between;
114
+ margin-bottom: 1rem;
115
+ align-items: center;
116
+ }
117
+
118
+ .pizza-label {
119
+ font-size: 0.875rem;
120
+ font-weight: 500;
121
+ color: var(--color-text-secondary);
122
+ text-transform: uppercase;
123
+ letter-spacing: 0.05em;
124
+ }
125
+
126
+ .pizza-value {
127
+ font-size: 1.5rem;
128
+ font-weight: 700;
129
+ font-variant-numeric: tabular-nums;
130
+ color: var(--color-text-primary);
131
+ }
132
+
133
+ .pizza-value-blue {
134
+ color: var(--color-secondary);
135
+ }
136
+
137
+ .pizza-unit {
138
+ font-size: 0.875rem;
139
+ font-weight: 400;
140
+ color: var(--color-text-tertiary);
141
+ margin-left: 0.25rem;
142
+ }
143
+
144
+ .pizza-slider {
145
+ width: 100%;
146
+ height: 0.5rem;
147
+ background-color: #e2e8f0;
148
+ border-radius: 0.5rem;
149
+ appearance: none;
150
+ cursor: pointer;
151
+ accent-color: var(--color-primary);
152
+ transition: all 0.2s;
153
+ }
154
+
155
+ .pizza-slider:hover {
156
+ accent-color: var(--color-primary-dark);
157
+ }
158
+
159
+ .pizza-slider-blue {
160
+ accent-color: var(--color-secondary);
161
+ }
162
+
163
+ .pizza-slider-blue:hover {
164
+ accent-color: var(--color-secondary-dark);
165
+ }
166
+
167
+ .pizza-slider::-webkit-slider-thumb {
168
+ -webkit-appearance: none;
169
+ appearance: none;
170
+ width: 1.25rem;
171
+ height: 1.25rem;
172
+ border-radius: 50%;
173
+ background: currentcolor;
174
+ cursor: pointer;
175
+ box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.5);
176
+ }
177
+
178
+ .pizza-slider::-moz-range-thumb {
179
+ width: 1.25rem;
180
+ height: 1.25rem;
181
+ border-radius: 50%;
182
+ background: currentcolor;
183
+ cursor: pointer;
184
+ border: none;
185
+ box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.5);
186
+ }
187
+
188
+ .theme-dark .pizza-slider::-webkit-slider-thumb {
189
+ box-shadow: 0 0 0 4px rgba(15, 23, 42, 0.5);
190
+ }
191
+
192
+ .theme-dark .pizza-slider::-moz-range-thumb {
193
+ box-shadow: 0 0 0 4px rgba(15, 23, 42, 0.5);
194
+ }
195
+
196
+ .pizza-range-labels {
197
+ display: flex;
198
+ justify-content: space-between;
199
+ font-size: 0.75rem;
200
+ color: var(--color-text-tertiary);
201
+ margin-top: 0.5rem;
202
+ }
203
+
204
+ .pizza-tip {
205
+ background-color: rgba(249, 115, 22, 0.05);
206
+ border-radius: 1.5rem;
207
+ padding: 1.5rem;
208
+ border: 1px solid rgba(249, 115, 22, 0.1);
209
+ display: flex;
210
+ gap: 1rem;
211
+ }
212
+
213
+ .pizza-tip-icon-wrapper {
214
+ padding: 0.75rem;
215
+ background-color: rgba(249, 115, 22, 0.1);
216
+ border-radius: 0.75rem;
217
+ height: fit-content;
218
+ flex-shrink: 0;
219
+ }
220
+
221
+ .pizza-tip-icon {
222
+ width: 1.5rem;
223
+ height: 1.5rem;
224
+ color: var(--color-orange-600);
225
+ }
226
+
227
+ .pizza-tip-title {
228
+ font-weight: 700;
229
+ color: var(--color-orange-900);
230
+ margin-bottom: 0.25rem;
231
+ }
232
+
233
+ .theme-dark .pizza-tip-title {
234
+ color: var(--color-orange-100);
235
+ }
236
+
237
+ .pizza-tip-text {
238
+ font-size: 0.875rem;
239
+ color: var(--color-orange-700);
240
+ line-height: 1.5;
241
+ }
242
+
243
+ .theme-dark .pizza-tip-text {
244
+ color: var(--color-amber-100);
245
+ }
246
+
247
+ .pizza-recipe {
248
+ }
249
+
250
+ @media (min-width: 1024px) {
251
+ .pizza-recipe {
252
+ grid-column: span 1;
253
+ grid-row: span 1;
254
+ }
255
+ }
256
+
257
+ .pizza-recipe-card {
258
+ background-color: var(--color-bg-dark);
259
+ color: var(--color-white);
260
+ border-radius: 1.5rem;
261
+ padding: 1.5rem;
262
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.2);
263
+ }
264
+
265
+ @media (min-width: 768px) {
266
+ .pizza-recipe-card {
267
+ padding: 3rem;
268
+ }
269
+ }
270
+
271
+ .pizza-recipe-header {
272
+ display: flex;
273
+ flex-direction: column;
274
+ gap: 1rem;
275
+ margin-bottom: 3rem;
276
+ }
277
+
278
+ @media (min-width: 768px) {
279
+ .pizza-recipe-header {
280
+ flex-direction: row;
281
+ align-items: center;
282
+ justify-content: space-between;
283
+ gap: 1rem;
284
+ }
285
+ }
286
+
287
+ .pizza-recipe-title {
288
+ font-size: 1.875rem;
289
+ font-weight: 700;
290
+ }
291
+
292
+ .pizza-yeast-buttons {
293
+ display: flex;
294
+ gap: 0.5rem;
295
+ background-color: rgba(255, 255, 255, 0.1);
296
+ padding: 0.25rem;
297
+ border-radius: 0.75rem;
298
+ width: 100%;
299
+ }
300
+
301
+ @media (min-width: 768px) {
302
+ .pizza-yeast-buttons {
303
+ width: auto;
304
+ }
305
+ }
306
+
307
+ .pizza-yeast-btn {
308
+ flex: 1;
309
+ padding: 0.5rem 1rem;
310
+ border-radius: 0.5rem;
311
+ background-color: transparent;
312
+ color: var(--color-text-tertiary);
313
+ border: none;
314
+ font-size: 0.875rem;
315
+ font-weight: 500;
316
+ cursor: pointer;
317
+ transition: all 0.2s;
318
+ text-align: center;
319
+ }
320
+
321
+ .pizza-yeast-btn:hover {
322
+ background-color: rgba(255, 255, 255, 0.1);
323
+ }
324
+
325
+ @media (min-width: 768px) {
326
+ .pizza-yeast-btn {
327
+ flex: none;
328
+ }
329
+ }
330
+
331
+ .pizza-yeast-btn-active {
332
+ background-color: rgba(255, 255, 255, 0.1);
333
+ color: var(--color-white);
334
+ }
335
+
336
+ .pizza-recipe-grid {
337
+ display: grid;
338
+ grid-template-columns: 1fr;
339
+ gap: 3rem;
340
+ }
341
+
342
+ @media (min-width: 768px) {
343
+ .pizza-recipe-grid {
344
+ grid-template-columns: repeat(2, 1fr);
345
+ gap: 2rem;
346
+ }
347
+ }
348
+
349
+ .pizza-recipe-group {
350
+ display: flex;
351
+ flex-direction: column;
352
+ gap: 1rem;
353
+ }
354
+
355
+ .pizza-recipe-label {
356
+ display: flex;
357
+ align-items: center;
358
+ gap: 0.75rem;
359
+ font-size: 0.75rem;
360
+ font-weight: 700;
361
+ text-transform: uppercase;
362
+ letter-spacing: 0.05em;
363
+ transition: color 0.2s;
364
+ }
365
+
366
+ .pizza-recipe-label-white {
367
+ color: var(--color-slate-400);
368
+ }
369
+
370
+ .pizza-recipe-label-white:hover {
371
+ color: var(--color-white);
372
+ }
373
+
374
+ .pizza-recipe-label-blue {
375
+ color: var(--color-blue-400);
376
+ }
377
+
378
+ .pizza-recipe-label-blue:hover {
379
+ color: var(--color-blue-300);
380
+ }
381
+
382
+ .pizza-recipe-label-gray {
383
+ color: var(--color-slate-400);
384
+ }
385
+
386
+ .pizza-recipe-label-gray:hover {
387
+ color: var(--color-slate-200);
388
+ }
389
+
390
+ .pizza-recipe-label-orange {
391
+ color: var(--color-orange-400);
392
+ }
393
+
394
+ .pizza-recipe-label-orange:hover {
395
+ color: var(--color-orange-300);
396
+ }
397
+
398
+ .pizza-recipe-icon {
399
+ width: 1.25rem;
400
+ height: 1.25rem;
401
+ }
402
+
403
+ .pizza-recipe-value {
404
+ font-size: 3rem;
405
+ font-weight: 900;
406
+ letter-spacing: -0.05em;
407
+ font-variant-numeric: tabular-nums;
408
+ }
409
+
410
+ @media (min-width: 768px) {
411
+ .pizza-recipe-value {
412
+ font-size: 3.75rem;
413
+ }
414
+ }
415
+
416
+ .pizza-recipe-value-blue {
417
+ color: var(--color-blue-100);
418
+ }
419
+
420
+ .pizza-recipe-value-orange {
421
+ color: var(--color-orange-100);
422
+ }
423
+
424
+ .pizza-recipe-unit {
425
+ font-size: 1.125rem;
426
+ font-weight: 400;
427
+ color: var(--color-text-secondary);
428
+ margin-left: 0.25rem;
429
+ }
430
+
431
+ .pizza-recipe-bar {
432
+ height: 0.25rem;
433
+ width: 100%;
434
+ background-color: var(--color-slate-800);
435
+ border-radius: 0.25rem;
436
+ overflow: hidden;
437
+ }
438
+
439
+ .pizza-recipe-bar-fill {
440
+ height: 100%;
441
+ background-color: var(--color-white);
442
+ width: 100%;
443
+ transform-origin: left;
444
+ transition: transform 1s;
445
+ border-radius: 0.25rem;
446
+ }
447
+
448
+ .pizza-recipe-bar-fill-blue {
449
+ background-color: var(--color-blue-500);
450
+ }
451
+
452
+ .pizza-recipe-note {
453
+ font-size: 0.75rem;
454
+ color: var(--color-text-secondary);
455
+ margin-top: 0.5rem;
456
+ }
457
+
458
+ .pizza-recipe-total {
459
+ margin-top: 3rem;
460
+ padding-top: 2rem;
461
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
462
+ display: flex;
463
+ justify-content: space-between;
464
+ align-items: flex-end;
465
+ }
466
+
467
+ .pizza-recipe-total-label {
468
+ font-size: 0.875rem;
469
+ color: var(--color-slate-400);
470
+ }
471
+
472
+ .pizza-recipe-total-value {
473
+ font-size: 1.5rem;
474
+ font-weight: 700;
475
+ }
476
+
477
+ .pizza-visual {
478
+ }
479
+
480
+ @media (min-width: 1024px) {
481
+ .pizza-visual {
482
+ grid-column: span 1;
483
+ grid-row: span 1;
484
+ }
485
+ }
486
+
487
+ .pizza-visual-card {
488
+ background-color: var(--color-bg);
489
+ border-radius: 1.5rem;
490
+ padding: 1.5rem;
491
+ border: 1px solid var(--color-border);
492
+ display: flex;
493
+ flex-direction: column;
494
+ align-items: center;
495
+ justify-content: center;
496
+ min-height: 20rem;
497
+ }
498
+
499
+ .pizza-visual-container {
500
+ position: relative;
501
+ width: 100%;
502
+ max-width: 20rem;
503
+ height: 16rem;
504
+ display: flex;
505
+ align-items: center;
506
+ justify-content: center;
507
+ }
508
+
509
+ .pizza-visual-dough {
510
+ border-radius: 50%;
511
+ background-color: #f3e5ab;
512
+ box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1), 0 4px 6px rgba(0, 0, 0, 0.1);
513
+ transition: all 0.5s;
514
+ position: relative;
515
+ z-index: 10;
516
+ }
517
+
518
+ .pizza-visual-dough::after {
519
+ content: "";
520
+ position: absolute;
521
+ inset: 0;
522
+ border-radius: 50%;
523
+ background: linear-gradient(
524
+ 135deg,
525
+ rgba(255, 255, 255, 0.4) 0%,
526
+ rgba(0, 0, 0, 0.05) 100%
527
+ );
528
+ }
529
+
530
+ .pizza-visual-reference {
531
+ position: absolute;
532
+ bottom: 0;
533
+ right: 1rem;
534
+ display: flex;
535
+ flex-direction: column;
536
+ align-items: center;
537
+ opacity: 0.3;
538
+ pointer-events: none;
539
+ z-index: 0;
540
+ }
541
+
542
+ .pizza-visual-reference-icon {
543
+ width: 6rem;
544
+ height: 6rem;
545
+ color: var(--color-slate-400);
546
+ }
547
+
548
+ .pizza-visual-reference-label {
549
+ font-size: 0.625rem;
550
+ text-transform: uppercase;
551
+ letter-spacing: 0.05em;
552
+ font-weight: 700;
553
+ color: var(--color-slate-400);
554
+ }
555
+
556
+ .pizza-visual-diameter {
557
+ position: absolute;
558
+ bottom: -2rem;
559
+ font-size: 0.75rem;
560
+ color: var(--color-text-tertiary);
561
+ }
562
+
563
+ .pizza-visual-text {
564
+ margin-top: 2rem;
565
+ text-align: center;
566
+ font-size: 0.875rem;
567
+ color: var(--color-text-secondary);
568
+ max-width: 16rem;
569
+ }
@@ -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} />
@@ -0,0 +1,143 @@
1
+ interface DOMElements {
2
+ inputPizzas: HTMLInputElement;
3
+ inputWeight: HTMLInputElement;
4
+ inputHydration: HTMLInputElement;
5
+ inputSalt: HTMLInputElement;
6
+ displayPizzas: HTMLElement | null;
7
+ displayWeight: HTMLElement | null;
8
+ displayHydration: HTMLElement | null;
9
+ displaySalt: HTMLElement | null;
10
+ resultFlour: HTMLElement | null;
11
+ resultWater: HTMLElement | null;
12
+ resultSalt: HTMLElement | null;
13
+ resultYeast: HTMLElement | null;
14
+ resultTotal: HTMLElement | null;
15
+ barWater: HTMLElement | null;
16
+ doughVisual: HTMLElement;
17
+ visualDiameter: HTMLElement | null;
18
+ btnYeastFresh: HTMLElement | null;
19
+ btnYeastDry: HTMLElement | null;
20
+ }
21
+
22
+ interface CalculatorState {
23
+ useFreshYeast: boolean;
24
+ dom: DOMElements;
25
+ }
26
+
27
+ function getInputValues(dom: DOMElements) {
28
+ return {
29
+ pizzas: parseInt(dom.inputPizzas.value),
30
+ weight: parseInt(dom.inputWeight.value),
31
+ hydration: parseInt(dom.inputHydration.value) / 100,
32
+ salt: parseFloat(dom.inputSalt.value) / 100,
33
+ };
34
+ }
35
+
36
+ function calculateValues(inputs: ReturnType<typeof getInputValues>, useFreshYeast: boolean) {
37
+ const totalDoughWeight = inputs.pizzas * inputs.weight;
38
+ const flour = totalDoughWeight / (1 + inputs.hydration + inputs.salt);
39
+ return {
40
+ flour,
41
+ water: flour * inputs.hydration,
42
+ saltGrams: flour * inputs.salt,
43
+ yeast: useFreshYeast ? flour * 0.003 : flour * 0.001,
44
+ totalDough: totalDoughWeight,
45
+ };
46
+ }
47
+
48
+ function updateDisplay(state: CalculatorState) {
49
+ const inputs = getInputValues(state.dom);
50
+ const values = calculateValues(inputs, state.useFreshYeast);
51
+ const totalWeight = values.flour + values.water + values.saltGrams + values.yeast;
52
+
53
+ if (state.dom.resultFlour) state.dom.resultFlour.textContent = Math.round(values.flour).toString();
54
+ if (state.dom.resultWater) state.dom.resultWater.textContent = Math.round(values.water).toString();
55
+ if (state.dom.resultSalt) state.dom.resultSalt.textContent = (Math.round(values.saltGrams * 10) / 10).toString();
56
+ if (state.dom.resultYeast) state.dom.resultYeast.textContent = (Math.round(values.yeast * 10) / 10).toString();
57
+ if (state.dom.resultTotal) state.dom.resultTotal.textContent = Math.round(totalWeight).toString();
58
+
59
+ if (state.dom.barWater) {
60
+ const waterPercentage = (values.water / values.totalDough) * 100;
61
+ state.dom.barWater.style.width = waterPercentage + "%";
62
+ }
63
+
64
+ const diameter = Math.round(Math.sqrt((inputs.weight * inputs.pizzas) / 0.06));
65
+ if (state.dom.visualDiameter) state.dom.visualDiameter.textContent = diameter.toString();
66
+
67
+ const size = Math.max(100, Math.min(300, diameter * 2));
68
+ state.dom.doughVisual.style.width = size + "px";
69
+ state.dom.doughVisual.style.height = size + "px";
70
+ }
71
+
72
+ function setupInputListeners(state: CalculatorState) {
73
+ const { inputPizzas, inputWeight, inputHydration, inputSalt, displayPizzas, displayWeight, displayHydration, displaySalt } = state.dom;
74
+
75
+ function updateDisplays() {
76
+ if (displayPizzas) displayPizzas.textContent = inputPizzas.value;
77
+ if (displayWeight) displayWeight.textContent = inputWeight.value;
78
+ if (displayHydration) displayHydration.textContent = inputHydration.value;
79
+ if (displaySalt) displaySalt.textContent = parseFloat(inputSalt.value).toFixed(1);
80
+ updateDisplay(state);
81
+ }
82
+
83
+ inputPizzas.addEventListener("input", updateDisplays);
84
+ inputWeight.addEventListener("input", updateDisplays);
85
+ inputHydration.addEventListener("input", updateDisplays);
86
+ inputSalt.addEventListener("input", updateDisplays);
87
+ }
88
+
89
+ function setupYeastListeners(state: CalculatorState) {
90
+ const { btnYeastFresh, btnYeastDry } = state.dom;
91
+
92
+ if (btnYeastFresh) {
93
+ btnYeastFresh.addEventListener("click", () => {
94
+ state.useFreshYeast = true;
95
+ btnYeastFresh?.classList.add("pizza-yeast-btn-active");
96
+ btnYeastDry?.classList.remove("pizza-yeast-btn-active");
97
+ updateDisplay(state);
98
+ });
99
+ }
100
+
101
+ if (btnYeastDry) {
102
+ btnYeastDry.addEventListener("click", () => {
103
+ state.useFreshYeast = false;
104
+ btnYeastDry?.classList.add("pizza-yeast-btn-active");
105
+ btnYeastFresh?.classList.remove("pizza-yeast-btn-active");
106
+ updateDisplay(state);
107
+ });
108
+ }
109
+ }
110
+
111
+ function getDOMElements(): DOMElements {
112
+ return {
113
+ inputPizzas: document.getElementById("input-pizzas") as HTMLInputElement,
114
+ inputWeight: document.getElementById("input-weight") as HTMLInputElement,
115
+ inputHydration: document.getElementById("input-hydration") as HTMLInputElement,
116
+ inputSalt: document.getElementById("input-salt") as HTMLInputElement,
117
+ displayPizzas: document.getElementById("display-pizzas"),
118
+ displayWeight: document.getElementById("display-weight"),
119
+ displayHydration: document.getElementById("display-hydration"),
120
+ displaySalt: document.getElementById("display-salt"),
121
+ resultFlour: document.getElementById("result-flour"),
122
+ resultWater: document.getElementById("result-water"),
123
+ resultSalt: document.getElementById("result-salt"),
124
+ resultYeast: document.getElementById("result-yeast"),
125
+ resultTotal: document.getElementById("result-total"),
126
+ barWater: document.getElementById("bar-water"),
127
+ doughVisual: document.getElementById("dough-visual") as HTMLElement,
128
+ visualDiameter: document.getElementById("visual-diameter"),
129
+ btnYeastFresh: document.getElementById("btn-yeast-fresh"),
130
+ btnYeastDry: document.getElementById("btn-yeast-dry"),
131
+ };
132
+ }
133
+
134
+ export function initPizzaCalculator() {
135
+ const state: CalculatorState = {
136
+ useFreshYeast: true,
137
+ dom: getDOMElements(),
138
+ };
139
+
140
+ setupInputListeners(state);
141
+ setupYeastListeners(state);
142
+ updateDisplay(state);
143
+ }