@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,154 @@
1
+ import type { ToolLocaleContent } from '../../../types';
2
+
3
+ export const content: ToolLocaleContent = {
4
+ slug: 'kitchen-timer',
5
+ title: 'Multiple Kitchen Timer',
6
+ description:
7
+ 'Manage multiple cooking times simultaneously. Independent alarms, ideal for chefs and kitchen organization (Mise en Place).',
8
+ faqTitle: 'Frequently Asked Questions',
9
+ bibliographyTitle: 'References',
10
+
11
+ ui: {
12
+ addTimer: 'Add Timer',
13
+ stopAll: 'Stop All',
14
+ defaultName: 'Timer',
15
+ newTimerName: 'New Timer',
16
+ timerDefault1: 'Timer 1',
17
+ timerDefault2: 'Timer 2',
18
+ timerDefault3: 'Timer 3',
19
+ label: 'Label',
20
+ hours: 'Hours',
21
+ minutes: 'Min',
22
+ seconds: 'Sec',
23
+ ready: 'Ready',
24
+ start: 'Start',
25
+ pause: 'Pause',
26
+ reset: 'Reset',
27
+ addOneMin: '+1 min',
28
+ addFiveMin: '+5 min',
29
+ status: {
30
+ ready: 'Ready',
31
+ running: 'Running',
32
+ paused: 'Paused',
33
+ finished: 'TIME!',
34
+ },
35
+ },
36
+
37
+ faq: [
38
+ {
39
+ question: 'How many timers can I create?',
40
+ answer:
41
+ 'Unlimited. Add as many as you need with the \'+\' button. Perfect for cooking multiple dishes simultaneously: pasta boiling, sauce reducing, meat resting, and oven baking, all at once.',
42
+ },
43
+ {
44
+ question: 'Why is resting time important for meat?',
45
+ answer:
46
+ 'When you cut meat fresh from heat, juices escape onto the plate. If you rest 5-10 minutes, fibers relax and juices redistribute. Result: juicy meat instead of dry. Resting is passive cooking.',
47
+ },
48
+ {
49
+ question: 'Does it work with screen locked?',
50
+ answer:
51
+ 'Yes, but you need to grant notification permissions. Timers keep running in background and will alert you with sound and browser notification even if you switch tabs or lock your phone.',
52
+ },
53
+ {
54
+ question: 'What is the food \'Danger Zone\'?',
55
+ answer:
56
+ 'Between 5°C and 65°C bacteria multiply rapidly. Cooked foods shouldn\'t stay in this range more than 2 hours (1 hour if temperature exceeds 30°C). Use a timer to monitor cooling before refrigerating.',
57
+ },
58
+ ],
59
+
60
+ bibliography: [
61
+ {
62
+ name: 'Food Safety: USDA Guidelines',
63
+ url: 'https://www.fsis.usda.gov/',
64
+ },
65
+ {
66
+ name: 'Mise en Place - Professional Cooking',
67
+ url: 'https://www.escoffier.edu/',
68
+ },
69
+ ],
70
+
71
+ howTo: [
72
+ {
73
+ name: 'Create multiple timers',
74
+ text: 'Use the \'+\' button to add as many timers as you need. Perfect for orchestrating multiple dishes simultaneously.',
75
+ },
76
+ {
77
+ name: 'Customize each timer',
78
+ text: 'Change the name of each timer to identify what\'s cooking: \'Oven\', \'Rice\', \'Sauce\', etc.',
79
+ },
80
+ {
81
+ name: 'Control from mobile dock',
82
+ text: 'On mobile, active timers appear in a sliding dock at the bottom. Pause or restart directly from there.',
83
+ },
84
+ {
85
+ name: 'Receive notifications',
86
+ text: 'Authorize notifications so the browser alerts you when time\'s up, even if you switch tabs.',
87
+ },
88
+ ],
89
+
90
+ seo: [
91
+ {
92
+ type: 'title',
93
+ text: 'Temporal Mastery in the Kitchen',
94
+ level: 2,
95
+ },
96
+ {
97
+ type: 'paragraph',
98
+ html: 'Professional cooking isn\'t based solely on recipes, but on <strong>precise time management</strong>. The French concept <em>"Mise en Place"</em> (everything in its place) includes time as an ingredient. A steak that rests 5 minutes redistributes its juices and becomes tender; one that doesn\'t rest loses them at the first cut.',
99
+ },
100
+ {
101
+ type: 'paragraph',
102
+ html: 'This <strong>Multiple Timer</strong> tool has been designed for home chefs and professionals who need to orchestrate a symphony of dishes: pasta boiling, roast in the oven, and sauce reducing, all happening simultaneously.',
103
+ },
104
+ {
105
+ type: 'title',
106
+ text: 'The Role of Temperature and Time',
107
+ level: 3,
108
+ },
109
+ {
110
+ type: 'paragraph',
111
+ html: 'Cooking is essentially applying heat for a specific duration to transform the chemical structure of food. Protein denaturation and the Maillard reaction (which gives golden color and flavor) critically depend on the timer.',
112
+ },
113
+ {
114
+ type: 'table',
115
+ headers: ['Food', 'Cooking Point', 'Time', 'Benefit'],
116
+ rows: [
117
+ ['Soft-boiled Eggs', 'Liquid yolk', '3-4 min', 'Soft protein, easy digestion'],
118
+ ['Mollet Eggs', 'Dense yolk', '5-6 min', 'Perfect texture balance'],
119
+ ['Hard-boiled Eggs', 'Fully set', '9-11 min', 'Maximum firmness'],
120
+ ['Meat - Searing', 'High heat', '1-2 min/side', 'Maillard reaction, juiciness'],
121
+ ['Meat - Fine Rest', 'Resting', '5 min', 'Juice redistribution'],
122
+ ['Meat - Large Rest', 'Resting', '15-20 min', 'Relaxed fibers'],
123
+ ],
124
+ },
125
+ {
126
+ type: 'title',
127
+ text: 'Food Safety: The Danger Zone',
128
+ level: 3,
129
+ },
130
+ {
131
+ type: 'paragraph',
132
+ html: 'Time doesn\'t just affect quality, it affects safety. The bacterial "Danger Zone" sits between 5°C and 65°C. Cooked foods must not remain in this range for more than <strong>2 hours</strong> (or 1 hour if ambient temperature exceeds 30°C). Use a timer to monitor cooling before storing food in the fridge.',
133
+ },
134
+ {
135
+ type: 'title',
136
+ text: 'Professional Organization Tips',
137
+ level: 3,
138
+ },
139
+ {
140
+ type: 'list',
141
+ items: [
142
+ '<strong>Stagger finishing times:</strong> If everything finishes at once, you\'ll feel overwhelmed plating. Try to have side dishes ready 5 minutes before the main course.',
143
+ '<strong>Use residual heat:</strong> Turn off vegetables or pasta 1-2 minutes before the timer sounds. Residual heat will finish cooking gently.',
144
+ '<strong>Name your timers:</strong> In a busy kitchen it\'s easy to forget which alarm is which. Use this tool\'s renaming feature to label \'Oven\', \'Rice\', or \'Sauce\'.',
145
+ ],
146
+ },
147
+ {
148
+ type: 'tip',
149
+ html: '<strong>Professional tip:</strong> Time precision is what separates a chef from a home cook. Invest in a good timer and use it always. Experience tells you when something "looks ready", but time guarantees consistency.',
150
+ },
151
+ ],
152
+
153
+ schemas: [],
154
+ };
@@ -0,0 +1,154 @@
1
+ import type { ToolLocaleContent } from '../../../types';
2
+
3
+ export const content: ToolLocaleContent = {
4
+ slug: 'temporizador-cocina',
5
+ title: 'Temporizador de Cocina Múltiple',
6
+ description:
7
+ 'Gestiona múltiples tiempos de cocción simultáneamente. Alarmas independientes, ideal para chefs y organización en la cocina (Mise en Place).',
8
+ faqTitle: 'Preguntas Frecuentes',
9
+ bibliographyTitle: 'Referencias',
10
+
11
+ ui: {
12
+ addTimer: 'Añadir Temporizador',
13
+ stopAll: 'Detener Todas',
14
+ defaultName: 'Temporizador',
15
+ newTimerName: 'Nuevo Temporizador',
16
+ timerDefault1: 'Temporizador 1',
17
+ timerDefault2: 'Temporizador 2',
18
+ timerDefault3: 'Temporizador 3',
19
+ label: 'Etiqueta',
20
+ hours: 'Horas',
21
+ minutes: 'Min',
22
+ seconds: 'Seg',
23
+ ready: 'Listo',
24
+ start: 'Iniciar',
25
+ pause: 'Pausar',
26
+ reset: 'Reset',
27
+ addOneMin: '+1 min',
28
+ addFiveMin: '+5 min',
29
+ status: {
30
+ ready: 'Listo',
31
+ running: 'Corriendo',
32
+ paused: 'Pausado',
33
+ finished: '¡TIEMPO!',
34
+ },
35
+ },
36
+
37
+ faq: [
38
+ {
39
+ question: '¿Cuántos temporizadores puedo crear?',
40
+ answer:
41
+ 'Ilimitados. Añade tantos como necesites con el botón \'+\'. Ideal para cocinar varios platos simultáneamente: pasta hirviendo, salsa reduciéndose, carne reposando, y horno funcionando, todo a la vez.',
42
+ },
43
+ {
44
+ question: '¿Por qué es importante el tiempo de reposo en carnes?',
45
+ answer:
46
+ 'Cuando cortas carne recién salida del fuego, los jugos escapan al plato. Si reposas 5-10 minutos, las fibras se relajan y los jugos se redistribuyen. Resultado: carne jugosa en lugar de seca. El reposo es cocción pasiva.',
47
+ },
48
+ {
49
+ question: '¿Funciona con la pantalla bloqueada?',
50
+ answer:
51
+ 'Sí, pero necesitas dar permisos de notificaciones. Los temporizadores siguen corriendo en segundo plano y te avisarán con sonido y notificación del navegador incluso si cambias de pestaña o bloqueas el móvil.',
52
+ },
53
+ {
54
+ question: '¿Qué es la \'Zona de Peligro\' alimentaria?',
55
+ answer:
56
+ 'Entre 5°C y 65°C las bacterias se multiplican rápidamente. Los alimentos cocinados no deben estar en esta zona más de 2 horas (1 hora si hace >30°C). Usa un temporizador para controlar el enfriamiento antes de refrigerar.',
57
+ },
58
+ ],
59
+
60
+ bibliography: [
61
+ {
62
+ name: 'Seguridad Alimentaria: USDA Guidelines',
63
+ url: 'https://www.fsis.usda.gov/',
64
+ },
65
+ {
66
+ name: 'Mise en Place - La Cocina Profesional',
67
+ url: 'https://www.escoffier.edu/',
68
+ },
69
+ ],
70
+
71
+ howTo: [
72
+ {
73
+ name: 'Crea múltiples temporizadores',
74
+ text: 'Usa el botón \'+\' para añadir tantos temporizadores como necesites. Ideal para orquestar varios platos simultáneamente.',
75
+ },
76
+ {
77
+ name: 'Personaliza cada temporizador',
78
+ text: 'Cambia el nombre de cada temporizador para identificar qué está cocinando: \'Horno\', \'Arroz\', \'Salsa\', etc.',
79
+ },
80
+ {
81
+ name: 'Controla desde el dock móvil',
82
+ text: 'En móviles, los temporizadores activos aparecen en un dock deslizable en la parte inferior. Pausa o reinicia directamente desde ahí.',
83
+ },
84
+ {
85
+ name: 'Recibe notificaciones',
86
+ text: 'Autoriza notificaciones para que el navegador te avise cuando se acabe el tiempo, incluso si cambias de pestaña.',
87
+ },
88
+ ],
89
+
90
+ seo: [
91
+ {
92
+ type: 'title',
93
+ text: 'Maestría Temporal en la Cocina',
94
+ level: 2,
95
+ },
96
+ {
97
+ type: 'paragraph',
98
+ html: 'La cocina profesional no se basa solo en recetas, sino en la <strong>gestión precisa del tiempo</strong>. El concepto francés <em>"Mise en Place"</em> (todo en su lugar) incluye el tiempo como un ingrediente más. Un filete que reposa 5 minutos redistribuye sus jugos y se vuelve tierno; uno que no reposa, los pierde al primer corte.',
99
+ },
100
+ {
101
+ type: 'paragraph',
102
+ html: 'Esta herramienta de <strong>Temporizador Múltiple</strong> ha sido diseñada para chefs caseros y profesionales que necesitan orquestar una sinfonía de platos: la pasta hirviendo, el asado en el horno, y la reducción de salsa, todo ocurriendo simultáneamente.',
103
+ },
104
+ {
105
+ type: 'title',
106
+ text: 'El Papel de la Temperatura y el Tiempo',
107
+ level: 3,
108
+ },
109
+ {
110
+ type: 'paragraph',
111
+ html: 'Cocinar es esencialmente aplicar calor durante un tiempo determinado para transformar la estructura química de los alimentos. La desnaturalización de las proteínas y la reacción de Maillard (que da el color dorado y sabor) dependen críticamente del cronómetro.',
112
+ },
113
+ {
114
+ type: 'table',
115
+ headers: ['Alimento', 'Punto de Cocción', 'Tiempo', 'Beneficio'],
116
+ rows: [
117
+ ['Huevos Pasados por agua', 'Yema líquida', '3-4 min', 'Proteína suave, fácil digestión'],
118
+ ['Huevos Mollet', 'Yema densa', '5-6 min', 'Balance perfecto entre textura'],
119
+ ['Huevos Duros', 'Cuajado total', '9-11 min', 'Máxima consistencia'],
120
+ ['Carnes - Sellado', 'Alta temperatura', '1-2 min/lado', 'Reacción Maillard, jugosidad'],
121
+ ['Carnes - Reposo fino', 'Reposo', '5 min', 'Redistribución de jugos'],
122
+ ['Carnes - Reposo grande', 'Reposo', '15-20 min', 'Fibras relajadas'],
123
+ ],
124
+ },
125
+ {
126
+ type: 'title',
127
+ text: 'Seguridad Alimentaria: La Zona de Peligro',
128
+ level: 3,
129
+ },
130
+ {
131
+ type: 'paragraph',
132
+ html: 'El tiempo no solo afecta la calidad, sino la seguridad. La "Zona de Peligro" bacteriana se sitúa entre los 5°C y los 65°C. Los alimentos cocinados no deben permanecer en este rango por más de <strong>2 horas</strong> (o 1 hora si la temperatura ambiente supera los 30°C). Usa un temporizador para controlar el enfriamiento antes de guardar la comida en la nevera.',
133
+ },
134
+ {
135
+ type: 'title',
136
+ text: 'Consejos Profesionales de Organización',
137
+ level: 3,
138
+ },
139
+ {
140
+ type: 'list',
141
+ items: [
142
+ '<strong>Escalona los tiempos de finalización:</strong> Si todo termina a la vez, te agobiarás emplatando. Intenta que los acompañamientos estén listos 5 minutos antes que el plato principal.',
143
+ '<strong>Usa el calor residual:</strong> Apaga el fuego de las verduras o pastas 1-2 minutos antes de que el temporizador suene. El calor residual terminará la cocción suavemente.',
144
+ '<strong>Nombra tus temporizadores:</strong> En una cocina ajetreada es fácil olvidar qué alarma es cual. Usa la función de renombrado de esta herramienta para etiquetar \'Horno\', \'Arroz\' o \'Salsa\'.',
145
+ ],
146
+ },
147
+ {
148
+ type: 'tip',
149
+ html: '<strong>Consejo profesional:</strong> La precisión del tiempo es lo que diferencia a un chef de un cocinero casero. Invierte en un buen temporizador y úsalo siempre. La experiencia te dice cuándo algo "se ve listo", pero el tiempo te garantiza consistencia.',
150
+ },
151
+ ],
152
+
153
+ schemas: [],
154
+ };
@@ -0,0 +1,154 @@
1
+ import type { ToolLocaleContent } from '../../../types';
2
+
3
+ export const content: ToolLocaleContent = {
4
+ slug: 'kitchen-timer',
5
+ title: 'Multiple Kitchen Timer',
6
+ description:
7
+ 'Manage multiple cooking times simultaneously. Independent alarms, ideal for chefs and kitchen organization (Mise en Place).',
8
+ faqTitle: 'Questions Fréquemment Posées',
9
+ bibliographyTitle: 'Références',
10
+
11
+ ui: {
12
+ addTimer: 'Add Timer',
13
+ stopAll: 'Stop All',
14
+ defaultName: 'Timer',
15
+ newTimerName: 'New Timer',
16
+ timerDefault1: 'Timer 1',
17
+ timerDefault2: 'Timer 2',
18
+ timerDefault3: 'Timer 3',
19
+ label: 'Label',
20
+ hours: 'Hours',
21
+ minutes: 'Min',
22
+ seconds: 'Sec',
23
+ ready: 'Ready',
24
+ start: 'Start',
25
+ pause: 'Pause',
26
+ reset: 'Reset',
27
+ addOneMin: '+1 min',
28
+ addFiveMin: '+5 min',
29
+ status: {
30
+ ready: 'Ready',
31
+ running: 'Running',
32
+ paused: 'Paused',
33
+ finished: 'TIME!',
34
+ },
35
+ },
36
+
37
+ faq: [
38
+ {
39
+ question: 'How many timers can I create?',
40
+ answer:
41
+ 'Unlimited. Add as many as you need with the \'+\' button. Perfect for cooking multiple dishes simultaneously: pasta boiling, sauce reducing, meat resting, and oven baking, all at once.',
42
+ },
43
+ {
44
+ question: 'Why is resting time important for meat?',
45
+ answer:
46
+ 'When you cut meat fresh from heat, juices escape onto the plate. If you rest 5-10 minutes, fibers relax and juices redistribute. Result: juicy meat instead of dry. Resting is passive cooking.',
47
+ },
48
+ {
49
+ question: 'Does it work with screen locked?',
50
+ answer:
51
+ 'Yes, but you need to grant notification permissions. Timers keep running in background and will alert you with sound and browser notification even if you switch tabs or lock your phone.',
52
+ },
53
+ {
54
+ question: 'What is the food \'Danger Zone\'?',
55
+ answer:
56
+ 'Between 5°C and 65°C bacteria multiply rapidly. Cooked foods shouldn\'t stay in this range more than 2 hours (1 hour if temperature exceeds 30°C). Use a timer to monitor cooling before refrigerating.',
57
+ },
58
+ ],
59
+
60
+ bibliography: [
61
+ {
62
+ name: 'Food Safety: USDA Guidelines',
63
+ url: 'https://www.fsis.usda.gov/',
64
+ },
65
+ {
66
+ name: 'Mise en Place - Professional Cooking',
67
+ url: 'https://www.escoffier.edu/',
68
+ },
69
+ ],
70
+
71
+ howTo: [
72
+ {
73
+ name: 'Create multiple timers',
74
+ text: 'Use the \'+\' button to add as many timers as you need. Perfect for orchestrating multiple dishes simultaneously.',
75
+ },
76
+ {
77
+ name: 'Customize each timer',
78
+ text: 'Change the name of each timer to identify what\'s cooking: \'Oven\', \'Rice\', \'Sauce\', etc.',
79
+ },
80
+ {
81
+ name: 'Control from mobile dock',
82
+ text: 'On mobile, active timers appear in a sliding dock at the bottom. Pause or restart directly from there.',
83
+ },
84
+ {
85
+ name: 'Receive notifications',
86
+ text: 'Authorize notifications so the browser alerts you when time\'s up, even if you switch tabs.',
87
+ },
88
+ ],
89
+
90
+ seo: [
91
+ {
92
+ type: 'title',
93
+ text: 'Temporal Mastery in the Kitchen',
94
+ level: 2,
95
+ },
96
+ {
97
+ type: 'paragraph',
98
+ html: 'Professional cooking isn\'t based solely on recipes, but on <strong>precise time management</strong>. The French concept <em>"Mise en Place"</em> (everything in its place) includes time as an ingredient. A steak that rests 5 minutes redistributes its juices and becomes tender; one that doesn\'t rest loses them at the first cut.',
99
+ },
100
+ {
101
+ type: 'paragraph',
102
+ html: 'This <strong>Multiple Timer</strong> tool has been designed for home chefs and professionals who need to orchestrate a symphony of dishes: pasta boiling, roast in the oven, and sauce reducing, all happening simultaneously.',
103
+ },
104
+ {
105
+ type: 'title',
106
+ text: 'The Role of Temperature and Time',
107
+ level: 3,
108
+ },
109
+ {
110
+ type: 'paragraph',
111
+ html: 'Cooking is essentially applying heat for a specific duration to transform the chemical structure of food. Protein denaturation and the Maillard reaction (which gives golden color and flavor) critically depend on the timer.',
112
+ },
113
+ {
114
+ type: 'table',
115
+ headers: ['Food', 'Cooking Point', 'Time', 'Benefit'],
116
+ rows: [
117
+ ['Soft-boiled Eggs', 'Liquid yolk', '3-4 min', 'Soft protein, easy digestion'],
118
+ ['Mollet Eggs', 'Dense yolk', '5-6 min', 'Perfect texture balance'],
119
+ ['Hard-boiled Eggs', 'Fully set', '9-11 min', 'Maximum firmness'],
120
+ ['Meat - Searing', 'High heat', '1-2 min/side', 'Maillard reaction, juiciness'],
121
+ ['Meat - Fine Rest', 'Resting', '5 min', 'Juice redistribution'],
122
+ ['Meat - Large Rest', 'Resting', '15-20 min', 'Relaxed fibers'],
123
+ ],
124
+ },
125
+ {
126
+ type: 'title',
127
+ text: 'Food Safety: The Danger Zone',
128
+ level: 3,
129
+ },
130
+ {
131
+ type: 'paragraph',
132
+ html: 'Time doesn\'t just affect quality, it affects safety. The bacterial "Danger Zone" sits between 5°C and 65°C. Cooked foods must not remain in this range for more than <strong>2 hours</strong> (or 1 hour if ambient temperature exceeds 30°C). Use a timer to monitor cooling before storing food in the fridge.',
133
+ },
134
+ {
135
+ type: 'title',
136
+ text: 'Professional Organization Tips',
137
+ level: 3,
138
+ },
139
+ {
140
+ type: 'list',
141
+ items: [
142
+ '<strong>Stagger finishing times:</strong> If everything finishes at once, you\'ll feel overwhelmed plating. Try to have side dishes ready 5 minutes before the main course.',
143
+ '<strong>Use residual heat:</strong> Turn off vegetables or pasta 1-2 minutes before the timer sounds. Residual heat will finish cooking gently.',
144
+ '<strong>Name your timers:</strong> In a busy kitchen it\'s easy to forget which alarm is which. Use this tool\'s renaming feature to label \'Oven\', \'Rice\', or \'Sauce\'.',
145
+ ],
146
+ },
147
+ {
148
+ type: 'tip',
149
+ html: '<strong>Professional tip:</strong> Time precision is what separates a chef from a home cook. Invest in a good timer and use it always. Experience tells you when something "looks ready", but time guarantees consistency.',
150
+ },
151
+ ],
152
+
153
+ schemas: [],
154
+ };
@@ -0,0 +1,26 @@
1
+ import type { CookingToolEntry, ToolDefinition } from '../../types';
2
+ import KitchenTimerComponent from './component.astro';
3
+ import KitchenTimerSEO from './seo.astro';
4
+ import KitchenTimerBibliography from './bibliography.astro';
5
+
6
+ export const kitchenTimer: CookingToolEntry = {
7
+ id: 'kitchen-timer',
8
+ icons: {
9
+ bg: 'mdi:clock-outline',
10
+ fg: 'mdi:fire',
11
+ },
12
+ i18n: {
13
+ es: () => import('./i18n/es').then((m) => m.content),
14
+ en: () => import('./i18n/en').then((m) => m.content),
15
+ fr: () => import('./i18n/fr').then((m) => m.content),
16
+ },
17
+ };
18
+
19
+ export { KitchenTimerComponent, KitchenTimerSEO, KitchenTimerBibliography };
20
+
21
+ export const KITCHEN_TIMER_TOOL: ToolDefinition = {
22
+ entry: kitchenTimer,
23
+ Component: KitchenTimerComponent,
24
+ SEOComponent: KitchenTimerSEO,
25
+ BibliographyComponent: KitchenTimerBibliography,
26
+ };
@@ -0,0 +1,55 @@
1
+ import { KitchenTimer } from "./lib/KitchenTimer";
2
+
3
+ function setupAddTimerButton(
4
+ timerTemplate: HTMLTemplateElement,
5
+ timersGrid: HTMLElement,
6
+ activeTimers: KitchenTimer[]
7
+ ) {
8
+ document.getElementById("add-timer-btn")?.addEventListener("click", () => {
9
+ if (!timerTemplate || !timersGrid) return;
10
+
11
+ const clone = timerTemplate.content.cloneNode(true) as DocumentFragment;
12
+ const newCard = clone.querySelector(".timer-card") as HTMLElement;
13
+
14
+ const count = activeTimers.length + 1;
15
+ newCard.setAttribute("data-index", count.toString());
16
+ const nameInput = newCard.querySelector(".timer-name") as HTMLInputElement;
17
+ if (nameInput) nameInput.value = `Temporizador ${count}`;
18
+
19
+ timersGrid.appendChild(newCard);
20
+
21
+ const timer = new KitchenTimer(newCard);
22
+ activeTimers.push(timer);
23
+
24
+ newCard.scrollIntoView({ behavior: "smooth", block: "center" });
25
+ });
26
+ }
27
+
28
+ function setupStopAllButton(activeTimers: KitchenTimer[]) {
29
+ document.getElementById("stop-all")?.addEventListener("click", () => {
30
+ activeTimers.forEach((t) => t.pause());
31
+ });
32
+ }
33
+
34
+ function requestNotificationPermission() {
35
+ if ("Notification" in window) {
36
+ if (Notification.permission !== "granted" && Notification.permission !== "denied") {
37
+ Notification.requestPermission();
38
+ }
39
+ }
40
+ }
41
+
42
+ export function initKitchenTimers() {
43
+ const timersGrid = document.getElementById("timers-grid") as HTMLElement;
44
+ const timerTemplate = document.getElementById("timer-template") as HTMLTemplateElement;
45
+ const activeTimers: KitchenTimer[] = [];
46
+
47
+ document.querySelectorAll(".timer-card").forEach((card) => {
48
+ const timer = new KitchenTimer(card as HTMLElement);
49
+ activeTimers.push(timer);
50
+ });
51
+
52
+ setupAddTimerButton(timerTemplate, timersGrid, activeTimers);
53
+ setupStopAllButton(activeTimers);
54
+ requestNotificationPermission();
55
+ }
@@ -0,0 +1,27 @@
1
+ export function getAudioContext(): AudioContext | null {
2
+ try {
3
+ const audioContextClass = window.AudioContext || (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext;
4
+ if (audioContextClass) {
5
+ return new audioContextClass();
6
+ }
7
+ } catch {
8
+ console.warn("AudioContext not available");
9
+ }
10
+ return null;
11
+ }
12
+
13
+ export function playBeep(audioContext: AudioContext, frequency: number = 800, duration: number = 0.1): void {
14
+ const now = audioContext.currentTime;
15
+ const osc = audioContext.createOscillator();
16
+ const gain = audioContext.createGain();
17
+
18
+ osc.connect(gain);
19
+ gain.connect(audioContext.destination);
20
+ osc.type = "sine";
21
+ osc.frequency.value = frequency;
22
+ gain.gain.setValueAtTime(0.3, now);
23
+ gain.gain.exponentialRampToValueAtTime(0.01, now + duration);
24
+
25
+ osc.start(now);
26
+ osc.stop(now + duration);
27
+ }
@@ -0,0 +1,97 @@
1
+ import type { KitchenTimer } from "./KitchenTimer";
2
+
3
+ export class DockManager {
4
+ private dockElement: HTMLElement | null;
5
+ private containerElement: HTMLElement | null;
6
+ private timers: KitchenTimer[] = [];
7
+
8
+ constructor(dockId: string, containerId: string) {
9
+ this.dockElement = document.getElementById(dockId);
10
+ this.containerElement = document.getElementById(containerId);
11
+ }
12
+
13
+ public registerTimer(timer: KitchenTimer) {
14
+ this.timers.push(timer);
15
+
16
+ timer.addEventListener("update", () => this.sync());
17
+ this.sync();
18
+ }
19
+
20
+ public sync() {
21
+ if (!this.containerElement || !this.dockElement) return;
22
+
23
+ const relevantTimers = this.timers.filter(
24
+ (t) => t.totalSeconds > 0 && t.remainingSeconds > 0
25
+ );
26
+
27
+ if (relevantTimers.length === 0) {
28
+ this.dockElement.setAttribute("data-visible", "false");
29
+ return;
30
+ }
31
+
32
+ this.dockElement.setAttribute("data-visible", "true");
33
+ this.containerElement.innerHTML = "";
34
+
35
+ relevantTimers.sort((a, b) => a.remainingSeconds - b.remainingSeconds);
36
+
37
+ relevantTimers.forEach((t) => {
38
+ const item = this.createDockItem(t);
39
+ this.containerElement?.appendChild(item);
40
+ });
41
+ }
42
+
43
+ private createDockItem(timer: KitchenTimer): HTMLElement {
44
+ const item = document.createElement("div");
45
+ const baseClasses =
46
+ "flex items-center gap-2 px-3 py-2 rounded-lg border min-w-[130px] max-w-[160px] flex-shrink-0 shadow-sm transition-colors";
47
+ const stateClasses = timer.isRunning
48
+ ? "border-orange-500 bg-orange-50/30 dark:border-orange-500 dark:bg-orange-900/20"
49
+ : "bg-white dark:bg-slate-800 border-slate-200 dark:border-slate-700";
50
+
51
+ item.className = `${baseClasses} ${stateClasses}`;
52
+ item.innerHTML = this.getDockItemHTML(timer);
53
+
54
+ this.attachDockItemListeners(item, timer);
55
+ return item;
56
+ }
57
+
58
+ private getDockItemHTML(timer: KitchenTimer): string {
59
+ const formattedTime = this.formatTimeMin(timer.remainingSeconds);
60
+ const timerName = timer.getName();
61
+ const buttonClass = timer.isRunning
62
+ ? "bg-slate-100 dark:bg-slate-700 text-slate-500 dark:text-slate-400 hover:text-slate-800 dark:hover:text-slate-200"
63
+ : "bg-orange-500 text-white hover:bg-orange-600";
64
+ const iconPath = timer.isRunning
65
+ ? '<path d="M14,19H18V5H14M6,19H10V5H6V19Z" />'
66
+ : '<path d="M8,5.14V19.14L19,12.14L8,5.14Z" />';
67
+
68
+ return `
69
+ <div class="flex-1 min-w-0 flex flex-col justify-center">
70
+ <div class="text-[10px] text-slate-500 dark:text-slate-400 truncate uppercase tracking-wide leading-tight font-bold">${timerName}</div>
71
+ <div class="text-base font-mono font-bold text-slate-900 dark:text-white leading-none mt-0.5 tabular-nums">${formattedTime}</div>
72
+ </div>
73
+ <button class="dock-toggle p-1.5 rounded-full flex-shrink-0 transition-colors ${buttonClass}">
74
+ <svg class="w-3.5 h-3.5 pointer-events-none" viewBox="0 0 24 24" fill="currentColor">${iconPath}</svg>
75
+ </button>
76
+ `;
77
+ }
78
+
79
+ private attachDockItemListeners(item: HTMLElement, timer: KitchenTimer) {
80
+ const btn = item.querySelector(".dock-toggle");
81
+ btn?.addEventListener("click", (e) => {
82
+ e.stopPropagation();
83
+ timer.toggle();
84
+ });
85
+
86
+ item.addEventListener("click", () => {
87
+ timer.element.scrollIntoView({ behavior: "smooth", block: "center" });
88
+ });
89
+ }
90
+
91
+ private formatTimeMin(seconds: number): string {
92
+ if (seconds < 60) return `${seconds}s`;
93
+ const m = Math.floor(seconds / 60);
94
+ const s = seconds % 60;
95
+ return `${m}:${s.toString().padStart(2, "0")}`;
96
+ }
97
+ }