clampography 2.0.0-beta.13 → 2.0.0-beta.15

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.13",
3
+ "version": "2.0.0-beta.15",
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",
@@ -45,6 +45,6 @@
45
45
  "tailwindcss": ">=4.0.0"
46
46
  },
47
47
  "devDependencies": {
48
- "tailwindcss": "^4.0.0"
48
+ "tailwindcss": "^4.1.18"
49
49
  }
50
50
  }
package/src/convert.js CHANGED
@@ -171,13 +171,13 @@ try {
171
171
  console.log(`📖 Reading ${INPUT_FILE}...`);
172
172
  const cssContent = readFileSync(resolve(INPUT_FILE), "utf-8");
173
173
 
174
- console.log("⚙️ Converting CSS to JS...");
174
+ console.log(" Converting CSS to JS...");
175
175
  const jsContent = convertCSStoJS(cssContent);
176
176
 
177
177
  console.log(`💾 Writing ${OUTPUT_FILE}...`);
178
178
  writeFileSync(resolve(OUTPUT_FILE), jsContent, "utf-8");
179
179
 
180
- console.log(" Conversion complete!");
180
+ console.log("✔️ Conversion complete!");
181
181
  console.log(`📄 Output: ${OUTPUT_FILE}`);
182
182
  } catch (error) {
183
183
  console.error("❌ Error:", error.message);
package/src/extra.js CHANGED
@@ -17,6 +17,8 @@ export default {
17
17
  "text-decoration-line": "underline",
18
18
  "text-decoration-thickness": "2px",
19
19
  "text-underline-offset": "4px",
20
+ "text-decoration-color":
21
+ "color-mix(in oklab, var(--clampography-link) 30%, transparent)",
20
22
  "transition-property": "color, text-decoration-color",
21
23
  "transition-duration": "150ms",
22
24
  },
@@ -35,7 +37,7 @@ export default {
35
37
  },
36
38
 
37
39
  // Inline Code
38
- ":where(code, kbd, samp)": {
40
+ ":where(code:not(pre code), kbd, samp)": {
39
41
  "background-color": "var(--clampography-surface)",
40
42
  "color": "var(--clampography-heading)",
41
43
  "border": "1px solid var(--clampography-border)",
package/src/index.js CHANGED
@@ -3,6 +3,9 @@ import { themes as builtInThemes } from "./themes.js";
3
3
  import baseStyles from "./base.js";
4
4
  import extraStyles from "./extra.js";
5
5
 
6
+ // Import version from package.json
7
+ import { version } from "../package.json" with { type: "json" };
8
+
6
9
  /**
7
10
  * Helper to resolve boolean options from CSS configuration.
8
11
  * CSS values often come as strings ("true"/"false"), which are both truthy in JS.
@@ -17,111 +20,154 @@ const resolveBool = (value, defaultValue) => {
17
20
  * Main plugin function.
18
21
  */
19
22
  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
- let isAllThemes = false; // Track if user specified "all"
37
-
38
- // Normalize input to an array of strings
39
- let rawThemeList = [];
40
-
41
- if (typeof configThemes === "string") {
42
- if (["all", "true", "yes"].includes(configThemes.trim())) {
43
- // Special case: themes: all
44
- isAllThemes = true;
45
- rawThemeList = Object.keys(builtInThemes);
46
- } else if (["false", "none", "no"].includes(configThemes.trim())) {
47
- // Explicitly disabled themes
48
- rawThemeList = [];
49
- } else {
50
- rawThemeList = configThemes.split(",");
51
- }
52
- } else if (Array.isArray(configThemes)) {
53
- rawThemeList = configThemes;
54
- } else {
55
- // Default behavior: NO themes loaded automatically.
56
- // User must specify themes to load them.
57
- rawThemeList = [];
58
- }
59
-
60
- // 3. Process the list and look for flags (--default, --prefersdark)
61
- rawThemeList.forEach((rawItem) => {
62
- let themeName = rawItem.trim();
63
-
64
- // Ignore empty entries
65
- if (!themeName) return;
66
-
67
- // Check for --default flag
68
- if (themeName.includes("--default")) {
69
- themeName = themeName.replace("--default", "").trim();
70
- defaultThemeName = themeName;
23
+ (() => {
24
+ let firstRun = true; // Track first run for logging
25
+
26
+ return (options = {}) => {
27
+ return ({ addBase }) => {
28
+ // Extract logs option (default: true)
29
+ const showLogs = resolveBool(options.logs, true);
30
+
31
+ // Show startup log only once
32
+ if (showLogs && firstRun) {
33
+ console.log(`🍀 Clampography v${version} loaded successfully`);
34
+ firstRun = false;
71
35
  }
72
36
 
73
- // Check for --prefersdark flag
74
- if (themeName.toLowerCase().includes("--prefersdark")) {
75
- themeName = themeName.replace(/--prefersdark/i, "").trim();
76
- prefersDarkTheme = themeName;
37
+ // 1. Load Base and Extra styles
38
+ // We use the helper to correctly parse "false" string from CSS
39
+ const includeBase = resolveBool(options.base, true); // Default: true
40
+ const includeExtra = resolveBool(options.extra, false); // Default: false
41
+
42
+ includeBase && addBase(baseStyles);
43
+ includeExtra && addBase(extraStyles);
44
+
45
+ // 2. Parse themes configuration
46
+ let configThemes = options.themes;
47
+ let themesToInclude = [];
48
+ let defaultThemeName = null;
49
+ let prefersDarkTheme = false;
50
+ let rootSelector = options.root ?? ":root";
51
+ let isAllThemes = false; // Track if user specified "all"
52
+
53
+ // Normalize input to an array of strings
54
+ let rawThemeList = [];
55
+ if (typeof configThemes === "string") {
56
+ if (["all", "true", "yes"].includes(configThemes.trim())) {
57
+ // Special case: themes: all
58
+ isAllThemes = true;
59
+ rawThemeList = Object.keys(builtInThemes);
60
+ } else if (["false", "none", "no"].includes(configThemes.trim())) {
61
+ // Explicitly disabled themes
62
+ rawThemeList = [];
63
+ } else {
64
+ rawThemeList = configThemes.split(",");
65
+ }
66
+ } else if (Array.isArray(configThemes)) {
67
+ rawThemeList = configThemes;
68
+ } else {
69
+ // Default behavior: NO themes loaded automatically.
70
+ // User must specify themes to load them.
71
+ rawThemeList = [];
77
72
  }
78
73
 
79
- // Check if theme exists in the database
80
- if (builtInThemes[themeName]) {
81
- themesToInclude.push(themeName);
74
+ // 3. Process the list and look for flags (--default, --prefersdark)
75
+ rawThemeList.forEach((rawItem) => {
76
+ let themeName = rawItem.trim();
77
+
78
+ // Ignore empty entries
79
+ if (!themeName) return;
80
+
81
+ // Check for --default flag
82
+ if (themeName.includes("--default")) {
83
+ themeName = themeName.replace("--default", "").trim();
84
+ defaultThemeName = themeName;
85
+ }
86
+
87
+ // Check for --prefersdark flag
88
+ if (themeName.toLowerCase().includes("--prefersdark")) {
89
+ themeName = themeName.replace(/--prefersdark/i, "").trim();
90
+ prefersDarkTheme = themeName;
91
+ }
92
+
93
+ // Check if theme exists in the database
94
+ if (builtInThemes[themeName]) {
95
+ themesToInclude.push(themeName);
96
+ }
97
+ });
98
+
99
+ // If list is empty after filtering, stop here
100
+ if (
101
+ themesToInclude.length === 0 && !defaultThemeName && !prefersDarkTheme
102
+ ) return;
103
+
104
+ // 4. Auto-configure defaults for "themes: all"
105
+ // If user didn't specify --default or --prefersdark flags,
106
+ // automatically set light as default and dark for prefers-color-scheme
107
+ if (isAllThemes) {
108
+ if (!defaultThemeName && themesToInclude.includes("light")) {
109
+ defaultThemeName = "light";
110
+ }
111
+
112
+ if (!prefersDarkTheme && themesToInclude.includes("dark")) {
113
+ prefersDarkTheme = "dark";
114
+ }
82
115
  }
83
- });
84
-
85
- // If list is empty after filtering, stop here
86
- if (
87
- themesToInclude.length === 0 && !defaultThemeName && !prefersDarkTheme
88
- ) return;
89
-
90
- // 4. Auto-configure defaults for "themes: all"
91
- // If user didn't specify --default or --prefersdark flags,
92
- // automatically set light as default and dark for prefers-color-scheme
93
- if (isAllThemes) {
94
- if (!defaultThemeName && themesToInclude.includes("light")) {
95
- defaultThemeName = "light";
96
- }
97
- if (!prefersDarkTheme && themesToInclude.includes("dark")) {
98
- prefersDarkTheme = "dark";
99
- }
100
- }
101
116
 
102
- // 5. Generate CSS
103
- const themeStyles = {};
117
+ // 5. Generate CSS
118
+ const themeStyles = {};
104
119
 
105
- // A. Default theme (:root)
106
- if (defaultThemeName && builtInThemes[defaultThemeName]) {
107
- themeStyles[rootSelector] = builtInThemes[defaultThemeName];
108
- }
120
+ // A. Default theme - uses :where() for lower specificity
121
+ if (defaultThemeName && builtInThemes[defaultThemeName]) {
122
+ const defaultSelector =
123
+ `:where(${rootSelector}),[data-theme="${defaultThemeName}"]`;
124
+ themeStyles[defaultSelector] = builtInThemes[defaultThemeName];
125
+ }
109
126
 
110
- // B. Theme for prefers-color-scheme: dark
111
- if (prefersDarkTheme && builtInThemes[prefersDarkTheme]) {
112
- themeStyles["@media (prefers-color-scheme: dark)"] = {
113
- [rootSelector]: builtInThemes[prefersDarkTheme],
114
- };
115
- }
127
+ // B. Theme for prefers-color-scheme: dark - only applies to root selector
128
+ if (prefersDarkTheme && builtInThemes[prefersDarkTheme]) {
129
+ themeStyles["@media (prefers-color-scheme: dark)"] = {
130
+ [rootSelector]: builtInThemes[prefersDarkTheme],
131
+ };
132
+ }
116
133
 
117
- // C. Scoped styles [data-theme="..."]
118
- themesToInclude.forEach((themeName) => {
119
- themeStyles[`[data-theme="${themeName}"]`] = builtInThemes[themeName];
120
- });
134
+ // C. All themes available via [data-theme] attribute
135
+ // WITH higher specificity to override media query
136
+ themesToInclude.forEach((themeName) => {
137
+ // Skip if already added as default (to avoid duplication)
138
+ if (themeName === defaultThemeName) return;
139
+
140
+ const selector =
141
+ `html[data-theme="${themeName}"], [data-theme="${themeName}"]`;
142
+ themeStyles[selector] = builtInThemes[themeName];
143
+ });
144
+
145
+ // ✅ D. CRITICAL: Override media query with data-theme selectors
146
+ // This ensures manual theme selection always wins over system preference
147
+ if (prefersDarkTheme) {
148
+ themeStyles["@media (prefers-color-scheme: dark)"] = {
149
+ ...themeStyles["@media (prefers-color-scheme: dark)"],
150
+ };
151
+
152
+ // Add all themes inside media query to override the :root rule
153
+ themesToInclude.forEach((themeName) => {
154
+ if (themeName === prefersDarkTheme) return; // Skip the prefersDark theme itself
155
+
156
+ const selector =
157
+ `html[data-theme="${themeName}"], [data-theme="${themeName}"]`;
158
+
159
+ // Add inside media query
160
+ if (!themeStyles["@media (prefers-color-scheme: dark)"][selector]) {
161
+ themeStyles["@media (prefers-color-scheme: dark)"][selector] =
162
+ builtInThemes[themeName];
163
+ }
164
+ });
165
+ }
121
166
 
122
- addBase(themeStyles);
167
+ addBase(themeStyles);
168
+ };
123
169
  };
124
- },
170
+ })(),
125
171
  // Theme extension - enables utilities like bg-surface, text-heading, etc.
126
172
  (options = {}) => {
127
173
  return {
@@ -8,11 +8,17 @@ export default plugin.withOptions((options = {}) => {
8
8
  const isDefault = options.default ?? false;
9
9
  const isPrefersDark = options.prefersdark ?? false;
10
10
  const rootSelector = options.root ?? ":root";
11
- // Defaults to light scheme if not specified
12
11
  const colorScheme = options["color-scheme"] ?? "light";
13
12
 
13
+ // This option is completely separate from the main plugin's logs option
14
+ const showLogs = options.logs !== false;
15
+
14
16
  if (!themeName) {
15
- console.warn("Clampography: Theme definition missing 'name' property.");
17
+ if (showLogs) {
18
+ console.warn(
19
+ "🍀 Clampography: Theme definition missing 'name' property.",
20
+ );
21
+ }
16
22
  return;
17
23
  }
18
24
 
@@ -50,14 +56,15 @@ export default plugin.withOptions((options = {}) => {
50
56
  Object.keys(options).forEach((key) => {
51
57
  // Ignore metadata keys
52
58
  if (
53
- ["name", "default", "prefersdark", "root", "color-scheme"].includes(key)
59
+ ["name", "default", "prefersdark", "root", "color-scheme", "logs"]
60
+ .includes(key)
54
61
  ) return;
55
62
 
56
63
  const value = options[key];
57
64
 
58
65
  if (keyMap[key]) {
59
- // Validate color format for better DX
60
- if (value && typeof value === "string") {
66
+ // Validate color format (only if logs enabled)
67
+ if (showLogs && value && typeof value === "string") {
61
68
  // Check if value starts with oklch() or is a valid CSS color
62
69
  const isOklch = value.trim().startsWith("oklch(");
63
70
  const isHex = /^#[0-9A-Fa-f]{3,8}$/.test(value.trim());
@@ -74,7 +81,7 @@ export default plugin.withOptions((options = {}) => {
74
81
 
75
82
  if (isHex || isRgb) {
76
83
  console.info(
77
- `Clampography (${themeName}): Color "${key}" uses ${
84
+ `🍀 Clampography (${themeName}): Color "${key}" uses ${
78
85
  isHex ? "HEX" : "RGB"
79
86
  } format. ` +
80
87
  `Consider using OKLCH format for better color space support and smoother gradients.`,
@@ -94,15 +101,18 @@ export default plugin.withOptions((options = {}) => {
94
101
  // 4. Generate Styles
95
102
  const styles = {};
96
103
 
97
- // A. Define the theme as a named data-theme
98
- styles[`[data-theme="${themeName}"]`] = themeColors;
104
+ // Build selector based on flags
105
+ let selector = `[data-theme="${themeName}"]`;
99
106
 
100
- // B. If default, apply to root
107
+ // If default, prepend :where(root) with lower specificity
101
108
  if (isDefault) {
102
- styles[rootSelector] = themeColors;
109
+ selector = `:where(${rootSelector}),${selector}`;
103
110
  }
104
111
 
105
- // C. If prefers-dark, apply to media query
112
+ // Apply theme to the constructed selector
113
+ styles[selector] = themeColors;
114
+
115
+ // If prefers-dark, apply only to root selector in media query
106
116
  if (isPrefersDark) {
107
117
  styles["@media (prefers-color-scheme: dark)"] = {
108
118
  [rootSelector]: themeColors,