PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.13
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.13
2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 2.6.4 2.6.3 2.5.23 All 138 releases
metasync / admin / js / metasync-dashboard.js

metasync-dashboard.js in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.13, at admin/js/metasync-dashboard.js

883 lines 25.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Dashboard-inspired JavaScript enhancements for Metasync plugin
3 * Adds modern interactions and animations matching the dashboard design
4 */
5
6 (function ($) {
7 'use strict';
8
9 $(document).ready(function () {
10
11 // Only run on plugin pages
12 if (!$('.metasync-dashboard-wrap').length) {
13 return;
14 }
15
16 // Fix plugin admin styles (scoped)
17 fixPluginAdminStyles();
18
19 // Add loading states to buttons
20 enhanceButtonInteractions();
21
22 // Add smooth transitions to cards
23 // enhanceCardAnimations(); // Disabled - was too distracting in settings pages
24
25 // Enhance form validation
26 enhanceFormValidation();
27
28 // Add dashboard-style notifications
29 enhanceDashboardNotifications();
30
31 // Enhanced API response handling for Google Console
32 enhanceGoogleConsoleResponse();
33
34 // Enhanced Search Atlas Connect handling
35 enhanceSearchAtlasConnect();
36
37 // Integrate with existing connect functions
38 integrateConnectWithDashboard();
39
40 // Add tooltips to stat cards
41 addStatCardTooltips();
42
43 // Initialize progress bars
44 initializeProgressBars();
45
46 // Fix stat card text overflow
47 fixStatCardTextOverflow();
48
49 // Handle responsive layout
50 handleResponsiveLayout();
51
52 });
53
54 /**
55 * Enhance button interactions with loading states - SCOPED TO PLUGIN
56 */
57 function enhanceButtonInteractions() {
58 $('.metasync-dashboard-wrap .button-primary, .metasync-dashboard-wrap .button-secondary').on('click', function (e) {
59 const $button = $(this);
60
61 // Don't add loading to certain buttons
62 if ($button.hasClass('no-loading') || $button.attr('type') === 'submit') {
63 return;
64 }
65
66 // Add loading state
67 $button.addClass('dashboard-loading');
68 $button.prop('disabled', true);
69
70 // Remove loading state after animation
71 setTimeout(() => {
72 $button.removeClass('dashboard-loading');
73 $button.prop('disabled', false);
74 }, 2000);
75 });
76 }
77
78 /**
79 * Add hover animations to dashboard cards - SCOPED TO PLUGIN
80 */
81 function enhanceCardAnimations() {
82 $('.metasync-dashboard-wrap .dashboard-card').hover(
83 function () {
84 $(this).addClass('card-hover');
85 },
86 function () {
87 $(this).removeClass('card-hover');
88 }
89 );
90
91 // Add stagger animation to stat cards
92 $('.metasync-dashboard-wrap .dashboard-stat-card').each(function (index) {
93 $(this).css('animation-delay', (index * 0.1) + 's');
94 $(this).addClass('fade-in-up');
95 });
96 }
97
98 /**
99 * Enhance form validation with better UX - SCOPED TO PLUGIN
100 */
101 function enhanceFormValidation() {
102 $('.metasync-dashboard-wrap input, .metasync-dashboard-wrap textarea, .metasync-dashboard-wrap select').on('blur', function () {
103 const $input = $(this);
104
105 if ($input.is(':invalid')) {
106 $input.addClass('input-error');
107 showInputError($input, 'Please check this field');
108 } else {
109 $input.removeClass('input-error');
110 hideInputError($input);
111 }
112 });
113
114 $('.metasync-dashboard-wrap input, .metasync-dashboard-wrap textarea, .metasync-dashboard-wrap select').on('input', function () {
115 const $input = $(this);
116 if ($input.hasClass('input-error') && $input.is(':valid')) {
117 $input.removeClass('input-error');
118 hideInputError($input);
119 }
120 });
121 }
122
123 /**
124 * Show input error with dashboard styling
125 */
126 function showInputError($input, message) {
127 const errorId = 'error-' + Math.random().toString(36).substr(2, 9);
128
129 if ($input.siblings('.input-error-message').length === 0) {
130 $input.after(`
131 <div class="input-error-message" id="${errorId}" style="
132 color: var(--dashboard-error);
133 font-size: 12px;
134 margin-top: 4px;
135 opacity: 0;
136 transform: translateY(-10px);
137 transition: all 0.3s ease;
138 ">${message}</div>
139 `);
140
141 setTimeout(() => {
142 $(`#${errorId}`).css({
143 opacity: 1,
144 transform: 'translateY(0)'
145 });
146 }, 10);
147 }
148 }
149
150 /**
151 * Hide input error
152 */
153 function hideInputError($input) {
154 const $error = $input.siblings('.input-error-message');
155 $error.css({
156 opacity: 0,
157 transform: 'translateY(-10px)'
158 });
159
160 setTimeout(() => {
161 $error.remove();
162 }, 300);
163 }
164
165 /**
166 * Enhance dashboard notifications - SCOPED TO PLUGIN
167 */
168 function enhanceDashboardNotifications() {
169 $('.metasync-dashboard-wrap .notice').each(function () {
170 const $notice = $(this);
171 $notice.addClass('dashboard-notice-enhanced');
172
173 // Add close button
174 if (!$notice.find('.notice-dismiss').length) {
175 $notice.append(`
176 <button type="button" class="notice-dismiss">
177 <span class="screen-reader-text">Dismiss this notice.</span>
178 </button>
179 `);
180 }
181 });
182
183 // Handle notice dismiss ONLY within plugin pages
184 $('.metasync-dashboard-wrap').on('click', '.notice-dismiss', function () {
185 $(this).closest('.notice').fadeOut(300);
186 });
187 }
188
189 /**
190 * Enhanced Google Console API response handling
191 */
192 function enhanceGoogleConsoleResponse() {
193 const $responseDiv = $('#metasync-giapi-response');
194
195 if ($responseDiv.length === 0) {
196 return;
197 }
198
199 // Show response with animation when data is received
200 const originalShow = $responseDiv.show;
201 $responseDiv.show = function () {
202 $responseDiv.css('opacity', 0).slideDown(300).animate({ opacity: 1 }, 300);
203 return this;
204 };
205
206 // Enhanced send button for Google Console
207 $('#metasync-btn-send').on('click', function () {
208 const $button = $(this);
209 const originalText = $button.text();
210
211 $button.html('🔄 Sending...').prop('disabled', true);
212
213 // Simulate API call progress (replace with actual API integration)
214 let progress = 0;
215 const progressInterval = setInterval(() => {
216 progress += 10;
217 if (progress >= 100) {
218 clearInterval(progressInterval);
219 $button.html(originalText).prop('disabled', false);
220 }
221 }, 200);
222 });
223 }
224
225 /**
226 * Add tooltips to stat cards - SCOPED TO PLUGIN
227 */
228 function addStatCardTooltips() {
229 $('.metasync-dashboard-wrap .dashboard-stat-card').each(function () {
230 const $card = $(this);
231 const label = $card.find('.dashboard-stat-label').text();
232 const value = $card.find('.dashboard-stat-value').text();
233
234 $card.attr('title', `${label}: ${value}`)
235 .addClass('dashboard-tooltip');
236 });
237 }
238
239 /**
240 * Initialize animated progress bars - SCOPED TO PLUGIN
241 */
242 function initializeProgressBars() {
243 $('.metasync-dashboard-wrap .dashboard-progress-bar').each(function () {
244 const $bar = $(this);
245 const width = $bar.data('width') || '0%';
246
247 // Animate progress bar on scroll into view
248 const observer = new IntersectionObserver((entries) => {
249 entries.forEach(entry => {
250 if (entry.isIntersecting) {
251 setTimeout(() => {
252 $bar.css('width', width);
253 }, 500);
254 observer.unobserve(entry.target);
255 }
256 });
257 });
258
259 observer.observe($bar[0]);
260 });
261 }
262
263 /**
264 * Add success animation to forms after submission
265 */
266 function addSuccessAnimation($element) {
267 $element.addClass('metasync-sa-connect-success-animation');
268 setTimeout(() => {
269 $element.removeClass('metasync-sa-connect-success-animation');
270 }, 600);
271 }
272
273 /**
274 * Enhanced tab navigation - SCOPED TO PLUGIN
275 */
276 $('.metasync-dashboard-wrap .metasync-nav-tab, .metasync-dashboard-wrap .nav-tab').on('click', function (e) {
277 const $tab = $(this);
278
279 // Add loading effect
280 $tab.addClass('tab-loading');
281
282 // Remove loading after page load
283 setTimeout(() => {
284 $tab.removeClass('tab-loading');
285 }, 1000);
286 });
287
288 /**
289 * Set active navigation tab based on current page
290 */
291 function setActiveNavTab() {
292 const currentUrl = window.location.href;
293 const $navTabs = $('.metasync-dashboard-wrap .metasync-nav-tab');
294
295 $navTabs.each(function () {
296 const $tab = $(this);
297 const tabHref = $tab.attr('href');
298
299 if (tabHref && currentUrl.includes(tabHref.split('?')[1])) {
300 $navTabs.removeClass('active');
301 $tab.addClass('active');
302 }
303 });
304 }
305
306 // Set active tab on page load
307 setActiveNavTab();
308
309 /**
310 * Add keyboard navigation for accessibility - SCOPED TO PLUGIN
311 */
312 $('.metasync-dashboard-wrap .dashboard-card').attr('tabindex', '0').on('keydown', function (e) {
313 if (e.key === 'Enter' || e.key === ' ') {
314 $(this).click();
315 }
316 });
317
318 /**
319 * Dark mode toggle functionality (if needed)
320 */
321 function addDarkModeToggle() {
322 // This can be expanded if manual dark mode toggle is needed
323 // Currently the dashboard is always dark themed
324 }
325
326 /**
327 * Responsive navigation improvements
328 */
329 function enhanceResponsiveNavigation() {
330 const $navTabs = $('.nav-tab-wrapper');
331
332 if ($navTabs.length && window.innerWidth < 768) {
333 $navTabs.addClass('nav-mobile');
334 }
335
336 $(window).on('resize', function () {
337 if (window.innerWidth < 768) {
338 $navTabs.addClass('nav-mobile');
339 } else {
340 $navTabs.removeClass('nav-mobile');
341 }
342 });
343 }
344
345 // Initialize responsive enhancements
346 enhanceResponsiveNavigation();
347
348 /**
349 * Fix WordPress admin styles - SCOPED TO PLUGIN ONLY
350 */
351 function fixPluginAdminStyles() {
352 const $pluginWrap = $('.metasync-dashboard-wrap');
353
354 if ($pluginWrap.length === 0) {
355 return;
356 }
357
358 // Handle WordPress notices ONLY within plugin pages
359 $pluginWrap.find('.notice, .error, .updated').each(function () {
360 $(this).addClass('dashboard-notice');
361 });
362
363 // Fix nav tabs background ONLY within plugin pages
364 $pluginWrap.find('.nav-tab-wrapper').css('background', 'transparent');
365 }
366
367 /**
368 * Fix stat card text overflow issues - SCOPED TO PLUGIN
369 */
370 function fixStatCardTextOverflow() {
371 $('.metasync-dashboard-wrap .dashboard-stat-card').each(function () {
372 const $card = $(this);
373 const $value = $card.find('.dashboard-stat-value');
374 const $label = $card.find('.dashboard-stat-label');
375
376 // Handle long theme names or values
377 if ($value.text().length > 20) {
378 $value.addClass('long-text');
379 $card.attr('title', $value.text());
380 }
381
382 // Adjust font size based on text length
383 if ($value.text().length > 15) {
384 $value.css('font-size', '1.4rem');
385 }
386 if ($value.text().length > 25) {
387 $value.css('font-size', '1.2rem');
388 }
389 });
390 }
391
392 /**
393 * Handle responsive layout changes - SCOPED TO PLUGIN
394 */
395 function handleResponsiveLayout() {
396 function adjustLayout() {
397 const $pluginWrap = $('.metasync-dashboard-wrap');
398 if ($pluginWrap.length === 0) {
399 return;
400 }
401
402 const windowWidth = $(window).width();
403
404 if (windowWidth < 768) {
405 $pluginWrap.find('.dashboard-stats').addClass('mobile-layout');
406 $pluginWrap.find('.metasync-sa-connect-buttons').addClass('mobile-buttons');
407 } else {
408 $pluginWrap.find('.dashboard-stats').removeClass('mobile-layout');
409 $pluginWrap.find('.metasync-sa-connect-buttons').removeClass('mobile-buttons');
410 }
411
412 if (windowWidth < 1200) {
413 $pluginWrap.find('.dashboard-card').addClass('compact-layout');
414 } else {
415 $pluginWrap.find('.dashboard-card').removeClass('compact-layout');
416 }
417 }
418
419 // Run on load
420 adjustLayout();
421
422 // Run on resize
423 $(window).on('resize', debounce(adjustLayout, 250));
424 }
425
426 /**
427 * Debounce function for performance
428 */
429 function debounce(func, wait) {
430 let timeout;
431 return function executedFunction(...args) {
432 const later = () => {
433 clearTimeout(timeout);
434 func(...args);
435 };
436 clearTimeout(timeout);
437 timeout = setTimeout(later, wait);
438 };
439 }
440
441 /**
442 * Enhanced Search Atlas Connect handling - SCOPED TO PLUGIN
443 *
444 * Handles 1-click connect to retrieve Search Atlas API key and Otto UUID.
445 * Does NOT create WordPress login sessions.
446 */
447 function enhanceSearchAtlasConnect() {
448 const $connectContainer = $('.metasync-dashboard-wrap .metasync-sa-connect-container');
449 const $connectBtn = $('.metasync-dashboard-wrap #connect-searchatlas-btn');
450 const $retryBtn = $('.metasync-dashboard-wrap .metasync-sa-connect-retry-btn');
451
452 if ($connectContainer.length === 0) {
453 return;
454 }
455
456 // Handle connect button clicks
457 $connectBtn.on('click', function () {
458 const $btn = $(this);
459 const originalText = $btn.text();
460
461 // Add loading state
462 $btn.prop('disabled', true)
463 .html('<span class="metasync-sa-connect-loading"></span> Opening Authentication...')
464 .addClass('dashboard-loading');
465
466 // Show progress indicator
467 showConnectProgress();
468
469 // Start progress timer
470 startConnectTimer();
471
472 // Remove any existing status messages
473 $('.metasync-sa-connect-status').remove();
474 });
475
476 // Handle retry button clicks
477 $(document).on('click', '.metasync-sa-connect-retry-btn', function () {
478 const $btn = $(this);
479
480 // Remove status messages
481 $('.metasync-sa-connect-status').remove();
482
483 // Re-enable connect button
484 $connectBtn.prop('disabled', false)
485 .html('🔄 Re-authenticate with ' + (window.MetasyncConfig && window.MetasyncConfig.pluginName ? window.MetasyncConfig.pluginName : 'Search Atlas'))
486 .removeClass('dashboard-loading');
487
488 // Hide progress
489 $('.metasync-sa-connect-progress').fadeOut(300);
490 });
491
492 // Handle authentication tips toggle for custom elements only (exclude summary elements)
493 $(document).on('click', '.metasync-sa-connect-tips-toggle:not(summary)', function (e) {
494 const $toggle = $(this);
495 const $content = $toggle.siblings('.metasync-sa-connect-tips-content');
496 const $icon = $toggle.find('.tips-icon');
497
498 $content.slideToggle(300);
499 $icon.text($content.is(':visible') ? '' : '');
500 });
501
502 // Handle details element with custom animation
503 $(document).on('click', 'details.metasync-sa-connect-tips summary', function (e) {
504
505 const $details = $(this).closest('details');
506 if ($details.prop('open')) {
507 // Is open, about to close
508 e.preventDefault();
509 $details.find('> div').slideUp(400, () => {
510 $details.removeAttr('open');
511 });
512 } else {
513 // Is closed, about to open. Let it open, then animate.
514 // The 'toggle' event is another way to handle this.
515 e.preventDefault();
516 $details.attr('open', 'open');
517 $details.find('> div').hide();
518 $details.find('> div').slideDown(400);
519
520 }
521 });
522 }
523
524 /**
525 * Show connect progress indicator
526 */
527 function showConnectProgress() {
528 const progressHTML = `
529 <div class="metasync-sa-connect-progress">
530 <div class="metasync-sa-connect-progress-header">
531 <span>🔒 Authentication in Progress</span>
532 <span class="metasync-sa-connect-progress-time">0min elapsed, 5min remaining</span>
533 </div>
534 <div class="metasync-sa-connect-progress-bar">
535 <div class="metasync-sa-connect-progress-fill" style="width: 0%"></div>
536 </div>
537 <div class="metasync-sa-connect-progress-text">Please complete the connect flow in the popup window...</div>
538 </div>
539 `;
540
541 $('.metasync-sa-connect-container').append(progressHTML);
542 }
543
544 /**
545 * Start connect timer and progress animation
546 */
547 function startConnectTimer() {
548 let elapsed = 0;
549 const maxTime = 300; // 5 minutes in seconds
550
551 const timer = setInterval(() => {
552 elapsed += 1;
553 const remaining = maxTime - elapsed;
554 const progressPercent = (elapsed / maxTime) * 100;
555
556 // Update progress bar
557 $('.metasync-sa-connect-progress-fill').css('width', progressPercent + '%');
558
559 // Update time display
560 const elapsedMin = Math.floor(elapsed / 60);
561 const remainingMin = Math.floor(remaining / 60);
562 $('.metasync-sa-connect-progress-time').text(`${elapsedMin}min elapsed, ${remainingMin}min remaining`);
563
564 // Check if time is up
565 if (elapsed >= maxTime) {
566 clearInterval(timer);
567 showConnectTimeout();
568 }
569 }, 1000);
570
571 // Store timer reference for cleanup
572 $('.metasync-sa-connect-container').data('connect-timer', timer);
573 }
574
575 /**
576 * Show connect timeout message
577 */
578 function showConnectTimeout() {
579 showConnectStatus('error', 'Authentication Timeout', 'The authentication window timed out. You can try again when you\'re ready.');
580 }
581
582 /**
583 * Show connect status message
584 */
585 function showConnectStatus(type, title, message) {
586 const iconMap = {
587 success: '�
588 ',
589 error: '⚠️',
590 info: 'ℹ️',
591 warning: '⚠️'
592 };
593
594 const statusHTML = `
595 <div class="metasync-sa-connect-status ${type}">
596 <div class="metasync-sa-connect-status-content">
597 <div class="metasync-sa-connect-status-title">
598 ${iconMap[type]} ${title}
599 </div>
600 <div class="metasync-sa-connect-status-message">${message}</div>
601 ${type === 'error' ? '<button class="metasync-sa-connect-retry-btn">🔄 Try Again</button>' : ''}
602 </div>
603 </div>
604 `;
605
606 // Remove existing status
607 $('.metasync-sa-connect-status').remove();
608
609 // Add new status
610 $('.metasync-sa-connect-container').append(statusHTML);
611
612 // Clear timer and progress
613 const timer = $('.metasync-sa-connect-container').data('connect-timer');
614 if (timer) {
615 clearInterval(timer);
616 }
617
618 // Reset button state
619 $('#connect-searchatlas-btn').prop('disabled', false)
620 .html('🔄 Re-authenticate with ' + (window.MetasyncConfig && window.MetasyncConfig.pluginName ? window.MetasyncConfig.pluginName : 'Search Atlas'))
621 .removeClass('dashboard-loading');
622
623 // Hide progress after delay
624 setTimeout(() => {
625 $('.metasync-sa-connect-progress').fadeOut(300);
626 }, 1000);
627 }
628
629 /**
630 * Integrate with existing Search Atlas connect functions
631 */
632 function integrateConnectWithDashboard() {
633 // Override existing SSO status display to use dashboard styling
634 if (typeof window.showConnectStatus !== 'undefined') {
635 const originalShowConnectStatus = window.showConnectStatus;
636
637 window.showConnectStatus = function (type, title, message, actions) {
638 // If we're on a dashboard page, use our enhanced styling
639 if ($('.metasync-dashboard-wrap').length > 0) {
640 showConnectStatus(type, title, message);
641 } else {
642 // Fallback to original function for non-dashboard pages
643 originalShowConnectStatus.call(this, type, title, message, actions);
644 }
645 };
646 }
647
648 // Enhance existing SSO containers with dashboard styling
649 $('.metasync-sa-connect-container').each(function () {
650 if (!$(this).closest('.metasync-dashboard-wrap').length) {
651 return; // Only enhance containers within dashboard pages
652 }
653
654 // Add dashboard classes to existing elements
655 $(this).addClass('dashboard-enhanced');
656
657 // Style existing status messages
658 $(this).find('.metasync-sa-connect-status').each(function () {
659 $(this).addClass('dashboard-styled');
660 });
661
662 // Style existing progress elements
663 $(this).find('.metasync-sa-connect-progress').each(function () {
664 $(this).addClass('dashboard-styled');
665 });
666
667 // Style existing authentication tips (PHP-generated details element)
668 // Check for both class-based tips AND details elements to prevent duplication
669 const hasExistingTips = $(this).find('.metasync-sa-connect-tips').length > 0 ||
670 $(this).find('details summary').filter(function () {
671 return $(this).text().includes('Authentication Tips');
672 }).length > 0;
673
674 if (!hasExistingTips) {
675 // No existing tips found, this shouldn't happen but just in case
676 console.log('No authentication tips found in connect container');
677 } else {
678 // Style existing PHP-generated details element
679 $(this).find('details').each(function () {
680 const $details = $(this);
681 if ($details.find('summary').text().includes('Authentication Tips')) {
682 // Add dashboard styling to existing tips
683 $details.addClass('metasync-sa-connect-tips dashboard-styled');
684 $details.find('div').addClass('metasync-sa-connect-tips-content');
685 }
686 });
687 }
688 });
689
690 // Monitor for dynamically added SSO elements
691 const observer = new MutationObserver(function (mutations) {
692 mutations.forEach(function (mutation) {
693 mutation.addedNodes.forEach(function (node) {
694 if (node.nodeType === 1) { // Element node
695 const $node = $(node);
696
697 // Style new SSO status messages
698 if ($node.hasClass('metasync-sa-connect-status')) {
699 $node.addClass('dashboard-styled');
700 }
701
702 // Style new progress indicators
703 if ($node.hasClass('metasync-sa-connect-progress')) {
704 $node.addClass('dashboard-styled');
705 }
706
707 // Check for nested SSO elements
708 $node.find('.metasync-sa-connect-status, .metasync-sa-connect-progress').addClass('dashboard-styled');
709 }
710 });
711 });
712 });
713
714 // Start observing
715 $('.metasync-dashboard-wrap').each(function () {
716 observer.observe(this, {
717 childList: true,
718 subtree: true
719 });
720 });
721 }
722
723 // Expose functions globally for SSO integration
724 window.metasyncDashboard = {
725 showConnectStatus: showConnectStatus,
726 showConnectProgress: showConnectProgress,
727 startConnectTimer: startConnectTimer,
728 integrateConnectWithDashboard: integrateConnectWithDashboard
729 };
730
731 })(jQuery);
732
733 // CSS animations for JavaScript enhancements
734 const additionalCSS = `
735 <style>
736 .fade-in-up {
737 animation: fadeInUp 0.6s ease forwards;
738 }
739
740 @keyframes fadeInUp {
741 from {
742 opacity: 0;
743 transform: translateY(20px);
744 }
745 to {
746 opacity: 1;
747 transform: translateY(0);
748 }
749 }
750
751 .card-hover {
752 /* Removed translateY animation - was too distracting in settings pages */
753 /* transform: translateY(-4px) !important; */
754 /* transition: transform 0.3s ease !important; */
755 }
756
757 .input-error {
758 border-color: var(--dashboard-error) !important;
759 box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1) !important;
760 }
761
762 .dashboard-notice-enhanced {
763 backdrop-filter: blur(10px);
764 animation: slideInRight 0.3s ease;
765 }
766
767 @keyframes slideInRight {
768 from {
769 opacity: 0;
770 transform: translateX(20px);
771 }
772 to {
773 opacity: 1;
774 transform: translateX(0);
775 }
776 }
777
778 .tab-loading {
779 position: relative;
780 overflow: hidden;
781 }
782
783 .tab-loading::after {
784 content: '';
785 position: absolute;
786 top: 0;
787 left: -100%;
788 width: 100%;
789 height: 100%;
790 background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
791 animation: shimmer 1s ease-in-out;
792 }
793
794 /* Enhanced loading effect for new nav tabs */
795 .metasync-nav-tab.tab-loading {
796 opacity: 0.7;
797 }
798
799 .metasync-nav-tab.tab-loading .tab-icon {
800 animation: spin 1s linear infinite;
801 display: inline-flex;
802 align-items: center;
803 justify-content: center;
804 transform-origin: center;
805 }
806
807 /* Animation for existing authentication tips */
808 .tips-animated {
809 animation: slideInTips 0.3s ease;
810 }
811
812 @keyframes slideInTips {
813 from {
814 opacity: 0;
815 transform: translateY(-10px);
816 }
817 to {
818 opacity: 1;
819 transform: translateY(0);
820 }
821 }
822
823 @keyframes shimmer {
824 0% { left: -100%; }
825 100% { left: 100%; }
826 }
827
828 .nav-mobile {
829 flex-direction: column !important;
830 gap: 8px !important;
831 }
832
833 .nav-mobile .nav-tab {
834 text-align: center;
835 width: 100%;
836 }
837
838 /* Additional responsive classes */
839 .mobile-layout .dashboard-stat-card {
840 margin-bottom: 12px;
841 }
842
843 .mobile-buttons {
844 flex-direction: column;
845 align-items: stretch;
846 }
847
848 .mobile-buttons button {
849 width: 100%;
850 margin: 4px 0 !important;
851 }
852
853 .compact-layout {
854 padding: 16px !important;
855 }
856
857 .long-text {
858 word-break: break-word;
859 hyphens: auto;
860 }
861
862 /* Dashboard notice styling */
863 .dashboard-notice {
864 backdrop-filter: blur(10px);
865 animation: slideInNotice 0.3s ease;
866 }
867
868 @keyframes slideInNotice {
869 from {
870 opacity: 0;
871 transform: translateX(-20px);
872 }
873 to {
874 opacity: 1;
875 transform: translateX(0);
876 }
877 }
878 </style>
879 `;
880
881 // Inject additional CSS
882 document.head.insertAdjacentHTML('beforeend', additionalCSS);
883