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,1773 @@
1
+ # Course Authoring Guide
2
+
3
+ > **Intended Audience: AI Agents** — This document is a machine-readable reference for AI agents authoring courses. For human-readable documentation, see `USER_GUIDE.md`.
4
+
5
+ **Related Docs:**
6
+ - `FRAMEWORK_GUIDE.md` - Framework internals (not needed for course authoring)
7
+ - `COURSE_OUTLINE_GUIDE.md` - Blueprint creation for instructional design
8
+
9
+ > **MCP users:** Use `coursecode_css_catalog`, `coursecode_component_catalog`, `coursecode_interaction_catalog`, and `coursecode_icon_catalog` tools for dynamic, always-current references. The sections below cover usage patterns and rules that the catalog tools do not provide.
10
+
11
+ ---
12
+
13
+ ## CLI Commands
14
+
15
+ | Command | Description |
16
+ |---------|-------------|
17
+ | `coursecode create <name>` | Create a new course project (includes example slides) |
18
+ | `coursecode create <name> --blank` | Create a blank project (no example content) |
19
+ | `coursecode clean` | Remove example files and reset config to minimal starter |
20
+ | `coursecode new slide <id>` | Create a new slide file in `course/slides/` |
21
+ | `coursecode new assessment <id>` | Create a new assessment file in `course/slides/` |
22
+ | `coursecode new config` | Create a minimal `course-config.js` (errors if exists) |
23
+ | `coursecode convert` | Convert docx/pptx/pdf in `course/references/` to markdown |
24
+ | `coursecode import <pptx>` | Import PowerPoint as a presentation course |
25
+ | `coursecode dev` | Run Vite build in watch mode (outputs to `dist/`) |
26
+ | `coursecode build` | Build SCORM package (ZIP ready for LMS) |
27
+ | `coursecode preview` | Live preview with stub LMS + auto-rebuild on changes |
28
+ | `coursecode preview --export` | Export static preview for stakeholder sharing |
29
+ | `coursecode upgrade` | Upgrade framework in current project |
30
+ | `coursecode upgrade --configs` | Also update vite.config.js and eslint.config.js |
31
+ | `coursecode narration` | Generate audio narration from text |
32
+ | `coursecode info` | Show info about current project |
33
+ | `coursecode export-content` | Export course content for translation/review |
34
+ | `coursecode test-errors` | Test error reporting webhook configuration |
35
+ | `coursecode test-data` | Test data reporting webhook configuration |
36
+ | `coursecode mcp` | Start MCP server for AI agent integration |
37
+
38
+ **Format options** (for local `dev`, `build`, `preview` — not needed for cloud-deployed courses):
39
+ ```bash
40
+ coursecode build --format scorm1.2 # Build for SCORM 1.2 LMS
41
+ coursecode preview --format cmi5 # Preview cmi5 format
42
+ ```
43
+ Supported formats: `cmi5` (default), `scorm2004`, `scorm1.2`, `lti`, `scorm1.2-proxy`, `scorm2004-proxy`, `cmi5-remote`
44
+
45
+ > **Cloud courses:** When deployed to CourseCode Cloud, the format is chosen at download time, not build time. The cloud generates any format from a single universal build. The `--format` flag and `course-config.js` format setting are only relevant for local CLI workflows.
46
+
47
+ ## Example Files in New Projects
48
+
49
+ New projects created with `coursecode create` include example slides (prefixed `example-`) and a pre-configured `course-config.js` referencing them. When authoring a real course, **delete all `example-*` slide and audio files** and replace `course-config.js` with your own structure. The `coursecode clean` CLI command automates this — it removes all `example-*` files and resets the config to a minimal starter.
50
+
51
+ ---
52
+
53
+ ## CLI Import (PowerPoint)
54
+
55
+ Converts a `.pptx` file into a complete CourseCode project using the `presentation` layout. Each slide is exported as a PNG image and wrapped in an HTML slide file.
56
+
57
+ ```bash
58
+ # Auto-export via PowerPoint (macOS only)
59
+ coursecode import presentation.pptx
60
+
61
+ # Use pre-exported slide images (any platform)
62
+ coursecode import presentation.pptx --slides-dir ./exported-pngs/
63
+ ```
64
+
65
+ | Option | Description |
66
+ |--------|-------------|
67
+ | `-n, --name <name>` | Project name (default: derived from filename) |
68
+ | `--slides-dir <dir>` | Directory of pre-exported slide PNGs (skips PowerPoint) |
69
+ | `--no-install` | Skip `npm install` |
70
+
71
+ **What it generates:**
72
+
73
+ ```
74
+ my-course/
75
+ ├── course/
76
+ │ ├── course-config.js # layout: 'presentation', no engagement
77
+ │ ├── assets/slides/ # slide-01.png … slide-N.png
78
+ │ ├── slides/ # slide-01.html … slide-N.html (<img> wrappers)
79
+ │ └── references/converted/ # Converted text from source documents
80
+ └── framework/
81
+ ```
82
+
83
+ **Auto-export (macOS):** Uses AppleScript to drive Microsoft PowerPoint — exports each slide as PNG, then closes the file. Requires PowerPoint to be installed.
84
+
85
+ **Manual fallback:** If PowerPoint isn't available, export slides to PNG manually (PowerPoint → File → Save As → PNG), then use `--slides-dir` to point at the exported folder.
86
+
87
+ **Progressive enhancement:** After import, AI agents or authors can replace image slides with interactive HTML, add assessments, or add engagement tracking.
88
+
89
+
90
+ ## CLI Preview
91
+
92
+ Test your course with a stub LMS wrapper.
93
+
94
+ **Architecture:** Preview runs Vite in **build watch mode** (not dev server) outputting to `dist/`, then serves `dist/` via a lightweight HTTP server with an embedded stub SCORM API. This mirrors how courses run in real LMSs.
95
+
96
+ ### Live Preview (Default)
97
+
98
+ Runs Vite build in watch mode + stub LMS server. Changes to source files trigger automatic rebuilds:
99
+
100
+ ```bash
101
+ coursecode preview # Open http://localhost:4173
102
+ ```
103
+
104
+ | Option | Description |
105
+ |--------|-------------|
106
+ | `--port <port>` | Preview server port (default: 4173) |
107
+ | `--title <title>` | Custom browser title |
108
+ | `--no-content` | Disable course content viewer |
109
+ | `-f, --format <format>` | LMS format: `scorm2004`, `scorm1.2`, `cmi5`, or `lti` |
110
+
111
+ ### Static Export
112
+
113
+ Generate a self-contained folder for stakeholder sharing (Netlify, GitHub Pages, etc.):
114
+
115
+ ```bash
116
+ coursecode preview --export # Build and create ./course-preview
117
+ coursecode preview --export -p "secret" # With password protection
118
+ coursecode preview --export --skip-build # Use existing dist/
119
+ coursecode preview --export -o ./stakeholder # Custom output directory
120
+ coursecode preview --export --no-content # Exclude content viewer
121
+ ```
122
+
123
+ | Option | Description |
124
+ |--------|-------------|
125
+ | `-e, --export` | Export static folder instead of running live server |
126
+ | `-o, --output <dir>` | Output directory (default: `./course-preview`) |
127
+ | `-p, --password <pwd>` | Optional password protection |
128
+ | `--skip-build` | Use existing `dist/` instead of rebuilding |
129
+ | `--nojekyll` | Add `.nojekyll` file (required for GitHub Pages) |
130
+ | `--no-content` | Disable course content viewer |
131
+ | `-f, --format <format>` | LMS format: `scorm2004`, `scorm1.2`, `cmi5`, or `lti` |
132
+
133
+ **Deployment:** Drag the output folder to Netlify (or similar) for instant shareable URL.
134
+
135
+ ### GitHub Pages Deployment
136
+
137
+ GitHub Pages only serves from two locations: repository root (`/`) or `/docs`. To deploy your preview:
138
+
139
+ ```bash
140
+ # Export to /docs folder with .nojekyll file
141
+ coursecode preview --export -o ./docs --nojekyll
142
+ ```
143
+
144
+ Then in your repository settings, enable GitHub Pages and set the source to `/docs`.
145
+
146
+ **Important:** The `--nojekyll` flag creates a `.nojekyll` file which prevents Jekyll processing. Without this, GitHub Pages may ignore files/folders starting with underscores or dots (like `_course.html`).
147
+
148
+ To include the `/docs` folder in your repository, comment out `course-preview/` in `.gitignore` (or add `docs/` if using that name).
149
+
150
+ ### Features (Both Modes)
151
+
152
+ - **Stub SCORM API** (`API_1484_11`) with localStorage persistence
153
+ - **Preview toolbar** (starts collapsed): Reset Course, Skip Gating toggle, Content, Debug buttons
154
+ - **Debug panel** with three tabs:
155
+ - **State**: Runtime CMI data and decoded suspend_data
156
+ - **API Log**: Every GetValue/SetValue call with timestamps
157
+ - **Errors**: Validation warnings (invalid values, type mismatches, range errors)
158
+ - **Content viewer**: Rendered course content as Markdown for quick reference (disable with `--no-content`)
159
+ - **URL parameters**: `?skipGating=true`, `?debug=true`
160
+ - **MCP automation**: `coursecode mcp` starts an MCP server that connects to the preview for AI-controlled testing
161
+
162
+ Live preview includes **automatic live reload**: when you save a file, Vite rebuilds and the course iframe refreshes automatically (stub LMS state is preserved).
163
+
164
+ ---
165
+
166
+ ## CLI Narration
167
+
168
+ Generate audio from text sources using AI TTS via API.
169
+
170
+ | Option | Description |
171
+ |--------|-------------|
172
+ | `-f, --force` | Regenerate all narration (ignore cache) |
173
+ | `-s, --slide <id>` | Generate narration for a specific slide only |
174
+ | `--dry-run` | Preview what would be generated |
175
+
176
+ **Examples:**
177
+
178
+ ```bash
179
+ coursecode narration # Generate all changed narration
180
+ coursecode narration --slide intro # Generate for one slide only
181
+ coursecode narration --force # Regenerate all (ignore cache)
182
+ coursecode narration --dry-run # Preview without generating
183
+ ```
184
+
185
+
186
+ ## CLI Export Content
187
+
188
+ Extracts reviewable text content from SCORM course source files into structured Markdown.
189
+
190
+ | Option | Description |
191
+ |--------|-------------|
192
+ | `-o, --output <file>` | Output file path (defaults to stdout) |
193
+ | `--no-answers` | Exclude correct answers for interactions (included by default) |
194
+ | `--no-feedback` | Exclude feedback text (included by default) |
195
+ | `--no-interactions` | Exclude interactions and assessment questions from output |
196
+ | `--include-narration` | Include narration transcripts |
197
+ | `--interactions-only` | Export only interactions and assessment questions (no slide content) |
198
+ | `--slides <ids>` | Comma-separated slide IDs to export |
199
+ | `--format <type>` | Output format: `md` or `json` (default: `md`) |
200
+ | `--course-path <path>` | Path to course directory (default: `./course`) |
201
+
202
+ **Examples:**
203
+
204
+ ```bash
205
+ # Export all content to stdout
206
+ coursecode export-content
207
+
208
+ # Export to a file for review
209
+ coursecode export-content -o content-review.md
210
+
211
+ # Export only interactions for SME review
212
+ coursecode export-content --interactions-only -o quiz-review.md
213
+
214
+ # Export specific slides
215
+ coursecode export-content --slides intro,module1,summary -o selected-content.md
216
+
217
+ # Export with narration transcripts included
218
+ coursecode export-content --include-narration -o full-content.md
219
+
220
+ # Export as JSON for programmatic processing
221
+ coursecode export-content --format json -o content.json
222
+ ```
223
+
224
+ ---
225
+
226
+ ## Writing Style
227
+
228
+ - **No em-dashes** in sentance structure. Use alternative phrasing or punctuation instead.
229
+
230
+ ---
231
+
232
+ ## Core Principle
233
+
234
+ **NEVER modify `framework/` directory** - all course work in `course/` only.
235
+
236
+ ## Quick Start
237
+
238
+ 1. **Edit course metadata** in `course/course-config.js` (auto-populates SCORM manifest)
239
+
240
+ 2. **Define structure** in `course/course-config.js`:
241
+ ```javascript
242
+ structure: [
243
+ { id: 'intro', title: 'Introduction', file: 'intro.js' },
244
+ { id: 'content-1', title: 'Lesson 1', file: 'content.js', locked: { stateFlag: 'intro-complete' } }
245
+ ]
246
+ ```
247
+
248
+ 3. **Create slide** in `course/slides/intro.js`:
249
+ ```javascript
250
+ export const slide = {
251
+ render(root, context) {
252
+ const container = document.createElement('div');
253
+ container.innerHTML = `<h1>Welcome</h1><p>Content here</p>`;
254
+ return container; // Must return a DOM element
255
+ }
256
+ };
257
+ ```
258
+
259
+ > **⚠️ No import statements.** Components, interactions, CSS classes, and icons are all globally available at runtime. The only valid `import` is for local assets (images, SVGs). Access framework APIs via `const { createXxxQuestion } = CourseCode;` (destructure from global, **not** an import).
260
+
261
+ 4. **Add styles** to `course/theme.css` (only for custom branding - use framework utility classes first, see CSS Quick Reference section below)
262
+
263
+ ## Build Process
264
+
265
+ The template uses Vite with automated SCORM packaging. The `imsmanifest.xml` is **automatically generated** from `course-config.js` during build. Never edit the manifest directly.
266
+
267
+ > **Cloud builds:** The format in `course-config.js` determines the meta tag stamped during a local build, but for cloud-deployed courses this is irrelevant. The cloud re-stamps the meta tag and generates the manifest for any requested format from a single universal `dist/` — no rebuild needed.
268
+
269
+ ## External Hosting (CDN Deployment)
270
+
271
+ Deploy courses to a CDN for instant updates without re-uploading to the LMS.
272
+
273
+ **Formats:**
274
+ | Format | Upload to LMS | Deploy to CDN |
275
+ |--------|---------------|---------------|
276
+ | `scorm1.2-proxy` | Tiny proxy ZIP (~15KB) | Full course |
277
+ | `scorm2004-proxy` | Tiny proxy ZIP (~15KB) | Full course |
278
+ | `cmi5-remote` | Manifest-only ZIP (~1KB) | Full course |
279
+
280
+ **Setup:**
281
+ ```javascript
282
+ // course-config.js
283
+ format: 'scorm1.2-proxy',
284
+ externalUrl: 'https://cdn.example.com/my-course',
285
+ accessControl: {
286
+ clients: {
287
+ 'acme-corp': { token: 'abc123' },
288
+ 'globex': { token: 'def456' }
289
+ }
290
+ }
291
+ ```
292
+
293
+ **Generate tokens:**
294
+ ```bash
295
+ coursecode token # Generate random token
296
+ coursecode token --add acme-corp # Add client with token to config
297
+ ```
298
+
299
+ **Build:**
300
+ ```bash
301
+ coursecode build # Creates dist/ + *_<client>_proxy.zip per client
302
+ ```
303
+
304
+ **Deploy:**
305
+ 1. Upload `dist/` to CDN (GitHub Pages, Vercel, Netlify, etc.)
306
+ 2. Upload client-specific proxy ZIP to each client's LMS
307
+ 3. Future updates: just redeploy to CDN—no LMS re-upload needed
308
+
309
+ **Multi-tenant benefits:**
310
+ - One CDN deployment serves multiple LMS clients
311
+ - Each client gets a unique token baked into their package
312
+ - Disable a client by removing from config and redeploying
313
+
314
+ **General benefits:**
315
+ - Hot-fix typos/content without LMS involvement
316
+ - Smaller LMS package = faster upload/launch
317
+ - CDN caching = better performance
318
+
319
+ ---
320
+
321
+ ## Data Persistence
322
+
323
+
324
+ **Never call SCORM API directly** - use these managers:
325
+
326
+ | Manager | Purpose | Key Methods |
327
+ |---------|---------|-------------|
328
+ | `StateManager` | Custom persistent data | `.set(key, value)`, `.get(key)` |
329
+ | `ObjectiveManager` | Track learning objectives | Auto: built-in criteria + assessment-linked<br>Manual: `.setCompletionStatus(id, status)`, `.setSuccessStatus(id, status)`, `.setScore(id, score)` |
330
+ | `InteractionManager` | Record student responses | Auto-logs interactions |
331
+ | `FlagManager` | Boolean flags in suspend data | `.setFlag(name, bool)`, `.getFlag(name)` |
332
+ | `EngagementManager` | Track content engagement | `.getProgress(slideId)`, `.isSlideComplete(slideId)` |
333
+ | `ScoreManager` | Calculate and report scores | Auto: configured in `course-config.js` |
334
+
335
+ ### Objectives (in `course-config.js`)
336
+
337
+ ```javascript
338
+ objectives: [
339
+ { id: 'complete-intro', criteria: { type: 'slideVisited', slideId: 'intro' } },
340
+ { id: 'complete-foundation', criteria: { type: 'allSlidesVisited', slideIds: ['intro-01', 'intro-02'] } },
341
+ { id: 'master-content', criteria: { type: 'timeOnSlide', slideId: 'content-1', minSeconds: 60 } },
342
+ { id: 'intro-done', criteria: { type: 'flag', key: 'intro-complete', equals: true } },
343
+ { id: 'all-unlocked', criteria: { type: 'allFlags', flags: ['step1', 'step2', 'step3'] } },
344
+ { id: 'obj-final-exam-passed' } // No criteria - auto-managed by assessment
345
+ ]
346
+ ```
347
+
348
+ ### Course-Level Scoring
349
+
350
+ The framework supports automatic calculation and reporting of `cmi.score.raw` based on assessment and objective scores. This is **optional** and configured in `course-config.js`. The course score will automatically recalculate when objective scores change.
351
+
352
+ ### Course Updates & Existing Learners
353
+
354
+ When you update a course (add/remove slides, change assessments), existing learners may have incompatible stored data:
355
+
356
+ - **Dev mode**: Throws errors to catch issues during testing
357
+ - **Prod mode**: Gracefully recovers (reverts to defaults, filters missing questions)
358
+
359
+ **Best practice**: Use `metadata.version` in `course-config.js` to track course versions.
360
+
361
+ ---
362
+
363
+ ## Audio Narration
364
+
365
+ Three audio modes available.
366
+
367
+ ### Narration Writing Best Practices
368
+
369
+ Narration should **complement** on-screen content, not read it verbatim. Learners read faster than audio plays; duplicating text creates cognitive dissonance.
370
+
371
+ **Effective narration:**
372
+ - Expands on displayed points with context or examples
373
+ - Guides attention: "Notice the three steps listed here" then explains their significance
374
+ - Provides transitions between concepts
375
+ - Adds conversational tone and expert insight
376
+
377
+ **Avoid:**
378
+ - Reading bullet points or headings word-for-word
379
+ - Narrating every piece of on-screen text
380
+ - Describing obvious visual elements
381
+
382
+ **Exception:** Critical warnings, verbatim policies, or procedures may benefit from matched audio and text for reinforcement.
383
+
384
+ ### Slide-Level Audio
385
+
386
+ Configure in `course-config.js`:
387
+
388
+ ```javascript
389
+ {
390
+ id: 'intro',
391
+ component: '@slides/intro.js',
392
+ title: 'Introduction',
393
+ audio: {
394
+ src: 'audio/intro-narration.mp3', // Relative to course/assets/
395
+ autoplay: false,
396
+ completionThreshold: 0.95
397
+ },
398
+ engagement: {
399
+ required: true,
400
+ requirements: [
401
+ { type: 'slideAudioComplete', message: 'Listen to the narration before continuing' }
402
+ ]
403
+ }
404
+ }
405
+ ```
406
+
407
+ ### Standalone Audio Players
408
+
409
+ Place anywhere on slide:
410
+
411
+ ```html
412
+ <div data-component="audio-player"
413
+ data-audio-src="audio/narration.mp3"
414
+ data-audio-id="section1-audio">
415
+ </div>
416
+ ```
417
+
418
+ Gate with: `{ type: 'audioComplete', audioId: 'section1-audio' }`
419
+
420
+ ### Modal Audio
421
+
422
+ Add to modal triggers:
423
+
424
+ ```html
425
+ <button
426
+ data-component="modal-trigger"
427
+ data-modal-id="details-modal"
428
+ data-title="Learn More"
429
+ data-audio-src="audio/modal-details.mp3">
430
+ Show Details
431
+ </button>
432
+ ```
433
+
434
+ Gate with: `{ type: 'modalAudioComplete', modalId: 'details-modal' }`
435
+
436
+ ### Video Players
437
+
438
+ Native HTML5, YouTube, or Vimeo:
439
+
440
+ ```html
441
+ <!-- Native video (custom controls, progress tracking) -->
442
+ <div data-component="video-player"
443
+ data-video-src="video/intro.mp4"
444
+ data-video-id="intro-video"
445
+ data-video-poster="images/poster.jpg">
446
+ </div>
447
+
448
+ <!-- YouTube/Vimeo (platform controls, auto-detected) -->
449
+ <div data-component="video-player"
450
+ data-video-src="https://youtu.be/xyz123"
451
+ data-video-id="demo-video">
452
+ </div>
453
+ ```
454
+
455
+ Gate with: `{ type: 'videoComplete', videoId: 'intro-video' }`
456
+
457
+ > **Note:** External videos (YouTube/Vimeo) don't support progress tracking or completion gating.
458
+
459
+ ### Automated Narration Generation
460
+
461
+ Generate audio from text using ElevenLabs:
462
+
463
+ ```javascript
464
+ // In slide file
465
+ export const narration = `Welcome to this course...`;
466
+
467
+ // Or with voice settings
468
+ export const narration = {
469
+ text: `Welcome to this course...`,
470
+ voice_id: 'EXAVITQu4vr4xnSDxMaL',
471
+ stability: 0.5
472
+ };
473
+ ```
474
+
475
+ ```javascript
476
+ // In course-config.js
477
+ audio: { src: '@slides/intro.js' } // → generates assets/audio/intro.mp3
478
+ ```
479
+
480
+ Run `npm run narration` to generate.
481
+
482
+ ---
483
+
484
+ ## Interactions Reference
485
+
486
+ > **MCP users:** Use `coursecode_interaction_catalog` for full schemas. Below covers usage patterns the catalog doesn't provide.
487
+
488
+ All interactions follow the same pattern:
489
+
490
+ ```javascript
491
+ // Destructure from the global CourseCode object — NOT an import statement
492
+ const { createXxxQuestion } = CourseCode;
493
+
494
+ const question = createXxxQuestion({ id: 'unique-id', prompt: 'Question text', ...typeSpecificConfig });
495
+ question.render(container); // Or question.render(container, savedResponse) to restore state
496
+ ```
497
+
498
+ > **⚠️ No imports needed.** `CourseCode` is a global object available in all slide files. Use destructuring (`const { ... } = CourseCode`) to access factory functions. Do NOT use `import { ... } from '...'` for any framework API.
499
+
500
+ **Common options** (all types):
501
+ - `id` (required): Unique identifier, used for engagement tracking
502
+ - `prompt` (required): Question text displayed to learner
503
+ - `controlled`: `false` (default) = auto-registers for engagement tracking; `true` = assessment-managed
504
+ - `feedback`: `{ correct: 'Custom message', incorrect: 'Custom message' }`
505
+
506
+ ### Multiple Choice
507
+
508
+ ```javascript
509
+ // Single-select (radio buttons)
510
+ createMultipleChoiceQuestion({
511
+ id: 'q1', prompt: 'Which is correct?',
512
+ choices: [
513
+ { value: 'a', text: 'Option A' },
514
+ { value: 'b', text: 'Option B', description: 'Optional hint' },
515
+ { value: 'c', text: 'Option C' }
516
+ ],
517
+ correctAnswer: 'b'
518
+ });
519
+
520
+ // Multi-select (checkboxes) - mark correct choices with `correct: true`
521
+ createMultipleChoiceQuestion({
522
+ id: 'q2', prompt: 'Select all that apply:',
523
+ multiple: true,
524
+ choices: [
525
+ { value: 'a', text: 'Option A', correct: true },
526
+ { value: 'b', text: 'Option B' },
527
+ { value: 'c', text: 'Option C', correct: true }
528
+ ]
529
+ });
530
+ ```
531
+
532
+ ### True/False
533
+
534
+ ```javascript
535
+ createTrueFalseQuestion({
536
+ id: 'tf1', prompt: 'The sky is blue.',
537
+ correctAnswer: true, // boolean
538
+ autoCheck: false // true = instant feedback on selection (no check button)
539
+ });
540
+ ```
541
+
542
+ ### Fill-in-the-Blank
543
+
544
+ ```javascript
545
+ // INLINE MODE - Text with embedded inputs using {{placeholders}}
546
+ createFillInQuestion({
547
+ id: 'fill1',
548
+ template: 'The capital of {{country}} is Paris.',
549
+ blanks: { country: { correct: 'France', placeholder: 'country...' } }
550
+ });
551
+
552
+ // STACKED MODE - uses `prompt` instead of `template`
553
+ createFillInQuestion({
554
+ id: 'fill2', prompt: 'What is the capital of France?',
555
+ blanks: { answer: { correct: 'Paris', placeholder: 'Enter answer...' } }
556
+ });
557
+ ```
558
+
559
+ **Fuzzy matching:** `correct: ['Paris', 'paris']` (multiple answers) | `typoTolerance: 1` (Levenshtein) | `caseSensitive: false` (default). Whitespace auto-normalized.
560
+
561
+ ### Matching
562
+
563
+ ```javascript
564
+ createMatchingQuestion({
565
+ id: 'match1', prompt: 'Match the terms to definitions:',
566
+ pairs: [
567
+ { id: 'term1', text: 'HTML', match: 'Markup Language' },
568
+ { id: 'term2', text: 'CSS', match: 'Styling' },
569
+ { id: 'term3', text: 'JS', match: 'Scripting' }
570
+ ],
571
+ feedbackMode: 'deferred' // 'deferred' (default) = check all at once; 'immediate' = instant per-match
572
+ });
573
+ ```
574
+
575
+ ### Drag-and-Drop
576
+
577
+ ```javascript
578
+ createDragDropQuestion({
579
+ id: 'dd1', prompt: 'Drag items to correct zones:',
580
+ items: [
581
+ { id: 'item1', content: 'Apple' },
582
+ { id: 'item2', content: 'Carrot' },
583
+ { id: 'item3', content: 'Banana' }
584
+ ],
585
+ dropZones: [
586
+ { id: 'fruit', label: 'Fruits', accepts: ['item1', 'item3'], maxItems: 2 },
587
+ { id: 'veg', label: 'Vegetables', accepts: ['item2'], maxItems: 1 }
588
+ ]
589
+ });
590
+ ```
591
+
592
+ ### Numeric
593
+
594
+ ```javascript
595
+ // Exact value with tolerance
596
+ createNumericQuestion({
597
+ id: 'num1', prompt: 'What is 2 + 2?',
598
+ correctRange: { exact: 4 },
599
+ tolerance: 0, // allows ±tolerance
600
+ placeholder: 'Enter number...', units: 'items'
601
+ });
602
+
603
+ // Range-based
604
+ createNumericQuestion({
605
+ id: 'num2', prompt: 'Enter a number between 10 and 20:',
606
+ correctRange: { min: 10, max: 20 }
607
+ });
608
+ ```
609
+
610
+ ### Hotspot
611
+
612
+ ```javascript
613
+ createHotspotQuestion({
614
+ id: 'hot1', prompt: 'Click the correct region:',
615
+ image: { src: 'assets/images/diagram.png', alt: 'Diagram' },
616
+ hotspots: [
617
+ // pos = [x%, y%, width%, height%]
618
+ { id: 'zone1', pos: [10, 20, 15, 10], correct: true, label: 'Correct Zone', feedback: 'Good choice!' },
619
+ { id: 'zone2', pos: [50, 30, 20, 15], correct: false, label: 'Wrong Zone', feedback: 'Try again' }
620
+ ]
621
+ });
622
+ ```
623
+
624
+ **Appearance themes**: `'correct'`, `'incorrect'`, `'primary'`, `'accent'` — or provide custom `appearance` object.
625
+
626
+ ### Sequencing
627
+
628
+ ```javascript
629
+ createSequencingQuestion({
630
+ id: 'seq1', prompt: 'Arrange in correct order:',
631
+ sequenceLabels: ['First', 'Last'], // Optional: shows direction track (2+ labels)
632
+ items: [
633
+ { id: 'step1', text: 'First step' },
634
+ { id: 'step2', text: 'Second step' },
635
+ { id: 'step3', text: 'Third step' }
636
+ ],
637
+ correctOrder: ['step1', 'step2', 'step3'] // Items auto-shuffle on render
638
+ });
639
+ ```
640
+
641
+ ### Likert (Survey/Rating Scale)
642
+
643
+ ```javascript
644
+ // Survey mode (no correct answers)
645
+ createLikertQuestion({
646
+ id: 'survey1', prompt: 'Rate your agreement:',
647
+ scale: [
648
+ { value: '1', text: 'Strongly Disagree' },
649
+ { value: '2', text: 'Disagree' },
650
+ { value: '3', text: 'Neutral' },
651
+ { value: '4', text: 'Agree' },
652
+ { value: '5', text: 'Strongly Agree' }
653
+ ],
654
+ questions: [
655
+ { id: 'q1', text: 'The content was clear.' },
656
+ { id: 'q2', text: 'The examples were helpful.' }
657
+ ]
658
+ });
659
+
660
+ // Quiz mode: add correctAnswers: { q1: '4', q2: '5' }
661
+ ```
662
+
663
+ ### Custom Interactions
664
+
665
+ Add a `.js` file to `course/interactions/`. File `rating-scale.js` → factory `CourseCode.createRatingScaleQuestion()`. See `course/interactions/PLUGIN_GUIDE.md`.
666
+
667
+ ### Interaction Methods
668
+
669
+ All interaction objects returned by `createXxxQuestion()` expose:
670
+
671
+ | Method | Description |
672
+ |--------|-------------|
673
+ | `render(container, initialResponse?)` | Renders into container, optionally restoring saved response |
674
+ | `getResponse()` | Returns current learner response |
675
+ | `setResponse(response)` | Programmatically sets response |
676
+ | `evaluate(response?)` | Returns `{ correct, score, response }` |
677
+ | `checkAnswer()` | Evaluates and shows feedback, returns evaluation |
678
+ | `reset()` | Clears response and feedback |
679
+ | `getCorrectAnswer()` | Returns correct answer (for review screens) |
680
+
681
+ **Event:** `interaction-checked` fires on the container after `checkAnswer()` with `e.detail.{ isCorrect, evaluation }`.
682
+
683
+ ---
684
+
685
+ ## Interactions vs Assessments
686
+
687
+ **Practice Questions** (above): Standalone, auto-tracked via engagement, immediate feedback.
688
+
689
+ **Assessments** (below): Graded, question pools, randomization, retake logic, objective-linked.
690
+
691
+ ### Assessments (Graded)
692
+
693
+ ```javascript
694
+ const { AssessmentManager } = CourseCode;
695
+
696
+ const assessment = AssessmentManager.createAssessment({ ...config, questions }, overrides);
697
+ assessment.render(container);
698
+ ```
699
+
700
+ **Assessment Config:**
701
+ ```javascript
702
+ export const config = {
703
+ id: 'final-exam',
704
+ assessmentObjective: 'obj-final-exam-passed', // Auto-updates objective (null for none)
705
+ settings: {
706
+ passingScore: 80,
707
+ randomizeQuestions: true,
708
+ randomizeOnRetake: true,
709
+ allowUnansweredSubmission: true
710
+ }
711
+ };
712
+ ```
713
+
714
+ **Features**: Question banks, randomization, progressive intervention, auto-linked objectives.
715
+
716
+ ---
717
+
718
+ ## Styling
719
+
720
+ **Priority**: Utility classes → Design tokens → Custom CSS in `theme.css`
721
+
722
+ **Important:** `position:fixed` and `position:sticky` are NOT available in SCORM iframes (would escape iframe boundaries). Use `position:absolute` with a positioned parent container instead.
723
+
724
+ ### Styled Lists
725
+
726
+ Use `.list-styled` (bullets) or `.list-numbered` (ordered) for enhanced list styling with colored markers:
727
+
728
+ ```html
729
+ <ul class="list-styled">
730
+ <li>First point</li>
731
+ <li>Second point</li>
732
+ </ul>
733
+ ```
734
+
735
+ ### Container-First Spacing
736
+
737
+ All content should be in containers that control spacing. Elements like headings, paragraphs, lists, dividers, and tables have **no default margins**. Use:
738
+
739
+ - `.stack-sm` (8px), `.stack-md` (16px), `.stack-lg` (24px) for vertical layouts
740
+ - `.gap-*` (0-6) for flex/grid gaps
741
+
742
+ ```html
743
+ <div class="content-medium stack-lg">
744
+ <h1>Title</h1>
745
+ <p>Paragraph with no extra spacing—stack handles it.</p>
746
+ <div class="divider"></div>
747
+ <p>More content.</p>
748
+ </div>
749
+ ```
750
+
751
+ ### Slide Headers
752
+
753
+ Use `.slide-header` for consistent slide titles:
754
+
755
+ ```html
756
+ <header class="slide-header">
757
+ <h1>Welcome to the Course</h1>
758
+ <p>Your guide to getting started</p>
759
+ </header>
760
+ ```
761
+
762
+ Variants: `.slide-header-left`, `.slide-header-divider`
763
+
764
+ Optional eyebrow: `<span class="eyebrow">Module 1</span>` above the title.
765
+
766
+ ### CSS Quick Reference
767
+
768
+ > **MCP users:** Use `coursecode_css_catalog` for the complete class reference extracted from CSS source files. The patterns and rules below supplement the catalog.
769
+
770
+ #### Golden Rules
771
+
772
+ 1. ✅ **Always wrap text content in `.content-*` class** (narrow/medium/wide)
773
+ 2. ✅ **Use `.stack-*` containers for vertical spacing** (not margins on elements)
774
+ 3. ✅ **Use UI components for common layouts** (intro-cards, steps, features)
775
+ 4. ✅ **Never nest `.card` inside `.card`** (flatten structure)
776
+ 5. ✅ **Always add `.btn` base class to buttons** (before variant)
777
+ 6. ✅ **Use utility classes instead of inline styles** (`.m-4` not `style="margin: 1rem"`)
778
+ 7. ✅ **Add alt text to all images** (accessibility requirement)
779
+ 8. ✅ **Use semantic HTML** (h1 → h2 → h3, not skipping levels)
780
+ 9. ✅ **One h1 per slide** (multiple h1 tags = error)
781
+ 10. ✅ **Slide ID must match filename** (`id: 'intro'` → `@slides/intro.js`)
782
+
783
+ > **Container-First Spacing:** Elements like headings, paragraphs, lists, dividers, and tables have **no default margins**. Use `.stack-sm`, `.stack-md`, `.stack-lg`, or `.gap-*` on containers to control spacing.
784
+
785
+ #### Layout Classes
786
+
787
+ | Class | Effect |
788
+ |-------|--------|
789
+ | `.content-narrow` | 700px max-width |
790
+ | `.content-medium` | 900px max-width (default) |
791
+ | `.content-wide` | 1200px max-width |
792
+ | `.content-full` | No max-width |
793
+ | `.stack-sm/md/lg` | Vertical flex with gap |
794
+ | `.cols-2`, `.cols-3` | Grid columns |
795
+ | `.cols-auto-fit` | Auto-fit grid (min 280px) |
796
+ | `.split-50-50`, `.split-60-40`, `.split-40-60` | Grid splits |
797
+
798
+ #### UI Component Patterns
799
+
800
+ **Intro Cards:**
801
+ ```html
802
+ <div class="content-wide">
803
+ <h1>Title</h1>
804
+ <p class="lead">Subtitle</p>
805
+ <div data-component="intro-cards">
806
+ <div class="intro"><h2>Section</h2><p>Text</p></div>
807
+ <div class="card-grid">
808
+ <div class="card">Card 1</div>
809
+ <div class="card">Card 2</div>
810
+ </div>
811
+ </div>
812
+ </div>
813
+ ```
814
+
815
+ **Numbered Steps:**
816
+ ```html
817
+ <div data-component="steps">
818
+ <div class="step">
819
+ <div class="step-number">1</div>
820
+ <div class="step-content"><h3>Title</h3><p>Description</p></div>
821
+ </div>
822
+ </div>
823
+ ```
824
+ Variants: `data-style="connected"`, `data-style="connected-minimal"`, `data-style="compact"`
825
+
826
+ **Timeline:**
827
+ ```html
828
+ <div data-component="timeline">
829
+ <div class="timeline-item">
830
+ <span class="timeline-date">2020</span>
831
+ <div class="timeline-marker"></div>
832
+ <div class="timeline-content"><h3>Event</h3><p>Details</p></div>
833
+ </div>
834
+ </div>
835
+ ```
836
+
837
+ **Hero:**
838
+ ```html
839
+ <div data-component="hero" class="hero-gradient">
840
+ <div class="hero-content">
841
+ <span class="hero-badge">New</span>
842
+ <!-- Optional: no border on hero badge -->
843
+ <span class="hero-badge hero-badge-borderless">AI-Powered</span>
844
+ <h1 class="hero-title">Welcome</h1>
845
+ <p class="hero-subtitle">Subtitle</p>
846
+ <div class="hero-cta"><button class="btn btn-primary">Get Started</button></div>
847
+ </div>
848
+ </div>
849
+ ```
850
+
851
+ **Badges:**
852
+ ```html
853
+ <span class="badge badge-primary">Primary</span>
854
+ <span class="badge badge-success">Success</span>
855
+ <span class="badge badge-warning">Warning</span>
856
+ <span class="badge badge-danger">Danger</span>
857
+ <span class="badge badge-info">Info</span>
858
+ <span class="badge badge-outline">Outline</span>
859
+ <span class="badge badge-primary badge-borderless">Borderless</span>
860
+ ```
861
+ Variants: `badge-primary`, `badge-secondary`, `badge-accent`, `badge-success`, `badge-warning`, `badge-danger`, `badge-info`, `badge-outline`, `badge-borderless`
862
+
863
+ **Stats, Quote, Checklist, Features, Comparison** — use `data-component="stats|quote|checklist|features|comparison"` with their respective child classes.
864
+
865
+ **Flip Card:**
866
+ ```html
867
+ <div class="flip-card" data-component="flip-card">
868
+ <div class="flip-card-inner">
869
+ <div class="flip-card-front">Front</div>
870
+ <div class="flip-card-back">Back</div>
871
+ </div>
872
+ </div>
873
+ ```
874
+ Back variants: `.bg-light`, `.bg-primary-subtle`, `.bg-success-subtle`, `.bg-warning-subtle`, `.bg-danger-subtle`, `.bg-info-subtle`, `.bg-secondary`, `.bg-dark`
875
+
876
+ **Callouts (recommended):**
877
+ ```html
878
+ <!-- Modern default -->
879
+ <aside class="callout callout--info" data-component="callout" data-icon="auto">
880
+ <h4 class="callout__title">Helpful context</h4>
881
+ <div class="callout__body">
882
+ <p>Use this for most informational guidance.</p>
883
+ </div>
884
+ </aside>
885
+
886
+ <!-- Dismissible warning with actions -->
887
+ <aside class="callout callout--warning callout--dismissible" data-component="callout" data-icon="auto">
888
+ <button class="callout__dismiss" aria-label="Dismiss">×</button>
889
+ <h4 class="callout__title">Heads up</h4>
890
+ <div class="callout__body"><p>Double-check before continuing.</p></div>
891
+ <div class="callout__actions">
892
+ <button class="btn btn-sm btn-outline-secondary">Review</button>
893
+ </div>
894
+ </aside>
895
+ ```
896
+ Severity: `callout--neutral`, `callout--info`, `callout--success`, `callout--warning`, `callout--danger`
897
+ Density: `callout--compact`, `callout--spacious`
898
+ Styles and behavior: `callout--filled`, `callout--actionable`, `callout--dismissible`
899
+ Icon syntax: set `data-component="callout"` and `data-icon="auto"` for automatic semantic icons, or set a specific icon name (example: `data-icon="book-open"`).
900
+
901
+ #### Quick Combos
902
+
903
+ ```html
904
+ <!-- Two columns -->
905
+ <div class="content-wide cols-2"><div>Col 1</div><div>Col 2</div></div>
906
+
907
+ <!-- Stacked cards -->
908
+ <div class="content-medium stack-md">
909
+ <div class="card">Card 1</div>
910
+ <div class="card">Card 2</div>
911
+ </div>
912
+
913
+ <!-- Card with header/body/footer (background + bold are automatic) -->
914
+ <div class="card">
915
+ <div class="card-header">
916
+ <h4>Header</h4>
917
+ </div>
918
+ <div class="card-body stack-sm">
919
+ <p>Body content</p>
920
+ </div>
921
+ <div class="card-footer">
922
+ <button class="btn btn-sm btn-primary">Action</button>
923
+ </div>
924
+ </div>
925
+
926
+ <!-- Centered button -->
927
+ <div class="flex justify-center mt-6"><button class="btn btn-primary">Action</button></div>
928
+
929
+ <!-- Styled lists -->
930
+ <ul class="list-styled"><li>Bulleted</li></ul>
931
+ <ol class="list-numbered"><li>Numbered</li></ol>
932
+ ```
933
+
934
+ #### Design Tokens (CSS Variables)
935
+
936
+ Override in `course/theme.css` to rebrand:
937
+
938
+ | Variable | Purpose |
939
+ |----------|--------|
940
+ | `--color-primary` | Main brand color |
941
+ | `--color-secondary` | Secondary brand color |
942
+ | `--color-accent` | Accent/success color |
943
+ | `--color-success/warning/danger/info` | Semantic colors |
944
+ | `--bg-page/surface/subtle/muted/inset` | Semantic backgrounds (dark-mode aware) |
945
+ | `--shadow-sm/md/lg/xl` | Box shadows |
946
+ | `--gradient-header/success/progress/subtle` | Gradient tokens |
947
+
948
+ #### Typography Scale
949
+
950
+ | Element/Class | Size | Use For |
951
+ |--------------|------|---------|
952
+ | `h1` | 2.5rem | Page/slide title |
953
+ | `h2` | 2rem | Section heading |
954
+ | `h3` | 1.75rem | Subsection heading |
955
+ | `h4` | 1.5rem | Minor heading |
956
+ | `.font-size-lg` | 1.125rem | Lead text, emphasis |
957
+ | Default | 1rem | Body text |
958
+ | `.font-size-sm` | 0.875rem | Small text, captions |
959
+
960
+ #### Debugging
961
+
962
+ | Issue | Check | Fix |
963
+ |-------|-------|-----|
964
+ | Button not clickable | Is `:disabled` set? | Remove disabled attribute |
965
+ | Content too wide | Is `.content-*` wrapper missing? | Add `.content-medium` wrapper |
966
+ | Items not aligned | Is `.flex` on parent? | Add `.flex .align-items-center` |
967
+ | Wrong spacing | Multiple spacing classes? | Use only one (`.m-4` not `.m-4 .m-2`) |
968
+
969
+ #### CSS File Locations
970
+
971
+ | Need | File |
972
+ |------|------|
973
+ | Design tokens | `design-tokens.css` |
974
+ | Layout (content width, stacks, columns) | `02-layout.css` |
975
+ | UI components (hero, steps, timeline, etc.) | `components/*.css` |
976
+ | Component styles | `components/*.css` |
977
+ | Interaction styles | `interactions/*.css` |
978
+ | Utilities (spacing, display, flex, animations) | `utilities/*.css` |
979
+
980
+ ### Icons
981
+
982
+ Use the `iconManager` utility to render icons. Never use inline SVGs.
983
+
984
+ ```javascript
985
+ const { iconManager } = CourseCode;
986
+
987
+ // Basic usage - returns SVG string
988
+ const icon = iconManager.getIcon('info');
989
+
990
+ // With size option
991
+ const largeIcon = iconManager.getIcon('check-circle', { size: 'lg' });
992
+
993
+ // With custom class
994
+ const styledIcon = iconManager.getIcon('alert-triangle', { class: 'icon-warning' });
995
+
996
+ // In HTML template
997
+ container.innerHTML = `
998
+ <span class="icon-text">
999
+ ${iconManager.getIcon('info', { size: 'md', class: 'icon-primary' })}
1000
+ <span>Information</span>
1001
+ </span>
1002
+ `;
1003
+ ```
1004
+
1005
+ **Size options:** `xs` (12px), `sm` (16px), `md` (20px), `lg` (24px), `xl` (32px), `2xl` (48px), `3xl` (64px) — or pass numeric px value
1006
+
1007
+ **Color classes:** `.icon-primary`, `.icon-secondary`, `.icon-success`, `.icon-warning`, `.icon-danger`, `.icon-muted`
1008
+
1009
+ **Layout wrappers:** `.icon-text` (icon + label), `.icon-after` (icon after text), `.icon-above` (stacked)
1010
+
1011
+ **Animation classes:** `.icon-spin` (loading), `.icon-pulse`, `.icon-bounce`
1012
+
1013
+ ### Auto-Wrapping
1014
+
1015
+ Slides are **automatically wrapped** with `.content-medium`. Override per-slide:
1016
+ ```html
1017
+ <div data-content-width="narrow|medium|wide|full">...</div>
1018
+ ```
1019
+
1020
+ Or change global default in `course-config.js`:
1021
+ ```javascript
1022
+ slideDefaults: { contentWidth: 'wide' }
1023
+ ```
1024
+
1025
+ ### Course Layout
1026
+
1027
+ Set `layout` in `course-config.js` to change the overall page structure:
1028
+
1029
+ | Layout | Header | Sidebar | Navigation | Best For |
1030
+ |--------|--------|---------|------------|----------|
1031
+ | `article` (default) | Minimal | Hidden | Floating pill | Documentation-style |
1032
+ | `traditional` | Full | Toggle | Standard footer | Traditional courses |
1033
+ | `focused` | Hidden | Hidden | Floating pill | Single-screen immersive content (no scrolling) |
1034
+ | `presentation` | Hidden | Hidden | Edge arrows | Slideshows |
1035
+ | `canvas` | Hidden | Hidden | None (opt-in) | Custom HTML/CSS/JS with full LMS infrastructure |
1036
+
1037
+ > **Note:** The `focused` and `presentation` layouts are designed for viewport-fit content that doesn't scroll. Content is vertically centered within the viewport. Use `article` if your slides need scrolling.
1038
+
1039
+ > **Note:** The `canvas` layout provides zero framework CSS opinions. Authors bring their own HTML/CSS/JS and get full access to LMS drivers, tracking, engagement, and navigation via `window.CourseCode` and `course-config.js`. Opt back in to nav UI via `navigation.sidebar.enabled` or `navigation.footer.enabled`.
1040
+
1041
+ ```javascript
1042
+ layout: 'article', // Modern web-page feel
1043
+ ```
1044
+
1045
+ #### Canvas Layout — `canvasSlide()` Helper
1046
+
1047
+ For canvas mode, use the `canvasSlide()` helper to minimize boilerplate:
1048
+
1049
+ ```javascript
1050
+ // course/slides/my-page.js
1051
+ const { canvasSlide } = CourseCode;
1052
+
1053
+ export const slide = canvasSlide(`
1054
+ <style>
1055
+ .my-app { background: #0a0a0a; color: white; height: 100vh; }
1056
+ .my-app button { background: #6366f1; border: none; padding: 12px 24px; color: white; border-radius: 8px; }
1057
+ </style>
1058
+ <div class="my-app">
1059
+ <h1>My Custom Page</h1>
1060
+ <button id="next">Continue</button>
1061
+ </div>
1062
+ `, (el, api) => {
1063
+ el.querySelector('#next').onclick = () => api.NavigationActions.goToNextAvailableSlide();
1064
+ });
1065
+ ```
1066
+
1067
+ The first argument is your HTML (including `<style>` tags). The optional second argument is an init callback receiving the DOM element and the `CourseCode` API.
1068
+
1069
+ ### Theme Customization
1070
+
1071
+ Use `course/theme.css` only for: brand colors, custom fonts, organization-specific overrides.
1072
+
1073
+ **The Palette System:** All colors derive from 9 palette values via `color-mix()`. Override these in `theme.css` to rebrand — all semantic colors, light/dark variants, alpha values, and dark mode automatically cascade.
1074
+
1075
+ ```css
1076
+ :root {
1077
+ /* Core palette - change these to rebrand */
1078
+ --palette-blue: #0057b7; /* → Primary */
1079
+ --palette-blue-light: #1e40af; /* → Info */
1080
+ --palette-green: #059669; /* → Success */
1081
+ --palette-yellow: #f7b801; /* → Accent */
1082
+ --palette-amber: #f18701; /* → Secondary / Warning */
1083
+ --palette-orange: #f35b04; /* → Brand vibrant */
1084
+ --palette-red: #c7322b; /* → Danger / Error */
1085
+
1086
+ /* Optional: override derived values if needed */
1087
+ --color-primary: var(--palette-blue);
1088
+ --gradient-header: linear-gradient(135deg, var(--color-primary), var(--color-gray-700));
1089
+
1090
+ /* Component style variants */
1091
+ --tab-style: pills; /* default | pills | buttons | minimal | boxed */
1092
+ --accordion-style: flush; /* default | flush | separated | minimal | boxed */
1093
+ --card-style: elevated; /* default | outlined | elevated | flat | accent-top */
1094
+ }
1095
+ ```
1096
+
1097
+ **Typography:** Override font families and sizes for your brand. Load custom web fonts with `@import` at the top of `theme.css`, then set the tokens:
1098
+
1099
+ ```css
1100
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
1101
+ :root {
1102
+ --font-family-sans: 'Inter', sans-serif;
1103
+ --font-family-display: 'Inter', sans-serif;
1104
+ --font-family-mono: ui-monospace, monospace;
1105
+ --font-size-base: 1rem; /* Body text (16px default) */
1106
+ --font-size-4xl: 2.25rem; /* Main headings (36px default) */
1107
+ }
1108
+ ```
1109
+
1110
+ **Layout sizing:** Control header, sidebar, and footer dimensions:
1111
+
1112
+ ```css
1113
+ :root {
1114
+ --header-height: 72px;
1115
+ --header-title-size: 1.125rem; /* Course title in header */
1116
+ --header-title-weight: 700;
1117
+ --header-padding-x: 1.5rem;
1118
+ --sidebar-width: 280px;
1119
+ --footer-padding-y: 0.75rem;
1120
+ }
1121
+ ```
1122
+
1123
+ **Use data attributes** on `<html>` or individual components for variants:
1124
+ - `data-header-style="gradient|solid|minimal|dark|transparent"`
1125
+ - `data-header-border="accent|thin|none|gradient"`
1126
+ - `data-sidebar-style="default|dark|primary|minimal"`
1127
+ - `data-footer-layout="default|centered|minimal|floating"`
1128
+ - `data-tab-style="pills"` (on specific component to override theme default)
1129
+
1130
+ ---
1131
+
1132
+ ## Slide Navigation
1133
+
1134
+ ### Automatic Behavior
1135
+
1136
+ - **First slide**: Previous button auto-disabled
1137
+ - **Last slide**: Next button auto-disabled
1138
+ - **Gating**: Next button disabled until requirements met
1139
+
1140
+ No configuration needed for standard sequential navigation.
1141
+
1142
+ ### Navigation Controls (Optional)
1143
+
1144
+ Override default Next/Previous behavior for special flows:
1145
+
1146
+ ```javascript
1147
+ navigation: {
1148
+ sequential: true,
1149
+ controls: {
1150
+ exitTarget: 'assessment', // Next button → specific slide (for remediation loops)
1151
+ nextTarget: 'custom-slide', // Alternative to exitTarget
1152
+ previousTarget: 'intro' // Override Previous button target
1153
+ }
1154
+ }
1155
+ ```
1156
+
1157
+ **Use case: Remedial slide that loops back to assessment:**
1158
+
1159
+ ```javascript
1160
+ {
1161
+ id: 'remedial',
1162
+ component: '@slides/remedial.js',
1163
+ menu: { hidden: true },
1164
+ navigation: {
1165
+ controls: { exitTarget: 'assessment' }, // Next → back to assessment
1166
+ gating: {
1167
+ conditions: [{ type: 'assessmentStatus', assessmentId: 'final-exam', requires: 'failed' }]
1168
+ },
1169
+ sequence: {
1170
+ includeByDefault: false,
1171
+ includeWhen: { type: 'assessmentStatus', assessmentId: 'final-exam', requires: 'failed' },
1172
+ insert: { position: 'after', slideId: 'assessment' }
1173
+ }
1174
+ }
1175
+ }
1176
+ ```
1177
+
1178
+ ### Gating (Lock Slides)
1179
+
1180
+ ```javascript
1181
+ navigation: {
1182
+ gating: {
1183
+ mode: 'all', // 'all' or 'any'
1184
+ message: 'Complete previous content first.',
1185
+ conditions: [
1186
+ { type: 'objectiveStatus', objectiveId: 'core-content', completion_status: 'completed' },
1187
+ { type: 'assessmentStatus', assessmentId: 'final-exam', requires: 'passed' },
1188
+ { type: 'flag', key: 'intro-done', equals: true }
1189
+ ]
1190
+ }
1191
+ }
1192
+ ```
1193
+
1194
+ ### Dynamic Sequence (Conditional Slides)
1195
+
1196
+ Include slides only when conditions are met:
1197
+
1198
+ ```javascript
1199
+ navigation: {
1200
+ sequence: {
1201
+ includeByDefault: false, // Hidden until condition met
1202
+ includeWhen: { type: 'assessmentStatus', assessmentId: 'exam', requires: 'failed' },
1203
+ insert: { position: 'after', slideId: 'assessment' } // Where to insert
1204
+ }
1205
+ }
1206
+ ```
1207
+
1208
+ ### Document Gallery
1209
+
1210
+ Collapsible sidebar gallery that auto-discovers files from a configured directory at build time. Opens documents in the lightbox.
1211
+
1212
+ ```javascript
1213
+ // In course-config.js → navigation
1214
+ documentGallery: {
1215
+ enabled: true,
1216
+ directory: 'assets/docs', // Relative to course/
1217
+ label: 'Resources', // Toggle button label
1218
+ icon: 'file-text', // Icon key
1219
+ allowDownloads: false, // Show download button in lightbox
1220
+ fileTypes: ['pdf', 'md', 'jpg', 'png'] // File types to include
1221
+ }
1222
+ ```
1223
+
1224
+ **File placement:** Put documents in `course/assets/docs/`. For PDF thumbnails, place `<filename>_thumbnail.png` alongside the PDF.
1225
+
1226
+ **Behavior:** Gallery collapses the nav menu when expanded. Resets to collapsed when sidebar closes.
1227
+
1228
+ ---
1229
+
1230
+ ## Engagement Tracking
1231
+
1232
+ All slides MUST have `engagement` config. Set `required: false` for no tracking:
1233
+
1234
+ ```javascript
1235
+ engagement: {
1236
+ required: true,
1237
+ mode: 'all', // 'all' or 'any'
1238
+ message: 'Custom requirement text', // Optional: overrides entire tooltip
1239
+ requirements: [
1240
+ { type: 'viewAllTabs' },
1241
+ { type: 'viewAllPanels' }, // accordions
1242
+ { type: 'viewAllFlipCards' },
1243
+ { type: 'viewAllHotspots' },
1244
+ { type: 'interactionComplete', interactionId: 'q1', label: 'Quiz 1' },
1245
+ { type: 'allInteractionsComplete' },
1246
+ { type: 'scrollDepth', percentage: 80 },
1247
+ { type: 'timeOnSlide', minSeconds: 120 },
1248
+ { type: 'slideAudioComplete' },
1249
+ { type: 'audioComplete', audioId: 'my-audio' },
1250
+ { type: 'modalAudioComplete', modalId: 'my-modal' },
1251
+ { type: 'flag', key: 'custom-step-complete', equals: true },
1252
+ { type: 'allFlags', flags: ['step1', 'step2', 'step3'] }
1253
+ ]
1254
+ // showIndicator defaults to true when required is true; set false to hide
1255
+ }
1256
+ ```
1257
+
1258
+ ### Tooltip Customization
1259
+
1260
+ - **`message`** (engagement-level): Overrides entire tooltip text for the slide
1261
+ - **`label`** (requirement-level): Customizes the interaction name in dynamic tooltips
1262
+
1263
+ Without customization, tooltips auto-generate from IDs (e.g., `my-quiz` → "Complete: My Quiz").
1264
+
1265
+ ### Custom Flag-Based Tracking
1266
+
1267
+ For custom interactions not covered by built-in components:
1268
+
1269
+ ```javascript
1270
+ const { flagManager } = CourseCode;
1271
+
1272
+ root.addEventListener('click', (e) => {
1273
+ if (e.target.dataset.hotspot) {
1274
+ flagManager.setFlag(`hotspot-${e.target.dataset.hotspot}-clicked`, true);
1275
+ }
1276
+ });
1277
+ ```
1278
+
1279
+ ---
1280
+
1281
+ ## Declarative UI Components
1282
+
1283
+ Components auto-initialize via `data-component` attributes. No imports needed—just use the HTML patterns. CSS classes, icons, and interaction factories are also globally available; never add `import` statements for them.
1284
+
1285
+ > **MCP users:** Use `coursecode_component_catalog` for full schemas and HTML templates. Below covers usage patterns and notes the catalog doesn't provide.
1286
+
1287
+ ### Quick Reference
1288
+
1289
+ | Component | Attribute | Engagement Tracking |
1290
+ |-----------|-----------|---------------------|
1291
+ | Tabs | `data-component="tabs"` | `viewAllTabs` |
1292
+ | Accordion | `data-component="accordion"` | `viewAllPanels` |
1293
+ | Flip Card | `data-component="flip-card"` | `viewAllFlipCards` |
1294
+ | Interactive Timeline | `data-component="interactive-timeline"` | `viewAllTimelineEvents` |
1295
+ | Interactive Image | `data-component="interactive-image"` | `viewAllHotspots` |
1296
+ | Modal Trigger | `data-component="modal-trigger"` | `viewAllModals` |
1297
+ | Carousel | `data-component="carousel"` | - |
1298
+ | Collapse | `data-component="collapse"` | - |
1299
+ | Dropdown | `data-component="dropdown"` | - |
1300
+ | Alert | `data-component="alert"` | - |
1301
+ | Progress | `data-component="progress"` | - |
1302
+ | Embed Frame | `data-component="embed-frame"` | Custom (via `flag`) |
1303
+
1304
+ ### Tabs
1305
+
1306
+ ```html
1307
+ <div data-component="tabs">
1308
+ <div class="tab-list">
1309
+ <button class="tab-button" data-action="select-tab" aria-controls="panel-1">Tab 1</button>
1310
+ <button class="tab-button" data-action="select-tab" aria-controls="panel-2">Tab 2</button>
1311
+ </div>
1312
+ <div id="panel-1" class="tab-content">Content 1</div>
1313
+ <div id="panel-2" class="tab-content">Content 2</div>
1314
+ </div>
1315
+ ```
1316
+
1317
+ ### Accordion
1318
+
1319
+ ```html
1320
+ <div id="faq-accordion" class="accordion" data-component="accordion" data-mode="single">
1321
+ <div data-title="Section 1">Content 1</div>
1322
+ <div data-title="Section 2">Content 2</div>
1323
+ </div>
1324
+ ```
1325
+
1326
+ **Required:** `id` must be set for engagement tracking persistence.
1327
+
1328
+ `data-mode`: `single` (one panel open) | `multi` (multiple panels open)
1329
+
1330
+ ### Flip Card
1331
+
1332
+ ```html
1333
+ <div class="flip-card" data-component="flip-card" data-flip-card-id="card-1">
1334
+ <div class="flip-card-inner">
1335
+ <div class="flip-card-front">
1336
+ <h3>Front Side</h3>
1337
+ <p>Click to flip</p>
1338
+ </div>
1339
+ <div class="flip-card-back">
1340
+ <h3>Back Side</h3>
1341
+ <p>Hidden content revealed</p>
1342
+ </div>
1343
+ </div>
1344
+ </div>
1345
+ ```
1346
+
1347
+ **Required:** `data-flip-card-id` must be unique for engagement tracking.
1348
+
1349
+ **Back Side Variants:** Add to `.flip-card-back`: `.bg-light`, `.bg-primary-subtle`, `.bg-success-subtle`, `.bg-warning-subtle`, `.bg-danger-subtle`, `.bg-info-subtle`, `.bg-secondary`, `.bg-dark`
1350
+
1351
+ ### Interactive Timeline
1352
+
1353
+ ```html
1354
+ <div class="interactive-timeline" data-component="interactive-timeline">
1355
+ <div class="timeline-event" data-event-id="event-1">
1356
+ <div class="timeline-marker"></div>
1357
+ <div class="timeline-date">2020</div>
1358
+ <div class="timeline-summary">
1359
+ <h4>Event Title</h4>
1360
+ <p>Brief description</p>
1361
+ </div>
1362
+ <div class="timeline-details">
1363
+ <p>Expanded content revealed on click...</p>
1364
+ </div>
1365
+ </div>
1366
+ </div>
1367
+ ```
1368
+
1369
+ **Required:** `data-event-id` must be unique for engagement tracking.
1370
+
1371
+ **Options:** Add `data-timeline-mode="sequential"` to require events be viewed in order.
1372
+
1373
+ ### Carousel
1374
+
1375
+ ```html
1376
+ <div class="carousel" data-component="carousel">
1377
+ <div class="carousel-track">
1378
+ <div class="carousel-slide">Slide 1</div>
1379
+ <div class="carousel-slide">Slide 2</div>
1380
+ <div class="carousel-slide">Slide 3</div>
1381
+ </div>
1382
+ <button class="carousel-button prev" data-action="prev-slide" aria-label="Previous">&#10094;</button>
1383
+ <button class="carousel-button next" data-action="next-slide" aria-label="Next">&#10095;</button>
1384
+ <div class="carousel-dots"></div>
1385
+ </div>
1386
+ ```
1387
+
1388
+ ### Collapse (Show/Hide)
1389
+
1390
+ ```html
1391
+ <div data-component="collapse">
1392
+ <div class="collapse-panel" id="transcript">
1393
+ <p>Expandable content here...</p>
1394
+ </div>
1395
+ <button class="collapse-trigger" data-action="toggle-collapse" aria-controls="transcript" aria-expanded="false">
1396
+ <span class="collapse-text-show">Show Transcript</span>
1397
+ <span class="collapse-text-hide">Hide Transcript</span>
1398
+ </button>
1399
+ </div>
1400
+ ```
1401
+
1402
+ ### Alert (Dismissible)
1403
+
1404
+ ```html
1405
+ <div class="alert alert-warning" data-component="alert">
1406
+ <p>This is a warning message.</p>
1407
+ <button class="alert-close" data-action="dismiss-alert" aria-label="Dismiss">&times;</button>
1408
+ </div>
1409
+ ```
1410
+
1411
+ Variants: `.alert-info`, `.alert-success`, `.alert-warning`, `.alert-danger`
1412
+
1413
+ ### Progress Bar
1414
+
1415
+ ```html
1416
+ <div class="progress-bar" data-component="progress" id="my-progress" data-initial-value="25">
1417
+ <div class="progress-bar-fill"></div>
1418
+ <span class="progress-bar-text">25%</span>
1419
+ </div>
1420
+ ```
1421
+
1422
+ Update programmatically:
1423
+ ```javascript
1424
+ const { updateProgress } = CourseCode;
1425
+ updateProgress('my-progress', 75);
1426
+ ```
1427
+
1428
+ ### Embed Frame (Sandboxed Custom Apps)
1429
+
1430
+ Embed custom HTML/JS applications with complete CSS isolation and JavaScript sandboxing:
1431
+
1432
+ ```html
1433
+ <div data-component="embed-frame"
1434
+ data-src="assets/widgets/my-app.html"
1435
+ data-embed-id="custom-widget"
1436
+ data-aspect-ratio="16/9">
1437
+ </div>
1438
+ ```
1439
+
1440
+ **Attributes:**
1441
+ - `data-src` - Path to HTML file (relative to `course/`)
1442
+ - `data-embed-id` - Unique ID for tracking
1443
+ - `data-aspect-ratio` - Optional (e.g., `"16/9"`, `"4/3"`). Omit for auto-height mode.
1444
+
1445
+ **Communication API (from embedded content):**
1446
+ ```javascript
1447
+ // Set a flag (triggers engagement re-evaluation)
1448
+ parent.postMessage({ type: 'coursecode:flag', key: 'widget-complete', value: true }, '*');
1449
+
1450
+ // Log to framework console
1451
+ parent.postMessage({ type: 'coursecode:log', level: 'info', message: 'Widget ready' }, '*');
1452
+
1453
+ // Request resize (auto-height mode only)
1454
+ parent.postMessage({ type: 'coursecode:resize', height: 400 }, '*');
1455
+ ```
1456
+
1457
+ **Engagement tracking:** Use `flag` requirement with the key set by the embedded widget.
1458
+
1459
+
1460
+ ### Dropdown
1461
+
1462
+ ```html
1463
+ <div id="role-dropdown" data-component="dropdown">
1464
+ <button class="dropdown-trigger" data-action="toggle-dropdown">Select Role</button>
1465
+ <div class="dropdown-menu">
1466
+ <button class="dropdown-item" data-action="select-item" data-value="engineer">Engineer</button>
1467
+ <button class="dropdown-item" data-action="select-item" data-value="manager">Manager</button>
1468
+ </div>
1469
+ </div>
1470
+ ```
1471
+
1472
+ ### Interactive Image (Hotspots)
1473
+
1474
+ ```html
1475
+ <div data-component="interactive-image" id="diagram">
1476
+ <img src="assets/images/diagram.png" alt="Diagram" />
1477
+ <button data-hotspot-id="zone1" data-title="Component A" data-body="Description here..."
1478
+ class="interactive-image-hotspot" style="top: 20%; left: 30%;">A</button>
1479
+ <button data-hotspot-id="zone2" data-title="Component B" data-body="More details..."
1480
+ class="interactive-image-hotspot" style="top: 50%; left: 60%;">B</button>
1481
+ </div>
1482
+ ```
1483
+
1484
+ Hotspots open modals by default. Link to accordion with `data-accordion-id="accordion-id"`.
1485
+
1486
+ **Hotspot data attributes:**
1487
+
1488
+ | Attribute | Values | Description |
1489
+ |-----------|--------|-------------|
1490
+ | `data-layout` | `inline` | Use for flexbox/grid layouts (removes absolute positioning) |
1491
+ | `data-shape` | `circle`, `rect`, `rounded` | Hotspot shape |
1492
+ | `data-color` | `primary`, `success`, `info`, `warning`, `danger`, etc. | Hotspot color |
1493
+ | `data-fill` | `solid`, `transparent`, `semi` | Fill style |
1494
+ | `data-variant` | `area` | Transparent overlay style for regions |
1495
+ | `data-pulse` | `false` | Disable pulse animation (per-hotspot or on container) |
1496
+ | `data-scale` | `false` | Disable scale transform on hover/active states |
1497
+
1498
+ **Inline layout variant:** For hotspots in flexbox/grid layouts (not positioned over an image):
1499
+
1500
+ ```html
1501
+ <div data-component="interactive-image" data-accordion-id="my-accordion" id="my-diagram">
1502
+ <div class="flex flex-col gap-2">
1503
+ <button class="interactive-image-hotspot" data-hotspot-id="item1"
1504
+ data-layout="inline" data-shape="rect" data-fill="solid" data-color="success">
1505
+ Item 1
1506
+ </button>
1507
+ <button class="interactive-image-hotspot" data-hotspot-id="item2"
1508
+ data-layout="inline" data-shape="rect" data-fill="solid" data-color="info">
1509
+ Item 2
1510
+ </button>
1511
+ </div>
1512
+ </div>
1513
+ ```
1514
+
1515
+
1516
+
1517
+ ### Value Display (Reactive Text)
1518
+
1519
+ ```html
1520
+ <input type="range" id="slider" min="0" max="100" value="50" />
1521
+ <div data-component="value-display"
1522
+ data-source="#slider"
1523
+ data-event="input"
1524
+ data-format="Current value: {value}%">
1525
+ </div>
1526
+ ```
1527
+
1528
+ ### Tooltip
1529
+
1530
+ ```html
1531
+ <!-- Basic -->
1532
+ <span data-tooltip="Helpful hint">Hover me</span>
1533
+
1534
+ <!-- With options -->
1535
+ <span data-tooltip="Details here"
1536
+ data-tooltip-position="bottom"
1537
+ data-tooltip-delay="300"
1538
+ data-tooltip-theme="light">Info</span>
1539
+ ```
1540
+
1541
+ | Attribute | Values | Default |
1542
+ |-----------|--------|---------|
1543
+ | `data-tooltip-position` | `top`, `bottom`, `left`, `right` | `top` |
1544
+ | `data-tooltip-delay` | ms | `0` |
1545
+ | `data-tooltip-theme` | `dark`, `light` | `dark` |
1546
+ | `data-tooltip-width` | px | `280` |
1547
+
1548
+ ---
1549
+
1550
+ ## Global UI Actions
1551
+
1552
+ These are **global singletons** managed by the framework—use them anywhere without initialization.
1553
+
1554
+ ### Notifications
1555
+
1556
+ ```javascript
1557
+ const { showNotification } = CourseCode;
1558
+
1559
+ showNotification('Saved successfully', 'success'); // Auto-dismiss after 5s
1560
+ showNotification('Check your input', 'warning', 8000); // Custom duration (ms)
1561
+ showNotification('Connection lost', 'error'); // Stays until dismissed
1562
+ showNotification('FYI: New feature', 'info', 3000);
1563
+ ```
1564
+
1565
+ **Types:** `success`, `error`, `warning`, `info`
1566
+
1567
+ **Declarative:**
1568
+ ```html
1569
+ <button data-action="show-notification" data-type="success" data-message="Done!">Show</button>
1570
+ ```
1571
+
1572
+ ### Modals (Programmatic)
1573
+
1574
+ ```javascript
1575
+ const { Modal } = CourseCode;
1576
+
1577
+ Modal.show({
1578
+ title: 'Confirm Action',
1579
+ body: '<p>Are you sure?</p>',
1580
+ footer: `
1581
+ <button class="btn btn-secondary" data-action="close-modal">Cancel</button>
1582
+ <button class="btn btn-primary" data-action="confirm">Confirm</button>
1583
+ `,
1584
+ config: {
1585
+ closeOnBackdrop: true, // Click outside to close
1586
+ closeOnEscape: true // ESC key to close
1587
+ },
1588
+ audio: { // Optional narration
1589
+ src: 'assets/audio/modal.mp3',
1590
+ autoplay: true,
1591
+ required: true, // Must complete for engagement
1592
+ completionThreshold: 0.9
1593
+ },
1594
+ onOpen: () => { /* callback */ },
1595
+ onClose: () => { /* callback */ }
1596
+ });
1597
+
1598
+ Modal.hide(); // Close programmatically
1599
+ ```
1600
+
1601
+ ### Modals (Declarative Triggers)
1602
+
1603
+ ```html
1604
+ <!-- Basic trigger (body from element) -->
1605
+ <button data-component="modal-trigger"
1606
+ data-title="Learn More"
1607
+ data-body="#my-modal-content">
1608
+ Open Modal
1609
+ </button>
1610
+ <template id="my-modal-content">
1611
+ <p>Modal content here. Use &lt;template&gt; to hide from page.</p>
1612
+ </template>
1613
+
1614
+ <!-- With audio narration -->
1615
+ <button data-component="modal-trigger"
1616
+ data-modal-id="info-modal"
1617
+ data-title="Important Info"
1618
+ data-body="#info-content"
1619
+ data-audio-src="assets/audio/info.mp3"
1620
+ data-audio-required="true">
1621
+ Info
1622
+ </button>
1623
+ ```
1624
+
1625
+ **Engagement tracking:** Use `viewAllModals` requirement to ensure learners open all modals. Use `modalAudioComplete` if audio must be heard.
1626
+
1627
+ ### Navigation
1628
+
1629
+ ```javascript
1630
+ const { NavigationActions } = CourseCode;
1631
+ NavigationActions.goToSlide('slide-id', context);
1632
+ ```
1633
+
1634
+ ---
1635
+
1636
+ ## Course Completion & Feedback
1637
+
1638
+ When users complete the course and click Exit on the final slide, a **built-in completion modal** appears automatically.
1639
+
1640
+ ### Configuration
1641
+
1642
+ ```javascript
1643
+ // In course-config.js
1644
+ completion: {
1645
+ promptForRating: true, // Show 5-star rating (Likert)
1646
+ promptForComments: true // Show comment textarea
1647
+ }
1648
+ ```
1649
+
1650
+ ### What Gets Stored
1651
+
1652
+ | Input | SCORM Storage | Details |
1653
+ |-------|---------------|---------|
1654
+ | Star rating | `cmi.interactions.n.*` | Likert interaction with id `'course-rating'` |
1655
+ | Comment | `cmi.comments_from_learner.n.*` | With location `'course-completion'` |
1656
+
1657
+ Set both to `false` for a simple "Congratulations" message without feedback collection.
1658
+
1659
+ ---
1660
+
1661
+ ## Environment Config
1662
+
1663
+ In `course/course-config.js`:
1664
+
1665
+ ```javascript
1666
+ environment: {
1667
+ disableBeforeUnloadGuard: false, // false (prod) = warning on F5/close, true (dev) = no warning
1668
+
1669
+ // --- External Communications (all optional, config-driven) ---
1670
+
1671
+ // Error alerts → webhook (e.g., Cloudflare Worker → email via Resend)
1672
+ errorReporting: {
1673
+ endpoint: 'https://your-worker.workers.dev/errors',
1674
+ includeContext: true, // Include course/slide info (default: true)
1675
+ enableUserReports: true // Add "Report Issue" to settings menu
1676
+ },
1677
+
1678
+ // Learning records → external analytics system
1679
+ dataReporting: {
1680
+ endpoint: 'https://your-endpoint.workers.dev/data',
1681
+ batchSize: 10, // Flush after N records (default: 10)
1682
+ flushInterval: 30000, // Or flush every 30s (default)
1683
+ includeContext: true // Include course metadata (default: true)
1684
+ },
1685
+
1686
+ // Pub/sub channel → course-to-course communication via relay
1687
+ channel: {
1688
+ endpoint: 'https://your-relay.workers.dev',
1689
+ channelId: 'session-abc-123' // Shared across all connected instances
1690
+ }
1691
+ }
1692
+ ```
1693
+
1694
+ ### External Communications
1695
+
1696
+ Three optional, config-driven tools for outbound communication. Each activates when its `endpoint` is set — no code changes needed.
1697
+
1698
+ | Tool | What it sends | Transport | Example backend |
1699
+ |------|--------------|-----------|-----------------|
1700
+ | **Error Reporter** | Framework errors (`*:error` events) | POST per error (60s dedup) | `cloudflare-error-worker.js` |
1701
+ | **Data Reporter** | Assessment results, objectives, interactions | Batched POST + `sendBeacon` on unload | `cloudflare-data-worker.js` |
1702
+ | **Course Channel** | Any JSON (content-agnostic) | POST to send, SSE to receive | `cloudflare-channel-relay.js` |
1703
+
1704
+ **Error Reporting** subscribes to all `*:error` EventBus events and sends to your endpoint. Test with `coursecode test-errors`.
1705
+
1706
+ **Data Reporting** batches learning records (assessments, objectives, interactions) and flushes on batch size or timer. Uses `sendBeacon` on page close.
1707
+
1708
+ **Course Channel** is a generic pub/sub pipe. Send any JSON; receive via EventBus. The relay endpoint is a dumb fan-out router — it doesn't interpret messages. Use for live sync, polling, instructor commands, or anything else.
1709
+
1710
+ ```javascript
1711
+ // Send (from slide code)
1712
+ window.CourseCode.sendChannelMessage({ type: 'navigate', slideId: 'slide-03' });
1713
+
1714
+ // Receive
1715
+ eventBus.on('channel:message', (data) => { /* any JSON */ });
1716
+ ```
1717
+
1718
+ All example backends are in `framework/docs/examples/`.
1719
+
1720
+ > **Local dev:** Error and data reporters are automatically disabled during watch builds (`coursecode preview`, `coursecode dev`). Production builds (`coursecode build`) send reports normally.
1721
+
1722
+ ---
1723
+
1724
+ ## Icon System
1725
+
1726
+ The framework provides a comprehensive, standardized SVG icon system (Lucide). 163+ icons available.
1727
+
1728
+ > **MCP users:** Use `coursecode_icon_catalog` to browse all available icons by category and get SVG content.
1729
+
1730
+ ### Usage in Course Content
1731
+
1732
+ **Standard Usage (Components)**
1733
+ Most components handle icons automatically based on your `course-config.js` settings.
1734
+
1735
+ **Manual Usage (Slide JavaScript)**
1736
+ ```javascript
1737
+ const { iconManager } = CourseCode;
1738
+
1739
+ container.innerHTML = `
1740
+ <span class="icon-text">
1741
+ ${iconManager.getIcon('info-circle', { size: 'md', class: 'icon-primary' })}
1742
+ <span>Information</span>
1743
+ </span>
1744
+ `;
1745
+ ```
1746
+
1747
+ **Size options:** `xs` (12px), `sm` (16px), `md` (20px), `lg` (24px), `xl` (32px), `2xl` (48px), `3xl` (64px)
1748
+
1749
+ **Color classes:** `.icon-primary`, `.icon-secondary`, `.icon-success`, `.icon-warning`, `.icon-danger`, `.icon-muted`
1750
+
1751
+ **Layout wrappers:** `.icon-text` (icon + label), `.icon-after` (icon after text), `.icon-above` (stacked)
1752
+
1753
+ ### Adding Custom Icons
1754
+
1755
+ Add custom icons to `course/icons.js` (never modify the framework):
1756
+
1757
+ ```javascript
1758
+ // course/icons.js
1759
+ export const customIcons = {
1760
+ 'rocket': '<path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84 1.25-1.82 1.64-2.86z"/>'
1761
+ };
1762
+ ```
1763
+
1764
+ Then use anywhere:
1765
+
1766
+ ```javascript
1767
+ // In course-config.js
1768
+ menu: { label: 'Launch', icon: 'rocket' }
1769
+
1770
+ // In slide code
1771
+ const { iconManager } = CourseCode;
1772
+ const rocketIcon = iconManager.getIcon('rocket', { size: 'lg' });
1773
+ ```