@birdycom/omniacss 0.0.0-stage → 0.11.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 (106) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +43 -2
  3. package/dist/oc-colors.css +4429 -0
  4. package/dist/oc-theme.css +69 -0
  5. package/dist/omnia.css +465435 -0
  6. package/dist/omnia.min.css +2 -0
  7. package/dist/omnia.starter.css +13459 -0
  8. package/dist/omnia.starter.min.css +1 -0
  9. package/dist/tokens/omnia.dtcg.json +1496 -0
  10. package/dist/tokens/omnia.tokens.json +580 -0
  11. package/dist/tokens/omnia_tokens.py +189 -0
  12. package/package.json +61 -4
  13. package/scss/base/_reset.scss +88 -0
  14. package/scss/config/omniacss.config.scss +114 -0
  15. package/scss/config/omniacss.fonts.scss +34 -0
  16. package/scss/functions/_functions.scss +407 -0
  17. package/scss/json/html-colors.json +1998 -0
  18. package/scss/json/omnia-colors.json +500 -0
  19. package/scss/json/ral-colors.json +3430 -0
  20. package/scss/layout/_media_query.scss +18 -0
  21. package/scss/omnia_colors.scss +7 -0
  22. package/scss/omnia_main.scss +143 -0
  23. package/scss/profiles/omnia_main.hsl.scss +12 -0
  24. package/scss/profiles/omnia_main.oklab.scss +12 -0
  25. package/scss/profiles/omnia_main.oklch.scss +12 -0
  26. package/scss/utilities/_accessibility.scss +42 -0
  27. package/scss/utilities/_alignment.scss +861 -0
  28. package/scss/utilities/_anchor_positioning.scss +65 -0
  29. package/scss/utilities/_animation_properties.scss +143 -0
  30. package/scss/utilities/_aspect.scss +73 -0
  31. package/scss/utilities/_backgrounds.scss +49 -0
  32. package/scss/utilities/_borders_extended.scss +97 -0
  33. package/scss/utilities/_borders_outlines.scss +161 -0
  34. package/scss/utilities/_clamp.scss +111 -0
  35. package/scss/utilities/_colors_html.scss +341 -0
  36. package/scss/utilities/_colors_mix.scss +84 -0
  37. package/scss/utilities/_colors_omnia.scss +315 -0
  38. package/scss/utilities/_colors_ral.scss +151 -0
  39. package/scss/utilities/_colors_tokens.scss +87 -0
  40. package/scss/utilities/_container.scss +58 -0
  41. package/scss/utilities/_containment.scss +82 -0
  42. package/scss/utilities/_corner_shape.scss +30 -0
  43. package/scss/utilities/_css_functions.scss +34 -0
  44. package/scss/utilities/_dark_mode.scss +117 -0
  45. package/scss/utilities/_display.scss +58 -0
  46. package/scss/utilities/_drop_shadow_colors.scss +26 -0
  47. package/scss/utilities/_effects.scss +50 -0
  48. package/scss/utilities/_effects_advanced.scss +102 -0
  49. package/scss/utilities/_experimental_css.scss +149 -0
  50. package/scss/utilities/_filters.scss +89 -0
  51. package/scss/utilities/_flexbox.scss +318 -0
  52. package/scss/utilities/_font_variants.scss +139 -0
  53. package/scss/utilities/_fonts.scss +276 -0
  54. package/scss/utilities/_gap.scss +104 -0
  55. package/scss/utilities/_gradient_stops.scss +48 -0
  56. package/scss/utilities/_gradients.scss +99 -0
  57. package/scss/utilities/_grid.scss +673 -0
  58. package/scss/utilities/_index.scss +22 -0
  59. package/scss/utilities/_interactivity.scss +198 -0
  60. package/scss/utilities/_layering.scss +24 -0
  61. package/scss/utilities/_layout_extras.scss +171 -0
  62. package/scss/utilities/_layout_flow.scss +210 -0
  63. package/scss/utilities/_mask_extras.scss +62 -0
  64. package/scss/utilities/_motion_transforms.scss +167 -0
  65. package/scss/utilities/_overflow.scss +41 -0
  66. package/scss/utilities/_position.scss +49 -0
  67. package/scss/utilities/_responsive.scss +272 -0
  68. package/scss/utilities/_scroll_animations.scss +80 -0
  69. package/scss/utilities/_scroll_spacing.scss +33 -0
  70. package/scss/utilities/_scrollbar.scss +49 -0
  71. package/scss/utilities/_shadow_tokens.scss +21 -0
  72. package/scss/utilities/_shapes.scss +72 -0
  73. package/scss/utilities/_sizing.scss +458 -0
  74. package/scss/utilities/_sizing_logical.scss +99 -0
  75. package/scss/utilities/_space_divide.scss +85 -0
  76. package/scss/utilities/_spacing_margin.scss +199 -0
  77. package/scss/utilities/_spacing_padding.scss +195 -0
  78. package/scss/utilities/_starting_style.scss +53 -0
  79. package/scss/utilities/_state_variants.scss +196 -0
  80. package/scss/utilities/_svg.scss +100 -0
  81. package/scss/utilities/_table.scss +46 -0
  82. package/scss/utilities/_table_advanced.scss +19 -0
  83. package/scss/utilities/_text.scss +1331 -0
  84. package/scss/utilities/_transforms_3d.scss +76 -0
  85. package/scss/utilities/_typography_advanced.scss +124 -0
  86. package/scss/utilities/_typography_modern.scss +83 -0
  87. package/scss/utilities/_view_transitions.scss +64 -0
  88. package/scss/utilities/_writing_modes.scss +72 -0
  89. package/scss/variables/_color_tone.scss +39 -0
  90. package/scss/variables/_hulp.scss +108 -0
  91. package/scss/variables/_mixins.scss +40 -0
  92. package/scss/variables/_modifiers.scss +32 -0
  93. package/scss/variables/_variables.scss +302 -0
  94. package/scss/variables/_variables_colors_html.scss +2019 -0
  95. package/scss/variables/_variables_colors_omnia.scss +465 -0
  96. package/scss/variables/_variables_colors_ral.scss +3472 -0
  97. package/scss/variables/_variables_effects.scss +109 -0
  98. package/scss/variables/_variables_flex_grid.scss +427 -0
  99. package/scss/variables/_variables_layout.scss +591 -0
  100. package/scss/variables/_variables_typography.scss +550 -0
  101. package/src/engine.mjs +258 -0
  102. package/src/postcss.mjs +43 -0
  103. package/src/starter-classes.mjs +124 -0
  104. package/src/tokens.mjs +377 -0
  105. package/src/variants.mjs +278 -0
  106. package/tokens/semantic.json +80 -0
