lido-player 0.0.2-beta-16-dev → 0.0.2-beta-17-dev

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.
Files changed (76) hide show
  1. package/dist/cjs/{decode-C08Ujimj.js → decode-DUZYNXIv.js} +1 -1
  2. package/dist/cjs/{index-BkBqiwe8.js → index-BEjUmVtH.js} +189 -53
  3. package/dist/cjs/{index-Dy19zuTX.js → index-DnqQXD5x.js} +53 -12
  4. package/dist/cjs/index.cjs.js +2 -2
  5. package/dist/cjs/lido-avatar_22.cjs.entry.js +60 -54
  6. package/dist/cjs/lido-player.cjs.js +3 -3
  7. package/dist/cjs/loader.cjs.js +2 -2
  8. package/dist/collection/collection-manifest.json +1 -1
  9. package/dist/collection/components/calculator/lido-calculator.js +3 -3
  10. package/dist/collection/components/mathMatrix/lido-math-matrix.js +12 -2
  11. package/dist/collection/components/scale/assets/Shadow.svg +9 -0
  12. package/dist/collection/components/scale/lido-balance.css +1 -1
  13. package/dist/collection/components/scale/lido-balance.js +13 -11
  14. package/dist/collection/components/text/lido-text.js +28 -2
  15. package/dist/collection/components/trace/assets/Tracing-hand.svg +8 -0
  16. package/dist/collection/components/trace/lido-trace.js +25 -38
  17. package/dist/collection/css/index.css +7 -4
  18. package/dist/collection/stories/Templates/blender/blender.stories.js +9 -5
  19. package/dist/collection/utils/audioPlayer.js +31 -9
  20. package/dist/collection/utils/utils.js +20 -10
  21. package/dist/collection/utils/utilsHandlers/animationHandler.js +9 -2
  22. package/dist/collection/utils/utilsHandlers/clickHandler.js +8 -0
  23. package/dist/collection/utils/utilsHandlers/dragDropHandler.js +28 -8
  24. package/dist/collection/utils/utilsHandlers/lidoCalculatorHandler.js +12 -15
  25. package/dist/collection/utils/utilsHandlers/slideHandler.js +76 -17
  26. package/dist/components/index.js +1 -1
  27. package/dist/components/lido-balance.js +1 -1
  28. package/dist/components/lido-calculator.js +1 -1
  29. package/dist/components/lido-container.js +1 -1
  30. package/dist/components/lido-home.js +1 -1
  31. package/dist/components/lido-keyboard.js +1 -1
  32. package/dist/components/lido-math-matrix.js +1 -1
  33. package/dist/components/lido-root.js +1 -1
  34. package/dist/components/lido-text.js +1 -1
  35. package/dist/components/lido-trace.js +1 -1
  36. package/dist/components/p-BNNX1Pew.js +12 -0
  37. package/dist/components/p-CTlwn9r7.js +1 -0
  38. package/dist/components/{p-C2p0KZh4.js → p-Cj-6tqzU.js} +1 -1
  39. package/dist/components/p-CjdDpXfk.js +1 -0
  40. package/dist/components/{p-Uluc7jQ7.js → p-D54c-ybm.js} +1 -1
  41. package/dist/components/p-DdQ1eqJr.js +1 -0
  42. package/dist/components/{p-D-IYWSAa.js → p-SPZTb5fI.js} +1 -1
  43. package/dist/components/p-yT7udDfd.js +1 -0
  44. package/dist/esm/{decode-CGJIPA4t.js → decode-BgBfbPZu.js} +1 -1
  45. package/dist/esm/{index-Dq-Z8Dx0.js → index-BeHFCsWf.js} +54 -13
  46. package/dist/esm/{index-C854B63S.js → index-D-29PaRa.js} +189 -53
  47. package/dist/esm/index.js +2 -2
  48. package/dist/esm/lido-avatar_22.entry.js +60 -54
  49. package/dist/esm/lido-player.js +4 -4
  50. package/dist/esm/loader.js +3 -3
  51. package/dist/lido-player/assets/Shadow.svg +9 -0
  52. package/dist/lido-player/assets/Tracing-hand.svg +8 -0
  53. package/dist/lido-player/index.esm.js +1 -1
  54. package/dist/lido-player/lido-player.css +1 -1
  55. package/dist/lido-player/lido-player.esm.js +1 -1
  56. package/dist/lido-player/p-856e1956.entry.js +12 -0
  57. package/dist/lido-player/{p-Bg5mBsd9.js → p-BA2X6O7O.js} +1 -1
  58. package/dist/lido-player/p-BeHFCsWf.js +3 -0
  59. package/dist/lido-player/p-TuBmCgtK.js +1 -0
  60. package/dist/types/components/scale/lido-balance.d.ts +2 -2
  61. package/dist/types/components/text/lido-text.d.ts +6 -1
  62. package/dist/types/components/trace/lido-trace.d.ts +1 -1
  63. package/dist/types/components.d.ts +21 -10
  64. package/dist/types/stencil-public-runtime.d.ts +12 -0
  65. package/dist/types/utils/audioPlayer.d.ts +2 -0
  66. package/dist/types/utils/utilsHandlers/slideHandler.d.ts +5 -0
  67. package/loader/index.d.ts +1 -1
  68. package/package.json +1 -1
  69. package/dist/components/p-C7U1h9gI.js +0 -1
  70. package/dist/components/p-CS6iRQJh.js +0 -1
  71. package/dist/components/p-DGvzMlyn.js +0 -12
  72. package/dist/components/p-DPAlnts-.js +0 -1
  73. package/dist/components/p-DYs8s-uh.js +0 -1
  74. package/dist/lido-player/p-C4OTDhbr.js +0 -1
  75. package/dist/lido-player/p-Dq-Z8Dx0.js +0 -2
  76. package/dist/lido-player/p-f0e333c6.entry.js +0 -12
