@mathscan/math-exercise-engine 1.1.14 → 1.1.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
@@ -68,6 +68,9 @@
68
68
  /** Margin between inline elements (0.25rem = 4px) */
69
69
  --exercise-element-margin: 0.25rem;
70
70
 
71
+ /** Minimum space kept between the exercise content and the numeric keypad */
72
+ --exercise-keypad-gap: 1rem;
73
+
71
74
  /* ========================================
72
75
  TYPOGRAPHY VARIABLES
73
76
  ======================================== */
@@ -152,6 +155,10 @@
152
155
  display: flex;
153
156
  justify-content: space-between;
154
157
  align-items: flex-start;
158
+ /* Guaranteed breathing room between the content and the keypad.
159
+ space-between alone only separates them while there is leftover
160
+ space, so wide content would otherwise run right up to the keypad. */
161
+ gap: var(--exercise-keypad-gap);
155
162
  }
156
163
 
157
164
  /* When inside a keypad layout, the exercise layout must grow to fill
@@ -319,15 +326,7 @@ mark {
319
326
  font-weight: bold;
320
327
  }
321
328
 
322
- /* ========================================
323
- IMAGE
324
- ======================================== */
325
-
326
- .image-item {
327
- vertical-align: middle;
328
- max-width: 100%;
329
- object-fit: contain;
330
- }
329
+ /* Image styles live in image.css */
331
330
 
332
331
  /* ========================================
333
332
  OPERATORS & SYMBOLS
@@ -546,7 +545,7 @@ mark {
546
545
  }
547
546
 
548
547
  .pill-bg-path {
549
- fill: var(--exercise-pill-bg, #FFE7B4);
548
+ fill: var(--exercise-pill-bg, #ffe7b4);
550
549
  }
551
550
 
552
551
  /*
@@ -685,11 +684,14 @@ button.drag-option {
685
684
  /**
686
685
  * Keypad container dimensions:
687
686
  * - min-width: 150px ensures keypad is always usable
688
- * - margins: 5px on each side for spacing
687
+ * - flex-shrink: 0 so the keypad keeps its size and the content
688
+ * (flex: 1; min-width: 0) absorbs the squeeze instead
689
+ * - margin-right: 5px keeps it off the outer edge; the space on its
690
+ * left comes from the container gap (--exercise-keypad-gap)
689
691
  */
690
692
  min-width: 150px;
693
+ flex-shrink: 0;
691
694
  margin-right: 5px;
692
- margin-left: 5px;
693
695
  }
694
696
 
