emily-css 1.0.8 → 1.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.js CHANGED
@@ -142,8 +142,9 @@ function generateAllColours(colourConfig) {
142
142
  // ============================================================================
143
143
 
144
144
  function generateSpacing(baseUnit, scale) {
145
- // scale is now a key-value object from config
146
- // Just return it as-is since values are already defined (e.g., "1rem", "4px")
145
+ // Spacing values are defined explicitly in emily.config.json under spacing.scale.
146
+ // The baseUnit key in config is informational only it documents the design intent
147
+ // (e.g. "this system is based on 8px") but does not drive generation.
147
148
  return scale;
148
149
  }
149
150
 
@@ -151,30 +152,40 @@ function generateSpacing(baseUnit, scale) {
151
152
  // FONT PRESETS
152
153
  // ============================================================================
153
154
 
155
+ // Font presets define the CSS font-family stack only.
156
+ // Loading the actual font files is the user's responsibility — link them in your HTML
157
+ // or use @fontsource packages in your build. emily-css does not generate @import rules
158
+ // for external CDNs so it stays self-contained and works offline.
159
+ // See docs: https://emilyui.com/docs/getting-started
154
160
  const FONT_PRESETS = {
155
161
  'system': {
156
162
  stack: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
157
- googleFont: false,
158
163
  },
159
164
  'inter': {
160
165
  name: 'Inter',
161
166
  stack: '"Inter", system-ui, sans-serif',
162
- googleFont: true,
163
- importUrl: 'https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap',
164
167
  },
165
168
  'lexend': {
166
169
  name: 'Lexend',
167
170
  stack: '"Lexend", system-ui, sans-serif',
168
- googleFont: true,
169
- importUrl: 'https://fonts.googleapis.com/css2?family=Lexend:wght@100..900&display=swap',
170
171
  },
171
172
  'georgia': {
172
173
  stack: 'Georgia, "Times New Roman", serif',
173
- googleFont: false,
174
+ },
175
+ 'dm-sans': {
176
+ name: 'DM Sans',
177
+ stack: '"DM Sans", system-ui, sans-serif',
178
+ },
179
+ 'nunito': {
180
+ name: 'Nunito',
181
+ stack: '"Nunito", system-ui, sans-serif',
182
+ },
183
+ 'atkinson': {
184
+ name: 'Atkinson Hyperlegible',
185
+ stack: '"Atkinson Hyperlegible", system-ui, sans-serif',
174
186
  },
175
187
  'mono': {
176
188
  stack: '"Menlo", "Monaco", "Courier New", monospace',
177
- googleFont: false,
178
189
  },
179
190
  };
180
191
 
@@ -197,14 +208,6 @@ function generateFontCSS(config) {
197
208
  let fontFace = '';
198
209
  let bodyFont = '';
199
210
 
200
- // Import Google Fonts — dedupe if heading and body use the same font
201
- const imports = new Set();
202
- if (headingPreset.googleFont) imports.add(headingPreset.importUrl);
203
- if (bodyPreset.googleFont) imports.add(bodyPreset.importUrl);
204
- imports.forEach(url => {
205
- fontFace += `@import url("${url}");\n`;
206
- });
207
-
208
211
  bodyFont += ` body {\n font-family: ${bodyPreset.stack};\n font-synthesis: style;\n }\n`;
209
212
  bodyFont += ` h1, h2, h3, h4, h5, h6 {\n font-family: ${headingPreset.stack};\n }\n`;
210
213
 
@@ -284,7 +287,8 @@ const {
284
287
  blendUtilities,
285
288
  cursorUtilities,
286
289
  accessibilityUtilities,
287
- containerUtilities
290
+ containerUtilities,
291
+ codeUtilities
288
292
  } = require('./generators');
289
293
 
290
294
  // ============================================================================
@@ -471,8 +475,7 @@ function generateTypographyUtilities(config) {
471
475
  css += `.text-right { text-align: right; }\n`;
472
476
  css += `.text-justify { text-align: justify; }\n`;
473
477
 
474
- // Text wrapping
475
- css += `.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n`;
478
+ // Text wrapping (.truncate lives in overflowUtilities in generators.js — not duplicated here)
476
479
  css += `.whitespace-nowrap { white-space: nowrap; }\n`;
477
480
  css += `.whitespace-normal { white-space: normal; }\n`;
478
481
  css += `.break-words { word-break: break-word; }\n`;
@@ -582,7 +585,7 @@ function generateBaseStyles(config) {
582
585
  // Build a lookup map from font size name → CSS variable
583
586
  const fontSizeMap = {};
584
587
  (config.typography?.fontSizes || []).forEach(({ name }) => {
585
- fontSizeMap[name] = `var(--emily-text-${name})`;
588
+ fontSizeMap[name] = `var(--text-${name})`;
586
589
  });
587
590
 
588
591
  // Line height hints per size — keeps headings tighter than body text
@@ -779,7 +782,7 @@ function addStateVariants(css) {
779
782
  // BUILD FUNCTION
780
783
  // ============================================================================
781
784
 
782
- function build(options = {}) {
785
+ function buildFullFramework() {
783
786
  const configPath = path.join(process.cwd(), 'emily.config.json');
784
787
  if (!fs.existsSync(configPath)) {
785
788
  console.error(`\n emily-css: No config found.\n Expected: ${configPath}\n Run "emily-css init" to create one.\n`);
@@ -787,24 +790,7 @@ function build(options = {}) {
787
790
  }
788
791
  const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
789
792
 
790
- if (options.purge) {
791
- const { purgeCSS } = require('./purge.js');
792
- const cssPath = path.join(process.cwd(), 'dist/emily.css');
793
- if (!fs.existsSync(cssPath)) {
794
- console.error(' emily-css: Run "emily-css build" first.');
795
- process.exit(1);
796
- }
797
- console.log(`Purging unused utilities from ${options.purge}...`);
798
- const css = fs.readFileSync(cssPath, 'utf8');
799
- const purged = purgeCSS(css, options.purge);
800
- const minified = purged.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\s+/g, ' ').replace(/\s?\{/g, '{').replace(/\s?\}/g, '}').replace(/;\s/g, ';').trim();
801
- fs.writeFileSync(path.join(process.cwd(), 'dist/emily.purged.css'), purged);
802
- fs.writeFileSync(path.join(process.cwd(), 'dist/emily.purged.min.css'), minified);
803
- console.log('✓ Purged CSS: dist/emily.purged.css');
804
- return;
805
- }
806
-
807
- console.log('Building EmilyUI...');
793
+ console.log('Building EmilyCSS full framework...');
808
794
 
809
795
  // Generate colours
810
796
  const colours = generateAllColours(config.colours);
@@ -844,6 +830,7 @@ function build(options = {}) {
844
830
  utilityCss += cursorUtilities();
845
831
  utilityCss += accessibilityUtilities();
846
832
  utilityCss += containerUtilities();
833
+ utilityCss += codeUtilities();
847
834
 
848
835
  // Add state, dark mode, and responsive variants to utilities
849
836
  utilityCss = addStateVariants(utilityCss);
@@ -904,6 +891,38 @@ function build(options = {}) {
904
891
  p, h1, h2, h3, h4, h5, h6 {
905
892
  overflow-wrap: break-word;
906
893
  }
894
+
895
+ /* Code blocks — VSCode Dark+ style by default */
896
+ pre {
897
+ background-color: #1e1e1e;
898
+ color: #d4d4d4;
899
+ padding: 1.25rem;
900
+ border-radius: 0 0 6px;
901
+ overflow-x: auto;
902
+ font-family: "Menlo", "Monaco", "Courier New", monospace;
903
+ font-size: 0.875rem;
904
+ line-height: 1.7;
905
+ border: 1px solid #333;
906
+ }
907
+
908
+ pre code {
909
+ background: none;
910
+ padding: 0;
911
+ border-radius: 0;
912
+ color: inherit;
913
+ font-size: inherit;
914
+ 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;
925
+ }
907
926
  ${bodyFont}`;
908
927
 
909
928
  // @font-face must sit outside @layer for broadest browser compatibility
@@ -926,26 +945,107 @@ ${bodyFont}`;
926
945
  fs.writeFileSync(outputPath, css);
927
946
  console.log(`✓ Generated CSS: ${outputPath}`);
928
947
  console.log(`✓ File size: ${(css.length / 1024).toFixed(2)} KB (unminified)`);
948
+ console.log('Full framework build complete');
949
+ }
950
+
951
+ function minify(css) {
952
+ return css
953
+ .replace(/\/\*[\s\S]*?\*\//g, '')
954
+ .replace(/\s+/g, ' ')
955
+ .replace(/\s?\{/g, '{')
956
+ .replace(/\s?\}/g, '}')
957
+ .replace(/;\s/g, ';')
958
+ .trim();
959
+ }
960
+
961
+ function getConfig() {
962
+ const configPath = path.join(process.cwd(), 'emily.config.json');
963
+
964
+ if (!fs.existsSync(configPath)) {
965
+ console.error('\n emily-css: No config found. Run "emily-css init" first.\n');
966
+ process.exit(1);
967
+ }
929
968
 
930
- // Generate minified version
931
- const minified = css.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\s+/g, ' ').replace(/\s?\{/g, '{').replace(/\s?\}/g, '}').replace(/;\s/g, ';').trim();
969
+ return JSON.parse(fs.readFileSync(configPath, 'utf8'));
970
+ }
971
+
972
+ function getSourceDir(config) {
973
+ return config.purge && config.purge.sourceDir ? config.purge.sourceDir : '.';
974
+ }
975
+
976
+ function buildProductionCss() {
977
+ const config = getConfig();
978
+ const sourceDir = getSourceDir(config);
979
+ const cssPath = path.join(process.cwd(), 'dist/emily.css');
932
980
  const minPath = path.join(process.cwd(), 'dist/emily.min.css');
981
+
982
+ if (!fs.existsSync(cssPath)) {
983
+ buildFullFramework();
984
+ }
985
+
986
+ const { purgeCSS } = require('./purge.js');
987
+ const css = fs.readFileSync(cssPath, 'utf8');
988
+ const purged = purgeCSS(css, sourceDir, config);
989
+ const minified = minify(purged);
990
+
933
991
  fs.writeFileSync(minPath, minified);
934
- console.log(' -> Minified: ' + minPath);
935
- console.log(' -> File size: ' + (minified.length / 1024).toFixed(2) + ' KB (minified)');
992
+
993
+ return {
994
+ css,
995
+ purged,
996
+ minified,
997
+ originalSize: Buffer.byteLength(css, 'utf8'),
998
+ outputSize: Buffer.byteLength(minified, 'utf8')
999
+ };
1000
+ }
1001
+
1002
+ function isFrameworkStale() {
1003
+ const configPath = path.join(process.cwd(), 'emily.config.json');
1004
+ const cssPath = path.join(process.cwd(), 'dist/emily.css');
1005
+
1006
+ if (!fs.existsSync(cssPath)) return true;
1007
+ if (!fs.existsSync(configPath)) return true;
1008
+
1009
+ return fs.statSync(configPath).mtimeMs > fs.statSync(cssPath).mtimeMs;
1010
+ }
1011
+
1012
+ function ensureFullFramework() {
1013
+ if (isFrameworkStale()) {
1014
+ buildFullFramework();
1015
+ }
1016
+ }
1017
+
1018
+ function build(options = {}) {
1019
+ ensureFullFramework();
1020
+
1021
+ const result = buildProductionCss();
1022
+ const cssPath = path.join(process.cwd(), 'dist/emily.css');
1023
+
1024
+ console.log('✓ Generated production CSS: dist/emily.min.css');
1025
+ console.log('✓ File size: ' + (result.outputSize / 1024).toFixed(2) + ' KB');
1026
+
1027
+ if (!options.keepFull && fs.existsSync(cssPath)) {
1028
+ try {
1029
+ fs.unlinkSync(cssPath);
1030
+ console.log('Removed dist/emily.css for production build.');
1031
+ } catch (e) {
1032
+ // Windows FUSE: can't delete, non-fatal
1033
+ }
1034
+ }
936
1035
 
937
1036
  console.log('Build complete');
938
1037
  }
939
1038
 
940
1039
  if (require.main === module) {
941
1040
  const args = process.argv.slice(2);
942
- const purgeIndex = args.indexOf('--purge');
943
- const purgeDir = purgeIndex !== -1 ? args[purgeIndex + 1] : null;
944
- build(purgeDir ? { purge: purgeDir } : {});
1041
+ build({ keepFull: args.includes('--keep-full') });
945
1042
  }
946
1043
 
947
1044
  module.exports = {
948
1045
  build,
1046
+ buildFullFramework,
1047
+ buildProductionCss,
1048
+ ensureFullFramework,
949
1049
  hexToOklch,
950
1050
  oklchToHex,
951
1051
  generateColourScale,
@@ -958,4 +1058,5 @@ module.exports = {
958
1058
  addStateVariants,
959
1059
  addResponsiveVariants,
960
1060
  generateFontCSS,
1061
+ codeUtilities,
961
1062
  };