@jjlmoya/utils-home 1.1.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 +62 -0
- package/src/category/i18n/en.ts +24 -0
- package/src/category/i18n/es.ts +24 -0
- package/src/category/i18n/fr.ts +24 -0
- package/src/category/index.ts +12 -0
- package/src/category/seo.astro +15 -0
- package/src/components/PreviewNavSidebar.astro +116 -0
- package/src/components/PreviewToolbar.astro +143 -0
- package/src/data.ts +11 -0
- package/src/env.d.ts +5 -0
- package/src/index.ts +26 -0
- package/src/layouts/PreviewLayout.astro +117 -0
- package/src/pages/[locale]/[slug].astro +146 -0
- package/src/pages/[locale].astro +251 -0
- package/src/pages/index.astro +4 -0
- package/src/tests/faq_count.test.ts +19 -0
- package/src/tests/locale_completeness.test.ts +42 -0
- package/src/tests/mocks/astro_mock.js +2 -0
- package/src/tests/no_h1_in_components.test.ts +48 -0
- package/src/tests/seo_length.test.ts +22 -0
- package/src/tests/tool_validation.test.ts +17 -0
- package/src/tool/dewPointCalculator/bibliography.astro +14 -0
- package/src/tool/dewPointCalculator/component.astro +443 -0
- package/src/tool/dewPointCalculator/i18n/en.ts +183 -0
- package/src/tool/dewPointCalculator/i18n/es.ts +183 -0
- package/src/tool/dewPointCalculator/i18n/fr.ts +183 -0
- package/src/tool/dewPointCalculator/index.ts +34 -0
- package/src/tool/dewPointCalculator/logic.ts +16 -0
- package/src/tool/dewPointCalculator/seo.astro +14 -0
- package/src/tool/dewPointCalculator/ui.ts +13 -0
- package/src/tool/ledSavingCalculator/bibliography.astro +14 -0
- package/src/tool/ledSavingCalculator/component.astro +520 -0
- package/src/tool/ledSavingCalculator/i18n/en.ts +217 -0
- package/src/tool/ledSavingCalculator/i18n/es.ts +217 -0
- package/src/tool/ledSavingCalculator/i18n/fr.ts +217 -0
- package/src/tool/ledSavingCalculator/index.ts +34 -0
- package/src/tool/ledSavingCalculator/logic.ts +31 -0
- package/src/tool/ledSavingCalculator/seo.astro +14 -0
- package/src/tool/ledSavingCalculator/ui.ts +32 -0
- package/src/tool/projectorCalculator/bibliography.astro +14 -0
- package/src/tool/projectorCalculator/component.astro +569 -0
- package/src/tool/projectorCalculator/i18n/en.ts +181 -0
- package/src/tool/projectorCalculator/i18n/es.ts +181 -0
- package/src/tool/projectorCalculator/i18n/fr.ts +181 -0
- package/src/tool/projectorCalculator/index.ts +34 -0
- package/src/tool/projectorCalculator/logic.ts +21 -0
- package/src/tool/projectorCalculator/seo.astro +14 -0
- package/src/tool/projectorCalculator/ui.ts +16 -0
- package/src/tool/qrGenerator/bibliography.astro +14 -0
- package/src/tool/qrGenerator/component.astro +499 -0
- package/src/tool/qrGenerator/i18n/en.ts +233 -0
- package/src/tool/qrGenerator/i18n/es.ts +233 -0
- package/src/tool/qrGenerator/i18n/fr.ts +233 -0
- package/src/tool/qrGenerator/index.ts +34 -0
- package/src/tool/qrGenerator/logic.ts +27 -0
- package/src/tool/qrGenerator/seo.astro +14 -0
- package/src/tool/qrGenerator/ui.ts +23 -0
- package/src/tool/solarCalculator/bibliography.astro +14 -0
- package/src/tool/solarCalculator/component.astro +532 -0
- package/src/tool/solarCalculator/i18n/en.ts +176 -0
- package/src/tool/solarCalculator/i18n/es.ts +176 -0
- package/src/tool/solarCalculator/i18n/fr.ts +176 -0
- package/src/tool/solarCalculator/index.ts +34 -0
- package/src/tool/solarCalculator/logic.ts +31 -0
- package/src/tool/solarCalculator/seo.astro +14 -0
- package/src/tool/solarCalculator/ui.ts +11 -0
- package/src/tool/tariffComparator/bibliography.astro +14 -0
- package/src/tool/tariffComparator/component.astro +595 -0
- package/src/tool/tariffComparator/i18n/en.ts +192 -0
- package/src/tool/tariffComparator/i18n/es.ts +192 -0
- package/src/tool/tariffComparator/i18n/fr.ts +192 -0
- package/src/tool/tariffComparator/index.ts +34 -0
- package/src/tool/tariffComparator/logic.ts +47 -0
- package/src/tool/tariffComparator/seo.astro +14 -0
- package/src/tool/tariffComparator/ui.ts +25 -0
- package/src/tools.ts +9 -0
- package/src/types.ts +72 -0
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { DewPointCalculatorUI } from './ui';
|
|
3
|
+
import { calculateDewPoint, getMoldRisk } from './logic';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
ui?: Record<string, unknown>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { ui = {} } = Astro.props;
|
|
10
|
+
const dUI = ui as DewPointCalculatorUI;
|
|
11
|
+
|
|
12
|
+
const initialTemp = 22;
|
|
13
|
+
const initialHumidity = 60;
|
|
14
|
+
const initialDew = calculateDewPoint(initialTemp, initialHumidity);
|
|
15
|
+
const initialRisk = getMoldRisk(initialTemp, initialDew);
|
|
16
|
+
|
|
17
|
+
function capitalize(s: string) {
|
|
18
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
19
|
+
}
|
|
20
|
+
const riskLabelKey = `risk${capitalize(initialRisk)}` as keyof DewPointCalculatorUI;
|
|
21
|
+
const riskDescKey = `risk${capitalize(initialRisk)}Desc` as keyof DewPointCalculatorUI;
|
|
22
|
+
|
|
23
|
+
const TRACK_X = 20;
|
|
24
|
+
const TRACK_W = 260;
|
|
25
|
+
const TRACK_Y = 34;
|
|
26
|
+
const tempX = TRACK_X + (initialTemp / 50) * TRACK_W;
|
|
27
|
+
const dewX = TRACK_X + (Math.max(0, Math.min(50, initialDew)) / 50) * TRACK_W;
|
|
28
|
+
const fillX = Math.min(dewX, tempX);
|
|
29
|
+
const fillW = Math.abs(tempX - dewX);
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
<div class="dew-wrapper">
|
|
33
|
+
<div
|
|
34
|
+
class="dew-card"
|
|
35
|
+
data-risk-low={dUI.riskLow}
|
|
36
|
+
data-risk-medium={dUI.riskMedium}
|
|
37
|
+
data-risk-high={dUI.riskHigh}
|
|
38
|
+
data-risk-extreme={dUI.riskExtreme}
|
|
39
|
+
data-risk-low-desc={dUI.riskLowDesc}
|
|
40
|
+
data-risk-medium-desc={dUI.riskMediumDesc}
|
|
41
|
+
data-risk-high-desc={dUI.riskHighDesc}
|
|
42
|
+
data-risk-extreme-desc={dUI.riskExtremeDesc}
|
|
43
|
+
>
|
|
44
|
+
<div class="dew-left">
|
|
45
|
+
<div class="dew-field">
|
|
46
|
+
<div class="dew-field-top">
|
|
47
|
+
<label class="dew-label" for="dew-temp">{dUI.labelTemperature}</label>
|
|
48
|
+
<span class="dew-val-display">
|
|
49
|
+
<span id="dew-temp-num">{initialTemp}</span><span class="dew-unit"> °C</span>
|
|
50
|
+
</span>
|
|
51
|
+
</div>
|
|
52
|
+
<input type="range" id="dew-temp" min="0" max="50" step="0.5" value={initialTemp} class="dew-slider" />
|
|
53
|
+
<div class="dew-steps">
|
|
54
|
+
<button class="dew-step" id="dew-temp-minus" aria-label="decrease temperature">−</button>
|
|
55
|
+
<button class="dew-step" id="dew-temp-plus" aria-label="increase temperature">+</button>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div class="dew-field">
|
|
60
|
+
<div class="dew-field-top">
|
|
61
|
+
<label class="dew-label" for="dew-hum">{dUI.labelHumidity}</label>
|
|
62
|
+
<span class="dew-val-display">
|
|
63
|
+
<span id="dew-hum-num">{initialHumidity}</span><span class="dew-unit"> %</span>
|
|
64
|
+
</span>
|
|
65
|
+
</div>
|
|
66
|
+
<input type="range" id="dew-hum" min="10" max="95" step="1" value={initialHumidity} class="dew-slider" />
|
|
67
|
+
<div class="dew-steps">
|
|
68
|
+
<button class="dew-step" id="dew-hum-minus" aria-label="decrease humidity">−</button>
|
|
69
|
+
<button class="dew-step" id="dew-hum-plus" aria-label="increase humidity">+</button>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<div class="dew-right">
|
|
75
|
+
<div class="dew-result-section">
|
|
76
|
+
<p class="dew-result-label">{dUI.labelDewPoint}</p>
|
|
77
|
+
<p class="dew-result-value">
|
|
78
|
+
<span id="dew-display-value">{initialDew.toFixed(1)}</span><span class="dew-result-unit"> °C</span>
|
|
79
|
+
</p>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<div id="dew-risk-badge" class={`dew-risk-badge dew-risk-${initialRisk}`}>
|
|
83
|
+
<span class="dew-risk-dot"></span>
|
|
84
|
+
<div class="dew-risk-text">
|
|
85
|
+
<span id="dew-risk-label" class="dew-risk-name">{dUI[riskLabelKey]}</span>
|
|
86
|
+
<span id="dew-risk-desc" class="dew-risk-desc">{dUI[riskDescKey]}</span>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<svg class="dew-gauge" viewBox="0 0 300 80" preserveAspectRatio="xMidYMid meet">
|
|
91
|
+
<rect class="dew-gauge-track" x="20" y={TRACK_Y - 4} width="260" height="8" rx="4"></rect>
|
|
92
|
+
<rect id="dew-gauge-fill" x={fillX} y={TRACK_Y - 4} width={fillW} height="8" rx="4" fill="#22c55e"></rect>
|
|
93
|
+
<circle id="dew-gauge-dew" cx={dewX} cy={TRACK_Y} r="6" fill="#06b6d4"></circle>
|
|
94
|
+
<circle id="dew-gauge-temp" cx={tempX} cy={TRACK_Y} r="8" fill="#8b5cf6"></circle>
|
|
95
|
+
<text x="20" y="66" fill="#64748b" font-size="9" text-anchor="start">0°C</text>
|
|
96
|
+
<text x="280" y="66" fill="#64748b" font-size="9" text-anchor="middle">50°C</text>
|
|
97
|
+
<text id="dew-gauge-temp-txt" x={tempX} y="20" fill="#8b5cf6" font-size="9" text-anchor="middle">{initialTemp}°C</text>
|
|
98
|
+
<text id="dew-gauge-dew-txt" x={dewX} y="20" fill="#06b6d4" font-size="9" text-anchor="middle">{initialDew.toFixed(1)}°C</text>
|
|
99
|
+
</svg>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<script>
|
|
105
|
+
import { calculateDewPoint, getMoldRisk } from './logic';
|
|
106
|
+
|
|
107
|
+
const TRACK_X = 20;
|
|
108
|
+
const TRACK_W = 260;
|
|
109
|
+
|
|
110
|
+
const RISK_COLORS: Record<string, string> = {
|
|
111
|
+
low: '#22c55e',
|
|
112
|
+
medium: '#f59e0b',
|
|
113
|
+
high: '#f97316',
|
|
114
|
+
extreme: '#ef4444',
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
function setTxt(id: string, val: string) {
|
|
118
|
+
const el = document.getElementById(id);
|
|
119
|
+
if (el) el.textContent = val;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function setAttr(id: string, attr: string, val: string) {
|
|
123
|
+
const el = document.getElementById(id);
|
|
124
|
+
if (el) el.setAttribute(attr, val);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function toX(val: number): number {
|
|
128
|
+
return TRACK_X + (Math.max(0, Math.min(50, val)) / 50) * TRACK_W;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function cap(s: string): string {
|
|
132
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function updateGauge(temp: number, dew: number, risk: string) {
|
|
136
|
+
const tX = toX(temp);
|
|
137
|
+
const dX = toX(dew);
|
|
138
|
+
const color = RISK_COLORS[risk] ?? '#22c55e';
|
|
139
|
+
|
|
140
|
+
setAttr('dew-gauge-fill', 'x', String(Math.min(dX, tX)));
|
|
141
|
+
setAttr('dew-gauge-fill', 'width', String(Math.abs(tX - dX)));
|
|
142
|
+
setAttr('dew-gauge-fill', 'fill', color);
|
|
143
|
+
setAttr('dew-gauge-temp', 'cx', String(tX));
|
|
144
|
+
setAttr('dew-gauge-dew', 'cx', String(dX));
|
|
145
|
+
setAttr('dew-gauge-temp-txt', 'x', String(tX));
|
|
146
|
+
setAttr('dew-gauge-dew-txt', 'x', String(dX));
|
|
147
|
+
setTxt('dew-gauge-temp-txt', `${temp}°C`);
|
|
148
|
+
setTxt('dew-gauge-dew-txt', `${dew.toFixed(1)}°C`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function updateRisk(risk: string, card: HTMLElement) {
|
|
152
|
+
const badge = document.getElementById('dew-risk-badge');
|
|
153
|
+
if (!badge) return;
|
|
154
|
+
badge.className = `dew-risk-badge dew-risk-${risk}`;
|
|
155
|
+
const label = card.dataset[`risk${cap(risk)}`] ?? '';
|
|
156
|
+
const desc = card.dataset[`risk${cap(risk)}Desc`] ?? '';
|
|
157
|
+
setTxt('dew-risk-label', label);
|
|
158
|
+
setTxt('dew-risk-desc', desc);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function calculate(card: HTMLElement) {
|
|
162
|
+
const tempEl = document.getElementById('dew-temp') as HTMLInputElement | null;
|
|
163
|
+
const humEl = document.getElementById('dew-hum') as HTMLInputElement | null;
|
|
164
|
+
if (!tempEl || !humEl) return;
|
|
165
|
+
const temp = parseFloat(tempEl.value);
|
|
166
|
+
const hum = parseFloat(humEl.value);
|
|
167
|
+
if (isNaN(temp) || isNaN(hum)) return;
|
|
168
|
+
const dew = calculateDewPoint(temp, hum);
|
|
169
|
+
const risk = getMoldRisk(temp, dew);
|
|
170
|
+
setTxt('dew-display-value', dew.toFixed(1));
|
|
171
|
+
setTxt('dew-temp-num', temp % 1 === 0 ? String(temp) : temp.toFixed(1));
|
|
172
|
+
setTxt('dew-hum-num', String(hum));
|
|
173
|
+
updateGauge(temp, dew, risk);
|
|
174
|
+
updateRisk(risk, card);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function attachStep(btnId: string, sliderId: string, delta: number) {
|
|
178
|
+
const btn = document.getElementById(btnId);
|
|
179
|
+
const slider = document.getElementById(sliderId) as HTMLInputElement | null;
|
|
180
|
+
if (!btn || !slider) return;
|
|
181
|
+
btn.addEventListener('click', () => {
|
|
182
|
+
const min = parseFloat(slider.min);
|
|
183
|
+
const max = parseFloat(slider.max);
|
|
184
|
+
const newVal = Math.max(min, Math.min(max, Math.round((parseFloat(slider.value) + delta) * 10) / 10));
|
|
185
|
+
slider.value = String(newVal);
|
|
186
|
+
slider.dispatchEvent(new Event('input'));
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function init() {
|
|
191
|
+
const card = document.querySelector('.dew-card') as HTMLElement | null;
|
|
192
|
+
if (!card) return;
|
|
193
|
+
const tempSlider = document.getElementById('dew-temp') as HTMLInputElement | null;
|
|
194
|
+
const humSlider = document.getElementById('dew-hum') as HTMLInputElement | null;
|
|
195
|
+
if (!tempSlider || !humSlider) return;
|
|
196
|
+
|
|
197
|
+
tempSlider.addEventListener('input', () => calculate(card));
|
|
198
|
+
humSlider.addEventListener('input', () => calculate(card));
|
|
199
|
+
|
|
200
|
+
attachStep('dew-temp-minus', 'dew-temp', -0.5);
|
|
201
|
+
attachStep('dew-temp-plus', 'dew-temp', 0.5);
|
|
202
|
+
attachStep('dew-hum-minus', 'dew-hum', -1);
|
|
203
|
+
attachStep('dew-hum-plus', 'dew-hum', 1);
|
|
204
|
+
|
|
205
|
+
calculate(card);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
document.addEventListener('astro:page-load', init);
|
|
209
|
+
init();
|
|
210
|
+
</script>
|
|
211
|
+
|
|
212
|
+
<style>
|
|
213
|
+
.dew-wrapper {
|
|
214
|
+
--dew-p: #8b5cf6;
|
|
215
|
+
|
|
216
|
+
width: 100%;
|
|
217
|
+
padding: 1rem 0;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.dew-card {
|
|
221
|
+
background: var(--bg-surface);
|
|
222
|
+
width: calc(100% - 24px);
|
|
223
|
+
max-width: 900px;
|
|
224
|
+
margin: 0 auto;
|
|
225
|
+
border-radius: 24px;
|
|
226
|
+
overflow: hidden;
|
|
227
|
+
display: flex;
|
|
228
|
+
flex-direction: column;
|
|
229
|
+
border: 1px solid var(--border-color);
|
|
230
|
+
color: var(--text-main);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
@media (min-width: 768px) {
|
|
234
|
+
.dew-card {
|
|
235
|
+
flex-direction: row;
|
|
236
|
+
min-height: 500px;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.dew-left {
|
|
241
|
+
flex: 0 0 auto;
|
|
242
|
+
width: 100%;
|
|
243
|
+
padding: 32px;
|
|
244
|
+
border-bottom: 1px solid var(--border-color);
|
|
245
|
+
display: flex;
|
|
246
|
+
flex-direction: column;
|
|
247
|
+
gap: 32px;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
@media (min-width: 768px) {
|
|
251
|
+
.dew-left {
|
|
252
|
+
width: 340px;
|
|
253
|
+
border-bottom: none;
|
|
254
|
+
border-right: 1px solid var(--border-color);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.dew-right {
|
|
259
|
+
flex: 1;
|
|
260
|
+
background: var(--bg-page);
|
|
261
|
+
display: flex;
|
|
262
|
+
flex-direction: column;
|
|
263
|
+
align-items: center;
|
|
264
|
+
justify-content: center;
|
|
265
|
+
gap: 24px;
|
|
266
|
+
padding: 32px;
|
|
267
|
+
min-height: 360px;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.dew-field {
|
|
271
|
+
display: flex;
|
|
272
|
+
flex-direction: column;
|
|
273
|
+
gap: 12px;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.dew-field-top {
|
|
277
|
+
display: flex;
|
|
278
|
+
justify-content: space-between;
|
|
279
|
+
align-items: flex-end;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.dew-label {
|
|
283
|
+
font-size: 0.6875rem;
|
|
284
|
+
font-weight: 700;
|
|
285
|
+
text-transform: uppercase;
|
|
286
|
+
letter-spacing: 0.1em;
|
|
287
|
+
color: var(--dew-p);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.dew-val-display {
|
|
291
|
+
font-size: 2rem;
|
|
292
|
+
font-weight: 900;
|
|
293
|
+
color: var(--text-main);
|
|
294
|
+
line-height: 1;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.dew-unit {
|
|
298
|
+
font-size: 1rem;
|
|
299
|
+
font-weight: 400;
|
|
300
|
+
color: var(--text-muted);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.dew-slider {
|
|
304
|
+
width: 100%;
|
|
305
|
+
height: 6px;
|
|
306
|
+
accent-color: var(--dew-p);
|
|
307
|
+
cursor: pointer;
|
|
308
|
+
border-radius: 9999px;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.dew-steps {
|
|
312
|
+
display: flex;
|
|
313
|
+
gap: 8px;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.dew-step {
|
|
317
|
+
flex: 1;
|
|
318
|
+
padding: 8px;
|
|
319
|
+
border-radius: 10px;
|
|
320
|
+
border: 1px solid var(--border-color);
|
|
321
|
+
background: var(--bg-surface);
|
|
322
|
+
color: var(--text-main);
|
|
323
|
+
font-size: 1.25rem;
|
|
324
|
+
font-weight: 700;
|
|
325
|
+
cursor: pointer;
|
|
326
|
+
transition: all 0.15s;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.dew-step:hover {
|
|
330
|
+
border-color: var(--dew-p);
|
|
331
|
+
color: var(--dew-p);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.dew-result-section {
|
|
335
|
+
text-align: center;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.dew-result-label {
|
|
339
|
+
font-size: 0.6875rem;
|
|
340
|
+
font-weight: 700;
|
|
341
|
+
text-transform: uppercase;
|
|
342
|
+
letter-spacing: 0.15em;
|
|
343
|
+
color: var(--text-muted);
|
|
344
|
+
margin: 0 0 8px;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.dew-result-value {
|
|
348
|
+
font-size: 5rem;
|
|
349
|
+
font-weight: 900;
|
|
350
|
+
color: #06b6d4;
|
|
351
|
+
line-height: 1;
|
|
352
|
+
margin: 0;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.dew-result-unit {
|
|
356
|
+
font-size: 2.5rem;
|
|
357
|
+
font-weight: 300;
|
|
358
|
+
color: var(--text-muted);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.dew-risk-badge {
|
|
362
|
+
display: flex;
|
|
363
|
+
align-items: center;
|
|
364
|
+
gap: 10px;
|
|
365
|
+
padding: 12px 18px;
|
|
366
|
+
border-radius: 12px;
|
|
367
|
+
width: 100%;
|
|
368
|
+
max-width: 320px;
|
|
369
|
+
transition: background 0.3s;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.dew-risk-dot {
|
|
373
|
+
width: 10px;
|
|
374
|
+
height: 10px;
|
|
375
|
+
border-radius: 50%;
|
|
376
|
+
flex-shrink: 0;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.dew-risk-text {
|
|
380
|
+
display: flex;
|
|
381
|
+
flex-direction: column;
|
|
382
|
+
gap: 2px;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.dew-risk-name {
|
|
386
|
+
font-size: 0.875rem;
|
|
387
|
+
font-weight: 700;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.dew-risk-desc {
|
|
391
|
+
font-size: 0.6875rem;
|
|
392
|
+
opacity: 0.8;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.dew-risk-low {
|
|
396
|
+
background: rgba(34, 197, 94, 0.1);
|
|
397
|
+
border: 1px solid rgba(34, 197, 94, 0.2);
|
|
398
|
+
color: #4ade80;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.dew-risk-low .dew-risk-dot {
|
|
402
|
+
background: #22c55e;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.dew-risk-medium {
|
|
406
|
+
background: rgba(245, 158, 11, 0.1);
|
|
407
|
+
border: 1px solid rgba(245, 158, 11, 0.2);
|
|
408
|
+
color: #fbbf24;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.dew-risk-medium .dew-risk-dot {
|
|
412
|
+
background: #f59e0b;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.dew-risk-high {
|
|
416
|
+
background: rgba(249, 115, 22, 0.1);
|
|
417
|
+
border: 1px solid rgba(249, 115, 22, 0.2);
|
|
418
|
+
color: #fb923c;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.dew-risk-high .dew-risk-dot {
|
|
422
|
+
background: #f97316;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.dew-risk-extreme {
|
|
426
|
+
background: rgba(239, 68, 68, 0.1);
|
|
427
|
+
border: 1px solid rgba(239, 68, 68, 0.2);
|
|
428
|
+
color: #f87171;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.dew-risk-extreme .dew-risk-dot {
|
|
432
|
+
background: #ef4444;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.dew-gauge {
|
|
436
|
+
width: 100%;
|
|
437
|
+
max-width: 320px;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.dew-gauge-track {
|
|
441
|
+
fill: var(--bg-muted);
|
|
442
|
+
}
|
|
443
|
+
</style>
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
import type { DewPointCalculatorUI } from '../ui';
|
|
4
|
+
|
|
5
|
+
const slug = 'dew-point-calculator';
|
|
6
|
+
const title = 'Dew Point Calculator';
|
|
7
|
+
const description =
|
|
8
|
+
'Calculate the condensation temperature on your walls using ambient humidity and temperature. An essential tool for preventing dampness and protecting the structural health of your home.';
|
|
9
|
+
|
|
10
|
+
const faqData = [
|
|
11
|
+
{
|
|
12
|
+
question: 'What exactly is the dew point?',
|
|
13
|
+
answer:
|
|
14
|
+
'It is the temperature to which the air must cool for water vapour to condense into liquid water. The higher the relative humidity, the closer the dew point is to the current temperature.',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
question: 'Why does mould appear in the corners of my home?',
|
|
18
|
+
answer:
|
|
19
|
+
'Corners are often thermal bridges where the wall is colder. If the surface temperature falls below the dew point, liquid water forms. Mould needs that constant moisture to grow.',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
question: 'How can I reduce humidity at home?',
|
|
23
|
+
answer:
|
|
24
|
+
'The most effective method is ventilation — especially after showering or cooking — and using dehumidifiers. Maintaining a constant temperature also helps the air retain more vapour without condensing.',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
question: 'Is condensation mould dangerous?',
|
|
28
|
+
answer:
|
|
29
|
+
'Yes. Mould releases spores that can cause respiratory problems, allergies and asthma. Detecting the condensation risk with this calculator is the first step towards a healthy home.',
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const howToData = [
|
|
34
|
+
{
|
|
35
|
+
name: 'Measure temperature and humidity',
|
|
36
|
+
text: 'Use a thermometer and hygrometer to find the current values for the room.',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'Enter the values',
|
|
40
|
+
text: 'Adjust the temperature in degrees Celsius and the humidity percentage in the calculator.',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'Get the critical temperature',
|
|
44
|
+
text: 'The tool will tell you the exact temperature at which water will start to condense on surfaces.',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'Check surfaces',
|
|
48
|
+
text: 'If you have a laser thermometer, measure your wall temperature. If it is equal to or below the result, you need to ventilate or insulate.',
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
const faqSchema: WithContext<FAQPage> = {
|
|
53
|
+
'@context': 'https://schema.org',
|
|
54
|
+
'@type': 'FAQPage',
|
|
55
|
+
mainEntity: faqData.map((item) => ({
|
|
56
|
+
'@type': 'Question',
|
|
57
|
+
name: item.question,
|
|
58
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
|
|
59
|
+
})),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const howToSchema: WithContext<HowTo> = {
|
|
63
|
+
'@context': 'https://schema.org',
|
|
64
|
+
'@type': 'HowTo',
|
|
65
|
+
name: title,
|
|
66
|
+
description,
|
|
67
|
+
step: howToData.map((step) => ({
|
|
68
|
+
'@type': 'HowToStep',
|
|
69
|
+
name: step.name,
|
|
70
|
+
text: step.text,
|
|
71
|
+
})),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const appSchema: WithContext<SoftwareApplication> = {
|
|
75
|
+
'@context': 'https://schema.org',
|
|
76
|
+
'@type': 'SoftwareApplication',
|
|
77
|
+
name: title,
|
|
78
|
+
description,
|
|
79
|
+
applicationCategory: 'UtilityApplication',
|
|
80
|
+
operatingSystem: 'All',
|
|
81
|
+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
|
|
82
|
+
inLanguage: 'en',
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const content: ToolLocaleContent<DewPointCalculatorUI> = {
|
|
86
|
+
slug,
|
|
87
|
+
title,
|
|
88
|
+
description,
|
|
89
|
+
faqTitle: 'Frequently Asked Questions',
|
|
90
|
+
faq: faqData,
|
|
91
|
+
bibliographyTitle: 'Bibliography',
|
|
92
|
+
bibliography: [
|
|
93
|
+
{
|
|
94
|
+
name: 'Magnus Approximation of the Dew-Point — Meteorological Applications (2011)',
|
|
95
|
+
url: 'https://es.scribd.com/document/331352069/dew-point',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: 'Guide to Meteorological Instruments and Methods of Observation — WMO (2021)',
|
|
99
|
+
url: 'https://community.wmo.int/site/knowledge-hub/programmes-and-initiatives/instruments-and-methods-of-observation-programme-imop/guide-instruments-and-methods-of-observation-wmo-no-8',
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
howTo: howToData,
|
|
103
|
+
schemas: [faqSchema, howToSchema, appSchema],
|
|
104
|
+
seo: [
|
|
105
|
+
{
|
|
106
|
+
type: 'title',
|
|
107
|
+
text: 'What is the Dew Point and Why Does It Matter at Home?',
|
|
108
|
+
level: 2,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
type: 'paragraph',
|
|
112
|
+
html: 'The dew point is the temperature to which air must cool for water vapour to condense into liquid water. In a home, this concept marks the boundary between a healthy house and one with structural moisture problems. When the temperature of a surface (such as a poorly insulated wall) falls below the dew point, water droplets appear — the ideal breeding ground for <em>Aspergillus</em> and other harmful fungi.',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
type: 'stats',
|
|
116
|
+
items: [
|
|
117
|
+
{ value: '> 5°C', label: 'Safe Difference', icon: 'mdi:shield-check' },
|
|
118
|
+
{ value: '40–60%', label: 'Ideal Humidity', icon: 'mdi:water-percent' },
|
|
119
|
+
{ value: '< 1°C', label: 'Extreme Danger', icon: 'mdi:alert' },
|
|
120
|
+
],
|
|
121
|
+
columns: 3,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
type: 'comparative',
|
|
125
|
+
items: [
|
|
126
|
+
{
|
|
127
|
+
title: 'The Magnus-Tetens Formula',
|
|
128
|
+
description: 'To calculate the dew point with scientific accuracy we use the Magnus-Tetens approximation, with constants b=17.625 and c=243.04°C recommended by the World Meteorological Organization for temperatures between 0°C and 50°C.',
|
|
129
|
+
icon: 'mdi:calculator',
|
|
130
|
+
points: ['Scientific accuracy validated by the WMO', 'Valid for residential temperature ranges'],
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
title: 'Thermal Bridges and Mould',
|
|
134
|
+
description: 'Room corners, window frames, and poorly insulated exterior walls are the coldest points. If their temperature falls below the dew point, condensation and mould are inevitable.',
|
|
135
|
+
icon: 'mdi:home-thermometer',
|
|
136
|
+
points: ['Corners are the most vulnerable spots', 'Thermal insulation prevents condensation'],
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
columns: 2,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
type: 'title',
|
|
143
|
+
text: 'Risk Levels',
|
|
144
|
+
level: 3,
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
type: 'paragraph',
|
|
148
|
+
html: '<strong>Low Risk (difference > 5°C):</strong> The air is far from saturation. Your walls are safe. <strong>Medium Risk (3–5°C):</strong> Watch corners and thermal bridges. <strong>High Risk (1–3°C):</strong> Condensation likely on glass and cold zones — ventilate immediately. <strong>Extreme Danger (< 1°C):</strong> Active condensation with imminent risk of black mould growth.',
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
type: 'diagnostic',
|
|
152
|
+
variant: 'warning',
|
|
153
|
+
title: 'The Golden Rule',
|
|
154
|
+
icon: 'mdi:thermometer-alert',
|
|
155
|
+
badge: 'Key Tip',
|
|
156
|
+
html: '<p>If your wall temperature is within <strong>3°C of the dew point</strong>, you face an imminent condensation risk. Ventilate, use extractor fans in bathrooms and kitchens, and keep relative humidity between 40% and 60%.</p>',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
type: 'summary',
|
|
160
|
+
title: 'How to Prevent Condensation',
|
|
161
|
+
items: [
|
|
162
|
+
'Ventilate daily, especially after showering or cooking.',
|
|
163
|
+
'Keep relative humidity between 40% and 60%.',
|
|
164
|
+
'Use extractor fans in bathrooms and kitchens to remove steam.',
|
|
165
|
+
'Never dry laundry indoors without ventilation.',
|
|
166
|
+
'Thermally insulate walls to prevent cold surfaces.',
|
|
167
|
+
],
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
ui: {
|
|
171
|
+
labelTemperature: 'Ambient Temperature',
|
|
172
|
+
labelHumidity: 'Relative Humidity',
|
|
173
|
+
labelDewPoint: 'Dew Point',
|
|
174
|
+
riskLow: 'Low Risk',
|
|
175
|
+
riskMedium: 'Medium Risk',
|
|
176
|
+
riskHigh: 'High Risk',
|
|
177
|
+
riskExtreme: 'Extreme Danger',
|
|
178
|
+
riskLowDesc: 'Difference > 5°C. Surfaces are safe.',
|
|
179
|
+
riskMediumDesc: 'Difference 3–5°C. Watch the corners.',
|
|
180
|
+
riskHighDesc: 'Difference 1–3°C. Ventilate immediately.',
|
|
181
|
+
riskExtremeDesc: 'Difference < 1°C. Active condensation.',
|
|
182
|
+
},
|
|
183
|
+
};
|