@jjlmoya/utils-cooking 1.37.0 → 1.39.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 (59) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +4 -0
  3. package/src/entries.ts +5 -1
  4. package/src/index.ts +2 -0
  5. package/src/tests/i18n-titles.test.ts +1 -1
  6. package/src/tests/leavener-acid-neutralizer.test.ts +42 -0
  7. package/src/tests/locale_completeness.test.ts +2 -2
  8. package/src/tests/tool_validation.test.ts +2 -2
  9. package/src/tool/leavener-acid-neutralizer/bibliography.astro +6 -0
  10. package/src/tool/leavener-acid-neutralizer/bibliography.ts +10 -0
  11. package/src/tool/leavener-acid-neutralizer/component.astro +335 -0
  12. package/src/tool/leavener-acid-neutralizer/entry.ts +26 -0
  13. package/src/tool/leavener-acid-neutralizer/i18n/de.ts +279 -0
  14. package/src/tool/leavener-acid-neutralizer/i18n/en.ts +279 -0
  15. package/src/tool/leavener-acid-neutralizer/i18n/es.ts +275 -0
  16. package/src/tool/leavener-acid-neutralizer/i18n/fr.ts +279 -0
  17. package/src/tool/leavener-acid-neutralizer/i18n/id.ts +279 -0
  18. package/src/tool/leavener-acid-neutralizer/i18n/it.ts +275 -0
  19. package/src/tool/leavener-acid-neutralizer/i18n/ja.ts +279 -0
  20. package/src/tool/leavener-acid-neutralizer/i18n/ko.ts +279 -0
  21. package/src/tool/leavener-acid-neutralizer/i18n/nl.ts +279 -0
  22. package/src/tool/leavener-acid-neutralizer/i18n/pl.ts +279 -0
  23. package/src/tool/leavener-acid-neutralizer/i18n/pt.ts +279 -0
  24. package/src/tool/leavener-acid-neutralizer/i18n/ru.ts +279 -0
  25. package/src/tool/leavener-acid-neutralizer/i18n/sv.ts +279 -0
  26. package/src/tool/leavener-acid-neutralizer/i18n/tr.ts +279 -0
  27. package/src/tool/leavener-acid-neutralizer/i18n/zh.ts +279 -0
  28. package/src/tool/leavener-acid-neutralizer/index.ts +10 -0
  29. package/src/tool/leavener-acid-neutralizer/leavener-acid-neutralizer.css +424 -0
  30. package/src/tool/leavener-acid-neutralizer/logic.ts +57 -0
  31. package/src/tool/leavener-acid-neutralizer/seo.astro +15 -0
  32. package/src/tool/pectin-jam-setting-calculator/bibliography.astro +6 -0
  33. package/src/tool/pectin-jam-setting-calculator/bibliography.ts +10 -0
  34. package/src/tool/pectin-jam-setting-calculator/component.astro +170 -0
  35. package/src/tool/pectin-jam-setting-calculator/components/CalculatorInputs.astro +44 -0
  36. package/src/tool/pectin-jam-setting-calculator/components/DropTestVisualizer.astro +40 -0
  37. package/src/tool/pectin-jam-setting-calculator/components/FruitSelector.astro +38 -0
  38. package/src/tool/pectin-jam-setting-calculator/components/RecipeResults.astro +72 -0
  39. package/src/tool/pectin-jam-setting-calculator/entry.ts +26 -0
  40. package/src/tool/pectin-jam-setting-calculator/i18n/de.ts +248 -0
  41. package/src/tool/pectin-jam-setting-calculator/i18n/en.ts +248 -0
  42. package/src/tool/pectin-jam-setting-calculator/i18n/es.ts +248 -0
  43. package/src/tool/pectin-jam-setting-calculator/i18n/fr.ts +248 -0
  44. package/src/tool/pectin-jam-setting-calculator/i18n/id.ts +248 -0
  45. package/src/tool/pectin-jam-setting-calculator/i18n/it.ts +248 -0
  46. package/src/tool/pectin-jam-setting-calculator/i18n/ja.ts +248 -0
  47. package/src/tool/pectin-jam-setting-calculator/i18n/ko.ts +248 -0
  48. package/src/tool/pectin-jam-setting-calculator/i18n/nl.ts +248 -0
  49. package/src/tool/pectin-jam-setting-calculator/i18n/pl.ts +248 -0
  50. package/src/tool/pectin-jam-setting-calculator/i18n/pt.ts +248 -0
  51. package/src/tool/pectin-jam-setting-calculator/i18n/ru.ts +248 -0
  52. package/src/tool/pectin-jam-setting-calculator/i18n/sv.ts +248 -0
  53. package/src/tool/pectin-jam-setting-calculator/i18n/tr.ts +248 -0
  54. package/src/tool/pectin-jam-setting-calculator/i18n/zh.ts +248 -0
  55. package/src/tool/pectin-jam-setting-calculator/index.ts +11 -0
  56. package/src/tool/pectin-jam-setting-calculator/logic.ts +96 -0
  57. package/src/tool/pectin-jam-setting-calculator/pectin-jam-setting-calculator.css +730 -0
  58. package/src/tool/pectin-jam-setting-calculator/seo.astro +15 -0
  59. package/src/tools.ts +4 -0
