/** * Course Manager — Frontend JS v3.0 */ (function ($) { 'use strict'; // ── Session-based lesson navigation ───────────────── // Navigates to a level/lesson by saving to session then reloading — no URL change function navigateToLesson( level, lesson, callback ) { if ( ! level && ! lesson ) { if ( typeof callback === 'function' ) callback(); return; } $.ajax({ url: CM.ajax_url, method: 'POST', data: { action: 'cm_navigate_lesson', nonce: CM.nonce, level: level || 1, lesson: lesson || 1, }, complete: function() { if ( typeof callback === 'function' ) { callback(); } else { window.location.reload(); } } }); } // ── Sidebar lesson link clicks ──────────────────────── $(document).on('click', '.cm-nav-lesson', function(e) { e.preventDefault(); var level = parseInt( $(this).data('level') ); var lesson = parseInt( $(this).data('lesson') ); navigateToLesson( level, lesson ); }); // ── Enrollment modal ──────────────────────────────── function openEnrollModal() { $('#cm-enroll-overlay').fadeIn(250); } $(document).on('click', '#cm-enroll-trigger, #cm-enroll-trigger-inline', function(e) { e.preventDefault(); openEnrollModal(); }); $(document).on('click', '#cm-enroll-close', function() { $('#cm-enroll-overlay').fadeOut(200); }); $(document).on('click', '#cm-enroll-overlay', function(e) { if ( $(e.target).is('#cm-enroll-overlay') ) $('#cm-enroll-overlay').fadeOut(200); }); $(document).on('click', '#cm-enroll-agree', function() { var $btn = $(this); $btn.prop('disabled', true).text('Enrolling…'); $.ajax({ url: CM.ajax_url, method: 'POST', data: { action: 'cm_enroll_user', nonce: CM.nonce }, success: function(res) { if ( res.success ) { // Reload page — shortcode will re-render in enrolled state window.location.reload(); } else { alert( res.data.message || 'Something went wrong. Please try again.' ); $btn.prop('disabled', false).text('Agree & Enroll'); } }, error: function() { alert('Network error. Please try again.'); $btn.prop('disabled', false).text('Agree & Enroll'); } }); }); // ── Sidebar level accordion ───────────────────────── $(document).on('click', '.cm-level-header', function () { var $group = $(this).closest('.cm-level-group'); if ( $group.hasClass('cm-level-locked') ) return; var $list = $group.find('.cm-lesson-list'); var $chevron = $(this).find('.cm-level-chevron'); var isOpen = $list.hasClass('cm-level-open'); // Close all others $('.cm-lesson-list.cm-level-open').not($list).slideUp(250).removeClass('cm-level-open'); $('.cm-level-chevron').not($chevron).text('▼'); // Toggle this one if ( isOpen ) { $list.slideUp(250).removeClass('cm-level-open'); $chevron.text('▼'); } else { $list.slideDown(250).addClass('cm-level-open'); $chevron.text('▲'); } }); // ── Take Quiz button ──────────────────────────────── $(document).on('click', '#cm-take-quiz-btn', function () { var $btn = $(this); // Guest on gated lesson — show login popup if ( $btn.data('logged-in') === 0 || $btn.data('logged-in') === '0' ) { $('#cm-login-overlay').fadeIn(250); return; } var $section = $('#cm-quiz-section'); if ( $section.hasClass('cm-quiz-open') ) { $section.removeClass('cm-quiz-open'); $btn.html( $btn.data('original') || $btn.html() ); } else { if ( ! $btn.data('original') ) $btn.data('original', $btn.html()); $btn.html('▲ Hide Quiz'); $section.addClass('cm-quiz-open'); $('html, body').animate({ scrollTop: $section.offset().top - 80 }, 400); } }); // ── Login popup close ─────────────────────────────── $(document).on('click', '#cm-login-modal-close', function () { $('#cm-login-overlay').fadeOut(200); }); $(document).on('click', '#cm-login-overlay', function (e) { if ( $(e.target).is('#cm-login-overlay') ) $('#cm-login-overlay').fadeOut(200); }); $(document).on('keydown', function (e) { if ( e.key === 'Escape' ) $('#cm-login-overlay').fadeOut(200); }); // ── No-quiz Complete Lesson button ───────────────── $(document).on('click', '.cm-no-quiz-complete', function () { var $btn = $(this); var lessonId = $btn.data('lesson-id'); var nextUrl = $btn.data('next-url') || ''; var isLast = $btn.data('is-last-in-level') === 1 || $btn.data('is-last-in-level') === '1'; var currentLevel = $btn.data('current-level') || ''; var nextLevel = $btn.data('next-level') || ''; var alreadyDone = $btn.data('already-done') === 1 || $btn.data('already-done') === '1'; function showLevelCompleteOrRedirect() { var navLevel = parseInt( $btn.data('nav-level') || 0 ); var navLesson = parseInt( $btn.data('nav-lesson') || 0 ); if ( isLast ) { $('#cm-level-complete-overlay') .data('nav-level', navLevel) .data('nav-lesson', navLesson) .data('level-title', currentLevel) .data('next-level', nextLevel) .data('quiz-passed', 0) .data('quiz-total', 0); showLevelCompleteModal( navLevel, navLesson, currentLevel, nextLevel, 0, 0 ); } else if ( navLevel ) { navigateToLesson( navLevel, navLesson ); } } if ( alreadyDone ) { showLevelCompleteOrRedirect(); return; } // Mark lesson done: confirmation animation → AJAX → lock → navigate $btn.prop('disabled', true) .removeClass('cm-btn-active-complete') .addClass('cm-btn-confirming') .html(' Saving…'); $.ajax({ url: CM.ajax_url, method: 'POST', data: { action: 'cm_complete_lesson', nonce: CM.nonce, lesson_id: lessonId, }, success: function () { // Brief "done" state before navigating $btn.removeClass('cm-btn-confirming') .addClass('cm-btn-done') .html('✅ Lesson Complete'); // Advance progress bar advanceProgressBar(); // Short pause so user sees the locked state, then navigate/modal setTimeout(function() { showLevelCompleteOrRedirect(); }, 800); }, error: function () { $btn.prop('disabled', false) .removeClass('cm-btn-confirming') .addClass('cm-btn-active-complete') .text('Mark Lesson Complete'); alert('Something went wrong. Please try again.'); }, }); }); // ── Advance master tracker + sidebar bar ──────────── function advanceProgressBar() { // 1. Master tracker dots var $current = $('.cm-mpt-dot--current'); if ( $current.length ) { $current.removeClass('cm-mpt-dot--current').addClass('cm-mpt-dot--done'); $current.find('.cm-mpt-dot-inner').text('✓'); $current.next('.cm-mpt-connector').addClass('cm-mpt-connector--done'); var $row = $current.closest('.cm-mpt-row'); var $badge = $row.find('.cm-mpt-badge'); var doneCnt = $row.find('.cm-mpt-dot--done').length; var totCnt = $row.find('.cm-mpt-dot').length; if ( doneCnt === totCnt ) { $row.removeClass('cm-mpt-row--active').addClass('cm-mpt-row--done'); var levelTitle = $row.find('.cm-mpt-level-label').text(); $badge.addClass('cm-mpt-badge--done').html('✅ ' + levelTitle + ' Complete'); if ( $('.cm-mpt-row').length === $('.cm-mpt-row--done').length ) { $('.cm-mpt-final').addClass('cm-mpt-final--done'); } } else { $badge.find('.cm-mpt-badge-progress').text( doneCnt + '/' + totCnt ); } } // 2. Sidebar progress bar var $wrap = $('.cm-progress-wrap[data-total]'); var $bar = $wrap.find('.cm-progress-bar-fill'); var $label = $wrap.find('.cm-progress-label'); if ( $wrap.length ) { var total = parseInt( $wrap.data('total') ) || 0; var passed = parseInt( $wrap.data('passed') ) || 0; var newPassed = passed + 1; var newPct = total > 0 ? Math.min( 100, Math.round( (newPassed / total) * 100 ) ) : 0; $bar.css('width', newPct + '%'); $label.text( newPassed + '/' + total + ' completed' ); $wrap.data('passed', newPassed); } } // Points config: map level position → points awarded // This mirrors CM_WPLOYALTY_LEVEL_CAMPAIGNS points in rewards.php var CM_LEVEL_POINTS = CM.level_points || { 1: 500, 2: 1000, 3: 1500, 4: 2000 }; // ── Shared level-complete modal launcher ──────────── function showLevelCompleteModal( navLevel, navLesson, levelTitle, nextLevel, qPassed, qTotal ) { var isCourse = ( !navLevel ); var currentLevelPos = parseInt( $('#cm-level-complete-overlay').data('current-level-pos') || 0 ); $('#cm-lc-title').text( levelTitle ); var scoreHtml = ''; if ( qTotal > 0 ) { var pct = Math.round( (qPassed / qTotal) * 100 ); scoreHtml = '
' + qPassed + '/' + qTotal + 'Quizzes Passed
'; scoreHtml += '
' + pct + '%Level Score
'; } else { scoreHtml = '
All Lessons Completed
'; } $('#cm-lc-score').html( scoreHtml ); var $btn = $('#cm-lc-next-btn'); // All buttons go through points popup first via cm-lc-show-points class $btn.removeClass('cm-lc-course-done cm-lc-show-points'); if ( isCourse ) { $btn.text('🎓 Course Complete!').addClass('cm-lc-course-done') .data('nav-level', 0).data('nav-lesson', 0) .data('current-level-pos', currentLevelPos); $('#cm-lc-badge').text('🎓'); } else if ( nextLevel ) { $btn.text('Start ' + nextLevel + ' →').addClass('cm-lc-show-points') .data('nav-level', navLevel).data('nav-lesson', navLesson) .data('current-level-pos', currentLevelPos); } else { $btn.text('Continue →').addClass('cm-lc-show-points') .data('nav-level', navLevel).data('nav-lesson', navLesson) .data('current-level-pos', currentLevelPos); } $('#cm-level-complete-overlay').fadeIn(300); launchConfetti(); } // ── Inline continue button (same-level, no modal) ───── $(document).on('click', '.cm-nav-continue', function(e) { e.preventDefault(); var navLevel = parseInt( $(this).data('nav-level') || 0 ); var navLesson = parseInt( $(this).data('nav-lesson') || 0 ); navigateToLesson( navLevel, navLesson ); }); // ── Option selection ──────────────────────────────── $(document).on('click', '.cm-option-label', function () { var $q = $(this).closest('.cm-question'); $q.find('.cm-option-label').removeClass('cm-selected'); $(this).addClass('cm-selected'); $(this).find('.cm-option-radio').prop('checked', true); }); // ── Quiz submission ───────────────────────────────── $(document).on('submit', '#cm-quiz-form', function (e) { e.preventDefault(); var $form = $(this); var lessonId = $form.data('lesson-id'); var total = parseInt( $form.data('total') ); var nextUrl = $form.data('next-url') || ''; var navLevel = parseInt( $form.data('nav-level') || 0 ); var navLesson = parseInt( $form.data('nav-lesson') || 0 ); var $btn = $('#cm-submit-btn'); var answers = {}; for ( var i = 0; i < total; i++ ) { answers[i] = $form.find('input[name="cm_q_' + i + '"]:checked').val() || ''; } // Validate all answered var unanswered = 0; for ( var j = 0; j < total; j++ ) { if ( ! answers[j] ) unanswered++; } if ( unanswered > 0 ) { showInlineError('Please answer all ' + unanswered + ' remaining question(s) before submitting.'); return; } $btn.prop('disabled', true).html(' Checking…'); $.ajax({ url: CM.ajax_url, method: 'POST', data: { action: 'cm_submit_quiz', nonce: CM.nonce, lesson_id: lessonId, answers: answers, }, success: function (res) { if ( res.success ) { renderResults( res.data, nextUrl ); lockForm(); } else { showInlineError( res.data.message || 'Something went wrong. Please try again.' ); $btn.prop('disabled', false).text('Submit Quiz'); } }, error: function () { showInlineError('Network error. Please check your connection and try again.'); $btn.prop('disabled', false).text('Submit Quiz'); }, }); }); // ── Render results ────────────────────────────────── function renderResults( data, nextUrl ) { var $results = $('#cm-quiz-results'); var $section = $('#cm-quiz-section'); var passed = data.passed; var score = data.correct + '/' + data.total + ' (' + data.score_pct + '%)'; var isLastInLevel = $section.data('is-last-in-level') === 1 || $section.data('is-last-in-level') === '1'; var currentLevel = $section.data('current-level') || ''; var nextLevel = $section.data('next-level') || ''; var levelQuizPassed = parseInt( $section.data('level-quiz-passed') ) || 0; var levelQuizTotal = parseInt( $section.data('level-quiz-total') ) || 0; // Store results for Reveal Answers $('#cm-quiz-form').data('results', data.results); // Mark questions right/wrong (no option highlight yet) $.each(data.results, function (idx, r) { $('.cm-question[data-index="' + idx + '"]').addClass( r.is_correct ? 'cm-correct-q' : 'cm-wrong-q' ); }); var html = ''; if ( passed ) { html += '
✅ You Passed!
'; html += '

You scored ' + score + '. Great work!

'; } else { html += '
❌ Not Quite…
'; html += '

You scored ' + score + '. You need at least ' + data.passing_pct + '% to pass.

'; } html += '
'; if ( passed && isLastInLevel ) { html += ''; } else if ( passed && navLevel ) { html += ''; } else { html += ''; } html += ''; html += '
'; $results.html(html) .addClass( passed ? 'cm-result-pass' : 'cm-result-fail' ) .slideDown(400); $('html, body').animate({ scrollTop: $results.offset().top - 100 }, 500); $('.cm-quiz-footer').slideUp(300); // Store level context for the modal if ( passed && isLastInLevel ) { $('#cm-level-complete-overlay') .data('nav-level', navLevel) .data('nav-lesson', navLesson) .data('level-title', currentLevel) .data('next-level', nextLevel) .data('quiz-passed', levelQuizPassed + ( data.passed ? 1 : 0 ) ) .data('quiz-total', levelQuizTotal); } } // ── Level complete modal trigger (quiz path) ──────── $(document).on('click', '#cm-continue-level-btn', function () { var $overlay = $('#cm-level-complete-overlay'); var navLevel = parseInt( $(this).data('nav-level') || $overlay.data('nav-level') || 0 ); var navLesson = parseInt( $(this).data('nav-lesson') || $overlay.data('nav-lesson') || 0 ); showLevelCompleteModal( navLevel, navLesson, $overlay.data('level-title') || '', $overlay.data('next-level') || '', $overlay.data('quiz-passed') || 0, $overlay.data('quiz-total') || 0 ); }); // ── Points awarded popup ───────────────────────────── // Points map: level position -> points (must match rewards.php config) var cmLevelPoints = CM.level_points || { 1: 500, 2: 1000, 3: 1500, 4: 2000 }; function showPointsPopup( currentLevelPos, navLevel, navLesson, afterCallback ) { var pts = cmLevelPoints[ currentLevelPos ] || 0; // Check if this level was already rewarded (repeat completion) var rewardedMap = {}; try { rewardedMap = JSON.parse( $('#cm-level-complete-overlay').data('level-rewarded') || '{}' ); } catch(e) {} var alreadyRewarded = rewardedMap[ currentLevelPos ] === true; if ( ! pts || alreadyRewarded ) { // Show "first time only" message instead of points amount $('#cm-pts-amount').hide(); $('#cm-pts-icon').text('ℹ️'); $('#cm-pts-label').text('Heads Up'); $('#cm-pts-msg').text( 'Reward points are earned for the first time only. ' + 'You've already earned your points for this level.' ).css('color', '#374151'); $('#cm-pts-close').text('Got it'); $('#cm-points-overlay').fadeIn(300); // No confetti for repeat completions $('#cm-pts-close').data('callback', afterCallback); return; } // First-time completion — show points awarded $('#cm-pts-amount').show().text( '+' + pts.toLocaleString() ); $('#cm-pts-icon').text('🎉'); $('#cm-pts-label').text('Points Awarded!'); $('#cm-pts-msg').text( pts.toLocaleString() + ' points added to your rewards balance' ).css('color', '#6b7280'); $('#cm-pts-close').text('Awesome!'); $('#cm-points-overlay').fadeIn(300); launchPointsConfetti(); // Store callback for dismiss $('#cm-pts-close').data('callback', afterCallback); } $(document).on('click', '#cm-pts-close', function() { $('#cm-points-overlay').fadeOut(200, function() { var cb = $('#cm-pts-close').data('callback'); if ( typeof cb === 'function' ) cb(); }); }); function launchPointsConfetti() { var $c = $('#cm-points-overlay .cm-pts-confetti'); if ( ! $c.length ) { $c = $('
'); $('#cm-points-overlay').prepend($c); } $c.empty(); var colors = ['#fbbf24','#f87171','#34d399','#60a5fa','#c084fc','#fff','#36604e']; for ( var i = 0; i < 60; i++ ) { var $p = $(''); $p.css({ '--x': ( Math.random() * 200 - 100 ) + 'vw', '--y': ( Math.random() * -120 - 20 ) + 'px', '--rot': ( Math.random() * 720 ) + 'deg', '--delay': ( Math.random() * 0.5 ) + 's', '--size': ( Math.random() * 8 + 6 ) + 'px', 'background': colors[ Math.floor( Math.random() * colors.length ) ], 'left': ( Math.random() * 100 ) + '%', }); $c.append($p); } } // Patch: after level modal fades in, hook the next button to show points first // We use a delegated pre-handler on the overlay button $(document).on('click', '.cm-lc-show-points', function(e) { e.preventDefault(); var navLevel = parseInt( $(this).data('nav-level') || 0 ); var navLesson = parseInt( $(this).data('nav-lesson') || 0 ); var currentLvlPos = parseInt( $(this).data('current-level-pos') || 0 ); $('#cm-level-complete-overlay').fadeOut(200, function() { showPointsPopup( currentLvlPos, navLevel, navLesson, function() { if ( navLevel ) { navigateToLesson( navLevel, navLesson ); } else { // Course complete — show course modal var now = new Date(); var opts = { year: 'numeric', month: 'long', day: 'numeric' }; $('#cm-course-complete-date').text( now.toLocaleDateString( undefined, opts ) ); $('#cm-course-complete-overlay').fadeIn(300); launchCourseConfetti(); } }); }); }); // ── Course completion modal ───────────────────────── $(document).on('click', '.cm-lc-course-done', function(e) { e.preventDefault(); // Build date string var now = new Date(); var opts = { year: 'numeric', month: 'long', day: 'numeric' }; var dateStr = now.toLocaleDateString( undefined, opts ); $('#cm-course-complete-date').text( dateStr ); // Hide level modal, show course modal $('#cm-level-complete-overlay').fadeOut(200, function() { $('#cm-course-complete-overlay').fadeIn(300); launchCourseConfetti(); }); }); // Close course complete overlay $(document).on('click', '#cm-course-complete-close', function() { $('#cm-course-complete-overlay').fadeOut(200); }); $(document).on('click', '#cm-course-complete-overlay', function(e) { if ( $(e.target).is('#cm-course-complete-overlay') ) { $('#cm-course-complete-overlay').fadeOut(200); } }); // ── Course confetti (longer, denser burst) ─────────── function launchCourseConfetti() { var $container = $('#cm-course-confetti'); $container.empty(); var colors = ['#36604e','#a8d5b5','#fbbf24','#f87171','#60a5fa','#c084fc','#fff','#fb923c']; for ( var i = 0; i < 120; i++ ) { var $p = $(''); var shape = Math.random() > 0.5 ? '50%' : '2px'; $p.css({ '--x': ( Math.random() * 220 - 110 ) + 'vw', '--y': ( Math.random() * -160 - 20 ) + 'px', '--rot': ( Math.random() * 1080 ) + 'deg', '--delay': ( Math.random() * 1.0 ) + 's', '--size': ( Math.random() * 10 + 6 ) + 'px', 'background': colors[ Math.floor( Math.random() * colors.length ) ], 'left': ( Math.random() * 100 ) + '%', 'border-radius': shape, 'animation-duration': ( Math.random() * 1 + 2 ) + 's', }); $container.append($p); } } // ── Confetti ──────────────────────────────────────── function launchConfetti() { var $container = $('#cm-lc-confetti'); $container.empty(); var colors = ['#36604e','#a8d5b5','#fbbf24','#f87171','#60a5fa','#c084fc','#fff']; for ( var i = 0; i < 72; i++ ) { var $p = $(''); $p.css({ '--x': ( Math.random() * 200 - 100 ) + 'vw', '--y': ( Math.random() * -120 - 20 ) + 'px', '--rot': ( Math.random() * 720 ) + 'deg', '--delay': ( Math.random() * 0.6 ) + 's', '--size': ( Math.random() * 8 + 6 ) + 'px', 'background': colors[ Math.floor( Math.random() * colors.length ) ], 'left': ( Math.random() * 100 ) + '%', }); $container.append($p); } } // ── Reveal / Hide answers ─────────────────────────── $(document).on('click', '#cm-reveal-btn', function () { var results = $('#cm-quiz-form').data('results'); if ( ! results ) return; $.each(results, function (idx, r) { $('.cm-question[data-index="' + idx + '"]').find('.cm-option-label').each(function () { var val = $(this).find('.cm-option-radio').val(); if ( val === r.correct ) $(this).addClass('cm-answer-correct'); if ( val === r.selected && ! r.is_correct ) $(this).addClass('cm-answer-wrong'); }); }); $(this).text('▲ Hide Answers').attr('id', 'cm-hide-btn'); }); $(document).on('click', '#cm-hide-btn', function () { $('.cm-option-label').removeClass('cm-answer-correct cm-answer-wrong'); $(this).text('👁 Reveal Answers').attr('id', 'cm-reveal-btn'); }); // ── Retry ─────────────────────────────────────────── $(document).on('click', '#cm-retry-btn', function () { $('#cm-quiz-form')[0].reset(); $('.cm-option-label').removeClass('cm-selected cm-answer-correct cm-answer-wrong'); $('.cm-question').removeClass('cm-correct-q cm-wrong-q'); $('#cm-quiz-form').removeData('results'); $('#cm-quiz-results').slideUp(300, function () { $(this).html('').removeClass('cm-result-pass cm-result-fail'); }); $('.cm-quiz-footer').slideDown(300); $('#cm-submit-btn').prop('disabled', false).text('Submit Quiz'); $('html, body').animate({ scrollTop: $('#cm-quiz-section').offset().top - 80 }, 400); }); // ── Lock form after submission ────────────────────── function lockForm() { $('#cm-quiz-form .cm-option-radio').prop('disabled', true); $('#cm-quiz-form .cm-option-label').css('cursor', 'default'); } // ── Inline error ──────────────────────────────────── function showInlineError(msg) { var $err = $('#cm-inline-error'); if ( ! $err.length ) { $err = $('

'); $('.cm-quiz-footer').append($err); } $err.text(msg).hide().fadeIn(300); setTimeout(function () { $err.fadeOut(400); }, 4000); } }(jQuery));