@jjlmoya/utils-home 1.28.0 → 1.30.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/entries.ts +7 -1
  3. package/src/index.ts +2 -0
  4. package/src/tests/locale_completeness.test.ts +2 -2
  5. package/src/tests/tool_validation.test.ts +2 -2
  6. package/src/tool/applianceCostCalculator/appliance-cost-calculator.css +635 -0
  7. package/src/tool/applianceCostCalculator/bibliography.astro +14 -0
  8. package/src/tool/applianceCostCalculator/bibliography.ts +14 -0
  9. package/src/tool/applianceCostCalculator/component.astro +322 -0
  10. package/src/tool/applianceCostCalculator/entry.ts +29 -0
  11. package/src/tool/applianceCostCalculator/i18n/de.ts +229 -0
  12. package/src/tool/applianceCostCalculator/i18n/en.ts +229 -0
  13. package/src/tool/applianceCostCalculator/i18n/es.ts +229 -0
  14. package/src/tool/applianceCostCalculator/i18n/fr.ts +229 -0
  15. package/src/tool/applianceCostCalculator/i18n/id.ts +229 -0
  16. package/src/tool/applianceCostCalculator/i18n/it.ts +229 -0
  17. package/src/tool/applianceCostCalculator/i18n/ja.ts +229 -0
  18. package/src/tool/applianceCostCalculator/i18n/ko.ts +229 -0
  19. package/src/tool/applianceCostCalculator/i18n/nl.ts +229 -0
  20. package/src/tool/applianceCostCalculator/i18n/pl.ts +229 -0
  21. package/src/tool/applianceCostCalculator/i18n/pt.ts +229 -0
  22. package/src/tool/applianceCostCalculator/i18n/ru.ts +229 -0
  23. package/src/tool/applianceCostCalculator/i18n/sv.ts +229 -0
  24. package/src/tool/applianceCostCalculator/i18n/tr.ts +229 -0
  25. package/src/tool/applianceCostCalculator/i18n/zh.ts +229 -0
  26. package/src/tool/applianceCostCalculator/index.ts +9 -0
  27. package/src/tool/applianceCostCalculator/logic.ts +122 -0
  28. package/src/tool/applianceCostCalculator/seo.astro +15 -0
  29. package/src/tool/applianceCostCalculator/ui.ts +41 -0
  30. package/src/tool/deskErgonomics/bibliography.astro +14 -0
  31. package/src/tool/deskErgonomics/bibliography.ts +5 -0
  32. package/src/tool/deskErgonomics/component.astro +192 -0
  33. package/src/tool/deskErgonomics/desk-ergonomics.css +361 -0
  34. package/src/tool/deskErgonomics/entry.ts +29 -0
  35. package/src/tool/deskErgonomics/i18n/de.ts +237 -0
  36. package/src/tool/deskErgonomics/i18n/en.ts +215 -0
  37. package/src/tool/deskErgonomics/i18n/es.ts +237 -0
  38. package/src/tool/deskErgonomics/i18n/fr.ts +237 -0
  39. package/src/tool/deskErgonomics/i18n/id.ts +215 -0
  40. package/src/tool/deskErgonomics/i18n/it.ts +237 -0
  41. package/src/tool/deskErgonomics/i18n/ja.ts +215 -0
  42. package/src/tool/deskErgonomics/i18n/ko.ts +215 -0
  43. package/src/tool/deskErgonomics/i18n/nl.ts +215 -0
  44. package/src/tool/deskErgonomics/i18n/pl.ts +215 -0
  45. package/src/tool/deskErgonomics/i18n/pt.ts +237 -0
  46. package/src/tool/deskErgonomics/i18n/ru.ts +215 -0
  47. package/src/tool/deskErgonomics/i18n/sv.ts +215 -0
  48. package/src/tool/deskErgonomics/i18n/tr.ts +215 -0
  49. package/src/tool/deskErgonomics/i18n/zh.ts +215 -0
  50. package/src/tool/deskErgonomics/index.ts +9 -0
  51. package/src/tool/deskErgonomics/logic.ts +49 -0
  52. package/src/tool/deskErgonomics/seo.astro +15 -0
  53. package/src/tool/deskErgonomics/ui.ts +28 -0
  54. package/src/tool/vampireDrawSimulator/bibliography.ts +3 -3
  55. package/src/tools.ts +4 -0
