← All changes
|
includes/extensions/Custom_Cursor/Custom_Cursor.php
+315
-24
51.1.44
→
51.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, |