@jjlmoya/utils-developer 1.2.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 (228) hide show
  1. package/package.json +64 -0
  2. package/src/category/i18n/en.ts +34 -0
  3. package/src/category/i18n/es.ts +34 -0
  4. package/src/category/i18n/fr.ts +34 -0
  5. package/src/category/index.ts +31 -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 +78 -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/css_specificity_logic.test.ts +39 -0
  17. package/src/tests/faq_count.test.ts +19 -0
  18. package/src/tests/locale_completeness.test.ts +42 -0
  19. package/src/tests/mocks/astro_mock.js +2 -0
  20. package/src/tests/no_h1_in_components.test.ts +48 -0
  21. package/src/tests/schemas_fulfillment.test.ts +23 -0
  22. package/src/tests/seo_length.test.ts +22 -0
  23. package/src/tests/title_quality.test.ts +55 -0
  24. package/src/tests/tool_validation.test.ts +17 -0
  25. package/src/tool/aspectRatio/bibliography.astro +14 -0
  26. package/src/tool/aspectRatio/component.astro +468 -0
  27. package/src/tool/aspectRatio/i18n/en.ts +177 -0
  28. package/src/tool/aspectRatio/i18n/es.ts +177 -0
  29. package/src/tool/aspectRatio/i18n/fr.ts +177 -0
  30. package/src/tool/aspectRatio/index.ts +34 -0
  31. package/src/tool/aspectRatio/seo.astro +14 -0
  32. package/src/tool/aspectRatio/ui.ts +12 -0
  33. package/src/tool/calculadoraTiempoDatos/bibliography.astro +14 -0
  34. package/src/tool/calculadoraTiempoDatos/component.astro +829 -0
  35. package/src/tool/calculadoraTiempoDatos/i18n/en.ts +150 -0
  36. package/src/tool/calculadoraTiempoDatos/i18n/es.ts +163 -0
  37. package/src/tool/calculadoraTiempoDatos/i18n/fr.ts +150 -0
  38. package/src/tool/calculadoraTiempoDatos/index.ts +33 -0
  39. package/src/tool/calculadoraTiempoDatos/seo.astro +14 -0
  40. package/src/tool/calculadoraTiempoDatos/ui.ts +24 -0
  41. package/src/tool/colorConverter/bibliography.astro +14 -0
  42. package/src/tool/colorConverter/component.astro +504 -0
  43. package/src/tool/colorConverter/i18n/en.ts +126 -0
  44. package/src/tool/colorConverter/i18n/es.ts +126 -0
  45. package/src/tool/colorConverter/i18n/fr.ts +126 -0
  46. package/src/tool/colorConverter/index.ts +33 -0
  47. package/src/tool/colorConverter/seo.astro +14 -0
  48. package/src/tool/colorConverter/ui.ts +16 -0
  49. package/src/tool/conversorExcelCsvHtml/bibliography.astro +14 -0
  50. package/src/tool/conversorExcelCsvHtml/component.astro +595 -0
  51. package/src/tool/conversorExcelCsvHtml/i18n/en.ts +129 -0
  52. package/src/tool/conversorExcelCsvHtml/i18n/es.ts +129 -0
  53. package/src/tool/conversorExcelCsvHtml/i18n/fr.ts +129 -0
  54. package/src/tool/conversorExcelCsvHtml/index.ts +33 -0
  55. package/src/tool/conversorExcelCsvHtml/seo.astro +14 -0
  56. package/src/tool/conversorExcelCsvHtml/ui.ts +14 -0
  57. package/src/tool/cronGenerator/bibliography.astro +14 -0
  58. package/src/tool/cronGenerator/component.astro +667 -0
  59. package/src/tool/cronGenerator/i18n/en.ts +191 -0
  60. package/src/tool/cronGenerator/i18n/es.ts +191 -0
  61. package/src/tool/cronGenerator/i18n/fr.ts +191 -0
  62. package/src/tool/cronGenerator/index.ts +34 -0
  63. package/src/tool/cronGenerator/seo.astro +14 -0
  64. package/src/tool/cronGenerator/ui.ts +45 -0
  65. package/src/tool/cssSpecificityCalculator/bibliography.astro +14 -0
  66. package/src/tool/cssSpecificityCalculator/component.astro +292 -0
  67. package/src/tool/cssSpecificityCalculator/i18n/en.ts +168 -0
  68. package/src/tool/cssSpecificityCalculator/i18n/es.ts +168 -0
  69. package/src/tool/cssSpecificityCalculator/i18n/fr.ts +168 -0
  70. package/src/tool/cssSpecificityCalculator/index.ts +34 -0
  71. package/src/tool/cssSpecificityCalculator/logic.ts +100 -0
  72. package/src/tool/cssSpecificityCalculator/seo.astro +14 -0
  73. package/src/tool/cssSpecificityCalculator/ui-controller.ts +57 -0
  74. package/src/tool/cssSpecificityCalculator/ui.ts +11 -0
  75. package/src/tool/cssToInlineConverter/bibliography.astro +14 -0
  76. package/src/tool/cssToInlineConverter/component.astro +308 -0
  77. package/src/tool/cssToInlineConverter/i18n/en.ts +160 -0
  78. package/src/tool/cssToInlineConverter/i18n/es.ts +160 -0
  79. package/src/tool/cssToInlineConverter/i18n/fr.ts +160 -0
  80. package/src/tool/cssToInlineConverter/index.ts +34 -0
  81. package/src/tool/cssToInlineConverter/seo.astro +14 -0
  82. package/src/tool/cssToInlineConverter/ui.ts +12 -0
  83. package/src/tool/duplicateCssRemover/bibliography.astro +14 -0
  84. package/src/tool/duplicateCssRemover/component.astro +353 -0
  85. package/src/tool/duplicateCssRemover/i18n/en.ts +167 -0
  86. package/src/tool/duplicateCssRemover/i18n/es.ts +167 -0
  87. package/src/tool/duplicateCssRemover/i18n/fr.ts +167 -0
  88. package/src/tool/duplicateCssRemover/index.ts +34 -0
  89. package/src/tool/duplicateCssRemover/seo.astro +14 -0
  90. package/src/tool/duplicateCssRemover/ui.ts +12 -0
  91. package/src/tool/generadorSecurityTxt/bibliography.astro +14 -0
  92. package/src/tool/generadorSecurityTxt/component.astro +473 -0
  93. package/src/tool/generadorSecurityTxt/i18n/en.ts +173 -0
  94. package/src/tool/generadorSecurityTxt/i18n/es.ts +173 -0
  95. package/src/tool/generadorSecurityTxt/i18n/fr.ts +173 -0
  96. package/src/tool/generadorSecurityTxt/index.ts +33 -0
  97. package/src/tool/generadorSecurityTxt/seo.astro +14 -0
  98. package/src/tool/generadorSecurityTxt/ui.ts +27 -0
  99. package/src/tool/hashGenerator/bibliography.astro +14 -0
  100. package/src/tool/hashGenerator/component.astro +367 -0
  101. package/src/tool/hashGenerator/i18n/en.ts +118 -0
  102. package/src/tool/hashGenerator/i18n/es.ts +118 -0
  103. package/src/tool/hashGenerator/i18n/fr.ts +118 -0
  104. package/src/tool/hashGenerator/index.ts +33 -0
  105. package/src/tool/hashGenerator/md5.ts +138 -0
  106. package/src/tool/hashGenerator/seo.astro +14 -0
  107. package/src/tool/hashGenerator/ui.ts +11 -0
  108. package/src/tool/inspectorCertificadosSsl/bibliography.astro +14 -0
  109. package/src/tool/inspectorCertificadosSsl/component.astro +580 -0
  110. package/src/tool/inspectorCertificadosSsl/i18n/en.ts +214 -0
  111. package/src/tool/inspectorCertificadosSsl/i18n/es.ts +214 -0
  112. package/src/tool/inspectorCertificadosSsl/i18n/fr.ts +214 -0
  113. package/src/tool/inspectorCertificadosSsl/index.ts +33 -0
  114. package/src/tool/inspectorCertificadosSsl/seo.astro +14 -0
  115. package/src/tool/inspectorCertificadosSsl/ui.ts +29 -0
  116. package/src/tool/jsonFormatter/bibliography.astro +14 -0
  117. package/src/tool/jsonFormatter/component.astro +590 -0
  118. package/src/tool/jsonFormatter/i18n/en.ts +156 -0
  119. package/src/tool/jsonFormatter/i18n/es.ts +156 -0
  120. package/src/tool/jsonFormatter/i18n/fr.ts +156 -0
  121. package/src/tool/jsonFormatter/index.ts +34 -0
  122. package/src/tool/jsonFormatter/seo.astro +14 -0
  123. package/src/tool/jsonFormatter/ui.ts +15 -0
  124. package/src/tool/keycode/bibliography.astro +14 -0
  125. package/src/tool/keycode/component.astro +540 -0
  126. package/src/tool/keycode/i18n/en.ts +160 -0
  127. package/src/tool/keycode/i18n/es.ts +160 -0
  128. package/src/tool/keycode/i18n/fr.ts +160 -0
  129. package/src/tool/keycode/index.ts +34 -0
  130. package/src/tool/keycode/seo.astro +14 -0
  131. package/src/tool/keycode/ui.ts +10 -0
  132. package/src/tool/llmCostCalculator/bibliography.astro +14 -0
  133. package/src/tool/llmCostCalculator/component.astro +488 -0
  134. package/src/tool/llmCostCalculator/data.ts +99 -0
  135. package/src/tool/llmCostCalculator/i18n/en.ts +165 -0
  136. package/src/tool/llmCostCalculator/i18n/es.ts +165 -0
  137. package/src/tool/llmCostCalculator/i18n/fr.ts +165 -0
  138. package/src/tool/llmCostCalculator/index.ts +34 -0
  139. package/src/tool/llmCostCalculator/seo.astro +14 -0
  140. package/src/tool/llmCostCalculator/ui.ts +11 -0
  141. package/src/tool/mobileMockupGenerator/app.ts +614 -0
  142. package/src/tool/mobileMockupGenerator/bibliography.astro +14 -0
  143. package/src/tool/mobileMockupGenerator/component.astro +1044 -0
  144. package/src/tool/mobileMockupGenerator/constants.ts +48 -0
  145. package/src/tool/mobileMockupGenerator/i18n/en.ts +121 -0
  146. package/src/tool/mobileMockupGenerator/i18n/es.ts +121 -0
  147. package/src/tool/mobileMockupGenerator/i18n/fr.ts +121 -0
  148. package/src/tool/mobileMockupGenerator/index.ts +33 -0
  149. package/src/tool/mobileMockupGenerator/rendering.ts +189 -0
  150. package/src/tool/mobileMockupGenerator/seo.astro +14 -0
  151. package/src/tool/mobileMockupGenerator/store.ts +145 -0
  152. package/src/tool/mobileMockupGenerator/types.ts +41 -0
  153. package/src/tool/mobileMockupGenerator/ui.ts +48 -0
  154. package/src/tool/musicalTypography/bibliography.astro +14 -0
  155. package/src/tool/musicalTypography/component.astro +480 -0
  156. package/src/tool/musicalTypography/i18n/en.ts +179 -0
  157. package/src/tool/musicalTypography/i18n/es.ts +179 -0
  158. package/src/tool/musicalTypography/i18n/fr.ts +179 -0
  159. package/src/tool/musicalTypography/index.ts +33 -0
  160. package/src/tool/musicalTypography/seo.astro +14 -0
  161. package/src/tool/musicalTypography/ui.ts +25 -0
  162. package/src/tool/placeholderGenerator/bibliography.astro +14 -0
  163. package/src/tool/placeholderGenerator/component.astro +499 -0
  164. package/src/tool/placeholderGenerator/i18n/en.ts +176 -0
  165. package/src/tool/placeholderGenerator/i18n/es.ts +176 -0
  166. package/src/tool/placeholderGenerator/i18n/fr.ts +176 -0
  167. package/src/tool/placeholderGenerator/index.ts +34 -0
  168. package/src/tool/placeholderGenerator/seo.astro +14 -0
  169. package/src/tool/placeholderGenerator/ui.ts +14 -0
  170. package/src/tool/promptLibrary/bibliography.astro +14 -0
  171. package/src/tool/promptLibrary/component.astro +1131 -0
  172. package/src/tool/promptLibrary/controller.ts +49 -0
  173. package/src/tool/promptLibrary/i18n/en.ts +131 -0
  174. package/src/tool/promptLibrary/i18n/es.ts +131 -0
  175. package/src/tool/promptLibrary/i18n/fr.ts +126 -0
  176. package/src/tool/promptLibrary/index.ts +33 -0
  177. package/src/tool/promptLibrary/seo.astro +14 -0
  178. package/src/tool/promptLibrary/ui.ts +29 -0
  179. package/src/tool/readabilityCalculator/bibliography.astro +14 -0
  180. package/src/tool/readabilityCalculator/component.astro +534 -0
  181. package/src/tool/readabilityCalculator/i18n/en.ts +133 -0
  182. package/src/tool/readabilityCalculator/i18n/es.ts +133 -0
  183. package/src/tool/readabilityCalculator/i18n/fr.ts +133 -0
  184. package/src/tool/readabilityCalculator/index.ts +33 -0
  185. package/src/tool/readabilityCalculator/seo.astro +14 -0
  186. package/src/tool/readabilityCalculator/ui.ts +27 -0
  187. package/src/tool/svgSanitizer/bibliography.astro +14 -0
  188. package/src/tool/svgSanitizer/component.astro +554 -0
  189. package/src/tool/svgSanitizer/i18n/en.ts +125 -0
  190. package/src/tool/svgSanitizer/i18n/es.ts +125 -0
  191. package/src/tool/svgSanitizer/i18n/fr.ts +125 -0
  192. package/src/tool/svgSanitizer/index.ts +33 -0
  193. package/src/tool/svgSanitizer/seo.astro +14 -0
  194. package/src/tool/svgSanitizer/ui.ts +18 -0
  195. package/src/tool/svgToCss/bibliography.astro +14 -0
  196. package/src/tool/svgToCss/component.astro +381 -0
  197. package/src/tool/svgToCss/i18n/en.ts +174 -0
  198. package/src/tool/svgToCss/i18n/es.ts +174 -0
  199. package/src/tool/svgToCss/i18n/fr.ts +174 -0
  200. package/src/tool/svgToCss/index.ts +34 -0
  201. package/src/tool/svgToCss/seo.astro +14 -0
  202. package/src/tool/svgToCss/ui.ts +13 -0
  203. package/src/tool/urlCleaner/bibliography.astro +14 -0
  204. package/src/tool/urlCleaner/component.astro +458 -0
  205. package/src/tool/urlCleaner/i18n/en.ts +116 -0
  206. package/src/tool/urlCleaner/i18n/es.ts +116 -0
  207. package/src/tool/urlCleaner/i18n/fr.ts +116 -0
  208. package/src/tool/urlCleaner/index.ts +33 -0
  209. package/src/tool/urlCleaner/seo.astro +14 -0
  210. package/src/tool/urlCleaner/ui.ts +12 -0
  211. package/src/tool/urlEncoderDecoder/bibliography.astro +14 -0
  212. package/src/tool/urlEncoderDecoder/component.astro +291 -0
  213. package/src/tool/urlEncoderDecoder/i18n/en.ts +162 -0
  214. package/src/tool/urlEncoderDecoder/i18n/es.ts +162 -0
  215. package/src/tool/urlEncoderDecoder/i18n/fr.ts +162 -0
  216. package/src/tool/urlEncoderDecoder/index.ts +34 -0
  217. package/src/tool/urlEncoderDecoder/seo.astro +14 -0
  218. package/src/tool/urlEncoderDecoder/ui.ts +11 -0
  219. package/src/tool/utmGenerator/bibliography.astro +14 -0
  220. package/src/tool/utmGenerator/component.astro +270 -0
  221. package/src/tool/utmGenerator/i18n/en.ts +117 -0
  222. package/src/tool/utmGenerator/i18n/es.ts +117 -0
  223. package/src/tool/utmGenerator/i18n/fr.ts +117 -0
  224. package/src/tool/utmGenerator/index.ts +33 -0
  225. package/src/tool/utmGenerator/seo.astro +14 -0
  226. package/src/tool/utmGenerator/ui.ts +13 -0
  227. package/src/tools.ts +27 -0
  228. package/src/types.ts +72 -0
