clampography 2.0.0-beta.10 → 2.0.0-beta.12
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/package.json +1 -1
- package/src/base.css +1 -1
- package/src/base.js +3 -6
- package/src/convert.js +185 -0
- package/src/extra.js +72 -48
- package/src/index.js +111 -85
- package/src/theme-plugin.js +37 -5
- package/src/themes.js +52 -36
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clampography",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.12",
|
|
4
4
|
"description": "Fluid typography system based on CSS clamp() with optional themes and extra styles. A feature-rich alternative to Tailwind CSS Typography plugin.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
package/src/base.css
CHANGED
|
@@ -252,7 +252,7 @@
|
|
|
252
252
|
/* Time element */
|
|
253
253
|
time {
|
|
254
254
|
font-style: normal;
|
|
255
|
-
font-variant-numeric: tabular-nums;
|
|
255
|
+
font-variant-numeric: tabular-nums;
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
/* --------------------------------------------------------------------------
|
package/src/base.js
CHANGED
|
@@ -144,15 +144,13 @@ export default {
|
|
|
144
144
|
"line-height": "1.5",
|
|
145
145
|
},
|
|
146
146
|
|
|
147
|
-
kbd: {
|
|
147
|
+
":where(code, kbd, samp)": {
|
|
148
148
|
"font-family": "var(--font-family-mono)",
|
|
149
149
|
"font-size": "0.875em",
|
|
150
|
-
"font-weight": "600",
|
|
151
150
|
},
|
|
152
151
|
|
|
153
|
-
|
|
154
|
-
"font-
|
|
155
|
-
"font-size": "0.875em",
|
|
152
|
+
kbd: {
|
|
153
|
+
"font-weight": "600",
|
|
156
154
|
},
|
|
157
155
|
|
|
158
156
|
data: {
|
|
@@ -331,7 +329,6 @@ export default {
|
|
|
331
329
|
"margin-top": "var(--spacing-md)",
|
|
332
330
|
"margin-bottom": "var(--spacing-md)",
|
|
333
331
|
padding: "var(--spacing-md)",
|
|
334
|
-
border: "0",
|
|
335
332
|
},
|
|
336
333
|
|
|
337
334
|
legend: {
|
package/src/convert.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Converts base.css to base.js format with preserved comments
|
|
5
|
+
* https://claude.ai/chat/90dedd98-3e31-4831-a620-c35d6523b836
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
9
|
+
import { resolve } from "path";
|
|
10
|
+
|
|
11
|
+
// Configuration
|
|
12
|
+
const INPUT_FILE = "base.css";
|
|
13
|
+
const OUTPUT_FILE = "base.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parse CSS content into structured data
|
|
17
|
+
*/
|
|
18
|
+
function parseCSS(css) {
|
|
19
|
+
const result = {};
|
|
20
|
+
const lines = css.split("\n");
|
|
21
|
+
let currentContext = result;
|
|
22
|
+
let contextStack = [result];
|
|
23
|
+
let currentSelector = null;
|
|
24
|
+
let inAtLayer = false;
|
|
25
|
+
let buffer = "";
|
|
26
|
+
let commentBuffer = [];
|
|
27
|
+
|
|
28
|
+
for (let i = 0; i < lines.length; i++) {
|
|
29
|
+
const line = lines[i].trim();
|
|
30
|
+
|
|
31
|
+
// Skip empty lines
|
|
32
|
+
if (!line) {
|
|
33
|
+
if (commentBuffer.length > 0) {
|
|
34
|
+
commentBuffer.push("");
|
|
35
|
+
}
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Capture comments
|
|
40
|
+
if (line.startsWith("/*")) {
|
|
41
|
+
const commentLines = [line];
|
|
42
|
+
while (i < lines.length && !lines[i].includes("*/")) {
|
|
43
|
+
i++;
|
|
44
|
+
commentLines.push(lines[i].trim());
|
|
45
|
+
}
|
|
46
|
+
commentBuffer.push(...commentLines);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Handle @layer
|
|
51
|
+
if (line.startsWith("@layer")) {
|
|
52
|
+
const match = line.match(/@layer\s+(\w+)/);
|
|
53
|
+
if (match) {
|
|
54
|
+
inAtLayer = true;
|
|
55
|
+
currentContext["@layer base"] = {};
|
|
56
|
+
contextStack.push(currentContext["@layer base"]);
|
|
57
|
+
currentContext = currentContext["@layer base"];
|
|
58
|
+
}
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Handle opening brace (new rule)
|
|
63
|
+
if (line.includes("{") && !line.includes("}")) {
|
|
64
|
+
const selector = line.replace("{", "").trim();
|
|
65
|
+
|
|
66
|
+
// Store comments before selector
|
|
67
|
+
if (commentBuffer.length > 0) {
|
|
68
|
+
currentContext[`__comment_${Object.keys(currentContext).length}`] =
|
|
69
|
+
commentBuffer.join("\n");
|
|
70
|
+
commentBuffer = [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
currentContext[selector] = {};
|
|
74
|
+
contextStack.push(currentContext[selector]);
|
|
75
|
+
currentContext = currentContext[selector];
|
|
76
|
+
currentSelector = selector;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Handle closing brace
|
|
81
|
+
if (line === "}") {
|
|
82
|
+
contextStack.pop();
|
|
83
|
+
currentContext = contextStack[contextStack.length - 1];
|
|
84
|
+
currentSelector = null;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Handle properties
|
|
89
|
+
if (line.includes(":") && currentSelector) {
|
|
90
|
+
let [prop, ...valueParts] = line.split(":");
|
|
91
|
+
let value = valueParts.join(":").trim();
|
|
92
|
+
|
|
93
|
+
// Remove semicolon and inline comments
|
|
94
|
+
const inlineComment = value.match(/\/\*.*?\*\//);
|
|
95
|
+
if (inlineComment) {
|
|
96
|
+
value = value.replace(inlineComment[0], "").trim();
|
|
97
|
+
}
|
|
98
|
+
value = value.replace(";", "").trim();
|
|
99
|
+
|
|
100
|
+
prop = prop.trim();
|
|
101
|
+
|
|
102
|
+
// Store inline comment separately if exists
|
|
103
|
+
if (inlineComment && inlineComment[0]) {
|
|
104
|
+
currentContext[`__inline_comment_${prop}`] = inlineComment[0];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
currentContext[prop] = value;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Convert parsed CSS to JS object string
|
|
116
|
+
*/
|
|
117
|
+
function toJSObject(obj, indent = 0) {
|
|
118
|
+
const spaces = " ".repeat(indent);
|
|
119
|
+
const lines = [];
|
|
120
|
+
|
|
121
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
122
|
+
// Handle comments
|
|
123
|
+
if (key.startsWith("__comment_")) {
|
|
124
|
+
const comment = value.split("\n").map((line) => `${spaces}${line}`).join(
|
|
125
|
+
"\n",
|
|
126
|
+
);
|
|
127
|
+
lines.push(comment);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (key.startsWith("__inline_comment_")) {
|
|
132
|
+
continue; // Skip inline comments in first pass
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
136
|
+
// It's a nested object (selector)
|
|
137
|
+
lines.push(`${spaces}"${key}": {`);
|
|
138
|
+
lines.push(toJSObject(value, indent + 1));
|
|
139
|
+
lines.push(`${spaces}},`);
|
|
140
|
+
} else {
|
|
141
|
+
// It's a property
|
|
142
|
+
const formattedValue = value.includes('"') || value.includes("'")
|
|
143
|
+
? `"${value.replace(/"/g, '\\"')}"`
|
|
144
|
+
: `"${value}"`;
|
|
145
|
+
|
|
146
|
+
// Check for inline comment
|
|
147
|
+
const inlineCommentKey = `__inline_comment_${key}`;
|
|
148
|
+
const inlineComment = obj[inlineCommentKey];
|
|
149
|
+
const comment = inlineComment ? ` ${inlineComment}` : "";
|
|
150
|
+
|
|
151
|
+
lines.push(`${spaces}"${key}": ${formattedValue},${comment}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return lines.join("\n");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Main conversion function
|
|
160
|
+
*/
|
|
161
|
+
function convertCSStoJS(cssContent) {
|
|
162
|
+
const parsed = parseCSS(cssContent);
|
|
163
|
+
const jsContent = `export default {\n${
|
|
164
|
+
toJSObject(parsed["@layer base"] || parsed, 1)
|
|
165
|
+
}\n};\n`;
|
|
166
|
+
return jsContent;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Main execution
|
|
170
|
+
try {
|
|
171
|
+
console.log(`📖 Reading ${INPUT_FILE}...`);
|
|
172
|
+
const cssContent = readFileSync(resolve(INPUT_FILE), "utf-8");
|
|
173
|
+
|
|
174
|
+
console.log("⚙️ Converting CSS to JS...");
|
|
175
|
+
const jsContent = convertCSStoJS(cssContent);
|
|
176
|
+
|
|
177
|
+
console.log(`💾 Writing ${OUTPUT_FILE}...`);
|
|
178
|
+
writeFileSync(resolve(OUTPUT_FILE), jsContent, "utf-8");
|
|
179
|
+
|
|
180
|
+
console.log("✅ Conversion complete!");
|
|
181
|
+
console.log(`📄 Output: ${OUTPUT_FILE}`);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.error("❌ Error:", error.message);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
package/src/extra.js
CHANGED
|
@@ -1,64 +1,87 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Extra opinionated styles and coloring.
|
|
3
|
-
* Applies colors to the structural base elements.
|
|
4
|
-
*/
|
|
5
1
|
export default {
|
|
6
2
|
// --- Basic Coloring (Applying theme variables) ---
|
|
7
|
-
|
|
8
3
|
"body": {
|
|
9
|
-
"background-color": "var(--clampography-background)",
|
|
10
|
-
"color": "var(--clampography-text)",
|
|
4
|
+
"background-color": "oklch(var(--clampography-background))",
|
|
5
|
+
"color": "oklch(var(--clampography-text))",
|
|
11
6
|
},
|
|
12
7
|
|
|
13
8
|
":where(h1, h2, h3, h4, h5, h6)": {
|
|
14
|
-
"color": "var(--clampography-heading)",
|
|
9
|
+
"color": "oklch(var(--clampography-heading))",
|
|
15
10
|
},
|
|
16
11
|
|
|
12
|
+
// Styled Links (Enhanced)
|
|
17
13
|
"a": {
|
|
18
|
-
"color": "var(--clampography-link)",
|
|
14
|
+
"color": "oklch(var(--clampography-link))",
|
|
15
|
+
"font-weight": "700",
|
|
16
|
+
"letter-spacing": "0.025em",
|
|
17
|
+
"text-decoration-line": "underline",
|
|
18
|
+
"text-decoration-thickness": "2px",
|
|
19
|
+
"text-underline-offset": "4px",
|
|
20
|
+
"transition-property": "color, text-decoration-color",
|
|
21
|
+
"transition-duration": "150ms",
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
"a:hover": {
|
|
25
|
+
"text-decoration-color": "oklch(var(--clampography-primary))",
|
|
19
26
|
},
|
|
20
27
|
|
|
21
28
|
// Lists
|
|
22
29
|
"ul > li::before": {
|
|
23
|
-
"background-color": "var(--clampography-primary)", // Bullet points
|
|
30
|
+
"background-color": "oklch(var(--clampography-primary))", // Bullet points
|
|
24
31
|
},
|
|
25
32
|
|
|
26
33
|
"ol > li::before": {
|
|
27
|
-
"color": "var(--clampography-secondary)", // Numbers
|
|
34
|
+
"color": "oklch(var(--clampography-secondary))", // Numbers
|
|
28
35
|
},
|
|
29
36
|
|
|
30
37
|
// Inline Code
|
|
31
38
|
":where(code, kbd, samp)": {
|
|
32
|
-
"background-color": "var(--clampography-surface)",
|
|
33
|
-
"color": "var(--clampography-heading)",
|
|
34
|
-
"border": "1px solid var(--clampography-border)",
|
|
39
|
+
"background-color": "oklch(var(--clampography-surface))",
|
|
40
|
+
"color": "oklch(var(--clampography-heading))",
|
|
41
|
+
"border": "1px solid oklch(var(--clampography-border))",
|
|
35
42
|
"border-radius": "0.25rem",
|
|
43
|
+
"padding": "0.125rem var(--spacing-xs)",
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// Keyboard input - vertical alignment
|
|
47
|
+
"kbd": {
|
|
48
|
+
transform: "translateY(-0.15em)",
|
|
36
49
|
},
|
|
37
50
|
|
|
38
51
|
// Preformatted Code Blocks
|
|
39
52
|
"pre": {
|
|
40
|
-
"background-color": "var(--clampography-surface)",
|
|
41
|
-
"border": "1px solid var(--clampography-border)",
|
|
53
|
+
"background-color": "oklch(var(--clampography-surface))",
|
|
54
|
+
"border": "1px solid oklch(var(--clampography-border))",
|
|
42
55
|
"border-radius": "0.375rem",
|
|
43
56
|
"padding": "1rem",
|
|
44
57
|
},
|
|
45
58
|
|
|
46
59
|
// Tables
|
|
60
|
+
"table": {
|
|
61
|
+
"padding": "var(--spacing-sm)",
|
|
62
|
+
"border": "1px solid oklch(var(--clampography-border))",
|
|
63
|
+
},
|
|
64
|
+
|
|
47
65
|
"th": {
|
|
48
|
-
"color": "var(--clampography-heading)",
|
|
66
|
+
"color": "oklch(var(--clampography-heading))",
|
|
49
67
|
},
|
|
50
68
|
|
|
51
69
|
"th, td": {
|
|
52
|
-
"border
|
|
70
|
+
"border": "1px solid oklch(var(--clampography-border))",
|
|
53
71
|
},
|
|
54
72
|
|
|
55
73
|
"thead th": {
|
|
56
74
|
"border-bottom-width": "2px",
|
|
57
75
|
},
|
|
58
76
|
|
|
77
|
+
// Zebra striping for table rows
|
|
78
|
+
"tbody tr:nth-child(even)": {
|
|
79
|
+
"background-color": "oklch(var(--clampography-surface))",
|
|
80
|
+
},
|
|
81
|
+
|
|
59
82
|
// Captions & Muted text
|
|
60
83
|
"caption, figcaption, .muted": {
|
|
61
|
-
"color": "var(--clampography-muted)",
|
|
84
|
+
"color": "oklch(var(--clampography-muted))",
|
|
62
85
|
},
|
|
63
86
|
|
|
64
87
|
// Horizontal Rule (Thematic)
|
|
@@ -67,72 +90,73 @@ export default {
|
|
|
67
90
|
"border-width": "0",
|
|
68
91
|
"margin-top": "3rem",
|
|
69
92
|
"margin-bottom": "3rem",
|
|
70
|
-
"background-color": "var(--clampography-border)",
|
|
93
|
+
"background-color": "oklch(var(--clampography-border))",
|
|
71
94
|
},
|
|
72
95
|
|
|
73
|
-
// --- Opinionated Extras ---
|
|
74
|
-
|
|
75
96
|
// Styled Blockquote
|
|
76
97
|
"blockquote": {
|
|
77
98
|
"border-left-width": "4px",
|
|
78
|
-
"border-left-color": "var(--clampography-primary)",
|
|
79
|
-
"background-color": "var(--clampography-surface)",
|
|
99
|
+
"border-left-color": "oklch(var(--clampography-primary))",
|
|
100
|
+
"background-color": "oklch(var(--clampography-surface))",
|
|
80
101
|
"padding": "1rem",
|
|
81
102
|
"border-radius": "0.25rem",
|
|
82
103
|
"font-style": "italic",
|
|
83
|
-
"color": "var(--clampography-heading)",
|
|
84
|
-
},
|
|
85
|
-
|
|
86
|
-
// Styled Links (Enhanced)
|
|
87
|
-
"a": {
|
|
88
|
-
"font-weight": "700",
|
|
89
|
-
"letter-spacing": "0.025em",
|
|
90
|
-
"text-decoration-line": "underline",
|
|
91
|
-
"text-decoration-thickness": "2px",
|
|
92
|
-
"text-underline-offset": "4px",
|
|
93
|
-
"transition-property": "color, text-decoration-color",
|
|
94
|
-
"transition-duration": "150ms",
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
"a:hover": {
|
|
98
|
-
"text-decoration-color": "var(--clampography-primary)",
|
|
104
|
+
"color": "oklch(var(--clampography-heading))",
|
|
99
105
|
},
|
|
100
106
|
|
|
101
107
|
// Mark
|
|
102
108
|
"mark": {
|
|
103
|
-
"background-color": "var(--clampography-primary)",
|
|
104
|
-
"color": "var(--clampography-background)",
|
|
109
|
+
"background-color": "oklch(var(--clampography-primary))",
|
|
110
|
+
"color": "oklch(var(--clampography-background))",
|
|
111
|
+
"padding": "0.125rem var(--spacing-xs)",
|
|
112
|
+
"border-radius": "0.25rem",
|
|
105
113
|
},
|
|
106
114
|
|
|
107
115
|
// Deleted Text
|
|
108
116
|
"del": {
|
|
109
|
-
"text-decoration-color": "var(--clampography-secondary)",
|
|
117
|
+
"text-decoration-color": "oklch(var(--clampography-secondary))",
|
|
110
118
|
"text-decoration-thickness": "2px",
|
|
111
119
|
},
|
|
112
120
|
|
|
121
|
+
// Buttons - All types
|
|
122
|
+
// WILL BE REMOVED FROM THIS FILE
|
|
123
|
+
":where(button, [type='button'], [type='reset'], [type='submit'])": {
|
|
124
|
+
"padding": "var(--spacing-xs) var(--spacing-sm)",
|
|
125
|
+
"border": "1px solid oklch(var(--clampography-border))",
|
|
126
|
+
"border-radius": "0.375rem", // ← Rounded corners
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
// Inputs - All types
|
|
130
|
+
// WILL BE REMOVED FROM THIS FILE
|
|
131
|
+
":where(input:not([type='checkbox'], [type='radio']), textarea, select)": {
|
|
132
|
+
"padding": "var(--spacing-xs) var(--spacing-sm)",
|
|
133
|
+
"border": "1px solid oklch(var(--clampography-border))",
|
|
134
|
+
"border-radius": "0.375rem", // ← Rounded corners
|
|
135
|
+
},
|
|
136
|
+
|
|
113
137
|
// Fieldset
|
|
114
138
|
"fieldset": {
|
|
115
|
-
"border": "1px solid var(--clampography-border)",
|
|
139
|
+
"border": "1px solid oklch(var(--clampography-border))",
|
|
116
140
|
"border-radius": "0.375rem",
|
|
117
141
|
},
|
|
118
142
|
|
|
119
143
|
"legend": {
|
|
120
|
-
"color": "var(--clampography-heading)",
|
|
144
|
+
"color": "oklch(var(--clampography-heading))",
|
|
121
145
|
},
|
|
122
146
|
|
|
123
147
|
// Details
|
|
124
148
|
"details": {
|
|
125
|
-
"border": "1px solid var(--clampography-border)",
|
|
149
|
+
"border": "1px solid oklch(var(--clampography-border))",
|
|
126
150
|
"border-radius": "0.375rem",
|
|
127
151
|
"padding": "0.5rem",
|
|
128
152
|
},
|
|
129
153
|
|
|
130
154
|
"summary": {
|
|
131
|
-
"color": "var(--clampography-heading)",
|
|
155
|
+
"color": "oklch(var(--clampography-heading))",
|
|
132
156
|
},
|
|
133
157
|
|
|
134
158
|
"details[open] > summary": {
|
|
135
|
-
"border-bottom": "1px solid var(--clampography-border)",
|
|
159
|
+
"border-bottom": "1px solid oklch(var(--clampography-border))",
|
|
136
160
|
"padding-bottom": "0.5rem",
|
|
137
161
|
},
|
|
138
162
|
};
|
package/src/index.js
CHANGED
|
@@ -16,94 +16,120 @@ const resolveBool = (value, defaultValue) => {
|
|
|
16
16
|
/**
|
|
17
17
|
* Main plugin function.
|
|
18
18
|
*/
|
|
19
|
-
export default plugin.withOptions(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (configThemes
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
19
|
+
export default plugin.withOptions(
|
|
20
|
+
(options = {}) => {
|
|
21
|
+
return ({ addBase }) => {
|
|
22
|
+
// 1. Load Base and Extra styles
|
|
23
|
+
// We use the helper to correctly parse "false" string from CSS
|
|
24
|
+
const includeBase = resolveBool(options.base, true); // Default: true
|
|
25
|
+
const includeExtra = resolveBool(options.extra, false); // Default: false
|
|
26
|
+
|
|
27
|
+
includeBase && addBase(baseStyles);
|
|
28
|
+
includeExtra && addBase(extraStyles);
|
|
29
|
+
|
|
30
|
+
// 2. Parse themes configuration
|
|
31
|
+
let configThemes = options.themes;
|
|
32
|
+
let themesToInclude = [];
|
|
33
|
+
let defaultThemeName = null;
|
|
34
|
+
let prefersDarkTheme = false;
|
|
35
|
+
let rootSelector = options.root ?? ":root";
|
|
36
|
+
|
|
37
|
+
// Normalize input to an array of strings
|
|
38
|
+
let rawThemeList = [];
|
|
39
|
+
|
|
40
|
+
if (typeof configThemes === "string") {
|
|
41
|
+
if (["all", "true", "yes"].includes(configThemes.trim())) {
|
|
42
|
+
// Special case: themes: all
|
|
43
|
+
rawThemeList = Object.keys(builtInThemes);
|
|
44
|
+
} else if (["false", "none", "no"].includes(configThemes.trim())) {
|
|
45
|
+
// Explicitly disabled themes
|
|
46
|
+
rawThemeList = [];
|
|
47
|
+
} else {
|
|
48
|
+
rawThemeList = configThemes.split(",");
|
|
49
|
+
}
|
|
50
|
+
} else if (Array.isArray(configThemes)) {
|
|
51
|
+
rawThemeList = configThemes;
|
|
46
52
|
} else {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
rawThemeList = configThemes;
|
|
51
|
-
} else {
|
|
52
|
-
// Default behavior: NO themes loaded automatically.
|
|
53
|
-
// User must specify themes to load them.
|
|
54
|
-
rawThemeList = [];
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// 3. Process the list and look for flags (--default, --prefersdark)
|
|
58
|
-
rawThemeList.forEach((rawItem) => {
|
|
59
|
-
let themeName = rawItem.trim();
|
|
60
|
-
|
|
61
|
-
// Ignore empty entries
|
|
62
|
-
if (!themeName) return;
|
|
63
|
-
|
|
64
|
-
// Check for --default flag
|
|
65
|
-
if (themeName.includes("--default")) {
|
|
66
|
-
themeName = themeName.replace("--default", "").trim();
|
|
67
|
-
defaultThemeName = themeName;
|
|
53
|
+
// Default behavior: NO themes loaded automatically.
|
|
54
|
+
// User must specify themes to load them.
|
|
55
|
+
rawThemeList = [];
|
|
68
56
|
}
|
|
69
57
|
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
themeName =
|
|
73
|
-
|
|
58
|
+
// 3. Process the list and look for flags (--default, --prefersdark)
|
|
59
|
+
rawThemeList.forEach((rawItem) => {
|
|
60
|
+
let themeName = rawItem.trim();
|
|
61
|
+
|
|
62
|
+
// Ignore empty entries
|
|
63
|
+
if (!themeName) return;
|
|
64
|
+
|
|
65
|
+
// Check for --default flag
|
|
66
|
+
if (themeName.includes("--default")) {
|
|
67
|
+
themeName = themeName.replace("--default", "").trim();
|
|
68
|
+
defaultThemeName = themeName;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Check for --prefersdark flag
|
|
72
|
+
if (themeName.toLowerCase().includes("--prefersdark")) {
|
|
73
|
+
themeName = themeName.replace(/--prefersdark/i, "").trim();
|
|
74
|
+
prefersDarkTheme = themeName;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check if theme exists in the database
|
|
78
|
+
if (builtInThemes[themeName]) {
|
|
79
|
+
themesToInclude.push(themeName);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// If list is empty after filtering, stop here
|
|
84
|
+
if (
|
|
85
|
+
themesToInclude.length === 0 && !defaultThemeName && !prefersDarkTheme
|
|
86
|
+
) return;
|
|
87
|
+
|
|
88
|
+
// 4. Generate CSS
|
|
89
|
+
const themeStyles = {};
|
|
90
|
+
|
|
91
|
+
// A. Default theme (:root)
|
|
92
|
+
if (defaultThemeName && builtInThemes[defaultThemeName]) {
|
|
93
|
+
themeStyles[rootSelector] = builtInThemes[defaultThemeName];
|
|
74
94
|
}
|
|
75
95
|
|
|
76
|
-
//
|
|
77
|
-
if (builtInThemes[
|
|
78
|
-
|
|
96
|
+
// B. Theme for prefers-color-scheme: dark
|
|
97
|
+
if (prefersDarkTheme && builtInThemes[prefersDarkTheme]) {
|
|
98
|
+
themeStyles["@media (prefers-color-scheme: dark)"] = {
|
|
99
|
+
[rootSelector]: builtInThemes[prefersDarkTheme],
|
|
100
|
+
};
|
|
79
101
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
102
|
+
|
|
103
|
+
// C. Scoped styles [data-theme="..."]
|
|
104
|
+
themesToInclude.forEach((themeName) => {
|
|
105
|
+
themeStyles[`[data-theme="${themeName}"]`] = builtInThemes[themeName];
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
addBase(themeStyles);
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
// Theme extension - enables utilities like bg-surface, text-heading, etc.
|
|
112
|
+
(options = {}) => {
|
|
113
|
+
return {
|
|
114
|
+
theme: {
|
|
115
|
+
extend: {
|
|
116
|
+
colors: {
|
|
117
|
+
background: "var(--clampography-background)",
|
|
118
|
+
border: "var(--clampography-border)",
|
|
119
|
+
error: "var(--clampography-error)",
|
|
120
|
+
heading: "var(--clampography-heading)",
|
|
121
|
+
info: "var(--clampography-info)",
|
|
122
|
+
link: "var(--clampography-link)",
|
|
123
|
+
muted: "var(--clampography-muted)",
|
|
124
|
+
primary: "var(--clampography-primary)",
|
|
125
|
+
secondary: "var(--clampography-secondary)",
|
|
126
|
+
success: "var(--clampography-success)",
|
|
127
|
+
surface: "var(--clampography-surface)",
|
|
128
|
+
text: "var(--clampography-text)",
|
|
129
|
+
warning: "var(--clampography-warning)",
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
);
|
package/src/theme-plugin.js
CHANGED
|
@@ -27,14 +27,18 @@ export default plugin.withOptions((options = {}) => {
|
|
|
27
27
|
// Mapping of simplified keys to full CSS variable names
|
|
28
28
|
const keyMap = {
|
|
29
29
|
"background": "--clampography-background",
|
|
30
|
-
"surface": "--clampography-surface",
|
|
31
30
|
"border": "--clampography-border",
|
|
31
|
+
"error": "--clampography-error",
|
|
32
32
|
"heading": "--clampography-heading",
|
|
33
|
-
"
|
|
34
|
-
"muted": "--clampography-muted",
|
|
33
|
+
"info": "--clampography-info",
|
|
35
34
|
"link": "--clampography-link",
|
|
35
|
+
"muted": "--clampography-muted",
|
|
36
36
|
"primary": "--clampography-primary",
|
|
37
37
|
"secondary": "--clampography-secondary",
|
|
38
|
+
"success": "--clampography-success",
|
|
39
|
+
"surface": "--clampography-surface",
|
|
40
|
+
"text": "--clampography-text",
|
|
41
|
+
"warning": "--clampography-warning",
|
|
38
42
|
};
|
|
39
43
|
|
|
40
44
|
// First, populate with fallback colors
|
|
@@ -49,10 +53,38 @@ export default plugin.withOptions((options = {}) => {
|
|
|
49
53
|
["name", "default", "prefersdark", "root", "color-scheme"].includes(key)
|
|
50
54
|
) return;
|
|
51
55
|
|
|
56
|
+
const value = options[key];
|
|
57
|
+
|
|
52
58
|
if (keyMap[key]) {
|
|
53
|
-
|
|
59
|
+
// Validate color format for better DX
|
|
60
|
+
if (value && typeof value === "string") {
|
|
61
|
+
// Check if value starts with oklch() or is a valid CSS color
|
|
62
|
+
const isOklch = value.trim().startsWith("oklch(");
|
|
63
|
+
const isHex = /^#[0-9A-Fa-f]{3,8}$/.test(value.trim());
|
|
64
|
+
const isRgb = value.trim().startsWith("rgb(") ||
|
|
65
|
+
value.trim().startsWith("rgba(");
|
|
66
|
+
|
|
67
|
+
if (!isOklch && !isHex && !isRgb) {
|
|
68
|
+
console.warn(
|
|
69
|
+
`Clampography (${themeName}): Color "${key}" has value "${value}" which may not be a valid color format. ` +
|
|
70
|
+
`For best compatibility with opacity modifiers (e.g., bg-${key}/20), use full OKLCH format: ` +
|
|
71
|
+
`oklch(70% 0.2 180) or oklch(0.7 0.2 180)`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (isHex || isRgb) {
|
|
76
|
+
console.info(
|
|
77
|
+
`Clampography (${themeName}): Color "${key}" uses ${
|
|
78
|
+
isHex ? "HEX" : "RGB"
|
|
79
|
+
} format. ` +
|
|
80
|
+
`Consider using OKLCH format for better color space support and smoother gradients.`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
themeColors[keyMap[key]] = value;
|
|
54
86
|
} else if (key.startsWith("--")) {
|
|
55
|
-
themeColors[key] =
|
|
87
|
+
themeColors[key] = value;
|
|
56
88
|
}
|
|
57
89
|
});
|
|
58
90
|
|
package/src/themes.js
CHANGED
|
@@ -1,51 +1,67 @@
|
|
|
1
1
|
export const themes = {
|
|
2
2
|
light: {
|
|
3
3
|
"color-scheme": "light",
|
|
4
|
-
"--clampography-background": "
|
|
5
|
-
"--clampography-
|
|
6
|
-
"--clampography-
|
|
7
|
-
"--clampography-heading": "
|
|
8
|
-
"--clampography-
|
|
9
|
-
"--clampography-
|
|
10
|
-
"--clampography-
|
|
11
|
-
"--clampography-primary": "
|
|
12
|
-
"--clampography-secondary": "
|
|
4
|
+
"--clampography-background": "oklch(100% 0 0)",
|
|
5
|
+
"--clampography-border": "oklch(92% 0.003 264)",
|
|
6
|
+
"--clampography-error": "oklch(63% 0.22 27)",
|
|
7
|
+
"--clampography-heading": "oklch(15% 0.02 264)",
|
|
8
|
+
"--clampography-info": "oklch(63% 0.258 262)",
|
|
9
|
+
"--clampography-link": "oklch(43% 0.19 264)",
|
|
10
|
+
"--clampography-muted": "oklch(52% 0.014 258)",
|
|
11
|
+
"--clampography-primary": "oklch(63% 0.258 262)",
|
|
12
|
+
"--clampography-secondary": "oklch(55% 0.28 300)",
|
|
13
|
+
"--clampography-success": "oklch(65% 0.17 165)",
|
|
14
|
+
"--clampography-surface": "oklch(96% 0.003 264)",
|
|
15
|
+
"--clampography-text": "oklch(31% 0.02 257)",
|
|
16
|
+
"--clampography-warning": "oklch(72% 0.17 65)",
|
|
13
17
|
},
|
|
14
18
|
dark: {
|
|
15
19
|
"color-scheme": "dark",
|
|
16
|
-
"--clampography-background": "
|
|
17
|
-
"--clampography-
|
|
18
|
-
"--clampography-
|
|
19
|
-
"--clampography-heading": "
|
|
20
|
-
"--clampography-
|
|
21
|
-
"--clampography-
|
|
22
|
-
"--clampography-
|
|
23
|
-
"--clampography-primary": "
|
|
24
|
-
"--clampography-secondary": "
|
|
20
|
+
"--clampography-background": "oklch(10% 0 0)",
|
|
21
|
+
"--clampography-border": "oklch(31% 0.03 254)",
|
|
22
|
+
"--clampography-error": "oklch(63% 0.22 27)",
|
|
23
|
+
"--clampography-heading": "oklch(98% 0.003 264)",
|
|
24
|
+
"--clampography-info": "oklch(72% 0.17 254)",
|
|
25
|
+
"--clampography-link": "oklch(72% 0.17 254)",
|
|
26
|
+
"--clampography-muted": "oklch(68% 0.024 254)",
|
|
27
|
+
"--clampography-primary": "oklch(63% 0.258 262)",
|
|
28
|
+
"--clampography-secondary": "oklch(63% 0.25 300)",
|
|
29
|
+
"--clampography-success": "oklch(65% 0.17 165)",
|
|
30
|
+
"--clampography-surface": "oklch(12% 0 0)",
|
|
31
|
+
"--clampography-text": "oklch(95% 0 0)",
|
|
32
|
+
"--clampography-warning": "oklch(72% 0.17 65)",
|
|
25
33
|
},
|
|
26
34
|
retro: {
|
|
27
35
|
"color-scheme": "light",
|
|
28
|
-
"--clampography-background": "
|
|
29
|
-
"--clampography-
|
|
30
|
-
"--clampography-
|
|
31
|
-
"--clampography-heading": "
|
|
32
|
-
"--clampography-
|
|
33
|
-
"--clampography-
|
|
34
|
-
"--clampography-
|
|
35
|
-
"--clampography-primary": "
|
|
36
|
-
"--clampography-secondary": "
|
|
36
|
+
"--clampography-background": "oklch(91% 0.03 85)",
|
|
37
|
+
"--clampography-border": "oklch(78% 0.05 85)",
|
|
38
|
+
"--clampography-error": "oklch(52% 0.15 35)",
|
|
39
|
+
"--clampography-heading": "oklch(18% 0.02 35)",
|
|
40
|
+
"--clampography-info": "oklch(60% 0.06 230)",
|
|
41
|
+
"--clampography-link": "oklch(63% 0.13 40)",
|
|
42
|
+
"--clampography-muted": "oklch(44% 0.03 45)",
|
|
43
|
+
"--clampography-primary": "oklch(60% 0.19 35)",
|
|
44
|
+
"--clampography-secondary": "oklch(80% 0.14 85)",
|
|
45
|
+
"--clampography-success": "oklch(62% 0.10 130)",
|
|
46
|
+
"--clampography-surface": "oklch(87% 0.04 85)",
|
|
47
|
+
"--clampography-text": "oklch(28% 0.03 40)",
|
|
48
|
+
"--clampography-warning": "oklch(80% 0.14 85)",
|
|
37
49
|
},
|
|
38
50
|
cyberpunk: {
|
|
39
51
|
"color-scheme": "dark",
|
|
40
|
-
"--clampography-background": "
|
|
41
|
-
"--clampography-
|
|
42
|
-
"--clampography-
|
|
43
|
-
"--clampography-heading": "
|
|
44
|
-
"--clampography-
|
|
45
|
-
"--clampography-
|
|
46
|
-
"--clampography-
|
|
47
|
-
"--clampography-primary": "
|
|
48
|
-
"--clampography-secondary": "
|
|
52
|
+
"--clampography-background": "oklch(8% 0.04 264)",
|
|
53
|
+
"--clampography-border": "oklch(27% 0.09 254)",
|
|
54
|
+
"--clampography-error": "oklch(60% 0.29 340)",
|
|
55
|
+
"--clampography-heading": "oklch(100% 0 0)",
|
|
56
|
+
"--clampography-info": "oklch(88% 0.16 195)",
|
|
57
|
+
"--clampography-link": "oklch(60% 0.29 340)",
|
|
58
|
+
"--clampography-muted": "oklch(55% 0.07 200)",
|
|
59
|
+
"--clampography-primary": "oklch(93% 0.22 105)",
|
|
60
|
+
"--clampography-secondary": "oklch(87% 0.20 165)",
|
|
61
|
+
"--clampography-success": "oklch(87% 0.20 165)",
|
|
62
|
+
"--clampography-surface": "oklch(12% 0.05 264)",
|
|
63
|
+
"--clampography-text": "oklch(88% 0.16 195)",
|
|
64
|
+
"--clampography-warning": "oklch(93% 0.22 105)",
|
|
49
65
|
},
|
|
50
66
|
};
|
|
51
67
|
|