|
// ==UserScript== |
|
// @name Not-a-Robot Level Navigator |
|
// @version 1.4.1 |
|
// @description Cheat for neal.fun's Not-a-Robot game to jump to any level directly. |
|
// @author Adas |
|
// @match https://neal.fun/not-a-robot/ |
|
// @grant none |
|
// @tag cheat |
|
// @homepageURL https://gist.github.com/adasThePro/1453a2c8ed66c30f3e447dbe70884cdb#file-readme-md |
|
// @source https://gist.github.com/adasThePro/1453a2c8ed66c30f3e447dbe70884cdb#file-not-a-robot-cheat-js |
|
// @updateURL https://gist.githubusercontent.com/adasThePro/1453a2c8ed66c30f3e447dbe70884cdb/raw/meta.js |
|
// @downloadURL https://gist.githubusercontent.com/adasThePro/1453a2c8ed66c30f3e447dbe70884cdb/raw/not-a-robot-cheat.js |
|
// @supportURL https://t.me/Woopes |
|
// ==/UserScript== |
|
|
|
(function () { |
|
'use strict'; |
|
|
|
if (window.levelNavigatorInitialized) return; |
|
window.levelNavigatorInitialized = true; |
|
|
|
const CONFIG = { |
|
VERSION: '1.4.1', |
|
LEVEL_RANGE: { MIN: 1, MAX: 48 }, |
|
STORAGE_KEY: 'not-a-robot-level', |
|
|
|
SHORTCUTS: { |
|
TOGGLE_NAVIGATOR: 'KeyL', |
|
SHOW_SHORTCUTS: 'Slash', |
|
PREVIOUS_LEVEL: 'ArrowLeft', |
|
NEXT_LEVEL: 'ArrowRight' |
|
}, |
|
|
|
SELECTORS: { |
|
RESET_BUTTON: '.reset', |
|
CHEAT_BUTTON: '.level-nav-btn', |
|
FOOTER_TEXT: '.site-footer-text', |
|
SITE_LEVEL: '.site-level span', |
|
NAV_NOTIFICATION: '.nav-notification', |
|
LEVEL_NAV_OVERLAY: '.level-nav-overlay', |
|
SHORTCUTS_MENU_OVERLAY: '.shortcuts-menu-overlay' |
|
}, |
|
|
|
COLORS: { |
|
PRIMARY: '#3b82f6', |
|
PRIMARY_HOVER: '#2563eb', |
|
SUCCESS: '#10b981', |
|
SUCCESS_HOVER: '#059669', |
|
DANGER: '#ef4444', |
|
DANGER_HOVER: '#dc2626', |
|
GRAY_50: '#f9fafb', |
|
GRAY_100: '#f3f4f6', |
|
GRAY_200: '#e5e7eb', |
|
GRAY_300: '#d1d5db', |
|
GRAY_400: '#9ca3af', |
|
GRAY_500: '#6b7280', |
|
GRAY_600: '#4b5563', |
|
GRAY_700: '#374151', |
|
GRAY_800: '#1f2937', |
|
GRAY_900: '#111827', |
|
WHITE: '#ffffff' |
|
}, |
|
|
|
TIMING: { |
|
BUTTON_CHECK_INTERVAL: 200, |
|
MAX_ATTEMPTS: 30, |
|
INITIAL_DELAY: 300, |
|
NOTIFICATION_DURATION: 2500, |
|
ANIMATION_DURATION: 200 |
|
} |
|
}; |
|
|
|
/* ----------------- Utilities ----------------- */ |
|
|
|
class DOMUtils { |
|
static _fragmentCache = new Map(); |
|
|
|
static createElement(tag, className = '', styles = {}, attributes = {}) { |
|
const el = document.createElement(tag); |
|
if (className) el.className = className; |
|
if (Object.keys(styles).length) Object.assign(el.style, styles); |
|
if (Object.keys(attributes).length) { |
|
for (const [k, v] of Object.entries(attributes)) el.setAttribute(k, v); |
|
} |
|
return el; |
|
} |
|
|
|
static safeRemove(el) { |
|
el?.remove(); |
|
} |
|
|
|
static queryOrCache(selector) { |
|
return document.querySelector(selector); |
|
} |
|
|
|
static createFragment(html) { |
|
if (this._fragmentCache.has(html)) return this._fragmentCache.get(html).cloneNode(true); |
|
const template = document.createElement('template'); |
|
template.innerHTML = html; |
|
const fragment = template.content.cloneNode(true); |
|
this._fragmentCache.set(html, fragment); |
|
return fragment.cloneNode(true); |
|
} |
|
} |
|
|
|
class Validator { |
|
static validateLevel(value) { |
|
const level = parseInt(value, 10); |
|
const { MIN, MAX } = CONFIG.LEVEL_RANGE; |
|
if (isNaN(level)) return { isValid: false, errorMessage: 'Please enter a valid number' }; |
|
if (level < MIN || level > MAX) return { isValid: false, errorMessage: `Level must be between ${MIN} and ${MAX}` }; |
|
return { isValid: true, level }; |
|
} |
|
} |
|
|
|
class StorageManager { |
|
static _cachedLevel = null; |
|
static _isStorageAvailable = null; |
|
|
|
static _checkStorageAvailability() { |
|
if (this._isStorageAvailable !== null) return this._isStorageAvailable; |
|
try { |
|
const test = '__storage_test__'; |
|
localStorage.setItem(test, test); |
|
localStorage.removeItem(test); |
|
this._isStorageAvailable = true; |
|
} catch { |
|
this._isStorageAvailable = false; |
|
} |
|
return this._isStorageAvailable; |
|
} |
|
|
|
static setLevel(level) { |
|
if (!this._checkStorageAvailability()) { |
|
throw new Error('Storage unavailable. Check browser settings.'); |
|
} |
|
|
|
const value = String(level - 1); |
|
localStorage.setItem(CONFIG.STORAGE_KEY, value); |
|
this._cachedLevel = level; |
|
} |
|
|
|
static getLevel() { |
|
if (this._cachedLevel !== null) return this._cachedLevel; |
|
|
|
if (!this._checkStorageAvailability()) return 1; |
|
|
|
const stored = localStorage.getItem(CONFIG.STORAGE_KEY); |
|
const level = stored ? parseInt(stored, 10) + 1 : 1; |
|
this._cachedLevel = level; |
|
return level; |
|
} |
|
|
|
static getCurrentLevelFromDOM() { |
|
const el = DOMUtils.queryOrCache(CONFIG.SELECTORS.SITE_LEVEL); |
|
if (el) { |
|
const match = el.textContent.match(/Level\s*(\d+)/i); |
|
if (match) { |
|
const level = parseInt(match[1], 10); |
|
this._cachedLevel = level; |
|
return level; |
|
} |
|
} |
|
return this.getLevel(); |
|
} |
|
|
|
static clearCache() { |
|
this._cachedLevel = null; |
|
} |
|
} |
|
|
|
class NotificationManager { |
|
static _currentNotification = null; |
|
static _timeoutId = null; |
|
|
|
static show(message, type = 'info') { |
|
if (this._currentNotification) { |
|
clearTimeout(this._timeoutId); |
|
DOMUtils.safeRemove(this._currentNotification); |
|
} |
|
|
|
const bgColors = { |
|
info: CONFIG.COLORS.GRAY_800, |
|
success: CONFIG.COLORS.SUCCESS, |
|
error: CONFIG.COLORS.DANGER |
|
}; |
|
|
|
const n = DOMUtils.createElement('div', 'nav-notification', { |
|
position: 'fixed', |
|
bottom: '20px', |
|
left: '50%', |
|
transform: 'translateX(-50%) translateY(100%)', |
|
background: bgColors[type], |
|
color: CONFIG.COLORS.WHITE, |
|
padding: '12px 16px', |
|
borderRadius: '8px', |
|
fontSize: '14px', |
|
fontFamily: 'Segoe UI, system-ui, -apple-system, sans-serif', |
|
zIndex: '1000000', |
|
boxShadow: '0 8px 16px rgba(0,0,0,0.2)', |
|
transition: `transform ${CONFIG.TIMING.ANIMATION_DURATION}ms ease-out`, |
|
maxWidth: '320px', |
|
wordWrap: 'break-word', |
|
border: '1px solid rgba(255,255,255,0.1)', |
|
willChange: 'transform' |
|
}); |
|
|
|
n.textContent = message; |
|
document.body.appendChild(n); |
|
this._currentNotification = n; |
|
|
|
requestAnimationFrame(() => { |
|
n.style.transform = 'translateX(-50%) translateY(0)'; |
|
}); |
|
|
|
this._timeoutId = setTimeout(() => this._hide(n), CONFIG.TIMING.NOTIFICATION_DURATION); |
|
} |
|
|
|
static _hide(notification) { |
|
if (!notification.parentNode) return; |
|
notification.style.transform = 'translateX(-50%) translateY(100%)'; |
|
setTimeout(() => { |
|
DOMUtils.safeRemove(notification); |
|
if (this._currentNotification === notification) { |
|
this._currentNotification = null; |
|
} |
|
}, CONFIG.TIMING.ANIMATION_DURATION); |
|
} |
|
} |
|
|
|
/* ----------------- Level Navigator ----------------- */ |
|
|
|
class LevelNavigator { |
|
constructor() { |
|
this.currentOverlay = null; |
|
this.shortcutsMenuOverlay = null; |
|
this.levelsEscapeHandler = null; |
|
this.shortcutsEscapeHandler = null; |
|
this.isInitialized = false; |
|
this._installShortcuts(); |
|
} |
|
|
|
_installShortcuts() { |
|
const handler = (e) => { |
|
if (e.repeat) return; |
|
|
|
const { code, ctrlKey, shiftKey, altKey } = e; |
|
const sc = CONFIG.SHORTCUTS; |
|
const tag = e.target.tagName; |
|
const isInputElement = tag === 'INPUT' || tag === 'TEXTAREA' || e.target.isContentEditable; |
|
|
|
const shortcuts = { |
|
[`${sc.TOGGLE_NAVIGATOR}:false:false:false`]: () => this.toggleLevelDialog(), |
|
[`${sc.SHOW_SHORTCUTS}:true:false:false`]: () => this.toggleShortcutsMenu(), |
|
[`${sc.PREVIOUS_LEVEL}:false:true:false`]: () => this.navigateToLevel(-1), |
|
[`${sc.NEXT_LEVEL}:false:true:false`]: () => this.navigateToLevel(1) |
|
}; |
|
|
|
const key = `${code}:${ctrlKey}:${shiftKey}:${altKey}`; |
|
const action = shortcuts[key]; |
|
|
|
if (action) { |
|
const isNavigationShortcut = code === sc.PREVIOUS_LEVEL || code === sc.NEXT_LEVEL; |
|
const hasModifiers = ctrlKey || shiftKey || altKey; |
|
|
|
if (isInputElement && !hasModifiers && !isNavigationShortcut) { |
|
return; |
|
} |
|
|
|
e.preventDefault(); |
|
e.stopPropagation(); |
|
action(); |
|
} |
|
}; |
|
|
|
document.addEventListener('keydown', handler, { passive: false }); |
|
} |
|
|
|
toggleLevelDialog() { |
|
if (this.currentOverlay) this.closeDialog(); |
|
else this.showLevelDialog(); |
|
} |
|
|
|
showLevelDialog() { |
|
if (this.currentOverlay) return; |
|
const overlay = this._createOverlay((evt) => { |
|
if (evt.target === overlay) this.closeDialog(); |
|
}); |
|
const dialog = this._createDialog(); |
|
overlay.appendChild(dialog); |
|
document.body.appendChild(overlay); |
|
this.currentOverlay = overlay; |
|
|
|
requestAnimationFrame(() => { |
|
overlay.style.opacity = '1'; |
|
dialog.style.transform = 'scale(1)'; |
|
}); |
|
|
|
const input = dialog.querySelector('input[type="number"]'); |
|
} |
|
|
|
_createOverlay(onClick) { |
|
const overlay = DOMUtils.createElement('div', 'level-nav-overlay', { |
|
position: 'fixed', |
|
top: '0', |
|
left: '0', |
|
width: '100vw', |
|
height: '100vh', |
|
background: 'rgba(0,0,0,0.6)', |
|
zIndex: '999999', |
|
display: 'flex', |
|
alignItems: 'center', |
|
justifyContent: 'center', |
|
padding: '16px', |
|
boxSizing: 'border-box', |
|
opacity: '0', |
|
transition: 'opacity 0.2s ease' |
|
}); |
|
|
|
overlay.addEventListener('click', onClick); |
|
|
|
this.levelsEscapeHandler = (e) => { |
|
if (e.key === 'Escape') { |
|
e.stopPropagation(); |
|
this.closeDialog(); |
|
} |
|
}; |
|
document.addEventListener('keydown', this.levelsEscapeHandler); |
|
|
|
return overlay; |
|
} |
|
|
|
_createDialog() { |
|
const dialog = DOMUtils.createElement('div', 'level-nav-dialog', { |
|
background: CONFIG.COLORS.WHITE, |
|
borderRadius: '12px', |
|
padding: '24px', |
|
boxShadow: '0 20px 25px rgba(0,0,0,0.08)', |
|
fontFamily: 'system-ui, -apple-system, sans-serif', |
|
width: '100%', |
|
maxWidth: '420px', |
|
maxHeight: '90vh', |
|
overflowY: 'auto', |
|
transform: 'scale(0.95)', |
|
transition: `transform ${CONFIG.TIMING.ANIMATION_DURATION}ms ease`, |
|
position: 'relative', |
|
willChange: 'transform' |
|
}); |
|
|
|
const fragment = document.createDocumentFragment(); |
|
|
|
const closeButton = this._createCloseButton(); |
|
closeButton.setAttribute('aria-label', 'Close levels dialog'); |
|
fragment.appendChild(closeButton); |
|
|
|
fragment.appendChild(this._createHeader()); |
|
fragment.appendChild(this._createCurrentLevelSection()); |
|
fragment.appendChild(this._createNavigationSection()); |
|
fragment.appendChild(this._createJumpSection()); |
|
fragment.appendChild(this._createFooter()); |
|
|
|
dialog.appendChild(fragment); |
|
return dialog; |
|
} |
|
|
|
_createHeader() { |
|
const header = DOMUtils.createElement('div', '', { |
|
marginBottom: '18px', |
|
textAlign: 'center', |
|
position: 'relative' |
|
}); |
|
|
|
const title = DOMUtils.createElement('h2', '', { |
|
margin: '0 0 8px 0', |
|
color: CONFIG.COLORS.GRAY_900, |
|
fontSize: '20px', |
|
fontWeight: '600' |
|
}); |
|
title.textContent = 'Levels'; |
|
|
|
const infoButton = DOMUtils.createElement('button', '', { |
|
position: 'absolute', |
|
top: '0', |
|
left: '0', |
|
background: CONFIG.COLORS.GRAY_100, |
|
border: `1px solid ${CONFIG.COLORS.GRAY_200}`, |
|
borderRadius: '50%', |
|
width: '28px', |
|
height: '28px', |
|
fontSize: '14px', |
|
fontWeight: 'bold', |
|
color: CONFIG.COLORS.GRAY_600, |
|
cursor: 'pointer', |
|
display: 'flex', |
|
alignItems: 'center', |
|
justifyContent: 'center', |
|
transition: 'all 0.15s ease', |
|
zIndex: '5', |
|
pointerEvents: 'auto' |
|
}, { type: 'button', 'aria-label': 'Show keyboard shortcuts' }); |
|
|
|
infoButton.textContent = '?'; |
|
infoButton.addEventListener('mouseenter', () => { |
|
infoButton.style.backgroundColor = CONFIG.COLORS.GRAY_200; |
|
infoButton.style.borderColor = CONFIG.COLORS.GRAY_300; |
|
}); |
|
infoButton.addEventListener('mouseleave', () => { |
|
infoButton.style.backgroundColor = CONFIG.COLORS.GRAY_100; |
|
infoButton.style.borderColor = CONFIG.COLORS.GRAY_200; |
|
}); |
|
infoButton.addEventListener('click', () => this.toggleShortcutsMenu()); |
|
|
|
header.appendChild(title); |
|
header.appendChild(infoButton); |
|
return header; |
|
} |
|
|
|
_createCurrentLevelSection() { |
|
const section = DOMUtils.createElement('div', '', { |
|
background: `linear-gradient(135deg, ${CONFIG.COLORS.PRIMARY}08, ${CONFIG.COLORS.SUCCESS}08)`, |
|
border: `1px solid ${CONFIG.COLORS.GRAY_200}`, |
|
borderRadius: '12px', |
|
padding: '18px', |
|
marginBottom: '18px', |
|
textAlign: 'center' |
|
}); |
|
|
|
const label = DOMUtils.createElement('div', '', { |
|
fontSize: '12px', |
|
color: CONFIG.COLORS.GRAY_500, |
|
marginBottom: '8px', |
|
textTransform: 'uppercase', |
|
letterSpacing: '0.8px', |
|
fontWeight: '600' |
|
}); |
|
label.textContent = 'Current Level'; |
|
|
|
const currentLevel = DOMUtils.createElement('div', '', { |
|
fontSize: '36px', |
|
fontWeight: '700', |
|
color: CONFIG.COLORS.PRIMARY, |
|
marginBottom: '4px' |
|
}); |
|
const cur = StorageManager.getCurrentLevelFromDOM(); |
|
currentLevel.textContent = cur; |
|
|
|
const progress = DOMUtils.createElement('div', '', { |
|
fontSize: '13px', |
|
color: CONFIG.COLORS.GRAY_600 |
|
}); |
|
const percentage = Math.round((cur / CONFIG.LEVEL_RANGE.MAX) * 100); |
|
progress.textContent = `${percentage}% complete (${cur}/${CONFIG.LEVEL_RANGE.MAX})`; |
|
|
|
section.appendChild(label); |
|
section.appendChild(currentLevel); |
|
section.appendChild(progress); |
|
return section; |
|
} |
|
|
|
_createNavigationSection() { |
|
const section = DOMUtils.createElement('div', '', { marginBottom: '18px' }); |
|
const title = DOMUtils.createElement('h3', '', { |
|
margin: '0 0 12px 0', |
|
fontSize: '16px', |
|
fontWeight: '600', |
|
color: CONFIG.COLORS.GRAY_800, |
|
display: 'flex', |
|
alignItems: 'center', |
|
gap: '6px' |
|
}); |
|
title.innerHTML = '⚡ Quick Navigation'; |
|
|
|
const navGrid = DOMUtils.createElement('div', '', { |
|
display: 'grid', |
|
gridTemplateColumns: '1fr 1fr', |
|
gap: '10px' |
|
}); |
|
|
|
const cur = StorageManager.getCurrentLevelFromDOM(); |
|
const prevBtn = this._createNavButton('← Previous', 'previous', () => this.navigateToLevel(-1)); |
|
const nextBtn = this._createNavButton('Next →', 'next', () => this.navigateToLevel(1)); |
|
|
|
if (cur <= 1) { |
|
prevBtn.disabled = true; |
|
Object.assign(prevBtn.style, { opacity: '0.45', cursor: 'not-allowed' }); |
|
} |
|
if (cur >= CONFIG.LEVEL_RANGE.MAX) { |
|
nextBtn.disabled = true; |
|
Object.assign(nextBtn.style, { opacity: '0.45', cursor: 'not-allowed' }); |
|
} |
|
|
|
navGrid.appendChild(prevBtn); |
|
navGrid.appendChild(nextBtn); |
|
section.appendChild(title); |
|
section.appendChild(navGrid); |
|
return section; |
|
} |
|
|
|
_createNavButton(text, type, onClick) { |
|
const isPrev = type === 'previous'; |
|
const base = isPrev ? CONFIG.COLORS.GRAY_600 : CONFIG.COLORS.SUCCESS; |
|
const hover = isPrev ? CONFIG.COLORS.GRAY_700 : CONFIG.COLORS.SUCCESS_HOVER; |
|
|
|
const btn = DOMUtils.createElement('button', '', { |
|
padding: '12px 14px', |
|
fontSize: '14px', |
|
fontFamily: 'system-ui, -apple-system, sans-serif', |
|
fontWeight: '600', |
|
border: `2px solid ${base}`, |
|
borderRadius: '10px', |
|
cursor: 'pointer', |
|
transition: 'all 0.14s ease', |
|
outline: 'none', |
|
background: 'transparent', |
|
color: base, |
|
display: 'flex', |
|
alignItems: 'center', |
|
justifyContent: 'center', |
|
gap: '6px', |
|
minHeight: '44px' |
|
}, { type: 'button' }); |
|
|
|
btn.textContent = text; |
|
|
|
btn.addEventListener('mouseenter', () => { |
|
if (btn.disabled) return; |
|
btn.style.backgroundColor = base; |
|
btn.style.color = CONFIG.COLORS.WHITE; |
|
btn.style.transform = 'translateY(-2px)'; |
|
btn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.12)'; |
|
}); |
|
btn.addEventListener('mouseleave', () => { |
|
if (btn.disabled) return; |
|
btn.style.backgroundColor = 'transparent'; |
|
btn.style.color = base; |
|
btn.style.transform = 'translateY(0)'; |
|
btn.style.boxShadow = 'none'; |
|
}); |
|
btn.addEventListener('focus', () => { |
|
btn.style.outline = `2px solid ${base}`; |
|
btn.style.outlineOffset = '2px'; |
|
}); |
|
btn.addEventListener('blur', () => (btn.style.outline = 'none')); |
|
|
|
btn.addEventListener('click', (e) => { |
|
e.preventDefault(); |
|
onClick(); |
|
}); |
|
|
|
return btn; |
|
} |
|
|
|
_createCloseButton() { |
|
const btn = DOMUtils.createElement('button', '', { |
|
position: 'absolute', |
|
top: '12px', |
|
right: '12px', |
|
background: 'transparent', |
|
border: 'none', |
|
fontSize: '20px', |
|
cursor: 'pointer', |
|
color: CONFIG.COLORS.GRAY_400, |
|
width: '36px', |
|
height: '36px', |
|
borderRadius: '50%', |
|
display: 'flex', |
|
alignItems: 'center', |
|
justifyContent: 'center', |
|
transition: 'all 0.12s ease', |
|
padding: '0', |
|
zIndex: '10', |
|
pointerEvents: 'auto' |
|
}, { type: 'button' }); |
|
|
|
btn.innerHTML = '×'; |
|
|
|
btn.addEventListener('mouseenter', () => { |
|
btn.style.color = CONFIG.COLORS.GRAY_600; |
|
btn.style.backgroundColor = CONFIG.COLORS.GRAY_100; |
|
}); |
|
btn.addEventListener('mouseleave', () => { |
|
btn.style.color = CONFIG.COLORS.GRAY_400; |
|
btn.style.backgroundColor = 'transparent'; |
|
}); |
|
btn.addEventListener('click', () => this.closeDialog()); |
|
|
|
return btn; |
|
} |
|
|
|
_createJumpSection() { |
|
const section = DOMUtils.createElement('div', '', {}); |
|
|
|
const title = DOMUtils.createElement('h3', '', { |
|
margin: '0 0 12px 0', |
|
fontSize: '16px', |
|
fontWeight: '600', |
|
color: CONFIG.COLORS.GRAY_800, |
|
display: 'flex', |
|
alignItems: 'center', |
|
gap: '6px' |
|
}); |
|
title.innerHTML = '🚀 Jump to Level'; |
|
|
|
const inputGroup = DOMUtils.createElement('div', '', { |
|
display: 'flex', |
|
gap: '10px', |
|
alignItems: 'stretch' |
|
}); |
|
|
|
const input = DOMUtils.createElement('input', '', { |
|
width: '100%', |
|
padding: '12px 14px', |
|
border: `2px solid ${CONFIG.COLORS.GRAY_300}`, |
|
borderRadius: '10px', |
|
fontSize: '16px', |
|
fontFamily: 'system-ui, -apple-system, sans-serif', |
|
outline: 'none', |
|
transition: 'border-color 0.12s ease', |
|
boxSizing: 'border-box' |
|
}, { |
|
type: 'number', |
|
min: CONFIG.LEVEL_RANGE.MIN.toString(), |
|
max: CONFIG.LEVEL_RANGE.MAX.toString(), |
|
placeholder: `Enter level (1-${CONFIG.LEVEL_RANGE.MAX})` |
|
}); |
|
|
|
input.addEventListener('focus', () => (input.style.borderColor = CONFIG.COLORS.PRIMARY)); |
|
input.addEventListener('blur', () => (input.style.borderColor = CONFIG.COLORS.GRAY_300)); |
|
input.addEventListener('keydown', (e) => { |
|
if (e.key === 'Enter') { |
|
e.preventDefault(); |
|
this.handleJumpToLevel(); |
|
} |
|
}); |
|
|
|
const jumpButton = DOMUtils.createElement('button', '', { |
|
padding: '12px 16px', |
|
fontSize: '14px', |
|
fontFamily: 'system-ui, -apple-system, sans-serif', |
|
fontWeight: '600', |
|
border: 'none', |
|
borderRadius: '10px', |
|
cursor: 'pointer', |
|
transition: 'all 0.12s ease', |
|
outline: 'none', |
|
background: CONFIG.COLORS.PRIMARY, |
|
color: CONFIG.COLORS.WHITE, |
|
whiteSpace: 'nowrap' |
|
}, { type: 'button' }); |
|
|
|
jumpButton.textContent = 'Jump'; |
|
jumpButton.addEventListener('mouseenter', () => (jumpButton.style.backgroundColor = CONFIG.COLORS.PRIMARY_HOVER)); |
|
jumpButton.addEventListener('mouseleave', () => (jumpButton.style.backgroundColor = CONFIG.COLORS.PRIMARY)); |
|
jumpButton.addEventListener('click', (e) => { |
|
e.preventDefault(); |
|
this.handleJumpToLevel(); |
|
}); |
|
|
|
inputGroup.appendChild(input); |
|
inputGroup.appendChild(jumpButton); |
|
section.appendChild(title); |
|
section.appendChild(inputGroup); |
|
return section; |
|
} |
|
|
|
_createFooter() { |
|
const footerHtml = ` |
|
<div style=" |
|
margin: 16px -24px -24px -24px; |
|
padding: 12px 24px; |
|
border-top: 1px solid ${CONFIG.COLORS.GRAY_200}; |
|
text-align: center; |
|
background: linear-gradient(135deg, ${CONFIG.COLORS.GRAY_50}, ${CONFIG.COLORS.WHITE}); |
|
border-radius: 0 0 12px 12px; |
|
"> |
|
<div style=" |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
gap: 4px; |
|
flex-wrap: wrap; |
|
"> |
|
<span style=" |
|
font-size: 11px; |
|
color: ${CONFIG.COLORS.GRAY_500}; |
|
font-family: Segoe UI, system-ui, -apple-system, sans-serif; |
|
font-weight: 500; |
|
">v${CONFIG.VERSION}</span> |
|
<span style="color: ${CONFIG.COLORS.GRAY_300}; font-size: 11px;">•</span> |
|
<span style=" |
|
font-size: 11px; |
|
color: ${CONFIG.COLORS.GRAY_500}; |
|
font-family: Segoe UI, system-ui, -apple-system, sans-serif; |
|
font-weight: 500; |
|
">© ${new Date().getFullYear()} Adas</span> |
|
</div> |
|
</div> |
|
`; |
|
return DOMUtils.createFragment(footerHtml).firstElementChild; |
|
} |
|
|
|
navigateToLevel(direction) { |
|
const current = StorageManager.getCurrentLevelFromDOM(); |
|
const target = current + direction; |
|
const v = Validator.validateLevel(target); |
|
if (!v.isValid) { |
|
NotificationManager.show(v.errorMessage, 'error'); |
|
return; |
|
} |
|
try { |
|
StorageManager.setLevel(v.level); |
|
if (this.currentOverlay) this.closeDialog(); |
|
NotificationManager.show(`Jumping to Level ${v.level}`, 'success'); |
|
setTimeout(() => this.reloadPage(), 600); |
|
} catch (err) { |
|
NotificationManager.show(err.message, 'error'); |
|
} |
|
} |
|
|
|
handleJumpToLevel() { |
|
const input = this.currentOverlay?.querySelector('input[type="number"]'); |
|
if (!input) return; |
|
const v = Validator.validateLevel(input.value); |
|
if (!v.isValid) { |
|
NotificationManager.show(v.errorMessage, 'error'); |
|
input.focus(); |
|
return; |
|
} |
|
try { |
|
StorageManager.setLevel(v.level); |
|
this.closeDialog(); |
|
NotificationManager.show(`Jumping to Level ${v.level}`, 'success'); |
|
setTimeout(() => this.reloadPage(), 600); |
|
} catch (err) { |
|
NotificationManager.show(err.message, 'error'); |
|
} |
|
} |
|
|
|
closeDialog() { |
|
if (!this.currentOverlay) return; |
|
if (this.levelsEscapeHandler) { |
|
document.removeEventListener('keydown', this.levelsEscapeHandler); |
|
this.levelsEscapeHandler = null; |
|
} |
|
const dialog = this.currentOverlay.querySelector('.level-nav-dialog'); |
|
this.currentOverlay.style.opacity = '0'; |
|
if (dialog) dialog.style.transform = 'scale(0.95)'; |
|
setTimeout(() => { |
|
DOMUtils.safeRemove(this.currentOverlay); |
|
this.currentOverlay = null; |
|
}, 180); |
|
} |
|
|
|
toggleShortcutsMenu() { |
|
if (this.shortcutsMenuOverlay) this.closeShortcutsMenu(); |
|
else this.showShortcutsMenu(); |
|
} |
|
|
|
showShortcutsMenu() { |
|
if (this.shortcutsMenuOverlay) return; |
|
const overlay = this._createShortcutsOverlay(); |
|
const menu = this._createShortcutsMenu(); |
|
overlay.appendChild(menu); |
|
document.body.appendChild(overlay); |
|
this.shortcutsMenuOverlay = overlay; |
|
requestAnimationFrame(() => { |
|
overlay.style.opacity = '1'; |
|
menu.style.transform = 'scale(1)'; |
|
}); |
|
} |
|
|
|
_createShortcutsOverlay() { |
|
const overlay = DOMUtils.createElement('div', 'shortcuts-menu-overlay', { |
|
position: 'fixed', |
|
top: '0', |
|
left: '0', |
|
width: '100vw', |
|
height: '100vh', |
|
background: 'rgba(0,0,0,0.6)', |
|
zIndex: '1000000', |
|
display: 'flex', |
|
alignItems: 'center', |
|
justifyContent: 'center', |
|
padding: '16px', |
|
boxSizing: 'border-box', |
|
opacity: '0', |
|
transition: 'opacity 0.18s ease' |
|
}); |
|
|
|
overlay.addEventListener('click', (e) => { |
|
if (e.target === overlay) this.closeShortcutsMenu(); |
|
}); |
|
|
|
this.shortcutsEscapeHandler = (e) => { |
|
if (e.key === 'Escape' || (e.code === 'Slash' && e.ctrlKey && !e.repeat)) { |
|
e.stopPropagation(); |
|
this.closeShortcutsMenu(); |
|
} |
|
}; |
|
document.addEventListener('keydown', this.shortcutsEscapeHandler); |
|
|
|
return overlay; |
|
} |
|
|
|
_createShortcutsMenu() { |
|
const menu = DOMUtils.createElement('div', 'shortcuts-menu', { |
|
background: CONFIG.COLORS.WHITE, |
|
borderRadius: '12px', |
|
padding: '24px', |
|
boxShadow: '0 20px 25px rgba(0,0,0,0.08)', |
|
fontFamily: 'system-ui, -apple-system, sans-serif', |
|
width: '100%', |
|
maxWidth: '400px', |
|
transform: 'scale(0.95)', |
|
transition: `transform ${CONFIG.TIMING.ANIMATION_DURATION}ms ease`, |
|
position: 'relative', |
|
willChange: 'transform' |
|
}); |
|
|
|
const header = DOMUtils.createElement('div', '', { marginBottom: '20px', textAlign: 'center' }); |
|
const title = DOMUtils.createElement('h2', '', { |
|
margin: '0 0 8px 0', |
|
color: CONFIG.COLORS.GRAY_900, |
|
fontSize: '18px', |
|
fontWeight: '600' |
|
}); |
|
title.textContent = 'Shortcuts'; |
|
|
|
const closeButton = DOMUtils.createElement('button', '', { |
|
position: 'absolute', |
|
top: '12px', |
|
right: '12px', |
|
background: 'transparent', |
|
border: 'none', |
|
fontSize: '20px', |
|
cursor: 'pointer', |
|
color: CONFIG.COLORS.GRAY_400, |
|
width: '36px', |
|
height: '36px', |
|
borderRadius: '50%', |
|
display: 'flex', |
|
alignItems: 'center', |
|
justifyContent: 'center', |
|
transition: 'all 0.12s ease', |
|
padding: '0' |
|
}, { type: 'button', 'aria-label': 'Close shortcuts menu' }); |
|
closeButton.innerHTML = '×'; |
|
closeButton.addEventListener('mouseenter', () => { |
|
closeButton.style.color = CONFIG.COLORS.GRAY_600; |
|
closeButton.style.backgroundColor = CONFIG.COLORS.GRAY_100; |
|
}); |
|
closeButton.addEventListener('mouseleave', () => { |
|
closeButton.style.color = CONFIG.COLORS.GRAY_400; |
|
closeButton.style.backgroundColor = 'transparent'; |
|
}); |
|
closeButton.addEventListener('click', () => this.closeShortcutsMenu()); |
|
|
|
const createKbd = (txt) => { |
|
const k = DOMUtils.createElement('kbd', '', { |
|
padding: '4px 8px', |
|
background: CONFIG.COLORS.GRAY_100, |
|
borderRadius: '4px', |
|
fontSize: '12px', |
|
fontFamily: 'monospace', |
|
border: `1px solid ${CONFIG.COLORS.GRAY_200}`, |
|
minWidth: '32px', |
|
textAlign: 'center', |
|
display: 'inline-block' |
|
}); |
|
k.textContent = txt; |
|
return k; |
|
}; |
|
|
|
const shortcuts = [ |
|
{ keys: ['L'], description: 'Toggle Levels menu' }, |
|
{ keys: ['Ctrl', '/'], description: 'Show/Hide this shortcuts menu' }, |
|
{ keys: ['Shift', '←'], description: 'Previous Level' }, |
|
{ keys: ['Shift', '→'], description: 'Next Level' }, |
|
{ keys: ['Esc'], description: 'Close any open menu' } |
|
]; |
|
|
|
const list = DOMUtils.createElement('div', '', { display: 'flex', flexDirection: 'column', gap: '12px' }); |
|
|
|
shortcuts.forEach((sc) => { |
|
const row = DOMUtils.createElement('div', '', { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '8px 0' }); |
|
const keysContainer = DOMUtils.createElement('div', '', { display: 'flex', alignItems: 'center', gap: '6px' }); |
|
sc.keys.forEach((key, idx) => { |
|
if (idx > 0) { |
|
const plus = DOMUtils.createElement('span', '', { color: CONFIG.COLORS.GRAY_400, fontSize: '12px', margin: '0 2px' }); |
|
plus.textContent = '+'; |
|
keysContainer.appendChild(plus); |
|
} |
|
keysContainer.appendChild(createKbd(key)); |
|
}); |
|
const desc = DOMUtils.createElement('span', '', { fontSize: '14px', color: CONFIG.COLORS.GRAY_700, flex: '1', textAlign: 'right', marginLeft: '16px' }); |
|
desc.textContent = sc.description; |
|
row.appendChild(keysContainer); |
|
row.appendChild(desc); |
|
list.appendChild(row); |
|
}); |
|
|
|
const footer = DOMUtils.createElement('div', '', { |
|
marginTop: '20px', |
|
paddingTop: '12px', |
|
borderTop: `1px solid ${CONFIG.COLORS.GRAY_200}`, |
|
textAlign: 'center', |
|
background: `linear-gradient(135deg, ${CONFIG.COLORS.GRAY_50}, ${CONFIG.COLORS.WHITE})`, |
|
borderRadius: '0 0 12px 12px', |
|
margin: '20px -24px -24px -24px', |
|
padding: '12px 24px' |
|
}); |
|
|
|
const container = DOMUtils.createElement('div', '', { |
|
display: 'flex', |
|
alignItems: 'center', |
|
justifyContent: 'center', |
|
gap: '4px', |
|
flexWrap: 'wrap' |
|
}); |
|
|
|
const versionText = DOMUtils.createElement('span', '', { |
|
fontSize: '11px', |
|
color: CONFIG.COLORS.GRAY_500, |
|
fontFamily: 'Segoe UI, system-ui, -apple-system, sans-serif', |
|
fontWeight: '500' |
|
}); |
|
versionText.textContent = `v${CONFIG.VERSION}`; |
|
|
|
const separator = DOMUtils.createElement('span', '', { |
|
color: CONFIG.COLORS.GRAY_300, |
|
fontSize: '11px' |
|
}); |
|
separator.textContent = '•'; |
|
|
|
const copyrightText = DOMUtils.createElement('span', '', { |
|
fontSize: '11px', |
|
color: CONFIG.COLORS.GRAY_500, |
|
fontFamily: 'Segoe UI, system-ui, -apple-system, sans-serif', |
|
fontWeight: '500' |
|
}); |
|
copyrightText.textContent = `© ${new Date().getFullYear()} Adas`; |
|
|
|
container.appendChild(versionText); |
|
container.appendChild(separator); |
|
container.appendChild(copyrightText); |
|
footer.appendChild(container); |
|
|
|
header.appendChild(title); |
|
menu.appendChild(closeButton); |
|
menu.appendChild(header); |
|
menu.appendChild(list); |
|
menu.appendChild(footer); |
|
return menu; |
|
} |
|
|
|
closeShortcutsMenu() { |
|
if (!this.shortcutsMenuOverlay) return; |
|
if (this.shortcutsEscapeHandler) { |
|
document.removeEventListener('keydown', this.shortcutsEscapeHandler); |
|
this.shortcutsEscapeHandler = null; |
|
} |
|
const menu = this.shortcutsMenuOverlay.querySelector('.shortcuts-menu'); |
|
this.shortcutsMenuOverlay.style.opacity = '0'; |
|
if (menu) menu.style.transform = 'scale(0.95)'; |
|
setTimeout(() => { |
|
DOMUtils.safeRemove(this.shortcutsMenuOverlay); |
|
this.shortcutsMenuOverlay = null; |
|
}, 160); |
|
} |
|
|
|
createNavigationButton() { |
|
const btn = DOMUtils.createElement('button', 'level-nav-btn', { |
|
background: CONFIG.COLORS.WHITE, |
|
cursor: 'pointer', |
|
padding: '10px 16px', |
|
color: CONFIG.COLORS.GRAY_700, |
|
fontSize: '14px', |
|
fontFamily: 'system-ui, -apple-system, sans-serif', |
|
fontWeight: '500', |
|
border: `1px solid ${CONFIG.COLORS.GRAY_300}`, |
|
borderRadius: '8px', |
|
transition: 'all 0.15s ease', |
|
outline: 'none', |
|
display: 'flex', |
|
alignItems: 'center', |
|
gap: '6px' |
|
}, { type: 'button', 'aria-label': 'Open level navigator' }); |
|
|
|
btn.textContent = 'Levels'; |
|
|
|
btn.addEventListener('mouseenter', () => { |
|
btn.style.backgroundColor = CONFIG.COLORS.GRAY_50; |
|
btn.style.borderColor = CONFIG.COLORS.GRAY_400; |
|
btn.style.transform = 'translateY(-1px)'; |
|
}); |
|
btn.addEventListener('mouseleave', () => { |
|
btn.style.backgroundColor = CONFIG.COLORS.WHITE; |
|
btn.style.borderColor = CONFIG.COLORS.GRAY_300; |
|
btn.style.transform = 'translateY(0)'; |
|
}); |
|
btn.addEventListener('focus', () => { |
|
btn.style.outline = `2px solid ${CONFIG.COLORS.PRIMARY}`; |
|
btn.style.outlineOffset = '2px'; |
|
}); |
|
btn.addEventListener('blur', () => (btn.style.outline = 'none')); |
|
btn.addEventListener('click', () => this.toggleLevelDialog()); |
|
return btn; |
|
} |
|
|
|
integrateButton() { |
|
const resetButton = document.querySelector(CONFIG.SELECTORS.RESET_BUTTON); |
|
const navButton = this.createNavigationButton(); |
|
|
|
if (resetButton && resetButton.parentNode) { |
|
try { |
|
const wrapper = DOMUtils.createElement('div', 'nav-button-wrapper', { |
|
display: 'inline-flex', |
|
alignItems: 'center' |
|
}); |
|
wrapper.appendChild(navButton); |
|
resetButton.parentNode.replaceChild(wrapper, resetButton); |
|
|
|
Object.assign(navButton.style, { background: 'transparent', border: 'none', borderRadius: '0', color: '#191919', fontSize: '15px' }); |
|
|
|
this.isInitialized = true; |
|
return true; |
|
} catch (err) { |
|
console.warn('Level Navigator: failed to replace reset button, falling back to footer insertion', err); |
|
} |
|
} |
|
|
|
const footerText = document.querySelector(CONFIG.SELECTORS.FOOTER_TEXT); |
|
if (!footerText || document.querySelector(CONFIG.SELECTORS.CHEAT_BUTTON)) return false; |
|
const parent = footerText.parentNode; |
|
if (!parent) return false; |
|
|
|
const wrapper = DOMUtils.createElement('div', 'nav-button-wrapper', { |
|
display: 'flex', |
|
alignItems: 'center', |
|
marginRight: '12px' |
|
}); |
|
wrapper.appendChild(navButton); |
|
|
|
try { |
|
parent.insertBefore(wrapper, footerText); |
|
const computed = window.getComputedStyle(parent); |
|
if (computed.display === 'block') { |
|
parent.style.display = 'flex'; |
|
parent.style.justifyContent = 'space-between'; |
|
parent.style.alignItems = 'center'; |
|
} |
|
} catch (err) { |
|
parent.appendChild(wrapper); |
|
} |
|
|
|
Object.assign(navButton.style, { background: 'transparent', border: 'none', borderRadius: '0', color: '#191919', fontSize: '15px' }); |
|
|
|
this.isInitialized = true; |
|
return true; |
|
} |
|
|
|
reloadPage() { |
|
window.location.reload(); |
|
} |
|
} |
|
|
|
/* ---------------- Orchestrator ---------------- */ |
|
|
|
class App { |
|
constructor() { |
|
this.navigator = new LevelNavigator(); |
|
this.attempts = 0; |
|
} |
|
|
|
initialize() { |
|
setTimeout(() => this._tryIntegrate(), CONFIG.TIMING.INITIAL_DELAY); |
|
} |
|
|
|
_tryIntegrate() { |
|
const tryFn = () => { |
|
if (this.attempts >= CONFIG.TIMING.MAX_ATTEMPTS) { |
|
console.warn('Level Navigator: Failed to initialize after maximum attempts'); |
|
return; |
|
} |
|
if (this.navigator.integrateButton()) { |
|
console.log('Level Navigator: Initialized'); |
|
const welcomeKey = 'levelNavWelcomeShown'; |
|
if (!sessionStorage.getItem(welcomeKey)) { |
|
sessionStorage.setItem(welcomeKey, 'true'); |
|
setTimeout(() => { |
|
NotificationManager.show('Level navigator initialized! Press L to toggle, Ctrl+/ for shortcuts'); |
|
}, CONFIG.TIMING.NOTIFICATION_DURATION); |
|
} |
|
return; |
|
} |
|
this.attempts++; |
|
setTimeout(tryFn, CONFIG.TIMING.BUTTON_CHECK_INTERVAL); |
|
}; |
|
tryFn(); |
|
} |
|
} |
|
|
|
const app = new App(); |
|
app.initialize(); |
|
})(); |