// ==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);
}
}
})();