@mathscan/math-exercise-engine 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/dist/style.css CHANGED
@@ -238,6 +238,10 @@
238
238
  unicode-bidi: plaintext;
239
239
  }
240
240
 
241
+ .text-item-number {
242
+ font-variant-numeric: tabular-nums;
243
+ }
244
+
241
245
  .text-item-arabic {
242
246
  /* Direction is handled by parent text-group with unicode-bidi: plaintext */
243
247
  }
@@ -586,6 +590,49 @@ button.drag-option {
586
590
  color: white;
587
591
  }
588
592
 
593
+ /* ========================================
594
+ NUMERIC KEYPAD V2 (diamond / 45-degree style)
595
+ ======================================== */
596
+
597
+ .diamond-keypad {
598
+ display: flex;
599
+ flex-direction: column;
600
+ align-items: center;
601
+ gap: 0;
602
+ padding: 0;
603
+ }
604
+
605
+ .diamond-keypad-row {
606
+ display: flex;
607
+ gap: 20px;
608
+ }
609
+
610
+ .diamond-keypad .keypad-btn {
611
+ transform: rotate(45deg);
612
+ }
613
+
614
+ .diamond-keypad .keypad-btn span {
615
+ transform: rotate(-45deg);
616
+ display: inline-flex;
617
+ align-items: center;
618
+ justify-content: center;
619
+ pointer-events: none;
620
+ line-height: 1;
621
+ }
622
+
623
+ .diamond-keypad .keypad-btn:hover {
624
+ box-shadow: 0 4px 12px rgba(0, 122, 204, 0.18);
625
+ }
626
+
627
+ .diamond-keypad .keypad-btn:active {
628
+ transform: rotate(45deg) scale(0.92);
629
+ }
630
+
631
+ .diamond-keypad .keypad-btn-backspace {
632
+ border-color: var(--exercise-accent, #705dc0);
633
+ color: var(--exercise-accent, #705dc0);
634
+ }
635
+
589
636
  /* ========================================
590
637
  TABLE LAYOUT
591
638
  ======================================== */
@@ -664,7 +711,6 @@ th.exercise-table-cell {
664
711
  }
665
712
 
666
713
  .fraction-input {
667
- width: 3.2em !important;
668
714
  padding: 0.15em 0.2em;
669
715
  font-size: 0.9em;
670
716
  text-align: center;
@@ -679,6 +725,152 @@ th.exercise-table-cell {
679
725
  display: inline-flex;
680
726
  }
681
727
 
728
+ /* ========================================
729
+ VISUAL FRACTION WIDGET
730
+ ======================================== */
731
+
732
+ .visual-fraction-widget {
733
+ --visual-fraction-size: clamp(7rem, 24vw, 12rem);
734
+ --visual-fraction-part-size: 1.35rem;
735
+ --visual-fraction-line-width: 1px;
736
+ --visual-fraction-svg-line-width: 0.5px;
737
+ --visual-fraction-gap: var(--visual-fraction-line-width);
738
+ --visual-fraction-border-radius: 0;
739
+ --visual-fraction-border-color: #705DC0;
740
+ --visual-fraction-empty-bg: var(--exercise-bg, #fff);
741
+ --visual-fraction-hover-color: #E6E0FF;
742
+ --visual-fraction-part-color: #AE9CFF;
743
+ --visual-fraction-part-text-color: #fff;
744
+ --visual-fraction-label-color: var(--exercise-text-light, #555);
745
+ display: inline-flex;
746
+ flex-direction: column;
747
+ align-items: center;
748
+ gap: 0.35rem;
749
+ max-width: 100%;
750
+ vertical-align: middle;
751
+ }
752
+
753
+ .visual-fraction-widget__parts {
754
+ display: grid;
755
+ gap: var(--visual-fraction-gap);
756
+ border: var(--visual-fraction-line-width) solid var(--visual-fraction-border-color);
757
+ border-radius: var(--visual-fraction-border-radius);
758
+ background: var(--visual-fraction-border-color);
759
+ max-width: 100%;
760
+ overflow: hidden;
761
+ }
762
+
763
+ .visual-fraction-widget__parts--horizontal {
764
+ grid-template-columns: repeat(var(--visual-fraction-divisions, 1), 1fr);
765
+ grid-template-rows: var(--visual-fraction-part-size);
766
+ width: var(--visual-fraction-size);
767
+ }
768
+
769
+ .visual-fraction-widget__parts--vertical {
770
+ grid-template-columns: var(--visual-fraction-part-size);
771
+ grid-template-rows: repeat(var(--visual-fraction-divisions, 1), 1fr);
772
+ height: var(--visual-fraction-size);
773
+ }
774
+
775
+ .visual-fraction-widget__part {
776
+ min-width: 0;
777
+ min-height: 0;
778
+ background: var(--visual-fraction-empty-bg);
779
+ color: var(--exercise-text, #333);
780
+ transition: background 0.2s ease, border-color 0.2s ease, filter 0.2s ease;
781
+ }
782
+
783
+ .visual-fraction-widget__part[role="button"] {
784
+ cursor: pointer;
785
+ }
786
+
787
+ .visual-fraction-widget__part[role="button"]:hover {
788
+ background: var(--visual-fraction-hover-color);
789
+ }
790
+
791
+ .visual-fraction-widget__part[role="button"]:focus-visible {
792
+ outline: 2px solid var(--exercise-border-focus, var(--exercise-primary, #007acc));
793
+ outline-offset: -2px;
794
+ }
795
+
796
+ .visual-fraction-widget__part--filled,
797
+ .visual-fraction-widget__part--answer {
798
+ background: var(--visual-fraction-part-color);
799
+ color: var(--visual-fraction-part-text-color);
800
+ }
801
+
802
+ .visual-fraction-widget__part--filled[role="button"]:hover,
803
+ .visual-fraction-widget__part--answer[role="button"]:hover {
804
+ background: var(--visual-fraction-part-color);
805
+ color: var(--visual-fraction-part-text-color);
806
+ }
807
+
808
+ .visual-fraction-widget__sector {
809
+ fill: var(--visual-fraction-empty-bg);
810
+ stroke: var(--visual-fraction-border-color);
811
+ stroke-width: var(--visual-fraction-svg-line-width);
812
+ stroke-linejoin: round;
813
+ stroke-linecap: round;
814
+ transition: fill 0.2s ease, filter 0.2s ease;
815
+ }
816
+
817
+ .visual-fraction-widget__sector[role="button"] {
818
+ cursor: pointer;
819
+ }
820
+
821
+ .visual-fraction-widget__sector[role="button"]:hover {
822
+ fill: var(--visual-fraction-hover-color);
823
+ }
824
+
825
+ .visual-fraction-widget__sector[role="button"]:focus-visible {
826
+ outline: none;
827
+ filter: drop-shadow(0 0 0.18rem var(--exercise-border-focus, var(--exercise-primary, #007acc)));
828
+ }
829
+
830
+ .visual-fraction-widget__sector.visual-fraction-widget__part--filled,
831
+ .visual-fraction-widget__sector.visual-fraction-widget__part--answer,
832
+ .visual-fraction-widget__sector.visual-fraction-widget__part--filled[role="button"]:hover,
833
+ .visual-fraction-widget__sector.visual-fraction-widget__part--answer[role="button"]:hover {
834
+ fill: var(--visual-fraction-part-color);
835
+ }
836
+
837
+ .visual-fraction-widget.widget--correct .visual-fraction-widget__parts {
838
+ border-color: var(--exercise-correct, #22c55e);
839
+ background: var(--exercise-correct, #22c55e);
840
+ }
841
+
842
+ .visual-fraction-widget.widget--incorrect .visual-fraction-widget__parts {
843
+ border-color: var(--exercise-incorrect, #ef4444);
844
+ background: var(--exercise-incorrect, #ef4444);
845
+ }
846
+
847
+ .visual-fraction-widget.widget--correct .visual-fraction-widget__sector {
848
+ stroke: var(--exercise-correct, #22c55e);
849
+ }
850
+
851
+ .visual-fraction-widget.widget--incorrect .visual-fraction-widget__sector {
852
+ stroke: var(--exercise-incorrect, #ef4444);
853
+ }
854
+
855
+ .visual-fraction-widget__shape {
856
+ width: var(--visual-fraction-size);
857
+ height: var(--visual-fraction-size);
858
+ overflow: visible;
859
+ }
860
+
861
+ .visual-fraction-widget--grid .visual-fraction-widget__parts {
862
+ grid-template-columns: repeat(var(--visual-fraction-grid-cols, 3), 1fr);
863
+ grid-template-rows: repeat(var(--visual-fraction-grid-rows, 3), 1fr);
864
+ width: var(--visual-fraction-size);
865
+ height: var(--visual-fraction-size);
866
+ }
867
+
868
+ .visual-fraction-widget__label {
869
+ color: var(--visual-fraction-label-color);
870
+ font-size: 0.9em;
871
+ font-weight: var(--exercise-font-weight, 500);
872
+ }
873
+
682
874
  /* ========================================
683
875
  MATCHER WIDGET
684
876
  ======================================== */
@@ -686,101 +878,76 @@ th.exercise-table-cell {
686
878
  .matcher-widget {
687
879
  display: flex;
688
880
  flex-direction: column;
689
- gap: 12px;
690
- padding: 16px;
881
+ gap: clamp(10px, 2.5vw, 16px);
882
+ width: min(100%, 580px);
883
+ margin-inline: auto;
691
884
  }
692
885
 
693
886
  .matcher-row {
694
887
  display: grid;
695
- grid-template-columns: repeat(var(--matcher-cols, 3), 1fr);
696
- gap: 12px;
888
+ grid-template-columns: repeat(var(--matcher-cols, 3), minmax(0, 1fr));
889
+ gap: clamp(10px, 2.5vw, 16px);
697
890
  }
698
891
 
699
892
  .matcher-card {
700
893
  display: flex;
701
- flex-wrap: wrap;
702
894
  align-items: center;
703
895
  justify-content: center;
704
- width: 100%;
705
- min-height: 56px;
706
- padding: 14px 12px;
707
- border: 2.5px solid var(--exercise-border, #ddd);
708
- border-radius: 14px;
709
- background: var(--exercise-bg, #fff);
710
- color: var(--exercise-text, #333);
711
- font-size: var(--exercise-font-size, 1rem);
712
- font-weight: var(--exercise-font-weight, 400);
713
- line-height: 1.4;
896
+ min-height: clamp(72px, 14vw, 96px);
897
+ padding: clamp(8px, 2vw, 12px);
898
+ border: 2px solid #FFCA76;
899
+ border-radius: clamp(12px, 3vw, 17px);
900
+ background: #FFFFFF;
901
+ color: #333333;
714
902
  text-align: center;
715
- overflow-wrap: break-word;
716
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
717
903
  cursor: pointer;
718
- user-select: none;
904
+ box-shadow: 0 4px 0 rgba(255, 202, 118, 0.22);
719
905
  transition:
720
- border-color 0.2s ease,
721
- background 0.2s ease,
722
- color 0.2s ease,
723
- box-shadow 0.2s ease,
724
- transform 0.2s ease,
725
- filter 0.2s ease;
906
+ background 280ms cubic-bezier(0.34, 1.56, 0.64, 1),
907
+ border-color 280ms cubic-bezier(0.34, 1.56, 0.64, 1),
908
+ transform 220ms cubic-bezier(0.34, 1.56, 0.64, 1),
909
+ box-shadow 280ms ease;
726
910
  }
727
911
 
912
+ .matcher-card:active {
913
+ transform: scale(0.95);
914
+ }
915
+
916
+ /* Intentionally dropped — kept for reference if hover feedback is restored.
728
917
  .matcher-card:hover {
729
918
  border-color: var(--exercise-accent, #705dc0);
730
919
  box-shadow: 0 4px 10px rgba(112, 93, 192, 0.22);
731
- }
732
-
733
- .matcher-card:active {
734
- transform: scale(0.96);
735
- }
920
+ } */
736
921
 
737
922
  .matcher-card--selected {
738
- background: var(--matcher-card-color);
923
+ background: color-mix(in srgb, var(--matcher-card-color) 12%, #FFFFFF);
739
924
  border-color: var(--matcher-card-color);
740
- color: var(--matcher-card-text-color, #1a1a1a);
741
- transform: translateY(-3px);
742
- box-shadow: 0 6px 16px rgba(0, 0, 0, 0.18);
925
+ box-shadow: 0 4px 0 color-mix(in srgb, var(--matcher-card-color) 30%, transparent);
926
+ transform: scale(1.04);
743
927
  }
744
928
 
745
- /* Force all nested text descendants to inherit the button's color so
746
- validation/assignment colors aren't masked by their own color rules. */
747
-
748
- .matcher-card--selected * {
749
- color: inherit;
750
- }
751
-
752
- .matcher-card--selected:hover {
753
- border-color: var(--exercise-accent, #705dc0);
754
- filter: brightness(1.07);
929
+ .widget--correct .matcher-card--selected {
930
+ border-color: #43B925;
931
+ background: #F2FFEE;
932
+ box-shadow: 0 4px 0 rgba(67, 185, 37, 0.30);
755
933
  }
756
934
 
757
- .matcher-card--selected:active {
758
- transform: scale(0.96) translateY(0);
759
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
760
- filter: none;
761
- }
762
-
763
- .matcher-widget.widget--correct .matcher-card--selected {
764
- background: var(--exercise-correct, #22c55e);
765
- border-color: var(--exercise-correct, #22c55e);
766
- color: #fff;
935
+ .widget--incorrect .matcher-card--selected {
936
+ border-color: #D6234D;
937
+ background: #FFF0F7;
938
+ box-shadow: 0 4px 0 rgba(214, 35, 77, 0.25);
767
939
  }
768
940
 
769
- .matcher-widget.widget--correct .matcher-card:not(.matcher-card--selected) {
941
+ /* Intentionally dropped — kept for reference if unselected-card validation feedback is restored.
942
+ .widget--correct .matcher-card:not(.matcher-card--selected) {
770
943
  border-color: var(--exercise-correct, #22c55e);
771
944
  opacity: 0.5;
772
945
  }
773
946
 
774
- .matcher-widget.widget--incorrect .matcher-card--selected {
775
- background: var(--exercise-incorrect, #ef4444);
776
- border-color: var(--exercise-incorrect, #ef4444);
777
- color: #fff;
778
- }
779
-
780
- .matcher-widget.widget--incorrect .matcher-card:not(.matcher-card--selected) {
947
+ .widget--incorrect .matcher-card:not(.matcher-card--selected) {
781
948
  border-color: var(--exercise-incorrect, #ef4444);
782
949
  opacity: 0.5;
783
- }
950
+ } */
784
951
 
785
952
  /**
786
953
  * Number Line Styles
@@ -800,6 +967,10 @@ th.exercise-table-cell {
800
967
  margin-bottom: var(--exercise-margin-bottom);
801
968
  user-select: none;
802
969
  --nl-chip-zone-height: 3rem;
970
+ --nl-position-content-height: 0px;
971
+ --nl-position-content-min-height: 2.8rem;
972
+ --nl-visual-fraction-part-size: clamp(0.8rem, 2vw, 1rem);
973
+ --nl-visual-fraction-size: clamp(5.5rem, 18vw, 8rem);
803
974
  /* Visual gap between end ticks and arrowheads — pure CSS, no effect on calculations */
804
975
  --nl-arrow-gap: 8px;
805
976
  }
@@ -818,7 +989,7 @@ th.exercise-table-cell {
818
989
  .number-line-content-row {
819
990
  position: relative;
820
991
  width: 100%;
821
- min-height: 2.8rem;
992
+ min-height: max(var(--nl-position-content-min-height), var(--nl-position-content-height));
822
993
  }
823
994
 
824
995
  .number-line-position-content {
@@ -831,6 +1002,11 @@ th.exercise-table-cell {
831
1002
  z-index: 1;
832
1003
  }
833
1004
 
1005
+ .number-line-position-content .visual-fraction-widget {
1006
+ --visual-fraction-part-size: var(--nl-visual-fraction-part-size);
1007
+ --visual-fraction-size: var(--nl-visual-fraction-size);
1008
+ }
1009
+
834
1010
  /* ========================================
835
1011
  ROW 2 — AXIS (track + ticks + arrows + dots)
836
1012
  ======================================== */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mathscan/math-exercise-engine",
3
- "version": "1.0.15",
3
+ "version": "1.0.17",
4
4
  "description": "A DSL parser and validator for math exercises - transforms text-based exercise definitions into structured AST and JSON",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",