emily-css 1.0.17 → 1.0.19

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,6 +4,19 @@ All notable changes to `emily-css` are documented here.
4
4
 
5
5
  ---
6
6
 
7
+ ## v1.0.19 — May 2026
8
+
9
+ **Add framework-aware output paths and bundled showcase template**
10
+
11
+ ---
12
+ ## v1.0.18 — May 2026
13
+
14
+ ****
15
+
16
+ ### Changed
17
+ - added more utitlies as a code block
18
+
19
+ ---
7
20
  ## v1.0.17 — May 2026
8
21
 
9
22
  **added new utilties, and added component patterns**
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. Development
31
+ ### 3. Start Building
32
32
 
33
- ```bash
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
- npm run emily:showcase # Serves at http://localhost:3456
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 the showcase
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 # Setup + first build
57
- npx emily-css build # Regenerate CSS
58
- npx emily-css watch # Development watch mode
59
- npx emily-css purge # Remove unused styles for production
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
- Configure it in `emily.config.json`:
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 (e.g. `bg-${colour}`) are not detected. Use static strings or add them to your safelist.
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 Example
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
- "extensions": [".html", ".php", ".jsx", ".vue"]
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 building, run `npm run emily:showcase` and visit `http://localhost:3456`. It contains production-ready, accessible components built with your exact brand.
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 (`emily-css` npm package + CLI)
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-lg bg-white border border-neutral-30 shadow-sm">
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. Use `@fontsource` (recommended):
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/bin/emilyui.js CHANGED
@@ -7,8 +7,6 @@ if (command === "init") {
7
7
  } else if (command === "build") {
8
8
  const { build } = require("../src/index.js");
9
9
  build({ keepFull: process.argv.includes("--keep-full") });
10
- } else if (command === "purge") {
11
- require("../src/purge-cmd.js");
12
10
  } else if (command === "watch") {
13
11
  require("../src/watch.js");
14
12
  } else if (command === "showcase") {
@@ -19,9 +17,8 @@ if (command === "init") {
19
17
 
20
18
  Commands:
21
19
  emily-css init Set up a new project (interactive wizard)
22
- emily-css build Generate production CSS to dist/emily.min.css
23
- emily-css watch Dev mode: watch for config changes and rebuild
24
- emily-css purge Scan project files and remove unused utilities
20
+ emily-css build Generate production CSS to the configured output path
21
+ emily-css watch Dev mode: watch for changes and rebuild
25
22
  emily-css showcase Launch the component showcase in your browser
26
23
  emily-css help Show this help text
27
24
 
@@ -39,12 +36,11 @@ if (command === "init") {
39
36
 
40
37
  Usage:
41
38
  emily-css init Set up a new project
42
- emily-css build Generate production CSS
39
+ emily-css build Generate production CSS to the configured output path
43
40
  emily-css watch Dev mode: rebuild on changes
44
- emily-css purge Remove unused utilities
45
41
  emily-css showcase Browse components in your browser
46
42
  emily-css help Full command reference
47
43
 
48
44
  Run emily-css help for more detail.
49
45
  `);
50
- }
46
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emily-css",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
4
4
  "description": "A config-driven utility CSS framework. Define your brand once, generate the CSS.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -9,6 +9,7 @@
9
9
  "files": [
10
10
  "bin/",
11
11
  "src/",
12
+ "templates/",
12
13
  "README.md",
13
14
  "LICENSE",
14
15
  "CHANGELOG.md"
@@ -22,7 +23,10 @@
22
23
  "emily:showcase": "node src/showcase.js",
23
24
  "commit": "node scripts/commit.js",
24
25
  "release": "node scripts/release.js",
25
- "ship": "node scripts/ship.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"
26
30
  },
27
31
  "keywords": [
28
32
  "css",
@@ -50,6 +54,7 @@
50
54
  "cross-spawn": "^7.0.6",
51
55
  "emily-css": "^1.0.8",
52
56
  "enquirer": "^2.4.1",
57
+ "fast-glob": "^3.3.3",
53
58
  "ora": "^5.4.1"
54
59
  }
55
60
  }
package/src/index.js CHANGED
@@ -867,28 +867,54 @@ function generatePatternComponents() {
867
867
  // BUILD FUNCTION
868
868
  // ============================================================================
869
869
 
870
- function buildFullFramework() {
871
- const configPath = path.join(process.cwd(), 'emily.config.json');
870
+ function getConfigPath() {
871
+ return path.join(process.cwd(), 'emily.config.json');
872
+ }
873
+
874
+ function getConfig() {
875
+ const configPath = getConfigPath();
876
+
872
877
  if (!fs.existsSync(configPath)) {
873
- console.error(`\n emily-css: No config found.\n Expected: ${configPath}\n Run "emily-css init" to create one.\n`);
878
+ console.error('\n emily-css: No config found. Run "emily-css init" first.\n');
874
879
  process.exit(1);
875
880
  }
876
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
881
+
882
+ return JSON.parse(fs.readFileSync(configPath, 'utf8'));
883
+ }
884
+
885
+ function getFullCssPath(config) {
886
+ return path.join(process.cwd(), config.output?.fullCss || 'dist/emily.css');
887
+ }
888
+
889
+ function getProductionCssPath(config) {
890
+ return path.join(process.cwd(), config.output?.css || 'dist/emily.min.css');
891
+ }
892
+
893
+ function ensureDirectoryForFile(filePath) {
894
+ const dir = path.dirname(filePath);
895
+
896
+ if (!fs.existsSync(dir)) {
897
+ fs.mkdirSync(dir, { recursive: true });
898
+ }
899
+ }
900
+
901
+ function getSourceDir(config) {
902
+ return config.purge?.sourceDir || '.';
903
+ }
904
+
905
+ function buildFullFramework() {
906
+ const config = getConfig();
877
907
 
878
908
  console.log('Building EmilyCSS full framework...');
879
909
 
880
- // Generate colours
881
910
  const colours = generateAllColours(config.colours);
882
911
  console.log(`✓ Generated ${Object.keys(colours).length} colour scales`);
883
912
 
884
- // Generate spacing
885
913
  const spacing = generateSpacing(config.baseUnit, config.spacing.scale);
886
914
  console.log(`✓ Generated ${Object.keys(spacing).length} spacing values`);
887
915
 
888
- // 1. Generate Variables (Theme Layer)
889
916
  const variablesCss = generateCSSVariables(colours, spacing, config);
890
917
 
891
- // 2. Generate Utilities (Utilities Layer)
892
918
  let utilityCss = '';
893
919
  utilityCss += displayUtilities();
894
920
  utilityCss += generateSpacingUtilities(spacing);
@@ -923,21 +949,16 @@ function buildFullFramework() {
923
949
  utilityCss += backgroundUtilities();
924
950
  utilityCss += filterUtilities();
925
951
 
926
- // Add state, dark mode, and responsive variants to utilities
927
952
  utilityCss = addStateVariants(utilityCss);
928
953
  utilityCss = addDarkModeVariants(utilityCss);
929
954
  utilityCss = addResponsiveVariants(utilityCss, config);
930
955
 
931
- // 3. Assemble Final CSS with Layers
932
- // Layer order matters: later layers win over earlier ones.
933
- // theme → CSS custom properties / design tokens
934
- // base → box-sizing reset, font-face, body defaults
935
- // components → reserved for future component styles
936
- // utilities → generated utility classes (highest priority)
937
956
  const { fontFace, bodyFont } = generateFontCSS(config);
957
+
938
958
  const fontLabel = typeof config.fontFamily === 'object'
939
959
  ? 'heading: ' + (config.fontFamily.heading || 'system') + ', body: ' + (config.fontFamily.body || 'system')
940
960
  : (config.fontFamily || 'system');
961
+
941
962
  console.log('✓ Font: ' + fontLabel);
942
963
 
943
964
  const baseCss = `
@@ -946,7 +967,6 @@ function buildFullFramework() {
946
967
  box-sizing: border-box;
947
968
  }
948
969
 
949
- /* Remove default margin/padding on common elements */
950
970
  body, h1, h2, h3, h4, h5, h6, p,
951
971
  ul, ol, dl, dd, figure, blockquote,
952
972
  fieldset, textarea, pre {
@@ -954,23 +974,19 @@ function buildFullFramework() {
954
974
  padding: 0;
955
975
  }
956
976
 
957
- /* Lists: remove bullets/numbers when unstyled */
958
977
  ul, ol {
959
978
  list-style: none;
960
979
  }
961
980
 
962
- /* Inherit fonts for form elements */
963
981
  input, button, textarea, select {
964
982
  font: inherit;
965
983
  }
966
984
 
967
- /* Sensible media defaults */
968
985
  img, picture, video, canvas, svg {
969
986
  display: block;
970
987
  max-width: 100%;
971
988
  }
972
989
 
973
- /* Remove default button styles */
974
990
  button {
975
991
  background: none;
976
992
  border: none;
@@ -978,22 +994,38 @@ function buildFullFramework() {
978
994
  padding: 0;
979
995
  }
980
996
 
981
- /* Avoid overflow on long words */
982
997
  p, h1, h2, h3, h4, h5, h6 {
983
998
  overflow-wrap: break-word;
984
999
  }
985
1000
 
986
- /* Code blocks — VSCode Dark+ style by default */
1001
+ code {
1002
+ font-family: "Menlo", "Monaco", "Courier New", monospace;
1003
+ font-size: 0.875em;
1004
+ background-color: #0d0c0b;
1005
+ color: #a3c986;
1006
+ padding: 0.125rem 0.4rem;
1007
+ border-radius: 4px;
1008
+ display: inline;
1009
+ }
1010
+
1011
+ code.block {
1012
+ display: block;
1013
+ padding: 0.625rem 1rem;
1014
+ border-radius: 6px;
1015
+ font-size: 0.8125rem;
1016
+ line-height: 1.6;
1017
+ }
1018
+
987
1019
  pre {
988
- background-color: #1e1e1e;
989
- color: #d4d4d4;
1020
+ background-color: #0d0c0b;
1021
+ color: #e4e0db;
990
1022
  padding: 1.25rem;
991
- border-radius: 0 0 6px;
1023
+ border-radius: 6px;
992
1024
  overflow-x: auto;
993
1025
  font-family: "Menlo", "Monaco", "Courier New", monospace;
994
1026
  font-size: 0.875rem;
995
1027
  line-height: 1.7;
996
- border: 1px solid #333;
1028
+ border: 1px solid #2a2520;
997
1029
  }
998
1030
 
999
1031
  pre code {
@@ -1003,38 +1035,25 @@ function buildFullFramework() {
1003
1035
  color: inherit;
1004
1036
  font-size: inherit;
1005
1037
  font-family: inherit;
1006
- }
1007
-
1008
- /* Inline code */
1009
- code {
1010
- font-family: "Menlo", "Monaco", "Courier New", monospace;
1011
- font-size: 0.875em;
1012
- background-color: #2d2d2d;
1013
- color: #d4d4d4;
1014
- padding: 0.125rem 0.375rem;
1015
- border-radius: 4px;
1038
+ display: inline;
1016
1039
  }
1017
1040
  ${bodyFont}`;
1018
1041
 
1019
- // @font-face must sit outside @layer for broadest browser compatibility
1020
1042
  let css = fontFace ? `${fontFace}\n` : '';
1021
1043
  css += `@layer theme, base, components, utilities;\n\n`;
1022
1044
  css += `@layer theme {\n${variablesCss}}\n\n`;
1045
+
1023
1046
  const baseStylesCss = generateBaseStyles(config);
1024
1047
  css += `@layer base {${baseCss}${baseStylesCss}}\n\n`;
1025
1048
  css += `@layer components {\n${generatePatternComponents()}}\n\n`;
1026
1049
  css += `@layer utilities {\n${utilityCss}}\n`;
1027
1050
 
1028
- // Write output
1029
- const outputPath = path.join(process.cwd(), 'dist/emily.css');
1030
- const outputDir = path.dirname(outputPath);
1051
+ const fullCssPath = getFullCssPath(config);
1031
1052
 
1032
- if (!fs.existsSync(outputDir)) {
1033
- fs.mkdirSync(outputDir, { recursive: true });
1034
- }
1053
+ ensureDirectoryForFile(fullCssPath);
1054
+ fs.writeFileSync(fullCssPath, css);
1035
1055
 
1036
- fs.writeFileSync(outputPath, css);
1037
- console.log(`✓ Generated CSS: ${outputPath}`);
1056
+ console.log(`✓ Generated CSS: ${fullCssPath}`);
1038
1057
  console.log(`✓ File size: ${(css.length / 1024).toFixed(2)} KB (unminified)`);
1039
1058
  console.log('Full framework build complete');
1040
1059
  }
@@ -1049,55 +1068,44 @@ function minify(css) {
1049
1068
  .trim();
1050
1069
  }
1051
1070
 
1052
- function getConfig() {
1053
- const configPath = path.join(process.cwd(), 'emily.config.json');
1054
-
1055
- if (!fs.existsSync(configPath)) {
1056
- console.error('\n emily-css: No config found. Run "emily-css init" first.\n');
1057
- process.exit(1);
1058
- }
1059
-
1060
- return JSON.parse(fs.readFileSync(configPath, 'utf8'));
1061
- }
1062
-
1063
- function getSourceDir(config) {
1064
- return config.purge && config.purge.sourceDir ? config.purge.sourceDir : '.';
1065
- }
1066
-
1067
1071
  function buildProductionCss() {
1068
1072
  const config = getConfig();
1069
1073
  const sourceDir = getSourceDir(config);
1070
- const cssPath = path.join(process.cwd(), 'dist/emily.css');
1071
- const minPath = path.join(process.cwd(), 'dist/emily.min.css');
1074
+ const fullCssPath = getFullCssPath(config);
1075
+ const productionCssPath = getProductionCssPath(config);
1072
1076
 
1073
- if (!fs.existsSync(cssPath)) {
1077
+ if (!fs.existsSync(fullCssPath)) {
1074
1078
  buildFullFramework();
1075
1079
  }
1076
1080
 
1077
1081
  const { purgeCSS } = require('./purge.js');
1078
- const css = fs.readFileSync(cssPath, 'utf8');
1082
+ const css = fs.readFileSync(fullCssPath, 'utf8');
1079
1083
  const purged = purgeCSS(css, sourceDir, config);
1080
1084
  const minified = minify(purged);
1081
1085
 
1082
- fs.writeFileSync(minPath, minified);
1086
+ ensureDirectoryForFile(productionCssPath);
1087
+ fs.writeFileSync(productionCssPath, minified);
1083
1088
 
1084
1089
  return {
1085
1090
  css,
1086
1091
  purged,
1087
1092
  minified,
1088
1093
  originalSize: Buffer.byteLength(css, 'utf8'),
1089
- outputSize: Buffer.byteLength(minified, 'utf8')
1094
+ outputSize: Buffer.byteLength(minified, 'utf8'),
1095
+ outputPath: productionCssPath,
1096
+ fullCssPath,
1090
1097
  };
1091
1098
  }
1092
1099
 
1093
1100
  function isFrameworkStale() {
1094
- const configPath = path.join(process.cwd(), 'emily.config.json');
1095
- const cssPath = path.join(process.cwd(), 'dist/emily.css');
1101
+ const config = getConfig();
1102
+ const configPath = getConfigPath();
1103
+ const fullCssPath = getFullCssPath(config);
1096
1104
 
1097
- if (!fs.existsSync(cssPath)) return true;
1105
+ if (!fs.existsSync(fullCssPath)) return true;
1098
1106
  if (!fs.existsSync(configPath)) return true;
1099
1107
 
1100
- return fs.statSync(configPath).mtimeMs > fs.statSync(cssPath).mtimeMs;
1108
+ return fs.statSync(configPath).mtimeMs > fs.statSync(fullCssPath).mtimeMs;
1101
1109
  }
1102
1110
 
1103
1111
  function ensureFullFramework() {
@@ -1109,18 +1117,19 @@ function ensureFullFramework() {
1109
1117
  function build(options = {}) {
1110
1118
  ensureFullFramework();
1111
1119
 
1120
+ const config = getConfig();
1121
+ const fullCssPath = getFullCssPath(config);
1112
1122
  const result = buildProductionCss();
1113
- const cssPath = path.join(process.cwd(), 'dist/emily.css');
1114
1123
 
1115
- console.log('✓ Generated production CSS: dist/emily.min.css');
1124
+ console.log('✓ Generated production CSS: ' + path.relative(process.cwd(), result.outputPath));
1116
1125
  console.log('✓ File size: ' + (result.outputSize / 1024).toFixed(2) + ' KB');
1117
1126
 
1118
- if (!options.keepFull && fs.existsSync(cssPath)) {
1127
+ if (!options.keepFull && fs.existsSync(fullCssPath)) {
1119
1128
  try {
1120
- fs.unlinkSync(cssPath);
1121
- console.log('Removed dist/emily.css for production build.');
1122
- } catch (e) {
1123
- // Windows FUSE: can't delete, non-fatal
1129
+ fs.unlinkSync(fullCssPath);
1130
+ console.log('Removed ' + path.relative(process.cwd(), fullCssPath) + ' for production build.');
1131
+ } catch (error) {
1132
+ // Windows FUSE: cannot always delete files cleanly, non-fatal.
1124
1133
  }
1125
1134
  }
1126
1135