@jjlmoya/utils-diy 1.26.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 (48) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +2 -1
  3. package/src/entries.ts +4 -1
  4. package/src/tests/locale_completeness.test.ts +2 -3
  5. package/src/tests/seo_translation_completeness.test.ts +69 -0
  6. package/src/tests/tool_validation.test.ts +2 -2
  7. package/src/tool/drywallCalculator/i18n/de.ts +1 -1
  8. package/src/tool/drywallCalculator/i18n/id.ts +2 -2
  9. package/src/tool/drywallCalculator/i18n/it.ts +2 -2
  10. package/src/tool/drywallCalculator/i18n/ja.ts +1 -1
  11. package/src/tool/drywallCalculator/i18n/ko.ts +1 -1
  12. package/src/tool/drywallCalculator/i18n/nl.ts +2 -2
  13. package/src/tool/drywallCalculator/i18n/pl.ts +3 -3
  14. package/src/tool/drywallCalculator/i18n/pt.ts +3 -3
  15. package/src/tool/drywallCalculator/i18n/ru.ts +2 -2
  16. package/src/tool/drywallCalculator/i18n/sv.ts +3 -3
  17. package/src/tool/drywallCalculator/i18n/tr.ts +3 -3
  18. package/src/tool/drywallCalculator/i18n/zh.ts +3 -3
  19. package/src/tool/excavationVolumeCalculator/bibliography.astro +6 -0
  20. package/src/tool/excavationVolumeCalculator/bibliography.ts +12 -0
  21. package/src/tool/excavationVolumeCalculator/component.astro +80 -0
  22. package/src/tool/excavationVolumeCalculator/controller.ts +69 -0
  23. package/src/tool/excavationVolumeCalculator/dom-views.ts +63 -0
  24. package/src/tool/excavationVolumeCalculator/entry.ts +26 -0
  25. package/src/tool/excavationVolumeCalculator/evaluator.ts +21 -0
  26. package/src/tool/excavationVolumeCalculator/excavation-volume-calculator.css +442 -0
  27. package/src/tool/excavationVolumeCalculator/i18n/de.ts +120 -0
  28. package/src/tool/excavationVolumeCalculator/i18n/en.ts +120 -0
  29. package/src/tool/excavationVolumeCalculator/i18n/es.ts +120 -0
  30. package/src/tool/excavationVolumeCalculator/i18n/fr.ts +120 -0
  31. package/src/tool/excavationVolumeCalculator/i18n/id.ts +120 -0
  32. package/src/tool/excavationVolumeCalculator/i18n/it.ts +120 -0
  33. package/src/tool/excavationVolumeCalculator/i18n/ja.ts +120 -0
  34. package/src/tool/excavationVolumeCalculator/i18n/ko.ts +120 -0
  35. package/src/tool/excavationVolumeCalculator/i18n/nl.ts +120 -0
  36. package/src/tool/excavationVolumeCalculator/i18n/pl.ts +120 -0
  37. package/src/tool/excavationVolumeCalculator/i18n/pt.ts +120 -0
  38. package/src/tool/excavationVolumeCalculator/i18n/ru.ts +120 -0
  39. package/src/tool/excavationVolumeCalculator/i18n/sv.ts +120 -0
  40. package/src/tool/excavationVolumeCalculator/i18n/tr.ts +120 -0
  41. package/src/tool/excavationVolumeCalculator/i18n/zh.ts +120 -0
  42. package/src/tool/excavationVolumeCalculator/index.ts +11 -0
  43. package/src/tool/excavationVolumeCalculator/logic.test.ts +29 -0
  44. package/src/tool/excavationVolumeCalculator/logic.ts +35 -0
  45. package/src/tool/excavationVolumeCalculator/seo.astro +11 -0
  46. package/src/tool/excavationVolumeCalculator/storage.ts +28 -0
  47. package/src/tool/excavationVolumeCalculator/ui.ts +66 -0
  48. package/src/tools.ts +2 -1