@@ -0,0 +1,248 @@
1
+ import type { ToolLocaleContent } from '../../../types';
2
+ import { bibliography } from '../bibliography';
3
+
4
+ const title = '果胶与果酱凝固计算器 每次都能完美凝胶';
5
+ const description = '精确计算水果所需的果胶、柠檬酸和糖的用量,实现完美的果酱凝固。通过精确的化学计算,避免果酱过稀或过硬。';
6
+
7
+ const faq = [
8
+ {
9
+ question: '为什么果胶能让果酱凝固?',
10
+ answer: '果胶是水果细胞壁中的天然多糖。当与糖和酸(pH 2.8-3.5)一起加热时,果胶分子形成三维凝胶网络,将水和糖包裹其中,创造出果酱的可涂抹质地。',
11
+ },
12
+ {
13
+ question: 'HM果胶和LM果胶有什么区别?',
14
+ answer: '高甲氧基(HM)果胶需要至少60%的糖和pH低于3.5才能凝胶,适合传统果酱。低甲氧基(LM)果胶通过钙离子而非糖来凝胶,可用于低糖或无糖果酱。',
15
+ },
16
+ {
17
+ question: '为什么我的果酱有时会太稀?',
18
+ answer: '果酱太稀通常是由于果胶不足(水果天然缺乏果胶)、酸不足(pH过高无法凝胶)或糖不足(对于HM果胶)。过度加水稀释或加热不足也会阻碍凝固。',
19
+ },
20
+ {
21
+ question: '可以用柠檬汁代替柠檬酸粉末吗?',
22
+ answer: '可以。本计算器将柠檬酸换算为等量的柠檬汁。约25毫升柠檬汁提供的酸度相当于1克柠檬酸。柠檬汁还能为果酱增添清新风味。',
23
+ },
24
+ {
25
+ question: '哪些水果天然果胶含量高?',
26
+ answer: '苹果、榅桲、黑莓、蔓越莓和醋栗天然富含果胶,通常不需要额外添加果胶。未完全成熟的水果也比完全成熟的水果含有更多的果胶。',
27
+ },
28
+ ];
29
+
30
+ const howTo = [
31
+ {
32
+ name: '选择水果',
33
+ text: '选择您要制作果酱的水果。每种水果具有不同的天然果胶和酸度水平,这决定了需要添加什么辅料。',
34
+ },
35
+ {
36
+ name: '称量准备好的水果',
37
+ text: '输入已清洗、切好的水果重量(克)。精确称量对于精准制作果酱至关重要。',
38
+ },
39
+ {
40
+ name: '选择果胶类型',
41
+ text: '传统高糖果酱选择经典(HM)型,如果想要由钙激活的更健康的低糖果酱,则选择低糖(LM)型。',
42
+ },
43
+ {
44
+ name: '查看完美配方',
45
+ text: '计算器会输出所需的精确果胶粉末、柠檬酸(或柠檬汁)和糖的克数。在烹饪过程中加入这些,确保凝固成功。',
46
+ },
47
+ ];
48
+
49
+ const faqSchema = {
50
+ '@context': 'https://schema.org' as const,
51
+ '@type': 'FAQPage' as const,
52
+ mainEntity: faq.map((item) => ({
53
+ '@type': 'Question' as const,
54
+ name: item.question,
55
+ acceptedAnswer: { '@type': 'Answer' as const, text: item.answer },
56
+ })),
57
+ };
58
+
59
+ const howToSchema = {
60
+ '@context': 'https://schema.org' as const,
61
+ '@type': 'HowTo' as const,
62
+ name: title,
63
+ description,
64
+ step: howTo.map((step) => ({
65
+ '@type': 'HowToStep' as const,
66
+ name: step.name,
67
+ text: step.text,
68
+ })),
69
+ };
70
+
71
+ const appSchema = {
72
+ '@context': 'https://schema.org' as const,
73
+ '@type': 'SoftwareApplication' as const,
74
+ name: title,
75
+ description,
76
+ applicationCategory: 'UtilitiesApplication',
77
+ operatingSystem: 'Web',
78
+ offers: { '@type': 'Offer' as const, price: '0', priceCurrency: 'EUR' },
79
+ };
80
+
81
+ export const content: ToolLocaleContent = {
82
+ slug: 'pectin-jam-setting-calculator',
83
+ title: '果胶与果酱凝固计算器',
84
+ description: '精确计算水果所需的果胶、柠檬酸和糖的用量,实现完美的果酱凝固 - 不再有太稀或太硬的果酱。',
85
+ faqTitle: '常见问题',
86
+ ui: {
87
+ title: '果胶与果酱凝固计算器',
88
+ subtitle: '完美果酱的精密凝胶化学',
89
+ fruitLabel: '选择水果',
90
+ allFruits: '全部',
91
+ highPectin: '高果胶',
92
+ mediumPectin: '中等果胶',
93
+ lowPectin: '低果胶',
94
+ weightLabel: '水果重量',
95
+ weightPlaceholder: '例如 1000',
96
+ weightUnitMetric: '克',
97
+ weightUnitImperial: '盎司',
98
+ pectinTypeLabel: '果胶类型',
99
+ pectinHM: '经典(HM)',
100
+ pectinLM: '低糖(LM)',
101
+ sugarModeLabel: '糖模式',
102
+ sugarModeAuto: '自动',
103
+ sugarModeManual: '手动',
104
+ sugarLabel: '糖的重量',
105
+ sugarPlaceholder: '例如 650',
106
+ recipeTitle: '配方',
107
+ pectinNeeded: '果胶',
108
+ citricAcidNeeded: '柠檬酸',
109
+ lemonJuiceNeeded: '柠檬汁',
110
+ sugarNeeded: '糖',
111
+ totalYield: '总产量',
112
+ sugarPercent: '糖',
113
+ sugarLow: '低',
114
+ sugarIdeal: '理想',
115
+ sugarHigh: '高',
116
+ sugarOfFruit: '占水果重量',
117
+ sugarOfTotal: '占总重',
118
+ statusPerfect: '完美凝胶',
119
+ statusPerfectDesc: '您的配比平衡。果酱将完美凝固,质地丝滑、易于涂抹、光泽明亮。',
120
+ statusSlightlyThin: '略微偏稀',
121
+ statusSlightlyThinDesc: '果酱可能会偏软凝固。考虑增加果胶用量或减少水分以获得更坚实的凝胶。',
122
+ statusTooThin: '太稀 - 有流质果酱风险',
123
+ statusTooThinDesc: '如果不调整,此果酱很可能会保持液态。增加糖量(HM果胶)或添加更多果胶。',
124
+ statusTooStiff: '太硬',
125
+ statusTooStiffDesc: '凝胶可能会变得像橡胶一样。将果胶减半或略微增加水果重量。',
126
+ dropTestTitle: '冷盘测试',
127
+ dropTestHow: '滴在冷盘上',
128
+ dropStatusLabel: '结果',
129
+ dropTestPerfect: '会凝固。液滴起皱并保持形状',
130
+ dropTestThin: '流质。从盘子上滑落',
131
+ dropTestStiff: '太硬。几乎不流动',
132
+ dropPlateLabel: '盘子',
133
+ dropDropLabel: '液滴',
134
+ sugarAutoHint: '自动计算',
135
+ sugarManualHint: '输入用量',
136
+ unitLabel: '计量系统',
137
+ metricLabel: '公制(克)',
138
+ imperialLabel: '英制(盎司)',
139
+ disclaimer: '使用数字厨房秤称量所有食材以获得最佳效果。体积测量对于制作果酱不可靠。',
140
+ },
141
+ faq,
142
+ howTo,
143
+ seo: [
144
+ {
145
+ type: 'title',
146
+ text: '果酱凝固的完整科学:果胶、酸和糖的平衡',
147
+ level: 2,
148
+ },
149
+ {
150
+ type: 'paragraph',
151
+ html: '果酱制作是化学与烹饪的精确交叉。其核心在于,果肉转变为坚实可涂抹的凝胶依赖于三种分子的正确平衡:<strong>果胶</strong>(凝胶剂)、<strong>酸</strong>(激活果胶的催化剂)和<strong>糖</strong>(将水从果胶链中拉出的脱水剂)。如果没有正确的配比,最终得到的要么是水果汤,要么是橡胶块。',
152
+ },
153
+ {
154
+ type: 'title',
155
+ text: '果胶如何形成凝胶网络',
156
+ level: 3,
157
+ },
158
+ {
159
+ type: 'paragraph',
160
+ html: '果胶是一种主要由半乳糖醛酸单元组成的复杂多糖,存在于植物细胞壁的中层。在天然状态下,果胶分子带负电荷并相互排斥,保持溶解在水果的水分中。要形成凝胶,必须同时满足三个条件:(1)必须有足够的糖来竞争水分子,使果胶链脱水;(2)pH必须降至3.5以下,通过羧基的质子化中和负电荷;(3)温度必须超过104-105°C以完全溶解和激活果胶。当这些条件满足时,果胶链形成连接区域 - 链通过氢键和疏水相互作用结合的区域 - 创建一个连续的三维海绵状网络,将果汁和糖浆包裹其中。',
161
+ },
162
+ {
163
+ type: 'title',
164
+ text: '高甲氧基(HM)果胶与低甲氧基(LM)果胶',
165
+ level: 3,
166
+ },
167
+ {
168
+ type: 'paragraph',
169
+ html: '果胶按其酯化度(DE)分类,即被甲醇酯化的羧基百分比。<strong>高甲氧基(HM)果胶</strong>(DE > 50%)需要至少55-65%的可溶性固形物(糖)和pH在2.8至3.5之间才能形成凝胶。这是传统果酱配方中使用的经典果胶。没有足够的糖,HM果胶会形成弱凝胶或根本不凝胶。<strong>低甲氧基(LM)果胶</strong>(DE < 50%)通过不同的机制凝胶:它通过二价钙离子(Ca²⁺)在游离羧基之间桥接进行交联。LM果胶可以在添加很少或不添加糖的情况下凝胶,非常适合低热量、适合糖尿病患者或天然甜味的果酱。一些LM果胶还能耐受高达5.5的更宽pH范围,适用于无花果和梨等低酸水果。',
170
+ },
171
+ {
172
+ type: 'title',
173
+ text: '不同水果品种的天然果胶含量',
174
+ level: 3,
175
+ },
176
+ {
177
+ type: 'paragraph',
178
+ html: '并非所有水果的果胶含量都相同。水果根据其天然果胶和酸度水平分为三类。了解您的水果在这个谱系中的位置决定了是否需要添加果胶粉末或柠檬酸。',
179
+ },
180
+ {
181
+ type: 'table',
182
+ headers: ['果胶水平', '酸度水平', '示例水果', '需要添加的果胶'],
183
+ rows: [
184
+ ['高', '高', '苹果、蔓越莓、醋栗', '0%(无需)'],
185
+ ['高', '中/低', '榅桲、黑莓', '0%(无需)'],
186
+ ['中', '高', '覆盆子、罗甘莓', '水果重量的0.3%'],
187
+ ['中', '中', '李子、杏', '水果重量的0.3%'],
188
+ ['中', '低', '蓝莓', '水果重量的0.3%'],
189
+ ['低', '高', '樱桃、葡萄', '水果重量的0.6%'],
190
+ ['低', '中', '桃、芒果', '水果重量的0.6%'],
191
+ ['低', '低', '草莓、梨、无花果', '水果重量的0.6%'],
192
+ ],
193
+ },
194
+ {
195
+ type: 'title',
196
+ text: 'pH在果酱凝胶中的关键作用',
197
+ level: 3,
198
+ },
199
+ {
200
+ type: 'paragraph',
201
+ html: '果酱混合物的pH值可以说是家庭果酱制作中最容易被忽视的变量。当pH高于3.8时,果胶上的羧基保持离子化(带负电荷),产生静电排斥,无论添加多少糖或果胶都会阻止凝胶形成。当通过添加柠檬酸或柠檬汁使pH降至3.5以下时,这些基团被质子化,使得相邻果胶链之间能够形成氢键。最佳凝胶区域在pH 2.8至3.2之间。低于pH 2.8时,凝胶变脆并可能出现脱水收缩(渗液)。高于pH 3.5时,也会发生脱水收缩,且凝胶太弱。这就是为什么草莓和无花果等低酸水果几乎总是需要添加柠檬酸 - 它们的天然pH太高,无法正常激活果胶。',
202
+ },
203
+ {
204
+ type: 'title',
205
+ text: '糖浓度及其对凝胶强度的影响',
206
+ level: 3,
207
+ },
208
+ {
209
+ type: 'paragraph',
210
+ html: '糖在HM果胶果酱中有两个作用。首先,糖具有高吸湿性 - 它强烈地与果胶争夺水分子,将水合层从果胶链上拉开,迫使它们相互结合。其次,糖提高了混合物的沸点,使果酱能够达到104-105°C的凝固点。在65%的糖浓度下,果胶链充分脱水,形成强韧的凝胶。低于60%时,凝胶强度线性减弱。高于68%时,糖可能超过其溶解度极限,在冷却时结晶。对于LM果胶果酱,糖只起调味作用,因为凝胶化依赖于钙桥。使用非营养性甜味剂增甜的LM果胶果酱中,糖含量可低至5-10%。',
211
+ },
212
+ {
213
+ type: 'title',
214
+ text: '柠檬酸与柠檬汁:换算与最佳实践',
215
+ level: 3,
216
+ },
217
+ {
218
+ type: 'paragraph',
219
+ html: '柠檬酸(C₆H₈O₇)是商业果酱制作中使用的主要酸,因为它标准化、无味且精确。柠檬汁按重量计约含5%的柠檬酸,外加苹果酸和抗坏血酸(维生素C)。<strong>1克纯柠檬酸在降低pH的能力上大约相当于25毫升新鲜柠檬汁</strong>。然而,柠檬汁还会增加液体体积,这必须计入总含水量中。为了获得最一致的结果,请使用溶解在少量水中的柠檬酸粉末。使用柠檬汁时,需额外考虑需要煮沸蒸发的20-30毫升液体才能达到凝固点。',
220
+ },
221
+ {
222
+ type: 'title',
223
+ text: '冷盘测试:判断凝固点的视觉方法',
224
+ level: 3,
225
+ },
226
+ {
227
+ type: 'paragraph',
228
+ html: '传统的冷盘测试(也称为皱纹测试或碟子测试)仍然是家庭果酱制作者最可靠的方法之一。在开始烹饪前,将一个小瓷盘放入冰箱冷冻10分钟。当您认为果酱已达到凝固点时,将一茶匙热果酱滴在冷盘上,冷却30秒。用指尖推动液滴边缘:如果表面明显起皱且液滴保持形状,则已达到凝胶点。如果自由流动,继续煮沸2-3分钟后再测试。本计算器以视觉方式模拟该测试,在您开始烹饪之前就显示您的配方配比是否能通过冷盘测试。',
229
+ },
230
+ {
231
+ type: 'title',
232
+ text: '故障排除:果酱失败的原因及修复方法',
233
+ level: 3,
234
+ },
235
+ {
236
+ type: 'list',
237
+ items: [
238
+ '<strong>流质果酱(冷却后未凝固):</strong>最常见的原因是煮沸不足 - 混合物从未达到104-105°C。重新煮沸果酱,每公斤水果添加1-2克柠檬酸,并用冷盘法测试。或者,将5克果胶粉溶解在冷水中,在沸腾时搅入果酱中,再煮2分钟。',
239
+ '<strong>橡胶状或过硬的果酱:</strong>相对于水果重量添加了过多果胶,或果酱过度煮沸超过106°C,使果胶网络降解为紧密脆性的结构。要挽救,用100-200毫升苹果汁或水轻轻重新加热以稀释果胶浓度。',
240
+ '<strong>脱水收缩(凝胶中渗出水):</strong>这表明酸过多(pH低于2.8)或糖超过68%,两者都会导致果胶网络收缩并挤出水分。加入少量小苏打(碳酸氢钠)以逐步提高pH值。',
241
+ '<strong>结晶(颗粒质地):</strong>糖浓度超过溶解度,或未溶解的糖晶体充当了成核位点。在最后煮沸过程中不断搅拌,并加入1汤匙玉米糖浆或葡萄糖,这能抑制结晶。',
242
+ '<strong>储存期间表面发霉:</strong>果酱加热不足(未达到杀菌温度)、糖含量过低(HM果胶低于60%)或瓶子未正确消毒。始终使用已消毒的瓶子,并在水浴中处理10分钟。',
243
+ ],
244
+ },
245
+ ],
246
+ bibliography,
247
+ schemas: [faqSchema, howToSchema, appSchema],
248
+ };
@@ -0,0 +1,11 @@
1
+ import type { ToolDefinition } from '../../types';
2
+ import { pectinJam } from './entry';
3
+
4
+ export * from './entry';
5
+
6
+ export const PECTIN_JAM_SETTING_TOOL: ToolDefinition = {
7
+ entry: pectinJam,
8
+ Component: () => import('./component.astro'),
9
+ SEOComponent: () => import('./seo.astro'),
10
+ BibliographyComponent: () => import('./bibliography.astro'),
11
+ };
@@ -0,0 +1,96 @@
1
+ export interface FruitData {
2
+ name: string;
3
+ pectin: 'high' | 'medium' | 'low';
4
+ acidity: 'high' | 'medium' | 'low';
5
+ moisture: number;
6
+ color: string;
7
+ emoji: string;
8
+ }
9
+
10
+ export const FRUIT_DATABASE: Record<string, FruitData> = {
11
+ apple: { name: 'Apple', pectin: 'high', acidity: 'high', moisture: 0.84, color: '#dc2626', emoji: '' },
12
+ quince: { name: 'Quince', pectin: 'high', acidity: 'medium', moisture: 0.84, color: '#ea580c', emoji: '' },
13
+ blackberry: { name: 'Blackberry', pectin: 'high', acidity: 'low', moisture: 0.88, color: '#7c3aed', emoji: '' },
14
+ cranberry: { name: 'Cranberry', pectin: 'high', acidity: 'high', moisture: 0.87, color: '#be123c', emoji: '' },
15
+ gooseberry: { name: 'Gooseberry', pectin: 'high', acidity: 'high', moisture: 0.88, color: '#65a30d', emoji: '' },
16
+ plum: { name: 'Plum', pectin: 'medium', acidity: 'medium', moisture: 0.85, color: '#7c3aed', emoji: '' },
17
+ apricot: { name: 'Apricot', pectin: 'medium', acidity: 'medium', moisture: 0.86, color: '#d97706', emoji: '' },
18
+ blueberry: { name: 'Blueberry', pectin: 'medium', acidity: 'low', moisture: 0.84, color: '#4f46e5', emoji: '' },
19
+ raspberry: { name: 'Raspberry', pectin: 'medium', acidity: 'high', moisture: 0.87, color: '#e11d48', emoji: '' },
20
+ peach: { name: 'Peach', pectin: 'low', acidity: 'medium', moisture: 0.89, color: '#d97706', emoji: '' },
21
+ strawberry: { name: 'Strawberry', pectin: 'low', acidity: 'low', moisture: 0.92, color: '#e11d48', emoji: '' },
22
+ pear: { name: 'Pear', pectin: 'low', acidity: 'low', moisture: 0.84, color: '#65a30d', emoji: '' },
23
+ fig: { name: 'Fig', pectin: 'low', acidity: 'low', moisture: 0.79, color: '#92400e', emoji: '' },
24
+ cherry: { name: 'Cherry', pectin: 'low', acidity: 'high', moisture: 0.82, color: '#be123c', emoji: '' },
25
+ grape: { name: 'Grape', pectin: 'low', acidity: 'high', moisture: 0.81, color: '#7c3aed', emoji: '' },
26
+ mango: { name: 'Mango', pectin: 'low', acidity: 'medium', moisture: 0.83, color: '#d97706', emoji: '' },
27
+ };
28
+
29
+ export interface JamInput {
30
+ fruitType: string;
31
+ fruitWeight: number;
32
+ pectinType: 'HM' | 'LM';
33
+ sugarWeight?: number;
34
+ }
35
+
36
+ export interface JamResult {
37
+ pectinGrams: number;
38
+ citricAcidGrams: number;
39
+ lemonJuiceMl: number;
40
+ sugarGrams: number;
41
+ totalYield: number;
42
+ sugarPercentage: number;
43
+ status: 'perfect' | 'slightly-thin' | 'too-thin' | 'too-stiff';
44
+ fruitColor: string;
45
+ }
46
+
47
+ export class JamLogic {
48
+ static calculate(input: JamInput): JamResult {
49
+ const fruit = FRUIT_DATABASE[input.fruitType] ?? FRUIT_DATABASE['strawberry']!;
50
+ const fw = Math.max(10, input.fruitWeight);
51
+
52
+ const pectinGrams = JamLogic.computePectin(fw, fruit.pectin);
53
+ const citricAcidGrams = JamLogic.computeCitric(fw, fruit.acidity);
54
+ const lemonJuiceMl = parseFloat((citricAcidGrams * 15).toFixed(0));
55
+ const sugarGrams = JamLogic.computeSugar(input, fw);
56
+ const cookedFruit = parseFloat((fw * 0.55).toFixed(0));
57
+ const totalYield = parseFloat((cookedFruit + sugarGrams + pectinGrams + citricAcidGrams).toFixed(0));
58
+ const sugarPercentage = parseFloat(((sugarGrams / totalYield) * 100).toFixed(1));
59
+ const status = JamLogic.computeStatus(input.pectinType, sugarPercentage, fruit.pectin, pectinGrams);
60
+
61
+ return {
62
+ pectinGrams, citricAcidGrams, lemonJuiceMl, sugarGrams,
63
+ totalYield, sugarPercentage, status, fruitColor: fruit.color,
64
+ };
65
+ }
66
+
67
+ private static computePectin(fw: number, pectin: string): number {
68
+ if (pectin === 'high') return 0;
69
+ if (pectin === 'medium') return parseFloat((fw * 0.003).toFixed(1));
70
+ return parseFloat((fw * 0.006).toFixed(1));
71
+ }
72
+
73
+ private static computeCitric(fw: number, acidity: string): number {
74
+ if (acidity === 'high') return 0;
75
+ if (acidity === 'medium') return parseFloat((fw * 0.003).toFixed(1));
76
+ return parseFloat((fw * 0.008).toFixed(1));
77
+ }
78
+
79
+ private static computeSugar(input: JamInput, fw: number): number {
80
+ if (input.sugarWeight != null) return input.sugarWeight;
81
+ if (input.pectinType === 'LM') return parseFloat((fw * 0.1).toFixed(0));
82
+ return parseFloat((fw * 1.0).toFixed(0));
83
+ }
84
+
85
+ private static computeStatus(
86
+ pectinType: string, sugarPct: number, pectin: string, pectinGrams: number
87
+ ): JamResult['status'] {
88
+ const hasEnoughPectin = pectin !== 'low' || pectinGrams > 0;
89
+ if (pectinType === 'HM') {
90
+ if (sugarPct >= 60 && hasEnoughPectin) return 'perfect';
91
+ return sugarPct >= 50 ? 'slightly-thin' : 'too-thin';
92
+ }
93
+ if (hasEnoughPectin) return 'perfect';
94
+ return pectinGrams > 0 ? 'slightly-thin' : 'too-thin';
95
+ }
96
+ }