@jjlmoya/utils-cooking 1.27.0 → 1.28.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 +2 -0
- package/src/entries.ts +3 -1
- package/src/index.ts +1 -0
- package/src/tests/i18n-titles.test.ts +3 -2
- package/src/tests/lacto-fermentation-salt-calculator.test.ts +64 -0
- package/src/tests/locale_completeness.test.ts +2 -2
- 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/tools.ts +2 -2
package/package.json
CHANGED
package/src/category/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { ingredientRescaler } from '../tool/ingredient-rescaler/entry';
|
|
|
11
11
|
import { sourdoughCalculator } from '../tool/sourdough-calculator/entry';
|
|
12
12
|
import { rouxGuide } from '../tool/roux-guide/entry';
|
|
13
13
|
import { cookwareGuide } from '../tool/cookware-guide/entry';
|
|
14
|
+
import { lactoFermentationSalt } from '../tool/lacto-fermentation-salt-calculator/entry';
|
|
14
15
|
|
|
15
16
|
export const cookingCategory: CookingCategoryEntry = {
|
|
16
17
|
icon: 'mdi:chef-hat',
|
|
@@ -27,6 +28,7 @@ export const cookingCategory: CookingCategoryEntry = {
|
|
|
27
28
|
sourdoughCalculator,
|
|
28
29
|
rouxGuide,
|
|
29
30
|
cookwareGuide,
|
|
31
|
+
lactoFermentationSalt,
|
|
30
32
|
],
|
|
31
33
|
i18n: {
|
|
32
34
|
es: () => import('./i18n/es').then((m) => m.content),
|
package/src/entries.ts
CHANGED
|
@@ -11,6 +11,7 @@ export { pizza } from './tool/pizza/entry';
|
|
|
11
11
|
export { rouxGuide } from './tool/roux-guide/entry';
|
|
12
12
|
export { sourdoughCalculator } from './tool/sourdough-calculator/entry';
|
|
13
13
|
export { yeastConverter } from './tool/yeast-converter/entry';
|
|
14
|
+
export { lactoFermentationSalt } from './tool/lacto-fermentation-salt-calculator/entry';
|
|
14
15
|
export { cookingCategory } from './category';
|
|
15
16
|
import { americanKitchenConverter } from './tool/american-kitchen-converter/entry';
|
|
16
17
|
import { bananaCare } from './tool/banana-ripeness/entry';
|
|
@@ -25,4 +26,5 @@ import { pizza } from './tool/pizza/entry';
|
|
|
25
26
|
import { rouxGuide } from './tool/roux-guide/entry';
|
|
26
27
|
import { sourdoughCalculator } from './tool/sourdough-calculator/entry';
|
|
27
28
|
import { yeastConverter } from './tool/yeast-converter/entry';
|
|
28
|
-
|
|
29
|
+
import { lactoFermentationSalt } from './tool/lacto-fermentation-salt-calculator/entry';
|
|
30
|
+
export const ALL_ENTRIES = [americanKitchenConverter, bananaCare, brine, cookwareGuide, eggTimer, ingredientRescaler, kitchenTimer, meringuePeak, moldScaler, pizza, rouxGuide, sourdoughCalculator, yeastConverter, lactoFermentationSalt];
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ export { SOURDOUGH_CALCULATOR_TOOL } from './tool/sourdough-calculator';
|
|
|
14
14
|
export { ROUX_GUIDE_TOOL } from './tool/roux-guide';
|
|
15
15
|
export { COOKWARE_GUIDE_TOOL } from './tool/cookware-guide';
|
|
16
16
|
export { YEAST_CONVERTER_TOOL } from './tool/yeast-converter';
|
|
17
|
+
export { LACTO_FERMENTATION_SALT_TOOL } from './tool/lacto-fermentation-salt-calculator';
|
|
17
18
|
|
|
18
19
|
export type {
|
|
19
20
|
KnownLocale,
|
|
@@ -32,8 +32,8 @@ describe("i18n titles for FAQ", () => {
|
|
|
32
32
|
}
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
it("should have
|
|
36
|
-
expect(ALL_TOOLS.length).toBe(
|
|
35
|
+
it("should have 14 tools with complete i18n setup", async () => {
|
|
36
|
+
expect(ALL_TOOLS.length).toBe(14);
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
it("tool IDs should be correctly registered", () => {
|
|
@@ -50,5 +50,6 @@ describe("i18n titles for FAQ", () => {
|
|
|
50
50
|
expect(toolIds).toContain("sourdough-calculator");
|
|
51
51
|
expect(toolIds).toContain("roux-guide");
|
|
52
52
|
expect(toolIds).toContain("cookware-guide");
|
|
53
|
+
expect(toolIds).toContain("lacto-fermentation-salt-calculator");
|
|
53
54
|
});
|
|
54
55
|
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { FermentationLogic } from '../tool/lacto-fermentation-salt-calculator/logic';
|
|
3
|
+
|
|
4
|
+
describe('Lacto-Fermentation Salt Calculator Logic', () => {
|
|
5
|
+
it('calculates correct dry salting values', () => {
|
|
6
|
+
const res = FermentationLogic.calculate({
|
|
7
|
+
vegWeight: 1000,
|
|
8
|
+
waterWeight: 0,
|
|
9
|
+
salinity: 2.5,
|
|
10
|
+
mode: 'dry',
|
|
11
|
+
});
|
|
12
|
+
expect(res.saltGrams).toBe(25);
|
|
13
|
+
expect(res.status).toBe('optimal');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('calculates correct wet brining values', () => {
|
|
17
|
+
const res = FermentationLogic.calculate({
|
|
18
|
+
vegWeight: 1000,
|
|
19
|
+
waterWeight: 500,
|
|
20
|
+
salinity: 2.0,
|
|
21
|
+
mode: 'wet',
|
|
22
|
+
});
|
|
23
|
+
expect(res.saltGrams).toBe(30);
|
|
24
|
+
expect(res.status).toBe('optimal');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('correctly classifies status based on salinity thresholds', () => {
|
|
28
|
+
const dangerRes = FermentationLogic.calculate({
|
|
29
|
+
vegWeight: 1000,
|
|
30
|
+
waterWeight: 0,
|
|
31
|
+
salinity: 1.5,
|
|
32
|
+
mode: 'dry',
|
|
33
|
+
});
|
|
34
|
+
expect(dangerRes.status).toBe('danger');
|
|
35
|
+
|
|
36
|
+
const optimalRes = FermentationLogic.calculate({
|
|
37
|
+
vegWeight: 1000,
|
|
38
|
+
waterWeight: 0,
|
|
39
|
+
salinity: 3.0,
|
|
40
|
+
mode: 'dry',
|
|
41
|
+
});
|
|
42
|
+
expect(optimalRes.status).toBe('optimal');
|
|
43
|
+
|
|
44
|
+
const inhibitedRes = FermentationLogic.calculate({
|
|
45
|
+
vegWeight: 1000,
|
|
46
|
+
waterWeight: 0,
|
|
47
|
+
salinity: 4.0,
|
|
48
|
+
mode: 'dry',
|
|
49
|
+
});
|
|
50
|
+
expect(inhibitedRes.status).toBe('inhibited');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('calculates volumetric conversions accurately', () => {
|
|
54
|
+
const res = FermentationLogic.calculate({
|
|
55
|
+
vegWeight: 1000,
|
|
56
|
+
waterWeight: 0,
|
|
57
|
+
salinity: 2.0,
|
|
58
|
+
mode: 'dry',
|
|
59
|
+
});
|
|
60
|
+
expect(res.saltGrams).toBe(20);
|
|
61
|
+
expect(res.fineTeaspoons).toBe(3.5);
|
|
62
|
+
expect(res.kosherTeaspoons).toBe(4.7);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -4,8 +4,8 @@ import { cookingCategory } from '../data';
|
|
|
4
4
|
|
|
5
5
|
describe('Tool Validation Suite', () => {
|
|
6
6
|
describe('Library Registration', () => {
|
|
7
|
-
it('should have
|
|
8
|
-
expect(ALL_TOOLS.length).toBe(
|
|
7
|
+
it('should have 14 tools in ALL_TOOLS', () => {
|
|
8
|
+
expect(ALL_TOOLS.length).toBe(14);
|
|
9
9
|
});
|
|
10
10
|
|
|
11
11
|
it('cookingCategory should be defined', () => {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const bibliography = [
|
|
2
|
+
{
|
|
3
|
+
name: 'Microbiology and Technology of Fermented Foods',
|
|
4
|
+
url: 'https://onlinelibrary.wiley.com/doi/book/10.1002/9780470277515',
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
name: 'Lactic Acid Bacteria: Microbiological and Functional Aspects',
|
|
8
|
+
url: 'https://www.taylorfrancis.com/books/edit/10.1201/9780429057465/lactic-acid-bacteria-gabriel-vinderola-arthur-ouwehand-seppo-salminen-atte-von-wright',
|
|
9
|
+
},
|
|
10
|
+
];
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
---
|
|
2
|
+
import WizardMode from './components/WizardMode.astro';
|
|
3
|
+
import DigitalScale from './components/DigitalScale.astro';
|
|
4
|
+
import PetriDish from './components/PetriDish.astro';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
ui: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { ui } = Astro.props;
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<div class="fermentation-container">
|
|
14
|
+
<div class="fermentation-card">
|
|
15
|
+
<div class="fermentation-glow-1"></div>
|
|
16
|
+
<div class="fermentation-glow-2"></div>
|
|
17
|
+
|
|
18
|
+
<div class="fermentation-grid">
|
|
19
|
+
<div class="fermentation-controls-section">
|
|
20
|
+
<WizardMode ui={ui} />
|
|
21
|
+
<DigitalScale ui={ui} />
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div class="fermentation-visuals-section">
|
|
25
|
+
<PetriDish ui={ui} />
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<script is:inline define:vars={{ ui }}>
|
|
32
|
+
const metricBtn = document.getElementById('toggle-metric-btn');
|
|
33
|
+
const imperialBtn = document.getElementById('toggle-imperial-btn');
|
|
34
|
+
const dryBtn = document.getElementById('toggle-dry-btn');
|
|
35
|
+
const wetBtn = document.getElementById('toggle-wet-btn');
|
|
36
|
+
const vegInput = document.getElementById('veg-weight-input');
|
|
37
|
+
const waterInput = document.getElementById('water-weight-input');
|
|
38
|
+
const waterGroup = document.getElementById('water-weight-group');
|
|
39
|
+
const salinitySlider = document.getElementById('salinity-slider');
|
|
40
|
+
|
|
41
|
+
const salinityDisplay = document.getElementById('salinity-val-display');
|
|
42
|
+
const scaleDisplay = document.getElementById('scale-main-value');
|
|
43
|
+
const scaleUnitDisplay = document.getElementById('scale-unit-label');
|
|
44
|
+
const fineDisplay = document.getElementById('fine-teaspoons-val');
|
|
45
|
+
const kosherDisplay = document.getElementById('kosher-teaspoons-val');
|
|
46
|
+
|
|
47
|
+
const petriBac = document.getElementById('petri-bacteria');
|
|
48
|
+
const petriMold = document.getElementById('petri-mold');
|
|
49
|
+
const petriCrys = document.getElementById('petri-crystals');
|
|
50
|
+
|
|
51
|
+
const statusBadge = document.getElementById('status-badge');
|
|
52
|
+
const statusBadgeText = document.getElementById('status-badge-text');
|
|
53
|
+
const statusDescText = document.getElementById('status-description-text');
|
|
54
|
+
|
|
55
|
+
let activeMode = 'dry';
|
|
56
|
+
let activeUnit = 'metric';
|
|
57
|
+
|
|
58
|
+
function updatePetriAndStatus(salinity) {
|
|
59
|
+
if (salinity < 2.0) {
|
|
60
|
+
petriMold.style.opacity = '1';
|
|
61
|
+
petriBac.style.opacity = '0';
|
|
62
|
+
petriCrys.style.opacity = '0';
|
|
63
|
+
|
|
64
|
+
statusBadge.className = 'status-badge danger';
|
|
65
|
+
statusBadgeText.textContent = ui.statusDanger;
|
|
66
|
+
statusDescText.textContent = ui.statusDangerDesc;
|
|
67
|
+
} else if (salinity > 3.5) {
|
|
68
|
+
petriMold.style.opacity = '0';
|
|
69
|
+
petriBac.style.opacity = '0';
|
|
70
|
+
petriCrys.style.opacity = '1';
|
|
71
|
+
|
|
72
|
+
statusBadge.className = 'status-badge inhibited';
|
|
73
|
+
statusBadgeText.textContent = ui.statusInhibited;
|
|
74
|
+
statusDescText.textContent = ui.statusInhibitedDesc;
|
|
75
|
+
} else {
|
|
76
|
+
petriMold.style.opacity = '0';
|
|
77
|
+
petriBac.style.opacity = '1';
|
|
78
|
+
petriCrys.style.opacity = '0';
|
|
79
|
+
|
|
80
|
+
statusBadge.className = 'status-badge optimal';
|
|
81
|
+
statusBadgeText.textContent = ui.statusOptimal;
|
|
82
|
+
statusDescText.textContent = ui.statusOptimalDesc;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function updateView() {
|
|
87
|
+
const vegWeight = Math.max(0, parseFloat(vegInput.value) || 0);
|
|
88
|
+
const waterWeight = Math.max(0, parseFloat(waterInput.value) || 0);
|
|
89
|
+
const salinity = Math.max(0, parseFloat(salinitySlider.value) || 0);
|
|
90
|
+
|
|
91
|
+
salinityDisplay.textContent = `${salinity.toFixed(1)}%`;
|
|
92
|
+
|
|
93
|
+
const totalWeight = activeMode === 'dry' ? vegWeight : (vegWeight + waterWeight);
|
|
94
|
+
const saltResult = Math.max(0, (totalWeight * salinity) / 100);
|
|
95
|
+
|
|
96
|
+
scaleDisplay.textContent = saltResult.toFixed(2);
|
|
97
|
+
scaleUnitDisplay.textContent = activeUnit === 'metric' ? 'g' : 'oz';
|
|
98
|
+
|
|
99
|
+
const saltGrams = activeUnit === 'imperial' ? saltResult * 28.3495 : saltResult;
|
|
100
|
+
|
|
101
|
+
fineDisplay.textContent = Math.max(0, saltGrams / 5.7).toFixed(1);
|
|
102
|
+
kosherDisplay.textContent = Math.max(0, saltGrams / 4.3).toFixed(1);
|
|
103
|
+
|
|
104
|
+
updatePetriAndStatus(salinity);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function convertInputsTo(targetUnit) {
|
|
108
|
+
const currentVeg = parseFloat(vegInput.value) || 0;
|
|
109
|
+
const currentWater = parseFloat(waterInput.value) || 0;
|
|
110
|
+
|
|
111
|
+
const labels = document.querySelectorAll('.weight-unit-label');
|
|
112
|
+
|
|
113
|
+
if (targetUnit === 'imperial') {
|
|
114
|
+
vegInput.value = (currentVeg * 0.035274).toFixed(1);
|
|
115
|
+
waterInput.value = (currentWater * 0.035274).toFixed(1);
|
|
116
|
+
labels.forEach(label => label.textContent = 'oz');
|
|
117
|
+
} else {
|
|
118
|
+
vegInput.value = Math.round(currentVeg / 0.035274).toString();
|
|
119
|
+
waterInput.value = Math.round(currentWater / 0.035274).toString();
|
|
120
|
+
labels.forEach(label => label.textContent = 'g');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
metricBtn.addEventListener('click', () => {
|
|
125
|
+
if (activeUnit === 'metric') return;
|
|
126
|
+
activeUnit = 'metric';
|
|
127
|
+
metricBtn.classList.add('active');
|
|
128
|
+
imperialBtn.classList.remove('active');
|
|
129
|
+
convertInputsTo('metric');
|
|
130
|
+
updateView();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
imperialBtn.addEventListener('click', () => {
|
|
134
|
+
if (activeUnit === 'imperial') return;
|
|
135
|
+
activeUnit = 'imperial';
|
|
136
|
+
imperialBtn.classList.add('active');
|
|
137
|
+
metricBtn.classList.remove('active');
|
|
138
|
+
convertInputsTo('imperial');
|
|
139
|
+
updateView();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
dryBtn.addEventListener('click', () => {
|
|
143
|
+
activeMode = 'dry';
|
|
144
|
+
dryBtn.classList.add('active');
|
|
145
|
+
wetBtn.classList.remove('active');
|
|
146
|
+
waterGroup.classList.add('hidden');
|
|
147
|
+
updateView();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
wetBtn.addEventListener('click', () => {
|
|
151
|
+
activeMode = 'wet';
|
|
152
|
+
wetBtn.classList.add('active');
|
|
153
|
+
dryBtn.classList.remove('active');
|
|
154
|
+
waterGroup.classList.remove('hidden');
|
|
155
|
+
updateView();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
vegInput.addEventListener('input', updateView);
|
|
159
|
+
waterInput.addEventListener('input', updateView);
|
|
160
|
+
salinitySlider.addEventListener('input', updateView);
|
|
161
|
+
|
|
162
|
+
updateView();
|
|
163
|
+
</script>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
ui: Record<string, string>;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const { ui } = Astro.props;
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<div class="digital-scale-container">
|
|
10
|
+
<div class="scale-header">
|
|
11
|
+
<span class="scale-title">{ui.scaleTitle}</span>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div class="scale-plate">
|
|
15
|
+
<div class="scale-display">
|
|
16
|
+
<div class="scale-digits">
|
|
17
|
+
<span id="scale-main-value">25.00</span>
|
|
18
|
+
<span id="scale-unit-label" class="scale-unit">g</span>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="scale-label">{ui.saltGramsLabel}</div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div class="scale-conversions">
|
|
25
|
+
<div class="conversion-item">
|
|
26
|
+
<span class="conversion-label">{ui.fineLabel}</span>
|
|
27
|
+
<div class="conversion-val">
|
|
28
|
+
<span id="fine-teaspoons-val">4.4</span>
|
|
29
|
+
<span class="conversion-unit">tsp</span>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div class="conversion-item">
|
|
34
|
+
<span class="conversion-label">{ui.kosherLabel}</span>
|
|
35
|
+
<div class="conversion-val">
|
|
36
|
+
<span id="kosher-teaspoons-val">5.8</span>
|
|
37
|
+
<span class="conversion-unit">tsp</span>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<p class="scale-disclaimer">{ui.disclaimer}</p>
|
|
43
|
+
</div>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
ui: Record<string, string>;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const { ui } = Astro.props;
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<div class="petri-dish-section">
|
|
10
|
+
<div class="petri-header">
|
|
11
|
+
<span class="petri-title">{ui.petriTitle}</span>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div class="petri-container">
|
|
15
|
+
<div class="petri-dish">
|
|
16
|
+
<div class="petri-agar">
|
|
17
|
+
<svg viewBox="0 0 200 200" class="petri-svg" xmlns="http://www.w3.org/2000/svg">
|
|
18
|
+
<g id="petri-bacteria" class="bacteria-layer">
|
|
19
|
+
<ellipse cx="60" cy="80" rx="6" ry="14" transform="rotate(25 60 80)" class="bac-cell"></ellipse>
|
|
20
|
+
<ellipse cx="140" cy="70" rx="5" ry="12" transform="rotate(-35 140 70)" class="bac-cell"></ellipse>
|
|
21
|
+
<ellipse cx="100" cy="130" rx="7" ry="15" transform="rotate(45 100 130)" class="bac-cell"></ellipse>
|
|
22
|
+
<circle cx="80" cy="50" r="5" class="bac-coccus"></circle>
|
|
23
|
+
<circle cx="120" cy="110" r="4.5" class="bac-coccus"></circle>
|
|
24
|
+
<circle cx="70" cy="150" r="6" class="bac-coccus"></circle>
|
|
25
|
+
</g>
|
|
26
|
+
|
|
27
|
+
<g id="petri-mold" class="mold-layer">
|
|
28
|
+
<path d="M 40,100 C 50,80 70,80 80,100 C 90,80 110,80 120,100 C 110,120 90,120 80,100 C 70,120 50,120 40,100 Z" class="mold-fungus" fill="none" stroke-width="3"></path>
|
|
29
|
+
<path d="M 110,60 C 120,45 135,45 140,60 C 145,45 160,45 160,60" class="mold-fungus" fill="none" stroke-width="2.5"></path>
|
|
30
|
+
<path d="M 70,140 C 80,125 95,125 100,140 C 105,125 120,125 120,140" class="mold-fungus" fill="none" stroke-width="2.5"></path>
|
|
31
|
+
<circle cx="80" cy="100" r="3" class="mold-spore"></circle>
|
|
32
|
+
<circle cx="140" cy="60" r="2" class="mold-spore"></circle>
|
|
33
|
+
<circle cx="100" cy="140" r="2.5" class="mold-spore"></circle>
|
|
34
|
+
</g>
|
|
35
|
+
|
|
36
|
+
<g id="petri-crystals" class="crystals-layer">
|
|
37
|
+
<rect x="50" y="50" width="12" height="12" transform="rotate(15 56 56)" class="crystal-cube"></rect>
|
|
38
|
+
<rect x="130" y="120" width="16" height="16" transform="rotate(45 138 128)" class="crystal-cube"></rect>
|
|
39
|
+
<rect x="60" y="120" width="10" height="10" transform="rotate(5 65 125)" class="crystal-cube"></rect>
|
|
40
|
+
<rect x="140" y="45" width="14" height="14" transform="rotate(30 147 52)" class="crystal-cube"></rect>
|
|
41
|
+
</g>
|
|
42
|
+
</svg>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div class="status-box">
|
|
48
|
+
<div id="status-badge" class="status-badge optimal">
|
|
49
|
+
<span id="status-badge-text">{ui.statusOptimal}</span>
|
|
50
|
+
</div>
|
|
51
|
+
<p id="status-description-text" class="status-desc-text">
|
|
52
|
+
{ui.statusOptimalDesc}
|
|
53
|
+
</p>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
ui: Record<string, string>;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const { ui } = Astro.props;
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<div class="fermentation-wizard">
|
|
10
|
+
<div class="wizard-control-group">
|
|
11
|
+
<label class="wizard-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="wizard-control-group">
|
|
23
|
+
<label class="wizard-label">
|
|
24
|
+
{ui.modeLabel}
|
|
25
|
+
</label>
|
|
26
|
+
<div class="mode-toggle-container">
|
|
27
|
+
<button type="button" id="toggle-dry-btn" class="mode-btn active" data-mode="dry">
|
|
28
|
+
{ui.dryMode}
|
|
29
|
+
</button>
|
|
30
|
+
<button type="button" id="toggle-wet-btn" class="mode-btn" data-mode="wet">
|
|
31
|
+
{ui.wetMode}
|
|
32
|
+
</button>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div class="wizard-control-group">
|
|
37
|
+
<label for="veg-weight-input" class="wizard-label">
|
|
38
|
+
{ui.vegWeightLabel} (<span class="weight-unit-label">g</span>)
|
|
39
|
+
</label>
|
|
40
|
+
<input
|
|
41
|
+
type="number"
|
|
42
|
+
id="veg-weight-input"
|
|
43
|
+
min="0"
|
|
44
|
+
max="100000"
|
|
45
|
+
step="any"
|
|
46
|
+
value="1000"
|
|
47
|
+
class="wizard-number-input"
|
|
48
|
+
inputmode="decimal"
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div id="water-weight-group" class="wizard-control-group hidden">
|
|
53
|
+
<label for="water-weight-input" class="wizard-label">
|
|
54
|
+
{ui.waterWeightLabel} (<span class="weight-unit-label">g</span>)
|
|
55
|
+
</label>
|
|
56
|
+
<input
|
|
57
|
+
type="number"
|
|
58
|
+
id="water-weight-input"
|
|
59
|
+
min="0"
|
|
60
|
+
max="100000"
|
|
61
|
+
step="any"
|
|
62
|
+
value="500"
|
|
63
|
+
class="wizard-number-input"
|
|
64
|
+
inputmode="decimal"
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div class="wizard-control-group">
|
|
69
|
+
<div class="slider-header">
|
|
70
|
+
<label for="salinity-slider" class="wizard-label">{ui.salinityLabel}</label>
|
|
71
|
+
<span id="salinity-val-display" class="slider-val">2.5%</span>
|
|
72
|
+
</div>
|
|
73
|
+
<input
|
|
74
|
+
type="range"
|
|
75
|
+
id="salinity-slider"
|
|
76
|
+
min="1.0"
|
|
77
|
+
max="6.0"
|
|
78
|
+
step="0.1"
|
|
79
|
+
value="2.5"
|
|
80
|
+
class="wizard-slider"
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CookingToolEntry } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const lactoFermentationSalt: CookingToolEntry = {
|
|
4
|
+
id: 'lacto-fermentation-salt-calculator',
|
|
5
|
+
icons: {
|
|
6
|
+
bg: 'mdi:leaf',
|
|
7
|
+
fg: 'mdi:flask-outline',
|
|
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
|
+
};
|