@jjlmoya/utils-hardware 1.20.0 → 1.22.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 (55) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +3 -1
  3. package/src/entries.ts +7 -1
  4. package/src/index.ts +2 -0
  5. package/src/tests/locale_completeness.test.ts +2 -2
  6. package/src/tests/tool_validation.test.ts +2 -2
  7. package/src/tool/mouseScrollTest/bibliography.astro +14 -0
  8. package/src/tool/mouseScrollTest/bibliography.ts +16 -0
  9. package/src/tool/mouseScrollTest/component.astro +150 -0
  10. package/src/tool/mouseScrollTest/entry.ts +29 -0
  11. package/src/tool/mouseScrollTest/i18n/de.ts +248 -0
  12. package/src/tool/mouseScrollTest/i18n/en.ts +253 -0
  13. package/src/tool/mouseScrollTest/i18n/es.ts +248 -0
  14. package/src/tool/mouseScrollTest/i18n/fr.ts +248 -0
  15. package/src/tool/mouseScrollTest/i18n/id.ts +248 -0
  16. package/src/tool/mouseScrollTest/i18n/it.ts +248 -0
  17. package/src/tool/mouseScrollTest/i18n/ja.ts +248 -0
  18. package/src/tool/mouseScrollTest/i18n/ko.ts +248 -0
  19. package/src/tool/mouseScrollTest/i18n/nl.ts +248 -0
  20. package/src/tool/mouseScrollTest/i18n/pl.ts +248 -0
  21. package/src/tool/mouseScrollTest/i18n/pt.ts +248 -0
  22. package/src/tool/mouseScrollTest/i18n/ru.ts +248 -0
  23. package/src/tool/mouseScrollTest/i18n/sv.ts +248 -0
  24. package/src/tool/mouseScrollTest/i18n/tr.ts +248 -0
  25. package/src/tool/mouseScrollTest/i18n/zh.ts +248 -0
  26. package/src/tool/mouseScrollTest/index.ts +9 -0
  27. package/src/tool/mouseScrollTest/logic.ts +277 -0
  28. package/src/tool/mouseScrollTest/mouse-scroll-test.css +610 -0
  29. package/src/tool/mouseScrollTest/seo.astro +15 -0
  30. package/src/tool/mouseScrollTest/ui.ts +34 -0
  31. package/src/tool/upsRuntimeCalculator/bibliography.astro +14 -0
  32. package/src/tool/upsRuntimeCalculator/bibliography.ts +16 -0
  33. package/src/tool/upsRuntimeCalculator/component.astro +384 -0
  34. package/src/tool/upsRuntimeCalculator/entry.ts +29 -0
  35. package/src/tool/upsRuntimeCalculator/i18n/de.ts +208 -0
  36. package/src/tool/upsRuntimeCalculator/i18n/en.ts +208 -0
  37. package/src/tool/upsRuntimeCalculator/i18n/es.ts +208 -0
  38. package/src/tool/upsRuntimeCalculator/i18n/fr.ts +208 -0
  39. package/src/tool/upsRuntimeCalculator/i18n/id.ts +208 -0
  40. package/src/tool/upsRuntimeCalculator/i18n/it.ts +208 -0
  41. package/src/tool/upsRuntimeCalculator/i18n/ja.ts +208 -0
  42. package/src/tool/upsRuntimeCalculator/i18n/ko.ts +208 -0
  43. package/src/tool/upsRuntimeCalculator/i18n/nl.ts +208 -0
  44. package/src/tool/upsRuntimeCalculator/i18n/pl.ts +208 -0
  45. package/src/tool/upsRuntimeCalculator/i18n/pt.ts +208 -0
  46. package/src/tool/upsRuntimeCalculator/i18n/ru.ts +208 -0
  47. package/src/tool/upsRuntimeCalculator/i18n/sv.ts +208 -0
  48. package/src/tool/upsRuntimeCalculator/i18n/tr.ts +208 -0
  49. package/src/tool/upsRuntimeCalculator/i18n/zh.ts +208 -0
  50. package/src/tool/upsRuntimeCalculator/index.ts +11 -0
  51. package/src/tool/upsRuntimeCalculator/logic.ts +48 -0
  52. package/src/tool/upsRuntimeCalculator/seo.astro +15 -0
  53. package/src/tool/upsRuntimeCalculator/ui.ts +31 -0
  54. package/src/tool/upsRuntimeCalculator/ups-runtime-calculator.css +530 -0
  55. package/src/tools.ts +3 -1
