@trishchuk/coolors-mcp 1.0.0 → 1.0.1
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.
- package/.claude/settings.local.json +2 -6
- package/.github/ISSUE_TEMPLATE/bug_report.md +20 -8
- package/.github/ISSUE_TEMPLATE/feature_request.md +22 -8
- package/.github/pull_request_template.md +33 -8
- package/.github/workflows/ci.yml +97 -97
- package/.github/workflows/deploy-docs.yml +9 -9
- package/.github/workflows/release.yml +15 -15
- package/README.md +26 -1
- package/TOOLS_UK.md +233 -0
- package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js +30 -12
- package/docs/.vitepress/cache/deps/_metadata.json +1 -1
- package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js +9 -6
- package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js +2543 -1612
- package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js +3508 -2529
- package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js +1902 -1003
- package/docs/.vitepress/cache/deps/cytoscape.js +13303 -7347
- package/docs/.vitepress/cache/deps/dayjs.js +494 -272
- package/docs/.vitepress/cache/deps/debug.js +82 -38
- package/docs/.vitepress/cache/deps/prismjs.js +444 -272
- package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js +80 -73
- package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js +93 -62
- package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js +13 -13
- package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js +34 -27
- package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js +20 -17
- package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js +75 -41
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +2005 -1438
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +2 -2
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js +566 -229
- package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js +382 -270
- package/docs/.vitepress/cache/deps/vitepress___minisearch.js +334 -125
- package/docs/.vitepress/cache/deps/vue.js +2 -2
- package/docs/.vitepress/components/ClientGrid.vue +9 -3
- package/docs/.vitepress/components/CodeBlock.vue +51 -44
- package/docs/.vitepress/components/ConfigModal.vue +151 -67
- package/docs/.vitepress/components/DiagramModal.vue +186 -154
- package/docs/.vitepress/components/TroubleshootingModal.vue +101 -96
- package/docs/.vitepress/config.js +171 -141
- package/docs/.vitepress/theme/FundingLayout.vue +65 -54
- package/docs/.vitepress/theme/Layout.vue +21 -21
- package/docs/.vitepress/theme/components/AdBanner.vue +73 -52
- package/docs/.vitepress/theme/components/AdPlaceholder.vue +3 -3
- package/docs/.vitepress/theme/components/FundingEffects.vue +77 -53
- package/docs/.vitepress/theme/components/FundingHero.vue +78 -63
- package/docs/.vitepress/theme/components/SupportSection.vue +106 -89
- package/docs/.vitepress/theme/custom-app.css +19 -12
- package/docs/.vitepress/theme/custom.css +33 -25
- package/docs/.vitepress/theme/index.js +19 -16
- package/docs/concepts/accessibility.md +59 -47
- package/docs/concepts/color-spaces.md +28 -6
- package/docs/concepts/distance-metrics.md +45 -30
- package/docs/concepts/hct.md +30 -27
- package/docs/concepts/image-analysis.md +52 -21
- package/docs/concepts/material-design.md +43 -17
- package/docs/concepts/theme-matching.md +64 -40
- package/docs/examples/basic-colors.md +92 -108
- package/docs/examples/creating-themes.md +104 -108
- package/docs/examples/css-refactoring.md +33 -29
- package/docs/examples/image-extraction.md +145 -138
- package/docs/getting-started.md +45 -34
- package/docs/index.md +5 -1
- package/docs/installation.md +15 -1
- package/docs/tools/accessibility.md +74 -68
- package/docs/tools/image-extraction.md +62 -54
- package/docs/tools/theme-matching.md +45 -42
- package/note.md +1 -2
- package/package.json +2 -2
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
--app-success: #10b981;
|
|
13
13
|
--app-warning: #f59e0b;
|
|
14
14
|
--app-error: #ef4444;
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
/* Override VitePress brand colors */
|
|
17
17
|
--vp-c-brand-1: var(--app-primary);
|
|
18
18
|
--vp-c-brand-2: var(--app-primary-dark);
|
|
19
19
|
--vp-c-brand-3: var(--app-secondary);
|
|
20
20
|
--vp-c-brand-soft: rgba(14, 165, 233, 0.14);
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
/* Home hero gradient */
|
|
23
23
|
--vp-home-hero-name-color: transparent;
|
|
24
24
|
--vp-home-hero-name-background: linear-gradient(
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
var(--app-primary) 30%,
|
|
27
27
|
var(--app-secondary)
|
|
28
28
|
);
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
/* Button styles */
|
|
31
31
|
--vp-button-brand-border: transparent;
|
|
32
32
|
--vp-button-brand-bg: var(--app-primary);
|
|
@@ -57,7 +57,9 @@
|
|
|
57
57
|
|
|
58
58
|
/* Feature cards with hover effects */
|
|
59
59
|
.VPFeature {
|
|
60
|
-
transition:
|
|
60
|
+
transition:
|
|
61
|
+
transform 0.2s ease,
|
|
62
|
+
box-shadow 0.2s ease;
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
.VPFeature:hover {
|
|
@@ -70,13 +72,13 @@
|
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
/* Code blocks styling */
|
|
73
|
-
div[class*=
|
|
75
|
+
div[class*="language-"] {
|
|
74
76
|
position: relative;
|
|
75
77
|
border-radius: 8px;
|
|
76
78
|
overflow: hidden;
|
|
77
79
|
}
|
|
78
80
|
|
|
79
|
-
div[class*=
|
|
81
|
+
div[class*="language-"]::before {
|
|
80
82
|
content: attr(data-lang);
|
|
81
83
|
position: absolute;
|
|
82
84
|
top: 8px;
|
|
@@ -158,11 +160,15 @@ div[class*='language-']::before {
|
|
|
158
160
|
.client-card--recommended {
|
|
159
161
|
border-color: var(--app-primary);
|
|
160
162
|
position: relative;
|
|
161
|
-
background: linear-gradient(
|
|
163
|
+
background: linear-gradient(
|
|
164
|
+
135deg,
|
|
165
|
+
var(--vp-c-bg) 0%,
|
|
166
|
+
var(--vp-c-brand-soft) 100%
|
|
167
|
+
);
|
|
162
168
|
}
|
|
163
169
|
|
|
164
170
|
.client-card--recommended::before {
|
|
165
|
-
content:
|
|
171
|
+
content: "RECOMMENDED";
|
|
166
172
|
position: absolute;
|
|
167
173
|
top: -12px;
|
|
168
174
|
left: 50%;
|
|
@@ -260,7 +266,7 @@ div[class*='language-']::before {
|
|
|
260
266
|
.client-card {
|
|
261
267
|
margin: 1rem 0;
|
|
262
268
|
}
|
|
263
|
-
|
|
269
|
+
|
|
264
270
|
.tool-card {
|
|
265
271
|
padding: 1rem;
|
|
266
272
|
}
|
|
@@ -268,7 +274,8 @@ div[class*='language-']::before {
|
|
|
268
274
|
|
|
269
275
|
/* Animation for loading states */
|
|
270
276
|
@keyframes pulse {
|
|
271
|
-
0%,
|
|
277
|
+
0%,
|
|
278
|
+
100% {
|
|
272
279
|
opacity: 1;
|
|
273
280
|
}
|
|
274
281
|
50% {
|
|
@@ -300,14 +307,14 @@ div[class*='language-']::before {
|
|
|
300
307
|
}
|
|
301
308
|
|
|
302
309
|
/* Code snippet copy button enhancement */
|
|
303
|
-
div[class*=
|
|
310
|
+
div[class*="language-"] .copy {
|
|
304
311
|
background-color: var(--vp-c-bg-soft);
|
|
305
312
|
border: 1px solid var(--vp-c-divider);
|
|
306
313
|
border-radius: 4px;
|
|
307
314
|
transition: all 0.2s ease;
|
|
308
315
|
}
|
|
309
316
|
|
|
310
|
-
div[class*=
|
|
317
|
+
div[class*="language-"] .copy:hover {
|
|
311
318
|
background-color: var(--app-primary);
|
|
312
319
|
border-color: var(--app-primary);
|
|
313
320
|
}
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
/* Base colors and fonts */
|
|
4
4
|
:root {
|
|
5
|
-
--vp-font-family-base:
|
|
6
|
-
|
|
5
|
+
--vp-font-family-base:
|
|
6
|
+
-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu",
|
|
7
|
+
"Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
|
8
|
+
--vp-font-family-mono:
|
|
9
|
+
"SF Mono", Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
7
10
|
}
|
|
8
11
|
|
|
9
12
|
/* Dark theme refinements */
|
|
@@ -142,7 +145,6 @@ html.dark {
|
|
|
142
145
|
color: var(--vp-c-text-2);
|
|
143
146
|
}
|
|
144
147
|
|
|
145
|
-
|
|
146
148
|
/* Tables - wiki style */
|
|
147
149
|
.vp-doc table {
|
|
148
150
|
margin: 20px 0;
|
|
@@ -226,7 +228,6 @@ html.dark .VPNavBarTitle .logo {
|
|
|
226
228
|
border-left: 4px solid var(--vp-c-brand);
|
|
227
229
|
}
|
|
228
230
|
|
|
229
|
-
|
|
230
231
|
/* Modern hero section */
|
|
231
232
|
.VPHero {
|
|
232
233
|
padding: 80px 24px;
|
|
@@ -280,7 +281,7 @@ html.dark .VPNavBarTitle .logo {
|
|
|
280
281
|
|
|
281
282
|
/* Floating star on hover */
|
|
282
283
|
.VPButton.alt::before {
|
|
283
|
-
content:
|
|
284
|
+
content: "⭐";
|
|
284
285
|
position: absolute;
|
|
285
286
|
font-size: 18px;
|
|
286
287
|
top: 50%;
|
|
@@ -294,7 +295,7 @@ html.dark .VPNavBarTitle .logo {
|
|
|
294
295
|
|
|
295
296
|
/* Multiple burst particles */
|
|
296
297
|
.VPButton.alt::after {
|
|
297
|
-
content:
|
|
298
|
+
content: "✨ ⭐ ✨ ⭐ ✨";
|
|
298
299
|
position: absolute;
|
|
299
300
|
font-size: 12px;
|
|
300
301
|
top: 50%;
|
|
@@ -355,11 +356,13 @@ html.dark .VPNavBarTitle .logo {
|
|
|
355
356
|
}
|
|
356
357
|
50% {
|
|
357
358
|
opacity: 1;
|
|
358
|
-
transform: translate(-50%, -50%) translateY(-100px) scale(1.5)
|
|
359
|
+
transform: translate(-50%, -50%) translateY(-100px) scale(1.5)
|
|
360
|
+
rotate(180deg);
|
|
359
361
|
}
|
|
360
362
|
100% {
|
|
361
363
|
opacity: 0;
|
|
362
|
-
transform: translate(-50%, -50%) translateY(-100px) scale(2.5)
|
|
364
|
+
transform: translate(-50%, -50%) translateY(-100px) scale(2.5)
|
|
365
|
+
rotate(360deg);
|
|
363
366
|
}
|
|
364
367
|
}
|
|
365
368
|
|
|
@@ -421,7 +424,7 @@ html.dark .VPFeature {
|
|
|
421
424
|
/* Light mode hover */
|
|
422
425
|
.VPFeature:hover {
|
|
423
426
|
border-color: var(--vp-c-brand-light);
|
|
424
|
-
box-shadow:
|
|
427
|
+
box-shadow:
|
|
425
428
|
0 12px 32px rgba(0, 0, 0, 0.1),
|
|
426
429
|
0 2px 4px rgba(0, 0, 0, 0.05);
|
|
427
430
|
}
|
|
@@ -430,7 +433,7 @@ html.dark .VPFeature {
|
|
|
430
433
|
html.dark .VPFeature:hover {
|
|
431
434
|
background: rgba(255, 255, 255, 0.05);
|
|
432
435
|
border-color: rgba(66, 184, 131, 0.5);
|
|
433
|
-
box-shadow:
|
|
436
|
+
box-shadow:
|
|
434
437
|
0 12px 32px rgba(0, 0, 0, 0.4),
|
|
435
438
|
0 0 0 1px rgba(66, 184, 131, 0.3),
|
|
436
439
|
inset 0 1px 0 0 rgba(255, 255, 255, 0.1);
|
|
@@ -438,7 +441,7 @@ html.dark .VPFeature:hover {
|
|
|
438
441
|
|
|
439
442
|
/* Gradient overlay for depth */
|
|
440
443
|
.VPFeature::before {
|
|
441
|
-
content:
|
|
444
|
+
content: "";
|
|
442
445
|
position: absolute;
|
|
443
446
|
top: 0;
|
|
444
447
|
left: 0;
|
|
@@ -511,10 +514,11 @@ html.dark .VPFeature:hover {
|
|
|
511
514
|
|
|
512
515
|
/* Pulse effect when actively exploring */
|
|
513
516
|
@keyframes explorePulse {
|
|
514
|
-
0%,
|
|
517
|
+
0%,
|
|
518
|
+
100% {
|
|
515
519
|
transform: translateY(-3px) scale(1);
|
|
516
520
|
}
|
|
517
|
-
50% {
|
|
521
|
+
50% {
|
|
518
522
|
transform: translateY(-6px) scale(1.05);
|
|
519
523
|
}
|
|
520
524
|
}
|
|
@@ -525,8 +529,13 @@ html.dark .VPFeature:hover {
|
|
|
525
529
|
|
|
526
530
|
/* Animated dots while exploring */
|
|
527
531
|
@keyframes dotDance {
|
|
528
|
-
0%,
|
|
529
|
-
|
|
532
|
+
0%,
|
|
533
|
+
100% {
|
|
534
|
+
transform: translateY(0) scale(1);
|
|
535
|
+
}
|
|
536
|
+
50% {
|
|
537
|
+
transform: translateY(-4px) scale(1.5);
|
|
538
|
+
}
|
|
530
539
|
}
|
|
531
540
|
|
|
532
541
|
.VPHome:has(.VPFeature:hover) .explore-hint .dot:nth-child(1) {
|
|
@@ -560,7 +569,7 @@ html.dark .VPFeature:hover {
|
|
|
560
569
|
|
|
561
570
|
/* Add diff symbols as pseudo-elements (hidden by default) */
|
|
562
571
|
.diff-line::before {
|
|
563
|
-
content:
|
|
572
|
+
content: "";
|
|
564
573
|
position: absolute;
|
|
565
574
|
left: 4px;
|
|
566
575
|
font-family: var(--vp-font-family-mono);
|
|
@@ -571,7 +580,7 @@ html.dark .VPFeature:hover {
|
|
|
571
580
|
|
|
572
581
|
/* Line numbers - typed out on hover */
|
|
573
582
|
.diff-line::after {
|
|
574
|
-
content:
|
|
583
|
+
content: "";
|
|
575
584
|
position: absolute;
|
|
576
585
|
right: 100%;
|
|
577
586
|
margin-right: 12px;
|
|
@@ -586,19 +595,19 @@ html.dark .VPFeature:hover {
|
|
|
586
595
|
}
|
|
587
596
|
|
|
588
597
|
.diff-delete::after {
|
|
589
|
-
content:
|
|
598
|
+
content: "6739";
|
|
590
599
|
}
|
|
591
600
|
|
|
592
601
|
.diff-add::after {
|
|
593
|
-
content:
|
|
602
|
+
content: "6740";
|
|
594
603
|
}
|
|
595
604
|
|
|
596
605
|
.diff-delete::before {
|
|
597
|
-
content:
|
|
606
|
+
content: "-";
|
|
598
607
|
}
|
|
599
608
|
|
|
600
609
|
.diff-add::before {
|
|
601
|
-
content:
|
|
610
|
+
content: "+";
|
|
602
611
|
}
|
|
603
612
|
|
|
604
613
|
/* Hover state - transform to diff view */
|
|
@@ -614,7 +623,9 @@ html.dark .VPFeature:hover {
|
|
|
614
623
|
.VPFeature:first-child:hover .diff-line::after {
|
|
615
624
|
opacity: 0.6;
|
|
616
625
|
max-width: 50px;
|
|
617
|
-
transition:
|
|
626
|
+
transition:
|
|
627
|
+
opacity 0.3s ease 0.3s,
|
|
628
|
+
max-width 0.4s ease 0.3s;
|
|
618
629
|
}
|
|
619
630
|
|
|
620
631
|
/* Ensure space for line numbers only on File Analysis card */
|
|
@@ -694,6 +705,3 @@ html.dark .VPFeature:first-child:hover .diff-add::before {
|
|
|
694
705
|
.VPSidebar::-webkit-scrollbar {
|
|
695
706
|
display: none; /* Chrome/Safari/Webkit */
|
|
696
707
|
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
@@ -1,25 +1,28 @@
|
|
|
1
|
-
import DefaultTheme from
|
|
2
|
-
import Layout from
|
|
3
|
-
import FundingLayout from
|
|
4
|
-
import DiagramModal from
|
|
5
|
-
import CodeBlock from
|
|
6
|
-
import ClientGrid from
|
|
7
|
-
import
|
|
8
|
-
import
|
|
1
|
+
import DefaultTheme from "vitepress/theme";
|
|
2
|
+
import Layout from "./Layout.vue";
|
|
3
|
+
import FundingLayout from "./FundingLayout.vue";
|
|
4
|
+
import DiagramModal from "../components/DiagramModal.vue";
|
|
5
|
+
import CodeBlock from "../components/CodeBlock.vue";
|
|
6
|
+
import ClientGrid from "../components/ClientGrid.vue";
|
|
7
|
+
import "./custom.css";
|
|
8
|
+
import "./custom-app.css";
|
|
9
9
|
|
|
10
10
|
export default {
|
|
11
11
|
extends: DefaultTheme,
|
|
12
12
|
Layout: Layout,
|
|
13
13
|
enhanceApp({ app, router }) {
|
|
14
|
-
app.component(
|
|
15
|
-
app.component(
|
|
16
|
-
app.component(
|
|
17
|
-
app.component(
|
|
14
|
+
app.component("DiagramModal", DiagramModal);
|
|
15
|
+
app.component("CodeBlock", CodeBlock);
|
|
16
|
+
app.component("ClientGrid", ClientGrid);
|
|
17
|
+
app.component("FundingLayout", FundingLayout);
|
|
18
18
|
},
|
|
19
19
|
setup() {
|
|
20
20
|
// Force dark mode on initial load
|
|
21
|
-
if (
|
|
22
|
-
|
|
21
|
+
if (
|
|
22
|
+
typeof window !== "undefined" &&
|
|
23
|
+
!localStorage.getItem("vitepress-theme-appearance")
|
|
24
|
+
) {
|
|
25
|
+
localStorage.setItem("vitepress-theme-appearance", "dark");
|
|
23
26
|
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -8,12 +8,13 @@ Coolors MCP ensures color choices meet accessibility standards through comprehen
|
|
|
8
8
|
|
|
9
9
|
The Web Content Accessibility Guidelines (WCAG) define contrast requirements for readable content:
|
|
10
10
|
|
|
11
|
-
| Level
|
|
12
|
-
|
|
13
|
-
| **AA**
|
|
14
|
-
| **AAA** | 7:1
|
|
11
|
+
| Level | Normal Text | Large Text | Non-Text |
|
|
12
|
+
| ------- | ----------- | ---------- | -------- |
|
|
13
|
+
| **AA** | 4.5:1 | 3:1 | 3:1 |
|
|
14
|
+
| **AAA** | 7:1 | 4.5:1 | N/A |
|
|
15
15
|
|
|
16
16
|
**Large text** is defined as:
|
|
17
|
+
|
|
17
18
|
- 18pt (24px) or larger
|
|
18
19
|
- 14pt (18.5px) or larger if bold
|
|
19
20
|
|
|
@@ -24,11 +25,9 @@ Contrast ratio is based on relative luminance:
|
|
|
24
25
|
```javascript
|
|
25
26
|
// Relative luminance calculation
|
|
26
27
|
function getLuminance(rgb) {
|
|
27
|
-
const [r, g, b] = rgb.map(val => {
|
|
28
|
+
const [r, g, b] = rgb.map((val) => {
|
|
28
29
|
val = val / 255;
|
|
29
|
-
return val <= 0.03928
|
|
30
|
-
? val / 12.92
|
|
31
|
-
: Math.pow((val + 0.055) / 1.055, 2.4);
|
|
30
|
+
return val <= 0.03928 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4);
|
|
32
31
|
});
|
|
33
32
|
|
|
34
33
|
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
@@ -59,19 +58,19 @@ function getContrastRatio(color1, color2) {
|
|
|
59
58
|
|
|
60
59
|
HCT's tone component directly correlates with contrast:
|
|
61
60
|
|
|
62
|
-
| Tone Difference | Approximate Contrast | Use Case
|
|
63
|
-
|
|
64
|
-
| 40
|
|
65
|
-
| 50
|
|
66
|
-
| 70
|
|
67
|
-
| 90
|
|
61
|
+
| Tone Difference | Approximate Contrast | Use Case |
|
|
62
|
+
| --------------- | -------------------- | ----------------------- |
|
|
63
|
+
| 40 | ~3:1 | Large text, UI elements |
|
|
64
|
+
| 50 | ~4.5:1 | Normal text (AA) |
|
|
65
|
+
| 70 | ~7:1 | Enhanced (AAA) |
|
|
66
|
+
| 90 | ~15:1 | Maximum readability |
|
|
68
67
|
|
|
69
68
|
### Predictable Relationships
|
|
70
69
|
|
|
71
70
|
```javascript
|
|
72
71
|
// Guaranteed accessible pairs in HCT
|
|
73
|
-
const background = { h: 200, c: 20, t: 95 };
|
|
74
|
-
const text = { h: 200, c: 20, t: 20 };
|
|
72
|
+
const background = { h: 200, c: 20, t: 95 }; // Light surface
|
|
73
|
+
const text = { h: 200, c: 20, t: 20 }; // Dark text
|
|
75
74
|
// Tone difference: 75 = guaranteed 7:1+ contrast
|
|
76
75
|
```
|
|
77
76
|
|
|
@@ -117,7 +116,9 @@ const text = { h: 200, c: 20, t: 20 }; // Dark text
|
|
|
117
116
|
## Material Design Contrast Levels
|
|
118
117
|
|
|
119
118
|
### Default Contrast (0.0)
|
|
119
|
+
|
|
120
120
|
Meets AA accessibility:
|
|
121
|
+
|
|
121
122
|
- **Guaranteed minimums**:
|
|
122
123
|
- 4.5:1 for all text and icons
|
|
123
124
|
- 3:1 for required non-text elements
|
|
@@ -125,7 +126,9 @@ Meets AA accessibility:
|
|
|
125
126
|
- 7:1 for high emphasis text
|
|
126
127
|
|
|
127
128
|
### Medium Contrast (+0.5)
|
|
129
|
+
|
|
128
130
|
Exceeds AA requirements:
|
|
131
|
+
|
|
129
132
|
- **Guaranteed minimums**:
|
|
130
133
|
- 4.5:1 for all text
|
|
131
134
|
- 3:1 for all non-text elements
|
|
@@ -134,7 +137,9 @@ Exceeds AA requirements:
|
|
|
134
137
|
- 11:1 for high emphasis
|
|
135
138
|
|
|
136
139
|
### High Contrast (+1.0)
|
|
140
|
+
|
|
137
141
|
Meets AAA accessibility:
|
|
142
|
+
|
|
138
143
|
- **Guaranteed minimums**:
|
|
139
144
|
- 7:1 for all text and icons
|
|
140
145
|
- 4.5:1 for all non-text elements
|
|
@@ -196,9 +201,9 @@ function getAccessibleTextColor(background) {
|
|
|
196
201
|
|
|
197
202
|
// Use white or black based on background
|
|
198
203
|
if (bgLuminance > 0.5) {
|
|
199
|
-
return
|
|
204
|
+
return "#000000"; // Dark text on light background
|
|
200
205
|
} else {
|
|
201
|
-
return
|
|
206
|
+
return "#ffffff"; // Light text on dark background
|
|
202
207
|
}
|
|
203
208
|
}
|
|
204
209
|
```
|
|
@@ -209,12 +214,12 @@ function getAccessibleTextColor(background) {
|
|
|
209
214
|
|
|
210
215
|
Material Design ensures these pairs always meet contrast requirements:
|
|
211
216
|
|
|
212
|
-
| Background
|
|
213
|
-
|
|
214
|
-
| surface
|
|
215
|
-
| primary
|
|
216
|
-
| primaryContainer | onPrimaryContainer | 4.5:1
|
|
217
|
-
| error
|
|
217
|
+
| Background | Foreground | Min Contrast | Use Case |
|
|
218
|
+
| ---------------- | ------------------ | ------------ | --------------- |
|
|
219
|
+
| surface | onSurface | 4.5:1 | Main content |
|
|
220
|
+
| primary | onPrimary | 4.5:1 | Primary actions |
|
|
221
|
+
| primaryContainer | onPrimaryContainer | 4.5:1 | Containers |
|
|
222
|
+
| error | onError | 4.5:1 | Error states |
|
|
218
223
|
|
|
219
224
|
### Role-Based Adjustments
|
|
220
225
|
|
|
@@ -225,17 +230,17 @@ function assignColorRoles(sourceColor, contrastLevel = 0) {
|
|
|
225
230
|
|
|
226
231
|
// Adjust tone mappings based on contrast level
|
|
227
232
|
const toneMap = {
|
|
228
|
-
primary: 40 -
|
|
233
|
+
primary: 40 - contrastLevel * 10,
|
|
229
234
|
onPrimary: 100,
|
|
230
|
-
primaryContainer: 90 +
|
|
231
|
-
onPrimaryContainer: 10 -
|
|
235
|
+
primaryContainer: 90 + contrastLevel * 5,
|
|
236
|
+
onPrimaryContainer: 10 - contrastLevel * 5,
|
|
232
237
|
};
|
|
233
238
|
|
|
234
239
|
// Ensure minimum contrast
|
|
235
240
|
return Object.entries(toneMap).map(([role, tone]) => ({
|
|
236
241
|
role,
|
|
237
242
|
color: palette[tone],
|
|
238
|
-
meetsContrast: true
|
|
243
|
+
meetsContrast: true,
|
|
239
244
|
}));
|
|
240
245
|
}
|
|
241
246
|
```
|
|
@@ -245,6 +250,7 @@ function assignColorRoles(sourceColor, contrastLevel = 0) {
|
|
|
245
250
|
### Design Guidelines
|
|
246
251
|
|
|
247
252
|
#### DO's
|
|
253
|
+
|
|
248
254
|
- ✅ Test all color combinations for contrast
|
|
249
255
|
- ✅ Provide high contrast mode options
|
|
250
256
|
- ✅ Use semantic color roles consistently
|
|
@@ -252,6 +258,7 @@ function assignColorRoles(sourceColor, contrastLevel = 0) {
|
|
|
252
258
|
- ✅ Test with real content, not just color swatches
|
|
253
259
|
|
|
254
260
|
#### DON'Ts
|
|
261
|
+
|
|
255
262
|
- ❌ Rely on color alone to convey information
|
|
256
263
|
- ❌ Use low contrast for aesthetic reasons
|
|
257
264
|
- ❌ Assume large text allows poor contrast
|
|
@@ -261,6 +268,7 @@ function assignColorRoles(sourceColor, contrastLevel = 0) {
|
|
|
261
268
|
### Testing Strategies
|
|
262
269
|
|
|
263
270
|
#### Automated Testing
|
|
271
|
+
|
|
264
272
|
```javascript
|
|
265
273
|
// Test all color combinations in theme
|
|
266
274
|
function testThemeAccessibility(theme) {
|
|
@@ -276,7 +284,7 @@ function testThemeAccessibility(theme) {
|
|
|
276
284
|
context,
|
|
277
285
|
ratio,
|
|
278
286
|
passes: ratio >= required,
|
|
279
|
-
recommendation: getRecommendation(ratio, context)
|
|
287
|
+
recommendation: getRecommendation(ratio, context),
|
|
280
288
|
});
|
|
281
289
|
});
|
|
282
290
|
|
|
@@ -285,6 +293,7 @@ function testThemeAccessibility(theme) {
|
|
|
285
293
|
```
|
|
286
294
|
|
|
287
295
|
#### Manual Testing
|
|
296
|
+
|
|
288
297
|
1. **Blur test**: Blur your vision - can you still read it?
|
|
289
298
|
2. **Grayscale test**: Convert to grayscale - still distinguishable?
|
|
290
299
|
3. **Sunlight test**: View in bright light conditions
|
|
@@ -297,14 +306,15 @@ function testThemeAccessibility(theme) {
|
|
|
297
306
|
|
|
298
307
|
Approximately 8% of men and 0.5% of women have color vision deficiency:
|
|
299
308
|
|
|
300
|
-
| Type
|
|
301
|
-
|
|
302
|
-
| Protanopia
|
|
303
|
-
| Deuteranopia | 1.2%
|
|
304
|
-
| Tritanopia
|
|
305
|
-
| Monochromacy | Rare
|
|
309
|
+
| Type | Frequency | Description | Design Impact |
|
|
310
|
+
| ------------ | --------- | --------------- | --------------------- |
|
|
311
|
+
| Protanopia | 1.3% | No red cones | Red/green confusion |
|
|
312
|
+
| Deuteranopia | 1.2% | No green cones | Red/green confusion |
|
|
313
|
+
| Tritanopia | 0.001% | No blue cones | Blue/yellow confusion |
|
|
314
|
+
| Monochromacy | Rare | No color vision | Rely on contrast only |
|
|
306
315
|
|
|
307
316
|
#### Design Strategies
|
|
317
|
+
|
|
308
318
|
```javascript
|
|
309
319
|
// Ensure information isn't conveyed by color alone
|
|
310
320
|
✓ Error: "❌ Invalid input" (icon + text)
|
|
@@ -326,15 +336,15 @@ Dark themes require special attention:
|
|
|
326
336
|
```javascript
|
|
327
337
|
// Light theme
|
|
328
338
|
const lightTheme = {
|
|
329
|
-
surface: { tone: 99 },
|
|
330
|
-
onSurface: { tone: 10 },
|
|
339
|
+
surface: { tone: 99 }, // Very light
|
|
340
|
+
onSurface: { tone: 10 }, // Very dark
|
|
331
341
|
// Contrast: ~15:1
|
|
332
342
|
};
|
|
333
343
|
|
|
334
344
|
// Dark theme - NOT just inverted
|
|
335
345
|
const darkTheme = {
|
|
336
|
-
surface: { tone: 10 },
|
|
337
|
-
onSurface: { tone: 90 },
|
|
346
|
+
surface: { tone: 10 }, // Very dark
|
|
347
|
+
onSurface: { tone: 90 }, // Light, not white
|
|
338
348
|
// Contrast: ~13:1 (slightly less harsh)
|
|
339
349
|
};
|
|
340
350
|
```
|
|
@@ -354,9 +364,9 @@ function getEffectiveColor(foreground, background, alpha) {
|
|
|
354
364
|
|
|
355
365
|
// Ensure minimum opacity for text
|
|
356
366
|
const minOpacity = {
|
|
357
|
-
normalText: 0.87,
|
|
358
|
-
secondaryText: 0.
|
|
359
|
-
disabledText: 0.38
|
|
367
|
+
normalText: 0.87, // ~87% opacity minimum
|
|
368
|
+
secondaryText: 0.6, // ~60% for secondary
|
|
369
|
+
disabledText: 0.38, // ~38% for disabled
|
|
360
370
|
};
|
|
361
371
|
```
|
|
362
372
|
|
|
@@ -366,7 +376,7 @@ const minOpacity = {
|
|
|
366
376
|
|
|
367
377
|
```jsx
|
|
368
378
|
function AccessibleButton({ color, children }) {
|
|
369
|
-
const [textColor, setTextColor] = useState(
|
|
379
|
+
const [textColor, setTextColor] = useState("#ffffff");
|
|
370
380
|
|
|
371
381
|
useEffect(() => {
|
|
372
382
|
// Automatically choose accessible text color
|
|
@@ -378,7 +388,7 @@ function AccessibleButton({ color, children }) {
|
|
|
378
388
|
<button
|
|
379
389
|
style={{
|
|
380
390
|
backgroundColor: color,
|
|
381
|
-
color: textColor
|
|
391
|
+
color: textColor,
|
|
382
392
|
}}
|
|
383
393
|
aria-label={children}
|
|
384
394
|
>
|
|
@@ -434,14 +444,14 @@ class AccessibilityValidator {
|
|
|
434
444
|
background,
|
|
435
445
|
ratio,
|
|
436
446
|
required: this.minRatio,
|
|
437
|
-
suggestion: this.suggestFix(color, background)
|
|
447
|
+
suggestion: this.suggestFix(color, background),
|
|
438
448
|
});
|
|
439
449
|
}
|
|
440
450
|
});
|
|
441
451
|
|
|
442
452
|
return {
|
|
443
453
|
valid: issues.length === 0,
|
|
444
|
-
issues
|
|
454
|
+
issues,
|
|
445
455
|
};
|
|
446
456
|
}
|
|
447
457
|
|
|
@@ -454,6 +464,7 @@ class AccessibilityValidator {
|
|
|
454
464
|
## Resources
|
|
455
465
|
|
|
456
466
|
### Testing Tools
|
|
467
|
+
|
|
457
468
|
- Chrome DevTools (Lighthouse)
|
|
458
469
|
- Firefox Accessibility Inspector
|
|
459
470
|
- axe DevTools
|
|
@@ -461,6 +472,7 @@ class AccessibilityValidator {
|
|
|
461
472
|
- Stark (Figma/Sketch plugin)
|
|
462
473
|
|
|
463
474
|
### Guidelines
|
|
475
|
+
|
|
464
476
|
- [WCAG 2.1](https://www.w3.org/WAI/WCAG21/quickref/)
|
|
465
477
|
- [Material Design Accessibility](https://m3.material.io/foundations/accessible-design)
|
|
466
478
|
- [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/)
|
|
@@ -470,4 +482,4 @@ class AccessibilityValidator {
|
|
|
470
482
|
- [HCT System](./hct.md) - Tone-based contrast
|
|
471
483
|
- [Material Design](./material-design.md) - Contrast levels
|
|
472
484
|
- [Theme Matching](./theme-matching.md) - Accessibility scoring
|
|
473
|
-
- [Color Spaces](./color-spaces.md) - Understanding luminance
|
|
485
|
+
- [Color Spaces](./color-spaces.md) - Understanding luminance
|