@@ -0,0 +1,829 @@
1
+ ---
2
+ import { Icon } from 'astro-icon/components';
3
+ import type { KnownLocale } from '../../types';
4
+ import type { CalculadoraTiempoDatosUI } from './ui';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ ui?: Record<string, unknown>;
9
+ }
10
+
11
+ const { ui } = Astro.props;
12
+ const t = (ui ?? {}) as CalculadoraTiempoDatosUI;
13
+ ---
14
+
15
+ <div id="ctd-root" data-i18n={JSON.stringify(t)} class="ctd-wrapper">
16
+ <div class="ctd-card">
17
+ <div class="ctd-header">
18
+ <div class="ctd-icon">
19
+ <Icon name="mdi:clock" />
20
+ </div>
21
+ <h2>{t.headerTitle}</h2>
22
+ </div>
23
+
24
+ <div class="ctd-input-section">
25
+ <label for="ctd-size">
26
+ {t.labelWebSize}
27
+ </label>
28
+ <div class="ctd-input-group">
29
+ <input
30
+ type="number"
31
+ id="ctd-size"
32
+ class="ctd-input"
33
+ value="3.5"
34
+ min="0.1"
35
+ max="100"
36
+ step="0.1"
37
+ />
38
+ <span class="ctd-input-unit">{t.unit}</span>
39
+ </div>
40
+
41
+ <div class="ctd-presets-label">{t.presetsLabel}</div>
42
+ <div class="ctd-presets">
43
+ <button class="ctd-preset" data-value="2.8" data-label="Instagram">
44
+ Instagram
45
+ </button>
46
+ <button class="ctd-preset" data-value="2.5" data-label="TikTok">
47
+ TikTok
48
+ </button>
49
+ <button class="ctd-preset" data-value="3.2" data-label="YouTube">
50
+ YouTube
51
+ </button>
52
+ <button class="ctd-preset" data-value="1.8" data-label="Twitter/X">
53
+ Twitter/X
54
+ </button>
55
+ <button class="ctd-preset active" data-value="2.2" data-label="Netflix">
56
+ Netflix
57
+ </button>
58
+ <button class="ctd-preset" data-value="3.5" data-label="Discord">
59
+ Discord
60
+ </button>
61
+ </div>
62
+ </div>
63
+
64
+ <div class="ctd-speed-section">
65
+ <label>{t.labelSpeed}</label>
66
+ <div class="ctd-speed-buttons">
67
+ <button class="ctd-speed-btn" data-speed="3g">
68
+ <span class="ctd-speed-label">{t.speedLabel3g}</span>
69
+ <span class="ctd-speed-value">{t.speedValue3g}</span>
70
+ </button>
71
+ <button class="ctd-speed-btn active" data-speed="4g">
72
+ <span class="ctd-speed-label">{t.speedLabel4g}</span>
73
+ <span class="ctd-speed-value">{t.speedValue4g}</span>
74
+ </button>
75
+ <button class="ctd-speed-btn" data-speed="5g">
76
+ <span class="ctd-speed-label">{t.speedLabel5g}</span>
77
+ <span class="ctd-speed-value">{t.speedValue5g}</span>
78
+ </button>
79
+ </div>
80
+ </div>
81
+
82
+ <div class="ctd-multiplier-section">
83
+ <label for="ctd-times">{t.labelTimes}</label>
84
+ <div class="ctd-multiplier-input">
85
+ <button class="ctd-times-btn" data-times="1">1x</button>
86
+ <button class="ctd-times-btn" data-times="10">10x</button>
87
+ <button class="ctd-times-btn active" data-times="50">50x</button>
88
+ <button class="ctd-times-btn" data-times="100">100x</button>
89
+ <button class="ctd-times-btn" data-times="200">200x</button>
90
+ <input
91
+ type="number"
92
+ id="ctd-times"
93
+ class="ctd-times-input"
94
+ value="50"
95
+ min="1"
96
+ max="10000"
97
+ />
98
+ </div>
99
+ </div>
100
+
101
+ <div class="ctd-results">
102
+ <div class="ctd-result-single">
103
+ <div class="ctd-result-label">{t.resultSingleLoad}</div>
104
+ <div class="ctd-result-time" id="ctd-single-time">1.4s</div>
105
+ <div class="ctd-result-life" id="ctd-single-life">0.0000006%</div>
106
+ </div>
107
+
108
+ <div class="ctd-divider"></div>
109
+
110
+ <div class="ctd-result-total">
111
+ <div class="ctd-result-label">
112
+ {t.resultDailyTotal} (<span id="ctd-times-display">50</span>x)
113
+ </div>
114
+ <div class="ctd-result-time-total" id="ctd-total-time">1m 10s</div>
115
+ <div class="ctd-result-life-total" id="ctd-total-life">0.0000289%</div>
116
+ </div>
117
+ </div>
118
+
119
+ <div class="ctd-impact">
120
+ <div class="ctd-impact-card">
121
+ <div class="ctd-impact-number" id="ctd-year-hours">8,760 h</div>
122
+ <div class="ctd-impact-text">{t.resultTimePerYear}</div>
123
+ </div>
124
+ <div class="ctd-impact-card">
125
+ <div class="ctd-impact-number" id="ctd-life-years">1.0 años</div>
126
+ <div class="ctd-impact-text">{t.resultLifeYears}</div>
127
+ </div>
128
+ </div>
129
+
130
+ <div class="ctd-message" id="ctd-message">
131
+ <strong>{t.resultMessage}</strong>
132
+ </div>
133
+ </div>
134
+ </div>
135
+
136
+ <script>
137
+ const LIFE_EXPECTANCY_SECONDS = 2522880000;
138
+ const LIFE_EXPECTANCY_YEARS = 80;
139
+
140
+ const SPEEDS: Record<string, number> = {
141
+ '3g': 0.4,
142
+ '4g': 10,
143
+ '5g': 100,
144
+ };
145
+
146
+ const PRESETS: Record<string, string> = {
147
+ '2.8': 'Instagram',
148
+ '2.5': 'TikTok',
149
+ '3.2': 'YouTube',
150
+ '1.8': 'Twitter/X',
151
+ '2.2': 'Netflix',
152
+ '3.5': 'Discord',
153
+ };
154
+
155
+ let selectedSpeed = '4g';
156
+
157
+ function calculateLoadTime(sizeMB: number, speedMbps: number): number {
158
+ return (sizeMB * 8) / speedMbps;
159
+ }
160
+
161
+ function formatTime(seconds: number): string {
162
+ if (seconds < 1) return Math.round(seconds * 1000) + 'ms';
163
+ if (seconds < 60) return seconds.toFixed(1) + 's';
164
+ const minutes = Math.floor(seconds / 60);
165
+ const secs = Math.floor(seconds % 60);
166
+ if (minutes < 60) return `${minutes}m ${secs}s`;
167
+ const hours = Math.floor(minutes / 60);
168
+ const mins = minutes % 60;
169
+ return `${hours}h ${mins}m`;
170
+ }
171
+
172
+ function calculateLifePercent(seconds: number): number {
173
+ return (seconds / LIFE_EXPECTANCY_SECONDS) * 100;
174
+ }
175
+
176
+ function updateTimeElements(singleTime: number, singleLife: number, totalTime: number, totalLife: number): void {
177
+ const singleTimeEl = document.getElementById('ctd-single-time');
178
+ const singleLifeEl = document.getElementById('ctd-single-life');
179
+ const totalTimeEl = document.getElementById('ctd-total-time');
180
+ const totalLifeEl = document.getElementById('ctd-total-life');
181
+
182
+ if (singleTimeEl) singleTimeEl.textContent = formatTime(singleTime);
183
+ if (singleLifeEl) singleLifeEl.textContent = singleLife.toFixed(7) + '%';
184
+ if (totalTimeEl) totalTimeEl.textContent = formatTime(totalTime);
185
+ if (totalLifeEl) totalLifeEl.textContent = totalLife.toFixed(7) + '%';
186
+ }
187
+
188
+ function updateImpactElements(totalTimePerYear: number): void {
189
+ const yearHoursEl = document.getElementById('ctd-year-hours');
190
+ const lifeYearsEl = document.getElementById('ctd-life-years');
191
+ const yearHours = (totalTimePerYear / 3600).toFixed(0);
192
+ const lifeYears = (totalTimePerYear / LIFE_EXPECTANCY_SECONDS) * LIFE_EXPECTANCY_YEARS;
193
+
194
+ if (yearHoursEl) yearHoursEl.textContent = parseInt(yearHours).toLocaleString() + ' h';
195
+ if (lifeYearsEl) lifeYearsEl.textContent = lifeYears.toFixed(2) + ' años';
196
+ }
197
+
198
+ function updateResults(): void {
199
+ const sizeInput = document.getElementById('ctd-size') as HTMLInputElement;
200
+ const timesInput = document.getElementById('ctd-times') as HTMLInputElement;
201
+
202
+ if (!sizeInput || !timesInput) return;
203
+
204
+ const size = parseFloat(sizeInput.value) || 0;
205
+ const times = parseInt(timesInput.value) || 1;
206
+
207
+ const singleTime = calculateLoadTime(size, SPEEDS[selectedSpeed]);
208
+ const totalTime = singleTime * times;
209
+ const totalTimePerYear = totalTime * 365;
210
+
211
+ const singleLife = calculateLifePercent(singleTime);
212
+ const totalLife = calculateLifePercent(totalTime);
213
+
214
+ const timesDisplayEl = document.getElementById('ctd-times-display');
215
+ if (timesDisplayEl) timesDisplayEl.textContent = times.toString();
216
+
217
+ updateTimeElements(singleTime, singleLife, totalTime, totalLife);
218
+ updateImpactElements(totalTimePerYear);
219
+ updateMessage(times, size);
220
+ }
221
+
222
+ function updateMessage(times: number, size: number): void {
223
+ const messageEl = document.getElementById('ctd-message');
224
+ const appName = PRESETS[size.toFixed(1)] || 'esta app';
225
+ const singleTime = calculateLoadTime(size, SPEEDS[selectedSpeed]);
226
+ const totalTime = singleTime * times;
227
+ const totalTimePerYear = totalTime * 365;
228
+ const lifeYears = (totalTimePerYear / LIFE_EXPECTANCY_SECONDS) * LIFE_EXPECTANCY_YEARS;
229
+
230
+ const message = `Has perdido aproximadamente ${lifeYears.toFixed(2)} años de vida esperando a que cargue ${appName}`;
231
+
232
+ if (messageEl) messageEl.innerHTML = `<strong>${message}</strong>`;
233
+ }
234
+
235
+ function setupSpeedButtons(): void {
236
+ const speedBtns = document.querySelectorAll('.ctd-speed-btn');
237
+ speedBtns.forEach((btn) => {
238
+ btn.addEventListener('click', (e) => {
239
+ const target = e.currentTarget as HTMLButtonElement;
240
+ const speed = target.dataset.speed;
241
+ if (speed) {
242
+ selectedSpeed = speed;
243
+ speedBtns.forEach((b) => b.classList.remove('active'));
244
+ target.classList.add('active');
245
+ updateResults();
246
+ }
247
+ });
248
+ });
249
+ }
250
+
251
+ function setupPresets(): void {
252
+ const sizeInput = document.getElementById('ctd-size') as HTMLInputElement;
253
+ const presets = document.querySelectorAll('.ctd-preset');
254
+ presets.forEach((btn) => {
255
+ btn.addEventListener('click', (e) => {
256
+ const target = e.currentTarget as HTMLButtonElement;
257
+ const value = target.dataset.value;
258
+ if (value && sizeInput) {
259
+ sizeInput.value = value;
260
+ presets.forEach((b) => b.classList.remove('active'));
261
+ target.classList.add('active');
262
+ updateResults();
263
+ }
264
+ });
265
+ });
266
+ }
267
+
268
+ function setupTimesButtons(): void {
269
+ const timesInput = document.getElementById('ctd-times') as HTMLInputElement;
270
+ const timesBtns = document.querySelectorAll('.ctd-times-btn');
271
+ timesBtns.forEach((btn) => {
272
+ btn.addEventListener('click', (e) => {
273
+ const target = e.currentTarget as HTMLButtonElement;
274
+ const times = target.dataset.times;
275
+ if (times && timesInput) {
276
+ timesInput.value = times;
277
+ timesBtns.forEach((b) => b.classList.remove('active'));
278
+ target.classList.add('active');
279
+ updateResults();
280
+ }
281
+ });
282
+ });
283
+ }
284
+
285
+ function initCalculator(): void {
286
+ const sizeInput = document.getElementById('ctd-size') as HTMLInputElement;
287
+ const timesInput = document.getElementById('ctd-times') as HTMLInputElement;
288
+
289
+ sizeInput?.addEventListener('input', updateResults);
290
+ timesInput?.addEventListener('input', updateResults);
291
+
292
+ setupSpeedButtons();
293
+ setupPresets();
294
+ setupTimesButtons();
295
+
296
+ updateResults();
297
+ }
298
+
299
+ if (document.readyState === 'loading') {
300
+ document.addEventListener('DOMContentLoaded', initCalculator);
301
+ } else {
302
+ initCalculator();
303
+ }
304
+ </script>
305
+
306
+ <style>
307
+ .ctd-wrapper {
308
+ --ctd-primary: #6366f1;
309
+ --ctd-secondary: #8b5cf6;
310
+ --ctd-accent: #ec4899;
311
+
312
+ display: flex;
313
+ align-items: center;
314
+ justify-content: center;
315
+ padding: 2rem 1rem;
316
+ min-height: 100vh;
317
+ }
318
+
319
+ .ctd-card {
320
+ background: white;
321
+ border-radius: 2.5rem;
322
+ padding: 3rem;
323
+ max-width: 700px;
324
+ width: 100%;
325
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.15);
326
+ }
327
+
328
+ :global(.theme-dark) .ctd-card {
329
+ background: rgb(30, 41, 59);
330
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
331
+ }
332
+
333
+ .ctd-header {
334
+ display: flex;
335
+ align-items: center;
336
+ gap: 1.2rem;
337
+ margin-bottom: 3rem;
338
+ }
339
+
340
+ .ctd-icon {
341
+ width: 60px;
342
+ height: 60px;
343
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #ec4899 100%);
344
+ border-radius: 1.2rem;
345
+ display: flex;
346
+ align-items: center;
347
+ justify-content: center;
348
+ color: white;
349
+ box-shadow: 0 10px 25px rgba(99, 102, 241, 0.3);
350
+ flex-shrink: 0;
351
+ }
352
+
353
+ .ctd-icon :global(svg) {
354
+ width: 32px;
355
+ height: 32px;
356
+ }
357
+
358
+ .ctd-header h2 {
359
+ font-size: 2rem;
360
+ font-weight: 900;
361
+ margin: 0;
362
+ color: rgb(15, 23, 42);
363
+ letter-spacing: -0.02em;
364
+ }
365
+
366
+ :global(.theme-dark) .ctd-header h2 {
367
+ color: rgb(241, 245, 249);
368
+ }
369
+
370
+ .ctd-input-section {
371
+ margin-bottom: 2.5rem;
372
+ }
373
+
374
+ .ctd-input-section > label {
375
+ display: block;
376
+ font-size: 0.85rem;
377
+ font-weight: 700;
378
+ color: rgb(100, 116, 139);
379
+ margin-bottom: 0.8rem;
380
+ text-transform: uppercase;
381
+ letter-spacing: 0.05em;
382
+ }
383
+
384
+ :global(.theme-dark) .ctd-input-section > label {
385
+ color: rgb(203, 213, 225);
386
+ }
387
+
388
+ .ctd-input-group {
389
+ display: flex;
390
+ align-items: center;
391
+ gap: 1rem;
392
+ margin-bottom: 2rem;
393
+ }
394
+
395
+ .ctd-input {
396
+ flex: 1;
397
+ background: rgb(248, 250, 252);
398
+ border: 2px solid rgb(226, 232, 240);
399
+ border-radius: 1.3rem;
400
+ padding: 1.2rem 1.5rem;
401
+ font-size: 1.5rem;
402
+ font-weight: 800;
403
+ color: rgb(15, 23, 42);
404
+ outline: none;
405
+ transition: all 0.3s;
406
+ box-sizing: border-box;
407
+ }
408
+
409
+ :global(.theme-dark) .ctd-input {
410
+ background: rgb(15, 23, 42);
411
+ border-color: rgb(51, 65, 85);
412
+ color: rgb(241, 245, 249);
413
+ }
414
+
415
+ .ctd-input:focus {
416
+ border-color: var(--ctd-primary);
417
+ background: white;
418
+ box-shadow: 0 0 0 6px rgba(99, 102, 241, 0.1);
419
+ }
420
+
421
+ :global(.theme-dark) .ctd-input:focus {
422
+ background: rgb(30, 41, 59);
423
+ }
424
+
425
+ .ctd-input-unit {
426
+ font-size: 0.95rem;
427
+ font-weight: 700;
428
+ color: rgb(100, 116, 139);
429
+ white-space: nowrap;
430
+ }
431
+
432
+ :global(.theme-dark) .ctd-input-unit {
433
+ color: rgb(203, 213, 225);
434
+ }
435
+
436
+ .ctd-presets-label {
437
+ font-size: 0.75rem;
438
+ font-weight: 800;
439
+ color: var(--ctd-primary);
440
+ text-transform: uppercase;
441
+ letter-spacing: 0.1em;
442
+ margin-bottom: 1rem;
443
+ }
444
+
445
+ .ctd-presets {
446
+ display: grid;
447
+ grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
448
+ gap: 0.8rem;
449
+ }
450
+
451
+ .ctd-preset {
452
+ background: rgb(241, 245, 249);
453
+ border: 2px solid rgb(226, 232, 240);
454
+ border-radius: 1rem;
455
+ padding: 1rem;
456
+ font-size: 0.8rem;
457
+ font-weight: 700;
458
+ color: rgb(15, 23, 42);
459
+ cursor: pointer;
460
+ transition: all 0.2s;
461
+ display: flex;
462
+ flex-direction: column;
463
+ align-items: center;
464
+ gap: 0.5rem;
465
+ }
466
+
467
+ :global(.theme-dark) .ctd-preset {
468
+ background: rgb(15, 23, 42);
469
+ border-color: rgb(51, 65, 85);
470
+ color: rgb(241, 245, 249);
471
+ }
472
+
473
+ .ctd-preset:hover {
474
+ border-color: var(--ctd-primary);
475
+ background: rgb(219, 234, 254);
476
+ transform: translateY(-3px);
477
+ box-shadow: 0 8px 16px rgba(99, 102, 241, 0.2);
478
+ }
479
+
480
+ :global(.theme-dark) .ctd-preset:hover {
481
+ background: rgba(99, 102, 241, 0.1);
482
+ }
483
+
484
+ .ctd-preset.active {
485
+ background: linear-gradient(135deg, var(--ctd-primary), var(--ctd-secondary));
486
+ color: white;
487
+ border-color: transparent;
488
+ box-shadow: 0 8px 20px rgba(99, 102, 241, 0.3);
489
+ }
490
+
491
+ .ctd-speed-section {
492
+ margin-bottom: 2.5rem;
493
+ }
494
+
495
+ .ctd-speed-section > label {
496
+ display: block;
497
+ font-size: 0.85rem;
498
+ font-weight: 700;
499
+ color: rgb(100, 116, 139);
500
+ margin-bottom: 1rem;
501
+ text-transform: uppercase;
502
+ letter-spacing: 0.05em;
503
+ }
504
+
505
+ :global(.theme-dark) .ctd-speed-section > label {
506
+ color: rgb(203, 213, 225);
507
+ }
508
+
509
+ .ctd-speed-buttons {
510
+ display: grid;
511
+ grid-template-columns: repeat(3, 1fr);
512
+ gap: 1rem;
513
+ }
514
+
515
+ .ctd-speed-btn {
516
+ background: rgb(241, 245, 249);
517
+ border: 2px solid rgb(226, 232, 240);
518
+ border-radius: 1.1rem;
519
+ padding: 1rem;
520
+ display: flex;
521
+ flex-direction: column;
522
+ align-items: center;
523
+ gap: 0.4rem;
524
+ cursor: pointer;
525
+ transition: all 0.3s;
526
+ }
527
+
528
+ :global(.theme-dark) .ctd-speed-btn {
529
+ background: rgb(15, 23, 42);
530
+ border-color: rgb(51, 65, 85);
531
+ }
532
+
533
+ .ctd-speed-label {
534
+ font-size: 0.9rem;
535
+ font-weight: 800;
536
+ color: rgb(15, 23, 42);
537
+ }
538
+
539
+ :global(.theme-dark) .ctd-speed-label {
540
+ color: rgb(241, 245, 249);
541
+ }
542
+
543
+ .ctd-speed-value {
544
+ font-size: 0.75rem;
545
+ font-weight: 700;
546
+ color: rgb(100, 116, 139);
547
+ }
548
+
549
+ :global(.theme-dark) .ctd-speed-value {
550
+ color: rgb(203, 213, 225);
551
+ }
552
+
553
+ .ctd-speed-btn:hover {
554
+ border-color: var(--ctd-primary);
555
+ background: rgb(219, 234, 254);
556
+ transform: translateY(-3px);
557
+ box-shadow: 0 8px 16px rgba(99, 102, 241, 0.2);
558
+ }
559
+
560
+ :global(.theme-dark) .ctd-speed-btn:hover {
561
+ background: rgba(99, 102, 241, 0.1);
562
+ }
563
+
564
+ .ctd-speed-btn.active {
565
+ background: linear-gradient(135deg, var(--ctd-primary), var(--ctd-secondary));
566
+ color: white;
567
+ border-color: transparent;
568
+ box-shadow: 0 8px 20px rgba(99, 102, 241, 0.3);
569
+ }
570
+
571
+ .ctd-speed-btn.active .ctd-speed-label {
572
+ color: white;
573
+ }
574
+
575
+ .ctd-speed-btn.active .ctd-speed-value {
576
+ color: rgba(255, 255, 255, 0.9);
577
+ }
578
+
579
+ .ctd-multiplier-section {
580
+ margin-bottom: 2.5rem;
581
+ }
582
+
583
+ .ctd-multiplier-section > label {
584
+ display: block;
585
+ font-size: 0.85rem;
586
+ font-weight: 700;
587
+ color: rgb(100, 116, 139);
588
+ margin-bottom: 1rem;
589
+ text-transform: uppercase;
590
+ letter-spacing: 0.05em;
591
+ }
592
+
593
+ :global(.theme-dark) .ctd-multiplier-section > label {
594
+ color: rgb(203, 213, 225);
595
+ }
596
+
597
+ .ctd-multiplier-input {
598
+ display: flex;
599
+ gap: 0.6rem;
600
+ flex-wrap: wrap;
601
+ align-items: center;
602
+ }
603
+
604
+ .ctd-times-btn {
605
+ background: rgb(241, 245, 249);
606
+ border: 2px solid rgb(226, 232, 240);
607
+ border-radius: 0.9rem;
608
+ padding: 0.8rem 1.2rem;
609
+ font-size: 0.8rem;
610
+ font-weight: 700;
611
+ color: rgb(15, 23, 42);
612
+ cursor: pointer;
613
+ transition: all 0.2s;
614
+ }
615
+
616
+ :global(.theme-dark) .ctd-times-btn {
617
+ background: rgb(15, 23, 42);
618
+ border-color: rgb(51, 65, 85);
619
+ color: rgb(241, 245, 249);
620
+ }
621
+
622
+ .ctd-times-btn:hover {
623
+ border-color: var(--ctd-primary);
624
+ background: rgb(219, 234, 254);
625
+ }
626
+
627
+ :global(.theme-dark) .ctd-times-btn:hover {
628
+ background: rgba(99, 102, 241, 0.1);
629
+ }
630
+
631
+ .ctd-times-btn.active {
632
+ background: var(--ctd-primary);
633
+ color: white;
634
+ border-color: var(--ctd-primary);
635
+ }
636
+
637
+ .ctd-times-input {
638
+ padding: 0.8rem 1rem;
639
+ border: 2px solid rgb(226, 232, 240);
640
+ border-radius: 0.9rem;
641
+ font-size: 0.8rem;
642
+ font-weight: 700;
643
+ background: rgb(248, 250, 252);
644
+ color: rgb(15, 23, 42);
645
+ outline: none;
646
+ width: 80px;
647
+ transition: all 0.2s;
648
+ box-sizing: border-box;
649
+ }
650
+
651
+ :global(.theme-dark) .ctd-times-input {
652
+ background: rgb(15, 23, 42);
653
+ border-color: rgb(51, 65, 85);
654
+ color: rgb(241, 245, 249);
655
+ }
656
+
657
+ .ctd-times-input:focus {
658
+ border-color: var(--ctd-primary);
659
+ box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1);
660
+ }
661
+
662
+ .ctd-results {
663
+ background: linear-gradient(135deg, rgb(240, 249, 255) 0%, rgb(240, 253, 244) 100%);
664
+ border-radius: 1.5rem;
665
+ padding: 2rem;
666
+ margin-bottom: 2rem;
667
+ display: grid;
668
+ grid-template-columns: 1fr auto 1fr;
669
+ gap: 2rem;
670
+ align-items: center;
671
+ }
672
+
673
+ :global(.theme-dark) .ctd-results {
674
+ background: linear-gradient(135deg, rgba(99, 102, 241, 0.05), rgba(16, 185, 129, 0.05));
675
+ }
676
+
677
+ .ctd-result-single,
678
+ .ctd-result-total {
679
+ display: flex;
680
+ flex-direction: column;
681
+ align-items: center;
682
+ gap: 0.5rem;
683
+ }
684
+
685
+ .ctd-result-label {
686
+ font-size: 0.8rem;
687
+ font-weight: 700;
688
+ color: rgb(100, 116, 139);
689
+ text-transform: uppercase;
690
+ letter-spacing: 0.05em;
691
+ }
692
+
693
+ :global(.theme-dark) .ctd-result-label {
694
+ color: rgb(203, 213, 225);
695
+ }
696
+
697
+ .ctd-result-time,
698
+ .ctd-result-time-total {
699
+ font-size: 2rem;
700
+ font-weight: 900;
701
+ color: var(--ctd-primary);
702
+ line-height: 1;
703
+ }
704
+
705
+ .ctd-result-life,
706
+ .ctd-result-life-total {
707
+ font-size: 0.75rem;
708
+ font-weight: 700;
709
+ color: rgb(100, 116, 139);
710
+ }
711
+
712
+ :global(.theme-dark) .ctd-result-life,
713
+ :global(.theme-dark) .ctd-result-life-total {
714
+ color: rgb(203, 213, 225);
715
+ }
716
+
717
+ .ctd-divider {
718
+ width: 2px;
719
+ height: 100%;
720
+ background: linear-gradient(180deg, transparent, rgb(203, 213, 225), transparent);
721
+ }
722
+
723
+ :global(.theme-dark) .ctd-divider {
724
+ background: linear-gradient(180deg, transparent, rgba(203, 213, 225, 0.2), transparent);
725
+ }
726
+
727
+ .ctd-impact {
728
+ display: grid;
729
+ grid-template-columns: repeat(2, 1fr);
730
+ gap: 1.5rem;
731
+ margin-bottom: 2rem;
732
+ }
733
+
734
+ .ctd-impact-card {
735
+ background: linear-gradient(135deg, rgba(99, 102, 241, 0.1), rgba(255, 140, 0, 0.1));
736
+ border: 2px solid rgba(99, 102, 241, 0.2);
737
+ border-radius: 1.3rem;
738
+ padding: 2rem;
739
+ text-align: center;
740
+ transition: all 0.3s;
741
+ }
742
+
743
+ :global(.theme-dark) .ctd-impact-card {
744
+ background: linear-gradient(135deg, rgba(99, 102, 241, 0.05), rgba(255, 140, 0, 0.05));
745
+ border-color: rgba(99, 102, 241, 0.1);
746
+ }
747
+
748
+ .ctd-impact-card:hover {
749
+ transform: translateY(-4px);
750
+ box-shadow: 0 12px 24px rgba(99, 102, 241, 0.2);
751
+ }
752
+
753
+ .ctd-impact-number {
754
+ font-size: 2.5rem;
755
+ font-weight: 900;
756
+ background: linear-gradient(135deg, var(--ctd-primary), var(--ctd-accent));
757
+ background-clip: text;
758
+ -webkit-background-clip: text;
759
+ -webkit-text-fill-color: transparent;
760
+ line-height: 1;
761
+ margin-bottom: 0.5rem;
762
+ }
763
+
764
+ .ctd-impact-text {
765
+ font-size: 0.85rem;
766
+ font-weight: 700;
767
+ color: rgb(100, 116, 139);
768
+ }
769
+
770
+ :global(.theme-dark) .ctd-impact-text {
771
+ color: rgb(203, 213, 225);
772
+ }
773
+
774
+ .ctd-message {
775
+ background: linear-gradient(135deg, rgb(254, 243, 199) 0%, rgb(254, 202, 202) 100%);
776
+ border: 2px solid rgb(251, 191, 36);
777
+ border-radius: 1.3rem;
778
+ padding: 1.5rem;
779
+ text-align: center;
780
+ font-size: 1rem;
781
+ font-weight: 700;
782
+ color: rgb(146, 64, 14);
783
+ line-height: 1.6;
784
+ }
785
+
786
+ :global(.theme-dark) .ctd-message {
787
+ background: linear-gradient(135deg, rgba(254, 243, 199, 0.1), rgba(254, 202, 202, 0.1));
788
+ border-color: rgba(251, 191, 36, 0.3);
789
+ color: rgb(251, 191, 36);
790
+ }
791
+
792
+ @media (max-width: 768px) {
793
+ .ctd-wrapper {
794
+ padding: 1rem;
795
+ min-height: auto;
796
+ }
797
+
798
+ .ctd-card {
799
+ padding: 1.5rem;
800
+ }
801
+
802
+ .ctd-header {
803
+ margin-bottom: 1.2rem;
804
+ gap: 0.8rem;
805
+ }
806
+
807
+ .ctd-header h2 {
808
+ font-size: 1.3rem;
809
+ }
810
+
811
+ .ctd-results {
812
+ grid-template-columns: 1fr;
813
+ gap: 0.8rem;
814
+ padding: 1rem;
815
+ }
816
+
817
+ .ctd-divider {
818
+ display: none;
819
+ }
820
+
821
+ .ctd-speed-buttons {
822
+ grid-template-columns: 1fr;
823
+ }
824
+
825
+ .ctd-impact {
826
+ grid-template-columns: 1fr;
827
+ }
828
+ }
829
+ </style>