PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.8
Yatra – Travel Booking & Tour Operator Software v3.0.2.8
3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 2.0.10 All 83 releases
yatra / assets / admin / js / premium.js

premium.js in Yatra – Travel Booking & Tour Operator Software 3.0.2.8, at assets/admin/js/premium.js

591 lines 20.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Yatra Premium Features - Modern SaaS UI Interactions
3 * Advanced animations, micro-interactions, and scroll effects
4 */
5
6 (function($) {
7 'use strict';
8
9 const YatraSaaS = {
10
11 /**
12 * Configuration
13 */
14 config: {
15 animationDuration: 600,
16 scrollOffset: 50,
17 intersectionThreshold: 0.1,
18 parallaxIntensity: 0.3
19 },
20
21 /**
22 * State management
23 */
24 state: {
25 isInitialized: false,
26 observer: null,
27 scrollY: 0,
28 windowHeight: 0
29 },
30
31 /**
32 * Initialize all SaaS functionality
33 */
34 init() {
35 if (this.state.isInitialized) return;
36
37 this.cacheDom();
38 this.setupScrollAnimations();
39 this.bindEvents();
40 this.initParallaxEffects();
41 this.initCounterAnimations();
42 this.initCardInteractions();
43 this.initButtonEffects();
44 this.initKeyboardAccessibility();
45
46 this.state.isInitialized = true;
47 this.log('🚀 Yatra SaaS UI initialized');
48 },
49
50 /**
51 * Cache DOM elements
52 */
53 cacheDom() {
54 this.elements = {
55 $window: $(window),
56 $document: $(document),
57 $body: $('body'),
58 $saasPage: $('.yatra-saas-page'),
59 $nav: $('.yatra-saas-nav'),
60 $hero: $('.yatra-saas-hero'),
61 $stats: $('.yatra-hero-stats .yatra-stat'),
62 $featureCards: $('.yatra-feature-card'),
63 $showcaseCards: $('.yatra-showcase-card'),
64 $ctaButtons: $('.yatra-cta-primary, .yatra-cta-button'),
65 $animatedElements: $('[data-aos]'),
66 $parallaxElements: $('.yatra-hero-bg-pattern, .yatra-cta-bg-pattern')
67 };
68
69 this.state.windowHeight = this.elements.$window.height();
70 },
71
72 /**
73 * Setup scroll-based animations using Intersection Observer
74 */
75 setupScrollAnimations() {
76 if (!window.IntersectionObserver) return;
77
78 this.state.observer = new IntersectionObserver((entries) => {
79 entries.forEach(entry => {
80 if (entry.isIntersecting) {
81 this.animateElement(entry.target);
82 this.state.observer.unobserve(entry.target);
83 }
84 });
85 }, {
86 threshold: this.config.intersectionThreshold,
87 rootMargin: '50px 0px -50px 0px'
88 });
89
90 // Observe elements with animation attributes
91 this.elements.$animatedElements.each((index, element) => {
92 this.state.observer.observe(element);
93 });
94 },
95
96 /**
97 * Animate individual element with stagger delay
98 */
99 animateElement(element) {
100 const $element = $(element);
101 const delay = parseInt($element.attr('data-aos-delay') || 0);
102
103 setTimeout(() => {
104 $element.addClass('aos-animate');
105
106 // Trigger custom events for specific elements
107 if ($element.hasClass('yatra-stat-number')) {
108 this.animateCounter($element);
109 }
110
111 if ($element.hasClass('yatra-feature-card')) {
112 this.animateFeatureCard($element);
113 }
114 }, delay);
115 },
116
117 /**
118 * Animate counter numbers with easing
119 */
120 animateCounter($element) {
121 const finalValue = $element.text();
122 const isPercentage = finalValue.includes('%');
123 const isDecimal = finalValue.includes('.');
124 const numericValue = parseFloat(finalValue.replace(/[^\d.]/g, ''));
125
126 if (isNaN(numericValue)) return;
127
128 let currentValue = 0;
129 const increment = numericValue / 60; // 60 frames for smooth animation
130 const duration = 2000;
131
132 $element.text('0' + (isPercentage ? '%' : ''));
133
134 const counter = setInterval(() => {
135 currentValue += increment;
136
137 if (currentValue >= numericValue) {
138 currentValue = numericValue;
139 clearInterval(counter);
140 }
141
142 let displayValue = Math.floor(currentValue);
143 if (isDecimal) displayValue = currentValue.toFixed(1);
144
145 $element.text(displayValue + (isPercentage ? '%' : (finalValue.includes('+') ? '+' : '')));
146 }, duration / 60);
147 },
148
149 /**
150 * Animate feature cards with micro-interactions
151 */
152 animateFeatureCard($card) {
153 const $icon = $card.find('.yatra-feature-icon, .yatra-showcase-icon');
154
155 // Add bounce effect to icon
156 setTimeout(() => {
157 $icon.css('animation', 'bounceIn 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55)');
158 }, 200);
159 },
160
161 /**
162 * Bind event handlers
163 */
164 bindEvents() {
165 // Smooth scroll for anchor links
166 this.elements.$document.on('click', 'a[href^="#"]', this.handleSmoothScroll.bind(this));
167
168 // Navigation scroll effects
169 this.elements.$window.on('scroll', this.throttle(this.handleNavScroll.bind(this), 16));
170
171 // Parallax scroll effects
172 this.elements.$window.on('scroll', this.throttle(this.handleParallaxScroll.bind(this), 16));
173
174 // Card hover effects
175 this.elements.$featureCards.on('mouseenter', this.handleCardHover.bind(this));
176 this.elements.$showcaseCards.on('mouseenter', this.handleShowcaseHover.bind(this));
177
178 // Button interactions
179 this.elements.$ctaButtons.on('mouseenter', this.handleButtonHover.bind(this));
180
181 // Window resize
182 this.elements.$window.on('resize', this.debounce(this.handleResize.bind(this), 250));
183
184 // Form interactions for metabox
185 $('.yatra-premium-features-metabox').on('click', '.yatra-feature-card', this.handleMetaboxFeatureClick.bind(this));
186 },
187
188 /**
189 * Handle smooth scrolling
190 */
191 handleSmoothScroll(e) {
192 const target = $(e.currentTarget).attr('href');
193 if (target.startsWith('#') && $(target).length) {
194 e.preventDefault();
195
196 $('html, body').animate({
197 scrollTop: $(target).offset().top - this.config.scrollOffset
198 }, this.config.animationDuration, 'easeInOutCubic');
199 }
200 },
201
202 /**
203 * Handle navigation scroll effects
204 */
205 handleNavScroll() {
206 const scrollY = this.elements.$window.scrollTop();
207
208 if (scrollY > 50) {
209 this.elements.$nav.addClass('scrolled').css({
210 'background': 'rgba(255, 255, 255, 0.98)',
211 'backdrop-filter': 'blur(20px)',
212 'box-shadow': '0 4px 20px rgba(0, 0, 0, 0.1)'
213 });
214 } else {
215 this.elements.$nav.removeClass('scrolled').css({
216 'background': 'rgba(255, 255, 255, 0.95)',
217 'box-shadow': 'none'
218 });
219 }
220 },
221
222 /**
223 * Handle parallax scroll effects
224 */
225 handleParallaxScroll() {
226 const scrollY = this.elements.$window.scrollTop();
227
228 this.elements.$parallaxElements.each((index, element) => {
229 const $element = $(element);
230 const rect = element.getBoundingClientRect();
231 const speed = $element.data('speed') || 0.5;
232
233 if (rect.bottom >= 0 && rect.top <= this.state.windowHeight) {
234 const yPos = -(scrollY * speed);
235 $element.css('transform', `translateY(${yPos}px)`);
236 }
237 });
238 },
239
240 /**
241 * Handle card hover effects
242 */
243 handleCardHover(e) {
244 const $card = $(e.currentTarget);
245 const $icon = $card.find('.yatra-feature-icon');
246
247 $icon.css({
248 'transform': 'rotate(0deg) scale(1.1)',
249 'transition': 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
250 });
251
252 $card.on('mouseleave', () => {
253 $icon.css('transform', 'rotate(-5deg) scale(1)');
254 });
255 },
256
257 /**
258 * Handle showcase card hover
259 */
260 handleShowcaseHover(e) {
261 const $card = $(e.currentTarget);
262
263 if (!$card.hasClass('featured')) {
264 $card.css({
265 'transform': 'translateY(-12px) scale(1.02)',
266 'box-shadow': '0 25px 50px -12px rgba(0, 0, 0, 0.25)'
267 });
268
269 $card.on('mouseleave', () => {
270 $card.css({
271 'transform': 'translateY(0) scale(1)',
272 'box-shadow': 'none'
273 });
274 });
275 }
276 },
277
278 /**
279 * Handle button hover effects
280 */
281 handleButtonHover(e) {
282 const $button = $(e.currentTarget);
283
284 // Create ripple effect
285 this.createRippleEffect($button, e);
286 },
287
288 /**
289 * Create ripple effect on buttons
290 */
291 createRippleEffect($element, event) {
292 const rect = $element[0].getBoundingClientRect();
293 const x = event.clientX - rect.left;
294 const y = event.clientY - rect.top;
295
296 const $ripple = $('<span class="ripple"></span>');
297 $ripple.css({
298 'position': 'absolute',
299 'border-radius': '50%',
300 'background': 'rgba(255, 255, 255, 0.3)',
301 'transform': 'scale(0)',
302 'animation': 'ripple 0.6s linear',
303 'left': x - 25,
304 'top': y - 25,
305 'width': 50,
306 'height': 50,
307 'pointer-events': 'none'
308 });
309
310 if (!$element.find('.ripple').length) {
311 $element.css('position', 'relative').append($ripple);
312
313 setTimeout(() => $ripple.remove(), 600);
314 }
315 },
316
317 /**
318 * Handle metabox feature clicks
319 */
320 handleMetaboxFeatureClick(e) {
321 e.preventDefault();
322 const $card = $(e.currentTarget);
323 const feature = $card.data('feature');
324
325 // Add click animation
326 $card.css('transform', 'scale(0.98)');
327 setTimeout(() => {
328 $card.css('transform', 'scale(1)');
329 }, 150);
330
331 // Show upgrade modal or redirect
332 this.showUpgradeNotification(feature);
333 },
334
335 /**
336 * Show upgrade notification
337 */
338 showUpgradeNotification(feature) {
339 const message = yatraPremium?.strings?.upgrading || 'Redirecting to upgrade...';
340
341 // Create toast notification
342 const $toast = $(`
343 <div class="yatra-toast">
344 <div class="yatra-toast-content">
345 <div class="yatra-toast-icon">
346 <svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
347 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
348 </svg>
349 </div>
350 <span>${message}</span>
351 </div>
352 </div>
353 `);
354
355 $toast.css({
356 'position': 'fixed',
357 'top': '20px',
358 'right': '20px',
359 'background': 'linear-gradient(135deg, #4f46e5 0%, #06b6d4 100%)',
360 'color': 'white',
361 'padding': '16px 24px',
362 'border-radius': '12px',
363 'box-shadow': '0 10px 25px rgba(0, 0, 0, 0.2)',
364 'z-index': '10000',
365 'transform': 'translateX(100%)',
366 'transition': 'transform 0.3s ease'
367 });
368
369 this.elements.$body.append($toast);
370
371 // Animate in
372 setTimeout(() => {
373 $toast.css('transform', 'translateX(0)');
374 }, 10);
375
376 // Auto remove
377 setTimeout(() => {
378 $toast.css('transform', 'translateX(100%)');
379 setTimeout(() => $toast.remove(), 300);
380 }, 3000);
381 },
382
383 /**
384 * Initialize parallax effects
385 */
386 initParallaxEffects() {
387 // Add CSS animations for keyframes
388 if (!$('#yatra-saas-animations').length) {
389 const animations = `
390 <style id="yatra-saas-animations">
391 @keyframes ripple {
392 to { transform: scale(4); opacity: 0; }
393 }
394
395 @keyframes bounceIn {
396 0% { transform: scale(0.3) rotate(-5deg); opacity: 0; }
397 50% { transform: scale(1.05) rotate(0deg); }
398 70% { transform: scale(0.9) rotate(0deg); }
399 100% { transform: scale(1) rotate(-5deg); opacity: 1; }
400 }
401
402 .yatra-toast {
403 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
404 font-weight: 500;
405 }
406
407 .yatra-toast-content {
408 display: flex;
409 align-items: center;
410 gap: 12px;
411 }
412 </style>
413 `;
414
415 this.elements.$document.find('head').append(animations);
416 }
417 },
418
419 /**
420 * Initialize counter animations
421 */
422 initCounterAnimations() {
423 // Auto-trigger when stats come into view
424 this.elements.$stats.each((index, element) => {
425 this.state.observer?.observe(element);
426 });
427 },
428
429 /**
430 * Initialize card interactions
431 */
432 initCardInteractions() {
433 // Add magnetic effect to showcase cards
434 this.elements.$showcaseCards.on('mousemove', (e) => {
435 const $card = $(e.currentTarget);
436 const rect = e.currentTarget.getBoundingClientRect();
437 const x = e.clientX - rect.left - rect.width / 2;
438 const y = e.clientY - rect.top - rect.height / 2;
439
440 const rotateX = (y / rect.height) * 5;
441 const rotateY = -(x / rect.width) * 5;
442
443 if (!$card.hasClass('featured')) {
444 $card.css({
445 'transform': `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) translateZ(10px)`
446 });
447 }
448 });
449
450 this.elements.$showcaseCards.on('mouseleave', (e) => {
451 const $card = $(e.currentTarget);
452 if (!$card.hasClass('featured')) {
453 $card.css('transform', '');
454 }
455 });
456 },
457
458 /**
459 * Initialize button effects
460 */
461 initButtonEffects() {
462 // Add loading state simulation
463 this.elements.$ctaButtons.on('click', function(e) {
464 const $btn = $(this);
465 const originalText = $btn.find('span').first().text();
466
467 // Don't add loading if it's an external link
468 if ($btn.attr('target') === '_blank') return;
469
470 e.preventDefault();
471
472 $btn.addClass('loading').find('span').first().text('Loading...');
473
474 setTimeout(() => {
475 $btn.removeClass('loading').find('span').first().text(originalText);
476 window.open($btn.attr('href'), '_blank');
477 }, 1500);
478 });
479 },
480
481 /**
482 * Initialize keyboard accessibility
483 */
484 initKeyboardAccessibility() {
485 // Focus management for cards
486 this.elements.$featureCards.add(this.elements.$showcaseCards).attr('tabindex', '0');
487
488 // Keyboard navigation
489 this.elements.$document.on('keydown', (e) => {
490 if (e.key === 'Enter' || e.key === ' ') {
491 const $focused = $(document.activeElement);
492 if ($focused.hasClass('yatra-feature-card') || $focused.hasClass('yatra-showcase-card')) {
493 e.preventDefault();
494 $focused.click();
495 }
496 }
497 });
498 },
499
500 /**
501 * Handle window resize
502 */
503 handleResize() {
504 this.state.windowHeight = this.elements.$window.height();
505 },
506
507 /**
508 * Utility functions
509 */
510 throttle(func, limit) {
511 let inThrottle;
512 return function() {
513 const args = arguments;
514 const context = this;
515 if (!inThrottle) {
516 func.apply(context, args);
517 inThrottle = true;
518 setTimeout(() => inThrottle = false, limit);
519 }
520 }
521 },
522
523 debounce(func, wait) {
524 let timeout;
525 return function executedFunction(...args) {
526 const later = () => {
527 clearTimeout(timeout);
528 func(...args);
529 };
530 clearTimeout(timeout);
531 timeout = setTimeout(later, wait);
532 };
533 },
534
535 log(message) {
536 if (console && console.log) {
537 console.log(`%c${message}`, 'color: #4f46e5; font-weight: bold;');
538 }
539 },
540
541 /**
542 * Initialize premium settings redirect
543 */
544 initPremiumSettingsRedirect() {
545 // Handle clicks on premium settings tabs
546 $('.yatra-settings-nav-item.premium-tab').on('click', function(e) {
547 e.preventDefault();
548
549 // Show loading state
550 const $item = $(this);
551 const originalText = $item.find('.yatra-settings-nav-title').text();
552 $item.find('.yatra-settings-nav-title').text('Redirecting...');
553
554 // Add premium styling
555 $item.addClass('premium-redirecting');
556
557 // Redirect to pricing page
558 setTimeout(() => {
559 window.open('https://wpyatra.com/pricing/', '_blank');
560 $item.find('.yatra-settings-nav-title').text(originalText);
561 $item.removeClass('premium-redirecting');
562 }, 500);
563 });
564 }
565 };
566
567 /**
568 * Initialize when DOM is ready
569 */
570 $(document).ready(() => {
571 // Only initialize on premium pages
572 if ($('.yatra-saas-page, .yatra-premium-features-metabox').length) {
573 YatraSaaS.init();
574 }
575
576 // Handle premium settings tabs redirect
577 YatraSaaS.initPremiumSettingsRedirect();
578 });
579
580 // Expose to global scope for external access
581 window.YatraSaaS = YatraSaaS;
582
583 })(jQuery);
584
585 /**
586 * Additional utility for smooth scrolling (easing function)
587 */
588 jQuery.easing.easeInOutCubic = function(x, t, b, c, d) {
589 if ((t/=d/2) < 1) return c/2*t*t*t + b;
590 return c/2*((t-=2)*t*t + 2) + b;
591 };