coursecode 0.1.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 (362) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +322 -0
  3. package/THIRD_PARTY_NOTICES.md +22 -0
  4. package/bin/cli.js +331 -0
  5. package/framework/assets/logo-coursecode-black.svg +14 -0
  6. package/framework/assets/logo-coursecode-white.svg +14 -0
  7. package/framework/assets/logo-coursecode.svg +14 -0
  8. package/framework/css/01-base.css +160 -0
  9. package/framework/css/02-layout.css +499 -0
  10. package/framework/css/accessibility.css +834 -0
  11. package/framework/css/components/accordions.css +710 -0
  12. package/framework/css/components/assessments.css +520 -0
  13. package/framework/css/components/audio-player.css +570 -0
  14. package/framework/css/components/badges.css +80 -0
  15. package/framework/css/components/breadcrumbs.css +87 -0
  16. package/framework/css/components/buttons.css +707 -0
  17. package/framework/css/components/callouts.css +1280 -0
  18. package/framework/css/components/cards.css +475 -0
  19. package/framework/css/components/carousel.css +193 -0
  20. package/framework/css/components/checkbox-group.css +123 -0
  21. package/framework/css/components/checklist.css +203 -0
  22. package/framework/css/components/collapse.css +96 -0
  23. package/framework/css/components/comparison.css +33 -0
  24. package/framework/css/components/content-image.css +36 -0
  25. package/framework/css/components/document-gallery.css +425 -0
  26. package/framework/css/components/dropdown.css +115 -0
  27. package/framework/css/components/embed-frame.css +142 -0
  28. package/framework/css/components/engagement.css +412 -0
  29. package/framework/css/components/features.css +35 -0
  30. package/framework/css/components/flip-cards.css +253 -0
  31. package/framework/css/components/footer.css +353 -0
  32. package/framework/css/components/forms.css +294 -0
  33. package/framework/css/components/hero.css +216 -0
  34. package/framework/css/components/images.css +528 -0
  35. package/framework/css/components/interactive-timeline.css +274 -0
  36. package/framework/css/components/intro-cards.css +30 -0
  37. package/framework/css/components/lightbox.css +666 -0
  38. package/framework/css/components/loading.css +65 -0
  39. package/framework/css/components/modals.css +235 -0
  40. package/framework/css/components/notifications.css +107 -0
  41. package/framework/css/components/quote.css +150 -0
  42. package/framework/css/components/sidebar.css +684 -0
  43. package/framework/css/components/slide-header.css +52 -0
  44. package/framework/css/components/spinner.css +62 -0
  45. package/framework/css/components/stats.css +44 -0
  46. package/framework/css/components/steps.css +232 -0
  47. package/framework/css/components/tables.css +90 -0
  48. package/framework/css/components/tabs.css +347 -0
  49. package/framework/css/components/timeline.css +154 -0
  50. package/framework/css/components/toggle.css +95 -0
  51. package/framework/css/components/tooltip.css +226 -0
  52. package/framework/css/components/video-player.css +438 -0
  53. package/framework/css/design-tokens.css +707 -0
  54. package/framework/css/framework.css +86 -0
  55. package/framework/css/interactions/accessibility.css +75 -0
  56. package/framework/css/interactions/base.css +92 -0
  57. package/framework/css/interactions/drag-drop.css +295 -0
  58. package/framework/css/interactions/fill-in-the-blank.css +236 -0
  59. package/framework/css/interactions/hotspots.css +69 -0
  60. package/framework/css/interactions/index.css +45 -0
  61. package/framework/css/interactions/interactive-image.css +359 -0
  62. package/framework/css/interactions/likert.css +126 -0
  63. package/framework/css/interactions/matching.css +354 -0
  64. package/framework/css/interactions/numeric-input.css +78 -0
  65. package/framework/css/interactions/sequencing.css +378 -0
  66. package/framework/css/interactions/true-false.css +177 -0
  67. package/framework/css/layouts/article.css +258 -0
  68. package/framework/css/layouts/base.css +30 -0
  69. package/framework/css/layouts/canvas.css +38 -0
  70. package/framework/css/layouts/focused.css +236 -0
  71. package/framework/css/layouts/index.css +29 -0
  72. package/framework/css/layouts/presentation.css +191 -0
  73. package/framework/css/layouts/traditional.css +52 -0
  74. package/framework/css/responsive.css +439 -0
  75. package/framework/css/utilities/accessibility-utils.css +59 -0
  76. package/framework/css/utilities/animations.css +419 -0
  77. package/framework/css/utilities/borders.css +72 -0
  78. package/framework/css/utilities/colors.css +76 -0
  79. package/framework/css/utilities/container.css +46 -0
  80. package/framework/css/utilities/decorative.css +442 -0
  81. package/framework/css/utilities/display.css +257 -0
  82. package/framework/css/utilities/flexbox.css +80 -0
  83. package/framework/css/utilities/grid.css +69 -0
  84. package/framework/css/utilities/icons.css +534 -0
  85. package/framework/css/utilities/lists.css +190 -0
  86. package/framework/css/utilities/spacing.css +167 -0
  87. package/framework/css/utilities/tables.css +81 -0
  88. package/framework/css/utilities/typography.css +159 -0
  89. package/framework/css/utilities/visibility.css +117 -0
  90. package/framework/docs/COURSE_AUTHORING_GUIDE.md +1773 -0
  91. package/framework/docs/COURSE_OUTLINE_GUIDE.md +725 -0
  92. package/framework/docs/COURSE_OUTLINE_TEMPLATE.md +161 -0
  93. package/framework/docs/DATA_MODEL.md +409 -0
  94. package/framework/docs/FRAMEWORK_GUIDE.md +1088 -0
  95. package/framework/docs/USER_GUIDE.md +583 -0
  96. package/framework/docs/examples/cloudflare-channel-relay.js +169 -0
  97. package/framework/docs/examples/cloudflare-data-worker.js +102 -0
  98. package/framework/docs/examples/cloudflare-error-worker.js +228 -0
  99. package/framework/index.html +175 -0
  100. package/framework/js/app/AppActions.js +410 -0
  101. package/framework/js/app/AppState.js +225 -0
  102. package/framework/js/app/AppUI.js +616 -0
  103. package/framework/js/assessment/AssessmentActions.js +615 -0
  104. package/framework/js/assessment/AssessmentFactory.js +471 -0
  105. package/framework/js/assessment/AssessmentState.js +322 -0
  106. package/framework/js/assessment/AssessmentUI.js +451 -0
  107. package/framework/js/automation/api-engagement.js +196 -0
  108. package/framework/js/automation/api-interactions.js +167 -0
  109. package/framework/js/automation/api.js +242 -0
  110. package/framework/js/automation/index.js +41 -0
  111. package/framework/js/components/interactions/drag-drop.js +884 -0
  112. package/framework/js/components/interactions/fill-in.js +535 -0
  113. package/framework/js/components/interactions/hotspot.js +702 -0
  114. package/framework/js/components/interactions/interaction-base.js +511 -0
  115. package/framework/js/components/interactions/likert.js +301 -0
  116. package/framework/js/components/interactions/matching.js +699 -0
  117. package/framework/js/components/interactions/multiple-choice.js +377 -0
  118. package/framework/js/components/interactions/numeric.js +271 -0
  119. package/framework/js/components/interactions/sequencing.js +423 -0
  120. package/framework/js/components/interactions/true-false.js +241 -0
  121. package/framework/js/components/ui-components/accordion.js +442 -0
  122. package/framework/js/components/ui-components/alert.js +88 -0
  123. package/framework/js/components/ui-components/audio-player.js +1193 -0
  124. package/framework/js/components/ui-components/callout.js +121 -0
  125. package/framework/js/components/ui-components/carousel.js +145 -0
  126. package/framework/js/components/ui-components/checkbox-group.js +87 -0
  127. package/framework/js/components/ui-components/checklist.js +40 -0
  128. package/framework/js/components/ui-components/collapse.js +114 -0
  129. package/framework/js/components/ui-components/comparison.js +30 -0
  130. package/framework/js/components/ui-components/conditional-display.js +150 -0
  131. package/framework/js/components/ui-components/content-image.js +41 -0
  132. package/framework/js/components/ui-components/dropdown.js +262 -0
  133. package/framework/js/components/ui-components/embed-frame.js +274 -0
  134. package/framework/js/components/ui-components/features.js +33 -0
  135. package/framework/js/components/ui-components/flip-card.js +230 -0
  136. package/framework/js/components/ui-components/form-validator.js +76 -0
  137. package/framework/js/components/ui-components/hero.js +49 -0
  138. package/framework/js/components/ui-components/index.js +12 -0
  139. package/framework/js/components/ui-components/interactive-image.js +235 -0
  140. package/framework/js/components/ui-components/interactive-timeline.js +285 -0
  141. package/framework/js/components/ui-components/intro-cards.js +35 -0
  142. package/framework/js/components/ui-components/lightbox.js +652 -0
  143. package/framework/js/components/ui-components/modal.js +386 -0
  144. package/framework/js/components/ui-components/notifications.js +145 -0
  145. package/framework/js/components/ui-components/progress.js +88 -0
  146. package/framework/js/components/ui-components/quote.js +41 -0
  147. package/framework/js/components/ui-components/stats.js +33 -0
  148. package/framework/js/components/ui-components/steps.js +41 -0
  149. package/framework/js/components/ui-components/tabs.js +255 -0
  150. package/framework/js/components/ui-components/timeline.js +42 -0
  151. package/framework/js/components/ui-components/toggle-group.js +73 -0
  152. package/framework/js/components/ui-components/tooltip.js +458 -0
  153. package/framework/js/components/ui-components/value-display.js +133 -0
  154. package/framework/js/components/ui-components/video-player.js +686 -0
  155. package/framework/js/core/component-catalog.js +121 -0
  156. package/framework/js/core/event-bus.js +178 -0
  157. package/framework/js/core/interaction-catalog.js +149 -0
  158. package/framework/js/dev/runtime-linter.js +1725 -0
  159. package/framework/js/drivers/cmi5-driver.js +768 -0
  160. package/framework/js/drivers/driver-factory.js +77 -0
  161. package/framework/js/drivers/driver-interface.js +110 -0
  162. package/framework/js/drivers/http-driver-base.js +241 -0
  163. package/framework/js/drivers/lti-driver.js +508 -0
  164. package/framework/js/drivers/proxy-driver.js +444 -0
  165. package/framework/js/drivers/scorm-12-driver.js +560 -0
  166. package/framework/js/drivers/scorm-2004-driver.js +775 -0
  167. package/framework/js/drivers/scorm-driver-base.js +112 -0
  168. package/framework/js/engagement/engagement-manager.js +404 -0
  169. package/framework/js/engagement/engagement-progress.js +191 -0
  170. package/framework/js/engagement/engagement-trackers.js +215 -0
  171. package/framework/js/engagement/requirement-strategies.js +268 -0
  172. package/framework/js/main.js +727 -0
  173. package/framework/js/managers/accessibility-manager.js +499 -0
  174. package/framework/js/managers/assessment-manager.js +230 -0
  175. package/framework/js/managers/audio-manager.js +944 -0
  176. package/framework/js/managers/comment-manager.js +88 -0
  177. package/framework/js/managers/flag-manager.js +86 -0
  178. package/framework/js/managers/interaction-manager.js +254 -0
  179. package/framework/js/managers/interaction-registry.js +96 -0
  180. package/framework/js/managers/objective-manager.js +423 -0
  181. package/framework/js/managers/score-manager.js +441 -0
  182. package/framework/js/managers/video-manager.js +536 -0
  183. package/framework/js/navigation/Breadcrumbs.js +234 -0
  184. package/framework/js/navigation/NavigationActions.js +1132 -0
  185. package/framework/js/navigation/NavigationState.js +276 -0
  186. package/framework/js/navigation/NavigationUI.js +574 -0
  187. package/framework/js/navigation/document-gallery.js +357 -0
  188. package/framework/js/navigation/navigation-helpers.js +175 -0
  189. package/framework/js/navigation/navigation-validators.js +174 -0
  190. package/framework/js/state/index.js +8 -0
  191. package/framework/js/state/lms-connection.js +482 -0
  192. package/framework/js/state/lms-error-utils.js +58 -0
  193. package/framework/js/state/state-commits.js +200 -0
  194. package/framework/js/state/state-domains.js +86 -0
  195. package/framework/js/state/state-manager.js +502 -0
  196. package/framework/js/state/state-validation.js +311 -0
  197. package/framework/js/state/transaction-log.js +41 -0
  198. package/framework/js/state/xapi-statement-service.js +325 -0
  199. package/framework/js/utilities/access-control.js +99 -0
  200. package/framework/js/utilities/breakpoint-manager.js +315 -0
  201. package/framework/js/utilities/canvas-slide.js +35 -0
  202. package/framework/js/utilities/conditional-display.js +388 -0
  203. package/framework/js/utilities/course-channel.js +214 -0
  204. package/framework/js/utilities/course-helpers.js +420 -0
  205. package/framework/js/utilities/data-reporter.js +273 -0
  206. package/framework/js/utilities/error-reporter.js +313 -0
  207. package/framework/js/utilities/hotspot-helper.js +341 -0
  208. package/framework/js/utilities/icons.js +348 -0
  209. package/framework/js/utilities/logger.js +92 -0
  210. package/framework/js/utilities/markdown-renderer.js +45 -0
  211. package/framework/js/utilities/scroll-tracker.js +68 -0
  212. package/framework/js/utilities/ui-initializer.js +146 -0
  213. package/framework/js/utilities/utilities.js +293 -0
  214. package/framework/js/utilities/view-manager.js +227 -0
  215. package/framework/js/validation/html-validators.js +422 -0
  216. package/framework/js/validation/scorm-validators.js +438 -0
  217. package/framework/js/vendor/pipwerks.js +931 -0
  218. package/framework/scripts/generate-narration.js +629 -0
  219. package/framework/scripts/tts-providers/azure-provider.js +178 -0
  220. package/framework/scripts/tts-providers/base-provider.js +81 -0
  221. package/framework/scripts/tts-providers/deepgram-provider.js +135 -0
  222. package/framework/scripts/tts-providers/elevenlabs-provider.js +148 -0
  223. package/framework/scripts/tts-providers/google-provider.js +272 -0
  224. package/framework/scripts/tts-providers/index.js +158 -0
  225. package/framework/scripts/tts-providers/openai-provider.js +143 -0
  226. package/framework/version.json +63 -0
  227. package/lib/authoring-api.js +919 -0
  228. package/lib/build-linter.js +450 -0
  229. package/lib/build-packaging.js +186 -0
  230. package/lib/build.js +88 -0
  231. package/lib/cloud.js +691 -0
  232. package/lib/convert.js +341 -0
  233. package/lib/course-parser.js +936 -0
  234. package/lib/course-writer.js +258 -0
  235. package/lib/create.js +248 -0
  236. package/lib/css-index.js +237 -0
  237. package/lib/dev.js +51 -0
  238. package/lib/export-content.js +1246 -0
  239. package/lib/headless-browser.js +413 -0
  240. package/lib/import.js +377 -0
  241. package/lib/index.js +80 -0
  242. package/lib/info.js +79 -0
  243. package/lib/interaction-formatters.js +568 -0
  244. package/lib/manifest/cmi5-manifest.js +63 -0
  245. package/lib/manifest/lti-tool-config.js +53 -0
  246. package/lib/manifest/manifest-factory.js +99 -0
  247. package/lib/manifest/scorm-12-manifest.js +61 -0
  248. package/lib/manifest/scorm-2004-manifest.js +94 -0
  249. package/lib/manifest/scorm-proxy-manifest.js +104 -0
  250. package/lib/manifest-parser.js +96 -0
  251. package/lib/mcp-prompts.js +753 -0
  252. package/lib/mcp-server.js +316 -0
  253. package/lib/narration.js +53 -0
  254. package/lib/pdf-structure.js +142 -0
  255. package/lib/preview-export.js +231 -0
  256. package/lib/preview-routes-api.js +662 -0
  257. package/lib/preview-routes-editing.js +159 -0
  258. package/lib/preview-routes-lms.js +230 -0
  259. package/lib/preview-server.js +564 -0
  260. package/lib/project-utils.js +269 -0
  261. package/lib/proxy-templates/proxy.html +68 -0
  262. package/lib/proxy-templates/scorm-bridge.js +112 -0
  263. package/lib/scaffold.js +193 -0
  264. package/lib/schema-extractor.js +361 -0
  265. package/lib/slide-source-editor.js +586 -0
  266. package/lib/stub-player/app-viewer.js +195 -0
  267. package/lib/stub-player/app.js +370 -0
  268. package/lib/stub-player/catalog-panel.js +312 -0
  269. package/lib/stub-player/config-panel.js +1303 -0
  270. package/lib/stub-player/content-generator.js +586 -0
  271. package/lib/stub-player/content-viewer.js +173 -0
  272. package/lib/stub-player/debug-panel.js +420 -0
  273. package/lib/stub-player/edit-mode.js +922 -0
  274. package/lib/stub-player/edit-utils.js +400 -0
  275. package/lib/stub-player/header-bar.js +354 -0
  276. package/lib/stub-player/interaction-editor.js +210 -0
  277. package/lib/stub-player/interactions-panel.js +565 -0
  278. package/lib/stub-player/lms-api.js +1094 -0
  279. package/lib/stub-player/login-screen.js +74 -0
  280. package/lib/stub-player/outline-mode.js +689 -0
  281. package/lib/stub-player/styles/_assessments-panel.css +245 -0
  282. package/lib/stub-player/styles/_base.css +89 -0
  283. package/lib/stub-player/styles/_catalog-icons.css +96 -0
  284. package/lib/stub-player/styles/_catalog-panel.css +291 -0
  285. package/lib/stub-player/styles/_config-panel.css +636 -0
  286. package/lib/stub-player/styles/_content-viewer.css +834 -0
  287. package/lib/stub-player/styles/_debug-panel.css +576 -0
  288. package/lib/stub-player/styles/_edit-mode.css +128 -0
  289. package/lib/stub-player/styles/_header-bar.css +343 -0
  290. package/lib/stub-player/styles/_interaction-editor.css +140 -0
  291. package/lib/stub-player/styles/_interactions-panel.css +1038 -0
  292. package/lib/stub-player/styles/_login-screen.css +102 -0
  293. package/lib/stub-player/styles/_outline-mode.css +752 -0
  294. package/lib/stub-player/styles.css +15 -0
  295. package/lib/stub-player.js +160 -0
  296. package/lib/test-data-reporting.js +176 -0
  297. package/lib/test-error-reporting.js +146 -0
  298. package/lib/token.js +86 -0
  299. package/lib/upgrade.js +257 -0
  300. package/lib/validation-rules.js +517 -0
  301. package/lib/vite-plugin-content-discovery.js +296 -0
  302. package/package.json +108 -0
  303. package/schemas/XMLSchema.dtd +402 -0
  304. package/schemas/adlcp_v1p3.xsd +111 -0
  305. package/schemas/adlnav_v1p3.xsd +61 -0
  306. package/schemas/adlseq_v1p3.xsd +93 -0
  307. package/schemas/common/anyElement.xsd +27 -0
  308. package/schemas/common/dataTypes.xsd +138 -0
  309. package/schemas/common/elementNames.xsd +767 -0
  310. package/schemas/common/elementTypes.xsd +786 -0
  311. package/schemas/common/rootElement.xsd +31 -0
  312. package/schemas/common/vocabTypes.xsd +345 -0
  313. package/schemas/common/vocabValues.xsd +257 -0
  314. package/schemas/datatypes.dtd +203 -0
  315. package/schemas/ims_xml.xsd +35 -0
  316. package/schemas/imscp_v1p1.xsd +368 -0
  317. package/schemas/imsss_v1p0.xsd +67 -0
  318. package/schemas/imsss_v1p0auxresource.xsd +19 -0
  319. package/schemas/imsss_v1p0control.xsd +20 -0
  320. package/schemas/imsss_v1p0delivery.xsd +17 -0
  321. package/schemas/imsss_v1p0limit.xsd +47 -0
  322. package/schemas/imsss_v1p0objective.xsd +67 -0
  323. package/schemas/imsss_v1p0random.xsd +16 -0
  324. package/schemas/imsss_v1p0rollup.xsd +46 -0
  325. package/schemas/imsss_v1p0seqrule.xsd +108 -0
  326. package/schemas/imsss_v1p0util.xsd +94 -0
  327. package/schemas/license.txt +17 -0
  328. package/schemas/lom.xsd +102 -0
  329. package/schemas/lomCustom.xsd +62 -0
  330. package/schemas/lomLoose.xsd +62 -0
  331. package/schemas/lomStrict.xsd +62 -0
  332. package/schemas/xml.xsd +81 -0
  333. package/template/.env.example +92 -0
  334. package/template/course/assets/audio/example-intro.mp3 +0 -0
  335. package/template/course/assets/audio/example-ui-demo--compact-player.mp3 +0 -0
  336. package/template/course/assets/audio/example-ui-demo--demo-modal.mp3 +0 -0
  337. package/template/course/assets/audio/example-ui-demo--full-player.mp3 +0 -0
  338. package/template/course/assets/docs/example_md_1.md +39 -0
  339. package/template/course/assets/docs/example_md_2.md +41 -0
  340. package/template/course/assets/docs/example_pdf_1_thumbnail.png +0 -0
  341. package/template/course/assets/docs/example_pdf_2.pdf +0 -0
  342. package/template/course/assets/images/course-architecture.svg +36 -0
  343. package/template/course/assets/images/logo.svg +14 -0
  344. package/template/course/assets/widgets/counter-demo.html +190 -0
  345. package/template/course/assets/widgets/gravity-painter.html +384 -0
  346. package/template/course/course-config.js +539 -0
  347. package/template/course/icons.js +19 -0
  348. package/template/course/interactions/PLUGIN_GUIDE.md +97 -0
  349. package/template/course/slides/example-course-structure.js +138 -0
  350. package/template/course/slides/example-final-exam.js +144 -0
  351. package/template/course/slides/example-finishing.js +127 -0
  352. package/template/course/slides/example-interactions-showcase.js +615 -0
  353. package/template/course/slides/example-preview-tour.js +129 -0
  354. package/template/course/slides/example-remedial.js +143 -0
  355. package/template/course/slides/example-summary.js +103 -0
  356. package/template/course/slides/example-ui-showcase.js +1805 -0
  357. package/template/course/slides/example-welcome.js +123 -0
  358. package/template/course/slides/example-workflow.js +140 -0
  359. package/template/course/theme.css +165 -0
  360. package/template/eslint.config.js +47 -0
  361. package/template/package.json +28 -0
  362. package/template/vite.config.js +339 -0
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Access Control - validates client tokens for multi-tenant CDN hosting
3
+ *
4
+ * Checks URL params (clientId, token) against course config.
5
+ * Used by external hosting modes (scorm*-proxy, cmi5-remote).
6
+ */
7
+
8
+ import { courseConfig } from '../../../course/course-config.js';
9
+
10
+ /**
11
+ * Constant-time string comparison to prevent timing side-channel attacks.
12
+ * Returns false for mismatched lengths without leaking which byte differs.
13
+ * @param {string} a
14
+ * @param {string} b
15
+ * @returns {boolean}
16
+ */
17
+ function timingSafeEqual(a, b) {
18
+ if (typeof a !== 'string' || typeof b !== 'string') return false;
19
+ if (a.length !== b.length) return false;
20
+ let mismatch = 0;
21
+ for (let i = 0; i < a.length; i++) {
22
+ mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
23
+ }
24
+ return mismatch === 0;
25
+ }
26
+
27
+ /**
28
+ * Validate access based on URL token
29
+ * @returns {{ valid: boolean, clientId: string | null, error: string | null }}
30
+ */
31
+ export function validateAccess() {
32
+ const accessControl = courseConfig.accessControl;
33
+
34
+ // If no access control clients configured, allow all
35
+ if (!accessControl?.clients) {
36
+ return { valid: true, clientId: null, error: null };
37
+ }
38
+
39
+ const params = new URLSearchParams(window.location.search);
40
+ const clientId = params.get('clientId');
41
+ const token = params.get('token');
42
+
43
+ // Missing credentials
44
+ if (!clientId || !token) {
45
+ return {
46
+ valid: false,
47
+ clientId: null,
48
+ error: 'Missing clientId or token'
49
+ };
50
+ }
51
+
52
+ // Unknown client
53
+ const client = accessControl.clients?.[clientId];
54
+ if (!client) {
55
+ return {
56
+ valid: false,
57
+ clientId,
58
+ error: `Unknown client: ${clientId}`
59
+ };
60
+ }
61
+
62
+ // Invalid token (constant-time comparison)
63
+ if (!timingSafeEqual(client.token, token)) {
64
+ return {
65
+ valid: false,
66
+ clientId,
67
+ error: 'Invalid token'
68
+ };
69
+ }
70
+
71
+ // Success
72
+ return { valid: true, clientId, error: null };
73
+ }
74
+
75
+ /**
76
+ * Show unauthorized screen and halt initialization
77
+ * @param {string} error - Error message to display
78
+ */
79
+ export function showUnauthorizedScreen(_error) {
80
+ document.body.innerHTML = `
81
+ <div style="
82
+ display: flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+ min-height: 100vh;
86
+ font-family: system-ui, sans-serif;
87
+ background: #1a1a2e;
88
+ color: #fff;
89
+ ">
90
+ <div style="text-align: center; padding: 2rem;">
91
+ <div style="font-size: 4rem; margin-bottom: 1rem;">🔒</div>
92
+ <h1 style="margin: 0 0 0.5rem; font-size: 1.5rem;">Access Denied</h1>
93
+ <p style="margin: 0; opacity: 0.7; font-size: 0.9rem;">
94
+ This course is not authorized for this deployment.
95
+ </p>
96
+ </div>
97
+ </div>
98
+ `;
99
+ }
@@ -0,0 +1,315 @@
1
+ /**
2
+ * Breakpoint Manager - Responsive breakpoint detection and class management
3
+ * ============================================================================
4
+ *
5
+ * PURPOSE: Dynamically applies breakpoint CSS classes to the <html> element
6
+ * based on viewport width, enabling responsive styles throughout the framework.
7
+ *
8
+ * BREAKPOINTS (matching design-tokens.css):
9
+ * - Large Desktop: >= 1440px → adds .bp-min-large-desktop
10
+ * - Desktop: < 1440px → adds .bp-max-desktop
11
+ * - Tablet Land: < 1200px → adds .bp-max-tablet-landscape
12
+ * - Tablet Port: < 1024px → adds .bp-max-tablet-portrait
13
+ * - Mobile Land: < 768px → adds .bp-max-mobile-landscape
14
+ * - Mobile Port: < 480px → adds .bp-max-mobile-portrait
15
+ *
16
+ * USAGE:
17
+ * import { breakpointManager } from './utilities/breakpoint-manager.js';
18
+ *
19
+ * // Initialize (called automatically in main.js)
20
+ * breakpointManager.init();
21
+ *
22
+ * // Get current breakpoint name
23
+ * const bp = breakpointManager.getCurrentBreakpoint(); // e.g., 'tablet-portrait'
24
+ *
25
+ * // Check if at or below a breakpoint
26
+ * if (breakpointManager.isAtMost('tablet-portrait')) { ... }
27
+ *
28
+ * // Listen for breakpoint changes
29
+ * breakpointManager.onChange((newBreakpoint, oldBreakpoint) => {
30
+ * console.log(`Changed from ${oldBreakpoint} to ${newBreakpoint}`);
31
+ * });
32
+ *
33
+ * DEPENDENCIES:
34
+ * - Requires: design-tokens.css breakpoint values
35
+ * - Used by: responsive.css (applies styles based on .bp-* classes)
36
+ *
37
+ * LMS COMPATIBILITY:
38
+ * - Works reliably in iframe contexts (Litmos, Cornerstone, etc.)
39
+ * - Uses standard DOM APIs (window.innerWidth, resize event)
40
+ * - Passive event listener for performance
41
+ *
42
+ * LAST UPDATED: 2024-12-10
43
+ * ============================================================================
44
+ */
45
+
46
+ import { logger } from './logger.js';
47
+
48
+ /**
49
+ * Breakpoint definitions ordered from largest to smallest
50
+ * Order matters for determining the "current" breakpoint
51
+ */
52
+ const BREAKPOINTS = [
53
+ { name: 'large-desktop', minWidth: 1440, className: 'bp-min-large-desktop' },
54
+ { name: 'desktop', maxWidth: 1439, className: 'bp-max-desktop' },
55
+ { name: 'tablet-landscape', maxWidth: 1199, className: 'bp-max-tablet-landscape' },
56
+ { name: 'tablet-portrait', maxWidth: 1023, className: 'bp-max-tablet-portrait' },
57
+ { name: 'mobile-landscape', maxWidth: 767, className: 'bp-max-mobile-landscape' },
58
+ { name: 'mobile-portrait', maxWidth: 479, className: 'bp-max-mobile-portrait' }
59
+ ];
60
+
61
+ /**
62
+ * All possible breakpoint class names for easy removal
63
+ */
64
+ const ALL_BREAKPOINT_CLASSES = BREAKPOINTS.map(bp => bp.className);
65
+
66
+ /**
67
+ * Breakpoint Manager singleton
68
+ */
69
+ class BreakpointManager {
70
+ constructor() {
71
+ this._initialized = false;
72
+ this._currentBreakpoint = null;
73
+ this._listeners = [];
74
+ this._resizeTimeout = null;
75
+
76
+ // Bind methods for event listener
77
+ this._handleResize = this._handleResize.bind(this);
78
+ }
79
+
80
+ /**
81
+ * Initialize the breakpoint manager
82
+ * Sets up resize listener and applies initial breakpoint classes
83
+ */
84
+ init() {
85
+ if (this._initialized) {
86
+ logger.warn('[BreakpointManager] Already initialized');
87
+ return;
88
+ }
89
+
90
+ // Apply initial breakpoint classes
91
+ this._updateBreakpoints();
92
+
93
+ // Listen for resize with debouncing for performance
94
+ window.addEventListener('resize', this._handleResize, { passive: true });
95
+
96
+ this._initialized = true;
97
+ logger.debug('[BreakpointManager] Initialized, current breakpoint:', this._currentBreakpoint);
98
+ }
99
+
100
+ /**
101
+ * Clean up event listeners (useful for testing or cleanup)
102
+ */
103
+ destroy() {
104
+ if (!this._initialized) return;
105
+
106
+ window.removeEventListener('resize', this._handleResize);
107
+
108
+ if (this._resizeTimeout) {
109
+ clearTimeout(this._resizeTimeout);
110
+ }
111
+
112
+ // Remove all breakpoint classes
113
+ const html = document.documentElement;
114
+ html.classList.remove(...ALL_BREAKPOINT_CLASSES);
115
+
116
+ this._listeners = [];
117
+ this._initialized = false;
118
+ this._currentBreakpoint = null;
119
+
120
+ logger.debug('[BreakpointManager] Destroyed');
121
+ }
122
+
123
+ /**
124
+ * Handle resize events with debouncing
125
+ * @private
126
+ */
127
+ _handleResize() {
128
+ // Debounce resize events (16ms ≈ 60fps)
129
+ if (this._resizeTimeout) {
130
+ clearTimeout(this._resizeTimeout);
131
+ }
132
+
133
+ this._resizeTimeout = setTimeout(() => {
134
+ this._updateBreakpoints();
135
+ }, 16);
136
+ }
137
+
138
+ /**
139
+ * Update breakpoint classes on the <html> element
140
+ * @private
141
+ */
142
+ _updateBreakpoints() {
143
+ const width = window.innerWidth;
144
+ const html = document.documentElement;
145
+ const oldBreakpoint = this._currentBreakpoint;
146
+
147
+ // Remove all existing breakpoint classes
148
+ html.classList.remove(...ALL_BREAKPOINT_CLASSES);
149
+
150
+ // Determine which breakpoint classes to add
151
+ // Multiple classes can be active (cascade down)
152
+ let primaryBreakpoint = null;
153
+
154
+ for (const bp of BREAKPOINTS) {
155
+ let shouldAdd = false;
156
+
157
+ if (bp.minWidth !== undefined && width >= bp.minWidth) {
158
+ shouldAdd = true;
159
+ } else if (bp.maxWidth !== undefined && width <= bp.maxWidth) {
160
+ shouldAdd = true;
161
+ }
162
+
163
+ if (shouldAdd) {
164
+ html.classList.add(bp.className);
165
+
166
+ // The first matching breakpoint is the "primary" one
167
+ if (!primaryBreakpoint) {
168
+ primaryBreakpoint = bp.name;
169
+ }
170
+ }
171
+ }
172
+
173
+ this._currentBreakpoint = primaryBreakpoint;
174
+
175
+ // Notify listeners if breakpoint changed
176
+ if (oldBreakpoint !== this._currentBreakpoint) {
177
+ logger.debug('[BreakpointManager] Breakpoint changed:', oldBreakpoint, '→', this._currentBreakpoint);
178
+ this._notifyListeners(this._currentBreakpoint, oldBreakpoint);
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Notify all registered listeners of breakpoint change
184
+ * @private
185
+ */
186
+ _notifyListeners(newBreakpoint, oldBreakpoint) {
187
+ for (const callback of this._listeners) {
188
+ try {
189
+ callback(newBreakpoint, oldBreakpoint);
190
+ } catch (error) {
191
+ logger.error('[BreakpointManager] Listener error:', error);
192
+ }
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Get the current primary breakpoint name
198
+ * @returns {string|null} Current breakpoint name (e.g., 'tablet-portrait')
199
+ */
200
+ getCurrentBreakpoint() {
201
+ return this._currentBreakpoint;
202
+ }
203
+
204
+ /**
205
+ * Get the current viewport width
206
+ * @returns {number} Current viewport width in pixels
207
+ */
208
+ getViewportWidth() {
209
+ return window.innerWidth;
210
+ }
211
+
212
+ /**
213
+ * Check if the current viewport is at or below a specific breakpoint
214
+ * @param {string} breakpointName - Name of breakpoint to check (e.g., 'tablet-portrait')
215
+ * @returns {boolean} True if viewport is at or below the specified breakpoint
216
+ */
217
+ isAtMost(breakpointName) {
218
+ const bp = BREAKPOINTS.find(b => b.name === breakpointName);
219
+ if (!bp || bp.maxWidth === undefined) {
220
+ logger.warn('[BreakpointManager] Unknown breakpoint:', breakpointName);
221
+ return false;
222
+ }
223
+ return window.innerWidth <= bp.maxWidth;
224
+ }
225
+
226
+ /**
227
+ * Check if the current viewport is at or above a specific breakpoint
228
+ * @param {string} breakpointName - Name of breakpoint to check (e.g., 'large-desktop')
229
+ * @returns {boolean} True if viewport is at or above the specified breakpoint
230
+ */
231
+ isAtLeast(breakpointName) {
232
+ const bp = BREAKPOINTS.find(b => b.name === breakpointName);
233
+ if (!bp) {
234
+ logger.warn('[BreakpointManager] Unknown breakpoint:', breakpointName);
235
+ return false;
236
+ }
237
+
238
+ if (bp.minWidth !== undefined) {
239
+ return window.innerWidth >= bp.minWidth;
240
+ }
241
+ // For max-width breakpoints, "at least" means above their threshold
242
+ if (bp.maxWidth !== undefined) {
243
+ return window.innerWidth > bp.maxWidth;
244
+ }
245
+ return false;
246
+ }
247
+
248
+ /**
249
+ * Check if viewport matches a mobile breakpoint (portrait or landscape)
250
+ * @returns {boolean} True if viewport is mobile-sized
251
+ */
252
+ isMobile() {
253
+ return this.isAtMost('mobile-landscape');
254
+ }
255
+
256
+ /**
257
+ * Check if viewport matches a tablet breakpoint
258
+ * @returns {boolean} True if viewport is tablet-sized
259
+ */
260
+ isTablet() {
261
+ const width = window.innerWidth;
262
+ return width >= 768 && width < 1200;
263
+ }
264
+
265
+ /**
266
+ * Check if viewport matches a desktop breakpoint
267
+ * @returns {boolean} True if viewport is desktop-sized
268
+ */
269
+ isDesktop() {
270
+ return window.innerWidth >= 1200;
271
+ }
272
+
273
+ /**
274
+ * Register a callback for breakpoint changes
275
+ * @param {Function} callback - Function called with (newBreakpoint, oldBreakpoint)
276
+ * @returns {Function} Unsubscribe function
277
+ */
278
+ onChange(callback) {
279
+ if (typeof callback !== 'function') {
280
+ logger.error('[BreakpointManager] onChange requires a function');
281
+ return () => {};
282
+ }
283
+
284
+ this._listeners.push(callback);
285
+
286
+ // Return unsubscribe function
287
+ return () => {
288
+ const index = this._listeners.indexOf(callback);
289
+ if (index > -1) {
290
+ this._listeners.splice(index, 1);
291
+ }
292
+ };
293
+ }
294
+
295
+ /**
296
+ * Get all breakpoint definitions
297
+ * @returns {Array} Array of breakpoint objects
298
+ */
299
+ getBreakpoints() {
300
+ return [...BREAKPOINTS];
301
+ }
302
+
303
+ /**
304
+ * Force a breakpoint update (useful after orientation changes)
305
+ */
306
+ refresh() {
307
+ this._updateBreakpoints();
308
+ }
309
+ }
310
+
311
+ // Export singleton instance
312
+ export const breakpointManager = new BreakpointManager();
313
+
314
+ // Also export class for testing purposes
315
+ export { BreakpointManager };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @file canvas-slide.js
3
+ * @description Convenience helper for canvas layout authors.
4
+ * Wraps an HTML string and optional init callback into a valid slide export.
5
+ *
6
+ * Usage (in a slide .js file):
7
+ *
8
+ * const { canvasSlide } = CourseCode;
9
+ *
10
+ * export const slide = canvasSlide(`
11
+ * <style>.my-app { color: white; }</style>
12
+ * <div class="my-app">
13
+ * <h1>Hello</h1>
14
+ * <button id="next">Next</button>
15
+ * </div>
16
+ * `, (el, api) => {
17
+ * el.querySelector('#next').onclick = () => api.NavigationActions.goToNextAvailableSlide();
18
+ * });
19
+ *
20
+ * @param {string} html - The HTML content (can include <style> tags)
21
+ * @param {Function} [init] - Optional callback: (element, CourseCode) => void
22
+ * @returns {object} A valid slide export with render() method
23
+ */
24
+ export function canvasSlide(html, init) {
25
+ return {
26
+ render() {
27
+ const el = document.createElement('div');
28
+ el.innerHTML = html;
29
+ if (init) {
30
+ init(el, window.CourseCode);
31
+ }
32
+ return el;
33
+ }
34
+ };
35
+ }