(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.`);
})();