@jjlmoya/utils-diy 1.1.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 (135) hide show
  1. package/package.json +60 -0
  2. package/src/category/i18n/en.ts +110 -0
  3. package/src/category/i18n/es.ts +110 -0
  4. package/src/category/i18n/fr.ts +101 -0
  5. package/src/category/index.ts +25 -0
  6. package/src/category/seo.astro +15 -0
  7. package/src/components/PreviewNavSidebar.astro +116 -0
  8. package/src/components/PreviewToolbar.astro +143 -0
  9. package/src/data.ts +11 -0
  10. package/src/env.d.ts +5 -0
  11. package/src/index.ts +57 -0
  12. package/src/layouts/PreviewLayout.astro +117 -0
  13. package/src/pages/[locale]/[slug].astro +146 -0
  14. package/src/pages/[locale].astro +251 -0
  15. package/src/pages/index.astro +4 -0
  16. package/src/tests/faq_count.test.ts +19 -0
  17. package/src/tests/locale_completeness.test.ts +42 -0
  18. package/src/tests/mocks/astro_mock.js +2 -0
  19. package/src/tests/no_h1_in_components.test.ts +48 -0
  20. package/src/tests/schemas_fulfillment.test.ts +23 -0
  21. package/src/tests/seo_length.test.ts +22 -0
  22. package/src/tests/title_quality.test.ts +55 -0
  23. package/src/tests/tool_validation.test.ts +17 -0
  24. package/src/tool/balusterCalculator/bibliography.astro +14 -0
  25. package/src/tool/balusterCalculator/component.astro +605 -0
  26. package/src/tool/balusterCalculator/i18n/en.ts +264 -0
  27. package/src/tool/balusterCalculator/i18n/es.ts +264 -0
  28. package/src/tool/balusterCalculator/i18n/fr.ts +264 -0
  29. package/src/tool/balusterCalculator/index.ts +33 -0
  30. package/src/tool/balusterCalculator/seo.astro +15 -0
  31. package/src/tool/balusterCalculator/ui.ts +15 -0
  32. package/src/tool/clayCalculator/bibliography.astro +14 -0
  33. package/src/tool/clayCalculator/component.astro +731 -0
  34. package/src/tool/clayCalculator/i18n/en.ts +183 -0
  35. package/src/tool/clayCalculator/i18n/es.ts +183 -0
  36. package/src/tool/clayCalculator/i18n/fr.ts +183 -0
  37. package/src/tool/clayCalculator/index.ts +33 -0
  38. package/src/tool/clayCalculator/seo.astro +15 -0
  39. package/src/tool/clayCalculator/ui.ts +19 -0
  40. package/src/tool/concreteCalculator/bibliography.astro +13 -0
  41. package/src/tool/concreteCalculator/component.astro +1089 -0
  42. package/src/tool/concreteCalculator/i18n/en.ts +201 -0
  43. package/src/tool/concreteCalculator/i18n/es.ts +201 -0
  44. package/src/tool/concreteCalculator/i18n/fr.ts +201 -0
  45. package/src/tool/concreteCalculator/index.ts +28 -0
  46. package/src/tool/concreteCalculator/seo.astro +14 -0
  47. package/src/tool/concreteCalculator/ui.ts +35 -0
  48. package/src/tool/cutOptimizer/bibliography.astro +13 -0
  49. package/src/tool/cutOptimizer/component.astro +825 -0
  50. package/src/tool/cutOptimizer/i18n/en.ts +178 -0
  51. package/src/tool/cutOptimizer/i18n/es.ts +178 -0
  52. package/src/tool/cutOptimizer/i18n/fr.ts +178 -0
  53. package/src/tool/cutOptimizer/index.ts +28 -0
  54. package/src/tool/cutOptimizer/optimizer.ts +199 -0
  55. package/src/tool/cutOptimizer/seo.astro +14 -0
  56. package/src/tool/cutOptimizer/ui.ts +25 -0
  57. package/src/tool/drillCalculator/bibliography.astro +13 -0
  58. package/src/tool/drillCalculator/component.astro +1210 -0
  59. package/src/tool/drillCalculator/engine.ts +63 -0
  60. package/src/tool/drillCalculator/i18n/en.ts +137 -0
  61. package/src/tool/drillCalculator/i18n/es.ts +137 -0
  62. package/src/tool/drillCalculator/i18n/fr.ts +137 -0
  63. package/src/tool/drillCalculator/index.ts +28 -0
  64. package/src/tool/drillCalculator/seo.astro +14 -0
  65. package/src/tool/drillCalculator/ui.ts +41 -0
  66. package/src/tool/drillSharpener/bibliography.astro +13 -0
  67. package/src/tool/drillSharpener/component.astro +712 -0
  68. package/src/tool/drillSharpener/engine.ts +42 -0
  69. package/src/tool/drillSharpener/i18n/en.ts +121 -0
  70. package/src/tool/drillSharpener/i18n/es.ts +121 -0
  71. package/src/tool/drillSharpener/i18n/fr.ts +121 -0
  72. package/src/tool/drillSharpener/index.ts +31 -0
  73. package/src/tool/drillSharpener/seo.astro +14 -0
  74. package/src/tool/drillSharpener/ui.ts +53 -0
  75. package/src/tool/epoxyCalculator/bibliography.astro +14 -0
  76. package/src/tool/epoxyCalculator/component.astro +888 -0
  77. package/src/tool/epoxyCalculator/i18n/en.ts +195 -0
  78. package/src/tool/epoxyCalculator/i18n/es.ts +195 -0
  79. package/src/tool/epoxyCalculator/i18n/fr.ts +195 -0
  80. package/src/tool/epoxyCalculator/index.ts +33 -0
  81. package/src/tool/epoxyCalculator/seo.astro +15 -0
  82. package/src/tool/epoxyCalculator/ui.ts +22 -0
  83. package/src/tool/furnitureFit/bibliography.astro +13 -0
  84. package/src/tool/furnitureFit/component.astro +552 -0
  85. package/src/tool/furnitureFit/engine.ts +51 -0
  86. package/src/tool/furnitureFit/i18n/en.ts +152 -0
  87. package/src/tool/furnitureFit/i18n/es.ts +152 -0
  88. package/src/tool/furnitureFit/i18n/fr.ts +152 -0
  89. package/src/tool/furnitureFit/index.ts +28 -0
  90. package/src/tool/furnitureFit/seo.astro +14 -0
  91. package/src/tool/furnitureFit/ui.ts +21 -0
  92. package/src/tool/mortarCalculator/bibliography.astro +13 -0
  93. package/src/tool/mortarCalculator/component.astro +1139 -0
  94. package/src/tool/mortarCalculator/i18n/en.ts +219 -0
  95. package/src/tool/mortarCalculator/i18n/es.ts +205 -0
  96. package/src/tool/mortarCalculator/i18n/fr.ts +220 -0
  97. package/src/tool/mortarCalculator/index.ts +24 -0
  98. package/src/tool/mortarCalculator/seo.astro +14 -0
  99. package/src/tool/mortarCalculator/ui.ts +37 -0
  100. package/src/tool/passepartoutCalculator/bibliography.astro +13 -0
  101. package/src/tool/passepartoutCalculator/component.astro +741 -0
  102. package/src/tool/passepartoutCalculator/i18n/en.ts +178 -0
  103. package/src/tool/passepartoutCalculator/i18n/es.ts +178 -0
  104. package/src/tool/passepartoutCalculator/i18n/fr.ts +178 -0
  105. package/src/tool/passepartoutCalculator/index.ts +28 -0
  106. package/src/tool/passepartoutCalculator/seo.astro +14 -0
  107. package/src/tool/passepartoutCalculator/ui.ts +17 -0
  108. package/src/tool/stairCalculator/bibliography.astro +52 -0
  109. package/src/tool/stairCalculator/component.astro +766 -0
  110. package/src/tool/stairCalculator/engine.ts +128 -0
  111. package/src/tool/stairCalculator/i18n/en.ts +149 -0
  112. package/src/tool/stairCalculator/i18n/es.ts +149 -0
  113. package/src/tool/stairCalculator/i18n/fr.ts +149 -0
  114. package/src/tool/stairCalculator/index.ts +31 -0
  115. package/src/tool/stairCalculator/seo.astro +211 -0
  116. package/src/tool/stairCalculator/ui.ts +109 -0
  117. package/src/tool/thermalExpansionCalculator/bibliography.astro +13 -0
  118. package/src/tool/thermalExpansionCalculator/component.astro +771 -0
  119. package/src/tool/thermalExpansionCalculator/engine.ts +23 -0
  120. package/src/tool/thermalExpansionCalculator/i18n/en.ts +174 -0
  121. package/src/tool/thermalExpansionCalculator/i18n/es.ts +174 -0
  122. package/src/tool/thermalExpansionCalculator/i18n/fr.ts +174 -0
  123. package/src/tool/thermalExpansionCalculator/index.ts +28 -0
  124. package/src/tool/thermalExpansionCalculator/seo.astro +14 -0
  125. package/src/tool/thermalExpansionCalculator/ui.ts +41 -0
  126. package/src/tool/voltageDropCalculator/bibliography.astro +13 -0
  127. package/src/tool/voltageDropCalculator/component.astro +1022 -0
  128. package/src/tool/voltageDropCalculator/i18n/en.ts +155 -0
  129. package/src/tool/voltageDropCalculator/i18n/es.ts +155 -0
  130. package/src/tool/voltageDropCalculator/i18n/fr.ts +148 -0
  131. package/src/tool/voltageDropCalculator/index.ts +28 -0
  132. package/src/tool/voltageDropCalculator/seo.astro +14 -0
  133. package/src/tool/voltageDropCalculator/ui.ts +22 -0
  134. package/src/tools.ts +16 -0
  135. package/src/types.ts +72 -0
