@jjlmoya/utils-cooking 1.29.0 → 1.31.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/i18n_coverage.test.ts +1 -0
- package/src/tests/ice-cream-pac-pod.test.ts +68 -0
- package/src/tests/locale_completeness.test.ts +3 -2
- package/src/tests/tool_validation.test.ts +3 -2
- package/src/tool/botulism-canning-safety/bibliography.astro +6 -0
- package/src/tool/botulism-canning-safety/bibliography.ts +10 -0
- package/src/tool/botulism-canning-safety/botulism-canning-safety.css +545 -0
- package/src/tool/botulism-canning-safety/component.astro +296 -0
- package/src/tool/botulism-canning-safety/components/AutoclaveDisplay.astro +62 -0
- package/src/tool/botulism-canning-safety/components/SporeVisualizer.astro +48 -0
- package/src/tool/botulism-canning-safety/components/ThermalControls.astro +60 -0
- package/src/tool/botulism-canning-safety/entry.ts +26 -0
- package/src/tool/botulism-canning-safety/i18n/de.ts +188 -0
- package/src/tool/botulism-canning-safety/i18n/en.ts +188 -0
- package/src/tool/botulism-canning-safety/i18n/es.ts +188 -0
- package/src/tool/botulism-canning-safety/i18n/fr.ts +188 -0
- package/src/tool/botulism-canning-safety/i18n/id.ts +188 -0
- package/src/tool/botulism-canning-safety/i18n/it.ts +188 -0
- package/src/tool/botulism-canning-safety/i18n/ja.ts +188 -0
- package/src/tool/botulism-canning-safety/i18n/ko.ts +188 -0
- package/src/tool/botulism-canning-safety/i18n/nl.ts +188 -0
- package/src/tool/botulism-canning-safety/i18n/pl.ts +188 -0
- package/src/tool/botulism-canning-safety/i18n/pt.ts +188 -0
- package/src/tool/botulism-canning-safety/i18n/ru.ts +188 -0
- package/src/tool/botulism-canning-safety/i18n/sv.ts +188 -0
- package/src/tool/botulism-canning-safety/i18n/tr.ts +188 -0
- package/src/tool/botulism-canning-safety/i18n/zh.ts +188 -0
- package/src/tool/botulism-canning-safety/index.ts +11 -0
- package/src/tool/botulism-canning-safety/logic.ts +47 -0
- package/src/tool/botulism-canning-safety/seo.astro +15 -0
- 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 +291 -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/component.astro +37 -0
- package/src/tools.ts +5 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
---
|
|
2
|
+
import SugarFormulators from './components/SugarFormulators.astro';
|
|
3
|
+
import IceCryoGauge from './components/IceCryoGauge.astro';
|
|
4
|
+
import ScoopVisualizer from './components/ScoopVisualizer.astro';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
ui: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { ui } = Astro.props;
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<div class="ice-cream-container">
|
|
14
|
+
<div class="cryo-card">
|
|
15
|
+
<div class="cryo-grid">
|
|
16
|
+
|
|
17
|
+
<div class="cryo-inputs-column">
|
|
18
|
+
<SugarFormulators ui={ui} />
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div class="cryo-visuals-column">
|
|
22
|
+
<IceCryoGauge ui={ui} />
|
|
23
|
+
<ScoopVisualizer ui={ui} />
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<script is:inline define:vars={{ ui }}>
|
|
30
|
+
const metricBtn = document.getElementById('toggle-metric-btn');
|
|
31
|
+
const imperialBtn = document.getElementById('toggle-imperial-btn');
|
|
32
|
+
|
|
33
|
+
const baseInput = document.getElementById('base-weight-input');
|
|
34
|
+
const baseSlider = document.getElementById('base-weight-slider');
|
|
35
|
+
const tempInput = document.getElementById('target-temp-input');
|
|
36
|
+
const tempSlider = document.getElementById('target-temp-slider');
|
|
37
|
+
|
|
38
|
+
const sucroseInput = document.getElementById('sucrose-input');
|
|
39
|
+
const sucroseSlider = document.getElementById('sucrose-slider');
|
|
40
|
+
const dextroseInput = document.getElementById('dextrose-input');
|
|
41
|
+
const dextroseSlider = document.getElementById('dextrose-slider');
|
|
42
|
+
const glucoseInput = document.getElementById('glucose-input');
|
|
43
|
+
const glucoseSlider = document.getElementById('glucose-slider');
|
|
44
|
+
const invertedInput = document.getElementById('inverted-input');
|
|
45
|
+
const invertedSlider = document.getElementById('inverted-slider');
|
|
46
|
+
const trehaloseInput = document.getElementById('trehalose-input');
|
|
47
|
+
const trehaloseSlider = document.getElementById('trehalose-slider');
|
|
48
|
+
|
|
49
|
+
const targetPacDisplay = document.getElementById('target-pac-display');
|
|
50
|
+
const pacValueDisplay = document.getElementById('pac-value-display');
|
|
51
|
+
const podValueDisplay = document.getElementById('pod-value-display');
|
|
52
|
+
const solidsValueDisplay = document.getElementById('solids-value-display');
|
|
53
|
+
|
|
54
|
+
const pacFill = document.getElementById('pac-popsicle-fill');
|
|
55
|
+
const podFill = document.getElementById('pod-popsicle-fill');
|
|
56
|
+
const solidsFill = document.getElementById('solids-popsicle-fill');
|
|
57
|
+
|
|
58
|
+
const texStone = document.getElementById('texture-stone');
|
|
59
|
+
const texCreamy = document.getElementById('texture-creamy');
|
|
60
|
+
const texSoup = document.getElementById('texture-soup');
|
|
61
|
+
const statusGlow = document.getElementById('status-glow');
|
|
62
|
+
|
|
63
|
+
const statusBadge = document.getElementById('status-badge');
|
|
64
|
+
const statusBadgeText = document.getElementById('status-badge-text');
|
|
65
|
+
const statusDescText = document.getElementById('status-description-text');
|
|
66
|
+
|
|
67
|
+
let activeUnit = 'metric';
|
|
68
|
+
|
|
69
|
+
function bindInputSlider(input, slider, min, max) {
|
|
70
|
+
input.addEventListener('input', () => {
|
|
71
|
+
let val = parseFloat(input.value) || 0;
|
|
72
|
+
if (val < min) val = min;
|
|
73
|
+
if (val > max) val = max;
|
|
74
|
+
slider.value = val;
|
|
75
|
+
updateView();
|
|
76
|
+
});
|
|
77
|
+
slider.addEventListener('input', () => {
|
|
78
|
+
input.value = slider.value;
|
|
79
|
+
updateView();
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
bindInputSlider(baseInput, baseSlider, 100, 5000);
|
|
84
|
+
bindInputSlider(tempInput, tempSlider, -25, -5);
|
|
85
|
+
bindInputSlider(sucroseInput, sucroseSlider, 0, 500);
|
|
86
|
+
bindInputSlider(dextroseInput, dextroseSlider, 0, 300);
|
|
87
|
+
bindInputSlider(glucoseInput, glucoseSlider, 0, 300);
|
|
88
|
+
bindInputSlider(invertedInput, invertedSlider, 0, 300);
|
|
89
|
+
bindInputSlider(trehaloseInput, trehaloseSlider, 0, 300);
|
|
90
|
+
|
|
91
|
+
function toGrams(val) {
|
|
92
|
+
return activeUnit === 'imperial' ? val / 0.035274 : val;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function toCelsius(val) {
|
|
96
|
+
return activeUnit === 'imperial' ? (val - 32) * 5 / 9 : val;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function setStateUI(state) {
|
|
100
|
+
const isStone = state === 'stone';
|
|
101
|
+
const isSoup = state === 'soup';
|
|
102
|
+
const isCreamy = state === 'creamy';
|
|
103
|
+
|
|
104
|
+
texStone.classList.toggle('hidden', !isStone);
|
|
105
|
+
texCreamy.classList.toggle('hidden', !isCreamy);
|
|
106
|
+
texSoup.classList.toggle('hidden', !isSoup);
|
|
107
|
+
|
|
108
|
+
if (isStone) {
|
|
109
|
+
statusGlow.className = 'bowl-status-glow glow-stone';
|
|
110
|
+
statusBadge.className = 'status-badge danger';
|
|
111
|
+
statusBadgeText.textContent = ui.stoneState;
|
|
112
|
+
statusDescText.textContent = ui.stoneDesc;
|
|
113
|
+
} else if (isSoup) {
|
|
114
|
+
statusGlow.className = 'bowl-status-glow glow-soup';
|
|
115
|
+
statusBadge.className = 'status-badge inhibited';
|
|
116
|
+
statusBadgeText.textContent = ui.soupState;
|
|
117
|
+
statusDescText.textContent = ui.soupDesc;
|
|
118
|
+
} else {
|
|
119
|
+
statusGlow.className = 'bowl-status-glow glow-creamy';
|
|
120
|
+
statusBadge.className = 'status-badge optimal';
|
|
121
|
+
statusBadgeText.textContent = ui.creamyState;
|
|
122
|
+
statusDescText.textContent = ui.creamyDesc;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function setGauges(totalPAC, totalPOD, solidsPercentage) {
|
|
127
|
+
pacFill.style.height = Math.min(100, (totalPAC / 400) * 100) + '%';
|
|
128
|
+
podFill.style.height = Math.min(100, (totalPOD / 40) * 100) + '%';
|
|
129
|
+
solidsFill.style.height = Math.min(100, solidsPercentage) + '%';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function updateUI(state, values) {
|
|
133
|
+
targetPacDisplay.textContent = values.targetPAC.toString();
|
|
134
|
+
pacValueDisplay.textContent = values.totalPAC.toFixed(0);
|
|
135
|
+
podValueDisplay.textContent = values.totalPOD.toFixed(0);
|
|
136
|
+
solidsValueDisplay.textContent = values.solidsPercentage.toFixed(0) + '%';
|
|
137
|
+
setGauges(values.totalPAC, values.totalPOD, values.solidsPercentage);
|
|
138
|
+
setStateUI(state);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function computeState(totalPAC, targetPAC) {
|
|
142
|
+
const tolerance = 40;
|
|
143
|
+
if (totalPAC < targetPAC - tolerance) return 'stone';
|
|
144
|
+
if (totalPAC > targetPAC + tolerance) return 'soup';
|
|
145
|
+
return 'creamy';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function readValues() {
|
|
149
|
+
return {
|
|
150
|
+
base: parseFloat(baseInput.value) || 0,
|
|
151
|
+
temp: parseFloat(tempInput.value) || 0,
|
|
152
|
+
sucrose: parseFloat(sucroseInput.value) || 0,
|
|
153
|
+
dextrose: parseFloat(dextroseInput.value) || 0,
|
|
154
|
+
glucose: parseFloat(glucoseInput.value) || 0,
|
|
155
|
+
inverted: parseFloat(invertedInput.value) || 0,
|
|
156
|
+
trehalose: parseFloat(trehaloseInput.value) || 0,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function computeMetrics(v) {
|
|
161
|
+
const bg = toGrams(v.base);
|
|
162
|
+
const sg = toGrams(v.sucrose);
|
|
163
|
+
const dg = toGrams(v.dextrose);
|
|
164
|
+
const gg = toGrams(v.glucose);
|
|
165
|
+
const ig = toGrams(v.inverted);
|
|
166
|
+
const tg = toGrams(v.trehalose);
|
|
167
|
+
|
|
168
|
+
const waterWeight = bg * 0.7 + ig * 0.3;
|
|
169
|
+
const otherSolids = bg * 0.3;
|
|
170
|
+
const totalSolids = otherSolids + sg + dg + gg * 0.95 + ig * 0.7 + tg * 0.9;
|
|
171
|
+
const totalMixWeight = bg + sg + dg + gg + ig + tg;
|
|
172
|
+
const pacContribution = sg * 1.0 + dg * 1.9 + gg * 0.9 + ig * 1.9 + tg * 1.0;
|
|
173
|
+
const podContribution = sg * 1.0 + dg * 0.7 + gg * 0.4 + ig * 1.3 + tg * 0.45;
|
|
174
|
+
|
|
175
|
+
const totalPAC = waterWeight > 0 ? parseFloat(((pacContribution / waterWeight) * 1000).toFixed(1)) : 0;
|
|
176
|
+
const totalPOD = totalMixWeight > 0 ? parseFloat(((podContribution / totalMixWeight) * 100).toFixed(1)) : 0;
|
|
177
|
+
const solidsPct = totalMixWeight > 0 ? parseFloat(((totalSolids / totalMixWeight) * 100).toFixed(1)) : 0;
|
|
178
|
+
const targetPAC = Math.round(18 * Math.abs(toCelsius(v.temp)));
|
|
179
|
+
|
|
180
|
+
return { totalPAC, totalPOD, solidsPercentage: solidsPct, targetPAC };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const STORAGE_KEY = 'cooking-ice-cream-state';
|
|
184
|
+
|
|
185
|
+
function saveState() {
|
|
186
|
+
const state = {
|
|
187
|
+
activeUnit,
|
|
188
|
+
base: baseInput.value,
|
|
189
|
+
temp: tempInput.value,
|
|
190
|
+
sucrose: sucroseInput.value,
|
|
191
|
+
dextrose: dextroseInput.value,
|
|
192
|
+
glucose: glucoseInput.value,
|
|
193
|
+
inverted: invertedInput.value,
|
|
194
|
+
trehalose: trehaloseInput.value
|
|
195
|
+
};
|
|
196
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function loadState() {
|
|
200
|
+
try {
|
|
201
|
+
const state = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
|
|
202
|
+
if (state.activeUnit) {
|
|
203
|
+
activeUnit = state.activeUnit;
|
|
204
|
+
imperialBtn.classList.toggle('active', state.activeUnit === 'imperial');
|
|
205
|
+
metricBtn.classList.toggle('active', state.activeUnit === 'metric');
|
|
206
|
+
if (state.activeUnit === 'imperial') {
|
|
207
|
+
document.querySelectorAll('.weight-unit-label').forEach(l => l.textContent = ui.ozLabel);
|
|
208
|
+
document.querySelectorAll('.temp-unit-label').forEach(t => t.textContent = ui.fLabel);
|
|
209
|
+
tempSlider.min = '-13'; tempSlider.max = '23';
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const sliderPairs = [['base', baseInput, baseSlider], ['temp', tempInput, tempSlider],
|
|
213
|
+
['sucrose', sucroseInput, sucroseSlider], ['dextrose', dextroseInput, dextroseSlider],
|
|
214
|
+
['glucose', glucoseInput, glucoseSlider], ['inverted', invertedInput, invertedSlider],
|
|
215
|
+
['trehalose', trehaloseInput, trehaloseSlider]];
|
|
216
|
+
sliderPairs.forEach(([key, input, slider]) => { if (state[key]) { input.value = state[key]; slider.value = state[key]; } });
|
|
217
|
+
} catch {}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function updateView() {
|
|
221
|
+
const v = readValues();
|
|
222
|
+
const metrics = computeMetrics(v);
|
|
223
|
+
const state = computeState(metrics.totalPAC, metrics.targetPAC);
|
|
224
|
+
updateUI(state, metrics);
|
|
225
|
+
saveState();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function convertInputs(targetUnit, inputs, sliders) {
|
|
229
|
+
inputs.forEach((input, index) => {
|
|
230
|
+
const val = parseFloat(input.value) || 0;
|
|
231
|
+
if (targetUnit === 'imperial') {
|
|
232
|
+
input.value = (val * 0.035274).toFixed(2);
|
|
233
|
+
sliders[index].max = (parseFloat(sliders[index].max) * 0.035274).toFixed(2);
|
|
234
|
+
} else {
|
|
235
|
+
input.value = Math.round(val / 0.035274).toString();
|
|
236
|
+
sliders[index].max = Math.round(parseFloat(sliders[index].max) / 0.035274).toString();
|
|
237
|
+
}
|
|
238
|
+
sliders[index].value = input.value;
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function convertTemp(targetUnit) {
|
|
243
|
+
const tempVal = parseFloat(tempInput.value) || 0;
|
|
244
|
+
if (targetUnit === 'imperial') {
|
|
245
|
+
tempInput.value = Math.round((tempVal * 9 / 5) + 32).toString();
|
|
246
|
+
tempSlider.min = '-13';
|
|
247
|
+
tempSlider.max = '23';
|
|
248
|
+
} else {
|
|
249
|
+
tempInput.value = Math.round((tempVal - 32) * 5 / 9).toString();
|
|
250
|
+
tempSlider.min = '-25';
|
|
251
|
+
tempSlider.max = '-5';
|
|
252
|
+
}
|
|
253
|
+
tempSlider.value = tempInput.value;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function convertUnitsTo(targetUnit) {
|
|
257
|
+
const labels = document.querySelectorAll('.weight-unit-label');
|
|
258
|
+
const tempLabels = document.querySelectorAll('.temp-unit-label');
|
|
259
|
+
|
|
260
|
+
const inputs = [baseInput, sucroseInput, dextroseInput, glucoseInput, invertedInput, trehaloseInput];
|
|
261
|
+
const sliders = [baseSlider, sucroseSlider, dextroseSlider, glucoseSlider, invertedSlider, trehaloseSlider];
|
|
262
|
+
|
|
263
|
+
convertInputs(targetUnit, inputs, sliders);
|
|
264
|
+
convertTemp(targetUnit);
|
|
265
|
+
|
|
266
|
+
labels.forEach(l => l.textContent = targetUnit === 'imperial' ? ui.ozLabel : ui.gLabel);
|
|
267
|
+
tempLabels.forEach(t => t.textContent = targetUnit === 'imperial' ? ui.fLabel : ui.cLabel);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
metricBtn.addEventListener('click', () => {
|
|
271
|
+
if (activeUnit === 'metric') return;
|
|
272
|
+
activeUnit = 'metric';
|
|
273
|
+
metricBtn.classList.add('active');
|
|
274
|
+
imperialBtn.classList.remove('active');
|
|
275
|
+
convertUnitsTo('metric');
|
|
276
|
+
updateView();
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
imperialBtn.addEventListener('click', () => {
|
|
280
|
+
if (activeUnit === 'imperial') return;
|
|
281
|
+
activeUnit = 'imperial';
|
|
282
|
+
imperialBtn.classList.add('active');
|
|
283
|
+
metricBtn.classList.remove('active');
|
|
284
|
+
convertUnitsTo('imperial');
|
|
285
|
+
updateView();
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
loadState();
|
|
289
|
+
updateView();
|
|
290
|
+
</script>
|
|
291
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
ui: Record<string, string>;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const { ui } = Astro.props;
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<div class="popsicle-indicators-grid">
|
|
10
|
+
<div class="popsicle-bar pac-bar">
|
|
11
|
+
<div class="popsicle-label-top">{ui.pacLabel}</div>
|
|
12
|
+
<div class="popsicle-body-wrapper">
|
|
13
|
+
<div class="popsicle-outline">
|
|
14
|
+
<div id="pac-popsicle-fill" class="popsicle-fill pac-fill-color"></div>
|
|
15
|
+
<div class="popsicle-number-overlay">
|
|
16
|
+
<span id="pac-value-display">0</span>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="popsicle-stick"></div>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="popsicle-target-display">
|
|
22
|
+
<span>Target:</span>
|
|
23
|
+
<span id="target-pac-display">0</span>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div class="popsicle-bar pod-bar">
|
|
28
|
+
<div class="popsicle-label-top">{ui.podLabel}</div>
|
|
29
|
+
<div class="popsicle-body-wrapper">
|
|
30
|
+
<div class="popsicle-outline">
|
|
31
|
+
<div id="pod-popsicle-fill" class="popsicle-fill pod-fill-color"></div>
|
|
32
|
+
<div class="popsicle-number-overlay">
|
|
33
|
+
<span id="pod-value-display">0</span>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="popsicle-stick"></div>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="popsicle-target-display"> </div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div class="popsicle-bar solids-bar">
|
|
42
|
+
<div class="popsicle-label-top">{ui.solidsLabel}</div>
|
|
43
|
+
<div class="popsicle-body-wrapper">
|
|
44
|
+
<div class="popsicle-outline">
|
|
45
|
+
<div id="solids-popsicle-fill" class="popsicle-fill solids-fill-color"></div>
|
|
46
|
+
<div class="popsicle-number-overlay">
|
|
47
|
+
<span id="solids-value-display">0%</span>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
<div class="popsicle-stick"></div>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="popsicle-target-display"> </div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
ui: Record<string, string>;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const { ui } = Astro.props;
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<div class="scoop-visualizer-container">
|
|
10
|
+
<h3 class="visualizer-header">{ui.scoopabilityLabel}</h3>
|
|
11
|
+
|
|
12
|
+
<div class="sundae-bowl-display">
|
|
13
|
+
<div class="bowl-inner">
|
|
14
|
+
<div id="texture-stone" class="texture-state hidden">
|
|
15
|
+
<svg viewBox="0 0 100 100" class="scoop-svg">
|
|
16
|
+
<path d="M25,80 L75,80 L80,95 L20,95 Z" class="bowl-base"></path>
|
|
17
|
+
<path d="M15,80 L85,80 L75,50 L25,50 Z" class="bowl-glass"></path>
|
|
18
|
+
<polygon points="35,65 65,65 70,30 30,30" class="sundae-ice-block"></polygon>
|
|
19
|
+
<line x1="45" y1="40" x2="45" y2="55" class="sundae-crack"></line>
|
|
20
|
+
<line x1="55" y1="35" x2="55" y2="50" class="sundae-crack"></line>
|
|
21
|
+
</svg>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div id="texture-creamy" class="texture-state">
|
|
25
|
+
<svg viewBox="0 0 100 100" class="scoop-svg">
|
|
26
|
+
<path d="M25,80 L75,80 L80,95 L20,95 Z" class="bowl-base"></path>
|
|
27
|
+
<path d="M15,80 L85,80 L75,50 L25,50 Z" class="bowl-glass"></path>
|
|
28
|
+
<path d="M30,60 Q20,30 50,20 Q80,30 70,60 Z" class="sundae-swirl-body"></path>
|
|
29
|
+
<path d="M38,48 Q50,32 62,48 C55,42 45,42 38,48 Z" class="sundae-swirl-shade"></path>
|
|
30
|
+
<circle cx="50" cy="15" r="5" class="sundae-cherry"></circle>
|
|
31
|
+
<path d="M50,10 Q55,2 62,5" class="sundae-cherry-stem"></path>
|
|
32
|
+
</svg>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div id="texture-soup" class="texture-state hidden">
|
|
36
|
+
<svg viewBox="0 0 100 100" class="scoop-svg">
|
|
37
|
+
<path d="M25,80 L75,80 L80,95 L20,95 Z" class="bowl-base"></path>
|
|
38
|
+
<path d="M15,80 L85,80 L75,50 L25,50 Z" class="bowl-glass"></path>
|
|
39
|
+
<path d="M18,75 Q50,65 82,75 L73,53 Q50,45 27,53 Z" class="sundae-melted-puddle"></path>
|
|
40
|
+
<path d="M26,80 Q23,90 20,85" class="sundae-drip-stream"></path>
|
|
41
|
+
<path d="M74,80 Q77,92 78,88" class="sundae-drip-stream"></path>
|
|
42
|
+
</svg>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="bowl-status-glow" id="status-glow"></div>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<div class="scoop-status-card">
|
|
49
|
+
<div id="status-badge" class="status-badge optimal">
|
|
50
|
+
<span id="status-badge-text">{ui.creamyState}</span>
|
|
51
|
+
</div>
|
|
52
|
+
<p id="status-description-text" class="status-desc">{ui.creamyDesc}</p>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
ui: Record<string, string>;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const { ui } = Astro.props;
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<div class="ice-cream-recipe-stack">
|
|
10
|
+
<div class="base-parameters-board">
|
|
11
|
+
<div class="board-title">Ice Cream Base Parameters</div>
|
|
12
|
+
|
|
13
|
+
<div class="unit-toggle-container">
|
|
14
|
+
<button type="button" id="toggle-metric-btn" class="unit-btn active">{ui.metricUnit}</button>
|
|
15
|
+
<button type="button" id="toggle-imperial-btn" class="unit-btn">{ui.imperialUnit}</button>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div class="base-field">
|
|
19
|
+
<div class="field-header">
|
|
20
|
+
<label for="base-weight-input" class="field-label">{ui.baseWeightLabel}</label>
|
|
21
|
+
<div class="field-value-wrap">
|
|
22
|
+
<input type="number" id="base-weight-input" value="1000" min="100" max="5000" step="50" class="field-number" />
|
|
23
|
+
<span class="weight-unit-label">{ui.gLabel}</span>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
<input type="range" id="base-weight-slider" min="100" max="5000" step="50" value="1000" class="cone-slider" />
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div class="base-field">
|
|
30
|
+
<div class="field-header">
|
|
31
|
+
<label for="target-temp-input" class="field-label">{ui.targetTempLabel}</label>
|
|
32
|
+
<div class="field-value-wrap">
|
|
33
|
+
<input type="number" id="target-temp-input" value="-12" min="-25" max="-5" step="1" class="field-number" />
|
|
34
|
+
<span class="temp-unit-label">{ui.cLabel}</span>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
<input type="range" id="target-temp-slider" min="-25" max="-5" step="1" value="-12" class="cone-slider" />
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div class="scoop-recipe-cone">
|
|
42
|
+
<div class="scoop-scoop sucrose-scoop">
|
|
43
|
+
<div class="scoop-inner-content">
|
|
44
|
+
<div class="scoop-label-row">
|
|
45
|
+
<label for="sucrose-input" class="scoop-text-label">{ui.sucroseLabel}</label>
|
|
46
|
+
<div class="scoop-readout">
|
|
47
|
+
<input type="number" id="sucrose-input" value="150" min="0" max="500" step="5" class="scoop-number-input" />
|
|
48
|
+
<span class="weight-unit-label">{ui.gLabel}</span>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
<input type="range" id="sucrose-slider" min="0" max="500" step="5" value="150" class="scoop-slider-bar" />
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div class="scoop-scoop dextrose-scoop">
|
|
56
|
+
<div class="scoop-inner-content">
|
|
57
|
+
<div class="scoop-label-row">
|
|
58
|
+
<label for="dextrose-input" class="scoop-text-label">{ui.dextroseLabel}</label>
|
|
59
|
+
<div class="scoop-readout">
|
|
60
|
+
<input type="number" id="dextrose-input" value="30" min="0" max="300" step="5" class="scoop-number-input" />
|
|
61
|
+
<span class="weight-unit-label">{ui.gLabel}</span>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
<input type="range" id="dextrose-slider" min="0" max="300" step="5" value="30" class="scoop-slider-bar" />
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div class="scoop-scoop glucose-scoop">
|
|
69
|
+
<div class="scoop-inner-content">
|
|
70
|
+
<div class="scoop-label-row">
|
|
71
|
+
<label for="glucose-input" class="scoop-text-label">{ui.glucoseLabel}</label>
|
|
72
|
+
<div class="scoop-readout">
|
|
73
|
+
<input type="number" id="glucose-input" value="20" min="0" max="300" step="5" class="scoop-number-input" />
|
|
74
|
+
<span class="weight-unit-label">{ui.gLabel}</span>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
<input type="range" id="glucose-slider" min="0" max="300" step="5" value="20" class="scoop-slider-bar" />
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div class="scoop-scoop inverted-scoop">
|
|
82
|
+
<div class="scoop-inner-content">
|
|
83
|
+
<div class="scoop-label-row">
|
|
84
|
+
<label for="inverted-input" class="scoop-text-label">{ui.invertedLabel}</label>
|
|
85
|
+
<div class="scoop-readout">
|
|
86
|
+
<input type="number" id="inverted-input" value="0" min="0" max="300" step="5" class="scoop-number-input" />
|
|
87
|
+
<span class="weight-unit-label">{ui.gLabel}</span>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
<input type="range" id="inverted-slider" min="0" max="300" step="5" value="0" class="scoop-slider-bar" />
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div class="scoop-scoop trehalose-scoop">
|
|
95
|
+
<div class="scoop-inner-content">
|
|
96
|
+
<div class="scoop-label-row">
|
|
97
|
+
<label for="trehalose-input" class="scoop-text-label">{ui.trehaloseLabel}</label>
|
|
98
|
+
<div class="scoop-readout">
|
|
99
|
+
<input type="number" id="trehalose-input" value="0" min="0" max="300" step="5" class="scoop-number-input" />
|
|
100
|
+
<span class="weight-unit-label">{ui.gLabel}</span>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
<input type="range" id="trehalose-slider" min="0" max="300" step="5" value="0" class="scoop-slider-bar" />
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CookingToolEntry } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const iceCreamPacPod: CookingToolEntry = {
|
|
4
|
+
id: 'ice-cream-pac-pod',
|
|
5
|
+
icons: {
|
|
6
|
+
bg: 'mdi:snowflake',
|
|
7
|
+
fg: 'mdi:ice-cream',
|
|
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
|
+
};
|