clampography 0.9.11 → 2.0.0-beta.10

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/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # 🙌 Clampography
2
2
 
3
+ > **WARNING**: Beta 2.0.0 is in development and currently unstable.
4
+
3
5
  **Clampography** is a pure CSS typography system that uses the
4
6
  [clamp()](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/clamp)
5
7
  function for fluid, responsive text scaling. It's designed as an alternative to
package/package.json CHANGED
@@ -1,18 +1,16 @@
1
1
  {
2
2
  "name": "clampography",
3
- "version": "0.9.11",
4
- "description": "Fluid typography system based on CSS clamp(). Alternative to Tailwind CSS Typography plugin.",
5
- "main": "clampography.css",
6
- "style": "clampography.css",
7
- "files": [
8
- "clampography.css",
9
- "presets/"
10
- ],
3
+ "version": "2.0.0-beta.10",
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
+ "main": "src/index.js",
6
+ "type": "module",
11
7
  "exports": {
12
- ".": "./clampography.css",
13
- "./base": "./clampography.css",
14
- "./*": "./presets/*.css"
8
+ ".": "./src/index.js",
9
+ "./theme": "./src/theme-plugin.js"
15
10
  },
11
+ "files": [
12
+ "src"
13
+ ],
16
14
  "keywords": [
17
15
  "alternative",
18
16
  "blog",
@@ -26,11 +24,14 @@
26
24
  "font-size",
27
25
  "markdown",
28
26
  "style",
27
+ "styles",
29
28
  "tailwind",
30
29
  "tailwind-css",
31
30
  "tailwind-plugin",
32
31
  "tailwindcss",
33
32
  "text",
33
+ "theme",
34
+ "themes",
34
35
  "typography"
35
36
  ],
36
37
  "repository": {
@@ -39,5 +40,11 @@
39
40
  },
40
41
  "homepage": "https://next.dav.one/clampography/",
41
42
  "author": "Dawid Wąsowski",
42
- "license": "MIT"
43
+ "license": "MIT",
44
+ "peerDependencies": {
45
+ "tailwindcss": ">=4.0.0"
46
+ },
47
+ "devDependencies": {
48
+ "tailwindcss": "^4.0.0"
49
+ }
43
50
  }
