您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Advanced marker system with drawing, animations, cloud sync, webhooks, and modern UI
// ==UserScript== // @name Custom Markers Pro - Advanced Edition v11 // @namespace http://tampermonkey.net/ // @version 11.1 // @description Advanced marker system with drawing, animations, cloud sync, webhooks, and modern UI // @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,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPSc2NCcgaGVpZ2h0PSc2NCcgdmlld0JveD0nMCAwIDY0IDY0Jz48cmVjdCB3aWR0aD0nMTAwJScgaGVpZ2h0PScxMDAlJyBmaWxsPScjMDAwJyByeD0nMTAnLz48dGV4dCB4PSc1MCUnIHk9JzUwJScgZm9udC1zaXplPSczNicgdGV4dC1hbmNob3I9J21pZGRsZScgZG9taW5hbnQtYmFzZWxpbmU9J2NlbnRyYWwnPvCfkajigI3wn5K7PC90ZXh0Pjwvc3ZnPg== // ==/UserScript== // @ts-nocheck /* ======================================== QUICK REFERENCE GUIDE - Custom Markers Pro v11 ======================================== */ /* POSITIONING REFERENCE: top: Increase number = moves DOWN right: Increase number = moves LEFT bottom: Increase number = moves UP left: Increase number = moves RIGHT */ /* MAIN UI ELEMENTS LOCATION & APPEARANCE: Change Main Control Button loc/look in .cm-control-btn { top: 150px; right: 20px; } Change Edit Mode Button loc/look in .cm-toggle-edit { bottom: 30px; right: 100px; } Change Drawing Mode Button loc/look in .cm-toggle-drawing { bottom: 30px; right: 170px; } Change Bulk Select FAB loc/look in .cm-fab-container { bottom: 30px; right: 30px; } Change Activation Button loc/look in .cm-activate-btn { bottom: 30px; right: 30px; } Change Drawing Toolbar loc/look in .cm-drawing-toolbar { top: 80px; left: 20px; } Change Bulk Toolbar loc/look in .cm-bulk-toolbar { top: 80px; left: 50%; } Change Panel Width loc/look in .cm-panel { width: 450px; } */ /* KEYBOARD SHORTCUTS: Ctrl+E = Toggle Edit Mode Ctrl+D = Toggle Drawing Mode Ctrl+F = Focus Search Box Ctrl+S = Save Data Ctrl+A (in bulk mode) = Select All Markers Delete (in bulk mode) = Delete Selected */ /* COLORS & GRADIENTS: 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%) Main Accent Color = #667eea Delete/Danger Color = #f56565 */ /* Z-INDEX LAYERS (from bottom to top): 999995 = Overlay & SVG Canvas 999996 = Drawing Canvas & Markers 999997 = Drawing/Bulk Toolbars, Dragging Items 999998 = FAB Buttons, Toggle Buttons 999999 = Control Button, Activation Button 1000000 = Panel, Context Menu 1000001 = Tooltip, Modal */ /* ANIMATIONS AVAILABLE: pulse, bounce, rotate, glow, // Original shake, wiggle, fade, flip, // New added zoom, slide, spin // New added Change animation speed in @keyframes definitions Change default animation in state.animationType = 'pulse' */ /* ICON SIZES: Marker Icons = font-size: 32px (in .cm-marker) Icon Preview = font-size: 32px (in .cm-icon-preview) Marker List Icons = font-size: 28px (in updateMarkersList) FAB Button Icons = font-size: 24px (in .cm-fab) */ /* STORAGE KEYS: Main Data = custom_markers_pro_v11_${WEBSITE_HOST} Notes = custom_markers_notes_${WEBSITE_HOST} */ /* AUTO-INIT WEBSITES: Add/remove in autoInitPatterns array at bottom of script: - mapgenie.io - maps.google.* - openstreetmap.org - bing.com/maps - github.com - stackoverflow.com */ /* DRAWING TOOLS: rectangle, circle, line, arrow, // Shape tools text, pen, // Text & freehand select, delete, clear // Action tools Change default tool in state.currentDrawingTool = 'rectangle' */ /* TEXT FORMATTING OPTIONS: Bold toggle = #text-bold checkbox Italic toggle = #text-italic checkbox Size range = #text-size (10-48px) Default font = 'Inter, sans-serif' */ /* BORDER STYLES: solid, dashed, dotted Change in #draw-style select */ /* PANEL TABS: markers, notes, settings Change default in state.currentTab = 'markers' */ /* THEME: Toggle dark mode = state.isDarkMode Dark mode class = .cm-dark-mode */ /* WEBHOOK & CLOUD SYNC: Webhook URL var = WEBHOOK_URL (line ~2840) Cloud API var = CLOUD_SYNC_API (line ~2841) */ /* IMPORTANT FUNCTION LOCATIONS: Main init() = line ~3089 createUI() = line ~3109 renderMarkers() = line ~4380 renderShapes() = line ~4437 initDrawing() = line ~4522 showTooltip() = line ~4717 hideTooltip() = line ~4774 */ /* MODIFY DEFAULTS: Default marker icon = '📍' in resetForm() Default animation = 'pulse' in resetForm() Panel open side = right: -450px (closed) to right: 0 (open) Float animation = 3s in @keyframes float */ /* ADD NEW FEATURES: New animations = Add @keyframes and .cm-marker.animate-{name} New icons = Add to iconOptions object New drawing tools = Add to drawing toolbar HTML & handleMouseDown New export formats = Add new export function & button */ /* //adding cusom fixes look for css for specific sites that dont work line 1804 */ (function() { 'use strict'; // Modern UI Styles with Glassmorphism and Animations GM_addStyle(` /* Force proper positioning context */ .cm-control-btn, .cm-panel, .cm-overlay, .cm-drawing-canvas, .cm-svg-canvas, .cm-drawing-toolbar, .cm-bulk-toolbar, .cm-toggle-edit, .cm-toggle-drawing, .cm-fab-container, .cm-activate-btn, .cm-context-menu, .cm-tooltip { position: fixed !important; transform: translateZ(0) !important; /* Force GPU layer */ } /* Reset any parent transforms affecting our elements */ body > * { transform: none !important; } /* Import Modern Font */ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); /* CSS Variables for Theming */ :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); } /* Dark mode variables */ .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); } /* Modern Control Button with Floating Animation */ .cm-control-btn { position: fixed; top: 150px; right: 20px; z-index: 999999; background: var(--primary-gradient); color: white; padding: 16px 24px; border-radius: var(--border-radius); cursor: pointer; font-family: 'Inter', sans-serif; font-weight: 600; font-size: 15px; box-shadow: 0 10px 40px var(--shadow-color); backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); border: 1px solid var(--glass-border); transition: var(--transition); display: flex; align-items: center; gap: 10px; animation: float 3s ease-in-out infinite; } @keyframes float { 0%, 100% { transform: translateY(0px); } 50% { transform: translateY(-10px); } } .cm-control-btn:hover { transform: translateY(-3px) scale(1.05); box-shadow: 0 20px 40px var(--shadow-color); } .cm-control-btn::before { content: ''; position: absolute; inset: 0; border-radius: var(--border-radius); padding: 2px; background: linear-gradient(45deg, #667eea, #764ba2, #f093fb, #f5576c); -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); -webkit-mask-composite: xor; mask-composite: exclude; opacity: 0; transition: opacity 0.3s; } .cm-control-btn:hover::before { opacity: 1; } /* Activation Button with Glow Effect */ .cm-activate-btn { position: fixed; bottom: 30px; right: 30px; z-index: 999999; background: none; color: #667eea; font-size: 48px; cursor: pointer; border: none; filter: drop-shadow(0 0 10px rgba(102, 126, 234, 0.8)); transition: var(--transition); animation: spin-glow 4s linear infinite; } @keyframes spin-glow { from { transform: rotate(0deg); filter: drop-shadow(0 0 10px rgba(102, 126, 234, 0.8)); } 50% { filter: drop-shadow(0 0 20px rgba(102, 126, 234, 1)); } to { transform: rotate(360deg); filter: drop-shadow(0 0 10px rgba(102, 126, 234, 0.8)); } } .cm-activate-btn:hover { filter: drop-shadow(0 0 25px rgba(102, 126, 234, 1)); transform: scale(1.1); } .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: 1000000; } .cm-activate-btn:hover + .cm-activate-tooltip { opacity: 1; } /* Modern Glassmorphic Panel */ .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: 1000000; 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; } /* Panel Header with Gradient */ .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 { 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 { background: rgba(255,255,255,0.3); transform: scale(1.1); } .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: 20px; } .cm-close-btn:hover { background: rgba(255,255,255,0.3); transform: rotate(90deg); } /* Panel Content */ .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%); } /* Custom Scrollbar */ .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; } /* Tab Navigation */ .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); } /* Modern Form Styles */ .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; } .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; } /* Modern Buttons */ .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); } } /* Search Bar */ .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; } .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; } /* Marker Items with Hover Effects */ .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; } /* Icon Preview with Animation */ .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); } } /* Marker Animations - ORIGINAL PLUS NEW ONES */ @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)); } } /* NEW ANIMATIONS */ @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; } /* Drawing Mode Styles */ .cm-drawing-canvas { position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 999996; } .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); } /* SVG Canvas for Pen Drawing */ .cm-svg-canvas { position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 999995; } .cm-svg-canvas path { pointer-events: stroke; cursor: pointer; } .cm-svg-canvas path.selected { stroke: #f5576c !important; stroke-width: 4 !important; fill: none; } /* Efficient Drawing Toolbar */ .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: 999997; 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: white; 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; } /* Bulk Selection Toolbar */ .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: 999997; } .cm-bulk-toolbar.active { display: flex; } .cm-bulk-count { font-weight: 600; color: var(--text-primary); } /* Settings Panel */ .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); } /* Note Taking Mode */ .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; } /* Export Options */ .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; } /* Floating Action Buttons */ .cm-fab-container { position: fixed; bottom: 30px; right: 30px; display: flex; flex-direction: column; gap: 12px; z-index: 999998; } .cm-fab { width: 56px; height: 56px; border-radius: 50%; background: var(--primary-gradient); color: white; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 24px; box-shadow: 0 4px 20px rgba(102, 126, 234, 0.3); transition: var(--transition); } .cm-fab:hover { transform: scale(1.1); box-shadow: 0 6px 30px rgba(102, 126, 234, 0.4); } /* Toggle Edit Mode Button */ .cm-toggle-edit { position: fixed; bottom: 30px; right: 100px; width: 60px; height: 60px; border-radius: 50%; background: var(--secondary-gradient); color: white; border: none; cursor: pointer; display: none; align-items: center; justify-content: center; font-size: 24px; box-shadow: 0 6px 30px rgba(245, 87, 108, 0.4); transition: var(--transition); z-index: 999998; } .cm-toggle-edit.visible { display: flex; } .cm-toggle-edit:hover { transform: rotate(180deg) scale(1.1); } .cm-toggle-edit.active { background: var(--success-gradient); } /* Toggle Drawing Mode Button */ .cm-toggle-drawing { position: fixed; bottom: 30px; right: 170px; width: 60px; height: 60px; border-radius: 50%; background: linear-gradient(135deg, #fa709a 0%, #fee140 100%); color: white; border: none; cursor: pointer; display: none; align-items: center; justify-content: center; font-size: 24px; box-shadow: 0 6px 30px rgba(250, 112, 154, 0.4); transition: var(--transition); z-index: 999998; } .cm-toggle-drawing.visible { display: flex; } .cm-toggle-drawing:hover { transform: scale(1.1); } .cm-toggle-drawing.active { background: var(--success-gradient); } /* Markers */ .cm-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 999995; } .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: 999996; } .cm-marker:hover { transform: scale(1.15); } .cm-marker.dragging { cursor: grabbing; transform: scale(1.2); z-index: 999997; } .cm-marker.selected { filter: drop-shadow(0 0 10px rgba(245, 87, 108, 0.8)); } /* Tooltip */ .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: 1000001; 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; } /* Messages */ .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; } /* Loading Spinner */ .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; } @keyframes spin { to { transform: rotate(360deg); } } /* Context Menu */ .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: 1000000; display: none; min-width: 200px; } .cm-dark-mode .cm-context-menu { background: rgba(40,40,40,1); } .cm-context-menu.active { display: block; } .cm-context-item { padding: 10px 16px; border-radius: 8px; cursor: pointer; transition: var(--transition); display: flex; align-items: center; gap: 10px; } .cm-context-item:hover { background: rgba(102, 126, 234, 0.1); } /* Checkbox for marker selection */ .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_pro_v11_${WEBSITE_HOST}`; const WEBHOOK_URL = ''; // Add your webhook URL here const CLOUD_SYNC_API = ''; // Add your cloud sync API endpoint here let state = { markers: [], shapes: [], paths: [], // For pen tool SVG paths isEditMode: false, isDrawingMode: false, isBulkMode: false, isDragging: false, currentDragItem: null, editingMarkerId: null, selectedMarkers: new Set(), selectedShapeId: null, // For drawing select tool currentDrawingTool: 'rectangle', animationType: 'pulse', tooltip: null, isInitialized: false, currentTab: 'markers', isDarkMode: false, settings: { autoSave: true, animations: true, soundEffects: false, cloudSync: false, webhooks: false, contextDetection: true } }; // Icon Options with Categories const iconOptions = { '📍': 'Location Pin', '⭐': 'Star', '🔴': 'Red Circle', '🔵': 'Blue Circle', '🟢': 'Green Circle', '🟡': 'Yellow Circle', '⚠️': 'Warning', '❗': 'Important', '❓': 'Question', '💡': 'Idea', '🚩': 'Flag', '🎯': 'Target', '💎': 'Diamond', '🔥': 'Fire', '⚡': 'Lightning', '🔑': 'Key', '💀': 'Danger', '⚔️': 'Battle', '🛡️': 'Defense', '🏠': 'House', '⛺': 'Camp', '🏰': 'Castle', '🏭': 'Factory', '👁️': 'Point of Interest', '📦': 'Loot Box', '🎪': 'Event', '🌟': 'Special', '🎨': 'Art', '📝': 'Note', '📸': 'Photo', '🎮': 'Gaming', '🎵': 'Music', '🎬': 'Video' }; // ======================================== // Utility Functions // ======================================== function loadData() { 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; } } function saveData() { if (state.settings.autoSave) { 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() })); } } function exportToJSON() { const data = { website: { host: WEBSITE_HOST, url: window.location.href, title: document.title, exportDate: new Date().toISOString() }, version: '11.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; // ----- Table of Contents ----- doc.setFont("helvetica", "bold"); doc.setFontSize(16); doc.setTextColor(0, 0, 0); doc.text("Table of Contents", margin, y); y += 12; state.markers.forEach((marker, idx) => { 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; }); y += 10; // Add header/footer function addHeaderFooter() { doc.setFont("helvetica", "italic"); doc.setFontSize(10); doc.setTextColor(120); const pageCount = doc.internal.getNumberOfPages(); for (let i = 1; i <= pageCount; i++) { doc.setPage(i); // Header doc.text(`Custom Markers for ${WEBSITE_HOST}`, margin, 10); // Footer doc.text(`Page ${i} / ${pageCount}`, pageWidth - margin - 20, pageHeight - 10); } } // ----- Process markers ----- let index = 0; y = 50; function processNextMarker() { if (index >= state.markers.length) { addHeaderFooter(); doc.save(`markers_${WEBSITE_HOST.replace(/\./g, '_')}_${Date.now()}.pdf`); if (typeof showMessage === "function") { showMessage('Exported to PDF successfully!', 'success'); } else { alert("Exported to PDF successfully!"); } return; } const marker = state.markers[index++]; if (y > pageHeight - 50) { doc.addPage(); y = 20; } // Separator line if (index > 1) { doc.setDrawColor(200); doc.setLineWidth(0.4); doc.line(margin, y, pageWidth - margin, y); y += 5; } // Marker title doc.setFont("helvetica", "bold"); doc.setFontSize(15); doc.setTextColor(0, 80, 180); const icon = marker.icon ? marker.icon : "⭐"; doc.text(`${index}. ${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 - 50) { 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 += 6; // Clickable 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 }); doc.setTextColor(0, 0, 0); y += 12; } else { y += 8; } processNextMarker(); } processNextMarker(); } //end of pdf function 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) 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) 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'); } } //adding cusom fixes look for css for specific sites that dont work function fixSiteSpecificIssues() { const hostname = window.location.hostname; // Fix for flaticon.com - OneTrust elements are blocking ---yeah flaticon is stubborn so no fix for foating elemnts :D if (hostname.includes('flaticon.com')) { setTimeout(() => { const style = document.createElement('style'); style.textContent = ` /* Match or exceed OneTrust's z-index */ .cm-control-btn, .cm-panel, .cm-toggle-edit, .cm-toggle-drawing, .cm-fab-container, .cm-drawing-toolbar, .cm-bulk-toolbar, .cm-activate-btn { z-index: 2147483647 !important; position: fixed !important; } .cm-tooltip, .cm-context-menu { z-index: 2147483647 !important; position: fixed !important; } .cm-overlay, .cm-drawing-canvas, .cm-svg-canvas { z-index: 2147483645 !important; position: fixed !important; } /* Push OneTrust elements down */ #onetrust-pc-sdk, #ot-anchor, #ot-fltr-modal, .onetrust-pc-dark-filter { z-index: 2147483640 !important; } /* Ensure our markers/shapes are visible */ .cm-marker { z-index: 2147483645 !important; position: absolute !important; } .cm-drawing-shape { z-index: 2147483645 !important; position: absolute !important; } `; document.head.appendChild(style); console.log('Applied Flaticon/OneTrust fixes'); }, 1000); } // Add Other site fixes here... if (hostname.includes('reddit.com')) { document.body.style.position = 'static'; } 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() { if (state.isInitialized || document.querySelector('.cm-activate-btn')) return; const container = document.createElement('div'); container.style.cssText = 'position: fixed; bottom: 30px; right: 30px; z-index: 999999;'; const activateBtn = document.createElement('button'); activateBtn.className = 'cm-activate-btn'; activateBtn.innerHTML = '⚙'; activateBtn.onclick = () => { container.remove(); init(); }; const tooltip = document.createElement('div'); tooltip.className = 'cm-activate-tooltip'; tooltip.textContent = 'Activate Markers Pro'; activateBtn.addEventListener('mouseenter', (e) => { const rect = activateBtn.getBoundingClientRect(); tooltip.style.bottom = '80px'; tooltip.style.right = '0'; }); container.appendChild(activateBtn); container.appendChild(tooltip); document.body.appendChild(container); } // ======================================== // Initialize Function // ======================================== function init() { if (state.isInitialized) { togglePanel(); return; } state.isInitialized = true; loadData(); createUI(); loadNotes(); renderMarkers(); renderShapes(); renderPaths(); setupEventListeners(); updateMarkersList(); applyTheme(); sendWebhook('init', { markersCount: state.markers.length }); fixSiteSpecificIssues(); } // ======================================== // Create UI Elements (REMOVED SHARE/QR SELECTED) // ======================================== 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 with REMOVED share/QR selected buttons const panel = document.createElement('div'); panel.className = 'cm-panel'; panel.innerHTML = ` <div class="cm-panel-header"> <div class="cm-panel-title"> <span>🎯</span> <span>Markers Pro v11</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;"> ${Object.entries(iconOptions).map(([icon, name]) => `<option value="${icon}">${icon} ${name}</option>` ).join('')} </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);"> v11.1 • 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); // Overlay for markers const overlay = document.createElement('div'); overlay.className = 'cm-overlay'; overlay.id = 'marker-overlay'; document.body.appendChild(overlay); // Drawing canvas const drawingCanvas = document.createElement('div'); drawingCanvas.className = 'cm-drawing-canvas'; drawingCanvas.id = 'drawing-canvas'; document.body.appendChild(drawingCanvas); // 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'); svgCanvas.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:999995;'; document.body.appendChild(svgCanvas); // Drawing toolbar with NEW STYLING OPTIONS 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); // 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); // Tooltip state.tooltip = document.createElement('div'); state.tooltip.className = 'cm-tooltip'; document.body.appendChild(state.tooltip); // FAB buttons const fabContainer = document.createElement('div'); fabContainer.className = 'cm-fab-container'; fabContainer.innerHTML = ` <button class="cm-fab" id="fab-bulk" title="Bulk Select">☑️</button> `; document.body.appendChild(fabContainer); // Toggle edit button const toggleBtn = document.createElement('button'); toggleBtn.className = 'cm-toggle-edit'; toggleBtn.innerHTML = '✏️'; toggleBtn.title = 'Toggle Edit Mode (Ctrl+E)'; document.body.appendChild(toggleBtn); // Toggle drawing button const toggleDrawBtn = document.createElement('button'); toggleDrawBtn.className = 'cm-toggle-drawing'; toggleDrawBtn.innerHTML = '🎨'; toggleDrawBtn.title = 'Toggle Drawing Mode (Ctrl+D)'; document.body.appendChild(toggleDrawBtn); // 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> `; document.body.appendChild(contextMenu); } // ======================================== // Event Listeners - REMOVED SHARE/QR SELECTED // ======================================== function setupEventListeners() { // 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); // FAB buttons document.getElementById('fab-bulk').addEventListener('click', toggleBulkMode); // Toggle buttons document.querySelector('.cm-toggle-edit').addEventListener('click', toggleEditMode); document.querySelector('.cm-toggle-drawing').addEventListener('click', toggleDrawingMode); // Drawing tools with FIXED SELECT/DELETE 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); // Show/hide text options based on tool 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) => { if (e.ctrlKey && e.key === 'e') { e.preventDefault(); toggleEditMode(); } else if (e.ctrlKey && e.key === 'd') { e.preventDefault(); toggleDrawingMode(); } else if (e.ctrlKey && e.key === 'f') { e.preventDefault(); document.getElementById('search-markers').focus(); } else if (e.ctrlKey && e.key === 's') { e.preventDefault(); saveData(); showMessage('Saved!', 'success'); } }); // 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'); }); // 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.contains(e.target)) { contextMenu.classList.remove('active'); } }); document.querySelectorAll('.cm-context-item').forEach(item => { item.addEventListener('click', (e) => { handleContextAction(e.target.dataset.action); }); }); } // ======================================== // Core Functions // ======================================== function togglePanel() { const panel = document.querySelector('.cm-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() { state.isEditMode = !state.isEditMode; const overlay = document.getElementById('marker-overlay'); const toggleBtn = document.querySelector('.cm-toggle-edit'); overlay.classList.toggle('active', state.isEditMode); toggleBtn.classList.toggle('active', state.isEditMode); if (!state.isEditMode) { document.body.style.userSelect = ''; } else { document.body.style.userSelect = 'none'; } } function toggleDrawingMode() { state.isDrawingMode = !state.isDrawingMode; const canvas = document.getElementById('drawing-canvas'); const toolbar = document.querySelector('.cm-drawing-toolbar'); const toggleBtn = document.querySelector('.cm-toggle-drawing'); const svgCanvas = document.getElementById('svg-canvas'); canvas.classList.toggle('active', state.isDrawingMode); toolbar.classList.toggle('active', state.isDrawingMode); toggleBtn.classList.toggle('active', state.isDrawingMode); if (svgCanvas) { svgCanvas.style.pointerEvents = state.isDrawingMode ? 'auto' : 'none'; console.log('SVG canvas pointer-events:', svgCanvas.style.pointerEvents); } if (state.isDrawingMode) { canvas.style.pointerEvents = 'auto'; // prevent canvas from blocking SVG clicks canvas.style.pointerEvents = state.currentDrawingTool === 'select' || state.currentDrawingTool === 'delete' ? 'none' : 'auto'; initDrawing(); } else { canvas.style.pointerEvents = 'none'; } } function toggleBulkMode() { state.isBulkMode = !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(); document.querySelector('.cm-toggle-edit').classList.add('visible'); document.querySelector('.cm-toggle-drawing').classList.add('visible'); 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'); 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'); 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'); 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; } 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'; document.querySelector('.cm-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'); } // ======================================== // 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; markerEl.style.left = marker.x; markerEl.style.top = marker.y; 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.classList.contains('persistent')) { showTooltip(e, marker, false); } }); markerEl.addEventListener('mouseleave', () => { if (!state.tooltip.classList.contains('persistent')) { hideTooltip(); } }); overlay.appendChild(markerEl); }); const toggleBtn = document.querySelector('.cm-toggle-edit'); const drawBtn = document.querySelector('.cm-toggle-drawing'); if (toggleBtn) { toggleBtn.classList.toggle('visible', state.markers.length > 0); } if (drawBtn) { drawBtn.classList.toggle('visible', true); } } function renderShapes() { const canvas = document.getElementById('drawing-canvas'); if (!canvas) { console.log('Canvas not found!'); 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; 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'; } // SINGLE combined click and drag handler shapeEl.addEventListener('click', (e) => { console.log('Shape clicked!', state.isDrawingMode, state.currentDrawingTool); e.stopPropagation(); if (state.isDrawingMode) { if (state.currentDrawingTool === 'select') { console.log('Select tool active, selecting shape:', shape.id); state.selectedShapeId = shape.id; document.querySelectorAll('.cm-drawing-shape').forEach(s => s.classList.remove('selected')); shapeEl.classList.add('selected'); } else if (state.currentDrawingTool === 'delete') { state.shapes = state.shapes.filter(s => s.id !== shape.id); saveData(); renderShapes(); } } }); // Drag handler for selected shapes shapeEl.addEventListener('mousedown', (e) => { if (state.isDrawingMode && state.currentDrawingTool === 'select' && shapeEl.classList.contains('selected')) { e.stopPropagation(); 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() { 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); } }); canvas.appendChild(shapeEl); }); } function renderPaths() { const svgCanvas = document.getElementById('svg-canvas'); if (!svgCanvas) { console.error('SVG Canvas not found!'); return; } console.log('=== RENDER PATHS START ==='); console.log('Total paths:', state.paths.length); console.log('Drawing mode:', state.isDrawingMode); console.log('Current tool:', state.currentDrawingTool); // Clear existing paths svgCanvas.innerHTML = ''; // Set pointer-events based on tool if (state.isDrawingMode) { if (state.currentDrawingTool === 'pen') { svgCanvas.style.pointerEvents = 'none'; console.log('SVG Canvas pointer-events: none (pen tool)'); } else if (['select', 'delete'].includes(state.currentDrawingTool)) { svgCanvas.style.pointerEvents = 'auto'; console.log('SVG Canvas pointer-events: auto (select/delete)'); } } else { svgCanvas.style.pointerEvents = 'none'; console.log('SVG Canvas pointer-events: none (not drawing mode)'); } state.paths.forEach((pathData, index) => { console.log(`Creating path ${index}:`, pathData.id); 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'; console.log(`Path ${index} pointer-events:`, path.style.pointerEvents); // Add click handler for select/delete path.addEventListener('click', (e) => { console.log('*** PATH CLICKED ***'); console.log('Path ID:', pathData.id); console.log('Current tool:', state.currentDrawingTool); console.log('Is drawing mode:', state.isDrawingMode); e.stopPropagation(); if (state.isDrawingMode) { if (state.currentDrawingTool === 'select') { console.log('SELECTING path:', pathData.id); // Remove previous selection svgCanvas.querySelectorAll('path').forEach(p => { p.classList.remove('selected'); }); // Add selection to clicked path path.classList.add('selected'); console.log('Path selected successfully'); } else if (state.currentDrawingTool === 'delete') { console.log('DELETING path:', pathData.id); state.paths = state.paths.filter(p => p.id !== pathData.id); saveData(); renderPaths(); console.log('Path deleted successfully'); } else { console.log('Tool not select or delete:', state.currentDrawingTool); } } else { console.log('Not in drawing mode - ignoring click'); } }); // Add drag functionality for selected paths path.addEventListener('mousedown', (e) => { if (state.isDrawingMode && state.currentDrawingTool === 'select' && path.classList.contains('selected')) { e.stopPropagation(); console.log('*** STARTING DRAG ***'); console.log('Path ID:', pathData.id); console.log('Start position:', e.clientX, e.clientY); const startX = e.clientX; const startY = e.clientY; // Parse the original path data const originalPath = pathData.path; console.log('Original path:', originalPath.substring(0, 50) + '...'); function onMouseMove(ev) { const dx = ev.clientX - startX; const dy = ev.clientY - startY; // Transform the path by translating all coordinates 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); console.log('Dragging - offset:', dx, dy); } function onMouseUp(ev) { const dx = ev.clientX - startX; const dy = ev.clientY - startY; console.log('*** DRAG ENDED ***'); console.log('Final offset:', dx, dy); // Save the new path position 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(); console.log('Path moved and saved'); console.log('New path:', pathData.path.substring(0, 50) + '...'); document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); } document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); } }); svgCanvas.appendChild(path); console.log(`Path ${index} added to SVG canvas with click and drag handlers`); }); console.log('=== RENDER PATHS END ==='); } //end of renderPaths() function 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>'; document.getElementById('markers-count').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);">${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(''); document.getElementById('markers-count').textContent = markersToShow.length.toString(); // Attach event listeners after rendering 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 - FIXED WITH SELECT/DELETE // ======================================== function initDrawing() { const canvas = document.getElementById('drawing-canvas'); const svgCanvas = document.getElementById('svg-canvas'); let isDrawing = false; let startX, startY; let currentShape = null; let currentPath = null; const handleMouseDown = (e) => { if (!state.isDrawingMode) return; // Don't create new shapes for select/delete/clear tools if (['select', 'delete', 'clear'].includes(state.currentDrawingTool)) return; isDrawing = true; startX = e.clientX; startY = e.clientY; if (state.currentDrawingTool === 'pen') { console.log('Starting pen drawing'); // Start pen drawing with SVG path 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') { const text = prompt('Enter text:'); 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; } else { // Create shape element 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) { // Continue pen path const d = currentPath.getAttribute('d'); currentPath.setAttribute('d', `${d} L ${e.clientX} ${e.clientY}`); } else if ((state.currentDrawingTool === 'line' || state.currentDrawingTool === 'arrow') && currentShape) { // Calculate line angle and length const dx = e.clientX - startX; 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) { // Rectangle or circle 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) { // Save pen path const pathData = currentPath.getAttribute('d'); const pathId = Date.now().toString(); console.log('Saving pen path:', pathData); // Add an ID to the path element so we can reference it currentPath.setAttribute('data-path-id', pathId); currentPath.style.cursor = 'pointer'; state.paths.push({ id: Date.now().toString(), path: pathData, color: currentPath.getAttribute('stroke'), strokeWidth: currentPath.getAttribute('stroke-width') }); console.log('Paths after save:', state.paths.length); saveData(); renderPaths(); currentPath = null; const svgCanvas = document.getElementById('svg-canvas'); if (svgCanvas && state.isDrawingMode) { svgCanvas.style.pointerEvents = 'auto'; } } else if (currentShape) { // Save shape 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; } }; 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'; // Let clicks through to SVG } 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'; } } // ADDED: Re-render paths to update pointer-events 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); } // ======================================== // REPLACED Tooltip Functions // ======================================== // keep a global handler references so we can remove them later window._cmTooltipOutsideHandler = null; window._cmTooltipCloseHandler = null; function showTooltip(e, marker, isPersistent = false) { if (state.isDragging) 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;">${marker.title}</div> <div style="margin-bottom: 8px;">${marker.description || 'No description'}</div> ${marker.linkUrl ? `<div style="margin-top: 8px;"><a href="${marker.linkUrl}" target="_blank" style="color: #667eea; text-decoration: none; font-weight: 600;">🔗 ${marker.linkTitle || 'View Link'}</a></div>` : ''} ${marker.imageUrl ? `<div style="margin-top: 8px;"><img src="${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'; // attach close handler to the close button (safe: remove previous) 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); } // click outside to close: attach once (and ensure we clean it later) if (window._cmTooltipOutsideHandler) { document.removeEventListener('click', window._cmTooltipOutsideHandler); window._cmTooltipOutsideHandler = null; } window._cmTooltipOutsideHandler = function(evt) { // if click target is outside tooltip, hide it if (!state.tooltip.contains(evt.target)) { hideTooltip(); } }; // delay adding to allow the current click (that opened the tooltip) not to close it immediately 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; } // remove tooltip close handler if any 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() }; const notesKey = `custom_markers_notes_${WEBSITE_HOST}`; console.log('Saving note with key:', notesKey); console.log('Note content:', content); let notes = JSON.parse(localStorage.getItem(notesKey) || '[]'); console.log('Existing notes count:', notes.length); notes.push(note); localStorage.setItem(notesKey, JSON.stringify(notes)); // Verify save const savedNotes = JSON.parse(localStorage.getItem(notesKey)); console.log('Notes after save:', savedNotes.length); console.log('Latest note content:', savedNotes[savedNotes.length - 1].content); showMessage('Note saved!', 'success'); sendWebhook('note_saved', note); } function loadNotes() { const notesKey = `custom_markers_notes_${WEBSITE_HOST}`; console.log('Loading notes with key:', notesKey); const notesString = localStorage.getItem(notesKey); console.log('Raw notes from storage:', notesString); const notes = JSON.parse(notesString || '[]'); console.log('Parsed notes count:', notes.length); if (notes.length > 0) { // Load the most recent note const latestNote = notes[notes.length - 1]; console.log('Loading latest note:', latestNote); // Wait for DOM to be ready setTimeout(() => { const noteContent = document.getElementById('note-content'); if (noteContent) { noteContent.innerHTML = latestNote.content; console.log('Note content loaded into editor'); } else { console.error('Note content element not found!'); } }, 100); } else { console.log('No notes found to load'); } } // ======================================== // 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); } } })();