@intellectif/lk-react 14.0.0 → 15.0.0

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 (139) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +5 -2
  3. package/dist/{WrittenResponse-pB1ik8hg.d.cts → WrittenResponse-BPbhERgk.d.cts} +1 -1
  4. package/dist/{WrittenResponse-C0P34En1.d.ts → WrittenResponse-ZAxLR_AU.d.ts} +1 -1
  5. package/dist/{chunk-YEYKJFEC.cjs → chunk-26GQO5I2.cjs} +12 -12
  6. package/dist/{chunk-YEYKJFEC.cjs.map → chunk-26GQO5I2.cjs.map} +1 -1
  7. package/dist/chunk-2HAEQDHQ.js +2330 -0
  8. package/dist/chunk-2HAEQDHQ.js.map +1 -0
  9. package/dist/{chunk-47HHC4GD.js → chunk-33BQNWAQ.js} +5 -5
  10. package/dist/{chunk-47HHC4GD.js.map → chunk-33BQNWAQ.js.map} +1 -1
  11. package/dist/{chunk-SE6WWDVW.cjs → chunk-4H3JTX74.cjs} +13 -13
  12. package/dist/{chunk-SE6WWDVW.cjs.map → chunk-4H3JTX74.cjs.map} +1 -1
  13. package/dist/{chunk-TDJEKXEP.js → chunk-52GXJNQ3.js} +84 -2
  14. package/dist/chunk-52GXJNQ3.js.map +1 -0
  15. package/dist/{chunk-USBANZ7K.cjs → chunk-53YOZOGN.cjs} +5 -5
  16. package/dist/{chunk-USBANZ7K.cjs.map → chunk-53YOZOGN.cjs.map} +1 -1
  17. package/dist/{chunk-LLXCX5KK.js → chunk-AF72QUBI.js} +5 -5
  18. package/dist/{chunk-LLXCX5KK.js.map → chunk-AF72QUBI.js.map} +1 -1
  19. package/dist/{chunk-XUEIUEPI.js → chunk-CE6OGJE6.js} +5 -5
  20. package/dist/{chunk-XUEIUEPI.js.map → chunk-CE6OGJE6.js.map} +1 -1
  21. package/dist/{chunk-RRH7ZPXC.cjs → chunk-DP3DM7FR.cjs} +6 -6
  22. package/dist/{chunk-RRH7ZPXC.cjs.map → chunk-DP3DM7FR.cjs.map} +1 -1
  23. package/dist/{chunk-J2CDPIRC.cjs → chunk-DQ3VVU5T.cjs} +27 -25
  24. package/dist/chunk-DQ3VVU5T.cjs.map +1 -0
  25. package/dist/{chunk-U3APKQ7P.cjs → chunk-EKBEKDWO.cjs} +13 -13
  26. package/dist/{chunk-U3APKQ7P.cjs.map → chunk-EKBEKDWO.cjs.map} +1 -1
  27. package/dist/{chunk-RXS455MR.js → chunk-EVBYEWHO.js} +3 -2
  28. package/dist/chunk-EVBYEWHO.js.map +1 -0
  29. package/dist/{chunk-B2XQLU2E.cjs → chunk-FKGUKFC7.cjs} +24 -6
  30. package/dist/chunk-FKGUKFC7.cjs.map +1 -0
  31. package/dist/{chunk-YMBBM5K7.js → chunk-H6I2JDNW.js} +10 -10
  32. package/dist/{chunk-YMBBM5K7.js.map → chunk-H6I2JDNW.js.map} +1 -1
  33. package/dist/{chunk-XIBOFTOL.js → chunk-HA3VRF6E.js} +22 -4
  34. package/dist/chunk-HA3VRF6E.js.map +1 -0
  35. package/dist/{chunk-I33FOKZ7.js → chunk-I2QGW4GD.js} +14 -12
  36. package/dist/chunk-I2QGW4GD.js.map +1 -0
  37. package/dist/{chunk-72UGRIMM.js → chunk-IE5ZNCWN.js} +5 -5
  38. package/dist/{chunk-72UGRIMM.js.map → chunk-IE5ZNCWN.js.map} +1 -1
  39. package/dist/{chunk-NDQD3ZRY.cjs → chunk-L7NE7PR4.cjs} +13 -13
  40. package/dist/{chunk-NDQD3ZRY.cjs.map → chunk-L7NE7PR4.cjs.map} +1 -1
  41. package/dist/{chunk-D57QZILZ.js → chunk-QEB6O6GU.js} +62 -37
  42. package/dist/chunk-QEB6O6GU.js.map +1 -0
  43. package/dist/{chunk-62AKSD5Y.js → chunk-QM4AV6EG.js} +39 -11
  44. package/dist/chunk-QM4AV6EG.js.map +1 -0
  45. package/dist/{chunk-LGCUOLP6.js → chunk-RT5FTHUU.js} +5 -5
  46. package/dist/{chunk-LGCUOLP6.js.map → chunk-RT5FTHUU.js.map} +1 -1
  47. package/dist/chunk-SRD4KVAP.cjs +2332 -0
  48. package/dist/chunk-SRD4KVAP.cjs.map +1 -0
  49. package/dist/{chunk-BVRUUYYR.cjs → chunk-UAYWLYFW.cjs} +18 -18
  50. package/dist/{chunk-BVRUUYYR.cjs.map → chunk-UAYWLYFW.cjs.map} +1 -1
  51. package/dist/{chunk-D4DQFTA7.cjs → chunk-UCKA22ZJ.cjs} +3 -2
  52. package/dist/chunk-UCKA22ZJ.cjs.map +1 -0
  53. package/dist/{chunk-F4TBCRGI.cjs → chunk-WKOV25NP.cjs} +42 -13
  54. package/dist/chunk-WKOV25NP.cjs.map +1 -0
  55. package/dist/{chunk-WTS34S5E.cjs → chunk-XMJE34FY.cjs} +13 -13
  56. package/dist/{chunk-WTS34S5E.cjs.map → chunk-XMJE34FY.cjs.map} +1 -1
  57. package/dist/{chunk-SWY6PWXK.js → chunk-YWQH35VO.js} +4 -4
  58. package/dist/{chunk-SWY6PWXK.js.map → chunk-YWQH35VO.js.map} +1 -1
  59. package/dist/{chunk-LB3FEV7Z.cjs → chunk-Z6TVOAO5.cjs} +84 -2
  60. package/dist/chunk-Z6TVOAO5.cjs.map +1 -0
  61. package/dist/{chunk-36FTYJWJ.cjs → chunk-ZH5WT2IE.cjs} +80 -55
  62. package/dist/chunk-ZH5WT2IE.cjs.map +1 -0
  63. package/dist/{chunk-BO6P723E.js → chunk-ZJMTHPHY.js} +3 -3
  64. package/dist/{chunk-BO6P723E.js.map → chunk-ZJMTHPHY.js.map} +1 -1
  65. package/dist/components/ActivityPreview.cjs +12 -12
  66. package/dist/components/ActivityPreview.d.cts +2 -2
  67. package/dist/components/ActivityPreview.d.ts +2 -2
  68. package/dist/components/ActivityPreview.js +11 -11
  69. package/dist/components/ActivitySequence.cjs +13 -13
  70. package/dist/components/ActivitySequence.d.cts +14 -7
  71. package/dist/components/ActivitySequence.d.ts +14 -7
  72. package/dist/components/ActivitySequence.js +12 -12
  73. package/dist/components/Dictation.cjs +5 -5
  74. package/dist/components/Dictation.d.cts +1 -1
  75. package/dist/components/Dictation.d.ts +1 -1
  76. package/dist/components/Dictation.js +4 -4
  77. package/dist/components/FillInTheBlanks.cjs +5 -5
  78. package/dist/components/FillInTheBlanks.d.cts +1 -1
  79. package/dist/components/FillInTheBlanks.d.ts +1 -1
  80. package/dist/components/FillInTheBlanks.js +4 -4
  81. package/dist/components/GapSelect.cjs +5 -5
  82. package/dist/components/GapSelect.d.cts +1 -1
  83. package/dist/components/GapSelect.d.ts +1 -1
  84. package/dist/components/GapSelect.js +4 -4
  85. package/dist/components/InteractiveVideo.cjs +26 -0
  86. package/dist/components/InteractiveVideo.cjs.map +1 -0
  87. package/dist/components/InteractiveVideo.d.cts +77 -0
  88. package/dist/components/InteractiveVideo.d.ts +77 -0
  89. package/dist/components/InteractiveVideo.js +17 -0
  90. package/dist/components/InteractiveVideo.js.map +1 -0
  91. package/dist/components/MultipleChoice.cjs +5 -5
  92. package/dist/components/MultipleChoice.d.cts +1 -1
  93. package/dist/components/MultipleChoice.d.ts +1 -1
  94. package/dist/components/MultipleChoice.js +4 -4
  95. package/dist/components/PronunciationFeedback.cjs +4 -4
  96. package/dist/components/PronunciationFeedback.d.cts +5 -2
  97. package/dist/components/PronunciationFeedback.d.ts +5 -2
  98. package/dist/components/PronunciationFeedback.js +3 -3
  99. package/dist/components/ReadAloud.cjs +6 -6
  100. package/dist/components/ReadAloud.d.cts +2 -2
  101. package/dist/components/ReadAloud.d.ts +2 -2
  102. package/dist/components/ReadAloud.js +5 -5
  103. package/dist/components/StimulusPanel.cjs +4 -4
  104. package/dist/components/StimulusPanel.d.cts +1 -1
  105. package/dist/components/StimulusPanel.d.ts +1 -1
  106. package/dist/components/StimulusPanel.js +3 -3
  107. package/dist/components/WrittenResponse.cjs +5 -5
  108. package/dist/components/WrittenResponse.d.cts +3 -3
  109. package/dist/components/WrittenResponse.d.ts +3 -3
  110. package/dist/components/WrittenResponse.js +4 -4
  111. package/dist/i18n/LkIntlProvider.cjs +7 -7
  112. package/dist/i18n/LkIntlProvider.d.cts +2 -2
  113. package/dist/i18n/LkIntlProvider.d.ts +2 -2
  114. package/dist/i18n/LkIntlProvider.js +1 -1
  115. package/dist/index.cjs +44 -39
  116. package/dist/index.cjs.map +1 -1
  117. package/dist/index.d.cts +3 -2
  118. package/dist/index.d.ts +3 -2
  119. package/dist/index.js +15 -14
  120. package/dist/index.js.map +1 -1
  121. package/dist/{strings--ADnHcaA.d.cts → strings-5588Q4Te.d.cts} +118 -1
  122. package/dist/{strings-CtdSf2St.d.ts → strings-D0IWcsUm.d.ts} +118 -1
  123. package/dist/theme/ThemeProvider.cjs +6 -6
  124. package/dist/theme/ThemeProvider.js +1 -1
  125. package/dist/theme/defaults.css +2 -0
  126. package/dist/theme/skin.css +1365 -1
  127. package/package.json +13 -3
  128. package/dist/chunk-36FTYJWJ.cjs.map +0 -1
  129. package/dist/chunk-62AKSD5Y.js.map +0 -1
  130. package/dist/chunk-B2XQLU2E.cjs.map +0 -1
  131. package/dist/chunk-D4DQFTA7.cjs.map +0 -1
  132. package/dist/chunk-D57QZILZ.js.map +0 -1
  133. package/dist/chunk-F4TBCRGI.cjs.map +0 -1
  134. package/dist/chunk-I33FOKZ7.js.map +0 -1
  135. package/dist/chunk-J2CDPIRC.cjs.map +0 -1
  136. package/dist/chunk-LB3FEV7Z.cjs.map +0 -1
  137. package/dist/chunk-RXS455MR.js.map +0 -1
  138. package/dist/chunk-TDJEKXEP.js.map +0 -1
  139. package/dist/chunk-XIBOFTOL.js.map +0 -1
