emily-css 1.0.25 → 1.0.27

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
@@ -359,7 +359,6 @@ function generateSpacingUtilities(spacing) {
359
359
  function generateFlexboxUtilities(spacing) {
360
360
  let css = `/* Flexbox */\n`;
361
361
 
362
- // Note: .flex is already generated in displayUtilities(), removed duplicate here
363
362
  css += `.inline-flex { display: inline-flex; }\n`;
364
363
 
365
364
  // Direction
@@ -373,22 +372,65 @@ function generateFlexboxUtilities(spacing) {
373
372
  css += `.flex-nowrap { flex-wrap: nowrap; }\n`;
374
373
  css += `.flex-wrap-reverse { flex-wrap: wrap-reverse; }\n`;
375
374
 
376
- // Grow/shrink
375
+ // Flex shorthand
377
376
  css += `.flex-1 { flex: 1 1 0%; }\n`;
378
377
  css += `.flex-auto { flex: 1 1 auto; }\n`;
378
+ css += `.flex-initial { flex: 0 1 auto; }\n`;
379
379
  css += `.flex-none { flex: none; }\n`;
380
+
381
+ // Grow/shrink
380
382
  css += `.grow { flex-grow: 1; }\n`;
381
383
  css += `.grow-0 { flex-grow: 0; }\n`;
382
384
  css += `.shrink { flex-shrink: 1; }\n`;
383
385
  css += `.shrink-0 { flex-shrink: 0; }\n`;
384
386
 
387
+ // Flex basis
388
+ css += `.basis-auto { flex-basis: auto; }\n`;
389
+ css += `.basis-full { flex-basis: 100%; }\n`;
390
+ Object.entries(spacing).forEach(([key, value]) => {
391
+ const escaped = escapeClassName(key);
392
+ css += `.basis-${escaped} { flex-basis: ${value}; }\n`;
393
+ });
394
+
395
+ const fractions = {
396
+ '1\\/2': '50%', '1\\/3': '33.333333%', '2\\/3': '66.666667%',
397
+ '1\\/4': '25%', '2\\/4': '50%', '3\\/4': '75%',
398
+ '1\\/5': '20%', '2\\/5': '40%', '3\\/5': '60%', '4\\/5': '80%',
399
+ '1\\/6': '16.666667%', '2\\/6': '33.333333%', '3\\/6': '50%', '4\\/6': '66.666667%', '5\\/6': '83.333333%',
400
+ '1\\/12': '8.333333%', '2\\/12': '16.666667%', '3\\/12': '25%', '4\\/12': '33.333333%', '5\\/12': '41.666667%', '6\\/12': '50%', '7\\/12': '58.333333%', '8\\/12': '66.666667%', '9\\/12': '75%', '10\\/12': '83.333333%', '11\\/12': '91.666667%'
401
+ };
402
+ Object.entries(fractions).forEach(([name, value]) => {
403
+ css += `.basis-${name} { flex-basis: ${value}; }\n`;
404
+ });
405
+
406
+ // Order
407
+ css += `.order-first { order: -9999; }\n`;
408
+ css += `.order-last { order: 9999; }\n`;
409
+ css += `.order-none { order: 0; }\n`;
410
+ for (let i = 1; i <= 12; i++) {
411
+ css += `.order-${i} { order: ${i}; }\n`;
412
+ }
413
+
385
414
  // Justify (main axis)
415
+ css += `.justify-normal { justify-content: normal; }\n`;
386
416
  css += `.justify-start { justify-content: flex-start; }\n`;
387
417
  css += `.justify-end { justify-content: flex-end; }\n`;
388
418
  css += `.justify-center { justify-content: center; }\n`;
389
419
  css += `.justify-between { justify-content: space-between; }\n`;
390
420
  css += `.justify-around { justify-content: space-around; }\n`;
391
421
  css += `.justify-evenly { justify-content: space-evenly; }\n`;
422
+ css += `.justify-stretch { justify-content: stretch; }\n`;
423
+
424
+ // Content alignment
425
+ css += `.content-normal { align-content: normal; }\n`;
426
+ css += `.content-center { align-content: center; }\n`;
427
+ css += `.content-start { align-content: flex-start; }\n`;
428
+ css += `.content-end { align-content: flex-end; }\n`;
429
+ css += `.content-between { align-content: space-between; }\n`;
430
+ css += `.content-around { align-content: space-around; }\n`;
431
+ css += `.content-evenly { align-content: space-evenly; }\n`;
432
+ css += `.content-baseline { align-content: baseline; }\n`;
433
+ css += `.content-stretch { align-content: stretch; }\n`;
392
434
 
393
435
  // Items (cross axis)
394
436
  css += `.items-start { align-items: flex-start; }\n`;
@@ -398,11 +440,32 @@ function generateFlexboxUtilities(spacing) {
398
440
  css += `.items-stretch { align-items: stretch; }\n`;
399
441
 
400
442
  // Self alignment
443
+ css += `.self-auto { align-self: auto; }\n`;
401
444
  css += `.self-start { align-self: flex-start; }\n`;
402
445
  css += `.self-end { align-self: flex-end; }\n`;
403
446
  css += `.self-center { align-self: center; }\n`;
404
447
  css += `.self-stretch { align-self: stretch; }\n`;
405
- css += `.self-auto { align-self: auto; }\n`;
448
+ css += `.self-baseline { align-self: baseline; }\n`;
449
+
450
+ // Place utilities
451
+ css += `.place-content-center { place-content: center; }\n`;
452
+ css += `.place-content-start { place-content: start; }\n`;
453
+ css += `.place-content-end { place-content: end; }\n`;
454
+ css += `.place-content-between { place-content: space-between; }\n`;
455
+ css += `.place-content-around { place-content: space-around; }\n`;
456
+ css += `.place-content-evenly { place-content: space-evenly; }\n`;
457
+ css += `.place-content-baseline { place-content: baseline; }\n`;
458
+ css += `.place-content-stretch { place-content: stretch; }\n`;
459
+ css += `.place-items-start { place-items: start; }\n`;
460
+ css += `.place-items-end { place-items: end; }\n`;
461
+ css += `.place-items-center { place-items: center; }\n`;
462
+ css += `.place-items-baseline { place-items: baseline; }\n`;
463
+ css += `.place-items-stretch { place-items: stretch; }\n`;
464
+ css += `.place-self-auto { place-self: auto; }\n`;
465
+ css += `.place-self-start { place-self: start; }\n`;
466
+ css += `.place-self-end { place-self: end; }\n`;
467
+ css += `.place-self-center { place-self: center; }\n`;
468
+ css += `.place-self-stretch { place-self: stretch; }\n`;
406
469
 
407
470
  css += `\n`;
408
471
  return css;
@@ -415,45 +478,59 @@ function generateFlexboxUtilities(spacing) {
415
478
  function generateGridUtilities(spacing) {
416
479
  let css = `/* Grid */\n`;
417
480
 
418
- // Note: .grid is already generated in displayUtilities(), removed duplicate here
419
481
  css += `.inline-grid { display: inline-grid; }\n`;
420
482
 
421
- // Grid columns
483
+ css += `.grid-cols-none { grid-template-columns: none; }\n`;
484
+ css += `.grid-cols-subgrid { grid-template-columns: subgrid; }\n`;
422
485
  for (let i = 1; i <= 12; i++) {
423
486
  css += `.grid-cols-${i} { grid-template-columns: repeat(${i}, minmax(0, 1fr)); }\n`;
424
487
  }
425
488
 
426
- // Column span
489
+ css += `.grid-rows-none { grid-template-rows: none; }\n`;
490
+ css += `.grid-rows-subgrid { grid-template-rows: subgrid; }\n`;
491
+ for (let i = 1; i <= 12; i++) {
492
+ css += `.grid-rows-${i} { grid-template-rows: repeat(${i}, minmax(0, 1fr)); }\n`;
493
+ }
494
+
427
495
  for (let i = 1; i <= 12; i++) {
428
496
  css += `.col-span-${i} { grid-column: span ${i} / span ${i}; }\n`;
429
497
  }
430
498
  css += `.col-span-full { grid-column: 1 / -1; }\n`;
431
-
432
- // Column start/end
499
+ css += `.col-auto { grid-column: auto; }\n`;
433
500
  for (let i = 1; i <= 13; i++) {
434
501
  css += `.col-start-${i} { grid-column-start: ${i}; }\n`;
435
502
  css += `.col-end-${i} { grid-column-end: ${i}; }\n`;
436
503
  }
504
+ css += `.col-start-auto { grid-column-start: auto; }\n`;
505
+ css += `.col-end-auto { grid-column-end: auto; }\n`;
437
506
 
438
- // Row span
439
- for (let i = 1; i <= 6; i++) {
507
+ for (let i = 1; i <= 12; i++) {
440
508
  css += `.row-span-${i} { grid-row: span ${i} / span ${i}; }\n`;
441
509
  }
442
510
  css += `.row-span-full { grid-row: 1 / -1; }\n`;
443
-
444
- // Row start/end
445
- for (let i = 1; i <= 6; i++) {
511
+ css += `.row-auto { grid-row: auto; }\n`;
512
+ for (let i = 1; i <= 13; i++) {
446
513
  css += `.row-start-${i} { grid-row-start: ${i}; }\n`;
447
514
  css += `.row-end-${i} { grid-row-end: ${i}; }\n`;
448
515
  }
516
+ css += `.row-start-auto { grid-row-start: auto; }\n`;
517
+ css += `.row-end-auto { grid-row-end: auto; }\n`;
518
+
519
+ css += `.grid-flow-row { grid-auto-flow: row; }\n`;
520
+ css += `.grid-flow-col { grid-auto-flow: column; }\n`;
521
+ css += `.grid-flow-dense { grid-auto-flow: dense; }\n`;
522
+ css += `.grid-flow-row-dense { grid-auto-flow: row dense; }\n`;
523
+ css += `.grid-flow-col-dense { grid-auto-flow: column dense; }\n`;
449
524
 
450
- // Auto flow
451
525
  css += `.auto-cols-auto { grid-auto-columns: auto; }\n`;
526
+ css += `.auto-cols-min { grid-auto-columns: min-content; }\n`;
527
+ css += `.auto-cols-max { grid-auto-columns: max-content; }\n`;
452
528
  css += `.auto-cols-fr { grid-auto-columns: minmax(0, 1fr); }\n`;
453
529
  css += `.auto-rows-auto { grid-auto-rows: auto; }\n`;
530
+ css += `.auto-rows-min { grid-auto-rows: min-content; }\n`;
531
+ css += `.auto-rows-max { grid-auto-rows: max-content; }\n`;
454
532
  css += `.auto-rows-fr { grid-auto-rows: minmax(0, 1fr); }\n`;
455
533
 
456
- // Gap
457
534
  Object.entries(spacing).forEach(([key, value]) => {
458
535
  const escaped = escapeClassName(key);
459
536
  css += `.gap-${escaped} { gap: ${value}; }\n`;
@@ -472,57 +549,76 @@ function generateGridUtilities(spacing) {
472
549
  function generateTypographyUtilities(config) {
473
550
  let css = `/* Typography */\n`;
474
551
 
475
- // Font sizes
476
552
  config.typography.fontSizes.forEach(fontSize => {
477
553
  css += `.text-${fontSize.name} { font-size: var(--text-${fontSize.name}); line-height: ${fontSize.lineHeight}; }\n`;
478
554
  });
479
555
 
480
- // Font weights
481
556
  Object.entries(config.typography.fontWeights).forEach(([name, weight]) => {
482
557
  css += `.font-${name} { font-weight: ${weight}; }\n`;
483
558
  });
484
559
 
485
- // Text alignment
560
+ css += `.italic { font-style: italic; }\n`;
561
+ css += `.not-italic { font-style: normal; }\n`;
562
+
486
563
  css += `.text-left { text-align: left; }\n`;
487
564
  css += `.text-center { text-align: center; }\n`;
488
565
  css += `.text-right { text-align: right; }\n`;
489
566
  css += `.text-justify { text-align: justify; }\n`;
567
+ css += `.text-start { text-align: start; }\n`;
568
+ css += `.text-end { text-align: end; }\n`;
490
569
 
491
- // Text wrapping (.truncate lives in overflowUtilities in generators.js — not duplicated here)
492
- css += `.whitespace-nowrap { white-space: nowrap; }\n`;
493
570
  css += `.whitespace-normal { white-space: normal; }\n`;
494
- css += `.break-words { word-break: break-word; }\n`;
571
+ css += `.whitespace-nowrap { white-space: nowrap; }\n`;
572
+ css += `.whitespace-pre { white-space: pre; }\n`;
573
+ css += `.whitespace-pre-line { white-space: pre-line; }\n`;
574
+ css += `.whitespace-pre-wrap { white-space: pre-wrap; }\n`;
575
+ css += `.whitespace-break-spaces { white-space: break-spaces; }\n`;
576
+ css += `.text-wrap { text-wrap: wrap; }\n`;
577
+ css += `.text-nowrap { text-wrap: nowrap; }\n`;
578
+ css += `.text-balance { text-wrap: balance; }\n`;
579
+ css += `.text-pretty { text-wrap: pretty; }\n`;
580
+ css += `.break-normal { overflow-wrap: normal; word-break: normal; }\n`;
581
+ css += `.break-words { overflow-wrap: break-word; }\n`;
495
582
  css += `.break-all { word-break: break-all; }\n`;
496
-
497
- // Line height
498
- css += `.leading-tight { line-height: 1.2; }\n`;
583
+ css += `.break-keep { word-break: keep-all; }\n`;
584
+ css += `.hyphens-none { hyphens: none; }\n`;
585
+ css += `.hyphens-manual { hyphens: manual; }\n`;
586
+ css += `.hyphens-auto { hyphens: auto; }\n`;
587
+
588
+ css += `.leading-none { line-height: 1; }\n`;
589
+ css += `.leading-tight { line-height: 1.25; }\n`;
590
+ css += `.leading-snug { line-height: 1.375; }\n`;
499
591
  css += `.leading-normal { line-height: 1.5; }\n`;
500
- css += `.leading-relaxed { line-height: 1.75; }\n`;
592
+ css += `.leading-relaxed { line-height: 1.625; }\n`;
593
+ css += `.leading-loose { line-height: 2; }\n`;
594
+ css += `.text-display { font-size: clamp(2.5rem, 6vw, 4rem); }\n`;
501
595
 
502
- // Letter spacing
503
596
  css += `.tracking-tighter { letter-spacing: -0.05em; }\n`;
504
- css += `.tracking-tight { letter-spacing: -0.02em; }\n`;
597
+ css += `.tracking-tight { letter-spacing: -0.025em; }\n`;
505
598
  css += `.tracking-normal { letter-spacing: 0em; }\n`;
506
- css += `.tracking-wide { letter-spacing: 0.02em; }\n`;
599
+ css += `.tracking-wide { letter-spacing: 0.025em; }\n`;
507
600
  css += `.tracking-wider { letter-spacing: 0.05em; }\n`;
508
601
  css += `.tracking-widest { letter-spacing: 0.1em; }\n`;
509
602
 
510
- // Text decoration
511
- css += `.underline { text-decoration: underline; }\n`;
512
- css += `.no-underline { text-decoration: none; }\n`;
513
- css += `.line-through { text-decoration: line-through; }\n`;
603
+ css += `.underline { text-decoration-line: underline; }\n`;
604
+ css += `.overline { text-decoration-line: overline; }\n`;
605
+ css += `.line-through { text-decoration-line: line-through; }\n`;
606
+ css += `.no-underline { text-decoration-line: none; }\n`;
607
+ css += `.decoration-solid { text-decoration-style: solid; }\n`;
608
+ css += `.decoration-double { text-decoration-style: double; }\n`;
609
+ css += `.decoration-dotted { text-decoration-style: dotted; }\n`;
610
+ css += `.decoration-dashed { text-decoration-style: dashed; }\n`;
611
+ css += `.decoration-wavy { text-decoration-style: wavy; }\n`;
514
612
  css += `.underline-offset-auto { text-underline-offset: auto; }\n`;
515
- css += `.underline-offset-1 { text-underline-offset: 1px; }\n`;
516
- css += `.underline-offset-2 { text-underline-offset: 2px; }\n`;
517
- css += `.underline-offset-4 { text-underline-offset: 4px; }\n`;
518
- css += `.underline-offset-8 { text-underline-offset: 8px; }\n`;
613
+ [0, 1, 2, 4, 8].forEach(value => {
614
+ css += `.underline-offset-${value} { text-underline-offset: ${value}px; }\n`;
615
+ });
519
616
  css += `.decoration-auto { text-decoration-thickness: auto; }\n`;
520
617
  css += `.decoration-from-font { text-decoration-thickness: from-font; }\n`;
521
- css += `.decoration-1 { text-decoration-thickness: 1px; }\n`;
522
- css += `.decoration-2 { text-decoration-thickness: 2px; }\n`;
523
- css += `.decoration-4 { text-decoration-thickness: 4px; }\n`;
618
+ [0, 1, 2, 4, 8].forEach(value => {
619
+ css += `.decoration-${value} { text-decoration-thickness: ${value}px; }\n`;
620
+ });
524
621
 
525
- // Font variant numeric
526
622
  css += `.normal-nums { font-variant-numeric: normal; }\n`;
527
623
  css += `.ordinal { font-variant-numeric: ordinal; }\n`;
528
624
  css += `.slashed-zero { font-variant-numeric: slashed-zero; }\n`;
@@ -533,17 +629,19 @@ function generateTypographyUtilities(config) {
533
629
  css += `.diagonal-fractions { font-variant-numeric: diagonal-fractions; }\n`;
534
630
  css += `.stacked-fractions { font-variant-numeric: stacked-fractions; }\n`;
535
631
 
536
- // Text transform
537
632
  css += `.uppercase { text-transform: uppercase; }\n`;
538
633
  css += `.lowercase { text-transform: lowercase; }\n`;
539
634
  css += `.capitalize { text-transform: capitalize; }\n`;
635
+ css += `.normal-case { text-transform: none; }\n`;
540
636
 
541
- // Font family utilities
542
637
  css += `.font-sans { font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }\n`;
543
638
  css += `.font-serif { font-family: Georgia, "Times New Roman", serif; }\n`;
544
639
  css += `.font-mono { font-family: "Menlo", "Monaco", "Courier New", monospace; }\n`;
545
640
  css += `.font-inter { font-family: "Inter", system-ui, sans-serif; }\n`;
546
641
  css += `.font-lexend { font-family: "Lexend", system-ui, sans-serif; }\n`;
642
+ css += `.font-dm-sans { font-family: "DM Sans", system-ui, sans-serif; }\n`;
643
+ css += `.font-nunito { font-family: "Nunito", system-ui, sans-serif; }\n`;
644
+ css += `.font-atkinson { font-family: "Atkinson Hyperlegible", system-ui, sans-serif; }\n`;
547
645
 
548
646
  css += `\n`;
549
647
  return css;
@@ -556,53 +654,74 @@ function generateTypographyUtilities(config) {
556
654
  function generateBorderUtilities(config) {
557
655
  let css = `/* Borders & Radius */\n`;
558
656
 
559
- // Border widths from config
560
657
  const borderWidths = config.spacing.borderWidths || [0, 2, 4, 8];
561
658
 
562
- // Border width
563
- css += `.border { border-width: 1px; }\n`;
659
+ css += `.border { border-width: 1px; border-style: solid; }\n`;
564
660
  borderWidths.forEach(width => {
565
661
  css += `.border-${width} { border-width: ${width}px; }\n`;
566
662
  });
567
663
 
568
- // Border sides (default 1px + widths)
664
+ const sides = {
665
+ t: 'top', r: 'right', b: 'bottom', l: 'left',
666
+ x: ['left', 'right'], y: ['top', 'bottom'],
667
+ s: 'inline-start', e: 'inline-end'
668
+ };
569
669
 
570
- ['t', 'r', 'b', 'l'].forEach(side => {
571
- css += `.border-${side} { border-${side === 't' ? 'top' : side === 'r' ? 'right' : side === 'b' ? 'bottom' : 'left'}-width: 1px; }\n`;
670
+ Object.entries(sides).forEach(([side, value]) => {
671
+ if (Array.isArray(value)) {
672
+ css += `.border-${side} { border-${value[0]}-width: 1px; border-${value[1]}-width: 1px; border-${value[0]}-style: solid; border-${value[1]}-style: solid; }\n`;
673
+ } else {
674
+ css += `.border-${side} { border-${value}-width: 1px; border-${value}-style: solid; }\n`;
675
+ }
572
676
  });
573
677
 
574
678
  borderWidths.forEach(width => {
575
- ['t', 'r', 'b', 'l'].forEach(side => {
576
- const sideMap = { t: 'top', r: 'right', b: 'bottom', l: 'left' };
577
- css += `.border-${side}-${width} { border-${sideMap[side]}-width: ${width}px; }\n`;
679
+ Object.entries(sides).forEach(([side, value]) => {
680
+ if (Array.isArray(value)) {
681
+ css += `.border-${side}-${width} { border-${value[0]}-width: ${width}px; border-${value[1]}-width: ${width}px; border-${value[0]}-style: solid; border-${value[1]}-style: solid; }\n`;
682
+ } else {
683
+ css += `.border-${side}-${width} { border-${value}-width: ${width}px; border-${value}-style: solid; }\n`;
684
+ }
578
685
  });
579
686
  });
580
687
 
581
- // Border style
582
688
  css += `.border-solid { border-style: solid; }\n`;
583
689
  css += `.border-dashed { border-style: dashed; }\n`;
584
690
  css += `.border-dotted { border-style: dotted; }\n`;
585
691
  css += `.border-double { border-style: double; }\n`;
692
+ css += `.border-hidden { border-style: hidden; }\n`;
586
693
  css += `.border-none { border-style: none; }\n`;
694
+ css += `.border-transparent { border-color: transparent; }\n`;
695
+ css += `.border-current { border-color: currentColor; }\n`;
696
+ css += `.border-black { border-color: #000000; }\n`;
697
+ css += `.border-white { border-color: #ffffff; }\n`;
587
698
 
588
- // Border radius (base)
589
699
  const baseRadius = config.spacing.borderRadius['base'] || '8px';
590
700
  css += `.rounded { border-radius: ${baseRadius}; }\n`;
591
701
 
592
- // Border radius (named)
593
702
  Object.entries(config.spacing.borderRadius).forEach(([name, value]) => {
594
703
  css += `.rounded-${name} { border-radius: ${value}; }\n`;
595
704
  });
596
705
 
597
- // Border radius per side
598
- css += `.rounded-t { border-top-left-radius: ${baseRadius}; border-top-right-radius: ${baseRadius}; }\n`;
599
- css += `.rounded-b { border-bottom-left-radius: ${baseRadius}; border-bottom-right-radius: ${baseRadius}; }\n`;
600
- css += `.rounded-l { border-top-left-radius: ${baseRadius}; border-bottom-left-radius: ${baseRadius}; }\n`;
601
- css += `.rounded-r { border-top-right-radius: ${baseRadius}; border-bottom-right-radius: ${baseRadius}; }\n`;
602
- css += `.rounded-tl { border-top-left-radius: ${baseRadius}; }\n`;
603
- css += `.rounded-tr { border-top-right-radius: ${baseRadius}; }\n`;
604
- css += `.rounded-bl { border-bottom-left-radius: ${baseRadius}; }\n`;
605
- css += `.rounded-br { border-bottom-right-radius: ${baseRadius}; }\n`;
706
+ const radiusTargets = {
707
+ t: ['top-left', 'top-right'], r: ['top-right', 'bottom-right'],
708
+ b: ['bottom-right', 'bottom-left'], l: ['top-left', 'bottom-left'],
709
+ tl: ['top-left'], tr: ['top-right'], br: ['bottom-right'], bl: ['bottom-left']
710
+ };
711
+
712
+ Object.entries(radiusTargets).forEach(([side, corners]) => {
713
+ corners.forEach(corner => {
714
+ css += `.rounded-${side} { border-${corner}-radius: ${baseRadius}; }\n`;
715
+ });
716
+ });
717
+
718
+ Object.entries(config.spacing.borderRadius).forEach(([name, value]) => {
719
+ Object.entries(radiusTargets).forEach(([side, corners]) => {
720
+ corners.forEach(corner => {
721
+ css += `.rounded-${side}-${name} { border-${corner}-radius: ${value}; }\n`;
722
+ });
723
+ });
724
+ });
606
725
 
607
726
  css += `\n`;
608
727
  return css;
@@ -625,7 +744,8 @@ function generateBaseStyles(config) {
625
744
  // Line height hints per size — keeps headings tighter than body text
626
745
  const lineHeightMap = {
627
746
  xs: '1.5', sm: '1.5', base: '1.6', lg: '1.6',
628
- xl: '1.5', '2xl': '1.4', '3xl': '1.35', '4xl': '1.3'
747
+ xl: '1.5', '2xl': '1.4', '3xl': '1.35', '4xl': '1.3',
748
+ '5xl': '1.15', '6xl': '1.1', '7xl': '1.05', '8xl': '1', '9xl': '1'
629
749
  };
630
750
 
631
751
  let css = '\n /* Base element styles (from baseStyles in emily.config.json) */\n';
@@ -645,26 +765,29 @@ function generateBaseStyles(config) {
645
765
 
646
766
  function generateColourUtilities(colours) {
647
767
  let css = `/* Colours: Background, Text, Borders, Accents */\n`;
768
+ // Uses CSS custom properties rather than hardcoded hex so colour utilities
769
+ // can be overridden via variable redefinition (e.g. dark mode, theme layers).
770
+ // The hex values are still declared as --color-* tokens in @layer theme.
648
771
 
649
772
  Object.entries(colours).forEach(([colourName, shades]) => {
650
773
  // Background colours
651
- Object.entries(shades).forEach(([shade, hex]) => {
652
- css += `.bg-${colourName}-${shade} { background-color: ${hex}; }\n`;
774
+ Object.entries(shades).forEach(([shade]) => {
775
+ css += `.bg-${colourName}-${shade} { background-color: var(--color-${colourName}-${shade}); }\n`;
653
776
  });
654
777
 
655
778
  // Text colours
656
- Object.entries(shades).forEach(([shade, hex]) => {
657
- css += `.text-${colourName}-${shade} { color: ${hex}; }\n`;
779
+ Object.entries(shades).forEach(([shade]) => {
780
+ css += `.text-${colourName}-${shade} { color: var(--color-${colourName}-${shade}); }\n`;
658
781
  });
659
782
 
660
783
  // Border colours
661
- Object.entries(shades).forEach(([shade, hex]) => {
662
- css += `.border-${colourName}-${shade} { border-color: ${hex}; }\n`;
784
+ Object.entries(shades).forEach(([shade]) => {
785
+ css += `.border-${colourName}-${shade} { border-color: var(--color-${colourName}-${shade}); }\n`;
663
786
  });
664
787
 
665
788
  // Accent colours (for form elements like checkboxes, radio buttons)
666
- Object.entries(shades).forEach(([shade, hex]) => {
667
- css += `.accent-${colourName}-${shade} { accent-color: ${hex}; }\n`;
789
+ Object.entries(shades).forEach(([shade]) => {
790
+ css += `.accent-${colourName}-${shade} { accent-color: var(--color-${colourName}-${shade}); }\n`;
668
791
  });
669
792
  });
670
793
 
@@ -876,6 +999,330 @@ function generatePatternComponents() {
876
999
  gap: var(--space-4, 1rem);
877
1000
  align-items: center;
878
1001
  }
1002
+
1003
+ /* ---- Layout ---- */
1004
+
1005
+ /* Constrained width container — 1100px max, full-width on small screens */
1006
+ .width-container {
1007
+ width: 100%;
1008
+ max-width: 1100px;
1009
+ margin-inline: auto;
1010
+ padding-inline: var(--space-4, 1rem);
1011
+ }
1012
+
1013
+ @media (min-width: 640px) {
1014
+ .width-container {
1015
+ padding-inline: var(--space-6, 1.5rem);
1016
+ }
1017
+ }
1018
+
1019
+ @media (min-width: 1024px) {
1020
+ .width-container {
1021
+ padding-inline: var(--space-8, 2rem);
1022
+ }
1023
+ }
1024
+
1025
+ @media (min-width: 1140px) {
1026
+ .width-container {
1027
+ padding-inline: 0;
1028
+ }
1029
+ }
1030
+
1031
+ /* ---- Forms ---- */
1032
+
1033
+ .field-container {
1034
+ display: flex;
1035
+ flex-direction: column;
1036
+ gap: var(--space-2, 0.5rem);
1037
+ margin-bottom: var(--space-6, 1.5rem);
1038
+ }
1039
+
1040
+ .field-container label {
1041
+ display: block;
1042
+ font-weight: var(--font-weight-semibold, 600);
1043
+ color: var(--color-neutral-90);
1044
+ font-size: var(--text-base, 16px);
1045
+ line-height: 1.4;
1046
+ margin-bottom: var(--space-1, 0.25rem);
1047
+ }
1048
+
1049
+ fieldset {
1050
+ border: none;
1051
+ padding: 0;
1052
+ margin: 0 0 var(--space-6, 1.5rem);
1053
+ }
1054
+
1055
+ fieldset legend {
1056
+ display: block;
1057
+ font-size: var(--text-lg, 18px);
1058
+ font-weight: var(--font-weight-semibold, 600);
1059
+ margin-bottom: var(--space-3, 0.75rem);
1060
+ color: var(--color-neutral-90);
1061
+ padding: 0;
1062
+ }
1063
+
1064
+ .form-hint {
1065
+ font-size: var(--text-sm, 14px);
1066
+ color: var(--color-neutral-60);
1067
+ margin-bottom: var(--space-1, 0.25rem);
1068
+ }
1069
+
1070
+ input[type="text"],
1071
+ input[type="email"],
1072
+ input[type="password"],
1073
+ input[type="number"],
1074
+ input[type="tel"],
1075
+ input[type="url"],
1076
+ input[type="search"],
1077
+ input[type="date"],
1078
+ select,
1079
+ textarea {
1080
+ width: 100%;
1081
+ max-width: 100%;
1082
+ padding: var(--space-3, 0.75rem) var(--space-4, 1rem);
1083
+ border: 2px solid var(--color-neutral-30);
1084
+ border-radius: 8px;
1085
+ background-color: #ffffff;
1086
+ color: var(--color-neutral-90);
1087
+ font-family: inherit;
1088
+ font-size: var(--text-base, 16px);
1089
+ line-height: var(--leading-base, 1.6);
1090
+ appearance: none;
1091
+ transition: border-color 200ms ease, box-shadow 200ms ease;
1092
+ }
1093
+
1094
+ select {
1095
+ 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");
1096
+ background-position: right var(--space-2, 0.5rem) center;
1097
+ background-repeat: no-repeat;
1098
+ background-size: 1.5em 1.5em;
1099
+ padding-right: var(--space-10, 2.5rem);
1100
+ cursor: pointer;
1101
+ }
1102
+
1103
+ textarea {
1104
+ min-height: 120px;
1105
+ resize: vertical;
1106
+ }
1107
+
1108
+ input[type="text"]:focus,
1109
+ input[type="email"]:focus,
1110
+ input[type="password"]:focus,
1111
+ input[type="number"]:focus,
1112
+ input[type="tel"]:focus,
1113
+ input[type="url"]:focus,
1114
+ input[type="search"]:focus,
1115
+ input[type="date"]:focus,
1116
+ select:focus,
1117
+ textarea:focus {
1118
+ outline: 2px solid var(--color-neutral-80);
1119
+ outline-offset: 3px;
1120
+ border-color: var(--color-neutral-80);
1121
+ box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
1122
+ }
1123
+
1124
+ .checkbox-group,
1125
+ .radio-group {
1126
+ display: flex;
1127
+ align-items: center;
1128
+ gap: var(--space-3, 0.75rem);
1129
+ margin-bottom: var(--space-4, 1rem);
1130
+ }
1131
+
1132
+ .checkbox-group label,
1133
+ .radio-group label {
1134
+ font-weight: var(--font-weight-normal, 400);
1135
+ margin-bottom: 0;
1136
+ cursor: pointer;
1137
+ font-size: var(--text-base, 16px);
1138
+ }
1139
+
1140
+ input[type="checkbox"] {
1141
+ width: 1.5rem;
1142
+ height: 1.5rem;
1143
+ margin: 0;
1144
+ cursor: pointer;
1145
+ accent-color: var(--color-brand-80);
1146
+ flex-shrink: 0;
1147
+ }
1148
+
1149
+ input[type="checkbox"]:focus {
1150
+ outline: 2px solid var(--color-neutral-80);
1151
+ outline-offset: 3px;
1152
+ box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
1153
+ }
1154
+
1155
+ input[type="radio"] {
1156
+ width: 1.5rem;
1157
+ height: 1.5rem;
1158
+ margin: 0;
1159
+ border-radius: 50%;
1160
+ appearance: none;
1161
+ background-color: #ffffff;
1162
+ border: 2px solid var(--color-neutral-30);
1163
+ display: grid;
1164
+ place-content: center;
1165
+ cursor: pointer;
1166
+ flex-shrink: 0;
1167
+ transition: background-color 200ms ease, border-color 200ms ease;
1168
+ }
1169
+
1170
+ input[type="radio"]::before {
1171
+ content: "";
1172
+ width: 0.75rem;
1173
+ height: 0.75rem;
1174
+ border-radius: 50%;
1175
+ transform: scale(0);
1176
+ transition: 120ms transform ease-in-out;
1177
+ background-color: var(--color-brand-80);
1178
+ }
1179
+
1180
+ input[type="radio"]:checked {
1181
+ border-color: var(--color-brand-80);
1182
+ }
1183
+
1184
+ input[type="radio"]:checked::before {
1185
+ transform: scale(1);
1186
+ }
1187
+
1188
+ input[type="radio"]:hover {
1189
+ background-color: var(--color-brand-10);
1190
+ border-color: var(--color-brand-80);
1191
+ }
1192
+
1193
+ input[type="radio"]:focus {
1194
+ outline: 2px solid var(--color-neutral-80);
1195
+ outline-offset: 3px;
1196
+ border-radius: 50%;
1197
+ box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
1198
+ }
1199
+
1200
+ input[aria-invalid="true"] {
1201
+ border-color: var(--color-error-80) !important;
1202
+ border-width: 3px;
1203
+ }
1204
+
1205
+ .form-error-message {
1206
+ font-size: var(--text-sm, 14px);
1207
+ font-weight: var(--font-weight-bold, 700);
1208
+ color: var(--color-error-80);
1209
+ margin-top: var(--space-1, 0.25rem);
1210
+ display: block;
1211
+ }
1212
+
1213
+ .error-summary {
1214
+ border: 4px solid var(--color-error-80);
1215
+ padding: var(--space-6, 1.5rem);
1216
+ margin-bottom: var(--space-8, 2rem);
1217
+ border-radius: 8px;
1218
+ }
1219
+
1220
+ .error-summary ul {
1221
+ list-style: disc;
1222
+ padding-left: var(--space-5, 1.25rem);
1223
+ }
1224
+
1225
+ .error-summary a {
1226
+ color: var(--color-error-80);
1227
+ }
1228
+
1229
+ /* ---- Buttons ---- */
1230
+
1231
+ .btn {
1232
+ display: inline-flex;
1233
+ align-items: center;
1234
+ justify-content: center;
1235
+ padding: var(--space-3, 0.75rem) var(--space-6, 1.5rem);
1236
+ font-weight: var(--font-weight-semibold, 600);
1237
+ border-radius: 8px;
1238
+ cursor: pointer;
1239
+ transition: background-color 200ms ease, border-color 200ms ease, color 200ms ease;
1240
+ border: 2px solid transparent;
1241
+ text-align: center;
1242
+ min-height: 3rem;
1243
+ font-size: var(--text-base, 16px);
1244
+ text-decoration: none;
1245
+ font-family: inherit;
1246
+ line-height: 1;
1247
+ }
1248
+
1249
+ .btn-primary {
1250
+ background-color: var(--color-brand-80);
1251
+ color: #ffffff;
1252
+ border-color: transparent;
1253
+ }
1254
+
1255
+ .btn-primary:hover {
1256
+ background-color: var(--color-brand-90);
1257
+ }
1258
+
1259
+ .btn-primary:focus-visible {
1260
+ outline: 2px solid var(--color-neutral-80);
1261
+ outline-offset: 3px;
1262
+ box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
1263
+ }
1264
+
1265
+ .btn-secondary {
1266
+ background-color: #ffffff;
1267
+ color: var(--color-accent-80);
1268
+ border-color: var(--color-accent-80);
1269
+ }
1270
+
1271
+ .btn-secondary:hover {
1272
+ background-color: var(--color-accent-10);
1273
+ color: var(--color-accent-90);
1274
+ border-color: var(--color-accent-90);
1275
+ }
1276
+
1277
+ .btn-secondary:focus-visible {
1278
+ outline: 2px solid var(--color-neutral-80);
1279
+ outline-offset: 3px;
1280
+ box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
1281
+ }
1282
+
1283
+ .btn-ghost {
1284
+ background-color: transparent;
1285
+ color: var(--color-neutral-80);
1286
+ border-color: transparent;
1287
+ }
1288
+
1289
+ .btn-ghost:hover {
1290
+ background-color: var(--color-neutral-10);
1291
+ }
1292
+
1293
+ .btn-ghost:focus-visible {
1294
+ outline: 2px solid var(--color-neutral-80);
1295
+ outline-offset: 3px;
1296
+ box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
1297
+ }
1298
+
1299
+ .btn-danger {
1300
+ background-color: var(--color-error-80);
1301
+ color: #ffffff;
1302
+ border-color: transparent;
1303
+ }
1304
+
1305
+ .btn-danger:hover {
1306
+ background-color: var(--color-error-90);
1307
+ }
1308
+
1309
+ .btn-danger:focus-visible {
1310
+ outline: 2px solid var(--color-neutral-80);
1311
+ outline-offset: 3px;
1312
+ box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
1313
+ }
1314
+
1315
+ .btn-sm {
1316
+ padding: var(--space-2, 0.5rem) var(--space-4, 1rem);
1317
+ font-size: var(--text-sm, 14px);
1318
+ min-height: 2.25rem;
1319
+ }
1320
+
1321
+ .btn-lg {
1322
+ padding: var(--space-4, 1rem) var(--space-8, 2rem);
1323
+ font-size: var(--text-lg, 18px);
1324
+ min-height: 3.5rem;
1325
+ }
879
1326
  `;
880
1327
  }
881
1328
 
@@ -1022,6 +1469,59 @@ function buildFullFramework() {
1022
1469
  overflow-wrap: break-word;
1023
1470
  }
1024
1471
 
1472
+ /* Base heading scale */
1473
+ h1 {
1474
+ font-size: var(--text-4xl, 36px);
1475
+ line-height: var(--leading-4xl, 1.3);
1476
+ font-weight: var(--font-weight-bold, 700);
1477
+ margin-bottom: var(--space-6, 1.5rem);
1478
+ }
1479
+
1480
+ h2 {
1481
+ font-size: var(--text-3xl, 30px);
1482
+ line-height: var(--leading-3xl, 1.4);
1483
+ font-weight: var(--font-weight-bold, 700);
1484
+ margin-bottom: var(--space-5, 1.25rem);
1485
+ }
1486
+
1487
+ h3 {
1488
+ font-size: var(--text-2xl, 24px);
1489
+ line-height: var(--leading-2xl, 1.4);
1490
+ font-weight: var(--font-weight-semibold, 600);
1491
+ margin-bottom: var(--space-4, 1rem);
1492
+ }
1493
+
1494
+ h4 {
1495
+ font-size: var(--text-xl, 20px);
1496
+ line-height: var(--leading-xl, 1.6);
1497
+ font-weight: var(--font-weight-semibold, 600);
1498
+ margin-bottom: var(--space-3, 0.75rem);
1499
+ }
1500
+
1501
+ h5 {
1502
+ font-size: var(--text-lg, 18px);
1503
+ line-height: var(--leading-lg, 1.6);
1504
+ font-weight: var(--font-weight-medium, 500);
1505
+ margin-bottom: var(--space-3, 0.75rem);
1506
+ }
1507
+
1508
+ h6 {
1509
+ font-size: var(--text-base, 16px);
1510
+ line-height: var(--leading-base, 1.6);
1511
+ font-weight: var(--font-weight-medium, 500);
1512
+ margin-bottom: var(--space-2, 0.5rem);
1513
+ }
1514
+
1515
+ p {
1516
+ font-size: var(--text-base, 16px);
1517
+ line-height: var(--leading-base, 1.6);
1518
+ margin-bottom: var(--space-4, 1rem);
1519
+ }
1520
+
1521
+ p:last-child {
1522
+ margin-bottom: 0;
1523
+ }
1524
+
1025
1525
  code {
1026
1526
  font-family: "Menlo", "Monaco", "Courier New", monospace;
1027
1527
  font-size: 0.875em;
@@ -1044,7 +1544,7 @@ function buildFullFramework() {
1044
1544
  background-color: #0d0c0b;
1045
1545
  color: #e4e0db;
1046
1546
  padding: 1.25rem;
1047
- border-radius: 6px;
1547
+ border-radius: 0 0 6px;
1048
1548
  overflow-x: auto;
1049
1549
  font-family: "Menlo", "Monaco", "Courier New", monospace;
1050
1550
  font-size: 0.875rem;
@@ -1180,8 +1680,10 @@ module.exports = {
1180
1680
  generateSemanticColourUtilities,
1181
1681
  generateTypographyUtilities,
1182
1682
  generateSpacingUtilities,
1683
+ generateFlexboxUtilities,
1684
+ generateGridUtilities,
1183
1685
  addStateVariants,
1184
1686
  addResponsiveVariants,
1185
1687
  generateFontCSS,
1186
1688
  codeUtilities,
1187
- };
1689
+ };