@jjlmoya/utils-home 1.17.0 → 1.24.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 (162) hide show
  1. package/package.json +1 -1
  2. package/src/category/i18n/de.ts +10 -10
  3. package/src/category/i18n/en.ts +8 -8
  4. package/src/category/i18n/es.ts +2 -2
  5. package/src/category/i18n/fr.ts +15 -15
  6. package/src/category/i18n/id.ts +8 -8
  7. package/src/category/i18n/it.ts +7 -7
  8. package/src/category/i18n/nl.ts +8 -8
  9. package/src/category/i18n/pl.ts +10 -10
  10. package/src/category/i18n/pt.ts +8 -8
  11. package/src/category/i18n/ru.ts +10 -10
  12. package/src/category/i18n/sv.ts +8 -8
  13. package/src/category/i18n/tr.ts +4 -4
  14. package/src/category/i18n/zh.ts +8 -8
  15. package/src/entries.ts +7 -1
  16. package/src/tests/locale_completeness.test.ts +2 -2
  17. package/src/tests/no_en_dash.test.ts +70 -0
  18. package/src/tests/tool_validation.test.ts +2 -2
  19. package/src/tool/acTonnageCalculator/ac-tonnage-calculator.css +467 -0
  20. package/src/tool/acTonnageCalculator/bibliography.astro +14 -0
  21. package/src/tool/acTonnageCalculator/bibliography.ts +5 -0
  22. package/src/tool/acTonnageCalculator/client-animations.ts +80 -0
  23. package/src/tool/acTonnageCalculator/client.ts +171 -0
  24. package/src/tool/acTonnageCalculator/component.astro +186 -0
  25. package/src/tool/acTonnageCalculator/entry.ts +29 -0
  26. package/src/tool/acTonnageCalculator/i18n/de.ts +63 -0
  27. package/src/tool/acTonnageCalculator/i18n/en.ts +136 -0
  28. package/src/tool/acTonnageCalculator/i18n/es.ts +136 -0
  29. package/src/tool/acTonnageCalculator/i18n/fr.ts +61 -0
  30. package/src/tool/acTonnageCalculator/i18n/id.ts +61 -0
  31. package/src/tool/acTonnageCalculator/i18n/it.ts +61 -0
  32. package/src/tool/acTonnageCalculator/i18n/ja.ts +61 -0
  33. package/src/tool/acTonnageCalculator/i18n/ko.ts +61 -0
  34. package/src/tool/acTonnageCalculator/i18n/nl.ts +61 -0
  35. package/src/tool/acTonnageCalculator/i18n/pl.ts +61 -0
  36. package/src/tool/acTonnageCalculator/i18n/pt.ts +61 -0
  37. package/src/tool/acTonnageCalculator/i18n/ru.ts +61 -0
  38. package/src/tool/acTonnageCalculator/i18n/sv.ts +61 -0
  39. package/src/tool/acTonnageCalculator/i18n/tr.ts +61 -0
  40. package/src/tool/acTonnageCalculator/i18n/zh.ts +61 -0
  41. package/src/tool/acTonnageCalculator/index.ts +8 -0
  42. package/src/tool/acTonnageCalculator/logic.ts +56 -0
  43. package/src/tool/acTonnageCalculator/seo.astro +15 -0
  44. package/src/tool/acTonnageCalculator/ui.ts +39 -0
  45. package/src/tool/dewPointCalculator/bibliography.ts +2 -2
  46. package/src/tool/dewPointCalculator/i18n/de.ts +5 -5
  47. package/src/tool/dewPointCalculator/i18n/en.ts +6 -6
  48. package/src/tool/dewPointCalculator/i18n/es.ts +5 -5
  49. package/src/tool/dewPointCalculator/i18n/fr.ts +6 -6
  50. package/src/tool/dewPointCalculator/i18n/id.ts +5 -5
  51. package/src/tool/dewPointCalculator/i18n/it.ts +5 -5
  52. package/src/tool/dewPointCalculator/i18n/ja.ts +4 -4
  53. package/src/tool/dewPointCalculator/i18n/ko.ts +4 -4
  54. package/src/tool/dewPointCalculator/i18n/nl.ts +5 -5
  55. package/src/tool/dewPointCalculator/i18n/pl.ts +5 -5
  56. package/src/tool/dewPointCalculator/i18n/pt.ts +5 -5
  57. package/src/tool/dewPointCalculator/i18n/ru.ts +11 -11
  58. package/src/tool/dewPointCalculator/i18n/sv.ts +5 -5
  59. package/src/tool/dewPointCalculator/i18n/tr.ts +4 -4
  60. package/src/tool/dewPointCalculator/i18n/zh.ts +5 -5
  61. package/src/tool/heatingComparator/i18n/de.ts +8 -8
  62. package/src/tool/heatingComparator/i18n/en.ts +1 -1
  63. package/src/tool/heatingComparator/i18n/es.ts +1 -1
  64. package/src/tool/heatingComparator/i18n/fr.ts +7 -7
  65. package/src/tool/heatingComparator/i18n/id.ts +1 -1
  66. package/src/tool/heatingComparator/i18n/it.ts +1 -1
  67. package/src/tool/heatingComparator/i18n/nl.ts +1 -1
  68. package/src/tool/heatingComparator/i18n/pl.ts +1 -1
  69. package/src/tool/heatingComparator/i18n/pt.ts +1 -1
  70. package/src/tool/heatingComparator/i18n/ru.ts +12 -12
  71. package/src/tool/heatingComparator/i18n/sv.ts +4 -4
  72. package/src/tool/heatingComparator/i18n/zh.ts +6 -6
  73. package/src/tool/ledSavingCalculator/bibliography.ts +3 -3
  74. package/src/tool/ledSavingCalculator/i18n/de.ts +4 -4
  75. package/src/tool/ledSavingCalculator/i18n/en.ts +4 -4
  76. package/src/tool/ledSavingCalculator/i18n/es.ts +4 -4
  77. package/src/tool/ledSavingCalculator/i18n/fr.ts +8 -8
  78. package/src/tool/ledSavingCalculator/i18n/id.ts +3 -3
  79. package/src/tool/ledSavingCalculator/i18n/it.ts +4 -4
  80. package/src/tool/ledSavingCalculator/i18n/ja.ts +3 -3
  81. package/src/tool/ledSavingCalculator/i18n/ko.ts +2 -2
  82. package/src/tool/ledSavingCalculator/i18n/nl.ts +3 -3
  83. package/src/tool/ledSavingCalculator/i18n/pl.ts +3 -3
  84. package/src/tool/ledSavingCalculator/i18n/pt.ts +3 -3
  85. package/src/tool/ledSavingCalculator/i18n/ru.ts +6 -6
  86. package/src/tool/ledSavingCalculator/i18n/sv.ts +3 -3
  87. package/src/tool/ledSavingCalculator/i18n/tr.ts +3 -3
  88. package/src/tool/ledSavingCalculator/i18n/zh.ts +4 -4
  89. package/src/tool/projectorCalculator/bibliography.ts +3 -3
  90. package/src/tool/projectorCalculator/i18n/de.ts +2 -2
  91. package/src/tool/projectorCalculator/i18n/en.ts +1 -1
  92. package/src/tool/projectorCalculator/i18n/es.ts +2 -2
  93. package/src/tool/projectorCalculator/i18n/fr.ts +4 -4
  94. package/src/tool/projectorCalculator/i18n/id.ts +2 -2
  95. package/src/tool/projectorCalculator/i18n/it.ts +2 -2
  96. package/src/tool/projectorCalculator/i18n/ja.ts +2 -2
  97. package/src/tool/projectorCalculator/i18n/ko.ts +2 -2
  98. package/src/tool/projectorCalculator/i18n/nl.ts +2 -2
  99. package/src/tool/projectorCalculator/i18n/pl.ts +3 -3
  100. package/src/tool/projectorCalculator/i18n/pt.ts +2 -2
  101. package/src/tool/projectorCalculator/i18n/ru.ts +5 -5
  102. package/src/tool/projectorCalculator/i18n/sv.ts +2 -2
  103. package/src/tool/projectorCalculator/i18n/tr.ts +2 -2
  104. package/src/tool/projectorCalculator/i18n/zh.ts +4 -4
  105. package/src/tool/qrGenerator/bibliography.ts +1 -1
  106. package/src/tool/qrGenerator/i18n/en.ts +1 -1
  107. package/src/tool/qrGenerator/i18n/fr.ts +1 -1
  108. package/src/tool/solarCalculator/bibliography.ts +2 -2
  109. package/src/tool/solarCalculator/i18n/de.ts +2 -2
  110. package/src/tool/solarCalculator/i18n/en.ts +5 -5
  111. package/src/tool/solarCalculator/i18n/es.ts +3 -3
  112. package/src/tool/solarCalculator/i18n/fr.ts +6 -6
  113. package/src/tool/solarCalculator/i18n/id.ts +2 -2
  114. package/src/tool/solarCalculator/i18n/it.ts +2 -2
  115. package/src/tool/solarCalculator/i18n/ja.ts +2 -2
  116. package/src/tool/solarCalculator/i18n/ko.ts +2 -2
  117. package/src/tool/solarCalculator/i18n/nl.ts +2 -2
  118. package/src/tool/solarCalculator/i18n/pl.ts +3 -3
  119. package/src/tool/solarCalculator/i18n/pt.ts +2 -2
  120. package/src/tool/solarCalculator/i18n/ru.ts +5 -5
  121. package/src/tool/solarCalculator/i18n/sv.ts +2 -2
  122. package/src/tool/solarCalculator/i18n/tr.ts +2 -2
  123. package/src/tool/solarCalculator/i18n/zh.ts +3 -3
  124. package/src/tool/tariffComparator/bibliography.ts +1 -1
  125. package/src/tool/tariffComparator/i18n/en.ts +3 -3
  126. package/src/tool/tariffComparator/i18n/es.ts +3 -3
  127. package/src/tool/tariffComparator/i18n/fr.ts +6 -6
  128. package/src/tool/tariffComparator/i18n/pl.ts +1 -1
  129. package/src/tool/tariffComparator/i18n/zh.ts +1 -1
  130. package/src/tool/wifiRangeSimulator/bibliography.astro +14 -0
  131. package/src/tool/wifiRangeSimulator/bibliography.ts +14 -0
  132. package/src/tool/wifiRangeSimulator/component.astro +170 -0
  133. package/src/tool/wifiRangeSimulator/entry.ts +29 -0
  134. package/src/tool/wifiRangeSimulator/i18n/de.ts +477 -0
  135. package/src/tool/wifiRangeSimulator/i18n/en.ts +477 -0
  136. package/src/tool/wifiRangeSimulator/i18n/es.ts +477 -0
  137. package/src/tool/wifiRangeSimulator/i18n/fr.ts +477 -0
  138. package/src/tool/wifiRangeSimulator/i18n/id.ts +477 -0
  139. package/src/tool/wifiRangeSimulator/i18n/it.ts +477 -0
  140. package/src/tool/wifiRangeSimulator/i18n/ja.ts +477 -0
  141. package/src/tool/wifiRangeSimulator/i18n/ko.ts +477 -0
  142. package/src/tool/wifiRangeSimulator/i18n/nl.ts +477 -0
  143. package/src/tool/wifiRangeSimulator/i18n/pl.ts +477 -0
  144. package/src/tool/wifiRangeSimulator/i18n/pt.ts +477 -0
  145. package/src/tool/wifiRangeSimulator/i18n/ru.ts +477 -0
  146. package/src/tool/wifiRangeSimulator/i18n/sv.ts +477 -0
  147. package/src/tool/wifiRangeSimulator/i18n/tr.ts +477 -0
  148. package/src/tool/wifiRangeSimulator/i18n/zh.ts +477 -0
  149. package/src/tool/wifiRangeSimulator/i18n-utils.ts +14 -0
  150. package/src/tool/wifiRangeSimulator/index.ts +8 -0
  151. package/src/tool/wifiRangeSimulator/logic.ts +220 -0
  152. package/src/tool/wifiRangeSimulator/seo.astro +15 -0
  153. package/src/tool/wifiRangeSimulator/sketch-actions.ts +168 -0
  154. package/src/tool/wifiRangeSimulator/sketch-events.ts +138 -0
  155. package/src/tool/wifiRangeSimulator/sketch-render-dash.ts +170 -0
  156. package/src/tool/wifiRangeSimulator/sketch-render-device.ts +42 -0
  157. package/src/tool/wifiRangeSimulator/sketch-render.ts +155 -0
  158. package/src/tool/wifiRangeSimulator/sketch-state.ts +186 -0
  159. package/src/tool/wifiRangeSimulator/sketch.ts +100 -0
  160. package/src/tool/wifiRangeSimulator/ui.ts +69 -0
  161. package/src/tool/wifiRangeSimulator/wifi-range-simulator.css +583 -0
  162. package/src/tools.ts +4 -0
