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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clampography",
3
- "version": "2.0.0-beta.10",
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; /* Monospace numbers for alignment */
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
- samp: {
154
- "font-family": "var(--font-family-mono)",
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-bottom": "1px solid var(--clampography-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((options = {}) => {
20
- return ({ addBase }) => {
21
- // 1. Load Base and Extra styles
22
- // We use the helper to correctly parse "false" string from CSS
23
- const includeBase = resolveBool(options.base, true); // Default: true
24
- const includeExtra = resolveBool(options.extra, false); // Default: false
25
-
26
- includeBase && addBase(baseStyles);
27
- includeExtra && addBase(extraStyles);
28
-
29
- // 2. Parse themes configuration
30
- let configThemes = options.themes;
31
- let themesToInclude = [];
32
- let defaultThemeName = null;
33
- let prefersDarkTheme = false;
34
- let rootSelector = options.root ?? ":root";
35
-
36
- // Normalize input to an array of strings
37
- let rawThemeList = [];
38
-
39
- if (typeof configThemes === "string") {
40
- if (configThemes.trim() === "all") {
41
- // Special case: themes: all
42
- rawThemeList = Object.keys(builtInThemes);
43
- } else if (configThemes.trim() === "false") {
44
- // Explicitly disabled themes
45
- rawThemeList = [];
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
- rawThemeList = configThemes.split(",");
48
- }
49
- } else if (Array.isArray(configThemes)) {
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
- // Check for --prefersdark flag
71
- if (themeName.toLowerCase().includes("--prefersdark")) {
72
- themeName = themeName.replace(/--prefersdark/i, "").trim();
73
- prefersDarkTheme = themeName;
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
- // Check if theme exists in the database
77
- if (builtInThemes[themeName]) {
78
- themesToInclude.push(themeName);
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
- // If list is empty after filtering, stop here
83
- if (
84
- themesToInclude.length === 0 && !defaultThemeName && !prefersDarkTheme
85
- ) return;
86
-
87
- // 4. Generate CSS
88
- const themeStyles = {};
89
-
90
- // A. Default theme (:root)
91
- if (defaultThemeName && builtInThemes[defaultThemeName]) {
92
- themeStyles[rootSelector] = builtInThemes[defaultThemeName];
93
- }
94
-
95
- // B. Theme for prefers-color-scheme: dark
96
- if (prefersDarkTheme && builtInThemes[prefersDarkTheme]) {
97
- themeStyles["@media (prefers-color-scheme: dark)"] = {
98
- [rootSelector]: builtInThemes[prefersDarkTheme],
99
- };
100
- }
101
-
102
- // C. Scoped styles [data-theme="..."]
103
- themesToInclude.forEach((themeName) => {
104
- themeStyles[`[data-theme="${themeName}"]`] = builtInThemes[themeName];
105
- });
106
-
107
- addBase(themeStyles);
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
+ );
@@ -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
- "text": "--clampography-text",
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
- themeColors[keyMap[key]] = options[key];
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] = options[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": "#ffffff",
5
- "--clampography-surface": "#f3f4f6",
6
- "--clampography-border": "#e5e7eb",
7
- "--clampography-heading": "#111827",
8
- "--clampography-text": "#374151",
9
- "--clampography-muted": "#6b7280",
10
- "--clampography-link": "#2563eb",
11
- "--clampography-primary": "#3b82f6",
12
- "--clampography-secondary": "#9333ea",
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": "#0f172a",
17
- "--clampography-surface": "#1e293b",
18
- "--clampography-border": "#334155",
19
- "--clampography-heading": "#f8fafc",
20
- "--clampography-text": "#cbd5e1",
21
- "--clampography-muted": "#94a3b8",
22
- "--clampography-link": "#60a5fa",
23
- "--clampography-primary": "#3b82f6",
24
- "--clampography-secondary": "#a855f7",
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": "#ece3ca",
29
- "--clampography-surface": "#e0d6b6",
30
- "--clampography-border": "#cbbd99",
31
- "--clampography-heading": "#2c2420",
32
- "--clampography-text": "#4a3b32",
33
- "--clampography-muted": "#756354",
34
- "--clampography-link": "#d97757",
35
- "--clampography-primary": "#e45f2b",
36
- "--clampography-secondary": "#f6c342",
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": "#000b1e",
41
- "--clampography-surface": "#051630",
42
- "--clampography-border": "#0f3661",
43
- "--clampography-heading": "#ffffff",
44
- "--clampography-text": "#00f0ff",
45
- "--clampography-muted": "#008f99",
46
- "--clampography-link": "#ff0099",
47
- "--clampography-primary": "#fcee0a",
48
- "--clampography-secondary": "#05ffa1",
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