css-is-awesome 1.1.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.
- package/AGENTS.md +343 -0
- package/CHANGELOG.md +1036 -0
- package/CLAUDE.md +9 -0
- package/CONTRACT.md +580 -0
- package/GEMINI.md +9 -0
- package/LICENSE +21 -0
- package/LICENSE-third-party +88 -0
- package/MIGRATION.md +312 -0
- package/README.md +380 -0
- package/ROADMAP.md +714 -0
- package/THEMING.md +290 -0
- package/THREE-TIERS.md +158 -0
- package/VERSIONING.md +200 -0
- package/_index.scss +34 -0
- package/api.scss +16 -0
- package/bin/README.md +57 -0
- package/bin/cia.cjs +114 -0
- package/bin/migrate-bootstrap.cjs +427 -0
- package/bin/migrate-tailwind.cjs +832 -0
- package/css-is-awesome.instructions.md +533 -0
- package/dist/css-is-awesome.core.css +598 -0
- package/dist/css-is-awesome.core.min.css +1 -0
- package/dist/css-is-awesome.css +3733 -0
- package/dist/css-is-awesome.min.css +1 -0
- package/dist/css-is-awesome.utilities.css +2445 -0
- package/dist/css-is-awesome.utilities.min.css +1 -0
- package/dist/tokens.css +508 -0
- package/dist/tokens.d.ts +266 -0
- package/dist/tokens.min.css +1 -0
- package/figma-tokens/README.md +57 -0
- package/figma-tokens/primitives-brand.scss +121 -0
- package/figma-tokens/semantic-dark.scss +61 -0
- package/figma-tokens/semantic-light.scss +61 -0
- package/figma-tokens/tokens.json +144 -0
- package/llm.txt +183 -0
- package/mcp/server.cjs +1495 -0
- package/package.json +199 -0
- package/public/icons/README.md +278 -0
- package/public/icons/arrow-right.svg +4 -0
- package/public/icons/check.svg +4 -0
- package/public/icons/chevron-down.svg +4 -0
- package/public/icons/close.svg +4 -0
- package/public/icons/core/arrow-down.svg +13 -0
- package/public/icons/core/arrow-left.svg +13 -0
- package/public/icons/core/arrow-right.svg +13 -0
- package/public/icons/core/arrow-up.svg +13 -0
- package/public/icons/core/bell.svg +13 -0
- package/public/icons/core/calendar.svg +15 -0
- package/public/icons/core/check.svg +12 -0
- package/public/icons/core/chevron-down.svg +12 -0
- package/public/icons/core/chevron-left.svg +12 -0
- package/public/icons/core/chevron-right.svg +12 -0
- package/public/icons/core/chevron-up.svg +12 -0
- package/public/icons/core/clock.svg +13 -0
- package/public/icons/core/close.svg +13 -0
- package/public/icons/core/copy.svg +13 -0
- package/public/icons/core/download.svg +14 -0
- package/public/icons/core/edit.svg +13 -0
- package/public/icons/core/error.svg +14 -0
- package/public/icons/core/external-link.svg +14 -0
- package/public/icons/core/eye-off.svg +15 -0
- package/public/icons/core/eye.svg +13 -0
- package/public/icons/core/filter.svg +12 -0
- package/public/icons/core/heart.svg +12 -0
- package/public/icons/core/help.svg +14 -0
- package/public/icons/core/home.svg +13 -0
- package/public/icons/core/info.svg +14 -0
- package/public/icons/core/loading.svg +12 -0
- package/public/icons/core/lock.svg +13 -0
- package/public/icons/core/mail.svg +13 -0
- package/public/icons/core/menu.svg +14 -0
- package/public/icons/core/message.svg +12 -0
- package/public/icons/core/minus.svg +12 -0
- package/public/icons/core/more-horizontal.svg +14 -0
- package/public/icons/core/pause.svg +13 -0
- package/public/icons/core/play.svg +12 -0
- package/public/icons/core/plus.svg +13 -0
- package/public/icons/core/refresh.svg +15 -0
- package/public/icons/core/save.svg +14 -0
- package/public/icons/core/search.svg +13 -0
- package/public/icons/core/settings.svg +13 -0
- package/public/icons/core/share.svg +14 -0
- package/public/icons/core/sort.svg +15 -0
- package/public/icons/core/star.svg +12 -0
- package/public/icons/core/success.svg +13 -0
- package/public/icons/core/trash.svg +16 -0
- package/public/icons/core/unlock.svg +13 -0
- package/public/icons/core/upload.svg +14 -0
- package/public/icons/core/user.svg +13 -0
- package/public/icons/core/users.svg +15 -0
- package/public/icons/core/warning.svg +14 -0
- package/public/icons/download.svg +4 -0
- package/public/icons/edit.svg +4 -0
- package/public/icons/menu.svg +4 -0
- package/public/icons/search.svg +4 -0
- package/public/theme.css +4179 -0
- package/public/themes/README.md +102 -0
- package/public/themes/boilerplate/theme.css +154 -0
- package/public/themes/boilerplate-dark/theme.css +171 -0
- package/public/themes/boilerplate-light/theme.css +171 -0
- package/public/themes/cupertino/theme.css +165 -0
- package/public/themes/cupertino-dark/theme.css +200 -0
- package/public/themes/cupertino-light/theme.css +227 -0
- package/public/themes/glass/theme.css +190 -0
- package/public/themes/glass-dark/theme.css +201 -0
- package/public/themes/glass-light/theme.css +229 -0
- package/public/themes/graphite/theme.css +155 -0
- package/public/themes/graphite-dark/theme.css +223 -0
- package/public/themes/graphite-light/theme.css +191 -0
- package/public/themes/press/theme.css +189 -0
- package/public/themes/press-dark/theme.css +199 -0
- package/public/themes/press-light/theme.css +235 -0
- package/public/themes/prism/theme.css +159 -0
- package/public/themes/prism-dark/theme.css +185 -0
- package/public/themes/prism-light/theme.css +188 -0
- package/public/themes/sketchbook/theme.css +152 -0
- package/public/themes/sketchbook-dark/theme.css +194 -0
- package/public/themes/sketchbook-light/theme.css +188 -0
- package/public/themes/terminal/theme.css +159 -0
- package/public/themes/terminal-dark/theme.css +229 -0
- package/public/themes/terminal-light/theme.css +152 -0
- package/scripts/README.md +70 -0
- package/scripts/icon-contract.json +67 -0
- package/scripts/icon-validator.js +205 -0
- package/scripts/prepare-dist.mjs +65 -0
- package/scripts/theme-a11y.js +415 -0
- package/scripts/theme-contract.json +171 -0
- package/scripts/theme-validator.js +674 -0
- package/scss/README.md +55 -0
- package/scss/_animations-utilities.scss +107 -0
- package/scss/_animations.scss +248 -0
- package/scss/_app-styles.scss +15 -0
- package/scss/_generator.scss +174 -0
- package/scss/_icons.scss +475 -0
- package/scss/_index.scss +17 -0
- package/scss/_layout.scss +597 -0
- package/scss/_mixins.scss +1036 -0
- package/scss/_system.scss +291 -0
- package/scss/_utilities.scss +377 -0
- package/scss/api.scss +61 -0
- package/scss/components/_accordion.scss +72 -0
- package/scss/components/_buttons.scss +171 -0
- package/scss/components/_copy-button.scss +69 -0
- package/scss/components/_data.scss +190 -0
- package/scss/components/_feedback.scss +197 -0
- package/scss/components/_forms.scss +272 -0
- package/scss/components/_index.scss +17 -0
- package/scss/components/_navigation.scss +186 -0
- package/scss/components/_overlay.scss +259 -0
- package/scss/components/_stepper.scss +159 -0
- package/scss/components/_tabs.scss +72 -0
- package/scss/core.scss +54 -0
- package/scss/examples/_theming.scss +107 -0
- package/scss/examples/_usage.scss +317 -0
- package/scss/main.scss +64 -0
- package/scss/recipes/README.md +110 -0
- package/scss/recipes/_bare-tags.scss +232 -0
- package/scss/recipes/_recipe-template.md +119 -0
- package/scss/recipes/combobox.md +509 -0
- package/scss/recipes/dialog.md +265 -0
- package/scss/recipes/print-to-pdf.md +263 -0
- package/scss/theme/README.md +67 -0
- package/scss/theme/_brand.scss +18 -0
- package/scss/theme/_colors-dark.scss +68 -0
- package/scss/theme/_colors-light.scss +70 -0
- package/scss/theme/_components.scss +94 -0
- package/scss/theme/_icons.scss +104 -0
- package/scss/theme/_index.scss +62 -0
- package/scss/theme/_shadows.scss +65 -0
- package/scss/theme/_shape.scss +13 -0
- package/scss/theme/_typography.scss +9 -0
- package/scss/theme/registry.json +447 -0
- package/scss/themes/boilerplate-dark.scss +212 -0
- package/scss/themes/boilerplate-light.scss +209 -0
- package/scss/themes/boilerplate.scss +181 -0
- package/scss/themes/cupertino-dark.scss +238 -0
- package/scss/themes/cupertino-light.scss +266 -0
- package/scss/themes/cupertino.scss +194 -0
- package/scss/themes/glass-dark.scss +238 -0
- package/scss/themes/glass-light.scss +266 -0
- package/scss/themes/glass.scss +220 -0
- package/scss/themes/graphite-dark.scss +260 -0
- package/scss/themes/graphite-light.scss +228 -0
- package/scss/themes/graphite.scss +182 -0
- package/scss/themes/press-dark.scss +237 -0
- package/scss/themes/press-light.scss +273 -0
- package/scss/themes/press.scss +241 -0
- package/scss/themes/prism-dark.scss +217 -0
- package/scss/themes/prism-light.scss +220 -0
- package/scss/themes/prism.scss +190 -0
- package/scss/themes/sketchbook-dark.scss +231 -0
- package/scss/themes/sketchbook-light.scss +231 -0
- package/scss/themes/sketchbook.scss +178 -0
- package/scss/themes/terminal-dark.scss +267 -0
- package/scss/themes/terminal-light.scss +175 -0
- package/scss/themes/terminal.scss +185 -0
- package/scss/tokens.scss +23 -0
- package/scss/utilities-only.scss +4 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
/**
|
|
4
|
+
* icon-validator.js
|
|
5
|
+
* --------------------------------------------------------------
|
|
6
|
+
* Validates the bundled icon packs against the contract in
|
|
7
|
+
* scripts/icon-contract.json. Mirrors theme-validator.js: every
|
|
8
|
+
* pack must declare every glyph the contract names; missing files
|
|
9
|
+
* fail CI.
|
|
10
|
+
*
|
|
11
|
+
* Zero runtime dependencies — pure fs/path/process.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* node scripts/icon-validator.js # validate every pack
|
|
15
|
+
* node scripts/icon-validator.js core # validate one pack
|
|
16
|
+
* node scripts/icon-validator.js --json # machine-readable
|
|
17
|
+
*
|
|
18
|
+
* What it checks (per pack):
|
|
19
|
+
* 1. public/icons/<pack>/<name>.svg exists for every contract glyph.
|
|
20
|
+
* 2. The file is a real SVG (starts with `<?xml` or `<svg`).
|
|
21
|
+
*
|
|
22
|
+
* What it does NOT check:
|
|
23
|
+
* - Per-theme override files. Themes opt in by declaring
|
|
24
|
+
* --cia-icon-<name> in their theme.css; they're never required
|
|
25
|
+
* to ship overrides.
|
|
26
|
+
*
|
|
27
|
+
* Exit codes:
|
|
28
|
+
* 0 — every required glyph is present
|
|
29
|
+
* 1 — at least one glyph is missing or unreadable
|
|
30
|
+
* 2 — usage error (bad contract, bad args)
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
'use strict';
|
|
34
|
+
|
|
35
|
+
const fs = require('fs');
|
|
36
|
+
const path = require('path');
|
|
37
|
+
|
|
38
|
+
const REPO_ROOT = path.resolve(__dirname, '..');
|
|
39
|
+
const CONTRACT_PATH = path.join(__dirname, 'icon-contract.json');
|
|
40
|
+
const ICONS_DIR = path.join(REPO_ROOT, 'public', 'icons');
|
|
41
|
+
|
|
42
|
+
const USE_COLOR = Boolean(process.stdout.isTTY);
|
|
43
|
+
const color = (code, s) => (USE_COLOR ? `\x1b[${code}m${s}\x1b[0m` : s);
|
|
44
|
+
const green = (s) => color('32', s);
|
|
45
|
+
const red = (s) => color('31', s);
|
|
46
|
+
const dim = (s) => color('2', s);
|
|
47
|
+
const bold = (s) => color('1', s);
|
|
48
|
+
|
|
49
|
+
function loadContract() {
|
|
50
|
+
let raw;
|
|
51
|
+
try {
|
|
52
|
+
raw = fs.readFileSync(CONTRACT_PATH, 'utf8');
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.error(red('error:') + ` could not read ${CONTRACT_PATH}\n ${err.message}`);
|
|
55
|
+
process.exit(2);
|
|
56
|
+
}
|
|
57
|
+
let parsed;
|
|
58
|
+
try {
|
|
59
|
+
parsed = JSON.parse(raw);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.error(red('error:') + ` icon-contract.json is not valid JSON: ${err.message}`);
|
|
62
|
+
process.exit(2);
|
|
63
|
+
}
|
|
64
|
+
if (!parsed || !parsed.packs || typeof parsed.packs !== 'object') {
|
|
65
|
+
console.error(red('error:') + ' icon-contract.json missing `packs` object');
|
|
66
|
+
process.exit(2);
|
|
67
|
+
}
|
|
68
|
+
return parsed;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function validatePack(packName, packSpec) {
|
|
72
|
+
const packDir = path.join(ICONS_DIR, packName);
|
|
73
|
+
const result = {
|
|
74
|
+
pack: packName,
|
|
75
|
+
dir: packDir,
|
|
76
|
+
glyphsExpected: packSpec.glyphs.length,
|
|
77
|
+
glyphsFound: 0,
|
|
78
|
+
missing: [],
|
|
79
|
+
malformed: [],
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
if (!fs.existsSync(packDir)) {
|
|
83
|
+
result.missing = [...packSpec.glyphs];
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (const glyph of packSpec.glyphs) {
|
|
88
|
+
const file = path.join(packDir, `${glyph}.svg`);
|
|
89
|
+
if (!fs.existsSync(file)) {
|
|
90
|
+
result.missing.push(glyph);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
let head;
|
|
94
|
+
try {
|
|
95
|
+
head = fs.readFileSync(file, 'utf8').slice(0, 200).trimStart();
|
|
96
|
+
} catch (err) {
|
|
97
|
+
result.malformed.push({ glyph, reason: err.message });
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (!/^<\?xml/.test(head) && !/^<svg/.test(head)) {
|
|
101
|
+
result.malformed.push({ glyph, reason: 'does not start with <?xml or <svg' });
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
result.glyphsFound++;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function reportPack(r) {
|
|
111
|
+
const ok = r.missing.length === 0 && r.malformed.length === 0;
|
|
112
|
+
if (ok) {
|
|
113
|
+
console.log(
|
|
114
|
+
`${green('✓')} ${bold('pack:' + r.pack)} ${dim(
|
|
115
|
+
`(${r.glyphsFound}/${r.glyphsExpected} glyphs)`
|
|
116
|
+
)}`
|
|
117
|
+
);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
console.log(`${red('x')} ${bold('pack:' + r.pack)}`);
|
|
121
|
+
if (r.missing.length > 0) {
|
|
122
|
+
console.log(red(` missing (${r.missing.length}):`));
|
|
123
|
+
for (const g of r.missing) console.log(` ${red(g)}`);
|
|
124
|
+
}
|
|
125
|
+
if (r.malformed.length > 0) {
|
|
126
|
+
console.log(red(` malformed (${r.malformed.length}):`));
|
|
127
|
+
for (const m of r.malformed) console.log(` ${red(m.glyph)} — ${dim(m.reason)}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function printUsage() {
|
|
132
|
+
console.log(
|
|
133
|
+
[
|
|
134
|
+
'Usage:',
|
|
135
|
+
' node scripts/icon-validator.js [pack-name ...]',
|
|
136
|
+
' node scripts/icon-validator.js --json',
|
|
137
|
+
'',
|
|
138
|
+
'With no args, validates every pack listed in scripts/icon-contract.json.',
|
|
139
|
+
].join('\n')
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function main(argv) {
|
|
144
|
+
const args = argv.slice(2);
|
|
145
|
+
if (args.includes('-h') || args.includes('--help')) {
|
|
146
|
+
printUsage();
|
|
147
|
+
process.exit(0);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const contract = loadContract();
|
|
151
|
+
const wantJson = args.includes('--json');
|
|
152
|
+
const named = args.filter((a) => !a.startsWith('-'));
|
|
153
|
+
|
|
154
|
+
const allPacks = Object.keys(contract.packs);
|
|
155
|
+
const packsToCheck = named.length > 0 ? named : allPacks;
|
|
156
|
+
|
|
157
|
+
for (const p of packsToCheck) {
|
|
158
|
+
if (!contract.packs[p]) {
|
|
159
|
+
console.error(red('error:') + ` unknown pack '${p}'. Known: ${allPacks.join(', ')}`);
|
|
160
|
+
process.exit(2);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (!wantJson) {
|
|
165
|
+
console.log(
|
|
166
|
+
dim(`icon-validator — contract v${contract.version} (${packsToCheck.length} pack(s))`)
|
|
167
|
+
);
|
|
168
|
+
console.log('');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const results = packsToCheck.map((p) => validatePack(p, contract.packs[p]));
|
|
172
|
+
|
|
173
|
+
if (wantJson) {
|
|
174
|
+
process.stdout.write(JSON.stringify({ ok: results.every(r => r.missing.length === 0 && r.malformed.length === 0), results }, null, 2) + '\n');
|
|
175
|
+
process.exit(results.every(r => r.missing.length === 0 && r.malformed.length === 0) ? 0 : 1);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let failed = false;
|
|
179
|
+
for (const r of results) {
|
|
180
|
+
reportPack(r);
|
|
181
|
+
if (r.missing.length > 0 || r.malformed.length > 0) failed = true;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
console.log('');
|
|
185
|
+
if (failed) {
|
|
186
|
+
console.log(red(bold('FAIL')) + dim(' — one or more packs incomplete'));
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
console.log(
|
|
190
|
+
green(bold('OK')) +
|
|
191
|
+
dim(
|
|
192
|
+
` — ${results.length} pack(s), ${results.reduce(
|
|
193
|
+
(n, r) => n + r.glyphsFound,
|
|
194
|
+
0
|
|
195
|
+
)} glyph(s) validated`
|
|
196
|
+
)
|
|
197
|
+
);
|
|
198
|
+
process.exit(0);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = { validatePack, loadContract };
|
|
202
|
+
|
|
203
|
+
if (require.main === module) {
|
|
204
|
+
main(process.argv);
|
|
205
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// prepare-dist.mjs — npm `prepare` hook
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// `dist/` is gitignored, and npm runs `prepublishOnly` ONLY when publishing.
|
|
6
|
+
// It runs `prepare` in three cases: after `npm install` in a clone, before
|
|
7
|
+
// `npm pack`/`publish`, AND when a consumer installs the package from a git
|
|
8
|
+
// URL.
|
|
9
|
+
//
|
|
10
|
+
// That last case is the one that bit us. With only `prepublishOnly`, a
|
|
11
|
+
// consumer pinning `git+https://github.com/…/css-is-awesome.git#<sha>` — which
|
|
12
|
+
// is the ONLY way to install cia until it is published to npm — received a
|
|
13
|
+
// package with no `dist/` at all. Every CSS entry point silently pointed at a
|
|
14
|
+
// file that did not exist:
|
|
15
|
+
//
|
|
16
|
+
// exports["."].style -> ./dist/css-is-awesome.css (missing)
|
|
17
|
+
// exports["."].default -> ./dist/css-is-awesome.css (missing)
|
|
18
|
+
// exports["./core"] -> ./dist/css-is-awesome.core.css (missing)
|
|
19
|
+
//
|
|
20
|
+
// SCSS consumers were unaffected (scss/ is committed), which is why it went
|
|
21
|
+
// unnoticed — the one consumer we have uses the SCSS path.
|
|
22
|
+
//
|
|
23
|
+
// Guards:
|
|
24
|
+
// - Skips when dist/ is already populated, so a plain `npm install` in a
|
|
25
|
+
// working clone doesn't pay for a rebuild every time.
|
|
26
|
+
// - Skips when the build inputs are absent (installing from a published
|
|
27
|
+
// tarball, where dist/ ships prebuilt and scss/ may be all that's needed).
|
|
28
|
+
// - Never fails the install: a broken prepare would make cia impossible to
|
|
29
|
+
// install at all, which is worse than a missing dist/.
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
import { execSync } from "node:child_process";
|
|
33
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
34
|
+
import path from "node:path";
|
|
35
|
+
import { fileURLToPath } from "node:url";
|
|
36
|
+
|
|
37
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
38
|
+
const DIST = path.join(ROOT, "dist");
|
|
39
|
+
const SCSS = path.join(ROOT, "scss");
|
|
40
|
+
|
|
41
|
+
function distIsPopulated() {
|
|
42
|
+
try {
|
|
43
|
+
return readdirSync(DIST).some((f) => f.endsWith(".css"));
|
|
44
|
+
} catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (distIsPopulated()) {
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!existsSync(SCSS)) {
|
|
54
|
+
// No sources to build from — nothing sensible to do.
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
console.log("prepare: dist/ is empty — building CSS bundles…");
|
|
60
|
+
execSync("npm run build:css:all", { cwd: ROOT, stdio: "inherit", shell: true });
|
|
61
|
+
} catch (err) {
|
|
62
|
+
// Deliberately non-fatal. SCSS consumers work without dist/; failing here
|
|
63
|
+
// would break the install outright.
|
|
64
|
+
console.warn(`prepare: could not build dist/ (${err.message}). SCSS entry points still work.`);
|
|
65
|
+
}
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const AUDIT_PAIRS = [
|
|
5
|
+
{ fg: '--text-primary', bg: '--paper', required: 4.5, kind: 'text', note: 'primary body text on the page background' },
|
|
6
|
+
{ fg: '--text-secondary', bg: '--paper', required: 4.5, kind: 'text', note: 'secondary body text on the page background' },
|
|
7
|
+
{ fg: '--text-tertiary', bg: '--paper', required: 3.0, kind: 'large-text', warnIfBelow: 4.5, note: 'tertiary text - legal at large sizes; warn if used as body' },
|
|
8
|
+
{ fg: '--text-muted', bg: '--paper', required: 4.5, kind: 'text', note: 'muted UI text on the page background' },
|
|
9
|
+
{ fg: '--ink', bg: '--paper', required: 4.5, kind: 'text', note: 'native ink on native paper' },
|
|
10
|
+
{ fg: '--ink-soft', bg: '--paper', required: 4.5, kind: 'text', note: 'soft ink on native paper' },
|
|
11
|
+
{ fg: '--ink-faint', bg: '--paper', required: 3.0, kind: 'large-text', warnIfBelow: 4.5, note: 'faint ink - large/UI only; warn if used as body' },
|
|
12
|
+
{ fg: '--text-link', bg: '--paper', required: 4.5, kind: 'text', note: 'inline link color on the page background' },
|
|
13
|
+
{ fg: '--text-inverse', bg: '--action-primary-default', required: 4.5, kind: 'text', note: 'button label on the primary action surface' },
|
|
14
|
+
{ fg: '--text-inverse', bg: '--ai', required: 4.5, kind: 'text', note: 'inverse text on the AI/brand accent surface' },
|
|
15
|
+
{ fg: '--success-text', bg: '--success-subtle', required: 4.5, kind: 'text', note: 'success message text on its subtle background' },
|
|
16
|
+
{ fg: '--warning-text', bg: '--warning-subtle', required: 4.5, kind: 'text', note: 'warning message text on its subtle background' },
|
|
17
|
+
{ fg: '--error-text', bg: '--error-subtle', required: 4.5, kind: 'text', note: 'error message text on its subtle background' },
|
|
18
|
+
{ fg: '--info-text', bg: '--info-subtle', required: 4.5, kind: 'text', note: 'info message text on its subtle background' },
|
|
19
|
+
{ fg: '--border-default', bg: '--paper', required: 3.0, kind: 'non-text', decorative: true, note: 'default border against the page background - decorative; WCAG 2.2 SC 1.4.11 applies only to graphical objects essential for understanding content' },
|
|
20
|
+
{ fg: '--border-focus', bg: '--paper', required: 3.0, kind: 'non-text', note: 'focus ring against the page background' },
|
|
21
|
+
{ fg: '--shu', bg: '--paper', required: 3.0, kind: 'non-text', note: 'seal accent - must read as a UI mark on paper' },
|
|
22
|
+
|
|
23
|
+
// Code palette. Every one of these six tokens exists to put coloured text on
|
|
24
|
+
// --code-bg, which makes them the most obviously gradeable pairs in the
|
|
25
|
+
// contract — and they were ungraded until 2026-08-29. Syntax highlighting is
|
|
26
|
+
// body text: someone reads it, so 4.5:1 applies. A code block whose comments
|
|
27
|
+
// or string literals sit at 3.6:1 is a real legibility failure, and it hid
|
|
28
|
+
// here precisely because nobody thought of a token named "green" as text.
|
|
29
|
+
{ fg: '--code-ink', bg: '--code-bg', required: 4.5, kind: 'text', note: 'default code text on the code surface' },
|
|
30
|
+
{ fg: '--code-muted', bg: '--code-bg', required: 4.5, kind: 'text', note: 'code comments on the code surface - read as prose, so full text contrast' },
|
|
31
|
+
{ fg: '--code-accent', bg: '--code-bg', required: 4.5, kind: 'text', note: 'keyword / accent token on the code surface' },
|
|
32
|
+
{ fg: '--code-blue', bg: '--code-bg', required: 4.5, kind: 'text', note: 'blue syntax token on the code surface' },
|
|
33
|
+
{ fg: '--code-green', bg: '--code-bg', required: 4.5, kind: 'text', note: 'green syntax token (strings) on the code surface' },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const NAMED_COLORS = Object.freeze({
|
|
37
|
+
aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],
|
|
38
|
+
azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],
|
|
39
|
+
blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],
|
|
40
|
+
burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],
|
|
41
|
+
coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],
|
|
42
|
+
cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],
|
|
43
|
+
darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],
|
|
44
|
+
darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],
|
|
45
|
+
darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],
|
|
46
|
+
darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],
|
|
47
|
+
deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],
|
|
48
|
+
dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],
|
|
49
|
+
fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],
|
|
50
|
+
goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],
|
|
51
|
+
grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],
|
|
52
|
+
indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],
|
|
53
|
+
lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],
|
|
54
|
+
lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],
|
|
55
|
+
lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],
|
|
56
|
+
lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],
|
|
57
|
+
lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],
|
|
58
|
+
linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],
|
|
59
|
+
mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],
|
|
60
|
+
mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],
|
|
61
|
+
midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],
|
|
62
|
+
navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],
|
|
63
|
+
olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],
|
|
64
|
+
palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],
|
|
65
|
+
papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],
|
|
66
|
+
plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],
|
|
67
|
+
red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],
|
|
68
|
+
salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],
|
|
69
|
+
sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],
|
|
70
|
+
slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],
|
|
71
|
+
steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],
|
|
72
|
+
tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],
|
|
73
|
+
white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
class UnsupportedColor extends Error {
|
|
77
|
+
constructor(reason, raw) { super('unsupported color (' + reason + '): ' + raw); this.name = 'UnsupportedColor'; this.reason = reason; this.raw = raw; }
|
|
78
|
+
}
|
|
79
|
+
class InvalidColor extends Error {
|
|
80
|
+
constructor(raw) { super('invalid color: ' + raw); this.name = 'InvalidColor'; this.raw = raw; }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function clamp01(n) { if (n < 0) return 0; if (n > 1) return 1; return n; }
|
|
84
|
+
|
|
85
|
+
function parseAlpha(raw) {
|
|
86
|
+
const s = String(raw).trim();
|
|
87
|
+
if (s.endsWith('%')) return clamp01(parseFloat(s) / 100);
|
|
88
|
+
const n = parseFloat(s);
|
|
89
|
+
if (!Number.isFinite(n)) return 1;
|
|
90
|
+
return clamp01(n);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function parseChannel(raw) {
|
|
94
|
+
const s = String(raw).trim();
|
|
95
|
+
if (s.endsWith('%')) return Math.max(0, Math.min(255, (parseFloat(s) / 100) * 255));
|
|
96
|
+
const n = parseFloat(s);
|
|
97
|
+
if (!Number.isFinite(n)) throw new InvalidColor(raw);
|
|
98
|
+
return Math.max(0, Math.min(255, n));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function hslToRgb(h, s, l) {
|
|
102
|
+
h = ((h % 360) + 360) % 360;
|
|
103
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
104
|
+
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
|
|
105
|
+
const m = l - c / 2;
|
|
106
|
+
let r1 = 0, g1 = 0, b1 = 0;
|
|
107
|
+
if (h < 60) { r1 = c; g1 = x; b1 = 0; }
|
|
108
|
+
else if (h < 120) { r1 = x; g1 = c; b1 = 0; }
|
|
109
|
+
else if (h < 180) { r1 = 0; g1 = c; b1 = x; }
|
|
110
|
+
else if (h < 240) { r1 = 0; g1 = x; b1 = c; }
|
|
111
|
+
else if (h < 300) { r1 = x; g1 = 0; b1 = c; }
|
|
112
|
+
else { r1 = c; g1 = 0; b1 = x; }
|
|
113
|
+
return [(r1 + m) * 255, (g1 + m) * 255, (b1 + m) * 255];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function tokenizeFnArgs(inner) {
|
|
117
|
+
if (inner.indexOf(',') >= 0) return inner.split(',').map(function(s){return s.trim();}).filter(Boolean);
|
|
118
|
+
const slashIdx = inner.indexOf('/');
|
|
119
|
+
if (slashIdx >= 0) {
|
|
120
|
+
const left = inner.slice(0, slashIdx).trim();
|
|
121
|
+
const right = inner.slice(slashIdx + 1).trim();
|
|
122
|
+
return left.split(/\s+/).filter(Boolean).concat([right]);
|
|
123
|
+
}
|
|
124
|
+
return inner.split(/\s+/).filter(Boolean);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* Comma-split that respects nesting, so light-dark(rgba(15,23,42,.04), #fff)
|
|
128
|
+
yields two args instead of five. tokenizeFnArgs above splits naively and is
|
|
129
|
+
only safe for the flat rgb()/hsl() arg lists it was written for. */
|
|
130
|
+
function splitTopLevelArgs(inner) {
|
|
131
|
+
const out = [];
|
|
132
|
+
let depth = 0;
|
|
133
|
+
let cur = '';
|
|
134
|
+
for (let i = 0; i < inner.length; i++) {
|
|
135
|
+
const ch = inner.charAt(i);
|
|
136
|
+
if (ch === '(') depth++;
|
|
137
|
+
else if (ch === ')') depth--;
|
|
138
|
+
if (ch === ',' && depth === 0) { out.push(cur.trim()); cur = ''; continue; }
|
|
139
|
+
cur += ch;
|
|
140
|
+
}
|
|
141
|
+
if (cur.trim()) out.push(cur.trim());
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function stripValueComments(s) { return String(s).replace(/\/\*[\s\S]*?\*\//g, '').trim(); }
|
|
146
|
+
|
|
147
|
+
function parseColor(rawValue) {
|
|
148
|
+
if (rawValue == null) throw new InvalidColor(String(rawValue));
|
|
149
|
+
let s = stripValueComments(rawValue).toLowerCase();
|
|
150
|
+
if (s === '' || s === 'none') throw new InvalidColor(rawValue);
|
|
151
|
+
if (s === 'transparent') return { r: 0, g: 0, b: 0, a: 0 };
|
|
152
|
+
if (s === 'currentcolor' || s === 'inherit' || s === 'initial' || s === 'unset' || s === 'revert') {
|
|
153
|
+
throw new UnsupportedColor('keyword', rawValue);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (s.charAt(0) === '#') {
|
|
157
|
+
const h = s.slice(1);
|
|
158
|
+
let r, g, b, a = 1;
|
|
159
|
+
if (h.length === 3 || h.length === 4) {
|
|
160
|
+
if (!/^[0-9a-f]+$/.test(h)) throw new InvalidColor(rawValue);
|
|
161
|
+
r = parseInt(h.charAt(0) + h.charAt(0), 16);
|
|
162
|
+
g = parseInt(h.charAt(1) + h.charAt(1), 16);
|
|
163
|
+
b = parseInt(h.charAt(2) + h.charAt(2), 16);
|
|
164
|
+
if (h.length === 4) a = parseInt(h.charAt(3) + h.charAt(3), 16) / 255;
|
|
165
|
+
} else if (h.length === 6 || h.length === 8) {
|
|
166
|
+
if (!/^[0-9a-f]+$/.test(h)) throw new InvalidColor(rawValue);
|
|
167
|
+
r = parseInt(h.slice(0, 2), 16);
|
|
168
|
+
g = parseInt(h.slice(2, 4), 16);
|
|
169
|
+
b = parseInt(h.slice(4, 6), 16);
|
|
170
|
+
if (h.length === 8) a = parseInt(h.slice(6, 8), 16) / 255;
|
|
171
|
+
} else {
|
|
172
|
+
throw new InvalidColor(rawValue);
|
|
173
|
+
}
|
|
174
|
+
return { r: r, g: g, b: b, a: a };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const fnMatch = /^(rgba?|hsla?)\(([^)]+)\)$/.exec(s);
|
|
178
|
+
if (fnMatch) {
|
|
179
|
+
const fn = fnMatch[1];
|
|
180
|
+
const args = tokenizeFnArgs(fnMatch[2]);
|
|
181
|
+
if (fn === 'rgb' || fn === 'rgba') {
|
|
182
|
+
if (args.length < 3 || args.length > 4) throw new InvalidColor(rawValue);
|
|
183
|
+
const r = parseChannel(args[0]);
|
|
184
|
+
const g = parseChannel(args[1]);
|
|
185
|
+
const b = parseChannel(args[2]);
|
|
186
|
+
const a = args.length === 4 ? parseAlpha(args[3]) : 1;
|
|
187
|
+
return { r: r, g: g, b: b, a: a };
|
|
188
|
+
}
|
|
189
|
+
if (args.length < 3 || args.length > 4) throw new InvalidColor(rawValue);
|
|
190
|
+
let h = parseFloat(args[0]);
|
|
191
|
+
if (!Number.isFinite(h)) throw new InvalidColor(rawValue);
|
|
192
|
+
if (args[0].endsWith('rad')) h = (h * 180) / Math.PI;
|
|
193
|
+
else if (args[0].endsWith('grad')) h = (h * 360) / 400;
|
|
194
|
+
else if (args[0].endsWith('turn')) h = h * 360;
|
|
195
|
+
const sRaw = args[1];
|
|
196
|
+
const lRaw = args[2];
|
|
197
|
+
if (!sRaw.endsWith('%') || !lRaw.endsWith('%')) throw new InvalidColor(rawValue);
|
|
198
|
+
const sat = clamp01(parseFloat(sRaw) / 100);
|
|
199
|
+
const lig = clamp01(parseFloat(lRaw) / 100);
|
|
200
|
+
const a = args.length === 4 ? parseAlpha(args[3]) : 1;
|
|
201
|
+
const rgb = hslToRgb(h, sat, lig);
|
|
202
|
+
return { r: rgb[0], g: rgb[1], b: rgb[2], a: a };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (/^(oklch|oklab|lab|lch|hwb|color|color-mix)\b/.test(s)) {
|
|
206
|
+
const reason = s.split(/[(\s]/, 1)[0];
|
|
207
|
+
throw new UnsupportedColor(reason, rawValue);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (Object.prototype.hasOwnProperty.call(NAMED_COLORS, s)) {
|
|
211
|
+
const arr = NAMED_COLORS[s];
|
|
212
|
+
return { r: arr[0], g: arr[1], b: arr[2], a: 1 };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
throw new InvalidColor(rawValue);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function linearize(c8) {
|
|
219
|
+
const c = c8 / 255;
|
|
220
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function relativeLuminance(rgb) {
|
|
224
|
+
const R = linearize(rgb.r);
|
|
225
|
+
const G = linearize(rgb.g);
|
|
226
|
+
const B = linearize(rgb.b);
|
|
227
|
+
return 0.2126 * R + 0.7152 * G + 0.0722 * B;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function contrastRatio(c1, c2) {
|
|
231
|
+
const L1 = relativeLuminance(c1);
|
|
232
|
+
const L2 = relativeLuminance(c2);
|
|
233
|
+
const light = Math.max(L1, L2);
|
|
234
|
+
const dark = Math.min(L1, L2);
|
|
235
|
+
return (light + 0.05) / (dark + 0.05);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function composite(fg, bg) {
|
|
239
|
+
const a = fg.a;
|
|
240
|
+
if (a >= 1) return { r: fg.r, g: fg.g, b: fg.b, a: 1 };
|
|
241
|
+
const r = fg.r * a + bg.r * (1 - a);
|
|
242
|
+
const g = fg.g * a + bg.g * (1 - a);
|
|
243
|
+
const b = fg.b * a + bg.b * (1 - a);
|
|
244
|
+
return { r: r, g: g, b: b, a: 1 };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function extractTokenValuesFromBlock(blockBody) {
|
|
248
|
+
const out = new Map();
|
|
249
|
+
const re = /(--[A-Za-z_][A-Za-z0-9_-]*)\s*:/g;
|
|
250
|
+
let m;
|
|
251
|
+
while ((m = re.exec(blockBody)) !== null) {
|
|
252
|
+
const name = m[1];
|
|
253
|
+
let i = m.index + m[0].length;
|
|
254
|
+
let depth = 0;
|
|
255
|
+
let value = '';
|
|
256
|
+
while (i < blockBody.length) {
|
|
257
|
+
const ch = blockBody.charAt(i);
|
|
258
|
+
if (ch === '(') depth++;
|
|
259
|
+
else if (ch === ')') depth--;
|
|
260
|
+
else if (ch === ';' && depth === 0) break;
|
|
261
|
+
else if (ch === '}' && depth === 0) break;
|
|
262
|
+
value += ch;
|
|
263
|
+
i++;
|
|
264
|
+
}
|
|
265
|
+
out.set(name, value.trim());
|
|
266
|
+
}
|
|
267
|
+
return out;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function resolveTokenValue(tokenName, values, globals) {
|
|
271
|
+
const seen = new Set();
|
|
272
|
+
const chain = [];
|
|
273
|
+
let current = tokenName;
|
|
274
|
+
while (true) {
|
|
275
|
+
if (seen.has(current)) return { value: null, chain: chain, error: 'cycle detected at ' + current };
|
|
276
|
+
seen.add(current);
|
|
277
|
+
chain.push(current);
|
|
278
|
+
let raw = values.get(current);
|
|
279
|
+
if (raw == null && globals) raw = globals.get(current);
|
|
280
|
+
if (raw == null) return { value: null, chain: chain, error: 'undefined token ' + current };
|
|
281
|
+
raw = stripValueComments(raw);
|
|
282
|
+
const varOnly = /^var\(\s*(--[A-Za-z_][A-Za-z0-9_-]*)\s*(?:,\s*([^)]*))?\)$/.exec(raw);
|
|
283
|
+
if (varOnly) {
|
|
284
|
+
const refName = varOnly[1];
|
|
285
|
+
if (values.has(refName) || (globals && globals.has(refName))) { current = refName; continue; }
|
|
286
|
+
const fallback = (varOnly[2] || '').trim();
|
|
287
|
+
if (fallback) return { value: fallback, chain: chain.concat(['(fallback) ' + fallback]), error: null };
|
|
288
|
+
return { value: null, chain: chain, error: 'undefined var ref ' + refName };
|
|
289
|
+
}
|
|
290
|
+
return { value: raw, chain: chain, error: null };
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/* Resolve a raw declaration value to RGBA for one colour scheme.
|
|
295
|
+
Handles the two wrappers parseColor() can't see through on its own:
|
|
296
|
+
light-dark(L, D) -> the branch for `scheme`
|
|
297
|
+
var(--x) -> the referenced token (or its fallback)
|
|
298
|
+
Both can nest, hence the recursion + depth guard. */
|
|
299
|
+
function parseColorValue(raw, values, globals, scheme, depth) {
|
|
300
|
+
const d = depth || 0;
|
|
301
|
+
if (d > 8) throw new InvalidColor(raw);
|
|
302
|
+
const s = stripValueComments(raw);
|
|
303
|
+
|
|
304
|
+
const ld = /^light-dark\(([\s\S]*)\)$/i.exec(s);
|
|
305
|
+
if (ld) {
|
|
306
|
+
const args = splitTopLevelArgs(ld[1]);
|
|
307
|
+
if (args.length !== 2) throw new InvalidColor(raw);
|
|
308
|
+
return parseColorValue(scheme === 'dark' ? args[1] : args[0], values, globals, scheme, d + 1);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const varOnly = /^var\(\s*(--[A-Za-z_][A-Za-z0-9_-]*)\s*(?:,([\s\S]*))?\)$/.exec(s);
|
|
312
|
+
if (varOnly) {
|
|
313
|
+
const refName = varOnly[1];
|
|
314
|
+
if ((values && values.has(refName)) || (globals && globals.has(refName))) {
|
|
315
|
+
const rr = resolveTokenValue(refName, values, globals);
|
|
316
|
+
if (!rr.value) throw new InvalidColor(s);
|
|
317
|
+
return parseColorValue(rr.value, values, globals, scheme, d + 1);
|
|
318
|
+
}
|
|
319
|
+
const fallback = (varOnly[2] || '').trim();
|
|
320
|
+
if (fallback) return parseColorValue(fallback, values, globals, scheme, d + 1);
|
|
321
|
+
throw new InvalidColor(s);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return parseColor(s);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function resolveColor(tokenName, values, globals, scheme) {
|
|
328
|
+
const r = resolveTokenValue(tokenName, values, globals);
|
|
329
|
+
if (!r.value) return { error: r.error, raw: null, chain: r.chain };
|
|
330
|
+
try {
|
|
331
|
+
const rgba = parseColorValue(r.value, values, globals, scheme || 'light');
|
|
332
|
+
return { rgba: rgba, raw: r.value, chain: r.chain };
|
|
333
|
+
} catch (err) {
|
|
334
|
+
return { error: err.message, raw: r.value, chain: r.chain, errorKind: err.name };
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function evaluatePair(pair, env, scheme, warnMargin) {
|
|
339
|
+
const paperResolved = env.values.has('--paper')
|
|
340
|
+
? resolveColor('--paper', env.values, env.globals, scheme)
|
|
341
|
+
: null;
|
|
342
|
+
const paperRgba = paperResolved && paperResolved.rgba
|
|
343
|
+
? paperResolved.rgba
|
|
344
|
+
: { r: 255, g: 255, b: 255, a: 1 };
|
|
345
|
+
|
|
346
|
+
const fg = resolveColor(pair.fg, env.values, env.globals, scheme);
|
|
347
|
+
const bg = resolveColor(pair.bg, env.values, env.globals, scheme);
|
|
348
|
+
|
|
349
|
+
if (fg.errorKind === 'UnsupportedColor' || bg.errorKind === 'UnsupportedColor') {
|
|
350
|
+
return {
|
|
351
|
+
pair: pair, status: 'skip', scheme: scheme,
|
|
352
|
+
skipReason: 'unsupported color space (oklch/oklab/etc.)',
|
|
353
|
+
fgRaw: fg.raw, bgRaw: bg.raw,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (fg.error || bg.error) {
|
|
358
|
+
return {
|
|
359
|
+
pair: pair, status: 'skip', scheme: scheme,
|
|
360
|
+
skipReason: fg.error ? (pair.fg + ': ' + fg.error) : (pair.bg + ': ' + bg.error),
|
|
361
|
+
fgRaw: fg.raw, bgRaw: bg.raw,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const bgOpaque = bg.rgba.a < 1 ? composite(bg.rgba, paperRgba) : bg.rgba;
|
|
366
|
+
const fgOpaque = fg.rgba.a < 1 ? composite(fg.rgba, bgOpaque) : fg.rgba;
|
|
367
|
+
|
|
368
|
+
const ratio = contrastRatio(fgOpaque, bgOpaque);
|
|
369
|
+
const required = pair.required;
|
|
370
|
+
let status;
|
|
371
|
+
if (ratio + 1e-6 < required) status = pair.decorative ? 'info' : 'fail';
|
|
372
|
+
else if (typeof pair.warnIfBelow === 'number' ? ratio + 1e-6 < pair.warnIfBelow : ratio < required + warnMargin) status = 'warn';
|
|
373
|
+
else status = 'pass';
|
|
374
|
+
|
|
375
|
+
return {
|
|
376
|
+
pair: pair, status: status, scheme: scheme, ratio: ratio, required: required,
|
|
377
|
+
kind: pair.kind, note: pair.note,
|
|
378
|
+
fgRaw: fg.raw, bgRaw: bg.raw, fgChain: fg.chain, bgChain: bg.chain,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function auditThemeTokens(env, options) {
|
|
383
|
+
const opts = options || {};
|
|
384
|
+
const warnMargin = typeof opts.warnMargin === 'number' ? opts.warnMargin : 0.5;
|
|
385
|
+
const out = [];
|
|
386
|
+
|
|
387
|
+
/* A light-dark() token is two colours wearing one name, so a single-scheme
|
|
388
|
+
audit can green-light a theme that's illegible in the other mode. Evaluate
|
|
389
|
+
both and keep the worse row — fail-by-default means worst case wins. */
|
|
390
|
+
for (const pair of AUDIT_PAIRS) {
|
|
391
|
+
const rows = ['light', 'dark'].map(function (scheme) {
|
|
392
|
+
return evaluatePair(pair, env, scheme, warnMargin);
|
|
393
|
+
});
|
|
394
|
+
const scored = rows.filter(function (r) { return r.status !== 'skip'; });
|
|
395
|
+
if (scored.length === 0) { out.push(rows[0]); continue; }
|
|
396
|
+
out.push(scored.reduce(function (worst, r) {
|
|
397
|
+
return r.ratio < worst.ratio ? r : worst;
|
|
398
|
+
}));
|
|
399
|
+
}
|
|
400
|
+
return out;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
module.exports = {
|
|
404
|
+
AUDIT_PAIRS: AUDIT_PAIRS,
|
|
405
|
+
parseColor: parseColor,
|
|
406
|
+
relativeLuminance: relativeLuminance,
|
|
407
|
+
contrastRatio: contrastRatio,
|
|
408
|
+
composite: composite,
|
|
409
|
+
resolveTokenValue: resolveTokenValue,
|
|
410
|
+
resolveColor: resolveColor,
|
|
411
|
+
extractTokenValuesFromBlock: extractTokenValuesFromBlock,
|
|
412
|
+
auditThemeTokens: auditThemeTokens,
|
|
413
|
+
UnsupportedColor: UnsupportedColor,
|
|
414
|
+
InvalidColor: InvalidColor,
|
|
415
|
+
};
|