@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,771 @@
1
+ ---
2
+ import type { ThermalExpansionCalculatorUI } from "./ui";
3
+ interface Props {
4
+ ui?: ThermalExpansionCalculatorUI;
5
+ }
6
+ const { ui } = Astro.props;
7
+ const t = (ui ?? {}) as ThermalExpansionCalculatorUI;
8
+ ---
9
+
10
+ <div
11
+ class="te-root"
12
+ data-te-root
13
+ data-te-risk-low={t.riskLow}
14
+ data-te-risk-moderate={t.riskModerate}
15
+ data-te-risk-critical={t.riskCritical}
16
+ data-te-tip-default={t.tipDefault}
17
+ >
18
+ <div class="te-grid">
19
+ <aside class="te-sidebar">
20
+ <p class="te-section-hdr">{t.configTitle}</p>
21
+
22
+ <div class="te-cat-badge">
23
+ <span class="te-cat-dot"></span>
24
+ <span class="te-cat-name" data-te-cat-label>{t.catMetals}</span>
25
+ </div>
26
+
27
+ <div class="te-field">
28
+ <label for="te-sel-mat" class="te-label">{t.labelMaterial}</label>
29
+ <select
30
+ id="te-sel-mat"
31
+ class="te-select"
32
+ data-te-material
33
+ data-te-input
34
+ >
35
+ <optgroup label={t.groupMetals} data-cat-label={t.catMetals}>
36
+ <option value="0.000012" data-tip={t.tipSteel}>{t.matSteel}</option>
37
+ <option value="0.000023" data-tip={t.tipAluminum}
38
+ >{t.matAluminum}</option
39
+ >
40
+ <option value="0.000017">{t.matCopper}</option>
41
+ <option value="0.000011">{t.matIron}</option>
42
+ </optgroup>
43
+ <optgroup
44
+ label={t.groupConstruction}
45
+ data-cat-label={t.catConstruction}
46
+ >
47
+ <option value="0.000012" data-tip={t.tipConcrete}
48
+ >{t.matConcrete}</option
49
+ >
50
+ <option value="0.000009">{t.matBrick}</option>
51
+ <option value="0.000009" data-tip={t.tipGlass}>{t.matGlass}</option>
52
+ </optgroup>
53
+ <optgroup label={t.groupWood} data-cat-label={t.catWood}>
54
+ <option value="0.000005" data-tip={t.tipWood}
55
+ >{t.matWoodFiber}</option
56
+ >
57
+ <option value="0.000040" data-tip={t.tipWood}
58
+ >{t.matWoodTransversal}</option
59
+ >
60
+ <option value="0.000030" data-tip={t.tipPvc}>{t.matPvc}</option>
61
+ </optgroup>
62
+ </select>
63
+ </div>
64
+
65
+ <div class="te-field">
66
+ <label for="te-len" class="te-label">{t.labelLength}</label>
67
+ <div class="te-input-row">
68
+ <input
69
+ id="te-len"
70
+ type="number"
71
+ class="te-input"
72
+ value="5"
73
+ step="0.1"
74
+ min="0"
75
+ data-te-length
76
+ data-te-input
77
+ />
78
+ <span class="te-unit">m</span>
79
+ </div>
80
+ </div>
81
+
82
+ <div class="te-temp-grid">
83
+ <div class="te-field">
84
+ <label for="te-t1" class="te-label">{t.labelTempStart}</label>
85
+ <div class="te-input-row">
86
+ <input
87
+ id="te-t1"
88
+ type="number"
89
+ class="te-input"
90
+ value="20"
91
+ step="1"
92
+ data-te-t1
93
+ data-te-input
94
+ />
95
+ <span class="te-unit">°C</span>
96
+ </div>
97
+ </div>
98
+ <div class="te-field">
99
+ <label for="te-t2" class="te-label">{t.labelTempEnd}</label>
100
+ <div class="te-input-row">
101
+ <input
102
+ id="te-t2"
103
+ type="number"
104
+ class="te-input"
105
+ value="60"
106
+ step="1"
107
+ data-te-t2
108
+ data-te-input
109
+ />
110
+ <span class="te-unit">°C</span>
111
+ </div>
112
+ </div>
113
+ </div>
114
+
115
+ <div class="te-delta-bar">
116
+ <span class="te-delta-lbl">{t.deltaLabel}</span>
117
+ <span class="te-delta-val" data-te-delta-display>40 °C</span>
118
+ </div>
119
+ </aside>
120
+
121
+ <main class="te-main">
122
+ <p class="te-section-hdr">{t.vizTitle}</p>
123
+
124
+ <div class="te-viz">
125
+ <div class="te-beam-wrap">
126
+ <div class="te-beam-base"></div>
127
+ <div class="te-beam-expand" data-te-beam></div>
128
+ </div>
129
+ <div class="te-markers">
130
+ <span class="te-marker">0 m</span>
131
+ <span class="te-marker" data-te-length-marker>5 m</span>
132
+ </div>
133
+ <div class="te-ind-wrap">
134
+ <span class="te-ind" data-te-indicator>+0.00 mm</span>
135
+ </div>
136
+ </div>
137
+
138
+ <div class="te-results">
139
+ <div class="te-result-primary">
140
+ <span class="te-result-lbl">{t.resultGrowthLabel}</span>
141
+ <div class="te-result-val">
142
+ <span class="te-result-num" data-te-growth>2.40</span>
143
+ <span class="te-result-unit">{t.unitMm}</span>
144
+ </div>
145
+ </div>
146
+ <div class="te-result-secondary">
147
+ <span class="te-result-lbl">{t.resultFinalLabel}</span>
148
+ <div class="te-result-val">
149
+ <span class="te-result-num" data-te-final>5.002</span>
150
+ <span class="te-result-unit">{t.unitM}</span>
151
+ </div>
152
+ </div>
153
+ </div>
154
+
155
+ <div class="te-ref-grid">
156
+ <div class="te-ref-card" data-te-risk-card>
157
+ <span class="te-ref-title">{t.riskTitle}</span>
158
+ <span class="te-ref-val" data-te-risk-el>{t.riskLow}</span>
159
+ </div>
160
+ <div class="te-ref-card">
161
+ <span class="te-ref-title">{t.jointTitle}</span>
162
+ <span class="te-ref-val" data-te-joint>3 mm</span>
163
+ </div>
164
+ </div>
165
+
166
+ <div class="te-tip-card">
167
+ <span class="te-tip-bullet" aria-hidden="true">*</span>
168
+ <p class="te-tip-text" data-te-tip>{t.tipDefault}</p>
169
+ </div>
170
+ </main>
171
+ </div>
172
+ </div>
173
+
174
+ <script>
175
+ import { calculateExpansion, type TeResult } from "./engine";
176
+
177
+ interface TeState {
178
+ L0: number;
179
+ alpha: number;
180
+ T1: number;
181
+ T2: number;
182
+ }
183
+
184
+ function teNum(el: Element | null): number {
185
+ const val = (el as HTMLInputElement | null)?.value ?? "0";
186
+ return parseFloat(val) || 0;
187
+ }
188
+
189
+ function teAttr(root: Element, key: string): string {
190
+ return root.getAttribute(key) ?? "";
191
+ }
192
+
193
+ function teReadState(root: Element): TeState {
194
+ return {
195
+ L0: teNum(root.querySelector("[data-te-length]")),
196
+ alpha: teNum(root.querySelector("[data-te-material]")),
197
+ T1: teNum(root.querySelector("[data-te-t1]")),
198
+ T2: teNum(root.querySelector("[data-te-t2]")),
199
+ };
200
+ }
201
+
202
+ function teUpdateDelta(root: Element, state: TeState): void {
203
+ const el = root.querySelector("[data-te-delta-display]");
204
+ if (el) el.textContent = `${state.T2 - state.T1} \u00B0C`;
205
+ }
206
+
207
+ function teUpdateBeam(root: Element, deltaLmm: number): void {
208
+ const beam = root.querySelector("[data-te-beam]") as HTMLElement | null;
209
+ if (!beam) return;
210
+ const px = Math.min(40, (Math.abs(deltaLmm) / 10) * 10);
211
+ beam.style.width = `calc(100% + ${px}px)`;
212
+ }
213
+
214
+ function teUpdateResults(root: Element, result: TeResult): void {
215
+ const growth = root.querySelector("[data-te-growth]");
216
+ const final = root.querySelector("[data-te-final]");
217
+ const ind = root.querySelector("[data-te-indicator]");
218
+ if (growth) growth.textContent = result.deltaLmm.toFixed(2);
219
+ if (final) final.textContent = result.finalLength.toFixed(3);
220
+ if (ind) ind.textContent = `+${result.deltaLmm.toFixed(2)} mm`;
221
+ }
222
+
223
+ function teUpdateRisk(root: Element, result: TeResult): void {
224
+ const riskEl = root.querySelector("[data-te-risk-el]");
225
+ const card = root.querySelector("[data-te-risk-card]");
226
+ const joint = root.querySelector("[data-te-joint]");
227
+ if (riskEl)
228
+ riskEl.textContent = teAttr(root, `data-te-risk-${result.riskType}`);
229
+ if (card) card.setAttribute("data-risk", result.riskType);
230
+ if (joint) joint.textContent = `${result.minJoint} mm`;
231
+ }
232
+
233
+ function teUpdateTip(root: Element): void {
234
+ const select = root.querySelector(
235
+ "[data-te-material]",
236
+ ) as HTMLSelectElement | null;
237
+ const tipEl = root.querySelector("[data-te-tip]");
238
+ if (!select || !tipEl) return;
239
+ const opt = select.options[select.selectedIndex];
240
+ tipEl.textContent = opt?.dataset.tip ?? teAttr(root, "data-te-tip-default");
241
+ }
242
+
243
+ function teCatLabel(root: Element): void {
244
+ const select = root.querySelector(
245
+ "[data-te-material]",
246
+ ) as HTMLSelectElement | null;
247
+ const catEl = root.querySelector("[data-te-cat-label]");
248
+ if (!select || !catEl) return;
249
+ const og = select.options[select.selectedIndex]
250
+ ?.parentElement as HTMLOptGroupElement | null;
251
+ catEl.textContent = og?.dataset.catLabel ?? "";
252
+ }
253
+
254
+ function teUpdate(root: Element): void {
255
+ const state = teReadState(root);
256
+ const result = calculateExpansion(
257
+ state.L0,
258
+ state.alpha,
259
+ state.T1,
260
+ state.T2,
261
+ );
262
+ const marker = root.querySelector("[data-te-length-marker]");
263
+ if (marker) marker.textContent = `${state.L0} m`;
264
+ teUpdateDelta(root, state);
265
+ teUpdateBeam(root, result.deltaLmm);
266
+ teUpdateResults(root, result);
267
+ teUpdateRisk(root, result);
268
+ teUpdateTip(root);
269
+ teCatLabel(root);
270
+ }
271
+
272
+ function teAttach(root: Element): void {
273
+ root.querySelectorAll("[data-te-input]").forEach((el) => {
274
+ el.addEventListener("input", () => teUpdate(root));
275
+ });
276
+ teUpdate(root);
277
+ }
278
+
279
+ document.querySelectorAll("[data-te-root]").forEach(teAttach);
280
+ </script>
281
+
282
+ <style>
283
+ .te-root {
284
+ width: 100%;
285
+ max-width: 1100px;
286
+ margin: 0 auto;
287
+ }
288
+
289
+ .te-grid {
290
+ display: grid;
291
+ grid-template-columns: 340px 1fr;
292
+ border: 1px solid #e5e7eb;
293
+ border-radius: 24px;
294
+ overflow: hidden;
295
+ }
296
+
297
+ :global(.theme-dark) .te-grid {
298
+ border-color: rgba(255, 255, 255, 0.08);
299
+ }
300
+
301
+ .te-sidebar {
302
+ padding: 2rem;
303
+ background: rgba(255, 255, 255, 0.6);
304
+ border-right: 1px solid #e5e7eb;
305
+ display: flex;
306
+ flex-direction: column;
307
+ gap: 1.5rem;
308
+ }
309
+
310
+ :global(.theme-dark) .te-sidebar {
311
+ background: rgba(255, 255, 255, 0.02);
312
+ border-right-color: rgba(255, 255, 255, 0.06);
313
+ }
314
+
315
+ .te-main {
316
+ padding: 2rem;
317
+ display: flex;
318
+ flex-direction: column;
319
+ gap: 1.5rem;
320
+ background: rgba(255, 255, 255, 0.3);
321
+ }
322
+
323
+ :global(.theme-dark) .te-main {
324
+ background: rgba(255, 255, 255, 0.01);
325
+ }
326
+
327
+ .te-section-hdr {
328
+ font-size: 0.65rem;
329
+ font-weight: 800;
330
+ text-transform: uppercase;
331
+ letter-spacing: 0.1em;
332
+ color: #f97316;
333
+ margin: 0;
334
+ }
335
+
336
+ .te-cat-badge {
337
+ display: flex;
338
+ align-items: center;
339
+ gap: 0.75rem;
340
+ background: white;
341
+ border: 1px solid rgba(0, 0, 0, 0.05);
342
+ border-radius: 12px;
343
+ padding: 0.75rem 1rem;
344
+ }
345
+
346
+ :global(.theme-dark) .te-cat-badge {
347
+ background: rgba(255, 255, 255, 0.04);
348
+ border-color: rgba(255, 255, 255, 0.06);
349
+ }
350
+
351
+ .te-cat-dot {
352
+ width: 10px;
353
+ height: 10px;
354
+ border-radius: 50%;
355
+ background: #f97316;
356
+ flex-shrink: 0;
357
+ }
358
+
359
+ .te-cat-name {
360
+ font-size: 0.8rem;
361
+ font-weight: 800;
362
+ color: #374151;
363
+ }
364
+
365
+ :global(.theme-dark) .te-cat-name {
366
+ color: #e5e7eb;
367
+ }
368
+
369
+ .te-field {
370
+ display: flex;
371
+ flex-direction: column;
372
+ gap: 0.5rem;
373
+ }
374
+
375
+ .te-label {
376
+ font-size: 0.7rem;
377
+ font-weight: 700;
378
+ text-transform: uppercase;
379
+ letter-spacing: 0.05em;
380
+ color: #6b7280;
381
+ }
382
+
383
+ :global(.theme-dark) .te-label {
384
+ color: #9ca3af;
385
+ }
386
+
387
+ .te-input-row {
388
+ position: relative;
389
+ display: flex;
390
+ align-items: center;
391
+ }
392
+
393
+ .te-input {
394
+ width: 100%;
395
+ padding: 0.875rem 3rem 0.875rem 1rem;
396
+ border: 2px solid #e5e7eb;
397
+ border-radius: 12px;
398
+ font-size: 1.1rem;
399
+ font-weight: 800;
400
+ background: white;
401
+ color: #111827;
402
+ transition: border-color 0.2s;
403
+ }
404
+
405
+ .te-input:focus {
406
+ outline: none;
407
+ border-color: #f97316;
408
+ }
409
+
410
+ :global(.theme-dark) .te-input {
411
+ background: rgba(0, 0, 0, 0.25);
412
+ border-color: rgba(255, 255, 255, 0.1);
413
+ color: black;
414
+ }
415
+
416
+ :global(.theme-dark) .te-input:focus {
417
+ border-color: #f97316;
418
+ }
419
+
420
+ .te-unit {
421
+ position: absolute;
422
+ right: 1rem;
423
+ font-size: 0.7rem;
424
+ font-weight: 900;
425
+ color: #f97316;
426
+ pointer-events: none;
427
+ }
428
+
429
+ .te-select {
430
+ width: 100%;
431
+ padding: 0.875rem 2.5rem 0.875rem 1rem;
432
+ border: 2px solid #e5e7eb;
433
+ border-radius: 12px;
434
+ font-weight: 700;
435
+ font-size: 0.9rem;
436
+ background: white;
437
+ color: #111827;
438
+ appearance: none;
439
+ -webkit-appearance: none;
440
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24'%3E%3Cpath fill='%23f97316' d='M7.41 8.58L12 13.17l4.59-4.59L18 10l-6 6l-6-6l1.41-1.42Z'/%3E%3C/svg%3E");
441
+ background-repeat: no-repeat;
442
+ background-position: right 0.75rem center;
443
+ cursor: pointer;
444
+ transition: border-color 0.2s;
445
+ }
446
+
447
+ .te-select:focus {
448
+ outline: none;
449
+ border-color: #f97316;
450
+ }
451
+
452
+ :global(.theme-dark) .te-select {
453
+ background-color: rgba(0, 0, 0, 0.25);
454
+ border-color: rgba(255, 255, 255, 0.1);
455
+ color: #f9fafb;
456
+ }
457
+
458
+ .te-temp-grid {
459
+ display: grid;
460
+ grid-template-columns: 1fr 1fr;
461
+ gap: 0.75rem;
462
+ }
463
+
464
+ .te-delta-bar {
465
+ display: flex;
466
+ justify-content: space-between;
467
+ align-items: center;
468
+ background: rgba(249, 115, 22, 0.08);
469
+ border: 1px solid rgba(249, 115, 22, 0.2);
470
+ border-radius: 12px;
471
+ padding: 1rem 1.25rem;
472
+ margin-top: auto;
473
+ }
474
+
475
+ .te-delta-lbl {
476
+ font-size: 0.65rem;
477
+ font-weight: 800;
478
+ text-transform: uppercase;
479
+ letter-spacing: 0.08em;
480
+ color: #f97316;
481
+ }
482
+
483
+ .te-delta-val {
484
+ font-size: 1.1rem;
485
+ font-weight: 900;
486
+ color: #f97316;
487
+ }
488
+
489
+ .te-viz {
490
+ position: relative;
491
+ background: rgba(0, 0, 0, 0.02);
492
+ border: 1px dashed rgba(0, 0, 0, 0.1);
493
+ border-radius: 16px;
494
+ padding: 2.5rem 3rem 1.5rem;
495
+ min-height: 120px;
496
+ display: flex;
497
+ flex-direction: column;
498
+ gap: 0.5rem;
499
+ justify-content: center;
500
+ }
501
+
502
+ :global(.theme-dark) .te-viz {
503
+ background: rgba(255, 255, 255, 0.01);
504
+ border-color: rgba(255, 255, 255, 0.06);
505
+ }
506
+
507
+ .te-beam-wrap {
508
+ position: relative;
509
+ height: 32px;
510
+ border-radius: 4px;
511
+ overflow: visible;
512
+ }
513
+
514
+ .te-beam-base {
515
+ position: absolute;
516
+ inset: 0;
517
+ background: linear-gradient(to bottom, #d1d5db, #9ca3af);
518
+ border-radius: 4px;
519
+ z-index: 2;
520
+ }
521
+
522
+ :global(.theme-dark) .te-beam-base {
523
+ background: linear-gradient(to bottom, #4b5563, #374151);
524
+ }
525
+
526
+ .te-beam-expand {
527
+ position: absolute;
528
+ left: 0;
529
+ top: 0;
530
+ height: 100%;
531
+ width: 100%;
532
+ background: rgba(249, 115, 22, 0.25);
533
+ border-right: 3px solid #f97316;
534
+ border-radius: 4px;
535
+ transition: width 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
536
+ z-index: 1;
537
+ }
538
+
539
+ .te-markers {
540
+ display: flex;
541
+ justify-content: space-between;
542
+ padding: 0 2px;
543
+ }
544
+
545
+ .te-marker {
546
+ font-size: 0.6rem;
547
+ font-weight: 800;
548
+ color: #9ca3af;
549
+ }
550
+
551
+ .te-ind-wrap {
552
+ position: absolute;
553
+ top: 0.75rem;
554
+ left: 0;
555
+ right: 0;
556
+ display: flex;
557
+ justify-content: center;
558
+ }
559
+
560
+ .te-ind {
561
+ background: #f97316;
562
+ color: white;
563
+ padding: 0.25rem 0.75rem;
564
+ border-radius: 20px;
565
+ font-size: 0.7rem;
566
+ font-weight: 900;
567
+ box-shadow: 0 4px 12px rgba(249, 115, 22, 0.35);
568
+ animation: te-bounce 2s infinite;
569
+ }
570
+
571
+ @keyframes te-bounce {
572
+ 0%,
573
+ 100% {
574
+ transform: translateY(0);
575
+ }
576
+ 50% {
577
+ transform: translateY(-4px);
578
+ }
579
+ }
580
+
581
+ .te-results {
582
+ display: grid;
583
+ grid-template-columns: 1fr 1fr;
584
+ gap: 1rem;
585
+ }
586
+
587
+ .te-result-primary,
588
+ .te-result-secondary {
589
+ padding: 1.5rem;
590
+ border-radius: 18px;
591
+ display: flex;
592
+ flex-direction: column;
593
+ gap: 0.5rem;
594
+ }
595
+
596
+ .te-result-primary {
597
+ background: linear-gradient(135deg, #f97316, #ea580c);
598
+ color: white;
599
+ box-shadow: 0 12px 28px rgba(234, 88, 12, 0.25);
600
+ }
601
+
602
+ .te-result-secondary {
603
+ background: rgba(255, 255, 255, 0.5);
604
+ border: 1px solid rgba(255, 255, 255, 0.7);
605
+ }
606
+
607
+ :global(.theme-dark) .te-result-secondary {
608
+ background: rgba(255, 255, 255, 0.04);
609
+ border-color: rgba(255, 255, 255, 0.06);
610
+ }
611
+
612
+ .te-result-lbl {
613
+ font-size: 0.65rem;
614
+ font-weight: 800;
615
+ text-transform: uppercase;
616
+ letter-spacing: 0.08em;
617
+ opacity: 0.75;
618
+ }
619
+
620
+ .te-result-secondary .te-result-lbl {
621
+ color: #6b7280;
622
+ opacity: 1;
623
+ }
624
+
625
+ :global(.theme-dark) .te-result-secondary .te-result-lbl {
626
+ color: #9ca3af;
627
+ }
628
+
629
+ .te-result-val {
630
+ display: flex;
631
+ align-items: baseline;
632
+ gap: 0.35rem;
633
+ }
634
+
635
+ .te-result-num {
636
+ font-size: 2rem;
637
+ font-weight: 900;
638
+ line-height: 1;
639
+ }
640
+
641
+ .te-result-secondary .te-result-num {
642
+ color: #111827;
643
+ }
644
+
645
+ :global(.theme-dark) .te-result-secondary .te-result-num {
646
+ color: #f9fafb;
647
+ }
648
+
649
+ .te-result-unit {
650
+ font-size: 0.8rem;
651
+ font-weight: 800;
652
+ opacity: 0.7;
653
+ }
654
+
655
+ .te-result-secondary .te-result-unit {
656
+ color: #374151;
657
+ opacity: 1;
658
+ }
659
+
660
+ :global(.theme-dark) .te-result-secondary .te-result-unit {
661
+ color: #d1d5db;
662
+ }
663
+
664
+ .te-ref-grid {
665
+ display: grid;
666
+ grid-template-columns: 1fr 1fr;
667
+ gap: 0.75rem;
668
+ }
669
+
670
+ .te-ref-card {
671
+ background: rgba(255, 255, 255, 0.4);
672
+ border: 1px solid rgba(255, 255, 255, 0.6);
673
+ border-radius: 14px;
674
+ padding: 1rem 1.25rem;
675
+ display: flex;
676
+ flex-direction: column;
677
+ gap: 0.3rem;
678
+ transition:
679
+ background 0.25s,
680
+ border-color 0.25s;
681
+ }
682
+
683
+ :global(.theme-dark) .te-ref-card {
684
+ background: rgba(255, 255, 255, 0.03);
685
+ border-color: rgba(255, 255, 255, 0.06);
686
+ }
687
+
688
+ .te-ref-card[data-risk="critical"] {
689
+ background: rgba(239, 68, 68, 0.08);
690
+ border-color: rgba(239, 68, 68, 0.25);
691
+ }
692
+
693
+ .te-ref-card[data-risk="moderate"] {
694
+ background: rgba(245, 158, 11, 0.08);
695
+ border-color: rgba(245, 158, 11, 0.25);
696
+ }
697
+
698
+ .te-ref-title {
699
+ font-size: 0.6rem;
700
+ font-weight: 800;
701
+ text-transform: uppercase;
702
+ letter-spacing: 0.08em;
703
+ color: #9ca3af;
704
+ }
705
+
706
+ .te-ref-val {
707
+ font-size: 1.1rem;
708
+ font-weight: 900;
709
+ color: #374151;
710
+ }
711
+
712
+ :global(.theme-dark) .te-ref-val {
713
+ color: #e5e7eb;
714
+ }
715
+
716
+ .te-ref-card[data-risk="critical"] .te-ref-val {
717
+ color: #ef4444;
718
+ }
719
+
720
+ .te-ref-card[data-risk="moderate"] .te-ref-val {
721
+ color: #f59e0b;
722
+ }
723
+
724
+ .te-tip-card {
725
+ display: flex;
726
+ align-items: flex-start;
727
+ gap: 0.75rem;
728
+ background: rgba(249, 115, 22, 0.04);
729
+ border: 1px dashed rgba(249, 115, 22, 0.2);
730
+ border-radius: 14px;
731
+ padding: 1rem 1.25rem;
732
+ }
733
+
734
+ .te-tip-bullet {
735
+ color: #f97316;
736
+ font-size: 0.7rem;
737
+ flex-shrink: 0;
738
+ padding-top: 0.15rem;
739
+ }
740
+
741
+ .te-tip-text {
742
+ margin: 0;
743
+ font-size: 0.82rem;
744
+ line-height: 1.55;
745
+ color: #6b7280;
746
+ font-style: italic;
747
+ }
748
+
749
+ :global(.theme-dark) .te-tip-text {
750
+ color: #9ca3af;
751
+ }
752
+
753
+ @media (max-width: 860px) {
754
+ .te-grid {
755
+ grid-template-columns: 1fr;
756
+ }
757
+
758
+ .te-sidebar {
759
+ border-right: none;
760
+ border-bottom: 1px solid #e5e7eb;
761
+ }
762
+
763
+ :global(.theme-dark) .te-sidebar {
764
+ border-bottom-color: rgba(255, 255, 255, 0.06);
765
+ }
766
+
767
+ .te-results {
768
+ grid-template-columns: 1fr;
769
+ }
770
+ }
771
+ </style>