@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,534 @@
1
+ ---
2
+ import type { ReadabilityCalculatorUI } from './ui';
3
+
4
+ interface Props {
5
+ ui?: Record<string, unknown>;
6
+ }
7
+
8
+ const { ui } = Astro.props;
9
+ const t = (ui ?? {}) as ReadabilityCalculatorUI;
10
+ ---
11
+
12
+ <div id="rc-root" class="rc-root" data-i18n={JSON.stringify(t)}>
13
+ <div class="rc-container">
14
+ <div class="rc-card rc-left">
15
+ <div class="rc-control-section">
16
+ <div class="rc-group-title">{t.labelColors}</div>
17
+ <div class="rc-inputs-grid">
18
+ <div class="rc-input-wrapper">
19
+ <label class="rc-label-mini">{t.labelText}</label>
20
+ <div class="rc-color-input-container">
21
+ <input type="color" id="rc-text-color" class="rc-color-picker" value="#1e293b" />
22
+ <span id="rc-text-hex" class="rc-hex-text">#1E293B</span>
23
+ </div>
24
+ </div>
25
+ <div class="rc-input-wrapper">
26
+ <label class="rc-label-mini">{t.labelBg}</label>
27
+ <div class="rc-color-input-container">
28
+ <input type="color" id="rc-bg-color" class="rc-color-picker" value="#ffffff" />
29
+ <span id="rc-bg-hex" class="rc-hex-text">#FFFFFF</span>
30
+ </div>
31
+ </div>
32
+ </div>
33
+
34
+ <div class="rc-group-title">{t.labelTypo}</div>
35
+ <div class="rc-sliders-stack">
36
+ <div class="rc-slider-group">
37
+ <div class="rc-slider-header">
38
+ <label class="rc-label-mini">{t.labelFontSize}</label>
39
+ <span id="rc-size-val" class="rc-slider-value">16px</span>
40
+ </div>
41
+ <input type="range" id="rc-size-slider" class="rc-range" min="12" max="72" value="16" />
42
+ </div>
43
+ <div class="rc-slider-group">
44
+ <div class="rc-slider-header">
45
+ <label class="rc-label-mini">{t.labelFontWeight}</label>
46
+ <span id="rc-weight-val" class="rc-slider-value">400</span>
47
+ </div>
48
+ <input type="range" id="rc-weight-slider" class="rc-range" min="100" max="900" step="100" value="400" />
49
+ </div>
50
+ </div>
51
+
52
+ <div class="rc-group-title">{t.labelCompare}</div>
53
+ <div class="rc-comparison-grid">
54
+ <div class="rc-score-card">
55
+ <span class="rc-score-label">{t.wcagRatioLabel}</span>
56
+ <span id="rc-wcag-ratio" class="rc-score-value">21.00</span>
57
+ <span id="rc-wcag-status" class="rc-score-status rc-status-pass">{t.wcagAAA}</span>
58
+ </div>
59
+ <div class="rc-score-card">
60
+ <span class="rc-score-label">{t.apcaLcLabel}</span>
61
+ <span id="rc-apca-lc" class="rc-score-value">100</span>
62
+ <span id="rc-apca-status" class="rc-score-status rc-status-pass">{t.apcaExcellent}</span>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </div>
67
+
68
+ <div class="rc-card rc-right">
69
+ <div class="rc-preview-container">
70
+ <div class="rc-group-title">{t.labelPreview}</div>
71
+ <div id="rc-preview-box" class="rc-main-preview-box">
72
+ <p id="rc-preview-text" class="rc-preview-text">{t.previewText}</p>
73
+ </div>
74
+
75
+ <div class="rc-apca-breakdown">
76
+ <div class="rc-group-title rc-group-title-sm">{t.labelApcaRecs}</div>
77
+ <div class="rc-breakdown-list">
78
+ <div class="rc-breakdown-item">
79
+ <span class="rc-item-label">{t.labelBody}</span>
80
+ <span id="rc-apca-body" class="rc-item-check rc-check-yes"></span>
81
+ </div>
82
+ <div class="rc-breakdown-item">
83
+ <span class="rc-item-label">{t.labelSmall}</span>
84
+ <span id="rc-apca-small" class="rc-item-check rc-check-yes"></span>
85
+ </div>
86
+ <div class="rc-breakdown-item">
87
+ <span class="rc-item-label">{t.labelUi}</span>
88
+ <span id="rc-apca-ui" class="rc-item-check rc-check-yes"></span>
89
+ </div>
90
+ </div>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ </div>
96
+
97
+ <style>
98
+ .rc-root {
99
+ width: 100%;
100
+ }
101
+
102
+ .rc-container {
103
+ display: grid;
104
+ grid-template-columns: 1fr;
105
+ gap: 2rem;
106
+ max-width: 1200px;
107
+ margin: 0 auto;
108
+ padding: 1.5rem;
109
+ }
110
+
111
+ @media (min-width: 1024px) {
112
+ .rc-container {
113
+ grid-template-columns: 1fr 1fr;
114
+ }
115
+ }
116
+
117
+ .rc-card {
118
+ background: rgba(255, 255, 255, 0.7);
119
+ backdrop-filter: blur(16px);
120
+ -webkit-backdrop-filter: blur(16px);
121
+ border: 1px solid rgba(255, 255, 255, 0.4);
122
+ border-radius: 1.5rem;
123
+ padding: 2.5rem;
124
+ display: flex;
125
+ flex-direction: column;
126
+ gap: 2rem;
127
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 10px 15px -3px rgba(0, 0, 0, 0.05), inset 0 0 0 1px rgba(255, 255, 255, 0.5);
128
+ }
129
+
130
+ :global(.theme-dark) .rc-card {
131
+ background: rgba(15, 23, 42, 0.6);
132
+ border: 1px solid rgba(255, 255, 255, 0.08);
133
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.2);
134
+ }
135
+
136
+ .rc-control-section {
137
+ display: flex;
138
+ flex-direction: column;
139
+ gap: 2rem;
140
+ }
141
+
142
+ .rc-group-title {
143
+ font-size: 0.75rem;
144
+ font-weight: 800;
145
+ color: rgb(100, 116, 139);
146
+ text-transform: uppercase;
147
+ letter-spacing: 0.1em;
148
+ margin-bottom: 1rem;
149
+ }
150
+
151
+ .rc-group-title-sm {
152
+ margin-bottom: 0.75rem;
153
+ }
154
+
155
+ :global(.theme-dark) .rc-group-title {
156
+ color: rgb(148, 163, 184);
157
+ }
158
+
159
+ .rc-inputs-grid {
160
+ display: grid;
161
+ grid-template-columns: repeat(2, 1fr);
162
+ gap: 1.5rem;
163
+ }
164
+
165
+ .rc-input-wrapper {
166
+ display: flex;
167
+ flex-direction: column;
168
+ gap: 0.75rem;
169
+ }
170
+
171
+ .rc-label-mini {
172
+ font-size: 0.875rem;
173
+ font-weight: 600;
174
+ color: rgb(30, 41, 59);
175
+ }
176
+
177
+ :global(.theme-dark) .rc-label-mini {
178
+ color: rgb(241, 245, 249);
179
+ }
180
+
181
+ .rc-color-input-container {
182
+ display: flex;
183
+ align-items: center;
184
+ gap: 0.75rem;
185
+ background: rgba(255, 255, 255, 0.5);
186
+ border: 1px solid rgb(226, 232, 240);
187
+ padding: 0.5rem;
188
+ border-radius: 0.75rem;
189
+ transition: all 0.2s ease;
190
+ }
191
+
192
+ :global(.theme-dark) .rc-color-input-container {
193
+ background: rgba(30, 41, 59, 0.5);
194
+ border-color: rgb(51, 65, 85);
195
+ }
196
+
197
+ .rc-color-picker {
198
+ -webkit-appearance: none;
199
+ -moz-appearance: none;
200
+ appearance: none;
201
+ width: 2.5rem;
202
+ height: 2.5rem;
203
+ border: none;
204
+ border-radius: 0.5rem;
205
+ cursor: pointer;
206
+ background: none;
207
+ }
208
+
209
+ .rc-color-picker::-webkit-color-swatch-wrapper {
210
+ padding: 0;
211
+ }
212
+
213
+ .rc-color-picker::-webkit-color-swatch {
214
+ border: 1px solid rgba(0, 0, 0, 0.1);
215
+ border-radius: 0.4rem;
216
+ }
217
+
218
+ :global(.theme-dark) .rc-color-picker::-webkit-color-swatch {
219
+ border: 1px solid rgba(255, 255, 255, 0.2);
220
+ }
221
+
222
+ .rc-hex-text {
223
+ font-size: 0.875rem;
224
+ font-weight: 600;
225
+ color: rgb(71, 85, 105);
226
+ flex: 1;
227
+ }
228
+
229
+ :global(.theme-dark) .rc-hex-text {
230
+ color: rgb(148, 163, 184);
231
+ }
232
+
233
+ .rc-sliders-stack {
234
+ display: flex;
235
+ flex-direction: column;
236
+ gap: 1.5rem;
237
+ }
238
+
239
+ .rc-slider-group {
240
+ display: flex;
241
+ flex-direction: column;
242
+ gap: 0.75rem;
243
+ }
244
+
245
+ .rc-slider-header {
246
+ display: flex;
247
+ justify-content: space-between;
248
+ align-items: center;
249
+ }
250
+
251
+ .rc-slider-value {
252
+ background: rgb(14, 165, 233);
253
+ color: rgb(255, 255, 255);
254
+ font-size: 0.75rem;
255
+ font-weight: 700;
256
+ padding: 0.15rem 0.5rem;
257
+ border-radius: 1rem;
258
+ }
259
+
260
+ .rc-range {
261
+ -webkit-appearance: none;
262
+ appearance: none;
263
+ width: 100%;
264
+ height: 6px;
265
+ background: rgb(226, 232, 240);
266
+ border-radius: 3px;
267
+ outline: none;
268
+ }
269
+
270
+ :global(.theme-dark) .rc-range {
271
+ background: rgb(51, 65, 85);
272
+ }
273
+
274
+ .rc-range::-webkit-slider-thumb {
275
+ -webkit-appearance: none;
276
+ appearance: none;
277
+ width: 18px;
278
+ height: 18px;
279
+ background: rgb(14, 165, 233);
280
+ border-radius: 50%;
281
+ cursor: pointer;
282
+ box-shadow: 0 0 0 4px rgba(14, 165, 233, 0.1);
283
+ transition: all 0.2s ease;
284
+ }
285
+
286
+ .rc-range::-webkit-slider-thumb:hover {
287
+ transform: scale(1.15);
288
+ box-shadow: 0 0 0 6px rgba(14, 165, 233, 0.2);
289
+ }
290
+
291
+ .rc-comparison-grid {
292
+ display: grid;
293
+ grid-template-columns: repeat(2, 1fr);
294
+ gap: 1rem;
295
+ }
296
+
297
+ .rc-score-card {
298
+ padding: 1.25rem;
299
+ border-radius: 1rem;
300
+ display: flex;
301
+ flex-direction: column;
302
+ align-items: center;
303
+ gap: 0.5rem;
304
+ background: rgba(255, 255, 255, 0.4);
305
+ border: 1px solid rgba(0, 0, 0, 0.05);
306
+ }
307
+
308
+ :global(.theme-dark) .rc-score-card {
309
+ background: rgba(255, 255, 255, 0.03);
310
+ border-color: rgba(255, 255, 255, 0.05);
311
+ }
312
+
313
+ .rc-score-label {
314
+ font-size: 0.65rem;
315
+ font-weight: 800;
316
+ text-transform: uppercase;
317
+ color: rgb(100, 116, 139);
318
+ text-align: center;
319
+ }
320
+
321
+ .rc-score-value {
322
+ font-size: 1.5rem;
323
+ font-weight: 800;
324
+ }
325
+
326
+ .rc-score-status {
327
+ font-size: 0.75rem;
328
+ font-weight: 700;
329
+ padding: 0.2rem 0.6rem;
330
+ border-radius: 0.5rem;
331
+ text-transform: uppercase;
332
+ }
333
+
334
+ .rc-status-pass {
335
+ background: rgb(220, 252, 231);
336
+ color: rgb(22, 101, 52);
337
+ }
338
+
339
+ .rc-status-fail {
340
+ background: rgb(254, 226, 226);
341
+ color: rgb(153, 27, 27);
342
+ }
343
+
344
+ :global(.theme-dark) .rc-status-pass {
345
+ background: rgba(34, 197, 94, 0.15);
346
+ color: rgb(134, 239, 172);
347
+ }
348
+
349
+ :global(.theme-dark) .rc-status-fail {
350
+ background: rgba(239, 68, 68, 0.15);
351
+ color: rgb(252, 165, 165);
352
+ }
353
+
354
+ .rc-preview-container {
355
+ flex: 1;
356
+ display: flex;
357
+ flex-direction: column;
358
+ gap: 1.5rem;
359
+ }
360
+
361
+ .rc-main-preview-box {
362
+ padding: 2.5rem;
363
+ border-radius: 1.25rem;
364
+ transition: all 0.3s ease;
365
+ border: 1px solid rgba(0, 0, 0, 0.05);
366
+ min-height: 160px;
367
+ display: flex;
368
+ align-items: center;
369
+ }
370
+
371
+ .rc-preview-text {
372
+ line-height: 1.5;
373
+ margin: 0;
374
+ }
375
+
376
+ .rc-apca-breakdown {
377
+ background: rgba(14, 165, 233, 0.05);
378
+ padding: 1.5rem;
379
+ border-radius: 1rem;
380
+ border: 1px solid rgba(14, 165, 233, 0.1);
381
+ }
382
+
383
+ .rc-breakdown-list {
384
+ display: flex;
385
+ flex-direction: column;
386
+ gap: 0.75rem;
387
+ }
388
+
389
+ .rc-breakdown-item {
390
+ display: flex;
391
+ justify-content: space-between;
392
+ font-size: 0.875rem;
393
+ }
394
+
395
+ .rc-item-label {
396
+ color: rgb(71, 85, 105);
397
+ font-weight: 600;
398
+ }
399
+
400
+ :global(.theme-dark) .rc-item-label {
401
+ color: rgb(148, 163, 184);
402
+ }
403
+
404
+ .rc-item-check {
405
+ font-weight: 700;
406
+ display: flex;
407
+ align-items: center;
408
+ gap: 0.4rem;
409
+ }
410
+
411
+ .rc-check-yes {
412
+ color: rgb(16, 185, 129);
413
+ }
414
+
415
+ .rc-check-no {
416
+ color: rgb(239, 68, 68);
417
+ }
418
+ </style>
419
+
420
+ <script>
421
+ import type { ReadabilityCalculatorUI } from './ui';
422
+
423
+ const root = document.getElementById('rc-root')!;
424
+ const t = JSON.parse(root.dataset.i18n ?? '{}') as ReadabilityCalculatorUI;
425
+
426
+ const textColorInput = document.getElementById('rc-text-color') as HTMLInputElement;
427
+ const bgColorInput = document.getElementById('rc-bg-color') as HTMLInputElement;
428
+ const sizeSlider = document.getElementById('rc-size-slider') as HTMLInputElement;
429
+ const weightSlider = document.getElementById('rc-weight-slider') as HTMLInputElement;
430
+ const textHexEl = document.getElementById('rc-text-hex') as HTMLElement;
431
+ const bgHexEl = document.getElementById('rc-bg-hex') as HTMLElement;
432
+ const sizeValEl = document.getElementById('rc-size-val') as HTMLElement;
433
+ const weightValEl = document.getElementById('rc-weight-val') as HTMLElement;
434
+ const wcagRatioEl = document.getElementById('rc-wcag-ratio') as HTMLElement;
435
+ const wcagStatusEl = document.getElementById('rc-wcag-status') as HTMLElement;
436
+ const apcaLcEl = document.getElementById('rc-apca-lc') as HTMLElement;
437
+ const apcaStatusEl = document.getElementById('rc-apca-status') as HTMLElement;
438
+ const previewBox = document.getElementById('rc-preview-box') as HTMLElement;
439
+ const previewText = document.getElementById('rc-preview-text') as HTMLElement;
440
+ const apcaBodyEl = document.getElementById('rc-apca-body') as HTMLElement;
441
+ const apcaSmallEl = document.getElementById('rc-apca-small') as HTMLElement;
442
+ const apcaUiEl = document.getElementById('rc-apca-ui') as HTMLElement;
443
+
444
+ const svgYes = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="rgb(16,185,129)" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>';
445
+ const svgNo = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="rgb(239,68,68)" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>';
446
+
447
+ function hexToRgb(hex: string): { r: number; g: number; b: number } {
448
+ return { r: parseInt(hex.slice(1, 3), 16), g: parseInt(hex.slice(3, 5), 16), b: parseInt(hex.slice(5, 7), 16) };
449
+ }
450
+
451
+ function getLuminance(r: number, g: number, b: number): number {
452
+ const lin = (v: number) => { const n = v / 255; return n <= 0.03928 ? n / 12.92 : Math.pow((n + 0.055) / 1.055, 2.4); };
453
+ return lin(r) * 0.2126 + lin(g) * 0.7152 + lin(b) * 0.0722;
454
+ }
455
+
456
+ function calcWCAG(lum1: number, lum2: number): number {
457
+ return (Math.max(lum1, lum2) + 0.05) / (Math.min(lum1, lum2) + 0.05);
458
+ }
459
+
460
+ function sRGBtoY(r: number, g: number, b: number): number {
461
+ return Math.pow(r / 255, 2.4) * 0.2126729 + Math.pow(g / 255, 2.4) * 0.7151522 + Math.pow(b / 255, 2.4) * 0.072175;
462
+ }
463
+
464
+ function calcAPCA(textHex: string, bgHex: string): number {
465
+ const tr = hexToRgb(textHex); const br = hexToRgb(bgHex);
466
+ const Ytxt = sRGBtoY(tr.r, tr.g, tr.b); const Ybg = sRGBtoY(br.r, br.g, br.b);
467
+ const scale = 1.14;
468
+ const contrast = Ybg > Ytxt
469
+ ? (Math.pow(Ybg, 0.88) - Math.pow(Ytxt, 0.56)) * scale
470
+ : (Math.pow(Ybg, 0.56) - Math.pow(Ytxt, 0.88)) * scale;
471
+ return Math.round(Math.abs(contrast) * 100);
472
+ }
473
+
474
+ function setCheck(el: HTMLElement, ok: boolean): void {
475
+ el.innerHTML = `${ok ? svgYes : svgNo} ${ok ? t.statusYes : t.statusNo}`;
476
+ el.className = `rc-item-check ${ok ? 'rc-check-yes' : 'rc-check-no'}`;
477
+ }
478
+
479
+ function getWcagStatus(ratio: number): { text: string; pass: boolean } {
480
+ if (ratio >= 7) return { text: t.wcagAAA, pass: true };
481
+ if (ratio >= 4.5) return { text: t.wcagAA, pass: true };
482
+ if (ratio >= 3) return { text: t.wcagLarge, pass: true };
483
+ return { text: t.wcagFail, pass: false };
484
+ }
485
+
486
+ function getApcaStatus(lc: number): { text: string; pass: boolean } {
487
+ if (lc >= 75) return { text: t.apcaExcellent, pass: true };
488
+ if (lc >= 60) return { text: t.apcaGood, pass: true };
489
+ if (lc >= 45) return { text: t.apcaMinimal, pass: true };
490
+ return { text: t.apcaPoor, pass: false };
491
+ }
492
+
493
+ function update(): void {
494
+ const textHex = textColorInput.value;
495
+ const bgHex = bgColorInput.value;
496
+ const size = parseInt(sizeSlider.value);
497
+ const weight = parseInt(weightSlider.value);
498
+
499
+ textHexEl.textContent = textHex.toUpperCase();
500
+ bgHexEl.textContent = bgHex.toUpperCase();
501
+ sizeValEl.textContent = `${size}px`;
502
+ weightValEl.textContent = weight.toString();
503
+
504
+ const tc = hexToRgb(textHex); const bc = hexToRgb(bgHex);
505
+ const ratio = calcWCAG(getLuminance(tc.r, tc.g, tc.b), getLuminance(bc.r, bc.g, bc.b));
506
+ wcagRatioEl.textContent = ratio.toFixed(2);
507
+
508
+ const ws = getWcagStatus(ratio);
509
+ wcagStatusEl.textContent = ws.text;
510
+ wcagStatusEl.className = `rc-score-status ${ws.pass ? 'rc-status-pass' : 'rc-status-fail'}`;
511
+
512
+ const Lc = calcAPCA(textHex, bgHex);
513
+ apcaLcEl.textContent = Lc.toString();
514
+
515
+ const as_ = getApcaStatus(Lc);
516
+ apcaStatusEl.textContent = as_.text;
517
+ apcaStatusEl.className = `rc-score-status ${as_.pass ? 'rc-status-pass' : 'rc-status-fail'}`;
518
+
519
+ previewBox.style.backgroundColor = bgHex;
520
+ previewText.style.color = textHex;
521
+ previewText.style.fontSize = `${size}px`;
522
+ previewText.style.fontWeight = weight.toString();
523
+
524
+ setCheck(apcaBodyEl, Lc >= 60 && size >= 14);
525
+ setCheck(apcaSmallEl, Lc >= 75 && size >= 12);
526
+ setCheck(apcaUiEl, Lc >= 45);
527
+ }
528
+
529
+ [textColorInput, bgColorInput, sizeSlider, weightSlider].forEach((el) => {
530
+ el.addEventListener('input', update);
531
+ });
532
+
533
+ update();
534
+ </script>
@@ -0,0 +1,133 @@
1
+ import type { WithContext, FAQPage, HowToThing, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { ReadabilityCalculatorUI } from '../ui';
4
+
5
+ const slug = 'visual-readability-calculator-wcag-apca';
6
+ const title = 'Visual Readability Calculator WCAG and APCA';
7
+ const description = 'Check the real contrast of your designs with APCA (WCAG 3.0). Analyze how font weight and size affect actual readability. From simple ratios to perceptual legibility.';
8
+
9
+ const faqData = [
10
+ {
11
+ question: 'What is APCA and why is it different from WCAG 2.1?',
12
+ answer: 'APCA is the Advanced Perceptual Contrast Algorithm developed for WCAG 3.0. Unlike the old simple ratio, APCA uses mathematical models that mimic how the human brain perceives contrast, taking into account polarity (light vs. dark background) and font size/weight.',
13
+ },
14
+ {
15
+ question: 'What does the Lc value mean?',
16
+ answer: 'Lc (Lightness Contrast) is the contrast value generated by APCA. A value of 100 represents ideal maximum contrast, while values below 60 start to be critical for continuous reading text. It is an absolute magnitude scale of perception.',
17
+ },
18
+ {
19
+ question: 'How does font weight affect readability?',
20
+ answer: 'Thin/Light fonts require much higher contrast to be readable. APCA penalizes low-weight fonts, indicating that a design passing WCAG with a weight-100 font may be illegible in practice.',
21
+ },
22
+ {
23
+ question: 'Is this calculator private?',
24
+ answer: 'Yes, all calculations are performed locally in your browser. The colors and settings you analyze are never sent to any server, guaranteeing full privacy for your design projects.',
25
+ },
26
+ ];
27
+
28
+ const howToData = [
29
+ { name: 'Choose Colors', text: 'Use the color pickers to set the text color and background color of your design.' },
30
+ { name: 'Adjust Typography', text: 'Move the font size and weight sliders to simulate your actual typography.' },
31
+ { name: 'Read the Results', text: 'Check the WCAG 2.1 ratio and the APCA Lc value to know if your design is accessible.' },
32
+ { name: 'Review Recommendations', text: 'Check APCA-specific recommendations for body text, small text and UI elements.' },
33
+ ];
34
+
35
+ const faqSchema: WithContext<FAQPage> = {
36
+ '@context': 'https://schema.org',
37
+ '@type': 'FAQPage',
38
+ mainEntity: faqData.map((item) => ({
39
+ '@type': 'Question',
40
+ name: item.question,
41
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
42
+ })),
43
+ };
44
+
45
+ const howToSchema: WithContext<HowToThing> = {
46
+ '@context': 'https://schema.org',
47
+ '@type': 'HowTo',
48
+ name: title,
49
+ description,
50
+ step: howToData.map((s, i) => ({ '@type': 'HowToStep', position: i + 1, name: s.name, text: s.text })),
51
+ };
52
+
53
+ const appSchema: WithContext<SoftwareApplication> = {
54
+ '@context': 'https://schema.org',
55
+ '@type': 'SoftwareApplication',
56
+ name: title,
57
+ description,
58
+ applicationCategory: 'UtilityApplication',
59
+ operatingSystem: 'All',
60
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
61
+ inLanguage: 'en',
62
+ };
63
+
64
+ const ui: ReadabilityCalculatorUI = {
65
+ labelColors: 'Base Colors',
66
+ labelText: 'Text',
67
+ labelBg: 'Background',
68
+ labelTypo: 'Typography',
69
+ labelFontSize: 'Font Size',
70
+ labelFontWeight: 'Weight',
71
+ labelCompare: 'Contrast Comparison',
72
+ labelPreview: 'Perceptual Preview',
73
+ labelApcaRecs: 'APCA Recommendations',
74
+ labelBody: 'Reading Text (Body)',
75
+ labelSmall: 'Small Text / Caption',
76
+ labelUi: 'UI / Buttons',
77
+ statusYes: 'Yes',
78
+ statusNo: 'No',
79
+ wcagAAA: 'Passes AAA',
80
+ wcagAA: 'Passes AA',
81
+ wcagLarge: 'Large Text Only',
82
+ wcagFail: 'Fails',
83
+ apcaExcellent: 'Excellent',
84
+ apcaGood: 'Good',
85
+ apcaMinimal: 'Minimal',
86
+ apcaPoor: 'Poor',
87
+ previewText: 'Visual readability is not just mathematics. It is how light and shadow interact with your eyes.',
88
+ wcagRatioLabel: 'WCAG 2.1 Ratio',
89
+ apcaLcLabel: 'APCA Lc (WCAG 3)',
90
+ };
91
+
92
+ export const content: ToolLocaleContent<ReadabilityCalculatorUI> = {
93
+ slug,
94
+ title,
95
+ description,
96
+ ui,
97
+ faqTitle: 'Frequently Asked Questions',
98
+ faq: faqData,
99
+ bibliographyTitle: 'Resources on Contrast and APCA',
100
+ bibliography: [
101
+ { name: 'W3C: WCAG 3.0 Draft (Silver)', url: 'https://www.w3.org/WAI/standards-guidelines/wcag/wcag3-intro/' },
102
+ { name: 'Myndex: APCA Reference Guide', url: 'https://apcaw3.myndex.com/' },
103
+ { name: 'MDN: Accessibility and Color Contrast', url: 'https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_WCAG/Perceivable/Color_contrast' },
104
+ ],
105
+ howTo: howToData,
106
+ schemas: [faqSchema, howToSchema, appSchema],
107
+ seo: [
108
+ { type: 'title', text: 'Visual Readability Calculator (WCAG vs APCA)', level: 2 },
109
+ {
110
+ type: 'paragraph',
111
+ html: 'Understand how your colors and typography affect actual reading with the new perceptual accessibility standard. WCAG 2.1 uses a simple 2008 mathematical formula. <strong>APCA</strong> is the new model proposed for <strong>WCAG 3.0</strong> that mimics human perception.',
112
+ },
113
+ { type: 'title', text: 'Key Points of APCA', level: 3 },
114
+ {
115
+ type: 'list',
116
+ items: [
117
+ '<strong>Polarity:</strong> Understands that Dark Mode is perceived differently than Light Mode.',
118
+ '<strong>Font Weight:</strong> Assigns specific contrast levels (Lc) based on typography weight.',
119
+ '<strong>Linearity:</strong> Fixes mathematical inaccuracies in the 2008 relative luminance model.',
120
+ ],
121
+ },
122
+ { type: 'title', text: 'Recommended APCA Levels', level: 3 },
123
+ {
124
+ type: 'list',
125
+ items: [
126
+ '<strong>Lc 90+:</strong> Ideal for very small or low-weight text.',
127
+ '<strong>Lc 75:</strong> The standard for main body reading text.',
128
+ '<strong>Lc 60:</strong> Minimum for readable medium-size content.',
129
+ '<strong>Lc 45:</strong> Minimum for large or decorative elements.',
130
+ ],
131
+ },
132
+ ],
133
+ };