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.
Files changed (105) hide show
  1. package/README.md +81 -0
  2. package/bin/init.js +89 -0
  3. package/dist/zeus.css +8456 -0
  4. package/dist/zeus.min.css +1 -0
  5. package/package.json +44 -0
  6. package/scss/.stylelintrc.json +41 -0
  7. package/scss/API.md +168 -0
  8. package/scss/CHANGELOG.md +281 -0
  9. package/scss/LICENSE +21 -0
  10. package/scss/README.md +160 -0
  11. package/scss/cheatsheet.md +249 -0
  12. package/scss/dist/zeus.css +8465 -0
  13. package/scss/dist/zeus.css.map +1 -0
  14. package/scss/dist/zeus.min.css +1 -0
  15. package/scss/dist/zeus.min.css.map +1 -0
  16. package/scss/foundation/core/_api.scss +10 -0
  17. package/scss/foundation/core/_design.scss +33 -0
  18. package/scss/foundation/core/_engine.scss +18 -0
  19. package/scss/foundation/core/buttons/variants.scss +72 -0
  20. package/scss/foundation/core/design/_design-system.scss +41 -0
  21. package/scss/foundation/core/design/_validate.scss +114 -0
  22. package/scss/foundation/core/design/borders/border-line.scss +29 -0
  23. package/scss/foundation/core/design/borders/border-radius.scss +31 -0
  24. package/scss/foundation/core/design/buttons/sizes.scss +32 -0
  25. package/scss/foundation/core/design/colors/_color-derivation.scss +80 -0
  26. package/scss/foundation/core/design/colors/base-colors.scss +31 -0
  27. package/scss/foundation/core/design/colors/semantic-colors.scss +130 -0
  28. package/scss/foundation/core/design/core/basic.scss +40 -0
  29. package/scss/foundation/core/design/dimensions/sizes.scss +29 -0
  30. package/scss/foundation/core/design/dimensions/spaces.scss +42 -0
  31. package/scss/foundation/core/design/effects/_z-index.scss +30 -0
  32. package/scss/foundation/core/design/effects/shadows.scss +65 -0
  33. package/scss/foundation/core/design/layout/grid.scss +86 -0
  34. package/scss/foundation/core/design/typography/typography.scss +435 -0
  35. package/scss/foundation/core/engine/_fluid-core.scss +54 -0
  36. package/scss/foundation/core/engine/animations.scss +91 -0
  37. package/scss/foundation/core/engine/buttons.scss +226 -0
  38. package/scss/foundation/core/engine/color-api.scss +37 -0
  39. package/scss/foundation/core/engine/helpers.scss +39 -0
  40. package/scss/foundation/core/engine/layout.scss +156 -0
  41. package/scss/foundation/core/engine/responsive.scss +67 -0
  42. package/scss/foundation/core/engine/shadows.scss +186 -0
  43. package/scss/foundation/core/engine/shortcuts.scss +30 -0
  44. package/scss/foundation/core/engine/sizing.scss +49 -0
  45. package/scss/foundation/core/engine/theme-builder.scss +83 -0
  46. package/scss/foundation/core/engine/typographics.scss +44 -0
  47. package/scss/foundation/core/engine/typography-calc.scss +71 -0
  48. package/scss/foundation/foundation.scss +29 -0
  49. package/scss/foundation/views/_render.scss +13 -0
  50. package/scss/foundation/views/components/_alert.scss +103 -0
  51. package/scss/foundation/views/components/_avatar.scss +84 -0
  52. package/scss/foundation/views/components/_badge.scss +100 -0
  53. package/scss/foundation/views/components/_breadcrumb.scss +70 -0
  54. package/scss/foundation/views/components/_card.scss +117 -0
  55. package/scss/foundation/views/components/_dropdown.scss +123 -0
  56. package/scss/foundation/views/components/_form.scss +209 -0
  57. package/scss/foundation/views/components/_index.scss +20 -0
  58. package/scss/foundation/views/components/_modal.scss +138 -0
  59. package/scss/foundation/views/components/_pagination.scss +82 -0
  60. package/scss/foundation/views/components/_skeleton.scss +69 -0
  61. package/scss/foundation/views/components/_table.scss +94 -0
  62. package/scss/foundation/views/components/_tabs.scss +96 -0
  63. package/scss/foundation/views/components/_toast.scss +139 -0
  64. package/scss/foundation/views/components/_tooltip.scss +100 -0
  65. package/scss/foundation/views/patterns/_hero.scss +121 -0
  66. package/scss/foundation/views/patterns/_index.scss +8 -0
  67. package/scss/foundation/views/patterns/_sidebar-layout.scss +83 -0
  68. package/scss/foundation/views/render/_layer-order.scss +11 -0
  69. package/scss/foundation/views/render/classes/_animations.scss +233 -0
  70. package/scss/foundation/views/render/classes/_aspect-ratio.scss +17 -0
  71. package/scss/foundation/views/render/classes/_borders.scss +31 -0
  72. package/scss/foundation/views/render/classes/_buttons.scss +40 -0
  73. package/scss/foundation/views/render/classes/_colors.scss +83 -0
  74. package/scss/foundation/views/render/classes/_compounds.scss +103 -0
  75. package/scss/foundation/views/render/classes/_containers.scss +20 -0
  76. package/scss/foundation/views/render/classes/_misc-utils.scss +51 -0
  77. package/scss/foundation/views/render/classes/_position.scss +56 -0
  78. package/scss/foundation/views/render/classes/_sections.scss +30 -0
  79. package/scss/foundation/views/render/classes/_shadows.scss +198 -0
  80. package/scss/foundation/views/render/classes/_sizes.scss +39 -0
  81. package/scss/foundation/views/render/classes/_sizing-utils.scss +53 -0
  82. package/scss/foundation/views/render/classes/_spacing.scss +91 -0
  83. package/scss/foundation/views/render/classes/_state-variants.scss +47 -0
  84. package/scss/foundation/views/render/classes/_typography.scss +32 -0
  85. package/scss/foundation/views/render/classes/_utilities.scss +103 -0
  86. package/scss/foundation/views/render/classes/_z-index.scss +22 -0
  87. package/scss/foundation/views/render/classes/flexbox.scss +56 -0
  88. package/scss/foundation/views/render/classes/grid.scss +195 -0
  89. package/scss/foundation/views/render/classes.scss +116 -0
  90. package/scss/foundation/views/render/html-body.scss +54 -0
  91. package/scss/foundation/views/render/property-registration.scss +77 -0
  92. package/scss/foundation/views/render/reset.scss +147 -0
  93. package/scss/foundation/views/render/root-generator.scss +166 -0
  94. package/scss/foundation/views/render/root.scss +19 -0
  95. package/scss/foundation/views/render/typography.scss +129 -0
  96. package/scss/llms.txt +247 -0
  97. package/scss/package.json +89 -0
  98. package/scss/scripts/gen-cheatsheet.mjs +178 -0
  99. package/scss/scripts/gen-tokens.mjs +91 -0
  100. package/scss/scripts/test-compile.mjs +474 -0
  101. package/scss/zeus.config.scss +99 -0
  102. package/scss/zeus.customize.scss +246 -0
  103. package/scss/zeus.scss +17 -0
  104. package/scss/zeus.token.sets.example.scss +176 -0
  105. 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;