@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,308 @@
1
+ ---
2
+ import type { KnownLocale } from '../../types';
3
+ import type { CssToInlineConverterUI } from './ui';
4
+
5
+ interface Props {
6
+ locale?: KnownLocale;
7
+ ui?: Record<string, unknown>;
8
+ }
9
+
10
+ const { ui } = Astro.props;
11
+ const t = (ui ?? {}) as CssToInlineConverterUI;
12
+ ---
13
+
14
+ <div
15
+ class="cssi-root"
16
+ data-btn-copy={t.btnCopy}
17
+ data-btn-copied={t.btnCopied}
18
+ data-msg-error={t.msgError}
19
+ >
20
+ <div class="cssi-card">
21
+ <div class="cssi-grid">
22
+ <div class="cssi-field">
23
+ <span class="cssi-field-label">{t.labelHtml}</span>
24
+ <textarea
25
+ id="cssi-html-input"
26
+ class="cssi-textarea"
27
+ placeholder={t.placeholderHtml}
28
+ spellcheck="false"
29
+ ></textarea>
30
+ </div>
31
+ <div class="cssi-field">
32
+ <span class="cssi-field-label">{t.labelCss}</span>
33
+ <textarea
34
+ id="cssi-css-input"
35
+ class="cssi-textarea"
36
+ placeholder={t.placeholderCss}
37
+ spellcheck="false"
38
+ ></textarea>
39
+ </div>
40
+ </div>
41
+
42
+ <div class="cssi-actions">
43
+ <button id="cssi-convert-btn" class="cssi-btn">
44
+ <svg class="cssi-btn-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
45
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"></path>
46
+ </svg>
47
+ {t.btnConvert}
48
+ </button>
49
+ </div>
50
+
51
+ <div class="cssi-field">
52
+ <div class="cssi-output-header">
53
+ <span class="cssi-field-label">{t.labelOutput}</span>
54
+ <button id="cssi-copy-btn" class="cssi-copy-btn">{t.btnCopy}</button>
55
+ </div>
56
+ <textarea
57
+ id="cssi-html-output"
58
+ class="cssi-textarea cssi-textarea-output"
59
+ readonly
60
+ placeholder={t.placeholderOutput}
61
+ spellcheck="false"
62
+ ></textarea>
63
+ </div>
64
+ </div>
65
+ </div>
66
+
67
+ <style>
68
+ .cssi-root {
69
+ --cssi-primary: #ec4899;
70
+ --cssi-primary-dark: #db2777;
71
+ --cssi-card-bg: #fff;
72
+ --cssi-bg: #f8fafc;
73
+ --cssi-text: #0f172a;
74
+ --cssi-text-muted: #64748b;
75
+ --cssi-border: #e2e8f0;
76
+ --cssi-output-bg: rgba(236, 72, 153, 0.04);
77
+
78
+ max-width: 1000px;
79
+ margin: 2rem auto;
80
+ color: var(--cssi-text);
81
+ }
82
+
83
+ :global(.theme-dark) .cssi-root {
84
+ --cssi-card-bg: #1e293b;
85
+ --cssi-bg: #0f172a;
86
+ --cssi-text: #f1f5f9;
87
+ --cssi-text-muted: #94a3b8;
88
+ --cssi-border: #334155;
89
+ --cssi-output-bg: rgba(236, 72, 153, 0.08);
90
+ }
91
+
92
+ .cssi-card {
93
+ background: var(--cssi-card-bg);
94
+ border: 1px solid var(--cssi-border);
95
+ border-radius: 1.25rem;
96
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06);
97
+ padding: 1.75rem;
98
+ display: flex;
99
+ flex-direction: column;
100
+ gap: 1.25rem;
101
+ }
102
+
103
+ .cssi-grid {
104
+ display: grid;
105
+ grid-template-columns: 1fr 1fr;
106
+ gap: 1.25rem;
107
+ }
108
+
109
+ @media (max-width: 768px) {
110
+ .cssi-grid {
111
+ grid-template-columns: 1fr;
112
+ }
113
+ }
114
+
115
+ .cssi-field {
116
+ display: flex;
117
+ flex-direction: column;
118
+ gap: 0.5rem;
119
+ }
120
+
121
+ .cssi-output-header {
122
+ display: flex;
123
+ justify-content: space-between;
124
+ align-items: center;
125
+ }
126
+
127
+ .cssi-field-label {
128
+ font-size: 0.8rem;
129
+ font-weight: 700;
130
+ color: var(--cssi-text-muted);
131
+ text-transform: uppercase;
132
+ letter-spacing: 0.06em;
133
+ }
134
+
135
+ .cssi-copy-btn {
136
+ background: transparent;
137
+ border: 1px solid var(--cssi-border);
138
+ color: var(--cssi-text-muted);
139
+ padding: 0.2rem 0.6rem;
140
+ border-radius: 0.4rem;
141
+ font-size: 0.75rem;
142
+ font-weight: 600;
143
+ cursor: pointer;
144
+ transition: all 0.15s;
145
+ }
146
+
147
+ .cssi-copy-btn:hover {
148
+ background: var(--cssi-bg);
149
+ color: var(--cssi-primary);
150
+ border-color: var(--cssi-primary);
151
+ }
152
+
153
+ .cssi-textarea {
154
+ width: 100%;
155
+ height: 220px;
156
+ padding: 1rem 1.25rem;
157
+ font-size: 0.875rem;
158
+ color: var(--cssi-text);
159
+ background: var(--cssi-bg);
160
+ border: 1px solid var(--cssi-border);
161
+ border-radius: 0.875rem;
162
+ resize: vertical;
163
+ transition: border-color 0.2s, box-shadow 0.2s;
164
+ outline: none;
165
+ white-space: pre;
166
+ overflow: auto;
167
+ line-height: 1.6;
168
+ }
169
+
170
+ .cssi-textarea:focus {
171
+ border-color: var(--cssi-primary);
172
+ box-shadow: 0 0 0 3px rgba(236, 72, 153, 0.12);
173
+ }
174
+
175
+ .cssi-textarea-output {
176
+ border-color: var(--cssi-primary);
177
+ background: var(--cssi-output-bg);
178
+ height: 180px;
179
+ }
180
+
181
+ .cssi-textarea-output:focus {
182
+ box-shadow: 0 0 0 3px rgba(236, 72, 153, 0.12);
183
+ }
184
+
185
+ .cssi-actions {
186
+ display: flex;
187
+ justify-content: center;
188
+ }
189
+
190
+ .cssi-btn {
191
+ display: flex;
192
+ align-items: center;
193
+ gap: 0.5rem;
194
+ background: var(--cssi-primary);
195
+ color: #fff;
196
+ font-weight: 700;
197
+ font-size: 0.95rem;
198
+ padding: 0.875rem 2rem;
199
+ border-radius: 0.875rem;
200
+ border: none;
201
+ cursor: pointer;
202
+ transition: transform 0.15s, box-shadow 0.15s, background 0.15s;
203
+ box-shadow: 0 4px 12px rgba(236, 72, 153, 0.3);
204
+ }
205
+
206
+ .cssi-btn:hover {
207
+ background: var(--cssi-primary-dark);
208
+ transform: translateY(-2px);
209
+ box-shadow: 0 6px 16px rgba(236, 72, 153, 0.4);
210
+ }
211
+
212
+ .cssi-btn-icon {
213
+ width: 18px;
214
+ height: 18px;
215
+ }
216
+ </style>
217
+
218
+ <script>
219
+ const root = document.querySelector('.cssi-root') as HTMLElement | null;
220
+ const htmlInput = document.getElementById('cssi-html-input') as HTMLTextAreaElement;
221
+ const cssInput = document.getElementById('cssi-css-input') as HTMLTextAreaElement;
222
+ const htmlOutput = document.getElementById('cssi-html-output') as HTMLTextAreaElement;
223
+ const convertBtn = document.getElementById('cssi-convert-btn');
224
+ const copyBtn = document.getElementById('cssi-copy-btn');
225
+
226
+ const txtCopy = root?.dataset.btnCopy ?? 'Copy';
227
+ const txtCopied = root?.dataset.btnCopied ?? 'Copied!';
228
+ const txtError = root?.dataset.msgError ?? 'Processing error.';
229
+
230
+ function countMatches(text: string, regex: RegExp): number {
231
+ return (text.match(regex) ?? []).length;
232
+ }
233
+
234
+ function getSpecificity(s: string): number {
235
+ let text = s;
236
+ const ids = countMatches(text, /#[\w-]+/g);
237
+ text = text.replace(/#[\w-]+/g, ' ');
238
+ const classes = countMatches(text, /\.[\w-]+/g);
239
+ text = text.replace(/\.[\w-]+/g, ' ');
240
+ const attrs = countMatches(text, /\[[^\]]+\]/g);
241
+ text = text.replace(/\[[^\]]+\]/g, ' ');
242
+ const pElems = countMatches(text, /::[\w-]+/g);
243
+ text = text.replace(/::[\w-]+/g, ' ');
244
+ const pClasses = countMatches(text, /:[\w-()]+/g);
245
+ text = text.replace(/:[\w-()]+/g, ' ');
246
+ const parts = text.replace(/[*+>~,]/g, ' ').trim().split(/\s+/);
247
+ const elems = parts[0] ? parts.length : 0;
248
+ return ids * 100 + (classes + attrs + pClasses) * 10 + (elems + pElems);
249
+ }
250
+
251
+ type CssRule = { selector: string; styles: string; spec: number };
252
+
253
+ function extractRules(rawCss: string): CssRule[] {
254
+ const clean = rawCss.replace(/\/\*[\s\S]*?\*\//g, '');
255
+ const matches = clean.match(/(?:[^{}]+)\s*\{[^}]+\}/g) ?? [];
256
+ const rules: CssRule[] = [];
257
+ for (const block of matches) {
258
+ const braceIdx = block.indexOf('{');
259
+ if (braceIdx === -1) continue;
260
+ const styles = block.slice(braceIdx + 1).replace('}', '').trim();
261
+ const selectors = block.slice(0, braceIdx).split(',').map((s) => s.trim());
262
+ if (!styles) continue;
263
+ for (const sel of selectors) {
264
+ if (sel && !sel.startsWith('@')) rules.push({ selector: sel, styles, spec: getSpecificity(sel) });
265
+ }
266
+ }
267
+ return rules.sort((a, b) => a.spec - b.spec);
268
+ }
269
+
270
+ function applyRule(doc: Document, rule: CssRule): void {
271
+ const nodes = doc.querySelectorAll(rule.selector);
272
+ nodes.forEach((el) => {
273
+ if (!(el instanceof HTMLElement)) return;
274
+ let existing = el.getAttribute('style') ?? '';
275
+ if (existing && !existing.endsWith(';')) existing += '; ';
276
+ el.setAttribute('style', existing + rule.styles);
277
+ });
278
+ }
279
+
280
+ function convertToInline(rawHtml: string, rawCss: string): string {
281
+ const rules = extractRules(rawCss);
282
+ const doc = new DOMParser().parseFromString(rawHtml, 'text/html');
283
+ for (const rule of rules) {
284
+ try { applyRule(doc, rule); } catch { }
285
+ }
286
+ return doc.body.innerHTML;
287
+ }
288
+
289
+ if (convertBtn && htmlInput && cssInput && htmlOutput) {
290
+ convertBtn.addEventListener('click', () => {
291
+ if (!htmlInput.value.trim()) return;
292
+ try {
293
+ htmlOutput.value = convertToInline(htmlInput.value, cssInput.value);
294
+ } catch {
295
+ htmlOutput.value = txtError;
296
+ }
297
+ });
298
+ }
299
+
300
+ if (copyBtn && htmlOutput) {
301
+ copyBtn.addEventListener('click', async () => {
302
+ if (!htmlOutput.value) return;
303
+ await navigator.clipboard.writeText(htmlOutput.value);
304
+ copyBtn.textContent = txtCopied;
305
+ setTimeout(() => { copyBtn.textContent = txtCopy; }, 1500);
306
+ });
307
+ }
308
+ </script>
@@ -0,0 +1,160 @@
1
+ import type { WithContext, FAQPage, HowToThing, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { CssToInlineConverterUI } from '../ui';
4
+
5
+ const slug = 'css-to-inline-converter';
6
+ const title = 'CSS to Inline HTML Converter. Inliner for Emails';
7
+ const description =
8
+ 'Transform your stylesheets and CSS classes into automatically inlined styles in your HTML. Ideal for newsletters, transactional emails, and safe web layout.';
9
+
10
+ const faqData = [
11
+ {
12
+ question: 'Why do emails need inline CSS instead of external stylesheets?',
13
+ answer:
14
+ 'Email clients like Outlook, Gmail, or Apple Mail filter or ignore <link> and <style> tags for security reasons. The only guaranteed way for a style to be applied correctly in an email is to embed it directly in the style attribute of each HTML element.',
15
+ },
16
+ {
17
+ question: 'What happens if an element already has its own style attribute?',
18
+ answer:
19
+ 'The tool respects existing inline styles and appends the new properties, simulating CSS cascade behavior: properties declared later override earlier ones in case of conflict.',
20
+ },
21
+ {
22
+ question: 'Does it work with complex selectors like :hover or media queries?',
23
+ answer:
24
+ 'The tool processes class, id, attribute, and structural pseudo-class selectors that DOMParser can resolve. State-dependent selectors like :hover and media queries cannot be inlined (they depend on runtime environment) and are ignored.',
25
+ },
26
+ {
27
+ question: 'Is my HTML and CSS code sent to any server?',
28
+ answer:
29
+ 'No. All processing happens 100% in your browser using the native DOMParser API. No byte of your code leaves your device, guaranteeing total privacy for templates with sensitive content.',
30
+ },
31
+ ];
32
+
33
+ const howToData = [
34
+ {
35
+ name: 'Paste your original HTML',
36
+ text: 'Write or paste the HTML you want to process in the "Original HTML" field. It can be a fragment or a complete document.',
37
+ },
38
+ {
39
+ name: 'Add your CSS rules',
40
+ text: 'Paste the classes and ids you want to inject in the "CSS Rules" field. The tool applies them respecting selector specificity.',
41
+ },
42
+ {
43
+ name: 'Convert and copy',
44
+ text: 'Click "Inject CSS into HTML". The result with all inline styles will appear below, ready to copy and paste into your email manager or CMS.',
45
+ },
46
+ ];
47
+
48
+ const faqSchema: WithContext<FAQPage> = {
49
+ '@context': 'https://schema.org',
50
+ '@type': 'FAQPage',
51
+ mainEntity: faqData.map((item) => ({
52
+ '@type': 'Question',
53
+ name: item.question,
54
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
55
+ })),
56
+ };
57
+
58
+ const howToSchema: WithContext<HowToThing> = {
59
+ '@context': 'https://schema.org',
60
+ '@type': 'HowTo',
61
+ name: title,
62
+ description,
63
+ step: howToData.map((step, i) => ({
64
+ '@type': 'HowToStep',
65
+ position: i + 1,
66
+ name: step.name,
67
+ text: step.text,
68
+ })),
69
+ };
70
+
71
+ const appSchema: WithContext<SoftwareApplication> = {
72
+ '@context': 'https://schema.org',
73
+ '@type': 'SoftwareApplication',
74
+ name: title,
75
+ description,
76
+ applicationCategory: 'UtilityApplication',
77
+ operatingSystem: 'All',
78
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
79
+ inLanguage: 'en',
80
+ };
81
+
82
+ const ui: CssToInlineConverterUI = {
83
+ labelHtml: 'Original HTML',
84
+ labelCss: 'CSS Rules',
85
+ labelOutput: 'Inline HTML Result',
86
+ placeholderHtml: '<div class="my-class">Hello World</div>',
87
+ placeholderCss: '.my-class { color: red; padding: 10px; }',
88
+ placeholderOutput: 'Your HTML with embedded styles will appear here...',
89
+ btnConvert: 'Inject CSS into HTML',
90
+ btnCopy: 'Copy Code',
91
+ btnCopied: 'Copied!',
92
+ msgError: 'Processing error. Check that your HTML and CSS are valid.',
93
+ };
94
+
95
+ export const content: ToolLocaleContent<CssToInlineConverterUI> = {
96
+ slug,
97
+ title,
98
+ description,
99
+ ui,
100
+ faqTitle: 'Frequently Asked Questions',
101
+ faq: faqData,
102
+ bibliographyTitle: 'References and Documentation',
103
+ bibliography: [
104
+ {
105
+ name: 'Can I email: HTML and CSS Support Matrix for Emails',
106
+ url: 'https://www.caniemail.com/',
107
+ },
108
+ {
109
+ name: 'MDN Web Docs: The global style attribute',
110
+ url: 'https://developer.mozilla.org/en/docs/Web/HTML/Global_attributes/style',
111
+ },
112
+ {
113
+ name: 'DOMParser API: Safe in-browser parsing',
114
+ url: 'https://developer.mozilla.org/en/docs/Web/API/DOMParser',
115
+ },
116
+ ],
117
+ howTo: howToData,
118
+ schemas: [faqSchema, howToSchema, appSchema],
119
+ seo: [
120
+ {
121
+ type: 'title',
122
+ text: 'What Is a CSS Inliner and Why Do You Need One?',
123
+ level: 2,
124
+ },
125
+ {
126
+ type: 'paragraph',
127
+ html: 'When developing modern web applications, we are used to separating concerns: HTML builds the structure, and an external CSS file provides all the visual styling. However, not all environments trust external stylesheets or global <code>&lt;style&gt;</code> tags.',
128
+ },
129
+ {
130
+ type: 'paragraph',
131
+ html: 'The most popular and strict use case where external CSS fails is <strong>Email Template Development</strong>. In these environments, the only reliable way for a font, color, or margin to render correctly is to be nested directly in the tag: <code>&lt;span style="color: red;"&gt;</code>.',
132
+ },
133
+ {
134
+ type: 'title',
135
+ text: 'The CSS Problem in Email Clients',
136
+ level: 3,
137
+ },
138
+ {
139
+ type: 'paragraph',
140
+ html: 'Email clients like Microsoft Outlook, Apple Mail, or Gmail have notorious histories with their restrictive CSS rendering engines. Most filter or discard <code>&lt;link&gt;</code> or <code>&lt;style&gt;</code> tags for fear of code injections that could break the reading interface.',
141
+ },
142
+ {
143
+ type: 'tip',
144
+ html: 'For newsletters or transactional emails (receipts, account confirmations), using tables and <em>inline CSS</em> is the gold standard in the email marketing industry.',
145
+ },
146
+ {
147
+ type: 'title',
148
+ text: 'How This Tool Works in Your Browser',
149
+ level: 3,
150
+ },
151
+ {
152
+ type: 'list',
153
+ items: [
154
+ '<strong>Safe Parsing:</strong> Uses the <code>DOMParser API</code> to temporarily transform the input HTML into a safe virtual DOM inside your browser.',
155
+ '<strong>Cascade Simulation:</strong> Analyzes your CSS rules, applies specificity weights to selectors, and mutates the <code>style</code> attributes of selected HTML elements by injecting the code.',
156
+ '<strong>100% Offline:</strong> No byte of your code leaves your device. Total privacy for templates with sensitive content.',
157
+ ],
158
+ },
159
+ ],
160
+ };
@@ -0,0 +1,160 @@
1
+ import type { WithContext, FAQPage, HowToThing, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { CssToInlineConverterUI } from '../ui';
4
+
5
+ const slug = 'convertidor-css-inline';
6
+ const title = 'Convertidor de CSS Externo a Inline HTML. Inliner para Emails';
7
+ const description =
8
+ 'Transforma tus hojas de estilo y clases CSS a estilos incrustados en línea automáticamente en tu HTML. Ideal para newsletters, emails transaccionales y maquetación web segura.';
9
+
10
+ const faqData = [
11
+ {
12
+ question: '¿Por qué los emails necesitan CSS inline y no externo?',
13
+ answer:
14
+ 'Los clientes de correo como Outlook, Gmail o Apple Mail filtran o ignoran las etiquetas <link> y <style> por razones de seguridad. La única forma garantizada de que un estilo se aplique correctamente en un email es incrustarlo directamente en el atributo style de cada elemento HTML.',
15
+ },
16
+ {
17
+ question: '¿Qué ocurre si un elemento tiene ya un atributo style propio?',
18
+ answer:
19
+ 'La herramienta respeta los estilos inline existentes y añade las nuevas propiedades a continuación, simulando el comportamiento de la cascada CSS: las propiedades declaradas más tarde sobreescriben las anteriores en caso de conflicto.',
20
+ },
21
+ {
22
+ question: '¿Funciona con selectores complejos como :hover o media queries?',
23
+ answer:
24
+ 'La herramienta procesa selectores de clase, id, atributos y pseudo-clases estructurales que DOMParser puede resolver. Los selectores dependientes de estado como :hover y las media queries no se pueden inlinear (ya que dependen del entorno en tiempo de ejecución) y son ignorados.',
25
+ },
26
+ {
27
+ question: '¿Mi código HTML y CSS se envía a algún servidor?',
28
+ answer:
29
+ 'No. Todo el procesamiento ocurre 100% en tu navegador usando la DOMParser API nativa. Ningún byte de tu código sale de tu dispositivo, lo que garantiza privacidad total para plantillas con contenido sensible.',
30
+ },
31
+ ];
32
+
33
+ const howToData = [
34
+ {
35
+ name: 'Pega tu HTML original',
36
+ text: 'Escribe o pega el HTML que deseas procesar en el campo "HTML Original". Puede ser un fragmento o un documento completo.',
37
+ },
38
+ {
39
+ name: 'Añade tus reglas CSS',
40
+ text: 'Pega en el campo "Reglas CSS" las clases e ids que deseas inyectar. La herramienta las aplica respetando la especificidad de los selectores.',
41
+ },
42
+ {
43
+ name: 'Convierte y copia',
44
+ text: 'Haz clic en "Inyectar CSS en el HTML". El resultado con todos los estilos inline aparecerá abajo listo para copiar y pegar en tu gestor de email o CMS.',
45
+ },
46
+ ];
47
+
48
+ const faqSchema: WithContext<FAQPage> = {
49
+ '@context': 'https://schema.org',
50
+ '@type': 'FAQPage',
51
+ mainEntity: faqData.map((item) => ({
52
+ '@type': 'Question',
53
+ name: item.question,
54
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
55
+ })),
56
+ };
57
+
58
+ const howToSchema: WithContext<HowToThing> = {
59
+ '@context': 'https://schema.org',
60
+ '@type': 'HowTo',
61
+ name: title,
62
+ description,
63
+ step: howToData.map((step, i) => ({
64
+ '@type': 'HowToStep',
65
+ position: i + 1,
66
+ name: step.name,
67
+ text: step.text,
68
+ })),
69
+ };
70
+
71
+ const appSchema: WithContext<SoftwareApplication> = {
72
+ '@context': 'https://schema.org',
73
+ '@type': 'SoftwareApplication',
74
+ name: title,
75
+ description,
76
+ applicationCategory: 'UtilityApplication',
77
+ operatingSystem: 'All',
78
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
79
+ inLanguage: 'es',
80
+ };
81
+
82
+ const ui: CssToInlineConverterUI = {
83
+ labelHtml: 'HTML Original',
84
+ labelCss: 'Reglas CSS',
85
+ labelOutput: 'Resultado HTML En Línea',
86
+ placeholderHtml: '<div class="mi-clase">Hola Mundo</div>',
87
+ placeholderCss: '.mi-clase { color: red; padding: 10px; }',
88
+ placeholderOutput: 'Aquí aparecerá tu HTML con los estilos incrustados...',
89
+ btnConvert: 'Inyectar CSS en el HTML',
90
+ btnCopy: 'Copiar Código',
91
+ btnCopied: '¡Copiado!',
92
+ msgError: 'Error al procesar. Comprueba que el HTML y el CSS sean correctos.',
93
+ };
94
+
95
+ export const content: ToolLocaleContent<CssToInlineConverterUI> = {
96
+ slug,
97
+ title,
98
+ description,
99
+ ui,
100
+ faqTitle: 'Preguntas Frecuentes',
101
+ faq: faqData,
102
+ bibliographyTitle: 'Referencias y Documentación',
103
+ bibliography: [
104
+ {
105
+ name: 'Can I email: La matriz de soporte HTML y CSS para Emails',
106
+ url: 'https://www.caniemail.com/',
107
+ },
108
+ {
109
+ name: 'MDN Web Docs: El atributo global de estilo (style)',
110
+ url: 'https://developer.mozilla.org/es/docs/Web/HTML/Global_attributes/style',
111
+ },
112
+ {
113
+ name: 'DOMParser API: Parseo seguro en el navegador',
114
+ url: 'https://developer.mozilla.org/es/docs/Web/API/DOMParser',
115
+ },
116
+ ],
117
+ howTo: howToData,
118
+ schemas: [faqSchema, howToSchema, appSchema],
119
+ seo: [
120
+ {
121
+ type: 'title',
122
+ text: '¿Qué es un CSS Inliner y por qué lo necesitas?',
123
+ level: 2,
124
+ },
125
+ {
126
+ type: 'paragraph',
127
+ html: 'Al desarrollar aplicaciones web modernas, estamos acostumbrados a separar responsabilidades: el HTML construye la estructura, y un archivo CSS externo aporta todo el estilo visual. Sin embargo, no todos los entornos confían en hojas de estilo externas ni etiquetas globales <code>&lt;style&gt;</code>.',
128
+ },
129
+ {
130
+ type: 'paragraph',
131
+ html: 'El caso de uso más popular y estricto donde el CSS externo falla es el <strong>Desarrollo de Plantillas de Email</strong>. En estos contextos, la única manera fiable de que una fuente, color o margen renderice correctamente es que esté anidado directamente en la etiqueta: <code>&lt;span style="color: red;"&gt;</code>.',
132
+ },
133
+ {
134
+ type: 'title',
135
+ text: 'El problema del CSS en correos electrónicos',
136
+ level: 3,
137
+ },
138
+ {
139
+ type: 'paragraph',
140
+ html: 'Los clientes de correo como Microsoft Outlook, Apple Mail o Gmail tienen historiales por sus motores de renderizado CSS restrictivos. La mayoría filtra o descarta etiquetas <code>&lt;link&gt;</code> o <code>&lt;style&gt;</code> por temor a inyecciones de código que puedan romper la interfaz de lectura.',
141
+ },
142
+ {
143
+ type: 'tip',
144
+ html: 'Para newsletters o correos transaccionales (recibos, confirmaciones de cuenta), usar tablas y estilos en línea (<em>inline CSS</em>) es el estándar oro en la industria del email marketing.',
145
+ },
146
+ {
147
+ type: 'title',
148
+ text: 'Cómo funciona esta herramienta en tu navegador',
149
+ level: 3,
150
+ },
151
+ {
152
+ type: 'list',
153
+ items: [
154
+ '<strong>Parseo Seguro:</strong> Utiliza la <code>DOMParser API</code> para transformar temporalmente el HTML de entrada en un DOM virtual seguro dentro de tu navegador.',
155
+ '<strong>Simulación de Cascada:</strong> Analiza tus reglas CSS, aplica pesos de especificidad a los selectores y muta los atributos <code>style</code> de los elementos seleccionados inyectando el código.',
156
+ '<strong>100% Offline:</strong> Ningún byte de tu código sale de tu dispositivo. Privacidad total para plantillas con contenido sensible.',
157
+ ],
158
+ },
159
+ ],
160
+ };