PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.7
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.7
2.0.13 2.0.12 2.0.11 2.0.10 2.0.9 trunk 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8
blockenberg / blocks / contrast-checker / frontend.js

frontend.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.7, at blocks/contrast-checker/frontend.js

251 lines 14.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function () {
2 'use strict';
3
4 function typoCssVarsForEl(typo, prefix, el) {
5 if (!typo) return;
6 if (typo.family) el.style.setProperty(prefix + 'font-family', "'" + typo.family + "', sans-serif");
7 if (typo.weight) el.style.setProperty(prefix + 'font-weight', typo.weight);
8 if (typo.transform) el.style.setProperty(prefix + 'text-transform', typo.transform);
9 if (typo.style) el.style.setProperty(prefix + 'font-style', typo.style);
10 if (typo.decoration) el.style.setProperty(prefix + 'text-decoration', typo.decoration);
11 var su = typo.sizeUnit || 'px';
12 if (typo.sizeDesktop !== '' && typo.sizeDesktop != null) el.style.setProperty(prefix + 'font-size-d', typo.sizeDesktop + su);
13 if (typo.sizeTablet !== '' && typo.sizeTablet != null) el.style.setProperty(prefix + 'font-size-t', typo.sizeTablet + su);
14 if (typo.sizeMobile !== '' && typo.sizeMobile != null) el.style.setProperty(prefix + 'font-size-m', typo.sizeMobile + su);
15 var lhu = typo.lineHeightUnit || 'px';
16 if (typo.lineHeightDesktop !== '' && typo.lineHeightDesktop != null) el.style.setProperty(prefix + 'line-height-d', typo.lineHeightDesktop + lhu);
17 if (typo.lineHeightTablet !== '' && typo.lineHeightTablet != null) el.style.setProperty(prefix + 'line-height-t', typo.lineHeightTablet + lhu);
18 if (typo.lineHeightMobile !== '' && typo.lineHeightMobile != null) el.style.setProperty(prefix + 'line-height-m', typo.lineHeightMobile + lhu);
19 var lsu = typo.letterSpacingUnit || 'px';
20 if (typo.letterSpacingDesktop !== '' && typo.letterSpacingDesktop != null) {
21 el.style.setProperty(prefix + 'letter-spacing-d', typo.letterSpacingDesktop + lsu);
22 el.style.setProperty(prefix + 'letter-spacing', typo.letterSpacingDesktop + lsu);
23 }
24 if (typo.letterSpacingTablet !== '' && typo.letterSpacingTablet != null) el.style.setProperty(prefix + 'letter-spacing-t', typo.letterSpacingTablet + lsu);
25 if (typo.letterSpacingMobile !== '' && typo.letterSpacingMobile != null) el.style.setProperty(prefix + 'letter-spacing-m', typo.letterSpacingMobile + lsu);
26 var wsu = typo.wordSpacingUnit || 'px';
27 if (typo.wordSpacingDesktop !== '' && typo.wordSpacingDesktop != null) {
28 el.style.setProperty(prefix + 'word-spacing-d', typo.wordSpacingDesktop + wsu);
29 el.style.setProperty(prefix + 'word-spacing', typo.wordSpacingDesktop + wsu);
30 }
31 if (typo.wordSpacingTablet !== '' && typo.wordSpacingTablet != null) el.style.setProperty(prefix + 'word-spacing-t', typo.wordSpacingTablet + wsu);
32 if (typo.wordSpacingMobile !== '' && typo.wordSpacingMobile != null) el.style.setProperty(prefix + 'word-spacing-m', typo.wordSpacingMobile + wsu);
33 }
34
35 function hexToRgb(hex) {
36 hex = hex.replace('#', '');
37 if (hex.length === 3) hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
38 var n = parseInt(hex, 16);
39 return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
40 }
41
42 function luminance(hex) {
43 try {
44 var rgb = hexToRgb(hex);
45 var vals = [rgb.r, rgb.g, rgb.b].map(function(v) {
46 v = v / 255;
47 return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
48 });
49 return 0.2126 * vals[0] + 0.7152 * vals[1] + 0.0722 * vals[2];
50 } catch(e) { return 0; }
51 }
52
53 function contrastRatio(fg, bg) {
54 var l1 = luminance(fg), l2 = luminance(bg);
55 var lighter = Math.max(l1, l2), darker = Math.min(l1, l2);
56 return (lighter + 0.05) / (darker + 0.05);
57 }
58
59 function isValidHex(hex) {
60 return /^#([0-9A-Fa-f]{3}){1,2}$/.test(hex);
61 }
62
63 // Suggestions: darken fg or lighten bg to reach AA
64 function suggestColors(fg, bg) {
65 var suggestions = [];
66 // Try some darker versions of fg
67 var fgRgb = hexToRgb(fg);
68 var steps = [0.8, 0.6, 0.4, 0.2, 0];
69 steps.forEach(function(factor) {
70 var r = Math.round(fgRgb.r * factor);
71 var g = Math.round(fgRgb.g * factor);
72 var b = Math.round(fgRgb.b * factor);
73 var hex = '#' + [r,g,b].map(function(v){ return ('0'+v.toString(16)).slice(-2); }).join('');
74 var ratio = contrastRatio(hex, bg);
75 if (ratio >= 4.5) {
76 suggestions.push({ hex: hex, ratio: ratio, label: 'Darker Fg' });
77 return;
78 }
79 });
80 // Classic black/white
81 [['#000000','Black'],['#ffffff','White'],['#1e293b','Dark'],['#f8fafc','Light']].forEach(function(pair) {
82 var ratio = contrastRatio(pair[0], bg);
83 if (ratio >= 4.5) suggestions.push({ hex: pair[0], ratio: ratio, label: pair[1] });
84 });
85 // Deduplicate
86 var seen = {};
87 return suggestions.filter(function(s) {
88 if (seen[s.hex]) return false;
89 seen[s.hex] = true;
90 return true;
91 }).slice(0, 5);
92 }
93
94 var WCAG_LEVELS = [
95 { id: 'aa-normal', label: 'AA', sub: 'Normal text ≥ 4.5:1', min: 4.5 },
96 { id: 'aa-large', label: 'AA', sub: 'Large text ≥ 3:1', min: 3.0 },
97 { id: 'aaa-normal', label: 'AAA', sub: 'Normal text ≥ 7:1', min: 7.0 },
98 { id: 'aaa-large', label: 'AAA', sub: 'Large text ≥ 4.5:1', min: 4.5 }
99 ];
100
101 document.querySelectorAll('.bkbg-cc-app').forEach(function (root) {
102 var opts = {};
103 try { opts = JSON.parse(root.getAttribute('data-opts') || '{}'); } catch(e) {}
104
105 /* Apply typography CSS vars on root (survives innerHTML replacements) */
106 typoCssVarsForEl(opts.typoTitle, '--bkcchk-title-', root);
107 typoCssVarsForEl(opts.typoSubtitle, '--bkcchk-sub-', root);
108
109 var showPreview = opts.showPreview !== false;
110 var showSuggestions = opts.showSuggestions !== false;
111 var previewText = opts.previewText || 'The quick brown fox jumps over the lazy dog';
112 var passColor = opts.passColor || '#16a34a';
113 var failColor = opts.failColor || '#dc2626';
114 var accentColor = opts.accentColor || '#6c3fb5';
115 var cardBg = opts.cardBg || '#ffffff';
116 var sectionBg = opts.sectionBg || '#f5f3ff';
117 var titleColor = opts.titleColor || '#111827';
118 var subtitleColor = opts.subtitleColor || '#6b7280';
119 var labelColor = opts.labelColor || '#374151';
120
121 var fg = opts.defaultFg || '#1e1b4b';
122 var bg = opts.defaultBg || '#ffffff';
123
124 function gradeColor(ratio) {
125 return ratio >= 4.5 ? passColor : ratio >= 3 ? '#d97706' : failColor;
126 }
127 function gradeLabel(ratio) {
128 return ratio >= 4.5 ? 'Excellent' : ratio >= 3 ? 'Good' : ratio >= 2 ? 'Poor' : 'Fail';
129 }
130
131 function render() {
132 var validFg = isValidHex(fg) ? fg : '#000000';
133 var validBg = isValidHex(bg) ? bg : '#ffffff';
134 var ratio = contrastRatio(validFg, validBg);
135 var gc = gradeColor(ratio);
136 var sug = showSuggestions && ratio < 4.5 ? suggestColors(validFg, validBg) : [];
137
138 root.innerHTML =
139 '<div class="bkbg-cc-card" style="background:' + cardBg + ';max-width:' + (opts.contentMaxWidth||680) + 'px;">' +
140 '<h2 class="bkbg-cc-title" style="color:' + titleColor + ';">' + (opts.title || 'Color Contrast Checker') + '</h2>' +
141 '<p class="bkbg-cc-subtitle" style="color:' + subtitleColor + ';">' + (opts.subtitle || 'Test accessibility compliance with WCAG 2.1 standards') + '</p>' +
142
143 '<div class="bkbg-cc-pickers" style="background:' + sectionBg + ';">' +
144 '<div class="bkbg-cc-picker-group">' +
145 '<label class="bkbg-cc-label" style="color:' + labelColor + ';">�
146 � Foreground Color</label>' +
147 '<div class="bkbg-cc-color-row">' +
148 '<input type="color" class="bkbg-cc-color-input" id="cc-fg" value="' + validFg + '">' +
149 '<input type="text" class="bkbg-cc-hex-input" id="cc-fg-text" value="' + validFg.toUpperCase() + '" maxlength="7" style="color:' + labelColor + ';font-weight:700;font-size:14px;border:1.5px solid #e5e7eb;border-radius:8px;padding:8px 10px;width:100px;font-family:monospace;background:' + cardBg + ';">' +
150 '</div>' +
151 '</div>' +
152 '<button class="bkbg-cc-swap" id="cc-swap" style="background:' + accentColor + ';" title="Swap colors">⇆</button>' +
153 '<div class="bkbg-cc-picker-group">' +
154 '<label class="bkbg-cc-label" style="color:' + labelColor + ';">◻ Background Color</label>' +
155 '<div class="bkbg-cc-color-row">' +
156 '<input type="color" class="bkbg-cc-color-input" id="cc-bg" value="' + validBg + '">' +
157 '<input type="text" class="bkbg-cc-hex-input" id="cc-bg-text" value="' + validBg.toUpperCase() + '" maxlength="7" style="color:' + labelColor + ';font-weight:700;font-size:14px;border:1.5px solid #e5e7eb;border-radius:8px;padding:8px 10px;width:100px;font-family:monospace;background:' + cardBg + ';">' +
158 '</div>' +
159 '</div>' +
160 '</div>' +
161
162 (showPreview ?
163 '<div class="bkbg-cc-preview" style="background:' + validBg + ';">' +
164 '<p class="bkbg-cc-preview-normal" style="color:' + validFg + ';">' + previewText + ' — Normal (16px)</p>' +
165 '<p class="bkbg-cc-preview-large" style="color:' + validFg + ';">' + previewText + ' — Large (24px)</p>' +
166 '<p class="bkbg-cc-preview-bold" style="color:' + validFg + ';">' + previewText + ' — Bold (14px)</p>' +
167 '</div>' : '') +
168
169 '<div class="bkbg-cc-ratio-box" style="border-color:' + gc + '44;background:' + gc + '10;">' +
170 '<div class="bkbg-cc-ratio-val" style="color:' + gc + ';">' + ratio.toFixed(2) + ':1</div>' +
171 '<div class="bkbg-cc-ratio-lbl" style="color:' + labelColor + ';">Contrast Ratio</div>' +
172 '<div class="bkbg-cc-ratio-grade" style="background:' + gc + ';color:#fff;">' + gradeLabel(ratio) + '</div>' +
173 '</div>' +
174
175 '<div class="bkbg-cc-wcag-grid">' +
176 WCAG_LEVELS.map(function(level) {
177 var pass = ratio >= level.min;
178 var col = pass ? passColor : failColor;
179 return '<div class="bkbg-cc-wcag-row" style="border-color:' + col + '33;background:' + col + '09;">' +
180 '<div class="bkbg-cc-wcag-badge" style="background:' + col + ';color:#fff;">' + level.label + '</div>' +
181 '<div class="bkbg-cc-wcag-info">' +
182 '<div class="bkbg-cc-wcag-sub" style="color:' + labelColor + ';">' + level.sub + '</div>' +
183 '</div>' +
184 '<div class="bkbg-cc-wcag-status" style="color:' + col + ';">' + (pass ? '✓ Pass' : '✗ Fail') + '</div>' +
185 '</div>';
186 }).join('') +
187 '</div>' +
188
189 (sug.length > 0 ?
190 '<div class="bkbg-cc-suggestions">' +
191 '<div class="bkbg-cc-suggestions-title" style="color:' + labelColor + ';">💡 Suggested foreground colors to reach AA:</div>' +
192 '<div class="bkbg-cc-sug-chips">' +
193 sug.map(function(s) {
194 return '<button class="bkbg-cc-sug-chip" data-sugfg="' + s.hex + '" style="background:' + cardBg + ';color:' + labelColor + ';">' +
195 '<span class="bkbg-cc-sug-swatch" style="background:' + s.hex + ';"></span>' +
196 s.hex.toUpperCase() + ' (' + s.ratio.toFixed(1) + ':1)' +
197 '</button>';
198 }).join('') +
199 '</div>' +
200 '</div>' : '') +
201 '</div>';
202
203 bindEvents();
204 }
205
206 function update(newFg, newBg) {
207 fg = newFg || fg;
208 bg = newBg || bg;
209 render();
210 }
211
212 function bindEvents() {
213 var fgPicker = root.querySelector('#cc-fg');
214 var bgPicker = root.querySelector('#cc-bg');
215 var fgText = root.querySelector('#cc-fg-text');
216 var bgText = root.querySelector('#cc-bg-text');
217 var swapBtn = root.querySelector('#cc-swap');
218
219 if (fgPicker) fgPicker.addEventListener('input', function() {
220 fg = this.value; if (fgText) fgText.value = fg.toUpperCase(); render();
221 });
222 if (bgPicker) bgPicker.addEventListener('input', function() {
223 bg = this.value; if (bgText) bgText.value = bg.toUpperCase(); render();
224 });
225 if (fgText) fgText.addEventListener('input', function() {
226 var v = this.value.trim();
227 if (!v.startsWith('#')) v = '#' + v;
228 if (isValidHex(v)) { fg = v; if (fgPicker) fgPicker.value = fg; render(); }
229 });
230 if (bgText) bgText.addEventListener('input', function() {
231 var v = this.value.trim();
232 if (!v.startsWith('#')) v = '#' + v;
233 if (isValidHex(v)) { bg = v; if (bgPicker) bgPicker.value = bg; render(); }
234 });
235 if (swapBtn) swapBtn.addEventListener('click', function() {
236 var t = fg; fg = bg; bg = t; render();
237 });
238
239 root.querySelectorAll('[data-sugfg]').forEach(function(btn) {
240 btn.addEventListener('click', function() {
241 fg = this.getAttribute('data-sugfg');
242 render();
243 });
244 });
245 }
246
247 render();
248 });
249
250 })();
251