@@ -0,0 +1,2332 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ var chunkL7NE7PR4_cjs = require('./chunk-L7NE7PR4.cjs');
5
+ var chunk4H3JTX74_cjs = require('./chunk-4H3JTX74.cjs');
6
+ var chunkEKBEKDWO_cjs = require('./chunk-EKBEKDWO.cjs');
7
+ var chunkXMJE34FY_cjs = require('./chunk-XMJE34FY.cjs');
8
+ var chunkZH5WT2IE_cjs = require('./chunk-ZH5WT2IE.cjs');
9
+ var chunk53YOZOGN_cjs = require('./chunk-53YOZOGN.cjs');
10
+ var chunkZ6TVOAO5_cjs = require('./chunk-Z6TVOAO5.cjs');
11
+ var lkCore = require('@intellectif/lk-core');
12
+ var react = require('react');
13
+ var jsxRuntime = require('react/jsx-runtime');
14
+
15
+ // src/components/InteractiveVideo/format.ts
16
+ var SPEEDS = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5];
17
+ function clock(value) {
18
+ const total = Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
19
+ const hours = Math.floor(total / 3600);
20
+ const minutes = Math.floor(total % 3600 / 60);
21
+ const secs = total % 60;
22
+ const pad = (n) => String(n).padStart(2, "0");
23
+ return hours > 0 ? `${hours}:${pad(minutes)}:${pad(secs)}` : `${minutes}:${pad(secs)}`;
24
+ }
25
+ function nudgeSpeed(current, direction) {
26
+ const at = SPEEDS.indexOf(current);
27
+ const from = at === -1 ? SPEEDS.indexOf(1) : at;
28
+ return SPEEDS[Math.min(SPEEDS.length - 1, Math.max(0, from + direction))];
29
+ }
30
+ function Icon({ children, filled = false }) {
31
+ return /* @__PURE__ */ jsxRuntime.jsx(
32
+ "svg",
33
+ {
34
+ className: "lk-iv-icon",
35
+ viewBox: "0 0 24 24",
36
+ width: "20",
37
+ height: "20",
38
+ fill: filled ? "currentColor" : "none",
39
+ stroke: "currentColor",
40
+ strokeWidth: "1.8",
41
+ strokeLinecap: "round",
42
+ strokeLinejoin: "round",
43
+ "aria-hidden": "true",
44
+ focusable: "false",
45
+ children
46
+ }
47
+ );
48
+ }
49
+ var PlayIcon = () => /* @__PURE__ */ jsxRuntime.jsx(Icon, { filled: true, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 5.5v13a1 1 0 0 0 1.5.86l10.2-6.5a1 1 0 0 0 0-1.72L9.5 4.64A1 1 0 0 0 8 5.5Z" }) });
50
+ var PauseIcon = () => /* @__PURE__ */ jsxRuntime.jsxs(Icon, { filled: true, children: [
51
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "6.5", y: "5", width: "3.5", height: "14", rx: "1" }),
52
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "14", y: "5", width: "3.5", height: "14", rx: "1" })
53
+ ] });
54
+ var ReplayIcon = () => /* @__PURE__ */ jsxRuntime.jsxs(Icon, { children: [
55
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 12a8 8 0 1 0 2.4-5.7" }),
56
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 4v4h4" })
57
+ ] });
58
+ var BackIcon = () => /* @__PURE__ */ jsxRuntime.jsxs(Icon, { children: [
59
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M11 6.5 5.5 12l5.5 5.5" }),
60
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18.5 6.5 13 12l5.5 5.5" })
61
+ ] });
62
+ var ForwardIcon = () => /* @__PURE__ */ jsxRuntime.jsxs(Icon, { children: [
63
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m13 6.5 5.5 5.5-5.5 5.5" }),
64
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5.5 6.5 11 12l-5.5 5.5" })
65
+ ] });
66
+ var VolumeIcon = ({ level }) => /* @__PURE__ */ jsxRuntime.jsxs(Icon, { children: [
67
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 9.5v5h3.5L12 18.5v-13L7.5 9.5H4Z" }),
68
+ level === "muted" ? /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m16 9.5 5 5m0-5-5 5" }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
69
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M15.5 9.5a3.5 3.5 0 0 1 0 5" }),
70
+ level === "high" ? /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 7a7 7 0 0 1 0 10" }) : null
71
+ ] })
72
+ ] });
73
+ var CaptionsIcon = ({ on }) => /* @__PURE__ */ jsxRuntime.jsxs(Icon, { children: [
74
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3", y: "5.5", width: "18", height: "13", rx: "2.5" }),
75
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10 10.5a2 2 0 1 0 0 3M16 10.5a2 2 0 1 0 0 3" }),
76
+ on ? null : /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m4 20 16-16" })
77
+ ] });
78
+ var SettingsIcon = () => /* @__PURE__ */ jsxRuntime.jsxs(Icon, { children: [
79
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "3" }),
80
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 3.5v2M12 18.5v2M20.5 12h-2M5.5 12h-2M18 6l-1.4 1.4M7.4 16.6 6 18M18 18l-1.4-1.4M7.4 7.4 6 6" })
81
+ ] });
82
+ var ContentsIcon = () => /* @__PURE__ */ jsxRuntime.jsxs(Icon, { children: [
83
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 6.5h11M9 12h11M9 17.5h11" }),
84
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4.5 6.5h.01M4.5 12h.01M4.5 17.5h.01" })
85
+ ] });
86
+ var PictureInPictureIcon = () => /* @__PURE__ */ jsxRuntime.jsxs(Icon, { children: [
87
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3", y: "5", width: "18", height: "14", rx: "2" }),
88
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "12", y: "11.5", width: "6.5", height: "5", rx: "1" })
89
+ ] });
90
+ var FullscreenIcon = ({ on }) => /* @__PURE__ */ jsxRuntime.jsx(Icon, { children: on ? /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 4v3a2 2 0 0 1-2 2H4M20 9h-3a2 2 0 0 1-2-2V4M4 15h3a2 2 0 0 1 2 2v3M15 20v-3a2 2 0 0 1 2-2h3" }) : /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 9V6a2 2 0 0 1 2-2h3M15 4h3a2 2 0 0 1 2 2v3M20 15v3a2 2 0 0 1-2 2h-3M9 20H6a2 2 0 0 1-2-2v-3" }) });
91
+ var CheckIcon = () => /* @__PURE__ */ jsxRuntime.jsx(Icon, { children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m5 12.5 4.5 4.5L19 7.5" }) });
92
+ var ChevronIcon = ({ direction }) => /* @__PURE__ */ jsxRuntime.jsx(Icon, { children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: direction === "right" ? "m9.5 6 6 6-6 6" : "m14.5 6-6 6 6 6" }) });
93
+ var CloseIcon = () => /* @__PURE__ */ jsxRuntime.jsx(Icon, { children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6.5 6.5 11 11m0-11-11 11" }) });
94
+ var AlertIcon = () => /* @__PURE__ */ jsxRuntime.jsxs(Icon, { children: [
95
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 4 2.8 19.5h18.4L12 4Z" }),
96
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 10v4M12 17h.01" })
97
+ ] });
98
+
99
+ // src/components/InteractiveVideo/shortcuts.ts
100
+ var SHORTCUT_KEYS = [
101
+ { action: "play-pause", keys: "Space / K" },
102
+ { action: "jump-10", keys: "J / L" },
103
+ { action: "jump-5", keys: "\u2190 / \u2192" },
104
+ { action: "jump-1", keys: "Shift + \u2190 / \u2192" },
105
+ { action: "frame", keys: ", / ." },
106
+ { action: "speed", keys: "< / >" },
107
+ { action: "percent", keys: "0 \u2013 9" },
108
+ { action: "start-end", keys: "Home / End" },
109
+ { action: "chapter", keys: "[ / ]" },
110
+ { action: "mute", keys: "M" },
111
+ { action: "captions", keys: "C" },
112
+ { action: "transcript", keys: "T" },
113
+ { action: "picture-in-picture", keys: "P" },
114
+ { action: "fullscreen", keys: "F" },
115
+ { action: "shortcut-list", keys: "?" }
116
+ ];
117
+ var JUMP_SECONDS = 10;
118
+ var FRAME_SECONDS = 1 / 30;
119
+ function Menu({
120
+ label,
121
+ onClose,
122
+ children
123
+ }) {
124
+ const ref = react.useRef(null);
125
+ react.useEffect(() => {
126
+ const items = ref.current?.querySelectorAll('[role^="menuitem"]');
127
+ const checked = ref.current?.querySelector('[aria-checked="true"]');
128
+ (checked ?? items?.[0])?.focus();
129
+ }, []);
130
+ const onKeyDown = (event) => {
131
+ const items = Array.from(
132
+ ref.current?.querySelectorAll('[role^="menuitem"]') ?? []
133
+ );
134
+ const at = items.indexOf(document.activeElement);
135
+ const move = (to) => {
136
+ event.preventDefault();
137
+ items[(to + items.length) % items.length]?.focus();
138
+ };
139
+ if (event.key === "ArrowDown") {
140
+ move(at + 1);
141
+ } else if (event.key === "ArrowUp") {
142
+ move(at - 1);
143
+ } else if (event.key === "Home") {
144
+ move(0);
145
+ } else if (event.key === "End") {
146
+ move(items.length - 1);
147
+ } else if (event.key === "Escape") {
148
+ event.preventDefault();
149
+ event.stopPropagation();
150
+ onClose();
151
+ }
152
+ event.stopPropagation();
153
+ };
154
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: "lk-iv-menu", role: "menu", "aria-label": label, onKeyDown, children });
155
+ }
156
+ function SpeedMenu({
157
+ speed,
158
+ strings,
159
+ onChoose,
160
+ onClose
161
+ }) {
162
+ return /* @__PURE__ */ jsxRuntime.jsx(Menu, { label: strings.media.speed, onClose, children: SPEEDS.map((rate) => /* @__PURE__ */ jsxRuntime.jsxs(
163
+ "button",
164
+ {
165
+ type: "button",
166
+ role: "menuitemradio",
167
+ "aria-checked": rate === speed,
168
+ className: "lk-iv-menu-item",
169
+ onClick: () => onChoose(rate),
170
+ children: [
171
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: strings.videoSpeedValue(rate) }),
172
+ rate === speed ? /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, {}) : null
173
+ ]
174
+ },
175
+ rate
176
+ )) });
177
+ }
178
+ function SettingsMenu({
179
+ preferences,
180
+ tracks,
181
+ activeTrack,
182
+ captionsFailed,
183
+ strings,
184
+ onChange,
185
+ onShowShortcuts,
186
+ onClose
187
+ }) {
188
+ const [page, setPage] = react.useState("main");
189
+ const captionsValue = !preferences.captions || activeTrack === void 0 ? strings.videoOff : activeTrack.label;
190
+ const back = /* @__PURE__ */ jsxRuntime.jsxs(
191
+ "button",
192
+ {
193
+ type: "button",
194
+ role: "menuitem",
195
+ className: "lk-iv-menu-item lk-iv-menu-back",
196
+ onClick: () => setPage("main"),
197
+ children: [
198
+ /* @__PURE__ */ jsxRuntime.jsx(ChevronIcon, { direction: "left" }),
199
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: page === "captions" ? strings.videoCaptionLanguage : strings.videoCaptionSize })
200
+ ]
201
+ }
202
+ );
203
+ if (page === "captions") {
204
+ return /* @__PURE__ */ jsxRuntime.jsxs(Menu, { label: strings.videoCaptionLanguage, onClose, children: [
205
+ back,
206
+ /* @__PURE__ */ jsxRuntime.jsxs(
207
+ "button",
208
+ {
209
+ type: "button",
210
+ role: "menuitemradio",
211
+ "aria-checked": !preferences.captions,
212
+ className: "lk-iv-menu-item",
213
+ onClick: () => onChange({ captions: false }),
214
+ children: [
215
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: strings.videoOff }),
216
+ !preferences.captions ? /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, {}) : null
217
+ ]
218
+ }
219
+ ),
220
+ tracks.map((track) => {
221
+ const on = preferences.captions && activeTrack === track;
222
+ return /* @__PURE__ */ jsxRuntime.jsxs(
223
+ "button",
224
+ {
225
+ type: "button",
226
+ role: "menuitemradio",
227
+ "aria-checked": on,
228
+ className: "lk-iv-menu-item",
229
+ lang: track.srclang,
230
+ onClick: () => onChange({ captions: true, captionLanguage: track.srclang }),
231
+ children: [
232
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: track.label }),
233
+ on ? /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, {}) : null
234
+ ]
235
+ },
236
+ `${track.kind}:${track.srclang}`
237
+ );
238
+ }),
239
+ captionsFailed ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-iv-menu-note", role: "status", children: strings.videoCaptionsFailed }) : null
240
+ ] }, "captions");
241
+ }
242
+ if (page === "size") {
243
+ return /* @__PURE__ */ jsxRuntime.jsxs(Menu, { label: strings.videoCaptionSize, onClose, children: [
244
+ back,
245
+ ["small", "medium", "large"].map((size) => /* @__PURE__ */ jsxRuntime.jsxs(
246
+ "button",
247
+ {
248
+ type: "button",
249
+ role: "menuitemradio",
250
+ "aria-checked": preferences.captionSize === size,
251
+ className: "lk-iv-menu-item",
252
+ onClick: () => onChange({ captionSize: size }),
253
+ children: [
254
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: strings.videoCaptionSizeValue(size) }),
255
+ preferences.captionSize === size ? /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, {}) : null
256
+ ]
257
+ },
258
+ size
259
+ ))
260
+ ] }, "size");
261
+ }
262
+ return /* @__PURE__ */ jsxRuntime.jsxs(Menu, { label: strings.videoSettings, onClose, children: [
263
+ tracks.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
264
+ /* @__PURE__ */ jsxRuntime.jsxs(
265
+ "button",
266
+ {
267
+ type: "button",
268
+ role: "menuitem",
269
+ className: "lk-iv-menu-item",
270
+ onClick: () => setPage("captions"),
271
+ children: [
272
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: strings.videoCaptionLanguage }),
273
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "lk-iv-menu-value", children: [
274
+ captionsValue,
275
+ /* @__PURE__ */ jsxRuntime.jsx(ChevronIcon, { direction: "right" })
276
+ ] })
277
+ ]
278
+ }
279
+ ),
280
+ /* @__PURE__ */ jsxRuntime.jsxs(
281
+ "button",
282
+ {
283
+ type: "button",
284
+ role: "menuitem",
285
+ className: "lk-iv-menu-item",
286
+ onClick: () => setPage("size"),
287
+ children: [
288
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: strings.videoCaptionSize }),
289
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "lk-iv-menu-value", children: [
290
+ strings.videoCaptionSizeValue(preferences.captionSize),
291
+ /* @__PURE__ */ jsxRuntime.jsx(ChevronIcon, { direction: "right" })
292
+ ] })
293
+ ]
294
+ }
295
+ ),
296
+ /* @__PURE__ */ jsxRuntime.jsxs(
297
+ "button",
298
+ {
299
+ type: "button",
300
+ role: "menuitemcheckbox",
301
+ "aria-checked": preferences.captionBackground,
302
+ className: "lk-iv-menu-item",
303
+ onClick: () => onChange({ captionBackground: !preferences.captionBackground }),
304
+ children: [
305
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: strings.videoCaptionBackground }),
306
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-iv-menu-value", children: preferences.captionBackground ? strings.videoOn : strings.videoOff })
307
+ ]
308
+ }
309
+ )
310
+ ] }) : null,
311
+ /* @__PURE__ */ jsxRuntime.jsxs(
312
+ "button",
313
+ {
314
+ type: "button",
315
+ role: "menuitemcheckbox",
316
+ "aria-checked": preferences.shortcuts,
317
+ className: "lk-iv-menu-item",
318
+ onClick: () => onChange({ shortcuts: !preferences.shortcuts }),
319
+ children: [
320
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: strings.videoKeyboardShortcuts }),
321
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-iv-menu-value", children: preferences.shortcuts ? strings.videoOn : strings.videoOff })
322
+ ]
323
+ }
324
+ ),
325
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", role: "menuitem", className: "lk-iv-menu-item", onClick: onShowShortcuts, children: [
326
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: strings.videoShortcutList }),
327
+ /* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "lk-iv-kbd", children: "?" })
328
+ ] })
329
+ ] }, "main");
330
+ }
331
+ function ShortcutSheet({ strings, onClose }) {
332
+ const closeRef = react.useRef(null);
333
+ react.useEffect(() => {
334
+ closeRef.current?.focus();
335
+ }, []);
336
+ return /* @__PURE__ */ jsxRuntime.jsxs(
337
+ "div",
338
+ {
339
+ className: "lk-iv-sheet",
340
+ role: "dialog",
341
+ "aria-modal": "false",
342
+ "aria-label": strings.videoKeyboardShortcuts,
343
+ onKeyDown: (event) => {
344
+ if (event.key === "Escape" || event.key === "?") {
345
+ event.preventDefault();
346
+ event.stopPropagation();
347
+ onClose();
348
+ }
349
+ },
350
+ children: [
351
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-iv-sheet-head", children: [
352
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "lk-iv-sheet-title", children: strings.videoKeyboardShortcuts }),
353
+ /* @__PURE__ */ jsxRuntime.jsx(
354
+ "button",
355
+ {
356
+ ref: closeRef,
357
+ type: "button",
358
+ className: "lk-iv-button",
359
+ "aria-label": strings.videoClose,
360
+ onClick: onClose,
361
+ children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {})
362
+ }
363
+ )
364
+ ] }),
365
+ /* @__PURE__ */ jsxRuntime.jsx("dl", { className: "lk-iv-shortcuts", children: SHORTCUT_KEYS.map(({ action, keys }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-iv-shortcut", children: [
366
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { children: /* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "lk-iv-kbd", children: keys }) }),
367
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: strings.videoShortcut(action) })
368
+ ] }, action)) })
369
+ ]
370
+ }
371
+ );
372
+ }
373
+ function ContentsPanel({
374
+ idPrefix,
375
+ tab,
376
+ onTab,
377
+ chapters,
378
+ quizzes,
379
+ cues,
380
+ activeCue,
381
+ limit,
382
+ strings,
383
+ onSeek,
384
+ onOpenQuiz
385
+ }) {
386
+ const [query, setQuery] = react.useState("");
387
+ const listRef = react.useRef(null);
388
+ const followUntil = react.useRef(0);
389
+ const hasTranscript = cues.length > 0;
390
+ const shownTab = hasTranscript ? tab : "contents";
391
+ const lines = react.useMemo(() => {
392
+ const trimmed = query.trim().toLowerCase();
393
+ return cues.map((cue, index) => ({ cue, index })).filter(({ cue }) => cue.start <= limit).filter(({ cue }) => trimmed === "" || cue.text.toLowerCase().includes(trimmed));
394
+ }, [cues, limit, query]);
395
+ react.useEffect(() => {
396
+ if (shownTab !== "transcript" || query.trim() !== "" || Date.now() < followUntil.current) {
397
+ return;
398
+ }
399
+ const active = listRef.current?.querySelector('[data-active="true"]');
400
+ if (active !== null && active !== void 0 && listRef.current !== null) {
401
+ const list = listRef.current;
402
+ const top = active.offsetTop - list.clientHeight / 3;
403
+ list.scrollTo?.({ top, behavior: "smooth" });
404
+ }
405
+ }, [activeCue, shownTab, query]);
406
+ const rows = [
407
+ ...chapters.map((chapter) => ({ kind: "chapter", at: chapter.at, chapter })),
408
+ ...quizzes.map((quiz) => ({ kind: "quiz", at: quiz.at, quiz }))
409
+ ].sort((a, b) => a.at - b.at || (a.kind === "chapter" ? -1 : 1));
410
+ const highlight = (text) => {
411
+ const trimmed = query.trim();
412
+ const at = trimmed === "" ? -1 : text.toLowerCase().indexOf(trimmed.toLowerCase());
413
+ if (at === -1) {
414
+ return text;
415
+ }
416
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
417
+ text.slice(0, at),
418
+ /* @__PURE__ */ jsxRuntime.jsx("mark", { children: text.slice(at, at + trimmed.length) }),
419
+ text.slice(at + trimmed.length)
420
+ ] });
421
+ };
422
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "lk-iv-panel", "aria-label": strings.videoPanel, children: [
423
+ hasTranscript ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lk-iv-tabs", role: "tablist", "aria-label": strings.videoPanel, children: ["contents", "transcript"].map((name) => /* @__PURE__ */ jsxRuntime.jsx(
424
+ "button",
425
+ {
426
+ type: "button",
427
+ role: "tab",
428
+ id: `${idPrefix}-tab-${name}`,
429
+ "aria-controls": `${idPrefix}-tabpanel`,
430
+ "aria-selected": shownTab === name,
431
+ tabIndex: shownTab === name ? 0 : -1,
432
+ className: "lk-iv-tab",
433
+ onClick: () => onTab(name),
434
+ onKeyDown: (event) => {
435
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
436
+ event.preventDefault();
437
+ onTab(name === "contents" ? "transcript" : "contents");
438
+ }
439
+ },
440
+ children: name === "contents" ? strings.videoContents : strings.videoTranscript
441
+ },
442
+ name
443
+ )) }) : null,
444
+ shownTab === "contents" ? /* @__PURE__ */ jsxRuntime.jsx(
445
+ "ol",
446
+ {
447
+ className: "lk-iv-contents",
448
+ ...hasTranscript ? {
449
+ role: "tabpanel",
450
+ id: `${idPrefix}-tabpanel`,
451
+ "aria-labelledby": `${idPrefix}-tab-contents`
452
+ } : { "aria-label": strings.videoContents },
453
+ children: rows.map(
454
+ (row) => row.kind === "chapter" ? /* @__PURE__ */ jsxRuntime.jsx("li", { className: "lk-iv-contents-chapter", children: /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "lk-iv-row", onClick: () => onSeek(row.chapter.at), children: [
455
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-iv-row-time", children: clock(row.chapter.at) }),
456
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-iv-row-title", children: row.chapter.title })
457
+ ] }) }, `c-${row.at}`) : /* @__PURE__ */ jsxRuntime.jsx(
458
+ "li",
459
+ {
460
+ className: "lk-iv-contents-quiz",
461
+ "data-state": row.quiz.state,
462
+ children: /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "lk-iv-row", onClick: () => onOpenQuiz(row.quiz.id), children: [
463
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-iv-row-time", children: clock(row.quiz.at) }),
464
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "lk-iv-row-title", children: [
465
+ /* @__PURE__ */ jsxRuntime.jsx(
466
+ "span",
467
+ {
468
+ className: "lk-iv-marker-dot",
469
+ "data-state": row.quiz.state,
470
+ "aria-hidden": "true"
471
+ }
472
+ ),
473
+ row.quiz.title,
474
+ row.quiz.required ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
475
+ " ",
476
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-iv-tag", children: strings.videoRequired })
477
+ ] }) : null
478
+ ] }),
479
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-iv-row-status", children: strings.videoQuizProgress(row.quiz.answered, row.quiz.total) })
480
+ ] })
481
+ },
482
+ `q-${row.quiz.id}`
483
+ )
484
+ )
485
+ }
486
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(
487
+ "div",
488
+ {
489
+ className: "lk-iv-transcript",
490
+ role: "tabpanel",
491
+ id: `${idPrefix}-tabpanel`,
492
+ "aria-labelledby": `${idPrefix}-tab-transcript`,
493
+ children: [
494
+ /* @__PURE__ */ jsxRuntime.jsx(
495
+ "input",
496
+ {
497
+ id: `${idPrefix}-search`,
498
+ className: "lk-iv-search",
499
+ type: "search",
500
+ value: query,
501
+ placeholder: strings.videoTranscriptSearch,
502
+ "aria-label": strings.videoTranscriptSearch,
503
+ onChange: (event) => setQuery(event.target.value)
504
+ }
505
+ ),
506
+ /* @__PURE__ */ jsxRuntime.jsx(
507
+ "div",
508
+ {
509
+ ref: listRef,
510
+ className: "lk-iv-lines",
511
+ onWheel: () => {
512
+ followUntil.current = Date.now() + 4e3;
513
+ },
514
+ onTouchMove: () => {
515
+ followUntil.current = Date.now() + 4e3;
516
+ },
517
+ children: lines.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-iv-empty", children: strings.videoTranscriptNoMatch }) : lines.map(({ cue, index }) => /* @__PURE__ */ jsxRuntime.jsxs(
518
+ "button",
519
+ {
520
+ type: "button",
521
+ className: "lk-iv-line",
522
+ "data-active": index === activeCue || void 0,
523
+ onClick: () => onSeek(cue.start),
524
+ children: [
525
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-iv-row-time", children: clock(cue.start) }),
526
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: highlight(cue.text) })
527
+ ]
528
+ },
529
+ `${cue.start}-${index}`
530
+ ))
531
+ }
532
+ )
533
+ ]
534
+ }
535
+ )
536
+ ] });
537
+ }
538
+ function EndScreen({
539
+ idPrefix,
540
+ renderMode,
541
+ quizzes,
542
+ answered,
543
+ total,
544
+ percent,
545
+ strings,
546
+ onOpenQuiz,
547
+ onWatchAgain,
548
+ onFinish
549
+ }) {
550
+ const headingRef = react.useRef(null);
551
+ react.useEffect(() => {
552
+ headingRef.current?.focus();
553
+ }, []);
554
+ return /* @__PURE__ */ jsxRuntime.jsx("section", { className: "lk-iv-end", "aria-labelledby": `${idPrefix}-end-title`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-iv-end-card", children: [
555
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { id: `${idPrefix}-end-title`, ref: headingRef, tabIndex: -1, className: "lk-iv-end-title", children: strings.videoEnded }),
556
+ total > 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-iv-end-summary", children: percent !== null && renderMode !== "exam" ? strings.videoEndScore(answered, total, percent) : strings.videoEndAnswered(answered, total) }) : null,
557
+ quizzes.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "lk-iv-end-list", children: quizzes.map((quiz) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs(
558
+ "button",
559
+ {
560
+ type: "button",
561
+ className: "lk-iv-row",
562
+ "data-state": quiz.state,
563
+ onClick: () => onOpenQuiz(quiz.id),
564
+ children: [
565
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-iv-row-time", children: clock(quiz.at) }),
566
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "lk-iv-row-title", children: [
567
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-iv-marker-dot", "data-state": quiz.state, "aria-hidden": "true" }),
568
+ quiz.title
569
+ ] }),
570
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-iv-row-status", children: strings.videoQuizProgress(quiz.answered, quiz.total) })
571
+ ]
572
+ }
573
+ ) }, quiz.id)) }) : null,
574
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-iv-end-actions", children: [
575
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "lk-iv-action", onClick: onWatchAgain, children: [
576
+ /* @__PURE__ */ jsxRuntime.jsx(ReplayIcon, {}),
577
+ strings.videoWatchAgain
578
+ ] }),
579
+ onFinish !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "lk-iv-action lk-iv-action-primary", onClick: onFinish, children: strings.videoFinish }) : null
580
+ ] })
581
+ ] }) });
582
+ }
583
+
584
+ // src/components/InteractiveVideo/prefs.ts
585
+ var DEFAULT_PREFERENCES = {
586
+ speed: 1,
587
+ volume: 1,
588
+ muted: false,
589
+ captions: true,
590
+ captionLanguage: null,
591
+ captionSize: "medium",
592
+ captionBackground: true,
593
+ panel: false,
594
+ shortcuts: true
595
+ };
596
+ var STORAGE_KEY = "lk.video.v1";
597
+ function sanitizePreferences(raw) {
598
+ if (typeof raw !== "object" || raw === null) {
599
+ return DEFAULT_PREFERENCES;
600
+ }
601
+ const value = raw;
602
+ const bool = (input, fallback) => typeof input === "boolean" ? input : fallback;
603
+ const speed = SPEEDS.includes(value.speed) ? value.speed : DEFAULT_PREFERENCES.speed;
604
+ const volume = typeof value.volume === "number" && Number.isFinite(value.volume) ? Math.min(1, Math.max(0, value.volume)) : DEFAULT_PREFERENCES.volume;
605
+ const size = value.captionSize === "small" || value.captionSize === "medium" || value.captionSize === "large" ? value.captionSize : DEFAULT_PREFERENCES.captionSize;
606
+ const language = typeof value.captionLanguage === "string" && /^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/.test(value.captionLanguage) ? value.captionLanguage : null;
607
+ return {
608
+ speed,
609
+ volume,
610
+ muted: bool(value.muted, DEFAULT_PREFERENCES.muted),
611
+ captions: bool(value.captions, DEFAULT_PREFERENCES.captions),
612
+ captionLanguage: language,
613
+ captionSize: size,
614
+ captionBackground: bool(value.captionBackground, DEFAULT_PREFERENCES.captionBackground),
615
+ panel: bool(value.panel, DEFAULT_PREFERENCES.panel),
616
+ shortcuts: bool(value.shortcuts, DEFAULT_PREFERENCES.shortcuts)
617
+ };
618
+ }
619
+ function readPreferences() {
620
+ try {
621
+ const stored = globalThis.localStorage?.getItem(STORAGE_KEY);
622
+ return stored ? sanitizePreferences(JSON.parse(stored)) : DEFAULT_PREFERENCES;
623
+ } catch {
624
+ return DEFAULT_PREFERENCES;
625
+ }
626
+ }
627
+ function writePreferences(preferences) {
628
+ try {
629
+ globalThis.localStorage?.setItem(STORAGE_KEY, JSON.stringify(preferences));
630
+ } catch {
631
+ }
632
+ }
633
+
634
+ // src/components/InteractiveVideo/quiz-engine.ts
635
+ function crossedQuiz(cues, previous, now, handled) {
636
+ let earliest;
637
+ for (const cue of cues) {
638
+ if (handled.has(cue.id) || !(previous < cue.at && cue.at <= now)) {
639
+ continue;
640
+ }
641
+ if (earliest === void 0 || cue.at < earliest.at) {
642
+ earliest = cue;
643
+ }
644
+ }
645
+ return earliest;
646
+ }
647
+ function quizzesAtEnd(cues, duration, handled) {
648
+ return cues.filter((cue) => !handled.has(cue.id) && cue.at >= duration - 0.05).sort((a, b) => a.at - b.at);
649
+ }
650
+ function limitSeek(timeline, from, to, furthest, finished) {
651
+ if (to <= from) {
652
+ return { at: to };
653
+ }
654
+ let at = to;
655
+ let hold;
656
+ if (timeline.navigation === "no-skip-ahead" && to > furthest) {
657
+ at = Math.max(from, furthest);
658
+ hold = { kind: "no-skip-ahead", furthest };
659
+ }
660
+ const blocking = timeline.cues.filter((cue) => cue.required === true && cue.at > from && cue.at <= to && !finished(cue.id)).sort((a, b) => a.at - b.at)[0];
661
+ if (blocking !== void 0 && blocking.at <= at) {
662
+ return { at: blocking.at, hold: { kind: "required", cue: blocking } };
663
+ }
664
+ return hold === void 0 ? { at } : { at, hold };
665
+ }
666
+ function limitResume(timeline, at, finished) {
667
+ const blocking = timeline.cues.filter((cue) => cue.required === true && cue.at < at && !finished(cue.id)).sort((a, b) => a.at - b.at)[0];
668
+ return blocking === void 0 ? at : Math.max(0, blocking.at - 0.25);
669
+ }
670
+
671
+ // src/components/InteractiveVideo/vtt.ts
672
+ var MAX_VTT_LENGTH = 2e6;
673
+ var MAX_CUES = 1e4;
674
+ var BYTE_ORDER_MARK = String.fromCharCode(65279);
675
+ var TIMESTAMP = /^(?:(\d+):)?([0-5]\d):([0-5]\d)\.(\d{3})$/;
676
+ function seconds(stamp) {
677
+ const match = TIMESTAMP.exec(stamp.trim());
678
+ if (match === null) {
679
+ return void 0;
680
+ }
681
+ const [, hours = "0", minutes = "0", secs = "0", millis = "0"] = match;
682
+ return Number(hours) * 3600 + Number(minutes) * 60 + Number(secs) + Number(millis) / 1e3;
683
+ }
684
+ var ENTITIES = {
685
+ amp: "&",
686
+ lt: "<",
687
+ gt: ">",
688
+ quot: '"',
689
+ apos: "'",
690
+ // Built from code points: an escape can be rewritten as the raw, invisible
691
+ // character itself, by a formatter or an editor.
692
+ nbsp: String.fromCharCode(160),
693
+ lrm: String.fromCharCode(8206),
694
+ rlm: String.fromCharCode(8207)
695
+ };
696
+ function plainText(payload) {
697
+ return payload.replace(/<[^>]*>/g, "").replace(/&(#x?[0-9a-f]+|[a-z]+);/gi, (whole, name) => {
698
+ if (name.startsWith("#")) {
699
+ const code = name[1] === "x" || name[1] === "X" ? Number.parseInt(name.slice(2), 16) : Number.parseInt(name.slice(1), 10);
700
+ return Number.isFinite(code) && code > 0 && code <= 1114111 ? String.fromCodePoint(code) : whole;
701
+ }
702
+ return ENTITIES[name.toLowerCase()] ?? whole;
703
+ }).replace(/\s*\n\s*/g, " ").trim();
704
+ }
705
+ function parseWebVtt(input) {
706
+ if (typeof input !== "string") {
707
+ return [];
708
+ }
709
+ const raw = input.slice(0, MAX_VTT_LENGTH);
710
+ const text = (raw.startsWith(BYTE_ORDER_MARK) ? raw.slice(1) : raw).replace(/\r\n?/g, "\n");
711
+ if (!/^WEBVTT(?:[ \t].*)?(?:\n|$)/.test(text)) {
712
+ return [];
713
+ }
714
+ const cues = [];
715
+ for (const block of text.split(/\n{2,}/)) {
716
+ if (cues.length >= MAX_CUES) {
717
+ break;
718
+ }
719
+ const lines = block.split("\n");
720
+ const timingIndex = lines.findIndex((line) => line.includes("-->"));
721
+ if (timingIndex === -1) {
722
+ continue;
723
+ }
724
+ const [from = "", rest = ""] = lines[timingIndex].split("-->");
725
+ const start = seconds(from);
726
+ const end = seconds(rest.trim().split(/\s+/)[0] ?? "");
727
+ if (start === void 0 || end === void 0 || end <= start) {
728
+ continue;
729
+ }
730
+ const payload = plainText(lines.slice(timingIndex + 1).join("\n"));
731
+ if (payload === "") {
732
+ continue;
733
+ }
734
+ cues.push({ start, end, text: payload });
735
+ }
736
+ return cues.map((cue, index) => ({ cue, index })).sort((a, b) => a.cue.start - b.cue.start || a.index - b.index).map(({ cue }) => cue);
737
+ }
738
+ function cueIndexAt(cues, time) {
739
+ let low = 0;
740
+ let high = cues.length - 1;
741
+ let found = -1;
742
+ while (low <= high) {
743
+ const middle = low + high >> 1;
744
+ if (cues[middle].start <= time) {
745
+ found = middle;
746
+ low = middle + 1;
747
+ } else {
748
+ high = middle - 1;
749
+ }
750
+ }
751
+ for (let index = found; index >= 0 && index > found - 8; index -= 1) {
752
+ const cue = cues[index];
753
+ if (time < cue.end) {
754
+ return index;
755
+ }
756
+ }
757
+ return -1;
758
+ }
759
+ var BUBBLE_HALF = 120;
760
+ var MARKER_REACH = 8;
761
+ function Scrubber({
762
+ current,
763
+ duration,
764
+ buffered,
765
+ chapters,
766
+ markers,
767
+ cues,
768
+ previewLimit,
769
+ strings,
770
+ onSeek,
771
+ onScrubChange
772
+ }) {
773
+ const trackRef = react.useRef(null);
774
+ const [hover, setHover] = react.useState(null);
775
+ const [dragging, setDragging] = react.useState(false);
776
+ const ready = duration > 0;
777
+ const percent = (value) => `${ready ? Math.min(100, Math.max(0, value / duration * 100)) : 0}%`;
778
+ const measure = react.useCallback(
779
+ (clientX) => {
780
+ const rect = trackRef.current?.getBoundingClientRect();
781
+ if (rect === void 0 || rect.width === 0 || !ready) {
782
+ return null;
783
+ }
784
+ const ratio = Math.min(1, Math.max(0, (clientX - rect.left) / rect.width));
785
+ return { x: ratio * rect.width, width: rect.width, time: ratio * duration };
786
+ },
787
+ [duration, ready]
788
+ );
789
+ const onPointerDown = (event) => {
790
+ const at = measure(event.clientX);
791
+ if (at === null) {
792
+ return;
793
+ }
794
+ event.currentTarget.setPointerCapture(event.pointerId);
795
+ setDragging(true);
796
+ onScrubChange(true);
797
+ setHover(at);
798
+ onSeek(at.time);
799
+ };
800
+ const onPointerMove = (event) => {
801
+ const at = measure(event.clientX);
802
+ if (at === null) {
803
+ return;
804
+ }
805
+ setHover(at);
806
+ if (dragging) {
807
+ onSeek(at.time);
808
+ }
809
+ };
810
+ const endDrag = (event) => {
811
+ if (!dragging) {
812
+ return;
813
+ }
814
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
815
+ event.currentTarget.releasePointerCapture(event.pointerId);
816
+ }
817
+ setDragging(false);
818
+ onScrubChange(false);
819
+ };
820
+ const onKeyDown = (event) => {
821
+ if (!ready) {
822
+ return;
823
+ }
824
+ const step = event.shiftKey ? 1 : 5;
825
+ const moves = {
826
+ ArrowLeft: -step,
827
+ ArrowRight: step,
828
+ ArrowDown: -step,
829
+ ArrowUp: step,
830
+ PageDown: -30,
831
+ PageUp: 30
832
+ };
833
+ const move = moves[event.key];
834
+ if (move !== void 0) {
835
+ event.preventDefault();
836
+ event.stopPropagation();
837
+ onSeek(current + move);
838
+ } else if (event.key === "Home" || event.key === "End") {
839
+ event.preventDefault();
840
+ event.stopPropagation();
841
+ onSeek(event.key === "Home" ? 0 : duration);
842
+ }
843
+ };
844
+ const chapterAt = (time) => [...chapters].reverse().find((chapter) => chapter.at <= time);
845
+ const hoverCue = hover !== null && hover.time <= previewLimit ? cues[cueIndexAt(cues, hover.time)] : void 0;
846
+ const hoverMarker = hover === null ? void 0 : markers.find(
847
+ (marker) => Math.abs(marker.at / duration * hover.width - hover.x) <= MARKER_REACH
848
+ );
849
+ const hoverChapter = hover === null ? void 0 : chapterAt(hover.time);
850
+ const bubbleLeft = hover === null ? 0 : hover.width > BUBBLE_HALF * 2 ? Math.min(hover.width - BUBBLE_HALF, Math.max(BUBBLE_HALF, hover.x)) : hover.width / 2;
851
+ const currentChapter = chapterAt(current);
852
+ return /* @__PURE__ */ jsxRuntime.jsxs(
853
+ "div",
854
+ {
855
+ ref: trackRef,
856
+ className: "lk-iv-scrubber",
857
+ role: "slider",
858
+ tabIndex: ready ? 0 : -1,
859
+ "aria-label": strings.media.seek,
860
+ "aria-valuemin": 0,
861
+ "aria-valuemax": Math.round(duration),
862
+ "aria-valuenow": Math.round(current),
863
+ "aria-valuetext": currentChapter === void 0 ? strings.media.timeValue(clock(current), clock(duration)) : `${strings.media.timeValue(clock(current), clock(duration))}, ${currentChapter.title}`,
864
+ "data-dragging": dragging || void 0,
865
+ onPointerDown,
866
+ onPointerMove,
867
+ onPointerUp: endDrag,
868
+ onPointerCancel: endDrag,
869
+ onPointerLeave: () => {
870
+ if (!dragging) {
871
+ setHover(null);
872
+ }
873
+ },
874
+ onKeyDown,
875
+ children: [
876
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-iv-scrubber-track", "aria-hidden": "true", children: [
877
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lk-iv-scrubber-buffered", style: { width: percent(buffered) } }),
878
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lk-iv-scrubber-played", style: { width: percent(current) } }),
879
+ chapters.filter((chapter) => chapter.at > 0 && chapter.at < duration).map((chapter) => /* @__PURE__ */ jsxRuntime.jsx(
880
+ "span",
881
+ {
882
+ className: "lk-iv-scrubber-gap",
883
+ style: { insetInlineStart: percent(chapter.at) }
884
+ },
885
+ `chapter-${chapter.at}`
886
+ ))
887
+ ] }),
888
+ /* @__PURE__ */ jsxRuntime.jsx(
889
+ "div",
890
+ {
891
+ className: "lk-iv-scrubber-thumb",
892
+ "aria-hidden": "true",
893
+ style: { insetInlineStart: percent(current) }
894
+ }
895
+ ),
896
+ markers.map((marker) => /* @__PURE__ */ jsxRuntime.jsx(
897
+ "span",
898
+ {
899
+ className: "lk-iv-marker",
900
+ "data-state": marker.state,
901
+ "data-required": marker.required || void 0,
902
+ "aria-hidden": "true",
903
+ style: { insetInlineStart: percent(Math.min(marker.at, duration)) }
904
+ },
905
+ marker.id
906
+ )),
907
+ hover !== null ? /* @__PURE__ */ jsxRuntime.jsxs(
908
+ "div",
909
+ {
910
+ className: "lk-iv-bubble",
911
+ "aria-hidden": "true",
912
+ style: { insetInlineStart: `${bubbleLeft}px` },
913
+ children: [
914
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "lk-iv-bubble-time", children: [
915
+ clock(hover.time),
916
+ hoverChapter !== void 0 ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "lk-iv-bubble-chapter", children: [
917
+ " \xB7 ",
918
+ hoverChapter.title
919
+ ] }) : null
920
+ ] }),
921
+ hoverMarker !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-iv-bubble-quiz", children: hoverMarker.label }) : null,
922
+ hoverCue !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-iv-bubble-caption", children: hoverCue.text }) : null
923
+ ]
924
+ }
925
+ ) : null
926
+ ]
927
+ }
928
+ );
929
+ }
930
+ function contentKey(group) {
931
+ try {
932
+ return JSON.stringify([group.id, group.stimulus, group.items, group.timeline]);
933
+ } catch {
934
+ return String(group?.id);
935
+ }
936
+ }
937
+ function InteractiveVideo(props) {
938
+ const key = react.useMemo(() => contentKey(props.group), [props.group]);
939
+ return /* @__PURE__ */ jsxRuntime.jsx(Player, { ...props }, key);
940
+ }
941
+ function onNextFrame(run) {
942
+ if (typeof requestAnimationFrame === "function") {
943
+ const frame = requestAnimationFrame(run);
944
+ return () => cancelAnimationFrame(frame);
945
+ }
946
+ const timer = setTimeout(run, 33);
947
+ return () => clearTimeout(timer);
948
+ }
949
+ var playerCount = 0;
950
+ var ITEM_TYPES = /* @__PURE__ */ new Set([
951
+ "multiple-choice",
952
+ "fill-in-the-blanks",
953
+ "gap-select",
954
+ "dictation",
955
+ "read-aloud"
956
+ ]);
957
+ function Player(props) {
958
+ const {
959
+ renderMode = "practice",
960
+ onProgress,
961
+ onSubmit,
962
+ onActivityComplete,
963
+ onFinished,
964
+ onInteraction,
965
+ recordingBinding,
966
+ workletUrl,
967
+ captionsLoader,
968
+ label,
969
+ shuffleSeed,
970
+ locale,
971
+ theme,
972
+ sanitizeHtml
973
+ } = props;
974
+ const strings = chunkZ6TVOAO5_cjs.useLkStrings(props.strings);
975
+ const group = react.useRef(props.group).current;
976
+ const timeline = group.timeline ?? { cues: [] };
977
+ const media = group.stimulus.media;
978
+ const chapters = timeline.chapters ?? [];
979
+ const devError = react.useMemo(() => {
980
+ if (!chunk53YOZOGN_cjs.isDevelopment()) {
981
+ return null;
982
+ }
983
+ if (group.timeline === void 0) {
984
+ return new Error(
985
+ `InteractiveVideo "${group.id}" was given an item group with no timeline. Give it a timeline \u2014 a list of quizzes, each at a moment of the video \u2014 or render it with <ActivitySequence>.`
986
+ );
987
+ }
988
+ if (group.redacted === true) {
989
+ try {
990
+ lkCore.assertRedactedItemGroup(group);
991
+ } catch (error) {
992
+ return error;
993
+ }
994
+ return null;
995
+ }
996
+ const result = lkCore.validateItemGroup(group);
997
+ return result.success ? null : new lkCore.ActivitySchemaError("item-group", result.errors);
998
+ }, [group]);
999
+ const slots = react.useMemo(() => {
1000
+ try {
1001
+ return lkCore.flattenSequence([group]);
1002
+ } catch {
1003
+ return [];
1004
+ }
1005
+ }, [group]);
1006
+ const cues = react.useMemo(
1007
+ () => timeline.cues.filter(
1008
+ (cue) => slots.some((slot) => slot.group?.cue?.id === cue.id && ITEM_TYPES.has(slot.activity.type))
1009
+ ),
1010
+ [timeline.cues, slots]
1011
+ );
1012
+ const slotsByCue = react.useMemo(() => {
1013
+ const byCue = /* @__PURE__ */ new Map();
1014
+ for (const slot of slots) {
1015
+ const cueId = slot.group?.cue?.id;
1016
+ if (cueId !== void 0 && ITEM_TYPES.has(slot.activity.type)) {
1017
+ byCue.set(cueId, [...byCue.get(cueId) ?? [], slot]);
1018
+ }
1019
+ }
1020
+ return byCue;
1021
+ }, [slots]);
1022
+ const [playerId] = react.useState(() => {
1023
+ playerCount += 1;
1024
+ return `lk-iv-${playerCount}`;
1025
+ });
1026
+ const [preferences, setPreferences] = react.useState(() => ({
1027
+ ...DEFAULT_PREFERENCES,
1028
+ ...props.preferences
1029
+ }));
1030
+ const preferenceOverride = react.useRef(props.preferences);
1031
+ react.useEffect(() => {
1032
+ setPreferences({ ...readPreferences(), ...preferenceOverride.current });
1033
+ }, []);
1034
+ const changePreferences = react.useCallback((change) => {
1035
+ setPreferences((previous) => {
1036
+ const next = { ...previous, ...change };
1037
+ writePreferences(next);
1038
+ return next;
1039
+ });
1040
+ }, []);
1041
+ const [submitted] = react.useState(() => new Set(props.submittedSlotIds ?? []));
1042
+ const [answered, setAnswered] = react.useState(() => {
1043
+ const seeded = /* @__PURE__ */ new Set();
1044
+ for (const slot of slots) {
1045
+ if (submitted.has(slot.slotId) || props.outcomes !== void 0 && Object.hasOwn(props.outcomes, slot.slotId)) {
1046
+ seeded.add(slot.slotId);
1047
+ }
1048
+ }
1049
+ return seeded;
1050
+ });
1051
+ const answeredRef = react.useRef(answered);
1052
+ answeredRef.current = answered;
1053
+ const scores = react.useRef(/* @__PURE__ */ new Map());
1054
+ const markAnswered = react.useCallback((slotId) => {
1055
+ setAnswered((previous) => {
1056
+ if (previous.has(slotId)) {
1057
+ return previous;
1058
+ }
1059
+ const next = new Set(previous);
1060
+ next.add(slotId);
1061
+ answeredRef.current = next;
1062
+ return next;
1063
+ });
1064
+ }, []);
1065
+ const finished = react.useCallback(
1066
+ (cueId) => (slotsByCue.get(cueId) ?? []).every((slot) => answeredRef.current.has(slot.slotId)),
1067
+ [slotsByCue]
1068
+ );
1069
+ const shellRef = react.useRef(null);
1070
+ const videoRef = react.useRef(null);
1071
+ const playButtonRef = react.useRef(null);
1072
+ const quizHeadingRef = react.useRef(null);
1073
+ const [playing, setPlaying] = react.useState(false);
1074
+ const [waiting, setWaiting] = react.useState(false);
1075
+ const [errorCode, setErrorCode] = react.useState(null);
1076
+ const [current, setCurrent] = react.useState(0);
1077
+ const [duration, setDuration] = react.useState(0);
1078
+ const [buffered, setBuffered] = react.useState(0);
1079
+ const [scrubbing, setScrubbing] = react.useState(false);
1080
+ const [ended, setEnded] = react.useState(false);
1081
+ const [fullscreen, setFullscreen] = react.useState(false);
1082
+ const [pictureInPicture, setPictureInPicture] = react.useState(false);
1083
+ const [countdown, setCountdown] = react.useState(false);
1084
+ const [menu, setMenu] = react.useState(null);
1085
+ const [sheet, setSheet] = react.useState(false);
1086
+ const [panelTab, setPanelTab] = react.useState("contents");
1087
+ const [chromeShown, setChromeShown] = react.useState(true);
1088
+ const [focusInChrome, setFocusInChrome] = react.useState(false);
1089
+ const [announcement, setAnnouncement] = react.useState("");
1090
+ const previousTime = react.useRef(0);
1091
+ const furthest = react.useRef(0);
1092
+ const [furthestShown, setFurthestShown] = react.useState(0);
1093
+ const lastProgressAt = react.useRef(0);
1094
+ const hideTimer = react.useRef(null);
1095
+ const lastPointer = react.useRef("mouse");
1096
+ const finishedOnce = react.useRef(false);
1097
+ const [openQuiz, setOpenQuiz] = react.useState(null);
1098
+ const openQuizRef = react.useRef(openQuiz);
1099
+ openQuizRef.current = openQuiz;
1100
+ const [mounted, setMounted] = react.useState(/* @__PURE__ */ new Set());
1101
+ const handled = react.useRef(/* @__PURE__ */ new Set());
1102
+ const announce = react.useCallback((text) => {
1103
+ setAnnouncement("");
1104
+ onNextFrame(() => setAnnouncement(text));
1105
+ }, []);
1106
+ const emit = react.useCallback(
1107
+ (type, payload) => {
1108
+ onInteraction?.({
1109
+ type,
1110
+ activityId: group.id,
1111
+ timestamp: Date.now(),
1112
+ ...payload !== void 0 ? { payload } : {}
1113
+ });
1114
+ },
1115
+ [onInteraction, group.id]
1116
+ );
1117
+ const reportProgress = react.useCallback(
1118
+ (force) => {
1119
+ const video = videoRef.current;
1120
+ if (onProgress === void 0 || video === null) {
1121
+ return;
1122
+ }
1123
+ const now = Date.now();
1124
+ if (!force && now - lastProgressAt.current < 5e3) {
1125
+ return;
1126
+ }
1127
+ lastProgressAt.current = now;
1128
+ onProgress({ progressVersion: "1.0", at: video.currentTime, furthest: furthest.current });
1129
+ },
1130
+ [onProgress]
1131
+ );
1132
+ const quizTitle = react.useCallback(
1133
+ (cue) => cue.title ?? strings.videoQuiz,
1134
+ [strings.videoQuiz]
1135
+ );
1136
+ const openAt = react.useCallback(
1137
+ (cue) => {
1138
+ const video = videoRef.current;
1139
+ const quizSlots = slotsByCue.get(cue.id) ?? [];
1140
+ if (video !== null) {
1141
+ video.pause();
1142
+ if (Math.abs(video.currentTime - cue.at) > 0.25 && cue.at <= (video.duration || cue.at)) {
1143
+ video.currentTime = Math.min(cue.at, video.duration || cue.at);
1144
+ }
1145
+ previousTime.current = cue.at;
1146
+ const webkit = video;
1147
+ if (webkit.webkitDisplayingFullscreen) {
1148
+ webkit.webkitExitFullscreen?.();
1149
+ }
1150
+ if (typeof document !== "undefined" && document.pictureInPictureElement === video) {
1151
+ void document.exitPictureInPicture?.().catch(() => void 0);
1152
+ }
1153
+ }
1154
+ const firstOpen = quizSlots.findIndex((slot) => !answeredRef.current.has(slot.slotId));
1155
+ setMenu(null);
1156
+ setSheet(false);
1157
+ setEnded(false);
1158
+ setMounted((previous) => previous.has(cue.id) ? previous : /* @__PURE__ */ new Set([...previous, cue.id]));
1159
+ setOpenQuiz({ cueId: cue.id, step: firstOpen === -1 ? 0 : firstOpen });
1160
+ announce(strings.videoQuizOpened(clock(cue.at), quizSlots.length));
1161
+ emit("video-quiz-opened", { cueId: cue.id, at: cue.at });
1162
+ reportProgress(true);
1163
+ },
1164
+ [slotsByCue, announce, strings, emit, reportProgress]
1165
+ );
1166
+ react.useEffect(() => {
1167
+ if (openQuiz !== null) {
1168
+ quizHeadingRef.current?.focus();
1169
+ }
1170
+ }, [openQuiz?.cueId, openQuiz]);
1171
+ const closeQuiz = react.useCallback(
1172
+ (how) => {
1173
+ const open = openQuizRef.current;
1174
+ if (open === null) {
1175
+ return;
1176
+ }
1177
+ const cue = cues.find((candidate) => candidate.id === open.cueId);
1178
+ setOpenQuiz(null);
1179
+ const video = videoRef.current;
1180
+ if (how === "rewatch" && cue !== void 0 && video !== null) {
1181
+ const earlier = [...cues.map((c) => c.at), ...chapters.map((c) => c.at)].filter((at) => at < cue.at - 0.5).sort((a, b) => b - a)[0];
1182
+ const to = earlier ?? 0;
1183
+ video.currentTime = to;
1184
+ previousTime.current = to;
1185
+ setCurrent(to);
1186
+ } else if (cue !== void 0) {
1187
+ handled.current.add(cue.id);
1188
+ emit(how === "skip" ? "video-quiz-skipped" : "video-quiz-closed", { cueId: cue.id });
1189
+ }
1190
+ playButtonRef.current?.focus();
1191
+ if (video !== null && !(video.ended || duration > 0 && video.currentTime >= duration - 0.05)) {
1192
+ void video.play().catch(() => setPlaying(false));
1193
+ } else if (video !== null) {
1194
+ const next = quizzesAtEnd(cues, duration, handled.current)[0];
1195
+ if (next !== void 0) {
1196
+ openAt(next);
1197
+ } else {
1198
+ setEnded(true);
1199
+ }
1200
+ }
1201
+ },
1202
+ [cues, chapters, emit, duration, openAt]
1203
+ );
1204
+ const onTick = react.useCallback(
1205
+ (now) => {
1206
+ const before = previousTime.current;
1207
+ previousTime.current = now;
1208
+ if (now > furthest.current) {
1209
+ furthest.current = now;
1210
+ }
1211
+ if (openQuizRef.current !== null || scrubbing) {
1212
+ return;
1213
+ }
1214
+ const crossed = crossedQuiz(cues, before, now, handled.current);
1215
+ if (crossed !== void 0) {
1216
+ openAt(crossed);
1217
+ }
1218
+ },
1219
+ [cues, scrubbing, openAt]
1220
+ );
1221
+ react.useEffect(() => {
1222
+ if (!playing || scrubbing) {
1223
+ return;
1224
+ }
1225
+ let cancel = () => {
1226
+ };
1227
+ const tick = () => {
1228
+ const video = videoRef.current;
1229
+ if (video !== null) {
1230
+ setCurrent(video.currentTime);
1231
+ setBuffered(bufferedAhead(video));
1232
+ onTick(video.currentTime);
1233
+ if (furthest.current > furthestShown + 1) {
1234
+ setFurthestShown(furthest.current);
1235
+ }
1236
+ reportProgress(false);
1237
+ }
1238
+ cancel = onNextFrame(tick);
1239
+ };
1240
+ cancel = onNextFrame(tick);
1241
+ return () => cancel();
1242
+ }, [playing, scrubbing, onTick, reportProgress, furthestShown]);
1243
+ const seek = react.useCallback(
1244
+ (to) => {
1245
+ const video = videoRef.current;
1246
+ if (video === null || duration <= 0) {
1247
+ return;
1248
+ }
1249
+ const from = video.currentTime;
1250
+ const target = Math.min(Math.max(0, to), duration);
1251
+ const limited = renderMode === "review" ? { at: target } : limitSeek(timeline, from, target, furthest.current, finished);
1252
+ video.currentTime = limited.at;
1253
+ previousTime.current = limited.at;
1254
+ setCurrent(limited.at);
1255
+ setEnded(false);
1256
+ emit("video-seeked", { from, to: limited.at, clamped: limited.hold !== void 0 });
1257
+ if (limited.hold?.kind === "required") {
1258
+ openAt(limited.hold.cue);
1259
+ announce(strings.videoHeldAtQuiz(clock(limited.hold.cue.at)));
1260
+ } else if (limited.hold?.kind === "no-skip-ahead") {
1261
+ announce(strings.videoHeldAhead);
1262
+ }
1263
+ reportProgress(true);
1264
+ },
1265
+ [duration, renderMode, timeline, finished, emit, announce, strings, openAt, reportProgress]
1266
+ );
1267
+ const togglePlay = react.useCallback(() => {
1268
+ const video = videoRef.current;
1269
+ if (video === null) {
1270
+ return;
1271
+ }
1272
+ if (ended) {
1273
+ setEnded(false);
1274
+ handled.current.clear();
1275
+ video.currentTime = 0;
1276
+ previousTime.current = -1;
1277
+ void video.play().catch(() => setPlaying(false));
1278
+ return;
1279
+ }
1280
+ if (video.paused) {
1281
+ if (video.currentTime === 0 && previousTime.current <= 0) {
1282
+ const atStart = crossedQuiz(cues, -1, 0, handled.current);
1283
+ if (atStart !== void 0) {
1284
+ previousTime.current = 0;
1285
+ openAt(atStart);
1286
+ return;
1287
+ }
1288
+ }
1289
+ void video.play().catch(() => setPlaying(false));
1290
+ } else {
1291
+ video.pause();
1292
+ }
1293
+ }, [ended, cues, openAt]);
1294
+ const tracks = react.useMemo(() => {
1295
+ if (media?.tracks !== void 0 && media.tracks.length > 0) {
1296
+ return media.tracks.filter(
1297
+ (track) => track.kind === "captions" || track.kind === "subtitles"
1298
+ );
1299
+ }
1300
+ return media?.captionsUrl !== void 0 ? [
1301
+ {
1302
+ kind: "captions",
1303
+ src: media.captionsUrl,
1304
+ srclang: locale ?? "en",
1305
+ label: strings.videoCaptionLanguage
1306
+ }
1307
+ ] : [];
1308
+ }, [media, locale, strings.videoCaptionLanguage]);
1309
+ const activeTrack = tracks.find((track) => track.srclang === preferences.captionLanguage) ?? tracks.find((track) => track.default === true) ?? tracks[0];
1310
+ const [captionCues, setCaptionCues] = react.useState([]);
1311
+ const [captionsFailed, setCaptionsFailed] = react.useState(false);
1312
+ const loaderRef = react.useRef(captionsLoader);
1313
+ loaderRef.current = captionsLoader;
1314
+ react.useEffect(() => {
1315
+ if (activeTrack === void 0) {
1316
+ setCaptionCues([]);
1317
+ return;
1318
+ }
1319
+ let live = true;
1320
+ setCaptionsFailed(false);
1321
+ const load = loaderRef.current ? loaderRef.current(activeTrack) : fetch(activeTrack.src, { credentials: "same-origin" }).then((response) => {
1322
+ if (!response.ok) {
1323
+ throw new Error(String(response.status));
1324
+ }
1325
+ return response.text();
1326
+ });
1327
+ Promise.resolve(load).then((text) => {
1328
+ if (live) {
1329
+ const parsed = parseWebVtt(text);
1330
+ setCaptionCues(parsed);
1331
+ setCaptionsFailed(parsed.length === 0);
1332
+ }
1333
+ }).catch(() => {
1334
+ if (live) {
1335
+ setCaptionCues([]);
1336
+ setCaptionsFailed(true);
1337
+ }
1338
+ });
1339
+ return () => {
1340
+ live = false;
1341
+ };
1342
+ }, [activeTrack]);
1343
+ const hasCaptions = captionCues.length > 0;
1344
+ const activeCueIndex = react.useMemo(() => cueIndexAt(captionCues, current), [captionCues, current]);
1345
+ const spoilerLimit = timeline.navigation === "no-skip-ahead" && renderMode !== "review" ? Math.max(furthestShown, current) : Number.POSITIVE_INFINITY;
1346
+ react.useEffect(() => {
1347
+ const video = videoRef.current;
1348
+ if (video !== null) {
1349
+ video.playbackRate = preferences.speed;
1350
+ video.volume = preferences.volume;
1351
+ video.muted = preferences.muted;
1352
+ }
1353
+ }, [preferences.speed, preferences.volume, preferences.muted]);
1354
+ react.useEffect(() => {
1355
+ const onChange = () => setFullscreen(document.fullscreenElement === shellRef.current);
1356
+ document.addEventListener("fullscreenchange", onChange);
1357
+ return () => document.removeEventListener("fullscreenchange", onChange);
1358
+ }, []);
1359
+ react.useEffect(() => {
1360
+ const video = videoRef.current;
1361
+ if (video === null) {
1362
+ return;
1363
+ }
1364
+ const enter = () => {
1365
+ setPictureInPicture(true);
1366
+ emit("video-pip-changed", { on: true });
1367
+ };
1368
+ const leave = () => {
1369
+ setPictureInPicture(false);
1370
+ emit("video-pip-changed", { on: false });
1371
+ };
1372
+ video.addEventListener("enterpictureinpicture", enter);
1373
+ video.addEventListener("leavepictureinpicture", leave);
1374
+ return () => {
1375
+ video.removeEventListener("enterpictureinpicture", enter);
1376
+ video.removeEventListener("leavepictureinpicture", leave);
1377
+ };
1378
+ }, [emit]);
1379
+ react.useEffect(
1380
+ () => () => {
1381
+ if (hideTimer.current !== null) {
1382
+ clearTimeout(hideTimer.current);
1383
+ }
1384
+ },
1385
+ []
1386
+ );
1387
+ const revealChrome = react.useCallback(() => {
1388
+ setChromeShown(true);
1389
+ if (hideTimer.current !== null) {
1390
+ clearTimeout(hideTimer.current);
1391
+ }
1392
+ hideTimer.current = setTimeout(() => setChromeShown(false), 2400);
1393
+ }, []);
1394
+ const toggleFullscreen = react.useCallback(() => {
1395
+ const shell = shellRef.current;
1396
+ const video = videoRef.current;
1397
+ if (typeof document === "undefined" || shell === null) {
1398
+ return;
1399
+ }
1400
+ if (document.fullscreenElement) {
1401
+ void document.exitFullscreen().catch(() => void 0);
1402
+ } else if (typeof shell.requestFullscreen === "function" && document.fullscreenEnabled !== false) {
1403
+ void shell.requestFullscreen().catch(() => void 0);
1404
+ } else {
1405
+ video?.webkitEnterFullscreen?.();
1406
+ }
1407
+ emit("video-fullscreen-changed");
1408
+ }, [emit]);
1409
+ const pictureInPictureSupported = typeof document !== "undefined" && document.pictureInPictureEnabled === true;
1410
+ const togglePictureInPicture = react.useCallback(() => {
1411
+ const video = videoRef.current;
1412
+ if (video === null) {
1413
+ return;
1414
+ }
1415
+ if (document.pictureInPictureElement) {
1416
+ void document.exitPictureInPicture().catch(() => void 0);
1417
+ } else {
1418
+ void video.requestPictureInPicture?.().catch(() => void 0);
1419
+ }
1420
+ }, []);
1421
+ const jumpPoints = react.useMemo(
1422
+ () => [.../* @__PURE__ */ new Set([...chapters.map((c) => c.at), ...cues.map((c) => c.at)])].sort((a, b) => a - b),
1423
+ [chapters, cues]
1424
+ );
1425
+ const onKeyDown = (event) => {
1426
+ if (openQuiz !== null || !preferences.shortcuts) {
1427
+ return;
1428
+ }
1429
+ const target = event.target;
1430
+ if (target.isContentEditable || ["INPUT", "TEXTAREA", "SELECT"].includes(target.tagName) || target.tagName === "BUTTON" && (event.key === " " || event.key === "Enter")) {
1431
+ return;
1432
+ }
1433
+ if (event.ctrlKey || event.metaKey || event.altKey) {
1434
+ return;
1435
+ }
1436
+ const video = videoRef.current;
1437
+ const key = event.key;
1438
+ const done = () => {
1439
+ event.preventDefault();
1440
+ revealChrome();
1441
+ };
1442
+ if (/^[0-9]$/.test(key)) {
1443
+ done();
1444
+ seek(Number(key) / 10 * duration);
1445
+ return;
1446
+ }
1447
+ switch (key) {
1448
+ case " ":
1449
+ case "k":
1450
+ case "K":
1451
+ done();
1452
+ togglePlay();
1453
+ return;
1454
+ case "j":
1455
+ case "J":
1456
+ done();
1457
+ seek(current - JUMP_SECONDS);
1458
+ return;
1459
+ case "l":
1460
+ case "L":
1461
+ done();
1462
+ seek(current + JUMP_SECONDS);
1463
+ return;
1464
+ case "ArrowLeft":
1465
+ done();
1466
+ seek(current - (event.shiftKey ? 1 : 5));
1467
+ return;
1468
+ case "ArrowRight":
1469
+ done();
1470
+ seek(current + (event.shiftKey ? 1 : 5));
1471
+ return;
1472
+ case "Home":
1473
+ done();
1474
+ seek(0);
1475
+ return;
1476
+ case "End":
1477
+ done();
1478
+ seek(duration);
1479
+ return;
1480
+ case ",":
1481
+ case ".":
1482
+ done();
1483
+ video?.pause();
1484
+ seek(current + (key === "," ? -FRAME_SECONDS : FRAME_SECONDS));
1485
+ return;
1486
+ case "<":
1487
+ case ">":
1488
+ done();
1489
+ changePreferences({ speed: nudgeSpeed(preferences.speed, key === "<" ? -1 : 1) });
1490
+ return;
1491
+ case "[": {
1492
+ done();
1493
+ const previous = [...jumpPoints].reverse().find((at) => at < current - 1);
1494
+ seek(previous ?? 0);
1495
+ return;
1496
+ }
1497
+ case "]": {
1498
+ done();
1499
+ const next = jumpPoints.find((at) => at > current + 0.5);
1500
+ if (next !== void 0) {
1501
+ seek(next);
1502
+ }
1503
+ return;
1504
+ }
1505
+ case "m":
1506
+ case "M":
1507
+ done();
1508
+ changePreferences({ muted: !preferences.muted });
1509
+ return;
1510
+ case "c":
1511
+ case "C":
1512
+ if (hasCaptions) {
1513
+ done();
1514
+ changePreferences({ captions: !preferences.captions });
1515
+ }
1516
+ return;
1517
+ case "t":
1518
+ case "T":
1519
+ if (hasCaptions) {
1520
+ done();
1521
+ setPanelTab("transcript");
1522
+ changePreferences({ panel: !(preferences.panel && panelTab === "transcript") });
1523
+ }
1524
+ return;
1525
+ case "p":
1526
+ case "P":
1527
+ if (pictureInPictureSupported) {
1528
+ done();
1529
+ togglePictureInPicture();
1530
+ }
1531
+ return;
1532
+ case "f":
1533
+ case "F":
1534
+ done();
1535
+ toggleFullscreen();
1536
+ return;
1537
+ case "?":
1538
+ done();
1539
+ setSheet((open) => !open);
1540
+ return;
1541
+ default:
1542
+ return;
1543
+ }
1544
+ };
1545
+ const quizzes = cues.map((cue) => {
1546
+ const quizSlots = slotsByCue.get(cue.id) ?? [];
1547
+ const count = quizSlots.filter((slot) => answered.has(slot.slotId)).length;
1548
+ return {
1549
+ id: cue.id,
1550
+ at: cue.at,
1551
+ title: quizTitle(cue),
1552
+ answered: count,
1553
+ total: quizSlots.length,
1554
+ // In review the attempt is over: nothing is required of the learner any
1555
+ // more, in the list or on the bar.
1556
+ required: cue.required === true && renderMode !== "review",
1557
+ state: markerState(quizSlots, answered, renderMode, props.outcomes)
1558
+ };
1559
+ });
1560
+ const markers = quizzes.map((quiz) => ({
1561
+ id: quiz.id,
1562
+ at: quiz.at,
1563
+ label: `${quiz.title} \xB7 ${strings.videoQuizProgress(quiz.answered, quiz.total)}`,
1564
+ state: quiz.state,
1565
+ required: quiz.required
1566
+ }));
1567
+ const totalQuestions = quizzes.reduce((sum, quiz) => sum + quiz.total, 0);
1568
+ const answeredCount = quizzes.reduce((sum, quiz) => sum + quiz.answered, 0);
1569
+ const graded = [...scores.current.values()];
1570
+ const percent = graded.length === 0 ? null : Math.round(graded.reduce((a, b) => a + b, 0) / graded.length * 100);
1571
+ const summary = react.useCallback(() => {
1572
+ return {
1573
+ slots: slots.filter((slot) => slot.group?.cue !== void 0).map((slot) => {
1574
+ const cueId = slot.group?.cue?.id;
1575
+ return {
1576
+ slotId: slot.slotId,
1577
+ activityId: slot.activity.id,
1578
+ cueId,
1579
+ status: answeredRef.current.has(slot.slotId) ? "answered" : handled.current.has(cueId) ? "skipped" : "unreached"
1580
+ };
1581
+ })
1582
+ };
1583
+ }, [slots]);
1584
+ const finish = react.useCallback(() => {
1585
+ if (finishedOnce.current) {
1586
+ return;
1587
+ }
1588
+ finishedOnce.current = true;
1589
+ onFinished?.(summary());
1590
+ }, [onFinished, summary]);
1591
+ react.useEffect(() => {
1592
+ if (ended && totalQuestions > 0 && answeredCount === totalQuestions && renderMode !== "review") {
1593
+ finish();
1594
+ }
1595
+ }, [ended, totalQuestions, answeredCount, renderMode, finish]);
1596
+ const slotBindings = react.useMemo(() => {
1597
+ const bindings = /* @__PURE__ */ new Map();
1598
+ if (recordingBinding === void 0) {
1599
+ return bindings;
1600
+ }
1601
+ const { assess, playbackUrl } = recordingBinding;
1602
+ for (const slot of slots) {
1603
+ const place = {
1604
+ slotId: slot.slotId,
1605
+ index: slot.index,
1606
+ activityId: slot.activity.id
1607
+ };
1608
+ bindings.set(slot.slotId, {
1609
+ upload: (take) => recordingBinding.upload(take, place),
1610
+ ...assess !== void 0 ? { assess: (ref) => assess.call(recordingBinding, ref, place) } : {},
1611
+ ...playbackUrl !== void 0 ? { playbackUrl: (ref) => playbackUrl.call(recordingBinding, ref, place) } : {}
1612
+ });
1613
+ }
1614
+ return bindings;
1615
+ }, [slots, recordingBinding]);
1616
+ const renderQuestion = (slot, cueId) => {
1617
+ const activity = slot.activity;
1618
+ const place = {
1619
+ slotId: slot.slotId,
1620
+ index: slot.index,
1621
+ activityId: activity.id,
1622
+ cueId
1623
+ };
1624
+ const restored = props.responses !== void 0 && Object.hasOwn(props.responses, slot.slotId) ? props.responses[slot.slotId] : void 0;
1625
+ const outcome = props.outcomes !== void 0 && Object.hasOwn(props.outcomes, slot.slotId) ? props.outcomes[slot.slotId] : void 0;
1626
+ const common = {
1627
+ renderMode,
1628
+ ...restored !== void 0 ? { defaultValue: restored } : {},
1629
+ ...submitted.has(slot.slotId) ? { defaultSubmitted: true } : {},
1630
+ ...outcome !== void 0 ? { outcome } : {},
1631
+ onSubmit: (response) => {
1632
+ markAnswered(slot.slotId);
1633
+ onSubmit?.(response, place);
1634
+ },
1635
+ onComplete: (result) => {
1636
+ markAnswered(slot.slotId);
1637
+ if (result.maxScore > 0) {
1638
+ scores.current.set(slot.slotId, result.score / result.maxScore);
1639
+ }
1640
+ onActivityComplete?.(result, place);
1641
+ },
1642
+ ...onInteraction !== void 0 ? { onInteraction } : {},
1643
+ ...sanitizeHtml !== void 0 ? { sanitizeHtml } : {},
1644
+ ...props.strings !== void 0 ? { strings: props.strings } : {},
1645
+ ...locale !== void 0 ? { locale } : {}
1646
+ };
1647
+ let question;
1648
+ switch (activity.type) {
1649
+ case "multiple-choice":
1650
+ question = /* @__PURE__ */ jsxRuntime.jsx(
1651
+ chunkL7NE7PR4_cjs.MultipleChoice,
1652
+ {
1653
+ data: activity,
1654
+ ...common,
1655
+ ...shuffleSeed !== void 0 ? { shuffleSeed } : {}
1656
+ }
1657
+ );
1658
+ break;
1659
+ case "fill-in-the-blanks":
1660
+ question = /* @__PURE__ */ jsxRuntime.jsx(chunk4H3JTX74_cjs.FillInTheBlanks, { data: activity, ...common });
1661
+ break;
1662
+ case "gap-select":
1663
+ question = /* @__PURE__ */ jsxRuntime.jsx(
1664
+ chunkEKBEKDWO_cjs.GapSelect,
1665
+ {
1666
+ data: activity,
1667
+ ...common,
1668
+ ...shuffleSeed !== void 0 ? { shuffleSeed } : {}
1669
+ }
1670
+ );
1671
+ break;
1672
+ case "dictation":
1673
+ question = /* @__PURE__ */ jsxRuntime.jsx(chunkXMJE34FY_cjs.Dictation, { data: activity, ...common });
1674
+ break;
1675
+ case "read-aloud": {
1676
+ const binding = slotBindings.get(slot.slotId);
1677
+ const assessment = props.assessments !== void 0 && Object.hasOwn(props.assessments, slot.slotId) ? props.assessments[slot.slotId] : void 0;
1678
+ question = /* @__PURE__ */ jsxRuntime.jsx(
1679
+ chunkZH5WT2IE_cjs.ReadAloud,
1680
+ {
1681
+ data: activity,
1682
+ ...common,
1683
+ ...binding !== void 0 ? { recordingBinding: binding } : {},
1684
+ ...assessment !== void 0 ? { assessment } : {},
1685
+ ...workletUrl !== void 0 ? { workletUrl } : {}
1686
+ }
1687
+ );
1688
+ break;
1689
+ }
1690
+ default:
1691
+ question = null;
1692
+ }
1693
+ return question;
1694
+ };
1695
+ const channels = react.useRef(/* @__PURE__ */ new Map());
1696
+ const channelFor = (slotId) => {
1697
+ let channel = channels.current.get(slotId);
1698
+ if (channel === void 0) {
1699
+ channel = { captureGroup: `${playerId}::${slotId}`, takeState: () => void 0 };
1700
+ channels.current.set(slotId, channel);
1701
+ }
1702
+ return channel;
1703
+ };
1704
+ if (devError !== null) {
1705
+ throw devError;
1706
+ }
1707
+ const openCue = openQuiz === null ? void 0 : cues.find((cue) => cue.id === openQuiz.cueId);
1708
+ const openSlots = openCue === void 0 ? [] : slotsByCue.get(openCue.id) ?? [];
1709
+ const step = openQuiz?.step ?? 0;
1710
+ const stepSlot = openSlots[step];
1711
+ const stepAnswered = stepSlot !== void 0 && answered.has(stepSlot.slotId);
1712
+ const lastStep = step >= openSlots.length - 1;
1713
+ const required = openCue?.required === true && renderMode !== "review";
1714
+ const canAdvance = !required || stepAnswered;
1715
+ const goToStep = (next) => {
1716
+ setOpenQuiz((open) => open === null ? open : { ...open, step: next });
1717
+ const slot = openSlots[next];
1718
+ if (slot !== void 0 && openCue !== void 0) {
1719
+ announce(strings.videoQuestionProgress(next + 1, openSlots.length));
1720
+ emit("video-quiz-question-shown", { cueId: openCue.id, slotId: slot.slotId });
1721
+ }
1722
+ };
1723
+ const showCaption = preferences.captions && hasCaptions && activeCueIndex !== -1 && openQuiz === null && !ended && errorCode === null;
1724
+ const chromeVisible = !playing || chromeShown || menu !== null || scrubbing || focusInChrome || sheet || openQuiz !== null;
1725
+ const volumeLevel = preferences.muted || preferences.volume === 0 ? "muted" : preferences.volume < 0.5 ? "low" : "high";
1726
+ return (
1727
+ // biome-ignore lint/a11y/noStaticElementInteractions: the player's keyboard shortcuts, scoped to the player; each is also a button
1728
+ /* @__PURE__ */ jsxRuntime.jsxs(
1729
+ "div",
1730
+ {
1731
+ ref: shellRef,
1732
+ className: "lk-iv",
1733
+ tabIndex: -1,
1734
+ lang: locale,
1735
+ style: theme,
1736
+ "data-render-mode": renderMode,
1737
+ "data-quiz-open": openQuiz !== null || void 0,
1738
+ "data-fullscreen": fullscreen || void 0,
1739
+ "data-chrome": chromeVisible ? "shown" : "hidden",
1740
+ onKeyDown,
1741
+ children: [
1742
+ /* @__PURE__ */ jsxRuntime.jsxs(
1743
+ "div",
1744
+ {
1745
+ className: "lk-iv-stage",
1746
+ onPointerDown: (event) => {
1747
+ lastPointer.current = event.pointerType;
1748
+ },
1749
+ onPointerMove: (event) => {
1750
+ if (event.pointerType !== "touch") {
1751
+ revealChrome();
1752
+ }
1753
+ },
1754
+ onPointerLeave: () => {
1755
+ if (playing) {
1756
+ setChromeShown(false);
1757
+ }
1758
+ },
1759
+ children: [
1760
+ /* @__PURE__ */ jsxRuntime.jsx(
1761
+ "video",
1762
+ {
1763
+ ref: videoRef,
1764
+ className: "lk-iv-video",
1765
+ src: media?.url,
1766
+ ...media?.poster !== void 0 ? { poster: media.poster } : {},
1767
+ preload: "metadata",
1768
+ playsInline: true,
1769
+ "aria-label": group.title ?? media?.alt,
1770
+ onClick: () => {
1771
+ if (lastPointer.current === "touch" && !chromeVisible) {
1772
+ revealChrome();
1773
+ return;
1774
+ }
1775
+ togglePlay();
1776
+ },
1777
+ onDoubleClick: toggleFullscreen,
1778
+ onPlay: () => {
1779
+ setPlaying(true);
1780
+ setEnded(false);
1781
+ revealChrome();
1782
+ emit("video-played", { at: videoRef.current?.currentTime ?? 0 });
1783
+ },
1784
+ onPause: () => {
1785
+ setPlaying(false);
1786
+ emit("video-paused", { at: videoRef.current?.currentTime ?? 0 });
1787
+ reportProgress(true);
1788
+ },
1789
+ onWaiting: () => setWaiting(true),
1790
+ onPlaying: () => setWaiting(false),
1791
+ onProgress: (event) => setBuffered(bufferedAhead(event.currentTarget)),
1792
+ onTimeUpdate: (event) => {
1793
+ if (!playing && !scrubbing) {
1794
+ setCurrent(event.currentTarget.currentTime);
1795
+ }
1796
+ },
1797
+ onRateChange: (event) => emit("video-rate-changed", { rate: event.currentTarget.playbackRate }),
1798
+ onLoadedMetadata: (event) => {
1799
+ const video = event.currentTarget;
1800
+ const length = Number.isFinite(video.duration) ? video.duration : 0;
1801
+ setDuration(length);
1802
+ video.playbackRate = preferences.speed;
1803
+ video.volume = preferences.volume;
1804
+ video.muted = preferences.muted;
1805
+ const stored = lkCore.readMediaProgress(props.progress, length);
1806
+ if (stored !== null) {
1807
+ const at = renderMode === "review" ? stored.at : limitResume(timeline, stored.at, finished);
1808
+ furthest.current = Math.max(stored.furthest, at);
1809
+ setFurthestShown(furthest.current);
1810
+ video.currentTime = at;
1811
+ previousTime.current = at;
1812
+ setCurrent(at);
1813
+ }
1814
+ },
1815
+ onEnded: () => {
1816
+ setPlaying(false);
1817
+ setCurrent(duration);
1818
+ emit("video-ended");
1819
+ reportProgress(true);
1820
+ const atEnd = quizzesAtEnd(cues, duration, handled.current);
1821
+ const first = atEnd[0];
1822
+ if (first !== void 0) {
1823
+ openAt(first);
1824
+ } else {
1825
+ setEnded(true);
1826
+ }
1827
+ },
1828
+ onError: (event) => {
1829
+ setWaiting(false);
1830
+ setErrorCode(event.currentTarget.error?.code ?? 0);
1831
+ }
1832
+ }
1833
+ ),
1834
+ label !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-iv-label", children: label }) : null,
1835
+ showCaption ? /* @__PURE__ */ jsxRuntime.jsx(
1836
+ "div",
1837
+ {
1838
+ className: "lk-iv-caption",
1839
+ "data-size": preferences.captionSize,
1840
+ "data-background": preferences.captionBackground || void 0,
1841
+ lang: activeTrack?.srclang,
1842
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: captionCues[activeCueIndex]?.text })
1843
+ }
1844
+ ) : null,
1845
+ waiting && errorCode === null && openQuiz === null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-iv-spinner", "aria-hidden": "true" }) : null,
1846
+ errorCode !== null ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-iv-error", role: "alert", children: [
1847
+ /* @__PURE__ */ jsxRuntime.jsx(AlertIcon, {}),
1848
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: strings.videoError(errorCode) }),
1849
+ /* @__PURE__ */ jsxRuntime.jsx(
1850
+ "button",
1851
+ {
1852
+ type: "button",
1853
+ className: "lk-iv-action",
1854
+ onClick: () => {
1855
+ setErrorCode(null);
1856
+ videoRef.current?.load();
1857
+ },
1858
+ children: strings.videoTryAgain
1859
+ }
1860
+ )
1861
+ ] }) : null,
1862
+ !playing && !waiting && errorCode === null && openQuiz === null && !ended ? /* @__PURE__ */ jsxRuntime.jsx(
1863
+ "button",
1864
+ {
1865
+ type: "button",
1866
+ className: "lk-iv-big-play",
1867
+ "aria-label": strings.media.play,
1868
+ onClick: togglePlay,
1869
+ children: /* @__PURE__ */ jsxRuntime.jsx(PlayIcon, {})
1870
+ }
1871
+ ) : null,
1872
+ /* @__PURE__ */ jsxRuntime.jsxs(
1873
+ "div",
1874
+ {
1875
+ className: "lk-iv-chrome",
1876
+ inert: openQuiz !== null,
1877
+ onFocus: () => setFocusInChrome(true),
1878
+ onBlur: (event) => {
1879
+ if (!event.currentTarget.contains(event.relatedTarget)) {
1880
+ setFocusInChrome(false);
1881
+ }
1882
+ },
1883
+ children: [
1884
+ /* @__PURE__ */ jsxRuntime.jsx(
1885
+ Scrubber,
1886
+ {
1887
+ current,
1888
+ duration,
1889
+ buffered,
1890
+ chapters,
1891
+ markers,
1892
+ cues: captionCues,
1893
+ previewLimit: spoilerLimit,
1894
+ strings,
1895
+ onSeek: seek,
1896
+ onScrubChange: setScrubbing
1897
+ }
1898
+ ),
1899
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-iv-bar", children: [
1900
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-iv-bar-group", children: [
1901
+ /* @__PURE__ */ jsxRuntime.jsx(
1902
+ "button",
1903
+ {
1904
+ ref: playButtonRef,
1905
+ type: "button",
1906
+ className: "lk-iv-button",
1907
+ "aria-label": ended ? strings.videoReplay : playing ? strings.media.pause : strings.media.play,
1908
+ onClick: togglePlay,
1909
+ children: ended ? /* @__PURE__ */ jsxRuntime.jsx(ReplayIcon, {}) : playing ? /* @__PURE__ */ jsxRuntime.jsx(PauseIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(PlayIcon, {})
1910
+ }
1911
+ ),
1912
+ /* @__PURE__ */ jsxRuntime.jsx(
1913
+ "button",
1914
+ {
1915
+ type: "button",
1916
+ className: "lk-iv-button lk-iv-wide",
1917
+ "aria-label": strings.videoBack(JUMP_SECONDS),
1918
+ onClick: () => seek(current - JUMP_SECONDS),
1919
+ children: /* @__PURE__ */ jsxRuntime.jsx(BackIcon, {})
1920
+ }
1921
+ ),
1922
+ /* @__PURE__ */ jsxRuntime.jsx(
1923
+ "button",
1924
+ {
1925
+ type: "button",
1926
+ className: "lk-iv-button lk-iv-wide",
1927
+ "aria-label": strings.videoForward(JUMP_SECONDS),
1928
+ onClick: () => seek(current + JUMP_SECONDS),
1929
+ children: /* @__PURE__ */ jsxRuntime.jsx(ForwardIcon, {})
1930
+ }
1931
+ ),
1932
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-iv-volume", children: [
1933
+ /* @__PURE__ */ jsxRuntime.jsx(
1934
+ "button",
1935
+ {
1936
+ type: "button",
1937
+ className: "lk-iv-button",
1938
+ "aria-label": preferences.muted ? strings.media.unmute : strings.media.mute,
1939
+ "aria-pressed": preferences.muted,
1940
+ onClick: () => changePreferences({ muted: !preferences.muted }),
1941
+ children: /* @__PURE__ */ jsxRuntime.jsx(VolumeIcon, { level: volumeLevel })
1942
+ }
1943
+ ),
1944
+ /* @__PURE__ */ jsxRuntime.jsx(
1945
+ "input",
1946
+ {
1947
+ id: `${playerId}-volume`,
1948
+ className: "lk-iv-volume-slider",
1949
+ type: "range",
1950
+ min: 0,
1951
+ max: 1,
1952
+ step: 0.05,
1953
+ value: preferences.muted ? 0 : preferences.volume,
1954
+ "aria-label": strings.media.volume,
1955
+ onChange: (event) => changePreferences({ volume: Number(event.target.value), muted: false })
1956
+ }
1957
+ )
1958
+ ] }),
1959
+ /* @__PURE__ */ jsxRuntime.jsx(
1960
+ "button",
1961
+ {
1962
+ type: "button",
1963
+ className: "lk-iv-time",
1964
+ "aria-label": countdown ? strings.videoShowElapsed : strings.videoShowRemaining,
1965
+ onClick: () => setCountdown((on) => !on),
1966
+ children: /* @__PURE__ */ jsxRuntime.jsxs("span", { "aria-hidden": "true", children: [
1967
+ countdown ? `-${clock(Math.max(0, duration - current))}` : clock(current),
1968
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "lk-iv-time-total", children: [
1969
+ " / ",
1970
+ clock(duration)
1971
+ ] })
1972
+ ] })
1973
+ }
1974
+ )
1975
+ ] }),
1976
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-iv-bar-group", children: [
1977
+ quizzes.length > 0 || chapters.length > 0 || hasCaptions ? /* @__PURE__ */ jsxRuntime.jsx(
1978
+ "button",
1979
+ {
1980
+ type: "button",
1981
+ className: "lk-iv-button",
1982
+ "aria-label": strings.videoPanel,
1983
+ "aria-pressed": preferences.panel,
1984
+ onClick: () => changePreferences({ panel: !preferences.panel }),
1985
+ children: /* @__PURE__ */ jsxRuntime.jsx(ContentsIcon, {})
1986
+ }
1987
+ ) : null,
1988
+ hasCaptions ? /* @__PURE__ */ jsxRuntime.jsx(
1989
+ "button",
1990
+ {
1991
+ type: "button",
1992
+ className: "lk-iv-button",
1993
+ "aria-label": preferences.captions ? strings.videoCaptionsHide : strings.videoCaptionsShow,
1994
+ "aria-pressed": preferences.captions,
1995
+ onClick: () => {
1996
+ changePreferences({ captions: !preferences.captions });
1997
+ emit("video-captions-changed", {
1998
+ srclang: preferences.captions ? null : activeTrack?.srclang ?? null
1999
+ });
2000
+ },
2001
+ children: /* @__PURE__ */ jsxRuntime.jsx(CaptionsIcon, { on: preferences.captions })
2002
+ }
2003
+ ) : null,
2004
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-iv-menu-anchor", children: [
2005
+ /* @__PURE__ */ jsxRuntime.jsx(
2006
+ "button",
2007
+ {
2008
+ type: "button",
2009
+ className: "lk-iv-button lk-iv-speed",
2010
+ "aria-label": strings.media.speed,
2011
+ "aria-haspopup": "menu",
2012
+ "aria-expanded": menu === "speed",
2013
+ onClick: () => setMenu((open) => open === "speed" ? null : "speed"),
2014
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: `${preferences.speed}\xD7` })
2015
+ }
2016
+ ),
2017
+ menu === "speed" ? /* @__PURE__ */ jsxRuntime.jsx(
2018
+ SpeedMenu,
2019
+ {
2020
+ speed: preferences.speed,
2021
+ strings,
2022
+ onChoose: (speed) => {
2023
+ changePreferences({ speed });
2024
+ setMenu(null);
2025
+ },
2026
+ onClose: () => setMenu(null)
2027
+ }
2028
+ ) : null
2029
+ ] }),
2030
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-iv-menu-anchor", children: [
2031
+ /* @__PURE__ */ jsxRuntime.jsx(
2032
+ "button",
2033
+ {
2034
+ type: "button",
2035
+ className: "lk-iv-button",
2036
+ "aria-label": strings.videoSettings,
2037
+ "aria-haspopup": "menu",
2038
+ "aria-expanded": menu === "settings",
2039
+ onClick: () => setMenu((open) => open === "settings" ? null : "settings"),
2040
+ children: /* @__PURE__ */ jsxRuntime.jsx(SettingsIcon, {})
2041
+ }
2042
+ ),
2043
+ menu === "settings" ? /* @__PURE__ */ jsxRuntime.jsx(
2044
+ SettingsMenu,
2045
+ {
2046
+ preferences,
2047
+ tracks,
2048
+ activeTrack,
2049
+ captionsFailed,
2050
+ strings,
2051
+ onChange: changePreferences,
2052
+ onShowShortcuts: () => {
2053
+ setMenu(null);
2054
+ setSheet(true);
2055
+ },
2056
+ onClose: () => setMenu(null)
2057
+ }
2058
+ ) : null
2059
+ ] }),
2060
+ pictureInPictureSupported ? /* @__PURE__ */ jsxRuntime.jsx(
2061
+ "button",
2062
+ {
2063
+ type: "button",
2064
+ className: "lk-iv-button lk-iv-wide",
2065
+ "aria-label": strings.videoPictureInPicture,
2066
+ "aria-pressed": pictureInPicture,
2067
+ onClick: togglePictureInPicture,
2068
+ children: /* @__PURE__ */ jsxRuntime.jsx(PictureInPictureIcon, {})
2069
+ }
2070
+ ) : null,
2071
+ /* @__PURE__ */ jsxRuntime.jsx(
2072
+ "button",
2073
+ {
2074
+ type: "button",
2075
+ className: "lk-iv-button",
2076
+ "aria-label": fullscreen ? strings.videoExitFullscreen : strings.videoFullscreen,
2077
+ onClick: toggleFullscreen,
2078
+ children: /* @__PURE__ */ jsxRuntime.jsx(FullscreenIcon, { on: fullscreen })
2079
+ }
2080
+ )
2081
+ ] })
2082
+ ] })
2083
+ ]
2084
+ }
2085
+ ),
2086
+ /* @__PURE__ */ jsxRuntime.jsxs(
2087
+ "div",
2088
+ {
2089
+ className: "lk-iv-quiz",
2090
+ role: "dialog",
2091
+ "aria-modal": "false",
2092
+ "aria-labelledby": `${playerId}-quiz-title`,
2093
+ hidden: openQuiz === null,
2094
+ onKeyDown: (event) => {
2095
+ if (event.key === "Escape" && !required && openQuiz !== null) {
2096
+ event.preventDefault();
2097
+ closeQuiz("skip");
2098
+ }
2099
+ },
2100
+ children: [
2101
+ openCue !== void 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-iv-quiz-head", children: [
2102
+ /* @__PURE__ */ jsxRuntime.jsxs(
2103
+ "h3",
2104
+ {
2105
+ id: `${playerId}-quiz-title`,
2106
+ ref: quizHeadingRef,
2107
+ tabIndex: -1,
2108
+ className: "lk-iv-quiz-title",
2109
+ children: [
2110
+ quizTitle(openCue),
2111
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "lk-iv-quiz-time", children: [
2112
+ " \xB7 ",
2113
+ clock(openCue.at)
2114
+ ] }),
2115
+ required ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2116
+ " ",
2117
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-iv-tag", children: strings.videoRequired })
2118
+ ] }) : null
2119
+ ]
2120
+ }
2121
+ ),
2122
+ openSlots.length > 1 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-iv-steps", children: [
2123
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-iv-steps-label", children: strings.videoQuestionProgress(step + 1, openSlots.length) }),
2124
+ /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "lk-iv-steps-dots", children: openSlots.map((slot, index) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(
2125
+ "button",
2126
+ {
2127
+ type: "button",
2128
+ className: "lk-iv-step",
2129
+ "data-current": index === step || void 0,
2130
+ "data-answered": answered.has(slot.slotId) || void 0,
2131
+ "aria-current": index === step ? "step" : void 0,
2132
+ "aria-label": strings.videoQuestionStep(
2133
+ index + 1,
2134
+ answered.has(slot.slotId)
2135
+ ),
2136
+ onClick: () => goToStep(index)
2137
+ }
2138
+ ) }, slot.slotId)) })
2139
+ ] }) : null
2140
+ ] }) : null,
2141
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lk-iv-quiz-body", children: cues.filter((cue) => mounted.has(cue.id)).map(
2142
+ (cue) => (slotsByCue.get(cue.id) ?? []).map((slot, index) => /* @__PURE__ */ jsxRuntime.jsx(
2143
+ QuestionPane,
2144
+ {
2145
+ hidden: !(openQuiz?.cueId === cue.id && step === index),
2146
+ channel: channelFor(slot.slotId),
2147
+ children: renderQuestion(slot, cue.id)
2148
+ },
2149
+ slot.slotId
2150
+ ))
2151
+ ) }),
2152
+ openCue !== void 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-iv-quiz-foot", children: [
2153
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-iv-quiz-foot-start", children: [
2154
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "lk-iv-link", onClick: () => closeQuiz("rewatch"), children: [
2155
+ /* @__PURE__ */ jsxRuntime.jsx(ReplayIcon, {}),
2156
+ strings.videoRewatch
2157
+ ] }),
2158
+ !required && !lastStep ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "lk-iv-link", onClick: () => closeQuiz("skip"), children: strings.videoSkipQuiz }) : null
2159
+ ] }),
2160
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-iv-quiz-foot-end", children: [
2161
+ renderMode === "exam" && stepAnswered ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-iv-saved", role: "status", children: strings.videoAnswerSaved }) : null,
2162
+ /* @__PURE__ */ jsxRuntime.jsxs(
2163
+ "button",
2164
+ {
2165
+ type: "button",
2166
+ className: stepAnswered || renderMode === "review" ? "lk-iv-action lk-iv-action-primary" : "lk-iv-action",
2167
+ "aria-disabled": !canAdvance || void 0,
2168
+ onClick: () => {
2169
+ if (!canAdvance) {
2170
+ return;
2171
+ }
2172
+ if (lastStep) {
2173
+ closeQuiz("continue");
2174
+ } else {
2175
+ goToStep(step + 1);
2176
+ }
2177
+ },
2178
+ children: [
2179
+ lastStep ? strings.videoContinue : strings.videoNextQuestion,
2180
+ lastStep ? /* @__PURE__ */ jsxRuntime.jsx(PlayIcon, {}) : null
2181
+ ]
2182
+ }
2183
+ )
2184
+ ] })
2185
+ ] }) : null
2186
+ ]
2187
+ }
2188
+ ),
2189
+ ended && openQuiz === null ? /* @__PURE__ */ jsxRuntime.jsx(
2190
+ EndScreen,
2191
+ {
2192
+ idPrefix: playerId,
2193
+ renderMode,
2194
+ quizzes,
2195
+ answered: answeredCount,
2196
+ total: totalQuestions,
2197
+ percent,
2198
+ strings,
2199
+ onOpenQuiz: (id) => {
2200
+ const cue = cues.find((candidate) => candidate.id === id);
2201
+ if (cue !== void 0) {
2202
+ openAt(cue);
2203
+ }
2204
+ },
2205
+ onWatchAgain: togglePlay,
2206
+ onFinish: renderMode === "review" ? void 0 : finish
2207
+ }
2208
+ ) : null,
2209
+ sheet ? /* @__PURE__ */ jsxRuntime.jsx(
2210
+ ShortcutSheet,
2211
+ {
2212
+ strings,
2213
+ onClose: () => {
2214
+ setSheet(false);
2215
+ playButtonRef.current?.focus();
2216
+ }
2217
+ }
2218
+ ) : null
2219
+ ]
2220
+ }
2221
+ ),
2222
+ preferences.panel ? /* @__PURE__ */ jsxRuntime.jsx(
2223
+ ContentsPanel,
2224
+ {
2225
+ idPrefix: playerId,
2226
+ tab: panelTab,
2227
+ onTab: setPanelTab,
2228
+ chapters,
2229
+ quizzes,
2230
+ cues: captionCues,
2231
+ activeCue: activeCueIndex,
2232
+ limit: spoilerLimit,
2233
+ strings,
2234
+ onSeek: seek,
2235
+ onOpenQuiz: (id) => {
2236
+ const cue = cues.find((candidate) => candidate.id === id);
2237
+ if (cue === void 0) {
2238
+ return;
2239
+ }
2240
+ if (renderMode !== "review" && limitSeek(
2241
+ timeline,
2242
+ videoRef.current?.currentTime ?? 0,
2243
+ cue.at,
2244
+ furthest.current,
2245
+ finished
2246
+ ).at < cue.at - 0.25) {
2247
+ seek(cue.at);
2248
+ return;
2249
+ }
2250
+ openAt(cue);
2251
+ }
2252
+ }
2253
+ ) : null,
2254
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lk-iv-live", "aria-live": "polite", "aria-atomic": "true", children: announcement })
2255
+ ]
2256
+ }
2257
+ )
2258
+ );
2259
+ }
2260
+ function QuestionPane({
2261
+ hidden,
2262
+ channel,
2263
+ children
2264
+ }) {
2265
+ const ref = react.useRef(null);
2266
+ const captureGroup = channel.captureGroup;
2267
+ react.useEffect(() => {
2268
+ if (!hidden) {
2269
+ return;
2270
+ }
2271
+ chunkZH5WT2IE_cjs.stopCaptureGroup(captureGroup);
2272
+ for (const media of ref.current?.querySelectorAll("audio, video") ?? []) {
2273
+ const element = media;
2274
+ if (!element.paused) {
2275
+ element.pause();
2276
+ }
2277
+ }
2278
+ }, [hidden, captureGroup]);
2279
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: "lk-iv-question", hidden, children: /* @__PURE__ */ jsxRuntime.jsx(chunkZH5WT2IE_cjs.SequenceSlotContext.Provider, { value: channel, children }) });
2280
+ }
2281
+ function bufferedAhead(video) {
2282
+ const ranges = video.buffered;
2283
+ if (ranges === void 0) {
2284
+ return 0;
2285
+ }
2286
+ for (let index = 0; index < ranges.length; index += 1) {
2287
+ if (ranges.start(index) <= video.currentTime && video.currentTime <= ranges.end(index)) {
2288
+ return ranges.end(index);
2289
+ }
2290
+ }
2291
+ return 0;
2292
+ }
2293
+ function markerState(quizSlots, answered, renderMode, outcomes) {
2294
+ if (renderMode === "review" && outcomes !== void 0) {
2295
+ const results = quizSlots.map(
2296
+ (slot) => Object.hasOwn(outcomes, slot.slotId) ? outcomes[slot.slotId] : void 0
2297
+ );
2298
+ if (results.some((outcome) => outcome === void 0 || outcome.status === "deferred")) {
2299
+ return "pending";
2300
+ }
2301
+ const scored = results.filter(
2302
+ (outcome) => outcome?.status === "scored"
2303
+ );
2304
+ if (scored.length === results.length && scored.every((outcome) => outcome.passed)) {
2305
+ return "correct";
2306
+ }
2307
+ return scored.some((outcome) => outcome.passed) ? "partial" : "incorrect";
2308
+ }
2309
+ const count = quizSlots.filter((slot) => answered.has(slot.slotId)).length;
2310
+ if (count === quizSlots.length && count > 0) {
2311
+ return "answered";
2312
+ }
2313
+ if (count > 0) {
2314
+ return "open";
2315
+ }
2316
+ return "unreached";
2317
+ }
2318
+ function InteractiveVideo2(props) {
2319
+ const strings = chunkZ6TVOAO5_cjs.useLkStrings(props.strings);
2320
+ return /* @__PURE__ */ jsxRuntime.jsx(
2321
+ chunk53YOZOGN_cjs.ActivityErrorBoundary,
2322
+ {
2323
+ ...props.group?.title !== void 0 ? { activityTitle: props.group.title } : {},
2324
+ strings,
2325
+ children: /* @__PURE__ */ jsxRuntime.jsx(InteractiveVideo, { ...props })
2326
+ }
2327
+ );
2328
+ }
2329
+
2330
+ exports.InteractiveVideo = InteractiveVideo2;
2331
+ //# sourceMappingURL=chunk-SRD4KVAP.cjs.map
2332
+ //# sourceMappingURL=chunk-SRD4KVAP.cjs.map