@patch-adams/core 1.5.18 → 1.5.20
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/dist/cli.cjs +154 -49
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +154 -49
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +154 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +154 -49
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/detectors/format-detector.ts","../src/detectors/authoring-tool-detector.ts","../src/detectors/index.ts","../src/config/schema.ts","../src/config/defaults.ts","../src/config/loader.ts","../src/templates/css-before.ts","../src/templates/css-after.ts","../src/templates/lrs-bridge.ts","../src/templates/js-before.ts","../src/templates/js-after.ts","../src/templates/skin-css.ts","../src/templates/skin-js.ts","../src/patcher/html-injector.ts","../src/patcher/storyline-html-injector.ts","../src/patcher/manifest-updater.ts","../src/patcher/index.ts","../src/fingerprint/course-metadata.ts","../src/plugins/registry.ts","../src/cli.ts"],"names":["z","resolve","existsSync","extname","readFileSync","pathToFileURL","AdmZip","crypto","archiver","PassThrough","Command","ora","basename","writeFileSync","chalk","FormatDetector"],"mappings":"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A;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA,CAAA;AAs/IrC;;;ACzlJA,SAAS,SAAS,GAAA,EAAwC;AACxD,EAAA,IAAI,CAAC,KAAK,OAAO,EAAA;AACjB,EAAA,OAAO,IACJ,OAAA,CAAQ,KAAA,EAAO,MAAM,CAAA,CACrB,OAAA,CAAQ,MAAM,KAAK,CAAA,CACnB,QAAQ,IAAA,EAAM,KAAK,EACnB,OAAA,CAAQ,KAAA,EAAO,KAAK,CAAA,CACpB,OAAA,CAAQ,OAAO,KAAK,CAAA;AACzB;AAYO,SAAS,uBAAuB,OAAA,EAAkC;AACvE,EAAA,MAAM,EAAE,WAAW,SAAA,EAAW,SAAA,EAAW,cAAc,QAAA,EAAU,SAAA,EAAW,MAAK,GAAI,OAAA;AAGrF,EAAA,MAAM,aAAA,GAAgB,sBAAsB,SAAS,CAAA;AAGrD,EAAA,MAAM,cAAwB,EAAC;AAE/B,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,WAAA,CAAY,KAAK,CAAA,eAAA,EAAkB,QAAA,CAAS,QAAA,CAAS,QAAQ,CAAC,CAAA,EAAA,CAAI,CAAA;AAClE,IAAA,WAAA,CAAY,IAAA,CAAK,CAAA,qBAAA,EAAwB,QAAA,CAAS,cAAc,CAAA,EAAA,CAAI,CAAA;AACpE,IAAA,WAAA,CAAY,IAAA,CAAK,CAAA,aAAA,EAAgB,QAAA,CAAS,MAAM,CAAA,EAAA,CAAI,CAAA;AAEpD,IAAA,IAAI,SAAS,KAAA,EAAO;AAClB,MAAA,WAAA,CAAY,KAAK,CAAA,YAAA,EAAe,QAAA,CAAS,QAAA,CAAS,KAAK,CAAC,CAAA,EAAA,CAAI,CAAA;AAAA,IAC9D;AACA,IAAA,IAAI,SAAS,WAAA,EAAa;AACxB,MAAA,WAAA,CAAY,KAAK,CAAA,kBAAA,EAAqB,QAAA,CAAS,QAAA,CAAS,WAAW,CAAC,CAAA,EAAA,CAAI,CAAA;AAAA,IAC1E;AACA,IAAA,IAAI,SAAS,cAAA,EAAgB;AAC3B,MAAA,WAAA,CAAY,KAAK,CAAA,qBAAA,EAAwB,QAAA,CAAS,QAAA,CAAS,cAAc,CAAC,CAAA,EAAA,CAAI,CAAA;AAAA,IAChF;AACA,IAAA,IAAI,SAAS,SAAA,EAAW;AACtB,MAAA,WAAA,CAAY,KAAK,CAAA,gBAAA,EAAmB,QAAA,CAAS,QAAA,CAAS,SAAS,CAAC,CAAA,EAAA,CAAI,CAAA;AAAA,IACtE;AACA,IAAA,IAAI,QAAA,CAAS,iBAAiB,IAAA,EAAM;AAClC,MAAA,WAAA,CAAY,IAAA,CAAK,CAAA,kBAAA,EAAqB,QAAA,CAAS,YAAY,CAAA,CAAA,CAAG,CAAA;AAAA,IAChE;AACA,IAAA,IAAI,SAAS,QAAA,EAAU;AACrB,MAAA,WAAA,CAAY,KAAK,CAAA,eAAA,EAAkB,QAAA,CAAS,QAAA,CAAS,QAAQ,CAAC,CAAA,EAAA,CAAI,CAAA;AAAA,IACpE;AACA,IAAA,IAAI,SAAS,QAAA,EAAU;AACrB,MAAA,WAAA,CAAY,KAAK,CAAA,eAAA,EAAkB,QAAA,CAAS,QAAA,CAAS,QAAQ,CAAC,CAAA,EAAA,CAAI,CAAA;AAAA,IACpE;AACA,IAAA,IAAI,SAAS,OAAA,EAAS;AACpB,MAAA,WAAA,CAAY,KAAK,CAAA,cAAA,EAAiB,QAAA,CAAS,QAAA,CAAS,OAAO,CAAC,CAAA,EAAA,CAAI,CAAA;AAAA,IAClE;AACA,IAAA,WAAA,CAAY,IAAA,CAAK,CAAA,kBAAA,EAAqB,QAAA,CAAS,WAAW,CAAA,EAAA,CAAI,CAAA;AAAA,EAChE;AAEA,EAAA,MAAM,WAAA,GAAc,WAAA,CAAY,MAAA,GAAS,CAAA,GACrC;AAAA;AAAA,EAAkB,WAAA,CAAY,IAAA,CAAK,IAAI,CAAC;AAAA,IAAA,CAAA,GACxC,EAAA;AAEJ,EAAA,OAAO,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAAA,EAKO,SAAS,CAAA;AAAA,iBAAA,EACN,YAAY,KAAK,IAAA,GAAO;AAAA,SAAA,EAAc,SAAS,IAAI,CAAC,CAAA,EAAA,CAAA,GAAO,EAAE,GAAG,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,kBAAA,EAK1E,IAAA,GAAO,8CAA8C,EAAE;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA,oBAAA,EAsDrD,SAAS,CAAA;AAAA,oBAAA,EACT,SAAS,CAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqC7B,aAAa;AAAA,SAAA,CAAA;AAEf;AAKO,SAAS,oBAAA,CAAqB,QAA0B,QAAA,EAAmD;AAGhH,EAAA,MAAM,eAAA,GAAkB,MAAA,CAAO,SAAA,IAAa,EAAC;AAC7C,EAAA,MAAM,SAAA,GAA8B;AAAA,IAClC,OAAA,EAAS,gBAAgB,OAAA,IAAW,IAAA;AAAA,IACpC,UAAA,EAAY,gBAAgB,UAAA,IAAc,IAAA;AAAA,IAC1C,eAAA,EAAiB,gBAAgB,eAAA,IAAmB,IAAA;AAAA,IACpD,YAAA,EAAc,gBAAgB,YAAA,IAAgB,IAAA;AAAA,IAC9C,iBAAA,EAAmB,gBAAgB,iBAAA,IAAqB,IAAA;AAAA,IACxD,gBAAA,EAAkB,gBAAgB,gBAAA,IAAoB,IAAA;AAAA,IACtD,KAAA,EAAO,gBAAgB,KAAA,IAAS,KAAA;AAAA,IAChC,aAAa,eAAA,CAAgB,WAAA;AAAA,IAC7B,oBAAoB,eAAA,CAAgB,kBAAA;AAAA,IACpC,gBAAgB,eAAA,CAAgB,cAAA;AAAA,IAChC,eAAe,eAAA,CAAgB,aAAA;AAAA,IAC/B,SAAS,eAAA,CAAgB,OAAA;AAAA,IACzB,kBAAkB,eAAA,CAAgB,gBAAA;AAAA,IAClC,qBAAqB,eAAA,CAAgB,mBAAA;AAAA,IACrC,cAAc,eAAA,CAAgB,YAAA;AAAA,IAC9B,aAAa,eAAA,CAAgB,WAAA;AAAA,IAC7B,aAAa,eAAA,CAAgB;AAAA,GAC/B;AAEA,EAAA,OAAO;AAAA,IACL,SAAA,EAAW,CAAA,EAAG,MAAA,CAAO,YAAY,CAAA,CAAA,EAAI,MAAA,CAAO,YAAA,CAAa,EAAE,CAAA,CAAA,EAAI,MAAA,CAAO,QAAA,CAAS,QAAQ,CAAA,CAAA;AAAA,IACvF,SAAA,EAAW,GAAG,MAAA,CAAO,YAAA,CAAa,EAAE,CAAA,CAAA,EAAI,MAAA,CAAO,SAAS,QAAQ,CAAA,CAAA;AAAA,IAChE,WAAW,MAAA,CAAO,SAAA;AAAA,IAClB,cAAc,MAAA,CAAO,YAAA;AAAA,IACrB,UAAU,QAAA,IAAY,IAAA;AAAA,IACtB,SAAA;AAAA,IACA,MAAM,MAAA,CAAO;AAAA,GACf;AACF;;;ACnNO,SAAS,sBAAsB,OAAA,EAAiC;AACrE,EAAA,MAAM,EAAE,SAAA,EAAW,SAAA,EAAW,OAAA,EAAS,cAAa,GAAI,OAAA;AAExD,EAAA,OAAO,CAAA;AAAA;AAAA;AAAA;AAAA,oBAAA,EAIa,SAAS,CAAA;AAAA,oBAAA,EACT,SAAS,CAAA;AAAA,gBAAA,EACb,OAAO,CAAA;AAAA,uBAAA,EACA,YAAY,CAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAA,CAAA;AA0FrC;AAKO,SAAS,oBAAoB,MAAA,EAA0C;AAC5E,EAAA,OAAO;AAAA,IACL,SAAA,EAAW,CAAA,EAAG,MAAA,CAAO,YAAY,CAAA,CAAA,EAAI,MAAA,CAAO,YAAA,CAAa,EAAE,CAAA,CAAA,EAAI,MAAA,CAAO,OAAA,CAAQ,QAAQ,CAAA,CAAA;AAAA,IACtF,SAAA,EAAW,GAAG,MAAA,CAAO,YAAA,CAAa,EAAE,CAAA,CAAA,EAAI,MAAA,CAAO,QAAQ,QAAQ,CAAA,CAAA;AAAA,IAC/D,OAAA,EAAS,OAAO,OAAA,CAAQ,OAAA;AAAA,IACxB,cAAc,MAAA,CAAO;AAAA,GACvB;AACF;;;ACpHO,SAAS,sBAAsB,OAAA,EAAiC;AACrE,EAAA,MAAM,EAAE,SAAA,EAAW,SAAA,EAAW,OAAA,EAAQ,GAAI,OAAA;AAE1C,EAAA,OAAO,CAAA;AAAA;AAAA;AAAA;AAAA,oBAAA,EAIa,SAAS,CAAA;AAAA,oBAAA,EACT,SAAS,CAAA;AAAA,gBAAA,EACb,OAAO,CAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA,SAAA,CAAA;AAqEzB;AAKO,SAAS,oBAAoB,MAAA,EAAiD;AACnF,EAAA,IAAI,CAAC,MAAA,CAAO,IAAA,EAAM,OAAO,IAAA;AAEzB,EAAA,MAAM,WAAA,GAAc,IAAA,CAAK,GAAA,EAAI,CAAE,SAAS,EAAE,CAAA,GAAI,IAAA,CAAK,MAAA,GAAS,QAAA,CAAS,EAAE,CAAA,CAAE,KAAA,CAAM,GAAG,CAAC,CAAA;AACnF,EAAA,OAAO;AAAA,IACL,SAAA,EAAW,GAAG,MAAA,CAAO,YAAY,SAAS,MAAA,CAAO,IAAI,gBAAgB,WAAW,CAAA,CAAA;AAAA,IAChF,SAAA,EAAW,CAAA,KAAA,EAAQ,MAAA,CAAO,IAAI,CAAA,UAAA,CAAA;AAAA,IAC9B,OAAA,EAAS,OAAO,QAAA,CAAS;AAAA;AAAA,GAC3B;AACF;;;AC5FO,SAAS,qBAAqB,OAAA,EAAgC;AACnE,EAAA,MAAM,EAAE,SAAA,EAAW,SAAA,EAAW,OAAA,EAAQ,GAAI,OAAA;AAE1C,EAAA,OAAO,CAAA;AAAA;AAAA;AAAA;AAAA,oBAAA,EAIa,SAAS,CAAA;AAAA,oBAAA,EACT,SAAS,CAAA;AAAA,gBAAA,EACb,OAAO,CAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAA,CAAA;AAiFzB;AAKO,SAAS,mBAAmB,MAAA,EAAgD;AACjF,EAAA,IAAI,CAAC,MAAA,CAAO,IAAA,EAAM,OAAO,IAAA;AAEzB,EAAA,MAAM,WAAA,GAAc,IAAA,CAAK,GAAA,EAAI,CAAE,SAAS,EAAE,CAAA,GAAI,IAAA,CAAK,MAAA,GAAS,QAAA,CAAS,EAAE,CAAA,CAAE,KAAA,CAAM,GAAG,CAAC,CAAA;AACnF,EAAA,OAAO;AAAA,IACL,SAAA,EAAW,GAAG,MAAA,CAAO,YAAY,SAAS,MAAA,CAAO,IAAI,gBAAgB,WAAW,CAAA,CAAA;AAAA,IAChF,SAAA,EAAW,CAAA,KAAA,EAAQ,MAAA,CAAO,IAAI,CAAA,UAAA,CAAA;AAAA,IAC9B,OAAA,EAAS,OAAO,OAAA,CAAQ;AAAA;AAAA,GAC1B;AACF;;;ACvFO,IAAM,eAAN,MAAmB;AAAA,EAChB,MAAA;AAAA,EACA,QAAA,GAAkC,IAAA;AAAA,EAClC,YAAA,GAA6C,IAAA;AAAA,EAErD,YAAY,MAAA,EAA0B;AACpC,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,gBAAgB,MAAA,EAAqC;AACnD,IAAA,IAAA,CAAK,YAAA,GAAe,MAAA;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,QAAA,EAAgC;AAC1C,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY,QAAA,EAAwB;AAElC,IAAA,IAAI,CAAC,KAAK,QAAA,EAAU;AAClB,MAAA,IAAA,CAAK,QAAA,GAAW;AAAA,QACd,QAAA;AAAA,QACA,cAAA,EAAgB,UAAA;AAAA,QAChB,MAAA,EAAQ,SAAA;AAAA,QACR,KAAA,EAAO,IAAA;AAAA,QACP,WAAA,EAAa,IAAA;AAAA,QACb,cAAA,EAAgB,IAAA;AAAA,QAChB,SAAA,EAAW,IAAA;AAAA,QACX,YAAA,EAAc,IAAA;AAAA,QACd,QAAA,EAAU,IAAA;AAAA,QACV,QAAA,EAAU,IAAA;AAAA,QACV,OAAA,EAAS,IAAA;AAAA,QACT,WAAA,EAAA,iBAAa,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,OACtC;AAAA,IACF,CAAA,MAAO;AACL,MAAA,IAAA,CAAK,SAAS,QAAA,GAAW,QAAA;AAAA,IAC3B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,IAAA,EAAsB;AAC3B,IAAA,IAAI,MAAA,GAAS,IAAA;AAGb,IAAA,MAAA,GAAS,IAAA,CAAK,kBAAkB,MAAM,CAAA;AAGtC,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,SAAA,CAAU,OAAA,EAAS;AACjC,MAAA,MAAA,GAAS,IAAA,CAAK,gBAAgB,MAAM,CAAA;AAAA,IACtC;AAGA,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,OAAA,EAAS;AAChC,MAAA,MAAA,GAAS,IAAA,CAAK,eAAe,MAAM,CAAA;AAAA,IACrC;AAGA,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,OAAA,EAAS;AAChC,MAAA,MAAA,GAAS,IAAA,CAAK,eAAe,MAAM,CAAA;AAAA,IACrC;AAGA,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,OAAA,CAAQ,OAAA,EAAS;AAC/B,MAAA,MAAA,GAAS,IAAA,CAAK,cAAc,MAAM,CAAA;AAAA,IACpC;AAGA,IAAA,IAAI,IAAA,CAAK,OAAO,IAAA,EAAM;AACpB,MAAA,MAAA,GAAS,IAAA,CAAK,cAAc,MAAM,CAAA;AAClC,MAAA,MAAA,GAAS,IAAA,CAAK,aAAa,MAAM,CAAA;AAAA,IACnC;AAGA,IAAA,IAAI,KAAK,YAAA,EAAc;AACrB,MAAA,MAAA,GAAS,IAAA,CAAK,mBAAmB,MAAM,CAAA;AAAA,IACzC;AAEA,IAAA,OAAO,MAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,mBAAmB,IAAA,EAAsB;AAC/C,IAAA,IAAI,MAAA,GAAS,IAAA;AAGb,IAAA,MAAM,SAAA,GAAY;AAAA,MAChB,IAAA,CAAK,cAAc,SAAA,IAAa,EAAA;AAAA,MAChC,IAAA,CAAK,cAAc,QAAA,IAAY;AAAA,KACjC,CAAE,MAAA,CAAO,OAAO,CAAA,CAAE,KAAK,IAAI,CAAA;AAE3B,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,MAAM,QAAA,GAAW,CAAA;AAAA;AAAA,EAErB,SAAS;AAAA,QAAA,CAAA;AAEL,MAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,WAAA,EAAa,CAAA,EAAG,QAAQ;AAAA,OAAA,CAAW,CAAA;AAC3D,MAAA,OAAA,CAAQ,IAAI,2CAA2C,CAAA;AAAA,IACzD;AAGA,IAAA,MAAM,QAAA,GAAW;AAAA,MACf,IAAA,CAAK,cAAc,QAAA,IAAY,EAAA;AAAA,MAC/B,IAAA,CAAK,cAAc,OAAA,IAAW;AAAA,KAChC,CAAE,MAAA,CAAO,OAAO,CAAA,CAAE,KAAK,IAAI,CAAA;AAE3B,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,MAAM,OAAA,GAAU,CAAA;AAAA;AAAA,EAEpB,QAAQ;AAAA,SAAA,CAAA;AAEJ,MAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,WAAA,EAAa,CAAA,EAAG,OAAO;AAAA,OAAA,CAAW,CAAA;AAC1D,MAAA,OAAA,CAAQ,IAAI,0CAA0C,CAAA;AAAA,IACxD;AAEA,IAAA,OAAO,MAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,kBAAkB,IAAA,EAAsB;AAC9C,IAAA,MAAM,EAAE,SAAA,EAAW,YAAA,EAAc,IAAA,KAAS,IAAA,CAAK,MAAA;AAC/C,IAAA,MAAM,OAAA,GAAU,CAAA,EAAG,SAAS,CAAA,CAAA,EAAI,YAAY,GAAG,IAAA,GAAO,CAAA,YAAA,EAAe,IAAI,CAAA,CAAA,GAAK,EAAE,CAAA,CAAA;AAGhF,IAAA,MAAM,YAAsB,EAAC;AAC7B,IAAA,IAAI,KAAK,QAAA,EAAU;AACjB,MAAA,SAAA,CAAU,IAAA,CAAK,sBAAsB,IAAA,CAAK,UAAA,CAAW,KAAK,QAAA,CAAS,QAAQ,CAAC,CAAA,CAAA,CAAG,CAAA;AAC/E,MAAA,SAAA,CAAU,IAAA,CAAK,CAAA,gBAAA,EAAmB,IAAA,CAAK,QAAA,CAAS,MAAM,CAAA,CAAA,CAAG,CAAA;AACzD,MAAA,IAAI,IAAA,CAAK,SAAS,KAAA,EAAO;AACvB,QAAA,SAAA,CAAU,IAAA,CAAK,kBAAkB,IAAA,CAAK,UAAA,CAAW,KAAK,QAAA,CAAS,KAAK,CAAC,CAAA,CAAA,CAAG,CAAA;AAAA,MAC1E;AAAA,IACF;AACA,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,SAAA,CAAU,KAAK,CAAA,cAAA,EAAiB,IAAA,CAAK,UAAA,CAAW,IAAI,CAAC,CAAA,CAAA,CAAG,CAAA;AAAA,IAC1D;AACA,IAAA,MAAM,cAAA,GAAiB,UAAU,MAAA,GAAS,CAAA,GAAI,MAAM,SAAA,CAAU,IAAA,CAAK,GAAG,CAAA,GAAI,EAAA;AAG1E,IAAA,MAAM,cAAA,GAAiB,gBAAA;AACvB,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,cAAc,CAAA;AAEvC,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,MAAM,UAAA,GAAa,MAAM,CAAC,CAAA;AAG1B,IAAA,IAAI,6BAAA,CAA8B,IAAA,CAAK,UAAU,CAAA,EAAG;AAElD,MAAA,OAAO,IAAA,CAAK,OAAA;AAAA,QACV,cAAA;AAAA,QACA,CAAC,QAAgB,KAAA,KAAkB;AACjC,UAAA,MAAM,WAAW,KAAA,CAAM,OAAA;AAAA,YACrB,+BAAA;AAAA,YACA,CAAC,UAAA,EAAoB,eAAA,KAA4B,CAAA,OAAA,EAAU,eAAe,IAAI,OAAO,CAAA,CAAA;AAAA,WACvF;AACA,UAAA,OAAO,CAAA,KAAA,EAAQ,QAAQ,CAAA,EAAG,cAAc,CAAA,CAAA,CAAA;AAAA,QAC1C;AAAA,OACF;AAAA,IACF,CAAA,MAAO;AAEL,MAAA,OAAO,IAAA,CAAK,QAAQ,cAAA,EAAgB,CAAA,KAAA,EAAQ,UAAU,CAAA,QAAA,EAAW,OAAO,CAAA,CAAA,EAAI,cAAc,CAAA,CAAA,CAAG,CAAA;AAAA,IAC/F;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,WAAW,GAAA,EAAqB;AACtC,IAAA,OAAO,IACJ,OAAA,CAAQ,IAAA,EAAM,OAAO,CAAA,CACrB,OAAA,CAAQ,MAAM,QAAQ,CAAA,CACtB,QAAQ,IAAA,EAAM,OAAO,EACrB,OAAA,CAAQ,IAAA,EAAM,MAAM,CAAA,CACpB,OAAA,CAAQ,MAAM,MAAM,CAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgB,IAAA,EAAsB;AAC5C,IAAA,MAAM,OAAA,GAAU,qBAAA,CAAsB,IAAA,CAAK,MAAM,CAAA;AACjD,IAAA,MAAM,MAAA,GAAS,wBAAwB,OAAO,CAAA;AAG9C,IAAA,OAAO,IAAA,CAAK,QAAQ,gBAAA,EAAkB,CAAA;AAAA,EAAa,MAAM,CAAA,CAAE,CAAA;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,IAAA,EAAsB;AAC3C,IAAA,MAAM,OAAA,GAAU,oBAAA,CAAqB,IAAA,CAAK,MAAA,EAAQ,KAAK,QAAQ,CAAA;AAC/D,IAAA,MAAM,MAAA,GAAS,uBAAuB,OAAO,CAAA;AAG7C,IAAA,MAAM,eAAA,GAAkB,kCAAA;AACxB,IAAA,IAAI,IAAA,CAAK,QAAA,CAAS,eAAe,CAAA,EAAG;AAElC,MAAA,MAAM,mBAAA,GAAsB,uBAAA;AAC5B,MAAA,MAAM,cAAA,GAAiB,IAAA,CAAK,OAAA,CAAQ,eAAe,CAAA;AACnD,MAAA,MAAM,cAAA,GAAiB,IAAA,CAAK,SAAA,CAAU,cAAc,CAAA;AACpD,MAAA,MAAM,QAAA,GAAW,cAAA,CAAe,KAAA,CAAM,mBAAmB,CAAA;AAEzD,MAAA,IAAI,QAAA,IAAY,QAAA,CAAS,KAAA,KAAU,MAAA,EAAW;AAC5C,QAAA,MAAM,YAAY,cAAA,GAAiB,QAAA,CAAS,KAAA,GAAQ,QAAA,CAAS,CAAC,CAAA,CAAE,MAAA;AAChE,QAAA,OAAO,IAAA,CAAK,MAAM,CAAA,EAAG,SAAS,IAAI,IAAA,GAAO,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,SAAS,CAAA;AAAA,MACxE;AAAA,IACF;AAGA,IAAA,OAAO,IAAA,CAAK,QAAQ,gBAAA,EAAkB,CAAA;AAAA,EAAa,MAAM,CAAA,CAAE,CAAA;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,IAAA,EAAsB;AAC3C,IAAA,MAAM,OAAA,GAAU,oBAAA,CAAqB,IAAA,CAAK,MAAM,CAAA;AAChD,IAAA,MAAM,MAAA,GAAS,uBAAuB,OAAO,CAAA;AAG7C,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,CAAA,EAAG,MAAM;AAAA,OAAA,CAAW,CAAA;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,IAAA,EAAsB;AAC1C,IAAA,MAAM,OAAA,GAAU,mBAAA,CAAoB,IAAA,CAAK,MAAM,CAAA;AAC/C,IAAA,MAAM,MAAA,GAAS,sBAAsB,OAAO,CAAA;AAG5C,IAAA,MAAM,gBAAA,GAAmB,sCAAA;AACzB,IAAA,IAAI,gBAAA,CAAiB,IAAA,CAAK,IAAI,CAAA,EAAG;AAC/B,MAAA,OAAO,IAAA,CAAK,QAAQ,gBAAA,EAAkB,CAAA;AAAA,EAAO,MAAM,CAAA,CAAE,CAAA;AAAA,IACvD;AAGA,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,CAAA,EAAG,MAAM;AAAA,OAAA,CAAW,CAAA;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,IAAA,EAAsB;AAC1C,IAAA,MAAM,OAAA,GAAU,mBAAA,CAAoB,IAAA,CAAK,MAAM,CAAA;AAC/C,IAAA,IAAI,CAAC,SAAS,OAAO,IAAA;AAErB,IAAA,MAAM,MAAA,GAAS,sBAAsB,OAAO,CAAA;AAE5C,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,CAAA,EAAG,MAAM;AAAA,OAAA,CAAW,CAAA;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAa,IAAA,EAAsB;AACzC,IAAA,MAAM,OAAA,GAAU,kBAAA,CAAmB,IAAA,CAAK,MAAM,CAAA;AAC9C,IAAA,IAAI,CAAC,SAAS,OAAO,IAAA;AAErB,IAAA,MAAM,MAAA,GAAS,qBAAqB,OAAO,CAAA;AAE3C,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,CAAA,EAAG,MAAM;AAAA,OAAA,CAAW,CAAA;AAAA,EACvD;AACF,CAAA;;;ACxRO,IAAM,wBAAN,MAA4B;AAAA,EACzB,MAAA;AAAA,EACA,QAAA,GAAkC,IAAA;AAAA,EAClC,YAAA,GAA6C,IAAA;AAAA,EAErD,YAAY,MAAA,EAA0B;AACpC,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,gBAAgB,MAAA,EAAqC;AACnD,IAAA,IAAA,CAAK,YAAA,GAAe,MAAA;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,QAAA,EAAgC;AAC1C,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,IAAA,EAAsB;AAC3B,IAAA,IAAI,MAAA,GAAS,IAAA;AAGb,IAAA,MAAA,GAAS,IAAA,CAAK,kBAAkB,MAAM,CAAA;AAGtC,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,SAAA,CAAU,OAAA,EAAS;AACjC,MAAA,MAAA,GAAS,IAAA,CAAK,gBAAgB,MAAM,CAAA;AAAA,IACtC;AAGA,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,OAAA,EAAS;AAChC,MAAA,MAAA,GAAS,IAAA,CAAK,eAAe,MAAM,CAAA;AAAA,IACrC;AAGA,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,OAAA,EAAS;AAChC,MAAA,MAAA,GAAS,IAAA,CAAK,eAAe,MAAM,CAAA;AAAA,IACrC;AAGA,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,OAAA,CAAQ,OAAA,EAAS;AAC/B,MAAA,MAAA,GAAS,IAAA,CAAK,cAAc,MAAM,CAAA;AAAA,IACpC;AAGA,IAAA,IAAI,IAAA,CAAK,OAAO,IAAA,EAAM;AACpB,MAAA,MAAA,GAAS,IAAA,CAAK,cAAc,MAAM,CAAA;AAClC,MAAA,MAAA,GAAS,IAAA,CAAK,aAAa,MAAM,CAAA;AAAA,IACnC;AAGA,IAAA,IAAI,KAAK,YAAA,EAAc;AACrB,MAAA,MAAA,GAAS,IAAA,CAAK,mBAAmB,MAAM,CAAA;AAAA,IACzC;AAEA,IAAA,OAAO,MAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,mBAAmB,IAAA,EAAsB;AAC/C,IAAA,IAAI,MAAA,GAAS,IAAA;AAGb,IAAA,MAAM,SAAA,GAAY;AAAA,MAChB,IAAA,CAAK,cAAc,SAAA,IAAa,EAAA;AAAA,MAChC,IAAA,CAAK,cAAc,QAAA,IAAY;AAAA,KACjC,CACG,MAAA,CAAO,OAAO,CAAA,CACd,KAAK,IAAI,CAAA;AAEZ,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,MAAM,QAAA,GAAW,CAAA;AAAA;AAAA,EAErB,SAAS;AAAA,QAAA,CAAA;AAEL,MAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,WAAA,EAAa,CAAA,EAAG,QAAQ;AAAA,OAAA,CAAW,CAAA;AAC3D,MAAA,OAAA,CAAQ,IAAI,oDAAoD,CAAA;AAAA,IAClE;AAGA,IAAA,MAAM,QAAA,GAAW;AAAA,MACf,IAAA,CAAK,cAAc,QAAA,IAAY,EAAA;AAAA,MAC/B,IAAA,CAAK,cAAc,OAAA,IAAW;AAAA,KAChC,CACG,MAAA,CAAO,OAAO,CAAA,CACd,KAAK,IAAI,CAAA;AAEZ,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,MAAM,OAAA,GAAU,CAAA;AAAA;AAAA,EAEpB,QAAQ;AAAA,SAAA,CAAA;AAEJ,MAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,WAAA,EAAa,CAAA,EAAG,OAAO;AAAA,OAAA,CAAW,CAAA;AAC1D,MAAA,OAAA,CAAQ,IAAI,mDAAmD,CAAA;AAAA,IACjE;AAEA,IAAA,OAAO,MAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,kBAAkB,IAAA,EAAsB;AAC9C,IAAA,MAAM,EAAE,SAAA,EAAW,YAAA,EAAc,IAAA,KAAS,IAAA,CAAK,MAAA;AAC/C,IAAA,MAAM,OAAA,GAAU,CAAA,EAAG,SAAS,CAAA,CAAA,EAAI,YAAY,GAAG,IAAA,GAAO,CAAA,YAAA,EAAe,IAAI,CAAA,CAAA,GAAK,EAAE,CAAA,CAAA;AAGhF,IAAA,MAAM,YAAsB,EAAC;AAC7B,IAAA,IAAI,KAAK,QAAA,EAAU;AACjB,MAAA,SAAA,CAAU,IAAA,CAAK,sBAAsB,IAAA,CAAK,UAAA,CAAW,KAAK,QAAA,CAAS,QAAQ,CAAC,CAAA,CAAA,CAAG,CAAA;AAC/E,MAAA,SAAA,CAAU,IAAA,CAAK,CAAA,gBAAA,EAAmB,IAAA,CAAK,QAAA,CAAS,MAAM,CAAA,CAAA,CAAG,CAAA;AACzD,MAAA,SAAA,CAAU,KAAK,0BAA0B,CAAA;AACzC,MAAA,IAAI,IAAA,CAAK,SAAS,KAAA,EAAO;AACvB,QAAA,SAAA,CAAU,IAAA,CAAK,kBAAkB,IAAA,CAAK,UAAA,CAAW,KAAK,QAAA,CAAS,KAAK,CAAC,CAAA,CAAA,CAAG,CAAA;AAAA,MAC1E;AAAA,IACF;AACA,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,SAAA,CAAU,KAAK,CAAA,cAAA,EAAiB,IAAA,CAAK,UAAA,CAAW,IAAI,CAAC,CAAA,CAAA,CAAG,CAAA;AAAA,IAC1D;AACA,IAAA,MAAM,cAAA,GAAiB,UAAU,MAAA,GAAS,CAAA,GAAI,MAAM,SAAA,CAAU,IAAA,CAAK,GAAG,CAAA,GAAI,EAAA;AAG1E,IAAA,MAAM,cAAA,GAAiB,gBAAA;AACvB,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,cAAc,CAAA;AAEvC,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,MAAM,UAAA,GAAa,MAAM,CAAC,CAAA;AAG1B,IAAA,IAAI,6BAAA,CAA8B,IAAA,CAAK,UAAU,CAAA,EAAG;AAElD,MAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,cAAA,EAAgB,CAAC,QAAgB,KAAA,KAAkB;AACrE,QAAA,MAAM,WAAW,KAAA,CAAM,OAAA;AAAA,UACrB,+BAAA;AAAA,UACA,CAAC,UAAA,EAAoB,eAAA,KACnB,CAAA,OAAA,EAAU,eAAe,IAAI,OAAO,CAAA,CAAA;AAAA,SACxC;AACA,QAAA,OAAO,CAAA,KAAA,EAAQ,QAAQ,CAAA,EAAG,cAAc,CAAA,CAAA,CAAA;AAAA,MAC1C,CAAC,CAAA;AAAA,IACH,CAAA,MAAO;AAEL,MAAA,OAAO,IAAA,CAAK,OAAA;AAAA,QACV,cAAA;AAAA,QACA,CAAA,KAAA,EAAQ,UAAU,CAAA,QAAA,EAAW,OAAO,IAAI,cAAc,CAAA,CAAA;AAAA,OACxD;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,WAAW,GAAA,EAAqB;AACtC,IAAA,OAAO,IACJ,OAAA,CAAQ,IAAA,EAAM,OAAO,CAAA,CACrB,OAAA,CAAQ,MAAM,QAAQ,CAAA,CACtB,QAAQ,IAAA,EAAM,OAAO,EACrB,OAAA,CAAQ,IAAA,EAAM,MAAM,CAAA,CACpB,OAAA,CAAQ,MAAM,MAAM,CAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgB,IAAA,EAAsB;AAC5C,IAAA,MAAM,OAAA,GAAU,qBAAA,CAAsB,IAAA,CAAK,MAAM,CAAA;AACjD,IAAA,MAAM,MAAA,GAAS,wBAAwB,OAAO,CAAA;AAG9C,IAAA,OAAO,IAAA,CAAK,QAAQ,gBAAA,EAAkB,CAAA;AAAA,EAAa,MAAM,CAAA,CAAE,CAAA;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,IAAA,EAAsB;AAC3C,IAAA,MAAM,OAAA,GAAU,oBAAA,CAAqB,IAAA,CAAK,MAAA,EAAQ,KAAK,QAAQ,CAAA;AAC/D,IAAA,MAAM,MAAA,GAAS,uBAAuB,OAAO,CAAA;AAG7C,IAAA,MAAM,eAAA,GAAkB,kCAAA;AACxB,IAAA,IAAI,IAAA,CAAK,QAAA,CAAS,eAAe,CAAA,EAAG;AAElC,MAAA,MAAM,mBAAA,GAAsB,uBAAA;AAC5B,MAAA,MAAM,cAAA,GAAiB,IAAA,CAAK,OAAA,CAAQ,eAAe,CAAA;AACnD,MAAA,MAAM,cAAA,GAAiB,IAAA,CAAK,SAAA,CAAU,cAAc,CAAA;AACpD,MAAA,MAAM,QAAA,GAAW,cAAA,CAAe,KAAA,CAAM,mBAAmB,CAAA;AAEzD,MAAA,IAAI,QAAA,IAAY,QAAA,CAAS,KAAA,KAAU,MAAA,EAAW;AAC5C,QAAA,MAAM,YAAY,cAAA,GAAiB,QAAA,CAAS,KAAA,GAAQ,QAAA,CAAS,CAAC,CAAA,CAAE,MAAA;AAChE,QAAA,OAAO,IAAA,CAAK,MAAM,CAAA,EAAG,SAAS,IAAI,IAAA,GAAO,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,SAAS,CAAA;AAAA,MACxE;AAAA,IACF;AAGA,IAAA,OAAO,IAAA,CAAK,QAAQ,gBAAA,EAAkB,CAAA;AAAA,EAAa,MAAM,CAAA,CAAE,CAAA;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,IAAA,EAAsB;AAC3C,IAAA,MAAM,OAAA,GAAU,oBAAA,CAAqB,IAAA,CAAK,MAAM,CAAA;AAChD,IAAA,MAAM,MAAA,GAAS,uBAAuB,OAAO,CAAA;AAG7C,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,CAAA,EAAG,MAAM;AAAA,OAAA,CAAW,CAAA;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,cAAc,IAAA,EAAsB;AAC1C,IAAA,MAAM,OAAA,GAAU,mBAAA,CAAoB,IAAA,CAAK,MAAM,CAAA;AAC/C,IAAA,MAAM,MAAA,GAAS,sBAAsB,OAAO,CAAA;AAG5C,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,CAAA,EAAG,MAAM;AAAA,OAAA,CAAW,CAAA;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,IAAA,EAAsB;AAC1C,IAAA,MAAM,OAAA,GAAU,mBAAA,CAAoB,IAAA,CAAK,MAAM,CAAA;AAC/C,IAAA,IAAI,CAAC,SAAS,OAAO,IAAA;AAErB,IAAA,MAAM,MAAA,GAAS,sBAAsB,OAAO,CAAA;AAC5C,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,CAAA,EAAG,MAAM;AAAA,OAAA,CAAW,CAAA;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAa,IAAA,EAAsB;AACzC,IAAA,MAAM,OAAA,GAAU,kBAAA,CAAmB,IAAA,CAAK,MAAM,CAAA;AAC9C,IAAA,IAAI,CAAC,SAAS,OAAO,IAAA;AAErB,IAAA,MAAM,MAAA,GAAS,qBAAqB,OAAO,CAAA;AAC3C,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,CAAA,EAAG,MAAM;AAAA,OAAA,CAAW,CAAA;AAAA,EACvD;AACF,CAAA;;;ACvRO,IAAM,kBAAN,MAAsB;AAAA,EACnB,MAAA;AAAA,EAER,YAAY,MAAA,EAA0B;AACpC,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAA,CAAO,GAAA,EAAa,MAAA,EAAuB,KAAA,EAAgC;AACzE,IAAA,MAAM,WAAqB,EAAC;AAE5B,IAAA,QAAQ,MAAA;AAAQ,MACd,KAAK,SAAA;AAAA,MACL,KAAK,aAAA;AAAA,MACL,KAAK,aAAA;AACH,QAAA,QAAA,CAAS,KAAK,GAAG,IAAA,CAAK,iBAAA,CAAkB,GAAA,EAAK,KAAK,CAAC,CAAA;AACnD,QAAA;AAAA,MAEF,KAAK,MAAA;AAEH,QAAA,QAAA,CAAS,KAAK,GAAG,IAAA,CAAK,iBAAA,CAAkB,GAAA,EAAK,KAAK,CAAC,CAAA;AACnD,QAAA;AAAA,MAEF,KAAK,MAAA;AAGH,QAAA;AAAA,MAEF,KAAK,MAAA;AAEH,QAAA,MAAM,YAAA,GAAe,GAAA,CAAI,QAAA,CAAS,iBAAiB,CAAA;AACnD,QAAA,IAAI,YAAA,EAAc;AAChB,UAAA,QAAA,CAAS,KAAK,GAAG,IAAA,CAAK,iBAAA,CAAkB,GAAA,EAAK,KAAK,CAAC,CAAA;AAAA,QACrD;AACA,QAAA;AAAA;AAGJ,IAAA,OAAO,QAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,iBAAA,CAAkB,KAAa,KAAA,EAAgC;AACrE,IAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,QAAA,CAAS,iBAAiB,CAAA;AAC5C,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,OAAO,EAAC;AAAA,IACV;AAEA,IAAA,IAAI;AACF,MAAA,MAAM,UAAA,GAAa,KAAA,CAAM,OAAA,EAAQ,CAAE,SAAS,OAAO,CAAA;AAGnD,MAAA,MAAM,UAAA,GAAa;AAAA,QACjB,KAAA,CAAM,SAAA;AAAA,QACN,KAAA,CAAM,QAAA;AAAA,QACN,KAAA,CAAM,QAAA;AAAA,QACN,KAAA,CAAM,OAAA;AAAA,QACN,KAAA,CAAM,OAAA;AAAA,QACN,KAAA,CAAM;AAAA,OACR,CAAE,OAAO,OAAO,CAAA;AAEhB,MAAA,IAAI,UAAA,CAAW,WAAW,CAAA,EAAG;AAC3B,QAAA,OAAO,EAAC;AAAA,MACV;AAGA,MAAA,MAAM,cAAA,GAAiB,UAAA,CACpB,GAAA,CAAI,CAAC,QAAA,KAAa,qBAAqB,QAAQ,CAAA,IAAA,CAAM,CAAA,CACrD,IAAA,CAAK,IAAI,CAAA;AAIZ,MAAA,MAAM,oBAAA,GAAuB,oBAAA;AAC7B,MAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,KAAA,CAAM,oBAAoB,CAAA;AAEnD,MAAA,IAAI,CAAC,KAAA,EAAO;AACV,QAAA,OAAA,CAAQ,KAAK,gEAAgE,CAAA;AAC7E,QAAA,OAAO,EAAC;AAAA,MACV;AAEA,MAAA,MAAM,aAAa,UAAA,CAAW,OAAA;AAAA,QAC5B,oBAAA;AAAA,QACA;AAAA,EAAK,cAAc;AAAA,aAAA;AAAA,OACrB;AAEA,MAAA,GAAA,CAAI,WAAW,iBAAA,EAAmB,MAAA,CAAO,IAAA,CAAK,UAAA,EAAY,OAAO,CAAC,CAAA;AAElE,MAAA,OAAO,CAAC,iBAAiB,CAAA;AAAA,IAC3B,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,kDAAkD,KAAK,CAAA;AACrE,MAAA,OAAO,EAAC;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,gBAAA,CAAiB,KAAa,aAAA,EAAgC;AAC5D,IAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,QAAA,CAAS,iBAAiB,CAAA;AAC5C,IAAA,IAAI,CAAC,OAAO,OAAO,KAAA;AAEnB,IAAA,MAAM,GAAA,GAAM,KAAA,CAAM,OAAA,EAAQ,CAAE,SAAS,OAAO,CAAA;AAC5C,IAAA,MAAM,UAAU,GAAA,CAAI,OAAA;AAAA,MAClB,oDAAA;AAAA,MACA,KAAK,aAAa,CAAA,EAAA;AAAA,KACpB;AAEA,IAAA,IAAI,OAAA,KAAY,KAAK,OAAO,KAAA;AAE5B,IAAA,GAAA,CAAI,WAAW,iBAAA,EAAmB,MAAA,CAAO,IAAA,CAAK,OAAA,EAAS,OAAO,CAAC,CAAA;AAC/D,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,YAAA,GAA8B;AAC5B,IAAA,MAAM,QAAuB,EAAC;AAE9B,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,SAAA,CAAU,OAAA,EAAS;AACjC,MAAA,KAAA,CAAM,SAAA,GAAY,CAAA,aAAA,EAAgB,IAAA,CAAK,MAAA,CAAO,YAAA,CAAa,GAAG,CAAA,CAAA,EAAI,IAAA,CAAK,MAAA,CAAO,SAAA,CAAU,QAAQ,CAAA,CAAA;AAAA,IAClG;AAEA,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,OAAA,EAAS;AAChC,MAAA,KAAA,CAAM,QAAA,GAAW,CAAA,aAAA,EAAgB,IAAA,CAAK,MAAA,CAAO,YAAA,CAAa,GAAG,CAAA,CAAA,EAAI,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,QAAQ,CAAA,CAAA;AAAA,IAChG;AAEA,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,OAAA,EAAS;AAChC,MAAA,KAAA,CAAM,QAAA,GAAW,CAAA,aAAA,EAAgB,IAAA,CAAK,MAAA,CAAO,YAAA,CAAa,EAAE,CAAA,CAAA,EAAI,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,QAAQ,CAAA,CAAA;AAAA,IAC/F;AAEA,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,OAAA,CAAQ,OAAA,EAAS;AAC/B,MAAA,KAAA,CAAM,OAAA,GAAU,CAAA,aAAA,EAAgB,IAAA,CAAK,MAAA,CAAO,YAAA,CAAa,EAAE,CAAA,CAAA,EAAI,IAAA,CAAK,MAAA,CAAO,OAAA,CAAQ,QAAQ,CAAA,CAAA;AAAA,IAC7F;AAEA,IAAA,OAAO,KAAA;AAAA,EACT;AACF,CAAA;;;ACtJA,oBAAA,EAAA;AACA,4BAAA,EAAA;;;AC2BO,SAAS,qBAAA,CAAsB,KAAa,MAAA,EAAgC;AACjF,EAAA,MAAM,QAAA,GAA2B;AAAA,IAC/B,QAAA,EAAU,SAAA;AAAA,IACV,cAAA,EAAgB,UAAA;AAAA,IAChB,MAAA;AAAA,IACA,KAAA,EAAO,IAAA;AAAA,IACP,WAAA,EAAa,IAAA;AAAA,IACb,cAAA,EAAgB,IAAA;AAAA,IAChB,SAAA,EAAW,IAAA;AAAA,IACX,YAAA,EAAc,IAAA;AAAA,IACd,QAAA,EAAU,IAAA;AAAA,IACV,QAAA,EAAU,IAAA;AAAA,IACV,OAAA,EAAS,IAAA;AAAA,IACT,WAAA,EAAA,iBAAa,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,GACtC;AAGA,EAAA,MAAM,QAAA,GAAW,GAAA,CAAI,QAAA,CAAS,iBAAiB,CAAA;AAC/C,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,sBAAA,CAAuB,SAAS,OAAA,EAAQ,CAAE,QAAA,CAAS,OAAO,GAAG,QAAQ,CAAA;AAAA,EACvE;AAGA,EAAA,MAAM,MAAA,GAAS,GAAA,CAAI,QAAA,CAAS,YAAY,CAAA;AACxC,EAAA,IAAI,MAAA,EAAQ;AACV,IAAA,iBAAA,CAAkB,OAAO,OAAA,EAAQ,CAAE,QAAA,CAAS,OAAO,GAAG,QAAQ,CAAA;AAAA,EAChE;AAGA,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,QAAA,CAAS,UAAU,CAAA;AACpC,EAAA,IAAI,IAAA,EAAM;AACR,IAAA,eAAA,CAAgB,KAAK,OAAA,EAAQ,CAAE,QAAA,CAAS,OAAO,GAAG,QAAQ,CAAA;AAAA,EAC5D;AAGA,EAAA,IAAI,CAAC,SAAS,KAAA,EAAO;AACnB,IAAA,MAAM,SAAA,GAAY,GAAA,CAAI,QAAA,CAAS,yBAAyB,CAAA;AACxD,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,MAAM,OAAA,GAAU,SAAA,CAAU,OAAA,EAAQ,CAAE,SAAS,OAAO,CAAA;AACpD,MAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,KAAA,CAAM,0BAA0B,CAAA;AAC3D,MAAA,IAAI,UAAA,EAAY;AACd,QAAA,QAAA,CAAS,KAAA,GAAQ,UAAA,CAAW,CAAC,CAAA,CAAE,IAAA,EAAK;AAAA,MACtC;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,QAAA;AACT;AAKA,SAAS,sBAAA,CAAuB,SAAiB,QAAA,EAAgC;AAE/E,EAAA,MAAM,eAAA,GAAkB,OAAA,CAAQ,KAAA,CAAM,kDAAkD,CAAA;AACxF,EAAA,IAAI,eAAA,EAAiB;AACnB,IAAA,QAAA,CAAS,QAAA,GAAW,gBAAgB,CAAC,CAAA;AACrC,IAAA,QAAA,CAAS,cAAA,GAAiB,UAAA;AAAA,EAC5B;AAGA,EAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,KAAA,CAAM,+CAA+C,CAAA;AAClF,EAAA,IAAI,YAAA,EAAc;AAChB,IAAA,QAAA,CAAS,OAAA,GAAU,aAAa,CAAC,CAAA;AAAA,EACnC;AAIA,EAAA,MAAM,aAAA,GAAgB,OAAA,CAAQ,KAAA,CAAM,qDAAqD,CAAA;AACzF,EAAA,IAAI,aAAA,EAAe;AACjB,IAAA,QAAA,CAAS,KAAA,GAAQ,aAAA,CAAc,CAAC,CAAA,CAAE,IAAA,EAAK;AAAA,EACzC;AAGA,EAAA,IAAI,CAAC,SAAS,KAAA,EAAO;AACnB,IAAA,MAAM,aAAA,GAAgB,OAAA,CAAQ,KAAA,CAAM,8EAA8E,CAAA;AAClH,IAAA,IAAI,aAAA,EAAe;AACjB,MAAA,QAAA,CAAS,KAAA,GAAQ,aAAA,CAAc,CAAC,CAAA,CAAE,IAAA,EAAK;AAAA,IACzC;AAAA,EACF;AAGA,EAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,KAAA,CAAM,oFAAoF,CAAA;AACpH,EAAA,IAAI,SAAA,EAAW;AACb,IAAA,QAAA,CAAS,WAAA,GAAc,SAAA,CAAU,CAAC,CAAA,CAAE,IAAA,EAAK;AAAA,EAC3C;AAGA,EAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,KAAA,CAAM,sDAAsD,CAAA;AACvF,EAAA,IAAI,UAAA,EAAY;AACd,IAAA,QAAA,CAAS,cAAA,GAAiB,WAAW,CAAC,CAAA;AAAA,EACxC;AAGA,EAAA,MAAM,aAAA,GAAgB,OAAA,CAAQ,KAAA,CAAM,4CAA4C,CAAA;AAChF,EAAA,IAAI,aAAA,EAAe;AACjB,IAAA,QAAA,CAAS,SAAA,GAAY,cAAc,CAAC,CAAA;AAAA,EACtC;AAGA,EAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,KAAA,CAAM,oDAAoD,CAAA;AACvF,EAAA,IAAI,YAAA,EAAc;AAChB,IAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,YAAA,CAAa,CAAC,CAAC,CAAA;AACxC,IAAA,IAAI,CAAC,KAAA,CAAM,KAAK,CAAA,EAAG;AACjB,MAAA,QAAA,CAAS,YAAA,GAAe,KAAA;AAAA,IAC1B;AAAA,EACF;AAGA,EAAA,IAAI,QAAA,CAAS,iBAAiB,IAAA,EAAM;AAClC,IAAA,MAAM,cAAA,GAAiB,OAAA,CAAQ,KAAA,CAAM,oEAAoE,CAAA;AACzG,IAAA,IAAI,cAAA,EAAgB;AAClB,MAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,cAAA,CAAe,CAAC,CAAC,CAAA;AAC1C,MAAA,IAAI,CAAC,KAAA,CAAM,KAAK,CAAA,EAAG;AACjB,QAAA,QAAA,CAAS,eAAe,KAAA,GAAQ,GAAA;AAAA,MAClC;AAAA,IACF;AAAA,EACF;AAGA,EAAA,MAAM,aAAA,GAAgB,OAAA,CAAQ,KAAA,CAAM,oEAAoE,CAAA;AACxG,EAAA,IAAI,aAAA,EAAe;AACjB,IAAA,QAAA,CAAS,QAAA,GAAW,aAAA,CAAc,CAAC,CAAA,CAAE,IAAA,EAAK;AAAA,EAC5C;AAGA,EAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,KAAA,CAAM,kCAAkC,CAAA;AAClE,EAAA,IAAI,SAAA,EAAW;AACb,IAAA,QAAA,CAAS,QAAA,GAAW,UAAU,CAAC,CAAA;AAAA,EACjC;AACA,EAAA,IAAI,CAAC,SAAS,QAAA,EAAU;AACtB,IAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,KAAA,CAAM,kDAAkD,CAAA;AACrF,IAAA,IAAI,YAAA,EAAc;AAChB,MAAA,QAAA,CAAS,QAAA,GAAW,YAAA,CAAa,CAAC,CAAA,CAAE,IAAA,EAAK;AAAA,IAC3C;AAAA,EACF;AACF;AAKA,SAAS,iBAAA,CAAkB,SAAiB,QAAA,EAAgC;AAE1E,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,KAAA,CAAM,0CAA0C,CAAA;AACxE,EAAA,IAAI,OAAA,IAAW,QAAA,CAAS,cAAA,KAAmB,UAAA,EAAY;AACrD,IAAA,QAAA,CAAS,QAAA,GAAW,QAAQ,CAAC,CAAA;AAC7B,IAAA,QAAA,CAAS,cAAA,GAAiB,QAAA;AAAA,EAC5B;AAGA,EAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,KAAA,CAAM,6BAA6B,CAAA;AAC7D,EAAA,IAAI,SAAA,IAAa,CAAC,QAAA,CAAS,KAAA,EAAO;AAChC,IAAA,QAAA,CAAS,KAAA,GAAQ,SAAA,CAAU,CAAC,CAAA,CAAE,IAAA,EAAK;AAAA,EACrC;AAGA,EAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,KAAA,CAAM,2CAA2C,CAAA;AAC3E,EAAA,IAAI,SAAA,IAAa,CAAC,QAAA,CAAS,WAAA,EAAa;AACtC,IAAA,QAAA,CAAS,WAAA,GAAc,SAAA,CAAU,CAAC,CAAA,CAAE,IAAA,EAAK;AAAA,EAC3C;AAGA,EAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,KAAA,CAAM,iCAAiC,CAAA;AACnE,EAAA,IAAI,WAAA,EAAa;AACf,IAAA,QAAA,CAAS,SAAA,GAAY,WAAA,CAAY,CAAC,CAAA,CAAE,IAAA,EAAK;AAAA,EAC3C;AACF;AAKA,SAAS,eAAA,CAAgB,SAAiB,QAAA,EAAgC;AAExE,EAAA,MAAM,aAAA,GAAgB,OAAA,CAAQ,KAAA,CAAM,wCAAwC,CAAA;AAC5E,EAAA,IAAI,aAAA,IAAiB,QAAA,CAAS,cAAA,KAAmB,UAAA,EAAY;AAC3D,IAAA,QAAA,CAAS,QAAA,GAAW,cAAc,CAAC,CAAA;AACnC,IAAA,QAAA,CAAS,cAAA,GAAiB,MAAA;AAAA,EAC5B;AAGA,EAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,KAAA,CAAM,+BAA+B,CAAA;AAChE,EAAA,IAAI,UAAA,IAAc,CAAC,QAAA,CAAS,KAAA,EAAO;AACjC,IAAA,QAAA,CAAS,KAAA,GAAQ,UAAA,CAAW,CAAC,CAAA,CAAE,IAAA,EAAK;AAAA,EACtC;AAGA,EAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,KAAA,CAAM,2CAA2C,CAAA;AAC3E,EAAA,IAAI,SAAA,IAAa,CAAC,QAAA,CAAS,WAAA,EAAa;AACtC,IAAA,QAAA,CAAS,WAAA,GAAc,SAAA,CAAU,CAAC,CAAA,CAAE,IAAA,EAAK;AAAA,EAC3C;AAGA,EAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,KAAA,CAAM,2BAA2B,CAAA;AAC7D,EAAA,IAAI,WAAA,EAAa;AACf,IAAA,QAAA,CAAS,SAAA,GAAY,WAAA,CAAY,CAAC,CAAA,CAAE,IAAA,EAAK;AAAA,EAC3C;AAGA,EAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,KAAA,CAAM,gCAAgC,CAAA;AAClE,EAAA,IAAI,WAAA,EAAa;AAGf,IAAA,IAAI,YAAY,CAAC,CAAA,CAAE,aAAY,CAAE,QAAA,CAAS,QAAQ,CAAA,EAAG;AACnD,MAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,KAAA,CAAM,sCAAsC,CAAA;AACzE,MAAA,IAAI,YAAA,EAAc;AAChB,QAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,YAAA,CAAa,CAAC,CAAC,CAAA;AACxC,QAAA,IAAI,CAAC,KAAA,CAAM,KAAK,CAAA,EAAG;AACjB,UAAA,QAAA,CAAS,YAAA,GAAe,KAAA;AAAA,QAC1B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AC3OO,IAAM,iBAAN,MAAqB;AAAA,EAClB,OAAA,uBAA6C,GAAA,EAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASzD,SAAS,MAAA,EAAgC;AACvC,IAAA,IAAI,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,MAAA,CAAO,IAAI,CAAA,EAAG;AACjC,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,QAAA,EAAW,MAAA,CAAO,IAAI,CAAA,uBAAA,CAAyB,CAAA;AAAA,IACjE;AACA,IAAA,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,MAAA,CAAO,IAAA,EAAM,MAAM,CAAA;AACpC,IAAA,OAAA,CAAQ,IAAI,CAAA,yBAAA,EAA4B,MAAA,CAAO,IAAI,CAAA,EAAA,EAAK,MAAA,CAAO,OAAO,CAAA,CAAE,CAAA;AAAA,EAC1E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,WAAW,IAAA,EAAuB;AAChC,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,OAAA,CAAQ,MAAA,CAAO,IAAI,CAAA;AACxC,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,2BAAA,EAA8B,IAAI,CAAA,CAAE,CAAA;AAAA,IAClD;AACA,IAAA,OAAO,OAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,UAAU,IAAA,EAA4C;AACpD,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,IAAI,CAAA;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAA,GAAoC;AAClC,IAAA,OAAO,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,OAAA,CAAQ,QAAQ,CAAA;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cAAA,GAA2B;AACzB,IAAA,OAAO,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,OAAA,CAAQ,MAAM,CAAA;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,UAAU,IAAA,EAAuB;AAC/B,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,IAAI,CAAA;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,eAAe,aAAA,EAAqD;AAClE,IAAA,MAAM,MAAA,GAAgC;AAAA,MACpC,SAAA,EAAW,EAAA;AAAA,MACX,QAAA,EAAU,EAAA;AAAA,MACV,QAAA,EAAU,EAAA;AAAA,MACV,OAAA,EAAS;AAAA,KACX;AAEA,IAAA,KAAA,MAAW,CAAC,UAAA,EAAY,SAAS,KAAK,MAAA,CAAO,OAAA,CAAQ,aAAa,CAAA,EAAG;AAEnE,MAAA,IAAI,CAAC,UAAU,OAAA,EAAS;AACtB,QAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,uCAAA,EAA0C,UAAU,CAAA,CAAE,CAAA;AAClE,QAAA;AAAA,MACF;AAGA,MAAA,MAAM,MAAA,GAAS,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,UAAU,CAAA;AAC1C,MAAA,IAAI,CAAC,MAAA,EAAQ;AACX,QAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,qBAAA,EAAwB,UAAU,CAAA,iCAAA,CAAmC,CAAA;AAClF,QAAA;AAAA,MACF;AAEA,MAAA,IAAI;AAEF,QAAA,MAAM,eAAA,GAAkB,MAAA,CAAO,YAAA,CAAa,KAAA,CAAM,SAAS,CAAA;AAC3D,QAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,oCAAA,EAAuC,UAAU,CAAA,CAAE,CAAA;AAG/D,QAAA,IAAI,OAAO,WAAA,EAAa;AACtB,UAAA,MAAM,GAAA,GAAM,MAAA,CAAO,WAAA,CAAY,eAAe,CAAA;AAC9C,UAAA,IAAI,IAAI,MAAA,EAAQ;AACd,YAAA,MAAA,CAAO,SAAA,IAAa,kBAAkB,UAAU,CAAA;AAAA,EAAY,IAAI,MAAM;;AAAA,CAAA;AAAA,UACxE;AACA,UAAA,IAAI,IAAI,KAAA,EAAO;AACb,YAAA,MAAA,CAAO,QAAA,IAAY,kBAAkB,UAAU,CAAA;AAAA,EAAY,IAAI,KAAK;;AAAA,CAAA;AAAA,UACtE;AAAA,QACF;AAGA,QAAA,IAAI,OAAO,UAAA,EAAY;AACrB,UAAA,MAAM,EAAA,GAAK,MAAA,CAAO,UAAA,CAAW,eAAe,CAAA;AAC5C,UAAA,IAAI,GAAG,MAAA,EAAQ;AACb,YAAA,MAAA,CAAO,QAAA,IAAY,kBAAkB,UAAU,CAAA;AAAA,EAAS,GAAG,MAAM;;AAAA,CAAA;AAAA,UACnE;AACA,UAAA,IAAI,GAAG,KAAA,EAAO;AACZ,YAAA,MAAA,CAAO,OAAA,IAAW,kBAAkB,UAAU,CAAA;AAAA,EAAS,GAAG,KAAK;;AAAA,CAAA;AAAA,UACjE;AAAA,QACF;AAAA,MACF,SAAS,KAAA,EAAO;AACd,QAAA,MAAM,UAAU,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AACrE,QAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,sCAAA,EAAyC,UAAU,CAAA,GAAA,EAAM,OAAO,CAAA,CAAE,CAAA;AAAA,MAElF;AAAA,IACF;AAEA,IAAA,OAAO,MAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,QAAQ,KAAA,EAAM;AACnB,IAAA,OAAA,CAAQ,IAAI,+BAA+B,CAAA;AAAA,EAC7C;AACF,CAAA;AAMO,IAAM,cAAA,GAAiB,IAAI,cAAA,EAAe;;;AFvFjD,IAAM,kBAAA,GAAqB,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AAW3B,IAAM,iBAAA,GAAoB,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AAW1B,IAAM,iBAAA,GAAoB,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA,CAAA;AAa1B,IAAM,gBAAA,GAAmB,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA,CAAA;AAmBlB,IAAM,UAAN,MAAc;AAAA,EACX,MAAA;AAAA,EACA,gBAAA;AAAA,EACA,qBAAA;AAAA,EACA,eAAA;AAAA,EACA,cAAA;AAAA,EACA,qBAAA;AAAA,EAER,YAAY,MAAA,EAAmC;AAE7C,IAAA,IAAA,CAAK,MAAA,GAAS,kBAAkB,MAAM,CAAA;AACtC,IAAA,IAAA,CAAK,gBAAA,GAAmB,IAAI,YAAA,CAAa,IAAA,CAAK,MAAM,CAAA;AACpD,IAAA,IAAA,CAAK,qBAAA,GAAwB,IAAI,qBAAA,CAAsB,IAAA,CAAK,MAAM,CAAA;AAClE,IAAA,IAAA,CAAK,eAAA,GAAkB,IAAI,eAAA,CAAgB,IAAA,CAAK,MAAM,CAAA;AACtD,IAAA,IAAA,CAAK,cAAA,GAAiB,IAAI,cAAA,EAAe;AACzC,IAAA,IAAA,CAAK,qBAAA,GAAwB,IAAI,qBAAA,EAAsB;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,UAAU,GAAA,EAA0C;AAChE,IAAA,IAAI;AACF,MAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,oBAAA,EAAuB,GAAG,CAAA,CAAE,CAAA;AACxC,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAG,CAAA;AAChC,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,OAAA,CAAQ,IAAA,CAAK,6BAA6B,GAAG,CAAA,EAAA,EAAK,SAAS,MAAM,CAAA,CAAA,EAAI,QAAA,CAAS,UAAU,CAAA,CAAE,CAAA;AAC1F,QAAA,OAAO,KAAA,CAAA;AAAA,MACT;AACA,MAAA,MAAM,OAAA,GAAU,MAAM,QAAA,CAAS,IAAA,EAAK;AACpC,MAAA,OAAA,CAAQ,IAAI,CAAA,kBAAA,EAAqB,GAAG,CAAA,EAAA,EAAK,OAAA,CAAQ,MAAM,CAAA,OAAA,CAAS,CAAA;AAChE,MAAA,OAAO,OAAA;AAAA,IACT,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,yBAAA,EAA4B,GAAG,CAAA,CAAA,CAAA,EAAK,KAAK,CAAA;AACtD,MAAA,OAAO,MAAA;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,oBAAA,GAAkD;AAC9D,IAAA,MAAM,UAA4B,EAAC;AACnC,IAAA,MAAM,EAAE,cAAc,YAAA,EAAc,SAAA,EAAW,UAAU,QAAA,EAAU,OAAA,KAAY,IAAA,CAAK,MAAA;AAEpF,IAAA,MAAM,gBAAiC,EAAC;AAExC,IAAA,IAAI,UAAU,OAAA,EAAS;AACrB,MAAA,MAAM,GAAA,GAAM,GAAG,YAAY,CAAA,CAAA,EAAI,aAAa,GAAG,CAAA,CAAA,EAAI,UAAU,QAAQ,CAAA,CAAA;AACrE,MAAA,aAAA,CAAc,IAAA;AAAA,QACZ,IAAA,CAAK,SAAA,CAAU,GAAG,CAAA,CAAE,KAAK,CAAA,OAAA,KAAW;AAAE,UAAA,OAAA,CAAQ,SAAA,GAAY,OAAA;AAAA,QAAS,CAAC;AAAA,OACtE;AAAA,IACF;AAEA,IAAA,IAAI,SAAS,OAAA,EAAS;AACpB,MAAA,MAAM,GAAA,GAAM,GAAG,YAAY,CAAA,CAAA,EAAI,aAAa,GAAG,CAAA,CAAA,EAAI,SAAS,QAAQ,CAAA,CAAA;AACpE,MAAA,aAAA,CAAc,IAAA;AAAA,QACZ,IAAA,CAAK,SAAA,CAAU,GAAG,CAAA,CAAE,KAAK,CAAA,OAAA,KAAW;AAAE,UAAA,OAAA,CAAQ,QAAA,GAAW,OAAA;AAAA,QAAS,CAAC;AAAA,OACrE;AAAA,IACF;AAEA,IAAA,IAAI,SAAS,OAAA,EAAS;AACpB,MAAA,MAAM,GAAA,GAAM,GAAG,YAAY,CAAA,CAAA,EAAI,aAAa,EAAE,CAAA,CAAA,EAAI,SAAS,QAAQ,CAAA,CAAA;AACnE,MAAA,aAAA,CAAc,IAAA;AAAA,QACZ,IAAA,CAAK,SAAA,CAAU,GAAG,CAAA,CAAE,KAAK,CAAA,OAAA,KAAW;AAAE,UAAA,OAAA,CAAQ,QAAA,GAAW,OAAA;AAAA,QAAS,CAAC;AAAA,OACrE;AAAA,IACF;AAEA,IAAA,IAAI,QAAQ,OAAA,EAAS;AACnB,MAAA,MAAM,GAAA,GAAM,GAAG,YAAY,CAAA,CAAA,EAAI,aAAa,EAAE,CAAA,CAAA,EAAI,QAAQ,QAAQ,CAAA,CAAA;AAClE,MAAA,aAAA,CAAc,IAAA;AAAA,QACZ,IAAA,CAAK,SAAA,CAAU,GAAG,CAAA,CAAE,KAAK,CAAA,OAAA,KAAW;AAAE,UAAA,OAAA,CAAQ,OAAA,GAAU,OAAA;AAAA,QAAS,CAAC;AAAA,OACpE;AAAA,IACF;AAGA,IAAA,IAAI,IAAA,CAAK,OAAO,IAAA,EAAM;AACpB,MAAA,MAAM,aAAa,CAAA,EAAG,YAAY,CAAA,MAAA,EAAS,IAAA,CAAK,OAAO,IAAI,CAAA,UAAA,CAAA;AAC3D,MAAA,aAAA,CAAc,IAAA;AAAA,QACZ,IAAA,CAAK,SAAA,CAAU,UAAU,CAAA,CAAE,KAAK,CAAA,OAAA,KAAW;AAAE,UAAA,OAAA,CAAQ,OAAA,GAAU,OAAA;AAAA,QAAS,CAAC;AAAA,OAC3E;AAEA,MAAA,MAAM,YAAY,CAAA,EAAG,YAAY,CAAA,MAAA,EAAS,IAAA,CAAK,OAAO,IAAI,CAAA,UAAA,CAAA;AAC1D,MAAA,aAAA,CAAc,IAAA;AAAA,QACZ,IAAA,CAAK,SAAA,CAAU,SAAS,CAAA,CAAE,KAAK,CAAA,OAAA,KAAW;AAAE,UAAA,OAAA,CAAQ,MAAA,GAAS,OAAA;AAAA,QAAS,CAAC;AAAA,OACzE;AAAA,IACF;AAGA,IAAA,MAAM,OAAA,CAAQ,IAAI,aAAa,CAAA;AAE/B,IAAA,OAAO,OAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,KAAA,CACJ,WAAA,EACA,OAAA,GAAwB,EAAC,EACyB;AAElD,IAAA,MAAM,mBAAA,GAAsC;AAAA,MAC1C,QAAA,EAAU,EAAA;AAAA,MACV,cAAA,EAAgB,UAAA;AAAA,MAChB,MAAA,EAAQ,SAAA;AAAA,MACR,KAAA,EAAO,IAAA;AAAA,MACP,WAAA,EAAa,IAAA;AAAA,MACb,cAAA,EAAgB,IAAA;AAAA,MAChB,SAAA,EAAW,IAAA;AAAA,MACX,YAAA,EAAc,IAAA;AAAA,MACd,QAAA,EAAU,IAAA;AAAA,MACV,QAAA,EAAU,IAAA;AAAA,MACV,OAAA,EAAS,IAAA;AAAA,MACT,WAAA,EAAA,iBAAa,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,KACtC;AAEA,IAAA,MAAM,MAAA,GAAsB;AAAA,MAC1B,OAAA,EAAS,KAAA;AAAA,MACT,MAAA,EAAQ,SAAA;AAAA,MACR,iBAAA,EAAmB,SAAA;AAAA,MACnB,aAAA,EAAe,SAAA;AAAA,MACf,wBAAA,EAA0B,SAAA;AAAA,MAC1B,QAAA,EAAU,mBAAA;AAAA,MACV,QAAA,EAAU,EAAA;AAAA,MACV,cAAA,EAAgB,UAAA;AAAA,MAChB,eAAe,EAAC;AAAA,MAChB,YAAY,EAAC;AAAA,MACb,QAAQ,EAAC;AAAA,MACT,UAAU;AAAC,KACb;AAEA,IAAA,IAAI;AAEF,MAAA,MAAM,GAAA,GAAM,IAAIC,uBAAA,CAAO,WAAW,CAAA;AAGlC,MAAA,MAAA,CAAO,MAAA,GAAS,IAAA,CAAK,cAAA,CAAe,MAAA,CAAO,GAAG,CAAA;AAC9C,MAAA,MAAA,CAAO,iBAAA,GAAoB,IAAA,CAAK,cAAA,CAAe,oBAAA,CAAqB,OAAO,MAAM,CAAA;AAEjF,MAAA,IAAI,MAAA,CAAO,WAAW,SAAA,EAAW;AAC/B,QAAA,MAAA,CAAO,QAAA,CAAS,KAAK,uDAAuD,CAAA;AAAA,MAC9E;AAGA,MAAA,MAAM,QAAA,GAAW,IAAA,CAAK,qBAAA,CAAsB,MAAA,CAAO,GAAG,CAAA;AACtD,MAAA,MAAA,CAAO,gBAAgB,QAAA,CAAS,IAAA;AAChC,MAAA,MAAA,CAAO,2BAA2B,QAAA,CAAS,WAAA;AAE3C,MAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,mCAAA,EAAsC,QAAA,CAAS,WAAW,CAAA,CAAE,CAAA;AACxE,MAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,eAAA,EAAkB,QAAA,CAAS,YAAY,CAAA,CAAE,CAAA;AACrD,MAAA,IAAI,QAAA,CAAS,qBAAqB,MAAA,EAAQ;AACxC,QAAA,OAAA,CAAQ,IAAI,CAAA,qBAAA,EAAwB,QAAA,CAAS,oBAAoB,IAAA,CAAK,IAAI,CAAC,CAAA,CAAE,CAAA;AAAA,MAC/E;AACA,MAAA,IAAI,SAAS,OAAA,EAAS,OAAA,CAAQ,IAAI,CAAA,aAAA,EAAgB,QAAA,CAAS,OAAO,CAAA,CAAE,CAAA;AAGpE,MAAA,MAAM,QAAA,GAAW,qBAAA,CAAsB,GAAA,EAAK,MAAA,CAAO,MAAM,CAAA;AACzD,MAAA,MAAA,CAAO,QAAA,GAAW,QAAA;AAClB,MAAA,MAAA,CAAO,WAAW,QAAA,CAAS,QAAA;AAC3B,MAAA,MAAA,CAAO,iBAAiB,QAAA,CAAS,cAAA;AAEjC,MAAA,OAAA,CAAQ,IAAI,CAAA,oCAAA,CAAsC,CAAA;AAClD,MAAA,OAAA,CAAQ,IAAI,CAAA,eAAA,EAAkB,QAAA,CAAS,QAAQ,CAAA,UAAA,EAAa,QAAA,CAAS,cAAc,CAAA,CAAA,CAAG,CAAA;AACtF,MAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,YAAA,EAAe,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAC5C,MAAA,IAAI,SAAS,KAAA,EAAO,OAAA,CAAQ,IAAI,CAAA,WAAA,EAAc,QAAA,CAAS,KAAK,CAAA,CAAE,CAAA;AAC9D,MAAA,IAAI,QAAA,CAAS,WAAA,EAAa,OAAA,CAAQ,GAAA,CAAI,CAAA,iBAAA,EAAoB,QAAA,CAAS,WAAA,CAAY,SAAA,CAAU,CAAA,EAAG,EAAE,CAAC,CAAA,GAAA,CAAK,CAAA;AACpG,MAAA,IAAI,SAAS,QAAA,EAAU,OAAA,CAAQ,IAAI,CAAA,cAAA,EAAiB,QAAA,CAAS,QAAQ,CAAA,CAAE,CAAA;AACvE,MAAA,IAAI,QAAA,CAAS,iBAAiB,IAAA,EAAM,OAAA,CAAQ,IAAI,CAAA,mBAAA,EAAsB,QAAA,CAAS,YAAY,CAAA,CAAE,CAAA;AAI7F,MAAA,MAAM,aAAA,GAAgB,IAAA,CAAK,MAAA,CAAO,SAAA,EAAW,YAAA;AAE7C,MAAA,IAAI,aAAA,EAAe;AAEjB,QAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,iDAAA,EAAoD,aAAa,CAAA,CAAE,CAAA;AAC/E,QAAA,QAAA,CAAS,QAAA,GAAW,aAAA;AACpB,QAAA,QAAA,CAAS,cAAA,GAAiB,UAAA;AAC1B,QAAA,MAAA,CAAO,QAAA,GAAW,aAAA;AAClB,QAAA,IAAI,IAAA,CAAK,eAAA,CAAgB,gBAAA,CAAiB,GAAA,EAAK,aAAa,CAAA,EAAG;AAC7D,UAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,0CAAA,EAA6C,aAAa,CAAA,CAAE,CAAA;AAAA,QAC1E;AAAA,MACF,CAAA,MAAA,IAAW,QAAA,CAAS,cAAA,KAAmB,UAAA,EAAY;AAEjD,QAAA,MAAM,SAAA,GAAYC,wBAAO,UAAA,EAAW;AACpC,QAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,uDAAA,EAA0D,SAAS,CAAA,CAAE,CAAA;AACjF,QAAA,QAAA,CAAS,QAAA,GAAW,SAAA;AACpB,QAAA,MAAA,CAAO,QAAA,GAAW,SAAA;AAClB,QAAA,IAAI,IAAA,CAAK,eAAA,CAAgB,gBAAA,CAAiB,GAAA,EAAK,SAAS,CAAA,EAAG;AACzD,UAAA,OAAA,CAAQ,IAAI,CAAA,qDAAA,CAAuD,CAAA;AAAA,QACrE;AAAA,MACF;AAGA,MAAA,MAAM,aAAA,GAAgB,OAAA,CAAQ,IAAA,IAAQ,IAAA,CAAK,MAAA,CAAO,IAAA;AAClD,MAAA,IAAI,QAAQ,IAAA,IAAQ,OAAA,CAAQ,IAAA,KAAS,IAAA,CAAK,OAAO,IAAA,EAAM;AAErD,QAAA,IAAA,CAAK,MAAA,CAAO,OAAO,OAAA,CAAQ,IAAA;AAE3B,QAAA,IAAA,CAAK,gBAAA,GAAmB,IAAI,YAAA,CAAa,IAAA,CAAK,MAAM,CAAA;AACpD,QAAA,IAAA,CAAK,qBAAA,GAAwB,IAAI,qBAAA,CAAsB,IAAA,CAAK,MAAM,CAAA;AAClE,QAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,wCAAA,EAA2C,OAAA,CAAQ,IAAI,CAAA,CAAE,CAAA;AAAA,MACvE;AACA,MAAA,IAAI,aAAA,EAAe;AACjB,QAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,gBAAA,EAAmB,aAAa,CAAA,CAAE,CAAA;AAAA,MAChD;AAGA,MAAA,IAAI,QAAQ,WAAA,EAAa;AACvB,QAAA,IAAA,CAAK,MAAA,CAAO,SAAA,GAAY,IAAA,CAAK,MAAA,CAAO,aAAa,EAAC;AAClD,QAAA,IAAA,CAAK,MAAA,CAAO,SAAA,CAAU,WAAA,GAAc,OAAA,CAAQ,WAAA;AAC5C,QAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,wBAAA,EAA2B,OAAA,CAAQ,WAAW,CAAA,CAAE,CAAA;AAAA,MAC9D;AAGA,MAAA,MAAM,YAAA,GAAe,IAAA,CAAK,eAAA,CAAgB,QAAA,CAAS,IAAI,CAAA;AACvD,MAAA,YAAA,CAAa,YAAY,QAAQ,CAAA;AAGjC,MAAA,IAAI,mBAAqC,EAAC;AAC1C,MAAA,IAAI,IAAA,CAAK,MAAA,CAAO,cAAA,IAAkB,CAAC,QAAQ,SAAA,EAAW;AACpD,QAAA,OAAA,CAAQ,IAAI,wDAAwD,CAAA;AACpE,QAAA,gBAAA,GAAmB,MAAM,KAAK,oBAAA,EAAqB;AACnD,QAAA,MAAM,eAAe,MAAA,CAAO,MAAA,CAAO,gBAAgB,CAAA,CAAE,MAAA,CAAO,OAAO,CAAA,CAAE,MAAA;AACrE,QAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,kBAAA,EAAqB,YAAY,CAAA,aAAA,CAAe,CAAA;AAAA,MAC9D;AAGA,MAAA,MAAM,YAAA,GAAe,KAAK,oBAAA,EAAqB;AAC/C,MAAA,MAAM,kBACJ,YAAA,CAAa,SAAA,IAAa,aAAa,QAAA,IAAY,YAAA,CAAa,YAAY,YAAA,CAAa,OAAA;AAC3F,MAAA,IAAI,eAAA,EAAiB;AACnB,QAAA,YAAA,CAAa,gBAAgB,YAAY,CAAA;AACzC,QAAA,OAAA,CAAQ,IAAI,2DAA2D,CAAA;AAAA,MACzE;AAIA,MAAA,MAAM,gBAAA,GAAmB,CAAC,QAAA,CAAS,YAAA,EAAc,GAAI,QAAA,CAAS,mBAAA,IAAuB,EAAG,CAAA;AAExF,MAAA,KAAA,MAAW,YAAY,gBAAA,EAAkB;AACvC,QAAA,MAAM,SAAA,GAAY,GAAA,CAAI,QAAA,CAAS,QAAQ,CAAA;AACvC,QAAA,IAAI,CAAC,SAAA,EAAW;AACd,UAAA,IAAI,QAAA,KAAa,SAAS,YAAA,EAAc;AAEtC,YAAA,MAAM,IAAI,KAAA;AAAA,cACR,CAAA,eAAA,EAAkB,QAAQ,CAAA,6BAAA,EAAgC,QAAA,CAAS,WAAW,CAAA,QAAA;AAAA,aAChF;AAAA,UACF;AAEA,UAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,wCAAA,EAA2C,QAAQ,CAAA,CAAE,CAAA;AACjE,UAAA;AAAA,QACF;AAEA,QAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,8BAAA,EAAiC,QAAQ,CAAA,CAAE,CAAA;AACvD,QAAA,MAAM,YAAA,GAAe,SAAA,CAAU,OAAA,EAAQ,CAAE,SAAS,OAAO,CAAA;AACzD,QAAA,MAAM,WAAA,GAAc,YAAA,CAAa,MAAA,CAAO,YAAY,CAAA;AACpD,QAAA,GAAA,CAAI,WAAW,QAAA,EAAU,MAAA,CAAO,IAAA,CAAK,WAAA,EAAa,OAAO,CAAC,CAAA;AAC1D,QAAA,MAAA,CAAO,aAAA,CAAc,KAAK,QAAQ,CAAA;AAAA,MACpC;AAGA,MAAA,MAAM,aAAa,IAAA,CAAK,gBAAA,CAAiB,GAAA,EAAK,OAAA,EAAS,kBAAkB,QAAQ,CAAA;AACjF,MAAA,MAAA,CAAO,UAAA,CAAW,IAAA,CAAK,GAAG,UAAU,CAAA;AAGpC,MAAA,IAAI,IAAA,CAAK,OAAO,eAAA,EAAiB;AAE/B,QAAA,MAAM,aAAA,GAAgB,IAAA,CAAK,kBAAA,CAAmB,UAAU,CAAA;AACxD,QAAA,MAAM,oBAAoB,IAAA,CAAK,eAAA,CAAgB,OAAO,GAAA,EAAK,MAAA,CAAO,QAAQ,aAAa,CAAA;AACvF,QAAA,MAAA,CAAO,aAAA,CAAc,IAAA,CAAK,GAAG,iBAAiB,CAAA;AAAA,MAChD;AAIA,MAAA,MAAM,YAAA,GAAe,MAAM,IAAA,CAAK,oBAAA,CAAqB,GAAG,CAAA;AAExD,MAAA,MAAA,CAAO,OAAA,GAAU,IAAA;AACjB,MAAA,OAAO,EAAE,MAAA,EAAQ,YAAA,EAAc,MAAA,EAAO;AAAA,IACxC,SAAS,KAAA,EAAO;AACd,MAAA,MAAM,UAAU,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AACrE,MAAA,MAAA,CAAO,MAAA,CAAO,KAAK,OAAO,CAAA;AAC1B,MAAA,MAAM,KAAA;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,qBAAqB,MAAA,EAAiC;AAC5D,IAAA,OAAO,IAAI,OAAA,CAAQ,CAACN,QAAAA,EAAS,MAAA,KAAW;AACtC,MAAA,MAAM,OAAA,GAAUO,0BAAS,KAAA,EAAO;AAAA,QAC9B,IAAA,EAAM,EAAE,KAAA,EAAO,CAAA;AAAE;AAAA,OAClB,CAAA;AAED,MAAA,MAAM,SAAmB,EAAC;AAC1B,MAAA,MAAM,WAAA,GAAc,IAAIC,kBAAA,EAAY;AAEpC,MAAA,WAAA,CAAY,GAAG,MAAA,EAAQ,CAAC,UAAkB,MAAA,CAAO,IAAA,CAAK,KAAK,CAAC,CAAA;AAC5D,MAAA,WAAA,CAAY,EAAA,CAAG,OAAO,MAAMR,QAAAA,CAAQ,OAAO,MAAA,CAAO,MAAM,CAAC,CAAC,CAAA;AAC1D,MAAA,WAAA,CAAY,EAAA,CAAG,SAAS,MAAM,CAAA;AAE9B,MAAA,OAAA,CAAQ,EAAA,CAAG,SAAS,MAAM,CAAA;AAC1B,MAAA,OAAA,CAAQ,KAAK,WAAW,CAAA;AAExB,MAAA,MAAM,OAAA,GAAU,OAAO,UAAA,EAAW;AAElC,MAAA,KAAA,MAAW,SAAS,OAAA,EAAS;AAC3B,QAAA,MAAM,YAAY,KAAA,CAAM,SAAA;AACxB,QAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,MAAA,CAAO,IAAA,GAAO,IAAI,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,IAAI,CAAA,mBAAI,IAAI,IAAA,EAAK;AAEzE,QAAA,IAAI,MAAM,WAAA,EAAa;AAErB,UAAA,OAAA,CAAQ,OAAO,EAAA,EAAI;AAAA,YACjB,MAAM,SAAA,CAAU,QAAA,CAAS,GAAG,CAAA,GAAI,YAAY,SAAA,GAAY,GAAA;AAAA,YACxD,IAAA,EAAM,KAAA;AAAA,YACN,KAAA,EAAO;AAAA,WACR,CAAA;AACD,UAAA;AAAA,QACF;AAEA,QAAA,MAAM,SAAA,GAAY,MAAM,OAAA,EAAQ;AAGhC,QAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,MAAA,CAAO,MAAA,KAAW,CAAA;AAEtC,QAAA,OAAA,CAAQ,OAAO,SAAA,EAAW;AAAA,UACxB,IAAA,EAAM,SAAA;AAAA,UACN,IAAA,EAAM,KAAA;AAAA,UACN;AAAA,SACD,CAAA;AAAA,MACH;AAEA,MAAA,OAAA,CAAQ,QAAA,EAAS;AAAA,IACnB,CAAC,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgB,IAAA,EAA2D;AACjF,IAAA,QAAQ,IAAA;AAAM,MACZ,KAAK,WAAA;AACH,QAAA,OAAO,IAAA,CAAK,qBAAA;AAAA,MACd,KAAK,MAAA;AAAA,MACL;AAEE,QAAA,OAAO,IAAA,CAAK,gBAAA;AAAA;AAChB,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,sBAAsB,QAAA,EAAqC;AACjE,IAAA,QAAQ,SAAS,IAAA;AAAM,MACrB,KAAK,WAAA;AAEH,QAAA,OAAO,OAAA;AAAA,MACT,KAAK,MAAA;AACH,QAAA,OAAO,cAAA;AAAA,MACT,KAAK,QAAA;AAEH,QAAA,OAAO,EAAA;AAAA,MACT;AAEE,QAAA,MAAM,WAAW,QAAA,CAAS,YAAA;AAC1B,QAAA,IAAI,QAAA,CAAS,QAAA,CAAS,GAAG,CAAA,EAAG;AAC1B,UAAA,OAAO,QAAA,CAAS,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,CAAA;AAAA,QAC9B;AAEA,QAAA,OAAO,EAAA;AAAA;AACX,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,mBAAmB,UAAA,EAAqE;AAC9F,IAAA,MAAM,QAAuD,EAAC;AAE9D,IAAA,KAAA,MAAW,YAAY,UAAA,EAAY;AACjC,MAAA,IAAI,IAAA,CAAK,MAAA,CAAO,IAAA,IAAQ,QAAA,CAAS,QAAA,CAAS,QAAQ,IAAA,CAAK,MAAA,CAAO,IAAI,CAAA,UAAA,CAAY,CAAA,EAAG;AAC/E,QAAA,KAAA,CAAM,OAAA,GAAU,QAAA;AAAA,MAClB,CAAA,MAAA,IAAW,IAAA,CAAK,MAAA,CAAO,IAAA,IAAQ,QAAA,CAAS,QAAA,CAAS,CAAA,KAAA,EAAQ,IAAA,CAAK,MAAA,CAAO,IAAI,CAAA,UAAA,CAAY,CAAA,EAAG;AACtF,QAAA,KAAA,CAAM,MAAA,GAAS,QAAA;AAAA,MACjB,WAAW,QAAA,CAAS,QAAA,CAAS,KAAK,MAAA,CAAO,SAAA,CAAU,QAAQ,CAAA,EAAG;AAC5D,QAAA,KAAA,CAAM,SAAA,GAAY,QAAA;AAAA,MACpB,WAAW,QAAA,CAAS,QAAA,CAAS,KAAK,MAAA,CAAO,QAAA,CAAS,QAAQ,CAAA,EAAG;AAC3D,QAAA,KAAA,CAAM,QAAA,GAAW,QAAA;AAAA,MACnB,WAAW,QAAA,CAAS,QAAA,CAAS,KAAK,MAAA,CAAO,QAAA,CAAS,QAAQ,CAAA,EAAG;AAC3D,QAAA,KAAA,CAAM,QAAA,GAAW,QAAA;AAAA,MACnB,WAAW,QAAA,CAAS,QAAA,CAAS,KAAK,MAAA,CAAO,OAAA,CAAQ,QAAQ,CAAA,EAAG;AAC1D,QAAA,KAAA,CAAM,OAAA,GAAU,QAAA;AAAA,MAClB;AAAA,IACF;AAEA,IAAA,OAAO,KAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,oBAAA,GAA8C;AACpD,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,MAAA,CAAO,OAAA,IAAW,EAAC;AACxC,IAAA,MAAM,cAAA,GAAiB,MAAA,CAAO,OAAA,CAAQ,OAAO,CAAA,CAAE,MAAA,CAAO,CAAC,CAAC,CAAA,EAAG,GAAG,CAAA,KAAM,GAAA,CAAI,OAAO,CAAA;AAE/E,IAAA,IAAI,cAAA,CAAe,WAAW,CAAA,EAAG;AAC/B,MAAA,OAAO,EAAE,WAAW,EAAA,EAAI,QAAA,EAAU,IAAI,QAAA,EAAU,EAAA,EAAI,SAAS,EAAA,EAAG;AAAA,IAClE;AAEA,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,gCAAA,EAAmC,cAAA,CAAe,MAAM,CAAA,kBAAA,CAAoB,CAAA;AACxF,IAAA,OAAO,cAAA,CAAe,eAAe,OAAO,CAAA;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,iBACN,GAAA,EACA,OAAA,EACA,OAAA,GAA4B,IAC5B,QAAA,EACU;AACV,IAAA,MAAM,QAAkB,EAAC;AACzB,IAAA,MAAM,UAAA,GAAa,QAAA,GAAW,IAAA,CAAK,qBAAA,CAAsB,QAAQ,CAAA,GAAI,cAAA;AACrE,IAAA,MAAM,QAAA,GAAW,UAAA,GAAa,CAAA,EAAG,UAAU,CAAA,CAAA,CAAA,GAAM,EAAA;AACjD,IAAA,MAAM,YAAY,CAAA,EAAG,QAAQ,GAAG,IAAA,CAAK,MAAA,CAAO,aAAa,GAAG,CAAA,CAAA;AAC5D,IAAA,MAAM,WAAW,CAAA,EAAG,QAAQ,GAAG,IAAA,CAAK,MAAA,CAAO,aAAa,EAAE,CAAA,CAAA;AAG1D,IAAA,MAAM,YAAA,GAAe,KAAK,oBAAA,EAAqB;AAC/C,IAAA,MAAM,kBAAA,GAAqB,YAAA,CAAa,SAAA,CAAU,MAAA,GAAS,CAAA;AAC3D,IAAA,MAAM,iBAAA,GAAoB,YAAA,CAAa,QAAA,CAAS,MAAA,GAAS,CAAA;AACzD,IAAA,MAAM,iBAAA,GAAoB,YAAA,CAAa,QAAA,CAAS,MAAA,GAAS,CAAA;AACzD,IAAA,MAAM,gBAAA,GAAmB,YAAA,CAAa,OAAA,CAAQ,MAAA,GAAS,CAAA;AAGvD,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,SAAA,CAAU,OAAA,EAAS;AACjC,MAAA,MAAM,OAAO,CAAA,EAAG,SAAS,IAAI,IAAA,CAAK,MAAA,CAAO,UAAU,QAAQ,CAAA,CAAA;AAC3D,MAAA,IAAI,OAAA,GAAU,OAAA,CAAQ,gBAAA,IAAoB,OAAA,CAAQ,SAAA,IAAa,kBAAA;AAC/D,MAAA,IAAI,kBAAA,EAAoB;AACtB,QAAA,OAAA,IAAW,4CAA4C,YAAA,CAAa,SAAA;AACpE,QAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,uCAAA,EAA0C,IAAI,CAAA,CAAE,CAAA;AAAA,MAC9D;AACA,MAAA,GAAA,CAAI,QAAQ,IAAA,EAAM,MAAA,CAAO,IAAA,CAAK,OAAA,EAAS,OAAO,CAAC,CAAA;AAC/C,MAAA,KAAA,CAAM,KAAK,IAAI,CAAA;AACf,MAAA,IAAI,QAAQ,SAAA,EAAW,OAAA,CAAQ,GAAA,CAAI,CAAA,oCAAA,EAAuC,IAAI,CAAA,CAAE,CAAA;AAAA,IAClF;AAGA,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,OAAA,EAAS;AAChC,MAAA,MAAM,OAAO,CAAA,EAAG,SAAS,IAAI,IAAA,CAAK,MAAA,CAAO,SAAS,QAAQ,CAAA,CAAA;AAC1D,MAAA,IAAI,OAAA,GAAU,OAAA,CAAQ,eAAA,IAAmB,OAAA,CAAQ,QAAA,IAAY,iBAAA;AAC7D,MAAA,IAAI,iBAAA,EAAmB;AACrB,QAAA,OAAA,IAAW,2CAA2C,YAAA,CAAa,QAAA;AACnE,QAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,sCAAA,EAAyC,IAAI,CAAA,CAAE,CAAA;AAAA,MAC7D;AACA,MAAA,GAAA,CAAI,QAAQ,IAAA,EAAM,MAAA,CAAO,IAAA,CAAK,OAAA,EAAS,OAAO,CAAC,CAAA;AAC/C,MAAA,KAAA,CAAM,KAAK,IAAI,CAAA;AACf,MAAA,IAAI,QAAQ,QAAA,EAAU,OAAA,CAAQ,GAAA,CAAI,CAAA,oCAAA,EAAuC,IAAI,CAAA,CAAE,CAAA;AAAA,IACjF;AAGA,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,OAAA,EAAS;AAChC,MAAA,MAAM,OAAO,CAAA,EAAG,QAAQ,IAAI,IAAA,CAAK,MAAA,CAAO,SAAS,QAAQ,CAAA,CAAA;AACzD,MAAA,IAAI,OAAA,GAAU,OAAA,CAAQ,eAAA,IAAmB,OAAA,CAAQ,QAAA,IAAY,iBAAA;AAC7D,MAAA,IAAI,iBAAA,EAAmB;AACrB,QAAA,OAAA,IAAW,wCAAwC,YAAA,CAAa,QAAA;AAChE,QAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,sCAAA,EAAyC,IAAI,CAAA,CAAE,CAAA;AAAA,MAC7D;AACA,MAAA,GAAA,CAAI,QAAQ,IAAA,EAAM,MAAA,CAAO,IAAA,CAAK,OAAA,EAAS,OAAO,CAAC,CAAA;AAC/C,MAAA,KAAA,CAAM,KAAK,IAAI,CAAA;AACf,MAAA,IAAI,QAAQ,QAAA,EAAU,OAAA,CAAQ,GAAA,CAAI,CAAA,oCAAA,EAAuC,IAAI,CAAA,CAAE,CAAA;AAAA,IACjF;AAGA,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,OAAA,CAAQ,OAAA,EAAS;AAC/B,MAAA,MAAM,OAAO,CAAA,EAAG,QAAQ,IAAI,IAAA,CAAK,MAAA,CAAO,QAAQ,QAAQ,CAAA,CAAA;AACxD,MAAA,IAAI,OAAA,GAAU,OAAA,CAAQ,cAAA,IAAkB,OAAA,CAAQ,OAAA,IAAW,gBAAA;AAC3D,MAAA,IAAI,gBAAA,EAAkB;AACpB,QAAA,OAAA,IAAW,uCAAuC,YAAA,CAAa,OAAA;AAC/D,QAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,qCAAA,EAAwC,IAAI,CAAA,CAAE,CAAA;AAAA,MAC5D;AACA,MAAA,GAAA,CAAI,QAAQ,IAAA,EAAM,MAAA,CAAO,IAAA,CAAK,OAAA,EAAS,OAAO,CAAC,CAAA;AAC/C,MAAA,KAAA,CAAM,KAAK,IAAI,CAAA;AACf,MAAA,IAAI,QAAQ,OAAA,EAAS,OAAA,CAAQ,GAAA,CAAI,CAAA,oCAAA,EAAuC,IAAI,CAAA,CAAE,CAAA;AAAA,IAChF;AAGA,IAAA,IAAI,IAAA,CAAK,OAAO,IAAA,EAAM;AACpB,MAAA,MAAM,cAAc,CAAA,EAAG,QAAQ,CAAA,KAAA,EAAQ,IAAA,CAAK,OAAO,IAAI,CAAA,UAAA,CAAA;AACvD,MAAA,MAAM,iBAAiB,OAAA,CAAQ,OAAA,IAAW,CAAA,yBAAA,EAA4B,IAAA,CAAK,OAAO,IAAI,CAAA;AAAA,CAAA;AACtF,MAAA,GAAA,CAAI,QAAQ,WAAA,EAAa,MAAA,CAAO,IAAA,CAAK,cAAA,EAAgB,OAAO,CAAC,CAAA;AAC7D,MAAA,KAAA,CAAM,KAAK,WAAW,CAAA;AACtB,MAAA,IAAI,QAAQ,OAAA,EAAS,OAAA,CAAQ,GAAA,CAAI,CAAA,qCAAA,EAAwC,WAAW,CAAA,CAAE,CAAA;AAAA,WACjF,OAAA,CAAQ,GAAA,CAAI,CAAA,sCAAA,EAAyC,WAAW,CAAA,CAAE,CAAA;AAEvE,MAAA,MAAM,aAAa,CAAA,EAAG,QAAQ,CAAA,KAAA,EAAQ,IAAA,CAAK,OAAO,IAAI,CAAA,UAAA,CAAA;AACtD,MAAA,MAAM,gBAAgB,OAAA,CAAQ,MAAA,IAAU,CAAA,wBAAA,EAA2B,IAAA,CAAK,OAAO,IAAI,CAAA;AAAA,CAAA;AACnF,MAAA,GAAA,CAAI,QAAQ,UAAA,EAAY,MAAA,CAAO,IAAA,CAAK,aAAA,EAAe,OAAO,CAAC,CAAA;AAC3D,MAAA,KAAA,CAAM,KAAK,UAAU,CAAA;AACrB,MAAA,IAAI,QAAQ,MAAA,EAAQ,OAAA,CAAQ,GAAA,CAAI,CAAA,oCAAA,EAAuC,UAAU,CAAA,CAAE,CAAA;AAAA,WAC9E,OAAA,CAAQ,GAAA,CAAI,CAAA,qCAAA,EAAwC,UAAU,CAAA,CAAE,CAAA;AAAA,IACvE;AAEA,IAAA,OAAO,KAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,SAAA,GAA8B;AAC5B,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,WAAA,CAAY,MAAA,EAAgB,OAAA,EAAyC;AACzE,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,kCAAA,EAAqC,MAAA,CAAO,MAAM,CAAA,MAAA,CAAQ,CAAA;AACtE,IAAA,MAAM,EAAE,QAAQ,aAAA,EAAe,MAAA,KAAW,MAAM,IAAA,CAAK,KAAA,CAAM,MAAA,EAAQ,OAAO,CAAA;AAC1E,IAAA,OAAA,CAAQ,IAAI,CAAA,uBAAA,CAAA,EAA2B,IAAA,CAAK,UAAU,MAAA,EAAQ,IAAA,EAAM,CAAC,CAAC,CAAA;AACtE,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,oBAAA,EAAuB,aAAA,CAAc,MAAM,CAAA,MAAA,CAAQ,CAAA;AAC/D,IAAA,OAAO,aAAA;AAAA,EACT;AACF,CAAA;;;AGnpBA,IAAM,OAAA,GAAU,IAAIS,iBAAA,EAAQ;AAE5B,OAAA,CACG,KAAK,aAAa,CAAA,CAClB,YAAY,wEAAwE,CAAA,CACpF,QAAQ,OAAO,CAAA;AAElB,OAAA,CACG,QAAQ,eAAe,CAAA,CACvB,WAAA,CAAY,8BAA8B,EAC1C,MAAA,CAAO,qBAAA,EAAuB,iDAAiD,CAAA,CAC/E,OAAO,qBAAA,EAAuB,4DAA4D,CAAA,CAC1F,MAAA,CAAO,uBAAuB,sCAAsC,CAAA,CACpE,MAAA,CAAO,oBAAA,EAAsB,qCAAqC,CAAA,CAClE,MAAA,CAAO,oBAAA,EAAsB,qCAAqC,EAClE,MAAA,CAAO,mBAAA,EAAqB,oCAAoC,CAAA,CAChE,OAAO,eAAA,EAAiB,8BAA8B,EACtD,MAAA,CAAO,OAAO,OAAe,OAAA,KAAY;AACxC,EAAA,MAAM,OAAA,GAAUC,oBAAA,CAAI,0BAA0B,CAAA,CAAE,KAAA,EAAM;AAEtD,EAAA,IAAI;AAEF,IAAA,IAAI,MAAA;AACJ,IAAA,MAAM,UAAA,GAAa,QAAQ,MAAA,IAAU,yBAAA;AAErC,IAAA,IAAIT,aAAAA,CAAWD,YAAAA,CAAQ,UAAU,CAAC,CAAA,EAAG;AACnC,MAAA,MAAA,GAAS,MAAM,WAAW,UAAU,CAAA;AACpC,MAAA,OAAA,CAAQ,IAAA,GAAO,sBAAA;AAAA,IACjB,CAAA,MAAO;AACL,MAAA,OAAA,CAAQ,IAAA,GAAO,sCAAA;AACf,MAAA,MAAA,GAAS,iBAAA,CAAkB,EAAE,CAAA;AAAA,IAC/B;AAGA,IAAA,IAAI,OAAA,CAAQ,aAAa,KAAA,EAAO;AAC9B,MAAA,MAAA,GAAS,EAAE,GAAG,MAAA,EAAQ,eAAA,EAAiB,KAAA,EAAM;AAAA,IAC/C;AAGA,IAAA,MAAM,eAAA,GAAkB,sBAAA,CAAuB,KAAA,CAAM,MAAM,CAAA;AAE3D,IAAA,OAAA,CAAQ,IAAA,GAAO,uBAAA;AAGf,IAAA,MAAM,SAAA,GAAYA,aAAQ,KAAK,CAAA;AAC/B,IAAA,IAAI,CAACC,aAAAA,CAAW,SAAS,CAAA,EAAG;AAC1B,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,sBAAA,EAAyB,SAAS,CAAA,CAAE,CAAA;AAAA,IACtD;AACA,IAAA,MAAM,WAAA,GAAcE,gBAAa,SAAS,CAAA;AAG1C,IAAA,MAAM,eAKF,EAAC;AAEL,IAAA,IAAI,QAAQ,SAAA,EAAW;AACrB,MAAA,YAAA,CAAa,mBAAmBA,eAAAA,CAAaH,YAAAA,CAAQ,OAAA,CAAQ,SAAS,GAAG,OAAO,CAAA;AAAA,IAClF;AAEA,IAAA,IAAI,QAAQ,QAAA,EAAU;AACpB,MAAA,YAAA,CAAa,kBAAkBG,eAAAA,CAAaH,YAAAA,CAAQ,OAAA,CAAQ,QAAQ,GAAG,OAAO,CAAA;AAAA,IAChF;AAEA,IAAA,IAAI,QAAQ,QAAA,EAAU;AACpB,MAAA,YAAA,CAAa,kBAAkBG,eAAAA,CAAaH,YAAAA,CAAQ,OAAA,CAAQ,QAAQ,GAAG,OAAO,CAAA;AAAA,IAChF;AAEA,IAAA,IAAI,QAAQ,OAAA,EAAS;AACnB,MAAA,YAAA,CAAa,iBAAiBG,eAAAA,CAAaH,YAAAA,CAAQ,OAAA,CAAQ,OAAO,GAAG,OAAO,CAAA;AAAA,IAC9E;AAGA,IAAC,YAAA,CAAqB,WAAA,GAAcW,aAAA,CAAS,SAAS,CAAA;AAEtD,IAAA,OAAA,CAAQ,IAAA,GAAO,qBAAA;AAGf,IAAA,MAAM,OAAA,GAAU,IAAI,OAAA,CAAQ,eAAe,CAAA;AAC3C,IAAA,MAAM,EAAE,QAAQ,MAAA,EAAO,GAAI,MAAM,OAAA,CAAQ,KAAA,CAAM,aAAa,YAAY,CAAA;AAGxE,IAAA,MAAM,aACJ,OAAA,CAAQ,MAAA,IAAU,SAAA,CAAU,OAAA,CAAQ,WAAW,cAAc,CAAA;AAE/D,IAAA,OAAA,CAAQ,IAAA,GAAO,wBAAA;AACf,IAAAC,gBAAA,CAAc,YAAY,MAAM,CAAA;AAEhC,IAAA,OAAA,CAAQ,OAAA,CAAQC,sBAAA,CAAM,KAAA,CAAM,oBAAoB,CAAC,CAAA;AAGjD,IAAA,OAAA,CAAQ,IAAI,EAAE,CAAA;AACd,IAAA,OAAA,CAAQ,IAAIA,sBAAA,CAAM,IAAA,CAAK,kBAAkB,CAAA,EAAG,OAAO,iBAAiB,CAAA;AACpE,IAAA,OAAA,CAAQ,IAAIA,sBAAA,CAAM,IAAA,CAAK,gBAAgB,CAAA,EAAG,gBAAgB,YAAY,CAAA;AACtE,IAAA,OAAA,CAAQ,IAAI,EAAE,CAAA;AAEd,IAAA,IAAI,MAAA,CAAO,aAAA,CAAc,MAAA,GAAS,CAAA,EAAG;AACnC,MAAA,OAAA,CAAQ,GAAA,CAAIA,sBAAA,CAAM,IAAA,CAAK,iBAAiB,CAAC,CAAA;AACzC,MAAA,MAAA,CAAO,aAAA,CAAc,OAAA,CAAQ,CAAC,CAAA,KAAM,QAAQ,GAAA,CAAI,CAAA,EAAA,EAAKA,sBAAA,CAAM,IAAA,CAAK,QAAG,CAAC,CAAA,CAAA,EAAI,CAAC,EAAE,CAAC,CAAA;AAAA,IAC9E;AAEA,IAAA,IAAI,MAAA,CAAO,UAAA,CAAW,MAAA,GAAS,CAAA,EAAG;AAChC,MAAA,OAAA,CAAQ,GAAA,CAAIA,sBAAA,CAAM,IAAA,CAAK,cAAc,CAAC,CAAA;AACtC,MAAA,MAAA,CAAO,UAAA,CAAW,OAAA,CAAQ,CAAC,CAAA,KAAM,QAAQ,GAAA,CAAI,CAAA,EAAA,EAAKA,sBAAA,CAAM,IAAA,CAAK,QAAG,CAAC,CAAA,CAAA,EAAI,CAAC,EAAE,CAAC,CAAA;AAAA,IAC3E;AAEA,IAAA,IAAI,MAAA,CAAO,QAAA,CAAS,MAAA,GAAS,CAAA,EAAG;AAC9B,MAAA,OAAA,CAAQ,IAAI,EAAE,CAAA;AACd,MAAA,OAAA,CAAQ,GAAA,CAAIA,sBAAA,CAAM,MAAA,CAAO,WAAW,CAAC,CAAA;AACrC,MAAA,MAAA,CAAO,QAAA,CAAS,OAAA,CAAQ,CAAC,CAAA,KAAM,QAAQ,GAAA,CAAI,CAAA,EAAA,EAAKA,sBAAA,CAAM,MAAA,CAAO,GAAG,CAAC,CAAA,CAAA,EAAI,CAAC,EAAE,CAAC,CAAA;AAAA,IAC3E;AAEA,IAAA,OAAA,CAAQ,IAAI,EAAE,CAAA;AACd,IAAA,OAAA,CAAQ,GAAA,CAAIA,sBAAA,CAAM,KAAA,CAAM,SAAS,GAAG,UAAU,CAAA;AAAA,EAChD,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,IAAA,CAAKA,sBAAA,CAAM,GAAA,CAAI,iBAAiB,CAAC,CAAA;AACzC,IAAA,OAAA,CAAQ,KAAA;AAAA,MACNA,sBAAA,CAAM,IAAI,KAAA,YAAiB,KAAA,GAAQ,MAAM,OAAA,GAAU,MAAA,CAAO,KAAK,CAAC;AAAA,KAClE;AACA,IAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EAChB;AACF,CAAC,CAAA;AAEH,OAAA,CACG,OAAA,CAAQ,MAAM,CAAA,CACd,WAAA,CAAY,oCAAoC,CAAA,CAChD,MAAA,CAAO,qBAAA,EAAuB,gDAAgD,CAAA,CAC9E,MAAA,CAAO,CAAC,OAAA,KAAY;AACnB,EAAA,MAAM,UAAA,GAAab,YAAAA,CAAQ,OAAA,CAAQ,MAAA,IAAU,yBAAyB,CAAA;AAEtE,EAAA,IAAIC,aAAAA,CAAW,UAAU,CAAA,EAAG;AAC1B,IAAA,OAAA,CAAQ,GAAA;AAAA,MACNY,sBAAA,CAAM,MAAA,CAAO,CAAA,mCAAA,EAAsC,UAAU,CAAA,CAAE;AAAA,KACjE;AACA,IAAA,OAAA,CAAQ,GAAA,CAAIA,sBAAA,CAAM,MAAA,CAAO,0CAA0C,CAAC,CAAA;AACpE,IAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EAChB;AAEA,EAAA,MAAM,WAAW,sBAAA,EAAuB;AACxC,EAAAD,gBAAA,CAAc,YAAY,QAAQ,CAAA;AAClC,EAAA,OAAA,CAAQ,IAAIC,sBAAA,CAAM,KAAA,CAAM,CAAA,4BAAA,EAA+B,UAAU,EAAE,CAAC,CAAA;AACpE,EAAA,OAAA,CAAQ,IAAI,EAAE,CAAA;AACd,EAAA,OAAA,CAAQ,IAAI,aAAa,CAAA;AACzB,EAAA,OAAA,CAAQ,GAAA,CAAI,aAAaA,sBAAA,CAAM,IAAA,CAAKF,cAAS,UAAU,CAAC,CAAC,CAAA,2BAAA,CAA6B,CAAA;AACtF,EAAA,OAAA,CAAQ,IAAI,CAAA,SAAA,EAAYE,sBAAA,CAAM,IAAA,CAAK,+BAA+B,CAAC,CAAA,kBAAA,CAAoB,CAAA;AACzF,CAAC,CAAA;AAEH,OAAA,CACG,OAAA,CAAQ,cAAc,CAAA,CACtB,WAAA,CAAY,iDAAiD,CAAA,CAC7D,MAAA,CAAO,OAAO,KAAA,KAAkB;AAC/B,EAAA,MAAM,OAAA,GAAUH,oBAAA,CAAI,sBAAsB,CAAA,CAAE,KAAA,EAAM;AAElD,EAAA,IAAI;AACF,IAAA,MAAM,SAAA,GAAYV,aAAQ,KAAK,CAAA;AAC/B,IAAA,IAAI,CAACC,aAAAA,CAAW,SAAS,CAAA,EAAG;AAC1B,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,sBAAA,EAAyB,SAAS,CAAA,CAAE,CAAA;AAAA,IACtD;AAEA,IAAA,MAAM,WAAA,GAAcE,gBAAa,SAAS,CAAA;AAC1C,IAAA,MAAME,OAAAA,GAAAA,CAAU,MAAM,OAAO,SAAS,CAAA,EAAG,OAAA;AACzC,IAAA,MAAM,GAAA,GAAM,IAAIA,OAAAA,CAAO,WAAW,CAAA;AAElC,IAAA,MAAM,EAAE,cAAA,EAAAS,eAAAA,EAAe,GAAI,MAAM,OAAA,CAAA,OAAA,EAAA,CAAA,IAAA,CAAA,OAAA,cAAA,EAAA,EAAA,iBAAA,CAAA,CAAA;AACjC,IAAA,MAAM,QAAA,GAAW,IAAIA,eAAAA,EAAe;AACpC,IAAA,MAAM,MAAA,GAAS,QAAA,CAAS,MAAA,CAAO,GAAG,CAAA;AAClC,IAAA,MAAM,UAAA,GAAa,QAAA,CAAS,oBAAA,CAAqB,MAAM,CAAA;AAEvD,IAAA,OAAA,CAAQ,QAAQ,kBAAkB,CAAA;AAElC,IAAA,OAAA,CAAQ,IAAI,EAAE,CAAA;AACd,IAAA,OAAA,CAAQ,GAAA,CAAID,sBAAA,CAAM,IAAA,CAAK,OAAO,GAAG,SAAS,CAAA;AAC1C,IAAA,OAAA,CAAQ,GAAA,CAAIA,sBAAA,CAAM,IAAA,CAAK,SAAS,GAAG,UAAU,CAAA;AAC7C,IAAA,OAAA,CAAQ,IAAI,EAAE,CAAA;AAGd,IAAA,MAAM,QAAA,GAAW;AAAA,MACf,yBAAA;AAAA,MACA,2BAAA;AAAA,MACA,iBAAA;AAAA,MACA,UAAA;AAAA,MACA;AAAA,KACF;AAEA,IAAA,OAAA,CAAQ,GAAA,CAAIA,sBAAA,CAAM,IAAA,CAAK,YAAY,CAAC,CAAA;AACpC,IAAA,KAAA,MAAW,QAAQ,QAAA,EAAU;AAC3B,MAAA,MAAM,MAAA,GAAS,GAAA,CAAI,QAAA,CAAS,IAAI,CAAA,KAAM,IAAA;AACtC,MAAA,MAAM,IAAA,GAAO,SAASA,sBAAA,CAAM,KAAA,CAAM,QAAG,CAAA,GAAIA,sBAAA,CAAM,KAAK,QAAG,CAAA;AACvD,MAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,EAAA,EAAK,IAAI,CAAA,CAAA,EAAI,IAAI,CAAA,CAAE,CAAA;AAAA,IACjC;AAGA,IAAA,MAAM,OAAA,GAAU,IAAI,UAAA,EAAW;AAC/B,IAAA,OAAA,CAAQ,IAAI,EAAE,CAAA;AACd,IAAA,OAAA,CAAQ,IAAIA,sBAAA,CAAM,IAAA,CAAK,cAAc,CAAA,EAAG,QAAQ,MAAM,CAAA;AAAA,EACxD,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,IAAA,CAAKA,sBAAA,CAAM,GAAA,CAAI,iBAAiB,CAAC,CAAA;AACzC,IAAA,OAAA,CAAQ,KAAA;AAAA,MACNA,sBAAA,CAAM,IAAI,KAAA,YAAiB,KAAA,GAAQ,MAAM,OAAA,GAAU,MAAA,CAAO,KAAK,CAAC;AAAA,KAClE;AACA,IAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EAChB;AACF,CAAC,CAAA;AAEH,OAAA,CAAQ,KAAA,EAAM","file":"cli.cjs","sourcesContent":["import type AdmZip from 'adm-zip';\n\n/**\n * Supported package formats\n */\nexport type PackageFormat =\n | 'scorm12'\n | 'scorm2004-3'\n | 'scorm2004-4'\n | 'cmi5'\n | 'xapi'\n | 'aicc'\n | 'unknown';\n\n/**\n * Detect the package format by examining manifest files\n */\nexport class FormatDetector {\n /**\n * Detect the package format from a ZIP file\n */\n detect(zip: AdmZip): PackageFormat {\n // Check for cmi5.xml first (most specific)\n if (zip.getEntry('cmi5.xml')) {\n return 'cmi5';\n }\n\n // Check for xAPI (tincan.xml)\n if (zip.getEntry('tincan.xml')) {\n return 'xapi';\n }\n\n // Check for SCORM via imsmanifest.xml\n const manifest = zip.getEntry('imsmanifest.xml');\n if (manifest) {\n const content = manifest.getData().toString('utf-8');\n return this.detectScormVersion(content);\n }\n\n // Check for AICC-specific files\n if (this.hasAiccFiles(zip)) {\n return 'aicc';\n }\n\n return 'unknown';\n }\n\n /**\n * Detect SCORM version from manifest content\n */\n private detectScormVersion(content: string): PackageFormat {\n // Check for SCORM 2004 4th Edition\n if (content.includes('4th Edition') || content.includes('CAM 1.4')) {\n return 'scorm2004-4';\n }\n\n // Check for SCORM 2004 3rd Edition\n if (\n content.includes('3rd Edition') ||\n content.includes('2004 3rd') ||\n content.includes('adlcp_v1p3')\n ) {\n return 'scorm2004-3';\n }\n\n // Check for SCORM 2004 (generic)\n if (content.includes('2004') && content.includes('adlseq')) {\n return 'scorm2004-3'; // Default to 3rd edition for generic 2004\n }\n\n // Check for SCORM 1.2\n if (\n content.includes('1.2') ||\n content.includes('adlcp_rootv1p2') ||\n content.includes('imscp_rootv1p1p2')\n ) {\n return 'scorm12';\n }\n\n // Check for AICC markers in manifest\n if (content.toLowerCase().includes('aicc')) {\n return 'aicc';\n }\n\n // Default to SCORM 1.2 if we have a manifest but can't determine version\n return 'scorm12';\n }\n\n /**\n * Check for AICC-specific files\n */\n private hasAiccFiles(zip: AdmZip): boolean {\n const aiccExtensions = ['.crs', '.au', '.des', '.cst'];\n const entries = zip.getEntries();\n\n for (const entry of entries) {\n const name = entry.entryName.toLowerCase();\n if (aiccExtensions.some((ext) => name.endsWith(ext))) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Get a human-readable format name\n */\n getFormatDisplayName(format: PackageFormat): string {\n const names: Record<PackageFormat, string> = {\n scorm12: 'SCORM 1.2',\n 'scorm2004-3': 'SCORM 2004 3rd Edition',\n 'scorm2004-4': 'SCORM 2004 4th Edition',\n cmi5: 'cmi5',\n xapi: 'xAPI (Tin Can)',\n aicc: 'AICC',\n unknown: 'Unknown',\n };\n\n return names[format];\n }\n}\n","import type AdmZip from 'adm-zip';\n\n/**\n * Supported e-learning authoring tools\n */\nexport type AuthoringTool =\n | 'rise'\n | 'storyline'\n | 'captivate'\n | 'lectora'\n | 'xyleme'\n | 'unknown';\n\n/**\n * Information about the detected authoring tool\n */\nexport interface AuthoringToolInfo {\n tool: AuthoringTool;\n displayName: string;\n /** Path to the main HTML file to patch */\n mainHtmlPath: string;\n /** Additional HTML files that should also be patched (e.g., index_lms.html for Storyline) */\n additionalHtmlPaths?: string[];\n /** Version of the authoring tool if detectable */\n version?: string;\n}\n\n/**\n * Detect the authoring tool used to create a course package.\n * This determines which HTML injection strategy to use.\n */\nexport class AuthoringToolDetector {\n /**\n * Detect the authoring tool from a ZIP file\n */\n detect(zip: AdmZip): AuthoringToolInfo {\n // Check for Rise first (most common in our use case)\n const riseResult = this.detectRise(zip);\n if (riseResult) return riseResult;\n\n // Check for Storyline\n const storylineResult = this.detectStoryline(zip);\n if (storylineResult) return storylineResult;\n\n // Check for Adobe Captivate\n const captivateResult = this.detectCaptivate(zip);\n if (captivateResult) return captivateResult;\n\n // Check for Lectora\n const lectoraResult = this.detectLectora(zip);\n if (lectoraResult) return lectoraResult;\n\n // Check for Xyleme\n const xylemeResult = this.detectXyleme(zip);\n if (xylemeResult) return xylemeResult;\n\n // Unknown - try to find any index.html\n return this.detectUnknown(zip);\n }\n\n /**\n * Detect Articulate Rise packages\n * Rise packages have: scormcontent/index.html with __loadEntry() call\n */\n private detectRise(zip: AdmZip): AuthoringToolInfo | null {\n const indexEntry = zip.getEntry('scormcontent/index.html');\n if (!indexEntry) return null;\n\n const content = indexEntry.getData().toString('utf-8');\n\n // Rise has a distinctive __loadEntry() call\n if (content.includes('__loadEntry()')) {\n // Try to extract version from comments\n const versionMatch = content.match(/Rise\\s+v?(\\d+\\.\\d+(?:\\.\\d+)?)/i);\n return {\n tool: 'rise',\n displayName: 'Articulate Rise',\n mainHtmlPath: 'scormcontent/index.html',\n version: versionMatch?.[1],\n };\n }\n\n // Also check for Rise-specific lib structure\n if (zip.getEntry('scormcontent/lib/rise/')) {\n return {\n tool: 'rise',\n displayName: 'Articulate Rise',\n mainHtmlPath: 'scormcontent/index.html',\n };\n }\n\n return null;\n }\n\n /**\n * Detect Articulate Storyline packages\n * Storyline packages have: story.html with window.globals.publishSource = 'storyline'\n * or html5/lib/scripts/bootstrapper.min.js\n *\n * IMPORTANT: Storyline has TWO entry points that need patching:\n * - story.html: for standalone viewing\n * - index_lms.html: for LMS launch (what SCORM/xAPI uses)\n */\n private detectStoryline(zip: AdmZip): AuthoringToolInfo | null {\n const storyEntry = zip.getEntry('story.html');\n const lmsEntry = zip.getEntry('index_lms.html');\n\n // Build list of HTML files to patch\n const htmlPaths: string[] = [];\n if (storyEntry) htmlPaths.push('story.html');\n if (lmsEntry) htmlPaths.push('index_lms.html');\n\n // Check for story.html (primary entry point for standalone)\n if (storyEntry) {\n const content = storyEntry.getData().toString('utf-8');\n\n // Storyline marker in window.globals\n if (\n content.includes(\"publishSource: 'storyline'\") ||\n content.includes('publishSource:\"storyline\"')\n ) {\n const versionMatch = content.match(/playerVersion:\\s*['\"](\\d+\\.\\d+\\.\\d+\\.\\d+)['\"]/);\n return {\n tool: 'storyline',\n displayName: 'Articulate Storyline',\n mainHtmlPath: htmlPaths[0],\n additionalHtmlPaths: htmlPaths.slice(1),\n version: versionMatch?.[1],\n };\n }\n\n // Also check HTML comment for Storyline\n if (content.includes('Created using Storyline')) {\n const versionMatch = content.match(/version:\\s*(\\d+\\.\\d+\\.\\d+\\.\\d+)/);\n return {\n tool: 'storyline',\n displayName: 'Articulate Storyline',\n mainHtmlPath: htmlPaths[0],\n additionalHtmlPaths: htmlPaths.slice(1),\n version: versionMatch?.[1],\n };\n }\n }\n\n // Check for index_lms.html (LMS entry point) as fallback detection\n if (lmsEntry) {\n const content = lmsEntry.getData().toString('utf-8');\n if (\n content.includes(\"publishSource: 'storyline'\") ||\n content.includes('Created using Storyline')\n ) {\n const versionMatch = content.match(/version:\\s*(\\d+\\.\\d+\\.\\d+\\.\\d+)/);\n return {\n tool: 'storyline',\n displayName: 'Articulate Storyline',\n mainHtmlPath: htmlPaths[0],\n additionalHtmlPaths: htmlPaths.slice(1),\n version: versionMatch?.[1],\n };\n }\n }\n\n // Check for Storyline's distinctive folder structure\n if (zip.getEntry('html5/lib/scripts/bootstrapper.min.js')) {\n // Found Storyline structure, determine main HTML\n if (htmlPaths.length > 0) {\n return {\n tool: 'storyline',\n displayName: 'Articulate Storyline',\n mainHtmlPath: htmlPaths[0],\n additionalHtmlPaths: htmlPaths.slice(1),\n };\n }\n }\n\n return null;\n }\n\n /**\n * Detect Adobe Captivate packages\n * Captivate packages typically have: project.txt or Captivate.js\n */\n private detectCaptivate(zip: AdmZip): AuthoringToolInfo | null {\n // Check for Captivate markers\n const entries = zip.getEntries();\n for (const entry of entries) {\n const name = entry.entryName.toLowerCase();\n if (name.includes('captivate') && name.endsWith('.js')) {\n // Find main HTML\n const indexHtml = zip.getEntry('index.html');\n return {\n tool: 'captivate',\n displayName: 'Adobe Captivate',\n mainHtmlPath: indexHtml ? 'index.html' : 'index.html',\n };\n }\n }\n\n // Check index.html for Captivate markers\n const indexEntry = zip.getEntry('index.html');\n if (indexEntry) {\n const content = indexEntry.getData().toString('utf-8');\n if (\n content.includes('Adobe Captivate') ||\n content.includes('cp.') ||\n content.includes('cpAPIInterface')\n ) {\n return {\n tool: 'captivate',\n displayName: 'Adobe Captivate',\n mainHtmlPath: 'index.html',\n };\n }\n }\n\n return null;\n }\n\n /**\n * Detect Lectora packages\n * Lectora packages have distinctive trivantis markers\n */\n private detectLectora(zip: AdmZip): AuthoringToolInfo | null {\n const indexEntry = zip.getEntry('index.html');\n if (indexEntry) {\n const content = indexEntry.getData().toString('utf-8');\n if (\n content.includes('Trivantis') ||\n content.includes('Lectora') ||\n content.includes('trivDojo')\n ) {\n return {\n tool: 'lectora',\n displayName: 'Lectora',\n mainHtmlPath: 'index.html',\n };\n }\n }\n\n return null;\n }\n\n /**\n * Detect Xyleme/Bravais packages (thin pack or full)\n * Xyleme packages have: window.Xyleme namespace, cds-import.xml, or manifest.xml\n */\n private detectXyleme(zip: AdmZip): AuthoringToolInfo | null {\n // Check for Xyleme-specific files\n const hasCdsImport = zip.getEntry('cds-import.xml') !== null;\n const hasXylemeManifest = zip.getEntry('manifest.xml') !== null;\n\n // Check index.html for Xyleme markers\n const indexEntry = zip.getEntry('index.html');\n if (indexEntry) {\n const content = indexEntry.getData().toString('utf-8');\n\n // Xyleme has distinctive window.Xyleme namespace\n if (\n content.includes('window.Xyleme') ||\n content.includes('Xyleme.environment') ||\n hasCdsImport ||\n hasXylemeManifest\n ) {\n // Try to extract version\n const versionMatch = content.match(/XPE_PUBLIC_VERSION\\s*=\\s*['\"]([^'\"]+)['\"]/);\n\n // Collect all HTML pages that need patching (Xyleme has many page HTMLs)\n const additionalHtmlPaths: string[] = [];\n const entries = zip.getEntries();\n for (const entry of entries) {\n // Xyleme pages are GUIDs like: 9ee5c376-a354-45ca-9565-6472233fc13e.html\n if (\n entry.entryName.match(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\\.html$/i)\n ) {\n additionalHtmlPaths.push(entry.entryName);\n }\n }\n\n return {\n tool: 'xyleme',\n displayName: 'Xyleme/Bravais',\n mainHtmlPath: 'index.html',\n additionalHtmlPaths: additionalHtmlPaths.length > 0 ? additionalHtmlPaths : undefined,\n version: versionMatch?.[1],\n };\n }\n }\n\n return null;\n }\n\n /**\n * Fallback detection for unknown authoring tools\n * Try to find any suitable HTML file to patch\n */\n private detectUnknown(zip: AdmZip): AuthoringToolInfo {\n // Priority list of HTML files to look for\n const htmlFiles = [\n 'index.html',\n 'index_lms.html',\n 'story.html',\n 'launch.html',\n 'player.html',\n 'scormcontent/index.html',\n ];\n\n for (const htmlFile of htmlFiles) {\n if (zip.getEntry(htmlFile)) {\n return {\n tool: 'unknown',\n displayName: 'Unknown Authoring Tool',\n mainHtmlPath: htmlFile,\n };\n }\n }\n\n // Last resort: find any .html file at root or one level deep\n const entries = zip.getEntries();\n for (const entry of entries) {\n if (\n entry.entryName.endsWith('.html') &&\n !entry.entryName.includes('/') // root level\n ) {\n return {\n tool: 'unknown',\n displayName: 'Unknown Authoring Tool',\n mainHtmlPath: entry.entryName,\n };\n }\n }\n\n // Absolutely nothing found\n return {\n tool: 'unknown',\n displayName: 'Unknown Authoring Tool',\n mainHtmlPath: 'index.html', // Will fail later if doesn't exist\n };\n }\n\n /**\n * Get a human-readable tool name\n */\n getToolDisplayName(tool: AuthoringTool): string {\n const names: Record<AuthoringTool, string> = {\n rise: 'Articulate Rise',\n storyline: 'Articulate Storyline',\n captivate: 'Adobe Captivate',\n lectora: 'Lectora',\n xyleme: 'Xyleme/Bravais',\n unknown: 'Unknown',\n };\n return names[tool];\n }\n}\n","export { FormatDetector, type PackageFormat } from './format-detector.js';\nexport {\n AuthoringToolDetector,\n type AuthoringTool,\n type AuthoringToolInfo,\n} from './authoring-tool-detector.js';\n","import { z } from 'zod';\n\n/**\n * LRS Bridge configuration for xAPI/LRS communication\n * Enables Rise courses to communicate with Bravais LRS like Xyleme native courses\n */\nexport const LrsBridgeConfigSchema = z.object({\n /** Whether LRS bridge is enabled (default: true) */\n enabled: z.boolean().default(true),\n /** Track video/audio plays - play, pause, complete events (default: true) */\n trackMedia: z.boolean().default(true),\n /** Track page/lesson navigation (default: true) */\n trackNavigation: z.boolean().default(true),\n /** Track quiz/knowledge check answers (default: true) */\n trackQuizzes: z.boolean().default(true),\n /** Track interactive element clicks - tabs, accordions, flashcards, etc. (default: true) */\n trackInteractions: z.boolean().default(true),\n /** Allow custom xAPI statements via window.pa_patcher.lrs.sendCustomStatement() (default: true) */\n customStatements: z.boolean().default(true),\n /** Enable debug logging to console (default: false) */\n debug: z.boolean().default(false),\n /** LRS endpoint URL for direct xAPI posting - if not set, auto-detects from Bravais domain */\n lrsEndpoint: z.string().optional(),\n /** Whether to include credentials (cookies) in LRS requests (default: true) */\n lrsWithCredentials: z.boolean().default(true),\n /** Course homepage IRI for xAPI context */\n courseHomepage: z.string().optional(),\n /** Auto-detect Bravais LRS endpoint from parent frame URL (default: true) */\n autoDetectLrs: z.boolean().default(true),\n /** Basic auth credentials for LRS (Base64 encoded \"username:password\") - used when no cookie session exists */\n lrsAuth: z.string().optional(),\n /** LRS proxy endpoint URL - statements are sent here instead of direct LRS, proxy handles auth server-side */\n lrsProxyEndpoint: z.string().optional(),\n /** Employee API endpoint URL for user identity lookup (e.g., https://node.example.com/admin_api/users/employees/) */\n employeeApiEndpoint: z.string().optional(),\n /** Document GUID for xAPI statement aggregation - baked into package at wrap time */\n documentGuid: z.string().optional(),\n /** Version GUID for xAPI statement aggregation - unique per export */\n versionGuid: z.string().optional(),\n /** Original SCORM package filename - baked in at wrap time for LRS searchability */\n packageName: z.string().optional(),\n});\n\n/**\n * Configuration for a blocking asset (CSS before, JS before)\n * These load synchronously and block page rendering\n */\nexport const BlockingAssetConfigSchema = z.object({\n /** Filename for the asset */\n filename: z.string().min(1),\n /** Whether this asset is enabled */\n enabled: z.boolean().default(true),\n});\n\n/**\n * Configuration for an async asset (CSS after, JS after)\n * These load asynchronously with remote-first, local fallback\n */\nexport const AsyncAssetConfigSchema = z.object({\n /** Filename for the asset */\n filename: z.string().min(1),\n /** Whether this asset is enabled */\n enabled: z.boolean().default(true),\n /** Timeout in milliseconds for remote loading before falling back to local */\n timeout: z.number().min(1000).max(30000).default(5000),\n});\n\n/**\n * Local folder configuration for fallback files\n */\nexport const LocalFoldersConfigSchema = z.object({\n /** Folder name for CSS files within scormcontent */\n css: z.string().default('css'),\n /** Folder name for JS files within scormcontent */\n js: z.string().default('js'),\n});\n\n/**\n * Base schema for plugin configuration\n * Each plugin can extend this with their own options\n */\nexport const PluginConfigBaseSchema = z.object({\n /** Whether this plugin is enabled */\n enabled: z.boolean().default(false),\n}).passthrough(); // Allow additional properties from individual plugins\n\n/**\n * Plugins configuration - map of plugin name to plugin config\n */\nexport const PluginsConfigSchema = z.record(z.string(), PluginConfigBaseSchema).default({});\n\n/**\n * Main configuration schema for Patch-Adams\n */\nexport const PatchAdamsConfigSchema = z.object({\n /** Remote domain for assets (e.g., \"https://cdn.example.com/rise-overrides\") */\n remoteDomain: z.string().url(),\n\n /** CSS class added to <html> tag for specificity (default: \"pa-patched\") */\n htmlClass: z.string().default('pa-patched'),\n\n /** CSS class added during loading, removed when ready (default: \"pa-loading\") */\n loadingClass: z.string().default('pa-loading'),\n\n /** CSS loaded at start of <head> - blocking, prevents FOUC */\n cssBefore: BlockingAssetConfigSchema.default({\n filename: 'before.css',\n enabled: true,\n }),\n\n /** CSS loaded at end of <head> - async with fallback, for overrides */\n cssAfter: AsyncAssetConfigSchema.default({\n filename: 'after.css',\n enabled: true,\n timeout: 5000,\n }),\n\n /** JS loaded at start of <head> - blocking, for setup/interception */\n jsBefore: BlockingAssetConfigSchema.default({\n filename: 'before.js',\n enabled: true,\n }),\n\n /** JS loaded at end of <body> - async with fallback, removes loading class */\n jsAfter: AsyncAssetConfigSchema.default({\n filename: 'after.js',\n enabled: true,\n timeout: 5000,\n }),\n\n /** Local folder names for fallback files */\n localFolders: LocalFoldersConfigSchema.default({}),\n\n /** Whether to update manifest files (imsmanifest.xml, etc.) for SCORM compliance */\n updateManifests: z.boolean().default(true),\n\n /** Whether to fetch remote files and bake them as local fallbacks */\n fetchFallbacks: z.boolean().default(true),\n\n /** LRS Bridge configuration for xAPI/LRS communication with Bravais */\n lrsBridge: LrsBridgeConfigSchema.default({}),\n\n /** Plugin configurations - each plugin is keyed by its name */\n plugins: PluginsConfigSchema,\n\n /** Optional skin name — adds 'pa-skinned' + skin class to <html>, loads skin CSS/JS after core files */\n skin: z.string().min(1).optional(),\n});\n\nexport type PatchAdamsConfig = z.infer<typeof PatchAdamsConfigSchema>;\nexport type PluginsConfig = z.infer<typeof PluginsConfigSchema>;\nexport type LrsBridgeConfig = z.infer<typeof LrsBridgeConfigSchema>;\nexport type BlockingAssetConfig = z.infer<typeof BlockingAssetConfigSchema>;\nexport type AsyncAssetConfig = z.infer<typeof AsyncAssetConfigSchema>;\nexport type LocalFoldersConfig = z.infer<typeof LocalFoldersConfigSchema>;\n","import type { PatchAdamsConfig } from './schema.js';\n\n/**\n * Default configuration values for Patch-Adams\n */\nexport const defaultConfig: PatchAdamsConfig = {\n remoteDomain: 'https://cdn.example.com/rise-overrides',\n\n htmlClass: 'pa-patched',\n loadingClass: 'pa-loading',\n\n cssBefore: {\n filename: 'before.css',\n enabled: true,\n },\n\n cssAfter: {\n filename: 'after.css',\n enabled: true,\n timeout: 5000,\n },\n\n jsBefore: {\n filename: 'before.js',\n enabled: true,\n },\n\n jsAfter: {\n filename: 'after.js',\n enabled: true,\n timeout: 5000,\n },\n\n localFolders: {\n css: 'css',\n js: 'js',\n },\n\n updateManifests: true,\n\n fetchFallbacks: true,\n\n lrsBridge: {\n enabled: true,\n trackMedia: true,\n trackNavigation: true,\n trackQuizzes: true,\n trackInteractions: true,\n customStatements: true,\n debug: false,\n lrsEndpoint: undefined,\n lrsWithCredentials: true,\n courseHomepage: undefined,\n autoDetectLrs: true,\n },\n\n plugins: {},\n};\n","import { readFileSync, existsSync } from 'fs';\nimport { resolve, extname } from 'path';\nimport { pathToFileURL } from 'url';\nimport { PatchAdamsConfigSchema, type PatchAdamsConfig } from './schema.js';\nimport { defaultConfig } from './defaults.js';\n\n/**\n * Load and validate a Patch-Adams configuration file\n * Supports .ts, .js, and .json files\n */\nexport async function loadConfig(configPath: string): Promise<PatchAdamsConfig> {\n const absolutePath = resolve(configPath);\n\n if (!existsSync(absolutePath)) {\n throw new Error(`Configuration file not found: ${absolutePath}`);\n }\n\n const ext = extname(absolutePath).toLowerCase();\n let rawConfig: unknown;\n\n if (ext === '.json') {\n const content = readFileSync(absolutePath, 'utf-8');\n rawConfig = JSON.parse(content);\n } else if (ext === '.ts' || ext === '.js' || ext === '.mjs') {\n // Dynamic import for ESM modules\n const fileUrl = pathToFileURL(absolutePath).href;\n const module = await import(fileUrl);\n rawConfig = module.default ?? module;\n } else {\n throw new Error(`Unsupported config file extension: ${ext}. Use .ts, .js, .mjs, or .json`);\n }\n\n // Validate and return\n const validated = PatchAdamsConfigSchema.parse(rawConfig);\n return validated;\n}\n\n/**\n * Create a merged config with defaults\n */\nexport function mergeWithDefaults(partial: Partial<PatchAdamsConfig>): PatchAdamsConfig {\n return PatchAdamsConfigSchema.parse({\n ...defaultConfig,\n ...partial,\n });\n}\n\n/**\n * Generate a configuration file template\n */\nexport function generateConfigTemplate(): string {\n return `import type { PatchAdamsConfig } from '@patch-adams/core';\n\nconst config: PatchAdamsConfig = {\n // Remote domain where your CSS/JS files are hosted\n remoteDomain: 'https://cdn.example.com/rise-overrides',\n\n // HTML classes for specificity and loading state\n htmlClass: 'pa-patched', // Always present on <html>\n loadingClass: 'pa-loading', // Removed when assets are loaded\n\n // CSS loaded at start of <head> - BLOCKING\n // Use this to hide content and prevent flash of unstyled content\n cssBefore: {\n filename: 'before.css',\n enabled: true,\n },\n\n // CSS loaded at end of <head> - ASYNC with fallback\n // Use this for style overrides\n cssAfter: {\n filename: 'after.css',\n enabled: true,\n timeout: 5000, // ms before falling back to local\n },\n\n // JS loaded at start of <head> - BLOCKING\n // Use this for setup, API interception, globals\n jsBefore: {\n filename: 'before.js',\n enabled: true,\n },\n\n // JS loaded at end of <body> - ASYNC with fallback\n // Use this for DOM manipulation after Rise loads\n // This should remove the loadingClass when done\n jsAfter: {\n filename: 'after.js',\n enabled: true,\n timeout: 5000, // ms before falling back to local\n },\n\n // Folder names for local fallback files within scormcontent/\n localFolders: {\n css: 'css',\n js: 'js',\n },\n\n // Update manifest files for SCORM compliance\n updateManifests: true,\n};\n\nexport default config;\n`;\n}\n","import type { PatchAdamsConfig } from '../config/schema.js';\n\nexport interface CssBeforeOptions {\n remoteUrl: string;\n localPath: string;\n htmlClass: string;\n loadingClass: string;\n}\n\n/**\n * Generate the blocking CSS loader for the \"before\" slot\n * This loads at the start of <head> and blocks rendering until loaded\n *\n * Strategy:\n * 1. First, inline the critical CSS to hide content immediately\n * 2. Then load the remote CSS synchronously (or local fallback)\n */\nexport function generateCssBeforeLoader(options: CssBeforeOptions): string {\n const { remoteUrl, localPath, htmlClass, loadingClass } = options;\n\n return `<!-- === PATCH-ADAMS: CSS BEFORE (blocking) === -->\n<style data-pa=\"css-before-critical\">\n/* Critical: Hide content until ready */\nhtml.${htmlClass}.${loadingClass} body {\n visibility: hidden !important;\n}\n</style>\n<script data-pa=\"css-before-loader\">\n(function() {\n 'use strict';\n var REMOTE_URL = \"${remoteUrl}\";\n var LOCAL_PATH = \"${localPath}\";\n\n function loadCSSSync(url) {\n var link = document.createElement('link');\n link.rel = 'stylesheet';\n link.href = url;\n link.setAttribute('data-pa', 'css-before');\n document.head.appendChild(link);\n return link;\n }\n\n // Try remote first\n var link = loadCSSSync(REMOTE_URL);\n\n link.onerror = function() {\n console.warn('[PA-Patcher] CSS before failed to load from remote, using local fallback');\n document.head.removeChild(link);\n loadCSSSync(LOCAL_PATH);\n };\n\n link.onload = function() {\n console.log('[PA-Patcher] CSS before loaded from remote:', REMOTE_URL);\n };\n})();\n</script>`;\n}\n\n/**\n * Build options from config\n */\nexport function buildCssBeforeOptions(config: PatchAdamsConfig): CssBeforeOptions {\n return {\n remoteUrl: `${config.remoteDomain}/${config.localFolders.css}/${config.cssBefore.filename}`,\n localPath: `${config.localFolders.css}/${config.cssBefore.filename}`,\n htmlClass: config.htmlClass,\n loadingClass: config.loadingClass,\n };\n}\n","import type { PatchAdamsConfig } from '../config/schema.js';\n\nexport interface CssAfterOptions {\n remoteUrl: string;\n localPath: string;\n timeout: number;\n}\n\n/**\n * Generate the async CSS loader for the \"after\" slot\n * This loads at the end of <head> with remote-first, local fallback\n */\nexport function generateCssAfterLoader(options: CssAfterOptions): string {\n const { remoteUrl, localPath, timeout } = options;\n\n return `<!-- === PATCH-ADAMS: CSS AFTER (async with fallback) === -->\n<script data-pa=\"css-after-loader\">\n(function() {\n 'use strict';\n var REMOTE_URL = \"${remoteUrl}\";\n var LOCAL_PATH = \"${localPath}\";\n var TIMEOUT = ${timeout};\n\n function loadCSS(url, onSuccess, onError) {\n var link = document.createElement('link');\n link.rel = 'stylesheet';\n link.href = url;\n link.setAttribute('data-pa', 'css-after');\n\n link.onload = function() {\n if (onSuccess) onSuccess();\n };\n\n link.onerror = function() {\n if (onError) onError();\n };\n\n document.head.appendChild(link);\n return link;\n }\n\n function loadCSSWithFallback() {\n var loaded = false;\n var timeoutId;\n\n // Try remote first\n var remoteLink = loadCSS(\n REMOTE_URL,\n function() {\n if (loaded) return;\n loaded = true;\n clearTimeout(timeoutId);\n console.log('[PA-Patcher] CSS after loaded from remote:', REMOTE_URL);\n },\n function() {\n if (loaded) return;\n loaded = true;\n clearTimeout(timeoutId);\n loadLocalFallback();\n }\n );\n\n // Timeout fallback\n timeoutId = setTimeout(function() {\n if (loaded) return;\n loaded = true;\n console.warn('[PA-Patcher] CSS after timed out, using local fallback');\n if (remoteLink.parentNode) {\n document.head.removeChild(remoteLink);\n }\n loadLocalFallback();\n }, TIMEOUT);\n }\n\n function loadLocalFallback() {\n loadCSS(\n LOCAL_PATH,\n function() {\n console.log('[PA-Patcher] CSS after loaded from local fallback:', LOCAL_PATH);\n },\n function() {\n console.error('[PA-Patcher] CSS after failed to load from both remote and local');\n }\n );\n }\n\n // Execute immediately\n loadCSSWithFallback();\n})();\n</script>`;\n}\n\n/**\n * Build options from config\n */\nexport function buildCssAfterOptions(config: PatchAdamsConfig): CssAfterOptions {\n return {\n remoteUrl: `${config.remoteDomain}/${config.localFolders.css}/${config.cssAfter.filename}`,\n localPath: `${config.localFolders.css}/${config.cssAfter.filename}`,\n timeout: config.cssAfter.timeout,\n };\n}\n","/**\n * LRS Bridge Template for PA-Patcher\n *\n * Generates JavaScript code that bridges Rise courses to an LRS endpoint.\n * Supports direct xAPI statement posting when PlayerIntegration is unavailable.\n * Version 2.0.0 - Full xAPI support with direct LRS communication\n */\n\nexport interface LrsBridgeOptions {\n /** Whether LRS bridge is enabled */\n enabled: boolean;\n /** Track video/audio plays (play, pause, complete) */\n trackMedia: boolean;\n /** Track page/lesson navigation */\n trackNavigation: boolean;\n /** Track quiz/knowledge check answers */\n trackQuizzes: boolean;\n /** Track interactive element clicks (tabs, accordions, etc.) */\n trackInteractions: boolean;\n /** Allow custom xAPI statements via API */\n customStatements: boolean;\n /** Enable debug logging */\n debug: boolean;\n /** LRS endpoint URL for direct xAPI posting - if not provided, auto-detects from Bravais domain */\n lrsEndpoint?: string;\n /** Whether to include credentials (cookies) in LRS requests */\n lrsWithCredentials?: boolean;\n /** Course homepage IRI for xAPI context */\n courseHomepage?: string;\n /** Auto-detect Bravais LRS endpoint from parent frame URL (default: true) */\n autoDetectLrs?: boolean;\n /** Employee API endpoint for user lookup (e.g., https://node.example.com/admin_api/users/employees/) */\n employeeApiEndpoint?: string;\n /** Basic auth credentials for LRS (Base64 encoded \"username:password\") - used when no cookie session exists */\n lrsAuth?: string;\n /** LRS proxy endpoint URL - statements are sent here instead of direct LRS, proxy handles auth server-side */\n lrsProxyEndpoint?: string;\n /** Document GUID for xAPI statement aggregation - baked into package at wrap time */\n documentGuid?: string;\n /** Version GUID for xAPI statement aggregation - unique per export */\n versionGuid?: string;\n /** Original SCORM package filename - baked in at wrap time for LRS searchability */\n packageName?: string;\n}\n\nexport const DEFAULT_LRS_OPTIONS: LrsBridgeOptions = {\n enabled: true,\n trackMedia: true,\n trackNavigation: true,\n trackQuizzes: true,\n trackInteractions: true,\n customStatements: true,\n debug: false,\n lrsEndpoint: '',\n lrsWithCredentials: true,\n courseHomepage: '',\n autoDetectLrs: true,\n employeeApiEndpoint: '',\n lrsAuth: '',\n lrsProxyEndpoint: '',\n documentGuid: '',\n versionGuid: '',\n packageName: '',\n};\n\n/**\n * Generate the LRS bridge JavaScript code\n * This gets embedded into the js-before loader and runs immediately\n */\nexport function generateLrsBridgeCode(options: LrsBridgeOptions): string {\n if (!options.enabled) {\n return `\n // LRS Bridge disabled\n window.pa_patcher.lrs = { enabled: false, initialized: false };\n`;\n }\n\n // Escape strings for embedding in JS\n const lrsEndpoint = options.lrsEndpoint || '';\n const courseHomepage = options.courseHomepage || '';\n const employeeApiEndpoint = options.employeeApiEndpoint || '';\n const lrsAuth = options.lrsAuth || '';\n const lrsProxyEndpoint = options.lrsProxyEndpoint || '';\n const documentGuid = options.documentGuid || '';\n const versionGuid = options.versionGuid || '';\n const packageName = options.packageName || '';\n\n return `\n // ==========================================================================\n // PATCH-ADAMS LRS BRIDGE v2.2.0\n // Full xAPI support with direct LRS communication for Rise courses\n // ALL statements use DOCUMENT as main object for Bravais aggregation\n // Activity type (video, assessment, etc.) stored in object.definition.type\n // Page/lesson info stored in context.extensions for navigation tracking\n // ==========================================================================\n (function() {\n 'use strict';\n\n var DEBUG = ${options.debug};\n // Allow URL-based debug toggle: ?pa_debug=1 or #pa_debug\n try {\n if (window.location.search.indexOf('pa_debug') > -1 || window.location.hash.indexOf('pa_debug') > -1) {\n DEBUG = true;\n }\n } catch (e) {}\n var TRACK_MEDIA = ${options.trackMedia};\n var TRACK_NAVIGATION = ${options.trackNavigation};\n var TRACK_QUIZZES = ${options.trackQuizzes};\n var TRACK_INTERACTIONS = ${options.trackInteractions};\n var CUSTOM_STATEMENTS = ${options.customStatements};\n var LRS_ENDPOINT_CONFIG = '${lrsEndpoint}';\n var LRS_WITH_CREDENTIALS = ${options.lrsWithCredentials ?? true};\n var COURSE_HOMEPAGE = '${courseHomepage}';\n var AUTO_DETECT_LRS = ${options.autoDetectLrs ?? true};\n var EMPLOYEE_API_ENDPOINT = '${employeeApiEndpoint}';\n var LRS_AUTH = '${lrsAuth}';\n var LRS_PROXY_ENDPOINT = '${lrsProxyEndpoint}';\n var DOCUMENT_GUID = '${documentGuid}';\n var VERSION_GUID = '${versionGuid}';\n var PACKAGE_NAME = '${packageName}';\n\n // Bravais LRS endpoint pattern: https://lrs-{tenant}.bravais.com/XAPI/statements\n // Example: core-acme.bravais.com -> lrs-acme.bravais.com\n var LRS_ENDPOINT = ''; // Will be set by auto-detection or config\n\n function log() {\n if (DEBUG && window.console && window.console.log) {\n var args = Array.prototype.slice.call(arguments);\n args.unshift('[PA-LRS]');\n console.log.apply(console, args);\n }\n }\n\n function warn() {\n if (window.console && window.console.warn) {\n var args = Array.prototype.slice.call(arguments);\n args.unshift('[PA-LRS]');\n console.warn.apply(console, args);\n }\n }\n\n // ========================================================================\n // XAPI VERB DEFINITIONS (ADL standard IRIs)\n // ========================================================================\n var VERBS = {\n launched: {\n id: 'http://adlnet.gov/expapi/verbs/launched',\n display: { 'en-US': 'launched' }\n },\n initialized: {\n id: 'http://adlnet.gov/expapi/verbs/initialized',\n display: { 'en-US': 'initialized' }\n },\n viewed: {\n id: 'http://id.tincanapi.com/verb/viewed',\n display: { 'en-US': 'viewed' }\n },\n experienced: {\n id: 'http://adlnet.gov/expapi/verbs/experienced',\n display: { 'en-US': 'experienced' }\n },\n played: {\n id: 'https://w3id.org/xapi/video/verbs/played',\n display: { 'en-US': 'played' }\n },\n paused: {\n id: 'https://w3id.org/xapi/video/verbs/paused',\n display: { 'en-US': 'paused' }\n },\n completed: {\n id: 'http://adlnet.gov/expapi/verbs/completed',\n display: { 'en-US': 'completed' }\n },\n attempted: {\n id: 'http://adlnet.gov/expapi/verbs/attempted',\n display: { 'en-US': 'attempted' }\n },\n answered: {\n id: 'http://adlnet.gov/expapi/verbs/answered',\n display: { 'en-US': 'answered' }\n },\n passed: {\n id: 'http://adlnet.gov/expapi/verbs/passed',\n display: { 'en-US': 'passed' }\n },\n failed: {\n id: 'http://adlnet.gov/expapi/verbs/failed',\n display: { 'en-US': 'failed' }\n },\n interacted: {\n id: 'http://adlnet.gov/expapi/verbs/interacted',\n display: { 'en-US': 'interacted' }\n },\n progressed: {\n id: 'http://adlnet.gov/expapi/verbs/progressed',\n display: { 'en-US': 'progressed' }\n },\n terminated: {\n id: 'http://adlnet.gov/expapi/verbs/terminated',\n display: { 'en-US': 'terminated' }\n },\n // ActivityStrea.ms verbs for Xyleme/Bravais compatibility\n opened: {\n id: 'http://activitystrea.ms/schema/1.0/open',\n display: { 'en-US': 'opened' }\n },\n closed: {\n id: 'http://activitystrea.ms/schema/1.0/close',\n display: { 'en-US': 'closed' }\n },\n exited: {\n id: 'http://activitystrea.ms/schema/1.0/close',\n display: { 'en-US': 'exited' }\n }\n };\n\n // ========================================================================\n // ACTIVITY TYPES\n // ========================================================================\n var ACTIVITY_TYPES = {\n course: 'http://adlnet.gov/expapi/activities/course',\n module: 'http://adlnet.gov/expapi/activities/module',\n lesson: 'http://adlnet.gov/expapi/activities/lesson',\n assessment: 'http://adlnet.gov/expapi/activities/assessment',\n question: 'http://adlnet.gov/expapi/activities/question',\n interaction: 'http://adlnet.gov/expapi/activities/interaction',\n media: 'http://adlnet.gov/expapi/activities/media',\n video: 'https://w3id.org/xapi/video/activity-type/video',\n audio: 'https://w3id.org/xapi/audio/activity-type/audio'\n };\n\n // ========================================================================\n // XYLEME RESOURCE TYPES (for Bravais Analytics)\n // ========================================================================\n var RESOURCE_TYPES = {\n course: 'Course',\n document: 'Course',\n lesson: 'Topic',\n page: 'Topic',\n topic: 'Topic',\n assessment: 'Assessment',\n question: 'Question',\n interaction: 'Topic',\n media: 'Topic',\n video: 'Topic',\n audio: 'Topic'\n };\n\n // Xyleme-specific activity type IRIs\n var XYLEME_ACTIVITY_TYPES = {\n page: 'http://activitystrea.ms/schema/1.0/page',\n document: 'http://xyleme.com/bravais/activities/document',\n assessment: 'http://adlnet.gov/expapi/activities/assessment',\n question: 'http://adlnet.gov/expapi/activities/question'\n };\n\n // ========================================================================\n // LRS NAMESPACE & STATE\n // ========================================================================\n var LRS = window.pa_patcher.lrs = {\n version: '2.2.0',\n enabled: true,\n initialized: false,\n mode: null, // 'playerIntegration', 'directLRS', 'scormOnly'\n integration: null, // Xyleme PlayerIntegration (if available)\n integrationLevel: null,\n scormApi: null,\n scormApiLevel: null,\n scormApiWindow: null,\n scormApiFound: false,\n scormApiType: null,\n lrsEndpoint: LRS_ENDPOINT,\n statementQueue: [], // xAPI statements queued for sending\n eventLog: [], // All events logged (for debugging)\n currentLesson: null,\n courseInfo: null, // Course metadata\n actor: null, // xAPI actor object\n sessionId: null, // Session UUID\n launchTime: null, // ISO timestamp of course launch\n courseAttemptId: null, // Unique ID for this course attempt session (Xyleme format)\n employeeId: null, // Employee ID from employee lookup\n // Statistics\n stats: {\n statementsSent: 0,\n statementsQueued: 0,\n statementsFailed: 0\n }\n };\n\n // Generate UUID for session tracking\n function generateUUID() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n var r = Math.random() * 16 | 0;\n var v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n }\n\n // Clean HTML entities from text — handles both proper (") and broken (quot;) forms\n // Rise strips the & from entities in SCORM interaction IDs, so we need both\n function decodeEntities(str) {\n if (!str || typeof str !== 'string') return str;\n // 1. Replace known broken entities (without &) that Rise leaves behind\n str = str\n .replace(/quot;/g, '\"')\n .replace(/apos;/g, \"'\")\n .replace(/amp;/g, '&')\n .replace(/lt;/g, '<')\n .replace(/gt;/g, '>')\n .replace(/nbsp;/g, ' ');\n // 2. Replace numeric entities like " ' " etc.\n str = str.replace(/&#(x?[0-9a-fA-F]+);/g, function(match, code) {\n var num = code.charAt(0) === 'x' ? parseInt(code.substring(1), 16) : parseInt(code, 10);\n return isNaN(num) ? match : String.fromCharCode(num);\n });\n // 3. Catch any remaining standard &entities; via textarea decode\n try {\n var txt = document.createElement('textarea');\n txt.innerHTML = str;\n str = txt.value;\n } catch (e) {}\n return str;\n }\n\n LRS.sessionId = generateUUID();\n LRS.courseAttemptId = generateUUID(); // Unique per session for Xyleme correlation\n LRS.launchTime = new Date().toISOString();\n log('Generated courseAttemptId:', LRS.courseAttemptId);\n\n // ========================================================================\n // 1. BRAVAIS LRS ENDPOINT AUTO-DETECTION\n // ========================================================================\n\n /**\n * Auto-detect Bravais LRS endpoint from current URL or parent frames\n * Bravais pattern: https://core-{tenant}.bravais.com -> https://lrs-{tenant}.bravais.com/XAPI/statements\n */\n function detectBravaisLrsEndpoint() {\n var urlsToCheck = [];\n\n // Check current window\n urlsToCheck.push(window.location.href);\n\n // Check parent frames\n try {\n var win = window;\n for (var i = 0; i < 10; i++) {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n try {\n urlsToCheck.push(win.location.href);\n } catch (e) {\n // Cross-origin, try document.referrer\n break;\n }\n } else {\n break;\n }\n }\n } catch (e) {}\n\n // Also check referrer\n if (document.referrer) {\n urlsToCheck.push(document.referrer);\n }\n\n log('Checking URLs for Bravais domain:', urlsToCheck);\n\n // Look for Bravais domain pattern\n for (var j = 0; j < urlsToCheck.length; j++) {\n var url = urlsToCheck[j];\n // Match: core-{tenant}.bravais.com or {tenant}.bravais.com\n var match = url.match(/(?:core-)?([a-zA-Z0-9_-]+)\\\\.bravais\\\\.com/);\n if (match) {\n var tenant = match[1];\n var lrsEndpoint = 'https://lrs-' + tenant + '.bravais.com/XAPI/statements';\n log('Detected Bravais tenant:', tenant);\n log('Auto-detected LRS endpoint:', lrsEndpoint);\n return lrsEndpoint;\n }\n }\n\n log('No Bravais domain detected');\n return null;\n }\n\n // ========================================================================\n // 2. SCORM/PLAYERINTEGRATION DETECTION\n // ========================================================================\n\n function findAPIInFrameHierarchy(apiName, maxLevels) {\n maxLevels = maxLevels || 10;\n var win = window;\n var level = 0;\n\n while (level < maxLevels) {\n try {\n if (win[apiName]) {\n log('Found ' + apiName + ' at level ' + level);\n return { api: win[apiName], level: level, window: win };\n }\n } catch (e) {\n log('Cross-origin block at level ' + level);\n }\n\n try {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n level++;\n } else {\n break;\n }\n } catch (e) {\n break;\n }\n }\n\n return null;\n }\n\n function findPlayerIntegration(maxLevels) {\n maxLevels = maxLevels || 10;\n var win = window;\n var level = 0;\n\n while (level < maxLevels) {\n try {\n if (win.PlayerIntegration) {\n log('Found PlayerIntegration at level ' + level);\n return { integration: win.PlayerIntegration, level: level, window: win };\n }\n } catch (e) {}\n\n try {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n level++;\n } else {\n break;\n }\n } catch (e) {\n break;\n }\n }\n\n return null;\n }\n\n function setupBridge() {\n try {\n // 1. Try PlayerIntegration first (Xyleme native mode) - for SCORM APIs\n var integrationResult = findPlayerIntegration(10);\n if (integrationResult) {\n LRS.integration = integrationResult.integration;\n LRS.integrationLevel = integrationResult.level;\n log('Found PlayerIntegration at level', integrationResult.level);\n\n if (LRS.integration.Internal_SCORM_2004_API) {\n window.API_1484_11 = LRS.integration.Internal_SCORM_2004_API;\n LRS.scormApi = window.API_1484_11;\n LRS.scormApiFound = true;\n LRS.scormApiType = '2004';\n }\n if (LRS.integration.Internal_SCORM_12_API) {\n window.API = LRS.integration.Internal_SCORM_12_API;\n if (!LRS.scormApiFound) {\n LRS.scormApi = window.API;\n LRS.scormApiFound = true;\n LRS.scormApiType = '1.2';\n }\n }\n // Don't return early - continue to detect LRS endpoint for direct xAPI\n } else {\n // 2. Search for SCORM APIs directly\n log('No PlayerIntegration found, searching for SCORM APIs...');\n\n var api2004 = findAPIInFrameHierarchy('API_1484_11', 10);\n if (api2004) {\n LRS.scormApi = api2004.api;\n LRS.scormApiLevel = api2004.level;\n LRS.scormApiWindow = api2004.window;\n LRS.scormApiFound = true;\n LRS.scormApiType = '2004';\n log('Found SCORM 2004 API at level ' + api2004.level);\n } else {\n var api12 = findAPIInFrameHierarchy('API', 10);\n if (api12) {\n LRS.scormApi = api12.api;\n LRS.scormApiLevel = api12.level;\n LRS.scormApiWindow = api12.window;\n LRS.scormApiFound = true;\n LRS.scormApiType = '1.2';\n log('Found SCORM 1.2 API at level ' + api12.level);\n }\n }\n }\n\n // 3. ALWAYS try to determine LRS endpoint for direct xAPI statements\n // This is critical for detailed tracking (media, interactions, quizzes)\n // Priority: 1) Configured endpoint, 2) Auto-detected Bravais endpoint\n if (LRS_ENDPOINT_CONFIG && LRS_ENDPOINT_CONFIG.length > 0) {\n LRS_ENDPOINT = LRS_ENDPOINT_CONFIG;\n log('Using configured LRS endpoint:', LRS_ENDPOINT);\n } else if (AUTO_DETECT_LRS) {\n var detectedEndpoint = detectBravaisLrsEndpoint();\n if (detectedEndpoint) {\n LRS_ENDPOINT = detectedEndpoint;\n log('Using auto-detected Bravais LRS endpoint:', LRS_ENDPOINT);\n }\n }\n\n LRS.lrsEndpoint = LRS_ENDPOINT;\n\n // 4. Determine mode based on what we found\n // Prefer directLRS when available for detailed tracking\n if (LRS_ENDPOINT && LRS_ENDPOINT.length > 0) {\n LRS.mode = 'directLRS';\n log('Using direct LRS mode with endpoint:', LRS_ENDPOINT);\n if (LRS.integration) {\n log('PlayerIntegration also available for SCORM state');\n }\n return true;\n } else if (LRS.integration) {\n LRS.mode = 'playerIntegration';\n log('Using PlayerIntegration mode (no direct LRS endpoint)');\n return true;\n } else if (LRS.scormApiFound) {\n LRS.mode = 'scormOnly';\n log('Using SCORM-only mode (no LRS endpoint detected)');\n return true;\n }\n\n warn('No LRS endpoint or SCORM API found');\n return false;\n\n } catch (e) {\n warn('Error setting up bridge:', e);\n return false;\n }\n }\n\n // ========================================================================\n // 3. ACTOR EXTRACTION (multiple sources with fallbacks)\n // ========================================================================\n\n // Store for Bravais session data\n var bravaisSessionData = null;\n var bravaisSessionFetched = false;\n\n /**\n * Parse actor info from URL query parameters\n * Bravais thin packs often pass user info via URL params\n */\n function getActorFromUrlParams() {\n try {\n var params = new URLSearchParams(window.location.search);\n\n // Check various URL parameter names used by Bravais\n var userId = params.get('learner_id') || params.get('learnerId') ||\n params.get('user_id') || params.get('userId') ||\n params.get('actor_id') || params.get('actorId');\n var userName = params.get('learner_name') || params.get('learnerName') ||\n params.get('user_name') || params.get('userName') ||\n params.get('actor_name') || params.get('actorName');\n var userEmail = params.get('learner_email') || params.get('learnerEmail') ||\n params.get('user_email') || params.get('userEmail') ||\n params.get('actor_email') || params.get('actorEmail') ||\n params.get('email');\n var homePage = params.get('homepage') || params.get('homePage') ||\n params.get('tenant') || params.get('tenantId');\n\n if (userId || userEmail) {\n log('Found actor info in URL params');\n var actor = { objectType: 'Agent' };\n\n if (userName) {\n actor.name = userName;\n }\n if (userEmail) {\n actor.mbox = userEmail.indexOf('mailto:') === 0 ? userEmail : 'mailto:' + userEmail;\n }\n if (userId) {\n actor.account = {\n homePage: homePage || window.location.origin,\n name: userId\n };\n }\n return actor;\n }\n\n // Check for base64 encoded actor parameter\n var actorParam = params.get('actor');\n if (actorParam) {\n try {\n var actorJson = atob(actorParam);\n var actor = JSON.parse(actorJson);\n log('Found base64 encoded actor in URL');\n return actor;\n } catch (e) {\n log('Could not decode actor param:', e.message);\n }\n }\n\n } catch (e) {\n log('Error parsing URL params:', e.message);\n }\n return null;\n }\n\n /**\n * Parse actor from Bravais launch data (stored in SCORM launch_data)\n * Bravais stores JSON in cmi.launch_data with user context\n */\n function getActorFromLaunchData() {\n if (!LRS.scormApi) return null;\n\n try {\n var launchData = null;\n if (LRS.scormApiType === '2004') {\n launchData = LRS.scormApi.GetValue('cmi.launch_data');\n } else {\n launchData = LRS.scormApi.LMSGetValue('cmi.launch_data');\n }\n\n if (launchData) {\n // Bravais stores base64 encoded JSON in launch_data\n try {\n var decoded = atob(launchData);\n var data = JSON.parse(decoded);\n log('Parsed launch data:', Object.keys(data));\n\n // Look for actor/user info in launch data\n if (data.actor) {\n log('Found actor in launch data');\n return data.actor;\n }\n\n // Build actor from launch data fields\n if (data.userId || data.userID || data.learnerId) {\n var actor = { objectType: 'Agent' };\n var userId = data.userId || data.userID || data.learnerId;\n var userName = data.userName || data.userFirstName ?\n ((data.userFirstName || '') + ' ' + (data.userLastName || '')).trim() :\n data.learnerName;\n var userEmail = data.userEmail || data.learnerEmail || data.email;\n var homePage = data.xApiTenantId || data.tenantId || data.homePage;\n\n if (userName) actor.name = userName;\n if (userEmail) actor.mbox = 'mailto:' + userEmail;\n if (userId) {\n actor.account = {\n homePage: homePage || window.location.origin,\n name: String(userId)\n };\n }\n log('Built actor from launch data fields');\n return actor;\n }\n } catch (e) {\n // Not base64/JSON, might be plain text\n log('Launch data is not base64 JSON:', e.message);\n }\n }\n } catch (e) {\n log('Error reading launch data:', e.message);\n }\n return null;\n }\n\n /**\n * Try to get user info from Bravais context cookie\n * Cookie name pattern: Bravais-{env}-{region}-Context\n * Cookie value is base64 encoded JSON with user info\n */\n function getActorFromBravaisCookie() {\n try {\n var cookies = document.cookie.split(';');\n for (var i = 0; i < cookies.length; i++) {\n var cookie = cookies[i].trim();\n // Look for Bravais context cookies\n if (cookie.indexOf('Bravais-') === 0 && cookie.indexOf('-Context=') > -1) {\n var cookieName = cookie.split('=')[0];\n var cookieValue = cookie.substring(cookieName.length + 1);\n log('Found Bravais context cookie:', cookieName);\n\n try {\n // Cookie value is base64 encoded JSON\n var decoded = atob(cookieValue);\n var contextData = JSON.parse(decoded);\n log('Decoded Bravais cookie data keys:', Object.keys(contextData));\n\n // Extract user info from context\n if (contextData.userId || contextData.userID || contextData.user) {\n var actor = { objectType: 'Agent' };\n var user = contextData.user || contextData;\n\n var userId = user.userId || user.userID || user.id;\n var userName = user.userName || user.name ||\n ((user.firstName || user.userFirstName || '') + ' ' +\n (user.lastName || user.userLastName || '')).trim();\n var userEmail = user.email || user.userEmail;\n var homePage = contextData.xApiTenantId || contextData.tenantId ||\n contextData.portalUrl || contextData.coreUrl;\n\n if (userName) actor.name = userName;\n if (userEmail) actor.mbox = 'mailto:' + userEmail;\n if (userId) {\n actor.account = {\n homePage: homePage || window.location.origin,\n name: String(userId)\n };\n }\n log('Built actor from Bravais cookie');\n return actor;\n }\n } catch (e) {\n log('Could not decode Bravais cookie:', e.message);\n }\n }\n }\n log('No Bravais context cookie found or not accessible');\n } catch (e) {\n log('Cannot access cookies (cross-origin or blocked)');\n }\n return null;\n }\n\n /**\n * Try to get actor from parent frame's data\n * Bravais thin packs inject sessionData, actor, and PARAMS into the parent page\n */\n function getActorFromParentFrame() {\n try {\n var win = window;\n for (var level = 0; level < 10; level++) {\n try {\n // Check for Bravais PARAMS object (contains actor directly)\n if (win.PARAMS && win.PARAMS.actor) {\n log('Found PARAMS.actor at level', level);\n var actor = win.PARAMS.actor;\n // Ensure it's properly formatted\n if (actor.objectType !== 'Agent') {\n actor.objectType = 'Agent';\n }\n return actor;\n }\n\n // Check for Bravais sessionData (server-injected user info)\n if (win.sessionData && (win.sessionData.userId || win.sessionData.userID)) {\n log('Found sessionData at level', level);\n var sd = win.sessionData;\n var actor = { objectType: 'Agent' };\n\n // Name from firstName + lastName\n var firstName = sd.userFirstName || '';\n var lastName = sd.userLastName || '';\n if (firstName || lastName) {\n actor.name = (firstName + ' ' + lastName).trim();\n }\n\n // Account with homePage and userId\n var homePage = sd.xApiTenantId || sd.webPortalUrl || sd.coreUrl;\n var userId = sd.userId || sd.userID;\n if (homePage && userId) {\n actor.account = {\n homePage: homePage,\n name: String(userId)\n };\n }\n\n // mbox from email\n if (sd.userEmail) {\n actor.mbox = 'mailto:' + sd.userEmail;\n }\n\n log('Built actor from sessionData:', actor);\n return actor;\n }\n\n // Check for standalone actor variable (Bravais sets this from sessionData)\n if (win.actor && (win.actor.account || win.actor.mbox)) {\n log('Found actor variable at level', level);\n var actor = win.actor;\n if (actor.objectType !== 'Agent') {\n actor.objectType = 'Agent';\n }\n return actor;\n }\n\n // Check for Bravais player data\n if (win.BRAVAIS_LEARNER) {\n log('Found BRAVAIS_LEARNER at level', level);\n var learner = win.BRAVAIS_LEARNER;\n var actor = { objectType: 'Agent' };\n if (learner.name) actor.name = learner.name;\n if (learner.email) actor.mbox = 'mailto:' + learner.email;\n if (learner.id) {\n actor.account = {\n homePage: learner.homePage || window.location.origin,\n name: String(learner.id)\n };\n }\n return actor;\n }\n\n // Check for xAPI actor\n if (win.xAPILearner || win.XAPI_ACTOR) {\n log('Found xAPI actor at level', level);\n return win.xAPILearner || win.XAPI_ACTOR;\n }\n\n // Check for PA_ACTOR (set by patch-adams)\n if (win.PA_ACTOR) {\n log('Found PA_ACTOR at level', level);\n return win.PA_ACTOR;\n }\n\n // Check for generic learner data\n if (win.learnerData || win.userData) {\n var data = win.learnerData || win.userData;\n log('Found learner/user data at level', level);\n if (data.id || data.userId || data.email) {\n var actor = { objectType: 'Agent' };\n if (data.name) actor.name = data.name;\n if (data.email) actor.mbox = 'mailto:' + data.email;\n if (data.id || data.userId) {\n actor.account = {\n homePage: data.homePage || window.location.origin,\n name: String(data.id || data.userId)\n };\n }\n return actor;\n }\n }\n\n } catch (e) {\n // Cross-origin blocked at this level\n log('Cross-origin block at level', level, '- trying session API instead');\n break;\n }\n\n if (win.parent && win.parent !== win) {\n win = win.parent;\n } else {\n break;\n }\n }\n } catch (e) {\n log('Error accessing parent frames:', e.message);\n }\n return null;\n }\n\n /**\n * Fetch user info from Bravais session API\n * Returns session data or null if not available\n */\n function fetchBravaisSessionData(callback) {\n if (bravaisSessionFetched) {\n callback(bravaisSessionData);\n return;\n }\n\n // Detect Bravais core URL from parent frames or current URL\n var coreUrl = null;\n var urlsToCheck = [window.location.href, document.referrer];\n\n try {\n var win = window;\n for (var i = 0; i < 10; i++) {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n try {\n urlsToCheck.push(win.location.href);\n } catch (e) { break; }\n } else { break; }\n }\n } catch (e) {}\n\n log('URLs to check for core URL:', urlsToCheck);\n\n for (var j = 0; j < urlsToCheck.length; j++) {\n var match = urlsToCheck[j].match(/(https?:\\\\/\\\\/core-[a-zA-Z0-9_-]+\\\\.bravais\\\\.com)/);\n if (match) {\n coreUrl = match[1];\n break;\n }\n }\n\n if (!coreUrl) {\n log('No Bravais core URL detected for session fetch');\n bravaisSessionFetched = true;\n callback(null);\n return;\n }\n\n log('Fetching session data from:', coreUrl + '/api/shared/sessionData');\n\n var xhr = new XMLHttpRequest();\n xhr.open('GET', coreUrl + '/api/shared/sessionData', true);\n xhr.withCredentials = true; // Send cookies for auth\n xhr.setRequestHeader('Accept', 'application/json');\n\n xhr.onreadystatechange = function() {\n if (xhr.readyState === 4) {\n bravaisSessionFetched = true;\n log('Session API response status:', xhr.status);\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n bravaisSessionData = JSON.parse(xhr.responseText);\n log('Bravais session data received:', Object.keys(bravaisSessionData));\n callback(bravaisSessionData);\n } catch (e) {\n warn('Error parsing session data:', e);\n callback(null);\n }\n } else {\n log('Session data fetch failed (CORS or auth). Status:', xhr.status);\n callback(null);\n }\n }\n };\n\n xhr.onerror = function() {\n bravaisSessionFetched = true;\n log('Network error fetching session data (CORS blocked)');\n callback(null);\n };\n\n try {\n xhr.send();\n } catch (e) {\n bravaisSessionFetched = true;\n warn('Error sending session request:', e);\n callback(null);\n }\n }\n\n /**\n * Build actor from Bravais session data\n */\n function buildActorFromBravaisSession(sessionData) {\n if (!sessionData) return null;\n\n var actor = {\n objectType: 'Agent'\n };\n\n // Name from first + last name\n var firstName = sessionData.userFirstName || '';\n var lastName = sessionData.userLastName || '';\n if (firstName || lastName) {\n actor.name = (firstName + ' ' + lastName).trim();\n }\n\n // Account with homePage and user ID\n var homePage = sessionData.xApiTenantId || sessionData.webPortalUrl || sessionData.coreUrl;\n if (homePage && sessionData.userID) {\n actor.account = {\n homePage: homePage,\n name: String(sessionData.userID)\n };\n }\n\n // mbox from email (preferred by Bravais LRS)\n if (sessionData.userEmail) {\n actor.mbox = 'mailto:' + sessionData.userEmail;\n }\n\n return actor;\n }\n\n // Store for employee lookup data\n var employeeLookupData = null;\n var employeeLookupFetched = false;\n\n /**\n * Fetch user email from employee API using employee ID\n * Endpoint configured via EMPLOYEE_API_ENDPOINT (e.g., https://node.example.com/admin_api/users/employees/)\n */\n function fetchEmployeeEmail(employeeId, callback) {\n if (!employeeId) {\n callback(null);\n return;\n }\n\n // Skip if no employee API endpoint configured\n if (!EMPLOYEE_API_ENDPOINT) {\n log('Employee API endpoint not configured, skipping lookup');\n callback(null);\n return;\n }\n\n // In-memory cache check\n if (employeeLookupFetched && employeeLookupData) {\n callback(employeeLookupData);\n return;\n }\n\n // localStorage cache check (persists across page loads / sessions)\n var cacheKey = 'pa_employee_' + employeeId;\n try {\n var cached = localStorage.getItem(cacheKey);\n if (cached) {\n var parsed = JSON.parse(cached);\n // TTL: 7 days = 604800000 ms; also invalidate if missing bravaisUserId (old cache)\n if (parsed.timestamp && (Date.now() - parsed.timestamp) < 604800000 && parsed.bravaisUserId) {\n log('Employee data from localStorage cache');\n employeeLookupData = parsed;\n employeeLookupFetched = true;\n callback(parsed);\n return;\n }\n localStorage.removeItem(cacheKey);\n }\n } catch (e) {\n // localStorage unavailable (private browsing, iframe restrictions)\n }\n\n // Build URL: endpoint should end with ? or include query param structure\n var apiUrl = EMPLOYEE_API_ENDPOINT + (EMPLOYEE_API_ENDPOINT.indexOf('?') >= 0 ? '&' : '?') + 'q=' + encodeURIComponent(employeeId);\n log('Fetching employee data from:', apiUrl);\n\n var xhr = new XMLHttpRequest();\n xhr.open('GET', apiUrl, true);\n xhr.withCredentials = true;\n xhr.setRequestHeader('Accept', 'application/json');\n\n xhr.onreadystatechange = function() {\n if (xhr.readyState === 4) {\n employeeLookupFetched = true;\n log('Employee API response status:', xhr.status);\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n var data = JSON.parse(xhr.responseText);\n log('Employee data received:', data);\n\n // Response could be array or single object\n var employee = Array.isArray(data) ? data[0] : data;\n if (employee) {\n // API returns: business_email, fname, lname, emp_id\n employeeLookupData = {\n email: employee.business_email || employee.email || employee.Email || employee.userEmail,\n name: employee.name || employee.displayName || employee.fullName ||\n ((employee.fname || employee.firstName || '') + ' ' + (employee.lname || employee.lastName || '')).trim(),\n firstName: employee.fname || employee.firstName || employee.first_name,\n lastName: employee.lname || employee.lastName || employee.last_name,\n employeeId: employee.emp_id || employee.employeeId || employee.employee_id || employeeId,\n bravaisUserId: employee.bravaisUserId || null,\n tenantUrl: employee.tenantUrl || null\n };\n log('Parsed employee data:', employeeLookupData);\n // Persist to localStorage for cross-session caching\n try {\n var cacheData = {};\n for (var k in employeeLookupData) { if (employeeLookupData.hasOwnProperty(k)) cacheData[k] = employeeLookupData[k]; }\n cacheData.timestamp = Date.now();\n localStorage.setItem(cacheKey, JSON.stringify(cacheData));\n log('Employee data cached in localStorage');\n } catch (e) { /* localStorage unavailable */ }\n callback(employeeLookupData);\n } else {\n callback(null);\n }\n } catch (e) {\n warn('Error parsing employee data:', e);\n callback(null);\n }\n } else {\n log('Employee lookup failed. Status:', xhr.status);\n callback(null);\n }\n }\n };\n\n xhr.onerror = function() {\n employeeLookupFetched = true;\n log('Network error fetching employee data');\n callback(null);\n };\n\n try {\n xhr.send();\n } catch (e) {\n employeeLookupFetched = true;\n warn('Error sending employee request:', e);\n callback(null);\n }\n }\n\n /**\n * Enhance actor with email from employee lookup\n */\n function enhanceActorWithEmail(actor, callback) {\n if (!actor) {\n callback(actor);\n return;\n }\n\n // If actor already has email, no need to lookup\n if (actor.mbox && actor.mbox !== 'mailto:' && actor.mbox !== 'mailto:unknown') {\n callback(actor);\n return;\n }\n\n // Get employee ID from actor account name (SCORM learner_id is often employee ID)\n var employeeId = actor.account ? actor.account.name : null;\n if (!employeeId || employeeId === 'unknown' || employeeId === 'preview_user') {\n callback(actor);\n return;\n }\n\n log('Looking up email for employee ID:', employeeId);\n\n fetchEmployeeEmail(employeeId, function(employeeData) {\n console.info('[PA-LRS] Employee lookup result for ' + employeeId + ': ' +\n (employeeData ? 'name=' + employeeData.name + ', email=' + employeeData.email +\n ', bravaisUserId=' + employeeData.bravaisUserId + ', tenantUrl=' + employeeData.tenantUrl\n : 'NO DATA'));\n if (employeeData && employeeData.email) {\n actor.mbox = 'mailto:' + employeeData.email;\n log('Enhanced actor with email:', employeeData.email);\n\n // Also update name if we got a better one\n if (employeeData.name && (!actor.name || actor.name === 'Unknown Learner')) {\n actor.name = employeeData.name;\n }\n\n // Update account with Bravais user ID and tenant URL if available\n if (employeeData.bravaisUserId) {\n var originalAccountName = actor.account ? actor.account.name : null;\n actor.account = {\n name: employeeData.bravaisUserId,\n homePage: employeeData.tenantUrl || (actor.account && actor.account.homePage) || window.location.origin\n };\n log('Updated actor account: bravaisUserId=' + employeeData.bravaisUserId + ', homePage=' + actor.account.homePage + ' (was: ' + originalAccountName + ')');\n }\n\n // Expose employee ID on LRS object for plugins (e.g. feedback)\n if (employeeData.employeeId) {\n LRS.employeeId = employeeData.employeeId;\n }\n }\n callback(actor);\n });\n }\n\n function extractActor() {\n var defaultActor = {\n objectType: 'Agent',\n name: 'Unknown Learner',\n account: {\n homePage: COURSE_HOMEPAGE || window.location.origin,\n name: 'unknown'\n }\n };\n\n var actor = null;\n\n log('Extracting actor from available sources...');\n\n // Priority 1: URL parameters (most reliable for thin packs)\n actor = getActorFromUrlParams();\n if (actor && isValidActor(actor)) {\n log('Actor source: URL parameters');\n LRS.actor = actor;\n return actor;\n }\n\n // Priority 2: Parent frame data\n actor = getActorFromParentFrame();\n if (actor && isValidActor(actor)) {\n log('Actor source: Parent frame');\n LRS.actor = actor;\n return actor;\n }\n\n // Priority 3: Bravais context cookie (if accessible)\n actor = getActorFromBravaisCookie();\n if (actor && isValidActor(actor)) {\n log('Actor source: Bravais cookie');\n LRS.actor = actor;\n return actor;\n }\n\n // Priority 4: SCORM launch data (may contain user info)\n actor = getActorFromLaunchData();\n if (actor && isValidActor(actor)) {\n log('Actor source: SCORM launch data');\n LRS.actor = actor;\n return actor;\n }\n\n // Priority 5: Standard SCORM learner fields\n if (LRS.scormApi) {\n try {\n var scormLearnerId = null;\n var scormLearnerName = null;\n\n if (LRS.scormApiType === '2004') {\n scormLearnerId = LRS.scormApi.GetValue('cmi.learner_id');\n scormLearnerName = LRS.scormApi.GetValue('cmi.learner_name');\n } else {\n scormLearnerId = LRS.scormApi.LMSGetValue('cmi.core.student_id');\n scormLearnerName = LRS.scormApi.LMSGetValue('cmi.core.student_name');\n }\n\n log('SCORM learner_id:', scormLearnerId);\n log('SCORM learner_name:', scormLearnerName);\n\n // Check if we have valid SCORM data (not preview/unknown)\n var isValidScormId = scormLearnerId &&\n scormLearnerId !== 'preview_user' &&\n scormLearnerId !== 'unknown' &&\n scormLearnerId !== '';\n\n if (isValidScormId) {\n actor = {\n objectType: 'Agent',\n account: {\n homePage: COURSE_HOMEPAGE || window.location.origin,\n name: scormLearnerId\n }\n };\n // Don't use SCORM student_name - it's unreliable (\"Last, First\" format varies by LMS).\n // The employee API lookup will provide the correct name and email.\n log('Actor source: SCORM API (id only, name from employee lookup)');\n LRS.actor = actor;\n return actor;\n }\n\n log('SCORM learner ID is preview/unknown, continuing search...');\n } catch (e) {\n warn('Error extracting actor from SCORM:', e);\n }\n }\n\n // Priority 6: Already-fetched Bravais session data\n if (bravaisSessionData) {\n var bravaisActor = buildActorFromBravaisSession(bravaisSessionData);\n if (bravaisActor && isValidActor(bravaisActor)) {\n log('Actor source: Bravais session (cached)');\n LRS.actor = bravaisActor;\n return bravaisActor;\n }\n }\n\n log('No valid actor found, using default (Unknown Learner)');\n LRS.actor = defaultActor;\n return defaultActor;\n }\n\n /**\n * Check if an actor has valid identifying information\n */\n function isValidActor(actor) {\n if (!actor) return false;\n\n // Must have at least one valid identifier\n var hasValidMbox = actor.mbox && actor.mbox !== 'mailto:' && actor.mbox !== 'mailto:unknown';\n var hasValidAccount = actor.account && actor.account.name &&\n actor.account.name !== 'unknown' &&\n actor.account.name !== 'preview_user' &&\n actor.account.name !== '';\n\n return hasValidMbox || hasValidAccount;\n }\n\n /**\n * Async version that fetches Bravais session if needed\n * Also re-checks all sources in case they became available\n * Finally enhances actor with email from employee lookup if needed\n */\n function extractActorAsync(callback) {\n // Helper to finalize actor with email lookup\n function finalizeActor(actor) {\n // Try to enhance actor with email if missing\n enhanceActorWithEmail(actor, function(enhancedActor) {\n LRS.actor = enhancedActor;\n callback(enhancedActor);\n });\n }\n\n // If we already have a valid actor with email, use it\n if (LRS.actor && isValidActor(LRS.actor) && LRS.actor.mbox && LRS.actor.mbox !== 'mailto:' && LRS.actor.mbox !== 'mailto:unknown') {\n log('Using existing valid actor with email');\n callback(LRS.actor);\n return;\n }\n\n // If we have a valid actor but no email, try to enhance it\n if (LRS.actor && isValidActor(LRS.actor)) {\n log('Valid actor found, checking for email enhancement');\n finalizeActor(LRS.actor);\n return;\n }\n\n // Re-check sync sources first (they might have loaded since init)\n var syncActor = extractActor();\n if (isValidActor(syncActor)) {\n log('Sync actor found, enhancing with email');\n finalizeActor(syncActor);\n return;\n }\n\n // Try Bravais session API as last resort\n fetchBravaisSessionData(function(sessionData) {\n if (sessionData) {\n var bravaisActor = buildActorFromBravaisSession(sessionData);\n if (isValidActor(bravaisActor)) {\n log('Updated actor from Bravais session API:', bravaisActor);\n finalizeActor(bravaisActor);\n return;\n }\n }\n\n // Fall back to whatever we have, still try email enhancement\n log('Async extraction complete, enhancing:', LRS.actor);\n finalizeActor(LRS.actor);\n });\n }\n\n // ========================================================================\n // 4. COURSE INFO EXTRACTION (Xyleme/Bravais format)\n // ========================================================================\n\n /**\n * Extract Bravais shared link token from URL\n * Pattern: /api/shared/{token}/files/...\n */\n function extractSharedLinkToken() {\n var urlsToCheck = [window.location.href];\n\n try {\n var win = window;\n for (var i = 0; i < 5; i++) {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n try {\n urlsToCheck.push(win.location.href);\n } catch (e) { break; }\n } else { break; }\n }\n } catch (e) {}\n\n for (var j = 0; j < urlsToCheck.length; j++) {\n var url = urlsToCheck[j];\n // Match: /api/shared/{token}/files/\n var match = url.match(/\\\\/api\\\\/shared\\\\/([a-zA-Z0-9_-]+)\\\\/files/);\n if (match) {\n log('Extracted shared link token:', match[1]);\n return match[1];\n }\n }\n return null;\n }\n\n // Store for document API data\n var documentApiData = null;\n var documentApiFetched = false;\n var sharedLinkApiData = null;\n\n /**\n * Detect Bravais core URL from current page context\n */\n function detectCoreUrl() {\n var urlsToCheck = [window.location.href, document.referrer];\n\n try {\n var win = window;\n for (var i = 0; i < 10; i++) {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n try {\n urlsToCheck.push(win.location.href);\n } catch (e) { break; }\n } else { break; }\n }\n } catch (e) {}\n\n for (var j = 0; j < urlsToCheck.length; j++) {\n var match = urlsToCheck[j].match(/(https?:\\\\/\\\\/core-[a-zA-Z0-9_-]+\\\\.bravais\\\\.com)/);\n if (match) {\n return match[1];\n }\n }\n return null;\n }\n\n /**\n * Fetch shared link data from Bravais API to get the real document ID\n * Endpoint: /api/v3/sharedLinks/token/{token}\n * Returns: { documentId, documentName, format, ... }\n */\n function fetchSharedLinkData(token, callback) {\n if (!token) {\n callback(null);\n return;\n }\n\n var coreUrl = detectCoreUrl();\n if (!coreUrl) {\n log('No Bravais core URL detected for shared link fetch');\n callback(null);\n return;\n }\n\n var apiUrl = coreUrl + '/api/v3/sharedLinks/token/' + token;\n log('Fetching shared link data from:', apiUrl);\n\n var xhr = new XMLHttpRequest();\n xhr.open('GET', apiUrl, true);\n xhr.withCredentials = true;\n xhr.setRequestHeader('Accept', 'application/json');\n\n xhr.onreadystatechange = function() {\n if (xhr.readyState === 4) {\n log('Shared link API response status:', xhr.status);\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n sharedLinkApiData = JSON.parse(xhr.responseText);\n log('Shared link data received:', sharedLinkApiData);\n if (sharedLinkApiData.documentId) {\n log('Real document ID from shared link:', sharedLinkApiData.documentId);\n }\n callback(sharedLinkApiData);\n } catch (e) {\n warn('Error parsing shared link data:', e);\n callback(null);\n }\n } else {\n // 401/403 is expected - API requires auth that may not be available from iframe\n if (xhr.status === 401 || xhr.status === 403) {\n log('Shared link API requires auth (expected), will use fallback');\n } else {\n log('Shared link fetch failed. Status:', xhr.status);\n }\n callback(null);\n }\n }\n };\n\n xhr.onerror = function() {\n log('Network error fetching shared link data');\n callback(null);\n };\n\n try {\n xhr.send();\n } catch (e) {\n warn('Error sending shared link request:', e);\n callback(null);\n }\n }\n\n /**\n * Fetch document metadata from Bravais shared API to get GUIDs\n * PRIMARY endpoint: /api/shared/{token}/documents (does NOT require auth)\n * FALLBACK endpoint: /api/v3/documents/{documentId} (requires auth)\n */\n function fetchDocumentMetadata(documentIdOrToken, callback) {\n if (documentApiFetched) {\n callback(documentApiData);\n return;\n }\n\n var coreUrl = detectCoreUrl();\n if (!coreUrl) {\n log('No Bravais core URL detected for document metadata fetch');\n documentApiFetched = true;\n callback(null);\n return;\n }\n\n // Determine which endpoint to use\n // If we have a shared link token, use /api/shared/{token}/documents (no auth required)\n // This is the same endpoint Xyleme Cloud Player uses\n var sharedToken = LRS.courseInfo ? LRS.courseInfo.sharedLinkToken : null;\n var apiUrl;\n\n if (sharedToken) {\n apiUrl = coreUrl + '/api/shared/' + sharedToken + '/documents';\n log('Fetching document via shared API (no auth required):', apiUrl);\n } else if (documentIdOrToken) {\n apiUrl = coreUrl + '/api/v3/documents/' + documentIdOrToken;\n log('Fetching document via v3 API (requires auth):', apiUrl);\n } else {\n log('No shared token or document ID available for metadata fetch');\n documentApiFetched = true;\n callback(null);\n return;\n }\n\n var xhr = new XMLHttpRequest();\n xhr.open('GET', apiUrl, true);\n xhr.withCredentials = true;\n xhr.setRequestHeader('Accept', 'application/json');\n\n xhr.onreadystatechange = function() {\n if (xhr.readyState === 4) {\n documentApiFetched = true;\n log('Document API response status:', xhr.status);\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n documentApiData = JSON.parse(xhr.responseText);\n log('Document metadata received:', documentApiData);\n // Validate that we got the required fields\n if (documentApiData.guid) {\n log('Document GUID found:', documentApiData.guid);\n } else {\n warn('Document API response missing guid field!', documentApiData);\n }\n if (documentApiData.latestVersion && documentApiData.latestVersion.guid) {\n log('Version GUID found:', documentApiData.latestVersion.guid);\n } else {\n warn('Document API response missing latestVersion.guid field!', documentApiData);\n }\n callback(documentApiData);\n } catch (e) {\n warn('Error parsing document metadata:', e);\n callback(null);\n }\n } else {\n // 401/403 is expected - API requires auth that may not be available from iframe\n if (xhr.status === 401 || xhr.status === 403) {\n log('Document API requires auth (expected), will use fallback');\n } else {\n log('Document metadata fetch failed. Status:', xhr.status);\n }\n callback(null);\n }\n }\n };\n\n xhr.onerror = function() {\n documentApiFetched = true;\n log('Network error fetching document metadata');\n callback(null);\n };\n\n try {\n xhr.send();\n } catch (e) {\n documentApiFetched = true;\n warn('Error sending document request:', e);\n callback(null);\n }\n }\n\n /**\n * Fetch document name from our authenticated server (cdsImporter proxy).\n * Derives server base URL from LRS_PROXY_ENDPOINT.\n * Falls back gracefully if CORS blocks or server is unavailable.\n * This is the final fallback when Bravais API and PARAMS are not available.\n */\n function fetchDocumentNameFromServer(documentId, callback) {\n if (!LRS_PROXY_ENDPOINT || !documentId) {\n callback(null);\n return;\n }\n\n // Derive server base from LRS proxy endpoint\n // e.g., https://api.example.com/create/statement → https://api.example.com/create/\n var serverBase = LRS_PROXY_ENDPOINT.replace(/\\\\/statement\\\\/?$/, '/');\n if (!serverBase || serverBase === LRS_PROXY_ENDPOINT) {\n log('Could not derive server base URL from LRS_PROXY_ENDPOINT');\n callback(null);\n return;\n }\n\n var apiUrl = serverBase + 'cdsImporter/api/documents/' + documentId;\n log('Fetching document name from server:', apiUrl);\n\n var xhr = new XMLHttpRequest();\n xhr.open('GET', apiUrl, true);\n xhr.withCredentials = true;\n xhr.setRequestHeader('Accept', 'application/json');\n xhr.timeout = 5000;\n\n xhr.onreadystatechange = function() {\n if (xhr.readyState === 4) {\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n var data = JSON.parse(xhr.responseText);\n if (data.name) {\n log('Document name from server:', data.name);\n callback(data.name);\n } else {\n callback(null);\n }\n } catch (e) {\n callback(null);\n }\n } else {\n log('Server document lookup returned:', xhr.status);\n callback(null);\n }\n }\n };\n\n xhr.onerror = function() {\n log('Server document lookup network error (CORS or connectivity)');\n callback(null);\n };\n xhr.ontimeout = function() {\n log('Server document lookup timed out');\n callback(null);\n };\n\n try { xhr.send(); } catch (e) { callback(null); }\n }\n\n /**\n * Update course info with data from document API\n */\n function updateCourseInfoFromApi(docData) {\n if (!docData || !LRS.courseInfo) return;\n\n // Document GUID — Bravais API GUID ALWAYS overrides baked-in GUID.\n // The baked-in GUID is a random UUID from wrap time. The Bravais GUID is the\n // canonical document identifier that Analytics uses to link statements to documents.\n // Without this override, our statements have a different object.id than cloudplayer\n // statements, so Analytics treats them as unrelated to the document.\n if (docData.guid) {\n if (LRS.courseInfo.guid && LRS.courseInfo.guid !== docData.guid) {\n log('Overriding baked-in GUID with Bravais GUID:', LRS.courseInfo.guid, '->', docData.guid);\n }\n LRS.courseInfo.guid = docData.guid;\n LRS.courseInfo.id = 'http://xyleme.com/bravais/document/' + docData.guid;\n log('Document GUID from API:', docData.guid);\n }\n\n // Version GUID — same principle: Bravais version GUID must override baked-in\n if (docData.latestVersion && docData.latestVersion.guid) {\n if (LRS.courseInfo.versionGuid && LRS.courseInfo.versionGuid !== docData.latestVersion.guid) {\n log('Overriding baked-in version GUID:', LRS.courseInfo.versionGuid, '->', docData.latestVersion.guid);\n }\n LRS.courseInfo.versionGuid = docData.latestVersion.guid;\n log('Version GUID from API:', docData.latestVersion.guid);\n }\n\n // Other metadata\n if (docData.name && LRS.courseInfo.title === 'Rise Course') {\n LRS.courseInfo.title = docData.name;\n }\n if (docData.format) {\n LRS.courseInfo.format = docData.format;\n }\n if (docData.resourceType) {\n LRS.courseInfo.resourceType = docData.resourceType;\n }\n\n // Update packageName from API document name (matches Bravais Analytics object name)\n // Only set if not already baked in at wrap time\n if (docData.name && !LRS.courseInfo.packageName) {\n LRS.courseInfo.packageName = docData.name;\n log('Updated packageName from API:', docData.name);\n }\n\n // Update documentId from API if we didn't have it\n if (docData.id && !LRS.courseInfo.documentId) {\n LRS.courseInfo.documentId = String(docData.id);\n log('Updated documentId from API:', docData.id);\n }\n\n log('Course info updated from API:', LRS.courseInfo);\n }\n\n /**\n * Extract Bravais document ID from URL\n * Pattern: /api/shared/.../files/{documentId}/scormcontent/\n */\n function extractBravaisDocumentId() {\n var urlsToCheck = [window.location.href];\n\n try {\n var win = window;\n for (var i = 0; i < 5; i++) {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n try {\n urlsToCheck.push(win.location.href);\n } catch (e) { break; }\n } else { break; }\n }\n } catch (e) {}\n\n for (var j = 0; j < urlsToCheck.length; j++) {\n var url = urlsToCheck[j];\n // Match: /files/{documentId}/scormcontent/ or /files/{documentId}/\n var match = url.match(/\\\\/files\\\\/([0-9]+)(?:\\\\/|$)/);\n if (match) {\n log('Extracted Bravais document ID:', match[1]);\n return match[1];\n }\n }\n return null;\n }\n\n /**\n * Extract course metadata from SCORM launch data\n * Bravais stores course info in base64 JSON in cmi.launch_data\n */\n function extractCourseFromLaunchData() {\n if (!LRS.scormApi) return null;\n\n try {\n var launchData = null;\n if (LRS.scormApiType === '2004') {\n launchData = LRS.scormApi.GetValue('cmi.launch_data');\n } else {\n launchData = LRS.scormApi.LMSGetValue('cmi.launch_data');\n }\n\n if (launchData) {\n try {\n var decoded = atob(launchData);\n var data = JSON.parse(decoded);\n log('Launch data for course info:', Object.keys(data));\n log('Launch data contents:', JSON.stringify(data));\n\n return {\n // Document/Course GUIDs\n guid: data.guid || data.courseGuid || data.documentGuid,\n versionGuid: data.versionGuid || data.courseVersionGuid || data.documentVersionGuid,\n // Numeric IDs\n documentId: data.documentId || data.courseId || data.cdsId,\n versionId: data.versionId || data.courseVersionId,\n // Metadata\n title: data.title || data.courseTitle || data.documentTitle || data.name,\n description: data.description || data.courseDescription,\n format: data.format || 'SCORM by Rise',\n resourceType: data.resourceType || 'Course',\n language: data.language || '',\n version: data.version || 1,\n cpv: data.cpv,\n // Shared link info\n sharedLinkName: data.sharedLinkName,\n sharedLinkToken: data.sharedLinkToken\n };\n } catch (e) {\n log('Launch data not JSON:', e.message);\n }\n }\n } catch (e) {\n log('Error reading launch data for course:', e.message);\n }\n return null;\n }\n\n /**\n * Extract course title from getCourseTitle() if it exists (from preloadIntegrity.js)\n */\n function extractCourseTitleFromPreload() {\n try {\n var win = window;\n for (var level = 0; level < 10; level++) {\n try {\n if (typeof win.getCourseTitle === 'function') {\n var title = win.getCourseTitle();\n if (title && title !== 'Rise Course') {\n log('Found course title from getCourseTitle() at level', level, ':', title);\n return title;\n }\n }\n } catch (e) {}\n\n if (win.parent && win.parent !== win) {\n win = win.parent;\n } else {\n break;\n }\n }\n } catch (e) {}\n return null;\n }\n\n /**\n * Try to extract document data from Xyleme Cloud Player's internal state\n * The Cloud Player stores this after fetching /api/shared/{token}/documents\n */\n function extractFromXylemeCloudPlayer() {\n try {\n var win = window;\n for (var level = 0; level < 10; level++) {\n try {\n // Check for Xyleme Cloud Player's document data in various locations\n // The player stores data in multiple places depending on version\n\n // Check for CdsDataService data\n if (win._cdsDataService && win._cdsDataService.documentData) {\n var docData = win._cdsDataService.documentData;\n log('Found document data in _cdsDataService at level', level);\n return extractGuidsFromDocumentData(docData);\n }\n\n // Check for window.documentData\n if (win.documentData && win.documentData.guid) {\n log('Found document data in window.documentData at level', level);\n return extractGuidsFromDocumentData(win.documentData);\n }\n\n // Check for PlayerIntegration's internal data\n if (win.playerIntegration && win.playerIntegration._documentData) {\n log('Found document data in playerIntegration at level', level);\n return extractGuidsFromDocumentData(win.playerIntegration._documentData);\n }\n\n // Check for __xyleme_data\n if (win.__xyleme_data && win.__xyleme_data.document) {\n log('Found document data in __xyleme_data at level', level);\n return extractGuidsFromDocumentData(win.__xyleme_data.document);\n }\n\n // Check for documentVersionData which has the versionGuid\n if (win.documentVersionData) {\n log('Found documentVersionData at level', level);\n return {\n versionGuid: win.documentVersionData.guid || win.documentVersionData.versionGuid,\n documentId: win.documentVersionData.documentId\n };\n }\n\n } catch (e) {}\n\n if (win.parent && win.parent !== win) {\n win = win.parent;\n } else {\n break;\n }\n }\n } catch (e) {}\n return null;\n }\n\n function extractGuidsFromDocumentData(data) {\n if (!data) return null;\n\n var result = {};\n\n // Document GUID\n if (data.guid) result.guid = data.guid;\n if (data.documentGuid) result.guid = data.documentGuid;\n\n // Version GUID - usually in latestVersion or currentVersion\n if (data.latestVersion && data.latestVersion.guid) {\n result.versionGuid = data.latestVersion.guid;\n }\n if (data.currentVersion && data.currentVersion.guid) {\n result.versionGuid = data.currentVersion.guid;\n }\n if (data.versionGuid) result.versionGuid = data.versionGuid;\n\n // Numeric IDs\n if (data.id) result.documentId = data.id;\n if (data.documentId) result.documentId = data.documentId;\n if (data.cdsId) result.documentId = data.cdsId;\n\n // Title\n if (data.name) result.title = data.name;\n if (data.title) result.title = data.title;\n\n log('Extracted GUIDs from Cloud Player:', result);\n return Object.keys(result).length > 0 ? result : null;\n }\n\n function extractCourseInfo() {\n var info = {\n // Xyleme IRI format: http://xyleme.com/bravais/document/{guid}\n id: null,\n // GUIDs\n guid: null, // Document GUID (fe57e5f8-c46b-4b2c-a0c7-884af6b0001d)\n versionGuid: null, // Version GUID (4c884a96-8da8-472b-8d59-c1d2bce0008f)\n // Numeric IDs\n documentId: null, // Bravais document CDS ID (numeric)\n versionId: null, // Version number\n // Metadata\n title: 'Rise Course',\n description: '',\n format: 'SCORM by Rise',\n resourceType: 'Course',\n language: '',\n version: 1,\n cpv: null,\n // Shared link\n sharedLinkToken: null,\n sharedLinkName: null,\n // Tenant info\n homepage: COURSE_HOMEPAGE || window.location.origin,\n tenantHomepage: null, // https://{tenant}.bravais.com format\n // Package info\n packageName: null // Original SCORM zip filename\n };\n\n // 0. Use baked-in GUIDs from PA-Patcher config (highest priority - set at wrap time)\n if (DOCUMENT_GUID) {\n info.guid = DOCUMENT_GUID;\n info.id = 'http://xyleme.com/bravais/document/' + DOCUMENT_GUID;\n log('Document GUID from baked-in config:', DOCUMENT_GUID);\n }\n if (VERSION_GUID) {\n info.versionGuid = VERSION_GUID;\n log('Version GUID from baked-in config:', VERSION_GUID);\n }\n if (PACKAGE_NAME) {\n info.packageName = PACKAGE_NAME;\n log('Package name from baked-in config:', PACKAGE_NAME);\n }\n\n // 1. Extract shared link token and document ID from URL\n info.sharedLinkToken = extractSharedLinkToken();\n info.documentId = extractBravaisDocumentId();\n\n // 2. Detect tenant homepage from URL\n var tenantMatch = window.location.href.match(/core-([a-zA-Z0-9_-]+)\\\\.bravais\\\\.com/);\n if (tenantMatch) {\n info.tenantHomepage = 'https://' + tenantMatch[1] + '.bravais.com';\n log('Detected tenant homepage:', info.tenantHomepage);\n }\n\n // 3. Extract from SCORM launch data (most reliable for Bravais)\n var launchInfo = extractCourseFromLaunchData();\n if (launchInfo) {\n info.guid = launchInfo.guid || info.guid;\n info.versionGuid = launchInfo.versionGuid || info.versionGuid;\n info.title = launchInfo.title || info.title;\n info.versionId = launchInfo.versionId || info.versionId;\n info.documentId = launchInfo.documentId || info.documentId;\n info.description = launchInfo.description || info.description;\n info.format = launchInfo.format || info.format;\n info.resourceType = launchInfo.resourceType || info.resourceType;\n info.language = launchInfo.language || info.language;\n info.version = launchInfo.version || info.version;\n info.cpv = launchInfo.cpv || info.cpv;\n info.sharedLinkToken = launchInfo.sharedLinkToken || info.sharedLinkToken;\n info.sharedLinkName = launchInfo.sharedLinkName || info.sharedLinkName;\n }\n\n // 3b. Try to extract GUIDs from Xyleme Cloud Player's internal data\n // This is more reliable than API calls which may fail with 401\n if (!info.guid || !info.versionGuid) {\n var cloudPlayerData = extractFromXylemeCloudPlayer();\n if (cloudPlayerData) {\n info.guid = cloudPlayerData.guid || info.guid;\n info.versionGuid = cloudPlayerData.versionGuid || info.versionGuid;\n info.documentId = cloudPlayerData.documentId || info.documentId;\n info.title = cloudPlayerData.title || info.title;\n log('Updated course info from Cloud Player:', {\n guid: info.guid,\n versionGuid: info.versionGuid,\n documentId: info.documentId\n });\n }\n }\n\n // 4. Try getCourseTitle() from preloadIntegrity.js\n var preloadTitle = extractCourseTitleFromPreload();\n if (preloadTitle) {\n info.title = preloadTitle;\n }\n\n // 5. Try Rise's data\n try {\n if (window.__RISE_COURSE_DATA__) {\n info.title = info.title === 'Rise Course' ?\n (window.__RISE_COURSE_DATA__.title || info.title) : info.title;\n info.guid = window.__RISE_COURSE_DATA__.id || info.guid;\n }\n\n var metaTitle = document.querySelector('meta[property=\"og:title\"]');\n if (metaTitle && metaTitle.content && info.title === 'Rise Course') {\n info.title = metaTitle.content;\n }\n\n var metaDesc = document.querySelector('meta[property=\"og:description\"]');\n if (metaDesc && metaDesc.content) {\n info.description = metaDesc.content;\n }\n } catch (e) {}\n\n // 6. Check document title\n if (info.title === 'Rise Course' && document.title && document.title !== 'Rise Course') {\n info.title = document.title;\n }\n\n // 7. Get from PA metadata if available (check both .courseMetadata and .course)\n var paMeta = (window.pa_patcher && window.pa_patcher.courseMetadata) ||\n (window.pa_patcher && window.pa_patcher.course) || null;\n if (paMeta) {\n info.documentId = paMeta.documentId || paMeta.courseId || info.documentId;\n if (!info.guid) info.guid = paMeta.guid || paMeta.courseGuid || null;\n if (!info.versionGuid) info.versionGuid = paMeta.versionGuid || null;\n info.title = (info.title === 'Rise Course') ? (paMeta.title || paMeta.courseTitle || info.title) : info.title;\n info.versionId = paMeta.versionId || info.versionId;\n }\n\n // 8. Extract Bravais document name from launch environment (runtime)\n // This is the name shown in Bravais Analytics and used for search.\n // Overrides packageName so the statement object name matches the Bravais document.\n if (!info.packageName) {\n var bravaisDocName = null;\n\n // Strategy 1: Walk parent frames for PARAMS.documentName and PARAMS.did\n try {\n var win = window;\n for (var i = 0; i < 10; i++) {\n try {\n if (win.PARAMS) {\n if (win.PARAMS.documentName && !bravaisDocName) {\n bravaisDocName = win.PARAMS.documentName;\n log('Bravais document name from PARAMS:', bravaisDocName);\n }\n if (win.PARAMS.did && !info.documentId) {\n info.documentId = String(win.PARAMS.did);\n log('Bravais document ID from PARAMS.did:', info.documentId);\n }\n }\n } catch (e) {}\n if (win === win.parent) break;\n win = win.parent;\n }\n } catch (e) {}\n\n // Strategy 2: Walk parent frames checking window.name\n // Bravais sets the iframe name attribute to the document name\n if (!bravaisDocName) {\n try {\n var win = window;\n for (var i = 0; i < 10; i++) {\n try {\n var frameName = win.name;\n // Document names have hyphens and are long; skip generic frame names\n if (frameName && frameName.length > 20 && frameName.indexOf('-') > -1 &&\n frameName.indexOf('scorm') === -1 && frameName.indexOf('API') === -1) {\n bravaisDocName = frameName;\n log('Bravais document name from frame name:', bravaisDocName);\n break;\n }\n } catch (e) {}\n if (win === win.parent) break;\n win = win.parent;\n }\n } catch (e) {}\n }\n\n if (bravaisDocName) {\n info.packageName = bravaisDocName;\n }\n }\n\n // 9. Build the course ID in Xyleme IRI format\n // Native format: http://xyleme.com/bravais/document/{guid}\n if (info.guid) {\n info.id = 'http://xyleme.com/bravais/document/' + info.guid;\n } else if (info.documentId) {\n // Fallback: use document ID\n info.id = 'http://xyleme.com/bravais/document/' + info.documentId;\n } else {\n info.id = window.location.href.split('#')[0].split('?')[0];\n }\n\n // Decode any HTML entities in title/description\n if (info.title) info.title = decodeEntities(info.title);\n if (info.description) info.description = decodeEntities(info.description);\n\n // Build shared link name if not provided\n if (!info.sharedLinkName && info.title && info.sharedLinkToken) {\n info.sharedLinkName = info.title + ' - LMS Thin Pack';\n }\n\n LRS.courseInfo = info;\n log('Course info extracted:', info);\n return info;\n }\n\n // ========================================================================\n // 4b. LESSON/SECTION NAME EXTRACTION\n // ========================================================================\n\n /**\n * Get the current lesson/section information from Rise DOM\n * Returns { id, name, lessonIndex, sectionName }\n */\n function getCurrentLessonInfo() {\n var lessonInfo = {\n id: window.location.hash || '#/',\n name: null,\n lessonIndex: null,\n sectionName: null\n };\n\n try {\n // 1. Try to get from Rise's navigation sidebar\n // Rise shows active lesson with specific classes\n var activeLesson = document.querySelector(\n '.sidebar__link--current, ' +\n '.sidebar-lesson--active, ' +\n '.nav-sidebar__lesson--active, ' +\n '[class*=\"sidebar\"] [class*=\"current\"], ' +\n '[class*=\"sidebar\"] [class*=\"active\"], ' +\n '[aria-current=\"page\"], ' +\n '[aria-current=\"true\"]'\n );\n\n if (activeLesson) {\n var lessonTitle = activeLesson.querySelector(\n '.sidebar__link-text, ' +\n '.lesson-title, ' +\n '.sidebar-lesson__title, ' +\n '[class*=\"title\"], ' +\n '[class*=\"label\"]'\n );\n if (lessonTitle) {\n lessonInfo.name = lessonTitle.textContent.trim();\n } else {\n lessonInfo.name = activeLesson.textContent.trim();\n }\n\n // Try to get the section/module name (parent group)\n var section = activeLesson.closest(\n '.sidebar__section, ' +\n '.sidebar-section, ' +\n '[class*=\"section\"], ' +\n '[class*=\"module\"]'\n );\n if (section) {\n var sectionTitle = section.querySelector(\n '.sidebar__section-title, ' +\n '.section-title, ' +\n '[class*=\"section-title\"], ' +\n '[class*=\"module-title\"], ' +\n 'h2, h3'\n );\n if (sectionTitle) {\n lessonInfo.sectionName = sectionTitle.textContent.trim();\n }\n }\n }\n\n // 2. Try Rise's header/breadcrumb\n if (!lessonInfo.name) {\n var headerTitle = document.querySelector(\n '.lesson-header__title, ' +\n '.lesson__title, ' +\n '.content-header__title, ' +\n '[class*=\"lesson\"] [class*=\"header\"] [class*=\"title\"], ' +\n '.blocks-lesson-header__title'\n );\n if (headerTitle) {\n lessonInfo.name = headerTitle.textContent.trim();\n }\n }\n\n // 3. Try breadcrumbs\n if (!lessonInfo.name || !lessonInfo.sectionName) {\n var breadcrumbs = document.querySelectorAll(\n '.breadcrumb__item, ' +\n '.breadcrumbs li, ' +\n '[class*=\"breadcrumb\"] span, ' +\n '[class*=\"breadcrumb\"] a'\n );\n if (breadcrumbs.length >= 2) {\n // Usually: Course > Section > Lesson\n if (!lessonInfo.sectionName && breadcrumbs.length >= 2) {\n lessonInfo.sectionName = breadcrumbs[breadcrumbs.length - 2].textContent.trim();\n }\n if (!lessonInfo.name) {\n lessonInfo.name = breadcrumbs[breadcrumbs.length - 1].textContent.trim();\n }\n }\n }\n\n // 4. Try Rise's internal data\n if (window.__RISE_COURSE_DATA__ && window.__RISE_COURSE_DATA__.lessons) {\n var lessonId = lessonInfo.id.replace('#/lessons/', '').replace('#/', '');\n var lessons = window.__RISE_COURSE_DATA__.lessons;\n for (var i = 0; i < lessons.length; i++) {\n if (lessons[i].id === lessonId || lessons[i].slug === lessonId) {\n lessonInfo.name = lessons[i].title || lessonInfo.name;\n lessonInfo.lessonIndex = i;\n break;\n }\n }\n }\n\n // 5. Clean up - remove any \"Home\" or generic names if we're on a real lesson\n if (lessonInfo.name) {\n lessonInfo.name = lessonInfo.name.replace(/^\\\\\\\\d+\\\\\\\\.\\\\\\\\s*/, ''); // Remove leading numbers \"1. \"\n if (lessonInfo.name.length > 200) {\n lessonInfo.name = lessonInfo.name.substring(0, 200) + '...';\n }\n }\n\n if (lessonInfo.sectionName) {\n lessonInfo.sectionName = lessonInfo.sectionName.replace(/^\\\\\\\\d+\\\\\\\\.\\\\\\\\s*/, '');\n if (lessonInfo.sectionName.length > 200) {\n lessonInfo.sectionName = lessonInfo.sectionName.substring(0, 200) + '...';\n }\n }\n\n } catch (e) {\n warn('Error extracting lesson info:', e);\n }\n\n log('Current lesson info:', lessonInfo);\n return lessonInfo;\n }\n\n /**\n * Cache the current lesson info (updates on navigation)\n */\n var cachedLessonInfo = null;\n var cachedLessonHash = null;\n\n function getCachedLessonInfo() {\n var currentHash = window.location.hash;\n if (cachedLessonHash !== currentHash) {\n cachedLessonInfo = getCurrentLessonInfo();\n cachedLessonHash = currentHash;\n }\n return cachedLessonInfo || getCurrentLessonInfo();\n }\n\n // ========================================================================\n // 5. XAPI STATEMENT BUILDING\n // ========================================================================\n\n /**\n * Build an xAPI statement with COURSE as the main object\n * This ensures ALL statements show up when searching by document name in Bravais Analytics\n *\n * The activity type (video, assessment, interaction, etc.) is stored in object.definition.type\n * The specific activity details (media src, page ID, etc.) are stored in object.definition.extensions\n */\n function buildStatement(verb, activityType, activityDetails, result, context) {\n // ALWAYS use course as the main object\n var courseObj = buildCourseActivityObject();\n if (!courseObj) {\n // Fallback if course info not available\n courseObj = {\n objectType: 'Activity',\n id: window.location.href.split('#')[0].split('?')[0],\n definition: {\n type: 'http://xyleme.com/bravais/activities/document',\n name: { 'en-US': decodeEntities(document.title) || 'Rise Course' }\n }\n };\n }\n\n // Override the activity type based on the statement being sent\n // This allows filtering by type (video, assessment, interaction) in Analytics\n if (activityType) {\n courseObj.definition.type = activityType;\n\n // Also update resourceType extension to human-readable form for Bravais Type column\n courseObj.definition.extensions = courseObj.definition.extensions || {};\n var resourceTypeMap = {\n 'https://w3id.org/xapi/video/activity-type/video': 'Video',\n 'https://w3id.org/xapi/audio/activity-type/audio': 'Audio',\n 'http://adlnet.gov/expapi/activities/media': 'Media',\n 'http://adlnet.gov/expapi/activities/assessment': 'Assessment',\n 'http://adlnet.gov/expapi/activities/question': 'Question',\n 'http://adlnet.gov/expapi/activities/interaction': 'Interaction',\n 'http://adlnet.gov/expapi/activities/lesson': 'Lesson',\n 'http://adlnet.gov/expapi/activities/module': 'Module',\n 'http://adlnet.gov/expapi/activities/course': 'Course',\n 'http://xyleme.com/bravais/activities/document': 'Course'\n };\n courseObj.definition.extensions['resourceType'] = resourceTypeMap[activityType] || 'Course';\n }\n\n // Add activity-specific details to extensions\n courseObj.definition.extensions = courseObj.definition.extensions || {};\n\n if (activityDetails) {\n // Merge activity details into extensions, decoding any HTML entities in string values\n for (var key in activityDetails) {\n if (activityDetails.hasOwnProperty(key)) {\n var val = activityDetails[key];\n courseObj.definition.extensions[key] = typeof val === 'string' ? decodeEntities(val) : val;\n }\n }\n }\n\n var statement = {\n id: generateUUID(),\n actor: LRS.actor || extractActor(),\n verb: typeof verb === 'string' ? (VERBS[verb] || { id: verb, display: { 'en-US': verb } }) : verb,\n object: courseObj,\n timestamp: new Date().toISOString()\n };\n\n if (result) {\n statement.result = result;\n }\n\n // Build context with all Xyleme extensions using unified builder\n statement.context = buildXylemeContext(context);\n\n return statement;\n }\n\n /**\n * Legacy buildStatement for backward compatibility\n * Routes to new signature\n */\n function buildStatementLegacy(verb, object, result, context) {\n // Extract type and details from the legacy object\n var activityType = object && object.definition ? object.definition.type : null;\n var activityDetails = {};\n\n if (object) {\n activityDetails.activityId = object.id;\n if (object.definition) {\n if (object.definition.name && object.definition.name['en-US']) {\n activityDetails.activityName = object.definition.name['en-US'];\n }\n if (object.definition.description && object.definition.description['en-US']) {\n activityDetails.activityDescription = object.definition.description['en-US'];\n }\n if (object.definition.interactionType) {\n activityDetails.interactionType = object.definition.interactionType;\n }\n // Copy any existing extensions\n if (object.definition.extensions) {\n for (var key in object.definition.extensions) {\n if (object.definition.extensions.hasOwnProperty(key)) {\n activityDetails[key] = object.definition.extensions[key];\n }\n }\n }\n }\n }\n\n return buildStatement(verb, activityType, activityDetails, result, context);\n }\n\n /**\n * Build the course activity object in Xyleme native format\n * Object ID: http://xyleme.com/bravais/document/{guid}\n * Object type: http://xyleme.com/bravais/activities/document\n */\n function buildCourseActivityObject() {\n if (!LRS.courseInfo) return null;\n\n // Warn if we don't have the required GUIDs for Bravais aggregation\n if (!LRS.courseInfo.guid) {\n warn('Missing document GUID - statement may fail Bravais aggregation. Using numeric ID:', LRS.courseInfo.documentId);\n }\n if (!LRS.courseInfo.versionGuid) {\n warn('Missing version GUID - statement may fail Bravais aggregation (document_version_id will be null)');\n }\n\n // Use packageName (Bravais document name) as the object name when available,\n // so statements match the searchable document name in Bravais Analytics.\n // Strip .zip extension to match Bravais convention. Fall back to Rise course title.\n var objectName = LRS.courseInfo.packageName\n ? LRS.courseInfo.packageName.replace(/\\.zip$/i, '')\n : decodeEntities(LRS.courseInfo.title);\n\n var obj = {\n objectType: 'Activity',\n id: LRS.courseInfo.id,\n definition: {\n type: 'http://xyleme.com/bravais/activities/document',\n name: { 'en-US': objectName }\n }\n };\n\n // Add Xyleme-format extensions\n obj.definition.extensions = {};\n\n // Version ID in Xyleme IRI format - REQUIRED for Bravais aggregation\n if (LRS.courseInfo.versionGuid) {\n obj.definition.extensions['versionId'] =\n 'http://xyleme.com/bravais/document.version/' + LRS.courseInfo.versionGuid;\n } else {\n // Log error - this will cause aggregation failure\n warn('versionId extension not set - this statement will fail Bravais aggregation!');\n }\n\n // Format (e.g., \"SCORM by Rise\")\n obj.definition.extensions['format'] = LRS.courseInfo.format || 'SCORM by Rise';\n\n // Language\n obj.definition.extensions['language'] = LRS.courseInfo.language || '';\n\n // Version number\n obj.definition.extensions['version'] = LRS.courseInfo.version || 1;\n\n // Document CDS ID (numeric)\n if (LRS.courseInfo.documentId) {\n obj.definition.extensions['documentCdsId'] =\n parseInt(LRS.courseInfo.documentId, 10) || LRS.courseInfo.documentId;\n }\n\n // Resource type\n obj.definition.extensions['resourceType'] = LRS.courseInfo.resourceType || 'Course';\n\n // Package name (original SCORM zip filename)\n if (LRS.courseInfo.packageName) {\n obj.definition.extensions['packageName'] = LRS.courseInfo.packageName;\n }\n\n return obj;\n }\n\n /**\n * Build the parent course activity for context.contextActivities.parent\n * This links sub-activities (media, pages, interactions) to the course document\n * CRITICAL: This is what allows statements to show up when searching by document name in Analytics\n */\n function buildParentCourseActivity() {\n if (!LRS.courseInfo || !LRS.courseInfo.id) return null;\n\n var parent = {\n id: LRS.courseInfo.id,\n objectType: 'Activity',\n definition: {\n type: 'http://xyleme.com/bravais/activities/document',\n name: { 'en-US': decodeEntities(LRS.courseInfo.title) || 'Rise Course' }\n }\n };\n\n // Add documentCdsId - this is what Bravais uses for document search\n if (LRS.courseInfo.documentId) {\n parent.definition.extensions = {\n documentCdsId: parseInt(LRS.courseInfo.documentId, 10) || LRS.courseInfo.documentId\n };\n }\n\n return parent;\n }\n\n // ========================================================================\n // XYLEME NATIVE FORMAT BUILDERS\n // ========================================================================\n\n /**\n * Build context object in Xyleme native format\n * Includes all required context extensions for Bravais correlation\n */\n function buildXylemeContext(additionalContext) {\n var ctx = additionalContext || {};\n\n // Platform: \"Cloud Player\" for Xyleme compatibility\n ctx.platform = 'Cloud Player';\n\n ctx.extensions = ctx.extensions || {};\n\n if (LRS.courseInfo) {\n // Document URI (required for document correlation)\n if (LRS.courseInfo.guid) {\n ctx.extensions['document'] = 'http://xyleme.com/bravais/document/' + LRS.courseInfo.guid;\n } else if (LRS.courseInfo.documentId) {\n ctx.extensions['document'] = 'http://xyleme.com/bravais/document/' + LRS.courseInfo.documentId;\n }\n\n // Document version URI\n if (LRS.courseInfo.versionGuid) {\n ctx.extensions['documentVersion'] = 'http://xyleme.com/bravais/document.version/' + LRS.courseInfo.versionGuid;\n }\n\n // Shared link info\n if (LRS.courseInfo.sharedLinkToken) {\n ctx.extensions['sharedLinkToken'] = LRS.courseInfo.sharedLinkToken;\n }\n if (LRS.courseInfo.sharedLinkName) {\n ctx.extensions['sharedLinkName'] = LRS.courseInfo.sharedLinkName;\n }\n }\n\n // Course attempt ID (unique per session)\n ctx.extensions['courseAttemptId'] = LRS.courseAttemptId;\n\n // Publish index (default to 1)\n ctx.extensions['publishIndex'] = (LRS.courseInfo && LRS.courseInfo.publishIndex) ? LRS.courseInfo.publishIndex : 1;\n\n // Employee ID from employee lookup (if available)\n if (employeeLookupData && employeeLookupData.employeeId) {\n ctx.extensions['employeeId'] = employeeLookupData.employeeId;\n }\n\n // Xyleme schema version and PII flag\n ctx.extensions['http://xyleme.com/bravais/extensions/statement-schema'] = 'xyleme_10';\n ctx.extensions['http://xyleme.com/bravais/extensions/protect_pii'] = false;\n\n // Add parent course activity for document-level aggregation in Bravais\n // This allows statements with activity-specific objects to still be found\n // when searching by document name in Analytics\n var parentActivity = buildParentCourseActivity();\n if (parentActivity) {\n ctx.contextActivities = ctx.contextActivities || {};\n ctx.contextActivities.parent = [parentActivity];\n }\n\n return ctx;\n }\n\n /**\n * Build activity object for page/topic-level statements\n * Uses Xyleme page URI format instead of course/document\n */\n function buildPageActivityObject(pageInfo) {\n var pageGuid = pageInfo.pageGuid || pageInfo.id || generateUUID();\n\n return {\n objectType: 'Activity',\n id: 'http://xyleme.com/bravais/page/' + pageGuid,\n definition: {\n type: XYLEME_ACTIVITY_TYPES.page,\n name: { 'en-US': pageInfo.name || pageInfo.title || 'Page' },\n extensions: {\n resourceType: pageInfo.resourceType || RESOURCE_TYPES[pageInfo.type] || 'Topic'\n }\n }\n };\n }\n\n /**\n * Build activity object for assessments in Xyleme format\n */\n function buildAssessmentActivityObject(assessmentInfo) {\n var assessmentGuid = assessmentInfo.assessmentGuid || assessmentInfo.id || generateUUID();\n\n return {\n objectType: 'Activity',\n id: 'http://xyleme.com/bravais/assessment/' + assessmentGuid,\n definition: {\n type: XYLEME_ACTIVITY_TYPES.assessment,\n name: { 'en-US': assessmentInfo.title || 'Assessment' },\n extensions: {\n resourceType: 'Assessment'\n }\n }\n };\n }\n\n /**\n * Build activity object for questions in Xyleme format\n * Name format: \"Assessment Name - Q#: Question text...\"\n */\n /**\n * Simple string hash for generating short, stable IDs from long strings.\n * Returns a hex string (8 chars). Not cryptographic — just for uniqueness.\n */\n function hashString(str) {\n var hash = 0;\n for (var i = 0; i < str.length; i++) {\n var ch = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + ch;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash).toString(16).padStart(8, '0');\n }\n\n function buildQuestionActivityObject(questionInfo) {\n var rawGuid = questionInfo.questionGuid || questionInfo.id || generateUUID();\n\n // Ensure the object ID stays under 255 chars total to avoid Bravais aggregation failures.\n // Rise SCORM interaction IDs can be very long (full slugified question text).\n // Use a truncated prefix + hash to keep it short but unique and stable.\n var questionGuid = rawGuid;\n if (rawGuid.length > 80) {\n questionGuid = rawGuid.substring(0, 60) + '_' + hashString(rawGuid);\n }\n\n // Build human-readable display name\n var displayName = (questionInfo.assessmentName || 'Knowledge Check');\n if (questionInfo.questionNumber) {\n displayName += ' - Q' + questionInfo.questionNumber;\n }\n var questionText = decodeEntities(questionInfo.text || questionInfo.questionText || '');\n if (questionText.length > 50) {\n displayName += ': ' + questionText.substring(0, 47) + '...';\n } else if (questionText.length > 0) {\n displayName += ': ' + questionText;\n }\n\n return {\n objectType: 'Activity',\n id: 'http://xyleme.com/bravais/question/' + questionGuid,\n definition: {\n type: XYLEME_ACTIVITY_TYPES.question,\n name: { 'en-US': displayName },\n description: { 'en-US': questionText || 'Question' },\n extensions: {\n resourceType: 'Question',\n questionNumber: questionInfo.questionNumber || null\n }\n }\n };\n }\n\n /**\n * Format seconds into MM:SS or HH:MM:SS format\n */\n function formatMediaTime(seconds) {\n if (typeof seconds !== 'number' || isNaN(seconds)) return '0:00';\n var totalSeconds = Math.floor(seconds);\n var hours = Math.floor(totalSeconds / 3600);\n var minutes = Math.floor((totalSeconds % 3600) / 60);\n var secs = totalSeconds % 60;\n\n if (hours > 0) {\n return hours + ':' + (minutes < 10 ? '0' : '') + minutes + ':' + (secs < 10 ? '0' : '') + secs;\n }\n return minutes + ':' + (secs < 10 ? '0' : '') + secs;\n }\n\n /**\n * Build activity object for media (video/audio) with human-readable name\n * Name format varies by action:\n * - played: \"Video: Title (started at 1:23)\"\n * - paused: \"Video: Title (paused at 2:45)\"\n * - completed: \"Video: Title (completed)\"\n */\n function buildMediaActivityObject(mediaInfo) {\n var mediaGuid = mediaInfo.mediaGuid || generateUUID();\n var mediaType = mediaInfo.type === 'audio' ? ACTIVITY_TYPES.audio : ACTIVITY_TYPES.video;\n var resourceType = mediaInfo.type === 'audio' ? 'Audio' : 'Video';\n\n // Build human-readable display name\n var baseName = mediaInfo.name || '';\n if (!baseName || baseName === 'video' || baseName === 'audio' || baseName === 'Media') {\n baseName = mediaInfo.lessonName || 'Media';\n }\n\n // Add time context based on action\n var displayName = resourceType + ': ' + baseName;\n var currentTime = mediaInfo.currentTime || 0;\n var action = mediaInfo.action;\n\n if (action === 'play' || action === 'played') {\n displayName += ' (started at ' + formatMediaTime(currentTime) + ')';\n } else if (action === 'pause' || action === 'paused') {\n displayName += ' (paused at ' + formatMediaTime(currentTime) + ')';\n } else if (action === 'completed' || action === 'complete') {\n displayName += ' (completed)';\n }\n\n return {\n objectType: 'Activity',\n id: 'http://xyleme.com/bravais/media/' + mediaGuid,\n definition: {\n type: mediaType,\n name: { 'en-US': displayName },\n extensions: {\n resourceType: resourceType,\n mediaSrc: mediaInfo.src || '',\n mediaType: mediaInfo.type || 'video',\n duration: mediaInfo.duration || 0\n }\n }\n };\n }\n\n /**\n * Build activity object for interactions (tabs, accordions, flashcards, etc.)\n * Name format: \"Tab: Label\" or \"Accordion: Label\"\n */\n function buildInteractionActivityObject(interactionInfo) {\n var typeLabels = {\n 'tab': 'Tab',\n 'accordion': 'Accordion',\n 'flashcard': 'Flashcard',\n 'hotspot': 'Hotspot',\n 'process-step': 'Process Step',\n 'process': 'Process Step',\n 'sorting': 'Sorting Activity',\n 'button': 'Button',\n 'labeled-graphic': 'Labeled Graphic',\n 'timeline': 'Timeline',\n 'scenario': 'Scenario',\n 'checklist': 'Checklist',\n 'marker': 'Marker'\n };\n\n var interactionType = interactionInfo.type || interactionInfo.interactionType || 'interaction';\n var typeLabel = typeLabels[interactionType] || 'Interaction';\n\n // Build human-readable display name\n var displayName = interactionInfo.name;\n if (!displayName) {\n displayName = typeLabel + ': ' + (interactionInfo.label || interactionInfo.id || 'Item');\n }\n\n return {\n objectType: 'Activity',\n id: 'http://xyleme.com/bravais/interaction/' + (interactionInfo.id || generateUUID()),\n definition: {\n type: ACTIVITY_TYPES.interaction,\n name: { 'en-US': displayName },\n extensions: {\n resourceType: 'Interaction',\n interactionType: interactionType\n }\n }\n };\n }\n\n /**\n * Build xAPI statement in Xyleme native format\n * Supports page-level objects while maintaining document context\n */\n function buildStatementXyleme(verb, activityObject, result, additionalContext) {\n var statement = {\n id: generateUUID(),\n actor: LRS.actor || extractActor(),\n verb: typeof verb === 'string' ? (VERBS[verb] || { id: verb, display: { 'en-US': verb } }) : verb,\n object: activityObject,\n timestamp: new Date().toISOString()\n };\n\n if (result) {\n statement.result = result;\n }\n\n // Build context with all Xyleme extensions\n statement.context = buildXylemeContext(additionalContext);\n\n return statement;\n }\n\n function buildActivityObject(id, type, name, description) {\n var obj = {\n objectType: 'Activity',\n id: id,\n definition: {\n type: type || ACTIVITY_TYPES.lesson\n }\n };\n\n if (name) {\n obj.definition.name = { 'en-US': name };\n }\n if (description) {\n obj.definition.description = { 'en-US': description };\n }\n\n return obj;\n }\n\n // ========================================================================\n // 6. STATEMENT SENDING\n // ========================================================================\n\n function sendStatement(statement) {\n log('Sending statement:', statement.verb.display['en-US'] || statement.verb.id, statement);\n\n // Log first statement visibly (not gated by DEBUG) for production diagnostics\n if (LRS.stats.statementsSent === 0 && LRS.stats.statementsFailed === 0 && LRS.stats.statementsQueued === 0) {\n if (window.console && window.console.info) {\n var verb = statement.verb.display ? (statement.verb.display['en-US'] || statement.verb.id) : statement.verb.id;\n console.info('[PA-LRS] First statement: verb=' + verb +\n ', endpoint=' + (LRS_ENDPOINT || 'NONE') +\n ', proxy=' + (LRS_PROXY_ENDPOINT ? 'yes' : 'no') +\n ', mode=' + LRS.mode);\n }\n }\n\n // Store in event log\n LRS.eventLog.push({\n statement: statement,\n timestamp: new Date().toISOString(),\n mode: LRS.mode,\n lrsEndpoint: LRS_ENDPOINT\n });\n\n // ALWAYS prefer direct LRS when endpoint is available\n // This ensures all our detailed tracking (media, interactions, etc.) goes to the LRS\n // PlayerIntegration is used only for SCORM state, not for xAPI statements\n if (LRS_ENDPOINT && LRS_ENDPOINT.length > 0) {\n log('Sending via directLRS:', LRS_ENDPOINT);\n return sendViaDirectLRS(statement);\n } else if (LRS.mode === 'playerIntegration' && LRS.integration) {\n // Fallback to PlayerIntegration only if no direct LRS endpoint\n log('No LRS endpoint, falling back to PlayerIntegration');\n return sendViaPlayerIntegration(statement);\n } else {\n // SCORM-only or offline mode - just log\n log('Statement logged (no LRS):', statement.verb.display['en-US']);\n LRS.stats.statementsQueued++;\n return Promise.resolve({ logged: true });\n }\n }\n\n function sendViaPlayerIntegration(statement) {\n // Map xAPI verb to PlayerIntegration method\n var verbId = statement.verb.id;\n var data = {\n ...statement.object.definition,\n id: statement.object.id,\n timestamp: statement.timestamp\n };\n\n if (statement.result) {\n data.result = statement.result;\n }\n\n try {\n if (verbId.indexOf('played') > -1 || verbId.indexOf('paused') > -1 || verbId.indexOf('completed') > -1) {\n if (statement.object.definition && statement.object.definition.type &&\n (statement.object.definition.type.indexOf('video') > -1 || statement.object.definition.type.indexOf('audio') > -1)) {\n LRS.integration.mediaPlayed(data);\n }\n } else if (verbId.indexOf('experienced') > -1 || verbId.indexOf('viewed') > -1 || verbId.indexOf('launched') > -1) {\n LRS.integration.contentOpened(data);\n } else if (verbId.indexOf('attempted') > -1) {\n LRS.integration.assessmentStarted(data);\n } else if (verbId.indexOf('passed') > -1 || verbId.indexOf('failed') > -1) {\n LRS.integration.assessmentEnded(data);\n } else if (verbId.indexOf('answered') > -1) {\n LRS.integration.questionAnswered(data);\n } else if (verbId.indexOf('interacted') > -1) {\n LRS.integration.sendCustomStatement({ verb: 'interacted', data: data });\n } else {\n LRS.integration.sendCustomStatement({ verb: verbId, data: data });\n }\n\n LRS.stats.statementsSent++;\n return Promise.resolve({ sent: true, via: 'playerIntegration' });\n\n } catch (e) {\n warn('Error sending via PlayerIntegration:', e);\n LRS.stats.statementsFailed++;\n return Promise.reject(e);\n }\n }\n\n function sendViaDirectLRS(statement) {\n // Use proxy endpoint if configured, otherwise direct LRS\n var useProxy = LRS_PROXY_ENDPOINT && LRS_PROXY_ENDPOINT.length > 0;\n var endpoint = useProxy ? LRS_PROXY_ENDPOINT : LRS_ENDPOINT;\n\n if (!endpoint || endpoint.length === 0) {\n warn('No LRS endpoint available');\n LRS.stats.statementsFailed++;\n return Promise.reject(new Error('No LRS endpoint'));\n }\n\n return new Promise(function(resolve, reject) {\n var xhr = new XMLHttpRequest();\n // POST to proxy (proxy handles PUT to LRS), POST to direct LRS\n xhr.open('POST', endpoint, true);\n\n // xAPI required headers\n xhr.setRequestHeader('Content-Type', 'application/json');\n xhr.setRequestHeader('X-Experience-API-Version', '1.0');\n\n if (useProxy) {\n // Proxy handles auth server-side, no auth headers needed\n log('Sending via LRS proxy:', endpoint);\n } else if (LRS_AUTH && LRS_AUTH.length > 0) {\n xhr.setRequestHeader('Authorization', 'Basic ' + LRS_AUTH);\n log('Using Basic Auth for LRS request');\n } else if (LRS_WITH_CREDENTIALS) {\n xhr.withCredentials = true;\n }\n\n xhr.onreadystatechange = function() {\n if (xhr.readyState === 4) {\n if (xhr.status >= 200 && xhr.status < 300) {\n log('Statement sent successfully:', statement.verb.display['en-US'], useProxy ? '(via proxy)' : '(direct)');\n LRS.stats.statementsSent++;\n resolve({ sent: true, via: useProxy ? 'proxy' : 'directLRS', status: xhr.status });\n } else {\n warn('Statement send failed:', xhr.status, xhr.statusText);\n LRS.stats.statementsFailed++;\n\n // Queue for retry if server error\n if (xhr.status >= 500) {\n LRS.statementQueue.push(statement);\n }\n\n reject(new Error('HTTP ' + xhr.status + ': ' + xhr.statusText));\n }\n }\n };\n\n xhr.onerror = function() {\n warn('Network error sending statement');\n LRS.stats.statementsFailed++;\n LRS.statementQueue.push(statement);\n reject(new Error('Network error'));\n };\n\n try {\n xhr.send(JSON.stringify(statement));\n } catch (e) {\n warn('Error sending statement:', e);\n LRS.stats.statementsFailed++;\n reject(e);\n }\n });\n }\n\n // Retry queue processing\n function processStatementQueue() {\n if (LRS.statementQueue.length === 0) return;\n\n log('Processing statement queue:', LRS.statementQueue.length, 'statements');\n\n var queue = LRS.statementQueue.slice();\n LRS.statementQueue = [];\n\n queue.forEach(function(statement) {\n sendViaDirectLRS(statement).catch(function(e) {\n // Already re-queued on failure\n });\n });\n }\n\n // ========================================================================\n // 7. PUBLIC API METHODS\n // ========================================================================\n\n // Course launched - uses Xyleme native format for course object\n LRS.courseLaunched = function() {\n if (!TRACK_NAVIGATION) return;\n\n // Course launch uses the document activity type (default)\n var statement = buildStatement(\n 'launched',\n 'http://xyleme.com/bravais/activities/document',\n { event: 'course_launched' }\n );\n sendStatement(statement);\n };\n\n // Content/Page viewed - uses page-specific object for human-readable display\n // Object shows page/lesson title, parent context maintains document aggregation\n LRS.contentOpened = function(data) {\n if (!TRACK_NAVIGATION) return;\n\n // Get current lesson info from DOM (includes name)\n var lessonInfo = getCurrentLessonInfo();\n\n var result = null;\n if (data.duration) {\n result = { duration: data.duration };\n }\n\n // Extract clean page/lesson ID from Rise hash paths\n var lessonId = data.pageGuid || data.lessonId || lessonInfo.id || '';\n if (lessonId.indexOf('#/lessons/') === 0) {\n lessonId = lessonId.substring(10); // Remove '#/lessons/'\n } else if (lessonId.indexOf('#/') === 0) {\n lessonId = lessonId.substring(2); // Remove '#/'\n }\n\n // Get the page title - prefer lesson name from DOM\n var pageTitle = data.title || lessonInfo.name || 'Page';\n\n // Build page activity object with human-readable name\n // This shows the page/lesson title in the Object column\n var pageObject = buildPageActivityObject({\n pageGuid: lessonId,\n name: pageTitle,\n resourceType: 'Lesson'\n });\n\n // Add page info to context extensions\n var additionalContext = {\n extensions: {\n pageId: lessonId || 'home',\n pageTitle: pageTitle,\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName,\n pageUrl: data.url || window.location.href\n }\n };\n\n // Use 'experienced' verb for viewing content\n // Parent course activity added by buildXylemeContext ensures document aggregation\n var statement = buildStatementXyleme('experienced', pageObject, result, additionalContext);\n sendStatement(statement);\n };\n\n // Media events - uses activity-specific object with human-readable name\n // Parent context (added by buildXylemeContext) ensures document aggregation\n LRS.mediaPlayed = function(data) {\n if (!TRACK_MEDIA) return;\n\n // Get current lesson context\n var lessonInfo = getCachedLessonInfo();\n\n var verbKey = data.action === 'play' ? 'played' :\n data.action === 'pause' ? 'paused' :\n data.action === 'completed' ? 'completed' : 'played';\n\n // Build activity-specific object with human-readable name including time context\n // e.g., \"Video: Introduction (started at 1:23)\" or \"Audio: Podcast (paused at 2:45)\"\n var mediaObject = buildMediaActivityObject({\n type: data.type,\n src: data.src,\n name: data.name,\n duration: data.duration,\n lessonName: lessonInfo.name,\n action: data.action,\n currentTime: data.currentTime\n });\n\n var result = {\n extensions: {\n 'https://w3id.org/xapi/video/extensions/time': data.currentTime || 0,\n 'https://w3id.org/xapi/video/extensions/progress': data.duration > 0 ?\n Math.round((data.currentTime / data.duration) * 100) / 100 : 0,\n 'https://w3id.org/xapi/video/extensions/length': data.duration || 0\n }\n };\n\n if (data.action === 'completed') {\n result.completion = true;\n }\n\n // Context includes lesson info; parent course activity added by buildXylemeContext\n var additionalContext = {\n extensions: {\n lessonId: lessonInfo.id,\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName\n }\n };\n\n var statement = buildStatementXyleme(verbKey, mediaObject, result, additionalContext);\n sendStatement(statement);\n };\n\n // Assessment started\n LRS.assessmentStarted = function(data) {\n if (!TRACK_QUIZZES) return;\n\n // Get lesson context\n var lessonInfo = getCachedLessonInfo();\n\n // Build assessment activity object for Xyleme format\n var assessmentObject = buildAssessmentActivityObject({\n assessmentGuid: data.assessmentGuid || data.id,\n title: data.title || data.assessmentName || 'Assessment'\n });\n\n // Add lesson context to extensions\n assessmentObject.definition = assessmentObject.definition || {};\n assessmentObject.definition.extensions = assessmentObject.definition.extensions || {};\n assessmentObject.definition.extensions.lessonName = lessonInfo.name;\n assessmentObject.definition.extensions.sectionName = lessonInfo.sectionName;\n\n var statement = buildStatementXyleme('attempted', assessmentObject);\n sendStatement(statement);\n };\n\n // Assessment ended\n LRS.assessmentEnded = function(data) {\n if (!TRACK_QUIZZES) return;\n\n // Get lesson context (may be passed in from extractQuizResult)\n var lessonInfo = data.lessonName ? { name: data.lessonName, sectionName: data.sectionName } : getCachedLessonInfo();\n\n // Build assessment activity object for Xyleme format\n var assessmentObject = buildAssessmentActivityObject({\n assessmentGuid: data.assessmentGuid || data.assessmentId || data.id,\n title: data.title || data.assessmentName || 'Assessment'\n });\n\n // Add lesson context to extensions\n assessmentObject.definition = assessmentObject.definition || {};\n assessmentObject.definition.extensions = assessmentObject.definition.extensions || {};\n assessmentObject.definition.extensions.lessonName = lessonInfo.name;\n assessmentObject.definition.extensions.sectionName = lessonInfo.sectionName;\n assessmentObject.definition.extensions.assessmentName = data.assessmentName || data.title;\n\n var result = {\n completion: true,\n extensions: {\n 'https://w3id.org/xapi/acrossx/extensions/assessmentName': data.assessmentName || data.title || null,\n 'https://w3id.org/xapi/acrossx/extensions/lessonName': lessonInfo.name || null,\n 'https://w3id.org/xapi/acrossx/extensions/sectionName': lessonInfo.sectionName || null,\n 'https://w3id.org/xapi/acrossx/extensions/questionCount': data.questionCount || null\n }\n };\n\n if (typeof data.score === 'number') {\n result.score = {\n scaled: data.score / 100,\n raw: data.score,\n max: 100,\n min: 0\n };\n result.success = data.score >= (data.passingScore || 70);\n }\n\n if (data.duration) {\n result.duration = data.duration;\n }\n\n var verbKey = result.success !== false ? 'passed' : 'failed';\n if (typeof result.success === 'undefined') {\n verbKey = 'completed';\n }\n\n var statement = buildStatementXyleme(verbKey, assessmentObject, result);\n sendStatement(statement);\n };\n\n // Question answered\n LRS.questionAnswered = function(data) {\n if (!TRACK_QUIZZES) return;\n\n // Get lesson context if not provided\n var lessonInfo = data.lessonName ? data : getCachedLessonInfo();\n\n // Build question activity object for Xyleme format\n var questionObject = buildQuestionActivityObject({\n questionGuid: data.questionGuid || data.questionId,\n text: data.questionText || 'Question'\n });\n\n // Build comprehensive result with human-readable data\n var result = {\n response: data.answer || data.response || '',\n extensions: {\n 'https://w3id.org/xapi/acrossx/extensions/questionNumber': data.questionNumber || null,\n 'https://w3id.org/xapi/acrossx/extensions/questionText': data.questionText || null,\n 'https://w3id.org/xapi/acrossx/extensions/answerText': data.answer || data.response || null,\n 'https://w3id.org/xapi/acrossx/extensions/correctAnswer': data.correctAnswer || null,\n 'https://w3id.org/xapi/acrossx/extensions/assessmentName': data.assessmentName || null,\n 'https://w3id.org/xapi/acrossx/extensions/lessonName': lessonInfo.lessonName || lessonInfo.name || null,\n 'https://w3id.org/xapi/acrossx/extensions/sectionName': lessonInfo.sectionName || null\n }\n };\n\n if (data.result === 'correct' || data.correct === true) {\n result.success = true;\n } else if (data.result === 'incorrect' || data.correct === false) {\n result.success = false;\n }\n\n var statement = buildStatementXyleme('answered', questionObject, result);\n sendStatement(statement);\n };\n\n // Submit all questions\n LRS.questionsSubmitAll = function(data) {\n if (!TRACK_QUIZZES) return;\n\n // Send individual question statements\n if (data.questions && Array.isArray(data.questions)) {\n data.questions.forEach(function(q) {\n LRS.questionAnswered(q);\n });\n }\n\n // Then send assessment completed\n LRS.assessmentEnded(data);\n };\n\n // Interactions (tabs, accordions, etc.) - uses activity-specific object\n // Object name shows the interaction type and label, e.g., \"Tab: Overview\"\n // Parent context ensures these appear in document search results\n LRS.interacted = function(data) {\n if (!TRACK_INTERACTIONS) return;\n\n // Get current lesson context\n var lessonInfo = getCachedLessonInfo();\n\n // Build activity-specific object with human-readable name\n // e.g., \"Tab: Getting Started\", \"Accordion: FAQ\"\n var interactionObject = buildInteractionActivityObject({\n type: data.type || data.interactionType,\n id: data.id,\n name: data.name,\n label: data.name || data.type\n });\n\n // Context includes lesson info; parent course activity added by buildXylemeContext\n var additionalContext = {\n extensions: {\n interactionId: data.id || 'interaction-' + Date.now(),\n lessonId: lessonInfo.id,\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName\n }\n };\n\n var statement = buildStatementXyleme('interacted', interactionObject, null, additionalContext);\n sendStatement(statement);\n };\n\n // Custom statement\n LRS.sendCustomStatement = function(data) {\n if (!CUSTOM_STATEMENTS) return;\n\n var verb = data.verb || 'interacted';\n var objectData = data.object || {};\n\n // Activity type from data or default to interaction\n var activityType = objectData.type || ACTIVITY_TYPES.interaction;\n\n // Activity details\n var activityDetails = {\n customActivityId: objectData.id || data.id || 'custom-' + Date.now(),\n customActivityName: objectData.name || data.name || 'Custom Activity'\n };\n\n // Merge any extra data\n if (data.data) {\n for (var key in data.data) {\n if (data.data.hasOwnProperty(key)) {\n activityDetails[key] = data.data[key];\n }\n }\n }\n\n var statement = buildStatement(verb, activityType, activityDetails, data.result, data.context);\n sendStatement(statement);\n };\n\n // Send a \"launched\" statement — called by skin after email gate / actor setup\n LRS.sendLaunchStatement = function(data) {\n data = data || {};\n var activityDetails = {\n launchSource: data.source || 'email-gate',\n employeeEmail: data.email || (LRS.actor && LRS.actor.mbox ? LRS.actor.mbox.replace('mailto:', '') : undefined),\n employeeId: data.employeeId || LRS.employeeId || undefined\n };\n\n if (data.employeeName) activityDetails.employeeName = data.employeeName;\n\n var statement = buildStatement('launched', ACTIVITY_TYPES.course, activityDetails, null, null);\n log('Sending launch statement for:', activityDetails.employeeEmail || '(unknown)');\n sendStatement(statement);\n };\n\n // Send a course completion statement — called by skin or auto-detected via SCORM\n LRS.sendCompletionStatement = function(data) {\n data = data || {};\n // Cancel any pending SCORM auto-detection to avoid double-fire for same attempt\n if (scormCompletionDebounce) {\n clearTimeout(scormCompletionDebounce);\n scormCompletionDebounce = null;\n }\n\n var status = data.status || 'completed';\n var courseTitle = data.courseTitle || (LRS.courseInfo && LRS.courseInfo.title ? decodeEntities(LRS.courseInfo.title) : decodeEntities(document.title) || 'Course');\n\n var verbKey = 'completed';\n if (status === 'passed') verbKey = 'passed';\n if (status === 'failed') verbKey = 'failed';\n\n var result = { completion: true, success: (status === 'passed') };\n\n if (typeof data.score === 'number') {\n var max = data.scoreMax || 100;\n var min = data.scoreMin || 0;\n result.score = {\n raw: data.score,\n max: max,\n min: min,\n scaled: max > min ? (data.score - min) / (max - min) : 0\n };\n }\n\n var activityDetails = {\n courseTitle: courseTitle,\n completionStatus: status,\n employeeEmail: data.email || (LRS.actor && LRS.actor.mbox ? LRS.actor.mbox.replace('mailto:', '') : undefined),\n employeeId: data.employeeId || LRS.employeeId || undefined\n };\n if (data.employeeName) activityDetails.employeeName = data.employeeName;\n\n // 1. Always send \"completed\" statement\n log('Sending completed statement, score:', result.score, 'title:', courseTitle);\n var completedStatement = buildStatement('completed', ACTIVITY_TYPES.course, activityDetails, result, null);\n sendStatement(completedStatement);\n\n // 2. Send \"passed\" or \"failed\" statement (assessment outcome)\n if (status === 'passed' || status === 'failed') {\n log('Sending', status, 'statement');\n var outcomeStatement = buildStatement(status, ACTIVITY_TYPES.course, activityDetails, result, null);\n sendStatement(outcomeStatement);\n }\n };\n\n // Send a \"terminated\" statement — called by skin close button or auto on page unload\n var exitStatementSent = false;\n LRS.sendExitStatement = function(data) {\n if (exitStatementSent) return;\n exitStatementSent = true;\n data = data || {};\n\n var courseTitle = (LRS.courseInfo && LRS.courseInfo.title) ? decodeEntities(LRS.courseInfo.title) : decodeEntities(document.title) || 'Course';\n var duration = null;\n if (LRS.launchTime) {\n var ms = new Date().getTime() - new Date(LRS.launchTime).getTime();\n var secs = Math.floor(ms / 1000);\n var mins = Math.floor(secs / 60);\n var hrs = Math.floor(mins / 60);\n duration = 'PT' + (hrs > 0 ? hrs + 'H' : '') + (mins % 60) + 'M' + (secs % 60) + 'S';\n }\n\n var activityDetails = {\n courseTitle: courseTitle,\n exitSource: data.source || 'page-unload',\n employeeEmail: LRS.actor && LRS.actor.mbox ? LRS.actor.mbox.replace('mailto:', '') : undefined,\n employeeId: LRS.employeeId || undefined,\n sessionDuration: duration,\n statementsSent: LRS.stats.statementsSent\n };\n\n var result = duration ? { duration: duration } : null;\n log('Sending exit statement, duration:', duration);\n var statement = buildStatement('terminated', ACTIVITY_TYPES.course, activityDetails, result, null);\n sendStatement(statement);\n };\n\n // Auto-send exit statement on page unload\n window.addEventListener('beforeunload', function() {\n if (!exitStatementSent && LRS.actor) {\n LRS.sendExitStatement({ source: 'page-unload' });\n }\n });\n\n /**\n * Re-extract actor from SCORM after LMSInitialize has been called.\n * Call this from the SCORM wrapper after scormInit() succeeds,\n * because the bridge initializes in <head> before LMSInitialize runs.\n */\n LRS.refreshActor = function(callback) {\n // If SCORM API wasn't found during <head> init, re-scan now\n // (some LMS inject window.API after page starts loading)\n if (!LRS.scormApi) {\n log('refreshActor: SCORM API missing, re-scanning...');\n var api2004 = findAPIInFrameHierarchy('API_1484_11', 10);\n if (api2004) {\n LRS.scormApi = api2004.api;\n LRS.scormApiFound = true;\n LRS.scormApiType = '2004';\n log('refreshActor: found SCORM 2004 API');\n } else {\n var api12 = findAPIInFrameHierarchy('API', 10);\n if (api12) {\n LRS.scormApi = api12.api;\n LRS.scormApiFound = true;\n LRS.scormApiType = '1.2';\n log('refreshActor: found SCORM 1.2 API');\n }\n }\n }\n\n // Always-visible diagnostic: confirm refreshActor is being called and show SCORM data (TEMPORARY)\n if (window.console && window.console.info) {\n var scormId = 'n/a', scormName = 'n/a';\n if (LRS.scormApi) {\n try {\n scormId = LRS.scormApiType === '2004'\n ? LRS.scormApi.GetValue('cmi.learner_id')\n : LRS.scormApi.LMSGetValue('cmi.core.student_id');\n scormName = LRS.scormApiType === '2004'\n ? LRS.scormApi.GetValue('cmi.learner_name')\n : LRS.scormApi.LMSGetValue('cmi.core.student_name');\n } catch(e) { scormId = 'error'; scormName = 'error'; }\n }\n console.info('[PA-LRS] refreshActor called. SCORM API=' + (LRS.scormApi ? 'found' : 'MISSING') +\n ', student_id=' + scormId +\n ', student_name=' + scormName +\n ', previous actor=' + (LRS.actor ? LRS.actor.name : 'none'));\n }\n log('refreshActor called - re-extracting from SCORM...');\n var previousName = LRS.actor ? LRS.actor.name : 'none';\n\n // Re-run sync extraction (SCORM data should now be available)\n var newActor = extractActor();\n\n if (isValidActor(newActor) && newActor.account && newActor.account.name !== 'unknown') {\n log('refreshActor: got valid actor:', newActor.name, newActor.account ? newActor.account.name : '');\n // Enhance with email lookup (async)\n extractActorAsync(function(enhancedActor) {\n if (window.console && window.console.info) {\n console.info('[PA-LRS] Actor refreshed: ' + previousName + ' -> ' + enhancedActor.name +\n (enhancedActor.mbox ? ' (' + enhancedActor.mbox + ')' : ''));\n }\n if (callback) callback(enhancedActor);\n });\n } else {\n log('refreshActor: no valid actor found after re-extraction');\n if (callback) callback(LRS.actor);\n }\n };\n\n // Course terminated/exited\n LRS.courseTerminated = function() {\n // Calculate duration\n var duration = null;\n if (LRS.launchTime) {\n var launchMs = new Date(LRS.launchTime).getTime();\n var nowMs = Date.now();\n var durationMs = nowMs - launchMs;\n // Convert to ISO 8601 duration\n var seconds = Math.floor(durationMs / 1000);\n var minutes = Math.floor(seconds / 60);\n var hours = Math.floor(minutes / 60);\n seconds = seconds % 60;\n minutes = minutes % 60;\n duration = 'PT' + hours + 'H' + minutes + 'M' + seconds + 'S';\n }\n\n var result = duration ? { duration: duration } : null;\n\n // Use course object for termination (document-level) with 'exited' verb for Xyleme\n var courseObj = buildCourseActivityObject();\n if (courseObj) {\n courseObj.definition.extensions = courseObj.definition.extensions || {};\n courseObj.definition.extensions.resourceType = 'Course';\n }\n\n var statement = buildStatementXyleme('exited', courseObj, result);\n sendStatement(statement);\n };\n\n // Legacy compatibility methods\n LRS.choiceBranchSelected = function(data) {\n LRS.interacted({ type: 'choice-branch', id: data.id, name: 'Choice Branch Selected' });\n };\n\n LRS.choiceBranchDiscarded = function(data) {\n LRS.interacted({ type: 'choice-branch', id: data.id, name: 'Choice Branch Discarded' });\n };\n\n // ========================================================================\n // PUBLIC API — expose core internals for skin scripts\n // Usage: window.pa_patcher.lrs.api.sendStatement(stmt)\n // ========================================================================\n LRS.api = {\n sendStatement: sendStatement,\n buildStatement: buildStatement,\n buildCourseActivityObject: buildCourseActivityObject,\n buildXylemeContext: buildXylemeContext,\n generateUUID: generateUUID,\n decodeEntities: decodeEntities,\n extractActor: extractActor,\n getCachedLessonInfo: getCachedLessonInfo,\n VERBS: VERBS,\n ACTIVITY_TYPES: ACTIVITY_TYPES\n };\n\n // ========================================================================\n // 8. RISE EVENT INTERCEPTORS\n // ========================================================================\n\n function setupMediaInterceptors() {\n if (!TRACK_MEDIA) return;\n\n // Track which elements we've already attached listeners to\n var trackedMedia = new WeakSet();\n\n // Helper to get media name/title from element or surrounding context\n function getMediaName(el) {\n // Check element attributes\n var name = el.title || el.getAttribute('aria-label') || el.getAttribute('data-name');\n if (name) return name;\n\n // Check parent Rise video block for title\n var videoBlock = findClosest(el, '[data-block-type=\"video\"], .blocks-video, .video-block');\n if (videoBlock) {\n var titleEl = videoBlock.querySelector('.video-title, .block-title, [class*=\"title\"]');\n if (titleEl) return titleEl.textContent.trim();\n }\n\n // Use src filename as fallback\n var src = el.src || el.currentSrc || '';\n if (src) {\n var filename = src.split('/').pop().split('?')[0];\n if (filename && filename.length < 100) return filename;\n }\n\n return el.tagName.toLowerCase();\n }\n\n // Attach media event listeners to a single element\n function attachMediaListeners(el) {\n if (trackedMedia.has(el)) return;\n trackedMedia.add(el);\n\n var mediaType = el.tagName.toLowerCase();\n log('Attaching media listeners to:', mediaType, el.src || el.currentSrc || 'no-src');\n\n el.addEventListener('play', function() {\n log('Media play event captured:', mediaType);\n LRS.mediaPlayed({\n type: mediaType,\n src: el.src || el.currentSrc || '',\n name: getMediaName(el),\n currentTime: el.currentTime || 0,\n duration: el.duration || 0,\n action: 'play'\n });\n });\n\n el.addEventListener('pause', function() {\n // Ignore pause events that fire right before ended\n if (el.ended) return;\n log('Media pause event captured:', mediaType);\n LRS.mediaPlayed({\n type: mediaType,\n src: el.src || el.currentSrc || '',\n name: getMediaName(el),\n currentTime: el.currentTime || 0,\n duration: el.duration || 0,\n action: 'pause'\n });\n });\n\n el.addEventListener('ended', function() {\n log('Media ended event captured:', mediaType);\n LRS.mediaPlayed({\n type: mediaType,\n src: el.src || el.currentSrc || '',\n name: getMediaName(el),\n currentTime: el.duration || 0,\n duration: el.duration || 0,\n action: 'completed'\n });\n });\n }\n\n // Scan for and attach listeners to all video/audio elements\n function scanForMedia(root) {\n var elements = (root || document).querySelectorAll('video, audio');\n log('Scanning for media elements, found:', elements.length);\n for (var i = 0; i < elements.length; i++) {\n attachMediaListeners(elements[i]);\n }\n }\n\n // Initial scan\n scanForMedia();\n\n // Document-level capture for any we might miss\n document.addEventListener('play', function(e) {\n var target = e.target;\n if (target.tagName === 'VIDEO' || target.tagName === 'AUDIO') {\n // Attach listeners if not already tracked\n if (!trackedMedia.has(target)) {\n log('Captured play from untracked media, attaching listeners');\n attachMediaListeners(target);\n }\n }\n }, true);\n\n // MutationObserver to detect dynamically added video/audio elements\n var mediaObserver = new MutationObserver(function(mutations) {\n var needsScan = false;\n mutations.forEach(function(mutation) {\n mutation.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'VIDEO' || node.tagName === 'AUDIO') {\n attachMediaListeners(node);\n } else if (node.querySelectorAll) {\n // Check for nested video/audio\n var nested = node.querySelectorAll('video, audio');\n if (nested.length > 0) {\n needsScan = true;\n }\n }\n }\n });\n });\n if (needsScan) {\n scanForMedia();\n }\n });\n\n // Start observing once DOM is ready\n function startMediaObserver() {\n if (document.body) {\n mediaObserver.observe(document.body, {\n childList: true,\n subtree: true\n });\n log('Media mutation observer started');\n } else {\n setTimeout(startMediaObserver, 100);\n }\n }\n startMediaObserver();\n\n // Periodic rescan for Rise lazy-loaded content\n setInterval(function() {\n scanForMedia();\n }, 3000);\n\n log('Media interceptors set up (enhanced)');\n }\n\n function setupNavigationInterceptors() {\n if (!TRACK_NAVIGATION) return;\n\n window.addEventListener('hashchange', function() {\n var newLesson = window.location.hash || '#/';\n if (newLesson !== LRS.currentLesson) {\n LRS.currentLesson = newLesson;\n LRS.contentOpened({\n lessonId: newLesson,\n url: window.location.href,\n action: 'navigated'\n });\n }\n });\n\n LRS.currentLesson = window.location.hash || '#/';\n\n log('Navigation interceptors set up');\n }\n\n function setupQuizInterceptors() {\n if (!TRACK_QUIZZES) return;\n\n // Set up Knowledge Check specific interceptors for Rise blocks\n setupKnowledgeCheckInterceptors();\n\n var quizObserver = new MutationObserver(function(mutations) {\n mutations.forEach(function(mutation) {\n mutation.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n detectQuizElements(node);\n }\n });\n });\n });\n\n function startQuizObserver() {\n if (document.body) {\n quizObserver.observe(document.body, {\n childList: true,\n subtree: true\n });\n log('Quiz observer started');\n } else {\n setTimeout(startQuizObserver, 100);\n }\n }\n startQuizObserver();\n }\n\n function detectQuizElements(node) {\n var resultClasses = ['quiz-result', 'knowledge-result', 'quiz-feedback', 'assessment-result',\n 'blocks-quiz__result', 'block-quiz-results'];\n\n resultClasses.forEach(function(cls) {\n if (node.classList && node.classList.contains(cls)) {\n extractQuizResult(node);\n }\n var results = node.querySelectorAll ? node.querySelectorAll('.' + cls) : [];\n results.forEach(function(resultNode) {\n extractQuizResult(resultNode);\n });\n });\n\n var startClasses = ['quiz-start', 'knowledge-check', 'assessment-intro', 'blocks-quiz'];\n startClasses.forEach(function(cls) {\n if (node.classList && node.classList.contains(cls)) {\n LRS.assessmentStarted({\n assessmentId: extractAssessmentId(node)\n });\n }\n });\n }\n\n function extractAssessmentId(node) {\n return node.getAttribute('data-block-id') ||\n node.getAttribute('data-assessment-id') ||\n node.getAttribute('id') ||\n 'quiz-' + Date.now();\n }\n\n /**\n * Extract quiz/assessment name from the DOM\n */\n function extractAssessmentName(node) {\n // Try various selectors for assessment/quiz titles\n var titleEl = node.querySelector(\n '.quiz-title, .assessment-title, .knowledge-check-title, ' +\n '.blocks-quiz__title, .block-title, ' +\n '[class*=\"quiz\"] [class*=\"title\"], ' +\n '[class*=\"assessment\"] [class*=\"title\"], ' +\n 'h2, h3'\n );\n if (titleEl) {\n return titleEl.textContent.trim().substring(0, 200);\n }\n\n // Try to get from parent block\n var parentBlock = node.closest('[data-block-type]');\n if (parentBlock) {\n var blockTitle = parentBlock.querySelector('.block-title, h2, h3');\n if (blockTitle) {\n return blockTitle.textContent.trim().substring(0, 200);\n }\n }\n\n // Get current lesson name as context\n var lessonInfo = getCachedLessonInfo();\n if (lessonInfo.name) {\n return 'Quiz in ' + lessonInfo.name;\n }\n\n return 'Knowledge Check';\n }\n\n function extractQuizResult(node) {\n var scoreEl = node.querySelector('.score-value, .quiz-score, .score-percentage, [class*=\"score\"]');\n var score = scoreEl ? parseFloat(scoreEl.textContent.replace(/[^0-9.]/g, '')) : null;\n\n // Get assessment name and lesson context\n var assessmentName = extractAssessmentName(node);\n var lessonInfo = getCachedLessonInfo();\n\n // Find all question containers - Rise uses various structures\n var questions = node.querySelectorAll(\n '.question-result, .question-feedback, .question-item, ' +\n '.blocks-quiz__question, .quiz-question, ' +\n '[class*=\"question-\"][class*=\"result\"], ' +\n '[class*=\"question-\"][class*=\"feedback\"], ' +\n '[data-question-id]'\n );\n\n log('Found', questions.length, 'question elements in quiz result');\n\n if (questions.length > 0) {\n questions.forEach(function(q, index) {\n // Determine if correct - check multiple indicators\n var isCorrect = q.classList.contains('correct') ||\n q.classList.contains('is-correct') ||\n q.querySelector('.correct, .is-correct, [class*=\"correct\"]') !== null ||\n q.getAttribute('data-correct') === 'true' ||\n q.getAttribute('data-result') === 'correct';\n\n // Get the question text - try multiple selectors\n var questionText = '';\n var questionEl = q.querySelector(\n '.question-text, .question-stem, .question-title, ' +\n '.blocks-quiz__question-text, .quiz-question__text, ' +\n '[class*=\"question-text\"], [class*=\"question-stem\"], ' +\n '[class*=\"prompt\"], p:first-of-type'\n );\n if (questionEl) {\n questionText = questionEl.textContent.trim();\n }\n\n // Get the learner's selected answer text\n var answerText = '';\n var answerEl = q.querySelector(\n '.selected-answer, .learner-response, .answer-text, ' +\n '.user-answer, .chosen-answer, .response-text, ' +\n '.blocks-quiz__response, .quiz-question__response, ' +\n '[class*=\"selected\"], [class*=\"chosen\"], [class*=\"response\"], ' +\n '[class*=\"user-answer\"]'\n );\n if (answerEl) {\n answerText = answerEl.textContent.trim();\n }\n\n // If no specific answer element, try to find selected radio/checkbox label\n if (!answerText) {\n var selectedInput = q.querySelector('input:checked, [aria-checked=\"true\"]');\n if (selectedInput) {\n var label = q.querySelector('label[for=\"' + selectedInput.id + '\"]');\n if (label) {\n answerText = label.textContent.trim();\n } else {\n var parentLabel = selectedInput.closest('label');\n if (parentLabel) {\n answerText = parentLabel.textContent.trim();\n }\n }\n }\n }\n\n // Get question ID if available\n var questionId = q.getAttribute('data-question-id') ||\n q.getAttribute('data-block-id') ||\n q.getAttribute('id') ||\n 'q' + (index + 1);\n\n // Get correct answer text if available (for reporting)\n var correctAnswerText = '';\n var correctEl = q.querySelector(\n '.correct-answer, .right-answer, ' +\n '[class*=\"correct-answer\"], [class*=\"right-answer\"]'\n );\n if (correctEl) {\n correctAnswerText = correctEl.textContent.trim();\n }\n\n log('Question', index + 1, ':', {\n questionText: questionText.substring(0, 50) + '...',\n answerText: answerText.substring(0, 50) + '...',\n isCorrect: isCorrect\n });\n\n LRS.questionAnswered({\n questionId: questionId,\n questionNumber: index + 1,\n questionText: questionText.substring(0, 500),\n answer: answerText.substring(0, 500),\n correctAnswer: correctAnswerText.substring(0, 500),\n result: isCorrect ? 'correct' : 'incorrect',\n assessmentName: assessmentName,\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName\n });\n });\n }\n\n LRS.assessmentEnded({\n assessmentId: extractAssessmentId(node),\n assessmentName: assessmentName,\n score: score,\n questionCount: questions.length,\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName\n });\n }\n\n // Track submitted Knowledge Check questions to avoid duplicates\n var submittedKnowledgeChecks = {};\n var kcQuestionCounter = 0;\n\n /**\n * Set up interceptors specifically for Rise Knowledge Check blocks\n * These blocks have a different DOM structure than standard quiz results\n */\n function setupKnowledgeCheckInterceptors() {\n if (!TRACK_QUIZZES) return;\n\n // Intercept submit button clicks on Knowledge Check blocks\n // NOTE: If the SCORM tracker is active, it handles quiz tracking via\n // cmi.interactions — skip DOM scraping to avoid duplicate statements\n document.addEventListener('click', function(e) {\n if (scormTrackerActive) return; // SCORM tracker handles this\n\n var submitBtn = e.target.closest('.quiz-card__button');\n if (!submitBtn) return;\n\n // Find the Knowledge Check block\n var kcBlock = submitBtn.closest('[data-test-id=\"block-kc-card\"]') ||\n submitBtn.closest('.block-knowledge');\n if (!kcBlock) return;\n\n // Wait for feedback to appear after submission\n setTimeout(function() {\n extractKnowledgeCheckResult(kcBlock);\n }, 500);\n }, true);\n\n log('Knowledge Check interceptors set up');\n }\n\n /**\n * Extract and send xAPI statement for a Knowledge Check submission\n */\n function extractKnowledgeCheckResult(kcBlock) {\n // Get block ID for context\n var blockContainer = kcBlock.closest('[data-block-id]');\n var blockId = blockContainer ? blockContainer.getAttribute('data-block-id') : null;\n\n // Get question text first — needed for per-question dedup key\n var questionText = '';\n var questionTextEl = kcBlock.querySelector('.quiz-card__title .fr-view, .quiz-card__title');\n if (questionTextEl) {\n questionText = questionTextEl.textContent.trim();\n }\n\n // Get question ID from the title element\n var questionTitleEl = kcBlock.querySelector('.quiz-card__title');\n var questionId = questionTitleEl ? questionTitleEl.id : null;\n\n // Build a question-specific dedup key using question text hash\n // This ensures each question in a multi-question quiz block gets its own key\n var questionHash = questionText ? questionText.substring(0, 100) : (questionId || blockId || generateUUID());\n var submissionKey = 'kc-' + questionHash;\n\n var feedbackLabel = kcBlock.querySelector('.quiz-card__feedback-label');\n if (!feedbackLabel) {\n log('Knowledge Check: No feedback visible yet');\n return;\n }\n\n var feedbackText = feedbackLabel.textContent.trim().toLowerCase();\n var submissionId = submissionKey + '-' + feedbackText;\n\n if (submittedKnowledgeChecks[submissionId]) {\n log('Knowledge Check: Already processed this submission');\n return;\n }\n submittedKnowledgeChecks[submissionId] = true;\n\n // Use question-specific ID for the statement (not the shared block ID)\n if (!questionId) {\n questionId = blockId ? 'q-' + blockId + '-' + questionHash.substring(0, 20) : 'q-' + generateUUID();\n }\n\n // Determine question type from aria-label\n var wrapper = kcBlock.querySelector('[data-test-id=\"block-knowledge-wrapper\"]');\n var ariaLabel = wrapper ? wrapper.getAttribute('aria-label') : '';\n var questionType = 'unknown';\n if (ariaLabel.indexOf('Multiple choice') > -1) questionType = 'multiple-choice';\n else if (ariaLabel.indexOf('Multiple response') > -1) questionType = 'multiple-response';\n else if (ariaLabel.indexOf('Fill in the blank') > -1) questionType = 'fill-in-blank';\n else if (ariaLabel.indexOf('Matching') > -1) questionType = 'matching';\n\n // Get selected answer(s) based on question type\n var answerText = extractKnowledgeCheckAnswer(kcBlock, questionType);\n\n // Get correct/incorrect from feedback\n var isCorrect = feedbackText === 'correct';\n\n // Get lesson context\n var lessonInfo = getCachedLessonInfo();\n\n log('Knowledge Check submitted:', {\n questionId: questionId,\n questionText: questionText.substring(0, 50) + '...',\n questionType: questionType,\n answer: answerText.substring(0, 50) + '...',\n correct: isCorrect\n });\n\n // Increment question counter for this session\n kcQuestionCounter++;\n\n // Send question answered statement using existing LRS method\n LRS.questionAnswered({\n questionId: questionId,\n questionGuid: blockId || generateUUID(),\n questionNumber: kcQuestionCounter,\n questionText: questionText.substring(0, 500),\n questionType: questionType,\n answer: answerText.substring(0, 500),\n correct: isCorrect,\n result: isCorrect ? 'correct' : 'incorrect',\n assessmentName: 'Knowledge Check',\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName\n });\n }\n\n /**\n * Extract the selected answer text from a Knowledge Check block\n * based on the question type\n */\n function extractKnowledgeCheckAnswer(kcBlock, questionType) {\n var answerText = '';\n\n if (questionType === 'multiple-choice') {\n // Find checked radio button\n var checkedInput = kcBlock.querySelector('.quiz-multiple-choice-option__input:checked');\n if (checkedInput) {\n var label = checkedInput.closest('.quiz-multiple-choice-option');\n var textEl = label ? label.querySelector('.quiz-multiple-choice-option__label .fr-view, .quiz-multiple-choice-option__label') : null;\n answerText = textEl ? textEl.textContent.trim() : '';\n }\n }\n else if (questionType === 'multiple-response') {\n // Find all checked checkboxes\n var checkedInputs = kcBlock.querySelectorAll('.quiz-multiple-response-option__input:checked');\n var answers = [];\n checkedInputs.forEach(function(input) {\n var label = input.closest('.quiz-multiple-response-option');\n var textEl = label ? label.querySelector('.quiz-multiple-response-option__text .fr-view, .quiz-multiple-response-option__text') : null;\n if (textEl) answers.push(textEl.textContent.trim());\n });\n answerText = answers.join('; ');\n }\n else if (questionType === 'fill-in-blank') {\n // Get text input value\n var textInput = kcBlock.querySelector('.quiz-fill__input');\n answerText = textInput ? textInput.value.trim() : '';\n }\n else if (questionType === 'matching') {\n // Extract matching pairs from the drop zones\n var dropZones = kcBlock.querySelectorAll('.matching-drop-zone');\n var pairs = [];\n dropZones.forEach(function(zone) {\n var prompt = zone.querySelector('.matching-prompt-content');\n var response = zone.querySelector('.matching-interaction-piece-content');\n if (prompt && response) {\n pairs.push(prompt.textContent.trim() + ' → ' + response.textContent.trim());\n }\n });\n answerText = pairs.length > 0 ? pairs.join('; ') : 'Matching submitted';\n }\n\n return answerText;\n }\n\n // ========================================================================\n // SCORM INTERACTION TRACKER\n // Intercepts cmi.interactions.N.* SetValue calls to capture quiz answers\n // directly from the SCORM data model — works regardless of Rise UI format\n // (Knowledge Check blocks, quiz lessons, etc.)\n //\n // IMPORTANT: This finds and wraps the ACTUAL SCORM API that Rise uses\n // (window.API, parent.API, or global functions), NOT the bridge's copy.\n // It runs unconditionally at init, not gated by actor resolution.\n // ========================================================================\n var scormInteractions = {}; // Pending interactions keyed by index N\n var scormInteractionsSent = {}; // Track which interactions were already sent\n var scormTrackerActive = false; // Set true when SCORM tracker wraps SetValue — KC handler defers\n var scormCourseData = {}; // Accumulates course-level SCORM data (score, status)\n var scormCompletionDebounce = null; // Debounce timer — prevents duplicate fires within same completion event\n\n function interceptScormSetValue(key, value) {\n if (typeof key !== 'string') return;\n\n // ---- Course-level SCORM data (score, status, completion) ----\n // SCORM 1.2\n if (key === 'cmi.core.score.raw') { scormCourseData.scoreRaw = parseFloat(value); }\n if (key === 'cmi.core.score.max') { scormCourseData.scoreMax = parseFloat(value); }\n if (key === 'cmi.core.score.min') { scormCourseData.scoreMin = parseFloat(value); }\n if (key === 'cmi.core.lesson_status') { scormCourseData.status = String(value).toLowerCase(); }\n // SCORM 2004\n if (key === 'cmi.score.raw') { scormCourseData.scoreRaw = parseFloat(value); }\n if (key === 'cmi.score.max') { scormCourseData.scoreMax = parseFloat(value); }\n if (key === 'cmi.score.min') { scormCourseData.scoreMin = parseFloat(value); }\n if (key === 'cmi.score.scaled') { scormCourseData.scoreScaled = parseFloat(value); }\n if (key === 'cmi.completion_status') { scormCourseData.completionStatus = String(value).toLowerCase(); }\n if (key === 'cmi.success_status') { scormCourseData.successStatus = String(value).toLowerCase(); }\n\n // Fire course completion statement when status indicates pass/fail/complete\n // Uses debounce (not one-time flag) so retries/new attempts are captured\n if (key === 'cmi.core.lesson_status' || key === 'cmi.success_status' || key === 'cmi.completion_status') {\n var status = String(value).toLowerCase();\n if (status === 'passed' || status === 'failed' || status === 'completed') {\n if (scormCompletionDebounce) clearTimeout(scormCompletionDebounce);\n scormCompletionDebounce = setTimeout(function() {\n scormCompletionDebounce = null;\n sendCourseCompletionStatement(status);\n }, 500);\n }\n }\n\n // ---- Interaction-level tracking (existing) ----\n var interactionPattern = /^cmi\\\\.interactions\\\\.(\\\\d+)\\\\.(.+)$/;\n var match = key.match(interactionPattern);\n if (!match) return;\n\n var idx = match[1];\n var field = match[2];\n\n // Initialize interaction tracking for this index\n if (!scormInteractions[idx]) {\n scormInteractions[idx] = {};\n }\n\n // Store the field value\n scormInteractions[idx][field] = String(value);\n\n log('SCORM Interaction [' + idx + '].' + field + ' = ' + String(value).substring(0, 100));\n\n // When 'result' is set, the interaction is complete — fire xAPI statement\n if (field === 'result' && !scormInteractionsSent[idx]) {\n scormInteractionsSent[idx] = true;\n var interaction = scormInteractions[idx];\n\n // Map SCORM interaction type to readable type\n var typeMap = {\n 'choice': 'multiple-choice',\n 'true-false': 'true-false',\n 'fill-in': 'fill-in-blank',\n 'matching': 'matching',\n 'performance': 'performance',\n 'sequencing': 'sequencing',\n 'likert': 'likert',\n 'numeric': 'numeric'\n };\n\n var scormType = interaction.type || 'unknown';\n var questionType = typeMap[scormType] || scormType;\n\n // Determine correctness from SCORM result value\n // SCORM 1.2: 'correct', 'wrong', 'unanticipated', 'neutral'\n // SCORM 2004: 'correct', 'incorrect', 'unanticipated', 'neutral'\n var isCorrect = String(value) === 'correct';\n\n // Get student response\n var studentResponse = interaction.student_response || '';\n\n // Get correct response pattern\n var correctResponse = '';\n Object.keys(interaction).forEach(function(k) {\n if (k.indexOf('correct_responses') > -1 && k.indexOf('pattern') > -1) {\n correctResponse = interaction[k];\n }\n });\n\n // Rise interaction IDs encode the question text (underscored)\n // e.g. \"Development_Week_Qui_I_want_a_clear_picture_of_...\"\n var interactionId = interaction.id || ('interaction-' + idx);\n\n // Try to make a readable question text from the interaction ID\n var questionText = decodeEntities(\n interactionId\n .replace(/_\\\\d+$/, '') // remove trailing _0\n .replace(/_/g, ' ') // underscores to spaces\n .substring(0, 200)\n );\n\n // Get lesson context\n var lessonInfo = getCachedLessonInfo();\n\n // Increment question counter\n kcQuestionCounter++;\n\n log('SCORM Interaction complete [' + idx + ']:', {\n questionNumber: kcQuestionCounter,\n questionText: questionText.substring(0, 60) + '...',\n type: questionType,\n studentResponse: studentResponse,\n correctResponse: correctResponse,\n result: String(value),\n correct: isCorrect\n });\n\n // Send xAPI answered statement\n LRS.questionAnswered({\n questionId: interactionId,\n questionGuid: interactionId,\n questionNumber: kcQuestionCounter,\n questionText: questionText,\n questionType: questionType,\n answer: studentResponse,\n correctAnswer: correctResponse,\n correct: isCorrect,\n result: isCorrect ? 'correct' : 'incorrect',\n assessmentName: lessonInfo.name || 'Quiz',\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName\n });\n }\n }\n\n function sendCourseCompletionStatement(status) {\n logGroup('Course Completion');\n log('Status:', status);\n log('SCORM course data:', scormCourseData);\n\n var courseTitle = (LRS.courseInfo && LRS.courseInfo.title) ? decodeEntities(LRS.courseInfo.title) : decodeEntities(document.title) || 'Course';\n\n // Build result with score\n var result = { completion: true };\n\n var raw = scormCourseData.scoreRaw;\n var max = scormCourseData.scoreMax || 100;\n var min = scormCourseData.scoreMin || 0;\n\n if (typeof raw === 'number' && !isNaN(raw)) {\n result.score = {\n raw: raw,\n max: max,\n min: min,\n scaled: scormCourseData.scoreScaled != null ? scormCourseData.scoreScaled : (max > min ? (raw - min) / (max - min) : 0)\n };\n }\n\n result.success = (status === 'passed');\n\n var activityDetails = {\n courseTitle: courseTitle,\n completionStatus: status,\n employeeEmail: LRS.actor && LRS.actor.mbox ? LRS.actor.mbox.replace('mailto:', '') : undefined,\n employeeId: LRS.employeeId || undefined\n };\n\n // 1. Send \"completed\" statement (course was finished)\n log('Sending completed statement, score:', result.score, 'title:', courseTitle);\n var completedStatement = buildStatement('completed', ACTIVITY_TYPES.course, activityDetails, result, null);\n sendStatement(completedStatement);\n\n // 2. Send \"passed\" or \"failed\" statement (assessment outcome)\n if (status === 'passed' || status === 'failed') {\n log('Sending', status, 'statement');\n var outcomeStatement = buildStatement(status, ACTIVITY_TYPES.course, activityDetails, result, null);\n sendStatement(outcomeStatement);\n }\n\n logGroupEnd();\n }\n\n function setupScormInteractionTracker() {\n if (!TRACK_QUIZZES) return;\n\n // Find the ACTUAL SCORM API that Rise uses — NOT our bridge's copy.\n // Rise discovers the API via standard SCORM lookup (window.API, parent chain).\n // The Bravais CDS player's ProxyApi.injectLmsApi() sets this up.\n var wrapped = false;\n\n // Try 1: window.API (SCORM 1.2) or window.API_1484_11 (SCORM 2004)\n try {\n if (window.API && typeof window.API.LMSSetValue === 'function') {\n var origSetValue = window.API.LMSSetValue;\n window.API.LMSSetValue = function(key, value) {\n var result = origSetValue.apply(window.API, arguments);\n interceptScormSetValue(key, value);\n return result;\n };\n wrapped = true;\n log('SCORM Interaction Tracker: Wrapped window.API.LMSSetValue');\n }\n } catch (e) { log('SCORM Tracker: Cannot access window.API:', e.message); }\n\n if (!wrapped) {\n try {\n if (window.API_1484_11 && typeof window.API_1484_11.SetValue === 'function') {\n var origSetValue2004 = window.API_1484_11.SetValue;\n window.API_1484_11.SetValue = function(key, value) {\n var result = origSetValue2004.apply(window.API_1484_11, arguments);\n interceptScormSetValue(key, value);\n return result;\n };\n wrapped = true;\n log('SCORM Interaction Tracker: Wrapped window.API_1484_11.SetValue');\n }\n } catch (e) { log('SCORM Tracker: Cannot access window.API_1484_11:', e.message); }\n }\n\n // Try 2: Parent frame API\n if (!wrapped) {\n try {\n if (window.parent && window.parent !== window) {\n if (window.parent.API && typeof window.parent.API.LMSSetValue === 'function') {\n var origParentSetValue = window.parent.API.LMSSetValue;\n window.parent.API.LMSSetValue = function(key, value) {\n var result = origParentSetValue.apply(window.parent.API, arguments);\n interceptScormSetValue(key, value);\n return result;\n };\n wrapped = true;\n log('SCORM Interaction Tracker: Wrapped window.parent.API.LMSSetValue');\n }\n }\n } catch (e) { log('SCORM Tracker: Cannot access parent API (cross-origin)'); }\n }\n\n // Try 3: Global LMS functions (Bravais/Xyleme mock API)\n if (!wrapped) {\n try {\n if (typeof window.LMSSetValue === 'function') {\n var origGlobalSetValue = window.LMSSetValue;\n window.LMSSetValue = function(key, value) {\n var result = origGlobalSetValue.apply(window, arguments);\n interceptScormSetValue(key, value);\n return result;\n };\n wrapped = true;\n log('SCORM Interaction Tracker: Wrapped window.LMSSetValue (global)');\n }\n } catch (e) { log('SCORM Tracker: Cannot wrap global LMSSetValue:', e.message); }\n }\n\n // Try 4: Fall back to bridge's copy (least likely to work but worth trying)\n if (!wrapped && LRS.scormApi) {\n var setValueFn = LRS.scormApiType === '2004' ? 'SetValue' : 'LMSSetValue';\n if (typeof LRS.scormApi[setValueFn] === 'function') {\n var origBridgeSetValue = LRS.scormApi[setValueFn];\n LRS.scormApi[setValueFn] = function(key, value) {\n var result = origBridgeSetValue.apply(LRS.scormApi, arguments);\n interceptScormSetValue(key, value);\n return result;\n };\n wrapped = true;\n log('SCORM Interaction Tracker: Wrapped LRS.scormApi.' + setValueFn + ' (bridge copy)');\n }\n }\n\n if (wrapped) {\n scormTrackerActive = true;\n log('SCORM Interaction Tracker active — KC DOM handler will defer');\n } else {\n // API not available yet — retry in 2 seconds (Bravais proxy may not be ready)\n log('SCORM Interaction Tracker: No SCORM API found yet, retrying in 2s...');\n setTimeout(function() {\n if (!scormTrackerActive) {\n setupScormInteractionTracker();\n }\n }, 2000);\n }\n }\n\n function setupInteractionInterceptors() {\n if (!TRACK_INTERACTIONS) return;\n\n document.addEventListener('click', function(e) {\n var target = e.target;\n\n // Tabs\n var tabTrigger = findClosest(target, '[data-block-type=\"tabs\"] .tab-item, .tabs__tab, .tab-trigger, [role=\"tab\"]');\n if (tabTrigger) {\n LRS.interacted({\n type: 'tab',\n id: tabTrigger.getAttribute('data-tab-id') ||\n tabTrigger.getAttribute('aria-controls') ||\n tabTrigger.textContent.trim().substring(0, 50),\n name: 'Tab: ' + (tabTrigger.textContent.trim().substring(0, 30) || 'Tab')\n });\n return;\n }\n\n // Accordions\n var accordionTrigger = findClosest(target, '[data-block-type=\"accordion\"] .accordion-trigger, .accordion__header, .accordion-item__header, [aria-expanded]');\n if (accordionTrigger && accordionTrigger.getAttribute('aria-expanded')) {\n LRS.interacted({\n type: 'accordion',\n id: accordionTrigger.getAttribute('data-accordion-id') ||\n accordionTrigger.getAttribute('aria-controls') ||\n accordionTrigger.textContent.trim().substring(0, 50),\n name: 'Accordion: ' + (accordionTrigger.textContent.trim().substring(0, 30) || 'Section')\n });\n return;\n }\n\n // Flashcards\n var flashcard = findClosest(target, '.flashcard, [data-block-type=\"flashcard\"], .blocks-flashcard');\n if (flashcard) {\n LRS.interacted({\n type: 'flashcard',\n id: flashcard.getAttribute('data-card-id') || 'flashcard-' + Date.now(),\n name: 'Flashcard'\n });\n return;\n }\n\n // Process/Timeline\n var processItem = findClosest(target, '.process-item, .timeline-item, [data-block-type=\"process\"] .step, .blocks-process__item');\n if (processItem) {\n LRS.interacted({\n type: 'process-step',\n id: processItem.getAttribute('data-step-id') ||\n processItem.getAttribute('data-index') ||\n 'step-' + Date.now(),\n name: 'Process Step'\n });\n return;\n }\n\n // Hotspots/Markers\n var hotspot = findClosest(target, '.hotspot-marker, .marker-item, [data-block-type=\"labeled-graphic\"] .marker, .blocks-labeled-graphic__marker');\n if (hotspot) {\n LRS.interacted({\n type: 'hotspot',\n id: hotspot.getAttribute('data-marker-id') ||\n hotspot.getAttribute('data-index') ||\n 'hotspot-' + Date.now(),\n name: 'Hotspot'\n });\n return;\n }\n\n // Sorting activities\n var sortItem = findClosest(target, '.sort-item, [data-block-type=\"sorting\"] .item, .blocks-sorting__item');\n if (sortItem) {\n LRS.interacted({\n type: 'sorting',\n id: sortItem.getAttribute('data-item-id') || 'sort-item',\n name: 'Sorting Activity'\n });\n return;\n }\n\n // Buttons / Continue\n var button = findClosest(target, '.continue-button, .nav-button, [data-block-type=\"button\"], .blocks-button');\n if (button) {\n LRS.interacted({\n type: 'button',\n id: button.getAttribute('data-button-id') || 'button',\n name: 'Button: ' + (button.textContent.trim().substring(0, 30) || 'Continue')\n });\n }\n\n }, true);\n\n log('Interaction interceptors set up');\n }\n\n function findClosest(el, selector) {\n if (!el || !selector) return null;\n if (el.closest) {\n return el.closest(selector);\n }\n var current = el;\n while (current && current !== document) {\n if (matchesSelector(current, selector)) {\n return current;\n }\n current = current.parentElement;\n }\n return null;\n }\n\n function matchesSelector(el, selector) {\n var fn = el.matches || el.webkitMatchesSelector || el.msMatchesSelector;\n return fn ? fn.call(el, selector) : false;\n }\n\n // ========================================================================\n // 9. INITIALIZATION\n // ========================================================================\n\n function init() {\n log('Initializing LRS bridge v2.6.0...');\n\n // Extract course info early\n extractCourseInfo();\n\n // Setup connection\n var bridgeReady = setupBridge();\n\n // Track async readiness: both actor and doc metadata must resolve before sending statements\n var actorReady = false;\n var docReady = false;\n var launchEventsSent = false;\n\n function tryLaunchEvents() {\n if (launchEventsSent) return;\n if (!actorReady || !docReady) return;\n launchEventsSent = true;\n log('Both actor and doc metadata ready, sending launch events');\n setTimeout(sendLaunchEvents, 50);\n }\n\n if (bridgeReady) {\n LRS.initialized = true;\n\n // Extract actor (sync first for immediate use)\n extractActor();\n\n log('Bridge initialized in mode:', LRS.mode);\n log('Actor:', LRS.actor);\n log('Course:', LRS.courseInfo);\n\n // Then try async actor extraction (employee lookup, Bravais session)\n // Launch events will NOT be sent until this completes\n extractActorAsync(function(actor) {\n log('Actor updated after async fetch:', actor);\n\n // Check if actor is still \"Unknown Learner\" - SCORM API might not be available yet\n // The SCORM API is often injected by scorm-calls.js AFTER <head> init completes\n var isUnknown = !actor || actor.name === 'Unknown Learner' ||\n !actor.account || actor.account.name === 'unknown';\n\n if (isUnknown) {\n log('Actor is Unknown Learner - SCORM API likely not available yet, polling for it...');\n var scormPollCount = 0;\n var scormMaxPolls = 30; // wait up to 30 seconds\n var scormResolved = false;\n\n // Helper: search all possible locations for SCORM API\n function findScormApiAnywhere() {\n // 1. Search parent frame hierarchy (standard approach)\n var api2004 = findAPIInFrameHierarchy('API_1484_11', 10);\n if (api2004) return { api: api2004.api, type: '2004', source: 'parent-chain level ' + api2004.level };\n var api12 = findAPIInFrameHierarchy('API', 10);\n if (api12) return { api: api12.api, type: '1.2', source: 'parent-chain level ' + api12.level };\n\n // 2. Check window.opener and its parent hierarchy (LMS opens content in popup)\n try {\n if (window.opener && window.opener !== window) {\n // Direct check on opener\n if (window.opener.API_1484_11) return { api: window.opener.API_1484_11, type: '2004', source: 'window.opener' };\n if (window.opener.API) return { api: window.opener.API, type: '1.2', source: 'window.opener' };\n // Check opener's parent chain\n var openerWin = window.opener;\n var openerLevel = 0;\n while (openerLevel < 10) {\n try {\n if (openerWin.API_1484_11) return { api: openerWin.API_1484_11, type: '2004', source: 'opener-chain level ' + openerLevel };\n if (openerWin.API) return { api: openerWin.API, type: '1.2', source: 'opener-chain level ' + openerLevel };\n if (openerWin.parent && openerWin.parent !== openerWin) { openerWin = openerWin.parent; openerLevel++; }\n else break;\n } catch(e) { break; }\n }\n }\n } catch(e) { /* cross-origin opener */ }\n\n // 3. Check for global LMS functions (Bravais/Xyleme mock API)\n try {\n if (typeof window.LMSInitialize === 'function') {\n return { api: { LMSInitialize: window.LMSInitialize, LMSGetValue: window.LMSGetValue, LMSSetValue: window.LMSSetValue, LMSCommit: window.LMSCommit, LMSFinish: window.LMSFinish, LMSGetLastError: window.LMSGetLastError, LMSGetErrorString: window.LMSGetErrorString }, type: '1.2', source: 'global-functions' };\n }\n } catch(e) {}\n try {\n if (window.parent && window.parent !== window && typeof window.parent.LMSInitialize === 'function') {\n return { api: { LMSInitialize: window.parent.LMSInitialize, LMSGetValue: window.parent.LMSGetValue, LMSSetValue: window.parent.LMSSetValue, LMSCommit: window.parent.LMSCommit, LMSFinish: window.parent.LMSFinish, LMSGetLastError: window.parent.LMSGetLastError, LMSGetErrorString: window.parent.LMSGetErrorString }, type: '1.2', source: 'parent-global-functions' };\n }\n } catch(e) {}\n\n return null;\n }\n\n function onScormApiFound(result, pollNum) {\n if (scormResolved) return;\n scormResolved = true;\n\n LRS.scormApi = result.api;\n LRS.scormApiFound = true;\n LRS.scormApiType = result.type;\n\n // Read learner_id for diagnostics\n var scormLearnerId = 'n/a', scormLearnerName = 'n/a';\n try {\n if (result.type === '2004') {\n scormLearnerId = result.api.GetValue('cmi.learner_id');\n scormLearnerName = result.api.GetValue('cmi.learner_name');\n } else {\n scormLearnerId = result.api.LMSGetValue('cmi.core.student_id');\n scormLearnerName = result.api.LMSGetValue('cmi.core.student_name');\n }\n } catch(e) { scormLearnerId = 'error'; scormLearnerName = 'error'; }\n console.info('[PA-LRS] SCORM API found (poll ' + pollNum + ', source=' + result.source +\n ', type=' + result.type + '): learner_id=' + scormLearnerId + ', learner_name=' + scormLearnerName);\n\n // refreshActor re-reads learner_id from SCORM and runs employee lookup\n LRS.refreshActor(function(refreshedActor) {\n if (window.console && window.console.info) {\n console.info('[PA-LRS] Actor resolved (after SCORM wait, poll ' + pollNum + '): name=' +\n (refreshedActor ? refreshedActor.name : 'none') +\n ', mbox=' + (refreshedActor ? refreshedActor.mbox : 'none') +\n ', account=' + (refreshedActor && refreshedActor.account ? refreshedActor.account.name : 'none'));\n }\n actorReady = true;\n tryLaunchEvents();\n });\n }\n\n var scormPollTimer = setInterval(function() {\n if (scormResolved) { clearInterval(scormPollTimer); return; }\n scormPollCount++;\n\n var result = findScormApiAnywhere();\n\n if (result) {\n clearInterval(scormPollTimer);\n onScormApiFound(result, scormPollCount);\n } else if (scormPollCount >= scormMaxPolls) {\n clearInterval(scormPollTimer);\n log('SCORM API not found after ' + scormMaxPolls + 's, proceeding with Unknown Learner');\n if (window.console && window.console.info) {\n console.info('[PA-LRS] Actor resolved (timeout ' + scormMaxPolls + 's): name=' + (actor ? actor.name : 'none') +\n ', account=' + (actor && actor.account ? actor.account.name : 'none'));\n }\n actorReady = true;\n tryLaunchEvents();\n }\n }, 1000);\n } else {\n // Actor is valid - proceed immediately\n if (window.console && window.console.info) {\n console.info('[PA-LRS] Actor resolved: name=' + (actor ? actor.name : 'none') +\n ', mbox=' + (actor ? actor.mbox : 'none') +\n ', account=' + (actor && actor.account ? actor.account.name : 'none'));\n }\n actorReady = true;\n tryLaunchEvents();\n }\n });\n } else {\n warn('Bridge setup failed - operating in offline mode');\n LRS.mode = 'offline';\n actorReady = true; // No actor to wait for in offline mode\n }\n\n // Always-visible bridge summary (not gated by DEBUG)\n // This ensures diagnostics are available even in production builds\n if (window.console && window.console.info) {\n console.info('[PA-LRS] Bridge v' + LRS.version + ': mode=' + LRS.mode +\n ', endpoint=' + (LRS_ENDPOINT ? LRS_ENDPOINT.substring(0, 30) + '...' : 'NONE') +\n ', proxy=' + (LRS_PROXY_ENDPOINT ? 'yes' : 'no') +\n ', docGuid=' + (DOCUMENT_GUID || 'NONE') +\n ', verGuid=' + (VERSION_GUID || 'NONE') +\n ', actor=' + (LRS.actor ? (LRS.actor.name || 'anonymous') : 'none') +\n ', hasRefreshActor=' + (typeof LRS.refreshActor === 'function'));\n }\n\n // Set up event interceptors\n setupMediaInterceptors();\n setupNavigationInterceptors();\n setupQuizInterceptors();\n setupInteractionInterceptors();\n\n // Intercept SCORM cmi.interactions to capture quiz answers as xAPI statements\n // This wraps the ACTUAL SCORM API (window.API etc.) that Rise calls\n setupScormInteractionTracker();\n\n // Fetch document metadata from API to get GUIDs (async)\n // Then send course launched event\n var sharedLinkToken = LRS.courseInfo ? LRS.courseInfo.sharedLinkToken : null;\n var documentId = LRS.courseInfo ? LRS.courseInfo.documentId : null;\n\n function sendLaunchEvents() {\n // Log the course info state before sending statements\n log('Sending launch events with course info:', {\n id: LRS.courseInfo ? LRS.courseInfo.id : null,\n guid: LRS.courseInfo ? LRS.courseInfo.guid : null,\n versionGuid: LRS.courseInfo ? LRS.courseInfo.versionGuid : null,\n documentId: LRS.courseInfo ? LRS.courseInfo.documentId : null\n });\n\n if (TRACK_NAVIGATION) {\n LRS.courseLaunched();\n LRS.contentOpened({\n lessonId: window.location.hash || '#/',\n url: window.location.href,\n action: 'launched'\n });\n }\n }\n\n function onDocReady() {\n docReady = true;\n tryLaunchEvents();\n }\n\n // After Bravais API metadata resolves, try our authenticated server as a\n // final fallback to get the document name for packageName.\n // This handles cases where PARAMS is cross-origin blocked and no baked name exists.\n function maybeEnrichAndReady() {\n if (LRS.courseInfo.packageName || !LRS.courseInfo.documentId || !LRS_PROXY_ENDPOINT) {\n onDocReady();\n return;\n }\n log('No packageName yet, trying server-side document lookup for ID:', LRS.courseInfo.documentId);\n fetchDocumentNameFromServer(LRS.courseInfo.documentId, function(name) {\n if (name) {\n LRS.courseInfo.packageName = name;\n log('Set packageName from server lookup:', name);\n }\n onDocReady();\n });\n }\n\n function fetchDocDataAndReady(docId) {\n fetchDocumentMetadata(docId, function(docData) {\n if (docData) {\n updateCourseInfoFromApi(docData);\n }\n maybeEnrichAndReady();\n });\n }\n\n if (!LRS.courseInfo.guid) {\n // When we have a shared link token, use /api/shared/{token}/documents directly\n // This endpoint does NOT require authentication and returns both document GUID and version GUID\n if (sharedLinkToken) {\n log('Have shared link token, fetching document data via shared API...');\n // fetchDocumentMetadata will use /api/shared/{token}/documents when sharedLinkToken is present\n fetchDocumentMetadata(null, function(docData) {\n if (docData) {\n updateCourseInfoFromApi(docData);\n // Also update document name if available\n if (docData.name) {\n LRS.courseInfo.sharedLinkName = docData.name;\n }\n } else {\n warn('Could not fetch document data from shared API - statements may fail aggregation');\n }\n maybeEnrichAndReady();\n });\n } else if (documentId) {\n // No shared link token, try with extracted document ID (requires auth)\n log('No shared link token, fetching document data with ID:', documentId);\n fetchDocDataAndReady(documentId);\n } else {\n // No identifiers available\n warn('No shared link token or document ID - statements may fail aggregation');\n maybeEnrichAndReady();\n }\n } else {\n // Already have GUID — still try to enrich packageName if missing\n maybeEnrichAndReady();\n }\n\n // Setup beforeunload to send terminated\n window.addEventListener('beforeunload', function() {\n LRS.courseTerminated();\n });\n\n // Retry queue every 30 seconds\n setInterval(processStatementQueue, 30000);\n\n // Retry bridge connection if failed\n if (!LRS.initialized && LRS.mode !== 'directLRS') {\n var retryCount = 0;\n var maxRetries = 10;\n var retryInterval = setInterval(function() {\n retryCount++;\n if (setupBridge()) {\n LRS.initialized = true;\n extractActorAsync(function(actor) {\n log('Actor extracted on retry:', actor);\n });\n log('Bridge connected on retry ' + retryCount);\n clearInterval(retryInterval);\n } else if (retryCount >= maxRetries) {\n clearInterval(retryInterval);\n }\n }, 1000);\n }\n\n log('LRS bridge setup complete');\n }\n\n // Initialize\n init();\n\n })();\n`;\n}\n","import type { PatchAdamsConfig, LrsBridgeConfig } from '../config/schema.js';\nimport type { CourseMetadata } from '../fingerprint/course-metadata.js';\nimport { generateLrsBridgeCode, type LrsBridgeOptions } from './lrs-bridge.js';\n\nexport interface JsBeforeOptions {\n remoteUrl: string;\n localPath: string;\n htmlClass: string;\n loadingClass: string;\n /** Full course metadata */\n metadata: CourseMetadata | null;\n /** LRS Bridge configuration */\n lrsBridge: LrsBridgeOptions;\n /** Optional skin name */\n skin?: string;\n}\n\n/**\n * Escape a string for safe inclusion in JavaScript\n */\nfunction escapeJs(str: string | null | undefined): string {\n if (!str) return '';\n return str\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/'/g, \"\\\\'\")\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r');\n}\n\n/**\n * Generate the blocking JS loader for the \"before\" slot\n * This loads at the start of <head> and blocks rendering until loaded\n *\n * Use this for:\n * - Setting up global variables\n * - Intercepting Rise APIs before they initialize\n * - Preparing the environment\n * - LRS Bridge for xAPI/SCORM communication\n */\nexport function generateJsBeforeLoader(options: JsBeforeOptions): string {\n const { remoteUrl, localPath, htmlClass, loadingClass, metadata, lrsBridge, skin } = options;\n\n // Generate LRS bridge code\n const lrsBridgeCode = generateLrsBridgeCode(lrsBridge);\n\n // Build course object with all available metadata\n const courseLines: string[] = [];\n\n if (metadata) {\n courseLines.push(` courseId: '${escapeJs(metadata.courseId)}',`);\n courseLines.push(` courseIdSource: '${metadata.courseIdSource}',`);\n courseLines.push(` format: '${metadata.format}',`);\n\n if (metadata.title) {\n courseLines.push(` title: '${escapeJs(metadata.title)}',`);\n }\n if (metadata.description) {\n courseLines.push(` description: '${escapeJs(metadata.description)}',`);\n }\n if (metadata.organizationId) {\n courseLines.push(` organizationId: '${escapeJs(metadata.organizationId)}',`);\n }\n if (metadata.launchUrl) {\n courseLines.push(` launchUrl: '${escapeJs(metadata.launchUrl)}',`);\n }\n if (metadata.masteryScore !== null) {\n courseLines.push(` masteryScore: ${metadata.masteryScore},`);\n }\n if (metadata.duration) {\n courseLines.push(` duration: '${escapeJs(metadata.duration)}',`);\n }\n if (metadata.language) {\n courseLines.push(` language: '${escapeJs(metadata.language)}',`);\n }\n if (metadata.version) {\n courseLines.push(` version: '${escapeJs(metadata.version)}',`);\n }\n courseLines.push(` extractedAt: '${metadata.extractedAt}',`);\n }\n\n const courseBlock = courseLines.length > 0\n ? `\\n course: {\\n${courseLines.join('\\n')}\\n },`\n : '';\n\n return `<!-- === PATCH-ADAMS: JS BEFORE (blocking) === -->\n<script data-pa=\"js-before-loader\">\n// Initialize PA-Patcher global namespace IMMEDIATELY (before IIFE)\nwindow.pa_patcher = window.pa_patcher || {\n version: '1.0.25',\n htmlClass: '${htmlClass}',\n loadingClass: '${loadingClass}',${skin ? `\\n skin: '${escapeJs(skin)}',` : ''}${courseBlock}\n loaded: {\n cssBefore: false,\n cssAfter: false,\n jsBefore: false,\n jsAfter: false${skin ? ',\\n skinCss: false,\\n skinJs: false' : ''}\n }\n};\n\n// Filter out browser extension errors that can interfere with LMS error handling\n// These are not course errors - they come from extensions like SingleFile, LastPass, etc.\n(function() {\n var extensionPatterns = [\n 'extension',\n 'chrome-extension',\n 'moz-extension',\n 'safari-extension',\n 'message channel closed',\n 'listener indicated an asynchronous response',\n 'Receiving end does not exist',\n 'single-file',\n 'singlefile'\n ];\n\n function isExtensionError(error) {\n if (!error) return false;\n var msg = (error.message || error.reason || String(error)).toLowerCase();\n for (var i = 0; i < extensionPatterns.length; i++) {\n if (msg.indexOf(extensionPatterns[i]) !== -1) return true;\n }\n // Check stack trace for extension URLs\n var stack = (error.stack || '').toLowerCase();\n if (stack.indexOf('extension') !== -1 || stack.indexOf('moz-extension') !== -1) return true;\n return false;\n }\n\n // Handle unhandled promise rejections from extensions\n window.addEventListener('unhandledrejection', function(event) {\n if (isExtensionError(event.reason)) {\n event.preventDefault();\n event.stopImmediatePropagation();\n console.debug('[PA-Patcher] Suppressed browser extension error:', event.reason);\n return false;\n }\n }, true);\n\n // Handle regular errors from extensions\n window.addEventListener('error', function(event) {\n if (isExtensionError(event.error || event.message)) {\n event.preventDefault();\n event.stopImmediatePropagation();\n console.debug('[PA-Patcher] Suppressed browser extension error:', event.message);\n return false;\n }\n }, true);\n})();\n\n(function() {\n 'use strict';\n var REMOTE_URL = \"${remoteUrl}\";\n var LOCAL_PATH = \"${localPath}\";\n\n function loadJSSync(url) {\n var script = document.createElement('script');\n script.src = url;\n script.setAttribute('data-pa', 'js-before');\n // Note: For truly blocking behavior, we use document.write\n // but that's fragile. Instead we'll handle errors gracefully.\n document.head.appendChild(script);\n return script;\n }\n\n // Try remote first\n var script = loadJSSync(REMOTE_URL);\n\n script.onerror = function() {\n console.warn('[PA-Patcher] JS before failed to load from remote, using local fallback');\n document.head.removeChild(script);\n var fallback = loadJSSync(LOCAL_PATH);\n fallback.onload = function() {\n if (window.pa_patcher && window.pa_patcher.loaded) {\n window.pa_patcher.loaded.jsBefore = true;\n }\n console.log('[PA-Patcher] JS before loaded from local fallback:', LOCAL_PATH);\n };\n fallback.onerror = function() {\n console.error('[PA-Patcher] JS before failed to load from both remote and local');\n };\n };\n\n script.onload = function() {\n if (window.pa_patcher && window.pa_patcher.loaded) {\n window.pa_patcher.loaded.jsBefore = true;\n }\n console.log('[PA-Patcher] JS before loaded from remote:', REMOTE_URL);\n };\n})();\n${lrsBridgeCode}\n</script>`;\n}\n\n/**\n * Build options from config\n */\nexport function buildJsBeforeOptions(config: PatchAdamsConfig, metadata?: CourseMetadata | null): JsBeforeOptions {\n // Convert LrsBridgeConfig to LrsBridgeOptions with defaults for backwards compatibility\n // This handles configs from older versions that don't have lrsBridge\n const lrsBridgeConfig = config.lrsBridge ?? {};\n const lrsBridge: LrsBridgeOptions = {\n enabled: lrsBridgeConfig.enabled ?? true,\n trackMedia: lrsBridgeConfig.trackMedia ?? true,\n trackNavigation: lrsBridgeConfig.trackNavigation ?? true,\n trackQuizzes: lrsBridgeConfig.trackQuizzes ?? true,\n trackInteractions: lrsBridgeConfig.trackInteractions ?? true,\n customStatements: lrsBridgeConfig.customStatements ?? true,\n debug: lrsBridgeConfig.debug ?? false,\n lrsEndpoint: lrsBridgeConfig.lrsEndpoint,\n lrsWithCredentials: lrsBridgeConfig.lrsWithCredentials,\n courseHomepage: lrsBridgeConfig.courseHomepage,\n autoDetectLrs: lrsBridgeConfig.autoDetectLrs,\n lrsAuth: lrsBridgeConfig.lrsAuth,\n lrsProxyEndpoint: lrsBridgeConfig.lrsProxyEndpoint,\n employeeApiEndpoint: lrsBridgeConfig.employeeApiEndpoint,\n documentGuid: lrsBridgeConfig.documentGuid,\n versionGuid: lrsBridgeConfig.versionGuid,\n packageName: lrsBridgeConfig.packageName,\n };\n\n return {\n remoteUrl: `${config.remoteDomain}/${config.localFolders.js}/${config.jsBefore.filename}`,\n localPath: `${config.localFolders.js}/${config.jsBefore.filename}`,\n htmlClass: config.htmlClass,\n loadingClass: config.loadingClass,\n metadata: metadata ?? null,\n lrsBridge,\n skin: config.skin,\n };\n}\n","import type { PatchAdamsConfig } from '../config/schema.js';\n\nexport interface JsAfterOptions {\n remoteUrl: string;\n localPath: string;\n timeout: number;\n loadingClass: string;\n}\n\n/**\n * Generate the async JS loader for the \"after\" slot\n * This loads at the end of <body> with remote-first, local fallback\n *\n * This script is responsible for:\n * - Loading your override JS after Rise has initialized\n * - Removing the loading class to reveal the content\n */\nexport function generateJsAfterLoader(options: JsAfterOptions): string {\n const { remoteUrl, localPath, timeout, loadingClass } = options;\n\n return `<!-- === PATCH-ADAMS: JS AFTER (async with fallback) === -->\n<script data-pa=\"js-after-loader\">\n(function() {\n 'use strict';\n var REMOTE_URL = \"${remoteUrl}\";\n var LOCAL_PATH = \"${localPath}\";\n var TIMEOUT = ${timeout};\n var LOADING_CLASS = \"${loadingClass}\";\n\n function loadJS(url, onSuccess, onError) {\n var script = document.createElement('script');\n script.src = url;\n script.async = true;\n script.setAttribute('data-pa', 'js-after');\n\n script.onload = function() {\n if (onSuccess) onSuccess();\n };\n\n script.onerror = function() {\n if (onError) onError();\n };\n\n document.body.appendChild(script);\n return script;\n }\n\n function removeLoadingClass() {\n document.documentElement.classList.remove(LOADING_CLASS);\n if (window.pa_patcher && window.pa_patcher.loaded) {\n window.pa_patcher.loaded.jsAfter = true;\n }\n console.log('[PA-Patcher] Loading complete, content revealed');\n }\n\n function loadJSWithFallback() {\n var loaded = false;\n var timeoutId;\n\n // Try remote first\n var remoteScript = loadJS(\n REMOTE_URL,\n function() {\n if (loaded) return;\n loaded = true;\n clearTimeout(timeoutId);\n console.log('[PA-Patcher] JS after loaded from remote:', REMOTE_URL);\n // Give the script a moment to execute, then remove loading class\n setTimeout(removeLoadingClass, 50);\n },\n function() {\n if (loaded) return;\n loaded = true;\n clearTimeout(timeoutId);\n loadLocalFallback();\n }\n );\n\n // Timeout fallback\n timeoutId = setTimeout(function() {\n if (loaded) return;\n loaded = true;\n console.warn('[PA-Patcher] JS after timed out, using local fallback');\n if (remoteScript.parentNode) {\n document.body.removeChild(remoteScript);\n }\n loadLocalFallback();\n }, TIMEOUT);\n }\n\n function loadLocalFallback() {\n loadJS(\n LOCAL_PATH,\n function() {\n console.log('[PA-Patcher] JS after loaded from local fallback:', LOCAL_PATH);\n setTimeout(removeLoadingClass, 50);\n },\n function() {\n console.error('[PA-Patcher] JS after failed to load from both remote and local');\n // Still remove loading class so content is visible even if JS fails\n removeLoadingClass();\n }\n );\n }\n\n // Execute after DOM is ready to ensure Rise has loaded\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', function() {\n // Small delay to ensure Rise has initialized\n setTimeout(loadJSWithFallback, 100);\n });\n } else {\n // DOM is already ready\n setTimeout(loadJSWithFallback, 100);\n }\n})();\n</script>`;\n}\n\n/**\n * Build options from config\n */\nexport function buildJsAfterOptions(config: PatchAdamsConfig): JsAfterOptions {\n return {\n remoteUrl: `${config.remoteDomain}/${config.localFolders.js}/${config.jsAfter.filename}`,\n localPath: `${config.localFolders.js}/${config.jsAfter.filename}`,\n timeout: config.jsAfter.timeout,\n loadingClass: config.loadingClass,\n };\n}\n","import type { PatchAdamsConfig } from '../config/schema.js';\n\nexport interface SkinCssOptions {\n remoteUrl: string;\n localPath: string;\n timeout: number;\n}\n\n/**\n * Generate the async CSS loader for the skin slot\n * Loads AFTER core CSS (after.css) to allow skin styles to override core styles\n * Same remote-first + local fallback pattern as css-after\n */\nexport function generateSkinCssLoader(options: SkinCssOptions): string {\n const { remoteUrl, localPath, timeout } = options;\n\n return `<!-- === PATCH-ADAMS: SKIN CSS (async with fallback) === -->\n<script data-pa=\"skin-css-loader\">\n(function() {\n 'use strict';\n var REMOTE_URL = \"${remoteUrl}\";\n var LOCAL_PATH = \"${localPath}\";\n var TIMEOUT = ${timeout};\n\n function loadCSS(url, onSuccess, onError) {\n var link = document.createElement('link');\n link.rel = 'stylesheet';\n link.href = url;\n link.setAttribute('data-pa', 'skin-css');\n\n link.onload = function() {\n if (onSuccess) onSuccess();\n };\n\n link.onerror = function() {\n if (onError) onError();\n };\n\n document.head.appendChild(link);\n return link;\n }\n\n function loadCSSWithFallback() {\n var loaded = false;\n var timeoutId;\n\n // Try remote first\n var remoteLink = loadCSS(\n REMOTE_URL,\n function() {\n if (loaded) return;\n loaded = true;\n clearTimeout(timeoutId);\n console.log('[PA-Patcher] Skin CSS loaded from remote:', REMOTE_URL);\n },\n function() {\n if (loaded) return;\n loaded = true;\n clearTimeout(timeoutId);\n loadLocalFallback();\n }\n );\n\n // Timeout fallback\n timeoutId = setTimeout(function() {\n if (loaded) return;\n loaded = true;\n console.warn('[PA-Patcher] Skin CSS timed out, using local fallback');\n if (remoteLink.parentNode) {\n document.head.removeChild(remoteLink);\n }\n loadLocalFallback();\n }, TIMEOUT);\n }\n\n function loadLocalFallback() {\n loadCSS(\n LOCAL_PATH,\n function() {\n console.log('[PA-Patcher] Skin CSS loaded from local fallback:', LOCAL_PATH);\n },\n function() {\n console.error('[PA-Patcher] Skin CSS failed to load from both remote and local');\n }\n );\n }\n\n // Execute immediately\n loadCSSWithFallback();\n})();\n</script>`;\n}\n\n/**\n * Build skin CSS options from config\n */\nexport function buildSkinCssOptions(config: PatchAdamsConfig): SkinCssOptions | null {\n if (!config.skin) return null;\n\n const cacheBuster = Date.now().toString(36) + Math.random().toString(36).slice(2, 6);\n return {\n remoteUrl: `${config.remoteDomain}/skin/${config.skin}/style.css?v=${cacheBuster}`,\n localPath: `skin/${config.skin}/style.css`,\n timeout: config.cssAfter.timeout, // reuse cssAfter timeout\n };\n}\n","import type { PatchAdamsConfig } from '../config/schema.js';\n\nexport interface SkinJsOptions {\n remoteUrl: string;\n localPath: string;\n timeout: number;\n}\n\n/**\n * Generate the async JS loader for the skin slot\n * Loads AFTER core JS (after.js) to allow skin scripts to override core behavior\n * Same remote-first + local fallback pattern as js-after, but WITHOUT loading class removal\n */\nexport function generateSkinJsLoader(options: SkinJsOptions): string {\n const { remoteUrl, localPath, timeout } = options;\n\n return `<!-- === PATCH-ADAMS: SKIN JS (async with fallback) === -->\n<script data-pa=\"skin-js-loader\">\n(function() {\n 'use strict';\n var REMOTE_URL = \"${remoteUrl}\";\n var LOCAL_PATH = \"${localPath}\";\n var TIMEOUT = ${timeout};\n\n function loadJS(url, onSuccess, onError) {\n var script = document.createElement('script');\n script.src = url;\n script.async = true;\n script.setAttribute('data-pa', 'skin-js');\n\n script.onload = function() {\n if (onSuccess) onSuccess();\n };\n\n script.onerror = function() {\n if (onError) onError();\n };\n\n document.body.appendChild(script);\n return script;\n }\n\n function loadJSWithFallback() {\n var loaded = false;\n var timeoutId;\n\n // Try remote first\n var remoteScript = loadJS(\n REMOTE_URL,\n function() {\n if (loaded) return;\n loaded = true;\n clearTimeout(timeoutId);\n console.log('[PA-Patcher] Skin JS loaded from remote:', REMOTE_URL);\n if (window.pa_patcher && window.pa_patcher.loaded) {\n window.pa_patcher.loaded.skinJs = true;\n }\n },\n function() {\n if (loaded) return;\n loaded = true;\n clearTimeout(timeoutId);\n loadLocalFallback();\n }\n );\n\n // Timeout fallback\n timeoutId = setTimeout(function() {\n if (loaded) return;\n loaded = true;\n console.warn('[PA-Patcher] Skin JS timed out, using local fallback');\n if (remoteScript.parentNode) {\n document.body.removeChild(remoteScript);\n }\n loadLocalFallback();\n }, TIMEOUT);\n }\n\n function loadLocalFallback() {\n loadJS(\n LOCAL_PATH,\n function() {\n console.log('[PA-Patcher] Skin JS loaded from local fallback:', LOCAL_PATH);\n if (window.pa_patcher && window.pa_patcher.loaded) {\n window.pa_patcher.loaded.skinJs = true;\n }\n },\n function() {\n console.error('[PA-Patcher] Skin JS failed to load from both remote and local');\n }\n );\n }\n\n // Execute after DOM is ready\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', function() {\n setTimeout(loadJSWithFallback, 150);\n });\n } else {\n setTimeout(loadJSWithFallback, 150);\n }\n})();\n</script>`;\n}\n\n/**\n * Build skin JS options from config\n */\nexport function buildSkinJsOptions(config: PatchAdamsConfig): SkinJsOptions | null {\n if (!config.skin) return null;\n\n const cacheBuster = Date.now().toString(36) + Math.random().toString(36).slice(2, 6);\n return {\n remoteUrl: `${config.remoteDomain}/skin/${config.skin}/script.js?v=${cacheBuster}`,\n localPath: `skin/${config.skin}/script.js`,\n timeout: config.jsAfter.timeout, // reuse jsAfter timeout\n };\n}\n","import type { PatchAdamsConfig } from '../config/schema.js';\nimport type { CourseMetadata } from '../fingerprint/course-metadata.js';\nimport type { GeneratedPluginAssets } from '../plugins/types.js';\nimport {\n generateCssBeforeLoader,\n buildCssBeforeOptions,\n generateCssAfterLoader,\n buildCssAfterOptions,\n generateJsBeforeLoader,\n buildJsBeforeOptions,\n generateJsAfterLoader,\n buildJsAfterOptions,\n generateSkinCssLoader,\n buildSkinCssOptions,\n generateSkinJsLoader,\n buildSkinJsOptions,\n} from '../templates/index.js';\n\n/**\n * HTML Injector for patching Rise course index.html\n *\n * Injection points:\n * 1. CSS Before - Start of <head> (blocking)\n * 2. JS Before - Start of <head>, after CSS Before (blocking)\n * 3. CSS After - End of <head> (async with fallback)\n * 4. JS After - End of <body>, after __loadEntry() (async with fallback)\n * 5. Plugin Assets - Inline CSS/JS injected directly (always executes)\n *\n * Also adds classes and data attributes to <html> tag\n */\nexport class HtmlInjector {\n private config: PatchAdamsConfig;\n private metadata: CourseMetadata | null = null;\n private pluginAssets: GeneratedPluginAssets | null = null;\n\n constructor(config: PatchAdamsConfig) {\n this.config = config;\n }\n\n /**\n * Set plugin-generated assets to inject inline into the HTML.\n * These are injected directly (not via external files) so they always execute.\n */\n setPluginAssets(assets: GeneratedPluginAssets): void {\n this.pluginAssets = assets;\n }\n\n /**\n * Set the course metadata to be injected into the HTML\n */\n setMetadata(metadata: CourseMetadata): void {\n this.metadata = metadata;\n }\n\n /**\n * @deprecated Use setMetadata instead\n * Set the course ID to be injected into the HTML\n */\n setCourseId(courseId: string): void {\n // Create minimal metadata for backwards compatibility\n if (!this.metadata) {\n this.metadata = {\n courseId,\n courseIdSource: 'fallback',\n format: 'unknown',\n title: null,\n description: null,\n organizationId: null,\n launchUrl: null,\n masteryScore: null,\n duration: null,\n language: null,\n version: null,\n extractedAt: new Date().toISOString(),\n };\n } else {\n this.metadata.courseId = courseId;\n }\n }\n\n /**\n * Inject all loaders into HTML\n */\n inject(html: string): string {\n let result = html;\n\n // Step 1: Add classes and data attributes to <html> tag\n result = this.addHtmlAttributes(result);\n\n // Step 2: Inject CSS Before at start of <head>\n if (this.config.cssBefore.enabled) {\n result = this.injectCssBefore(result);\n }\n\n // Step 3: Inject JS Before at start of <head> (after CSS Before)\n if (this.config.jsBefore.enabled) {\n result = this.injectJsBefore(result);\n }\n\n // Step 4: Inject CSS After at end of <head>\n if (this.config.cssAfter.enabled) {\n result = this.injectCssAfter(result);\n }\n\n // Step 5: Inject JS After at end of <body>\n if (this.config.jsAfter.enabled) {\n result = this.injectJsAfter(result);\n }\n\n // Step 6: Inject Skin CSS/JS (after core, before plugins)\n if (this.config.skin) {\n result = this.injectSkinCss(result);\n result = this.injectSkinJs(result);\n }\n\n // Step 7: Inject plugin assets inline (always executes, independent of remote/local loading)\n if (this.pluginAssets) {\n result = this.injectPluginAssets(result);\n }\n\n return result;\n }\n\n /**\n * Inject plugin assets inline into the HTML.\n * Plugin CSS goes at end of <head>, plugin JS goes at end of <body>.\n * These are injected directly so they always execute regardless of\n * whether remote or local fallback files are loaded.\n */\n private injectPluginAssets(html: string): string {\n let result = html;\n\n // Inject plugin CSS (before + after combined) at end of <head>\n const pluginCss = [\n this.pluginAssets?.cssBefore || '',\n this.pluginAssets?.cssAfter || '',\n ].filter(Boolean).join('\\n');\n\n if (pluginCss) {\n const cssBlock = `<!-- === PATCH-ADAMS: PLUGIN CSS (inline) === -->\n<style data-pa=\"plugin-css\">\n${pluginCss}\n</style>`;\n result = result.replace(/<\\/head>/i, `${cssBlock}\\n</head>`);\n console.log('[HtmlInjector] Injected plugin CSS inline');\n }\n\n // Inject plugin JS (before + after combined) at end of <body>\n const pluginJs = [\n this.pluginAssets?.jsBefore || '',\n this.pluginAssets?.jsAfter || '',\n ].filter(Boolean).join('\\n');\n\n if (pluginJs) {\n const jsBlock = `<!-- === PATCH-ADAMS: PLUGIN JS (inline) === -->\n<script data-pa=\"plugin-js\">\n${pluginJs}\n</script>`;\n result = result.replace(/<\\/body>/i, `${jsBlock}\\n</body>`);\n console.log('[HtmlInjector] Injected plugin JS inline');\n }\n\n return result;\n }\n\n /**\n * Add pa-patched and pa-loading classes to <html> tag\n * Also adds data attributes for course metadata\n */\n private addHtmlAttributes(html: string): string {\n const { htmlClass, loadingClass, skin } = this.config;\n const classes = `${htmlClass} ${loadingClass}${skin ? ` pa-skinned ${skin}` : ''}`;\n\n // Build data attributes from metadata\n const dataAttrs: string[] = [];\n if (this.metadata) {\n dataAttrs.push(`data-pa-course-id=\"${this.escapeAttr(this.metadata.courseId)}\"`);\n dataAttrs.push(`data-pa-format=\"${this.metadata.format}\"`);\n if (this.metadata.title) {\n dataAttrs.push(`data-pa-title=\"${this.escapeAttr(this.metadata.title)}\"`);\n }\n }\n if (skin) {\n dataAttrs.push(`data-pa-skin=\"${this.escapeAttr(skin)}\"`);\n }\n const dataAttrString = dataAttrs.length > 0 ? ' ' + dataAttrs.join(' ') : '';\n\n // Match <html> tag with or without existing class attribute\n const htmlTagPattern = /<html([^>]*)>/i;\n const match = html.match(htmlTagPattern);\n\n if (!match) {\n return html;\n }\n\n const attributes = match[1];\n\n // Check if class attribute exists\n if (/class\\s*=\\s*[\"'][^\"']*[\"']/i.test(attributes)) {\n // Append to existing class\n return html.replace(\n htmlTagPattern,\n (_match: string, attrs: string) => {\n const newAttrs = attrs.replace(\n /class\\s*=\\s*[\"']([^\"']*)[\"']/i,\n (_fullMatch: string, existingClasses: string) => `class=\"${existingClasses} ${classes}\"`\n );\n return `<html${newAttrs}${dataAttrString}>`;\n }\n );\n } else {\n // Add class attribute\n return html.replace(htmlTagPattern, `<html${attributes} class=\"${classes}\"${dataAttrString}>`);\n }\n }\n\n /**\n * Escape a string for use in HTML attributes\n */\n private escapeAttr(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n }\n\n /**\n * Inject CSS Before loader at start of <head>\n */\n private injectCssBefore(html: string): string {\n const options = buildCssBeforeOptions(this.config);\n const loader = generateCssBeforeLoader(options);\n\n // Insert right after <head> tag\n return html.replace(/<head([^>]*)>/i, `<head$1>\\n${loader}`);\n }\n\n /**\n * Inject JS Before loader at start of <head> (after CSS Before if present)\n */\n private injectJsBefore(html: string): string {\n const options = buildJsBeforeOptions(this.config, this.metadata);\n const loader = generateJsBeforeLoader(options);\n\n // Look for the CSS Before marker to insert after it\n const cssBeforeMarker = '<!-- === PATCH-ADAMS: CSS BEFORE';\n if (html.includes(cssBeforeMarker)) {\n // Find end of CSS Before block and insert after\n const cssBeforeEndPattern = /<\\/script>\\s*(?=\\n|<)/;\n const cssBeforeStart = html.indexOf(cssBeforeMarker);\n const afterCssBefore = html.substring(cssBeforeStart);\n const endMatch = afterCssBefore.match(cssBeforeEndPattern);\n\n if (endMatch && endMatch.index !== undefined) {\n const insertPos = cssBeforeStart + endMatch.index + endMatch[0].length;\n return html.slice(0, insertPos) + '\\n' + loader + html.slice(insertPos);\n }\n }\n\n // Fallback: insert after <head> tag\n return html.replace(/<head([^>]*)>/i, `<head$1>\\n${loader}`);\n }\n\n /**\n * Inject CSS After loader at end of <head>\n */\n private injectCssAfter(html: string): string {\n const options = buildCssAfterOptions(this.config);\n const loader = generateCssAfterLoader(options);\n\n // Insert before </head>\n return html.replace(/<\\/head>/i, `${loader}\\n</head>`);\n }\n\n /**\n * Inject JS After loader at end of <body> (after __loadEntry)\n */\n private injectJsAfter(html: string): string {\n const options = buildJsAfterOptions(this.config);\n const loader = generateJsAfterLoader(options);\n\n // Try to find __loadEntry() call and insert after it\n const loadEntryPattern = /(<script>__loadEntry\\(\\)<\\/script>)/i;\n if (loadEntryPattern.test(html)) {\n return html.replace(loadEntryPattern, `$1\\n${loader}`);\n }\n\n // Fallback: insert before </body>\n return html.replace(/<\\/body>/i, `${loader}\\n</body>`);\n }\n\n /**\n * Inject Skin CSS loader after CSS After (before </head>)\n */\n private injectSkinCss(html: string): string {\n const options = buildSkinCssOptions(this.config);\n if (!options) return html;\n\n const loader = generateSkinCssLoader(options);\n // Insert before </head> (after CSS After which was already injected there)\n return html.replace(/<\\/head>/i, `${loader}\\n</head>`);\n }\n\n /**\n * Inject Skin JS loader after JS After (before </body>)\n */\n private injectSkinJs(html: string): string {\n const options = buildSkinJsOptions(this.config);\n if (!options) return html;\n\n const loader = generateSkinJsLoader(options);\n // Insert before </body> (after JS After which was already injected there)\n return html.replace(/<\\/body>/i, `${loader}\\n</body>`);\n }\n}\n","import type { PatchAdamsConfig } from '../config/schema.js';\nimport type { CourseMetadata } from '../fingerprint/course-metadata.js';\nimport type { GeneratedPluginAssets } from '../plugins/types.js';\nimport {\n generateCssBeforeLoader,\n buildCssBeforeOptions,\n generateCssAfterLoader,\n buildCssAfterOptions,\n generateJsBeforeLoader,\n buildJsBeforeOptions,\n generateJsAfterLoader,\n buildJsAfterOptions,\n generateSkinCssLoader,\n buildSkinCssOptions,\n generateSkinJsLoader,\n buildSkinJsOptions,\n} from '../templates/index.js';\n\n/**\n * HTML Injector for patching Articulate Storyline course HTML files\n *\n * Storyline structure:\n * - Main entry: story.html (standalone) or index_lms.html (LMS)\n * - Player scripts: html5/lib/scripts/bootstrapper.min.js (loaded at end of body)\n * - Course data: html5/data/js/*.js\n * - CSS: html5/data/css/output.min.css\n *\n * Injection points:\n * 1. CSS Before - Start of <head> (blocking)\n * 2. JS Before - Start of <head>, after CSS Before (blocking)\n * 3. CSS After - End of <head>, before </head>\n * 4. JS After - End of <body>, before </body> (after content, before bootstrapper runs)\n * 5. Plugin Assets - Inline CSS/JS injected directly (always executes)\n *\n * Also adds classes and data attributes to <html> tag\n */\nexport class StorylineHtmlInjector {\n private config: PatchAdamsConfig;\n private metadata: CourseMetadata | null = null;\n private pluginAssets: GeneratedPluginAssets | null = null;\n\n constructor(config: PatchAdamsConfig) {\n this.config = config;\n }\n\n /**\n * Set plugin-generated assets to inject inline into the HTML.\n * These are injected directly (not via external files) so they always execute.\n */\n setPluginAssets(assets: GeneratedPluginAssets): void {\n this.pluginAssets = assets;\n }\n\n /**\n * Set the course metadata to be injected into the HTML\n */\n setMetadata(metadata: CourseMetadata): void {\n this.metadata = metadata;\n }\n\n /**\n * Inject all loaders into HTML\n */\n inject(html: string): string {\n let result = html;\n\n // Step 1: Add classes and data attributes to <html> tag\n result = this.addHtmlAttributes(result);\n\n // Step 2: Inject CSS Before at start of <head>\n if (this.config.cssBefore.enabled) {\n result = this.injectCssBefore(result);\n }\n\n // Step 3: Inject JS Before at start of <head> (after CSS Before)\n if (this.config.jsBefore.enabled) {\n result = this.injectJsBefore(result);\n }\n\n // Step 4: Inject CSS After at end of <head>\n if (this.config.cssAfter.enabled) {\n result = this.injectCssAfter(result);\n }\n\n // Step 5: Inject JS After at end of <body>\n if (this.config.jsAfter.enabled) {\n result = this.injectJsAfter(result);\n }\n\n // Step 6: Inject Skin CSS/JS (after core, before plugins)\n if (this.config.skin) {\n result = this.injectSkinCss(result);\n result = this.injectSkinJs(result);\n }\n\n // Step 7: Inject plugin assets inline (always executes, independent of remote/local loading)\n if (this.pluginAssets) {\n result = this.injectPluginAssets(result);\n }\n\n return result;\n }\n\n /**\n * Inject plugin assets inline into the HTML.\n * Plugin CSS goes at end of <head>, plugin JS goes at end of <body>.\n */\n private injectPluginAssets(html: string): string {\n let result = html;\n\n // Inject plugin CSS at end of <head>\n const pluginCss = [\n this.pluginAssets?.cssBefore || '',\n this.pluginAssets?.cssAfter || '',\n ]\n .filter(Boolean)\n .join('\\n');\n\n if (pluginCss) {\n const cssBlock = `<!-- === PATCH-ADAMS: PLUGIN CSS (inline) === -->\n<style data-pa=\"plugin-css\">\n${pluginCss}\n</style>`;\n result = result.replace(/<\\/head>/i, `${cssBlock}\\n</head>`);\n console.log('[StorylineHtmlInjector] Injected plugin CSS inline');\n }\n\n // Inject plugin JS at end of <body>\n const pluginJs = [\n this.pluginAssets?.jsBefore || '',\n this.pluginAssets?.jsAfter || '',\n ]\n .filter(Boolean)\n .join('\\n');\n\n if (pluginJs) {\n const jsBlock = `<!-- === PATCH-ADAMS: PLUGIN JS (inline) === -->\n<script data-pa=\"plugin-js\">\n${pluginJs}\n</script>`;\n result = result.replace(/<\\/body>/i, `${jsBlock}\\n</body>`);\n console.log('[StorylineHtmlInjector] Injected plugin JS inline');\n }\n\n return result;\n }\n\n /**\n * Add pa-patched and pa-loading classes to <html> tag\n * Also adds data attributes for course metadata\n */\n private addHtmlAttributes(html: string): string {\n const { htmlClass, loadingClass, skin } = this.config;\n const classes = `${htmlClass} ${loadingClass}${skin ? ` pa-skinned ${skin}` : ''}`;\n\n // Build data attributes from metadata\n const dataAttrs: string[] = [];\n if (this.metadata) {\n dataAttrs.push(`data-pa-course-id=\"${this.escapeAttr(this.metadata.courseId)}\"`);\n dataAttrs.push(`data-pa-format=\"${this.metadata.format}\"`);\n dataAttrs.push('data-pa-tool=\"storyline\"');\n if (this.metadata.title) {\n dataAttrs.push(`data-pa-title=\"${this.escapeAttr(this.metadata.title)}\"`);\n }\n }\n if (skin) {\n dataAttrs.push(`data-pa-skin=\"${this.escapeAttr(skin)}\"`);\n }\n const dataAttrString = dataAttrs.length > 0 ? ' ' + dataAttrs.join(' ') : '';\n\n // Match <html> tag with or without existing class attribute\n const htmlTagPattern = /<html([^>]*)>/i;\n const match = html.match(htmlTagPattern);\n\n if (!match) {\n return html;\n }\n\n const attributes = match[1];\n\n // Check if class attribute exists\n if (/class\\s*=\\s*[\"'][^\"']*[\"']/i.test(attributes)) {\n // Append to existing class\n return html.replace(htmlTagPattern, (_match: string, attrs: string) => {\n const newAttrs = attrs.replace(\n /class\\s*=\\s*[\"']([^\"']*)[\"']/i,\n (_fullMatch: string, existingClasses: string) =>\n `class=\"${existingClasses} ${classes}\"`\n );\n return `<html${newAttrs}${dataAttrString}>`;\n });\n } else {\n // Add class attribute\n return html.replace(\n htmlTagPattern,\n `<html${attributes} class=\"${classes}\"${dataAttrString}>`\n );\n }\n }\n\n /**\n * Escape a string for use in HTML attributes\n */\n private escapeAttr(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n }\n\n /**\n * Inject CSS Before loader at start of <head>\n */\n private injectCssBefore(html: string): string {\n const options = buildCssBeforeOptions(this.config);\n const loader = generateCssBeforeLoader(options);\n\n // Insert right after <head> tag\n return html.replace(/<head([^>]*)>/i, `<head$1>\\n${loader}`);\n }\n\n /**\n * Inject JS Before loader at start of <head> (after CSS Before if present)\n */\n private injectJsBefore(html: string): string {\n const options = buildJsBeforeOptions(this.config, this.metadata);\n const loader = generateJsBeforeLoader(options);\n\n // Look for the CSS Before marker to insert after it\n const cssBeforeMarker = '<!-- === PATCH-ADAMS: CSS BEFORE';\n if (html.includes(cssBeforeMarker)) {\n // Find end of CSS Before block and insert after\n const cssBeforeEndPattern = /<\\/script>\\s*(?=\\n|<)/;\n const cssBeforeStart = html.indexOf(cssBeforeMarker);\n const afterCssBefore = html.substring(cssBeforeStart);\n const endMatch = afterCssBefore.match(cssBeforeEndPattern);\n\n if (endMatch && endMatch.index !== undefined) {\n const insertPos = cssBeforeStart + endMatch.index + endMatch[0].length;\n return html.slice(0, insertPos) + '\\n' + loader + html.slice(insertPos);\n }\n }\n\n // Fallback: insert after <head> tag\n return html.replace(/<head([^>]*)>/i, `<head$1>\\n${loader}`);\n }\n\n /**\n * Inject CSS After loader at end of <head>\n */\n private injectCssAfter(html: string): string {\n const options = buildCssAfterOptions(this.config);\n const loader = generateCssAfterLoader(options);\n\n // Insert before </head>\n return html.replace(/<\\/head>/i, `${loader}\\n</head>`);\n }\n\n /**\n * Inject JS After loader at end of <body>\n *\n * Storyline note: The bootstrapper.min.js is loaded via <script src=\"...\">\n * AFTER </body> tag (unusual but that's how Storyline works).\n * We inject before </body> so our code runs before the bootstrapper.\n */\n private injectJsAfter(html: string): string {\n const options = buildJsAfterOptions(this.config);\n const loader = generateJsAfterLoader(options);\n\n // Insert before </body>\n return html.replace(/<\\/body>/i, `${loader}\\n</body>`);\n }\n\n /**\n * Inject Skin CSS loader after CSS After (before </head>)\n */\n private injectSkinCss(html: string): string {\n const options = buildSkinCssOptions(this.config);\n if (!options) return html;\n\n const loader = generateSkinCssLoader(options);\n return html.replace(/<\\/head>/i, `${loader}\\n</head>`);\n }\n\n /**\n * Inject Skin JS loader after JS After (before </body>)\n */\n private injectSkinJs(html: string): string {\n const options = buildSkinJsOptions(this.config);\n if (!options) return html;\n\n const loader = generateSkinJsLoader(options);\n return html.replace(/<\\/body>/i, `${loader}\\n</body>`);\n }\n}\n","import type AdmZip from 'adm-zip';\nimport type { PatchAdamsConfig } from '../config/schema.js';\nimport type { PackageFormat } from '../detectors/format-detector.js';\n\nexport interface ManifestPaths {\n cssBefore?: string;\n cssAfter?: string;\n jsBefore?: string;\n jsAfter?: string;\n skinCss?: string;\n skinJs?: string;\n}\n\n/**\n * Update manifest files to include injected assets\n * This ensures SCORM compliance for strict LMS systems\n */\nexport class ManifestUpdater {\n private config: PatchAdamsConfig;\n\n constructor(config: PatchAdamsConfig) {\n this.config = config;\n }\n\n /**\n * Update manifest files based on package format\n */\n update(zip: AdmZip, format: PackageFormat, paths: ManifestPaths): string[] {\n const modified: string[] = [];\n\n switch (format) {\n case 'scorm12':\n case 'scorm2004-3':\n case 'scorm2004-4':\n modified.push(...this.updateImsManifest(zip, paths));\n break;\n\n case 'cmi5':\n // cmi5 packages also typically have an imsmanifest.xml\n modified.push(...this.updateImsManifest(zip, paths));\n break;\n\n case 'xapi':\n // xAPI/tincan.xml doesn't have file manifests\n // Files are loaded directly by HTML\n break;\n\n case 'aicc':\n // AICC may have imsmanifest.xml\n const aiccManifest = zip.getEntry('imsmanifest.xml');\n if (aiccManifest) {\n modified.push(...this.updateImsManifest(zip, paths));\n }\n break;\n }\n\n return modified;\n }\n\n /**\n * Update imsmanifest.xml to include new file references\n * Uses string manipulation to preserve original XML formatting exactly\n */\n private updateImsManifest(zip: AdmZip, paths: ManifestPaths): string[] {\n const entry = zip.getEntry('imsmanifest.xml');\n if (!entry) {\n return [];\n }\n\n try {\n const xmlContent = entry.getData().toString('utf-8');\n\n // Build file entries to add\n const filesToAdd = [\n paths.cssBefore,\n paths.cssAfter,\n paths.jsBefore,\n paths.jsAfter,\n paths.skinCss,\n paths.skinJs,\n ].filter(Boolean) as string[];\n\n if (filesToAdd.length === 0) {\n return [];\n }\n\n // Generate new file entry XML strings\n const newFileEntries = filesToAdd\n .map((filePath) => ` <file href=\"${filePath}\" />`)\n .join('\\n');\n\n // Find the closing </resource> tag and insert before it\n // This preserves all original XML formatting, comments, etc.\n const resourceClosePattern = /(\\s*)<\\/resource>/i;\n const match = xmlContent.match(resourceClosePattern);\n\n if (!match) {\n console.warn('[PA-Patcher] Could not find </resource> tag in imsmanifest.xml');\n return [];\n }\n\n const updatedXml = xmlContent.replace(\n resourceClosePattern,\n `\\n${newFileEntries}\\n$1</resource>`\n );\n\n zip.updateFile('imsmanifest.xml', Buffer.from(updatedXml, 'utf-8'));\n\n return ['imsmanifest.xml'];\n } catch (error) {\n console.error('[PA-Patcher] Failed to update imsmanifest.xml:', error);\n return [];\n }\n }\n\n /**\n * Update the manifest identifier attribute to a new value.\n * Used to ensure every course has a unique identifier (e.g., project UUID).\n */\n updateIdentifier(zip: AdmZip, newIdentifier: string): boolean {\n const entry = zip.getEntry('imsmanifest.xml');\n if (!entry) return false;\n\n const xml = entry.getData().toString('utf-8');\n const updated = xml.replace(\n /(<manifest[^>]+identifier\\s*=\\s*[\"'])[^\"']+([\"'])/i,\n `$1${newIdentifier}$2`\n );\n\n if (updated === xml) return false;\n\n zip.updateFile('imsmanifest.xml', Buffer.from(updated, 'utf-8'));\n return true;\n }\n\n /**\n * Get the file paths that will be added to the package\n */\n getFilePaths(): ManifestPaths {\n const paths: ManifestPaths = {};\n\n if (this.config.cssBefore.enabled) {\n paths.cssBefore = `scormcontent/${this.config.localFolders.css}/${this.config.cssBefore.filename}`;\n }\n\n if (this.config.cssAfter.enabled) {\n paths.cssAfter = `scormcontent/${this.config.localFolders.css}/${this.config.cssAfter.filename}`;\n }\n\n if (this.config.jsBefore.enabled) {\n paths.jsBefore = `scormcontent/${this.config.localFolders.js}/${this.config.jsBefore.filename}`;\n }\n\n if (this.config.jsAfter.enabled) {\n paths.jsAfter = `scormcontent/${this.config.localFolders.js}/${this.config.jsAfter.filename}`;\n }\n\n return paths;\n }\n}\n","import crypto from 'crypto';\nimport AdmZip from 'adm-zip';\nimport archiver from 'archiver';\nimport { PassThrough } from 'stream';\nimport type { PatchAdamsConfig } from '../config/schema.js';\nimport { mergeWithDefaults } from '../config/loader.js';\nimport { HtmlInjector } from './html-injector.js';\nimport { StorylineHtmlInjector } from './storyline-html-injector.js';\nimport { ManifestUpdater } from './manifest-updater.js';\nimport { FormatDetector, type PackageFormat } from '../detectors/format-detector.js';\nimport {\n AuthoringToolDetector,\n type AuthoringTool,\n type AuthoringToolInfo,\n} from '../detectors/authoring-tool-detector.js';\nimport { extractCourseMetadata, type CourseMetadata } from '../fingerprint/course-metadata.js';\nimport { pluginRegistry, type GeneratedPluginAssets } from '../plugins/index.js';\n\n/**\n * Result of a patch operation\n */\nexport interface PatchResult {\n success: boolean;\n format: PackageFormat;\n formatDisplayName: string;\n /** Detected authoring tool */\n authoringTool: AuthoringTool;\n authoringToolDisplayName: string;\n /** Full course metadata */\n metadata: CourseMetadata;\n /** @deprecated Use metadata.courseId instead */\n courseId: string;\n /** @deprecated Use metadata.courseIdSource instead */\n courseIdSource: 'manifest' | 'tincan' | 'cmi5' | 'fallback';\n filesModified: string[];\n filesAdded: string[];\n errors: string[];\n warnings: string[];\n}\n\n/**\n * Options for patching\n */\nexport interface PatchOptions {\n /** Content for CSS before (local fallback) - overrides fetched content */\n cssBeforeContent?: string;\n /** Content for CSS after (local fallback) - overrides fetched content */\n cssAfterContent?: string;\n /** Content for JS before (local fallback) - overrides fetched content */\n jsBeforeContent?: string;\n /** Content for JS after (local fallback) - overrides fetched content */\n jsAfterContent?: string;\n /** Skip fetching remote files even if fetchFallbacks is enabled in config */\n skipFetch?: boolean;\n /** Optional skin name — per-call override for config.skin */\n skin?: string;\n /** Original package filename — baked into LRS bridge for statement searchability */\n packageName?: string;\n}\n\n/**\n * Fetched fallback content from remote server\n */\ninterface FetchedFallbacks {\n cssBefore?: string;\n cssAfter?: string;\n jsBefore?: string;\n jsAfter?: string;\n skinCss?: string;\n skinJs?: string;\n}\n\n/**\n * Default fallback file contents\n */\nconst DEFAULT_CSS_BEFORE = `/* PA-Patcher: CSS Before (blocking)\n * This file loads at the start of <head> and blocks rendering.\n * Use it to hide content and prevent flash of unstyled content.\n *\n * Example:\n * html.pa-patched.pa-loading body {\n * visibility: hidden !important;\n * }\n */\n`;\n\nconst DEFAULT_CSS_AFTER = `/* PA-Patcher: CSS After (async)\n * This file loads at the end of <head> with remote fallback.\n * Use it for style overrides that take precedence over Rise styles.\n *\n * Example:\n * html.pa-patched .blocks-text {\n * font-family: 'Your Font', sans-serif !important;\n * }\n */\n`;\n\nconst DEFAULT_JS_BEFORE = `// PA-Patcher: JS Before (blocking)\n// This file loads at the start of <head> and blocks rendering.\n// Use it for setup, API interception, and preparing globals.\n//\n// Example:\n// window.PatchAdams.customConfig = {\n// theme: 'dark',\n// analytics: true\n// };\n\nconsole.log('[PA-Patcher] JS Before loaded');\n`;\n\nconst DEFAULT_JS_AFTER = `// PA-Patcher: JS After (async)\n// This file loads at the end of <body> with remote fallback.\n// Use it for DOM manipulation after Rise has initialized.\n//\n// IMPORTANT: This script should NOT remove the loading class.\n// The loader script handles that automatically after this script loads.\n//\n// Example:\n// document.querySelectorAll('.blocks-text').forEach(function(el) {\n// // Your modifications here\n// });\n\nconsole.log('[PA-Patcher] JS After loaded');\n`;\n\n/**\n * Main Patcher class for patching e-learning course packages\n * Supports Rise, Storyline, and other authoring tools\n */\nexport class Patcher {\n private config: PatchAdamsConfig;\n private riseHtmlInjector: HtmlInjector;\n private storylineHtmlInjector: StorylineHtmlInjector;\n private manifestUpdater: ManifestUpdater;\n private formatDetector: FormatDetector;\n private authoringToolDetector: AuthoringToolDetector;\n\n constructor(config: Partial<PatchAdamsConfig>) {\n // Merge with defaults so partial configs work correctly\n this.config = mergeWithDefaults(config);\n this.riseHtmlInjector = new HtmlInjector(this.config);\n this.storylineHtmlInjector = new StorylineHtmlInjector(this.config);\n this.manifestUpdater = new ManifestUpdater(this.config);\n this.formatDetector = new FormatDetector();\n this.authoringToolDetector = new AuthoringToolDetector();\n }\n\n /**\n * Fetch a single file from a URL\n * Returns undefined if fetch fails (non-blocking)\n */\n private async fetchFile(url: string): Promise<string | undefined> {\n try {\n console.log(`[Patcher] Fetching: ${url}`);\n const response = await fetch(url);\n if (!response.ok) {\n console.warn(`[Patcher] Failed to fetch ${url}: ${response.status} ${response.statusText}`);\n return undefined;\n }\n const content = await response.text();\n console.log(`[Patcher] Fetched ${url} (${content.length} bytes)`);\n return content;\n } catch (error) {\n console.warn(`[Patcher] Error fetching ${url}:`, error);\n return undefined;\n }\n }\n\n /**\n * Fetch all remote fallback files\n * Non-blocking: if any fetch fails, we use the default content\n */\n private async fetchRemoteFallbacks(): Promise<FetchedFallbacks> {\n const fetched: FetchedFallbacks = {};\n const { remoteDomain, localFolders, cssBefore, cssAfter, jsBefore, jsAfter } = this.config;\n\n const fetchPromises: Promise<void>[] = [];\n\n if (cssBefore.enabled) {\n const url = `${remoteDomain}/${localFolders.css}/${cssBefore.filename}`;\n fetchPromises.push(\n this.fetchFile(url).then(content => { fetched.cssBefore = content; })\n );\n }\n\n if (cssAfter.enabled) {\n const url = `${remoteDomain}/${localFolders.css}/${cssAfter.filename}`;\n fetchPromises.push(\n this.fetchFile(url).then(content => { fetched.cssAfter = content; })\n );\n }\n\n if (jsBefore.enabled) {\n const url = `${remoteDomain}/${localFolders.js}/${jsBefore.filename}`;\n fetchPromises.push(\n this.fetchFile(url).then(content => { fetched.jsBefore = content; })\n );\n }\n\n if (jsAfter.enabled) {\n const url = `${remoteDomain}/${localFolders.js}/${jsAfter.filename}`;\n fetchPromises.push(\n this.fetchFile(url).then(content => { fetched.jsAfter = content; })\n );\n }\n\n // Fetch skin files if skin is configured\n if (this.config.skin) {\n const skinCssUrl = `${remoteDomain}/skin/${this.config.skin}/style.css`;\n fetchPromises.push(\n this.fetchFile(skinCssUrl).then(content => { fetched.skinCss = content; })\n );\n\n const skinJsUrl = `${remoteDomain}/skin/${this.config.skin}/script.js`;\n fetchPromises.push(\n this.fetchFile(skinJsUrl).then(content => { fetched.skinJs = content; })\n );\n }\n\n // Fetch all in parallel\n await Promise.all(fetchPromises);\n\n return fetched;\n }\n\n /**\n * Patch a Rise course ZIP file\n * @param inputBuffer - Input ZIP file as Buffer\n * @param options - Additional patching options\n * @returns Patched ZIP file as Buffer and result details\n */\n async patch(\n inputBuffer: Buffer,\n options: PatchOptions = {}\n ): Promise<{ buffer: Buffer; result: PatchResult }> {\n // Create placeholder metadata for initialization\n const placeholderMetadata: CourseMetadata = {\n courseId: '',\n courseIdSource: 'fallback',\n format: 'unknown',\n title: null,\n description: null,\n organizationId: null,\n launchUrl: null,\n masteryScore: null,\n duration: null,\n language: null,\n version: null,\n extractedAt: new Date().toISOString(),\n };\n\n const result: PatchResult = {\n success: false,\n format: 'unknown',\n formatDisplayName: 'Unknown',\n authoringTool: 'unknown',\n authoringToolDisplayName: 'Unknown',\n metadata: placeholderMetadata,\n courseId: '',\n courseIdSource: 'fallback',\n filesModified: [],\n filesAdded: [],\n errors: [],\n warnings: [],\n };\n\n try {\n // 1. Open ZIP with adm-zip for reading\n const zip = new AdmZip(inputBuffer);\n\n // 2. Detect package format\n result.format = this.formatDetector.detect(zip);\n result.formatDisplayName = this.formatDetector.getFormatDisplayName(result.format);\n\n if (result.format === 'unknown') {\n result.warnings.push('Could not determine package format, proceeding anyway');\n }\n\n // 2b. Detect authoring tool\n const toolInfo = this.authoringToolDetector.detect(zip);\n result.authoringTool = toolInfo.tool;\n result.authoringToolDisplayName = toolInfo.displayName;\n\n console.log(`[Patcher] Detected authoring tool: ${toolInfo.displayName}`);\n console.log(` - Main HTML: ${toolInfo.mainHtmlPath}`);\n if (toolInfo.additionalHtmlPaths?.length) {\n console.log(` - Additional HTML: ${toolInfo.additionalHtmlPaths.join(', ')}`);\n }\n if (toolInfo.version) console.log(` - Version: ${toolInfo.version}`);\n\n // 3. Extract comprehensive course metadata\n const metadata = extractCourseMetadata(zip, result.format);\n result.metadata = metadata;\n result.courseId = metadata.courseId;\n result.courseIdSource = metadata.courseIdSource;\n\n console.log(`[Patcher] Course metadata extracted:`);\n console.log(` - Course ID: ${metadata.courseId} (source: ${metadata.courseIdSource})`);\n console.log(` - Format: ${metadata.format}`);\n if (metadata.title) console.log(` - Title: ${metadata.title}`);\n if (metadata.description) console.log(` - Description: ${metadata.description.substring(0, 50)}...`);\n if (metadata.language) console.log(` - Language: ${metadata.language}`);\n if (metadata.masteryScore !== null) console.log(` - Mastery Score: ${metadata.masteryScore}`);\n\n // 3b. Ensure unique course identifier\n // Priority: documentGuid (from config) > manifest identifier > generated UUID\n const configDocGuid = this.config.lrsBridge?.documentGuid;\n\n if (configDocGuid) {\n // documentGuid takes priority (e.g., podcast projectId)\n console.log(`[Patcher] Overriding courseId with documentGuid: ${configDocGuid}`);\n metadata.courseId = configDocGuid;\n metadata.courseIdSource = 'manifest';\n result.courseId = configDocGuid;\n if (this.manifestUpdater.updateIdentifier(zip, configDocGuid)) {\n console.log(`[Patcher] Updated manifest identifier to: ${configDocGuid}`);\n }\n } else if (metadata.courseIdSource === 'fallback') {\n // No documentGuid and no manifest identifier — generate one\n const generated = crypto.randomUUID();\n console.log(`[Patcher] No course identifier found, generating UUID: ${generated}`);\n metadata.courseId = generated;\n result.courseId = generated;\n if (this.manifestUpdater.updateIdentifier(zip, generated)) {\n console.log(`[Patcher] Wrote generated UUID to manifest identifier`);\n }\n }\n\n // 4. Apply per-call skin override if provided\n const effectiveSkin = options.skin || this.config.skin;\n if (options.skin && options.skin !== this.config.skin) {\n // Temporarily set skin on config for this patch call\n this.config.skin = options.skin;\n // Rebuild injectors with updated config\n this.riseHtmlInjector = new HtmlInjector(this.config);\n this.storylineHtmlInjector = new StorylineHtmlInjector(this.config);\n console.log(`[Patcher] Using per-call skin override: ${options.skin}`);\n }\n if (effectiveSkin) {\n console.log(`[Patcher] Skin: ${effectiveSkin}`);\n }\n\n // 4c. Bake package name into LRS bridge for statement searchability\n if (options.packageName) {\n this.config.lrsBridge = this.config.lrsBridge ?? {};\n this.config.lrsBridge.packageName = options.packageName;\n console.log(`[Patcher] Package name: ${options.packageName}`);\n }\n\n // 4b. Set course metadata on appropriate HTML injector\n const htmlInjector = this.getHtmlInjector(toolInfo.tool);\n htmlInjector.setMetadata(metadata);\n\n // 5. Fetch remote fallback files if enabled\n let fetchedFallbacks: FetchedFallbacks = {};\n if (this.config.fetchFallbacks && !options.skipFetch) {\n console.log('[Patcher] Fetching remote files for local fallbacks...');\n fetchedFallbacks = await this.fetchRemoteFallbacks();\n const fetchedCount = Object.values(fetchedFallbacks).filter(Boolean).length;\n console.log(`[Patcher] Fetched ${fetchedCount} remote files`);\n }\n\n // 5b. Generate plugin assets and pass to HTML injector for inline injection\n const pluginAssets = this.generatePluginAssets();\n const hasPluginAssets =\n pluginAssets.cssBefore || pluginAssets.cssAfter || pluginAssets.jsBefore || pluginAssets.jsAfter;\n if (hasPluginAssets) {\n htmlInjector.setPluginAssets(pluginAssets);\n console.log('[Patcher] Plugin assets will be injected inline into HTML');\n }\n\n // 6. Find and patch HTML files (varies by authoring tool)\n // Some tools like Storyline have multiple entry points (story.html, index_lms.html)\n const htmlPathsToPatch = [toolInfo.mainHtmlPath, ...(toolInfo.additionalHtmlPaths || [])];\n\n for (const htmlPath of htmlPathsToPatch) {\n const htmlEntry = zip.getEntry(htmlPath);\n if (!htmlEntry) {\n if (htmlPath === toolInfo.mainHtmlPath) {\n // Main HTML is required\n throw new Error(\n `Could not find ${htmlPath} in package. Is this a valid ${toolInfo.displayName} export?`\n );\n }\n // Additional HTML files are optional\n console.log(`[Patcher] Optional HTML file not found: ${htmlPath}`);\n continue;\n }\n\n console.log(`[Patcher] Patching HTML file: ${htmlPath}`);\n const originalHtml = htmlEntry.getData().toString('utf-8');\n const patchedHtml = htmlInjector.inject(originalHtml);\n zip.updateFile(htmlPath, Buffer.from(patchedHtml, 'utf-8'));\n result.filesModified.push(htmlPath);\n }\n\n // 7. Add local fallback files (using fetched content or defaults)\n const addedFiles = this.addFallbackFiles(zip, options, fetchedFallbacks, toolInfo);\n result.filesAdded.push(...addedFiles);\n\n // 8. Update manifest files\n if (this.config.updateManifests) {\n // Build manifest paths from the actual files added (not hardcoded paths)\n const manifestPaths = this.buildManifestPaths(addedFiles);\n const modifiedManifests = this.manifestUpdater.update(zip, result.format, manifestPaths);\n result.filesModified.push(...modifiedManifests);\n }\n\n // 9. Write output using archiver for better ZIP compatibility\n // adm-zip's toBuffer() produces ZIPs that some parsers don't handle well\n const outputBuffer = await this.writeZipWithArchiver(zip);\n\n result.success = true;\n return { buffer: outputBuffer, result };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n result.errors.push(message);\n throw error;\n }\n }\n\n /**\n * Write ZIP using archiver for better compatibility\n * adm-zip's toBuffer() produces ZIPs that some parsers don't handle well.\n * archiver produces more standard ZIP output.\n */\n private writeZipWithArchiver(admZip: AdmZip): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const archive = archiver('zip', {\n zlib: { level: 9 }, // Maximum compression\n });\n\n const chunks: Buffer[] = [];\n const passThrough = new PassThrough();\n\n passThrough.on('data', (chunk: Buffer) => chunks.push(chunk));\n passThrough.on('end', () => resolve(Buffer.concat(chunks)));\n passThrough.on('error', reject);\n\n archive.on('error', reject);\n archive.pipe(passThrough);\n\n const entries = admZip.getEntries();\n\n for (const entry of entries) {\n const entryName = entry.entryName;\n const mtime = entry.header.time ? new Date(entry.header.time) : new Date();\n\n if (entry.isDirectory) {\n // Preserve explicit directory entries - some LMS systems require them\n archive.append('', {\n name: entryName.endsWith('/') ? entryName : entryName + '/',\n date: mtime,\n store: true,\n });\n continue;\n }\n\n const entryData = entry.getData();\n\n // Determine if we should store or compress based on original\n const store = entry.header.method === 0;\n\n archive.append(entryData, {\n name: entryName,\n date: mtime,\n store: store,\n });\n }\n\n archive.finalize();\n });\n }\n\n /**\n * Get the appropriate HTML injector for the authoring tool\n */\n private getHtmlInjector(tool: AuthoringTool): HtmlInjector | StorylineHtmlInjector {\n switch (tool) {\n case 'storyline':\n return this.storylineHtmlInjector;\n case 'rise':\n default:\n // Use Rise injector as default (works for most SCORM packages)\n return this.riseHtmlInjector;\n }\n }\n\n /**\n * Get the base folder for fallback files based on authoring tool\n */\n private getFallbackBaseFolder(toolInfo: AuthoringToolInfo): string {\n switch (toolInfo.tool) {\n case 'storyline':\n // Storyline uses html5/ folder structure\n return 'html5';\n case 'rise':\n return 'scormcontent';\n case 'xyleme':\n // Xyleme thin packs have files at root level\n return '';\n default:\n // For unknown tools, try to determine from main HTML path\n const htmlPath = toolInfo.mainHtmlPath;\n if (htmlPath.includes('/')) {\n return htmlPath.split('/')[0];\n }\n // Root level - use empty string (files at root)\n return '';\n }\n }\n\n /**\n * Build manifest paths from the actual file paths that were added\n * This ensures the manifest references the correct paths regardless of authoring tool\n */\n private buildManifestPaths(addedFiles: string[]): import('./manifest-updater.js').ManifestPaths {\n const paths: import('./manifest-updater.js').ManifestPaths = {};\n\n for (const filePath of addedFiles) {\n if (this.config.skin && filePath.endsWith(`skin/${this.config.skin}/style.css`)) {\n paths.skinCss = filePath;\n } else if (this.config.skin && filePath.endsWith(`skin/${this.config.skin}/script.js`)) {\n paths.skinJs = filePath;\n } else if (filePath.endsWith(this.config.cssBefore.filename)) {\n paths.cssBefore = filePath;\n } else if (filePath.endsWith(this.config.cssAfter.filename)) {\n paths.cssAfter = filePath;\n } else if (filePath.endsWith(this.config.jsBefore.filename)) {\n paths.jsBefore = filePath;\n } else if (filePath.endsWith(this.config.jsAfter.filename)) {\n paths.jsAfter = filePath;\n }\n }\n\n return paths;\n }\n\n /**\n * Generate assets from all enabled plugins\n */\n private generatePluginAssets(): GeneratedPluginAssets {\n const plugins = this.config.plugins || {};\n const enabledPlugins = Object.entries(plugins).filter(([_, cfg]) => cfg.enabled);\n\n if (enabledPlugins.length === 0) {\n return { cssBefore: '', cssAfter: '', jsBefore: '', jsAfter: '' };\n }\n\n console.log(`[Patcher] Generating assets for ${enabledPlugins.length} enabled plugin(s)`);\n return pluginRegistry.generateAssets(plugins);\n }\n\n /**\n * Add local fallback files to the ZIP\n * Priority: options > fetched > defaults\n * Plugin assets are appended after the main content\n */\n private addFallbackFiles(\n zip: AdmZip,\n options: PatchOptions,\n fetched: FetchedFallbacks = {},\n toolInfo?: AuthoringToolInfo\n ): string[] {\n const added: string[] = [];\n const baseFolder = toolInfo ? this.getFallbackBaseFolder(toolInfo) : 'scormcontent';\n const basePath = baseFolder ? `${baseFolder}/` : '';\n const cssFolder = `${basePath}${this.config.localFolders.css}`;\n const jsFolder = `${basePath}${this.config.localFolders.js}`;\n\n // Generate plugin assets\n const pluginAssets = this.generatePluginAssets();\n const hasPluginCssBefore = pluginAssets.cssBefore.length > 0;\n const hasPluginCssAfter = pluginAssets.cssAfter.length > 0;\n const hasPluginJsBefore = pluginAssets.jsBefore.length > 0;\n const hasPluginJsAfter = pluginAssets.jsAfter.length > 0;\n\n // CSS Before (priority: options > fetched > default) + plugins\n if (this.config.cssBefore.enabled) {\n const path = `${cssFolder}/${this.config.cssBefore.filename}`;\n let content = options.cssBeforeContent ?? fetched.cssBefore ?? DEFAULT_CSS_BEFORE;\n if (hasPluginCssBefore) {\n content += '\\n\\n/* === PLUGIN CSS (Before) === */\\n' + pluginAssets.cssBefore;\n console.log(`[Patcher] Added plugin CSS (before) to ${path}`);\n }\n zip.addFile(path, Buffer.from(content, 'utf-8'));\n added.push(path);\n if (fetched.cssBefore) console.log(`[Patcher] Using fetched content for ${path}`);\n }\n\n // CSS After (priority: options > fetched > default) + plugins\n if (this.config.cssAfter.enabled) {\n const path = `${cssFolder}/${this.config.cssAfter.filename}`;\n let content = options.cssAfterContent ?? fetched.cssAfter ?? DEFAULT_CSS_AFTER;\n if (hasPluginCssAfter) {\n content += '\\n\\n/* === PLUGIN CSS (After) === */\\n' + pluginAssets.cssAfter;\n console.log(`[Patcher] Added plugin CSS (after) to ${path}`);\n }\n zip.addFile(path, Buffer.from(content, 'utf-8'));\n added.push(path);\n if (fetched.cssAfter) console.log(`[Patcher] Using fetched content for ${path}`);\n }\n\n // JS Before (priority: options > fetched > default) + plugins\n if (this.config.jsBefore.enabled) {\n const path = `${jsFolder}/${this.config.jsBefore.filename}`;\n let content = options.jsBeforeContent ?? fetched.jsBefore ?? DEFAULT_JS_BEFORE;\n if (hasPluginJsBefore) {\n content += '\\n\\n// === PLUGIN JS (Before) ===\\n' + pluginAssets.jsBefore;\n console.log(`[Patcher] Added plugin JS (before) to ${path}`);\n }\n zip.addFile(path, Buffer.from(content, 'utf-8'));\n added.push(path);\n if (fetched.jsBefore) console.log(`[Patcher] Using fetched content for ${path}`);\n }\n\n // JS After (priority: options > fetched > default) + plugins\n if (this.config.jsAfter.enabled) {\n const path = `${jsFolder}/${this.config.jsAfter.filename}`;\n let content = options.jsAfterContent ?? fetched.jsAfter ?? DEFAULT_JS_AFTER;\n if (hasPluginJsAfter) {\n content += '\\n\\n// === PLUGIN JS (After) ===\\n' + pluginAssets.jsAfter;\n console.log(`[Patcher] Added plugin JS (after) to ${path}`);\n }\n zip.addFile(path, Buffer.from(content, 'utf-8'));\n added.push(path);\n if (fetched.jsAfter) console.log(`[Patcher] Using fetched content for ${path}`);\n }\n\n // Skin CSS fallback (fetched > placeholder)\n if (this.config.skin) {\n const skinCssPath = `${basePath}skin/${this.config.skin}/style.css`;\n const skinCssContent = fetched.skinCss ?? `/* PA-Patcher: Skin CSS (${this.config.skin}) */\\n`;\n zip.addFile(skinCssPath, Buffer.from(skinCssContent, 'utf-8'));\n added.push(skinCssPath);\n if (fetched.skinCss) console.log(`[Patcher] Using fetched skin CSS for ${skinCssPath}`);\n else console.log(`[Patcher] Added placeholder skin CSS: ${skinCssPath}`);\n\n const skinJsPath = `${basePath}skin/${this.config.skin}/script.js`;\n const skinJsContent = fetched.skinJs ?? `// PA-Patcher: Skin JS (${this.config.skin})\\n`;\n zip.addFile(skinJsPath, Buffer.from(skinJsContent, 'utf-8'));\n added.push(skinJsPath);\n if (fetched.skinJs) console.log(`[Patcher] Using fetched skin JS for ${skinJsPath}`);\n else console.log(`[Patcher] Added placeholder skin JS: ${skinJsPath}`);\n }\n\n return added;\n }\n\n /**\n * Get the current configuration\n */\n getConfig(): PatchAdamsConfig {\n return this.config;\n }\n\n /**\n * Convenience method to patch a buffer and return only the patched buffer.\n * Used by package-uploader integration.\n * @param buffer - Input ZIP file as Buffer\n * @returns Patched ZIP file as Buffer\n */\n async patchBuffer(buffer: Buffer, options?: PatchOptions): Promise<Buffer> {\n console.log(`[Patcher] patchBuffer called with ${buffer.length} bytes`);\n const { buffer: patchedBuffer, result } = await this.patch(buffer, options);\n console.log(`[Patcher] Patch result:`, JSON.stringify(result, null, 2));\n console.log(`[Patcher] Returning ${patchedBuffer.length} bytes`);\n return patchedBuffer;\n }\n}\n\nexport { HtmlInjector } from './html-injector.js';\nexport { StorylineHtmlInjector } from './storyline-html-injector.js';\nexport { ManifestUpdater, type ManifestPaths } from './manifest-updater.js';\n","import type AdmZip from 'adm-zip';\n\n/**\n * Comprehensive course metadata extracted from manifests\n */\nexport interface CourseMetadata {\n /** The Rise course ID (from manifest identifier) */\n courseId: string;\n /** Source of the course ID */\n courseIdSource: 'manifest' | 'tincan' | 'cmi5' | 'fallback';\n /** Package format */\n format: 'scorm12' | 'scorm2004-3' | 'scorm2004-4' | 'cmi5' | 'xapi' | 'aicc' | 'unknown';\n /** Course title */\n title: string | null;\n /** Course description */\n description: string | null;\n /** Organization/item identifier within the manifest */\n organizationId: string | null;\n /** Launch URL/entry point */\n launchUrl: string | null;\n /** Mastery score (if specified) */\n masteryScore: number | null;\n /** Duration/time limit (if specified) */\n duration: string | null;\n /** Language code */\n language: string | null;\n /** Version info from manifest */\n version: string | null;\n /** Timestamp when this metadata was extracted */\n extractedAt: string;\n /** Unique ID generated for this specific patch operation */\n patchId?: string;\n}\n\n/**\n * Extract comprehensive course metadata from a course package\n */\nexport function extractCourseMetadata(zip: AdmZip, format: string): CourseMetadata {\n const metadata: CourseMetadata = {\n courseId: 'unknown',\n courseIdSource: 'fallback',\n format: format as CourseMetadata['format'],\n title: null,\n description: null,\n organizationId: null,\n launchUrl: null,\n masteryScore: null,\n duration: null,\n language: null,\n version: null,\n extractedAt: new Date().toISOString(),\n };\n\n // Try to extract from imsmanifest.xml (SCORM)\n const manifest = zip.getEntry('imsmanifest.xml');\n if (manifest) {\n extractFromImsManifest(manifest.getData().toString('utf-8'), metadata);\n }\n\n // Try to extract from tincan.xml (xAPI)\n const tincan = zip.getEntry('tincan.xml');\n if (tincan) {\n extractFromTincan(tincan.getData().toString('utf-8'), metadata);\n }\n\n // Try to extract from cmi5.xml\n const cmi5 = zip.getEntry('cmi5.xml');\n if (cmi5) {\n extractFromCmi5(cmi5.getData().toString('utf-8'), metadata);\n }\n\n // Try to extract title from index.html as fallback\n if (!metadata.title) {\n const indexHtml = zip.getEntry('scormcontent/index.html');\n if (indexHtml) {\n const content = indexHtml.getData().toString('utf-8');\n const titleMatch = content.match(/<title>([^<]+)<\\/title>/i);\n if (titleMatch) {\n metadata.title = titleMatch[1].trim();\n }\n }\n }\n\n return metadata;\n}\n\n/**\n * Extract metadata from imsmanifest.xml (SCORM 1.2 and 2004)\n */\nfunction extractFromImsManifest(content: string, metadata: CourseMetadata): void {\n // Course ID from manifest identifier\n const identifierMatch = content.match(/<manifest[^>]+identifier\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (identifierMatch) {\n metadata.courseId = identifierMatch[1];\n metadata.courseIdSource = 'manifest';\n }\n\n // Version from manifest version attribute\n const versionMatch = content.match(/<manifest[^>]+version\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (versionMatch) {\n metadata.version = versionMatch[1];\n }\n\n // Title - try multiple locations\n // 1. From <title> within <organization>\n const orgTitleMatch = content.match(/<organization[^>]*>[\\s\\S]*?<title>([^<]+)<\\/title>/i);\n if (orgTitleMatch) {\n metadata.title = orgTitleMatch[1].trim();\n }\n\n // 2. From <lom:title> or <general><title>\n if (!metadata.title) {\n const lomTitleMatch = content.match(/<(?:lom:)?title>\\s*<(?:lom:)?langstring[^>]*>([^<]+)<\\/(?:lom:)?langstring>/i);\n if (lomTitleMatch) {\n metadata.title = lomTitleMatch[1].trim();\n }\n }\n\n // Description from LOM metadata\n const descMatch = content.match(/<(?:lom:)?description>\\s*<(?:lom:)?langstring[^>]*>([^<]+)<\\/(?:lom:)?langstring>/i);\n if (descMatch) {\n metadata.description = descMatch[1].trim();\n }\n\n // Organization identifier\n const orgIdMatch = content.match(/<organization[^>]+identifier\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (orgIdMatch) {\n metadata.organizationId = orgIdMatch[1];\n }\n\n // Launch URL from <resource> href\n const resourceMatch = content.match(/<resource[^>]+href\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (resourceMatch) {\n metadata.launchUrl = resourceMatch[1];\n }\n\n // Mastery score (SCORM 1.2 style)\n const masteryMatch = content.match(/<adlcp:masteryscore>([^<]+)<\\/adlcp:masteryscore>/i);\n if (masteryMatch) {\n const score = parseFloat(masteryMatch[1]);\n if (!isNaN(score)) {\n metadata.masteryScore = score;\n }\n }\n\n // Mastery score (SCORM 2004 style - completion threshold)\n if (metadata.masteryScore === null) {\n const thresholdMatch = content.match(/<imsss:minNormalizedMeasure>([^<]+)<\\/imsss:minNormalizedMeasure>/i);\n if (thresholdMatch) {\n const score = parseFloat(thresholdMatch[1]);\n if (!isNaN(score)) {\n metadata.masteryScore = score * 100; // Convert 0-1 to 0-100\n }\n }\n }\n\n // Duration/time limit\n const durationMatch = content.match(/<(?:adlcp:)?timelimitaction>([^<]+)<\\/(?:adlcp:)?timelimitaction>/i);\n if (durationMatch) {\n metadata.duration = durationMatch[1].trim();\n }\n\n // Language from xml:lang or lom:language\n const langMatch = content.match(/xml:lang\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (langMatch) {\n metadata.language = langMatch[1];\n }\n if (!metadata.language) {\n const lomLangMatch = content.match(/<(?:lom:)?language>([^<]+)<\\/(?:lom:)?language>/i);\n if (lomLangMatch) {\n metadata.language = lomLangMatch[1].trim();\n }\n }\n}\n\n/**\n * Extract metadata from tincan.xml (xAPI courses)\n */\nfunction extractFromTincan(content: string, metadata: CourseMetadata): void {\n // Activity ID as course ID\n const idMatch = content.match(/<activity[^>]+id\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (idMatch && metadata.courseIdSource === 'fallback') {\n metadata.courseId = idMatch[1];\n metadata.courseIdSource = 'tincan';\n }\n\n // Activity name/title\n const nameMatch = content.match(/<name[^>]*>([^<]+)<\\/name>/i);\n if (nameMatch && !metadata.title) {\n metadata.title = nameMatch[1].trim();\n }\n\n // Activity description\n const descMatch = content.match(/<description[^>]*>([^<]+)<\\/description>/i);\n if (descMatch && !metadata.description) {\n metadata.description = descMatch[1].trim();\n }\n\n // Launch URL\n const launchMatch = content.match(/<launch[^>]*>([^<]+)<\\/launch>/i);\n if (launchMatch) {\n metadata.launchUrl = launchMatch[1].trim();\n }\n}\n\n/**\n * Extract metadata from cmi5.xml\n */\nfunction extractFromCmi5(content: string, metadata: CourseMetadata): void {\n // Course ID\n const courseIdMatch = content.match(/<course[^>]+id\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (courseIdMatch && metadata.courseIdSource === 'fallback') {\n metadata.courseId = courseIdMatch[1];\n metadata.courseIdSource = 'cmi5';\n }\n\n // Course title\n const titleMatch = content.match(/<title[^>]*>([^<]+)<\\/title>/i);\n if (titleMatch && !metadata.title) {\n metadata.title = titleMatch[1].trim();\n }\n\n // Course description\n const descMatch = content.match(/<description[^>]*>([^<]+)<\\/description>/i);\n if (descMatch && !metadata.description) {\n metadata.description = descMatch[1].trim();\n }\n\n // AU launch URL\n const launchMatch = content.match(/<url[^>]*>([^<]+)<\\/url>/i);\n if (launchMatch) {\n metadata.launchUrl = launchMatch[1].trim();\n }\n\n // Mastery score from moveOn\n const moveOnMatch = content.match(/moveOn\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (moveOnMatch) {\n // cmi5 uses moveOn values like \"Passed\", \"Completed\", \"CompletedAndPassed\"\n // If PassedOrFailed is required, there's likely a mastery score\n if (moveOnMatch[1].toLowerCase().includes('passed')) {\n const masteryMatch = content.match(/masteryScore\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (masteryMatch) {\n const score = parseFloat(masteryMatch[1]);\n if (!isNaN(score)) {\n metadata.masteryScore = score;\n }\n }\n }\n }\n}\n","import type {\n PatchAdamsPlugin,\n PluginConfig,\n PluginsConfig,\n GeneratedPluginAssets,\n} from './types.js';\n\n/**\n * Plugin Registry for Patch-Adams\n *\n * Manages registration and asset generation for plugins.\n * Plugins are registered at module load time and invoked during patching\n * based on the configuration.\n */\nexport class PluginRegistry {\n private plugins: Map<string, PatchAdamsPlugin> = new Map();\n\n /**\n * Register a plugin with the registry.\n * Plugins should be registered at module initialization time.\n *\n * @param plugin - The plugin to register\n * @throws Error if a plugin with the same name is already registered\n */\n register(plugin: PatchAdamsPlugin): void {\n if (this.plugins.has(plugin.name)) {\n throw new Error(`Plugin \"${plugin.name}\" is already registered`);\n }\n this.plugins.set(plugin.name, plugin);\n console.log(`[PA-Plugins] Registered: ${plugin.name} v${plugin.version}`);\n }\n\n /**\n * Unregister a plugin by name.\n *\n * @param name - The plugin name to unregister\n * @returns true if the plugin was removed, false if it wasn't registered\n */\n unregister(name: string): boolean {\n const removed = this.plugins.delete(name);\n if (removed) {\n console.log(`[PA-Plugins] Unregistered: ${name}`);\n }\n return removed;\n }\n\n /**\n * Get a registered plugin by name.\n *\n * @param name - The plugin name\n * @returns The plugin or undefined if not found\n */\n getPlugin(name: string): PatchAdamsPlugin | undefined {\n return this.plugins.get(name);\n }\n\n /**\n * Get all registered plugins.\n *\n * @returns Array of all registered plugins\n */\n getAllPlugins(): PatchAdamsPlugin[] {\n return Array.from(this.plugins.values());\n }\n\n /**\n * Get names of all registered plugins.\n *\n * @returns Array of plugin names\n */\n getPluginNames(): string[] {\n return Array.from(this.plugins.keys());\n }\n\n /**\n * Check if a plugin is registered.\n *\n * @param name - The plugin name to check\n * @returns true if registered\n */\n hasPlugin(name: string): boolean {\n return this.plugins.has(name);\n }\n\n /**\n * Generate combined assets from all enabled plugins.\n *\n * @param pluginConfigs - Configuration for each plugin (keyed by plugin name)\n * @returns Combined CSS and JS assets from all enabled plugins\n */\n generateAssets(pluginConfigs: PluginsConfig): GeneratedPluginAssets {\n const result: GeneratedPluginAssets = {\n cssBefore: '',\n cssAfter: '',\n jsBefore: '',\n jsAfter: '',\n };\n\n for (const [pluginName, rawConfig] of Object.entries(pluginConfigs)) {\n // Skip if plugin is not enabled\n if (!rawConfig.enabled) {\n console.log(`[PA-Plugins] Skipping disabled plugin: ${pluginName}`);\n continue;\n }\n\n // Get the plugin\n const plugin = this.plugins.get(pluginName);\n if (!plugin) {\n console.warn(`[PA-Plugins] Plugin \"${pluginName}\" not found in registry, skipping`);\n continue;\n }\n\n try {\n // Validate configuration against plugin schema\n const validatedConfig = plugin.configSchema.parse(rawConfig) as PluginConfig;\n console.log(`[PA-Plugins] Generating assets for: ${pluginName}`);\n\n // Generate CSS\n if (plugin.generateCss) {\n const css = plugin.generateCss(validatedConfig);\n if (css.before) {\n result.cssBefore += `/* === Plugin: ${pluginName} === */\\n${css.before}\\n\\n`;\n }\n if (css.after) {\n result.cssAfter += `/* === Plugin: ${pluginName} === */\\n${css.after}\\n\\n`;\n }\n }\n\n // Generate JS\n if (plugin.generateJs) {\n const js = plugin.generateJs(validatedConfig);\n if (js.before) {\n result.jsBefore += `// === Plugin: ${pluginName} ===\\n${js.before}\\n\\n`;\n }\n if (js.after) {\n result.jsAfter += `// === Plugin: ${pluginName} ===\\n${js.after}\\n\\n`;\n }\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`[PA-Plugins] Error processing plugin \"${pluginName}\": ${message}`);\n // Continue with other plugins\n }\n }\n\n return result;\n }\n\n /**\n * Clear all registered plugins.\n * Useful for testing.\n */\n clear(): void {\n this.plugins.clear();\n console.log('[PA-Plugins] Registry cleared');\n }\n}\n\n/**\n * Global plugin registry instance.\n * Use this singleton to register and access plugins.\n */\nexport const pluginRegistry = new PluginRegistry();\n","#!/usr/bin/env node\nimport { Command } from 'commander';\nimport { readFileSync, writeFileSync, existsSync } from 'fs';\nimport { resolve, basename } from 'path';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { Patcher } from './patcher/index.js';\nimport { loadConfig, generateConfigTemplate, mergeWithDefaults } from './config/index.js';\nimport { PatchAdamsConfigSchema } from './config/schema.js';\n\nconst program = new Command();\n\nprogram\n .name('patch-adams')\n .description('Patch Rise course packages to inject remote CSS/JS with local fallback')\n .version('1.0.0');\n\nprogram\n .command('patch <input>')\n .description('Patch a Rise course ZIP file')\n .option('-o, --output <path>', 'Output file path (default: <input>-patched.zip)')\n .option('-c, --config <path>', 'Configuration file path (default: ./patch-adams.config.ts)')\n .option('--css-before <path>', 'Local CSS file for \"before\" fallback')\n .option('--css-after <path>', 'Local CSS file for \"after\" fallback')\n .option('--js-before <path>', 'Local JS file for \"before\" fallback')\n .option('--js-after <path>', 'Local JS file for \"after\" fallback')\n .option('--no-manifest', 'Skip updating manifest files')\n .action(async (input: string, options) => {\n const spinner = ora('Loading configuration...').start();\n\n try {\n // Load config\n let config;\n const configPath = options.config || './patch-adams.config.ts';\n\n if (existsSync(resolve(configPath))) {\n config = await loadConfig(configPath);\n spinner.text = 'Configuration loaded';\n } else {\n spinner.text = 'No config file found, using defaults';\n config = mergeWithDefaults({});\n }\n\n // Override manifest update setting\n if (options.manifest === false) {\n config = { ...config, updateManifests: false };\n }\n\n // Validate config\n const validatedConfig = PatchAdamsConfigSchema.parse(config);\n\n spinner.text = 'Reading input file...';\n\n // Read input ZIP\n const inputPath = resolve(input);\n if (!existsSync(inputPath)) {\n throw new Error(`Input file not found: ${inputPath}`);\n }\n const inputBuffer = readFileSync(inputPath);\n\n // Read optional CSS/JS files\n const patchOptions: {\n cssBeforeContent?: string;\n cssAfterContent?: string;\n jsBeforeContent?: string;\n jsAfterContent?: string;\n } = {};\n\n if (options.cssBefore) {\n patchOptions.cssBeforeContent = readFileSync(resolve(options.cssBefore), 'utf-8');\n }\n\n if (options.cssAfter) {\n patchOptions.cssAfterContent = readFileSync(resolve(options.cssAfter), 'utf-8');\n }\n\n if (options.jsBefore) {\n patchOptions.jsBeforeContent = readFileSync(resolve(options.jsBefore), 'utf-8');\n }\n\n if (options.jsAfter) {\n patchOptions.jsAfterContent = readFileSync(resolve(options.jsAfter), 'utf-8');\n }\n\n // Pass original package filename for LRS statement searchability\n (patchOptions as any).packageName = basename(inputPath);\n\n spinner.text = 'Patching package...';\n\n // Patch\n const patcher = new Patcher(validatedConfig);\n const { buffer, result } = await patcher.patch(inputBuffer, patchOptions);\n\n // Determine output path\n const outputPath =\n options.output || inputPath.replace(/\\.zip$/i, '-patched.zip');\n\n spinner.text = 'Writing output file...';\n writeFileSync(outputPath, buffer);\n\n spinner.succeed(chalk.green('Patching complete!'));\n\n // Display results\n console.log('');\n console.log(chalk.blue('Format detected:'), result.formatDisplayName);\n console.log(chalk.blue('Remote domain:'), validatedConfig.remoteDomain);\n console.log('');\n\n if (result.filesModified.length > 0) {\n console.log(chalk.blue('Files modified:'));\n result.filesModified.forEach((f) => console.log(` ${chalk.gray('•')} ${f}`));\n }\n\n if (result.filesAdded.length > 0) {\n console.log(chalk.blue('Files added:'));\n result.filesAdded.forEach((f) => console.log(` ${chalk.gray('•')} ${f}`));\n }\n\n if (result.warnings.length > 0) {\n console.log('');\n console.log(chalk.yellow('Warnings:'));\n result.warnings.forEach((w) => console.log(` ${chalk.yellow('!')} ${w}`));\n }\n\n console.log('');\n console.log(chalk.green('Output:'), outputPath);\n } catch (error) {\n spinner.fail(chalk.red('Patching failed'));\n console.error(\n chalk.red(error instanceof Error ? error.message : String(error))\n );\n process.exit(1);\n }\n });\n\nprogram\n .command('init')\n .description('Create a sample configuration file')\n .option('-o, --output <path>', 'Output path (default: ./patch-adams.config.ts)')\n .action((options) => {\n const outputPath = resolve(options.output || './patch-adams.config.ts');\n\n if (existsSync(outputPath)) {\n console.log(\n chalk.yellow(`Configuration file already exists: ${outputPath}`)\n );\n console.log(chalk.yellow('Use --output to specify a different path'));\n process.exit(1);\n }\n\n const template = generateConfigTemplate();\n writeFileSync(outputPath, template);\n console.log(chalk.green(`Configuration file created: ${outputPath}`));\n console.log('');\n console.log('Next steps:');\n console.log(` 1. Edit ${chalk.cyan(basename(outputPath))} to configure your settings`);\n console.log(` 2. Run ${chalk.cyan('patch-adams patch <input.zip>')} to patch a course`);\n });\n\nprogram\n .command('info <input>')\n .description('Display information about a Rise course package')\n .action(async (input: string) => {\n const spinner = ora('Analyzing package...').start();\n\n try {\n const inputPath = resolve(input);\n if (!existsSync(inputPath)) {\n throw new Error(`Input file not found: ${inputPath}`);\n }\n\n const inputBuffer = readFileSync(inputPath);\n const AdmZip = (await import('adm-zip')).default;\n const zip = new AdmZip(inputBuffer);\n\n const { FormatDetector } = await import('./detectors/index.js');\n const detector = new FormatDetector();\n const format = detector.detect(zip);\n const formatName = detector.getFormatDisplayName(format);\n\n spinner.succeed('Package analyzed');\n\n console.log('');\n console.log(chalk.blue('File:'), inputPath);\n console.log(chalk.blue('Format:'), formatName);\n console.log('');\n\n // Check for key files\n const keyFiles = [\n 'scormcontent/index.html',\n 'scormdriver/indexAPI.html',\n 'imsmanifest.xml',\n 'cmi5.xml',\n 'tincan.xml',\n ];\n\n console.log(chalk.blue('Key files:'));\n for (const file of keyFiles) {\n const exists = zip.getEntry(file) !== null;\n const icon = exists ? chalk.green('✓') : chalk.gray('○');\n console.log(` ${icon} ${file}`);\n }\n\n // Count total files\n const entries = zip.getEntries();\n console.log('');\n console.log(chalk.blue('Total files:'), entries.length);\n } catch (error) {\n spinner.fail(chalk.red('Analysis failed'));\n console.error(\n chalk.red(error instanceof Error ? error.message : String(error))\n );\n process.exit(1);\n }\n });\n\nprogram.parse();\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/detectors/format-detector.ts","../src/detectors/authoring-tool-detector.ts","../src/detectors/index.ts","../src/config/schema.ts","../src/config/defaults.ts","../src/config/loader.ts","../src/templates/css-before.ts","../src/templates/css-after.ts","../src/templates/lrs-bridge.ts","../src/templates/js-before.ts","../src/templates/js-after.ts","../src/templates/skin-css.ts","../src/templates/skin-js.ts","../src/patcher/html-injector.ts","../src/patcher/storyline-html-injector.ts","../src/patcher/manifest-updater.ts","../src/patcher/index.ts","../src/fingerprint/course-metadata.ts","../src/plugins/registry.ts","../src/cli.ts"],"names":["z","resolve","existsSync","extname","readFileSync","pathToFileURL","AdmZip","crypto","archiver","PassThrough","Command","ora","basename","writeFileSync","chalk","FormatDetector"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAiBa,cAAA;AAjBb,IAAA,oBAAA,GAAA,KAAA,CAAA;AAAA,EAAA,kCAAA,GAAA;AAiBO,IAAM,iBAAN,MAAqB;AAAA;AAAA;AAAA;AAAA,MAI1B,OAAO,GAAA,EAA4B;AAEjC,QAAA,IAAI,GAAA,CAAI,QAAA,CAAS,UAAU,CAAA,EAAG;AAC5B,UAAA,OAAO,MAAA;AAAA,QACT;AAGA,QAAA,IAAI,GAAA,CAAI,QAAA,CAAS,YAAY,CAAA,EAAG;AAC9B,UAAA,OAAO,MAAA;AAAA,QACT;AAGA,QAAA,MAAM,QAAA,GAAW,GAAA,CAAI,QAAA,CAAS,iBAAiB,CAAA;AAC/C,QAAA,IAAI,QAAA,EAAU;AACZ,UAAA,MAAM,OAAA,GAAU,QAAA,CAAS,OAAA,EAAQ,CAAE,SAAS,OAAO,CAAA;AACnD,UAAA,OAAO,IAAA,CAAK,mBAAmB,OAAO,CAAA;AAAA,QACxC;AAGA,QAAA,IAAI,IAAA,CAAK,YAAA,CAAa,GAAG,CAAA,EAAG;AAC1B,UAAA,OAAO,MAAA;AAAA,QACT;AAEA,QAAA,OAAO,SAAA;AAAA,MACT;AAAA;AAAA;AAAA;AAAA,MAKQ,mBAAmB,OAAA,EAAgC;AAEzD,QAAA,IAAI,QAAQ,QAAA,CAAS,aAAa,KAAK,OAAA,CAAQ,QAAA,CAAS,SAAS,CAAA,EAAG;AAClE,UAAA,OAAO,aAAA;AAAA,QACT;AAGA,QAAA,IACE,OAAA,CAAQ,QAAA,CAAS,aAAa,CAAA,IAC9B,OAAA,CAAQ,QAAA,CAAS,UAAU,CAAA,IAC3B,OAAA,CAAQ,QAAA,CAAS,YAAY,CAAA,EAC7B;AACA,UAAA,OAAO,aAAA;AAAA,QACT;AAGA,QAAA,IAAI,QAAQ,QAAA,CAAS,MAAM,KAAK,OAAA,CAAQ,QAAA,CAAS,QAAQ,CAAA,EAAG;AAC1D,UAAA,OAAO,aAAA;AAAA,QACT;AAGA,QAAA,IACE,OAAA,CAAQ,QAAA,CAAS,KAAK,CAAA,IACtB,OAAA,CAAQ,QAAA,CAAS,gBAAgB,CAAA,IACjC,OAAA,CAAQ,QAAA,CAAS,kBAAkB,CAAA,EACnC;AACA,UAAA,OAAO,SAAA;AAAA,QACT;AAGA,QAAA,IAAI,OAAA,CAAQ,WAAA,EAAY,CAAE,QAAA,CAAS,MAAM,CAAA,EAAG;AAC1C,UAAA,OAAO,MAAA;AAAA,QACT;AAGA,QAAA,OAAO,SAAA;AAAA,MACT;AAAA;AAAA;AAAA;AAAA,MAKQ,aAAa,GAAA,EAAsB;AACzC,QAAA,MAAM,cAAA,GAAiB,CAAC,MAAA,EAAQ,KAAA,EAAO,QAAQ,MAAM,CAAA;AACrD,QAAA,MAAM,OAAA,GAAU,IAAI,UAAA,EAAW;AAE/B,QAAA,KAAA,MAAW,SAAS,OAAA,EAAS;AAC3B,UAAA,MAAM,IAAA,GAAO,KAAA,CAAM,SAAA,CAAU,WAAA,EAAY;AACzC,UAAA,IAAI,cAAA,CAAe,KAAK,CAAC,GAAA,KAAQ,KAAK,QAAA,CAAS,GAAG,CAAC,CAAA,EAAG;AACpD,YAAA,OAAO,IAAA;AAAA,UACT;AAAA,QACF;AAEA,QAAA,OAAO,KAAA;AAAA,MACT;AAAA;AAAA;AAAA;AAAA,MAKA,qBAAqB,MAAA,EAA+B;AAClD,QAAA,MAAM,KAAA,GAAuC;AAAA,UAC3C,OAAA,EAAS,WAAA;AAAA,UACT,aAAA,EAAe,wBAAA;AAAA,UACf,aAAA,EAAe,wBAAA;AAAA,UACf,IAAA,EAAM,MAAA;AAAA,UACN,IAAA,EAAM,gBAAA;AAAA,UACN,IAAA,EAAM,MAAA;AAAA,UACN,OAAA,EAAS;AAAA,SACX;AAEA,QAAA,OAAO,MAAM,MAAM,CAAA;AAAA,MACrB;AAAA,KACF;AAAA,EAAA;AAAA,CAAA,CAAA;;;ACzHA,IA+Ba,qBAAA;AA/Bb,IAAA,4BAAA,GAAA,KAAA,CAAA;AAAA,EAAA,0CAAA,GAAA;AA+BO,IAAM,wBAAN,MAA4B;AAAA;AAAA;AAAA;AAAA,MAIjC,OAAO,GAAA,EAAgC;AAErC,QAAA,MAAM,UAAA,GAAa,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA;AACtC,QAAA,IAAI,YAAY,OAAO,UAAA;AAGvB,QAAA,MAAM,eAAA,GAAkB,IAAA,CAAK,eAAA,CAAgB,GAAG,CAAA;AAChD,QAAA,IAAI,iBAAiB,OAAO,eAAA;AAG5B,QAAA,MAAM,eAAA,GAAkB,IAAA,CAAK,eAAA,CAAgB,GAAG,CAAA;AAChD,QAAA,IAAI,iBAAiB,OAAO,eAAA;AAG5B,QAAA,MAAM,aAAA,GAAgB,IAAA,CAAK,aAAA,CAAc,GAAG,CAAA;AAC5C,QAAA,IAAI,eAAe,OAAO,aAAA;AAG1B,QAAA,MAAM,YAAA,GAAe,IAAA,CAAK,YAAA,CAAa,GAAG,CAAA;AAC1C,QAAA,IAAI,cAAc,OAAO,YAAA;AAGzB,QAAA,OAAO,IAAA,CAAK,cAAc,GAAG,CAAA;AAAA,MAC/B;AAAA;AAAA;AAAA;AAAA;AAAA,MAMQ,WAAW,GAAA,EAAuC;AACxD,QAAA,MAAM,UAAA,GAAa,GAAA,CAAI,QAAA,CAAS,yBAAyB,CAAA;AACzD,QAAA,IAAI,CAAC,YAAY,OAAO,IAAA;AAExB,QAAA,MAAM,OAAA,GAAU,UAAA,CAAW,OAAA,EAAQ,CAAE,SAAS,OAAO,CAAA;AAGrD,QAAA,IAAI,OAAA,CAAQ,QAAA,CAAS,eAAe,CAAA,EAAG;AAErC,UAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,KAAA,CAAM,gCAAgC,CAAA;AACnE,UAAA,OAAO;AAAA,YACL,IAAA,EAAM,MAAA;AAAA,YACN,WAAA,EAAa,iBAAA;AAAA,YACb,YAAA,EAAc,yBAAA;AAAA,YACd,OAAA,EAAS,eAAe,CAAC;AAAA,WAC3B;AAAA,QACF;AAGA,QAAA,IAAI,GAAA,CAAI,QAAA,CAAS,wBAAwB,CAAA,EAAG;AAC1C,UAAA,OAAO;AAAA,YACL,IAAA,EAAM,MAAA;AAAA,YACN,WAAA,EAAa,iBAAA;AAAA,YACb,YAAA,EAAc;AAAA,WAChB;AAAA,QACF;AAEA,QAAA,OAAO,IAAA;AAAA,MACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAWQ,gBAAgB,GAAA,EAAuC;AAC7D,QAAA,MAAM,UAAA,GAAa,GAAA,CAAI,QAAA,CAAS,YAAY,CAAA;AAC5C,QAAA,MAAM,QAAA,GAAW,GAAA,CAAI,QAAA,CAAS,gBAAgB,CAAA;AAG9C,QAAA,MAAM,YAAsB,EAAC;AAC7B,QAAA,IAAI,UAAA,EAAY,SAAA,CAAU,IAAA,CAAK,YAAY,CAAA;AAC3C,QAAA,IAAI,QAAA,EAAU,SAAA,CAAU,IAAA,CAAK,gBAAgB,CAAA;AAG7C,QAAA,IAAI,UAAA,EAAY;AACd,UAAA,MAAM,OAAA,GAAU,UAAA,CAAW,OAAA,EAAQ,CAAE,SAAS,OAAO,CAAA;AAGrD,UAAA,IACE,QAAQ,QAAA,CAAS,4BAA4B,KAC7C,OAAA,CAAQ,QAAA,CAAS,2BAA2B,CAAA,EAC5C;AACA,YAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,KAAA,CAAM,+CAA+C,CAAA;AAClF,YAAA,OAAO;AAAA,cACL,IAAA,EAAM,WAAA;AAAA,cACN,WAAA,EAAa,sBAAA;AAAA,cACb,YAAA,EAAc,UAAU,CAAC,CAAA;AAAA,cACzB,mBAAA,EAAqB,SAAA,CAAU,KAAA,CAAM,CAAC,CAAA;AAAA,cACtC,OAAA,EAAS,eAAe,CAAC;AAAA,aAC3B;AAAA,UACF;AAGA,UAAA,IAAI,OAAA,CAAQ,QAAA,CAAS,yBAAyB,CAAA,EAAG;AAC/C,YAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,KAAA,CAAM,iCAAiC,CAAA;AACpE,YAAA,OAAO;AAAA,cACL,IAAA,EAAM,WAAA;AAAA,cACN,WAAA,EAAa,sBAAA;AAAA,cACb,YAAA,EAAc,UAAU,CAAC,CAAA;AAAA,cACzB,mBAAA,EAAqB,SAAA,CAAU,KAAA,CAAM,CAAC,CAAA;AAAA,cACtC,OAAA,EAAS,eAAe,CAAC;AAAA,aAC3B;AAAA,UACF;AAAA,QACF;AAGA,QAAA,IAAI,QAAA,EAAU;AACZ,UAAA,MAAM,OAAA,GAAU,QAAA,CAAS,OAAA,EAAQ,CAAE,SAAS,OAAO,CAAA;AACnD,UAAA,IACE,QAAQ,QAAA,CAAS,4BAA4B,KAC7C,OAAA,CAAQ,QAAA,CAAS,yBAAyB,CAAA,EAC1C;AACA,YAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,KAAA,CAAM,iCAAiC,CAAA;AACpE,YAAA,OAAO;AAAA,cACL,IAAA,EAAM,WAAA;AAAA,cACN,WAAA,EAAa,sBAAA;AAAA,cACb,YAAA,EAAc,UAAU,CAAC,CAAA;AAAA,cACzB,mBAAA,EAAqB,SAAA,CAAU,KAAA,CAAM,CAAC,CAAA;AAAA,cACtC,OAAA,EAAS,eAAe,CAAC;AAAA,aAC3B;AAAA,UACF;AAAA,QACF;AAGA,QAAA,IAAI,GAAA,CAAI,QAAA,CAAS,uCAAuC,CAAA,EAAG;AAEzD,UAAA,IAAI,SAAA,CAAU,SAAS,CAAA,EAAG;AACxB,YAAA,OAAO;AAAA,cACL,IAAA,EAAM,WAAA;AAAA,cACN,WAAA,EAAa,sBAAA;AAAA,cACb,YAAA,EAAc,UAAU,CAAC,CAAA;AAAA,cACzB,mBAAA,EAAqB,SAAA,CAAU,KAAA,CAAM,CAAC;AAAA,aACxC;AAAA,UACF;AAAA,QACF;AAEA,QAAA,OAAO,IAAA;AAAA,MACT;AAAA;AAAA;AAAA;AAAA;AAAA,MAMQ,gBAAgB,GAAA,EAAuC;AAE7D,QAAA,MAAM,OAAA,GAAU,IAAI,UAAA,EAAW;AAC/B,QAAA,KAAA,MAAW,SAAS,OAAA,EAAS;AAC3B,UAAA,MAAM,IAAA,GAAO,KAAA,CAAM,SAAA,CAAU,WAAA,EAAY;AACzC,UAAA,IAAI,KAAK,QAAA,CAAS,WAAW,KAAK,IAAA,CAAK,QAAA,CAAS,KAAK,CAAA,EAAG;AAEtD,YAAA,MAAM,SAAA,GAAY,GAAA,CAAI,QAAA,CAAS,YAAY,CAAA;AAC3C,YAAA,OAAO;AAAA,cACL,IAAA,EAAM,WAAA;AAAA,cACN,WAAA,EAAa,iBAAA;AAAA,cACb,YAAA,EAAc,YAAY,YAAA,GAAe;AAAA,aAC3C;AAAA,UACF;AAAA,QACF;AAGA,QAAA,MAAM,UAAA,GAAa,GAAA,CAAI,QAAA,CAAS,YAAY,CAAA;AAC5C,QAAA,IAAI,UAAA,EAAY;AACd,UAAA,MAAM,OAAA,GAAU,UAAA,CAAW,OAAA,EAAQ,CAAE,SAAS,OAAO,CAAA;AACrD,UAAA,IACE,OAAA,CAAQ,QAAA,CAAS,iBAAiB,CAAA,IAClC,OAAA,CAAQ,QAAA,CAAS,KAAK,CAAA,IACtB,OAAA,CAAQ,QAAA,CAAS,gBAAgB,CAAA,EACjC;AACA,YAAA,OAAO;AAAA,cACL,IAAA,EAAM,WAAA;AAAA,cACN,WAAA,EAAa,iBAAA;AAAA,cACb,YAAA,EAAc;AAAA,aAChB;AAAA,UACF;AAAA,QACF;AAEA,QAAA,OAAO,IAAA;AAAA,MACT;AAAA;AAAA;AAAA;AAAA;AAAA,MAMQ,cAAc,GAAA,EAAuC;AAC3D,QAAA,MAAM,UAAA,GAAa,GAAA,CAAI,QAAA,CAAS,YAAY,CAAA;AAC5C,QAAA,IAAI,UAAA,EAAY;AACd,UAAA,MAAM,OAAA,GAAU,UAAA,CAAW,OAAA,EAAQ,CAAE,SAAS,OAAO,CAAA;AACrD,UAAA,IACE,OAAA,CAAQ,QAAA,CAAS,WAAW,CAAA,IAC5B,OAAA,CAAQ,QAAA,CAAS,SAAS,CAAA,IAC1B,OAAA,CAAQ,QAAA,CAAS,UAAU,CAAA,EAC3B;AACA,YAAA,OAAO;AAAA,cACL,IAAA,EAAM,SAAA;AAAA,cACN,WAAA,EAAa,SAAA;AAAA,cACb,YAAA,EAAc;AAAA,aAChB;AAAA,UACF;AAAA,QACF;AAEA,QAAA,OAAO,IAAA;AAAA,MACT;AAAA;AAAA;AAAA;AAAA;AAAA,MAMQ,aAAa,GAAA,EAAuC;AAE1D,QAAA,MAAM,YAAA,GAAe,GAAA,CAAI,QAAA,CAAS,gBAAgB,CAAA,KAAM,IAAA;AACxD,QAAA,MAAM,iBAAA,GAAoB,GAAA,CAAI,QAAA,CAAS,cAAc,CAAA,KAAM,IAAA;AAG3D,QAAA,MAAM,UAAA,GAAa,GAAA,CAAI,QAAA,CAAS,YAAY,CAAA;AAC5C,QAAA,IAAI,UAAA,EAAY;AACd,UAAA,MAAM,OAAA,GAAU,UAAA,CAAW,OAAA,EAAQ,CAAE,SAAS,OAAO,CAAA;AAGrD,UAAA,IACE,OAAA,CAAQ,SAAS,eAAe,CAAA,IAChC,QAAQ,QAAA,CAAS,oBAAoB,CAAA,IACrC,YAAA,IACA,iBAAA,EACA;AAEA,YAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,KAAA,CAAM,2CAA2C,CAAA;AAG9E,YAAA,MAAM,sBAAgC,EAAC;AACvC,YAAA,MAAM,OAAA,GAAU,IAAI,UAAA,EAAW;AAC/B,YAAA,KAAA,MAAW,SAAS,OAAA,EAAS;AAE3B,cAAA,IACE,KAAA,CAAM,SAAA,CAAU,KAAA,CAAM,uEAAuE,CAAA,EAC7F;AACA,gBAAA,mBAAA,CAAoB,IAAA,CAAK,MAAM,SAAS,CAAA;AAAA,cAC1C;AAAA,YACF;AAEA,YAAA,OAAO;AAAA,cACL,IAAA,EAAM,QAAA;AAAA,cACN,WAAA,EAAa,gBAAA;AAAA,cACb,YAAA,EAAc,YAAA;AAAA,cACd,mBAAA,EAAqB,mBAAA,CAAoB,MAAA,GAAS,CAAA,GAAI,mBAAA,GAAsB,MAAA;AAAA,cAC5E,OAAA,EAAS,eAAe,CAAC;AAAA,aAC3B;AAAA,UACF;AAAA,QACF;AAEA,QAAA,OAAO,IAAA;AAAA,MACT;AAAA;AAAA;AAAA;AAAA;AAAA,MAMQ,cAAc,GAAA,EAAgC;AAEpD,QAAA,MAAM,SAAA,GAAY;AAAA,UAChB,YAAA;AAAA,UACA,gBAAA;AAAA,UACA,YAAA;AAAA,UACA,aAAA;AAAA,UACA,aAAA;AAAA,UACA;AAAA,SACF;AAEA,QAAA,KAAA,MAAW,YAAY,SAAA,EAAW;AAChC,UAAA,IAAI,GAAA,CAAI,QAAA,CAAS,QAAQ,CAAA,EAAG;AAC1B,YAAA,OAAO;AAAA,cACL,IAAA,EAAM,SAAA;AAAA,cACN,WAAA,EAAa,wBAAA;AAAA,cACb,YAAA,EAAc;AAAA,aAChB;AAAA,UACF;AAAA,QACF;AAGA,QAAA,MAAM,OAAA,GAAU,IAAI,UAAA,EAAW;AAC/B,QAAA,KAAA,MAAW,SAAS,OAAA,EAAS;AAC3B,UAAA,IACE,KAAA,CAAM,SAAA,CAAU,QAAA,CAAS,OAAO,CAAA,IAChC,CAAC,KAAA,CAAM,SAAA,CAAU,QAAA,CAAS,GAAG,CAAA,EAC7B;AACA,YAAA,OAAO;AAAA,cACL,IAAA,EAAM,SAAA;AAAA,cACN,WAAA,EAAa,wBAAA;AAAA,cACb,cAAc,KAAA,CAAM;AAAA,aACtB;AAAA,UACF;AAAA,QACF;AAGA,QAAA,OAAO;AAAA,UACL,IAAA,EAAM,SAAA;AAAA,UACN,WAAA,EAAa,wBAAA;AAAA,UACb,YAAA,EAAc;AAAA;AAAA,SAChB;AAAA,MACF;AAAA;AAAA;AAAA;AAAA,MAKA,mBAAmB,IAAA,EAA6B;AAC9C,QAAA,MAAM,KAAA,GAAuC;AAAA,UAC3C,IAAA,EAAM,iBAAA;AAAA,UACN,SAAA,EAAW,sBAAA;AAAA,UACX,SAAA,EAAW,iBAAA;AAAA,UACX,OAAA,EAAS,SAAA;AAAA,UACT,MAAA,EAAQ,gBAAA;AAAA,UACR,OAAA,EAAS;AAAA,SACX;AACA,QAAA,OAAO,MAAM,IAAI,CAAA;AAAA,MACnB;AAAA,KACF;AAAA,EAAA;AAAA,CAAA,CAAA;;;ACjWA,IAAA,iBAAA,GAAA,EAAA;AAAA,QAAA,CAAA,iBAAA,EAAA;AAAA,EAAA,qBAAA,EAAA,MAAA,qBAAA;AAAA,EAAA,cAAA,EAAA,MAAA;AAAA,CAAA,CAAA;AAAA,IAAA,cAAA,GAAA,KAAA,CAAA;AAAA,EAAA,wBAAA,GAAA;AAAA,IAAA,oBAAA,EAAA;AACA,IAAA,4BAAA,EAAA;AAAA,EAAA;AAAA,CAAA,CAAA;ACKO,IAAM,qBAAA,GAAwBA,MAAE,MAAA,CAAO;AAAA;AAAA,EAE5C,OAAA,EAASA,KAAA,CAAE,OAAA,EAAQ,CAAE,QAAQ,IAAI,CAAA;AAAA;AAAA,EAEjC,UAAA,EAAYA,KAAA,CAAE,OAAA,EAAQ,CAAE,QAAQ,IAAI,CAAA;AAAA;AAAA,EAEpC,eAAA,EAAiBA,KAAA,CAAE,OAAA,EAAQ,CAAE,QAAQ,IAAI,CAAA;AAAA;AAAA,EAEzC,YAAA,EAAcA,KAAA,CAAE,OAAA,EAAQ,CAAE,QAAQ,IAAI,CAAA;AAAA;AAAA,EAEtC,iBAAA,EAAmBA,KAAA,CAAE,OAAA,EAAQ,CAAE,QAAQ,IAAI,CAAA;AAAA;AAAA,EAE3C,gBAAA,EAAkBA,KAAA,CAAE,OAAA,EAAQ,CAAE,QAAQ,IAAI,CAAA;AAAA;AAAA,EAE1C,KAAA,EAAOA,KAAA,CAAE,OAAA,EAAQ,CAAE,QAAQ,KAAK,CAAA;AAAA;AAAA,EAEhC,WAAA,EAAaA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA;AAAA,EAEjC,kBAAA,EAAoBA,KAAA,CAAE,OAAA,EAAQ,CAAE,QAAQ,IAAI,CAAA;AAAA;AAAA,EAE5C,cAAA,EAAgBA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA;AAAA,EAEpC,aAAA,EAAeA,KAAA,CAAE,OAAA,EAAQ,CAAE,QAAQ,IAAI,CAAA;AAAA;AAAA,EAEvC,OAAA,EAASA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA;AAAA,EAE7B,gBAAA,EAAkBA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA;AAAA,EAEtC,mBAAA,EAAqBA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA;AAAA,EAEzC,YAAA,EAAcA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA;AAAA,EAElC,WAAA,EAAaA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA;AAAA,EAEjC,WAAA,EAAaA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAA;AAC1B,CAAC,CAAA;AAMM,IAAM,yBAAA,GAA4BA,MAAE,MAAA,CAAO;AAAA;AAAA,EAEhD,QAAA,EAAUA,KAAA,CAAE,MAAA,EAAO,CAAE,IAAI,CAAC,CAAA;AAAA;AAAA,EAE1B,OAAA,EAASA,KAAA,CAAE,OAAA,EAAQ,CAAE,QAAQ,IAAI;AACnC,CAAC,CAAA;AAMM,IAAM,sBAAA,GAAyBA,MAAE,MAAA,CAAO;AAAA;AAAA,EAE7C,QAAA,EAAUA,KAAA,CAAE,MAAA,EAAO,CAAE,IAAI,CAAC,CAAA;AAAA;AAAA,EAE1B,OAAA,EAASA,KAAA,CAAE,OAAA,EAAQ,CAAE,QAAQ,IAAI,CAAA;AAAA;AAAA,EAEjC,OAAA,EAASA,KAAA,CAAE,MAAA,EAAO,CAAE,GAAA,CAAI,GAAI,CAAA,CAAE,GAAA,CAAI,GAAK,CAAA,CAAE,OAAA,CAAQ,GAAI;AACvD,CAAC,CAAA;AAKM,IAAM,wBAAA,GAA2BA,MAAE,MAAA,CAAO;AAAA;AAAA,EAE/C,GAAA,EAAKA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAQ,KAAK,CAAA;AAAA;AAAA,EAE7B,EAAA,EAAIA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAQ,IAAI;AAC7B,CAAC,CAAA;AAMM,IAAM,sBAAA,GAAyBA,MAAE,MAAA,CAAO;AAAA;AAAA,EAE7C,OAAA,EAASA,KAAA,CAAE,OAAA,EAAQ,CAAE,QAAQ,KAAK;AACpC,CAAC,EAAE,WAAA,EAAY;AAKR,IAAM,mBAAA,GAAsBA,KAAA,CAAE,MAAA,CAAOA,KAAA,CAAE,MAAA,IAAU,sBAAsB,CAAA,CAAE,OAAA,CAAQ,EAAE,CAAA;AAKnF,IAAM,sBAAA,GAAyBA,MAAE,MAAA,CAAO;AAAA;AAAA,EAE7C,YAAA,EAAcA,KAAA,CAAE,MAAA,EAAO,CAAE,GAAA,EAAI;AAAA;AAAA,EAG7B,SAAA,EAAWA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAQ,YAAY,CAAA;AAAA;AAAA,EAG1C,YAAA,EAAcA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAQ,YAAY,CAAA;AAAA;AAAA,EAG7C,SAAA,EAAW,0BAA0B,OAAA,CAAQ;AAAA,IAC3C,QAAA,EAAU,YAAA;AAAA,IACV,OAAA,EAAS;AAAA,GACV,CAAA;AAAA;AAAA,EAGD,QAAA,EAAU,uBAAuB,OAAA,CAAQ;AAAA,IACvC,QAAA,EAAU,WAAA;AAAA,IACV,OAAA,EAAS,IAAA;AAAA,IACT,OAAA,EAAS;AAAA,GACV,CAAA;AAAA;AAAA,EAGD,QAAA,EAAU,0BAA0B,OAAA,CAAQ;AAAA,IAC1C,QAAA,EAAU,WAAA;AAAA,IACV,OAAA,EAAS;AAAA,GACV,CAAA;AAAA;AAAA,EAGD,OAAA,EAAS,uBAAuB,OAAA,CAAQ;AAAA,IACtC,QAAA,EAAU,UAAA;AAAA,IACV,OAAA,EAAS,IAAA;AAAA,IACT,OAAA,EAAS;AAAA,GACV,CAAA;AAAA;AAAA,EAGD,YAAA,EAAc,wBAAA,CAAyB,OAAA,CAAQ,EAAE,CAAA;AAAA;AAAA,EAGjD,eAAA,EAAiBA,KAAA,CAAE,OAAA,EAAQ,CAAE,QAAQ,IAAI,CAAA;AAAA;AAAA,EAGzC,cAAA,EAAgBA,KAAA,CAAE,OAAA,EAAQ,CAAE,QAAQ,IAAI,CAAA;AAAA;AAAA,EAGxC,SAAA,EAAW,qBAAA,CAAsB,OAAA,CAAQ,EAAE,CAAA;AAAA;AAAA,EAG3C,OAAA,EAAS,mBAAA;AAAA;AAAA,EAGT,MAAMA,KAAA,CAAE,MAAA,GAAS,GAAA,CAAI,CAAC,EAAE,QAAA;AAC1B,CAAC,CAAA;;;AC9IM,IAAM,aAAA,GAAkC;AAAA,EAC7C,YAAA,EAAc,wCAAA;AAAA,EAEd,SAAA,EAAW,YAAA;AAAA,EACX,YAAA,EAAc,YAAA;AAAA,EAEd,SAAA,EAAW;AAAA,IACT,QAAA,EAAU,YAAA;AAAA,IACV,OAAA,EAAS;AAAA,GACX;AAAA,EAEA,QAAA,EAAU;AAAA,IACR,QAAA,EAAU,WAAA;AAAA,IACV,OAAA,EAAS,IAAA;AAAA,IACT,OAAA,EAAS;AAAA,GACX;AAAA,EAEA,QAAA,EAAU;AAAA,IACR,QAAA,EAAU,WAAA;AAAA,IACV,OAAA,EAAS;AAAA,GACX;AAAA,EAEA,OAAA,EAAS;AAAA,IACP,QAAA,EAAU,UAAA;AAAA,IACV,OAAA,EAAS,IAAA;AAAA,IACT,OAAA,EAAS;AAAA,GACX;AAAA,EAEA,YAAA,EAAc;AAAA,IACZ,GAAA,EAAK,KAAA;AAAA,IACL,EAAA,EAAI;AAAA,GACN;AAAA,EAEA,eAAA,EAAiB,IAAA;AAAA,EAEjB,cAAA,EAAgB,IAAA;AAAA,EAEhB,SAAA,EAAW;AAAA,IACT,OAAA,EAAS,IAAA;AAAA,IACT,UAAA,EAAY,IAAA;AAAA,IACZ,eAAA,EAAiB,IAAA;AAAA,IACjB,YAAA,EAAc,IAAA;AAAA,IACd,iBAAA,EAAmB,IAAA;AAAA,IACnB,gBAAA,EAAkB,IAAA;AAAA,IAClB,KAAA,EAAO,KAAA;AAAA,IACP,WAAA,EAAa,MAAA;AAAA,IACb,kBAAA,EAAoB,IAAA;AAAA,IACpB,cAAA,EAAgB,MAAA;AAAA,IAChB,aAAA,EAAe;AAAA,GACjB;AAAA,EAEA,SAAS;AACX,CAAA;;;AC/CA,eAAsB,WAAW,UAAA,EAA+C;AAC9E,EAAA,MAAM,YAAA,GAAeC,aAAQ,UAAU,CAAA;AAEvC,EAAA,IAAI,CAACC,aAAA,CAAW,YAAY,CAAA,EAAG;AAC7B,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,8BAAA,EAAiC,YAAY,CAAA,CAAE,CAAA;AAAA,EACjE;AAEA,EAAA,MAAM,GAAA,GAAMC,YAAA,CAAQ,YAAY,CAAA,CAAE,WAAA,EAAY;AAC9C,EAAA,IAAI,SAAA;AAEJ,EAAA,IAAI,QAAQ,OAAA,EAAS;AACnB,IAAA,MAAM,OAAA,GAAUC,eAAA,CAAa,YAAA,EAAc,OAAO,CAAA;AAClD,IAAA,SAAA,GAAY,IAAA,CAAK,MAAM,OAAO,CAAA;AAAA,EAChC,WAAW,GAAA,KAAQ,KAAA,IAAS,GAAA,KAAQ,KAAA,IAAS,QAAQ,MAAA,EAAQ;AAE3D,IAAA,MAAM,OAAA,GAAUC,iBAAA,CAAc,YAAY,CAAA,CAAE,IAAA;AAC5C,IAAA,MAAM,MAAA,GAAS,MAAM,OAAO,OAAA,CAAA;AAC5B,IAAA,SAAA,GAAY,OAAO,OAAA,IAAW,MAAA;AAAA,EAChC,CAAA,MAAO;AACL,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,mCAAA,EAAsC,GAAG,CAAA,8BAAA,CAAgC,CAAA;AAAA,EAC3F;AAGA,EAAA,MAAM,SAAA,GAAY,sBAAA,CAAuB,KAAA,CAAM,SAAS,CAAA;AACxD,EAAA,OAAO,SAAA;AACT;AAKO,SAAS,kBAAkB,OAAA,EAAsD;AACtF,EAAA,OAAO,uBAAuB,KAAA,CAAM;AAAA,IAClC,GAAG,aAAA;AAAA,IACH,GAAG;AAAA,GACJ,CAAA;AACH;AAKO,SAAS,sBAAA,GAAiC;AAC/C,EAAA,OAAO,CAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA,CAAA;AAqDT;;;ACvFO,SAAS,wBAAwB,OAAA,EAAmC;AACzE,EAAA,MAAM,EAAE,SAAA,EAAW,SAAA,EAAW,SAAA,EAAW,cAAa,GAAI,OAAA;AAE1D,EAAA,OAAO,CAAA;AAAA;AAAA;AAAA,KAAA,EAGF,SAAS,IAAI,YAAY,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAA,EAOV,SAAS,CAAA;AAAA,oBAAA,EACT,SAAS,CAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA,SAAA,CAAA;AAyB/B;AAKO,SAAS,sBAAsB,MAAA,EAA4C;AAChF,EAAA,OAAO;AAAA,IACL,SAAA,EAAW,CAAA,EAAG,MAAA,CAAO,YAAY,CAAA,CAAA,EAAI,MAAA,CAAO,YAAA,CAAa,GAAG,CAAA,CAAA,EAAI,MAAA,CAAO,SAAA,CAAU,QAAQ,CAAA,CAAA;AAAA,IACzF,SAAA,EAAW,GAAG,MAAA,CAAO,YAAA,CAAa,GAAG,CAAA,CAAA,EAAI,MAAA,CAAO,UAAU,QAAQ,CAAA,CAAA;AAAA,IAClE,WAAW,MAAA,CAAO,SAAA;AAAA,IAClB,cAAc,MAAA,CAAO;AAAA,GACvB;AACF;;;ACxDO,SAAS,uBAAuB,OAAA,EAAkC;AACvE,EAAA,MAAM,EAAE,SAAA,EAAW,SAAA,EAAW,OAAA,EAAQ,GAAI,OAAA;AAE1C,EAAA,OAAO,CAAA;AAAA;AAAA;AAAA;AAAA,oBAAA,EAIa,SAAS,CAAA;AAAA,oBAAA,EACT,SAAS,CAAA;AAAA,gBAAA,EACb,OAAO,CAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA,SAAA,CAAA;AAqEzB;AAKO,SAAS,qBAAqB,MAAA,EAA2C;AAC9E,EAAA,OAAO;AAAA,IACL,SAAA,EAAW,CAAA,EAAG,MAAA,CAAO,YAAY,CAAA,CAAA,EAAI,MAAA,CAAO,YAAA,CAAa,GAAG,CAAA,CAAA,EAAI,MAAA,CAAO,QAAA,CAAS,QAAQ,CAAA,CAAA;AAAA,IACxF,SAAA,EAAW,GAAG,MAAA,CAAO,YAAA,CAAa,GAAG,CAAA,CAAA,EAAI,MAAA,CAAO,SAAS,QAAQ,CAAA,CAAA;AAAA,IACjE,OAAA,EAAS,OAAO,QAAA,CAAS;AAAA,GAC3B;AACF;;;AChCO,SAAS,sBAAsB,OAAA,EAAmC;AACvE,EAAA,IAAI,CAAC,QAAQ,OAAA,EAAS;AACpB,IAAA,OAAO;AAAA;AAAA;AAAA,CAAA;AAAA,EAIT;AAGA,EAAA,MAAM,WAAA,GAAc,QAAQ,WAAA,IAAe,EAAA;AAC3C,EAAA,MAAM,cAAA,GAAiB,QAAQ,cAAA,IAAkB,EAAA;AACjD,EAAA,MAAM,mBAAA,GAAsB,QAAQ,mBAAA,IAAuB,EAAA;AAC3D,EAAA,MAAM,OAAA,GAAU,QAAQ,OAAA,IAAW,EAAA;AACnC,EAAA,MAAM,gBAAA,GAAmB,QAAQ,gBAAA,IAAoB,EAAA;AACrD,EAAA,MAAM,YAAA,GAAe,QAAQ,YAAA,IAAgB,EAAA;AAC7C,EAAA,MAAM,WAAA,GAAc,QAAQ,WAAA,IAAe,EAAA;AAC3C,EAAA,MAAM,WAAA,GAAc,QAAQ,WAAA,IAAe,EAAA;AAE3C,EAAA,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA,gBAAA,EAWS,QAAQ,KAAK,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBAAA,EAOP,QAAQ,UAAU,CAAA;AAAA,2BAAA,EACb,QAAQ,eAAe,CAAA;AAAA,wBAAA,EAC1B,QAAQ,YAAY,CAAA;AAAA,6BAAA,EACf,QAAQ,iBAAiB,CAAA;AAAA,4BAAA,EAC1B,QAAQ,gBAAgB,CAAA;AAAA,+BAAA,EACrB,WAAW,CAAA;AAAA,+BAAA,EACX,OAAA,CAAQ,sBAAsB,IAAI,CAAA;AAAA,2BAAA,EACtC,cAAc,CAAA;AAAA,0BAAA,EACf,OAAA,CAAQ,iBAAiB,IAAI,CAAA;AAAA,iCAAA,EACtB,mBAAmB,CAAA;AAAA,oBAAA,EAChC,OAAO,CAAA;AAAA,8BAAA,EACG,gBAAgB,CAAA;AAAA,yBAAA,EACrB,YAAY,CAAA;AAAA,wBAAA,EACb,WAAW,CAAA;AAAA,wBAAA,EACX,WAAW,CAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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","file":"cli.cjs","sourcesContent":["import type AdmZip from 'adm-zip';\n\n/**\n * Supported package formats\n */\nexport type PackageFormat =\n | 'scorm12'\n | 'scorm2004-3'\n | 'scorm2004-4'\n | 'cmi5'\n | 'xapi'\n | 'aicc'\n | 'unknown';\n\n/**\n * Detect the package format by examining manifest files\n */\nexport class FormatDetector {\n /**\n * Detect the package format from a ZIP file\n */\n detect(zip: AdmZip): PackageFormat {\n // Check for cmi5.xml first (most specific)\n if (zip.getEntry('cmi5.xml')) {\n return 'cmi5';\n }\n\n // Check for xAPI (tincan.xml)\n if (zip.getEntry('tincan.xml')) {\n return 'xapi';\n }\n\n // Check for SCORM via imsmanifest.xml\n const manifest = zip.getEntry('imsmanifest.xml');\n if (manifest) {\n const content = manifest.getData().toString('utf-8');\n return this.detectScormVersion(content);\n }\n\n // Check for AICC-specific files\n if (this.hasAiccFiles(zip)) {\n return 'aicc';\n }\n\n return 'unknown';\n }\n\n /**\n * Detect SCORM version from manifest content\n */\n private detectScormVersion(content: string): PackageFormat {\n // Check for SCORM 2004 4th Edition\n if (content.includes('4th Edition') || content.includes('CAM 1.4')) {\n return 'scorm2004-4';\n }\n\n // Check for SCORM 2004 3rd Edition\n if (\n content.includes('3rd Edition') ||\n content.includes('2004 3rd') ||\n content.includes('adlcp_v1p3')\n ) {\n return 'scorm2004-3';\n }\n\n // Check for SCORM 2004 (generic)\n if (content.includes('2004') && content.includes('adlseq')) {\n return 'scorm2004-3'; // Default to 3rd edition for generic 2004\n }\n\n // Check for SCORM 1.2\n if (\n content.includes('1.2') ||\n content.includes('adlcp_rootv1p2') ||\n content.includes('imscp_rootv1p1p2')\n ) {\n return 'scorm12';\n }\n\n // Check for AICC markers in manifest\n if (content.toLowerCase().includes('aicc')) {\n return 'aicc';\n }\n\n // Default to SCORM 1.2 if we have a manifest but can't determine version\n return 'scorm12';\n }\n\n /**\n * Check for AICC-specific files\n */\n private hasAiccFiles(zip: AdmZip): boolean {\n const aiccExtensions = ['.crs', '.au', '.des', '.cst'];\n const entries = zip.getEntries();\n\n for (const entry of entries) {\n const name = entry.entryName.toLowerCase();\n if (aiccExtensions.some((ext) => name.endsWith(ext))) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Get a human-readable format name\n */\n getFormatDisplayName(format: PackageFormat): string {\n const names: Record<PackageFormat, string> = {\n scorm12: 'SCORM 1.2',\n 'scorm2004-3': 'SCORM 2004 3rd Edition',\n 'scorm2004-4': 'SCORM 2004 4th Edition',\n cmi5: 'cmi5',\n xapi: 'xAPI (Tin Can)',\n aicc: 'AICC',\n unknown: 'Unknown',\n };\n\n return names[format];\n }\n}\n","import type AdmZip from 'adm-zip';\n\n/**\n * Supported e-learning authoring tools\n */\nexport type AuthoringTool =\n | 'rise'\n | 'storyline'\n | 'captivate'\n | 'lectora'\n | 'xyleme'\n | 'unknown';\n\n/**\n * Information about the detected authoring tool\n */\nexport interface AuthoringToolInfo {\n tool: AuthoringTool;\n displayName: string;\n /** Path to the main HTML file to patch */\n mainHtmlPath: string;\n /** Additional HTML files that should also be patched (e.g., index_lms.html for Storyline) */\n additionalHtmlPaths?: string[];\n /** Version of the authoring tool if detectable */\n version?: string;\n}\n\n/**\n * Detect the authoring tool used to create a course package.\n * This determines which HTML injection strategy to use.\n */\nexport class AuthoringToolDetector {\n /**\n * Detect the authoring tool from a ZIP file\n */\n detect(zip: AdmZip): AuthoringToolInfo {\n // Check for Rise first (most common in our use case)\n const riseResult = this.detectRise(zip);\n if (riseResult) return riseResult;\n\n // Check for Storyline\n const storylineResult = this.detectStoryline(zip);\n if (storylineResult) return storylineResult;\n\n // Check for Adobe Captivate\n const captivateResult = this.detectCaptivate(zip);\n if (captivateResult) return captivateResult;\n\n // Check for Lectora\n const lectoraResult = this.detectLectora(zip);\n if (lectoraResult) return lectoraResult;\n\n // Check for Xyleme\n const xylemeResult = this.detectXyleme(zip);\n if (xylemeResult) return xylemeResult;\n\n // Unknown - try to find any index.html\n return this.detectUnknown(zip);\n }\n\n /**\n * Detect Articulate Rise packages\n * Rise packages have: scormcontent/index.html with __loadEntry() call\n */\n private detectRise(zip: AdmZip): AuthoringToolInfo | null {\n const indexEntry = zip.getEntry('scormcontent/index.html');\n if (!indexEntry) return null;\n\n const content = indexEntry.getData().toString('utf-8');\n\n // Rise has a distinctive __loadEntry() call\n if (content.includes('__loadEntry()')) {\n // Try to extract version from comments\n const versionMatch = content.match(/Rise\\s+v?(\\d+\\.\\d+(?:\\.\\d+)?)/i);\n return {\n tool: 'rise',\n displayName: 'Articulate Rise',\n mainHtmlPath: 'scormcontent/index.html',\n version: versionMatch?.[1],\n };\n }\n\n // Also check for Rise-specific lib structure\n if (zip.getEntry('scormcontent/lib/rise/')) {\n return {\n tool: 'rise',\n displayName: 'Articulate Rise',\n mainHtmlPath: 'scormcontent/index.html',\n };\n }\n\n return null;\n }\n\n /**\n * Detect Articulate Storyline packages\n * Storyline packages have: story.html with window.globals.publishSource = 'storyline'\n * or html5/lib/scripts/bootstrapper.min.js\n *\n * IMPORTANT: Storyline has TWO entry points that need patching:\n * - story.html: for standalone viewing\n * - index_lms.html: for LMS launch (what SCORM/xAPI uses)\n */\n private detectStoryline(zip: AdmZip): AuthoringToolInfo | null {\n const storyEntry = zip.getEntry('story.html');\n const lmsEntry = zip.getEntry('index_lms.html');\n\n // Build list of HTML files to patch\n const htmlPaths: string[] = [];\n if (storyEntry) htmlPaths.push('story.html');\n if (lmsEntry) htmlPaths.push('index_lms.html');\n\n // Check for story.html (primary entry point for standalone)\n if (storyEntry) {\n const content = storyEntry.getData().toString('utf-8');\n\n // Storyline marker in window.globals\n if (\n content.includes(\"publishSource: 'storyline'\") ||\n content.includes('publishSource:\"storyline\"')\n ) {\n const versionMatch = content.match(/playerVersion:\\s*['\"](\\d+\\.\\d+\\.\\d+\\.\\d+)['\"]/);\n return {\n tool: 'storyline',\n displayName: 'Articulate Storyline',\n mainHtmlPath: htmlPaths[0],\n additionalHtmlPaths: htmlPaths.slice(1),\n version: versionMatch?.[1],\n };\n }\n\n // Also check HTML comment for Storyline\n if (content.includes('Created using Storyline')) {\n const versionMatch = content.match(/version:\\s*(\\d+\\.\\d+\\.\\d+\\.\\d+)/);\n return {\n tool: 'storyline',\n displayName: 'Articulate Storyline',\n mainHtmlPath: htmlPaths[0],\n additionalHtmlPaths: htmlPaths.slice(1),\n version: versionMatch?.[1],\n };\n }\n }\n\n // Check for index_lms.html (LMS entry point) as fallback detection\n if (lmsEntry) {\n const content = lmsEntry.getData().toString('utf-8');\n if (\n content.includes(\"publishSource: 'storyline'\") ||\n content.includes('Created using Storyline')\n ) {\n const versionMatch = content.match(/version:\\s*(\\d+\\.\\d+\\.\\d+\\.\\d+)/);\n return {\n tool: 'storyline',\n displayName: 'Articulate Storyline',\n mainHtmlPath: htmlPaths[0],\n additionalHtmlPaths: htmlPaths.slice(1),\n version: versionMatch?.[1],\n };\n }\n }\n\n // Check for Storyline's distinctive folder structure\n if (zip.getEntry('html5/lib/scripts/bootstrapper.min.js')) {\n // Found Storyline structure, determine main HTML\n if (htmlPaths.length > 0) {\n return {\n tool: 'storyline',\n displayName: 'Articulate Storyline',\n mainHtmlPath: htmlPaths[0],\n additionalHtmlPaths: htmlPaths.slice(1),\n };\n }\n }\n\n return null;\n }\n\n /**\n * Detect Adobe Captivate packages\n * Captivate packages typically have: project.txt or Captivate.js\n */\n private detectCaptivate(zip: AdmZip): AuthoringToolInfo | null {\n // Check for Captivate markers\n const entries = zip.getEntries();\n for (const entry of entries) {\n const name = entry.entryName.toLowerCase();\n if (name.includes('captivate') && name.endsWith('.js')) {\n // Find main HTML\n const indexHtml = zip.getEntry('index.html');\n return {\n tool: 'captivate',\n displayName: 'Adobe Captivate',\n mainHtmlPath: indexHtml ? 'index.html' : 'index.html',\n };\n }\n }\n\n // Check index.html for Captivate markers\n const indexEntry = zip.getEntry('index.html');\n if (indexEntry) {\n const content = indexEntry.getData().toString('utf-8');\n if (\n content.includes('Adobe Captivate') ||\n content.includes('cp.') ||\n content.includes('cpAPIInterface')\n ) {\n return {\n tool: 'captivate',\n displayName: 'Adobe Captivate',\n mainHtmlPath: 'index.html',\n };\n }\n }\n\n return null;\n }\n\n /**\n * Detect Lectora packages\n * Lectora packages have distinctive trivantis markers\n */\n private detectLectora(zip: AdmZip): AuthoringToolInfo | null {\n const indexEntry = zip.getEntry('index.html');\n if (indexEntry) {\n const content = indexEntry.getData().toString('utf-8');\n if (\n content.includes('Trivantis') ||\n content.includes('Lectora') ||\n content.includes('trivDojo')\n ) {\n return {\n tool: 'lectora',\n displayName: 'Lectora',\n mainHtmlPath: 'index.html',\n };\n }\n }\n\n return null;\n }\n\n /**\n * Detect Xyleme/Bravais packages (thin pack or full)\n * Xyleme packages have: window.Xyleme namespace, cds-import.xml, or manifest.xml\n */\n private detectXyleme(zip: AdmZip): AuthoringToolInfo | null {\n // Check for Xyleme-specific files\n const hasCdsImport = zip.getEntry('cds-import.xml') !== null;\n const hasXylemeManifest = zip.getEntry('manifest.xml') !== null;\n\n // Check index.html for Xyleme markers\n const indexEntry = zip.getEntry('index.html');\n if (indexEntry) {\n const content = indexEntry.getData().toString('utf-8');\n\n // Xyleme has distinctive window.Xyleme namespace\n if (\n content.includes('window.Xyleme') ||\n content.includes('Xyleme.environment') ||\n hasCdsImport ||\n hasXylemeManifest\n ) {\n // Try to extract version\n const versionMatch = content.match(/XPE_PUBLIC_VERSION\\s*=\\s*['\"]([^'\"]+)['\"]/);\n\n // Collect all HTML pages that need patching (Xyleme has many page HTMLs)\n const additionalHtmlPaths: string[] = [];\n const entries = zip.getEntries();\n for (const entry of entries) {\n // Xyleme pages are GUIDs like: 9ee5c376-a354-45ca-9565-6472233fc13e.html\n if (\n entry.entryName.match(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\\.html$/i)\n ) {\n additionalHtmlPaths.push(entry.entryName);\n }\n }\n\n return {\n tool: 'xyleme',\n displayName: 'Xyleme/Bravais',\n mainHtmlPath: 'index.html',\n additionalHtmlPaths: additionalHtmlPaths.length > 0 ? additionalHtmlPaths : undefined,\n version: versionMatch?.[1],\n };\n }\n }\n\n return null;\n }\n\n /**\n * Fallback detection for unknown authoring tools\n * Try to find any suitable HTML file to patch\n */\n private detectUnknown(zip: AdmZip): AuthoringToolInfo {\n // Priority list of HTML files to look for\n const htmlFiles = [\n 'index.html',\n 'index_lms.html',\n 'story.html',\n 'launch.html',\n 'player.html',\n 'scormcontent/index.html',\n ];\n\n for (const htmlFile of htmlFiles) {\n if (zip.getEntry(htmlFile)) {\n return {\n tool: 'unknown',\n displayName: 'Unknown Authoring Tool',\n mainHtmlPath: htmlFile,\n };\n }\n }\n\n // Last resort: find any .html file at root or one level deep\n const entries = zip.getEntries();\n for (const entry of entries) {\n if (\n entry.entryName.endsWith('.html') &&\n !entry.entryName.includes('/') // root level\n ) {\n return {\n tool: 'unknown',\n displayName: 'Unknown Authoring Tool',\n mainHtmlPath: entry.entryName,\n };\n }\n }\n\n // Absolutely nothing found\n return {\n tool: 'unknown',\n displayName: 'Unknown Authoring Tool',\n mainHtmlPath: 'index.html', // Will fail later if doesn't exist\n };\n }\n\n /**\n * Get a human-readable tool name\n */\n getToolDisplayName(tool: AuthoringTool): string {\n const names: Record<AuthoringTool, string> = {\n rise: 'Articulate Rise',\n storyline: 'Articulate Storyline',\n captivate: 'Adobe Captivate',\n lectora: 'Lectora',\n xyleme: 'Xyleme/Bravais',\n unknown: 'Unknown',\n };\n return names[tool];\n }\n}\n","export { FormatDetector, type PackageFormat } from './format-detector.js';\nexport {\n AuthoringToolDetector,\n type AuthoringTool,\n type AuthoringToolInfo,\n} from './authoring-tool-detector.js';\n","import { z } from 'zod';\n\n/**\n * LRS Bridge configuration for xAPI/LRS communication\n * Enables Rise courses to communicate with Bravais LRS like Xyleme native courses\n */\nexport const LrsBridgeConfigSchema = z.object({\n /** Whether LRS bridge is enabled (default: true) */\n enabled: z.boolean().default(true),\n /** Track video/audio plays - play, pause, complete events (default: true) */\n trackMedia: z.boolean().default(true),\n /** Track page/lesson navigation (default: true) */\n trackNavigation: z.boolean().default(true),\n /** Track quiz/knowledge check answers (default: true) */\n trackQuizzes: z.boolean().default(true),\n /** Track interactive element clicks - tabs, accordions, flashcards, etc. (default: true) */\n trackInteractions: z.boolean().default(true),\n /** Allow custom xAPI statements via window.pa_patcher.lrs.sendCustomStatement() (default: true) */\n customStatements: z.boolean().default(true),\n /** Enable debug logging to console (default: false) */\n debug: z.boolean().default(false),\n /** LRS endpoint URL for direct xAPI posting - if not set, auto-detects from Bravais domain */\n lrsEndpoint: z.string().optional(),\n /** Whether to include credentials (cookies) in LRS requests (default: true) */\n lrsWithCredentials: z.boolean().default(true),\n /** Course homepage IRI for xAPI context */\n courseHomepage: z.string().optional(),\n /** Auto-detect Bravais LRS endpoint from parent frame URL (default: true) */\n autoDetectLrs: z.boolean().default(true),\n /** Basic auth credentials for LRS (Base64 encoded \"username:password\") - used when no cookie session exists */\n lrsAuth: z.string().optional(),\n /** LRS proxy endpoint URL - statements are sent here instead of direct LRS, proxy handles auth server-side */\n lrsProxyEndpoint: z.string().optional(),\n /** Employee API endpoint URL for user identity lookup (e.g., https://node.example.com/admin_api/users/employees/) */\n employeeApiEndpoint: z.string().optional(),\n /** Document GUID for xAPI statement aggregation - baked into package at wrap time */\n documentGuid: z.string().optional(),\n /** Version GUID for xAPI statement aggregation - unique per export */\n versionGuid: z.string().optional(),\n /** Original SCORM package filename - baked in at wrap time for LRS searchability */\n packageName: z.string().optional(),\n});\n\n/**\n * Configuration for a blocking asset (CSS before, JS before)\n * These load synchronously and block page rendering\n */\nexport const BlockingAssetConfigSchema = z.object({\n /** Filename for the asset */\n filename: z.string().min(1),\n /** Whether this asset is enabled */\n enabled: z.boolean().default(true),\n});\n\n/**\n * Configuration for an async asset (CSS after, JS after)\n * These load asynchronously with remote-first, local fallback\n */\nexport const AsyncAssetConfigSchema = z.object({\n /** Filename for the asset */\n filename: z.string().min(1),\n /** Whether this asset is enabled */\n enabled: z.boolean().default(true),\n /** Timeout in milliseconds for remote loading before falling back to local */\n timeout: z.number().min(1000).max(30000).default(5000),\n});\n\n/**\n * Local folder configuration for fallback files\n */\nexport const LocalFoldersConfigSchema = z.object({\n /** Folder name for CSS files within scormcontent */\n css: z.string().default('css'),\n /** Folder name for JS files within scormcontent */\n js: z.string().default('js'),\n});\n\n/**\n * Base schema for plugin configuration\n * Each plugin can extend this with their own options\n */\nexport const PluginConfigBaseSchema = z.object({\n /** Whether this plugin is enabled */\n enabled: z.boolean().default(false),\n}).passthrough(); // Allow additional properties from individual plugins\n\n/**\n * Plugins configuration - map of plugin name to plugin config\n */\nexport const PluginsConfigSchema = z.record(z.string(), PluginConfigBaseSchema).default({});\n\n/**\n * Main configuration schema for Patch-Adams\n */\nexport const PatchAdamsConfigSchema = z.object({\n /** Remote domain for assets (e.g., \"https://cdn.example.com/rise-overrides\") */\n remoteDomain: z.string().url(),\n\n /** CSS class added to <html> tag for specificity (default: \"pa-patched\") */\n htmlClass: z.string().default('pa-patched'),\n\n /** CSS class added during loading, removed when ready (default: \"pa-loading\") */\n loadingClass: z.string().default('pa-loading'),\n\n /** CSS loaded at start of <head> - blocking, prevents FOUC */\n cssBefore: BlockingAssetConfigSchema.default({\n filename: 'before.css',\n enabled: true,\n }),\n\n /** CSS loaded at end of <head> - async with fallback, for overrides */\n cssAfter: AsyncAssetConfigSchema.default({\n filename: 'after.css',\n enabled: true,\n timeout: 5000,\n }),\n\n /** JS loaded at start of <head> - blocking, for setup/interception */\n jsBefore: BlockingAssetConfigSchema.default({\n filename: 'before.js',\n enabled: true,\n }),\n\n /** JS loaded at end of <body> - async with fallback, removes loading class */\n jsAfter: AsyncAssetConfigSchema.default({\n filename: 'after.js',\n enabled: true,\n timeout: 5000,\n }),\n\n /** Local folder names for fallback files */\n localFolders: LocalFoldersConfigSchema.default({}),\n\n /** Whether to update manifest files (imsmanifest.xml, etc.) for SCORM compliance */\n updateManifests: z.boolean().default(true),\n\n /** Whether to fetch remote files and bake them as local fallbacks */\n fetchFallbacks: z.boolean().default(true),\n\n /** LRS Bridge configuration for xAPI/LRS communication with Bravais */\n lrsBridge: LrsBridgeConfigSchema.default({}),\n\n /** Plugin configurations - each plugin is keyed by its name */\n plugins: PluginsConfigSchema,\n\n /** Optional skin name — adds 'pa-skinned' + skin class to <html>, loads skin CSS/JS after core files */\n skin: z.string().min(1).optional(),\n});\n\nexport type PatchAdamsConfig = z.infer<typeof PatchAdamsConfigSchema>;\nexport type PluginsConfig = z.infer<typeof PluginsConfigSchema>;\nexport type LrsBridgeConfig = z.infer<typeof LrsBridgeConfigSchema>;\nexport type BlockingAssetConfig = z.infer<typeof BlockingAssetConfigSchema>;\nexport type AsyncAssetConfig = z.infer<typeof AsyncAssetConfigSchema>;\nexport type LocalFoldersConfig = z.infer<typeof LocalFoldersConfigSchema>;\n","import type { PatchAdamsConfig } from './schema.js';\n\n/**\n * Default configuration values for Patch-Adams\n */\nexport const defaultConfig: PatchAdamsConfig = {\n remoteDomain: 'https://cdn.example.com/rise-overrides',\n\n htmlClass: 'pa-patched',\n loadingClass: 'pa-loading',\n\n cssBefore: {\n filename: 'before.css',\n enabled: true,\n },\n\n cssAfter: {\n filename: 'after.css',\n enabled: true,\n timeout: 5000,\n },\n\n jsBefore: {\n filename: 'before.js',\n enabled: true,\n },\n\n jsAfter: {\n filename: 'after.js',\n enabled: true,\n timeout: 5000,\n },\n\n localFolders: {\n css: 'css',\n js: 'js',\n },\n\n updateManifests: true,\n\n fetchFallbacks: true,\n\n lrsBridge: {\n enabled: true,\n trackMedia: true,\n trackNavigation: true,\n trackQuizzes: true,\n trackInteractions: true,\n customStatements: true,\n debug: false,\n lrsEndpoint: undefined,\n lrsWithCredentials: true,\n courseHomepage: undefined,\n autoDetectLrs: true,\n },\n\n plugins: {},\n};\n","import { readFileSync, existsSync } from 'fs';\nimport { resolve, extname } from 'path';\nimport { pathToFileURL } from 'url';\nimport { PatchAdamsConfigSchema, type PatchAdamsConfig } from './schema.js';\nimport { defaultConfig } from './defaults.js';\n\n/**\n * Load and validate a Patch-Adams configuration file\n * Supports .ts, .js, and .json files\n */\nexport async function loadConfig(configPath: string): Promise<PatchAdamsConfig> {\n const absolutePath = resolve(configPath);\n\n if (!existsSync(absolutePath)) {\n throw new Error(`Configuration file not found: ${absolutePath}`);\n }\n\n const ext = extname(absolutePath).toLowerCase();\n let rawConfig: unknown;\n\n if (ext === '.json') {\n const content = readFileSync(absolutePath, 'utf-8');\n rawConfig = JSON.parse(content);\n } else if (ext === '.ts' || ext === '.js' || ext === '.mjs') {\n // Dynamic import for ESM modules\n const fileUrl = pathToFileURL(absolutePath).href;\n const module = await import(fileUrl);\n rawConfig = module.default ?? module;\n } else {\n throw new Error(`Unsupported config file extension: ${ext}. Use .ts, .js, .mjs, or .json`);\n }\n\n // Validate and return\n const validated = PatchAdamsConfigSchema.parse(rawConfig);\n return validated;\n}\n\n/**\n * Create a merged config with defaults\n */\nexport function mergeWithDefaults(partial: Partial<PatchAdamsConfig>): PatchAdamsConfig {\n return PatchAdamsConfigSchema.parse({\n ...defaultConfig,\n ...partial,\n });\n}\n\n/**\n * Generate a configuration file template\n */\nexport function generateConfigTemplate(): string {\n return `import type { PatchAdamsConfig } from '@patch-adams/core';\n\nconst config: PatchAdamsConfig = {\n // Remote domain where your CSS/JS files are hosted\n remoteDomain: 'https://cdn.example.com/rise-overrides',\n\n // HTML classes for specificity and loading state\n htmlClass: 'pa-patched', // Always present on <html>\n loadingClass: 'pa-loading', // Removed when assets are loaded\n\n // CSS loaded at start of <head> - BLOCKING\n // Use this to hide content and prevent flash of unstyled content\n cssBefore: {\n filename: 'before.css',\n enabled: true,\n },\n\n // CSS loaded at end of <head> - ASYNC with fallback\n // Use this for style overrides\n cssAfter: {\n filename: 'after.css',\n enabled: true,\n timeout: 5000, // ms before falling back to local\n },\n\n // JS loaded at start of <head> - BLOCKING\n // Use this for setup, API interception, globals\n jsBefore: {\n filename: 'before.js',\n enabled: true,\n },\n\n // JS loaded at end of <body> - ASYNC with fallback\n // Use this for DOM manipulation after Rise loads\n // This should remove the loadingClass when done\n jsAfter: {\n filename: 'after.js',\n enabled: true,\n timeout: 5000, // ms before falling back to local\n },\n\n // Folder names for local fallback files within scormcontent/\n localFolders: {\n css: 'css',\n js: 'js',\n },\n\n // Update manifest files for SCORM compliance\n updateManifests: true,\n};\n\nexport default config;\n`;\n}\n","import type { PatchAdamsConfig } from '../config/schema.js';\n\nexport interface CssBeforeOptions {\n remoteUrl: string;\n localPath: string;\n htmlClass: string;\n loadingClass: string;\n}\n\n/**\n * Generate the blocking CSS loader for the \"before\" slot\n * This loads at the start of <head> and blocks rendering until loaded\n *\n * Strategy:\n * 1. First, inline the critical CSS to hide content immediately\n * 2. Then load the remote CSS synchronously (or local fallback)\n */\nexport function generateCssBeforeLoader(options: CssBeforeOptions): string {\n const { remoteUrl, localPath, htmlClass, loadingClass } = options;\n\n return `<!-- === PATCH-ADAMS: CSS BEFORE (blocking) === -->\n<style data-pa=\"css-before-critical\">\n/* Critical: Hide content until ready */\nhtml.${htmlClass}.${loadingClass} body {\n visibility: hidden !important;\n}\n</style>\n<script data-pa=\"css-before-loader\">\n(function() {\n 'use strict';\n var REMOTE_URL = \"${remoteUrl}\";\n var LOCAL_PATH = \"${localPath}\";\n\n function loadCSSSync(url) {\n var link = document.createElement('link');\n link.rel = 'stylesheet';\n link.href = url;\n link.setAttribute('data-pa', 'css-before');\n document.head.appendChild(link);\n return link;\n }\n\n // Try remote first\n var link = loadCSSSync(REMOTE_URL);\n\n link.onerror = function() {\n console.warn('[PA-Patcher] CSS before failed to load from remote, using local fallback');\n document.head.removeChild(link);\n loadCSSSync(LOCAL_PATH);\n };\n\n link.onload = function() {\n console.log('[PA-Patcher] CSS before loaded from remote:', REMOTE_URL);\n };\n})();\n</script>`;\n}\n\n/**\n * Build options from config\n */\nexport function buildCssBeforeOptions(config: PatchAdamsConfig): CssBeforeOptions {\n return {\n remoteUrl: `${config.remoteDomain}/${config.localFolders.css}/${config.cssBefore.filename}`,\n localPath: `${config.localFolders.css}/${config.cssBefore.filename}`,\n htmlClass: config.htmlClass,\n loadingClass: config.loadingClass,\n };\n}\n","import type { PatchAdamsConfig } from '../config/schema.js';\n\nexport interface CssAfterOptions {\n remoteUrl: string;\n localPath: string;\n timeout: number;\n}\n\n/**\n * Generate the async CSS loader for the \"after\" slot\n * This loads at the end of <head> with remote-first, local fallback\n */\nexport function generateCssAfterLoader(options: CssAfterOptions): string {\n const { remoteUrl, localPath, timeout } = options;\n\n return `<!-- === PATCH-ADAMS: CSS AFTER (async with fallback) === -->\n<script data-pa=\"css-after-loader\">\n(function() {\n 'use strict';\n var REMOTE_URL = \"${remoteUrl}\";\n var LOCAL_PATH = \"${localPath}\";\n var TIMEOUT = ${timeout};\n\n function loadCSS(url, onSuccess, onError) {\n var link = document.createElement('link');\n link.rel = 'stylesheet';\n link.href = url;\n link.setAttribute('data-pa', 'css-after');\n\n link.onload = function() {\n if (onSuccess) onSuccess();\n };\n\n link.onerror = function() {\n if (onError) onError();\n };\n\n document.head.appendChild(link);\n return link;\n }\n\n function loadCSSWithFallback() {\n var loaded = false;\n var timeoutId;\n\n // Try remote first\n var remoteLink = loadCSS(\n REMOTE_URL,\n function() {\n if (loaded) return;\n loaded = true;\n clearTimeout(timeoutId);\n console.log('[PA-Patcher] CSS after loaded from remote:', REMOTE_URL);\n },\n function() {\n if (loaded) return;\n loaded = true;\n clearTimeout(timeoutId);\n loadLocalFallback();\n }\n );\n\n // Timeout fallback\n timeoutId = setTimeout(function() {\n if (loaded) return;\n loaded = true;\n console.warn('[PA-Patcher] CSS after timed out, using local fallback');\n if (remoteLink.parentNode) {\n document.head.removeChild(remoteLink);\n }\n loadLocalFallback();\n }, TIMEOUT);\n }\n\n function loadLocalFallback() {\n loadCSS(\n LOCAL_PATH,\n function() {\n console.log('[PA-Patcher] CSS after loaded from local fallback:', LOCAL_PATH);\n },\n function() {\n console.error('[PA-Patcher] CSS after failed to load from both remote and local');\n }\n );\n }\n\n // Execute immediately\n loadCSSWithFallback();\n})();\n</script>`;\n}\n\n/**\n * Build options from config\n */\nexport function buildCssAfterOptions(config: PatchAdamsConfig): CssAfterOptions {\n return {\n remoteUrl: `${config.remoteDomain}/${config.localFolders.css}/${config.cssAfter.filename}`,\n localPath: `${config.localFolders.css}/${config.cssAfter.filename}`,\n timeout: config.cssAfter.timeout,\n };\n}\n","/**\n * LRS Bridge Template for PA-Patcher\n *\n * Generates JavaScript code that bridges Rise courses to an LRS endpoint.\n * Supports direct xAPI statement posting when PlayerIntegration is unavailable.\n * Version 2.0.0 - Full xAPI support with direct LRS communication\n */\n\nexport interface LrsBridgeOptions {\n /** Whether LRS bridge is enabled */\n enabled: boolean;\n /** Track video/audio plays (play, pause, complete) */\n trackMedia: boolean;\n /** Track page/lesson navigation */\n trackNavigation: boolean;\n /** Track quiz/knowledge check answers */\n trackQuizzes: boolean;\n /** Track interactive element clicks (tabs, accordions, etc.) */\n trackInteractions: boolean;\n /** Allow custom xAPI statements via API */\n customStatements: boolean;\n /** Enable debug logging */\n debug: boolean;\n /** LRS endpoint URL for direct xAPI posting - if not provided, auto-detects from Bravais domain */\n lrsEndpoint?: string;\n /** Whether to include credentials (cookies) in LRS requests */\n lrsWithCredentials?: boolean;\n /** Course homepage IRI for xAPI context */\n courseHomepage?: string;\n /** Auto-detect Bravais LRS endpoint from parent frame URL (default: true) */\n autoDetectLrs?: boolean;\n /** Employee API endpoint for user lookup (e.g., https://node.example.com/admin_api/users/employees/) */\n employeeApiEndpoint?: string;\n /** Basic auth credentials for LRS (Base64 encoded \"username:password\") - used when no cookie session exists */\n lrsAuth?: string;\n /** LRS proxy endpoint URL - statements are sent here instead of direct LRS, proxy handles auth server-side */\n lrsProxyEndpoint?: string;\n /** Document GUID for xAPI statement aggregation - baked into package at wrap time */\n documentGuid?: string;\n /** Version GUID for xAPI statement aggregation - unique per export */\n versionGuid?: string;\n /** Original SCORM package filename - baked in at wrap time for LRS searchability */\n packageName?: string;\n}\n\nexport const DEFAULT_LRS_OPTIONS: LrsBridgeOptions = {\n enabled: true,\n trackMedia: true,\n trackNavigation: true,\n trackQuizzes: true,\n trackInteractions: true,\n customStatements: true,\n debug: false,\n lrsEndpoint: '',\n lrsWithCredentials: true,\n courseHomepage: '',\n autoDetectLrs: true,\n employeeApiEndpoint: '',\n lrsAuth: '',\n lrsProxyEndpoint: '',\n documentGuid: '',\n versionGuid: '',\n packageName: '',\n};\n\n/**\n * Generate the LRS bridge JavaScript code\n * This gets embedded into the js-before loader and runs immediately\n */\nexport function generateLrsBridgeCode(options: LrsBridgeOptions): string {\n if (!options.enabled) {\n return `\n // LRS Bridge disabled\n window.pa_patcher.lrs = { enabled: false, initialized: false };\n`;\n }\n\n // Escape strings for embedding in JS\n const lrsEndpoint = options.lrsEndpoint || '';\n const courseHomepage = options.courseHomepage || '';\n const employeeApiEndpoint = options.employeeApiEndpoint || '';\n const lrsAuth = options.lrsAuth || '';\n const lrsProxyEndpoint = options.lrsProxyEndpoint || '';\n const documentGuid = options.documentGuid || '';\n const versionGuid = options.versionGuid || '';\n const packageName = options.packageName || '';\n\n return `\n // ==========================================================================\n // PATCH-ADAMS LRS BRIDGE v2.2.0\n // Full xAPI support with direct LRS communication for Rise courses\n // ALL statements use DOCUMENT as main object for Bravais aggregation\n // Activity type (video, assessment, etc.) stored in object.definition.type\n // Page/lesson info stored in context.extensions for navigation tracking\n // ==========================================================================\n (function() {\n 'use strict';\n\n var DEBUG = ${options.debug};\n // Allow URL-based debug toggle: ?pa_debug=1 or #pa_debug\n try {\n if (window.location.search.indexOf('pa_debug') > -1 || window.location.hash.indexOf('pa_debug') > -1) {\n DEBUG = true;\n }\n } catch (e) {}\n var TRACK_MEDIA = ${options.trackMedia};\n var TRACK_NAVIGATION = ${options.trackNavigation};\n var TRACK_QUIZZES = ${options.trackQuizzes};\n var TRACK_INTERACTIONS = ${options.trackInteractions};\n var CUSTOM_STATEMENTS = ${options.customStatements};\n var LRS_ENDPOINT_CONFIG = '${lrsEndpoint}';\n var LRS_WITH_CREDENTIALS = ${options.lrsWithCredentials ?? true};\n var COURSE_HOMEPAGE = '${courseHomepage}';\n var AUTO_DETECT_LRS = ${options.autoDetectLrs ?? true};\n var EMPLOYEE_API_ENDPOINT = '${employeeApiEndpoint}';\n var LRS_AUTH = '${lrsAuth}';\n var LRS_PROXY_ENDPOINT = '${lrsProxyEndpoint}';\n var DOCUMENT_GUID = '${documentGuid}';\n var VERSION_GUID = '${versionGuid}';\n var PACKAGE_NAME = '${packageName}';\n\n // Bravais LRS endpoint pattern: https://lrs-{tenant}.bravais.com/XAPI/statements\n // Example: core-acme.bravais.com -> lrs-acme.bravais.com\n var LRS_ENDPOINT = ''; // Will be set by auto-detection or config\n\n function log() {\n if (DEBUG && window.console && window.console.log) {\n var args = Array.prototype.slice.call(arguments);\n args.unshift('[PA-LRS]');\n console.log.apply(console, args);\n }\n }\n\n function warn() {\n if (window.console && window.console.warn) {\n var args = Array.prototype.slice.call(arguments);\n args.unshift('[PA-LRS]');\n console.warn.apply(console, args);\n }\n }\n\n // ========================================================================\n // XAPI VERB DEFINITIONS (ADL standard IRIs)\n // ========================================================================\n var VERBS = {\n launched: {\n id: 'http://adlnet.gov/expapi/verbs/launched',\n display: { 'en-US': 'launched' }\n },\n initialized: {\n id: 'http://adlnet.gov/expapi/verbs/initialized',\n display: { 'en-US': 'initialized' }\n },\n viewed: {\n id: 'http://id.tincanapi.com/verb/viewed',\n display: { 'en-US': 'viewed' }\n },\n experienced: {\n id: 'http://adlnet.gov/expapi/verbs/experienced',\n display: { 'en-US': 'experienced' }\n },\n played: {\n id: 'https://w3id.org/xapi/video/verbs/played',\n display: { 'en-US': 'played' }\n },\n paused: {\n id: 'https://w3id.org/xapi/video/verbs/paused',\n display: { 'en-US': 'paused' }\n },\n completed: {\n id: 'http://adlnet.gov/expapi/verbs/completed',\n display: { 'en-US': 'completed' }\n },\n attempted: {\n id: 'http://adlnet.gov/expapi/verbs/attempted',\n display: { 'en-US': 'attempted' }\n },\n answered: {\n id: 'http://adlnet.gov/expapi/verbs/answered',\n display: { 'en-US': 'answered' }\n },\n passed: {\n id: 'http://adlnet.gov/expapi/verbs/passed',\n display: { 'en-US': 'passed' }\n },\n failed: {\n id: 'http://adlnet.gov/expapi/verbs/failed',\n display: { 'en-US': 'failed' }\n },\n interacted: {\n id: 'http://adlnet.gov/expapi/verbs/interacted',\n display: { 'en-US': 'interacted' }\n },\n progressed: {\n id: 'http://adlnet.gov/expapi/verbs/progressed',\n display: { 'en-US': 'progressed' }\n },\n terminated: {\n id: 'http://adlnet.gov/expapi/verbs/terminated',\n display: { 'en-US': 'terminated' }\n },\n // ActivityStrea.ms verbs for Xyleme/Bravais compatibility\n opened: {\n id: 'http://activitystrea.ms/schema/1.0/open',\n display: { 'en-US': 'opened' }\n },\n closed: {\n id: 'http://activitystrea.ms/schema/1.0/close',\n display: { 'en-US': 'closed' }\n },\n exited: {\n id: 'http://activitystrea.ms/schema/1.0/close',\n display: { 'en-US': 'exited' }\n }\n };\n\n // ========================================================================\n // ACTIVITY TYPES\n // ========================================================================\n var ACTIVITY_TYPES = {\n course: 'http://adlnet.gov/expapi/activities/course',\n module: 'http://adlnet.gov/expapi/activities/module',\n lesson: 'http://adlnet.gov/expapi/activities/lesson',\n assessment: 'http://adlnet.gov/expapi/activities/assessment',\n question: 'http://adlnet.gov/expapi/activities/question',\n interaction: 'http://adlnet.gov/expapi/activities/interaction',\n media: 'http://adlnet.gov/expapi/activities/media',\n video: 'https://w3id.org/xapi/video/activity-type/video',\n audio: 'https://w3id.org/xapi/audio/activity-type/audio'\n };\n\n // ========================================================================\n // XYLEME RESOURCE TYPES (for Bravais Analytics)\n // ========================================================================\n var RESOURCE_TYPES = {\n course: 'Course',\n document: 'Course',\n lesson: 'Topic',\n page: 'Topic',\n topic: 'Topic',\n assessment: 'Assessment',\n question: 'Question',\n interaction: 'Topic',\n media: 'Topic',\n video: 'Topic',\n audio: 'Topic'\n };\n\n // Xyleme-specific activity type IRIs\n var XYLEME_ACTIVITY_TYPES = {\n page: 'http://activitystrea.ms/schema/1.0/page',\n document: 'http://xyleme.com/bravais/activities/document',\n assessment: 'http://adlnet.gov/expapi/activities/assessment',\n question: 'http://adlnet.gov/expapi/activities/question'\n };\n\n // ========================================================================\n // LRS NAMESPACE & STATE\n // ========================================================================\n var LRS = window.pa_patcher.lrs = {\n version: '2.2.0',\n enabled: true,\n initialized: false,\n mode: null, // 'playerIntegration', 'directLRS', 'scormOnly'\n integration: null, // Xyleme PlayerIntegration (if available)\n integrationLevel: null,\n scormApi: null,\n scormApiLevel: null,\n scormApiWindow: null,\n scormApiFound: false,\n scormApiType: null,\n lrsEndpoint: LRS_ENDPOINT,\n statementQueue: [], // xAPI statements queued for sending\n eventLog: [], // All events logged (for debugging)\n currentLesson: null,\n courseInfo: null, // Course metadata\n actor: null, // xAPI actor object\n sessionId: null, // Session UUID\n launchTime: null, // ISO timestamp of course launch\n courseAttemptId: null, // Unique ID for this course attempt session (Xyleme format)\n employeeId: null, // Employee ID from employee lookup\n // Statistics\n stats: {\n statementsSent: 0,\n statementsQueued: 0,\n statementsFailed: 0\n }\n };\n\n // Generate UUID for session tracking\n function generateUUID() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n var r = Math.random() * 16 | 0;\n var v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n }\n\n // Clean HTML entities from text — handles both proper (") and broken (quot;) forms\n // Rise strips the & from entities in SCORM interaction IDs, so we need both\n function decodeEntities(str) {\n if (!str || typeof str !== 'string') return str;\n // 1. Replace known broken entities (without &) that Rise leaves behind\n str = str\n .replace(/quot;/g, '\"')\n .replace(/apos;/g, \"'\")\n .replace(/amp;/g, '&')\n .replace(/lt;/g, '<')\n .replace(/gt;/g, '>')\n .replace(/nbsp;/g, ' ');\n // 2. Replace numeric entities like " ' " etc.\n str = str.replace(/&#(x?[0-9a-fA-F]+);/g, function(match, code) {\n var num = code.charAt(0) === 'x' ? parseInt(code.substring(1), 16) : parseInt(code, 10);\n return isNaN(num) ? match : String.fromCharCode(num);\n });\n // 3. Catch any remaining standard &entities; via textarea decode\n try {\n var txt = document.createElement('textarea');\n txt.innerHTML = str;\n str = txt.value;\n } catch (e) {}\n return str;\n }\n\n LRS.sessionId = generateUUID();\n LRS.courseAttemptId = generateUUID(); // Unique per session for Xyleme correlation\n LRS.launchTime = new Date().toISOString();\n log('Generated courseAttemptId:', LRS.courseAttemptId);\n\n // ========================================================================\n // 1. BRAVAIS LRS ENDPOINT AUTO-DETECTION\n // ========================================================================\n\n /**\n * Auto-detect Bravais LRS endpoint from current URL or parent frames\n * Bravais pattern: https://core-{tenant}.bravais.com -> https://lrs-{tenant}.bravais.com/XAPI/statements\n */\n function detectBravaisLrsEndpoint() {\n var urlsToCheck = [];\n\n // Check current window\n urlsToCheck.push(window.location.href);\n\n // Check parent frames\n try {\n var win = window;\n for (var i = 0; i < 10; i++) {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n try {\n urlsToCheck.push(win.location.href);\n } catch (e) {\n // Cross-origin, try document.referrer\n break;\n }\n } else {\n break;\n }\n }\n } catch (e) {}\n\n // Also check referrer\n if (document.referrer) {\n urlsToCheck.push(document.referrer);\n }\n\n log('Checking URLs for Bravais domain:', urlsToCheck);\n\n // Look for Bravais domain pattern\n for (var j = 0; j < urlsToCheck.length; j++) {\n var url = urlsToCheck[j];\n // Match: core-{tenant}.bravais.com or {tenant}.bravais.com\n var match = url.match(/(?:core-)?([a-zA-Z0-9_-]+)\\\\.bravais\\\\.com/);\n if (match) {\n var tenant = match[1];\n var lrsEndpoint = 'https://lrs-' + tenant + '.bravais.com/XAPI/statements';\n log('Detected Bravais tenant:', tenant);\n log('Auto-detected LRS endpoint:', lrsEndpoint);\n return lrsEndpoint;\n }\n }\n\n log('No Bravais domain detected');\n return null;\n }\n\n // ========================================================================\n // 2. SCORM/PLAYERINTEGRATION DETECTION\n // ========================================================================\n\n function findAPIInFrameHierarchy(apiName, maxLevels) {\n maxLevels = maxLevels || 10;\n var win = window;\n var level = 0;\n\n while (level < maxLevels) {\n try {\n if (win[apiName]) {\n log('Found ' + apiName + ' at level ' + level);\n return { api: win[apiName], level: level, window: win };\n }\n } catch (e) {\n log('Cross-origin block at level ' + level);\n }\n\n try {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n level++;\n } else {\n break;\n }\n } catch (e) {\n break;\n }\n }\n\n return null;\n }\n\n function findPlayerIntegration(maxLevels) {\n maxLevels = maxLevels || 10;\n var win = window;\n var level = 0;\n\n while (level < maxLevels) {\n try {\n if (win.PlayerIntegration) {\n log('Found PlayerIntegration at level ' + level);\n return { integration: win.PlayerIntegration, level: level, window: win };\n }\n } catch (e) {}\n\n try {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n level++;\n } else {\n break;\n }\n } catch (e) {\n break;\n }\n }\n\n return null;\n }\n\n function setupBridge() {\n try {\n // 1. Try PlayerIntegration first (Xyleme native mode) - for SCORM APIs\n var integrationResult = findPlayerIntegration(10);\n if (integrationResult) {\n LRS.integration = integrationResult.integration;\n LRS.integrationLevel = integrationResult.level;\n log('Found PlayerIntegration at level', integrationResult.level);\n\n if (LRS.integration.Internal_SCORM_2004_API) {\n window.API_1484_11 = LRS.integration.Internal_SCORM_2004_API;\n LRS.scormApi = window.API_1484_11;\n LRS.scormApiFound = true;\n LRS.scormApiType = '2004';\n }\n if (LRS.integration.Internal_SCORM_12_API) {\n window.API = LRS.integration.Internal_SCORM_12_API;\n if (!LRS.scormApiFound) {\n LRS.scormApi = window.API;\n LRS.scormApiFound = true;\n LRS.scormApiType = '1.2';\n }\n }\n // Don't return early - continue to detect LRS endpoint for direct xAPI\n } else {\n // 2. Search for SCORM APIs directly\n log('No PlayerIntegration found, searching for SCORM APIs...');\n\n var api2004 = findAPIInFrameHierarchy('API_1484_11', 10);\n if (api2004) {\n LRS.scormApi = api2004.api;\n LRS.scormApiLevel = api2004.level;\n LRS.scormApiWindow = api2004.window;\n LRS.scormApiFound = true;\n LRS.scormApiType = '2004';\n log('Found SCORM 2004 API at level ' + api2004.level);\n } else {\n var api12 = findAPIInFrameHierarchy('API', 10);\n if (api12) {\n LRS.scormApi = api12.api;\n LRS.scormApiLevel = api12.level;\n LRS.scormApiWindow = api12.window;\n LRS.scormApiFound = true;\n LRS.scormApiType = '1.2';\n log('Found SCORM 1.2 API at level ' + api12.level);\n }\n }\n }\n\n // 3. ALWAYS try to determine LRS endpoint for direct xAPI statements\n // This is critical for detailed tracking (media, interactions, quizzes)\n // Priority: 1) Configured endpoint, 2) Auto-detected Bravais endpoint\n if (LRS_ENDPOINT_CONFIG && LRS_ENDPOINT_CONFIG.length > 0) {\n LRS_ENDPOINT = LRS_ENDPOINT_CONFIG;\n log('Using configured LRS endpoint:', LRS_ENDPOINT);\n } else if (AUTO_DETECT_LRS) {\n var detectedEndpoint = detectBravaisLrsEndpoint();\n if (detectedEndpoint) {\n LRS_ENDPOINT = detectedEndpoint;\n log('Using auto-detected Bravais LRS endpoint:', LRS_ENDPOINT);\n }\n }\n\n LRS.lrsEndpoint = LRS_ENDPOINT;\n\n // 4. Determine mode based on what we found\n // Prefer directLRS when available for detailed tracking\n if (LRS_ENDPOINT && LRS_ENDPOINT.length > 0) {\n LRS.mode = 'directLRS';\n log('Using direct LRS mode with endpoint:', LRS_ENDPOINT);\n if (LRS.integration) {\n log('PlayerIntegration also available for SCORM state');\n }\n return true;\n } else if (LRS.integration) {\n LRS.mode = 'playerIntegration';\n log('Using PlayerIntegration mode (no direct LRS endpoint)');\n return true;\n } else if (LRS.scormApiFound) {\n LRS.mode = 'scormOnly';\n log('Using SCORM-only mode (no LRS endpoint detected)');\n return true;\n }\n\n warn('No LRS endpoint or SCORM API found');\n return false;\n\n } catch (e) {\n warn('Error setting up bridge:', e);\n return false;\n }\n }\n\n // ========================================================================\n // 3. ACTOR EXTRACTION (multiple sources with fallbacks)\n // ========================================================================\n\n // Store for Bravais session data\n var bravaisSessionData = null;\n var bravaisSessionFetched = false;\n\n /**\n * Parse actor info from URL query parameters\n * Bravais thin packs often pass user info via URL params\n */\n function getActorFromUrlParams() {\n try {\n var params = new URLSearchParams(window.location.search);\n\n // Check various URL parameter names used by Bravais\n var userId = params.get('learner_id') || params.get('learnerId') ||\n params.get('user_id') || params.get('userId') ||\n params.get('actor_id') || params.get('actorId');\n var userName = params.get('learner_name') || params.get('learnerName') ||\n params.get('user_name') || params.get('userName') ||\n params.get('actor_name') || params.get('actorName');\n var userEmail = params.get('learner_email') || params.get('learnerEmail') ||\n params.get('user_email') || params.get('userEmail') ||\n params.get('actor_email') || params.get('actorEmail') ||\n params.get('email');\n var homePage = params.get('homepage') || params.get('homePage') ||\n params.get('tenant') || params.get('tenantId');\n\n if (userId || userEmail) {\n log('Found actor info in URL params');\n var actor = { objectType: 'Agent' };\n\n if (userName) {\n actor.name = userName;\n }\n if (userEmail) {\n actor.mbox = userEmail.indexOf('mailto:') === 0 ? userEmail : 'mailto:' + userEmail;\n }\n if (userId) {\n actor.account = {\n homePage: homePage || window.location.origin,\n name: userId\n };\n }\n return actor;\n }\n\n // Check for base64 encoded actor parameter\n var actorParam = params.get('actor');\n if (actorParam) {\n try {\n var actorJson = atob(actorParam);\n var actor = JSON.parse(actorJson);\n log('Found base64 encoded actor in URL');\n return actor;\n } catch (e) {\n log('Could not decode actor param:', e.message);\n }\n }\n\n } catch (e) {\n log('Error parsing URL params:', e.message);\n }\n return null;\n }\n\n /**\n * Parse actor from Bravais launch data (stored in SCORM launch_data)\n * Bravais stores JSON in cmi.launch_data with user context\n */\n function getActorFromLaunchData() {\n if (!LRS.scormApi) return null;\n\n try {\n var launchData = null;\n if (LRS.scormApiType === '2004') {\n launchData = LRS.scormApi.GetValue('cmi.launch_data');\n } else {\n launchData = LRS.scormApi.LMSGetValue('cmi.launch_data');\n }\n\n if (launchData) {\n // Bravais stores base64 encoded JSON in launch_data\n try {\n var decoded = atob(launchData);\n var data = JSON.parse(decoded);\n log('Parsed launch data:', Object.keys(data));\n\n // Look for actor/user info in launch data\n if (data.actor) {\n log('Found actor in launch data');\n return data.actor;\n }\n\n // Build actor from launch data fields\n if (data.userId || data.userID || data.learnerId) {\n var actor = { objectType: 'Agent' };\n var userId = data.userId || data.userID || data.learnerId;\n var userName = data.userName || data.userFirstName ?\n ((data.userFirstName || '') + ' ' + (data.userLastName || '')).trim() :\n data.learnerName;\n var userEmail = data.userEmail || data.learnerEmail || data.email;\n var homePage = data.xApiTenantId || data.tenantId || data.homePage;\n\n if (userName) actor.name = userName;\n if (userEmail) actor.mbox = 'mailto:' + userEmail;\n if (userId) {\n actor.account = {\n homePage: homePage || window.location.origin,\n name: String(userId)\n };\n }\n log('Built actor from launch data fields');\n return actor;\n }\n } catch (e) {\n // Not base64/JSON, might be plain text\n log('Launch data is not base64 JSON:', e.message);\n }\n }\n } catch (e) {\n log('Error reading launch data:', e.message);\n }\n return null;\n }\n\n /**\n * Try to get user info from Bravais context cookie\n * Cookie name pattern: Bravais-{env}-{region}-Context\n * Cookie value is base64 encoded JSON with user info\n */\n function getActorFromBravaisCookie() {\n try {\n var cookies = document.cookie.split(';');\n for (var i = 0; i < cookies.length; i++) {\n var cookie = cookies[i].trim();\n // Look for Bravais context cookies\n if (cookie.indexOf('Bravais-') === 0 && cookie.indexOf('-Context=') > -1) {\n var cookieName = cookie.split('=')[0];\n var cookieValue = cookie.substring(cookieName.length + 1);\n log('Found Bravais context cookie:', cookieName);\n\n try {\n // Cookie value is base64 encoded JSON\n var decoded = atob(cookieValue);\n var contextData = JSON.parse(decoded);\n log('Decoded Bravais cookie data keys:', Object.keys(contextData));\n\n // Extract user info from context\n if (contextData.userId || contextData.userID || contextData.user) {\n var actor = { objectType: 'Agent' };\n var user = contextData.user || contextData;\n\n var userId = user.userId || user.userID || user.id;\n var userName = user.userName || user.name ||\n ((user.firstName || user.userFirstName || '') + ' ' +\n (user.lastName || user.userLastName || '')).trim();\n var userEmail = user.email || user.userEmail;\n var homePage = contextData.xApiTenantId || contextData.tenantId ||\n contextData.portalUrl || contextData.coreUrl;\n\n if (userName) actor.name = userName;\n if (userEmail) actor.mbox = 'mailto:' + userEmail;\n if (userId) {\n actor.account = {\n homePage: homePage || window.location.origin,\n name: String(userId)\n };\n }\n log('Built actor from Bravais cookie');\n return actor;\n }\n } catch (e) {\n log('Could not decode Bravais cookie:', e.message);\n }\n }\n }\n log('No Bravais context cookie found or not accessible');\n } catch (e) {\n log('Cannot access cookies (cross-origin or blocked)');\n }\n return null;\n }\n\n /**\n * Try to get actor from parent frame's data\n * Bravais thin packs inject sessionData, actor, and PARAMS into the parent page\n */\n function getActorFromParentFrame() {\n try {\n var win = window;\n for (var level = 0; level < 10; level++) {\n try {\n // Check for Bravais PARAMS object (contains actor directly)\n if (win.PARAMS && win.PARAMS.actor) {\n log('Found PARAMS.actor at level', level);\n var actor = win.PARAMS.actor;\n // Ensure it's properly formatted\n if (actor.objectType !== 'Agent') {\n actor.objectType = 'Agent';\n }\n return actor;\n }\n\n // Check for Bravais sessionData (server-injected user info)\n if (win.sessionData && (win.sessionData.userId || win.sessionData.userID)) {\n log('Found sessionData at level', level);\n var sd = win.sessionData;\n var actor = { objectType: 'Agent' };\n\n // Name from firstName + lastName\n var firstName = sd.userFirstName || '';\n var lastName = sd.userLastName || '';\n if (firstName || lastName) {\n actor.name = (firstName + ' ' + lastName).trim();\n }\n\n // Account with homePage and userId\n var homePage = sd.xApiTenantId || sd.webPortalUrl || sd.coreUrl;\n var userId = sd.userId || sd.userID;\n if (homePage && userId) {\n actor.account = {\n homePage: homePage,\n name: String(userId)\n };\n }\n\n // mbox from email\n if (sd.userEmail) {\n actor.mbox = 'mailto:' + sd.userEmail;\n }\n\n log('Built actor from sessionData:', actor);\n return actor;\n }\n\n // Check for standalone actor variable (Bravais sets this from sessionData)\n if (win.actor && (win.actor.account || win.actor.mbox)) {\n log('Found actor variable at level', level);\n var actor = win.actor;\n if (actor.objectType !== 'Agent') {\n actor.objectType = 'Agent';\n }\n return actor;\n }\n\n // Check for Bravais player data\n if (win.BRAVAIS_LEARNER) {\n log('Found BRAVAIS_LEARNER at level', level);\n var learner = win.BRAVAIS_LEARNER;\n var actor = { objectType: 'Agent' };\n if (learner.name) actor.name = learner.name;\n if (learner.email) actor.mbox = 'mailto:' + learner.email;\n if (learner.id) {\n actor.account = {\n homePage: learner.homePage || window.location.origin,\n name: String(learner.id)\n };\n }\n return actor;\n }\n\n // Check for xAPI actor\n if (win.xAPILearner || win.XAPI_ACTOR) {\n log('Found xAPI actor at level', level);\n return win.xAPILearner || win.XAPI_ACTOR;\n }\n\n // Check for PA_ACTOR (set by patch-adams)\n if (win.PA_ACTOR) {\n log('Found PA_ACTOR at level', level);\n return win.PA_ACTOR;\n }\n\n // Check for generic learner data\n if (win.learnerData || win.userData) {\n var data = win.learnerData || win.userData;\n log('Found learner/user data at level', level);\n if (data.id || data.userId || data.email) {\n var actor = { objectType: 'Agent' };\n if (data.name) actor.name = data.name;\n if (data.email) actor.mbox = 'mailto:' + data.email;\n if (data.id || data.userId) {\n actor.account = {\n homePage: data.homePage || window.location.origin,\n name: String(data.id || data.userId)\n };\n }\n return actor;\n }\n }\n\n } catch (e) {\n // Cross-origin blocked at this level\n log('Cross-origin block at level', level, '- trying session API instead');\n break;\n }\n\n if (win.parent && win.parent !== win) {\n win = win.parent;\n } else {\n break;\n }\n }\n } catch (e) {\n log('Error accessing parent frames:', e.message);\n }\n return null;\n }\n\n /**\n * Fetch user info from Bravais session API\n * Returns session data or null if not available\n */\n function fetchBravaisSessionData(callback) {\n if (bravaisSessionFetched) {\n callback(bravaisSessionData);\n return;\n }\n\n // Detect Bravais core URL from parent frames or current URL\n var coreUrl = null;\n var urlsToCheck = [window.location.href, document.referrer];\n\n try {\n var win = window;\n for (var i = 0; i < 10; i++) {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n try {\n urlsToCheck.push(win.location.href);\n } catch (e) { break; }\n } else { break; }\n }\n } catch (e) {}\n\n log('URLs to check for core URL:', urlsToCheck);\n\n for (var j = 0; j < urlsToCheck.length; j++) {\n var match = urlsToCheck[j].match(/(https?:\\\\/\\\\/core-[a-zA-Z0-9_-]+\\\\.bravais\\\\.com)/);\n if (match) {\n coreUrl = match[1];\n break;\n }\n }\n\n if (!coreUrl) {\n log('No Bravais core URL detected for session fetch');\n bravaisSessionFetched = true;\n callback(null);\n return;\n }\n\n log('Fetching session data from:', coreUrl + '/api/shared/sessionData');\n\n var xhr = new XMLHttpRequest();\n xhr.open('GET', coreUrl + '/api/shared/sessionData', true);\n xhr.withCredentials = true; // Send cookies for auth\n xhr.setRequestHeader('Accept', 'application/json');\n\n xhr.onreadystatechange = function() {\n if (xhr.readyState === 4) {\n bravaisSessionFetched = true;\n log('Session API response status:', xhr.status);\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n bravaisSessionData = JSON.parse(xhr.responseText);\n log('Bravais session data received:', Object.keys(bravaisSessionData));\n callback(bravaisSessionData);\n } catch (e) {\n warn('Error parsing session data:', e);\n callback(null);\n }\n } else {\n log('Session data fetch failed (CORS or auth). Status:', xhr.status);\n callback(null);\n }\n }\n };\n\n xhr.onerror = function() {\n bravaisSessionFetched = true;\n log('Network error fetching session data (CORS blocked)');\n callback(null);\n };\n\n try {\n xhr.send();\n } catch (e) {\n bravaisSessionFetched = true;\n warn('Error sending session request:', e);\n callback(null);\n }\n }\n\n /**\n * Build actor from Bravais session data\n */\n function buildActorFromBravaisSession(sessionData) {\n if (!sessionData) return null;\n\n var actor = {\n objectType: 'Agent'\n };\n\n // Name from first + last name\n var firstName = sessionData.userFirstName || '';\n var lastName = sessionData.userLastName || '';\n if (firstName || lastName) {\n actor.name = (firstName + ' ' + lastName).trim();\n }\n\n // Account with homePage and user ID\n var homePage = sessionData.xApiTenantId || sessionData.webPortalUrl || sessionData.coreUrl;\n if (homePage && sessionData.userID) {\n actor.account = {\n homePage: homePage,\n name: String(sessionData.userID)\n };\n }\n\n // mbox from email (preferred by Bravais LRS)\n if (sessionData.userEmail) {\n actor.mbox = 'mailto:' + sessionData.userEmail;\n }\n\n return actor;\n }\n\n // Store for employee lookup data\n var employeeLookupData = null;\n var employeeLookupFetched = false;\n\n /**\n * Fetch user email from employee API using employee ID\n * Endpoint configured via EMPLOYEE_API_ENDPOINT (e.g., https://node.example.com/admin_api/users/employees/)\n */\n function fetchEmployeeEmail(employeeId, callback) {\n if (!employeeId) {\n callback(null);\n return;\n }\n\n // Skip if no employee API endpoint configured\n if (!EMPLOYEE_API_ENDPOINT) {\n log('Employee API endpoint not configured, skipping lookup');\n callback(null);\n return;\n }\n\n // In-memory cache check\n if (employeeLookupFetched && employeeLookupData) {\n callback(employeeLookupData);\n return;\n }\n\n // localStorage cache check (persists across page loads / sessions)\n var cacheKey = 'pa_employee_' + employeeId;\n try {\n var cached = localStorage.getItem(cacheKey);\n if (cached) {\n var parsed = JSON.parse(cached);\n // TTL: 7 days = 604800000 ms; also invalidate if missing bravaisUserId (old cache)\n if (parsed.timestamp && (Date.now() - parsed.timestamp) < 604800000 && parsed.bravaisUserId) {\n log('Employee data from localStorage cache');\n employeeLookupData = parsed;\n employeeLookupFetched = true;\n callback(parsed);\n return;\n }\n localStorage.removeItem(cacheKey);\n }\n } catch (e) {\n // localStorage unavailable (private browsing, iframe restrictions)\n }\n\n // Build URL: endpoint should end with ? or include query param structure\n var apiUrl = EMPLOYEE_API_ENDPOINT + (EMPLOYEE_API_ENDPOINT.indexOf('?') >= 0 ? '&' : '?') + 'q=' + encodeURIComponent(employeeId);\n log('Fetching employee data from:', apiUrl);\n\n var xhr = new XMLHttpRequest();\n xhr.open('GET', apiUrl, true);\n xhr.withCredentials = true;\n xhr.setRequestHeader('Accept', 'application/json');\n\n xhr.onreadystatechange = function() {\n if (xhr.readyState === 4) {\n employeeLookupFetched = true;\n log('Employee API response status:', xhr.status);\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n var data = JSON.parse(xhr.responseText);\n log('Employee data received:', data);\n\n // Response could be array or single object\n var employee = Array.isArray(data) ? data[0] : data;\n if (employee) {\n // API returns: business_email, fname, lname, emp_id\n employeeLookupData = {\n email: employee.business_email || employee.email || employee.Email || employee.userEmail,\n name: employee.name || employee.displayName || employee.fullName ||\n ((employee.fname || employee.firstName || '') + ' ' + (employee.lname || employee.lastName || '')).trim(),\n firstName: employee.fname || employee.firstName || employee.first_name,\n lastName: employee.lname || employee.lastName || employee.last_name,\n employeeId: employee.emp_id || employee.employeeId || employee.employee_id || employeeId,\n bravaisUserId: employee.bravaisUserId || null,\n tenantUrl: employee.tenantUrl || null\n };\n log('Parsed employee data:', employeeLookupData);\n // Persist to localStorage for cross-session caching\n try {\n var cacheData = {};\n for (var k in employeeLookupData) { if (employeeLookupData.hasOwnProperty(k)) cacheData[k] = employeeLookupData[k]; }\n cacheData.timestamp = Date.now();\n localStorage.setItem(cacheKey, JSON.stringify(cacheData));\n log('Employee data cached in localStorage');\n } catch (e) { /* localStorage unavailable */ }\n callback(employeeLookupData);\n } else {\n callback(null);\n }\n } catch (e) {\n warn('Error parsing employee data:', e);\n callback(null);\n }\n } else {\n log('Employee lookup failed. Status:', xhr.status);\n callback(null);\n }\n }\n };\n\n xhr.onerror = function() {\n employeeLookupFetched = true;\n log('Network error fetching employee data');\n callback(null);\n };\n\n try {\n xhr.send();\n } catch (e) {\n employeeLookupFetched = true;\n warn('Error sending employee request:', e);\n callback(null);\n }\n }\n\n /**\n * Enhance actor with email from employee lookup\n */\n function enhanceActorWithEmail(actor, callback) {\n if (!actor) {\n callback(actor);\n return;\n }\n\n // If actor already has email, no need to lookup\n if (actor.mbox && actor.mbox !== 'mailto:' && actor.mbox !== 'mailto:unknown') {\n callback(actor);\n return;\n }\n\n // Get employee ID from actor account name (SCORM learner_id is often employee ID)\n var employeeId = actor.account ? actor.account.name : null;\n if (!employeeId || employeeId === 'unknown' || employeeId === 'preview_user') {\n callback(actor);\n return;\n }\n\n log('Looking up email for employee ID:', employeeId);\n\n fetchEmployeeEmail(employeeId, function(employeeData) {\n console.info('[PA-LRS] Employee lookup result for ' + employeeId + ': ' +\n (employeeData ? 'name=' + employeeData.name + ', email=' + employeeData.email +\n ', bravaisUserId=' + employeeData.bravaisUserId + ', tenantUrl=' + employeeData.tenantUrl\n : 'NO DATA'));\n if (employeeData && employeeData.email) {\n actor.mbox = 'mailto:' + employeeData.email;\n log('Enhanced actor with email:', employeeData.email);\n\n // Also update name if we got a better one\n if (employeeData.name && (!actor.name || actor.name === 'Unknown Learner')) {\n actor.name = employeeData.name;\n }\n\n // Update account with Bravais user ID and tenant URL if available\n if (employeeData.bravaisUserId) {\n var originalAccountName = actor.account ? actor.account.name : null;\n actor.account = {\n name: employeeData.bravaisUserId,\n homePage: employeeData.tenantUrl || (actor.account && actor.account.homePage) || window.location.origin\n };\n log('Updated actor account: bravaisUserId=' + employeeData.bravaisUserId + ', homePage=' + actor.account.homePage + ' (was: ' + originalAccountName + ')');\n }\n\n // Expose employee ID on LRS object for plugins (e.g. feedback)\n if (employeeData.employeeId) {\n LRS.employeeId = employeeData.employeeId;\n }\n }\n callback(actor);\n });\n }\n\n function extractActor() {\n var defaultActor = {\n objectType: 'Agent',\n name: 'Unknown Learner',\n account: {\n homePage: COURSE_HOMEPAGE || window.location.origin,\n name: 'unknown'\n }\n };\n\n var actor = null;\n\n log('Extracting actor from available sources...');\n\n // Priority 1: URL parameters (most reliable for thin packs)\n actor = getActorFromUrlParams();\n if (actor && isValidActor(actor)) {\n log('Actor source: URL parameters');\n LRS.actor = actor;\n return actor;\n }\n\n // Priority 2: Parent frame data\n actor = getActorFromParentFrame();\n if (actor && isValidActor(actor)) {\n log('Actor source: Parent frame');\n LRS.actor = actor;\n return actor;\n }\n\n // Priority 3: Bravais context cookie (if accessible)\n actor = getActorFromBravaisCookie();\n if (actor && isValidActor(actor)) {\n log('Actor source: Bravais cookie');\n LRS.actor = actor;\n return actor;\n }\n\n // Priority 4: SCORM launch data (may contain user info)\n actor = getActorFromLaunchData();\n if (actor && isValidActor(actor)) {\n log('Actor source: SCORM launch data');\n LRS.actor = actor;\n return actor;\n }\n\n // Priority 5: Standard SCORM learner fields\n if (LRS.scormApi) {\n try {\n var scormLearnerId = null;\n var scormLearnerName = null;\n\n if (LRS.scormApiType === '2004') {\n scormLearnerId = LRS.scormApi.GetValue('cmi.learner_id');\n scormLearnerName = LRS.scormApi.GetValue('cmi.learner_name');\n } else {\n scormLearnerId = LRS.scormApi.LMSGetValue('cmi.core.student_id');\n scormLearnerName = LRS.scormApi.LMSGetValue('cmi.core.student_name');\n }\n\n log('SCORM learner_id:', scormLearnerId);\n log('SCORM learner_name:', scormLearnerName);\n\n // Check if we have valid SCORM data (not preview/unknown)\n var isValidScormId = scormLearnerId &&\n scormLearnerId !== 'preview_user' &&\n scormLearnerId !== 'unknown' &&\n scormLearnerId !== '';\n\n if (isValidScormId) {\n actor = {\n objectType: 'Agent',\n account: {\n homePage: COURSE_HOMEPAGE || window.location.origin,\n name: scormLearnerId\n }\n };\n // Don't use SCORM student_name - it's unreliable (\"Last, First\" format varies by LMS).\n // The employee API lookup will provide the correct name and email.\n log('Actor source: SCORM API (id only, name from employee lookup)');\n LRS.actor = actor;\n return actor;\n }\n\n log('SCORM learner ID is preview/unknown, continuing search...');\n } catch (e) {\n warn('Error extracting actor from SCORM:', e);\n }\n }\n\n // Priority 6: Already-fetched Bravais session data\n if (bravaisSessionData) {\n var bravaisActor = buildActorFromBravaisSession(bravaisSessionData);\n if (bravaisActor && isValidActor(bravaisActor)) {\n log('Actor source: Bravais session (cached)');\n LRS.actor = bravaisActor;\n return bravaisActor;\n }\n }\n\n log('No valid actor found, using default (Unknown Learner)');\n LRS.actor = defaultActor;\n return defaultActor;\n }\n\n /**\n * Check if an actor has valid identifying information\n */\n function isValidActor(actor) {\n if (!actor) return false;\n\n // Must have at least one valid identifier\n var hasValidMbox = actor.mbox && actor.mbox !== 'mailto:' && actor.mbox !== 'mailto:unknown';\n var hasValidAccount = actor.account && actor.account.name &&\n actor.account.name !== 'unknown' &&\n actor.account.name !== 'preview_user' &&\n actor.account.name !== '';\n\n return hasValidMbox || hasValidAccount;\n }\n\n // ========================================================================\n // CROSS-FRAME ACTOR SHARING\n // PA-Patcher injects the bridge into ALL HTML files in a SCORM package.\n // Rise courses have multiple HTML files (index.html, scormcontent/index.html),\n // each getting their own bridge instance with its own LRS.actor.\n // The skin overlay (email gate) only runs in one frame and sets the actor there.\n // Without sharing, other bridge instances keep the Anonymous Learner actor.\n // Solution: persist actor to localStorage so all bridge instances can use it.\n // ========================================================================\n var ACTOR_STORAGE_KEY = 'pa_lrs_shared_actor';\n\n function isAnonymousActor(actor) {\n if (!actor) return true;\n if (!actor.name) return true;\n var n = actor.name.toLowerCase();\n return n === 'anonymous learner' || n === 'unknown learner' || n === 'anonymous' || n === 'unknown';\n }\n\n /**\n * Persist actor to localStorage for cross-frame sharing.\n * Only stores non-anonymous actors.\n */\n function persistActor(actor) {\n if (!actor || isAnonymousActor(actor)) return;\n try {\n localStorage.setItem(ACTOR_STORAGE_KEY, JSON.stringify(actor));\n log('Actor persisted to localStorage:', actor.name);\n } catch (e) { /* localStorage unavailable */ }\n }\n\n /**\n * Load shared actor from localStorage.\n * Returns the stored actor or null.\n */\n function loadSharedActor() {\n try {\n var stored = localStorage.getItem(ACTOR_STORAGE_KEY);\n if (stored) {\n var actor = JSON.parse(stored);\n if (actor && !isAnonymousActor(actor)) {\n return actor;\n }\n }\n } catch (e) { /* localStorage unavailable or parse error */ }\n return null;\n }\n\n /**\n * Get the best available actor for statement building.\n * Priority: LRS.actor (if non-anonymous) > localStorage shared actor > LRS.actor > extractActor()\n */\n function getActor() {\n // If current actor is non-anonymous, use it\n if (LRS.actor && !isAnonymousActor(LRS.actor)) {\n return LRS.actor;\n }\n // Check localStorage for actor set by another frame (e.g., skin overlay)\n var shared = loadSharedActor();\n if (shared) {\n // Update local actor so future calls are fast\n LRS.actor = shared;\n log('Actor loaded from cross-frame storage:', shared.name);\n return shared;\n }\n // Fallback to current actor or re-extract\n return LRS.actor || extractActor();\n }\n\n /**\n * Set actor on the bridge with cross-frame persistence.\n * Called by skins and external code via window.pa_patcher.lrs.setActor(actor)\n */\n LRS.setActor = function(actor) {\n LRS.actor = actor;\n persistActor(actor);\n // Also try to propagate to other frames in the hierarchy\n try {\n var w = window;\n for (var i = 0; i < 10; i++) {\n try {\n if (w !== window && w.pa_patcher && w.pa_patcher.lrs) {\n w.pa_patcher.lrs.actor = actor;\n }\n } catch (e) { /* cross-origin */ }\n if (w === w.parent) break;\n w = w.parent;\n }\n } catch (e) {}\n if (window.console && window.console.info) {\n console.info('[PA-LRS] Actor set:', actor.name,\n actor.mbox ? '(' + actor.mbox + ')' : '',\n '— shared across frames');\n }\n };\n\n /**\n * Async version that fetches Bravais session if needed\n * Also re-checks all sources in case they became available\n * Finally enhances actor with email from employee lookup if needed\n */\n function extractActorAsync(callback) {\n // Helper to finalize actor with email lookup\n function finalizeActor(actor) {\n // Try to enhance actor with email if missing\n enhanceActorWithEmail(actor, function(enhancedActor) {\n LRS.actor = enhancedActor;\n persistActor(enhancedActor);\n callback(enhancedActor);\n });\n }\n\n // If we already have a valid actor with email, use it\n if (LRS.actor && isValidActor(LRS.actor) && LRS.actor.mbox && LRS.actor.mbox !== 'mailto:' && LRS.actor.mbox !== 'mailto:unknown') {\n log('Using existing valid actor with email');\n callback(LRS.actor);\n return;\n }\n\n // If we have a valid actor but no email, try to enhance it\n if (LRS.actor && isValidActor(LRS.actor)) {\n log('Valid actor found, checking for email enhancement');\n finalizeActor(LRS.actor);\n return;\n }\n\n // Re-check sync sources first (they might have loaded since init)\n var syncActor = extractActor();\n if (isValidActor(syncActor)) {\n log('Sync actor found, enhancing with email');\n finalizeActor(syncActor);\n return;\n }\n\n // Try Bravais session API as last resort\n fetchBravaisSessionData(function(sessionData) {\n if (sessionData) {\n var bravaisActor = buildActorFromBravaisSession(sessionData);\n if (isValidActor(bravaisActor)) {\n log('Updated actor from Bravais session API:', bravaisActor);\n finalizeActor(bravaisActor);\n return;\n }\n }\n\n // Fall back to whatever we have, still try email enhancement\n log('Async extraction complete, enhancing:', LRS.actor);\n finalizeActor(LRS.actor);\n });\n }\n\n // ========================================================================\n // 4. COURSE INFO EXTRACTION (Xyleme/Bravais format)\n // ========================================================================\n\n /**\n * Extract Bravais shared link token from URL\n * Pattern: /api/shared/{token}/files/...\n */\n function extractSharedLinkToken() {\n var urlsToCheck = [window.location.href];\n\n try {\n var win = window;\n for (var i = 0; i < 5; i++) {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n try {\n urlsToCheck.push(win.location.href);\n } catch (e) { break; }\n } else { break; }\n }\n } catch (e) {}\n\n for (var j = 0; j < urlsToCheck.length; j++) {\n var url = urlsToCheck[j];\n // Match: /api/shared/{token}/files/\n var match = url.match(/\\\\/api\\\\/shared\\\\/([a-zA-Z0-9_-]+)\\\\/files/);\n if (match) {\n log('Extracted shared link token:', match[1]);\n return match[1];\n }\n }\n return null;\n }\n\n // Store for document API data\n var documentApiData = null;\n var documentApiFetched = false;\n var sharedLinkApiData = null;\n\n /**\n * Detect Bravais core URL from current page context\n */\n function detectCoreUrl() {\n var urlsToCheck = [window.location.href, document.referrer];\n\n try {\n var win = window;\n for (var i = 0; i < 10; i++) {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n try {\n urlsToCheck.push(win.location.href);\n } catch (e) { break; }\n } else { break; }\n }\n } catch (e) {}\n\n for (var j = 0; j < urlsToCheck.length; j++) {\n var match = urlsToCheck[j].match(/(https?:\\\\/\\\\/core-[a-zA-Z0-9_-]+\\\\.bravais\\\\.com)/);\n if (match) {\n return match[1];\n }\n }\n return null;\n }\n\n /**\n * Fetch shared link data from Bravais API to get the real document ID\n * Endpoint: /api/v3/sharedLinks/token/{token}\n * Returns: { documentId, documentName, format, ... }\n */\n function fetchSharedLinkData(token, callback) {\n if (!token) {\n callback(null);\n return;\n }\n\n var coreUrl = detectCoreUrl();\n if (!coreUrl) {\n log('No Bravais core URL detected for shared link fetch');\n callback(null);\n return;\n }\n\n var apiUrl = coreUrl + '/api/v3/sharedLinks/token/' + token;\n log('Fetching shared link data from:', apiUrl);\n\n var xhr = new XMLHttpRequest();\n xhr.open('GET', apiUrl, true);\n xhr.withCredentials = true;\n xhr.setRequestHeader('Accept', 'application/json');\n\n xhr.onreadystatechange = function() {\n if (xhr.readyState === 4) {\n log('Shared link API response status:', xhr.status);\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n sharedLinkApiData = JSON.parse(xhr.responseText);\n log('Shared link data received:', sharedLinkApiData);\n if (sharedLinkApiData.documentId) {\n log('Real document ID from shared link:', sharedLinkApiData.documentId);\n }\n callback(sharedLinkApiData);\n } catch (e) {\n warn('Error parsing shared link data:', e);\n callback(null);\n }\n } else {\n // 401/403 is expected - API requires auth that may not be available from iframe\n if (xhr.status === 401 || xhr.status === 403) {\n log('Shared link API requires auth (expected), will use fallback');\n } else {\n log('Shared link fetch failed. Status:', xhr.status);\n }\n callback(null);\n }\n }\n };\n\n xhr.onerror = function() {\n log('Network error fetching shared link data');\n callback(null);\n };\n\n try {\n xhr.send();\n } catch (e) {\n warn('Error sending shared link request:', e);\n callback(null);\n }\n }\n\n /**\n * Fetch document metadata from Bravais shared API to get GUIDs\n * PRIMARY endpoint: /api/shared/{token}/documents (does NOT require auth)\n * FALLBACK endpoint: /api/v3/documents/{documentId} (requires auth)\n */\n function fetchDocumentMetadata(documentIdOrToken, callback) {\n if (documentApiFetched) {\n callback(documentApiData);\n return;\n }\n\n var coreUrl = detectCoreUrl();\n if (!coreUrl) {\n log('No Bravais core URL detected for document metadata fetch');\n documentApiFetched = true;\n callback(null);\n return;\n }\n\n // Determine which endpoint to use\n // If we have a shared link token, use /api/shared/{token}/documents (no auth required)\n // This is the same endpoint Xyleme Cloud Player uses\n var sharedToken = LRS.courseInfo ? LRS.courseInfo.sharedLinkToken : null;\n var apiUrl;\n\n if (sharedToken) {\n apiUrl = coreUrl + '/api/shared/' + sharedToken + '/documents';\n log('Fetching document via shared API (no auth required):', apiUrl);\n } else if (documentIdOrToken) {\n apiUrl = coreUrl + '/api/v3/documents/' + documentIdOrToken;\n log('Fetching document via v3 API (requires auth):', apiUrl);\n } else {\n log('No shared token or document ID available for metadata fetch');\n documentApiFetched = true;\n callback(null);\n return;\n }\n\n var xhr = new XMLHttpRequest();\n xhr.open('GET', apiUrl, true);\n xhr.withCredentials = true;\n xhr.setRequestHeader('Accept', 'application/json');\n\n xhr.onreadystatechange = function() {\n if (xhr.readyState === 4) {\n documentApiFetched = true;\n log('Document API response status:', xhr.status);\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n documentApiData = JSON.parse(xhr.responseText);\n log('Document metadata received:', documentApiData);\n // Validate that we got the required fields\n if (documentApiData.guid) {\n log('Document GUID found:', documentApiData.guid);\n } else {\n warn('Document API response missing guid field!', documentApiData);\n }\n if (documentApiData.latestVersion && documentApiData.latestVersion.guid) {\n log('Version GUID found:', documentApiData.latestVersion.guid);\n } else {\n warn('Document API response missing latestVersion.guid field!', documentApiData);\n }\n callback(documentApiData);\n } catch (e) {\n warn('Error parsing document metadata:', e);\n callback(null);\n }\n } else {\n // 401/403 is expected - API requires auth that may not be available from iframe\n if (xhr.status === 401 || xhr.status === 403) {\n log('Document API requires auth (expected), will use fallback');\n } else {\n log('Document metadata fetch failed. Status:', xhr.status);\n }\n callback(null);\n }\n }\n };\n\n xhr.onerror = function() {\n documentApiFetched = true;\n log('Network error fetching document metadata');\n callback(null);\n };\n\n try {\n xhr.send();\n } catch (e) {\n documentApiFetched = true;\n warn('Error sending document request:', e);\n callback(null);\n }\n }\n\n /**\n * Fetch document name from our authenticated server (cdsImporter proxy).\n * Derives server base URL from LRS_PROXY_ENDPOINT.\n * Falls back gracefully if CORS blocks or server is unavailable.\n * This is the final fallback when Bravais API and PARAMS are not available.\n */\n function fetchDocumentNameFromServer(documentId, callback) {\n if (!LRS_PROXY_ENDPOINT || !documentId) {\n callback(null);\n return;\n }\n\n // Derive server base from LRS proxy endpoint\n // e.g., https://api.example.com/create/statement → https://api.example.com/create/\n var serverBase = LRS_PROXY_ENDPOINT.replace(/\\\\/statement\\\\/?$/, '/');\n if (!serverBase || serverBase === LRS_PROXY_ENDPOINT) {\n log('Could not derive server base URL from LRS_PROXY_ENDPOINT');\n callback(null);\n return;\n }\n\n var apiUrl = serverBase + 'cdsImporter/api/documents/' + documentId;\n log('Fetching document name from server:', apiUrl);\n\n var xhr = new XMLHttpRequest();\n xhr.open('GET', apiUrl, true);\n xhr.withCredentials = true;\n xhr.setRequestHeader('Accept', 'application/json');\n xhr.timeout = 5000;\n\n xhr.onreadystatechange = function() {\n if (xhr.readyState === 4) {\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n var data = JSON.parse(xhr.responseText);\n if (data.name) {\n log('Document name from server:', data.name);\n callback(data.name);\n } else {\n callback(null);\n }\n } catch (e) {\n callback(null);\n }\n } else {\n log('Server document lookup returned:', xhr.status);\n callback(null);\n }\n }\n };\n\n xhr.onerror = function() {\n log('Server document lookup network error (CORS or connectivity)');\n callback(null);\n };\n xhr.ontimeout = function() {\n log('Server document lookup timed out');\n callback(null);\n };\n\n try { xhr.send(); } catch (e) { callback(null); }\n }\n\n /**\n * Update course info with data from document API\n */\n function updateCourseInfoFromApi(docData) {\n if (!docData || !LRS.courseInfo) return;\n\n // Document GUID — Bravais API GUID ALWAYS overrides baked-in GUID.\n // The baked-in GUID is a random UUID from wrap time. The Bravais GUID is the\n // canonical document identifier that Analytics uses to link statements to documents.\n // Without this override, our statements have a different object.id than cloudplayer\n // statements, so Analytics treats them as unrelated to the document.\n if (docData.guid) {\n var oldGuid = LRS.courseInfo.guid;\n LRS.courseInfo.guid = docData.guid;\n LRS.courseInfo.id = 'http://xyleme.com/bravais/document/' + docData.guid;\n // Always-visible log (not behind DEBUG) so we can confirm override in production\n console.info('[PA-LRS] Document GUID set from API:', docData.guid,\n oldGuid && oldGuid !== docData.guid ? '(was: ' + oldGuid + ')' : '');\n }\n\n // Version GUID — same principle: Bravais version GUID must override baked-in\n if (docData.latestVersion && docData.latestVersion.guid) {\n if (LRS.courseInfo.versionGuid && LRS.courseInfo.versionGuid !== docData.latestVersion.guid) {\n log('Overriding baked-in version GUID:', LRS.courseInfo.versionGuid, '->', docData.latestVersion.guid);\n }\n LRS.courseInfo.versionGuid = docData.latestVersion.guid;\n log('Version GUID from API:', docData.latestVersion.guid);\n }\n\n // Other metadata\n if (docData.name && LRS.courseInfo.title === 'Rise Course') {\n LRS.courseInfo.title = docData.name;\n }\n if (docData.format) {\n LRS.courseInfo.format = docData.format;\n }\n if (docData.resourceType) {\n LRS.courseInfo.resourceType = docData.resourceType;\n }\n\n // Update packageName from API document name (matches Bravais Analytics object name)\n // Only set if not already baked in at wrap time\n if (docData.name && !LRS.courseInfo.packageName) {\n LRS.courseInfo.packageName = docData.name;\n log('Updated packageName from API:', docData.name);\n }\n\n // Update documentId from API if we didn't have it\n if (docData.id && !LRS.courseInfo.documentId) {\n LRS.courseInfo.documentId = String(docData.id);\n log('Updated documentId from API:', docData.id);\n }\n\n log('Course info updated from API:', LRS.courseInfo);\n }\n\n /**\n * Extract Bravais document ID from URL\n * Pattern: /api/shared/.../files/{documentId}/scormcontent/\n */\n function extractBravaisDocumentId() {\n var urlsToCheck = [window.location.href];\n\n try {\n var win = window;\n for (var i = 0; i < 5; i++) {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n try {\n urlsToCheck.push(win.location.href);\n } catch (e) { break; }\n } else { break; }\n }\n } catch (e) {}\n\n for (var j = 0; j < urlsToCheck.length; j++) {\n var url = urlsToCheck[j];\n // Match: /files/{documentId}/scormcontent/ or /files/{documentId}/\n var match = url.match(/\\\\/files\\\\/([0-9]+)(?:\\\\/|$)/);\n if (match) {\n log('Extracted Bravais document ID:', match[1]);\n return match[1];\n }\n }\n return null;\n }\n\n /**\n * Extract course metadata from SCORM launch data\n * Bravais stores course info in base64 JSON in cmi.launch_data\n */\n function extractCourseFromLaunchData() {\n if (!LRS.scormApi) return null;\n\n try {\n var launchData = null;\n if (LRS.scormApiType === '2004') {\n launchData = LRS.scormApi.GetValue('cmi.launch_data');\n } else {\n launchData = LRS.scormApi.LMSGetValue('cmi.launch_data');\n }\n\n if (launchData) {\n try {\n var decoded = atob(launchData);\n var data = JSON.parse(decoded);\n log('Launch data for course info:', Object.keys(data));\n log('Launch data contents:', JSON.stringify(data));\n\n return {\n // Document/Course GUIDs\n guid: data.guid || data.courseGuid || data.documentGuid,\n versionGuid: data.versionGuid || data.courseVersionGuid || data.documentVersionGuid,\n // Numeric IDs\n documentId: data.documentId || data.courseId || data.cdsId,\n versionId: data.versionId || data.courseVersionId,\n // Metadata\n title: data.title || data.courseTitle || data.documentTitle || data.name,\n description: data.description || data.courseDescription,\n format: data.format || 'SCORM by Rise',\n resourceType: data.resourceType || 'Course',\n language: data.language || '',\n version: data.version || 1,\n cpv: data.cpv,\n // Shared link info\n sharedLinkName: data.sharedLinkName,\n sharedLinkToken: data.sharedLinkToken\n };\n } catch (e) {\n log('Launch data not JSON:', e.message);\n }\n }\n } catch (e) {\n log('Error reading launch data for course:', e.message);\n }\n return null;\n }\n\n /**\n * Extract course title from getCourseTitle() if it exists (from preloadIntegrity.js)\n */\n function extractCourseTitleFromPreload() {\n try {\n var win = window;\n for (var level = 0; level < 10; level++) {\n try {\n if (typeof win.getCourseTitle === 'function') {\n var title = win.getCourseTitle();\n if (title && title !== 'Rise Course') {\n log('Found course title from getCourseTitle() at level', level, ':', title);\n return title;\n }\n }\n } catch (e) {}\n\n if (win.parent && win.parent !== win) {\n win = win.parent;\n } else {\n break;\n }\n }\n } catch (e) {}\n return null;\n }\n\n /**\n * Try to extract document data from Xyleme Cloud Player's internal state\n * The Cloud Player stores this after fetching /api/shared/{token}/documents\n */\n function extractFromXylemeCloudPlayer() {\n try {\n var win = window;\n for (var level = 0; level < 10; level++) {\n try {\n // Check for Xyleme Cloud Player's document data in various locations\n // The player stores data in multiple places depending on version\n\n // Check for CdsDataService data\n if (win._cdsDataService && win._cdsDataService.documentData) {\n var docData = win._cdsDataService.documentData;\n log('Found document data in _cdsDataService at level', level);\n return extractGuidsFromDocumentData(docData);\n }\n\n // Check for window.documentData\n if (win.documentData && win.documentData.guid) {\n log('Found document data in window.documentData at level', level);\n return extractGuidsFromDocumentData(win.documentData);\n }\n\n // Check for PlayerIntegration's internal data\n if (win.playerIntegration && win.playerIntegration._documentData) {\n log('Found document data in playerIntegration at level', level);\n return extractGuidsFromDocumentData(win.playerIntegration._documentData);\n }\n\n // Check for __xyleme_data\n if (win.__xyleme_data && win.__xyleme_data.document) {\n log('Found document data in __xyleme_data at level', level);\n return extractGuidsFromDocumentData(win.__xyleme_data.document);\n }\n\n // Check for documentVersionData which has the versionGuid\n if (win.documentVersionData) {\n log('Found documentVersionData at level', level);\n return {\n versionGuid: win.documentVersionData.guid || win.documentVersionData.versionGuid,\n documentId: win.documentVersionData.documentId\n };\n }\n\n } catch (e) {}\n\n if (win.parent && win.parent !== win) {\n win = win.parent;\n } else {\n break;\n }\n }\n } catch (e) {}\n return null;\n }\n\n function extractGuidsFromDocumentData(data) {\n if (!data) return null;\n\n var result = {};\n\n // Document GUID\n if (data.guid) result.guid = data.guid;\n if (data.documentGuid) result.guid = data.documentGuid;\n\n // Version GUID - usually in latestVersion or currentVersion\n if (data.latestVersion && data.latestVersion.guid) {\n result.versionGuid = data.latestVersion.guid;\n }\n if (data.currentVersion && data.currentVersion.guid) {\n result.versionGuid = data.currentVersion.guid;\n }\n if (data.versionGuid) result.versionGuid = data.versionGuid;\n\n // Numeric IDs\n if (data.id) result.documentId = data.id;\n if (data.documentId) result.documentId = data.documentId;\n if (data.cdsId) result.documentId = data.cdsId;\n\n // Title\n if (data.name) result.title = data.name;\n if (data.title) result.title = data.title;\n\n log('Extracted GUIDs from Cloud Player:', result);\n return Object.keys(result).length > 0 ? result : null;\n }\n\n function extractCourseInfo() {\n var info = {\n // Xyleme IRI format: http://xyleme.com/bravais/document/{guid}\n id: null,\n // GUIDs\n guid: null, // Document GUID (fe57e5f8-c46b-4b2c-a0c7-884af6b0001d)\n versionGuid: null, // Version GUID (4c884a96-8da8-472b-8d59-c1d2bce0008f)\n // Numeric IDs\n documentId: null, // Bravais document CDS ID (numeric)\n versionId: null, // Version number\n // Metadata\n title: 'Rise Course',\n description: '',\n format: 'SCORM by Rise',\n resourceType: 'Course',\n language: '',\n version: 1,\n cpv: null,\n // Shared link\n sharedLinkToken: null,\n sharedLinkName: null,\n // Tenant info\n homepage: COURSE_HOMEPAGE || window.location.origin,\n tenantHomepage: null, // https://{tenant}.bravais.com format\n // Package info\n packageName: null // Original SCORM zip filename\n };\n\n // 0. Use baked-in GUIDs from PA-Patcher config (highest priority - set at wrap time)\n if (DOCUMENT_GUID) {\n info.guid = DOCUMENT_GUID;\n info.id = 'http://xyleme.com/bravais/document/' + DOCUMENT_GUID;\n log('Document GUID from baked-in config:', DOCUMENT_GUID);\n }\n if (VERSION_GUID) {\n info.versionGuid = VERSION_GUID;\n log('Version GUID from baked-in config:', VERSION_GUID);\n }\n if (PACKAGE_NAME) {\n info.packageName = PACKAGE_NAME;\n log('Package name from baked-in config:', PACKAGE_NAME);\n }\n\n // 1. Extract shared link token and document ID from URL\n info.sharedLinkToken = extractSharedLinkToken();\n info.documentId = extractBravaisDocumentId();\n\n // 2. Detect tenant homepage from URL\n var tenantMatch = window.location.href.match(/core-([a-zA-Z0-9_-]+)\\\\.bravais\\\\.com/);\n if (tenantMatch) {\n info.tenantHomepage = 'https://' + tenantMatch[1] + '.bravais.com';\n log('Detected tenant homepage:', info.tenantHomepage);\n }\n\n // 3. Extract from SCORM launch data (most reliable for Bravais)\n var launchInfo = extractCourseFromLaunchData();\n if (launchInfo) {\n info.guid = launchInfo.guid || info.guid;\n info.versionGuid = launchInfo.versionGuid || info.versionGuid;\n info.title = launchInfo.title || info.title;\n info.versionId = launchInfo.versionId || info.versionId;\n info.documentId = launchInfo.documentId || info.documentId;\n info.description = launchInfo.description || info.description;\n info.format = launchInfo.format || info.format;\n info.resourceType = launchInfo.resourceType || info.resourceType;\n info.language = launchInfo.language || info.language;\n info.version = launchInfo.version || info.version;\n info.cpv = launchInfo.cpv || info.cpv;\n info.sharedLinkToken = launchInfo.sharedLinkToken || info.sharedLinkToken;\n info.sharedLinkName = launchInfo.sharedLinkName || info.sharedLinkName;\n }\n\n // 3b. Always try to extract GUIDs from Xyleme Cloud Player's internal data.\n // Cloud Player data is authoritative — it has the canonical Bravais GUID.\n // This overrides any baked-in GUID which is just a random UUID from wrap time.\n var cloudPlayerData = extractFromXylemeCloudPlayer();\n if (cloudPlayerData) {\n if (cloudPlayerData.guid) info.guid = cloudPlayerData.guid;\n if (cloudPlayerData.versionGuid) info.versionGuid = cloudPlayerData.versionGuid;\n info.documentId = cloudPlayerData.documentId || info.documentId;\n info.title = cloudPlayerData.title || info.title;\n log('Updated course info from Cloud Player:', {\n guid: info.guid,\n versionGuid: info.versionGuid,\n documentId: info.documentId\n });\n }\n\n // 4. Try getCourseTitle() from preloadIntegrity.js\n var preloadTitle = extractCourseTitleFromPreload();\n if (preloadTitle) {\n info.title = preloadTitle;\n }\n\n // 5. Try Rise's data\n try {\n if (window.__RISE_COURSE_DATA__) {\n info.title = info.title === 'Rise Course' ?\n (window.__RISE_COURSE_DATA__.title || info.title) : info.title;\n info.guid = window.__RISE_COURSE_DATA__.id || info.guid;\n }\n\n var metaTitle = document.querySelector('meta[property=\"og:title\"]');\n if (metaTitle && metaTitle.content && info.title === 'Rise Course') {\n info.title = metaTitle.content;\n }\n\n var metaDesc = document.querySelector('meta[property=\"og:description\"]');\n if (metaDesc && metaDesc.content) {\n info.description = metaDesc.content;\n }\n } catch (e) {}\n\n // 6. Check document title\n if (info.title === 'Rise Course' && document.title && document.title !== 'Rise Course') {\n info.title = document.title;\n }\n\n // 7. Get from PA metadata if available (check both .courseMetadata and .course)\n var paMeta = (window.pa_patcher && window.pa_patcher.courseMetadata) ||\n (window.pa_patcher && window.pa_patcher.course) || null;\n if (paMeta) {\n info.documentId = paMeta.documentId || paMeta.courseId || info.documentId;\n if (!info.guid) info.guid = paMeta.guid || paMeta.courseGuid || null;\n if (!info.versionGuid) info.versionGuid = paMeta.versionGuid || null;\n info.title = (info.title === 'Rise Course') ? (paMeta.title || paMeta.courseTitle || info.title) : info.title;\n info.versionId = paMeta.versionId || info.versionId;\n }\n\n // 8. Extract Bravais document name from launch environment (runtime)\n // This is the name shown in Bravais Analytics and used for search.\n // Overrides packageName so the statement object name matches the Bravais document.\n if (!info.packageName) {\n var bravaisDocName = null;\n\n // Strategy 1: Walk parent frames for PARAMS.documentName and PARAMS.did\n try {\n var win = window;\n for (var i = 0; i < 10; i++) {\n try {\n if (win.PARAMS) {\n if (win.PARAMS.documentName && !bravaisDocName) {\n bravaisDocName = win.PARAMS.documentName;\n log('Bravais document name from PARAMS:', bravaisDocName);\n }\n if (win.PARAMS.did && !info.documentId) {\n info.documentId = String(win.PARAMS.did);\n log('Bravais document ID from PARAMS.did:', info.documentId);\n }\n }\n } catch (e) {}\n if (win === win.parent) break;\n win = win.parent;\n }\n } catch (e) {}\n\n // Strategy 2: Walk parent frames checking window.name\n // Bravais sets the iframe name attribute to the document name\n if (!bravaisDocName) {\n try {\n var win = window;\n for (var i = 0; i < 10; i++) {\n try {\n var frameName = win.name;\n // Document names have hyphens and are long; skip generic frame names\n if (frameName && frameName.length > 20 && frameName.indexOf('-') > -1 &&\n frameName.indexOf('scorm') === -1 && frameName.indexOf('API') === -1) {\n bravaisDocName = frameName;\n log('Bravais document name from frame name:', bravaisDocName);\n break;\n }\n } catch (e) {}\n if (win === win.parent) break;\n win = win.parent;\n }\n } catch (e) {}\n }\n\n if (bravaisDocName) {\n info.packageName = bravaisDocName;\n }\n }\n\n // 9. Build the course ID in Xyleme IRI format\n // Native format: http://xyleme.com/bravais/document/{guid}\n if (info.guid) {\n info.id = 'http://xyleme.com/bravais/document/' + info.guid;\n } else if (info.documentId) {\n // Fallback: use document ID\n info.id = 'http://xyleme.com/bravais/document/' + info.documentId;\n } else {\n info.id = window.location.href.split('#')[0].split('?')[0];\n }\n\n // Decode any HTML entities in title/description\n if (info.title) info.title = decodeEntities(info.title);\n if (info.description) info.description = decodeEntities(info.description);\n\n // Build shared link name if not provided\n if (!info.sharedLinkName && info.title && info.sharedLinkToken) {\n info.sharedLinkName = info.title + ' - LMS Thin Pack';\n }\n\n LRS.courseInfo = info;\n log('Course info extracted:', info);\n return info;\n }\n\n // ========================================================================\n // 4b. LESSON/SECTION NAME EXTRACTION\n // ========================================================================\n\n /**\n * Get the current lesson/section information from Rise DOM\n * Returns { id, name, lessonIndex, sectionName }\n */\n function getCurrentLessonInfo() {\n var lessonInfo = {\n id: window.location.hash || '#/',\n name: null,\n lessonIndex: null,\n sectionName: null\n };\n\n try {\n // 1. Try to get from Rise's navigation sidebar\n // Rise shows active lesson with specific classes\n var activeLesson = document.querySelector(\n '.sidebar__link--current, ' +\n '.sidebar-lesson--active, ' +\n '.nav-sidebar__lesson--active, ' +\n '[class*=\"sidebar\"] [class*=\"current\"], ' +\n '[class*=\"sidebar\"] [class*=\"active\"], ' +\n '[aria-current=\"page\"], ' +\n '[aria-current=\"true\"]'\n );\n\n if (activeLesson) {\n var lessonTitle = activeLesson.querySelector(\n '.sidebar__link-text, ' +\n '.lesson-title, ' +\n '.sidebar-lesson__title, ' +\n '[class*=\"title\"], ' +\n '[class*=\"label\"]'\n );\n if (lessonTitle) {\n lessonInfo.name = lessonTitle.textContent.trim();\n } else {\n lessonInfo.name = activeLesson.textContent.trim();\n }\n\n // Try to get the section/module name (parent group)\n var section = activeLesson.closest(\n '.sidebar__section, ' +\n '.sidebar-section, ' +\n '[class*=\"section\"], ' +\n '[class*=\"module\"]'\n );\n if (section) {\n var sectionTitle = section.querySelector(\n '.sidebar__section-title, ' +\n '.section-title, ' +\n '[class*=\"section-title\"], ' +\n '[class*=\"module-title\"], ' +\n 'h2, h3'\n );\n if (sectionTitle) {\n lessonInfo.sectionName = sectionTitle.textContent.trim();\n }\n }\n }\n\n // 2. Try Rise's header/breadcrumb\n if (!lessonInfo.name) {\n var headerTitle = document.querySelector(\n '.lesson-header__title, ' +\n '.lesson__title, ' +\n '.content-header__title, ' +\n '[class*=\"lesson\"] [class*=\"header\"] [class*=\"title\"], ' +\n '.blocks-lesson-header__title'\n );\n if (headerTitle) {\n lessonInfo.name = headerTitle.textContent.trim();\n }\n }\n\n // 3. Try breadcrumbs\n if (!lessonInfo.name || !lessonInfo.sectionName) {\n var breadcrumbs = document.querySelectorAll(\n '.breadcrumb__item, ' +\n '.breadcrumbs li, ' +\n '[class*=\"breadcrumb\"] span, ' +\n '[class*=\"breadcrumb\"] a'\n );\n if (breadcrumbs.length >= 2) {\n // Usually: Course > Section > Lesson\n if (!lessonInfo.sectionName && breadcrumbs.length >= 2) {\n lessonInfo.sectionName = breadcrumbs[breadcrumbs.length - 2].textContent.trim();\n }\n if (!lessonInfo.name) {\n lessonInfo.name = breadcrumbs[breadcrumbs.length - 1].textContent.trim();\n }\n }\n }\n\n // 4. Try Rise's internal data\n if (window.__RISE_COURSE_DATA__ && window.__RISE_COURSE_DATA__.lessons) {\n var lessonId = lessonInfo.id.replace('#/lessons/', '').replace('#/', '');\n var lessons = window.__RISE_COURSE_DATA__.lessons;\n for (var i = 0; i < lessons.length; i++) {\n if (lessons[i].id === lessonId || lessons[i].slug === lessonId) {\n lessonInfo.name = lessons[i].title || lessonInfo.name;\n lessonInfo.lessonIndex = i;\n break;\n }\n }\n }\n\n // 5. Clean up - remove any \"Home\" or generic names if we're on a real lesson\n if (lessonInfo.name) {\n lessonInfo.name = lessonInfo.name.replace(/^\\\\\\\\d+\\\\\\\\.\\\\\\\\s*/, ''); // Remove leading numbers \"1. \"\n if (lessonInfo.name.length > 200) {\n lessonInfo.name = lessonInfo.name.substring(0, 200) + '...';\n }\n }\n\n if (lessonInfo.sectionName) {\n lessonInfo.sectionName = lessonInfo.sectionName.replace(/^\\\\\\\\d+\\\\\\\\.\\\\\\\\s*/, '');\n if (lessonInfo.sectionName.length > 200) {\n lessonInfo.sectionName = lessonInfo.sectionName.substring(0, 200) + '...';\n }\n }\n\n } catch (e) {\n warn('Error extracting lesson info:', e);\n }\n\n log('Current lesson info:', lessonInfo);\n return lessonInfo;\n }\n\n /**\n * Cache the current lesson info (updates on navigation)\n */\n var cachedLessonInfo = null;\n var cachedLessonHash = null;\n\n function getCachedLessonInfo() {\n var currentHash = window.location.hash;\n if (cachedLessonHash !== currentHash) {\n cachedLessonInfo = getCurrentLessonInfo();\n cachedLessonHash = currentHash;\n }\n return cachedLessonInfo || getCurrentLessonInfo();\n }\n\n // ========================================================================\n // 5. XAPI STATEMENT BUILDING\n // ========================================================================\n\n /**\n * Build an xAPI statement with COURSE as the main object\n * This ensures ALL statements show up when searching by document name in Bravais Analytics\n *\n * The activity type (video, assessment, interaction, etc.) is stored in object.definition.type\n * The specific activity details (media src, page ID, etc.) are stored in object.definition.extensions\n */\n function buildStatement(verb, activityType, activityDetails, result, context) {\n // ALWAYS use course as the main object\n var courseObj = buildCourseActivityObject();\n if (!courseObj) {\n // Fallback if course info not available\n courseObj = {\n objectType: 'Activity',\n id: window.location.href.split('#')[0].split('?')[0],\n definition: {\n type: 'http://xyleme.com/bravais/activities/document',\n name: { 'en-US': decodeEntities(document.title) || 'Rise Course' }\n }\n };\n }\n\n // Override the activity type based on the statement being sent\n // This allows filtering by type (video, assessment, interaction) in Analytics\n if (activityType) {\n courseObj.definition.type = activityType;\n\n // Also update resourceType extension to human-readable form for Bravais Type column\n courseObj.definition.extensions = courseObj.definition.extensions || {};\n var resourceTypeMap = {\n 'https://w3id.org/xapi/video/activity-type/video': 'Video',\n 'https://w3id.org/xapi/audio/activity-type/audio': 'Audio',\n 'http://adlnet.gov/expapi/activities/media': 'Media',\n 'http://adlnet.gov/expapi/activities/assessment': 'Assessment',\n 'http://adlnet.gov/expapi/activities/question': 'Question',\n 'http://adlnet.gov/expapi/activities/interaction': 'Interaction',\n 'http://adlnet.gov/expapi/activities/lesson': 'Lesson',\n 'http://adlnet.gov/expapi/activities/module': 'Module',\n 'http://adlnet.gov/expapi/activities/course': 'Course',\n 'http://xyleme.com/bravais/activities/document': 'Course'\n };\n courseObj.definition.extensions['resourceType'] = resourceTypeMap[activityType] || 'Course';\n }\n\n // Add activity-specific details to extensions\n courseObj.definition.extensions = courseObj.definition.extensions || {};\n\n if (activityDetails) {\n // Merge activity details into extensions, decoding any HTML entities in string values\n for (var key in activityDetails) {\n if (activityDetails.hasOwnProperty(key)) {\n var val = activityDetails[key];\n courseObj.definition.extensions[key] = typeof val === 'string' ? decodeEntities(val) : val;\n }\n }\n }\n\n var statement = {\n id: generateUUID(),\n actor: getActor(),\n verb: typeof verb === 'string' ? (VERBS[verb] || { id: verb, display: { 'en-US': verb } }) : verb,\n object: courseObj,\n timestamp: new Date().toISOString()\n };\n\n if (result) {\n statement.result = result;\n }\n\n // Build context with all Xyleme extensions using unified builder\n statement.context = buildXylemeContext(context);\n\n return statement;\n }\n\n /**\n * Legacy buildStatement for backward compatibility\n * Routes to new signature\n */\n function buildStatementLegacy(verb, object, result, context) {\n // Extract type and details from the legacy object\n var activityType = object && object.definition ? object.definition.type : null;\n var activityDetails = {};\n\n if (object) {\n activityDetails.activityId = object.id;\n if (object.definition) {\n if (object.definition.name && object.definition.name['en-US']) {\n activityDetails.activityName = object.definition.name['en-US'];\n }\n if (object.definition.description && object.definition.description['en-US']) {\n activityDetails.activityDescription = object.definition.description['en-US'];\n }\n if (object.definition.interactionType) {\n activityDetails.interactionType = object.definition.interactionType;\n }\n // Copy any existing extensions\n if (object.definition.extensions) {\n for (var key in object.definition.extensions) {\n if (object.definition.extensions.hasOwnProperty(key)) {\n activityDetails[key] = object.definition.extensions[key];\n }\n }\n }\n }\n }\n\n return buildStatement(verb, activityType, activityDetails, result, context);\n }\n\n /**\n * Build the course activity object in Xyleme native format\n * Object ID: http://xyleme.com/bravais/document/{guid}\n * Object type: http://xyleme.com/bravais/activities/document\n */\n function buildCourseActivityObject() {\n if (!LRS.courseInfo) return null;\n\n // Warn if we don't have the required GUIDs for Bravais aggregation\n if (!LRS.courseInfo.guid) {\n warn('Missing document GUID - statement may fail Bravais aggregation. Using numeric ID:', LRS.courseInfo.documentId);\n }\n if (!LRS.courseInfo.versionGuid) {\n warn('Missing version GUID - statement may fail Bravais aggregation (document_version_id will be null)');\n }\n\n // Use packageName (Bravais document name) as the object name when available,\n // so statements match the searchable document name in Bravais Analytics.\n // Strip .zip extension to match Bravais convention. Fall back to Rise course title.\n var objectName = LRS.courseInfo.packageName\n ? LRS.courseInfo.packageName.replace(/\\.zip$/i, '')\n : decodeEntities(LRS.courseInfo.title);\n\n var obj = {\n objectType: 'Activity',\n id: LRS.courseInfo.id,\n definition: {\n type: 'http://xyleme.com/bravais/activities/document',\n name: { 'en-US': objectName }\n }\n };\n\n // Add Xyleme-format extensions\n obj.definition.extensions = {};\n\n // Version ID in Xyleme IRI format - REQUIRED for Bravais aggregation\n if (LRS.courseInfo.versionGuid) {\n obj.definition.extensions['versionId'] =\n 'http://xyleme.com/bravais/document.version/' + LRS.courseInfo.versionGuid;\n } else {\n // Log error - this will cause aggregation failure\n warn('versionId extension not set - this statement will fail Bravais aggregation!');\n }\n\n // Format (e.g., \"SCORM by Rise\")\n obj.definition.extensions['format'] = LRS.courseInfo.format || 'SCORM by Rise';\n\n // Language\n obj.definition.extensions['language'] = LRS.courseInfo.language || '';\n\n // Version number\n obj.definition.extensions['version'] = LRS.courseInfo.version || 1;\n\n // Document CDS ID (numeric)\n if (LRS.courseInfo.documentId) {\n obj.definition.extensions['documentCdsId'] =\n parseInt(LRS.courseInfo.documentId, 10) || LRS.courseInfo.documentId;\n }\n\n // Resource type\n obj.definition.extensions['resourceType'] = LRS.courseInfo.resourceType || 'Course';\n\n // Package name (original SCORM zip filename)\n if (LRS.courseInfo.packageName) {\n obj.definition.extensions['packageName'] = LRS.courseInfo.packageName;\n }\n\n return obj;\n }\n\n /**\n * Build the parent course activity for context.contextActivities.parent\n * This links sub-activities (media, pages, interactions) to the course document\n * CRITICAL: This is what allows statements to show up when searching by document name in Analytics\n */\n function buildParentCourseActivity() {\n if (!LRS.courseInfo || !LRS.courseInfo.id) return null;\n\n var parent = {\n id: LRS.courseInfo.id,\n objectType: 'Activity',\n definition: {\n type: 'http://xyleme.com/bravais/activities/document',\n name: { 'en-US': decodeEntities(LRS.courseInfo.title) || 'Rise Course' }\n }\n };\n\n // Add documentCdsId - this is what Bravais uses for document search\n if (LRS.courseInfo.documentId) {\n parent.definition.extensions = {\n documentCdsId: parseInt(LRS.courseInfo.documentId, 10) || LRS.courseInfo.documentId\n };\n }\n\n return parent;\n }\n\n // ========================================================================\n // XYLEME NATIVE FORMAT BUILDERS\n // ========================================================================\n\n /**\n * Build context object in Xyleme native format\n * Includes all required context extensions for Bravais correlation\n */\n function buildXylemeContext(additionalContext) {\n var ctx = additionalContext || {};\n\n // Platform: \"Cloud Player\" for Xyleme compatibility\n ctx.platform = 'Cloud Player';\n\n ctx.extensions = ctx.extensions || {};\n\n if (LRS.courseInfo) {\n // Document URI (required for document correlation)\n if (LRS.courseInfo.guid) {\n ctx.extensions['document'] = 'http://xyleme.com/bravais/document/' + LRS.courseInfo.guid;\n } else if (LRS.courseInfo.documentId) {\n ctx.extensions['document'] = 'http://xyleme.com/bravais/document/' + LRS.courseInfo.documentId;\n }\n\n // Document version URI\n if (LRS.courseInfo.versionGuid) {\n ctx.extensions['documentVersion'] = 'http://xyleme.com/bravais/document.version/' + LRS.courseInfo.versionGuid;\n }\n\n // Shared link info\n if (LRS.courseInfo.sharedLinkToken) {\n ctx.extensions['sharedLinkToken'] = LRS.courseInfo.sharedLinkToken;\n }\n if (LRS.courseInfo.sharedLinkName) {\n ctx.extensions['sharedLinkName'] = LRS.courseInfo.sharedLinkName;\n }\n }\n\n // Course attempt ID (unique per session)\n ctx.extensions['courseAttemptId'] = LRS.courseAttemptId;\n\n // Publish index (default to 1)\n ctx.extensions['publishIndex'] = (LRS.courseInfo && LRS.courseInfo.publishIndex) ? LRS.courseInfo.publishIndex : 1;\n\n // Employee ID from employee lookup (if available)\n if (employeeLookupData && employeeLookupData.employeeId) {\n ctx.extensions['employeeId'] = employeeLookupData.employeeId;\n }\n\n // Xyleme schema version and PII flag\n ctx.extensions['http://xyleme.com/bravais/extensions/statement-schema'] = 'xyleme_10';\n ctx.extensions['http://xyleme.com/bravais/extensions/protect_pii'] = false;\n\n // Add parent course activity for document-level aggregation in Bravais\n // This allows statements with activity-specific objects to still be found\n // when searching by document name in Analytics\n var parentActivity = buildParentCourseActivity();\n if (parentActivity) {\n ctx.contextActivities = ctx.contextActivities || {};\n ctx.contextActivities.parent = [parentActivity];\n }\n\n return ctx;\n }\n\n /**\n * Build activity object for page/topic-level statements\n * Uses Xyleme page URI format instead of course/document\n */\n function buildPageActivityObject(pageInfo) {\n var pageGuid = pageInfo.pageGuid || pageInfo.id || generateUUID();\n\n return {\n objectType: 'Activity',\n id: 'http://xyleme.com/bravais/page/' + pageGuid,\n definition: {\n type: XYLEME_ACTIVITY_TYPES.page,\n name: { 'en-US': pageInfo.name || pageInfo.title || 'Page' },\n extensions: {\n resourceType: pageInfo.resourceType || RESOURCE_TYPES[pageInfo.type] || 'Topic'\n }\n }\n };\n }\n\n /**\n * Build activity object for assessments in Xyleme format\n */\n function buildAssessmentActivityObject(assessmentInfo) {\n var assessmentGuid = assessmentInfo.assessmentGuid || assessmentInfo.id || generateUUID();\n\n return {\n objectType: 'Activity',\n id: 'http://xyleme.com/bravais/assessment/' + assessmentGuid,\n definition: {\n type: XYLEME_ACTIVITY_TYPES.assessment,\n name: { 'en-US': assessmentInfo.title || 'Assessment' },\n extensions: {\n resourceType: 'Assessment'\n }\n }\n };\n }\n\n /**\n * Build activity object for questions in Xyleme format\n * Name format: \"Assessment Name - Q#: Question text...\"\n */\n /**\n * Simple string hash for generating short, stable IDs from long strings.\n * Returns a hex string (8 chars). Not cryptographic — just for uniqueness.\n */\n function hashString(str) {\n var hash = 0;\n for (var i = 0; i < str.length; i++) {\n var ch = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + ch;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash).toString(16).padStart(8, '0');\n }\n\n function buildQuestionActivityObject(questionInfo) {\n var rawGuid = questionInfo.questionGuid || questionInfo.id || generateUUID();\n\n // Ensure the object ID stays under 255 chars total to avoid Bravais aggregation failures.\n // Rise SCORM interaction IDs can be very long (full slugified question text).\n // Use a truncated prefix + hash to keep it short but unique and stable.\n var questionGuid = rawGuid;\n if (rawGuid.length > 80) {\n questionGuid = rawGuid.substring(0, 60) + '_' + hashString(rawGuid);\n }\n\n // Build human-readable display name\n var displayName = (questionInfo.assessmentName || 'Knowledge Check');\n if (questionInfo.questionNumber) {\n displayName += ' - Q' + questionInfo.questionNumber;\n }\n var questionText = decodeEntities(questionInfo.text || questionInfo.questionText || '');\n if (questionText.length > 50) {\n displayName += ': ' + questionText.substring(0, 47) + '...';\n } else if (questionText.length > 0) {\n displayName += ': ' + questionText;\n }\n\n return {\n objectType: 'Activity',\n id: 'http://xyleme.com/bravais/question/' + questionGuid,\n definition: {\n type: XYLEME_ACTIVITY_TYPES.question,\n name: { 'en-US': displayName },\n description: { 'en-US': questionText || 'Question' },\n extensions: {\n resourceType: 'Question',\n questionNumber: questionInfo.questionNumber || null\n }\n }\n };\n }\n\n /**\n * Format seconds into MM:SS or HH:MM:SS format\n */\n function formatMediaTime(seconds) {\n if (typeof seconds !== 'number' || isNaN(seconds)) return '0:00';\n var totalSeconds = Math.floor(seconds);\n var hours = Math.floor(totalSeconds / 3600);\n var minutes = Math.floor((totalSeconds % 3600) / 60);\n var secs = totalSeconds % 60;\n\n if (hours > 0) {\n return hours + ':' + (minutes < 10 ? '0' : '') + minutes + ':' + (secs < 10 ? '0' : '') + secs;\n }\n return minutes + ':' + (secs < 10 ? '0' : '') + secs;\n }\n\n /**\n * Build activity object for media (video/audio) with human-readable name\n * Name format varies by action:\n * - played: \"Video: Title (started at 1:23)\"\n * - paused: \"Video: Title (paused at 2:45)\"\n * - completed: \"Video: Title (completed)\"\n */\n function buildMediaActivityObject(mediaInfo) {\n var mediaGuid = mediaInfo.mediaGuid || generateUUID();\n var mediaType = mediaInfo.type === 'audio' ? ACTIVITY_TYPES.audio : ACTIVITY_TYPES.video;\n var resourceType = mediaInfo.type === 'audio' ? 'Audio' : 'Video';\n\n // Build human-readable display name\n var baseName = mediaInfo.name || '';\n if (!baseName || baseName === 'video' || baseName === 'audio' || baseName === 'Media') {\n baseName = mediaInfo.lessonName || 'Media';\n }\n\n // Add time context based on action\n var displayName = resourceType + ': ' + baseName;\n var currentTime = mediaInfo.currentTime || 0;\n var action = mediaInfo.action;\n\n if (action === 'play' || action === 'played') {\n displayName += ' (started at ' + formatMediaTime(currentTime) + ')';\n } else if (action === 'pause' || action === 'paused') {\n displayName += ' (paused at ' + formatMediaTime(currentTime) + ')';\n } else if (action === 'completed' || action === 'complete') {\n displayName += ' (completed)';\n }\n\n return {\n objectType: 'Activity',\n id: 'http://xyleme.com/bravais/media/' + mediaGuid,\n definition: {\n type: mediaType,\n name: { 'en-US': displayName },\n extensions: {\n resourceType: resourceType,\n mediaSrc: mediaInfo.src || '',\n mediaType: mediaInfo.type || 'video',\n duration: mediaInfo.duration || 0\n }\n }\n };\n }\n\n /**\n * Build activity object for interactions (tabs, accordions, flashcards, etc.)\n * Name format: \"Tab: Label\" or \"Accordion: Label\"\n */\n function buildInteractionActivityObject(interactionInfo) {\n var typeLabels = {\n 'tab': 'Tab',\n 'accordion': 'Accordion',\n 'flashcard': 'Flashcard',\n 'hotspot': 'Hotspot',\n 'process-step': 'Process Step',\n 'process': 'Process Step',\n 'sorting': 'Sorting Activity',\n 'button': 'Button',\n 'labeled-graphic': 'Labeled Graphic',\n 'timeline': 'Timeline',\n 'scenario': 'Scenario',\n 'checklist': 'Checklist',\n 'marker': 'Marker'\n };\n\n var interactionType = interactionInfo.type || interactionInfo.interactionType || 'interaction';\n var typeLabel = typeLabels[interactionType] || 'Interaction';\n\n // Build human-readable display name\n var displayName = interactionInfo.name;\n if (!displayName) {\n displayName = typeLabel + ': ' + (interactionInfo.label || interactionInfo.id || 'Item');\n }\n\n return {\n objectType: 'Activity',\n id: 'http://xyleme.com/bravais/interaction/' + (interactionInfo.id || generateUUID()),\n definition: {\n type: ACTIVITY_TYPES.interaction,\n name: { 'en-US': displayName },\n extensions: {\n resourceType: 'Interaction',\n interactionType: interactionType\n }\n }\n };\n }\n\n /**\n * Build xAPI statement in Xyleme native format\n * Supports page-level objects while maintaining document context\n */\n function buildStatementXyleme(verb, activityObject, result, additionalContext) {\n var statement = {\n id: generateUUID(),\n actor: getActor(),\n verb: typeof verb === 'string' ? (VERBS[verb] || { id: verb, display: { 'en-US': verb } }) : verb,\n object: activityObject,\n timestamp: new Date().toISOString()\n };\n\n if (result) {\n statement.result = result;\n }\n\n // Build context with all Xyleme extensions\n statement.context = buildXylemeContext(additionalContext);\n\n return statement;\n }\n\n function buildActivityObject(id, type, name, description) {\n var obj = {\n objectType: 'Activity',\n id: id,\n definition: {\n type: type || ACTIVITY_TYPES.lesson\n }\n };\n\n if (name) {\n obj.definition.name = { 'en-US': name };\n }\n if (description) {\n obj.definition.description = { 'en-US': description };\n }\n\n return obj;\n }\n\n // ========================================================================\n // 6. STATEMENT SENDING\n // ========================================================================\n\n function sendStatement(statement) {\n log('Sending statement:', statement.verb.display['en-US'] || statement.verb.id, statement);\n\n // Log first statement visibly (not gated by DEBUG) for production diagnostics\n if (LRS.stats.statementsSent === 0 && LRS.stats.statementsFailed === 0 && LRS.stats.statementsQueued === 0) {\n if (window.console && window.console.info) {\n var verb = statement.verb.display ? (statement.verb.display['en-US'] || statement.verb.id) : statement.verb.id;\n console.info('[PA-LRS] First statement: verb=' + verb +\n ', endpoint=' + (LRS_ENDPOINT || 'NONE') +\n ', proxy=' + (LRS_PROXY_ENDPOINT ? 'yes' : 'no') +\n ', mode=' + LRS.mode);\n }\n }\n\n // Store in event log\n LRS.eventLog.push({\n statement: statement,\n timestamp: new Date().toISOString(),\n mode: LRS.mode,\n lrsEndpoint: LRS_ENDPOINT\n });\n\n // ALWAYS prefer direct LRS when endpoint is available\n // This ensures all our detailed tracking (media, interactions, etc.) goes to the LRS\n // PlayerIntegration is used only for SCORM state, not for xAPI statements\n if (LRS_ENDPOINT && LRS_ENDPOINT.length > 0) {\n log('Sending via directLRS:', LRS_ENDPOINT);\n return sendViaDirectLRS(statement);\n } else if (LRS.mode === 'playerIntegration' && LRS.integration) {\n // Fallback to PlayerIntegration only if no direct LRS endpoint\n log('No LRS endpoint, falling back to PlayerIntegration');\n return sendViaPlayerIntegration(statement);\n } else {\n // SCORM-only or offline mode - just log\n log('Statement logged (no LRS):', statement.verb.display['en-US']);\n LRS.stats.statementsQueued++;\n return Promise.resolve({ logged: true });\n }\n }\n\n function sendViaPlayerIntegration(statement) {\n // Map xAPI verb to PlayerIntegration method\n var verbId = statement.verb.id;\n var data = {\n ...statement.object.definition,\n id: statement.object.id,\n timestamp: statement.timestamp\n };\n\n if (statement.result) {\n data.result = statement.result;\n }\n\n try {\n if (verbId.indexOf('played') > -1 || verbId.indexOf('paused') > -1 || verbId.indexOf('completed') > -1) {\n if (statement.object.definition && statement.object.definition.type &&\n (statement.object.definition.type.indexOf('video') > -1 || statement.object.definition.type.indexOf('audio') > -1)) {\n LRS.integration.mediaPlayed(data);\n }\n } else if (verbId.indexOf('experienced') > -1 || verbId.indexOf('viewed') > -1 || verbId.indexOf('launched') > -1) {\n LRS.integration.contentOpened(data);\n } else if (verbId.indexOf('attempted') > -1) {\n LRS.integration.assessmentStarted(data);\n } else if (verbId.indexOf('passed') > -1 || verbId.indexOf('failed') > -1) {\n LRS.integration.assessmentEnded(data);\n } else if (verbId.indexOf('answered') > -1) {\n LRS.integration.questionAnswered(data);\n } else if (verbId.indexOf('interacted') > -1) {\n LRS.integration.sendCustomStatement({ verb: 'interacted', data: data });\n } else {\n LRS.integration.sendCustomStatement({ verb: verbId, data: data });\n }\n\n LRS.stats.statementsSent++;\n return Promise.resolve({ sent: true, via: 'playerIntegration' });\n\n } catch (e) {\n warn('Error sending via PlayerIntegration:', e);\n LRS.stats.statementsFailed++;\n return Promise.reject(e);\n }\n }\n\n function sendViaDirectLRS(statement) {\n // Use proxy endpoint if configured, otherwise direct LRS\n var useProxy = LRS_PROXY_ENDPOINT && LRS_PROXY_ENDPOINT.length > 0;\n var endpoint = useProxy ? LRS_PROXY_ENDPOINT : LRS_ENDPOINT;\n\n if (!endpoint || endpoint.length === 0) {\n warn('No LRS endpoint available');\n LRS.stats.statementsFailed++;\n return Promise.reject(new Error('No LRS endpoint'));\n }\n\n return new Promise(function(resolve, reject) {\n var xhr = new XMLHttpRequest();\n // POST to proxy (proxy handles PUT to LRS), POST to direct LRS\n xhr.open('POST', endpoint, true);\n\n // xAPI required headers\n xhr.setRequestHeader('Content-Type', 'application/json');\n xhr.setRequestHeader('X-Experience-API-Version', '1.0');\n\n if (useProxy) {\n // Proxy handles auth server-side, no auth headers needed\n log('Sending via LRS proxy:', endpoint);\n } else if (LRS_AUTH && LRS_AUTH.length > 0) {\n xhr.setRequestHeader('Authorization', 'Basic ' + LRS_AUTH);\n log('Using Basic Auth for LRS request');\n } else if (LRS_WITH_CREDENTIALS) {\n xhr.withCredentials = true;\n }\n\n xhr.onreadystatechange = function() {\n if (xhr.readyState === 4) {\n if (xhr.status >= 200 && xhr.status < 300) {\n log('Statement sent successfully:', statement.verb.display['en-US'], useProxy ? '(via proxy)' : '(direct)');\n LRS.stats.statementsSent++;\n resolve({ sent: true, via: useProxy ? 'proxy' : 'directLRS', status: xhr.status });\n } else {\n warn('Statement send failed:', xhr.status, xhr.statusText);\n LRS.stats.statementsFailed++;\n\n // Queue for retry if server error\n if (xhr.status >= 500) {\n LRS.statementQueue.push(statement);\n }\n\n reject(new Error('HTTP ' + xhr.status + ': ' + xhr.statusText));\n }\n }\n };\n\n xhr.onerror = function() {\n warn('Network error sending statement');\n LRS.stats.statementsFailed++;\n LRS.statementQueue.push(statement);\n reject(new Error('Network error'));\n };\n\n try {\n xhr.send(JSON.stringify(statement));\n } catch (e) {\n warn('Error sending statement:', e);\n LRS.stats.statementsFailed++;\n reject(e);\n }\n });\n }\n\n // Retry queue processing\n function processStatementQueue() {\n if (LRS.statementQueue.length === 0) return;\n\n log('Processing statement queue:', LRS.statementQueue.length, 'statements');\n\n var queue = LRS.statementQueue.slice();\n LRS.statementQueue = [];\n\n queue.forEach(function(statement) {\n sendViaDirectLRS(statement).catch(function(e) {\n // Already re-queued on failure\n });\n });\n }\n\n // ========================================================================\n // 7. PUBLIC API METHODS\n // ========================================================================\n\n // Course launched - uses Xyleme native format for course object\n LRS.courseLaunched = function() {\n if (!TRACK_NAVIGATION) return;\n\n // Course launch uses the document activity type (default)\n var statement = buildStatement(\n 'launched',\n 'http://xyleme.com/bravais/activities/document',\n { event: 'course_launched' }\n );\n sendStatement(statement);\n };\n\n // Content/Page viewed - uses page-specific object for human-readable display\n // Object shows page/lesson title, parent context maintains document aggregation\n LRS.contentOpened = function(data) {\n if (!TRACK_NAVIGATION) return;\n\n // Get current lesson info from DOM (includes name)\n var lessonInfo = getCurrentLessonInfo();\n\n var result = null;\n if (data.duration) {\n result = { duration: data.duration };\n }\n\n // Extract clean page/lesson ID from Rise hash paths\n var lessonId = data.pageGuid || data.lessonId || lessonInfo.id || '';\n if (lessonId.indexOf('#/lessons/') === 0) {\n lessonId = lessonId.substring(10); // Remove '#/lessons/'\n } else if (lessonId.indexOf('#/') === 0) {\n lessonId = lessonId.substring(2); // Remove '#/'\n }\n\n // Get the page title - prefer lesson name from DOM\n var pageTitle = data.title || lessonInfo.name || 'Page';\n\n // Build page activity object with human-readable name\n // This shows the page/lesson title in the Object column\n var pageObject = buildPageActivityObject({\n pageGuid: lessonId,\n name: pageTitle,\n resourceType: 'Lesson'\n });\n\n // Add page info to context extensions\n var additionalContext = {\n extensions: {\n pageId: lessonId || 'home',\n pageTitle: pageTitle,\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName,\n pageUrl: data.url || window.location.href\n }\n };\n\n // Use 'experienced' verb for viewing content\n // Parent course activity added by buildXylemeContext ensures document aggregation\n var statement = buildStatementXyleme('experienced', pageObject, result, additionalContext);\n sendStatement(statement);\n };\n\n // Media events - uses activity-specific object with human-readable name\n // Parent context (added by buildXylemeContext) ensures document aggregation\n LRS.mediaPlayed = function(data) {\n if (!TRACK_MEDIA) return;\n\n // Get current lesson context\n var lessonInfo = getCachedLessonInfo();\n\n var verbKey = data.action === 'play' ? 'played' :\n data.action === 'pause' ? 'paused' :\n data.action === 'completed' ? 'completed' : 'played';\n\n // Build activity-specific object with human-readable name including time context\n // e.g., \"Video: Introduction (started at 1:23)\" or \"Audio: Podcast (paused at 2:45)\"\n var mediaObject = buildMediaActivityObject({\n type: data.type,\n src: data.src,\n name: data.name,\n duration: data.duration,\n lessonName: lessonInfo.name,\n action: data.action,\n currentTime: data.currentTime\n });\n\n var result = {\n extensions: {\n 'https://w3id.org/xapi/video/extensions/time': data.currentTime || 0,\n 'https://w3id.org/xapi/video/extensions/progress': data.duration > 0 ?\n Math.round((data.currentTime / data.duration) * 100) / 100 : 0,\n 'https://w3id.org/xapi/video/extensions/length': data.duration || 0\n }\n };\n\n if (data.action === 'completed') {\n result.completion = true;\n }\n\n // Context includes lesson info; parent course activity added by buildXylemeContext\n var additionalContext = {\n extensions: {\n lessonId: lessonInfo.id,\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName\n }\n };\n\n var statement = buildStatementXyleme(verbKey, mediaObject, result, additionalContext);\n sendStatement(statement);\n };\n\n // Assessment started\n LRS.assessmentStarted = function(data) {\n if (!TRACK_QUIZZES) return;\n\n // Get lesson context\n var lessonInfo = getCachedLessonInfo();\n\n // Build assessment activity object for Xyleme format\n var assessmentObject = buildAssessmentActivityObject({\n assessmentGuid: data.assessmentGuid || data.id,\n title: data.title || data.assessmentName || 'Assessment'\n });\n\n // Add lesson context to extensions\n assessmentObject.definition = assessmentObject.definition || {};\n assessmentObject.definition.extensions = assessmentObject.definition.extensions || {};\n assessmentObject.definition.extensions.lessonName = lessonInfo.name;\n assessmentObject.definition.extensions.sectionName = lessonInfo.sectionName;\n\n var statement = buildStatementXyleme('attempted', assessmentObject);\n sendStatement(statement);\n };\n\n // Assessment ended\n LRS.assessmentEnded = function(data) {\n if (!TRACK_QUIZZES) return;\n\n // Get lesson context (may be passed in from extractQuizResult)\n var lessonInfo = data.lessonName ? { name: data.lessonName, sectionName: data.sectionName } : getCachedLessonInfo();\n\n // Build assessment activity object for Xyleme format\n var assessmentObject = buildAssessmentActivityObject({\n assessmentGuid: data.assessmentGuid || data.assessmentId || data.id,\n title: data.title || data.assessmentName || 'Assessment'\n });\n\n // Add lesson context to extensions\n assessmentObject.definition = assessmentObject.definition || {};\n assessmentObject.definition.extensions = assessmentObject.definition.extensions || {};\n assessmentObject.definition.extensions.lessonName = lessonInfo.name;\n assessmentObject.definition.extensions.sectionName = lessonInfo.sectionName;\n assessmentObject.definition.extensions.assessmentName = data.assessmentName || data.title;\n\n var result = {\n completion: true,\n extensions: {\n 'https://w3id.org/xapi/acrossx/extensions/assessmentName': data.assessmentName || data.title || null,\n 'https://w3id.org/xapi/acrossx/extensions/lessonName': lessonInfo.name || null,\n 'https://w3id.org/xapi/acrossx/extensions/sectionName': lessonInfo.sectionName || null,\n 'https://w3id.org/xapi/acrossx/extensions/questionCount': data.questionCount || null\n }\n };\n\n if (typeof data.score === 'number') {\n result.score = {\n scaled: data.score / 100,\n raw: data.score,\n max: 100,\n min: 0\n };\n result.success = data.score >= (data.passingScore || 70);\n }\n\n if (data.duration) {\n result.duration = data.duration;\n }\n\n var verbKey = result.success !== false ? 'passed' : 'failed';\n if (typeof result.success === 'undefined') {\n verbKey = 'completed';\n }\n\n var statement = buildStatementXyleme(verbKey, assessmentObject, result);\n sendStatement(statement);\n };\n\n // Question answered\n LRS.questionAnswered = function(data) {\n if (!TRACK_QUIZZES) return;\n\n // Get lesson context if not provided\n var lessonInfo = data.lessonName ? data : getCachedLessonInfo();\n\n // Build question activity object for Xyleme format\n var questionObject = buildQuestionActivityObject({\n questionGuid: data.questionGuid || data.questionId,\n text: data.questionText || 'Question'\n });\n\n // Build comprehensive result with human-readable data\n var result = {\n response: data.answer || data.response || '',\n extensions: {\n 'https://w3id.org/xapi/acrossx/extensions/questionNumber': data.questionNumber || null,\n 'https://w3id.org/xapi/acrossx/extensions/questionText': data.questionText || null,\n 'https://w3id.org/xapi/acrossx/extensions/answerText': data.answer || data.response || null,\n 'https://w3id.org/xapi/acrossx/extensions/correctAnswer': data.correctAnswer || null,\n 'https://w3id.org/xapi/acrossx/extensions/assessmentName': data.assessmentName || null,\n 'https://w3id.org/xapi/acrossx/extensions/lessonName': lessonInfo.lessonName || lessonInfo.name || null,\n 'https://w3id.org/xapi/acrossx/extensions/sectionName': lessonInfo.sectionName || null\n }\n };\n\n if (data.result === 'correct' || data.correct === true) {\n result.success = true;\n } else if (data.result === 'incorrect' || data.correct === false) {\n result.success = false;\n }\n\n var statement = buildStatementXyleme('answered', questionObject, result);\n sendStatement(statement);\n };\n\n // Submit all questions\n LRS.questionsSubmitAll = function(data) {\n if (!TRACK_QUIZZES) return;\n\n // Send individual question statements\n if (data.questions && Array.isArray(data.questions)) {\n data.questions.forEach(function(q) {\n LRS.questionAnswered(q);\n });\n }\n\n // Then send assessment completed\n LRS.assessmentEnded(data);\n };\n\n // Interactions (tabs, accordions, etc.) - uses activity-specific object\n // Object name shows the interaction type and label, e.g., \"Tab: Overview\"\n // Parent context ensures these appear in document search results\n LRS.interacted = function(data) {\n if (!TRACK_INTERACTIONS) return;\n\n // Get current lesson context\n var lessonInfo = getCachedLessonInfo();\n\n // Build activity-specific object with human-readable name\n // e.g., \"Tab: Getting Started\", \"Accordion: FAQ\"\n var interactionObject = buildInteractionActivityObject({\n type: data.type || data.interactionType,\n id: data.id,\n name: data.name,\n label: data.name || data.type\n });\n\n // Context includes lesson info; parent course activity added by buildXylemeContext\n var additionalContext = {\n extensions: {\n interactionId: data.id || 'interaction-' + Date.now(),\n lessonId: lessonInfo.id,\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName\n }\n };\n\n var statement = buildStatementXyleme('interacted', interactionObject, null, additionalContext);\n sendStatement(statement);\n };\n\n // Custom statement\n LRS.sendCustomStatement = function(data) {\n if (!CUSTOM_STATEMENTS) return;\n\n var verb = data.verb || 'interacted';\n var objectData = data.object || {};\n\n // Activity type from data or default to interaction\n var activityType = objectData.type || ACTIVITY_TYPES.interaction;\n\n // Activity details\n var activityDetails = {\n customActivityId: objectData.id || data.id || 'custom-' + Date.now(),\n customActivityName: objectData.name || data.name || 'Custom Activity'\n };\n\n // Merge any extra data\n if (data.data) {\n for (var key in data.data) {\n if (data.data.hasOwnProperty(key)) {\n activityDetails[key] = data.data[key];\n }\n }\n }\n\n var statement = buildStatement(verb, activityType, activityDetails, data.result, data.context);\n sendStatement(statement);\n };\n\n // Send a \"launched\" statement — called by skin after email gate / actor setup\n LRS.sendLaunchStatement = function(data) {\n data = data || {};\n var activityDetails = {\n launchSource: data.source || 'email-gate',\n employeeEmail: data.email || (LRS.actor && LRS.actor.mbox ? LRS.actor.mbox.replace('mailto:', '') : undefined),\n employeeId: data.employeeId || LRS.employeeId || undefined\n };\n\n if (data.employeeName) activityDetails.employeeName = data.employeeName;\n\n var statement = buildStatement('launched', ACTIVITY_TYPES.course, activityDetails, null, null);\n log('Sending launch statement for:', activityDetails.employeeEmail || '(unknown)');\n sendStatement(statement);\n };\n\n // Send a course completion statement — called by skin or auto-detected via SCORM\n LRS.sendCompletionStatement = function(data) {\n data = data || {};\n // Cancel any pending SCORM auto-detection to avoid double-fire for same attempt\n if (scormCompletionDebounce) {\n clearTimeout(scormCompletionDebounce);\n scormCompletionDebounce = null;\n }\n\n var status = data.status || 'completed';\n var courseTitle = data.courseTitle || (LRS.courseInfo && LRS.courseInfo.title ? decodeEntities(LRS.courseInfo.title) : decodeEntities(document.title) || 'Course');\n\n var verbKey = 'completed';\n if (status === 'passed') verbKey = 'passed';\n if (status === 'failed') verbKey = 'failed';\n\n var result = { completion: true, success: (status === 'passed') };\n\n if (typeof data.score === 'number') {\n var max = data.scoreMax || 100;\n var min = data.scoreMin || 0;\n result.score = {\n raw: data.score,\n max: max,\n min: min,\n scaled: max > min ? (data.score - min) / (max - min) : 0\n };\n }\n\n var activityDetails = {\n courseTitle: courseTitle,\n completionStatus: status,\n employeeEmail: data.email || (LRS.actor && LRS.actor.mbox ? LRS.actor.mbox.replace('mailto:', '') : undefined),\n employeeId: data.employeeId || LRS.employeeId || undefined\n };\n if (data.employeeName) activityDetails.employeeName = data.employeeName;\n\n // 1. Always send \"completed\" statement\n log('Sending completed statement, score:', result.score, 'title:', courseTitle);\n var completedStatement = buildStatement('completed', ACTIVITY_TYPES.course, activityDetails, result, null);\n sendStatement(completedStatement);\n\n // 2. Send \"passed\" or \"failed\" statement (assessment outcome)\n if (status === 'passed' || status === 'failed') {\n log('Sending', status, 'statement');\n var outcomeStatement = buildStatement(status, ACTIVITY_TYPES.course, activityDetails, result, null);\n sendStatement(outcomeStatement);\n }\n };\n\n // Send a \"terminated\" statement — called by skin close button or auto on page unload\n var exitStatementSent = false;\n LRS.sendExitStatement = function(data) {\n if (exitStatementSent) return;\n exitStatementSent = true;\n data = data || {};\n\n var courseTitle = (LRS.courseInfo && LRS.courseInfo.title) ? decodeEntities(LRS.courseInfo.title) : decodeEntities(document.title) || 'Course';\n var duration = null;\n if (LRS.launchTime) {\n var ms = new Date().getTime() - new Date(LRS.launchTime).getTime();\n var secs = Math.floor(ms / 1000);\n var mins = Math.floor(secs / 60);\n var hrs = Math.floor(mins / 60);\n duration = 'PT' + (hrs > 0 ? hrs + 'H' : '') + (mins % 60) + 'M' + (secs % 60) + 'S';\n }\n\n var activityDetails = {\n courseTitle: courseTitle,\n exitSource: data.source || 'page-unload',\n employeeEmail: LRS.actor && LRS.actor.mbox ? LRS.actor.mbox.replace('mailto:', '') : undefined,\n employeeId: LRS.employeeId || undefined,\n sessionDuration: duration,\n statementsSent: LRS.stats.statementsSent\n };\n\n var result = duration ? { duration: duration } : null;\n log('Sending exit statement, duration:', duration);\n var statement = buildStatement('terminated', ACTIVITY_TYPES.course, activityDetails, result, null);\n sendStatement(statement);\n };\n\n // Auto-send exit statement on page unload\n window.addEventListener('beforeunload', function() {\n if (!exitStatementSent && LRS.actor) {\n LRS.sendExitStatement({ source: 'page-unload' });\n }\n });\n\n /**\n * Re-extract actor from SCORM after LMSInitialize has been called.\n * Call this from the SCORM wrapper after scormInit() succeeds,\n * because the bridge initializes in <head> before LMSInitialize runs.\n */\n LRS.refreshActor = function(callback) {\n // If SCORM API wasn't found during <head> init, re-scan now\n // (some LMS inject window.API after page starts loading)\n if (!LRS.scormApi) {\n log('refreshActor: SCORM API missing, re-scanning...');\n var api2004 = findAPIInFrameHierarchy('API_1484_11', 10);\n if (api2004) {\n LRS.scormApi = api2004.api;\n LRS.scormApiFound = true;\n LRS.scormApiType = '2004';\n log('refreshActor: found SCORM 2004 API');\n } else {\n var api12 = findAPIInFrameHierarchy('API', 10);\n if (api12) {\n LRS.scormApi = api12.api;\n LRS.scormApiFound = true;\n LRS.scormApiType = '1.2';\n log('refreshActor: found SCORM 1.2 API');\n }\n }\n }\n\n // Always-visible diagnostic: confirm refreshActor is being called and show SCORM data (TEMPORARY)\n if (window.console && window.console.info) {\n var scormId = 'n/a', scormName = 'n/a';\n if (LRS.scormApi) {\n try {\n scormId = LRS.scormApiType === '2004'\n ? LRS.scormApi.GetValue('cmi.learner_id')\n : LRS.scormApi.LMSGetValue('cmi.core.student_id');\n scormName = LRS.scormApiType === '2004'\n ? LRS.scormApi.GetValue('cmi.learner_name')\n : LRS.scormApi.LMSGetValue('cmi.core.student_name');\n } catch(e) { scormId = 'error'; scormName = 'error'; }\n }\n console.info('[PA-LRS] refreshActor called. SCORM API=' + (LRS.scormApi ? 'found' : 'MISSING') +\n ', student_id=' + scormId +\n ', student_name=' + scormName +\n ', previous actor=' + (LRS.actor ? LRS.actor.name : 'none'));\n }\n log('refreshActor called - re-extracting from SCORM...');\n var previousName = LRS.actor ? LRS.actor.name : 'none';\n\n // Re-run sync extraction (SCORM data should now be available)\n var newActor = extractActor();\n\n if (isValidActor(newActor) && newActor.account && newActor.account.name !== 'unknown') {\n log('refreshActor: got valid actor:', newActor.name, newActor.account ? newActor.account.name : '');\n // Enhance with email lookup (async)\n extractActorAsync(function(enhancedActor) {\n if (window.console && window.console.info) {\n console.info('[PA-LRS] Actor refreshed: ' + previousName + ' -> ' + enhancedActor.name +\n (enhancedActor.mbox ? ' (' + enhancedActor.mbox + ')' : ''));\n }\n if (callback) callback(enhancedActor);\n });\n } else {\n log('refreshActor: no valid actor found after re-extraction');\n if (callback) callback(LRS.actor);\n }\n };\n\n // Course terminated/exited\n LRS.courseTerminated = function() {\n // Calculate duration\n var duration = null;\n if (LRS.launchTime) {\n var launchMs = new Date(LRS.launchTime).getTime();\n var nowMs = Date.now();\n var durationMs = nowMs - launchMs;\n // Convert to ISO 8601 duration\n var seconds = Math.floor(durationMs / 1000);\n var minutes = Math.floor(seconds / 60);\n var hours = Math.floor(minutes / 60);\n seconds = seconds % 60;\n minutes = minutes % 60;\n duration = 'PT' + hours + 'H' + minutes + 'M' + seconds + 'S';\n }\n\n var result = duration ? { duration: duration } : null;\n\n // Use course object for termination (document-level) with 'exited' verb for Xyleme\n var courseObj = buildCourseActivityObject();\n if (courseObj) {\n courseObj.definition.extensions = courseObj.definition.extensions || {};\n courseObj.definition.extensions.resourceType = 'Course';\n }\n\n var statement = buildStatementXyleme('exited', courseObj, result);\n sendStatement(statement);\n };\n\n // Legacy compatibility methods\n LRS.choiceBranchSelected = function(data) {\n LRS.interacted({ type: 'choice-branch', id: data.id, name: 'Choice Branch Selected' });\n };\n\n LRS.choiceBranchDiscarded = function(data) {\n LRS.interacted({ type: 'choice-branch', id: data.id, name: 'Choice Branch Discarded' });\n };\n\n // ========================================================================\n // PUBLIC API — expose core internals for skin scripts\n // Usage: window.pa_patcher.lrs.api.sendStatement(stmt)\n // ========================================================================\n LRS.api = {\n sendStatement: sendStatement,\n buildStatement: buildStatement,\n buildCourseActivityObject: buildCourseActivityObject,\n buildXylemeContext: buildXylemeContext,\n generateUUID: generateUUID,\n decodeEntities: decodeEntities,\n extractActor: extractActor,\n getCachedLessonInfo: getCachedLessonInfo,\n VERBS: VERBS,\n ACTIVITY_TYPES: ACTIVITY_TYPES\n };\n\n // ========================================================================\n // 8. RISE EVENT INTERCEPTORS\n // ========================================================================\n\n function setupMediaInterceptors() {\n if (!TRACK_MEDIA) return;\n\n // Track which elements we've already attached listeners to\n var trackedMedia = new WeakSet();\n\n // Helper to get media name/title from element or surrounding context\n function getMediaName(el) {\n // Check element attributes\n var name = el.title || el.getAttribute('aria-label') || el.getAttribute('data-name');\n if (name) return name;\n\n // Check parent Rise video block for title\n var videoBlock = findClosest(el, '[data-block-type=\"video\"], .blocks-video, .video-block');\n if (videoBlock) {\n var titleEl = videoBlock.querySelector('.video-title, .block-title, [class*=\"title\"]');\n if (titleEl) return titleEl.textContent.trim();\n }\n\n // Use src filename as fallback\n var src = el.src || el.currentSrc || '';\n if (src) {\n var filename = src.split('/').pop().split('?')[0];\n if (filename && filename.length < 100) return filename;\n }\n\n return el.tagName.toLowerCase();\n }\n\n // Attach media event listeners to a single element\n function attachMediaListeners(el) {\n if (trackedMedia.has(el)) return;\n trackedMedia.add(el);\n\n var mediaType = el.tagName.toLowerCase();\n log('Attaching media listeners to:', mediaType, el.src || el.currentSrc || 'no-src');\n\n el.addEventListener('play', function() {\n log('Media play event captured:', mediaType);\n LRS.mediaPlayed({\n type: mediaType,\n src: el.src || el.currentSrc || '',\n name: getMediaName(el),\n currentTime: el.currentTime || 0,\n duration: el.duration || 0,\n action: 'play'\n });\n });\n\n el.addEventListener('pause', function() {\n // Ignore pause events that fire right before ended\n if (el.ended) return;\n log('Media pause event captured:', mediaType);\n LRS.mediaPlayed({\n type: mediaType,\n src: el.src || el.currentSrc || '',\n name: getMediaName(el),\n currentTime: el.currentTime || 0,\n duration: el.duration || 0,\n action: 'pause'\n });\n });\n\n el.addEventListener('ended', function() {\n log('Media ended event captured:', mediaType);\n LRS.mediaPlayed({\n type: mediaType,\n src: el.src || el.currentSrc || '',\n name: getMediaName(el),\n currentTime: el.duration || 0,\n duration: el.duration || 0,\n action: 'completed'\n });\n });\n }\n\n // Scan for and attach listeners to all video/audio elements\n function scanForMedia(root) {\n var elements = (root || document).querySelectorAll('video, audio');\n log('Scanning for media elements, found:', elements.length);\n for (var i = 0; i < elements.length; i++) {\n attachMediaListeners(elements[i]);\n }\n }\n\n // Initial scan\n scanForMedia();\n\n // Document-level capture for any we might miss\n document.addEventListener('play', function(e) {\n var target = e.target;\n if (target.tagName === 'VIDEO' || target.tagName === 'AUDIO') {\n // Attach listeners if not already tracked\n if (!trackedMedia.has(target)) {\n log('Captured play from untracked media, attaching listeners');\n attachMediaListeners(target);\n }\n }\n }, true);\n\n // MutationObserver to detect dynamically added video/audio elements\n var mediaObserver = new MutationObserver(function(mutations) {\n var needsScan = false;\n mutations.forEach(function(mutation) {\n mutation.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'VIDEO' || node.tagName === 'AUDIO') {\n attachMediaListeners(node);\n } else if (node.querySelectorAll) {\n // Check for nested video/audio\n var nested = node.querySelectorAll('video, audio');\n if (nested.length > 0) {\n needsScan = true;\n }\n }\n }\n });\n });\n if (needsScan) {\n scanForMedia();\n }\n });\n\n // Start observing once DOM is ready\n function startMediaObserver() {\n if (document.body) {\n mediaObserver.observe(document.body, {\n childList: true,\n subtree: true\n });\n log('Media mutation observer started');\n } else {\n setTimeout(startMediaObserver, 100);\n }\n }\n startMediaObserver();\n\n // Periodic rescan for Rise lazy-loaded content\n setInterval(function() {\n scanForMedia();\n }, 3000);\n\n log('Media interceptors set up (enhanced)');\n }\n\n function setupNavigationInterceptors() {\n if (!TRACK_NAVIGATION) return;\n\n window.addEventListener('hashchange', function() {\n var newLesson = window.location.hash || '#/';\n if (newLesson !== LRS.currentLesson) {\n LRS.currentLesson = newLesson;\n LRS.contentOpened({\n lessonId: newLesson,\n url: window.location.href,\n action: 'navigated'\n });\n }\n });\n\n LRS.currentLesson = window.location.hash || '#/';\n\n log('Navigation interceptors set up');\n }\n\n function setupQuizInterceptors() {\n if (!TRACK_QUIZZES) return;\n\n // Set up Knowledge Check specific interceptors for Rise blocks\n setupKnowledgeCheckInterceptors();\n\n var quizObserver = new MutationObserver(function(mutations) {\n mutations.forEach(function(mutation) {\n mutation.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n detectQuizElements(node);\n }\n });\n });\n });\n\n function startQuizObserver() {\n if (document.body) {\n quizObserver.observe(document.body, {\n childList: true,\n subtree: true\n });\n log('Quiz observer started');\n } else {\n setTimeout(startQuizObserver, 100);\n }\n }\n startQuizObserver();\n }\n\n function detectQuizElements(node) {\n var resultClasses = ['quiz-result', 'knowledge-result', 'quiz-feedback', 'assessment-result',\n 'blocks-quiz__result', 'block-quiz-results'];\n\n resultClasses.forEach(function(cls) {\n if (node.classList && node.classList.contains(cls)) {\n extractQuizResult(node);\n }\n var results = node.querySelectorAll ? node.querySelectorAll('.' + cls) : [];\n results.forEach(function(resultNode) {\n extractQuizResult(resultNode);\n });\n });\n\n var startClasses = ['quiz-start', 'knowledge-check', 'assessment-intro', 'blocks-quiz'];\n startClasses.forEach(function(cls) {\n if (node.classList && node.classList.contains(cls)) {\n LRS.assessmentStarted({\n assessmentId: extractAssessmentId(node)\n });\n }\n });\n }\n\n function extractAssessmentId(node) {\n return node.getAttribute('data-block-id') ||\n node.getAttribute('data-assessment-id') ||\n node.getAttribute('id') ||\n 'quiz-' + Date.now();\n }\n\n /**\n * Extract quiz/assessment name from the DOM\n */\n function extractAssessmentName(node) {\n // Try various selectors for assessment/quiz titles\n var titleEl = node.querySelector(\n '.quiz-title, .assessment-title, .knowledge-check-title, ' +\n '.blocks-quiz__title, .block-title, ' +\n '[class*=\"quiz\"] [class*=\"title\"], ' +\n '[class*=\"assessment\"] [class*=\"title\"], ' +\n 'h2, h3'\n );\n if (titleEl) {\n return titleEl.textContent.trim().substring(0, 200);\n }\n\n // Try to get from parent block\n var parentBlock = node.closest('[data-block-type]');\n if (parentBlock) {\n var blockTitle = parentBlock.querySelector('.block-title, h2, h3');\n if (blockTitle) {\n return blockTitle.textContent.trim().substring(0, 200);\n }\n }\n\n // Get current lesson name as context\n var lessonInfo = getCachedLessonInfo();\n if (lessonInfo.name) {\n return 'Quiz in ' + lessonInfo.name;\n }\n\n return 'Knowledge Check';\n }\n\n function extractQuizResult(node) {\n var scoreEl = node.querySelector('.score-value, .quiz-score, .score-percentage, [class*=\"score\"]');\n var score = scoreEl ? parseFloat(scoreEl.textContent.replace(/[^0-9.]/g, '')) : null;\n\n // Get assessment name and lesson context\n var assessmentName = extractAssessmentName(node);\n var lessonInfo = getCachedLessonInfo();\n\n // Find all question containers - Rise uses various structures\n var questions = node.querySelectorAll(\n '.question-result, .question-feedback, .question-item, ' +\n '.blocks-quiz__question, .quiz-question, ' +\n '[class*=\"question-\"][class*=\"result\"], ' +\n '[class*=\"question-\"][class*=\"feedback\"], ' +\n '[data-question-id]'\n );\n\n log('Found', questions.length, 'question elements in quiz result');\n\n if (questions.length > 0) {\n questions.forEach(function(q, index) {\n // Determine if correct - check multiple indicators\n var isCorrect = q.classList.contains('correct') ||\n q.classList.contains('is-correct') ||\n q.querySelector('.correct, .is-correct, [class*=\"correct\"]') !== null ||\n q.getAttribute('data-correct') === 'true' ||\n q.getAttribute('data-result') === 'correct';\n\n // Get the question text - try multiple selectors\n var questionText = '';\n var questionEl = q.querySelector(\n '.question-text, .question-stem, .question-title, ' +\n '.blocks-quiz__question-text, .quiz-question__text, ' +\n '[class*=\"question-text\"], [class*=\"question-stem\"], ' +\n '[class*=\"prompt\"], p:first-of-type'\n );\n if (questionEl) {\n questionText = questionEl.textContent.trim();\n }\n\n // Get the learner's selected answer text\n var answerText = '';\n var answerEl = q.querySelector(\n '.selected-answer, .learner-response, .answer-text, ' +\n '.user-answer, .chosen-answer, .response-text, ' +\n '.blocks-quiz__response, .quiz-question__response, ' +\n '[class*=\"selected\"], [class*=\"chosen\"], [class*=\"response\"], ' +\n '[class*=\"user-answer\"]'\n );\n if (answerEl) {\n answerText = answerEl.textContent.trim();\n }\n\n // If no specific answer element, try to find selected radio/checkbox label\n if (!answerText) {\n var selectedInput = q.querySelector('input:checked, [aria-checked=\"true\"]');\n if (selectedInput) {\n var label = q.querySelector('label[for=\"' + selectedInput.id + '\"]');\n if (label) {\n answerText = label.textContent.trim();\n } else {\n var parentLabel = selectedInput.closest('label');\n if (parentLabel) {\n answerText = parentLabel.textContent.trim();\n }\n }\n }\n }\n\n // Get question ID if available\n var questionId = q.getAttribute('data-question-id') ||\n q.getAttribute('data-block-id') ||\n q.getAttribute('id') ||\n 'q' + (index + 1);\n\n // Get correct answer text if available (for reporting)\n var correctAnswerText = '';\n var correctEl = q.querySelector(\n '.correct-answer, .right-answer, ' +\n '[class*=\"correct-answer\"], [class*=\"right-answer\"]'\n );\n if (correctEl) {\n correctAnswerText = correctEl.textContent.trim();\n }\n\n log('Question', index + 1, ':', {\n questionText: questionText.substring(0, 50) + '...',\n answerText: answerText.substring(0, 50) + '...',\n isCorrect: isCorrect\n });\n\n LRS.questionAnswered({\n questionId: questionId,\n questionNumber: index + 1,\n questionText: questionText.substring(0, 500),\n answer: answerText.substring(0, 500),\n correctAnswer: correctAnswerText.substring(0, 500),\n result: isCorrect ? 'correct' : 'incorrect',\n assessmentName: assessmentName,\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName\n });\n });\n }\n\n LRS.assessmentEnded({\n assessmentId: extractAssessmentId(node),\n assessmentName: assessmentName,\n score: score,\n questionCount: questions.length,\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName\n });\n }\n\n // Track submitted Knowledge Check questions to avoid duplicates\n var submittedKnowledgeChecks = {};\n var kcQuestionCounter = 0;\n\n /**\n * Set up interceptors specifically for Rise Knowledge Check blocks\n * These blocks have a different DOM structure than standard quiz results\n */\n function setupKnowledgeCheckInterceptors() {\n if (!TRACK_QUIZZES) return;\n\n // Intercept submit button clicks on Knowledge Check blocks\n // NOTE: If the SCORM tracker is active, it handles quiz tracking via\n // cmi.interactions — skip DOM scraping to avoid duplicate statements\n document.addEventListener('click', function(e) {\n if (scormTrackerActive) return; // SCORM tracker handles this\n\n var submitBtn = e.target.closest('.quiz-card__button');\n if (!submitBtn) return;\n\n // Find the Knowledge Check block\n var kcBlock = submitBtn.closest('[data-test-id=\"block-kc-card\"]') ||\n submitBtn.closest('.block-knowledge');\n if (!kcBlock) return;\n\n // Wait for feedback to appear after submission\n setTimeout(function() {\n extractKnowledgeCheckResult(kcBlock);\n }, 500);\n }, true);\n\n log('Knowledge Check interceptors set up');\n }\n\n /**\n * Extract and send xAPI statement for a Knowledge Check submission\n */\n function extractKnowledgeCheckResult(kcBlock) {\n // Get block ID for context\n var blockContainer = kcBlock.closest('[data-block-id]');\n var blockId = blockContainer ? blockContainer.getAttribute('data-block-id') : null;\n\n // Get question text first — needed for per-question dedup key\n var questionText = '';\n var questionTextEl = kcBlock.querySelector('.quiz-card__title .fr-view, .quiz-card__title');\n if (questionTextEl) {\n questionText = questionTextEl.textContent.trim();\n }\n\n // Get question ID from the title element\n var questionTitleEl = kcBlock.querySelector('.quiz-card__title');\n var questionId = questionTitleEl ? questionTitleEl.id : null;\n\n // Build a question-specific dedup key using question text hash\n // This ensures each question in a multi-question quiz block gets its own key\n var questionHash = questionText ? questionText.substring(0, 100) : (questionId || blockId || generateUUID());\n var submissionKey = 'kc-' + questionHash;\n\n var feedbackLabel = kcBlock.querySelector('.quiz-card__feedback-label');\n if (!feedbackLabel) {\n log('Knowledge Check: No feedback visible yet');\n return;\n }\n\n var feedbackText = feedbackLabel.textContent.trim().toLowerCase();\n var submissionId = submissionKey + '-' + feedbackText;\n\n if (submittedKnowledgeChecks[submissionId]) {\n log('Knowledge Check: Already processed this submission');\n return;\n }\n submittedKnowledgeChecks[submissionId] = true;\n\n // Use question-specific ID for the statement (not the shared block ID)\n if (!questionId) {\n questionId = blockId ? 'q-' + blockId + '-' + questionHash.substring(0, 20) : 'q-' + generateUUID();\n }\n\n // Determine question type from aria-label\n var wrapper = kcBlock.querySelector('[data-test-id=\"block-knowledge-wrapper\"]');\n var ariaLabel = wrapper ? wrapper.getAttribute('aria-label') : '';\n var questionType = 'unknown';\n if (ariaLabel.indexOf('Multiple choice') > -1) questionType = 'multiple-choice';\n else if (ariaLabel.indexOf('Multiple response') > -1) questionType = 'multiple-response';\n else if (ariaLabel.indexOf('Fill in the blank') > -1) questionType = 'fill-in-blank';\n else if (ariaLabel.indexOf('Matching') > -1) questionType = 'matching';\n\n // Get selected answer(s) based on question type\n var answerText = extractKnowledgeCheckAnswer(kcBlock, questionType);\n\n // Get correct/incorrect from feedback\n var isCorrect = feedbackText === 'correct';\n\n // Get lesson context\n var lessonInfo = getCachedLessonInfo();\n\n log('Knowledge Check submitted:', {\n questionId: questionId,\n questionText: questionText.substring(0, 50) + '...',\n questionType: questionType,\n answer: answerText.substring(0, 50) + '...',\n correct: isCorrect\n });\n\n // Increment question counter for this session\n kcQuestionCounter++;\n\n // Send question answered statement using existing LRS method\n LRS.questionAnswered({\n questionId: questionId,\n questionGuid: blockId || generateUUID(),\n questionNumber: kcQuestionCounter,\n questionText: questionText.substring(0, 500),\n questionType: questionType,\n answer: answerText.substring(0, 500),\n correct: isCorrect,\n result: isCorrect ? 'correct' : 'incorrect',\n assessmentName: 'Knowledge Check',\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName\n });\n }\n\n /**\n * Extract the selected answer text from a Knowledge Check block\n * based on the question type\n */\n function extractKnowledgeCheckAnswer(kcBlock, questionType) {\n var answerText = '';\n\n if (questionType === 'multiple-choice') {\n // Find checked radio button\n var checkedInput = kcBlock.querySelector('.quiz-multiple-choice-option__input:checked');\n if (checkedInput) {\n var label = checkedInput.closest('.quiz-multiple-choice-option');\n var textEl = label ? label.querySelector('.quiz-multiple-choice-option__label .fr-view, .quiz-multiple-choice-option__label') : null;\n answerText = textEl ? textEl.textContent.trim() : '';\n }\n }\n else if (questionType === 'multiple-response') {\n // Find all checked checkboxes\n var checkedInputs = kcBlock.querySelectorAll('.quiz-multiple-response-option__input:checked');\n var answers = [];\n checkedInputs.forEach(function(input) {\n var label = input.closest('.quiz-multiple-response-option');\n var textEl = label ? label.querySelector('.quiz-multiple-response-option__text .fr-view, .quiz-multiple-response-option__text') : null;\n if (textEl) answers.push(textEl.textContent.trim());\n });\n answerText = answers.join('; ');\n }\n else if (questionType === 'fill-in-blank') {\n // Get text input value\n var textInput = kcBlock.querySelector('.quiz-fill__input');\n answerText = textInput ? textInput.value.trim() : '';\n }\n else if (questionType === 'matching') {\n // Extract matching pairs from the drop zones\n var dropZones = kcBlock.querySelectorAll('.matching-drop-zone');\n var pairs = [];\n dropZones.forEach(function(zone) {\n var prompt = zone.querySelector('.matching-prompt-content');\n var response = zone.querySelector('.matching-interaction-piece-content');\n if (prompt && response) {\n pairs.push(prompt.textContent.trim() + ' → ' + response.textContent.trim());\n }\n });\n answerText = pairs.length > 0 ? pairs.join('; ') : 'Matching submitted';\n }\n\n return answerText;\n }\n\n // ========================================================================\n // SCORM INTERACTION TRACKER\n // Intercepts cmi.interactions.N.* SetValue calls to capture quiz answers\n // directly from the SCORM data model — works regardless of Rise UI format\n // (Knowledge Check blocks, quiz lessons, etc.)\n //\n // IMPORTANT: This finds and wraps the ACTUAL SCORM API that Rise uses\n // (window.API, parent.API, or global functions), NOT the bridge's copy.\n // It runs unconditionally at init, not gated by actor resolution.\n // ========================================================================\n var scormInteractions = {}; // Pending interactions keyed by index N\n var scormInteractionsSent = {}; // Track which interactions were already sent\n var scormTrackerActive = false; // Set true when SCORM tracker wraps SetValue — KC handler defers\n var scormCourseData = {}; // Accumulates course-level SCORM data (score, status)\n var scormCompletionDebounce = null; // Debounce timer — prevents duplicate fires within same completion event\n\n function interceptScormSetValue(key, value) {\n if (typeof key !== 'string') return;\n\n // ---- Course-level SCORM data (score, status, completion) ----\n // SCORM 1.2\n if (key === 'cmi.core.score.raw') { scormCourseData.scoreRaw = parseFloat(value); }\n if (key === 'cmi.core.score.max') { scormCourseData.scoreMax = parseFloat(value); }\n if (key === 'cmi.core.score.min') { scormCourseData.scoreMin = parseFloat(value); }\n if (key === 'cmi.core.lesson_status') { scormCourseData.status = String(value).toLowerCase(); }\n // SCORM 2004\n if (key === 'cmi.score.raw') { scormCourseData.scoreRaw = parseFloat(value); }\n if (key === 'cmi.score.max') { scormCourseData.scoreMax = parseFloat(value); }\n if (key === 'cmi.score.min') { scormCourseData.scoreMin = parseFloat(value); }\n if (key === 'cmi.score.scaled') { scormCourseData.scoreScaled = parseFloat(value); }\n if (key === 'cmi.completion_status') { scormCourseData.completionStatus = String(value).toLowerCase(); }\n if (key === 'cmi.success_status') { scormCourseData.successStatus = String(value).toLowerCase(); }\n\n // Fire course completion statement when status indicates pass/fail/complete\n // Uses debounce (not one-time flag) so retries/new attempts are captured\n if (key === 'cmi.core.lesson_status' || key === 'cmi.success_status' || key === 'cmi.completion_status') {\n var status = String(value).toLowerCase();\n if (status === 'passed' || status === 'failed' || status === 'completed') {\n if (scormCompletionDebounce) clearTimeout(scormCompletionDebounce);\n scormCompletionDebounce = setTimeout(function() {\n scormCompletionDebounce = null;\n sendCourseCompletionStatement(status);\n }, 500);\n }\n }\n\n // ---- Interaction-level tracking (existing) ----\n var interactionPattern = /^cmi\\\\.interactions\\\\.(\\\\d+)\\\\.(.+)$/;\n var match = key.match(interactionPattern);\n if (!match) return;\n\n var idx = match[1];\n var field = match[2];\n\n // Initialize interaction tracking for this index\n if (!scormInteractions[idx]) {\n scormInteractions[idx] = {};\n }\n\n // Store the field value\n scormInteractions[idx][field] = String(value);\n\n log('SCORM Interaction [' + idx + '].' + field + ' = ' + String(value).substring(0, 100));\n\n // When 'result' is set, the interaction is complete — fire xAPI statement\n if (field === 'result' && !scormInteractionsSent[idx]) {\n scormInteractionsSent[idx] = true;\n var interaction = scormInteractions[idx];\n\n // Map SCORM interaction type to readable type\n var typeMap = {\n 'choice': 'multiple-choice',\n 'true-false': 'true-false',\n 'fill-in': 'fill-in-blank',\n 'matching': 'matching',\n 'performance': 'performance',\n 'sequencing': 'sequencing',\n 'likert': 'likert',\n 'numeric': 'numeric'\n };\n\n var scormType = interaction.type || 'unknown';\n var questionType = typeMap[scormType] || scormType;\n\n // Determine correctness from SCORM result value\n // SCORM 1.2: 'correct', 'wrong', 'unanticipated', 'neutral'\n // SCORM 2004: 'correct', 'incorrect', 'unanticipated', 'neutral'\n var isCorrect = String(value) === 'correct';\n\n // Get student response\n var studentResponse = interaction.student_response || '';\n\n // Get correct response pattern\n var correctResponse = '';\n Object.keys(interaction).forEach(function(k) {\n if (k.indexOf('correct_responses') > -1 && k.indexOf('pattern') > -1) {\n correctResponse = interaction[k];\n }\n });\n\n // Rise interaction IDs encode the question text (underscored)\n // e.g. \"Development_Week_Qui_I_want_a_clear_picture_of_...\"\n var interactionId = interaction.id || ('interaction-' + idx);\n\n // Try to make a readable question text from the interaction ID\n var questionText = decodeEntities(\n interactionId\n .replace(/_\\\\d+$/, '') // remove trailing _0\n .replace(/_/g, ' ') // underscores to spaces\n .substring(0, 200)\n );\n\n // Get lesson context\n var lessonInfo = getCachedLessonInfo();\n\n // Increment question counter\n kcQuestionCounter++;\n\n log('SCORM Interaction complete [' + idx + ']:', {\n questionNumber: kcQuestionCounter,\n questionText: questionText.substring(0, 60) + '...',\n type: questionType,\n studentResponse: studentResponse,\n correctResponse: correctResponse,\n result: String(value),\n correct: isCorrect\n });\n\n // Send xAPI answered statement\n LRS.questionAnswered({\n questionId: interactionId,\n questionGuid: interactionId,\n questionNumber: kcQuestionCounter,\n questionText: questionText,\n questionType: questionType,\n answer: studentResponse,\n correctAnswer: correctResponse,\n correct: isCorrect,\n result: isCorrect ? 'correct' : 'incorrect',\n assessmentName: lessonInfo.name || 'Quiz',\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName\n });\n }\n }\n\n function sendCourseCompletionStatement(status) {\n logGroup('Course Completion');\n log('Status:', status);\n log('SCORM course data:', scormCourseData);\n\n var courseTitle = (LRS.courseInfo && LRS.courseInfo.title) ? decodeEntities(LRS.courseInfo.title) : decodeEntities(document.title) || 'Course';\n\n // Build result with score\n var result = { completion: true };\n\n var raw = scormCourseData.scoreRaw;\n var max = scormCourseData.scoreMax || 100;\n var min = scormCourseData.scoreMin || 0;\n\n if (typeof raw === 'number' && !isNaN(raw)) {\n result.score = {\n raw: raw,\n max: max,\n min: min,\n scaled: scormCourseData.scoreScaled != null ? scormCourseData.scoreScaled : (max > min ? (raw - min) / (max - min) : 0)\n };\n }\n\n result.success = (status === 'passed');\n\n var activityDetails = {\n courseTitle: courseTitle,\n completionStatus: status,\n employeeEmail: LRS.actor && LRS.actor.mbox ? LRS.actor.mbox.replace('mailto:', '') : undefined,\n employeeId: LRS.employeeId || undefined\n };\n\n // 1. Send \"completed\" statement (course was finished)\n log('Sending completed statement, score:', result.score, 'title:', courseTitle);\n var completedStatement = buildStatement('completed', ACTIVITY_TYPES.course, activityDetails, result, null);\n sendStatement(completedStatement);\n\n // 2. Send \"passed\" or \"failed\" statement (assessment outcome)\n if (status === 'passed' || status === 'failed') {\n log('Sending', status, 'statement');\n var outcomeStatement = buildStatement(status, ACTIVITY_TYPES.course, activityDetails, result, null);\n sendStatement(outcomeStatement);\n }\n\n logGroupEnd();\n }\n\n function setupScormInteractionTracker() {\n if (!TRACK_QUIZZES) return;\n\n // Find the ACTUAL SCORM API that Rise uses — NOT our bridge's copy.\n // Rise discovers the API via standard SCORM lookup (window.API, parent chain).\n // The Bravais CDS player's ProxyApi.injectLmsApi() sets this up.\n var wrapped = false;\n\n // Try 1: window.API (SCORM 1.2) or window.API_1484_11 (SCORM 2004)\n try {\n if (window.API && typeof window.API.LMSSetValue === 'function') {\n var origSetValue = window.API.LMSSetValue;\n window.API.LMSSetValue = function(key, value) {\n var result = origSetValue.apply(window.API, arguments);\n interceptScormSetValue(key, value);\n return result;\n };\n wrapped = true;\n log('SCORM Interaction Tracker: Wrapped window.API.LMSSetValue');\n }\n } catch (e) { log('SCORM Tracker: Cannot access window.API:', e.message); }\n\n if (!wrapped) {\n try {\n if (window.API_1484_11 && typeof window.API_1484_11.SetValue === 'function') {\n var origSetValue2004 = window.API_1484_11.SetValue;\n window.API_1484_11.SetValue = function(key, value) {\n var result = origSetValue2004.apply(window.API_1484_11, arguments);\n interceptScormSetValue(key, value);\n return result;\n };\n wrapped = true;\n log('SCORM Interaction Tracker: Wrapped window.API_1484_11.SetValue');\n }\n } catch (e) { log('SCORM Tracker: Cannot access window.API_1484_11:', e.message); }\n }\n\n // Try 2: Parent frame API\n if (!wrapped) {\n try {\n if (window.parent && window.parent !== window) {\n if (window.parent.API && typeof window.parent.API.LMSSetValue === 'function') {\n var origParentSetValue = window.parent.API.LMSSetValue;\n window.parent.API.LMSSetValue = function(key, value) {\n var result = origParentSetValue.apply(window.parent.API, arguments);\n interceptScormSetValue(key, value);\n return result;\n };\n wrapped = true;\n log('SCORM Interaction Tracker: Wrapped window.parent.API.LMSSetValue');\n }\n }\n } catch (e) { log('SCORM Tracker: Cannot access parent API (cross-origin)'); }\n }\n\n // Try 3: Global LMS functions (Bravais/Xyleme mock API)\n if (!wrapped) {\n try {\n if (typeof window.LMSSetValue === 'function') {\n var origGlobalSetValue = window.LMSSetValue;\n window.LMSSetValue = function(key, value) {\n var result = origGlobalSetValue.apply(window, arguments);\n interceptScormSetValue(key, value);\n return result;\n };\n wrapped = true;\n log('SCORM Interaction Tracker: Wrapped window.LMSSetValue (global)');\n }\n } catch (e) { log('SCORM Tracker: Cannot wrap global LMSSetValue:', e.message); }\n }\n\n // Try 4: Fall back to bridge's copy (least likely to work but worth trying)\n if (!wrapped && LRS.scormApi) {\n var setValueFn = LRS.scormApiType === '2004' ? 'SetValue' : 'LMSSetValue';\n if (typeof LRS.scormApi[setValueFn] === 'function') {\n var origBridgeSetValue = LRS.scormApi[setValueFn];\n LRS.scormApi[setValueFn] = function(key, value) {\n var result = origBridgeSetValue.apply(LRS.scormApi, arguments);\n interceptScormSetValue(key, value);\n return result;\n };\n wrapped = true;\n log('SCORM Interaction Tracker: Wrapped LRS.scormApi.' + setValueFn + ' (bridge copy)');\n }\n }\n\n if (wrapped) {\n scormTrackerActive = true;\n log('SCORM Interaction Tracker active — KC DOM handler will defer');\n } else {\n // API not available yet — retry in 2 seconds (Bravais proxy may not be ready)\n log('SCORM Interaction Tracker: No SCORM API found yet, retrying in 2s...');\n setTimeout(function() {\n if (!scormTrackerActive) {\n setupScormInteractionTracker();\n }\n }, 2000);\n }\n }\n\n function setupInteractionInterceptors() {\n if (!TRACK_INTERACTIONS) return;\n\n document.addEventListener('click', function(e) {\n var target = e.target;\n\n // Tabs\n var tabTrigger = findClosest(target, '[data-block-type=\"tabs\"] .tab-item, .tabs__tab, .tab-trigger, [role=\"tab\"]');\n if (tabTrigger) {\n LRS.interacted({\n type: 'tab',\n id: tabTrigger.getAttribute('data-tab-id') ||\n tabTrigger.getAttribute('aria-controls') ||\n tabTrigger.textContent.trim().substring(0, 50),\n name: 'Tab: ' + (tabTrigger.textContent.trim().substring(0, 30) || 'Tab')\n });\n return;\n }\n\n // Accordions\n var accordionTrigger = findClosest(target, '[data-block-type=\"accordion\"] .accordion-trigger, .accordion__header, .accordion-item__header, [aria-expanded]');\n if (accordionTrigger && accordionTrigger.getAttribute('aria-expanded')) {\n LRS.interacted({\n type: 'accordion',\n id: accordionTrigger.getAttribute('data-accordion-id') ||\n accordionTrigger.getAttribute('aria-controls') ||\n accordionTrigger.textContent.trim().substring(0, 50),\n name: 'Accordion: ' + (accordionTrigger.textContent.trim().substring(0, 30) || 'Section')\n });\n return;\n }\n\n // Flashcards\n var flashcard = findClosest(target, '.flashcard, [data-block-type=\"flashcard\"], .blocks-flashcard');\n if (flashcard) {\n LRS.interacted({\n type: 'flashcard',\n id: flashcard.getAttribute('data-card-id') || 'flashcard-' + Date.now(),\n name: 'Flashcard'\n });\n return;\n }\n\n // Process/Timeline\n var processItem = findClosest(target, '.process-item, .timeline-item, [data-block-type=\"process\"] .step, .blocks-process__item');\n if (processItem) {\n LRS.interacted({\n type: 'process-step',\n id: processItem.getAttribute('data-step-id') ||\n processItem.getAttribute('data-index') ||\n 'step-' + Date.now(),\n name: 'Process Step'\n });\n return;\n }\n\n // Hotspots/Markers\n var hotspot = findClosest(target, '.hotspot-marker, .marker-item, [data-block-type=\"labeled-graphic\"] .marker, .blocks-labeled-graphic__marker');\n if (hotspot) {\n LRS.interacted({\n type: 'hotspot',\n id: hotspot.getAttribute('data-marker-id') ||\n hotspot.getAttribute('data-index') ||\n 'hotspot-' + Date.now(),\n name: 'Hotspot'\n });\n return;\n }\n\n // Sorting activities\n var sortItem = findClosest(target, '.sort-item, [data-block-type=\"sorting\"] .item, .blocks-sorting__item');\n if (sortItem) {\n LRS.interacted({\n type: 'sorting',\n id: sortItem.getAttribute('data-item-id') || 'sort-item',\n name: 'Sorting Activity'\n });\n return;\n }\n\n // Buttons / Continue\n var button = findClosest(target, '.continue-button, .nav-button, [data-block-type=\"button\"], .blocks-button');\n if (button) {\n LRS.interacted({\n type: 'button',\n id: button.getAttribute('data-button-id') || 'button',\n name: 'Button: ' + (button.textContent.trim().substring(0, 30) || 'Continue')\n });\n }\n\n }, true);\n\n log('Interaction interceptors set up');\n }\n\n function findClosest(el, selector) {\n if (!el || !selector) return null;\n if (el.closest) {\n return el.closest(selector);\n }\n var current = el;\n while (current && current !== document) {\n if (matchesSelector(current, selector)) {\n return current;\n }\n current = current.parentElement;\n }\n return null;\n }\n\n function matchesSelector(el, selector) {\n var fn = el.matches || el.webkitMatchesSelector || el.msMatchesSelector;\n return fn ? fn.call(el, selector) : false;\n }\n\n // ========================================================================\n // 9. INITIALIZATION\n // ========================================================================\n\n function init() {\n log('Initializing LRS bridge v2.6.0...');\n\n // Extract course info early\n extractCourseInfo();\n\n // Setup connection\n var bridgeReady = setupBridge();\n\n // Track async readiness: both actor and doc metadata must resolve before sending statements\n var actorReady = false;\n var docReady = false;\n var launchEventsSent = false;\n\n function tryLaunchEvents() {\n if (launchEventsSent) return;\n if (!actorReady || !docReady) return;\n launchEventsSent = true;\n log('Both actor and doc metadata ready, sending launch events');\n setTimeout(sendLaunchEvents, 50);\n }\n\n if (bridgeReady) {\n LRS.initialized = true;\n\n // Extract actor (sync first for immediate use)\n extractActor();\n\n // Check localStorage for actor set by another frame (e.g., skin overlay in a different HTML file)\n if (isAnonymousActor(LRS.actor)) {\n var sharedActor = loadSharedActor();\n if (sharedActor) {\n LRS.actor = sharedActor;\n log('Loaded actor from cross-frame storage at init:', sharedActor.name);\n }\n }\n\n log('Bridge initialized in mode:', LRS.mode);\n log('Actor:', LRS.actor);\n log('Course:', LRS.courseInfo);\n\n // Then try async actor extraction (employee lookup, Bravais session)\n // Launch events will NOT be sent until this completes\n extractActorAsync(function(actor) {\n log('Actor updated after async fetch:', actor);\n\n // Check if actor is still \"Unknown Learner\" - SCORM API might not be available yet\n // The SCORM API is often injected by scorm-calls.js AFTER <head> init completes\n var isUnknown = !actor || actor.name === 'Unknown Learner' ||\n !actor.account || actor.account.name === 'unknown';\n\n if (isUnknown) {\n log('Actor is Unknown Learner - SCORM API likely not available yet, polling for it...');\n var scormPollCount = 0;\n var scormMaxPolls = 30; // wait up to 30 seconds\n var scormResolved = false;\n\n // Helper: search all possible locations for SCORM API\n function findScormApiAnywhere() {\n // 1. Search parent frame hierarchy (standard approach)\n var api2004 = findAPIInFrameHierarchy('API_1484_11', 10);\n if (api2004) return { api: api2004.api, type: '2004', source: 'parent-chain level ' + api2004.level };\n var api12 = findAPIInFrameHierarchy('API', 10);\n if (api12) return { api: api12.api, type: '1.2', source: 'parent-chain level ' + api12.level };\n\n // 2. Check window.opener and its parent hierarchy (LMS opens content in popup)\n try {\n if (window.opener && window.opener !== window) {\n // Direct check on opener\n if (window.opener.API_1484_11) return { api: window.opener.API_1484_11, type: '2004', source: 'window.opener' };\n if (window.opener.API) return { api: window.opener.API, type: '1.2', source: 'window.opener' };\n // Check opener's parent chain\n var openerWin = window.opener;\n var openerLevel = 0;\n while (openerLevel < 10) {\n try {\n if (openerWin.API_1484_11) return { api: openerWin.API_1484_11, type: '2004', source: 'opener-chain level ' + openerLevel };\n if (openerWin.API) return { api: openerWin.API, type: '1.2', source: 'opener-chain level ' + openerLevel };\n if (openerWin.parent && openerWin.parent !== openerWin) { openerWin = openerWin.parent; openerLevel++; }\n else break;\n } catch(e) { break; }\n }\n }\n } catch(e) { /* cross-origin opener */ }\n\n // 3. Check for global LMS functions (Bravais/Xyleme mock API)\n try {\n if (typeof window.LMSInitialize === 'function') {\n return { api: { LMSInitialize: window.LMSInitialize, LMSGetValue: window.LMSGetValue, LMSSetValue: window.LMSSetValue, LMSCommit: window.LMSCommit, LMSFinish: window.LMSFinish, LMSGetLastError: window.LMSGetLastError, LMSGetErrorString: window.LMSGetErrorString }, type: '1.2', source: 'global-functions' };\n }\n } catch(e) {}\n try {\n if (window.parent && window.parent !== window && typeof window.parent.LMSInitialize === 'function') {\n return { api: { LMSInitialize: window.parent.LMSInitialize, LMSGetValue: window.parent.LMSGetValue, LMSSetValue: window.parent.LMSSetValue, LMSCommit: window.parent.LMSCommit, LMSFinish: window.parent.LMSFinish, LMSGetLastError: window.parent.LMSGetLastError, LMSGetErrorString: window.parent.LMSGetErrorString }, type: '1.2', source: 'parent-global-functions' };\n }\n } catch(e) {}\n\n return null;\n }\n\n function onScormApiFound(result, pollNum) {\n if (scormResolved) return;\n scormResolved = true;\n\n LRS.scormApi = result.api;\n LRS.scormApiFound = true;\n LRS.scormApiType = result.type;\n\n // Read learner_id for diagnostics\n var scormLearnerId = 'n/a', scormLearnerName = 'n/a';\n try {\n if (result.type === '2004') {\n scormLearnerId = result.api.GetValue('cmi.learner_id');\n scormLearnerName = result.api.GetValue('cmi.learner_name');\n } else {\n scormLearnerId = result.api.LMSGetValue('cmi.core.student_id');\n scormLearnerName = result.api.LMSGetValue('cmi.core.student_name');\n }\n } catch(e) { scormLearnerId = 'error'; scormLearnerName = 'error'; }\n console.info('[PA-LRS] SCORM API found (poll ' + pollNum + ', source=' + result.source +\n ', type=' + result.type + '): learner_id=' + scormLearnerId + ', learner_name=' + scormLearnerName);\n\n // refreshActor re-reads learner_id from SCORM and runs employee lookup\n LRS.refreshActor(function(refreshedActor) {\n if (window.console && window.console.info) {\n console.info('[PA-LRS] Actor resolved (after SCORM wait, poll ' + pollNum + '): name=' +\n (refreshedActor ? refreshedActor.name : 'none') +\n ', mbox=' + (refreshedActor ? refreshedActor.mbox : 'none') +\n ', account=' + (refreshedActor && refreshedActor.account ? refreshedActor.account.name : 'none'));\n }\n actorReady = true;\n tryLaunchEvents();\n });\n }\n\n var scormPollTimer = setInterval(function() {\n if (scormResolved) { clearInterval(scormPollTimer); return; }\n scormPollCount++;\n\n var result = findScormApiAnywhere();\n\n if (result) {\n clearInterval(scormPollTimer);\n onScormApiFound(result, scormPollCount);\n } else if (scormPollCount >= scormMaxPolls) {\n clearInterval(scormPollTimer);\n log('SCORM API not found after ' + scormMaxPolls + 's, proceeding with Unknown Learner');\n if (window.console && window.console.info) {\n console.info('[PA-LRS] Actor resolved (timeout ' + scormMaxPolls + 's): name=' + (actor ? actor.name : 'none') +\n ', account=' + (actor && actor.account ? actor.account.name : 'none'));\n }\n actorReady = true;\n tryLaunchEvents();\n }\n }, 1000);\n } else {\n // Actor is valid - proceed immediately\n if (window.console && window.console.info) {\n console.info('[PA-LRS] Actor resolved: name=' + (actor ? actor.name : 'none') +\n ', mbox=' + (actor ? actor.mbox : 'none') +\n ', account=' + (actor && actor.account ? actor.account.name : 'none'));\n }\n actorReady = true;\n tryLaunchEvents();\n }\n });\n } else {\n warn('Bridge setup failed - operating in offline mode');\n LRS.mode = 'offline';\n actorReady = true; // No actor to wait for in offline mode\n }\n\n // Always-visible bridge summary (not gated by DEBUG)\n // This ensures diagnostics are available even in production builds\n // Shows current GUID (may be from Cloud Player, baked-in, or pending API override)\n var currentGuid = LRS.courseInfo ? LRS.courseInfo.guid : DOCUMENT_GUID;\n var currentVerGuid = LRS.courseInfo ? LRS.courseInfo.versionGuid : VERSION_GUID;\n var sharedToken = LRS.courseInfo ? LRS.courseInfo.sharedLinkToken : null;\n if (window.console && window.console.info) {\n console.info('[PA-LRS] Bridge v' + LRS.version + ': mode=' + LRS.mode +\n ', endpoint=' + (LRS_ENDPOINT ? LRS_ENDPOINT.substring(0, 30) + '...' : 'NONE') +\n ', proxy=' + (LRS_PROXY_ENDPOINT ? 'yes' : 'no') +\n ', docGuid=' + (currentGuid || 'NONE') +\n (currentGuid !== DOCUMENT_GUID ? ' (overridden from baked: ' + DOCUMENT_GUID + ')' : '') +\n ', verGuid=' + (currentVerGuid || 'NONE') +\n ', sharedToken=' + (sharedToken || 'NONE') +\n ', actor=' + (LRS.actor ? (LRS.actor.name || 'anonymous') : 'none') +\n ', hasRefreshActor=' + (typeof LRS.refreshActor === 'function'));\n }\n\n // Set up event interceptors\n setupMediaInterceptors();\n setupNavigationInterceptors();\n setupQuizInterceptors();\n setupInteractionInterceptors();\n\n // Intercept SCORM cmi.interactions to capture quiz answers as xAPI statements\n // This wraps the ACTUAL SCORM API (window.API etc.) that Rise calls\n setupScormInteractionTracker();\n\n // Fetch document metadata from API to get GUIDs (async)\n // Then send course launched event\n var sharedLinkToken = LRS.courseInfo ? LRS.courseInfo.sharedLinkToken : null;\n var documentId = LRS.courseInfo ? LRS.courseInfo.documentId : null;\n\n function sendLaunchEvents() {\n // Log the course info state before sending statements\n log('Sending launch events with course info:', {\n id: LRS.courseInfo ? LRS.courseInfo.id : null,\n guid: LRS.courseInfo ? LRS.courseInfo.guid : null,\n versionGuid: LRS.courseInfo ? LRS.courseInfo.versionGuid : null,\n documentId: LRS.courseInfo ? LRS.courseInfo.documentId : null\n });\n\n if (TRACK_NAVIGATION) {\n LRS.courseLaunched();\n LRS.contentOpened({\n lessonId: window.location.hash || '#/',\n url: window.location.href,\n action: 'launched'\n });\n }\n }\n\n function onDocReady() {\n docReady = true;\n tryLaunchEvents();\n }\n\n // After Bravais API metadata resolves, try our authenticated server as a\n // final fallback to get the document name for packageName.\n // This handles cases where PARAMS is cross-origin blocked and no baked name exists.\n function maybeEnrichAndReady() {\n if (LRS.courseInfo.packageName || !LRS.courseInfo.documentId || !LRS_PROXY_ENDPOINT) {\n onDocReady();\n return;\n }\n log('No packageName yet, trying server-side document lookup for ID:', LRS.courseInfo.documentId);\n fetchDocumentNameFromServer(LRS.courseInfo.documentId, function(name) {\n if (name) {\n LRS.courseInfo.packageName = name;\n log('Set packageName from server lookup:', name);\n }\n onDocReady();\n });\n }\n\n function fetchDocDataAndReady(docId) {\n fetchDocumentMetadata(docId, function(docData) {\n if (docData) {\n updateCourseInfoFromApi(docData);\n }\n maybeEnrichAndReady();\n });\n }\n\n // ALWAYS fetch document metadata from the Bravais API.\n // Even if we have a baked-in GUID, we must override it with the canonical\n // Bravais GUID so our statements link to the same document as cloudplayer.\n // The baked-in GUID is a random UUID from wrap time; the Bravais GUID is\n // assigned at upload and is what Analytics uses for aggregation.\n if (sharedLinkToken) {\n log('Have shared link token, fetching document data via shared API...');\n fetchDocumentMetadata(null, function(docData) {\n if (docData) {\n updateCourseInfoFromApi(docData);\n if (docData.name) {\n LRS.courseInfo.sharedLinkName = docData.name;\n }\n } else {\n warn('Could not fetch document data from shared API - statements may use baked-in GUID');\n }\n maybeEnrichAndReady();\n });\n } else if (documentId) {\n log('Fetching document data with ID:', documentId);\n fetchDocDataAndReady(documentId);\n } else {\n // No identifiers available\n warn('No shared link token or document ID - statements will use baked-in GUID');\n maybeEnrichAndReady();\n }\n\n // Setup beforeunload to send terminated\n window.addEventListener('beforeunload', function() {\n LRS.courseTerminated();\n });\n\n // Retry queue every 30 seconds\n setInterval(processStatementQueue, 30000);\n\n // Retry bridge connection if failed\n if (!LRS.initialized && LRS.mode !== 'directLRS') {\n var retryCount = 0;\n var maxRetries = 10;\n var retryInterval = setInterval(function() {\n retryCount++;\n if (setupBridge()) {\n LRS.initialized = true;\n extractActorAsync(function(actor) {\n log('Actor extracted on retry:', actor);\n });\n log('Bridge connected on retry ' + retryCount);\n clearInterval(retryInterval);\n } else if (retryCount >= maxRetries) {\n clearInterval(retryInterval);\n }\n }, 1000);\n }\n\n log('LRS bridge setup complete');\n }\n\n // Initialize\n init();\n\n })();\n`;\n}\n","import type { PatchAdamsConfig, LrsBridgeConfig } from '../config/schema.js';\nimport type { CourseMetadata } from '../fingerprint/course-metadata.js';\nimport { generateLrsBridgeCode, type LrsBridgeOptions } from './lrs-bridge.js';\n\nexport interface JsBeforeOptions {\n remoteUrl: string;\n localPath: string;\n htmlClass: string;\n loadingClass: string;\n /** Full course metadata */\n metadata: CourseMetadata | null;\n /** LRS Bridge configuration */\n lrsBridge: LrsBridgeOptions;\n /** Optional skin name */\n skin?: string;\n}\n\n/**\n * Escape a string for safe inclusion in JavaScript\n */\nfunction escapeJs(str: string | null | undefined): string {\n if (!str) return '';\n return str\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/'/g, \"\\\\'\")\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r');\n}\n\n/**\n * Generate the blocking JS loader for the \"before\" slot\n * This loads at the start of <head> and blocks rendering until loaded\n *\n * Use this for:\n * - Setting up global variables\n * - Intercepting Rise APIs before they initialize\n * - Preparing the environment\n * - LRS Bridge for xAPI/SCORM communication\n */\nexport function generateJsBeforeLoader(options: JsBeforeOptions): string {\n const { remoteUrl, localPath, htmlClass, loadingClass, metadata, lrsBridge, skin } = options;\n\n // Generate LRS bridge code\n const lrsBridgeCode = generateLrsBridgeCode(lrsBridge);\n\n // Build course object with all available metadata\n const courseLines: string[] = [];\n\n if (metadata) {\n courseLines.push(` courseId: '${escapeJs(metadata.courseId)}',`);\n courseLines.push(` courseIdSource: '${metadata.courseIdSource}',`);\n courseLines.push(` format: '${metadata.format}',`);\n\n if (metadata.title) {\n courseLines.push(` title: '${escapeJs(metadata.title)}',`);\n }\n if (metadata.description) {\n courseLines.push(` description: '${escapeJs(metadata.description)}',`);\n }\n if (metadata.organizationId) {\n courseLines.push(` organizationId: '${escapeJs(metadata.organizationId)}',`);\n }\n if (metadata.launchUrl) {\n courseLines.push(` launchUrl: '${escapeJs(metadata.launchUrl)}',`);\n }\n if (metadata.masteryScore !== null) {\n courseLines.push(` masteryScore: ${metadata.masteryScore},`);\n }\n if (metadata.duration) {\n courseLines.push(` duration: '${escapeJs(metadata.duration)}',`);\n }\n if (metadata.language) {\n courseLines.push(` language: '${escapeJs(metadata.language)}',`);\n }\n if (metadata.version) {\n courseLines.push(` version: '${escapeJs(metadata.version)}',`);\n }\n courseLines.push(` extractedAt: '${metadata.extractedAt}',`);\n }\n\n const courseBlock = courseLines.length > 0\n ? `\\n course: {\\n${courseLines.join('\\n')}\\n },`\n : '';\n\n return `<!-- === PATCH-ADAMS: JS BEFORE (blocking) === -->\n<script data-pa=\"js-before-loader\">\n// Initialize PA-Patcher global namespace IMMEDIATELY (before IIFE)\nwindow.pa_patcher = window.pa_patcher || {\n version: '1.0.25',\n htmlClass: '${htmlClass}',\n loadingClass: '${loadingClass}',${skin ? `\\n skin: '${escapeJs(skin)}',` : ''}${courseBlock}\n loaded: {\n cssBefore: false,\n cssAfter: false,\n jsBefore: false,\n jsAfter: false${skin ? ',\\n skinCss: false,\\n skinJs: false' : ''}\n }\n};\n\n// Filter out browser extension errors that can interfere with LMS error handling\n// These are not course errors - they come from extensions like SingleFile, LastPass, etc.\n(function() {\n var extensionPatterns = [\n 'extension',\n 'chrome-extension',\n 'moz-extension',\n 'safari-extension',\n 'message channel closed',\n 'listener indicated an asynchronous response',\n 'Receiving end does not exist',\n 'single-file',\n 'singlefile'\n ];\n\n function isExtensionError(error) {\n if (!error) return false;\n var msg = (error.message || error.reason || String(error)).toLowerCase();\n for (var i = 0; i < extensionPatterns.length; i++) {\n if (msg.indexOf(extensionPatterns[i]) !== -1) return true;\n }\n // Check stack trace for extension URLs\n var stack = (error.stack || '').toLowerCase();\n if (stack.indexOf('extension') !== -1 || stack.indexOf('moz-extension') !== -1) return true;\n return false;\n }\n\n // Handle unhandled promise rejections from extensions\n window.addEventListener('unhandledrejection', function(event) {\n if (isExtensionError(event.reason)) {\n event.preventDefault();\n event.stopImmediatePropagation();\n console.debug('[PA-Patcher] Suppressed browser extension error:', event.reason);\n return false;\n }\n }, true);\n\n // Handle regular errors from extensions\n window.addEventListener('error', function(event) {\n if (isExtensionError(event.error || event.message)) {\n event.preventDefault();\n event.stopImmediatePropagation();\n console.debug('[PA-Patcher] Suppressed browser extension error:', event.message);\n return false;\n }\n }, true);\n})();\n\n(function() {\n 'use strict';\n var REMOTE_URL = \"${remoteUrl}\";\n var LOCAL_PATH = \"${localPath}\";\n\n function loadJSSync(url) {\n var script = document.createElement('script');\n script.src = url;\n script.setAttribute('data-pa', 'js-before');\n // Note: For truly blocking behavior, we use document.write\n // but that's fragile. Instead we'll handle errors gracefully.\n document.head.appendChild(script);\n return script;\n }\n\n // Try remote first\n var script = loadJSSync(REMOTE_URL);\n\n script.onerror = function() {\n console.warn('[PA-Patcher] JS before failed to load from remote, using local fallback');\n document.head.removeChild(script);\n var fallback = loadJSSync(LOCAL_PATH);\n fallback.onload = function() {\n if (window.pa_patcher && window.pa_patcher.loaded) {\n window.pa_patcher.loaded.jsBefore = true;\n }\n console.log('[PA-Patcher] JS before loaded from local fallback:', LOCAL_PATH);\n };\n fallback.onerror = function() {\n console.error('[PA-Patcher] JS before failed to load from both remote and local');\n };\n };\n\n script.onload = function() {\n if (window.pa_patcher && window.pa_patcher.loaded) {\n window.pa_patcher.loaded.jsBefore = true;\n }\n console.log('[PA-Patcher] JS before loaded from remote:', REMOTE_URL);\n };\n})();\n${lrsBridgeCode}\n</script>`;\n}\n\n/**\n * Build options from config\n */\nexport function buildJsBeforeOptions(config: PatchAdamsConfig, metadata?: CourseMetadata | null): JsBeforeOptions {\n // Convert LrsBridgeConfig to LrsBridgeOptions with defaults for backwards compatibility\n // This handles configs from older versions that don't have lrsBridge\n const lrsBridgeConfig = config.lrsBridge ?? {};\n const lrsBridge: LrsBridgeOptions = {\n enabled: lrsBridgeConfig.enabled ?? true,\n trackMedia: lrsBridgeConfig.trackMedia ?? true,\n trackNavigation: lrsBridgeConfig.trackNavigation ?? true,\n trackQuizzes: lrsBridgeConfig.trackQuizzes ?? true,\n trackInteractions: lrsBridgeConfig.trackInteractions ?? true,\n customStatements: lrsBridgeConfig.customStatements ?? true,\n debug: lrsBridgeConfig.debug ?? false,\n lrsEndpoint: lrsBridgeConfig.lrsEndpoint,\n lrsWithCredentials: lrsBridgeConfig.lrsWithCredentials,\n courseHomepage: lrsBridgeConfig.courseHomepage,\n autoDetectLrs: lrsBridgeConfig.autoDetectLrs,\n lrsAuth: lrsBridgeConfig.lrsAuth,\n lrsProxyEndpoint: lrsBridgeConfig.lrsProxyEndpoint,\n employeeApiEndpoint: lrsBridgeConfig.employeeApiEndpoint,\n documentGuid: lrsBridgeConfig.documentGuid,\n versionGuid: lrsBridgeConfig.versionGuid,\n packageName: lrsBridgeConfig.packageName,\n };\n\n return {\n remoteUrl: `${config.remoteDomain}/${config.localFolders.js}/${config.jsBefore.filename}`,\n localPath: `${config.localFolders.js}/${config.jsBefore.filename}`,\n htmlClass: config.htmlClass,\n loadingClass: config.loadingClass,\n metadata: metadata ?? null,\n lrsBridge,\n skin: config.skin,\n };\n}\n","import type { PatchAdamsConfig } from '../config/schema.js';\n\nexport interface JsAfterOptions {\n remoteUrl: string;\n localPath: string;\n timeout: number;\n loadingClass: string;\n}\n\n/**\n * Generate the async JS loader for the \"after\" slot\n * This loads at the end of <body> with remote-first, local fallback\n *\n * This script is responsible for:\n * - Loading your override JS after Rise has initialized\n * - Removing the loading class to reveal the content\n */\nexport function generateJsAfterLoader(options: JsAfterOptions): string {\n const { remoteUrl, localPath, timeout, loadingClass } = options;\n\n return `<!-- === PATCH-ADAMS: JS AFTER (async with fallback) === -->\n<script data-pa=\"js-after-loader\">\n(function() {\n 'use strict';\n var REMOTE_URL = \"${remoteUrl}\";\n var LOCAL_PATH = \"${localPath}\";\n var TIMEOUT = ${timeout};\n var LOADING_CLASS = \"${loadingClass}\";\n\n function loadJS(url, onSuccess, onError) {\n var script = document.createElement('script');\n script.src = url;\n script.async = true;\n script.setAttribute('data-pa', 'js-after');\n\n script.onload = function() {\n if (onSuccess) onSuccess();\n };\n\n script.onerror = function() {\n if (onError) onError();\n };\n\n document.body.appendChild(script);\n return script;\n }\n\n function removeLoadingClass() {\n document.documentElement.classList.remove(LOADING_CLASS);\n if (window.pa_patcher && window.pa_patcher.loaded) {\n window.pa_patcher.loaded.jsAfter = true;\n }\n console.log('[PA-Patcher] Loading complete, content revealed');\n }\n\n function loadJSWithFallback() {\n var loaded = false;\n var timeoutId;\n\n // Try remote first\n var remoteScript = loadJS(\n REMOTE_URL,\n function() {\n if (loaded) return;\n loaded = true;\n clearTimeout(timeoutId);\n console.log('[PA-Patcher] JS after loaded from remote:', REMOTE_URL);\n // Give the script a moment to execute, then remove loading class\n setTimeout(removeLoadingClass, 50);\n },\n function() {\n if (loaded) return;\n loaded = true;\n clearTimeout(timeoutId);\n loadLocalFallback();\n }\n );\n\n // Timeout fallback\n timeoutId = setTimeout(function() {\n if (loaded) return;\n loaded = true;\n console.warn('[PA-Patcher] JS after timed out, using local fallback');\n if (remoteScript.parentNode) {\n document.body.removeChild(remoteScript);\n }\n loadLocalFallback();\n }, TIMEOUT);\n }\n\n function loadLocalFallback() {\n loadJS(\n LOCAL_PATH,\n function() {\n console.log('[PA-Patcher] JS after loaded from local fallback:', LOCAL_PATH);\n setTimeout(removeLoadingClass, 50);\n },\n function() {\n console.error('[PA-Patcher] JS after failed to load from both remote and local');\n // Still remove loading class so content is visible even if JS fails\n removeLoadingClass();\n }\n );\n }\n\n // Execute after DOM is ready to ensure Rise has loaded\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', function() {\n // Small delay to ensure Rise has initialized\n setTimeout(loadJSWithFallback, 100);\n });\n } else {\n // DOM is already ready\n setTimeout(loadJSWithFallback, 100);\n }\n})();\n</script>`;\n}\n\n/**\n * Build options from config\n */\nexport function buildJsAfterOptions(config: PatchAdamsConfig): JsAfterOptions {\n return {\n remoteUrl: `${config.remoteDomain}/${config.localFolders.js}/${config.jsAfter.filename}`,\n localPath: `${config.localFolders.js}/${config.jsAfter.filename}`,\n timeout: config.jsAfter.timeout,\n loadingClass: config.loadingClass,\n };\n}\n","import type { PatchAdamsConfig } from '../config/schema.js';\n\nexport interface SkinCssOptions {\n remoteUrl: string;\n localPath: string;\n timeout: number;\n}\n\n/**\n * Generate the async CSS loader for the skin slot\n * Loads AFTER core CSS (after.css) to allow skin styles to override core styles\n * Same remote-first + local fallback pattern as css-after\n */\nexport function generateSkinCssLoader(options: SkinCssOptions): string {\n const { remoteUrl, localPath, timeout } = options;\n\n return `<!-- === PATCH-ADAMS: SKIN CSS (async with fallback) === -->\n<script data-pa=\"skin-css-loader\">\n(function() {\n 'use strict';\n var REMOTE_URL = \"${remoteUrl}\";\n var LOCAL_PATH = \"${localPath}\";\n var TIMEOUT = ${timeout};\n\n function loadCSS(url, onSuccess, onError) {\n var link = document.createElement('link');\n link.rel = 'stylesheet';\n link.href = url;\n link.setAttribute('data-pa', 'skin-css');\n\n link.onload = function() {\n if (onSuccess) onSuccess();\n };\n\n link.onerror = function() {\n if (onError) onError();\n };\n\n document.head.appendChild(link);\n return link;\n }\n\n function loadCSSWithFallback() {\n var loaded = false;\n var timeoutId;\n\n // Try remote first\n var remoteLink = loadCSS(\n REMOTE_URL,\n function() {\n if (loaded) return;\n loaded = true;\n clearTimeout(timeoutId);\n console.log('[PA-Patcher] Skin CSS loaded from remote:', REMOTE_URL);\n },\n function() {\n if (loaded) return;\n loaded = true;\n clearTimeout(timeoutId);\n loadLocalFallback();\n }\n );\n\n // Timeout fallback\n timeoutId = setTimeout(function() {\n if (loaded) return;\n loaded = true;\n console.warn('[PA-Patcher] Skin CSS timed out, using local fallback');\n if (remoteLink.parentNode) {\n document.head.removeChild(remoteLink);\n }\n loadLocalFallback();\n }, TIMEOUT);\n }\n\n function loadLocalFallback() {\n loadCSS(\n LOCAL_PATH,\n function() {\n console.log('[PA-Patcher] Skin CSS loaded from local fallback:', LOCAL_PATH);\n },\n function() {\n console.error('[PA-Patcher] Skin CSS failed to load from both remote and local');\n }\n );\n }\n\n // Execute immediately\n loadCSSWithFallback();\n})();\n</script>`;\n}\n\n/**\n * Build skin CSS options from config\n */\nexport function buildSkinCssOptions(config: PatchAdamsConfig): SkinCssOptions | null {\n if (!config.skin) return null;\n\n const cacheBuster = Date.now().toString(36) + Math.random().toString(36).slice(2, 6);\n return {\n remoteUrl: `${config.remoteDomain}/skin/${config.skin}/style.css?v=${cacheBuster}`,\n localPath: `skin/${config.skin}/style.css`,\n timeout: config.cssAfter.timeout, // reuse cssAfter timeout\n };\n}\n","import type { PatchAdamsConfig } from '../config/schema.js';\n\nexport interface SkinJsOptions {\n remoteUrl: string;\n localPath: string;\n timeout: number;\n}\n\n/**\n * Generate the async JS loader for the skin slot\n * Loads AFTER core JS (after.js) to allow skin scripts to override core behavior\n * Same remote-first + local fallback pattern as js-after, but WITHOUT loading class removal\n */\nexport function generateSkinJsLoader(options: SkinJsOptions): string {\n const { remoteUrl, localPath, timeout } = options;\n\n return `<!-- === PATCH-ADAMS: SKIN JS (async with fallback) === -->\n<script data-pa=\"skin-js-loader\">\n(function() {\n 'use strict';\n var REMOTE_URL = \"${remoteUrl}\";\n var LOCAL_PATH = \"${localPath}\";\n var TIMEOUT = ${timeout};\n\n function loadJS(url, onSuccess, onError) {\n var script = document.createElement('script');\n script.src = url;\n script.async = true;\n script.setAttribute('data-pa', 'skin-js');\n\n script.onload = function() {\n if (onSuccess) onSuccess();\n };\n\n script.onerror = function() {\n if (onError) onError();\n };\n\n document.body.appendChild(script);\n return script;\n }\n\n function loadJSWithFallback() {\n var loaded = false;\n var timeoutId;\n\n // Try remote first\n var remoteScript = loadJS(\n REMOTE_URL,\n function() {\n if (loaded) return;\n loaded = true;\n clearTimeout(timeoutId);\n console.log('[PA-Patcher] Skin JS loaded from remote:', REMOTE_URL);\n if (window.pa_patcher && window.pa_patcher.loaded) {\n window.pa_patcher.loaded.skinJs = true;\n }\n },\n function() {\n if (loaded) return;\n loaded = true;\n clearTimeout(timeoutId);\n loadLocalFallback();\n }\n );\n\n // Timeout fallback\n timeoutId = setTimeout(function() {\n if (loaded) return;\n loaded = true;\n console.warn('[PA-Patcher] Skin JS timed out, using local fallback');\n if (remoteScript.parentNode) {\n document.body.removeChild(remoteScript);\n }\n loadLocalFallback();\n }, TIMEOUT);\n }\n\n function loadLocalFallback() {\n loadJS(\n LOCAL_PATH,\n function() {\n console.log('[PA-Patcher] Skin JS loaded from local fallback:', LOCAL_PATH);\n if (window.pa_patcher && window.pa_patcher.loaded) {\n window.pa_patcher.loaded.skinJs = true;\n }\n },\n function() {\n console.error('[PA-Patcher] Skin JS failed to load from both remote and local');\n }\n );\n }\n\n // Execute after DOM is ready\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', function() {\n setTimeout(loadJSWithFallback, 150);\n });\n } else {\n setTimeout(loadJSWithFallback, 150);\n }\n})();\n</script>`;\n}\n\n/**\n * Build skin JS options from config\n */\nexport function buildSkinJsOptions(config: PatchAdamsConfig): SkinJsOptions | null {\n if (!config.skin) return null;\n\n const cacheBuster = Date.now().toString(36) + Math.random().toString(36).slice(2, 6);\n return {\n remoteUrl: `${config.remoteDomain}/skin/${config.skin}/script.js?v=${cacheBuster}`,\n localPath: `skin/${config.skin}/script.js`,\n timeout: config.jsAfter.timeout, // reuse jsAfter timeout\n };\n}\n","import type { PatchAdamsConfig } from '../config/schema.js';\nimport type { CourseMetadata } from '../fingerprint/course-metadata.js';\nimport type { GeneratedPluginAssets } from '../plugins/types.js';\nimport {\n generateCssBeforeLoader,\n buildCssBeforeOptions,\n generateCssAfterLoader,\n buildCssAfterOptions,\n generateJsBeforeLoader,\n buildJsBeforeOptions,\n generateJsAfterLoader,\n buildJsAfterOptions,\n generateSkinCssLoader,\n buildSkinCssOptions,\n generateSkinJsLoader,\n buildSkinJsOptions,\n} from '../templates/index.js';\n\n/**\n * HTML Injector for patching Rise course index.html\n *\n * Injection points:\n * 1. CSS Before - Start of <head> (blocking)\n * 2. JS Before - Start of <head>, after CSS Before (blocking)\n * 3. CSS After - End of <head> (async with fallback)\n * 4. JS After - End of <body>, after __loadEntry() (async with fallback)\n * 5. Plugin Assets - Inline CSS/JS injected directly (always executes)\n *\n * Also adds classes and data attributes to <html> tag\n */\nexport class HtmlInjector {\n private config: PatchAdamsConfig;\n private metadata: CourseMetadata | null = null;\n private pluginAssets: GeneratedPluginAssets | null = null;\n\n constructor(config: PatchAdamsConfig) {\n this.config = config;\n }\n\n /**\n * Set plugin-generated assets to inject inline into the HTML.\n * These are injected directly (not via external files) so they always execute.\n */\n setPluginAssets(assets: GeneratedPluginAssets): void {\n this.pluginAssets = assets;\n }\n\n /**\n * Set the course metadata to be injected into the HTML\n */\n setMetadata(metadata: CourseMetadata): void {\n this.metadata = metadata;\n }\n\n /**\n * @deprecated Use setMetadata instead\n * Set the course ID to be injected into the HTML\n */\n setCourseId(courseId: string): void {\n // Create minimal metadata for backwards compatibility\n if (!this.metadata) {\n this.metadata = {\n courseId,\n courseIdSource: 'fallback',\n format: 'unknown',\n title: null,\n description: null,\n organizationId: null,\n launchUrl: null,\n masteryScore: null,\n duration: null,\n language: null,\n version: null,\n extractedAt: new Date().toISOString(),\n };\n } else {\n this.metadata.courseId = courseId;\n }\n }\n\n /**\n * Inject all loaders into HTML\n */\n inject(html: string): string {\n let result = html;\n\n // Step 1: Add classes and data attributes to <html> tag\n result = this.addHtmlAttributes(result);\n\n // Step 2: Inject CSS Before at start of <head>\n if (this.config.cssBefore.enabled) {\n result = this.injectCssBefore(result);\n }\n\n // Step 3: Inject JS Before at start of <head> (after CSS Before)\n if (this.config.jsBefore.enabled) {\n result = this.injectJsBefore(result);\n }\n\n // Step 4: Inject CSS After at end of <head>\n if (this.config.cssAfter.enabled) {\n result = this.injectCssAfter(result);\n }\n\n // Step 5: Inject JS After at end of <body>\n if (this.config.jsAfter.enabled) {\n result = this.injectJsAfter(result);\n }\n\n // Step 6: Inject Skin CSS/JS (after core, before plugins)\n if (this.config.skin) {\n result = this.injectSkinCss(result);\n result = this.injectSkinJs(result);\n }\n\n // Step 7: Inject plugin assets inline (always executes, independent of remote/local loading)\n if (this.pluginAssets) {\n result = this.injectPluginAssets(result);\n }\n\n return result;\n }\n\n /**\n * Inject plugin assets inline into the HTML.\n * Plugin CSS goes at end of <head>, plugin JS goes at end of <body>.\n * These are injected directly so they always execute regardless of\n * whether remote or local fallback files are loaded.\n */\n private injectPluginAssets(html: string): string {\n let result = html;\n\n // Inject plugin CSS (before + after combined) at end of <head>\n const pluginCss = [\n this.pluginAssets?.cssBefore || '',\n this.pluginAssets?.cssAfter || '',\n ].filter(Boolean).join('\\n');\n\n if (pluginCss) {\n const cssBlock = `<!-- === PATCH-ADAMS: PLUGIN CSS (inline) === -->\n<style data-pa=\"plugin-css\">\n${pluginCss}\n</style>`;\n result = result.replace(/<\\/head>/i, `${cssBlock}\\n</head>`);\n console.log('[HtmlInjector] Injected plugin CSS inline');\n }\n\n // Inject plugin JS (before + after combined) at end of <body>\n const pluginJs = [\n this.pluginAssets?.jsBefore || '',\n this.pluginAssets?.jsAfter || '',\n ].filter(Boolean).join('\\n');\n\n if (pluginJs) {\n const jsBlock = `<!-- === PATCH-ADAMS: PLUGIN JS (inline) === -->\n<script data-pa=\"plugin-js\">\n${pluginJs}\n</script>`;\n result = result.replace(/<\\/body>/i, `${jsBlock}\\n</body>`);\n console.log('[HtmlInjector] Injected plugin JS inline');\n }\n\n return result;\n }\n\n /**\n * Add pa-patched and pa-loading classes to <html> tag\n * Also adds data attributes for course metadata\n */\n private addHtmlAttributes(html: string): string {\n const { htmlClass, loadingClass, skin } = this.config;\n const classes = `${htmlClass} ${loadingClass}${skin ? ` pa-skinned ${skin}` : ''}`;\n\n // Build data attributes from metadata\n const dataAttrs: string[] = [];\n if (this.metadata) {\n dataAttrs.push(`data-pa-course-id=\"${this.escapeAttr(this.metadata.courseId)}\"`);\n dataAttrs.push(`data-pa-format=\"${this.metadata.format}\"`);\n if (this.metadata.title) {\n dataAttrs.push(`data-pa-title=\"${this.escapeAttr(this.metadata.title)}\"`);\n }\n }\n if (skin) {\n dataAttrs.push(`data-pa-skin=\"${this.escapeAttr(skin)}\"`);\n }\n const dataAttrString = dataAttrs.length > 0 ? ' ' + dataAttrs.join(' ') : '';\n\n // Match <html> tag with or without existing class attribute\n const htmlTagPattern = /<html([^>]*)>/i;\n const match = html.match(htmlTagPattern);\n\n if (!match) {\n return html;\n }\n\n const attributes = match[1];\n\n // Check if class attribute exists\n if (/class\\s*=\\s*[\"'][^\"']*[\"']/i.test(attributes)) {\n // Append to existing class\n return html.replace(\n htmlTagPattern,\n (_match: string, attrs: string) => {\n const newAttrs = attrs.replace(\n /class\\s*=\\s*[\"']([^\"']*)[\"']/i,\n (_fullMatch: string, existingClasses: string) => `class=\"${existingClasses} ${classes}\"`\n );\n return `<html${newAttrs}${dataAttrString}>`;\n }\n );\n } else {\n // Add class attribute\n return html.replace(htmlTagPattern, `<html${attributes} class=\"${classes}\"${dataAttrString}>`);\n }\n }\n\n /**\n * Escape a string for use in HTML attributes\n */\n private escapeAttr(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n }\n\n /**\n * Inject CSS Before loader at start of <head>\n */\n private injectCssBefore(html: string): string {\n const options = buildCssBeforeOptions(this.config);\n const loader = generateCssBeforeLoader(options);\n\n // Insert right after <head> tag\n return html.replace(/<head([^>]*)>/i, `<head$1>\\n${loader}`);\n }\n\n /**\n * Inject JS Before loader at start of <head> (after CSS Before if present)\n */\n private injectJsBefore(html: string): string {\n const options = buildJsBeforeOptions(this.config, this.metadata);\n const loader = generateJsBeforeLoader(options);\n\n // Look for the CSS Before marker to insert after it\n const cssBeforeMarker = '<!-- === PATCH-ADAMS: CSS BEFORE';\n if (html.includes(cssBeforeMarker)) {\n // Find end of CSS Before block and insert after\n const cssBeforeEndPattern = /<\\/script>\\s*(?=\\n|<)/;\n const cssBeforeStart = html.indexOf(cssBeforeMarker);\n const afterCssBefore = html.substring(cssBeforeStart);\n const endMatch = afterCssBefore.match(cssBeforeEndPattern);\n\n if (endMatch && endMatch.index !== undefined) {\n const insertPos = cssBeforeStart + endMatch.index + endMatch[0].length;\n return html.slice(0, insertPos) + '\\n' + loader + html.slice(insertPos);\n }\n }\n\n // Fallback: insert after <head> tag\n return html.replace(/<head([^>]*)>/i, `<head$1>\\n${loader}`);\n }\n\n /**\n * Inject CSS After loader at end of <head>\n */\n private injectCssAfter(html: string): string {\n const options = buildCssAfterOptions(this.config);\n const loader = generateCssAfterLoader(options);\n\n // Insert before </head>\n return html.replace(/<\\/head>/i, `${loader}\\n</head>`);\n }\n\n /**\n * Inject JS After loader at end of <body> (after __loadEntry)\n */\n private injectJsAfter(html: string): string {\n const options = buildJsAfterOptions(this.config);\n const loader = generateJsAfterLoader(options);\n\n // Try to find __loadEntry() call and insert after it\n const loadEntryPattern = /(<script>__loadEntry\\(\\)<\\/script>)/i;\n if (loadEntryPattern.test(html)) {\n return html.replace(loadEntryPattern, `$1\\n${loader}`);\n }\n\n // Fallback: insert before </body>\n return html.replace(/<\\/body>/i, `${loader}\\n</body>`);\n }\n\n /**\n * Inject Skin CSS loader after CSS After (before </head>)\n */\n private injectSkinCss(html: string): string {\n const options = buildSkinCssOptions(this.config);\n if (!options) return html;\n\n const loader = generateSkinCssLoader(options);\n // Insert before </head> (after CSS After which was already injected there)\n return html.replace(/<\\/head>/i, `${loader}\\n</head>`);\n }\n\n /**\n * Inject Skin JS loader after JS After (before </body>)\n */\n private injectSkinJs(html: string): string {\n const options = buildSkinJsOptions(this.config);\n if (!options) return html;\n\n const loader = generateSkinJsLoader(options);\n // Insert before </body> (after JS After which was already injected there)\n return html.replace(/<\\/body>/i, `${loader}\\n</body>`);\n }\n}\n","import type { PatchAdamsConfig } from '../config/schema.js';\nimport type { CourseMetadata } from '../fingerprint/course-metadata.js';\nimport type { GeneratedPluginAssets } from '../plugins/types.js';\nimport {\n generateCssBeforeLoader,\n buildCssBeforeOptions,\n generateCssAfterLoader,\n buildCssAfterOptions,\n generateJsBeforeLoader,\n buildJsBeforeOptions,\n generateJsAfterLoader,\n buildJsAfterOptions,\n generateSkinCssLoader,\n buildSkinCssOptions,\n generateSkinJsLoader,\n buildSkinJsOptions,\n} from '../templates/index.js';\n\n/**\n * HTML Injector for patching Articulate Storyline course HTML files\n *\n * Storyline structure:\n * - Main entry: story.html (standalone) or index_lms.html (LMS)\n * - Player scripts: html5/lib/scripts/bootstrapper.min.js (loaded at end of body)\n * - Course data: html5/data/js/*.js\n * - CSS: html5/data/css/output.min.css\n *\n * Injection points:\n * 1. CSS Before - Start of <head> (blocking)\n * 2. JS Before - Start of <head>, after CSS Before (blocking)\n * 3. CSS After - End of <head>, before </head>\n * 4. JS After - End of <body>, before </body> (after content, before bootstrapper runs)\n * 5. Plugin Assets - Inline CSS/JS injected directly (always executes)\n *\n * Also adds classes and data attributes to <html> tag\n */\nexport class StorylineHtmlInjector {\n private config: PatchAdamsConfig;\n private metadata: CourseMetadata | null = null;\n private pluginAssets: GeneratedPluginAssets | null = null;\n\n constructor(config: PatchAdamsConfig) {\n this.config = config;\n }\n\n /**\n * Set plugin-generated assets to inject inline into the HTML.\n * These are injected directly (not via external files) so they always execute.\n */\n setPluginAssets(assets: GeneratedPluginAssets): void {\n this.pluginAssets = assets;\n }\n\n /**\n * Set the course metadata to be injected into the HTML\n */\n setMetadata(metadata: CourseMetadata): void {\n this.metadata = metadata;\n }\n\n /**\n * Inject all loaders into HTML\n */\n inject(html: string): string {\n let result = html;\n\n // Step 1: Add classes and data attributes to <html> tag\n result = this.addHtmlAttributes(result);\n\n // Step 2: Inject CSS Before at start of <head>\n if (this.config.cssBefore.enabled) {\n result = this.injectCssBefore(result);\n }\n\n // Step 3: Inject JS Before at start of <head> (after CSS Before)\n if (this.config.jsBefore.enabled) {\n result = this.injectJsBefore(result);\n }\n\n // Step 4: Inject CSS After at end of <head>\n if (this.config.cssAfter.enabled) {\n result = this.injectCssAfter(result);\n }\n\n // Step 5: Inject JS After at end of <body>\n if (this.config.jsAfter.enabled) {\n result = this.injectJsAfter(result);\n }\n\n // Step 6: Inject Skin CSS/JS (after core, before plugins)\n if (this.config.skin) {\n result = this.injectSkinCss(result);\n result = this.injectSkinJs(result);\n }\n\n // Step 7: Inject plugin assets inline (always executes, independent of remote/local loading)\n if (this.pluginAssets) {\n result = this.injectPluginAssets(result);\n }\n\n return result;\n }\n\n /**\n * Inject plugin assets inline into the HTML.\n * Plugin CSS goes at end of <head>, plugin JS goes at end of <body>.\n */\n private injectPluginAssets(html: string): string {\n let result = html;\n\n // Inject plugin CSS at end of <head>\n const pluginCss = [\n this.pluginAssets?.cssBefore || '',\n this.pluginAssets?.cssAfter || '',\n ]\n .filter(Boolean)\n .join('\\n');\n\n if (pluginCss) {\n const cssBlock = `<!-- === PATCH-ADAMS: PLUGIN CSS (inline) === -->\n<style data-pa=\"plugin-css\">\n${pluginCss}\n</style>`;\n result = result.replace(/<\\/head>/i, `${cssBlock}\\n</head>`);\n console.log('[StorylineHtmlInjector] Injected plugin CSS inline');\n }\n\n // Inject plugin JS at end of <body>\n const pluginJs = [\n this.pluginAssets?.jsBefore || '',\n this.pluginAssets?.jsAfter || '',\n ]\n .filter(Boolean)\n .join('\\n');\n\n if (pluginJs) {\n const jsBlock = `<!-- === PATCH-ADAMS: PLUGIN JS (inline) === -->\n<script data-pa=\"plugin-js\">\n${pluginJs}\n</script>`;\n result = result.replace(/<\\/body>/i, `${jsBlock}\\n</body>`);\n console.log('[StorylineHtmlInjector] Injected plugin JS inline');\n }\n\n return result;\n }\n\n /**\n * Add pa-patched and pa-loading classes to <html> tag\n * Also adds data attributes for course metadata\n */\n private addHtmlAttributes(html: string): string {\n const { htmlClass, loadingClass, skin } = this.config;\n const classes = `${htmlClass} ${loadingClass}${skin ? ` pa-skinned ${skin}` : ''}`;\n\n // Build data attributes from metadata\n const dataAttrs: string[] = [];\n if (this.metadata) {\n dataAttrs.push(`data-pa-course-id=\"${this.escapeAttr(this.metadata.courseId)}\"`);\n dataAttrs.push(`data-pa-format=\"${this.metadata.format}\"`);\n dataAttrs.push('data-pa-tool=\"storyline\"');\n if (this.metadata.title) {\n dataAttrs.push(`data-pa-title=\"${this.escapeAttr(this.metadata.title)}\"`);\n }\n }\n if (skin) {\n dataAttrs.push(`data-pa-skin=\"${this.escapeAttr(skin)}\"`);\n }\n const dataAttrString = dataAttrs.length > 0 ? ' ' + dataAttrs.join(' ') : '';\n\n // Match <html> tag with or without existing class attribute\n const htmlTagPattern = /<html([^>]*)>/i;\n const match = html.match(htmlTagPattern);\n\n if (!match) {\n return html;\n }\n\n const attributes = match[1];\n\n // Check if class attribute exists\n if (/class\\s*=\\s*[\"'][^\"']*[\"']/i.test(attributes)) {\n // Append to existing class\n return html.replace(htmlTagPattern, (_match: string, attrs: string) => {\n const newAttrs = attrs.replace(\n /class\\s*=\\s*[\"']([^\"']*)[\"']/i,\n (_fullMatch: string, existingClasses: string) =>\n `class=\"${existingClasses} ${classes}\"`\n );\n return `<html${newAttrs}${dataAttrString}>`;\n });\n } else {\n // Add class attribute\n return html.replace(\n htmlTagPattern,\n `<html${attributes} class=\"${classes}\"${dataAttrString}>`\n );\n }\n }\n\n /**\n * Escape a string for use in HTML attributes\n */\n private escapeAttr(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n }\n\n /**\n * Inject CSS Before loader at start of <head>\n */\n private injectCssBefore(html: string): string {\n const options = buildCssBeforeOptions(this.config);\n const loader = generateCssBeforeLoader(options);\n\n // Insert right after <head> tag\n return html.replace(/<head([^>]*)>/i, `<head$1>\\n${loader}`);\n }\n\n /**\n * Inject JS Before loader at start of <head> (after CSS Before if present)\n */\n private injectJsBefore(html: string): string {\n const options = buildJsBeforeOptions(this.config, this.metadata);\n const loader = generateJsBeforeLoader(options);\n\n // Look for the CSS Before marker to insert after it\n const cssBeforeMarker = '<!-- === PATCH-ADAMS: CSS BEFORE';\n if (html.includes(cssBeforeMarker)) {\n // Find end of CSS Before block and insert after\n const cssBeforeEndPattern = /<\\/script>\\s*(?=\\n|<)/;\n const cssBeforeStart = html.indexOf(cssBeforeMarker);\n const afterCssBefore = html.substring(cssBeforeStart);\n const endMatch = afterCssBefore.match(cssBeforeEndPattern);\n\n if (endMatch && endMatch.index !== undefined) {\n const insertPos = cssBeforeStart + endMatch.index + endMatch[0].length;\n return html.slice(0, insertPos) + '\\n' + loader + html.slice(insertPos);\n }\n }\n\n // Fallback: insert after <head> tag\n return html.replace(/<head([^>]*)>/i, `<head$1>\\n${loader}`);\n }\n\n /**\n * Inject CSS After loader at end of <head>\n */\n private injectCssAfter(html: string): string {\n const options = buildCssAfterOptions(this.config);\n const loader = generateCssAfterLoader(options);\n\n // Insert before </head>\n return html.replace(/<\\/head>/i, `${loader}\\n</head>`);\n }\n\n /**\n * Inject JS After loader at end of <body>\n *\n * Storyline note: The bootstrapper.min.js is loaded via <script src=\"...\">\n * AFTER </body> tag (unusual but that's how Storyline works).\n * We inject before </body> so our code runs before the bootstrapper.\n */\n private injectJsAfter(html: string): string {\n const options = buildJsAfterOptions(this.config);\n const loader = generateJsAfterLoader(options);\n\n // Insert before </body>\n return html.replace(/<\\/body>/i, `${loader}\\n</body>`);\n }\n\n /**\n * Inject Skin CSS loader after CSS After (before </head>)\n */\n private injectSkinCss(html: string): string {\n const options = buildSkinCssOptions(this.config);\n if (!options) return html;\n\n const loader = generateSkinCssLoader(options);\n return html.replace(/<\\/head>/i, `${loader}\\n</head>`);\n }\n\n /**\n * Inject Skin JS loader after JS After (before </body>)\n */\n private injectSkinJs(html: string): string {\n const options = buildSkinJsOptions(this.config);\n if (!options) return html;\n\n const loader = generateSkinJsLoader(options);\n return html.replace(/<\\/body>/i, `${loader}\\n</body>`);\n }\n}\n","import type AdmZip from 'adm-zip';\nimport type { PatchAdamsConfig } from '../config/schema.js';\nimport type { PackageFormat } from '../detectors/format-detector.js';\n\nexport interface ManifestPaths {\n cssBefore?: string;\n cssAfter?: string;\n jsBefore?: string;\n jsAfter?: string;\n skinCss?: string;\n skinJs?: string;\n}\n\n/**\n * Update manifest files to include injected assets\n * This ensures SCORM compliance for strict LMS systems\n */\nexport class ManifestUpdater {\n private config: PatchAdamsConfig;\n\n constructor(config: PatchAdamsConfig) {\n this.config = config;\n }\n\n /**\n * Update manifest files based on package format\n */\n update(zip: AdmZip, format: PackageFormat, paths: ManifestPaths): string[] {\n const modified: string[] = [];\n\n switch (format) {\n case 'scorm12':\n case 'scorm2004-3':\n case 'scorm2004-4':\n modified.push(...this.updateImsManifest(zip, paths));\n break;\n\n case 'cmi5':\n // cmi5 packages also typically have an imsmanifest.xml\n modified.push(...this.updateImsManifest(zip, paths));\n break;\n\n case 'xapi':\n // xAPI/tincan.xml doesn't have file manifests\n // Files are loaded directly by HTML\n break;\n\n case 'aicc':\n // AICC may have imsmanifest.xml\n const aiccManifest = zip.getEntry('imsmanifest.xml');\n if (aiccManifest) {\n modified.push(...this.updateImsManifest(zip, paths));\n }\n break;\n }\n\n return modified;\n }\n\n /**\n * Update imsmanifest.xml to include new file references\n * Uses string manipulation to preserve original XML formatting exactly\n */\n private updateImsManifest(zip: AdmZip, paths: ManifestPaths): string[] {\n const entry = zip.getEntry('imsmanifest.xml');\n if (!entry) {\n return [];\n }\n\n try {\n const xmlContent = entry.getData().toString('utf-8');\n\n // Build file entries to add\n const filesToAdd = [\n paths.cssBefore,\n paths.cssAfter,\n paths.jsBefore,\n paths.jsAfter,\n paths.skinCss,\n paths.skinJs,\n ].filter(Boolean) as string[];\n\n if (filesToAdd.length === 0) {\n return [];\n }\n\n // Generate new file entry XML strings\n const newFileEntries = filesToAdd\n .map((filePath) => ` <file href=\"${filePath}\" />`)\n .join('\\n');\n\n // Find the closing </resource> tag and insert before it\n // This preserves all original XML formatting, comments, etc.\n const resourceClosePattern = /(\\s*)<\\/resource>/i;\n const match = xmlContent.match(resourceClosePattern);\n\n if (!match) {\n console.warn('[PA-Patcher] Could not find </resource> tag in imsmanifest.xml');\n return [];\n }\n\n const updatedXml = xmlContent.replace(\n resourceClosePattern,\n `\\n${newFileEntries}\\n$1</resource>`\n );\n\n zip.updateFile('imsmanifest.xml', Buffer.from(updatedXml, 'utf-8'));\n\n return ['imsmanifest.xml'];\n } catch (error) {\n console.error('[PA-Patcher] Failed to update imsmanifest.xml:', error);\n return [];\n }\n }\n\n /**\n * Update the manifest identifier attribute to a new value.\n * Used to ensure every course has a unique identifier (e.g., project UUID).\n */\n updateIdentifier(zip: AdmZip, newIdentifier: string): boolean {\n const entry = zip.getEntry('imsmanifest.xml');\n if (!entry) return false;\n\n const xml = entry.getData().toString('utf-8');\n const updated = xml.replace(\n /(<manifest[^>]+identifier\\s*=\\s*[\"'])[^\"']+([\"'])/i,\n `$1${newIdentifier}$2`\n );\n\n if (updated === xml) return false;\n\n zip.updateFile('imsmanifest.xml', Buffer.from(updated, 'utf-8'));\n return true;\n }\n\n /**\n * Get the file paths that will be added to the package\n */\n getFilePaths(): ManifestPaths {\n const paths: ManifestPaths = {};\n\n if (this.config.cssBefore.enabled) {\n paths.cssBefore = `scormcontent/${this.config.localFolders.css}/${this.config.cssBefore.filename}`;\n }\n\n if (this.config.cssAfter.enabled) {\n paths.cssAfter = `scormcontent/${this.config.localFolders.css}/${this.config.cssAfter.filename}`;\n }\n\n if (this.config.jsBefore.enabled) {\n paths.jsBefore = `scormcontent/${this.config.localFolders.js}/${this.config.jsBefore.filename}`;\n }\n\n if (this.config.jsAfter.enabled) {\n paths.jsAfter = `scormcontent/${this.config.localFolders.js}/${this.config.jsAfter.filename}`;\n }\n\n return paths;\n }\n}\n","import crypto from 'crypto';\nimport AdmZip from 'adm-zip';\nimport archiver from 'archiver';\nimport { PassThrough } from 'stream';\nimport type { PatchAdamsConfig } from '../config/schema.js';\nimport { mergeWithDefaults } from '../config/loader.js';\nimport { HtmlInjector } from './html-injector.js';\nimport { StorylineHtmlInjector } from './storyline-html-injector.js';\nimport { ManifestUpdater } from './manifest-updater.js';\nimport { FormatDetector, type PackageFormat } from '../detectors/format-detector.js';\nimport {\n AuthoringToolDetector,\n type AuthoringTool,\n type AuthoringToolInfo,\n} from '../detectors/authoring-tool-detector.js';\nimport { extractCourseMetadata, type CourseMetadata } from '../fingerprint/course-metadata.js';\nimport { pluginRegistry, type GeneratedPluginAssets } from '../plugins/index.js';\n\n/**\n * Result of a patch operation\n */\nexport interface PatchResult {\n success: boolean;\n format: PackageFormat;\n formatDisplayName: string;\n /** Detected authoring tool */\n authoringTool: AuthoringTool;\n authoringToolDisplayName: string;\n /** Full course metadata */\n metadata: CourseMetadata;\n /** @deprecated Use metadata.courseId instead */\n courseId: string;\n /** @deprecated Use metadata.courseIdSource instead */\n courseIdSource: 'manifest' | 'tincan' | 'cmi5' | 'fallback';\n filesModified: string[];\n filesAdded: string[];\n errors: string[];\n warnings: string[];\n}\n\n/**\n * Options for patching\n */\nexport interface PatchOptions {\n /** Content for CSS before (local fallback) - overrides fetched content */\n cssBeforeContent?: string;\n /** Content for CSS after (local fallback) - overrides fetched content */\n cssAfterContent?: string;\n /** Content for JS before (local fallback) - overrides fetched content */\n jsBeforeContent?: string;\n /** Content for JS after (local fallback) - overrides fetched content */\n jsAfterContent?: string;\n /** Skip fetching remote files even if fetchFallbacks is enabled in config */\n skipFetch?: boolean;\n /** Optional skin name — per-call override for config.skin */\n skin?: string;\n /** Original package filename — baked into LRS bridge for statement searchability */\n packageName?: string;\n}\n\n/**\n * Fetched fallback content from remote server\n */\ninterface FetchedFallbacks {\n cssBefore?: string;\n cssAfter?: string;\n jsBefore?: string;\n jsAfter?: string;\n skinCss?: string;\n skinJs?: string;\n}\n\n/**\n * Default fallback file contents\n */\nconst DEFAULT_CSS_BEFORE = `/* PA-Patcher: CSS Before (blocking)\n * This file loads at the start of <head> and blocks rendering.\n * Use it to hide content and prevent flash of unstyled content.\n *\n * Example:\n * html.pa-patched.pa-loading body {\n * visibility: hidden !important;\n * }\n */\n`;\n\nconst DEFAULT_CSS_AFTER = `/* PA-Patcher: CSS After (async)\n * This file loads at the end of <head> with remote fallback.\n * Use it for style overrides that take precedence over Rise styles.\n *\n * Example:\n * html.pa-patched .blocks-text {\n * font-family: 'Your Font', sans-serif !important;\n * }\n */\n`;\n\nconst DEFAULT_JS_BEFORE = `// PA-Patcher: JS Before (blocking)\n// This file loads at the start of <head> and blocks rendering.\n// Use it for setup, API interception, and preparing globals.\n//\n// Example:\n// window.PatchAdams.customConfig = {\n// theme: 'dark',\n// analytics: true\n// };\n\nconsole.log('[PA-Patcher] JS Before loaded');\n`;\n\nconst DEFAULT_JS_AFTER = `// PA-Patcher: JS After (async)\n// This file loads at the end of <body> with remote fallback.\n// Use it for DOM manipulation after Rise has initialized.\n//\n// IMPORTANT: This script should NOT remove the loading class.\n// The loader script handles that automatically after this script loads.\n//\n// Example:\n// document.querySelectorAll('.blocks-text').forEach(function(el) {\n// // Your modifications here\n// });\n\nconsole.log('[PA-Patcher] JS After loaded');\n`;\n\n/**\n * Main Patcher class for patching e-learning course packages\n * Supports Rise, Storyline, and other authoring tools\n */\nexport class Patcher {\n private config: PatchAdamsConfig;\n private riseHtmlInjector: HtmlInjector;\n private storylineHtmlInjector: StorylineHtmlInjector;\n private manifestUpdater: ManifestUpdater;\n private formatDetector: FormatDetector;\n private authoringToolDetector: AuthoringToolDetector;\n\n constructor(config: Partial<PatchAdamsConfig>) {\n // Merge with defaults so partial configs work correctly\n this.config = mergeWithDefaults(config);\n this.riseHtmlInjector = new HtmlInjector(this.config);\n this.storylineHtmlInjector = new StorylineHtmlInjector(this.config);\n this.manifestUpdater = new ManifestUpdater(this.config);\n this.formatDetector = new FormatDetector();\n this.authoringToolDetector = new AuthoringToolDetector();\n }\n\n /**\n * Fetch a single file from a URL\n * Returns undefined if fetch fails (non-blocking)\n */\n private async fetchFile(url: string): Promise<string | undefined> {\n try {\n console.log(`[Patcher] Fetching: ${url}`);\n const response = await fetch(url);\n if (!response.ok) {\n console.warn(`[Patcher] Failed to fetch ${url}: ${response.status} ${response.statusText}`);\n return undefined;\n }\n const content = await response.text();\n console.log(`[Patcher] Fetched ${url} (${content.length} bytes)`);\n return content;\n } catch (error) {\n console.warn(`[Patcher] Error fetching ${url}:`, error);\n return undefined;\n }\n }\n\n /**\n * Fetch all remote fallback files\n * Non-blocking: if any fetch fails, we use the default content\n */\n private async fetchRemoteFallbacks(): Promise<FetchedFallbacks> {\n const fetched: FetchedFallbacks = {};\n const { remoteDomain, localFolders, cssBefore, cssAfter, jsBefore, jsAfter } = this.config;\n\n const fetchPromises: Promise<void>[] = [];\n\n if (cssBefore.enabled) {\n const url = `${remoteDomain}/${localFolders.css}/${cssBefore.filename}`;\n fetchPromises.push(\n this.fetchFile(url).then(content => { fetched.cssBefore = content; })\n );\n }\n\n if (cssAfter.enabled) {\n const url = `${remoteDomain}/${localFolders.css}/${cssAfter.filename}`;\n fetchPromises.push(\n this.fetchFile(url).then(content => { fetched.cssAfter = content; })\n );\n }\n\n if (jsBefore.enabled) {\n const url = `${remoteDomain}/${localFolders.js}/${jsBefore.filename}`;\n fetchPromises.push(\n this.fetchFile(url).then(content => { fetched.jsBefore = content; })\n );\n }\n\n if (jsAfter.enabled) {\n const url = `${remoteDomain}/${localFolders.js}/${jsAfter.filename}`;\n fetchPromises.push(\n this.fetchFile(url).then(content => { fetched.jsAfter = content; })\n );\n }\n\n // Fetch skin files if skin is configured\n if (this.config.skin) {\n const skinCssUrl = `${remoteDomain}/skin/${this.config.skin}/style.css`;\n fetchPromises.push(\n this.fetchFile(skinCssUrl).then(content => { fetched.skinCss = content; })\n );\n\n const skinJsUrl = `${remoteDomain}/skin/${this.config.skin}/script.js`;\n fetchPromises.push(\n this.fetchFile(skinJsUrl).then(content => { fetched.skinJs = content; })\n );\n }\n\n // Fetch all in parallel\n await Promise.all(fetchPromises);\n\n return fetched;\n }\n\n /**\n * Patch a Rise course ZIP file\n * @param inputBuffer - Input ZIP file as Buffer\n * @param options - Additional patching options\n * @returns Patched ZIP file as Buffer and result details\n */\n async patch(\n inputBuffer: Buffer,\n options: PatchOptions = {}\n ): Promise<{ buffer: Buffer; result: PatchResult }> {\n // Create placeholder metadata for initialization\n const placeholderMetadata: CourseMetadata = {\n courseId: '',\n courseIdSource: 'fallback',\n format: 'unknown',\n title: null,\n description: null,\n organizationId: null,\n launchUrl: null,\n masteryScore: null,\n duration: null,\n language: null,\n version: null,\n extractedAt: new Date().toISOString(),\n };\n\n const result: PatchResult = {\n success: false,\n format: 'unknown',\n formatDisplayName: 'Unknown',\n authoringTool: 'unknown',\n authoringToolDisplayName: 'Unknown',\n metadata: placeholderMetadata,\n courseId: '',\n courseIdSource: 'fallback',\n filesModified: [],\n filesAdded: [],\n errors: [],\n warnings: [],\n };\n\n try {\n // 1. Open ZIP with adm-zip for reading\n const zip = new AdmZip(inputBuffer);\n\n // 2. Detect package format\n result.format = this.formatDetector.detect(zip);\n result.formatDisplayName = this.formatDetector.getFormatDisplayName(result.format);\n\n if (result.format === 'unknown') {\n result.warnings.push('Could not determine package format, proceeding anyway');\n }\n\n // 2b. Detect authoring tool\n const toolInfo = this.authoringToolDetector.detect(zip);\n result.authoringTool = toolInfo.tool;\n result.authoringToolDisplayName = toolInfo.displayName;\n\n console.log(`[Patcher] Detected authoring tool: ${toolInfo.displayName}`);\n console.log(` - Main HTML: ${toolInfo.mainHtmlPath}`);\n if (toolInfo.additionalHtmlPaths?.length) {\n console.log(` - Additional HTML: ${toolInfo.additionalHtmlPaths.join(', ')}`);\n }\n if (toolInfo.version) console.log(` - Version: ${toolInfo.version}`);\n\n // 3. Extract comprehensive course metadata\n const metadata = extractCourseMetadata(zip, result.format);\n result.metadata = metadata;\n result.courseId = metadata.courseId;\n result.courseIdSource = metadata.courseIdSource;\n\n console.log(`[Patcher] Course metadata extracted:`);\n console.log(` - Course ID: ${metadata.courseId} (source: ${metadata.courseIdSource})`);\n console.log(` - Format: ${metadata.format}`);\n if (metadata.title) console.log(` - Title: ${metadata.title}`);\n if (metadata.description) console.log(` - Description: ${metadata.description.substring(0, 50)}...`);\n if (metadata.language) console.log(` - Language: ${metadata.language}`);\n if (metadata.masteryScore !== null) console.log(` - Mastery Score: ${metadata.masteryScore}`);\n\n // 3b. Ensure unique course identifier\n // Priority: documentGuid (from config) > manifest identifier > generated UUID\n const configDocGuid = this.config.lrsBridge?.documentGuid;\n\n if (configDocGuid) {\n // documentGuid takes priority (e.g., podcast projectId)\n console.log(`[Patcher] Overriding courseId with documentGuid: ${configDocGuid}`);\n metadata.courseId = configDocGuid;\n metadata.courseIdSource = 'manifest';\n result.courseId = configDocGuid;\n if (this.manifestUpdater.updateIdentifier(zip, configDocGuid)) {\n console.log(`[Patcher] Updated manifest identifier to: ${configDocGuid}`);\n }\n } else if (metadata.courseIdSource === 'fallback') {\n // No documentGuid and no manifest identifier — generate one\n const generated = crypto.randomUUID();\n console.log(`[Patcher] No course identifier found, generating UUID: ${generated}`);\n metadata.courseId = generated;\n result.courseId = generated;\n if (this.manifestUpdater.updateIdentifier(zip, generated)) {\n console.log(`[Patcher] Wrote generated UUID to manifest identifier`);\n }\n }\n\n // 4. Apply per-call skin override if provided\n const effectiveSkin = options.skin || this.config.skin;\n if (options.skin && options.skin !== this.config.skin) {\n // Temporarily set skin on config for this patch call\n this.config.skin = options.skin;\n // Rebuild injectors with updated config\n this.riseHtmlInjector = new HtmlInjector(this.config);\n this.storylineHtmlInjector = new StorylineHtmlInjector(this.config);\n console.log(`[Patcher] Using per-call skin override: ${options.skin}`);\n }\n if (effectiveSkin) {\n console.log(`[Patcher] Skin: ${effectiveSkin}`);\n }\n\n // 4c. Bake package name into LRS bridge for statement searchability\n if (options.packageName) {\n this.config.lrsBridge = this.config.lrsBridge ?? {};\n this.config.lrsBridge.packageName = options.packageName;\n console.log(`[Patcher] Package name: ${options.packageName}`);\n }\n\n // 4b. Set course metadata on appropriate HTML injector\n const htmlInjector = this.getHtmlInjector(toolInfo.tool);\n htmlInjector.setMetadata(metadata);\n\n // 5. Fetch remote fallback files if enabled\n let fetchedFallbacks: FetchedFallbacks = {};\n if (this.config.fetchFallbacks && !options.skipFetch) {\n console.log('[Patcher] Fetching remote files for local fallbacks...');\n fetchedFallbacks = await this.fetchRemoteFallbacks();\n const fetchedCount = Object.values(fetchedFallbacks).filter(Boolean).length;\n console.log(`[Patcher] Fetched ${fetchedCount} remote files`);\n }\n\n // 5b. Generate plugin assets and pass to HTML injector for inline injection\n const pluginAssets = this.generatePluginAssets();\n const hasPluginAssets =\n pluginAssets.cssBefore || pluginAssets.cssAfter || pluginAssets.jsBefore || pluginAssets.jsAfter;\n if (hasPluginAssets) {\n htmlInjector.setPluginAssets(pluginAssets);\n console.log('[Patcher] Plugin assets will be injected inline into HTML');\n }\n\n // 6. Find and patch HTML files (varies by authoring tool)\n // Some tools like Storyline have multiple entry points (story.html, index_lms.html)\n const htmlPathsToPatch = [toolInfo.mainHtmlPath, ...(toolInfo.additionalHtmlPaths || [])];\n\n for (const htmlPath of htmlPathsToPatch) {\n const htmlEntry = zip.getEntry(htmlPath);\n if (!htmlEntry) {\n if (htmlPath === toolInfo.mainHtmlPath) {\n // Main HTML is required\n throw new Error(\n `Could not find ${htmlPath} in package. Is this a valid ${toolInfo.displayName} export?`\n );\n }\n // Additional HTML files are optional\n console.log(`[Patcher] Optional HTML file not found: ${htmlPath}`);\n continue;\n }\n\n console.log(`[Patcher] Patching HTML file: ${htmlPath}`);\n const originalHtml = htmlEntry.getData().toString('utf-8');\n const patchedHtml = htmlInjector.inject(originalHtml);\n zip.updateFile(htmlPath, Buffer.from(patchedHtml, 'utf-8'));\n result.filesModified.push(htmlPath);\n }\n\n // 7. Add local fallback files (using fetched content or defaults)\n const addedFiles = this.addFallbackFiles(zip, options, fetchedFallbacks, toolInfo);\n result.filesAdded.push(...addedFiles);\n\n // 8. Update manifest files\n if (this.config.updateManifests) {\n // Build manifest paths from the actual files added (not hardcoded paths)\n const manifestPaths = this.buildManifestPaths(addedFiles);\n const modifiedManifests = this.manifestUpdater.update(zip, result.format, manifestPaths);\n result.filesModified.push(...modifiedManifests);\n }\n\n // 9. Write output using archiver for better ZIP compatibility\n // adm-zip's toBuffer() produces ZIPs that some parsers don't handle well\n const outputBuffer = await this.writeZipWithArchiver(zip);\n\n result.success = true;\n return { buffer: outputBuffer, result };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n result.errors.push(message);\n throw error;\n }\n }\n\n /**\n * Write ZIP using archiver for better compatibility\n * adm-zip's toBuffer() produces ZIPs that some parsers don't handle well.\n * archiver produces more standard ZIP output.\n */\n private writeZipWithArchiver(admZip: AdmZip): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const archive = archiver('zip', {\n zlib: { level: 9 }, // Maximum compression\n });\n\n const chunks: Buffer[] = [];\n const passThrough = new PassThrough();\n\n passThrough.on('data', (chunk: Buffer) => chunks.push(chunk));\n passThrough.on('end', () => resolve(Buffer.concat(chunks)));\n passThrough.on('error', reject);\n\n archive.on('error', reject);\n archive.pipe(passThrough);\n\n const entries = admZip.getEntries();\n\n for (const entry of entries) {\n const entryName = entry.entryName;\n const mtime = entry.header.time ? new Date(entry.header.time) : new Date();\n\n if (entry.isDirectory) {\n // Preserve explicit directory entries - some LMS systems require them\n archive.append('', {\n name: entryName.endsWith('/') ? entryName : entryName + '/',\n date: mtime,\n store: true,\n });\n continue;\n }\n\n const entryData = entry.getData();\n\n // Determine if we should store or compress based on original\n const store = entry.header.method === 0;\n\n archive.append(entryData, {\n name: entryName,\n date: mtime,\n store: store,\n });\n }\n\n archive.finalize();\n });\n }\n\n /**\n * Get the appropriate HTML injector for the authoring tool\n */\n private getHtmlInjector(tool: AuthoringTool): HtmlInjector | StorylineHtmlInjector {\n switch (tool) {\n case 'storyline':\n return this.storylineHtmlInjector;\n case 'rise':\n default:\n // Use Rise injector as default (works for most SCORM packages)\n return this.riseHtmlInjector;\n }\n }\n\n /**\n * Get the base folder for fallback files based on authoring tool\n */\n private getFallbackBaseFolder(toolInfo: AuthoringToolInfo): string {\n switch (toolInfo.tool) {\n case 'storyline':\n // Storyline uses html5/ folder structure\n return 'html5';\n case 'rise':\n return 'scormcontent';\n case 'xyleme':\n // Xyleme thin packs have files at root level\n return '';\n default:\n // For unknown tools, try to determine from main HTML path\n const htmlPath = toolInfo.mainHtmlPath;\n if (htmlPath.includes('/')) {\n return htmlPath.split('/')[0];\n }\n // Root level - use empty string (files at root)\n return '';\n }\n }\n\n /**\n * Build manifest paths from the actual file paths that were added\n * This ensures the manifest references the correct paths regardless of authoring tool\n */\n private buildManifestPaths(addedFiles: string[]): import('./manifest-updater.js').ManifestPaths {\n const paths: import('./manifest-updater.js').ManifestPaths = {};\n\n for (const filePath of addedFiles) {\n if (this.config.skin && filePath.endsWith(`skin/${this.config.skin}/style.css`)) {\n paths.skinCss = filePath;\n } else if (this.config.skin && filePath.endsWith(`skin/${this.config.skin}/script.js`)) {\n paths.skinJs = filePath;\n } else if (filePath.endsWith(this.config.cssBefore.filename)) {\n paths.cssBefore = filePath;\n } else if (filePath.endsWith(this.config.cssAfter.filename)) {\n paths.cssAfter = filePath;\n } else if (filePath.endsWith(this.config.jsBefore.filename)) {\n paths.jsBefore = filePath;\n } else if (filePath.endsWith(this.config.jsAfter.filename)) {\n paths.jsAfter = filePath;\n }\n }\n\n return paths;\n }\n\n /**\n * Generate assets from all enabled plugins\n */\n private generatePluginAssets(): GeneratedPluginAssets {\n const plugins = this.config.plugins || {};\n const enabledPlugins = Object.entries(plugins).filter(([_, cfg]) => cfg.enabled);\n\n if (enabledPlugins.length === 0) {\n return { cssBefore: '', cssAfter: '', jsBefore: '', jsAfter: '' };\n }\n\n console.log(`[Patcher] Generating assets for ${enabledPlugins.length} enabled plugin(s)`);\n return pluginRegistry.generateAssets(plugins);\n }\n\n /**\n * Add local fallback files to the ZIP\n * Priority: options > fetched > defaults\n * Plugin assets are appended after the main content\n */\n private addFallbackFiles(\n zip: AdmZip,\n options: PatchOptions,\n fetched: FetchedFallbacks = {},\n toolInfo?: AuthoringToolInfo\n ): string[] {\n const added: string[] = [];\n const baseFolder = toolInfo ? this.getFallbackBaseFolder(toolInfo) : 'scormcontent';\n const basePath = baseFolder ? `${baseFolder}/` : '';\n const cssFolder = `${basePath}${this.config.localFolders.css}`;\n const jsFolder = `${basePath}${this.config.localFolders.js}`;\n\n // Generate plugin assets\n const pluginAssets = this.generatePluginAssets();\n const hasPluginCssBefore = pluginAssets.cssBefore.length > 0;\n const hasPluginCssAfter = pluginAssets.cssAfter.length > 0;\n const hasPluginJsBefore = pluginAssets.jsBefore.length > 0;\n const hasPluginJsAfter = pluginAssets.jsAfter.length > 0;\n\n // CSS Before (priority: options > fetched > default) + plugins\n if (this.config.cssBefore.enabled) {\n const path = `${cssFolder}/${this.config.cssBefore.filename}`;\n let content = options.cssBeforeContent ?? fetched.cssBefore ?? DEFAULT_CSS_BEFORE;\n if (hasPluginCssBefore) {\n content += '\\n\\n/* === PLUGIN CSS (Before) === */\\n' + pluginAssets.cssBefore;\n console.log(`[Patcher] Added plugin CSS (before) to ${path}`);\n }\n zip.addFile(path, Buffer.from(content, 'utf-8'));\n added.push(path);\n if (fetched.cssBefore) console.log(`[Patcher] Using fetched content for ${path}`);\n }\n\n // CSS After (priority: options > fetched > default) + plugins\n if (this.config.cssAfter.enabled) {\n const path = `${cssFolder}/${this.config.cssAfter.filename}`;\n let content = options.cssAfterContent ?? fetched.cssAfter ?? DEFAULT_CSS_AFTER;\n if (hasPluginCssAfter) {\n content += '\\n\\n/* === PLUGIN CSS (After) === */\\n' + pluginAssets.cssAfter;\n console.log(`[Patcher] Added plugin CSS (after) to ${path}`);\n }\n zip.addFile(path, Buffer.from(content, 'utf-8'));\n added.push(path);\n if (fetched.cssAfter) console.log(`[Patcher] Using fetched content for ${path}`);\n }\n\n // JS Before (priority: options > fetched > default) + plugins\n if (this.config.jsBefore.enabled) {\n const path = `${jsFolder}/${this.config.jsBefore.filename}`;\n let content = options.jsBeforeContent ?? fetched.jsBefore ?? DEFAULT_JS_BEFORE;\n if (hasPluginJsBefore) {\n content += '\\n\\n// === PLUGIN JS (Before) ===\\n' + pluginAssets.jsBefore;\n console.log(`[Patcher] Added plugin JS (before) to ${path}`);\n }\n zip.addFile(path, Buffer.from(content, 'utf-8'));\n added.push(path);\n if (fetched.jsBefore) console.log(`[Patcher] Using fetched content for ${path}`);\n }\n\n // JS After (priority: options > fetched > default) + plugins\n if (this.config.jsAfter.enabled) {\n const path = `${jsFolder}/${this.config.jsAfter.filename}`;\n let content = options.jsAfterContent ?? fetched.jsAfter ?? DEFAULT_JS_AFTER;\n if (hasPluginJsAfter) {\n content += '\\n\\n// === PLUGIN JS (After) ===\\n' + pluginAssets.jsAfter;\n console.log(`[Patcher] Added plugin JS (after) to ${path}`);\n }\n zip.addFile(path, Buffer.from(content, 'utf-8'));\n added.push(path);\n if (fetched.jsAfter) console.log(`[Patcher] Using fetched content for ${path}`);\n }\n\n // Skin CSS fallback (fetched > placeholder)\n if (this.config.skin) {\n const skinCssPath = `${basePath}skin/${this.config.skin}/style.css`;\n const skinCssContent = fetched.skinCss ?? `/* PA-Patcher: Skin CSS (${this.config.skin}) */\\n`;\n zip.addFile(skinCssPath, Buffer.from(skinCssContent, 'utf-8'));\n added.push(skinCssPath);\n if (fetched.skinCss) console.log(`[Patcher] Using fetched skin CSS for ${skinCssPath}`);\n else console.log(`[Patcher] Added placeholder skin CSS: ${skinCssPath}`);\n\n const skinJsPath = `${basePath}skin/${this.config.skin}/script.js`;\n const skinJsContent = fetched.skinJs ?? `// PA-Patcher: Skin JS (${this.config.skin})\\n`;\n zip.addFile(skinJsPath, Buffer.from(skinJsContent, 'utf-8'));\n added.push(skinJsPath);\n if (fetched.skinJs) console.log(`[Patcher] Using fetched skin JS for ${skinJsPath}`);\n else console.log(`[Patcher] Added placeholder skin JS: ${skinJsPath}`);\n }\n\n return added;\n }\n\n /**\n * Get the current configuration\n */\n getConfig(): PatchAdamsConfig {\n return this.config;\n }\n\n /**\n * Convenience method to patch a buffer and return only the patched buffer.\n * Used by package-uploader integration.\n * @param buffer - Input ZIP file as Buffer\n * @returns Patched ZIP file as Buffer\n */\n async patchBuffer(buffer: Buffer, options?: PatchOptions): Promise<Buffer> {\n console.log(`[Patcher] patchBuffer called with ${buffer.length} bytes`);\n const { buffer: patchedBuffer, result } = await this.patch(buffer, options);\n console.log(`[Patcher] Patch result:`, JSON.stringify(result, null, 2));\n console.log(`[Patcher] Returning ${patchedBuffer.length} bytes`);\n return patchedBuffer;\n }\n}\n\nexport { HtmlInjector } from './html-injector.js';\nexport { StorylineHtmlInjector } from './storyline-html-injector.js';\nexport { ManifestUpdater, type ManifestPaths } from './manifest-updater.js';\n","import type AdmZip from 'adm-zip';\n\n/**\n * Comprehensive course metadata extracted from manifests\n */\nexport interface CourseMetadata {\n /** The Rise course ID (from manifest identifier) */\n courseId: string;\n /** Source of the course ID */\n courseIdSource: 'manifest' | 'tincan' | 'cmi5' | 'fallback';\n /** Package format */\n format: 'scorm12' | 'scorm2004-3' | 'scorm2004-4' | 'cmi5' | 'xapi' | 'aicc' | 'unknown';\n /** Course title */\n title: string | null;\n /** Course description */\n description: string | null;\n /** Organization/item identifier within the manifest */\n organizationId: string | null;\n /** Launch URL/entry point */\n launchUrl: string | null;\n /** Mastery score (if specified) */\n masteryScore: number | null;\n /** Duration/time limit (if specified) */\n duration: string | null;\n /** Language code */\n language: string | null;\n /** Version info from manifest */\n version: string | null;\n /** Timestamp when this metadata was extracted */\n extractedAt: string;\n /** Unique ID generated for this specific patch operation */\n patchId?: string;\n}\n\n/**\n * Extract comprehensive course metadata from a course package\n */\nexport function extractCourseMetadata(zip: AdmZip, format: string): CourseMetadata {\n const metadata: CourseMetadata = {\n courseId: 'unknown',\n courseIdSource: 'fallback',\n format: format as CourseMetadata['format'],\n title: null,\n description: null,\n organizationId: null,\n launchUrl: null,\n masteryScore: null,\n duration: null,\n language: null,\n version: null,\n extractedAt: new Date().toISOString(),\n };\n\n // Try to extract from imsmanifest.xml (SCORM)\n const manifest = zip.getEntry('imsmanifest.xml');\n if (manifest) {\n extractFromImsManifest(manifest.getData().toString('utf-8'), metadata);\n }\n\n // Try to extract from tincan.xml (xAPI)\n const tincan = zip.getEntry('tincan.xml');\n if (tincan) {\n extractFromTincan(tincan.getData().toString('utf-8'), metadata);\n }\n\n // Try to extract from cmi5.xml\n const cmi5 = zip.getEntry('cmi5.xml');\n if (cmi5) {\n extractFromCmi5(cmi5.getData().toString('utf-8'), metadata);\n }\n\n // Try to extract title from index.html as fallback\n if (!metadata.title) {\n const indexHtml = zip.getEntry('scormcontent/index.html');\n if (indexHtml) {\n const content = indexHtml.getData().toString('utf-8');\n const titleMatch = content.match(/<title>([^<]+)<\\/title>/i);\n if (titleMatch) {\n metadata.title = titleMatch[1].trim();\n }\n }\n }\n\n return metadata;\n}\n\n/**\n * Extract metadata from imsmanifest.xml (SCORM 1.2 and 2004)\n */\nfunction extractFromImsManifest(content: string, metadata: CourseMetadata): void {\n // Course ID from manifest identifier\n const identifierMatch = content.match(/<manifest[^>]+identifier\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (identifierMatch) {\n metadata.courseId = identifierMatch[1];\n metadata.courseIdSource = 'manifest';\n }\n\n // Version from manifest version attribute\n const versionMatch = content.match(/<manifest[^>]+version\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (versionMatch) {\n metadata.version = versionMatch[1];\n }\n\n // Title - try multiple locations\n // 1. From <title> within <organization>\n const orgTitleMatch = content.match(/<organization[^>]*>[\\s\\S]*?<title>([^<]+)<\\/title>/i);\n if (orgTitleMatch) {\n metadata.title = orgTitleMatch[1].trim();\n }\n\n // 2. From <lom:title> or <general><title>\n if (!metadata.title) {\n const lomTitleMatch = content.match(/<(?:lom:)?title>\\s*<(?:lom:)?langstring[^>]*>([^<]+)<\\/(?:lom:)?langstring>/i);\n if (lomTitleMatch) {\n metadata.title = lomTitleMatch[1].trim();\n }\n }\n\n // Description from LOM metadata\n const descMatch = content.match(/<(?:lom:)?description>\\s*<(?:lom:)?langstring[^>]*>([^<]+)<\\/(?:lom:)?langstring>/i);\n if (descMatch) {\n metadata.description = descMatch[1].trim();\n }\n\n // Organization identifier\n const orgIdMatch = content.match(/<organization[^>]+identifier\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (orgIdMatch) {\n metadata.organizationId = orgIdMatch[1];\n }\n\n // Launch URL from <resource> href\n const resourceMatch = content.match(/<resource[^>]+href\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (resourceMatch) {\n metadata.launchUrl = resourceMatch[1];\n }\n\n // Mastery score (SCORM 1.2 style)\n const masteryMatch = content.match(/<adlcp:masteryscore>([^<]+)<\\/adlcp:masteryscore>/i);\n if (masteryMatch) {\n const score = parseFloat(masteryMatch[1]);\n if (!isNaN(score)) {\n metadata.masteryScore = score;\n }\n }\n\n // Mastery score (SCORM 2004 style - completion threshold)\n if (metadata.masteryScore === null) {\n const thresholdMatch = content.match(/<imsss:minNormalizedMeasure>([^<]+)<\\/imsss:minNormalizedMeasure>/i);\n if (thresholdMatch) {\n const score = parseFloat(thresholdMatch[1]);\n if (!isNaN(score)) {\n metadata.masteryScore = score * 100; // Convert 0-1 to 0-100\n }\n }\n }\n\n // Duration/time limit\n const durationMatch = content.match(/<(?:adlcp:)?timelimitaction>([^<]+)<\\/(?:adlcp:)?timelimitaction>/i);\n if (durationMatch) {\n metadata.duration = durationMatch[1].trim();\n }\n\n // Language from xml:lang or lom:language\n const langMatch = content.match(/xml:lang\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (langMatch) {\n metadata.language = langMatch[1];\n }\n if (!metadata.language) {\n const lomLangMatch = content.match(/<(?:lom:)?language>([^<]+)<\\/(?:lom:)?language>/i);\n if (lomLangMatch) {\n metadata.language = lomLangMatch[1].trim();\n }\n }\n}\n\n/**\n * Extract metadata from tincan.xml (xAPI courses)\n */\nfunction extractFromTincan(content: string, metadata: CourseMetadata): void {\n // Activity ID as course ID\n const idMatch = content.match(/<activity[^>]+id\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (idMatch && metadata.courseIdSource === 'fallback') {\n metadata.courseId = idMatch[1];\n metadata.courseIdSource = 'tincan';\n }\n\n // Activity name/title\n const nameMatch = content.match(/<name[^>]*>([^<]+)<\\/name>/i);\n if (nameMatch && !metadata.title) {\n metadata.title = nameMatch[1].trim();\n }\n\n // Activity description\n const descMatch = content.match(/<description[^>]*>([^<]+)<\\/description>/i);\n if (descMatch && !metadata.description) {\n metadata.description = descMatch[1].trim();\n }\n\n // Launch URL\n const launchMatch = content.match(/<launch[^>]*>([^<]+)<\\/launch>/i);\n if (launchMatch) {\n metadata.launchUrl = launchMatch[1].trim();\n }\n}\n\n/**\n * Extract metadata from cmi5.xml\n */\nfunction extractFromCmi5(content: string, metadata: CourseMetadata): void {\n // Course ID\n const courseIdMatch = content.match(/<course[^>]+id\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (courseIdMatch && metadata.courseIdSource === 'fallback') {\n metadata.courseId = courseIdMatch[1];\n metadata.courseIdSource = 'cmi5';\n }\n\n // Course title\n const titleMatch = content.match(/<title[^>]*>([^<]+)<\\/title>/i);\n if (titleMatch && !metadata.title) {\n metadata.title = titleMatch[1].trim();\n }\n\n // Course description\n const descMatch = content.match(/<description[^>]*>([^<]+)<\\/description>/i);\n if (descMatch && !metadata.description) {\n metadata.description = descMatch[1].trim();\n }\n\n // AU launch URL\n const launchMatch = content.match(/<url[^>]*>([^<]+)<\\/url>/i);\n if (launchMatch) {\n metadata.launchUrl = launchMatch[1].trim();\n }\n\n // Mastery score from moveOn\n const moveOnMatch = content.match(/moveOn\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (moveOnMatch) {\n // cmi5 uses moveOn values like \"Passed\", \"Completed\", \"CompletedAndPassed\"\n // If PassedOrFailed is required, there's likely a mastery score\n if (moveOnMatch[1].toLowerCase().includes('passed')) {\n const masteryMatch = content.match(/masteryScore\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (masteryMatch) {\n const score = parseFloat(masteryMatch[1]);\n if (!isNaN(score)) {\n metadata.masteryScore = score;\n }\n }\n }\n }\n}\n","import type {\n PatchAdamsPlugin,\n PluginConfig,\n PluginsConfig,\n GeneratedPluginAssets,\n} from './types.js';\n\n/**\n * Plugin Registry for Patch-Adams\n *\n * Manages registration and asset generation for plugins.\n * Plugins are registered at module load time and invoked during patching\n * based on the configuration.\n */\nexport class PluginRegistry {\n private plugins: Map<string, PatchAdamsPlugin> = new Map();\n\n /**\n * Register a plugin with the registry.\n * Plugins should be registered at module initialization time.\n *\n * @param plugin - The plugin to register\n * @throws Error if a plugin with the same name is already registered\n */\n register(plugin: PatchAdamsPlugin): void {\n if (this.plugins.has(plugin.name)) {\n throw new Error(`Plugin \"${plugin.name}\" is already registered`);\n }\n this.plugins.set(plugin.name, plugin);\n console.log(`[PA-Plugins] Registered: ${plugin.name} v${plugin.version}`);\n }\n\n /**\n * Unregister a plugin by name.\n *\n * @param name - The plugin name to unregister\n * @returns true if the plugin was removed, false if it wasn't registered\n */\n unregister(name: string): boolean {\n const removed = this.plugins.delete(name);\n if (removed) {\n console.log(`[PA-Plugins] Unregistered: ${name}`);\n }\n return removed;\n }\n\n /**\n * Get a registered plugin by name.\n *\n * @param name - The plugin name\n * @returns The plugin or undefined if not found\n */\n getPlugin(name: string): PatchAdamsPlugin | undefined {\n return this.plugins.get(name);\n }\n\n /**\n * Get all registered plugins.\n *\n * @returns Array of all registered plugins\n */\n getAllPlugins(): PatchAdamsPlugin[] {\n return Array.from(this.plugins.values());\n }\n\n /**\n * Get names of all registered plugins.\n *\n * @returns Array of plugin names\n */\n getPluginNames(): string[] {\n return Array.from(this.plugins.keys());\n }\n\n /**\n * Check if a plugin is registered.\n *\n * @param name - The plugin name to check\n * @returns true if registered\n */\n hasPlugin(name: string): boolean {\n return this.plugins.has(name);\n }\n\n /**\n * Generate combined assets from all enabled plugins.\n *\n * @param pluginConfigs - Configuration for each plugin (keyed by plugin name)\n * @returns Combined CSS and JS assets from all enabled plugins\n */\n generateAssets(pluginConfigs: PluginsConfig): GeneratedPluginAssets {\n const result: GeneratedPluginAssets = {\n cssBefore: '',\n cssAfter: '',\n jsBefore: '',\n jsAfter: '',\n };\n\n for (const [pluginName, rawConfig] of Object.entries(pluginConfigs)) {\n // Skip if plugin is not enabled\n if (!rawConfig.enabled) {\n console.log(`[PA-Plugins] Skipping disabled plugin: ${pluginName}`);\n continue;\n }\n\n // Get the plugin\n const plugin = this.plugins.get(pluginName);\n if (!plugin) {\n console.warn(`[PA-Plugins] Plugin \"${pluginName}\" not found in registry, skipping`);\n continue;\n }\n\n try {\n // Validate configuration against plugin schema\n const validatedConfig = plugin.configSchema.parse(rawConfig) as PluginConfig;\n console.log(`[PA-Plugins] Generating assets for: ${pluginName}`);\n\n // Generate CSS\n if (plugin.generateCss) {\n const css = plugin.generateCss(validatedConfig);\n if (css.before) {\n result.cssBefore += `/* === Plugin: ${pluginName} === */\\n${css.before}\\n\\n`;\n }\n if (css.after) {\n result.cssAfter += `/* === Plugin: ${pluginName} === */\\n${css.after}\\n\\n`;\n }\n }\n\n // Generate JS\n if (plugin.generateJs) {\n const js = plugin.generateJs(validatedConfig);\n if (js.before) {\n result.jsBefore += `// === Plugin: ${pluginName} ===\\n${js.before}\\n\\n`;\n }\n if (js.after) {\n result.jsAfter += `// === Plugin: ${pluginName} ===\\n${js.after}\\n\\n`;\n }\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`[PA-Plugins] Error processing plugin \"${pluginName}\": ${message}`);\n // Continue with other plugins\n }\n }\n\n return result;\n }\n\n /**\n * Clear all registered plugins.\n * Useful for testing.\n */\n clear(): void {\n this.plugins.clear();\n console.log('[PA-Plugins] Registry cleared');\n }\n}\n\n/**\n * Global plugin registry instance.\n * Use this singleton to register and access plugins.\n */\nexport const pluginRegistry = new PluginRegistry();\n","#!/usr/bin/env node\nimport { Command } from 'commander';\nimport { readFileSync, writeFileSync, existsSync } from 'fs';\nimport { resolve, basename } from 'path';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { Patcher } from './patcher/index.js';\nimport { loadConfig, generateConfigTemplate, mergeWithDefaults } from './config/index.js';\nimport { PatchAdamsConfigSchema } from './config/schema.js';\n\nconst program = new Command();\n\nprogram\n .name('patch-adams')\n .description('Patch Rise course packages to inject remote CSS/JS with local fallback')\n .version('1.0.0');\n\nprogram\n .command('patch <input>')\n .description('Patch a Rise course ZIP file')\n .option('-o, --output <path>', 'Output file path (default: <input>-patched.zip)')\n .option('-c, --config <path>', 'Configuration file path (default: ./patch-adams.config.ts)')\n .option('--css-before <path>', 'Local CSS file for \"before\" fallback')\n .option('--css-after <path>', 'Local CSS file for \"after\" fallback')\n .option('--js-before <path>', 'Local JS file for \"before\" fallback')\n .option('--js-after <path>', 'Local JS file for \"after\" fallback')\n .option('--no-manifest', 'Skip updating manifest files')\n .action(async (input: string, options) => {\n const spinner = ora('Loading configuration...').start();\n\n try {\n // Load config\n let config;\n const configPath = options.config || './patch-adams.config.ts';\n\n if (existsSync(resolve(configPath))) {\n config = await loadConfig(configPath);\n spinner.text = 'Configuration loaded';\n } else {\n spinner.text = 'No config file found, using defaults';\n config = mergeWithDefaults({});\n }\n\n // Override manifest update setting\n if (options.manifest === false) {\n config = { ...config, updateManifests: false };\n }\n\n // Validate config\n const validatedConfig = PatchAdamsConfigSchema.parse(config);\n\n spinner.text = 'Reading input file...';\n\n // Read input ZIP\n const inputPath = resolve(input);\n if (!existsSync(inputPath)) {\n throw new Error(`Input file not found: ${inputPath}`);\n }\n const inputBuffer = readFileSync(inputPath);\n\n // Read optional CSS/JS files\n const patchOptions: {\n cssBeforeContent?: string;\n cssAfterContent?: string;\n jsBeforeContent?: string;\n jsAfterContent?: string;\n } = {};\n\n if (options.cssBefore) {\n patchOptions.cssBeforeContent = readFileSync(resolve(options.cssBefore), 'utf-8');\n }\n\n if (options.cssAfter) {\n patchOptions.cssAfterContent = readFileSync(resolve(options.cssAfter), 'utf-8');\n }\n\n if (options.jsBefore) {\n patchOptions.jsBeforeContent = readFileSync(resolve(options.jsBefore), 'utf-8');\n }\n\n if (options.jsAfter) {\n patchOptions.jsAfterContent = readFileSync(resolve(options.jsAfter), 'utf-8');\n }\n\n // Pass original package filename for LRS statement searchability\n (patchOptions as any).packageName = basename(inputPath);\n\n spinner.text = 'Patching package...';\n\n // Patch\n const patcher = new Patcher(validatedConfig);\n const { buffer, result } = await patcher.patch(inputBuffer, patchOptions);\n\n // Determine output path\n const outputPath =\n options.output || inputPath.replace(/\\.zip$/i, '-patched.zip');\n\n spinner.text = 'Writing output file...';\n writeFileSync(outputPath, buffer);\n\n spinner.succeed(chalk.green('Patching complete!'));\n\n // Display results\n console.log('');\n console.log(chalk.blue('Format detected:'), result.formatDisplayName);\n console.log(chalk.blue('Remote domain:'), validatedConfig.remoteDomain);\n console.log('');\n\n if (result.filesModified.length > 0) {\n console.log(chalk.blue('Files modified:'));\n result.filesModified.forEach((f) => console.log(` ${chalk.gray('•')} ${f}`));\n }\n\n if (result.filesAdded.length > 0) {\n console.log(chalk.blue('Files added:'));\n result.filesAdded.forEach((f) => console.log(` ${chalk.gray('•')} ${f}`));\n }\n\n if (result.warnings.length > 0) {\n console.log('');\n console.log(chalk.yellow('Warnings:'));\n result.warnings.forEach((w) => console.log(` ${chalk.yellow('!')} ${w}`));\n }\n\n console.log('');\n console.log(chalk.green('Output:'), outputPath);\n } catch (error) {\n spinner.fail(chalk.red('Patching failed'));\n console.error(\n chalk.red(error instanceof Error ? error.message : String(error))\n );\n process.exit(1);\n }\n });\n\nprogram\n .command('init')\n .description('Create a sample configuration file')\n .option('-o, --output <path>', 'Output path (default: ./patch-adams.config.ts)')\n .action((options) => {\n const outputPath = resolve(options.output || './patch-adams.config.ts');\n\n if (existsSync(outputPath)) {\n console.log(\n chalk.yellow(`Configuration file already exists: ${outputPath}`)\n );\n console.log(chalk.yellow('Use --output to specify a different path'));\n process.exit(1);\n }\n\n const template = generateConfigTemplate();\n writeFileSync(outputPath, template);\n console.log(chalk.green(`Configuration file created: ${outputPath}`));\n console.log('');\n console.log('Next steps:');\n console.log(` 1. Edit ${chalk.cyan(basename(outputPath))} to configure your settings`);\n console.log(` 2. Run ${chalk.cyan('patch-adams patch <input.zip>')} to patch a course`);\n });\n\nprogram\n .command('info <input>')\n .description('Display information about a Rise course package')\n .action(async (input: string) => {\n const spinner = ora('Analyzing package...').start();\n\n try {\n const inputPath = resolve(input);\n if (!existsSync(inputPath)) {\n throw new Error(`Input file not found: ${inputPath}`);\n }\n\n const inputBuffer = readFileSync(inputPath);\n const AdmZip = (await import('adm-zip')).default;\n const zip = new AdmZip(inputBuffer);\n\n const { FormatDetector } = await import('./detectors/index.js');\n const detector = new FormatDetector();\n const format = detector.detect(zip);\n const formatName = detector.getFormatDisplayName(format);\n\n spinner.succeed('Package analyzed');\n\n console.log('');\n console.log(chalk.blue('File:'), inputPath);\n console.log(chalk.blue('Format:'), formatName);\n console.log('');\n\n // Check for key files\n const keyFiles = [\n 'scormcontent/index.html',\n 'scormdriver/indexAPI.html',\n 'imsmanifest.xml',\n 'cmi5.xml',\n 'tincan.xml',\n ];\n\n console.log(chalk.blue('Key files:'));\n for (const file of keyFiles) {\n const exists = zip.getEntry(file) !== null;\n const icon = exists ? chalk.green('✓') : chalk.gray('○');\n console.log(` ${icon} ${file}`);\n }\n\n // Count total files\n const entries = zip.getEntries();\n console.log('');\n console.log(chalk.blue('Total files:'), entries.length);\n } catch (error) {\n spinner.fail(chalk.red('Analysis failed'));\n console.error(\n chalk.red(error instanceof Error ? error.message : String(error))\n );\n process.exit(1);\n }\n });\n\nprogram.parse();\n"]}
|