package/src/base.js ADDED
@@ -0,0 +1,474 @@
1
+ export default {
2
+ // ROOT CONFIGURATION
3
+ "@layer base": {
4
+ ":root": {
5
+ "--spacing-xs": "clamp(0.5rem, 0.375rem + 0.625vw, 0.75rem)",
6
+ "--spacing-sm": "clamp(0.75rem, 0.5625rem + 0.9375vw, 1.25rem)",
7
+ "--spacing-md": "clamp(1rem, 0.75rem + 1.25vw, 1.5rem)",
8
+ "--spacing-lg": "clamp(1.5rem, 1.125rem + 1.875vw, 2.5rem)",
9
+ "--spacing-xl": "clamp(2rem, 1.5rem + 2.5vw, 3rem)",
10
+ "--scroll-offset": "5rem",
11
+ "--font-family-base":
12
+ "system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif",
13
+ "--font-family-mono":
14
+ "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
15
+ },
16
+ },
17
+
18
+ // BODY - Global Typography Settings
19
+ body: {
20
+ "font-family": "var(--font-family-base)",
21
+ "font-size": "clamp(1rem, 0.95rem + 0.25vw, 1.125rem)",
22
+ "line-height": "1.75",
23
+ "text-rendering": "optimizeLegibility",
24
+ "-webkit-font-smoothing": "antialiased",
25
+ "-moz-osx-font-smoothing": "grayscale",
26
+ "text-wrap": "pretty",
27
+ },
28
+
29
+ // HEADINGS (H1-H6) - Structural Hierarchy
30
+ ":where(h1, h2, h3, h4, h5, h6)": {
31
+ "font-weight": "600",
32
+ "scroll-margin-top": "var(--scroll-offset)",
33
+ },
34
+
35
+ h1: {
36
+ "font-size": "clamp(2.25rem, 1.95rem + 1.5vw, 3rem)",
37
+ "line-height": "1.1111",
38
+ "font-weight": "800",
39
+ "margin-top": "0",
40
+ "margin-bottom": "var(--spacing-xl)",
41
+ },
42
+
43
+ h2: {
44
+ "font-size": "clamp(1.5rem, 1.35rem + 0.75vw, 1.875rem)",
45
+ "line-height": "1.3333",
46
+ "font-weight": "700",
47
+ "margin-top": "var(--spacing-xl)",
48
+ "margin-bottom": "var(--spacing-md)",
49
+ },
50
+
51
+ h3: {
52
+ "font-size": "clamp(1.25rem, 1.15rem + 0.5vw, 1.5rem)",
53
+ "line-height": "1.5",
54
+ "margin-top": "var(--spacing-lg)",
55
+ "margin-bottom": "var(--spacing-sm)",
56
+ },
57
+
58
+ h4: {
59
+ "font-size": "clamp(1rem, 0.975rem + 0.125vw, 1.125rem)",
60
+ "line-height": "1.5",
61
+ "margin-top": "var(--spacing-lg)",
62
+ "margin-bottom": "var(--spacing-sm)",
63
+ },
64
+
65
+ h5: {
66
+ "font-size": "1rem",
67
+ "line-height": "1.5",
68
+ "margin-top": "var(--spacing-md)",
69
+ "margin-bottom": "var(--spacing-xs)",
70
+ },
71
+
72
+ h6: {
73
+ "font-size": "0.875rem",
74
+ "line-height": "1.5",
75
+ "margin-top": "var(--spacing-md)",
76
+ "margin-bottom": "var(--spacing-xs)",
77
+ },
78
+
79
+ ":is(h1, h2, h3, h4, h5, h6):first-child": {
80
+ "margin-top": "0",
81
+ },
82
+
83
+ // LINKS - Clickable Text (Structural Indicators)
84
+ a: {
85
+ "text-decoration-line": "underline",
86
+ "text-decoration-thickness": "0.0625em",
87
+ "text-underline-offset": "0.15em",
88
+ cursor: "pointer",
89
+ },
90
+
91
+ ":where(h1, h2, h3, h4, h5, h6) a": {
92
+ "text-decoration": "none",
93
+ },
94
+
95
+ // MENU - Interactive Lists (Toolbars, Navigation)
96
+ menu: {
97
+ "list-style": "none",
98
+ "margin-bottom": "var(--spacing-md)",
99
+ "padding-left": "0",
100
+ },
101
+
102
+ "menu > li::before": {
103
+ display: "none",
104
+ },
105
+
106
+ // HGROUP - Heading Groups (Title + Subtitle)
107
+ hgroup: {
108
+ "margin-bottom": "var(--spacing-lg)",
109
+ },
110
+
111
+ "hgroup :where(h1, h2, h3, h4, h5, h6)": {
112
+ "margin-bottom": "var(--spacing-xs)",
113
+ },
114
+
115
+ "hgroup :where(p)": {
116
+ "margin-top": "0",
117
+ "margin-bottom": "0",
118
+ "font-size": "0.875em",
119
+ "font-weight": "400",
120
+ "line-height": "1.5",
121
+ },
122
+
123
+ // TEXT CONTENT - Paragraphs & Inline Elements
124
+ p: {
125
+ "line-height": "1.75",
126
+ "margin-bottom": "var(--spacing-md)",
127
+ },
128
+
129
+ ":where(strong, b)": {
130
+ "font-weight": "700",
131
+ },
132
+
133
+ ":where(em, i, cite, var)": {
134
+ "font-style": "italic",
135
+ },
136
+
137
+ dfn: {
138
+ "font-style": "italic",
139
+ "font-weight": "600",
140
+ },
141
+
142
+ small: {
143
+ "font-size": "0.875em",
144
+ "line-height": "1.5",
145
+ },
146
+
147
+ kbd: {
148
+ "font-family": "var(--font-family-mono)",
149
+ "font-size": "0.875em",
150
+ "font-weight": "600",
151
+ },
152
+
153
+ samp: {
154
+ "font-family": "var(--font-family-mono)",
155
+ "font-size": "0.875em",
156
+ },
157
+
158
+ data: {
159
+ "font-variant-numeric": "tabular-nums",
160
+ },
161
+
162
+ ":where(sub, sup)": {
163
+ "font-size": "0.75em",
164
+ "line-height": "0",
165
+ position: "relative",
166
+ "vertical-align": "baseline",
167
+ },
168
+
169
+ sup: {
170
+ top: "-0.5em",
171
+ },
172
+
173
+ sub: {
174
+ bottom: "-0.25em",
175
+ },
176
+
177
+ "abbr[title]": {
178
+ "text-decoration": "underline dotted",
179
+ cursor: "help",
180
+ },
181
+
182
+ del: {
183
+ "text-decoration": "line-through",
184
+ },
185
+
186
+ ins: {
187
+ "text-decoration": "underline",
188
+ },
189
+
190
+ s: {
191
+ "text-decoration": "line-through",
192
+ },
193
+
194
+ u: {
195
+ "text-decoration": "underline",
196
+ },
197
+
198
+ mark: {
199
+ "font-style": "normal",
200
+ "font-weight": "inherit",
201
+ },
202
+
203
+ address: {
204
+ "font-style": "italic",
205
+ "margin-top": "var(--spacing-md)",
206
+ "margin-bottom": "var(--spacing-md)",
207
+ },
208
+
209
+ time: {
210
+ "font-style": "normal",
211
+ "font-variant-numeric": "tabular-nums",
212
+ },
213
+
214
+ // BLOCKQUOTES - Structural Spacing Only
215
+ blockquote: {
216
+ "margin-top": "var(--spacing-lg)",
217
+ "margin-bottom": "var(--spacing-lg)",
218
+ "padding-left": "var(--spacing-md)",
219
+ },
220
+
221
+ "blockquote blockquote": {
222
+ "margin-top": "var(--spacing-sm)",
223
+ "margin-bottom": "var(--spacing-sm)",
224
+ "padding-left": "var(--spacing-sm)",
225
+ },
226
+
227
+ q: {
228
+ "font-style": "inherit",
229
+ },
230
+
231
+ // LISTS - Custom Structured Markers (Prose-like)
232
+ ":where(ul, ol)": {
233
+ "list-style": "none",
234
+ "margin-bottom": "var(--spacing-md)",
235
+ "padding-left": "clamp(1.5rem, 1.25rem + 1.25vw, 2rem)",
236
+ },
237
+
238
+ li: {
239
+ position: "relative",
240
+ "padding-left": "0.375em",
241
+ },
242
+
243
+ "li + li": {
244
+ "margin-top": "var(--spacing-xs)",
245
+ },
246
+
247
+ "li > ul, li > ol": {
248
+ "margin-top": "var(--spacing-xs)",
249
+ "margin-bottom": "var(--spacing-sm)",
250
+ "padding-left": "var(--spacing-md)",
251
+ },
252
+
253
+ "ul > li::before": {
254
+ content: "''",
255
+ position: "absolute",
256
+ left: "-1.125em",
257
+ top: "calc(0.875em - 0.1875em)",
258
+ width: "0.375em",
259
+ height: "0.375em",
260
+ "background-color": "currentColor",
261
+ "border-radius": "50%",
262
+ },
263
+
264
+ ol: {
265
+ "counter-reset": "list-counter",
266
+ },
267
+
268
+ "ol > li": {
269
+ "counter-increment": "list-counter",
270
+ },
271
+
272
+ "ol > li::before": {
273
+ content: "counter(list-counter) '.'",
274
+ position: "absolute",
275
+ left: "-2.5em",
276
+ width: "1.75em",
277
+ "text-align": "right",
278
+ "font-weight": "600",
279
+ color: "currentColor",
280
+ },
281
+
282
+ // DEFINITION LISTS - Glossaries & Key-Value Pairs
283
+ dl: {
284
+ "margin-top": "var(--spacing-md)",
285
+ "margin-bottom": "var(--spacing-md)",
286
+ },
287
+
288
+ dt: {
289
+ "font-weight": "600",
290
+ "margin-top": "var(--spacing-sm)",
291
+ },
292
+
293
+ "dt:first-child": {
294
+ "margin-top": "0",
295
+ },
296
+
297
+ dd: {
298
+ "margin-left": "var(--spacing-md)",
299
+ },
300
+
301
+ "dt + dd": {
302
+ "margin-top": "var(--spacing-xs)",
303
+ },
304
+
305
+ "dd + dd": {
306
+ "margin-top": "var(--spacing-xs)",
307
+ },
308
+
309
+ "dd:last-child": {
310
+ "margin-bottom": "0",
311
+ },
312
+
313
+ // CODE BLOCKS - Monospace Typography
314
+ pre: {
315
+ "margin-top": "var(--spacing-md)",
316
+ "margin-bottom": "var(--spacing-md)",
317
+ "font-family": "var(--font-family-mono)",
318
+ "line-height": "1.6",
319
+ "overflow-x": "auto",
320
+ },
321
+
322
+ "pre code": {
323
+ "font-size": "inherit",
324
+ padding: "0",
325
+ background: "none",
326
+ "border-radius": "0",
327
+ },
328
+
329
+ // FORMS - Basic Structure
330
+ fieldset: {
331
+ "margin-top": "var(--spacing-md)",
332
+ "margin-bottom": "var(--spacing-md)",
333
+ padding: "var(--spacing-md)",
334
+ border: "0",
335
+ },
336
+
337
+ legend: {
338
+ "font-weight": "600",
339
+ padding: "0 var(--spacing-xs)",
340
+ },
341
+
342
+ label: {
343
+ display: "inline-block",
344
+ "font-weight": "600",
345
+ "margin-bottom": "var(--spacing-xs)",
346
+ },
347
+
348
+ output: {
349
+ display: "inline-block",
350
+ "font-variant-numeric": "tabular-nums",
351
+ },
352
+
353
+ ":where(meter, progress)": {
354
+ display: "inline-block",
355
+ "vertical-align": "middle",
356
+ },
357
+
358
+ // FORM CONTROLS - Typography Inheritance
359
+ "input, button, textarea, select, optgroup": {
360
+ "font-family": "inherit",
361
+ "font-size": "100%",
362
+ "line-height": "inherit",
363
+ },
364
+
365
+ textarea: {
366
+ "line-height": "1.5",
367
+ },
368
+
369
+ "button, [type='button'], [type='reset'], [type='submit']": {
370
+ cursor: "pointer",
371
+ },
372
+
373
+ // MEDIA - Images, Videos, Figures
374
+ ":where(img, video, canvas, audio, iframe, svg)": {
375
+ "max-width": "100%",
376
+ height: "auto",
377
+ "vertical-align": "middle",
378
+ },
379
+
380
+ figure: {
381
+ "margin-top": "var(--spacing-lg)",
382
+ "margin-bottom": "var(--spacing-lg)",
383
+ },
384
+
385
+ figcaption: {
386
+ "margin-top": "0.375rem",
387
+ "font-size": "0.875em",
388
+ "line-height": "1.5",
389
+ },
390
+
391
+ // TABLES - Structural Layout
392
+ table: {
393
+ width: "100%",
394
+ "margin-top": "var(--spacing-md)",
395
+ "margin-bottom": "var(--spacing-md)",
396
+ "border-collapse": "collapse",
397
+ "font-size": "1em",
398
+ "line-height": "1.6",
399
+ },
400
+
401
+ caption: {
402
+ "margin-bottom": "var(--spacing-xs)",
403
+ "font-size": "0.875em",
404
+ "font-weight": "600",
405
+ "text-align": "left",
406
+ },
407
+
408
+ "th, td": {
409
+ padding: "var(--spacing-xs) var(--spacing-sm)",
410
+ "text-align": "left",
411
+ },
412
+
413
+ th: {
414
+ "font-weight": "600",
415
+ },
416
+
417
+ "thead th": {
418
+ "vertical-align": "bottom",
419
+ },
420
+
421
+ "tbody th, tbody td": {
422
+ "vertical-align": "top",
423
+ },
424
+
425
+ "tfoot th, tfoot td": {
426
+ "vertical-align": "top",
427
+ },
428
+
429
+ "tbody + tbody": {
430
+ "border-top-width": "2px",
431
+ },
432
+
433
+ // SEPARATORS - Spacing Only
434
+ hr: {
435
+ "margin-top": "var(--spacing-xl)",
436
+ "margin-bottom": "var(--spacing-xl)",
437
+ border: "0",
438
+ "border-top": "1px solid",
439
+ },
440
+
441
+ // INTERACTIVE ELEMENTS - Accessibility
442
+ ":where(:focus, :focus-visible)": {
443
+ "outline-offset": "2px",
444
+ },
445
+
446
+ details: {
447
+ "margin-top": "var(--spacing-md)",
448
+ "margin-bottom": "var(--spacing-md)",
449
+ },
450
+
451
+ summary: {
452
+ cursor: "pointer",
453
+ "font-weight": "600",
454
+ },
455
+
456
+ "details[open] > summary": {
457
+ "margin-bottom": "var(--spacing-xs)",
458
+ },
459
+
460
+ dialog: {
461
+ "font-size": "inherit",
462
+ "line-height": "inherit",
463
+ },
464
+
465
+ // UTILITIES - Margin Cleanup
466
+ ":where(h1, h2, h3, h4, h5, h6, p, ul:not(li > ul, li > ol), ol:not(li > ul, li > ol), dl, blockquote, figure, table, pre):first-child":
467
+ {
468
+ "margin-top": "0",
469
+ },
470
+
471
+ ":where(p, ul, ol, dl, blockquote, figure, table, pre):last-child": {
472
+ "margin-bottom": "0",
473
+ },
474
+ };
package/src/extra.js ADDED
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Extra opinionated styles and coloring.
3
+ * Applies colors to the structural base elements.
4
+ */
5
+ export default {
6
+ // --- Basic Coloring (Applying theme variables) ---
7
+
8
+ "body": {
9
+ "background-color": "var(--clampography-background)",
10
+ "color": "var(--clampography-text)",
11
+ },
12
+
13
+ ":where(h1, h2, h3, h4, h5, h6)": {
14
+ "color": "var(--clampography-heading)",
15
+ },
16
+
17
+ "a": {
18
+ "color": "var(--clampography-link)",
19
+ },
20
+
21
+ // Lists
22
+ "ul > li::before": {
23
+ "background-color": "var(--clampography-primary)", // Bullet points
24
+ },
25
+
26
+ "ol > li::before": {
27
+ "color": "var(--clampography-secondary)", // Numbers
28
+ },
29
+
30
+ // Inline Code
31
+ ":where(code, kbd, samp)": {
32
+ "background-color": "var(--clampography-surface)",
33
+ "color": "var(--clampography-heading)",
34
+ "border": "1px solid var(--clampography-border)",
35
+ "border-radius": "0.25rem",
36
+ },
37
+
38
+ // Preformatted Code Blocks
39
+ "pre": {
40
+ "background-color": "var(--clampography-surface)",
41
+ "border": "1px solid var(--clampography-border)",
42
+ "border-radius": "0.375rem",
43
+ "padding": "1rem",
44
+ },
45
+
46
+ // Tables
47
+ "th": {
48
+ "color": "var(--clampography-heading)",
49
+ },
50
+
51
+ "th, td": {
52
+ "border-bottom": "1px solid var(--clampography-border)",
53
+ },
54
+
55
+ "thead th": {
56
+ "border-bottom-width": "2px",
57
+ },
58
+
59
+ // Captions & Muted text
60
+ "caption, figcaption, .muted": {
61
+ "color": "var(--clampography-muted)",
62
+ },
63
+
64
+ // Horizontal Rule (Thematic)
65
+ "hr": {
66
+ "height": "1px",
67
+ "border-width": "0",
68
+ "margin-top": "3rem",
69
+ "margin-bottom": "3rem",
70
+ "background-color": "var(--clampography-border)",
71
+ },
72
+
73
+ // --- Opinionated Extras ---
74
+
75
+ // Styled Blockquote
76
+ "blockquote": {
77
+ "border-left-width": "4px",
78
+ "border-left-color": "var(--clampography-primary)",
79
+ "background-color": "var(--clampography-surface)",
80
+ "padding": "1rem",
81
+ "border-radius": "0.25rem",
82
+ "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)",
99
+ },
100
+
101
+ // Mark
102
+ "mark": {
103
+ "background-color": "var(--clampography-primary)",
104
+ "color": "var(--clampography-background)",
105
+ },
106
+
107
+ // Deleted Text
108
+ "del": {
109
+ "text-decoration-color": "var(--clampography-secondary)",
110
+ "text-decoration-thickness": "2px",
111
+ },
112
+
113
+ // Fieldset
114
+ "fieldset": {
115
+ "border": "1px solid var(--clampography-border)",
116
+ "border-radius": "0.375rem",
117
+ },
118
+
119
+ "legend": {
120
+ "color": "var(--clampography-heading)",
121
+ },
122
+
123
+ // Details
124
+ "details": {
125
+ "border": "1px solid var(--clampography-border)",
126
+ "border-radius": "0.375rem",
127
+ "padding": "0.5rem",
128
+ },
129
+
130
+ "summary": {
131
+ "color": "var(--clampography-heading)",
132
+ },
133
+
134
+ "details[open] > summary": {
135
+ "border-bottom": "1px solid var(--clampography-border)",
136
+ "padding-bottom": "0.5rem",
137
+ },
138
+ };
package/src/index.js ADDED
@@ -0,0 +1,109 @@
1
+ import plugin from "tailwindcss/plugin";
2
+ import { themes as builtInThemes } from "./themes.js";
3
+ import baseStyles from "./base.js";
4
+ import extraStyles from "./extra.js";
5
+
6
+ /**
7
+ * Helper to resolve boolean options from CSS configuration.
8
+ * CSS values often come as strings ("true"/"false"), which are both truthy in JS.
9
+ */
10
+ const resolveBool = (value, defaultValue) => {
11
+ if (value === "false" || value === false) return false;
12
+ if (value === "true" || value === true) return true;
13
+ return defaultValue;
14
+ };
15
+
16
+ /**
17
+ * Main plugin function.
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 = [];
46
+ } 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;
68
+ }
69
+
70
+ // Check for --prefersdark flag
71
+ if (themeName.toLowerCase().includes("--prefersdark")) {
72
+ themeName = themeName.replace(/--prefersdark/i, "").trim();
73
+ prefersDarkTheme = themeName;
74
+ }
75
+
76
+ // Check if theme exists in the database
77
+ if (builtInThemes[themeName]) {
78
+ themesToInclude.push(themeName);
79
+ }
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
+ });
@@ -0,0 +1,82 @@
1
+ import plugin from "tailwindcss/plugin";
2
+ import { themes as builtInThemes } from "./themes.js";
3
+
4
+ export default plugin.withOptions((options = {}) => {
5
+ return ({ addBase }) => {
6
+ // 1. Extract metadata
7
+ const themeName = options.name;
8
+ const isDefault = options.default ?? false;
9
+ const isPrefersDark = options.prefersdark ?? false;
10
+ const rootSelector = options.root ?? ":root";
11
+ // Defaults to light scheme if not specified
12
+ const colorScheme = options["color-scheme"] ?? "light";
13
+
14
+ if (!themeName) {
15
+ console.warn("Clampography: Theme definition missing 'name' property.");
16
+ return;
17
+ }
18
+
19
+ // 2. Prepare Base Colors (Fallback)
20
+ // We fetch the full palette of the requested color scheme (light/dark)
21
+ // to fill in any missing gaps in the user's definition.
22
+ const fallbackTheme = builtInThemes[colorScheme] || builtInThemes["light"];
23
+
24
+ // 3. Extract & Merge Colors
25
+ const themeColors = {};
26
+
27
+ // Mapping of simplified keys to full CSS variable names
28
+ const keyMap = {
29
+ "background": "--clampography-background",
30
+ "surface": "--clampography-surface",
31
+ "border": "--clampography-border",
32
+ "heading": "--clampography-heading",
33
+ "text": "--clampography-text",
34
+ "muted": "--clampography-muted",
35
+ "link": "--clampography-link",
36
+ "primary": "--clampography-primary",
37
+ "secondary": "--clampography-secondary",
38
+ };
39
+
40
+ // First, populate with fallback colors
41
+ Object.keys(fallbackTheme).forEach((key) => {
42
+ themeColors[key] = fallbackTheme[key];
43
+ });
44
+
45
+ // Then override with user provided values
46
+ Object.keys(options).forEach((key) => {
47
+ // Ignore metadata keys
48
+ if (
49
+ ["name", "default", "prefersdark", "root", "color-scheme"].includes(key)
50
+ ) return;
51
+
52
+ if (keyMap[key]) {
53
+ themeColors[keyMap[key]] = options[key];
54
+ } else if (key.startsWith("--")) {
55
+ themeColors[key] = options[key];
56
+ }
57
+ });
58
+
59
+ // Add the CSS property 'color-scheme' for browser UI adaptation (scrollbars, etc.)
60
+ themeColors["color-scheme"] = colorScheme;
61
+
62
+ // 4. Generate Styles
63
+ const styles = {};
64
+
65
+ // A. Define the theme as a named data-theme
66
+ styles[`[data-theme="${themeName}"]`] = themeColors;
67
+
68
+ // B. If default, apply to root
69
+ if (isDefault) {
70
+ styles[rootSelector] = themeColors;
71
+ }
72
+
73
+ // C. If prefers-dark, apply to media query
74
+ if (isPrefersDark) {
75
+ styles["@media (prefers-color-scheme: dark)"] = {
76
+ [rootSelector]: themeColors,
77
+ };
78
+ }
79
+
80
+ addBase(styles);
81
+ };
82
+ });
package/src/themes.js ADDED
@@ -0,0 +1,52 @@
1
+ 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
+ },
50
+ };
51
+
52
+ export const themesList = Object.keys(themes);
@@ -1,19 +0,0 @@
1
- /* You need daisyUI for this preset. Make sure its colors are accessible. */
2
-
3
- @import '../clampography.css';
4
-
5
- @layer base {
6
- a {
7
- @apply text-primary font-bold tracking-wider underline decoration-2 underline-offset-4 transition-colors duration-150;
8
-
9
- text-decoration-color: color-mix(
10
- in srgb,
11
- var(--color-primary) 30%,
12
- transparent
13
- );
14
- }
15
-
16
- a:hover {
17
- text-decoration-color: var(--color-primary);
18
- }
19
- }
File without changes