695
697
  .numeric-keypad {
@@ -780,10 +782,8 @@ button.drag-option {
780
782
 
781
783
  .exercise-table {
782
784
  border-spacing: 0;
783
- border: 2px solid var(--exercise-table-border, #8b7ec8);
784
- border-radius: 12px;
785
- overflow: hidden;
786
785
  font-size: small;
786
+ width: 100%;
787
787
  }
788
788
 
789
789
  .exercise-table-cell {
@@ -801,12 +801,32 @@ button.drag-option {
801
801
  }
802
802
 
803
803
  table.exercise-table {
804
- border-collapse: collapse;
804
+ border-collapse: separate;
805
+ border-spacing: 0;
805
806
  }
806
807
 
807
808
  td.exercise-table-cell,
808
809
  th.exercise-table-cell {
809
- border: 2px solid #7c6cf2;
810
+ box-shadow: inset -2px -2px 0 0 #7c6cf2;
811
+ }
812
+
813
+ td.exercise-table-cell:last-child,
814
+ th.exercise-table-cell:last-child {
815
+ box-shadow: inset 0 -2px 0 0 #7c6cf2;
816
+ }
817
+
818
+ tr.exercise-table-row:last-child td.exercise-table-cell,
819
+ tr.exercise-table-row:last-child th.exercise-table-cell,
820
+ td.exercise-table-cell.exercise-table-cell--last-row,
821
+ th.exercise-table-cell.exercise-table-cell--last-row {
822
+ box-shadow: inset -2px 0 0 0 #7c6cf2;
823
+ }
824
+
825
+ tr.exercise-table-row:last-child td.exercise-table-cell:last-child,
826
+ tr.exercise-table-row:last-child th.exercise-table-cell:last-child,
827
+ td.exercise-table-cell.exercise-table-cell--last-row:last-child,
828
+ th.exercise-table-cell.exercise-table-cell--last-row:last-child {
829
+ box-shadow: none;
810
830
  }
811
831
 
812
832
  /* ========================================
@@ -968,11 +988,11 @@ th.exercise-table-cell {
968
988
  fill: var(--visual-fraction-hover-color);
969
989
  }
970
990
 
971
- .visual-fraction-widget__sector[role="button"]:focus {
991
+ .visual-fraction-widget__sector[role='button']:focus {
972
992
  outline: none;
973
993
  }
974
994
 
975
- .visual-fraction-widget__sector[role="button"]:focus-visible {
995
+ .visual-fraction-widget__sector[role='button']:focus-visible {
976
996
  /* Keyboard-only focus indicator: a drop-shadow that follows the SVG shape
977
997
  instead of a rectangle, preserving accessibility without the visual glitch. */
978
998
  outline: none;
@@ -1106,102 +1126,394 @@ th.exercise-table-cell {
1106
1126
  ======================================== */
1107
1127
 
1108
1128
  .pair-matcher-widget {
1109
- --pm-orange: #FFCA76; --pm-purple: #705DC0;
1110
- --pm-selected-bg: #E7E2FF; --pm-selected-border: #AE9CFF;
1111
- --pm-card-width: 163px; --pm-card-height: 82px; --pm-card-radius: 12px; --pm-card-border-width: 1px;
1112
- --pm-dot-size: 17px; --pm-dot-border-width: 2px;
1113
- --pm-line-color: var(--pm-orange); --pm-line-color-correct: #43B925; --pm-line-color-incorrect: #D6234D; --pm-line-width: 4px;
1114
- position: relative; display: flex; flex-direction: column;
1115
- gap: 52px; width: max-content; max-width: 100%; margin-inline: auto; padding: 0;
1129
+ --pm-orange: #ffca76;
1130
+ --pm-purple: #705dc0;
1131
+ --pm-selected-bg: #e7e2ff;
1132
+ --pm-selected-border: #ae9cff;
1133
+ --pm-card-width: 163px;
1134
+ --pm-card-height: 82px;
1135
+ --pm-card-radius: 12px;
1136
+ --pm-card-border-width: 1px;
1137
+ --pm-dot-size: 17px;
1138
+ --pm-dot-border-width: 2px;
1139
+ --pm-line-color: var(--pm-orange);
1140
+ --pm-line-color-correct: #43b925;
1141
+ --pm-line-color-incorrect: #d6234d;
1142
+ --pm-line-width: 4px;
1143
+ position: relative;
1144
+ display: flex;
1145
+ flex-direction: column;
1146
+ gap: 52px;
1147
+ width: max-content;
1148
+ max-width: 100%;
1149
+ margin-inline: auto;
1150
+ padding: 0;
1116
1151
  }
1117
1152
 
1118
1153
  .pair-matcher-row {
1119
- position: relative; z-index: 2; display: grid;
1154
+ position: relative;
1155
+ z-index: 2;
1156
+ display: grid;
1120
1157
  grid-template-columns: repeat(var(--pm-cols, 3), minmax(0, var(--pm-card-width)));
1121
- justify-content: center; gap: 18px; align-items: stretch; min-width: 0;
1158
+ justify-content: center;
1159
+ gap: 18px;
1160
+ align-items: stretch;
1161
+ min-width: 0;
1122
1162
  }
1123
1163
 
1124
- .pair-matcher-cell { display: flex; flex-direction: column; align-items: center; min-width: 0; }
1164
+ .pair-matcher-cell {
1165
+ display: flex;
1166
+ flex-direction: column;
1167
+ align-items: center;
1168
+ min-width: 0;
1169
+ }
1125
1170
 
1126
1171
  .pair-matcher-card {
1127
- box-sizing: border-box; display: flex; align-items: center; justify-content: center;
1128
- width: 100%; min-width: 0; max-width: 100%; align-self: stretch; flex: 1 1 auto;
1129
- min-height: var(--pm-card-height); height: auto; padding: 6px 8px;
1130
- border: var(--pm-card-border-width) solid var(--pm-orange); border-radius: var(--pm-card-radius);
1131
- background: #fff; color: var(--pm-purple);
1132
- font: 500 1rem / 1.2 var(--exercise-font-family, inherit); letter-spacing: 0;
1133
- text-align: center; word-break: normal; overflow-wrap: break-word;
1134
- white-space: normal; hyphens: auto; overflow: visible; cursor: pointer;
1135
- transition: background 0.35s ease, border-color 0.35s ease, transform 0.2s ease;
1172
+ box-sizing: border-box;
1173
+ display: flex;
1174
+ align-items: center;
1175
+ justify-content: center;
1176
+ width: 100%;
1177
+ min-width: 0;
1178
+ max-width: 100%;
1179
+ align-self: stretch;
1180
+ flex: 1 1 auto;
1181
+ min-height: var(--pm-card-height);
1182
+ height: auto;
1183
+ padding: 6px 8px;
1184
+ border: var(--pm-card-border-width) solid var(--pm-orange);
1185
+ border-radius: var(--pm-card-radius);
1186
+ background: #fff;
1187
+ color: var(--pm-purple);
1188
+ font: 500 1rem / 1.2 var(--exercise-font-family, inherit);
1189
+ letter-spacing: 0;
1190
+ text-align: center;
1191
+ word-break: normal;
1192
+ overflow-wrap: break-word;
1193
+ white-space: normal;
1194
+ hyphens: auto;
1195
+ overflow: visible;
1196
+ cursor: pointer;
1197
+ transition:
1198
+ background 0.35s ease,
1199
+ border-color 0.35s ease,
1200
+ transform 0.2s ease;
1136
1201
  }
1137
1202
 
1138
- .pair-matcher-card:not([aria-disabled="true"]):active { transform: scale(0.98); }
1203
+ .pair-matcher-card:not([aria-disabled='true']):active {
1204
+ transform: scale(0.98);
1205
+ }
1139
1206
 
1140
- .pair-matcher-card[aria-disabled="true"] { cursor: default; }
1207
+ .pair-matcher-card[aria-disabled='true'] {
1208
+ cursor: default;
1209
+ }
1141
1210
 
1142
- .pair-matcher-card--pending, .pair-matcher-card--flash { border-color: var(--pm-selected-border); background: var(--pm-selected-bg); }
1211
+ .pair-matcher-card--pending,
1212
+ .pair-matcher-card--flash {
1213
+ border-color: var(--pm-selected-border);
1214
+ background: var(--pm-selected-bg);
1215
+ }
1143
1216
 
1144
- .pair-matcher-card--correct { border-color: #43B925; background: #F2FFEE; color: #43B925; }
1217
+ .pair-matcher-card--correct {
1218
+ border-color: #43b925;
1219
+ background: #f2ffee;
1220
+ color: #43b925;
1221
+ }
1145
1222
 
1146
- .pair-matcher-card--incorrect { border-color: #D6234D; background: #FFF0F7; color: #D6234D; }
1223
+ .pair-matcher-card--incorrect {
1224
+ border-color: #d6234d;
1225
+ background: #fff0f7;
1226
+ color: #d6234d;
1227
+ }
1147
1228
 
1148
1229
  .pair-matcher-dot {
1149
- position: relative; z-index: 3; flex-shrink: 0;
1150
- width: var(--pm-dot-size); height: var(--pm-dot-size);
1151
- border: var(--pm-dot-border-width) solid var(--pm-orange); border-radius: 50%; background: #fff;
1230
+ position: relative;
1231
+ z-index: 3;
1232
+ flex-shrink: 0;
1233
+ width: var(--pm-dot-size);
1234
+ height: var(--pm-dot-size);
1235
+ border: var(--pm-dot-border-width) solid var(--pm-orange);
1236
+ border-radius: 50%;
1237
+ background: #fff;
1152
1238
  }
1153
1239
 
1154
- .pair-matcher-dot--south { margin-top: calc(var(--pm-dot-size) / -2); }
1240
+ .pair-matcher-dot--south {
1241
+ margin-top: calc(var(--pm-dot-size) / -2);
1242
+ }
1155
1243
 
1156
- .pair-matcher-dot--north { margin-bottom: calc(var(--pm-dot-size) / -2); }
1244
+ .pair-matcher-dot--north {
1245
+ margin-bottom: calc(var(--pm-dot-size) / -2);
1246
+ }
1157
1247
 
1158
- .pair-matcher-dot--active { background: var(--pm-orange); }
1248
+ .pair-matcher-dot--active {
1249
+ background: var(--pm-orange);
1250
+ }
1159
1251
 
1160
- .pair-matcher-dot--pending { border-color: var(--pm-selected-border); background: #fff; }
1252
+ .pair-matcher-dot--pending {
1253
+ border-color: var(--pm-selected-border);
1254
+ background: #fff;
1255
+ }
1161
1256
 
1162
- .pair-matcher-dot--correct { border-color: #43B925; background: #43B925; }
1257
+ .pair-matcher-dot--correct {
1258
+ border-color: #43b925;
1259
+ background: #43b925;
1260
+ }
1163
1261
 
1164
- .pair-matcher-dot--incorrect { border-color: #D6234D; background: #D6234D; }
1262
+ .pair-matcher-dot--incorrect {
1263
+ border-color: #d6234d;
1264
+ background: #d6234d;
1265
+ }
1165
1266
 
1166
1267
  .pair-matcher-lines {
1167
- position: absolute; inset: 0; z-index: 1;
1168
- width: 100%; height: 100%; overflow: visible; pointer-events: none;
1268
+ position: absolute;
1269
+ inset: 0;
1270
+ z-index: 1;
1271
+ width: 100%;
1272
+ height: 100%;
1273
+ overflow: visible;
1274
+ pointer-events: none;
1169
1275
  }
1170
1276
 
1171
- .pair-matcher-line { stroke-dasharray: 1; animation: pair-matcher-line-draw 0.5s ease both; }
1277
+ .pair-matcher-line {
1278
+ stroke-dasharray: 1;
1279
+ animation: pair-matcher-line-draw 0.5s ease both;
1280
+ }
1172
1281
 
1173
- @keyframes pair-matcher-line-draw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
1282
+ @keyframes pair-matcher-line-draw {
1283
+ from {
1284
+ stroke-dashoffset: 1;
1285
+ }
1286
+ to {
1287
+ stroke-dashoffset: 0;
1288
+ }
1289
+ }
1174
1290
 
1175
1291
  .pair-matcher-card .text-item,
1176
1292
  .pair-matcher-card .question-text,
1177
1293
  .pair-matcher-card .group {
1178
- margin: 0; min-width: 0; max-width: 100%;
1179
- color: inherit; font: inherit;
1180
- overflow-wrap: break-word; word-break: normal; white-space: normal;
1294
+ margin: 0;
1295
+ min-width: 0;
1296
+ max-width: 100%;
1297
+ color: inherit;
1298
+ font: inherit;
1299
+ overflow-wrap: break-word;
1300
+ word-break: normal;
1301
+ white-space: normal;
1181
1302
  }
1182
1303
 
1183
1304
  .pair-matcher-card:not(.pair-matcher-card--rtl) .text-item,
1184
1305
  .pair-matcher-card:not(.pair-matcher-card--rtl) .question-text,
1185
- .pair-matcher-card:not(.pair-matcher-card--rtl) .group { overflow-wrap: break-word; }
1306
+ .pair-matcher-card:not(.pair-matcher-card--rtl) .group {
1307
+ overflow-wrap: break-word;
1308
+ }
1186
1309
 
1187
- .pair-matcher-card--rtl { direction: rtl; unicode-bidi: plaintext; }
1310
+ .pair-matcher-card--rtl {
1311
+ direction: rtl;
1312
+ unicode-bidi: plaintext;
1313
+ }
1188
1314
 
1189
1315
  .pair-matcher-card-content {
1190
- display: block; width: 100%; min-width: 0; max-width: 100%; text-align: inherit; white-space: normal;
1316
+ display: block;
1317
+ width: 100%;
1318
+ min-width: 0;
1319
+ max-width: 100%;
1320
+ text-align: inherit;
1321
+ white-space: normal;
1191
1322
  }
1192
1323
 
1193
- .pair-matcher-card-content .space { display: inline; width: auto; }
1324
+ .pair-matcher-card-content .space {
1325
+ display: inline;
1326
+ width: auto;
1327
+ }
1194
1328
 
1195
1329
  .pair-matcher-card .text-item-arabic,
1196
1330
  .pair-matcher-card .question-text-arabic {
1197
- direction: rtl; font-size: 0.72rem; line-height: 1.15;
1198
- overflow-wrap: normal; word-break: normal; hyphens: manual;
1331
+ direction: rtl;
1332
+ font-size: 0.72rem;
1333
+ line-height: 1.15;
1334
+ overflow-wrap: normal;
1335
+ word-break: normal;
1336
+ hyphens: manual;
1199
1337
  }
1200
1338
 
1201
1339
  @media (max-width: 420px) {
1202
- .pair-matcher-widget { gap: 46px; }
1203
- .pair-matcher-row { grid-template-columns: repeat(var(--pm-cols, 3), minmax(0, 1fr)); gap: 12px; }
1204
- .pair-matcher-card { min-height: 58px; padding: 6px 7px; font-size: 0.92rem; }
1340
+ .pair-matcher-widget {
1341
+ gap: 46px;
1342
+ }
1343
+ .pair-matcher-row {
1344
+ grid-template-columns: repeat(var(--pm-cols, 3), minmax(0, 1fr));
1345
+ gap: 12px;
1346
+ }
1347
+ .pair-matcher-card {
1348
+ min-height: 58px;
1349
+ padding: 6px 7px;
1350
+ font-size: 0.92rem;
1351
+ }
1352
+ }
1353
+
1354
+ /**
1355
+ * Image Styles
1356
+ *
1357
+ * Inline images and their fullscreen (lightbox) view.
1358
+ * The lightbox is portalled to document.body, so its rules must not depend
1359
+ * on any ancestor inside .math-exercise-renderer.
1360
+ */
1361
+
1362
+ /* ========================================
1363
+ INLINE IMAGE
1364
+ ======================================== */
1365
+
1366
+ /**
1367
+ * Wrapper exists only for zoomable images. It is exactly the size of the
1368
+ * image — no padding, no gap, no extra track — because .exercise-line is a
1369
+ * flex row: any width the wrapper adds would push every later item along,
1370
+ * which would move a second inline image on the same line.
1371
+ */
1372
+
1373
+ .image-item-wrapper {
1374
+ position: relative;
1375
+ display: inline-block;
1376
+ /* line-height: 0 stops the inline descender gap from making the wrapper
1377
+ taller than the image it wraps. */
1378
+ line-height: 0;
1379
+ vertical-align: middle;
1380
+ }
1381
+
1382
+ .image-item {
1383
+ vertical-align: middle;
1384
+ max-width: 100%;
1385
+ object-fit: contain;
1386
+ }
1387
+
1388
+ .image-item-zoomable {
1389
+ cursor: zoom-in;
1390
+ }
1391
+
1392
+ /**
1393
+ * Expand affordance. Always visible: sitting outside the image, it costs
1394
+ * nothing to show, and a hover-reveal would be undiscoverable on touch.
1395
+ *
1396
+ * The diamond artwork carries all the chrome, so the button contributes no
1397
+ * border or background of its own. Its `color` drives the glyph, which is
1398
+ * drawn with currentColor.
1399
+ *
1400
+ * Absolutely positioned over the image's top-right corner: out of flow, so
1401
+ * it costs zero layout width.
1402
+ *
1403
+ * The overhang is deliberately small. .exercise-line is a flex row with only
1404
+ * a 0.25rem gap, so anything hanging past the image's right edge lands on the
1405
+ * next item — a live button there would swallow clicks meant for the input
1406
+ * beside the image. At 30% the badge still reads as a corner badge while the
1407
+ * part sticking out is a few pixels of tapering tip.
1408
+ *
1409
+ * clip-path does the rest: it removes the square's dead corners from the hit
1410
+ * area (clipped-away regions do not receive pointer events), which is exactly
1411
+ * where the box poked furthest into its neighbours. The polygon runs through
1412
+ * the diamond's four tips with a little tolerance for the 2px stroke, so it
1413
+ * circumscribes the artwork and clips no visible pixels.
1414
+ */
1415
+
1416
+ .image-zoom-btn {
1417
+ position: absolute;
1418
+ top: 0;
1419
+ right: 0;
1420
+ transform: translate(30%, -30%);
1421
+ clip-path: polygon(50.75% 13%, 90% 52.5%, 50.75% 92%, 11.5% 52.5%);
1422
+ display: flex;
1423
+ align-items: center;
1424
+ justify-content: center;
1425
+ flex-shrink: 0;
1426
+ /* Slightly larger than a plain square button: the diamond only fills
1427
+ ~75% of its viewBox, so the visible badge lands near 1.7rem. */
1428
+ width: 2.25rem;
1429
+ height: 2.25rem;
1430
+ padding: 0;
1431
+ border: none;
1432
+ background: none;
1433
+ color: var(--exercise-accent, #705dc0);
1434
+ line-height: 0;
1435
+ cursor: zoom-in;
1436
+ transition: transform 0.15s ease;
1437
+ }
1438
+
1439
+ .image-zoom-icon {
1440
+ width: 100%;
1441
+ height: 100%;
1442
+ display: block;
1443
+ }
1444
+
1445
+ /* The translate must be repeated here: transform is one property, so a bare
1446
+ scale() would drop the positioning and snap the badge back onto the image. */
1447
+
1448
+ .image-zoom-btn:hover,
1449
+ .image-zoom-btn:focus-visible {
1450
+ transform: translate(30%, -30%) scale(1.08);
1451
+ }
1452
+
1453
+ @media (prefers-reduced-motion: reduce) {
1454
+ .image-zoom-btn {
1455
+ transition: none;
1456
+ }
1457
+
1458
+ .image-zoom-btn:hover,
1459
+ .image-zoom-btn:focus-visible {
1460
+ transform: translate(30%, -30%);
1461
+ }
1462
+ }
1463
+
1464
+ /* ========================================
1465
+ FULLSCREEN LIGHTBOX
1466
+ ======================================== */
1467
+
1468
+ .image-lightbox {
1469
+ position: fixed;
1470
+ inset: 0;
1471
+ z-index: 1000;
1472
+ display: flex;
1473
+ align-items: center;
1474
+ justify-content: center;
1475
+ padding: 2rem;
1476
+ background: rgba(0, 0, 0, 0.85);
1477
+ cursor: zoom-out;
1478
+ }
1479
+
1480
+ .image-lightbox-img {
1481
+ max-width: 100%;
1482
+ max-height: 100%;
1483
+ object-fit: contain;
1484
+ cursor: default;
1485
+ }
1486
+
1487
+ .image-lightbox-close {
1488
+ position: absolute;
1489
+ top: 1rem;
1490
+ right: 1rem;
1491
+ display: flex;
1492
+ align-items: center;
1493
+ justify-content: center;
1494
+ width: 2.5rem;
1495
+ height: 2.5rem;
1496
+ padding: 0;
1497
+ border: none;
1498
+ border-radius: 50%;
1499
+ /* Solid white disc: the cross is the accent purple, which would be muddy
1500
+ read straight against the dark backdrop. */
1501
+ background: rgba(255, 255, 255, 0.95);
1502
+ color: var(--exercise-accent, #705dc0);
1503
+ line-height: 0;
1504
+ cursor: pointer;
1505
+ transition: background 0.15s ease;
1506
+ }
1507
+
1508
+ .image-lightbox-close-icon {
1509
+ width: 1.5rem;
1510
+ height: 1.5rem;
1511
+ display: block;
1512
+ }
1513
+
1514
+ .image-lightbox-close:hover,
1515
+ .image-lightbox-close:focus-visible {
1516
+ background: #ffffff;
1205
1517
  }
1206
1518
 
1207
1519
  /**
@@ -2171,6 +2483,11 @@ th.exercise-table-cell {
2171
2483
  /** Reduce gap between keypad buttons */
2172
2484
  gap: var(--exercise-element-margin);
2173
2485
  }
2486
+
2487
+ .exercise-layout-container.has-keypad {
2488
+ /** Tighter separation between content and keypad on narrow screens */
2489
+ gap: calc(var(--exercise-keypad-gap) * 0.5);
2490
+ }
2174
2491
  }
2175
2492
 
2176
2493
  /* ========================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mathscan/math-exercise-engine",
3
- "version": "1.1.14",
3
+ "version": "1.1.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",