@mathscan/math-exercise-engine 1.0.15 → 1.0.16

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