clampography 2.0.0-beta.9 → 2.0.0

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/src/kbd.js ADDED
@@ -0,0 +1,88 @@
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
+ if (char === "," && depth === 0) {
15
+ parts.push(current.trim());
16
+ current = "";
17
+ } else {
18
+ current += char;
19
+ }
20
+ }
21
+ parts.push(current.trim());
22
+
23
+ return parts
24
+ .filter(Boolean)
25
+ .map((part) => {
26
+ if (part === ":root" || part === "body") return root;
27
+ return `${root} ${part}`;
28
+ })
29
+ .join(", ");
30
+ };
31
+
32
+ return {
33
+ // ── <kbd> — 3D isometric keyboard key effect ──────────────────────────────
34
+ //
35
+ // The illusion is built from three layers:
36
+ // 1. Key face → background-color of the element itself
37
+ // 2. Key depth → first box-shadow layer (the visible "side" of the key)
38
+ // 3. Key cast → second box-shadow layer (subtle drop shadow)
39
+ //
40
+ // rgba() overlays are used for the depth/cast so the effect adapts
41
+ // automatically to any background (works on light AND dark themes).
42
+ // Theme variables are used for the face/border when available.
43
+
44
+ [scope("kbd")]: {
45
+ "display": "inline-block",
46
+ "padding": "0.1em 0.45em",
47
+ "min-width": "2em",
48
+ "text-align": "center",
49
+ "font-size": "0.8em",
50
+ "font-weight": "700",
51
+ "line-height": "1.5",
52
+ "white-space": "nowrap",
53
+ "vertical-align": "0.1em",
54
+ "cursor": "default",
55
+ "user-select": "none",
56
+
57
+ // Key face
58
+ "background-color": "var(--clampography-surface, oklch(94% 0.004 264))",
59
+ "color": "var(--clampography-text, oklch(18% 0.015 264))",
60
+
61
+ // Key outline
62
+ "border": "1px solid var(--clampography-border, oklch(76% 0.008 264))",
63
+ "border-radius": "4px",
64
+
65
+ // 3D depth layers:
66
+ // layer 1 — the key "body" (side face, seen in isometric view)
67
+ // layer 2 — the soft drop shadow beneath
68
+ "box-shadow": [
69
+ "0 2px 0 color-mix(in oklab, var(--clampography-border, oklch(60% 0.008 264)) 100%, black 18%)",
70
+ "0 3px 2px rgba(0, 0, 0, 0.15)",
71
+ "inset 0 1px 0 rgba(255, 255, 255, 0.5)",
72
+ ].join(", "),
73
+
74
+ "transition-property": "box-shadow, transform, border-bottom-width",
75
+ "transition-duration": "80ms",
76
+ },
77
+
78
+ // Pressed state — key travels down by 2px
79
+ [scope("kbd:active")]: {
80
+ "transform": "translateY(2px)",
81
+ "box-shadow": [
82
+ "0 0 0 color-mix(in oklab, var(--clampography-border, oklch(60% 0.008 264)) 100%, black 18%)",
83
+ "0 1px 1px rgba(0, 0, 0, 0.1)",
84
+ "inset 0 1px 0 rgba(255, 255, 255, 0.3)",
85
+ ].join(", "),
86
+ },
87
+ };
88
+ };
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
+ };
@@ -7,15 +7,33 @@ export default plugin.withOptions((options = {}) => {
7
7
  const themeName = options.name;
8
8
  const isDefault = options.default ?? false;
9
9
  const isPrefersDark = options.prefersdark ?? false;
10
- const rootSelector = options.root ?? ":root";
11
- // Defaults to light scheme if not specified
10
+ const rootSelector = options.root || ":root";
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
 
25
+ // LOGGING: Custom theme loaded
26
+ if (showLogs) {
27
+ const flags = [];
28
+ if (isDefault) flags.push("Default");
29
+ if (isPrefersDark) flags.push("Dark Pref");
30
+ const flagStr = flags.length > 0 ? ` (${flags.join(", ")})` : "";
31
+
32
+ console.log(
33
+ `✨ Clampography: Loaded custom theme "${themeName}"${flagStr}`,
34
+ );
35
+ }
36
+
19
37
  // 2. Prepare Base Colors (Fallback)
20
38
  // We fetch the full palette of the requested color scheme (light/dark)
21
39
  // to fill in any missing gaps in the user's definition.
@@ -27,14 +45,18 @@ export default plugin.withOptions((options = {}) => {
27
45
  // Mapping of simplified keys to full CSS variable names
28
46
  const keyMap = {
29
47
  "background": "--clampography-background",
30
- "surface": "--clampography-surface",
31
48
  "border": "--clampography-border",
49
+ "error": "--clampography-error",
32
50
  "heading": "--clampography-heading",
33
- "text": "--clampography-text",
34
- "muted": "--clampography-muted",
51
+ "info": "--clampography-info",
35
52
  "link": "--clampography-link",
53
+ "muted": "--clampography-muted",
36
54
  "primary": "--clampography-primary",
37
55
  "secondary": "--clampography-secondary",
56
+ "success": "--clampography-success",
57
+ "surface": "--clampography-surface",
58
+ "text": "--clampography-text",
59
+ "warning": "--clampography-warning",
38
60
  };
39
61
 
40
62
  // First, populate with fallback colors
@@ -46,13 +68,42 @@ export default plugin.withOptions((options = {}) => {
46
68
  Object.keys(options).forEach((key) => {
47
69
  // Ignore metadata keys
48
70
  if (
49
- ["name", "default", "prefersdark", "root", "color-scheme"].includes(key)
71
+ ["name", "default", "prefersdark", "root", "color-scheme", "logs"]
72
+ .includes(key)
50
73
  ) return;
51
74
 
75
+ const value = options[key];
76
+
52
77
  if (keyMap[key]) {
53
- themeColors[keyMap[key]] = options[key];
78
+ // Validate color format (only if logs enabled)
79
+ if (showLogs && value && typeof value === "string") {
80
+ // Check if value starts with oklch() or is a valid CSS color
81
+ const isOklch = value.trim().startsWith("oklch(");
82
+ const isHex = /^#[0-9A-Fa-f]{3,8}$/.test(value.trim());
83
+ const isRgb = value.trim().startsWith("rgb(") ||
84
+ value.trim().startsWith("rgba(");
85
+
86
+ if (!isOklch && !isHex && !isRgb) {
87
+ console.warn(
88
+ `Clampography (${themeName}): Color "${key}" has value "${value}" which may not be a valid color format. ` +
89
+ `For best compatibility with opacity modifiers (e.g., bg-${key}/20), use full OKLCH format: ` +
90
+ `oklch(70% 0.2 180) or oklch(0.7 0.2 180)`,
91
+ );
92
+ }
93
+
94
+ if (isHex || isRgb) {
95
+ console.info(
96
+ `🍀 Clampography (${themeName}): Color "${key}" uses ${
97
+ isHex ? "HEX" : "RGB"
98
+ } format. ` +
99
+ `Consider using OKLCH format for better color space support and smoother gradients.`,
100
+ );
101
+ }
102
+ }
103
+
104
+ themeColors[keyMap[key]] = value;
54
105
  } else if (key.startsWith("--")) {
55
- themeColors[key] = options[key];
106
+ themeColors[key] = value;
56
107
  }
57
108
  });
58
109
 
@@ -62,15 +113,18 @@ export default plugin.withOptions((options = {}) => {
62
113
  // 4. Generate Styles
63
114
  const styles = {};
64
115
 
65
- // A. Define the theme as a named data-theme
66
- styles[`[data-theme="${themeName}"]`] = themeColors;
116
+ // Build selector based on flags
117
+ let selector = `[data-theme="${themeName}"]`;
67
118
 
68
- // B. If default, apply to root
119
+ // If default, prepend :where(root) with lower specificity
69
120
  if (isDefault) {
70
- styles[rootSelector] = themeColors;
121
+ selector = `:where(${rootSelector}),${selector}`;
71
122
  }
72
123
 
73
- // C. If prefers-dark, apply to media query
124
+ // Apply theme to the constructed selector
125
+ styles[selector] = themeColors;
126
+
127
+ // If prefers-dark, apply only to root selector in media query
74
128
  if (isPrefersDark) {
75
129
  styles["@media (prefers-color-scheme: dark)"] = {
76
130
  [rootSelector]: themeColors,
package/src/theme.js ADDED
@@ -0,0 +1,34 @@
1
+ import { themes } from "./themes.js";
2
+
3
+ /**
4
+ * CDN Theme Generator
5
+ * Extracts all themes from themes.js and structures them
6
+ * for the Vanilla CSS build process.
7
+ */
8
+ export default (options = {}) => {
9
+ const root = options.root || ":root";
10
+ const themeStyles = {};
11
+
12
+ // 1. Default Theme (Light)
13
+ if (themes["light"]) {
14
+ // :where() lowers specificity so users can override it easily
15
+ themeStyles[`:where(${root})`] = themes["light"];
16
+ }
17
+
18
+ // 2. Dark Mode (prefers-color-scheme)
19
+ if (themes["dark"]) {
20
+ themeStyles["@media (prefers-color-scheme: dark)"] = {
21
+ // Default to dark if system prefers dark
22
+ [root]: themes["dark"]
23
+ };
24
+ }
25
+
26
+ // 3. Explicit Data Themes (Handles all themes, including light and dark)
27
+ // Because these are generated after the media query, they have equal
28
+ // specificity to :root but appear later in the CSS, thus overriding system preference.
29
+ for (const [name, themeData] of Object.entries(themes)) {
30
+ themeStyles[`[data-theme="${name}"]`] = themeData;
31
+ }
32
+
33
+ return themeStyles;
34
+ };
package/src/themes.js CHANGED
@@ -1,52 +1,47 @@
1
+ /**
2
+ * Built-in Themes
3
+ *
4
+ * Auto-generated by Clampography Dev Server.
5
+ * Do not manually edit this file unless you know what you're doing.
6
+ */
7
+
8
+ export const dark = {
9
+ "color-scheme": "dark",
10
+ "--clampography-background": "oklch(13.3% 0.016 284.0)",
11
+ "--clampography-border": "oklch(43.4% 0.006 258.3)",
12
+ "--clampography-error": "oklch(62.9% 0.220 27.0)",
13
+ "--clampography-heading": "oklch(94.6% 0.000 89.9)",
14
+ "--clampography-info": "oklch(71.2% 0.155 251.4)",
15
+ "--clampography-link": "oklch(75.3% 0.133 248.6)",
16
+ "--clampography-muted": "oklch(68.9% 0.006 264.5)",
17
+ "--clampography-primary": "oklch(75.3% 0.133 248.6)",
18
+ "--clampography-secondary": "oklch(62.6% 0.243 301.2)",
19
+ "--clampography-success": "oklch(66.3% 0.148 160.6)",
20
+ "--clampography-surface": "oklch(21.2% 0.009 255.6)",
21
+ "--clampography-text": "oklch(88.8% 0.007 268.5)",
22
+ "--clampography-warning": "oklch(72.1% 0.164 63.1)",
23
+ };
24
+
25
+ export const light = {
26
+ "color-scheme": "light",
27
+ "--clampography-background": "oklch(100.0% 0.000 89.9)",
28
+ "--clampography-border": "oklch(94.6% 0.000 89.9)",
29
+ "--clampography-error": "oklch(65.4% 0.229 27.0)",
30
+ "--clampography-heading": "oklch(15.0% 0.021 264.3)",
31
+ "--clampography-info": "oklch(68.3% 0.166 259.7)",
32
+ "--clampography-link": "oklch(60.3% 0.216 259.8)",
33
+ "--clampography-muted": "oklch(51.9% 0.014 259.8)",
34
+ "--clampography-primary": "oklch(60.3% 0.216 259.8)",
35
+ "--clampography-secondary": "oklch(54.9% 0.280 300.0)",
36
+ "--clampography-success": "oklch(75.8% 0.166 161.7)",
37
+ "--clampography-surface": "oklch(98.5% 0.000 89.9)",
38
+ "--clampography-text": "oklch(31.1% 0.020 257.3)",
39
+ "--clampography-warning": "oklch(79.9% 0.136 72.2)",
40
+ };
41
+
1
42
  export const themes = {
2
- light: {
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",
13
- },
14
- dark: {
15
- "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",
25
- },
26
- retro: {
27
- "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",
37
- },
38
- cyberpunk: {
39
- "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",
49
- },
43
+ "dark": dark,
44
+ "light": light
50
45
  };
51
46
 
52
47
  export const themesList = Object.keys(themes);
@@ -0,0 +1,24 @@
1
+ import plugin from "tailwindcss/plugin";
2
+
3
+ interface ClampographyOptions {
4
+ themes?: string | string[] | boolean;
5
+ base?: boolean;
6
+ extra?: boolean;
7
+ forms?: boolean;
8
+ kbd?: boolean;
9
+ print?: boolean;
10
+ "fluid-min"?: string | number;
11
+ fluidMin?: string | number;
12
+ "fluid-max"?: string | number;
13
+ fluidMax?: string | number;
14
+ typography?: string;
15
+ root?: string;
16
+ prefix?: string | boolean;
17
+ logs?: boolean;
18
+ }
19
+
20
+ declare const clampographyPlugin: ReturnType<typeof plugin.withOptions<ClampographyOptions>>;
21
+
22
+ export default clampographyPlugin;
23
+
24
+ export type { ClampographyVars } from "./vars.js";
@@ -0,0 +1,32 @@
1
+ import plugin from "tailwindcss/plugin";
2
+
3
+ interface ThemePluginOptions {
4
+ name: string;
5
+ default?: boolean;
6
+ prefersdark?: boolean;
7
+ "color-scheme"?: "light" | "dark";
8
+ root?: string;
9
+ logs?: boolean;
10
+
11
+ // Color options
12
+ background?: string;
13
+ border?: string;
14
+ error?: string;
15
+ heading?: string;
16
+ info?: string;
17
+ link?: string;
18
+ muted?: string;
19
+ primary?: string;
20
+ secondary?: string;
21
+ success?: string;
22
+ surface?: string;
23
+ text?: string;
24
+ warning?: string;
25
+
26
+ // Allow custom CSS variables
27
+ [key: `--${string}`]: string | undefined;
28
+ }
29
+
30
+ declare const themePlugin: ReturnType<typeof plugin.withOptions<ThemePluginOptions>>;
31
+
32
+ export default themePlugin;
@@ -0,0 +1,28 @@
1
+ declare module "clampography/themes" {
2
+ export interface ThemeColors {
3
+ "color-scheme": "light" | "dark";
4
+ "--clampography-background": string;
5
+ "--clampography-border": string;
6
+ "--clampography-error": string;
7
+ "--clampography-heading": string;
8
+ "--clampography-info": string;
9
+ "--clampography-link": string;
10
+ "--clampography-muted": string;
11
+ "--clampography-primary": string;
12
+ "--clampography-secondary": string;
13
+ "--clampography-success": string;
14
+ "--clampography-surface": string;
15
+ "--clampography-text": string;
16
+ "--clampography-warning": string;
17
+ [key: string]: string;
18
+ }
19
+
20
+ export interface Themes {
21
+ light: ThemeColors;
22
+ dark: ThemeColors;
23
+ [themeName: string]: ThemeColors;
24
+ }
25
+
26
+ export const themes: Themes;
27
+ export const themesList: string[];
28
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Auto-generated by Clampography
3
+ * TypeScript definitions for Clampography CSS Variables
4
+ */
5
+
6
+ export type ClampographyVars =
7
+ | "--clampography-background"
8
+ | "--clampography-border"
9
+ | "--clampography-error"
10
+ | "--clampography-fluid-max"
11
+ | "--clampography-fluid-min"
12
+ | "--clampography-font-base"
13
+ | "--clampography-font-mono"
14
+ | "--clampography-h1-base"
15
+ | "--clampography-h1-max"
16
+ | "--clampography-h1-min"
17
+ | "--clampography-h1-scale"
18
+ | "--clampography-h1-size"
19
+ | "--clampography-h1-slope"
20
+ | "--clampography-h2-base"
21
+ | "--clampography-h2-max"
22
+ | "--clampography-h2-min"
23
+ | "--clampography-h2-scale"
24
+ | "--clampography-h2-size"
25
+ | "--clampography-h2-slope"
26
+ | "--clampography-h3-base"
27
+ | "--clampography-h3-max"
28
+ | "--clampography-h3-min"
29
+ | "--clampography-h3-scale"
30
+ | "--clampography-h3-size"
31
+ | "--clampography-h3-slope"
32
+ | "--clampography-h4-base"
33
+ | "--clampography-h4-max"
34
+ | "--clampography-h4-min"
35
+ | "--clampography-h4-scale"
36
+ | "--clampography-h4-size"
37
+ | "--clampography-h4-slope"
38
+ | "--clampography-h5-base"
39
+ | "--clampography-h5-max"
40
+ | "--clampography-h5-min"
41
+ | "--clampography-h5-scale"
42
+ | "--clampography-h5-size"
43
+ | "--clampography-h5-slope"
44
+ | "--clampography-h6-base"
45
+ | "--clampography-h6-max"
46
+ | "--clampography-h6-min"
47
+ | "--clampography-h6-scale"
48
+ | "--clampography-h6-size"
49
+ | "--clampography-h6-slope"
50
+ | "--clampography-heading"
51
+ | "--clampography-heading-scale"
52
+ | "--clampography-info"
53
+ | "--clampography-link"
54
+ | "--clampography-list-indent"
55
+ | "--clampography-muted"
56
+ | "--clampography-primary"
57
+ | "--clampography-scroll-offset"
58
+ | "--clampography-secondary"
59
+ | "--clampography-spacing-lg"
60
+ | "--clampography-spacing-md"
61
+ | "--clampography-spacing-sm"
62
+ | "--clampography-spacing-xl"
63
+ | "--clampography-spacing-xs"
64
+ | "--clampography-success"
65
+ | "--clampography-surface"
66
+ | "--clampography-text"
67
+ | "--clampography-transition-duration"
68
+ | "--clampography-warning";