@jjlmoya/utils-cooking 1.27.0 → 1.29.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 +4 -0
- package/src/entries.ts +5 -1
- package/src/index.ts +2 -0
- package/src/tests/i18n-titles.test.ts +4 -2
- package/src/tests/lacto-fermentation-salt-calculator.test.ts +64 -0
- package/src/tests/locale_completeness.test.ts +2 -2
- package/src/tests/spherification-bath-calculator.test.ts +57 -0
- package/src/tests/tool_validation.test.ts +2 -2
- package/src/tool/lacto-fermentation-salt-calculator/bibliography.astro +6 -0
- package/src/tool/lacto-fermentation-salt-calculator/bibliography.ts +10 -0
- package/src/tool/lacto-fermentation-salt-calculator/component.astro +163 -0
- package/src/tool/lacto-fermentation-salt-calculator/components/DigitalScale.astro +43 -0
- package/src/tool/lacto-fermentation-salt-calculator/components/PetriDish.astro +55 -0
- package/src/tool/lacto-fermentation-salt-calculator/components/WizardMode.astro +83 -0
- package/src/tool/lacto-fermentation-salt-calculator/entry.ts +26 -0
- package/src/tool/lacto-fermentation-salt-calculator/i18n/de.ts +195 -0
- package/src/tool/lacto-fermentation-salt-calculator/i18n/en.ts +195 -0
- package/src/tool/lacto-fermentation-salt-calculator/i18n/es.ts +195 -0
- package/src/tool/lacto-fermentation-salt-calculator/i18n/fr.ts +195 -0
- package/src/tool/lacto-fermentation-salt-calculator/i18n/id.ts +195 -0
- package/src/tool/lacto-fermentation-salt-calculator/i18n/it.ts +195 -0
- package/src/tool/lacto-fermentation-salt-calculator/i18n/ja.ts +195 -0
- package/src/tool/lacto-fermentation-salt-calculator/i18n/ko.ts +195 -0
- package/src/tool/lacto-fermentation-salt-calculator/i18n/nl.ts +195 -0
- package/src/tool/lacto-fermentation-salt-calculator/i18n/pl.ts +195 -0
- package/src/tool/lacto-fermentation-salt-calculator/i18n/pt.ts +195 -0
- package/src/tool/lacto-fermentation-salt-calculator/i18n/ru.ts +195 -0
- package/src/tool/lacto-fermentation-salt-calculator/i18n/sv.ts +195 -0
- package/src/tool/lacto-fermentation-salt-calculator/i18n/tr.ts +195 -0
- package/src/tool/lacto-fermentation-salt-calculator/i18n/zh.ts +195 -0
- package/src/tool/lacto-fermentation-salt-calculator/index.ts +11 -0
- package/src/tool/lacto-fermentation-salt-calculator/lacto-fermentation-salt-calculator.css +692 -0
- package/src/tool/lacto-fermentation-salt-calculator/logic.ts +37 -0
- package/src/tool/lacto-fermentation-salt-calculator/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 +4 -2
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { lactoFermentationSalt } from './entry';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: KnownLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { locale = 'en' } = Astro.props;
|
|
11
|
+
const content = await lactoFermentationSalt.i18n[locale]?.();
|
|
12
|
+
if (!content) return null;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
{content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const bibliography = [
|
|
2
|
+
{
|
|
3
|
+
name: 'Modernist Cuisine: The Art and Science of Cooking (Nathan Myhrvold)',
|
|
4
|
+
url: 'https://modernistcuisine.com/books/modernist-cuisine/',
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
name: 'Science and Cooking: Physics Meets Food (Michael Brenner et al.)',
|
|
8
|
+
url: 'https://www.scribd.com/document/522544303/Science-and-Cooking-Michael-Brenner',
|
|
9
|
+
},
|
|
10
|
+
];
|
|
@@ -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
|
+
};
|