@mathscan/math-exercise-engine 1.0.14 → 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
@@ -317,6 +317,16 @@ mark {
317
317
  font-weight: bold;
318
318
  }
319
319
 
320
+ /* ========================================
321
+ IMAGE
322
+ ======================================== */
323
+
324
+ .image-item {
325
+ vertical-align: middle;
326
+ max-width: 100%;
327
+ object-fit: contain;
328
+ }
329
+
320
330
  /* ========================================
321
331
  OPERATORS & SYMBOLS
322
332
  ======================================== */
@@ -576,6 +586,49 @@ button.drag-option {
576
586
  color: white;
577
587
  }
578
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
+
579
632
  /* ========================================
580
633
  TABLE LAYOUT
581
634
  ======================================== */
@@ -606,7 +659,8 @@ table.exercise-table {
606
659
  border-collapse: collapse;
607
660
  }
608
661
 
609
- td.exercise-table-cell, th.exercise-table-cell {
662
+ td.exercise-table-cell,
663
+ th.exercise-table-cell {
610
664
  border: 2px solid #7c6cf2;
611
665
  }
612
666
 
@@ -668,6 +722,152 @@ td.exercise-table-cell, th.exercise-table-cell {
668
722
  display: inline-flex;
669
723
  }
670
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
+
671
871
  /* ========================================
672
872
  MATCHER WIDGET
673
873
  ======================================== */
@@ -675,98 +875,76 @@ td.exercise-table-cell, th.exercise-table-cell {
675
875
  .matcher-widget {
676
876
  display: flex;
677
877
  flex-direction: column;
678
- gap: 12px;
679
- padding: 16px;
878
+ gap: clamp(10px, 2.5vw, 16px);
879
+ width: min(100%, 580px);
880
+ margin-inline: auto;
680
881
  }
681
882
 
682
883
  .matcher-row {
683
884
  display: grid;
684
- grid-template-columns: repeat(var(--matcher-cols, 3), 1fr);
685
- gap: 12px;
885
+ grid-template-columns: repeat(var(--matcher-cols, 3), minmax(0, 1fr));
886
+ gap: clamp(10px, 2.5vw, 16px);
686
887
  }
687
888
 
688
889
  .matcher-card {
689
890
  display: flex;
690
- flex-wrap: wrap;
691
891
  align-items: center;
692
892
  justify-content: center;
693
- gap: 0 6px;
694
- width: 100%;
695
- min-height: 56px;
696
- padding: 14px 12px;
697
- border: 2.5px solid var(--exercise-border, #ddd);
698
- border-radius: 14px;
699
- background: var(--exercise-bg, #fff);
700
- color: var(--exercise-text, #333);
701
- font-size: var(--exercise-font-size, 1rem);
702
- font-weight: var(--exercise-font-weight, 400);
703
- 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;
704
899
  text-align: center;
705
- overflow-wrap: break-word;
706
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
707
900
  cursor: pointer;
708
- user-select: none;
709
- transition: border-color 0.2s ease, background 0.2s ease,
710
- color 0.2s ease, box-shadow 0.2s ease,
711
- transform 0.2s ease, filter 0.2s ease;
712
- }
713
-
714
- .matcher-card:hover {
715
- border-color: var(--exercise-accent, #705DC0);
716
- box-shadow: 0 4px 10px rgba(112, 93, 192, 0.22);
901
+ box-shadow: 0 4px 0 rgba(255, 202, 118, 0.22);
902
+ transition:
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;
717
907
  }
718
908
 
719
909
  .matcher-card:active {
720
- transform: scale(0.96);
910
+ transform: scale(0.95);
721
911
  }
722
912
 
913
+ /* Intentionally dropped — kept for reference if hover feedback is restored.
914
+ .matcher-card:hover {
915
+ border-color: var(--exercise-accent, #705dc0);
916
+ box-shadow: 0 4px 10px rgba(112, 93, 192, 0.22);
917
+ } */
918
+
723
919
  .matcher-card--selected {
724
- background: var(--matcher-card-color);
920
+ background: color-mix(in srgb, var(--matcher-card-color) 12%, #FFFFFF);
725
921
  border-color: var(--matcher-card-color);
726
- color: var(--matcher-card-text-color, #1a1a1a);
727
- transform: translateY(-3px);
728
- box-shadow: 0 6px 16px rgba(0, 0, 0, 0.18);
729
- }
730
-
731
- /* Force all nested text descendants to inherit the button's color so
732
- validation/assignment colors aren't masked by their own color rules. */
733
-
734
- .matcher-card--selected * {
735
- color: inherit;
922
+ box-shadow: 0 4px 0 color-mix(in srgb, var(--matcher-card-color) 30%, transparent);
923
+ transform: scale(1.04);
736
924
  }
737
925
 
738
- .matcher-card--selected:hover {
739
- border-color: var(--exercise-accent, #705DC0);
740
- 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);
741
930
  }
742
931
 
743
- .matcher-card--selected:active {
744
- transform: scale(0.96) translateY(0);
745
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
746
- filter: none;
747
- }
748
-
749
- .matcher-widget.widget--correct .matcher-card--selected {
750
- background: var(--exercise-correct, #22c55e);
751
- border-color: var(--exercise-correct, #22c55e);
752
- 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);
753
936
  }
754
937
 
755
- .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) {
756
940
  border-color: var(--exercise-correct, #22c55e);
757
941
  opacity: 0.5;
758
942
  }
759
943
 
760
- .matcher-widget.widget--incorrect .matcher-card--selected {
761
- background: var(--exercise-incorrect, #ef4444);
762
- border-color: var(--exercise-incorrect, #ef4444);
763
- color: #fff;
764
- }
765
-
766
- .matcher-widget.widget--incorrect .matcher-card:not(.matcher-card--selected) {
944
+ .widget--incorrect .matcher-card:not(.matcher-card--selected) {
767
945
  border-color: var(--exercise-incorrect, #ef4444);
768
946
  opacity: 0.5;
769
- }
947
+ } */
770
948
 
771
949
  /**
772
950
  * Number Line Styles
@@ -786,6 +964,10 @@ td.exercise-table-cell, th.exercise-table-cell {
786
964
  margin-bottom: var(--exercise-margin-bottom);
787
965
  user-select: none;
788
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);
789
971
  /* Visual gap between end ticks and arrowheads — pure CSS, no effect on calculations */
790
972
  --nl-arrow-gap: 8px;
791
973
  }
@@ -804,7 +986,7 @@ td.exercise-table-cell, th.exercise-table-cell {
804
986
  .number-line-content-row {
805
987
  position: relative;
806
988
  width: 100%;
807
- min-height: 2.8rem;
989
+ min-height: max(var(--nl-position-content-min-height), var(--nl-position-content-height));
808
990
  }
809
991
 
810
992
  .number-line-position-content {
@@ -817,6 +999,11 @@ td.exercise-table-cell, th.exercise-table-cell {
817
999
  z-index: 1;
818
1000
  }
819
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
+
820
1007
  /* ========================================
821
1008
  ROW 2 — AXIS (track + ticks + arrows + dots)
822
1009
  ======================================== */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mathscan/math-exercise-engine",
3
- "version": "1.0.14",
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",