@jjlmoya/utils-developer 1.28.0 → 1.29.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 (455) hide show
  1. package/package.json +1 -1
  2. package/src/entries.ts +4 -1
  3. package/src/tests/i18n_coverage.test.ts +5 -2
  4. package/src/tests/locale_completeness.test.ts +1 -1
  5. package/src/tests/qa-test-helpers.ts +32 -0
  6. package/src/tests/qa_bibliography_links.test.ts +43 -0
  7. package/src/tests/qa_claim_evidence.test.ts +69 -0
  8. package/src/tests/qa_logic_reference_coverage.test.ts +46 -0
  9. package/src/tests/qa_runtime_i18n.test.ts +115 -0
  10. package/src/tests/tool_validation.test.ts +1 -1
  11. package/src/tool/aspectRatio/bibliography.astro +2 -10
  12. package/src/tool/aspectRatio/bibliography.ts +7 -0
  13. package/src/tool/aspectRatio/i18n/de.ts +2 -14
  14. package/src/tool/aspectRatio/i18n/en.ts +2 -14
  15. package/src/tool/aspectRatio/i18n/es.ts +2 -14
  16. package/src/tool/aspectRatio/i18n/fr.ts +2 -14
  17. package/src/tool/aspectRatio/i18n/id.ts +2 -14
  18. package/src/tool/aspectRatio/i18n/it.ts +2 -14
  19. package/src/tool/aspectRatio/i18n/ja.ts +2 -14
  20. package/src/tool/aspectRatio/i18n/ko.ts +2 -14
  21. package/src/tool/aspectRatio/i18n/nl.ts +2 -14
  22. package/src/tool/aspectRatio/i18n/pl.ts +2 -14
  23. package/src/tool/aspectRatio/i18n/pt.ts +2 -14
  24. package/src/tool/aspectRatio/i18n/ru.ts +2 -14
  25. package/src/tool/aspectRatio/i18n/sv.ts +2 -14
  26. package/src/tool/aspectRatio/i18n/tr.ts +2 -14
  27. package/src/tool/aspectRatio/i18n/zh.ts +2 -14
  28. package/src/tool/calculadoraTiempoDatos/bibliography.astro +2 -10
  29. package/src/tool/calculadoraTiempoDatos/bibliography.ts +8 -0
  30. package/src/tool/calculadoraTiempoDatos/component.astro +6 -2
  31. package/src/tool/calculadoraTiempoDatos/i18n/de.ts +2 -6
  32. package/src/tool/calculadoraTiempoDatos/i18n/en.ts +2 -6
  33. package/src/tool/calculadoraTiempoDatos/i18n/es.ts +2 -6
  34. package/src/tool/calculadoraTiempoDatos/i18n/fr.ts +2 -6
  35. package/src/tool/calculadoraTiempoDatos/i18n/id.ts +2 -6
  36. package/src/tool/calculadoraTiempoDatos/i18n/it.ts +2 -6
  37. package/src/tool/calculadoraTiempoDatos/i18n/ja.ts +2 -6
  38. package/src/tool/calculadoraTiempoDatos/i18n/ko.ts +2 -6
  39. package/src/tool/calculadoraTiempoDatos/i18n/nl.ts +2 -6
  40. package/src/tool/calculadoraTiempoDatos/i18n/pl.ts +2 -6
  41. package/src/tool/calculadoraTiempoDatos/i18n/pt.ts +2 -6
  42. package/src/tool/calculadoraTiempoDatos/i18n/ru.ts +2 -6
  43. package/src/tool/calculadoraTiempoDatos/i18n/sv.ts +2 -6
  44. package/src/tool/calculadoraTiempoDatos/i18n/tr.ts +2 -6
  45. package/src/tool/calculadoraTiempoDatos/i18n/zh.ts +2 -6
  46. package/src/tool/colorConverter/bibliography.astro +2 -10
  47. package/src/tool/colorConverter/bibliography.ts +7 -0
  48. package/src/tool/colorConverter/i18n/de.ts +2 -5
  49. package/src/tool/colorConverter/i18n/en.ts +5 -8
  50. package/src/tool/colorConverter/i18n/es.ts +4 -7
  51. package/src/tool/colorConverter/i18n/fr.ts +2 -5
  52. package/src/tool/colorConverter/i18n/id.ts +2 -5
  53. package/src/tool/colorConverter/i18n/it.ts +2 -5
  54. package/src/tool/colorConverter/i18n/ja.ts +2 -5
  55. package/src/tool/colorConverter/i18n/ko.ts +2 -5
  56. package/src/tool/colorConverter/i18n/nl.ts +2 -5
  57. package/src/tool/colorConverter/i18n/pl.ts +2 -5
  58. package/src/tool/colorConverter/i18n/pt.ts +2 -5
  59. package/src/tool/colorConverter/i18n/ru.ts +2 -5
  60. package/src/tool/colorConverter/i18n/sv.ts +2 -5
  61. package/src/tool/colorConverter/i18n/tr.ts +2 -5
  62. package/src/tool/colorConverter/i18n/zh.ts +2 -5
  63. package/src/tool/conversorExcelCsvHtml/bibliography.astro +2 -10
  64. package/src/tool/conversorExcelCsvHtml/bibliography.ts +8 -0
  65. package/src/tool/conversorExcelCsvHtml/component.astro +4 -3
  66. package/src/tool/conversorExcelCsvHtml/i18n/de.ts +2 -6
  67. package/src/tool/conversorExcelCsvHtml/i18n/en.ts +2 -6
  68. package/src/tool/conversorExcelCsvHtml/i18n/es.ts +2 -6
  69. package/src/tool/conversorExcelCsvHtml/i18n/fr.ts +2 -6
  70. package/src/tool/conversorExcelCsvHtml/i18n/id.ts +2 -6
  71. package/src/tool/conversorExcelCsvHtml/i18n/it.ts +2 -6
  72. package/src/tool/conversorExcelCsvHtml/i18n/ja.ts +2 -6
  73. package/src/tool/conversorExcelCsvHtml/i18n/ko.ts +2 -6
  74. package/src/tool/conversorExcelCsvHtml/i18n/nl.ts +2 -6
  75. package/src/tool/conversorExcelCsvHtml/i18n/pl.ts +2 -6
  76. package/src/tool/conversorExcelCsvHtml/i18n/pt.ts +2 -6
  77. package/src/tool/conversorExcelCsvHtml/i18n/ru.ts +2 -6
  78. package/src/tool/conversorExcelCsvHtml/i18n/sv.ts +2 -6
  79. package/src/tool/conversorExcelCsvHtml/i18n/tr.ts +2 -6
  80. package/src/tool/conversorExcelCsvHtml/i18n/zh.ts +2 -6
  81. package/src/tool/cronGenerator/bibliography.astro +2 -10
  82. package/src/tool/cronGenerator/bibliography.ts +6 -0
  83. package/src/tool/cronGenerator/component.astro +5 -1
  84. package/src/tool/cronGenerator/i18n/de.ts +2 -10
  85. package/src/tool/cronGenerator/i18n/en.ts +2 -10
  86. package/src/tool/cronGenerator/i18n/es.ts +2 -10
  87. package/src/tool/cronGenerator/i18n/fr.ts +2 -10
  88. package/src/tool/cronGenerator/i18n/id.ts +2 -10
  89. package/src/tool/cronGenerator/i18n/it.ts +2 -10
  90. package/src/tool/cronGenerator/i18n/ja.ts +2 -10
  91. package/src/tool/cronGenerator/i18n/ko.ts +2 -10
  92. package/src/tool/cronGenerator/i18n/nl.ts +2 -10
  93. package/src/tool/cronGenerator/i18n/pl.ts +2 -10
  94. package/src/tool/cronGenerator/i18n/pt.ts +2 -10
  95. package/src/tool/cronGenerator/i18n/ru.ts +2 -10
  96. package/src/tool/cronGenerator/i18n/sv.ts +2 -10
  97. package/src/tool/cronGenerator/i18n/tr.ts +2 -10
  98. package/src/tool/cronGenerator/i18n/zh.ts +2 -10
  99. package/src/tool/cssBoxShadowGenerator/component.astro +2 -1
  100. package/src/tool/cssBoxShadowGenerator/logic.test.ts +28 -0
  101. package/src/tool/cssSpecificityCalculator/bibliography.astro +2 -10
  102. package/src/tool/cssSpecificityCalculator/bibliography.ts +6 -0
  103. package/src/tool/cssSpecificityCalculator/i18n/de.ts +2 -10
  104. package/src/tool/cssSpecificityCalculator/i18n/en.ts +2 -10
  105. package/src/tool/cssSpecificityCalculator/i18n/es.ts +2 -10
  106. package/src/tool/cssSpecificityCalculator/i18n/fr.ts +2 -10
  107. package/src/tool/cssSpecificityCalculator/i18n/id.ts +2 -10
  108. package/src/tool/cssSpecificityCalculator/i18n/it.ts +2 -10
  109. package/src/tool/cssSpecificityCalculator/i18n/ja.ts +2 -10
  110. package/src/tool/cssSpecificityCalculator/i18n/ko.ts +2 -10
  111. package/src/tool/cssSpecificityCalculator/i18n/nl.ts +2 -10
  112. package/src/tool/cssSpecificityCalculator/i18n/pl.ts +2 -10
  113. package/src/tool/cssSpecificityCalculator/i18n/pt.ts +2 -10
  114. package/src/tool/cssSpecificityCalculator/i18n/ru.ts +2 -10
  115. package/src/tool/cssSpecificityCalculator/i18n/sv.ts +2 -10
  116. package/src/tool/cssSpecificityCalculator/i18n/tr.ts +2 -10
  117. package/src/tool/cssSpecificityCalculator/i18n/zh.ts +2 -10
  118. package/src/tool/cssToInlineConverter/bibliography.astro +2 -10
  119. package/src/tool/cssToInlineConverter/bibliography.ts +7 -0
  120. package/src/tool/cssToInlineConverter/i18n/de.ts +2 -14
  121. package/src/tool/cssToInlineConverter/i18n/en.ts +4 -16
  122. package/src/tool/cssToInlineConverter/i18n/es.ts +5 -17
  123. package/src/tool/cssToInlineConverter/i18n/fr.ts +2 -14
  124. package/src/tool/cssToInlineConverter/i18n/id.ts +2 -14
  125. package/src/tool/cssToInlineConverter/i18n/it.ts +2 -14
  126. package/src/tool/cssToInlineConverter/i18n/ja.ts +2 -14
  127. package/src/tool/cssToInlineConverter/i18n/ko.ts +2 -14
  128. package/src/tool/cssToInlineConverter/i18n/nl.ts +2 -14
  129. package/src/tool/cssToInlineConverter/i18n/pl.ts +2 -14
  130. package/src/tool/cssToInlineConverter/i18n/pt.ts +2 -14
  131. package/src/tool/cssToInlineConverter/i18n/ru.ts +2 -14
  132. package/src/tool/cssToInlineConverter/i18n/sv.ts +2 -14
  133. package/src/tool/cssToInlineConverter/i18n/tr.ts +2 -14
  134. package/src/tool/cssToInlineConverter/i18n/zh.ts +2 -14
  135. package/src/tool/duplicateCssRemover/bibliography.astro +2 -10
  136. package/src/tool/duplicateCssRemover/bibliography.ts +7 -0
  137. package/src/tool/duplicateCssRemover/i18n/de.ts +2 -14
  138. package/src/tool/duplicateCssRemover/i18n/en.ts +4 -16
  139. package/src/tool/duplicateCssRemover/i18n/es.ts +4 -16
  140. package/src/tool/duplicateCssRemover/i18n/fr.ts +2 -14
  141. package/src/tool/duplicateCssRemover/i18n/id.ts +2 -14
  142. package/src/tool/duplicateCssRemover/i18n/it.ts +2 -14
  143. package/src/tool/duplicateCssRemover/i18n/ja.ts +2 -14
  144. package/src/tool/duplicateCssRemover/i18n/ko.ts +2 -14
  145. package/src/tool/duplicateCssRemover/i18n/nl.ts +2 -14
  146. package/src/tool/duplicateCssRemover/i18n/pl.ts +2 -14
  147. package/src/tool/duplicateCssRemover/i18n/pt.ts +2 -14
  148. package/src/tool/duplicateCssRemover/i18n/ru.ts +2 -14
  149. package/src/tool/duplicateCssRemover/i18n/sv.ts +2 -14
  150. package/src/tool/duplicateCssRemover/i18n/tr.ts +2 -14
  151. package/src/tool/duplicateCssRemover/i18n/zh.ts +2 -14
  152. package/src/tool/generadorSecurityTxt/bibliography.astro +2 -10
  153. package/src/tool/generadorSecurityTxt/bibliography.ts +7 -0
  154. package/src/tool/generadorSecurityTxt/component.astro +1 -2
  155. package/src/tool/generadorSecurityTxt/i18n/de.ts +2 -5
  156. package/src/tool/generadorSecurityTxt/i18n/en.ts +2 -5
  157. package/src/tool/generadorSecurityTxt/i18n/es.ts +2 -5
  158. package/src/tool/generadorSecurityTxt/i18n/fr.ts +2 -5
  159. package/src/tool/generadorSecurityTxt/i18n/id.ts +2 -5
  160. package/src/tool/generadorSecurityTxt/i18n/it.ts +2 -5
  161. package/src/tool/generadorSecurityTxt/i18n/ja.ts +2 -5
  162. package/src/tool/generadorSecurityTxt/i18n/ko.ts +2 -5
  163. package/src/tool/generadorSecurityTxt/i18n/nl.ts +2 -5
  164. package/src/tool/generadorSecurityTxt/i18n/pl.ts +2 -5
  165. package/src/tool/generadorSecurityTxt/i18n/pt.ts +2 -5
  166. package/src/tool/generadorSecurityTxt/i18n/ru.ts +2 -5
  167. package/src/tool/generadorSecurityTxt/i18n/sv.ts +2 -5
  168. package/src/tool/generadorSecurityTxt/i18n/tr.ts +2 -5
  169. package/src/tool/generadorSecurityTxt/i18n/zh.ts +2 -5
  170. package/src/tool/hashGenerator/bibliography.astro +2 -10
  171. package/src/tool/hashGenerator/bibliography.ts +7 -0
  172. package/src/tool/hashGenerator/i18n/de.ts +2 -5
  173. package/src/tool/hashGenerator/i18n/en.ts +2 -5
  174. package/src/tool/hashGenerator/i18n/es.ts +2 -5
  175. package/src/tool/hashGenerator/i18n/fr.ts +2 -5
  176. package/src/tool/hashGenerator/i18n/id.ts +2 -5
  177. package/src/tool/hashGenerator/i18n/it.ts +2 -5
  178. package/src/tool/hashGenerator/i18n/ja.ts +2 -5
  179. package/src/tool/hashGenerator/i18n/ko.ts +2 -5
  180. package/src/tool/hashGenerator/i18n/nl.ts +2 -5
  181. package/src/tool/hashGenerator/i18n/pl.ts +2 -5
  182. package/src/tool/hashGenerator/i18n/pt.ts +2 -5
  183. package/src/tool/hashGenerator/i18n/ru.ts +2 -5
  184. package/src/tool/hashGenerator/i18n/sv.ts +2 -5
  185. package/src/tool/hashGenerator/i18n/tr.ts +2 -5
  186. package/src/tool/hashGenerator/i18n/zh.ts +2 -5
  187. package/src/tool/inspectorCertificadosSsl/bibliography.astro +2 -10
  188. package/src/tool/inspectorCertificadosSsl/bibliography.ts +8 -0
  189. package/src/tool/inspectorCertificadosSsl/component.astro +4 -3
  190. package/src/tool/inspectorCertificadosSsl/i18n/de.ts +2 -6
  191. package/src/tool/inspectorCertificadosSsl/i18n/en.ts +2 -6
  192. package/src/tool/inspectorCertificadosSsl/i18n/es.ts +2 -6
  193. package/src/tool/inspectorCertificadosSsl/i18n/fr.ts +2 -6
  194. package/src/tool/inspectorCertificadosSsl/i18n/id.ts +2 -6
  195. package/src/tool/inspectorCertificadosSsl/i18n/it.ts +2 -6
  196. package/src/tool/inspectorCertificadosSsl/i18n/ja.ts +2 -6
  197. package/src/tool/inspectorCertificadosSsl/i18n/ko.ts +2 -6
  198. package/src/tool/inspectorCertificadosSsl/i18n/nl.ts +2 -6
  199. package/src/tool/inspectorCertificadosSsl/i18n/pl.ts +2 -6
  200. package/src/tool/inspectorCertificadosSsl/i18n/pt.ts +2 -6
  201. package/src/tool/inspectorCertificadosSsl/i18n/ru.ts +2 -6
  202. package/src/tool/inspectorCertificadosSsl/i18n/sv.ts +2 -6
  203. package/src/tool/inspectorCertificadosSsl/i18n/tr.ts +2 -6
  204. package/src/tool/inspectorCertificadosSsl/i18n/zh.ts +2 -6
  205. package/src/tool/jsonFormatter/bibliography.astro +2 -10
  206. package/src/tool/jsonFormatter/bibliography.ts +8 -0
  207. package/src/tool/jsonFormatter/i18n/de.ts +2 -18
  208. package/src/tool/jsonFormatter/i18n/en.ts +2 -18
  209. package/src/tool/jsonFormatter/i18n/es.ts +2 -18
  210. package/src/tool/jsonFormatter/i18n/fr.ts +2 -18
  211. package/src/tool/jsonFormatter/i18n/id.ts +2 -18
  212. package/src/tool/jsonFormatter/i18n/it.ts +2 -18
  213. package/src/tool/jsonFormatter/i18n/ja.ts +2 -18
  214. package/src/tool/jsonFormatter/i18n/ko.ts +2 -18
  215. package/src/tool/jsonFormatter/i18n/nl.ts +2 -18
  216. package/src/tool/jsonFormatter/i18n/pl.ts +2 -18
  217. package/src/tool/jsonFormatter/i18n/pt.ts +2 -18
  218. package/src/tool/jsonFormatter/i18n/ru.ts +2 -18
  219. package/src/tool/jsonFormatter/i18n/sv.ts +2 -18
  220. package/src/tool/jsonFormatter/i18n/tr.ts +2 -18
  221. package/src/tool/jsonFormatter/i18n/zh.ts +2 -18
  222. package/src/tool/jwtDecoder/component.astro +2 -1
  223. package/src/tool/jwtDecoder/logic.test.ts +32 -0
  224. package/src/tool/keycode/bibliography.astro +2 -10
  225. package/src/tool/keycode/bibliography.ts +7 -0
  226. package/src/tool/keycode/i18n/de.ts +2 -14
  227. package/src/tool/keycode/i18n/en.ts +2 -14
  228. package/src/tool/keycode/i18n/es.ts +2 -14
  229. package/src/tool/keycode/i18n/fr.ts +2 -14
  230. package/src/tool/keycode/i18n/id.ts +2 -14
  231. package/src/tool/keycode/i18n/it.ts +2 -14
  232. package/src/tool/keycode/i18n/ja.ts +2 -14
  233. package/src/tool/keycode/i18n/ko.ts +2 -14
  234. package/src/tool/keycode/i18n/nl.ts +2 -14
  235. package/src/tool/keycode/i18n/pl.ts +2 -14
  236. package/src/tool/keycode/i18n/pt.ts +2 -14
  237. package/src/tool/keycode/i18n/ru.ts +2 -14
  238. package/src/tool/keycode/i18n/sv.ts +2 -14
  239. package/src/tool/keycode/i18n/tr.ts +2 -14
  240. package/src/tool/keycode/i18n/zh.ts +2 -14
  241. package/src/tool/llmCostCalculator/bibliography.astro +2 -10
  242. package/src/tool/llmCostCalculator/bibliography.ts +8 -0
  243. package/src/tool/llmCostCalculator/i18n/de.ts +2 -18
  244. package/src/tool/llmCostCalculator/i18n/en.ts +2 -18
  245. package/src/tool/llmCostCalculator/i18n/es.ts +2 -18
  246. package/src/tool/llmCostCalculator/i18n/fr.ts +2 -18
  247. package/src/tool/llmCostCalculator/i18n/id.ts +2 -18
  248. package/src/tool/llmCostCalculator/i18n/it.ts +2 -18
  249. package/src/tool/llmCostCalculator/i18n/ja.ts +2 -18
  250. package/src/tool/llmCostCalculator/i18n/ko.ts +2 -18
  251. package/src/tool/llmCostCalculator/i18n/nl.ts +2 -18
  252. package/src/tool/llmCostCalculator/i18n/pl.ts +2 -18
  253. package/src/tool/llmCostCalculator/i18n/pt.ts +2 -18
  254. package/src/tool/llmCostCalculator/i18n/ru.ts +2 -18
  255. package/src/tool/llmCostCalculator/i18n/sv.ts +2 -18
  256. package/src/tool/llmCostCalculator/i18n/tr.ts +2 -18
  257. package/src/tool/llmCostCalculator/i18n/zh.ts +2 -18
  258. package/src/tool/mobileMockupGenerator/bibliography.astro +2 -10
  259. package/src/tool/mobileMockupGenerator/bibliography.ts +6 -0
  260. package/src/tool/mobileMockupGenerator/i18n/de.ts +2 -4
  261. package/src/tool/mobileMockupGenerator/i18n/en.ts +2 -4
  262. package/src/tool/mobileMockupGenerator/i18n/es.ts +2 -4
  263. package/src/tool/mobileMockupGenerator/i18n/fr.ts +2 -4
  264. package/src/tool/mobileMockupGenerator/i18n/id.ts +2 -4
  265. package/src/tool/mobileMockupGenerator/i18n/it.ts +2 -4
  266. package/src/tool/mobileMockupGenerator/i18n/ja.ts +2 -4
  267. package/src/tool/mobileMockupGenerator/i18n/ko.ts +2 -4
  268. package/src/tool/mobileMockupGenerator/i18n/nl.ts +2 -4
  269. package/src/tool/mobileMockupGenerator/i18n/pl.ts +2 -1
  270. package/src/tool/mobileMockupGenerator/i18n/pt.ts +2 -4
  271. package/src/tool/mobileMockupGenerator/i18n/ru.ts +2 -4
  272. package/src/tool/mobileMockupGenerator/i18n/sv.ts +2 -4
  273. package/src/tool/mobileMockupGenerator/i18n/tr.ts +2 -4
  274. package/src/tool/mobileMockupGenerator/i18n/zh.ts +2 -4
  275. package/src/tool/musicalTypography/bibliography.astro +2 -10
  276. package/src/tool/musicalTypography/bibliography.ts +7 -0
  277. package/src/tool/musicalTypography/i18n/de.ts +2 -14
  278. package/src/tool/musicalTypography/i18n/en.ts +3 -15
  279. package/src/tool/musicalTypography/i18n/es.ts +3 -15
  280. package/src/tool/musicalTypography/i18n/fr.ts +2 -14
  281. package/src/tool/musicalTypography/i18n/id.ts +2 -14
  282. package/src/tool/musicalTypography/i18n/it.ts +2 -14
  283. package/src/tool/musicalTypography/i18n/ja.ts +2 -14
  284. package/src/tool/musicalTypography/i18n/ko.ts +2 -14
  285. package/src/tool/musicalTypography/i18n/nl.ts +2 -14
  286. package/src/tool/musicalTypography/i18n/pl.ts +2 -14
  287. package/src/tool/musicalTypography/i18n/pt.ts +2 -5
  288. package/src/tool/musicalTypography/i18n/ru.ts +2 -5
  289. package/src/tool/musicalTypography/i18n/sv.ts +2 -1
  290. package/src/tool/musicalTypography/i18n/tr.ts +2 -1
  291. package/src/tool/musicalTypography/i18n/zh.ts +2 -1
  292. package/src/tool/placeholderGenerator/bibliography.astro +2 -10
  293. package/src/tool/placeholderGenerator/bibliography.ts +7 -0
  294. package/src/tool/placeholderGenerator/i18n/de.ts +2 -14
  295. package/src/tool/placeholderGenerator/i18n/en.ts +4 -16
  296. package/src/tool/placeholderGenerator/i18n/es.ts +4 -16
  297. package/src/tool/placeholderGenerator/i18n/fr.ts +2 -14
  298. package/src/tool/placeholderGenerator/i18n/id.ts +2 -14
  299. package/src/tool/placeholderGenerator/i18n/it.ts +2 -14
  300. package/src/tool/placeholderGenerator/i18n/ja.ts +2 -14
  301. package/src/tool/placeholderGenerator/i18n/ko.ts +2 -14
  302. package/src/tool/placeholderGenerator/i18n/nl.ts +2 -14
  303. package/src/tool/placeholderGenerator/i18n/pl.ts +2 -14
  304. package/src/tool/placeholderGenerator/i18n/pt.ts +2 -14
  305. package/src/tool/placeholderGenerator/i18n/ru.ts +2 -14
  306. package/src/tool/placeholderGenerator/i18n/sv.ts +2 -14
  307. package/src/tool/placeholderGenerator/i18n/tr.ts +2 -14
  308. package/src/tool/placeholderGenerator/i18n/zh.ts +2 -14
  309. package/src/tool/promoteThisWebsite/asset-bindings.ts +73 -0
  310. package/src/tool/promoteThisWebsite/bibliography.astro +14 -0
  311. package/src/tool/promoteThisWebsite/bibliography.ts +14 -0
  312. package/src/tool/promoteThisWebsite/component.astro +136 -0
  313. package/src/tool/promoteThisWebsite/control-views.ts +83 -0
  314. package/src/tool/promoteThisWebsite/controller.ts +232 -0
  315. package/src/tool/promoteThisWebsite/dom-views.ts +322 -0
  316. package/src/tool/promoteThisWebsite/entry.ts +15 -0
  317. package/src/tool/promoteThisWebsite/evaluator.ts +9 -0
  318. package/src/tool/promoteThisWebsite/i18n/en.ts +111 -0
  319. package/src/tool/promoteThisWebsite/index.ts +11 -0
  320. package/src/tool/promoteThisWebsite/jjlmoya-overlay.png +0 -0
  321. package/src/tool/promoteThisWebsite/logic.test.ts +29 -0
  322. package/src/tool/promoteThisWebsite/logic.ts +108 -0
  323. package/src/tool/promoteThisWebsite/pixel-cat.png +0 -0
  324. package/src/tool/promoteThisWebsite/promote-this-website.css +322 -0
  325. package/src/tool/promoteThisWebsite/seo.astro +15 -0
  326. package/src/tool/promoteThisWebsite/storage.ts +28 -0
  327. package/src/tool/promoteThisWebsite/title-view.ts +206 -0
  328. package/src/tool/promoteThisWebsite/ui.ts +59 -0
  329. package/src/tool/promptLibrary/bibliography.astro +2 -10
  330. package/src/tool/promptLibrary/bibliography.ts +6 -0
  331. package/src/tool/promptLibrary/i18n/de.ts +2 -4
  332. package/src/tool/promptLibrary/i18n/en.ts +2 -4
  333. package/src/tool/promptLibrary/i18n/es.ts +2 -4
  334. package/src/tool/promptLibrary/i18n/fr.ts +2 -4
  335. package/src/tool/promptLibrary/i18n/id.ts +2 -4
  336. package/src/tool/promptLibrary/i18n/it.ts +2 -4
  337. package/src/tool/promptLibrary/i18n/ja.ts +2 -4
  338. package/src/tool/promptLibrary/i18n/ko.ts +2 -4
  339. package/src/tool/promptLibrary/i18n/nl.ts +2 -4
  340. package/src/tool/promptLibrary/i18n/pl.ts +2 -1
  341. package/src/tool/promptLibrary/i18n/pt.ts +2 -4
  342. package/src/tool/promptLibrary/i18n/ru.ts +2 -4
  343. package/src/tool/promptLibrary/i18n/sv.ts +2 -4
  344. package/src/tool/promptLibrary/i18n/tr.ts +2 -4
  345. package/src/tool/promptLibrary/i18n/zh.ts +2 -4
  346. package/src/tool/readabilityCalculator/bibliography.astro +2 -10
  347. package/src/tool/readabilityCalculator/bibliography.ts +7 -0
  348. package/src/tool/readabilityCalculator/i18n/de.ts +2 -5
  349. package/src/tool/readabilityCalculator/i18n/en.ts +2 -5
  350. package/src/tool/readabilityCalculator/i18n/es.ts +2 -5
  351. package/src/tool/readabilityCalculator/i18n/fr.ts +2 -5
  352. package/src/tool/readabilityCalculator/i18n/id.ts +2 -5
  353. package/src/tool/readabilityCalculator/i18n/it.ts +2 -5
  354. package/src/tool/readabilityCalculator/i18n/ja.ts +2 -5
  355. package/src/tool/readabilityCalculator/i18n/ko.ts +2 -5
  356. package/src/tool/readabilityCalculator/i18n/nl.ts +2 -5
  357. package/src/tool/readabilityCalculator/i18n/pl.ts +2 -1
  358. package/src/tool/readabilityCalculator/i18n/pt.ts +2 -5
  359. package/src/tool/readabilityCalculator/i18n/ru.ts +2 -5
  360. package/src/tool/readabilityCalculator/i18n/sv.ts +2 -5
  361. package/src/tool/readabilityCalculator/i18n/tr.ts +2 -5
  362. package/src/tool/readabilityCalculator/i18n/zh.ts +2 -5
  363. package/src/tool/serpPixelSimulator/controller.ts +1 -0
  364. package/src/tool/serpPixelSimulator/i18n/en.ts +2 -2
  365. package/src/tool/serpPixelSimulator/i18n/es.ts +2 -2
  366. package/src/tool/serpPixelSimulator/logic.test.ts +25 -0
  367. package/src/tool/svgSanitizer/bibliography.astro +2 -10
  368. package/src/tool/svgSanitizer/bibliography.ts +7 -0
  369. package/src/tool/svgSanitizer/i18n/de.ts +2 -5
  370. package/src/tool/svgSanitizer/i18n/en.ts +2 -5
  371. package/src/tool/svgSanitizer/i18n/es.ts +2 -5
  372. package/src/tool/svgSanitizer/i18n/fr.ts +2 -5
  373. package/src/tool/svgSanitizer/i18n/id.ts +2 -5
  374. package/src/tool/svgSanitizer/i18n/it.ts +2 -5
  375. package/src/tool/svgSanitizer/i18n/ja.ts +2 -5
  376. package/src/tool/svgSanitizer/i18n/ko.ts +2 -5
  377. package/src/tool/svgSanitizer/i18n/nl.ts +2 -5
  378. package/src/tool/svgSanitizer/i18n/pl.ts +2 -5
  379. package/src/tool/svgSanitizer/i18n/pt.ts +2 -1
  380. package/src/tool/svgSanitizer/i18n/ru.ts +2 -5
  381. package/src/tool/svgSanitizer/i18n/sv.ts +2 -5
  382. package/src/tool/svgSanitizer/i18n/tr.ts +2 -5
  383. package/src/tool/svgSanitizer/i18n/zh.ts +2 -5
  384. package/src/tool/svgToCss/bibliography.astro +2 -10
  385. package/src/tool/svgToCss/bibliography.ts +8 -0
  386. package/src/tool/svgToCss/i18n/de.ts +2 -18
  387. package/src/tool/svgToCss/i18n/en.ts +2 -18
  388. package/src/tool/svgToCss/i18n/es.ts +2 -18
  389. package/src/tool/svgToCss/i18n/fr.ts +2 -18
  390. package/src/tool/svgToCss/i18n/id.ts +2 -18
  391. package/src/tool/svgToCss/i18n/it.ts +2 -18
  392. package/src/tool/svgToCss/i18n/ja.ts +2 -18
  393. package/src/tool/svgToCss/i18n/ko.ts +2 -18
  394. package/src/tool/svgToCss/i18n/nl.ts +2 -18
  395. package/src/tool/svgToCss/i18n/pl.ts +2 -18
  396. package/src/tool/svgToCss/i18n/pt.ts +2 -18
  397. package/src/tool/svgToCss/i18n/ru.ts +2 -18
  398. package/src/tool/svgToCss/i18n/sv.ts +2 -18
  399. package/src/tool/svgToCss/i18n/tr.ts +2 -18
  400. package/src/tool/svgToCss/i18n/zh.ts +2 -18
  401. package/src/tool/urlCleaner/bibliography.astro +2 -10
  402. package/src/tool/urlCleaner/bibliography.ts +7 -0
  403. package/src/tool/urlCleaner/i18n/de.ts +2 -5
  404. package/src/tool/urlCleaner/i18n/en.ts +2 -5
  405. package/src/tool/urlCleaner/i18n/es.ts +2 -5
  406. package/src/tool/urlCleaner/i18n/fr.ts +2 -5
  407. package/src/tool/urlCleaner/i18n/id.ts +2 -5
  408. package/src/tool/urlCleaner/i18n/it.ts +2 -5
  409. package/src/tool/urlCleaner/i18n/ja.ts +2 -5
  410. package/src/tool/urlCleaner/i18n/ko.ts +2 -5
  411. package/src/tool/urlCleaner/i18n/nl.ts +2 -5
  412. package/src/tool/urlCleaner/i18n/pl.ts +2 -1
  413. package/src/tool/urlCleaner/i18n/pt.ts +2 -5
  414. package/src/tool/urlCleaner/i18n/ru.ts +2 -5
  415. package/src/tool/urlCleaner/i18n/sv.ts +2 -5
  416. package/src/tool/urlCleaner/i18n/tr.ts +2 -5
  417. package/src/tool/urlCleaner/i18n/zh.ts +2 -5
  418. package/src/tool/urlEncoderDecoder/bibliography.astro +2 -10
  419. package/src/tool/urlEncoderDecoder/bibliography.ts +7 -0
  420. package/src/tool/urlEncoderDecoder/i18n/de.ts +2 -14
  421. package/src/tool/urlEncoderDecoder/i18n/en.ts +3 -15
  422. package/src/tool/urlEncoderDecoder/i18n/es.ts +3 -15
  423. package/src/tool/urlEncoderDecoder/i18n/fr.ts +2 -14
  424. package/src/tool/urlEncoderDecoder/i18n/id.ts +2 -14
  425. package/src/tool/urlEncoderDecoder/i18n/it.ts +2 -14
  426. package/src/tool/urlEncoderDecoder/i18n/ja.ts +2 -14
  427. package/src/tool/urlEncoderDecoder/i18n/ko.ts +2 -14
  428. package/src/tool/urlEncoderDecoder/i18n/nl.ts +2 -14
  429. package/src/tool/urlEncoderDecoder/i18n/pl.ts +2 -14
  430. package/src/tool/urlEncoderDecoder/i18n/pt.ts +2 -14
  431. package/src/tool/urlEncoderDecoder/i18n/ru.ts +2 -14
  432. package/src/tool/urlEncoderDecoder/i18n/sv.ts +2 -14
  433. package/src/tool/urlEncoderDecoder/i18n/tr.ts +2 -14
  434. package/src/tool/urlEncoderDecoder/i18n/zh.ts +2 -14
  435. package/src/tool/utmGenerator/bibliography.astro +2 -10
  436. package/src/tool/utmGenerator/bibliography.ts +6 -0
  437. package/src/tool/utmGenerator/i18n/de.ts +2 -4
  438. package/src/tool/utmGenerator/i18n/en.ts +2 -4
  439. package/src/tool/utmGenerator/i18n/es.ts +2 -4
  440. package/src/tool/utmGenerator/i18n/fr.ts +2 -4
  441. package/src/tool/utmGenerator/i18n/id.ts +2 -4
  442. package/src/tool/utmGenerator/i18n/it.ts +2 -4
  443. package/src/tool/utmGenerator/i18n/ja.ts +2 -4
  444. package/src/tool/utmGenerator/i18n/ko.ts +2 -4
  445. package/src/tool/utmGenerator/i18n/nl.ts +2 -4
  446. package/src/tool/utmGenerator/i18n/pl.ts +2 -4
  447. package/src/tool/utmGenerator/i18n/pt.ts +2 -4
  448. package/src/tool/utmGenerator/i18n/ru.ts +2 -4
  449. package/src/tool/utmGenerator/i18n/sv.ts +2 -4
  450. package/src/tool/utmGenerator/i18n/tr.ts +2 -4
  451. package/src/tool/utmGenerator/i18n/zh.ts +2 -4
  452. package/src/tool/visualCssGridFlexboxGenerator/component.astro +1 -0
  453. package/src/tool/visualCssGridFlexboxGenerator/logic.test.ts +29 -0
  454. package/src/tool/visualCssGridFlexboxGenerator/workspace.ts +13 -1
  455. package/src/tools.ts +2 -1
