@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,248 @@
1
+ import type { ToolLocaleContent } from "../../../types";
2
+
3
+ export const content: ToolLocaleContent = {
4
+ slug: "calculateur-rafraichissement-levain-proportions-ratio",
5
+ title: "Calculateur de Rafraîchissement du Levain",
6
+ description:
7
+ "Calculez automatiquement les proportions exactes de levain, farine et eau pour l'entretien et le rafraîchissement de votre culture. Ratios prédéfinis ou personnalisés.",
8
+ faqTitle: "Questions Fréquentes sur le Levain",
9
+ faq: [
10
+ {
11
+ question: "Qu'est-ce qu'un ratio 1:1:1 ?",
12
+ answer:
13
+ "C'est le ratio le plus courant pour entretenir le levain à température ambiante. Cela signifie 1 part de levain, 1 part de farine et 1 part d'eau. Idéal pour un rafraîchissement quotidien.",
14
+ },
15
+ {
16
+ question: "Quand utiliser un ratio 1:2:2 ?",
17
+ answer:
18
+ "Un ratio 1:2:2 est souvent utilisé pour réactiver ou réveiller un levain. Il offre une courbe de fermentation plus prévisible, idéale après un passage au réfrigérateur.",
19
+ },
20
+ {
21
+ question: "Pourquoi utiliser le ratio 1:5:5 ?",
22
+ answer:
23
+ "Le ratio 1:5:5 est le favori de nombreux boulangers. Il permet une fenêtre de fermentation de 8 à 12 heures avant que le levain n'atteigne son pic, équilibrant commodité et contrôle du temps.",
24
+ },
25
+ {
26
+ question: "Puis-je utiliser des ratios personnalisés ?",
27
+ answer:
28
+ "Absolument. Si vous avez un protocole spécifique, vous pouvez saisir vos propres ratios. Certains boulangers utilisent 1:10:10 pour une fermentation très lente, d'autres 1:1:0.8 pour un levain plus ferme.",
29
+ },
30
+ {
31
+ question: "Le calculateur arrondit-il les grammes ?",
32
+ answer:
33
+ "Oui. Il arrondit à l'entier le plus proche par praticité. Pour une précision maximale, utilisez une balance digitale ; toutefois, de petits écarts d'arrondi n'impactent pas significativement la fermentation.",
34
+ },
35
+ ],
36
+ bibliographyTitle: "Bibliographie et Ressources",
37
+ bibliography: [
38
+ {
39
+ name: "Tartine Bread - Chad Robertson",
40
+ url: "https://www.penguinrandomhouse.com/books/310016/tartine-bread-by-chad-robertson/",
41
+ },
42
+ {
43
+ name: "The Flavor Bible - Scaling and Ratios",
44
+ url: "https://www.flavorprints.com/",
45
+ },
46
+ {
47
+ name: "Full Proof Baking - Guides on Sourdough",
48
+ url: "https://www.fullproofbaking.com/",
49
+ },
50
+ ],
51
+ howTo: [
52
+ {
53
+ name: "Entrez la quantité totale",
54
+ text: "Définissez le poids total de levain dont vous avez besoin pour votre recette (ex: 300g pour un pain classique).",
55
+ },
56
+ {
57
+ name: "Sélectionnez le ratio",
58
+ text: "Choisissez parmi les ratios prédéfinis (Maintenir, Activer, Retarder, Fort, Sweet Spot) ou créez votre propre ratio personnalisé.",
59
+ },
60
+ {
61
+ name: "Obtenez vos mesures exactes",
62
+ text: "Le calculateur vous montre exactement combien de levain, de farine et d'eau mélanger. Mélangez et laissez fermenter selon votre planning.",
63
+ },
64
+ ],
65
+ seo: [
66
+ {
67
+ type: "title",
68
+ text: "Guide Maître pour l'Entretien et le Rafraîchissement du Levain",
69
+ level: 2,
70
+ },
71
+ {
72
+ type: "paragraph",
73
+ html: "Le <strong>levain</strong> n'est pas seulement un ingrédient ; c'est un écosystème vivant de levures sauvages et de bactéries lactiques (LAB). Un pain exceptionnel commence par la santé de cette culture, gérée par des ratios précis et un contrôle de la température.",
74
+ },
75
+ {
76
+ type: "stats",
77
+ columns: 4,
78
+ items: [
79
+ {
80
+ value: "24-26°C",
81
+ label: "Temp. d'Activité Optimale",
82
+ icon: "mdi:thermometer",
83
+ },
84
+ {
85
+ value: "3.8 - 4.2",
86
+ label: "pH Idéal de la Culture",
87
+ icon: "mdi:test-tube",
88
+ },
89
+ {
90
+ value: "100%",
91
+ label: "Hydratation Standard",
92
+ icon: "mdi:water-percent",
93
+ },
94
+ {
95
+ value: "x2 - x3",
96
+ label: "Croissance au Pic",
97
+ icon: "mdi:trending-up",
98
+ },
99
+ ],
100
+ },
101
+ {
102
+ type: "title",
103
+ text: "Comparaison des Types de Culture de Levain",
104
+ level: 3,
105
+ },
106
+ {
107
+ type: "comparative",
108
+ columns: 2,
109
+ items: [
110
+ {
111
+ title: "Levain Liquide (100% Hyd.)",
112
+ icon: "mdi:water",
113
+ description: "Le choix le plus courant en boulangerie artisanale moderne. Très facile à mélanger et à incorporer.",
114
+ points: [
115
+ "Vitesse de fermentation plus rapide",
116
+ "Profil aromatique plus lactique (doux)",
117
+ "Facile à mesurer et à rafraîchir",
118
+ "Idéal pour baguettes et pains de campagne",
119
+ ],
120
+ },
121
+ {
122
+ title: "Levain Dur (Pasta Madre)",
123
+ icon: "mdi:bread-slice-outline",
124
+ description: "Traditionnel en boulangerie italienne. Hydratation autour de 50%. Réputé pour sa force.",
125
+ highlight: true,
126
+ points: [
127
+ "Fermentation plus lente et stable",
128
+ "Profil aromatique plus acétique",
129
+ "Plus grande force pour pâtes enrichies",
130
+ "Idéal pour Panettone et Brioche",
131
+ ],
132
+ },
133
+ ],
134
+ },
135
+ {
136
+ type: "title",
137
+ text: "Comprendre les Ratios Mathématiques de Rafraîchissement",
138
+ level: 3,
139
+ },
140
+ {
141
+ type: "paragraph",
142
+ html: "Le ratio représente les parts d'inoculum (ancien levain) par rapport à la farine et à l'eau. Par exemple, un ratio 1:2:2 signifie 1 part de culture pour 2 parts de farine et 2 parts d'eau.",
143
+ },
144
+ {
145
+ type: "table",
146
+ headers: ["Ratio", "Usage Recommandé", "Temps Est. (24°C)", "Avantage Clé"],
147
+ rows: [
148
+ ["1:1:1", "Entretien quotidien", "4-6 heures", "Maintenance rapide"],
149
+ ["1:2:2", "Activation avant cuisson", "6-8 heures", "Équilibre d'acidité"],
150
+ ["1:5:5", "Usage boulanger standard", "8-12 heures", "Pic d'activité prévisible"],
151
+ ["1:10:10", "Retard longue durée", "16-24 heures", "Gestion d'horaire flexible"],
152
+ ],
153
+ },
154
+ {
155
+ type: "title",
156
+ text: "Sélection des Farines pour le Rafraîchissement",
157
+ level: 3,
158
+ },
159
+ {
160
+ type: "comparative",
161
+ columns: 2,
162
+ items: [
163
+ {
164
+ title: "Farine de Seigle Complète",
165
+ icon: "mdi:grain",
166
+ description: "Un super-aliment pour les levures sauvages. Contient plus de nutriments que le blé.",
167
+ points: [
168
+ "Activité fermentaire explosive",
169
+ "Apporte des arômes terreux profonds",
170
+ "Maintient mieux les niveaux d'acidité",
171
+ ],
172
+ },
173
+ {
174
+ title: "Farine de Blé Blanche",
175
+ icon: "mdi:shaker-outline",
176
+ description: "Idéal pour ceux qui cherchent un goût plus doux où le levain ne domine pas le profil du pain.",
177
+ points: [
178
+ "Goût plus neutre et polyvalent",
179
+ "Observation très claire des bulles",
180
+ "Moins enclin à la sur-fermentation",
181
+ ],
182
+ },
183
+ ],
184
+ },
185
+ {
186
+ type: "diagnostic",
187
+ variant: "warning",
188
+ title: "Signes d'un Levain Affamé",
189
+ html: "Si votre culture présente un liquide sombre en surface (hooch), sent intensément le vinaigre ou l'acétone, ou s'affaisse très vite après son pic, elle a besoin d'un ratio de rafraîchissement plus élevé ou d'une température de stockage plus basse.",
190
+ },
191
+ {
192
+ type: "title",
193
+ text: "Glossaire Technique du Levain",
194
+ level: 3,
195
+ },
196
+ {
197
+ type: "glossary",
198
+ items: [
199
+ {
200
+ term: "Levain de tout-point",
201
+ definition: "Le levain final préparé à partir de la culture mère pour être utilisé directement dans une recette de pain.",
202
+ },
203
+ {
204
+ term: "Hooch",
205
+ definition: "Couche d'alcool et d'eau qui s'accumule lorsque le levain a épuisé sa source de nourriture. Pas nocif mais indique la faim.",
206
+ },
207
+ {
208
+ term: "Pic d'Activité",
209
+ definition: "Le moment d'expansion maximale et de densité de population de levures la plus élevée. Idéal pour pétrir.",
210
+ },
211
+ {
212
+ term: "Bactéries Lactiques (LAB)",
213
+ definition: "Micro-organismes responsables de la production d'acides organiques qui donnent le goût caractéristique et améliorent la conservation.",
214
+ },
215
+ ],
216
+ },
217
+ {
218
+ type: "tip",
219
+ title: "Le Test du Flottage",
220
+ html: "Pour vérifier si votre levain est prêt, déposez une cuillère à café dans un verre d'eau. S'il flotte, c'est qu'il a emprisonné assez de CO2 et que les levures sont à leur pic.",
221
+ },
222
+ {
223
+ type: "paragraph",
224
+ html: "L'utilisation de notre calculateur vous permet de standardiser votre processus de boulangerie et de mieux comprendre la biologie de votre pain. La constance est le secret de l'excellence artisanale.",
225
+ },
226
+ ],
227
+ ui: {
228
+ totalAmount: "Quantité Totale",
229
+ refreshRatio: "Ratio de Rafraîchissement",
230
+ maintain: "Maintenir",
231
+ activate: "Activer",
232
+ retard: "Retarder",
233
+ strong: "Fort",
234
+ sweetSpot: "Sweet Spot",
235
+ custom: "Perso",
236
+ sourdough: "Levain",
237
+ activeCulture: "Culture active",
238
+ flour: "Farine",
239
+ flourType: "Force ou Complète",
240
+ water: "Eau",
241
+ chlorineFree: "Sans chlore",
242
+ todayFormula: "Formule du Jour",
243
+ hydration: "Hydratation 100%",
244
+ totalDough: "Total Levain",
245
+ mm: "Levain",
246
+ },
247
+ schemas: [],
248
+ };
@@ -0,0 +1,24 @@
1
+ import type { CookingToolEntry, ToolDefinition } from '../../types';
2
+ import SourdoughCalculatorComponent from './component.astro';
3
+ import SourdoughCalculatorSEO from './seo.astro';
4
+ import SourdoughCalculatorBibliography from './bibliography.astro';
5
+
6
+ export const sourdoughCalculator: CookingToolEntry = {
7
+ id: 'sourdough-calculator',
8
+ icons: {
9
+ bg: 'mdi:bacteria',
10
+ fg: 'mdi:scale-balance',
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 const SOURDOUGH_CALCULATOR_TOOL: ToolDefinition = {
20
+ entry: sourdoughCalculator,
21
+ Component: SourdoughCalculatorComponent,
22
+ SEOComponent: SourdoughCalculatorSEO,
23
+ BibliographyComponent: SourdoughCalculatorBibliography,
24
+ };
@@ -0,0 +1,131 @@
1
+ interface Ratio {
2
+ s: number;
3
+ f: number;
4
+ w: number;
5
+ }
6
+
7
+ interface CalculationResult {
8
+ starter: number;
9
+ flour: number;
10
+ water: number;
11
+ }
12
+
13
+ class SourdoughCalculator {
14
+ static calculate(totalAmount: number, ratio: Ratio): CalculationResult {
15
+ if (totalAmount <= 0) {
16
+ return { starter: 0, flour: 0, water: 0 };
17
+ }
18
+
19
+ const totalParts = ratio.s + ratio.f + ratio.w;
20
+ if (totalParts <= 0) {
21
+ return { starter: 0, flour: 0, water: 0 };
22
+ }
23
+
24
+ const unitWeight = totalAmount / totalParts;
25
+
26
+ return {
27
+ starter: Math.round(unitWeight * ratio.s),
28
+ flour: Math.round(unitWeight * ratio.f),
29
+ water: Math.round(unitWeight * ratio.w),
30
+ };
31
+ }
32
+ }
33
+
34
+ interface SourdoughElements {
35
+ totalInput: HTMLInputElement;
36
+ ratioBtns: NodeListOf<Element>;
37
+ customInputsDiv: HTMLElement | null;
38
+ customInputs: NodeListOf<HTMLInputElement>;
39
+ outStarter: HTMLElement;
40
+ outFlour: HTMLElement;
41
+ outWater: HTMLElement;
42
+ outTotal: HTMLElement;
43
+ }
44
+
45
+ function setupElements(): SourdoughElements | null {
46
+ const totalInput = document.getElementById('total-amount') as HTMLInputElement | null;
47
+ const outStarter = document.getElementById('val-starter');
48
+ const outFlour = document.getElementById('val-flour');
49
+ const outWater = document.getElementById('val-water');
50
+ const outTotal = document.getElementById('val-total');
51
+
52
+ if (!totalInput || !outStarter || !outFlour || !outWater || !outTotal) {
53
+ return null;
54
+ }
55
+
56
+ return {
57
+ totalInput,
58
+ ratioBtns: document.querySelectorAll('.ratio-btn'),
59
+ customInputsDiv: document.getElementById('custom-ratio-inputs'),
60
+ customInputs: document.querySelectorAll('.custom-input') as NodeListOf<HTMLInputElement>,
61
+ outStarter,
62
+ outFlour,
63
+ outWater,
64
+ outTotal,
65
+ };
66
+ }
67
+
68
+ function setupRatioButtons(els: SourdoughElements, currentRatio: Ratio, calculate: () => void, updateFromCustom: () => void): void {
69
+ els.ratioBtns.forEach((btn) => {
70
+ btn.addEventListener('click', () => {
71
+ els.ratioBtns.forEach((b) => {
72
+ b.classList.remove('ratio-active');
73
+ b.classList.add('ratio-inactive');
74
+ });
75
+
76
+ btn.classList.remove('ratio-inactive');
77
+ btn.classList.add('ratio-active');
78
+
79
+ if (btn.id === 'custom-ratio-btn') {
80
+ els.customInputsDiv?.classList.add('visible');
81
+ updateFromCustom();
82
+ } else {
83
+ els.customInputsDiv?.classList.remove('visible');
84
+ const ratioStr = btn.getAttribute('data-ratio');
85
+ if (ratioStr) {
86
+ const parts = ratioStr.split(':').map(Number);
87
+ currentRatio.s = parts[0];
88
+ currentRatio.f = parts[1];
89
+ currentRatio.w = parts[2];
90
+ calculate();
91
+ }
92
+ }
93
+ });
94
+ });
95
+ }
96
+
97
+ function setupCustomInputs(els: SourdoughElements, updateFromCustom: () => void): void {
98
+ els.customInputs.forEach((inp) => {
99
+ inp.addEventListener('input', updateFromCustom);
100
+ });
101
+ }
102
+
103
+ export function initSourdoughCalculator(): void {
104
+ const els = setupElements();
105
+ if (!els) return;
106
+
107
+ let currentRatio: Ratio = { s: 1, f: 1, w: 1 };
108
+
109
+ function calculate(): void {
110
+ const total = parseInt(els.totalInput.value) || 0;
111
+ const result = SourdoughCalculator.calculate(total, currentRatio);
112
+
113
+ els.outStarter.textContent = result.starter.toString();
114
+ els.outFlour.textContent = result.flour.toString();
115
+ els.outWater.textContent = result.water.toString();
116
+ els.outTotal.textContent = `${result.starter + result.flour + result.water} g`;
117
+ }
118
+
119
+ function updateFromCustom(): void {
120
+ const s = parseFloat((document.getElementById('custom-s') as HTMLInputElement)?.value) || 1;
121
+ const f = parseFloat((document.getElementById('custom-f') as HTMLInputElement)?.value) || 1;
122
+ const w = parseFloat((document.getElementById('custom-w') as HTMLInputElement)?.value) || 1;
123
+ currentRatio = { s, f, w };
124
+ calculate();
125
+ }
126
+
127
+ els.totalInput.addEventListener('input', calculate);
128
+ setupRatioButtons(els, currentRatio, calculate, updateFromCustom);
129
+ setupCustomInputs(els, updateFromCustom);
130
+ calculate();
131
+ }
@@ -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 }} />
package/src/tools.ts ADDED
@@ -0,0 +1,29 @@
1
+ import type { ToolDefinition } from './types';
2
+ import { AMERICAN_KITCHEN_CONVERTER_TOOL } from './tool/american-kitchen-converter';
3
+ import { MERENGUE_CALCULATOR_TOOL } from './tool/meringue-peak';
4
+ import { BANANA_CARE_TOOL } from './tool/banana-ripeness';
5
+ import { EGG_TIMER_TOOL } from './tool/egg-timer';
6
+ import { KITCHEN_TIMER_TOOL } from './tool/kitchen-timer';
7
+ import { PIZZA_TOOL } from './tool/pizza';
8
+ import { BRINE_TOOL } from './tool/brine';
9
+ import { MOLD_SCALER_TOOL } from './tool/mold-scaler';
10
+ import { INGREDIENT_RESCALER_TOOL } from './tool/ingredient-rescaler';
11
+ import { SOURDOUGH_CALCULATOR_TOOL } from './tool/sourdough-calculator';
12
+ import { ROUX_GUIDE_TOOL } from './tool/roux-guide';
13
+ import { COOKWARE_GUIDE_TOOL } from './tool/cookware-guide';
14
+
15
+ export const ALL_TOOLS: ToolDefinition[] = [
16
+ AMERICAN_KITCHEN_CONVERTER_TOOL,
17
+ MERENGUE_CALCULATOR_TOOL,
18
+ BANANA_CARE_TOOL,
19
+ EGG_TIMER_TOOL,
20
+ KITCHEN_TIMER_TOOL,
21
+ PIZZA_TOOL,
22
+ BRINE_TOOL,
23
+ MOLD_SCALER_TOOL,
24
+ INGREDIENT_RESCALER_TOOL,
25
+ SOURDOUGH_CALCULATOR_TOOL,
26
+ ROUX_GUIDE_TOOL,
27
+ COOKWARE_GUIDE_TOOL,
28
+ ];
29
+
package/src/types.ts ADDED
@@ -0,0 +1,73 @@
1
+ import type { SEOSection } from '@jjlmoya/utils-shared';
2
+ import type { WithContext, Thing } from 'schema-dts';
3
+
4
+ export type { SEOSection };
5
+
6
+ export type KnownLocale =
7
+ | 'ar' | 'da' | 'de' | 'en' | 'es' | 'fi'
8
+ | 'fr' | 'it' | 'ja' | 'ko' | 'nb' | 'nl'
9
+ | 'pl' | 'pt' | 'ru' | 'sv' | 'tr' | 'zh';
10
+
11
+ export interface FAQItem {
12
+ question: string;
13
+ answer: string;
14
+ }
15
+
16
+ export interface BibliographyEntry {
17
+ name: string;
18
+ url: string;
19
+ }
20
+
21
+ export interface HowToStep {
22
+ name: string;
23
+ text: string;
24
+ }
25
+
26
+ export interface ToolLocaleContent<TUI extends Record<string, string> = Record<string, string>> {
27
+ slug: string;
28
+ title: string;
29
+ description: string;
30
+ ui: TUI;
31
+ seo: SEOSection[];
32
+ faqTitle?: string;
33
+ faq: FAQItem[];
34
+ bibliographyTitle?: string;
35
+ bibliography: BibliographyEntry[];
36
+ howTo: HowToStep[];
37
+ schemas: WithContext<Thing>[];
38
+ }
39
+
40
+ export interface CategoryLocaleContent {
41
+ slug: string;
42
+ title: string;
43
+ description: string;
44
+ seo: SEOSection[];
45
+ }
46
+
47
+ export type LocaleLoader<T> = () => Promise<T>;
48
+
49
+ export type LocaleMap<T> = Partial<Record<KnownLocale, LocaleLoader<T>>>;
50
+
51
+ export interface CookingToolEntry<TUI extends Record<string, string> = Record<string, string>> {
52
+ id: string;
53
+ icons: {
54
+ bg: string;
55
+ fg: string;
56
+ };
57
+ appSlug?: string;
58
+ i18n: LocaleMap<ToolLocaleContent<TUI>>;
59
+ }
60
+
61
+ export interface CookingCategoryEntry {
62
+ icon: string;
63
+ tools: CookingToolEntry[];
64
+ i18n: LocaleMap<CategoryLocaleContent>;
65
+ }
66
+
67
+ export interface ToolDefinition {
68
+ entry: CookingToolEntry;
69
+ Component: unknown;
70
+ SEOComponent: unknown;
71
+ BibliographyComponent: unknown;
72
+ }
73
+