emily-css 1.0.26 → 1.0.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/CHANGELOG.md CHANGED
@@ -4,259 +4,285 @@ All notable changes to `emily-css` are documented here.
4
4
 
5
5
  ---
6
6
 
7
- ## v1.0.26 — May 2026
7
+ ## v1.0.28 — May 2026
8
8
 
9
- **Expand utility coverage and typography scale**
10
-
11
- ### Added
12
- - Expand utility coverage and typography scale
13
-
14
- ---
15
- ## v1.0.26 — May 2026
16
-
17
- **Colour utilities now variable-based; fix grey text on dark surfaces**
18
-
19
- ### Fixed
20
- - `generateColourUtilities` now emits `var(--color-*)` references instead of hardcoded hex values for all `text-*`, `bg-*`, `border-*`, and `accent-*` utilities. Consistent with how semantic colours and ring/fill/stroke utilities already worked. Enables theme-layer overrides without specificity hacks.
21
- - Homepage "How it works" cards: `text-neutral-80` / `text-neutral-40` on `bg-dark` produced near-invisible text in light mode (contrast ~2.3:1). Fixed to `text-neutral-10` / `text-neutral-30` — matches the pattern used by the hero and CTA banner.
22
- - Removed erroneous backslash in `h-2\.5` HTML class strings in `index.vue` (rendered fine via Vue template compilation, but confusing and inconsistent).
23
- - Stripped null bytes from `pages/index.vue` (6) and `package.json` (339) in `emilyui-site`.
9
+ **added new utilities**
24
10
 
25
11
  ### Changed
26
- - `tests/test.js`: updated `accent-brand-80` assertion to expect `var(--color-brand-80)` rather than hardcoded hex, matching the new generation behaviour.
12
+ - added new utilties and tests
27
13
 
28
14
  ---
29
-
30
- ## v1.0.25 — May 2026
31
-
32
- **updaed colour**
33
-
34
- ### Fixed
35
- - change colour on code
36
-
37
- ---
38
- ## v1.0.24 — May 2026
15
+ ## v1.0.27 — May 2026
39
16
 
40
17
  **colour system redesign — brand/accent tokens + semantic colours (v1.0.23)**
41
18
 
42
19
  ### Added
43
20
  - colour system redesign — brand/accent tokens + semantic colours (v1.0.23)
44
21
 
45
- ---
46
- ## v1.0.23 — May 2026
47
-
48
- **Colour system redesign: brand/accent naming + semantic colour tokens**
49
-
50
22
  ### Changed
51
- - Renamed `primary` colour token to `brand`, `secondary` to `accent` across config, utilities, and showcase
52
- - `btn-primary` and `btn-secondary` are now explicit separate colour tokens (no longer aliases)
53
- - Default config generated by `emily-css init` updated to reflect new naming
54
-
55
- ### Added
56
- - `semanticColours` config key: single-value colour tokens with no shade scale
57
- - `generateSemanticColourUtilities()`: generates `bg-dark`, `text-dark`, `border-dark`, `fill-dark` etc.
58
- - Semantic colour utilities automatically pick up `hover:`, `focus:`, `dark:`, and responsive variants
59
- - Default config includes `dark: "#1A1A1A"` and `light: "#FAFAFA"` as semantic colour examples
60
-
61
- ---
62
-
63
- ## v1.0.22 — May 2026
64
-
65
- **· Improve purge extraction and package robustness tests**
66
-
67
- ---
68
- ## v1.0.21 — May 2026
69
-
70
- **"Include bundled showcase template**
71
-
72
- ---
73
- ## v1.0.20 — May 2026
74
-
75
- **replace folder structure template to tempalates**
76
-
77
- ### Fixed
78
- - replace folder structure template to tempalates
79
-
80
- ---
81
- ## v1.0.19 — May 2026
82
-
83
- **Add framework-aware output paths and bundled showcase template**
84
-
85
- ---
86
- ## v1.0.18 — May 2026
87
-
88
- ****
89
-
90
- ### Changed
91
- - added more utitlies as a code block
92
-
93
- ---
94
- ## v1.0.17 — May 2026
95
-
96
- **added new utilties, and added component patterns**
97
-
98
- ### Added
99
- - added new utilties, and added component patterns
100
-
101
- ---
102
- ## v1.0.16 — May 2026
103
-
104
- **feat: add Round 2 utility set — 156/156 tests passing**
105
-
106
- ### Added
107
- - feat: add Round 2 utility set — 156/156 tests passing
108
-
109
- ---
110
- ## v1.0.15 — May 2026
111
-
112
- **updated readme**
113
-
114
- ---
115
- ## v1.0.14 — May 2026
116
-
117
- **updated readme**
118
-
119
- ### Changed
120
- - updated readme
121
-
122
- ---
123
- ## v1.0.13 — May 2026
124
-
125
- **added functionality to commit to github and then push to npm release**
126
-
127
- ### Added
128
- - added functionality to commit to github and then push to npm release
129
-
130
- ---
131
- ## v1.0.12 — May 2026
132
-
133
- ****
134
-
135
- ---
136
- ## v1.0.11 — May 2026
137
-
138
- ****
139
-
140
- ---
141
- ## v1.0.10 — May 2026
142
-
143
- **Changelog automation**
144
-
145
- ### Added
146
- - Release script (`npm run release`) — reads git log, prompts for version bump, writes CHANGELOG.md, commits and tags automatically
147
-
148
- ---
149
-
150
- ## v1.0.9 — May 2026
151
-
152
- **Showcase server + code block utilities**
153
-
154
- ### Added
155
- - Showcase server — run `npm run emily:showcase` to browse components on localhost:3456
156
- - Code block utilities (`.code-window`, `.token-*`) in generated CSS — VSCode Dark+ style syntax highlighting
157
- - `pre`/`code` base styles added to `@layer base`
158
-
159
- ---
160
-
161
- ## v1.0.8 — May 2026
162
-
163
- **Watch mode + build pipeline**
164
-
165
- ### Added
166
- - Watch mode — `npx emily-css watch` rebuilds CSS automatically on config change
167
- - Build + purge can now be chained in a single pipeline
168
- - npm scripts wired into `package.json` during `npx emily-css init`
169
-
170
- ### Changed
171
- - `init.js` provides a more guided setup experience with clearer prompts
172
- - `bin/emilyui.js` updated to correctly route `watch`/`build`/`purge` subcommands
173
-
174
- ---
175
-
176
- ## v1.0.7 — April 2026
177
-
178
- **Purge regex fix**
179
-
180
- ### Fixed
181
- - Purge crashed when class names contained regex metacharacters (`[`, `]`, `(`, `)`, `{`, `}`, etc). Previously only `:` and `.` were escaped — now uses full metacharacter escaping
182
-
183
- ---
184
-
185
- ## v1.0.6 — April 2026
186
-
187
- **Dist + font cleanup**
188
-
189
- ### Changed
190
- - `dist/` and `fonts/` directories removed from git tracking
191
-
192
- ### Breaking changes
193
- - If you were pulling `dist/emily.css` directly from GitHub, run `npx emily-css build` locally instead
194
-
195
- ---
196
-
197
- ## v1.0.5 — April 2026
198
-
199
- **Split font system**
200
-
201
- ### Added
202
- - Separate heading and body font config — `fontFamily.heading` and `fontFamily.body` are now independent
203
- - Google Fonts CDN integration — fonts loaded via `@import` in generated CSS
204
- - Font-specific tests added to test suite
205
-
206
- ### Changed
207
- - Bundled font files removed from the npm package
208
- - `emily.config.json` updated with split `fontFamily` structure
209
-
210
- ### Breaking changes
211
- - If your config has `fontFamily` as a single string, update it to `{ "heading": "...", "body": "..." }`
212
-
213
- ---
214
-
215
- ## v1.0.3 — April 2026
216
-
217
- **Package size reduction**
218
-
219
- ### Changed
220
- - Demo CSS removed from the published npm package
221
-
222
- ---
223
-
224
- ## v1.0.2 — April 2026
225
-
226
- **Package distribution fix**
227
-
228
- ### Added
229
- - `.npmignore` added — controls which files are included in the published package
230
- - `dist` files explicitly declared in `package.json` `files` array
231
-
232
- ### Fixed
233
- - Unnecessary files (tests, src, docs) were being included in the npm tarball
234
-
235
- ---
236
-
237
- ## v1.0.1 — April 2026
238
-
239
- **CLI rename fix**
240
-
241
- ### Fixed
242
- - Internal references to old `emily-ui` CLI name updated to `emily-css`
243
-
244
- ### Breaking changes
245
- - If you had scripts referencing `npx emily-ui`, update them to `npx emily-css`
246
-
247
- ---
248
-
249
- ## v1.0.0 — April 2026
250
-
251
- **Initial release**
252
-
253
- ### Added
254
- - 11,844 utility classes generated from `emily.config.json`
255
- - OKLCH colour scale generation — one hex in, 10-shade scale out
256
- - Responsive variants (`sm:` `md:` `lg:` `xl:` `2xl:`)
257
- - State variants (`hover:` `focus-visible:` `active:` `disabled:` `dark:`)
258
- - Purge system — strips unused classes, ~97% file size reduction
259
- - Interactive setup wizard (`npx emily-css init`)
260
- - 72 tests, all passing
23
+ - updated utilties and new showcase
261
24
 