@@ -0,0 +1,192 @@
1
+ ---
2
+ import type { DeskErgonomicsUI } from './ui';
3
+ import { calculateErgonomics, fmt0 } from './logic';
4
+
5
+ interface Props {
6
+ ui?: Record<string, unknown>;
7
+ }
8
+
9
+ const { ui = {} } = Astro.props;
10
+ const eUI = ui as DeskErgonomicsUI;
11
+
12
+ const defaultHeight = 175;
13
+ const initial = calculateErgonomics(defaultHeight, 'male', 'sit');
14
+
15
+ const maxVal = 140;
16
+ const chairPct = Math.min((initial.chairHeight / maxVal) * 100, 100);
17
+ const deskPct = Math.min((initial.deskHeight / maxVal) * 100, 100);
18
+ const monPct = Math.min((initial.monitorHeight / maxVal) * 100, 100);
19
+ ---
20
+
21
+ <div class="ergo-wrapper">
22
+ <div class="ergo-card" id="ergo-card">
23
+ <div class="ergo-header">
24
+ <div class="ergo-mode-toggle" id="ergo-mode-toggle">
25
+ <button class="ergo-mode-btn ergo-mode-active" data-mode="sit">{eUI.modeSit}</button>
26
+ <button class="ergo-mode-btn" data-mode="stand">{eUI.modeStand}</button>
27
+ </div>
28
+ <div class="ergo-field">
29
+ <div class="ergo-field-top">
30
+ <span class="ergo-label">{eUI.labelHeight}</span>
31
+ <span class="ergo-val" id="ergo-height-val">{defaultHeight} {eUI.unitCm}</span>
32
+ </div>
33
+ <input type="range" class="ergo-slider" id="ergo-height" min="140" max="210" step="1" value={defaultHeight} />
34
+ </div>
35
+ <div class="ergo-gender-row" id="ergo-gender-row">
36
+ <button class="ergo-gender-btn ergo-gender-active" data-gender="male">{eUI.genderMale}</button>
37
+ <button class="ergo-gender-btn" data-gender="female">{eUI.genderFemale}</button>
38
+ </div>
39
+ </div>
40
+
41
+ <div class="ergo-viz" id="ergo-viz">
42
+ <div class="ergo-ruler" id="ergo-ruler" />
43
+
44
+ <div class="ergo-arc" id="ergo-arc">
45
+ <span class="ergo-arc-label" id="arc-label">{initial.monitorDistance} {eUI.unitCm}</span>
46
+ </div>
47
+
48
+ <div class="ergo-tower">
49
+ <div class="ergo-tower-icon">
50
+ <svg viewBox="0 0 24 24"><path d="M5 11l7-7 7 7M12 4v16"/></svg>
51
+ </div>
52
+ <div class="ergo-tower-track" id="tower-chair">
53
+ <div class="ergo-tower-zone" id="zone-chair" />
54
+ <div class="ergo-tower-fill" id="fill-chair" style={`height:${chairPct}%`} />
55
+ <div class="ergo-tower-pip" id="pip-chair" style={`bottom:${chairPct}%`} />
56
+ </div>
57
+ <span class="ergo-tower-label">{eUI.labelChair}</span>
58
+ <span class="ergo-tower-val" id="val-chair">{fmt0(initial.chairHeight)}</span>
59
+ <span class="ergo-tower-unit">{eUI.unitCm}</span>
60
+ </div>
61
+
62
+ <div class="ergo-tower">
63
+ <div class="ergo-tower-icon">
64
+ <svg viewBox="0 0 24 24"><rect x="2" y="7" width="20" height="10" rx="2"/><path d="M6 17v4M18 17v4"/></svg>
65
+ </div>
66
+ <div class="ergo-tower-track" id="tower-desk">
67
+ <div class="ergo-tower-zone" id="zone-desk" />
68
+ <div class="ergo-tower-fill" id="fill-desk" style={`height:${deskPct}%`} />
69
+ <div class="ergo-tower-pip" id="pip-desk" style={`bottom:${deskPct}%`} />
70
+ </div>
71
+ <span class="ergo-tower-label">{eUI.labelDesk}</span>
72
+ <span class="ergo-tower-val" id="val-desk">{fmt0(initial.deskHeight)}</span>
73
+ <span class="ergo-tower-unit">{eUI.unitCm}</span>
74
+ </div>
75
+
76
+ <div class="ergo-tower">
77
+ <div class="ergo-tower-icon">
78
+ <svg viewBox="0 0 24 24"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg>
79
+ </div>
80
+ <div class="ergo-tower-track" id="tower-monitor">
81
+ <div class="ergo-tower-zone" id="zone-monitor" />
82
+ <div class="ergo-tower-fill" id="fill-monitor" style={`height:${monPct}%`} />
83
+ <div class="ergo-tower-pip" id="pip-monitor" style={`bottom:${monPct}%`} />
84
+ </div>
85
+ <span class="ergo-tower-label">{eUI.labelMonitor}</span>
86
+ <span class="ergo-tower-val" id="val-monitor">{fmt0(initial.monitorHeight)}</span>
87
+ <span class="ergo-tower-unit">{eUI.unitCm}</span>
88
+ </div>
89
+ </div>
90
+
91
+ <div class="ergo-footer">
92
+ <div class="ergo-stat">
93
+ <p class="ergo-stat-label">{eUI.labelChair}</p>
94
+ <p class="ergo-stat-value" id="stat-chair">{fmt0(initial.chairHeight)} {eUI.unitCm}</p>
95
+ </div>
96
+ <div class="ergo-stat">
97
+ <p class="ergo-stat-label">{eUI.labelDesk}</p>
98
+ <p class="ergo-stat-value" id="stat-desk">{fmt0(initial.deskHeight)} {eUI.unitCm}</p>
99
+ </div>
100
+ <div class="ergo-stat">
101
+ <p class="ergo-stat-label">{eUI.labelMonitor}</p>
102
+ <p class="ergo-stat-value" id="stat-monitor">{fmt0(initial.monitorHeight)} {eUI.unitCm}</p>
103
+ </div>
104
+ </div>
105
+ </div>
106
+ </div>
107
+
108
+ <script>
109
+ import { calculateErgonomics, fmt0 } from './logic';
110
+
111
+ function el(id) { return document.getElementById(id); }
112
+ function setTxt(id, val) { const e = el(id); if (e) e.textContent = val; }
113
+ function setStyle(id, styles) { const e = el(id); if (!e) return; Object.entries(styles).forEach(([k, v]) => { e.style[k] = v; }); }
114
+ function toggleClass(sel, attr, val, cls) { document.querySelectorAll(sel).forEach((b) => b.classList.toggle(cls, (b as HTMLElement).dataset[attr] === val)); }
115
+
116
+ let height = 175;
117
+ let gender = 'male';
118
+ let mode = 'sit';
119
+
120
+ const MAX_V = 140;
121
+
122
+ function pct(v) { return Math.min((v / MAX_V) * 100, 100); }
123
+
124
+ function updateTower(p) {
125
+ setStyle(`fill-${p.id}`, { height: `${pct(p.val)}%` });
126
+ setStyle(`pip-${p.id}`, { bottom: `${pct(p.val)}%` });
127
+ setTxt(`val-${p.id}`, fmt0(p.val));
128
+ }
129
+
130
+ function updateZone(p) {
131
+ const zone = el(`zone-${p.id}`);
132
+ if (!zone) return;
133
+ const topPct = pct(p.optMax);
134
+ const botPct = pct(p.optMin);
135
+ zone.style.top = `${100 - topPct}%`;
136
+ zone.style.height = `${topPct - botPct}%`;
137
+ }
138
+
139
+ function calculate() {
140
+ const r = calculateErgonomics(height, gender, mode);
141
+ setTxt('stat-chair', `${fmt0(r.chairHeight)} cm`);
142
+ setTxt('stat-desk', `${fmt0(r.deskHeight)} cm`);
143
+ setTxt('stat-monitor', `${fmt0(r.monitorHeight)} cm`);
144
+ setTxt('arc-label', `${fmt0(r.monitorDistance)} cm`);
145
+
146
+ updateTower({ id: 'chair', val: r.chairHeight });
147
+ updateTower({ id: 'desk', val: r.deskHeight });
148
+ updateTower({ id: 'monitor', val: r.monitorHeight });
149
+
150
+ updateZone({ id: 'chair', optMin: r.chairHeight - 3, optMax: r.chairHeight + 3 });
151
+ updateZone({ id: 'desk', optMin: r.deskHeight - 3, optMax: r.deskHeight + 3 });
152
+ updateZone({ id: 'monitor', optMin: r.monitorHeight - 5, optMax: r.monitorHeight + 5 });
153
+ }
154
+
155
+ function bindSlider() {
156
+ const slider = el('ergo-height');
157
+ const val = el('ergo-height-val');
158
+ if (slider) {
159
+ slider.addEventListener('input', () => {
160
+ height = parseInt(slider.value, 10);
161
+ if (val) val.textContent = `${height} cm`;
162
+ calculate();
163
+ });
164
+ }
165
+ }
166
+
167
+ function bindToggles() {
168
+ document.querySelectorAll('#ergo-mode-toggle .ergo-mode-btn').forEach((btn) => {
169
+ btn.addEventListener('click', () => {
170
+ mode = (btn as HTMLElement).dataset.mode;
171
+ toggleClass('#ergo-mode-toggle .ergo-mode-btn', 'mode', mode, 'ergo-mode-active');
172
+ calculate();
173
+ });
174
+ });
175
+ document.querySelectorAll('#ergo-gender-row .ergo-gender-btn').forEach((btn) => {
176
+ btn.addEventListener('click', () => {
177
+ gender = (btn as HTMLElement).dataset.gender;
178
+ toggleClass('#ergo-gender-row .ergo-gender-btn', 'gender', gender, 'ergo-gender-active');
179
+ calculate();
180
+ });
181
+ });
182
+ }
183
+
184
+ function init() {
185
+ bindSlider();
186
+ bindToggles();
187
+ calculate();
188
+ }
189
+
190
+ document.addEventListener('astro:page-load', init);
191
+ init();
192
+ </script>
@@ -0,0 +1,361 @@
1
+ .ergo-wrapper {
2
+ --e-accent: #0ea5e9;
3
+ --e-ok: #22c55e;
4
+ --e-warn: #f59e0b;
5
+ --e-danger: #ef4444;
6
+ --e-surface: var(--bg-surface);
7
+ --e-border: var(--border-color);
8
+ --e-text: var(--text-main);
9
+ --e-muted: var(--text-muted);
10
+ --e-page: var(--bg-page);
11
+
12
+ width: 100%;
13
+ padding: 1rem 0;
14
+ }
15
+
16
+ .ergo-card {
17
+ width: 100%;
18
+ max-width: 640px;
19
+ margin: 0 auto;
20
+ background: var(--e-surface);
21
+ border: 1px solid var(--e-border);
22
+ border-radius: 24px;
23
+ overflow: hidden;
24
+ display: flex;
25
+ flex-direction: column;
26
+ }
27
+
28
+ /* ---- HEADER ---- */
29
+ .ergo-header {
30
+ padding: 1.25rem;
31
+ border-bottom: 1px solid var(--e-border);
32
+ display: flex;
33
+ flex-direction: column;
34
+ gap: 0.875rem;
35
+ }
36
+
37
+ .ergo-mode-toggle {
38
+ display: flex;
39
+ background: var(--e-page);
40
+ padding: 4px;
41
+ border-radius: 14px;
42
+ gap: 2px;
43
+ border: 1px solid var(--e-border);
44
+ }
45
+
46
+ .ergo-mode-btn {
47
+ flex: 1;
48
+ padding: 0.55rem 0.5rem;
49
+ border: none;
50
+ background: transparent;
51
+ border-radius: 10px;
52
+ font-size: 0.75rem;
53
+ font-weight: 800;
54
+ text-transform: uppercase;
55
+ letter-spacing: 0.1em;
56
+ color: var(--e-muted);
57
+ cursor: pointer;
58
+ transition: all 0.25s;
59
+ }
60
+
61
+ .ergo-mode-active {
62
+ background: var(--e-accent);
63
+ color: #fff;
64
+ box-shadow: 0 4px 14px rgba(14, 165, 233, 0.3);
65
+ }
66
+
67
+ .ergo-field {
68
+ display: flex;
69
+ flex-direction: column;
70
+ gap: 0.5rem;
71
+ }
72
+
73
+ .ergo-field-top {
74
+ display: flex;
75
+ justify-content: space-between;
76
+ align-items: center;
77
+ }
78
+
79
+ .ergo-label {
80
+ font-size: 0.625rem;
81
+ font-weight: 900;
82
+ text-transform: uppercase;
83
+ letter-spacing: 0.16em;
84
+ color: var(--e-muted);
85
+ }
86
+
87
+ .ergo-val {
88
+ font-size: 0.875rem;
89
+ font-weight: 800;
90
+ color: var(--e-text);
91
+ }
92
+
93
+ .ergo-slider {
94
+ width: 100%;
95
+ height: 6px;
96
+ border-radius: 3px;
97
+ background: var(--e-page);
98
+ outline: none;
99
+ border: none;
100
+ -webkit-appearance: none;
101
+ appearance: none;
102
+ }
103
+
104
+ .ergo-slider::-webkit-slider-thumb {
105
+ -webkit-appearance: none;
106
+ appearance: none;
107
+ width: 20px;
108
+ height: 20px;
109
+ border-radius: 50%;
110
+ background: var(--e-accent);
111
+ cursor: pointer;
112
+ border: 3px solid var(--e-surface);
113
+ box-shadow: 0 2px 8px rgba(14, 165, 233, 0.3);
114
+ }
115
+
116
+ .ergo-slider::-moz-range-thumb {
117
+ width: 20px;
118
+ height: 20px;
119
+ border-radius: 50%;
120
+ background: var(--e-accent);
121
+ cursor: pointer;
122
+ border: 3px solid var(--e-surface);
123
+ box-shadow: 0 2px 8px rgba(14, 165, 233, 0.3);
124
+ }
125
+
126
+ .ergo-gender-row {
127
+ display: flex;
128
+ gap: 0.5rem;
129
+ }
130
+
131
+ .ergo-gender-btn {
132
+ flex: 1;
133
+ padding: 0.5rem;
134
+ border-radius: 10px;
135
+ border: 1px solid var(--e-border);
136
+ background: var(--e-page);
137
+ color: var(--e-muted);
138
+ font-size: 0.8rem;
139
+ font-weight: 700;
140
+ cursor: pointer;
141
+ transition: all 0.2s;
142
+ }
143
+
144
+ .ergo-gender-active {
145
+ background: var(--e-accent);
146
+ border-color: var(--e-accent);
147
+ color: #fff;
148
+ }
149
+
150
+ /* ---- VISUALIZER ---- */
151
+ .ergo-viz {
152
+ position: relative;
153
+ flex: 1;
154
+ min-height: 340px;
155
+ background: var(--e-page);
156
+ display: flex;
157
+ align-items: flex-end;
158
+ justify-content: center;
159
+ padding: 2rem 2rem 2.5rem;
160
+ gap: 1.5rem;
161
+ overflow: hidden;
162
+ }
163
+
164
+ .ergo-tower {
165
+ position: relative;
166
+ width: 72px;
167
+ display: flex;
168
+ flex-direction: column;
169
+ align-items: center;
170
+ gap: 0.5rem;
171
+ }
172
+
173
+ .ergo-tower-track {
174
+ position: relative;
175
+ width: 100%;
176
+ height: 220px;
177
+ background: var(--e-surface);
178
+ border-radius: 16px;
179
+ border: 1px solid var(--e-border);
180
+ overflow: hidden;
181
+ }
182
+
183
+ .ergo-tower-zone {
184
+ position: absolute;
185
+ left: 0;
186
+ right: 0;
187
+ background: linear-gradient(180deg, rgba(34,197,94,0.08), rgba(34,197,94,0.03));
188
+ border-top: 1px dashed rgba(34,197,94,0.25);
189
+ border-bottom: 1px dashed rgba(34,197,94,0.25);
190
+ }
191
+
192
+ .ergo-tower-fill {
193
+ position: absolute;
194
+ left: 0;
195
+ right: 0;
196
+ bottom: 0;
197
+ background: linear-gradient(180deg, rgba(14,165,233,0.25), rgba(14,165,233,0.08));
198
+ border-radius: 0 0 14px 14px;
199
+ transition: height 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
200
+ }
201
+
202
+ .ergo-tower-pip {
203
+ position: absolute;
204
+ left: 0;
205
+ right: 0;
206
+ height: 2px;
207
+ background: var(--e-accent);
208
+ box-shadow: 0 0 8px rgba(14, 165, 233, 0.4);
209
+ transition: bottom 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
210
+ }
211
+
212
+ .ergo-tower-pip::after {
213
+ content: '';
214
+ position: absolute;
215
+ right: 4px;
216
+ top: 50%;
217
+ transform: translateY(-50%);
218
+ width: 6px;
219
+ height: 6px;
220
+ border-radius: 50%;
221
+ background: var(--e-accent);
222
+ }
223
+
224
+ .ergo-tower-icon {
225
+ width: 32px;
226
+ height: 32px;
227
+ border-radius: 10px;
228
+ background: var(--e-surface);
229
+ border: 1px solid var(--e-border);
230
+ display: flex;
231
+ align-items: center;
232
+ justify-content: center;
233
+ color: var(--e-muted);
234
+ }
235
+
236
+ .ergo-tower-icon svg {
237
+ width: 18px;
238
+ height: 18px;
239
+ stroke: currentcolor;
240
+ stroke-width: 2;
241
+ fill: none;
242
+ stroke-linecap: round;
243
+ stroke-linejoin: round;
244
+ }
245
+
246
+ .ergo-tower-label {
247
+ font-size: 0.6rem;
248
+ font-weight: 800;
249
+ text-transform: uppercase;
250
+ letter-spacing: 0.12em;
251
+ color: var(--e-muted);
252
+ }
253
+
254
+ .ergo-tower-val {
255
+ font-size: 1.25rem;
256
+ font-weight: 900;
257
+ color: var(--e-text);
258
+ line-height: 1;
259
+ }
260
+
261
+ .ergo-tower-unit {
262
+ font-size: 0.7rem;
263
+ font-weight: 600;
264
+ color: var(--e-muted);
265
+ }
266
+
267
+ /* Ruler lines */
268
+ .ergo-ruler {
269
+ position: absolute;
270
+ left: 0.75rem;
271
+ top: 2rem;
272
+ bottom: 3rem;
273
+ width: 1px;
274
+ background: linear-gradient(to bottom, transparent 0%, var(--e-border) 10%, var(--e-border) 90%, transparent 100%);
275
+ }
276
+
277
+ .ergo-ruler-mark {
278
+ position: absolute;
279
+ left: 2px;
280
+ width: 6px;
281
+ height: 1px;
282
+ background: var(--e-border);
283
+ }
284
+
285
+ /* Distance arc */
286
+ .ergo-arc {
287
+ position: absolute;
288
+ left: 50%;
289
+ top: 2rem;
290
+ transform: translateX(-50%);
291
+ width: 120px;
292
+ height: 60px;
293
+ border: 1px dashed rgba(14, 165, 233, 0.2);
294
+ border-bottom: none;
295
+ border-radius: 60px 60px 0 0;
296
+ pointer-events: none;
297
+ }
298
+
299
+ .ergo-arc-label {
300
+ position: absolute;
301
+ left: 50%;
302
+ top: 0.5rem;
303
+ transform: translateX(-50%);
304
+ font-size: 0.6rem;
305
+ font-weight: 800;
306
+ color: var(--e-accent);
307
+ letter-spacing: 0.05em;
308
+ white-space: nowrap;
309
+ }
310
+
311
+ /* ---- FOOTER ---- */
312
+ .ergo-footer {
313
+ display: grid;
314
+ grid-template-columns: repeat(3, 1fr);
315
+ gap: 0.5rem;
316
+ padding: 1rem 1.25rem;
317
+ border-top: 1px solid var(--e-border);
318
+ }
319
+
320
+ .ergo-stat {
321
+ text-align: center;
322
+ padding: 0.625rem 0.375rem;
323
+ border-radius: 14px;
324
+ background: var(--e-page);
325
+ border: 1px solid var(--e-border);
326
+ transition: transform 0.2s, border-color 0.2s;
327
+ }
328
+
329
+ .ergo-stat:hover {
330
+ transform: translateY(-2px);
331
+ border-color: rgba(14, 165, 233, 0.25);
332
+ }
333
+
334
+ .ergo-stat-label {
335
+ font-size: 0.55rem;
336
+ font-weight: 800;
337
+ text-transform: uppercase;
338
+ letter-spacing: 0.13em;
339
+ color: var(--e-muted);
340
+ margin: 0 0 0.25rem;
341
+ }
342
+
343
+ .ergo-stat-value {
344
+ font-size: 0.95rem;
345
+ font-weight: 800;
346
+ color: var(--e-text);
347
+ margin: 0;
348
+ }
349
+
350
+ /* ---- RESPONSIVE ---- */
351
+ @media (max-width: 520px) {
352
+ .ergo-wrapper { padding: 0.75rem 0; }
353
+ .ergo-card { border-radius: 20px; }
354
+ .ergo-header { padding: 1rem; }
355
+ .ergo-viz { padding: 1.5rem 1rem 2rem; gap: 1rem; min-height: 300px;
356
+ }
357
+ .ergo-tower { width: 60px; }
358
+ .ergo-tower-track { height: 180px; }
359
+ .ergo-footer { grid-template-columns: 1fr; padding: 0.75rem 1rem; gap: 0.4rem;
360
+ }
361
+ }
@@ -0,0 +1,29 @@
1
+ import type { HomeToolEntry, ToolLocaleContent } from '../../types';
2
+ import type { DeskErgonomicsUI } from './ui';
3
+
4
+ export type DeskErgonomicsLocaleContent = ToolLocaleContent<DeskErgonomicsUI>;
5
+
6
+ export const deskErgonomics: HomeToolEntry<DeskErgonomicsUI> = {
7
+ id: 'desk-ergonomics',
8
+ icons: {
9
+ bg: 'mdi:desk',
10
+ fg: 'mdi:human',
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
+ };