@@ -1,4 +1,4 @@
1
- import { h, Host } from "@stencil/core";
1
+ import { h, Host, getAssetPath } from "@stencil/core";
2
2
  import { convertUrlToRelative, executeActions, triggerNextContainer, speakIcon, setVisibilityWithDelay, parseProp, storingEachActivityScore, calculateScore, } from "../../utils/utils";
3
3
  import { fingerUrl, LidoContainer, TraceMode } from "../../utils/constants";
4
4
  // Enum for different tracing modes
@@ -88,7 +88,7 @@ export class LidoTrace {
88
88
  /**
89
89
  * URL for the finger hint image
90
90
  */
91
- this.fingerHintUrl = 'https://aeakbcdznktpsbrfsgys.supabase.co/storage/v1/object/public/template-assets/trace/Tracing-hand.svg';
91
+ this.fingerHintUrl = getAssetPath('assets/Tracing-hand.svg');
92
92
  /**
93
93
  * Indicates whether to highlight the text associated with the SVG when the trace is completed.
94
94
  */
@@ -112,6 +112,8 @@ export class LidoTrace {
112
112
  this.isDragging = false;
113
113
  this.activePointerId = null;
114
114
  // Counter for throttling free trace updates
115
+ // This is only an internal counter. Keeping it out of @State prevents a
116
+ // Stencil component re-render while the user is dragging.
115
117
  this.freeTraceUpdateCounter = 0;
116
118
  /** ───────────────────────────────────────────────────────────
117
119
  * NEW: idle‑timer + finger‑hint state
@@ -144,6 +146,7 @@ export class LidoTrace {
144
146
  lastPointerPos: null,
145
147
  dragOffset: null,
146
148
  isCompletingPath: false,
149
+ touchProximityThreshold: null,
147
150
  };
148
151
  const url = this.svgUrls[this.currentSvgIndex];
149
152
  if (!url || url.trim() === '') {
@@ -363,6 +366,12 @@ export class LidoTrace {
363
366
  setupDrawingPath(state) {
364
367
  state.paths.forEach((path, index) => {
365
368
  const pathLength = path.getTotalLength();
369
+ // Geometry calls are expensive on slower SVG implementations. Cache
370
+ // the length once and reuse it during pointer movement.
371
+ path._lidoTraceLength = pathLength;
372
+ path._lidoTraceStart = path.getPointAtLength(0);
373
+ path._lidoTraceEnd = path.getPointAtLength(pathLength);
374
+ path._lidoTraceIsClosed = this.getDistanceSquared(path._lidoTraceStart, path._lidoTraceEnd) < 200;
366
375
  // /** give every path an id so <mpath> can follow it */
367
376
  path.setAttribute('id', 'lido-path-' + index); // ← NEW
368
377
  // Create green path for tracing effect
@@ -484,7 +493,7 @@ export class LidoTrace {
484
493
  }
485
494
  // Modified handlePointerMove function
486
495
  async handlePointerMove(state) {
487
- var _a, _b, _c, _d, _e, _f;
496
+ var _a, _b, _c, _d, _e;
488
497
  if (!state.isDragging)
489
498
  return;
490
499
  if (!state.circle || !state.paths || state.paths.length === 0)
@@ -584,19 +593,9 @@ export class LidoTrace {
584
593
  // Move the draggable circle with the freehand trace
585
594
  state.circle.setAttribute('cx', pointerPos.x.toString());
586
595
  state.circle.setAttribute('cy', pointerPos.y.toString());
587
- // Only re-append if not already children list
588
- const childNodes = (_b = state.svg) === null || _b === void 0 ? void 0 : _b.childNodes;
589
- let circleFound = false;
590
- for (let i = 0; i < ((childNodes === null || childNodes === void 0 ? void 0 : childNodes.length) || 0); i++) {
591
- const child = childNodes === null || childNodes === void 0 ? void 0 : childNodes.item(i);
592
- if (child && child.tagName === 'circle') {
593
- circleFound = true;
594
- break; // No need to continue once found
595
- }
596
- }
597
- // If not found, append the circle
598
- if (!circleFound && state.circle) {
599
- (_c = state.svg) === null || _c === void 0 ? void 0 : _c.appendChild(state.circle);
596
+ // Keep the original safety check without scanning every SVG child.
597
+ if (state.circle.parentNode !== state.svg) {
598
+ (_b = state.svg) === null || _b === void 0 ? void 0 : _b.appendChild(state.circle);
600
599
  }
601
600
  // Update the last pointer position
602
601
  state.lastPointerPos = pointerPos;
@@ -640,27 +639,15 @@ export class LidoTrace {
640
639
  state.circle.setAttribute('cx', guidedClosestPoint.x.toString());
641
640
  state.circle.setAttribute('cy', guidedClosestPoint.y.toString());
642
641
  }
643
- // Only re-append if not already children list
644
- const childNodes = (_d = state.svg) === null || _d === void 0 ? void 0 : _d.childNodes;
645
- let circleFound = false;
646
- for (let i = 0; i < ((childNodes === null || childNodes === void 0 ? void 0 : childNodes.length) || 0); i++) {
647
- const child = childNodes === null || childNodes === void 0 ? void 0 : childNodes.item(i);
648
- if (child && child.tagName === 'circle') {
649
- circleFound = true;
650
- break; // No need to continue once found
651
- }
652
- }
653
- // If not found, append the circle
654
- if (!circleFound && state.circle) {
655
- (_e = state.svg) === null || _e === void 0 ? void 0 : _e.appendChild(state.circle);
642
+ // Keep the original safety check without scanning every SVG child.
643
+ if (state.circle.parentNode !== state.svg) {
644
+ (_c = state.svg) === null || _c === void 0 ? void 0 : _c.appendChild(state.circle);
656
645
  }
657
- (_f = currentPath.greenPath) === null || _f === void 0 ? void 0 : _f.setAttribute('stroke-dashoffset', (state.totalPathLength - state.lastLength).toString());
646
+ (_d = currentPath.greenPath) === null || _d === void 0 ? void 0 : _d.setAttribute('stroke-dashoffset', (state.totalPathLength - state.lastLength).toString());
658
647
  // Completion logic for closed paths: only allow completion if almost all points are traced
659
648
  const COMPLETION_THRESHOLD = 0.95; // 95% of the path must be traced
660
649
  let percentComplete = state.lastLength / state.totalPathLength;
661
- let startPoint = currentPath.getPointAtLength(0);
662
- let endPoint = currentPath.getPointAtLength(currentPath.getTotalLength());
663
- let pathIsClosed = this.getDistanceSquared(startPoint, endPoint) < 200; // threshold for overlap
650
+ const pathIsClosed = (_e = currentPath._lidoTraceIsClosed) !== null && _e !== void 0 ? _e : false;
664
651
  if (pathIsClosed && state.totalPathLength > 50) {
665
652
  if (percentComplete >= COMPLETION_THRESHOLD) {
666
653
  if (state.isCompletingPath)
@@ -854,7 +841,8 @@ export class LidoTrace {
854
841
  }
855
842
  // Find the closest point on the given path to the specified point using two-pass sampling (optimized)
856
843
  getClosestPointOnPath(pathNode, point, lastLength) {
857
- const pathLength = pathNode.getTotalLength();
844
+ var _a;
845
+ const pathLength = (_a = pathNode._lidoTraceLength) !== null && _a !== void 0 ? _a : pathNode.getTotalLength();
858
846
  let closestPoint = { x: 0, y: 0, length: 0 };
859
847
  let minDistanceSquared = Infinity;
860
848
  const coarseStep = 40;
@@ -1047,7 +1035,7 @@ export class LidoTrace {
1047
1035
  };
1048
1036
  }
1049
1037
  render() {
1050
- return (h(Host, { key: '31f7f62d73ed98ce0a3b224435e7b64574cf506c', class: "lido-trace", id: this.id, audio: this.audio, onCorrect: this.onCorrect, onInCorrect: this.onInCorrect, style: this.style, "aria-label": this.ariaLabel, "aria-hidden": this.ariaHidden, tabindex: this.tabIndex, "disable-speak": this.disableSpeak }, h("div", { key: '6cf11b7d5f9aeb3c58cbb462e63d3f340384a403', style: this.style, id: "lido-svgContainer" })));
1038
+ return (h(Host, { key: '4955636a199ba56ca7f6d73144aca42ab4e3f411', class: "lido-trace", id: this.id, audio: this.audio, onCorrect: this.onCorrect, onInCorrect: this.onInCorrect, style: this.style, "aria-label": this.ariaLabel, "aria-hidden": this.ariaHidden, tabindex: this.tabIndex, "disable-speak": this.disableSpeak }, h("div", { key: 'b732946e97fefb0d4cb192e5c9b6863568ff64be', style: this.style, id: "lido-svgContainer" })));
1051
1039
  }
1052
1040
  static get is() { return "lido-trace"; }
1053
1041
  static get originalStyleUrls() {
@@ -1060,7 +1048,7 @@ export class LidoTrace {
1060
1048
  "$": ["lido-trace.css"]
1061
1049
  };
1062
1050
  }
1063
- static get assetsDirs() { return ["svg", "images"]; }
1051
+ static get assetsDirs() { return ["assets"]; }
1064
1052
  static get properties() {
1065
1053
  return {
1066
1054
  "showSpeakIcon": {
@@ -1361,7 +1349,7 @@ export class LidoTrace {
1361
1349
  "setter": false,
1362
1350
  "reflect": false,
1363
1351
  "attribute": "finger-hint-url",
1364
- "defaultValue": "'https://aeakbcdznktpsbrfsgys.supabase.co/storage/v1/object/public/template-assets/trace/Tracing-hand.svg'"
1352
+ "defaultValue": "getAssetPath('assets/Tracing-hand.svg')"
1365
1353
  },
1366
1354
  "onInCorrect": {
1367
1355
  "type": "string",
@@ -1493,7 +1481,6 @@ export class LidoTrace {
1493
1481
  "fileIndex": {},
1494
1482
  "isDragging": {},
1495
1483
  "activePointerId": {},
1496
- "freeTraceUpdateCounter": {},
1497
1484
  "idleTimer": {},
1498
1485
  "fingerImg": {}
1499
1486
  };
@@ -1,6 +1,10 @@
1
- @import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
2
- @import url('https://fonts.googleapis.com/css2?family=Baloo+2:wght@400..800&display=swap');
3
- @import url('https://fonts.googleapis.com/css2?family=Baloo+Bhai+2:wght@400..800&display=swap');
1
+ @font-face {
2
+ font-family: 'Baloo Bhai 2';
3
+ font-style: normal;
4
+ font-weight: 400 800;
5
+ font-display: swap;
6
+ src: url('../assets/offline-assets/fonts/Baloo-Bhai-2/Baloo-Bhai-2-Variable.woff2') format('woff2');
7
+ }
4
8
  body {
5
9
  overflow: hidden;
6
10
  background-position: center;
@@ -189,7 +193,6 @@ body {
189
193
  top: -25px;
190
194
  right: -25px;
191
195
  z-index: 10;
192
- background-image : url("https://aeakbcdznktpsbrfsgys.supabase.co/storage/v1/object/public/template-assets/template/audioIcon.png");
193
196
  background-color: white;
194
197
  border : 4px solid #F34D08;
195
198
  border-radius :16px;
@@ -39,13 +39,16 @@ function buildObjectiveFromNumber(num) {
39
39
  const objective = [hStr, tStr, oStr].filter(s => s !== "").join(",");
40
40
  return {
41
41
  objective, // same as before
42
+ hundreds,
43
+ tens,
44
+ ones,
42
45
  };
43
46
  }
44
47
  function getContainerXml(args) {
45
- const { objective } = buildObjectiveFromNumber(args.number);
48
+ const { objective, hundreds, tens, ones } = buildObjectiveFromNumber(args.number);
46
49
  return `
47
50
  <main>
48
- <lido-container id="lido-container" visible="true" template-id="dragAndDrop" objective="${objective}" onCorrect="lido-avatar.avatarAnimate='Success'; this.sleep='2000';" onInCorrect="lido-avatar.avatarAnimate='Fail'; this.sleep='2000';" onEntry="" is-continue-on-correct="true" bg-color="transparent" bg-image="https://aeakbcdznktpsbrfsgys.supabase.co/storage/v1/object/public/template-assets/background-images/Blender.png" drop-action="infinite-drop" is-allow-only-correct="${args.isAllowOnlyCorrect}">
51
+ <lido-container id="lido-container" visible="true" template-id="blender" objective="${objective}" onCorrect="lido-avatar.avatarAnimate='Success'; this.sleep='2000';" onInCorrect="lido-avatar.avatarAnimate='Fail'; this.sleep='2000';" onEntry="" is-continue-on-correct="true" bg-color="transparent" bg-image="https://aeakbcdznktpsbrfsgys.supabase.co/storage/v1/object/public/template-assets/background-images/Blender.png" drop-action="infinite-drop" is-allow-only-correct="${args.isAllowOnlyCorrect}">
49
52
 
50
53
  <!-- Chimple Avatar -->
51
54
  <lido-cell layout="pos" id="pos1" disable-edit="true" value="pos2" height="landscape.448px,portrait.402px" width="landscape.350px,portrait.398px" x="landscape.1332px, portrait.-8%" y="landscape.547px, portrait.1%" aria-hidden="true" z="0" bg-color="transparent" visible="true" onEntry="">
@@ -65,7 +68,7 @@ function getContainerXml(args) {
65
68
  <lido-text id="hundreds" height="70px" width="108px" visible="true" value="000" string="000" font-family="'Baloo Bhai 2'" font-color="#182A4F" border-radius="21px" bg-color="white" font-size="52px" onInCorrect="" onEntry="this.fontWeight='800';" border-image="">
66
69
  </lido-text>
67
70
  </lido-cell>
68
- <lido-text id="hundredsDrop" value="100" dropAttr="landscape.stackcascade,portrait.verticalstack" is-allow-only-one-drop="false" type="drop" tab-index="1" layout="row" bg-color="white" visible="true" height="landscape.359px,portrait.752px" width="landscape.698px,portrait.290px" onEntry="this.justify-content='flex-start';">
71
+ <lido-text id="hundredsDrop" value="100" required-drops="${hundreds}" dropAttr="landscape.stackcascade,portrait.verticalstack" is-allow-only-one-drop="false" type="drop" tab-index="1" layout="row" bg-color="white" visible="true" height="landscape.359px,portrait.752px" width="landscape.698px,portrait.290px" onEntry="this.justify-content='flex-start';">
69
72
  </lido-text>
70
73
  </lido-cell>
71
74
 
@@ -77,7 +80,8 @@ function getContainerXml(args) {
77
80
  <lido-text id="tens" height="70px" width="77px" visible="true" value="00" string="00" font-family="'Baloo Bhai 2'" font-color="#182A4F" border-radius="21px" bg-color="white" font-size="52px" onInCorrect="" onEntry="this.fontWeight='800';" border-image="">
78
81
  </lido-text>
79
82
  </lido-cell>
80
- <lido-text id="tensDrop" value="10" dropAttr="landscape.stackcascade,portrait.verticalstack" type="drop" is-allow-only-one-drop="false" tab-index="2" layout="row" bg-color="white" visible="true" height="landscape.357px,portrait.752px" width="landscape.414px,portrait.290px" onEntry="this.justify-content='flex-start';" >
83
+ <lido-text id="tensDrop" value="10" required-drops="${tens}" dropAttr="landscape.stackcascade,portrait.verticalstack" type="drop" is-allow-only-one-drop="false" tab-index="2" layout="row" bg-color="white" visible="true"
84
+ height="landscape.357px,portrait.752px" width="landscape.414px,portrait.290px" onEntry="this.justify-content='flex-start';" >
81
85
  </lido-text>
82
86
  </lido-cell>
83
87
 
@@ -89,7 +93,7 @@ function getContainerXml(args) {
89
93
  <lido-text id="units" height="70px" width="55px" visible="true" value="0" string="0" font-family="'Baloo Bhai 2'" font-color="#182A4F" border-radius="21px" bg-color="white" font-size="52px" onInCorrect="" onEntry="this.fontWeight='800';" border-image="">
90
94
  </lido-text>
91
95
  </lido-cell>
92
- <lido-text id="unitsDrop" value="1" dropAttr="landscape.stackcascade,portrait.verticalstack" type="drop" is-allow-only-one-drop="false" tab-index="3" layout="row" bg-color="white" visible="true" height="landscape.360px,portrait.752px" width="landscape.310px,portrait.290px" onEntry="this.justify-content='flex-start';">
96
+ <lido-text id="unitsDrop" value="1" required-drops="${ones}" dropAttr="landscape.stackcascade,portrait.verticalstack" type="drop" is-allow-only-one-drop="false" tab-index="3" layout="row" bg-color="white" visible="true" height="landscape.360px,portrait.752px" width="landscape.310px,portrait.290px" onEntry="this.justify-content='flex-start';">
93
97
  </lido-text>
94
98
  </lido-cell>
95
99
  </lido-cell>
@@ -4,9 +4,18 @@ import { setDraggingDisabled } from "./utilsHandlers/dragDropHandler";
4
4
  import { NextContainerKey, PrevContainerKey, ActivityChangeKey, GameCompletedKey, GameExitKey, ActivityEndKey, LessonEndKey, LidoContainer } from "./constants";
5
5
  import { LANGUAGE_PROFILES, FAST_WORDS_BY_LANG } from "./constants";
6
6
  export class AudioPlayer {
7
+ resolvePlaybackWait() {
8
+ if (!this.endPromiseResolve) {
9
+ return;
10
+ }
11
+ const resolve = this.endPromiseResolve;
12
+ this.endPromiseResolve = null;
13
+ resolve();
14
+ }
7
15
  constructor() {
8
16
  this.currentTargetElement = null;
9
17
  this.pendingReplayElement = null;
18
+ this.playbackHasStarted = false;
10
19
  this.highlightOverlay = null;
11
20
  this.wordRects = [];
12
21
  this.activeWordIndex = -1;
@@ -64,15 +73,12 @@ export class AudioPlayer {
64
73
  window.speechSynthesis.cancel();
65
74
  }
66
75
  // Resolve any pending "ended" wait so callers can continue.
67
- if (this.endPromiseResolve) {
68
- const resolve = this.endPromiseResolve;
69
- this.endPromiseResolve = null;
70
- resolve();
71
- }
76
+ this.resolvePlaybackWait();
72
77
  if (!preserveReplay) {
73
78
  this.pendingReplayElement = null;
74
79
  }
75
80
  this.currentTargetElement = null;
81
+ this.playbackHasStarted = false;
76
82
  this.audioElement.pause();
77
83
  this.audioElement.currentTime = 0;
78
84
  this.audioElement.src = '';
@@ -110,6 +116,7 @@ export class AudioPlayer {
110
116
  }
111
117
  async play(targetElement) {
112
118
  this.registerVisibilityEvents();
119
+ this.playbackHasStarted = false;
113
120
  if (!this.isWindowVisible()) {
114
121
  this.pendingReplayElement = targetElement;
115
122
  await this.stop(true);
@@ -163,6 +170,24 @@ export class AudioPlayer {
163
170
  if (audioUrl) {
164
171
  audioUrl = convertUrlToRelative(audioUrl);
165
172
  this.audioElement.src = audioUrl;
173
+ this.audioElement.onplaying = () => {
174
+ this.playbackHasStarted = true;
175
+ };
176
+ this.audioElement.onpause = () => {
177
+ if (this.playbackHasStarted && !this.audioElement.ended) {
178
+ this.resolvePlaybackWait();
179
+ }
180
+ };
181
+ this.audioElement.onstalled = () => {
182
+ if (this.playbackHasStarted) {
183
+ this.resolvePlaybackWait();
184
+ }
185
+ };
186
+ this.audioElement.onerror = () => {
187
+ if (this.playbackHasStarted) {
188
+ this.resolvePlaybackWait();
189
+ }
190
+ };
166
191
  try {
167
192
  // setDraggingDisabled(true);
168
193
  const language = container.getAttribute('Lang') || 'en';
@@ -192,10 +217,7 @@ export class AudioPlayer {
192
217
  await new Promise(resolve => {
193
218
  this.endPromiseResolve = resolve;
194
219
  this.audioElement.onended = () => {
195
- if (this.endPromiseResolve === resolve) {
196
- this.endPromiseResolve = null;
197
- }
198
- resolve();
220
+ this.resolvePlaybackWait();
199
221
  };
200
222
  });
201
223
  }
@@ -337,10 +337,11 @@ export const executeActions = async (actionsString, thisElement, element) => {
337
337
  if (dropTo === "hundredsDrop")
338
338
  hundreds++;
339
339
  });
340
- const unitsValue = units * 1;
341
- const tensValue = tens * 10;
342
- const hundredsValue = hundreds * 100;
343
- const totalValue = unitsValue + tensValue + hundredsValue;
340
+ // Blender displays the number of blocks placed in each column.
341
+ // Blender's drag/drop values are also the required block counts.
342
+ const unitsValue = units;
343
+ const tensValue = tens;
344
+ const hundredsValue = hundreds;
344
345
  // ✅ Update Lido Text Boxes
345
346
  const unitsBox = document.getElementById("units");
346
347
  const tensBox = document.getElementById("tens");
@@ -601,18 +602,27 @@ export const calculateScore = () => {
601
602
  gameScore.totalRightMovesCount += rightMoves;
602
603
  gameScore.totalWrongMovesCount += wrongMoves;
603
604
  let finalScore = Math.floor((rightMoves / (rightMoves + wrongMoves)) * 100);
605
+ console.log('[Activity Score]', { rightMoves, wrongMoves, activityScore: finalScore });
604
606
  storeActivityScore(finalScore);
605
607
  gameScore.rightMoves = 0;
606
608
  gameScore.wrongMoves = 0;
607
609
  };
608
610
  export async function onActivityComplete(dragElement, dropElement) {
609
- var _a, _b;
611
+ var _a, _b, _c;
610
612
  const container = document.getElementById(LidoContainer);
611
613
  if (!container)
612
614
  return;
615
+ const isBlender = container.getAttribute('template-id') === 'blender';
613
616
  await executeActions("this.alignMatch='true'", dropElement, dragElement);
614
617
  if (dragElement && dropElement) {
615
- const isCorrect = dropElement['value'].toLowerCase().includes(dragElement['value'].toLowerCase());
618
+ let isCorrect = dropElement['value'].toLowerCase().includes(dragElement['value'].toLowerCase());
619
+ if (isBlender) {
620
+ const requiredDrops = Number((_a = dropElement.getAttribute('required-drops')) !== null && _a !== void 0 ? _a : 0);
621
+ const placedDrops = document.querySelectorAll(`[drop-to="${dropElement.id}"]`).length;
622
+ // Blender accepts every count from 1 through the required count.
623
+ // Zero or an overfilled drop zone uses the incorrect action.
624
+ isCorrect = placedDrops > 0 && placedDrops <= requiredDrops;
625
+ }
616
626
  // storing each activity score based on isCorrect for (all drag-drop events)
617
627
  // storingEachActivityScore(isCorrect);
618
628
  if (isCorrect) {
@@ -621,10 +631,10 @@ export async function onActivityComplete(dragElement, dropElement) {
621
631
  console.log("Right Moves : ", gameScore.rightMoves);
622
632
  console.log("Wrong Moves : ", gameScore.wrongMoves);
623
633
  }
624
- const onCorrect = ((_a = container.getAttribute('dropAttr')) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === DropMode.EnableAnimation.toLowerCase()
634
+ const onCorrect = ((_b = container.getAttribute('dropAttr')) === null || _b === void 0 ? void 0 : _b.toLowerCase()) === DropMode.EnableAnimation.toLowerCase()
625
635
  ? ''
626
636
  : dropElement.getAttribute('onCorrect');
627
- if (onCorrect) {
637
+ if (onCorrect && !isBlender) {
628
638
  await executeActions(onCorrect, dropElement, dragElement);
629
639
  }
630
640
  //for multiply beeds
@@ -639,7 +649,7 @@ export async function onActivityComplete(dragElement, dropElement) {
639
649
  console.log("Wrong Moves : ", gameScore.wrongMoves);
640
650
  }
641
651
  const onInCorrect = dropElement.getAttribute('onInCorrect');
642
- if (onInCorrect) {
652
+ if (onInCorrect && !isBlender) {
643
653
  await executeActions(onInCorrect, dropElement, dragElement);
644
654
  }
645
655
  }
@@ -648,7 +658,7 @@ export async function onActivityComplete(dragElement, dropElement) {
648
658
  const sortedValues = getSortedValuesArrayFromMap(dragScore);
649
659
  container.setAttribute(SelectedValuesKey, JSON.stringify(sortedValues));
650
660
  //localStorage
651
- let drag = JSON.parse((_b = localStorage.getItem(DragMapKey)) !== null && _b !== void 0 ? _b : '{}');
661
+ let drag = JSON.parse((_c = localStorage.getItem(DragMapKey)) !== null && _c !== void 0 ? _c : '{}');
652
662
  const index = dropElement.getAttribute('tab-index');
653
663
  if (!drag[index]) {
654
664
  drag[index] = [];
@@ -60,7 +60,7 @@ export function dragDropAnimation(container, dragElement, dropElement) {
60
60
  dropElement.classList.remove('zoom-fade-in');
61
61
  dragElement.classList.add('zoom-fade-out');
62
62
  dropElement.classList.add('zoom-fade-out');
63
- setTimeout(() => {
63
+ const cleanup = () => {
64
64
  div.remove();
65
65
  container.style.pointerEvents = 'auto';
66
66
  if (isObjectiveComplete) {
@@ -68,6 +68,13 @@ export function dragDropAnimation(container, dragElement, dropElement) {
68
68
  triggerNextContainer();
69
69
  container.removeAttribute(AnimationVanishedValuesKey);
70
70
  }
71
- }, 800); // match animation duration
71
+ };
72
+ const templateId = (container.getAttribute('template-id') || '').toLowerCase();
73
+ if (templateId === 'letterpairing' || templateId === 'numberpair') {
74
+ cleanup();
75
+ }
76
+ else {
77
+ setTimeout(cleanup, 800); // match animation duration
78
+ }
72
79
  }, 250);
73
80
  }
@@ -65,6 +65,14 @@ export function addClickListenerForClickType(element) {
65
65
  const container = document.getElementById(LidoContainer);
66
66
  if (container.getAttribute('canplay') === 'false')
67
67
  return;
68
+ const isBubbleTypeKeyboardKey = (container === null || container === void 0 ? void 0 : container.getAttribute('template-id')) === 'bubbleType' &&
69
+ element.closest('lido-keyboard') !== null;
70
+ // BubbleType manages key clicks and audio in lido-keyboard.tsx directly.
71
+ // Letting the generic click handler attach here creates a competing stop/play
72
+ // path that can interrupt the first bubble audio.
73
+ if (isBubbleTypeKeyboardKey) {
74
+ return;
75
+ }
68
76
  element.style.cursor = 'pointer';
69
77
  if (!element) {
70
78
  console.error('No element provided.');
@@ -5,6 +5,7 @@ import { DropLength, SelectedValuesKey, DropMode, DropToAttr, DropTimeAttr, Lido
5
5
  import { dispatchElementDropEvent } from "../customEvents";
6
6
  import { highlightElement, removeHighlight } from "./highlightHandler";
7
7
  import { dragDropAnimation } from "./animationHandler";
8
+ const isBlenderTemplate = (container) => (container === null || container === void 0 ? void 0 : container.getAttribute('template-id')) === 'blender';
8
9
  export function buildDropHasDragFromDOM() {
9
10
  const dropHasDrag = {};
10
11
  const dropElements = document.querySelectorAll("[type='drop']");
@@ -63,7 +64,6 @@ export function enableDraggingWithScaling(element) {
63
64
  let mutationFlag = false;
64
65
  // Fetch the container element
65
66
  const container = document.getElementById(LidoContainer);
66
- const templateId = container.getAttribute("template-id");
67
67
  if (!container) {
68
68
  console.error(`Container with ID "container" not found.`);
69
69
  return;
@@ -230,7 +230,7 @@ export function enableDraggingWithScaling(element) {
230
230
  allElements.forEach(otherElement => {
231
231
  var _a;
232
232
  if (storedTabIndexes.includes(JSON.parse(otherElement.getAttribute('tab-index')))) {
233
- if (!(((_a = element.getAttribute('dropAttr')) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === DropMode.Diagonal) && container.getAttribute("template-id") !== "blender") {
233
+ if (!(((_a = element.getAttribute('dropAttr')) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === DropMode.Diagonal) && !isBlenderTemplate(container)) {
234
234
  if (otherElement) {
235
235
  setOpacityIfChanged(otherElement, "0.3");
236
236
  }
@@ -318,7 +318,7 @@ export function enableDraggingWithScaling(element) {
318
318
  else {
319
319
  onElementDropComplete(element, mostOverlappedElement);
320
320
  }
321
- if (templateId === "blender" && element && mostOverlappedElement) {
321
+ if (isBlenderTemplate(container) && element && mostOverlappedElement) {
322
322
  const allElements = document.querySelectorAll(`*`);
323
323
  allElements.forEach(el => {
324
324
  removeHighlight(el);
@@ -546,7 +546,7 @@ export async function handleResetDragElement(dragElement, dropElement, dropHasDr
546
546
  highlightElement();
547
547
  }
548
548
  export async function onElementDropComplete(dragElement, dropElement) {
549
- var _a, _b, _c, _d, _e, _f, _g, _h;
549
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
550
550
  const container = document.getElementById(LidoContainer);
551
551
  const selectedValueData = (_a = container.getAttribute(SelectedValuesKey)) !== null && _a !== void 0 ? _a : "[]";
552
552
  const dragSelectedData = JSON.stringify(buildDragSelectedMapFromDOM());
@@ -769,28 +769,48 @@ export async function onElementDropComplete(dragElement, dropElement) {
769
769
  // Add pulse and highlight effect for a successful match
770
770
  const isCorrect = dropElement['value'].toLowerCase().includes(dragElement['value'].toLowerCase());
771
771
  dispatchElementDropEvent(dragElement, dropElement, isCorrect);
772
- if (container.getAttribute("template-id") !== "blender") {
772
+ if (!isBlenderTemplate(container)) {
773
773
  storingEachActivityScore(isCorrect);
774
774
  }
775
775
  dragElement.style.opacity = '1';
776
776
  const allDropElements = document.querySelectorAll('.drop-element');
777
777
  allDropElements.forEach(el => updateDropBorder(el));
778
+ if (isBlenderTemplate(container)) {
779
+ const requiredDrops = Number((_j = dropElement.getAttribute('required-drops')) !== null && _j !== void 0 ? _j : 0);
780
+ const placedDrops = document.querySelectorAll(`[${DropToAttr}="${dropElement.id}"]`).length;
781
+ const action = placedDrops > 0 && placedDrops <= requiredDrops
782
+ ? dropElement.getAttribute('onCorrect')
783
+ : dropElement.getAttribute('onInCorrect');
784
+ if (action) {
785
+ await executeActions(action, dropElement, dragElement);
786
+ }
787
+ }
778
788
  highlightElement();
779
789
  await onActivityComplete(dragElement, dropElement);
780
790
  }
781
791
  export function updateDropBorder(element) {
792
+ var _a;
782
793
  if (!element || !element.classList.contains('drop-element'))
783
794
  return;
784
795
  const container = document.getElementById(LidoContainer);
785
796
  if (!container)
786
797
  return;
787
798
  const showBorder = container.getAttribute('show-drop-border');
788
- if (showBorder !== 'true') {
799
+ if (showBorder !== 'true' && !isBlenderTemplate(container)) {
789
800
  return;
790
801
  }
791
802
  const dropId = element.id;
792
803
  const dragSelectedElements = document.querySelectorAll(`[${DropToAttr}="${dropId}"]`);
793
- if (dragSelectedElements.length > 0 && container.getAttribute("template-id") !== "blender") {
804
+ if (isBlenderTemplate(container)) {
805
+ const requiredDrops = Number((_a = element.getAttribute('required-drops')) !== null && _a !== void 0 ? _a : 0);
806
+ const placedDrops = dragSelectedElements.length;
807
+ const showCorrectBorder = placedDrops > 0 && placedDrops <= requiredDrops;
808
+ element.classList.toggle('empty', !showCorrectBorder);
809
+ element.classList.remove('filled');
810
+ element.style.setProperty('border', showCorrectBorder ? '5px dashed green' : '5px dashed #f34d08', 'important');
811
+ return;
812
+ }
813
+ if (dragSelectedElements.length > 0 && !isBlenderTemplate(container)) {
794
814
  element.classList.add('filled');
795
815
  element.classList.remove('empty');
796
816
  element.classList.remove('highlight-element');
@@ -880,7 +900,7 @@ export async function onClickDropOrDragElement(element, type) {
880
900
  // Move the drag element to the drop position
881
901
  selectedDragElement.style.transform = `translate(${translateX}px, ${translateY}px)`;
882
902
  // Remove highlights after moving the element
883
- if (container.getAttribute("template-id") !== "blender") {
903
+ if (!isBlenderTemplate(container)) {
884
904
  const allElements = document.querySelectorAll(`*`);
885
905
  allElements.forEach(el => {
886
906
  removeHighlight(el);
@@ -4,27 +4,24 @@ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
4
4
  /* ------------------ animation ------------------ */
5
5
  const animationToRemoveEquationSolvedCellForNimbleTable = (activeCell, container) => {
6
6
  return new Promise((resolve) => {
7
- const keyframes = `
8
- @keyframes widthDecrease {
9
- 0% {
10
- background-color: ${activeCell.style.backgroundColor};
11
- height: ${activeCell.style.height};
12
- margin: ${activeCell.style.margin};
13
- }
14
- 100% { background-color: transparent; height: 0px; margin: 0px; }
15
- }`;
16
- const styleSheet = document.styleSheets[0];
17
- styleSheet.insertRule(keyframes, styleSheet.cssRules.length);
18
- activeCell.style.animation = `widthDecrease 0s`;
19
- activeCell.addEventListener("animationend", () => {
20
- activeCell.remove();
7
+ let removed = false;
8
+ const removeActiveCell = () => {
9
+ if (removed)
10
+ return;
11
+ removed = true;
12
+ if (activeCell.isConnected) {
13
+ activeCell.remove();
14
+ }
21
15
  // Trigger next container only after the last calculate cell is removed
22
16
  const remainingCalculateTypes = container.querySelectorAll("[type='calculate']");
23
17
  if (remainingCalculateTypes.length === 0) {
24
18
  setTimeout(() => triggerNextContainer(), 2000);
25
19
  }
26
20
  resolve();
27
- }, { once: true });
21
+ };
22
+ // Nimble uses a zero-duration removal; remove synchronously so progression
23
+ // does not depend on a browser animationend event.
24
+ removeActiveCell();
28
25
  });
29
26
  };
30
27
  /* ------------------ PRACTICE MODE ------------------ */