@jjlmoya/utils-home 1.25.0 → 1.27.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.
Files changed (54) hide show
  1. package/package.json +1 -1
  2. package/src/entries.ts +7 -1
  3. package/src/index.ts +1 -0
  4. package/src/tests/locale_completeness.test.ts +2 -2
  5. package/src/tests/tool_validation.test.ts +2 -2
  6. package/src/tool/vampireDrawSimulator/bibliography.astro +14 -0
  7. package/src/tool/vampireDrawSimulator/bibliography.ts +14 -0
  8. package/src/tool/vampireDrawSimulator/component.astro +320 -0
  9. package/src/tool/vampireDrawSimulator/entry.ts +29 -0
  10. package/src/tool/vampireDrawSimulator/i18n/de.ts +211 -0
  11. package/src/tool/vampireDrawSimulator/i18n/en.ts +211 -0
  12. package/src/tool/vampireDrawSimulator/i18n/es.ts +211 -0
  13. package/src/tool/vampireDrawSimulator/i18n/fr.ts +211 -0
  14. package/src/tool/vampireDrawSimulator/i18n/id.ts +211 -0
  15. package/src/tool/vampireDrawSimulator/i18n/it.ts +211 -0
  16. package/src/tool/vampireDrawSimulator/i18n/ja.ts +211 -0
  17. package/src/tool/vampireDrawSimulator/i18n/ko.ts +211 -0
  18. package/src/tool/vampireDrawSimulator/i18n/nl.ts +211 -0
  19. package/src/tool/vampireDrawSimulator/i18n/pl.ts +211 -0
  20. package/src/tool/vampireDrawSimulator/i18n/pt.ts +211 -0
  21. package/src/tool/vampireDrawSimulator/i18n/ru.ts +211 -0
  22. package/src/tool/vampireDrawSimulator/i18n/sv.ts +211 -0
  23. package/src/tool/vampireDrawSimulator/i18n/tr.ts +211 -0
  24. package/src/tool/vampireDrawSimulator/i18n/zh.ts +211 -0
  25. package/src/tool/vampireDrawSimulator/index.ts +9 -0
  26. package/src/tool/vampireDrawSimulator/logic.ts +31 -0
  27. package/src/tool/vampireDrawSimulator/seo.astro +15 -0
  28. package/src/tool/vampireDrawSimulator/ui.ts +32 -0
  29. package/src/tool/vampireDrawSimulator/vampire-draw-simulator.css +542 -0
  30. package/src/tool/wallPaintingCalculator/bibliography.astro +14 -0
  31. package/src/tool/wallPaintingCalculator/bibliography.ts +14 -0
  32. package/src/tool/wallPaintingCalculator/component.astro +340 -0
  33. package/src/tool/wallPaintingCalculator/entry.ts +29 -0
  34. package/src/tool/wallPaintingCalculator/i18n/de.ts +226 -0
  35. package/src/tool/wallPaintingCalculator/i18n/en.ts +226 -0
  36. package/src/tool/wallPaintingCalculator/i18n/es.ts +226 -0
  37. package/src/tool/wallPaintingCalculator/i18n/fr.ts +226 -0
  38. package/src/tool/wallPaintingCalculator/i18n/id.ts +226 -0
  39. package/src/tool/wallPaintingCalculator/i18n/it.ts +226 -0
  40. package/src/tool/wallPaintingCalculator/i18n/ja.ts +226 -0
  41. package/src/tool/wallPaintingCalculator/i18n/ko.ts +226 -0
  42. package/src/tool/wallPaintingCalculator/i18n/nl.ts +226 -0
  43. package/src/tool/wallPaintingCalculator/i18n/pl.ts +226 -0
  44. package/src/tool/wallPaintingCalculator/i18n/pt.ts +226 -0
  45. package/src/tool/wallPaintingCalculator/i18n/ru.ts +226 -0
  46. package/src/tool/wallPaintingCalculator/i18n/sv.ts +226 -0
  47. package/src/tool/wallPaintingCalculator/i18n/tr.ts +226 -0
  48. package/src/tool/wallPaintingCalculator/i18n/zh.ts +226 -0
  49. package/src/tool/wallPaintingCalculator/index.ts +9 -0
  50. package/src/tool/wallPaintingCalculator/logic.ts +27 -0
  51. package/src/tool/wallPaintingCalculator/seo.astro +15 -0
  52. package/src/tool/wallPaintingCalculator/ui.ts +42 -0
  53. package/src/tool/wallPaintingCalculator/wall-painting-calculator.css +326 -0
  54. package/src/tools.ts +4 -0
