clampography 2.0.0-beta.27 → 2.0.0-beta.28

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.
@@ -0,0 +1,43 @@
1
+ import fs from "fs";
2
+ import { resolve } from "path";
3
+ import * as themes from "./themes.js";
4
+
5
+ const OUTPUT_DIR = "css";
6
+ const OUTPUT_FILE = "figma-tokens.json";
7
+
8
+ function exportTokens() {
9
+ console.log("🎨 Starting Figma Design Tokens export...");
10
+
11
+ if (!fs.existsSync(OUTPUT_DIR)) {
12
+ fs.mkdirSync(OUTPUT_DIR, { recursive: true });
13
+ }
14
+
15
+ const tokens = {};
16
+ let themeCount = 0;
17
+
18
+ for (const [themeName, themeData] of Object.entries(themes)) {
19
+ // Figma convention: nested objects by theme name
20
+ tokens[themeName] = {};
21
+ themeCount++;
22
+
23
+ for (const [key, value] of Object.entries(themeData)) {
24
+ // Skip non-color attributes like color-scheme
25
+ if (!key.startsWith("--clampography-")) continue;
26
+
27
+ // Clean up the key name for Figma (e.g., "--clampography-primary" -> "primary")
28
+ const colorName = key.replace("--clampography-", "");
29
+
30
+ tokens[themeName][colorName] = {
31
+ value: value,
32
+ type: "color"
33
+ };
34
+ }
35
+ }
36
+
37
+ const outputPath = resolve(OUTPUT_DIR, OUTPUT_FILE);
38
+ fs.writeFileSync(outputPath, JSON.stringify(tokens, null, 2), "utf-8");
39
+
40
+ console.log(`✅ Successfully exported ${themeCount} themes to ${OUTPUT_DIR}/${OUTPUT_FILE}!`);
41
+ }
42
+
43
+ exportTokens();
package/src/extra.js CHANGED
@@ -21,23 +21,45 @@ export default (options = {}) => {
21
21
  }
22
22
  parts.push(current.trim());
23
23
 
24
+ const typographyPrefix = options.typography && options.typography !== "global" ? ` ${options.typography}` : "";
25
+
24
26
  return parts
25
27
  .filter(Boolean)
26
28
  .map((part) => {
27
29
  if (part === ":root" || part === "body") return root;
30
+ if (typographyPrefix) {
31
+ return `${root}${typographyPrefix} ${part}`;
32
+ }
28
33
  return `${root} ${part}`;
29
34
  })
30
35
  .join(", ");
31
36
  };
32
37
 
