@jjlmoya/utils-cooking 1.28.0 → 1.30.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.
- package/package.json +1 -1
- package/src/category/index.ts +5 -0
- package/src/entries.ts +6 -1
- package/src/index.ts +3 -0
- package/src/tests/i18n-titles.test.ts +8 -2
- package/src/tests/ice-cream-pac-pod.test.ts +68 -0
- package/src/tests/locale_completeness.test.ts +3 -2
- package/src/tests/spherification-bath-calculator.test.ts +57 -0
- package/src/tests/tool_validation.test.ts +3 -2
- package/src/tool/ice-cream-pac-pod/bibliography.astro +6 -0
- package/src/tool/ice-cream-pac-pod/bibliography.ts +10 -0
- package/src/tool/ice-cream-pac-pod/component.astro +252 -0
- package/src/tool/ice-cream-pac-pod/components/IceCryoGauge.astro +54 -0
- package/src/tool/ice-cream-pac-pod/components/ScoopVisualizer.astro +54 -0
- package/src/tool/ice-cream-pac-pod/components/SugarFormulators.astro +107 -0
- package/src/tool/ice-cream-pac-pod/entry.ts +26 -0
- package/src/tool/ice-cream-pac-pod/i18n/de.ts +182 -0
- package/src/tool/ice-cream-pac-pod/i18n/en.ts +183 -0
- package/src/tool/ice-cream-pac-pod/i18n/es.ts +182 -0
- package/src/tool/ice-cream-pac-pod/i18n/fr.ts +182 -0
- package/src/tool/ice-cream-pac-pod/i18n/id.ts +182 -0
- package/src/tool/ice-cream-pac-pod/i18n/it.ts +182 -0
- package/src/tool/ice-cream-pac-pod/i18n/ja.ts +182 -0
- package/src/tool/ice-cream-pac-pod/i18n/ko.ts +182 -0
- package/src/tool/ice-cream-pac-pod/i18n/nl.ts +177 -0
- package/src/tool/ice-cream-pac-pod/i18n/pl.ts +177 -0
- package/src/tool/ice-cream-pac-pod/i18n/pt.ts +177 -0
- package/src/tool/ice-cream-pac-pod/i18n/ru.ts +182 -0
- package/src/tool/ice-cream-pac-pod/i18n/sv.ts +177 -0
- package/src/tool/ice-cream-pac-pod/i18n/tr.ts +182 -0
- package/src/tool/ice-cream-pac-pod/i18n/zh.ts +182 -0
- package/src/tool/ice-cream-pac-pod/ice-cream-pac-pod.css +570 -0
- package/src/tool/ice-cream-pac-pod/index.ts +11 -0
- package/src/tool/ice-cream-pac-pod/logic.ts +62 -0
- package/src/tool/ice-cream-pac-pod/seo.astro +15 -0
- package/src/tool/spherification-bath-calculator/bibliography.astro +6 -0
- package/src/tool/spherification-bath-calculator/bibliography.ts +10 -0
- package/src/tool/spherification-bath-calculator/component.astro +213 -0
- package/src/tool/spherification-bath-calculator/components/PrecisionControls.astro +85 -0
- package/src/tool/spherification-bath-calculator/components/RecipeSummary.astro +60 -0
- package/src/tool/spherification-bath-calculator/components/SpherificationReactor.astro +73 -0
- package/src/tool/spherification-bath-calculator/entry.ts +26 -0
- package/src/tool/spherification-bath-calculator/i18n/de.ts +180 -0
- package/src/tool/spherification-bath-calculator/i18n/en.ts +180 -0
- package/src/tool/spherification-bath-calculator/i18n/es.ts +179 -0
- package/src/tool/spherification-bath-calculator/i18n/fr.ts +179 -0
- package/src/tool/spherification-bath-calculator/i18n/id.ts +180 -0
- package/src/tool/spherification-bath-calculator/i18n/it.ts +180 -0
- package/src/tool/spherification-bath-calculator/i18n/ja.ts +180 -0
- package/src/tool/spherification-bath-calculator/i18n/ko.ts +180 -0
- package/src/tool/spherification-bath-calculator/i18n/nl.ts +180 -0
- package/src/tool/spherification-bath-calculator/i18n/pl.ts +180 -0
- package/src/tool/spherification-bath-calculator/i18n/pt.ts +180 -0
- package/src/tool/spherification-bath-calculator/i18n/ru.ts +180 -0
- package/src/tool/spherification-bath-calculator/i18n/sv.ts +180 -0
- package/src/tool/spherification-bath-calculator/i18n/tr.ts +180 -0
- package/src/tool/spherification-bath-calculator/i18n/zh.ts +180 -0
- package/src/tool/spherification-bath-calculator/index.ts +11 -0
- package/src/tool/spherification-bath-calculator/logic.ts +54 -0
- package/src/tool/spherification-bath-calculator/seo.astro +15 -0
- package/src/tool/spherification-bath-calculator/spherification-bath-calculator.css +568 -0
- package/src/tools.ts +5 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
---
|
|
2
|
+
import PrecisionControls from './components/PrecisionControls.astro';
|
|
3
|
+
import RecipeSummary from './components/RecipeSummary.astro';
|
|
4
|
+
import SpherificationReactor from './components/SpherificationReactor.astro';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
ui: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { ui } = Astro.props;
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<div class="spherification-container">
|
|
14
|
+
<div class="spherification-console">
|
|
15
|
+
<div class="spherification-glow-1"></div>
|
|
16
|
+
<div class="spherification-glow-2"></div>
|
|
17
|
+
|
|
18
|
+
<div class="console-column controls-col">
|
|
19
|
+
<PrecisionControls ui={ui} />
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="console-column reactor-col">
|
|
23
|
+
<SpherificationReactor ui={ui} />
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="console-column summary-col">
|
|
27
|
+
<RecipeSummary ui={ui} />
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<script is:inline define:vars={{ ui }}>
|
|
33
|
+
const metricBtn = document.getElementById('toggle-metric-btn');
|
|
34
|
+
const imperialBtn = document.getElementById('toggle-imperial-btn');
|
|
35
|
+
const directBtn = document.getElementById('toggle-direct-btn');
|
|
36
|
+
const reverseBtn = document.getElementById('toggle-reverse-btn');
|
|
37
|
+
const xanthanBtn = document.getElementById('toggle-xanthan-btn');
|
|
38
|
+
const citrateBtn = document.getElementById('toggle-citrate-btn');
|
|
39
|
+
|
|
40
|
+
const baseInput = document.getElementById('base-weight-input');
|
|
41
|
+
const bathInput = document.getElementById('bath-weight-input');
|
|
42
|
+
|
|
43
|
+
const baseAgentName = document.getElementById('base-agent-name-display');
|
|
44
|
+
const bathAgentName = document.getElementById('bath-agent-name-display');
|
|
45
|
+
const baseAgentVal = document.getElementById('base-agent-grams-display');
|
|
46
|
+
const bathAgentVal = document.getElementById('bath-agent-grams-display');
|
|
47
|
+
const xanthanVal = document.getElementById('xanthan-grams-display');
|
|
48
|
+
const citrateVal = document.getElementById('citrate-grams-display');
|
|
49
|
+
|
|
50
|
+
const xanthanRow = document.getElementById('xanthan-summary-item');
|
|
51
|
+
const citrateRow = document.getElementById('citrate-summary-item');
|
|
52
|
+
const solubilityWarning = document.getElementById('solubility-warning');
|
|
53
|
+
|
|
54
|
+
const beakerBath = document.getElementById('beaker-bath');
|
|
55
|
+
const sphereCore = document.getElementById('reactor-sphere-core');
|
|
56
|
+
const sphereMembrane = document.getElementById('reactor-sphere-membrane');
|
|
57
|
+
|
|
58
|
+
const methodBadge = document.getElementById('method-badge');
|
|
59
|
+
const methodBadgeText = document.getElementById('method-badge-text');
|
|
60
|
+
const methodDescText = document.getElementById('method-description-text');
|
|
61
|
+
|
|
62
|
+
let activeUnit = 'metric';
|
|
63
|
+
let activeMethod = 'direct';
|
|
64
|
+
let useXanthan = false;
|
|
65
|
+
let useCitrate = false;
|
|
66
|
+
|
|
67
|
+
function updateReactorUI() {
|
|
68
|
+
if (activeMethod === 'direct') {
|
|
69
|
+
beakerBath.setAttribute('fill', 'url(#bath-direct-grad)');
|
|
70
|
+
sphereCore.setAttribute('fill', 'url(#liquid-direct-grad)');
|
|
71
|
+
sphereMembrane.className.baseVal = 'sphere-gel-membrane direct';
|
|
72
|
+
methodBadge.className = 'method-badge direct';
|
|
73
|
+
methodBadgeText.textContent = ui.directMethod;
|
|
74
|
+
methodDescText.textContent = ui.directDesc;
|
|
75
|
+
} else {
|
|
76
|
+
beakerBath.setAttribute('fill', 'url(#bath-reverse-grad)');
|
|
77
|
+
sphereCore.setAttribute('fill', 'url(#liquid-reverse-grad)');
|
|
78
|
+
sphereMembrane.className.baseVal = 'sphere-gel-membrane reverse';
|
|
79
|
+
methodBadge.className = 'method-badge reverse';
|
|
80
|
+
methodBadgeText.textContent = ui.reverseMethod;
|
|
81
|
+
methodDescText.textContent = ui.reverseDesc;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function updateRecipeList(result, displayFactor) {
|
|
86
|
+
baseAgentName.textContent = activeMethod === 'direct' ? 'Sodium Alginate' : 'Calcium Lactate';
|
|
87
|
+
bathAgentName.textContent = activeMethod === 'direct' ? 'Calcium Chloride' : 'Sodium Alginate';
|
|
88
|
+
|
|
89
|
+
baseAgentVal.textContent = (result.baseAgentGrams * displayFactor).toFixed(2);
|
|
90
|
+
bathAgentVal.textContent = (result.bathAgentGrams * displayFactor).toFixed(2);
|
|
91
|
+
|
|
92
|
+
if (useXanthan) {
|
|
93
|
+
xanthanRow.classList.remove('hidden');
|
|
94
|
+
xanthanVal.textContent = (result.xanthanGrams * displayFactor).toFixed(2);
|
|
95
|
+
} else {
|
|
96
|
+
xanthanRow.classList.add('hidden');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (useCitrate) {
|
|
100
|
+
citrateRow.classList.remove('hidden');
|
|
101
|
+
citrateVal.textContent = (result.citrateGrams * displayFactor).toFixed(2);
|
|
102
|
+
} else {
|
|
103
|
+
citrateRow.classList.add('hidden');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (result.solubilityWarning) {
|
|
107
|
+
solubilityWarning.classList.remove('hidden');
|
|
108
|
+
} else {
|
|
109
|
+
solubilityWarning.classList.add('hidden');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function computeResult(baseWeight, bathWeight) {
|
|
114
|
+
const agentRatioBase = activeMethod === 'direct' ? 0.005 : 0.02;
|
|
115
|
+
const agentRatioBath = activeMethod === 'direct' ? 0.01 : 0.005;
|
|
116
|
+
const directWarning = activeMethod === 'direct' && baseWeight * 0.005 > 5;
|
|
117
|
+
const reverseWarning = activeMethod === 'reverse' && bathWeight * 0.005 > 10;
|
|
118
|
+
return {
|
|
119
|
+
baseAgentGrams: baseWeight * agentRatioBase,
|
|
120
|
+
bathAgentGrams: bathWeight * agentRatioBath,
|
|
121
|
+
xanthanGrams: useXanthan ? baseWeight * 0.002 : 0,
|
|
122
|
+
citrateGrams: useCitrate ? baseWeight * 0.005 : 0,
|
|
123
|
+
solubilityWarning: directWarning || reverseWarning,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function updateView() {
|
|
128
|
+
let baseWeight = Math.max(0, parseFloat(baseInput.value) || 0);
|
|
129
|
+
let bathWeight = Math.max(0, parseFloat(bathInput.value) || 0);
|
|
130
|
+
|
|
131
|
+
if (activeUnit === 'imperial') {
|
|
132
|
+
baseWeight = baseWeight / 0.035274;
|
|
133
|
+
bathWeight = bathWeight / 0.035274;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const result = computeResult(baseWeight, bathWeight);
|
|
137
|
+
const displayFactor = activeUnit === 'imperial' ? 0.035274 : 1.0;
|
|
138
|
+
updateRecipeList(result, displayFactor);
|
|
139
|
+
updateReactorUI();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function convertInputsTo(targetUnit) {
|
|
143
|
+
const currentBase = parseFloat(baseInput.value) || 0;
|
|
144
|
+
const currentBath = parseFloat(bathInput.value) || 0;
|
|
145
|
+
const labels = document.querySelectorAll('.weight-unit-label');
|
|
146
|
+
const recipeLabels = document.querySelectorAll('.recipe-unit-label');
|
|
147
|
+
|
|
148
|
+
if (targetUnit === 'imperial') {
|
|
149
|
+
baseInput.value = (currentBase * 0.035274).toFixed(1);
|
|
150
|
+
bathInput.value = (currentBath * 0.035274).toFixed(1);
|
|
151
|
+
labels.forEach(label => label.textContent = 'oz');
|
|
152
|
+
recipeLabels.forEach(label => label.textContent = 'oz');
|
|
153
|
+
} else {
|
|
154
|
+
baseInput.value = Math.round(currentBase / 0.035274).toString();
|
|
155
|
+
bathInput.value = Math.round(currentBath / 0.035274).toString();
|
|
156
|
+
labels.forEach(label => label.textContent = 'g');
|
|
157
|
+
recipeLabels.forEach(label => label.textContent = 'g');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
metricBtn.addEventListener('click', () => {
|
|
162
|
+
if (activeUnit === 'metric') return;
|
|
163
|
+
activeUnit = 'metric';
|
|
164
|
+
metricBtn.classList.add('active');
|
|
165
|
+
imperialBtn.classList.remove('active');
|
|
166
|
+
convertInputsTo('metric');
|
|
167
|
+
updateView();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
imperialBtn.addEventListener('click', () => {
|
|
171
|
+
if (activeUnit === 'imperial') return;
|
|
172
|
+
activeUnit = 'imperial';
|
|
173
|
+
imperialBtn.classList.add('active');
|
|
174
|
+
metricBtn.classList.remove('active');
|
|
175
|
+
convertInputsTo('imperial');
|
|
176
|
+
updateView();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
directBtn.addEventListener('click', () => {
|
|
180
|
+
if (activeMethod === 'direct') return;
|
|
181
|
+
activeMethod = 'direct';
|
|
182
|
+
directBtn.classList.add('active');
|
|
183
|
+
reverseBtn.classList.remove('active');
|
|
184
|
+
updateView();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
reverseBtn.addEventListener('click', () => {
|
|
188
|
+
if (activeMethod === 'reverse') return;
|
|
189
|
+
activeMethod = 'reverse';
|
|
190
|
+
reverseBtn.classList.add('active');
|
|
191
|
+
directBtn.classList.remove('active');
|
|
192
|
+
updateView();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
xanthanBtn.addEventListener('click', () => {
|
|
196
|
+
useXanthan = !useXanthan;
|
|
197
|
+
xanthanBtn.classList.toggle('active', useXanthan);
|
|
198
|
+
document.getElementById('xanthan-switch-circle').classList.toggle('active', useXanthan);
|
|
199
|
+
updateView();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
citrateBtn.addEventListener('click', () => {
|
|
203
|
+
useCitrate = !useCitrate;
|
|
204
|
+
citrateBtn.classList.toggle('active', useCitrate);
|
|
205
|
+
document.getElementById('citrate-switch-circle').classList.toggle('active', useCitrate);
|
|
206
|
+
updateView();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
baseInput.addEventListener('input', updateView);
|
|
210
|
+
bathInput.addEventListener('input', updateView);
|
|
211
|
+
|
|
212
|
+
updateView();
|
|
213
|
+
</script>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
ui: Record<string, string>;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const { ui } = Astro.props;
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<div class="spherification-controls">
|
|
10
|
+
<div class="controls-group">
|
|
11
|
+
<label class="controls-label">{ui.unitLabel}</label>
|
|
12
|
+
<div class="unit-toggle-container">
|
|
13
|
+
<button type="button" id="toggle-metric-btn" class="mode-btn active" data-unit="metric">
|
|
14
|
+
{ui.metricUnit}
|
|
15
|
+
</button>
|
|
16
|
+
<button type="button" id="toggle-imperial-btn" class="mode-btn" data-unit="imperial">
|
|
17
|
+
{ui.imperialUnit}
|
|
18
|
+
</button>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="controls-group">
|
|
23
|
+
<label class="controls-label">{ui.methodLabel}</label>
|
|
24
|
+
<div class="method-toggle-container">
|
|
25
|
+
<button type="button" id="toggle-direct-btn" class="mode-btn active" data-method="direct">
|
|
26
|
+
{ui.directMethod}
|
|
27
|
+
</button>
|
|
28
|
+
<button type="button" id="toggle-reverse-btn" class="mode-btn" data-method="reverse">
|
|
29
|
+
{ui.reverseMethod}
|
|
30
|
+
</button>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div class="controls-group">
|
|
35
|
+
<label for="base-weight-input" class="controls-label">
|
|
36
|
+
{ui.baseWeightLabel} (<span class="weight-unit-label">g</span>)
|
|
37
|
+
</label>
|
|
38
|
+
<input
|
|
39
|
+
type="number"
|
|
40
|
+
id="base-weight-input"
|
|
41
|
+
min="0"
|
|
42
|
+
max="100000"
|
|
43
|
+
step="any"
|
|
44
|
+
value="500"
|
|
45
|
+
class="controls-number-input"
|
|
46
|
+
inputmode="decimal"
|
|
47
|
+
/>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div class="controls-group">
|
|
51
|
+
<label for="bath-weight-input" class="controls-label">
|
|
52
|
+
{ui.bathWeightLabel} (<span class="weight-unit-label">g</span>)
|
|
53
|
+
</label>
|
|
54
|
+
<input
|
|
55
|
+
type="number"
|
|
56
|
+
id="bath-weight-input"
|
|
57
|
+
min="0"
|
|
58
|
+
max="100000"
|
|
59
|
+
step="any"
|
|
60
|
+
value="1000"
|
|
61
|
+
class="controls-number-input"
|
|
62
|
+
inputmode="decimal"
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div class="switches-grid">
|
|
67
|
+
<button type="button" id="toggle-xanthan-btn" class="switch-row-btn">
|
|
68
|
+
<div class="switch-row-info">
|
|
69
|
+
<span class="switch-row-title">{ui.xanthanLabel}</span>
|
|
70
|
+
</div>
|
|
71
|
+
<div class="toggle-switch">
|
|
72
|
+
<div id="xanthan-switch-circle" class="switch-circle"></div>
|
|
73
|
+
</div>
|
|
74
|
+
</button>
|
|
75
|
+
|
|
76
|
+
<button type="button" id="toggle-citrate-btn" class="switch-row-btn">
|
|
77
|
+
<div class="switch-row-info">
|
|
78
|
+
<span class="switch-row-title">{ui.citrateLabel}</span>
|
|
79
|
+
</div>
|
|
80
|
+
<div class="toggle-switch">
|
|
81
|
+
<div id="citrate-switch-circle" class="switch-circle"></div>
|
|
82
|
+
</div>
|
|
83
|
+
</button>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
ui: Record<string, string>;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const { ui } = Astro.props;
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<div class="recipe-summary-container">
|
|
10
|
+
<div class="summary-header">
|
|
11
|
+
<span class="summary-title">{ui.recipeTitle}</span>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div class="recipe-list">
|
|
15
|
+
<div class="recipe-item">
|
|
16
|
+
<div class="recipe-item-name">
|
|
17
|
+
<span id="base-agent-name-display">{ui.baseGellingAgent}</span>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="recipe-item-val">
|
|
20
|
+
<span id="base-agent-grams-display">2.50</span>
|
|
21
|
+
<span class="recipe-unit-label">g</span>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="recipe-item">
|
|
26
|
+
<div class="recipe-item-name">
|
|
27
|
+
<span id="bath-agent-name-display">{ui.bathGellingAgent}</span>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="recipe-item-val">
|
|
30
|
+
<span id="bath-agent-grams-display">10.00</span>
|
|
31
|
+
<span class="recipe-unit-label">g</span>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div id="xanthan-summary-item" class="recipe-item hidden">
|
|
36
|
+
<div class="recipe-item-name">
|
|
37
|
+
<span>{ui.xanthanGum}</span>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="recipe-item-val">
|
|
40
|
+
<span id="xanthan-grams-display">0.00</span>
|
|
41
|
+
<span class="recipe-unit-label">g</span>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div id="citrate-summary-item" class="recipe-item hidden">
|
|
46
|
+
<div class="recipe-item-name">
|
|
47
|
+
<span>{ui.sodiumCitrate}</span>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="recipe-item-val">
|
|
50
|
+
<span id="citrate-grams-display">0.00</span>
|
|
51
|
+
<span class="recipe-unit-label">g</span>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div id="solubility-warning" class="solubility-warning hidden">
|
|
57
|
+
<div class="warning-badge">{ui.warningLabel}</div>
|
|
58
|
+
<p class="warning-text">{ui.warningDesc}</p>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
ui: Record<string, string>;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const { ui } = Astro.props;
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<div class="reactor-section">
|
|
10
|
+
<div class="reactor-header">
|
|
11
|
+
<span class="reactor-title">{ui.methodLabel}</span>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div class="reactor-container">
|
|
15
|
+
<div class="beaker-vessel">
|
|
16
|
+
<svg viewBox="0 0 200 240" class="reactor-svg" xmlns="http://www.w3.org/2000/svg">
|
|
17
|
+
<defs>
|
|
18
|
+
<linearGradient id="beaker-glass-grad" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
19
|
+
<stop offset="0%" stop-color="rgba(255,255,255,0.4)"></stop>
|
|
20
|
+
<stop offset="10%" stop-color="rgba(255,255,255,0.1)"></stop>
|
|
21
|
+
<stop offset="90%" stop-color="rgba(255,255,255,0.1)"></stop>
|
|
22
|
+
<stop offset="100%" stop-color="rgba(255,255,255,0.4)"></stop>
|
|
23
|
+
</linearGradient>
|
|
24
|
+
|
|
25
|
+
<linearGradient id="liquid-direct-grad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
26
|
+
<stop offset="0%" stop-color="#fb923c"></stop>
|
|
27
|
+
<stop offset="100%" stop-color="#ea580c"></stop>
|
|
28
|
+
</linearGradient>
|
|
29
|
+
|
|
30
|
+
<linearGradient id="liquid-reverse-grad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
31
|
+
<stop offset="0%" stop-color="#f472b6"></stop>
|
|
32
|
+
<stop offset="100%" stop-color="#db2777"></stop>
|
|
33
|
+
</linearGradient>
|
|
34
|
+
|
|
35
|
+
<linearGradient id="bath-direct-grad" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
36
|
+
<stop offset="0%" stop-color="rgba(56,189,248,0.15)"></stop>
|
|
37
|
+
<stop offset="100%" stop-color="rgba(14,165,233,0.3)"></stop>
|
|
38
|
+
</linearGradient>
|
|
39
|
+
|
|
40
|
+
<linearGradient id="bath-reverse-grad" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
41
|
+
<stop offset="0%" stop-color="rgba(168,85,247,0.15)"></stop>
|
|
42
|
+
<stop offset="100%" stop-color="rgba(126,34,206,0.3)"></stop>
|
|
43
|
+
</linearGradient>
|
|
44
|
+
</defs>
|
|
45
|
+
|
|
46
|
+
<rect x="30" y="70" width="140" height="150" rx="15" id="beaker-bath" class="beaker-liquid" fill="url(#bath-direct-grad)"></rect>
|
|
47
|
+
|
|
48
|
+
<rect x="26" y="66" width="148" height="158" rx="18" fill="none" stroke="url(#beaker-glass-grad)" stroke-width="4"></rect>
|
|
49
|
+
<line x1="20" y1="66" x2="180" y2="66" stroke="url(#beaker-glass-grad)" stroke-width="4"></line>
|
|
50
|
+
|
|
51
|
+
<g class="nozzle-assembly">
|
|
52
|
+
<rect x="94" y="10" width="12" height="30" fill="#94a3b8" rx="2"></rect>
|
|
53
|
+
<path d="M96,40 L104,40 L102,52 L98,52 Z" fill="#64748b"></path>
|
|
54
|
+
<circle cx="100" cy="56" r="3" class="droplet"></circle>
|
|
55
|
+
</g>
|
|
56
|
+
|
|
57
|
+
<g id="sphere-chamber" class="sphere-chamber">
|
|
58
|
+
<circle cx="100" cy="140" r="35" id="reactor-sphere-core" class="sphere-fluid-core" fill="url(#liquid-direct-grad)"></circle>
|
|
59
|
+
<circle cx="100" cy="140" r="35" id="reactor-sphere-membrane" class="sphere-gel-membrane"></circle>
|
|
60
|
+
</g>
|
|
61
|
+
</svg>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div class="method-info-card">
|
|
66
|
+
<div id="method-badge" class="method-badge direct">
|
|
67
|
+
<span id="method-badge-text">{ui.directMethod}</span>
|
|
68
|
+
</div>
|
|
69
|
+
<p id="method-description-text" class="method-desc-text">
|
|
70
|
+
{ui.directDesc}
|
|
71
|
+
</p>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CookingToolEntry } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const spherificationBath: CookingToolEntry = {
|
|
4
|
+
id: 'spherification-bath-calculator',
|
|
5
|
+
icons: {
|
|
6
|
+
bg: 'mdi:flask-outline',
|
|
7
|
+
fg: 'mdi:dots-circle',
|
|
8
|
+
},
|
|
9
|
+
i18n: {
|
|
10
|
+
en: () => import('./i18n/en').then((m) => m.content),
|
|
11
|
+
de: () => import('./i18n/de').then((m) => m.content),
|
|
12
|
+
es: () => import('./i18n/es').then((m) => m.content),
|
|
13
|
+
fr: () => import('./i18n/fr').then((m) => m.content),
|
|
14
|
+
id: () => import('./i18n/id').then((m) => m.content),
|
|
15
|
+
it: () => import('./i18n/it').then((m) => m.content),
|
|
16
|
+
ja: () => import('./i18n/ja').then((m) => m.content),
|
|
17
|
+
ko: () => import('./i18n/ko').then((m) => m.content),
|
|
18
|
+
nl: () => import('./i18n/nl').then((m) => m.content),
|
|
19
|
+
pl: () => import('./i18n/pl').then((m) => m.content),
|
|
20
|
+
pt: () => import('./i18n/pt').then((m) => m.content),
|
|
21
|
+
ru: () => import('./i18n/ru').then((m) => m.content),
|
|
22
|
+
sv: () => import('./i18n/sv').then((m) => m.content),
|
|
23
|
+
tr: () => import('./i18n/tr').then((m) => m.content),
|
|
24
|
+
zh: () => import('./i18n/zh').then((m) => m.content),
|
|
25
|
+
},
|
|
26
|
+
};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
2
|
+
import { bibliography } from '../bibliography';
|
|
3
|
+
|
|
4
|
+
const title = "Spherification Bath Calculator Precision Molecular Gastronomy Guide";
|
|
5
|
+
const description = "Berechne exakte Verhaltnisse von Natriumalginat und Calciumlactat fur direkte und reverse Spharifikation. Korrigiere Viskositat und Aziditat mit Xanthan und Citrat.";
|
|
6
|
+
|
|
7
|
+
const faq = [
|
|
8
|
+
{
|
|
9
|
+
question: "Was ist der Unterschied zwischen direkter und reverser Spharifikation?",
|
|
10
|
+
answer: "Bei der direkten Spharifikation wird Natriumalginat zur aromatisierten Basis gegeben und in ein Calciumbad getropft, wodurch eine dunne Membran entsteht, die nach innen geliert. Bei der reversen Spharifikation wird Calcium zur Basis gegeben und in ein Alginatbad getropft, wodurch eine Membran nach auBen wachst und der Gelierprozess durch Abspulen gestoppt wird."
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
question: "Warum werden meine Kugeln flach oder sinken nicht?",
|
|
14
|
+
answer: "Wenn die aromatisierte Basisflussigkeit eine geringere Dichte als das Natriumalginatbad hat, schwimmt sie anstatt eine Kugel zu bilden. Durch Zugabe einer kleinen Menge Xanthan erhohst du die Viskositat der Basis, sodass sie sinkt und perfekte Kugeln bildet."
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
question: "Was bewirkt Natriumcitrat bei der Spharifikation?",
|
|
18
|
+
answer: "Natriumcitrat ist ein Sequestrierungsmittel, das Calciumionen bindet und den pH-Wert erhoht. Wenn eine Basisflussigkeit stark sauer ist (pH unter 4,5) oder Calcium enthalt, geliert das Natriumalginat vorzeitig. Die Zugabe von Citrat neutralisiert diese Saure."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
question: "Kann ich jedes beliebige Wasser fur das Calciumbad verwenden?",
|
|
22
|
+
answer: "Leitungswasser mit hohem Mineralgehalt kann dazu fuhren, dass Natriumalginat verklumpt oder vorzeitig geliert. Verwende am besten destilliertes Wasser oder calciumarmes Flaschenwasser."
|
|
23
|
+
}
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const howTo = [
|
|
27
|
+
{
|
|
28
|
+
name: "Spharifikationsmethode auswahlen",
|
|
29
|
+
text: "Wahle Direkt fur dunnflussige, sofortige Gelierung oder Revers fur Flussigkeiten mit Alkohol, Milchprodukten oder Calcium."
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: "Flussigkeitsmengen eingeben",
|
|
33
|
+
text: "Gib das Gewicht deiner aromatisierten Basisflussigkeit und des Wasserbads in deiner bevorzugten Einheit ein."
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: "Textur- und Saurekorrekturen hinzufugen",
|
|
37
|
+
text: "Aktiviere Xanthan, wenn deine Basisflussigkeit dunn ist, oder Natriumcitrat, wenn sie stark sauer ist."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "Zutaten abmessen",
|
|
41
|
+
text: "Wiege die genauen Mengen an Natriumalginat, Calciumlactat oder -chlorid sowie die Korrekturmittel ab, die in der Zusammenfassung angezeigt werden."
|
|
42
|
+
}
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const faqSchema = {
|
|
46
|
+
'@context': 'https://schema.org' as const,
|
|
47
|
+
'@type': 'FAQPage' as const,
|
|
48
|
+
mainEntity: faq.map((item) => ({
|
|
49
|
+
'@type': 'Question' as const,
|
|
50
|
+
name: item.question,
|
|
51
|
+
acceptedAnswer: { '@type': 'Answer' as const, text: item.answer },
|
|
52
|
+
})),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const howToSchema = {
|
|
56
|
+
'@context': 'https://schema.org' as const,
|
|
57
|
+
'@type': 'HowTo' as const,
|
|
58
|
+
name: title,
|
|
59
|
+
description,
|
|
60
|
+
step: howTo.map((step) => ({
|
|
61
|
+
'@type': 'HowToStep' as const,
|
|
62
|
+
name: step.name,
|
|
63
|
+
text: step.text,
|
|
64
|
+
})),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const appSchema = {
|
|
68
|
+
'@context': 'https://schema.org' as const,
|
|
69
|
+
'@type': 'SoftwareApplication' as const,
|
|
70
|
+
name: title,
|
|
71
|
+
description,
|
|
72
|
+
applicationCategory: 'UtilitiesApplication',
|
|
73
|
+
operatingSystem: 'Web',
|
|
74
|
+
offers: { '@type': 'Offer' as const, price: '0', priceCurrency: 'EUR' },
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const content: ToolLocaleContent = {
|
|
78
|
+
slug: 'spherifikationsbad-rechner',
|
|
79
|
+
title: 'Spherifikationsbad Rechner',
|
|
80
|
+
description: 'Berechne exakte Verhaltnisse von Natriumalginat und Calciumlactat fur direkte und reverse Spharifikation. Korrigiere Viskositat und Aziditat mit Xanthan und Citrat.',
|
|
81
|
+
faqTitle: 'Haufig gestellte Fragen',
|
|
82
|
+
ui: {
|
|
83
|
+
title: "Spherifikationsbad Rechner",
|
|
84
|
+
subtitle: "Prazise Verhaltnissteuerung fur direkte und reverse Spharifikation",
|
|
85
|
+
unitLabel: "Maßeinheit",
|
|
86
|
+
metricUnit: "Metrisch (g / Gramm)",
|
|
87
|
+
imperialUnit: "Imperial (oz / Unzen)",
|
|
88
|
+
methodLabel: "Spharifikationsmethode",
|
|
89
|
+
directMethod: "Direkte Spharifikation",
|
|
90
|
+
reverseMethod: "Reverse Spharifikation",
|
|
91
|
+
baseWeightLabel: "Gewicht der Basisflussigkeit",
|
|
92
|
+
bathWeightLabel: "Gewicht des Badewassers",
|
|
93
|
+
xanthanLabel: "Xanthan aktivieren (Viskositatskorrektur)",
|
|
94
|
+
citrateLabel: "Natriumcitrat aktivieren (Saure/pH-Sequestrierung)",
|
|
95
|
+
recipeTitle: "Berechnete Rezeptzusammenfassung",
|
|
96
|
+
baseGellingAgent: "Geliermittel fur die Basis",
|
|
97
|
+
bathGellingAgent: "Geliermittel fur das Bad",
|
|
98
|
+
xanthanGum: "Xanthan (Verdickungsmittel)",
|
|
99
|
+
sodiumCitrate: "Natriumcitrat (Puffer)",
|
|
100
|
+
warningLabel: "Loslichkeitswarnung",
|
|
101
|
+
warningDesc: "Das erforderliche Wirkstoffgewicht liegt nahe an oder uber der Loslichkeitsgrenze fur diese Flussigkeitsmenge. Das Auflosen kann langer dauern oder eine sanfte Erwarmung erfordern.",
|
|
102
|
+
directDesc: "Ideal fur leichte Flussigkeiten mit niedrigem Calciumgehalt. Erzeugt delikate, dunnhautige Kugeln, die sofort serviert werden mussen.",
|
|
103
|
+
reverseDesc: "Ideal fur Milchprodukte, Alkohol, calciumreiche oder saure Flussigkeiten. Erzeugt stabile Kugeln, die nach dem Abspulen nicht weiter gelieren.",
|
|
104
|
+
},
|
|
105
|
+
faq,
|
|
106
|
+
howTo,
|
|
107
|
+
seo: [
|
|
108
|
+
{
|
|
109
|
+
type: 'title',
|
|
110
|
+
text: 'Die Wissenschaft der molekularen Spharifikation und Hydrokolloidgelierung',
|
|
111
|
+
level: 2,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
type: 'paragraph',
|
|
115
|
+
html: 'Spharifikation ist eine revolutionare kulinarische Technik, die einen flussigen Kern in einer dunnen, gelierten Membran einschließt. Ursprunglich in den 1940er Jahren in der industriellen Verpackungsbranche entwickelt, wurde sie Anfang der 2000er Jahre fur die moderne Kuche adaptiert. Die zugrundeliegende Chemie beruht auf den Wechselwirkungen von Hydrokolloiden, insbesondere der Vernetzung von Natriumalginatpolymeren bei Kontakt mit divalenten Calciumkationen.',
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
type: 'title',
|
|
119
|
+
text: 'Die molekulare Chemie: Natriumalginat und Calciumionen',
|
|
120
|
+
level: 3,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
type: 'paragraph',
|
|
124
|
+
html: 'Natriumalginat ist ein aus Braunalgen gewonnenes Polysaccharid, das aus linearen Ketten von beta-D-Mannuronsaure (M-Blocke) und alpha-L-Guluronsaure (G-Blocke) besteht. In seiner Natriumsalzform ist es vollstandig wasserloslich und bildet eine viskose Losung. Wenn Calciumionen (wie Calciumchlorid oder Calciumlactatgluconat) zugefugt werden, ersetzen die zweiwertigen Calciumionen (Ca2+) die einwertigen Natriumionen (Na+). Da Calcium zwei positive Ladungen tragt, bindet es an zwei G-Blocke benachbarter Polymerketten und verbruckt sie miteinander. Dieser Prozess, wissenschaftlich als <strong>Eierkartonmodell</strong> der Gelierung bezeichnet, verbindet die unabhangigen Polysaccharidketten zu einem starren, dreidimensionalen Hydrogelnetzwerk, das Wasser und Aromamolekule einschließt.',
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
type: 'title',
|
|
128
|
+
text: 'Direkte versus reverse Spharifikation: Mechanismen im Vergleich',
|
|
129
|
+
level: 3,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
type: 'paragraph',
|
|
133
|
+
html: 'Die beiden Hauptmethoden der Spharifikation unterscheiden sich darin, wo Geliermittel und Calciumsalze platziert werden, was zu unterschiedlichen mechanischen Eigenschaften fuhrt:',
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
type: 'list',
|
|
137
|
+
items: [
|
|
138
|
+
'<strong>Direkte Spharifikation:</strong> Natriumalginat wird in der aromatisierten Basisflussigkeit gelost, die dann in ein Calciumbad (ublicherweise 1,0% Calciumchlorid) getropft wird. Die Gelierung beginnt sofort an der Grenzflache. Da Calciumionen klein und mobil sind, wandern sie kontinuierlich vom Bad in den Kern der Kugel, wodurch die Gelmembran nach innen wachst. Wird die Kugel nicht sofort abgespult und serviert, geliert sie schließlich vollstandig durch und verwandelt sich in eine feste, gummartige Perle.',
|
|
139
|
+
'<strong>Reverse Spharifikation:</strong> Calciumlactatgluconat (2,0%) wird in der aromatisierten Basisflussigkeit gelost, die dann in ein Natriumalginatbad (0,5%) getropft wird. Da Alginatmolekule groB und langsam sind, konnen sie die neu gebildete Gelbarriere nicht leicht uberqueren. Stattdessen wandern Calciumionen nach außen in das Bad und lassen die Membran nach außen wachsen. Dies stoppt die Gelierung sofort, sobald die Kugel entnommen und in klarem Wasser abgespult wird, und erhalt einen vollstandig flussigen Kern auf unbestimmte Zeit.'
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
type: 'title',
|
|
144
|
+
text: 'Saure- und pH-Barrieren mit Natriumcitrat uberwinden',
|
|
145
|
+
level: 3,
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
type: 'paragraph',
|
|
149
|
+
html: 'Natriumalginat reagiert stark empfindlich auf Saure. Wenn der pH-Wert einer aromatisierten Basisflussigkeit unter 4,5 fallt, konnen sich die Alginatmolekule nicht richtig hydratisieren. Statt sich aufzulosen, fallen die Alginatketten als unlosliche Alginsaure aus und bilden fadenformige Klumpen. Um dies zu losen, verwenden Koche <strong>Natriumcitrat</strong> als Puffermittel. Natriumcitrat neutralisiert die Wasserstoffionen und erhoht den pH-Wert von sauren Zutaten wie Passionsfrucht- oder Limettensaft uber die kritische Schwelle von 4,5, sodass sich das Alginat vollstandig hydratisieren und saubere, kugelformige Gebilde formen kann.',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
type: 'title',
|
|
153
|
+
text: 'Anpassung von Dichte und Viskositat mit Xanthan',
|
|
154
|
+
level: 3,
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
type: 'paragraph',
|
|
158
|
+
html: 'Um eine Kugel zu formen, muss der Tropfen der aromatisierten Basisflussigkeit vollstandig im Gelierbad eintauchen. Wenn die aromatisierte Basisflussigkeit eine geringere Dichte als das Bad hat (wie ein leichter Alkohol oder ein saft auf Wasserbasis in einem dicken Natriumalginatbad bei der reversen Spharifikation), schwimmt sie an der Oberflache und flacht ab. Die Zugabe einer winzigen Menge <strong>Xanthan</strong> (typischerweise 0,1% bis 0,2%) erhoht die Viskositat der Basisflussigkeit. Diese zusatzliche Korperhaftigkeit verleiht dem Tropfen den nötigen Schwung, um in das Bad einzutauchen, sodass die Oberflachenspannung den Tropfen zu einer perfekten Kugel formen kann.',
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
type: 'table',
|
|
162
|
+
headers: ['Spharifikationsmethode', 'Ideale Zutaten', 'Erforderliche Zusatze', 'Lagereigenschaften', 'Gelierrichtung'],
|
|
163
|
+
rows: [
|
|
164
|
+
['Direkte Methode', 'Calciumarme Fruchtsafte, klare Bruhen, suße Sirupe', '0,5% Natriumalginat in der Basis, 1,0% Calciumchlorid im Bad', 'Muss sofort serviert werden, geliert mit der Zeit durch', 'Nach innen (zum Zentrum hin)'],
|
|
165
|
+
['Reverse Methode', 'Milchprodukte, Alkohol, calciumreiche oder stark saure Flussigkeiten', '2,0% Calciumlactat in der Basis, 0,5% Natriumalginat im Bad', 'Sehr stabil, kann stundenlang in Ol oder Wasser aufbewahrt werden', 'Nach außen (vom Zentrum weg)'],
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
type: 'title',
|
|
170
|
+
text: 'Abspulen und abschließende Konservierung',
|
|
171
|
+
level: 3,
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
type: 'paragraph',
|
|
175
|
+
html: 'Spule die fertigen Kugeln sofort nach der Entnahme aus dem Gelierbad in einem Bad mit sauberem, kaltem Wasser ab. Dies entfernt anhaftende Calcium- oder Alginatreste von der Außenhaut, stoppt die chemische Reaktion und verhindert Fehlgeschmack (insbesondere den leicht bitteren Geschmack von Calciumchlorid). Zur Aufbewahrung kannst du die Kugeln in eine Flussigkeit mit passender Dichte legen (wie die aromatisierte Basisflussigkeit ohne Zusatze oder einen leichten Zuckersirup), um zu verhindern, dass Wasser durch Osmose durch die Membran wandert und die Kugeln schrumpfen oder platzen.',
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
bibliography,
|
|
179
|
+
schemas: [faqSchema, howToSchema, appSchema],
|
|
180
|
+
};
|