| @@ -91,8 +91,64 @@ | ||
| 91 | 91 | |
| 92 | 92 | } |
| 93 | 93 | } |
| 94 | 94 | |
| 95 | + /** | |
| 96 | + * Flag the editor body when Elementor's panel is dark. | |
| 97 | + * | |
| 98 | + * Elementor has no stable "dark panel" class here, and its ui_theme | |
| 99 | + * preference can be light, dark, or auto (following the OS), so the panel's | |
| 100 | + * own background is measured instead. That keeps the AI controls readable | |
| 101 | + * whichever way the theme was arrived at. | |
| 102 | + */ | |
| 103 | + function syncPanelTheme() { | |
| 104 | + var isDark = null; | |
| 105 | + | |
| 106 | + try { | |
| 107 | + // Walk up from the control area to the first element that actually | |
| 108 | + // paints a background. The panel's inner wrappers are transparent, | |
| 109 | + // so asking any single one of them tells us nothing. | |
| 110 | + var node = document.querySelector('#elementor-controls') | |
| 111 | + || document.querySelector('#elementor-panel-content-wrapper') | |
| 112 | + || document.querySelector('#elementor-panel'); | |
| 113 | + | |
| 114 | + while (node) { | |
| 115 | + var parts = getComputedStyle(node).backgroundColor.match(/[\d.]+/g); | |
| 116 | + if (parts && parts.length >= 3 && (parts.length < 4 || parseFloat(parts[3]) > 0)) { | |
| 117 | + var luminance = (0.2126 * parts[0] + 0.7152 * parts[1] + 0.0722 * parts[2]) / 255; | |
| 118 | + isDark = luminance < 0.5; | |
| 119 | + break; | |
| 120 | + } | |
| 121 | + node = node.parentElement; | |
| 122 | + } | |
| 123 | + } catch (e) { | |
| 124 | + // Fall through to the media query below. | |
| 125 | + } | |
| 126 | + | |
| 127 | + if (isDark === null) { | |
| 128 | + try { | |
| 129 | + isDark = window.matchMedia('(prefers-color-scheme: dark)').matches; | |
| 130 | + } catch (e) { | |
| 131 | + isDark = false; | |
| 132 | + } | |
| 133 | + } | |
| 134 | + | |
| 135 | + $('body').toggleClass('king-addons-ai-dark', !!isDark); | |
| 136 | + } | |
| 137 | + | |
| 138 | + // Keep up with the OS switching themes while the editor is open. | |
| 139 | + try { | |
| 140 | + var colorSchemeQuery = window.matchMedia('(prefers-color-scheme: dark)'); | |
| 141 | + var onSchemeChange = function() { syncPanelTheme(); }; | |
| 142 | + if (colorSchemeQuery.addEventListener) { | |
| 143 | + colorSchemeQuery.addEventListener('change', onSchemeChange); | |
| 144 | + } else if (colorSchemeQuery.addListener) { | |
| 145 | + colorSchemeQuery.addListener(onSchemeChange); | |
| 146 | + } | |
| 147 | + } catch (e) { | |
| 148 | + // No media query support: the measured panel background still applies. | |
| 149 | + } | |
| 150 | + | |
| 95 | 151 | // Add CSS for animations directly to the document |
| 96 | 152 | function injectAnimationStyles() { |
| 97 | 153 | if ($('#king-addons-ai-animations').length === 0) { |
| 98 | 154 | const animationStyles = ` |
| @@ -120,13 +176,11 @@ | ||
| 120 | 176 | animation: kingAddonsPulse 1.5s infinite cubic-bezier(0.66, 0, 0, 1) !important; |
| 121 | 177 | border-color: #5B03FF !important; |
| 122 | 178 | } |
| 123 | 179 | .king-addons-field-shine { |
| 124 | - background-image: linear-gradient(90deg, | |
| 125 | - rgba(91,3,255,0) 0%, | |
| 126 | - rgba(91,3,255,0.2) 25%, | |
| 127 | - rgba(225,203,255,0.3) 50%, | |
| 128 | - rgba(91,3,255,0.2) 75%, | |
| 180 | + background-image: linear-gradient(90deg, | |
| 181 | + rgba(91,3,255,0) 0%, | |
| 182 | + rgba(91,3,255,0.14) 50%, | |
| 129 | 183 | rgba(91,3,255,0) 100%); |
| 130 | 184 | background-position: -100px; |
| 131 | 185 | background-size: 140px 100%; |
| 132 | 186 | background-repeat: no-repeat; |
| @@ -146,18 +200,22 @@ | ||
| 146 | 200 | transition: all 0.3s ease; |
| 147 | 201 | margin-top: 8px; |
| 148 | 202 | width: 100%; |
| 149 | 203 | } |
| 204 | + /* These two are unavailable while the prompt is open. Keeping | |
| 205 | + the violet, only dimmed, reads as "not right now" instead of | |
| 206 | + a grey block that looks like a styling failure. */ | |
| 150 | 207 | .king-addons-ai-buttons-wrapper.is-processing .ai-generate-btn, |
| 151 | 208 | .king-addons-ai-buttons-wrapper.is-processing .ai-change-btn { |
| 152 | - background: linear-gradient(135deg, #d6d6d6, #a0a0a0) !important; | |
| 153 | - opacity: 0.7; | |
| 209 | + background: rgba(91, 3, 255, .05) !important; | |
| 210 | + color: rgba(74, 2, 214, .42) !important; | |
| 154 | 211 | cursor: default; |
| 155 | 212 | box-shadow: none !important; |
| 213 | + transform: none !important; | |
| 156 | 214 | pointer-events: none; |
| 157 | 215 | } |
| 158 | 216 | .king-addons-ai-buttons-wrapper.is-processing img { |
| 159 | - filter: grayscale(100%); | |
| 217 | + opacity: .4 !important; | |
| 160 | 218 | } |
| 161 | 219 | .ai-prompt-container { |
| 162 | 220 | display: flex; |
| 163 | 221 | align-items: center; |
| @@ -185,16 +243,78 @@ | ||
| 185 | 243 | outline: none; |
| 186 | 244 | } |
| 187 | 245 | .ai-prompt-examples { |
| 188 | 246 | font-size: 11px; |
| 189 | - color: #6d7882; | |
| 247 | + color: #5c666f; | |
| 190 | 248 | margin-top: 4px; |
| 191 | 249 | line-height: 1.4; |
| 192 | 250 | } |
| 193 | 251 | .ai-prompt-examples strong { |
| 194 | - color: #556068; | |
| 195 | - font-weight: 500; | |
| 252 | + color: #3f4750; | |
| 253 | + font-weight: 600; | |
| 196 | 254 | } |
| 255 | + | |
| 256 | + /* | |
| 257 | + * Elementor's panel can be light or dark, and the setting | |
| 258 | + * follows the OS when it is set to Auto, so the theme is | |
| 259 | + * detected at runtime and flagged on <body>. Without this | |
| 260 | + * the hint text is near-invisible on the dark panel. | |
| 261 | + */ | |
| 262 | + body.king-addons-ai-dark .ai-prompt-examples { | |
| 263 | + color: #a7aeb6; | |
| 264 | + } | |
| 265 | + body.king-addons-ai-dark .ai-prompt-examples strong { | |
| 266 | + color: #e6e9ed; | |
| 267 | + } | |
| 268 | + body.king-addons-ai-dark .ai-prompt-input { | |
| 269 | + background: #2a2d32; | |
| 270 | + border-color: #45494f; | |
| 271 | + color: #e6e9ed; | |
| 272 | + } | |
| 273 | + body.king-addons-ai-dark .ai-prompt-input::placeholder { | |
| 274 | + color: #8d949c; | |
| 275 | + } | |
| 276 | + body.king-addons-ai-dark .ai-prompt-cancel { | |
| 277 | + background: #2a2d32; | |
| 278 | + border-color: #45494f; | |
| 279 | + color: #d5d9de; | |
| 280 | + } | |
| 281 | + body.king-addons-ai-dark .ai-prompt-cancel:hover { | |
| 282 | + background: #35393f; | |
| 283 | + } | |
| 284 | + body.king-addons-ai-dark .king-addons-ai-buttons-wrapper.is-processing .ai-generate-btn, | |
| 285 | + body.king-addons-ai-dark .king-addons-ai-buttons-wrapper.is-processing .ai-change-btn { | |
| 286 | + background: rgba(124, 77, 255, .09) !important; | |
| 287 | + color: rgba(201, 182, 255, .45) !important; | |
| 288 | + } | |
| 289 | + | |
| 290 | + /* The tint needs a lighter hue and a brighter label on the | |
| 291 | + dark panel; the light-panel values are in ai-textfield.css. */ | |
| 292 | + body.king-addons-ai-dark .ai-generate-btn, | |
| 293 | + body.king-addons-ai-dark .ai-change-btn { | |
| 294 | + --ka-ai-btn-bg: rgba(124, 77, 255, .22); | |
| 295 | + --ka-ai-btn-bg-hover: rgba(124, 77, 255, .32); | |
| 296 | + --ka-ai-btn-bg-active: rgba(124, 77, 255, .40); | |
| 297 | + --ka-ai-btn-fg: #C9B6FF; | |
| 298 | + --ka-ai-btn-ring: #8C7DFF; | |
| 299 | + } | |
| 300 | + | |
| 301 | + body.king-addons-ai-dark .ai-generate-btn img, | |
| 302 | + body.king-addons-ai-dark .ai-change-btn img { | |
| 303 | + /* The icon files are white already - let them through. */ | |
| 304 | + filter: none; | |
| 305 | + opacity: .92; | |
| 306 | + } | |
| 307 | + | |
| 308 | + body.king-addons-ai-dark .ai-prompt-submit { | |
| 309 | + background: #7C4DFF; | |
| 310 | + color: #ffffff; | |
| 311 | + border-color: transparent; | |
| 312 | + } | |
| 313 | + | |
| 314 | + body.king-addons-ai-dark .ai-prompt-submit:hover { | |
| 315 | + background: #6B3DEE; | |
| 316 | + } | |
| 197 | 317 | .ai-prompt-cancel { |
| 198 | 318 | width: 24px; |
| 199 | 319 | height: 24px; |
| 200 | 320 | display: flex; |
| @@ -221,9 +341,9 @@ | ||
| 221 | 341 | cursor: pointer; |
| 222 | 342 | transition: all 0.3s ease; |
| 223 | 343 | } |
| 224 | 344 | .ai-prompt-submit.is-processing { |
| 225 | - background: linear-gradient(135deg, #E1CBFF, #5B03FF) !important; | |
| 345 | + background: #5B03FF !important; | |
| 226 | 346 | border-color: transparent !important; |
| 227 | 347 | } |
| 228 | 348 | .ai-prompt-submit.is-processing img { |
| 229 | 349 | animation: kingAddonsRotatePulse 2s infinite ease-in-out; |
| @@ -250,8 +370,11 @@ | ||
| 250 | 370 | function injectAiButtons($container) { |
| 251 | 371 | |
| 252 | 372 | // Inject animation styles first |
| 253 | 373 | injectAnimationStyles(); |
| 374 | + | |
| 375 | + // The panel is rebuilt on every open, so re-check the theme here. | |
| 376 | + syncPanelTheme(); | |
| 254 | 377 | |
| 255 | 378 | // Handle standard text/textarea controls |
| 256 | 379 | $container.find('.elementor-control-type-text label.elementor-control-title, .elementor-control-type-textarea label.elementor-control-title').each(function(){ |
| 257 | 380 | var $label = $(this); |
| @@ -305,31 +428,15 @@ | ||
| 305 | 428 | if (!$inputWrapper.length) return; |
| 306 | 429 | } |
| 307 | 430 | |
| 308 | 431 | // Create "Generate" button |
| 309 | - var $btnLabel = $('<button type="button" class="ai-generate-btn ai-generate-btn--futuristic" title="AI Generate"></button>').css({ | |
| 310 | - verticalAlign: 'middle', | |
| 311 | - padding: '4px 8px', | |
| 312 | - fontSize: '12px', | |
| 313 | - cursor: 'pointer', | |
| 314 | - background: 'linear-gradient(135deg, #E1CBFF, #5B03FF)', | |
| 315 | - border: 'none', | |
| 316 | - borderRadius: '4px', | |
| 317 | - color: '#ffffff', | |
| 318 | - display: 'inline-flex', | |
| 319 | - alignItems: 'center', | |
| 320 | - // boxShadow: '0 0 6px rgba(225,203,255,0.7), 0 0 12px rgba(91,3,255,0.5)', | |
| 321 | - boxShadow: 'none', | |
| 322 | - transition: 'box-shadow 0.3s ease' | |
| 323 | - }); | |
| 432 | + // Appearance lives in ai-textfield.css so the buttons get real hover, | |
| 433 | + // active, focus and disabled states instead of inline overrides. | |
| 434 | + var $btnLabel = $('<button type="button" class="ai-generate-btn" title="AI Generate"></button>'); | |
| 324 | 435 | $btnLabel.append( |
| 325 | - $('<img>').attr('src', KingAddonsAiField.icon_url).css({ marginRight: '6px', width: '16px', height: '16px', verticalAlign: 'middle' }), | |
| 436 | + $('<img>').attr({ src: KingAddonsAiField.icon_url, alt: '' }), | |
| 326 | 437 | $('<span>').addClass('ai-generate-btn__label').text('AI Generate') |
| 327 | 438 | ); |
| 328 | - $btnLabel.hover( | |
| 329 | - function() { $(this).css('boxShadow', '0 0 8px rgba(225,203,255,0.9), 0 0 16px rgba(91,3,255,0.7)'); }, | |
| 330 | - function() { $(this).css('boxShadow', 'none'); } | |
| 331 | - ); | |
| 332 | 439 | |
| 333 | 440 | // Create "Change" button |
| 334 | 441 | var $changeBtn = $btnLabel.clone(); |
| 335 | 442 | $changeBtn.removeClass('ai-generate-btn').addClass('ai-change-btn'); |
| @@ -335,14 +442,8 @@ | ||
| 335 | 442 | $changeBtn.removeClass('ai-generate-btn').addClass('ai-change-btn'); |
| 336 | 443 | $changeBtn.find('span.ai-generate-btn__label').text('AI Change'); |
| 337 | 444 | $changeBtn.attr('title', 'AI Change'); |
| 338 | 445 | $changeBtn.find('img').attr('src', KingAddonsAiField.rewrite_icon_url || KingAddonsAiField.plugin_url + '/includes/admin/img/ai-rewrite.svg'); |
| 339 | - | |
| 340 | - // Add hover effect to the change button - cloning doesn't preserve hover handlers | |
| 341 | - $changeBtn.hover( | |
| 342 | - function() { $(this).css('boxShadow', '0 0 8px rgba(225,203,255,0.9), 0 0 16px rgba(91,3,255,0.7)'); }, | |
| 343 | - function() { $(this).css('boxShadow', 'none'); } | |
| 344 | - ); | |
| 345 | 446 | |
| 346 | 447 | // Add buttons to wrapper |
| 347 | 448 | $buttonsWrapper.append($btnLabel).append($changeBtn); |
| 348 | 449 | |
| @@ -390,9 +491,9 @@ | ||
| 390 | 491 | var settingsUrl = window.KingAddonsAiField && window.KingAddonsAiField.settings_url |
| 391 | 492 | ? window.KingAddonsAiField.settings_url |
| 392 | 493 | : '/wp-admin/admin.php?page=king-addons-ai-settings'; |
| 393 | 494 | $errorMessage.html( |
| 394 | - '<span>OpenAI API key is missing or invalid. Please configure your API key in AI Settings.</span>' + | |
| 495 | + '<span>' + ((window.KingAddonsAiField && KingAddonsAiField.missing_key_message) || 'The AI API key is missing or invalid. Please configure your API key in AI Settings.') + '</span>' + | |
| 395 | 496 | '<a href="' + settingsUrl + '" style="color:#0073aa;text-decoration:underline;white-space:nowrap;margin-left:10px;" target="_blank">Settings</a>' |
| 396 | 497 | ); |
| 397 | 498 | $errorMessage.insertAfter($buttonsWrapper).hide().fadeIn(200); |
| 398 | 499 | setTimeout(function() { $errorMessage.fadeOut(200, function() { $(this).remove(); }); }, 5000); |
| @@ -497,9 +598,9 @@ | ||
| 497 | 598 | // Also try to add a class to the iframe body |
| 498 | 599 | try { |
| 499 | 600 | $($iframe[0].contentDocument.body).addClass('king-addons-field-shine'); |
| 500 | 601 | } catch(e) { |
| 501 | - console.error('Could not access iframe body', e); | |
| 602 | + // console.error('Could not access iframe body', e); | |
| 502 | 603 | } |
| 503 | 604 | } |
| 504 | 605 | } else { |
| 505 | 606 | // For regular text inputs and textareas |
| @@ -536,9 +637,9 @@ | ||
| 536 | 637 | $iframe.removeClass('king-addons-wysiwyg-active'); |
| 537 | 638 | try { |
| 538 | 639 | $($iframe[0].contentDocument.body).removeClass('king-addons-field-shine'); |
| 539 | 640 | } catch(e) { |
| 540 | - console.error('Could not access iframe body', e); | |
| 641 | + // console.error('Could not access iframe body', e); | |
| 541 | 642 | } |
| 542 | 643 | } |
| 543 | 644 | } else { |
| 544 | 645 | $field.removeClass('king-addons-field-pulsing king-addons-field-shine'); |
| @@ -588,9 +689,9 @@ | ||
| 588 | 689 | var settingsUrl = window.KingAddonsAiField && window.KingAddonsAiField.settings_url |
| 589 | 690 | ? window.KingAddonsAiField.settings_url |
| 590 | 691 | : '/wp-admin/admin.php?page=king-addons-ai-settings'; |
| 591 | 692 | $errorMessage.html( |
| 592 | - '<span>OpenAI API key is missing or invalid. Please configure your API key in AI Settings.</span>' + | |
| 693 | + '<span>' + ((window.KingAddonsAiField && KingAddonsAiField.missing_key_message) || 'The AI API key is missing or invalid. Please configure your API key in AI Settings.') + '</span>' + | |
| 593 | 694 | '<a href="' + settingsUrl + '" style="color:#0073aa;text-decoration:underline;white-space:nowrap;margin-left:10px;" target="_blank">Settings</a>' |
| 594 | 695 | ); |
| 595 | 696 | $errorMessage.insertAfter($buttonsWrapper).hide().fadeIn(200); |
| 596 | 697 | setTimeout(function() { $errorMessage.fadeOut(200, function() { $(this).remove(); }); }, 5000); |
| @@ -693,9 +794,9 @@ | ||
| 693 | 794 | // Also try to add a class to the iframe body |
| 694 | 795 | try { |
| 695 | 796 | $($iframe[0].contentDocument.body).addClass('king-addons-field-shine'); |
| 696 | 797 | } catch(e) { |
| 697 | - console.error('Could not access iframe body', e); | |
| 798 | + // console.error('Could not access iframe body', e); | |
| 698 | 799 | } |
| 699 | 800 | } |
| 700 | 801 | } else { |
| 701 | 802 | // For regular text inputs and textareas |
| @@ -733,9 +834,9 @@ | ||
| 733 | 834 | $iframe.removeClass('king-addons-wysiwyg-active'); |
| 734 | 835 | try { |
| 735 | 836 | $($iframe[0].contentDocument.body).removeClass('king-addons-field-shine'); |
| 736 | 837 | } catch(e) { |
| 737 | - console.error('Could not access iframe body', e); | |
| 838 | + // console.error('Could not access iframe body', e); | |
| 738 | 839 | } |
| 739 | 840 | } |
| 740 | 841 | } else { |
| 741 | 842 | $field.removeClass('king-addons-field-pulsing king-addons-field-shine'); |
| @@ -769,9 +870,9 @@ | ||
| 769 | 870 | // Log for debugging |
| 770 | 871 | |
| 771 | 872 | // Check if we have a valid editor ID |
| 772 | 873 | if (!editorId) { |
| 773 | - console.error('King Addons: No editor ID found for WYSIWYG field'); | |
| 874 | + // console.error('King Addons: No editor ID found for WYSIWYG field'); | |
| 774 | 875 | if (appendMode) { |
| 775 | 876 | // Append the new content to the original |
| 776 | 877 | $field.val(originalContent + '\n\n' + value); |
| 777 | 878 | } else { |
| @@ -871,9 +972,9 @@ | ||
| 871 | 972 | $field.val(value); |
| 872 | 973 | } |
| 873 | 974 | } |
| 874 | 975 | } catch (e) { |
| 875 | - console.error('King Addons: Error updating WYSIWYG content', e); | |
| 976 | + // console.error('King Addons: Error updating WYSIWYG content', e); | |
| 876 | 977 | // Fallback - set the textarea value directly |
| 877 | 978 | if (appendMode) { |
| 878 | 979 | // Maintain HTML structure in fallback case |
| 879 | 980 | var existingContent = originalContent || ''; |
| @@ -910,9 +1011,9 @@ | ||
| 910 | 1011 | if (isWysiwyg && window.tinymce && tinymce.get($field.attr('id'))) { |
| 911 | 1012 | try { |
| 912 | 1013 | tinymce.get($field.attr('id')).fire('change'); |
| 913 | 1014 | } catch (e) { |
| 914 | - console.error('King Addons: Error triggering TinyMCE change event', e); | |
| 1015 | + // console.error('King Addons: Error triggering TinyMCE change event', e); | |
| 915 | 1016 | } |
| 916 | 1017 | } |
| 917 | 1018 | } |
| 918 | 1019 | |