33
38
  return {
39
+ // Transition duration token — drives smooth color changes when the theme changes.
40
+ // Scoped here (not in base.js) because transitions are an extra.js concern.
41
+ // Users can override: :root { --clampography-transition-duration: 0ms; } to disable.
42
+ [`:where(${root})`]: {
43
+ "--clampography-transition-duration": "200ms",
44
+ },
45
+
34
46
  // --- Basic Coloring & Font (with user-font priority) ---
35
- [root === ":root" ? "body" : root]: {
47
+ [(() => {
48
+ const typographyPrefix = options.typography && options.typography !== "global" ? ` ${options.typography}` : "";
49
+ const bodyBase = root === ":root" ? "body" : root;
50
+ return typographyPrefix ? `${bodyBase}${typographyPrefix}` : bodyBase;
51
+ })()]: {
36
52
  "background-color": "var(--clampography-background)",
37
53
  "color": "var(--clampography-text)",
38
54
  // --font-sans is Tailwind v4's way to expose the user's font choice.
39
55
  // If the user sets a font in their project, it wins. If not, fallback to clampography's system stack.
40
56
  "font-family": "var(--font-sans, var(--font-family-base))",
57
+ // Smooth theme transitions: all color CSS variables animate when data-theme changes.
58
+ // Duration is driven by the token set in base.js (default 200ms).
59
+ // Automatically disabled by the prefers-reduced-motion media query in base.js.
60
+ "transition-property": "color, background-color, border-color, text-decoration-color, fill, stroke",
61
+ "transition-duration": "var(--clampography-transition-duration, 200ms)",
62
+ "transition-timing-function": "ease",
41
63
  },
42
64
 
43
65
  [scope(":where(h1, h2, h3, h4, h5, h6)")]: {
@@ -150,7 +172,7 @@ export default (options = {}) => {
150
172
 
151
173
  // Deleted Text
152
174
  [scope("del")]: {
153
- "text-decoration-color": "var(--clampography-secondary)",
175
+ "text-decoration-color": "var(--clampography-error)",
154
176
  "text-decoration-thickness": "2px",
155
177
  },
156
178
 
@@ -170,5 +192,66 @@ export default (options = {}) => {
170
192
  "border-bottom-width": "1px",
171
193
  "padding-bottom": "var(--spacing-sm)",
172
194
  },
195
+
196
+ // ACCESSIBILITY: Disable all transitions for users who prefer reduced motion.
197
+ // Respects the OS-level "Reduce Motion" setting on macOS, Windows, iOS, and Android.
198
+ // Placed here (not in base.js) because it targets the transitions added above.
199
+ "@media (prefers-reduced-motion: reduce)": {
200
+ [(() => {
201
+ const typographyPrefix = options.typography && options.typography !== "global" ? ` ${options.typography}` : "";
202
+ const bodyBase = root === ":root" ? "body" : root;
203
+ return typographyPrefix ? `${bodyBase}${typographyPrefix}` : bodyBase;
204
+ })()]: {
205
+ "transition": "none",
206
+ "--clampography-transition-duration": "0ms",
207
+ },
208
+ },
209
+
210
+ // ACCESSIBILITY: High-contrast mode for users who need maximum legibility.
211
+ // Triggered automatically by the OS "Increase Contrast" setting on macOS,
212
+ // Windows High Contrast Mode, or Android's Accessibility settings.
213
+ "@media (prefers-contrast: more)": {
214
+ [(() => {
215
+ const typographyPrefix = options.typography && options.typography !== "global" ? ` ${options.typography}` : "";
216
+ const bodyBase = root === ":root" ? "body" : root;
217
+ return typographyPrefix ? `${bodyBase}${typographyPrefix}` : bodyBase;
218
+ })()]: {
219
+ // Override theme colors with absolute black/white for maximum legibility
220
+ "background-color": "white",
221
+ "color": "black",
222
+ },
223
+ [scope(":where(h1, h2, h3, h4, h5, h6)")]: {
224
+ "color": "black",
225
+ },
226
+ [scope("a")]: {
227
+ "color": "black",
228
+ "text-decoration": "underline",
229
+ "text-decoration-thickness": "2px",
230
+ "font-weight": "700",
231
+ },
232
+ [scope(":where(code:not(pre code), kbd, samp)")]: {
233
+ "background-color": "#f0f0f0",
234
+ "color": "black",
235
+ "border": "2px solid black",
236
+ },
237
+ [scope("pre")]: {
238
+ "background-color": "#f0f0f0",
239
+ "color": "black",
240
+ "border": "2px solid black",
241
+ },
242
+ [scope("blockquote")]: {
243
+ "background-color": "#f0f0f0",
244
+ "border-left-color": "black",
245
+ "border-left-width": "6px",
246
+ "color": "black",
247
+ },
248
+ [scope("th, td")]: {
249
+ "border": "2px solid black",
250
+ },
251
+ [scope("hr")]: {
252
+ "background-color": "black",
253
+ "height": "2px",
254
+ },
255
+ },
173
256
  };
174
257
  };
package/src/index.js CHANGED
@@ -4,6 +4,7 @@ import baseStyles from "./base.js";
4
4
  import extraStyles from "./extra.js";
5
5
  import formsStyles from "./forms.js";
6
6
  import kbdStyles from "./kbd.js";
7
+ import printStyles from "./print.js";
7
8
 
8
9
  // Import version from package.json
9
10
  import { version } from "../package.json" with { type: "json" };
@@ -44,12 +45,21 @@ export default plugin.withOptions(
44
45
  const includeExtra = resolveBool(options.extra, false); // Default: false
45
46
  const includeForms = resolveBool(options.forms, false); // Default: false
46
47
  const includeKbd = resolveBool(options.kbd, false); // Default: false
48
+ const includePrint = resolveBool(options.print, false); // Default: false
49
+
50
+ // Extract fluid bounds for clampography math engine
51
+ const fluidMin = parseInt(options["fluid-min"] || options.fluidMin || "320");
52
+ const fluidMax = parseInt(options["fluid-max"] || options.fluidMax || "1280");
53
+
54
+ // Extract typography scope option (default: global)
55
+ const typography = options.typography || "global";
47
56
 
48
57
  // Pass options to the style functions to enable scoping
49
- includeBase && addBase(baseStyles(options));
50
- includeExtra && addBase(extraStyles(options));
51
- includeForms && addBase(formsStyles(options));
52
- includeKbd && addBase(kbdStyles(options));
58
+ includeBase && addBase(baseStyles({ ...options, fluidMin, fluidMax, typography }));
59
+ includeExtra && addBase(extraStyles({ ...options, typography }));
60
+ includeForms && addBase(formsStyles({ ...options, typography }));
61
+ includeKbd && addBase(kbdStyles({ ...options, typography }));
62
+ includePrint && addBase(printStyles({ ...options, typography }));
53
63
 
54
64
  // 2. Parse themes configuration
55
65
  let configThemes = options.themes;
package/src/print.js ADDED
@@ -0,0 +1,92 @@
1
+ export default (options = {}) => {
2
+ const root = options.root || ":root";
3
+
4
+ // Helper to scope selectors safely (ignoring commas inside parentheses)
5
+ const scope = (selector) => {
6
+ const parts = [];
7
+ let current = "";
8
+ let depth = 0;
9
+
10
+ for (let i = 0; i < selector.length; i++) {
11
+ const char = selector[i];
12
+ if (char === "(") depth++;
13
+ if (char === ")") depth--;
14
+
15
+ if (char === "," && depth === 0) {
16
+ parts.push(current.trim());
17
+ current = "";
18
+ } else {
19
+ current += char;
20
+ }
21
+ }
22
+ parts.push(current.trim());
23
+
24
+ const typographyPrefix = options.typography && options.typography !== "global" ? ` ${options.typography}` : "";
25
+
26
+ return parts
27
+ .filter(Boolean)
28
+ .map((part) => {
29
+ if (part === ":root" || part === "body") return root;
30
+ if (typographyPrefix) {
31
+ return `${root}${typographyPrefix} ${part}`;
32
+ }
33
+ return `${root} ${part}`;
34
+ })
35
+ .join(", ");
36
+ };
37
+
38
+ return {
39
+ // PRINT OPTIMIZATION: Force clean, ink-friendly output for printing and PDF export.
40
+ // Overrides all theme colors, removes backgrounds, and converts fluid vw units to
41
+ // static sizes so text renders correctly on physical paper (A4/Letter).
42
+ "@media print": {
43
+ [(() => {
44
+ const typographyPrefix = options.typography && options.typography !== "global" ? ` ${options.typography}` : "";
45
+ const bodyBase = root === ":root" ? "body" : root;
46
+ return typographyPrefix ? `${bodyBase}${typographyPrefix}` : bodyBase;
47
+ })()]: {
48
+ // Static sizes — vw-based clamp() is meaningless on paper
49
+ "font-size": "12pt",
50
+ "line-height": "1.6",
51
+ // Force black-on-white for max legibility and ink saving
52
+ "color": "black",
53
+ "background": "white",
54
+ // Disable all transitions
55
+ "transition": "none",
56
+ },
57
+ [scope(":where(h1, h2, h3, h4, h5, h6)")]: {
58
+ "color": "black",
59
+ "page-break-after": "avoid",
60
+ },
61
+ [scope("h1")]: { "font-size": "28pt" },
62
+ [scope("h2")]: { "font-size": "22pt" },
63
+ [scope("h3")]: { "font-size": "18pt" },
64
+ [scope("h4")]: { "font-size": "14pt" },
65
+ [scope("h5")]: { "font-size": "12pt" },
66
+ [scope("h6")]: { "font-size": "11pt" },
67
+ [scope("a")]: {
68
+ // Force readable link styling on paper
69
+ "color": "black",
70
+ "text-decoration": "underline",
71
+ },
72
+ [scope("pre, blockquote")]: {
73
+ // Avoid cutting code blocks and blockquotes across page breaks
74
+ "page-break-inside": "avoid",
75
+ "border": "1px solid #ccc",
76
+ "background": "#f5f5f5",
77
+ },
78
+ [scope("table")]: {
79
+ "page-break-inside": "avoid",
80
+ "border": "1px solid #ccc",
81
+ },
82
+ [scope("th, td")]: {
83
+ "border": "1px solid #ccc",
84
+ },
85
+ [scope("img, figure")]: {
86
+ // Prevent images from overflowing the page
87
+ "max-width": "100%",
88
+ "page-break-inside": "avoid",
89
+ },
90
+ },
91
+ };
92
+ };