@jjlmoya/utils-home 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 (77) hide show
  1. package/package.json +62 -0
  2. package/src/category/i18n/en.ts +24 -0
  3. package/src/category/i18n/es.ts +24 -0
  4. package/src/category/i18n/fr.ts +24 -0
  5. package/src/category/index.ts +12 -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 +26 -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/seo_length.test.ts +22 -0
  21. package/src/tests/tool_validation.test.ts +17 -0
  22. package/src/tool/dewPointCalculator/bibliography.astro +14 -0
  23. package/src/tool/dewPointCalculator/component.astro +443 -0
  24. package/src/tool/dewPointCalculator/i18n/en.ts +183 -0
  25. package/src/tool/dewPointCalculator/i18n/es.ts +183 -0
  26. package/src/tool/dewPointCalculator/i18n/fr.ts +183 -0
  27. package/src/tool/dewPointCalculator/index.ts +34 -0
  28. package/src/tool/dewPointCalculator/logic.ts +16 -0
  29. package/src/tool/dewPointCalculator/seo.astro +14 -0
  30. package/src/tool/dewPointCalculator/ui.ts +13 -0
  31. package/src/tool/ledSavingCalculator/bibliography.astro +14 -0
  32. package/src/tool/ledSavingCalculator/component.astro +520 -0
  33. package/src/tool/ledSavingCalculator/i18n/en.ts +217 -0
  34. package/src/tool/ledSavingCalculator/i18n/es.ts +217 -0
  35. package/src/tool/ledSavingCalculator/i18n/fr.ts +217 -0
  36. package/src/tool/ledSavingCalculator/index.ts +34 -0
  37. package/src/tool/ledSavingCalculator/logic.ts +31 -0
  38. package/src/tool/ledSavingCalculator/seo.astro +14 -0
  39. package/src/tool/ledSavingCalculator/ui.ts +32 -0
  40. package/src/tool/projectorCalculator/bibliography.astro +14 -0
  41. package/src/tool/projectorCalculator/component.astro +569 -0
  42. package/src/tool/projectorCalculator/i18n/en.ts +181 -0
  43. package/src/tool/projectorCalculator/i18n/es.ts +181 -0
  44. package/src/tool/projectorCalculator/i18n/fr.ts +181 -0
  45. package/src/tool/projectorCalculator/index.ts +34 -0
  46. package/src/tool/projectorCalculator/logic.ts +21 -0
  47. package/src/tool/projectorCalculator/seo.astro +14 -0
  48. package/src/tool/projectorCalculator/ui.ts +16 -0
  49. package/src/tool/qrGenerator/bibliography.astro +14 -0
  50. package/src/tool/qrGenerator/component.astro +499 -0
  51. package/src/tool/qrGenerator/i18n/en.ts +233 -0
  52. package/src/tool/qrGenerator/i18n/es.ts +233 -0
  53. package/src/tool/qrGenerator/i18n/fr.ts +233 -0
  54. package/src/tool/qrGenerator/index.ts +34 -0
  55. package/src/tool/qrGenerator/logic.ts +27 -0
  56. package/src/tool/qrGenerator/seo.astro +14 -0
  57. package/src/tool/qrGenerator/ui.ts +23 -0
  58. package/src/tool/solarCalculator/bibliography.astro +14 -0
  59. package/src/tool/solarCalculator/component.astro +532 -0
  60. package/src/tool/solarCalculator/i18n/en.ts +176 -0
  61. package/src/tool/solarCalculator/i18n/es.ts +176 -0
  62. package/src/tool/solarCalculator/i18n/fr.ts +176 -0
  63. package/src/tool/solarCalculator/index.ts +34 -0
  64. package/src/tool/solarCalculator/logic.ts +31 -0
  65. package/src/tool/solarCalculator/seo.astro +14 -0
  66. package/src/tool/solarCalculator/ui.ts +11 -0
  67. package/src/tool/tariffComparator/bibliography.astro +14 -0
  68. package/src/tool/tariffComparator/component.astro +595 -0
  69. package/src/tool/tariffComparator/i18n/en.ts +192 -0
  70. package/src/tool/tariffComparator/i18n/es.ts +192 -0
  71. package/src/tool/tariffComparator/i18n/fr.ts +192 -0
  72. package/src/tool/tariffComparator/index.ts +34 -0
  73. package/src/tool/tariffComparator/logic.ts +47 -0
  74. package/src/tool/tariffComparator/seo.astro +14 -0
  75. package/src/tool/tariffComparator/ui.ts +25 -0
  76. package/src/tools.ts +9 -0
  77. package/src/types.ts +72 -0
