您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Advanced marker system with integrated expanding button menu
// ==UserScript== // @name Custom Markers Pro // @namespace http://tampermonkey.net/ // @version 2.1 // @description Advanced marker system with integrated expanding button menu // @author Disruptor // @match *://*/* // @grant GM_addStyle // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @grant GM_setClipboard // @require https://unpkg.com/[email protected]/dist/jspdf.umd.min.js // @license GPL-3.0 // @icon data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gVXBsb2FkZWQgdG86IFNWRyBSZXBvLCB3d3cuc3ZncmVwby5jb20sIEdlbmVyYXRvcjogU1ZHIFJlcG8gTWl4ZXIgVG9vbHMgLS0+DQo8c3ZnIHdpZHRoPSI4MDBweCIgaGVpZ2h0PSI4MDBweCIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgY2xhc3M9Imljb24iICB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTMwOS4yIDU4NC43NzZoMTA1LjVsLTQ5IDE1My4ySDIyNS44Yy03LjMgMC0xMy4zLTYtMTMuMy0xMy4zIDAtMi42IDAuOC01LjEgMi4yLTcuM2w4My40LTEyNi43YzIuNS0zLjYgNi43LTUuOSAxMS4xLTUuOXoiIGZpbGw9IiNGRkZGRkYiIC8+PHBhdGggZD0iTTQwNC41IDc5MS4yNzZIMjI1LjhjLTM2LjcgMC02Ni41LTI5LjgtNjYuNS02Ni41IDAtMTMgMy44LTI1LjcgMTEtMzYuNmw4My40LTEyNi43YzEyLjMtMTguNyAzMy4xLTI5LjkgNTUuNS0yOS45aDE3OC40bC04My4xIDI1OS43eiBtLTk1LjMtMjA2LjVjLTQuNSAwLTguNiAyLjItMTEuMSA2bC04My40IDEyNi43Yy0xLjQgMi4yLTIuMiA0LjctMi4yIDcuMyAwIDcuMyA2IDEzLjMgMTMuMyAxMy4zaDEzOS45bDQ5LTE1My4ySDMwOS4yeiIgZmlsbD0iIzMzMzMzMyIgLz48cGF0aCBkPSJNNDU0LjYgNTg0Ljc3NmgxMDkuNmwyNS4zIDE1My4zSDQyOS4zeiIgZmlsbD0iI0ZGRkZGRiIgLz48cGF0aCBkPSJNNjUyLjIgNzkxLjI3NkgzNjYuNmw0Mi44LTI1OS42aDIwMGw0Mi44IDI1OS42eiBtLTIyMi45LTUzLjJoMTYwLjJsLTI1LjMtMTUzLjNINDU0LjZsLTI1LjMgMTUzLjN6IiBmaWxsPSIjMzMzMzMzIiAvPjxwYXRoIGQ9Ik02MTguNiA1ODQuNzc2aDEwNS41YzQuNSAwIDguNiAyLjIgMTEuMSA2bDgzLjUgMTI2LjdjNCA2LjEgMi4zIDE0LjQtMy44IDE4LjQtMi4yIDEuNC00LjcgMi4yLTcuMyAyLjJINjY3LjdsLTQ5LjEtMTUzLjN6IiBmaWxsPSIjRkZGRkZGIiAvPjxwYXRoIGQ9Ik04MDcuNiA3OTEuMjc2SDYyOC45bC04My4xLTI1OS43aDE3OC40YzIyLjQgMCA0My4yIDExLjIgNTUuNSAyOS45bDgzLjQgMTI2LjdjOS44IDE0LjggMTMuMiAzMi42IDkuNiA1MHMtMTMuNyAzMi4zLTI4LjYgNDIuMWMtMTAuOCA3LjItMjMuNSAxMS0zNi41IDExeiBtLTEzOS45LTUzLjJoMTM5LjljMi42IDAgNS4xLTAuOCA3LjMtMi4yIDQtMi42IDUuMy02LjQgNS43LTguNCAwLjQtMiAwLjctNi0xLjktMTBsLTgzLjQtMTI2LjZjLTIuNS0zLjgtNi42LTYtMTEuMS02SDYxOC42bDQ5LjEgMTUzLjJ6IiBmaWxsPSIjMzMzMzMzIiAvPjxwYXRoIGQ9Ik01MzQuMSA2MzkuN0M2NTIuNSA1MzcuNCA3MTEuNyA0NDUuOCA3MTEuNyAzNjVjMC0xMjctMTAyLjctMjEyLjEtMTk1LTIxMi4xcy0xOTUgODUuMS0xOTUgMjEyLjFjMCA4MC44IDU5LjIgMTcyLjMgMTc3LjcgMjc0LjcgOS45IDguNiAyNC43IDguNiAzNC43IDB6IiBmaWxsPSIjOENBQUZGIiAvPjxwYXRoIGQ9Ik01MTYuNyA2NzIuN2MtMTIuNSAwLTI0LjktNC4zLTM0LjgtMTIuOUMzNTYuMiA1NTEuMiAyOTUuMSA0NTQuNyAyOTUuMSAzNjVjMC0xNDIuOCAxMTQuNi0yMzguNyAyMjEuNi0yMzguN1M3MzguMyAyMjIuMiA3MzguMyAzNjVjMCA4OS43LTYxLjEgMTg2LjItMTg2LjkgMjk0LjgtOS44IDguNi0yMi4zIDEyLjktMzQuNyAxMi45eiBtMC00OTMuMmMtNzkuNyAwLTE2OC40IDc2LjItMTY4LjQgMTg1LjUgMCA3Mi4zIDU2LjcgMTU4IDE2OC40IDI1NC42QzYyOC41IDUyMyA2ODUuMSA0MzcuMyA2ODUuMSAzNjVjMC0xMDkuMy04OC43LTE4NS41LTE2OC40LTE4NS41eiIgZmlsbD0iIzMzMzMzMyIgLz48cGF0aCBkPSJNNTE2LjcgMzQ4bS05Ny41IDBhOTcuNSA5Ny41IDAgMSAwIDE5NSAwIDk3LjUgOTcuNSAwIDEgMC0xOTUgMFoiIGZpbGw9IiNGRkZGRkYiIC8+PHBhdGggZD0iTTUxNi43IDQ3Mi4xYy02OC40IDAtMTI0LjEtNTUuNy0xMjQuMS0xMjQuMXM1NS43LTEyNC4xIDEyNC4xLTEyNC4xUzY0MC44IDI3OS41IDY0MC44IDM0OCA1ODUuMSA0NzIuMSA1MTYuNyA0NzIuMXogbTAtMTk1LjFjLTM5LjEgMC03MC45IDMxLjgtNzAuOSA3MC45IDAgMzkuMSAzMS44IDcwLjkgNzAuOSA3MC45czcwLjktMzEuOCA3MC45LTcwLjljMC0zOS4xLTMxLjgtNzAuOS03MC45LTcwLjl6IiBmaWxsPSIjMzMzMzMzIiAvPjwvc3ZnPg== // ==/UserScript== (function () { 'use strict'; // Check if GM_addStyle is available, provide fallback if not if (typeof GM_addStyle === 'undefined') { window.GM_addStyle = function (css) { const style = document.createElement('style'); style.textContent = css; document.head.appendChild(style); }; } // Modern UI Styles with Glassmorphism and Integrated Button System GM_addStyle(` @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); :root { --primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%); --secondary-gradient: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); --success-gradient: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); --warning-gradient: linear-gradient(135deg, #fa709a 0%, #fee140 100%); --glass-bg: rgba(255, 255, 255, 0.95); --glass-border: rgba(255, 255, 255, 0.18); --shadow-color: rgba(31, 38, 135, 0.37); --text-primary: #1a202c; --text-secondary: #4a5568; --border-radius: 20px; --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } .cm-dark-mode { --glass-bg: rgba(30, 30, 30, 0.95); --glass-border: rgba(255, 255, 255, 0.1); --text-primary: #f0f0f0; --text-secondary: #b0b0b0; --shadow-color: rgba(0, 0, 0, 0.5); } /* Integrated Button System */ #cm-button-container { position: fixed; bottom: 8px; right: 8px; z-index: 2147483647; pointer-events: auto; } #cm-button-container .button-box { position: relative; width: 4rem; height: 14rem; display: flex; flex-direction: column; align-items: center; user-select: none; } #cm-button-container .touch { flex: 1; width: 100%; cursor: pointer; background: transparent; pointer-events: auto; } #cm-button-container .button { width: 2.3rem; height: 2.3rem; position: absolute; left: 50%; bottom: 0%; transform: translate(-50%, 0); cursor: pointer; border: 2px solid #311703; border-radius: 6px; display: flex; justify-content: center; align-items: center; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); opacity: 0.95; box-shadow: inset 0 0 6px rgba(0,0,0,0.35); backdrop-filter: blur(10px); -webkit-backface-visibility: hidden; font-size: 24px; } #cm-button-container .button .icon { width: 18px; height: 18px; transition: transform .15s ease; opacity: 0.95; transform-origin: 50% 50%; } /* Button colors */ /* Panel button */ #cm-button-container .button:nth-child(6) { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); z-index: 30; } /* Edit button */ #cm-button-container .button:nth-child(7) { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); z-index: 35; } /* Drawing button */ #cm-button-container .button:nth-child(8) { background: linear-gradient(135deg, #fee140 0%, #fa709a 100%); z-index: 40; } /* Bulk button */ #cm-button-container .button:nth-child(9) { background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); z-index: 45; } /* Deactivate button */ #cm-button-container .button:nth-child(10) { background: linear-gradient(135deg, #f56565 0%, #ed64a6 100%); z-index: 35; } /* Main gear button */ #cm-button-container .button:nth-child(11) { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); z-index: 50; } /* Expanded positions */ #cm-button-container:hover .button:nth-child(6) { transform: translate(-50%, -250px) rotate(0deg); opacity: 1; } #cm-button-container:hover .button:nth-child(7) { transform: translate(-50%, -200px) rotate(0deg); opacity: 1; } #cm-button-container:hover .button:nth-child(8) { transform: translate(-50%, -150px) rotate(0deg); opacity: 1; } #cm-button-container:hover .button:nth-child(9) { transform: translate(-50%, -100px) rotate(0deg); opacity: 1; } #cm-button-container:hover .button:nth-child(10) { transform: translate(-50%, -50px) rotate(0deg); opacity: 1; } #cm-button-container:hover .button:nth-child(11) { transform: translate(-50%, 0px) rotate(0deg); opacity: 1; } /* Touch area hover effects */ #cm-button-container .touch.btn1:hover ~ .button:nth-child(6) { transform: translate(-50%, -250px) rotate(0deg); opacity: 1; } #cm-button-container .touch.btn2:hover ~ .button:nth-child(7) { transform: translate(-50%, -200px) rotate(0deg); opacity: 1; } #cm-button-container .touch.btn3:hover ~ .button:nth-child(8) { transform: translate(-50%, -150px) rotate(0deg); opacity: 1; } #cm-button-container .touch.btn4:hover ~ .button:nth-child(9) { transform: translate(-50%, -100px) rotate(0deg); opacity: 1; } #cm-button-container .touch.btn5:hover ~ .button:nth-child(10) { transform: translate(-50%, -50px) rotate(0deg); opacity: 1; } #cm-button-container .touch.btn6:hover ~ .button:nth-child(11) { transform: translate(-50%, 0px) rotate(0deg); opacity: 1; } #cm-button-container .button:hover { transform: scale(1.1); filter: drop-shadow(0 0 20px currentColor); } #cm-button-container .button:active { transform: scale(0.95); } /* Active states for toggle buttons */ #cm-button-container .button.active { filter: drop-shadow(0 0 15px currentColor); border-color: rgba(255,255,255,0.5); } #cm-button-container .button.active::after { content: '✓'; position: absolute; top: -5px; right: -5px; background: #43e97b; color: white; width: 18px; height: 18px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; } /* Tooltip for buttons */ #cm-button-container .button-tooltip { position: absolute; left: -120px; top: 50%; transform: translateY(-50%); background: rgba(0,0,0,0.9); color: white; padding: 8px 12px; border-radius: 8px; font-size: 14px; white-space: nowrap; opacity: 0; pointer-events: none; transition: opacity 0.3s; } #cm-button-container .button:hover .button-tooltip { opacity: 1; } /* Animation for the main gear button */ @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } #cm-button-container .button[data-button="main"]:hover .icon { animation: spin 1.2s linear infinite; } /* Quick actions menu */ .cm-quick-actions { background: white; border-radius: 12px; padding: 8px; box-shadow: 0 10px 40px rgba(0,0,0,0.15); z-index: 2147483647; min-width: 180px; } .cm-quick-action { padding: 10px; cursor: pointer; transition: background 0.3s; border-radius: 8px; } .cm-quick-action:hover { background: rgba(102, 126, 234, 0.1); } /* Fix for dark background sites */ .cm-drawing-canvas.active { background: rgba(255, 255, 255, 0.01); } @keyframes float { 0%, 100% { transform: translateY(0px) translateZ(0); } 50% { transform: translateY(-10px) translateZ(0); } } /* Activation button for initial state */ .cm-activate-container { position: fixed; bottom: 30px; right: 30px; z-index: 2147483640; } .cm-activate-btn { width: 2.3rem; height: 2.3rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; font-size: 32px; cursor: pointer; border: 2px solid #311703; border-radius: 6px; box-shadow: inset 0 0 6px rgba(0,0,0,0.35); filter: drop-shadow(0 0 10px rgba(102, 126, 234, 0.8)); transition: var(--transition); display: flex; align-items: center; justify-content: center; transition: transform 0.3s ease, filter 0.3s ease, box-shadow 0.3s ease; } .cm-activate-btn:hover { filter: drop-shadow(0 0 25px rgba(102, 126, 234, 1)); transform: scale(1.15) rotate(10deg); box-shadow: 0 0 15px rgba(102, 126, 234, 0.6); } .cm-activate-tooltip { position: absolute; background: rgba(0, 0, 0, 0.9); color: white; padding: 8px 12px; border-radius: 8px; font-size: 14px; font-family: 'Inter', sans-serif; white-space: nowrap; pointer-events: none; opacity: 0; transition: opacity 0.3s; z-index: 2147483641; bottom: 70px; right: 0; } .cm-activate-btn:hover + .cm-activate-tooltip { opacity: 1; } .cm-panel { position: fixed; top: 0; right: -450px; width: 450px; height: 100vh; background: var(--glass-bg); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); box-shadow: -10px 0 40px rgba(0,0,0,0.1); z-index: 2147483641; transition: right 0.4s cubic-bezier(0.4, 0, 0.2, 1); display: flex; flex-direction: column; font-family: 'Inter', sans-serif; border-left: 1px solid var(--glass-border); } .cm-panel.cm-dark-mode { background: rgba(30, 30, 30, 0.95); color: #f0f0f0; } .cm-panel.active { right: 0; } .cm-panel-header { background: var(--primary-gradient); color: white; padding: 24px; display: flex; justify-content: space-between; align-items: center; position: relative; overflow: hidden; } .cm-panel-header::after { content: ''; position: absolute; bottom: 0; left: 0; right: 0; height: 2px; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.5), transparent); animation: shimmer 3s infinite; } @keyframes shimmer { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } } .cm-panel-title { font-size: 22px; font-weight: 700; display: flex; align-items: center; gap: 10px; } .cm-header-buttons { display: flex; gap: 10px; align-items: center; } .cm-theme-toggle, .cm-close-btn { width: 36px; height: 36px; border-radius: 50%; background: rgba(255,255,255,0.2); border: 2px solid rgba(255,255,255,0.3); color: white; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: var(--transition); font-size: 18px; } .cm-theme-toggle:hover, .cm-close-btn:hover { background: rgba(255,255,255,0.3); transform: scale(1.1); } .cm-close-btn:hover { transform: rotate(90deg); } .cm-panel-content { flex: 1; overflow-y: auto; padding: 24px; background: linear-gradient(180deg, rgba(255,255,255,0.9) 0%, rgba(248,250,252,0.9) 100%); } .cm-dark-mode .cm-panel-content { background: linear-gradient(180deg, rgba(40,40,40,0.9) 0%, rgba(50,50,50,0.9) 100%); } .cm-panel-content::-webkit-scrollbar { width: 8px; } .cm-panel-content::-webkit-scrollbar-track { background: rgba(0,0,0,0.05); border-radius: 4px; } .cm-panel-content::-webkit-scrollbar-thumb { background: linear-gradient(180deg, #667eea, #764ba2); border-radius: 4px; } .cm-tabs { display: flex; gap: 8px; margin-bottom: 24px; background: rgba(0,0,0,0.03); padding: 4px; border-radius: 16px; } .cm-dark-mode .cm-tabs { background: rgba(255,255,255,0.05); } .cm-tab { flex: 1; padding: 10px 16px; background: transparent; border: none; color: var(--text-secondary); cursor: pointer; border-radius: 12px; font-family: 'Inter', sans-serif; font-weight: 500; transition: var(--transition); } .cm-tab.active { background: white; color: var(--text-primary); box-shadow: 0 2px 8px rgba(0,0,0,0.1); } .cm-dark-mode .cm-tab.active { background: rgba(60,60,60,1); color: #f0f0f0; } .cm-tab:hover:not(.active) { background: rgba(255,255,255,0.5); } .cm-form { background: white; padding: 24px; border-radius: var(--border-radius); box-shadow: 0 4px 20px rgba(0,0,0,0.08); margin-bottom: 24px; } .cm-dark-mode .cm-form { background: rgba(50,50,50,1); box-shadow: 0 4px 20px rgba(0,0,0,0.3); } .cm-form-group { margin-bottom: 20px; } .cm-label { display: block; margin-bottom: 8px; font-weight: 600; color: var(--text-primary); font-size: 14px; text-transform: uppercase; letter-spacing: 0.5px; } .cm-input, .cm-textarea, .cm-select { width: 100%; padding: 12px 16px; border: 2px solid #e2e8f0; border-radius: 12px; font-size: 15px; transition: var(--transition); font-family: 'Inter', sans-serif; background: white; box-sizing: border-box; } .cm-dark-mode .cm-input, .cm-dark-mode .cm-textarea, .cm-dark-mode .cm-select { background: rgba(40,40,40,1); border-color: rgba(255,255,255,0.1); color: #f0f0f0; } .cm-input:focus, .cm-textarea:focus, .cm-select:focus { outline: none; border-color: #667eea; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); } .cm-textarea { min-height: 100px; resize: vertical; } .cm-btn { padding: 12px 24px; border: none; border-radius: 12px; font-weight: 600; cursor: pointer; transition: var(--transition); font-size: 14px; font-family: 'Inter', sans-serif; position: relative; overflow: hidden; } .cm-btn::before { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0; border-radius: 50%; background: rgba(255,255,255,0.3); transform: translate(-50%, -50%); transition: width 0.3s, height 0.3s; } .cm-btn:active::before { width: 300px; height: 300px; } .cm-btn-primary { background: var(--primary-gradient); color: white; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); } .cm-btn-primary:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); } .cm-btn-secondary { background: #f7fafc; color: var(--text-primary); border: 2px solid #e2e8f0; } .cm-dark-mode .cm-btn-secondary { background: rgba(60,60,60,1); color: #f0f0f0; border-color: rgba(255,255,255,0.2); } .cm-btn-secondary:hover { background: #edf2f7; border-color: #cbd5e0; } .cm-btn-success { background: var(--success-gradient); color: white; box-shadow: 0 4px 15px rgba(67, 233, 123, 0.3); } .cm-btn-danger { background: linear-gradient(135deg, #f56565 0%, #ed64a6 100%); color: white; box-shadow: 0 4px 15px rgba(245, 101, 101, 0.3); } #donate-btn { transition: all 0.3s ease; } #donate-btn:hover { transform: translateY(-2px) scale(1.05); box-shadow: 0 6px 25px rgba(255, 196, 57, 0.4); } #donate-btn:hover span { animation: coffeeShake 0.5s ease-in-out; } @keyframes coffeeShake { 0%, 100% { transform: rotate(0deg); } 25% { transform: rotate(-10deg); } 75% { transform: rotate(10deg); } } .cm-search-container { position: relative; margin-bottom: 20px; } .cm-search-input { width: 100%; padding: 14px 48px 14px 20px; border: 2px solid #e2e8f0; border-radius: 50px; font-size: 15px; transition: var(--transition); font-family: 'Inter', sans-serif; box-sizing: border-box; } .cm-dark-mode .cm-search-input { background: rgba(40,40,40,1); border-color: rgba(255,255,255,0.1); color: #f0f0f0; } .cm-search-input:focus { border-color: #667eea; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); } .cm-search-icon { position: absolute; right: 20px; top: 50%; transform: translateY(-50%); color: #a0aec0; pointer-events: none; } .cm-marker-item { background: white; border-radius: 16px; padding: 16px; margin-bottom: 12px; display: flex; align-items: center; transition: var(--transition); border: 2px solid transparent; cursor: pointer; position: relative; overflow: hidden; } .cm-dark-mode .cm-marker-item { background: rgba(50,50,50,1); } .cm-marker-item::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(102, 126, 234, 0.05), transparent); transition: left 0.5s; } .cm-marker-item:hover { transform: translateX(-4px); border-color: #667eea; box-shadow: 0 4px 20px rgba(102, 126, 234, 0.1); } .cm-marker-item:hover::before { left: 100%; } .cm-marker-item.selected { background: linear-gradient(135deg, rgba(102, 126, 234, 0.1), rgba(118, 75, 162, 0.1)); border-color: #667eea; } .cm-icon-preview { display: inline-flex; align-items: center; justify-content: center; width: 60px; height: 60px; background: var(--primary-gradient); border-radius: 16px; font-size: 32px; margin-top: 10px; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); animation: iconBounce 2s ease-in-out infinite; } @keyframes iconBounce { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.1); } } `); // Adding animation keyframes and remaining CSS GM_addStyle(` @keyframes markerPulse { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.2); opacity: 0.8; } } @keyframes markerBounce { 0%, 100% { transform: translateY(0); } 25% { transform: translateY(-10px); } 75% { transform: translateY(-5px); } } @keyframes markerRotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } @keyframes markerGlow { 0%, 100% { filter: drop-shadow(0 0 5px rgba(102, 126, 234, 0.5)); } 50% { filter: drop-shadow(0 0 20px rgba(102, 126, 234, 0.8)); } } @keyframes markerShake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-5px); } 75% { transform: translateX(5px); } } @keyframes markerWiggle { 0%, 100% { transform: rotate(0deg); } 25% { transform: rotate(-5deg); } 75% { transform: rotate(5deg); } } @keyframes markerFade { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } } @keyframes markerFlip { 0% { transform: rotateY(0); } 100% { transform: rotateY(360deg); } } @keyframes markerZoom { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.5); } } @keyframes markerSlide { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(10px); } } @keyframes markerSpin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .cm-marker.animate-pulse { animation: markerPulse 2s ease-in-out infinite; } .cm-marker.animate-bounce { animation: markerBounce 1.5s ease-in-out infinite; } .cm-marker.animate-rotate { animation: markerRotate 3s linear infinite; } .cm-marker.animate-glow { animation: markerGlow 2s ease-in-out infinite; } .cm-marker.animate-shake { animation: markerShake 0.5s ease-in-out infinite; } .cm-marker.animate-wiggle { animation: markerWiggle 1s ease-in-out infinite; } .cm-marker.animate-fade { animation: markerFade 2s ease-in-out infinite; } .cm-marker.animate-flip { animation: markerFlip 3s ease-in-out infinite; } .cm-marker.animate-zoom { animation: markerZoom 2s ease-in-out infinite; } .cm-marker.animate-slide { animation: markerSlide 2s ease-in-out infinite; } .cm-marker.animate-spin { animation: markerSpin 2s linear infinite; } .cm-drawing-canvas { position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 2147483636; } .cm-drawing-canvas.active { pointer-events: auto; cursor: crosshair; } .cm-drawing-shape { position: absolute; border: 3px solid #667eea; background: rgba(102, 126, 234, 0.1); pointer-events: auto; cursor: move; } .cm-drawing-shape.circle { border-radius: 50%; } .cm-drawing-shape.line, .cm-drawing-shape.arrow { height: 0; background: none; border: none; border-top: 3px solid #667eea; transform-origin: left center; } .cm-drawing-shape.arrow::after { content: ''; position: absolute; right: -10px; top: -8px; width: 0; height: 0; border-left: 10px solid #667eea; border-top: 8px solid transparent; border-bottom: 8px solid transparent; } .cm-drawing-shape.text { border: none; background: none; font-family: 'Inter', sans-serif; font-size: 16px; padding: 4px 8px; color: #667eea; } .cm-drawing-canvas .cm-drawing-shape.selected { border-color: #f5576c !important; background: rgba(245, 87, 108, 0.1) !important; box-shadow: 0 0 10px rgba(245, 87, 108, 0.5); } .cm-svg-canvas { position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 2147483635; } .cm-svg-canvas path { pointer-events: stroke; cursor: pointer; } .cm-svg-canvas path.selected { stroke: #f5576c !important; stroke-width: 4 !important; fill: none; } .cm-drawing-toolbar { position: fixed; top: 80px; left: 20px; background: white; padding: 8px; border-radius: 16px; box-shadow: 0 10px 40px rgba(0,0,0,0.1); display: none; flex-direction: column; gap: 4px; z-index: 2147483637; max-height: calc(100vh - 160px); overflow-y: auto; } .cm-drawing-toolbar.active { display: flex; } .cm-drawing-toolbar-section { padding: 8px; border-bottom: 1px solid #e2e8f0; } .cm-drawing-toolbar-section:last-child { border-bottom: none; } .cm-drawing-toolbar-title { font-size: 12px; font-weight: 600; color: #667eea; margin-bottom: 8px; text-transform: uppercase; } .cm-drawing-tools { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; } .cm-drawing-tool { width: 40px; height: 40px; border-radius: 8px; border: 2px solid #e2e8f0; background: #cce5ff; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: var(--transition); font-size: 18px; } .cm-drawing-tool:hover { border-color: #667eea; transform: scale(1.1); } .cm-drawing-tool.active { background: var(--primary-gradient); border-color: transparent; color: white; } .cm-bulk-toolbar { position: fixed; top: 80px; left: 50%; transform: translateX(-50%); background: white; padding: 16px 24px; border-radius: 50px; box-shadow: 0 10px 40px rgba(0,0,0,0.15); display: none; align-items: center; gap: 16px; z-index: 2147483637; } .cm-bulk-toolbar.active { display: flex; } .cm-bulk-count { font-weight: 600; color: var(--text-primary); } .cm-settings { background: white; padding: 24px; border-radius: var(--border-radius); margin-top: 20px; } .cm-dark-mode .cm-settings { background: rgba(50,50,50,1); } .cm-settings-title { font-size: 18px; font-weight: 700; margin-bottom: 20px; color: var(--text-primary); display: flex; align-items: center; gap: 10px; } .cm-settings-group { margin-bottom: 20px; padding-bottom: 20px; border-bottom: 1px solid #e2e8f0; } .cm-settings-group:last-child { border-bottom: none; } .cm-toggle-switch { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; } .cm-switch { position: relative; width: 50px; height: 26px; } .cm-switch input { opacity: 0; width: 0; height: 0; } .cm-switch-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background: #cbd5e0; transition: 0.3s; border-radius: 26px; } .cm-switch-slider:before { position: absolute; content: ""; height: 20px; width: 20px; left: 3px; bottom: 3px; background: white; transition: 0.3s; border-radius: 50%; } .cm-switch input:checked + .cm-switch-slider { background: var(--primary-gradient); } .cm-switch input:checked + .cm-switch-slider:before { transform: translateX(24px); } .cm-note-editor { background: white; border-radius: 16px; padding: 20px; margin-top: 20px; } .cm-dark-mode .cm-note-editor { background: rgba(50,50,50,1); } .cm-note-toolbar { display: flex; gap: 8px; margin-bottom: 16px; padding-bottom: 16px; border-bottom: 2px solid #e2e8f0; } .cm-note-tool { width: 36px; height: 36px; border: 2px solid #e2e8f0; background: white; border-radius: 8px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: var(--transition); } .cm-dark-mode .cm-note-tool { background: rgba(40,40,40,1); border-color: rgba(255,255,255,0.1); } .cm-note-tool:hover { border-color: #667eea; background: rgba(102, 126, 234, 0.05); } .cm-note-tool.active { background: var(--primary-gradient); border-color: transparent; color: white; } .cm-note-content { min-height: 200px; padding: 16px; border: 2px solid #e2e8f0; border-radius: 12px; font-family: 'Inter', sans-serif; font-size: 15px; line-height: 1.6; } .cm-dark-mode .cm-note-content { background: rgba(40,40,40,1); border-color: rgba(255,255,255,0.1); color: #f0f0f0; } .cm-note-content:focus { outline: none; border-color: #667eea; } .cm-export-options { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin-top: 16px; } .cm-export-option { padding: 16px; border: 2px solid #e2e8f0; border-radius: 12px; cursor: pointer; text-align: center; transition: var(--transition); font-weight: 500; } .cm-dark-mode .cm-export-option { border-color: rgba(255,255,255,0.1); } .cm-export-option:hover { border-color: #667eea; background: rgba(102, 126, 234, 0.05); } .cm-export-option-icon { font-size: 24px; margin-bottom: 8px; } .cm-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 2147483635; } .cm-overlay.active { pointer-events: auto; } .cm-marker { position: absolute; cursor: pointer; pointer-events: auto; font-size: 32px; filter: drop-shadow(2px 4px 6px rgba(0,0,0,0.3)); transition: transform 0.2s; user-select: none; z-index: 2147483636; } .cm-marker:hover { transform: scale(1.15); } .cm-marker.dragging { cursor: grabbing; transform: scale(1.2); z-index: 2147483637; } .cm-marker.selected { filter: drop-shadow(0 0 10px rgba(245, 87, 108, 0.8)); } .cm-marker.anchored::after { content: '🔗'; position: absolute; top: -5px; right: -5px; font-size: 12px; background: white; border-radius: 50%; width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; } .cm-tooltip { position: absolute; background: rgba(0, 0, 0, 0.95); color: white; padding: 16px; border-radius: 12px; max-width: 350px; pointer-events: none; z-index: 2147483642; display: none; font-size: 14px; line-height: 1.6; backdrop-filter: blur(10px); border: 1px solid rgba(255,255,255,0.1); } .cm-tooltip.active { display: block; } .cm-tooltip.persistent { pointer-events: auto; } .cm-tooltip-close { float: right; cursor: pointer; padding: 4px; font-size: 16px; opacity: 0.8; transition: opacity 0.3s; } .cm-tooltip-close:hover { opacity: 1; } .cm-message { padding: 12px 20px; border-radius: 12px; margin: 12px 0; font-size: 14px; display: none; animation: slideInRight 0.3s ease; } @keyframes slideInRight { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } .cm-message.success { background: linear-gradient(135deg, rgba(67, 233, 123, 0.1), rgba(56, 249, 215, 0.1)); color: #22543d; border: 2px solid #48bb78; display: block; } .cm-message.error { background: linear-gradient(135deg, rgba(245, 101, 101, 0.1), rgba(237, 100, 166, 0.1)); color: #742a2a; border: 2px solid #fc8181; display: block; } .cm-spinner { width: 40px; height: 40px; border: 4px solid rgba(102, 126, 234, 0.2); border-top-color: #667eea; border-radius: 50%; animation: spin 1s linear infinite; } .cm-context-menu { position: absolute; background: white; border-radius: 12px; padding: 8px; box-shadow: 0 10px 40px rgba(0,0,0,0.15); z-index: 2147483641; display: none; min-width: 200px; } .cm-dark-mode .cm-context-menu { background: rgba(40,40,40,1); } .cm-context-menu.active { display: block; } .cm-shape-context-menu .cm-shape-context-item { padding: 10px 16px; border-radius: 8px; cursor: pointer; transition: var(--transition); display: flex; align-items: center; gap: 10px; user-select: none; } .cm-shape-context-menu .cm-shape-context-item:hover { background: rgba(102, 126, 234, 0.1); } .cm-context-item { padding: 10px 16px; border-radius: 8px; cursor: pointer !important; transition: var(--transition); display: flex; align-items: center; gap: 10px; user-select: none; background: rgba(26, 32, 44, 0.1); margin-bottom: 6px; } .cm-context-item:hover { background: rgba(102, 126, 234, 0.5); } .cm-marker-checkbox { width: 18px; height: 18px; margin-right: 12px; cursor: pointer; } `); // ======================================== // Configuration & State // ======================================== const WEBSITE_HOST = window.location.hostname; const STORAGE_KEY = `custom_markers_${WEBSITE_HOST}`; const NOTES_KEY = `custom_markers_notes_${WEBSITE_HOST}`; const WEBHOOK_URL = 'Add your webhook URL here'; // Add your webhook URL here const CLOUD_SYNC_API = 'Add your cloud sync API endpoint here'; // Add your cloud sync API endpoint here let state = { markers: [], shapes: [], paths: [], isEditMode: false, isDrawingMode: false, isBulkMode: false, isDragging: false, currentDragItem: null, editingMarkerId: null, selectedMarkers: new Set(), selectedShapeId: null, currentDrawingTool: 'rectangle', animationType: 'pulse', tooltip: null, isInitialized: false, currentTab: 'markers', isDarkMode: false, anchoredItems: new Map(), settings: { autoSave: true, animations: true, soundEffects: false, cloudSync: false, webhooks: false, contextDetection: true, }, }; // Icon Options with Categories - Fixed Emojis const iconOptions = { Basic: { '📍': 'Location Pin', '⭐': 'Star', '🌟': 'Special', '⚠️': 'Warning', '❗': 'Important', '❓': 'Question', '💡': 'Idea', '📝': 'Note', '📸': 'Photo', '📌': 'Push Pin', '📎': 'Attachment', }, Places: { '🏠': 'House', '⛺': 'Camp', '🏰': 'Castle', '🏭': 'Factory', '🎪': 'Event', '🏤': 'Post Office', '🏥': 'Hospital', '🏫': 'School', '🛕': 'Temple', '🏝️': 'Island', '🗺️': 'Map', }, 'Travel & Vehicles': { '⚓': 'Anchor', '🚢': 'Ship', '🚤': 'Boat', '🛶': 'Canoe', '🛳️': 'Cruise Ship', '✈️': 'Airplane', '🚁': 'Helicopter', '🚀': 'Rocket', '🚗': 'Car', '🚙': 'SUV', '🏎️': 'Race Car', '🚌': 'Bus', '🚉': 'Station', '🚆': 'Train', '🚲': 'Bicycle', '🛴': 'Scooter', '🛸': 'UFO', }, 'Combat & RPG': { '⚔️': 'Battle', '🗡': 'Dagger', '🏹': 'Bow & Arrow', '🛡️': 'Shield', '🔮': 'Crystal Ball', '🧙': 'Mage', '🧌': 'Troll', '🐲': 'Eastern Dragon', '💀': 'Danger', '🔥': 'Fire', '⚡': 'Lightning', '⛓️': 'Chains', '🪄': 'Wand', '📜': 'Scroll', }, 'Resources & Items': { '📦': 'Loot Box', '💎': 'Diamond', '🪙': 'Coin', '💰': 'Bag of Gold', '🥇': 'Medal', '🎁': 'Treasure', '🧪': 'Potion', '⚗️': 'Alchemy', '🔑': 'Key', '🧊': 'Ice Cube', '🗝': 'Ancient Key', '🧰': 'Toolbox', '⛏️': 'Pickaxe', '🔨': 'Hammer', '🪓': 'Axe', '🧲': 'Magnet', }, Nature: { '🌲': 'Tree', '🌳': 'Oak Tree', '🌴': 'Palm Tree', '🌱': 'Sprout', '🌾': 'Field', '🍄': 'Mushroom', '🌋': 'Volcano', '⛰️': 'Mountain', '🏞️': 'Park', '🏖️': 'Beach', '🌊': 'Wave', '🌌': 'Galaxy', '🌙': 'Moon', '☀️': 'Sun', '⭐': 'Star', '🌈': 'Rainbow', }, 'People & NPC': { '👑': 'King', '🤴': 'Prince', '👸': 'Princess', '🧝': 'Elf', '🧟': 'Zombie', '🧌': 'Troll', '🧚': 'Fairy', '🧜': 'Mermaid', '🧛': 'Vampire', '🧞': 'Genie', '🧓': 'Elder', '👩': 'Woman', '🧑': 'Person', '👥': 'Group', }, 'Animals & Creatures': { '🐉': 'Dragon', '🐺': 'Wolf', '🦁': 'Lion', '🐯': 'Tiger', '🐍': 'Snake', '🦅': 'Eagle', '🦇': 'Bat', '🦄': 'Unicorn', '🐗': 'Boar', '🐸': 'Frog', '🐟': 'Fish', '🦈': 'Shark', }, Entertainment: { '🎮': 'Gaming', '🎲': 'Dice', '🎵': 'Music', '🎸': 'Guitar', '🥁': 'Drum', '🎬': 'Video', '🎨': 'Art', '🎯': 'Target', '🏆': 'Trophy', }, }; // ======================================== // Utility Functions // ======================================== function escapeHtml(s) { const div = document.createElement('div'); div.textContent = String(s); return div.innerHTML; } function escapeAttr(s) { return String(s) .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function escapeSelector(selector) { if (typeof CSS !== 'undefined' && CSS.escape) { return CSS.escape(selector); } return selector.replace(/([!"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~])/g, '\\$1'); } function buildIconOptionsHTML(iconOptions) { let html = ''; const isNested = Object.values(iconOptions).some( (v) => typeof v === 'object' && v !== null ); if (isNested) { for (const [category, icons] of Object.entries(iconOptions)) { if (!icons || typeof icons !== 'object') continue; html += `<optgroup label="${escapeAttr(`----[${category}]----`)}">`; for (const [icon, label] of Object.entries(icons)) { html += `<option value="${escapeAttr(icon)}">${escapeHtml( icon )} ${escapeHtml(label)}</option>`; } html += `</optgroup>`; } } else { for (const [icon, label] of Object.entries(iconOptions)) { html += `<option value="${escapeAttr(icon)}">${escapeHtml( icon )} ${escapeHtml(label)}</option>`; } } return html; } function loadData() { try { const stored = localStorage.getItem(STORAGE_KEY); if (stored) { const data = JSON.parse(stored); state.markers = data.markers || []; state.shapes = data.shapes || []; state.paths = data.paths || []; state.settings = { ...state.settings, ...(data.settings || {}) }; state.isDarkMode = data.isDarkMode || false; } } catch (e) { console.error('Error loading data:', e); } } function saveData() { if (state.settings.autoSave) { try { localStorage.setItem( STORAGE_KEY, JSON.stringify({ markers: state.markers, shapes: state.shapes, paths: state.paths, settings: state.settings, isDarkMode: state.isDarkMode, lastSaved: new Date().toISOString(), }) ); } catch (e) { console.error('Error saving data:', e); showMessage('Failed to save data!', 'error'); } } } function exportToJSON() { const data = { website: { host: WEBSITE_HOST, url: window.location.href, title: document.title, exportDate: new Date().toISOString(), }, version: '2.0', markers: state.markers, shapes: state.shapes, paths: state.paths, settings: state.settings, }; const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json', }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `markers_${WEBSITE_HOST.replace( /\./g, '_' )}_${Date.now()}.json`; a.click(); URL.revokeObjectURL(url); showMessage('Exported to JSON successfully!', 'success'); } function exportToCSV() { let csv = 'ID,Title,Icon,Description,X,Y,Created,LinkURL,LinkTitle,ImageURL\n'; state.markers.forEach((marker) => { csv += `"${marker.id}","${marker.title}","${marker.icon}","${ marker.description || '' }","${marker.x}","${marker.y}","${marker.created}","${ marker.linkUrl || '' }","${marker.linkTitle || ''}","${marker.imageUrl || ''}"\n`; }); const blob = new Blob([csv], { type: 'text/csv' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `markers_${WEBSITE_HOST.replace( /\./g, '_' )}_${Date.now()}.csv`; a.click(); URL.revokeObjectURL(url); showMessage('Exported to CSV successfully!', 'success'); } function exportToPDF() { if (!window.jspdf || !window.jspdf.jsPDF) { alert('PDF export requires jsPDF library. Opening print dialog instead.'); window.print(); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF(); const pageHeight = doc.internal.pageSize.height; const pageWidth = doc.internal.pageSize.width; const margin = 25; let y = 20; // Title doc.setFont('helvetica', 'bold'); doc.setFontSize(20); doc.setTextColor(0, 0, 0); doc.text(`Custom Markers for ${WEBSITE_HOST}`, margin, y); y += 15; // Table of Contents doc.setFont('helvetica', 'bold'); doc.setFontSize(16); doc.text('Table of Contents', margin, y); y += 12; state.markers.forEach((marker, idx) => { if (y > pageHeight - 30) { doc.addPage(); y = 20; } doc.setFont('helvetica', 'normal'); doc.setFontSize(12); doc.setTextColor(30, 30, 120); doc.text(`${idx + 1}. ${marker.title}`, margin + 5, y); doc.setDrawColor(220); doc.setLineWidth(0.3); doc.line(margin, y + 2, pageWidth - margin, y + 2); y += 10; }); // Add markers details doc.addPage(); y = 20; state.markers.forEach((marker, index) => { if (y > pageHeight - 50) { doc.addPage(); y = 20; } // Marker title doc.setFont('helvetica', 'bold'); doc.setFontSize(15); doc.setTextColor(0, 80, 180); doc.text(`${index + 1}. ${marker.icon} ${marker.title}`, margin, y); y += 9; // Description if (marker.description) { doc.setFont('helvetica', 'italic'); doc.setFontSize(11); doc.setTextColor(80, 80, 80); const lines = doc.splitTextToSize( marker.description, pageWidth - margin * 2 ); lines.forEach((line) => { if (y > pageHeight - 30) { doc.addPage(); y = 20; } doc.text(line, margin + 7, y); y += 6; }); } // Position doc.setFont('helvetica', 'normal'); doc.setFontSize(11); doc.setTextColor(50, 50, 50); doc.text(`Position: ${marker.x}, ${marker.y}`, margin + 7, y); y += 8; // Link if (marker.linkUrl) { doc.setFont('helvetica', 'normal'); doc.setFontSize(11); doc.setTextColor(0, 0, 200); doc.textWithLink(marker.linkTitle || marker.linkUrl, margin + 7, y, { url: marker.linkUrl, }); y += 12; } // Separator doc.setDrawColor(200); doc.setLineWidth(0.4); doc.line(margin, y, pageWidth - margin, y); y += 10; }); // Add footer const pageCount = doc.internal.getNumberOfPages(); for (let i = 1; i <= pageCount; i++) { doc.setPage(i); doc.setFont('helvetica', 'italic'); doc.setFontSize(10); doc.setTextColor(120); doc.text( `Page ${i} / ${pageCount}`, pageWidth - margin - 20, pageHeight - 10 ); } doc.save(`markers_${WEBSITE_HOST.replace(/\./g, '_')}_${Date.now()}.pdf`); showMessage('Exported to PDF successfully!', 'success'); } function exportToMarkdown() { let md = `# Custom Markers for ${WEBSITE_HOST}\n\n`; md += `Exported: ${new Date().toLocaleString()}\n\n`; md += `## Markers (${state.markers.length})\n\n`; state.markers.forEach((marker) => { md += `### ${marker.icon} ${marker.title}\n`; md += `- **Position**: ${marker.x}, ${marker.y}\n`; if (marker.description) md += `- **Description**: ${marker.description}\n`; if (marker.linkUrl) md += `- **Link**: [${marker.linkTitle || 'View'}](${marker.linkUrl})\n`; if (marker.imageUrl) md += `- **Image**: \n`; md += `- **Created**: ${new Date(marker.created).toLocaleString()}\n\n`; }); const blob = new Blob([md], { type: 'text/markdown' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `markers_${WEBSITE_HOST.replace(/\./g, '_')}_${Date.now()}.md`; a.click(); URL.revokeObjectURL(url); showMessage('Exported to Markdown successfully!', 'success'); } async function sendWebhook(action, data) { if (state.settings.webhooks && !WEBHOOK_URL) { showMessage('⚠️ Webhooks enabled but no WEBHOOK_URL set!', 'error'); return; } if (!state.settings.webhooks) return; try { GM_xmlhttpRequest({ method: 'POST', url: WEBHOOK_URL, headers: { 'Content-Type': 'application/json', }, data: JSON.stringify({ action, website: WEBSITE_HOST, timestamp: new Date().toISOString(), data, }), onload: (response) => { console.log('Webhook sent:', response.status); }, onerror: (error) => { console.error('Webhook error:', error); }, }); } catch (error) { console.error('Webhook error:', error); } } async function syncToCloud() { if (state.settings.cloudSync && !CLOUD_SYNC_API) { showMessage('⚠️ Cloud Sync enabled but no CLOUD_SYNC_API set!', 'error'); return; } if (!state.settings.cloudSync) return; showMessage('Syncing to cloud...', 'success'); try { GM_xmlhttpRequest({ method: 'POST', url: CLOUD_SYNC_API, headers: { 'Content-Type': 'application/json', }, data: JSON.stringify({ website: WEBSITE_HOST, markers: state.markers, shapes: state.shapes, paths: state.paths, settings: state.settings, timestamp: new Date().toISOString(), }), onload: (response) => { showMessage('Synced to cloud successfully!', 'success'); }, onerror: (error) => { showMessage('Cloud sync failed!', 'error'); }, }); } catch (error) { showMessage('Cloud sync error!', 'error'); } } function fixSiteSpecificIssues() { const hostname = window.location.hostname; // Fix for sites with high z-index elements if ( hostname.includes('flaticon.com') || hostname.includes('onetrust.com') ) { setTimeout(() => { const style = document.createElement('style'); style.textContent = ` .cm-control-btn, .cm-panel, .cm-drawing-toolbar, .cm-bulk-toolbar, .cm-tooltip, .cm-context-menu, #cm-button-container { z-index: 2147483647 !important; } `; document.head.appendChild(style); }, 1000); } // Fix for Reddit if (hostname.includes('reddit.com')) { document.body.style.position = 'static'; } // Fix for Twitter/X if (hostname.includes('twitter.com') || hostname.includes('x.com')) { document.documentElement.style.transform = 'none'; } } function showMessage(msg, type) { const existingMsg = document.querySelector('.cm-message'); if (existingMsg) existingMsg.remove(); const msgEl = document.createElement('div'); msgEl.className = `cm-message ${type}`; msgEl.textContent = msg; const panel = document.querySelector('.cm-panel-content'); if (panel) { panel.insertBefore(msgEl, panel.firstChild); setTimeout(() => msgEl.remove(), 3000); } } function showActivationButton() { try { // Clean up any existing elements first const existingContainer = document.querySelector( '.cm-activate-container' ); if (existingContainer) { existingContainer.remove(); } // Don't show if already initialized if ( state.isInitialized && document.querySelector('#cm-button-container') ) { return; } const container = document.createElement('div'); container.className = 'cm-activate-container'; const activateBtn = document.createElement('button'); activateBtn.className = 'cm-activate-btn'; activateBtn.innerHTML = '<svg class="icon" viewBox="0 0 1024 1024"><path fill="#ffffff" d="M793.6 678.4c29.866667-51.2 81.066667-76.8 132.266667-72.533333 8.533333-29.866667 12.8-64 12.8-98.133334 0-29.866667-4.266667-59.733333-8.533334-85.333333-51.2 8.533333-106.666667-21.333333-136.533333-72.533333-29.866667-51.2-25.6-115.2 8.533333-153.6-46.933333-42.666667-98.133333-72.533333-157.866666-93.866667-17.066667 51.2-68.266667 85.333333-132.266667 85.333333s-110.933333-34.133333-132.266667-85.333333c-59.733333 21.333333-110.933333 55.466667-157.866666 98.133333 34.133333 42.666667 38.4 102.4 8.533333 153.6-29.866667 51.2-85.333333 76.8-136.533333 68.266667-4.266667 29.866667-8.533333 59.733333-8.533334 89.6 0 34.133333 4.266667 64 12.8 93.866667 51.2-4.266667 102.4 21.333333 132.266667 72.533333 25.6 51.2 25.6 106.666667-4.266667 149.333333 46.933333 42.666667 98.133333 72.533333 157.866667 89.6 21.333333-46.933333 68.266667-76.8 128-76.8s106.666667 29.866667 128 76.8c59.733333-17.066667 110.933333-51.2 157.866667-89.6-29.866667-42.666667-34.133333-98.133333-4.266667-149.333333z m-281.6 17.066667c-102.4 0-183.466667-81.066667-183.466667-183.466667S409.6 328.533333 512 328.533333s183.466667 81.066667 183.466667 183.466667-81.066667 183.466667-183.466667 183.466667z"/></svg>'; activateBtn.onclick = function () { const cont = document.querySelector('.cm-activate-container'); if (cont) cont.remove(); state.isInitialized = false; init(); }; const tooltip = document.createElement('div'); tooltip.className = 'cm-activate-tooltip'; tooltip.textContent = 'Activate Markers Pro'; container.appendChild(activateBtn); container.appendChild(tooltip); if (document.body) { document.body.appendChild(container); } else { document.addEventListener('DOMContentLoaded', () => showActivationButton() ); } } catch (error) { console.error('Error in showActivationButton:', error); } } // ======================================== // Integrated Button System Functions // ======================================== function createIntegratedButtons() { // Remove any existing button container const existing = document.getElementById('cm-button-container'); if (existing) existing.remove(); const buttonContainer = document.createElement('div'); buttonContainer.id = 'cm-button-container'; buttonContainer.innerHTML = ` <div class="button-box"> <div class="touch btn1"></div> <div class="touch btn2"></div> <div class="touch btn3"></div> <div class="touch btn4"></div> <div class="touch btn5"></div> <div class="touch btn6"></div> <!-- Button 1: Panel Toggle --> <div class="button" data-button="panel" title="Open Markers Panel (Ctrl+M)"> <span style="font-size: 28px;">📌</span> <div class="button-tooltip">Markers Panel</div> </div> <!-- Button 2: Edit Mode --> <div class="button" data-button="edit" title="Toggle Edit Mode (Ctrl+E)"> <span style="font-size: 28px;">✏️</span> <div class="button-tooltip">Edit Mode</div> </div> <!-- Button 3: Drawing Mode --> <div class="button" data-button="drawing" title="Toggle Drawing Mode (Ctrl+D)"> <span style="font-size: 28px;">🎨</span> <div class="button-tooltip">Drawing Mode</div> </div> <!-- Button 4: Bulk Select --> <div class="button" data-button="bulk" title="Bulk Select Mode (Ctrl+B)"> <span style="font-size: 28px;"><svg viewBox="0 0 64 64" width="20" height="20"><g><path fill="#394240" d="M12,60c0,2.211,1.789,4,4,4h32c2.211,0,4-1.789,4-4V16H12V60z M20,24h24v32H20V24z"/><path fill="#394240" d="M56,4H40c0-2.211-1.789-4-4-4h-8c-2.211,0-4,1.789-4,4H8C5.789,4,4,5.789,4,8s1.789,4,4,4h48c2.211,0,4-1.789,4-4S58.211,4,56,4z"/><rect x="20" y="24" fill="#B4CCB9" width="24" height="32"/></g></svg></span> <div class="button-tooltip">Bulk Select</div> </div> <!-- Button 5: Deactivate --> <div class="button" data-button="deactivate" title="Deactivate Markers Pro"> <span style="font-size: 28px;"><svg viewBox="0 0 1024 1024" class="icon"><path d="M513.28 80.64c-226.56 0-409.6 183.04-409.6 409.6s183.04 409.6 409.6 409.6 409.6-183.04 409.6-409.6-184.32-409.6-409.6-409.6z m0 153.6c47.36 0 92.16 12.8 129.28 35.84L293.12 619.52c-23.04-38.4-35.84-81.92-35.84-129.28 0-140.8 113.92-256 256-256z m0 512c-47.36 0-92.16-12.8-129.28-35.84l350.72-350.72c23.04 38.4 35.84 81.92 35.84 129.28-1.28 142.08-116.48 257.28-257.28 257.28z" fill="#D82621" /><path d="M513.28 912.64c-232.96 0-422.4-189.44-422.4-422.4s189.44-422.4 422.4-422.4 422.4 189.44 422.4 422.4-189.44 422.4-422.4 422.4z m0-819.2c-218.88 0-396.8 177.92-396.8 396.8s177.92 396.8 396.8 396.8 396.8-177.92 396.8-396.8-177.92-396.8-396.8-396.8z m0 665.6c-47.36 0-94.72-12.8-136.96-37.12-3.84-2.56-5.12-5.12-6.4-8.96 0-3.84 1.28-7.68 3.84-10.24l350.72-350.72c2.56-2.56 6.4-3.84 10.24-3.84 3.84 0 7.68 2.56 8.96 6.4C768 395.52 780.8 442.88 780.8 491.52c1.28 147.2-120.32 267.52-267.52 267.52z m-108.8-51.2c33.28 16.64 71.68 25.6 108.8 25.6 134.4 0 243.2-108.8 243.2-243.2 0-37.12-8.96-75.52-25.6-108.8L404.48 707.84z m-111.36-75.52h-1.28c-3.84 0-7.68-2.56-8.96-6.4-24.32-40.96-37.12-88.32-37.12-136.96 0-148.48 120.32-268.8 268.8-268.8 47.36 0 94.72 12.8 136.96 37.12 3.84 2.56 5.12 5.12 6.4 8.96s-1.28 7.68-3.84 10.24L302.08 628.48c-2.56 2.56-6.4 3.84-8.96 3.84z m220.16-385.28c-134.4 0-243.2 108.8-243.2 243.2 0 37.12 8.96 75.52 25.6 108.8l326.4-326.4c-34.56-16.64-71.68-25.6-108.8-25.6z" fill="#231C1C" /></svg></span> <div class="button-tooltip">Deactivate</div> </div> <!-- Button 6: Main Settings (Gear) --> <div class="button" data-button="main" title="Custom Markers Pro"> <svg class="icon" viewBox="0 0 1024 1024"> <path fill="#ffffff" d="M793.6 678.4c29.866667-51.2 81.066667-76.8 132.266667-72.533333 8.533333-29.866667 12.8-64 12.8-98.133334 0-29.866667-4.266667-59.733333-8.533334-85.333333-51.2 8.533333-106.666667-21.333333-136.533333-72.533333-29.866667-51.2-25.6-115.2 8.533333-153.6-46.933333-42.666667-98.133333-72.533333-157.866666-93.866667-17.066667 51.2-68.266667 85.333333-132.266667 85.333333s-110.933333-34.133333-132.266667-85.333333c-59.733333 21.333333-110.933333 55.466667-157.866666 98.133333 34.133333 42.666667 38.4 102.4 8.533333 153.6-29.866667 51.2-85.333333 76.8-136.533333 68.266667-4.266667 29.866667-8.533333 59.733333-8.533334 89.6 0 34.133333 4.266667 64 12.8 93.866667 51.2-4.266667 102.4 21.333333 132.266667 72.533333 25.6 51.2 25.6 106.666667-4.266667 149.333333 46.933333 42.666667 98.133333 72.533333 157.866667 89.6 21.333333-46.933333 68.266667-76.8 128-76.8s106.666667 29.866667 128 76.8c59.733333-17.066667 110.933333-51.2 157.866667-89.6-29.866667-42.666667-34.133333-98.133333-4.266667-149.333333z m-281.6 17.066667c-102.4 0-183.466667-81.066667-183.466667-183.466667S409.6 328.533333 512 328.533333s183.466667 81.066667 183.466667 183.466667-81.066667 183.466667-183.466667 183.466667z"/> </svg> <div class="button-tooltip">Settings</div> </div> </div> `; document.body.appendChild(buttonContainer); // Add event listeners for each button buttonContainer .querySelector('[data-button="panel"]') .addEventListener('click', togglePanel); buttonContainer .querySelector('[data-button="edit"]') .addEventListener('click', toggleEditMode); buttonContainer .querySelector('[data-button="drawing"]') .addEventListener('click', toggleDrawingMode); buttonContainer .querySelector('[data-button="bulk"]') .addEventListener('click', toggleBulkMode); buttonContainer .querySelector('[data-button="deactivate"]') .addEventListener('click', deactivateAll); buttonContainer .querySelector('[data-button="main"]') .addEventListener('click', () => { togglePanel(); switchTab('settings'); }); // Right-click on main button for quick actions buttonContainer .querySelector('[data-button="main"]') .addEventListener('contextmenu', (e) => { e.preventDefault(); showQuickActionsMenu(e); }); } function showQuickActionsMenu(e) { const existing = document.querySelector('.cm-quick-actions'); if (existing) existing.remove(); const menu = document.createElement('div'); menu.className = 'cm-quick-actions'; menu.style.cssText = ` position: fixed; left: ${e.pageX}px; top: ${e.pageY}px; background: white; border-radius: 12px; padding: 8px; box-shadow: 0 10px 40px rgba(0,0,0,0.15); z-index: 2147483647; min-width: 180px; `; menu.innerHTML = ` <div class="cm-quick-action" data-action="add-marker">➕ Quick Add Marker</div> <div class="cm-quick-action" data-action="export">📥 Quick Export</div> <div class="cm-quick-action" data-action="clear-all">🗑️ Clear All</div> <div class="cm-quick-action" data-action="help">❓ Help</div> `; document.body.appendChild(menu); menu.querySelectorAll('.cm-quick-action').forEach((item) => { item.addEventListener('click', () => { const action = item.dataset.action; switch (action) { case 'add-marker': quickAddMarker(); break; case 'export': exportToJSON(); break; case 'clear-all': if (confirm('Clear all markers and drawings?')) { state.markers = []; state.shapes = []; state.paths = []; saveData(); renderMarkers(); renderShapes(); renderPaths(); showMessage('All data cleared!', 'success'); } break; case 'help': showHelpDialog(); break; } menu.remove(); }); }); // Remove menu when clicking elsewhere setTimeout(() => { document.addEventListener('click', () => menu.remove(), { once: true }); }, 0); } function quickAddMarker() { const title = prompt('Enter marker title:'); if (!title) return; const marker = { id: Date.now().toString(), title, icon: '📍', animation: 'pulse', description: '', x: '50%', y: '50%', created: new Date().toISOString(), website: WEBSITE_HOST, context: window.location.pathname, }; state.markers.push(marker); saveData(); renderMarkers(); updateMarkersList(); showMessage('Marker added! Enter edit mode to position it.', 'success'); } function showHelpDialog() { alert(`Custom Markers Pro - Keyboard Shortcuts: Ctrl+M : Toggle Panel Ctrl+E : Toggle Edit Mode Ctrl+D : Toggle Drawing Mode Ctrl+B : Toggle Bulk Select Ctrl+S : Save Ctrl+F : Search Markers Right-click on markers for context menu. Right-click on main button for quick actions.`); } function deactivateAll() { if (confirm('Deactivate Custom Markers Pro?')) { cleanupExistingUI(); const buttonContainer = document.getElementById('cm-button-container'); if (buttonContainer) buttonContainer.remove(); state.isInitialized = false; state.isEditMode = false; state.isDrawingMode = false; state.isBulkMode = false; showActivationButton(); } } // ======================================== // Initialize Function // ======================================== function init() { // Don't re-init if UI already exists if (state.isInitialized && document.querySelector('#cm-button-container')) { togglePanel(); return; } state.isInitialized = true; loadData(); // Clean up any existing UI before creating new cleanupExistingUI(); // Create the integrated button system createIntegratedButtons(); createUI(); loadNotes(); renderMarkers(); renderShapes(); renderPaths(); setupEventListeners(); updateMarkersList(); applyTheme(); sendWebhook('init', { markersCount: state.markers.length }); fixSiteSpecificIssues(); } function cleanupExistingUI() { const elements = [ '.cm-control-btn', '.cm-panel', '.cm-overlay', '.cm-drawing-canvas', '.cm-svg-canvas', '.cm-drawing-toolbar', '.cm-bulk-toolbar', '.cm-tooltip', '.cm-context-menu', '.cm-shape-context-menu', '#cm-button-container', ]; elements.forEach((selector) => { const el = document.querySelector(selector); if (el) el.remove(); }); // Clean up global event handlers if (window._cmEventHandlers) { Object.values(window._cmEventHandlers).forEach((handler) => { if (handler && handler.remove) { handler.remove(); } }); window._cmEventHandlers = {}; } } // ======================================== // Create UI Elements // ======================================== function createUI() { if (document.querySelector('.cm-control-btn')) return; // Main control button const controlBtn = document.createElement('div'); controlBtn.className = 'cm-control-btn'; controlBtn.innerHTML = '<span>🚀</span><span>Markers Pro</span>'; document.body.appendChild(controlBtn); // Side panel const panel = document.createElement('div'); panel.className = 'cm-panel'; panel.innerHTML = ` <div class="cm-panel-header"> <div class="cm-panel-title"> <span>🎯</span> <span>CustomMarkers</span> </div> <div class="cm-header-buttons"> <button class="cm-theme-toggle" id="theme-toggle" title="Toggle Dark/Light Mode">🌓</button> <button class="cm-close-btn" id="panel-close">✕</button> </div> </div> <div class="cm-panel-content"> <div class="cm-tabs"> <button class="cm-tab active" data-tab="markers">📍 Markers</button> <button class="cm-tab" data-tab="notes">📝 Notes</button> <button class="cm-tab" data-tab="settings">⚙️ Settings</button> </div> <div class="cm-tab-content" id="tab-markers"> <div class="cm-search-container"> <input type="text" class="cm-search-input" id="search-markers" placeholder="Search markers..."> <span class="cm-search-icon">🔍</span> </div> <div class="cm-form"> <div class="cm-form-group"> <label class="cm-label">Title</label> <input type="text" class="cm-input" id="marker-title" placeholder="Enter marker title"> </div> <div class="cm-form-group"> <label class="cm-label">Icon & Animation</label> <div style="display: flex; gap: 12px;"> <select class="cm-select" id="marker-icon" style="flex: 1;"> ${buildIconOptionsHTML(iconOptions)} </select> <select class="cm-select" id="marker-animation" style="flex: 1;"> <option value="none">No Animation</option> <option value="pulse">Pulse</option> <option value="bounce">Bounce</option> <option value="rotate">Rotate</option> <option value="glow">Glow</option> <option value="shake">Shake</option> <option value="wiggle">Wiggle</option> <option value="fade">Fade</option> <option value="flip">Flip</option> <option value="zoom">Zoom</option> <option value="slide">Slide</option> <option value="spin">Spin</option> </select> </div> <div class="cm-icon-preview" id="icon-preview">📍</div> </div> <div class="cm-form-group"> <label class="cm-label">Description</label> <textarea class="cm-textarea" id="marker-description" placeholder="Add notes..."></textarea> </div> <div class="cm-form-group"> <label class="cm-label">Link URL</label> <input type="text" class="cm-input" id="marker-link-url" placeholder="https://example.com"> </div> <div class="cm-form-group"> <label class="cm-label">Link Title</label> <input type="text" class="cm-input" id="marker-link-title" placeholder="Click here for more info"> </div> <div class="cm-form-group"> <label class="cm-label">Image URL</label> <input type="text" class="cm-input" id="marker-image-url" placeholder="https://example.com/image.jpg"> </div> <div class="cm-form-group"> <button class="cm-btn cm-btn-primary" id="add-marker-btn">➕ Add Marker</button> <button class="cm-btn cm-btn-primary" id="save-marker-btn" style="display:none;">💾 Save Changes</button> <button class="cm-btn cm-btn-secondary" id="cancel-edit-btn" style="display:none;">Cancel</button> </div> </div> <div class="cm-export-options"> <div class="cm-export-option" id="export-json"> <div class="cm-export-option-icon">📄</div> <div>JSON</div> </div> <div class="cm-export-option" id="export-csv"> <div class="cm-export-option-icon">📊</div> <div>CSV</div> </div> <div class="cm-export-option" id="export-markdown"> <div class="cm-export-option-icon">📝</div> <div>Markdown</div> </div> <div class="cm-export-option" id="export-pdf"> <div class="cm-export-option-icon">📑</div> <div>PDF</div> </div> </div> <div class="cm-markers-list"> <h3 style="margin: 20px 0;">📌 Markers (<span id="markers-count">0</span>)</h3> <div id="markers-container"></div> </div> </div> <div class="cm-tab-content" id="tab-notes" style="display:none;"> <div class="cm-note-editor"> <div class="cm-note-toolbar"> <button class="cm-note-tool" data-format="bold">𝐁</button> <button class="cm-note-tool" data-format="italic">𝐼</button> <button class="cm-note-tool" data-format="underline">U̲</button> <button class="cm-note-tool" data-format="list">☰</button> <button class="cm-note-tool" data-format="link">🔗</button> </div> <div class="cm-note-content" contenteditable="true" id="note-content"> Start taking notes here... </div> <button class="cm-btn cm-btn-primary" id="save-note-btn" style="margin-top: 16px;">💾 Save Note</button> </div> </div> <div class="cm-tab-content" id="tab-settings" style="display:none;"> <div class="cm-settings"> <div class="cm-settings-title"> <span>⚙️</span> <span>Settings</span> </div> <div class="cm-settings-group"> <div class="cm-toggle-switch"> <span>Auto Save</span> <label class="cm-switch"> <input type="checkbox" id="setting-autosave" checked> <span class="cm-switch-slider"></span> </label> </div> <div class="cm-toggle-switch"> <span>Animations</span> <label class="cm-switch"> <input type="checkbox" id="setting-animations" checked> <span class="cm-switch-slider"></span> </label> </div> <div class="cm-toggle-switch"> <span>Cloud Sync</span> <label class="cm-switch"> <input type="checkbox" id="setting-cloudsync"> <span class="cm-switch-slider"></span> </label> </div> <div class="cm-toggle-switch"> <span>Webhooks</span> <label class="cm-switch"> <input type="checkbox" id="setting-webhooks"> <span class="cm-switch-slider"></span> </label> </div> </div> <div class="cm-form-group"> <label class="cm-label">Webhook URL</label> <input type="text" class="cm-input" id="webhook-url" placeholder="https://your-webhook-url.com"> </div> <div class="cm-form-group" style="margin-top: 30px; padding-top: 20px; border-top: 2px solid #e2e8f0;"> <div style="text-align: center;"> <h3 style="margin-bottom: 16px; color: var(--text-primary);">☕ Support Development</h3> <p style="margin-bottom: 20px; color: var(--text-secondary); font-size: 14px;"> If you find this tool useful, consider buying me a coffee! </p> <button class="cm-btn" id="donate-btn" style=" background: linear-gradient(135deg, #FFC439 0%, #FF9B3D 100%); color: white; padding: 14px 28px; font-size: 16px; font-weight: 600; box-shadow: 0 4px 15px rgba(255, 196, 57, 0.3); "> <span style="font-size: 20px; vertical-align: middle;">☕</span> Buy Me a Coffee via PayPal </button> <p style="margin-top: 12px; font-size: 12px; color: var(--text-secondary);"> • Made with ❤️ by Disruptor </p> </div> </div> <button class="cm-btn cm-btn-success" id="sync-now-btn">☁️ Sync Now</button> <button class="cm-btn cm-btn-danger" id="clear-all-btn" style="margin-top: 20px;">🗑️ Clear All Data</button> </div> </div> </div> `; document.body.appendChild(panel); // Create overlay for markers const overlay = document.createElement('div'); overlay.className = 'cm-overlay'; overlay.id = 'marker-overlay'; document.body.appendChild(overlay); // Create drawing canvas const drawingCanvas = document.createElement('div'); drawingCanvas.className = 'cm-drawing-canvas'; drawingCanvas.id = 'drawing-canvas'; document.body.appendChild(drawingCanvas); // Create SVG canvas for pen tool const svgCanvas = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' ); svgCanvas.setAttribute('class', 'cm-svg-canvas'); svgCanvas.setAttribute('id', 'svg-canvas'); document.body.appendChild(svgCanvas); // Create drawing toolbar const drawingToolbar = document.createElement('div'); drawingToolbar.className = 'cm-drawing-toolbar'; drawingToolbar.innerHTML = ` <div class="cm-drawing-toolbar-section"> <div class="cm-drawing-toolbar-title">Shapes</div> <div class="cm-drawing-tools"> <button class="cm-drawing-tool active" data-tool="rectangle" title="Rectangle">▭</button> <button class="cm-drawing-tool" data-tool="circle" title="Circle">○</button> <button class="cm-drawing-tool" data-tool="line" title="Line">╱</button> <button class="cm-drawing-tool" data-tool="arrow" title="Arrow">→</button> <button class="cm-drawing-tool" data-tool="text" title="Text">T</button> <button class="cm-drawing-tool" data-tool="pen" title="Free Draw">✏️</button> </div> </div> <div class="cm-drawing-toolbar-section"> <div class="cm-drawing-toolbar-title">Actions</div> <div class="cm-drawing-tools"> <button class="cm-drawing-tool" data-tool="select" title="Select">👆</button> <button class="cm-drawing-tool" data-tool="delete" title="Delete">🗑️</button> <button class="cm-drawing-tool" data-tool="clear" title="Clear All">💣</button> </div> </div> <div class="cm-drawing-toolbar-section"> <div class="cm-drawing-toolbar-title">Style</div> <input type="color" id="draw-color" value="#667eea" style="width: 100%; height: 32px; border-radius: 8px; border: 2px solid #e2e8f0;"> <input type="range" id="draw-thickness" min="1" max="20" value="3" style="width: 100%; margin-top: 8px;"> <select id="draw-style" style="width: 100%; margin-top: 8px; padding: 8px; border-radius: 8px; border: 2px solid #e2e8f0;"> <option value="solid">Solid</option> <option value="dashed">Dashed</option> <option value="dotted">Dotted</option> </select> </div> <div class="cm-drawing-toolbar-section" id="text-options" style="display:none;"> <div class="cm-drawing-toolbar-title">Text Style</div> <label style="display:flex;align-items:center;gap:8px;margin-bottom:8px;"> <input type="checkbox" id="text-bold"> <span style="color:#8B4513;">Bold</span> </label> <label style="display:flex;align-items:center;gap:8px;margin-bottom:8px;"> <input type="checkbox" id="text-italic"> <span style="color:#8B4513;">Italic</span> </label> <label style="display:block;margin-bottom:4px;">Size</label> <input type="range" id="text-size" min="10" max="48" value="16" style="width:100%;"> </div> `; document.body.appendChild(drawingToolbar); // Create bulk toolbar const bulkToolbar = document.createElement('div'); bulkToolbar.className = 'cm-bulk-toolbar'; bulkToolbar.innerHTML = ` <span class="cm-bulk-count">0 selected</span> <button class="cm-btn cm-btn-primary" id="bulk-delete">Delete</button> <button class="cm-btn cm-btn-secondary" id="bulk-export">Export</button> <button class="cm-btn cm-btn-secondary" id="bulk-cancel">Cancel</button> `; document.body.appendChild(bulkToolbar); // Create tooltip state.tooltip = document.createElement('div'); state.tooltip.className = 'cm-tooltip'; document.body.appendChild(state.tooltip); // Create context menu const contextMenu = document.createElement('div'); contextMenu.className = 'cm-context-menu'; contextMenu.innerHTML = ` <div class="cm-context-item" data-action="edit">✏️ Edit</div> <div class="cm-context-item" data-action="duplicate">📋 Duplicate</div> <div class="cm-context-item" data-action="delete">🗑️ Delete</div> <div class="cm-context-item" data-action="animate">✨ Change Animation</div> <div class="cm-context-item" data-action="anchor">⚓ Anchor to Element</div> `; document.body.appendChild(contextMenu); // Create separate context menu for shapes const shapeContextMenu = document.createElement('div'); shapeContextMenu.className = 'cm-context-menu cm-shape-context-menu'; shapeContextMenu.innerHTML = ` <div class="cm-shape-context-item" data-action="anchor-shape">⚓ Anchor to Element</div> <div class="cm-shape-context-item" data-action="unanchor-shape">🔓 Un-anchor</div> <div class="cm-shape-context-item" data-action="delete-shape">🗑️ Delete Shape</div> `; document.body.appendChild(shapeContextMenu); } // ======================================== // Event Listeners // ======================================== function setupEventListeners() { // Store handlers globally for cleanup window._cmEventHandlers = window._cmEventHandlers || {}; // Panel controls document .querySelector('.cm-control-btn') .addEventListener('click', togglePanel); document .getElementById('panel-close') .addEventListener('click', togglePanel); // Theme toggle document .getElementById('theme-toggle') .addEventListener('click', toggleTheme); // Tabs document.querySelectorAll('.cm-tab').forEach((tab) => { tab.addEventListener('click', (e) => { switchTab(e.target.dataset.tab); }); }); // Marker form document.getElementById('marker-icon').addEventListener('change', (e) => { document.getElementById('icon-preview').textContent = e.target.value; }); document .getElementById('marker-animation') .addEventListener('change', (e) => { const preview = document.getElementById('icon-preview'); preview.className = `cm-icon-preview animate-${e.target.value}`; }); document .getElementById('add-marker-btn') .addEventListener('click', addNewMarker); document .getElementById('save-marker-btn') .addEventListener('click', saveEditedMarker); document .getElementById('cancel-edit-btn') .addEventListener('click', cancelEdit); // Search document.getElementById('search-markers').addEventListener('input', (e) => { searchMarkers(e.target.value); }); // Export options document .getElementById('export-json') .addEventListener('click', exportToJSON); document .getElementById('export-csv') .addEventListener('click', exportToCSV); document .getElementById('export-markdown') .addEventListener('click', exportToMarkdown); document .getElementById('export-pdf') .addEventListener('click', exportToPDF); // Settings document .getElementById('setting-autosave') .addEventListener('change', (e) => { state.settings.autoSave = e.target.checked; saveData(); }); document .getElementById('setting-animations') .addEventListener('change', (e) => { state.settings.animations = e.target.checked; saveData(); renderMarkers(); }); document .getElementById('setting-cloudsync') .addEventListener('change', (e) => { state.settings.cloudSync = e.target.checked; saveData(); }); document .getElementById('setting-webhooks') .addEventListener('change', (e) => { state.settings.webhooks = e.target.checked; saveData(); }); document .getElementById('sync-now-btn') .addEventListener('click', syncToCloud); document.getElementById('clear-all-btn').addEventListener('click', () => { if ( confirm( 'Are you sure you want to clear all data? This cannot be undone!' ) ) { state.markers = []; state.shapes = []; state.paths = []; saveData(); updateMarkersList(); renderMarkers(); renderShapes(); renderPaths(); showMessage('All data cleared!', 'success'); } }); // Note editor document.querySelectorAll('.cm-note-tool').forEach((tool) => { tool.addEventListener('click', (e) => { applyFormat(e.target.dataset.format); }); }); document .getElementById('save-note-btn') .addEventListener('click', saveNote); // Drawing tools document.querySelectorAll('.cm-drawing-tool').forEach((tool) => { tool.addEventListener('click', (e) => { const toolType = e.target.dataset.tool; if (toolType === 'clear') { if (confirm('Clear all drawings?')) { state.shapes = []; state.paths = []; saveData(); renderShapes(); renderPaths(); } } else { selectDrawingTool(toolType); document.getElementById('text-options').style.display = toolType === 'text' ? 'block' : 'none'; } }); }); // Bulk actions document .getElementById('bulk-delete') .addEventListener('click', bulkDelete); document .getElementById('bulk-export') .addEventListener('click', bulkExport); document.getElementById('bulk-cancel').addEventListener('click', () => { state.isBulkMode = false; state.selectedMarkers.clear(); updateBulkToolbar(); renderMarkers(); }); // Keyboard shortcuts document.addEventListener('keydown', (e) => { // Ctrl+M for panel if (e.ctrlKey && e.key === 'm') { e.preventDefault(); togglePanel(); } // Ctrl+E for edit mode else if (e.ctrlKey && e.key === 'e') { e.preventDefault(); toggleEditMode(); } // Ctrl+D for drawing mode else if (e.ctrlKey && e.key === 'd') { e.preventDefault(); toggleDrawingMode(); } // Ctrl+B for bulk mode else if (e.ctrlKey && e.key === 'b') { e.preventDefault(); toggleBulkMode(); } // Ctrl+S for save else if (e.ctrlKey && e.key === 's') { e.preventDefault(); saveData(); showMessage('Saved!', 'success'); } // Ctrl+F for search else if (e.ctrlKey && e.key === 'f') { e.preventDefault(); if (document.getElementById('search-markers')) { togglePanel(); document.getElementById('search-markers').focus(); } } }); // PayPal Donation document.getElementById('donate-btn').addEventListener('click', () => { const paypalUrl = 'https://www.paypal.com/paypalme/TamamMatta'; window.open(paypalUrl, '_blank'); showMessage('Thank you for your support! 💙', 'success'); }); // Add scroll listener for anchored markers window.addEventListener( 'scroll', () => { const hasAnchoredMarkers = state.markers.some((m) => m.isAnchored); const hasAnchoredShapes = state.shapes.some((s) => s.isAnchored); const hasAnchoredPaths = state.paths.some((p) => p.isAnchored); if (hasAnchoredMarkers) { renderMarkers(); } if (hasAnchoredShapes) { renderShapes(); } if (hasAnchoredPaths) { renderPaths(); } }, { passive: true } ); // Context menu document.addEventListener('contextmenu', (e) => { const marker = e.target.closest('.cm-marker'); if (marker) { e.preventDefault(); showContextMenu(e, marker.dataset.markerId); } }); document.addEventListener('click', (e) => { const contextMenu = document.querySelector('.cm-context-menu'); if (!contextMenu) return; // Add null check const contextItem = e.target.closest('.cm-context-item'); if (contextItem) { const action = contextItem.dataset.action; const contextType = contextMenu.dataset.contextType; if (contextType === 'shape') { const shapeId = contextMenu.dataset.shapeId; if (action === 'anchor-shape') { anchorShape(shapeId); } else if (action === 'delete-shape') { state.shapes = state.shapes.filter((s) => s.id !== shapeId); saveData(); renderShapes(); } contextMenu.classList.remove('active'); } else { handleContextAction(action); } } else if (contextMenu.contains(e.target)) { // Do nothing if clicking inside menu } else { // Hide menu when clicking outside contextMenu.classList.remove('active'); } }); // Shape context menu handlers document.addEventListener('click', (e) => { const shapeMenu = document.querySelector('.cm-shape-context-menu'); if (shapeMenu && !shapeMenu.contains(e.target)) { shapeMenu.classList.remove('active'); } }); // Fix the event delegation for shape context items document.addEventListener('click', (e) => { if (e.target.classList.contains('cm-shape-context-item')) { const action = e.target.dataset.action; const menu = document.querySelector('.cm-shape-context-menu'); const shapeId = menu.dataset.shapeId; const shapeType = menu.dataset.shapeType; if (action === 'anchor-shape') { const wasDrawingMode = state.isDrawingMode; if (wasDrawingMode) { state.isDrawingMode = false; const canvas = document.getElementById('drawing-canvas'); if (canvas) canvas.style.pointerEvents = 'none'; } anchorShape(shapeId); setTimeout(() => { if (wasDrawingMode) { state.isDrawingMode = true; const canvas = document.getElementById('drawing-canvas'); if (canvas) canvas.style.pointerEvents = 'auto'; } }, 100); } else if (action === 'unanchor-shape') { let unanchored = false; if (shapeType === 'path') { const pathIndex = state.paths.findIndex((p) => p.id === shapeId); if (pathIndex !== -1) { if (state.paths[pathIndex].originalPath) { state.paths[pathIndex].path = state.paths[pathIndex].originalPath; delete state.paths[pathIndex].originalPath; } delete state.paths[pathIndex].isAnchored; delete state.paths[pathIndex].anchorSelector; delete state.paths[pathIndex].anchorOffsetX; delete state.paths[pathIndex].anchorOffsetY; unanchored = true; saveData(); renderPaths(); } } else { const shapeIndex = state.shapes.findIndex((s) => s.id === shapeId); if (shapeIndex !== -1) { if (state.shapes[shapeIndex].originalX) { state.shapes[shapeIndex].x = state.shapes[shapeIndex].originalX; delete state.shapes[shapeIndex].originalX; } if (state.shapes[shapeIndex].originalY) { state.shapes[shapeIndex].y = state.shapes[shapeIndex].originalY; delete state.shapes[shapeIndex].originalY; } delete state.shapes[shapeIndex].isAnchored; delete state.shapes[shapeIndex].anchorSelector; delete state.shapes[shapeIndex].anchorOffsetX; delete state.shapes[shapeIndex].anchorOffsetY; unanchored = true; saveData(); renderShapes(); } } if (unanchored) { showMessage('Un-anchored successfully', 'success'); } else { showMessage('Failed to un-anchor', 'error'); } } else if (action === 'delete-shape') { if (shapeType === 'path') { state.paths = state.paths.filter((p) => p.id !== shapeId); saveData(); renderPaths(); } else { state.shapes = state.shapes.filter((s) => s.id !== shapeId); saveData(); renderShapes(); } } menu.classList.remove('active'); } }); } // ======================================== // Core Functions // ======================================== function togglePanel() { const panel = document.querySelector('.cm-panel'); if (panel) { panel.classList.toggle('active'); } } function toggleTheme() { state.isDarkMode = !state.isDarkMode; applyTheme(); saveData(); } function applyTheme() { const panel = document.querySelector('.cm-panel'); if (panel) { panel.classList.toggle('cm-dark-mode', state.isDarkMode); } const contextMenu = document.querySelector('.cm-context-menu'); if (contextMenu) { contextMenu.classList.toggle('cm-dark-mode', state.isDarkMode); } } function switchTab(tabName) { state.currentTab = tabName; document.querySelectorAll('.cm-tab').forEach((tab) => { tab.classList.toggle('active', tab.dataset.tab === tabName); }); document.querySelectorAll('.cm-tab-content').forEach((content) => { content.style.display = content.id === `tab-${tabName}` ? 'block' : 'none'; }); } function toggleEditMode() { if (!state.isEditMode && state.isDrawingMode) { toggleDrawingMode(); } state.isEditMode = !state.isEditMode; const overlay = document.getElementById('marker-overlay'); const button = document.querySelector('[data-button="edit"]'); if (overlay) overlay.classList.toggle('active', state.isEditMode); if (button) button.classList.toggle('active', state.isEditMode); if (!state.isEditMode) { document.body.style.userSelect = ''; } else { document.body.style.userSelect = 'none'; } } function toggleDrawingMode() { if (!state.isDrawingMode && state.isEditMode) { toggleEditMode(); } state.isDrawingMode = !state.isDrawingMode; const canvas = document.getElementById('drawing-canvas'); const toolbar = document.querySelector('.cm-drawing-toolbar'); const button = document.querySelector('[data-button="drawing"]'); const svgCanvas = document.getElementById('svg-canvas'); if (canvas) canvas.classList.toggle('active', state.isDrawingMode); if (toolbar) toolbar.classList.toggle('active', state.isDrawingMode); if (button) button.classList.toggle('active', state.isDrawingMode); if (svgCanvas) { svgCanvas.style.pointerEvents = state.isDrawingMode ? 'auto' : 'none'; } if (state.isDrawingMode) { if (canvas) { canvas.style.pointerEvents = 'auto'; canvas.style.pointerEvents = state.currentDrawingTool === 'select' || state.currentDrawingTool === 'delete' ? 'none' : 'auto'; } initDrawing(); } else { if (canvas) { canvas.style.pointerEvents = 'none'; } } } function toggleBulkMode() { state.isBulkMode = !state.isBulkMode; const button = document.querySelector('[data-button="bulk"]'); if (button) button.classList.toggle('active', state.isBulkMode); updateBulkToolbar(); if (!state.isBulkMode) { state.selectedMarkers.clear(); renderMarkers(); } } function addNewMarker() { const title = document.getElementById('marker-title').value.trim(); const icon = document.getElementById('marker-icon').value; const animation = document.getElementById('marker-animation').value; const description = document .getElementById('marker-description') .value.trim(); const linkUrl = document.getElementById('marker-link-url').value.trim(); const linkTitle = document.getElementById('marker-link-title').value.trim(); const imageUrl = document.getElementById('marker-image-url').value.trim(); if (!title) { showMessage('Please enter a title!', 'error'); return; } const marker = { id: Date.now().toString(), title, icon, animation, description, linkUrl, linkTitle, imageUrl, x: 40 + Math.random() * 20 + '%', y: 40 + Math.random() * 20 + '%', created: new Date().toISOString(), website: WEBSITE_HOST, context: window.location.pathname, }; state.markers.push(marker); saveData(); resetForm(); updateMarkersList(); renderMarkers(); showMessage('Marker added!', 'success'); sendWebhook('marker_added', marker); } function saveEditedMarker() { const title = document.getElementById('marker-title').value.trim(); const icon = document.getElementById('marker-icon').value; const animation = document.getElementById('marker-animation').value; const description = document .getElementById('marker-description') .value.trim(); const linkUrl = document.getElementById('marker-link-url').value.trim(); const linkTitle = document.getElementById('marker-link-title').value.trim(); const imageUrl = document.getElementById('marker-image-url').value.trim(); if (!title || !state.editingMarkerId) { showMessage('Please enter a title!', 'error'); return; } const markerIndex = state.markers.findIndex( (m) => m.id === state.editingMarkerId ); if (markerIndex !== -1) { state.markers[markerIndex] = { ...state.markers[markerIndex], title, icon, animation, description, linkUrl, linkTitle, imageUrl, updated: new Date().toISOString(), }; saveData(); resetForm(); updateMarkersList(); renderMarkers(); showMessage('Marker updated!', 'success'); sendWebhook('marker_updated', state.markers[markerIndex]); } } function cancelEdit() { resetForm(); } function resetForm() { document.getElementById('marker-title').value = ''; document.getElementById('marker-icon').value = '📍'; document.getElementById('marker-animation').value = 'pulse'; document.getElementById('icon-preview').textContent = '📍'; document.getElementById('icon-preview').className = 'cm-icon-preview animate-pulse'; document.getElementById('marker-description').value = ''; document.getElementById('marker-link-url').value = ''; document.getElementById('marker-link-title').value = ''; document.getElementById('marker-image-url').value = ''; document.getElementById('add-marker-btn').style.display = 'inline-block'; document.getElementById('save-marker-btn').style.display = 'none'; document.getElementById('cancel-edit-btn').style.display = 'none'; state.editingMarkerId = null; } function searchMarkers(query) { const filtered = state.markers.filter( (m) => m.title.toLowerCase().includes(query.toLowerCase()) || m.description?.toLowerCase().includes(query.toLowerCase()) ); updateMarkersList(filtered); } function bulkDelete() { if (confirm(`Delete ${state.selectedMarkers.size} markers?`)) { state.markers = state.markers.filter( (m) => !state.selectedMarkers.has(m.id) ); state.selectedMarkers.clear(); saveData(); updateMarkersList(); renderMarkers(); updateBulkToolbar(); showMessage('Markers deleted!', 'success'); } } function bulkExport() { const selected = state.markers.filter((m) => state.selectedMarkers.has(m.id) ); const data = { website: WEBSITE_HOST, exportDate: new Date().toISOString(), markers: selected, }; const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json', }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `selected_markers_${Date.now()}.json`; a.click(); URL.revokeObjectURL(url); showMessage(`Exported ${selected.length} markers!`, 'success'); } function updateBulkToolbar() { const toolbar = document.querySelector('.cm-bulk-toolbar'); if (toolbar) { toolbar.classList.toggle('active', state.isBulkMode); if (state.isBulkMode) { toolbar.querySelector( '.cm-bulk-count' ).textContent = `${state.selectedMarkers.size} selected`; } } } function showContextMenu(e, markerId) { const menu = document.querySelector('.cm-context-menu'); if (menu) { menu.style.left = e.pageX + 'px'; menu.style.top = e.pageY + 'px'; menu.classList.add('active'); menu.dataset.markerId = markerId; } } function handleContextAction(action) { const menu = document.querySelector('.cm-context-menu'); if (!menu) return; const markerId = menu.dataset.markerId; switch (action) { case 'edit': editMarker(markerId); break; case 'duplicate': duplicateMarker(markerId); break; case 'delete': deleteMarker(markerId); break; case 'animate': changeMarkerAnimation(markerId); break; case 'anchor': anchorMarker(markerId); break; } menu.classList.remove('active'); } function editMarker(id) { const marker = state.markers.find((m) => m.id === id); if (!marker) return; state.editingMarkerId = id; document.getElementById('marker-title').value = marker.title; document.getElementById('marker-icon').value = marker.icon; document.getElementById('marker-animation').value = marker.animation || 'none'; document.getElementById('icon-preview').textContent = marker.icon; document.getElementById('marker-description').value = marker.description || ''; document.getElementById('marker-link-url').value = marker.linkUrl || ''; document.getElementById('marker-link-title').value = marker.linkTitle || ''; document.getElementById('marker-image-url').value = marker.imageUrl || ''; document.getElementById('add-marker-btn').style.display = 'none'; document.getElementById('save-marker-btn').style.display = 'inline-block'; document.getElementById('cancel-edit-btn').style.display = 'inline-block'; const panel = document.querySelector('.cm-panel'); if (panel) panel.classList.add('active'); switchTab('markers'); } function duplicateMarker(id) { const marker = state.markers.find((m) => m.id === id); if (!marker) return; const duplicate = { ...marker, id: Date.now().toString(), title: marker.title + ' (Copy)', x: parseFloat(marker.x) + 2 + '%', y: parseFloat(marker.y) + 2 + '%', created: new Date().toISOString(), }; state.markers.push(duplicate); saveData(); updateMarkersList(); renderMarkers(); showMessage('Marker duplicated!', 'success'); } function deleteMarker(id) { if (confirm('Delete this marker?')) { state.markers = state.markers.filter((m) => m.id !== id); saveData(); updateMarkersList(); renderMarkers(); showMessage('Marker deleted!', 'success'); sendWebhook('marker_deleted', { id }); } } function changeMarkerAnimation(id) { const marker = state.markers.find((m) => m.id === id); if (!marker) return; const animations = [ 'none', 'pulse', 'bounce', 'rotate', 'glow', 'shake', 'wiggle', 'fade', 'flip', 'zoom', 'slide', 'spin', ]; const currentIndex = animations.indexOf(marker.animation || 'none'); const nextIndex = (currentIndex + 1) % animations.length; marker.animation = animations[nextIndex]; saveData(); renderMarkers(); showMessage(`Animation changed to ${animations[nextIndex]}`, 'success'); } function anchorMarker(id) { const marker = state.markers.find((m) => m.id === id); if (!marker) return; showMessage('Click on an element to anchor this marker', 'success'); document.body.style.cursor = 'crosshair'; function handleAnchorClick(e) { e.preventDefault(); e.stopPropagation(); const target = document.elementFromPoint(e.clientX, e.clientY); if ( target && target !== document.body && target !== document.documentElement ) { let selector = target.tagName.toLowerCase(); if (target.id) { selector = `#${escapeSelector(target.id)}`; } else if (target.className && typeof target.className === 'string') { const classes = target.className.split(' ').filter((c) => c.trim()); if (classes.length > 0) { selector += `.${classes.map((c) => escapeSelector(c)).join('.')}`; } } marker.anchorSelector = selector; marker.isAnchored = true; const rect = target.getBoundingClientRect(); marker.anchorOffsetX = e.clientX - rect.left; marker.anchorOffsetY = e.clientY - rect.top; target.style.outline = '2px solid #667eea'; setTimeout(() => { target.style.outline = ''; }, 1000); showMessage('Marker anchored!', 'success'); saveData(); renderMarkers(); } document.body.style.cursor = ''; document.removeEventListener('click', handleAnchorClick); } setTimeout(() => { document.addEventListener('click', handleAnchorClick, { once: true }); }, 100); } function anchorShape(id) { let shape = state.shapes.find((s) => s.id === id); let isPath = false; if (!shape) { shape = state.paths.find((p) => p.id === id); isPath = true; } if (!shape) return; showMessage( 'Click on an element to anchor this ' + (isPath ? 'drawing' : 'shape'), 'success' ); document.body.style.cursor = 'crosshair'; function handleAnchorClick(e) { e.preventDefault(); e.stopPropagation(); const target = document.elementFromPoint(e.clientX, e.clientY); if ( target && target !== document.body && target !== document.documentElement ) { let selector = target.tagName.toLowerCase(); if (target.id) { selector = `#${escapeSelector(target.id)}`; } else if (target.className && typeof target.className === 'string') { const classes = target.className.split(' ').filter((c) => c.trim()); if (classes.length > 0) { selector += `.${classes.map((c) => escapeSelector(c)).join('.')}`; } } shape.anchorSelector = selector; shape.isAnchored = true; const rect = target.getBoundingClientRect(); if (isPath) { if (!shape.originalPath) { shape.originalPath = shape.path; } shape.anchorOffsetX = e.clientX - rect.left - window.pageXOffset; shape.anchorOffsetY = e.clientY - rect.top - window.pageYOffset; } else { shape.anchorOffsetX = e.clientX - rect.left; shape.anchorOffsetY = e.clientY - rect.top; shape.originalX = shape.x; shape.originalY = shape.y; } target.style.outline = '2px solid #667eea'; setTimeout(() => { target.style.outline = ''; }, 1000); showMessage((isPath ? 'Drawing' : 'Shape') + ' anchored!', 'success'); saveData(); if (isPath) { renderPaths(); } else { renderShapes(); } } document.body.style.cursor = ''; document.removeEventListener('click', handleAnchorClick); } setTimeout(() => { document.addEventListener('click', handleAnchorClick, { once: true }); }, 100); } // ======================================== // Rendering Functions // ======================================== function renderMarkers() { const overlay = document.getElementById('marker-overlay'); if (!overlay) return; overlay.querySelectorAll('.cm-marker').forEach((el) => el.remove()); state.markers.forEach((marker) => { const markerEl = document.createElement('div'); markerEl.className = `cm-marker ${ state.settings.animations ? `animate-${marker.animation || 'pulse'}` : '' }`; markerEl.innerHTML = marker.icon; markerEl.dataset.markerId = marker.id; if (marker.isAnchored && marker.anchorSelector) { try { const anchorEl = document.querySelector(marker.anchorSelector); if (anchorEl) { const rect = anchorEl.getBoundingClientRect(); markerEl.style.left = rect.left + (marker.anchorOffsetX || 0) + 'px'; markerEl.style.top = rect.top + (marker.anchorOffsetY || 0) + 'px'; } else { markerEl.style.left = marker.x; markerEl.style.top = marker.y; } } catch (e) { markerEl.style.left = marker.x; markerEl.style.top = marker.y; } } else { markerEl.style.left = marker.x; markerEl.style.top = marker.y; } if (marker.isAnchored) { markerEl.classList.add('anchored'); } if (state.selectedMarkers.has(marker.id)) { markerEl.classList.add('selected'); } // Event listeners markerEl.addEventListener('click', (e) => { if (state.isBulkMode) { e.stopPropagation(); if (state.selectedMarkers.has(marker.id)) { state.selectedMarkers.delete(marker.id); } else { state.selectedMarkers.add(marker.id); } updateBulkToolbar(); markerEl.classList.toggle('selected'); } else if (!state.isEditMode) { e.stopPropagation(); showTooltip(e, marker, true); } }); markerEl.addEventListener('mousedown', (e) => { if (state.isEditMode && !state.isBulkMode) { startDrag(e, 'marker'); } }); markerEl.addEventListener('mouseenter', (e) => { if ( !state.isEditMode && state.tooltip && !state.tooltip.classList.contains('persistent') ) { showTooltip(e, marker, false); } }); markerEl.addEventListener('mouseleave', () => { if (state.tooltip && !state.tooltip.classList.contains('persistent')) { hideTooltip(); } }); overlay.appendChild(markerEl); }); } function renderShapes() { const canvas = document.getElementById('drawing-canvas'); if (!canvas) return; canvas.querySelectorAll('.cm-drawing-shape').forEach((el) => el.remove()); state.shapes.forEach((shape) => { const shapeEl = document.createElement('div'); shapeEl.className = `cm-drawing-shape ${shape.type}`; shapeEl.dataset.shapeId = shape.id; if (shape.isAnchored && shape.anchorSelector) { try { const anchorEl = document.querySelector(shape.anchorSelector); if (anchorEl) { const rect = anchorEl.getBoundingClientRect(); shapeEl.style.left = rect.left + (shape.anchorOffsetX || 0) + 'px'; shapeEl.style.top = rect.top + (shape.anchorOffsetY || 0) + 'px'; } else { shapeEl.style.left = shape.x; shapeEl.style.top = shape.y; } } catch (e) { shapeEl.style.left = shape.x; shapeEl.style.top = shape.y; } } else { shapeEl.style.left = shape.x; shapeEl.style.top = shape.y; } if (shape.type === 'text') { shapeEl.textContent = shape.text; shapeEl.style.color = shape.borderColor || '#667eea'; shapeEl.style.fontSize = (shape.fontSize || 16) + 'px'; shapeEl.style.fontFamily = shape.fontFamily || 'Inter, sans-serif'; shapeEl.style.fontWeight = shape.bold ? 'bold' : 'normal'; shapeEl.style.fontStyle = shape.italic ? 'italic' : 'normal'; } else if (shape.type === 'line' || shape.type === 'arrow') { shapeEl.style.width = shape.width; shapeEl.style.transform = shape.transform; shapeEl.style.borderColor = shape.borderColor || '#667eea'; shapeEl.style.borderStyle = shape.borderStyle || 'solid'; } else { shapeEl.style.width = shape.width; shapeEl.style.height = shape.height; shapeEl.style.borderColor = shape.borderColor || '#667eea'; shapeEl.style.borderStyle = shape.borderStyle || 'solid'; } shapeEl.addEventListener('click', (e) => { e.stopPropagation(); if (state.isDrawingMode) { if (state.currentDrawingTool === 'select') { state.selectedShapeId = shape.id; document .querySelectorAll('.cm-drawing-shape') .forEach((s) => s.classList.remove('selected')); shapeEl.classList.add('selected'); return; } else if (state.currentDrawingTool === 'delete') { state.shapes = state.shapes.filter((s) => s.id !== shape.id); saveData(); renderShapes(); return; } } }); shapeEl.addEventListener('mousedown', (e) => { if ( state.isDrawingMode && state.currentDrawingTool === 'select' && shapeEl.classList.contains('selected') ) { e.stopPropagation(); if (shape.isAnchored) { showMessage( 'This shape is anchored. Right-click to un-anchor first.', 'error' ); return; } const startX = e.clientX - parseInt(shapeEl.style.left); const startY = e.clientY - parseInt(shapeEl.style.top); function onMouseMove(ev) { shapeEl.style.left = ev.clientX - startX + 'px'; shapeEl.style.top = ev.clientY - startY + 'px'; } function onMouseUp() { if (shape.isAnchored) { renderShapes(); showMessage( 'Anchored shapes cannot be moved. Un-anchor first to reposition.', 'error' ); } else { shape.x = shapeEl.style.left; shape.y = shapeEl.style.top; saveData(); } document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); } document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); } }); shapeEl.addEventListener('contextmenu', (e) => { e.preventDefault(); e.stopPropagation(); if ( state.isDrawingMode && !['select', 'delete'].includes(state.currentDrawingTool) ) { return; } const menu = document.querySelector('.cm-shape-context-menu'); if (menu) { menu.style.left = e.pageX + 'px'; menu.style.top = e.pageY + 'px'; menu.classList.add('active'); menu.dataset.shapeId = shape.id; } }); canvas.appendChild(shapeEl); }); } // Completing remaining rendering and utility functions /** * anchorPath(id) * Attach an anchor to a free-draw path by clicking a page element. */ function anchorPath(id) { const pathObj = state.paths.find((p) => p.id === id); if (!pathObj) return; showMessage('Click on an element to anchor this drawing', 'success'); document.body.style.cursor = 'crosshair'; function handleAnchorClick(e) { e.preventDefault(); e.stopPropagation(); const target = document.elementFromPoint(e.clientX, e.clientY); if ( target && target !== document.body && target !== document.documentElement ) { let selector = target.tagName.toLowerCase(); if (target.id) { selector = `#${escapeSelector(target.id)}`; } else if (target.className && typeof target.className === 'string') { const classes = target.className.split(' ').filter((c) => c.trim()); if (classes.length > 0) { selector += `.${classes.map((c) => escapeSelector(c)).join('.')}`; } } pathObj.anchorSelector = selector; pathObj.isAnchored = true; if (!pathObj.originalPath) pathObj.originalPath = pathObj.path; const rect = target.getBoundingClientRect(); pathObj.anchorOffsetX = e.clientX - rect.left; pathObj.anchorOffsetY = e.clientY - rect.top; target.style.outline = '2px solid #667eea'; setTimeout(() => { target.style.outline = ''; }, 1000); showMessage('Drawing anchored!', 'success'); saveData(); renderPaths(); } document.body.style.cursor = ''; document.removeEventListener('click', handleAnchorClick); } setTimeout(() => { document.addEventListener('click', handleAnchorClick, { once: true }); }, 100); } function renderPaths() { const svgCanvas = document.getElementById('svg-canvas'); if (!svgCanvas) return; svgCanvas.innerHTML = ''; if (state.isDrawingMode) { if (state.currentDrawingTool === 'pen') { svgCanvas.style.pointerEvents = 'none'; } else if (['select', 'delete'].includes(state.currentDrawingTool)) { svgCanvas.style.pointerEvents = 'auto'; } } else { svgCanvas.style.pointerEvents = 'none'; } state.paths.forEach((pathData) => { const path = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' ); path.setAttribute('fill', 'none'); path.setAttribute('stroke', pathData.color); path.setAttribute('stroke-width', pathData.strokeWidth || '3'); path.setAttribute('d', pathData.path); path.setAttribute('data-path-id', pathData.id); path.style.cursor = 'pointer'; path.style.pointerEvents = 'visibleStroke'; path.addEventListener('click', (e) => { e.stopPropagation(); if (state.isDrawingMode) { if (state.currentDrawingTool === 'select') { svgCanvas.querySelectorAll('path').forEach((p) => { p.classList.remove('selected'); }); path.classList.add('selected'); } else if (state.currentDrawingTool === 'delete') { state.paths = state.paths.filter((p) => p.id !== pathData.id); saveData(); renderPaths(); } } }); path.addEventListener('mousedown', (e) => { // If anchored, do not allow dragging with the select tool. if ( pathData.isAnchored && state.isDrawingMode && state.currentDrawingTool === 'select' ) { showMessage( 'This drawing is anchored. Right-click to un-anchor first.', 'error' ); return; } if ( state.isDrawingMode && state.currentDrawingTool === 'select' && path.classList.contains('selected') ) { e.stopPropagation(); const startX = e.clientX; const startY = e.clientY; const originalPath = pathData.path; function onMouseMove(ev) { const dx = ev.clientX - startX; const dy = ev.clientY - startY; const transformedPath = originalPath.replace( /([ML])\s*(\d+)\s+(\d+)/g, (match, cmd, x, y) => { const newX = parseInt(x) + dx; const newY = parseInt(y) + dy; return `${cmd} ${newX} ${newY}`; } ); path.setAttribute('d', transformedPath); } function onMouseUp(ev) { const dx = ev.clientX - startX; const dy = ev.clientY - startY; pathData.path = originalPath.replace( /([ML])\s*(\d+)\s+(\d+)/g, (match, cmd, x, y) => { const newX = parseInt(x) + dx; const newY = parseInt(y) + dy; return `${cmd} ${newX} ${newY}`; } ); saveData(); document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); } document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); } }); path.addEventListener('contextmenu', (e) => { e.preventDefault(); e.stopPropagation(); if ( state.isDrawingMode && !['select', 'delete'].includes(state.currentDrawingTool) ) { return; } const menu = document.querySelector('.cm-shape-context-menu'); if (menu) { menu.style.left = e.pageX + 'px'; menu.style.top = e.pageY + 'px'; menu.classList.add('active'); menu.dataset.shapeId = pathData.id; menu.dataset.shapeType = 'path'; } }); svgCanvas.appendChild(path); // If this path is anchored, compute a delta between the path's current screen rect and the anchor element rect. if (pathData.isAnchored && pathData.anchorSelector) { try { const anchorEl = document.querySelector(pathData.anchorSelector); if (anchorEl) { const anchorRect = anchorEl.getBoundingClientRect(); const pathRect = path.getBoundingClientRect(); const desiredLeft = anchorRect.left + (pathData.anchorOffsetX || 0); const desiredTop = anchorRect.top + (pathData.anchorOffsetY || 0); const deltaX = desiredLeft - pathRect.left; const deltaY = desiredTop - pathRect.top; path.style.transform = `translate(${deltaX}px, ${deltaY}px)`; path.style.transformOrigin = '0 0'; } } catch (err) { console.error('Failed to compute anchor transform for path:', err); } } }); } function updateMarkersList(markersToShow = state.markers) { const container = document.getElementById('markers-container'); if (!container) return; if (markersToShow.length === 0) { container.innerHTML = '<p style="text-align:center; color:#a0aec0;">No markers yet!</p>'; const countEl = document.getElementById('markers-count'); if (countEl) countEl.textContent = '0'; return; } container.innerHTML = markersToShow .map( (marker) => ` <div class="cm-marker-item" data-marker-id="${marker.id}"> <div style="font-size: 28px; margin-right: 16px;">${marker.icon}</div> <div style="flex: 1;"> <div style="font-weight: 600; color: var(--text-primary);">${escapeHtml( marker.title )}</div> <div style="font-size: 12px; color: var(--text-secondary);">${ marker.x }, ${marker.y}</div> </div> <div style="display: flex; gap: 8px;"> <button class="cm-btn cm-btn-secondary marker-edit-btn" data-id="${ marker.id }" style="padding: 8px 12px; font-size: 12px;">✏️</button> <button class="cm-btn cm-btn-danger marker-delete-btn" data-id="${ marker.id }" style="padding: 8px 12px; font-size: 12px;">🗑️</button> </div> </div> ` ) .join(''); const countEl = document.getElementById('markers-count'); if (countEl) countEl.textContent = markersToShow.length.toString(); container.querySelectorAll('.marker-edit-btn').forEach((btn) => { btn.addEventListener('click', () => editMarker(btn.dataset.id)); }); container.querySelectorAll('.marker-delete-btn').forEach((btn) => { btn.addEventListener('click', () => deleteMarker(btn.dataset.id)); }); } // ======================================== // Drawing Functions // ======================================== function initDrawing() { const canvas = document.getElementById('drawing-canvas'); const svgCanvas = document.getElementById('svg-canvas'); if (!canvas || !svgCanvas) return; let isDrawing = false; let startX, startY; let currentShape = null; let currentPath = null; let textPromptActive = false; if (window._drawingHandlers) { canvas.removeEventListener( 'mousedown', window._drawingHandlers.mousedown ); svgCanvas.removeEventListener( 'mousedown', window._drawingHandlers.mousedown ); document.removeEventListener( 'mousemove', window._drawingHandlers.mousemove ); document.removeEventListener('mouseup', window._drawingHandlers.mouseup); } const handleMouseDown = (e) => { if (!state.isDrawingMode) return; if (e.button === 2) return; if (['select', 'delete', 'clear'].includes(state.currentDrawingTool)) return; isDrawing = true; startX = e.clientX; startY = e.clientY; if (state.currentDrawingTool === 'pen') { currentPath = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' ); currentPath.setAttribute('fill', 'none'); currentPath.setAttribute( 'stroke', document.getElementById('draw-color').value ); currentPath.setAttribute( 'stroke-width', document.getElementById('draw-thickness').value ); currentPath.setAttribute('d', `M ${startX} ${startY}`); svgCanvas.appendChild(currentPath); } else if (state.currentDrawingTool === 'text') { if (textPromptActive) return; textPromptActive = true; const text = prompt('Enter text:'); textPromptActive = false; if (text) { const shape = { id: Date.now().toString(), type: 'text', text, x: startX + 'px', y: startY + 'px', borderColor: document.getElementById('draw-color').value, fontSize: document.getElementById('text-size')?.value || 16, bold: document.getElementById('text-bold')?.checked || false, italic: document.getElementById('text-italic')?.checked || false, fontFamily: 'Inter, sans-serif', }; state.shapes.push(shape); saveData(); renderShapes(); } isDrawing = false; return; } else { currentShape = document.createElement('div'); currentShape.className = `cm-drawing-shape ${state.currentDrawingTool}`; currentShape.style.left = startX + 'px'; currentShape.style.top = startY + 'px'; currentShape.style.borderColor = document.getElementById('draw-color').value; currentShape.style.borderStyle = document.getElementById('draw-style').value; if ( state.currentDrawingTool === 'line' || state.currentDrawingTool === 'arrow' ) { currentShape.style.width = '0px'; currentShape.style.transform = 'rotate(0deg)'; } else { currentShape.style.width = '0px'; currentShape.style.height = '0px'; } canvas.appendChild(currentShape); } }; const handleMouseMove = (e) => { if (!isDrawing) return; if (state.currentDrawingTool === 'pen' && currentPath) { const d = currentPath.getAttribute('d'); currentPath.setAttribute('d', `${d} L ${e.clientX} ${e.clientY}`); } else if ( (state.currentDrawingTool === 'line' || state.currentDrawingTool === 'arrow') && currentShape ) { const dx = e.clientX - startX; // Continuing from Part 6 - completing the handleMouseMove function // Correcting the incomplete line const dy = e.clientY - startY; const length = Math.sqrt(dx * dx + dy * dy); const angle = (Math.atan2(dy, dx) * 180) / Math.PI; currentShape.style.width = length + 'px'; currentShape.style.transform = `rotate(${angle}deg)`; } else if (currentShape) { const width = Math.abs(e.clientX - startX); const height = Math.abs(e.clientY - startY); currentShape.style.width = width + 'px'; currentShape.style.height = height + 'px'; currentShape.style.left = Math.min(e.clientX, startX) + 'px'; currentShape.style.top = Math.min(e.clientY, startY) + 'px'; } }; const handleMouseUp = () => { if (!isDrawing) return; isDrawing = false; if (state.currentDrawingTool === 'pen' && currentPath) { const pathData = currentPath.getAttribute('d'); const pathId = Date.now().toString(); currentPath.setAttribute('data-path-id', pathId); currentPath.style.cursor = 'pointer'; state.paths.push({ id: pathId, path: pathData, color: currentPath.getAttribute('stroke'), strokeWidth: currentPath.getAttribute('stroke-width'), }); saveData(); renderPaths(); currentPath = null; if (svgCanvas && state.isDrawingMode) { svgCanvas.style.pointerEvents = 'auto'; } } else if (currentShape) { const shape = { id: Date.now().toString(), type: state.currentDrawingTool, x: currentShape.style.left, y: currentShape.style.top, width: currentShape.style.width, height: currentShape.style.height, transform: currentShape.style.transform, borderColor: document.getElementById('draw-color').value, borderStyle: document.getElementById('draw-style').value, }; state.shapes.push(shape); saveData(); renderShapes(); currentShape = null; } }; window._drawingHandlers = { mousedown: handleMouseDown, mousemove: handleMouseMove, mouseup: handleMouseUp, }; canvas.addEventListener('mousedown', handleMouseDown); svgCanvas.addEventListener('mousedown', handleMouseDown); document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); } function selectDrawingTool(tool) { state.currentDrawingTool = tool; document.querySelectorAll('.cm-drawing-tool').forEach((t) => { t.classList.toggle('active', t.dataset.tool === tool); }); const canvas = document.getElementById('drawing-canvas'); if (canvas) { if (tool === 'select' || tool === 'delete') { canvas.style.pointerEvents = 'none'; } else { canvas.style.pointerEvents = state.isDrawingMode ? 'auto' : 'none'; } if (tool === 'select') { canvas.style.cursor = 'pointer'; } else if (tool === 'delete') { canvas.style.cursor = 'not-allowed'; } else { canvas.style.cursor = 'crosshair'; } } renderPaths(); } // ======================================== // Drag Functions // ======================================== function startDrag(e, type) { if (type === 'marker' && !state.isEditMode) return; state.isDragging = true; state.currentDragItem = e.target; state.currentDragItem.classList.add('dragging'); const rect = state.currentDragItem.getBoundingClientRect(); const offsetX = e.clientX - rect.left; const offsetY = e.clientY - rect.top; function onMouseMove(e) { if (!state.isDragging) return; const x = ((e.clientX - offsetX) / window.innerWidth) * 100; const y = ((e.clientY - offsetY) / window.innerHeight) * 100; state.currentDragItem.style.left = `${Math.max(0, Math.min(100, x))}%`; state.currentDragItem.style.top = `${Math.max(0, Math.min(100, y))}%`; } function onMouseUp() { if (!state.isDragging) return; state.isDragging = false; state.currentDragItem.classList.remove('dragging'); const id = state.currentDragItem.dataset.markerId; const marker = state.markers.find((m) => m.id === id); if (marker) { marker.x = state.currentDragItem.style.left; marker.y = state.currentDragItem.style.top; saveData(); updateMarkersList(); } state.currentDragItem = null; document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); } document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); } // ======================================== // Tooltip Functions // ======================================== window._cmTooltipOutsideHandler = null; window._cmTooltipCloseHandler = null; function showTooltip(e, marker, isPersistent = false) { if (state.isDragging || !state.tooltip) return; state.tooltip.innerHTML = ` ${ isPersistent ? '<div class="cm-tooltip-close" id="cm-tooltip-close">✕</div>' : '' } <div style="font-weight: 600; color: #667eea; margin-bottom: 8px; font-size: 16px;">${escapeHtml( marker.title )}</div> <div style="margin-bottom: 8px;">${escapeHtml( marker.description || 'No description' )}</div> ${ marker.linkUrl ? `<div style="margin-top: 8px;"><a href="${escapeAttr( marker.linkUrl )}" target="_blank" style="color: #667eea; text-decoration: none; font-weight: 600;">🔗 ${escapeHtml( marker.linkTitle || 'View Link' )}</a></div>` : '' } ${ marker.imageUrl ? `<div style="margin-top: 8px;"><img src="${escapeAttr( marker.imageUrl )}" style="max-width: 100%; border-radius: 8px;"></div>` : '' } `; state.tooltip.classList.add('active'); if (isPersistent) { state.tooltip.classList.add('persistent'); state.tooltip.style.pointerEvents = 'auto'; const closeBtn = document.getElementById('cm-tooltip-close'); if (closeBtn) { if (window._cmTooltipCloseHandler) { closeBtn.removeEventListener('click', window._cmTooltipCloseHandler); window._cmTooltipCloseHandler = null; } window._cmTooltipCloseHandler = function (ev) { ev.stopPropagation(); hideTooltip(); }; closeBtn.addEventListener('click', window._cmTooltipCloseHandler); } if (window._cmTooltipOutsideHandler) { document.removeEventListener('click', window._cmTooltipOutsideHandler); window._cmTooltipOutsideHandler = null; } window._cmTooltipOutsideHandler = function (evt) { if (!state.tooltip.contains(evt.target)) { hideTooltip(); } }; setTimeout( () => document.addEventListener('click', window._cmTooltipOutsideHandler), 0 ); } else { state.tooltip.style.pointerEvents = 'none'; } const tooltipWidth = 350; const tooltipHeight = 200; const padding = 15; let x = e.pageX + padding; let y = e.pageY + padding; if (x + tooltipWidth > window.innerWidth) { x = e.pageX - tooltipWidth - padding; } if (y + tooltipHeight > window.innerHeight) { y = e.pageY - tooltipHeight - padding; } if (x < 0) x = padding; if (y < 0) y = padding; state.tooltip.style.left = `${x}px`; state.tooltip.style.top = `${y}px`; } function hideTooltip() { if (state.tooltip) { state.tooltip.classList.remove('active', 'persistent'); state.tooltip.style.pointerEvents = 'none'; } if (window._cmTooltipOutsideHandler) { document.removeEventListener('click', window._cmTooltipOutsideHandler); window._cmTooltipOutsideHandler = null; } const closeBtn = document.getElementById('cm-tooltip-close'); if (closeBtn && window._cmTooltipCloseHandler) { closeBtn.removeEventListener('click', window._cmTooltipCloseHandler); window._cmTooltipCloseHandler = null; } } // ======================================== // Note Taking Functions // ======================================== function applyFormat(format) { const selection = window.getSelection(); if (!selection.rangeCount) return; switch (format) { case 'bold': document.execCommand('bold'); break; case 'italic': document.execCommand('italic'); break; case 'underline': document.execCommand('underline'); break; case 'list': document.execCommand('insertUnorderedList'); break; case 'link': { const url = prompt('Enter URL:'); if (url) document.execCommand('createLink', false, url); break; } } } function saveNote() { const content = document.getElementById('note-content').innerHTML; const note = { id: Date.now().toString(), content, website: WEBSITE_HOST, created: new Date().toISOString(), }; let notes = JSON.parse(localStorage.getItem(NOTES_KEY) || '[]'); notes.push(note); localStorage.setItem(NOTES_KEY, JSON.stringify(notes)); showMessage('Note saved!', 'success'); sendWebhook('note_saved', note); } function loadNotes() { const notes = JSON.parse(localStorage.getItem(NOTES_KEY) || '[]'); if (notes.length > 0) { const latestNote = notes[notes.length - 1]; setTimeout(() => { const noteContent = document.getElementById('note-content'); if (noteContent) { noteContent.innerHTML = latestNote.content; } }, 100); } } // ======================================== // Register Menu Command // ======================================== GM_registerMenuCommand('🎯 Activate Markers Pro', init); // ======================================== // Auto-initialization // ======================================== const autoInitPatterns = [ 'mapgenie.io', 'maps.google', 'google.com/maps', 'openstreetmap.org', 'bing.com/maps', 'here.com', 'waze.com', 'reddit.com/r/gaming', 'github.com', 'stackoverflow.com', ]; const shouldAutoInit = autoInitPatterns.some((pattern) => window.location.href.includes(pattern) ); if (shouldAutoInit) { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { setTimeout(init, 1000); } } else { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', showActivationButton); } else { setTimeout(showActivationButton, 500); } } })();