@jjlmoya/utils-cooking 1.35.0 → 1.37.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 +6 -0
- package/src/entries.ts +7 -1
- package/src/index.ts +3 -0
- package/src/tests/brix-sorbet-density-calculator.test.ts +53 -0
- package/src/tests/i18n-titles.test.ts +2 -2
- package/src/tests/locale_completeness.test.ts +2 -2
- package/src/tests/tool_validation.test.ts +2 -2
- package/src/tool/brix-sorbet-density-calculator/bibliography.astro +6 -0
- package/src/tool/brix-sorbet-density-calculator/bibliography.ts +10 -0
- package/src/tool/brix-sorbet-density-calculator/brix-sorbet-density-calculator.css +878 -0
- package/src/tool/brix-sorbet-density-calculator/component.astro +220 -0
- package/src/tool/brix-sorbet-density-calculator/entry.ts +26 -0
- package/src/tool/brix-sorbet-density-calculator/helpers.ts +102 -0
- package/src/tool/brix-sorbet-density-calculator/i18n/de.ts +295 -0
- package/src/tool/brix-sorbet-density-calculator/i18n/en.ts +295 -0
- package/src/tool/brix-sorbet-density-calculator/i18n/es.ts +295 -0
- package/src/tool/brix-sorbet-density-calculator/i18n/fr.ts +295 -0
- package/src/tool/brix-sorbet-density-calculator/i18n/id.ts +295 -0
- package/src/tool/brix-sorbet-density-calculator/i18n/it.ts +295 -0
- package/src/tool/brix-sorbet-density-calculator/i18n/ja.ts +295 -0
- package/src/tool/brix-sorbet-density-calculator/i18n/ko.ts +295 -0
- package/src/tool/brix-sorbet-density-calculator/i18n/nl.ts +295 -0
- package/src/tool/brix-sorbet-density-calculator/i18n/pl.ts +295 -0
- package/src/tool/brix-sorbet-density-calculator/i18n/pt.ts +295 -0
- package/src/tool/brix-sorbet-density-calculator/i18n/ru.ts +295 -0
- package/src/tool/brix-sorbet-density-calculator/i18n/sv.ts +295 -0
- package/src/tool/brix-sorbet-density-calculator/i18n/tr.ts +295 -0
- package/src/tool/brix-sorbet-density-calculator/i18n/zh.ts +295 -0
- package/src/tool/brix-sorbet-density-calculator/index.ts +11 -0
- package/src/tool/brix-sorbet-density-calculator/logic.ts +180 -0
- package/src/tool/brix-sorbet-density-calculator/script.ts +114 -0
- package/src/tool/brix-sorbet-density-calculator/seo.astro +15 -0
- package/src/tool/macaron-drying-predictor/bibliography.astro +6 -0
- package/src/tool/macaron-drying-predictor/bibliography.ts +14 -0
- package/src/tool/macaron-drying-predictor/component.astro +319 -0
- package/src/tool/macaron-drying-predictor/entry.ts +26 -0
- package/src/tool/macaron-drying-predictor/i18n/de.ts +245 -0
- package/src/tool/macaron-drying-predictor/i18n/en.ts +247 -0
- package/src/tool/macaron-drying-predictor/i18n/es.ts +245 -0
- package/src/tool/macaron-drying-predictor/i18n/fr.ts +245 -0
- package/src/tool/macaron-drying-predictor/i18n/id.ts +245 -0
- package/src/tool/macaron-drying-predictor/i18n/it.ts +245 -0
- package/src/tool/macaron-drying-predictor/i18n/ja.ts +245 -0
- package/src/tool/macaron-drying-predictor/i18n/ko.ts +245 -0
- package/src/tool/macaron-drying-predictor/i18n/nl.ts +245 -0
- package/src/tool/macaron-drying-predictor/i18n/pl.ts +245 -0
- package/src/tool/macaron-drying-predictor/i18n/pt.ts +245 -0
- package/src/tool/macaron-drying-predictor/i18n/ru.ts +245 -0
- package/src/tool/macaron-drying-predictor/i18n/sv.ts +245 -0
- package/src/tool/macaron-drying-predictor/i18n/tr.ts +245 -0
- package/src/tool/macaron-drying-predictor/i18n/zh.ts +245 -0
- package/src/tool/macaron-drying-predictor/index.ts +11 -0
- package/src/tool/macaron-drying-predictor/logic.ts +58 -0
- package/src/tool/macaron-drying-predictor/macaron-drying-predictor.css +551 -0
- package/src/tool/macaron-drying-predictor/seo.astro +15 -0
- package/src/tool/oil-smoke-point-tracker/bibliography.astro +6 -0
- package/src/tool/oil-smoke-point-tracker/bibliography.ts +10 -0
- package/src/tool/oil-smoke-point-tracker/component.astro +445 -0
- package/src/tool/oil-smoke-point-tracker/entry.ts +26 -0
- package/src/tool/oil-smoke-point-tracker/i18n/de.ts +285 -0
- package/src/tool/oil-smoke-point-tracker/i18n/en.ts +285 -0
- package/src/tool/oil-smoke-point-tracker/i18n/es.ts +285 -0
- package/src/tool/oil-smoke-point-tracker/i18n/fr.ts +285 -0
- package/src/tool/oil-smoke-point-tracker/i18n/id.ts +244 -0
- package/src/tool/oil-smoke-point-tracker/i18n/it.ts +244 -0
- package/src/tool/oil-smoke-point-tracker/i18n/ja.ts +244 -0
- package/src/tool/oil-smoke-point-tracker/i18n/ko.ts +244 -0
- package/src/tool/oil-smoke-point-tracker/i18n/nl.ts +244 -0
- package/src/tool/oil-smoke-point-tracker/i18n/pl.ts +244 -0
- package/src/tool/oil-smoke-point-tracker/i18n/pt.ts +244 -0
- package/src/tool/oil-smoke-point-tracker/i18n/ru.ts +244 -0
- package/src/tool/oil-smoke-point-tracker/i18n/sv.ts +244 -0
- package/src/tool/oil-smoke-point-tracker/i18n/tr.ts +244 -0
- package/src/tool/oil-smoke-point-tracker/i18n/zh.ts +244 -0
- package/src/tool/oil-smoke-point-tracker/index.ts +11 -0
- package/src/tool/oil-smoke-point-tracker/logic.ts +70 -0
- package/src/tool/oil-smoke-point-tracker/oil-smoke-point-tracker.css +937 -0
- package/src/tool/oil-smoke-point-tracker/seo.astro +15 -0
- package/src/tools.ts +6 -0
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { OIL_PRESETS } from './logic';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
ui: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const { ui } = Astro.props;
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<div class="ospt">
|
|
12
|
+
<div class="ospt-card status-good">
|
|
13
|
+
<div class="ospt-unit-row">
|
|
14
|
+
<button type="button" id="ospt-unit-metric" class="ospt-unit-btn active">Celsius</button>
|
|
15
|
+
<button type="button" id="ospt-unit-imperial" class="ospt-unit-btn">Fahrenheit</button>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="ospt-body">
|
|
18
|
+
<div class="ospt-controls">
|
|
19
|
+
<div class="ospt-input-group">
|
|
20
|
+
<label class="ospt-label">{ui.oilPresetLabel}</label>
|
|
21
|
+
<div class="ospt-oil-list" id="ospt-presets">
|
|
22
|
+
{OIL_PRESETS.map((oil, idx) => (
|
|
23
|
+
<button type="button" class={`ospt-oil-item ${idx === 1 ? 'active' : ''}`} data-id={oil.id}>
|
|
24
|
+
<span class={`ospt-oil-color color-${oil.id}`}></span>
|
|
25
|
+
<span>{ui[oil.nameKey] || oil.id}</span>
|
|
26
|
+
</button>
|
|
27
|
+
))}
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<div class="ospt-card-group">
|
|
32
|
+
<div class="ospt-input-group">
|
|
33
|
+
<label class="ospt-label" for="ospt-uses">{ui.usesLabel}</label>
|
|
34
|
+
<div class="ospt-slider-wrap">
|
|
35
|
+
<button type="button" class="ospt-adjuster-btn" data-dir="down" data-target="ospt-uses">-</button>
|
|
36
|
+
<input type="range" id="ospt-uses" class="ospt-slider" min="0" max="15" step="1" value="0" />
|
|
37
|
+
<button type="button" class="ospt-adjuster-btn" data-dir="up" data-target="ospt-uses">+</button>
|
|
38
|
+
<span class="ospt-value-display"><span id="ospt-uses-val">0</span></span>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div class="ospt-card-group">
|
|
44
|
+
<div class="ospt-input-group">
|
|
45
|
+
<label class="ospt-label" for="ospt-temp">{ui.tempLabel}</label>
|
|
46
|
+
<div class="ospt-slider-wrap">
|
|
47
|
+
<button type="button" class="ospt-adjuster-btn" data-dir="down" data-target="ospt-temp">-</button>
|
|
48
|
+
<input type="range" id="ospt-temp" class="ospt-slider" min="100" max="230" step="5" value="175" />
|
|
49
|
+
<button type="button" class="ospt-adjuster-btn" data-dir="up" data-target="ospt-temp">+</button>
|
|
50
|
+
<span class="ospt-value-display"><span id="ospt-temp-val">175</span><span class="ospt-unit ospt-temp-unit">°C</span></span>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div class="ospt-card-group">
|
|
56
|
+
<div class="ospt-input-group">
|
|
57
|
+
<label class="ospt-label">{ui.foodTypeLabel}</label>
|
|
58
|
+
<div class="ospt-radio-group">
|
|
59
|
+
<button type="button" class="ospt-radio-btn active" data-type="starch">
|
|
60
|
+
<span class="ospt-radio-dot"></span>
|
|
61
|
+
<span class="ospt-food-icon">
|
|
62
|
+
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M5 6v14a2 2 0 002 2h10a2 2 0 002-2V6M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>
|
|
63
|
+
</span>
|
|
64
|
+
<span>{ui.optionStarch}</span>
|
|
65
|
+
</button>
|
|
66
|
+
<button type="button" class="ospt-radio-btn" data-type="breaded">
|
|
67
|
+
<span class="ospt-radio-dot"></span>
|
|
68
|
+
<span class="ospt-food-icon">
|
|
69
|
+
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10zM9 13.5a1.5 1.5 0 110-3 1.5 1.5 0 010 3zm6 0a1.5 1.5 0 110-3 1.5 1.5 0 010 3z"/></svg>
|
|
70
|
+
</span>
|
|
71
|
+
<span>{ui.optionBreading}</span>
|
|
72
|
+
</button>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div class="ospt-result-panel">
|
|
79
|
+
<div class="ospt-visuals-container">
|
|
80
|
+
<div class="ospt-thermometer-box">
|
|
81
|
+
<div class="ospt-smoke-emitter" id="ospt-smoke-container">
|
|
82
|
+
<span class="ospt-smoke-particle"></span>
|
|
83
|
+
<span class="ospt-smoke-particle"></span>
|
|
84
|
+
<span class="ospt-smoke-particle"></span>
|
|
85
|
+
<span class="ospt-smoke-particle"></span>
|
|
86
|
+
</div>
|
|
87
|
+
<div class="ospt-thermo-wrapper">
|
|
88
|
+
<div class="ospt-thermo-scale">
|
|
89
|
+
<div class="ospt-scale-tick" style="bottom: 92.3%;"><span class="ospt-tick-val" data-c="220">220°C</span></div>
|
|
90
|
+
<div class="ospt-scale-tick" style="bottom: 69.2%;"><span class="ospt-tick-val" data-c="190">190°C</span></div>
|
|
91
|
+
<div class="ospt-scale-tick" style="bottom: 46.1%;"><span class="ospt-tick-val" data-c="160">160°C</span></div>
|
|
92
|
+
<div class="ospt-scale-tick" style="bottom: 23.0%;"><span class="ospt-tick-val" data-c="130">130°C</span></div>
|
|
93
|
+
<div class="ospt-scale-tick" style="bottom: 0%;"><span class="ospt-tick-val" data-c="100">100°C</span></div>
|
|
94
|
+
</div>
|
|
95
|
+
<div class="ospt-thermo-graphic">
|
|
96
|
+
<div class="ospt-thermo-fluid" id="ospt-thermo-fluid"></div>
|
|
97
|
+
<div class="ospt-thermo-bulb"></div>
|
|
98
|
+
<div class="ospt-thermo-smoke-point-marker" id="ospt-smoke-marker">
|
|
99
|
+
<span class="ospt-thermo-smoke-point-label" id="ospt-smoke-label">232°C</span>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div class="ospt-health-box">
|
|
106
|
+
<div class="ospt-gauge-title">{ui.polymerizationLabel}</div>
|
|
107
|
+
<div class="ospt-radial-gauge">
|
|
108
|
+
<svg viewBox="0 0 100 55" class="ospt-radial-svg">
|
|
109
|
+
<path d="M 10 50 A 40 40 0 0 1 90 50" fill="none" stroke-width="8" stroke-linecap="round" class="ospt-radial-bg"/>
|
|
110
|
+
<path d="M 10 50 A 40 40 0 0 1 90 50" fill="none" stroke="var(--ospt-green)" stroke-width="8" stroke-dasharray="53.8 197.52" stroke-dashoffset="0" stroke-linecap="round"/>
|
|
111
|
+
<path d="M 10 50 A 40 40 0 0 1 90 50" fill="none" stroke="var(--ospt-gold)" stroke-width="8" stroke-dasharray="36.0 215.32" stroke-dashoffset="-53.8"/>
|
|
112
|
+
<path d="M 10 50 A 40 40 0 0 1 90 50" fill="none" stroke="var(--ospt-red)" stroke-width="8" stroke-dasharray="36.0 215.32" stroke-dashoffset="-89.8" stroke-linecap="round"/>
|
|
113
|
+
<g id="ospt-radial-needle-group" class="ospt-radial-needle-group" transform="translate(50, 50) rotate(-90)">
|
|
114
|
+
<line x1="0" y1="0" x2="0" y2="-42" stroke="var(--ospt-ink)" stroke-width="1.5" stroke-linecap="round"/>
|
|
115
|
+
<circle cx="0" cy="0" r="4" fill="var(--ospt-ink)"/>
|
|
116
|
+
<circle cx="0" cy="0" r="1.5" fill="#ffffff"/>
|
|
117
|
+
</g>
|
|
118
|
+
</svg>
|
|
119
|
+
<div class="ospt-radial-value-container">
|
|
120
|
+
<span class="ospt-radial-val" id="ospt-health-val">100%</span>
|
|
121
|
+
<span class="ospt-radial-desc" id="ospt-health-desc">Safe</span>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<div class="ospt-grid-values">
|
|
128
|
+
<div class="ospt-stat-box">
|
|
129
|
+
<span class="ospt-stat-label">{ui.baseSmokePointLabel}</span>
|
|
130
|
+
<span class="ospt-stat-value" id="ospt-base-smoke-point">232°C</span>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<div class="ospt-stat-box">
|
|
134
|
+
<span class="ospt-stat-label">{ui.currentSmokePointLabel}</span>
|
|
135
|
+
<span class="ospt-stat-value" id="ospt-current-smoke-point">232°C</span>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<div class="ospt-tpc-curve-container">
|
|
140
|
+
<div class="ospt-tpc-title">{ui.polarCompoundsLabel}</div>
|
|
141
|
+
<div class="ospt-tpc-bar-wrap">
|
|
142
|
+
<div class="ospt-tpc-limit-line"></div>
|
|
143
|
+
<div class="ospt-tpc-current-marker" id="ospt-tpc-marker"></div>
|
|
144
|
+
</div>
|
|
145
|
+
<div class="ospt-tpc-labels">
|
|
146
|
+
<span style="left: 5.7%;">2%</span>
|
|
147
|
+
<span style="left: 42.8%;">15%</span>
|
|
148
|
+
<span class="limit" style="left: 71.4%;">25% (Limit)</span>
|
|
149
|
+
<span style="left: 100%;">35%</span>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<div class="ospt-status-card good" id="ospt-status-card">
|
|
154
|
+
<div class="ospt-status-header">
|
|
155
|
+
<span class="ospt-stat-label">{ui.statusLabel}</span>
|
|
156
|
+
<span class="ospt-badge good" id="ospt-status-badge">SAFE TO REUSE</span>
|
|
157
|
+
</div>
|
|
158
|
+
<div class="ospt-advice" id="ospt-advice-text">
|
|
159
|
+
{ui.adviceGood}
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<script is:inline define:vars={{ ui }}>
|
|
168
|
+
const getEl = (id) => document.getElementById(id);
|
|
169
|
+
|
|
170
|
+
const presets = document.querySelectorAll('.ospt-oil-item');
|
|
171
|
+
const usesInput = getEl('ospt-uses');
|
|
172
|
+
const tempInput = getEl('ospt-temp');
|
|
173
|
+
const radioButtons = document.querySelectorAll('.ospt-radio-btn');
|
|
174
|
+
|
|
175
|
+
const usesVal = getEl('ospt-uses-val');
|
|
176
|
+
const tempVal = getEl('ospt-temp-val');
|
|
177
|
+
|
|
178
|
+
const unitMetricBtn = getEl('ospt-unit-metric');
|
|
179
|
+
const unitImperialBtn = getEl('ospt-unit-imperial');
|
|
180
|
+
|
|
181
|
+
const thermoFluid = getEl('ospt-thermo-fluid');
|
|
182
|
+
const smokeMarker = getEl('ospt-smoke-marker');
|
|
183
|
+
const smokeLabel = getEl('ospt-smoke-label');
|
|
184
|
+
const smokeContainer = getEl('ospt-smoke-container');
|
|
185
|
+
|
|
186
|
+
const thermoDanger = getEl('ospt-thermo-danger');
|
|
187
|
+
const needleGroup = getEl('ospt-radial-needle-group');
|
|
188
|
+
const tpcMarker = getEl('ospt-tpc-marker');
|
|
189
|
+
const healthVal = getEl('ospt-health-val');
|
|
190
|
+
const healthDesc = getEl('ospt-health-desc');
|
|
191
|
+
|
|
192
|
+
const statusCard = getEl('ospt-status-card');
|
|
193
|
+
const statusBadge = getEl('ospt-status-badge');
|
|
194
|
+
const baseSmokePointDisp = getEl('ospt-base-smoke-point');
|
|
195
|
+
const currentSmokePointDisp = getEl('ospt-current-smoke-point');
|
|
196
|
+
const adviceTextDisp = getEl('ospt-advice-text');
|
|
197
|
+
|
|
198
|
+
let activeOilId = 'sunflower-refined';
|
|
199
|
+
let activeFoodType = 'starch';
|
|
200
|
+
let activeUnit = 'metric';
|
|
201
|
+
|
|
202
|
+
const presetsData = {
|
|
203
|
+
'avocado-refined': { baseC: 270, refined: true },
|
|
204
|
+
'sunflower-refined': { baseC: 232, refined: true },
|
|
205
|
+
'peanut-refined': { baseC: 232, refined: true },
|
|
206
|
+
'canola-refined': { baseC: 204, refined: true },
|
|
207
|
+
'olive-extra-virgin': { baseC: 190, refined: false },
|
|
208
|
+
'olive-pomace': { baseC: 238, refined: true },
|
|
209
|
+
'coconut-unrefined': { baseC: 177, refined: false },
|
|
210
|
+
'sunflower-unrefined': { baseC: 107, refined: false }
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
function cToF(c) {
|
|
214
|
+
return Math.round(c * 1.8 + 32);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function formatTemp(c) {
|
|
218
|
+
const r = Math.round(c);
|
|
219
|
+
if (activeUnit === 'imperial') {
|
|
220
|
+
return cToF(r) + '°F';
|
|
221
|
+
}
|
|
222
|
+
return r + '°C';
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function getTempC() {
|
|
226
|
+
const val = parseInt(tempInput.value) || 175;
|
|
227
|
+
return activeUnit === 'imperial' ? Math.round((val - 32) / 1.8) : val;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function updateDisplayValues() {
|
|
231
|
+
usesVal.textContent = usesInput.value;
|
|
232
|
+
tempVal.textContent = tempInput.value;
|
|
233
|
+
|
|
234
|
+
const unitLabels = document.querySelectorAll('.ospt-temp-unit');
|
|
235
|
+
unitLabels.forEach((el) => {
|
|
236
|
+
el.textContent = activeUnit === 'imperial' ? '°F' : '°C';
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const ticks = document.querySelectorAll('.ospt-tick-val');
|
|
240
|
+
ticks.forEach((tick) => {
|
|
241
|
+
const c = parseInt(tick.getAttribute('data-c'));
|
|
242
|
+
tick.textContent = activeUnit === 'imperial' ? cToF(c) + '°F' : c + '°C';
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function getStatus(polarCompounds, currentC) {
|
|
247
|
+
if (polarCompounds >= 25 || currentC <= 170) {
|
|
248
|
+
return 'discard';
|
|
249
|
+
}
|
|
250
|
+
if (polarCompounds >= 15 || currentC <= 190) {
|
|
251
|
+
return 'caution';
|
|
252
|
+
}
|
|
253
|
+
return 'good';
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function updateUI(data) {
|
|
257
|
+
const { baseC, currentC, polarCompounds, healthPercent, status, uses, tempC } = data;
|
|
258
|
+
if (statusCard) {
|
|
259
|
+
statusCard.className = 'ospt-status-card ' + status;
|
|
260
|
+
}
|
|
261
|
+
statusBadge.textContent = ui['status' + status.charAt(0).toUpperCase() + status.slice(1)];
|
|
262
|
+
statusBadge.className = 'ospt-badge ' + status;
|
|
263
|
+
|
|
264
|
+
baseSmokePointDisp.textContent = formatTemp(baseC);
|
|
265
|
+
currentSmokePointDisp.textContent = formatTemp(currentC);
|
|
266
|
+
adviceTextDisp.textContent = ui['advice' + status.charAt(0).toUpperCase() + status.slice(1)];
|
|
267
|
+
|
|
268
|
+
healthVal.textContent = healthPercent + '%';
|
|
269
|
+
const descMap = { good: 'Safe', caution: 'Caution', discard: 'Discard' };
|
|
270
|
+
healthDesc.textContent = descMap[status];
|
|
271
|
+
|
|
272
|
+
if (needleGroup) {
|
|
273
|
+
const angle = 90 - (healthPercent / 100) * 180;
|
|
274
|
+
needleGroup.setAttribute('transform', `translate(50, 50) rotate(${angle})`);
|
|
275
|
+
}
|
|
276
|
+
updateGaugeAndSliders(currentC, polarCompounds, uses, tempC);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function updateGaugeAndSliders(currentC, polarCompounds, uses, tempC) {
|
|
280
|
+
const fluidPct = Math.max(0, Math.min(100, (tempC - 100) / 130 * 100));
|
|
281
|
+
thermoFluid.style.height = fluidPct + '%';
|
|
282
|
+
|
|
283
|
+
const markerPct = Math.max(0, Math.min(100, (currentC - 100) / 130 * 100));
|
|
284
|
+
smokeMarker.style.bottom = markerPct + '%';
|
|
285
|
+
smokeLabel.textContent = formatTemp(currentC);
|
|
286
|
+
|
|
287
|
+
if (thermoDanger) {
|
|
288
|
+
thermoDanger.style.bottom = markerPct + '%';
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (tpcMarker) {
|
|
292
|
+
const tpcPct = Math.max(0, Math.min(100, (polarCompounds / 35) * 100));
|
|
293
|
+
tpcMarker.style.left = tpcPct + '%';
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
usesInput.style.setProperty('--uses-pct', (uses / 15 * 100) + '%');
|
|
297
|
+
|
|
298
|
+
const minC = 100;
|
|
299
|
+
const maxC = 230;
|
|
300
|
+
const rangeC = maxC - minC;
|
|
301
|
+
const safeTemp = currentC - 30;
|
|
302
|
+
const safePct = Math.max(0, Math.min(100, (safeTemp - minC) / rangeC * 100));
|
|
303
|
+
const dangerPct = Math.max(0, Math.min(100, (currentC - minC) / rangeC * 100));
|
|
304
|
+
|
|
305
|
+
tempInput.style.setProperty('--temp-safe-pct', safePct + '%');
|
|
306
|
+
tempInput.style.setProperty('--temp-danger-pct', dangerPct + '%');
|
|
307
|
+
|
|
308
|
+
if (tempC >= currentC) {
|
|
309
|
+
smokeContainer.classList.add('smoking');
|
|
310
|
+
} else {
|
|
311
|
+
smokeContainer.classList.remove('smoking');
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function calculate() {
|
|
316
|
+
const preset = presetsData[activeOilId] || presetsData['sunflower-refined'];
|
|
317
|
+
const baseC = preset.baseC;
|
|
318
|
+
const uses = parseInt(usesInput.value) || 0;
|
|
319
|
+
const tempC = getTempC();
|
|
320
|
+
|
|
321
|
+
const baseDegradation = preset.refined ? 4 : 9;
|
|
322
|
+
const tempFactor = 1 + Math.max(0, tempC - 180) * 0.06;
|
|
323
|
+
const foodFactor = activeFoodType === 'breaded' ? 2.2 : 1.0;
|
|
324
|
+
|
|
325
|
+
const degradationPerUse = baseDegradation * tempFactor * foodFactor;
|
|
326
|
+
const currentC = Math.max(60, baseC - uses * degradationPerUse);
|
|
327
|
+
|
|
328
|
+
const basePolarIncrease = preset.refined ? 1.5 : 3.0;
|
|
329
|
+
const polarCompounds = Math.min(100, 2 + uses * basePolarIncrease * tempFactor * foodFactor);
|
|
330
|
+
const healthPercent = Math.max(0, Math.round(100 - (polarCompounds / 35) * 100));
|
|
331
|
+
|
|
332
|
+
const status = getStatus(polarCompounds, currentC);
|
|
333
|
+
|
|
334
|
+
updateUI({ baseC, currentC, polarCompounds, healthPercent, status, uses, tempC });
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const STORAGE_KEY = 'ospt-v1';
|
|
338
|
+
|
|
339
|
+
function save() {
|
|
340
|
+
try {
|
|
341
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify({
|
|
342
|
+
oil: activeOilId,
|
|
343
|
+
uses: usesInput.value,
|
|
344
|
+
temp: getTempC(),
|
|
345
|
+
food: activeFoodType,
|
|
346
|
+
unit: activeUnit
|
|
347
|
+
}));
|
|
348
|
+
} catch {}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function restoreSettings(v) {
|
|
352
|
+
if (v.oil) {
|
|
353
|
+
activeOilId = v.oil;
|
|
354
|
+
presets.forEach((btn) => {
|
|
355
|
+
btn.classList.toggle('active', btn.getAttribute('data-id') === activeOilId);
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
if (v.uses) usesInput.value = v.uses;
|
|
359
|
+
if (v.food) {
|
|
360
|
+
activeFoodType = v.food;
|
|
361
|
+
radioButtons.forEach((btn) => {
|
|
362
|
+
btn.classList.toggle('active', btn.getAttribute('data-type') === activeFoodType);
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
if (v.unit) activeUnit = v.unit;
|
|
366
|
+
if (v.temp) {
|
|
367
|
+
tempInput.value = activeUnit === 'imperial' ? cToF(parseInt(v.temp)) : parseInt(v.temp);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function load() {
|
|
372
|
+
try {
|
|
373
|
+
const d = localStorage.getItem(STORAGE_KEY);
|
|
374
|
+
if (!d) return;
|
|
375
|
+
const v = JSON.parse(d);
|
|
376
|
+
restoreSettings(v);
|
|
377
|
+
} catch {}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function refresh() {
|
|
381
|
+
updateDisplayValues();
|
|
382
|
+
calculate();
|
|
383
|
+
save();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function toggleUnit(unit) {
|
|
387
|
+
const tempC = getTempC();
|
|
388
|
+
activeUnit = unit;
|
|
389
|
+
unitMetricBtn.classList.toggle('active', unit === 'metric');
|
|
390
|
+
unitImperialBtn.classList.toggle('active', unit === 'imperial');
|
|
391
|
+
if (unit === 'imperial') {
|
|
392
|
+
tempInput.min = 212;
|
|
393
|
+
tempInput.max = 446;
|
|
394
|
+
tempInput.step = 10;
|
|
395
|
+
tempInput.value = cToF(tempC);
|
|
396
|
+
} else {
|
|
397
|
+
tempInput.min = 100;
|
|
398
|
+
tempInput.max = 230;
|
|
399
|
+
tempInput.step = 5;
|
|
400
|
+
tempInput.value = tempC;
|
|
401
|
+
}
|
|
402
|
+
refresh();
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
unitMetricBtn.addEventListener('click', () => toggleUnit('metric'));
|
|
406
|
+
unitImperialBtn.addEventListener('click', () => toggleUnit('imperial'));
|
|
407
|
+
|
|
408
|
+
presets.forEach((btn) => {
|
|
409
|
+
btn.addEventListener('click', () => {
|
|
410
|
+
presets.forEach((p) => p.classList.remove('active'));
|
|
411
|
+
btn.classList.add('active');
|
|
412
|
+
activeOilId = btn.getAttribute('data-id');
|
|
413
|
+
refresh();
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
radioButtons.forEach((btn) => {
|
|
418
|
+
btn.addEventListener('click', () => {
|
|
419
|
+
radioButtons.forEach((r) => r.classList.remove('active'));
|
|
420
|
+
btn.classList.add('active');
|
|
421
|
+
activeFoodType = btn.getAttribute('data-type');
|
|
422
|
+
refresh();
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
document.querySelectorAll('.ospt-adjuster-btn').forEach((btn) => {
|
|
427
|
+
btn.addEventListener('click', () => {
|
|
428
|
+
const targetId = btn.getAttribute('data-target');
|
|
429
|
+
const dir = btn.getAttribute('data-dir');
|
|
430
|
+
const slider = getEl(targetId);
|
|
431
|
+
if (!slider) return;
|
|
432
|
+
const step = parseFloat(slider.getAttribute('step')) || 1;
|
|
433
|
+
const current = parseFloat(slider.value) || 0;
|
|
434
|
+
const next = dir === 'up' ? current + step : current - step;
|
|
435
|
+
slider.value = Math.max(parseFloat(slider.min), Math.min(parseFloat(slider.max), next));
|
|
436
|
+
refresh();
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
usesInput.addEventListener('input', refresh);
|
|
441
|
+
tempInput.addEventListener('input', refresh);
|
|
442
|
+
|
|
443
|
+
load();
|
|
444
|
+
toggleUnit(activeUnit);
|
|
445
|
+
</script>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CookingToolEntry } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const oilSmokePoint: CookingToolEntry = {
|
|
4
|
+
id: 'oil-smoke-point-tracker',
|
|
5
|
+
icons: {
|
|
6
|
+
bg: 'mdi:oil',
|
|
7
|
+
fg: 'mdi:fire-alert',
|
|
8
|
+
},
|
|
9
|
+
i18n: {
|
|
10
|
+
es: () => import('./i18n/es').then((m) => m.content),
|
|
11
|
+
en: () => import('./i18n/en').then((m) => m.content),
|
|
12
|
+
fr: () => import('./i18n/fr').then((m) => m.content),
|
|
13
|
+
de: () => import('./i18n/de').then((m) => m.content),
|
|
14
|
+
it: () => import('./i18n/it').then((m) => m.content),
|
|
15
|
+
pt: () => import('./i18n/pt').then((m) => m.content),
|
|
16
|
+
nl: () => import('./i18n/nl').then((m) => m.content),
|
|
17
|
+
sv: () => import('./i18n/sv').then((m) => m.content),
|
|
18
|
+
ru: () => import('./i18n/ru').then((m) => m.content),
|
|
19
|
+
tr: () => import('./i18n/tr').then((m) => m.content),
|
|
20
|
+
pl: () => import('./i18n/pl').then((m) => m.content),
|
|
21
|
+
id: () => import('./i18n/id').then((m) => m.content),
|
|
22
|
+
ja: () => import('./i18n/ja').then((m) => m.content),
|
|
23
|
+
zh: () => import('./i18n/zh').then((m) => m.content),
|
|
24
|
+
ko: () => import('./i18n/ko').then((m) => m.content),
|
|
25
|
+
},
|
|
26
|
+
};
|