Last active
July 15, 2026 09:37
-
-
Save khrnchn/1da2ffaf49b1fc9f853f8d96bc3d062c to your computer and use it in GitHub Desktop.
scicom-slides-super-jacked-with-time-travel.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (function superJackedISpringAutoAdvance() { | |
| const CONFIG = { | |
| speed: 15, | |
| checkIntervalMs: 400, | |
| completionThreshold: 0.99, | |
| stableTicksRequired: 3, | |
| completionGraceMs: 1000, | |
| retryAfterMs: 3000, | |
| popupCooldownMs: 1000, | |
| debug: true | |
| }; | |
| const HOST = window; | |
| const playerWindow = window; | |
| const playerDocument = document; | |
| if (HOST.__superJackedISpringRunning) { | |
| console.log('[super-jacked] already running'); | |
| return; | |
| } | |
| HOST.__superJackedISpringRunning = true; | |
| const SELECTORS = { | |
| progressTrack: [ | |
| '.progressbar', | |
| '[class*="progressbar"]:not([class*="thumb"])' | |
| ], | |
| progressThumb: [ | |
| '.progressbar__thumb', | |
| '[class*="progressbar__thumb"]' | |
| ], | |
| nextButton: [ | |
| '.navigation-controls__button_next', | |
| '.universal-control-panel__button_next', | |
| 'button[aria-label*="next" i]', | |
| 'button[title*="next" i]' | |
| ], | |
| popupButton: [ | |
| '.message-box-buttons-panel__window-button', | |
| '.message-box-buttons__window-button', | |
| '[class*="message-box"] button', | |
| '[role="dialog"] button' | |
| ], | |
| popupLayer: [ | |
| '.popup-layer', | |
| '.popups-layer', | |
| '.message-box', | |
| '[role="dialog"]', | |
| '[class*="popup"]', | |
| '[class*="modal"]' | |
| ] | |
| }; | |
| const runtime = { | |
| state: 'watching', | |
| stableCount: 0, | |
| completedAt: 0, | |
| lastNextClickAt: 0, | |
| lastPopupClickAt: 0, | |
| lastProgress: null | |
| }; | |
| function log(...args) { | |
| if (CONFIG.debug) { | |
| console.log('[super-jacked]', ...args); | |
| } | |
| } | |
| function installTimeDilation() { | |
| if (playerWindow.__superJackedClockPatch) { | |
| log('time dilation already installed'); | |
| return playerWindow.__superJackedClockPatch; | |
| } | |
| const performanceObject = playerWindow.performance; | |
| const DateObject = playerWindow.Date; | |
| const originals = { | |
| performanceNow: performanceObject.now.bind(performanceObject), | |
| dateNow: DateObject.now.bind(DateObject), | |
| setTimeout: playerWindow.setTimeout.bind(playerWindow), | |
| setInterval: playerWindow.setInterval.bind(playerWindow), | |
| clearTimeout: playerWindow.clearTimeout.bind(playerWindow), | |
| clearInterval: playerWindow.clearInterval.bind(playerWindow) | |
| }; | |
| const realPerformanceStart = originals.performanceNow(); | |
| const realDateStart = originals.dateNow(); | |
| performanceObject.now = function acceleratedPerformanceNow() { | |
| const elapsed = | |
| originals.performanceNow() - realPerformanceStart; | |
| return realPerformanceStart + elapsed * CONFIG.speed; | |
| }; | |
| DateObject.now = function acceleratedDateNow() { | |
| const elapsed = | |
| originals.dateNow() - realDateStart; | |
| return realDateStart + elapsed * CONFIG.speed; | |
| }; | |
| playerWindow.setTimeout = function acceleratedSetTimeout( | |
| callback, | |
| delay, | |
| ...args | |
| ) { | |
| return originals.setTimeout( | |
| callback, | |
| Math.max(0, Number(delay) || 0) / CONFIG.speed, | |
| ...args | |
| ); | |
| }; | |
| playerWindow.setInterval = function acceleratedSetInterval( | |
| callback, | |
| delay, | |
| ...args | |
| ) { | |
| return originals.setInterval( | |
| callback, | |
| Math.max(0, Number(delay) || 0) / CONFIG.speed, | |
| ...args | |
| ); | |
| }; | |
| const patch = { | |
| restore() { | |
| performanceObject.now = originals.performanceNow; | |
| DateObject.now = originals.dateNow; | |
| playerWindow.setTimeout = originals.setTimeout; | |
| playerWindow.setInterval = originals.setInterval; | |
| playerWindow.clearTimeout = originals.clearTimeout; | |
| playerWindow.clearInterval = originals.clearInterval; | |
| delete playerWindow.__superJackedClockPatch; | |
| log('timing functions restored'); | |
| } | |
| }; | |
| playerWindow.__superJackedClockPatch = patch; | |
| log(`time dilation installed at ${CONFIG.speed}x`); | |
| return patch; | |
| } | |
| const clockPatch = installTimeDilation(); | |
| function queryFirst(selectors, root = playerDocument) { | |
| for (const selector of selectors) { | |
| try { | |
| const element = root.querySelector(selector); | |
| if (element) { | |
| return element; | |
| } | |
| } catch {} | |
| } | |
| return null; | |
| } | |
| function isVisible(element) { | |
| if (!element || !element.isConnected) { | |
| return false; | |
| } | |
| const style = playerWindow.getComputedStyle(element); | |
| const rect = element.getBoundingClientRect(); | |
| return ( | |
| style.display !== 'none' && | |
| style.visibility !== 'hidden' && | |
| Number(style.opacity) !== 0 && | |
| rect.width > 0 && | |
| rect.height > 0 | |
| ); | |
| } | |
| function isClickable(element) { | |
| return ( | |
| isVisible(element) && | |
| !element.disabled && | |
| !element.classList.contains('disabled') && | |
| !element.classList.contains('is-disabled') && | |
| element.getAttribute('aria-disabled') !== 'true' | |
| ); | |
| } | |
| function findClickable(selectors, root = playerDocument) { | |
| for (const selector of selectors) { | |
| let elements; | |
| try { | |
| elements = root.querySelectorAll(selector); | |
| } catch { | |
| continue; | |
| } | |
| for (const element of elements) { | |
| if (isClickable(element)) { | |
| return element; | |
| } | |
| } | |
| } | |
| return null; | |
| } | |
| function fireClick(element) { | |
| if (!isClickable(element)) { | |
| return false; | |
| } | |
| try { | |
| element.scrollIntoView({ | |
| block: 'center', | |
| inline: 'center', | |
| behavior: 'auto' | |
| }); | |
| const rect = element.getBoundingClientRect(); | |
| const clientX = rect.left + rect.width / 2; | |
| const clientY = rect.top + rect.height / 2; | |
| const options = { | |
| bubbles: true, | |
| cancelable: true, | |
| composed: true, | |
| view: playerWindow, | |
| clientX, | |
| clientY, | |
| screenX: playerWindow.screenX + clientX, | |
| screenY: playerWindow.screenY + clientY, | |
| button: 0, | |
| buttons: 1, | |
| pointerId: 1, | |
| pointerType: 'mouse', | |
| isPrimary: true | |
| }; | |
| if (typeof playerWindow.PointerEvent === 'function') { | |
| element.dispatchEvent( | |
| new playerWindow.PointerEvent('pointerover', options) | |
| ); | |
| element.dispatchEvent( | |
| new playerWindow.PointerEvent('pointerenter', options) | |
| ); | |
| element.dispatchEvent( | |
| new playerWindow.PointerEvent('pointerdown', options) | |
| ); | |
| } | |
| element.dispatchEvent( | |
| new playerWindow.MouseEvent('mouseover', options) | |
| ); | |
| element.dispatchEvent( | |
| new playerWindow.MouseEvent('mouseenter', options) | |
| ); | |
| element.dispatchEvent( | |
| new playerWindow.MouseEvent('mousedown', options) | |
| ); | |
| element.focus?.(); | |
| if (typeof playerWindow.PointerEvent === 'function') { | |
| element.dispatchEvent( | |
| new playerWindow.PointerEvent('pointerup', { | |
| ...options, | |
| buttons: 0 | |
| }) | |
| ); | |
| } | |
| element.dispatchEvent( | |
| new playerWindow.MouseEvent('mouseup', { | |
| ...options, | |
| buttons: 0 | |
| }) | |
| ); | |
| element.click(); | |
| return true; | |
| } catch (error) { | |
| console.error('[super-jacked] click failed:', error); | |
| return false; | |
| } | |
| } | |
| function getProgressFromThumb() { | |
| const track = queryFirst(SELECTORS.progressTrack); | |
| const thumb = queryFirst(SELECTORS.progressThumb); | |
| if (!track || !thumb) { | |
| return null; | |
| } | |
| const trackRect = track.getBoundingClientRect(); | |
| const thumbRect = thumb.getBoundingClientRect(); | |
| if (trackRect.width <= 0) { | |
| return null; | |
| } | |
| const thumbCentre = | |
| thumbRect.left + thumbRect.width / 2; | |
| return Math.min( | |
| Math.max( | |
| (thumbCentre - trackRect.left) / trackRect.width, | |
| 0 | |
| ), | |
| 1 | |
| ); | |
| } | |
| function getProgressFromAttributes() { | |
| const candidates = playerDocument.querySelectorAll( | |
| '[role="progressbar"], progress, [aria-valuenow]' | |
| ); | |
| for (const element of candidates) { | |
| if (!isVisible(element)) { | |
| continue; | |
| } | |
| if ( | |
| typeof playerWindow.HTMLProgressElement === 'function' && | |
| element instanceof playerWindow.HTMLProgressElement | |
| ) { | |
| const maximum = element.max || 1; | |
| if (maximum > 0) { | |
| return Math.min( | |
| Math.max(element.value / maximum, 0), | |
| 1 | |
| ); | |
| } | |
| } | |
| const current = Number( | |
| element.getAttribute('aria-valuenow') | |
| ); | |
| const minimum = Number( | |
| element.getAttribute('aria-valuemin') ?? 0 | |
| ); | |
| const maximum = Number( | |
| element.getAttribute('aria-valuemax') ?? 100 | |
| ); | |
| if ( | |
| Number.isFinite(current) && | |
| Number.isFinite(minimum) && | |
| Number.isFinite(maximum) && | |
| maximum > minimum | |
| ) { | |
| return Math.min( | |
| Math.max( | |
| (current - minimum) / (maximum - minimum), | |
| 0 | |
| ), | |
| 1 | |
| ); | |
| } | |
| } | |
| return null; | |
| } | |
| function getProgress() { | |
| return ( | |
| getProgressFromThumb() ?? | |
| getProgressFromAttributes() | |
| ); | |
| } | |
| function getButtonText(button) { | |
| return ( | |
| button.textContent || | |
| button.getAttribute('aria-label') || | |
| button.getAttribute('title') || | |
| '' | |
| ) | |
| .trim() | |
| .toLowerCase(); | |
| } | |
| function findPopupButton() { | |
| const preferredTexts = [ | |
| 'ok', | |
| 'continue', | |
| 'proceed', | |
| 'yes' | |
| ]; | |
| for (const selector of SELECTORS.popupButton) { | |
| let buttons; | |
| try { | |
| buttons = playerDocument.querySelectorAll(selector); | |
| } catch { | |
| continue; | |
| } | |
| for (const desiredText of preferredTexts) { | |
| for (const button of buttons) { | |
| if ( | |
| isClickable(button) && | |
| getButtonText(button) === desiredText | |
| ) { | |
| return button; | |
| } | |
| } | |
| } | |
| } | |
| for (const popupSelector of SELECTORS.popupLayer) { | |
| let layers; | |
| try { | |
| layers = playerDocument.querySelectorAll(popupSelector); | |
| } catch { | |
| continue; | |
| } | |
| for (const layer of layers) { | |
| if (!isVisible(layer)) { | |
| continue; | |
| } | |
| const buttons = layer.querySelectorAll( | |
| 'button, [role="button"]' | |
| ); | |
| for (const desiredText of preferredTexts) { | |
| for (const button of buttons) { | |
| if ( | |
| isClickable(button) && | |
| getButtonText(button) === desiredText | |
| ) { | |
| return button; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return null; | |
| } | |
| function resetCompletionState() { | |
| runtime.state = 'watching'; | |
| runtime.stableCount = 0; | |
| runtime.completedAt = 0; | |
| } | |
| function clickPopupIfPresent(now) { | |
| if ( | |
| now - runtime.lastPopupClickAt < | |
| CONFIG.popupCooldownMs | |
| ) { | |
| return false; | |
| } | |
| const button = findPopupButton(); | |
| if (!button) { | |
| return false; | |
| } | |
| const text = getButtonText(button) || 'popup button'; | |
| if (fireClick(button)) { | |
| runtime.lastPopupClickAt = now; | |
| resetCompletionState(); | |
| log(`dismissed popup: "${text}"`); | |
| return true; | |
| } | |
| return false; | |
| } | |
| function clickNext() { | |
| const button = findClickable(SELECTORS.nextButton); | |
| if (!button) { | |
| log('Next button not currently available'); | |
| return false; | |
| } | |
| if (fireClick(button)) { | |
| log('clicked Next'); | |
| return true; | |
| } | |
| return false; | |
| } | |
| function tick() { | |
| if (!HOST.__superJackedISpringRunning) { | |
| return; | |
| } | |
| /* | |
| * This uses the original unpatched real clock. | |
| * Otherwise cooldowns would also run 15× faster. | |
| */ | |
| const realNow = | |
| clockPatch.realDateNow?.() ?? | |
| new globalThis.Date().getTime(); | |
| if (clickPopupIfPresent(realNow)) { | |
| return; | |
| } | |
| const progress = getProgress(); | |
| runtime.lastProgress = progress; | |
| if (progress === null) { | |
| log(`progress unavailable | state=${runtime.state}`); | |
| return; | |
| } | |
| log( | |
| `${(progress * 100).toFixed(1)}%`, | |
| `state=${runtime.state}`, | |
| `stable=${runtime.stableCount}` | |
| ); | |
| if (progress < 0.5) { | |
| resetCompletionState(); | |
| return; | |
| } | |
| if (progress >= CONFIG.completionThreshold) { | |
| runtime.stableCount += 1; | |
| } else { | |
| runtime.stableCount = 0; | |
| if (runtime.state !== 'clicked') { | |
| runtime.state = 'watching'; | |
| runtime.completedAt = 0; | |
| } | |
| } | |
| if ( | |
| runtime.state === 'watching' && | |
| runtime.stableCount >= CONFIG.stableTicksRequired | |
| ) { | |
| runtime.state = 'waiting'; | |
| runtime.completedAt = realNow; | |
| log('completion stable; grace period started'); | |
| return; | |
| } | |
| if ( | |
| runtime.state === 'waiting' && | |
| realNow - runtime.completedAt >= | |
| CONFIG.completionGraceMs | |
| ) { | |
| if (clickNext()) { | |
| runtime.state = 'clicked'; | |
| runtime.lastNextClickAt = realNow; | |
| } | |
| return; | |
| } | |
| if ( | |
| runtime.state === 'clicked' && | |
| realNow - runtime.lastNextClickAt >= | |
| CONFIG.retryAfterMs | |
| ) { | |
| if (progress >= CONFIG.completionThreshold) { | |
| log('page did not advance; retrying Next'); | |
| if (clickNext()) { | |
| runtime.lastNextClickAt = realNow; | |
| } | |
| } else { | |
| resetCompletionState(); | |
| } | |
| } | |
| } | |
| /* | |
| * Keep a reference to the real clock because Date.now is patched. | |
| */ | |
| clockPatch.realDateNow = | |
| playerWindow.__superJackedClockPatch | |
| ? playerWindow.__superJackedClockPatch.realDateNow | |
| : null; | |
| /* | |
| * The monitor itself uses the original timer, not the patched timer. | |
| */ | |
| const realMonitorTimer = | |
| playerWindow.__superJackedOriginalSetInterval || | |
| playerWindow.setInterval; | |
| HOST.__superJackedISpringTimer = | |
| realMonitorTimer( | |
| tick, | |
| CONFIG.checkIntervalMs | |
| ); | |
| HOST.__stopSuperJackedISpring = function () { | |
| clearInterval(HOST.__superJackedISpringTimer); | |
| HOST.__superJackedISpringTimer = null; | |
| HOST.__superJackedISpringRunning = false; | |
| clockPatch.restore(); | |
| delete HOST.__stopSuperJackedISpring; | |
| console.log('[super-jacked] stopped'); | |
| }; | |
| tick(); | |
| console.log( | |
| `[super-jacked] iframe-native mode running at ${CONFIG.speed}x.`, | |
| 'Stop with window.__stopSuperJackedISpring()' | |
| ); | |
| })(); |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
latest version is not tested yet, i think can do if got
document.querySelector('#scorm_object'), and to stop -window.__stopSuperJackedISpring()