262
25
  ---
26
+ # Changelog
27
+
28
+ All notable changes to `emily-css` are documented here.
29
+
30
+ ---
31
+
32
+ ## v1.0.26 — May 2026
33
+
34
+ **Expand utility coverage and typography scale**
35
+
36
+ ### Added
37
+ - Expand utility coverage and typography scale
38
+
39
+ ---
40
+ ## v1.0.26 — May 2026
41
+
42
+ **Colour utilities now variable-based; fix grey text on dark surfaces**
43
+
44
+ ### Fixed
45
+ - `generateColourUtilities` now emits `var(--color-*)` references instead of hardcoded hex values for all `text-*`, `bg-*`, `border-*`, and `accent-*` utilities. Consistent with how semantic colours and ring/fill/stroke utilities already worked. Enables theme-layer overrides without specificity hacks.
46
+ - Homepage "How it works" cards: `text-neutral-80` / `text-neutral-40` on `bg-dark` produced near-invisible text in light mode (contrast ~2.3:1). Fixed to `text-neutral-10` / `text-neutral-30` — matches the pattern used by the hero and CTA banner.
47
+ - Removed erroneous backslash in `h-2\.5` HTML class strings in `index.vue` (rendered fine via Vue template compilation, but confusing and inconsistent).
48
+ - Stripped null bytes from `pages/index.vue` (6) and `package.json` (339) in `emilyui-site`.
49
+
50
+ ### Changed
51
+ - `tests/test.js`: updated `accent-brand-80` assertion to expect `var(--color-brand-80)` rather than hardcoded hex, matching the new generation behaviour.
52
+
53
+ ---
54
+
55
+ ## v1.0.25 — May 2026
56
+
57
+ **updaed colour**
58
+
59
+ ### Fixed
60
+ - change colour on code
61
+
62
+ ---
63
+ ## v1.0.24 — May 2026
64
+
65
+ **colour system redesign — brand/accent tokens + semantic colours (v1.0.23)**
66
+
67
+ ### Added
68
+ - colour system redesign — brand/accent tokens + semantic colours (v1.0.23)
69
+
70
+ ---
71
+ ## v1.0.23 — May 2026
72
+
73
+ **Colour system redesign: brand/accent naming + semantic colour tokens**
74
+
75
+ ### Changed
76
+ - Renamed `primary` colour token to `brand`, `secondary` to `accent` across config, utilities, and showcase
77
+ - `btn-primary` and `btn-secondary` are now explicit separate colour tokens (no longer aliases)
78
+ - Default config generated by `emily-css init` updated to reflect new naming
79
+
80
+ ### Added
81
+ - `semanticColours` config key: single-value colour tokens with no shade scale
82
+ - `generateSemanticColourUtilities()`: generates `bg-dark`, `text-dark`, `border-dark`, `fill-dark` etc.
83
+ - Semantic colour utilities automatically pick up `hover:`, `focus:`, `dark:`, and responsive variants
84
+ - Default config includes `dark: "#1A1A1A"` and `light: "#FAFAFA"` as semantic colour examples
85
+
86
+ ---
87
+
88
+
89
+ ## v1.0.22 — May 2026
90
+
91
+ **· Improve purge extraction and package robustness tests**
92
+
93
+ ---
94
+ ## v1.0.21 — May 2026
95
+
96
+ **"Include bundled showcase template**
97
+
98
+ ---
99
+ ## v1.0.20 — May 2026
100
+
101
+ **replace folder structure template to tempalates**
102
+
103
+ ### Fixed
104
+ - replace folder structure template to tempalates
105
+
106
+ ---
107
+ ## v1.0.19 — May 2026
108
+
109
+ **Add framework-aware output paths and bundled showcase template**
110
+
111
+ ---
112
+ ## v1.0.18 — May 2026
113
+
114
+ ****
115
+
116
+ ### Changed
117
+ - added more utitlies as a code block
118
+
119
+ ---
120
+ ## v1.0.17 — May 2026
121
+
122
+ **added new utilties, and added component patterns**
123
+
124
+ ### Added
125
+ - added new utilties, and added component patterns
126
+
127
+ ---
128
+ ## v1.0.16 — May 2026
129
+
130
+ **feat: add Round 2 utility set — 156/156 tests passing**
131
+
132
+ ### Added
133
+ - feat: add Round 2 utility set — 156/156 tests passing
134
+
135
+ ---
136
+ ## v1.0.15 — May 2026
137
+
138
+ **updated readme**
139
+
140
+ ---
141
+ ## v1.0.14 — May 2026
142
+
143
+ **updated readme**
144
+
145
+ ### Changed
146
+ - updated readme
147
+
148
+ ---
149
+ ## v1.0.13 — May 2026
150
+
151
+ **added functionality to commit to github and then push to npm release**
152
+
153
+ ### Added
154
+ - added functionality to commit to github and then push to npm release
155
+
156
+ ---
157
+ ## v1.0.12 — May 2026
158
+
159
+ ****
160
+
161
+ ---
162
+ ## v1.0.11 — May 2026
163
+
164
+ ****
165
+
166
+ ---
167
+ ## v1.0.10 — May 2026
168
+
169
+ **Changelog automation**
170
+
171
+ ### Added
172
+ - Release script (`npm run release`) — reads git log, prompts for version bump, writes CHANGELOG.md, commits and tags automatically
173
+
174
+ ---
175
+
176
+ ## v1.0.9 — May 2026
177
+
178
+ **Showcase server + code block utilities**
179
+
180
+ ### Added
181
+ - Showcase server — run `npm run emily:showcase` to browse components on localhost:3456
182
+ - Code block utilities (`.code-window`, `.token-*`) in generated CSS — VSCode Dark+ style syntax highlighting
183
+ - `pre`/`code` base styles added to `@layer base`
184
+
185
+ ---
186
+
187
+ ## v1.0.8 — May 2026
188
+
189
+ **Watch mode + build pipeline**
190
+
191
+ ### Added
192
+ - Watch mode — `npx emily-css watch` rebuilds CSS automatically on config change
193
+ - Build + purge can now be chained in a single pipeline
194
+ - npm scripts wired into `package.json` during `npx emily-css init`
195
+
196
+ ### Changed
197
+ - `init.js` provides a more guided setup experience with clearer prompts
198
+ - `bin/emilyui.js` updated to correctly route `watch`/`build`/`purge` subcommands
199
+
200
+ ---
201
+
202
+ ## v1.0.7 — April 2026
203
+
204
+ **Purge regex fix**
205
+
206
+ ### Fixed
207
+ - Purge crashed when class names contained regex metacharacters (`[`, `]`, `(`, `)`, `{`, `}`, etc). Previously only `:` and `.` were escaped — now uses full metacharacter escaping
208
+
209
+ ---
210
+
211
+ ## v1.0.6 — April 2026
212
+
213
+ **Dist + font cleanup**
214
+
215
+ ### Changed
216
+ - `dist/` and `fonts/` directories removed from git tracking
217
+
218
+ ### Breaking changes
219
+ - If you were pulling `dist/emily.css` directly from GitHub, run `npx emily-css build` locally instead
220
+
221
+ ---
222
+
223
+ ## v1.0.5 — April 2026
224
+
225
+ **Split font system**
226
+
227
+ ### Added
228
+ - Separate heading and body font config — `fontFamily.heading` and `fontFamily.body` are now independent
229
+ - Google Fonts CDN integration — fonts loaded via `@import` in generated CSS
230
+ - Font-specific tests added to test suite
231
+
232
+ ### Changed
233
+ - Bundled font files removed from the npm package
234
+ - `emily.config.json` updated with split `fontFamily` structure
235
+
236
+ ### Breaking changes
237
+ - If your config has `fontFamily` as a single string, update it to `{ "heading": "...", "body": "..." }`
238
+
239
+ ---
240
+
241
+ ## v1.0.3 — April 2026
242
+
243
+ **Package size reduction**
244
+
245
+ ### Changed
246
+ - Demo CSS removed from the published npm package
247
+
248
+ ---
249
+
250
+ ## v1.0.2 — April 2026
251
+
252
+ **Package distribution fix**
253
+
254
+ ### Added
255
+ - `.npmignore` added — controls which files are included in the published package
256
+ - `dist` files explicitly declared in `package.json` `files` array
257
+
258
+ ### Fixed
259
+ - Unnecessary files (tests, src, docs) were being included in the npm tarball
260
+
261
+ ---
262
+
263
+ ## v1.0.1 — April 2026
264
+
265
+ **CLI rename fix**
266
+
267
+ ### Fixed
268
+ - Internal references to old `emily-ui` CLI name updated to `emily-css`
269
+
270
+ ### Breaking changes
271
+ - If you had scripts referencing `npx emily-ui`, update them to `npx emily-css`
272
+
273
+ ---
274
+
275
+ ## v1.0.0 — April 2026
276
+
277
+ **Initial release**
278
+
279
+ ### Added
280
+ - 11,844 utility classes generated from `emily.config.json`
281
+ - OKLCH colour scale generation — one hex in, 10-shade scale out
282
+ - Responsive variants (`sm:` `md:` `lg:` `xl:` `2xl:`)
283
+ - State variants (`hover:` `focus-visible:` `active:` `disabled:` `dark:`)
284
+ - Purge system — strips unused classes, ~97% file size reduction
285
+ - Interactive setup wizard (`npx emily-css init`)
286
+ - 72 tests, all passing
287
+
288
+ ---
package/package.json CHANGED
@@ -1,59 +1,59 @@
1
- {
2
- "name": "emily-css",
3
- "version": "1.0.26",
4
- "description": "A config-driven utility CSS framework. Define your brand once, generate the CSS.",
5
- "main": "src/index.js",
6
- "bin": {
7
- "emily-css": "bin/emilyui.js"
8
- },
9
- "files": [
10
- "bin/",
11
- "src/",
12
- "templates/",
13
- "README.md",
14
- "LICENSE",
15
- "CHANGELOG.md"
16
- ],
17
- "scripts": {
18
- "build": "node src/index.js",
19
- "dev": "nodemon src/index.js",
20
- "dev:full": "nodemon src/index.js -- --keep-full",
21
- "init": "node src/init.js",
22
- "test": "node tests/test.js",
23
- "emily:showcase": "node src/showcase.js",
24
- "commit": "node scripts/commit.js",
25
- "release": "node scripts/release.js",
26
- "ship": "node scripts/ship.js",
27
- "emily:build": "emily-css build",
28
- "emily:watch": "emily-css watch",
29
- "emily:help": "emily-css help"
30
- },
31
- "keywords": [
32
- "css",
33
- "design-system",
34
- "components",
35
- "config-driven",
36
- "utility-css",
37
- "accessibility",
38
- "drupal",
39
- "legacy",
40
- "no-build-step"
41
- ],
42
- "author": "Andy Terry",
43
- "license": "MIT",
44
- "engines": {
45
- "node": ">=16.0.0"
46
- },
47
- "devDependencies": {
48
- "nodemon": "^3.0.0"
49
- },
50
- "dependencies": {
51
- "boxen": "^5.1.2",
52
- "chalk": "^4.1.2",
53
- "chokidar": "^5.0.0",
54
- "cross-spawn": "^7.0.6",
55
- "enquirer": "^2.4.1",
56
- "fast-glob": "^3.3.3",
57
- "ora": "^5.4.1"
58
- }
59
- }
1
+ {
2
+ "name": "emily-css",
3
+ "version": "1.0.28",
4
+ "description": "A config-driven utility CSS framework. Define your brand once, generate the CSS.",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "emily-css": "bin/emilyui.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "src/",
12
+ "templates/",
13
+ "README.md",
14
+ "LICENSE",
15
+ "CHANGELOG.md"
16
+ ],
17
+ "scripts": {
18
+ "build": "node src/index.js",
19
+ "dev": "nodemon src/index.js",
20
+ "dev:full": "nodemon src/index.js -- --keep-full",
21
+ "init": "node src/init.js",
22
+ "test": "node tests/test.js",
23
+ "emily:showcase": "node src/showcase.js",
24
+ "commit": "node scripts/commit.js",
25
+ "release": "node scripts/release.js",
26
+ "ship": "node scripts/ship.js",
27
+ "emily:build": "emily-css build",
28
+ "emily:watch": "emily-css watch",
29
+ "emily:help": "emily-css help"
30
+ },
31
+ "keywords": [
32
+ "css",
33
+ "design-system",
34
+ "components",
35
+ "config-driven",
36
+ "utility-css",
37
+ "accessibility",
38
+ "drupal",
39
+ "legacy",
40
+ "no-build-step"
41
+ ],
42
+ "author": "Andy Terry",
43
+ "license": "MIT",
44
+ "engines": {
45
+ "node": ">=16.0.0"
46
+ },
47
+ "devDependencies": {
48
+ "nodemon": "^3.0.0"
49
+ },
50
+ "dependencies": {
51
+ "boxen": "^5.1.2",
52
+ "chalk": "^4.1.2",
53
+ "chokidar": "^5.0.0",
54
+ "cross-spawn": "^7.0.6",
55
+ "enquirer": "^2.4.1",
56
+ "fast-glob": "^3.3.3",
57
+ "ora": "^5.4.1"
58
+ }
59
+ }
package/src/generators.js CHANGED
@@ -424,6 +424,12 @@ function svgUtilities(colours) {
424
424
 
425
425
  css += `.fill-current { fill: currentColor; }\n`;
426
426
  css += `.stroke-current { stroke: currentColor; }\n`;
427
+ css += `.fill-white { fill: #FAFAFA; }\n`;
428
+ css += `.fill-black { fill: #111110; }\n`;
429
+ css += `.fill-transparent { fill: transparent; }\n`;
430
+ css += `.stroke-white { stroke: #FAFAFA; }\n`;
431
+ css += `.stroke-black { stroke: #111110; }\n`;
432
+ css += `.stroke-transparent { stroke: transparent; }\n`;
427
433
  css += `.stroke-0 { stroke-width: 0; }\n`;
428
434
  css += `.stroke-1 { stroke-width: 1; }\n`;
429
435
  css += `.stroke-2 { stroke-width: 2; }\n`;
@@ -602,6 +608,10 @@ function accessibilityUtilities() {
602
608
  return `/* Accessibility */
603
609
  .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border-width: 0; }
604
610
  .not-sr-only { position: static; width: auto; height: auto; padding: 0; margin: 0; overflow: visible; clip: auto; white-space: normal; }
611
+ .sr-only-focusable:not(:focus):not(:focus-within) { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border-width: 0; }
612
+ .focus-ring:focus-visible { outline: 2px solid var(--color-brand-80); outline-offset: 2px; }
613
+ .focus-ring-inset:focus-visible { outline: 2px solid var(--color-brand-80); outline-offset: -2px; }
614
+ .focus-ring-none:focus-visible { outline: none; }
605
615
  .focus-visible:focus { outline: 2px solid currentColor; outline-offset: 2px; }
606
616
  .focus\\:outline-none:focus { outline: 2px solid transparent; outline-offset: 2px; }
607
617
 
@@ -756,8 +766,8 @@ function divideUtilities(spacing, colours) {
756
766
  css += `.divide-${colourName}-${shade} > * + * { border-color: var(--color-${colourName}-${shade}); }\n`;
757
767
  });
758
768
  });
759
- css += `.divide-white > * + * { border-color: #ffffff; }\n`;
760
- css += `.divide-black > * + * { border-color: #000000; }\n`;
769
+ css += `.divide-white > * + * { border-color: #FAFAFA; }\n`;
770
+ css += `.divide-black > * + * { border-color: #111110; }\n`;
761
771
  css += `.divide-transparent > * + * { border-color: transparent; }\n`;
762
772
  css += `\n`;
763
773
  return css;
package/src/index.js CHANGED
@@ -693,8 +693,8 @@ function generateBorderUtilities(config) {
693
693
  css += `.border-none { border-style: none; }\n`;
694
694
  css += `.border-transparent { border-color: transparent; }\n`;
695
695
  css += `.border-current { border-color: currentColor; }\n`;
696
- css += `.border-black { border-color: #000000; }\n`;
697
- css += `.border-white { border-color: #ffffff; }\n`;
696
+ css += `.border-black { border-color: #111110; }\n`;
697
+ css += `.border-white { border-color: #FAFAFA; }\n`;
698
698
 
699
699
  const baseRadius = config.spacing.borderRadius['base'] || '8px';
700
700
  css += `.rounded { border-radius: ${baseRadius}; }\n`;
@@ -791,9 +791,15 @@ function generateColourUtilities(colours) {
791
791
  });
792
792
  });
793
793
 
794
- css += `.bg-white { background-color: #ffffff; }\n`;
794
+ css += `.bg-white { background-color: #FAFAFA; }\n`;
795
+ css += `.bg-black { background-color: #111110; }\n`;
795
796
  css += `.bg-transparent { background-color: transparent; }\n`;
796
- css += `.text-white { color: #ffffff; }\n`;
797
+ css += `.bg-current { background-color: currentColor; }\n`;
798
+
799
+ css += `.text-white { color: #FAFAFA; }\n`;
800
+ css += `.text-black { color: #111110; }\n`;
801
+ css += `.text-transparent { color: transparent; }\n`;
802
+ css += `.text-current { color: currentColor; }\n`;
797
803
 
798
804
  css += `\n`;
799
805
  return css;
@@ -812,6 +818,63 @@ function generateSemanticColourUtilities(semanticColours) {
812
818
  return css;
813
819
  }
814
820
 
821
+ // ============================================================================
822
+ // ARIA & DATA-STATE VARIANTS
823
+ // ============================================================================
824
+ // Generates ARIA attribute and data-state variants for all utility classes.
825
+ // Selectors target the attribute value directly so they work without JS —
826
+ // just toggle the attribute and the utility activates.
827
+ //
828
+ // Usage in HTML:
829
+ // aria-expanded: class="aria-expanded:block" aria-expanded="true"
830
+ // data-open: class="data-open:flex" data-state="open"
831
+ //
832
+ // Output examples:
833
+ // .aria-expanded\:block[aria-expanded="true"] { display: block; }
834
+ // .data-open\:flex[data-state="open"] { display: flex; }
835
+
836
+ function addAriaDataVariants(css) {
837
+ const variants = [
838
+ { name: 'aria-expanded', selector: '[aria-expanded="true"]' },
839
+ { name: 'aria-selected', selector: '[aria-selected="true"]' },
840
+ { name: 'aria-current', selector: '[aria-current="page"]' },
841
+ { name: 'aria-disabled', selector: '[aria-disabled="true"]' },
842
+ { name: 'data-open', selector: '[data-state="open"]' },
843
+ { name: 'data-closed', selector: '[data-state="closed"]' },
844
+ ];
845
+
846
+ let variantCss = css;
847
+
848
+ variants.forEach(variant => {
849
+ let variantRules = '';
850
+ const lines = css.split('\n');
851
+
852
+ lines.forEach(line => {
853
+ if (line.startsWith('.') && line.includes('{')) {
854
+ const className = line.split('{')[0].trim();
855
+ // Skip already-variant lines (contain ':' in class name) and special selectors
856
+ if (
857
+ !className.startsWith(':root') &&
858
+ !className.includes('@') &&
859
+ !className.includes('::') &&
860
+ !className.includes(':')
861
+ ) {
862
+ const classWithoutDot = className.substring(1);
863
+ const ariaSelector = `.${variant.name}\\:${classWithoutDot}${variant.selector}`;
864
+ const ariaRule = line.replace(className, ariaSelector);
865
+ variantRules += ariaRule + '\n';
866
+ }
867
+ }
868
+ });
869
+
870
+ if (variantRules) {
871
+ variantCss += `\n/* ARIA/data-state variant: ${variant.name} */\n` + variantRules;
872
+ }
873
+ });
874
+
875
+ return variantCss;
876
+ }
877
+
815
878
  // ============================================================================
816
879
  // DARK MODE VARIANTS
817
880
  // ============================================================================
@@ -983,6 +1046,65 @@ function generatePatternComponents() {
983
1046
  margin-inline: auto;
984
1047
  }
985
1048
 
1049
+ .prose-emily {
1050
+ max-width: 65ch;
1051
+ margin-inline: auto;
1052
+ }
1053
+
1054
+ .prose-emily > * + * {
1055
+ margin-top: var(--space-4, 1rem);
1056
+ }
1057
+
1058
+ .prose-emily h2,
1059
+ .prose-emily h3 {
1060
+ font-family: inherit;
1061
+ color: var(--color-neutral-90);
1062
+ line-height: 1.25;
1063
+ }
1064
+
1065
+ .prose-emily h2 {
1066
+ font-size: var(--text-2xl, 24px);
1067
+ margin-top: var(--space-10, 2.5rem);
1068
+ }
1069
+
1070
+ .prose-emily h3 {
1071
+ font-size: var(--text-xl, 20px);
1072
+ margin-top: var(--space-8, 2rem);
1073
+ }
1074
+
1075
+ .prose-emily p,
1076
+ .prose-emily li {
1077
+ color: var(--color-neutral-70);
1078
+ line-height: 1.75;
1079
+ }
1080
+
1081
+ .prose-emily ul,
1082
+ .prose-emily ol {
1083
+ padding-left: var(--space-6, 1.5rem);
1084
+ }
1085
+
1086
+ .prose-emily ul {
1087
+ list-style-type: disc;
1088
+ }
1089
+
1090
+ .prose-emily ol {
1091
+ list-style-type: decimal;
1092
+ }
1093
+
1094
+ .prose-emily a {
1095
+ color: var(--color-brand-80);
1096
+ text-decoration: underline;
1097
+ text-underline-offset: 2px;
1098
+ }
1099
+
1100
+ .prose-emily code {
1101
+ font-size: var(--text-sm, 14px);
1102
+ background-color: var(--color-neutral-10);
1103
+ border: 1px solid var(--color-neutral-20);
1104
+ border-radius: var(--space-1, 0.25rem);
1105
+ padding: 0.125rem 0.375rem;
1106
+ }
1107
+
986
1108
  /* ---- Composition ---- */
987
1109
 
988
1110
  /* Vertical stack with consistent gap — replaces manual margin chains */
@@ -999,6 +1121,330 @@ function generatePatternComponents() {
999
1121
  gap: var(--space-4, 1rem);
1000
1122
  align-items: center;
1001
1123
  }
1124
+
1125
+ /* ---- Layout ---- */
1126
+
1127
+ /* Constrained width container — 1100px max, full-width on small screens */
1128
+ .width-container {
1129
+ width: 100%;
1130
+ max-width: 1100px;
1131
+ margin-inline: auto;
1132
+ padding-inline: var(--space-4, 1rem);
1133
+ }
1134
+
1135
+ @media (min-width: 640px) {
1136
+ .width-container {
1137
+ padding-inline: var(--space-6, 1.5rem);
1138
+ }
1139
+ }
1140
+
1141
+ @media (min-width: 1024px) {
1142
+ .width-container {
1143
+ padding-inline: var(--space-8, 2rem);
1144
+ }
1145
+ }
1146
+
1147
+ @media (min-width: 1140px) {
1148
+ .width-container {
1149
+ padding-inline: 0;
1150
+ }
1151
+ }
1152
+
1153
+ /* ---- Forms ---- */
1154
+
1155
+ .field-container {
1156
+ display: flex;
1157
+ flex-direction: column;
1158
+ gap: var(--space-2, 0.5rem);
1159
+ margin-bottom: var(--space-6, 1.5rem);
1160
+ }
1161
+
1162
+ .field-container label {
1163
+ display: block;
1164
+ font-weight: var(--font-weight-semibold, 600);
1165
+ color: var(--color-neutral-90);
1166
+ font-size: var(--text-base, 16px);
1167
+ line-height: 1.4;
1168
+ margin-bottom: var(--space-1, 0.25rem);
1169
+ }
1170
+
1171
+ fieldset {
1172
+ border: none;
1173
+ padding: 0;
1174
+ margin: 0 0 var(--space-6, 1.5rem);
1175
+ }
1176
+
1177
+ fieldset legend {
1178
+ display: block;
1179
+ font-size: var(--text-lg, 18px);
1180
+ font-weight: var(--font-weight-semibold, 600);
1181
+ margin-bottom: var(--space-3, 0.75rem);
1182
+ color: var(--color-neutral-90);
1183
+ padding: 0;
1184
+ }
1185
+
1186
+ .form-hint {
1187
+ font-size: var(--text-sm, 14px);
1188
+ color: var(--color-neutral-60);
1189
+ margin-bottom: var(--space-1, 0.25rem);
1190
+ }
1191
+
1192
+ input[type="text"],
1193
+ input[type="email"],
1194
+ input[type="password"],
1195
+ input[type="number"],
1196
+ input[type="tel"],
1197
+ input[type="url"],
1198
+ input[type="search"],
1199
+ input[type="date"],
1200
+ select,
1201
+ textarea {
1202
+ width: 100%;
1203
+ max-width: 100%;
1204
+ padding: var(--space-3, 0.75rem) var(--space-4, 1rem);
1205
+ border: 2px solid var(--color-neutral-30);
1206
+ border-radius: 8px;
1207
+ background-color: #ffffff;
1208
+ color: var(--color-neutral-90);
1209
+ font-family: inherit;
1210
+ font-size: var(--text-base, 16px);
1211
+ line-height: var(--leading-base, 1.6);
1212
+ appearance: none;
1213
+ transition: border-color 200ms ease, box-shadow 200ms ease;
1214
+ }
1215
+
1216
+ select {
1217
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
1218
+ background-position: right var(--space-2, 0.5rem) center;
1219
+ background-repeat: no-repeat;
1220
+ background-size: 1.5em 1.5em;
1221
+ padding-right: var(--space-10, 2.5rem);
1222
+ cursor: pointer;
1223
+ }
1224
+
1225
+ textarea {
1226
+ min-height: 120px;
1227
+ resize: vertical;
1228
+ }
1229
+
1230
+ input[type="text"]:focus,
1231
+ input[type="email"]:focus,
1232
+ input[type="password"]:focus,
1233
+ input[type="number"]:focus,
1234
+ input[type="tel"]:focus,
1235
+ input[type="url"]:focus,
1236
+ input[type="search"]:focus,
1237
+ input[type="date"]:focus,
1238
+ select:focus,
1239
+ textarea:focus {
1240
+ outline: 2px solid var(--color-neutral-80);
1241
+ outline-offset: 3px;
1242
+ border-color: var(--color-neutral-80);
1243
+ box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
1244
+ }
1245
+
1246
+ .checkbox-group,
1247
+ .radio-group {
1248
+ display: flex;
1249
+ align-items: center;
1250
+ gap: var(--space-3, 0.75rem);
1251
+ margin-bottom: var(--space-4, 1rem);
1252
+ }
1253
+
1254
+ .checkbox-group label,
1255
+ .radio-group label {
1256
+ font-weight: var(--font-weight-normal, 400);
1257
+ margin-bottom: 0;
1258
+ cursor: pointer;
1259
+ font-size: var(--text-base, 16px);
1260
+ }
1261
+
1262
+ input[type="checkbox"] {
1263
+ width: 1.5rem;
1264
+ height: 1.5rem;
1265
+ margin: 0;
1266
+ cursor: pointer;
1267
+ accent-color: var(--color-brand-80);
1268
+ flex-shrink: 0;
1269
+ }
1270
+
1271
+ input[type="checkbox"]:focus {
1272
+ outline: 2px solid var(--color-neutral-80);
1273
+ outline-offset: 3px;
1274
+ box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
1275
+ }
1276
+
1277
+ input[type="radio"] {
1278
+ width: 1.5rem;
1279
+ height: 1.5rem;
1280
+ margin: 0;
1281
+ border-radius: 50%;
1282
+ appearance: none;
1283
+ background-color: #ffffff;
1284
+ border: 2px solid var(--color-neutral-30);
1285
+ display: grid;
1286
+ place-content: center;
1287
+ cursor: pointer;
1288
+ flex-shrink: 0;
1289
+ transition: background-color 200ms ease, border-color 200ms ease;
1290
+ }
1291
+
1292
+ input[type="radio"]::before {
1293
+ content: "";
1294
+ width: 0.75rem;
1295
+ height: 0.75rem;
1296
+ border-radius: 50%;
1297
+ transform: scale(0);
1298
+ transition: 120ms transform ease-in-out;
1299
+ background-color: var(--color-brand-80);
1300
+ }
1301
+
1302
+ input[type="radio"]:checked {
1303
+ border-color: var(--color-brand-80);
1304
+ }
1305
+
1306
+ input[type="radio"]:checked::before {
1307
+ transform: scale(1);
1308
+ }
1309
+
1310
+ input[type="radio"]:hover {
1311
+ background-color: var(--color-brand-10);
1312
+ border-color: var(--color-brand-80);
1313
+ }
1314
+
1315
+ input[type="radio"]:focus {
1316
+ outline: 2px solid var(--color-neutral-80);
1317
+ outline-offset: 3px;
1318
+ border-radius: 50%;
1319
+ box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
1320
+ }
1321
+
1322
+ input[aria-invalid="true"] {
1323
+ border-color: var(--color-error-80) !important;
1324
+ border-width: 3px;
1325
+ }
1326
+
1327
+ .form-error-message {
1328
+ font-size: var(--text-sm, 14px);
1329
+ font-weight: var(--font-weight-bold, 700);
1330
+ color: var(--color-error-80);
1331
+ margin-top: var(--space-1, 0.25rem);
1332
+ display: block;
1333
+ }
1334
+
1335
+ .error-summary {
1336
+ border: 4px solid var(--color-error-80);
1337
+ padding: var(--space-6, 1.5rem);
1338
+ margin-bottom: var(--space-8, 2rem);
1339
+ border-radius: 8px;
1340
+ }
1341
+
1342
+ .error-summary ul {
1343
+ list-style: disc;
1344
+ padding-left: var(--space-5, 1.25rem);
1345
+ }
1346
+
1347
+ .error-summary a {
1348
+ color: var(--color-error-80);
1349
+ }
1350
+
1351
+ /* ---- Buttons ---- */
1352
+
1353
+ .btn {
1354
+ display: inline-flex;
1355
+ align-items: center;
1356
+ justify-content: center;
1357
+ padding: var(--space-3, 0.75rem) var(--space-6, 1.5rem);
1358
+ font-weight: var(--font-weight-semibold, 600);
1359
+ border-radius: 8px;
1360
+ cursor: pointer;
1361
+ transition: background-color 200ms ease, border-color 200ms ease, color 200ms ease;
1362
+ border: 2px solid transparent;
1363
+ text-align: center;
1364
+ min-height: 3rem;
1365
+ font-size: var(--text-base, 16px);
1366
+ text-decoration: none;
1367
+ font-family: inherit;
1368
+ line-height: 1;
1369
+ }
1370
+
1371
+ .btn-primary {
1372
+ background-color: var(--color-brand-80);
1373
+ color: #ffffff;
1374
+ border-color: transparent;
1375
+ }
1376
+
1377
+ .btn-primary:hover {
1378
+ background-color: var(--color-brand-90);
1379
+ }
1380
+
1381
+ .btn-primary:focus-visible {
1382
+ outline: 2px solid var(--color-neutral-80);
1383
+ outline-offset: 3px;
1384
+ box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
1385
+ }
1386
+
1387
+ .btn-secondary {
1388
+ background-color: #ffffff;
1389
+ color: var(--color-accent-80);
1390
+ border-color: var(--color-accent-80);
1391
+ }
1392
+
1393
+ .btn-secondary:hover {
1394
+ background-color: var(--color-accent-10);
1395
+ color: var(--color-accent-90);
1396
+ border-color: var(--color-accent-90);
1397
+ }
1398
+
1399
+ .btn-secondary:focus-visible {
1400
+ outline: 2px solid var(--color-neutral-80);
1401
+ outline-offset: 3px;
1402
+ box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
1403
+ }
1404
+
1405
+ .btn-ghost {
1406
+ background-color: transparent;
1407
+ color: var(--color-neutral-80);
1408
+ border-color: transparent;
1409
+ }
1410
+
1411
+ .btn-ghost:hover {
1412
+ background-color: var(--color-neutral-10);
1413
+ }
1414
+
1415
+ .btn-ghost:focus-visible {
1416
+ outline: 2px solid var(--color-neutral-80);
1417
+ outline-offset: 3px;
1418
+ box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
1419
+ }
1420
+
1421
+ .btn-danger {
1422
+ background-color: var(--color-error-80);
1423
+ color: #ffffff;
1424
+ border-color: transparent;
1425
+ }
1426
+
1427
+ .btn-danger:hover {
1428
+ background-color: var(--color-error-90);
1429
+ }
1430
+
1431
+ .btn-danger:focus-visible {
1432
+ outline: 2px solid var(--color-neutral-80);
1433
+ outline-offset: 3px;
1434
+ box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
1435
+ }
1436
+
1437
+ .btn-sm {
1438
+ padding: var(--space-2, 0.5rem) var(--space-4, 1rem);
1439
+ font-size: var(--text-sm, 14px);
1440
+ min-height: 2.25rem;
1441
+ }
1442
+
1443
+ .btn-lg {
1444
+ padding: var(--space-4, 1rem) var(--space-8, 2rem);
1445
+ font-size: var(--text-lg, 18px);
1446
+ min-height: 3.5rem;
1447
+ }
1002
1448
  `;
1003
1449
  }
1004
1450
 
@@ -1097,6 +1543,7 @@ function buildFullFramework() {
1097
1543
  utilityCss += filterUtilities();
1098
1544
 
1099
1545
  utilityCss = addStateVariants(utilityCss);
1546
+ utilityCss = addAriaDataVariants(utilityCss);
1100
1547
  utilityCss = addDarkModeVariants(utilityCss);
1101
1548
  utilityCss = addResponsiveVariants(utilityCss, config);
1102
1549
 
@@ -1145,6 +1592,59 @@ function buildFullFramework() {
1145
1592
  overflow-wrap: break-word;
1146
1593
  }
1147
1594
 
1595
+ /* Base heading scale */
1596
+ h1 {
1597
+ font-size: var(--text-4xl, 36px);
1598
+ line-height: var(--leading-4xl, 1.3);
1599
+ font-weight: var(--font-weight-bold, 700);
1600
+ margin-bottom: var(--space-6, 1.5rem);
1601
+ }
1602
+
1603
+ h2 {
1604
+ font-size: var(--text-3xl, 30px);
1605
+ line-height: var(--leading-3xl, 1.4);
1606
+ font-weight: var(--font-weight-bold, 700);
1607
+ margin-bottom: var(--space-5, 1.25rem);
1608
+ }
1609
+
1610
+ h3 {
1611
+ font-size: var(--text-2xl, 24px);
1612
+ line-height: var(--leading-2xl, 1.4);
1613
+ font-weight: var(--font-weight-semibold, 600);
1614
+ margin-bottom: var(--space-4, 1rem);
1615
+ }
1616
+
1617
+ h4 {
1618
+ font-size: var(--text-xl, 20px);
1619
+ line-height: var(--leading-xl, 1.6);
1620
+ font-weight: var(--font-weight-semibold, 600);
1621
+ margin-bottom: var(--space-3, 0.75rem);
1622
+ }
1623
+
1624
+ h5 {
1625
+ font-size: var(--text-lg, 18px);
1626
+ line-height: var(--leading-lg, 1.6);
1627
+ font-weight: var(--font-weight-medium, 500);
1628
+ margin-bottom: var(--space-3, 0.75rem);
1629
+ }
1630
+
1631
+ h6 {
1632
+ font-size: var(--text-base, 16px);
1633
+ line-height: var(--leading-base, 1.6);
1634
+ font-weight: var(--font-weight-medium, 500);
1635
+ margin-bottom: var(--space-2, 0.5rem);
1636
+ }
1637
+
1638
+ p {
1639
+ font-size: var(--text-base, 16px);
1640
+ line-height: var(--leading-base, 1.6);
1641
+ margin-bottom: var(--space-4, 1rem);
1642
+ }
1643
+
1644
+ p:last-child {
1645
+ margin-bottom: 0;
1646
+ }
1647
+
1148
1648
  code {
1149
1649
  font-family: "Menlo", "Monaco", "Courier New", monospace;
1150
1650
  font-size: 0.875em;
@@ -1268,8 +1768,8 @@ function build(options = {}) {
1268
1768
  const fullCssPath = getFullCssPath(config);
1269
1769
  const result = buildProductionCss();
1270
1770
 
1271
- console.log(' Generated production CSS: ' + path.relative(process.cwd(), result.outputPath));
1272
- console.log(' File size: ' + (result.outputSize / 1024).toFixed(2) + ' KB');
1771
+ console.log('\u2713 Generated production CSS: ' + path.relative(process.cwd(), result.outputPath));
1772
+ console.log('\u2713 File size: ' + (result.outputSize / 1024).toFixed(2) + ' KB');
1273
1773
 
1274
1774
  if (!options.keepFull && fs.existsSync(fullCssPath)) {
1275
1775
  try {
@@ -1306,6 +1806,7 @@ module.exports = {
1306
1806
  generateFlexboxUtilities,
1307
1807
  generateGridUtilities,
1308
1808
  addStateVariants,
1809
+ addAriaDataVariants,
1309
1810
  addResponsiveVariants,
1310
1811
  generateFontCSS,
1311
1812
  codeUtilities,
package/src/purge.js CHANGED
@@ -173,7 +173,7 @@ function purgeBlock(block, usedClasses) {
173
173
  .replace(/:/g, "\\\\:");
174
174
 
175
175
  const boundaryRegex = new RegExp(
176
- `\\.${escapedUsed}(?::[\\w\\-]+|[\\s,>+~]|$)`,
176
+ `\\.${escapedUsed}(?::[\\w\\-]+|\\[|[\\s,>+~]|$)`,
177
177
  );
178
178
 
179
179
  if (boundaryRegex.test(selector)) return true;
@@ -275,5 +275,5 @@ function purgeCSS(css, scanDir, config) {
275
275
  module.exports = {
276
276
  purgeCSS,
277
277
  getAllFiles,
278
- extractClassNames,
279
- };
278
+ extractClassNames
279
+ };
@@ -4,7 +4,7 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>EmilyUI Showcase</title>
7
- <link rel="stylesheet" href="./dist/emily.min.css">
7
+ <link rel="stylesheet" href="../dist/emily.min.css">
8
8
  <style>
9
9
  html {
10
10
  scroll-behavior: smooth;
@@ -949,7 +949,7 @@
949
949
 
950
950
  <div class="lg:col-span-2">
951
951
  <h3 class="font-bold mb-3">.center-screen</h3>
952
- <div class="border border-neutral-20 rounded-t-lg overflow-hidden bg-neutral-80" style="min-height: 200px; position: relative;">
952
+ <div class="border border-neutral-20 rounded-t-lg overflow-hidden bg-neutral-80" style="min-height: 50vh; position: relative;">
953
953
  <div class="center-screen" style="position: absolute;">
954
954
  <div class="bg-white rounded-lg shadow-lg border border-neutral-20 p-6 text-center" style="max-width: 340px; width: 90%;">
955
955
  <h4 class="text-lg font-bold mb-2">Confirm action</h4>