@mathscan/math-exercise-engine 1.0.8 → 1.0.10

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
@@ -135,6 +135,17 @@
135
135
  /* When keypad is present, use flex layout for side-by-side */
136
136
  display: flex;
137
137
  justify-content: space-between;
138
+ align-items: flex-start;
139
+ }
140
+
141
+ /* When inside a keypad layout, the exercise layout must grow to fill
142
+ all available space. Without this, containers whose only content is
143
+ a NUMBER_LINE (all children absolutely-positioned) collapse to 0 width,
144
+ causing the number line to render as a zero-width element. */
145
+
146
+ .exercise-layout-container.has-keypad .exercise-layout {
147
+ flex: 1;
148
+ min-width: 0;
138
149
  }
139
150
 
140
151
  /* ========================================
@@ -545,6 +556,233 @@ td.exercise-table-cell, th.exercise-table-cell {
545
556
  border: 2px solid #7c6cf2;
546
557
  }
547
558
 
559
+ /**
560
+ * Number Line Styles
561
+ *
562
+ * Layout and visual styles for the number line component.
563
+ * Follows the same token and convention patterns as widgets.css / inputs.css.
564
+ * All sizing, spacing and color values use --exercise-* design tokens.
565
+ */
566
+
567
+ /* ========================================
568
+ NUMBER LINE — ROOT
569
+ ======================================== */
570
+
571
+ .number-line {
572
+ position: relative;
573
+ width: 100%;
574
+ margin-bottom: var(--exercise-margin-bottom);
575
+ user-select: none;
576
+ --nl-chip-zone-height: 3rem;
577
+ }
578
+
579
+ /* Chip zone: push the axis row down so chips overflowing upward
580
+ stay within the root boundary — no geometry side-effects on the axis */
581
+
582
+ .number-line--drag-mode {
583
+ padding-top: var(--nl-chip-zone-height);
584
+ }
585
+
586
+ /* ========================================
587
+ ROW 1 — POSITION CONTENT (inputs / labels above axis)
588
+ ======================================== */
589
+
590
+ .number-line-content-row {
591
+ position: relative;
592
+ width: 100%;
593
+ min-height: 2.8rem;
594
+ }
595
+
596
+ .number-line-position-content {
597
+ position: absolute;
598
+ bottom: 4px;
599
+ transform: translateX(-50%);
600
+ display: flex;
601
+ align-items: center;
602
+ justify-content: center;
603
+ z-index: 1;
604
+ }
605
+
606
+ /* ========================================
607
+ ROW 2 — AXIS (track + ticks + arrows + dots)
608
+ ======================================== */
609
+
610
+ .number-line-axis-row {
611
+ position: relative;
612
+ width: 100%;
613
+ height: 2rem;
614
+ }
615
+
616
+ /* In drag mode, chip zone is handled by .number-line--drag-mode padding-top on root */
617
+
618
+ /* Horizontal track line — spans [--nl-side-pad, 100% - --nl-side-pad] */
619
+
620
+ .number-line-track {
621
+ position: absolute;
622
+ left: var(--nl-side-pad, 7%);
623
+ right: var(--nl-side-pad, 7%);
624
+ top: 50%;
625
+ height: 2px;
626
+ background-color: var(--exercise-text, #333);
627
+ transform: translateY(-50%);
628
+ /* transition on base state so both enter and exit are smooth */
629
+ transition: all 0.2s ease;
630
+ }
631
+
632
+ /* ── Arrowheads (CSS border-triangle technique) ── */
633
+
634
+ .number-line-arrow {
635
+ position: absolute;
636
+ top: 50%;
637
+ width: 0;
638
+ height: 0;
639
+ }
640
+
641
+ /* Right-pointing arrow — base sits at the right end of the track */
642
+
643
+ .number-line-arrow-right {
644
+ left: calc(100% - var(--nl-side-pad, 7%));
645
+ transform: translateY(-50%);
646
+ border-top: 5px solid transparent;
647
+ border-bottom: 5px solid transparent;
648
+ border-left: 8px solid var(--exercise-text, #333);
649
+ }
650
+
651
+ /* Left-pointing arrow — tip extends just past the left end of the track */
652
+
653
+ .number-line-arrow-left {
654
+ left: var(--nl-side-pad, 7%);
655
+ transform: translate(-100%, -50%);
656
+ border-top: 5px solid transparent;
657
+ border-bottom: 5px solid transparent;
658
+ border-right: 8px solid var(--exercise-text, #333);
659
+ }
660
+
661
+ /* ── Major ticks ── */
662
+
663
+ .number-line-major-tick {
664
+ position: absolute;
665
+ top: 50%;
666
+ transform: translate(-50%, -50%);
667
+ width: 2px;
668
+ height: 14px;
669
+ background-color: var(--exercise-text, #333);
670
+ }
671
+
672
+ /* ── Minor ticks ── */
673
+
674
+ .number-line-minor-tick {
675
+ position: absolute;
676
+ top: 50%;
677
+ transform: translate(-50%, -50%);
678
+ width: 1px;
679
+ height: 8px;
680
+ background-color: var(--exercise-text-light, #555);
681
+ }
682
+
683
+ /* ── Position marker dots ── */
684
+
685
+ .number-line-position-dot {
686
+ position: absolute;
687
+ top: 50%;
688
+ transform: translate(-50%, -50%);
689
+ width: 16px;
690
+ height: 16px;
691
+ border-radius: 50%;
692
+ background-color: var(--exercise-primary, #007acc);
693
+ /** Shadow: same 0.2 opacity convention as .drag-option.selected */
694
+ box-shadow: 0 2px 4px rgba(0, 122, 204, 0.2);
695
+ z-index: 2;
696
+ }
697
+
698
+ /* ========================================
699
+ PLACED CHIP (free-drag mode)
700
+ ======================================== */
701
+
702
+ /*
703
+ * After the student drops a chip on the axis, a labelled button appears
704
+ * above the snapped position. Clicking it returns the chip to the bank.
705
+ *
706
+ * Structure:
707
+ * .number-line-placed-chip — absolute wrapper, positioned by left%
708
+ * .number-line-placed-chip-btn — the visible chip button
709
+ * ::after pseudo — thin connector line down to the axis
710
+ */
711
+
712
+ .number-line-placed-chip {
713
+ position: absolute;
714
+ bottom: calc(50% + 2px);
715
+ left: 0;
716
+ transform: translateX(-50%);
717
+ z-index: 3;
718
+ display: flex;
719
+ flex-direction: column;
720
+ align-items: center;
721
+ pointer-events: none;
722
+ }
723
+
724
+ /* Thin connector between chip and axis */
725
+
726
+ .number-line-placed-chip::after {
727
+ content: '';
728
+ display: block;
729
+ width: 2px;
730
+ height: 12px;
731
+ background-color: var(--exercise-primary, #007acc);
732
+ margin-top: 2px;
733
+ }
734
+
735
+ .number-line-placed-chip-btn {
736
+ pointer-events: all;
737
+ background-color: var(--exercise-primary, #007acc);
738
+ color: white;
739
+ /** White inner border uses border-width token for consistency */
740
+ border: var(--exercise-border-width) solid white;
741
+ border-radius: var(--exercise-border-radius, 0.25rem);
742
+ padding: var(--exercise-input-padding, 0.375rem 0.625rem);
743
+ font-size: var(--exercise-font-size, 0.875rem);
744
+ font-weight: var(--exercise-font-weight-bold, 600);
745
+ cursor: pointer;
746
+ /** Shadow: same convention as .drag-option.selected and .radio-option.selected */
747
+ box-shadow: 0 2px 4px rgba(0, 122, 204, 0.2);
748
+ transition: all 0.2s ease;
749
+ white-space: nowrap;
750
+ }
751
+
752
+ .number-line-placed-chip-btn:hover {
753
+ background-color: var(--exercise-danger, #ef4444);
754
+ /** Scale down like other interactive elements (consistent with .drag-option:active) */
755
+ transform: scale(0.98);
756
+ }
757
+
758
+ /* Axis highlight while a chip is hovering over it */
759
+
760
+ .nl-axis-drag-over .number-line-track {
761
+ background-color: var(--exercise-primary, #007acc);
762
+ /** Shadow: same 0.2 opacity convention */
763
+ box-shadow: 0 0 0 3px rgba(0, 122, 204, 0.2);
764
+ }
765
+
766
+ /* ========================================
767
+ ROW 3 — NUMERIC TICK LABELS
768
+ ======================================== */
769
+
770
+ .number-line-labels-row {
771
+ position: relative;
772
+ width: 100%;
773
+ height: 1.2rem;
774
+ margin-top: calc(var(--exercise-line-gap) * 0.5);
775
+ }
776
+
777
+ .number-line-tick-label {
778
+ position: absolute;
779
+ transform: translateX(-50%);
780
+ /** Slightly smaller than base font — same pattern as secondary text in the app */
781
+ font-size: calc(var(--exercise-font-size) * 0.8);
782
+ color: var(--exercise-text-light, #555);
783
+ white-space: nowrap;
784
+ }
785
+
548
786
  /**
549
787
  * Button & Action Styles
550
788
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mathscan/math-exercise-engine",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
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",