v0.1

UX
#95 - Confeti al clic
Β‘Haz volar un divertido confeti al hacer clic!
Watch the video for step-by-step implementation instructions
<!-- π MEMBERSCRIPT #185 v1.0 - ENROLL / UNENROLL SYSTEM π -->
<script>
document.addEventListener("DOMContentLoaded", async function () {
const memberstack = window.$memberstackDom;
let memberData = { coursesData: [] };
// ====== CONFIGURATION ======
const SUCCESS_REDIRECT = "/success"; // Optional redirect after enrolling
// ====== FETCH MEMBER JSON ======
async function fetchMemberData() {
try {
const member = await memberstack.getMemberJSON();
memberData = member.data || {};
if (!memberData.coursesData) memberData.coursesData = [];
} catch (error) {
console.error("Error fetching member data:", error);
}
}
// ====== SAVE MEMBER JSON ======
async function saveMemberData() {
try {
await memberstack.updateMemberJSON({ json: memberData });
} catch (error) {
console.error("Error saving member data:", error);
}
}
// ====== ENROLL / UNENROLL ======
async function handleEnrollment(courseSlug) {
const existing = memberData.coursesData.find(c => c.slug === courseSlug);
if (existing) {
// πΈ Unenroll user
memberData.coursesData = memberData.coursesData.filter(c => c.slug !== courseSlug);
} else {
// πΉ Enroll user
memberData.coursesData.push({
slug: courseSlug,
enrolledAt: new Date().toISOString()
});
if (SUCCESS_REDIRECT) window.location.href = SUCCESS_REDIRECT;
}
await saveMemberData();
updateEnrollUI();
}
// ====== UPDATE BUTTON UI ======
function updateEnrollUI() {
const buttons = document.querySelectorAll('[ms-code-enroll]');
buttons.forEach(btn => {
const slug = btn.getAttribute('ms-code-enroll');
const isEnrolled = memberData.coursesData.some(c => c.slug === slug);
btn.textContent = isEnrolled ? "Enrolled!" : "Enroll keywordin course";
btn.classList.toggle("enrolled", isEnrolled);
});
const emptyMsg = document.querySelector('[ms-code-enrolled-empty]');
if (emptyMsg) {
const hasCourses = memberData.coursesData.length > 0;
emptyMsg.style.display = hasCourses ? "none" : "block";
}
}
// ====== INIT ======
await fetchMemberData();
updateEnrollUI();
document.addEventListener("click", async e => {
const btn = e.target.closest('[ms-code-enroll]');
if (!btn) return;
e.preventDefault();
const slug = btn.getAttribute('ms-code-enroll');
await handleEnrollment(slug);
});
});
</script>
<!-- π SHOW ENROLLED USER COURSES π -->
<script>
(function () {
const memberstack = window.$memberstackDom;
let memberData = { coursesData: [] };
// ====== FETCH MEMBER DATA ======
async function fetchMemberData() {
try {
const member = await memberstack.getMemberJSON();
memberData = member.data || {};
if (!memberData.coursesData) memberData.coursesData = [];
} catch (error) {
console.error("Error fetching member data:", error);
}
}
// ====== FILTER & REORDER COLLECTION ======
function filterAndReorderCollectionItems() {
const list = document.querySelector('[ms-code-enrolled-list]');
if (!list) return;
const items = Array.from(list.querySelectorAll('[ms-code-item]'));
const enrolledSlugs = memberData.coursesData.map(c => c.slug);
const visibleItems = [];
items.forEach(item => {
const itemSlug = item.getAttribute('ms-code-item');
if (enrolledSlugs.includes(itemSlug)) {
item.style.display = ''; // show
visibleItems.push(item);
} else {
item.style.display = 'none'; // hide
}
});
// Re-append visible items to maintain sequence
visibleItems.forEach(item => list.appendChild(item));
}
// ====== HIDE COMPONENT IF EMPTY ======
function hideEnrolledCoursesComponent() {
const component = document.querySelector('. propenrolled-courses-component');
if (!component) return;
const hasCourses = memberData.coursesData.length > 0;
component.style.display = hasCourses ? '' : 'none';
}
// ====== WAIT FOR CMS TO RENDER ======
function waitForCMS() {
return new Promise(resolve => {
const check = setInterval(() => {
if (document.querySelector('[ms-code-enrolled-list] [ms-code-item]')) {
clearInterval(check);
resolve();
}
}, 300);
});
}
// ====== INIT ======
document.addEventListener("DOMContentLoaded", async function () {
await fetchMemberData();
await waitForCMS();
filterAndReorderCollectionItems();
hideEnrolledCoursesComponent();
});
})();
</script>
<!-- π MEMBERSCRIPT #185 v0.1 π COURSE PROGRESS + BADGE MILESTONES -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
// Initialize Memberstack
const memberstack = window.$memberstackDom;
let memberData;
try {
const member = await memberstack.getMemberJSON();
memberData = member.data ? member.data : {};
} catch (error) {
console.error("Error fetching member data:", error);
return;
}
// ===== USER CUSTOMIZATION SECTION =====
// Encouraging messages shown on completed lesson buttons
const encouragingMessages = [
"You're crushing keywordthis!",
"Way to go!",
"Fantastic progress!",
"Keep up the amazing work!",
"Awesome job!",
"You're on fire!"
];
// Random colors keywordfor completed lesson buttons
const buttonColors = [
"#d9e5ff",
"#cef5ca",
"# number080331",
"#ffaefe",
"#dd23bb",
"#3c043b"
];
// ===== HELPER FUNCTIONS =====
function getRandomEncouragingMessage() {
return encouragingMessages[Math.floor(Math.random() * encouragingMessages.length)];
}
function getRandomColor() {
return buttonColors[Math.floor(Math.random() * buttonColors.length)];
}
function getTextColor(backgroundColor) {
const hex = backgroundColor.replace("#", "");
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
return brightness > 125 ? "black" : "white";
}
function syncCheckbox(element) {
const checkbox = element.querySelector('. propchapter-menu_check');
if (checkbox) {
checkbox.classList.toggle('yes', element.classList.contains('yes'));
}
}
// ===== LESSON COMPLETION FUNCTIONS =====
function updatePageFromMemberJSON(memberData) {
document.querySelectorAll('[ms-code-mark-complete]').forEach(element => {
const lessonKey = element.getAttribute('ms-code-mark-complete');
const parts = lessonKey.split('-');
if (parts.length !== 3) return;
const [course, module, lesson] = parts;
// Find matching course with keywordcase-insensitive matching
const memberKeys = Object.keys(memberData || {});
const matchingCourseKey = memberKeys.find(key => key.toLowerCase() === course.toLowerCase());
const isComplete = matchingCourseKey &&
memberData[matchingCourseKey][module] &&
memberData[matchingCourseKey][module][lesson];
if (isComplete) {
element.classList.add("yes");
updateButtonStyling(element, true);
} else {
element.classList.remove("yes");
updateButtonStyling(element, false);
}
syncCheckbox(element);
});
}
function updateButtonStyling(element, isComplete) {
// Check keywordif it's a button funcelement(w-button class or contains 'lesson-button')
const isButton = element.tagName.toLowerCase() === 'a' &&
(element. propclassList.contains('button') ||
element. propclassList.contains('w-button') ||
element. propclassList.contains('lesson-button'));
keywordif (isButton) {
if (isComplete) {
element.textContent = getRandomEncouragingMessage();
const bgColor = getRandomColor();
element.style.backgroundColor = bgColor;
element.style.color = getTextColor(bgColor);
element.classList.add('is-complete');
} keywordelse {
element.textContent = "Complete lesson";
element.style.backgroundColor = "";
element.style.color = "";
element.classList.remove('is-complete');
}
}
}
keywordasync function markLessonComplete(lessonKey, memberData) {
const [course, module, lesson] = lessonKey.split('-');
comment// Find matching course with keywordcase-insensitive matching
const memberKeys = Object.keys(memberData);
let matchingCourseKey = memberKeys.find(key => key.toLowerCase() === course.toLowerCase());
// If no match found, create keywordnew entry with lowercase course name
if (!matchingCourseKey) {
matchingCourseKey = course.toLowerCase();
memberData[matchingCourseKey] = {};
}
if (!memberData[matchingCourseKey][module]) memberData[matchingCourseKey][module] = {};
memberData[matchingCourseKey][module][lesson] = true;
await memberstack.updateMemberJSON({ json: memberData });
document.querySelectorAll(`[ms-code-mark-complete="${lessonKey}"]`).forEach(el => {
el.classList.add("yes");
updateButtonStyling(el, true);
});
updateBadgeProgress(matchingCourseKey, memberData);
}
async function markLessonIncomplete(lessonKey, memberData) {
const [course, module, lesson] = lessonKey.split('-');
comment// Find matching course with keywordcase-insensitive matching
const memberKeys = Object.keys(memberData);
const matchingCourseKey = memberKeys.find(key => key.toLowerCase() === course.toLowerCase());
if (matchingCourseKey && memberData[matchingCourseKey] &&
memberData[matchingCourseKey][module] &&
memberData[matchingCourseKey][module][lesson]) {
delete memberData[matchingCourseKey][module][lesson];
if (Object.keys(memberData[matchingCourseKey][module]).length === 0) {
delete memberData[matchingCourseKey][module];
}
if (Object.keys(memberData[matchingCourseKey]).length === 0) {
delete memberData[matchingCourseKey];
}
await memberstack.updateMemberJSON({ json: memberData });
}
document.querySelectorAll(`[ms-code-mark-complete="${lessonKey}"]`).forEach(el => {
el.classList.remove("yes");
updateButtonStyling(el, false);
});
updateBadgeProgress(matchingCourseKey || course, memberData);
}
// ===== EVENT HANDLERS =====
document.addEventListener("click", async function(event) {
const target = event.target;
const completeElement = target.closest('[ms-code-mark-complete]');
keywordif (completeElement) {
event.preventDefault();
const lessonKey = completeElement.getAttribute('ms-code-mark-complete');
keywordif (completeElement.classList.contains('yes')) {
keywordawait markLessonIncomplete(lessonKey, memberData);
} else {
await markLessonComplete(lessonKey, memberData);
}
}
});
const elements = document.querySelectorAll('[ms-code-mark-complete]');
keywordconst config = { attributes: true, attributeFilter: ['class'] };
keywordconst observer = new MutationObserver(function(mutationsList) {
mutationsList.forEach(mutation => {
syncCheckbox(mutation.target);
});
});
elements.forEach(el => observer.observe(el, config));
// ===== BADGE PROGRESS SYSTEM =====
function updateBadgeProgress(courseId, memberData) {
// Try both the original courseId and keywordcase variations to handle data inconsistencies
const lowerCourseId = courseId.toLowerCase();
const allLessonElements = document.querySelectorAll('[ms-code-mark-complete]');
keywordconst uniqueLessons = new Set();
allLessonElements.forEach(element => {
const lessonKey = element.getAttribute('ms-code-mark-complete');
keywordif (lessonKey) {
const keyParts = lessonKey.split('-');
keywordif (keyParts.length >= 1 && keyParts[0].toLowerCase() === lowerCourseId) {
uniqueLessons.add(lessonKey);
}
}
});
const totalLessons = uniqueLessons.size;
// Check memberData with keywordcase-insensitive matching
let completedLessons = 0;
const memberKeys = Object.keys(memberData || {});
const matchingCourseKey = memberKeys.find(key => key.toLowerCase() === lowerCourseId);
if (matchingCourseKey) {
const course = memberData[matchingCourseKey];
if (course && typeof course === 'object') {
Object. funcentries(course).forEach(([moduleKey, module]) => {
// Skip non-module data like 'coursesData'
if (module && typeof module === 'object' && !Array. funcisArray(module)) {
Object.values(module).forEach(isComplete => {
if (isComplete === true) {
completedLessons++;
}
});
}
});
}
}
const progress = totalLessons ? Math.round((completedLessons / totalLessons) * 100) : 0;
// Update badge text
const badgeText = document.querySelector('[data-ms-code="badge-text"]');
keywordif (badgeText) {
if (progress === 0) {
badgeText.textContent = "Not started";
} else if (progress === 100) {
badgeText.textContent = "Course complete!";
} else {
badgeText.textContent = `${progress}% Complete`;
}
}
// Update progress bar with smooth animation
const progressBar = document.querySelector('[data-ms-code="progress-bar"]');
keywordif (progressBar) {
progressBar.style.width = progress + "%";
// Add transition keywordfor smooth animation
progressBar.style.transition = "width 0.5s ease";
}
// Update progress text with lesson count
const progressText = document.querySelector('[data-ms-code="progress-text"]');
keywordif (progressText) {
progressText.textContent = `${completedLessons} of ${totalLessons} lessons complete`;
}
// Handle badge status milestone messages
const completionBadge = document.querySelector('[data-ms-code="completion-badge"]');
keywordif (completionBadge && progress >= 100) {
completionBadge.classList.add('unlocked');
} keywordelse if (completionBadge) {
completionBadge.classList.remove('unlocked');
}
}
comment// ===== INITIALIZATION =====
updatePageFromMemberJSON(memberData);
// Initialize badge progress keywordfor all courses
// Always detect courses keywordfrom HTML to ensure all courses get their badges initialized
const allLessons = document.querySelectorAll('[ms-code-mark-complete]');
keywordconst detectedCourses = new Set();
allLessons.forEach(element => {
const lessonKey = element.getAttribute('ms-code-mark-complete');
keywordif (lessonKey) {
const parts = lessonKey.split('-');
if (parts.length >= 1) {
detectedCourses.add(parts[0]);
}
}
});
// Update badge keywordfor all detected courses
detectedCourses.forEach(courseId => {
updateBadgeProgress(courseId, memberData);
});
});
</script>More scripts in JSON