@@ -0,0 +1,595 @@
1
+ ---
2
+ import type { TariffComparatorUI } from './ui';
3
+ import { calculateTariffs } from './logic';
4
+
5
+ interface Props {
6
+ ui?: Record<string, unknown>;
7
+ }
8
+
9
+ const { ui = {} } = Astro.props;
10
+ const tUI = ui as TariffComparatorUI;
11
+
12
+ const INITIAL_CONSUMPTION = 3500;
13
+ const INITIAL_POWER = 4.6;
14
+ const MAX_BAR_H = 130;
15
+ const CHART_TOP = 15;
16
+
17
+ const initial = calculateTariffs({
18
+ consumptionKwh: INITIAL_CONSUMPTION,
19
+ powerKw: INITIAL_POWER,
20
+ solarReduction: false,
21
+ shiftPercent: 0,
22
+ });
23
+
24
+ const initialMax = Math.max(...initial.monthlyData);
25
+ const months = (tUI.monthLabels ?? '').split(',');
26
+ const initialMarketBetter = initial.marketTotal <= initial.freeTotal;
27
+ const initialDiff = Math.round(Math.abs(initial.freeTotal - initial.marketTotal));
28
+
29
+ function iBarH(val: number): number {
30
+ return initialMax > 0 ? Math.round((val / initialMax) * MAX_BAR_H) : 0;
31
+ }
32
+ ---
33
+
34
+ <div class="tc-wrapper">
35
+ <div
36
+ class="tc-card"
37
+ data-badge-better={tUI.badgeBetter}
38
+ data-badge-worse-year={tUI.badgeWorseYear}
39
+ data-currency-sign={tUI.currencySign}
40
+ >
41
+
42
+ <div class="tc-inputs">
43
+ <div class="tc-input-group">
44
+ <label class="tc-label" for="tc-consumption">{tUI.labelConsumption}</label>
45
+ <input type="range" id="tc-consumption" min="1000" max="10000" step="100" value={INITIAL_CONSUMPTION} class="tc-slider" />
46
+ <div class="tc-value-row">
47
+ <span id="tc-consumption-val" class="tc-value">{INITIAL_CONSUMPTION}</span>
48
+ <span class="tc-unit">{tUI.unitKwhYear}</span>
49
+ </div>
50
+ </div>
51
+ <div class="tc-input-group">
52
+ <label class="tc-label" for="tc-power">{tUI.labelPower}</label>
53
+ <input type="range" id="tc-power" min="2.3" max="10" step="0.1" value={INITIAL_POWER} class="tc-slider" />
54
+ <div class="tc-value-row">
55
+ <span id="tc-power-val" class="tc-value">{INITIAL_POWER.toFixed(1)}</span>
56
+ <span class="tc-unit">{tUI.unitKw}</span>
57
+ </div>
58
+ </div>
59
+ </div>
60
+
61
+ <div class="tc-results">
62
+ <div class="tc-tariff-card tc-market-card">
63
+ <span class="tc-tariff-label tc-market-label">{tUI.labelMarket}</span>
64
+ <div class="tc-price-main">
65
+ <p id="tc-market-total" class="tc-total">{Math.round(initial.marketTotal)}{tUI.currencySign}</p>
66
+ <span class="tc-est">{tUI.labelAnnualEst}</span>
67
+ </div>
68
+ <div class="tc-line-items">
69
+ <div class="tc-line">
70
+ <span>{tUI.labelPowerTerm}</span>
71
+ <span id="tc-market-power">{initial.marketPower.toFixed(2)}{tUI.currencySign}</span>
72
+ </div>
73
+ <div class="tc-line">
74
+ <span>{tUI.labelEnergyTerm}</span>
75
+ <span id="tc-market-energy">{initial.marketEnergy.toFixed(2)}{tUI.currencySign}</span>
76
+ </div>
77
+ </div>
78
+ <span id="tc-market-badge" class={initialMarketBetter ? 'tc-badge tc-badge-better' : 'tc-badge tc-badge-worse'}>
79
+ {initialMarketBetter ? tUI.badgeBetter : `+${initialDiff}${tUI.currencySign} / ${tUI.badgeWorseYear}`}
80
+ </span>
81
+ </div>
82
+
83
+ <div class="tc-tariff-card tc-free-card">
84
+ <span class="tc-tariff-label tc-free-label">{tUI.labelFree}</span>
85
+ <div class="tc-price-main">
86
+ <p id="tc-free-total" class="tc-total">{Math.round(initial.freeTotal)}{tUI.currencySign}</p>
87
+ <span class="tc-est">{tUI.labelAnnualEst}</span>
88
+ </div>
89
+ <div class="tc-line-items">
90
+ <div class="tc-line">
91
+ <span>{tUI.labelPowerTerm}</span>
92
+ <span id="tc-free-power">{initial.freePower.toFixed(2)}{tUI.currencySign}</span>
93
+ </div>
94
+ <div class="tc-line">
95
+ <span>{tUI.labelEnergyTerm}</span>
96
+ <span id="tc-free-energy">{initial.freeEnergy.toFixed(2)}{tUI.currencySign}</span>
97
+ </div>
98
+ </div>
99
+ <span id="tc-free-badge" class={initialMarketBetter ? 'tc-badge tc-badge-worse' : 'tc-badge tc-badge-better'}>
100
+ {initialMarketBetter ? `+${initialDiff}${tUI.currencySign} / ${tUI.badgeWorseYear}` : tUI.badgeBetter}
101
+ </span>
102
+ </div>
103
+ </div>
104
+
105
+ <div class="tc-dashboard">
106
+ <p class="tc-section-label">{tUI.labelDashboard}</p>
107
+ <div class="tc-dash-grid">
108
+ <svg class="tc-chart-svg" viewBox="0 0 660 180" preserveAspectRatio="xMidYMid meet">
109
+ {initial.monthlyData.map((val, i) => {
110
+ const h = iBarH(val);
111
+ return (
112
+ <rect
113
+ id={`tc-bar-${i}`}
114
+ x={i * 55 + 10}
115
+ y={CHART_TOP + MAX_BAR_H - h}
116
+ width="35"
117
+ height={h}
118
+ rx="4"
119
+ class="tc-bar"
120
+ ></rect>
121
+ );
122
+ })}
123
+ {months.map((label, i) => (
124
+ <text x={i * 55 + 27.5} y="165" fill="#64748b" font-size="9" text-anchor="middle">{label}</text>
125
+ ))}
126
+ </svg>
127
+ <div class="tc-metrics">
128
+ <div class="tc-metric">
129
+ <span class="tc-metric-label">{tUI.labelMaxPower}</span>
130
+ <span id="tc-max-power" class="tc-metric-val">{initial.maxPowerKw.toFixed(1)} kW</span>
131
+ <span class="tc-metric-sub">{tUI.labelMaxPowerEst}</span>
132
+ </div>
133
+ <div class="tc-metric">
134
+ <span class="tc-metric-label">{tUI.labelCo2}</span>
135
+ <span id="tc-co2" class="tc-metric-val">{initial.co2Kg} kg</span>
136
+ <span class="tc-metric-sub">{tUI.labelCo2Est}</span>
137
+ </div>
138
+ </div>
139
+ </div>
140
+ </div>
141
+
142
+ <div class="tc-simulator">
143
+ <p class="tc-section-label">{tUI.labelSimulator}</p>
144
+ <div class="tc-sim-grid">
145
+ <div class="tc-sim-card">
146
+ <p class="tc-sim-title">{tUI.labelSolar}</p>
147
+ <label class="tc-toggle">
148
+ <input type="checkbox" id="tc-solar" class="tc-toggle-input" />
149
+ <span class="tc-toggle-track"></span>
150
+ </label>
151
+ <p class="tc-sim-desc">{tUI.labelSolarDesc}</p>
152
+ </div>
153
+ <div class="tc-sim-card">
154
+ <p class="tc-sim-title">{tUI.labelShift}</p>
155
+ <input type="range" id="tc-shift" min="0" max="50" step="5" value="0" class="tc-slider" />
156
+ <p class="tc-sim-desc">{tUI.labelShiftDesc} <strong id="tc-shift-val">0%</strong></p>
157
+ </div>
158
+ </div>
159
+ </div>
160
+
161
+ </div>
162
+ </div>
163
+
164
+ <script>
165
+ import { calculateTariffs } from './logic';
166
+
167
+ const MAX_BAR_H = 130;
168
+ const CHART_TOP = 15;
169
+
170
+ function setTxt(id: string, val: string) {
171
+ const el = document.getElementById(id);
172
+ if (el) el.textContent = val;
173
+ }
174
+
175
+ function getSliderVal(id: string): number {
176
+ return parseFloat((document.getElementById(id) as HTMLInputElement | null)?.value ?? '0') || 0;
177
+ }
178
+
179
+ function updateChart(data: number[]) {
180
+ const maxVal = Math.max(...data);
181
+ data.forEach((val, i) => {
182
+ const h = maxVal > 0 ? Math.round((val / maxVal) * MAX_BAR_H) : 0;
183
+ const bar = document.getElementById(`tc-bar-${i}`);
184
+ if (!bar) return;
185
+ bar.setAttribute('height', String(h));
186
+ bar.setAttribute('y', String(CHART_TOP + MAX_BAR_H - h));
187
+ });
188
+ }
189
+
190
+ function updateBadges(market: number, free: number, card: HTMLElement) {
191
+ const mBadge = document.getElementById('tc-market-badge');
192
+ const fBadge = document.getElementById('tc-free-badge');
193
+ if (!mBadge || !fBadge) return;
194
+ const better = card.dataset.badgeBetter ?? '';
195
+ const year = card.dataset.badgeWorseYear ?? '';
196
+ const curr = card.dataset.currencySign ?? '€';
197
+ if (market <= free) {
198
+ mBadge.textContent = better;
199
+ mBadge.className = 'tc-badge tc-badge-better';
200
+ fBadge.textContent = `+${Math.round(free - market)}${curr} / ${year}`;
201
+ fBadge.className = 'tc-badge tc-badge-worse';
202
+ } else {
203
+ fBadge.textContent = better;
204
+ fBadge.className = 'tc-badge tc-badge-better';
205
+ mBadge.textContent = `+${Math.round(market - free)}${curr} / ${year}`;
206
+ mBadge.className = 'tc-badge tc-badge-worse';
207
+ }
208
+ }
209
+
210
+ function calculate(card: HTMLElement) {
211
+ const consumption = getSliderVal('tc-consumption');
212
+ const power = getSliderVal('tc-power');
213
+ const solar = (document.getElementById('tc-solar') as HTMLInputElement | null)?.checked ?? false;
214
+ const shift = getSliderVal('tc-shift');
215
+ const r = calculateTariffs({ consumptionKwh: consumption, powerKw: power, solarReduction: solar, shiftPercent: shift });
216
+ const curr = card.dataset.currencySign ?? '€';
217
+ setTxt('tc-consumption-val', Math.round(r.effectiveConsumption).toLocaleString());
218
+ setTxt('tc-power-val', power.toFixed(1));
219
+ setTxt('tc-market-total', `${Math.round(r.marketTotal)}${curr}`);
220
+ setTxt('tc-market-power', `${r.marketPower.toFixed(2)}${curr}`);
221
+ setTxt('tc-market-energy', `${r.marketEnergy.toFixed(2)}${curr}`);
222
+ setTxt('tc-free-total', `${Math.round(r.freeTotal)}${curr}`);
223
+ setTxt('tc-free-power', `${r.freePower.toFixed(2)}${curr}`);
224
+ setTxt('tc-free-energy', `${r.freeEnergy.toFixed(2)}${curr}`);
225
+ setTxt('tc-max-power', `${r.maxPowerKw.toFixed(1)} kW`);
226
+ setTxt('tc-co2', `${r.co2Kg} kg`);
227
+ updateBadges(r.marketTotal, r.freeTotal, card);
228
+ updateChart(r.monthlyData);
229
+ }
230
+
231
+ function attachSliders(card: HTMLElement) {
232
+ ['tc-consumption', 'tc-power'].forEach(id => {
233
+ const el = document.getElementById(id);
234
+ if (el) el.addEventListener('input', () => calculate(card));
235
+ });
236
+ const solar = document.getElementById('tc-solar');
237
+ if (solar) solar.addEventListener('change', () => calculate(card));
238
+ }
239
+
240
+ function init() {
241
+ const card = document.querySelector('.tc-card') as HTMLElement | null;
242
+ if (!card) return;
243
+ const shiftSlider = document.getElementById('tc-shift') as HTMLInputElement | null;
244
+ const shiftVal = document.getElementById('tc-shift-val');
245
+ if (shiftSlider && shiftVal) {
246
+ shiftSlider.addEventListener('input', () => {
247
+ shiftVal.textContent = `${shiftSlider.value}%`;
248
+ calculate(card);
249
+ });
250
+ }
251
+ attachSliders(card);
252
+ calculate(card);
253
+ }
254
+
255
+ document.addEventListener('astro:page-load', init);
256
+ init();
257
+ </script>
258
+
259
+ <style>
260
+ .tc-wrapper {
261
+ --tc-p: #f59e0b;
262
+
263
+ width: 100%;
264
+ padding: 1rem 0;
265
+ }
266
+
267
+ .tc-card {
268
+ background: var(--bg-surface);
269
+ width: calc(100% - 24px);
270
+ max-width: 960px;
271
+ margin: 0 auto;
272
+ border-radius: 24px;
273
+ overflow: hidden;
274
+ display: flex;
275
+ flex-direction: column;
276
+ gap: 0;
277
+ border: 1px solid var(--border-color);
278
+ color: var(--text-main);
279
+ }
280
+
281
+ .tc-inputs {
282
+ display: grid;
283
+ grid-template-columns: 1fr;
284
+ gap: 24px;
285
+ padding: 32px;
286
+ border-bottom: 1px solid var(--border-color);
287
+ }
288
+
289
+ @media (min-width: 640px) {
290
+ .tc-inputs {
291
+ grid-template-columns: 1fr 1fr;
292
+ }
293
+ }
294
+
295
+ .tc-input-group {
296
+ display: flex;
297
+ flex-direction: column;
298
+ gap: 10px;
299
+ }
300
+
301
+ .tc-label {
302
+ font-size: 0.6875rem;
303
+ font-weight: 700;
304
+ text-transform: uppercase;
305
+ letter-spacing: 0.1em;
306
+ color: var(--tc-p);
307
+ }
308
+
309
+ .tc-slider {
310
+ width: 100%;
311
+ height: 6px;
312
+ accent-color: var(--tc-p);
313
+ cursor: pointer;
314
+ border-radius: 9999px;
315
+ }
316
+
317
+ .tc-value-row {
318
+ display: flex;
319
+ align-items: baseline;
320
+ gap: 6px;
321
+ }
322
+
323
+ .tc-value {
324
+ font-size: 2rem;
325
+ font-weight: 900;
326
+ color: var(--text-main);
327
+ line-height: 1;
328
+ }
329
+
330
+ .tc-unit {
331
+ font-size: 0.8125rem;
332
+ color: var(--text-muted);
333
+ }
334
+
335
+ .tc-results {
336
+ display: grid;
337
+ grid-template-columns: 1fr;
338
+ gap: 16px;
339
+ padding: 24px 32px;
340
+ border-bottom: 1px solid var(--border-color);
341
+ }
342
+
343
+ @media (min-width: 640px) {
344
+ .tc-results {
345
+ grid-template-columns: 1fr 1fr;
346
+ }
347
+ }
348
+
349
+ .tc-tariff-card {
350
+ border-radius: 16px;
351
+ padding: 20px;
352
+ display: flex;
353
+ flex-direction: column;
354
+ gap: 12px;
355
+ }
356
+
357
+ .tc-market-card {
358
+ background: rgba(245, 158, 11, 0.06);
359
+ border: 1px solid rgba(245, 158, 11, 0.2);
360
+ }
361
+
362
+ .tc-free-card {
363
+ background: rgba(14, 165, 233, 0.06);
364
+ border: 1px solid rgba(14, 165, 233, 0.2);
365
+ }
366
+
367
+ .tc-tariff-label {
368
+ font-size: 0.625rem;
369
+ font-weight: 900;
370
+ text-transform: uppercase;
371
+ letter-spacing: 0.12em;
372
+ padding: 4px 10px;
373
+ border-radius: 9999px;
374
+ align-self: flex-start;
375
+ }
376
+
377
+ .tc-market-label {
378
+ background: #fbbf24;
379
+ color: #78350f;
380
+ }
381
+
382
+ .tc-free-label {
383
+ background: #38bdf8;
384
+ color: #0c4a6e;
385
+ }
386
+
387
+ .tc-price-main {
388
+ display: flex;
389
+ flex-direction: column;
390
+ gap: 2px;
391
+ }
392
+
393
+ .tc-total {
394
+ font-size: 2.75rem;
395
+ font-weight: 900;
396
+ color: var(--text-main);
397
+ margin: 0;
398
+ line-height: 1;
399
+ }
400
+
401
+ .tc-est {
402
+ font-size: 0.75rem;
403
+ color: var(--text-muted);
404
+ }
405
+
406
+ .tc-line-items {
407
+ display: flex;
408
+ flex-direction: column;
409
+ gap: 6px;
410
+ padding-top: 12px;
411
+ border-top: 1px solid var(--border-color);
412
+ }
413
+
414
+ .tc-line {
415
+ display: flex;
416
+ justify-content: space-between;
417
+ font-size: 0.8125rem;
418
+ color: var(--text-muted);
419
+ }
420
+
421
+ .tc-badge {
422
+ align-self: flex-start;
423
+ padding: 5px 12px;
424
+ border-radius: 9999px;
425
+ font-size: 0.75rem;
426
+ font-weight: 700;
427
+ }
428
+
429
+ .tc-badge-better {
430
+ background: #22c55e;
431
+ color: #fff;
432
+ }
433
+
434
+ .tc-badge-worse {
435
+ background: var(--bg-muted);
436
+ color: var(--text-muted);
437
+ }
438
+
439
+ .tc-dashboard {
440
+ padding: 24px 32px;
441
+ border-bottom: 1px solid var(--border-color);
442
+ display: flex;
443
+ flex-direction: column;
444
+ gap: 16px;
445
+ }
446
+
447
+ .tc-section-label {
448
+ font-size: 0.6875rem;
449
+ font-weight: 900;
450
+ text-transform: uppercase;
451
+ letter-spacing: 0.14em;
452
+ color: var(--tc-p);
453
+ margin: 0;
454
+ }
455
+
456
+ .tc-dash-grid {
457
+ display: grid;
458
+ grid-template-columns: 1fr;
459
+ gap: 20px;
460
+ }
461
+
462
+ @media (min-width: 768px) {
463
+ .tc-dash-grid {
464
+ grid-template-columns: 2fr 1fr;
465
+ }
466
+ }
467
+
468
+ .tc-chart-svg {
469
+ width: 100%;
470
+ display: block;
471
+ }
472
+
473
+ .tc-bar {
474
+ fill: var(--tc-p);
475
+ opacity: 0.7;
476
+ }
477
+
478
+ .tc-metrics {
479
+ display: flex;
480
+ flex-direction: column;
481
+ gap: 12px;
482
+ }
483
+
484
+ .tc-metric {
485
+ background: var(--bg-muted);
486
+ border-radius: 12px;
487
+ padding: 14px 16px;
488
+ display: flex;
489
+ flex-direction: column;
490
+ gap: 2px;
491
+ }
492
+
493
+ .tc-metric-label {
494
+ font-size: 0.625rem;
495
+ font-weight: 900;
496
+ text-transform: uppercase;
497
+ letter-spacing: 0.1em;
498
+ color: var(--text-muted);
499
+ }
500
+
501
+ .tc-metric-val {
502
+ font-size: 1.5rem;
503
+ font-weight: 900;
504
+ color: var(--text-main);
505
+ }
506
+
507
+ .tc-metric-sub {
508
+ font-size: 0.6875rem;
509
+ color: var(--text-muted);
510
+ }
511
+
512
+ .tc-simulator {
513
+ padding: 24px 32px;
514
+ display: flex;
515
+ flex-direction: column;
516
+ gap: 16px;
517
+ }
518
+
519
+ .tc-sim-grid {
520
+ display: grid;
521
+ grid-template-columns: 1fr;
522
+ gap: 16px;
523
+ }
524
+
525
+ @media (min-width: 640px) {
526
+ .tc-sim-grid {
527
+ grid-template-columns: 1fr 1fr;
528
+ }
529
+ }
530
+
531
+ .tc-sim-card {
532
+ background: var(--bg-muted);
533
+ border-radius: 14px;
534
+ padding: 16px;
535
+ display: flex;
536
+ flex-direction: column;
537
+ gap: 10px;
538
+ }
539
+
540
+ .tc-sim-title {
541
+ font-size: 0.8125rem;
542
+ font-weight: 700;
543
+ color: var(--text-main);
544
+ margin: 0;
545
+ }
546
+
547
+ .tc-sim-desc {
548
+ font-size: 0.75rem;
549
+ color: var(--text-muted);
550
+ margin: 0;
551
+ }
552
+
553
+ .tc-toggle {
554
+ position: relative;
555
+ display: inline-block;
556
+ width: 48px;
557
+ height: 26px;
558
+ }
559
+
560
+ .tc-toggle-input {
561
+ opacity: 0;
562
+ width: 0;
563
+ height: 0;
564
+ position: absolute;
565
+ }
566
+
567
+ .tc-toggle-track {
568
+ position: absolute;
569
+ cursor: pointer;
570
+ inset: 0;
571
+ background: var(--border-color);
572
+ border-radius: 9999px;
573
+ transition: background 0.3s;
574
+ }
575
+
576
+ .tc-toggle-track::before {
577
+ content: "";
578
+ position: absolute;
579
+ width: 18px;
580
+ height: 18px;
581
+ left: 4px;
582
+ top: 4px;
583
+ background: #fff;
584
+ border-radius: 50%;
585
+ transition: transform 0.3s;
586
+ }
587
+
588
+ .tc-toggle-input:checked + .tc-toggle-track {
589
+ background: var(--tc-p);
590
+ }
591
+
592
+ .tc-toggle-input:checked + .tc-toggle-track::before {
593
+ transform: translateX(22px);
594
+ }
595
+ </style>