package/src/tokens.mjs ADDED
@@ -0,0 +1,377 @@
1
+ // File: packages/core/src/tokens.mjs
2
+ // Semantic theme tokens: one JSON source (tokens/semantic.json) -> theme CSS for the web and resolved exports for
3
+ // non-web targets (desktop apps: Tkinter, Qt, WinUI, ...).
4
+ // Map: palette parsing -> OKLCH math + CSS Color 4 gamut mapping -> resolve per mode -> formats (css | json | dtcg | python)
5
+ // Author: Serge Cools | Framework: OmniaCSS
6
+ //
7
+ // Why: native toolkits know no light-dark(), var() or oklch(). The export resolves every token per mode to a final
8
+ // sRGB hex (gamut mapped like CSS Color 4, not clipped per channel), keeps the source oklch and alpha next to it,
9
+ // and stamps the OmniaCSS version so an app knows which release its colors match.
10
+
11
+ import fs from "node:fs/promises";
12
+ import path from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+
15
+ const coreDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
16
+ export const SEMANTIC_FILE = path.join(coreDir, "tokens/semantic.json");
17
+ export const MODES = ["light", "dark"];
18
+
19
+ /* ===================================================================
20
+ PALETTE — --color-<name>-<step>: oklch(...) from oc-colors.css
21
+ =================================================================== */
22
+
23
+ /** Map "red-70" -> { l, c, h } (l 0..1). Later declarations win, as in the cascade. */
24
+ export function parsePalette(css) {
25
+ const palette = new Map();
26
+ const re = /--color-([a-z0-9-]+):\s*oklch\(\s*([\d.]+)%\s+([\d.]+)\s+([\d.]+)(?:deg)?|--color-([a-z0-9-]+):\s*oklch\(\s*([\d.]+)%\s+([\d.]+)\s+none/g;
27
+ for (const m of css.matchAll(re)) {
28
+ if (m[1]) palette.set(m[1], { l: +m[2] / 100, c: +m[3], h: +m[4] });
29
+ else palette.set(m[5], { l: +m[6] / 100, c: +m[7], h: 0 });
30
+ }
31
+ return palette;
32
+ }
33
+
34
+ /* ===================================================================
35
+ COLOR MATH — OKLCH <-> OKLab <-> linear sRGB <-> sRGB
36
+ =================================================================== */
37
+
38
+ const toLab = ({ l, c, h }) => ({ L: l, a: c * Math.cos((h * Math.PI) / 180), b: c * Math.sin((h * Math.PI) / 180) });
39
+
40
+ function labToLinear({ L, a, b }) {
41
+ const l = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3;
42
+ const m = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3;
43
+ const s = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;
44
+ return [
45
+ 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
46
+ -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
47
+ -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s,
48
+ ];
49
+ }
50
+
51
+ function linearToLab([r, g, b]) {
52
+ const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
53
+ const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
54
+ const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
55
+ return {
56
+ L: 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
57
+ a: 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
58
+ b: 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,
59
+ };
60
+ }
61
+
62
+ const gamma = (v) => (Math.abs(v) <= 0.0031308 ? 12.92 * v : Math.sign(v) * (1.055 * Math.abs(v) ** (1 / 2.4) - 0.055));
63
+ const ungamma = (v) => (Math.abs(v) <= 0.04045 ? v / 12.92 : Math.sign(v) * ((Math.abs(v) + 0.055) / 1.055) ** 2.4);
64
+
65
+ const toSrgb = (lch) => labToLinear(toLab(lch)).map(gamma);
66
+ const srgbToLab = (rgb) => linearToLab(rgb.map(ungamma));
67
+ const inGamut = (rgb, eps = 0.000075) => rgb.every((v) => v >= -eps && v <= 1 + eps);
68
+ const clip = (rgb) => rgb.map((v) => Math.min(1, Math.max(0, v)));
69
+ const deltaEOK = (p, q) => Math.hypot(p.L - q.L, p.a - q.a, p.b - q.b);
70
+
71
+ /**
72
+ * CSS Color 4 gamut mapping to sRGB (https://www.w3.org/TR/css-color-4/#binsearch):
73
+ * lower the OKLCH chroma until the clipped color is within a just-noticeable difference (deltaEOK 0.02).
74
+ * Keeps lightness and hue, unlike clipping each channel.
75
+ */
76
+ export function gamutMap(lch) {
77
+ if (lch.l >= 1) return [1, 1, 1];
78
+ if (lch.l <= 0) return [0, 0, 0];
79
+ const origin = toSrgb(lch);
80
+ if (inGamut(origin)) return clip(origin);
81
+ const JND = 0.02;
82
+ const EPS = 0.0001;
83
+ let current = { ...lch };
84
+ let clipped = clip(origin);
85
+ if (deltaEOK(srgbToLab(clipped), toLab(current)) < JND) return clipped;
86
+ let min = 0;
87
+ let max = lch.c;
88
+ let minInGamut = true;
89
+ while (max - min > EPS) {
90
+ const chroma = (min + max) / 2;
91
+ current = { ...current, c: chroma };
92
+ const rgb = toSrgb(current);
93
+ if (minInGamut && inGamut(rgb)) { min = chroma; continue; }
94
+ clipped = clip(rgb);
95
+ const E = deltaEOK(srgbToLab(clipped), toLab(current));
96
+ if (E < JND) {
97
+ if (JND - E < EPS) return clipped;
98
+ minInGamut = false;
99
+ min = chroma;
100
+ } else {
101
+ max = chroma;
102
+ }
103
+ }
104
+ return clipped;
105
+ }
106
+
107
+ /** Per-channel clipping (what a naive conversion does) — only used to report the difference. */
108
+ export const clipSrgb = (lch) => clip(toSrgb(lch));
109
+ export const isInSrgb = (lch) => inGamut(toSrgb(lch));
110
+
111
+ export const toHex = (rgb) => "#" + rgb.map((v) => Math.round(Math.min(1, Math.max(0, v)) * 255).toString(16).padStart(2, "0")).join("");
112
+ const round = (n, d) => Number(n.toFixed(d));
113
+ export const oklchString = ({ l, c, h }) => `oklch(${round(l * 100, 2)}% ${round(c, 4)} ${round(h, 2)})`;
114
+
115
+ /** Alpha-blend rgb over a background (sRGB, gamma space, as browsers composite). */
116
+ const blend = (rgb, alpha, bg) => rgb.map((v, i) => v * alpha + bg[i] * (1 - alpha));
117
+
118
+ /* ===================================================================
119
+ RESOLVE — semantic.json -> final values per mode
120
+ A color value is a palette name ("frostblue-2"), a token reference ("@foreground"),
121
+ or { "color": <name or @ref>, "alpha": 0..1 }. Light/dark: { "light": v, "dark": v } or one value for both.
122
+ =================================================================== */
123
+
124
+ export async function loadSemantic(file = SEMANTIC_FILE) {
125
+ return JSON.parse(await fs.readFile(file, "utf8"));
126
+ }
127
+
128
+ export async function loadPalette(dist = path.join(coreDir, "dist")) {
129
+ return parsePalette(await fs.readFile(path.join(dist, "oc-colors.css"), "utf8"));
130
+ }
131
+
132
+ const perMode = (v) => (v && typeof v === "object" && ("light" in v || "dark" in v) ? v : { light: v, dark: v });
133
+
134
+ /** { name, alpha } for one token in one mode, references followed to a palette color. */
135
+ function resolveColor(tokens, name, mode, seen = []) {
136
+ if (seen.includes(name)) throw new Error(`token cycle: ${[...seen, name].join(" -> ")}`);
137
+ const def = tokens[name];
138
+ if (!def) throw new Error(`unknown token "@${name}"`);
139
+ let v = perMode(def.value)[mode];
140
+ let alpha = 1;
141
+ if (v && typeof v === "object") { alpha = v.alpha ?? 1; v = v.color; }
142
+ if (typeof v === "string" && v.startsWith("@")) {
143
+ const inner = resolveColor(tokens, v.slice(1), mode, [...seen, name]);
144
+ return { ...inner, alpha: inner.alpha * alpha, via: [v.slice(1), ...inner.via] };
145
+ }
146
+ return { color: v, alpha, via: [] };
147
+ }
148
+
149
+ /** Resolve every color token per mode: hex (gamut mapped), oklch source, alpha, palette name. */
150
+ export function resolveTokens(semantic, palette) {
151
+ const out = {};
152
+ for (const name of Object.keys(semantic.color)) {
153
+ out[name] = {};
154
+ for (const mode of MODES) {
155
+ const { color, alpha } = resolveColor(semantic.color, name, mode);
156
+ const lch = palette.get(color);
157
+ if (!lch) throw new Error(`token "${name}" (${mode}): palette color "${color}" not found in oc-colors.css`);
158
+ const rgb = gamutMap(lch);
159
+ const entry = { hex: toHex(rgb), oklch: oklchString(lch), palette: color };
160
+ if (alpha !== 1) entry.alpha = alpha;
161
+ if (!isInSrgb(lch)) { entry.gamutMapped = true; entry.clippedHex = toHex(clipSrgb(lch)); }
162
+ entry._rgb = rgb;
163
+ out[name][mode] = entry;
164
+ }
165
+ }
166
+ // Transparent tokens also get an opaque hex pre-blended over --oc-background (toolkits without alpha).
167
+ for (const name of Object.keys(out)) {
168
+ for (const mode of MODES) {
169
+ const e = out[name][mode];
170
+ if (e.alpha !== undefined) e.opaqueHex = toHex(blend(e._rgb, e.alpha, out.background[mode]._rgb));
171
+ }
172
+ }
173
+ for (const t of Object.values(out)) for (const mode of MODES) delete t[mode]._rgb;
174
+ return out;
175
+ }
176
+
177
+ /* ===================================================================
178
+ FORMATS
179
+ =================================================================== */
180
+
181
+ const cssColor = (tokens, v) => {
182
+ let alpha = 1;
183
+ if (v && typeof v === "object") { alpha = v.alpha ?? 1; v = v.color; }
184
+ const ref = v.startsWith("@") ? `var(--oc-${v.slice(1)})` : `var(--color-${v})`;
185
+ return alpha === 1 ? ref : `oklch(from ${ref} l c h / ${alpha})`;
186
+ };
187
+
188
+ /** oc-theme.css: the semantic tokens as light-dark() custom properties (the web side of the same source). */
189
+ export function toCss(semantic, version) {
190
+ const lines = [];
191
+ const pad = (s) => " " + s;
192
+ lines.push(`/* File: oc-theme.css — generated from tokens/semantic.json by @birdycom/omniacss ${version}. Do not edit. */`);
193
+ lines.push("/* Semantic theme tokens: every color is light-dark() of an Omnia palette token (oc-colors.css). */");
194
+ lines.push("/* Same source as the desktop export: npx omniacss tokens --format json|dtcg|python */");
195
+ lines.push(":root {");
196
+ lines.push(pad("color-scheme: light dark;"));
197
+ let group = null;
198
+ for (const [name, def] of Object.entries(semantic.color)) {
199
+ if (def.group !== group) { group = def.group; lines.push("", pad(`/* ${group} */`)); }
200
+ const v = def.value;
201
+ const value = v && typeof v === "object" && "light" in v
202
+ ? (JSON.stringify(v.light) === JSON.stringify(v.dark) ? cssColor(semantic.color, v.light) : `light-dark(${cssColor(semantic.color, v.light)}, ${cssColor(semantic.color, v.dark)})`)
203
+ : cssColor(semantic.color, v);
204
+ lines.push(pad(`--oc-${name}: ${value};`));
205
+ }
206
+ lines.push("", pad("/* Gradients */"));
207
+ for (const [name, g] of Object.entries(semantic.gradient || {})) {
208
+ const stops = g.stops.map((s, i) => `var(--oc-${s.color})${s.at !== undefined && i > 0 && i < g.stops.length - 1 ? ` ${s.at}%` : ""}`);
209
+ lines.push(pad(`--oc-gradient-${name}: linear-gradient(${g.angle}deg, ${stops.join(", ")});`));
210
+ }
211
+ lines.push("", pad("/* Shape, focus, motion, fonts */"));
212
+ lines.push(pad(`--oc-radius: ${semantic.size.radius.rem}rem;`));
213
+ lines.push(pad(`--oc-focus-width: ${semantic.size["focus-width"].px}px;`));
214
+ for (const [name, t] of Object.entries(semantic.transition)) lines.push(pad(`--oc-transition-${name}: ${t.ms}ms ${t.easing};`));
215
+ for (const [name, f] of Object.entries(semantic.font)) lines.push(pad(`--oc-font-${name}: ${f.css};`));
216
+ // Shadows: the color is its own light-dark() token, so the shadow follows the color-scheme without media queries.
217
+ lines.push("", pad("/* Shadows (x y blur spread; color + alpha per mode in --oc-shadow-<name>-color) */"));
218
+ for (const [name, s] of Object.entries(semantic.shadow)) {
219
+ const c = (mode) => `oklch(from var(--color-${perMode(s.color)[mode]}) l c h / ${perMode(s.alpha)[mode]})`;
220
+ lines.push(pad(`--oc-shadow-${name}-color: light-dark(${c("light")}, ${c("dark")});`));
221
+ lines.push(pad(`--oc-shadow-${name}: ${s.x}px ${s.y}px ${s.blur}px ${s.spread}px var(--oc-shadow-${name}-color);`));
222
+ }
223
+ lines.push("}", "");
224
+ return lines.join("\n");
225
+ }
226
+
227
+ const px = (rem) => rem * 16;
228
+ const pt = (pxv) => round(pxv * 0.75, 2);
229
+
230
+ /** Everything a non-web app needs, resolved: the data behind json, dtcg and python. */
231
+ export function buildExport(semantic, palette, version) {
232
+ const colors = resolveTokens(semantic, palette);
233
+ const shadows = {};
234
+ for (const [name, s] of Object.entries(semantic.shadow)) {
235
+ shadows[name] = { x: s.x, y: s.y, blur: s.blur, spread: s.spread };
236
+ for (const mode of MODES) {
237
+ const lch = palette.get(perMode(s.color)[mode]);
238
+ shadows[name][mode] = { hex: toHex(gamutMap(lch)), alpha: perMode(s.alpha)[mode] };
239
+ }
240
+ }
241
+ const gradients = {};
242
+ for (const [name, g] of Object.entries(semantic.gradient || {})) {
243
+ gradients[name] = { angle: g.angle };
244
+ for (const mode of MODES) gradients[name][mode] = g.stops.map((s, i) => ({ hex: colors[s.color][mode].hex, at: s.at ?? (i === 0 ? 0 : 100) }));
245
+ }
246
+ const text = Object.fromEntries(Object.entries(semantic.text).map(([k, v]) => [k, { px: v.px, pt: pt(v.px) }]));
247
+ return {
248
+ omniacss: version,
249
+ generated: "npx omniacss tokens",
250
+ modes: MODES,
251
+ colors,
252
+ sizes: {
253
+ radius: { rem: semantic.size.radius.rem, px: px(semantic.size.radius.rem), pt: pt(px(semantic.size.radius.rem)) },
254
+ "focus-width": { px: semantic.size["focus-width"].px, pt: pt(semantic.size["focus-width"].px) },
255
+ },
256
+ text,
257
+ fonts: Object.fromEntries(Object.entries(semantic.font).map(([k, f]) => [k, { windows: f.windows, macos: f.macos, linux: f.linux, css: f.css }])),
258
+ transitions: semantic.transition,
259
+ shadows,
260
+ gradients,
261
+ };
262
+ }
263
+
264
+ /** JSON: { omniacss, tokens: { background: { light: { hex, oklch }, dark: {…} }, radius: { px }, … } } */
265
+ export function toJson(data) {
266
+ const tokens = {};
267
+ for (const [name, t] of Object.entries(data.colors)) tokens[name] = t;
268
+ for (const [name, s] of Object.entries(data.sizes)) tokens[name] = s;
269
+ for (const [name, s] of Object.entries(data.text)) tokens[`text-${name}`] = s;
270
+ for (const [name, f] of Object.entries(data.fonts)) tokens[`font-${name}`] = f;
271
+ for (const [name, t] of Object.entries(data.transitions)) tokens[`transition-${name}`] = t;
272
+ for (const [name, s] of Object.entries(data.shadows)) tokens[`shadow-${name}`] = s;
273
+ for (const [name, g] of Object.entries(data.gradients)) tokens[`gradient-${name}`] = g;
274
+ return JSON.stringify({ omniacss: data.omniacss, generated: data.generated, modes: data.modes, tokens }, null, 2) + "\n";
275
+ }
276
+
277
+ const hexToComponents = (hex) => [1, 3, 5].map((i) => round(parseInt(hex.slice(i, i + 2), 16) / 255, 4));
278
+ const dtcgColor = (e) => ({ colorSpace: "srgb", components: hexToComponents(e.hex), alpha: e.alpha ?? 1, hex: e.hex });
279
+
280
+ /** W3C Design Tokens (DTCG): $value = light mode; dark mode in $extensions["com.birdycom.omniacss"].modes.dark. */
281
+ export function toDtcg(data) {
282
+ const ext = (extra) => ({ "com.birdycom.omniacss": extra });
283
+ const color = {};
284
+ for (const [name, t] of Object.entries(data.colors)) {
285
+ color[name] = {
286
+ $type: "color",
287
+ $value: dtcgColor(t.light),
288
+ $extensions: ext({ modes: { dark: dtcgColor(t.dark) }, oklch: { light: t.light.oklch, dark: t.dark.oklch }, palette: { light: t.light.palette, dark: t.dark.palette } }),
289
+ };
290
+ }
291
+ const dim = (v) => ({ $type: "dimension", $value: { value: v, unit: "px" } });
292
+ const out = {
293
+ $description: `OmniaCSS ${data.omniacss} semantic tokens (generated by npx omniacss tokens --format dtcg)`,
294
+ $extensions: ext({ version: data.omniacss, modes: data.modes }),
295
+ color,
296
+ radius: dim(data.sizes.radius.px),
297
+ "focus-width": dim(data.sizes["focus-width"].px),
298
+ text: Object.fromEntries(Object.entries(data.text).map(([k, v]) => [k, dim(v.px)])),
299
+ font: Object.fromEntries(Object.entries(data.fonts).map(([k, f]) => [k, { $type: "fontFamily", $value: f.windows, $extensions: ext({ macos: f.macos, linux: f.linux, css: f.css }) }])),
300
+ shadow: Object.fromEntries(Object.entries(data.shadows).map(([k, s]) => [k, {
301
+ $type: "shadow",
302
+ $value: { color: dtcgColor(s.light), offsetX: { value: s.x, unit: "px" }, offsetY: { value: s.y, unit: "px" }, blur: { value: s.blur, unit: "px" }, spread: { value: s.spread, unit: "px" } },
303
+ $extensions: ext({ modes: { dark: { color: dtcgColor(s.dark) } } }),
304
+ }])),
305
+ };
306
+ return JSON.stringify(out, null, 2) + "\n";
307
+ }
308
+
309
+ const py = (v) => JSON.stringify(v);
310
+ const pyKey = (k) => k.replace(/-/g, "_");
311
+
312
+ /** Python module: PALETTES = {"light": {...}, "dark": {...}} with plain hex (alpha tokens pre-blended), plus sizes and fonts. */
313
+ export function toPython(data) {
314
+ const L = [];
315
+ L.push(`# omnia_tokens.py — OmniaCSS ${data.omniacss} semantic tokens for non-web apps (Tkinter, Qt, ...).`);
316
+ L.push("# Generated by: npx omniacss tokens --format python (do not edit; regenerate after an OmniaCSS update)");
317
+ L.push("# Map: OMNIACSS_VERSION -> PALETTES (hex per mode) -> ALPHA -> OKLCH -> SIZES/TEXT -> FONTS -> SHADOWS -> font()");
318
+ L.push("#");
319
+ L.push("# Colors are resolved from light-dark(var(--color-...)) and gamut mapped to sRGB the CSS Color 4 way.");
320
+ L.push("# Tokens with transparency (see ALPHA) are pre-blended over \"background\" in PALETTES.");
321
+ L.push("");
322
+ L.push(`OMNIACSS_VERSION = ${py(data.omniacss)}`);
323
+ L.push("");
324
+ L.push("PALETTES = {");
325
+ for (const mode of data.modes) {
326
+ L.push(` ${py(mode)}: {`);
327
+ for (const [name, t] of Object.entries(data.colors)) L.push(` ${py(pyKey(name))}: ${py(t[mode].opaqueHex ?? t[mode].hex)},`);
328
+ L.push(" },");
329
+ }
330
+ L.push("}");
331
+ L.push("");
332
+ L.push("# Transparent tokens: (hex of the color itself, alpha). PALETTES holds the blended result.");
333
+ L.push("ALPHA = {");
334
+ for (const mode of data.modes) {
335
+ const items = Object.entries(data.colors).filter(([, t]) => t[mode].alpha !== undefined).map(([n, t]) => `${py(pyKey(n))}: (${py(t[mode].hex)}, ${t[mode].alpha})`);
336
+ L.push(` ${py(mode)}: {${items.join(", ")}},`);
337
+ }
338
+ L.push("}");
339
+ L.push("");
340
+ L.push("# Source colors (for wide-gamut renderers).");
341
+ L.push("OKLCH = {");
342
+ for (const mode of data.modes) {
343
+ L.push(` ${py(mode)}: {`);
344
+ for (const [name, t] of Object.entries(data.colors)) L.push(` ${py(pyKey(name))}: ${py(t[mode].oklch)},`);
345
+ L.push(" },");
346
+ }
347
+ L.push("}");
348
+ L.push("");
349
+ L.push("# Sizes in px (rem base 16 px); pt = px * 0.75.");
350
+ L.push(`SIZES = {${Object.entries(data.sizes).map(([k, v]) => `${py(pyKey(k))}: ${v.px}`).join(", ")}}`);
351
+ L.push(`TEXT = {${Object.entries(data.text).map(([k, v]) => `${py(k)}: ${v.px}`).join(", ")}}`);
352
+ L.push(`TEXT_PT = {${Object.entries(data.text).map(([k, v]) => `${py(k)}: ${v.pt}`).join(", ")}}`);
353
+ L.push("");
354
+ L.push("FONTS = {");
355
+ for (const [k, f] of Object.entries(data.fonts)) L.push(` ${py(k)}: {"windows": ${py(f.windows)}, "macos": ${py(f.macos)}, "linux": ${py(f.linux)}},`);
356
+ L.push("}");
357
+ L.push("");
358
+ L.push("# Shadows: x, y, blur, spread in px; color hex + alpha per mode.");
359
+ L.push("SHADOWS = {");
360
+ for (const [k, s] of Object.entries(data.shadows)) {
361
+ L.push(` ${py(k)}: {"x": ${s.x}, "y": ${s.y}, "blur": ${s.blur}, "spread": ${s.spread}, ` +
362
+ `"light": (${py(s.light.hex)}, ${s.light.alpha}), "dark": (${py(s.dark.hex)}, ${s.dark.alpha})},`);
363
+ }
364
+ L.push("}");
365
+ L.push("");
366
+ L.push("");
367
+ L.push("def font(name=\"sans\", platform=None):");
368
+ L.push(" \"\"\"Font family for this platform (sys.platform: win32 / darwin / linux).\"\"\"");
369
+ L.push(" import sys");
370
+ L.push(" key = {\"win32\": \"windows\", \"darwin\": \"macos\"}.get(platform or sys.platform, \"linux\")");
371
+ L.push(" return FONTS[name][key]");
372
+ L.push("");
373
+ return L.join("\n");
374
+ }
375
+
376
+ export const FORMATS = { json: toJson, dtcg: toDtcg, python: toPython };
377
+ export const EXTENSIONS = { json: "omnia.tokens.json", dtcg: "omnia.dtcg.json", python: "omnia_tokens.py" };
@@ -0,0 +1,278 @@
1
+ // File: packages/core/src/variants.mjs
2
+ // On-demand variants for the OmniaCSS engine: hover:, md:, group-hover:, has-[..]:, aria-*, data-*, dark:, cq-md: …
3
+ // A variant maps a list of "branches" ({ sel, at }) around the utility: `sel` is a selector template where `&` stands
4
+ // for the utility class, `at` the list of wrapping at-rules (outermost first). Most variants keep one branch; dark: makes two.
5
+ // Map: breakpoints -> maps -> parseCandidate -> applyVariant -> resolveVariants
6
+ // Author: Serge Cools | Framework: OmniaCSS
7
+
8
+ /** Viewport breakpoints (min-width). sm/md/lg match the native responsive utilities (_responsive.scss). */
9
+ export const BREAKPOINTS = { sm: "36rem", md: "50rem", lg: "64rem", xl: "80rem", "2xl": "96rem" };
10
+
11
+ /** Container-query sizes for cq-<size>: (xs..xl match _containment.scss). */
12
+ export const CONTAINER_SIZES = {
13
+ "3xs": "16rem", "2xs": "18rem", xs: "20rem", sm: "24rem", md: "28rem", lg: "32rem", xl: "36rem",
14
+ "2xl": "42rem", "3xl": "48rem", "4xl": "56rem", "5xl": "64rem", "6xl": "72rem", "7xl": "80rem",
15
+ };
16
+
17
+ const PSEUDO_CLASS = {
18
+ hover: ":hover", focus: ":focus", "focus-visible": ":focus-visible", "focus-within": ":focus-within", active: ":active",
19
+ visited: ":visited", target: ":target", disabled: ":disabled", enabled: ":enabled", checked: ":checked", indeterminate: ":indeterminate",
20
+ default: ":default", required: ":required", optional: ":optional", valid: ":valid", invalid: ":invalid", "user-valid": ":user-valid",
21
+ "user-invalid": ":user-invalid", "in-range": ":in-range", "out-of-range": ":out-of-range", "read-only": ":read-only",
22
+ "placeholder-shown": ":placeholder-shown", autofill: ":autofill", empty: ":empty",
23
+ first: ":first-child", last: ":last-child", only: ":only-child", odd: ":nth-child(odd)", even: ":nth-child(even)",
24
+ "first-of-type": ":first-of-type", "last-of-type": ":last-of-type", "only-of-type": ":only-of-type",
25
+ open: ":is([open], :popover-open, :open)", "popover-open": ":popover-open", inert: ":is([inert], [inert] *)",
26
+ };
27
+ const STATE_ORDER = ["visited", "target", "open", "popover-open", "default", "checked", "indeterminate", "placeholder-shown", "autofill", "optional", "required", "valid", "invalid", "user-valid", "user-invalid", "in-range", "out-of-range", "read-only", "empty", "focus-within", "hover", "focus", "focus-visible", "active", "enabled", "disabled", "inert"];
28
+ const STRUCT_ORDER = ["first", "last", "only", "odd", "even", "first-of-type", "last-of-type", "only-of-type"];
29
+
30
+ const PSEUDO_ELEMENT = {
31
+ before: "::before", after: "::after", "first-letter": "::first-letter", "first-line": "::first-line", marker: "::marker",
32
+ placeholder: "::placeholder", backdrop: "::backdrop", file: "::file-selector-button", selection: "::selection", "details-content": "::details-content",
33
+ };
34
+
35
+ const MEDIA = {
36
+ print: "print", "motion-safe": "(prefers-reduced-motion: no-preference)", "motion-reduce": "(prefers-reduced-motion: reduce)",
37
+ portrait: "(orientation: portrait)", landscape: "(orientation: landscape)", "contrast-more": "(prefers-contrast: more)",
38
+ "contrast-less": "(prefers-contrast: less)", "forced-colors": "(forced-colors: active)", "inverted-colors": "(inverted-colors: inverted)",
39
+ "pointer-fine": "(pointer: fine)", "pointer-coarse": "(pointer: coarse)", "pointer-none": "(pointer: none)",
40
+ "any-pointer-fine": "(any-pointer: fine)", "any-pointer-coarse": "(any-pointer: coarse)", "any-pointer-none": "(any-pointer: none)",
41
+ "hover-capable": "(hover: hover)", noscript: "(scripting: none)", "auto-dark": "(prefers-color-scheme: dark)",
42
+ };
43
+
44
+ /** Scroll-state container queries (the element must sit inside a `container-scroll-state` ancestor). */
45
+ const SCROLL_STATE = {
46
+ stuck: "not scroll-state(stuck: none)", "stuck-top": "scroll-state(stuck: top)", "stuck-bottom": "scroll-state(stuck: bottom)",
47
+ "stuck-left": "scroll-state(stuck: left)", "stuck-right": "scroll-state(stuck: right)",
48
+ snapped: "not scroll-state(snapped: none)", "snapped-x": "scroll-state(snapped: x)", "snapped-y": "scroll-state(snapped: y)",
49
+ "scrollable-top": "scroll-state(scrollable: top)", "scrollable-bottom": "scroll-state(scrollable: bottom)",
50
+ "scrollable-left": "scroll-state(scrollable: left)", "scrollable-right": "scroll-state(scrollable: right)",
51
+ };
52
+
53
+ const ARIA = ["busy", "checked", "disabled", "expanded", "hidden", "pressed", "readonly", "required", "selected"];
54
+
55
+ /** Fixed variant names per group (for editor tooling such as the OmniaCSS IntelliSense extension). */
56
+ export const VARIANT_GROUPS = {
57
+ pseudoClass: Object.keys(PSEUDO_CLASS), pseudoElement: Object.keys(PSEUDO_ELEMENT), media: Object.keys(MEDIA),
58
+ scrollState: Object.keys(SCROLL_STATE), aria: ARIA, other: ["dark", "rtl", "ltr", "starting", "*", "**"],
59
+ };
60
+
61
+ const DARK_EXPLICIT = ':where(.dark, [data-theme="dark"], :root:has(.oc-theme-switch input[value="dark"]:checked))';
62
+ const DARK_SYSTEM = ':where(:root:not([data-theme="light"], .light, :has(.oc-theme-switch input[value="light"]:checked)))';
63
+
64
+ /** Escape a string for use in a class selector. */
65
+ export function esc(s) {
66
+ let out = "";
67
+ for (let i = 0; i < s.length; i++) {
68
+ const ch = s[i];
69
+ if (/[0-9]/.test(ch) && (i === 0 || (i === 1 && s[0] === "-"))) out += "\\3" + ch + " ";
70
+ else if (/[A-Za-z0-9_\-¡-￿]/.test(ch)) out += ch;
71
+ else out += "\\" + ch;
72
+ }
73
+ return out;
74
+ }
75
+
76
+ /** Split on `sep` outside of [] and (). */
77
+ export function splitTop(str, sep = ":") {
78
+ const out = [];
79
+ let depth = 0, cur = "";
80
+ for (let i = 0; i < str.length; i++) {
81
+ const ch = str[i];
82
+ if (ch === "\\") { cur += ch + (str[++i] ?? ""); continue; }
83
+ if (ch === "[" || ch === "(") depth++;
84
+ else if (ch === "]" || ch === ")") depth--;
85
+ if (ch === sep && depth === 0) { out.push(cur); cur = ""; } else cur += ch;
86
+ }
87
+ out.push(cur);
88
+ return out;
89
+ }
90
+
91
+ /** `[a_b]` -> `a b` (underscore = space, `\_` stays an underscore). */
92
+ const arbitrary = (s) => {
93
+ const m = s.match(/^\[([\s\S]+)\]$/);
94
+ return m ? m[1].replace(/\\_/g, "\u0000").replace(/_/g, " ").replace(/\u0000/g, "_") : null;
95
+ };
96
+
97
+ const quoteAttr = (s) => {
98
+ const m = s.match(/^([^=~|^$*]+)([~|^$*]?=)(.*)$/);
99
+ if (!m) return s;
100
+ const v = /^["']/.test(m[3]) ? m[3] : `"${m[3]}"`;
101
+ return `${m[1]}${m[2]}${v}`;
102
+ };
103
+
104
+ /** Append a simple selector in front of a trailing pseudo-element: `&::after` + `:hover` -> `&:hover::after`. */
105
+ function addSimple(sel, suffix) {
106
+ const m = sel.match(/^(.*?)(::[a-z-]+)$/s);
107
+ return m ? m[1] + suffix + m[2] : sel + suffix;
108
+ }
109
+
110
+ /**
111
+ * Parse a class token: `md:hover:!bg-x` -> { variants: ["md","hover"], base: "bg-x", important: true }.
112
+ * Returns null when there is nothing to do (no variants and no `!`).
113
+ */
114
+ export function parseCandidate(token) {
115
+ if (token.length > 120) return null;
116
+ const parts = splitTop(token, ":");
117
+ let base = parts.pop();
118
+ if (!base) return null;
119
+ let important = false;
120
+ if (base.startsWith("!")) { important = true; base = base.slice(1); }
121
+ else if (base.endsWith("!")) { important = true; base = base.slice(0, -1); }
122
+ if (!/^-?[A-Za-z]/.test(base)) return null;
123
+ if (!parts.length && !important) return null;
124
+ if (parts.some((p) => !p)) return null;
125
+ return { token, variants: parts, base, important };
126
+ }
127
+
128
+ const state = (sel = "&", at = []) => ({ sel, at });
129
+
130
+ /** Selector pattern of a variant that only changes the element's selector ("&:hover"), or null. */
131
+ function innerPattern(name) {
132
+ const r = applyVariant(name, [state()]);
133
+ return r && r.length === 1 && !r[0].at.length ? r[0].sel : null;
134
+ }
135
+
136
+ const patternOf = (name) => {
137
+ const a = arbitrary(name);
138
+ if (a !== null) return a.includes("&") ? a : `&${a}`;
139
+ return innerPattern(name);
140
+ };
141
+
142
+ /**
143
+ * Apply one variant to a list of branches.
144
+ * @returns {{sel:string, at:string[]}[]|null} null when the variant is unknown
145
+ */
146
+ export function applyVariant(name, branches) {
147
+ const mapSel = (fn) => branches.map((b) => ({ ...b, sel: fn(b.sel) }));
148
+ const wrap = (at) => branches.map((b) => ({ ...b, at: [...b.at, at] }));
149
+ let m;
150
+
151
+ if (Object.hasOwn(PSEUDO_CLASS, name)) return mapSel((s) => addSimple(s, PSEUDO_CLASS[name]));
152
+ // marker: and selection: also reach the descendants (<ul class="marker:…"> colors the bullets of its <li>s).
153
+ if (name === "marker" || name === "selection") {
154
+ return branches.flatMap((b) => [{ ...b, sel: b.sel + PSEUDO_ELEMENT[name] }, { ...b, sel: `${b.sel} *${PSEUDO_ELEMENT[name]}` }]);
155
+ }
156
+ if (Object.hasOwn(PSEUDO_ELEMENT, name)) return mapSel((s) => s + PSEUDO_ELEMENT[name]);
157
+ if (Object.hasOwn(MEDIA, name)) return wrap(`@media ${MEDIA[name]}`);
158
+ if (Object.hasOwn(SCROLL_STATE, name)) return wrap(`@container ${SCROLL_STATE[name]}`);
159
+ if (name === "starting") return wrap("@starting-style");
160
+ if (name === "rtl" || name === "ltr") return mapSel((s) => addSimple(s, `:where(:dir(${name}), [dir="${name}"], [dir="${name}"] *)`));
161
+ if (name === "dark") {
162
+ return branches.flatMap((b) => [
163
+ { sel: `${DARK_EXPLICIT} ${b.sel}`, at: b.at },
164
+ { sel: `${DARK_SYSTEM} ${b.sel}`, at: [...b.at, "@media (prefers-color-scheme: dark)"] },
165
+ ]);
166
+ }
167
+ if (name === "*") return mapSel((s) => `${s} > *`);
168
+ if (name === "**") return mapSel((s) => `${s} *`);
169
+
170
+ // viewport: sm, max-sm, min-sm, min-[600px], max-[600px]
171
+ if (Object.hasOwn(BREAKPOINTS, name)) return wrap(`@media (min-width: ${BREAKPOINTS[name]})`);
172
+ if ((m = name.match(/^(min|max)-(.+)$/))) {
173
+ const [, kind, rest] = m;
174
+ const a = arbitrary(rest);
175
+ const v = a ?? BREAKPOINTS[rest];
176
+ if (v) return wrap(kind === "min" ? `@media (min-width: ${v})` : `@media (width < ${v})`);
177
+ }
178
+ // container queries: cq-md, cq-max-md, cq-[400px], cq-md/name (and the short form @md)
179
+ if ((m = name.match(/^(?:cq-|@)(?:(min|max)-)?(.+?)(?:\/([\w-]+))?$/))) {
180
+ const [, kind, size, cname] = m;
181
+ const a = arbitrary(size);
182
+ const v = a ?? CONTAINER_SIZES[size];
183
+ if (v) return wrap(`@container ${cname ? cname + " " : ""}${kind === "max" ? `(width < ${v})` : `(min-width: ${v})`}`);
184
+ }
185
+ // supports
186
+ if ((m = name.match(/^(not-)?supports-(.+)$/))) {
187
+ const a = arbitrary(m[2]);
188
+ const cond = a !== null ? (/^[\w-]+\s*:/.test(a) ? `(${a})` : /^[\w-]+$/.test(a) ? `(${a}: inherit)` : a) : `(${m[2]}: inherit)`;
189
+ return wrap(`@supports ${m[1] ? "not " : ""}${cond}`);
190
+ }
191
+ // data-* / aria-*
192
+ if ((m = name.match(/^data-(.+)$/))) {
193
+ const a = arbitrary(m[1]);
194
+ return mapSel((s) => addSimple(s, a !== null ? `[data-${quoteAttr(a)}]` : `[data-${m[1]}]`));
195
+ }
196
+ if ((m = name.match(/^aria-(.+)$/))) {
197
+ const a = arbitrary(m[1]);
198
+ if (a === null && !ARIA.includes(m[1])) return null;
199
+ return mapSel((s) => addSimple(s, a !== null ? `[aria-${quoteAttr(a)}]` : `[aria-${m[1]}="true"]`));
200
+ }
201
+ // group-* / peer-* (optionally named: group-hover/card)
202
+ if ((m = name.match(/^(group|peer)-(.+?)(?:\/([\w-]+))?$/))) {
203
+ const [, kind, innerName, gname] = m;
204
+ const pattern = patternOf(innerName);
205
+ if (!pattern) return null;
206
+ const marker = `:where(.${kind}${gname ? "\\/" + esc(gname) : ""})`;
207
+ const rel = pattern.replace(/&/g, marker);
208
+ return mapSel((s) => addSimple(s, `:is(${rel} ${kind === "group" ? "*" : "~ *"})`));
209
+ }
210
+ // has-* / not-* / in-*
211
+ if ((m = name.match(/^(has|not|in)-(.+)$/))) {
212
+ const [, kind, innerName] = m;
213
+ const pattern = patternOf(innerName);
214
+ if (!pattern && kind === "not") {
215
+ // not-md, not-print, not-supports-[..]
216
+ const inner = applyVariant(innerName, [state()]);
217
+ if (inner && inner.length === 1 && inner[0].at.length === 1) {
218
+ const at = inner[0].at[0];
219
+ if (/^@media /.test(at)) { const p = at.slice(7); return wrap(`@media not ${p.startsWith("(") ? "all and " : ""}${p}`); }
220
+ if (/^@supports not /.test(at)) return wrap(at.replace("@supports not ", "@supports "));
221
+ if (/^@supports /.test(at)) return wrap(at.replace("@supports ", "@supports not "));
222
+ }
223
+ return null;
224
+ }
225
+ if (!pattern || !pattern.startsWith("&")) return null;
226
+ const rest = pattern.slice(1);
227
+ if (kind === "has") return mapSel((s) => addSimple(s, `:has(${rest})`));
228
+ if (kind === "not") return mapSel((s) => addSimple(s, `:not(${rest})`));
229
+ return mapSel((s) => `:where(${rest || "*"}) ${s}`);
230
+ }
231
+ if ((m = name.match(/^nth-(last-)?(of-type-)?(.+)$/))) {
232
+ const a = arbitrary(m[3]);
233
+ const arg = a ?? (/^\d+$/.test(m[3]) ? m[3] : /^(odd|even)$/.test(m[3]) ? m[3] : null);
234
+ if (arg === null) return null;
235
+ return mapSel((s) => addSimple(s, `:nth-${m[1] ? "last-" : ""}${m[2] ? "of-type" : "child"}(${arg})`));
236
+ }
237
+ // arbitrary variant: [&>svg]:, [&_p]:, [@media(min-width:900px)]:
238
+ if ((m = name.match(/^\[([\s\S]+)\]$/))) {
239
+ const body = arbitrary(name);
240
+ if (body.startsWith("@")) return wrap(body.replace(/^@(media|supports|container)\s*/, "@$1 "));
241
+ if (!body.includes("&")) return null;
242
+ return mapSel((s) => body.replace(/&/g, s));
243
+ }
244
+ return null;
245
+ }
246
+
247
+ /** Apply all variants left to right. Returns branches or null if any variant is unknown. */
248
+ export function resolveVariants(variants) {
249
+ let branches = [state()];
250
+ for (const v of variants) {
251
+ branches = applyVariant(v, branches);
252
+ if (!branches) return null;
253
+ }
254
+ return branches;
255
+ }
256
+
257
+ /** Cascade rank of a variant list: states < group/peer < attribute < media < dark < screens < containers. */
258
+ export function rankOf(variants) {
259
+ let rank = 0;
260
+ for (const v of variants) {
261
+ let r;
262
+ if (Object.hasOwn(PSEUDO_ELEMENT, v)) r = 100;
263
+ else if (STRUCT_ORDER.includes(v)) r = 200 + STRUCT_ORDER.indexOf(v);
264
+ else if (Object.hasOwn(PSEUDO_CLASS, v)) r = 300 + STATE_ORDER.indexOf(v);
265
+ else if (/^(group|peer|in)-/.test(v)) r = 400;
266
+ else if (/^(has|not|aria|data|nth)-|^[*\[]/.test(v)) r = 500;
267
+ else if (/^(not-)?supports-/.test(v)) r = 600;
268
+ else if (v === "rtl" || v === "ltr") r = 650;
269
+ else if (Object.hasOwn(MEDIA, v) || Object.hasOwn(SCROLL_STATE, v)) r = 700;
270
+ else if (v === "dark") r = 800;
271
+ else if (v === "starting") r = 900;
272
+ else if (/^max-/.test(v)) r = 1000 - Object.keys(BREAKPOINTS).indexOf(v.slice(4)) * 10;
273
+ else if (Object.hasOwn(BREAKPOINTS, v) || /^min-/.test(v)) r = 1100 + Object.keys(BREAKPOINTS).indexOf(v.replace(/^min-/, "")) * 10;
274
+ else r = 2000;
275
+ rank += r;
276
+ }
277
+ return rank;
278
+ }