@@ -0,0 +1,63 @@
1
+ import type { ExcavationOutput, ExcavationCalculatorUI, SoilProfile } from './ui';
2
+ import { evaluateExcavation } from './evaluator';
3
+
4
+ const soilColors: Record<SoilProfile, string> = { sand: '#e6b86a', topsoil: '#86603b', clay: '#b8644b', gravel: '#8b9290' };
5
+
6
+ export function formatVolume(value: number): string {
7
+ return value.toLocaleString('en-US', { maximumFractionDigits: 2, minimumFractionDigits: 2 });
8
+ }
9
+
10
+ export function renderExcavationScene(root: Element, output: ExcavationOutput, soil: SoilProfile, ui: ExcavationCalculatorUI): void {
11
+ const scene = root.querySelector('[data-ev-scene]');
12
+ if (!scene) return;
13
+ const color = soilColors[soil];
14
+ const expansionWidth = Math.min(190, Math.max(40, output.looseHigh / Math.max(output.bankVolume, 0.01) * 105));
15
+ scene.innerHTML = `<svg class="ev-scene-svg" viewBox="0 0 640 320" role="img" aria-label="${ui.sceneTitle}">
16
+ <rect class="ev-sky" x="0" y="0" width="640" height="300" rx="20" />
17
+ <path class="ev-contour" d="M32 62 C126 22 205 75 293 45 S470 24 610 62 M42 83 C130 48 203 96 304 68 S477 49 600 85" />
18
+ <path class="ev-ground" d="M0 112 C120 102 190 119 290 108 C410 96 520 120 640 108 V300 H0Z" />
19
+ <path class="ev-strata" d="M0 160 C120 150 190 168 290 156 C410 144 520 167 640 156 M0 212 C120 202 190 220 290 208 C410 196 520 219 640 208" />
20
+ <path class="ev-cut" d="M150 112 V235 H405 V112" fill="${color}" />
21
+ <path class="ev-cut-line" d="M150 112 V235 H405 V112" />
22
+ <path class="ev-bank-fill" d="M178 142 H377 V214 H178Z" />
23
+ <path class="ev-loose-fill" d="M455 244 Q474 207 494 224 Q518 177 542 222 Q570 194 594 225 Q610 211 ${455 + expansionWidth} 244Z" />
24
+ <path class="ev-arrow" d="M420 178 C445 178 445 178 468 178 M458 168 L468 178 L458 188" />
25
+ <path class="ev-measure" d="M128 112 V235 M122 112 H134 M122 235 H134" />
26
+ <text class="ev-label ev-label-measure" x="105" y="181">D</text>
27
+ <text class="ev-label ev-label-bank" x="178" y="132">${ui.sceneBank}</text>
28
+ <text class="ev-label ev-label-loose" x="455" y="264">${ui.sceneLoose}</text>
29
+ <text class="ev-label ev-label-cut" x="158" y="286">${ui.sceneCut}</text>
30
+ <circle class="ev-sun" cx="570" cy="52" r="20" />
31
+ <path class="ev-grass" d="M0 112 ${grassPath()}" />
32
+ </svg>`;
33
+ }
34
+
35
+ function grassPath(): string {
36
+ return Array.from({ length: 28 }, (_, index) => {
37
+ const x = index * 24;
38
+ return `M${x} 112 l4 -10 M${x + 8} 112 l-2 -8`;
39
+ }).join(' ');
40
+ }
41
+
42
+ export function renderResults(root: Element, output: ExcavationOutput, soil: SoilProfile, ui: ExcavationCalculatorUI): void {
43
+ setText(root, '[data-ev-bank]', formatVolume(output.bankVolume));
44
+ setText(root, '[data-ev-loose-low]', formatVolume(output.looseLow));
45
+ setText(root, '[data-ev-loose-high]', formatVolume(output.looseHigh));
46
+ setText(root, '[data-ev-expansion]', `${Math.round(output.expansionLow * 100)}-${Math.round(output.expansionHigh * 100)}%`);
47
+ const evaluation = evaluateExcavation(output, soil);
48
+ const badge = root.querySelector('[data-ev-status]');
49
+ if (badge) {
50
+ badge.textContent = `${ui.soilStatusLabel}: ${ui[`soilStatus${capitalize(evaluation.status)}` as keyof ExcavationCalculatorUI]}`;
51
+ badge.setAttribute('data-ev-status-level', evaluation.status);
52
+ }
53
+ renderExcavationScene(root, output, soil, ui);
54
+ }
55
+
56
+ function setText(root: Element, selector: string, value: string): void {
57
+ const element = root.querySelector(selector);
58
+ if (element) element.textContent = value;
59
+ }
60
+
61
+ function capitalize(value: string): string {
62
+ return value.charAt(0).toUpperCase() + value.slice(1);
63
+ }
@@ -0,0 +1,26 @@
1
+ import type { DiyToolEntry, ToolLocaleContent } from '../../types';
2
+ import type { ExcavationCalculatorUI } from './ui';
3
+
4
+ export type ExcavationVolumeCalculatorLocaleContent = ToolLocaleContent<ExcavationCalculatorUI>;
5
+
6
+ export const excavationVolumeCalculator: DiyToolEntry<ExcavationCalculatorUI> = {
7
+ id: 'excavation-volume-soil-swell-calculator',
8
+ icons: { bg: 'mdi:land-fields', fg: 'mdi:shovel' },
9
+ i18n: {
10
+ de: () => import('./i18n/de').then((m) => m.content),
11
+ en: () => import('./i18n/en').then((m) => m.content),
12
+ es: () => import('./i18n/es').then((m) => m.content),
13
+ fr: () => import('./i18n/fr').then((m) => m.content),
14
+ id: () => import('./i18n/id').then((m) => m.content),
15
+ it: () => import('./i18n/it').then((m) => m.content),
16
+ ja: () => import('./i18n/ja').then((m) => m.content),
17
+ ko: () => import('./i18n/ko').then((m) => m.content),
18
+ nl: () => import('./i18n/nl').then((m) => m.content),
19
+ pl: () => import('./i18n/pl').then((m) => m.content),
20
+ pt: () => import('./i18n/pt').then((m) => m.content),
21
+ ru: () => import('./i18n/ru').then((m) => m.content),
22
+ sv: () => import('./i18n/sv').then((m) => m.content),
23
+ tr: () => import('./i18n/tr').then((m) => m.content),
24
+ zh: () => import('./i18n/zh').then((m) => m.content),
25
+ },
26
+ };
@@ -0,0 +1,21 @@
1
+ import type { ExcavationOutput, SoilProfile } from './ui';
2
+
3
+ export type ExcavationStatus = 'low' | 'medium' | 'high';
4
+
5
+ export interface ExcavationEvaluation {
6
+ status: ExcavationStatus;
7
+ ratio: number;
8
+ soil: SoilProfile;
9
+ }
10
+
11
+ export function evaluateExcavation(output: ExcavationOutput, soil: SoilProfile): ExcavationEvaluation {
12
+ const ratio = output.expansionHigh;
13
+ const status = getStatus(ratio);
14
+ return { status, ratio, soil };
15
+ }
16
+
17
+ function getStatus(ratio: number): ExcavationStatus {
18
+ if (ratio > 0.25) return 'high';
19
+ if (ratio >= 0.15) return 'medium';
20
+ return 'low';
21
+ }
@@ -0,0 +1,442 @@
1
+ :root {
2
+ --n-ink: #1d2925;
3
+ --n-muted: #64736b;
4
+ --n-paper: #f5f1e8;
5
+ --n-surface: #fffdf8;
6
+ --n-line: #d8d0c2;
7
+ --n-accent: #2f7359;
8
+ --n-accent-soft: #dceadd;
9
+ --n-sand: #e6b86a;
10
+ --n-soil: #86603b;
11
+ --n-warning: #8a5b20;
12
+ }
13
+
14
+ .theme-dark {
15
+ --n-ink: #f4f0e8;
16
+ --n-muted: #b3c0b8;
17
+ --n-paper: #263630;
18
+ --n-surface: #31453a;
19
+ --n-line: #64796c;
20
+ --n-accent: #9bd0a7;
21
+ --n-accent-soft: #315340;
22
+ --n-sand: #d5a855;
23
+ --n-soil: #b58257;
24
+ --n-warning: #f0c77b;
25
+ }
26
+
27
+ .ev-root {
28
+ max-width: 1120px;
29
+ margin: 0 auto;
30
+ color: var(--n-ink);
31
+ font: 1rem/1.4 Georgia, serif;
32
+ }
33
+
34
+ .ev-board {
35
+ overflow: hidden;
36
+ border: 1px solid var(--n-line);
37
+ border-radius: 1.4rem;
38
+ padding: clamp(0.85rem, 2vw, 1.35rem);
39
+ background: var(--n-paper);
40
+ }
41
+
42
+ .ev-intro {
43
+ display: flex;
44
+ align-items: start;
45
+ gap: 1rem;
46
+ margin-bottom: 0.85rem;
47
+ }
48
+
49
+ .ev-intro-mark {
50
+ display: grid;
51
+ flex: 0 0 3rem;
52
+ place-items: center;
53
+ height: 3rem;
54
+ border-radius: 50%;
55
+ background: var(--n-accent);
56
+ color: var(--n-paper);
57
+ font-size: 1.35rem;
58
+ }
59
+
60
+ .ev-intro p {
61
+ max-width: 62ch;
62
+ margin: 0;
63
+ color: var(--n-muted);
64
+ line-height: 1.55;
65
+ }
66
+
67
+ .ev-scene-panel {
68
+ display: grid;
69
+ gap: 0.9rem;
70
+ border-bottom: 1px solid var(--n-line);
71
+ padding-bottom: 0.95rem;
72
+ }
73
+
74
+ .ev-kicker {
75
+ color: var(--n-accent);
76
+ font: 700 0.72rem/1.2 system-ui, sans-serif;
77
+ letter-spacing: 0.12em;
78
+ text-transform: uppercase;
79
+ }
80
+
81
+ .ev-result-heading {
82
+ display: flex;
83
+ align-items: center;
84
+ justify-content: space-between;
85
+ gap: 0.8rem;
86
+ }
87
+
88
+ .ev-status {
89
+ border-radius: 999px;
90
+ padding: 0.38rem 0.6rem;
91
+ background: var(--n-accent);
92
+ color: var(--n-paper);
93
+ font: 700 0.68rem system-ui, sans-serif;
94
+ }
95
+
96
+ .ev-status[data-ev-status-level="high"] {
97
+ background: var(--n-warning);
98
+ }
99
+
100
+ .ev-scene {
101
+ min-height: 260px;
102
+ border: 1px solid var(--n-line);
103
+ border-radius: 0.9rem;
104
+ background: var(--n-surface);
105
+ }
106
+
107
+ .ev-scene-svg {
108
+ display: block;
109
+ width: 100%;
110
+ height: auto;
111
+ }
112
+
113
+ .ev-sky {
114
+ fill: var(--n-accent-soft);
115
+ }
116
+
117
+ .ev-ground {
118
+ fill: var(--n-soil);
119
+ opacity: 0.8;
120
+ }
121
+
122
+ .ev-contour,
123
+ .ev-strata {
124
+ fill: none;
125
+ stroke: var(--n-accent);
126
+ stroke-width: 2;
127
+ opacity: 0.45;
128
+ }
129
+
130
+ .ev-strata {
131
+ stroke: var(--n-sand);
132
+ stroke-width: 3;
133
+ opacity: 0.35;
134
+ }
135
+
136
+ .ev-cut {
137
+ stroke: var(--n-ink);
138
+ stroke-width: 2;
139
+ }
140
+
141
+ .ev-cut-line {
142
+ fill: none;
143
+ stroke: var(--n-ink);
144
+ stroke-dasharray: 5 5;
145
+ stroke-width: 2;
146
+ }
147
+
148
+ .ev-bank-fill {
149
+ fill: var(--n-sand);
150
+ opacity: 0.9;
151
+ }
152
+
153
+ .ev-loose-fill {
154
+ fill: var(--n-sand);
155
+ stroke: var(--n-ink);
156
+ stroke-width: 2;
157
+ }
158
+
159
+ .ev-arrow {
160
+ fill: none;
161
+ stroke: var(--n-accent);
162
+ stroke-width: 3;
163
+ }
164
+
165
+ .ev-measure {
166
+ fill: none;
167
+ stroke: var(--n-ink);
168
+ stroke-width: 2;
169
+ }
170
+
171
+ .ev-label {
172
+ fill: var(--n-ink);
173
+ font: 700 13px system-ui, sans-serif;
174
+ }
175
+
176
+ .ev-sun {
177
+ fill: var(--n-sand);
178
+ }
179
+
180
+ .ev-grass {
181
+ fill: none;
182
+ stroke: var(--n-accent-soft);
183
+ stroke-width: 3;
184
+ }
185
+
186
+ .ev-metrics {
187
+ display: grid;
188
+ grid-template-columns: 1fr 1.2fr;
189
+ gap: 0.6rem;
190
+ }
191
+
192
+ .ev-metric {
193
+ border: 1px solid var(--n-line);
194
+ border-radius: 0.75rem;
195
+ padding: 0.8rem;
196
+ }
197
+
198
+ .ev-metric-main {
199
+ border-color: var(--n-accent);
200
+ background: var(--n-accent);
201
+ color: var(--n-paper);
202
+ }
203
+
204
+ .ev-metric-label {
205
+ display: block;
206
+ font: 700 0.72rem system-ui, sans-serif;
207
+ }
208
+
209
+ .ev-metric-value {
210
+ display: block;
211
+ overflow: hidden;
212
+ margin-top: 0.4rem;
213
+ white-space: nowrap;
214
+ font: 700 clamp(1.2rem, 3.5vw, 2.2rem)/1 system-ui, sans-serif;
215
+ text-overflow: ellipsis;
216
+ }
217
+
218
+ .ev-unit-suffix {
219
+ font-size: 0.55em;
220
+ vertical-align: super;
221
+ }
222
+
223
+ .ev-metric-help {
224
+ display: block;
225
+ margin-top: 0.35rem;
226
+ color: var(--n-muted);
227
+ font: 0.72rem/1.4 system-ui, sans-serif;
228
+ }
229
+
230
+ .ev-metric-main .ev-metric-help {
231
+ color: var(--n-paper);
232
+ opacity: 0.85;
233
+ }
234
+
235
+ .ev-expansion {
236
+ display: flex;
237
+ justify-content: space-between;
238
+ gap: 1rem;
239
+ border-top: 1px solid var(--n-line);
240
+ padding-top: 0.75rem;
241
+ font: 700 0.8rem system-ui, sans-serif;
242
+ }
243
+
244
+ .ev-expansion strong {
245
+ color: var(--n-accent);
246
+ }
247
+
248
+ .ev-workbench {
249
+ display: grid;
250
+ grid-template-columns: 1fr;
251
+ gap: 1.25rem;
252
+ padding-top: 0.95rem;
253
+ }
254
+
255
+ .ev-control-panel {
256
+ display: grid;
257
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
258
+ align-content: start;
259
+ gap: 0.8rem 1.25rem;
260
+ }
261
+
262
+ .ev-control-heading {
263
+ grid-column: 1 / -1;
264
+ display: flex;
265
+ align-items: center;
266
+ justify-content: space-between;
267
+ gap: 1rem;
268
+ }
269
+
270
+ .ev-segmented {
271
+ display: inline-flex;
272
+ gap: 0.2rem;
273
+ border: 1px solid var(--n-line);
274
+ border-radius: 999px;
275
+ padding: 0.2rem;
276
+ background: var(--n-surface);
277
+ }
278
+
279
+ .ev-segmented button,
280
+ .ev-soil-button {
281
+ cursor: pointer;
282
+ border: 1px solid transparent;
283
+ color: var(--n-muted);
284
+ background: transparent;
285
+ font: 700 0.75rem system-ui, sans-serif;
286
+ }
287
+
288
+ .ev-segmented button {
289
+ border-radius: 999px;
290
+ padding: 0.48rem 0.7rem;
291
+ }
292
+
293
+ .ev-segmented button.ev-active,
294
+ .ev-soil-button.ev-active {
295
+ background: var(--n-accent);
296
+ color: var(--n-paper);
297
+ }
298
+
299
+ .ev-soil-button.ev-active .ev-soil-name,
300
+ .ev-soil-button.ev-active .ev-soil-band {
301
+ color: var(--n-paper);
302
+ }
303
+
304
+ .ev-soil-button:hover {
305
+ border-color: var(--n-accent);
306
+ color: var(--n-accent);
307
+ }
308
+
309
+ .ev-fieldset {
310
+ display: grid;
311
+ gap: 0.6rem;
312
+ margin: 0;
313
+ border: 0;
314
+ padding: 0;
315
+ }
316
+
317
+ .ev-fieldset:first-of-type {
318
+ grid-column: 1;
319
+ }
320
+
321
+ .ev-soil-fieldset {
322
+ grid-column: 2;
323
+ }
324
+
325
+ .ev-fieldset legend,
326
+ .ev-field label {
327
+ font: 700 0.8rem system-ui, sans-serif;
328
+ }
329
+
330
+ .ev-dimensions {
331
+ display: grid;
332
+ grid-template-columns: repeat(3, 1fr);
333
+ gap: 0.55rem;
334
+ }
335
+
336
+ .ev-field {
337
+ display: grid;
338
+ gap: 0.3rem;
339
+ color: var(--n-muted);
340
+ }
341
+
342
+ .ev-input {
343
+ display: flex;
344
+ align-items: center;
345
+ border: 1px solid var(--n-line);
346
+ border-radius: 0.55rem;
347
+ background: var(--n-surface);
348
+ }
349
+
350
+ .ev-input input {
351
+ min-width: 0;
352
+ width: 100%;
353
+ border: 0;
354
+ outline: 0;
355
+ padding: 0.65rem;
356
+ color: var(--n-ink);
357
+ background: transparent;
358
+ font: 700 1rem system-ui, sans-serif;
359
+ }
360
+
361
+ .ev-unit {
362
+ padding-right: 0.65rem;
363
+ color: var(--n-muted);
364
+ font: 700 0.72rem system-ui, sans-serif;
365
+ }
366
+
367
+ .ev-soil-grid {
368
+ display: grid;
369
+ grid-template-columns: repeat(2, 1fr);
370
+ gap: 0.45rem;
371
+ }
372
+
373
+ .ev-soil-button {
374
+ padding: 0.6rem;
375
+ border-color: var(--n-line);
376
+ border-radius: 0.65rem;
377
+ text-align: left;
378
+ }
379
+
380
+ .ev-soil-name {
381
+ display: block;
382
+ color: var(--n-ink);
383
+ }
384
+
385
+ .ev-soil-band {
386
+ display: block;
387
+ margin-top: 0.2rem;
388
+ color: var(--n-muted);
389
+ font: 0.7rem system-ui, sans-serif;
390
+ }
391
+
392
+ .ev-notes {
393
+ display: grid;
394
+ grid-template-columns: 1fr 1fr;
395
+ gap: 0.85rem;
396
+ border-top: 1px solid var(--n-line);
397
+ padding-top: 0.8rem;
398
+ }
399
+
400
+ .ev-interpretation,
401
+ .ev-warning {
402
+ margin: 0;
403
+ color: var(--n-muted);
404
+ font: 0.78rem/1.5 system-ui, sans-serif;
405
+ }
406
+
407
+ .ev-warning {
408
+ border-left: 3px solid var(--n-warning);
409
+ padding: 0.65rem 0.75rem;
410
+ color: var(--n-warning);
411
+ }
412
+
413
+ button:focus-visible,
414
+ input:focus-visible {
415
+ outline: 3px solid var(--n-sand);
416
+ outline-offset: 2px;
417
+ }
418
+
419
+ @media (max-width: 820px) {
420
+ .ev-control-panel,
421
+ .ev-notes {
422
+ grid-template-columns: 1fr;
423
+ }
424
+
425
+ .ev-control-heading,
426
+ .ev-fieldset:first-of-type,
427
+ .ev-soil-fieldset {
428
+ grid-column: 1;
429
+ }
430
+ }
431
+
432
+ @media (max-width: 480px) {
433
+ .ev-control-heading {
434
+ align-items: start;
435
+ flex-direction: column;
436
+ }
437
+
438
+ .ev-dimensions,
439
+ .ev-metrics {
440
+ grid-template-columns: 1fr;
441
+ }
442
+ }
@@ -0,0 +1,120 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { ExcavationCalculatorUI } from '../ui';
4
+ import { excavationVolumeCalculatorBibliography } from '../bibliography';
5
+
6
+ const slug = 'aushubvolumen-bodenauflockerungs-rechner';
7
+ const title = 'Aushubvolumen Rechner und Bodenauflockerungs Schätzung';
8
+ const description = 'Berechnen Sie das Festmaß des Aushubs und schätzen Sie das aufgelockerte Bodenvolumen nach dem Ausheben mit einer sichtbaren Planungsbandbreite für Sand, Mutterboden, Ton und Kies.';
9
+
10
+ const faq = [
11
+ {
12
+ question: 'Was ist der Unterschied zwischen Festmaß (Bank Volume) und Lockermaß (Loose Volume)?',
13
+ answer: 'Das Festmaß ist das Volumen des Bodens vor dem Aushub im gewachsenen Zustand, berechnet aus Länge, Breite und Tiefe. Das Lockermaß ist der erwartete Raumbedarf nach dem Ausheben inklusive des geschätzten Auflockerungsfaktors für die gewählte Bodenart.',
14
+ },
15
+ {
16
+ question: 'Wie wird das Aushubvolumen berechnet?',
17
+ answer: 'Multiplizieren Sie die Aushublänge mit der Breite und der Tiefe. Ein Aushub von 4 Metern Länge, 3 Metern Breite und 0,5 Metern Tiefe ergibt beispielsweise ein Festmaß von 6 Kubikmetern vor Berücksichtigung der Bodenauflockerung.',
18
+ },
19
+ {
20
+ question: 'Warum benötigt Ton eine breitere Auflockerungsspanne?',
21
+ answer: 'Das Verhalten von Ton hängt stark von Mineralogie, Feuchtigkeit, Dichte und Vorbelastung ab. Dieser Rechner verwendet ein breiteres Planungsband für Ton, da seine Volumenänderung variabler ist als ein rein geometrischer Schnitt vermuten lässt.',
22
+ },
23
+ {
24
+ question: 'Kann dieser Rechner eine geotechnische Bodenuntersuchung ersetzen?',
25
+ answer: 'Nein. Das Ergebnis ist eine Schätzung für Materialtransport und Lagerung. Ziehen Sie bei Fundamenten, Grundwasser, Stützbauwerken, kontaminiertem Boden oder deponiepflichtigem Aushub stets Fachpersonal hinzu.',
26
+ },
27
+ ];
28
+
29
+ const howTo = [
30
+ { name: 'Aushubabmessungen eingeben', text: 'Geben Sie Länge, Breite und Tiefe der Baugrube ein. Wechseln Sie bei Bedarf zwischen metrischen und imperialen Einheiten.' },
31
+ { name: 'Nächstgelegenes Bodenprofil wählen', text: 'Wählen Sie Sand, Mutterboden, Ton oder Kies, um die passende Auflockerungsspanne für das Lockermaß anzuwenden.' },
32
+ { name: 'Materiallogistik planen', text: 'Nutzen Sie das Festmaß für die ungestörte Baugrube und die Lockermaß-Spanne für Beladung, Lkw-Transport, Container oder Zwischenlagerung.' },
33
+ ];
34
+
35
+ const faqSchema: WithContext<FAQPage> = {
36
+ '@context': 'https://schema.org',
37
+ '@type': 'FAQPage',
38
+ mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })),
39
+ };
40
+
41
+ const howToSchema: WithContext<HowTo> = {
42
+ '@context': 'https://schema.org',
43
+ '@type': 'HowTo',
44
+ name: title,
45
+ description,
46
+ step: howTo.map((step, index) => ({ '@type': 'HowToStep', position: index + 1, name: step.name, text: step.text })),
47
+ };
48
+
49
+ const appSchema: WithContext<SoftwareApplication> = {
50
+ '@context': 'https://schema.org',
51
+ '@type': 'SoftwareApplication',
52
+ name: title,
53
+ description,
54
+ applicationCategory: 'UtilityApplication',
55
+ operatingSystem: 'All',
56
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
57
+ inLanguage: 'de',
58
+ };
59
+
60
+ export const content: ToolLocaleContent<ExcavationCalculatorUI> = {
61
+ slug,
62
+ title,
63
+ description,
64
+ faqTitle: 'Häufig gestellte Fragen',
65
+ faq,
66
+ bibliographyTitle: 'Quellen und technische Hinweise',
67
+ bibliography: excavationVolumeCalculatorBibliography,
68
+ howTo,
69
+ schemas: [faqSchema, howToSchema, appSchema],
70
+ seo: [
71
+ { type: 'title', text: 'Zwei Volumina beantworten zwei Fragen auf der Baustelle', level: 2 },
72
+ { type: 'paragraph', html: 'Das <strong>Festmaß</strong> beschreibt die Geometrie des ungestörten Bodens: Länge mal Breite mal Tiefe. Das <strong>Lockermaß</strong> ist der Raum, den das gelockerte Material nach dem Ausheben einnimmt. Die Unterscheidung verhindert, dass Container- oder Transportberechnungen auf falschen Bodenzuständen basieren.' },
73
+ { type: 'title', text: 'Lockermaß-Spanne für die Logistikplanung nutzen', level: 3 },
74
+ { type: 'paragraph', html: 'Wählen Sie das passende Bodenprofil und vergleichen Sie die unteren und oberen Lockermaß-Ergebnisse mit Lkw-Ladekapazitäten, Containergrößen, Haufwerksflächen und Zwischenlagern. Die Spanne ist bewusst variabel dargestellt, da Aushebemethode, Feuchte und Dichte das Ergebnis beeinflussen.' },
75
+ { type: 'tip', title: 'Ein Auflockerungswert ersetzt keine Bodenanalyse', html: 'Tonmineralogie, Grundwasser und Vorbelastung können das Abweichungsverhalten verändern. Für Fundamente, Stützmauern, Altlasten und deponiepflichtiges Material ist eine geotechnische Fachprüfung erforderlich.' },
76
+ { type: 'title', text: 'Annahmen vor der Kapazitätsbestellung prüfen', level: 3 },
77
+ { type: 'paragraph', html: 'Wenn die Bodenart unsicher ist, berechnen Sie den Aushub mit zwei plausiblen Profilen und planen Sie mit dem größeren Logistikergebnis, bis genaue Messwerte vorliegen. Das bietet höhere Sicherheit als eine scheinbar exakte Einzelzahl.' },
78
+ ],
79
+ ui: {
80
+ unitSystemLabel: 'Messsystem',
81
+ unitMetric: 'Metrisch',
82
+ unitImperial: 'Imperial',
83
+ onboarding: 'Geben Sie die Abmessungen des Aushubs ein, wählen Sie das Bodenprofil und nutzen Sie die Ergebnisse für die Aushub- und Transportplanung.',
84
+ dimensionLabel: 'Aushubabmessungen',
85
+ lengthLabel: 'Länge',
86
+ widthLabel: 'Breite',
87
+ depthLabel: 'Tiefe',
88
+ soilLabel: 'Bodenprofil',
89
+ soilSand: 'Sand',
90
+ soilTopsoil: 'Mutterboden',
91
+ soilClay: 'Ton / Lehm',
92
+ soilGravel: 'Kies',
93
+ soilSandBand: '10 bis 15% Auflockerung',
94
+ soilTopsoilBand: '15 bis 25% Auflockerung',
95
+ soilClayBand: '25 bis 40% Auflockerung',
96
+ soilGravelBand: '15 bis 25% Auflockerung',
97
+ bankVolumeLabel: 'Festmaß',
98
+ bankVolumeHelp: 'Ungestörter Boden in der Baugrube',
99
+ looseVolumeLabel: 'Lockermaß',
100
+ looseVolumeHelp: 'Planungsbereich nach dem Ausheben',
101
+ expansionLabel: 'Angewendete Bodenauflockerung',
102
+ expansionHelp: 'Spanne für das gewählte Profil',
103
+ sceneTitle: 'Vom Baugrund zum Haufwerk',
104
+ sceneBank: 'im Boden',
105
+ sceneLoose: 'gelockertes Material',
106
+ sceneGround: 'Gelaendekante',
107
+ sceneCut: 'ausgehobene Baugrube',
108
+ interpretationTitle: 'Anwendung der Ergebnisse.',
109
+ interpretationText: 'Die erste Zahl beschreibt die Grube. Die Spanne beschreibt den zusätzlichen Raumbedarf des gelockerten Bodens.',
110
+ warning: 'Nur für Planungs- und Logistikzwecke. Feuchtigkeit, Dichte und Aushebemethode verändern das tatsächliche Lockermaß. Ersetzt keine geotechnische Baugrunduntersuchung.',
111
+ unitLengthMetric: 'm',
112
+ unitLengthImperial: 'ft',
113
+ unitVolumeMetric: 'm³',
114
+ unitVolumeImperial: 'ft³',
115
+ soilStatusLow: 'geringe Auflockerung',
116
+ soilStatusMedium: 'mittlere Auflockerung',
117
+ soilStatusHigh: 'hohe Auflockerung',
118
+ soilStatusLabel: 'Planungsband',
119
+ },
120
+ };