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.
- package/LICENSE +21 -0
- package/README.md +322 -0
- package/THIRD_PARTY_NOTICES.md +22 -0
- package/bin/cli.js +331 -0
- package/framework/assets/logo-coursecode-black.svg +14 -0
- package/framework/assets/logo-coursecode-white.svg +14 -0
- package/framework/assets/logo-coursecode.svg +14 -0
- package/framework/css/01-base.css +160 -0
- package/framework/css/02-layout.css +499 -0
- package/framework/css/accessibility.css +834 -0
- package/framework/css/components/accordions.css +710 -0
- package/framework/css/components/assessments.css +520 -0
- package/framework/css/components/audio-player.css +570 -0
- package/framework/css/components/badges.css +80 -0
- package/framework/css/components/breadcrumbs.css +87 -0
- package/framework/css/components/buttons.css +707 -0
- package/framework/css/components/callouts.css +1280 -0
- package/framework/css/components/cards.css +475 -0
- package/framework/css/components/carousel.css +193 -0
- package/framework/css/components/checkbox-group.css +123 -0
- package/framework/css/components/checklist.css +203 -0
- package/framework/css/components/collapse.css +96 -0
- package/framework/css/components/comparison.css +33 -0
- package/framework/css/components/content-image.css +36 -0
- package/framework/css/components/document-gallery.css +425 -0
- package/framework/css/components/dropdown.css +115 -0
- package/framework/css/components/embed-frame.css +142 -0
- package/framework/css/components/engagement.css +412 -0
- package/framework/css/components/features.css +35 -0
- package/framework/css/components/flip-cards.css +253 -0
- package/framework/css/components/footer.css +353 -0
- package/framework/css/components/forms.css +294 -0
- package/framework/css/components/hero.css +216 -0
- package/framework/css/components/images.css +528 -0
- package/framework/css/components/interactive-timeline.css +274 -0
- package/framework/css/components/intro-cards.css +30 -0
- package/framework/css/components/lightbox.css +666 -0
- package/framework/css/components/loading.css +65 -0
- package/framework/css/components/modals.css +235 -0
- package/framework/css/components/notifications.css +107 -0
- package/framework/css/components/quote.css +150 -0
- package/framework/css/components/sidebar.css +684 -0
- package/framework/css/components/slide-header.css +52 -0
- package/framework/css/components/spinner.css +62 -0
- package/framework/css/components/stats.css +44 -0
- package/framework/css/components/steps.css +232 -0
- package/framework/css/components/tables.css +90 -0
- package/framework/css/components/tabs.css +347 -0
- package/framework/css/components/timeline.css +154 -0
- package/framework/css/components/toggle.css +95 -0
- package/framework/css/components/tooltip.css +226 -0
- package/framework/css/components/video-player.css +438 -0
- package/framework/css/design-tokens.css +707 -0
- package/framework/css/framework.css +86 -0
- package/framework/css/interactions/accessibility.css +75 -0
- package/framework/css/interactions/base.css +92 -0
- package/framework/css/interactions/drag-drop.css +295 -0
- package/framework/css/interactions/fill-in-the-blank.css +236 -0
- package/framework/css/interactions/hotspots.css +69 -0
- package/framework/css/interactions/index.css +45 -0
- package/framework/css/interactions/interactive-image.css +359 -0
- package/framework/css/interactions/likert.css +126 -0
- package/framework/css/interactions/matching.css +354 -0
- package/framework/css/interactions/numeric-input.css +78 -0
- package/framework/css/interactions/sequencing.css +378 -0
- package/framework/css/interactions/true-false.css +177 -0
- package/framework/css/layouts/article.css +258 -0
- package/framework/css/layouts/base.css +30 -0
- package/framework/css/layouts/canvas.css +38 -0
- package/framework/css/layouts/focused.css +236 -0
- package/framework/css/layouts/index.css +29 -0
- package/framework/css/layouts/presentation.css +191 -0
- package/framework/css/layouts/traditional.css +52 -0
- package/framework/css/responsive.css +439 -0
- package/framework/css/utilities/accessibility-utils.css +59 -0
- package/framework/css/utilities/animations.css +419 -0
- package/framework/css/utilities/borders.css +72 -0
- package/framework/css/utilities/colors.css +76 -0
- package/framework/css/utilities/container.css +46 -0
- package/framework/css/utilities/decorative.css +442 -0
- package/framework/css/utilities/display.css +257 -0
- package/framework/css/utilities/flexbox.css +80 -0
- package/framework/css/utilities/grid.css +69 -0
- package/framework/css/utilities/icons.css +534 -0
- package/framework/css/utilities/lists.css +190 -0
- package/framework/css/utilities/spacing.css +167 -0
- package/framework/css/utilities/tables.css +81 -0
- package/framework/css/utilities/typography.css +159 -0
- package/framework/css/utilities/visibility.css +117 -0
- package/framework/docs/COURSE_AUTHORING_GUIDE.md +1773 -0
- package/framework/docs/COURSE_OUTLINE_GUIDE.md +725 -0
- package/framework/docs/COURSE_OUTLINE_TEMPLATE.md +161 -0
- package/framework/docs/DATA_MODEL.md +409 -0
- package/framework/docs/FRAMEWORK_GUIDE.md +1088 -0
- package/framework/docs/USER_GUIDE.md +583 -0
- package/framework/docs/examples/cloudflare-channel-relay.js +169 -0
- package/framework/docs/examples/cloudflare-data-worker.js +102 -0
- package/framework/docs/examples/cloudflare-error-worker.js +228 -0
- package/framework/index.html +175 -0
- package/framework/js/app/AppActions.js +410 -0
- package/framework/js/app/AppState.js +225 -0
- package/framework/js/app/AppUI.js +616 -0
- package/framework/js/assessment/AssessmentActions.js +615 -0
- package/framework/js/assessment/AssessmentFactory.js +471 -0
- package/framework/js/assessment/AssessmentState.js +322 -0
- package/framework/js/assessment/AssessmentUI.js +451 -0
- package/framework/js/automation/api-engagement.js +196 -0
- package/framework/js/automation/api-interactions.js +167 -0
- package/framework/js/automation/api.js +242 -0
- package/framework/js/automation/index.js +41 -0
- package/framework/js/components/interactions/drag-drop.js +884 -0
- package/framework/js/components/interactions/fill-in.js +535 -0
- package/framework/js/components/interactions/hotspot.js +702 -0
- package/framework/js/components/interactions/interaction-base.js +511 -0
- package/framework/js/components/interactions/likert.js +301 -0
- package/framework/js/components/interactions/matching.js +699 -0
- package/framework/js/components/interactions/multiple-choice.js +377 -0
- package/framework/js/components/interactions/numeric.js +271 -0
- package/framework/js/components/interactions/sequencing.js +423 -0
- package/framework/js/components/interactions/true-false.js +241 -0
- package/framework/js/components/ui-components/accordion.js +442 -0
- package/framework/js/components/ui-components/alert.js +88 -0
- package/framework/js/components/ui-components/audio-player.js +1193 -0
- package/framework/js/components/ui-components/callout.js +121 -0
- package/framework/js/components/ui-components/carousel.js +145 -0
- package/framework/js/components/ui-components/checkbox-group.js +87 -0
- package/framework/js/components/ui-components/checklist.js +40 -0
- package/framework/js/components/ui-components/collapse.js +114 -0
- package/framework/js/components/ui-components/comparison.js +30 -0
- package/framework/js/components/ui-components/conditional-display.js +150 -0
- package/framework/js/components/ui-components/content-image.js +41 -0
- package/framework/js/components/ui-components/dropdown.js +262 -0
- package/framework/js/components/ui-components/embed-frame.js +274 -0
- package/framework/js/components/ui-components/features.js +33 -0
- package/framework/js/components/ui-components/flip-card.js +230 -0
- package/framework/js/components/ui-components/form-validator.js +76 -0
- package/framework/js/components/ui-components/hero.js +49 -0
- package/framework/js/components/ui-components/index.js +12 -0
- package/framework/js/components/ui-components/interactive-image.js +235 -0
- package/framework/js/components/ui-components/interactive-timeline.js +285 -0
- package/framework/js/components/ui-components/intro-cards.js +35 -0
- package/framework/js/components/ui-components/lightbox.js +652 -0
- package/framework/js/components/ui-components/modal.js +386 -0
- package/framework/js/components/ui-components/notifications.js +145 -0
- package/framework/js/components/ui-components/progress.js +88 -0
- package/framework/js/components/ui-components/quote.js +41 -0
- package/framework/js/components/ui-components/stats.js +33 -0
- package/framework/js/components/ui-components/steps.js +41 -0
- package/framework/js/components/ui-components/tabs.js +255 -0
- package/framework/js/components/ui-components/timeline.js +42 -0
- package/framework/js/components/ui-components/toggle-group.js +73 -0
- package/framework/js/components/ui-components/tooltip.js +458 -0
- package/framework/js/components/ui-components/value-display.js +133 -0
- package/framework/js/components/ui-components/video-player.js +686 -0
- package/framework/js/core/component-catalog.js +121 -0
- package/framework/js/core/event-bus.js +178 -0
- package/framework/js/core/interaction-catalog.js +149 -0
- package/framework/js/dev/runtime-linter.js +1725 -0
- package/framework/js/drivers/cmi5-driver.js +768 -0
- package/framework/js/drivers/driver-factory.js +77 -0
- package/framework/js/drivers/driver-interface.js +110 -0
- package/framework/js/drivers/http-driver-base.js +241 -0
- package/framework/js/drivers/lti-driver.js +508 -0
- package/framework/js/drivers/proxy-driver.js +444 -0
- package/framework/js/drivers/scorm-12-driver.js +560 -0
- package/framework/js/drivers/scorm-2004-driver.js +775 -0
- package/framework/js/drivers/scorm-driver-base.js +112 -0
- package/framework/js/engagement/engagement-manager.js +404 -0
- package/framework/js/engagement/engagement-progress.js +191 -0
- package/framework/js/engagement/engagement-trackers.js +215 -0
- package/framework/js/engagement/requirement-strategies.js +268 -0
- package/framework/js/main.js +727 -0
- package/framework/js/managers/accessibility-manager.js +499 -0
- package/framework/js/managers/assessment-manager.js +230 -0
- package/framework/js/managers/audio-manager.js +944 -0
- package/framework/js/managers/comment-manager.js +88 -0
- package/framework/js/managers/flag-manager.js +86 -0
- package/framework/js/managers/interaction-manager.js +254 -0
- package/framework/js/managers/interaction-registry.js +96 -0
- package/framework/js/managers/objective-manager.js +423 -0
- package/framework/js/managers/score-manager.js +441 -0
- package/framework/js/managers/video-manager.js +536 -0
- package/framework/js/navigation/Breadcrumbs.js +234 -0
- package/framework/js/navigation/NavigationActions.js +1132 -0
- package/framework/js/navigation/NavigationState.js +276 -0
- package/framework/js/navigation/NavigationUI.js +574 -0
- package/framework/js/navigation/document-gallery.js +357 -0
- package/framework/js/navigation/navigation-helpers.js +175 -0
- package/framework/js/navigation/navigation-validators.js +174 -0
- package/framework/js/state/index.js +8 -0
- package/framework/js/state/lms-connection.js +482 -0
- package/framework/js/state/lms-error-utils.js +58 -0
- package/framework/js/state/state-commits.js +200 -0
- package/framework/js/state/state-domains.js +86 -0
- package/framework/js/state/state-manager.js +502 -0
- package/framework/js/state/state-validation.js +311 -0
- package/framework/js/state/transaction-log.js +41 -0
- package/framework/js/state/xapi-statement-service.js +325 -0
- package/framework/js/utilities/access-control.js +99 -0
- package/framework/js/utilities/breakpoint-manager.js +315 -0
- package/framework/js/utilities/canvas-slide.js +35 -0
- package/framework/js/utilities/conditional-display.js +388 -0
- package/framework/js/utilities/course-channel.js +214 -0
- package/framework/js/utilities/course-helpers.js +420 -0
- package/framework/js/utilities/data-reporter.js +273 -0
- package/framework/js/utilities/error-reporter.js +313 -0
- package/framework/js/utilities/hotspot-helper.js +341 -0
- package/framework/js/utilities/icons.js +348 -0
- package/framework/js/utilities/logger.js +92 -0
- package/framework/js/utilities/markdown-renderer.js +45 -0
- package/framework/js/utilities/scroll-tracker.js +68 -0
- package/framework/js/utilities/ui-initializer.js +146 -0
- package/framework/js/utilities/utilities.js +293 -0
- package/framework/js/utilities/view-manager.js +227 -0
- package/framework/js/validation/html-validators.js +422 -0
- package/framework/js/validation/scorm-validators.js +438 -0
- package/framework/js/vendor/pipwerks.js +931 -0
- package/framework/scripts/generate-narration.js +629 -0
- package/framework/scripts/tts-providers/azure-provider.js +178 -0
- package/framework/scripts/tts-providers/base-provider.js +81 -0
- package/framework/scripts/tts-providers/deepgram-provider.js +135 -0
- package/framework/scripts/tts-providers/elevenlabs-provider.js +148 -0
- package/framework/scripts/tts-providers/google-provider.js +272 -0
- package/framework/scripts/tts-providers/index.js +158 -0
- package/framework/scripts/tts-providers/openai-provider.js +143 -0
- package/framework/version.json +63 -0
- package/lib/authoring-api.js +919 -0
- package/lib/build-linter.js +450 -0
- package/lib/build-packaging.js +186 -0
- package/lib/build.js +88 -0
- package/lib/cloud.js +691 -0
- package/lib/convert.js +341 -0
- package/lib/course-parser.js +936 -0
- package/lib/course-writer.js +258 -0
- package/lib/create.js +248 -0
- package/lib/css-index.js +237 -0
- package/lib/dev.js +51 -0
- package/lib/export-content.js +1246 -0
- package/lib/headless-browser.js +413 -0
- package/lib/import.js +377 -0
- package/lib/index.js +80 -0
- package/lib/info.js +79 -0
- package/lib/interaction-formatters.js +568 -0
- package/lib/manifest/cmi5-manifest.js +63 -0
- package/lib/manifest/lti-tool-config.js +53 -0
- package/lib/manifest/manifest-factory.js +99 -0
- package/lib/manifest/scorm-12-manifest.js +61 -0
- package/lib/manifest/scorm-2004-manifest.js +94 -0
- package/lib/manifest/scorm-proxy-manifest.js +104 -0
- package/lib/manifest-parser.js +96 -0
- package/lib/mcp-prompts.js +753 -0
- package/lib/mcp-server.js +316 -0
- package/lib/narration.js +53 -0
- package/lib/pdf-structure.js +142 -0
- package/lib/preview-export.js +231 -0
- package/lib/preview-routes-api.js +662 -0
- package/lib/preview-routes-editing.js +159 -0
- package/lib/preview-routes-lms.js +230 -0
- package/lib/preview-server.js +564 -0
- package/lib/project-utils.js +269 -0
- package/lib/proxy-templates/proxy.html +68 -0
- package/lib/proxy-templates/scorm-bridge.js +112 -0
- package/lib/scaffold.js +193 -0
- package/lib/schema-extractor.js +361 -0
- package/lib/slide-source-editor.js +586 -0
- package/lib/stub-player/app-viewer.js +195 -0
- package/lib/stub-player/app.js +370 -0
- package/lib/stub-player/catalog-panel.js +312 -0
- package/lib/stub-player/config-panel.js +1303 -0
- package/lib/stub-player/content-generator.js +586 -0
- package/lib/stub-player/content-viewer.js +173 -0
- package/lib/stub-player/debug-panel.js +420 -0
- package/lib/stub-player/edit-mode.js +922 -0
- package/lib/stub-player/edit-utils.js +400 -0
- package/lib/stub-player/header-bar.js +354 -0
- package/lib/stub-player/interaction-editor.js +210 -0
- package/lib/stub-player/interactions-panel.js +565 -0
- package/lib/stub-player/lms-api.js +1094 -0
- package/lib/stub-player/login-screen.js +74 -0
- package/lib/stub-player/outline-mode.js +689 -0
- package/lib/stub-player/styles/_assessments-panel.css +245 -0
- package/lib/stub-player/styles/_base.css +89 -0
- package/lib/stub-player/styles/_catalog-icons.css +96 -0
- package/lib/stub-player/styles/_catalog-panel.css +291 -0
- package/lib/stub-player/styles/_config-panel.css +636 -0
- package/lib/stub-player/styles/_content-viewer.css +834 -0
- package/lib/stub-player/styles/_debug-panel.css +576 -0
- package/lib/stub-player/styles/_edit-mode.css +128 -0
- package/lib/stub-player/styles/_header-bar.css +343 -0
- package/lib/stub-player/styles/_interaction-editor.css +140 -0
- package/lib/stub-player/styles/_interactions-panel.css +1038 -0
- package/lib/stub-player/styles/_login-screen.css +102 -0
- package/lib/stub-player/styles/_outline-mode.css +752 -0
- package/lib/stub-player/styles.css +15 -0
- package/lib/stub-player.js +160 -0
- package/lib/test-data-reporting.js +176 -0
- package/lib/test-error-reporting.js +146 -0
- package/lib/token.js +86 -0
- package/lib/upgrade.js +257 -0
- package/lib/validation-rules.js +517 -0
- package/lib/vite-plugin-content-discovery.js +296 -0
- package/package.json +108 -0
- package/schemas/XMLSchema.dtd +402 -0
- package/schemas/adlcp_v1p3.xsd +111 -0
- package/schemas/adlnav_v1p3.xsd +61 -0
- package/schemas/adlseq_v1p3.xsd +93 -0
- package/schemas/common/anyElement.xsd +27 -0
- package/schemas/common/dataTypes.xsd +138 -0
- package/schemas/common/elementNames.xsd +767 -0
- package/schemas/common/elementTypes.xsd +786 -0
- package/schemas/common/rootElement.xsd +31 -0
- package/schemas/common/vocabTypes.xsd +345 -0
- package/schemas/common/vocabValues.xsd +257 -0
- package/schemas/datatypes.dtd +203 -0
- package/schemas/ims_xml.xsd +35 -0
- package/schemas/imscp_v1p1.xsd +368 -0
- package/schemas/imsss_v1p0.xsd +67 -0
- package/schemas/imsss_v1p0auxresource.xsd +19 -0
- package/schemas/imsss_v1p0control.xsd +20 -0
- package/schemas/imsss_v1p0delivery.xsd +17 -0
- package/schemas/imsss_v1p0limit.xsd +47 -0
- package/schemas/imsss_v1p0objective.xsd +67 -0
- package/schemas/imsss_v1p0random.xsd +16 -0
- package/schemas/imsss_v1p0rollup.xsd +46 -0
- package/schemas/imsss_v1p0seqrule.xsd +108 -0
- package/schemas/imsss_v1p0util.xsd +94 -0
- package/schemas/license.txt +17 -0
- package/schemas/lom.xsd +102 -0
- package/schemas/lomCustom.xsd +62 -0
- package/schemas/lomLoose.xsd +62 -0
- package/schemas/lomStrict.xsd +62 -0
- package/schemas/xml.xsd +81 -0
- package/template/.env.example +92 -0
- package/template/course/assets/audio/example-intro.mp3 +0 -0
- package/template/course/assets/audio/example-ui-demo--compact-player.mp3 +0 -0
- package/template/course/assets/audio/example-ui-demo--demo-modal.mp3 +0 -0
- package/template/course/assets/audio/example-ui-demo--full-player.mp3 +0 -0
- package/template/course/assets/docs/example_md_1.md +39 -0
- package/template/course/assets/docs/example_md_2.md +41 -0
- package/template/course/assets/docs/example_pdf_1_thumbnail.png +0 -0
- package/template/course/assets/docs/example_pdf_2.pdf +0 -0
- package/template/course/assets/images/course-architecture.svg +36 -0
- package/template/course/assets/images/logo.svg +14 -0
- package/template/course/assets/widgets/counter-demo.html +190 -0
- package/template/course/assets/widgets/gravity-painter.html +384 -0
- package/template/course/course-config.js +539 -0
- package/template/course/icons.js +19 -0
- package/template/course/interactions/PLUGIN_GUIDE.md +97 -0
- package/template/course/slides/example-course-structure.js +138 -0
- package/template/course/slides/example-final-exam.js +144 -0
- package/template/course/slides/example-finishing.js +127 -0
- package/template/course/slides/example-interactions-showcase.js +615 -0
- package/template/course/slides/example-preview-tour.js +129 -0
- package/template/course/slides/example-remedial.js +143 -0
- package/template/course/slides/example-summary.js +103 -0
- package/template/course/slides/example-ui-showcase.js +1805 -0
- package/template/course/slides/example-welcome.js +123 -0
- package/template/course/slides/example-workflow.js +140 -0
- package/template/course/theme.css +165 -0
- package/template/eslint.config.js +47 -0
- package/template/package.json +28 -0
- 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">❮</button>
|
|
1383
|
+
<button class="carousel-button next" data-action="next-slide" aria-label="Next">❯</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">×</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 <template> 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
|
+
```
|