clampography 2.0.0-beta.26 → 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.
- package/css/base.css +36 -17
- package/css/base.min.css +1 -1
- package/css/clampography.css +101 -19
- package/css/clampography.min.css +1 -1
- package/css/extra.css +65 -2
- package/css/extra.min.css +1 -1
- package/css/figma-tokens.json +112 -0
- package/package.json +7 -8
- package/src/base.js +81 -20
- package/src/export-figma.js +43 -0
- package/src/extra.js +89 -3
- package/src/index.js +14 -4
- package/src/print.js +92 -0
- package/src/themes.js +44 -1409
package/package.json
CHANGED
|
@@ -24,9 +24,8 @@
|
|
|
24
24
|
"src",
|
|
25
25
|
"css"
|
|
26
26
|
],
|
|
27
|
-
"unpkg": "css/clampography.min.css",
|
|
28
|
-
"jsdelivr": "css/clampography.min.css",
|
|
29
27
|
"homepage": "https://next.dav.one/clampography/",
|
|
28
|
+
"jsdelivr": "css/clampography.min.css",
|
|
30
29
|
"keywords": [
|
|
31
30
|
"alternative",
|
|
32
31
|
"blog",
|
|
@@ -61,12 +60,12 @@
|
|
|
61
60
|
"url": "git+https://github.com/Avaray/clampography.git"
|
|
62
61
|
},
|
|
63
62
|
"scripts": {
|
|
64
|
-
"build": "bun src/convert.js",
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"dev:server": "bun run dev/server.js",
|
|
68
|
-
"dev": "bun run dev/server.js"
|
|
63
|
+
"build": "bun src/convert.js && bun src/export-figma.js",
|
|
64
|
+
"dev": "bun run dev/server.js",
|
|
65
|
+
"test": "bun test"
|
|
69
66
|
},
|
|
67
|
+
"sideEffects": false,
|
|
70
68
|
"type": "module",
|
|
71
|
-
"
|
|
69
|
+
"unpkg": "css/clampography.min.css",
|
|
70
|
+
"version": "2.0.0-beta.28"
|
|
72
71
|
}
|
package/src/base.js
CHANGED
|
@@ -21,34 +21,91 @@ 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) // Remove empty strings
|
|
26
28
|
.map((part) => {
|
|
27
29
|
if (part === ":root" || part === "body") return root;
|
|
30
|
+
|
|
31
|
+
// Apply typography scope isolation if configured
|
|
32
|
+
if (typographyPrefix) {
|
|
33
|
+
return `${root}${typographyPrefix} ${part}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
28
36
|
// Avoid double spacing
|
|
29
37
|
return `${root} ${part}`;
|
|
30
38
|
})
|
|
31
39
|
.join(", ");
|
|
32
40
|
};
|
|
33
41
|
|
|
42
|
+
// Fluid math engine: Generates mathematically perfect clamp() strings
|
|
43
|
+
// dynamically based on the configured min and max screen sizes.
|
|
44
|
+
const minScreenRem = (options.fluidMin || 320) / 16;
|
|
45
|
+
const maxScreenRem = (options.fluidMax || 1280) / 16;
|
|
46
|
+
|
|
47
|
+
const makeFluid = (minRem, maxRem) => {
|
|
48
|
+
// If min and max are the same, or if we have invalid screens, just return the static value
|
|
49
|
+
if (minRem === maxRem || minScreenRem >= maxScreenRem) return `${minRem}rem`;
|
|
50
|
+
|
|
51
|
+
const slope = (maxRem - minRem) / (maxScreenRem - minScreenRem);
|
|
52
|
+
const intersection = minRem - slope * minScreenRem;
|
|
53
|
+
|
|
54
|
+
const format = (num) => parseFloat(num.toFixed(4));
|
|
55
|
+
|
|
56
|
+
return `clamp(${minRem}rem, ${format(intersection)}rem + ${format(slope * 100)}vw, ${maxRem}rem)`;
|
|
57
|
+
};
|
|
58
|
+
|
|
34
59
|
return {
|
|
35
|
-
// ROOT CONFIGURATION
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"--spacing-
|
|
40
|
-
"--spacing-
|
|
41
|
-
"--spacing-
|
|
42
|
-
"--
|
|
60
|
+
// ROOT CONFIGURATION (CSS variables)
|
|
61
|
+
// Uses :where() for zero specificity so user overrides always win regardless of layer/source order
|
|
62
|
+
[`:where(${root})`]: {
|
|
63
|
+
// FLUID SPACING SYSTEM
|
|
64
|
+
"--spacing-xs": makeFluid(0.25, 0.75),
|
|
65
|
+
"--spacing-sm": makeFluid(0.375, 1.25),
|
|
66
|
+
"--spacing-md": makeFluid(0.5, 1.5),
|
|
67
|
+
"--spacing-lg": makeFluid(0.75, 2.5),
|
|
68
|
+
"--spacing-xl": makeFluid(1, 3),
|
|
69
|
+
"--list-indent": makeFluid(1.5, 2),
|
|
43
70
|
"--scroll-offset": "5rem",
|
|
44
71
|
"--font-family-base":
|
|
45
|
-
"system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif",
|
|
72
|
+
"Inter, system-ui, -apple-system, 'Segoe UI Variable Display', 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif",
|
|
46
73
|
"--font-family-mono":
|
|
47
|
-
"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
74
|
+
"ui-monospace, 'Cascadia Code', 'Cascadia Mono', 'Segoe UI Mono', 'Ubuntu Mono', SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
|
|
75
|
+
|
|
76
|
+
// HEADINGS FLUID TYPOGRAPHY
|
|
77
|
+
// Matches Tailwind CSS sizes: sm (min) to 2xl (max)
|
|
78
|
+
// Override any of these in :root to customize individual headings.
|
|
79
|
+
"--clampography-h1-size": makeFluid(1.875, 4),
|
|
80
|
+
"--clampography-h2-size": makeFluid(1.25, 3),
|
|
81
|
+
"--clampography-h3-size": makeFluid(1.125, 2.25),
|
|
82
|
+
"--clampography-h4-size": makeFluid(1, 1.5),
|
|
83
|
+
"--clampography-h5-size": "1rem",
|
|
84
|
+
"--clampography-h6-size": "0.875rem",
|
|
85
|
+
|
|
86
|
+
// Global heading scale multiplier (default: 1 = no scaling).
|
|
87
|
+
// Override in :root to proportionally scale all headings at once.
|
|
88
|
+
// Example: :root { --clampography-heading-scale: 0.85; }
|
|
89
|
+
"--clampography-heading-scale": "1",
|
|
90
|
+
|
|
91
|
+
// Individual heading scales (default to global scale)
|
|
92
|
+
"--clampography-h1-scale": "var(--clampography-heading-scale)",
|
|
93
|
+
"--clampography-h2-scale": "var(--clampography-heading-scale)",
|
|
94
|
+
"--clampography-h3-scale": "var(--clampography-heading-scale)",
|
|
95
|
+
"--clampography-h4-scale": "var(--clampography-heading-scale)",
|
|
96
|
+
"--clampography-h5-scale": "var(--clampography-heading-scale)",
|
|
97
|
+
"--clampography-h6-scale": "var(--clampography-heading-scale)",
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
// BODY STYLES (Typography baseline)
|
|
101
|
+
// Note: font-family is intentionally NOT set here.
|
|
102
|
+
// It is applied in extra.js with user-font priority via --font-sans.
|
|
103
|
+
[(() => {
|
|
104
|
+
const typographyPrefix = options.typography && options.typography !== "global" ? ` ${options.typography}` : "";
|
|
105
|
+
const bodyBase = root === ":root" ? "body" : root;
|
|
106
|
+
return typographyPrefix ? `${bodyBase}${typographyPrefix}` : bodyBase;
|
|
107
|
+
})()]: {
|
|
108
|
+
"font-size": makeFluid(0.875, 1.125),
|
|
52
109
|
"line-height": "1.75",
|
|
53
110
|
"text-rendering": "optimizeLegibility",
|
|
54
111
|
"-webkit-font-smoothing": "antialiased",
|
|
@@ -63,7 +120,7 @@ export default (options = {}) => {
|
|
|
63
120
|
},
|
|
64
121
|
|
|
65
122
|
[scope("h1")]: {
|
|
66
|
-
"font-size": "
|
|
123
|
+
"font-size": "calc(var(--clampography-h1-size) * var(--clampography-h1-scale))",
|
|
67
124
|
"line-height": "1.1111",
|
|
68
125
|
"font-weight": "800",
|
|
69
126
|
"margin-top": "0",
|
|
@@ -71,7 +128,7 @@ export default (options = {}) => {
|
|
|
71
128
|
},
|
|
72
129
|
|
|
73
130
|
[scope("h2")]: {
|
|
74
|
-
"font-size": "
|
|
131
|
+
"font-size": "calc(var(--clampography-h2-size) * var(--clampography-h2-scale))",
|
|
75
132
|
"line-height": "1.3333",
|
|
76
133
|
"font-weight": "700",
|
|
77
134
|
"margin-top": "var(--spacing-xl)",
|
|
@@ -79,28 +136,28 @@ export default (options = {}) => {
|
|
|
79
136
|
},
|
|
80
137
|
|
|
81
138
|
[scope("h3")]: {
|
|
82
|
-
"font-size": "
|
|
139
|
+
"font-size": "calc(var(--clampography-h3-size) * var(--clampography-h3-scale))",
|
|
83
140
|
"line-height": "1.5",
|
|
84
141
|
"margin-top": "var(--spacing-lg)",
|
|
85
142
|
"margin-bottom": "var(--spacing-sm)",
|
|
86
143
|
},
|
|
87
144
|
|
|
88
145
|
[scope("h4")]: {
|
|
89
|
-
"font-size": "
|
|
146
|
+
"font-size": "calc(var(--clampography-h4-size) * var(--clampography-h4-scale))",
|
|
90
147
|
"line-height": "1.5",
|
|
91
148
|
"margin-top": "var(--spacing-lg)",
|
|
92
149
|
"margin-bottom": "var(--spacing-sm)",
|
|
93
150
|
},
|
|
94
151
|
|
|
95
152
|
[scope("h5")]: {
|
|
96
|
-
"font-size": "
|
|
153
|
+
"font-size": "calc(var(--clampography-h5-size) * var(--clampography-h5-scale))",
|
|
97
154
|
"line-height": "1.5",
|
|
98
155
|
"margin-top": "var(--spacing-md)",
|
|
99
156
|
"margin-bottom": "var(--spacing-xs)",
|
|
100
157
|
},
|
|
101
158
|
|
|
102
159
|
[scope("h6")]: {
|
|
103
|
-
"font-size": "
|
|
160
|
+
"font-size": "calc(var(--clampography-h6-size) * var(--clampography-h6-scale))",
|
|
104
161
|
"line-height": "1.5",
|
|
105
162
|
"margin-top": "var(--spacing-md)",
|
|
106
163
|
"margin-bottom": "var(--spacing-xs)",
|
|
@@ -175,6 +232,8 @@ export default (options = {}) => {
|
|
|
175
232
|
[scope(":where(code, kbd, samp)")]: {
|
|
176
233
|
"font-family": "var(--font-family-mono)",
|
|
177
234
|
"font-size": "0.875em",
|
|
235
|
+
"-webkit-font-smoothing": "auto",
|
|
236
|
+
"-moz-osx-font-smoothing": "auto",
|
|
178
237
|
},
|
|
179
238
|
|
|
180
239
|
[scope("kbd")]: {
|
|
@@ -358,6 +417,8 @@ export default (options = {}) => {
|
|
|
358
417
|
"font-family": "var(--font-family-mono)",
|
|
359
418
|
"line-height": "1.6",
|
|
360
419
|
"overflow-x": "auto",
|
|
420
|
+
"-webkit-font-smoothing": "auto",
|
|
421
|
+
"-moz-osx-font-smoothing": "auto",
|
|
361
422
|
},
|
|
362
423
|
|
|
363
424
|
[scope("pre code")]: {
|
|
@@ -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,20 +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 {
|
|
34
|
-
//
|
|
35
|
-
|
|
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
|
+
|
|
46
|
+
// --- Basic Coloring & Font (with user-font priority) ---
|
|
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)",
|
|
54
|
+
// --font-sans is Tailwind v4's way to expose the user's font choice.
|
|
55
|
+
// If the user sets a font in their project, it wins. If not, fallback to clampography's system stack.
|
|
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",
|
|
38
63
|
},
|
|
39
64
|
|
|
40
65
|
[scope(":where(h1, h2, h3, h4, h5, h6)")]: {
|
|
@@ -147,7 +172,7 @@ export default (options = {}) => {
|
|
|
147
172
|
|
|
148
173
|
// Deleted Text
|
|
149
174
|
[scope("del")]: {
|
|
150
|
-
"text-decoration-color": "var(--clampography-
|
|
175
|
+
"text-decoration-color": "var(--clampography-error)",
|
|
151
176
|
"text-decoration-thickness": "2px",
|
|
152
177
|
},
|
|
153
178
|
|
|
@@ -167,5 +192,66 @@ export default (options = {}) => {
|
|
|
167
192
|
"border-bottom-width": "1px",
|
|
168
193
|
"padding-bottom": "var(--spacing-sm)",
|
|
169
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
|
+
},
|
|
170
256
|
};
|
|
171
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
|
+
};
|