emily-css 1.0.15 → 1.0.17

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,22 @@ All notable changes to `emily-css` are documented here.
4
4
 
5
5
  ---
6
6
 
7
+ ## v1.0.17 — May 2026
8
+
9
+ **added new utilties, and added component patterns**
10
+
11
+ ### Added
12
+ - added new utilties, and added component patterns
13
+
14
+ ---
15
+ ## v1.0.16 — May 2026
16
+
17
+ **feat: add Round 2 utility set — 156/156 tests passing**
18
+
19
+ ### Added
20
+ - feat: add Round 2 utility set — 156/156 tests passing
21
+
22
+ ---
7
23
  ## v1.0.15 — May 2026
8
24
 
9
25
  **updated readme**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emily-css",
3
- "version": "1.0.15",
3
+ "version": "1.0.17",
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": {
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);
@@ -931,7 +1022,7 @@ ${bodyFont}`;
931
1022
  css += `@layer theme {\n${variablesCss}}\n\n`;
932
1023
  const baseStylesCss = generateBaseStyles(config);
933
1024
  css += `@layer base {${baseCss}${baseStylesCss}}\n\n`;
934
- css += `@layer components {\n /* Reserved for component styles in a future release. */\n}\n\n`;
1025
+ css += `@layer components {\n${generatePatternComponents()}}\n\n`;
935
1026
  css += `@layer utilities {\n${utilityCss}}\n`;
936
1027
 
937
1028
  // Write output
@@ -1059,4 +1150,4 @@ module.exports = {
1059
1150
  addResponsiveVariants,
1060
1151
  generateFontCSS,
1061
1152
  codeUtilities,
1062
- };
1153
+ };