(function() { // --- CONFIGURATION --- const seekId = 'seek'; const nextId = 'next'; const targetClass = 'progress-bar cs-seekcontrol'; // --------------------- const seekNode = document.getElementById(seekId); if (!seekNode) { console.warn(`[Enforcer] Element with ID "${seekId}" not found on this page. Proceeding with video, button, and interaction controllers.`); } // --- NUCLEAR ACCESSIBILITY BYPASS --- // Override HTMLElement.prototype.focus so Storyline cannot programmatically force focus // and trigger the ARIA descendant crash loop. const originalFocus = HTMLElement.prototype.focus; HTMLElement.prototype.focus = function(options) { // If it's a storyline slide element, ignore the focus call entirely if (this.classList.contains('slide-object') || this.closest('.slide-object')) { return; } return originalFocus.call(this, options); }; // Helper: Handles non-video user interactions like Accordions and Flashcards conditionally const handleAccordionsAndFlashcards = () => { // Fix: Instead of forcing CSS, physically CLICK the accordion headers to trigger tracking events const accordionButtons = document.querySelectorAll('button.blocks-accordion__header'); if (accordionButtons.length > 0) { accordionButtons.forEach(btn => { // If the accordion is currently closed (aria-expanded is false or missing) and hasn't been handled yet if (btn.getAttribute('aria-expanded') !== 'true' && !btn.dataset.enforcerClicked) { console.log("[Enforcer] Simulating click on accordion header to trigger tracking..."); // Mark it immediately to prevent rapid multi-click loops during execution btn.dataset.enforcerClicked = "true"; // Trigger a complete hardware-like click sequence simulateTrueClick(btn); } }); } // Automatically flip flashcards (Once per unique element) const flashcards = document.querySelectorAll('.flashcard-side-flip__btn.flashcard-side-flip__btn--prefers-keyboard'); if (flashcards.length > 0) { flashcards.forEach(flashcard => { if (!flashcard.dataset.enforcerClicked) { console.log("[Enforcer] Flipping flashcard..."); flashcard.dataset.enforcerClicked = "true"; simulateTrueClick(flashcard); } }); } }; // Helper: Constantly checks for video, handles playback, skips to end, and clicks page buttons const handleVideoAndNavigation = () => { // 1. Check for bottom continue button and click if found const bottomButton = document.getElementsByClassName('continue-btn brand--ui')[0]; if (bottomButton) { console.log("[Enforcer] Bottom Page Button Found. Clicking..."); bottomButton.click(); } // 2. Check for process right arrow buttons and click them const processArrows = document.querySelectorAll('.process-arrow.process-arrow--right'); processArrows.forEach(arrow => { const isDisabled = arrow.hasAttribute('disabled') || arrow.classList.contains('is-disabled'); if (!isDisabled) { console.log("[Enforcer] Process Right Arrow Found. Clicking..."); simulateTrueClick(arrow); } }); // 3. Check for carousel control buttons and click each exactly ONCE const carouselButtons = document.querySelectorAll('.carousel-controls-item-btn'); carouselButtons.forEach(btn => { const isDisabled = btn.hasAttribute('disabled') || btn.classList.contains('is-disabled') || btn.getAttribute('aria-disabled') === 'true'; if (!isDisabled && !btn.dataset.enforcerClicked) { console.log("[Enforcer] Carousel Control Button Found. Clicking once..."); btn.dataset.enforcerClicked = "true"; simulateTrueClick(btn); } }); // 4. NEW: Check for visible labeled graphic markers and click each exactly ONCE const graphicMarkers = document.querySelectorAll('.labeled-graphic-marker.labeled-graphic-marker--visible'); graphicMarkers.forEach(marker => { const isDisabled = marker.hasAttribute('disabled') || marker.classList.contains('is-disabled'); if (!isDisabled && !marker.dataset.enforcerClicked) { console.log("[Enforcer] Labeled Graphic Marker Found. Clicking once..."); marker.dataset.enforcerClicked = "true"; simulateTrueClick(marker); } }); // 5. Locate the HTML5 Video element const video = document.querySelector('video'); if (video) { // Check if we have already dealt with this specific video element if (!video.dataset.enforcerProcessed) { // Click the Big Play button if it's visible on screen (Only once) const videoPlayButton = document.getElementsByClassName('vjs-big-play-button')[0]; if (videoPlayButton && videoPlayButton.style.display !== 'none') { console.log("[Enforcer] Video Play Button Found. Triggering play..."); videoPlayButton.click(); } // Fast-forward the video near the end (Only once) const targetTime = video.duration * 0.98; if (video.duration && video.currentTime < targetTime) { console.log(`[Enforcer] Skipping video to near end (${targetTime.toFixed(2)}s / ${video.duration.toFixed(2)}s)`); video.currentTime = targetTime; // Mark this video as processed so we don't snap it backward or click it again video.dataset.enforcerProcessed = "true"; } } } }; // Global cleaner to ruthlessly strip aria-hidden dynamically const openAriaGates = () => { if (document.activeElement && document.activeElement !== document.body) { document.activeElement.blur(); } const elements = document.querySelectorAll('[aria-hidden="true"], [style*="pointer-events: none"]'); elements.forEach(el => { el.removeAttribute('aria-hidden'); el.style.pointerEvents = 'auto'; }); // Run interaction and video modules safely alongside the cleaner loop handleVideoAndNavigation(); handleAccordionsAndFlashcards(); }; // Run the cleaner and all sub-handlers aggressively every 50ms in the background const globalCleaner = setInterval(openAriaGates, 50); // ------------------------------------ // Helper function to simulate a realistic mouse click sequence const simulateTrueClick = (element) => { if (!element) return; const eventOptions = { bubbles: true, cancelable: true, view: window }; element.dispatchEvent(new MouseEvent('mousedown', eventOptions)); element.dispatchEvent(new MouseEvent('mouseup', eventOptions)); element.dispatchEvent(new MouseEvent('click', eventOptions)); }; // Fast-forwards the underlying progress input to max const maximizeProgress = (label = "Timeline") => { const rangeInput = document.querySelector('input[data-ref="progressBar"]'); if (rangeInput) { const maxVal = parseInt(rangeInput.max) || 65250; rangeInput.value = maxVal; rangeInput.dispatchEvent(new Event('input', { bubbles: true })); rangeInput.dispatchEvent(new Event('change', { bubbles: true })); console.log(`[Enforcer] [${label}] Progress bar forced to maximum (${maxVal}).`); return true; } return false; }; // Handles picking an answer and submitting the quiz slide safely const bypassQuizGate = () => { openAriaGates(); const vectors = Array.from(document.querySelectorAll('.slide-object-vectorshape')); const submitButton = vectors.find(el => el.getAttribute('data-acc-text')?.toUpperCase() === 'SUBMIT' || el.innerText?.toUpperCase() === 'SUBMIT' ); const firstChoice = vectors.find(el => el.matches('[cursor-hover]') && el !== submitButton && !el.getAttribute('data-acc-text')?.toUpperCase().includes('SUBMIT') ); if (firstChoice) { console.log(`[Enforcer] Clicking quiz option: "${firstChoice.getAttribute('data-acc-text') || 'Option'}"`); simulateTrueClick(firstChoice); } if (submitButton) { // Force interaction availability immediately before clicking submitButton.removeAttribute('aria-hidden'); submitButton.style.pointerEvents = 'auto'; console.log("[Enforcer] Clicking SUBMIT button."); simulateTrueClick(submitButton); return true; } return false; }; // Looks for standard Next button OR the Feedback Layer's Continue button const clickNextOrContinue = () => { let attempts = 0; const maxAttempts = 15; const delayBetweenClicks = 300; console.log("[Enforcer] Starting search for Next / Continue buttons..."); const clickInterval = setInterval(() => { attempts++; openAriaGates(); const freshNextButton = document.getElementById(nextId); const continueButton = Array.from(document.querySelectorAll('.slide-object-vectorshape')) .find(el => el.getAttribute('data-acc-text')?.toUpperCase() === 'CONTINUE'); if (continueButton) { console.log(`[Enforcer] Found pop-up Continue button! Clicking... (Attempt ${attempts})`); maximizeProgress("Pop-up Layer"); continueButton.removeAttribute('aria-hidden'); continueButton.style.pointerEvents = 'auto'; simulateTrueClick(continueButton); } else if (freshNextButton) { console.log(`[Enforcer] Found standard Next button. Clicking... (Attempt ${attempts})`); simulateTrueClick(freshNextButton); } if (attempts >= maxAttempts) { clearInterval(clickInterval); console.log("[Enforcer] Finished navigation attempt sequence."); } }, delayBetweenClicks); }; // Main execution flow const enforceClassAndProgress = () => { // Guard clause: skip this sequence entirely if seekNode doesn't exist on this slide/page if (!seekNode) return; if (seekNode.className !== targetClass) { console.log(`[Enforcer] Class variation detected. Starting cascade...`); seekNode.className = targetClass; openAriaGates(); const progressResetSuccessful = maximizeProgress("Main Slide"); if (progressResetSuccessful) { setTimeout(() => { const isQuiz = bypassQuizGate(); if (isQuiz) { setTimeout(() => { maximizeProgress("Feedback Pop-up"); clickNextOrContinue(); }, 600); } else { setTimeout(clickNextOrContinue, 250); } }, 150); } } }; // Initialize the main execution flow if possible enforceClassAndProgress(); // Only configure and attach the MutationObserver if seekNode actually exists if (seekNode) { const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { if (mutation.type === 'attributes' && mutation.attributeName === 'class') { observer.disconnect(); enforceClassAndProgress(); startObserving(); } } }); const config = { attributes: true, attributeFilter: ['class'] }; function startObserving() { observer.observe(seekNode, config); } startObserving(); } console.log(`[Enforcer] Nuclear Mutation-Proof Engine Running.`); })();