@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,499 @@
1
+ ---
2
+ import type { KnownLocale } from '../../types';
3
+ import type { PlaceholderGeneratorUI } 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 PlaceholderGeneratorUI;
12
+ ---
13
+
14
+ <section class="pg-root">
15
+ <div class="pg-card">
16
+ <div class="pg-body">
17
+ <div class="pg-controls">
18
+ <div class="pg-control-group">
19
+ <label class="pg-label">{t.labelDimensions}</label>
20
+ <div class="pg-presets-row" id="p-presets">
21
+ <button type="button" class="pg-preset-pill" data-w="1920" data-h="1080">FHD</button>
22
+ <button type="button" class="pg-preset-pill" data-w="1280" data-h="720">HD</button>
23
+ <button type="button" class="pg-preset-pill" data-w="1080" data-h="1080">Insta</button>
24
+ <button type="button" class="pg-preset-pill" data-w="1200" data-h="630">OG Image</button>
25
+ <button type="button" class="pg-preset-pill active" data-w="800" data-h="600">800x600</button>
26
+ <button type="button" class="pg-preset-pill" data-w="250" data-h="250">Avatar</button>
27
+ </div>
28
+ </div>
29
+
30
+ <div class="pg-dims-group">
31
+ <div class="pg-control-group">
32
+ <label class="pg-label" for="p-width">{t.labelWidth}</label>
33
+ <input class="pg-input" type="number" id="p-width" value="800" min="1" max="8000" />
34
+ </div>
35
+ <div class="pg-control-group">
36
+ <label class="pg-label" for="p-height">{t.labelHeight}</label>
37
+ <input class="pg-input" type="number" id="p-height" value="600" min="1" max="8000" />
38
+ </div>
39
+ </div>
40
+
41
+ <div class="pg-colors-group">
42
+ <div class="pg-control-group">
43
+ <label class="pg-label" for="p-bg-color">{t.labelBgColor}</label>
44
+ <div class="pg-color-wrapper">
45
+ <input type="color" id="p-bg-color" value="#e2e8f0" />
46
+ <input class="pg-color-text" type="text" id="p-bg-text" value="#e2e8f0" maxlength="7" />
47
+ </div>
48
+ </div>
49
+ <div class="pg-control-group">
50
+ <label class="pg-label" for="p-text-color">{t.labelTextColor}</label>
51
+ <div class="pg-color-wrapper">
52
+ <input type="color" id="p-text-color" value="#64748b" />
53
+ <input class="pg-color-text" type="text" id="p-text-text" value="#64748b" maxlength="7" />
54
+ </div>
55
+ </div>
56
+ </div>
57
+
58
+ <div class="pg-control-group">
59
+ <label class="pg-label" for="p-custom-text">{t.labelCustomText}</label>
60
+ <input class="pg-input" type="text" id="p-custom-text" placeholder={t.placeholderCustomText} />
61
+ </div>
62
+
63
+ <div class="pg-typo-group">
64
+ <div class="pg-control-group">
65
+ <label class="pg-label" for="p-font-family">{t.labelFontFamily}</label>
66
+ <select class="pg-select" id="p-font-family">
67
+ <option value="Inter, sans-serif">Sans-serif (Inter)</option>
68
+ <option value="Georgia, serif">Serif</option>
69
+ <option value="monospace">Monospace</option>
70
+ </select>
71
+ </div>
72
+ <div class="pg-control-group">
73
+ <label class="pg-label" for="p-font-size">{t.labelFontSize}</label>
74
+ <select class="pg-select" id="p-font-size">
75
+ <option value="auto">{t.fontSizeAuto}</option>
76
+ <option value="16">16px</option>
77
+ <option value="24">24px</option>
78
+ <option value="32">32px</option>
79
+ <option value="48">48px</option>
80
+ <option value="64">64px</option>
81
+ <option value="128">128px</option>
82
+ </select>
83
+ </div>
84
+ </div>
85
+
86
+ <div class="pg-control-group">
87
+ <label class="pg-label">{t.labelFormat}</label>
88
+ <div class="pg-presets-row">
89
+ <label class="pg-radio-pill">
90
+ <input type="radio" name="p-format" value="image/webp" checked />
91
+ <span>WebP</span>
92
+ </label>
93
+ <label class="pg-radio-pill">
94
+ <input type="radio" name="p-format" value="image/png" />
95
+ <span>PNG</span>
96
+ </label>
97
+ <label class="pg-radio-pill">
98
+ <input type="radio" name="p-format" value="image/jpeg" />
99
+ <span>JPEG</span>
100
+ </label>
101
+ </div>
102
+ </div>
103
+
104
+ <button id="p-download-btn" class="pg-download-btn">
105
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="currentColor" aria-hidden="true">
106
+ <path d="M12 16L7 11L8.4 9.55L11 12.15V4H13V12.15L15.6 9.55L17 11L12 16ZM6 20C5.45 20 4.97917 19.8042 4.5875 19.4125C4.19583 19.0208 4 18.55 4 18V15H6V18H18V15H20V18C20 18.55 19.8042 19.0208 19.4125 19.4125C19.0208 19.8042 18.55 20 18 20H6Z"></path>
107
+ </svg>
108
+ {t.btnDownload}
109
+ </button>
110
+ </div>
111
+
112
+ <div class="pg-preview">
113
+ <canvas id="p-canvas" style="display: none;"></canvas>
114
+ <img id="p-image-preview" alt="Placeholder preview" />
115
+ </div>
116
+ </div>
117
+ </div>
118
+ </section>
119
+
120
+ <style>
121
+ .pg-root {
122
+ --pg-primary: #eab308;
123
+ --pg-primary-dark: #ca8a04;
124
+ --pg-card-bg: #fff;
125
+ --pg-bg: #f8fafc;
126
+ --pg-text: #1e293b;
127
+ --pg-text-muted: #64748b;
128
+ --pg-border: #e2e8f0;
129
+ --pg-input-bg: #f1f5f9;
130
+ --pg-input-text: #1e293b;
131
+ --pg-btn-bg: #111827;
132
+ --pg-btn-hover: #000;
133
+
134
+ max-width: 1100px;
135
+ margin: 2rem auto;
136
+ color: var(--pg-text);
137
+ }
138
+
139
+ :global(.theme-dark) .pg-root {
140
+ --pg-card-bg: #1e293b;
141
+ --pg-bg: #0f172a;
142
+ --pg-text: #f1f5f9;
143
+ --pg-text-muted: #94a3b8;
144
+ --pg-border: #334155;
145
+ --pg-input-bg: #0f172a;
146
+ --pg-input-text: #e2e8f0;
147
+ --pg-btn-bg: #f1f5f9;
148
+ --pg-btn-hover: #fff;
149
+ }
150
+
151
+ .pg-card {
152
+ background: var(--pg-card-bg);
153
+ border: 1px solid var(--pg-border);
154
+ border-radius: 1.25rem;
155
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06);
156
+ overflow: hidden;
157
+ }
158
+
159
+ .pg-body {
160
+ display: grid;
161
+ grid-template-columns: 300px 1fr;
162
+ min-height: 560px;
163
+ }
164
+
165
+ @media (max-width: 860px) {
166
+ .pg-body {
167
+ grid-template-columns: 1fr;
168
+ }
169
+
170
+ .pg-controls {
171
+ border-right: none;
172
+ border-bottom: 1px solid var(--pg-border);
173
+ }
174
+ }
175
+
176
+ .pg-controls {
177
+ border-right: 1px solid var(--pg-border);
178
+ padding: 1.25rem;
179
+ display: flex;
180
+ flex-direction: column;
181
+ gap: 1rem;
182
+ overflow-y: auto;
183
+ background: var(--pg-bg);
184
+ }
185
+
186
+ .pg-control-group {
187
+ display: flex;
188
+ flex-direction: column;
189
+ gap: 0.375rem;
190
+ }
191
+
192
+ .pg-label {
193
+ font-size: 0.8rem;
194
+ font-weight: 600;
195
+ color: var(--pg-text-muted);
196
+ text-transform: uppercase;
197
+ letter-spacing: 0.05em;
198
+ }
199
+
200
+ .pg-input,
201
+ .pg-select {
202
+ width: 100%;
203
+ padding: 0.6rem 0.875rem;
204
+ border-radius: 0.625rem;
205
+ border: 1px solid var(--pg-border);
206
+ background: var(--pg-card-bg);
207
+ font-size: 0.875rem;
208
+ color: var(--pg-input-text);
209
+ outline: none;
210
+ transition: border-color 0.2s;
211
+ }
212
+
213
+ .pg-input:focus,
214
+ .pg-select:focus {
215
+ border-color: var(--pg-primary);
216
+ box-shadow: 0 0 0 3px rgba(234, 179, 8, 0.15);
217
+ }
218
+
219
+ .pg-dims-group,
220
+ .pg-colors-group,
221
+ .pg-typo-group {
222
+ display: grid;
223
+ grid-template-columns: 1fr 1fr;
224
+ gap: 0.75rem;
225
+ }
226
+
227
+ .pg-presets-row {
228
+ display: flex;
229
+ flex-wrap: wrap;
230
+ gap: 0.375rem;
231
+ }
232
+
233
+ .pg-preset-pill {
234
+ background: var(--pg-input-bg);
235
+ border: 1px solid var(--pg-border);
236
+ color: var(--pg-text-muted);
237
+ padding: 0.3rem 0.6rem;
238
+ border-radius: 50px;
239
+ font-size: 0.75rem;
240
+ font-weight: 600;
241
+ cursor: pointer;
242
+ transition: all 0.2s;
243
+ }
244
+
245
+ .pg-preset-pill:hover,
246
+ :global(.pg-preset-pill.active) {
247
+ background: var(--pg-primary);
248
+ border-color: var(--pg-primary-dark);
249
+ color: #fff;
250
+ }
251
+
252
+ .pg-radio-pill {
253
+ position: relative;
254
+ cursor: pointer;
255
+ }
256
+
257
+ .pg-radio-pill input {
258
+ position: absolute;
259
+ opacity: 0;
260
+ width: 0;
261
+ height: 0;
262
+ }
263
+
264
+ .pg-radio-pill span {
265
+ display: inline-block;
266
+ background: var(--pg-input-bg);
267
+ border: 1px solid var(--pg-border);
268
+ color: var(--pg-text-muted);
269
+ padding: 0.3rem 0.6rem;
270
+ border-radius: 50px;
271
+ font-size: 0.75rem;
272
+ font-weight: 600;
273
+ transition: all 0.2s;
274
+ }
275
+
276
+ .pg-radio-pill input:checked + span {
277
+ background: var(--pg-primary);
278
+ border-color: var(--pg-primary-dark);
279
+ color: #fff;
280
+ }
281
+
282
+ .pg-color-wrapper {
283
+ display: flex;
284
+ align-items: center;
285
+ border: 1px solid var(--pg-border);
286
+ border-radius: 0.625rem;
287
+ overflow: hidden;
288
+ background: var(--pg-card-bg);
289
+ }
290
+
291
+ .pg-color-wrapper input[type="color"] {
292
+ -webkit-appearance: none;
293
+ appearance: none;
294
+ border: none;
295
+ width: 38px;
296
+ height: 38px;
297
+ padding: 0;
298
+ margin: 0;
299
+ cursor: pointer;
300
+ background: transparent;
301
+ flex-shrink: 0;
302
+ }
303
+
304
+ .pg-color-wrapper input[type="color"]::-webkit-color-swatch-wrapper {
305
+ padding: 0;
306
+ }
307
+
308
+ .pg-color-wrapper input[type="color"]::-webkit-color-swatch {
309
+ border: none;
310
+ border-right: 1px solid var(--pg-border);
311
+ }
312
+
313
+ .pg-color-text {
314
+ border: none;
315
+ padding: 0.5rem 0.625rem;
316
+ text-transform: uppercase;
317
+ font-weight: 600;
318
+ font-size: 0.8rem;
319
+ color: var(--pg-input-text);
320
+ background: transparent;
321
+ flex: 1;
322
+ outline: none;
323
+ width: 0;
324
+ }
325
+
326
+ .pg-download-btn {
327
+ background: var(--pg-btn-bg);
328
+ color: var(--pg-card-bg);
329
+ padding: 0.75rem 1.25rem;
330
+ border-radius: 0.75rem;
331
+ border: none;
332
+ font-size: 0.9rem;
333
+ font-weight: 700;
334
+ cursor: pointer;
335
+ display: flex;
336
+ align-items: center;
337
+ justify-content: center;
338
+ gap: 0.5rem;
339
+ transition:
340
+ background 0.2s,
341
+ transform 0.1s;
342
+ margin-top: 0.25rem;
343
+ }
344
+
345
+ .pg-download-btn:hover {
346
+ background: var(--pg-btn-hover);
347
+ }
348
+
349
+ .pg-download-btn:active {
350
+ transform: scale(0.98);
351
+ }
352
+
353
+ .pg-preview {
354
+ display: flex;
355
+ align-items: center;
356
+ justify-content: center;
357
+ padding: 2rem;
358
+ background: var(--pg-bg);
359
+ background-image: radial-gradient(var(--pg-border) 1px, transparent 1px);
360
+ background-size: 24px 24px;
361
+ }
362
+
363
+ #p-image-preview {
364
+ max-width: 100%;
365
+ height: auto;
366
+ object-fit: contain;
367
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);
368
+ border-radius: 8px;
369
+ }
370
+
371
+ canvas {
372
+ display: none;
373
+ }
374
+ </style>
375
+
376
+ <script>
377
+ const widthInput = document.getElementById('p-width') as HTMLInputElement;
378
+ const heightInput = document.getElementById('p-height') as HTMLInputElement;
379
+ const bgColorInput = document.getElementById('p-bg-color') as HTMLInputElement;
380
+ const bgTextInput = document.getElementById('p-bg-text') as HTMLInputElement;
381
+ const textColorInput = document.getElementById('p-text-color') as HTMLInputElement;
382
+ const textTextInput = document.getElementById('p-text-text') as HTMLInputElement;
383
+ const customTextInput = document.getElementById('p-custom-text') as HTMLInputElement;
384
+ const fontFamilyInput = document.getElementById('p-font-family') as HTMLSelectElement;
385
+ const fontSizeInput = document.getElementById('p-font-size') as HTMLSelectElement;
386
+ const canvas = document.getElementById('p-canvas') as HTMLCanvasElement;
387
+ const ctx = canvas.getContext('2d');
388
+ const downloadBtn = document.getElementById('p-download-btn') as HTMLButtonElement;
389
+ const imagePreview = document.getElementById('p-image-preview') as HTMLImageElement;
390
+ const presetBtns = document.querySelectorAll('.pg-preset-pill');
391
+ const formatRadios = document.querySelectorAll('input[name="p-format"]');
392
+
393
+ const getFormat = () => {
394
+ const checked = document.querySelector('input[name="p-format"]:checked') as HTMLInputElement;
395
+ return checked ? checked.value : 'image/webp';
396
+ };
397
+
398
+ const computeFontSize = (w: number, h: number, text: string) => {
399
+ if (fontSizeInput.value !== 'auto') return parseInt(fontSizeInput.value);
400
+ const raw = Math.min((w * 0.9 / text.length) * 1.8, h * 0.9 / 2);
401
+ return Math.max(12, Math.min(raw, 200));
402
+ };
403
+
404
+ const renderCanvas = () => {
405
+ if (!ctx) return;
406
+ const w = parseInt(widthInput.value) || 800;
407
+ const h = parseInt(heightInput.value) || 600;
408
+ canvas.width = w;
409
+ canvas.height = h;
410
+ ctx.fillStyle = bgColorInput.value;
411
+ ctx.fillRect(0, 0, w, h);
412
+ const textStr = customTextInput.value.trim() || `${w} × ${h}`;
413
+ ctx.fillStyle = textColorInput.value;
414
+ ctx.font = `600 ${computeFontSize(w, h, textStr)}px ${fontFamilyInput.value}`;
415
+ ctx.textAlign = 'center';
416
+ ctx.textBaseline = 'middle';
417
+ ctx.fillText(textStr, w / 2, h / 2);
418
+ const format = getFormat();
419
+ const ext = format.split('/')[1] || 'webp';
420
+ const fileName = `placeholder-${w}x${h}.${ext}`;
421
+ imagePreview.src = canvas.toDataURL(format, 0.9);
422
+ imagePreview.title = fileName;
423
+ imagePreview.alt = fileName;
424
+ };
425
+
426
+ const syncColor = (colorInput: HTMLInputElement, textInput: HTMLInputElement, val: string) => {
427
+ colorInput.value = val;
428
+ textInput.value = val;
429
+ renderCanvas();
430
+ };
431
+
432
+ const syncHex = (e: Event, type: 'bg' | 'text') => {
433
+ const target = e.target as HTMLInputElement;
434
+ let val = target.value;
435
+ if (val.length > 0 && val[0] !== '#') val = '#' + val;
436
+ if (/^#[0-9A-F]{6}$/i.test(val)) {
437
+ if (type === 'bg') {
438
+ syncColor(bgColorInput, bgTextInput, val);
439
+ } else {
440
+ syncColor(textColorInput, textTextInput, val);
441
+ }
442
+ }
443
+ };
444
+
445
+ const updateActivePreset = () => {
446
+ presetBtns.forEach((p) => {
447
+ if (p.getAttribute('data-w') === widthInput.value && p.getAttribute('data-h') === heightInput.value) {
448
+ p.classList.add('active');
449
+ } else {
450
+ p.classList.remove('active');
451
+ }
452
+ });
453
+ };
454
+
455
+ const inputs = [widthInput, heightInput, customTextInput, fontFamilyInput, fontSizeInput];
456
+ inputs.forEach((input) => input?.addEventListener('input', renderCanvas));
457
+
458
+ widthInput.addEventListener('input', updateActivePreset);
459
+ heightInput.addEventListener('input', updateActivePreset);
460
+
461
+ presetBtns.forEach((btn) => {
462
+ btn.addEventListener('click', () => {
463
+ widthInput.value = btn.getAttribute('data-w')!;
464
+ heightInput.value = btn.getAttribute('data-h')!;
465
+ updateActivePreset();
466
+ renderCanvas();
467
+ });
468
+ });
469
+
470
+ bgColorInput?.addEventListener('input', (e) => {
471
+ bgTextInput.value = (e.target as HTMLInputElement).value;
472
+ renderCanvas();
473
+ });
474
+
475
+ textColorInput?.addEventListener('input', (e) => {
476
+ textTextInput.value = (e.target as HTMLInputElement).value;
477
+ renderCanvas();
478
+ });
479
+
480
+ bgTextInput?.addEventListener('input', (e) => syncHex(e, 'bg'));
481
+ textTextInput?.addEventListener('input', (e) => syncHex(e, 'text'));
482
+
483
+ downloadBtn?.addEventListener('click', () => {
484
+ const w = parseInt(widthInput.value) || 800;
485
+ const h = parseInt(heightInput.value) || 600;
486
+ const format = getFormat();
487
+ const ext = format.split('/')[1] || 'png';
488
+ const a = document.createElement('a');
489
+ a.href = canvas.toDataURL(format, 0.9);
490
+ a.download = `placeholder-${w}x${h}.${ext}`;
491
+ document.body.appendChild(a);
492
+ a.click();
493
+ document.body.removeChild(a);
494
+ });
495
+
496
+ formatRadios.forEach((radio) => radio.addEventListener('change', renderCanvas));
497
+
498
+ renderCanvas();
499
+ </script>
@@ -0,0 +1,176 @@
1
+ import type { WithContext, FAQPage, HowToThing, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { PlaceholderGeneratorUI } from '../ui';
4
+
5
+ const slug = 'placeholder-image-generator';
6
+ const title = 'Placeholder Image Generator. Rapid Web Mockups Online';
7
+ const description =
8
+ 'Create custom placeholder images for your web designs. Adjust resolution, background, text and export in PNG, WebP or JPEG instantly.';
9
+
10
+ const faqData = [
11
+ {
12
+ question: 'What is a placeholder image?',
13
+ answer:
14
+ 'A placeholder or filler image is a temporary graphic used in web design and layout to reserve space where a definitive image will go. They help visualise the structure of a page before the final content is available.',
15
+ },
16
+ {
17
+ question: 'Can I use any resolution in the generator?',
18
+ answer:
19
+ 'Yes, you can enter any numeric value for width and height. The generator will create a canvas with the exact requested dimensions, perfect for strict grids or specific advertising banners.',
20
+ },
21
+ {
22
+ question: 'In what format are images downloaded?',
23
+ answer:
24
+ 'By default, images are generated in WebP for optimal compression, but you can choose to download them in PNG format to maintain maximum lossless quality, or JPEG for traditional compatibility.',
25
+ },
26
+ {
27
+ question: 'Is this processed on any server?',
28
+ answer:
29
+ 'No, all image rendering is generated instantly in the virtual memory (Canvas) of your own browser. It is instant and does not require sending data over the network.',
30
+ },
31
+ ];
32
+
33
+ const howToData = [
34
+ {
35
+ name: 'Set dimensions',
36
+ text: 'Enter width and height values directly or click on one of the presets (FHD, HD, Instagram, etc.) to fill them in automatically.',
37
+ },
38
+ {
39
+ name: 'Configure colours and text',
40
+ text: 'Choose background and text colours using the native pickers or by typing a hexadecimal code. Optionally, add custom text to replace the default dimension label.',
41
+ },
42
+ {
43
+ name: 'Select typography and format',
44
+ text: 'Choose the font family and size. Select the output format (WebP, PNG or JPEG) according to your needs.',
45
+ },
46
+ {
47
+ name: 'Download the image',
48
+ text: 'Click the Download button to save the generated placeholder directly to your device.',
49
+ },
50
+ ];
51
+
52
+ const faqSchema: WithContext<FAQPage> = {
53
+ '@context': 'https://schema.org',
54
+ '@type': 'FAQPage',
55
+ mainEntity: faqData.map((item) => ({
56
+ '@type': 'Question',
57
+ name: item.question,
58
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
59
+ })),
60
+ };
61
+
62
+ const howToSchema: WithContext<HowToThing> = {
63
+ '@context': 'https://schema.org',
64
+ '@type': 'HowTo',
65
+ name: title,
66
+ description,
67
+ step: howToData.map((step, i) => ({
68
+ '@type': 'HowToStep',
69
+ position: i + 1,
70
+ name: step.name,
71
+ text: step.text,
72
+ })),
73
+ };
74
+
75
+ const appSchema: WithContext<SoftwareApplication> = {
76
+ '@context': 'https://schema.org',
77
+ '@type': 'SoftwareApplication',
78
+ name: title,
79
+ description,
80
+ applicationCategory: 'UtilityApplication',
81
+ operatingSystem: 'All',
82
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
83
+ inLanguage: 'en',
84
+ };
85
+
86
+ const ui: PlaceholderGeneratorUI = {
87
+ labelDimensions: 'Quick Dimensions',
88
+ labelWidth: 'Width (px)',
89
+ labelHeight: 'Height (px)',
90
+ labelBgColor: 'Background',
91
+ labelTextColor: 'Text',
92
+ labelCustomText: 'Custom Text (Optional)',
93
+ placeholderCustomText: 'E.g.: Hero Banner',
94
+ labelFontFamily: 'Typography',
95
+ labelFontSize: 'Base Size',
96
+ fontSizeAuto: 'Automatic',
97
+ labelFormat: 'Output Format',
98
+ btnDownload: 'Download Image',
99
+ };
100
+
101
+ export const content: ToolLocaleContent<PlaceholderGeneratorUI> = {
102
+ slug,
103
+ title,
104
+ description,
105
+ ui,
106
+ faqTitle: 'Frequently Asked Questions',
107
+ faq: faqData,
108
+ bibliographyTitle: 'References and Documentation',
109
+ bibliography: [
110
+ {
111
+ name: 'MDN Web Docs: HTMLCanvasElement.toDataURL()',
112
+ url: 'https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL',
113
+ },
114
+ {
115
+ name: 'MDN Web Docs: CanvasRenderingContext2D',
116
+ url: 'https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D',
117
+ },
118
+ {
119
+ name: 'W3C: HTML Canvas 2D Context',
120
+ url: 'https://www.w3.org/TR/2dcontext/',
121
+ },
122
+ ],
123
+ howTo: howToData,
124
+ schemas: [faqSchema, howToSchema, appSchema],
125
+ seo: [
126
+ {
127
+ type: 'title',
128
+ text: 'The Ultimate Tool for Rapid Mockups',
129
+ level: 2,
130
+ },
131
+ {
132
+ type: 'paragraph',
133
+ html: 'During the early stages of conceptualisation or structural layout of a website (wireframing), we rarely have the final photographic content. Empty dimensions can distort the global visualisation of the product and hide critical spacing or proportion errors. A placeholder image generator instantly solves this, allowing you to inject precisely dimensioned coloured areas.',
134
+ },
135
+ {
136
+ type: 'tip',
137
+ title: 'Frictionless Design',
138
+ html: 'Integrating a space of exactly 1200x800 pixels is imperative when building CSS Grids. By downloading fill blocks you avoid injecting extra CSS or third-party scripts into your temporary code.',
139
+ },
140
+ {
141
+ type: 'title',
142
+ text: 'The Importance of Avoiding External Services',
143
+ level: 3,
144
+ },
145
+ {
146
+ type: 'paragraph',
147
+ html: 'A recurring practice in the frontend ecosystem consists of linking services like <code>via.placeholder.com</code> or similar directly in the <code>src</code> attributes of HTML. While theoretically agile through URL parameters, it has profound side effects that a meticulous developer would avoid at all costs.',
148
+ },
149
+ {
150
+ type: 'paragraph',
151
+ html: 'Inserting a remote domain in each image tag of a development page increases DNS requests, penalises hot-reloading systems of Vite, Gulp or Webpack and accidentally exposes traces in branches that eventually end up in the cloud. By using this utility and generating the image in your preferred format (WebP, PNG or JPEG), you centralise your prototype completely in localhost mode.',
152
+ },
153
+ {
154
+ type: 'title',
155
+ text: 'Key Features of the Generator Algorithm',
156
+ level: 3,
157
+ },
158
+ {
159
+ type: 'list',
160
+ items: [
161
+ '<strong>Pixel Perfect Resolution:</strong> Native HTML5 Canvas ensures the exported canvas corresponds arithmetically to the coordinates stipulated by the user.',
162
+ '<strong>Typographic Autoscaling:</strong> In Automatic mode, the font size calculates the perimeter area and number of characters to elegantly fit the text without causing unwanted <em>overflows</em>.',
163
+ '<strong>Hexadecimal Fusion:</strong> Bidirectional state control between native HTML ecosystem selectors and typed inputs, guaranteeing precise contrasts dictated by your Figma or Penpot UI/UX palette.',
164
+ ],
165
+ },
166
+ {
167
+ type: 'title',
168
+ text: 'The Hidden Art of Technical Wireframing',
169
+ level: 3,
170
+ },
171
+ {
172
+ type: 'paragraph',
173
+ html: 'There is no monolithic project or micro frontend that does not pass through foundation stages, especially when facing demanding clients or Product Managers with an iron vision. Facilitating agile graphical iterations without dragging the overhead of finalised <em>assets</em> separates the fast developer from the stuck one. This generator directly exploits the modern JS <code>toDataURL()</code> API on your local machine to deliver an industry-level result without dubious intermediate processing.',
174
+ },
175
+ ],
176
+ };