@@ -0,0 +1,171 @@
1
+ import { calculateAcTonnage } from './logic';
2
+ import { updateTheme, updateMiniRoom, spawnRipple } from './client-animations';
3
+
4
+ const MAX_BTU = 60000;
5
+ const CIRC = 2 * Math.PI * 85;
6
+ const COLORS = ['#22c55e', '#84cc16', '#f59e0b', '#f97316', '#ef4444'];
7
+ const M2_TO_FT2 = 10.7639;
8
+ const M_TO_FT = 3.28084;
9
+ const LS_KEY_UNIT = 'utils-home-unit-system';
10
+ const LS_KEY_POWER = 'ac-tonnage-power-unit';
11
+ const LS_KEY_STATE = 'ac-tonnage-state';
12
+
13
+ let unitSystem: 'metric' | 'imperial' = 'metric';
14
+ let powerUnit: 'btu' | 'frigorias' | 'tons' = 'btu';
15
+ const SYS = () => unitSystem;
16
+
17
+ function el(id: string) { return document.getElementById(id); }
18
+
19
+ function setTxt(id: string, val: string) { const e = el(id); if (e) e.textContent = val; }
20
+
21
+ function setAttr(id: string, attr: string, val: string) { const e = el(id); if (e) e.setAttribute(attr, val); }
22
+
23
+ function getNum(id: string): number { const e = el(id) as HTMLInputElement | null; return e ? parseFloat(e.value) || 0 : 0; }
24
+
25
+ function getStr(id: string): string { return (el(id) as HTMLSelectElement | null)?.value ?? ''; }
26
+
27
+ function colorFor(btu: number): string { return COLORS[Math.min(4, Math.floor(Math.min(1, btu / MAX_BTU) * 5))]; }
28
+
29
+ function unitLabel() { return SYS() === 'imperial' ? 'ft' : 'm'; }
30
+
31
+ function areaLabel() { return SYS() === 'imperial' ? 'ft²' : 'm²'; }
32
+
33
+ function toMetric(v: number, f: number) { return SYS() === 'imperial' ? v / f : v; }
34
+
35
+ function updateLabels() {
36
+ setTxt('ac-area-num', `${getNum('ac-area')} ${areaLabel()}`);
37
+ setTxt('ac-height-num', `${getNum('ac-height')} ${unitLabel()}`);
38
+ }
39
+
40
+ function readInputs() {
41
+ return { area: toMetric(getNum('ac-area'), M2_TO_FT2), height: toMetric(getNum('ac-height'), M_TO_FT), people: getNum('ac-people'), heat: getNum('ac-heat'), sun: getStr('ac-sun'), room: getStr('ac-room') };
42
+ }
43
+
44
+ function saveState() {
45
+ const inp = readInputs();
46
+ localStorage.setItem(LS_KEY_STATE, JSON.stringify({ area: inp.area, height: inp.height, people: inp.people, heat: inp.heat, sun: inp.sun, room: inp.room, unit: unitSystem, power: powerUnit }));
47
+ }
48
+
49
+ function loadState() {
50
+ try { return JSON.parse(localStorage.getItem(LS_KEY_STATE) || 'null'); } catch { return null; }
51
+ }
52
+
53
+ function getLabels(): Record<string, string> {
54
+ const e = el('ac-labels');
55
+ try { return JSON.parse(e?.dataset?.labels ?? '{}'); } catch { return {}; }
56
+ }
57
+
58
+ function updateRingDisplay(res: ReturnType<typeof calculateAcTonnage>, labels: Record<string, string>) {
59
+ if (powerUnit === 'frigorias') { setTxt('ac-ring-btu', res.frigorias.toLocaleString()); setTxt('ac-ring-unit', labels.labelUnitFrigorias ?? 'Frigorías'); }
60
+ else if (powerUnit === 'tons') { setTxt('ac-ring-btu', String(res.tons)); setTxt('ac-ring-unit', labels.labelUnitTons ?? 'Tons'); }
61
+ else { setTxt('ac-ring-btu', res.btu.toLocaleString()); setTxt('ac-ring-unit', labels.labelUnitBtu ?? 'BTU/h'); }
62
+ }
63
+
64
+ function renderBreakdown(res: ReturnType<typeof calculateAcTonnage>) {
65
+ const bd = el('ac-breakdown');
66
+ if (!bd) return;
67
+ const labels = getLabels();
68
+ bd.innerHTML = res.breakdown.map((b) => {
69
+ const labelKey = `bd${b.key.charAt(0).toUpperCase()}${b.key.slice(1)}`;
70
+ const pct = res.btu > 0 ? Math.min(100, (b.value / res.btu) * 100) : 0;
71
+ return `<div class="ac-bd-row"><span class="ac-bd-label">${labels[labelKey] ?? b.key}</span><div class="ac-bd-bar-wrap"><div class="ac-bd-bar" style="width:${pct}%"></div></div><span class="ac-bd-val">${b.value.toLocaleString()}</span></div>`;
72
+ }).join('');
73
+ }
74
+
75
+ function renderResult(res: ReturnType<typeof calculateAcTonnage>, people: number, heat: number) {
76
+ const color = colorFor(res.btu);
77
+ const offset = CIRC - (CIRC * Math.min(res.btu, MAX_BTU)) / MAX_BTU;
78
+ updateLabels();
79
+ setTxt('ac-people-num', String(people));
80
+ setTxt('ac-heat-num', String(heat));
81
+ setTxt('ac-frig', res.frigorias.toLocaleString());
82
+ setTxt('ac-tons', String(res.tons));
83
+ setAttr('ac-ring-fill', 'stroke-dashoffset', String(offset));
84
+ setAttr('ac-ring-fill', 'stroke', color);
85
+ updateRingDisplay(res, getLabels());
86
+ renderBreakdown(res);
87
+ updateTheme(res.btu);
88
+ updateMiniRoom(res.btu);
89
+ spawnRipple();
90
+ const card = el('ac-card');
91
+ if (card) { card.classList.remove('ac-peak'); void card.offsetWidth; card.classList.add('ac-peak'); }
92
+ }
93
+
94
+ function update() {
95
+ const inp = readInputs();
96
+ if (inp.area <= 0 || inp.height <= 0) return;
97
+ renderResult(calculateAcTonnage({ area: inp.area, ceilingHeight: inp.height, people: inp.people, heatSources: inp.heat, sunExposure: inp.sun as 'light' | 'medium' | 'heavy', roomType: inp.room as 'bedroom' | 'living' | 'kitchen' | 'office' | 'server' }), inp.people, inp.heat);
98
+ saveState();
99
+ }
100
+
101
+ function setSlider(id: string, cfg: { min: number; max: number; step: number; val: number }) {
102
+ const e = el(id) as HTMLInputElement | null;
103
+ if (!e) return;
104
+ e.min = String(cfg.min); e.max = String(cfg.max); e.step = String(cfg.step);
105
+ e.value = String(Math.max(cfg.min, Math.min(cfg.max, cfg.val)));
106
+ }
107
+
108
+ function setSelect(id: string, val: string) { const e = el(id) as HTMLSelectElement | null; if (e) e.value = val; }
109
+
110
+ function toggleBtns(sel: string, attr: string, val: string) {
111
+ document.querySelectorAll(sel).forEach((b) => b.classList.toggle('active', (b as HTMLElement).dataset[attr] === val));
112
+ }
113
+
114
+ function applyUnitSystem(sys: 'metric' | 'imperial') {
115
+ unitSystem = sys;
116
+ localStorage.setItem(LS_KEY_UNIT, sys);
117
+ toggleBtns('.ac-unit-toggle button', 'unit', sys);
118
+ const area = getNum('ac-area'), height = getNum('ac-height');
119
+ if (sys === 'imperial') { setSlider('ac-area', { min: 50, max: 1300, step: 10, val: Math.round(area * M2_TO_FT2) }); setSlider('ac-height', { min: 6, max: 20, step: 0.5, val: Math.round(height * M_TO_FT * 2) / 2 }); }
120
+ else { setSlider('ac-area', { min: 5, max: 120, step: 1, val: Math.round(area / M2_TO_FT2) }); setSlider('ac-height', { min: 2, max: 6, step: 0.1, val: Math.round((height / M_TO_FT) * 10) / 10 }); }
121
+ update();
122
+ }
123
+
124
+ function applyPowerUnit(unit: 'btu' | 'frigorias' | 'tons') {
125
+ powerUnit = unit;
126
+ localStorage.setItem(LS_KEY_POWER, unit);
127
+ toggleBtns('.ac-power-toggle button', 'power', unit);
128
+ }
129
+
130
+ function restoreSavedState() {
131
+ const saved = loadState();
132
+ if (!saved) return;
133
+ unitSystem = saved.unit ?? 'metric';
134
+ localStorage.setItem(LS_KEY_UNIT, unitSystem);
135
+ powerUnit = saved.power ?? 'btu';
136
+ localStorage.setItem(LS_KEY_POWER, powerUnit);
137
+ if (unitSystem === 'imperial') { setSlider('ac-area', { min: 50, max: 1300, step: 10, val: Math.round(saved.area * M2_TO_FT2) }); setSlider('ac-height', { min: 6, max: 20, step: 0.5, val: Math.round(saved.height * M_TO_FT * 2) / 2 }); }
138
+ else { setSlider('ac-area', { min: 5, max: 120, step: 1, val: saved.area }); setSlider('ac-height', { min: 2, max: 6, step: 0.1, val: saved.height }); }
139
+ setSlider('ac-people', { min: 0, max: 20, step: 1, val: saved.people });
140
+ setSlider('ac-heat', { min: 0, max: 20, step: 1, val: saved.heat });
141
+ setSelect('ac-sun', saved.sun);
142
+ setSelect('ac-room', saved.room);
143
+ toggleBtns('.ac-unit-toggle button', 'unit', unitSystem);
144
+ toggleBtns('.ac-power-toggle button', 'power', powerUnit);
145
+ }
146
+
147
+ function attachStep(btn: HTMLElement) {
148
+ const target = btn.dataset.target;
149
+ const delta = parseFloat(btn.dataset.delta ?? '0');
150
+ if (!target) return;
151
+ const slider = el(target) as HTMLInputElement | null;
152
+ if (!slider) return;
153
+ btn.addEventListener('click', () => {
154
+ const min = parseFloat(slider.min), max = parseFloat(slider.max), step = parseFloat(slider.step) || 1;
155
+ slider.value = String(Math.max(min, Math.min(max, Math.round((parseFloat(slider.value) + delta) / step) * step)));
156
+ slider.dispatchEvent(new Event('input'));
157
+ });
158
+ }
159
+
160
+ function init() {
161
+ restoreSavedState();
162
+ document.querySelectorAll('.ac-unit-toggle button').forEach((btn) => btn.addEventListener('click', () => { const s = (btn as HTMLElement).dataset.unit as 'metric' | 'imperial'; if (s) applyUnitSystem(s); }));
163
+ document.querySelectorAll('.ac-power-toggle button').forEach((btn) => btn.addEventListener('click', () => { const u = (btn as HTMLElement).dataset.power as 'btu' | 'frigorias' | 'tons'; if (u) { applyPowerUnit(u); update(); } }));
164
+ ['ac-area', 'ac-height', 'ac-people', 'ac-heat'].forEach((id) => { const e = el(id); if (e) e.addEventListener('input', update); });
165
+ ['ac-sun', 'ac-room'].forEach((id) => { const e = el(id); if (e) e.addEventListener('change', update); });
166
+ document.querySelectorAll('.ac-step').forEach((btn) => attachStep(btn as HTMLElement));
167
+ update();
168
+ }
169
+
170
+ document.addEventListener('astro:page-load', init);
171
+ init();
@@ -0,0 +1,186 @@
1
+ ---
2
+ import type { AcTonnageCalculatorUI } from './ui';
3
+ import { calculateAcTonnage } from './logic';
4
+
5
+ interface Props {
6
+ ui?: Record<string, unknown>;
7
+ }
8
+
9
+ const { ui = {} } = Astro.props;
10
+ const aUI = ui as AcTonnageCalculatorUI;
11
+
12
+ const defaults = {
13
+ area: 30,
14
+ height: 2.7,
15
+ people: 2,
16
+ heat: 1,
17
+ sun: 'medium',
18
+ room: 'living',
19
+ };
20
+
21
+ const initial = calculateAcTonnage({
22
+ area: defaults.area,
23
+ ceilingHeight: defaults.height,
24
+ people: defaults.people,
25
+ heatSources: defaults.heat,
26
+ sunExposure: defaults.sun as 'light' | 'medium' | 'heavy',
27
+ roomType: defaults.room as 'bedroom' | 'living' | 'kitchen' | 'office' | 'server',
28
+ });
29
+
30
+ const maxBtu = 60000;
31
+ const ringOffset = 2 * Math.PI * 90 - (2 * Math.PI * 90 * Math.min(initial.btu, maxBtu)) / maxBtu;
32
+ let initialColor = '#ef4444';
33
+ if (initial.btu < 18000) initialColor = '#22c55e';
34
+ else if (initial.btu < 36000) initialColor = '#f59e0b';
35
+
36
+ function roomLabel(btu: number): string {
37
+ if (btu < 18000) return aUI.labelRoomCozy;
38
+ if (btu < 36000) return aUI.labelRoomWarm;
39
+ return aUI.labelRoomHot;
40
+ }
41
+
42
+ function breakdownLabel(key: string): string {
43
+ if (key === 'baseCooling') return aUI.bdBaseCooling;
44
+ if (key === 'ceilingHeight') return aUI.bdCeilingHeight;
45
+ if (key === 'people') return aUI.bdPeople;
46
+ if (key === 'heatSources') return aUI.bdHeatSources;
47
+ return aUI.bdSunRoom;
48
+ }
49
+ ---
50
+
51
+ <div class="ac-wrapper">
52
+ <div class="ac-card" id="ac-card">
53
+ <div class="ac-left">
54
+ <div class="ac-unit-toggle" id="ac-unit-toggle">
55
+ <button class="active" data-unit="metric">{aUI.labelMetric}</button>
56
+ <button data-unit="imperial">{aUI.labelImperial}</button>
57
+ </div>
58
+ <div class="ac-field">
59
+ <div class="ac-field-top">
60
+ <span class="ac-label">{aUI.labelRoomSize}</span>
61
+ <span class="ac-val" id="ac-area-num">{defaults.area}</span>
62
+ </div>
63
+ <input class="ac-slider" type="range" id="ac-area" min="5" max="120" step="1" value={defaults.area} />
64
+ <div class="ac-steps">
65
+ <button class="ac-step" data-target="ac-area" data-delta="-5">−</button>
66
+ <button class="ac-step" data-target="ac-area" data-delta="5">+</button>
67
+ </div>
68
+ </div>
69
+ <div class="ac-field">
70
+ <div class="ac-field-top">
71
+ <span class="ac-label">{aUI.labelCeilingHeight}</span>
72
+ <span class="ac-val" id="ac-height-num">{defaults.height}</span>
73
+ </div>
74
+ <input class="ac-slider" type="range" id="ac-height" min="2" max="6" step="0.1" value={defaults.height} />
75
+ </div>
76
+ <div class="ac-field">
77
+ <div class="ac-field-top">
78
+ <span class="ac-label">{aUI.labelPeople}</span>
79
+ <span class="ac-val" id="ac-people-num">{defaults.people}</span>
80
+ </div>
81
+ <input class="ac-slider" type="range" id="ac-people" min="0" max="20" step="1" value={defaults.people} />
82
+ </div>
83
+ <div class="ac-field">
84
+ <div class="ac-field-top">
85
+ <span class="ac-label">{aUI.labelHeatSources}</span>
86
+ <span class="ac-val" id="ac-heat-num">{defaults.heat}</span>
87
+ </div>
88
+ <input class="ac-slider" type="range" id="ac-heat" min="0" max="20" step="1" value={defaults.heat} />
89
+ </div>
90
+ <div class="ac-row">
91
+ <div class="ac-select-wrap">
92
+ <span class="ac-label">{aUI.labelSunExposure}</span>
93
+ <select class="ac-select" id="ac-sun">
94
+ <option value="light">{aUI.sunLight}</option>
95
+ <option value="medium" selected>{aUI.sunMedium}</option>
96
+ <option value="heavy">{aUI.sunHeavy}</option>
97
+ </select>
98
+ </div>
99
+ <div class="ac-select-wrap">
100
+ <span class="ac-label">{aUI.labelRoomType}</span>
101
+ <select class="ac-select" id="ac-room">
102
+ <option value="bedroom">{aUI.roomBedroom}</option>
103
+ <option value="living" selected>{aUI.roomLiving}</option>
104
+ <option value="kitchen">{aUI.roomKitchen}</option>
105
+ <option value="office">{aUI.roomOffice}</option>
106
+ <option value="server">{aUI.roomServer}</option>
107
+ </select>
108
+ </div>
109
+ </div>
110
+ </div>
111
+
112
+ <div class="ac-right">
113
+ <div class="ac-unit-toggle ac-power-toggle" id="ac-power-toggle">
114
+ <button class="active" data-power="btu">{aUI.labelUnitBtu}</button>
115
+ <button data-power="frigorias">{aUI.labelUnitFrigorias}</button>
116
+ <button data-power="tons">{aUI.labelUnitTons}</button>
117
+ </div>
118
+ <div class="ac-ring-wrap">
119
+ <div class="ac-ring-glow" id="ac-ring-glow" />
120
+ <svg viewBox="0 0 200 200" width="160" height="160">
121
+ <circle class="ac-ring-bg" cx="100" cy="100" r="90" />
122
+ <circle id="ac-ring-fill" class="ac-ring-fill" cx="100" cy="100" r="90"
123
+ stroke-dasharray={2 * Math.PI * 90}
124
+ stroke-dashoffset={ringOffset}
125
+ stroke={initialColor} />
126
+ </svg>
127
+ <div class="ac-ring-inner">
128
+ <div class="ac-ring-label">{aUI.labelRequired}</div>
129
+ <div class="ac-ring-btu" id="ac-ring-btu">{initial.btu.toLocaleString()}</div>
130
+ <div class="ac-ring-unit" id="ac-ring-unit">{aUI.labelBtus}</div>
131
+ </div>
132
+ </div>
133
+
134
+ <div class="ac-mini-room" id="ac-mini-room">
135
+ <svg viewBox="0 0 180 120" width="180" height="120">
136
+ <rect class="ac-room-wall" x="15" y="15" width="150" height="90" rx="6" />
137
+ <rect class="ac-room-window" x="65" y="10" width="50" height="8" rx="4" />
138
+ <circle class="ac-room-person" cx="50" cy="80" r="8" />
139
+ <circle class="ac-room-person" cx="80" cy="80" r="8" />
140
+ <rect class="ac-room-pc" x="110" y="72" width="18" height="10" rx="2" />
141
+ <rect class="ac-room-sun" x="140" y="25" width="16" height="16" rx="8" />
142
+ <circle cx="145" cy="22" r="14" fill="url(#heat-glow)" opacity="0.3">
143
+ <animate attributeName="r" values="14;18;14" dur="3s" repeatCount="indefinite" />
144
+ </circle>
145
+ <rect x="55" y="98" width="70" height="18" rx="9" fill="var(--ac-surface)" stroke="var(--ac-border)" stroke-width="1" />
146
+ <text x="90" y="110" text-anchor="middle" fill="var(--ac-text)" font-size="10" font-weight="800" font-family="system-ui, -apple-system, sans-serif">{roomLabel(initial.btu)}</text>
147
+ <defs>
148
+ <radialGradient id="heat-glow" cx="0.5" cy="0.5" r="0.5">
149
+ <stop offset="0%" stop-color="rgba(239,68,68,0.2)" />
150
+ <stop offset="100%" stop-color="rgba(239,68,68,0)" />
151
+ </radialGradient>
152
+ </defs>
153
+ </svg>
154
+ </div>
155
+
156
+ <div class="ac-stats">
157
+ <div class="ac-stat">
158
+ <span class="ac-stat-label">{aUI.labelFrigorias}</span>
159
+ <span class="ac-stat-val" id="ac-frig">{initial.frigorias.toLocaleString()}</span>
160
+ </div>
161
+ <div class="ac-stat">
162
+ <span class="ac-stat-label">{aUI.labelTons}</span>
163
+ <span class="ac-stat-val" id="ac-tons">{initial.tons}</span>
164
+ </div>
165
+ </div>
166
+
167
+ <div class="ac-breakdown" id="ac-breakdown">
168
+ {initial.breakdown.map((b) => (
169
+ <div class="ac-bd-row">
170
+ <span class="ac-bd-label">{breakdownLabel(b.key)}</span>
171
+ <div class="ac-bd-bar-wrap">
172
+ <div class="ac-bd-bar" style={`width:${Math.min(100, (b.value / (initial.btu || 1)) * 100)}%`} />
173
+ </div>
174
+ <span class="ac-bd-val">{b.value.toLocaleString()}</span>
175
+ </div>
176
+ ))}
177
+ </div>
178
+ </div>
179
+ </div>
180
+ </div>
181
+
182
+ <div style="display:none" data-labels={JSON.stringify({ bdBaseCooling: aUI.bdBaseCooling, bdCeilingHeight: aUI.bdCeilingHeight, bdPeople: aUI.bdPeople, bdHeatSources: aUI.bdHeatSources, bdSunRoom: aUI.bdSunRoom, labelBtus: aUI.labelBtus, labelFrigorias: aUI.labelFrigorias, labelTons: aUI.labelTons, labelRequired: aUI.labelRequired, labelRoomCozy: aUI.labelRoomCozy, labelRoomWarm: aUI.labelRoomWarm, labelRoomHot: aUI.labelRoomHot, labelUnitBtu: aUI.labelUnitBtu, labelUnitFrigorias: aUI.labelUnitFrigorias, labelUnitTons: aUI.labelUnitTons })} id="ac-labels" />
183
+
184
+ <script>
185
+ import('./client.ts');
186
+ </script>
@@ -0,0 +1,29 @@
1
+ import type { HomeToolEntry, ToolLocaleContent } from '../../types';
2
+ import type { AcTonnageCalculatorUI } from './ui';
3
+
4
+ export type AcTonnageCalculatorLocaleContent = ToolLocaleContent<AcTonnageCalculatorUI>;
5
+
6
+ export const acTonnageCalculator: HomeToolEntry<AcTonnageCalculatorUI> = {
7
+ id: 'ac-tonnage-calculator',
8
+ icons: {
9
+ bg: 'mdi:air-conditioner',
10
+ fg: 'mdi:thermometer',
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,63 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { AcTonnageCalculatorUI } from '../ui';
4
+ import { bibliography } from '../bibliography';
5
+
6
+ const slug = 'klimaanlage-tonnage-rechner';
7
+ const title = 'Klimaanlagentonnage und Kühlleistungsrechner';
8
+ const description =
9
+ 'Berechne die exakte Klimaanlagengröße für deinen Raum in BTU, Frigorien und Tonnen. Gib Raumgröße, Deckenhöhe, Personen, Wärmequellen und Sonneneinstrahlung ein, um eine präzise Kühlempfehlung zu erhalten.';
10
+
11
+ const faqData = [
12
+ { question: 'Wie viele BTU brauche ich pro Quadratmeter?', answer: 'Der Standardwert liegt bei 600 BTU pro Quadratmeter für eine Standarddeckenhöhe von 2,5 Metern in gemäßigtem Klima. Dieser Wert steigt mit Deckenhöhe, Sonneneinstrahlung, Personenanzahl und wärmeerzeugenden Geräten.' },
13
+ { question: 'Was ist eine Frigoríe und wie vergleicht sie sich mit BTU?', answer: 'Eine Frigoríe ist eine ältere Kühlleistungseinheit, die in Spanien und Lateinamerika üblich ist. Eine Frigoríe entspricht etwa 3,968 BTU pro Stunde. Moderne Klimaanlagen geben oft beide Einheiten an, aber BTU ist der globale Standard.' },
14
+ { question: 'Wie rechne ich BTU in Kühltonnen um?', answer: 'Eine Kühltonne entspricht 12.000 BTU pro Stunde. Teile deinen BTU-Gesamtbedarf durch 12.000, um die Tonnage zu erhalten. Beispiel: 24.000 BTU entsprechen 2 Tonnen.' },
15
+ { question: 'Beeinflusst die Deckenhöhe die Klimaanlagengröße?', answer: 'Ja. Pro Meter über 2,7 Metern erhöht sich der Kühlbedarf um etwa 8%. Hohe Decken enthalten deutlich mehr Luftvolumen, das gekühlt werden muss.' },
16
+ ];
17
+
18
+ const howToData = [
19
+ { name: 'Raum ausmessen', text: 'Miss Länge und Breite in Metern und multipliziere sie, um die Fläche in Quadratmetern zu erhalten.' },
20
+ { name: 'Personen und Geräte zählen', text: 'Zähle die Personen, die den Raum regelmäßig nutzen, und Wärmequellen wie Computer, Fernseher und Öfen.' },
21
+ { name: 'Sonneneinstrahlung wählen', text: 'Wähle leicht, mittel oder stark je nach direkter Sonneneinstrahlung während der heißesten Tageszeit.' },
22
+ { name: 'Empfehlung ablesen', text: 'Der Rechner zeigt BTU, Frigorien und Tonnen an, damit du in jedem Markt das passende Gerät kaufen kannst.' },
23
+ ];
24
+
25
+ const faqSchema: WithContext<FAQPage> = {
26
+ '@context': 'https://schema.org', '@type': 'FAQPage',
27
+ mainEntity: faqData.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })),
28
+ };
29
+
30
+ const howToSchema: WithContext<HowTo> = {
31
+ '@context': 'https://schema.org', '@type': 'HowTo', name: title, description,
32
+ step: howToData.map((step) => ({ '@type': 'HowToStep', name: step.name, text: step.text })),
33
+ };
34
+
35
+ const appSchema: WithContext<SoftwareApplication> = {
36
+ '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: title, description,
37
+ applicationCategory: 'UtilityApplication', operatingSystem: 'All',
38
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' }, inLanguage: 'de',
39
+ };
40
+
41
+ export const content: ToolLocaleContent<AcTonnageCalculatorUI> = {
42
+ slug, title, description, faq: faqData, bibliography, howTo: howToData,
43
+ schemas: [faqSchema, howToSchema, appSchema],
44
+ seo: [
45
+ { type: 'title', text: 'Welche Klimaanlagengröße brauche ich?', level: 2 },
46
+ { type: 'paragraph', html: 'Die richtige Klimaanlagengröße hängt von der Raumfläche, Deckenhöhe, Personenanzahl, Sonneneinstrahlung und Wärmequellen ab. Verwende diesen Rechner, um die genauen BTU-, Frigorien- und Tonnage-Werte für deinen Raum zu erhalten.' },
47
+ { type: 'table', headers: ['Raumgröße', 'Empfohlene BTU', 'Tonnage', 'Typische Nutzung'], rows: [['10 m² (kleines Schlafzimmer)', '6.000 - 7.000 BTU', '0,5 - 0,75 Tonnen', 'Gästezimmer, Homeoffice'], ['15 m² (Schlafzimmer)', '9.000 - 10.000 BTU', '0,75 - 1 Tonne', 'Hauptschlafzimmer'], ['20 m² (Wohnzimmer)', '12.000 - 14.000 BTU', '1 - 1,25 Tonnen', 'Kleines Wohnzimmer'], ['30 m² (offener Grundriss)', '18.000 - 21.000 BTU', '1,5 - 1,75 Tonnen', 'Studio oder offene Küche'], ['40 m² (großes Wohnzimmer)', '24.000 - 28.000 BTU', '2 - 2,5 Tonnen', 'Großes Wohnzimmer + Essbereich']] },
48
+ { type: 'title', text: 'Warum die falsche Größe dich Geld kostet', level: 2 },
49
+ { type: 'paragraph', html: 'Eine zu kleine Klimaanlage läuft ununterbrochen, erreicht nie die gewünschte Temperatur und verschleißt den Kompressor vorzeitig. Eine zu große Anlage bläst kurze Kaltluftstöße, schaltet sich vor der Entfeuchtung ab und hinterlässt ein kaltes, feuchtes Raumklima. Beide Fehler kosten Geld.' },
50
+ { type: 'stats', items: [{ value: '600', label: 'BTU pro m² Basis', icon: 'mdi:thermometer' }, { value: '12000', label: 'BTU pro Tonne', icon: 'mdi:snowflake' }, { value: '3.968', label: 'BTU pro Frigoríe', icon: 'mdi:calculator' }], columns: 3 },
51
+ ],
52
+ ui: {
53
+ labelRoomSize: 'Raumfläche', labelRoomSizeFt: 'Raumfläche', labelCeilingHeight: 'Deckenhöhe', labelCeilingHeightFt: 'Deckenhöhe',
54
+ labelPeople: 'Personen', labelHeatSources: 'Wärmequellen', labelSunExposure: 'Sonneneinstrahlung', labelRoomType: 'Raumtyp',
55
+ labelCalculate: 'Berechnen', labelResult: 'Ergebnis', labelBtus: 'BTU/h', labelFrigorias: 'Frigorien', labelTons: 'Tonnen',
56
+ labelRequired: 'Erforderlich', labelRecommended: 'Empfohlen', labelUnitBtu: 'BTU/h', labelUnitFrigorias: 'Frigorien', labelUnitTons: 'Tonnen',
57
+ labelMetric: 'Metrisch', labelImperial: 'Imperial', labelRoomCozy: 'Gemütlich', labelRoomWarm: 'Warm', labelRoomHot: 'Heiß',
58
+ bdBaseCooling: 'Grundkühlung', bdCeilingHeight: 'Deckenhöhe', bdPeople: 'Personen', bdHeatSources: 'Wärmequellen', bdSunRoom: 'Sonne & Raumtyp',
59
+ sunLight: 'Leicht', sunMedium: 'Mittel', sunHeavy: 'Stark',
60
+ roomBedroom: 'Schlafzimmer', roomLiving: 'Wohnzimmer', roomKitchen: 'Küche', roomOffice: 'Büro', roomServer: 'Serverraum',
61
+ errorRequired: 'Bitte fülle alle Felder aus',
62
+ },
63
+ };
@@ -0,0 +1,136 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { AcTonnageCalculatorUI } from '../ui';
4
+ import { bibliography } from '../bibliography';
5
+
6
+ const slug = 'ac-tonnage-calculator';
7
+ const title = 'AC Tonnage and Cooling Capacity Calculator';
8
+ const description =
9
+ 'Calculate the exact air conditioner size your room needs in BTUs, frigorias, and tons. Input room size, ceiling height, occupants, heat sources, and sun exposure to get a precise cooling recommendation.';
10
+
11
+ const faqData = [
12
+ {
13
+ question: 'How many BTUs do I need per square metre?',
14
+ answer:
15
+ 'The standard baseline is 600 BTU per square metre for a standard 2.5 metre ceiling in a temperate climate. This increases with ceiling height, sun exposure, number of occupants, and heat generating appliances.',
16
+ },
17
+ {
18
+ question: 'What is a frigoría and how does it compare to a BTU?',
19
+ answer:
20
+ 'A frigoría is an older unit of cooling power common in Spain and Latin America. One frigoría equals approximately 3.968 BTU per hour. Modern AC units often list both units, but BTU is the global standard.',
21
+ },
22
+ {
23
+ question: 'How do I convert BTU to tons of cooling?',
24
+ answer:
25
+ 'One ton of refrigeration equals 12,000 BTU per hour. Divide your total BTU requirement by 12,000 to get the tonnage. For example, 24,000 BTU equals 2 tons.',
26
+ },
27
+ {
28
+ question: 'Does ceiling height affect AC sizing?',
29
+ answer:
30
+ 'Yes. For every metre above 2.7 metres, increase your cooling requirement by roughly 8 percent. High ceilings contain significantly more air volume that must be cooled.',
31
+ },
32
+ ];
33
+
34
+ const howToData = [
35
+ { name: 'Measure your room', text: 'Measure the length and width in metres and multiply to get the area in square metres.' },
36
+ { name: 'Count occupants and devices', text: 'Add the number of people who regularly use the room and count heat sources like computers, TVs, and ovens.' },
37
+ { name: 'Select sun exposure', text: 'Choose light, medium, or heavy based on how much direct sun the room receives during the hottest part of the day.' },
38
+ { name: 'Read the recommendation', text: 'The calculator outputs BTUs, frigorias, and tons so you can shop for the right unit in any market.' },
39
+ ];
40
+
41
+ const faqSchema: WithContext<FAQPage> = {
42
+ '@context': 'https://schema.org',
43
+ '@type': 'FAQPage',
44
+ mainEntity: faqData.map((item) => ({
45
+ '@type': 'Question',
46
+ name: item.question,
47
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
48
+ })),
49
+ };
50
+
51
+ const howToSchema: WithContext<HowTo> = {
52
+ '@context': 'https://schema.org',
53
+ '@type': 'HowTo',
54
+ name: title,
55
+ description,
56
+ step: howToData.map((step) => ({
57
+ '@type': 'HowToStep',
58
+ name: step.name,
59
+ text: step.text,
60
+ })),
61
+ };
62
+
63
+ const appSchema: WithContext<SoftwareApplication> = {
64
+ '@context': 'https://schema.org',
65
+ '@type': 'SoftwareApplication',
66
+ name: title,
67
+ description,
68
+ applicationCategory: 'UtilityApplication',
69
+ operatingSystem: 'All',
70
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
71
+ inLanguage: 'en',
72
+ };
73
+
74
+ export const content: ToolLocaleContent<AcTonnageCalculatorUI> = {
75
+ slug,
76
+ title,
77
+ description,
78
+ faq: faqData,
79
+ bibliography,
80
+ howTo: howToData,
81
+ schemas: [faqSchema, howToSchema, appSchema],
82
+ seo: [
83
+ { type: 'title', text: 'What Size Air Conditioner Do I Need?', level: 2 },
84
+ { type: 'paragraph', html: 'The right air conditioner size depends on your room area, ceiling height, how many people use the space, sun exposure, and heat sources like computers or kitchen appliances. Use this calculator to get the exact BTU, frigoría, and tonnage your room needs. Below is a quick reference for common room sizes with standard 2.5 m ceilings and light sun exposure.' },
85
+ { type: 'table', headers: ['Room size', 'Recommended BTU', 'Tonnage', 'Typical use'], rows: [['10 m² (small bedroom)', '6,000 - 7,000 BTU', '0.5 - 0.75 tons', 'Guest room, home office'], ['15 m² (bedroom)', '9,000 - 10,000 BTU', '0.75 - 1 ton', 'Master bedroom'], ['20 m² (living room)', '12,000 - 14,000 BTU', '1 - 1.25 tons', 'Small living room'], ['30 m² (open plan)', '18,000 - 21,000 BTU', '1.5 - 1.75 tons', 'Studio or open kitchen'], ['40 m² (large living)', '24,000 - 28,000 BTU', '2 - 2.5 tons', 'Large living + dining']] },
86
+ { type: 'title', text: 'Why Getting the Size Wrong Costs You Money', level: 2 },
87
+ { type: 'paragraph', html: 'An undersized air conditioner runs non-stop, never reaches the set temperature, and burns out its compressor years early. Your electricity bill spikes and you still feel uncomfortable. An oversized unit blasts cold air in short bursts, shuts off before dehumidifying, and leaves the room cold and damp. Both mistakes waste money. Getting the tonnage right is the single most important decision when buying an AC.' },
88
+ { type: 'stats', items: [{ value: '600', label: 'BTU per m² base', icon: 'mdi:thermometer' }, { value: '12000', label: 'BTU per ton', icon: 'mdi:snowflake' }, { value: '3.968', label: 'BTU per frigoría', icon: 'mdi:calculator' }], columns: 3 },
89
+ { type: 'title', text: 'How the Calculator Works', level: 3 },
90
+ { type: 'paragraph', html: 'This tool starts with a baseline of 600 BTU per square metre for a room with 2.5 metre ceilings. It then adds load for every extra metre of ceiling height, each person in the room, every heat-generating device, the amount of direct sun, and the room type. The result is your total cooling requirement in BTU per hour, plus the equivalent in frigorías and tons so you can shop anywhere in the world.' },
91
+ { type: 'title', text: 'Real Factors That Increase Your Cooling Load', level: 3 },
92
+ { type: 'paragraph', html: 'A 20 square metre bedroom and a 20 square metre kitchen need completely different AC units. Ovens, gaming PCs, large south-facing windows, and high ceilings add heat that a simple area chart ignores. Here is exactly how each factor changes your calculation.' },
93
+ { type: 'table', headers: ['Factor', 'Extra load', 'Practical fix'], rows: [['Ceiling height over 2.7 m', '+8% per extra metre', 'Buy a slightly larger unit or add a ceiling fan to circulate air.'], ['Direct afternoon sun', '+15% to +35%', 'Use reflective film or blackout blinds; size up the AC.'], ['Each extra person', '+500 BTU per person', 'Count the people who are normally in the room, not party guests.'], ['Kitchen with oven or stove', '+25% room multiplier', 'If possible, install a dedicated kitchen unit or size up by one step.'], ['Gaming PC or server', '+400 BTU per device', 'Position the AC vent to blow across the heat source.']] },
94
+ { type: 'title', text: 'BTU, Frigorías, and Tons: A Quick Guide', level: 3 },
95
+ { type: 'paragraph', html: '<strong>BTU (British Thermal Unit)</strong> is the global standard. One BTU is the energy needed to cool one pound of water by one degree Fahrenheit. <strong>Frigorías</strong> are still common in Spain and Latin America; one frigoría equals about 3.968 BTU per hour. <strong>Tons</strong> are used in North America; one ton of refrigeration equals 12,000 BTU per hour. This calculator shows all three so you can compare units from any manufacturer or retailer.' },
96
+ { type: 'tip', title: 'Buy at 80 Percent Capacity for Best Results', html: '<p>Choose an AC rated for about 80 percent of your calculated peak load, not 100 percent. A unit running at 80 percent capacity cycles less, removes humidity better, uses less electricity, and lasts several years longer than one constantly maxed out.</p>' },
97
+ { type: 'summary', title: 'Checklist: Buy the Right AC Unit', items: ['Measure the room length and width and multiply to get the area in square metres.', 'Check ceiling height; add roughly 8% cooling power for every metre above 2.7 m.', 'Count regular occupants and add 500 BTU per person beyond the first two.', 'Count heat sources like PCs, TVs, and ovens and add 400 BTU per device.', 'Check sun exposure; south-facing rooms with big windows need 15% to 35% more.', 'Use this calculator to get your total BTU, frigorías, and tons.', 'Buy a unit rated at about 80% of your calculated load.'] },
98
+ ],
99
+ ui: {
100
+ labelRoomSize: 'Room Area',
101
+ labelCeilingHeight: 'Ceiling Height',
102
+ labelPeople: 'People',
103
+ labelHeatSources: 'Heat Sources',
104
+ labelSunExposure: 'Sun Exposure',
105
+ labelRoomType: 'Room Type',
106
+ labelCalculate: 'Calculate',
107
+ labelResult: 'Result',
108
+ labelBtus: 'BTU/h',
109
+ labelFrigorias: 'Frigorías',
110
+ labelTons: 'Tons',
111
+ labelRequired: 'Required',
112
+ labelRecommended: 'Recommended',
113
+ labelUnitBtu: 'BTU/h',
114
+ labelUnitFrigorias: 'Frigorías',
115
+ labelUnitTons: 'Tons',
116
+ labelMetric: 'Metric',
117
+ labelImperial: 'Imperial',
118
+ labelRoomCozy: 'Cozy',
119
+ labelRoomWarm: 'Warm',
120
+ labelRoomHot: 'Hot',
121
+ bdBaseCooling: 'Base cooling',
122
+ bdCeilingHeight: 'Ceiling height',
123
+ bdPeople: 'People',
124
+ bdHeatSources: 'Heat sources',
125
+ bdSunRoom: 'Sun & room type',
126
+ sunLight: 'Light',
127
+ sunMedium: 'Medium',
128
+ sunHeavy: 'Heavy',
129
+ roomBedroom: 'Bedroom',
130
+ roomLiving: 'Living Room',
131
+ roomKitchen: 'Kitchen',
132
+ roomOffice: 'Office',
133
+ roomServer: 'Server Room',
134
+ errorRequired: 'Please fill in all fields',
135
+ },
136
+ };