@@ -0,0 +1,384 @@
1
+ ---
2
+ import { Icon } from 'astro-icon/components';
3
+ import type { KnownLocale } from '../../types';
4
+ import type { UpsRuntimeCalculatorUI } from './ui';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ ui?: Record<string, unknown>;
9
+ }
10
+
11
+ const { ui } = Astro.props;
12
+ const t = (ui ?? {}) as UpsRuntimeCalculatorUI;
13
+ ---
14
+
15
+ <div
16
+ class="ups-root"
17
+ data-config={JSON.stringify({
18
+ presetDesktop: t.presetDesktop,
19
+ presetMonitor: t.presetMonitor,
20
+ presetRouter: t.presetRouter,
21
+ presetNas: t.presetNas,
22
+ deviceName: t.deviceName,
23
+ watts: t.watts,
24
+ remove: t.remove,
25
+ bandLight: t.bandLight,
26
+ bandBalanced: t.bandBalanced,
27
+ bandHeavy: t.bandHeavy,
28
+ summaryPrefix: t.summaryPrefix,
29
+ minutes: t.minutes,
30
+ hours: t.hours,
31
+ wattsUnit: t.wattsUnit,
32
+ percentUnit: t.percentUnit,
33
+ assumptionsLabel: t.assumptionsLabel,
34
+ })}
35
+ >
36
+ <section class="ups-panel">
37
+ <div class="ups-console ups-load">
38
+ <div class="ups-strip">
39
+ <span>{t.loadTitle}</span>
40
+ <button class="ups-add" type="button" data-add-device>{t.addDevice}</button>
41
+ </div>
42
+ <div class="ups-devices" data-devices></div>
43
+ </div>
44
+
45
+ <div class="ups-machine" aria-hidden="true">
46
+ <div class="ups-shell">
47
+ <div class="ups-port-row">
48
+ <i></i><i></i><i></i><i></i>
49
+ </div>
50
+ <div class="ups-screen">
51
+ <span data-runtime-big>0</span>
52
+ <small data-runtime-unit>{t.minutes}</small>
53
+ </div>
54
+ <div class="ups-core">
55
+ <b></b><b></b><b></b><b></b>
56
+ </div>
57
+ </div>
58
+ <div class="ups-energy-rail">
59
+ <span data-load-bar></span>
60
+ </div>
61
+ </div>
62
+
63
+ <div class="ups-console ups-controls" aria-label={t.assumptionsLabel}>
64
+ <label class="ups-capacity">
65
+ <span>{t.batteryWh}</span>
66
+ <span class="ups-number-field ups-number-field-large">
67
+ <input data-battery type="number" min="50" max="3000" step="10" value="432" />
68
+ <em>{t.whUnit}</em>
69
+ </span>
70
+ </label>
71
+ <label>
72
+ <span>{t.efficiency}</span>
73
+ <input data-efficiency type="range" min="70" max="95" value="86" />
74
+ <strong data-efficiency-label>86%</strong>
75
+ </label>
76
+ <label>
77
+ <span>{t.powerFactor}</span>
78
+ <input data-power-factor type="range" min="50" max="100" value="70" />
79
+ <strong data-power-factor-label>0.70</strong>
80
+ </label>
81
+ <label>
82
+ <span>{t.reserve}</span>
83
+ <input data-reserve type="range" min="0" max="50" value="20" />
84
+ <strong data-reserve-label>20%</strong>
85
+ </label>
86
+ </div>
87
+
88
+ <div class="ups-dashboard">
89
+ <div class="ups-metrics">
90
+ <article>
91
+ <span>{t.totalLoad}</span>
92
+ <strong><output data-total-load>0</output> {t.wattsUnit}</strong>
93
+ <i data-load-meter></i>
94
+ </article>
95
+ <article>
96
+ <span>{t.runtime}</span>
97
+ <strong><output data-runtime>0</output></strong>
98
+ <i data-runtime-meter></i>
99
+ </article>
100
+ <article>
101
+ <span>{t.recommendedUps}</span>
102
+ <strong><output data-recommended>0</output> {t.vaUnit}</strong>
103
+ <i data-va-meter></i>
104
+ </article>
105
+ <article>
106
+ <span>{t.usableEnergy}</span>
107
+ <strong><output data-usable-energy>0</output> {t.whUnit}</strong>
108
+ <i data-energy-meter></i>
109
+ </article>
110
+ </div>
111
+ <p class="ups-summary" data-summary></p>
112
+ </div>
113
+ </section>
114
+ <template data-trash-icon>
115
+ <Icon name="mdi:trash-can-outline" class="ups-trash-icon" />
116
+ </template>
117
+ </div>
118
+
119
+ <link rel="stylesheet" href="./ups-runtime-calculator.css" />
120
+
121
+ <script>
122
+ interface Device {
123
+ name: string;
124
+ watts: number;
125
+ }
126
+ interface Config {
127
+ presetDesktop: string;
128
+ presetMonitor: string;
129
+ presetRouter: string;
130
+ presetNas: string;
131
+ deviceName: string;
132
+ watts: string;
133
+ remove: string;
134
+ bandLight: string;
135
+ bandBalanced: string;
136
+ bandHeavy: string;
137
+ summaryPrefix: string;
138
+ minutes: string;
139
+ hours: string;
140
+ wattsUnit: string;
141
+ percentUnit: string;
142
+ assumptionsLabel: string;
143
+ }
144
+ interface PersistedState {
145
+ devices?: Device[];
146
+ batteryWh?: string;
147
+ efficiency?: string;
148
+ powerFactor?: string;
149
+ reserve?: string;
150
+ }
151
+ type RuntimeStats = { totalWatts: number; usableWh: number; runtimeMinutes: number; recommendedWatts: number; recommendedVa: number };
152
+ const root = document.querySelector<HTMLElement>('.ups-root');
153
+ const storageKey = 'ups-runtime-calculator-state';
154
+ const config = JSON.parse(root?.dataset.config ?? '{}') as Config;
155
+ const defaultDevices: Device[] = [
156
+ { name: config.presetDesktop, watts: 260 },
157
+ { name: config.presetMonitor, watts: 45 },
158
+ { name: config.presetRouter, watts: 14 },
159
+ { name: config.presetNas, watts: 38 },
160
+ ];
161
+ const savedState = readState();
162
+ const devices: Device[] = Array.isArray(savedState.devices) && savedState.devices.length > 0
163
+ ? savedState.devices.map((device) => ({ name: String(device.name), watts: Number(device.watts) || 0 }))
164
+ : defaultDevices;
165
+ const els = {
166
+ devices: root?.querySelector<HTMLElement>('[data-devices]'),
167
+ add: root?.querySelector<HTMLButtonElement>('[data-add-device]'),
168
+ battery: root?.querySelector<HTMLInputElement>('[data-battery]'),
169
+ efficiency: root?.querySelector<HTMLInputElement>('[data-efficiency]'),
170
+ efficiencyLabel: root?.querySelector<HTMLElement>('[data-efficiency-label]'),
171
+ powerFactor: root?.querySelector<HTMLInputElement>('[data-power-factor]'),
172
+ powerFactorLabel: root?.querySelector<HTMLElement>('[data-power-factor-label]'),
173
+ reserve: root?.querySelector<HTMLInputElement>('[data-reserve]'),
174
+ reserveLabel: root?.querySelector<HTMLElement>('[data-reserve-label]'),
175
+ totalLoad: root?.querySelector<HTMLOutputElement>('[data-total-load]'),
176
+ runtime: root?.querySelector<HTMLOutputElement>('[data-runtime]'),
177
+ runtimeBig: root?.querySelector<HTMLElement>('[data-runtime-big]'),
178
+ runtimeUnit: root?.querySelector<HTMLElement>('[data-runtime-unit]'),
179
+ recommended: root?.querySelector<HTMLOutputElement>('[data-recommended]'),
180
+ usableEnergy: root?.querySelector<HTMLOutputElement>('[data-usable-energy]'),
181
+ summary: root?.querySelector<HTMLElement>('[data-summary]'),
182
+ loadBar: root?.querySelector<HTMLElement>('[data-load-bar]'),
183
+ loadMeter: root?.querySelector<HTMLElement>('[data-load-meter]'),
184
+ runtimeMeter: root?.querySelector<HTMLElement>('[data-runtime-meter]'),
185
+ vaMeter: root?.querySelector<HTMLElement>('[data-va-meter]'),
186
+ energyMeter: root?.querySelector<HTMLElement>('[data-energy-meter]'),
187
+ trashIcon: root?.querySelector<HTMLTemplateElement>('[data-trash-icon]'),
188
+ };
189
+ applySavedControls();
190
+ function readState(): PersistedState {
191
+ try {
192
+ return JSON.parse(localStorage.getItem(storageKey) ?? '{}') as PersistedState;
193
+ } catch {
194
+ return {};
195
+ }
196
+ }
197
+ function saveState() {
198
+ const nextState: PersistedState = {
199
+ devices,
200
+ batteryWh: els.battery?.value,
201
+ efficiency: els.efficiency?.value,
202
+ powerFactor: els.powerFactor?.value,
203
+ reserve: els.reserve?.value,
204
+ };
205
+ localStorage.setItem(storageKey, JSON.stringify(nextState));
206
+ }
207
+ function applySavedControls() {
208
+ [
209
+ [els.battery, savedState.batteryWh],
210
+ [els.efficiency, savedState.efficiency],
211
+ [els.powerFactor, savedState.powerFactor],
212
+ [els.reserve, savedState.reserve],
213
+ ].forEach(([input, value]) => applySavedValue(input as HTMLInputElement | null | undefined, value as string | undefined));
214
+ }
215
+ function applySavedValue(input: HTMLInputElement | null | undefined, value: string | undefined) {
216
+ if (input && value) {
217
+ input.value = value;
218
+ }
219
+ }
220
+ function calculate() {
221
+ const stats = getRuntimeStats();
222
+ updateLabels();
223
+ updateOutputs(stats);
224
+ updateMeters(stats);
225
+ saveState();
226
+ }
227
+ function getRuntimeStats(): RuntimeStats {
228
+ const totalWatts = devices.reduce((sum, device) => sum + Math.max(0, device.watts), 0);
229
+ const batteryWh = readNumber(els.battery, 0);
230
+ const efficiency = readNumber(els.efficiency, 86) / 100;
231
+ const reserve = readNumber(els.reserve, 20) / 100;
232
+ const powerFactor = readNumber(els.powerFactor, 70) / 100;
233
+ const usableWh = batteryWh * efficiency * (1 - reserve);
234
+ const runtimeMinutes = totalWatts > 0 ? (usableWh / totalWatts) * 60 : 0;
235
+ const recommendedWatts = Math.ceil((totalWatts * 1.25) / 10) * 10;
236
+ const recommendedVa = Math.ceil((recommendedWatts / powerFactor) / 50) * 50;
237
+ return { totalWatts, usableWh, runtimeMinutes, recommendedWatts, recommendedVa };
238
+ }
239
+ function updateLabels() {
240
+ const efficiency = readNumber(els.efficiency, 86) / 100;
241
+ const reserve = readNumber(els.reserve, 20) / 100;
242
+ const powerFactor = readNumber(els.powerFactor, 70) / 100;
243
+ els.efficiencyLabel!.textContent = `${Math.round(efficiency * 100)}${config.percentUnit}`;
244
+ els.powerFactorLabel!.textContent = powerFactor.toFixed(2);
245
+ els.reserveLabel!.textContent = `${Math.round(reserve * 100)}${config.percentUnit}`;
246
+ }
247
+
248
+ function updateOutputs(stats: RuntimeStats) {
249
+ const runtimeValue = getRuntimeValue(stats.runtimeMinutes);
250
+ const band = getBandText(stats.runtimeMinutes);
251
+ els.totalLoad!.textContent = stats.totalWatts.toFixed(0);
252
+ els.usableEnergy!.textContent = stats.usableWh.toFixed(0);
253
+ els.recommended!.textContent = stats.recommendedVa.toFixed(0);
254
+ els.runtime!.textContent = formatRuntime(stats.runtimeMinutes);
255
+ els.runtimeBig!.textContent = runtimeValue.value;
256
+ els.runtimeUnit!.textContent = runtimeValue.unit;
257
+ els.summary!.textContent = `${config.summaryPrefix} ${band} ${stats.recommendedVa.toFixed(0)} ${config.vaUnit} / ${stats.recommendedWatts.toFixed(0)} ${config.wattsUnit}.`;
258
+ }
259
+
260
+ function updateMeters(stats: RuntimeStats) {
261
+ const runtimeFill = clampFill(stats.runtimeMinutes * 2, 6);
262
+ const loadFill = clampFill(stats.totalWatts / 8, 8);
263
+ const vaFill = clampFill(stats.recommendedVa / 12, 8);
264
+ const energyFill = clampFill(stats.usableWh / 8, 8);
265
+ root?.style.setProperty('--ups-fill', `${runtimeFill}%`);
266
+ root?.style.setProperty('--ups-load-fill', `${loadFill}%`);
267
+ els.loadBar!.style.width = `${loadFill}%`;
268
+ els.loadMeter!.style.setProperty('--meter-fill', `${loadFill}%`);
269
+ els.runtimeMeter!.style.setProperty('--meter-fill', `${runtimeFill}%`);
270
+ els.vaMeter!.style.setProperty('--meter-fill', `${vaFill}%`);
271
+ els.energyMeter!.style.setProperty('--meter-fill', `${energyFill}%`);
272
+ }
273
+
274
+ function clampFill(value: number, min: number) {
275
+ return Math.max(min, Math.min(100, value));
276
+ }
277
+
278
+ function readNumber(input: HTMLInputElement | null | undefined, fallback: number) {
279
+ return input ? Number(input.value) : fallback;
280
+ }
281
+
282
+ function getRuntimeValue(minutes: number) {
283
+ if (minutes >= 60) {
284
+ return { value: (minutes / 60).toFixed(1), unit: config.hours };
285
+ }
286
+ return { value: minutes.toFixed(0), unit: config.minutes };
287
+ }
288
+
289
+ function getBandText(minutes: number) {
290
+ if (minutes >= 30) {
291
+ return config.bandLight;
292
+ }
293
+ if (minutes >= 12) {
294
+ return config.bandBalanced;
295
+ }
296
+ return config.bandHeavy;
297
+ }
298
+
299
+ function formatRuntime(minutes: number): string {
300
+ if (minutes >= 60) return `${(minutes / 60).toFixed(1)} ${config.hours}`;
301
+ return `${minutes.toFixed(0)} ${config.minutes}`;
302
+ }
303
+
304
+ function renderDevices() {
305
+ els.devices!.innerHTML = '';
306
+ devices.forEach((device, index) => els.devices!.append(createDeviceRow(device, index)));
307
+ }
308
+
309
+ function createDeviceRow(device: Device, index: number) {
310
+ const row = document.createElement('div');
311
+ const nameInput = createNameInput(device, index);
312
+ const wattsInput = createWattsInput(device, index);
313
+ row.className = 'ups-device';
314
+ row.append(nameInput, createWattsLabel(wattsInput), createRemoveButton(index));
315
+ return row;
316
+ }
317
+
318
+ function createNameInput(device: Device, index: number) {
319
+ const input = document.createElement('input');
320
+ input.type = 'text';
321
+ input.className = 'ups-device-name';
322
+ input.ariaLabel = config.deviceName;
323
+ input.value = device.name;
324
+ input.addEventListener('input', () => {
325
+ devices[index]!.name = input.value;
326
+ saveState();
327
+ });
328
+ return input;
329
+ }
330
+
331
+ function createWattsInput(device: Device, index: number) {
332
+ const input = document.createElement('input');
333
+ input.type = 'number';
334
+ input.min = '0';
335
+ input.max = '2000';
336
+ input.step = '5';
337
+ input.value = String(device.watts);
338
+ input.addEventListener('input', () => {
339
+ devices[index]!.watts = Number(input.value);
340
+ calculate();
341
+ });
342
+ return input;
343
+ }
344
+
345
+ function createWattsLabel(input: HTMLInputElement) {
346
+ const label = document.createElement('label');
347
+ const text = document.createElement('span');
348
+ const field = document.createElement('span');
349
+ const unit = document.createElement('em');
350
+ text.textContent = config.watts;
351
+ field.className = 'ups-number-field';
352
+ unit.textContent = config.wattsUnit;
353
+ field.append(input, unit);
354
+ label.append(text, field);
355
+ return label;
356
+ }
357
+
358
+ function createRemoveButton(index: number) {
359
+ const button = document.createElement('button');
360
+ button.type = 'button';
361
+ button.ariaLabel = config.remove;
362
+ appendTrashIcon(button);
363
+ button.addEventListener('click', () => {
364
+ devices.splice(index, 1);
365
+ renderDevices();
366
+ calculate();
367
+ });
368
+ return button;
369
+ }
370
+
371
+ function appendTrashIcon(button: HTMLButtonElement) {
372
+ if (els.trashIcon?.content) button.append(els.trashIcon.content.cloneNode(true));
373
+ }
374
+
375
+ els.add?.addEventListener('click', () => {
376
+ devices.push({ name: config.deviceName, watts: 25 });
377
+ renderDevices();
378
+ calculate();
379
+ });
380
+ [els.battery, els.efficiency, els.powerFactor, els.reserve].forEach((input) => input?.addEventListener('input', calculate));
381
+
382
+ renderDevices();
383
+ calculate();
384
+ </script>
@@ -0,0 +1,29 @@
1
+ import type { HardwareToolEntry, ToolLocaleContent } from '../../types';
2
+ import type { UpsRuntimeCalculatorUI } from './ui';
3
+
4
+ export type UpsRuntimeCalculatorLocaleContent = ToolLocaleContent<UpsRuntimeCalculatorUI>;
5
+
6
+ export const upsRuntimeCalculator: HardwareToolEntry<UpsRuntimeCalculatorUI> = {
7
+ id: 'ups-runtime-calculator',
8
+ icons: {
9
+ bg: 'mdi:battery-clock',
10
+ fg: 'mdi:power-plug-battery',
11
+ },
12
+ i18n: {
13
+ de: () => import('./i18n/de').then((m) => m.content),
14
+ en: () => import('./i18n/en').then((m) => m.content),
15
+ es: () => import('./i18n/es').then((m) => m.content),
16
+ fr: () => import('./i18n/fr').then((m) => m.content),
17
+ id: () => import('./i18n/id').then((m) => m.content),
18
+ it: () => import('./i18n/it').then((m) => m.content),
19
+ ja: () => import('./i18n/ja').then((m) => m.content),
20
+ ko: () => import('./i18n/ko').then((m) => m.content),
21
+ nl: () => import('./i18n/nl').then((m) => m.content),
22
+ pl: () => import('./i18n/pl').then((m) => m.content),
23
+ pt: () => import('./i18n/pt').then((m) => m.content),
24
+ ru: () => import('./i18n/ru').then((m) => m.content),
25
+ sv: () => import('./i18n/sv').then((m) => m.content),
26
+ tr: () => import('./i18n/tr').then((m) => m.content),
27
+ zh: () => import('./i18n/zh').then((m) => m.content),
28
+ },
29
+ };
@@ -0,0 +1,208 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { UpsRuntimeCalculatorUI } from '../ui';
4
+ import { bibliography } from '../bibliography';
5
+
6
+ const slug = 'usv-laufzeit-rechner';
7
+ const title = 'USV Laufzeit Rechner';
8
+ const description = 'Berechnen Sie die USV-Batterielaufzeit, die gesamte geschützte Last, die nutzbaren Wattstunden und die empfohlene VA-Größe für PCs, Monitore, Router, NAS-Geräte und Heimlabor-Hardware.';
9
+
10
+ const faqData = [
11
+ {
12
+ question: 'Wie berechne ich die USV-Laufzeit?',
13
+ answer: 'Addieren Sie die Wattzahl aller an der USV angeschlossenen Geräte, multiplizieren Sie die Batterie-Wattstunden der USV mit dem Wechselrichter-Wirkungsgrad, ziehen Sie die gewünschte Reserve ab und teilen Sie die nutzbaren Wattstunden durch die Last in Watt. Das Ergebnis in Stunden kann mit 60 multipliziert werden, um Minuten zu erhalten.',
14
+ },
15
+ {
16
+ question: 'Warum weicht die tatsächliche USV-Laufzeit von der Schätzung ab?',
17
+ answer: 'Die Laufzeit ändert sich mit Batteriealter, Temperatur, Entladerate, Wechselrichter-Effizienz, Lastspitzen und der Abschaltspannung des Herstellers. Betrachten Sie das Ergebnis als Planungsschätzung und überprüfen Sie es mit einem kontrollierten Abschalttest.',
18
+ },
19
+ {
20
+ question: 'Sollte ich eine USV nach Watt oder VA dimensionieren?',
21
+ answer: 'Prüfen Sie beides. Watt gibt die tatsächliche Leistung an, die die USV liefern kann, während VA den Leistungsfaktor einschließt. Eine USV muss Ihre Last in Watt übertreffen und genügend VA-Reserve für die angeschlossenen Geräte haben.',
22
+ },
23
+ {
24
+ question: 'Wie viel USV-Reserve sollte ich einplanen?',
25
+ answer: 'Ein praktisches Ziel ist es, die normale Last unter etwa 70-80 % der USV-Wattleistung zu halten. Dies lässt Raum für Anlaufspitzen, Batteriealterung und zukünftige Geräte.',
26
+ },
27
+ {
28
+ question: 'Kann ich Drucker oder Heizgeräte an eine USV anschließen?',
29
+ answer: 'Vermeiden Sie Laserdrucker, Heizgeräte und andere Lasten mit hohen Einschaltströmen, es sei denn, die USV ist ausdrücklich dafür ausgelegt. Sie können den Wechselrichter überlasten und die Laufzeit drastisch verkürzen.',
30
+ },
31
+ ];
32
+
33
+ const howToData = [
34
+ {
35
+ name: 'Geschützte Geräte auflisten',
36
+ text: 'Geben Sie die Geräte ein, die bei einem Stromausfall online bleiben müssen, z. B. Computer, Monitor, Router, Modem, NAS und Netzwerk-Switch.',
37
+ },
38
+ {
39
+ name: 'Realistische Wattzahlen eingeben',
40
+ text: 'Verwenden Sie nach Möglichkeit gemessene Werte von der Steckdose. Wenn Sie nur die Nennleistung des Netzteils kennen, reduzieren Sie diese auf die erwartete Betriebslast, anstatt den maximalen Etikettenwert zu verwenden.',
41
+ },
42
+ {
43
+ name: 'Batteriekapazität und Annahmen festlegen',
44
+ text: 'Geben Sie die Batterie-Wattstunden der USV, den Wechselrichter-Wirkungsgrad, den Leistungsfaktor und die gewünschte Reserve für ein sicheres Herunterfahren ein.',
45
+ },
46
+ {
47
+ name: 'Laufzeit und VA-Empfehlung ablesen',
48
+ text: 'Verwenden Sie die geschätzten Minuten und die empfohlene VA-Größe als Kauf- oder Konfigurationsleitfaden und validieren Sie die Einrichtung mit einem sicheren Ausfalltest.',
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, i) => ({
68
+ '@type': 'HowToStep',
69
+ position: i + 1,
70
+ name: step.name,
71
+ text: step.text,
72
+ })),
73
+ };
74
+
75
+ const appSchema: WithContext<SoftwareApplication> = {
76
+ '@context': 'https://schema.org',
77
+ '@type': 'SoftwareApplication',
78
+ name: title,
79
+ description,
80
+ applicationCategory: 'UtilityApplication',
81
+ operatingSystem: 'All',
82
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
83
+ inLanguage: 'de',
84
+ };
85
+
86
+ export const content: ToolLocaleContent<UpsRuntimeCalculatorUI> = {
87
+ slug,
88
+ title,
89
+ description,
90
+ faq: faqData,
91
+ bibliography,
92
+ howTo: howToData,
93
+ schemas: [faqSchema, howToSchema, appSchema],
94
+ seo: [
95
+ {
96
+ type: 'title',
97
+ text: 'USV-Laufzeit-Rechner: Batterie-Backup-Zeit schätzen',
98
+ level: 2,
99
+ },
100
+ {
101
+ type: 'paragraph',
102
+ html: 'Eine USV-Laufzeitschätzung beantwortet zwei praktische Fragen: Wie lange Ihre Hardware bei einem Stromausfall online bleiben kann und ob die USV groß genug für die angeschlossene Last ist. Dieser Rechner kombiniert Geräteleistung, Batterie-Wattstunden, Wechselrichter-Effizienz, Leistungsfaktor und Abschaltreserve, sodass das Ergebnis näher an der realen Planung liegt als eine einfache Division der Batteriekapazität.',
103
+ },
104
+ {
105
+ type: 'title',
106
+ text: 'Die Laufzeit-Formel',
107
+ level: 3,
108
+ },
109
+ {
110
+ type: 'paragraph',
111
+ html: 'Die geschätzte Laufzeit in Stunden ist <strong>nutzbare Batterie-Wattstunden geteilt durch die Gesamtlast in W</strong>. Die nutzbaren Wattstunden entsprechen nicht der aufgedruckten Batteriekapazität: Sie sollten um Wechselrichterverluste und die gewünschte Abschaltreserve bereinigt werden. Beispielsweise liefert eine 432-Wh-Batterie bei 86 % Effizienz und 20 % Reserve etwa 297 Wh nutzbare Energie.',
112
+ },
113
+ {
114
+ type: 'table',
115
+ headers: ['Eingabe', 'Warum sie wichtig ist', 'Praktische Orientierung'],
116
+ rows: [
117
+ ['Last in Watt', 'Steuert direkt die Laufzeit', 'Nach Möglichkeit mit einem Wandmessgerät messen, insbesondere bei Gaming-PCs und NAS-Systemen.'],
118
+ ['Batterie-Wh', 'Bestimmt den Energievorrat', 'Herstellerangaben oder Nennspannung multipliziert mit Amperestunden verwenden.'],
119
+ ['Effizienz', 'Berücksichtigt Wechselrichterverluste', '80-90 % sind ein sinnvoller Planungsbereich für viele Consumer-USV-Geräte.'],
120
+ ['Leistungsfaktor', 'Wandelt Watt in VA um', 'USV-Spezifikation verwenden, falls bekannt; 0,6-0,8 ist bei Einsteiger- und Mittelklassegeräten üblich.'],
121
+ ['Reserve', 'Sichert die Abschaltmarge', '10-25 % sind sinnvoll für ein kontrolliertes Herunterfahren von PC oder Server.'],
122
+ ],
123
+ },
124
+ {
125
+ type: 'title',
126
+ text: 'Wie viel Laufzeit benötigen Sie?',
127
+ level: 3,
128
+ },
129
+ {
130
+ type: 'list',
131
+ items: [
132
+ '5-10 Minuten: ausreichend, um kurze Spannungsschwankungen zu überbrücken oder einen Desktop sicher herunterzufahren.',
133
+ '10-20 Minuten: nützlich für Router, Modems, NAS-Geräte und kleine Heimlabor-Knoten.',
134
+ '30+ Minuten: besser für Netzwerkkontinuität, Remote-Arbeit und Standorte mit häufigen Ausfällen.',
135
+ 'Mehrere Stunden: erfordert in der Regel ein größeres Batteriesystem, nicht nur eine Desktop-USV.',
136
+ ],
137
+ },
138
+ {
139
+ type: 'title',
140
+ text: 'Watt vs. VA bei der USV-Auswahl',
141
+ level: 3,
142
+ },
143
+ {
144
+ type: 'paragraph',
145
+ html: 'USV-Produktnamen geben häufig VA an, aber die Watt-Leistung ist die härtere Grenze für reale Geräte. Eine 900-VA-USV unterstützt möglicherweise nur 540 W, während ein anderes 900-VA-Modell 600 W oder mehr unterstützt. Prüfen Sie beide Angaben und halten Sie den Normalbetrieb unter dem Maximum, um Überlastalarme, verkürzte Batterielebensdauer und Übertragungsfehler bei einem Ausfall zu vermeiden.',
146
+ },
147
+ {
148
+ type: 'title',
149
+ text: 'Lasten, die Laufzeitschätzungen verfälschen',
150
+ level: 3,
151
+ },
152
+ {
153
+ type: 'list',
154
+ items: [
155
+ 'Gaming-PCs können in Sekunden von niedrigem Leerlaufverbrauch auf hohe GPU-Last wechseln.',
156
+ 'Monitore variieren stark je nach Helligkeit, HDR-Modus und Bildschirmgröße.',
157
+ 'NAS-Geräte verbrauchen beim Hochfahren der Festplatten und bei Wiederherstellungen zusätzlichen Strom.',
158
+ 'Laserdrucker, Heizgeräte, Pumpen und Kompressoren sind schlechte USV-Lasten, sofern nicht ausdrücklich unterstützt.',
159
+ 'Alte Batterien können deutlich weniger Laufzeit liefern, als die ursprüngliche Kapazität vermuten lässt.',
160
+ ],
161
+ },
162
+ {
163
+ type: 'title',
164
+ text: 'Checkliste für sicheres Testen',
165
+ level: 3,
166
+ },
167
+ {
168
+ type: 'list',
169
+ items: [
170
+ 'Laden Sie die USV vor dem Test vollständig auf.',
171
+ 'Speichern Sie offene Arbeiten und vermeiden Sie Tests während kritischer Schreibvorgänge oder Firmware-Updates.',
172
+ 'Trennen Sie die Netzsteckdose, nicht die Geräte, und beobachten Sie USV-Lastprozentsatz und Batterieschätzung.',
173
+ 'Stellen Sie sicher, dass Ihr PC, NAS oder Server das Abschaltsignal erhält, bevor die Batterie erschöpft ist.',
174
+ 'Wiederholen Sie den Test alle paar Monate, da Blei-Säure-USV-Batterien schnell altern.',
175
+ ],
176
+ },
177
+ ],
178
+ ui: {
179
+ loadTitle: 'Geschützte Last',
180
+ addDevice: 'Gerät hinzufügen',
181
+ deviceName: 'Gerät',
182
+ watts: 'Watt',
183
+ remove: 'Gerät entfernen',
184
+ batteryWh: 'Batteriekapazität (Wh)',
185
+ efficiency: 'Wechselrichter-Effizienz',
186
+ powerFactor: 'Leistungsfaktor',
187
+ reserve: 'Abschaltreserve',
188
+ totalLoad: 'Gesamtlast',
189
+ runtime: 'Geschätzte Laufzeit',
190
+ recommendedUps: 'Empfohlene Größe',
191
+ usableEnergy: 'Nutzbare Energie',
192
+ minutes: 'Min.',
193
+ hours: 'Std.',
194
+ wattsUnit: 'W',
195
+ vaUnit: 'VA',
196
+ whUnit: 'Wh',
197
+ assumptionsLabel: 'USV-Annahmen',
198
+ presetDesktop: 'Desktop-PC',
199
+ presetMonitor: '27-Zoll-Monitor',
200
+ presetRouter: 'Router und Modem',
201
+ presetNas: 'NAS mit zwei Einschüben',
202
+ percentUnit: '%',
203
+ bandLight: 'komfortables Backup-Fenster mit einer empfohlenen USV bei etwa',
204
+ bandBalanced: 'ausgewogenes Backup-Fenster mit einer empfohlenen USV bei etwa',
205
+ bandHeavy: 'kurzes Backup-Fenster; erwägen Sie einen größeren Akku oder reduzieren Sie die Last bei etwa',
206
+ summaryPrefix: 'Diese Konfiguration hat ein',
207
+ },
208
+ };