@@ -0,0 +1,340 @@
1
+ ---
2
+ import type { WallPaintingCalculatorUI } from './ui';
3
+ import { calculateWallPainting, fmt2 } from './logic';
4
+
5
+ interface Props {
6
+ ui?: Record<string, unknown>;
7
+ }
8
+
9
+ const { ui = {} } = Astro.props;
10
+ const wUI = ui as WallPaintingCalculatorUI;
11
+
12
+ const M2_TO_FT2 = 10.7639;
13
+ const LITRE_TO_GALLON = 0.264172;
14
+ const M2PERL_TO_FT2PERGAL = 40.745;
15
+
16
+ const INITIAL_AREA = 30;
17
+ const INITIAL_YIELD = 12;
18
+ const INITIAL_COATS = 2;
19
+ const INITIAL_PRICE = 15;
20
+ const INITIAL_DILUTION = 5;
21
+
22
+ const initial = calculateWallPainting({
23
+ area: INITIAL_AREA,
24
+ yieldM2PerLitre: INITIAL_YIELD,
25
+ coats: INITIAL_COATS,
26
+ pricePerLitre: INITIAL_PRICE,
27
+ dilutionPercent: INITIAL_DILUTION,
28
+ });
29
+ ---
30
+
31
+ <div class="wall-wrapper">
32
+ <div class="wall-card"
33
+ data-label-area={wUI.labelArea}
34
+ data-label-area-ft={wUI.labelAreaFt}
35
+ data-unit-area={wUI.unitArea}
36
+ data-unit-area-ft={wUI.unitAreaFt}
37
+ data-label-yield={wUI.labelYield}
38
+ data-label-yield-ft={wUI.labelYieldFt}
39
+ data-unit-yield={wUI.unitYield}
40
+ data-unit-yield-ft={wUI.unitYieldFt}
41
+ data-label-price={wUI.labelPrice}
42
+ data-label-price-ft={wUI.labelPriceFt}
43
+ data-unit-price={wUI.unitPrice}
44
+ data-unit-price-gal={wUI.unitPriceGal}
45
+ data-label-paint={wUI.labelPaint}
46
+ data-label-paint-gal={wUI.labelPaintGal}
47
+ data-label-water={wUI.labelWater}
48
+ data-label-water-gal={wUI.labelWaterGal}
49
+ data-label-total={wUI.labelTotalVolume}
50
+ data-label-total-gal={wUI.labelTotalVolumeGal}
51
+ data-currency-sign={wUI.currencySign}
52
+ data-curr-usd={wUI.btnCurrUSD}
53
+ data-curr-eur={wUI.btnCurrEUR}
54
+ data-curr-gbp={wUI.btnCurrGBP}
55
+ data-curr-jpy={wUI.btnCurrJPY}
56
+ data-m2-to-ft2={M2_TO_FT2}
57
+ data-l-to-gal={LITRE_TO_GALLON}
58
+ data-m2pl-to-ft2pgal={M2PERL_TO_FT2PERGAL}
59
+ data-unit-coats={wUI.unitCoats}
60
+ >
61
+ <div class="wall-left">
62
+ <p class="wall-section-title">{wUI.sectionTitle}</p>
63
+
64
+ <div class="wall-unit-toggle">
65
+ <button class="wall-unit-btn wall-unit-active" data-unit="metric">{wUI.labelMetric}</button>
66
+ <button class="wall-unit-btn" data-unit="imperial">{wUI.labelImperial}</button>
67
+ </div>
68
+
69
+ <div class="wall-field">
70
+ <label class="wall-label" for="wall-area" id="wall-area-label">{wUI.labelArea}</label>
71
+ <div class="wall-number-row">
72
+ <input type="number" id="wall-area" value={INITIAL_AREA} min="1" max="5000" step="1" class="wall-number-input" />
73
+ <span class="wall-number-unit" id="wall-area-unit">{wUI.unitArea}</span>
74
+ </div>
75
+ </div>
76
+
77
+ <div class="wall-field">
78
+ <label class="wall-label" for="wall-yield" id="wall-yield-label">{wUI.labelYield}</label>
79
+ <div class="wall-number-row">
80
+ <input type="number" id="wall-yield" value={INITIAL_YIELD} min="1" max="5000" step="0.5" class="wall-number-input" />
81
+ <span class="wall-number-unit" id="wall-yield-unit">{wUI.unitYield}</span>
82
+ </div>
83
+ </div>
84
+
85
+ <div class="wall-field">
86
+ <label class="wall-label">{wUI.labelType}</label>
87
+ <div class="wall-type-grid">
88
+ <button class="wall-type-btn wall-type-active" data-metric-yield="10" data-imperial-yield="406.8" data-dilution="5">
89
+ <span class="wall-type-title">{wUI.btnPlasticMatTitle}</span>
90
+ <span class="wall-type-sub">{wUI.btnPlasticMatSub}</span>
91
+ </button>
92
+ <button class="wall-type-btn" data-metric-yield="12" data-imperial-yield="488.9" data-dilution="5">
93
+ <span class="wall-type-title">{wUI.btnPlasticSatinTitle}</span>
94
+ <span class="wall-type-sub">{wUI.btnPlasticSatinSub}</span>
95
+ </button>
96
+ <button class="wall-type-btn" data-metric-yield="11" data-imperial-yield="448.2" data-dilution="10">
97
+ <span class="wall-type-title">{wUI.btnEnamelTitle}</span>
98
+ <span class="wall-type-sub">{wUI.btnEnamelSub}</span>
99
+ </button>
100
+ </div>
101
+ </div>
102
+
103
+ <div class="wall-field">
104
+ <label class="wall-label" for="wall-coats">{wUI.labelCoats}</label>
105
+ <input type="range" id="wall-coats" min="1" max="4" value={INITIAL_COATS} step="1" class="wall-slider" />
106
+ <p id="wall-coats-desc" class="wall-coats-desc">{INITIAL_COATS} {wUI.unitCoats}</p>
107
+ </div>
108
+
109
+ <div class="wall-field">
110
+ <label class="wall-label" for="wall-price" id="wall-price-label">{wUI.labelPrice}</label>
111
+ <div class="wall-number-row">
112
+ <input type="number" id="wall-price" value={INITIAL_PRICE} min="0.01" step="0.01" class="wall-price-input" />
113
+ <span class="wall-number-unit" id="wall-price-unit">{wUI.currencySign}{wUI.unitPrice}</span>
114
+ </div>
115
+ </div>
116
+
117
+ <div class="wall-field">
118
+ <label class="wall-label" for="wall-dilution">{wUI.labelDilution}</label>
119
+ <div class="wall-number-row">
120
+ <input type="number" id="wall-dilution" value={INITIAL_DILUTION} min="0" max="50" step="1" class="wall-number-input" />
121
+ <span class="wall-number-unit">{wUI.unitDilution}</span>
122
+ </div>
123
+ </div>
124
+ </div>
125
+
126
+ <div class="wall-right">
127
+ <div class="wall-result-badge">{wUI.resultBadge}</div>
128
+
129
+ <div class="wall-paint-section">
130
+ <p class="wall-paint-label" id="wall-paint-label">{wUI.labelPaint}</p>
131
+ <p class="wall-paint-value">
132
+ <span id="wall-paint-num">{fmt2(initial.paintLitres)}</span>
133
+ <span id="wall-paint-unit" class="wall-paint-unit">{wUI.unitYield.split('/')[1]}</span>
134
+ </p>
135
+ </div>
136
+
137
+ <div class="wall-stats">
138
+ <div class="wall-stat">
139
+ <p class="wall-stat-label">{wUI.labelCost}</p>
140
+ <p id="wall-cost" class="wall-stat-value">{wUI.currencySign}{fmt2(initial.totalCost)}</p>
141
+ </div>
142
+ <div class="wall-stat-divider"></div>
143
+ <div class="wall-stat">
144
+ <p class="wall-stat-label" id="wall-water-label">{wUI.labelWater}</p>
145
+ <p id="wall-water" class="wall-stat-value">{fmt2(initial.waterLitres)} {wUI.unitYield.split('/')[1]}</p>
146
+ </div>
147
+ <div class="wall-stat-divider"></div>
148
+ <div class="wall-stat">
149
+ <p class="wall-stat-label" id="wall-total-label">{wUI.labelTotalVolume}</p>
150
+ <p id="wall-total" class="wall-stat-value">{fmt2(initial.totalVolume)} {wUI.unitYield.split('/')[1]}</p>
151
+ </div>
152
+ </div>
153
+
154
+ <div class="wall-currency-toggle">
155
+ <span class="wall-currency-label">{wUI.labelCurrency}</span>
156
+ <div class="wall-currency-btns">
157
+ <button class="wall-currency-btn wall-currency-active" data-currency="usd">{wUI.btnCurrUSD}</button>
158
+ <button class="wall-currency-btn" data-currency="eur">{wUI.btnCurrEUR}</button>
159
+ <button class="wall-currency-btn" data-currency="gbp">{wUI.btnCurrGBP}</button>
160
+ <button class="wall-currency-btn" data-currency="jpy">{wUI.btnCurrJPY}</button>
161
+ </div>
162
+ </div>
163
+ </div>
164
+ </div>
165
+ </div>
166
+
167
+ <script>
168
+ import { calculateWallPainting, fmt2 } from './logic';
169
+
170
+ const LS_KEY_UNIT = 'wall-painting-unit-system';
171
+ const LS_KEY_CURRENCY = 'wall-painting-currency';
172
+
173
+ let unitSystem: 'metric' | 'imperial' = 'metric';
174
+ let currency: 'usd' | 'eur' | 'gbp' | 'jpy' = 'usd';
175
+
176
+ function el(id: string) { return document.getElementById(id); }
177
+ function setTxt(id: string, val: string) { const e = el(id); if (e) e.textContent = val; }
178
+ function getNum(id: string): number { const e = el(id) as HTMLInputElement | null; return e ? parseFloat(e.value) || 0 : 0; }
179
+ function getDataset(card: HTMLElement, key: string): string { return card.dataset[key] ?? ''; }
180
+ function getFactor(card: HTMLElement, key: string): number { return parseFloat(getDataset(card, key)) || 1; }
181
+ function toMetric(v: number, f: number) { return unitSystem === 'imperial' ? v / f : v; }
182
+ function fromMetric(v: number, f: number) { return unitSystem === 'imperial' ? v * f : v; }
183
+
184
+ function getCurrencySign(card: HTMLElement): string {
185
+ const key = `curr${currency.charAt(0).toUpperCase()}${currency.slice(1)}`;
186
+ return getDataset(card, key) || getDataset(card, 'currencySign') || '$';
187
+ }
188
+
189
+ function calculate(card: HTMLElement) {
190
+ const m2ToFt2 = getFactor(card, 'm2ToFt2');
191
+ const lToGal = getFactor(card, 'lToGal');
192
+ const m2plToFt2pgal = getFactor(card, 'm2plToFt2pgal');
193
+ const currencySign = getCurrencySign(card);
194
+ const area = toMetric(getNum('wall-area'), m2ToFt2);
195
+ const yieldVal = toMetric(getNum('wall-yield'), m2plToFt2pgal);
196
+ const coats = getNum('wall-coats');
197
+ const price = toMetric(getNum('wall-price'), 1 / lToGal);
198
+ const dilution = getNum('wall-dilution');
199
+ const r = calculateWallPainting({ area, yieldM2PerLitre: yieldVal, coats, pricePerLitre: price, dilutionPercent: dilution });
200
+ setTxt('wall-paint-num', fmt2(fromMetric(r.paintLitres, lToGal)));
201
+ setTxt('wall-cost', `${currencySign}${fmt2(r.totalCost)}`);
202
+ setTxt('wall-water', `${fmt2(fromMetric(r.waterLitres, lToGal))} ${unitSystem === 'imperial' ? 'gal' : 'L'}`);
203
+ setTxt('wall-total', `${fmt2(fromMetric(r.totalVolume, lToGal))} ${unitSystem === 'imperial' ? 'gal' : 'L'}`);
204
+ }
205
+
206
+ function setAreaLabels(card: HTMLElement, i: boolean) {
207
+ setTxt('wall-area-label', i ? getDataset(card, 'labelAreaFt') : getDataset(card, 'labelArea'));
208
+ setTxt('wall-area-unit', i ? getDataset(card, 'unitAreaFt') : getDataset(card, 'unitArea'));
209
+ }
210
+
211
+ function setYieldLabels(card: HTMLElement, i: boolean) {
212
+ setTxt('wall-yield-label', i ? getDataset(card, 'labelYieldFt') : getDataset(card, 'labelYield'));
213
+ setTxt('wall-yield-unit', i ? getDataset(card, 'unitYieldFt') : getDataset(card, 'unitYield'));
214
+ }
215
+
216
+ function setPriceLabels(card: HTMLElement, i: boolean) {
217
+ const currencySign = getCurrencySign(card);
218
+ setTxt('wall-price-label', i ? getDataset(card, 'labelPriceFt') : getDataset(card, 'labelPrice'));
219
+ setTxt('wall-price-unit', `${currencySign}${i ? getDataset(card, 'unitPriceGal') : getDataset(card, 'unitPrice')}`);
220
+ }
221
+
222
+ function setResultLabels(card: HTMLElement, i: boolean) {
223
+ setTxt('wall-paint-label', i ? getDataset(card, 'labelPaintGal') : getDataset(card, 'labelPaint'));
224
+ setTxt('wall-paint-unit', i ? 'gal' : 'L');
225
+ setTxt('wall-water-label', i ? getDataset(card, 'labelWaterGal') : getDataset(card, 'labelWater'));
226
+ setTxt('wall-total-label', i ? getDataset(card, 'labelTotalGal') : getDataset(card, 'labelTotal'));
227
+ }
228
+
229
+ function updateLabels(card: HTMLElement) {
230
+ const i = unitSystem === 'imperial';
231
+ setAreaLabels(card, i);
232
+ setYieldLabels(card, i);
233
+ setPriceLabels(card, i);
234
+ setResultLabels(card, i);
235
+ }
236
+
237
+ function toggleClass(sel: string, attr: string, val: string, cls: string) {
238
+ document.querySelectorAll(sel).forEach((b) => b.classList.toggle(cls, (b as HTMLElement).dataset[attr] === val));
239
+ }
240
+
241
+ function applyUnitSystem(sys: 'metric' | 'imperial', card: HTMLElement) {
242
+ unitSystem = sys;
243
+ localStorage.setItem(LS_KEY_UNIT, sys);
244
+ toggleClass('.wall-unit-btn', 'unit', sys, 'wall-unit-active');
245
+ const m2ToFt2 = getFactor(card, 'm2ToFt2');
246
+ const m2plToFt2pgal = getFactor(card, 'm2plToFt2pgal');
247
+ const lToGal = getFactor(card, 'lToGal');
248
+ const areaEl = el('wall-area') as HTMLInputElement | null;
249
+ const yieldEl = el('wall-yield') as HTMLInputElement | null;
250
+ const priceEl = el('wall-price') as HTMLInputElement | null;
251
+ if (areaEl) areaEl.value = String(sys === 'imperial' ? Math.round(getNum('wall-area') * m2ToFt2) : Math.round(getNum('wall-area') / m2ToFt2));
252
+ if (yieldEl) yieldEl.value = String(sys === 'imperial' ? Math.round(getNum('wall-yield') * m2plToFt2pgal * 10) / 10 : Math.round(getNum('wall-yield') / m2plToFt2pgal * 10) / 10);
253
+ if (priceEl) priceEl.value = String(sys === 'imperial' ? Math.round(getNum('wall-price') / lToGal * 100) / 100 : Math.round(getNum('wall-price') * lToGal * 100) / 100);
254
+ updateLabels(card);
255
+ calculate(card);
256
+ }
257
+
258
+ function applyCurrency(curr: 'usd' | 'eur' | 'gbp' | 'jpy', card: HTMLElement) {
259
+ currency = curr;
260
+ localStorage.setItem(LS_KEY_CURRENCY, curr);
261
+ toggleClass('.wall-currency-btn', 'currency', curr, 'wall-currency-active');
262
+ const i = unitSystem === 'imperial';
263
+ const currencySign = getCurrencySign(card);
264
+ setTxt('wall-price-unit', `${currencySign}${i ? getDataset(card, 'unitPriceGal') : getDataset(card, 'unitPrice')}`);
265
+ calculate(card);
266
+ }
267
+
268
+ function attachTypeButtons(card: HTMLElement) {
269
+ document.querySelectorAll<HTMLElement>('.wall-type-btn').forEach((btn) => {
270
+ btn.addEventListener('click', () => {
271
+ document.querySelectorAll('.wall-type-btn').forEach((b) => b.classList.remove('wall-type-active'));
272
+ btn.classList.add('wall-type-active');
273
+ const yieldEl = el('wall-yield') as HTMLInputElement | null;
274
+ const dilEl = el('wall-dilution') as HTMLInputElement | null;
275
+ if (!yieldEl || !dilEl) return;
276
+ const isImperial = unitSystem === 'imperial';
277
+ const yieldKey = isImperial ? 'imperialYield' : 'metricYield';
278
+ if (btn.dataset[yieldKey]) yieldEl.value = btn.dataset[yieldKey]!;
279
+ if (btn.dataset.dilution) dilEl.value = btn.dataset.dilution;
280
+ calculate(card);
281
+ });
282
+ });
283
+ }
284
+
285
+ function restoreState() {
286
+ const savedUnit = localStorage.getItem(LS_KEY_UNIT) as 'metric' | 'imperial' | null;
287
+ if (savedUnit) unitSystem = savedUnit;
288
+ const savedCurr = localStorage.getItem(LS_KEY_CURRENCY) as 'usd' | 'eur' | 'gbp' | 'jpy' | null;
289
+ if (savedCurr) currency = savedCurr;
290
+ }
291
+
292
+ function attachUnitToggle(card: HTMLElement) {
293
+ document.querySelectorAll('.wall-unit-btn').forEach((btn) => {
294
+ btn.addEventListener('click', () => {
295
+ const s = (btn as HTMLElement).dataset.unit as 'metric' | 'imperial';
296
+ if (s) applyUnitSystem(s, card);
297
+ });
298
+ });
299
+ }
300
+
301
+ function attachCurrencyToggle(card: HTMLElement) {
302
+ document.querySelectorAll('.wall-currency-btn').forEach((btn) => {
303
+ btn.addEventListener('click', () => {
304
+ const c = (btn as HTMLElement).dataset.currency as 'usd' | 'eur' | 'gbp' | 'jpy';
305
+ if (c) applyCurrency(c, card);
306
+ });
307
+ });
308
+ }
309
+
310
+ function attachInputListeners(card: HTMLElement) {
311
+ ['wall-area', 'wall-yield', 'wall-price', 'wall-dilution'].forEach((id) => {
312
+ const e = el(id);
313
+ if (e) e.addEventListener('input', () => calculate(card));
314
+ });
315
+ }
316
+
317
+ function init() {
318
+ const card = document.querySelector('.wall-card') as HTMLElement | null;
319
+ if (!card) return;
320
+ restoreState();
321
+ toggleClass('.wall-unit-btn', 'unit', unitSystem, 'wall-unit-active');
322
+ toggleClass('.wall-currency-btn', 'currency', currency, 'wall-currency-active');
323
+ updateLabels(card);
324
+ attachUnitToggle(card);
325
+ attachCurrencyToggle(card);
326
+ attachTypeButtons(card);
327
+ const coatsSlider = el('wall-coats') as HTMLInputElement | null;
328
+ if (coatsSlider) {
329
+ coatsSlider.addEventListener('input', () => {
330
+ setTxt('wall-coats-desc', `${coatsSlider.value} ${getDataset(card, 'unitCoats')}`);
331
+ calculate(card);
332
+ });
333
+ }
334
+ attachInputListeners(card);
335
+ calculate(card);
336
+ }
337
+
338
+ document.addEventListener('astro:page-load', init);
339
+ init();
340
+ </script>
@@ -0,0 +1,29 @@
1
+ import type { HomeToolEntry, ToolLocaleContent } from '../../types';
2
+ import type { WallPaintingCalculatorUI } from './ui';
3
+
4
+ export type WallPaintingCalculatorLocaleContent = ToolLocaleContent<WallPaintingCalculatorUI>;
5
+
6
+ export const wallPaintingCalculator: HomeToolEntry<WallPaintingCalculatorUI> = {
7
+ id: 'wall-painting-calculator',
8
+ icons: {
9
+ bg: 'mdi:palette',
10
+ fg: 'mdi:format-paint',
11
+ },
12
+ i18n: {
13
+ de: async () => (await import('./i18n/de')).content,
14
+ en: async () => (await import('./i18n/en')).content,
15
+ es: async () => (await import('./i18n/es')).content,
16
+ fr: async () => (await import('./i18n/fr')).content,
17
+ id: async () => (await import('./i18n/id')).content,
18
+ it: async () => (await import('./i18n/it')).content,
19
+ ja: async () => (await import('./i18n/ja')).content,
20
+ ko: async () => (await import('./i18n/ko')).content,
21
+ nl: async () => (await import('./i18n/nl')).content,
22
+ pl: async () => (await import('./i18n/pl')).content,
23
+ pt: async () => (await import('./i18n/pt')).content,
24
+ ru: async () => (await import('./i18n/ru')).content,
25
+ sv: async () => (await import('./i18n/sv')).content,
26
+ tr: async () => (await import('./i18n/tr')).content,
27
+ zh: async () => (await import('./i18n/zh')).content,
28
+ },
29
+ };
@@ -0,0 +1,226 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { WallPaintingCalculatorUI } from '../ui';
4
+ import { bibliography } from '../bibliography';
5
+
6
+ const slug = 'wandfarben-rechner';
7
+ const title = 'Wandfarben und Verdünnungsrechner';
8
+ const description =
9
+ 'Berechnen Sie exakt, wie viele Liter oder Gallonen Farbe Sie für Ihre Wände benötigen. Passen Sie Decklagen, Oberflächentyp, Verdünnung an und ermitteln Sie die realen Kosten vor dem Kauf.';
10
+
11
+ const faqData = [
12
+ {
13
+ question: 'Wie berechne ich die genaue Farbmenge?',
14
+ answer:
15
+ 'Multiplizieren Sie die Wandfläche mit der Anzahl der Decklagen und teilen Sie durch den Farbertrag in Quadratmetern oder Quadratfuß pro Liter oder Gallone. Ziehen Sie zuerst Türen und Fenster von der Gesamtfläche ab.',
16
+ },
17
+ {
18
+ question: 'Wie viel Farbe braucht ein Standardzimmer?',
19
+ answer:
20
+ 'Ein 12 Quadratmeter großes Zimmer mit 2,5 Meter hohen Decken hat etwa 30 Quadratmeter Wandfläche. Bei zwei Decklagen und einem Ertrag von 12 Quadratmetern pro Liter benötigen Sie etwa 5 Liter Farbe.',
21
+ },
22
+ {
23
+ question: 'Soll ich die Farbe vor dem Auftragen verdünnen?',
24
+ answer:
25
+ 'Dispersionsfarben auf Wasserbasis sind meist gebrauchsfertig oder benötigen 5 bis 10 Prozent Wasser beim ersten Anstrich. Emaille- oder Ölfarben erfordern oft ein spezifisches Lösungsmittel, das auf der Dose angegeben ist.',
26
+ },
27
+ {
28
+ question: 'Wie viele Decklagen sind wirklich notwendig?',
29
+ answer:
30
+ 'Neue oder stark saugende Oberflächen benötigen einen Grundieranstrich plus zwei Deckanstriche. Das Überstreichen einer bereits gestrichenen Wand in einer ähnlichen Farbe benötigt meist nur ein bis zwei Decklagen.',
31
+ },
32
+ {
33
+ question: 'Ziehe ich Türen und Fenster ab?',
34
+ answer:
35
+ 'Ja. Messen Sie jede Öffnung und ziehen Sie sie von der Gesamtwandfläche ab. Als Faustregel sind Türen etwa 2 Quadratmeter und Fenster etwa 1,5 Quadratmeter groß.',
36
+ },
37
+ {
38
+ question: 'Ändert der Oberflächentyp den Ertrag?',
39
+ answer:
40
+ 'Ja. Glatte, vorbereitete Wände erbringen bis zu 14 Quadratmeter pro Liter. Rauer Beton, Rauputz oder saugendes Mauerwerk können den Ertrag auf 6 bis 8 Quadratmeter pro Liter senken.',
41
+ },
42
+ ];
43
+
44
+ const howToData = [
45
+ {
46
+ name: 'Messen Sie Ihre Wände',
47
+ text: 'Messen Sie Breite und Höhe jeder Wand. Multiplizieren Sie sie, um die Fläche jeder Wand zu erhalten, und addieren Sie alle Wände.',
48
+ },
49
+ {
50
+ name: 'Öffnungen abziehen',
51
+ text: 'Messen Sie Türen und Fenster und ziehen Sie deren Fläche von der Gesamtfläche ab, um die reale zu streichende Fläche zu erhalten.',
52
+ },
53
+ {
54
+ name: 'Wählen Sie den Farbtyp',
55
+ text: 'Wählen Sie den Farbtyp im Rechner. Dispersionsfarbe matt, Dispersionsfarbe seidenglänzend und Emaille haben unterschiedliche Erträge und Verdünnungsempfehlungen.',
56
+ },
57
+ {
58
+ name: 'Decklagen und Preis eingeben',
59
+ text: 'Geben Sie an, wie viele Decklagen Sie auftragen möchten, und den Preis pro Liter oder Gallone. Der Rechner gibt die genaue Menge und die Gesamtkosten aus.',
60
+ },
61
+ ];
62
+
63
+ const faqSchema: WithContext<FAQPage> = {
64
+ '@context': 'https://schema.org',
65
+ '@type': 'FAQPage',
66
+ mainEntity: faqData.map((item) => ({
67
+ '@type': 'Question',
68
+ name: item.question,
69
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
70
+ })),
71
+ };
72
+
73
+ const howToSchema: WithContext<HowTo> = {
74
+ '@context': 'https://schema.org',
75
+ '@type': 'HowTo',
76
+ name: title,
77
+ description,
78
+ step: howToData.map((step) => ({
79
+ '@type': 'HowToStep',
80
+ name: step.name,
81
+ text: step.text,
82
+ })),
83
+ };
84
+
85
+ const appSchema: WithContext<SoftwareApplication> = {
86
+ '@context': 'https://schema.org',
87
+ '@type': 'SoftwareApplication',
88
+ name: title,
89
+ description,
90
+ applicationCategory: 'UtilityApplication',
91
+ operatingSystem: 'All',
92
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
93
+ inLanguage: 'de',
94
+ };
95
+
96
+ export const content: ToolLocaleContent<WallPaintingCalculatorUI> = {
97
+ slug,
98
+ title,
99
+ description,
100
+ faq: faqData,
101
+ bibliography,
102
+ howTo: howToData,
103
+ schemas: [faqSchema, howToSchema, appSchema],
104
+ seo: [
105
+ {
106
+ type: 'title',
107
+ text: 'Farbrechner: Kaufen Sie Genau das, was Sie Brauchen',
108
+ level: 2,
109
+ },
110
+ {
111
+ type: 'paragraph',
112
+ html: 'Ein Zimmer ohne Vorabberechnung zu streichen endet meist in <strong>halbleeren Dosen</strong> oder Notfalleinkäufen. Der Grund ist simpel: der Farbertrag variiert mit der Porosität der Oberfläche, der Farbänderung und der Anzahl der Decklagen. Eine glatte, bereits gestrichene Wand kann bis zu 14 Quadratmeter pro Liter erbringen, während eine Rauputzwand die gleiche Menge auf nur 6 Quadratmeter pro Liter reduzieren kann.',
113
+ },
114
+ {
115
+ type: 'stats',
116
+ items: [
117
+ { value: 'Hoch', label: 'Standard Deckkraft', icon: 'mdi:format-paint' },
118
+ { value: '2 Schichten', label: 'Empfohlen', icon: 'mdi:layers' },
119
+ { value: '5-10%', label: 'Typische Verdünnung', icon: 'mdi:cup-water' },
120
+ ],
121
+ columns: 3,
122
+ },
123
+ {
124
+ type: 'title',
125
+ text: 'Wie der Farbertrag Wirklich Funktioniert',
126
+ level: 3,
127
+ },
128
+ {
129
+ type: 'paragraph',
130
+ html: 'Hersteller geben einen theoretischen Ertrag auf der Dose an. Diese Zahl geht von einer perfekt glatten, nicht saugenden, weißen Oberfläche aus, die mit einer professionellen Rolle unter Idealbedingungen aufgetragen wird. In der Realität <strong>reduzieren poröse Wände, dunkle Farben und Heimwerkeranwendungen</strong> diesen Ertrag um 20 bis 40 Prozent. Unser Rechner ermöglicht realistische Profile, damit Sie nicht zu knapp kalkulieren.',
131
+ },
132
+ {
133
+ type: 'comparative',
134
+ items: [
135
+ {
136
+ title: 'Dispersionsfarbe Matt',
137
+ description: 'Die gebräuchlichste Wahl für Innenräume. Hohe Deckkraft, leicht zu retuschieren und in den meisten Qualitäten waschbar.',
138
+ icon: 'mdi:palette',
139
+ points: ['Standard Deckkraft für Innenräume', 'Verdünnung: 5 bis 10 Prozent Wasser', 'Ideal für: Wohnzimmer und Schlafzimmer'],
140
+ },
141
+ {
142
+ title: 'Dispersionsfarbe Seidenglänzend',
143
+ description: 'Leicht glänzender Finish mit größerer Feuchtigkeits- und Fleckenresistenz. Schwieriger nahtlos zu retuschieren.',
144
+ icon: 'mdi:brightness-6',
145
+ points: ['Etwas höhere Deckkraft', 'Verdünnung: 5 Prozent Wasser', 'Ideal für: Küchen und Bäder'],
146
+ },
147
+ {
148
+ title: 'Emaillefarbe',
149
+ description: 'Harter und haltbarer Finish für Tischlerarbeiten, Heizkörper und stark frequentierte Bereiche. Erfordert Lösungsmittel oder spezifischen Verdünner.',
150
+ icon: 'mdi:spray',
151
+ points: ['Harter und langlebiger Finish', 'Verdünnung: 10 bis 15 Prozent Lösungsmittel', 'Ideal für: Türen, Rahmen und Möbel'],
152
+ },
153
+ ],
154
+ columns: 3,
155
+ },
156
+ {
157
+ type: 'diagnostic',
158
+ variant: 'info',
159
+ title: 'Praktische Oberflächenmessung',
160
+ icon: 'mdi:ruler-square',
161
+ badge: 'Tipp',
162
+ html: '<p style="margin:0">Multiplizieren Sie Breite mal Höhe jeder Wand. Für eine schnelle Gesamtsumme addieren Sie alle Breiten und multiplizieren sie mit der Raumhöhe. Ziehen Sie dann ungefähr 2 Quadratmeter pro Standardtür und 1,5 Quadratmeter pro Fenster ab.</p>',
163
+ },
164
+ {
165
+ type: 'title',
166
+ text: 'Regeln für Verdünnung und Auftrag',
167
+ level: 3,
168
+ },
169
+ {
170
+ type: 'paragraph',
171
+ html: 'Erste Anstriche auf neuen oder reparierten Oberflächen benötigen immer eine leichte Verdünnung, damit die Farbe eindringt und haftet. Deckanstriche sollten unverdünnt oder mit dem vom Hersteller angegebenen Minimum aufgetragen werden. <strong>Übermäßige Verdünnung zerstört das Pigment</strong> und hinterlässt transparente Stellen, die einen zusätzlichen Anstrich erfordern.',
172
+ },
173
+ {
174
+ type: 'summary',
175
+ title: 'Warum Vor dem Kauf Berechnen',
176
+ items: [
177
+ 'Vermeiden Sie Farbreste, die in der Dose eintrocknen und zu Abfall werden.',
178
+ 'Verhindern Sie Farbunterschiede zwischen Chargen, die zu verschiedenen Zeitpunkten gekauft wurden.',
179
+ 'Budgetieren Sie präzise: kennen Sie die realen Kosten inklusive Grundierung und Werkzeug.',
180
+ 'Planen Sie die Arbeit in einem Zug, ohne sie für zusätzliches Material zu unterbrechen.',
181
+ ],
182
+ },
183
+ ],
184
+ ui: {
185
+ sectionTitle: 'Oberfläche und Farbe',
186
+ labelMetric: 'Metrisch',
187
+ labelImperial: 'Imperial',
188
+ labelArea: 'Gesamtfläche zu streichen',
189
+ labelAreaFt: 'Gesamtfläche zu streichen',
190
+ unitArea: 'm2',
191
+ unitAreaFt: 'ft2',
192
+ labelYield: 'Farbertrag',
193
+ labelYieldFt: 'Farbertrag',
194
+ unitYield: 'm2/L',
195
+ unitYieldFt: 'ft2/gal',
196
+ labelType: 'Farbtyp wählen',
197
+ btnPlasticMatTitle: 'Dispersion Matt',
198
+ btnPlasticMatSub: 'Matt · Wasser 5%',
199
+ btnPlasticSatinTitle: 'Dispersion Seidenglänzend',
200
+ btnPlasticSatinSub: 'Seidenglänzend · Wasser 5%',
201
+ btnEnamelTitle: 'Emaille',
202
+ btnEnamelSub: 'Emaille · Lösungsmittel 10%',
203
+ labelCoats: 'Anzahl Decklagen',
204
+ unitCoats: 'Schichten',
205
+ labelPrice: 'Preis pro Liter',
206
+ labelPriceFt: 'Preis pro Gallone',
207
+ unitPrice: '/L',
208
+ unitPriceGal: '/gal',
209
+ labelDilution: 'Verdünnungsprozentsatz',
210
+ unitDilution: '%',
211
+ resultBadge: 'Farbberechnung',
212
+ labelPaint: 'Liter Farbe',
213
+ labelPaintGal: 'Gallonen Farbe',
214
+ labelCost: 'Gesamtkosten',
215
+ labelWater: 'Verdünnungsflüssigkeit',
216
+ labelWaterGal: 'Verdünnungsflüssigkeit',
217
+ labelTotalVolume: 'Gesamtmischung',
218
+ labelTotalVolumeGal: 'Gesamtmischung',
219
+ labelCurrency: 'Währung',
220
+ btnCurrUSD: '$',
221
+ btnCurrEUR: '€',
222
+ btnCurrGBP: '£',
223
+ btnCurrJPY: '¥',
224
+ currencySign: '€',
225
+ },
226
+ };