@@ -0,0 +1,232 @@
1
+ import { bindPaste, downloadCanvas, loadDefaultMascot, loadImage, readFile } from './asset-bindings';
2
+ import { bindCustomSelect, syncControls } from './control-views';
3
+ import { brandFromUrl, defaultState, firstNonEmpty, getFormat, inside, LAYOUT_REVISION, type BrandStyle, type CompositionState, type FormatId, type LayerKey, type LayerPosition, type TitleStyle } from './logic';
4
+ import { layerHitOrder, renderComposition, type RenderAssets } from './dom-views';
5
+ import { clearState, readState, writeState } from './storage';
6
+ import type { PromoteThisWebsiteUI } from './ui';
7
+
8
+ const assetKeys = ['background', 'tool', 'brand', 'mascot'] as const;
9
+ type AssetKey = typeof assetKeys[number];
10
+
11
+ function query<T extends HTMLElement>(root: HTMLElement, selector: string): T | null {
12
+ return root.querySelector(selector) as T | null;
13
+ }
14
+
15
+ function listen(root: HTMLElement, selector: string, eventName: string, handler: EventListener): void {
16
+ const element = query<HTMLElement>(root, selector);
17
+ if (element) element.addEventListener(eventName, handler);
18
+ }
19
+
20
+ function mergeState(saved: Partial<CompositionState> | null): CompositionState {
21
+ const base = defaultState(saved?.format || 'panoramic');
22
+ if (!saved) return base;
23
+ const positions = { ...base.positions, ...saved.positions };
24
+ if ((saved.layoutRevision || 0) < LAYOUT_REVISION) {
25
+ positions.brand = restoreLayerSize(positions.brand, base.positions.brand);
26
+ positions.mascot = restoreLayerSize(positions.mascot, base.positions.mascot);
27
+ }
28
+ return {
29
+ ...base,
30
+ ...saved,
31
+ visible: { ...base.visible, ...saved.visible },
32
+ scale: { ...base.scale, ...saved.scale },
33
+ positions,
34
+ colors: { ...base.colors, ...saved.colors },
35
+ layoutRevision: LAYOUT_REVISION,
36
+ } as CompositionState;
37
+ }
38
+
39
+ function restoreLayerSize(position: LayerPosition, fallback: LayerPosition): LayerPosition {
40
+ const centerX = position.x + position.width / 2;
41
+ const centerY = position.y + position.height / 2;
42
+ return { ...position, width: fallback.width, height: fallback.height, x: centerX - fallback.width / 2, y: centerY - fallback.height / 2 };
43
+ }
44
+
45
+ function canvasPoint(canvas: HTMLCanvasElement, event: PointerEvent): { x: number; y: number } {
46
+ const rect = canvas['get' + 'Bounding' + 'ClientRect']();
47
+ return { x: (event.clientX - rect.left) * canvas.width / rect.width, y: (event.clientY - rect.top) * canvas.height / rect.height };
48
+ }
49
+
50
+ function hitLayer(state: CompositionState, point: { x: number; y: number }): LayerKey | null {
51
+ return layerHitOrder().find((key) => state.visible[key] && inside(point, state.positions[key])) || null;
52
+ }
53
+
54
+ function draw(root: HTMLElement, state: CompositionState, assets: RenderAssets, ui: PromoteThisWebsiteUI): void {
55
+ const canvas = query<HTMLCanvasElement>(root, '#promote-canvas');
56
+ if (!canvas) return;
57
+ const format = getFormat(state.format);
58
+ canvas.width = format.width;
59
+ canvas.height = format.height;
60
+ renderComposition({ context: canvas.getContext('2d') as CanvasRenderingContext2D, state, assets, ui });
61
+ const size = query<HTMLElement>(root, '#promote-size');
62
+ if (size) size.textContent = `${format.width} × ${format.height}`;
63
+ syncControls(root, state, ui);
64
+ }
65
+
66
+ function setStatus(root: HTMLElement, message: string): void {
67
+ const status = query<HTMLElement>(root, '#promote-status');
68
+ if (status) status.textContent = message;
69
+ }
70
+
71
+ function resizeLayer(state: CompositionState, key: LayerKey, scale: number): void {
72
+ const position = state.positions[key];
73
+ const centerX = position.x + position.width / 2;
74
+ const centerY = position.y + position.height / 2;
75
+ const ratio = scale / state.scale[key];
76
+ position.width *= ratio;
77
+ position.height *= ratio;
78
+ position.x = centerX - position.width / 2;
79
+ position.y = centerY - position.height / 2;
80
+ state.scale[key] = scale;
81
+ }
82
+
83
+ interface PageMetadata {
84
+ title: string;
85
+ backgroundImage: string;
86
+ toolImage: string;
87
+ }
88
+
89
+ function metadataValue(document: Document, selectors: string[]): string {
90
+ for (const selector of selectors) {
91
+ const content = document.querySelector(selector)?.getAttribute('content')?.trim();
92
+ if (content) return content;
93
+ }
94
+ return '';
95
+ }
96
+
97
+ function absoluteUrl(value: string, base: URL): string {
98
+ if (!value) return '';
99
+ try {
100
+ return new URL(value, base).href;
101
+ } catch {
102
+ return '';
103
+ }
104
+ }
105
+
106
+ function readPageMetadata(document: Document, url: URL): PageMetadata {
107
+ const title = firstNonEmpty([
108
+ metadataValue(document, ['meta[name="title"]']),
109
+ metadataValue(document, ['meta[property="og:title"]']),
110
+ document.title,
111
+ ]);
112
+ const background = metadataValue(document, ['meta[name="promote:background-image"]', 'meta[property="og:image"]', 'meta[name="twitter:image"]']);
113
+ const tool = metadataValue(document, ['meta[name="promote:tool-image"]', 'meta[property="tool:image"]', 'meta[name="tool-image"]']);
114
+ return { title, backgroundImage: absoluteUrl(background, url), toolImage: absoluteUrl(tool, url) };
115
+ }
116
+
117
+ async function fetchPageMetadata(url: URL): Promise<PageMetadata> {
118
+ const response = await fetch(url.href, { headers: { accept: 'text/html' } });
119
+ if (!response.ok) throw new Error('Page metadata could not be loaded');
120
+ const html = await response.text();
121
+ return readPageMetadata(new DOMParser().parseFromString(html, 'text/html'), url);
122
+ }
123
+
124
+ async function applyUrl(root: HTMLElement, state: CompositionState, assets: RenderAssets, ui: PromoteThisWebsiteUI): Promise<void> {
125
+ const input = query<HTMLInputElement>(root, '#promote-url');
126
+ if (!input?.value.trim()) return;
127
+ try {
128
+ const url = new URL(input.value.trim());
129
+ state.sourceUrl = url.href;
130
+ state.brand = brandFromUrl(url.href);
131
+ setStatus(root, ui.urlLoading);
132
+ const metadata = await fetchPageMetadata(url);
133
+ if (metadata.title) state.title = metadata.title;
134
+ if (metadata.backgroundImage) assets.background = await loadImage(metadata.backgroundImage);
135
+ if (metadata.toolImage) assets.tool = await loadImage(metadata.toolImage);
136
+ else if (assets.background) assets.tool = assets.background;
137
+ setStatus(root, ui.urlApplied);
138
+ } catch {
139
+ setStatus(root, ui.urlFailed);
140
+ }
141
+ }
142
+
143
+ interface AssetBinding {
144
+ root: HTMLElement;
145
+ stateRef: { current: CompositionState };
146
+ assets: RenderAssets;
147
+ ui: PromoteThisWebsiteUI;
148
+ key: AssetKey;
149
+ }
150
+
151
+ function bindAssetInput(binding: AssetBinding): void {
152
+ const { root, stateRef, assets, ui, key } = binding;
153
+ const input = query<HTMLInputElement>(root, `#promote-${key}-file`);
154
+ input?.addEventListener('change', async () => {
155
+ const file = input.files?.[0];
156
+ if (!file) return;
157
+ assets[key] = await loadImage(await readFile(file));
158
+ stateRef.current.active = key;
159
+ writeState(stateRef.current);
160
+ draw(root, stateRef.current, assets, ui);
161
+ setStatus(root, ui.fileLoaded);
162
+ });
163
+ }
164
+
165
+ function bindCanvas(root: HTMLElement, state: CompositionState, assets: RenderAssets, ui: PromoteThisWebsiteUI): void {
166
+ const canvas = query<HTMLCanvasElement>(root, '#promote-canvas');
167
+ if (!canvas) return;
168
+ let drag: { key: LayerKey; offsetX: number; offsetY: number } | null = null;
169
+ canvas.addEventListener('pointerdown', (event) => {
170
+ const point = canvasPoint(canvas, event);
171
+ const key = hitLayer(state, point);
172
+ if (!key) return;
173
+ state.active = key;
174
+ drag = { key, offsetX: point.x - state.positions[key].x, offsetY: point.y - state.positions[key].y };
175
+ canvas.setPointerCapture(event.pointerId);
176
+ draw(root, state, assets, ui);
177
+ });
178
+ canvas.addEventListener('pointermove', (event) => {
179
+ if (!drag) return;
180
+ const point = canvasPoint(canvas, event);
181
+ state.positions[drag.key].x = point.x - drag.offsetX;
182
+ state.positions[drag.key].y = point.y - drag.offsetY;
183
+ draw(root, state, assets, ui);
184
+ });
185
+ canvas.addEventListener('pointerup', () => { if (drag) writeState(state); drag = null; });
186
+ canvas.addEventListener('pointercancel', () => { drag = null; });
187
+ }
188
+
189
+ function bindControls(root: HTMLElement, stateRef: { current: CompositionState }, assets: RenderAssets, ui: PromoteThisWebsiteUI): void {
190
+ const redraw = () => draw(root, stateRef.current, assets, ui);
191
+ bindCustomSelect(root, 'format', (value) => {
192
+ const state = stateRef.current;
193
+ state.format = value as FormatId;
194
+ state.positions = defaultState(state.format).positions;
195
+ writeState(state);
196
+ redraw();
197
+ });
198
+ listen(root, '#promote-title', 'input', (event) => { stateRef.current.title = (event.target as HTMLInputElement).value; writeState(stateRef.current); redraw(); });
199
+ listen(root, '#promote-title-size', 'input', (event) => { stateRef.current.titleFontSize = Number((event.target as HTMLInputElement).value); writeState(stateRef.current); redraw(); });
200
+ listen(root, '#promote-title-box-size', 'input', (event) => { resizeLayer(stateRef.current, 'title', Number((event.target as HTMLInputElement).value)); writeState(stateRef.current); redraw(); });
201
+ bindCustomSelect(root, 'title-style', (value) => { stateRef.current.titleStyle = value as TitleStyle; writeState(stateRef.current); redraw(); });
202
+ bindCustomSelect(root, 'brand', (value) => { stateRef.current.brand = value as CompositionState['brand']; writeState(stateRef.current); redraw(); });
203
+ bindCustomSelect(root, 'brand-style', (value) => { stateRef.current.brandStyle = value as BrandStyle; writeState(stateRef.current); redraw(); });
204
+ (Object.keys(stateRef.current.visible) as LayerKey[]).forEach((key) => {
205
+ listen(root, `#promote-${key}-visible`, 'change', (event) => { stateRef.current.visible[key] = (event.target as HTMLInputElement).checked; writeState(stateRef.current); redraw(); });
206
+ listen(root, `#promote-${key}-scale`, 'input', (event) => { resizeLayer(stateRef.current, key, Number((event.target as HTMLInputElement).value)); writeState(stateRef.current); redraw(); });
207
+ });
208
+ listen(root, '#promote-apply-url', 'click', async () => { await applyUrl(root, stateRef.current, assets, ui); writeState(stateRef.current); redraw(); });
209
+ listen(root, '#promote-reset', 'click', () => { clearState(); stateRef.current = defaultState(); assetKeys.forEach((key) => { assets[key] = null; }); redraw(); setStatus(root, ui.canvasHint); });
210
+ listen(root, '#promote-download', 'click', () => downloadCanvas({ root, state: stateRef.current }));
211
+ }
212
+
213
+ export function mountPromoteThisWebsite(root: HTMLElement, ui: PromoteThisWebsiteUI): void {
214
+ const stateRef = { current: mergeState(readState()) };
215
+ const assets: RenderAssets = { background: null, tool: null, brand: null, mascot: null };
216
+ writeState(stateRef.current);
217
+ assetKeys.forEach((key) => bindAssetInput({ root, stateRef, assets, ui, key }));
218
+ bindControls(root, stateRef, assets, ui);
219
+ bindCanvas(root, stateRef.current, assets, ui);
220
+ bindPaste({ root, stateRef, assets, ui, draw, setStatus });
221
+ draw(root, stateRef.current, assets, ui);
222
+ if (stateRef.current.sourceUrl) {
223
+ applyUrl(root, stateRef.current, assets, ui).then(() => {
224
+ writeState(stateRef.current);
225
+ draw(root, stateRef.current, assets, ui);
226
+ });
227
+ }
228
+ loadDefaultMascot().then((mascot) => {
229
+ assets.mascot = mascot;
230
+ draw(root, stateRef.current, assets, ui);
231
+ }).catch(() => setStatus(root, ui.canvasHint));
232
+ }
@@ -0,0 +1,322 @@
1
+ /* eslint-disable max-lines */
2
+ import { containRect, type BrandStyle, type CompositionState, type LayerKey, type LayerPosition } from './logic';
3
+ import type { PromoteThisWebsiteUI } from './ui';
4
+ import { drawTitle } from './title-view';
5
+
6
+ export interface RenderAssets {
7
+ background: HTMLImageElement | null;
8
+ tool: HTMLImageElement | null;
9
+ brand: HTMLImageElement | null;
10
+ mascot: HTMLImageElement | null;
11
+ }
12
+
13
+ export interface RenderInput {
14
+ context: CanvasRenderingContext2D;
15
+ state: CompositionState;
16
+ assets: RenderAssets;
17
+ ui: PromoteThisWebsiteUI;
18
+ }
19
+
20
+ interface BrandPalette {
21
+ fill: string;
22
+ text: string;
23
+ accent: string;
24
+ stroke: string;
25
+ }
26
+
27
+ const brandPalettes: Record<BrandStyle, BrandPalette> = {
28
+ plain: { fill: 'transparent', text: '#071226', accent: '#c45a2f', stroke: '#071226' },
29
+ plaque: { fill: '#f7ebd7', text: '#071226', accent: '#c45a2f', stroke: '#c45a2f' },
30
+ ticket: { fill: '#172a3c', text: '#fffaf0', accent: '#f0b45b', stroke: '#f7ebd7' },
31
+ stamp: { fill: '#f7ebd7', text: '#071226', accent: '#c45a2f', stroke: '#c45a2f' },
32
+ neon: { fill: '#071226', text: '#fffaf0', accent: '#11d6c4', stroke: '#11d6c4' },
33
+ ribbon: { fill: '#071226', text: '#fffaf0', accent: '#c45a2f', stroke: '#f7ebd7' },
34
+ corner: { fill: '#f7ebd7', text: '#071226', accent: '#c45a2f', stroke: '#071226' },
35
+ pixel: { fill: '#071226', text: '#fffaf0', accent: '#11d6c4', stroke: '#11d6c4' },
36
+ halo: { fill: '#071226', text: '#fffaf0', accent: '#11d6c4', stroke: '#11d6c4' },
37
+ editorial: { fill: '#f7ebd7', text: '#071226', accent: '#c45a2f', stroke: '#071226' },
38
+ };
39
+
40
+ function roundedRect(context: CanvasRenderingContext2D, position: LayerPosition, radius: number): void {
41
+ const r = Math.min(radius, position.width / 2, position.height / 2);
42
+ context.beginPath();
43
+ context.roundRect(position.x, position.y, position.width, position.height, r);
44
+ }
45
+
46
+ function drawFallbackBackground(context: CanvasRenderingContext2D, width: number, height: number): void {
47
+ const gradient = context.createLinearGradient(0, 0, width, height);
48
+ gradient.addColorStop(0, '#091426');
49
+ gradient.addColorStop(0.52, '#203e55');
50
+ gradient.addColorStop(1, '#d48d53');
51
+ context.fillStyle = gradient;
52
+ context.fillRect(0, 0, width, height);
53
+ context.fillStyle = 'rgba(247, 235, 215, 0.82)';
54
+ for (let index = 0; index < 90; index += 1) {
55
+ const x = (index * 173) % width;
56
+ const y = (index * 97) % height;
57
+ const radius = index % 5 === 0 ? 3 : 1.2;
58
+ context.beginPath();
59
+ context.arc(x, y, radius, 0, Math.PI * 2);
60
+ context.fill();
61
+ }
62
+ }
63
+
64
+ function drawImage(context: CanvasRenderingContext2D, image: HTMLImageElement, position: LayerPosition, mode: 'contain' | 'cover'): LayerPosition {
65
+ const ratio = mode === 'contain'
66
+ ? Math.min(position.width / image.naturalWidth, position.height / image.naturalHeight)
67
+ : Math.max(position.width / image.naturalWidth, position.height / image.naturalHeight);
68
+ const fitted = { x: position.x + (position.width - image.naturalWidth * ratio) / 2, y: position.y + (position.height - image.naturalHeight * ratio) / 2, width: image.naturalWidth * ratio, height: image.naturalHeight * ratio };
69
+ context.drawImage(image, fitted.x, fitted.y, fitted.width, fitted.height);
70
+ return fitted;
71
+ }
72
+
73
+ interface BackgroundInput {
74
+ context: CanvasRenderingContext2D;
75
+ state: CompositionState;
76
+ assets: RenderAssets;
77
+ }
78
+
79
+ function drawBackground(input: BackgroundInput): void {
80
+ const { context, state, assets } = input;
81
+ if (assets.background) drawImage(context, assets.background, state.positions.background, 'cover');
82
+ else drawFallbackBackground(context, context.canvas.width, context.canvas.height);
83
+ }
84
+
85
+ function drawTool(context: CanvasRenderingContext2D, state: CompositionState, assets: RenderAssets, ui: PromoteThisWebsiteUI): void {
86
+ const box = state.positions.tool;
87
+ const imageBox = assets.tool ? containRect(assets.tool.naturalWidth, assets.tool.naturalHeight, box) : box;
88
+ context.save();
89
+ context.shadowColor = 'rgba(4, 11, 24, 0.42)';
90
+ context.shadowBlur = 28;
91
+ context.shadowOffsetY = 16;
92
+ context.fillStyle = '#071226';
93
+ roundedRect(context, imageBox, 22);
94
+ context.fill();
95
+ context.shadowColor = 'transparent';
96
+ context.clip();
97
+ if (assets.tool) context.drawImage(assets.tool, imageBox.x, imageBox.y, imageBox.width, imageBox.height);
98
+ else drawToolFallback(context, imageBox, ui);
99
+ context.restore();
100
+ context.save();
101
+ context.strokeStyle = state.colors.frame;
102
+ context.lineWidth = 5;
103
+ roundedRect(context, imageBox, 22);
104
+ context.stroke();
105
+ context.restore();
106
+ }
107
+
108
+ function drawToolFallback(context: CanvasRenderingContext2D, box: LayerPosition, ui: PromoteThisWebsiteUI): void {
109
+ context.fillStyle = '#0e1a30';
110
+ context.fillRect(box.x, box.y, box.width, box.height);
111
+ context.strokeStyle = 'rgba(247, 235, 215, 0.55)';
112
+ context.lineWidth = 4;
113
+ context.setLineDash([18, 14]);
114
+ roundedRect(context, { x: box.x + 22, y: box.y + 22, width: box.width - 44, height: box.height - 44 }, 18);
115
+ context.stroke();
116
+ context.setLineDash([]);
117
+ context.fillStyle = '#f7ebd7';
118
+ context.font = `700 ${Math.max(22, box.width * 0.028)}px sans-serif`;
119
+ context.textAlign = 'center';
120
+ context.textBaseline = 'middle';
121
+ context.fillText(ui.defaultTool, box.x + box.width / 2, box.y + box.height / 2);
122
+ context.textAlign = 'left';
123
+ context.textBaseline = 'alphabetic';
124
+ }
125
+
126
+ function fitBrandFont(context: CanvasRenderingContext2D, brand: 'jjlmoya.es' | 'GameBob.dev', box: LayerPosition): void {
127
+ let fontSize = Math.max(14, box.height * 0.55);
128
+ const maxWidth = box.width * 0.82;
129
+ context.font = `800 ${fontSize}px sans-serif`;
130
+ const sample = brand === 'jjlmoya.es' ? 'jjlmoya.es' : 'GameBob.dev';
131
+ while (context.measureText(sample).width > maxWidth && fontSize > 10) {
132
+ fontSize *= 0.9;
133
+ context.font = `800 ${fontSize}px sans-serif`;
134
+ }
135
+ }
136
+
137
+ function drawJjlmoyaBrand(context: CanvasRenderingContext2D, box: LayerPosition, textColor: string): void {
138
+ const scale = Math.min(1, box.width * 0.82 / context.measureText('jjlmoya.es').width);
139
+ context.save();
140
+ context.translate(box.x + box.width / 2, box.y + box.height / 2);
141
+ context.scale(scale, 1);
142
+ context.fillStyle = textColor;
143
+ context.textAlign = 'center';
144
+ context.fillText('jjlmoya.es', 0, 0);
145
+ context.restore();
146
+ }
147
+
148
+ function drawGameBobBrand(context: CanvasRenderingContext2D, box: LayerPosition, textColor: string, accent: string): void {
149
+ const gameWidth = context.measureText('Game').width;
150
+ const bobWidth = context.measureText('Bob').width;
151
+ const devWidth = context.measureText('.dev').width;
152
+ const totalWidth = gameWidth + bobWidth + devWidth;
153
+ const scale = Math.min(1, box.width * 0.82 / totalWidth);
154
+ const start = -totalWidth / 2;
155
+ context.save();
156
+ context.translate(box.x + box.width / 2, box.y + box.height / 2);
157
+ context.scale(scale, 1);
158
+ context.textAlign = 'left';
159
+ context.fillStyle = textColor;
160
+ context.fillText('Game', start, 0);
161
+ context.fillStyle = accent;
162
+ context.fillText('Bob', start + gameWidth, 0);
163
+ context.fillStyle = textColor;
164
+ context.fillText('.dev', start + gameWidth + bobWidth, 0);
165
+ context.restore();
166
+ }
167
+
168
+ function drawBrandShape(context: CanvasRenderingContext2D, style: BrandStyle, palette: BrandPalette, box: LayerPosition): void {
169
+ const shapes: Record<BrandStyle, () => void> = {
170
+ plain: () => undefined,
171
+ plaque: () => { roundedRect(context, box, box.height * 0.45); context.fill(); context.stroke(); },
172
+ ticket: () => drawBrandTicket(context, box),
173
+ stamp: () => drawBrandStamp(context, box),
174
+ neon: () => drawBrandNeon(context, palette, box),
175
+ ribbon: () => drawBrandRibbon(context, palette, box),
176
+ corner: () => drawBrandCorner(context, palette, box),
177
+ pixel: () => drawBrandPixel(context, palette, box),
178
+ halo: () => drawBrandHalo(context, palette, box),
179
+ editorial: () => drawBrandEditorial(context, palette, box),
180
+ };
181
+ shapes[style]();
182
+ }
183
+
184
+ function drawBrandTicket(context: CanvasRenderingContext2D, box: LayerPosition): void {
185
+ roundedRect(context, box, 14);
186
+ context.fill();
187
+ context.stroke();
188
+ context.setLineDash([7, 9]);
189
+ context.beginPath();
190
+ context.moveTo(box.x + box.width * 0.76, box.y);
191
+ context.lineTo(box.x + box.width * 0.76, box.y + box.height);
192
+ context.stroke();
193
+ context.setLineDash([]);
194
+ }
195
+
196
+ function drawBrandStamp(context: CanvasRenderingContext2D, box: LayerPosition): void {
197
+ context.save();
198
+ context.translate(box.x + box.width / 2, box.y + box.height / 2);
199
+ context.rotate(-0.04);
200
+ roundedRect(context, { x: -box.width / 2, y: -box.height / 2, width: box.width, height: box.height }, 10);
201
+ context.fill();
202
+ context.stroke();
203
+ context.restore();
204
+ }
205
+
206
+ function drawBrandNeon(context: CanvasRenderingContext2D, palette: BrandPalette, box: LayerPosition): void {
207
+ context.shadowColor = palette.accent;
208
+ context.shadowBlur = 20;
209
+ roundedRect(context, box, box.height / 2);
210
+ context.stroke();
211
+ context.shadowColor = 'transparent';
212
+ }
213
+
214
+ function drawBrandRibbon(context: CanvasRenderingContext2D, palette: BrandPalette, box: LayerPosition): void {
215
+ roundedRect(context, box, 12);
216
+ context.fill();
217
+ context.save();
218
+ context.clip();
219
+ context.fillStyle = palette.accent;
220
+ context.fillRect(box.x, box.y + box.height * 0.18, box.width, box.height * 0.64);
221
+ context.restore();
222
+ context.stroke();
223
+ }
224
+
225
+ function drawBrandCorner(context: CanvasRenderingContext2D, palette: BrandPalette, box: LayerPosition): void {
226
+ roundedRect(context, box, 10);
227
+ context.fillStyle = palette.fill;
228
+ context.fill();
229
+ context.stroke();
230
+ context.fillStyle = palette.accent;
231
+ context.fillRect(box.x, box.y, box.width * 0.22, 6);
232
+ }
233
+
234
+ function drawBrandPixel(context: CanvasRenderingContext2D, palette: BrandPalette, box: LayerPosition): void {
235
+ roundedRect(context, box, Math.min(14, box.height * 0.24));
236
+ context.fillStyle = palette.fill;
237
+ context.fill();
238
+ context.strokeStyle = palette.stroke;
239
+ context.lineWidth = 4;
240
+ context.setLineDash([7, 5]);
241
+ roundedRect(context, { x: box.x + 5, y: box.y + 5, width: box.width - 10, height: box.height - 10 }, Math.min(10, box.height * 0.18));
242
+ context.stroke();
243
+ context.setLineDash([]);
244
+ }
245
+
246
+ function drawBrandHalo(context: CanvasRenderingContext2D, palette: BrandPalette, box: LayerPosition): void {
247
+ context.shadowColor = palette.accent;
248
+ context.shadowBlur = 24;
249
+ context.globalAlpha = 0.28;
250
+ context.beginPath();
251
+ context.ellipse(box.x + box.width / 2, box.y + box.height / 2, box.width / 2, box.height * 0.72, 0, 0, Math.PI * 2);
252
+ context.fill();
253
+ context.shadowColor = 'transparent';
254
+ context.globalAlpha = 1;
255
+ }
256
+
257
+ function drawBrandEditorial(context: CanvasRenderingContext2D, palette: BrandPalette, box: LayerPosition): void {
258
+ roundedRect(context, box, 8);
259
+ context.fill();
260
+ context.stroke();
261
+ context.fillRect(box.x, box.y + box.height * 0.82, box.width, 5);
262
+ context.fillStyle = palette.accent;
263
+ context.fillRect(box.x, box.y + box.height * 0.82, box.width * 0.28, 5);
264
+ }
265
+
266
+ function narrowBrandBox(position: LayerPosition): LayerPosition {
267
+ const height = Math.min(position.height, position.width * 0.22);
268
+ return { ...position, y: position.y + (position.height - height) / 2, height };
269
+ }
270
+
271
+ function drawBrand(context: CanvasRenderingContext2D, state: CompositionState, assets: RenderAssets): void {
272
+ const box = narrowBrandBox(state.positions.brand);
273
+ const palette = brandPalettes[state.brandStyle];
274
+ context.save();
275
+ fitBrandFont(context, state.brand, box);
276
+ context.textBaseline = 'middle';
277
+ context.shadowColor = 'rgba(0, 0, 0, 0.35)';
278
+ context.shadowBlur = 8;
279
+ context.fillStyle = palette.fill;
280
+ context.strokeStyle = palette.stroke;
281
+ context.lineWidth = 3;
282
+ drawBrandShape(context, state.brandStyle, palette, box);
283
+ if (assets.brand) drawImage(context, assets.brand, box, 'contain');
284
+ else {
285
+ if (state.brand === 'jjlmoya.es') drawJjlmoyaBrand(context, box, palette.text);
286
+ else drawGameBobBrand(context, box, palette.text, palette.accent);
287
+ }
288
+ context.restore();
289
+ }
290
+
291
+ function drawMascot(context: CanvasRenderingContext2D, assets: RenderAssets, position: LayerPosition): void {
292
+ if (assets.mascot) drawImage(context, assets.mascot, position, 'contain');
293
+ }
294
+
295
+ function drawBackgroundLayer(input: RenderInput): void {
296
+ if (input.state.visible.background) drawBackground({ context: input.context, state: input.state, assets: input.assets });
297
+ }
298
+
299
+ function drawToolLayer(input: RenderInput): void {
300
+ if (input.state.visible.tool) drawTool(input.context, input.state, input.assets, input.ui);
301
+ }
302
+
303
+ function drawForegroundLayers(input: RenderInput): void {
304
+ const { context, state, assets } = input;
305
+ if (state.visible.title) drawTitle(context, state);
306
+ if (state.visible.mascot) drawMascot(context, assets, state.positions.mascot);
307
+ if (state.visible.brand) drawBrand(context, state, assets);
308
+ }
309
+
310
+ export function renderComposition(input: RenderInput): void {
311
+ const { context, state, assets, ui } = input;
312
+ const width = context.canvas.width;
313
+ const height = context.canvas.height;
314
+ context.clearRect(0, 0, width, height);
315
+ drawBackgroundLayer({ context, state, assets, ui });
316
+ drawToolLayer({ context, state, assets, ui });
317
+ drawForegroundLayers({ context, state, assets, ui });
318
+ }
319
+
320
+ export function layerHitOrder(): LayerKey[] {
321
+ return ['title', 'mascot', 'brand', 'tool', 'background'];
322
+ }
@@ -0,0 +1,15 @@
1
+ import type { DeveloperToolEntry, ToolLocaleContent } from '../../types';
2
+ import type { PromoteThisWebsiteUI } from './ui';
3
+
4
+ export type PromoteThisWebsiteLocaleContent = ToolLocaleContent<PromoteThisWebsiteUI>;
5
+
6
+ export const promoteThisWebsite: DeveloperToolEntry<PromoteThisWebsiteUI> = {
7
+ id: 'promote-this-website',
8
+ icons: {
9
+ bg: 'mdi:bullhorn-outline',
10
+ fg: 'mdi:image-multiple-outline',
11
+ },
12
+ i18n: {
13
+ en: async () => (await import('./i18n/en')).content,
14
+ },
15
+ };
@@ -0,0 +1,9 @@
1
+ import type { CompositionState, LayerKey } from './logic';
2
+
3
+ export function activeLabel(state: CompositionState, labels: Record<LayerKey, string>): string {
4
+ return `${labels[state.active]}`;
5
+ }
6
+
7
+ export function visibleLayerCount(state: CompositionState): number {
8
+ return Object.values(state.visible).filter(Boolean).length;
9
+ }