@mathscan/math-exercise-engine 1.2.9 → 1.2.11
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/README.md +26 -0
- package/dist/{GeometryWidget-BRV6LLcW.mjs → GeometryWidget-2TCLbTEv.mjs} +2 -2
- package/dist/{GeometryWidget-BRV6LLcW.mjs.map → GeometryWidget-2TCLbTEv.mjs.map} +1 -1
- package/dist/{GeometryWidget-D2QyGzW-.js → GeometryWidget-BTPjXJz-.js} +2 -2
- package/dist/{GeometryWidget-D2QyGzW-.js.map → GeometryWidget-BTPjXJz-.js.map} +1 -1
- package/dist/{index-XAInSHXa.mjs → index-6z_8j1tM.mjs} +1675 -395
- package/dist/index-6z_8j1tM.mjs.map +1 -0
- package/dist/{index-D-QbiBn5.js → index-C3qDr71I.js} +1485 -205
- package/dist/index-C3qDr71I.js.map +1 -0
- package/dist/index.d.ts +14600 -7591
- package/dist/index.js +22 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +212 -191
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +559 -38
- package/package.json +1 -1
- package/dist/index-D-QbiBn5.js.map +0 -1
- package/dist/index-XAInSHXa.mjs.map +0 -1
package/dist/style.css
CHANGED
|
@@ -636,6 +636,27 @@ mark {
|
|
|
636
636
|
box-shadow: 0 2px 4px rgba(0, 122, 204, 0.2);
|
|
637
637
|
}
|
|
638
638
|
|
|
639
|
+
/* Bounce hint — repeats until the student drags any option */
|
|
640
|
+
|
|
641
|
+
@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); }
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
.drag-option.drag-option--hint {
|
|
650
|
+
transform-origin: center bottom;
|
|
651
|
+
animation: drag-option-hint 0.8s ease-in-out 0.4s 2 both;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
@media (prefers-reduced-motion: reduce) {
|
|
655
|
+
.drag-option.drag-option--hint {
|
|
656
|
+
animation: none;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
639
660
|
/* Legacy button-style drag options for backward compatibility */
|
|
640
661
|
|
|
641
662
|
button.drag-option {
|
|
@@ -811,17 +832,35 @@ button.drag-option {
|
|
|
811
832
|
TABLE LAYOUT
|
|
812
833
|
======================================== */
|
|
813
834
|
|
|
835
|
+
/*
|
|
836
|
+
* Grid, not table layout: with equal `1fr` columns every cell gets the same
|
|
837
|
+
* width — the widest cell's when the table shrinks to fit, an equal share of
|
|
838
|
+
* the row when it stretches. min-width: max-content keeps columns from going
|
|
839
|
+
* narrower than the widest cell (which would split numbers or push inputs out
|
|
840
|
+
* of their cell); the container scrolls instead.
|
|
841
|
+
*/
|
|
842
|
+
|
|
814
843
|
.exercise-table {
|
|
815
|
-
|
|
844
|
+
display: grid;
|
|
845
|
+
grid-template-columns: repeat(var(--exercise-table-cols), minmax(0, 1fr));
|
|
816
846
|
font-size: small;
|
|
817
847
|
width: 100%;
|
|
848
|
+
min-width: max-content;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/* Rows only group cells for a11y; cells sit directly on the grid. */
|
|
852
|
+
|
|
853
|
+
.exercise-table-row {
|
|
854
|
+
display: contents;
|
|
818
855
|
}
|
|
819
856
|
|
|
820
857
|
.exercise-table-cell {
|
|
858
|
+
display: flex;
|
|
859
|
+
align-items: center;
|
|
860
|
+
justify-content: center;
|
|
861
|
+
min-height: 36px;
|
|
821
862
|
padding: 4px;
|
|
822
863
|
text-align: center;
|
|
823
|
-
vertical-align: middle;
|
|
824
|
-
min-height: 36px;
|
|
825
864
|
color: var(--exercise-text);
|
|
826
865
|
}
|
|
827
866
|
|
|
@@ -834,35 +873,27 @@ button.drag-option {
|
|
|
834
873
|
.exercise-table-container {
|
|
835
874
|
border: 2px solid var(--exercise-table-border-color);
|
|
836
875
|
border-radius: 8px;
|
|
837
|
-
overflow:
|
|
876
|
+
overflow-x: auto;
|
|
877
|
+
/* overflow-x alone turns overflow-y into auto too; sub-pixel rounding then
|
|
878
|
+
shows a useless vertical scrollbar. Only sideways scrolling is wanted. */
|
|
879
|
+
overflow-y: hidden;
|
|
838
880
|
}
|
|
839
881
|
|
|
840
|
-
|
|
841
|
-
border-collapse: separate;
|
|
842
|
-
border-spacing: 0;
|
|
843
|
-
}
|
|
882
|
+
/* Inner grid lines: right + bottom on each cell, dropped at the outer edges. */
|
|
844
883
|
|
|
845
|
-
|
|
846
|
-
th.exercise-table-cell {
|
|
884
|
+
.exercise-table-cell {
|
|
847
885
|
box-shadow: inset -2px -2px 0 0 var(--exercise-table-border-color);
|
|
848
886
|
}
|
|
849
887
|
|
|
850
|
-
|
|
851
|
-
th.exercise-table-cell:last-child {
|
|
888
|
+
.exercise-table-cell.exercise-table-cell--last-col {
|
|
852
889
|
box-shadow: inset 0 -2px 0 0 var(--exercise-table-border-color);
|
|
853
890
|
}
|
|
854
891
|
|
|
855
|
-
|
|
856
|
-
tr.exercise-table-row:last-child th.exercise-table-cell,
|
|
857
|
-
td.exercise-table-cell.exercise-table-cell--last-row,
|
|
858
|
-
th.exercise-table-cell.exercise-table-cell--last-row {
|
|
892
|
+
.exercise-table-cell.exercise-table-cell--last-row {
|
|
859
893
|
box-shadow: inset -2px 0 0 0 var(--exercise-table-border-color);
|
|
860
894
|
}
|
|
861
895
|
|
|
862
|
-
|
|
863
|
-
tr.exercise-table-row:last-child th.exercise-table-cell:last-child,
|
|
864
|
-
td.exercise-table-cell.exercise-table-cell--last-row:last-child,
|
|
865
|
-
th.exercise-table-cell.exercise-table-cell--last-row:last-child {
|
|
896
|
+
.exercise-table-cell.exercise-table-cell--last-row.exercise-table-cell--last-col {
|
|
866
897
|
box-shadow: none;
|
|
867
898
|
}
|
|
868
899
|
|
|
@@ -1653,6 +1684,8 @@ th.exercise-table-cell.exercise-table-cell--last-row:last-child {
|
|
|
1653
1684
|
--nl-visual-fraction-size: clamp(5.5rem, 18vw, 8rem);
|
|
1654
1685
|
/* Visual gap between end ticks and arrowheads — pure CSS, no effect on calculations */
|
|
1655
1686
|
--nl-arrow-gap: 8px;
|
|
1687
|
+
/* How far an open-ended track runs past the end tick ('line' / 'line-left' styles) */
|
|
1688
|
+
--nl-open-end-length: 16px;
|
|
1656
1689
|
--nl-arrow-arc-color: var(--exercise-danger, #D6234D);
|
|
1657
1690
|
--nl-arrow-arc-stroke-width: 1.75;
|
|
1658
1691
|
}
|
|
@@ -1833,12 +1866,15 @@ th.exercise-table-cell.exercise-table-cell--last-row:last-child {
|
|
|
1833
1866
|
right: calc(var(--nl-side-pad, 7%) - var(--nl-arrow-gap, 8px));
|
|
1834
1867
|
}
|
|
1835
1868
|
|
|
1836
|
-
/*
|
|
1837
|
-
so the axis looks like
|
|
1869
|
+
/* Open end ('line' / 'line-left' styles) — track runs --nl-open-end-length past
|
|
1870
|
+
the end tick so the axis looks like it continues, not a closed segment */
|
|
1838
1871
|
|
|
1839
|
-
.number-line--
|
|
1840
|
-
left:
|
|
1841
|
-
|
|
1872
|
+
.number-line--extend-left .number-line-track {
|
|
1873
|
+
left: calc(var(--nl-side-pad, 7%) - var(--nl-open-end-length, 16px));
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
.number-line--extend-right .number-line-track {
|
|
1877
|
+
right: calc(var(--nl-side-pad, 7%) - var(--nl-open-end-length, 16px));
|
|
1842
1878
|
}
|
|
1843
1879
|
|
|
1844
1880
|
/* ── Arrowheads (CSS border-triangle technique) ── */
|
|
@@ -1870,16 +1906,6 @@ th.exercise-table-cell.exercise-table-cell--last-row:last-child {
|
|
|
1870
1906
|
border-right: 8px solid var(--nl-axis-color);
|
|
1871
1907
|
}
|
|
1872
1908
|
|
|
1873
|
-
/* Geometric line style — arrows reach the full container edges */
|
|
1874
|
-
|
|
1875
|
-
.number-line--style-line .number-line-arrow-right {
|
|
1876
|
-
left: calc(100%);
|
|
1877
|
-
}
|
|
1878
|
-
|
|
1879
|
-
.number-line--style-line .number-line-arrow-left {
|
|
1880
|
-
left: 0;
|
|
1881
|
-
}
|
|
1882
|
-
|
|
1883
1909
|
/* ── Major ticks ── */
|
|
1884
1910
|
|
|
1885
1911
|
.number-line-major-tick {
|
|
@@ -2789,8 +2815,22 @@ th.exercise-table-cell.exercise-table-cell--last-row:last-child {
|
|
|
2789
2815
|
|
|
2790
2816
|
/* ── Root container ─────────────────────────────────────────────────────────── */
|
|
2791
2817
|
|
|
2818
|
+
/* CSS-only scroll shadows (Lea Verou): edge-pinned radial gradients revealed by
|
|
2819
|
+
`local`-attached cover gradients that scroll away with the content. */
|
|
2820
|
+
|
|
2792
2821
|
.chart-scroll {
|
|
2793
2822
|
width: 100%;
|
|
2823
|
+
overflow-x: auto;
|
|
2824
|
+
-webkit-overflow-scrolling: touch;
|
|
2825
|
+
background:
|
|
2826
|
+
linear-gradient(to right, var(--exercise-bg, #fff) 30%, transparent),
|
|
2827
|
+
linear-gradient(to left, var(--exercise-bg, #fff) 30%, transparent) 100% 0,
|
|
2828
|
+
radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, 0.15), transparent),
|
|
2829
|
+
radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, 0.15), transparent) 100% 0;
|
|
2830
|
+
background-repeat: no-repeat;
|
|
2831
|
+
background-color: var(--exercise-bg, #fff);
|
|
2832
|
+
background-size: 40px 100%, 40px 100%, 14px 100%, 14px 100%;
|
|
2833
|
+
background-attachment: local, local, scroll, scroll;
|
|
2794
2834
|
}
|
|
2795
2835
|
|
|
2796
2836
|
.chart {
|
|
@@ -3496,6 +3536,475 @@ th.exercise-table-cell.exercise-table-cell--last-row:last-child {
|
|
|
3496
3536
|
width: 1px;
|
|
3497
3537
|
}
|
|
3498
3538
|
|
|
3539
|
+
/* ═══════════════════════════════════════════════════════════════════════════
|
|
3540
|
+
GROUPED-BAR CHART (.chart--grouped-bar)
|
|
3541
|
+
Two nesting levels: .chart-groups > .chart-group (cluster) > .chart-bar-wrapper.
|
|
3542
|
+
Bars inside a cluster reuse every bar-chart rule and var untouched; the only
|
|
3543
|
+
new spacing is the cluster gap (--chart-group-gap vs --chart-bar-gap).
|
|
3544
|
+
═══════════════════════════════════════════════════════════════════════════ */
|
|
3545
|
+
|
|
3546
|
+
.chart-groups {
|
|
3547
|
+
position: absolute;
|
|
3548
|
+
inset: 0;
|
|
3549
|
+
display: flex;
|
|
3550
|
+
align-items: flex-end;
|
|
3551
|
+
justify-content: center;
|
|
3552
|
+
padding-inline: var(--chart-group-gap);
|
|
3553
|
+
gap: var(--chart-group-gap);
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
.chart-group {
|
|
3557
|
+
flex: 0 0 var(--chart-group-width);
|
|
3558
|
+
width: var(--chart-group-width);
|
|
3559
|
+
display: flex;
|
|
3560
|
+
align-items: flex-end;
|
|
3561
|
+
justify-content: center;
|
|
3562
|
+
gap: var(--chart-bar-gap);
|
|
3563
|
+
height: 100%;
|
|
3564
|
+
}
|
|
3565
|
+
|
|
3566
|
+
/* Series declared but absent from this GROUP: holds the slot, paints nothing. */
|
|
3567
|
+
|
|
3568
|
+
.chart-group-bar-gap {
|
|
3569
|
+
flex: 0 0 var(--chart-bar-slot-width);
|
|
3570
|
+
width: var(--chart-bar-slot-width);
|
|
3571
|
+
height: 100%;
|
|
3572
|
+
}
|
|
3573
|
+
|
|
3574
|
+
/* Category label row: bar-chart's .chart-axis-x widened from a bar to a cluster. */
|
|
3575
|
+
|
|
3576
|
+
.chart--grouped-bar .chart-axis-x {
|
|
3577
|
+
padding-inline: var(--chart-group-gap);
|
|
3578
|
+
gap: var(--chart-group-gap);
|
|
3579
|
+
}
|
|
3580
|
+
|
|
3581
|
+
.chart--grouped-bar .chart-axis-tick-x {
|
|
3582
|
+
flex: 0 0 var(--chart-group-width);
|
|
3583
|
+
}
|
|
3584
|
+
|
|
3585
|
+
/* ── Horizontal ─────────────────────────────────────────────────────────────── */
|
|
3586
|
+
|
|
3587
|
+
.chart--horizontal .chart-groups {
|
|
3588
|
+
flex-direction: column;
|
|
3589
|
+
align-items: flex-start;
|
|
3590
|
+
justify-content: flex-start;
|
|
3591
|
+
padding-inline: 0;
|
|
3592
|
+
padding-block: var(--chart-group-gap);
|
|
3593
|
+
}
|
|
3594
|
+
|
|
3595
|
+
.chart--horizontal .chart-group {
|
|
3596
|
+
flex: 0 0 var(--chart-group-width);
|
|
3597
|
+
height: var(--chart-group-width);
|
|
3598
|
+
width: 100%;
|
|
3599
|
+
/* Column, not row: each bar needs the full plot width to fill from 0. */
|
|
3600
|
+
flex-direction: column;
|
|
3601
|
+
align-items: flex-start;
|
|
3602
|
+
justify-content: flex-start;
|
|
3603
|
+
}
|
|
3604
|
+
|
|
3605
|
+
.chart--horizontal .chart-group-bar-gap {
|
|
3606
|
+
flex: 0 0 var(--chart-bar-slot-width);
|
|
3607
|
+
width: 100%;
|
|
3608
|
+
height: var(--chart-bar-slot-width);
|
|
3609
|
+
}
|
|
3610
|
+
|
|
3611
|
+
.chart--grouped-bar.chart--horizontal .chart-axis-y--categories {
|
|
3612
|
+
padding-block: var(--chart-group-gap);
|
|
3613
|
+
gap: var(--chart-group-gap);
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3616
|
+
.chart--grouped-bar.chart--horizontal .chart-bar-y-label {
|
|
3617
|
+
flex: 0 0 var(--chart-group-width);
|
|
3618
|
+
height: var(--chart-group-width);
|
|
3619
|
+
}
|
|
3620
|
+
|
|
3621
|
+
/* ── Legend ─────────────────────────────────────────────────────────────────── */
|
|
3622
|
+
|
|
3623
|
+
/* Explicit cell: grid auto-placement would float it above the chart. */
|
|
3624
|
+
|
|
3625
|
+
.chart-legend {
|
|
3626
|
+
display: flex;
|
|
3627
|
+
flex-wrap: wrap;
|
|
3628
|
+
justify-content: center;
|
|
3629
|
+
gap: 0.4rem 1rem;
|
|
3630
|
+
padding-top: 0.6rem;
|
|
3631
|
+
grid-column: 3;
|
|
3632
|
+
grid-row: 5;
|
|
3633
|
+
}
|
|
3634
|
+
|
|
3635
|
+
.chart-legend-item {
|
|
3636
|
+
display: flex;
|
|
3637
|
+
align-items: center;
|
|
3638
|
+
gap: 0.4rem;
|
|
3639
|
+
}
|
|
3640
|
+
|
|
3641
|
+
.chart-legend-swatch {
|
|
3642
|
+
width: 0.85rem;
|
|
3643
|
+
height: 0.85rem;
|
|
3644
|
+
border-radius: 3px;
|
|
3645
|
+
flex: 0 0 auto;
|
|
3646
|
+
}
|
|
3647
|
+
|
|
3648
|
+
.chart-legend-label {
|
|
3649
|
+
font-size: var(--chart-label-font-size);
|
|
3650
|
+
color: var(--exercise-text-primary, #1f2937);
|
|
3651
|
+
}
|
|
3652
|
+
|
|
3653
|
+
/**
|
|
3654
|
+
* Widget tutorial overlay (renderer/tutorial).
|
|
3655
|
+
*
|
|
3656
|
+
* Portaled to <body>, so it sits OUTSIDE .math-exercise-renderer and does not
|
|
3657
|
+
* inherit its variables — the tokens below are declared on the overlay itself.
|
|
3658
|
+
* Override them on `.met-tutorial` to retheme.
|
|
3659
|
+
*
|
|
3660
|
+
* `--met-tutorial-font` is deliberately NOT declared here: set it anywhere above
|
|
3661
|
+
* (e.g. `:root`) to give the bubble the host's font. Unset → inherits from <body>.
|
|
3662
|
+
*/
|
|
3663
|
+
|
|
3664
|
+
.met-tutorial {
|
|
3665
|
+
--met-tutorial-dim: rgba(30, 24, 56, 0.45);
|
|
3666
|
+
--met-tutorial-glow: #ffd66b;
|
|
3667
|
+
--met-tutorial-accent: #705dc0;
|
|
3668
|
+
--met-tutorial-accent-soft: #ece8fb;
|
|
3669
|
+
--met-tutorial-accent-edge: #4f3f9a;
|
|
3670
|
+
--met-tutorial-next-bg: #ffd66b;
|
|
3671
|
+
--met-tutorial-next-edge: #d9a93a;
|
|
3672
|
+
--met-tutorial-next-text: #2e2463;
|
|
3673
|
+
--met-tutorial-text: #2e2463;
|
|
3674
|
+
--met-tutorial-bubble-border: #e0d9ff;
|
|
3675
|
+
|
|
3676
|
+
position: fixed;
|
|
3677
|
+
inset: 0;
|
|
3678
|
+
z-index: 1000;
|
|
3679
|
+
/* Takes every tap: the hand is a demo, nothing may reach the exercise. */
|
|
3680
|
+
pointer-events: auto;
|
|
3681
|
+
touch-action: none;
|
|
3682
|
+
}
|
|
3683
|
+
|
|
3684
|
+
.met-tutorial > * {
|
|
3685
|
+
position: fixed;
|
|
3686
|
+
}
|
|
3687
|
+
|
|
3688
|
+
/* ── Dim with spotlight holes ─────────────────────────────────────────── */
|
|
3689
|
+
|
|
3690
|
+
.met-tutorial-dim {
|
|
3691
|
+
inset: 0;
|
|
3692
|
+
width: 100%;
|
|
3693
|
+
height: 100%;
|
|
3694
|
+
fill: var(--met-tutorial-dim);
|
|
3695
|
+
animation: met-tutorial-fade 250ms ease-out both;
|
|
3696
|
+
}
|
|
3697
|
+
|
|
3698
|
+
.met-tutorial-glow {
|
|
3699
|
+
pointer-events: none;
|
|
3700
|
+
box-shadow:
|
|
3701
|
+
0 0 0 3px var(--met-tutorial-glow),
|
|
3702
|
+
0 0 22px 6px color-mix(in srgb, var(--met-tutorial-glow) 70%, transparent);
|
|
3703
|
+
transition: left 200ms ease, top 200ms ease, width 200ms ease, height 200ms ease;
|
|
3704
|
+
animation: met-tutorial-fade 250ms ease-out both;
|
|
3705
|
+
}
|
|
3706
|
+
|
|
3707
|
+
/* The real key under the hand, mid-press. */
|
|
3708
|
+
|
|
3709
|
+
.diamond-keypad .keypad-btn.met-tutorial-pressed {
|
|
3710
|
+
transform: rotate(45deg) scale(0.9);
|
|
3711
|
+
}
|
|
3712
|
+
|
|
3713
|
+
.keypad-btn.met-tutorial-pressed {
|
|
3714
|
+
transform: scale(0.92);
|
|
3715
|
+
box-shadow: 0 0 0 3px var(--met-tutorial-glow, #ffd66b);
|
|
3716
|
+
}
|
|
3717
|
+
|
|
3718
|
+
/* ── Hand ─────────────────────────────────────────────────────────────── */
|
|
3719
|
+
|
|
3720
|
+
.met-tutorial-hand {
|
|
3721
|
+
left: 0;
|
|
3722
|
+
top: 0;
|
|
3723
|
+
pointer-events: none;
|
|
3724
|
+
/* Glide between targets — well inside the 1400ms cycle (TutorialOverlay CYCLE_MS). */
|
|
3725
|
+
transition: transform 500ms cubic-bezier(0.45, 0, 0.25, 1);
|
|
3726
|
+
filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.25));
|
|
3727
|
+
}
|
|
3728
|
+
|
|
3729
|
+
.met-tutorial-hand-press {
|
|
3730
|
+
position: relative;
|
|
3731
|
+
width: 100%;
|
|
3732
|
+
height: 100%;
|
|
3733
|
+
transform-origin: 75% 28%;
|
|
3734
|
+
animation: met-tutorial-tap 1400ms ease-in-out both;
|
|
3735
|
+
}
|
|
3736
|
+
|
|
3737
|
+
.met-tutorial-hand-press svg {
|
|
3738
|
+
width: 100%;
|
|
3739
|
+
height: 100%;
|
|
3740
|
+
}
|
|
3741
|
+
|
|
3742
|
+
/* Press lands at ~46% of the cycle (PRESS_AT_MS = 650 / CYCLE_MS = 1400). */
|
|
3743
|
+
|
|
3744
|
+
@keyframes met-tutorial-tap {
|
|
3745
|
+
0%, 38% { transform: scale(1); }
|
|
3746
|
+
46% { transform: scale(0.84); }
|
|
3747
|
+
58%, 100% { transform: scale(1); }
|
|
3748
|
+
}
|
|
3749
|
+
|
|
3750
|
+
.met-tutorial-ripple {
|
|
3751
|
+
position: absolute;
|
|
3752
|
+
width: 44px;
|
|
3753
|
+
height: 44px;
|
|
3754
|
+
margin: -22px 0 0 -22px;
|
|
3755
|
+
border-radius: 50%;
|
|
3756
|
+
border: 3px solid var(--met-tutorial-glow);
|
|
3757
|
+
opacity: 0;
|
|
3758
|
+
animation: met-tutorial-ripple 1400ms ease-out both;
|
|
3759
|
+
}
|
|
3760
|
+
|
|
3761
|
+
@keyframes met-tutorial-ripple {
|
|
3762
|
+
0%, 44% { opacity: 0; transform: scale(0.3); }
|
|
3763
|
+
50% { opacity: 1; transform: scale(0.5); }
|
|
3764
|
+
80%, 100% { opacity: 0; transform: scale(1.4); }
|
|
3765
|
+
}
|
|
3766
|
+
|
|
3767
|
+
/* ── Keypad scenes ────────────────────────────────────────────────────── */
|
|
3768
|
+
|
|
3769
|
+
.met-tutorial-arc {
|
|
3770
|
+
inset: 0;
|
|
3771
|
+
width: 100%;
|
|
3772
|
+
height: 100%;
|
|
3773
|
+
overflow: visible;
|
|
3774
|
+
pointer-events: none;
|
|
3775
|
+
}
|
|
3776
|
+
|
|
3777
|
+
.met-tutorial-arc path {
|
|
3778
|
+
fill: none;
|
|
3779
|
+
stroke: white;
|
|
3780
|
+
stroke-width: 3;
|
|
3781
|
+
stroke-linecap: round;
|
|
3782
|
+
stroke-dasharray: 2 9;
|
|
3783
|
+
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.35));
|
|
3784
|
+
}
|
|
3785
|
+
|
|
3786
|
+
/* Rides the arc's path (offset-path is set inline, in viewport coordinates). */
|
|
3787
|
+
|
|
3788
|
+
.met-tutorial-hop {
|
|
3789
|
+
left: 0;
|
|
3790
|
+
top: 0;
|
|
3791
|
+
width: 16px;
|
|
3792
|
+
height: 16px;
|
|
3793
|
+
margin: -8px 0 0 -8px;
|
|
3794
|
+
border-radius: 50%;
|
|
3795
|
+
background: var(--met-tutorial-accent);
|
|
3796
|
+
box-shadow: 0 0 0 3px white;
|
|
3797
|
+
offset-rotate: 0deg;
|
|
3798
|
+
pointer-events: none;
|
|
3799
|
+
animation: met-tutorial-hop 550ms ease-in-out both;
|
|
3800
|
+
}
|
|
3801
|
+
|
|
3802
|
+
@keyframes met-tutorial-hop {
|
|
3803
|
+
from { offset-distance: 0%; }
|
|
3804
|
+
to { offset-distance: 100%; opacity: 0.2; }
|
|
3805
|
+
}
|
|
3806
|
+
|
|
3807
|
+
/* Dots over the real (empty) answer box — never digits. */
|
|
3808
|
+
|
|
3809
|
+
.met-tutorial-fill {
|
|
3810
|
+
display: flex;
|
|
3811
|
+
align-items: center;
|
|
3812
|
+
justify-content: center;
|
|
3813
|
+
gap: 0.35em;
|
|
3814
|
+
border-radius: 10px;
|
|
3815
|
+
pointer-events: none;
|
|
3816
|
+
font-size: 18px;
|
|
3817
|
+
font-weight: 700;
|
|
3818
|
+
color: var(--met-tutorial-accent);
|
|
3819
|
+
}
|
|
3820
|
+
|
|
3821
|
+
.met-tutorial-fill-dot {
|
|
3822
|
+
animation: met-tutorial-pop 250ms ease-out both;
|
|
3823
|
+
}
|
|
3824
|
+
|
|
3825
|
+
.met-tutorial-fill-slot {
|
|
3826
|
+
opacity: 0.55;
|
|
3827
|
+
}
|
|
3828
|
+
|
|
3829
|
+
@keyframes met-tutorial-pop {
|
|
3830
|
+
from { transform: scale(0.2); }
|
|
3831
|
+
to { transform: scale(1); }
|
|
3832
|
+
}
|
|
3833
|
+
|
|
3834
|
+
/* Stand-in check when the host passed no submitTarget. */
|
|
3835
|
+
|
|
3836
|
+
.met-tutorial-stand-in-check {
|
|
3837
|
+
display: flex;
|
|
3838
|
+
align-items: center;
|
|
3839
|
+
justify-content: center;
|
|
3840
|
+
border-radius: 12px;
|
|
3841
|
+
transform: rotate(45deg);
|
|
3842
|
+
background: var(--met-tutorial-accent);
|
|
3843
|
+
color: white;
|
|
3844
|
+
font-size: 24px;
|
|
3845
|
+
font-weight: 700;
|
|
3846
|
+
pointer-events: none;
|
|
3847
|
+
animation: met-tutorial-pop 250ms ease-out both;
|
|
3848
|
+
}
|
|
3849
|
+
|
|
3850
|
+
.met-tutorial-stand-in-check span {
|
|
3851
|
+
transform: rotate(-45deg);
|
|
3852
|
+
}
|
|
3853
|
+
|
|
3854
|
+
/* ── Bubble ───────────────────────────────────────────────────────────── */
|
|
3855
|
+
|
|
3856
|
+
.met-tutorial-bubble {
|
|
3857
|
+
box-sizing: border-box;
|
|
3858
|
+
padding: 16px 20px 12px;
|
|
3859
|
+
border: 1.5px solid var(--met-tutorial-bubble-border);
|
|
3860
|
+
border-radius: 14px;
|
|
3861
|
+
background: white;
|
|
3862
|
+
color: var(--met-tutorial-text);
|
|
3863
|
+
font-family: var(--met-tutorial-font, inherit);
|
|
3864
|
+
box-shadow: 0 4px 16px rgba(76, 63, 193, 0.18);
|
|
3865
|
+
animation: met-tutorial-bubble-in 300ms cubic-bezier(0.2, 0.9, 0.3, 1.3) both;
|
|
3866
|
+
transform-origin: 30px 100%;
|
|
3867
|
+
}
|
|
3868
|
+
|
|
3869
|
+
/* Tail pointing down at the anchor (the mascot). */
|
|
3870
|
+
|
|
3871
|
+
.met-tutorial-bubble--anchored::after {
|
|
3872
|
+
content: '';
|
|
3873
|
+
position: absolute;
|
|
3874
|
+
left: 28px;
|
|
3875
|
+
bottom: -11px;
|
|
3876
|
+
width: 16px;
|
|
3877
|
+
height: 16px;
|
|
3878
|
+
background: white;
|
|
3879
|
+
border-right: 1.5px solid var(--met-tutorial-bubble-border);
|
|
3880
|
+
border-bottom: 1.5px solid var(--met-tutorial-bubble-border);
|
|
3881
|
+
transform: rotate(45deg);
|
|
3882
|
+
}
|
|
3883
|
+
|
|
3884
|
+
@keyframes met-tutorial-bubble-in {
|
|
3885
|
+
from { opacity: 0; transform: scale(0.7); }
|
|
3886
|
+
to { opacity: 1; transform: scale(1); }
|
|
3887
|
+
}
|
|
3888
|
+
|
|
3889
|
+
.met-tutorial-text {
|
|
3890
|
+
margin: 0 0 12px;
|
|
3891
|
+
font-size: 18px;
|
|
3892
|
+
font-weight: 400;
|
|
3893
|
+
line-height: 1.375;
|
|
3894
|
+
text-align: center;
|
|
3895
|
+
}
|
|
3896
|
+
|
|
3897
|
+
.met-tutorial-actions {
|
|
3898
|
+
display: flex;
|
|
3899
|
+
gap: 10px;
|
|
3900
|
+
align-items: center;
|
|
3901
|
+
justify-content: center;
|
|
3902
|
+
}
|
|
3903
|
+
|
|
3904
|
+
/* Both buttons ≥ 44px — sized for small fingers. */
|
|
3905
|
+
|
|
3906
|
+
.met-tutorial-speak,
|
|
3907
|
+
.met-tutorial-next {
|
|
3908
|
+
min-height: 44px;
|
|
3909
|
+
border: none;
|
|
3910
|
+
cursor: pointer;
|
|
3911
|
+
font: inherit;
|
|
3912
|
+
transition: transform 120ms ease, filter 150ms ease;
|
|
3913
|
+
}
|
|
3914
|
+
|
|
3915
|
+
/* No browser ring (the black outline); a brand ring for keyboard users only. */
|
|
3916
|
+
|
|
3917
|
+
.met-tutorial-speak:focus,
|
|
3918
|
+
.met-tutorial-next:focus {
|
|
3919
|
+
outline: none;
|
|
3920
|
+
}
|
|
3921
|
+
|
|
3922
|
+
.met-tutorial-speak:focus-visible,
|
|
3923
|
+
.met-tutorial-next:focus-visible {
|
|
3924
|
+
outline: 3px solid var(--met-tutorial-accent);
|
|
3925
|
+
outline-offset: 3px;
|
|
3926
|
+
}
|
|
3927
|
+
|
|
3928
|
+
.met-tutorial-speak:active {
|
|
3929
|
+
transform: translateY(2px);
|
|
3930
|
+
box-shadow: 0 2px 0 var(--met-tutorial-accent-edge);
|
|
3931
|
+
}
|
|
3932
|
+
|
|
3933
|
+
/* Same 3D key as "فهمت" (shape, edge, press) — purple, so it reads as secondary. */
|
|
3934
|
+
|
|
3935
|
+
.met-tutorial-speak {
|
|
3936
|
+
display: inline-flex;
|
|
3937
|
+
align-items: center;
|
|
3938
|
+
justify-content: center;
|
|
3939
|
+
width: 48px;
|
|
3940
|
+
flex: none;
|
|
3941
|
+
border-radius: 12px;
|
|
3942
|
+
background: var(--met-tutorial-accent);
|
|
3943
|
+
box-shadow: 0 4px 0 var(--met-tutorial-accent-edge);
|
|
3944
|
+
color: white;
|
|
3945
|
+
}
|
|
3946
|
+
|
|
3947
|
+
.met-tutorial-speak:hover {
|
|
3948
|
+
filter: brightness(1.1);
|
|
3949
|
+
}
|
|
3950
|
+
|
|
3951
|
+
/* Yellow = "go" (🔊 stays purple: secondary). Darker bottom edge, like the 3D keys. */
|
|
3952
|
+
|
|
3953
|
+
.met-tutorial-next {
|
|
3954
|
+
flex: 1;
|
|
3955
|
+
padding: 0 24px;
|
|
3956
|
+
border-radius: 12px;
|
|
3957
|
+
background: var(--met-tutorial-next-bg);
|
|
3958
|
+
box-shadow: 0 4px 0 var(--met-tutorial-next-edge);
|
|
3959
|
+
color: var(--met-tutorial-next-text);
|
|
3960
|
+
font-size: 16px;
|
|
3961
|
+
font-weight: 700;
|
|
3962
|
+
}
|
|
3963
|
+
|
|
3964
|
+
.met-tutorial-next:hover {
|
|
3965
|
+
filter: brightness(1.1);
|
|
3966
|
+
}
|
|
3967
|
+
|
|
3968
|
+
.met-tutorial-next:active {
|
|
3969
|
+
transform: translateY(2px);
|
|
3970
|
+
box-shadow: 0 2px 0 var(--met-tutorial-next-edge);
|
|
3971
|
+
}
|
|
3972
|
+
|
|
3973
|
+
.met-tutorial-dots {
|
|
3974
|
+
display: flex;
|
|
3975
|
+
gap: 6px;
|
|
3976
|
+
justify-content: center;
|
|
3977
|
+
margin-top: 8px;
|
|
3978
|
+
}
|
|
3979
|
+
|
|
3980
|
+
.met-tutorial-dots span {
|
|
3981
|
+
width: 8px;
|
|
3982
|
+
height: 8px;
|
|
3983
|
+
border-radius: 50%;
|
|
3984
|
+
background: var(--met-tutorial-accent-soft);
|
|
3985
|
+
transition: background 200ms ease;
|
|
3986
|
+
}
|
|
3987
|
+
|
|
3988
|
+
.met-tutorial-dots span.is-done {
|
|
3989
|
+
background: color-mix(in srgb, var(--met-tutorial-accent) 45%, white);
|
|
3990
|
+
}
|
|
3991
|
+
|
|
3992
|
+
.met-tutorial-dots span.is-current {
|
|
3993
|
+
background: var(--met-tutorial-accent);
|
|
3994
|
+
}
|
|
3995
|
+
|
|
3996
|
+
@keyframes met-tutorial-fade {
|
|
3997
|
+
from { opacity: 0; }
|
|
3998
|
+
to { opacity: 1; }
|
|
3999
|
+
}
|
|
4000
|
+
|
|
4001
|
+
@media (prefers-reduced-motion: reduce) {
|
|
4002
|
+
.met-tutorial-hand { transition: none; }
|
|
4003
|
+
.met-tutorial-hand-press,
|
|
4004
|
+
.met-tutorial-ripple,
|
|
4005
|
+
.met-tutorial-hop { animation: none; }
|
|
4006
|
+
}
|
|
4007
|
+
|
|
3499
4008
|
/* Responsive Overrides - must be last */
|
|
3500
4009
|
|
|
3501
4010
|
/**
|
|
@@ -3659,13 +4168,25 @@ th.exercise-table-cell.exercise-table-cell--last-row:last-child {
|
|
|
3659
4168
|
border-color: #4a90d9;
|
|
3660
4169
|
}
|
|
3661
4170
|
|
|
3662
|
-
/*
|
|
4171
|
+
/* Slots holding a chip keep a hidden copy of the empty input as a sizer and lay
|
|
4172
|
+
the chip over it (see PlacedInputChip), so dropping never resizes the slot. */
|
|
4173
|
+
.inline-input-droppable,
|
|
4174
|
+
.fraction-slot-droppable {
|
|
4175
|
+
position: relative;
|
|
4176
|
+
}
|
|
4177
|
+
|
|
4178
|
+
/* Chip style for dropped values - colors match drag-option button. Size comes
|
|
4179
|
+
from the slot, so padding only keeps text off the border. */
|
|
3663
4180
|
.inline-input-chip {
|
|
3664
|
-
padding:
|
|
4181
|
+
padding: 0 4px;
|
|
3665
4182
|
border: 2px solid #9b8fc7;
|
|
3666
4183
|
border-radius: 8px;
|
|
3667
4184
|
background: #e8e4f3;
|
|
3668
|
-
font-size:
|
|
4185
|
+
font-size: inherit;
|
|
4186
|
+
text-align: center;
|
|
4187
|
+
white-space: nowrap;
|
|
4188
|
+
overflow: hidden;
|
|
4189
|
+
text-overflow: ellipsis;
|
|
3669
4190
|
font-weight: 500;
|
|
3670
4191
|
color: #5c5470;
|
|
3671
4192
|
cursor: pointer;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mathscan/math-exercise-engine",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.11",
|
|
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",
|