emily-css 1.0.15 → 1.0.18
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/CHANGELOG.md +24 -0
- package/README.md +21 -29
- package/package.json +1 -1
- package/src/generators.js +209 -2
- package/src/index.js +120 -17
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,30 @@ All notable changes to `emily-css` are documented here.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## v1.0.18 — May 2026
|
|
8
|
+
|
|
9
|
+
****
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- added more utitlies as a code block
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
## v1.0.17 — May 2026
|
|
16
|
+
|
|
17
|
+
**added new utilties, and added component patterns**
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- added new utilties, and added component patterns
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
## v1.0.16 — May 2026
|
|
24
|
+
|
|
25
|
+
**feat: add Round 2 utility set — 156/156 tests passing**
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- feat: add Round 2 utility set — 156/156 tests passing
|
|
29
|
+
|
|
30
|
+
---
|
|
7
31
|
## v1.0.15 — May 2026
|
|
8
32
|
|
|
9
33
|
**updated readme**
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ emilyCSS is built for real-world systems like **Drupal, Power Pages, WordPress,
|
|
|
20
20
|
npx emily-css init
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
This creates your `emily.config.json`, walks you through your brand settings, and runs your first build.
|
|
23
|
+
This creates your `emily.config.json`, walks you through your brand settings (colours, fonts, spacing, etc.), and runs your first build.
|
|
24
24
|
|
|
25
25
|
### 2. Link the CSS
|
|
26
26
|
|
|
@@ -28,17 +28,13 @@ This creates your `emily.config.json`, walks you through your brand settings, an
|
|
|
28
28
|
<link rel="stylesheet" href="./dist/emily.min.css">
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
### 3.
|
|
31
|
+
### 3. Start Building
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
npx emily-css watch # Rebuilds automatically on config/template changes
|
|
35
|
-
npx emily-css build # Manual rebuild
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Open the showcase:
|
|
33
|
+
Use the generated utilities and browse the showcase for ready-to-copy components.
|
|
39
34
|
|
|
40
35
|
```bash
|
|
41
|
-
|
|
36
|
+
npx emily-css build # Rebuild after config changes
|
|
37
|
+
npx emily-css watch # Watch mode for development
|
|
42
38
|
```
|
|
43
39
|
|
|
44
40
|
## Core Features
|
|
@@ -48,30 +44,24 @@ npm run emily:showcase # Serves at http://localhost:3456
|
|
|
48
44
|
- **Accessibility First** — Focus-visible rings, motion utilities, WCAG 2.2 AA colours
|
|
49
45
|
- **No Build Pipeline Required** — Just a static CSS file
|
|
50
46
|
- **Smart Purge** — Remove unused utilities for tiny production files
|
|
51
|
-
- **UI Starter Kit** — Copy-paste accessible components from
|
|
47
|
+
- **UI Starter Kit** — Copy-paste accessible components from showcase.html
|
|
52
48
|
|
|
53
49
|
## Commands
|
|
54
50
|
|
|
55
51
|
```bash
|
|
56
|
-
npx emily-css init
|
|
57
|
-
npx emily-css build
|
|
58
|
-
npx emily-css watch
|
|
59
|
-
npx emily-css purge
|
|
52
|
+
npx emily-css init # Setup config + first build
|
|
53
|
+
npx emily-css build # Regenerate CSS
|
|
54
|
+
npx emily-css watch # Development watch mode
|
|
55
|
+
npx emily-css purge # Remove unused styles for production
|
|
60
56
|
```
|
|
61
57
|
|
|
62
58
|
## How Purge Works
|
|
63
59
|
|
|
64
60
|
emilyCSS scans your templates for used class names and removes everything else.
|
|
65
61
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
```json
|
|
69
|
-
"purge": {
|
|
70
|
-
"extensions": [".html", ".php", ".twig", ".liquid", ".jsx", ".vue", ".astro"]
|
|
71
|
-
}
|
|
72
|
-
```
|
|
62
|
+
Supported files: `.html`, `.php`, `.twig`, `.liquid`, `.jsx`, `.vue`, `.astro`, etc. (configurable).
|
|
73
63
|
|
|
74
|
-
**Important:** Dynamically constructed classes
|
|
64
|
+
**Important:** Dynamically constructed classes like `bg-${colour}` are not detected. Use static strings or add them to the safelist.
|
|
75
65
|
|
|
76
66
|
## File Size (Typical)
|
|
77
67
|
|
|
@@ -80,7 +70,9 @@ Configure it in `emily.config.json`:
|
|
|
80
70
|
| Full build | ~1.1 MB |
|
|
81
71
|
| After purge | 10–50 KB |
|
|
82
72
|
|
|
83
|
-
## Configuration
|
|
73
|
+
## Configuration
|
|
74
|
+
|
|
75
|
+
Edit `emily.config.json`:
|
|
84
76
|
|
|
85
77
|
```json
|
|
86
78
|
{
|
|
@@ -95,7 +87,7 @@ Configure it in `emily.config.json`:
|
|
|
95
87
|
"neutral": "#57534E"
|
|
96
88
|
},
|
|
97
89
|
"purge": {
|
|
98
|
-
"
|
|
90
|
+
"content": ["./**/*.{html,php,jsx,tsx,vue}"]
|
|
99
91
|
}
|
|
100
92
|
}
|
|
101
93
|
```
|
|
@@ -104,12 +96,12 @@ After changes: `npx emily-css build`
|
|
|
104
96
|
|
|
105
97
|
## Component Showcase
|
|
106
98
|
|
|
107
|
-
After
|
|
99
|
+
After your first build, open `showcase.html` in your browser. It contains production-ready, accessible components (buttons, forms, alerts, cards, etc.) built with your brand.
|
|
108
100
|
|
|
109
101
|
## EmilyUI vs emilyCSS
|
|
110
102
|
|
|
111
103
|
- **EmilyUI** — The broader brand / ecosystem
|
|
112
|
-
- **emilyCSS** — The current product (
|
|
104
|
+
- **emilyCSS** — The current product (the emily-css npm package + CLI)
|
|
113
105
|
|
|
114
106
|
## Example Components
|
|
115
107
|
|
|
@@ -124,7 +116,7 @@ After building, run `npm run emily:showcase` and visit `http://localhost:3456`.
|
|
|
124
116
|
### Responsive Card
|
|
125
117
|
|
|
126
118
|
```html
|
|
127
|
-
<div class="w-full md:w-96 p-6 rounded-
|
|
119
|
+
<div class="w-full md:w-96 p-6 rounded-xl bg-white border border-neutral-20 shadow-sm">
|
|
128
120
|
<h2 class="text-2xl font-semibold text-neutral-90">Card Title</h2>
|
|
129
121
|
<p class="mt-3 text-neutral-70">Content goes here.</p>
|
|
130
122
|
</div>
|
|
@@ -132,7 +124,7 @@ After building, run `npm run emily:showcase` and visit `http://localhost:3456`.
|
|
|
132
124
|
|
|
133
125
|
## Fonts
|
|
134
126
|
|
|
135
|
-
emilyCSS applies font stacks but does not include font files.
|
|
127
|
+
emilyCSS applies font stacks but does not include font files. Recommended approach:
|
|
136
128
|
|
|
137
129
|
```bash
|
|
138
130
|
npm install @fontsource/inter @fontsource/lexend
|
|
@@ -147,4 +139,4 @@ Then import the weights you need.
|
|
|
147
139
|
|
|
148
140
|
## License
|
|
149
141
|
|
|
150
|
-
MIT
|
|
142
|
+
MIT
|
package/package.json
CHANGED
package/src/generators.js
CHANGED
|
@@ -65,6 +65,7 @@ function sizingUtilities(spacing) {
|
|
|
65
65
|
css += `.max-w-5xl { max-width: 64rem; }\n`;
|
|
66
66
|
css += `.max-w-6xl { max-width: 72rem; }\n`;
|
|
67
67
|
css += `.max-w-7xl { max-width: 80rem; }\n`;
|
|
68
|
+
css += `.max-w-prose { max-width: 65ch; }\n`;
|
|
68
69
|
|
|
69
70
|
// Aspect ratio
|
|
70
71
|
css += `.aspect-auto { aspect-ratio: auto; }\n`;
|
|
@@ -137,6 +138,7 @@ function overflowUtilities() {
|
|
|
137
138
|
.overflow-y-auto { overflow-y: auto; }
|
|
138
139
|
.overflow-y-hidden { overflow-y: hidden; }
|
|
139
140
|
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
141
|
+
.line-clamp-1 { display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden; }
|
|
140
142
|
.line-clamp-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
|
141
143
|
.line-clamp-3 { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
|
|
142
144
|
.line-clamp-4 { display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; }
|
|
@@ -148,7 +150,7 @@ function overflowUtilities() {
|
|
|
148
150
|
|
|
149
151
|
// Opacity
|
|
150
152
|
function opacityUtilities() {
|
|
151
|
-
const opacities = [0, 5, 10, 25, 50, 75, 90, 95, 100];
|
|
153
|
+
const opacities = [0, 5, 10, 20, 25, 30, 40, 50, 60, 70, 75, 80, 90, 95, 100];
|
|
152
154
|
let css = `/* Opacity */\n`;
|
|
153
155
|
|
|
154
156
|
opacities.forEach(op => {
|
|
@@ -277,6 +279,11 @@ function ringUtilities(colours) {
|
|
|
277
279
|
css += `.outline-0 { outline-width: 0; }\n`;
|
|
278
280
|
css += `.outline-1 { outline-width: 1px; }\n`;
|
|
279
281
|
css += `.outline-2 { outline-width: 2px; }\n`;
|
|
282
|
+
css += `.outline-offset-0 { outline-offset: 0px; }\n`;
|
|
283
|
+
css += `.outline-offset-1 { outline-offset: 1px; }\n`;
|
|
284
|
+
css += `.outline-offset-2 { outline-offset: 2px; }\n`;
|
|
285
|
+
css += `.outline-offset-4 { outline-offset: 4px; }\n`;
|
|
286
|
+
css += `.outline-offset-8 { outline-offset: 8px; }\n`;
|
|
280
287
|
|
|
281
288
|
css += `\n`;
|
|
282
289
|
return css;
|
|
@@ -353,6 +360,8 @@ function svgUtilities(colours) {
|
|
|
353
360
|
function formUtilities() {
|
|
354
361
|
return `/* Forms */
|
|
355
362
|
.appearance-none { appearance: none; }
|
|
363
|
+
.caret-transparent { caret-color: transparent; }
|
|
364
|
+
.caret-current { caret-color: currentColor; }
|
|
356
365
|
.placeholder-transparent::placeholder { color: transparent; }
|
|
357
366
|
.placeholder-current::placeholder { color: currentColor; }
|
|
358
367
|
.autofill\\:bg-transparent:autofill { background-color: transparent !important; }
|
|
@@ -398,6 +407,10 @@ function contentScrollUtilities() {
|
|
|
398
407
|
.snap-both { scroll-snap-type: both var(--emily-scroll-snap-strictness); }
|
|
399
408
|
.snap-mandatory { --emily-scroll-snap-strictness: mandatory; }
|
|
400
409
|
.snap-proximity { --emily-scroll-snap-strictness: proximity; }
|
|
410
|
+
.snap-start { scroll-snap-align: start; }
|
|
411
|
+
.snap-center { scroll-snap-align: center; }
|
|
412
|
+
.snap-end { scroll-snap-align: end; }
|
|
413
|
+
.snap-align-none { scroll-snap-align: none; }
|
|
401
414
|
|
|
402
415
|
`;
|
|
403
416
|
}
|
|
@@ -442,6 +455,16 @@ function cursorUtilities() {
|
|
|
442
455
|
.select-text { user-select: text; }
|
|
443
456
|
.select-all { user-select: all; }
|
|
444
457
|
.select-auto { user-select: auto; }
|
|
458
|
+
.resize-none { resize: none; }
|
|
459
|
+
.resize { resize: both; }
|
|
460
|
+
.resize-x { resize: horizontal; }
|
|
461
|
+
.resize-y { resize: vertical; }
|
|
462
|
+
.isolate { isolation: isolate; }
|
|
463
|
+
.isolation-auto { isolation: auto; }
|
|
464
|
+
.will-change-auto { will-change: auto; }
|
|
465
|
+
.will-change-scroll { will-change: scroll-position; }
|
|
466
|
+
.will-change-contents { will-change: contents; }
|
|
467
|
+
.will-change-transform { will-change: transform; }
|
|
445
468
|
|
|
446
469
|
`;
|
|
447
470
|
}
|
|
@@ -453,6 +476,15 @@ function accessibilityUtilities() {
|
|
|
453
476
|
.not-sr-only { position: static; width: auto; height: auto; padding: 0; margin: 0; overflow: visible; clip: auto; white-space: normal; }
|
|
454
477
|
.focus-visible:focus { outline: 2px solid currentColor; outline-offset: 2px; }
|
|
455
478
|
.focus\\:outline-none:focus { outline: 2px solid transparent; outline-offset: 2px; }
|
|
479
|
+
|
|
480
|
+
/* Touch target — WCAG 2.2 SC 2.5.8 minimum 24x24px hit area */
|
|
481
|
+
.touch-target { position: relative; }
|
|
482
|
+
.touch-target::before { content: ''; position: absolute; top: 50%; left: 50%; width: max(100%, 24px); height: max(100%, 24px); transform: translate(-50%, -50%); }
|
|
483
|
+
|
|
484
|
+
/* Skip link — reveals on focus for keyboard/AT users */
|
|
485
|
+
.skip-link { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border-width: 0; }
|
|
486
|
+
.skip-link:focus { position: fixed; top: 1rem; left: 1rem; z-index: 1070; width: auto; height: auto; padding: 0.75rem 1.25rem; background-color: #ffffff; color: #000000; font-weight: 700; text-decoration: underline; border: 2px solid currentColor; border-radius: 4px; clip: auto; white-space: normal; }
|
|
487
|
+
|
|
456
488
|
@media (prefers-reduced-motion: reduce) {
|
|
457
489
|
.motion-reduce\\:transition-none { transition-property: none; }
|
|
458
490
|
.motion-reduce\\:animate-none { animation: none; }
|
|
@@ -515,6 +547,175 @@ function codeUtilities() {
|
|
|
515
547
|
`;
|
|
516
548
|
}
|
|
517
549
|
|
|
550
|
+
// Animations
|
|
551
|
+
function animationUtilities() {
|
|
552
|
+
return `/* Animations — keyframes */
|
|
553
|
+
@keyframes spin {
|
|
554
|
+
to { transform: rotate(360deg); }
|
|
555
|
+
}
|
|
556
|
+
@keyframes ping {
|
|
557
|
+
75%, 100% { transform: scale(2); opacity: 0; }
|
|
558
|
+
}
|
|
559
|
+
@keyframes pulse {
|
|
560
|
+
50% { opacity: 0.5; }
|
|
561
|
+
}
|
|
562
|
+
@keyframes bounce {
|
|
563
|
+
0%, 100% { transform: translateY(-25%); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); }
|
|
564
|
+
50% { transform: translateY(0); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); }
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/* Animations — utilities */
|
|
568
|
+
.animate-none { animation: none; }
|
|
569
|
+
.animate-spin { animation: spin 1s linear infinite; }
|
|
570
|
+
.animate-ping { animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; }
|
|
571
|
+
.animate-pulse { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; }
|
|
572
|
+
.animate-bounce { animation: bounce 1s infinite; }
|
|
573
|
+
|
|
574
|
+
`;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
// Backdrop Filters
|
|
579
|
+
function backdropUtilities() {
|
|
580
|
+
return `/* Backdrop Filters */
|
|
581
|
+
.backdrop-blur-none { backdrop-filter: blur(0); }
|
|
582
|
+
.backdrop-blur-sm { backdrop-filter: blur(4px); }
|
|
583
|
+
.backdrop-blur { backdrop-filter: blur(8px); }
|
|
584
|
+
.backdrop-blur-md { backdrop-filter: blur(12px); }
|
|
585
|
+
.backdrop-blur-lg { backdrop-filter: blur(16px); }
|
|
586
|
+
.backdrop-blur-xl { backdrop-filter: blur(24px); }
|
|
587
|
+
.backdrop-blur-2xl { backdrop-filter: blur(40px); }
|
|
588
|
+
|
|
589
|
+
`;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Space Between
|
|
593
|
+
function spaceUtilities(spacing) {
|
|
594
|
+
let css = `/* Space Between */\n`;
|
|
595
|
+
Object.entries(spacing).forEach(([key, value]) => {
|
|
596
|
+
const escaped = key.replace(/\./g, '\\.');
|
|
597
|
+
css += `.space-x-${escaped} > * + * { margin-left: ${value}; }\n`;
|
|
598
|
+
css += `.space-y-${escaped} > * + * { margin-top: ${value}; }\n`;
|
|
599
|
+
css += `.-space-x-${escaped} > * + * { margin-left: -${value}; }\n`;
|
|
600
|
+
css += `.-space-y-${escaped} > * + * { margin-top: -${value}; }\n`;
|
|
601
|
+
});
|
|
602
|
+
css += `.space-x-auto > * + * { margin-left: auto; }\n`;
|
|
603
|
+
css += `.space-y-auto > * + * { margin-top: auto; }\n`;
|
|
604
|
+
css += `\n`;
|
|
605
|
+
return css;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Divide
|
|
609
|
+
function divideUtilities(spacing, colours) {
|
|
610
|
+
let css = `/* Divide */\n`;
|
|
611
|
+
// Widths
|
|
612
|
+
css += `.divide-x > * + * { border-left-width: 1px; border-left-style: solid; }\n`;
|
|
613
|
+
css += `.divide-y > * + * { border-top-width: 1px; border-top-style: solid; }\n`;
|
|
614
|
+
css += `.divide-x-0 > * + * { border-left-width: 0px; }\n`;
|
|
615
|
+
css += `.divide-y-0 > * + * { border-top-width: 0px; }\n`;
|
|
616
|
+
css += `.divide-x-2 > * + * { border-left-width: 2px; border-left-style: solid; }\n`;
|
|
617
|
+
css += `.divide-y-2 > * + * { border-top-width: 2px; border-top-style: solid; }\n`;
|
|
618
|
+
css += `.divide-x-4 > * + * { border-left-width: 4px; border-left-style: solid; }\n`;
|
|
619
|
+
css += `.divide-y-4 > * + * { border-top-width: 4px; border-top-style: solid; }\n`;
|
|
620
|
+
// Styles
|
|
621
|
+
css += `.divide-solid > * + * { border-style: solid; }\n`;
|
|
622
|
+
css += `.divide-dashed > * + * { border-style: dashed; }\n`;
|
|
623
|
+
css += `.divide-dotted > * + * { border-style: dotted; }\n`;
|
|
624
|
+
css += `.divide-none > * + * { border-style: none; }\n`;
|
|
625
|
+
// Colours
|
|
626
|
+
Object.entries(colours).forEach(([colourName, shades]) => {
|
|
627
|
+
Object.entries(shades).forEach(([shade]) => {
|
|
628
|
+
css += `.divide-${colourName}-${shade} > * + * { border-color: var(--color-${colourName}-${shade}); }\n`;
|
|
629
|
+
});
|
|
630
|
+
});
|
|
631
|
+
css += `.divide-white > * + * { border-color: #ffffff; }\n`;
|
|
632
|
+
css += `.divide-black > * + * { border-color: #000000; }\n`;
|
|
633
|
+
css += `.divide-transparent > * + * { border-color: transparent; }\n`;
|
|
634
|
+
css += `\n`;
|
|
635
|
+
return css;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Background Utilities
|
|
639
|
+
function backgroundUtilities() {
|
|
640
|
+
return `/* Background */
|
|
641
|
+
.bg-fixed { background-attachment: fixed; }
|
|
642
|
+
.bg-local { background-attachment: local; }
|
|
643
|
+
.bg-scroll { background-attachment: scroll; }
|
|
644
|
+
.bg-clip-border { background-clip: border-box; }
|
|
645
|
+
.bg-clip-padding { background-clip: padding-box; }
|
|
646
|
+
.bg-clip-content { background-clip: content-box; }
|
|
647
|
+
.bg-clip-text { -webkit-background-clip: text; background-clip: text; }
|
|
648
|
+
.bg-repeat { background-repeat: repeat; }
|
|
649
|
+
.bg-no-repeat { background-repeat: no-repeat; }
|
|
650
|
+
.bg-repeat-x { background-repeat: repeat-x; }
|
|
651
|
+
.bg-repeat-y { background-repeat: repeat-y; }
|
|
652
|
+
.bg-repeat-round { background-repeat: round; }
|
|
653
|
+
.bg-repeat-space { background-repeat: space; }
|
|
654
|
+
.bg-auto { background-size: auto; }
|
|
655
|
+
.bg-cover { background-size: cover; }
|
|
656
|
+
.bg-contain { background-size: contain; }
|
|
657
|
+
.bg-center { background-position: center; }
|
|
658
|
+
.bg-top { background-position: top; }
|
|
659
|
+
.bg-bottom { background-position: bottom; }
|
|
660
|
+
.bg-left { background-position: left; }
|
|
661
|
+
.bg-right { background-position: right; }
|
|
662
|
+
.bg-left-top { background-position: left top; }
|
|
663
|
+
.bg-left-bottom { background-position: left bottom; }
|
|
664
|
+
.bg-right-top { background-position: right top; }
|
|
665
|
+
.bg-right-bottom { background-position: right bottom; }
|
|
666
|
+
|
|
667
|
+
`;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// CSS Filters
|
|
671
|
+
function filterUtilities() {
|
|
672
|
+
return `/* Filters */
|
|
673
|
+
.filter-none { filter: none; }
|
|
674
|
+
.blur-none { filter: blur(0); }
|
|
675
|
+
.blur-sm { filter: blur(4px); }
|
|
676
|
+
.blur { filter: blur(8px); }
|
|
677
|
+
.blur-md { filter: blur(12px); }
|
|
678
|
+
.blur-lg { filter: blur(16px); }
|
|
679
|
+
.blur-xl { filter: blur(24px); }
|
|
680
|
+
.brightness-0 { filter: brightness(0); }
|
|
681
|
+
.brightness-50 { filter: brightness(.5); }
|
|
682
|
+
.brightness-75 { filter: brightness(.75); }
|
|
683
|
+
.brightness-90 { filter: brightness(.9); }
|
|
684
|
+
.brightness-100 { filter: brightness(1); }
|
|
685
|
+
.brightness-110 { filter: brightness(1.1); }
|
|
686
|
+
.brightness-125 { filter: brightness(1.25); }
|
|
687
|
+
.brightness-150 { filter: brightness(1.5); }
|
|
688
|
+
.brightness-200 { filter: brightness(2); }
|
|
689
|
+
.contrast-0 { filter: contrast(0); }
|
|
690
|
+
.contrast-50 { filter: contrast(.5); }
|
|
691
|
+
.contrast-75 { filter: contrast(.75); }
|
|
692
|
+
.contrast-100 { filter: contrast(1); }
|
|
693
|
+
.contrast-125 { filter: contrast(1.25); }
|
|
694
|
+
.contrast-150 { filter: contrast(1.5); }
|
|
695
|
+
.contrast-200 { filter: contrast(2); }
|
|
696
|
+
.grayscale-0 { filter: grayscale(0); }
|
|
697
|
+
.grayscale { filter: grayscale(100%); }
|
|
698
|
+
.invert-0 { filter: invert(0); }
|
|
699
|
+
.invert { filter: invert(100%); }
|
|
700
|
+
.sepia-0 { filter: sepia(0); }
|
|
701
|
+
.sepia { filter: sepia(100%); }
|
|
702
|
+
.saturate-0 { filter: saturate(0); }
|
|
703
|
+
.saturate-50 { filter: saturate(.5); }
|
|
704
|
+
.saturate-100 { filter: saturate(1); }
|
|
705
|
+
.saturate-150 { filter: saturate(1.5); }
|
|
706
|
+
.saturate-200 { filter: saturate(2); }
|
|
707
|
+
.hue-rotate-0 { filter: hue-rotate(0deg); }
|
|
708
|
+
.hue-rotate-15 { filter: hue-rotate(15deg); }
|
|
709
|
+
.hue-rotate-30 { filter: hue-rotate(30deg); }
|
|
710
|
+
.hue-rotate-60 { filter: hue-rotate(60deg); }
|
|
711
|
+
.hue-rotate-90 { filter: hue-rotate(90deg); }
|
|
712
|
+
.hue-rotate-180 { filter: hue-rotate(180deg); }
|
|
713
|
+
.-hue-rotate-30 { filter: hue-rotate(-30deg); }
|
|
714
|
+
.-hue-rotate-60 { filter: hue-rotate(-60deg); }
|
|
715
|
+
.-hue-rotate-90 { filter: hue-rotate(-90deg); }
|
|
716
|
+
|
|
717
|
+
`;
|
|
718
|
+
}
|
|
518
719
|
module.exports = {
|
|
519
720
|
displayUtilities,
|
|
520
721
|
sizingUtilities,
|
|
@@ -535,5 +736,11 @@ module.exports = {
|
|
|
535
736
|
cursorUtilities,
|
|
536
737
|
accessibilityUtilities,
|
|
537
738
|
containerUtilities,
|
|
538
|
-
codeUtilities
|
|
739
|
+
codeUtilities,
|
|
740
|
+
animationUtilities,
|
|
741
|
+
backdropUtilities,
|
|
742
|
+
spaceUtilities,
|
|
743
|
+
divideUtilities,
|
|
744
|
+
backgroundUtilities,
|
|
745
|
+
filterUtilities,
|
|
539
746
|
};
|
package/src/index.js
CHANGED
|
@@ -288,7 +288,13 @@ const {
|
|
|
288
288
|
cursorUtilities,
|
|
289
289
|
accessibilityUtilities,
|
|
290
290
|
containerUtilities,
|
|
291
|
-
codeUtilities
|
|
291
|
+
codeUtilities,
|
|
292
|
+
animationUtilities,
|
|
293
|
+
backdropUtilities,
|
|
294
|
+
spaceUtilities,
|
|
295
|
+
divideUtilities,
|
|
296
|
+
backgroundUtilities,
|
|
297
|
+
filterUtilities,
|
|
292
298
|
} = require('./generators');
|
|
293
299
|
|
|
294
300
|
// ============================================================================
|
|
@@ -498,6 +504,27 @@ function generateTypographyUtilities(config) {
|
|
|
498
504
|
css += `.underline { text-decoration: underline; }\n`;
|
|
499
505
|
css += `.no-underline { text-decoration: none; }\n`;
|
|
500
506
|
css += `.line-through { text-decoration: line-through; }\n`;
|
|
507
|
+
css += `.underline-offset-auto { text-underline-offset: auto; }\n`;
|
|
508
|
+
css += `.underline-offset-1 { text-underline-offset: 1px; }\n`;
|
|
509
|
+
css += `.underline-offset-2 { text-underline-offset: 2px; }\n`;
|
|
510
|
+
css += `.underline-offset-4 { text-underline-offset: 4px; }\n`;
|
|
511
|
+
css += `.underline-offset-8 { text-underline-offset: 8px; }\n`;
|
|
512
|
+
css += `.decoration-auto { text-decoration-thickness: auto; }\n`;
|
|
513
|
+
css += `.decoration-from-font { text-decoration-thickness: from-font; }\n`;
|
|
514
|
+
css += `.decoration-1 { text-decoration-thickness: 1px; }\n`;
|
|
515
|
+
css += `.decoration-2 { text-decoration-thickness: 2px; }\n`;
|
|
516
|
+
css += `.decoration-4 { text-decoration-thickness: 4px; }\n`;
|
|
517
|
+
|
|
518
|
+
// Font variant numeric
|
|
519
|
+
css += `.normal-nums { font-variant-numeric: normal; }\n`;
|
|
520
|
+
css += `.ordinal { font-variant-numeric: ordinal; }\n`;
|
|
521
|
+
css += `.slashed-zero { font-variant-numeric: slashed-zero; }\n`;
|
|
522
|
+
css += `.lining-nums { font-variant-numeric: lining-nums; }\n`;
|
|
523
|
+
css += `.oldstyle-nums { font-variant-numeric: oldstyle-nums; }\n`;
|
|
524
|
+
css += `.proportional-nums { font-variant-numeric: proportional-nums; }\n`;
|
|
525
|
+
css += `.tabular-nums { font-variant-numeric: tabular-nums; }\n`;
|
|
526
|
+
css += `.diagonal-fractions { font-variant-numeric: diagonal-fractions; }\n`;
|
|
527
|
+
css += `.stacked-fractions { font-variant-numeric: stacked-fractions; }\n`;
|
|
501
528
|
|
|
502
529
|
// Text transform
|
|
503
530
|
css += `.uppercase { text-transform: uppercase; }\n`;
|
|
@@ -739,6 +766,7 @@ function addStateVariants(css) {
|
|
|
739
766
|
const states = [
|
|
740
767
|
{ name: 'hover', selector: ':hover' },
|
|
741
768
|
{ name: 'focus', selector: ':focus' },
|
|
769
|
+
{ name: 'focus-within', selector: ':focus-within' },
|
|
742
770
|
{ name: 'focus-visible', selector: ':focus-visible' },
|
|
743
771
|
{ name: 'active', selector: ':active' },
|
|
744
772
|
{ name: 'disabled', selector: ':disabled' }
|
|
@@ -774,6 +802,63 @@ function addStateVariants(css) {
|
|
|
774
802
|
return variantCss;
|
|
775
803
|
}
|
|
776
804
|
|
|
805
|
+
|
|
806
|
+
// ============================================================================
|
|
807
|
+
// PATTERN COMPONENTS
|
|
808
|
+
// ============================================================================
|
|
809
|
+
// Composite classes that combine multiple utilities into named patterns.
|
|
810
|
+
// These live in @layer components so utilities always take precedence in the cascade.
|
|
811
|
+
// Gap values reference spacing variables generated from emily.config.json,
|
|
812
|
+
// with pixel fallbacks so they work even without the variables in scope.
|
|
813
|
+
|
|
814
|
+
function generatePatternComponents() {
|
|
815
|
+
return `
|
|
816
|
+
/* ---- Centering ---- */
|
|
817
|
+
|
|
818
|
+
/* Full-viewport overlay centering — use for modals, lightboxes, toasts */
|
|
819
|
+
.center-screen {
|
|
820
|
+
position: fixed;
|
|
821
|
+
inset: 0;
|
|
822
|
+
display: flex;
|
|
823
|
+
align-items: center;
|
|
824
|
+
justify-content: center;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/* Transform-based centering within a relative/absolute parent */
|
|
828
|
+
.center-absolute {
|
|
829
|
+
position: absolute;
|
|
830
|
+
top: 50%;
|
|
831
|
+
left: 50%;
|
|
832
|
+
transform: translate(-50%, -50%);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/* ---- Reading / Prose ---- */
|
|
836
|
+
|
|
837
|
+
/* Comfortable reading column — limits line length, centers the block */
|
|
838
|
+
.prose {
|
|
839
|
+
max-width: 65ch;
|
|
840
|
+
margin-inline: auto;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/* ---- Composition ---- */
|
|
844
|
+
|
|
845
|
+
/* Vertical stack with consistent gap — replaces manual margin chains */
|
|
846
|
+
.stack {
|
|
847
|
+
display: flex;
|
|
848
|
+
flex-direction: column;
|
|
849
|
+
gap: var(--space-4, 1rem);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/* Horizontal grouping with wrapping — for tags, button rows, icon lists */
|
|
853
|
+
.cluster {
|
|
854
|
+
display: flex;
|
|
855
|
+
flex-wrap: wrap;
|
|
856
|
+
gap: var(--space-4, 1rem);
|
|
857
|
+
align-items: center;
|
|
858
|
+
}
|
|
859
|
+
`;
|
|
860
|
+
}
|
|
861
|
+
|
|
777
862
|
// ============================================================================
|
|
778
863
|
// BUILD FUNCTION
|
|
779
864
|
// ============================================================================
|
|
@@ -831,6 +916,12 @@ function buildFullFramework() {
|
|
|
831
916
|
utilityCss += accessibilityUtilities();
|
|
832
917
|
utilityCss += containerUtilities();
|
|
833
918
|
utilityCss += codeUtilities();
|
|
919
|
+
utilityCss += animationUtilities();
|
|
920
|
+
utilityCss += backdropUtilities();
|
|
921
|
+
utilityCss += spaceUtilities(spacing);
|
|
922
|
+
utilityCss += divideUtilities(spacing, colours);
|
|
923
|
+
utilityCss += backgroundUtilities();
|
|
924
|
+
utilityCss += filterUtilities();
|
|
834
925
|
|
|
835
926
|
// Add state, dark mode, and responsive variants to utilities
|
|
836
927
|
utilityCss = addStateVariants(utilityCss);
|
|
@@ -892,19 +983,40 @@ function buildFullFramework() {
|
|
|
892
983
|
overflow-wrap: break-word;
|
|
893
984
|
}
|
|
894
985
|
|
|
895
|
-
/* Code
|
|
986
|
+
/* Code — terminal style by default */
|
|
987
|
+
code {
|
|
988
|
+
font-family: "Menlo", "Monaco", "Courier New", monospace;
|
|
989
|
+
font-size: 0.875em;
|
|
990
|
+
background-color: #0d0c0b;
|
|
991
|
+
color: #a3c986;
|
|
992
|
+
padding: 0.125rem 0.4rem;
|
|
993
|
+
border-radius: 4px;
|
|
994
|
+
display: inline;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
/* Block code — terminal command style, no extra classes needed */
|
|
998
|
+
code.block {
|
|
999
|
+
display: block;
|
|
1000
|
+
padding: 0.625rem 1rem;
|
|
1001
|
+
border-radius: 6px;
|
|
1002
|
+
font-size: 0.8125rem;
|
|
1003
|
+
line-height: 1.6;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
/* Pre — wraps multi-line code, consistent terminal look */
|
|
896
1007
|
pre {
|
|
897
|
-
background-color: #
|
|
898
|
-
color: #
|
|
1008
|
+
background-color: #0d0c0b;
|
|
1009
|
+
color: #e4e0db;
|
|
899
1010
|
padding: 1.25rem;
|
|
900
|
-
border-radius:
|
|
1011
|
+
border-radius: 6px;
|
|
901
1012
|
overflow-x: auto;
|
|
902
1013
|
font-family: "Menlo", "Monaco", "Courier New", monospace;
|
|
903
1014
|
font-size: 0.875rem;
|
|
904
1015
|
line-height: 1.7;
|
|
905
|
-
border: 1px solid #
|
|
1016
|
+
border: 1px solid #2a2520;
|
|
906
1017
|
}
|
|
907
1018
|
|
|
1019
|
+
/* Reset code inside pre — inherits pre's colours */
|
|
908
1020
|
pre code {
|
|
909
1021
|
background: none;
|
|
910
1022
|
padding: 0;
|
|
@@ -912,16 +1024,7 @@ function buildFullFramework() {
|
|
|
912
1024
|
color: inherit;
|
|
913
1025
|
font-size: inherit;
|
|
914
1026
|
font-family: inherit;
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
/* Inline code */
|
|
918
|
-
code {
|
|
919
|
-
font-family: "Menlo", "Monaco", "Courier New", monospace;
|
|
920
|
-
font-size: 0.875em;
|
|
921
|
-
background-color: #2d2d2d;
|
|
922
|
-
color: #d4d4d4;
|
|
923
|
-
padding: 0.125rem 0.375rem;
|
|
924
|
-
border-radius: 4px;
|
|
1027
|
+
display: inline;
|
|
925
1028
|
}
|
|
926
1029
|
${bodyFont}`;
|
|
927
1030
|
|
|
@@ -931,7 +1034,7 @@ ${bodyFont}`;
|
|
|
931
1034
|
css += `@layer theme {\n${variablesCss}}\n\n`;
|
|
932
1035
|
const baseStylesCss = generateBaseStyles(config);
|
|
933
1036
|
css += `@layer base {${baseCss}${baseStylesCss}}\n\n`;
|
|
934
|
-
css += `@layer components {\n
|
|
1037
|
+
css += `@layer components {\n${generatePatternComponents()}}\n\n`;
|
|
935
1038
|
css += `@layer utilities {\n${utilityCss}}\n`;
|
|
936
1039
|
|
|
937
1040
|
// Write output
|