@@ -0,0 +1,1022 @@
1
+ ---
2
+ import type { VoltageDropCalculatorUI } from './ui';
3
+
4
+ interface Props {
5
+ ui?: Record<string, unknown>;
6
+ }
7
+ const { ui } = Astro.props;
8
+ const t = (ui ?? {}) as VoltageDropCalculatorUI;
9
+
10
+ const SECTIONS = [
11
+ { val: 0.5, label: '0.5', fill: 6 },
12
+ { val: 0.75, label: '0.75', fill: 8 },
13
+ { val: 1.0, label: '1.0', fill: 10 },
14
+ { val: 1.5, label: '1.5', fill: 12 },
15
+ { val: 2.5, label: '2.5', fill: 16 },
16
+ { val: 4.0, label: '4.0', fill: 20 },
17
+ { val: 6.0, label: '6.0', fill: 24 },
18
+ { val: 10.0, label: '10', fill: 28 },
19
+ { val: 16.0, label: '16', fill: 32 },
20
+ { val: 25.0, label: '25', fill: 36 },
21
+ { val: 35.0, label: '35', fill: 40 },
22
+ { val: 50.0, label: '50', fill: 44 },
23
+ ];
24
+ ---
25
+
26
+ <div
27
+ class="vd-root"
28
+ data-vd-root
29
+ data-vd-s-excellent={t.statusExcellent}
30
+ data-vd-s-acceptable={t.statusAcceptable}
31
+ data-vd-s-critical={t.statusCritical}
32
+ data-vd-unit-watts={t.unitWatts}
33
+ data-vd-unit-amps={t.unitAmps}
34
+ >
35
+ <div class="vd-hero">
36
+ <div class="vd-pulse" data-vd-pulse></div>
37
+ <div class="vd-hero-body">
38
+ <div class="vd-hero-main">
39
+ <div class="vd-drop-label">{t.heroDropLabel}</div>
40
+ <div class="vd-drop-row">
41
+ <span class="vd-drop-val" data-vd-drop-v>0.00</span>
42
+ <span class="vd-drop-unit">{t.heroVoltsUnit}</span>
43
+ </div>
44
+ <div class="vd-status-row">
45
+ <div class="vd-status-pill" data-vd-status-pill data-vd-status="excellent">
46
+ <span class="vd-status-dot"></span>
47
+ <span data-vd-status-text>{t.statusExcellent}</span>
48
+ </div>
49
+ <div class="vd-pct-wrap">
50
+ <span data-vd-drop-p>0.00</span>{t.heroPctLoss}
51
+ </div>
52
+ </div>
53
+ </div>
54
+ <div class="vd-eff-card">
55
+ <div class="vd-eff-label">{t.heroEffLabel}</div>
56
+ <div class="vd-eff-val-row">
57
+ <span class="vd-eff-val" data-vd-eff>100.0</span>
58
+ <span class="vd-eff-pct">%</span>
59
+ </div>
60
+ <div class="vd-eff-bar-wrap">
61
+ <div class="vd-eff-bar-fill" data-vd-bar-eff style="width:100%"></div>
62
+ <div class="vd-eff-bar-loss"></div>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </div>
67
+
68
+ <div class="vd-body">
69
+ <div class="vd-row-2">
70
+ <div class="vd-card">
71
+ <label class="vd-field-label">{t.fieldVoltage}</label>
72
+ <div class="vd-toggle-wrap">
73
+ <div class="vd-volt-bg" data-vd-volt-bg></div>
74
+ <button class="vd-volt-btn vd-active" data-vd-volt-btn="12">12V</button>
75
+ <button class="vd-volt-btn" data-vd-volt-btn="24">24V</button>
76
+ <button class="vd-volt-btn" data-vd-volt-btn="230">230V</button>
77
+ </div>
78
+ </div>
79
+ <div class="vd-card">
80
+ <label class="vd-field-label">{t.fieldMaterial}</label>
81
+ <div class="vd-material-wrap">
82
+ <label class="vd-material-item vd-material-copper">
83
+ <input type="radio" name="vd-material" value="copper" class="vd-material-input" checked />
84
+ <div class="vd-material-box">
85
+ <span class="vd-wire-dot vd-wire-dot-copper"></span>
86
+ <span>{t.materialCopper}</span>
87
+ </div>
88
+ </label>
89
+ <label class="vd-material-item vd-material-alum">
90
+ <input type="radio" name="vd-material" value="aluminum" class="vd-material-input" />
91
+ <div class="vd-material-box">
92
+ <span class="vd-wire-dot vd-wire-dot-alum"></span>
93
+ <span>{t.materialAlum}</span>
94
+ </div>
95
+ </label>
96
+ </div>
97
+ </div>
98
+ </div>
99
+
100
+ <div class="vd-card vd-load-card">
101
+ <div class="vd-load-top">
102
+ <label class="vd-field-label">{t.fieldLoad}</label>
103
+ <div class="vd-mode-wrap">
104
+ <button class="vd-mode-btn vd-active" data-vd-mode-btn="watts">{t.modeW}</button>
105
+ <button class="vd-mode-btn" data-vd-mode-btn="amps">{t.modeA}</button>
106
+ </div>
107
+ </div>
108
+ <div class="vd-load-input-row">
109
+ <input type="number" data-vd-load-val value="60" class="vd-load-input" />
110
+ <span class="vd-load-unit" data-vd-load-unit>{t.unitWatts}</span>
111
+ </div>
112
+ </div>
113
+
114
+ <div class="vd-card">
115
+ <div class="vd-len-top">
116
+ <label class="vd-field-label">{t.fieldLength}</label>
117
+ <span class="vd-len-info"><span data-vd-len-display>10</span> {t.unitMeters}</span>
118
+ </div>
119
+ <div class="vd-slider-wrap">
120
+ <input type="range" data-vd-len-slider min="1" max="100" step="1" value="10" class="vd-slider-input" />
121
+ <div class="vd-slider-track">
122
+ <div class="vd-slider-fill" data-vd-len-track style="width:9.09%"></div>
123
+ </div>
124
+ <div class="vd-slider-thumb" data-vd-len-thumb style="left:9.09%">
125
+ <div class="vd-slider-knob"></div>
126
+ </div>
127
+ </div>
128
+ </div>
129
+
130
+ <div class="vd-card">
131
+ <div class="vd-section-top">
132
+ <label class="vd-field-label">{t.fieldSection}</label>
133
+ <span class="vd-section-badge">{t.sectionInfo}</span>
134
+ </div>
135
+ <div class="vd-gauge-outer">
136
+ <div class="vd-gauge-fade-l"></div>
137
+ <div class="vd-gauge-fade-r"></div>
138
+ <div class="vd-gauge-scroll" data-vd-gauge-scroll>
139
+ {SECTIONS.map((s) => (
140
+ <label class="vd-gauge-item">
141
+ <input
142
+ type="radio"
143
+ name="vd-section"
144
+ value={s.val}
145
+ class="vd-gauge-input"
146
+ checked={s.val === 1.5}
147
+ />
148
+ <div class="vd-gauge-box">
149
+ <div class="vd-wire-circle" style={`width:${s.fill}px;height:${s.fill}px`}></div>
150
+ <span class="vd-gauge-val">{s.label}</span>
151
+ <span class="vd-gauge-unit">mm²</span>
152
+ </div>
153
+ </label>
154
+ ))}
155
+ </div>
156
+ </div>
157
+ </div>
158
+ </div>
159
+ </div>
160
+
161
+ <script>
162
+ interface VdState {
163
+ voltage: number;
164
+ load: number;
165
+ isWatts: boolean;
166
+ length: number;
167
+ section: number;
168
+ material: string;
169
+ }
170
+
171
+ function vdCalc(root: Element, state: VdState): void {
172
+ const current = state.isWatts && state.voltage > 0 ? state.load / state.voltage : state.load;
173
+ const rho = state.material === 'aluminum' ? 0.028 : 0.0178;
174
+ const dropV = (2 * state.length * current * rho) / state.section;
175
+ const dropP = state.voltage > 0 ? (dropV / state.voltage) * 100 : 0;
176
+ vdUpdateHero(root, dropV, dropP);
177
+ vdUpdateStatus(root, dropP);
178
+ }
179
+
180
+ function vdUpdateHero(root: Element, dropV: number, dropP: number): void {
181
+ const ev = root.querySelector('[data-vd-drop-v]');
182
+ const ep = root.querySelector('[data-vd-drop-p]');
183
+ const eff = root.querySelector('[data-vd-eff]');
184
+ const bar = root.querySelector('[data-vd-bar-eff]') as HTMLElement | null;
185
+ if (ev) ev.textContent = dropV.toFixed(2);
186
+ if (ep) ep.textContent = dropP.toFixed(2);
187
+ const kept = Math.max(0, 100 - dropP);
188
+ if (eff) eff.textContent = kept.toFixed(1);
189
+ if (bar) bar.style.width = kept + '%';
190
+ }
191
+
192
+ function vdStatusFor(root: Element, dropP: number): [string, string] {
193
+ if (dropP < 3) return ['excellent', root.getAttribute('data-vd-s-excellent') ?? ''];
194
+ if (dropP < 5) return ['acceptable', root.getAttribute('data-vd-s-acceptable') ?? ''];
195
+ return ['critical', root.getAttribute('data-vd-s-critical') ?? ''];
196
+ }
197
+
198
+ function vdUpdateStatus(root: Element, dropP: number): void {
199
+ const [status, label] = vdStatusFor(root, dropP);
200
+ const pill = root.querySelector('[data-vd-status-pill]');
201
+ const txt = root.querySelector('[data-vd-status-text]');
202
+ const pulse = root.querySelector('[data-vd-pulse]');
203
+ if (pill) pill.setAttribute('data-vd-status', status);
204
+ if (pulse) pulse.setAttribute('data-vd-pulse-status', status);
205
+ if (txt) txt.textContent = label;
206
+ }
207
+
208
+ function vdAttachVoltage(root: Element, state: VdState): void {
209
+ const btns = Array.from(root.querySelectorAll('[data-vd-volt-btn]'));
210
+ const bg = root.querySelector('[data-vd-volt-bg]') as HTMLElement | null;
211
+ btns.forEach((btn, idx) => {
212
+ btn.addEventListener('click', () => {
213
+ state.voltage = parseFloat((btn as HTMLElement).dataset.vdVoltBtn ?? '0');
214
+ btns.forEach((b, i) => b.classList.toggle('vd-active', i === idx));
215
+ if (bg) bg.style.left = `${idx * 33.3 + 1}%`;
216
+ vdCalc(root, state);
217
+ });
218
+ });
219
+ }
220
+
221
+ function vdAttachLoad(root: Element, state: VdState): void {
222
+ const inp = root.querySelector('[data-vd-load-val]');
223
+ const unit = root.querySelector('[data-vd-load-unit]');
224
+ if (inp) inp.addEventListener('input', () => {
225
+ state.load = parseFloat((inp as HTMLInputElement).value) || 0;
226
+ vdCalc(root, state);
227
+ });
228
+ root.querySelectorAll('[data-vd-mode-btn]').forEach((btn) => {
229
+ btn.addEventListener('click', () => {
230
+ const mode = (btn as HTMLElement).dataset.vdModeBtn;
231
+ state.isWatts = mode === 'watts';
232
+ root.querySelectorAll('[data-vd-mode-btn]').forEach((b) => {
233
+ b.classList.toggle('vd-active', (b as HTMLElement).dataset.vdModeBtn === mode);
234
+ });
235
+ const key = state.isWatts ? 'data-vd-unit-watts' : 'data-vd-unit-amps';
236
+ if (unit) unit.textContent = root.getAttribute(key) ?? '';
237
+ vdCalc(root, state);
238
+ });
239
+ });
240
+ }
241
+
242
+ function vdAttachSlider(root: Element, state: VdState): void {
243
+ const slider = root.querySelector('[data-vd-len-slider]') as HTMLInputElement | null;
244
+ if (!slider) return;
245
+ slider.addEventListener('input', () => {
246
+ state.length = parseFloat(slider.value);
247
+ const pct = ((state.length - 1) / 99) * 100;
248
+ const track = root.querySelector('[data-vd-len-track]') as HTMLElement | null;
249
+ const thumb = root.querySelector('[data-vd-len-thumb]') as HTMLElement | null;
250
+ const display = root.querySelector('[data-vd-len-display]');
251
+ if (track) track.style.width = pct + '%';
252
+ if (thumb) thumb.style.left = pct + '%';
253
+ if (display) display.textContent = state.length.toString();
254
+ vdCalc(root, state);
255
+ });
256
+ }
257
+
258
+ function vdAttachSection(root: Element, state: VdState): void {
259
+ const box = root.querySelector('[data-vd-gauge-scroll]');
260
+ if (!box) return;
261
+ box.addEventListener('change', (e) => {
262
+ const tgt = e.target as HTMLInputElement;
263
+ if (tgt.name === 'vd-section') {
264
+ state.section = parseFloat(tgt.value);
265
+ vdCalc(root, state);
266
+ }
267
+ });
268
+ }
269
+
270
+ function vdAttachMaterial(root: Element, state: VdState): void {
271
+ root.querySelectorAll('input[name="vd-material"]').forEach((r) => {
272
+ r.addEventListener('change', (e) => {
273
+ state.material = (e.target as HTMLInputElement).value;
274
+ root.setAttribute('data-vd-material', state.material);
275
+ vdCalc(root, state);
276
+ });
277
+ });
278
+ }
279
+
280
+ function vdInit(root: Element): void {
281
+ const state: VdState = { voltage: 12, load: 60, isWatts: true, length: 10, section: 1.5, material: 'copper' };
282
+ root.setAttribute('data-vd-material', 'copper');
283
+ vdAttachVoltage(root, state);
284
+ vdAttachLoad(root, state);
285
+ vdAttachSlider(root, state);
286
+ vdAttachSection(root, state);
287
+ vdAttachMaterial(root, state);
288
+ vdCalc(root, state);
289
+ }
290
+
291
+ document.querySelectorAll('[data-vd-root]').forEach(vdInit);
292
+ </script>
293
+
294
+ <style>
295
+ .vd-root {
296
+ max-width: 700px;
297
+ margin: 0 auto;
298
+ border-radius: 1.5rem;
299
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
300
+ border: 1px solid #e2e8f0;
301
+ overflow: hidden;
302
+ }
303
+
304
+ :global(.theme-dark) .vd-root {
305
+ border-color: #1e293b;
306
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
307
+ }
308
+
309
+ .vd-hero {
310
+ background: #0f172a;
311
+ color: #fff;
312
+ padding: 2rem;
313
+ position: relative;
314
+ overflow: hidden;
315
+ }
316
+
317
+ .vd-pulse {
318
+ position: absolute;
319
+ top: -125px;
320
+ right: -125px;
321
+ width: 350px;
322
+ height: 350px;
323
+ background: radial-gradient(circle, rgba(99, 102, 241, 0.25), transparent 70%);
324
+ border-radius: 50%;
325
+ pointer-events: none;
326
+ transition: background 0.7s ease;
327
+ }
328
+
329
+ .vd-pulse[data-vd-pulse-status="acceptable"] {
330
+ background: radial-gradient(circle, rgba(245, 158, 11, 0.2), transparent 70%);
331
+ }
332
+
333
+ .vd-pulse[data-vd-pulse-status="critical"] {
334
+ background: radial-gradient(circle, rgba(239, 68, 68, 0.25), transparent 70%);
335
+ }
336
+
337
+ .vd-hero-body {
338
+ position: relative;
339
+ z-index: 1;
340
+ display: flex;
341
+ justify-content: space-between;
342
+ align-items: flex-end;
343
+ gap: 1.5rem;
344
+ flex-wrap: wrap;
345
+ }
346
+
347
+ .vd-drop-label {
348
+ font-size: 0.65rem;
349
+ font-weight: 700;
350
+ letter-spacing: 0.1em;
351
+ text-transform: uppercase;
352
+ color: #94a3b8;
353
+ margin-bottom: 0.5rem;
354
+ }
355
+
356
+ .vd-drop-row {
357
+ display: flex;
358
+ align-items: baseline;
359
+ gap: 0.5rem;
360
+ }
361
+
362
+ .vd-drop-val {
363
+ font-size: 3.5rem;
364
+ font-weight: 900;
365
+ line-height: 1;
366
+ letter-spacing: -0.02em;
367
+ }
368
+
369
+ .vd-drop-unit {
370
+ font-size: 1.5rem;
371
+ font-weight: 500;
372
+ color: #64748b;
373
+ }
374
+
375
+ .vd-status-row {
376
+ display: flex;
377
+ align-items: center;
378
+ gap: 0.75rem;
379
+ margin-top: 1rem;
380
+ }
381
+
382
+ .vd-status-pill {
383
+ display: flex;
384
+ align-items: center;
385
+ gap: 0.375rem;
386
+ padding: 0.25rem 0.75rem;
387
+ border-radius: 999px;
388
+ font-size: 0.65rem;
389
+ font-weight: 700;
390
+ text-transform: uppercase;
391
+ letter-spacing: 0.05em;
392
+ background: rgba(34, 197, 94, 0.2);
393
+ border: 1px solid rgba(34, 197, 94, 0.5);
394
+ color: #4ade80;
395
+ transition: all 0.3s ease;
396
+ }
397
+
398
+ .vd-status-pill[data-vd-status="acceptable"] {
399
+ background: rgba(245, 158, 11, 0.2);
400
+ border-color: rgba(245, 158, 11, 0.5);
401
+ color: #fbbf24;
402
+ }
403
+
404
+ .vd-status-pill[data-vd-status="critical"] {
405
+ background: rgba(239, 68, 68, 0.2);
406
+ border-color: rgba(239, 68, 68, 0.5);
407
+ color: #f87171;
408
+ }
409
+
410
+ .vd-status-dot {
411
+ width: 6px;
412
+ height: 6px;
413
+ border-radius: 50%;
414
+ background: currentcolor;
415
+ }
416
+
417
+ .vd-status-pill[data-vd-status="critical"] .vd-status-dot {
418
+ animation: vd-blink 1s ease-in-out infinite;
419
+ }
420
+
421
+ @keyframes vd-blink {
422
+ 0% {
423
+ opacity: 1;
424
+ }
425
+ 50% {
426
+ opacity: 0.2;
427
+ }
428
+ 100% {
429
+ opacity: 1;
430
+ }
431
+ }
432
+
433
+ .vd-pct-wrap {
434
+ font-size: 0.875rem;
435
+ font-weight: 500;
436
+ color: #94a3b8;
437
+ }
438
+
439
+ .vd-eff-card {
440
+ background: rgba(255, 255, 255, 0.05);
441
+ border: 1px solid rgba(255, 255, 255, 0.1);
442
+ border-radius: 1rem;
443
+ padding: 1rem;
444
+ min-width: 150px;
445
+ }
446
+
447
+ .vd-eff-label {
448
+ font-size: 0.65rem;
449
+ font-weight: 700;
450
+ letter-spacing: 0.1em;
451
+ text-transform: uppercase;
452
+ color: #94a3b8;
453
+ margin-bottom: 0.5rem;
454
+ }
455
+
456
+ .vd-eff-val-row {
457
+ display: flex;
458
+ align-items: baseline;
459
+ gap: 0.25rem;
460
+ margin-bottom: 0.5rem;
461
+ }
462
+
463
+ .vd-eff-val {
464
+ font-size: 1.875rem;
465
+ font-weight: 700;
466
+ }
467
+
468
+ .vd-eff-pct {
469
+ font-size: 0.875rem;
470
+ color: #94a3b8;
471
+ }
472
+
473
+ .vd-eff-bar-wrap {
474
+ display: flex;
475
+ height: 8px;
476
+ background: rgba(51, 65, 85, 0.5);
477
+ border-radius: 4px;
478
+ overflow: hidden;
479
+ }
480
+
481
+ .vd-eff-bar-fill {
482
+ height: 100%;
483
+ background: #4ade80;
484
+ transition: width 0.5s ease;
485
+ }
486
+
487
+ .vd-eff-bar-loss {
488
+ height: 100%;
489
+ flex: 1;
490
+ background: rgba(239, 68, 68, 0.5);
491
+ }
492
+
493
+ .vd-body {
494
+ padding: 1.5rem 2rem;
495
+ background: #f8fafc;
496
+ display: flex;
497
+ flex-direction: column;
498
+ gap: 1.5rem;
499
+ }
500
+
501
+ :global(.theme-dark) .vd-body {
502
+ background: rgba(15, 23, 42, 0.5);
503
+ }
504
+
505
+ .vd-row-2 {
506
+ display: grid;
507
+ grid-template-columns: 1fr 1fr;
508
+ gap: 1.5rem;
509
+ }
510
+
511
+ .vd-card {
512
+ background: #fff;
513
+ border: 1px solid #e2e8f0;
514
+ border-radius: 1rem;
515
+ padding: 1.25rem;
516
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
517
+ }
518
+
519
+ :global(.theme-dark) .vd-card {
520
+ background: #1e293b;
521
+ border-color: #334155;
522
+ }
523
+
524
+ .vd-field-label {
525
+ display: block;
526
+ font-size: 0.65rem;
527
+ font-weight: 700;
528
+ text-transform: uppercase;
529
+ letter-spacing: 0.05em;
530
+ color: #94a3b8;
531
+ margin-bottom: 0.75rem;
532
+ }
533
+
534
+ .vd-toggle-wrap {
535
+ position: relative;
536
+ display: flex;
537
+ background: #f1f5f9;
538
+ padding: 4px;
539
+ border-radius: 0.75rem;
540
+ }
541
+
542
+ :global(.theme-dark) .vd-toggle-wrap {
543
+ background: rgba(15, 23, 42, 0.8);
544
+ }
545
+
546
+ .vd-volt-bg {
547
+ position: absolute;
548
+ top: 4px;
549
+ left: 1%;
550
+ width: 32.9%;
551
+ height: calc(100% - 8px);
552
+ background: #fff;
553
+ border-radius: 0.5rem;
554
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
555
+ transition: left 0.3s ease;
556
+ pointer-events: none;
557
+ }
558
+
559
+ :global(.theme-dark) .vd-volt-bg {
560
+ background: #334155;
561
+ }
562
+
563
+ .vd-volt-btn {
564
+ flex: 1;
565
+ position: relative;
566
+ z-index: 1;
567
+ padding: 0.375rem 0;
568
+ font-size: 0.8125rem;
569
+ font-weight: 700;
570
+ border: none;
571
+ background: transparent;
572
+ color: #6b7280;
573
+ cursor: pointer;
574
+ border-radius: 0.5rem;
575
+ transition: color 0.15s;
576
+ }
577
+
578
+ .vd-volt-btn.vd-active {
579
+ color: #1e293b;
580
+ }
581
+
582
+ :global(.theme-dark) .vd-volt-btn {
583
+ color: #94a3b8;
584
+ }
585
+
586
+ :global(.theme-dark) .vd-volt-btn.vd-active {
587
+ color: #fff;
588
+ }
589
+
590
+ .vd-material-wrap {
591
+ display: flex;
592
+ gap: 1rem;
593
+ }
594
+
595
+ .vd-material-item {
596
+ flex: 1;
597
+ cursor: pointer;
598
+ }
599
+
600
+ .vd-material-input {
601
+ position: absolute;
602
+ width: 1px;
603
+ height: 1px;
604
+ padding: 0;
605
+ margin: -1px;
606
+ overflow: hidden;
607
+ clip-path: inset(50%);
608
+ white-space: nowrap;
609
+ border-width: 0;
610
+ }
611
+
612
+ .vd-material-box {
613
+ height: 2.5rem;
614
+ display: flex;
615
+ align-items: center;
616
+ justify-content: center;
617
+ gap: 0.5rem;
618
+ border-radius: 0.75rem;
619
+ border: 1px solid #e2e8f0;
620
+ background: #f8fafc;
621
+ font-size: 0.875rem;
622
+ font-weight: 700;
623
+ color: #64748b;
624
+ transition: all 0.15s ease;
625
+ }
626
+
627
+ :global(.theme-dark) .vd-material-box {
628
+ background: #0f172a;
629
+ border-color: #334155;
630
+ color: #94a3b8;
631
+ }
632
+
633
+ .vd-material-copper .vd-material-input:checked + .vd-material-box {
634
+ border-color: #d97706;
635
+ background: #fffbeb;
636
+ color: #92400e;
637
+ }
638
+
639
+ .vd-material-alum .vd-material-input:checked + .vd-material-box {
640
+ border-color: #64748b;
641
+ background: #f1f5f9;
642
+ color: #1e293b;
643
+ }
644
+
645
+ :global(.theme-dark) .vd-material-copper .vd-material-input:checked + .vd-material-box {
646
+ background: rgba(180, 83, 9, 0.2);
647
+ border-color: #d97706;
648
+ color: #fbbf24;
649
+ }
650
+
651
+ :global(.theme-dark) .vd-material-alum .vd-material-input:checked + .vd-material-box {
652
+ background: rgba(51, 65, 85, 0.8);
653
+ border-color: #64748b;
654
+ color: #fff;
655
+ }
656
+
657
+ .vd-wire-dot {
658
+ width: 0.75rem;
659
+ height: 0.75rem;
660
+ border-radius: 50%;
661
+ }
662
+
663
+ .vd-wire-dot-copper {
664
+ background: linear-gradient(to bottom right, #fbbf24, #d97706);
665
+ }
666
+
667
+ .vd-wire-dot-alum {
668
+ background: linear-gradient(to bottom right, #cbd5e1, #64748b);
669
+ }
670
+
671
+ .vd-load-top {
672
+ display: flex;
673
+ align-items: center;
674
+ justify-content: space-between;
675
+ margin-bottom: 1rem;
676
+ }
677
+
678
+ .vd-load-top .vd-field-label {
679
+ margin-bottom: 0;
680
+ }
681
+
682
+ .vd-mode-wrap {
683
+ display: flex;
684
+ background: #f1f5f9;
685
+ padding: 4px;
686
+ border-radius: 0.5rem;
687
+ }
688
+
689
+ :global(.theme-dark) .vd-mode-wrap {
690
+ background: #0f172a;
691
+ }
692
+
693
+ .vd-mode-btn {
694
+ padding: 0.25rem 0.75rem;
695
+ font-size: 0.75rem;
696
+ font-weight: 700;
697
+ border: none;
698
+ background: transparent;
699
+ color: #6b7280;
700
+ cursor: pointer;
701
+ border-radius: 0.375rem;
702
+ transition: all 0.15s;
703
+ }
704
+
705
+ .vd-mode-btn.vd-active {
706
+ background: #fff;
707
+ color: #1e293b;
708
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
709
+ }
710
+
711
+ :global(.theme-dark) .vd-mode-btn.vd-active {
712
+ background: #334155;
713
+ color: #fff;
714
+ }
715
+
716
+ .vd-load-input-row {
717
+ display: flex;
718
+ align-items: center;
719
+ gap: 1rem;
720
+ }
721
+
722
+ .vd-load-input {
723
+ width: 5rem;
724
+ background: transparent;
725
+ font-size: 1.5rem;
726
+ font-weight: 900;
727
+ text-align: right;
728
+ border: none;
729
+ border-bottom: 2px solid #e2e8f0;
730
+ color: #1e293b;
731
+ outline: none;
732
+ padding: 0;
733
+ transition: border-color 0.15s;
734
+ }
735
+
736
+ .vd-load-input:focus {
737
+ border-color: #6366f1;
738
+ }
739
+
740
+ :global(.theme-dark) .vd-load-input {
741
+ color: #fff;
742
+ border-color: #334155;
743
+ }
744
+
745
+ :global(.theme-dark) .vd-load-input:focus {
746
+ border-color: #6366f1;
747
+ }
748
+
749
+ .vd-load-unit {
750
+ font-size: 0.875rem;
751
+ font-weight: 700;
752
+ color: #94a3b8;
753
+ margin-top: 0.5rem;
754
+ }
755
+
756
+ .vd-len-top {
757
+ display: flex;
758
+ justify-content: space-between;
759
+ align-items: center;
760
+ margin-bottom: 1rem;
761
+ }
762
+
763
+ .vd-len-top .vd-field-label {
764
+ margin-bottom: 0;
765
+ }
766
+
767
+ .vd-len-info {
768
+ font-size: 0.875rem;
769
+ font-weight: 700;
770
+ color: #6366f1;
771
+ }
772
+
773
+ :global(.theme-dark) .vd-len-info {
774
+ color: #a5b4fc;
775
+ }
776
+
777
+ .vd-slider-wrap {
778
+ position: relative;
779
+ height: 24px;
780
+ display: flex;
781
+ align-items: center;
782
+ }
783
+
784
+ .vd-slider-input {
785
+ position: absolute;
786
+ width: 100%;
787
+ height: 100%;
788
+ z-index: 2;
789
+ opacity: 0;
790
+ cursor: pointer;
791
+ }
792
+
793
+ .vd-slider-track {
794
+ width: 100%;
795
+ height: 8px;
796
+ background: #f1f5f9;
797
+ border-radius: 4px;
798
+ overflow: hidden;
799
+ }
800
+
801
+ :global(.theme-dark) .vd-slider-track {
802
+ background: #334155;
803
+ }
804
+
805
+ .vd-slider-fill {
806
+ height: 100%;
807
+ background: #6366f1;
808
+ transition: width 0.075s;
809
+ }
810
+
811
+ .vd-slider-thumb {
812
+ position: absolute;
813
+ width: 24px;
814
+ height: 24px;
815
+ background: #fff;
816
+ border: 2px solid #6366f1;
817
+ border-radius: 50%;
818
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
819
+ z-index: 1;
820
+ pointer-events: none;
821
+ transition: left 0.075s;
822
+ transform: translateX(-50%);
823
+ display: flex;
824
+ align-items: center;
825
+ justify-content: center;
826
+ }
827
+
828
+ :global(.theme-dark) .vd-slider-thumb {
829
+ background: #1e293b;
830
+ }
831
+
832
+ .vd-slider-knob {
833
+ width: 6px;
834
+ height: 6px;
835
+ background: #6366f1;
836
+ border-radius: 50%;
837
+ }
838
+
839
+ .vd-section-top {
840
+ display: flex;
841
+ justify-content: space-between;
842
+ align-items: center;
843
+ margin-bottom: 1rem;
844
+ }
845
+
846
+ .vd-section-top .vd-field-label {
847
+ margin-bottom: 0;
848
+ }
849
+
850
+ .vd-section-badge {
851
+ font-size: 0.75rem;
852
+ font-weight: 500;
853
+ color: #64748b;
854
+ background: #f1f5f9;
855
+ padding: 0.25rem 0.5rem;
856
+ border-radius: 0.375rem;
857
+ }
858
+
859
+ :global(.theme-dark) .vd-section-badge {
860
+ background: #0f172a;
861
+ color: #94a3b8;
862
+ }
863
+
864
+ .vd-gauge-outer {
865
+ position: relative;
866
+ }
867
+
868
+ .vd-gauge-fade-l,
869
+ .vd-gauge-fade-r {
870
+ position: absolute;
871
+ top: 0;
872
+ bottom: 0;
873
+ width: 3rem;
874
+ z-index: 2;
875
+ pointer-events: none;
876
+ }
877
+
878
+ .vd-gauge-fade-l {
879
+ left: 0;
880
+ background: linear-gradient(to right, #fff, transparent);
881
+ }
882
+
883
+ .vd-gauge-fade-r {
884
+ right: 0;
885
+ background: linear-gradient(to left, #fff, transparent);
886
+ }
887
+
888
+ :global(.theme-dark) .vd-gauge-fade-l {
889
+ background: linear-gradient(to right, #1e293b, transparent);
890
+ }
891
+
892
+ :global(.theme-dark) .vd-gauge-fade-r {
893
+ background: linear-gradient(to left, #1e293b, transparent);
894
+ }
895
+
896
+ .vd-gauge-scroll {
897
+ display: flex;
898
+ gap: 1rem;
899
+ overflow-x: auto;
900
+ padding: 1rem 3rem;
901
+ scrollbar-width: none;
902
+ cursor: grab;
903
+ }
904
+
905
+ .vd-gauge-scroll:active {
906
+ cursor: grabbing;
907
+ }
908
+
909
+ .vd-gauge-scroll::-webkit-scrollbar {
910
+ display: none;
911
+ }
912
+
913
+ .vd-gauge-item {
914
+ flex-shrink: 0;
915
+ cursor: pointer;
916
+ }
917
+
918
+ .vd-gauge-input {
919
+ position: absolute;
920
+ width: 1px;
921
+ height: 1px;
922
+ overflow: hidden;
923
+ clip-path: inset(50%);
924
+ white-space: nowrap;
925
+ border-width: 0;
926
+ }
927
+
928
+ .vd-gauge-box {
929
+ width: 4rem;
930
+ height: 6rem;
931
+ border-radius: 999px;
932
+ background: #f8fafc;
933
+ border: 2px solid #e2e8f0;
934
+ display: flex;
935
+ flex-direction: column;
936
+ align-items: center;
937
+ justify-content: flex-end;
938
+ padding-bottom: 0.75rem;
939
+ transition: all 0.2s ease;
940
+ cursor: pointer;
941
+ }
942
+
943
+ :global(.theme-dark) .vd-gauge-box {
944
+ background: #0f172a;
945
+ border-color: #334155;
946
+ }
947
+
948
+ .vd-gauge-input:checked + .vd-gauge-box {
949
+ border-color: #6366f1;
950
+ background: #fff;
951
+ box-shadow: 0 4px 16px rgba(99, 102, 241, 0.2);
952
+ transform: translateY(-8px);
953
+ }
954
+
955
+ :global(.theme-dark) .vd-gauge-input:checked + .vd-gauge-box {
956
+ background: #1e293b;
957
+ border-color: #6366f1;
958
+ }
959
+
960
+ .vd-wire-circle {
961
+ margin-top: auto;
962
+ margin-bottom: 0.5rem;
963
+ border-radius: 50%;
964
+ background: linear-gradient(to bottom right, #fbbf24, #d97706);
965
+ transition: background 0.3s ease;
966
+ }
967
+
968
+ .vd-root[data-vd-material="aluminum"] .vd-wire-circle {
969
+ background: linear-gradient(to bottom right, #cbd5e1, #64748b);
970
+ }
971
+
972
+ .vd-gauge-val {
973
+ font-size: 0.875rem;
974
+ font-weight: 700;
975
+ color: #64748b;
976
+ }
977
+
978
+ .vd-gauge-input:checked + .vd-gauge-box .vd-gauge-val {
979
+ color: #6366f1;
980
+ }
981
+
982
+ :global(.theme-dark) .vd-gauge-val {
983
+ color: #94a3b8;
984
+ }
985
+
986
+ :global(.theme-dark) .vd-gauge-input:checked + .vd-gauge-box .vd-gauge-val {
987
+ color: #a5b4fc;
988
+ }
989
+
990
+ .vd-gauge-unit {
991
+ font-size: 0.625rem;
992
+ color: #94a3b8;
993
+ }
994
+
995
+ @media (max-width: 640px) {
996
+ .vd-hero {
997
+ padding: 1.5rem;
998
+ }
999
+
1000
+ .vd-hero-body {
1001
+ flex-direction: column;
1002
+ align-items: flex-start;
1003
+ }
1004
+
1005
+ .vd-eff-card {
1006
+ width: 100%;
1007
+ min-width: unset;
1008
+ }
1009
+
1010
+ .vd-body {
1011
+ padding: 1rem;
1012
+ }
1013
+
1014
+ .vd-row-2 {
1015
+ grid-template-columns: 1fr;
1016
+ }
1017
+
1018
+ .vd-drop-val {
1019
+ font-size: 2.5rem;
1020
+ }
1021
+ }
1022
+ </style>