PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.83
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.83
51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 51.1.46 All 39 releases
← All changes | includes/extensions/Custom_Cursor/Custom_Cursor.php +315 -24 51.1.4451.1.83 View file →
@@ -25,8 +25,15 @@
25 25 */
26 26 public const OPTION_KEY = 'king_addons_custom_cursor_settings';
27 27
28 28 /**
29 + * Admin screen hook suffix for the settings page.
30 + *
31 + * @var string
32 + */
33 + private string $settings_page_hook = '';
34 +
35 + /**
29 36 * Constructor.
30 37 */
31 38 public function __construct()
32 39 {
@@ -32,8 +39,9 @@
32 39 {
33 40 add_action('admin_init', [$this, 'register_settings']);
34 41 // Use priority 15 to ensure the parent menu exists before adding this submenu
35 42 add_action('admin_menu', [$this, 'register_settings_page'], 15);
43 + add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']);
36 44 add_action('wp_enqueue_scripts', [$this, 'maybe_enqueue_assets']);
37 45 add_action('elementor/preview/enqueue_scripts', [$this, 'enqueue_preview_assets']);
38 46 add_action('wp_footer', [$this, 'render_cursor_markup']);
39 47 add_filter('body_class', [$this, 'filter_body_classes']);
@@ -72,9 +80,9 @@
72 80 * @return void
73 81 */
74 82 public function register_settings_page(): void
75 83 {
76 - add_submenu_page(
84 + $this->settings_page_hook = (string) add_submenu_page(
77 85 'king-addons',
78 86 esc_html__('Custom Cursor', 'king-addons'),
79 87 esc_html__('Custom Cursor', 'king-addons'),
80 88 'manage_options',
@@ -83,8 +91,29 @@
83 91 );
84 92 }
85 93
86 94 /**
95 + * Enqueue admin assets for the Custom Cursor settings page.
96 + *
97 + * @param string $hook_suffix Current admin page hook suffix.
98 + *
99 + * @return void
100 + */
101 + public function enqueue_admin_assets(string $hook_suffix): void
102 + {
103 + if ('' === $this->settings_page_hook) {
104 + return;
105 + }
106 +
107 + if ($hook_suffix !== $this->settings_page_hook) {
108 + return;
109 + }
110 +
111 + // Required for wp.media modal.
112 + wp_enqueue_media();
113 + }
114 +
115 + /**
87 116 * Render admin settings page.
88 117 *
89 118 * @return void
90 119 */
@@ -100,9 +129,9 @@
100 129 // Theme mode is per-user
101 130 $theme_mode = get_user_meta(get_current_user_id(), 'king_addons_theme_mode', true);
102 131 $allowed_theme_modes = ['dark', 'light', 'auto'];
103 132 if (!in_array($theme_mode, $allowed_theme_modes, true)) {
104 - $theme_mode = 'dark';
133 + $theme_mode = 'auto';
105 134 }
106 135
107 136 // Enqueue shared V3 styles
108 137 wp_enqueue_style(
@@ -227,8 +256,53 @@
227 256 width: 18px;
228 257 height: 18px;
229 258 }
230 259
260 + /* Select dropdown with arrow */
261 + .ka-cc-v3 select {
262 + width: 100%;
263 + padding: 14px 48px 14px 18px !important;
264 + font-size: 15px;
265 + font-family: inherit;
266 + font-weight: 400;
267 + border: 1.5px solid rgba(0, 0, 0, 0.1);
268 + border-radius: 12px;
269 + background-color: #fff !important;
270 + color: #1d1d1f;
271 + transition: all 0.2s ease;
272 + cursor: pointer;
273 + -webkit-appearance: none !important;
274 + -moz-appearance: none !important;
275 + appearance: none !important;
276 + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='%231d1d1f' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") !important;
277 + background-repeat: no-repeat !important;
278 + background-position: right 16px center !important;
279 + background-size: 18px 18px !important;
280 + }
281 + body.ka-v3-dark .ka-cc-v3 select {
282 + background-color: #2c2c2e !important;
283 + border-color: rgba(255, 255, 255, 0.15);
284 + color: #f5f5f7;
285 + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='%23f5f5f7' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") !important;
286 + }
287 + .ka-cc-v3 select:hover {
288 + border-color: rgba(168, 85, 247, 0.5);
289 + }
290 + .ka-cc-v3 select:focus {
291 + outline: none;
292 + border-color: #a855f7;
293 + box-shadow: 0 0 0 3px rgba(168, 85, 247, 0.15);
294 + }
295 + .ka-cc-v3 select option {
296 + background: #fff;
297 + color: #1d1d1f;
298 + padding: 10px;
299 + }
300 + body.ka-v3-dark .ka-cc-v3 select option {
301 + background: #2c2c2e;
302 + color: #f5f5f7;
303 + }
304 +
231 305 /* Opacity range slider */
232 306 .ka-cc-v3 input[type="range"] {
233 307 -webkit-appearance: none;
234 308 height: 6px;
@@ -283,18 +357,19 @@
283 357 color: #a855f7;
284 358 }
285 359 .ka-cc-v3 .ka-preview-area {
286 360 height: 280px;
287 - display: flex;
288 - align-items: center;
289 - justify-content: center;
290 361 background: linear-gradient(135deg, #f5f5f7 0%, #e8e8ed 100%);
291 362 position: relative;
292 363 overflow: hidden;
364 + cursor: default;
293 365 }
294 366 body.ka-v3-dark .ka-cc-v3 .ka-preview-area {
295 - background: linear-gradient(135deg, #2c2c2e 0%, #1c1c1e 100%);
367 + background: linear-gradient(135deg, #48484a 0%, #3a3a3c 100%);
296 368 }
369 + .ka-cc-v3 .ka-preview-area.ka-cc-hide-original {
370 + cursor: none;
371 + }
297 372 .ka-cc-v3 .ka-preview-area::before {
298 373 content: '';
299 374 position: absolute;
300 375 inset: 0;
@@ -305,11 +380,14 @@
305 380 body.ka-v3-dark .ka-cc-v3 .ka-preview-area::before {
306 381 background-image: radial-gradient(rgba(255,255,255,0.1) 1px, transparent 1px);
307 382 }
308 383 .ka-cc-v3 .ka-preview-cursor {
309 - position: relative;
384 + position: absolute;
385 + top: 0;
386 + left: 0;
310 387 z-index: 1;
311 388 transition: all 0.15s ease;
389 + pointer-events: none;
312 390 }
313 391 .ka-cc-v3 .ka-preview-cursor-inner {
314 392 border-radius: 50%;
315 393 transition: all 0.15s ease;
@@ -379,11 +457,17 @@
379 457 body.ka-v3-dark .ka-cc-v3 .ka-preview-info-value {
380 458 color: #f5f5f7;
381 459 }
382 460
461 + /* Ripple animation */
462 + @keyframes ka-cc-ripple {
463 + 0% { transform: scale(0); opacity: 0.5; }
464 + 100% { transform: scale(2.5); opacity: 0; }
465 + }
466 +
383 467 /* Responsive */
384 468 @media (max-width: 1200px) {
385 - .ka-cc-v3 { flex-direction: column; }
469 + .ka-cc-v3 { flex-direction: column-reverse; }
386 470 .ka-cc-v3 .ka-cc-sidebar {
387 471 width: 100%;
388 472 order: -1;
389 473 }
@@ -410,11 +494,11 @@
410 494 <?php echo !empty($settings['enabled']) ? esc_html__('Enabled', 'king-addons') : esc_html__('Disabled', 'king-addons'); ?>
411 495 </span>
412 496 <div class="ka-v3-segmented" id="ka-v3-theme-segment" role="radiogroup" aria-label="Theme" data-active="<?php echo esc_attr($theme_mode); ?>">
413 497 <span class="ka-v3-segmented-indicator" aria-hidden="true"></span>
414 - <button type="button" class="ka-v3-segmented-btn" data-theme="light" aria-pressed="<?php echo $theme_mode === 'light' ? 'true' : 'false'; ?>"><?php esc_html_e('Light', 'king-addons'); ?></button>
415 - <button type="button" class="ka-v3-segmented-btn" data-theme="dark" aria-pressed="<?php echo $theme_mode === 'dark' ? 'true' : 'false'; ?>"><?php esc_html_e('Dark', 'king-addons'); ?></button>
416 - <button type="button" class="ka-v3-segmented-btn" data-theme="auto" aria-pressed="<?php echo $theme_mode === 'auto' ? 'true' : 'false'; ?>"><?php esc_html_e('Auto', 'king-addons'); ?></button>
498 + <button type="button" class="ka-v3-segmented-btn" data-theme="light" aria-pressed="<?php echo $theme_mode === 'light' ? 'true' : 'false'; ?>"><span class="ka-v3-segmented-icon" aria-hidden="true">☀︎</span><?php esc_html_e('Light', 'king-addons'); ?></button>
499 + <button type="button" class="ka-v3-segmented-btn" data-theme="dark" aria-pressed="<?php echo $theme_mode === 'dark' ? 'true' : 'false'; ?>"><span class="ka-v3-segmented-icon" aria-hidden="true">☾</span><?php esc_html_e('Dark', 'king-addons'); ?></button>
500 + <button type="button" class="ka-v3-segmented-btn" data-theme="auto" aria-pressed="<?php echo $theme_mode === 'auto' ? 'true' : 'false'; ?>"><span class="ka-v3-segmented-icon" aria-hidden="true">◐</span><?php esc_html_e('Auto', 'king-addons'); ?></button>
417 501 </div>
418 502 </div>
419 503 </div>
420 504
@@ -448,8 +532,18 @@
448 532 </label>
449 533 </div>
450 534 </div>
451 535 <div class="ka-row">
536 + <div class="ka-row-label"><?php esc_html_e('Hide Original Cursor', 'king-addons'); ?></div>
537 + <div class="ka-row-field">
538 + <label class="ka-toggle">
539 + <input type="checkbox" name="<?php echo esc_attr(self::OPTION_KEY); ?>[hide_original_cursor]" value="1" <?php checked(!empty($settings['hide_original_cursor'])); ?> />
540 + <span class="ka-toggle-slider"></span>
541 + <span class="ka-toggle-label"><?php esc_html_e('Hide default cursor (custom cursor follows it otherwise)', 'king-addons'); ?></span>
542 + </label>
543 + </div>
544 + </div>
545 + <div class="ka-row">
452 546 <div class="ka-row-label"><?php echo esc_html__('Rendering Mode', 'king-addons'); ?></div>
453 547 <div class="ka-row-field">
454 548 <select name="<?php echo esc_attr(self::OPTION_KEY); ?>[rendering_mode]" id="ka-cc-mode">
455 549 <option value="css" <?php selected($settings['rendering_mode'], 'css'); ?>><?php echo esc_html__('CSS Only (Faster)', 'king-addons'); ?></option>
@@ -541,8 +635,65 @@
541 635 </div>
542 636 </div>
543 637 </div>
544 638
639 + <!-- Image Cursor Settings (shown when type=image) -->
640 + <div class="ka-card" id="ka-cc-image-card" style="display:none">
641 + <div class="ka-card-header">
642 + <span class="dashicons dashicons-format-image"></span>
643 + <h2><?php echo esc_html__('Image Cursor', 'king-addons'); ?></h2>
644 + </div>
645 + <div class="ka-card-body">
646 + <div class="ka-row">
647 + <div class="ka-row-label"><?php echo esc_html__('Cursor Image', 'king-addons'); ?><?php if (!$is_pro): ?><span class="ka-pro-badge">PRO</span><?php endif; ?></div>
648 + <div class="ka-row-field">
649 + <div class="ka-image-upload-wrap" style="display:flex;align-items:center;gap:12px">
650 + <div class="ka-image-preview" id="ka-cc-image-preview" style="width:64px;height:64px;border:2px dashed #d1d5db;border-radius:8px;display:flex;align-items:center;justify-content:center;background:#f9fafb;overflow:hidden">
651 + <?php if (!empty($settings['image']['url'])): ?>
652 + <img src="<?php echo esc_url($settings['image']['url']); ?>" style="max-width:100%;max-height:100%;object-fit:contain" />
653 + <?php else: ?>
654 + <span class="dashicons dashicons-plus-alt2" style="color:#9ca3af;font-size:24px"></span>
655 + <?php endif; ?>
656 + </div>
657 + <div style="display:flex;flex-direction:column;gap:6px">
658 + <button type="button" class="button" id="ka-cc-image-upload-btn" <?php disabled(!$is_pro); ?>><?php echo esc_html__('Select Image', 'king-addons'); ?></button>
659 + <button type="button" class="button" id="ka-cc-image-remove-btn" style="color:#ef4444" <?php disabled(!$is_pro || empty($settings['image']['url'])); ?>><?php echo esc_html__('Remove', 'king-addons'); ?></button>
660 + </div>
661 + <input type="hidden" name="<?php echo esc_attr(self::OPTION_KEY); ?>[image][url]" id="ka-cc-image-url" value="<?php echo esc_attr($settings['image']['url']); ?>" />
662 + </div>
663 + </div>
664 + </div>
665 + <div class="ka-row">
666 + <div class="ka-row-label"><?php echo esc_html__('Image Size', 'king-addons'); ?></div>
667 + <div class="ka-row-field">
668 + <input type="number" name="<?php echo esc_attr(self::OPTION_KEY); ?>[image][size]" value="<?php echo esc_attr((int) $settings['image']['size']); ?>" min="8" max="256" id="ka-cc-image-size" <?php disabled(!$is_pro); ?> /> <span style="color:#64748b">px</span>
669 + </div>
670 + </div>
671 + <div class="ka-row">
672 + <div class="ka-row-label"><?php echo esc_html__('Hotspot X', 'king-addons'); ?></div>
673 + <div class="ka-row-field">
674 + <input type="number" name="<?php echo esc_attr(self::OPTION_KEY); ?>[image][hotspot_x]" value="<?php echo esc_attr((int) $settings['image']['hotspot_x']); ?>" min="0" max="256" id="ka-cc-image-hotspot-x" <?php disabled(!$is_pro); ?> /> <span style="color:#64748b">px</span>
675 + </div>
676 + </div>
677 + <div class="ka-row">
678 + <div class="ka-row-label"><?php echo esc_html__('Hotspot Y', 'king-addons'); ?></div>
679 + <div class="ka-row-field">
680 + <input type="number" name="<?php echo esc_attr(self::OPTION_KEY); ?>[image][hotspot_y]" value="<?php echo esc_attr((int) $settings['image']['hotspot_y']); ?>" min="0" max="256" id="ka-cc-image-hotspot-y" <?php disabled(!$is_pro); ?> /> <span style="color:#64748b">px</span>
681 + </div>
682 + </div>
683 + <div class="ka-row">
684 + <div class="ka-row-label"><?php echo esc_html__('Retina (2x)', 'king-addons'); ?></div>
685 + <div class="ka-row-field">
686 + <label class="ka-toggle">
687 + <input type="checkbox" name="<?php echo esc_attr(self::OPTION_KEY); ?>[image][retina]" value="1" <?php checked(!empty($settings['image']['retina'])); ?> <?php disabled(!$is_pro); ?> />
688 + <span class="ka-toggle-slider"></span>
689 + <span class="ka-toggle-label"><?php echo esc_html__('Image is @2x for Retina displays', 'king-addons'); ?></span>
690 + </label>
691 + </div>
692 + </div>
693 + </div>
694 + </div>
695 +
545 696 <!-- States -->
546 697 <div class="ka-card">
547 698 <div class="ka-card-header">
548 699 <span class="dashicons dashicons-superhero"></span>
@@ -647,13 +798,8 @@
647 798 <input type="hidden" name="<?php echo esc_attr(self::OPTION_KEY); ?>[targeting][include_post_types]" value="<?php echo esc_attr(implode(',', $settings['targeting']['include_post_types'])); ?>" />
648 799 <input type="hidden" name="<?php echo esc_attr(self::OPTION_KEY); ?>[targeting][include_urls]" value="<?php echo esc_attr(implode(',', $settings['targeting']['include_urls'])); ?>" />
649 800 <input type="hidden" name="<?php echo esc_attr(self::OPTION_KEY); ?>[targeting][exclude_pages]" value="<?php echo esc_attr(implode(',', $settings['targeting']['exclude_pages'])); ?>" />
650 801 <input type="hidden" name="<?php echo esc_attr(self::OPTION_KEY); ?>[targeting][exclude_post_types]" value="<?php echo esc_attr(implode(',', $settings['targeting']['exclude_post_types'])); ?>" />
651 - <input type="hidden" name="<?php echo esc_attr(self::OPTION_KEY); ?>[image][url]" value="<?php echo esc_attr($settings['image']['url']); ?>" />
652 - <input type="hidden" name="<?php echo esc_attr(self::OPTION_KEY); ?>[image][size]" value="<?php echo esc_attr($settings['image']['size']); ?>" />
653 - <input type="hidden" name="<?php echo esc_attr(self::OPTION_KEY); ?>[image][hotspot_x]" value="<?php echo esc_attr($settings['image']['hotspot_x']); ?>" />
654 - <input type="hidden" name="<?php echo esc_attr(self::OPTION_KEY); ?>[image][hotspot_y]" value="<?php echo esc_attr($settings['image']['hotspot_y']); ?>" />
655 - <input type="hidden" name="<?php echo esc_attr(self::OPTION_KEY); ?>[image][retina]" value="<?php echo !empty($settings['image']['retina']) ? '1' : ''; ?>" />
656 802 <input type="hidden" name="<?php echo esc_attr(self::OPTION_KEY); ?>[magnetic][enabled]" value="<?php echo !empty($settings['magnetic']['enabled']) ? '1' : ''; ?>" />
657 803 <input type="hidden" name="<?php echo esc_attr(self::OPTION_KEY); ?>[magnetic][strength]" value="<?php echo esc_attr($settings['magnetic']['strength']); ?>" />
658 804 <input type="hidden" name="<?php echo esc_attr(self::OPTION_KEY); ?>[magnetic][radius]" value="<?php echo esc_attr($settings['magnetic']['radius']); ?>" />
659 805 <input type="hidden" name="<?php echo esc_attr(self::OPTION_KEY); ?>[magnetic][selectors]" value="<?php echo esc_attr($settings['magnetic']['selectors']); ?>" />
@@ -852,8 +998,11 @@
852 998 'dual': 'Dual Circle',
853 999 'gradient': 'Gradient',
854 1000 'image': 'Image'
855 1001 };
1002 +
1003 + // Used to keep the cursor centered when preview is not hovered
1004 + let previewVisualSize = 14;
856 1005
857 1006 function updatePreview() {
858 1007 const size = parseInt(sizeInput.value) || 14;
859 1008 const fill = fillInput.value || '#111111';
@@ -886,8 +1035,9 @@
886 1035 inner.style.transform = 'scale(' + scale + ')';
887 1036
888 1037 // Base outer ring
889 1038 const outerSize = size + (borderWidth * 2) + 8;
1039 + previewVisualSize = Math.max(size, outerSize);
890 1040 outer.style.width = outerSize + 'px';
891 1041 outer.style.height = outerSize + 'px';
892 1042 outer.style.border = borderWidth + 'px solid ' + border;
893 1043 outer.style.marginTop = '-' + (outerSize / 2) + 'px';
@@ -976,8 +1126,9 @@
976 1126 inner.style.display = 'block';
977 1127 inner.style.borderRadius = '60% 40% 30% 70% / 60% 30% 70% 40%';
978 1128 inner.style.width = (size * 1.3) + 'px';
979 1129 inner.style.height = (size * 1.3) + 'px';
1130 + previewVisualSize = Math.max(previewVisualSize, (size * 1.3));
980 1131 break;
981 1132
982 1133 case 'dual':
983 1134 inner.style.display = 'block';
@@ -995,8 +1146,9 @@
995 1146 outer2.style.marginLeft = '-' + (outer2Size / 2) + 'px';
996 1147 outer2.style.top = '50%';
997 1148 outer2.style.left = '50%';
998 1149 outer2.style.opacity = '0.3';
1150 + previewVisualSize = Math.max(previewVisualSize, outer2Size);
999 1151 break;
1000 1152
1001 1153 case 'gradient':
1002 1154 inner.style.display = 'block';
@@ -1002,15 +1154,30 @@
1002 1154 inner.style.display = 'block';
1003 1155 inner.style.background = 'linear-gradient(135deg, ' + fill + ' 0%, ' + border + ' 100%)';
1004 1156 inner.style.width = (size * 1.2) + 'px';
1005 1157 inner.style.height = (size * 1.2) + 'px';
1158 + previewVisualSize = Math.max(previewVisualSize, (size * 1.2));
1006 1159 break;
1007 1160
1008 1161 case 'image':
1009 1162 inner.style.display = 'block';
1010 - inner.style.background = '#ddd';
1011 - inner.style.border = '2px dashed #999';
1012 1163 inner.style.borderRadius = '4px';
1164 + inner.style.background = 'transparent';
1165 + const imgUrl = document.getElementById('ka-cc-image-url');
1166 + const imgSize = document.getElementById('ka-cc-image-size');
1167 + const imgSizeVal = parseInt(imgSize?.value) || size;
1168 + inner.style.width = imgSizeVal + 'px';
1169 + inner.style.height = imgSizeVal + 'px';
1170 + if (imgUrl && imgUrl.value) {
1171 + inner.style.backgroundImage = 'url(' + imgUrl.value + ')';
1172 + inner.style.backgroundSize = 'contain';
1173 + inner.style.backgroundRepeat = 'no-repeat';
1174 + inner.style.backgroundPosition = 'center';
1175 + } else {
1176 + inner.style.background = '#ddd';
1177 + inner.style.border = '2px dashed #999';
1178 + }
1179 + previewVisualSize = Math.max(previewVisualSize, imgSizeVal);
1013 1180 break;
1014 1181
1015 1182 default:
1016 1183 inner.style.display = 'block';
@@ -1026,8 +1193,19 @@
1026 1193 infoSize.textContent = size + 'px';
1027 1194 infoFill.style.background = fill;
1028 1195 infoBorder.style.background = border;
1029 1196 }
1197 +
1198 + function centerPreviewCursor() {
1199 + const width = previewArea ? previewArea.clientWidth : 0;
1200 + const height = previewArea ? previewArea.clientHeight : 0;
1201 + const half = (previewVisualSize || 14) / 2;
1202 + cursorX = (width / 2) - half;
1203 + cursorY = (height / 2) - half;
1204 + targetX = cursorX;
1205 + targetY = cursorY;
1206 + cursor.style.transform = 'translate(' + cursorX + 'px, ' + cursorY + 'px)';
1207 + }
1030 1208
1031 1209 // Color picker sync
1032 1210 fillPicker.addEventListener('input', function() {
1033 1211 fillInput.value = this.value;
@@ -1090,28 +1268,80 @@
1090 1268 // Interactive preview - follow mouse
1091 1269 let isHovering = false;
1092 1270 let cursorX = 0, cursorY = 0;
1093 1271 let targetX = 0, targetY = 0;
1272 + let firstMove = true;
1273 + const hideOriginalInput = document.querySelector('input[name="<?php echo esc_attr(self::OPTION_KEY); ?>[hide_original_cursor]"]');
1094 1274
1095 - previewArea.addEventListener('mouseenter', function() {
1275 + function updatePreviewCursor() {
1276 + if (!hideOriginalInput) {
1277 + return;
1278 + }
1279 + previewArea.classList.toggle('ka-cc-hide-original', !!hideOriginalInput.checked);
1280 + }
1281 +
1282 + if (hideOriginalInput) {
1283 + hideOriginalInput.addEventListener('change', updatePreviewCursor);
1284 + updatePreviewCursor();
1285 + }
1286 +
1287 + previewArea.addEventListener('mouseenter', function(e) {
1096 1288 isHovering = true;
1289 + firstMove = true;
1290 + // Immediately position cursor at mouse entry point
1291 + const rect = previewArea.getBoundingClientRect();
1292 + const size = parseInt(sizeInput.value) || 14;
1293 + targetX = e.clientX - rect.left - (size / 2);
1294 + targetY = e.clientY - rect.top - (size / 2);
1295 + // Set cursor position instantly on enter
1296 + cursorX = targetX;
1297 + cursorY = targetY;
1298 + cursor.style.transform = 'translate(' + cursorX + 'px, ' + cursorY + 'px)';
1097 1299 });
1098 1300 previewArea.addEventListener('mouseleave', function() {
1099 1301 isHovering = false;
1100 - cursor.style.transform = '';
1302 + centerPreviewCursor();
1303 + firstMove = true;
1101 1304 });
1102 1305 previewArea.addEventListener('mousemove', function(e) {
1103 1306 if (!isHovering) return;
1104 1307 const rect = previewArea.getBoundingClientRect();
1105 - targetX = e.clientX - rect.left - 20;
1106 - targetY = e.clientY - rect.top - 20;
1308 + const size = parseInt(sizeInput.value) || 14;
1309 + targetX = e.clientX - rect.left - (size / 2);
1310 + targetY = e.clientY - rect.top - (size / 2);
1311 + // On first move after enter, snap to position
1312 + if (firstMove) {
1313 + cursorX = targetX;
1314 + cursorY = targetY;
1315 + firstMove = false;
1316 + }
1107 1317 });
1108 1318
1319 + // Click ripple effect in preview
1320 + previewArea.addEventListener('mousedown', function(e) {
1321 + if (!isHovering) return;
1322 + // Create ripple element
1323 + const ripple = document.createElement('div');
1324 + const rect = previewArea.getBoundingClientRect();
1325 + const x = e.clientX - rect.left;
1326 + const y = e.clientY - rect.top;
1327 + ripple.style.cssText = 'position:absolute;border-radius:50%;background:' + (fillInput.value || '#a855f7') + ';opacity:0.4;pointer-events:none;transform:scale(0);animation:ka-cc-ripple 0.6s ease-out forwards;';
1328 + ripple.style.left = (x - 30) + 'px';
1329 + ripple.style.top = (y - 30) + 'px';
1330 + ripple.style.width = '60px';
1331 + ripple.style.height = '60px';
1332 + previewArea.appendChild(ripple);
1333 + setTimeout(function() { ripple.remove(); }, 600);
1334 + // Scale down cursor briefly
1335 + cursor.style.transition = 'transform 0.1s ease';
1336 + setTimeout(function() { cursor.style.transition = 'none'; }, 100);
1337 + });
1338 +
1109 1339 // Smooth animation
1110 1340 function animateCursor() {
1111 1341 if (isHovering) {
1112 - cursorX += (targetX - cursorX) * 0.15;
1113 - cursorY += (targetY - cursorY) * 0.15;
1342 + cursorX += (targetX - cursorX) * 0.18;
1343 + cursorY += (targetY - cursorY) * 0.18;
1114 1344 cursor.style.transform = 'translate(' + cursorX + 'px, ' + cursorY + 'px)';
1115 1345 }
1116 1346 requestAnimationFrame(animateCursor);
1117 1347 }
@@ -1118,8 +1348,63 @@
1118 1348 animateCursor();
1119 1349
1120 1350 // Initial update
1121 1351 updatePreview();
1352 + centerPreviewCursor();
1353 +
1354 + // ====== Image Cursor Card visibility ======
1355 + const imageCard = document.getElementById('ka-cc-image-card');
1356 + function updateImageCardVisibility() {
1357 + if (imageCard && typeInput) {
1358 + imageCard.style.display = typeInput.value === 'image' ? '' : 'none';
1359 + }
1360 + }
1361 + if (typeInput) {
1362 + typeInput.addEventListener('change', updateImageCardVisibility);
1363 + updateImageCardVisibility();
1364 + }
1365 +
1366 + // ====== Image Upload via Media Library ======
1367 + const imageUploadBtn = document.getElementById('ka-cc-image-upload-btn');
1368 + const imageRemoveBtn = document.getElementById('ka-cc-image-remove-btn');
1369 + const imageUrlInput = document.getElementById('ka-cc-image-url');
1370 + const imagePreview = document.getElementById('ka-cc-image-preview');
1371 +
1372 + if (imageUploadBtn) {
1373 + imageUploadBtn.addEventListener('click', function(e) {
1374 + e.preventDefault();
1375 + e.stopPropagation();
1376 +
1377 + if (typeof wp === 'undefined' || !wp.media) {
1378 + alert('Media library not loaded. Please refresh the page.');
1379 + return;
1380 + }
1381 +
1382 + const mediaFrame = wp.media({
1383 + title: '<?php echo esc_js(__('Select Cursor Image', 'king-addons')); ?>',
1384 + button: { text: '<?php echo esc_js(__('Use this image', 'king-addons')); ?>' },
1385 + multiple: false,
1386 + library: { type: 'image' }
1387 + });
1388 + mediaFrame.on('select', function() {
1389 + const attachment = mediaFrame.state().get('selection').first().toJSON();
1390 + if (imageUrlInput) imageUrlInput.value = attachment.url;
1391 + if (imagePreview) imagePreview.innerHTML = '<img src="' + attachment.url + '" style="max-width:100%;max-height:100%;object-fit:contain" />';
1392 + if (imageRemoveBtn) imageRemoveBtn.disabled = false;
1393 + updatePreview();
1394 + });
1395 + mediaFrame.open();
1396 + });
1397 + }
1398 + if (imageRemoveBtn) {
1399 + imageRemoveBtn.addEventListener('click', function(e) {
1400 + e.preventDefault();
1401 + imageUrlInput.value = '';
1402 + imagePreview.innerHTML = '<span class="dashicons dashicons-plus-alt2" style="color:#9ca3af;font-size:24px"></span>';
1403 + imageRemoveBtn.disabled = true;
1404 + updatePreview();
1405 + });
1406 + }
1122 1407 })();
1123 1408 </script>
1124 1409 <?php
1125 1410 }
@@ -1279,8 +1564,11 @@
1279 1564 {
1280 1565 $settings = $this->get_settings();
1281 1566 if ($this->should_activate(false, $settings)) {
1282 1567 $classes[] = 'ka-custom-cursor-enabled';
1568 + if (!empty($settings['hide_original_cursor'])) {
1569 + $classes[] = 'ka-cursor-hide-original';
1570 + }
1283 1571 }
1284 1572 return $classes;
1285 1573 }
1286 1574
@@ -1432,8 +1720,9 @@
1432 1720 $output = $defaults;
1433 1721
1434 1722 $output['enabled'] = !empty($input['enabled']);
1435 1723 $output['disable_on_mobile'] = !empty($input['disable_on_mobile']);
1724 + $output['hide_original_cursor'] = !empty($input['hide_original_cursor']);
1436 1725 $output['rendering_mode'] = in_array($input['rendering_mode'] ?? 'css', ['css', 'enhanced'], true) ? $input['rendering_mode'] : 'css';
1437 1726
1438 1727 $preset = $input['preset'] ?? [];
1439 1728 $output['preset'] = [
@@ -1535,8 +1824,9 @@
1535 1824
1536 1825 return [
1537 1826 'enabled' => $enabled,
1538 1827 'mode' => $settings['rendering_mode'],
1828 + 'hideOriginalCursor' => !empty($settings['hide_original_cursor']),
1539 1829 'preset' => $settings['preset'],
1540 1830 'states' => $settings['states'],
1541 1831 'image' => $settings['image'],
1542 1832 'magnetic' => $settings['magnetic'],
@@ -1635,8 +1925,9 @@
1635 1925 {
1636 1926 return [
1637 1927 'enabled' => false,
1638 1928 'disable_on_mobile' => true,
1929 + 'hide_original_cursor' => true,
1639 1930 'rendering_mode' => 'css',
1640 1931 'preset' => [
1641 1932 'type' => 'dot',
1642 1933 'size' => 14,