zeus-css 1.0.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/README.md +81 -0
- package/bin/init.js +89 -0
- package/dist/zeus.css +8456 -0
- package/dist/zeus.min.css +1 -0
- package/package.json +44 -0
- package/scss/.stylelintrc.json +41 -0
- package/scss/API.md +168 -0
- package/scss/CHANGELOG.md +281 -0
- package/scss/LICENSE +21 -0
- package/scss/README.md +160 -0
- package/scss/cheatsheet.md +249 -0
- package/scss/dist/zeus.css +8465 -0
- package/scss/dist/zeus.css.map +1 -0
- package/scss/dist/zeus.min.css +1 -0
- package/scss/dist/zeus.min.css.map +1 -0
- package/scss/foundation/core/_api.scss +10 -0
- package/scss/foundation/core/_design.scss +33 -0
- package/scss/foundation/core/_engine.scss +18 -0
- package/scss/foundation/core/buttons/variants.scss +72 -0
- package/scss/foundation/core/design/_design-system.scss +41 -0
- package/scss/foundation/core/design/_validate.scss +114 -0
- package/scss/foundation/core/design/borders/border-line.scss +29 -0
- package/scss/foundation/core/design/borders/border-radius.scss +31 -0
- package/scss/foundation/core/design/buttons/sizes.scss +32 -0
- package/scss/foundation/core/design/colors/_color-derivation.scss +80 -0
- package/scss/foundation/core/design/colors/base-colors.scss +31 -0
- package/scss/foundation/core/design/colors/semantic-colors.scss +130 -0
- package/scss/foundation/core/design/core/basic.scss +40 -0
- package/scss/foundation/core/design/dimensions/sizes.scss +29 -0
- package/scss/foundation/core/design/dimensions/spaces.scss +42 -0
- package/scss/foundation/core/design/effects/_z-index.scss +30 -0
- package/scss/foundation/core/design/effects/shadows.scss +65 -0
- package/scss/foundation/core/design/layout/grid.scss +86 -0
- package/scss/foundation/core/design/typography/typography.scss +435 -0
- package/scss/foundation/core/engine/_fluid-core.scss +54 -0
- package/scss/foundation/core/engine/animations.scss +91 -0
- package/scss/foundation/core/engine/buttons.scss +226 -0
- package/scss/foundation/core/engine/color-api.scss +37 -0
- package/scss/foundation/core/engine/helpers.scss +39 -0
- package/scss/foundation/core/engine/layout.scss +156 -0
- package/scss/foundation/core/engine/responsive.scss +67 -0
- package/scss/foundation/core/engine/shadows.scss +186 -0
- package/scss/foundation/core/engine/shortcuts.scss +30 -0
- package/scss/foundation/core/engine/sizing.scss +49 -0
- package/scss/foundation/core/engine/theme-builder.scss +83 -0
- package/scss/foundation/core/engine/typographics.scss +44 -0
- package/scss/foundation/core/engine/typography-calc.scss +71 -0
- package/scss/foundation/foundation.scss +29 -0
- package/scss/foundation/views/_render.scss +13 -0
- package/scss/foundation/views/components/_alert.scss +103 -0
- package/scss/foundation/views/components/_avatar.scss +84 -0
- package/scss/foundation/views/components/_badge.scss +100 -0
- package/scss/foundation/views/components/_breadcrumb.scss +70 -0
- package/scss/foundation/views/components/_card.scss +117 -0
- package/scss/foundation/views/components/_dropdown.scss +123 -0
- package/scss/foundation/views/components/_form.scss +209 -0
- package/scss/foundation/views/components/_index.scss +20 -0
- package/scss/foundation/views/components/_modal.scss +138 -0
- package/scss/foundation/views/components/_pagination.scss +82 -0
- package/scss/foundation/views/components/_skeleton.scss +69 -0
- package/scss/foundation/views/components/_table.scss +94 -0
- package/scss/foundation/views/components/_tabs.scss +96 -0
- package/scss/foundation/views/components/_toast.scss +139 -0
- package/scss/foundation/views/components/_tooltip.scss +100 -0
- package/scss/foundation/views/patterns/_hero.scss +121 -0
- package/scss/foundation/views/patterns/_index.scss +8 -0
- package/scss/foundation/views/patterns/_sidebar-layout.scss +83 -0
- package/scss/foundation/views/render/_layer-order.scss +11 -0
- package/scss/foundation/views/render/classes/_animations.scss +233 -0
- package/scss/foundation/views/render/classes/_aspect-ratio.scss +17 -0
- package/scss/foundation/views/render/classes/_borders.scss +31 -0
- package/scss/foundation/views/render/classes/_buttons.scss +40 -0
- package/scss/foundation/views/render/classes/_colors.scss +83 -0
- package/scss/foundation/views/render/classes/_compounds.scss +103 -0
- package/scss/foundation/views/render/classes/_containers.scss +20 -0
- package/scss/foundation/views/render/classes/_misc-utils.scss +51 -0
- package/scss/foundation/views/render/classes/_position.scss +56 -0
- package/scss/foundation/views/render/classes/_sections.scss +30 -0
- package/scss/foundation/views/render/classes/_shadows.scss +198 -0
- package/scss/foundation/views/render/classes/_sizes.scss +39 -0
- package/scss/foundation/views/render/classes/_sizing-utils.scss +53 -0
- package/scss/foundation/views/render/classes/_spacing.scss +91 -0
- package/scss/foundation/views/render/classes/_state-variants.scss +47 -0
- package/scss/foundation/views/render/classes/_typography.scss +32 -0
- package/scss/foundation/views/render/classes/_utilities.scss +103 -0
- package/scss/foundation/views/render/classes/_z-index.scss +22 -0
- package/scss/foundation/views/render/classes/flexbox.scss +56 -0
- package/scss/foundation/views/render/classes/grid.scss +195 -0
- package/scss/foundation/views/render/classes.scss +116 -0
- package/scss/foundation/views/render/html-body.scss +54 -0
- package/scss/foundation/views/render/property-registration.scss +77 -0
- package/scss/foundation/views/render/reset.scss +147 -0
- package/scss/foundation/views/render/root-generator.scss +166 -0
- package/scss/foundation/views/render/root.scss +19 -0
- package/scss/foundation/views/render/typography.scss +129 -0
- package/scss/llms.txt +247 -0
- package/scss/package.json +89 -0
- package/scss/scripts/gen-cheatsheet.mjs +178 -0
- package/scss/scripts/gen-tokens.mjs +91 -0
- package/scss/scripts/test-compile.mjs +474 -0
- package/scss/zeus.config.scss +99 -0
- package/scss/zeus.customize.scss +246 -0
- package/scss/zeus.scss +17 -0
- package/scss/zeus.token.sets.example.scss +176 -0
- package/scss/zeus.tokens.json +687 -0
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ╔══════════════════════════════════════════════════════════════╗
|
|
3
|
+
// ║ ⚡ Zeus CSS — Compile Matrix Test ║
|
|
4
|
+
// ║ ║
|
|
5
|
+
// ║ Compiles the framework in every mode a real consumer would ║
|
|
6
|
+
// ║ use it in, and fails the run (non-zero exit code) if any ║
|
|
7
|
+
// ║ mode errors, emits a deprecation warning, or regresses a ║
|
|
8
|
+
// ║ known behavioral contract (zero-emission entry point, the ║
|
|
9
|
+
// ║ prefix system, or config overrides actually taking effect). ║
|
|
10
|
+
// ╚══════════════════════════════════════════════════════════════╝
|
|
11
|
+
|
|
12
|
+
import * as sass from "sass";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
import { dirname, join } from "node:path";
|
|
15
|
+
import { readFileSync, readdirSync, statSync, existsSync } from "node:fs";
|
|
16
|
+
|
|
17
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const ROOT = join(__dirname, "..");
|
|
19
|
+
|
|
20
|
+
let failures = 0;
|
|
21
|
+
|
|
22
|
+
function reportPass(label) {
|
|
23
|
+
console.log(` ✓ ${label}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function reportFail(label, detail) {
|
|
27
|
+
failures++;
|
|
28
|
+
console.error(` ✗ ${label}`);
|
|
29
|
+
if (detail) console.error(` ${detail}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Collects warnings/deprecations instead of letting Sass print them to
|
|
33
|
+
// stderr — lets us assert "zero warnings" as a pass/fail condition.
|
|
34
|
+
function makeLogger() {
|
|
35
|
+
const messages = [];
|
|
36
|
+
return {
|
|
37
|
+
warn(message, options) {
|
|
38
|
+
messages.push({ message, ...options });
|
|
39
|
+
},
|
|
40
|
+
messages,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function compileString(source, opts = {}) {
|
|
45
|
+
const logger = makeLogger();
|
|
46
|
+
const result = sass.compileString(source, {
|
|
47
|
+
loadPaths: [ROOT],
|
|
48
|
+
logger,
|
|
49
|
+
...opts,
|
|
50
|
+
});
|
|
51
|
+
return { css: result.css, warnings: logger.messages };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function compileFile(relativePath, opts = {}) {
|
|
55
|
+
const logger = makeLogger();
|
|
56
|
+
const result = sass.compile(join(ROOT, relativePath), {
|
|
57
|
+
loadPaths: [ROOT],
|
|
58
|
+
logger,
|
|
59
|
+
...opts,
|
|
60
|
+
});
|
|
61
|
+
return { css: result.css, warnings: logger.messages };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log("Zeus CSS — compile matrix\n");
|
|
65
|
+
|
|
66
|
+
// Captured by check 1, reused by check 8 (WCAG) so the contrast guard reads
|
|
67
|
+
// the actual compiled default palette instead of a hand-copied literal that
|
|
68
|
+
// could silently drift from zeus.customize.scss.
|
|
69
|
+
let defaultBuildCss = null;
|
|
70
|
+
|
|
71
|
+
// ── 1. Default build ─────────────────────────────────────────────
|
|
72
|
+
// The full stylesheet a consumer links once at the app root.
|
|
73
|
+
try {
|
|
74
|
+
const { css, warnings } = compileFile("foundation/foundation.scss");
|
|
75
|
+
defaultBuildCss = css;
|
|
76
|
+
if (warnings.length > 0) {
|
|
77
|
+
reportFail(
|
|
78
|
+
"1. Default build (foundation.scss)",
|
|
79
|
+
`${warnings.length} warning(s): ${warnings[0].message}`
|
|
80
|
+
);
|
|
81
|
+
} else if (css.length < 50_000) {
|
|
82
|
+
reportFail(
|
|
83
|
+
"1. Default build (foundation.scss)",
|
|
84
|
+
`output suspiciously small (${css.length} bytes) — expected >50KB`
|
|
85
|
+
);
|
|
86
|
+
} else {
|
|
87
|
+
reportPass(`1. Default build (foundation.scss) — ${(css.length / 1024).toFixed(1)}KB, 0 warnings`);
|
|
88
|
+
}
|
|
89
|
+
} catch (err) {
|
|
90
|
+
reportFail("1. Default build (foundation.scss)", err.message);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── 2. Module entry — zero-emission contract ────────────────────
|
|
94
|
+
// @use "zeus" as * must expose every mixin/function without leaking any
|
|
95
|
+
// CSS of its own. A component using a handful of mixins should compile
|
|
96
|
+
// to only its own rules — not the ~150 lines of keyframes/utility CSS
|
|
97
|
+
// that used to leak before this was fixed.
|
|
98
|
+
try {
|
|
99
|
+
const source = `
|
|
100
|
+
@use "zeus" as *;
|
|
101
|
+
.t { @include p("md"); @include text(h1); @include fade-in(); color: color("primary"); }
|
|
102
|
+
`;
|
|
103
|
+
const { css, warnings } = compileString(source);
|
|
104
|
+
const lineCount = css.trim().split("\n").length;
|
|
105
|
+
if (warnings.length > 0) {
|
|
106
|
+
reportFail("2. Module entry zero-emission", `${warnings.length} warning(s): ${warnings[0].message}`);
|
|
107
|
+
} else if (lineCount > 20) {
|
|
108
|
+
reportFail(
|
|
109
|
+
"2. Module entry zero-emission",
|
|
110
|
+
`expected a small, self-contained ruleset (<20 lines) but got ${lineCount} lines — ` +
|
|
111
|
+
`zeus.scss may be leaking top-level CSS again`
|
|
112
|
+
);
|
|
113
|
+
} else {
|
|
114
|
+
reportPass(`2. Module entry zero-emission — ${lineCount} lines, 0 warnings`);
|
|
115
|
+
}
|
|
116
|
+
} catch (err) {
|
|
117
|
+
reportFail("2. Module entry zero-emission", err.message);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ── 3. Prefix build ──────────────────────────────────────────────
|
|
121
|
+
// $zeus-use-prefix must apply consistently across every utility family.
|
|
122
|
+
try {
|
|
123
|
+
const source = `
|
|
124
|
+
@use "zeus.config" with ($zeus-use-prefix: true);
|
|
125
|
+
@use "foundation/foundation";
|
|
126
|
+
`;
|
|
127
|
+
const { css, warnings } = compileString(source);
|
|
128
|
+
const hasPrefixed = /\.z-container\b/.test(css) && /\.z-shadow-md\b/.test(css) && /\.z-grid-sidebar\b/.test(css);
|
|
129
|
+
const hasUnprefixedLeak = /(?<![-\w])\.container\s*\{/.test(css);
|
|
130
|
+
if (warnings.length > 0) {
|
|
131
|
+
reportFail("3. Prefix build", `${warnings.length} warning(s): ${warnings[0].message}`);
|
|
132
|
+
} else if (!hasPrefixed) {
|
|
133
|
+
reportFail("3. Prefix build", "expected .z-container / .z-shadow-md / .z-grid-sidebar in output");
|
|
134
|
+
} else if (hasUnprefixedLeak) {
|
|
135
|
+
reportFail("3. Prefix build", "found an unprefixed .container leaking through despite $zeus-use-prefix: true");
|
|
136
|
+
} else {
|
|
137
|
+
reportPass("3. Prefix build — prefixed classes present, no unprefixed leaks, 0 warnings");
|
|
138
|
+
}
|
|
139
|
+
} catch (err) {
|
|
140
|
+
reportFail("3. Prefix build", err.message);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── 4. Config override contract ──────────────────────────────────
|
|
144
|
+
// Whatever a developer writes in $zeus-colors / $zeus-column-gaps must
|
|
145
|
+
// win over the framework's own derived/default values.
|
|
146
|
+
try {
|
|
147
|
+
const source = `
|
|
148
|
+
@use "zeus.config" with (
|
|
149
|
+
$zeus-column-gaps: (lg: (floor: 30, ceil: 77)),
|
|
150
|
+
$zeus-colors: ("primary": oklch(60% 0.2 200))
|
|
151
|
+
);
|
|
152
|
+
@use "foundation/foundation";
|
|
153
|
+
`;
|
|
154
|
+
const { css, warnings } = compileString(source);
|
|
155
|
+
const gapOk = css.includes("clamp(30px") && css.includes("77px");
|
|
156
|
+
const colorOk = css.includes("--color-primary: oklch(60% 0.2 200deg)");
|
|
157
|
+
if (warnings.length > 0) {
|
|
158
|
+
reportFail("4. Config override contract", `${warnings.length} warning(s): ${warnings[0].message}`);
|
|
159
|
+
} else if (!gapOk || !colorOk) {
|
|
160
|
+
reportFail(
|
|
161
|
+
"4. Config override contract",
|
|
162
|
+
`override did not apply — gap ok: ${gapOk}, color ok: ${colorOk}`
|
|
163
|
+
);
|
|
164
|
+
} else {
|
|
165
|
+
reportPass("4. Config override contract — floor/ceil and color overrides both applied, 0 warnings");
|
|
166
|
+
}
|
|
167
|
+
} catch (err) {
|
|
168
|
+
reportFail("4. Config override contract", err.message);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ── 5. Public API smoke test — every public mixin/function ───────
|
|
172
|
+
// P2-19: check 2 only ever exercised 3 hand-picked mixins. A mixin that's
|
|
173
|
+
// public but never called by the framework's own default build (e.g.
|
|
174
|
+
// btn-icon-only() before P0-3) can carry a broken internal @use and nobody
|
|
175
|
+
// notices until a consumer reaches for it directly. This calls every public
|
|
176
|
+
// mixin/function (same "no leading _" convention as gen-cheatsheet.mjs) at
|
|
177
|
+
// least once through the zero-emission entry point.
|
|
178
|
+
try {
|
|
179
|
+
const source = `
|
|
180
|
+
@use "zeus" as *;
|
|
181
|
+
|
|
182
|
+
.smoke {
|
|
183
|
+
@include fade-in(); @include slide-in(); @include scale();
|
|
184
|
+
@include bounce(); @include pulse(); @include rotate();
|
|
185
|
+
@include button-base-reset(); @include button-structure();
|
|
186
|
+
@include button-apply-variant(); @include btn-primary();
|
|
187
|
+
@include btn-secondary(); @include btn-tertiary(); @include btn-icon-only();
|
|
188
|
+
@include btn-variant(); @include btn-size();
|
|
189
|
+
@include border-radius("md"); @include border-line("md"); @include box-size("md");
|
|
190
|
+
@include padding("md"); @include margin("md"); @include gap("md");
|
|
191
|
+
@include apply-shadow(); @include elevation(); @include hover-shadow();
|
|
192
|
+
@include focus-shadow(); @include interactive-shadow(); @include card-shadow();
|
|
193
|
+
@include semantic-shadow(); @include glow-effect(); @include inner-shadow();
|
|
194
|
+
@include layered-shadow("md", "primary"); @include conditional-shadow();
|
|
195
|
+
@include no-shadow();
|
|
196
|
+
@include p("md"); @include m("md"); @include g("md"); @include r("md");
|
|
197
|
+
@include t(h1); @include sz("md"); @include text(h1);
|
|
198
|
+
@include not-first-child(); @include not-last-child();
|
|
199
|
+
@include horizontal-scroll(); @include no-horizontal-scroll();
|
|
200
|
+
@include suppress-on("mobile");
|
|
201
|
+
--smoke-color: #{color("primary")};
|
|
202
|
+
--smoke-bp: #{bp("lg")};
|
|
203
|
+
--smoke-sizing: #{useclamp-sizing(10, 20)};
|
|
204
|
+
--smoke-typography: #{useclamp-typography("h1")};
|
|
205
|
+
--smoke-map: #{map-get-safe(("a": 1), "a")};
|
|
206
|
+
}
|
|
207
|
+
@include screen-above("lg") { .x { color: red; } }
|
|
208
|
+
@include screen-below("lg") { .x { color: red; } }
|
|
209
|
+
@include screen-between("sm", "lg") { .x { color: red; } }
|
|
210
|
+
@include zeus-theme("smoke-test", (
|
|
211
|
+
primary: #123456, secondary: #654321, accent: #abcdef,
|
|
212
|
+
body: #ffffff, text: #000000,
|
|
213
|
+
));
|
|
214
|
+
`;
|
|
215
|
+
const { warnings } = compileString(source);
|
|
216
|
+
if (warnings.length > 0) {
|
|
217
|
+
reportFail("5. Public API smoke test", `${warnings.length} warning(s): ${warnings[0].message}`);
|
|
218
|
+
} else {
|
|
219
|
+
reportPass("5. Public API smoke test — every public mixin/function invoked, 0 warnings");
|
|
220
|
+
}
|
|
221
|
+
} catch (err) {
|
|
222
|
+
reportFail("5. Public API smoke test", err.message);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ── 6. Config variable usage analysis ─────────────────────────────
|
|
226
|
+
// P2-19 "ανάλυση μεταβλητών": every $zeus-* variable declared in
|
|
227
|
+
// zeus.config.scss/zeus.customize.scss should actually be read somewhere in
|
|
228
|
+
// the engine — an orphaned variable is exactly the shape of the old
|
|
229
|
+
// $zeus-breakpoint-strategy (Q5): public config surface for a feature that
|
|
230
|
+
// doesn't do anything.
|
|
231
|
+
try {
|
|
232
|
+
const configSrc = readFileSync(join(ROOT, "zeus.config.scss"), "utf8");
|
|
233
|
+
const customizeSrc = readFileSync(join(ROOT, "zeus.customize.scss"), "utf8");
|
|
234
|
+
const declared = [...`${configSrc}\n${customizeSrc}`.matchAll(/^\$(zeus-[a-zA-Z0-9-]+)\s*:/gm)].map(
|
|
235
|
+
(m) => m[1]
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
function walkScss(dir) {
|
|
239
|
+
let out = [];
|
|
240
|
+
for (const entry of readdirSync(dir)) {
|
|
241
|
+
const full = join(dir, entry);
|
|
242
|
+
const s = statSync(full);
|
|
243
|
+
if (s.isDirectory()) out = out.concat(walkScss(full));
|
|
244
|
+
else if (entry.endsWith(".scss")) out.push(full);
|
|
245
|
+
}
|
|
246
|
+
return out;
|
|
247
|
+
}
|
|
248
|
+
const engineSrc = walkScss(join(ROOT, "foundation"))
|
|
249
|
+
.map((f) => readFileSync(f, "utf8"))
|
|
250
|
+
.join("\n");
|
|
251
|
+
|
|
252
|
+
const orphans = declared.filter((name) => {
|
|
253
|
+
const usageRegex = new RegExp(`\\$${name}\\b`, "g");
|
|
254
|
+
return (engineSrc.match(usageRegex) || []).length === 0;
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
if (orphans.length > 0) {
|
|
258
|
+
reportFail(
|
|
259
|
+
"6. Config variable usage analysis",
|
|
260
|
+
`declared but never read by foundation/: ${orphans.join(", ")}`
|
|
261
|
+
);
|
|
262
|
+
} else {
|
|
263
|
+
reportPass(`6. Config variable usage analysis — ${declared.length}/${declared.length} variables consumed`);
|
|
264
|
+
}
|
|
265
|
+
} catch (err) {
|
|
266
|
+
reportFail("6. Config variable usage analysis", err.message);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ── 7. CLI bridge template variable integrity ─────────────────────
|
|
270
|
+
// The exact bug class from Q5's bonus finding: npx zeus-css init generates a
|
|
271
|
+
// bridge file (scripts/build-package.ts's cliContent template, two levels up
|
|
272
|
+
// from this package) that forwards named $zeus-* variables into
|
|
273
|
+
// @use "zeus-css/scss/zeus.scss" with (...) — a renamed/removed variable on
|
|
274
|
+
// either side breaks that bridge silently until someone actually runs init.
|
|
275
|
+
// Soft-skips (doesn't fail) if the monorepo-only generator isn't present,
|
|
276
|
+
// e.g. when this package is checked out/published standalone.
|
|
277
|
+
try {
|
|
278
|
+
const buildPackagePath = join(ROOT, "..", "..", "scripts", "build-package.ts");
|
|
279
|
+
if (!existsSync(buildPackagePath)) {
|
|
280
|
+
reportPass("7. CLI bridge template variable integrity — skipped (build-package.ts not in this checkout)");
|
|
281
|
+
} else {
|
|
282
|
+
const configSrc = readFileSync(join(ROOT, "zeus.config.scss"), "utf8");
|
|
283
|
+
const customizeSrc = readFileSync(join(ROOT, "zeus.customize.scss"), "utf8");
|
|
284
|
+
const declared = new Set(
|
|
285
|
+
[...`${configSrc}\n${customizeSrc}`.matchAll(/^\$(zeus-[a-zA-Z0-9-]+)\s*:/gm)].map((m) => m[1])
|
|
286
|
+
);
|
|
287
|
+
const bridgeSrc = readFileSync(buildPackagePath, "utf8");
|
|
288
|
+
const referenced = [
|
|
289
|
+
...bridgeSrc.matchAll(/local(?:Config|Customize)\.\$(zeus-[a-zA-Z0-9-]+)/g),
|
|
290
|
+
].map((m) => m[1]);
|
|
291
|
+
|
|
292
|
+
const phantom = [...new Set(referenced)].filter((name) => !declared.has(name));
|
|
293
|
+
const missing = [...declared].filter((name) => !referenced.includes(name));
|
|
294
|
+
|
|
295
|
+
if (phantom.length > 0) {
|
|
296
|
+
reportFail(
|
|
297
|
+
"7. CLI bridge template variable integrity",
|
|
298
|
+
`bridge references variable(s) that don't exist: ${phantom.join(", ")}`
|
|
299
|
+
);
|
|
300
|
+
} else if (missing.length > 0) {
|
|
301
|
+
reportFail(
|
|
302
|
+
"7. CLI bridge template variable integrity",
|
|
303
|
+
`declared but not forwarded by the init bridge: ${missing.join(", ")}`
|
|
304
|
+
);
|
|
305
|
+
} else {
|
|
306
|
+
reportPass(`7. CLI bridge template variable integrity — ${referenced.length} variables match 1:1`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
} catch (err) {
|
|
310
|
+
reportFail("7. CLI bridge template variable integrity", err.message);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// ── 8. WCAG AA contrast regression guard ──────────────────────────
|
|
314
|
+
// P2-19 "contrast check": the exact class of bug that shipped twice this
|
|
315
|
+
// project (P1-3's .btn-primary, then secondary/accent) — a color token gets
|
|
316
|
+
// darkened/lightened without re-checking contrast. Reads the *compiled*
|
|
317
|
+
// default palette from check 1's output (not a hand-copied literal) so this
|
|
318
|
+
// can never itself drift from zeus.customize.scss, and fails the build the
|
|
319
|
+
// moment any of these pairs regresses below WCAG AA.
|
|
320
|
+
try {
|
|
321
|
+
if (!defaultBuildCss) {
|
|
322
|
+
reportFail("8. WCAG AA contrast regression guard", "no compiled CSS available (check 1 failed)");
|
|
323
|
+
} else {
|
|
324
|
+
const oklch = (name) => {
|
|
325
|
+
const m = defaultBuildCss.match(
|
|
326
|
+
new RegExp(`--color-${name}:\\s*oklch\\(([\\d.]+)%\\s+([\\d.]+)\\s+([\\d.]+)deg\\)`)
|
|
327
|
+
);
|
|
328
|
+
if (!m) throw new Error(`--color-${name} not found as a literal oklch() in compiled CSS`);
|
|
329
|
+
return { l: parseFloat(m[1]) / 100, c: parseFloat(m[2]), h: parseFloat(m[3]) };
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
// CSS Color 4 OKLab -> linear sRGB matrices, with chroma-reduction gamut
|
|
333
|
+
// mapping (binary search) so out-of-gamut tokens are handled roughly the
|
|
334
|
+
// way a real browser's oklch() resolves them, not naively clamped.
|
|
335
|
+
//
|
|
336
|
+
// ACCURACY CAVEAT: for colors outside sRGB this is an approximation of
|
|
337
|
+
// Chromium's actual gamut-mapping algorithm, and measured deviation is up
|
|
338
|
+
// to ~0.3 on the resulting ratio (the default accent computes 4.85 here
|
|
339
|
+
// but renders 4.55 in Chromium 148 — both pass, but the real margin is
|
|
340
|
+
// thinner than this number suggests). Treat a result in the 4.5–4.8 band
|
|
341
|
+
// as "verify in a browser", not "comfortably passing".
|
|
342
|
+
const toLinearSrgb = (L, C, hDeg) => {
|
|
343
|
+
const h = (hDeg * Math.PI) / 180;
|
|
344
|
+
const a = C * Math.cos(h);
|
|
345
|
+
const b = C * Math.sin(h);
|
|
346
|
+
const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
|
|
347
|
+
const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
|
|
348
|
+
const s_ = L - 0.0894841775 * a - 1.2914855480 * b;
|
|
349
|
+
const l = l_ ** 3, m = m_ ** 3, s = s_ ** 3;
|
|
350
|
+
return [
|
|
351
|
+
4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
|
|
352
|
+
-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
|
|
353
|
+
-0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s,
|
|
354
|
+
];
|
|
355
|
+
};
|
|
356
|
+
const gammaEncode = (c) => {
|
|
357
|
+
const cc = Math.min(1, Math.max(0, c));
|
|
358
|
+
return cc <= 0.0031308 ? cc * 12.92 : 1.055 * Math.pow(cc, 1 / 2.4) - 0.055;
|
|
359
|
+
};
|
|
360
|
+
const inGamut = ([r, g, b]) => r >= -1e-4 && r <= 1.0001 && g >= -1e-4 && g <= 1.0001 && b >= -1e-4 && b <= 1.0001;
|
|
361
|
+
const toSrgb255 = (L, C, hDeg) => {
|
|
362
|
+
let lin = toLinearSrgb(L, C, hDeg);
|
|
363
|
+
if (!inGamut(lin)) {
|
|
364
|
+
let lo = 0, hi = C;
|
|
365
|
+
for (let i = 0; i < 20; i++) {
|
|
366
|
+
const mid = (lo + hi) / 2;
|
|
367
|
+
if (inGamut(toLinearSrgb(L, mid, hDeg))) lo = mid; else hi = mid;
|
|
368
|
+
}
|
|
369
|
+
lin = toLinearSrgb(L, lo, hDeg);
|
|
370
|
+
}
|
|
371
|
+
return lin.map(gammaEncode).map((v) => Math.min(1, Math.max(0, v)) * 255);
|
|
372
|
+
};
|
|
373
|
+
const relLuminance = ([r, g, b]) => {
|
|
374
|
+
const f = (c) => {
|
|
375
|
+
c = c / 255;
|
|
376
|
+
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
377
|
+
};
|
|
378
|
+
return 0.2126 * f(r) + 0.7152 * f(g) + 0.0722 * f(b);
|
|
379
|
+
};
|
|
380
|
+
const contrastOf = (colA, colB) => {
|
|
381
|
+
const rgbA = toSrgb255(colA.l, colA.c, colA.h);
|
|
382
|
+
const rgbB = toSrgb255(colB.l, colB.c, colB.h);
|
|
383
|
+
const La = relLuminance(rgbA), Lb = relLuminance(rgbB);
|
|
384
|
+
const hi = Math.max(La, Lb), lo = Math.min(La, Lb);
|
|
385
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// Mirrors derive-on-color() in design/colors/_color-derivation.scss —
|
|
389
|
+
// same 0.6 lightness switch between near-dark (0.218) and near-light
|
|
390
|
+
// (0.976) at chroma 0. Kept in sync deliberately: if the SCSS threshold
|
|
391
|
+
// changes without changing this, the assertions below stop describing
|
|
392
|
+
// what actually ships and one of them will fail loudly.
|
|
393
|
+
const onColorOf = (col) => ({
|
|
394
|
+
l: col.l < 0.6 ? 0.976 : 0.218,
|
|
395
|
+
c: 0,
|
|
396
|
+
h: col.h,
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
const text = oklch("text");
|
|
400
|
+
const background = oklch("background");
|
|
401
|
+
const textInverse = oklch("text-inverse");
|
|
402
|
+
const primary = oklch("primary");
|
|
403
|
+
const secondary = oklch("secondary");
|
|
404
|
+
const accent = oklch("accent");
|
|
405
|
+
|
|
406
|
+
const systemOklch = (name) => {
|
|
407
|
+
const m = defaultBuildCss.match(
|
|
408
|
+
new RegExp(`--base-system-${name}:\\s*oklch\\(([\\d.]+)%\\s+([\\d.]+)\\s+([\\d.]+)deg\\)`)
|
|
409
|
+
);
|
|
410
|
+
if (!m) throw new Error(`--base-system-${name} not found as a literal oklch() in compiled CSS`);
|
|
411
|
+
return { l: parseFloat(m[1]) / 100, c: parseFloat(m[2]), h: parseFloat(m[3]) };
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
// Only pairings the framework actually ships are asserted here. Note what
|
|
415
|
+
// is deliberately NOT in this list: text-inverse (white) on
|
|
416
|
+
// --color-secondary. The default secondary is a light brand orange, so
|
|
417
|
+
// white-on-secondary is ~2.2:1 and is not a supported combination — the
|
|
418
|
+
// framework never emits it (see .z-badge--solid, which derives its
|
|
419
|
+
// foreground per fill). Asserting it would be asserting a combination the
|
|
420
|
+
// design system doesn't offer.
|
|
421
|
+
const pairs = [
|
|
422
|
+
{ label: "text/background (body)", a: text, b: background, min: 4.5 },
|
|
423
|
+
{ label: "text-inverse/primary (.btn-primary)", a: textInverse, b: primary, min: 4.5 },
|
|
424
|
+
{ label: "text-inverse/accent (.bg-accent + white)", a: textInverse, b: accent, min: 4.5 },
|
|
425
|
+
{ label: "text/secondary (.bg-secondary + dark text — the supported pairing)", a: text, b: secondary, min: 4.5 },
|
|
426
|
+
];
|
|
427
|
+
|
|
428
|
+
// Every .z-badge--solid variant, using the on-color the CSS actually
|
|
429
|
+
// derives. This is what caught the real bug: a hardcoded white foreground
|
|
430
|
+
// failed on 3 of these 6 fills.
|
|
431
|
+
for (const [label, col] of [
|
|
432
|
+
["primary", primary],
|
|
433
|
+
["secondary", secondary],
|
|
434
|
+
["accent", accent],
|
|
435
|
+
["success", systemOklch("success")],
|
|
436
|
+
["warning", systemOklch("warning")],
|
|
437
|
+
["error", systemOklch("error")],
|
|
438
|
+
]) {
|
|
439
|
+
pairs.push({
|
|
440
|
+
label: `.z-badge--solid.z-badge--${label} (derived on-color)`,
|
|
441
|
+
a: onColorOf(col),
|
|
442
|
+
b: col,
|
|
443
|
+
min: 4.5,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const results = pairs.map((p) => ({ ...p, ratio: contrastOf(p.a, p.b) }));
|
|
448
|
+
const regressed = results.filter((r) => r.ratio < r.min);
|
|
449
|
+
|
|
450
|
+
if (regressed.length > 0) {
|
|
451
|
+
reportFail(
|
|
452
|
+
"8. WCAG AA contrast regression guard",
|
|
453
|
+
regressed.map((r) => `${r.label} — ${r.ratio.toFixed(2)}:1, needs ${r.min}:1`).join("; ")
|
|
454
|
+
);
|
|
455
|
+
} else {
|
|
456
|
+
const lowest = results.reduce((a, b) => (a.ratio < b.ratio ? a : b));
|
|
457
|
+
reportPass(
|
|
458
|
+
`8. WCAG AA contrast regression guard — ${results.length} shipped pairings all ≥ 4.5:1 ` +
|
|
459
|
+
`(lowest: ${lowest.ratio.toFixed(2)} on ${lowest.label})`
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
} catch (err) {
|
|
464
|
+
reportFail("8. WCAG AA contrast regression guard", err.message);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
console.log("");
|
|
468
|
+
if (failures > 0) {
|
|
469
|
+
console.error(`${failures} check(s) failed.`);
|
|
470
|
+
process.exit(1);
|
|
471
|
+
} else {
|
|
472
|
+
console.log("All checks passed.");
|
|
473
|
+
process.exit(0);
|
|
474
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// ╔══════════════════════════════════════════════════════════════╗
|
|
2
|
+
// ║ ⚡ Zeus CSS — Zeus.config ║
|
|
3
|
+
// ║ ║
|
|
4
|
+
// ║ Core styles and definitions for zeus.config. ║
|
|
5
|
+
// ╚══════════════════════════════════════════════════════════════╝
|
|
6
|
+
|
|
7
|
+
// ╔══════════════════════════════════════════════════════════════╗
|
|
8
|
+
// ║ ⚡ Zeus CSS — Framework Configuration ║
|
|
9
|
+
// ║ ║
|
|
10
|
+
// ║ Controls framework features, naming strategies and the ║
|
|
11
|
+
// ║ fluid layout engine. For design tokens (colors, typography, ║
|
|
12
|
+
// ║ spacing etc.) see zeus.customize.scss. ║
|
|
13
|
+
// ╚══════════════════════════════════════════════════════════════╝
|
|
14
|
+
|
|
15
|
+
@use "sass:map";
|
|
16
|
+
|
|
17
|
+
// Design token customization values are forwarded from here so
|
|
18
|
+
// that all internal engine files can @use this config module
|
|
19
|
+
// and still access $zeus-colors etc. via config.$zeus-colors.
|
|
20
|
+
@forward "./zeus.customize";
|
|
21
|
+
|
|
22
|
+
// ────────────────────────────────────────────────────────────────
|
|
23
|
+
// 1. FEATURE TOGGLES & UTILITIES
|
|
24
|
+
// ────────────────────────────────────────────────────────────────
|
|
25
|
+
// Enable/disable framework modules to control compiled CSS size.
|
|
26
|
+
$zeus-enable-responsive-classes: true !default;
|
|
27
|
+
$zeus-enable-animation-classes: true !default;
|
|
28
|
+
$zeus-enable-grid-system: true !default;
|
|
29
|
+
$zeus-enable-shadow-classes: true !default;
|
|
30
|
+
$zeus-enable-color-utilities: true !default;
|
|
31
|
+
$zeus-enable-property-registration: true !default;
|
|
32
|
+
|
|
33
|
+
// ────────────────────────────────────────────────────────────────
|
|
34
|
+
// 2. NAMING & COMPILE STRATEGIES
|
|
35
|
+
// ────────────────────────────────────────────────────────────────
|
|
36
|
+
$zeus-use-prefix: false !default;
|
|
37
|
+
$zeus-class-prefix: "z-" !default; // Used only if $zeus-use-prefix is true
|
|
38
|
+
$zeus-version: "1.0.0" !default;
|
|
39
|
+
|
|
40
|
+
// Viewport limits & outer margins (Design Artboard references)
|
|
41
|
+
// Internal-only: the gutter between the viewport edge and the container,
|
|
42
|
+
// at each reference width — used solely to auto-derive $container-bounds
|
|
43
|
+
// (see core/design/core/basic.scss). Not the same setting as the public
|
|
44
|
+
// $zeus-outer-padding below — that one controls the compiled
|
|
45
|
+
// --padding-outer CSS var. Kept as a separate variable so overriding one
|
|
46
|
+
// never silently no-ops the other.
|
|
47
|
+
$zeus-viewports: (
|
|
48
|
+
mobile: 375, // Mobile Design width (px)
|
|
49
|
+
desktop: 1920, // Desktop Design width (px)
|
|
50
|
+
) !default;
|
|
51
|
+
|
|
52
|
+
$zeus-viewport-gutter: (
|
|
53
|
+
mobile: 16, // 16px left + 16px right
|
|
54
|
+
desktop: 64, // 64px left + 64px right
|
|
55
|
+
) !default;
|
|
56
|
+
|
|
57
|
+
// Optional developer override for container bounds (defaults to empty so engine auto-calculates)
|
|
58
|
+
$zeus-container-bounds: () !default;
|
|
59
|
+
|
|
60
|
+
// Global typography root base size (1rem = 16px)
|
|
61
|
+
$zeus-root-font-size: 16px !default;
|
|
62
|
+
|
|
63
|
+
// Global accessible keyboard focus indicator
|
|
64
|
+
$zeus-focus-ring: 2px solid var(--color-primary) !default;
|
|
65
|
+
|
|
66
|
+
// ────────────────────────────────────────────────────────────────
|
|
67
|
+
// 4. LAYOUT, BREAKPOINTS & GRID
|
|
68
|
+
// ────────────────────────────────────────────────────────────────
|
|
69
|
+
// Canonical wrapper limit
|
|
70
|
+
$zeus-outer-max-width: 2560px !default;
|
|
71
|
+
|
|
72
|
+
// Media query breakpoints.
|
|
73
|
+
// Override a single key: ( lg: 1400px )
|
|
74
|
+
$zeus-breakpoints: (
|
|
75
|
+
xs: 0,
|
|
76
|
+
sm: 576px,
|
|
77
|
+
md: 768px,
|
|
78
|
+
ml: 1024px,
|
|
79
|
+
lg: 1280px,
|
|
80
|
+
xl: 1920px,
|
|
81
|
+
xxl: 2560px,
|
|
82
|
+
) !default;
|
|
83
|
+
|
|
84
|
+
// Container outer padding — fluid margins (floor→ceil px, unit-less)
|
|
85
|
+
// Public: drives the compiled --padding-outer CSS var (see
|
|
86
|
+
// core/design/layout/grid.scss). Distinct from $zeus-artboard-padding above.
|
|
87
|
+
$zeus-outer-padding: (
|
|
88
|
+
floor: 16,
|
|
89
|
+
ceil: 128,
|
|
90
|
+
) !default;
|
|
91
|
+
|
|
92
|
+
// Column gaps — fluid spacing per breakpoint tier (floor→ceil px, unit-less)
|
|
93
|
+
$zeus-column-gaps: (
|
|
94
|
+
xs: (floor: 12, ceil: 16),
|
|
95
|
+
sm: (floor: 16, ceil: 20),
|
|
96
|
+
md: (floor: 20, ceil: 28),
|
|
97
|
+
lg: (floor: 28, ceil: 40),
|
|
98
|
+
xxl: (floor: 40, ceil: 64),
|
|
99
|
+
) !default;
|