@mathscan/math-exercise-engine 1.2.14 → 1.2.15

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
@@ -515,6 +515,87 @@ mark {
515
515
  opacity: 1;
516
516
  }
517
517
 
518
+ /* ========================================
519
+ DROP INPUT
520
+ ======================================== */
521
+
522
+ .drop-input {
523
+ direction: ltr;
524
+ text-align: center;
525
+ }
526
+
527
+ /* Border, color and padding come from .inline-input. 5em is InlineInput's default
528
+ box. Low specificity on purpose, so a host's .inline-input size wins. */
529
+
530
+ .drop-input.inline-input,
531
+ .drop-input-sizer.inline-input {
532
+ display: inline-flex;
533
+ align-items: center;
534
+ justify-content: center;
535
+ width: 5em;
536
+ /* A placeholder longer than the box grows it, on one line. Not fit-content: its
537
+ shrink-wrapped parent gets its width from this box, so that would wrap the text. */
538
+ min-width: max-content;
539
+ }
540
+
541
+ /* Keeps a line box when the box has no text, so it has the input's height. Only
542
+ when empty: beside a placeholder it is a flex item that a host's gap pushes apart. */
543
+
544
+ .drop-input--empty:empty::before,
545
+ .drop-input-sizer:empty::before {
546
+ content: '\200b';
547
+ }
548
+
549
+ /* Not a real <input>, so ::placeholder can't style it and it would read as an
550
+ answer. Faded from the input's own color, so it suits any host without an override.
551
+ Padding is here because hosts set .inline-input padding to 0 for a centered short
552
+ value, which leaves a box-sized placeholder touching the border. */
553
+
554
+ .drop-input-placeholder {
555
+ padding: 0 0.25em;
556
+ color: var(--exercise-placeholder, color-mix(in srgb, currentColor 50%, transparent));
557
+ font-weight: normal;
558
+ }
559
+
560
+ /* Sizer and chip share one grid cell, so the cell fits whichever is bigger. */
561
+
562
+ .drop-input.drop-input--filled {
563
+ display: inline-grid;
564
+ }
565
+
566
+ .drop-input--filled > * {
567
+ grid-area: 1 / 1;
568
+ }
569
+
570
+ .drop-input-sizer {
571
+ visibility: hidden;
572
+ }
573
+
574
+ .drop-input--locked {
575
+ cursor: default;
576
+ }
577
+
578
+ /* Unresolved stored value (no matching bank option) — plain text, still clickable to clear. */
579
+
580
+ .drop-input-unresolved {
581
+ padding: 0;
582
+ border: 0;
583
+ background: transparent;
584
+ color: inherit;
585
+ font: inherit;
586
+ cursor: pointer;
587
+ }
588
+
589
+ .drop-input.widget--correct {
590
+ border-color: var(--exercise-correct, #22c55e);
591
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--exercise-correct, #22c55e) 15%, transparent);
592
+ }
593
+
594
+ .drop-input.widget--incorrect {
595
+ border-color: var(--exercise-incorrect, #ef4444);
596
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--exercise-incorrect, #ef4444) 15%, transparent);
597
+ }
598
+
518
599
  /* ========================================
519
600
  VALIDATION STATUS
520
601
  ======================================== */
@@ -636,19 +717,23 @@ mark {
636
717
  box-shadow: 0 2px 4px rgba(0, 122, 204, 0.2);
637
718
  }
638
719
 
639
- /* Bounce hint — repeats until the student drags any option */
720
+ /* A gentle lift and soft halo invite the student to pick up a chip. */
640
721
 
641
722
  @keyframes drag-option-hint {
642
- 0%, 100% { transform: translateY(0) scale(1, 1); }
643
- 15% { transform: translateY(0) scale(1.12, 0.88); } /* squash before jump */
644
- 40% { transform: translateY(-14px) scale(0.94, 1.08) rotate(-4deg); } /* stretch in the air */
645
- 60% { transform: translateY(0) scale(1.1, 0.9); } /* squash on landing */
646
- 78% { transform: translateY(-4px) scale(0.98, 1.02) rotate(2deg); }
723
+ 0%, 100% {
724
+ transform: translateY(0);
725
+ box-shadow: 0 0 0 0 transparent, 0 0 0 transparent;
726
+ }
727
+ 40%, 55% {
728
+ transform: translateY(-5px);
729
+ box-shadow:
730
+ 0 0 0 5px color-mix(in srgb, var(--exercise-accent) 16%, transparent),
731
+ 0 6px 12px color-mix(in srgb, var(--exercise-accent) 22%, transparent);
732
+ }
647
733
  }
648
734
 
649
735
  .drag-option.drag-option--hint {
650
- transform-origin: center bottom;
651
- animation: drag-option-hint 0.8s ease-in-out 0.4s 2 both;
736
+ animation: drag-option-hint 1.8s ease-in-out 0.65s 1 both;
652
737
  }
653
738
 
654
739
  @media (prefers-reduced-motion: reduce) {
@@ -669,6 +754,35 @@ button.drag-option {
669
754
  font-weight: var(--exercise-font-weight);
670
755
  }
671
756
 
757
+ /* Cards holding images or colored text: on the accent surface they could become
758
+ unreadable, so only the surface changes. Text-only cards stay plain chips.
759
+ Same specificity as button.drag-option, so a host's restyle of that rule wins
760
+ here too and all cards in a bank match. */
761
+
762
+ button.drag-option--rich {
763
+ background: var(--exercise-bg);
764
+ color: var(--exercise-text);
765
+ }
766
+
767
+ /* The mobile fixed chip width would clip an image. */
768
+
769
+ .rich-drag-option-slot button.drag-option {
770
+ width: auto;
771
+ }
772
+
773
+ .rich-option-content {
774
+ display: inline-flex;
775
+ align-items: center;
776
+ white-space: nowrap;
777
+ }
778
+
779
+ /* Text takes its color from the card it sits on. COLOR_TEXT keeps its own. */
780
+
781
+ .rich-option-content .text-item,
782
+ .rich-option-content .bold-text {
783
+ color: inherit;
784
+ }
785
+
672
786
  /* ========================================
673
787
  RADIO OPTIONS
674
788
  ======================================== */
@@ -3672,6 +3786,7 @@ button.drag-option {
3672
3786
  --met-tutorial-next-text: #2e2463;
3673
3787
  --met-tutorial-text: #2e2463;
3674
3788
  --met-tutorial-bubble-border: #e0d9ff;
3789
+ --met-tutorial-fill-bg: #fff;
3675
3790
 
3676
3791
  position: fixed;
3677
3792
  inset: 0;
@@ -3824,6 +3939,8 @@ button.drag-option {
3824
3939
  justify-content: center;
3825
3940
  gap: 0.35em;
3826
3941
  border-radius: 10px;
3942
+ /* Opaque: a prefill or typed value in the real box must not show through. */
3943
+ background: var(--met-tutorial-fill-bg);
3827
3944
  pointer-events: none;
3828
3945
  font-size: 18px;
3829
3946
  font-weight: 700;
@@ -4126,6 +4243,35 @@ button.drag-option {
4126
4243
  cursor: grabbing;
4127
4244
  }
4128
4245
 
4246
+ /* Carries the theme variables to the overlay; drops the root's own card look. */
4247
+ .math-exercise-renderer.rich-drag-overlay-root {
4248
+ padding: 0;
4249
+ background: transparent;
4250
+ border-radius: 0;
4251
+ box-shadow: none;
4252
+ }
4253
+
4254
+ .rich-drag-overlay-option {
4255
+ display: inline-flex;
4256
+ align-items: center;
4257
+ justify-content: center;
4258
+ }
4259
+
4260
+ /* Same light surface as .drag-option--rich in the bank. */
4261
+ .drag-overlay-option--rich {
4262
+ background: var(--exercise-bg);
4263
+ color: var(--exercise-text);
4264
+ }
4265
+
4266
+ /* The droppable wrapper carries the inline flow (its child .drop-input keeps
4267
+ .inline-widget for the no-dnd path). drop-active styling is shared with
4268
+ .inline-input-droppable below. */
4269
+ .drop-input-droppable {
4270
+ display: inline-flex;
4271
+ align-items: center;
4272
+ vertical-align: middle;
4273
+ }
4274
+
4129
4275
  /* Note: .drag-options and .drag-option base styles live in styles/widgets.css
4130
4276
  (themed via CSS vars). Don't duplicate them here. */
4131
4277
 
@@ -4133,14 +4279,15 @@ button.drag-option {
4133
4279
  display: inline-block;
4134
4280
  }
4135
4281
 
4136
- /* Slot container - maintains space when item is dragged out */
4282
+ /* Slot container - maintains space when item is dragged out.
4283
+ Shared by the legacy chip bank and the rich DRAGGABLE_OPTIONS bank. */
4137
4284
  .drag-option-slot {
4138
4285
  display: inline-flex;
4139
4286
  min-width: 60px;
4140
4287
  min-height: 44px;
4141
4288
  }
4142
4289
 
4143
- /* Empty slot - shows where item came from, from pick-up on. It is sized by an
4290
+ /* Empty slot - shows where a chip came from, from pick-up on. It is sized by an
4144
4291
  invisible copy of the chip; the outline draws inside so the size stays exact.
4145
4292
  Solid, not dashed: dashed is the look of the inputs it must not be mistaken for. */
4146
4293
  .drag-option-empty {
@@ -4154,6 +4301,14 @@ button.drag-option {
4154
4301
  visibility: hidden;
4155
4302
  }
4156
4303
 
4304
+ /* Rich cards vary in size, so the slot always matches its hidden copy, even if a
4305
+ host gives .drag-option-empty a fixed width or a border that adds to the size.
4306
+ The outline above still draws the edge. */
4307
+ .rich-drag-option-slot .drag-option-empty {
4308
+ width: auto;
4309
+ border: 0;
4310
+ }
4311
+
4157
4312
  /* Inline input droppable */
4158
4313
  .inline-input-droppable {
4159
4314
  display: inline-flex;
@@ -4214,8 +4369,19 @@ button.drag-option {
4214
4369
  opacity: 0.4;
4215
4370
  }
4216
4371
 
4217
- /* When droppable is active, style the input inside */
4218
- .inline-input-droppable.drop-active .inline-input {
4372
+ /* A chip in a DROP_INPUT stacks with its sizer instead of overlaying it, so a
4373
+ large card can grow the slot. See .drop-input--filled. */
4374
+ .inline-input-chip.placed-rich-option {
4375
+ display: inline-flex;
4376
+ align-items: center;
4377
+ justify-content: center;
4378
+ padding: 4px 8px;
4379
+ }
4380
+
4381
+ /* When droppable is active, style the input inside.
4382
+ DROP_INPUT shares the inline-input affordance. */
4383
+ .inline-input-droppable.drop-active .inline-input,
4384
+ .drop-input-droppable.drop-active .drop-input {
4219
4385
  border-color: #4a90d9;
4220
4386
  background-color: rgba(74, 144, 217, 0.05);
4221
4387
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mathscan/math-exercise-engine",
3
- "version": "1.2.14",
3
+ "version": "1.2.15",
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",